mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Fixes acquire and submit semaphores syncronization
I forgot to finish CSubmitScheduler synchronization in
7c84c23114. Even ++m_FrameID was
forgotten. And it worked at the time. A proper synchronization is
described in renderer/backend/Vulkan/SwapChain.h.
This commit is contained in:
parent
4d83aa28e5
commit
07e5ad5b23
4 changed files with 113 additions and 52 deletions
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -58,17 +58,6 @@ std::unique_ptr<CSubmitScheduler> CSubmitScheduler::Create(
|
|||
submitScheduler->m_Fences.push_back({fence, INVALID_SUBMIT_HANDLE});
|
||||
}
|
||||
|
||||
for (FrameObject& frameObject : submitScheduler->m_FrameObjects)
|
||||
{
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo{};
|
||||
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
RETURN_NULLPTR_IF_NOT_VK_SUCCESS(vkCreateSemaphore(
|
||||
device->GetVkDevice(), &semaphoreCreateInfo, nullptr, &frameObject.acquireImageSemaphore));
|
||||
|
||||
RETURN_NULLPTR_IF_NOT_VK_SUCCESS(vkCreateSemaphore(
|
||||
device->GetVkDevice(), &semaphoreCreateInfo, nullptr, &frameObject.submitDone));
|
||||
}
|
||||
|
||||
submitScheduler->m_AcquireCommandContext = CRingCommandContext::Create(
|
||||
device, NUMBER_OF_FRAMES_IN_FLIGHT, queueFamilyIndex, *submitScheduler);
|
||||
if (!submitScheduler->m_AcquireCommandContext)
|
||||
|
|
@ -103,48 +92,39 @@ CSubmitScheduler::~CSubmitScheduler()
|
|||
for (Fence& fence : m_Fences)
|
||||
if (fence.value != VK_NULL_HANDLE)
|
||||
vkDestroyFence(device, fence.value, nullptr);
|
||||
|
||||
for (FrameObject& frameObject : m_FrameObjects)
|
||||
{
|
||||
if (frameObject.acquireImageSemaphore != VK_NULL_HANDLE)
|
||||
vkDestroySemaphore(device, frameObject.acquireImageSemaphore, nullptr);
|
||||
|
||||
if (frameObject.submitDone != VK_NULL_HANDLE)
|
||||
vkDestroySemaphore(device, frameObject.submitDone, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool CSubmitScheduler::AcquireNextImage(CSwapChain& swapChain)
|
||||
bool CSubmitScheduler::AcquireNextImage(CSwapChain& swapChain, VkSemaphore acquireImageSemaphore)
|
||||
{
|
||||
if (m_DebugWaitIdleBeforeAcquire)
|
||||
vkDeviceWaitIdle(m_Device->GetVkDevice());
|
||||
|
||||
FrameObject& frameObject = m_FrameObjects[m_FrameID % m_FrameObjects.size()];
|
||||
if (!swapChain.AcquireNextImage(frameObject.acquireImageSemaphore))
|
||||
if (!swapChain.AcquireNextImage())
|
||||
return false;
|
||||
swapChain.SubmitCommandsAfterAcquireNextImage(*m_AcquireCommandContext);
|
||||
|
||||
m_NextWaitSemaphore = frameObject.acquireImageSemaphore;
|
||||
m_NextWaitSemaphore = acquireImageSemaphore;
|
||||
m_NextWaitDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
m_AcquireCommandContext->Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSubmitScheduler::Present(CSwapChain& swapChain)
|
||||
CSubmitScheduler::SubmitHandle CSubmitScheduler::Present(CSwapChain& swapChain, VkSemaphore submitDone)
|
||||
{
|
||||
FrameObject& frameObject = m_FrameObjects[m_FrameID % m_FrameObjects.size()];
|
||||
swapChain.SubmitCommandsBeforePresent(*m_PresentCommandContext);
|
||||
m_NextSubmitSignalSemaphore = frameObject.submitDone;
|
||||
m_NextSubmitSignalSemaphore = submitDone;
|
||||
m_PresentCommandContext->Flush();
|
||||
Flush();
|
||||
const SubmitHandle submitHandle{Flush()};
|
||||
|
||||
if (m_DebugWaitIdleBeforePresent)
|
||||
vkDeviceWaitIdle(m_Device->GetVkDevice());
|
||||
|
||||
swapChain.Present(frameObject.submitDone, m_Queue);
|
||||
swapChain.Present(submitDone, m_Queue);
|
||||
|
||||
if (m_DebugWaitIdleAfterPresent)
|
||||
vkDeviceWaitIdle(m_Device->GetVkDevice());
|
||||
|
||||
return submitHandle;
|
||||
}
|
||||
|
||||
CSubmitScheduler::SubmitHandle CSubmitScheduler::Submit(VkCommandBuffer commandBuffer)
|
||||
|
|
@ -172,7 +152,7 @@ void CSubmitScheduler::WaitUntilFree(const SubmitHandle handle)
|
|||
}
|
||||
}
|
||||
|
||||
void CSubmitScheduler::Flush()
|
||||
CSubmitScheduler::SubmitHandle CSubmitScheduler::Flush()
|
||||
{
|
||||
ENSURE(!m_SubmittedCommandBuffers.empty());
|
||||
|
||||
|
|
@ -208,6 +188,8 @@ void CSubmitScheduler::Flush()
|
|||
m_NextSubmitSignalSemaphore = VK_NULL_HANDLE;
|
||||
|
||||
m_SubmittedCommandBuffers.clear();
|
||||
|
||||
return fence.lastUsedHandle;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -53,9 +53,9 @@ public:
|
|||
CDevice* device, const uint32_t queueFamilyIndex, VkQueue queue);
|
||||
~CSubmitScheduler();
|
||||
|
||||
bool AcquireNextImage(CSwapChain& swapChain);
|
||||
bool AcquireNextImage(CSwapChain& swapChain, VkSemaphore acquireImageSemaphore);
|
||||
|
||||
void Present(CSwapChain& swapChain);
|
||||
SubmitHandle Present(CSwapChain& swapChain, VkSemaphore submitDone);
|
||||
|
||||
SubmitHandle Submit(VkCommandBuffer commandBuffer);
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ public:
|
|||
|
||||
uint32_t GetFrameID() const { return m_FrameID; }
|
||||
|
||||
void Flush();
|
||||
SubmitHandle Flush();
|
||||
|
||||
private:
|
||||
CSubmitScheduler(CDevice* device, VkQueue queue);
|
||||
|
|
@ -90,17 +90,6 @@ private:
|
|||
};
|
||||
std::queue<SubmittedHandle> m_SubmittedHandles;
|
||||
|
||||
// We can't reuse frame data immediately after present because it might
|
||||
// still be processing on GPU.
|
||||
struct FrameObject
|
||||
{
|
||||
// We need to wait for the image on GPU to draw to it.
|
||||
VkSemaphore acquireImageSemaphore = VK_NULL_HANDLE;
|
||||
// We need to present only after all submit work is done.
|
||||
VkSemaphore submitDone = VK_NULL_HANDLE;
|
||||
};
|
||||
std::array<FrameObject, NUMBER_OF_FRAMES_IN_FLIGHT> m_FrameObjects;
|
||||
|
||||
VkSemaphore m_NextWaitSemaphore = VK_NULL_HANDLE;
|
||||
VkPipelineStageFlags m_NextWaitDstStageMask = 0;
|
||||
VkSemaphore m_NextSubmitSignalSemaphore = VK_NULL_HANDLE;
|
||||
|
|
|
|||
|
|
@ -248,6 +248,37 @@ std::unique_ptr<CSwapChain> CSwapChain::Create(
|
|||
swapChainCreateInfo.imageUsage, swapChainWidth, swapChainHeight);
|
||||
}
|
||||
|
||||
// +1 to minimize waiting on our side instead of vkAcquireNextImageKHR.
|
||||
for (size_t index{0}; index < NUMBER_OF_FRAMES_IN_FLIGHT + 1; ++index)
|
||||
{
|
||||
VkSemaphore semaphore{VK_NULL_HANDLE};
|
||||
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo{};
|
||||
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
|
||||
RETURN_NULLPTR_IF_NOT_VK_SUCCESS(vkCreateSemaphore(
|
||||
device->GetVkDevice(), &semaphoreCreateInfo, nullptr, &semaphore));
|
||||
snprintf(nameBuffer, std::size(nameBuffer), "SwapChainAcquireSemaphore #%zu", index);
|
||||
device->SetObjectName(VK_OBJECT_TYPE_SEMAPHORE, semaphore, nameBuffer);
|
||||
|
||||
swapChain->m_FrameObjects.emplace_back(FrameObject{semaphore, CSubmitScheduler::INVALID_SUBMIT_HANDLE});
|
||||
}
|
||||
|
||||
for (size_t index{0}; index < imageCount; ++index)
|
||||
{
|
||||
VkSemaphore semaphore{VK_NULL_HANDLE};
|
||||
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo{};
|
||||
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
|
||||
RETURN_NULLPTR_IF_NOT_VK_SUCCESS(vkCreateSemaphore(
|
||||
device->GetVkDevice(), &semaphoreCreateInfo, nullptr, &semaphore));
|
||||
snprintf(nameBuffer, std::size(nameBuffer), "SwapChainSubmitSemaphore #%zu", index);
|
||||
device->SetObjectName(VK_OBJECT_TYPE_SEMAPHORE, semaphore, nameBuffer);
|
||||
|
||||
swapChain->m_SubmitSemaphores.emplace_back(semaphore);
|
||||
}
|
||||
|
||||
swapChain->m_IsValid = true;
|
||||
|
||||
return swapChain;
|
||||
|
|
@ -257,13 +288,28 @@ CSwapChain::CSwapChain() = default;
|
|||
|
||||
CSwapChain::~CSwapChain()
|
||||
{
|
||||
VkDevice device{m_Device->GetVkDevice()};
|
||||
|
||||
m_Backbuffers.clear();
|
||||
|
||||
m_Textures.clear();
|
||||
m_DepthTexture.reset();
|
||||
|
||||
if (m_SwapChain != VK_NULL_HANDLE)
|
||||
vkDestroySwapchainKHR(m_Device->GetVkDevice(), m_SwapChain, nullptr);
|
||||
vkDestroySwapchainKHR(device, m_SwapChain, nullptr);
|
||||
|
||||
for (FrameObject& frameObject : m_FrameObjects)
|
||||
{
|
||||
if (frameObject.submitHandle != CSubmitScheduler::INVALID_SUBMIT_HANDLE)
|
||||
m_SubmitScheduler->WaitUntilFree(frameObject.submitHandle);
|
||||
|
||||
if (frameObject.acquireImageSemaphore != VK_NULL_HANDLE)
|
||||
vkDestroySemaphore(device, frameObject.acquireImageSemaphore, nullptr);
|
||||
}
|
||||
|
||||
for (VkSemaphore& semaphore : m_SubmitSemaphores)
|
||||
if (semaphore != VK_NULL_HANDLE)
|
||||
vkDestroySemaphore(device, semaphore, nullptr);
|
||||
}
|
||||
|
||||
IDevice* CSwapChain::GetDevice()
|
||||
|
|
@ -276,16 +322,29 @@ bool CSwapChain::AcquireNextBackbuffer()
|
|||
ENSURE(m_IsValid);
|
||||
|
||||
PROFILE3("AcquireNextBackbuffer");
|
||||
return m_SubmitScheduler->AcquireNextImage(*this);
|
||||
|
||||
// We need to make sure we can reuse the semaphore.
|
||||
FrameObject& frameObject{m_FrameObjects[m_FrameID % m_FrameObjects.size()]};
|
||||
if (frameObject.submitHandle != CSubmitScheduler::INVALID_SUBMIT_HANDLE)
|
||||
{
|
||||
PROFILE3("WaitAcquireSemaphore");
|
||||
m_SubmitScheduler->WaitUntilFree(frameObject.submitHandle);
|
||||
}
|
||||
|
||||
return m_SubmitScheduler->AcquireNextImage(*this, frameObject.acquireImageSemaphore);
|
||||
}
|
||||
|
||||
void CSwapChain::Present()
|
||||
{
|
||||
ENSURE(m_IsValid);
|
||||
ENSURE(m_CurrentImageIndex != std::numeric_limits<uint32_t>::max());
|
||||
ENSURE(m_CurrentImageIndex < m_SubmitSemaphores.size());
|
||||
|
||||
PROFILE3("Present");
|
||||
m_SubmitScheduler->Present(*this);
|
||||
FrameObject& frameObject{m_FrameObjects[m_FrameID % m_FrameObjects.size()]};
|
||||
frameObject.submitHandle = m_SubmitScheduler->Present(*this, m_SubmitSemaphores[m_CurrentImageIndex]);
|
||||
m_Device->OnPresent();
|
||||
++m_FrameID;
|
||||
}
|
||||
|
||||
size_t CSwapChain::SwapChainBackbuffer::BackbufferKeyHash::operator()(const BackbufferKey& key) const
|
||||
|
|
@ -304,11 +363,12 @@ CSwapChain::SwapChainBackbuffer::SwapChainBackbuffer(SwapChainBackbuffer&& other
|
|||
|
||||
CSwapChain::SwapChainBackbuffer& CSwapChain::SwapChainBackbuffer::operator=(SwapChainBackbuffer&& other) = default;
|
||||
|
||||
bool CSwapChain::AcquireNextImage(VkSemaphore acquireImageSemaphore)
|
||||
bool CSwapChain::AcquireNextImage()
|
||||
{
|
||||
ENSURE(m_IsValid);
|
||||
ENSURE(m_CurrentImageIndex == std::numeric_limits<uint32_t>::max());
|
||||
|
||||
VkSemaphore acquireImageSemaphore{m_FrameObjects[m_FrameID % m_FrameObjects.size()].acquireImageSemaphore};
|
||||
const VkResult acquireResult = vkAcquireNextImageKHR(
|
||||
m_Device->GetVkDevice(), m_SwapChain, std::numeric_limits<uint64_t>::max(),
|
||||
acquireImageSemaphore,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "renderer/backend/IFramebuffer.h"
|
||||
#include "renderer/backend/ISwapChain.h"
|
||||
#include "renderer/backend/vulkan/SubmitScheduler.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
|
@ -33,7 +34,6 @@
|
|||
namespace Renderer::Backend::Vulkan { class CDevice; }
|
||||
namespace Renderer::Backend::Vulkan { class CFramebuffer; }
|
||||
namespace Renderer::Backend::Vulkan { class CRingCommandContext; }
|
||||
namespace Renderer::Backend::Vulkan { class CSubmitScheduler; }
|
||||
namespace Renderer::Backend::Vulkan { class CTexture; }
|
||||
|
||||
namespace Renderer
|
||||
|
|
@ -66,7 +66,7 @@ public:
|
|||
|
||||
VkSwapchainKHR GetVkSwapchain() { return m_SwapChain; }
|
||||
|
||||
bool AcquireNextImage(VkSemaphore acquireImageSemaphore);
|
||||
bool AcquireNextImage();
|
||||
void SubmitCommandsAfterAcquireNextImage(
|
||||
CRingCommandContext& commandContext);
|
||||
void SubmitCommandsBeforePresent(
|
||||
|
|
@ -103,6 +103,36 @@ private:
|
|||
std::unique_ptr<CTexture> m_DepthTexture;
|
||||
VkFormat m_ImageFormat = VK_FORMAT_UNDEFINED;
|
||||
|
||||
uint32_t m_FrameID{0};
|
||||
|
||||
// We can't reuse the same acquire semaphore immediately after present
|
||||
// because it might still be processing on GPU as vkQueuePresentKHR doesn't
|
||||
// have to be blocking.
|
||||
// We need to wait for the image on GPU to draw to it.
|
||||
struct FrameObject
|
||||
{
|
||||
VkSemaphore acquireImageSemaphore{VK_NULL_HANDLE};
|
||||
// We need to know when we can reuse the semaphore.
|
||||
CSubmitScheduler::SubmitHandle submitHandle{CSubmitScheduler::INVALID_SUBMIT_HANDLE};
|
||||
};
|
||||
std::vector<FrameObject> m_FrameObjects;
|
||||
|
||||
// The number of submit semaphores should be equal to the number of images
|
||||
// in the swapchain. We could use NUMBER_OF_FRAMES_IN_FLIGHT of objects but
|
||||
// it might be not safe. Since vkQueuePresentKHR doesn't provide a way to
|
||||
// tell that a semaphore was signaled.
|
||||
//
|
||||
// A possible situation, list of acquired indices:
|
||||
// 0, 1, 2, 0, 1, 0, 1
|
||||
// ^
|
||||
// In theory in the end we might still have a semaphore in use for the
|
||||
// 2nd swapchain image.
|
||||
//
|
||||
// See also:
|
||||
// https://docs.vulkan.org/guide/latest/swapchain_semaphore_reuse.html
|
||||
// We need to present only after all submit work is done.
|
||||
std::vector<VkSemaphore> m_SubmitSemaphores;
|
||||
|
||||
struct SwapChainBackbuffer
|
||||
{
|
||||
using BackbufferKey = std::tuple<
|
||||
|
|
|
|||
Loading…
Reference in a new issue