From b14ac354bbf76c7028e12e88d36af3c0bb9c7329 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 19 Jul 2024 14:27:50 +0200 Subject: [PATCH] Optimize `DynamicTypedBuffer` and `DynamicStructuredBuffer` to use GPU-memory for commonly used single-frame uploads --- Source/Engine/Graphics/DynamicBuffer.cpp | 103 +++++++++++------------ Source/Engine/Graphics/DynamicBuffer.h | 32 ++++--- 2 files changed, 62 insertions(+), 73 deletions(-) diff --git a/Source/Engine/Graphics/DynamicBuffer.cpp b/Source/Engine/Graphics/DynamicBuffer.cpp index 54388fe85..9c5c06fae 100644 --- a/Source/Engine/Graphics/DynamicBuffer.cpp +++ b/Source/Engine/Graphics/DynamicBuffer.cpp @@ -22,69 +22,43 @@ DynamicBuffer::~DynamicBuffer() SAFE_DELETE_GPU_RESOURCE(_buffer); } -void DynamicBuffer::Flush() -{ - // Check if has sth to flush - const uint32 size = Data.Count(); - if (size > 0) - { - // Check if has no buffer - if (_buffer == nullptr) - _buffer = GPUDevice::Instance->CreateBuffer(_name); - - // Check if need to resize buffer - if (_buffer->GetSize() < size) - { - const uint32 numElements = Math::AlignUp(static_cast((size / _stride) * 1.3f), 32); - GPUBufferDescription desc; - InitDesc(desc, numElements); - if (_buffer->Init(desc)) - { - LOG(Fatal, "Cannot setup dynamic buffer '{0}'! Size: {1}", _name, Utilities::BytesToText(size)); - return; - } - } - - // Upload data to the buffer - if (GPUDevice::Instance->IsRendering()) - { - RenderContext::GPULocker.Lock(); - GPUDevice::Instance->GetMainContext()->UpdateBuffer(_buffer, Data.Get(), size); - RenderContext::GPULocker.Unlock(); - } - else - { - _buffer->SetData(Data.Get(), size); - } - } -} - void DynamicBuffer::Flush(GPUContext* context) { - // Check if has sth to flush const uint32 size = Data.Count(); - if (size > 0) + if (size == 0) + return; + + // Lazy-resize buffer + if (_buffer == nullptr) + _buffer = GPUDevice::Instance->CreateBuffer(_name); + if (_buffer->GetSize() < size) { - // Check if has no buffer - if (_buffer == nullptr) - _buffer = GPUDevice::Instance->CreateBuffer(_name); - - // Check if need to resize buffer - if (_buffer->GetSize() < size) + const int32 numElements = Math::AlignUp(static_cast((size / _stride) * 1.3f), 32); + GPUBufferDescription desc; + InitDesc(desc, numElements); + desc.Usage = SingleFrame ? GPUResourceUsage::Default : GPUResourceUsage::Dynamic; + if (_buffer->Init(desc)) { - const uint32 numElements = Math::AlignUp(static_cast((size / _stride) * 1.3f), 32); - GPUBufferDescription desc; - InitDesc(desc, numElements); - if (_buffer->Init(desc)) - { - LOG(Fatal, "Cannot setup dynamic buffer '{0}'! Size: {1}", _name, Utilities::BytesToText(size)); - return; - } + LOG(Fatal, "Cannot setup dynamic buffer '{0}'! Size: {1}", _name, Utilities::BytesToText(size)); + return; } + } - // Upload data to the buffer + // Upload data to the buffer + if (context) + { context->UpdateBuffer(_buffer, Data.Get(), size); } + else if (GPUDevice::Instance->IsRendering()) + { + RenderContext::GPULocker.Lock(); + GPUDevice::Instance->GetMainContext()->UpdateBuffer(_buffer, Data.Get(), size); + RenderContext::GPULocker.Unlock(); + } + else + { + _buffer->SetData(Data.Get(), size); + } } void DynamicBuffer::Dispose() @@ -93,10 +67,26 @@ void DynamicBuffer::Dispose() Data.Resize(0); } +void DynamicVertexBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) +{ + desc = GPUBufferDescription::Vertex(_stride, numElements, GPUResourceUsage::Dynamic); +} + +void DynamicIndexBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) +{ + desc = GPUBufferDescription::Index(_stride, numElements, GPUResourceUsage::Dynamic); +} + +DynamicStructuredBuffer::DynamicStructuredBuffer(uint32 initialCapacity, uint32 stride, bool isUnorderedAccess, const String& name) + : DynamicBuffer(initialCapacity, stride, name) + , _isUnorderedAccess(isUnorderedAccess) +{ + SingleFrame = true; // The most common use-case is just for a single upload of data prepared by CPU +} + void DynamicStructuredBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) { desc = GPUBufferDescription::Structured(numElements, _stride, _isUnorderedAccess); - desc.Usage = GPUResourceUsage::Dynamic; } DynamicTypedBuffer::DynamicTypedBuffer(uint32 initialCapacity, PixelFormat format, bool isUnorderedAccess, const String& name) @@ -104,6 +94,7 @@ DynamicTypedBuffer::DynamicTypedBuffer(uint32 initialCapacity, PixelFormat forma , _format(format) , _isUnorderedAccess(isUnorderedAccess) { + SingleFrame = true; // The most common use-case is just for a single upload of data prepared by CPU } void DynamicTypedBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) @@ -111,5 +102,5 @@ void DynamicTypedBuffer::InitDesc(GPUBufferDescription& desc, int32 numElements) auto bufferFlags = GPUBufferFlags::ShaderResource; if (_isUnorderedAccess) bufferFlags |= GPUBufferFlags::UnorderedAccess; - desc = GPUBufferDescription::Buffer(numElements * _stride, bufferFlags, _format, nullptr, _stride, GPUResourceUsage::Dynamic); + desc = GPUBufferDescription::Buffer(numElements * _stride, bufferFlags, _format, nullptr, _stride); } diff --git a/Source/Engine/Graphics/DynamicBuffer.h b/Source/Engine/Graphics/DynamicBuffer.h index 9fccb8b3a..d859301e0 100644 --- a/Source/Engine/Graphics/DynamicBuffer.h +++ b/Source/Engine/Graphics/DynamicBuffer.h @@ -6,7 +6,7 @@ #include "GPUBuffer.h" /// -/// Dynamic GPU buffer that allows to update and use GPU data (index/vertex/other) during single frame (supports dynamic resizing) +/// Dynamic GPU buffer that allows to update and use GPU data (index/vertex/other) during single frame (supports dynamic resizing). /// class FLAXENGINE_API DynamicBuffer { @@ -32,13 +32,18 @@ public: virtual ~DynamicBuffer(); public: + /// + /// True if buffer will be used once per-frame, otherwise it should support uploading data multiple times per-frame. If true 'GPUResourceUsage::Dynamic' will be used, otherwise 'GPUResourceUsage::Default'. + /// + bool SingleFrame = false; + /// /// The data container (raw bytes storage). /// Array Data; /// - /// Gets buffer (may be null since it's using 'late init' feature) + /// Gets buffer (can be null due to 'late init' feature). /// FORCE_INLINE GPUBuffer* GetBuffer() const { @@ -70,7 +75,7 @@ public: /// Amount of data to write (in bytes) FORCE_INLINE void Write(const void* bytes, int32 size) { - Data.Add((byte*)bytes, size); + Data.Add((const byte*)bytes, size); } /// @@ -97,7 +102,10 @@ public: /// /// Unlock buffer and flush data with a buffer (it will be ready for an immediate draw). /// - void Flush(); + void Flush() + { + Flush(nullptr); + } /// /// Unlock buffer and flush data with a buffer (it will be ready for a during next frame draw). @@ -133,10 +141,7 @@ public: protected: // [DynamicBuffer] - void InitDesc(GPUBufferDescription& desc, int32 numElements) override - { - desc = GPUBufferDescription::Vertex(_stride, numElements, GPUResourceUsage::Dynamic); - } + void InitDesc(GPUBufferDescription& desc, int32 numElements) override; }; /// @@ -158,10 +163,7 @@ public: protected: // [DynamicBuffer] - void InitDesc(GPUBufferDescription& desc, int32 numElements) override - { - desc = GPUBufferDescription::Index(_stride, numElements, GPUResourceUsage::Dynamic); - } + void InitDesc(GPUBufferDescription& desc, int32 numElements) override; }; /// @@ -180,11 +182,7 @@ public: /// Stride in bytes. /// True if unordered access usage. /// Buffer name. - DynamicStructuredBuffer(uint32 initialCapacity, uint32 stride, bool isUnorderedAccess = false, const String& name = String::Empty) - : DynamicBuffer(initialCapacity, stride, name) - , _isUnorderedAccess(isUnorderedAccess) - { - } + DynamicStructuredBuffer(uint32 initialCapacity, uint32 stride, bool isUnorderedAccess = false, const String& name = String::Empty); protected: // [DynamicBuffer]