diff options
| author | Egor Yusov <egor.yusov@gmail.com> | 2018-09-28 16:00:28 +0000 |
|---|---|---|
| committer | Egor Yusov <egor.yusov@gmail.com> | 2018-09-28 16:00:28 +0000 |
| commit | 88d66ba24a6d484fd90609a3a7031a781974e266 (patch) | |
| tree | 667fd91ea4716e09fa067880e651345def8237d7 /Graphics/GraphicsEngineVulkan | |
| parent | Reworked command buffer recycling in Vk backend using release queues (diff) | |
| download | DiligentCore-88d66ba24a6d484fd90609a3a7031a781974e266.tar.gz DiligentCore-88d66ba24a6d484fd90609a3a7031a781974e266.zip | |
Some minor updates to resource lifetime management in Vk backend + comments
Diffstat (limited to 'Graphics/GraphicsEngineVulkan')
15 files changed, 232 insertions, 123 deletions
diff --git a/Graphics/GraphicsEngineVulkan/include/CommandPoolManager.h b/Graphics/GraphicsEngineVulkan/include/CommandPoolManager.h index f1a62e14..f8e406d2 100644 --- a/Graphics/GraphicsEngineVulkan/include/CommandPoolManager.h +++ b/Graphics/GraphicsEngineVulkan/include/CommandPoolManager.h @@ -25,6 +25,7 @@ #include <deque> #include <mutex> +#include <atomic> #include "STDAllocator.h" #include "VulkanUtilities/VulkanLogicalDevice.h" @@ -41,10 +42,10 @@ public: uint32_t queueFamilyIndex, VkCommandPoolCreateFlags flags)noexcept; - CommandPoolManager (const CommandPoolManager&) = delete; - CommandPoolManager (CommandPoolManager&&) = delete; - CommandPoolManager& operator = (const CommandPoolManager&) = delete; - CommandPoolManager& operator = (CommandPoolManager&&) = delete; + CommandPoolManager (const CommandPoolManager&) = delete; + CommandPoolManager ( CommandPoolManager&&) = delete; + CommandPoolManager& operator = (const CommandPoolManager&) = delete; + CommandPoolManager& operator = ( CommandPoolManager&&) = delete; ~CommandPoolManager(); @@ -57,13 +58,17 @@ public: void DestroyPools(); private: - RenderDeviceVkImpl& m_DeviceVkImpl; - const std::string m_Name; - const uint32_t m_QueueFamilyIndex; - const VkCommandPoolCreateFlags m_CmdPoolFlags; + RenderDeviceVkImpl& m_DeviceVkImpl; + const std::string m_Name; + const uint32_t m_QueueFamilyIndex; + const VkCommandPoolCreateFlags m_CmdPoolFlags; - std::mutex m_Mutex; + std::mutex m_Mutex; std::deque< VulkanUtilities::CommandPoolWrapper, STDAllocatorRawMem<VulkanUtilities::CommandPoolWrapper> > m_CmdPools; + +#ifdef DEVELOPMENT + std::atomic_int32_t m_AllocatedPoolCounter = 0; +#endif }; } diff --git a/Graphics/GraphicsEngineVulkan/include/DescriptorPoolManager.h b/Graphics/GraphicsEngineVulkan/include/DescriptorPoolManager.h index 0ea4f7eb..1f52605d 100644 --- a/Graphics/GraphicsEngineVulkan/include/DescriptorPoolManager.h +++ b/Graphics/GraphicsEngineVulkan/include/DescriptorPoolManager.h @@ -29,6 +29,7 @@ #include <vector> #include <deque> #include <mutex> +#include <atomic> #include "VulkanUtilities/VulkanObjectWrappers.h" namespace Diligent @@ -109,7 +110,18 @@ private: DescriptorSetAllocator* DescrSetAllocator = nullptr; }; + // The class manages pool of descriptor set pools +// ______________________________ +// | | +// | DescriptorPoolManager | +// | | +// | | Pool[0] | Pool[1] | ... | +// |______________________________| +// | A +// GetPool() | | FreePool() +// V | +// class DescriptorPoolManager { public: @@ -148,8 +160,13 @@ protected: std::mutex m_Mutex; std::deque< VulkanUtilities::DescriptorPoolWrapper > m_Pools; + +#ifdef DEVELOPMENT + std::atomic_int32_t m_AllocatedPoolCounter = 0; +#endif }; + // The class allocates descriptors from the main descriptor pool. // Descriptors can be released and returned to the pool class DescriptorSetAllocator : public DescriptorPoolManager @@ -171,11 +188,28 @@ private: void FreeDescriptorSet(VkDescriptorSet Set, VkDescriptorPool Pool, Uint64 QueueMask); }; -// The class manages dynamic descriptor sets. It first requests descriptor pool from -// the global manager and performs allocations from this pool. When space in the pool is exhausted, -// the class requests new pool. + +// DynamicDescriptorSetAllocator manages dynamic descriptor sets. It first requests descriptor pool from +// the global manager and allocates descriptor sets from this pool. When space in the pool is exhausted, +// the class requests a new pool. // The class is not thread-safe as device contexts must not be used in multiple threads at the same time. -// Entire pools are recycled at the end of every frame. +// All allocated pools are recycled at the end of every frame. +// ____________________________________________________________________________ +// | | +// | DynamicDescriptorSetAllocator | +// | | +// | || DescriptorPool[0] | DescriptorPool[1] | ... | DescriptorPool[N] || | +// |__________|_________________________________________________________________| +// | A | +// | | | +// |Allocate() GetPool()| |FreePool() +// | _____|___________________V____ +// V | | +// VkDescriptorSet | DescriptorPoolManager | +// | | +// | |Global dynamic buffer| | +// |______________________________| +// class DynamicDescriptorSetAllocator { public: diff --git a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h index 086e0bbd..b82ba8be 100644 --- a/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/DeviceContextVkImpl.h @@ -163,8 +163,8 @@ public: return m_CommandBuffer; } - virtual void FinishFrame(bool ForceRelease)override final; - void FinishFrame(); + // TODO: remove ForceRelease + virtual void FinishFrame(bool /*ForceRelease*/)override final; VkDescriptorSet AllocateDynamicDescriptorSet(VkDescriptorSetLayout SetLayout) { diff --git a/Graphics/GraphicsEngineVulkan/include/RenderDeviceVkImpl.h b/Graphics/GraphicsEngineVulkan/include/RenderDeviceVkImpl.h index 4814f359..48cbc677 100644 --- a/Graphics/GraphicsEngineVulkan/include/RenderDeviceVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/RenderDeviceVkImpl.h @@ -46,7 +46,6 @@ #include "CommandPoolManager.h" #include "VulkanDynamicHeap.h" -/// Namespace for the Direct3D11 implementation of the graphics engine namespace Diligent { @@ -89,8 +88,8 @@ public: virtual void CreateBufferFromVulkanResource(VkBuffer vkBuffer, const BufferDesc& BuffDesc, IBuffer** ppBuffer)override final; - // Idles GPU - void IdleGPU(bool ReleaseStaleObjects); + // Idles the GPU + void IdleGPU(); // pImmediateCtx parameter is only used to make sure the command buffer is submitted from the immediate context // The method returns fence value associated with the submitted command buffer Uint64 ExecuteCommandBuffer(Uint32 QueueIndex, const VkSubmitInfo &SubmitInfo, class DeviceContextVkImpl* pImmediateCtx, std::vector<std::pair<Uint64, RefCntAutoPtr<IFence> > >* pSignalFences); @@ -98,8 +97,7 @@ public: void AllocateTransientCmdPool(VulkanUtilities::CommandPoolWrapper& CmdPool, VkCommandBuffer& vkCmdBuff, const Char* DebugPoolName = nullptr); void ExecuteAndDisposeTransientCmdBuff(Uint32 QueueIndex, VkCommandBuffer vkCmdBuff, VulkanUtilities::CommandPoolWrapper&& CmdPool); - void FinishFrame(bool ReleaseAllResources); - virtual void FinishFrame()override final { FinishFrame(false); } + virtual void ReleaseStaleResources(bool ForceRelease = false)override final; DescriptorSetAllocation AllocateDescriptorSet(Uint64 CommandQueueMask, VkDescriptorSetLayout SetLayout) { @@ -107,11 +105,11 @@ public: } DescriptorPoolManager& GetDynamicDescriptorPool(){return m_DynamicDescriptorPool;} - std::shared_ptr<const VulkanUtilities::VulkanInstance> GetVulkanInstance()const{return m_VulkanInstance;} - const VulkanUtilities::VulkanPhysicalDevice& GetPhysicalDevice()const {return *m_PhysicalDevice;} - const VulkanUtilities::VulkanLogicalDevice& GetLogicalDevice () {return *m_LogicalVkDevice;} - FramebufferCache& GetFramebufferCache(){return m_FramebufferCache;} - RenderPassCache& GetRenderPassCache() {return m_RenderPassCache;} + std::shared_ptr<const VulkanUtilities::VulkanInstance> GetVulkanInstance()const { return m_VulkanInstance;} + const VulkanUtilities::VulkanPhysicalDevice& GetPhysicalDevice()const { return *m_PhysicalDevice;} + const VulkanUtilities::VulkanLogicalDevice& GetLogicalDevice () { return *m_LogicalVkDevice;} + FramebufferCache& GetFramebufferCache() { return m_FramebufferCache;} + RenderPassCache& GetRenderPassCache() { return m_RenderPassCache;} VulkanUtilities::VulkanMemoryAllocation AllocateMemory(const VkMemoryRequirements& MemReqs, VkMemoryPropertyFlags MemoryProperties) { diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanDynamicHeap.h b/Graphics/GraphicsEngineVulkan/include/VulkanDynamicHeap.h index 10ccad1c..4282bd35 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanDynamicHeap.h +++ b/Graphics/GraphicsEngineVulkan/include/VulkanDynamicHeap.h @@ -55,6 +55,7 @@ struct VulkanDynamicAllocation VulkanDynamicAllocation (const VulkanDynamicAllocation&) = delete; VulkanDynamicAllocation& operator = (const VulkanDynamicAllocation&) = delete; + VulkanDynamicAllocation (VulkanDynamicAllocation&& rhs)noexcept : pDynamicMemMgr(rhs.pDynamicMemMgr), AlignedOffset (rhs.AlignedOffset), @@ -95,10 +96,19 @@ struct VulkanDynamicAllocation }; - +// VulkanDynamicMemoryManager manages allocation of master blocks from global dynamic buffer +// +// _______________________________________________________________________ +// | | +// | VulkanDynamicMemoryManager | +// | | +// | || - - - - - - - - - - Dynamic Memory Buffer- - - - - - - - - -|| | +// | || MasterBlock[0] | MasterBlock[1] | ... | MasterBlock[N-1] || | +// |_______________________________________________________________________| +// // We cannot use global memory manager for dynamic resources because they // need to use the same Vulkan buffer -class VulkanDynamicMemoryManager : public DynamicHeap::MasterBlockListBasedManager // or MasterBlockRingBufferBasedManager +class VulkanDynamicMemoryManager : public DynamicHeap::MasterBlockListBasedManager { public: using TBase = DynamicHeap::MasterBlockListBasedManager; @@ -137,6 +147,32 @@ private: }; + +// Dynamic heap is used by a device context to allocate dynamic space when +// mapping a buffer or a texture. This is very similar to upload heap, +// however dynamic heap uses special persistently mapped buffer while +// upload heap uses global memory manager. +// +// The heap allocates master blocks from the global dynamic memory manager. +// The pages are released and returned to the manager at the end of every frame. +// +// _______________________________________________________________________________________________________________________________ +// | | +// | VulkanDynamicHeap | +// | | +// | || - - - - - - - - - MasterBlock[0]- - - - - - - - -|| || - - - - - - - - - MasterBlock[1]- - - - - - - - -|| | +// | || Allocation0 | Allocation1 | ... | AllocationN || || Allocation0 | Allocation1 | ... | AllocationM || ... | +// |__________|____________________________________________________________________________________________________________________| +// | A | +// | | | +// |Allocate() AllocateMasterBlock()| |FinishFrame() +// | ______|___________________V____ +// V | | +// | VulkanDynamicMemoryManager | +// | | +// | |Global dynamic buffer| | +// |______________________________| +// class VulkanDynamicHeap { public: @@ -156,11 +192,13 @@ public: VulkanDynamicAllocation Allocate(Uint32 SizeInBytes, Uint32 Alignment); // CmdQueueMask indicates which command queues the allocations from this heap were used // with during the last frame - void FinishFrame(RenderDeviceVkImpl& DeviceVkImpl, Uint64 CmdQueueMask); + void ReleaseMasterBlocks(RenderDeviceVkImpl& DeviceVkImpl, Uint64 CmdQueueMask); using OffsetType = VulkanDynamicMemoryManager::OffsetType; using MasterBlock = VulkanDynamicMemoryManager::MasterBlock; static constexpr OffsetType InvalidOffset = VulkanDynamicMemoryManager::InvalidOffset; + + size_t GetAllocatedMasterBlockCount()const {return m_MasterBlocks.size();} private: VulkanDynamicMemoryManager& m_DynamicMemMgr; diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUploadHeap.h b/Graphics/GraphicsEngineVulkan/include/VulkanUploadHeap.h index 1b05b2f0..67c118c5 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanUploadHeap.h +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUploadHeap.h @@ -25,25 +25,52 @@ #include <unordered_map> #include "VulkanUtilities/VulkanMemoryManager.h" +#include "VulkanUtilities/VulkanObjectWrappers.h" namespace Diligent { +// Upload heap is used by a device context to update texture and buffer regions through +// UpdateBufferRegion() and UpdateTextureRegion(). +// +// The heap allocates pages from the global memory manager. +// The pages are released and returned to the manager at the end of every frame. +// +// _______________________________________________________________________________________________________________________________ +// | | +// | VulkanUploadHeap | +// | | +// | || - - - - - - - - - - Page[0] - - - - - - - - - - -|| || - - - - - - - - - - Page[1] - - - - - - - - - - -|| | +// | || Allocation0 | Allocation1 | ... | AllocationN || || Allocation0 | Allocation1 | ... | AllocationM || ... | +// |__________|____________________________________________________________________________________________________________________| +// | A | +// | | | +// |Allocate() CreateNewPage()| |ReleaseAllocatedPages() +// | ______|___________________V____ +// V | | +// VulkanUploadAllocation | Global Memory Manager | +// | (VulkanMemoryManager) | +// | | +// |______________________________| +// class RenderDeviceVkImpl; struct VulkanUploadAllocation { - VulkanUploadAllocation(){} - VulkanUploadAllocation(void* _CPUAddress, VkDeviceSize _Size, VkDeviceSize _AlignedOffset, VkBuffer _vkBuffer) : + VulkanUploadAllocation() noexcept {} + VulkanUploadAllocation(void* _CPUAddress, + VkDeviceSize _Size, + VkDeviceSize _AlignedOffset, + VkBuffer _vkBuffer) noexcept : CPUAddress (_CPUAddress), Size (_Size), AlignedOffset(_AlignedOffset), vkBuffer (_vkBuffer) {} - VulkanUploadAllocation (const VulkanUploadAllocation&) = delete; - VulkanUploadAllocation& operator = (const VulkanUploadAllocation&) = delete; - VulkanUploadAllocation (VulkanUploadAllocation&&) = default; - VulkanUploadAllocation& operator = (VulkanUploadAllocation&&) = default; + VulkanUploadAllocation (const VulkanUploadAllocation&) = delete; + VulkanUploadAllocation& operator = (const VulkanUploadAllocation&) = delete; + VulkanUploadAllocation ( VulkanUploadAllocation&&) = default; + VulkanUploadAllocation& operator = ( VulkanUploadAllocation&&) = default; VkBuffer vkBuffer = VK_NULL_HANDLE; // Vulkan buffer associated with this memory. void* CPUAddress = nullptr; @@ -58,14 +85,15 @@ public: std::string HeapName, VkDeviceSize PageSize); - VulkanUploadHeap (VulkanUploadHeap&&) = delete; - VulkanUploadHeap (const VulkanUploadHeap&) = delete; - VulkanUploadHeap& operator= (VulkanUploadHeap&) = delete; - VulkanUploadHeap& operator= (VulkanUploadHeap&& rhs) = delete; + VulkanUploadHeap (const VulkanUploadHeap&) = delete; + VulkanUploadHeap ( VulkanUploadHeap&&) = delete; + VulkanUploadHeap& operator= (const VulkanUploadHeap&) = delete; + VulkanUploadHeap& operator= ( VulkanUploadHeap&&) = delete; ~VulkanUploadHeap(); VulkanUploadAllocation Allocate(size_t SizeInBytes, size_t Alignment); + // Releases all allocated pages that are returned to the global memory manager by the release queues void ReleaseAllocatedPages(Uint64 CmdQueueMask); size_t GetStalePagesCount()const @@ -74,8 +102,10 @@ public: } private: - RenderDeviceVkImpl& m_RenderDevice; - std::string m_HeapName; + RenderDeviceVkImpl& m_RenderDevice; + std::string m_HeapName; + const VkDeviceSize m_PageSize; + struct UploadPageInfo { VulkanUtilities::VulkanMemoryAllocation MemAllocation; @@ -110,8 +140,6 @@ private: size_t m_CurrAllocatedSize = 0; size_t m_PeakAllocatedSize = 0; - const VkDeviceSize m_PageSize; - UploadPageInfo CreateNewPage(VkDeviceSize SizeInBytes); }; diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBufferPool.h b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBufferPool.h index 6023a42b..25318763 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBufferPool.h +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanCommandBufferPool.h @@ -26,6 +26,7 @@ #include <deque> #include <memory> #include <mutex> +#include <atomic> #include "vulkan.h" #include "VulkanLogicalDevice.h" #include "VulkanObjectWrappers.h" @@ -51,6 +52,10 @@ namespace VulkanUtilities CommandPoolWrapper&& Release(); +#ifdef DEVELOPMENT + int32_t DvpGetBufferCounter()const{return m_BuffCounter;} +#endif + private: // Shared point to logical device must be defined before the command pool std::shared_ptr<const VulkanUtilities::VulkanLogicalDevice> m_LogicalDevice; @@ -58,5 +63,8 @@ namespace VulkanUtilities std::mutex m_Mutex; std::deque< VkCommandBuffer > m_CmdBuffers; +#ifdef DEVELOPMENT + std::atomic_int32_t m_BuffCounter = 0; +#endif }; } diff --git a/Graphics/GraphicsEngineVulkan/interface/RenderDeviceVk.h b/Graphics/GraphicsEngineVulkan/interface/RenderDeviceVk.h index 1bb66d27..df032884 100644 --- a/Graphics/GraphicsEngineVulkan/interface/RenderDeviceVk.h +++ b/Graphics/GraphicsEngineVulkan/interface/RenderDeviceVk.h @@ -53,9 +53,12 @@ public: /// that all associated work has been finished virtual Bool IsFenceSignaled(Uint32 QueueIndex, Uint64 FenceValue) = 0; - /// Should be called at the end of the frame when attached to existing D3D12 device - /// Otherwise the method is automatically called before present - virtual void FinishFrame() = 0; + /// Purges device release queues and releases all stale resources. + /// This method is automatically called by ISwapChain::Present(). + /// \param [in] ForceRelease - Forces release of all objects. Use this option with + /// great care only if you are sure the resources are not + /// in use by the GPU (such as when the device has just be idled). + virtual void ReleaseStaleResources(bool ForceRelease = false) = 0; /// Creates a texture object from native Vulkan image diff --git a/Graphics/GraphicsEngineVulkan/src/CommandPoolManager.cpp b/Graphics/GraphicsEngineVulkan/src/CommandPoolManager.cpp index 2d37e145..e4a16061 100644 --- a/Graphics/GraphicsEngineVulkan/src/CommandPoolManager.cpp +++ b/Graphics/GraphicsEngineVulkan/src/CommandPoolManager.cpp @@ -65,18 +65,25 @@ VulkanUtilities::CommandPoolWrapper CommandPoolManager::AllocateCommandPool(cons LogicalDevice.ResetCommandPool(CmdPool); +#ifdef DEVELOPMENT + ++m_AllocatedPoolCounter; +#endif return std::move(CmdPool); } void CommandPoolManager::FreeCommandPool(VulkanUtilities::CommandPoolWrapper&& CmdPool) { std::lock_guard<std::mutex> LockGuard(m_Mutex); +#ifdef DEVELOPMENT + --m_AllocatedPoolCounter; +#endif m_CmdPools.emplace_back(std::move(CmdPool)); } void CommandPoolManager::DestroyPools() { std::lock_guard<std::mutex> LockGuard(m_Mutex); + DEV_CHECK_ERR(m_AllocatedPoolCounter == 0, m_AllocatedPoolCounter, " command pools have not been returned to the manager."); LOG_INFO_MESSAGE(m_Name, " allocated descriptor pool count: ", m_CmdPools.size() ); m_CmdPools.clear(); } diff --git a/Graphics/GraphicsEngineVulkan/src/DescriptorPoolManager.cpp b/Graphics/GraphicsEngineVulkan/src/DescriptorPoolManager.cpp index f05894e8..c10b8a14 100644 --- a/Graphics/GraphicsEngineVulkan/src/DescriptorPoolManager.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DescriptorPoolManager.cpp @@ -56,12 +56,16 @@ VulkanUtilities::DescriptorPoolWrapper DescriptorPoolManager::CreateDescriptorPo DescriptorPoolManager::~DescriptorPoolManager() { + DEV_CHECK_ERR(m_AllocatedPoolCounter == 0, "Not all allocated descriptor pools are returned to the pool manager"); LOG_INFO_MESSAGE(m_PoolName, " stats: allocated ", m_Pools.size(), " pool(s)"); } VulkanUtilities::DescriptorPoolWrapper DescriptorPoolManager::GetPool(const char* DebugName) { std::lock_guard<std::mutex> Lock(m_Mutex); +#ifdef DEVELOPMENT + ++m_AllocatedPoolCounter; +#endif if (m_Pools.empty()) return CreateDescriptorPool(DebugName); else @@ -79,6 +83,9 @@ void DescriptorPoolManager::FreePool(VulkanUtilities::DescriptorPoolWrapper&& Po std::lock_guard<std::mutex> Lock(m_Mutex); m_DeviceVkImpl.GetLogicalDevice().ResetDescriptorPool(Pool); m_Pools.emplace_back(std::move(Pool)); +#ifdef DEVELOPMENT + --m_AllocatedPoolCounter; +#endif } @@ -202,7 +209,7 @@ void DynamicDescriptorSetAllocator::ReleasePools(Uint64 QueueMask) { public: DescriptorPoolDeleter(DescriptorPoolManager& _PoolMgr, - VulkanUtilities::DescriptorPoolWrapper&& _Pool) : + VulkanUtilities::DescriptorPoolWrapper&& _Pool) noexcept : PoolMgr (&_PoolMgr), Pool (std::move(_Pool)) {} @@ -241,6 +248,7 @@ void DynamicDescriptorSetAllocator::ReleasePools(Uint64 QueueMask) DynamicDescriptorSetAllocator::~DynamicDescriptorSetAllocator() { + DEV_CHECK_ERR(m_AllocatedPools.empty(), "All allocated pools must be returned to the parent descriptor pool manager"); LOG_INFO_MESSAGE(m_Name, " peak descriptor pool count: ", m_PeakPoolCount); } diff --git a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp index 5841e37c..3d7d0658 100644 --- a/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/DeviceContextVkImpl.cpp @@ -108,29 +108,24 @@ namespace Diligent if (!m_bIsDeferred) { - // There should be no outstanding commands, but we need to call Flush to discard all stale - // context resources to actually destroy them in the next call to ReleaseStaleContextResources() Flush(); } - // TODO: remove - //DisposeCurrentCmdBuffer(m_CommandQueueId, m_LastSubmittedFenceValue); + FinishFrame(false); + + // There must be no stale resources + DEV_CHECK_ERR(m_UploadHeap.GetStalePagesCount() == 0, "All allocated upload heap pages must have been released at this point"); + DEV_CHECK_ERR(m_DynamicHeap.GetAllocatedMasterBlockCount() == 0, "All allocated dynamic heap master blocks must have been released"); + DEV_CHECK_ERR(m_DynamicDescrSetAllocator.GetAllocatedPoolCount() == 0, "All allocated dynamic descriptor set pools must have been released at this point"); - // There must be no resources in the stale resource list. For immediate context, all stale resources must have been - // moved to the release queue by Flush(). For deferred contexts, this should have happened in the last FinishCommandList() - // call. - VERIFY(m_UploadHeap.GetStalePagesCount() == 0, "All stale pages must have been discarded at this point"); - VERIFY(m_DynamicDescrSetAllocator.GetAllocatedPoolCount() == 0, "All allocated dynamic descriptor set pools must have been released at this point"); - auto VkCmdPool = m_CmdPool.Release(); pDeviceVkImpl->SafeReleaseDeviceObject(std::move(VkCmdPool), ~Uint64{0}); - // TODO: - // We must now wait for GPU to finish so that we can safely destroy all context resources. - // We need to idle when destroying deferred contexts as well since some resources may still be in use. - // Also, upload allocation for instance reference the upload heap, so it must be alive when these - // allocations are destroyed by the release queue. - pDeviceVkImpl->IdleGPU(true); + // The main reason we need to idle the GPU is because we need to make sure that all command buffers are returned to the + // pool. Upload heap, dynamic heap and dynamic descriptor manager return their resources to global managers and + // do not really need to wait for GPU to idle. + pDeviceVkImpl->IdleGPU(); + DEV_CHECK_ERR(m_CmdPool.DvpGetBufferCounter() == 0, "All command buffers must have been returned to the pool"); } IMPLEMENT_QUERY_INTERFACE( DeviceContextVkImpl, IID_DeviceContextVk, TDeviceContextBase ) @@ -156,19 +151,20 @@ namespace Diligent { public: CmdBufferDeleter(VkCommandBuffer _vkCmdBuff, - VulkanUtilities::VulkanCommandBufferPool& _Pool) : - vkCmdBuff(_vkCmdBuff), - Pool (&_Pool) + VulkanUtilities::VulkanCommandBufferPool& _Pool) noexcept : + vkCmdBuff (_vkCmdBuff), + Pool (&_Pool) { VERIFY_EXPR(vkCmdBuff != VK_NULL_HANDLE); } + CmdBufferDeleter (const CmdBufferDeleter&) = delete; CmdBufferDeleter& operator = (const CmdBufferDeleter&) = delete; CmdBufferDeleter& operator = ( CmdBufferDeleter&&) = delete; - CmdBufferDeleter(CmdBufferDeleter&& rhs) : - vkCmdBuff(rhs.vkCmdBuff), - Pool (rhs.Pool) + CmdBufferDeleter(CmdBufferDeleter&& rhs) noexcept : + vkCmdBuff (rhs.vkCmdBuff), + Pool (rhs.Pool) { rhs.vkCmdBuff = VK_NULL_HANDLE; rhs.Pool = nullptr; @@ -747,26 +743,29 @@ namespace Diligent ++m_State.NumCommands; } - void DeviceContextVkImpl::FinishFrame(bool ForceRelease) - { - // TODO: rework - FinishFrame(); - } - - void DeviceContextVkImpl::FinishFrame() + void DeviceContextVkImpl::FinishFrame(bool /*ForceRelease*/) { if(GetNumCommandsInCtx() != 0) LOG_ERROR_MESSAGE(m_bIsDeferred ? "There are outstanding commands in the deferred device context when finishing the frame. This is an error and may cause unpredicted behaviour. Close all deferred contexts and execute them before finishing the frame" : "There are outstanding commands in the immediate device context when finishing the frame. This is an error and may cause unpredicted behaviour. Call Flush() to submit all commands for execution before finishing the frame"); + if (!m_MappedTextures.empty()) + LOG_ERROR_MESSAGE("There are mapped textures in the device context when finishing the frame. All dynamic resources must be used in the same frame in which they are mapped."); + auto& DeviceVkImpl = *m_pDevice.RawPtr<RenderDeviceVkImpl>(); VERIFY_EXPR(m_bIsDeferred || m_SubmittedBuffersCmdQueueMask == (Uint64{1}<<m_CommandQueueId)); - // The released resources will go into the stale resources queue first, however they will move into - // the release queue rightaway when RenderDeviceVkImpl::FlushStaleResources() is called + + // Release resources used by the context during this frame. + + // Upload heap returns all allocated pages to the global memory manager m_UploadHeap.ReleaseAllocatedPages(m_SubmittedBuffersCmdQueueMask); - m_DynamicHeap.FinishFrame(DeviceVkImpl, m_SubmittedBuffersCmdQueueMask); + + // Dynamic heap returns all allocated master blocks to global dynamic memory manager + m_DynamicHeap.ReleaseMasterBlocks(DeviceVkImpl, m_SubmittedBuffersCmdQueueMask); + + // Dynamic descriptor set allocator returns all allocated pools to global dynamic descriptor pool manager m_DynamicDescrSetAllocator.ReleasePools(m_SubmittedBuffersCmdQueueMask); if (m_bIsDeferred) diff --git a/Graphics/GraphicsEngineVulkan/src/RenderDeviceVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/RenderDeviceVkImpl.cpp index 6831edf8..16c267df 100644 --- a/Graphics/GraphicsEngineVulkan/src/RenderDeviceVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/RenderDeviceVkImpl.cpp @@ -133,15 +133,11 @@ RenderDeviceVkImpl::~RenderDeviceVkImpl() { // Explicitly destroy dynamic heap m_DynamicMemoryManager.Destroy(); - // Finish current frame. This will release resources taken by previous frames, and - // will move all stale resources to the release queues. The resources will not be - // release until the next call to FinishFrame() - FinishFrame(false); + // Wait for the GPU to complete all its operations - IdleGPU(true); - // Call FinishFrame() again to destroy resources in - // release queues - FinishFrame(true); + IdleGPU(); + + ReleaseStaleResources(true); m_TransientCmdPoolMgr.DestroyPools(); @@ -218,9 +214,10 @@ void RenderDeviceVkImpl::ExecuteAndDisposeTransientCmdBuff(Uint32 QueueIndex, Vk // | | F < SubmittedFenceValue==F+1 | | // // Since transient command buffers do not count as real command buffers, submit them directly to the queue - // to avoid interference with the command buffer numbers + // to avoid interference with the command buffer counter Uint64 FenceValue = 0; - LockCommandQueue(QueueIndex, [&](ICommandQueueVk* pCmdQueueVk) + LockCommandQueue(QueueIndex, + [&](ICommandQueueVk* pCmdQueueVk) { FenceValue = pCmdQueueVk->Submit(SubmitInfo); @@ -306,22 +303,11 @@ Uint64 RenderDeviceVkImpl::ExecuteCommandBuffer(Uint32 QueueIndex, const VkSubmi } -void RenderDeviceVkImpl::IdleGPU(bool ReleaseStaleObjects) +void RenderDeviceVkImpl::IdleGPU() { - IdleCommandQueues(ReleaseStaleObjects); + IdleCommandQueues(true); m_LogicalVkDevice->WaitIdle(); - - if (ReleaseStaleObjects) - { - // Do not wait until the end of the frame and force deletion. - // This is necessary to release outstanding references to the - // swap chain buffers when it is resized in the middle of the frame. - // Since GPU has been idled, it it is safe to do so - - // SubmittedFenceValue has now been signaled by the GPU since we waited for it - //m_MainDescriptorPool.ReleaseStaleAllocations(m_CommandQueues[0].CmdQueue->GetCompletedFenceValue()); - m_MemoryMgr.ShrinkMemory(); - } + ReleaseStaleResources(); } void RenderDeviceVkImpl::FlushStaleResources(Uint32 CmdQueueIndex) @@ -332,23 +318,10 @@ void RenderDeviceVkImpl::FlushStaleResources(Uint32 CmdQueueIndex) TRenderDeviceBase::SubmitCommandBuffer(0, DummySumbitInfo, true); } -void RenderDeviceVkImpl::FinishFrame(bool ReleaseAllResources) +void RenderDeviceVkImpl::ReleaseStaleResources(bool ForceRelease) { - // Discard all remaining objects. This is important to do if there were - // no command lists submitted during the frame. All stale resources will - // be associated with the submitted fence value and thus will not be released - // until the GPU is finished with the current frame - - // TODO: Rework - FlushStaleResources(0); - - // TODO: rework this - //auto CompletedFenceValue = ReleaseAllResources ? std::numeric_limits<Uint64>::max() : m_CommandQueues[0].CmdQueue->GetCompletedFenceValue(); - - //m_MainDescriptorPool.ReleaseStaleAllocations(CompletedFenceValue); m_MemoryMgr.ShrinkMemory(); - - PurgeReleaseQueues(ReleaseAllResources); + PurgeReleaseQueues(ForceRelease); } diff --git a/Graphics/GraphicsEngineVulkan/src/SwapChainVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/SwapChainVkImpl.cpp index 21818b6d..2d25b036 100644 --- a/Graphics/GraphicsEngineVulkan/src/SwapChainVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/SwapChainVkImpl.cpp @@ -322,7 +322,7 @@ SwapChainVkImpl::~SwapChainVkImpl() if(m_VkSwapChain != VK_NULL_HANDLE) { auto *pDeviceVkImpl = m_pRenderDevice.RawPtr<RenderDeviceVkImpl>(); - pDeviceVkImpl->IdleGPU(true); + pDeviceVkImpl->IdleGPU(); vkDestroySwapchainKHR(pDeviceVkImpl->GetVkDevice(), m_VkSwapChain, NULL); } } @@ -463,7 +463,7 @@ void SwapChainVkImpl::Present(Uint32 SyncInterval) } pImmediateCtxVk->FinishFrame(false); - pDeviceVk->FinishFrame(); + pDeviceVk->ReleaseStaleResources(); if (!m_IsMinimized) { @@ -507,7 +507,7 @@ void SwapChainVkImpl::Resize( Uint32 NewWidth, Uint32 NewHeight ) // This will release references to Vk swap chain buffers hold by // m_pBackBufferRTV[] - pDeviceVk->IdleGPU(true); + pDeviceVk->IdleGPU(); // We must wait unitl GPU is idled before destroying semaphores as they // are destroyed immediately diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanDynamicHeap.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanDynamicHeap.cpp index 22ee9227..3e1db532 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanDynamicHeap.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanDynamicHeap.cpp @@ -257,7 +257,7 @@ VulkanDynamicAllocation VulkanDynamicHeap::Allocate(Uint32 SizeInBytes, Uint32 A return VulkanDynamicAllocation{}; } -void VulkanDynamicHeap::FinishFrame(RenderDeviceVkImpl& DeviceVkImpl, Uint64 CmdQueueMask) +void VulkanDynamicHeap::ReleaseMasterBlocks(RenderDeviceVkImpl& DeviceVkImpl, Uint64 CmdQueueMask) { m_DynamicMemMgr.ReleaseMasterBlocks(m_MasterBlocks, DeviceVkImpl, CmdQueueMask); m_MasterBlocks.clear(); @@ -271,6 +271,8 @@ void VulkanDynamicHeap::FinishFrame(RenderDeviceVkImpl& DeviceVkImpl, Uint64 Cmd VulkanDynamicHeap::~VulkanDynamicHeap() { + DEV_CHECK_ERR(m_MasterBlocks.empty(), m_MasterBlocks.size(), " master block(s) have not been returned to dynamic memory manager"); + LOG_INFO_MESSAGE(m_HeapName, " usage stats:\n" " Peak used/peak allocated size: ", FormatMemorySize(m_PeakUsedSize, 2, m_PeakAllocatedSize), '/', FormatMemorySize(m_PeakAllocatedSize, 2, m_PeakAllocatedSize), ". Peak utilization: ", std::fixed, std::setprecision(1), static_cast<double>(m_PeakUsedSize) / static_cast<double>(std::max(m_PeakAllocatedSize, 1U)) * 100.0, '%'); diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanCommandBufferPool.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanCommandBufferPool.cpp index 91a1b0a6..a8dd1d93 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanCommandBufferPool.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanCommandBufferPool.cpp @@ -47,6 +47,7 @@ namespace VulkanUtilities VulkanCommandBufferPool::~VulkanCommandBufferPool() { m_CmdPool.Release(); + DEV_CHECK_ERR(m_BuffCounter == 0, m_BuffCounter, " command buffer(s) have not been returned to the pool. If there are outstanding references to these buffers in release queues, FreeCommandBuffer() will crash when attempting to return a buffer to the pool."); } VkCommandBuffer VulkanCommandBufferPool::GetCommandBuffer(const char* DebugName) @@ -89,8 +90,10 @@ namespace VulkanUtilities // and recorded again between each submission. CmdBuffBeginInfo.pInheritanceInfo = nullptr; // Ignored for a primary command buffer auto err = vkBeginCommandBuffer(CmdBuffer, &CmdBuffBeginInfo); - VERIFY(err == VK_SUCCESS, "Failed to begin command buffer"); - + DEV_CHECK_ERR(err == VK_SUCCESS, "Failed to begin command buffer"); +#ifdef DEVELOPMENT + ++m_BuffCounter; +#endif return CmdBuffer; } @@ -101,6 +104,9 @@ namespace VulkanUtilities // executed the command buffer m_CmdBuffers.emplace_back(CmdBuffer); CmdBuffer = VK_NULL_HANDLE; +#ifdef DEVELOPMENT + --m_BuffCounter; +#endif } CommandPoolWrapper&& VulkanCommandBufferPool::Release() |
