diff options
| author | Egor Yusov <egor.yusov@gmail.com> | 2018-06-06 15:47:36 +0000 |
|---|---|---|
| committer | Egor Yusov <egor.yusov@gmail.com> | 2018-06-06 15:47:36 +0000 |
| commit | fe135f2472e4a598976860f3f40f85b898fd5cc0 (patch) | |
| tree | 03a76d6c1a07f7485350a15ee63e2818de20d9ad /Graphics | |
| parent | Improvements to resource liftime management in Vulkan (diff) | |
| download | DiligentCore-fe135f2472e4a598976860f3f40f85b898fd5cc0.tar.gz DiligentCore-fe135f2472e4a598976860f3f40f85b898fd5cc0.zip | |
Added ResourceReleaseQueue class
Diffstat (limited to 'Graphics')
4 files changed, 208 insertions, 106 deletions
diff --git a/Graphics/GraphicsAccessories/CMakeLists.txt b/Graphics/GraphicsAccessories/CMakeLists.txt index 8bf7d501..01101526 100644 --- a/Graphics/GraphicsAccessories/CMakeLists.txt +++ b/Graphics/GraphicsAccessories/CMakeLists.txt @@ -3,10 +3,11 @@ cmake_minimum_required (VERSION 3.6) project(GraphicsAccessories CXX) set(INTERFACE + interface/GraphicsAccessories.h + interface/ResourceReleaseQueue.h interface/RingBuffer.h interface/VariableSizeAllocationsManager.h interface/VariableSizeGPUAllocationsManager.h - interface/GraphicsAccessories.h ) set(SOURCE diff --git a/Graphics/GraphicsAccessories/interface/ResourceReleaseQueue.h b/Graphics/GraphicsAccessories/interface/ResourceReleaseQueue.h new file mode 100644 index 00000000..7f8aa22b --- /dev/null +++ b/Graphics/GraphicsAccessories/interface/ResourceReleaseQueue.h @@ -0,0 +1,193 @@ +/* Copyright 2015-2018 Egor Yusov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +// Helper class that handles free memory block management to accommodate variable-size allocation requests +// See http://diligentgraphics.com/diligent-engine/architecture/d3d12/variable-size-memory-allocations-manager/ + +#pragma once + +/// \file +/// Implementation of Diligent::ResourceReleaseQueue class + +#include <mutex> +#include <deque> +#include <memory> + +#include "MemoryAllocator.h" +#include "STDAllocator.h" +#include "DebugUtilities.h" + +namespace Diligent +{ + +/// Helper class that wraps stale resources of different types +class DynamicStaleResourceWrapper +{ +public: + // ___________________________ ___________________________ + // |DynamicStaleResourceWrapper| |DynamicStaleResourceWrapper| + // | | | | + // | m_pStaleResource | | m_pStaleResource | + // |__________|________________| |__________|________________| + // | | + // | | + // | | + // __________V___________________________________ __________V___________________________________ + // |SpecificStaleResource<VulkanBufferWrapper> | |SpecificStaleResource<VulkanMemoryAllocation> | + // | | | | + // | VulkanBufferWrapper m_SpecificResource; | | VulkanMemoryAllocation m_SpecificResource; | + // |______________________________________________| |______________________________________________| + // + + template<typename ResourceType> + static DynamicStaleResourceWrapper Create(ResourceType&& Resource) + { + class SpecificStaleResource : public StaleResourceBase + { + public: + SpecificStaleResource(ResourceType&& SpecificResource) : + m_SpecificResource(std::move(SpecificResource)) + {} + + SpecificStaleResource (const SpecificStaleResource&) = delete; + SpecificStaleResource (SpecificStaleResource&&) = delete; + SpecificStaleResource& operator = (const SpecificStaleResource&) = delete; + SpecificStaleResource& operator = (SpecificStaleResource&&) = delete; + + private: + ResourceType m_SpecificResource; + }; + return DynamicStaleResourceWrapper{new SpecificStaleResource{std::move(Resource)}}; + } + + DynamicStaleResourceWrapper(DynamicStaleResourceWrapper&& rhs)noexcept : + m_pStaleResource(std::move(rhs.m_pStaleResource)) + {} + + DynamicStaleResourceWrapper& operator = (DynamicStaleResourceWrapper&& rhs)noexcept + { + m_pStaleResource = std::move(rhs.m_pStaleResource); + } + + DynamicStaleResourceWrapper (const DynamicStaleResourceWrapper&) = delete; + DynamicStaleResourceWrapper& operator = (const DynamicStaleResourceWrapper&) = delete; + +private: + class StaleResourceBase + { + public: + virtual ~StaleResourceBase() = 0 {} + }; + + DynamicStaleResourceWrapper(StaleResourceBase *pStaleResource) : + m_pStaleResource(pStaleResource) + {} + + std::unique_ptr<StaleResourceBase> m_pStaleResource; +}; + +/// Facilitates safe resource destruction in D3D12 and Vulkan + +/// Resource destruction is a two-stage process: +/// * When resource is released, it is moved into the stale objects queue along with the next command list number +/// * When command list is submitted to the command queue, all stale objects associated with this +/// and earlier command lists are moved to the release queue, along with the fence value associated with +/// the command list +/// * Resources are removed and actually destroyed from the queue when fence is signaled and the queue is Purged +/// +/// \tparam ResourceWrapperType - Type of the resource wrapper used by the release queue. +template<typename ResourceWrapperType = DynamicStaleResourceWrapper> +class ResourceReleaseQueue +{ +public: + ResourceReleaseQueue(IMemoryAllocator& Allocator) : + m_ReleaseQueue(STD_ALLOCATOR_RAW_MEM(ReleaseQueueElemType, Allocator, "Allocator for deque<ReleaseQueueElemType>")), + m_StaleResources(STD_ALLOCATOR_RAW_MEM(ReleaseQueueElemType, Allocator, "Allocator for deque<ReleaseQueueElemType>")) + {} + + ~ResourceReleaseQueue() + { + VERIFY(m_StaleResources.empty(), "Not all stale objects were destroyed"); + VERIFY(m_ReleaseQueue.empty(), "Release queue is not empty"); + } + + /// Moves resource to the release queue + /// \param [in] Resource - Resource to be released + /// \param [in] NextCommandListNumber - Number of the command list that will be submitted to the queue next + template<typename ResourceType> + void SafeReleaseResource(ResourceType&& Resource, Uint64 NextCommandListNumber) + { + std::lock_guard<std::mutex> LockGuard(m_StaleObjectsMutex); + m_StaleResources.emplace_back(NextCommandListNumber, ResourceWrapperType::Create(std::move(Resource)) ); + } + + + void DiscardStaleResources(Uint64 CmdBuffNumber, Uint64 FenceValue) + { + // Only discard these stale objects that were released before CmdBuffNumber + // was executed + std::lock_guard<std::mutex> StaleObjectsLock(m_StaleObjectsMutex); + std::lock_guard<std::mutex> ReleaseQueueLock(m_ReleaseQueueMutex); + while (!m_StaleResources.empty() ) + { + auto &FirstStaleObj = m_StaleResources.front(); + if (FirstStaleObj.first <= CmdBuffNumber) + { + m_ReleaseQueue.emplace_back(FenceValue, std::move(FirstStaleObj.second)); + m_StaleResources.pop_front(); + } + else + break; + } + } + + + /// Removes all objects from the release queue whose fence value is + /// less than or equal to CompletedFenceValue + /// \param [in] CompletedFenceValue - Value of the fence that has been completed by the GPU + void Purge(Uint64 CompletedFenceValue) + { + std::lock_guard<std::mutex> LockGuard(m_ReleaseQueueMutex); + + // Release all objects whose associated fence value is at most CompletedFenceValue + while (!m_ReleaseQueue.empty()) + { + auto &FirstObj = m_ReleaseQueue.front(); + if (FirstObj.first <= CompletedFenceValue) + m_ReleaseQueue.pop_front(); + else + break; + } + } + +private: + + std::mutex m_ReleaseQueueMutex; + using ReleaseQueueElemType = std::pair<Uint64, ResourceWrapperType>; + std::deque< ReleaseQueueElemType, STDAllocatorRawMem<ReleaseQueueElemType> > m_ReleaseQueue; + + std::mutex m_StaleObjectsMutex; + std::deque< ReleaseQueueElemType, STDAllocatorRawMem<ReleaseQueueElemType> > m_StaleResources; +}; + +} diff --git a/Graphics/GraphicsEngineVulkan/include/RenderDeviceVkImpl.h b/Graphics/GraphicsEngineVulkan/include/RenderDeviceVkImpl.h index bda52b94..8998b187 100644 --- a/Graphics/GraphicsEngineVulkan/include/RenderDeviceVkImpl.h +++ b/Graphics/GraphicsEngineVulkan/include/RenderDeviceVkImpl.h @@ -43,6 +43,7 @@ #include "VulkanUtilities/VulkanUploadHeap.h" #include "FramebufferCache.h" #include "CommandPoolManager.h" +#include "ResourceReleaseQueue.h" /// Namespace for the Direct3D11 implementation of the graphics engine namespace Diligent @@ -103,7 +104,10 @@ public: void ExecuteAndDisposeTransientCmdBuff(VkCommandBuffer vkCmdBuff, VulkanUtilities::CommandPoolWrapper&& CmdPool); template<typename ObjectType> - void SafeReleaseVkObject(ObjectType&& Object); + void SafeReleaseVkObject(ObjectType&& Object) + { + m_ReleaseQueue.SafeReleaseResource(std::move(Object), m_NextCmdBuffNumber); + } void FinishFrame(bool ReleaseAllResources); virtual void FinishFrame()override final { FinishFrame(false); } @@ -137,7 +141,6 @@ public: private: virtual void TestTextureFormat( TEXTURE_FORMAT TexFormat )override final; void ProcessReleaseQueue(Uint64 CompletedFenceValue); - void DiscardStaleVkObjects(Uint64 CmdBuffNumber, Uint64 FenceValue); // Submits command buffer for execution to the command queue // Returns the submitted command buffer number and the fence value that has been set to signal by GPU @@ -185,20 +188,6 @@ private: // Resource X can // be released - - std::mutex m_ReleaseQueueMutex; - - class StaleVulkanObjectBase - { - public: - virtual ~StaleVulkanObjectBase() = 0 {} - }; - - using ReleaseQueueElemType = std::pair<Uint64, std::unique_ptr<StaleVulkanObjectBase> >; - std::deque< ReleaseQueueElemType, STDAllocatorRawMem<ReleaseQueueElemType> > m_VkObjReleaseQueue; - - std::mutex m_StaleObjectsMutex; - std::deque< ReleaseQueueElemType, STDAllocatorRawMem<ReleaseQueueElemType> > m_StaleVkObjects; FramebufferCache m_FramebufferCache; // [0] - Main descriptor pool @@ -214,6 +203,7 @@ private: CommandPoolManager m_TransientCmdPoolMgr; VulkanUtilities::VulkanMemoryManager m_MemoryMgr; + ResourceReleaseQueue<> m_ReleaseQueue; }; } diff --git a/Graphics/GraphicsEngineVulkan/src/RenderDeviceVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/RenderDeviceVkImpl.cpp index e46254ec..bde9dc2e 100644 --- a/Graphics/GraphicsEngineVulkan/src/RenderDeviceVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/RenderDeviceVkImpl.cpp @@ -32,8 +32,8 @@ #include "BufferVkImpl.h" #include "ShaderResourceBindingVkImpl.h" #include "DeviceContextVkImpl.h" - #include "EngineMemory.h" + namespace Diligent { @@ -56,13 +56,12 @@ RenderDeviceVkImpl :: RenderDeviceVkImpl(IReferenceCounters *pRefCounters, /*m_CmdListManager(this), m_ContextPool(STD_ALLOCATOR_RAW_MEM(ContextPoolElemType, GetRawAllocator(), "Allocator for vector<unique_ptr<CommandContext>>")), m_AvailableContexts(STD_ALLOCATOR_RAW_MEM(CommandContext*, GetRawAllocator(), "Allocator for vector<CommandContext*>")),*/ - m_VkObjReleaseQueue(STD_ALLOCATOR_RAW_MEM(ReleaseQueueElemType, GetRawAllocator(), "Allocator for queue<ReleaseQueueElemType>")), - m_StaleVkObjects(STD_ALLOCATOR_RAW_MEM(ReleaseQueueElemType, GetRawAllocator(), "Allocator for queue<ReleaseQueueElemType>")), m_DescriptorPools(STD_ALLOCATOR_RAW_MEM(DescriptorPoolManager, GetRawAllocator(), "Allocator for vector<DescriptorPoolManager>")), m_UploadHeaps(STD_ALLOCATOR_RAW_MEM(VulkanUtilities::VulkanUploadHeap, GetRawAllocator(), "Allocator for vector<VulkanUploadHeap>")), m_FramebufferCache(*this), m_TransientCmdPoolMgr(*m_LogicalVkDevice, pCmdQueue->GetQueueFamilyIndex(), VK_COMMAND_POOL_CREATE_TRANSIENT_BIT), - m_MemoryMgr("Global resource memory manager", *m_LogicalVkDevice, *m_PhysicalDevice, GetRawAllocator(), CreationAttribs.DeviceLocalMemoryPageSize, CreationAttribs.HostVisibleMemoryPageSize, CreationAttribs.DeviceLocalMemoryReserveSize, CreationAttribs.HostVisibleMemoryReserveSize) + m_MemoryMgr("Global resource memory manager", *m_LogicalVkDevice, *m_PhysicalDevice, GetRawAllocator(), CreationAttribs.DeviceLocalMemoryPageSize, CreationAttribs.HostVisibleMemoryPageSize, CreationAttribs.DeviceLocalMemoryReserveSize, CreationAttribs.HostVisibleMemoryReserveSize), + m_ReleaseQueue(GetRawAllocator()) { m_DeviceCaps.DevType = DeviceType::Vulkan; m_DeviceCaps.MajorVersion = 1; @@ -142,9 +141,6 @@ RenderDeviceVkImpl::~RenderDeviceVkImpl() m_ContextPool.clear(); #endif - VERIFY(m_StaleVkObjects.empty(), "Not all stale objects were destroyed"); - VERIFY(m_VkObjReleaseQueue.empty(), "Release queue is not empty"); - m_TransientCmdPoolMgr.DestroyPools(m_pCommandQueue->GetCompletedFenceValue()); //if(m_PhysicalDevice) @@ -265,7 +261,7 @@ void RenderDeviceVkImpl::ExecuteCommandBuffer(const VkSubmitInfo &SubmitInfo, De // As long as resources used by deferred contexts are not released until the command list // is executed through immediate context, this stategy always works. - DiscardStaleVkObjects(SubmittedCmdBuffNumber, SubmittedFenceValue); + m_ReleaseQueue.DiscardStaleResources(SubmittedCmdBuffNumber, SubmittedFenceValue); auto CompletedFenceValue = GetCompletedFenceValue(); ProcessReleaseQueue(CompletedFenceValue); m_MemoryMgr.ShrinkMemory(); @@ -310,7 +306,7 @@ void RenderDeviceVkImpl::IdleGPU(bool ReleaseStaleObjects) // 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 - DiscardStaleVkObjects(CmdBuffNumber, FenceValue); + m_ReleaseQueue.DiscardStaleResources(CmdBuffNumber, FenceValue); // FenceValue has now been signaled by the GPU since we waited for it auto CompletedFenceValue = FenceValue; ProcessReleaseQueue(CompletedFenceValue); @@ -408,95 +404,17 @@ void RenderDeviceVkImpl::FinishFrame(bool ReleaseAllResources) // Discard all remaining objects. This is important to do if there were // no command lists submitted during the frame - DiscardStaleVkObjects(CmdBuffNumber, NextFenceValue); + m_ReleaseQueue.DiscardStaleResources(CmdBuffNumber, NextFenceValue); ProcessReleaseQueue(CompletedFenceValue); m_MemoryMgr.ShrinkMemory(); Atomics::AtomicIncrement(m_FrameNumber); } -template<typename ObjectType> -void RenderDeviceVkImpl::SafeReleaseVkObject(ObjectType&& vkObject) -{ - class StaleVulkanObject : public RenderDeviceVkImpl::StaleVulkanObjectBase - { - public: - StaleVulkanObject(ObjectType&& Object) : - m_VkObject(std::move(Object)) - {} - - StaleVulkanObject (const StaleVulkanObject&) = delete; - StaleVulkanObject (StaleVulkanObject&&) = delete; - StaleVulkanObject& operator = (const StaleVulkanObject&) = delete; - StaleVulkanObject& operator = (StaleVulkanObject&&) = delete; - - private: - ObjectType m_VkObject; - }; - - // When Vk object is released, it is first moved into the - // stale objects list. The list is moved into a release queue - // after the next command list is executed. - std::lock_guard<std::mutex> LockGuard(m_StaleObjectsMutex); - m_StaleVkObjects.emplace_back(m_NextCmdBuffNumber, new StaleVulkanObject{std::move(vkObject)} ); -} - -#define INSTANTIATE_SAFE_RELEASE_VK_OBJECT(Type) template void RenderDeviceVkImpl::SafeReleaseVkObject<Type>(Type &&Object) - -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::BufferWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::BufferViewWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::ImageWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::ImageViewWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::SamplerWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::DeviceMemoryWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::RenderPassWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::PipelineWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::ShaderModuleWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::PipelineLayoutWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::FramebufferWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::DescriptorPoolWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::DescriptorSetLayoutWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::SemaphoreWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::CommandPoolWrapper); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::VulkanMemoryAllocation); -INSTANTIATE_SAFE_RELEASE_VK_OBJECT(VulkanUtilities::VulkanUploadAllocation); - -#undef INSTANTIATE_SAFE_RELEASE_VK_OBJECT - -void RenderDeviceVkImpl::DiscardStaleVkObjects(Uint64 CmdBuffNumber, Uint64 FenceValue) -{ - // Only discard these stale objects that were released before CmdBuffNumber - // was executed - std::lock_guard<std::mutex> StaleObjectsLock(m_StaleObjectsMutex); - std::lock_guard<std::mutex> ReleaseQueueLock(m_ReleaseQueueMutex); - while (!m_StaleVkObjects.empty() ) - { - auto &FirstStaleObj = m_StaleVkObjects.front(); - if (FirstStaleObj.first <= CmdBuffNumber) - { - m_VkObjReleaseQueue.emplace_back(FenceValue, std::move(FirstStaleObj.second)); - m_StaleVkObjects.pop_front(); - } - else - break; - } -} void RenderDeviceVkImpl::ProcessReleaseQueue(Uint64 CompletedFenceValue) { - { - std::lock_guard<std::mutex> LockGuard(m_ReleaseQueueMutex); - - // Release all objects whose associated fence value is at most CompletedFenceValue - while (!m_VkObjReleaseQueue.empty()) - { - auto &FirstObj = m_VkObjReleaseQueue.front(); - if (FirstObj.first <= CompletedFenceValue) - m_VkObjReleaseQueue.pop_front(); - else - break; - } - } + m_ReleaseQueue.Purge(CompletedFenceValue); { // This is OK if other thread disposes descriptor heap allocation at this time |
