From 00d14d67cbfd58539734c80dfe01b2c49a351014 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 16 Jun 2026 15:49:17 +0200 Subject: [PATCH] tlas build options --- ...ter.Graphics-RenderingElement3D-WebGPU.cpp | 4 ++- .../Crafter.Graphics-RenderingElement3D.cpp | 35 +++++++++++++------ .../Crafter.Graphics-RenderingElement3D.cppm | 24 +++++++++++-- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/implementations/Crafter.Graphics-RenderingElement3D-WebGPU.cpp b/implementations/Crafter.Graphics-RenderingElement3D-WebGPU.cpp index ffa1b47..326f049 100644 --- a/implementations/Crafter.Graphics-RenderingElement3D-WebGPU.cpp +++ b/implementations/Crafter.Graphics-RenderingElement3D-WebGPU.cpp @@ -188,7 +188,9 @@ void RenderingElement3D::BuildTLASBuild(WebGPUCommandEncoderRef /*cmd*/, std::ui tlas.builtInstanceCount = primitiveCount; } -void RenderingElement3D::BuildTLAS(WebGPUCommandEncoderRef cmd, std::uint32_t index) { +void RenderingElement3D::BuildTLAS(WebGPUCommandEncoderRef cmd, std::uint32_t index, RTBuildPreference) { + // `preference` is ignored — the software LBVH TLAS build has no + // FAST_TRACE/FAST_BUILD equivalent. See the interface comment. BuildTLASUpload(cmd, index); BuildTLASBuild(cmd, index); } diff --git a/implementations/Crafter.Graphics-RenderingElement3D.cpp b/implementations/Crafter.Graphics-RenderingElement3D.cpp index c9208f6..b43f43e 100644 --- a/implementations/Crafter.Graphics-RenderingElement3D.cpp +++ b/implementations/Crafter.Graphics-RenderingElement3D.cpp @@ -47,18 +47,33 @@ void RenderingElement3D::Remove(RenderingElement3D* e) { e->indexInElements = std::numeric_limits::max(); } -void RenderingElement3D::BuildTLAS(VkCommandBuffer cmd, std::uint32_t index) { +void RenderingElement3D::BuildTLAS(VkCommandBuffer cmd, std::uint32_t index, RTBuildPreference preference) { auto& tlas = tlases[index]; const std::uint32_t primitiveCount = static_cast(elements.size()); + // ALLOW_UPDATE is always set — BuildTLAS refits in place on later + // frames. The FAST_TRACE/FAST_BUILD preference is layered on top; the + // two preference bits are mutually exclusive, so exactly one is chosen. + // Both bits are baked into the AS at BUILD time and must be identical on + // every subsequent UPDATE, so a change in `preference` is treated as a + // topology change below to force a fresh rebuild with the new flags. + const VkBuildAccelerationStructureFlagsKHR buildFlags = + VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR + | (preference == RTBuildPreference::FastBuild + ? VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR + : VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); + // Refit (UPDATE) is allowed when the count matches the count this AS - // was last built for. A change forces a full rebuild because the AS - // storage and instance buffer were sized for the old count. Refit is - // dramatically cheaper at scale (millions of instances) — it walks the - // existing BVH and updates AABBs rather than reconstructing topology. + // was last built for and the build flags are unchanged. A change forces + // a full rebuild because the AS storage and instance buffer were sized + // for the old count (and a refit must inherit the original build flags). + // Refit is dramatically cheaper at scale (millions of instances) — it + // walks the existing BVH and updates AABBs rather than reconstructing + // topology. const bool topologyChanged = tlas.accelerationStructure == VK_NULL_HANDLE - || primitiveCount != tlas.builtInstanceCount; + || primitiveCount != tlas.builtInstanceCount + || buildFlags != tlas.builtFlags; { VkMemoryBarrier asBarrier { @@ -118,10 +133,9 @@ void RenderingElement3D::BuildTLAS(VkCommandBuffer cmd, std::uint32_t index) { VkAccelerationStructureBuildGeometryInfoKHR tlasBuildGeometryInfo { .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR, .type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, - // ALLOW_UPDATE is required for any subsequent UPDATE-mode (refit) - // build. Set it on every build so the AS we keep around can be - // refit on later frames. - .flags = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR, + // ALLOW_UPDATE (required for any subsequent UPDATE-mode refit) plus + // the caller's FAST_TRACE/FAST_BUILD preference — see buildFlags above. + .flags = buildFlags, .mode = topologyChanged ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR : VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR, @@ -170,6 +184,7 @@ void RenderingElement3D::BuildTLAS(VkCommandBuffer cmd, std::uint32_t index) { tlas.address = Device::vkGetAccelerationStructureDeviceAddressKHR(Device::device, &addrInfo); tlas.builtInstanceCount = primitiveCount; + tlas.builtFlags = buildFlags; } // For UPDATE mode, src == dst (in-place refit). For BUILD, src is diff --git a/interfaces/Crafter.Graphics-RenderingElement3D.cppm b/interfaces/Crafter.Graphics-RenderingElement3D.cppm index 6dcdd92..cf88d24 100644 --- a/interfaces/Crafter.Graphics-RenderingElement3D.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement3D.cppm @@ -52,6 +52,12 @@ export namespace Crafter { // reconstructs the topology from scratch. A change in count forces // a fresh rebuild because the AS is sized for that primitive count. std::uint32_t builtInstanceCount = 0; + // Build flags the current AS was created with — ALLOW_UPDATE (always + // set, since BuildTLAS refits in place) plus the chosen + // FAST_TRACE/FAST_BUILD preference bit. A refit (UPDATE mode) must + // use flags identical to the originating build, so a change in the + // requested preference forces a full rebuild rather than a refit. + VkBuildAccelerationStructureFlagsKHR builtFlags = 0; }; class RenderingElement3D { @@ -78,7 +84,17 @@ export namespace Crafter { static std::vector elements; inline static TlasWithBuffer tlases[Window::numFrames]; - static void BuildTLAS(VkCommandBuffer cmd, std::uint32_t index); + // Build (or in-place refit) the TLAS for frame `index`. `preference` + // maps onto the FAST_TRACE/FAST_BUILD bits exactly as Mesh::Build + // does for a BLAS: FastTrace (the default) spends more build time for + // faster traversal — right for a TLAS rebuilt at most once per frame + // and traced many times; FastBuild trades traversal speed for cheaper + // builds, for scenes whose instance set churns hard every frame. + // ALLOW_UPDATE is always set (refit is intrinsic to this path), so it + // is not exposed as an option. Changing `preference` between frames + // forces a full rebuild on the next call, since a refit must inherit + // the original build's flags. + static void BuildTLAS(VkCommandBuffer cmd, std::uint32_t index, RTBuildPreference preference = RTBuildPreference::FastTrace); // Register / unregister with `elements`. Use these instead of touching // the vector directly: linear find+erase is O(n) and pathological at @@ -183,7 +199,11 @@ export namespace Crafter { // interleave a compute pass (e.g. the ctor-time first build). static void BuildTLASUpload(WebGPUCommandEncoderRef cmd, std::uint32_t index); static void BuildTLASBuild(WebGPUCommandEncoderRef cmd, std::uint32_t index); - static void BuildTLAS(WebGPUCommandEncoderRef cmd, std::uint32_t index); + // `preference` mirrors the Vulkan signature for portability but has + // no effect here: the software LBVH-for-TLAS path has no hardware + // FAST_TRACE/FAST_BUILD knob (same as Mesh::Build on WebGPU). The + // parameter exists purely so portable code compiles unchanged. + static void BuildTLAS(WebGPUCommandEncoderRef cmd, std::uint32_t index, RTBuildPreference preference = RTBuildPreference::FastTrace); static void Add(RenderingElement3D* e); static void Remove(RenderingElement3D* e);