From e66fe223bf6182f7a15fb7bfcd734c6216ce6e0d Mon Sep 17 00:00:00 2001 From: dustinspace217 Date: Wed, 4 Nov 2026 09:21:32 -0800 Subject: [PATCH] Vulkan: use one rendering-finished semaphore per swap chain image (#7943) Each VulkanRenderFrame signaled its own rendering-finished semaphore and presented with it. The frame's fence only says the submit has finished, not that the presentation engine has consumed the semaphore, so the next submit on that frame could signal a semaphore that is still pending, which the validation layer reports as VUID-vkQueueSubmit-pSignalSemaphores-00067. Move the semaphore to the renderer, one per swap chain image, recreated with the swap chain, and pass the one for the acquired image to submitAndPresent. Acquiring an image again means its previous present has been consumed. Co-authored-by: Dustinspace217 --- code/graphics/vulkan/VulkanRenderFrame.cpp | 7 +++---- code/graphics/vulkan/VulkanRenderFrame.h | 3 +-- code/graphics/vulkan/VulkanRenderer.cpp | 9 +++++++++ code/graphics/vulkan/VulkanRenderer.h | 3 +++ code/graphics/vulkan/VulkanRendererLoop.cpp | 3 ++- code/graphics/vulkan/VulkanRendererSetup.cpp | 1 + 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/code/graphics/vulkan/VulkanRenderFrame.cpp b/code/graphics/vulkan/VulkanRenderFrame.cpp index 73739d030d..8338bf5751 100644 --- a/code/graphics/vulkan/VulkanRenderFrame.cpp +++ b/code/graphics/vulkan/VulkanRenderFrame.cpp @@ -10,7 +10,6 @@ VulkanRenderFrame::VulkanRenderFrame(vk::Device device, vk::SwapchainKHR swapCha constexpr vk::FenceCreateInfo fenceCreateInfo; m_imageAvailableSemaphore = device.createSemaphoreUnique(semaphoreCreateInfo); - m_renderingFinishedSemaphore = device.createSemaphoreUnique(semaphoreCreateInfo); m_frameInFlightFence = device.createFenceUnique(fenceCreateInfo); } bool VulkanRenderFrame::waitForFinish(uint64_t timeoutNs) @@ -82,7 +81,8 @@ SwapChainStatus VulkanRenderFrame::acquireSwapchainImage(uint32_t& outImageIndex } return SwapChainStatus::eSuccess; } -SwapChainStatus VulkanRenderFrame::submitAndPresent(const SCP_vector& cmdBuffers) +SwapChainStatus VulkanRenderFrame::submitAndPresent(const SCP_vector& cmdBuffers, + vk::Semaphore renderingFinished) { Assertion(!m_inFlight, "Cannot submit a frame for presentation when it is still in flight."); @@ -99,7 +99,7 @@ SwapChainStatus VulkanRenderFrame::submitAndPresent(const SCP_vector(cmdBuffers.size()); submitInfo.pCommandBuffers = cmdBuffers.data(); - const std::array signalSemaphores = {m_renderingFinishedSemaphore.get()}; + const std::array signalSemaphores = {renderingFinished}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores.data(); @@ -147,7 +147,6 @@ void VulkanRenderFrame::recreateSyncObjects() constexpr vk::SemaphoreCreateInfo semaphoreCreateInfo; m_imageAvailableSemaphore = m_device.createSemaphoreUnique(semaphoreCreateInfo); - m_renderingFinishedSemaphore = m_device.createSemaphoreUnique(semaphoreCreateInfo); } } // namespace graphics::vulkan diff --git a/code/graphics/vulkan/VulkanRenderFrame.h b/code/graphics/vulkan/VulkanRenderFrame.h index 8fe3e2e500..8e3dff77ab 100644 --- a/code/graphics/vulkan/VulkanRenderFrame.h +++ b/code/graphics/vulkan/VulkanRenderFrame.h @@ -30,7 +30,7 @@ class VulkanRenderFrame { void onFrameFinished(std::function finishFunc); - SwapChainStatus submitAndPresent(const SCP_vector& cmdBuffers); + SwapChainStatus submitAndPresent(const SCP_vector& cmdBuffers, vk::Semaphore renderingFinished); void updateSwapChain(vk::SwapchainKHR swapChain); @@ -50,7 +50,6 @@ class VulkanRenderFrame { vk::Queue m_presentQueue; vk::UniqueSemaphore m_imageAvailableSemaphore; - vk::UniqueSemaphore m_renderingFinishedSemaphore; vk::UniqueFence m_frameInFlightFence; SCP_vector> m_frameFinishedCallbacks; diff --git a/code/graphics/vulkan/VulkanRenderer.cpp b/code/graphics/vulkan/VulkanRenderer.cpp index 55a24154df..ef918cce14 100644 --- a/code/graphics/vulkan/VulkanRenderer.cpp +++ b/code/graphics/vulkan/VulkanRenderer.cpp @@ -430,6 +430,15 @@ void VulkanRenderer::createPresentSyncObjects() } m_swapChainImageRenderImage.resize(m_swapChainImages.size(), nullptr); + createRenderingFinishedSemaphores(); +} +void VulkanRenderer::createRenderingFinishedSemaphores() +{ + constexpr vk::SemaphoreCreateInfo semaphoreCreateInfo; + m_renderingFinishedSemaphores.clear(); + for (size_t i = 0; i < m_swapChainImages.size(); ++i) { + m_renderingFinishedSemaphores.push_back(m_device->createSemaphoreUnique(semaphoreCreateInfo)); + } } bool VulkanRenderer::readbackFramebuffer(ubyte** outPixels, uint32_t* outWidth, uint32_t* outHeight) diff --git a/code/graphics/vulkan/VulkanRenderer.h b/code/graphics/vulkan/VulkanRenderer.h index 608de49c8c..b0e05b65a0 100644 --- a/code/graphics/vulkan/VulkanRenderer.h +++ b/code/graphics/vulkan/VulkanRenderer.h @@ -451,6 +451,7 @@ class VulkanRenderer { void createCommandPool(const PhysicalDeviceValues& values); void createPresentSyncObjects(); + void createRenderingFinishedSemaphores(); void acquireNextSwapChainImage(); @@ -491,6 +492,8 @@ class VulkanRenderer { SCP_vector m_swapChainImageViews; SCP_vector m_swapChainFramebuffers; SCP_vector m_swapChainImageRenderImage; + // per image, not per frame: a frame's fence does not tell us when present has consumed the semaphore + SCP_vector m_renderingFinishedSemaphores; // HDR composition pipeline: the whole frame is rendered into these fp16 // images (via m_renderPass / m_swapChainFramebuffers) instead of directly diff --git a/code/graphics/vulkan/VulkanRendererLoop.cpp b/code/graphics/vulkan/VulkanRendererLoop.cpp index 673839fc66..58324d4265 100644 --- a/code/graphics/vulkan/VulkanRendererLoop.cpp +++ b/code/graphics/vulkan/VulkanRendererLoop.cpp @@ -218,7 +218,8 @@ void VulkanRenderer::flip() }); // Submit and present - auto presentStatus = m_frames[m_currentFrame]->submitAndPresent(m_currentCommandBuffers); + auto presentStatus = m_frames[m_currentFrame]->submitAndPresent(m_currentCommandBuffers, + m_renderingFinishedSemaphores[m_currentSwapChainImage].get()); if (presentStatus == SwapChainStatus::eSuboptimal || presentStatus == SwapChainStatus::eOutOfDate) { m_swapChainNeedsRecreation = true; diff --git a/code/graphics/vulkan/VulkanRendererSetup.cpp b/code/graphics/vulkan/VulkanRendererSetup.cpp index 9ce690fa02..80dc67b294 100644 --- a/code/graphics/vulkan/VulkanRendererSetup.cpp +++ b/code/graphics/vulkan/VulkanRendererSetup.cpp @@ -1294,6 +1294,7 @@ bool VulkanRenderer::recreateSwapChain() // Reset swap chain image tracking m_swapChainImageRenderImage.clear(); m_swapChainImageRenderImage.resize(m_swapChainImages.size(), nullptr); + createRenderingFinishedSemaphores(); m_previousSwapChainImage = UINT32_MAX; m_swapChainNeedsRecreation = false;