Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
probe_sim.cpp
Go to the documentation of this file.
1// probe_sim.cpp — 仮の刻みを単位の列として GPU で走らせる(フレームの枠ごとのリストに毎フレーム記録)と、その CPU リファレンス。
2// 使い方とデータの流れは probe_sim.h、規則は shaders/common/probe_sim.hlsli。
3#include "sim/probe_sim.h"
4
5#include <algorithm>
6#include <cstddef>
7
8#include "core/aliases.h"
9#include "core/log.h"
10#include "gpu/com_ptr.h"
11#include "gpu/resources.h"
12
13namespace bicameral::sim {
14 namespace {
15
16 // ルート署名(shaders/sim/probe_bindings.hlsli と同じ順。伝導の Work Graph もこれをグローバルのルート署名に使う):
17 // u0 セル・u1 イベントのリング・u2/u3/u5 抽出・u4 重さの捨て場・u6 ハッシュの表・u7 コマンドキュー・
18 // u8 刻みのイベントの一時置き場・u9/u10 活性の一覧・u11 予定の印・u12 熱のキャッシュ・u13 物理の物(T-0098。物理なしなら仮の置き場)
19 // → b0 単位の定数 → デバッグのリング
20 // → Work Graphs のカウンタ(u1 space1。T-0008)→ 連鎖のトレース(u2 space1。T-0087)→ t0 フレームの入力・t1〜t4 反応の表
21 constexpr gpu::RootSignatureLayout ROOT_LAYOUT{.uavCount = 14,
22 .rootConstantCount = PROBE_ROOT_CONSTANT_COUNT,
23 .debugRing = true,
24 .graphStats = true,
25 .graphTrace = true,
26 .srvCount = 5};
27
28 constexpr uint32_t UAV_CELLS = 0;
29 constexpr uint32_t UAV_EVENTS = 1;
30 constexpr uint32_t UAV_EXTRACTION0 = 2;
31 constexpr uint32_t UAV_EXTRACTION1 = 3;
32 constexpr uint32_t UAV_BUSY_SINK = 4;
33 constexpr uint32_t UAV_EXTRACTION2 = 5;
34 constexpr uint32_t UAV_HASHES = 6;
35 constexpr uint32_t UAV_COMMAND_QUEUE = 7;
36 constexpr uint32_t UAV_TICK_EVENTS = 8;
37 constexpr uint32_t UAV_ACTIVE_LIST0 = 9;
38 constexpr uint32_t UAV_ACTIVE_LIST1 = 10;
39 constexpr uint32_t UAV_BLOCK_SCHEDULE = 11;
40 constexpr uint32_t UAV_THERMAL = 12;
41 constexpr uint32_t UAV_BODIES = 13;
42 constexpr uint32_t SRV_INPUT = 0;
43 constexpr uint32_t SRV_REACTION_FIRST = 1; // t1 物質・t2 規則・t3 索引・t4 速度
44
45 constexpr uint32_t TIMESTAMPS_PER_SLOT = ProbeSim::MAX_UNITS_PER_FRAME + 2; // 始め・単位ごと・終わり
46 constexpr uint64_t CELL_BYTES = uint64_t{PROBE_CELL_COUNT} * sizeof(reaction::RxCell); // 1 世代
47 constexpr uint64_t THERMAL_BYTES = uint64_t{PROBE_CELL_COUNT} * sizeof(reaction::HcThermalCache); // 1 世代
48 constexpr uint32_t SLICE_BYTES = PROBE_SLICE_CELL_COUNT * 4;
49 constexpr uint32_t BUSY_GROUPS = PROBE_GRID_SIZE / PROBE_GROUP_SIZE;
50 constexpr uint32_t LINEAR_CELL_GROUPS = PROBE_CELL_COUNT / PROBE_LINEAR_GROUP_SIZE;
51 constexpr uint32_t BLOCK_GROUPS = (PROBE_BLOCK_COUNT + PROBE_LINEAR_GROUP_SIZE - 1) / PROBE_LINEAR_GROUP_SIZE;
52 constexpr uint32_t EXTRACTION_BYTES = PROBE_EXTRACTION_WORDS * 4;
53
54 // 活性の一覧の見出しは D3D12_NODE_GPU_INPUT そのもの(DispatchGraph が GPU のメモリから読む。probe_sim.hlsli)
55 static_assert(offsetof(D3D12_NODE_GPU_INPUT, EntrypointIndex) == size_t{PROBE_ACTIVE_LIST_ENTRYPOINT} * 4);
56 static_assert(offsetof(D3D12_NODE_GPU_INPUT, NumRecords) == size_t{PROBE_ACTIVE_LIST_COUNT} * 4);
57 static_assert(offsetof(D3D12_NODE_GPU_INPUT, Records) == size_t{PROBE_ACTIVE_LIST_ADDRESS} * 4);
58
59 static_assert(offsetof(D3D12_NODE_GPU_INPUT, Records) +
60 offsetof(D3D12_GPU_VIRTUAL_ADDRESS_AND_STRIDE, StrideInBytes) ==
61 size_t{PROBE_ACTIVE_LIST_STRIDE} * 4);
62
63 static_assert(sizeof(D3D12_NODE_GPU_INPUT) <= PROBE_ACTIVE_LIST_HEADER_BYTES);
64 constexpr uint32_t MAX_LOGGED_DEBUG_MESSAGES = 8;
65
66 // 伝導のグラフのカウンタの番号と上限(probe_sim.hlsli の PROBE_STATS_* と同じ順。T-0008)。
67 // WakeBlocks の出力は構造で 7 まで・ConductBlock は出力しない(一覧へは UAV で足す)ので、出力の「近い」は見ない。
68 // 活性の一覧は全部のブロックが活性でも溢れない大きさなので、満杯のときだけ知らせる
70 gpu::GraphStatsLayout layout{.name = "伝導(ProbeConduct)"};
71 layout.nodes.resize(2);
72 layout.nodes[PROBE_STATS_NODE_WAKE] = {.name = "WakeBlocks", .maxOutputRecords = PROBE_WAKE_MAX_RECORDS};
73 layout.nodes[PROBE_STATS_NODE_CONDUCT] = {.name = "ConductBlock"};
74 layout.gauges.resize(2);
75 layout.gauges[PROBE_STATS_GAUGE_ACTIVE_LIST] = {
76 .name = "活性の一覧", .capacity = PROBE_ACTIVE_LIST_CAPACITY, .warnPercent = 100};
77 layout.gauges[PROBE_STATS_GAUGE_COMMAND_QUEUE] = {.name = "コマンドキュー",
78 .capacity = PROBE_COMMAND_QUEUE_CAPACITY};
79
80 return layout;
81 }
82
83 template <typename T>
84 void WriteAt(std::byte* base, uint32_t offset, const T& value) {
85 std::memcpy(base + offset, &value, sizeof(T));
86 }
87
88 // アップロードのバッファを作って中身を書く(最初のフレームで既定のヒープへ写す元)
89 template <typename T>
90 ComPtr<ID3D12Resource> CreateFilledUpload(ID3D12Device* device, const std::vector<T>& data,
91 const wchar_t* name) {
92 const auto bytes = std::as_bytes(std::span(data));
93 ComPtr<ID3D12Resource> buffer = gpu::CreateBuffer(device, bytes.size(), gpu::BufferKind::Upload);
94 void* mapped = nullptr;
95 const D3D12_RANGE noRead{};
96 if (!buffer || FAILED(buffer->Map(0, &noRead, &mapped)))
97 return nullptr;
98
99 std::memcpy(mapped, bytes.data(), bytes.size());
100 buffer->Unmap(0, nullptr);
101 buffer->SetName(name);
102
103 return buffer;
104 }
105
106 // イベントのリングの見出し(probe_sim.hlsli の PROBE_EVENT_HEADER_*)
108 uint32_t requested = 0; // リングに書こうとした数
109 uint32_t tickDropped = 0; // 刻みの一時置き場で落とした数
110 };
111
112 ComPtr<ID3D12PipelineState> LoadComputePipeline(ID3D12Device* device, ID3D12RootSignature* rootSignature,
113 std::string_view shaderPath) {
114 const auto bytecode = gpu::LoadShader(shaderPath);
115 if (!bytecode) {
116 Log(Channel::Sim, Level::Error, "{}", bytecode.error());
117 return nullptr;
118 }
119
120 return gpu::CreateComputePipeline(device, rootSignature, *bytecode);
121 }
122
123 // 単位の定数(b0): 刻みの下位・上位・引数
124 void SetUnitConstants(ID3D12GraphicsCommandList10* list, uint64_t tick, uint32_t argument) {
125 const std::array<uint32_t, PROBE_ROOT_CONSTANT_COUNT> constants = {
126 static_cast<uint32_t>(tick), static_cast<uint32_t>(tick >> 32), argument};
127 list->SetComputeRoot32BitConstants(ROOT_LAYOUT.RootConstantIndex(), PROBE_ROOT_CONSTANT_COUNT,
128 constants.data(), 0);
129 }
130
131 } // namespace
132
133 ProbeCommand MakePokeCommand(uint64_t targetTick, uint32_t sequence, uint32_t x, uint32_t y, uint32_t z) {
134 ProbeCommand command{.targetTick = targetTick,
135 .sequence = sequence,
136 .type = static_cast<uint16_t>(PROBE_COMMAND_TYPE_POKE),
137 .size = 12};
138
139 command.payload[0] = x;
140 command.payload[1] = y;
141 command.payload[2] = z;
142
143 return command;
144 }
145
146 ProbeCommand MakePushCommand(uint64_t targetTick, uint32_t sequence, const std::array<int64_t, 3>& origin,
147 const std::array<int32_t, 3>& direction, uint32_t impulseMillinewtonSeconds) {
148 ProbeCommand command{.targetTick = targetTick,
149 .sequence = sequence,
150 .type = static_cast<uint16_t>(PROBE_COMMAND_TYPE_PUSH),
151 .size = 40};
152
153 for (size_t axis = 0; axis < 3; ++axis) {
154 const auto value = static_cast<uint64_t>(origin[axis]);
155 command.payload[axis * 2] = static_cast<uint32_t>(value);
156 command.payload[(axis * 2) + 1] = static_cast<uint32_t>(value >> 32);
157 command.payload[6 + axis] = static_cast<uint32_t>(direction[axis]);
158 }
159
160 command.payload[9] = impulseMillinewtonSeconds;
161
162 return command;
163 }
164
165 ProbeCommand MakeTableCommand(uint64_t targetTick, uint32_t sequence, uint64_t version) {
166 ProbeCommand command{.targetTick = targetTick,
167 .sequence = sequence,
168 .type = static_cast<uint16_t>(PROBE_COMMAND_TYPE_TABLE),
169 .size = 8};
170
171 command.payload[0] = static_cast<uint32_t>(version);
172 command.payload[1] = static_cast<uint32_t>(version >> 32);
173
174 return command;
175 }
176
177 // --- 作る ---
178
179 std::expected<ProbeSim, std::string> ProbeSim::Create(ID3D12Device5* device, D3D12_COMMAND_LIST_TYPE listType,
180 const BakedReactionTable& table,
181 const ProbeSimOptions& options) {
182 if (options.busyPieces == 0 || options.busyPieces > PROBE_MAX_BUSY_PIECES ||
183 options.busyIterations > PROBE_BUSY_ITERATIONS_LIMIT) {
184 return std::unexpected(std::format("重さの試験の値が範囲外: 繰り返し {} 分ける数 {}",
185 options.busyIterations, options.busyPieces));
186 }
187
188 auto events = gpu::ReadbackRing::Create(device, PROBE_EVENT_BYTES, PROBE_EVENT_HEADER_BYTES, FRAME_SLOT_COUNT,
189 L"ProbeSim.events");
190 if (!events)
191 return std::unexpected(events.error());
192
193 auto debugRing = gpu::DebugRing::Create(device, FRAME_SLOT_COUNT);
194 if (!debugRing)
195 return std::unexpected(debugRing.error());
196
197 auto graphStats = gpu::WorkGraphStats::Create(device, MakeConductStatsLayout(), FRAME_SLOT_COUNT);
198 if (!graphStats)
199 return std::unexpected(graphStats.error());
200
201 auto graphTrace = gpu::GraphTrace::Create(device, options.trace, FRAME_SLOT_COUNT, options.traceCapacity);
202 if (!graphTrace)
203 return std::unexpected(graphTrace.error());
204
205 ProbeSim sim(options, std::move(*events), std::move(*debugRing), std::move(*graphStats),
206 std::move(*graphTrace));
207 if (!sim.CreatePipelines(device))
208 return std::unexpected("仮の刻みのパイプラインを作れない");
209
210 if (!sim.CreateBuffers(device))
211 return std::unexpected("仮の刻みのバッファを作れない");
212
213 // 初めの世界は作る時だけ使う(呼んだ側が持つ)
214 sim.m_options.initialWorld = {};
215 if (!sim.CreateWorld(device, table, options.initialWorld))
216 return std::unexpected("仮の世界(反応の表・初めのセル)を作れない");
217
218 if (!sim.CreateFrameSlots(device, listType))
219 return std::unexpected("仮の刻みのフレームの枠を作れない");
220
221 if (auto physics = sim.CreatePhysics(device); !physics)
222 return std::unexpected(physics.error());
223
224 return sim;
225 }
226
227 bool ProbeSim::CreatePipelines(ID3D12Device5* device) {
228 m_rootSignature = gpu::CreateRootSignature(device, ROOT_LAYOUT);
229 if (!m_rootSignature)
230 return false;
231
232 ID3D12RootSignature* root = m_rootSignature.Get();
233 m_enqueuePipeline = LoadComputePipeline(device, root, "sim/probe_tick_enqueue.cso");
234 m_applyPipeline = LoadComputePipeline(device, root, "sim/probe_tick_apply.cso");
235 m_wakeDuePipeline = LoadComputePipeline(device, root, "sim/probe_tick_wake_due.cso");
236 m_refreshTablePipeline = LoadComputePipeline(device, root, "sim/probe_tick_refresh_table.cso");
237 m_busyPipeline = LoadComputePipeline(device, root, "sim/probe_tick_busy.cso");
238 m_hashCellsPipeline = LoadComputePipeline(device, root, "sim/probe_tick_hash_cells.cso");
239 m_flushEventsPipeline = LoadComputePipeline(device, root, "sim/probe_tick_flush_events.cso");
240 m_extractPipeline = LoadComputePipeline(device, root, "sim/probe_tick_extract.cso");
241
244 }
245
246 // 伝導の Work Graph(WakeBlocks → ConductBlock)。compute と同じルート署名をグローバルのルート署名にする
247 bool ProbeSim::CreateConductGraph(ID3D12Device5* device) {
248 const auto library = gpu::LoadShader("sim/probe_conduct.cso");
249 if (!library) {
250 Log(Channel::Sim, Level::Error, "{}", library.error());
251 return false;
252 }
253
254 auto graph = gpu::WorkGraph::Create(device, m_rootSignature.Get(), *library, L"ProbeConduct");
255 if (!graph) {
256 Log(Channel::Sim, Level::Error, "{}", graph.error());
257 return false;
258 }
259
260 m_conductGraph = std::make_unique<gpu::WorkGraph>(std::move(*graph));
261 m_conductEntrypoint = m_conductGraph->EntrypointIndex(L"WakeBlocks");
262
263 return m_conductEntrypoint != UINT32_MAX;
264 }
265
266 bool ProbeSim::CreateBuffers(ID3D12Device5* device) {
271 m_commandQueue = gpu::CreateBuffer(device, PROBE_COMMAND_QUEUE_BYTES, gpu::BufferKind::UnorderedAccess);
272 m_tickEvents = gpu::CreateBuffer(device, PROBE_TICK_EVENT_BYTES, gpu::BufferKind::UnorderedAccess);
275 return false;
276
277 m_blockSchedule->SetName(
278 L"ProbeSim.blockSchedule"); // 最初のフレームで初めの中身を写す(ProbeInitialScheduleWords)
279 // 活性の一覧: 作った時は 0(空。見出しは刻みの適用の単位が毎刻み書く)
280 for (uint32_t parity = 0; parity < 2; ++parity) {
281 m_activeLists[parity] = gpu::CreateBuffer(device, PROBE_ACTIVE_LIST_BYTES,
283 if (!m_activeLists[parity])
284 return false;
285
286 m_activeLists[parity]->SetName(std::format(L"ProbeSim.activeList{}", parity).c_str());
287 }
288
289 m_cells->SetName(L"ProbeSim.cells");
290 m_thermal->SetName(L"ProbeSim.thermal");
291 m_busySink->SetName(L"ProbeSim.busySink");
292 m_hashes->SetName(L"ProbeSim.hashes");
293 m_commandQueue->SetName(L"ProbeSim.commandQueue"); // 作った時は 0(末尾 = 先頭 = 0 の空のキュー)
294 m_tickEvents->SetName(L"ProbeSim.tickEvents");
295
296 for (uint32_t index = 0; index < PROBE_EXTRACTION_COUNT; ++index) {
297 m_extractions[index] = gpu::CreateBuffer(device, EXTRACTION_BYTES, gpu::BufferKind::UnorderedAccess);
298 if (!m_extractions[index])
299 return false;
300
301 m_extractions[index]->SetName(std::format(L"ProbeSim.extraction{}", index).c_str());
302 }
303
304 const D3D12_QUERY_HEAP_DESC queryDesc{.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP,
305 .Count = FRAME_SLOT_COUNT * TIMESTAMPS_PER_SLOT};
306
307 return SUCCEEDED(device->CreateQueryHeap(&queryDesc, IID_PPV_ARGS(&m_timestamps)));
308 }
309
310 // 物理(T-0098): 場面があれば物の 1 刻みを作る。場面は作った後は持たない
311 std::expected<void, std::string> ProbeSim::CreatePhysics(ID3D12Device5* device) {
312 const PhysicsScene* scene = m_options.physicsScene;
313 m_options.physicsScene = nullptr;
314 if (scene == nullptr)
315 return {};
316
317 auto physics = GpuPhysics::Create(device, *scene, m_options.physicsParameters, m_options.physicsOptions);
318 if (!physics)
319 return std::unexpected("仮の世界の物理を作れない: " + physics.error());
320
321 m_physics = std::make_unique<GpuPhysics>(std::move(*physics));
322 m_physicsRate = physics::PxStepRate(m_options.physicsParameters);
323
324 return {};
325 }
326
327 // 反応の表(既定のヒープ)と、表・初めの世界のアップロード。写すのは最初のフレーム(RecordInitialization)
328 bool ProbeSim::CreateWorld(ID3D12Device5* device, const BakedReactionTable& table,
329 std::span<const reaction::RxCell> initialWorld) {
330 FX_ASSERT(initialWorld.empty() || initialWorld.size() == PROBE_CELL_COUNT);
331 const std::vector<reaction::RxCell> cells = initialWorld.empty()
332 ? MakeProbeInitialWorld(table)
333 : std::vector<reaction::RxCell>(initialWorld.begin(),
334 initialWorld.end());
335 std::vector<reaction::HcThermalCache> caches;
336 caches.reserve(cells.size());
337 for (const reaction::RxCell& cell : cells)
338 caches.push_back(ProbeMakeCache(table.View(), cell));
339
341 CreateFilledUpload(device, table.species, L"ProbeSim.upload.species"),
342 CreateFilledUpload(device, table.rules, L"ProbeSim.upload.rules"),
343 CreateFilledUpload(device, table.ruleIndex, L"ProbeSim.upload.ruleIndex"),
344 CreateFilledUpload(device, table.rates, L"ProbeSim.upload.rates"),
345 CreateFilledUpload(device, cells, L"ProbeSim.upload.cells"),
346 CreateFilledUpload(device, caches, L"ProbeSim.upload.thermal"),
347 CreateFilledUpload(device, ProbeInitialScheduleWords(table, cells), L"ProbeSim.upload.blockSchedule")};
348
349 constexpr std::array<const wchar_t*, 4> TABLE_NAMES = {L"ProbeSim.species", L"ProbeSim.rules",
350 L"ProbeSim.ruleIndex", L"ProbeSim.rates"};
351 for (size_t index = 0; index < m_reactionTable.size(); ++index) {
352 if (!m_initialUploads[index])
353 return false;
354
355 m_reactionTable[index] = gpu::CreateBuffer(device, m_initialUploads[index]->GetDesc().Width,
357 if (!m_reactionTable[index])
358 return false;
359
360 m_reactionTable[index]->SetName(TABLE_NAMES[index]);
361 }
362
364
366 }
367
368 // 最初のフレームの始め: 表と初めの世界(2 世代とも同じ S(0))を既定のヒープへ写す。
369 // 既定のバッファは COMMON なので、写すときに COPY_DEST へ暗黙に昇格する。写した後は使う状態へ明示的に移す
370 // (フレームの終わりに COMMON へ戻る。バッファは ExecuteCommandLists の終わりで COMMON に落ちる)
371 void ProbeSim::RecordInitialization(ID3D12GraphicsCommandList10* list) {
372 for (size_t index = 0; index < m_reactionTable.size(); ++index) {
373 list->CopyBufferRegion(m_reactionTable[index].Get(), 0, m_initialUploads[index].Get(), 0,
374 m_initialUploads[index]->GetDesc().Width);
375 }
376
377 for (uint64_t generation = 0; generation < 2; ++generation) {
378 list->CopyBufferRegion(m_cells.Get(), generation * CELL_BYTES, m_initialUploads[4].Get(), 0, CELL_BYTES);
379 list->CopyBufferRegion(m_thermal.Get(), generation * THERMAL_BYTES, m_initialUploads[5].Get(), 0,
380 THERMAL_BYTES);
381 }
382
383 list->CopyBufferRegion(m_blockSchedule.Get(), 0, m_initialUploads[6].Get(), 0, PROBE_SCHEDULE_BYTES);
384
385 std::vector<D3D12_RESOURCE_BARRIER> barriers;
386 barriers.reserve(m_reactionTable.size() + 3);
387 for (const ComPtr<ID3D12Resource>& buffer : m_reactionTable) {
388 barriers.push_back(gpu::Transition(buffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
389 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE));
390 }
391
392 barriers.push_back(
393 gpu::Transition(m_cells.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
394 barriers.push_back(
395 gpu::Transition(m_thermal.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
396 barriers.push_back(gpu::Transition(m_blockSchedule.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
397 D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
398 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
399 m_initialized = true;
400 }
401
402 // フレームの枠ごとに: リスト(毎フレーム記録し直す)・入力のアップロード・読み戻し
403 bool ProbeSim::CreateFrameSlots(ID3D12Device5* device, D3D12_COMMAND_LIST_TYPE listType) {
404 for (FrameSlot& frame : m_slots) {
405 if (FAILED(device->CreateCommandAllocator(listType, IID_PPV_ARGS(&frame.allocator))) ||
406 FAILED(
407 device->CreateCommandList1(0, listType, D3D12_COMMAND_LIST_FLAG_NONE, IID_PPV_ARGS(&frame.list)))) {
408 return false;
409 }
410
411 frame.input = gpu::CreateBuffer(device, PROBE_INPUT_BYTES, gpu::BufferKind::Upload);
412 frame.timestampReadback = gpu::CreateBuffer(device, uint64_t{TIMESTAMPS_PER_SLOT} * 8,
414 frame.hashReadback = gpu::CreateBuffer(device, PROBE_HASH_BYTES, gpu::BufferKind::Readback);
415 if (!frame.input || !frame.timestampReadback || !frame.hashReadback)
416 return false;
417
418 void* mapped = nullptr;
419 const D3D12_RANGE noRead{};
420 if (FAILED(frame.input->Map(0, &noRead, &mapped)))
421 return false;
422
423 frame.mappedInput = static_cast<std::byte*>(mapped);
424 }
425
426 return true;
427 }
428
429 // --- フレームごとに記録する ---
430
431 bool ProbeSim::ValidateInput(uint32_t slot, const ProbeFrameInput& input) const {
432 const bool valid = slot < FRAME_SLOT_COUNT && input.firstUnit < UnitsPerTick() &&
433 input.unitCount <= MAX_UNITS_PER_FRAME && input.commands.size() <= PROBE_MAX_COMMANDS &&
434 input.extractionTarget < PROBE_EXTRACTION_COUNT;
435
436 if (!valid) {
437 Log(Channel::Sim, Level::Error, "フレームの入力が範囲外: slot {} 単位 {}+{} コマンド {} 抽出 {}", slot,
438 input.firstUnit, input.unitCount, input.commands.size(), input.extractionTarget);
439 return false;
440 }
441
442 return ValidateSavePoints(input) && ValidateCommands(input) && ValidateTableSwap(input);
443 }
444
445 // 表の差し替えの約束: (tick, 適用の単位) がこのフレームの単位の中にあり、表がある(T-0139)
447 const uint64_t first = (input.firstTick * UnitsPerTick()) + input.firstUnit;
448 for (const ProbeTableSwap& swap : input.tableSwaps) {
449 const uint64_t swapUnit = swap.tick * UnitsPerTick();
450 if (swap.table != nullptr && swapUnit >= first && swapUnit < first + input.unitCount)
451 continue;
452
454 "表の差し替えの刻み {} の適用の単位が、このフレーム(刻み {} の単位 {} から {} 個)に無い", swap.tick,
455 input.firstTick, input.firstUnit, input.unitCount);
456 return false;
457 }
458
459 return true;
460 }
461
462 // コマンドの約束(ファイルの先頭): キューの空き・並び・適用に間に合う刻み。破ると GPU で捨てられるか、キューが壊れる
464 // 保存点から戻すフレームは、足す前にキューが空になる(T-0143)
465 const bool restoring = input.restoreFrom != NO_SAVE_POINT;
466 const uint32_t freeSlots = restoring ? PROBE_COMMAND_QUEUE_CAPACITY : FreeCommandSlots();
467 if (input.commands.size() > freeSlots) {
468 Log(Channel::Sim, Level::Error, "コマンドキューの空きが足りない: 足す {} 空き {}", input.commands.size(),
469 freeSlots);
470 return false;
471 }
472
473 const uint64_t nextApplyTick = NextApplyTick(input.firstTick, input.firstUnit);
474 const ProbeCommand* previous = m_hasEnqueued && !restoring ? &m_lastEnqueued : nullptr;
475 for (const ProbeCommand& command : input.commands) {
476 if (command.targetTick < nextApplyTick || (previous != nullptr && !CommandPrecedes(*previous, command))) {
478 "コマンドの刻みか並びが不正: 刻み {} 番号 {}(適用に間に合う最初の刻み {}、前のコマンド {} / {})",
479 command.targetTick, command.sequence, nextApplyTick, previous != nullptr ? previous->targetTick : 0,
480 previous != nullptr ? previous->sequence : 0);
481 return false;
482 }
483
484 previous = &command;
485 }
486
487 return true;
488 }
489
490 // 見出し(probe_sim.hlsli の PROBE_HEADER_*)とコマンド
492 const auto commandCount = static_cast<uint32_t>(input.commands.size());
493
494 // 重さは 1 個あたりの回数にする(分けても 1 刻みの合計がほぼ同じになるように)
495 const uint32_t busyPerPiece = m_options.busyIterations == 0
496 ? 0
497 : std::max(1u, m_options.busyIterations / m_options.busyPieces);
498
499 const D3D12_GPU_VIRTUAL_ADDRESS list0 = m_activeLists[0]->GetGPUVirtualAddress();
500 const D3D12_GPU_VIRTUAL_ADDRESS list1 = m_activeLists[1]->GetGPUVirtualAddress();
501
502 const std::array<uint32_t, PROBE_HEADER_WORDS> header = {commandCount,
503 busyPerPiece,
506 static_cast<uint32_t>(list0),
507 static_cast<uint32_t>(list0 >> 32),
508 static_cast<uint32_t>(list1),
509 static_cast<uint32_t>(list1 >> 32),
510 m_viewSpecies[0],
511 m_viewSpecies[1],
512 m_viewSpecies[2],
513 m_physics ? m_physics->BodyCount() : 0,
514 static_cast<uint32_t>(m_physicsRate)};
515
516 WriteAt(frame.mappedInput, PROBE_INPUT_HEADER_OFFSET, header);
517
518 if (commandCount > 0) {
519 std::memcpy(frame.mappedInput + PROBE_INPUT_COMMANDS_OFFSET, input.commands.data(),
520 input.commands.size_bytes());
521 }
522 }
523
524 ID3D12CommandList* ProbeSim::RecordFrame(uint32_t slot, const ProbeFrameInput& input) {
525 if (!ValidateInput(slot, input))
526 return nullptr;
527
528 // 保存点から戻すフレームは、コマンドキューが空になってから足す(足す場所を入力に書く前に CPU の控えを空に。T-0143)
529 if (input.restoreFrom != NO_SAVE_POINT)
531
532 FrameSlot& frame = m_slots[slot];
533 frame.keepAlive.clear(); // この枠の前のリストは GPU が終えている(呼ぶ側の約束)
534 WriteInput(frame, input);
535 if (FAILED(frame.allocator->Reset()) || FAILED(frame.list->Reset(frame.allocator.Get(), nullptr))) {
536 Log(Channel::Sim, Level::Error, "フレームのリストを記録し直せない(slot {})", slot);
537 return nullptr;
538 }
539
540 ID3D12GraphicsCommandList10* list = frame.list.Get();
541 const uint32_t firstQuery = slot * TIMESTAMPS_PER_SLOT;
542 list->EndQuery(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, firstQuery);
543 if (!m_initialized)
545
546 // 巻き戻し(T-0143): 刻みの境界から始まるフレームの先頭で、保存点へ写すか保存点から戻す(コマンドを足す前)
547 if (input.restoreFrom != NO_SAVE_POINT)
548 RecordRestore(list, input.restoreFrom);
549 else if (input.saveTo != NO_SAVE_POINT)
550 RecordSave(list, input.saveTo, input.firstTick);
551
552 BindRootArguments(list, frame.input.Get());
553 m_events.RecordBegin(list);
554 m_debugRing.RecordBegin(list);
555 m_graphStats.RecordBegin(list);
556 m_graphTrace.RecordBegin(list, slot);
557 RecordPhysicsInitialization(list, frame.input.Get());
558
559 const D3D12_RESOURCE_BARRIER hashesToUav = gpu::Transition(m_hashes.Get(), D3D12_RESOURCE_STATE_COMMON,
560 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
561 list->ResourceBarrier(1, &hashesToUav);
562 // 活性の一覧はフレームの中では UAV(伝導の間だけ入力の組を GPU の入力の状態に)。フレームの終わりに COMMON へ戻す
563 RecordActiveListStates(list, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
564 RecordEnqueue(list, static_cast<uint32_t>(input.commands.size()));
565
566 // --- 単位を順に。刻みの終わりをまたいだら次の刻みへ(単位ごとに終わりのタイムスタンプ)---
567 uint64_t tick = input.firstTick;
568 uint32_t unit = input.firstUnit;
569 bool hasHash = false;
570
571 for (uint32_t index = 0; index < input.unitCount; ++index) {
572 // 表の差し替え(T-0139): その刻みの適用の単位の前に写す(適用の後の RefreshTable は RecordUnit)
573 const auto swap = rng::find(input.tableSwaps, tick, &ProbeTableSwap::tick);
574 const bool tableSwapped = unit == PROBE_UNIT_APPLY && swap != input.tableSwaps.end();
575 if (tableSwapped && !RecordTableSwap(list, frame, *swap->table)) {
576 Log(Channel::Sim, Level::Error, "反応表の差し替えを記録できない(刻み {})", tick);
577 return nullptr;
578 }
579
580 RecordUnit(list, frame.input.Get(), tick, unit, tableSwapped);
581 list->EndQuery(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, firstQuery + 1 + index);
582 hasHash = hasHash || unit == HashUnit();
583
584 if (++unit == UnitsPerTick()) {
585 unit = 0;
586 ++tick;
587 }
588 }
589
590 // --- 刻みの境界の状態 S(tick) を抽出へ(刻みの途中で終わっても、途中の刻みは別の世代に書いているので S(tick) は揃っている)---
591 if (input.extract)
592 RecordExtractAndHook(list, tick, input);
593
594 RecordActiveListStates(list, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COMMON);
595 if (input.readPhysics && m_physics)
596 m_physics->RecordReadback(list);
597
598 RecordReadbacks(list, slot, hasHash);
599 const uint32_t lastQuery = firstQuery + 1 + input.unitCount;
600 list->EndQuery(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, lastQuery);
601 list->ResolveQueryData(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, firstQuery, input.unitCount + 2,
602 frame.timestampReadback.Get(), 0);
603 if (FAILED(list->Close()))
604 return nullptr;
605
606 frame.firstTick = input.firstTick;
607 frame.firstUnit = input.firstUnit;
608 frame.unitCount = input.unitCount;
609 TrackCommands(input.commands, NextApplyTick(tick, unit));
610
611 return list;
612 }
613
614 // 抽出と、その後ろのフック(覗き窓。T-0096)。フックは書き終えた抽出を読めるように UAV のバリアの後
615 void ProbeSim::RecordExtractAndHook(ID3D12GraphicsCommandList10* list, uint64_t tick,
616 const ProbeFrameInput& input) {
617 RecordExtract(list, tick, input.extractionTarget);
618 if (!input.afterExtract)
619 return;
620
621 const D3D12_RESOURCE_BARRIER allUavs = gpu::UavBarrier(nullptr);
622 list->ResourceBarrier(1, &allUavs);
623 input.afterExtract(list, {.tick = tick,
624 .cells = m_cells.Get(),
625 .extraction = m_extractions[input.extractionTarget].Get(),
626 .debugRing = m_debugRing.GpuAddress()});
627 }
628
629 // 新しいコマンドを GPU のキューの末尾へ(フレームのリストの先頭。単位より前)
630 void ProbeSim::RecordEnqueue(ID3D12GraphicsCommandList10* list, uint32_t commandCount) const {
631 if (commandCount == 0)
632 return;
633
634 const D3D12_RESOURCE_BARRIER allUavs = gpu::UavBarrier(nullptr);
635 SetUnitConstants(list, 0, 0);
636 list->SetPipelineState(m_enqueuePipeline.Get());
637 list->Dispatch((commandCount + PROBE_LINEAR_GROUP_SIZE - 1) / PROBE_LINEAR_GROUP_SIZE, 1, 1);
638 list->ResourceBarrier(1, &allUavs);
639 }
640
641 // CPU 側の控え: 足したコマンドを数え、このフレームで適用の単位を記録した刻み(nextApplyTick より前)の分を取り出し済みにする
642 void ProbeSim::TrackCommands(std::span<const ProbeCommand> commands, uint64_t nextApplyTick) {
643 for (const ProbeCommand& command : commands) {
644 if (m_queuedTicks.empty() || m_queuedTicks.back().targetTick != command.targetTick)
645 m_queuedTicks.push_back({.targetTick = command.targetTick});
646
647 ++m_queuedTicks.back().count;
648 }
649
650 m_queuedCommandCount += static_cast<uint32_t>(commands.size());
651 m_commandTail += static_cast<uint32_t>(commands.size());
652 if (!commands.empty()) {
653 m_hasEnqueued = true;
654 m_lastEnqueued = commands.back();
655 }
656
657 const auto applied = rng::find_if(m_queuedTicks,
658 [&](const QueuedTick& queued) { return queued.targetTick >= nextApplyTick; });
659 for (auto queued = m_queuedTicks.begin(); queued != applied; ++queued)
660 m_queuedCommandCount -= queued->count;
661
662 m_queuedTicks.erase(m_queuedTicks.begin(), applied);
663 }
664
665 void ProbeSim::BindRootArguments(ID3D12GraphicsCommandList10* list, ID3D12Resource* input) const {
666 list->SetComputeRootSignature(m_rootSignature.Get());
667 BindRootViews(list, input);
668 }
669
670 // ルートの引数: ROOT_LAYOUT の順(u0〜u12・デバッグのリング・Work Graphs のカウンタ・t0 フレームの入力・t1〜t4 反応の表)
671 void ProbeSim::BindRootViews(ID3D12GraphicsCommandList10* list, ID3D12Resource* input) const {
672 list->SetComputeRootUnorderedAccessView(UAV_CELLS, m_cells->GetGPUVirtualAddress());
673 list->SetComputeRootUnorderedAccessView(UAV_THERMAL, m_thermal->GetGPUVirtualAddress());
674 list->SetComputeRootUnorderedAccessView(UAV_EVENTS, m_events.GpuAddress());
675 list->SetComputeRootUnorderedAccessView(UAV_EXTRACTION0, m_extractions[0]->GetGPUVirtualAddress());
676 list->SetComputeRootUnorderedAccessView(UAV_EXTRACTION1, m_extractions[1]->GetGPUVirtualAddress());
677 list->SetComputeRootUnorderedAccessView(UAV_EXTRACTION2, m_extractions[2]->GetGPUVirtualAddress());
678 list->SetComputeRootUnorderedAccessView(UAV_BUSY_SINK, m_busySink->GetGPUVirtualAddress());
679 list->SetComputeRootUnorderedAccessView(UAV_HASHES, m_hashes->GetGPUVirtualAddress());
680 list->SetComputeRootUnorderedAccessView(UAV_COMMAND_QUEUE, m_commandQueue->GetGPUVirtualAddress());
681 list->SetComputeRootUnorderedAccessView(UAV_TICK_EVENTS, m_tickEvents->GetGPUVirtualAddress());
682 list->SetComputeRootUnorderedAccessView(UAV_ACTIVE_LIST0, m_activeLists[0]->GetGPUVirtualAddress());
683 list->SetComputeRootUnorderedAccessView(UAV_ACTIVE_LIST1, m_activeLists[1]->GetGPUVirtualAddress());
684 list->SetComputeRootUnorderedAccessView(UAV_BLOCK_SCHEDULE, m_blockSchedule->GetGPUVirtualAddress());
685 // 物理が無ければ、シェーダーは物の数 0 で読まないので、仮に重さの捨て場を結ぶ
686 list->SetComputeRootUnorderedAccessView(
687 UAV_BODIES, m_physics ? m_physics->Bodies()->GetGPUVirtualAddress() : m_busySink->GetGPUVirtualAddress());
688 list->SetComputeRootUnorderedAccessView(ROOT_LAYOUT.DebugRingIndex(), m_debugRing.GpuAddress());
689 list->SetComputeRootUnorderedAccessView(ROOT_LAYOUT.GraphStatsIndex(), m_graphStats.GpuAddress());
690 list->SetComputeRootUnorderedAccessView(ROOT_LAYOUT.GraphTraceIndex(), m_graphTrace.GpuAddress());
691 list->SetComputeRootShaderResourceView(ROOT_LAYOUT.SrvIndex(SRV_INPUT), input->GetGPUVirtualAddress());
692 for (uint32_t index = 0; index < m_reactionTable.size(); ++index) {
693 list->SetComputeRootShaderResourceView(ROOT_LAYOUT.SrvIndex(SRV_REACTION_FIRST + index),
694 m_reactionTable[index]->GetGPUVirtualAddress());
695 }
696 }
697
698 // 1 つの単位(probe_sim.hlsli の単位の表)。最後に UAV バリアで、次の単位が結果を読めるようにする
699 void ProbeSim::RecordUnit(ID3D12GraphicsCommandList10* list, ID3D12Resource* input, uint64_t tick, uint32_t unit,
700 bool tableSwapped) {
701 const D3D12_RESOURCE_BARRIER allUavs = gpu::UavBarrier(nullptr);
702 SetUnitConstants(list, tick, 0);
703 if (unit == PROBE_UNIT_APPLY) {
704 // コマンドの適用は 1 スレッドがキューの先頭から番号順に(probe_tick.hlsl)。刻みの一覧と表の欄の用意も
705 list->SetPipelineState(m_applyPipeline.Get());
706 list->Dispatch(1, 1, 1);
707
708 // 表を差し替えた刻み: 熱のキャッシュ(2 世代)を新しい表で作り直し、全部のブロックの起こす刻みを今にする(T-0139)
709 if (tableSwapped) {
710 list->ResourceBarrier(1, &allUavs);
711 list->SetPipelineState(m_refreshTablePipeline.Get());
712 list->Dispatch(2 * LINEAR_CELL_GROUPS, 1, 1);
713 }
714
715 // 起こす刻みの来たブロックを同じ一覧へ(適用が一覧の見出しを整えた後。待ちの丸め。T-0122)
716 list->ResourceBarrier(1, &allUavs);
717 list->SetPipelineState(m_wakeDuePipeline.Get());
718 list->Dispatch(BLOCK_GROUPS, 1, 1);
719 } else if (unit == PROBE_UNIT_CONDUCT)
720 RecordConduct(list, input, tick);
721 else if (m_physics && unit == PROBE_UNIT_PHYSICS)
722 RecordPhysics(list, input, tick);
723 else if (unit == HashUnit()) {
724 // 表の欄は適用の単位が用意してある(刻み・0)
725 list->SetPipelineState(m_hashCellsPipeline.Get());
726 list->Dispatch(LINEAR_CELL_GROUPS, 1, 1);
727 // 刻みのイベントを並べてリングへ(ハッシュとは別のバッファなので間のバリアは要らない)
728 list->SetPipelineState(m_flushEventsPipeline.Get());
729 list->Dispatch(1, 1, 1);
730 } else {
731 list->SetPipelineState(m_busyPipeline.Get());
732 list->Dispatch(BUSY_GROUPS, BUSY_GROUPS, 1);
733 }
734
735 list->ResourceBarrier(1, &allUavs);
736 }
737
738 // 反応表の差し替え(T-0139・ADR-0047): 新しい表を新しい既定のバッファに写し、ルートの結び先を替える。差し替える前の表は
739 // このフレームの前の単位と、まだ GPU にある前のフレームが読むので、このリストが終わるまで持つ(状態を追わないよう、毎回新しく作る)
740 bool ProbeSim::RecordTableSwap(ID3D12GraphicsCommandList10* list, FrameSlot& frame,
741 const BakedReactionTable& table) {
742 ComPtr<ID3D12Device5> device;
743 if (FAILED(list->GetDevice(IID_PPV_ARGS(&device))))
744 return false;
745
746 const std::array<ComPtr<ID3D12Resource>, 4> uploads = {
747 CreateFilledUpload(device.Get(), table.species, L"ProbeSim.swap.species"),
748 CreateFilledUpload(device.Get(), table.rules, L"ProbeSim.swap.rules"),
749 CreateFilledUpload(device.Get(), table.ruleIndex, L"ProbeSim.swap.ruleIndex"),
750 CreateFilledUpload(device.Get(), table.rates, L"ProbeSim.swap.rates")};
751
752 std::array<D3D12_RESOURCE_BARRIER, 4> barriers{};
753 for (size_t index = 0; index < m_reactionTable.size(); ++index) {
754 if (!uploads[index])
755 return false;
756
757 const uint64_t bytes = uploads[index]->GetDesc().Width;
758 ComPtr<ID3D12Resource> buffer = gpu::CreateBuffer(device.Get(), bytes, gpu::BufferKind::UnorderedAccess);
759 if (!buffer)
760 return false;
761
762 // 作ったバッファは COMMON(写すときに COPY_DEST へ暗黙に昇格する。RecordInitialization と同じ)
763 list->CopyBufferRegion(buffer.Get(), 0, uploads[index].Get(), 0, bytes);
764 barriers[index] = gpu::Transition(buffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
765 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
766 frame.keepAlive.push_back(uploads[index]);
767 frame.keepAlive.push_back(std::move(m_reactionTable[index]));
768 m_reactionTable[index] = std::move(buffer);
769 }
770
771 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
772 BindRootViews(list, frame.input.Get());
773
774 return true;
775 }
776
777 // 伝導: 刻み t の活性の一覧を GPU の入力にして DispatchGraph(何ブロック計算するかは GPU だけが知っている)。
778 // 一覧は GPU の入力の間だけ読む状態(仕様: NON_PIXEL_SHADER_RESOURCE か COMMON)。グラフは次の刻みの一覧(もう一方の組)に UAV で書く
779 void ProbeSim::RecordConduct(ID3D12GraphicsCommandList10* list, ID3D12Resource* input, uint64_t tick) {
780 ID3D12Resource* activeList = m_activeLists[tick & 1].Get();
781 const D3D12_RESOURCE_BARRIER toInput = gpu::Transition(activeList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
782 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
783 list->ResourceBarrier(1, &toInput);
784
785 // 裏のメモリの初期化は最初の 1 回だけ(リストは記録した順に実行される)。グラフを設定してからルートの引数を結び直す
786 m_conductGraph->SetProgram(list, !m_conductInitialized);
788 BindRootViews(list, input);
789 SetUnitConstants(list, tick, 0);
790 gpu::WorkGraph::DispatchFromGpu(list, activeList->GetGPUVirtualAddress());
791
792 const D3D12_RESOURCE_BARRIER toUav = gpu::Transition(activeList, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
793 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
794 list->ResourceBarrier(1, &toUav);
795 }
796
797 // 最初のフレームだけ: 物の初めの状態(物理のルート署名で記録するので、仮の刻みのルートを結び直す)
798 void ProbeSim::RecordPhysicsInitialization(ID3D12GraphicsCommandList10* list, ID3D12Resource* input) {
800 return;
801
802 m_physics->RecordInitialize(list, m_debugRing.GpuAddress());
804 BindRootArguments(list, input);
805 }
806
807 // 物理: 物の 1 刻み(sim/gpu_physics。広域と接触の Work Graph・色ごとの Compute)。物理のルート署名で記録するので、終わったら結び直す。
808 // 物理の刻みは世界の刻みと同じ番号で進む(1 刻みに 1 回、刻みの順に記録する)
809 void ProbeSim::RecordPhysics(ID3D12GraphicsCommandList10* list, ID3D12Resource* input, uint64_t tick) {
810 if (m_physics->Tick() != tick)
811 Log(Channel::Sim, Level::Error, "物理の刻みが世界の刻みとずれた: 物理 {} 世界 {}", m_physics->Tick(), tick);
812
813 m_physics->RecordStep(list, m_debugRing.GpuAddress());
814 BindRootArguments(list, input);
815 }
816
817 void ProbeSim::RecordActiveListStates(ID3D12GraphicsCommandList10* list, D3D12_RESOURCE_STATES before,
818 D3D12_RESOURCE_STATES after) const {
819 const std::array<D3D12_RESOURCE_BARRIER, 2> barriers = {gpu::Transition(m_activeLists[0].Get(), before, after),
820 gpu::Transition(m_activeLists[1].Get(), before, after)};
821 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
822 }
823
824 void ProbeSim::RecordExtract(ID3D12GraphicsCommandList10* list, uint64_t tick, uint32_t target) const {
825 SetUnitConstants(list, tick, target);
826 list->SetPipelineState(m_extractPipeline.Get());
827 list->Dispatch(LINEAR_CELL_GROUPS, 1, 1);
828 }
829
830 // イベント・デバッグの出力・ノードのカウンタ・連鎖のトレース・(ハッシュの単位があれば)ハッシュの表を、slot の読み戻しのバッファへ
831 void ProbeSim::RecordReadbacks(ID3D12GraphicsCommandList10* list, uint32_t slot, bool hasHash) const {
832 m_events.RecordReadbackAndReset(list, slot);
833 m_debugRing.RecordReadbackAndReset(list, slot);
834 m_graphStats.RecordReadbackAndReset(list, slot);
835 m_graphTrace.RecordReadbackAndReset(list, slot);
836
837 if (hasHash) {
838 gpu::RecordCopyToReadback(list, m_hashes.Get(), m_slots[slot].hashReadback.Get());
839 const D3D12_RESOURCE_BARRIER toCommon = gpu::Transition(m_hashes.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE,
840 D3D12_RESOURCE_STATE_COMMON);
841 list->ResourceBarrier(1, &toCommon);
842
843 return;
844 }
845
846 const D3D12_RESOURCE_BARRIER toCommon = gpu::Transition(m_hashes.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
847 D3D12_RESOURCE_STATE_COMMON);
848 list->ResourceBarrier(1, &toCommon);
849 }
850
851 // --- 読み戻し ---
852
854 ProbeFrameReadback result;
855 const FrameSlot& frame = m_slots[slot];
856
857 // イベント: 見出しを先に読み、書かれた分だけを読む(並びは GPU が刻みの最後に並べた (刻み, 種類, 場所) の順)
858 EventRingHeader header;
859 const bool headerRead = m_events.Read(slot, std::as_writable_bytes(std::span(&header, 1)));
860 const uint32_t stored = std::min(header.requested, PROBE_EVENT_CAPACITY);
861 std::vector<uint32_t> words(PROBE_EVENT_HEADER_BYTES / 4 + size_t{stored} * PROBE_EVENT_WORDS);
862
863 if (headerRead && (stored == 0 || m_events.Read(slot, std::as_writable_bytes(std::span(words))))) {
864 result.droppedEventCount = header.requested - stored + header.tickDropped;
865 result.events.reserve(stored);
866 for (uint32_t index = 0; index < stored; ++index) {
867 const uint32_t* record = words.data() + PROBE_EVENT_HEADER_BYTES / 4 +
868 size_t{index} * PROBE_EVENT_WORDS;
869 result.events.push_back(
870 {.tick = record[0] | (uint64_t{record[1]} << 32), .type = record[2], .place = record[3]});
871 }
872 }
873
874 // タイムスタンプ: [0] 始め、[1 + i] 単位 i の終わり、[1 + 数] 全体の終わり
875 std::vector<uint64_t> timestamps(size_t{frame.unitCount} + 2);
876 if (gpu::ReadBuffer(frame.timestampReadback.Get(), std::as_writable_bytes(std::span(timestamps)))) {
877 result.gpuBeginTimestamp = timestamps.front();
878 result.gpuEndTimestamp = timestamps.back();
879 result.unitGpuTicks.reserve(frame.unitCount);
880
881 for (uint32_t index = 0; index < frame.unitCount; ++index) {
882 const uint64_t begin = timestamps[index];
883 const uint64_t end = timestamps[size_t{index} + 1];
884 result.unitGpuTicks.push_back(end >= begin ? end - begin : 0);
885 }
886 }
887
888 result.firstUnit = frame.firstUnit;
889 result.hashes = ReadHashes(frame);
890 result.debugAssertCount = m_debugRing.Drain(MAX_LOGGED_DEBUG_MESSAGES, slot).assertCount;
891
892 // ノードのカウンタ: 要約は Trace、上限に当たった・近づいたものは Warning(gpu/work_graph_stats.h)
893 auto graphStats = m_graphStats.Read(slot);
894 if (graphStats) {
895 result.graphFindingCount = static_cast<uint32_t>(m_graphStats.Report(*graphStats).size());
896 result.graphStats = std::move(*graphStats);
897 } else
898 Log(Channel::WorkGraph, Level::Warning, "{}", graphStats.error());
899
900 // 連鎖のトレース(範囲が無効なら空)
901 auto trace = m_graphTrace.Read(slot);
902 if (trace) {
903 result.trace = std::move(trace->records);
904 result.droppedTraceCount = trace->droppedCount;
905 } else
906 Log(Channel::WorkGraph, Level::Warning, "{}", trace.error());
907
908 return result;
909 }
910
911 // このフレームで終えた刻み t ごとに、表の (t + 1) 番目の S(t + 1) のハッシュ
912 std::vector<ProbeTickHash> ProbeSim::ReadHashes(const FrameSlot& frame) const {
913 std::vector<ProbeTickHash> hashes;
914 const uint64_t unitsBefore = frame.firstUnit;
915 const uint64_t unitsTotal = unitsBefore + frame.unitCount;
916 // firstTick から数えて終えた刻み(途中から始めた刻みを含む)
917 const uint64_t completedTicks = unitsTotal / UnitsPerTick();
918 if (completedTicks == 0)
919 return hashes;
920
921 std::vector<uint32_t> table(PROBE_HASH_BYTES / 4);
922 if (!gpu::ReadBuffer(frame.hashReadback.Get(), std::as_writable_bytes(std::span(table))))
923 return hashes;
924
925 hashes.reserve(completedTicks);
926 for (uint64_t offset = 1; offset <= completedTicks; ++offset) {
927 const uint64_t stateTick = frame.firstTick + offset;
928 const uint32_t* entry = table.data() + (stateTick % PROBE_HASH_CAPACITY) * (PROBE_HASH_ENTRY_BYTES / 4);
929 const uint64_t storedTick = entry[0] | (uint64_t{entry[1]} << 32);
930
931 if (storedTick != stateTick) {
932 Log(Channel::Sim, Level::Error, "ハッシュの表の刻みが合わない: 期待 {} 実際 {}", stateTick, storedTick);
933 continue;
934 }
935
936 hashes.push_back({.tick = stateTick,
937 .hash = entry[2] | (uint64_t{entry[3]} << 32),
938 .energy = entry[4] | (uint64_t{entry[5]} << 32),
939 .sourceEnergy = entry[8] | (uint64_t{entry[9]} << 32),
940 .scheduledBlocks = entry[6],
941 .bodyHash = entry[10] | (uint64_t{entry[11]} << 32)});
942 }
943
944 return hashes;
945 }
946
947 // --- 初めの世界(T-0089)---
948
949 namespace {
950
951 // 1 気圧・300 K の 0.5 m 角(0.125 m³)の空気 = 約 5.08 mol(O2 20.95 %・残りを N2。Ar は無視)
952 constexpr uint64_t AIR_OXYGEN_MICROMOLES = 1063800;
953 constexpr uint64_t AIR_NITROGEN_MICROMOLES = 4014200;
954
955 // 木箱の壁のセル: 体積の 1 割が木(密度 500 kg/m³ のセルロースで約 38.6 mol)、残りは孔の中の空気
956 // (reaction_test.cpp の CrateAir と同じ値)
957 constexpr uint64_t CRATE_CELLULOSE_MICROMOLES = 38600000;
958 constexpr uint64_t CRATE_OXYGEN_MICROMOLES = 983000;
959 constexpr uint64_t CRATE_NITROGEN_MICROMOLES = 3697000;
960
961 constexpr int32_t INITIAL_TEMPERATURE_MILLIKELVIN = 300000;
962
963 // 木箱: 中央の 8³ セル(4 m 角)、壁の厚さ 1 セル
964 constexpr uint32_t CRATE_SIZE = 8;
965 constexpr uint32_t CRATE_BEGIN = (PROBE_GRID_SIZE - CRATE_SIZE) / 2;
966 constexpr uint32_t CRATE_END = CRATE_BEGIN + CRATE_SIZE;
967
968 bool InCrate(uint32_t value) {
969 return value >= CRATE_BEGIN && value < CRATE_END;
970 }
971
972 bool OnCrateWall(uint32_t value) {
973 return value == CRATE_BEGIN || value == CRATE_END - 1;
974 }
975
976 bool IsCrateWall(uint32_t x, uint32_t y, uint32_t z) {
977 return InCrate(x) && InCrate(y) && InCrate(z) && (OnCrateWall(x) || OnCrateWall(y) || OnCrateWall(z));
978 }
979
980 } // namespace
981
982 std::vector<reaction::RxCell> MakeProbeInitialWorld(const BakedReactionTable& table) {
983 const uint32_t cellulose = table.SpeciesId("cellulose");
984 const uint32_t oxygen = table.SpeciesId("oxygen");
985 const uint32_t nitrogen = table.SpeciesId("nitrogen");
986 const std::array<SpeciesAmount, 2> air = {
987 SpeciesAmount{.species = oxygen, .amount = AIR_OXYGEN_MICROMOLES},
988 SpeciesAmount{.species = nitrogen, .amount = AIR_NITROGEN_MICROMOLES}};
989 const std::array<SpeciesAmount, 3> wall = {
990 SpeciesAmount{.species = cellulose, .amount = CRATE_CELLULOSE_MICROMOLES},
991 SpeciesAmount{.species = oxygen, .amount = CRATE_OXYGEN_MICROMOLES},
992 SpeciesAmount{.species = nitrogen, .amount = CRATE_NITROGEN_MICROMOLES}};
993
994 const reaction::RxCell airCell = MakeReactionCell(table, air, INITIAL_TEMPERATURE_MILLIKELVIN);
995 const reaction::RxCell wallCell = MakeReactionCell(table, wall, INITIAL_TEMPERATURE_MILLIKELVIN);
996
997 std::vector<reaction::RxCell> cells(PROBE_CELL_COUNT, airCell);
998 for (uint32_t index = 0; index < PROBE_CELL_COUNT; ++index) {
999 const uint32_t x = index % PROBE_GRID_SIZE;
1000 const uint32_t y = (index / PROBE_GRID_SIZE) % PROBE_GRID_SIZE;
1001 const uint32_t z = index / PROBE_SLICE_CELL_COUNT;
1002 if (IsCrateWall(x, y, z))
1003 cells[index] = wallCell;
1004 }
1005
1006 return cells;
1007 }
1008
1009 std::array<uint32_t, PROBE_VIEW_SPECIES_COUNT> ProbeViewSpecies(const BakedReactionTable& table) {
1010 return {table.SpeciesId("oxygen"), table.SpeciesId("carbon_dioxide"), table.SpeciesId("carbon")};
1011 }
1012
1013 // --- CPU リファレンス ---
1014
1015 namespace {
1016
1017 // ブロック block と 6 面の隣(格子の中)に印を付ける
1018 void MarkWithNeighbors(std::vector<uint8_t>& scheduled, uint32_t block) {
1019 constexpr auto AXIS = static_cast<int32_t>(PROBE_BLOCKS_PER_AXIS);
1020 constexpr std::array<std::array<int32_t, 3>, 7> OFFSETS = {
1021 {{0, 0, 0}, {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}}};
1022 const auto x = static_cast<int32_t>(block % PROBE_BLOCKS_PER_AXIS);
1023 const auto y = static_cast<int32_t>((block / PROBE_BLOCKS_PER_AXIS) % PROBE_BLOCKS_PER_AXIS);
1024 const auto z = static_cast<int32_t>(block / (PROBE_BLOCKS_PER_AXIS * PROBE_BLOCKS_PER_AXIS));
1025
1026 for (const auto& offset : OFFSETS) {
1027 const int32_t nx = x + offset[0];
1028 const int32_t ny = y + offset[1];
1029 const int32_t nz = z + offset[2];
1030 if (nx < 0 || ny < 0 || nz < 0 || nx >= AXIS || ny >= AXIS || nz >= AXIS)
1031 continue;
1032
1033 scheduled[ProbeBlockIndex(static_cast<uint32_t>(nx), static_cast<uint32_t>(ny),
1034 static_cast<uint32_t>(nz))] = 1;
1035 }
1036 }
1037
1038 // seeds(ブロックごとの 0/1)のブロックと 6 面の隣(GPU の WakeBlocks が予定するブロック)
1039 std::vector<uint8_t> ScheduleBlocks(std::span<const uint8_t> seeds) {
1040 std::vector<uint8_t> scheduled(PROBE_BLOCK_COUNT, 0);
1041 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
1042 if (seeds[block] != 0)
1043 MarkWithNeighbors(scheduled, block);
1044 }
1045
1046 return scheduled;
1047 }
1048
1049 // 前の刻みに計算しなかった(GPU では眠っていた)のに、この刻みに評価が要るブロック = GPU の WakeDueBlocks が起こすブロックの数
1050 uint32_t CountWokenBlocks(std::span<const uint8_t> flags, std::span<const uint8_t> previousScheduled) {
1051 uint32_t woken = 0;
1052 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
1053 if ((flags[block] & PROBE_BLOCK_FLAG_POSSIBLE) != 0 && previousScheduled[block] == 0)
1054 ++woken;
1055 }
1056
1057 return woken;
1058 }
1059
1060 // 1 世代のセルとキャッシュ
1062 reaction::RxCell* cells;
1063 reaction::HcThermalCache* caches;
1064 };
1065
1066 // 刻み tick のつつきを current に適用し、つついたブロックに印を付ける(GPU と同じく並びの順に)。足したエネルギーを返す。
1067 // つついたブロックは「刻みの直前に変わった」(tc = 刻み tick の印 − 1。probe_tick.hlsl の ApplyCommand と同じ)
1068 uint64_t ApplyPokes(const ReactionTableView& table, ReferenceGeneration current, uint64_t tick,
1069 std::span<const ProbeCommand> commands, std::vector<uint8_t>& seeds,
1070 std::vector<uint64_t>& changedMarks) {
1071 uint64_t source = 0;
1072 for (const ProbeCommand& command : commands) {
1073 if (command.targetTick != tick || command.type != PROBE_COMMAND_TYPE_POKE)
1074 continue;
1075
1076 const uint32_t x = command.payload[0];
1077 const uint32_t y = command.payload[1];
1078 const uint32_t z = command.payload[2];
1079 if (x >= PROBE_GRID_SIZE || y >= PROBE_GRID_SIZE || z >= PROBE_GRID_SIZE)
1080 continue;
1081
1082 const uint32_t index = ProbeCellIndex(x, y, z);
1083 const int64_t energy = ProbePokeEnergy(table, current.cells[index]);
1084 current.cells[index].energy += energy;
1085 current.caches[index] = ProbeMakeCache(table, current.cells[index]);
1086 source += static_cast<uint64_t>(energy);
1087 seeds[ProbeBlockOfCell(x, y, z)] = 1;
1088 changedMarks[ProbeBlockOfCell(x, y, z)] = ProbeChangeMark(tick) - 1;
1089 }
1090
1091 return source;
1092 }
1093
1094 // 1 セルの伝導と反応(GPU の ConductBlock と同じ。格子の外の面は自分を渡す = 断熱)
1095 ProbeCellStep ReferenceStepCell(const ReactionTableView& table, ReferenceGeneration current, uint32_t index,
1096 uint64_t tick, uint64_t changedMark) {
1097 const uint32_t x = index % PROBE_GRID_SIZE;
1098 const uint32_t y = (index / PROBE_GRID_SIZE) % PROBE_GRID_SIZE;
1099 const uint32_t z = index / PROBE_SLICE_CELL_COUNT;
1100 const reaction::HcThermalCache self = current.caches[index];
1101 const auto at = [&](bool inside, uint32_t nx, uint32_t ny, uint32_t nz) {
1102 return inside ? current.caches[ProbeCellIndex(nx, ny, nz)] : self;
1103 };
1104
1105 constexpr uint32_t LAST = PROBE_GRID_SIZE - 1;
1106
1107 return ProbeStepCell(table, current.cells[index], self, at(x > 0, x - 1, y, z), at(x < LAST, x + 1, y, z),
1108 at(y > 0, x, y - 1, z), at(y < LAST, x, y + 1, z), at(z > 0, x, y, z - 1),
1109 at(z < LAST, x, y, z + 1), tick, changedMark, index);
1110 }
1111
1112 uint32_t BlockOfCellIndex(uint32_t index) {
1113 return ProbeBlockOfCell(index % PROBE_GRID_SIZE, (index / PROBE_GRID_SIZE) % PROBE_GRID_SIZE,
1114 index / PROBE_SLICE_CELL_COUNT);
1115 }
1116
1117 } // namespace
1118
1119 std::vector<uint64_t> ProbeInitialBlockWakes(const BakedReactionTable& table,
1120 std::span<const reaction::RxCell> cells) {
1121 FX_ASSERT(cells.size() == PROBE_CELL_COUNT);
1122
1123 // 刻み 0 を tc = 0 で全部計算してみる(結果は捨てる)。変わるセルがあるブロックは刻み 0 に計算が要る(印 = 刻み 0 の印)。
1124 // 変わらないブロックは、刻み 0 を計算しなくても同じなので、セルが返す次に評価の要る刻みの最小
1125 const ReactionTableView view = table.View();
1126 std::vector<reaction::RxCell> trialCells(cells.begin(), cells.end());
1127 std::vector<reaction::HcThermalCache> caches;
1128 caches.reserve(cells.size());
1129 for (const reaction::RxCell& cell : cells)
1130 caches.push_back(ProbeMakeCache(view, cell));
1131
1132 const ReferenceGeneration initial{.cells = trialCells.data(), .caches = caches.data()};
1133 std::vector<uint64_t> wakes(PROBE_BLOCK_COUNT, reaction::RX_WAIT_NEVER);
1134 for (uint32_t index = 0; index < PROBE_CELL_COUNT; ++index) {
1135 const ProbeCellStep step = ReferenceStepCell(view, initial, index, 0, 0);
1136 uint64_t& wake = wakes[BlockOfCellIndex(index)];
1137 wake = std::min(wake, step.changed != 0 ? ProbeChangeMark(0) : step.wakeTick);
1138 }
1139
1140 return wakes;
1141 }
1142
1143 std::vector<uint32_t> ProbeInitialScheduleWords(const BakedReactionTable& table,
1144 std::span<const reaction::RxCell> cells) {
1145 std::vector<uint32_t> words(PROBE_SCHEDULE_BYTES / 4, 0);
1146 const std::vector<uint64_t> wakes = ProbeInitialBlockWakes(table, cells);
1147 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
1148 const uint32_t word = PROBE_SCHEDULE_WAKE_WORD + (block * 2);
1149 words[word] = static_cast<uint32_t>(wakes[block]);
1150 words[word + 1] = static_cast<uint32_t>(wakes[block] >> 32);
1151 }
1152
1153 return words;
1154 }
1155
1156 ProbeReference::ProbeReference(const BakedReactionTable& table, std::span<const reaction::RxCell> initialWorld)
1157 : m_table(&table),
1158 m_blockFlags(PROBE_BLOCK_COUNT, 0),
1159 m_changedMarks(PROBE_BLOCK_COUNT, 0),
1160 m_scheduled(PROBE_BLOCK_COUNT, 0) {
1161 FX_ASSERT(initialWorld.empty() || initialWorld.size() == PROBE_CELL_COUNT);
1162 const std::vector<reaction::RxCell> initial = initialWorld.empty()
1163 ? MakeProbeInitialWorld(table)
1164 : std::vector<reaction::RxCell>(initialWorld.begin(),
1165 initialWorld.end());
1166
1167 // 刻み 0 の予定の種: 初めの起こす刻みが刻み 0 の印以下のブロック(GPU の WakeDueBlocks と同じ)
1168 const std::vector<uint64_t> wakes = ProbeInitialBlockWakes(table, initial);
1169 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
1170 if (wakes[block] <= ProbeChangeMark(0))
1171 m_blockFlags[block] = static_cast<uint8_t>(PROBE_BLOCK_FLAG_POSSIBLE);
1172 }
1173
1174 m_cells.reserve(size_t{PROBE_CELL_COUNT} * 2);
1175 m_cells.insert(m_cells.end(), initial.begin(), initial.end());
1176 m_cells.insert(m_cells.end(), initial.begin(), initial.end());
1177
1178 m_caches.reserve(m_cells.size());
1179 for (const reaction::RxCell& cell : m_cells)
1180 m_caches.push_back(ProbeMakeCache(table.View(), cell));
1181 }
1182
1183 void ProbeReference::Advance(uint64_t tick, std::span<const ProbeCommand> commands,
1184 const BakedReactionTable* newTable) {
1185 if (newTable != nullptr)
1186 m_table = newTable;
1187
1188 const ReactionTableView table = m_table->View();
1189 const size_t currentBase = static_cast<size_t>(tick & 1) * PROBE_CELL_COUNT;
1190 const size_t nextBase = static_cast<size_t>((tick + 1) & 1) * PROBE_CELL_COUNT;
1191 const ReferenceGeneration current{.cells = m_cells.data() + currentBase,
1192 .caches = m_caches.data() + currentBase};
1193
1194 // (1) コマンドの適用。つついたブロックは、前の刻みで変わった・次の刻みに評価の要るブロックと同じく予定の種になる
1195 std::vector<uint8_t> seeds = m_blockFlags;
1196 m_sourceEnergy = ApplyPokes(table, current, tick, commands, seeds, m_changedMarks);
1197
1198 // 表を差し替えた刻み(GPU の RefreshTable と同じ。T-0139): 2 世代の熱のキャッシュを新しい表で作り直し、
1199 // 全部のブロックを「刻みの直前に変わった」にして起こす(速さ f が変わるので待ちを引き直す。ADR-0018)
1200 if (newTable != nullptr) {
1201 for (size_t index = 0; index < m_cells.size(); ++index)
1202 m_caches[index] = ProbeMakeCache(table, m_cells[index]);
1203
1204 rng::fill(m_changedMarks, ProbeChangeMark(tick) - 1);
1205 rng::fill(seeds, uint8_t{1});
1206 }
1207 m_wokenBlocks = CountWokenBlocks(m_blockFlags, m_scheduled);
1208 m_scheduled = ScheduleBlocks(seeds);
1209 m_scheduledBlocks = static_cast<uint32_t>(rng::count(m_scheduled, uint8_t{1}));
1210
1211 // (2) 伝導と反応(全部のセル。tc は刻みの初めの値)と、ブロックごとの変わったか・次に評価の要る刻みの最小
1212 std::vector<uint8_t> changed(PROBE_BLOCK_COUNT, 0);
1213 std::vector<uint64_t> wakes(PROBE_BLOCK_COUNT, reaction::RX_WAIT_NEVER);
1214 for (uint32_t index = 0; index < PROBE_CELL_COUNT; ++index) {
1215 const uint32_t block = BlockOfCellIndex(index);
1216 const ProbeCellStep step = ReferenceStepCell(table, current, index, tick, m_changedMarks[block]);
1217 m_cells[nextBase + index] = step.cell;
1218 m_caches[nextBase + index] = step.cache;
1219
1220 changed[block] |= static_cast<uint8_t>(step.changed);
1221 wakes[block] = std::min(wakes[block], step.wakeTick);
1222 }
1223
1224 // (3) 次の刻みの予定の種(GPU の ConductBlock と同じ。眠っているブロックの起こす刻みは、計算し直しても同じ値になる)
1225 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
1226 if (changed[block] != 0)
1227 m_changedMarks[block] = ProbeChangeMark(tick);
1228
1229 const bool possible = wakes[block] <= ProbeChangeMark(tick + 1);
1230 m_blockFlags[block] = static_cast<uint8_t>((changed[block] != 0 ? PROBE_BLOCK_FLAG_CHANGED : 0) |
1231 (possible ? PROBE_BLOCK_FLAG_POSSIBLE : 0));
1232 }
1233 }
1234
1235 std::span<const reaction::RxCell> ProbeReference::State(uint64_t tick) const {
1236 return std::span(m_cells).subspan(static_cast<size_t>(tick & 1) * PROBE_CELL_COUNT, PROBE_CELL_COUNT);
1237 }
1238
1239 uint64_t ProbeStateHash(std::span<const reaction::RxCell> cells) {
1240 uint64_t hash = 0;
1241 for (uint32_t index = 0; index < cells.size(); ++index)
1242 hash += ProbeCellHash(index, cells[index]);
1243
1244 return hash;
1245 }
1246
1247 std::span<const reaction::HcThermalCache> ProbeReference::Caches(uint64_t tick) const {
1248 return std::span(m_caches).subspan(static_cast<size_t>(tick & 1) * PROBE_CELL_COUNT, PROBE_CELL_COUNT);
1249 }
1250
1251 std::vector<uint32_t> MakeProbeExtractionCells(std::span<const reaction::RxCell> cells,
1252 std::span<const reaction::HcThermalCache> caches,
1253 const std::array<uint32_t, PROBE_VIEW_SPECIES_COUNT>& viewSpecies) {
1254 std::vector<uint32_t> words;
1255 words.reserve(cells.size() * PROBE_EXTRACTION_CELL_WORDS);
1256 for (size_t index = 0; index < cells.size(); ++index) {
1257 words.push_back(caches[index].temperature);
1258 for (const uint32_t species : viewSpecies)
1259 words.push_back(ProbeViewAmount(cells[index], species));
1260 }
1261
1262 return words;
1263 }
1264
1265 uint64_t ProbeExtractionHash(std::span<const uint32_t> words) {
1266 uint64_t hash = 0;
1267 for (const uint32_t word : words)
1268 hash = fx::FxHashCombine(hash, word);
1269
1270 return hash;
1271 }
1272
1273 uint64_t ProbeEnergySum(std::span<const reaction::RxCell> cells) {
1274 uint64_t energy = 0;
1275 for (const reaction::RxCell& cell : cells)
1276 energy += static_cast<uint64_t>(cell.energy);
1277
1278 return energy;
1279 }
1280
1281} // namespace bicameral::sim
static std::expected< DebugRing, std::string > Create(ID3D12Device *device, uint32_t slotCount=1)
static std::expected< GraphTrace, std::string > Create(ID3D12Device *device, const GraphTraceFilter &filter, uint32_t slotCount=1, uint32_t capacity=0)
static std::expected< ReadbackRing, std::string > Create(ID3D12Device *device, uint64_t bytes, uint64_t headerBytes, uint32_t slotCount, std::wstring_view name)
static std::expected< WorkGraphStats, std::string > Create(ID3D12Device *device, GraphStatsLayout layout, uint32_t slotCount=1)
static std::expected< WorkGraph, std::string > Create(ID3D12Device5 *device, ID3D12RootSignature *globalRootSignature, std::span< const std::byte > library, std::wstring_view programName)
static void DispatchFromGpu(ID3D12GraphicsCommandList10 *list, D3D12_GPU_VIRTUAL_ADDRESS nodeGpuInput)
static std::expected< GpuPhysics, std::string > Create(ID3D12Device5 *device, const PhysicsScene &scene, const physics::PxParameters &parameters, const GpuPhysicsOptions &options={})
std::vector< uint64_t > m_changedMarks
Definition probe_sim.h:490
ProbeReference(const BakedReactionTable &table, std::span< const reaction::RxCell > initialWorld={})
std::vector< uint8_t > m_blockFlags
Definition probe_sim.h:489
std::span< const reaction::RxCell > State(uint64_t tick) const
std::vector< reaction::RxCell > m_cells
Definition probe_sim.h:487
const BakedReactionTable * m_table
Definition probe_sim.h:486
std::vector< reaction::HcThermalCache > m_caches
Definition probe_sim.h:488
std::span< const reaction::HcThermalCache > Caches(uint64_t tick) const
void Advance(uint64_t tick, std::span< const ProbeCommand > commands, const BakedReactionTable *newTable=nullptr)
std::vector< uint8_t > m_scheduled
Definition probe_sim.h:491
void RecordReadbacks(ID3D12GraphicsCommandList10 *list, uint32_t slot, bool hasHash) const
ComPtr< ID3D12PipelineState > m_enqueuePipeline
Definition probe_sim.h:372
bool CreateFrameSlots(ID3D12Device5 *device, D3D12_COMMAND_LIST_TYPE listType)
void BindRootViews(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input) const
void RecordExtract(ID3D12GraphicsCommandList10 *list, uint64_t tick, uint32_t target) const
gpu::ReadbackRing m_events
Definition probe_sim.h:414
void WriteInput(FrameSlot &frame, const ProbeFrameInput &input) const
std::expected< void, std::string > CreatePhysics(ID3D12Device5 *device)
ComPtr< ID3D12PipelineState > m_applyPipeline
Definition probe_sim.h:373
std::array< ComPtr< ID3D12Resource >, 2 > m_activeLists
Definition probe_sim.h:410
std::array< ComPtr< ID3D12Resource >, PROBE_EXTRACTION_COUNT > m_extractions
Definition probe_sim.h:396
bool ValidateCommands(const ProbeFrameInput &input) const
ComPtr< ID3D12PipelineState > m_hashCellsPipeline
Definition probe_sim.h:378
bool CreateConductGraph(ID3D12Device5 *device)
void RecordInitialization(ID3D12GraphicsCommandList10 *list)
ComPtr< ID3D12PipelineState > m_flushEventsPipeline
Definition probe_sim.h:379
bool CreatePipelines(ID3D12Device5 *device)
ComPtr< ID3D12Resource > m_busySink
Definition probe_sim.h:397
ComPtr< ID3D12PipelineState > m_refreshTablePipeline
Definition probe_sim.h:376
bool CreateWorld(ID3D12Device5 *device, const BakedReactionTable &table, std::span< const reaction::RxCell > initialWorld)
gpu::DebugRing m_debugRing
Definition probe_sim.h:415
std::array< FrameSlot, FRAME_SLOT_COUNT > m_slots
Definition probe_sim.h:419
std::array< ComPtr< ID3D12Resource >, 7 > m_initialUploads
Definition probe_sim.h:393
void TrackCommands(std::span< const ProbeCommand > commands, uint64_t nextApplyTick)
ComPtr< ID3D12PipelineState > m_extractPipeline
Definition probe_sim.h:380
gpu::GraphTrace m_graphTrace
Definition probe_sim.h:417
std::vector< ProbeTickHash > ReadHashes(const FrameSlot &frame) const
bool ValidateInput(uint32_t slot, const ProbeFrameInput &input) const
ComPtr< ID3D12PipelineState > m_busyPipeline
Definition probe_sim.h:377
std::unique_ptr< GpuPhysics > m_physics
Definition probe_sim.h:400
void RecordPhysics(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input, uint64_t tick)
ComPtr< ID3D12RootSignature > m_rootSignature
Definition probe_sim.h:371
void RecordSave(ID3D12GraphicsCommandList10 *list, uint32_t index, uint64_t tick)
std::vector< QueuedTick > m_queuedTicks
Definition probe_sim.h:433
ComPtr< ID3D12Resource > m_hashes
Definition probe_sim.h:405
uint32_t HashUnit() const
Definition probe_sim.h:218
bool ValidateSavePoints(const ProbeFrameInput &input) const
bool ValidateTableSwap(const ProbeFrameInput &input) const
ComPtr< ID3D12Resource > m_blockSchedule
Definition probe_sim.h:411
ComPtr< ID3D12QueryHeap > m_timestamps
Definition probe_sim.h:418
std::array< uint32_t, PROBE_VIEW_SPECIES_COUNT > m_viewSpecies
Definition probe_sim.h:395
ProbeCommand m_lastEnqueued
Definition probe_sim.h:435
ComPtr< ID3D12Resource > m_commandQueue
Definition probe_sim.h:406
ProbeSim(const ProbeSimOptions &options, gpu::ReadbackRing &&events, gpu::DebugRing &&debugRing, gpu::WorkGraphStats &&graphStats, gpu::GraphTrace &&graphTrace)
Definition probe_sim.h:298
void RecordExtractAndHook(ID3D12GraphicsCommandList10 *list, uint64_t tick, const ProbeFrameInput &input)
ComPtr< ID3D12Resource > m_tickEvents
Definition probe_sim.h:407
gpu::WorkGraphStats m_graphStats
Definition probe_sim.h:416
bool CreateBuffers(ID3D12Device5 *device)
void RecordActiveListStates(ID3D12GraphicsCommandList10 *list, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) const
ComPtr< ID3D12PipelineState > m_wakeDuePipeline
Definition probe_sim.h:374
bool RecordTableSwap(ID3D12GraphicsCommandList10 *list, FrameSlot &frame, const BakedReactionTable &table)
uint32_t UnitsPerTick() const
Definition probe_sim.h:214
ProbeSimOptions m_options
Definition probe_sim.h:368
std::array< ComPtr< ID3D12Resource >, 4 > m_reactionTable
Definition probe_sim.h:391
static constexpr uint32_t MAX_UNITS_PER_FRAME
Definition probe_sim.h:204
static std::expected< ProbeSim, std::string > Create(ID3D12Device5 *device, D3D12_COMMAND_LIST_TYPE listType, const BakedReactionTable &table, const ProbeSimOptions &options={})
static uint64_t NextApplyTick(uint64_t tick, uint32_t unit)
Definition probe_sim.h:222
void RecordRestore(ID3D12GraphicsCommandList10 *list, uint32_t index)
void RecordConduct(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input, uint64_t tick)
ComPtr< ID3D12Resource > m_cells
Definition probe_sim.h:388
void RecordEnqueue(ID3D12GraphicsCommandList10 *list, uint32_t commandCount) const
std::unique_ptr< gpu::WorkGraph > m_conductGraph
Definition probe_sim.h:383
void BindRootArguments(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input) const
uint32_t FreeCommandSlots() const
Definition probe_sim.h:227
ProbeFrameReadback ReadFrame(uint32_t slot)
static constexpr uint32_t FRAME_SLOT_COUNT
Definition probe_sim.h:203
ComPtr< ID3D12Resource > m_thermal
Definition probe_sim.h:389
void RecordUnit(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input, uint64_t tick, uint32_t unit, bool tableSwapped)
ID3D12CommandList * RecordFrame(uint32_t slot, const ProbeFrameInput &input)
void RecordPhysicsInitialization(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input)
ComPtr< ID3D12RootSignature > CreateRootSignature(ID3D12Device *device, const RootSignatureLayout &layout)
Definition resources.cpp:93
std::expected< std::vector< std::byte >, std::string > LoadShader(std::string_view relativePath)
bool ReadBuffer(ID3D12Resource *readback, std::span< std::byte > destination)
Definition resources.cpp:52
ComPtr< ID3D12PipelineState > CreateComputePipeline(ID3D12Device *device, ID3D12RootSignature *rootSignature, std::span< const std::byte > bytecode)
D3D12_RESOURCE_BARRIER Transition(ID3D12Resource *resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
Definition resources.cpp:65
ComPtr< ID3D12Resource > CreateBuffer(ID3D12Device *device, uint64_t sizeBytes, BufferKind kind)
Definition resources.cpp:18
void RecordCopyToReadback(ID3D12GraphicsCommandList *list, ID3D12Resource *source, ID3D12Resource *readback)
Definition resources.cpp:78
D3D12_RESOURCE_BARRIER UavBarrier(ID3D12Resource *resource)
Definition resources.cpp:74
void MarkWithNeighbors(std::vector< uint8_t > &scheduled, uint32_t block)
std::vector< uint8_t > ScheduleBlocks(std::span< const uint8_t > seeds)
void WriteAt(std::byte *base, uint32_t offset, const T &value)
Definition probe_sim.cpp:84
constexpr gpu::RootSignatureLayout ROOT_LAYOUT
Definition probe_sim.cpp:21
uint64_t ApplyPokes(const ReactionTableView &table, ReferenceGeneration current, uint64_t tick, std::span< const ProbeCommand > commands, std::vector< uint8_t > &seeds, std::vector< uint64_t > &changedMarks)
bool IsCrateWall(uint32_t x, uint32_t y, uint32_t z)
ProbeCellStep ReferenceStepCell(const ReactionTableView &table, ReferenceGeneration current, uint32_t index, uint64_t tick, uint64_t changedMark)
ComPtr< ID3D12Resource > CreateFilledUpload(ID3D12Device *device, const std::vector< T > &data, const wchar_t *name)
Definition probe_sim.cpp:90
uint32_t CountWokenBlocks(std::span< const uint8_t > flags, std::span< const uint8_t > previousScheduled)
ComPtr< ID3D12PipelineState > LoadComputePipeline(ID3D12Device *device, ID3D12RootSignature *rootSignature, std::string_view shaderPath)
void SetUnitConstants(ID3D12GraphicsCommandList10 *list, uint64_t tick, uint32_t argument)
constexpr bool CommandPrecedes(const Command &a, const Command &b)
Definition command.h:32
constexpr uint32_t NO_SAVE_POINT
Definition probe_sim.h:133
std::vector< uint64_t > ProbeInitialBlockWakes(const BakedReactionTable &table, std::span< const reaction::RxCell > cells)
std::vector< uint32_t > ProbeInitialScheduleWords(const BakedReactionTable &table, std::span< const reaction::RxCell > cells)
uint64_t ProbeStateHash(std::span< const reaction::RxCell > cells)
ProbeCommand MakeTableCommand(uint64_t targetTick, uint32_t sequence, uint64_t version)
std::array< uint32_t, PROBE_VIEW_SPECIES_COUNT > ProbeViewSpecies(const BakedReactionTable &table)
uint64_t ProbeExtractionHash(std::span< const uint32_t > words)
std::vector< uint32_t > MakeProbeExtractionCells(std::span< const reaction::RxCell > cells, std::span< const reaction::HcThermalCache > caches, const std::array< uint32_t, PROBE_VIEW_SPECIES_COUNT > &viewSpecies)
std::vector< reaction::RxCell > MakeProbeInitialWorld(const BakedReactionTable &table)
uint64_t ProbeEnergySum(std::span< const reaction::RxCell > cells)
Command ProbeCommand
Definition probe_sim.h:53
RxCell MakeReactionCell(const BakedReactionTable &table, std::span< const SpeciesAmount > amounts, int32_t temperatureMilliKelvin)
ProbeCommand MakePokeCommand(uint64_t targetTick, uint32_t sequence, uint32_t x, uint32_t y, uint32_t z)
ProbeCommand MakePushCommand(uint64_t targetTick, uint32_t sequence, const std::array< int64_t, 3 > &origin, const std::array< int32_t, 3 > &direction, uint32_t impulseMillinewtonSeconds)
void Log(Channel channel, Level level, FormatWithLocation< std::type_identity_t< Args >... > pattern, Args &&... args)
Definition log.h:140
std::vector< GraphNodeLimits > nodes
std::vector< GraphGaugeLimits > gauges
uint32_t SrvIndex(uint32_t shaderRegister) const
Definition resources.h:42
std::vector< uint32_t > ruleIndex
uint32_t SpeciesId(std::string_view name) const
std::vector< reaction::RxSpecies > species
std::vector< reaction::RxRule > rules
ReactionTableView View() const
std::array< uint32_t, COMMAND_PAYLOAD_WORDS > payload
Definition command.h:24
std::function< void(ID3D12GraphicsCommandList10 *list, const ProbeExtractContext &context)> afterExtract
Definition probe_sim.h:162
std::span< const ProbeCommand > commands
Definition probe_sim.h:154
std::span< const ProbeTableSwap > tableSwaps
Definition probe_sim.h:172
std::vector< ProbeTickHash > hashes
Definition probe_sim.h:104
gpu::GraphStatsSnapshot graphStats
Definition probe_sim.h:116
std::vector< gpu::GraphTraceRecord > trace
Definition probe_sim.h:120
std::vector< uint64_t > unitGpuTicks
Definition probe_sim.h:107
std::vector< ProbeEvent > events
Definition probe_sim.h:102
std::span< const reaction::RxCell > initialWorld
Definition probe_sim.h:196
gpu::GraphTraceFilter trace
Definition probe_sim.h:182
const BakedReactionTable * table
Definition probe_sim.h:142