Skip to content

Commit 57d64cf

Browse files
committed
Improve resource cleanup with explicit memory deallocation and exception safety. Add pooled memory handling, warnings for deallocation failures, and enhanced cleanup logic for swapchain and per-entity resources.
1 parent d4b75b3 commit 57d64cf

File tree

4 files changed

+136
-6
lines changed

4 files changed

+136
-6
lines changed

attachments/simple_engine/renderer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,9 +622,10 @@ class Renderer {
622622

623623
// The texture that will hold a snapshot of the opaque scene
624624
vk::raii::Image opaqueSceneColorImage{nullptr};
625-
vk::raii::DeviceMemory opaqueSceneColorImageMemory{nullptr}; // <-- Standard Vulkan memory
626625
vk::raii::ImageView opaqueSceneColorImageView{nullptr};
627626
vk::raii::Sampler opaqueSceneColorSampler{nullptr};
627+
// Pooled allocation associated with opaqueSceneColorImage (must be explicitly deallocated)
628+
std::unique_ptr<MemoryPool::Allocation> opaqueSceneColorImageAllocation = nullptr;
628629

629630
// A descriptor set for the opaque scene color texture. We will have one for each frame in flight
630631
// to match the swapchain images.

attachments/simple_engine/renderer_core.cpp

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,111 @@ void Renderer::Cleanup() {
235235

236236
// Wait for the device to be idle before cleaning up
237237
device.waitIdle();
238+
239+
// Clean up swapchain-bound resources (depth, offscreen color, pipelines, views, etc.)
240+
cleanupSwapChain();
241+
242+
// Release per-entity resources and return pooled memory to the MemoryPool
238243
for (auto& resources : entityResources | std::views::values) {
239-
// Memory pool handles unmapping automatically, no need to manually unmap
244+
// Descriptor sets are RAII and freed with descriptorPool; just clear holders
240245
resources.basicDescriptorSets.clear();
241246
resources.pbrDescriptorSets.clear();
247+
248+
// Destroy UBO buffers and return pooled allocations
242249
resources.uniformBuffers.clear();
250+
for (auto& alloc : resources.uniformBufferAllocations) {
251+
if (alloc) {
252+
try { memoryPool->deallocate(std::move(alloc)); }
253+
catch (const std::exception& e) {
254+
std::cerr << "Warning: failed to deallocate UBO allocation during Cleanup: " << e.what() << std::endl;
255+
}
256+
}
257+
}
243258
resources.uniformBufferAllocations.clear();
244259
resources.uniformBuffersMapped.clear();
260+
261+
// Destroy instance buffer and return pooled allocation
262+
resources.instanceBuffer = nullptr;
263+
if (resources.instanceBufferAllocation) {
264+
try { memoryPool->deallocate(std::move(resources.instanceBufferAllocation)); }
265+
catch (const std::exception& e) {
266+
std::cerr << "Warning: failed to deallocate instance buffer allocation during Cleanup: " << e.what() << std::endl;
267+
}
268+
}
269+
resources.instanceBufferMapped = nullptr;
270+
}
271+
entityResources.clear();
272+
273+
// Release light storage buffers
274+
for (auto& lsb : lightStorageBuffers) {
275+
lsb.buffer = nullptr;
276+
if (lsb.allocation) {
277+
try { memoryPool->deallocate(std::move(lsb.allocation)); }
278+
catch (const std::exception& e) {
279+
std::cerr << "Warning: failed to deallocate light storage buffer during Cleanup: " << e.what() << std::endl;
280+
}
281+
}
282+
lsb.mapped = nullptr;
283+
lsb.capacity = 0;
284+
lsb.size = 0;
285+
}
286+
lightStorageBuffers.clear();
287+
288+
// Release mesh device-local buffers and return pooled allocations
289+
for (auto& [mesh, mres] : meshResources) {
290+
mres.vertexBuffer = nullptr;
291+
if (mres.vertexBufferAllocation) {
292+
try { memoryPool->deallocate(std::move(mres.vertexBufferAllocation)); }
293+
catch (const std::exception& e) {
294+
std::cerr << "Warning: failed to deallocate vertex buffer allocation during Cleanup: " << e.what() << std::endl;
295+
}
296+
}
297+
mres.indexBuffer = nullptr;
298+
if (mres.indexBufferAllocation) {
299+
try { memoryPool->deallocate(std::move(mres.indexBufferAllocation)); }
300+
catch (const std::exception& e) {
301+
std::cerr << "Warning: failed to deallocate index buffer allocation during Cleanup: " << e.what() << std::endl;
302+
}
303+
}
304+
// Staging buffers are RAII and not pooled
305+
mres.stagingVertexBuffer = nullptr;
306+
mres.stagingVertexBufferMemory = nullptr;
307+
mres.stagingIndexBuffer = nullptr;
308+
mres.stagingIndexBufferMemory = nullptr;
309+
mres.vertexBufferSizeBytes = 0;
310+
mres.indexBufferSizeBytes = 0;
311+
mres.indexCount = 0;
312+
}
313+
meshResources.clear();
314+
315+
// Release textures and return pooled allocations
316+
{
317+
std::unique_lock<std::shared_mutex> texLock(textureResourcesMutex);
318+
for (auto& [key, tres] : textureResources) {
319+
tres.textureSampler = nullptr;
320+
tres.textureImageView = nullptr;
321+
tres.textureImage = nullptr;
322+
if (tres.textureImageAllocation) {
323+
try { memoryPool->deallocate(std::move(tres.textureImageAllocation)); }
324+
catch (const std::exception& e) {
325+
std::cerr << "Warning: failed to deallocate texture image allocation during Cleanup: " << e.what() << std::endl;
326+
}
327+
}
328+
}
329+
textureResources.clear();
330+
textureAliases.clear();
331+
textureToEntities.clear();
332+
}
333+
334+
// Release default texture resources if allocated
335+
defaultTextureResources.textureSampler = nullptr;
336+
defaultTextureResources.textureImageView = nullptr;
337+
defaultTextureResources.textureImage = nullptr;
338+
if (defaultTextureResources.textureImageAllocation) {
339+
try { memoryPool->deallocate(std::move(defaultTextureResources.textureImageAllocation)); }
340+
catch (const std::exception& e) {
341+
std::cerr << "Warning: failed to deallocate default texture image allocation during Cleanup: " << e.what() << std::endl;
342+
}
245343
}
246344
// Also clear global descriptor sets that are allocated from descriptorPool, so they are
247345
// destroyed while the pool is still valid (avoid vkFreeDescriptorSets invalid pool errors)

attachments/simple_engine/renderer_rendering.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,29 @@ bool Renderer::createSyncObjects() {
249249

250250
// Clean up swap chain
251251
void Renderer::cleanupSwapChain() {
252-
// Clean up depth resources
252+
// Ensure GPU is idle before tearing down resources bound to the swapchain
253+
device.waitIdle();
254+
255+
// Clean up off-screen opaque scene color resources
256+
opaqueSceneColorSampler = nullptr;
257+
opaqueSceneColorImageView = nullptr;
258+
if (opaqueSceneColorImageAllocation) {
259+
try { memoryPool->deallocate(std::move(opaqueSceneColorImageAllocation)); }
260+
catch (const std::exception& e) {
261+
std::cerr << "Warning: failed to deallocate opaqueSceneColor allocation during cleanupSwapChain: " << e.what() << std::endl;
262+
}
263+
}
264+
opaqueSceneColorImage = nullptr;
265+
266+
// Clean up depth resources (free pooled allocation explicitly)
253267
depthImageView = nullptr;
268+
if (depthImageAllocation) {
269+
try { memoryPool->deallocate(std::move(depthImageAllocation)); }
270+
catch (const std::exception& e) {
271+
std::cerr << "Warning: failed to deallocate depth allocation during cleanupSwapChain: " << e.what() << std::endl;
272+
}
273+
}
254274
depthImage = nullptr;
255-
depthImageAllocation = nullptr;
256275

257276
// Clean up swap chain image views
258277
swapChainImageViews.clear();

attachments/simple_engine/renderer_resources.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,6 +1473,14 @@ void Renderer::createTransparentFallbackDescriptorSets() {
14731473

14741474
bool Renderer::createOpaqueSceneColorResources() {
14751475
try {
1476+
// If an old pooled allocation exists (e.g., after a resize), free it before creating a new one
1477+
if (opaqueSceneColorImageAllocation) {
1478+
try {
1479+
memoryPool->deallocate(std::move(opaqueSceneColorImageAllocation));
1480+
} catch (const std::exception& e) {
1481+
std::cerr << "Warning: failed to deallocate previous opaqueSceneColor allocation: " << e.what() << std::endl;
1482+
}
1483+
}
14761484
// Create the image
14771485
auto [image, allocation] = createImagePooled(
14781486
swapChainExtent.width,
@@ -1483,7 +1491,8 @@ bool Renderer::createOpaqueSceneColorResources() {
14831491
vk::MemoryPropertyFlagBits::eDeviceLocal);
14841492

14851493
opaqueSceneColorImage = std::move(image);
1486-
// We don't need a member for the allocation, it's managed by the unique_ptr
1494+
// Store the pooled allocation so it can be explicitly returned to the pool on cleanup
1495+
opaqueSceneColorImageAllocation = std::move(allocation);
14871496

14881497
// Create the image view
14891498
opaqueSceneColorImageView = createImageView(opaqueSceneColorImage, swapChainImageFormat, vk::ImageAspectFlagBits::eColor);
@@ -1857,7 +1866,10 @@ bool Renderer::createOrResizeLightStorageBuffers(size_t lightCount) {
18571866
// Clean up old buffer if it exists (now safe after waitIdle)
18581867
if (buffer.allocation) {
18591868
buffer.buffer = nullptr;
1860-
buffer.allocation.reset();
1869+
try { memoryPool->deallocate(std::move(buffer.allocation)); }
1870+
catch (const std::exception& e) {
1871+
std::cerr << "Warning: failed to deallocate previous light storage buffer allocation: " << e.what() << std::endl;
1872+
}
18611873
buffer.mapped = nullptr;
18621874
}
18631875

0 commit comments

Comments
 (0)