From f280cff0a2921a30afec517c3377756448a144c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Fri, 14 May 2021 02:04:47 +0200 Subject: [PATCH] Renderer: Add release queue to fix deletion while use --- .../OpenGLRenderer/OpenGLRenderWindow.hpp | 2 +- include/Nazara/Renderer/RenderFrame.hpp | 7 +- include/Nazara/Renderer/RenderFrame.inl | 15 ++++ include/Nazara/Renderer/RenderImage.hpp | 43 +++++++++- include/Nazara/Renderer/RenderImage.inl | 81 +++++++++++++++++++ .../Nazara/VulkanRenderer/VkRenderWindow.hpp | 2 +- .../VulkanRenderer/VulkanRenderImage.hpp | 6 +- .../VulkanRenderer/VulkanRenderImage.inl | 4 +- src/Nazara/Graphics/BakedFrameGraph.cpp | 2 +- .../OpenGLRenderer/OpenGLRenderImage.cpp | 1 + .../OpenGLRenderer/OpenGLRenderWindow.cpp | 4 +- src/Nazara/Renderer/RenderImage.cpp | 7 +- src/Nazara/VulkanRenderer/VkRenderWindow.cpp | 4 +- 13 files changed, 163 insertions(+), 15 deletions(-) diff --git a/include/Nazara/OpenGLRenderer/OpenGLRenderWindow.hpp b/include/Nazara/OpenGLRenderer/OpenGLRenderWindow.hpp index 2a74d2ff8..cfaf3cf1a 100644 --- a/include/Nazara/OpenGLRenderer/OpenGLRenderWindow.hpp +++ b/include/Nazara/OpenGLRenderer/OpenGLRenderWindow.hpp @@ -44,7 +44,7 @@ namespace Nz private: std::size_t m_currentFrame; std::shared_ptr m_device; - std::vector m_renderImage; + std::vector> m_renderImage; std::unique_ptr m_context; OpenGLRenderPass m_renderPass; OpenGLWindowFramebuffer m_framebuffer; diff --git a/include/Nazara/Renderer/RenderFrame.hpp b/include/Nazara/Renderer/RenderFrame.hpp index 1e16bd10c..effc367ec 100644 --- a/include/Nazara/Renderer/RenderFrame.hpp +++ b/include/Nazara/Renderer/RenderFrame.hpp @@ -10,13 +10,13 @@ #include #include #include +#include #include namespace Nz { class CommandBuffer; class CommandBufferBuilder; - class RenderImage; class UploadPool; class NAZARA_RENDERER_API RenderFrame @@ -34,10 +34,13 @@ namespace Nz inline bool IsFramebufferInvalidated() const; - void SubmitCommandBuffer(CommandBuffer* commandBuffer, QueueTypeFlags queueTypeFlags) ; + template void PushForRelease(T&& value); + template void PushReleaseCallback(F&& releaseCallback); void Present(); + void SubmitCommandBuffer(CommandBuffer* commandBuffer, QueueTypeFlags queueTypeFlags) ; + inline explicit operator bool(); RenderFrame& operator=(const RenderFrame&) = delete; diff --git a/include/Nazara/Renderer/RenderFrame.inl b/include/Nazara/Renderer/RenderFrame.inl index 6a3cd4e19..9a04e1981 100644 --- a/include/Nazara/Renderer/RenderFrame.inl +++ b/include/Nazara/Renderer/RenderFrame.inl @@ -23,6 +23,21 @@ namespace Nz return m_framebufferInvalidation; } + template + void RenderFrame::PushForRelease(T&& value) + { + return PushReleaseCallback([v = std::forward(value)] {}); + } + + template + void RenderFrame::PushReleaseCallback(F&& releaseCallback) + { + if (!m_image) + throw std::runtime_error("frame is either invalid or has already been presented"); + + m_image->PushReleaseCallback(std::forward(releaseCallback)); + } + inline RenderFrame::operator bool() { return m_image != nullptr; diff --git a/include/Nazara/Renderer/RenderImage.hpp b/include/Nazara/Renderer/RenderImage.hpp index 3081ac231..42fda8fe8 100644 --- a/include/Nazara/Renderer/RenderImage.hpp +++ b/include/Nazara/Renderer/RenderImage.hpp @@ -21,12 +21,19 @@ namespace Nz class NAZARA_RENDERER_API RenderImage { public: + class Releasable; + template class ReleasableLambda; + virtual ~RenderImage(); virtual void Execute(const std::function& callback, QueueTypeFlags queueTypeFlags) = 0; + inline void FlushReleaseQueue(); + virtual UploadPool& GetUploadPool() = 0; + template void PushReleaseCallback(F&& callback); + virtual void SubmitCommandBuffer(CommandBuffer* commandBuffer, QueueTypeFlags queueTypeFlags) = 0; virtual void Present() = 0; @@ -34,7 +41,41 @@ namespace Nz protected: RenderImage() = default; RenderImage(const RenderImage&) = delete; - RenderImage(RenderImage&&) = default; + RenderImage(RenderImage&&) = delete; + + private: + static constexpr std::size_t BlockSize = 4 * 1024; + + using Block = std::vector; + + std::vector m_releaseQueue; + std::vector m_releaseMemoryPool; + }; + + class NAZARA_RENDERER_API RenderImage::Releasable + { + public: + virtual ~Releasable(); + + virtual void Release() = 0; + }; + + template + class RenderImage::ReleasableLambda : public Releasable + { + public: + template ReleasableLambda(U&& lambda); + ReleasableLambda(const ReleasableLambda&) = delete; + ReleasableLambda(ReleasableLambda&&) = delete; + ~ReleasableLambda() = default; + + void Release() override; + + ReleasableLambda& operator=(const ReleasableLambda&) = delete; + ReleasableLambda& operator=(ReleasableLambda&&) = delete; + + private: + T m_lambda; }; } diff --git a/include/Nazara/Renderer/RenderImage.inl b/include/Nazara/Renderer/RenderImage.inl index 20f3103f8..fd4d3a638 100644 --- a/include/Nazara/Renderer/RenderImage.inl +++ b/include/Nazara/Renderer/RenderImage.inl @@ -3,10 +3,91 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include +#include #include namespace Nz { + inline void RenderImage::FlushReleaseQueue() + { + for (Releasable* releasable : m_releaseQueue) + { + releasable->Release(); + PlacementDestroy(releasable); + } + m_releaseQueue.clear(); + + for (auto& memoryblock : m_releaseMemoryPool) + memoryblock.clear(); + } + + template + void RenderImage::PushReleaseCallback(F&& callback) + { + using Functor = ReleasableLambda>>; + + constexpr std::size_t functorSize = sizeof(Functor); + constexpr std::size_t functorAlignment = alignof(Functor); + + // Try to minimize lost space + struct + { + Block* block = nullptr; + UInt64 alignedOffset = 0; + UInt64 lostSpace = 0; + } bestBlock; + + for (Block& block : m_releaseMemoryPool) + { + std::size_t freeOffset = block.size(); + + UInt64 alignedOffset = Align(freeOffset, functorAlignment); + if (alignedOffset + functorSize > block.capacity()) + continue; //< Not enough space + + UInt64 lostSpace = alignedOffset - freeOffset; + + if (!bestBlock.block || lostSpace < bestBlock.lostSpace) + { + bestBlock.block = █ + bestBlock.alignedOffset = alignedOffset; + bestBlock.lostSpace = lostSpace; + } + } + + // No block found, allocate a new one + if (!bestBlock.block) + { + Block newBlock; + newBlock.reserve(BlockSize); + + bestBlock.block = &m_releaseMemoryPool.emplace_back(std::move(newBlock)); + bestBlock.alignedOffset = 0; + bestBlock.lostSpace = 0; + } + + Block& targetBlock = *bestBlock.block; + targetBlock.resize(bestBlock.alignedOffset + functorSize); + + Functor* releasable = reinterpret_cast(&targetBlock[bestBlock.alignedOffset]); + PlacementNew(releasable, std::forward(callback)); + + m_releaseQueue.push_back(releasable); + } + + template + template + RenderImage::ReleasableLambda::ReleasableLambda(U&& lambda) : + m_lambda(std::forward(lambda)) + { + } + + template + void RenderImage::ReleasableLambda::Release() + { + m_lambda(); + } } #include diff --git a/include/Nazara/VulkanRenderer/VkRenderWindow.hpp b/include/Nazara/VulkanRenderer/VkRenderWindow.hpp index c14fc640d..6eee7bc49 100644 --- a/include/Nazara/VulkanRenderer/VkRenderWindow.hpp +++ b/include/Nazara/VulkanRenderer/VkRenderWindow.hpp @@ -74,7 +74,7 @@ namespace Nz std::shared_ptr m_device; std::size_t m_currentFrame; std::vector m_inflightFences; - std::vector m_concurrentImageData; + std::vector> m_concurrentImageData; Vk::DeviceMemory m_depthBufferMemory; Vk::Image m_depthBuffer; Vk::ImageView m_depthBufferView; diff --git a/include/Nazara/VulkanRenderer/VulkanRenderImage.hpp b/include/Nazara/VulkanRenderer/VulkanRenderImage.hpp index f7033b799..ecbd3c7c9 100644 --- a/include/Nazara/VulkanRenderer/VulkanRenderImage.hpp +++ b/include/Nazara/VulkanRenderer/VulkanRenderImage.hpp @@ -36,13 +36,13 @@ namespace Nz inline Vk::Semaphore& GetRenderFinishedSemaphore(); VulkanUploadPool& GetUploadPool() override; - void SubmitCommandBuffer(CommandBuffer* commandBuffer, QueueTypeFlags queueTypeFlags) override; - void SubmitCommandBuffer(VkCommandBuffer commandBuffer, QueueTypeFlags queueTypeFlags); - void Present() override; inline void Reset(UInt32 imageIndex); + void SubmitCommandBuffer(CommandBuffer* commandBuffer, QueueTypeFlags queueTypeFlags) override; + void SubmitCommandBuffer(VkCommandBuffer commandBuffer, QueueTypeFlags queueTypeFlags); + VulkanRenderImage& operator=(const VulkanRenderImage&) = delete; VulkanRenderImage& operator=(VulkanRenderImage&&) = delete; diff --git a/include/Nazara/VulkanRenderer/VulkanRenderImage.inl b/include/Nazara/VulkanRenderer/VulkanRenderImage.inl index 521233611..d1feba2a9 100644 --- a/include/Nazara/VulkanRenderer/VulkanRenderImage.inl +++ b/include/Nazara/VulkanRenderer/VulkanRenderImage.inl @@ -7,7 +7,7 @@ namespace Nz { - inline Vk::Fence& Nz::VulkanRenderImage::GetInFlightFence() + inline Vk::Fence& VulkanRenderImage::GetInFlightFence() { return m_inFlightFence; } @@ -29,6 +29,8 @@ namespace Nz inline void VulkanRenderImage::Reset(UInt32 imageIndex) { + FlushReleaseQueue(); + m_graphicalCommandsBuffers.clear(); m_currentCommandBuffer = 0; m_imageIndex = imageIndex; diff --git a/src/Nazara/Graphics/BakedFrameGraph.cpp b/src/Nazara/Graphics/BakedFrameGraph.cpp index 47833f400..0984290ca 100644 --- a/src/Nazara/Graphics/BakedFrameGraph.cpp +++ b/src/Nazara/Graphics/BakedFrameGraph.cpp @@ -45,7 +45,7 @@ namespace Nz if (!regenerateCommandBuffer) continue; - passData.commandBuffer.reset(); //< Release command buffer resources before reallocating it + renderFrame.PushForRelease(std::move(passData.commandBuffer)); passData.commandBuffer = m_commandPool->BuildCommandBuffer([&](CommandBufferBuilder& builder) { for (auto& textureTransition : passData.transitions) diff --git a/src/Nazara/OpenGLRenderer/OpenGLRenderImage.cpp b/src/Nazara/OpenGLRenderer/OpenGLRenderImage.cpp index 9f8c46114..7e2f04ced 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLRenderImage.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLRenderImage.cpp @@ -35,6 +35,7 @@ namespace Nz { m_owner.Present(); m_uploadPool.Reset(); + FlushReleaseQueue(); } void OpenGLRenderImage::SubmitCommandBuffer(CommandBuffer* commandBuffer, QueueTypeFlags /*queueTypeFlags*/) diff --git a/src/Nazara/OpenGLRenderer/OpenGLRenderWindow.cpp b/src/Nazara/OpenGLRenderer/OpenGLRenderWindow.cpp index cd84e7cd2..e0ddf9185 100644 --- a/src/Nazara/OpenGLRenderer/OpenGLRenderWindow.cpp +++ b/src/Nazara/OpenGLRenderer/OpenGLRenderWindow.cpp @@ -33,7 +33,7 @@ namespace Nz m_size = size; } - return RenderFrame(&m_renderImage[m_currentFrame], invalidateFramebuffer); + return RenderFrame(m_renderImage[m_currentFrame].get(), invalidateFramebuffer); } bool OpenGLRenderWindow::Create(RendererImpl* renderer, RenderSurface* surface, const RenderWindowParameters& parameters) @@ -55,7 +55,7 @@ namespace Nz m_renderImage.reserve(RenderImageCount); for (std::size_t i = 0; i < RenderImageCount; ++i) - m_renderImage.emplace_back(*this); + m_renderImage.emplace_back(std::make_unique(*this)); return true; } diff --git a/src/Nazara/Renderer/RenderImage.cpp b/src/Nazara/Renderer/RenderImage.cpp index 68ba064c7..6560534a1 100644 --- a/src/Nazara/Renderer/RenderImage.cpp +++ b/src/Nazara/Renderer/RenderImage.cpp @@ -7,5 +7,10 @@ namespace Nz { - RenderImage::~RenderImage() = default; + RenderImage::~RenderImage() + { + FlushReleaseQueue(); + } + + RenderImage::Releasable::~Releasable() = default; } diff --git a/src/Nazara/VulkanRenderer/VkRenderWindow.cpp b/src/Nazara/VulkanRenderer/VkRenderWindow.cpp index 22bb9b918..1eef29351 100644 --- a/src/Nazara/VulkanRenderer/VkRenderWindow.cpp +++ b/src/Nazara/VulkanRenderer/VkRenderWindow.cpp @@ -57,7 +57,7 @@ namespace Nz invalidateFramebuffer = true; } - VulkanRenderImage& currentFrame = m_concurrentImageData[m_currentFrame]; + VulkanRenderImage& currentFrame = *m_concurrentImageData[m_currentFrame]; Vk::Fence& inFlightFence = currentFrame.GetInFlightFence(); // Wait until previous rendering to this image has been done @@ -564,7 +564,7 @@ namespace Nz m_concurrentImageData.reserve(imageCount); for (std::size_t i = 0; i < imageCount; ++i) - m_concurrentImageData.emplace_back(*this); + m_concurrentImageData.emplace_back(std::make_unique(*this)); } return true;