memory safe object adding system (RT shadow error)
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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_ptr<Mes
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
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<VkAccelerationStructureGeometryKHR> geoms;
|
||||
@@ -185,6 +211,7 @@ AccelStructureHandle RayTracingManager::getOrBuildBLAS(const std::shared_ptr<Mes
|
||||
blas.deviceAddress = _vkGetAccelerationStructureDeviceAddressKHR(_device->device(), &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<MeshAsset> nonOwning(const_cast<MeshAsset *>(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);
|
||||
}
|
||||
|
||||
@@ -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<VkBuffer, AccelStructureHandle> _blasByVB;
|
||||
|
||||
// BLAS cache by vertex buffer handle (legacy) and by mesh pointer (preferred)
|
||||
std::unordered_map<VkBuffer, AccelStructureHandle> _blasByVB;
|
||||
std::unordered_map<const MeshAsset*, AccelStructureHandle> _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<AccelStructureHandle> _pendingBlasDestroy;
|
||||
|
||||
// Properties
|
||||
VkDeviceSize _minScratchAlignment{256};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
#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);
|
||||
|
||||
Reference in New Issue
Block a user