Skip to content

Commit 258a2ea

Browse files
SwapChainVk: reworked frame throttling (fix #666)
1 parent 75b63b7 commit 258a2ea

File tree

2 files changed

+21
-63
lines changed

2 files changed

+21
-63
lines changed

Graphics/GraphicsEngineVulkan/include/SwapChainVkImpl.hpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2022 Diligent Graphics LLC
2+
* Copyright 2019-2025 Diligent Graphics LLC
33
* Copyright 2015-2019 Egor Yusov
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -89,7 +89,6 @@ class SwapChainVkImpl final : public SwapChainBase<ISwapChainVk>
8989
void InitBuffersAndViews();
9090
VkResult AcquireNextImage(DeviceContextVkImpl* pDeviceCtxVk);
9191
void RecreateVulkanSwapchain(DeviceContextVkImpl* pImmediateCtxVk);
92-
void WaitForImageAcquiredFences();
9392
void ReleaseSwapChainResources(DeviceContextVkImpl* pImmediateCtxVk, bool DestroyVkSwapChain);
9493

9594
const NativeWindow m_Window;
@@ -114,19 +113,19 @@ class SwapChainVkImpl final : public SwapChainBase<ISwapChainVk>
114113

115114
std::vector<RefCntAutoPtr<ManagedSemaphore>> m_ImageAcquiredSemaphores;
116115
std::vector<RefCntAutoPtr<ManagedSemaphore>> m_DrawCompleteSemaphores;
117-
std::vector<VulkanUtilities::FenceWrapper> m_ImageAcquiredFences;
118116

119117
std::vector<RefCntAutoPtr<ITextureViewVk>, STDAllocatorRawMem<RefCntAutoPtr<ITextureViewVk>>> m_pBackBufferRTV;
120118

121119
std::vector<bool, STDAllocatorRawMem<bool>> m_SwapChainImagesInitialized;
122-
std::vector<bool, STDAllocatorRawMem<bool>> m_ImageAcquiredFenceSubmitted;
123120

124121
RefCntAutoPtr<ITextureViewVk> m_pDepthBufferDSV;
122+
RefCntAutoPtr<IFence> m_FrameCompleteFence;
125123

126124
Uint32 m_SemaphoreIndex = 0;
127125
uint32_t m_BackBufferIndex = 0;
128126
bool m_IsMinimized = false;
129127
bool m_VSyncEnabled = true;
128+
Uint32 m_FrameIndex = 1;
130129
};
131130

132131
} // namespace Diligent

Graphics/GraphicsEngineVulkan/src/SwapChainVkImpl.cpp

Lines changed: 18 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2024 Diligent Graphics LLC
2+
* Copyright 2019-2025 Diligent Graphics LLC
33
* Copyright 2015-2019 Egor Yusov
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -48,8 +48,7 @@ SwapChainVkImpl::SwapChainVkImpl(IReferenceCounters* pRefCounters,
4848
m_VulkanInstance {pRenderDeviceVk->GetVulkanInstance()},
4949
m_DesiredBufferCount {SCDesc.BufferCount},
5050
m_pBackBufferRTV (STD_ALLOCATOR_RAW_MEM(RefCntAutoPtr<ITextureView>, GetRawAllocator(), "Allocator for vector<RefCntAutoPtr<ITextureView>>")),
51-
m_SwapChainImagesInitialized (STD_ALLOCATOR_RAW_MEM(bool, GetRawAllocator(), "Allocator for vector<bool>")),
52-
m_ImageAcquiredFenceSubmitted(STD_ALLOCATOR_RAW_MEM(bool, GetRawAllocator(), "Allocator for vector<bool>"))
51+
m_SwapChainImagesInitialized (STD_ALLOCATOR_RAW_MEM(bool, GetRawAllocator(), "Allocator for vector<bool>"))
5352
// clang-format on
5453
{
5554
CreateSurface();
@@ -58,6 +57,10 @@ SwapChainVkImpl::SwapChainVkImpl(IReferenceCounters* pRefCounters,
5857
VkResult res = AcquireNextImage(pDeviceContextVk);
5958
DEV_CHECK_ERR(res == VK_SUCCESS, "Failed to acquire next image for the newly created swap chain");
6059
(void)res;
60+
61+
FenceDesc FenceCI;
62+
FenceCI.Name = "Swap chain frame complete fence";
63+
pRenderDeviceVk->CreateFence(FenceCI, &m_FrameCompleteFence);
6164
}
6265

6366
void SwapChainVkImpl::CreateSurface()
@@ -483,7 +486,6 @@ void SwapChainVkImpl::CreateVulkanSwapChain()
483486

484487
m_ImageAcquiredSemaphores.resize(swapchainImageCount);
485488
m_DrawCompleteSemaphores.resize(swapchainImageCount);
486-
m_ImageAcquiredFences.resize(swapchainImageCount);
487489
for (uint32_t i = 0; i < swapchainImageCount; ++i)
488490
{
489491
VkSemaphoreCreateInfo SemaphoreCI = {};
@@ -507,13 +509,6 @@ void SwapChainVkImpl::CreateVulkanSwapChain()
507509
VulkanUtilities::SemaphoreWrapper Semaphore = LogicalDevice.CreateSemaphore(SemaphoreCI, Name.c_str());
508510
ManagedSemaphore::Create(pRenderDeviceVk, std::move(Semaphore), Name.c_str(), &m_DrawCompleteSemaphores[i]);
509511
}
510-
511-
VkFenceCreateInfo FenceCI = {};
512-
513-
FenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
514-
FenceCI.pNext = nullptr;
515-
FenceCI.flags = 0;
516-
m_ImageAcquiredFences[i] = LogicalDevice.CreateFence(FenceCI);
517512
}
518513
}
519514

