diff --git a/src/core/vk_engine.cpp b/src/core/vk_engine.cpp index 3a901d8..dc19b80 100644 --- a/src/core/vk_engine.cpp +++ b/src/core/vk_engine.cpp @@ -877,6 +877,9 @@ void VulkanEngine::run() // and clear per-frame resources before building UI and recording commands. VK_CHECK(vkWaitForFences(_deviceManager->device(), 1, &get_current_frame()._renderFence, true, 1000000000)); + // Safe to destroy any BLAS queued for deletion now that the previous frame is idle. + if (_rayManager) { _rayManager->flushPendingDeletes(); } + if (_pickResultPending && _pickReadbackBuffer.buffer && _sceneManager) { vmaInvalidateAllocation(_deviceManager->allocator(), _pickReadbackBuffer.allocation, 0, sizeof(uint32_t)); diff --git a/src/core/vk_raytracing.cpp b/src/core/vk_raytracing.cpp index e84416c..c8fcc2a 100644 --- a/src/core/vk_raytracing.cpp +++ b/src/core/vk_raytracing.cpp @@ -32,6 +32,9 @@ void RayTracingManager::init(DeviceManager *dev, ResourceManager *res) void RayTracingManager::cleanup() { VkDevice dv = _device->device(); + // Destroy any deferred BLAS first + flushPendingDeletes(); + if (_tlas.handle) { _vkDestroyAccelerationStructureKHR(dv, _tlas.handle, nullptr); @@ -60,6 +63,25 @@ void RayTracingManager::cleanup() } } _blasByVB.clear(); + _blasByMesh.clear(); +} + +void RayTracingManager::flushPendingDeletes() +{ + if (_pendingBlasDestroy.empty()) return; + VkDevice dv = _device->device(); + for (auto &as : _pendingBlasDestroy) + { + if (as.handle) + { + _vkDestroyAccelerationStructureKHR(dv, as.handle, nullptr); + } + if (as.storage.buffer) + { + _resources->destroy_buffer(as.storage); + } + } + _pendingBlasDestroy.clear(); } static VkDeviceAddress get_buffer_address(VkDevice dev, VkBuffer buf) @@ -77,6 +99,10 @@ AccelStructureHandle RayTracingManager::getOrBuildBLAS(const std::shared_ptrsecond; } + if (auto it = _blasByMesh.find(mesh.get()); it != _blasByMesh.end()) + { + return it->second; + } // Build BLAS with one geometry per surface (skip empty primitives) std::vector geoms; @@ -185,6 +211,7 @@ AccelStructureHandle RayTracingManager::getOrBuildBLAS(const std::shared_ptrdevice(), &dai); _blasByVB.emplace(vb, blas); + _blasByMesh.emplace(mesh.get(), blas); return blas; } @@ -223,16 +250,33 @@ VkAccelerationStructureKHR RayTracingManager::buildTLASFromDrawContext(const Dra for (const auto &r: dc.OpaqueSurfaces) { - // Find mesh BLAS by vertex buffer + // Find mesh BLAS by vertex buffer, then by mesh pointer (if available). AccelStructureHandle blas{}; - // We don't have MeshAsset pointer here; BLAS cache is keyed by VB handle; if missing, skip auto it = _blasByVB.find(r.vertexBuffer); - if (it == _blasByVB.end()) + if (it != _blasByVB.end()) { - // Can't build BLAS on the fly without mesh topology; skip this instance + blas = it->second; + } + else if (r.sourceMesh) + { + auto itMesh = _blasByMesh.find(r.sourceMesh); + if (itMesh != _blasByMesh.end()) + { + blas = itMesh->second; + } + else + { + // Try to build on the fly if the mesh is still alive (non-owning shared_ptr wrapper). + std::shared_ptr nonOwning(const_cast(r.sourceMesh), [](MeshAsset *) {}); + blas = getOrBuildBLAS(nonOwning); + } + } + + if (!blas.handle) + { + // Can't build BLAS; skip this instance continue; } - blas = it->second; VkAccelerationStructureInstanceKHR inst{}; // Fill 3x4 row-major from GLM column-major mat4 @@ -352,13 +396,46 @@ void RayTracingManager::removeBLASForBuffer(VkBuffer vertexBuffer) auto it = _blasByVB.find(vertexBuffer); if (it == _blasByVB.end()) return; - if (it->second.handle) + // Defer destruction until after the next fence wait to avoid racing in-flight traces. + _pendingBlasDestroy.push_back(it->second); + + // Also erase corresponding mesh-keyed entry if present + for (auto mit = _blasByMesh.begin(); mit != _blasByMesh.end(); ) { - _vkDestroyAccelerationStructureKHR(dv, it->second.handle, nullptr); - } - if (it->second.storage.buffer) - { - _resources->destroy_buffer(it->second.storage); + if (mit->second.handle == it->second.handle) + { + mit = _blasByMesh.erase(mit); + } + else + { + ++mit; + } } _blasByVB.erase(it); } + +void RayTracingManager::removeBLASForMesh(const MeshAsset *mesh) +{ + if (!mesh) return; + VkDevice dv = _device->device(); + auto it = _blasByMesh.find(mesh); + if (it == _blasByMesh.end()) return; + + // Defer destruction until after the next fence wait to avoid racing in-flight traces. + _pendingBlasDestroy.push_back(it->second); + + // Remove any VB-keyed entries that point to the same BLAS + for (auto vbit = _blasByVB.begin(); vbit != _blasByVB.end(); ) + { + if (vbit->second.handle == it->second.handle) + { + vbit = _blasByVB.erase(vbit); + } + else + { + ++vbit; + } + } + + _blasByMesh.erase(it); +} diff --git a/src/core/vk_raytracing.h b/src/core/vk_raytracing.h index 74d2ead..776f82f 100644 --- a/src/core/vk_raytracing.h +++ b/src/core/vk_raytracing.h @@ -31,9 +31,14 @@ public: VkAccelerationStructureKHR tlas() const { return _tlas.handle; } VkDeviceAddress tlasAddress() const { return _tlas.deviceAddress; } + // Destroy any BLAS resources queued for deferred deletion. Call after GPU fence wait. + void flushPendingDeletes(); + // Remove and destroy a cached BLAS associated with a vertex buffer. // Safe to call even if no BLAS exists for the buffer. void removeBLASForBuffer(VkBuffer vertexBuffer); + // Remove and destroy a cached BLAS associated with a mesh pointer. + void removeBLASForMesh(const MeshAsset *mesh); private: // function pointers (resolved on init) @@ -45,15 +50,19 @@ private: DeviceManager* _device{nullptr}; ResourceManager* _resources{nullptr}; - - // BLAS cache by vertex buffer handle - std::unordered_map _blasByVB; + + // BLAS cache by vertex buffer handle (legacy) and by mesh pointer (preferred) + std::unordered_map _blasByVB; + std::unordered_map _blasByMesh; // TLAS + scratch / instance buffer (rebuilt per frame) AccelStructureHandle _tlas{}; AllocatedBuffer _tlasInstanceBuffer{}; size_t _tlasInstanceCapacity{0}; + // BLAS scheduled for destruction once GPU is idle + std::vector _pendingBlasDestroy; + // Properties VkDeviceSize _minScratchAlignment{256}; diff --git a/src/render/vk_renderpass_geometry.cpp b/src/render/vk_renderpass_geometry.cpp index d255c30..b4aa85f 100644 --- a/src/render/vk_renderpass_geometry.cpp +++ b/src/render/vk_renderpass_geometry.cpp @@ -278,8 +278,9 @@ void GeometryPass::draw_geometry(VkCommandBuffer cmd, push_constants.vertexBuffer = r.vertexBufferAddress; push_constants.objectID = r.objectID; - vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0, - sizeof(GPUDrawPushConstants), &push_constants); + vkCmdPushConstants(cmd, r.material->pipeline->layout, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + 0, sizeof(GPUDrawPushConstants), &push_constants); vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0); diff --git a/src/render/vk_renderpass_transparent.cpp b/src/render/vk_renderpass_transparent.cpp index bdedc95..6187776 100644 --- a/src/render/vk_renderpass_transparent.cpp +++ b/src/render/vk_renderpass_transparent.cpp @@ -197,8 +197,9 @@ void TransparentPass::draw_transparent(VkCommandBuffer cmd, push.worldMatrix = r.transform; push.vertexBuffer = r.vertexBufferAddress; push.objectID = r.objectID; - vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0, - sizeof(GPUDrawPushConstants), &push); + vkCmdPushConstants(cmd, r.material->pipeline->layout, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + 0, sizeof(GPUDrawPushConstants), &push); vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0); if (ctxLocal->stats) { diff --git a/src/scene/vk_loader.cpp b/src/scene/vk_loader.cpp index 78e49db..1339133 100644 --- a/src/scene/vk_loader.cpp +++ b/src/scene/vk_loader.cpp @@ -1070,7 +1070,7 @@ void LoadedGLTF::clearAll() { if (creator->_rayManager) { - creator->_rayManager->removeBLASForBuffer(v->meshBuffers.vertexBuffer.buffer); + creator->_rayManager->removeBLASForMesh(v.get()); } creator->_resourceManager->destroy_buffer(v->meshBuffers.indexBuffer); creator->_resourceManager->destroy_buffer(v->meshBuffers.vertexBuffer); diff --git a/src/scene/vk_scene.cpp b/src/scene/vk_scene.cpp index 903af65..1330793 100644 --- a/src/scene/vk_scene.cpp +++ b/src/scene/vk_scene.cpp @@ -14,6 +14,8 @@ #include #include #include + +#include "frame_resources.h" #include "core/config.h" void SceneManager::init(EngineContext *context) @@ -35,6 +37,16 @@ void SceneManager::update_scene() auto start = std::chrono::system_clock::now(); // Release any GLTF assets that were scheduled for safe destruction after GPU idle. + // Defer actual destruction to the current frame's deletion queue so we wait + // until the GPU has finished work that might still reference their resources. + if (_context && _context->currentFrame) + { + for (auto &sp : pendingGLTFRelease) + { + auto keepAlive = sp; // copy to keep ref count in the lambda + _context->currentFrame->_deletionQueue.push_function([keepAlive]() mutable { keepAlive.reset(); }); + } + } pendingGLTFRelease.clear(); mainDrawContext.OpaqueSurfaces.clear(); @@ -331,7 +343,16 @@ bool SceneManager::removeGLTFInstance(const std::string &name) // Defer destruction until after the next frame fence (update_scene). if (it->second.scene) { - pendingGLTFRelease.push_back(it->second.scene); + if (_context && _context->currentFrame) + { + auto keepAlive = it->second.scene; + _context->currentFrame->_deletionQueue.push_function([keepAlive]() mutable { keepAlive.reset(); }); + } + else + { + // Fallback: stash until we have a frame to attach the destruction to. + pendingGLTFRelease.push_back(it->second.scene); + } } dynamicGLTFInstances.erase(it);