Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
gpu_implicit.cpp
Go to the documentation of this file.
1// gpu_implicit.cpp — 細かいレベルの熱の陰解法の GPU 版(T-0117)。何をするかは gpu_implicit.h、段の中身は shaders/sim/implicit_conduct.hlsl。
2// 段の順は CPU の StepImplicit と同じ: 温度 → V サイクル(VCycle の再帰と同じ順に Smooth・Restrict・Prolong)× 上限 → 面の流れ → 安全網 → 足す。
3// 段と段の間は全体の UAV のバリア(次の段は前の段の書き込みを読む)。
4// 固定費を減らす形(T-0120): 隣が多い節は 1 グループで足す・小さい段から下の V サイクルは ImTail の 1 Dispatch。
5// 段の形は GPU のバッファから(T-0136): 長い行の節の一覧・ImTail の境・Dispatch の大きさは刻みの初めに GPU が作り(ImPlanLevels・ImPlanArgs)、
6// V サイクルは記録の上限の段まで ExecuteIndirect で積む。大きさは上限(GpuImplicitLimits)から。
7// 記録の形は刻みごとに選べる(T-0154): 積む段の数と最も粗い段の掃き出しを積むか。空の Dispatch とバリアを減らす。
8#include "sim/gpu_implicit.h"
9
10#include <algorithm>
11#include <cstring>
12#include <format>
13#include <span>
14#include <utility>
15
16#include "common/implicit_conduction.hlsli"
17#include "gpu/resources.h"
19
20using namespace bicameral::fx;
21using namespace bicameral::multires;
22
23namespace bicameral::sim {
24
25 namespace {
26
27 constexpr uint32_t THREADS = 64; // implicit_conduct.hlsl の IM_THREADS
28 constexpr uint32_t STATE_WORDS = 8;
29 constexpr uint32_t STATE_CYCLES = 2;
30 constexpr uint32_t STATE_LIMIT_DONE = 3;
31 constexpr uint32_t STATE_LIMIT_ROUNDS = 5;
32 constexpr uint32_t STATE_LIMITED_CELLS = 6;
33 constexpr uint32_t WIDE_WORDS = 3;
34 constexpr uint32_t WIDE_WORST_EXCESS = 2;
35 constexpr uint32_t COLOR_COUNT = 2;
36
37 constexpr uint32_t PREDICATE_CYCLES = 0; // 述語の語(uint64): V サイクルを止めた
38 constexpr uint32_t PREDICATE_LIMIT = 1; // 安全網を止めた
39 constexpr uint32_t PREDICATE_WORDS = 2;
40
41 constexpr uint32_t SWEEP_BITS = 8;
42
43 constexpr uint32_t UAV_COUNT = 11;
44 constexpr uint32_t ARGS_BYTES = 3 * sizeof(uint32_t); // D3D12_DISPATCH_ARGUMENTS
45
46 constexpr uint32_t ROOT_CONSTANT_COUNT = 16;
48 .uavCount = UAV_COUNT, .rootConstantCount = ROOT_CONSTANT_COUNT, .debugRing = true};
49
50 // implicit_conduct.hlsl の入口(Pass の順)
51 constexpr std::array<const char*, 15> SHADERS = {
52 "sim/implicit_begin.cso", "sim/implicit_start.cso", "sim/implicit_smooth.cso",
53 "sim/implicit_restrict.cso", "sim/implicit_prolong.cso", "sim/implicit_converged.cso",
54 "sim/implicit_cycle_end.cso", "sim/implicit_flows.cso", "sim/implicit_mark.cso",
55 "sim/implicit_limit_end.cso", "sim/implicit_limit_faces.cso", "sim/implicit_apply.cso",
56 "sim/implicit_tail.cso", "sim/implicit_plan_levels.cso", "sim/implicit_plan_args.cso"};
57
58 // RecordUpload が CPU の系から写すバッファ(Buffer の順。ほかは刻みの段が初めに書く)
59 constexpr std::array<bool, 11> UPLOADED = {true, true, true, true, true, false,
60 false, false, false, true, false};
61
62 // 計画の段の表の語(implicit_levels.hlsl の見出しと同じ並び)
63 constexpr uint32_t PlanDepthWord(uint32_t kind, uint32_t depth) {
64 return IM_PLAN_DEPTH_BASE + (kind * IM_PLAN_DEPTH_STRIDE) + depth;
65 }
66
67 // 段 depth の間接の引数の始まり
68 constexpr uint32_t DepthSlot(uint32_t depth) {
69 return IM_SLOT_DEPTH_BASE + (depth * IM_SLOT_DEPTH_STRIDE);
70 }
71
72 template <typename T>
73 std::vector<std::byte> ToBytes(const std::vector<T>& values) {
74 std::vector<std::byte> bytes(values.size() * sizeof(T));
75 if (!values.empty())
76 std::memcpy(bytes.data(), values.data(), bytes.size());
77
78 return bytes;
79 }
80
81 bool WriteUpload(ID3D12Resource* upload, std::span<const std::byte> bytes) {
82 void* mapped = nullptr;
83 const D3D12_RANGE noRead{.Begin = 0, .End = 0};
84 if (FAILED(upload->Map(0, &noRead, &mapped)))
85 return false;
86
87 std::memcpy(mapped, bytes.data(), bytes.size());
88 upload->Unmap(0, nullptr);
89
90 return true;
91 }
92
93 void GlobalUavBarrier(ID3D12GraphicsCommandList* list) {
94 const D3D12_RESOURCE_BARRIER barrier{.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV,
95 .UAV = {.pResource = nullptr}};
96 list->ResourceBarrier(1, &barrier);
97 }
98
99 std::vector<ImGpuCell> MakeCellImage(const ImplicitGrid& grid, const std::vector<uint32_t>& faceStarts) {
100 std::vector<ImGpuCell> cells(grid.cells.size());
101 for (size_t i = 0; i < cells.size(); ++i) {
102 const ImplicitCell& cell = grid.cells[i];
103 cells[i] = {.energy = cell.energy,
104 .fraction = cell.fraction,
105 .heatCapacity = cell.heatCapacity,
106 .startTemperature = cell.startTemperature,
107 .faceStart = faceStarts[i],
108 .faceEnd = faceStarts[i + 1]};
109 }
110
111 return cells;
112 }
113
114 // セルごとの面の一覧(面の番号の昇順。番号 × 2 + 粗い側なら 1)と、その始まり(セルの数 + 1)
115 std::pair<std::vector<uint32_t>, std::vector<uint32_t>> MakeCellFaces(const ImplicitGrid& grid) {
116 std::vector<std::vector<uint32_t>> perCell(grid.cells.size());
117 for (uint32_t f = 0; f < grid.faces.size(); ++f) {
118 perCell[grid.faces[f].fine].push_back(f * 2);
119 perCell[grid.faces[f].coarse].push_back((f * 2) + 1);
120 }
121
122 std::vector<uint32_t> list;
123 std::vector<uint32_t> starts(1, 0);
124 for (const std::vector<uint32_t>& faces : perCell) {
125 list.insert(list.end(), faces.begin(), faces.end());
126 starts.push_back(static_cast<uint32_t>(list.size()));
127 }
128
129 return {std::move(list), std::move(starts)};
130 }
131
132 struct NodeImages {
133 std::vector<ImGpuNode> nodes;
134 std::vector<ImGpuLink> links;
135 };
136
137 // 多重格子の節(段 0 から順に全部を 1 本に)と隣。節の子の一覧は lists の後ろに足す
138 // 段 depth の節の子(1 つ細かい段で親がその節の節。番号の昇順)を lists に足す
139 void AppendChildren(const ImplicitGrid& grid, size_t depth, const std::vector<uint32_t>& levelOffsets,
140 std::vector<ImGpuNode>& nodes, std::vector<uint32_t>& lists) {
141 const ImplicitGridLevel& finer = grid.levels[depth - 1];
142 const uint32_t offset = levelOffsets[depth];
143 std::vector<std::vector<uint32_t>> children(grid.levels[depth].levels.size());
144 for (uint32_t i = 0; i < finer.parents.size(); ++i)
145 children[finer.parents[i]].push_back(levelOffsets[depth - 1] + i);
146
147 for (size_t p = 0; p < children.size(); ++p) {
148 nodes[offset + p].childStart = static_cast<uint32_t>(lists.size());
149 lists.insert(lists.end(), children[p].begin(), children[p].end());
150 nodes[offset + p].childEnd = static_cast<uint32_t>(lists.size());
151 }
152 }
153
154 NodeImages MakeNodes(const ImplicitGrid& grid, const std::vector<uint32_t>& levelOffsets,
155 std::vector<uint32_t>& lists) {
156 std::vector<ImGpuNode> nodes(levelOffsets.back());
157 std::vector<ImGpuLink> links;
158 for (size_t depth = 0; depth < grid.levels.size(); ++depth) {
159 const ImplicitGridLevel& level = grid.levels[depth];
160 const uint32_t offset = levelOffsets[depth];
161 const bool coarsest = depth + 1 == grid.levels.size();
162 for (size_t i = 0; i < level.levels.size(); ++i) {
163 ImGpuNode& node = nodes[offset + i];
164 node.selfWeight = level.selfWeights[i];
165 node.restrictWeight = coarsest ? 0 : level.restrictWeights[i];
166 node.parent = coarsest ? 0 : levelOffsets[depth + 1] + level.parents[i];
167 node.color = level.colors[i];
168 node.linkStart = static_cast<uint32_t>(links.size());
169 for (uint32_t k = level.rowStarts[i]; k < level.rowStarts[i + 1]; ++k)
170 links.push_back({.weight = level.weights[k], .neighbor = offset + level.neighbors[k]});
171
172 node.linkEnd = static_cast<uint32_t>(links.size());
173 }
174
175 if (depth != 0)
176 AppendChildren(grid, depth, levelOffsets, nodes, lists);
177 }
178
179 return {.nodes = std::move(nodes), .links = std::move(links)};
180 }
181
182 } // namespace
183
184 std::expected<GpuImplicit, std::string> GpuImplicit::Create(ID3D12Device5* device, const GpuImplicitLimits& limits,
185 const GpuImplicitTuning& tuning) {
186 if (limits.cells == 0 || limits.nodes < limits.cells || limits.levels == 0 ||
187 limits.levels > IM_PLAN_MAX_LEVELS)
188 return std::unexpected("陰解法の上限が正しくない");
189
190 GpuImplicit result;
191 result.m_limits = limits;
192 result.m_tuning = tuning;
193 result.m_constants = {.maxNodes = limits.nodes,
194 .maxCells = limits.cells,
195 .maxFaces = limits.faces,
196 .dispatchLevels = std::clamp<uint32_t>(tuning.dispatchLevels, 1, limits.levels),
197 .tailMaxNodes = tuning.tailMaxNodes,
198 .tailMaxLinks = tuning.tailMaxLinks,
199 .coarsestTailMaxNodes = tuning.coarsestTailMaxNodes,
200 .maxLevels = limits.levels};
202 if (auto pipelines = result.CreatePipelines(device); !pipelines)
203 return std::unexpected(pipelines.error());
204
205 if (auto buffers = result.CreateBuffers(device); !buffers)
206 return std::unexpected(buffers.error());
207
208 return result;
209 }
210
211 std::expected<GpuImplicit, std::string> GpuImplicit::Create(ID3D12Device5* device, const ImplicitGrid& grid,
212 const GpuImplicitTuning& tuning) {
213 if (grid.levels.empty() || grid.cells.empty())
214 return std::unexpected("陰解法の段が無い");
215
216 return Create(device, LimitsOf(grid), tuning);
217 }
218
220 GpuImplicitLimits limits{.cells = static_cast<uint32_t>(grid.cells.size()),
221 .faces = static_cast<uint32_t>(grid.faces.size()),
222 .nodes = 0,
223 .links = 0,
224 .levels = std::max<uint32_t>(1, static_cast<uint32_t>(grid.levels.size()))};
225 for (const ImplicitGridLevel& level : grid.levels) {
226 limits.nodes += static_cast<uint32_t>(level.levels.size());
227 limits.links += static_cast<uint32_t>(level.neighbors.size());
228 }
229
230 return limits;
231 }
232
233 // 前の刻みで下りが止まった段(ImTail の境か最も粗い段)まで段ごとに積めば、同じ形の刻みは空の段を積まない。
234 // 形が深くなった刻みは、その分を ImTail が回す(値は同じ。その刻みだけ遅い。次の刻みで形が追いつく)
236 const uint32_t levels = std::max(previous.levelCount, 1u);
237 const uint32_t terminal = std::min(previous.wantedTailDepth, levels - 1);
238
239 return {.dispatchLevels = terminal + 1, .coarsestDispatch = previous.wantedTailDepth >= previous.levelCount};
240 }
241
242 // バッファの大きさ(上限から。空にならないように 1 つ分は持つ)
243 uint64_t GpuImplicit::BufferBytes(Buffer buffer) const {
244 const uint64_t nodes = m_limits.nodes;
245 const uint64_t cells = m_limits.cells;
246 const uint64_t faces = m_limits.faces;
247 switch (buffer) {
248 case BufferCells: return cells * sizeof(ImGpuCell);
249 case BufferFaces: return std::max<uint64_t>(1, faces) * sizeof(ImGpuFace);
250 case BufferLists: return ((2 * faces) + nodes + 1) * sizeof(uint32_t);
251 case BufferNodes: return nodes * sizeof(ImGpuNode);
252 case BufferLinks: return std::max<uint64_t>(1, m_limits.links) * sizeof(ImGpuLink);
253 case BufferWork: return ((3 * nodes) + cells + (2 * faces) + 1) * sizeof(int64_t);
254 case BufferState: return (STATE_WORDS + cells) * sizeof(uint32_t);
255 case BufferWide: return uint64_t{WIDE_WORDS} * sizeof(int64_t);
256 case BufferPredicate: return uint64_t{PREDICATE_WORDS} * sizeof(uint64_t);
257 case BufferPlan: return (IM_PLAN_FLAGS_BASE + (4 * nodes)) * sizeof(uint32_t);
258 case BufferArgs:
259 return uint64_t{IM_SLOT_DEPTH_BASE + (IM_SLOT_DEPTH_STRIDE * m_maxDispatchLevels)} * ARGS_BYTES;
260 default: return 0;
261 }
262 }
263
264 std::expected<void, std::string> GpuImplicit::CreatePipelines(ID3D12Device5* device) {
265 m_rootSignature = gpu::CreateRootSignature(device, ROOT_LAYOUT);
266 if (!m_rootSignature)
267 return std::unexpected("陰解法のルート署名を作れない");
268
269 for (uint32_t pass = 0; pass < PassCount; ++pass) {
270 const auto bytecode = gpu::LoadShader(SHADERS[pass]);
271 if (!bytecode)
272 return std::unexpected(bytecode.error());
273
274 m_pipelines[pass] = gpu::CreateComputePipeline(device, m_rootSignature.Get(), *bytecode);
275 if (!m_pipelines[pass])
276 return std::unexpected(std::format("陰解法のパイプラインを作れない({})", SHADERS[pass]));
277 }
278
279 // --- 間接の Dispatch(引数は ImPlanArgs が書く。ルート定数は変えないので署名は要らない)---
280 const D3D12_INDIRECT_ARGUMENT_DESC argument{.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH};
281 const D3D12_COMMAND_SIGNATURE_DESC signature{
282 .ByteStride = ARGS_BYTES, .NumArgumentDescs = 1, .pArgumentDescs = &argument, .NodeMask = 0};
283 if (FAILED(device->CreateCommandSignature(&signature, nullptr, IID_PPV_ARGS(&m_dispatchSignature))))
284 return std::unexpected("陰解法の間接の Dispatch の署名を作れない");
285
286 return {};
287 }
288
289 std::expected<void, std::string> GpuImplicit::CreateBuffers(ID3D12Device5* device) {
290 for (uint32_t i = 0; i < BufferCount; ++i) {
291 const auto buffer = static_cast<Buffer>(i);
293 if (!m_buffers[i])
294 return std::unexpected("陰解法のバッファを作れない");
295
296 if (!UPLOADED[i])
297 continue;
298
299 const uint64_t uploadBytes = buffer == BufferPlan ? uint64_t{IM_PLAN_HEADER_WORDS} * sizeof(uint32_t)
300 : BufferBytes(buffer);
301 m_uploads[i] = gpu::CreateBuffer(device, uploadBytes, gpu::BufferKind::Upload);
302 if (!m_uploads[i])
303 return std::unexpected("陰解法の写すバッファを作れない");
304 }
305
309 m_planReadback = gpu::CreateBuffer(device, uint64_t{IM_PLAN_HEADER_WORDS} * sizeof(uint32_t),
311 m_timestampReadback = gpu::CreateBuffer(device, uint64_t{MAX_TIMESTAMPS} * sizeof(uint64_t),
314 return std::unexpected("陰解法の読み戻しを作れない");
315
316 const D3D12_QUERY_HEAP_DESC queryDesc{
317 .Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP, .Count = MAX_TIMESTAMPS, .NodeMask = 0};
318 if (FAILED(device->CreateQueryHeap(&queryDesc, IID_PPV_ARGS(&m_timestamps))))
319 return std::unexpected("タイムスタンプのヒープを作れない");
320
321 return {};
322 }
323
324 // CPU の系を GPU の並びに: 節は段 0(= セル)から順に全部を 1 本に、隣・親・子は全体の番号に。
325 // 番号の一覧は面の一覧(面の数 × 2)→ 面の上限 × 2 まで空き → 子の一覧。計画の見出しに段の表と面の数
326 bool GpuImplicit::RecordUpload(ID3D12GraphicsCommandList* list, const ImplicitGrid& grid) {
327 const GpuImplicitLimits need = LimitsOf(grid);
328 if (grid.cells.empty() || need.cells > m_limits.cells || need.faces > m_limits.faces ||
329 need.nodes > m_limits.nodes || need.links > m_limits.links || need.levels > m_limits.levels)
330 return false;
331
332 std::vector<uint32_t> levelOffsets(1, 0);
333 for (const ImplicitGridLevel& level : grid.levels)
334 levelOffsets.push_back(levelOffsets.back() + static_cast<uint32_t>(level.levels.size()));
335
336 auto [lists, faceStarts] = MakeCellFaces(grid);
337 auto [nodes, links] = MakeNodes(grid, levelOffsets, lists);
338 lists.insert(lists.begin() + static_cast<std::ptrdiff_t>(2 * grid.faces.size()),
339 size_t{2} * (m_limits.faces - need.faces), 0u);
340 if (links.empty())
341 links.push_back({});
342
343 std::vector<ImGpuFace> faces(std::max<size_t>(1, grid.faces.size()));
344 for (size_t f = 0; f < grid.faces.size(); ++f) {
345 const ImplicitFace& face = grid.faces[f];
346 faces[f] = {.coefficientHigh = face.coefficient.hi,
347 .coefficientLow = face.coefficient.lo,
348 .fine = face.fine,
349 .coarse = face.coarse,
350 .gap = face.gap,
351 .coarseFraction = grid.cells[face.coarse].coarseFraction ? 1u : 0u};
352 }
353
354 std::vector<uint32_t> plan(IM_PLAN_HEADER_WORDS, 0);
355 plan[IM_PLAN_LEVEL_COUNT] = need.levels;
356 plan[IM_PLAN_FACES] = need.faces;
357 for (uint32_t depth = 0; depth < grid.levels.size(); ++depth) {
358 plan[PlanDepthWord(IM_PLAN_NODE_OFFSET, depth)] = levelOffsets[depth];
359 plan[PlanDepthWord(IM_PLAN_NODE_COUNT, depth)] = levelOffsets[depth + 1] - levelOffsets[depth];
360 }
361
362 std::array<std::vector<std::byte>, BufferCount> images;
363 images[BufferCells] = ToBytes(MakeCellImage(grid, faceStarts));
364 images[BufferFaces] = ToBytes(faces);
365 images[BufferLists] = ToBytes(lists);
366 images[BufferNodes] = ToBytes(nodes);
367 images[BufferLinks] = ToBytes(links);
368 images[BufferPlan] = ToBytes(plan);
369
370 // --- 写すものは COMMON から暗黙に COPY_DEST へ昇格して写し、ほかは COMMON から。どれも UAV へ ---
371 std::array<D3D12_RESOURCE_BARRIER, BufferCount> barriers{};
372 for (uint32_t i = 0; i < BufferCount; ++i) {
373 barriers[i] = gpu::Transition(m_buffers[i].Get(), D3D12_RESOURCE_STATE_COMMON,
374 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
375 if (!UPLOADED[i])
376 continue;
377
378 if (!WriteUpload(m_uploads[i].Get(), images[i]))
379 return false;
380
381 list->CopyBufferRegion(m_buffers[i].Get(), 0, m_uploads[i].Get(), 0, images[i].size());
382 barriers[i].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
383 }
384
385 list->ResourceBarrier(BufferCount, barriers.data());
386
387 return true;
388 }
389
390 void GpuImplicit::RecordReset(ID3D12GraphicsCommandList* list) {
391 std::array<D3D12_RESOURCE_BARRIER, BufferCount> barriers{};
392 for (uint32_t i = 0; i < BufferCount; ++i) {
393 barriers[i] = gpu::Transition(m_buffers[i].Get(), D3D12_RESOURCE_STATE_COMMON,
394 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
395 }
396
397 list->ResourceBarrier(BufferCount, barriers.data());
398 }
399
400 void GpuImplicit::RecordCopySystem(ID3D12GraphicsCommandList* list, ID3D12Resource* source, uint64_t cellsOffset,
401 uint64_t facesOffset, uint64_t listsOffset, ID3D12Resource* header,
402 uint64_t faceCountOffset) {
403 const std::array<CopyRegion, 4> regions = {
404 CopyRegion{.target = BufferCells,
405 .targetOffset = 0,
406 .source = source,
407 .sourceOffset = cellsOffset,
408 .bytes = BufferBytes(BufferCells)},
409 CopyRegion{.target = BufferFaces,
410 .targetOffset = 0,
411 .source = source,
412 .sourceOffset = facesOffset,
413 .bytes = BufferBytes(BufferFaces)},
414 CopyRegion{.target = BufferLists,
415 .targetOffset = 0,
416 .source = source,
417 .sourceOffset = listsOffset,
418 .bytes = uint64_t{2} * m_limits.faces * sizeof(uint32_t)},
419 CopyRegion{.target = BufferPlan,
420 .targetOffset = uint64_t{IM_PLAN_FACES} * sizeof(uint32_t),
421 .source = header,
422 .sourceOffset = faceCountOffset,
423 .bytes = sizeof(uint32_t)}};
424 RecordCopies(list, regions);
425 }
426
427 void GpuImplicit::RecordCopyLevels(ID3D12GraphicsCommandList* list, ID3D12Resource* header, ID3D12Resource* nodes,
428 ID3D12Resource* links, ID3D12Resource* children) {
429 const uint64_t childrenOffset = uint64_t{2} * m_limits.faces * sizeof(uint32_t);
430 const std::array<CopyRegion, 4> regions = {
431 CopyRegion{.target = BufferPlan,
432 .targetOffset = 0,
433 .source = header,
434 .sourceOffset = 0,
435 .bytes = uint64_t{IM_PLAN_SHAPE_WORDS} * sizeof(uint32_t)},
436 CopyRegion{.target = BufferNodes,
437 .targetOffset = 0,
438 .source = nodes,
439 .sourceOffset = 0,
440 .bytes = BufferBytes(BufferNodes)},
441 CopyRegion{.target = BufferLinks,
442 .targetOffset = 0,
443 .source = links,
444 .sourceOffset = 0,
445 .bytes = BufferBytes(BufferLinks)},
446 CopyRegion{.target = BufferLists,
447 .targetOffset = childrenOffset,
448 .source = children,
449 .sourceOffset = 0,
450 .bytes = BufferBytes(BufferLists) - childrenOffset}};
451 RecordCopies(list, regions);
452 }
453
454 // 写す(大きさは写す先の残りと source の残りの小さい方)。写す先は UAV → COPY_DEST → UAV
455 void GpuImplicit::RecordCopies(ID3D12GraphicsCommandList* list, std::span<const CopyRegion> regions) {
456 std::vector<D3D12_RESOURCE_BARRIER> barriers;
457 for (const CopyRegion& region : regions) {
458 ID3D12Resource* target = m_buffers[region.target].Get();
459 const bool seen = std::ranges::any_of(barriers, [&](const D3D12_RESOURCE_BARRIER& barrier) {
460 return barrier.Transition.pResource == target;
461 });
462 if (!seen) {
463 barriers.push_back(
464 gpu::Transition(target, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_DEST));
465 }
466 }
467
468 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
469 for (const CopyRegion& region : regions) {
470 const uint64_t sourceBytes = region.source->GetDesc().Width;
471 const uint64_t targetBytes = BufferBytes(region.target);
472 if (region.sourceOffset >= sourceBytes || region.targetOffset >= targetBytes)
473 continue;
474
475 const uint64_t bytes = std::min(
476 {region.bytes, sourceBytes - region.sourceOffset, targetBytes - region.targetOffset});
477 if (bytes != 0) {
478 list->CopyBufferRegion(m_buffers[region.target].Get(), region.targetOffset, region.source,
479 region.sourceOffset, bytes);
480 }
481 }
482
483 for (D3D12_RESOURCE_BARRIER& barrier : barriers)
484 std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
485
486 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
487 }
488
489 // 比べる用の段の形の像(GpuImplicit が Create で写すものと同じ。T-0134)
492 images.levelOffsets.assign(1, 0);
493 for (const ImplicitGridLevel& level : grid.levels)
494 images.levelOffsets.push_back(images.levelOffsets.back() + static_cast<uint32_t>(level.levels.size()));
495
496 auto [lists, faceStarts] = MakeCellFaces(grid);
497 auto [nodes, links] = MakeNodes(grid, images.levelOffsets, lists);
498 images.nodes = std::move(nodes);
499 images.links = std::move(links);
500 images.children.assign(lists.begin() + static_cast<std::ptrdiff_t>(2 * grid.faces.size()), lists.end());
501
502 return images;
503 }
504
505 void GpuImplicit::Dispatch(ID3D12GraphicsCommandList* list, Pass pass, uint32_t groups) {
506 list->SetPipelineState(m_pipelines[pass].Get());
507 list->SetComputeRoot32BitConstants(ROOT_LAYOUT.RootConstantIndex(), ROOT_CONSTANT_COUNT, &m_constants, 0);
508 list->Dispatch(groups, 1, 1);
509 GlobalUavBarrier(list);
510 }
511
512 // 間接の Dispatch: グループの数は計画の引数 slot(ImPlanArgs が書く。その段が要らなければ 0)
513 void GpuImplicit::DispatchIndirect(ID3D12GraphicsCommandList* list, Pass pass, uint32_t slot) {
514 list->SetPipelineState(m_pipelines[pass].Get());
515 list->SetComputeRoot32BitConstants(ROOT_LAYOUT.RootConstantIndex(), ROOT_CONSTANT_COUNT, &m_constants, 0);
516 list->ExecuteIndirect(m_dispatchSignature.Get(), 1, m_buffers[BufferArgs].Get(), uint64_t{slot} * ARGS_BYTES,
517 nullptr, 0);
518 GlobalUavBarrier(list);
519 }
520
521 // 段の形から長い行の節の一覧・ImTail の境・間接の引数を作り、引数を INDIRECT_ARGUMENT にする(T-0136)
522 void GpuImplicit::RecordPlan(ID3D12GraphicsCommandList* list) {
523 Dispatch(list, PassPlanLevels, m_constants.maxLevels);
524 Dispatch(list, PassPlanArgs, 1);
525 const D3D12_RESOURCE_BARRIER barrier = gpu::Transition(
526 m_buffers[BufferArgs].Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
527 list->ResourceBarrier(1, &barrier);
528 }
529
530 // 赤黒の掃き出し sweeps 回(1 回 = 色 0〜1。CPU の Smooth)。slot + 色が引数
531 void GpuImplicit::RecordSmooth(ID3D12GraphicsCommandList* list, uint32_t depth, uint32_t slot, uint32_t sweeps) {
532 m_constants.depth = depth;
533 for (uint32_t sweep = 0; sweep < sweeps; ++sweep) {
534 for (uint32_t color = 0; color < COLOR_COUNT; ++color) {
535 m_constants.color = color;
536 DispatchIndirect(list, PassSmooth, slot + color);
537 }
538 }
539 }
540
541 // CPU の VCycle の再帰を、記録の上限の段まで開いた形: 下り(前の掃き出し → 縮約)× 段 → 下りが止まる段(ImTail か最も粗い段の掃き出し)
542 // → 上り(直し → 後の掃き出し)× 段。下りが止まる段より下の Dispatch は引数が 0 グループ(ImPlanArgs)
543 void GpuImplicit::RecordVCycle(ID3D12GraphicsCommandList* list, const ImplicitOptions& options) {
544 const uint32_t levels = m_constants.dispatchLevels;
545 for (uint32_t depth = 0; depth + 1 < levels; ++depth) {
546 const uint32_t slot = DepthSlot(depth);
547 RecordSmooth(list, depth, slot + IM_SLOT_SMOOTH, options.preSmooth);
548 m_constants.depth = depth + 1;
549 DispatchIndirect(list, PassRestrict, slot + IM_SLOT_RESTRICT);
550 }
551
552 DispatchIndirect(list, PassTail, IM_SLOT_TAIL);
553 if (m_constants.coarsestDispatch != 0)
554 RecordSmooth(list, IM_TERMINAL_DEPTH, IM_SLOT_COARSEST, options.coarsestSweeps);
555
556 for (uint32_t depth = levels - 1; depth-- > 0;) {
557 const uint32_t slot = DepthSlot(depth);
558 m_constants.depth = depth;
559 DispatchIndirect(list, PassProlong, slot + IM_SLOT_PROLONG);
560 RecordSmooth(list, depth, slot + IM_SLOT_SMOOTH, options.postSmooth);
561 }
562 }
563
564 // ここから EndSkippable までの Dispatch を、述語の語 word が 0 でなければ飛ばす(語は前の段が UAV で書いた。バリアは飛ばない)
565 void GpuImplicit::BeginSkippable(ID3D12GraphicsCommandList* list, uint32_t word) {
566 if (!m_predication)
567 return;
568
569 ID3D12Resource* predicate = m_buffers[BufferPredicate].Get();
570 const D3D12_RESOURCE_BARRIER barrier = gpu::Transition(predicate, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
571 D3D12_RESOURCE_STATE_PREDICATION);
572 list->ResourceBarrier(1, &barrier);
573 list->SetPredication(predicate, uint64_t{word} * 8, D3D12_PREDICATION_OP_NOT_EQUAL_ZERO);
574 }
575
576 void GpuImplicit::EndSkippable(ID3D12GraphicsCommandList* list) {
577 if (!m_predication)
578 return;
579
580 list->SetPredication(nullptr, 0, D3D12_PREDICATION_OP_EQUAL_ZERO);
581 const D3D12_RESOURCE_BARRIER barrier = gpu::Transition(
582 m_buffers[BufferPredicate].Get(), D3D12_RESOURCE_STATE_PREDICATION, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
583 list->ResourceBarrier(1, &barrier);
584 }
585
586 bool GpuImplicit::RecordStep(ID3D12GraphicsCommandList* list, uint64_t debugRing, const ImplicitOptions& options,
587 uint32_t maxLimitRounds, const GpuImplicitRecordShape& shape) {
588 if (options.method != ImplicitMethod::Multigrid)
589 return false;
590
591 // 記録の形(T-0154)。計画(ImPlanArgs)も同じ段の数で ImTail の境を切る
592 const uint32_t requested = shape.dispatchLevels == 0 ? m_maxDispatchLevels : shape.dispatchLevels;
593 m_constants.dispatchLevels = std::clamp(requested, 1u, m_maxDispatchLevels);
594 m_constants.coarsestDispatch = shape.coarsestDispatch ? 1 : 0;
595
596 list->SetComputeRootSignature(m_rootSignature.Get());
597 for (uint32_t i = 0; i < BufferCount; ++i)
598 list->SetComputeRootUnorderedAccessView(i, m_buffers[i]->GetGPUVirtualAddress());
599
600 list->SetComputeRootUnorderedAccessView(ROOT_LAYOUT.DebugRingIndex(), debugRing);
601 m_constants.tolerance = options.toleranceMillikelvin;
602 m_constants.slack = options.limitSlackMillikelvin;
603 m_constants.correctionScale = static_cast<int32_t>(options.correctionScale);
604 m_constants.sweeps = options.preSmooth | (options.postSmooth << SWEEP_BITS) |
605 (options.coarsestSweeps << (2 * SWEEP_BITS));
606
607 // --- 計画 → 温度・範囲 → V サイクル × 上限(止めた後の回は述語で飛ばす)---
608 RecordPlan(list);
609 Dispatch(list, PassBegin, 1);
610 DispatchIndirect(list, PassStart, IM_SLOT_CELLS);
611 for (uint32_t cycle = 0; cycle < options.cycles; ++cycle) {
612 const bool skippable = cycle != 0 && options.toleranceMillikelvin != 0;
613 if (skippable)
614 BeginSkippable(list, PREDICATE_CYCLES);
615
616 RecordVCycle(list, options);
617
618 if (options.toleranceMillikelvin != 0)
619 DispatchIndirect(list, PassConverged, IM_SLOT_CONVERGED);
620
621 Dispatch(list, PassCycleEnd, 1);
622 if (skippable)
623 EndSkippable(list);
624 }
625
626 // --- 面の流れ → 安全網(印 → 止めるか → 面を戻す)× (上限 + 1)→ 足す ---
627 DispatchIndirect(list, PassFlows, IM_SLOT_FACES);
628 for (uint32_t pass = 0; pass <= maxLimitRounds; ++pass) {
629 if (pass != 0)
630 BeginSkippable(list, PREDICATE_LIMIT);
631
632 DispatchIndirect(list, PassMark, IM_SLOT_CELLS);
633 Dispatch(list, PassLimitEnd, 1);
634 DispatchIndirect(list, PassLimitFaces, IM_SLOT_FACES);
635 if (pass != 0)
636 EndSkippable(list);
637 }
638
639 DispatchIndirect(list, PassApply, IM_SLOT_CELLS);
640
641 const D3D12_RESOURCE_BARRIER barrier = gpu::Transition(
642 m_buffers[BufferArgs].Get(), D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
643 list->ResourceBarrier(1, &barrier);
644
645 return true;
646 }
647
648 uint32_t GpuImplicit::DispatchesPerCycle(const ImplicitOptions& options) const {
649 const uint32_t perLevel = (COLOR_COUNT * (options.preSmooth + options.postSmooth)) + 2;
650 const uint32_t judge = options.toleranceMillikelvin != 0 ? 2 : 1;
651
652 const uint32_t coarsest = m_constants.coarsestDispatch != 0 ? COLOR_COUNT * options.coarsestSweeps : 0;
653
654 return ((m_constants.dispatchLevels - 1) * perLevel) + 1 + coarsest + judge;
655 }
656
657 void GpuImplicit::RecordTimestamp(ID3D12GraphicsCommandList* list, uint32_t index) {
658 if (index >= MAX_TIMESTAMPS)
659 return;
660
661 list->EndQuery(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, index);
662 m_timestampCount = std::max(m_timestampCount, index + 1);
663 }
664
665 void GpuImplicit::RecordRelease(ID3D12GraphicsCommandList* list) {
666 std::array<D3D12_RESOURCE_BARRIER, BufferCount> barriers{};
667 for (uint32_t i = 0; i < BufferCount; ++i) {
668 barriers[i] = gpu::Transition(m_buffers[i].Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
669 D3D12_RESOURCE_STATE_COMMON);
670 }
671
672 list->ResourceBarrier(BufferCount, barriers.data());
673 }
674
675 void GpuImplicit::RecordReadback(ID3D12GraphicsCommandList* list) {
677 RecordCostReadback(list);
678 }
679
680 void GpuImplicit::RecordCostReadback(ID3D12GraphicsCommandList* list) {
683
684 D3D12_RESOURCE_BARRIER barrier = gpu::Transition(
685 m_buffers[BufferPlan].Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
686 list->ResourceBarrier(1, &barrier);
687 list->CopyBufferRegion(m_planReadback.Get(), 0, m_buffers[BufferPlan].Get(), 0,
688 uint64_t{IM_PLAN_HEADER_WORDS} * sizeof(uint32_t));
689 std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
690 list->ResourceBarrier(1, &barrier);
691 if (m_timestampCount > 0) {
692 list->ResolveQueryData(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 0, m_timestampCount,
693 m_timestampReadback.Get(), 0);
694 }
695 }
696
698 if (grid.cells.size() > m_limits.cells)
699 return false;
700
701 std::vector<ImGpuCell> cells(grid.cells.size());
702 if (!gpu::ReadBuffer(m_cellsReadback.Get(), std::as_writable_bytes(std::span(cells))) || !ReadCost(cost))
703 return false;
704
705 for (size_t i = 0; i < cells.size(); ++i) {
706 grid.cells[i].energy = cells[i].energy;
707 grid.cells[i].fraction = cells[i].fraction;
708 }
709
710 return true;
711 }
712
714 std::vector<uint32_t> state(STATE_WORDS);
715 std::vector<int64_t> wide(WIDE_WORDS);
716 std::vector<uint32_t> plan(IM_PLAN_HEADER_WORDS);
717 if (!gpu::ReadBuffer(m_stateReadback.Get(), std::as_writable_bytes(std::span(state))) ||
718 !gpu::ReadBuffer(m_wideReadback.Get(), std::as_writable_bytes(std::span(wide))) ||
719 !gpu::ReadBuffer(m_planReadback.Get(), std::as_writable_bytes(std::span(plan))))
720 return false;
721
722 cost = {.cycles = state[STATE_CYCLES],
723 .limitedCells = state[STATE_LIMITED_CELLS],
724 .limitRounds = state[STATE_LIMIT_ROUNDS],
725 .worstExcessMillikelvin = wide[WIDE_WORST_EXCESS],
726 .limitFinished = state[STATE_LIMIT_DONE] != 0,
727 .levelCount = plan[IM_PLAN_LEVEL_COUNT],
728 .tailDepth = plan[IM_PLAN_TAIL],
729 .wantedTailDepth = plan[IM_PLAN_WANTED_TAIL]};
730
731 return true;
732 }
733
734 std::vector<uint64_t> GpuImplicit::ReadTimestamps(uint32_t count) const {
735 std::vector<uint64_t> ticks(std::min(count, m_timestampCount));
736 if (!gpu::ReadBuffer(m_timestampReadback.Get(), std::as_writable_bytes(std::span(ticks))))
737 ticks.clear();
738
739 return ticks;
740 }
741
742} // namespace bicameral::sim
std::vector< uint64_t > ReadTimestamps(uint32_t count) const
ComPtr< ID3D12QueryHeap > m_timestamps
ComPtr< ID3D12Resource > m_wideReadback
ComPtr< ID3D12Resource > m_cellsReadback
void RecordCopies(ID3D12GraphicsCommandList *list, std::span< const CopyRegion > regions)
bool ReadCost(GpuImplicitCost &cost) const
void RecordSmooth(ID3D12GraphicsCommandList *list, uint32_t depth, uint32_t slot, uint32_t sweeps)
std::expected< void, std::string > CreatePipelines(ID3D12Device5 *device)
ComPtr< ID3D12Resource > m_planReadback
void RecordVCycle(ID3D12GraphicsCommandList *list, const ImplicitOptions &options)
static constexpr uint32_t MAX_TIMESTAMPS
bool RecordUpload(ID3D12GraphicsCommandList *list, const ImplicitGrid &grid)
ComPtr< ID3D12RootSignature > m_rootSignature
void RecordRelease(ID3D12GraphicsCommandList *list)
std::expected< void, std::string > CreateBuffers(ID3D12Device5 *device)
static std::expected< GpuImplicit, std::string > Create(ID3D12Device5 *device, const GpuImplicitLimits &limits, const GpuImplicitTuning &tuning={})
void RecordCopySystem(ID3D12GraphicsCommandList *list, ID3D12Resource *source, uint64_t cellsOffset, uint64_t facesOffset, uint64_t listsOffset, ID3D12Resource *header, uint64_t faceCountOffset)
static GpuImplicitRecordShape ShapeFrom(const GpuImplicitCost &previous)
uint32_t DispatchesPerCycle(const ImplicitOptions &options) const
void RecordCopyLevels(ID3D12GraphicsCommandList *list, ID3D12Resource *header, ID3D12Resource *nodes, ID3D12Resource *links, ID3D12Resource *children)
void Dispatch(ID3D12GraphicsCommandList *list, Pass pass, uint32_t groups)
bool Read(ImplicitGrid &grid, GpuImplicitCost &cost) const
ComPtr< ID3D12CommandSignature > m_dispatchSignature
void BeginSkippable(ID3D12GraphicsCommandList *list, uint32_t word)
void RecordPlan(ID3D12GraphicsCommandList *list)
ComPtr< ID3D12Resource > m_timestampReadback
void RecordReadback(ID3D12GraphicsCommandList *list)
void RecordReset(ID3D12GraphicsCommandList *list)
void RecordCostReadback(ID3D12GraphicsCommandList *list)
void EndSkippable(ID3D12GraphicsCommandList *list)
void RecordTimestamp(ID3D12GraphicsCommandList *list, uint32_t index)
ComPtr< ID3D12Resource > m_stateReadback
static GpuImplicitLimits LimitsOf(const ImplicitGrid &grid)
std::array< ComPtr< ID3D12PipelineState >, PassCount > m_pipelines
std::array< ComPtr< ID3D12Resource >, BufferCount > m_buffers
std::array< ComPtr< ID3D12Resource >, BufferCount > m_uploads
GpuImplicitTuning m_tuning
void DispatchIndirect(ID3D12GraphicsCommandList *list, Pass pass, uint32_t slot)
bool RecordStep(ID3D12GraphicsCommandList *list, uint64_t debugRing, const ImplicitOptions &options, uint32_t maxLimitRounds=DEFAULT_MAX_LIMIT_ROUNDS, const GpuImplicitRecordShape &shape={})
uint64_t BufferBytes(Buffer buffer) const
GpuImplicitLimits m_limits
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
std::pair< std::vector< uint32_t >, std::vector< uint32_t > > MakeCellFaces(const ImplicitGrid &grid)
std::vector< std::byte > ToBytes(const std::vector< T > &values)
bool WriteUpload(ID3D12Resource *upload, std::span< const std::byte > bytes)
void AppendChildren(const ImplicitGrid &grid, size_t depth, const std::vector< uint32_t > &levelOffsets, std::vector< ImGpuNode > &nodes, std::vector< uint32_t > &lists)
constexpr gpu::RootSignatureLayout ROOT_LAYOUT
constexpr uint32_t PlanDepthWord(uint32_t kind, uint32_t depth)
std::vector< ImGpuCell > MakeCellImage(const ImplicitGrid &grid, const std::vector< uint32_t > &faceStarts)
constexpr std::array< const char *, 15 > SHADERS
void GlobalUavBarrier(ID3D12GraphicsCommandList *list)
NodeImages MakeNodes(const ImplicitGrid &grid, const std::vector< uint32_t > &levelOffsets, std::vector< uint32_t > &lists)
GpuImplicitLevelImages MakeGpuImplicitLevelImages(const ImplicitGrid &grid)
std::vector< multires::ImGpuLink > links
std::vector< multires::ImGpuNode > nodes
std::vector< ImplicitGridLevel > levels
std::vector< ImplicitFace > faces
std::vector< ImplicitCell > cells