@@ -549,7 +544,6 @@ void SwapChainVkImpl::InitBuffersAndViews()
549544

550545
m_pBackBufferRTV.resize(m_SwapChainDesc.BufferCount);
551546
m_SwapChainImagesInitialized.resize(m_pBackBufferRTV.size(), false);
552-
m_ImageAcquiredFenceSubmitted.resize(m_pBackBufferRTV.size(), false);
553547

554548
uint32_t swapchainImageCount = m_SwapChainDesc.BufferCount;
555549
std::vector<VkImage> swapchainImages(swapchainImageCount);
@@ -615,39 +609,17 @@ VkResult SwapChainVkImpl::AcquireNextImage(DeviceContextVkImpl* pDeviceCtxVk)
615609
// applications can use fence to meter their frame generation work to match the
616610
// presentation rate.
617611

618-
// Explicitly make sure that there are no more pending frames in the command queue
619-
// than the number of the swap chain images.
620-
//
621-
// Nsc = 3 - number of the swap chain images
622-
//
623-
// N-Ns N-2 N-1 N (Current frame)
624-
// | | | |
625-
// |
626-
// Wait for this fence
627-
//
628-
// When acquiring swap chain image for frame N, we need to make sure that
629-
// frame N-Nsc has completed. To achieve that, we wait for the image acquire
630-
// fence for frame N-Nsc-1. Thus we will have no more than Nsc frames in the queue.
631-
Uint32 OldestSubmittedImageFenceInd = (m_SemaphoreIndex + 1u) % static_cast<Uint32>(m_ImageAcquiredFenceSubmitted.size());
632-
if (m_ImageAcquiredFenceSubmitted[OldestSubmittedImageFenceInd])
633-
{
634-
VkFence OldestSubmittedFence = m_ImageAcquiredFences[OldestSubmittedImageFenceInd];
635-
if (LogicalDevice.GetFenceStatus(OldestSubmittedFence) == VK_NOT_READY)
636-
{
637-
VkResult res = LogicalDevice.WaitForFences(1, &OldestSubmittedFence, VK_TRUE, UINT64_MAX);
638-
VERIFY_EXPR(res == VK_SUCCESS);
639-
(void)res;
640-
}
641-
LogicalDevice.ResetFence(OldestSubmittedFence);
642-
m_ImageAcquiredFenceSubmitted[OldestSubmittedImageFenceInd] = false;
612+
// vkAcquireNextImageKHR requires that the semaphore is not in use, so we must wait
613+
// for the frame (FrameIndex - BufferCount) to complete.
614+
// This also ensures that there are no more than BufferCount frames in flight at any time.
615+
if (m_FrameIndex > m_SwapChainDesc.BufferCount)
616+
{
617+
m_FrameCompleteFence->Wait(m_FrameIndex - m_SwapChainDesc.BufferCount);
643618
}
644619

645-
VkFence ImageAcquiredFence = m_ImageAcquiredFences[m_SemaphoreIndex];
646620
VkSemaphore ImageAcquiredSemaphore = m_ImageAcquiredSemaphores[m_SemaphoreIndex]->Get();
647621

648-
VkResult res = vkAcquireNextImageKHR(LogicalDevice.GetVkDevice(), m_VkSwapChain, UINT64_MAX, ImageAcquiredSemaphore, ImageAcquiredFence, &m_BackBufferIndex);
649-
650-
m_ImageAcquiredFenceSubmitted[m_SemaphoreIndex] = (res == VK_SUCCESS);
622+
VkResult res = vkAcquireNextImageKHR(LogicalDevice.GetVkDevice(), m_VkSwapChain, UINT64_MAX, ImageAcquiredSemaphore, VK_NULL_HANDLE, &m_BackBufferIndex);
651623
if (res == VK_SUCCESS)
652624
{
653625
// Next command in the device context must wait for the next image to be acquired.
@@ -696,6 +668,7 @@ void SwapChainVkImpl::Present(Uint32 SyncInterval)
696668
// The context can be empty if no render commands were issued by the app
697669
//VERIFY(pImmediateCtxVk->GetNumCommandsInCtx() != 0, "The context must not be flushed");
698670
pImmediateCtxVk->AddSignalSemaphore(m_DrawCompleteSemaphores[m_SemaphoreIndex]);
671+
pImmediateCtxVk->EnqueueSignal(m_FrameCompleteFence, m_FrameIndex++);
699672
}
700673

701674
pImmediateCtxVk->Flush();
@@ -772,20 +745,6 @@ void SwapChainVkImpl::Present(Uint32 SyncInterval)
772745
}
773746
}
774747

