Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
gpu_implicit_levels.cpp
Go to the documentation of this file.
1// gpu_implicit_levels.cpp — 細かいレベルの熱の陰解法の多重格子の段と重みを GPU で作る(T-0134)。何をするかは gpu_implicit_levels.h、
2// 段の中身と番号の付け方は shaders/sim/implicit_levels.hlsl。段の間は UAV のバリア、回は述語で飛ばす(GpuImplicit と同じ形)。
4
5#include <algorithm>
6#include <array>
7#include <bit>
8#include <cstring>
9#include <format>
10#include <span>
11#include <string_view>
12#include <utility>
13
14#include "gpu/resources.h"
15
16using namespace bicameral::multires;
17
18namespace bicameral::sim {
19
20 namespace {
21
22 constexpr uint32_t THREADS = 64; // implicit_levels.hlsl の LV_THREADS
23 constexpr uint32_t SCAN_THREADS = 1024; // implicit_levels.hlsl の LV_SCAN_THREADS
24 constexpr uint32_t UAV_COUNT = 7;
25 constexpr uint32_t ROOT_CONSTANT_COUNT = 13;
27 .uavCount = UAV_COUNT, .rootConstantCount = ROOT_CONSTANT_COUNT, .debugRing = true};
28
29 // implicit_levels.hlsl の作業場の並び(語)
30 constexpr uint32_t DEPTH_BASE = 16;
31 constexpr uint32_t DEPTH_STRIDE = 72;
32 constexpr uint32_t HEADER_WORDS = DEPTH_BASE + (8 * DEPTH_STRIDE);
33 constexpr uint32_t LEVEL_COUNT = 0;
34 constexpr uint32_t OVERFLOW_WORD = 1;
35 constexpr uint32_t NODE_OFFSET = 0;
36 constexpr uint32_t NODE_COUNT = 1;
37 constexpr uint32_t LINK_OFFSET = 2;
38 constexpr uint32_t LINK_COUNT = 3;
39 constexpr uint32_t NODE_WORDS = 16 + 10; // 節の欄 LvNode + 節の語
40 constexpr uint32_t LINK_WORDS = 10;
41 constexpr uint32_t MAX_LEVELS = 64; // DEPTH_STRIDE に収まる段の数
42
43 static_assert(sizeof(ImGpuNode) == 40 && sizeof(ImGpuLink) == 16, "implicit_levels.hlsl の並び");
44 // 見出しは GpuImplicit の計画の段の形へそのまま写す(RecordCopyTo。T-0136)
45 static_assert(HEADER_WORDS == IM_PLAN_SHAPE_WORDS && DEPTH_BASE == IM_PLAN_DEPTH_BASE &&
46 DEPTH_STRIDE == IM_PLAN_DEPTH_STRIDE && LEVEL_COUNT == IM_PLAN_LEVEL_COUNT &&
47 NODE_OFFSET == IM_PLAN_NODE_OFFSET && NODE_COUNT == IM_PLAN_NODE_COUNT,
48 "implicit_levels.hlsl の見出しと GpuImplicit の計画の並び");
49
50 // implicit_levels.hlsl の入口(段の順。Lv を除いた名前)
73
74 constexpr std::array<std::string_view, PassTotal> PASS_NAMES = {"clear",
75 "cells",
76 "cell_links",
77 "node_hash",
78 "node_first",
79 "parent_scan_local",
80 "parent_scan_groups",
81 "parents",
82 "link_hash",
83 "count",
84 "row_scan_local",
85 "row_scan_groups",
86 "place",
87 "sort",
88 "add_coefficients",
89 "coarse_nodes",
90 "finish",
91 "round_end",
92 "tail"};
93
94 // 回の中の段(述語で飛ばす。RoundEnd は述語の外)
109
110 uint32_t Groups(uint64_t threads) {
111 return std::max<uint32_t>(1, static_cast<uint32_t>((threads + THREADS - 1) / THREADS));
112 }
113
114 uint32_t ScanGroups(uint64_t count) {
115 return std::max<uint32_t>(1, static_cast<uint32_t>((count + SCAN_THREADS - 1) / SCAN_THREADS));
116 }
117
118 void GlobalUavBarrier(ID3D12GraphicsCommandList* list) {
119 const D3D12_RESOURCE_BARRIER barrier{.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV,
120 .UAV = {.pResource = nullptr}};
121 list->ResourceBarrier(1, &barrier);
122 }
123
124 // UAV のバッファの先頭 bytes を読み戻しへ写す(UAV → COPY_SOURCE → UAV)
125 void CopyToReadback(ID3D12GraphicsCommandList* list, ID3D12Resource* source, ID3D12Resource* readback,
126 uint64_t bytes) {
127 D3D12_RESOURCE_BARRIER barrier = gpu::Transition(source, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
128 D3D12_RESOURCE_STATE_COPY_SOURCE);
129 list->ResourceBarrier(1, &barrier);
130 list->CopyBufferRegion(readback, 0, source, 0, bytes);
131 std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
132 list->ResourceBarrier(1, &barrier);
133 }
134
135 template <typename T>
136 std::vector<T> Slice(const std::vector<std::byte>& bytes, uint32_t count) {
137 std::vector<T> values(count);
138 if (count != 0)
139 std::memcpy(values.data(), bytes.data(), size_t{count} * sizeof(T));
140
141 return values;
142 }
143
144 uint32_t DepthWord(uint32_t kind, uint32_t depth) {
145 return DEPTH_BASE + (kind * DEPTH_STRIDE) + depth;
146 }
147
148 } // namespace
149
150 std::expected<GpuImplicitLevels, std::string> GpuImplicitLevels::Create(ID3D12Device5* device,
151 const GpuImplicitBuild& build,
152 const GpuImplicitLevelLimits& limits) {
153 const GpuImplicitBuildLimits& buildLimits = build.Limits();
154 if (limits.nodes < buildLimits.cells || limits.links == 0 || limits.levels == 0 || limits.levels > MAX_LEVELS)
155 return std::unexpected("多重格子の段の上限が正しくない");
156
157 GpuImplicitLevels result;
158 Constants& constants = result.m_constants;
159 constants.maxCells = buildLimits.cells;
160 constants.maxNodes = limits.nodes;
161 constants.maxLinks = limits.links;
162 constants.levelLinks = 2 * MR_FACES * buildLimits.unknowns;
163 constants.maxLevels = limits.levels;
164 constants.nodeTableMask = std::bit_ceil(2 * constants.maxCells) - 1;
165 constants.linkTableMask = std::bit_ceil(2 * constants.levelLinks) - 1;
166 constants.keysWord = build.CellKeysWord();
167 constants.listCellsWord = build.ListCellsWord();
168 constants.facesByte = static_cast<uint32_t>(build.FacesOffset());
169 constants.listsByte = static_cast<uint32_t>(build.ListsOffset());
170 constants.tailMaxNodes = limits.tailMaxNodes;
171 result.m_dispatchRounds = std::min(limits.dispatchRounds, limits.levels - 1);
172
173 // --- 作業場: 見出し・節・隣・仮の子の一覧・2 つの表・接頭和のグループの和(implicit_levels.hlsl の番地の関数)---
174 const uint64_t words = HEADER_WORDS + (uint64_t{NODE_WORDS} * limits.nodes) +
175 (uint64_t{LINK_WORDS} * limits.links) + limits.nodes + constants.nodeTableMask + 1 +
176 constants.linkTableMask + 1 + (2 * (uint64_t{ScanGroups(constants.maxCells)} + 1));
177 result.m_workBytes = words * sizeof(uint32_t);
178
179 result.m_rootSignature = gpu::CreateRootSignature(device, ROOT_LAYOUT);
180 if (!result.m_rootSignature)
181 return std::unexpected("多重格子の段のルート署名を作れない");
182
183 for (const std::string_view name : PASS_NAMES) {
184 const std::string shader = std::format("sim/implicit_levels_{}.cso", name);
185 const auto bytecode = gpu::LoadShader(shader);
186 if (!bytecode)
187 return std::unexpected(bytecode.error());
188
189 auto pipeline = gpu::CreateComputePipeline(device, result.m_rootSignature.Get(), *bytecode);
190 if (!pipeline)
191 return std::unexpected(std::format("多重格子の段のパイプラインを作れない({})", shader));
192
193 result.m_pipelines.push_back(std::move(pipeline));
194 }
195
196 const uint64_t nodeBytes = uint64_t{limits.nodes} * sizeof(ImGpuNode);
197 const uint64_t linkBytes = uint64_t{limits.links} * sizeof(ImGpuLink);
198 const uint64_t childBytes = uint64_t{limits.nodes} * sizeof(uint32_t);
203 result.m_predicate = gpu::CreateBuffer(device, uint64_t{MAX_LEVELS} * 8, gpu::BufferKind::UnorderedAccess);
204 result.m_headerReadback = gpu::CreateBuffer(device, uint64_t{HEADER_WORDS} * 4, gpu::BufferKind::Readback);
208 result.m_timestampReadback = gpu::CreateBuffer(device, uint64_t{MAX_TIMESTAMPS} * sizeof(uint64_t),
210 if (!result.m_work || !result.m_nodes || !result.m_links || !result.m_children || !result.m_predicate ||
211 !result.m_headerReadback || !result.m_nodesReadback || !result.m_linksReadback ||
212 !result.m_childrenReadback || !result.m_timestampReadback)
213 return std::unexpected("多重格子の段のバッファを作れない");
214
215 const D3D12_QUERY_HEAP_DESC queryDesc{
216 .Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP, .Count = MAX_TIMESTAMPS, .NodeMask = 0};
217 if (FAILED(device->CreateQueryHeap(&queryDesc, IID_PPV_ARGS(&result.m_timestamps))))
218 return std::unexpected("タイムスタンプのヒープを作れない");
219
220 return result;
221 }
222
223 void GpuImplicitLevels::Dispatch(ID3D12GraphicsCommandList* list, uint32_t pass, uint32_t groups) {
224 list->SetPipelineState(m_pipelines[pass].Get());
225 list->SetComputeRoot32BitConstants(ROOT_LAYOUT.RootConstantIndex(), ROOT_CONSTANT_COUNT, &m_constants, 0);
226 list->Dispatch(groups, 1, 1);
227 GlobalUavBarrier(list);
228 }
229
230 // 回 coarsening の段を、その回の述語の語が 0 でなければ飛ばす(語は前の RoundEnd か CellLinks が UAV で書いた)
231 void GpuImplicitLevels::BeginSkippable(ID3D12GraphicsCommandList* list, uint32_t coarsening) {
232 const D3D12_RESOURCE_BARRIER barrier = gpu::Transition(m_predicate.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
233 D3D12_RESOURCE_STATE_PREDICATION);
234 list->ResourceBarrier(1, &barrier);
235 list->SetPredication(m_predicate.Get(), uint64_t{coarsening} * 8, D3D12_PREDICATION_OP_NOT_EQUAL_ZERO);
236 }
237
238 void GpuImplicitLevels::EndSkippable(ID3D12GraphicsCommandList* list) {
239 list->SetPredication(nullptr, 0, D3D12_PREDICATION_OP_EQUAL_ZERO);
240 const D3D12_RESOURCE_BARRIER barrier = gpu::Transition(m_predicate.Get(), D3D12_RESOURCE_STATE_PREDICATION,
241 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
242 list->ResourceBarrier(1, &barrier);
243 }
244
245 void GpuImplicitLevels::RecordBuild(ID3D12GraphicsCommandList* list, D3D12_GPU_VIRTUAL_ADDRESS debugRing,
246 const GpuImplicitBuild& build) {
247 list->SetComputeRootSignature(m_rootSignature.Get());
248 const std::array<ID3D12Resource*, UAV_COUNT> buffers = {
249 build.SystemBuffer(), build.WorkBuffer(), m_work.Get(), m_nodes.Get(),
250 m_links.Get(), m_children.Get(), m_predicate.Get()};
251 for (uint32_t i = 0; i < UAV_COUNT; ++i)
252 list->SetComputeRootUnorderedAccessView(i, buffers[i]->GetGPUVirtualAddress());
253
254 list->SetComputeRootUnorderedAccessView(ROOT_LAYOUT.DebugRingIndex(), debugRing);
255
256 // --- 段ごとのグループの数(数は GPU が決めるので上限から。超えたスレッドは何もしない。間接の引数は T-0135)---
257 const uint32_t cells = m_constants.maxCells;
258 const uint32_t links = m_constants.levelLinks;
259 const uint32_t both = std::max(cells, links);
260 const uint32_t clearWords = std::max(
261 {HEADER_WORDS, m_constants.nodeTableMask + 1, m_constants.linkTableMask + 1, m_constants.maxLevels});
262 std::array<uint32_t, PassTotal> groups{};
263 groups[PassClear] = Groups(clearWords);
264 groups[PassCells] = Groups(cells);
265 groups[PassCellLinks] = Groups(links);
266 groups[PassNodeHash] = Groups(cells);
267 groups[PassNodeFirst] = Groups(cells);
268 groups[PassParentScanLocal] = ScanGroups(cells);
269 groups[PassParentScanGroups] = 1;
270 groups[PassParents] = Groups(cells);
271 groups[PassLinkHash] = Groups(links);
272 groups[PassCount] = Groups(both);
273 groups[PassRowScanLocal] = ScanGroups(cells);
274 groups[PassRowScanGroups] = 1;
275 groups[PassPlace] = Groups(both);
276 groups[PassSort] = Groups(both);
277 groups[PassAddCoefficients] = Groups(links);
278 groups[PassCoarseNodes] = Groups(cells);
279 groups[PassFinish] = Groups(both);
280 groups[PassRoundEnd] = 1;
281 groups[PassTail] = 1;
282
283 // --- 段 0 → 回 d(段 d を縮約して段 d + 1)を Dispatch で m_dispatchRounds 回 → 残りは LvTail(T-0135)---
284 m_constants.depth = 0;
285 for (const Pass pass : {PassClear, PassCells, PassCellLinks})
286 Dispatch(list, pass, groups[pass]);
287
288 for (uint32_t coarsening = 0; coarsening < m_dispatchRounds; ++coarsening) {
289 m_constants.depth = coarsening;
290 BeginSkippable(list, coarsening);
291 for (const Pass pass : ROUND_PASSES)
292 Dispatch(list, pass, groups[pass]);
293
294 EndSkippable(list);
295 Dispatch(list, PassRoundEnd, groups[PassRoundEnd]);
296 }
297
298 if (UsesTail())
299 Dispatch(list, PassTail, groups[PassTail]);
300 }
301
303 return m_dispatchRounds + 1 < m_constants.maxLevels || m_constants.tailMaxNodes != 0;
304 }
305
307 return 3 + (UsesTail() ? 1 : 0) + (m_dispatchRounds * static_cast<uint32_t>(ROUND_PASSES.size() + 1));
308 }
309
310 void GpuImplicitLevels::RecordCopyTo(ID3D12GraphicsCommandList* list, GpuImplicit& implicit) {
311 const std::array<ID3D12Resource*, 4> sources = {m_work.Get(), m_nodes.Get(), m_links.Get(), m_children.Get()};
312 std::array<D3D12_RESOURCE_BARRIER, 4> barriers{};
313 for (size_t i = 0; i < sources.size(); ++i) {
314 barriers[i] = gpu::Transition(sources[i], D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
315 D3D12_RESOURCE_STATE_COPY_SOURCE);
316 }
317
318 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
319 implicit.RecordCopyLevels(list, m_work.Get(), m_nodes.Get(), m_links.Get(), m_children.Get());
320 for (D3D12_RESOURCE_BARRIER& barrier : barriers)
321 std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
322
323 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
324 }
325
326 void GpuImplicitLevels::RecordTimestamp(ID3D12GraphicsCommandList* list, uint32_t index) {
327 if (index >= MAX_TIMESTAMPS)
328 return;
329
330 list->EndQuery(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, index);
331 m_timestampCount = std::max(m_timestampCount, index + 1);
332 }
333
334 void GpuImplicitLevels::RecordReadback(ID3D12GraphicsCommandList* list) {
335 CopyToReadback(list, m_work.Get(), m_headerReadback.Get(), uint64_t{HEADER_WORDS} * 4);
336 CopyToReadback(list, m_nodes.Get(), m_nodesReadback.Get(), uint64_t{m_constants.maxNodes} * sizeof(ImGpuNode));
337 CopyToReadback(list, m_links.Get(), m_linksReadback.Get(), uint64_t{m_constants.maxLinks} * sizeof(ImGpuLink));
338 CopyToReadback(list, m_children.Get(), m_childrenReadback.Get(), uint64_t{m_constants.maxNodes} * 4);
339 if (m_timestampCount > 0) {
340 list->ResolveQueryData(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 0, m_timestampCount,
341 m_timestampReadback.Get(), 0);
342 }
343 }
344
345 std::expected<GpuImplicitLevelSystem, std::string> GpuImplicitLevels::Read() const {
346 std::array<uint32_t, HEADER_WORDS> header{};
347 std::vector<std::byte> nodes(uint64_t{m_constants.maxNodes} * sizeof(ImGpuNode));
348 std::vector<std::byte> links(uint64_t{m_constants.maxLinks} * sizeof(ImGpuLink));
349 std::vector<std::byte> children(uint64_t{m_constants.maxNodes} * 4);
350 if (!gpu::ReadBuffer(m_headerReadback.Get(), std::as_writable_bytes(std::span(header))) ||
351 !gpu::ReadBuffer(m_nodesReadback.Get(), nodes) || !gpu::ReadBuffer(m_linksReadback.Get(), links) ||
352 !gpu::ReadBuffer(m_childrenReadback.Get(), children))
353 return std::unexpected("作った段を読み戻せない");
354
356 system.overflow = header[OVERFLOW_WORD] != 0;
357 const uint32_t levelCount = header[LEVEL_COUNT];
358 if (system.overflow || levelCount == 0 || levelCount > MAX_LEVELS)
359 return system;
360
361 // --- 段ごとの始まり(最後は最も粗い段の終わり)---
362 GpuImplicitLevelImages& images = system.images;
363 for (uint32_t depth = 0; depth < levelCount; ++depth) {
364 images.levelOffsets.push_back(header[DepthWord(NODE_OFFSET, depth)]);
365 system.linkOffsets.push_back(header[DepthWord(LINK_OFFSET, depth)]);
366 }
367
368 const uint32_t last = levelCount - 1;
369 images.levelOffsets.push_back(header[DepthWord(NODE_OFFSET, last)] + header[DepthWord(NODE_COUNT, last)]);
370 system.linkOffsets.push_back(header[DepthWord(LINK_OFFSET, last)] + header[DepthWord(LINK_COUNT, last)]);
371 images.nodes = Slice<ImGpuNode>(nodes, images.levelOffsets.back());
372 images.links = Slice<ImGpuLink>(links, system.linkOffsets.back());
373 images.children = Slice<uint32_t>(children, images.levelOffsets[last]);
374
375 return system;
376 }
377
378 std::vector<uint64_t> GpuImplicitLevels::ReadTimestamps(uint32_t count) const {
379 std::vector<uint64_t> ticks(std::min(count, m_timestampCount));
380 if (!gpu::ReadBuffer(m_timestampReadback.Get(), std::as_writable_bytes(std::span(ticks))))
381 ticks.clear();
382
383 return ticks;
384 }
385
386} // namespace bicameral::sim
ID3D12Resource * SystemBuffer() const
const GpuImplicitBuildLimits & Limits() const
ID3D12Resource * WorkBuffer() const
void BeginSkippable(ID3D12GraphicsCommandList *list, uint32_t coarsening)
static constexpr uint32_t MAX_TIMESTAMPS
ComPtr< ID3D12Resource > m_childrenReadback
void EndSkippable(ID3D12GraphicsCommandList *list)
void RecordCopyTo(ID3D12GraphicsCommandList *list, GpuImplicit &implicit)
static std::expected< GpuImplicitLevels, std::string > Create(ID3D12Device5 *device, const GpuImplicitBuild &build, const GpuImplicitLevelLimits &limits)
ComPtr< ID3D12Resource > m_timestampReadback
ComPtr< ID3D12Resource > m_linksReadback
ComPtr< ID3D12Resource > m_headerReadback
std::vector< ComPtr< ID3D12PipelineState > > m_pipelines
void RecordReadback(ID3D12GraphicsCommandList *list)
std::expected< GpuImplicitLevelSystem, std::string > Read() const
void RecordTimestamp(ID3D12GraphicsCommandList *list, uint32_t index)
void RecordBuild(ID3D12GraphicsCommandList *list, D3D12_GPU_VIRTUAL_ADDRESS debugRing, const GpuImplicitBuild &build)
ComPtr< ID3D12RootSignature > m_rootSignature
void Dispatch(ID3D12GraphicsCommandList *list, uint32_t pass, uint32_t groups)
ComPtr< ID3D12Resource > m_nodesReadback
ComPtr< ID3D12QueryHeap > m_timestamps
std::vector< uint64_t > ReadTimestamps(uint32_t count) const
void RecordCopyLevels(ID3D12GraphicsCommandList *list, ID3D12Resource *header, ID3D12Resource *nodes, ID3D12Resource *links, ID3D12Resource *children)
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
constexpr std::array< std::string_view, PassTotal > PASS_NAMES
std::vector< T > Slice(const std::vector< std::byte > &bytes, uint32_t count)
void CopyToReadback(ID3D12GraphicsCommandList *list, ID3D12Resource *source, ID3D12Resource *readback, uint64_t bytes)
std::vector< multires::ImGpuLink > links
std::vector< multires::ImGpuNode > nodes