diff --git a/code/graphics/vulkan/VulkanRaytracing.cpp b/code/graphics/vulkan/VulkanRaytracing.cpp index 6ad8cf5e0a..3826fe758d 100644 --- a/code/graphics/vulkan/VulkanRaytracing.cpp +++ b/code/graphics/vulkan/VulkanRaytracing.cpp @@ -1,5 +1,7 @@ #include "VulkanRaytracing.h" +#include "VulkanDeletionQueue.h" + namespace graphics::vulkan { namespace { @@ -222,17 +224,36 @@ uint64_t VulkanRaytracingManager::makeKey(int pm_id, int submodel_num) void VulkanRaytracingManager::destroyBlasEntry(BlasEntry& entry) { - entry.accelStruct.reset(); + // TLASes in frames still in flight hold this BLAS's device address, so defer like ensureTlasCapacity does + if (entry.accelStruct) { + getDeletionQueue()->queueAccelerationStructure(entry.accelStruct.release()); + } if (entry.buffer) { - m_device.destroyBuffer(entry.buffer); + getDeletionQueue()->queueBuffer(entry.buffer, entry.allocation); entry.buffer = nullptr; - } - if (entry.allocation.isValid()) { + entry.allocation = VulkanAllocation(); + } else if (entry.allocation.isValid()) { m_memoryManager->freeAllocation(entry.allocation); } } +void VulkanRaytracingManager::retireBuiltTlases() +{ + // a built TLAS may still point at a retired BLAS, and screens that never rebuild the TLAS would keep binding it + for (auto& frame : m_frameTlas) { + if (frame.tlas) { + getDeletionQueue()->queueAccelerationStructure(frame.tlas.release()); + } + if (frame.tlasBuffer) { + getDeletionQueue()->queueBuffer(frame.tlasBuffer, frame.tlasAllocation); + frame.tlasBuffer = nullptr; + frame.tlasAllocation = VulkanAllocation(); + } + frame.tlasCapacity = 0; + } +} + vk::CommandBuffer VulkanRaytracingManager::beginSingleTimeCommands() { vk::CommandBufferAllocateInfo allocInfo; diff --git a/code/graphics/vulkan/VulkanRaytracing.h b/code/graphics/vulkan/VulkanRaytracing.h index 970a108ee8..c1f97fac32 100644 --- a/code/graphics/vulkan/VulkanRaytracing.h +++ b/code/graphics/vulkan/VulkanRaytracing.h @@ -93,8 +93,9 @@ public: /** * @brief Purge all cached BLASes for a model and free their GPU resources. * Called from model_unload()/model_free_all() before the model is destroyed. + * @return true if any BLAS was retired (the shadow TLAS binding must then be refreshed) */ - void onModelUnloaded(int pm_id); + bool onModelUnloaded(int pm_id); /** * @brief Rebuild the top-level acceleration structure for this frame from @@ -172,6 +173,7 @@ private: const BlasEntry* getOrBuildBlasEntry(int pm_id, int submodel_num); void destroyBlasEntry(BlasEntry& entry); + void retireBuiltTlases(); bool createRawBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, diff --git a/code/graphics/vulkan/VulkanRaytracingBlas.cpp b/code/graphics/vulkan/VulkanRaytracingBlas.cpp index b44d9741ed..b0a8b99fb8 100644 --- a/code/graphics/vulkan/VulkanRaytracingBlas.cpp +++ b/code/graphics/vulkan/VulkanRaytracingBlas.cpp @@ -182,6 +182,7 @@ const VulkanRaytracingManager::BlasEntry* VulkanRaytracingManager::getOrBuildBla // address) -- destroy and fall through to rebuild. destroyBlasEntry(it->second); m_blasCache.erase(it); + retireBuiltTlases(); } BlasEntry entry; @@ -236,20 +237,27 @@ void VulkanRaytracingManager::onModelLoaded(int pm_id) } } -void VulkanRaytracingManager::onModelUnloaded(int pm_id) +bool VulkanRaytracingManager::onModelUnloaded(int pm_id) { if (!m_enabled) { - return; + return false; } + bool retired = false; for (auto it = m_blasCache.begin(); it != m_blasCache.end();) { if (static_cast(it->first >> 32) == pm_id) { destroyBlasEntry(it->second); it = m_blasCache.erase(it); + retired = true; } else { ++it; } } + + if (retired) { + retireBuiltTlases(); + } + return retired; } } // namespace graphics::vulkan diff --git a/code/graphics/vulkan/gr_vulkan.cpp b/code/graphics/vulkan/gr_vulkan.cpp index 36bedcb4a0..e046dcd466 100644 --- a/code/graphics/vulkan/gr_vulkan.cpp +++ b/code/graphics/vulkan/gr_vulkan.cpp @@ -68,7 +68,10 @@ void vulkan_model_loaded(int pm_id) void vulkan_model_unloaded(int pm_id) { if (auto* rt = getRaytracingManager()) { - rt->onModelUnloaded(pm_id); + if (rt->onModelUnloaded(pm_id)) { + getDescriptorManager()->setCurrentShadowTlas(rt->getTlasForShaderBinding()); + getDrawManager()->invalidateGlobalSet(); + } } }