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
6366void 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-
789748void 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