Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1// Bicameral Engine — ランタイムの入口。
2//
3// 引数が無ければ窓を開いてフレームのループを回す(frame/frame_loop.h。T-0004・T-0012)。
4// bicameral --caps GPU の対応状況を表示して終了
5// --frames <n> n フレームで終える(自動の確認用。既定は窓を閉じるまで)
6// --no-vsync 垂直同期を待たずに Present する
7// --latency <2|3> CPU が GPU より先に進めるフレームの数(既定 2)
8// --target-fps <f> 重いときに描画が保つ fps(ADR-0011。30〜1000、既定 60)。予算 = 1000 / f − 描画 − 余裕
9// --sim-load <n> 重さの試験: 刻みに結果に入らない計算を n 回足す(世界が遅くなるのを見る。D-202)
10// --sim-split <k> 重さの試験を k 個の単位に分ける(1 フレームに予算ぶんの単位だけ投げる。既定 1)
11// --render-normal 描画のキューの優先度を NORMAL にする(既定 HIGH。比較用)
12// --auto-click 決まった場所を自動でクリックする(人がいない確認でイベントの流れを通す)
13// --auto-push 60 フレーム目に積み木の 5 段目を押す(人がいない確認で押すコマンドの流れを通す。T-0098)
14// --auto-ignite 最初のフレームに木箱の壁に 1 回だけ火をつける(最初の刻みに載るので、どの実行でも同じ世界。
15// --screenshot-tick の画像の比較用。T-0025)
16// --no-physics 仮の世界に物理(積み木)を入れない(既定は入れる。窓では Shift + 左クリックで押す。T-0098)
17// --check-physics CPU の物理を並べて走らせ、刻みごとの物のハッシュを突き合わせる(調べる用)
18// --physics-compute 物理の広域の選別を Compute で(既定は Work Graph。比べる用)
19// --record <path> 窓の操作(コマンド)と刻みごとのハッシュを再生ファイルに書く(終わるとき。save/replay_file)
20// --replay <path> 再生ファイルのコマンドで世界を進め、刻みごとのハッシュを突き合わせる。
21// 最後のハッシュまで確かめたら終わる(全部一致で 0、違えば 1)。窓のクリックは無視する
22// --view <volume|mip|slice> 最初のデバッグ表示(既定 volume。窓では 1・2・3 で切り替え。render/debug_view_controller.h)
23// --camera <yaw>,<pitch>,<距離> 最初のカメラ(度・度・セル。既定 35,25,150。0,0,80 で z = 32 の面を正面から)
24// --screenshot <path> 最後のフレームを BMP に書く(--frames と一緒に使う。render/screenshot.h)
25// --screenshot-tick <t> 刻み t の始めで世界を止め、S(t) を描いた画面を --screenshot に書いて終える(決まった画面。
26// 画像の比較用。--frames はそこまでの上限。tools/image_compare。T-0025)
27// --peek <x,y,z> 起動時からそのセルを覗き窓で覗く(影の鎖 k = 1〜9。窓では P。sim/probe_peek.h。T-0096)
28// --peek-depth <k> 覗き窓で潜る段(0〜9。カメラが点に寄る。窓では PageDown・PageUp)
29// --trace <path> 伝導の連鎖のトレースを刻みごとの木にして、終わるときに書く(sim/probe_trace.h。T-0087)
30// --trace-ticks <始め>:<終わり> トレースする刻み [始め, 終わり)(既定: 全部)
31// --trace-cells <x,y,z>:<x,y,z> トレースするセルの箱 [最小, 最大)(既定: 全部。そのセルを含むブロックを記録する)
32// --trace-dir <path> 窓の T で集めたトレースを書くフォルダ(既定: exe の横の traces/。T-0088)
33// --auto-trace 30 フレーム目に T を押す(人がいない確認で T の流れを通す。--auto-click と一緒に)
34// --editor エディタの殻(ImGui の時間の操作・状態の表示。Space = 止める・N = 1 刻み)を重ねる(T-0023)
35// --auto-time 決まったフレームで止める・1 刻み・速さを操作し、止まった・1 刻みずつ進んだかを確かめる(T-0023)
36// --save-points <n> 巻き戻しの保存点の数(--editor の既定は 6。0 なら巻き戻さない。1 つ約 33 MiB の VRAM。T-0143)
37// --save-interval <t> 保存点へ写す間隔(刻み。既定 120)
38// --auto-rewind 保存点が 2 つできたら、古い方へ 1 回巻き戻す(人がいない確認。--replay と一緒にハッシュ列を確かめる)
39// --packages <path> 反応表のパッケージのフォルダ(直下のフォルダが 1 つずつパッケージ。既定: exe の横の data/packages。
40// 読めない・検査で落ちたら起動しない。script/reaction_table_loader.h。T-0157)
41// --auto-lab 実験室の箱に木を置いて火を付け、GPU と CPU の一致と記録の再生を確かめる(--editor と一緒に。T-0142)
42// --warp WARP(ソフトウェアの D3D12)で走らせる
43// --log-dir <path> ログファイルの置き場所(既定: exe の横の logs/。ADR-0006)
44// --log-level <trace|debug|info|warning|error|fatal>
45// これより軽いログを捨てる(既定: Debug ビルドは debug、Release は info)
46//
47// 引数は wmain で UTF-16 のまま受け取る(main の char** は ANSI コードページなので日本語のパスが壊れる)。
48// 終わるときは必ず SingletonFinalizer::Finalize() を通す(ログを最後に閉じ、ファイルへ書き出すため)。
49#include <array>
50#include <charconv>
51#include <optional>
52
53#include "common/probe_sim.hlsli"
54#include "core/aliases.h"
55#include "core/log.h"
56#include "core/log_sinks.h"
57#include "core/paths.h"
58#include "core/singleton.h"
59#include "core/unicode.h"
60#include "frame/frame_loop.h"
61#include "platform/caps.h"
62#include "sim/probe_trace.h"
63
64namespace {
65
66 using namespace bicameral;
67
68 struct Options {
69 bool runCaps = false;
71 fs::path logDirectory; // 空なら DefaultLogDirectory()
73 bool hasLogLevel = false;
74
75 // --- 連鎖のトレースの範囲(--trace-ticks・--trace-cells。ParseOptions の最後に frameLoop.trace にする)---
76 std::array<uint64_t, 2> traceTicks = {0, UINT64_MAX};
77 std::array<uint64_t, 6> traceCells = {
78 0, 0, 0, sim::PROBE_GRID_SIZE, sim::PROBE_GRID_SIZE, sim::PROBE_GRID_SIZE};
79 };
80
81 // --- コマンドライン ---
82
83 // 10 進の整数。範囲外・数でなければ std::nullopt
84 std::optional<uint32_t> ParseCount(std::wstring_view text, uint32_t maximum) {
85 uint32_t value = 0;
86 const std::string utf8 = ToUtf8(text);
87 const auto [end, error] = std::from_chars(utf8.data(), utf8.data() + utf8.size(), value);
88 if (error != std::errc{} || end != utf8.data() + utf8.size() || value > maximum)
89 return std::nullopt;
90
91 return value;
92 }
93
94 // "a:b" や "x,y,z:x,y,z" のような 10 進の整数の並び(区切りは , か :)。数が N 個でなければ std::nullopt
95 template <size_t N>
96 std::optional<std::array<uint64_t, N>> ParseNumbers(std::wstring_view text) {
97 const std::string utf8 = ToUtf8(text);
98 std::array<uint64_t, N> values{};
99 const char* cursor = utf8.data();
100 const char* end = utf8.data() + utf8.size();
101
102 for (size_t index = 0; index < N; ++index) {
103 const auto [next, error] = std::from_chars(cursor, end, values[index]);
104 if (error != std::errc{})
105 return std::nullopt;
106
107 const bool last = index + 1 == N;
108 if (last ? next != end : (next == end || (*next != ',' && *next != ':')))
109 return std::nullopt;
110
111 cursor = next + 1;
112 }
113
114 return values;
115 }
116
117 // トレースの範囲の引数(--trace-ticks・--trace-cells)
118 std::expected<void, std::string> ParseTraceRange(std::wstring_view argument, std::wstring_view text,
119 Options& options) {
120 if (argument == L"--trace-ticks") {
121 const auto ticks = ParseNumbers<2>(text);
122 if (!ticks || (*ticks)[0] >= (*ticks)[1])
123 return std::unexpected(std::format("--trace-ticks の値が不正: {}(例: 10:20)", ToUtf8(text)));
124
125 options.traceTicks = *ticks;
126
127 return {};
128 }
129
130 const auto cells = ParseNumbers<6>(text);
131 const bool ordered = cells && (*cells)[0] < (*cells)[3] && (*cells)[1] < (*cells)[4] &&
132 (*cells)[2] < (*cells)[5];
133 if (!ordered)
134 return std::unexpected(std::format("--trace-cells の値が不正: {}(例: 0,0,28:64,64,36)", ToUtf8(text)));
135
136 options.traceCells = *cells;
137
138 return {};
139 }
140
141 // --trace のときだけ、範囲をフレームのループのトレースの範囲にする(セルの箱 → ブロックの座標の箱)
142 void ApplyTraceRange(Options& options) {
143 if (options.frameLoop.tracePath.empty())
144 return;
145
146 const auto cell = [&options](size_t index) {
147 return static_cast<uint32_t>(std::min<uint64_t>(options.traceCells[index], sim::PROBE_GRID_SIZE));
148 };
149
151 options.traceTicks[0], options.traceTicks[1], {cell(0), cell(1), cell(2)}, {cell(3), cell(4), cell(5)},
153 }
154
155 // "yaw,pitch,距離"(小数でよい)。距離は正
156 std::optional<render::OrbitCameraState> ParseCamera(std::wstring_view text) {
157 const std::string utf8 = ToUtf8(text);
158 std::array<float, 3> values{};
159 const char* cursor = utf8.data();
160 const char* end = utf8.data() + utf8.size();
161
162 for (size_t index = 0; index < values.size(); ++index) {
163 const auto [next, error] = std::from_chars(cursor, end, values[index]);
164 if (error != std::errc{})
165 return std::nullopt;
166 const bool last = index + 1 == values.size();
167 if (last ? next != end : (next == end || *next != ','))
168 return std::nullopt;
169
170 cursor = next + 1;
171 }
172
173 if (!(values[2] > 0.0f))
174 return std::nullopt;
175
177 camera.yawDegrees = values[0];
178 camera.pitchDegrees = values[1];
179 camera.distance = values[2];
180
181 return camera;
182 }
183
184 // 覗き窓の引数(--peek・--peek-depth)
185 std::expected<void, std::string> ParsePeekOption(std::wstring_view argument, std::wstring_view text,
187 if (argument == L"--peek-depth") {
188 const auto depth = ParseCount(text, render::PEEK_MAX_DEPTH);
189 if (!depth)
190 return std::unexpected(
191 std::format("--peek-depth の値が不正: {}(0〜{})", ToUtf8(text), render::PEEK_MAX_DEPTH));
192
193 loop.peekDepth = *depth;
194
195 return {};
196 }
197
198 const auto cell = ParseNumbers<3>(text);
199 if (!cell || std::ranges::any_of(*cell, [](uint64_t value) { return value >= sim::PROBE_GRID_SIZE; }))
200 return std::unexpected(std::format("--peek の値が不正: {}(例: 28,32,32)", ToUtf8(text)));
201
202 loop.peek = true;
203 loop.peekCell = {.x = static_cast<uint32_t>((*cell)[0]),
204 .y = static_cast<uint32_t>((*cell)[1]),
205 .z = static_cast<uint32_t>((*cell)[2])};
206
207 return {};
208 }
209
210 // 表示の引数(--view・--camera・--screenshot・--peek・--peek-depth)
211 std::expected<void, std::string> ParseViewOption(std::wstring_view argument, std::wstring_view text,
213 if (argument == L"--screenshot") {
214 loop.screenshotPath = text;
215 return {};
216 }
217
218 if (argument == L"--peek" || argument == L"--peek-depth")
219 return ParsePeekOption(argument, text, loop);
220
221 if (argument == L"--view") {
223 return {};
224 return std::unexpected(std::format("--view の値が不正: {}(volume・mip・slice)", ToUtf8(text)));
225 }
226
227 const auto camera = ParseCamera(text);
228 if (!camera)
229 return std::unexpected(std::format("--camera の値が不正: {}(例: 35,25,150)", ToUtf8(text)));
230
231 loop.camera = *camera;
232
233 return {};
234 }
235
236 // 目標 fps の範囲(ADR-0011: 30 より下げない)
237 constexpr uint32_t MIN_TARGET_FPS = 30;
238 constexpr uint32_t MAX_TARGET_FPS = 1000;
239
240 // フレームのループの値つきの引数(--frames・--latency・--target-fps・--sim-load・--sim-split・--screenshot-tick)
241 std::expected<void, std::string> ParseFrameLoopCount(std::wstring_view argument, std::wstring_view text,
243 const uint32_t maximum = argument == L"--latency" ? 3u
244 : argument == L"--target-fps" ? MAX_TARGET_FPS
245 : argument == L"--sim-load" ? sim::PROBE_BUSY_ITERATIONS_LIMIT
246 : argument == L"--sim-split" ? sim::PROBE_MAX_BUSY_PIECES
247 : UINT32_MAX;
248 const auto value = ParseCount(text, maximum);
249
250 if (!value || (argument == L"--latency" && *value < 2) || (argument == L"--sim-split" && *value == 0) ||
251 (argument == L"--target-fps" && *value < MIN_TARGET_FPS)) {
252 return std::unexpected(std::format("{} の値が不正: {}", ToUtf8(argument), ToUtf8(text)));
253 }
254
255 if (argument == L"--frames")
256 loop.frameLimit = *value;
257
258 if (argument == L"--latency")
259 loop.maxFrameLatency = *value;
260
261 if (argument == L"--target-fps")
262 loop.targetFps = *value;
263
264 if (argument == L"--sim-load")
265 loop.simLoad = *value;
266
267 if (argument == L"--sim-split")
268 loop.simSplit = *value;
269
270 if (argument == L"--screenshot-tick")
271 loop.screenshotTick = *value;
272
273 if (argument == L"--save-points")
274 loop.savePoints = *value;
275
276 if (argument == L"--save-interval")
277 loop.saveIntervalTicks = std::max(*value, 1u);
278
279 return {};
280 }
281
282 // フレームのループの値なしの引数。当てはまらなければ false
283 bool ParseFrameLoopFlag(std::wstring_view argument, frame::FrameLoopOptions& loop) {
284 if (argument == L"--no-vsync")
285 loop.vsync = false;
286 else if (argument == L"--auto-click")
287 loop.autoClick = true;
288 else if (argument == L"--auto-trace")
289 loop.autoTrace = true;
290 else if (argument == L"--auto-push")
291 loop.autoPush = true;
292 else if (argument == L"--auto-ignite")
293 loop.autoIgnite = true;
294 else if (argument == L"--no-physics")
295 loop.physics = false;
296 else if (argument == L"--check-physics")
297 loop.checkPhysics = true;
298 else if (argument == L"--physics-compute")
299 loop.physicsComputeBroadphase = true;
300 else if (argument == L"--warp")
302 else if (argument == L"--render-normal")
303 loop.renderHighPriority = false;
304 else if (argument == L"--editor")
305 loop.editor = true;
306 else if (argument == L"--auto-time")
307 loop.autoTime = true;
308 else if (argument == L"--auto-rewind")
309 loop.autoRewind = true;
310 else if (argument == L"--auto-lab")
311 loop.autoLab = true;
312 else
313 return false;
314
315 return true;
316 }
317
318 std::expected<Options, std::string> ParseOptions(std::span<wchar_t*> arguments) {
319 Options options;
320 for (size_t i = 1; i < arguments.size(); ++i) {
321 const std::wstring_view argument = arguments[i];
322 const bool hasValue = i + 1 < arguments.size();
323 if (argument == L"--caps")
324 options.runCaps = true;
325 else if (ParseFrameLoopFlag(argument, options.frameLoop))
326 continue;
327 else if ((argument == L"--frames" || argument == L"--latency" || argument == L"--target-fps" ||
328 argument == L"--sim-load" || argument == L"--sim-split" || argument == L"--screenshot-tick" ||
329 argument == L"--save-points" || argument == L"--save-interval") &&
330 hasValue) {
331 const auto parsed = ParseFrameLoopCount(argument, arguments[++i], options.frameLoop);
332 if (!parsed)
333 return std::unexpected(parsed.error());
334 } else if ((argument == L"--view" || argument == L"--camera" || argument == L"--screenshot" ||
335 argument == L"--peek" || argument == L"--peek-depth") &&
336 hasValue) {
337 const auto parsed = ParseViewOption(argument, arguments[++i], options.frameLoop);
338 if (!parsed)
339 return std::unexpected(parsed.error());
340 } else if ((argument == L"--trace-ticks" || argument == L"--trace-cells") && hasValue) {
341 const auto parsed = ParseTraceRange(argument, arguments[++i], options);
342 if (!parsed)
343 return std::unexpected(parsed.error());
344 } else if (argument == L"--trace" && hasValue)
345 options.frameLoop.tracePath = arguments[++i];
346 else if (argument == L"--trace-dir" && hasValue)
347 options.frameLoop.traceDirectory = arguments[++i];
348 else if (argument == L"--record" && hasValue)
349 options.frameLoop.recordPath = arguments[++i];
350 else if (argument == L"--replay" && hasValue)
351 options.frameLoop.replayPath = arguments[++i];
352 else if (argument == L"--packages" && hasValue)
353 options.frameLoop.packageRoot = arguments[++i];
354 else if (argument == L"--log-dir" && hasValue)
355 options.logDirectory = arguments[++i];
356 else if (argument == L"--log-level" && hasValue) {
357 const std::string name = ToUtf8(arguments[++i]);
358 options.hasLogLevel = ParseLevel(name, options.logLevel);
359 if (!options.hasLogLevel)
360 return std::unexpected(std::format("知らないログの重大度: {}", name));
361 } else
362 return std::unexpected(std::format("知らない引数: {}", ToUtf8(argument)));
363 }
364
365 ApplyTraceRange(options);
366 if (options.frameLoop.traceDirectory.empty())
367 options.frameLoop.traceDirectory = ExecutableDirectory() / L"traces";
368
369 return options;
370 }
371
372 // --- 本体 ---
373
374 int Run(std::span<wchar_t*> arguments) {
375 const auto options = ParseOptions(arguments);
376 Logger& logger = GetLogger();
377 if (!options) {
378 Log(Channel::Core, Level::Error, "{}(使い方は main.cpp の先頭)", options.error());
379 return 2;
380 }
381
382 if (options->hasLogLevel)
383 logger.SetMinLevel(options->logLevel);
384
385 const fs::path logDirectory = options->logDirectory.empty() ? DefaultLogDirectory() : options->logDirectory;
386 if (const auto logFile = OpenLogFile(logger, logDirectory))
387 Log(Channel::Core, Level::Info, "Bicameral Engine ログ: {}", ToUtf8(logFile->wstring()));
388 else
389 Log(Channel::Core, Level::Warning, "ログのファイルなしで続ける: {}", logFile.error());
390
391 if (options->runCaps)
392 return RunCapsProbe();
393
394 return frame::RunFrameLoop(options->frameLoop);
395 }
396
397} // namespace
398
399int wmain(int argc, wchar_t** argv) {
400 const int exitCode = Run(std::span(argv, static_cast<size_t>(argc)));
401 bicameral::SingletonFinalizer::Finalize(); // ログは最初に作られるので最後に壊れる(作った順の逆)
402
403 return exitCode;
404}
void SetMinLevel(Level level)
Definition log.h:105
int wmain(int argc, wchar_t **argv)
Definition main.cpp:399
std::expected< void, std::string > ParseTraceRange(std::wstring_view argument, std::wstring_view text, Options &options)
Definition main.cpp:118
int Run(std::span< wchar_t * > arguments)
Definition main.cpp:374
std::optional< uint32_t > ParseCount(std::wstring_view text, uint32_t maximum)
Definition main.cpp:84
std::expected< void, std::string > ParseFrameLoopCount(std::wstring_view argument, std::wstring_view text, frame::FrameLoopOptions &loop)
Definition main.cpp:241
constexpr uint32_t MAX_TARGET_FPS
Definition main.cpp:238
std::expected< void, std::string > ParseViewOption(std::wstring_view argument, std::wstring_view text, frame::FrameLoopOptions &loop)
Definition main.cpp:211
std::expected< Options, std::string > ParseOptions(std::span< wchar_t * > arguments)
Definition main.cpp:318
constexpr uint32_t MIN_TARGET_FPS
Definition main.cpp:237
std::expected< void, std::string > ParsePeekOption(std::wstring_view argument, std::wstring_view text, frame::FrameLoopOptions &loop)
Definition main.cpp:185
std::optional< std::array< uint64_t, N > > ParseNumbers(std::wstring_view text)
Definition main.cpp:96
bool ParseFrameLoopFlag(std::wstring_view argument, frame::FrameLoopOptions &loop)
Definition main.cpp:283
std::optional< render::OrbitCameraState > ParseCamera(std::wstring_view text)
Definition main.cpp:156
void ApplyTraceRange(Options &options)
Definition main.cpp:142
constexpr uint32_t TRACE_CAPACITY_PER_FRAME
Definition frame_loop.h:28
int RunFrameLoop(const FrameLoopOptions &options)
constexpr uint32_t PEEK_MAX_DEPTH
bool ParseDebugViewMode(std::string_view name, DebugViewMode &mode)
gpu::GraphTraceFilter ProbeTraceFilterForCells(uint64_t tickBegin, uint64_t tickEnd, std::array< uint32_t, 3 > cellMin, std::array< uint32_t, 3 > cellMax, uint32_t capacity)
Logger & GetLogger()
Definition log.h:120
std::expected< fs::path, std::string > OpenLogFile(Logger &logger, const fs::path &directory)
fs::path DefaultLogDirectory()
int RunCapsProbe()
Definition caps.cpp:175
fs::path ExecutableDirectory()
Definition paths.cpp:12
void Log(Channel channel, Level level, FormatWithLocation< std::type_identity_t< Args >... > pattern, Args &&... args)
Definition log.h:140
std::string ToUtf8(std::wstring_view text)
Definition unicode.cpp:10
Level
Definition log.h:53
bool ParseLevel(std::string_view text, Level &level)
Definition log.cpp:59
std::array< uint64_t, 2 > traceTicks
Definition main.cpp:76
frame::FrameLoopOptions frameLoop
Definition main.cpp:70
std::array< uint64_t, 6 > traceCells
Definition main.cpp:77
render::CellCoordinate peekCell
Definition frame_loop.h:69
std::optional< uint64_t > screenshotTick
Definition frame_loop.h:57
render::OrbitCameraState camera
Definition frame_loop.h:97
render::DebugViewSettings view
Definition frame_loop.h:96
gpu::GraphTraceFilter trace
Definition frame_loop.h:63