fix(rt): never destroy the static TLASes
RenderingElement3D::tlases was a plain static array, so its buffers were freed by its static destructor at process exit. The Vulkan driver and layers are loaded during Device::Initialize, after the array was constructed, so their own statics are torn down first, and destroying the buffers then called into freed driver and layer state: every ray-traced app run with CRAFTER_GRAPHICS_VALIDATION segfaulted on exit inside the validation layer. TLASHighWaterMark and TLASInstanceDirtyTracking worked around it by tearing frame 0's TLAS down by hand before returning. tlases now refers to an array that is never destroyed, the choice Device already makes for the device itself, and exit reclaims the memory. Indexing it is unchanged for callers. The hand teardown in the two tests goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5d25c1f825
commit
70d9c2c82a
3 changed files with 8 additions and 31 deletions
|
|
@ -115,7 +115,14 @@ export namespace Crafter {
|
|||
inline static std::uint64_t hostDataVersionCounter = 0;
|
||||
|
||||
static std::vector<RenderingElement3D*> elements;
|
||||
inline static TlasWithBuffer tlases[Window::numFrames];
|
||||
// Never destroyed, like Device::device. As a plain static its
|
||||
// destructor would run at process exit after the Vulkan driver and
|
||||
// layers, which are loaded during Device::Initialize (after this was
|
||||
// constructed), have torn down their own state, and destroying the
|
||||
// buffers would call into that freed state: the validation layer
|
||||
// segfaults on it. Exit reclaims them instead.
|
||||
inline static std::array<TlasWithBuffer, Window::numFrames>& tlases =
|
||||
*new std::array<TlasWithBuffer, Window::numFrames>();
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -351,22 +351,6 @@ int main() {
|
|||
Check(Device::validationErrorCount == 0,
|
||||
std::format("no Vulkan validation errors ({} seen)", Device::validationErrorCount));
|
||||
|
||||
// Tear down the frame-0 TLAS while the Vulkan device is still alive. The
|
||||
// static tlases[] array is destroyed at process exit, by which point the
|
||||
// device (also static) may already be gone — so its VulkanBuffer
|
||||
// destructors would fault. Releasing here keeps the exit path clean.
|
||||
{
|
||||
auto& t0 = RenderingElement3D::tlases[0];
|
||||
if (t0.accelerationStructure != VK_NULL_HANDLE) {
|
||||
Device::vkDestroyAccelerationStructureKHR(Device::device, t0.accelerationStructure, nullptr);
|
||||
t0.accelerationStructure = VK_NULL_HANDLE;
|
||||
}
|
||||
if (t0.instanceBuffer.buffer != VK_NULL_HANDLE) t0.instanceBuffer.Clear();
|
||||
if (t0.metadataBuffer.buffer != VK_NULL_HANDLE) t0.metadataBuffer.Clear();
|
||||
if (t0.scratchBuffer.buffer != VK_NULL_HANDLE) t0.scratchBuffer.Clear();
|
||||
if (t0.buffer.buffer != VK_NULL_HANDLE) t0.buffer.Clear();
|
||||
}
|
||||
|
||||
if (failures != 0) {
|
||||
std::println("{} check(s) failed", failures);
|
||||
return EXIT_FAILURE;
|
||||
|
|
|
|||
|
|
@ -213,20 +213,6 @@ int main() {
|
|||
Check(Device::validationErrorCount == 0,
|
||||
std::format("no Vulkan validation errors ({} seen)", Device::validationErrorCount));
|
||||
|
||||
// Tear down the frame-0 TLAS while the device is still alive (same rationale
|
||||
// as TLASHighWaterMark — the static tlases[] outlive the static device).
|
||||
{
|
||||
auto& t0 = RenderingElement3D::tlases[0];
|
||||
if (t0.accelerationStructure != VK_NULL_HANDLE) {
|
||||
Device::vkDestroyAccelerationStructureKHR(Device::device, t0.accelerationStructure, nullptr);
|
||||
t0.accelerationStructure = VK_NULL_HANDLE;
|
||||
}
|
||||
if (t0.instanceBuffer.buffer != VK_NULL_HANDLE) t0.instanceBuffer.Clear();
|
||||
if (t0.metadataBuffer.buffer != VK_NULL_HANDLE) t0.metadataBuffer.Clear();
|
||||
if (t0.scratchBuffer.buffer != VK_NULL_HANDLE) t0.scratchBuffer.Clear();
|
||||
if (t0.buffer.buffer != VK_NULL_HANDLE) t0.buffer.Clear();
|
||||
}
|
||||
|
||||
if (failures != 0) {
|
||||
std::println("{} check(s) failed", failures);
|
||||
return EXIT_FAILURE;
|
||||
|
|
|
|||
Loading…
Reference in a new issue