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.
|
// and clear per-frame resources before building UI and recording commands.
|
||||||
VK_CHECK(vkWaitForFences(_deviceManager->device(), 1, &get_current_frame()._renderFence, true, 1000000000));
|
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)
|
if (_pickResultPending && _pickReadbackBuffer.buffer && _sceneManager)
|
||||||
{
|
{
|
||||||
vmaInvalidateAllocation(_deviceManager->allocator(), _pickReadbackBuffer.allocation, 0, sizeof(uint32_t));
|
vmaInvalidateAllocation(_deviceManager->allocator(), _pickReadbackBuffer.allocation, 0, sizeof(uint32_t));
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ void RayTracingManager::init(DeviceManager *dev, ResourceManager *res)
|
|||||||
void RayTracingManager::cleanup()
|
void RayTracingManager::cleanup()
|
||||||
{
|
{
|
||||||
VkDevice dv = _device->device();
|
VkDevice dv = _device->device();
|
||||||
|
// Destroy any deferred BLAS first
|
||||||
|
flushPendingDeletes();
|
||||||
|
|
||||||
if (_tlas.handle)
|
if (_tlas.handle)
|
||||||
{
|
{
|
||||||
_vkDestroyAccelerationStructureKHR(dv, _tlas.handle, nullptr);
|
_vkDestroyAccelerationStructureKHR(dv, _tlas.handle, nullptr);
|
||||||
@@ -60,6 +63,25 @@ void RayTracingManager::cleanup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_blasByVB.clear();
|
_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)
|
static VkDeviceAddress get_buffer_address(VkDevice dev, VkBuffer buf)
|
||||||
@@ -77,6 +99,10 @@ AccelStructureHandle RayTracingManager::getOrBuildBLAS(const std::shared_ptr<Mes
|
|||||||
{
|
{
|
||||||
return it->second;
|
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)
|
// Build BLAS with one geometry per surface (skip empty primitives)
|
||||||
std::vector<VkAccelerationStructureGeometryKHR> geoms;
|
std::vector<VkAccelerationStructureGeometryKHR> geoms;
|
||||||
@@ -185,6 +211,7 @@ AccelStructureHandle RayTracingManager::getOrBuildBLAS(const std::shared_ptr<Mes
|
|||||||
blas.deviceAddress = _vkGetAccelerationStructureDeviceAddressKHR(_device->device(), &dai);
|
blas.deviceAddress = _vkGetAccelerationStructureDeviceAddressKHR(_device->device(), &dai);
|
||||||
|
|
||||||
_blasByVB.emplace(vb, blas);
|
_blasByVB.emplace(vb, blas);
|
||||||
|
_blasByMesh.emplace(mesh.get(), blas);
|
||||||
return blas;
|
return blas;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,16 +250,33 @@ VkAccelerationStructureKHR RayTracingManager::buildTLASFromDrawContext(const Dra
|
|||||||
|
|
||||||
for (const auto &r: dc.OpaqueSurfaces)
|
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{};
|
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);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
blas = it->second;
|
|
||||||
|
|
||||||
VkAccelerationStructureInstanceKHR inst{};
|
VkAccelerationStructureInstanceKHR inst{};
|
||||||
// Fill 3x4 row-major from GLM column-major mat4
|
// Fill 3x4 row-major from GLM column-major mat4
|
||||||
@@ -352,13 +396,46 @@ void RayTracingManager::removeBLASForBuffer(VkBuffer vertexBuffer)
|
|||||||
auto it = _blasByVB.find(vertexBuffer);
|
auto it = _blasByVB.find(vertexBuffer);
|
||||||
if (it == _blasByVB.end()) return;
|
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 (mit->second.handle == it->second.handle)
|
||||||
}
|
{
|
||||||
if (it->second.storage.buffer)
|
mit = _blasByMesh.erase(mit);
|
||||||
{
|
}
|
||||||
_resources->destroy_buffer(it->second.storage);
|
else
|
||||||
|
{
|
||||||
|
++mit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_blasByVB.erase(it);
|
_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; }
|
VkAccelerationStructureKHR tlas() const { return _tlas.handle; }
|
||||||
VkDeviceAddress tlasAddress() const { return _tlas.deviceAddress; }
|
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.
|
// Remove and destroy a cached BLAS associated with a vertex buffer.
|
||||||
// Safe to call even if no BLAS exists for the buffer.
|
// Safe to call even if no BLAS exists for the buffer.
|
||||||
void removeBLASForBuffer(VkBuffer vertexBuffer);
|
void removeBLASForBuffer(VkBuffer vertexBuffer);
|
||||||
|
// Remove and destroy a cached BLAS associated with a mesh pointer.
|
||||||
|
void removeBLASForMesh(const MeshAsset *mesh);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// function pointers (resolved on init)
|
// function pointers (resolved on init)
|
||||||
@@ -45,15 +50,19 @@ private:
|
|||||||
|
|
||||||
DeviceManager* _device{nullptr};
|
DeviceManager* _device{nullptr};
|
||||||
ResourceManager* _resources{nullptr};
|
ResourceManager* _resources{nullptr};
|
||||||
|
|
||||||
// BLAS cache by vertex buffer handle
|
// BLAS cache by vertex buffer handle (legacy) and by mesh pointer (preferred)
|
||||||
std::unordered_map<VkBuffer, AccelStructureHandle> _blasByVB;
|
std::unordered_map<VkBuffer, AccelStructureHandle> _blasByVB;
|
||||||
|
std::unordered_map<const MeshAsset*, AccelStructureHandle> _blasByMesh;
|
||||||
|
|
||||||
// TLAS + scratch / instance buffer (rebuilt per frame)
|
// TLAS + scratch / instance buffer (rebuilt per frame)
|
||||||
AccelStructureHandle _tlas{};
|
AccelStructureHandle _tlas{};
|
||||||
AllocatedBuffer _tlasInstanceBuffer{};
|
AllocatedBuffer _tlasInstanceBuffer{};
|
||||||
size_t _tlasInstanceCapacity{0};
|
size_t _tlasInstanceCapacity{0};
|
||||||
|
|
||||||
|
// BLAS scheduled for destruction once GPU is idle
|
||||||
|
std::vector<AccelStructureHandle> _pendingBlasDestroy;
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
VkDeviceSize _minScratchAlignment{256};
|
VkDeviceSize _minScratchAlignment{256};
|
||||||
|
|
||||||
|
|||||||
@@ -278,8 +278,9 @@ void GeometryPass::draw_geometry(VkCommandBuffer cmd,
|
|||||||
push_constants.vertexBuffer = r.vertexBufferAddress;
|
push_constants.vertexBuffer = r.vertexBufferAddress;
|
||||||
push_constants.objectID = r.objectID;
|
push_constants.objectID = r.objectID;
|
||||||
|
|
||||||
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
|
vkCmdPushConstants(cmd, r.material->pipeline->layout,
|
||||||
sizeof(GPUDrawPushConstants), &push_constants);
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
0, sizeof(GPUDrawPushConstants), &push_constants);
|
||||||
|
|
||||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||||
|
|
||||||
|
|||||||
@@ -197,8 +197,9 @@ void TransparentPass::draw_transparent(VkCommandBuffer cmd,
|
|||||||
push.worldMatrix = r.transform;
|
push.worldMatrix = r.transform;
|
||||||
push.vertexBuffer = r.vertexBufferAddress;
|
push.vertexBuffer = r.vertexBufferAddress;
|
||||||
push.objectID = r.objectID;
|
push.objectID = r.objectID;
|
||||||
vkCmdPushConstants(cmd, r.material->pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
|
vkCmdPushConstants(cmd, r.material->pipeline->layout,
|
||||||
sizeof(GPUDrawPushConstants), &push);
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
0, sizeof(GPUDrawPushConstants), &push);
|
||||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||||
if (ctxLocal->stats)
|
if (ctxLocal->stats)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1070,7 +1070,7 @@ void LoadedGLTF::clearAll()
|
|||||||
{
|
{
|
||||||
if (creator->_rayManager)
|
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.indexBuffer);
|
||||||
creator->_resourceManager->destroy_buffer(v->meshBuffers.vertexBuffer);
|
creator->_resourceManager->destroy_buffer(v->meshBuffers.vertexBuffer);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "frame_resources.h"
|
||||||
#include "core/config.h"
|
#include "core/config.h"
|
||||||
|
|
||||||
void SceneManager::init(EngineContext *context)
|
void SceneManager::init(EngineContext *context)
|
||||||
@@ -35,6 +37,16 @@ void SceneManager::update_scene()
|
|||||||
auto start = std::chrono::system_clock::now();
|
auto start = std::chrono::system_clock::now();
|
||||||
|
|
||||||
// Release any GLTF assets that were scheduled for safe destruction after GPU idle.
|
// 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();
|
pendingGLTFRelease.clear();
|
||||||
|
|
||||||
mainDrawContext.OpaqueSurfaces.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).
|
// Defer destruction until after the next frame fence (update_scene).
|
||||||
if (it->second.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);
|
dynamicGLTFInstances.erase(it);
|
||||||
|
|||||||
Reference in New Issue
Block a user