perf(rt): high-water-mark growth for TLAS host-input buffers (#64)

BuildTLAS reallocated the host-visible instanceBuffer and metadataBuffer
on every topology change. They only ever need to hold at least
primitiveCount entries (the AS build reads exactly primitiveCount via
tlasRangeInfo, and the copy loop writes [0, primitiveCount)), so a
shrink — or any growth that still fits the previous capacity — can reuse
the existing allocation. Gate the two Resize calls on a high-water-mark
check, removing two of the four reallocations on a count change. The AS
storage + scratch rebuild below is unchanged: it is tied to the AS
itself and dominates this path regardless.

Adds tests/TLASHighWaterMark driving the real hardware AS-build path: it
asserts a shrink (and within-capacity growth) reuses the buffers while a
growth past the high-water reallocates, that builtInstanceCount still
tracks the live count, and that feeding an oversized instance buffer to
the build produces zero Vulkan validation errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-06-16 18:24:09 +00:00
commit 8e6ba49743
3 changed files with 339 additions and 4 deletions

View file

@ -328,6 +328,37 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
bc.GetInterfacesAndImplementations(ifaces, blasImpls);
cfg.tests.push_back(std::move(blasTest));
// Issue #64: TLAS host-input buffers (instanceBuffer / metadataBuffer)
// grow on a high-water mark instead of reallocating to the exact count
// every topology change. Drives the real hardware AS-build path — a
// cube BLAS plus RenderingElement3D::BuildTLAS at a sequence of
// instance counts — and asserts that a shrink (and any growth within
// the high-water capacity) reuses the existing allocation while a
// growth past it reallocates, with the validation layer reporting no
// errors when an oversized instance buffer is fed to the build. Needs a
// Vulkan RT device at runtime, so it shares the native build settings.
Test tlasTest;
Configuration& tlc = tlasTest.config;
tlc.path = cfg.path;
tlc.name = "TLASHighWaterMark";
tlc.outputName = "TLASHighWaterMark";
tlc.type = ConfigurationType::Executable;
tlc.target = cfg.target;
tlc.march = cfg.march;
tlc.mtune = cfg.mtune;
tlc.debug = cfg.debug;
tlc.sysroot = cfg.sysroot;
tlc.dependencies = cfg.dependencies;
tlc.externalDependencies = cfg.externalDependencies;
tlc.compileFlags = cfg.compileFlags;
tlc.linkFlags = cfg.linkFlags;
tlc.defines = cfg.defines;
tlc.cFiles = cfg.cFiles;
std::vector<fs::path> tlasImpls(impls.begin(), impls.end());
tlasImpls.emplace_back("tests/TLASHighWaterMark/main");
tlc.GetInterfacesAndImplementations(ifaces, tlasImpls);
cfg.tests.push_back(std::move(tlasTest));
// 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