22 .rootConstantCount = PROBE_ROOT_CONSTANT_COUNT,
46 constexpr uint64_t
CELL_BYTES = uint64_t{PROBE_CELL_COUNT} *
sizeof(reaction::RxCell);
47 constexpr uint64_t
THERMAL_BYTES = uint64_t{PROBE_CELL_COUNT} *
sizeof(reaction::HcThermalCache);
49 constexpr uint32_t
BUSY_GROUPS = PROBE_GRID_SIZE / PROBE_GROUP_SIZE;
51 constexpr uint32_t
BLOCK_GROUPS = (PROBE_BLOCK_COUNT + PROBE_LINEAR_GROUP_SIZE - 1) / PROBE_LINEAR_GROUP_SIZE;
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);
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);
63 static_assert(
sizeof(D3D12_NODE_GPU_INPUT) <= PROBE_ACTIVE_LIST_HEADER_BYTES);
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"};
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};
84 void WriteAt(std::byte* base, uint32_t offset,
const T& value) {
85 std::memcpy(base + offset, &value,
sizeof(T));
91 const wchar_t* name) {
92 const auto bytes = std::as_bytes(std::span(data));
94 void* mapped =
nullptr;
95 const D3D12_RANGE noRead{};
96 if (!buffer || FAILED(buffer->Map(0, &noRead, &mapped)))
99 std::memcpy(mapped, bytes.data(), bytes.size());
100 buffer->Unmap(0,
nullptr);
101 buffer->SetName(name);
113 std::string_view shaderPath) {
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);
135 .sequence = sequence,
136 .type =
static_cast<uint16_t
>(PROBE_COMMAND_TYPE_POKE),
147 const std::array<int32_t, 3>& direction, uint32_t impulseMillinewtonSeconds) {
149 .sequence = sequence,
150 .type =
static_cast<uint16_t
>(PROBE_COMMAND_TYPE_PUSH),
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]);
160 command.
payload[9] = impulseMillinewtonSeconds;
167 .sequence = sequence,
168 .type =
static_cast<uint16_t
>(PROBE_COMMAND_TYPE_TABLE),
171 command.
payload[0] =
static_cast<uint32_t
>(version);
172 command.
payload[1] =
static_cast<uint32_t
>(version >> 32);
179 std::expected<ProbeSim, std::string>
ProbeSim::Create(ID3D12Device5* device, D3D12_COMMAND_LIST_TYPE listType,
184 return std::unexpected(std::format(
"重さの試験の値が範囲外: 繰り返し {} 分ける数 {}",
191 return std::unexpected(events.error());
195 return std::unexpected(debugRing.error());
199 return std::unexpected(graphStats.error());
203 return std::unexpected(graphTrace.error());
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(
"仮の刻みのパイプラインを作れない");
210 if (!
sim.CreateBuffers(device))
211 return std::unexpected(
"仮の刻みのバッファを作れない");
214 sim.m_options.initialWorld = {};
216 return std::unexpected(
"仮の世界(反応の表・初めのセル)を作れない");
218 if (!
sim.CreateFrameSlots(device, listType))
219 return std::unexpected(
"仮の刻みのフレームの枠を作れない");
221 if (
auto physics =
sim.CreatePhysics(device); !physics)
222 return std::unexpected(physics.error());
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");
237 m_busyPipeline = LoadComputePipeline(device, root,
"sim/probe_tick_busy.cso");
240 m_extractPipeline = LoadComputePipeline(device, root,
"sim/probe_tick_extract.cso");
260 m_conductGraph = std::make_unique<gpu::WorkGraph>(std::move(*graph));
278 L
"ProbeSim.blockSchedule");
280 for (uint32_t parity = 0; parity < 2; ++parity) {
286 m_activeLists[parity]->SetName(std::format(L
"ProbeSim.activeList{}", parity).c_str());
289 m_cells->SetName(L
"ProbeSim.cells");
292 m_hashes->SetName(L
"ProbeSim.hashes");
296 for (uint32_t index = 0; index < PROBE_EXTRACTION_COUNT; ++index) {
301 m_extractions[index]->SetName(std::format(L
"ProbeSim.extraction{}", index).c_str());
304 const D3D12_QUERY_HEAP_DESC queryDesc{.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP,
307 return SUCCEEDED(device->CreateQueryHeap(&queryDesc, IID_PPV_ARGS(&
m_timestamps)));
314 if (scene ==
nullptr)
319 return std::unexpected(
"仮の世界の物理を作れない: " + physics.error());
321 m_physics = std::make_unique<GpuPhysics>(std::move(*physics));
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()
333 : std::vector<reaction::RxCell>(initialWorld.begin(),
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));
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"),
349 constexpr std::array<const wchar_t*, 4> TABLE_NAMES = {L
"ProbeSim.species", L
"ProbeSim.rules",
350 L
"ProbeSim.ruleIndex", L
"ProbeSim.rates"};
377 for (uint64_t generation = 0; generation < 2; ++generation) {
385 std::vector<D3D12_RESOURCE_BARRIER> barriers;
388 barriers.push_back(
gpu::Transition(buffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
389 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE));
397 D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
398 list->ResourceBarrier(
static_cast<UINT
>(barriers.size()), barriers.data());
405 if (FAILED(device->CreateCommandAllocator(listType, IID_PPV_ARGS(&
frame.allocator))) ||
407 device->CreateCommandList1(0, listType, D3D12_COMMAND_LIST_FLAG_NONE, IID_PPV_ARGS(&
frame.list)))) {
418 void* mapped =
nullptr;
419 const D3D12_RANGE noRead{};
420 if (FAILED(
frame.input->Map(0, &noRead, &mapped)))
423 frame.mappedInput =
static_cast<std::byte*
>(mapped);
450 if (swap.
table !=
nullptr && swapUnit >= first && swapUnit < first + input.
unitCount)
454 "表の差し替えの刻み {} の適用の単位が、このフレーム(刻み {} の単位 {} から {} 個)に無い", swap.
tick,
466 const uint32_t freeSlots = restoring ? PROBE_COMMAND_QUEUE_CAPACITY :
FreeCommandSlots();
467 if (input.
commands.size() > freeSlots) {
478 "コマンドの刻みか並びが不正: 刻み {} 番号 {}(適用に間に合う最初の刻み {}、前のコマンド {} / {})",
480 previous !=
nullptr ? previous->
sequence : 0);
492 const auto commandCount =
static_cast<uint32_t
>(input.
commands.size());
495 const uint32_t busyPerPiece =
m_options.busyIterations == 0
499 const D3D12_GPU_VIRTUAL_ADDRESS list0 =
m_activeLists[0]->GetGPUVirtualAddress();
500 const D3D12_GPU_VIRTUAL_ADDRESS list1 =
m_activeLists[1]->GetGPUVirtualAddress();
502 const std::array<uint32_t, PROBE_HEADER_WORDS> header = {commandCount,
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),
516 WriteAt(
frame.mappedInput, PROBE_INPUT_HEADER_OFFSET, header);
518 if (commandCount > 0) {
519 std::memcpy(
frame.mappedInput + PROBE_INPUT_COMMANDS_OFFSET, input.
commands.data(),
533 frame.keepAlive.clear();
535 if (FAILED(
frame.allocator->Reset()) || FAILED(
frame.list->Reset(
frame.allocator.Get(),
nullptr))) {
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);
560 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
561 list->ResourceBarrier(1, &hashesToUav);
569 bool hasHash =
false;
571 for (uint32_t index = 0; index < input.
unitCount; ++index) {
574 const bool tableSwapped = unit == PROBE_UNIT_APPLY && swap != input.
tableSwaps.end();
581 list->EndQuery(
m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, firstQuery + 1 + index);
582 hasHash = hasHash || unit ==
HashUnit();
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()))
622 list->ResourceBarrier(1, &allUavs);
631 if (commandCount == 0)
635 SetUnitConstants(list, 0, 0);
637 list->Dispatch((commandCount + PROBE_LINEAR_GROUP_SIZE - 1) / PROBE_LINEAR_GROUP_SIZE, 1, 1);
638 list->ResourceBarrier(1, &allUavs);
652 if (!commands.empty()) {
659 for (
auto queued =
m_queuedTicks.begin(); queued != applied; ++queued)
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());
686 list->SetComputeRootUnorderedAccessView(
691 list->SetComputeRootShaderResourceView(ROOT_LAYOUT.
SrvIndex(SRV_INPUT), input->GetGPUVirtualAddress());
693 list->SetComputeRootShaderResourceView(ROOT_LAYOUT.
SrvIndex(SRV_REACTION_FIRST + index),
699 void ProbeSim::RecordUnit(ID3D12GraphicsCommandList10* list, ID3D12Resource* input, uint64_t tick, uint32_t unit,
702 SetUnitConstants(list, tick, 0);
703 if (unit == PROBE_UNIT_APPLY) {
706 list->Dispatch(1, 1, 1);
710 list->ResourceBarrier(1, &allUavs);
712 list->Dispatch(2 * LINEAR_CELL_GROUPS, 1, 1);
716 list->ResourceBarrier(1, &allUavs);
718 list->Dispatch(BLOCK_GROUPS, 1, 1);
719 }
else if (unit == PROBE_UNIT_CONDUCT)
721 else if (
m_physics && unit == PROBE_UNIT_PHYSICS)
726 list->Dispatch(LINEAR_CELL_GROUPS, 1, 1);
729 list->Dispatch(1, 1, 1);
732 list->Dispatch(BUSY_GROUPS, BUSY_GROUPS, 1);
735 list->ResourceBarrier(1, &allUavs);
742 ComPtr<ID3D12Device5> device;
743 if (FAILED(list->GetDevice(IID_PPV_ARGS(&device))))
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")};
752 std::array<D3D12_RESOURCE_BARRIER, 4> barriers{};
757 const uint64_t bytes = uploads[index]->GetDesc().Width;
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]);
771 list->ResourceBarrier(
static_cast<UINT
>(barriers.size()), barriers.data());
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);
789 SetUnitConstants(list, tick, 0);
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);
818 D3D12_RESOURCE_STATES after)
const {
821 list->ResourceBarrier(
static_cast<UINT
>(barriers.size()), barriers.data());
825 SetUnitConstants(list, tick, target);
827 list->Dispatch(LINEAR_CELL_GROUPS, 1, 1);
832 m_events.RecordReadbackAndReset(list, slot);
840 D3D12_RESOURCE_STATE_COMMON);
841 list->ResourceBarrier(1, &toCommon);
847 D3D12_RESOURCE_STATE_COMMON);
848 list->ResourceBarrier(1, &toCommon);
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);
863 if (headerRead && (stored == 0 ||
m_events.Read(slot, std::as_writable_bytes(std::span(words))))) {
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;
870 {.tick = record[0] | (uint64_t{record[1]} << 32), .type = record[2], .place = record[3]});
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)))) {
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);
903 result.
trace = std::move(trace->records);
913 std::vector<ProbeTickHash> hashes;
914 const uint64_t unitsBefore =
frame.firstUnit;
915 const uint64_t unitsTotal = unitsBefore +
frame.unitCount;
917 const uint64_t completedTicks = unitsTotal /
UnitsPerTick();
918 if (completedTicks == 0)
921 std::vector<uint32_t> table(PROBE_HASH_BYTES / 4);
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);
931 if (storedTick != stateTick) {
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)});
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}};
994 const reaction::RxCell airCell =
MakeReactionCell(table, air, INITIAL_TEMPERATURE_MILLIKELVIN);
995 const reaction::RxCell wallCell =
MakeReactionCell(table, wall, INITIAL_TEMPERATURE_MILLIKELVIN);
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;
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));
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)
1033 scheduled[ProbeBlockIndex(
static_cast<uint32_t
>(nx),
static_cast<uint32_t
>(ny),
1034 static_cast<uint32_t
>(nz))] = 1;
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)
1050 uint32_t
CountWokenBlocks(std::span<const uint8_t> flags, std::span<const uint8_t> previousScheduled) {
1052 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
1053 if ((flags[block] & PROBE_BLOCK_FLAG_POSSIBLE) != 0 && previousScheduled[block] == 0)
1069 std::span<const ProbeCommand> commands, std::vector<uint8_t>& seeds,
1070 std::vector<uint64_t>& changedMarks) {
1071 uint64_t source = 0;
1073 if (command.targetTick != tick || command.type != PROBE_COMMAND_TYPE_POKE)
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)
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;
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;
1105 constexpr uint32_t LAST = PROBE_GRID_SIZE - 1;
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);
1113 return ProbeBlockOfCell(index % PROBE_GRID_SIZE, (index / PROBE_GRID_SIZE) % PROBE_GRID_SIZE,
1114 index / PROBE_SLICE_CELL_COUNT);
1120 std::span<const reaction::RxCell> cells) {
1121 FX_ASSERT(cells.size() == PROBE_CELL_COUNT);
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));
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);
1144 std::span<const reaction::RxCell> cells) {
1145 std::vector<uint32_t> words(PROBE_SCHEDULE_BYTES / 4, 0);
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);
1161 FX_ASSERT(initialWorld.empty() || initialWorld.size() == PROBE_CELL_COUNT);
1162 const std::vector<reaction::RxCell> initial = initialWorld.empty()
1164 : std::vector<reaction::RxCell>(initialWorld.begin(),
1165 initialWorld.end());
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);
1174 m_cells.reserve(
size_t{PROBE_CELL_COUNT} * 2);
1179 for (
const reaction::RxCell& cell :
m_cells)
1180 m_caches.push_back(ProbeMakeCache(table.
View(), cell));
1185 if (newTable !=
nullptr)
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};
1200 if (newTable !=
nullptr) {
1201 for (
size_t index = 0; index <
m_cells.size(); ++index)
1205 rng::fill(seeds, uint8_t{1});
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;
1220 changed[block] |=
static_cast<uint8_t
>(step.changed);
1221 wakes[block] = std::min(wakes[block], step.wakeTick);
1225 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
1226 if (changed[block] != 0)
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));
1236 return std::span(
m_cells).subspan(
static_cast<size_t>(tick & 1) * PROBE_CELL_COUNT, PROBE_CELL_COUNT);
1241 for (uint32_t index = 0; index < cells.size(); ++index)
1242 hash += ProbeCellHash(index, cells[index]);
1248 return std::span(
m_caches).subspan(
static_cast<size_t>(tick & 1) * PROBE_CELL_COUNT, PROBE_CELL_COUNT);
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));
1267 for (
const uint32_t word : words)
1268 hash = fx::FxHashCombine(hash, word);
1274 uint64_t energy = 0;
1275 for (
const reaction::RxCell& cell : cells)
1276 energy +=
static_cast<uint64_t
>(cell.energy);
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 ¶meters, const GpuPhysicsOptions &options={})
std::vector< uint64_t > m_changedMarks
ProbeReference(const BakedReactionTable &table, std::span< const reaction::RxCell > initialWorld={})
std::vector< uint8_t > m_blockFlags
std::span< const reaction::RxCell > State(uint64_t tick) const
uint32_t m_scheduledBlocks
std::vector< reaction::RxCell > m_cells
const BakedReactionTable * m_table
std::vector< reaction::HcThermalCache > m_caches
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
void RecordReadbacks(ID3D12GraphicsCommandList10 *list, uint32_t slot, bool hasHash) const
ComPtr< ID3D12PipelineState > m_enqueuePipeline
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
void WriteInput(FrameSlot &frame, const ProbeFrameInput &input) const
std::expected< void, std::string > CreatePhysics(ID3D12Device5 *device)
ComPtr< ID3D12PipelineState > m_applyPipeline
void ResetCommandMirror()
std::array< ComPtr< ID3D12Resource >, 2 > m_activeLists
std::array< ComPtr< ID3D12Resource >, PROBE_EXTRACTION_COUNT > m_extractions
bool ValidateCommands(const ProbeFrameInput &input) const
ComPtr< ID3D12PipelineState > m_hashCellsPipeline
bool CreateConductGraph(ID3D12Device5 *device)
void RecordInitialization(ID3D12GraphicsCommandList10 *list)
ComPtr< ID3D12PipelineState > m_flushEventsPipeline
uint32_t m_queuedCommandCount
bool CreatePipelines(ID3D12Device5 *device)
ComPtr< ID3D12Resource > m_busySink
ComPtr< ID3D12PipelineState > m_refreshTablePipeline
bool CreateWorld(ID3D12Device5 *device, const BakedReactionTable &table, std::span< const reaction::RxCell > initialWorld)
gpu::DebugRing m_debugRing
std::array< FrameSlot, FRAME_SLOT_COUNT > m_slots
std::array< ComPtr< ID3D12Resource >, 7 > m_initialUploads
void TrackCommands(std::span< const ProbeCommand > commands, uint64_t nextApplyTick)
ComPtr< ID3D12PipelineState > m_extractPipeline
gpu::GraphTrace m_graphTrace
std::vector< ProbeTickHash > ReadHashes(const FrameSlot &frame) const
bool ValidateInput(uint32_t slot, const ProbeFrameInput &input) const
ComPtr< ID3D12PipelineState > m_busyPipeline
std::unique_ptr< GpuPhysics > m_physics
void RecordPhysics(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input, uint64_t tick)
ComPtr< ID3D12RootSignature > m_rootSignature
void RecordSave(ID3D12GraphicsCommandList10 *list, uint32_t index, uint64_t tick)
std::vector< QueuedTick > m_queuedTicks
ComPtr< ID3D12Resource > m_hashes
uint32_t HashUnit() const
bool ValidateSavePoints(const ProbeFrameInput &input) const
bool ValidateTableSwap(const ProbeFrameInput &input) const
ComPtr< ID3D12Resource > m_blockSchedule
ComPtr< ID3D12QueryHeap > m_timestamps
std::array< uint32_t, PROBE_VIEW_SPECIES_COUNT > m_viewSpecies
ProbeCommand m_lastEnqueued
ComPtr< ID3D12Resource > m_commandQueue
ProbeSim(const ProbeSimOptions &options, gpu::ReadbackRing &&events, gpu::DebugRing &&debugRing, gpu::WorkGraphStats &&graphStats, gpu::GraphTrace &&graphTrace)
void RecordExtractAndHook(ID3D12GraphicsCommandList10 *list, uint64_t tick, const ProbeFrameInput &input)
ComPtr< ID3D12Resource > m_tickEvents
gpu::WorkGraphStats m_graphStats
uint32_t m_conductEntrypoint
bool CreateBuffers(ID3D12Device5 *device)
void RecordActiveListStates(ID3D12GraphicsCommandList10 *list, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) const
ComPtr< ID3D12PipelineState > m_wakeDuePipeline
bool RecordTableSwap(ID3D12GraphicsCommandList10 *list, FrameSlot &frame, const BakedReactionTable &table)
uint32_t UnitsPerTick() const
bool m_physicsInitialized
ProbeSimOptions m_options
std::array< ComPtr< ID3D12Resource >, 4 > m_reactionTable
static constexpr uint32_t MAX_UNITS_PER_FRAME
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)
void RecordRestore(ID3D12GraphicsCommandList10 *list, uint32_t index)
void RecordConduct(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input, uint64_t tick)
ComPtr< ID3D12Resource > m_cells
void RecordEnqueue(ID3D12GraphicsCommandList10 *list, uint32_t commandCount) const
std::unique_ptr< gpu::WorkGraph > m_conductGraph
void BindRootArguments(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input) const
uint32_t FreeCommandSlots() const
ProbeFrameReadback ReadFrame(uint32_t slot)
static constexpr uint32_t FRAME_SLOT_COUNT
ComPtr< ID3D12Resource > m_thermal
void RecordUnit(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input, uint64_t tick, uint32_t unit, bool tableSwapped)
bool m_conductInitialized
ID3D12CommandList * RecordFrame(uint32_t slot, const ProbeFrameInput &input)
void RecordPhysicsInitialization(ID3D12GraphicsCommandList10 *list, ID3D12Resource *input)
ComPtr< ID3D12RootSignature > CreateRootSignature(ID3D12Device *device, const RootSignatureLayout &layout)
std::expected< std::vector< std::byte >, std::string > LoadShader(std::string_view relativePath)
bool ReadBuffer(ID3D12Resource *readback, std::span< std::byte > destination)
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)
ComPtr< ID3D12Resource > CreateBuffer(ID3D12Device *device, uint64_t sizeBytes, BufferKind kind)
void RecordCopyToReadback(ID3D12GraphicsCommandList *list, ID3D12Resource *source, ID3D12Resource *readback)
D3D12_RESOURCE_BARRIER UavBarrier(ID3D12Resource *resource)
constexpr uint32_t UAV_CELLS
constexpr uint32_t UAV_EXTRACTION0
constexpr uint64_t AIR_OXYGEN_MICROMOLES
constexpr uint32_t UAV_ACTIVE_LIST0
void MarkWithNeighbors(std::vector< uint8_t > &scheduled, uint32_t block)
std::vector< uint8_t > ScheduleBlocks(std::span< const uint8_t > seeds)
constexpr uint64_t CRATE_OXYGEN_MICROMOLES
constexpr uint32_t CRATE_SIZE
constexpr uint32_t BUSY_GROUPS
constexpr uint64_t CELL_BYTES
constexpr uint32_t UAV_BUSY_SINK
constexpr uint64_t CRATE_NITROGEN_MICROMOLES
void WriteAt(std::byte *base, uint32_t offset, const T &value)
uint32_t BlockOfCellIndex(uint32_t index)
constexpr uint32_t TIMESTAMPS_PER_SLOT
constexpr uint32_t UAV_HASHES
constexpr uint32_t LINEAR_CELL_GROUPS
constexpr uint32_t UAV_EXTRACTION1
constexpr gpu::RootSignatureLayout ROOT_LAYOUT
constexpr uint32_t UAV_EXTRACTION2
constexpr uint32_t UAV_EVENTS
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)
constexpr uint64_t AIR_NITROGEN_MICROMOLES
constexpr uint64_t THERMAL_BYTES
constexpr uint32_t SLICE_BYTES
bool InCrate(uint32_t value)
constexpr int32_t INITIAL_TEMPERATURE_MILLIKELVIN
constexpr uint32_t CRATE_END
ProbeCellStep ReferenceStepCell(const ReactionTableView &table, ReferenceGeneration current, uint32_t index, uint64_t tick, uint64_t changedMark)
constexpr uint32_t CRATE_BEGIN
constexpr uint32_t UAV_ACTIVE_LIST1
constexpr uint32_t UAV_THERMAL
ComPtr< ID3D12Resource > CreateFilledUpload(ID3D12Device *device, const std::vector< T > &data, const wchar_t *name)
constexpr uint32_t SRV_INPUT
uint32_t CountWokenBlocks(std::span< const uint8_t > flags, std::span< const uint8_t > previousScheduled)
constexpr uint32_t MAX_LOGGED_DEBUG_MESSAGES
bool OnCrateWall(uint32_t value)
constexpr uint32_t SRV_REACTION_FIRST
constexpr uint32_t EXTRACTION_BYTES
constexpr uint32_t UAV_BODIES
constexpr uint32_t UAV_COMMAND_QUEUE
ComPtr< ID3D12PipelineState > LoadComputePipeline(ID3D12Device *device, ID3D12RootSignature *rootSignature, std::string_view shaderPath)
void SetUnitConstants(ID3D12GraphicsCommandList10 *list, uint64_t tick, uint32_t argument)
constexpr uint32_t UAV_TICK_EVENTS
constexpr uint64_t CRATE_CELLULOSE_MICROMOLES
constexpr uint32_t UAV_BLOCK_SCHEDULE
constexpr uint32_t BLOCK_GROUPS
gpu::GraphStatsLayout MakeConductStatsLayout()
constexpr bool CommandPrecedes(const Command &a, const Command &b)
constexpr uint32_t NO_SAVE_POINT
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)
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)
std::vector< GraphNodeLimits > nodes
std::vector< GraphGaugeLimits > gauges
uint32_t GraphTraceIndex() const
uint32_t DebugRingIndex() const
uint32_t SrvIndex(uint32_t shaderRegister) const
uint32_t GraphStatsIndex() const
std::vector< uint64_t > rates
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
std::vector< ProbeTickHash > hashes
gpu::GraphStatsSnapshot graphStats
std::vector< gpu::GraphTraceRecord > trace
std::vector< uint64_t > unitGpuTicks
uint32_t droppedEventCount
uint64_t gpuBeginTimestamp
uint32_t droppedTraceCount
uint32_t graphFindingCount
std::vector< ProbeEvent > events
uint32_t debugAssertCount
std::span< const reaction::RxCell > initialWorld
gpu::GraphTraceFilter trace
const BakedReactionTable * table
reaction::HcThermalCache * caches