tlas build options

This commit is contained in:
Jorijn van der Graaf 2026-06-16 15:49:17 +02:00
commit 00d14d67cb
3 changed files with 50 additions and 13 deletions

View file

@ -188,7 +188,9 @@ void RenderingElement3D::BuildTLASBuild(WebGPUCommandEncoderRef /*cmd*/, std::ui
tlas.builtInstanceCount = primitiveCount; 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); BuildTLASUpload(cmd, index);
BuildTLASBuild(cmd, index); BuildTLASBuild(cmd, index);
} }

View file

@ -47,18 +47,33 @@ void RenderingElement3D::Remove(RenderingElement3D* e) {
e->indexInElements = std::numeric_limits<std::uint32_t>::max(); e->indexInElements = std::numeric_limits<std::uint32_t>::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]; auto& tlas = tlases[index];
const std::uint32_t primitiveCount = static_cast<std::uint32_t>(elements.size()); const std::uint32_t primitiveCount = static_cast<std::uint32_t>(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 // 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 // was last built for and the build flags are unchanged. A change forces
// storage and instance buffer were sized for the old count. Refit is // a full rebuild because the AS storage and instance buffer were sized
// dramatically cheaper at scale (millions of instances) — it walks the // for the old count (and a refit must inherit the original build flags).
// existing BVH and updates AABBs rather than reconstructing topology. // Refit is dramatically cheaper at scale (millions of instances) — it
// walks the existing BVH and updates AABBs rather than reconstructing
// topology.
const bool topologyChanged = const bool topologyChanged =
tlas.accelerationStructure == VK_NULL_HANDLE tlas.accelerationStructure == VK_NULL_HANDLE
|| primitiveCount != tlas.builtInstanceCount; || primitiveCount != tlas.builtInstanceCount
|| buildFlags != tlas.builtFlags;
{ {
VkMemoryBarrier asBarrier { VkMemoryBarrier asBarrier {
@ -118,10 +133,9 @@ void RenderingElement3D::BuildTLAS(VkCommandBuffer cmd, std::uint32_t index) {
VkAccelerationStructureBuildGeometryInfoKHR tlasBuildGeometryInfo { VkAccelerationStructureBuildGeometryInfoKHR tlasBuildGeometryInfo {
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR, .sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, .type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR,
// ALLOW_UPDATE is required for any subsequent UPDATE-mode (refit) // ALLOW_UPDATE (required for any subsequent UPDATE-mode refit) plus
// build. Set it on every build so the AS we keep around can be // the caller's FAST_TRACE/FAST_BUILD preference — see buildFlags above.
// refit on later frames. .flags = buildFlags,
.flags = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR,
.mode = topologyChanged .mode = topologyChanged
? VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR
: VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_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.address = Device::vkGetAccelerationStructureDeviceAddressKHR(Device::device, &addrInfo);
tlas.builtInstanceCount = primitiveCount; tlas.builtInstanceCount = primitiveCount;
tlas.builtFlags = buildFlags;
} }
// For UPDATE mode, src == dst (in-place refit). For BUILD, src is // For UPDATE mode, src == dst (in-place refit). For BUILD, src is

View file

@ -52,6 +52,12 @@ export namespace Crafter {
// reconstructs the topology from scratch. A change in count forces // reconstructs the topology from scratch. A change in count forces
// a fresh rebuild because the AS is sized for that primitive count. // a fresh rebuild because the AS is sized for that primitive count.
std::uint32_t builtInstanceCount = 0; 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 { class RenderingElement3D {
@ -78,7 +84,17 @@ export namespace Crafter {
static std::vector<RenderingElement3D*> elements; static std::vector<RenderingElement3D*> elements;
inline static TlasWithBuffer tlases[Window::numFrames]; 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 // Register / unregister with `elements`. Use these instead of touching
// the vector directly: linear find+erase is O(n) and pathological at // 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). // interleave a compute pass (e.g. the ctor-time first build).
static void BuildTLASUpload(WebGPUCommandEncoderRef cmd, std::uint32_t index); static void BuildTLASUpload(WebGPUCommandEncoderRef cmd, std::uint32_t index);
static void BuildTLASBuild(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 Add(RenderingElement3D* e);
static void Remove(RenderingElement3D* e); static void Remove(RenderingElement3D* e);