775-
void SwapChainVkImpl::WaitForImageAcquiredFences()
776-
{
777-
const VulkanUtilities::VulkanLogicalDevice& LogicalDevice = m_pRenderDevice.RawPtr<RenderDeviceVkImpl>()->GetLogicalDevice();
778-
for (size_t i = 0; i < m_ImageAcquiredFences.size(); ++i)
779-
{
780-
if (m_ImageAcquiredFenceSubmitted[i])
781-
{
782-
VkFence vkFence = m_ImageAcquiredFences[i];
783-
if (LogicalDevice.GetFenceStatus(vkFence) == VK_NOT_READY)
784-
LogicalDevice.WaitForFences(1, &vkFence, VK_TRUE, UINT64_MAX);
785-
}
786-
}
787-
}
788-
789748
void SwapChainVkImpl::ReleaseSwapChainResources(DeviceContextVkImpl* pImmediateCtxVk, bool DestroyVkSwapChain)
790749
{
791750
if (m_VkSwapChain == VK_NULL_HANDLE)
@@ -815,23 +774,23 @@ void SwapChainVkImpl::ReleaseSwapChainResources(DeviceContextVkImpl* pImmediateC
815774
// m_pBackBufferRTV[].
816775
pDeviceVk->IdleGPU();
817776

818-
// We need to explicitly wait for all submitted Image Acquired Fences to signal.
819777
// Just idling the GPU is not enough and results in validation warnings.
820778
// As a matter of fact, it is only required to check the fence status.
821-
WaitForImageAcquiredFences();
779+
if (m_FrameIndex > 1)
780+
{
781+
m_FrameCompleteFence->Wait(m_FrameIndex - 1);
782+
}
822783

823784
// All references to the swap chain must be released before it can be destroyed
824785
m_pBackBufferRTV.clear();
825786
m_SwapChainImagesInitialized.clear();
826-
m_ImageAcquiredFenceSubmitted.clear();
827787
m_pDepthBufferDSV.Release();
828788

829789
// We must wait until GPU is idled before destroying the fences as they
830790
// are destroyed immediately. The semaphores are managed and will be kept alive
831791
// by the device context they are submitted to.
832792
m_ImageAcquiredSemaphores.clear();
833793
m_DrawCompleteSemaphores.clear();
834-
m_ImageAcquiredFences.clear();
835794
m_SemaphoreIndex = 0;
836795

837796
if (DestroyVkSwapChain)

0 commit comments

Comments
 (0)