perf(tlas): dirty-track the per-frame TLAS instance+metadata upload (#118)
BuildTLAS rebuilt the host instance+metadata buffers with an O(n) copy of
every 64 B VkAccelerationStructureInstanceKHR + metadata entry every frame,
unconditionally, then flushed the whole high-water capacity (VK_WHOLE_SIZE)
on both buffers. At the millions-of-instances target that copy dominates the
CPU frame, and the whole-buffer flush costs on non-coherent BAR/VRAM.
Add a generation counter so only changed host-authored fields are copied, and
feed the same dirty span into the ranged FlushDevice(offset, bytes) overload:
- RenderingElement3D::hostDataVersion + MarkHostDataDirty() (bumps a global
monotonic counter). TlasWithBuffer::uploadedVersion records, per frame, the
version last copied into each slot. A slot is copied only when its element
advanced past the recorded version; version 0 ("untracked") reads dirty every
frame, so callers that don't opt in keep the prior copy-every-frame behaviour.
Globally-unique versions make this correct under relocation (Remove's
swap-and-pop, and remove+add that nets the same count on the refit path)
without tracking element identity. The reset on every topology change covers
buffer reallocation and the reshuffled element->slot mapping.
- The dirty [first, last] envelope drives both the copy and the flush: a new
VulkanBuffer::FlushDevice(cmd, access, stage, offset, bytes) overload flushes
+ barriers just that span for instanceBuffer, and the ranged
FlushDevice(offset, bytes) for metadataBuffer. When nothing is dirty both are
skipped — the skipped HOST->build barrier only ever ordered host writes, never
the application's compute-written GPU-owned transform (that compute->build
ordering is the caller's, and is unchanged).
Constraint honoured: transformOwnedByGpu transforms are still never host-copied.
The API field/method are mirrored on the WebGPU class for source portability
(the WebGPU build re-uploads its small mirror wholesale and ignores the version).
New test TLASInstanceDirtyTracking drives the real RT device and reads back the
host-mapped buffers to assert: tracked elements upload once then skip until
re-marked, untracked elements always upload, and relocation on the refit path
re-uploads exactly the moved slots — with zero validation-layer errors over the
ranged flush.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9d9f9d9d2c
commit
b5b8c04237
5 changed files with 437 additions and 12 deletions
33
project.cpp
33
project.cpp
|
|
@ -359,6 +359,39 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
tlc.GetInterfacesAndImplementations(ifaces, tlasImpls);
|
||||
cfg.tests.push_back(std::move(tlasTest));
|
||||
|
||||
// Issue #118: the per-frame TLAS instance+metadata host rebuild copies
|
||||
// (and flushes) only the slots whose element's host-authored data
|
||||
// changed, tracked via RenderingElement3D::hostDataVersion against the
|
||||
// per-frame uploadedVersion record; untracked elements (version 0) keep
|
||||
// the always-copy behaviour. Drives the real AS-build path — a cube
|
||||
// BLAS plus BuildTLAS at a sequence of marks/mutations — and reads back
|
||||
// the host-mapped buffers to assert that clean slots are skipped, dirty
|
||||
// slots re-uploaded, and relocation on the refit path re-uploads exactly
|
||||
// the moved slots, with the validation layer reporting no errors over
|
||||
// the ranged FlushDevice the dirty span feeds. Needs a Vulkan RT device
|
||||
// at runtime, so it shares the native build settings.
|
||||
Test tlasDirtyTest;
|
||||
Configuration& tld = tlasDirtyTest.config;
|
||||
tld.path = cfg.path;
|
||||
tld.name = "TLASInstanceDirtyTracking";
|
||||
tld.outputName = "TLASInstanceDirtyTracking";
|
||||
tld.type = ConfigurationType::Executable;
|
||||
tld.target = cfg.target;
|
||||
tld.march = cfg.march;
|
||||
tld.mtune = cfg.mtune;
|
||||
tld.debug = cfg.debug;
|
||||
tld.sysroot = cfg.sysroot;
|
||||
tld.dependencies = cfg.dependencies;
|
||||
tld.externalDependencies = cfg.externalDependencies;
|
||||
tld.compileFlags = cfg.compileFlags;
|
||||
tld.linkFlags = cfg.linkFlags;
|
||||
tld.defines = cfg.defines;
|
||||
tld.cFiles = cfg.cFiles;
|
||||
std::vector<fs::path> tlasDirtyImpls(impls.begin(), impls.end());
|
||||
tlasDirtyImpls.emplace_back("tests/TLASInstanceDirtyTracking/main");
|
||||
tld.GetInterfacesAndImplementations(ifaces, tlasDirtyImpls);
|
||||
cfg.tests.push_back(std::move(tlasDirtyTest));
|
||||
|
||||
// Issue #51: FontAtlas only re-uploads the dirty sub-rect now,
|
||||
// tracked via FontAtlas::DirtyRect. The accumulation/clamp math is
|
||||
// pure CPU, so this test drives it directly — no GPU device needed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue