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 <[email protected]>
This commit is contained in:
dustinspace217andDustinspace217 authored and GitHub committed 2026-11-04 18:21:32 +01:00
1 parent e5b591141c
commit e66fe223bf
6 files changed
+19 -7

No files matched your search

+3 -4
View File
@@ -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<vk::CommandBuffer>& cmdBuffers)
SwapChainStatus VulkanRenderFrame::submitAndPresent(const SCP_vector<vk::CommandBuffer>& 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<vk::Command
submitInfo.commandBufferCount = static_cast<uint32_t>(cmdBuffers.size());
submitInfo.pCommandBuffers = cmdBuffers.data();
const std::array<vk::Semaphore, 1> signalSemaphores = {m_renderingFinishedSemaphore.get()};
const std::array<vk::Semaphore, 1> 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
+1 -2
View File
@@ -30,7 +30,7 @@ class VulkanRenderFrame {
void onFrameFinished(std::function<void()> finishFunc);
SwapChainStatus submitAndPresent(const SCP_vector<vk::CommandBuffer>& cmdBuffers);
SwapChainStatus submitAndPresent(const SCP_vector<vk::CommandBuffer>& 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<std::function<void()>> m_frameFinishedCallbacks;
+9
View File
@@ -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)
+3
View File
@@ -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<vk::UniqueImageView> m_swapChainImageViews;
SCP_vector<vk::UniqueFramebuffer> m_swapChainFramebuffers;
SCP_vector<VulkanRenderFrame*> m_swapChainImageRenderImage;
// per image, not per frame: a frame's fence does not tell us when present has consumed the semaphore
SCP_vector<vk::UniqueSemaphore> m_renderingFinishedSemaphores;
// HDR composition pipeline: the whole frame is rendered into these fp16
// images (via m_renderPass / m_swapChainFramebuffers) instead of directly
+2 -1
View File
@@ -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;
@@ -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;