memory safe object adding system (RT shadow error)

This commit is contained in:
2025-11-21 16:46:04 +09:00
parent 4b05df3c04
commit cec2dadd94
7 changed files with 132 additions and 20 deletions

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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};

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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);