perf(rt): high-water-mark growth for TLAS host-input buffers (#64)
BuildTLAS reallocated the host-visible instanceBuffer and metadataBuffer on every topology change. They only ever need to hold at least primitiveCount entries (the AS build reads exactly primitiveCount via tlasRangeInfo, and the copy loop writes [0, primitiveCount)), so a shrink — or any growth that still fits the previous capacity — can reuse the existing allocation. Gate the two Resize calls on a high-water-mark check, removing two of the four reallocations on a count change. The AS storage + scratch rebuild below is unchanged: it is tied to the AS itself and dominates this path regardless. Adds tests/TLASHighWaterMark driving the real hardware AS-build path: it asserts a shrink (and within-capacity growth) reuses the buffers while a growth past the high-water reallocates, that builtInstanceCount still tracks the live count, and that feeding an oversized instance buffer to the build produces zero Vulkan validation errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ab222ffa1f
commit
8e6ba49743
3 changed files with 339 additions and 4 deletions
|
|
@ -88,12 +88,29 @@ void RenderingElement3D::BuildTLAS(VkCommandBuffer cmd, std::uint32_t index, RTB
|
|||
}
|
||||
|
||||
if (topologyChanged) {
|
||||
// Resize the host-visible inputs to match the new count.
|
||||
// Grow the host-visible inputs on a high-water mark rather than
|
||||
// resizing to the exact count every topology change. These buffers
|
||||
// only need to hold *at least* primitiveCount entries — the AS build
|
||||
// reads exactly primitiveCount of them via tlasRangeInfo, and the
|
||||
// copy loop below writes [0, primitiveCount) — so an allocation left
|
||||
// over from a larger earlier frame is reused as-is. This drops the
|
||||
// two host-buffer reallocations on a count decrease (and on an
|
||||
// increase that still fits the previous high-water capacity),
|
||||
// leaving only the AS storage + scratch rebuild below, which is tied
|
||||
// to the AS itself and dominates this path regardless.
|
||||
//
|
||||
// instanceBuffer and metadataBuffer grow in lockstep (always resized
|
||||
// together to the same entry count), so one capacity check covers
|
||||
// both. size is only meaningful once buffer is non-null, so the
|
||||
// null check must short-circuit before the division.
|
||||
// STORAGE_BUFFER_BIT is required because the application's compute
|
||||
// shaders bind this buffer as a storage SSBO (e.g. to write
|
||||
// shaders bind these buffers as storage SSBOs (e.g. to write
|
||||
// per-instance transforms directly into the TLAS instance data).
|
||||
tlas.instanceBuffer.Resize(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, primitiveCount);
|
||||
tlas.metadataBuffer.Resize(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, primitiveCount);
|
||||
if (tlas.instanceBuffer.buffer == VK_NULL_HANDLE
|
||||
|| primitiveCount > tlas.instanceBuffer.size / sizeof(VkAccelerationStructureInstanceKHR)) {
|
||||
tlas.instanceBuffer.Resize(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, primitiveCount);
|
||||
tlas.metadataBuffer.Resize(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, primitiveCount);
|
||||
}
|
||||
}
|
||||
|
||||
for(std::uint32_t i = 0; i < primitiveCount; i++) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue