Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
gpu_implicit_build.cpp
Go to the documentation of this file.
1// gpu_implicit_build.cpp — 細かいレベルの熱の陰解法の系を GPU の木から作る(T-0129)。何をするかは gpu_implicit_build.h、
2// 段の中身と番号の付け方は shaders/sim/implicit_build.hlsl。段の間は GpuMultires::RecordExternalDispatch が入れる UAV のバリア。
4
5#include <algorithm>
6#include <cstring>
7#include <format>
8#include <span>
9#include <string_view>
10#include <utility>
11
12#include "gpu/resources.h"
13
14using namespace bicameral::multires;
15
16namespace bicameral::sim {
17
18 namespace {
19
20 constexpr uint32_t THREADS = 64; // implicit_build.hlsl の BUILD_THREADS
21 constexpr uint32_t HEADER_WORDS = 16;
22 constexpr uint32_t HEADER_UNKNOWNS = 0;
23 constexpr uint32_t HEADER_BOUNDARY = 1;
24 constexpr uint32_t HEADER_FACES = 2;
25 constexpr uint32_t HEADER_OVERFLOW = 3;
26 constexpr uint32_t ENTRY_WORDS = 4; // 面の候補ごと: 先・印・面の番号・境のセルの番号
27 constexpr uint32_t CELL_WORDS = 3; // セルごと: 番地・面の数・面の一覧の始まり
28 constexpr uint32_t KEY_WORDS = 8; // セルごとの座標(implicit_build.hlsl の CellKeyWord。T-0134)
29 constexpr uint32_t SCAN_THREADS = 1024; // implicit_build.hlsl の SCAN_THREADS(接頭和の 1 グループ)
30 constexpr uint32_t FROZEN_MARKS_BIT = 1u << 24;
31
32 static_assert(sizeof(ImGpuCell) == 40 && sizeof(ImGpuFace) == 32,
33 "implicit_build.hlsl の CELL_BYTES・FACE_BYTES");
34
35 // implicit_build.hlsl の入口(段の順)
55
56 // implicit_build.hlsl の入口から Build を除いたもの(Pass の順)
57 constexpr std::array<const char*, PassCount> PASS_NAMES = {"Clear",
58 "CountBlocks",
59 "ScanBlocks",
60 "NumberUnknowns",
61 "FaceEntries",
62 "ScanEntriesLocal",
63 "ScanEntriesGroups",
64 "ScanEntriesAdd",
65 "Boundary",
66 "Cells",
67 "Faces",
68 "ScanCellsLocal",
69 "ScanCellsGroups",
70 "ScanCellsAdd",
71 "FillLists",
72 "SortLists"};
73
74 // シェーダーのファイル名(CMake の bicameral_add_shader と同じ: 入口の名前の大文字の前に _ を入れて小文字に)
75 std::string ShaderFile(std::string_view name) {
76 std::string file = "sim/implicit_build_";
77 for (size_t i = 0; i < name.size(); ++i) {
78 const char letter = name[i];
79 const bool upper = letter >= 'A' && letter <= 'Z';
80 if (upper && i > 0)
81 file += '_';
82
83 file += upper ? static_cast<char>(letter - 'A' + 'a') : letter;
84 }
85
86 return file + ".cso";
87 }
88
89 uint32_t Groups(uint64_t threads) {
90 return static_cast<uint32_t>((threads + THREADS - 1) / THREADS);
91 }
92
93 // UAV のバッファの先頭 bytes を読み戻しへ写す(UAV → COPY_SOURCE → UAV)
94 void CopyToReadback(ID3D12GraphicsCommandList* list, ID3D12Resource* source, ID3D12Resource* readback,
95 uint64_t bytes) {
96 D3D12_RESOURCE_BARRIER barrier = gpu::Transition(source, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
97 D3D12_RESOURCE_STATE_COPY_SOURCE);
98 list->ResourceBarrier(1, &barrier);
99 list->CopyBufferRegion(readback, 0, source, 0, bytes);
100 std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
101 list->ResourceBarrier(1, &barrier);
102 }
103
104 template <typename T>
105 std::vector<T> Slice(std::span<const std::byte> bytes, uint64_t offset, uint32_t count) {
106 std::vector<T> values(count);
107 if (count != 0)
108 std::memcpy(values.data(), bytes.data() + offset, size_t{count} * sizeof(T));
109
110 return values;
111 }
112
113 } // namespace
114
115 std::expected<GpuImplicitBuild, std::string> GpuImplicitBuild::Create(ID3D12Device5* device,
116 const GpuMultires& multires,
117 const MultiresCapacity& capacity,
118 const GpuImplicitBuildLimits& limits) {
119 if (limits.unknowns == 0 || limits.cells < limits.unknowns)
120 return std::unexpected("陰解法の系の上限が正しくない");
121
122 GpuImplicitBuild result;
123 result.m_limits = limits;
124 result.m_worldBlocks = capacity.worldBlocks;
125
126 // --- 作業場: 見出し・番号の表・最初の面の候補・枠の始まり・面の候補・セル(implicit_build.hlsl の番地の関数)---
127 const uint64_t mapWords = uint64_t{capacity.worldBlocks} * MR_BLOCK_CELLS;
128 // 続けて、接頭和のグループの和(面の候補 2 本・セル)・仮の面の一覧とその位置のセル
129 const uint64_t entries = uint64_t{MR_FACES} * limits.unknowns;
130 const uint64_t entryGroups = (entries + SCAN_THREADS - 1) / SCAN_THREADS;
131 const uint64_t cellGroups = (uint64_t{limits.cells} + SCAN_THREADS - 1) / SCAN_THREADS;
132 const uint64_t workWords = HEADER_WORDS + (2 * mapWords) + capacity.worldBlocks + 1 + (ENTRY_WORDS * entries) +
133 (uint64_t{CELL_WORDS} * limits.cells) + 1 + (2 * (entryGroups + 1)) +
134 (cellGroups + 1) + (uint64_t{4} * entries) + (uint64_t{KEY_WORDS} * limits.cells);
135 result.m_workBytes = workWords * sizeof(uint32_t);
136 result.m_systemBytes = result.ListsOffset() + (uint64_t{2} * MR_FACES * limits.unknowns * sizeof(uint32_t));
137
138 for (const char* name : PASS_NAMES) {
139 const std::string shader = ShaderFile(name);
140 const auto bytecode = gpu::LoadShader(shader);
141 if (!bytecode)
142 return std::unexpected(bytecode.error());
143
144 auto pipeline = gpu::CreateComputePipeline(device, multires.RootSignature(), *bytecode);
145 if (!pipeline)
146 return std::unexpected(std::format("系を作る段のパイプラインを作れない({})", shader));
147
148 result.m_pipelines.push_back(std::move(pipeline));
149 }
150
151 // --- 解いた変化を伝導の表へ足す段(T-0132。段の順〔PASS_NAMES〕には入れない)---
152 const auto applyBytecode = gpu::LoadShader(ShaderFile("Apply"));
153 if (!applyBytecode)
154 return std::unexpected(applyBytecode.error());
155
156 result.m_applyPipeline = gpu::CreateComputePipeline(device, multires.RootSignature(), *applyBytecode);
157 if (!result.m_applyPipeline)
158 return std::unexpected("解いた変化を足す段のパイプラインを作れない");
159
162 result.m_workReadback = gpu::CreateBuffer(device, uint64_t{HEADER_WORDS} * 4, gpu::BufferKind::Readback);
164 result.m_timestampReadback = gpu::CreateBuffer(device, uint64_t{MAX_TIMESTAMPS} * sizeof(uint64_t),
166 if (!result.m_work || !result.m_system || !result.m_workReadback || !result.m_systemReadback ||
167 !result.m_timestampReadback)
168 return std::unexpected("系を作る段のバッファを作れない");
169
170 const D3D12_QUERY_HEAP_DESC queryDesc{
171 .Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP, .Count = MAX_TIMESTAMPS, .NodeMask = 0};
172 if (FAILED(device->CreateQueryHeap(&queryDesc, IID_PPV_ARGS(&result.m_timestamps))))
173 return std::unexpected("タイムスタンプのヒープを作れない");
174
175 return result;
176 }
177
178 // 作業場の語の番号(implicit_build.hlsl の ListCellWord(0)・CellKeyWord(0) と同じ式)
180 const uint64_t mapWords = uint64_t{m_worldBlocks} * MR_BLOCK_CELLS;
181 const uint64_t entries = uint64_t{MR_FACES} * m_limits.unknowns;
182 const uint64_t entryGroups = (entries + SCAN_THREADS - 1) / SCAN_THREADS;
183 const uint64_t cellGroups = (uint64_t{m_limits.cells} + SCAN_THREADS - 1) / SCAN_THREADS;
184 const uint64_t words = HEADER_WORDS + (2 * mapWords) + m_worldBlocks + 1 + (ENTRY_WORDS * entries) +
185 (uint64_t{CELL_WORDS} * m_limits.cells) + 1 + (2 * (entryGroups + 1)) +
186 (cellGroups + 1) + (uint64_t{2} * entries);
187
188 return static_cast<uint32_t>(words);
189 }
190
192 return ListCellsWord() + (2 * MR_FACES * m_limits.unknowns);
193 }
194
196 return uint64_t{m_limits.cells} * sizeof(ImGpuCell);
197 }
198
200 return FacesOffset() + (uint64_t{MR_FACES} * m_limits.unknowns * sizeof(ImGpuFace));
201 }
202
203 void GpuImplicitBuild::RecordBuild(ID3D12GraphicsCommandList10* list, D3D12_GPU_VIRTUAL_ADDRESS debugRing,
204 GpuMultires& multires, const MultiresStepOptions& options, bool useFrozenMarks,
205 bool afterAdmit) {
206 FX_ASSERT(!afterAdmit || useFrozenMarks);
207 RecordPasses(list, debugRing, multires, options, useFrozenMarks, afterAdmit ? PassNumberUnknowns : PassClear,
208 PassCount);
209 }
210
211 void GpuImplicitBuild::RecordAdmit(ID3D12GraphicsCommandList10* list, D3D12_GPU_VIRTUAL_ADDRESS debugRing,
212 GpuMultires& multires, const MultiresStepOptions& options) {
213 RecordPasses(list, debugRing, multires, options, true, PassClear, PassNumberUnknowns);
214 }
215
216 void GpuImplicitBuild::RecordPasses(ID3D12GraphicsCommandList10* list, D3D12_GPU_VIRTUAL_ADDRESS debugRing,
217 GpuMultires& multires, const MultiresStepOptions& options, bool useFrozenMarks,
218 uint32_t firstPass, uint32_t endPass) {
219 const std::array<uint32_t, 4> external = ExternalConstants(options, useFrozenMarks);
220
221 // --- 段ごとのグループの数(数は GPU が決めるので上限から。超えたグループは何もしない)---
222 const uint64_t mapWords = uint64_t{m_worldBlocks} * MR_BLOCK_CELLS;
223 const uint64_t entries = uint64_t{MR_FACES} * m_limits.unknowns;
224 const auto scanGroups = [](uint64_t count) {
225 return static_cast<uint32_t>((count + SCAN_THREADS - 1) / SCAN_THREADS);
226 };
227 const std::array<uint32_t, PassCount> groups = {Groups(HEADER_WORDS + (2 * mapWords)),
229 1,
231 Groups(m_limits.unknowns),
232 scanGroups(entries),
233 1,
234 Groups(entries),
235 Groups(entries),
236 Groups(m_limits.cells),
237 Groups(entries),
238 scanGroups(m_limits.cells),
239 1,
240 Groups(m_limits.cells),
241 Groups(entries),
242 Groups(2 * entries)};
243
244 multires.SetExternalViews(m_work->GetGPUVirtualAddress(), m_system->GetGPUVirtualAddress());
245 for (uint32_t pass = firstPass; pass < endPass; ++pass) {
246 if (m_stampPasses)
247 RecordTimestamp(list, pass);
248
249 multires.RecordExternalDispatch(list, debugRing, m_pipelines[pass].Get(), groups[pass], external);
250 }
251
252 if (m_stampPasses && endPass == PassCount)
253 RecordTimestamp(list, PassCount);
254
255 multires.SetExternalViews(0, 0);
256 }
257
258 // RecordBuild と同じ定数(基準のレベル・implicitMaxGap・凍った印を見るか | 未知数の上限 | セルの上限)
259 std::array<uint32_t, 4> GpuImplicitBuild::ExternalConstants(const MultiresStepOptions& options,
260 bool useFrozenMarks) const {
261 const auto base = static_cast<uint16_t>(static_cast<int16_t>(options.subcycleBaseLevel));
262 const uint32_t packed = uint32_t{base} | (std::min(options.implicitMaxGap, 255u) << 16) |
263 (useFrozenMarks ? FROZEN_MARKS_BIT : 0u);
264
265 return {packed, m_limits.unknowns, m_limits.cells, 0};
266 }
267
268 void GpuImplicitBuild::RecordApply(ID3D12GraphicsCommandList10* list, D3D12_GPU_VIRTUAL_ADDRESS debugRing,
269 GpuMultires& multires, const MultiresStepOptions& options,
270 ID3D12Resource* solvedCells) {
271 multires.SetExternalViews(m_work->GetGPUVirtualAddress(), solvedCells->GetGPUVirtualAddress());
272 multires.RecordExternalDispatch(list, debugRing, m_applyPipeline.Get(), Groups(m_limits.cells),
273 ExternalConstants(options, true));
274 multires.SetExternalViews(0, 0);
275 }
276
277 void GpuImplicitBuild::RecordCopyTo(ID3D12GraphicsCommandList* list, GpuImplicit& implicit) {
278 std::array<D3D12_RESOURCE_BARRIER, 2> barriers = {
279 gpu::Transition(m_system.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE),
280 gpu::Transition(m_work.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE)};
281 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
282 implicit.RecordCopySystem(list, m_system.Get(), 0, FacesOffset(), ListsOffset(), m_work.Get(),
283 uint64_t{HEADER_FACES} * sizeof(uint32_t));
284 for (D3D12_RESOURCE_BARRIER& barrier : barriers)
285 std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
286
287 list->ResourceBarrier(static_cast<UINT>(barriers.size()), barriers.data());
288 }
289
291 return PassCount;
292 }
293
294 // 段の名前(計測の表に使う。implicit_build.hlsl の入口から Build を除いたもの)
295 const char* GpuImplicitBuild::StageName(uint32_t pass) {
296 return pass < PASS_NAMES.size() ? PASS_NAMES[pass] : "?";
297 }
298
299 void GpuImplicitBuild::RecordTimestamp(ID3D12GraphicsCommandList* list, uint32_t index) {
300 if (index >= MAX_TIMESTAMPS)
301 return;
302
303 list->EndQuery(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, index);
304 m_timestampCount = std::max(m_timestampCount, index + 1);
305 }
306
307 void GpuImplicitBuild::RecordReadback(ID3D12GraphicsCommandList* list) {
308 CopyToReadback(list, m_work.Get(), m_workReadback.Get(), uint64_t{HEADER_WORDS} * 4);
309 CopyToReadback(list, m_system.Get(), m_systemReadback.Get(), m_systemBytes);
310 if (m_timestampCount > 0) {
311 list->ResolveQueryData(m_timestamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 0, m_timestampCount,
312 m_timestampReadback.Get(), 0);
313 }
314 }
315
316 std::expected<GpuImplicitSystem, std::string> GpuImplicitBuild::Read() const {
317 std::array<uint32_t, HEADER_WORDS> header{};
318 std::vector<std::byte> bytes(m_systemBytes);
319 if (!gpu::ReadBuffer(m_workReadback.Get(), std::as_writable_bytes(std::span(header))) ||
320 !gpu::ReadBuffer(m_systemReadback.Get(), bytes))
321 return std::unexpected("作った系を読み戻せない");
322
323 GpuImplicitSystem system;
324 system.unknowns = header[HEADER_UNKNOWNS];
325 system.boundary = header[HEADER_BOUNDARY];
326 system.faces = header[HEADER_FACES];
327 system.overflow = header[HEADER_OVERFLOW] != 0;
328 if (system.overflow)
329 return system;
330
331 system.cells = Slice<ImGpuCell>(bytes, 0, system.unknowns + system.boundary);
332 system.faceList = Slice<ImGpuFace>(bytes, FacesOffset(), system.faces);
333 system.lists = Slice<uint32_t>(bytes, ListsOffset(), 2 * system.faces);
334
335 return system;
336 }
337
338 std::vector<uint64_t> GpuImplicitBuild::ReadTimestamps(uint32_t count) const {
339 std::vector<uint64_t> ticks(std::min(count, m_timestampCount));
340 if (!gpu::ReadBuffer(m_timestampReadback.Get(), std::as_writable_bytes(std::span(ticks))))
341 ticks.clear();
342
343 return ticks;
344 }
345
346} // namespace bicameral::sim
ComPtr< ID3D12Resource > m_workReadback
static constexpr uint32_t MAX_TIMESTAMPS
void RecordAdmit(ID3D12GraphicsCommandList10 *list, D3D12_GPU_VIRTUAL_ADDRESS debugRing, GpuMultires &multires, const MultiresStepOptions &options)
ComPtr< ID3D12Resource > m_system
std::vector< uint64_t > ReadTimestamps(uint32_t count) const
void RecordReadback(ID3D12GraphicsCommandList *list)
void RecordApply(ID3D12GraphicsCommandList10 *list, D3D12_GPU_VIRTUAL_ADDRESS debugRing, GpuMultires &multires, const MultiresStepOptions &options, ID3D12Resource *solvedCells)
ComPtr< ID3D12PipelineState > m_applyPipeline
ComPtr< ID3D12QueryHeap > m_timestamps
static std::expected< GpuImplicitBuild, std::string > Create(ID3D12Device5 *device, const GpuMultires &multires, const MultiresCapacity &capacity, const GpuImplicitBuildLimits &limits)
void RecordPasses(ID3D12GraphicsCommandList10 *list, D3D12_GPU_VIRTUAL_ADDRESS debugRing, GpuMultires &multires, const MultiresStepOptions &options, bool useFrozenMarks, uint32_t firstPass, uint32_t endPass)
std::expected< GpuImplicitSystem, std::string > Read() const
static const char * StageName(uint32_t pass)
void RecordTimestamp(ID3D12GraphicsCommandList *list, uint32_t index)
ComPtr< ID3D12Resource > m_timestampReadback
void RecordCopyTo(ID3D12GraphicsCommandList *list, GpuImplicit &implicit)
std::vector< ComPtr< ID3D12PipelineState > > m_pipelines
std::array< uint32_t, 4 > ExternalConstants(const MultiresStepOptions &options, bool useFrozenMarks) const
void RecordBuild(ID3D12GraphicsCommandList10 *list, D3D12_GPU_VIRTUAL_ADDRESS debugRing, GpuMultires &multires, const MultiresStepOptions &options, bool useFrozenMarks=false, bool afterAdmit=false)
ComPtr< ID3D12Resource > m_systemReadback
void RecordCopySystem(ID3D12GraphicsCommandList *list, ID3D12Resource *source, uint64_t cellsOffset, uint64_t facesOffset, uint64_t listsOffset, ID3D12Resource *header, uint64_t faceCountOffset)
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 CopyToReadback(ID3D12GraphicsCommandList *list, ID3D12Resource *source, ID3D12Resource *readback, uint64_t bytes)
constexpr std::array< const char *, PassCount > PASS_NAMES
std::vector< T > Slice(std::span< const std::byte > bytes, uint64_t offset, uint32_t count)
std::vector< multires::ImGpuCell > cells
std::vector< multires::ImGpuFace > faceList