fix(rt): rebuild TLASes in place, after earlier traces, and queue their handles like a Mesh's
BuildTLAS - A rebuild for a new instance count or build preference used to create a new acceleration structure and destroy the old one at once, though the command buffer being recorded could still use it (a scene and then a model built in one setup). The acceleration structure now spans its whole storage buffer, and a rebuild that fits builds into it again, so the handle and tlases[i].address stay the same and nothing is destroyed. A TLAS that outgrows its storage gets new storage and a new handle; the old handle goes to the deletion queue ahead of the storage it lives in. It is queued after the scratch is resized, and its address goes with it, so a Resize or create that throws leaves no address of a queued acceleration structure behind. - The barrier before the build also orders earlier traces and compute ray queries of the TLAS, which a refit or a rebuild in place would otherwise overwrite while they read it (BuildTlasWriteBarrier). Its source access mask holds no read bit: one does nothing, but makes synchronization validation 1.4.363 report the second of two builds in one command buffer as a write-after-write hazard. - TlasReadBarrier is now TlasBarrier, shared by both barriers. - Documented: call BuildTLAS once every earlier submission that used tlases[index] has completed, and when the address stays the same. TlasWithBuffer - Moves hand the acceleration structure over, and destroying or assigning over one queues it ahead of its storage. The implicit members dropped the handle over freed storage, or left two owners and a moved-from TLAS that refit through buffers it no longer had. Tests - TLASLifetime: destroying, moving, move-assigning and resetting a TlasWithBuffer. - TLASHighWaterMark skips without ray tracing. It asserts the acceleration structure is rebuilt in place on a shrink or a growth within capacity, that the old handle is queued before its storage on a growth past it, and that builds in one command buffer keep the handle while they fit, queue it once when one outgrows the storage, and free it only on its retire frame. - TLASBuildBarrier checks the barrier before the build too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
079b6b0e1f
commit
397ff2db51
6 changed files with 516 additions and 74 deletions
|
|
@ -1,15 +1,23 @@
|
|||
//SPDX-License-Identifier: LGPL-3.0-only
|
||||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// The dependency RenderingElement3D::BuildTLAS records after its build, which
|
||||
// orders the build before the shaders that read the TLAS: traceRayEXT in a
|
||||
// ray-tracing pass, rayQueryEXT in a compute pass, recorded after it in the
|
||||
// same command buffer (BuildTLAS in onUpdate, then RTPass, is the documented
|
||||
// per-frame flow). It used to be an execution-only barrier with no memory
|
||||
// barrier, which orders the commands but never makes the build's
|
||||
// acceleration-structure writes available and visible to those reads. The
|
||||
// dependency is built by BuildTlasReadBarrier, pure CPU logic that this test
|
||||
// drives directly with no GPU device, like SwapchainBarrierScope.
|
||||
// The dependencies RenderingElement3D::BuildTLAS records around its build.
|
||||
//
|
||||
// After it, the build is ordered before the shaders that read the TLAS:
|
||||
// traceRayEXT in a ray-tracing pass, rayQueryEXT in a compute pass, recorded
|
||||
// after it in the same command buffer (BuildTLAS in onUpdate, then RTPass, is
|
||||
// the documented per-frame flow). It used to be an execution-only barrier with
|
||||
// no memory barrier, which orders the commands but never makes the build's
|
||||
// acceleration-structure writes available and visible to those reads.
|
||||
//
|
||||
// Before it, the build is ordered after the earlier work it must follow. That
|
||||
// used to cover only earlier acceleration-structure builds, so a refit, or a
|
||||
// rebuild into the same storage, recorded after a trace or a compute ray query
|
||||
// of the TLAS could overwrite it while they still read it.
|
||||
//
|
||||
// The dependencies are built by BuildTlasWriteBarrier and BuildTlasReadBarrier,
|
||||
// pure CPU logic that this test drives directly with no GPU device, like
|
||||
// SwapchainBarrierScope.
|
||||
|
||||
#include <cstdlib>
|
||||
#include "vulkan/vulkan.h"
|
||||
|
|
@ -31,19 +39,39 @@ void Check(bool ok, std::string_view what) {
|
|||
} // namespace
|
||||
|
||||
int main() {
|
||||
const TlasReadBarrier barrier = BuildTlasReadBarrier();
|
||||
const TlasBarrier before = BuildTlasWriteBarrier();
|
||||
|
||||
Check(barrier.srcStage == VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
"the dependency starts at the acceleration-structure build");
|
||||
Check((barrier.dstStage & VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR) != 0,
|
||||
Check(before.dstStage == VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
"the dependency before the build ends at the acceleration-structure build");
|
||||
Check((before.srcStage & VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR) != 0,
|
||||
"it follows earlier builds (the BLASes it reads, an earlier build of this TLAS)");
|
||||
Check((before.srcStage & VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR) != 0,
|
||||
"and earlier ray-tracing shaders (traceRayEXT reading the TLAS)");
|
||||
Check((before.srcStage & VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT) != 0,
|
||||
"and earlier compute shaders (rayQueryEXT reading the TLAS)");
|
||||
|
||||
Check(before.memory.sType == VK_STRUCTURE_TYPE_MEMORY_BARRIER, "it carries a memory barrier");
|
||||
Check(before.memory.srcAccessMask == VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
|
||||
"earlier builds' acceleration-structure writes are made available, and no reads "
|
||||
"(a read bit does nothing but trip synchronization validation)");
|
||||
constexpr VkAccessFlags kBuildAccess =
|
||||
VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR;
|
||||
Check((before.memory.dstAccessMask & kBuildAccess) == kBuildAccess,
|
||||
"and visible to the build's reads and writes");
|
||||
|
||||
const TlasBarrier after = BuildTlasReadBarrier();
|
||||
|
||||
Check(after.srcStage == VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
"the dependency after the build starts at the acceleration-structure build");
|
||||
Check((after.dstStage & VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR) != 0,
|
||||
"it reaches ray-tracing shaders (traceRayEXT)");
|
||||
Check((barrier.dstStage & VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT) != 0,
|
||||
Check((after.dstStage & VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT) != 0,
|
||||
"it reaches compute shaders (rayQueryEXT)");
|
||||
|
||||
Check(barrier.memory.sType == VK_STRUCTURE_TYPE_MEMORY_BARRIER, "it carries a memory barrier");
|
||||
Check((barrier.memory.srcAccessMask & VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR) != 0,
|
||||
Check(after.memory.sType == VK_STRUCTURE_TYPE_MEMORY_BARRIER, "it carries a memory barrier");
|
||||
Check((after.memory.srcAccessMask & VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR) != 0,
|
||||
"the build's acceleration-structure writes are made available");
|
||||
Check((barrier.memory.dstAccessMask & VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR) != 0,
|
||||
Check((after.memory.dstAccessMask & VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR) != 0,
|
||||
"and visible to acceleration-structure reads");
|
||||
|
||||
std::println("{}", failures == 0 ? "all checks passed" : "check(s) failed");
|
||||
|
|
|
|||
Loading…
Reference in a new issue