diff --git a/include/offline_sdf_renderer.h b/include/offline_sdf_renderer.h index 3515c5e..b1ce280 100644 --- a/include/offline_sdf_renderer.h +++ b/include/offline_sdf_renderer.h @@ -60,9 +60,10 @@ class OfflineSDFRenderer : public SDFRenderer { void setupRenderContext(); void createPipeline(); void createCommandBuffers(); - void destroyRenderContext(); - void destroyPipeline(); - void destroy(); + void destroyDeviceObjects() noexcept; + void destroyRenderContextObjects() noexcept; + void destroyPipelineObjects() noexcept; + void destroy() noexcept; void recordCommandBuffer(uint32_t slotIndex, uint32_t currentFrame); [[nodiscard]] PPMDebugFrame debugReadbackOffscreenImage(const RingSlot &slot); @@ -85,6 +86,7 @@ class OfflineSDFRenderer : public SDFRenderer { void startEncoding(); void stopEncoding(); + void stopEncodingNoexcept() noexcept; void enqueueEncode(uint32_t slotIndex, uint32_t frameIndex); void waitForSlotEncode(uint32_t slotIndex); void runEncoderLoop(); @@ -95,6 +97,7 @@ class OfflineSDFRenderer : public SDFRenderer { OfflineSDFRenderer( const std::string &fragShaderPath, bool useToyTemplate = false, OfflineRenderOptions options = {}); + ~OfflineSDFRenderer() noexcept; void setup(); void renderFrames(); }; diff --git a/include/online_sdf_renderer.h b/include/online_sdf_renderer.h index 0f37e8e..8441650 100644 --- a/include/online_sdf_renderer.h +++ b/include/online_sdf_renderer.h @@ -29,19 +29,19 @@ class OnlineSDFRenderer : public SDFRenderer { private: // GLFW Setup GLFWApplication app; - GLFWwindow *window; + GLFWwindow *window = nullptr; // Vulkan Setup - VkSurfaceKHR surface; - VkSurfaceFormatKHR swapchainFormat; + VkSurfaceKHR surface = VK_NULL_HANDLE; + VkSurfaceFormatKHR swapchainFormat{}; vkutils::Semaphores imageAvailableSemaphores; vkutils::Semaphores renderFinishedSemaphores; // Shader Modules. // Full screen quad vert shader + frag shader VkSwapchainKHR swapchain = VK_NULL_HANDLE; - VkSurfaceCapabilitiesKHR surfaceCapabilities; - VkExtent2D swapchainSize; + VkSurfaceCapabilitiesKHR surfaceCapabilities{}; + VkExtent2D swapchainSize{}; vkutils::SwapchainImages swapchainImages; vkutils::SwapchainImageViews swapchainImageViews; vkutils::FrameBuffers frameBuffers; @@ -61,9 +61,11 @@ class OnlineSDFRenderer : public SDFRenderer { void createPipeline(); void tryRecreatePipeline(); void calcTimestamps(uint32_t imageIndex); - void destroyRenderContext(); - void destroyPipeline(); - void destroy(); + void destroyRenderContextOrThrow(); + void destroyDeviceObjects() noexcept; + void destroyRenderContextObjects() noexcept; + void destroyPipelineObjects() noexcept; + void destroy() noexcept; [[nodiscard]] vkutils::PushConstants getPushConstants(uint32_t currentFrame) noexcept; @@ -74,6 +76,7 @@ class OnlineSDFRenderer : public SDFRenderer { OnlineSDFRenderer(const std::string &fragShaderPath, bool useToyTemplate = false, OnlineRenderOptions options = {}); + ~OnlineSDFRenderer() noexcept; void setup(); void gameLoop(); }; diff --git a/include/vkutils.h b/include/vkutils.h index f3b778c..6a3c5ea 100644 --- a/include/vkutils.h +++ b/include/vkutils.h @@ -1438,6 +1438,7 @@ destroySwapchainImageViews(VkDevice device, vkDestroyImageView(device, swapchainImageViews.imageViews[i], nullptr); swapchainImageViews.imageViews[i] = VK_NULL_HANDLE; } + swapchainImageViews.count = 0; } static void destroyFrameBuffers(VkDevice device, @@ -1446,6 +1447,7 @@ static void destroyFrameBuffers(VkDevice device, vkDestroyFramebuffer(device, frameBuffers.framebuffers[i], nullptr); frameBuffers.framebuffers[i] = VK_NULL_HANDLE; } + frameBuffers.count = 0; } static void destroyFences(VkDevice device, Fences &fences) noexcept { @@ -1453,6 +1455,7 @@ static void destroyFences(VkDevice device, Fences &fences) noexcept { vkDestroyFence(device, fences.fences[i], nullptr); fences.fences[i] = VK_NULL_HANDLE; } + fences.count = 0; } static void destroySemaphores(VkDevice device, @@ -1461,6 +1464,7 @@ static void destroySemaphores(VkDevice device, vkDestroySemaphore(device, semaphores.semaphores[i], nullptr); semaphores.semaphores[i] = VK_NULL_HANDLE; } + semaphores.count = 0; } } // namespace vkutils diff --git a/src/offline_sdf_renderer.cpp b/src/offline_sdf_renderer.cpp index 6d2949e..265bce2 100644 --- a/src/offline_sdf_renderer.cpp +++ b/src/offline_sdf_renderer.cpp @@ -16,6 +16,11 @@ OfflineSDFRenderer::OfflineSDFRenderer( maxFrames(options.maxFrames), encodeSettings(std::move(options.encodeSettings)) {} +OfflineSDFRenderer::~OfflineSDFRenderer() noexcept { + stopEncodingNoexcept(); + destroy(); +} + uint32_t OfflineSDFRenderer::validateRingSize(uint32_t value) { if (value == 0 || value > MAX_FRAME_SLOTS) { throw std::runtime_error("ringSize must be 1..MAX_FRAME_SLOTS"); @@ -358,7 +363,6 @@ void OfflineSDFRenderer::renderFrames() { stopEncoding(); spdlog::info("Offline render done."); - destroy(); } void OfflineSDFRenderer::startEncoding() { @@ -458,6 +462,16 @@ void OfflineSDFRenderer::stopEncoding() { encoder.reset(); } +void OfflineSDFRenderer::stopEncodingNoexcept() noexcept { + try { + stopEncoding(); + } catch (const std::exception &e) { + spdlog::error("stopEncoding failed during teardown: {}", e.what()); + } catch (...) { + spdlog::error("stopEncoding failed during teardown: unknown error"); + } +} + void OfflineSDFRenderer::enqueueEncode(uint32_t slotIndex, uint32_t frameIndex) { std::unique_lock lock(encodeMutex); @@ -485,72 +499,52 @@ void OfflineSDFRenderer::waitForSlotEncode(uint32_t slotIndex) { throw std::runtime_error("FFmpeg encoder failed"); } -void OfflineSDFRenderer::destroyPipeline() { +void OfflineSDFRenderer::destroyPipelineObjects() noexcept { vkDestroyPipeline(logicalDevice, pipeline, nullptr); + pipeline = VK_NULL_HANDLE; vkDestroyPipelineLayout(logicalDevice, pipelineLayout, nullptr); + pipelineLayout = VK_NULL_HANDLE; vkDestroyShaderModule(logicalDevice, fragShaderModule, nullptr); + fragShaderModule = VK_NULL_HANDLE; } -void OfflineSDFRenderer::destroyRenderContext() { - VK_CHECK(vkDeviceWaitIdle(logicalDevice)); +void OfflineSDFRenderer::destroyRenderContextObjects() noexcept { for (size_t i = 0; i < ringSize; ++i) { RingSlot &slot = ringSlots[i]; - if (slot.framebuffer != VK_NULL_HANDLE) { - vkDestroyFramebuffer(logicalDevice, slot.framebuffer, nullptr); - slot.framebuffer = VK_NULL_HANDLE; - } - if (slot.imageView != VK_NULL_HANDLE) { - vkDestroyImageView(logicalDevice, slot.imageView, nullptr); - slot.imageView = VK_NULL_HANDLE; - } - if (slot.image != VK_NULL_HANDLE) { - vkDestroyImage(logicalDevice, slot.image, nullptr); - slot.image = VK_NULL_HANDLE; - } - if (slot.imageMemory != VK_NULL_HANDLE) { - vkFreeMemory(logicalDevice, slot.imageMemory, nullptr); - slot.imageMemory = VK_NULL_HANDLE; - } - if (slot.stagingBuffer.buffer != VK_NULL_HANDLE || - slot.stagingBuffer.memory != VK_NULL_HANDLE) { - if (slot.mappedData) { - vkUnmapMemory(logicalDevice, slot.stagingBuffer.memory); - slot.mappedData = nullptr; - } - vkutils::destroyReadbackBuffer(logicalDevice, slot.stagingBuffer); + vkDestroyFramebuffer(logicalDevice, slot.framebuffer, nullptr); + vkDestroyImageView(logicalDevice, slot.imageView, nullptr); + vkDestroyImage(logicalDevice, slot.image, nullptr); + vkFreeMemory(logicalDevice, slot.imageMemory, nullptr); + + if (slot.mappedData) { + vkUnmapMemory(logicalDevice, slot.stagingBuffer.memory); } - slot.pendingReadback = false; - slot.pendingEncode = false; + vkutils::destroyReadbackBuffer(logicalDevice, slot.stagingBuffer); } } -void OfflineSDFRenderer::destroy() { - VK_CHECK(vkDeviceWaitIdle(logicalDevice)); - vkutils::destroyFences(logicalDevice, fences); - destroyPipeline(); - destroyRenderContext(); - if (renderPass != VK_NULL_HANDLE) { - vkDestroyRenderPass(logicalDevice, renderPass, nullptr); - renderPass = VK_NULL_HANDLE; - } - if (queryPool != VK_NULL_HANDLE) { - vkDestroyQueryPool(logicalDevice, queryPool, nullptr); - queryPool = VK_NULL_HANDLE; - } - if (vertShaderModule != VK_NULL_HANDLE) { - vkDestroyShaderModule(logicalDevice, vertShaderModule, nullptr); - vertShaderModule = VK_NULL_HANDLE; - } - if (commandPool != VK_NULL_HANDLE) { - vkDestroyCommandPool(logicalDevice, commandPool, nullptr); - commandPool = VK_NULL_HANDLE; +void OfflineSDFRenderer::destroyDeviceObjects() noexcept { + const VkResult waitResult = vkDeviceWaitIdle(logicalDevice); + if (waitResult != VK_SUCCESS) { + spdlog::warn( + "vkDeviceWaitIdle failed during OfflineSDFRenderer teardown: {}", + static_cast(waitResult)); } + vkutils::destroyFences(logicalDevice, fences); + destroyPipelineObjects(); + destroyRenderContextObjects(); + vkDestroyRenderPass(logicalDevice, renderPass, nullptr); + vkDestroyQueryPool(logicalDevice, queryPool, nullptr); + vkDestroyShaderModule(logicalDevice, vertShaderModule, nullptr); + vkDestroyCommandPool(logicalDevice, commandPool, nullptr); +} + +void OfflineSDFRenderer::destroy() noexcept { if (logicalDevice != VK_NULL_HANDLE) { - vkDestroyDevice(logicalDevice, nullptr); - logicalDevice = VK_NULL_HANDLE; - } - if (instance != VK_NULL_HANDLE) { - vkDestroyInstance(instance, nullptr); - instance = VK_NULL_HANDLE; + destroyDeviceObjects(); } + vkDestroyDevice(logicalDevice, nullptr); + + // Instance owns no other offline objects at this point. + vkDestroyInstance(instance, nullptr); } diff --git a/src/online_sdf_renderer.cpp b/src/online_sdf_renderer.cpp index 005b873..9b9f48e 100644 --- a/src/online_sdf_renderer.cpp +++ b/src/online_sdf_renderer.cpp @@ -21,6 +21,8 @@ OnlineSDFRenderer::OnlineSDFRenderer( : SDFRenderer(fragShaderPath, useToyTemplate, options.debugDumpPPMDir), options(std::move(options)) {} +OnlineSDFRenderer::~OnlineSDFRenderer() noexcept { destroy(); } + void OnlineSDFRenderer::setup() { glfwSetup(); vulkanSetup(); @@ -122,7 +124,7 @@ void OnlineSDFRenderer::tryRecreatePipeline() { } VK_CHECK(vkDeviceWaitIdle(logicalDevice)); - destroyPipeline(); + destroyPipelineObjects(); createPipelineLayoutCommon(); fragShaderModule = vkutils::createShaderModule(logicalDevice, fragSpirv); pipeline = vkutils::createGraphicsPipeline( @@ -135,17 +137,43 @@ void OnlineSDFRenderer::createCommandBuffers() { swapchainImages.count); } -void OnlineSDFRenderer::destroyPipeline() { +void OnlineSDFRenderer::destroyPipelineObjects() noexcept { vkDestroyPipeline(logicalDevice, pipeline, nullptr); + pipeline = VK_NULL_HANDLE; vkDestroyPipelineLayout(logicalDevice, pipelineLayout, nullptr); + pipelineLayout = VK_NULL_HANDLE; vkDestroyShaderModule(logicalDevice, fragShaderModule, nullptr); + fragShaderModule = VK_NULL_HANDLE; } -void OnlineSDFRenderer::destroyRenderContext() { - VK_CHECK(vkDeviceWaitIdle(logicalDevice)); - VK_CHECK(vkResetCommandPool(logicalDevice, commandPool, 0)); +void OnlineSDFRenderer::destroyRenderContextObjects() noexcept { vkutils::destroyFrameBuffers(logicalDevice, frameBuffers); vkutils::destroySwapchainImageViews(logicalDevice, swapchainImageViews); +} + +void OnlineSDFRenderer::destroyDeviceObjects() noexcept { + const VkResult waitResult = vkDeviceWaitIdle(logicalDevice); + if (waitResult != VK_SUCCESS) { + spdlog::warn("vkDeviceWaitIdle failed during OnlineSDFRenderer teardown: {}", + static_cast(waitResult)); + } + + vkutils::destroySemaphores(logicalDevice, imageAvailableSemaphores); + vkutils::destroySemaphores(logicalDevice, renderFinishedSemaphores); + vkutils::destroyFences(logicalDevice, fences); + destroyPipelineObjects(); + vkDestroyShaderModule(logicalDevice, vertShaderModule, nullptr); + destroyRenderContextObjects(); + vkDestroyRenderPass(logicalDevice, renderPass, nullptr); + vkDestroySwapchainKHR(logicalDevice, swapchain, nullptr); + vkDestroyQueryPool(logicalDevice, queryPool, nullptr); + vkDestroyCommandPool(logicalDevice, commandPool, nullptr); +} + +void OnlineSDFRenderer::destroyRenderContextOrThrow() { + VK_CHECK(vkDeviceWaitIdle(logicalDevice)); + VK_CHECK(vkResetCommandPool(logicalDevice, commandPool, 0)); + destroyRenderContextObjects(); // Swapchain gets destroyed after passing oldSwapchain to createSwapchain } @@ -194,7 +222,7 @@ void OnlineSDFRenderer::gameLoop() { pipelineUpdated.store(true, std::memory_order_relaxed); }); auto recreateSwapchain = [&]() { - destroyRenderContext(); + destroyRenderContextOrThrow(); setupRenderContext(); app.framebufferResized = false; frameIndex = 0; @@ -292,27 +320,21 @@ void OnlineSDFRenderer::gameLoop() { filewatcher->stopWatching(); spdlog::info("Done!"); - destroy(); } -void OnlineSDFRenderer::destroy() { - VK_CHECK(vkDeviceWaitIdle(logicalDevice)); - vkutils::destroySemaphores(logicalDevice, imageAvailableSemaphores); - vkutils::destroySemaphores(logicalDevice, renderFinishedSemaphores); - vkutils::destroyFences(logicalDevice, fences); - vkDestroyPipeline(logicalDevice, pipeline, nullptr); - vkDestroyPipelineLayout(logicalDevice, pipelineLayout, nullptr); - vkDestroyShaderModule(logicalDevice, vertShaderModule, nullptr); - vkDestroyShaderModule(logicalDevice, fragShaderModule, nullptr); - vkutils::destroyFrameBuffers(logicalDevice, frameBuffers); - vkDestroyRenderPass(logicalDevice, renderPass, nullptr); - vkutils::destroySwapchainImageViews(logicalDevice, swapchainImageViews); - vkDestroySwapchainKHR(logicalDevice, swapchain, nullptr); - vkDestroyQueryPool(logicalDevice, queryPool, nullptr); - vkDestroyCommandPool(logicalDevice, commandPool, nullptr); +void OnlineSDFRenderer::destroy() noexcept { + if (logicalDevice != VK_NULL_HANDLE) { + destroyDeviceObjects(); + } vkDestroyDevice(logicalDevice, nullptr); + + // The surface belongs to the instance, so destroy it first. vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyInstance(instance, nullptr); - glfwDestroyWindow(window); - glfwTerminate(); + + if (window != nullptr) { + // GLFW window teardown comes last after Vulkan is gone. + glfwDestroyWindow(window); + glfwTerminate(); + } }