fix(peon_worker): Handle vulkan initialization errors in gpu recorder (#13035) 37997d7366 Co-authored-by: Arthur Vivian <arthur@rive.app>
diff --git a/.rive_head b/.rive_head index 609cb37..50fe441 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -ac4657c21c27ec4b99b415d54ca45a8b135fea63 +37997d7366fa70e190725e00f399a9dacd4c8a38
diff --git a/renderer/rive_vk_bootstrap/src/vulkan_frame_synchronizer.cpp b/renderer/rive_vk_bootstrap/src/vulkan_frame_synchronizer.cpp index c7cac00..d4a8ca2 100644 --- a/renderer/rive_vk_bootstrap/src/vulkan_frame_synchronizer.cpp +++ b/renderer/rive_vk_bootstrap/src/vulkan_frame_synchronizer.cpp
@@ -63,7 +63,7 @@ VK_CONFIRM_OR_RETURN_MSG( m_vkCreateFence(m_device, &fenceCreateInfo, nullptr, &sync.fence), - "Failed to create Vulkan fance"); + "Failed to create Vulkan fence"); VkCommandBufferAllocateInfo cbufferAllocateInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, @@ -104,19 +104,34 @@ VulkanFrameSynchronizer::~VulkanFrameSynchronizer() { + // The instance command function pointers are loaded first thing in the + // constructor, but if construction failed partway (e.g. an OOM creating a + // fence) this destructor still runs while some pointers are null. Guard + // every call so teardown of a partially-constructed object cannot segfault. + // // Note that the derived class will have already waited for the device to be // idle so we can safely destroy things here. for (auto& frame : m_inFlightFrames) { - destroySemaphore(frame.semaphore); - m_vkFreeCommandBuffers(m_device, - m_commandPool, - 1, - &frame.commandBuffer); - m_vkDestroyFence(m_device, frame.fence, nullptr); + if (m_vkDestroySemaphore != nullptr) + { + destroySemaphore(frame.semaphore); + } + if (m_vkFreeCommandBuffers != nullptr && + m_commandPool != VK_NULL_HANDLE) + { + m_vkFreeCommandBuffers(m_device, + m_commandPool, + 1, + &frame.commandBuffer); + } + if (m_vkDestroyFence != nullptr) + { + m_vkDestroyFence(m_device, frame.fence, nullptr); + } } - if (m_commandPool != VK_NULL_HANDLE) + if (m_commandPool != VK_NULL_HANDLE && m_vkDestroyCommandPool != nullptr) { m_vkDestroyCommandPool(m_device, m_commandPool, nullptr); }
diff --git a/renderer/rive_vk_bootstrap/src/vulkan_headless_frame_synchronizer.cpp b/renderer/rive_vk_bootstrap/src/vulkan_headless_frame_synchronizer.cpp index 503cd0e..31e2dd4 100644 --- a/renderer/rive_vk_bootstrap/src/vulkan_headless_frame_synchronizer.cpp +++ b/renderer/rive_vk_bootstrap/src/vulkan_headless_frame_synchronizer.cpp
@@ -83,8 +83,15 @@ VulkanHeadlessFrameSynchronizer::~VulkanHeadlessFrameSynchronizer() { - // Don't do anything until everything is flushed through. - m_vkDeviceWaitIdle(vkDevice()); + // Guard against partial construction: if the base constructor failed (e.g. + // an OOM creating a fence), this destructor still runs even though the + // derived device function pointers were never loaded. Calling the null + // m_vkDeviceWaitIdle here would segfault and crash the whole process. + if (m_vkDeviceWaitIdle != nullptr) + { + // Don't do anything until everything is flushed through. + m_vkDeviceWaitIdle(vkDevice()); + } } bool VulkanHeadlessFrameSynchronizer::isFrameStarted() const