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
|
|
@ -34,7 +34,8 @@ export namespace Crafter {
|
|||
// (UPDATE mode) which is dramatically cheaper than a full rebuild
|
||||
// — refit walks the existing BVH and updates AABBs, while rebuild
|
||||
// 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, since a refit must keep the primitive count of
|
||||
// the build it updates.
|
||||
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
|
||||
|
|
@ -53,6 +54,19 @@ export namespace Crafter {
|
|||
// each slot (and may have reallocated the buffers). See
|
||||
// RenderingElement3D::hostDataVersion.
|
||||
std::vector<std::uint64_t> uploadedVersion;
|
||||
|
||||
TlasWithBuffer() = default;
|
||||
// Moving hands the TLAS over: the moved-from TlasWithBuffer keeps no
|
||||
// acceleration structure, so only the last owner releases it, and its
|
||||
// next BuildTLAS builds a new one. Assigning queues the acceleration
|
||||
// structure this one held, as the destructor would; assign an empty
|
||||
// TlasWithBuffer to reset one.
|
||||
TlasWithBuffer(TlasWithBuffer&& other);
|
||||
TlasWithBuffer& operator=(TlasWithBuffer&& other);
|
||||
// Queues the acceleration structure for deferred deletion, ahead of
|
||||
// the storage it lives in (the buffers queue themselves): a command
|
||||
// buffer still being recorded or a frame in flight may still use it.
|
||||
~TlasWithBuffer();
|
||||
};
|
||||
|
||||
class RenderingElement3D {
|
||||
|
|
@ -133,6 +147,21 @@ export namespace Crafter {
|
|||
// 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.
|
||||
//
|
||||
// The build writes tlases[index] in place: its instance and metadata
|
||||
// buffers on the host as it records, its acceleration structure and
|
||||
// scratch on the GPU. So call it only once every earlier submission
|
||||
// that used tlases[index] has completed. Window::Render runs onUpdate
|
||||
// after waiting for the fence of the image it renders, so building
|
||||
// tlases[currentBuffer] there is safe. Earlier builds, traces and
|
||||
// compute ray queries in the same command buffer are ordered before
|
||||
// the build by the build itself.
|
||||
//
|
||||
// A rebuild for a new instance count or preference builds into the
|
||||
// same acceleration structure while it fits its storage, so
|
||||
// tlases[index].address stays the same. A TLAS that outgrows its
|
||||
// storage moves: read the address again after a build that grows the
|
||||
// scene.
|
||||
static void BuildTLAS(VkCommandBuffer cmd, std::uint32_t index, RTBuildPreference preference = RTBuildPreference::FastTrace);
|
||||
|
||||
// Register / unregister with `elements`. Use these instead of touching
|
||||
|
|
@ -142,18 +171,42 @@ export namespace Crafter {
|
|||
static void Remove(RenderingElement3D* e);
|
||||
};
|
||||
|
||||
// The dependency BuildTLAS records after its build, ordering it before
|
||||
// the shaders that read the TLAS later in the same command buffer:
|
||||
// traceRayEXT in a ray-tracing pass, rayQueryEXT in a compute pass. It
|
||||
// has to carry the acceleration-structure memory dependency; ordering
|
||||
// execution alone never makes the build's writes visible to those reads.
|
||||
struct TlasReadBarrier {
|
||||
// A dependency BuildTLAS records around its build.
|
||||
struct TlasBarrier {
|
||||
VkPipelineStageFlags srcStage;
|
||||
VkPipelineStageFlags dstStage;
|
||||
VkMemoryBarrier memory;
|
||||
};
|
||||
|
||||
inline TlasReadBarrier BuildTlasReadBarrier() {
|
||||
// The dependency BuildTLAS records before its build, ordering the build
|
||||
// after the earlier work in the same command buffer that it must follow:
|
||||
// BLAS builds whose results it reads, an earlier build of this TLAS,
|
||||
// whose storage and scratch it writes again, and traces and compute ray
|
||||
// queries of the TLAS, which a refit or a rebuild in place would
|
||||
// otherwise overwrite while they read it.
|
||||
inline TlasBarrier BuildTlasWriteBarrier() {
|
||||
return {
|
||||
.srcStage = VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
.dstStage = VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
.memory = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
// Writes only: the reads need just the execution dependency
|
||||
// the stages give them. A read bit here does nothing, except
|
||||
// that synchronization validation (layers 1.4.363) then
|
||||
// reports the second of two BuildTLAS calls in one command
|
||||
// buffer as a write-after-write hazard.
|
||||
.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
|
||||
.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// The dependency BuildTLAS records after its build, ordering it before
|
||||
// the shaders that read the TLAS later in the same command buffer:
|
||||
// traceRayEXT in a ray-tracing pass, rayQueryEXT in a compute pass. It
|
||||
// has to carry the acceleration-structure memory dependency; ordering
|
||||
// execution alone never makes the build's writes visible to those reads.
|
||||
inline TlasBarrier BuildTlasReadBarrier() {
|
||||
return {
|
||||
.srcStage = VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
.dstStage = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
|
|
|
|||
Loading…
Reference in a new issue