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

@ -47,18 +47,33 @@ void RenderingElement3D::Remove(RenderingElement3D* e) {
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];
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
// 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