Pārlūkot izejas kodu

Extract ring buffer handling for RT buffers to separate class (#17774)

* Extract ring buffer handling for RT buffers to separate class

Signed-off-by: Markus Prettner <[email protected]>

* Also use RingBuffer in DynamicBufferAllocator

Signed-off-by: Markus Prettner <[email protected]>

---------

Signed-off-by: Markus Prettner <[email protected]>
Markus Prettner 1 gadu atpakaļ
vecāks
revīzija
c7c6817c9a
25 mainītis faili ar 338 papildinājumiem un 337 dzēšanām
  1. 18 132
      Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp
  2. 8 13
      Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h
  3. 53 0
      Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/FrameCountMaxRingBuffer.h
  4. 1 0
      Gems/Atom/RHI/Code/atom_rhi_reflect_files.cmake
  5. 1 2
      Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingBlas.cpp
  6. 4 5
      Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingBlas.h
  7. 1 2
      Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingShaderTable.cpp
  8. 3 5
      Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingShaderTable.h
  9. 1 2
      Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingTlas.cpp
  10. 5 6
      Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingTlas.h
  11. 1 2
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingBlas.cpp
  12. 4 5
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingBlas.h
  13. 1 2
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingShaderTable.cpp
  14. 3 4
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingShaderTable.h
  15. 1 2
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingTlas.cpp
  16. 5 6
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingTlas.h
  17. 92 0
      Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Buffer/RingBuffer.h
  18. 14 21
      Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicBufferAllocator.h
  19. 2 2
      Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/RPISystemDescriptor.h
  20. 74 0
      Gems/Atom/RPI/Code/Source/RPI.Public/Buffer/RingBuffer.cpp
  21. 38 103
      Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicBufferAllocator.cpp
  22. 2 0
      Gems/Atom/RPI/Code/atom_rpi_public_files.cmake
  23. 1 1
      Gems/Atom/RPI/Registry/atom_rpi.setreg
  24. 2 18
      Gems/DiffuseProbeGrid/Code/Source/Render/DiffuseProbeGridFeatureProcessor.cpp
  25. 3 4
      Gems/DiffuseProbeGrid/Code/Source/Render/DiffuseProbeGridFeatureProcessor.h

+ 18 - 132
Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp

@@ -687,31 +687,7 @@ namespace AZ
         {
             if (m_meshInfoBufferNeedsUpdate)
             {
-                // advance to the next buffer in the frame list
-                m_currentMeshInfoFrameIndex = (m_currentMeshInfoFrameIndex + 1) % BufferFrameCount;
-
-                // update mesh info buffer
-                Data::Instance<RPI::Buffer>& currentMeshInfoGpuBuffer = m_meshInfoGpuBuffer[m_currentMeshInfoFrameIndex];
-                size_t newMeshByteCount = m_subMeshCount * sizeof(MeshInfo);
-
-                if (currentMeshInfoGpuBuffer == nullptr)
-                {
-                    // allocate the MeshInfo structured buffer
-                    RPI::CommonBufferDescriptor desc;
-                    desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly;
-                    desc.m_bufferName = "RayTracingMeshInfo";
-                    desc.m_byteCount = AZStd::max(newMeshByteCount, sizeof(MeshInfo));
-                    desc.m_elementSize = sizeof(MeshInfo);
-                    currentMeshInfoGpuBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
-                }
-                else if (currentMeshInfoGpuBuffer->GetBufferSize() < newMeshByteCount)
-                {
-                    // resize for the new sub-mesh count
-                    currentMeshInfoGpuBuffer->Resize(newMeshByteCount);
-                }
-
-                currentMeshInfoGpuBuffer->UpdateData(m_meshInfos.data(), newMeshByteCount);
-
+                m_meshInfoGpuBuffer.AdvanceCurrentBufferAndUpdateData(m_meshInfos);
                 m_meshInfoBufferNeedsUpdate = false;
             }
         }
@@ -723,8 +699,6 @@ namespace AZ
                 return;
             }
 
-            m_currentProceduralGeometryInfoFrameIndex = (m_currentProceduralGeometryInfoFrameIndex + 1) % BufferFrameCount;
-
             AZStd::vector<uint32_t> proceduralGeometryInfo;
             proceduralGeometryInfo.reserve(m_proceduralGeometry.size() * 2);
             for (const auto& proceduralGeometry : m_proceduralGeometry)
@@ -733,28 +707,7 @@ namespace AZ
                 proceduralGeometryInfo.push_back(proceduralGeometry.m_localInstanceIndex);
             }
 
-            Data::Instance<RPI::Buffer>& currentProceduralGeometryInfoGpuBuffer = m_proceduralGeometryInfoGpuBuffer[m_currentProceduralGeometryInfoFrameIndex];
-            size_t proceduralGeometryInfoByteCount = proceduralGeometryInfo.size() * sizeof(uint32_t);
-
-            if (currentProceduralGeometryInfoGpuBuffer == nullptr)
-            {
-                // allocate the MaterialInfo structured buffer
-                RPI::CommonBufferDescriptor desc;
-                desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly;
-                desc.m_bufferName = "ProceduralGeometryInfo";
-                desc.m_byteCount = proceduralGeometryInfoByteCount;
-                desc.m_elementSize = sizeof(uint32_t) * 2;
-                desc.m_elementFormat = RHI::Format::R32G32_UINT;
-                currentProceduralGeometryInfoGpuBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
-            }
-            else if (currentProceduralGeometryInfoGpuBuffer->GetBufferSize() < proceduralGeometryInfoByteCount)
-            {
-                // resize for the new index count
-                currentProceduralGeometryInfoGpuBuffer->Resize(proceduralGeometryInfoByteCount);
-            }
-
-            currentProceduralGeometryInfoGpuBuffer->UpdateData(proceduralGeometryInfo.data(), proceduralGeometryInfoByteCount);
-
+            m_proceduralGeometryInfoGpuBuffer.AdvanceCurrentBufferAndUpdateData(proceduralGeometryInfo);
             m_proceduralGeometryInfoBufferNeedsUpdate = false;
         }
 
@@ -762,33 +715,11 @@ namespace AZ
         {
             if (m_materialInfoBufferNeedsUpdate)
             {
-                // advance to the next buffer in the frame list
-                m_currentMaterialInfoFrameIndex = (m_currentMaterialInfoFrameIndex + 1) % BufferFrameCount;
-
-                // update MaterialInfo buffer
-                Data::Instance<RPI::Buffer>& currentMaterialInfoGpuBuffer = m_materialInfoGpuBuffer[m_currentMaterialInfoFrameIndex];
-                uint64_t meshMaterialInfoByteCount = m_subMeshCount * sizeof(MaterialInfo);
-                uint64_t proceduralMaterialInfoByteCount = m_proceduralGeometryMaterialInfos.size() * sizeof(MaterialInfo);
-                uint64_t newMaterialInfoByteCount = meshMaterialInfoByteCount + proceduralMaterialInfoByteCount;
-
-                if (currentMaterialInfoGpuBuffer == nullptr)
-                {
-                    // allocate the MaterialInfo structured buffer
-                    RPI::CommonBufferDescriptor desc;
-                    desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly;
-                    desc.m_bufferName = "RayTracingMaterialInfo";
-                    desc.m_byteCount = newMaterialInfoByteCount;
-                    desc.m_elementSize = sizeof(MaterialInfo);
-                    currentMaterialInfoGpuBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
-                }
-                else if (currentMaterialInfoGpuBuffer->GetBufferSize() < newMaterialInfoByteCount)
-                {
-                    // resize for the new sub-mesh count
-                    currentMaterialInfoGpuBuffer->Resize(newMaterialInfoByteCount);
-                }
-
-                currentMaterialInfoGpuBuffer->UpdateData(m_materialInfos.data(), meshMaterialInfoByteCount);
-                currentMaterialInfoGpuBuffer->UpdateData(m_proceduralGeometryMaterialInfos.data(), proceduralMaterialInfoByteCount, meshMaterialInfoByteCount);
+                m_materialInfoGpuBuffer.AdvanceCurrentElement();
+                m_materialInfoGpuBuffer.CreateOrResizeCurrentBufferWithElementCount<MaterialInfo>(
+                    m_subMeshCount + m_proceduralGeometryMaterialInfos.size());
+                m_materialInfoGpuBuffer.UpdateCurrentBufferData(m_materialInfos);
+                m_materialInfoGpuBuffer.UpdateCurrentBufferData(m_proceduralGeometryMaterialInfos, m_materialInfos.size());
 
                 m_materialInfoBufferNeedsUpdate = false;
             }
@@ -798,30 +729,6 @@ namespace AZ
         {
             if (m_indexListNeedsUpdate)
             {
-                // advance to the next buffer in the frame list
-                m_currentIndexListFrameIndex = (m_currentIndexListFrameIndex + 1) % BufferFrameCount;
-
-                // update mesh buffer indices buffer
-                Data::Instance<RPI::Buffer>& currentMeshBufferIndicesGpuBuffer = m_meshBufferIndicesGpuBuffer[m_currentIndexListFrameIndex];
-                size_t newMeshBufferIndicesByteCount = aznumeric_cast<uint32_t>(m_meshBufferIndices.GetIndexList().size()) * sizeof(uint32_t);
-
-                if (currentMeshBufferIndicesGpuBuffer == nullptr)
-                {
-                    // allocate the MeshBufferIndices buffer
-                    RPI::CommonBufferDescriptor desc;
-                    desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly;
-                    desc.m_bufferName = "RayTracingMeshBufferIndices";
-                    desc.m_byteCount = AZStd::max(newMeshBufferIndicesByteCount, sizeof(uint32_t));
-                    desc.m_elementSize = sizeof(IndexVector::value_type);
-                    desc.m_elementFormat = RHI::Format::R32_UINT;
-                    currentMeshBufferIndicesGpuBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
-                }
-                else if (currentMeshBufferIndicesGpuBuffer->GetBufferSize() < newMeshBufferIndicesByteCount)
-                {
-                    // resize for the new index count
-                    currentMeshBufferIndicesGpuBuffer->Resize(newMeshBufferIndicesByteCount);
-                }
-
 #if !USE_BINDLESS_SRG
                 // resolve to the true indices using the indirection list
                 // Note: this is done on the CPU to avoid double-indirection in the shader
@@ -839,32 +746,11 @@ namespace AZ
                     }
                 }
 
-                currentMeshBufferIndicesGpuBuffer->UpdateData(resolvedMeshBufferIndices.data(), newMeshBufferIndicesByteCount);
+                m_meshBufferIndicesGpuBuffer.AdvanceCurrentBufferAndUpdateData(resolvedMeshBufferIndices);
 #else
-                currentMeshBufferIndicesGpuBuffer->UpdateData(m_meshBufferIndices.GetIndexList().data(), newMeshBufferIndicesByteCount);
+                m_meshBufferIndicesGpuBuffer.AdvanceCurrentBufferAndUpdateData(m_meshBufferIndices.GetIndexList());
 #endif
 
-                // update material texture indices buffer
-                Data::Instance<RPI::Buffer>& currentMaterialTextureIndicesGpuBuffer = m_materialTextureIndicesGpuBuffer[m_currentIndexListFrameIndex];
-                size_t newMaterialTextureIndicesByteCount = m_materialTextureIndices.GetIndexList().size() * sizeof(uint32_t);
-
-                if (currentMaterialTextureIndicesGpuBuffer == nullptr)
-                {
-                    // allocate the MaterialInfo structured buffer
-                    RPI::CommonBufferDescriptor desc;
-                    desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly;
-                    desc.m_bufferName = "RayTracingMaterialTextureIndices";
-                    desc.m_byteCount = AZStd::max(newMaterialTextureIndicesByteCount, sizeof(uint32_t));
-                    desc.m_elementSize = sizeof(IndexVector::value_type);
-                    desc.m_elementFormat = RHI::Format::R32_UINT;
-                    currentMaterialTextureIndicesGpuBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
-                }
-                else if (currentMaterialTextureIndicesGpuBuffer->GetBufferSize() < newMaterialTextureIndicesByteCount)
-                {
-                    // resize for the new index count
-                    currentMaterialTextureIndicesGpuBuffer->Resize(newMaterialTextureIndicesByteCount);
-                }
-
 #if !USE_BINDLESS_SRG
                 // resolve to the true indices using the indirection list
                 // Note: this is done on the CPU to avoid double-indirection in the shader
@@ -882,9 +768,9 @@ namespace AZ
                     }
                 }
 
-                currentMaterialTextureIndicesGpuBuffer->UpdateData(resolvedMaterialTextureIndices.data(), newMaterialTextureIndicesByteCount);
+                m_materialTextureIndicesGpuBuffer.AdvanceCurrentBufferAndUpdateData(resolvedMaterialTextureIndices);
 #else
-                currentMaterialTextureIndicesGpuBuffer->UpdateData(m_materialTextureIndices.GetIndexList().data(), newMaterialTextureIndicesByteCount);
+                m_materialTextureIndicesGpuBuffer.AdvanceCurrentBufferAndUpdateData(m_materialTextureIndices.GetIndexList());
 #endif
 
                 m_indexListNeedsUpdate = false;
@@ -975,22 +861,22 @@ namespace AZ
                 m_rayTracingSceneSrg->SetConstant(constantIndex, imageBasedLightFeatureProcessor->GetExposure());
             }
 
-            if (m_meshInfoGpuBuffer[m_currentMeshInfoFrameIndex])
+            if (m_meshInfoGpuBuffer.IsCurrentBufferValid())
             {
                 bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_meshInfo"));
-                m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_meshInfoGpuBuffer[m_currentMeshInfoFrameIndex]->GetBufferView());
+                m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_meshInfoGpuBuffer.GetCurrentBufferView());
             }
 
             constantIndex = srgLayout->FindShaderInputConstantIndex(AZ::Name("m_meshInfoCount"));
             m_rayTracingSceneSrg->SetConstant(constantIndex, m_subMeshCount);
 
             bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_meshBufferIndices"));
-            m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_meshBufferIndicesGpuBuffer[m_currentIndexListFrameIndex]->GetBufferView());
+            m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_meshBufferIndicesGpuBuffer.GetCurrentBufferView());
 
-            if (m_proceduralGeometryInfoGpuBuffer[m_currentProceduralGeometryInfoFrameIndex])
+            if (m_proceduralGeometryInfoGpuBuffer.IsCurrentBufferValid())
             {
                 bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_proceduralGeometryInfo"));
-                m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_proceduralGeometryInfoGpuBuffer[m_currentProceduralGeometryInfoFrameIndex]->GetBufferView());
+                m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_proceduralGeometryInfoGpuBuffer.GetCurrentBufferView());
             }
 
 #if !USE_BINDLESS_SRG
@@ -1006,10 +892,10 @@ namespace AZ
             RHI::ShaderInputBufferIndex bufferIndex;
 
             bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_materialInfo"));
-            m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialInfoGpuBuffer[m_currentMaterialInfoFrameIndex]->GetBufferView());
+            m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialInfoGpuBuffer.GetCurrentBufferView());
 
             bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_materialTextureIndices"));
-            m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialTextureIndicesGpuBuffer[m_currentIndexListFrameIndex]->GetBufferView());
+            m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialTextureIndicesGpuBuffer.GetCurrentBufferView());
 
 #if !USE_BINDLESS_SRG
             RHI::ShaderInputImageUnboundedArrayIndex textureUnboundedArrayIndex = srgLayout->FindShaderInputImageUnboundedArrayIndex(AZ::Name("m_materialTextures"));

+ 8 - 13
Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h

@@ -15,6 +15,7 @@
 #include <Atom/RHI/RayTracingBufferPools.h>
 #include <Atom/RHI/BufferView.h>
 #include <Atom/RHI/ImageView.h>
+#include <Atom/RPI.Public/Buffer/RingBuffer.h>
 #include <Atom/RPI.Public/Shader/Shader.h>
 #include <Atom/Utils/StableDynamicArray.h>
 #include <AzCore/Math/Aabb.h>
@@ -355,10 +356,10 @@ namespace AZ
             RHI::AttachmentId GetTlasAttachmentId() const { return m_tlasAttachmentId; }
 
             //! Retrieves the GPU buffer containing information for all ray tracing meshes.
-            const Data::Instance<RPI::Buffer> GetMeshInfoGpuBuffer() const { return m_meshInfoGpuBuffer[m_currentMeshInfoFrameIndex]; }
+            const Data::Instance<RPI::Buffer> GetMeshInfoGpuBuffer() const { return m_meshInfoGpuBuffer.GetCurrentBuffer(); }
 
             //! Retrieves the GPU buffer containing information for all ray tracing materials.
-            const Data::Instance<RPI::Buffer> GetMaterialInfoGpuBuffer() const { return m_materialInfoGpuBuffer[m_currentMaterialInfoFrameIndex]; }
+            const Data::Instance<RPI::Buffer> GetMaterialInfoGpuBuffer() const { return m_materialInfoGpuBuffer.GetCurrentBuffer(); }
 
             //! Updates the RayTracingSceneSrg and RayTracingMaterialSrg, called after the TLAS is allocated in the RayTracingAccelerationStructurePass
             void UpdateRayTracingSrgs();
@@ -454,12 +455,8 @@ namespace AZ
             // vector of MeshInfo, transferred to the meshInfoGpuBuffer
             using MeshInfoVector = AZStd::vector<MeshInfo>;
             MeshInfoVector m_meshInfos;
-            static const uint32_t BufferFrameCount = 3;
-            Data::Instance<RPI::Buffer> m_meshInfoGpuBuffer[BufferFrameCount];
-            uint32_t m_currentMeshInfoFrameIndex = 0;
-
-            Data::Instance<RPI::Buffer> m_proceduralGeometryInfoGpuBuffer[BufferFrameCount];
-            uint32_t m_currentProceduralGeometryInfoFrameIndex = 0;
+            RPI::RingBuffer m_meshInfoGpuBuffer{ "RayTracingMeshInfo", RPI::CommonBufferPoolType::ReadOnly, sizeof(MeshInfo) };
+            RPI::RingBuffer m_proceduralGeometryInfoGpuBuffer{ "ProceduralGeometryInfo", RPI::CommonBufferPoolType::ReadOnly, RHI::Format::R32G32_UINT };
 
             // structure for data in the m_materialInfoBuffer, shaders that use the buffer must match this type
             struct alignas(16) MaterialInfo
@@ -492,8 +489,7 @@ namespace AZ
             using MaterialInfoVector = AZStd::vector<MaterialInfo>;
             MaterialInfoVector m_materialInfos;
             MaterialInfoVector m_proceduralGeometryMaterialInfos;
-            Data::Instance<RPI::Buffer> m_materialInfoGpuBuffer[BufferFrameCount];
-            uint32_t m_currentMaterialInfoFrameIndex = 0;
+            RPI::RingBuffer m_materialInfoGpuBuffer{ "RayTracingMaterialInfo", RPI::CommonBufferPoolType::ReadOnly, sizeof(MaterialInfo) };
 
             // update flags
             bool m_meshInfoBufferNeedsUpdate = false;
@@ -525,9 +521,8 @@ namespace AZ
             RayTracingIndexList<NumMaterialTexturesPerMesh> m_materialTextureIndices;
 
             // Gpu buffers for the mesh and material index lists
-            Data::Instance<RPI::Buffer> m_meshBufferIndicesGpuBuffer[BufferFrameCount];
-            Data::Instance<RPI::Buffer> m_materialTextureIndicesGpuBuffer[BufferFrameCount];
-            uint32_t m_currentIndexListFrameIndex = 0;
+            RPI::RingBuffer m_meshBufferIndicesGpuBuffer{ "RayTracingMeshBufferIndices", RPI::CommonBufferPoolType::ReadOnly, RHI::Format::R32_UINT };
+            RPI::RingBuffer m_materialTextureIndicesGpuBuffer{ "RayTracingMaterialTextureIndices", RPI::CommonBufferPoolType::ReadOnly, RHI::Format::R32_UINT };
 
             uint32_t m_skinnedMeshCount = 0;
 

+ 53 - 0
Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/FrameCountMaxRingBuffer.h

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <Atom/RHI.Reflect/Limits.h>
+#include <AzCore/std/containers/array.h>
+
+namespace AZ::RHI
+{
+    //! A class which manages a FrameCountMax number of objects and manages them in a ring buffer structure, meaning that whenever an
+    //! element needs to be updated, the current element index is incremented (mod FrameCountMax), which leaves the other elements
+    //! unchanged, which is necessary for some resources if the GPU and CPU are not in sync.
+    template<typename T>
+    class FrameCountMaxRingBuffer
+    {
+    private:
+        AZStd::array<T, Limits::Device::FrameCountMax> m_elements;
+        unsigned m_currentElementIndex{ 0 };
+
+    public:
+        //! Increments the current element index (mod FrameCountMax) and returns a reference the new current element. This should happen at
+        //! most once per frame.
+        T& AdvanceCurrentElement()
+        {
+            m_currentElementIndex = (m_currentElementIndex + 1) % Limits::Device::FrameCountMax;
+            return GetCurrentElement();
+        }
+
+        //! Returns the current element
+        const T& GetCurrentElement() const
+        {
+            return m_elements[m_currentElementIndex];
+        }
+
+        //! Returns the current element
+        T& GetCurrentElement()
+        {
+            return m_elements[m_currentElementIndex];
+        }
+
+        //! Returns the number of elements that are managed by this class
+        unsigned GetElementCount() const
+        {
+            return Limits::Device::FrameCountMax;
+        }
+    };
+} // namespace AZ::RHI

+ 1 - 0
Gems/Atom/RHI/Code/atom_rhi_reflect_files.cmake

@@ -62,6 +62,7 @@ set(FILES
     Source/RHI.Reflect/PhysicalDeviceDescriptor.cpp
     Include/Atom/RHI.Reflect/PhysicalDeviceDriverInfoSerializer.h
     Source/RHI.Reflect/PhysicalDeviceDriverInfoSerializer.cpp
+    Include/Atom/RHI.Reflect/FrameCountMaxRingBuffer.h
     Include/Atom/RHI.Reflect/InputStreamLayout.h
     Include/Atom/RHI.Reflect/InputStreamLayoutBuilder.h
     Include/Atom/RHI.Reflect/MultisampleState.h

+ 1 - 2
Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingBlas.cpp

@@ -30,8 +30,7 @@ namespace AZ
             ID3D12DeviceX* dx12Device = device.GetDevice();
 
             // advance to the next buffer
-            m_currentBufferIndex = (m_currentBufferIndex + 1) % BufferCount;
-            BlasBuffers& buffers = m_buffers[m_currentBufferIndex];
+            BlasBuffers& buffers = m_buffers.AdvanceCurrentElement();
 
             m_geometryDescs.clear();
 

+ 4 - 5
Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingBlas.h

@@ -7,6 +7,7 @@
  */
 #pragma once
 
+#include <Atom/RHI.Reflect/FrameCountMaxRingBuffer.h>
 #include <Atom/RHI/RayTracingAccelerationStructure.h>
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/std/smart_ptr/unique_ptr.h>
@@ -37,10 +38,10 @@ namespace AZ
 #ifdef AZ_DX12_DXR_SUPPORT
             const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS& GetInputs() const { return m_inputs; }
 #endif
-            const BlasBuffers& GetBuffers() const { return m_buffers[m_currentBufferIndex]; }
+            const BlasBuffers& GetBuffers() const { return m_buffers.GetCurrentElement(); }
 
             // RHI::RayTracingBlas overrides...
-            virtual bool IsValid() const override { return m_buffers[m_currentBufferIndex].m_blasBuffer != nullptr; }
+            virtual bool IsValid() const override { return GetBuffers().m_blasBuffer != nullptr; }
 
         private:
             RayTracingBlas() = default;
@@ -56,9 +57,7 @@ namespace AZ
 #endif
 
             // buffer list to keep buffers alive for several frames
-            static const uint32_t BufferCount = AZ::RHI::Limits::Device::FrameCountMax;
-            BlasBuffers m_buffers[BufferCount];
-            uint32_t m_currentBufferIndex = 0;
+            RHI::FrameCountMaxRingBuffer<BlasBuffers> m_buffers;
         };
     }
 }

+ 1 - 2
Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingShaderTable.cpp

@@ -124,8 +124,7 @@ namespace AZ
         {
 #ifdef AZ_DX12_DXR_SUPPORT
             // advance to the next buffer
-            m_currentBufferIndex = (m_currentBufferIndex + 1) % BufferCount;
-            ShaderTableBuffers& buffers = m_buffers[m_currentBufferIndex];
+            ShaderTableBuffers& buffers = m_buffers.AdvanceCurrentElement();
 
             // clear the shader table if the descriptor has no ray generation shader
             if (m_descriptor->GetRayGenerationRecord().empty())

+ 3 - 5
Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingShaderTable.h

@@ -8,6 +8,7 @@
 #pragma once
 
 #include <Atom/RHI/RayTracingShaderTable.h>
+#include <Atom/RHI.Reflect/FrameCountMaxRingBuffer.h>
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/std/smart_ptr/unique_ptr.h>
 
@@ -44,7 +45,7 @@ namespace AZ
                 uint32_t m_hitGroupTableStride = 0;
             };
 
-            const ShaderTableBuffers& GetBuffers() const { return m_buffers[m_currentBufferIndex]; }
+            const ShaderTableBuffers& GetBuffers() const { return m_buffers.GetCurrentElement(); }
 
         private:
 
@@ -65,10 +66,7 @@ namespace AZ
             RHI::ResultCode BuildInternal() override;
             //////////////////////////////////////////////////////////////////////////
 
-            static const uint32_t BufferCount = AZ::RHI::Limits::Device::FrameCountMax;
-            ShaderTableBuffers m_buffers[BufferCount];
-
-            uint32_t m_currentBufferIndex = 0;
+            RHI::FrameCountMaxRingBuffer<ShaderTableBuffers> m_buffers;
         };
     }
 }

+ 1 - 2
Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingTlas.cpp

@@ -31,8 +31,7 @@ namespace AZ
             ID3D12DeviceX* dx12Device = device.GetDevice();
 
             // advance to the next buffer
-            m_currentBufferIndex = (m_currentBufferIndex + 1) % BufferCount;
-            TlasBuffers& buffers = m_buffers[m_currentBufferIndex];
+            TlasBuffers& buffers = m_buffers.AdvanceCurrentElement();
 
             const RHI::RayTracingTlasInstanceVector& instances = descriptor->GetInstances();
             if (instances.empty())

+ 5 - 6
Gems/Atom/RHI/DX12/Code/Source/RHI/RayTracingTlas.h

@@ -9,6 +9,7 @@
 
 #include <RHI/DX12.h>
 #include <Atom/RHI/RayTracingAccelerationStructure.h>
+#include <Atom/RHI.Reflect/FrameCountMaxRingBuffer.h>
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/std/smart_ptr/unique_ptr.h>
 
@@ -37,11 +38,11 @@ namespace AZ
 #ifdef AZ_DX12_DXR_SUPPORT
             const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS& GetInputs() const { return m_inputs; }
 #endif
-            const TlasBuffers& GetBuffers() const { return m_buffers[m_currentBufferIndex]; }
+            const TlasBuffers& GetBuffers() const { return m_buffers.GetCurrentElement(); }
 
             // RHI::RayTracingTlas overrides...
-            const RHI::Ptr<RHI::Buffer> GetTlasBuffer() const override { return m_buffers[m_currentBufferIndex].m_tlasBuffer; }
-            const RHI::Ptr<RHI::Buffer> GetTlasInstancesBuffer() const override { return m_buffers[m_currentBufferIndex].m_tlasInstancesBuffer; }
+            const RHI::Ptr<RHI::Buffer> GetTlasBuffer() const override { return GetBuffers().m_tlasBuffer; }
+            const RHI::Ptr<RHI::Buffer> GetTlasInstancesBuffer() const override { return GetBuffers().m_tlasInstancesBuffer; }
 
         private:
             RayTracingTlas() = default;
@@ -54,9 +55,7 @@ namespace AZ
 #endif
 
             // buffer list to keep buffers alive for several frames
-            static const uint32_t BufferCount = AZ::RHI::Limits::Device::FrameCountMax;
-            TlasBuffers m_buffers[BufferCount];
-            uint32_t m_currentBufferIndex = 0;
+            RHI::FrameCountMaxRingBuffer<TlasBuffers> m_buffers;
         };
     }
 }

+ 1 - 2
Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingBlas.cpp

@@ -31,8 +31,7 @@ namespace AZ
             const VkPhysicalDeviceAccelerationStructurePropertiesKHR& accelerationStructureProperties = physicalDevice.GetPhysicalDeviceAccelerationStructureProperties();
 
             // advance to the next buffer
-            m_currentBufferIndex = (m_currentBufferIndex + 1) % BufferCount;
-            BlasBuffers& buffers = m_buffers[m_currentBufferIndex];
+            BlasBuffers& buffers = m_buffers.AdvanceCurrentElement();
 
             if (buffers.m_accelerationStructure)
             {

+ 4 - 5
Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingBlas.h

@@ -7,6 +7,7 @@
  */
 #pragma once
 
+#include <Atom/RHI.Reflect/FrameCountMaxRingBuffer.h>
 #include <Atom_RHI_Vulkan_Platform.h>
 #include <Atom/RHI/RayTracingAccelerationStructure.h>
 #include <AzCore/Memory/SystemAllocator.h>
@@ -39,10 +40,10 @@ namespace AZ
                 VkAccelerationStructureBuildGeometryInfoKHR m_buildInfo = {};
             };
 
-            const BlasBuffers& GetBuffers() const { return m_buffers[m_currentBufferIndex]; }
+            const BlasBuffers& GetBuffers() const { return m_buffers.GetCurrentElement(); }
 
             // RHI::RayTracingBlas overrides...
-            virtual bool IsValid() const override { return m_buffers[m_currentBufferIndex].m_accelerationStructure != VK_NULL_HANDLE; }
+            virtual bool IsValid() const override { return GetBuffers().m_accelerationStructure != VK_NULL_HANDLE; }
 
         private:
             RayTracingBlas() = default;
@@ -53,9 +54,7 @@ namespace AZ
             static VkBuildAccelerationStructureFlagsKHR GetAccelerationStructureBuildFlags(const RHI::RayTracingAccelerationStructureBuildFlags &buildFlags);
 
             // buffer list to keep buffers alive for several frames
-            static const uint32_t BufferCount = 3;
-            BlasBuffers m_buffers[BufferCount];
-            uint32_t m_currentBufferIndex = 0;
+            RHI::FrameCountMaxRingBuffer<BlasBuffers> m_buffers;
         };
     }
 }

+ 1 - 2
Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingShaderTable.cpp

@@ -83,8 +83,7 @@ namespace AZ
             uint32_t alignedShaderHandleSize = RHI::AlignUp(shaderHandleSize, rayTracingPipelineProperties.shaderGroupBaseAlignment);
 
             // advance to the next buffer
-            m_currentBufferIndex = (m_currentBufferIndex + 1) % BufferCount;
-            ShaderTableBuffers& buffers = m_buffers[m_currentBufferIndex];
+            ShaderTableBuffers& buffers = m_buffers.AdvanceCurrentElement();
 
             // clear the shader table if the descriptor has no ray generation shader
             if (m_descriptor->GetRayGenerationRecord().empty())

+ 3 - 4
Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingShaderTable.h

@@ -8,6 +8,7 @@
 #pragma once
 
 #include <Atom/RHI/RayTracingShaderTable.h>
+#include <Atom/RHI.Reflect/FrameCountMaxRingBuffer.h>
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/std/smart_ptr/unique_ptr.h>
 #include <Atom/RHI/Buffer.h>
@@ -43,7 +44,7 @@ namespace AZ
                 uint32_t m_hitGroupTableStride = 0;
             };
 
-            const ShaderTableBuffers& GetBuffers() const { return m_buffers[m_currentBufferIndex]; }
+            const ShaderTableBuffers& GetBuffers() const { return m_buffers.GetCurrentElement(); }
 
         private:
 
@@ -62,9 +63,7 @@ namespace AZ
             RHI::ResultCode BuildInternal() override;
             //////////////////////////////////////////////////////////////////////////
 
-            static const uint32_t BufferCount = 3;
-            ShaderTableBuffers m_buffers[BufferCount];
-            uint32_t m_currentBufferIndex = 0;
+            RHI::FrameCountMaxRingBuffer<ShaderTableBuffers> m_buffers;
         };
     }
 }

+ 1 - 2
Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingTlas.cpp

@@ -33,8 +33,7 @@ namespace AZ
             const VkPhysicalDeviceAccelerationStructurePropertiesKHR& accelerationStructureProperties = physicalDevice.GetPhysicalDeviceAccelerationStructureProperties();
                         
             // advance to the next buffer
-            m_currentBufferIndex = (m_currentBufferIndex + 1) % BufferCount;
-            TlasBuffers& buffers = m_buffers[m_currentBufferIndex];
+            TlasBuffers& buffers = m_buffers.AdvanceCurrentElement();
 
             if (buffers.m_accelerationStructure)
             {

+ 5 - 6
Gems/Atom/RHI/Vulkan/Code/Source/RHI/RayTracingTlas.h

@@ -9,6 +9,7 @@
 
 #include <Atom_RHI_Vulkan_Platform.h>
 #include <Atom/RHI/RayTracingAccelerationStructure.h>
+#include <Atom/RHI.Reflect/FrameCountMaxRingBuffer.h>
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/std/smart_ptr/unique_ptr.h>
 
@@ -40,11 +41,11 @@ namespace AZ
                 uint32_t m_instanceCount = 0;
             };
 
-            const TlasBuffers& GetBuffers() const { return m_buffers[m_currentBufferIndex]; }
+            const TlasBuffers& GetBuffers() const { return m_buffers.GetCurrentElement(); }
 
             // RHI::RayTracingTlas overrides...
-            const RHI::Ptr<RHI::Buffer> GetTlasBuffer() const override { return m_buffers[m_currentBufferIndex].m_tlasBuffer; }
-            const RHI::Ptr<RHI::Buffer> GetTlasInstancesBuffer() const override { return m_buffers[m_currentBufferIndex].m_tlasInstancesBuffer; }
+            const RHI::Ptr<RHI::Buffer> GetTlasBuffer() const override { return GetBuffers().m_tlasBuffer; }
+            const RHI::Ptr<RHI::Buffer> GetTlasInstancesBuffer() const override { return GetBuffers().m_tlasInstancesBuffer; }
 
         private:
             RayTracingTlas() = default;
@@ -53,9 +54,7 @@ namespace AZ
             RHI::ResultCode CreateBuffersInternal(RHI::Device& deviceBase, const RHI::RayTracingTlasDescriptor* descriptor, const RHI::RayTracingBufferPools& rayTracingBufferPools) override;
 
             // buffer list to keep buffers alive for several frames
-            static const uint32_t BufferCount = 3;
-            TlasBuffers m_buffers[BufferCount];
-            uint32_t m_currentBufferIndex = 0;
+            RHI::FrameCountMaxRingBuffer<TlasBuffers> m_buffers;
         };
     }
 }

+ 92 - 0
Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Buffer/RingBuffer.h

@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <Atom/RHI.Reflect/Format.h>
+#include <Atom/RHI.Reflect/FrameCountMaxRingBuffer.h>
+#include <Atom/RPI.Public/Buffer/Buffer.h>
+#include <Atom/RPI.Public/Buffer/BufferSystemInterface.h>
+#include <AzCore/std/containers/span.h>
+#include <AzCore/std/string/string.h>
+
+namespace AZ::RPI
+{
+    //! A class which manages a FrameCountMax number of RPI buffers and manages them in a ring buffer structure, meaning that whenever data
+    //! needs to be updated, the current buffer index is incremented (mod FrameCountMax) and the data is then written to the new current
+    //! buffer, such that the other buffers stay valid.
+    class RingBuffer : public RHI::FrameCountMaxRingBuffer<Data::Instance<Buffer>>
+    {
+        AZStd::string m_bufferName;
+        CommonBufferPoolType m_bufferPoolType{ CommonBufferPoolType::ReadOnly };
+        uint32_t m_elementSize{ 1 };
+        RHI::Format m_bufferFormat{ RHI::Format::Unknown };
+
+    public:
+        //! Creates a set of buffers with a name and a format. The element size is derived from the format.
+        RingBuffer(const AZStd::string& bufferName, CommonBufferPoolType bufferPoolType, RHI::Format bufferFormat);
+
+        //! Creates a set of buffers with a name and an element size. The format is set to unknown.
+        RingBuffer(const AZStd::string& bufferName, CommonBufferPoolType bufferPoolType, uint32_t elementSize);
+
+        //! Returns true if the current buffer was already created and is not empty.
+        bool IsCurrentBufferValid() const;
+
+        //! Returns the current buffer
+        const Data::Instance<Buffer>& GetCurrentBuffer() const;
+
+        //! Returns a RHI view of the current buffer
+        const RHI::BufferView* GetCurrentBufferView() const;
+
+        //! Increments the current buffer index, potentially resized the current buffer and updates the data of the current data. This is a
+        //! convenience function which calls AdvanceCurrentBuffer(), CreateOrResizeCurrentBuffer(...) and UpdateCurrentBufferData(...)
+        void AdvanceCurrentBufferAndUpdateData(const void* data, u64 dataSizeInBytes);
+
+        //! Increments the current buffer index, potentially resized the current buffer and updates the data of the current data. This is a
+        //! convenience function which calls AdvanceCurrentBuffer(), CreateOrResizeCurrentBuffer(...) and UpdateCurrentBufferData(...)
+        template<typename T>
+        void AdvanceCurrentBufferAndUpdateData(AZStd::span<const T> data)
+        {
+            AdvanceCurrentBufferAndUpdateData(data.data(), data.size() * sizeof(T));
+        }
+
+        //! Convenience function which allows automatic conversion from vector/array to span
+        template<typename T>
+        void AdvanceCurrentBufferAndUpdateData(const T& data)
+        {
+            AdvanceCurrentBufferAndUpdateData<typename T::value_type>(data);
+        }
+
+        //! Creates or resizes the current buffer to fit the given number of bytes.
+        void CreateOrResizeCurrentBuffer(u64 bufferSizeInBytes);
+
+        //! Creates or resizes the current buffer to fit the given number of elements.
+        template<typename T>
+        void CreateOrResizeCurrentBufferWithElementCount(u64 elementCount)
+        {
+            CreateOrResizeCurrentBuffer(elementCount * sizeof(T));
+        }
+
+        //! Updates the data of the current buffer.
+        void UpdateCurrentBufferData(const void* data, u64 dataSizeInBytes, u64 bufferOffsetInBytes);
+
+        //! Updates the data of the current buffer.
+        template<typename T>
+        void UpdateCurrentBufferData(AZStd::span<const T> data, u64 bufferElementOffset = 0)
+        {
+            UpdateCurrentBufferData(data.data(), data.size() * sizeof(T), bufferElementOffset * sizeof(T));
+        }
+
+        //! Convenience function which allows automatic conversion from vector/array to span
+        template<typename T>
+        void UpdateCurrentBufferData(const T& data, u64 bufferElementOffset = 0)
+        {
+            UpdateCurrentBufferData<typename T::value_type>(data, bufferElementOffset);
+        }
+    };
+} // namespace AZ::RPI

+ 14 - 21
Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicBufferAllocator.h

@@ -8,14 +8,9 @@
 
 #pragma once
 
-#include <AtomCore/Instance/Instance.h>
-
 #include <Atom/RHI/IndexBufferView.h>
 #include <Atom/RHI/StreamBufferView.h>
-
-#include <Atom/RPI.Public/Base.h>
-#include <Atom/RPI.Public/Buffer/Buffer.h>
-
+#include <Atom/RPI.Public/Buffer/RingBuffer.h>
 
 namespace AZ
 {
@@ -28,7 +23,7 @@ namespace AZ
         //! Since the allocations are sub-allocations they almost have zero cost with both cpu and gpu.
         //! Limitation: the allocation may fail if the request buffer size is larger than the ring buffer size or
         //!     there isn't enough unused memory available within the ring buffer. User may increase the input of Init(ringBufferSize)
-        //!     to increase the ring buffer's size. 
+        //!     to increase the ring buffer's size.
         class DynamicBufferAllocator
         {
         public:
@@ -38,13 +33,14 @@ namespace AZ
             virtual ~DynamicBufferAllocator() = default;
 
             //! One time initialization
-            //! This operation may be slow since it will allocate large size gpu resource. 
+            //! This operation may be slow since it will allocate large size gpu resource.
             void Init(uint32_t ringBufferSize);
 
             void Shutdown();
 
             //! Allocate a dynamic buffer with specified size and alignment
-            //! It may return nullptr if the input size is larger than ring buffer size or there isn't enough unused memory available within the ring buffer
+            //! It may return nullptr if the input size is larger than ring buffer size or there isn't enough unused memory available within
+            //! the ring buffer
             RHI::Ptr<DynamicBuffer> Allocate(uint32_t size, uint32_t alignment);
 
             //! Get an IndexBufferView for a DynamicBuffer used as an index buffer
@@ -65,20 +61,17 @@ namespace AZ
 
             // The position where the buffer is available.
             uint32_t m_currentPosition = 0;
-            // The upper bound limit of the allocation of current frame 
-            uint32_t m_endPositionLimit = 0;
-            // The allocated buffer size of current frame
-            uint32_t m_currentAllocatedSize = 0;
 
+            // The size of the buffer per frame
             uint32_t m_ringBufferSize = 0;
-            void* m_ringBufferStartAddress = 0;
-            Data::Instance<Buffer> m_ringBuffer;
-
-            // Allocation history which are in use by GPU. 
-            uint32_t m_frameStartPositions[AZ::RHI::Limits::Device::FrameCountMax];
-            uint32_t m_currentFrame = 0;
 
             bool m_enableAllocationWarning = false;
+
+            // The resident buffer data per frame
+            RingBuffer m_bufferData{ "DynamicBufferAllocator", CommonBufferPoolType::DynamicInputAssembly, 1 };
+
+            // The CPU address of the mapped buffer per frame
+            RHI::FrameCountMaxRingBuffer<void*> m_bufferStartAddresses;
         };
-    }
-}
+    } // namespace RPI
+} // namespace AZ

+ 2 - 2
Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/RPISystemDescriptor.h

@@ -21,8 +21,8 @@ namespace AZ
         {
             AZ_TYPE_INFO(DynamicDrawSystemDescriptor, "{BC1F1C0A-4A87-4D30-A331-BE8358A23F6D}");
 
-            //! The maxinum size of pool which is used to allocate dynamic buffers for dynamic draw system
-            uint32_t m_dynamicBufferPoolSize = 3 * 16 * 1024 * 1024;
+            //! The maxinum size of pool which is used to allocate dynamic buffers for dynamic draw system per frame
+            uint32_t m_dynamicBufferPoolSize = 16 * 1024 * 1024;
         };
 
         struct RPISystemDescriptor final

+ 74 - 0
Gems/Atom/RPI/Code/Source/RPI.Public/Buffer/RingBuffer.cpp

@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <Atom/RPI.Public/Buffer/RingBuffer.h>
+
+namespace AZ::RPI
+{
+    RingBuffer::RingBuffer(const AZStd::string& bufferName, CommonBufferPoolType bufferPoolType, RHI::Format bufferFormat)
+        : m_bufferName{ bufferName }
+        , m_bufferPoolType{ bufferPoolType }
+        , m_bufferFormat{ bufferFormat }
+        , m_elementSize{ RHI::GetFormatSize(bufferFormat) }
+    {
+    }
+
+    RingBuffer::RingBuffer(const AZStd::string& bufferName, CommonBufferPoolType bufferPoolType, uint32_t elementSize)
+        : m_bufferName{ bufferName }
+        , m_bufferPoolType{ bufferPoolType }
+        , m_elementSize{ elementSize }
+    {
+    }
+
+    bool RingBuffer::IsCurrentBufferValid() const
+    {
+        return GetCurrentBuffer();
+    }
+
+    const Data::Instance<Buffer>& RingBuffer::GetCurrentBuffer() const
+    {
+        return GetCurrentElement();
+    }
+
+    const RHI::BufferView* RingBuffer::GetCurrentBufferView() const
+    {
+        return GetCurrentBuffer()->GetBufferView();
+    }
+
+    void RingBuffer::AdvanceCurrentBufferAndUpdateData(const void* data, u64 dataSizeInBytes)
+    {
+        AdvanceCurrentElement();
+        CreateOrResizeCurrentBuffer(dataSizeInBytes);
+        UpdateCurrentBufferData(data, dataSizeInBytes, 0);
+    }
+
+    void RingBuffer::CreateOrResizeCurrentBuffer(u64 bufferSizeInBytes)
+    {
+        auto& currentBuffer{ GetCurrentElement() };
+        if (!currentBuffer)
+        {
+            CommonBufferDescriptor desc;
+            desc.m_bufferName = m_bufferName;
+            desc.m_poolType = m_bufferPoolType;
+            desc.m_elementSize = m_elementSize;
+            desc.m_elementFormat = m_bufferFormat;
+            desc.m_byteCount = bufferSizeInBytes;
+            currentBuffer = BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
+        }
+        else if (currentBuffer->GetBufferSize() < bufferSizeInBytes)
+        {
+            currentBuffer->Resize(bufferSizeInBytes);
+        }
+    }
+
+    void RingBuffer::UpdateCurrentBufferData(const void* data, u64 dataSizeInBytes, u64 bufferOffsetInBytes)
+    {
+        auto& currentBuffer{ GetCurrentBuffer() };
+        currentBuffer->UpdateData(data, dataSizeInBytes, bufferOffsetInBytes);
+    }
+} // namespace AZ::RPI

+ 38 - 103
Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicBufferAllocator.cpp

@@ -6,10 +6,8 @@
  *
  */
 
-#include <Atom/RPI.Public/Buffer/BufferSystemInterface.h>
-#include <Atom/RPI.Public/Buffer/Buffer.h>
-#include <Atom/RPI.Public/DynamicDraw/DynamicBufferAllocator.h>
 #include <Atom/RPI.Public/DynamicDraw/DynamicBuffer.h>
+#include <Atom/RPI.Public/DynamicDraw/DynamicBufferAllocator.h>
 
 namespace AZ
 {
@@ -17,141 +15,87 @@ namespace AZ
     {
         void DynamicBufferAllocator::Init(uint32_t ringBufferSize)
         {
-            if (m_ringBuffer)
+            if (m_bufferData.IsCurrentBufferValid())
             {
                 AZ_Assert(false, "DynamicBufferAllocator was already initialized");
                 return;
             }
 
-            // Create the ring buffer from common pool
-            RPI::CommonBufferDescriptor desc;
-            desc.m_poolType = RPI::CommonBufferPoolType::DynamicInputAssembly;
-            desc.m_bufferName = "DyanmicBufferRing";
-            desc.m_elementSize = 1;
-            desc.m_byteCount = ringBufferSize;
-            m_ringBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
-
-            if (m_ringBuffer.get() == nullptr)
-            {
-                AZ_Assert(false, "Failed to initialize DyanmicBufferAllocator");
-                return;
-            }
-            
             m_ringBufferSize = ringBufferSize;
-            m_ringBufferStartAddress = m_ringBuffer->Map(m_ringBufferSize, 0);
-            
-            m_currentPosition = 0;
-            m_endPositionLimit = 0;
-            m_currentFrame = 0;
-            for (uint32_t frame = 0; frame < AZ::RHI::Limits::Device::FrameCountMax; frame++)
+
+            for (unsigned i{ 0 }; i < m_bufferData.GetElementCount(); i++)
             {
-                m_frameStartPositions[frame] = 0;
+                m_bufferData.CreateOrResizeCurrentBuffer(m_ringBufferSize);
+                m_bufferStartAddresses.GetCurrentElement() = m_bufferData.GetCurrentElement()->Map(m_ringBufferSize, 0);
+                m_bufferData.AdvanceCurrentElement();
+                m_bufferStartAddresses.AdvanceCurrentElement();
             }
         }
 
         void DynamicBufferAllocator::Shutdown()
         {
-            m_ringBuffer->Unmap();
-            m_ringBuffer = nullptr;
-            m_ringBufferStartAddress = nullptr;
+            for (unsigned i{ 0 }; i < m_bufferData.GetElementCount(); i++)
+            {
+                m_bufferData.AdvanceCurrentElement()->Unmap();
+                m_bufferStartAddresses.AdvanceCurrentElement() = nullptr;
+            }
         }
 
-        // [GFX TODO][ATOM-13182] Add unit tests for DynamicBufferAllocator's Allocate function 
-        RHI::Ptr<DynamicBuffer> DynamicBufferAllocator::Allocate(uint32_t size, [[maybe_unused]]uint32_t alignment)
+        // [GFX TODO][ATOM-13182] Add unit tests for DynamicBufferAllocator's Allocate function
+        RHI::Ptr<DynamicBuffer> DynamicBufferAllocator::Allocate(uint32_t size, uint32_t alignment)
         {
-            size = RHI::AlignUp(size, alignment);
-            uint32_t allocatePosition = 0;
-
-            //m_ringBufferStartAddress can be null for Null back end
-            if (!m_ringBufferStartAddress)
+            // m_bufferData can be invalid for Null back end
+            if (!m_bufferStartAddresses.GetCurrentElement())
             {
                 return nullptr;
             }
 
+            m_currentPosition = RHI::AlignUp(m_currentPosition, alignment);
+
             if (size > m_ringBufferSize)
             {
-                AZ_WarningOnce("RPI", !m_enableAllocationWarning, "DynamicBufferAllocator::Allocate: try to allocate buffer which size is larger than the ring buffer size");
+                AZ_WarningOnce(
+                    "RPI",
+                    !m_enableAllocationWarning,
+                    "DynamicBufferAllocator::Allocate: try to allocate buffer which size is larger than the ring buffer size");
                 return nullptr;
             }
 
             // Return if the allocation of current frame has reached limit
-            if (m_endPositionLimit == m_currentPosition && m_currentAllocatedSize > 0)
+            if (m_currentPosition + size > m_ringBufferSize)
             {
-                AZ_WarningOnce("RPI", !m_enableAllocationWarning, "DynamicBufferAllocator::Allocate: no more buffer is available");
+                AZ_WarningOnce("RPI", !m_enableAllocationWarning, "DynamicBufferAllocator::Allocate: no more buffer space is available");
                 return nullptr;
             }
 
-            if (m_endPositionLimit > m_currentPosition)
-            {
-                if (m_endPositionLimit - m_currentPosition >= size)
-                {
-                    allocatePosition = m_currentPosition;
-                    m_currentPosition += size;
-                }
-                else
-                {
-                    AZ_WarningOnce("RPI", !m_enableAllocationWarning, "DynamicBufferAllocator::Allocate: requested size (%d bytes) is larger than the size left (%d bytes)", size, m_endPositionLimit - m_currentPosition);
-                    return nullptr;
-                }
-            }
-            else
-            {
-                if (m_ringBufferSize - m_currentPosition >= size)
-                {
-                    allocatePosition = m_currentPosition;
-                    m_currentPosition += size;
-                    if (m_ringBufferSize == m_currentPosition)
-                    {
-                        m_currentPosition = 0;
-                    }
-                }
-                else
-                {
-                    if (m_endPositionLimit >= size)
-                    {
-                        allocatePosition = 0;
-                        m_currentPosition = size;
-                    }
-                    else
-                    {
-                        AZ_WarningOnce("RPI", !m_enableAllocationWarning, "DynamicBufferAllocator::Allocate: requested size (%d bytes) is larger than the size left (%d bytes)", size, m_endPositionLimit);
-                        return nullptr;
-                    }
-                }
-            }
-
-            m_currentAllocatedSize += size;
-
             RHI::Ptr<DynamicBuffer> allocatedBuffer = aznew DynamicBuffer();
-            allocatedBuffer->m_address = (uint8_t*)m_ringBufferStartAddress + allocatePosition;
+            allocatedBuffer->m_address = (uint8_t*)m_bufferStartAddresses.GetCurrentElement() + m_currentPosition;
             allocatedBuffer->m_size = size;
             allocatedBuffer->m_allocator = this;
+
+            m_currentPosition += size;
+
             return allocatedBuffer;
         }
 
         RHI::IndexBufferView DynamicBufferAllocator::GetIndexBufferView(RHI::Ptr<DynamicBuffer> dynamicBuffer, RHI::IndexFormat format)
         {
             return RHI::IndexBufferView(
-                *m_ringBuffer->GetRHIBuffer(),
-                GetBufferAddressOffset(dynamicBuffer),
-                dynamicBuffer->m_size,
-                format
-            );
+                *m_bufferData.GetCurrentElement()->GetRHIBuffer(), GetBufferAddressOffset(dynamicBuffer), dynamicBuffer->m_size, format);
         }
 
         RHI::StreamBufferView DynamicBufferAllocator::GetStreamBufferView(RHI::Ptr<DynamicBuffer> dynamicBuffer, uint32_t strideByteCount)
         {
             return RHI::StreamBufferView(
-                *m_ringBuffer->GetRHIBuffer(),
+                *m_bufferData.GetCurrentElement()->GetRHIBuffer(),
                 GetBufferAddressOffset(dynamicBuffer),
                 dynamicBuffer->m_size,
-                strideByteCount
-            );
+                strideByteCount);
         }
 
         uint32_t DynamicBufferAllocator::GetBufferAddressOffset(RHI::Ptr<DynamicBuffer> dynamicBuffer)
         {
-            return aznumeric_cast<uint32_t>((uint8_t*)dynamicBuffer->m_address - (uint8_t*)m_ringBufferStartAddress);
+            return aznumeric_cast<uint32_t>((uint8_t*)dynamicBuffer->m_address - (uint8_t*)m_bufferStartAddresses.GetCurrentElement());
         }
 
         void DynamicBufferAllocator::SetEnableAllocationWarning(bool enable)
@@ -161,18 +105,9 @@ namespace AZ
 
         void DynamicBufferAllocator::FrameEnd()
         {
-            uint32_t nextFrame = (m_currentFrame + 1) % AZ::RHI::Limits::Device::FrameCountMax;
-
-            // The saved frame start position will become available since it's old than FrameCountMax. The saved start position of next frame is the new limit
-            m_endPositionLimit = m_frameStartPositions[nextFrame];
-
-            // Save start position for current frame
-            m_frameStartPositions[m_currentFrame] = m_currentPosition;
-
-            m_currentFrame = nextFrame;
-
-            m_currentAllocatedSize = 0;
-
+            m_bufferData.AdvanceCurrentElement();
+            m_bufferStartAddresses.AdvanceCurrentElement();
+            m_currentPosition = 0;
         }
-    }
-}
+    } // namespace RPI
+} // namespace AZ

+ 2 - 0
Gems/Atom/RPI/Code/atom_rpi_public_files.cmake

@@ -39,6 +39,7 @@ set(FILES
     Include/Atom/RPI.Public/Buffer/BufferPool.h
     Include/Atom/RPI.Public/Buffer/BufferSystem.h
     Include/Atom/RPI.Public/Buffer/BufferSystemInterface.h
+    Include/Atom/RPI.Public/Buffer/RingBuffer.h
     Include/Atom/RPI.Public/ColorManagement/TransformColor.h
     Include/Atom/RPI.Public/DynamicDraw/DynamicBuffer.h
     Include/Atom/RPI.Public/DynamicDraw/DynamicBufferAllocator.h
@@ -128,6 +129,7 @@ set(FILES
     Source/RPI.Public/Buffer/Buffer.cpp
     Source/RPI.Public/Buffer/BufferPool.cpp
     Source/RPI.Public/Buffer/BufferSystem.cpp
+    Source/RPI.Public/Buffer/RingBuffer.cpp
     Source/RPI.Public/DynamicDraw/DynamicBuffer.cpp
     Source/RPI.Public/DynamicDraw/DynamicBufferAllocator.cpp
     Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp

+ 1 - 1
Gems/Atom/RPI/Registry/atom_rpi.setreg

@@ -15,7 +15,7 @@
                         "TimestampQueryCount": 256
                     },
                     "DynamicDrawSystemDescriptor": {
-                        "DynamicBufferPoolSize": 50331648 // 3 * 16 * 1024 * 1024 (for 3 frames)
+                        "DynamicBufferPoolSize": 16777216 // 16 * 1024 * 1024 (per frame)
                     }
                 },
                 "UseDebugFallbackImages": true

+ 2 - 18
Gems/DiffuseProbeGrid/Code/Source/Render/DiffuseProbeGridFeatureProcessor.cpp

@@ -283,26 +283,10 @@ namespace AZ
             // build the query buffer for the irradiance queries (if any)
             if (m_irradianceQueries.size())
             {
-                uint32_t numQueries = aznumeric_cast<uint32_t>(m_irradianceQueries.size());
-                uint32_t elementSize = sizeof(IrradianceQueryVector::value_type);
-                uint32_t bufferSize = elementSize * numQueries;
-
-                // advance to the next buffer in the array
-                m_currentBufferIndex = (m_currentBufferIndex + 1) % BufferFrameCount;
-
-                // create a new buffer
-                RPI::CommonBufferDescriptor desc;
-                desc.m_poolType = RPI::CommonBufferPoolType::ReadWrite;
-                desc.m_bufferName = "DiffuseQueryBuffer";
-                desc.m_byteCount = bufferSize;
-                desc.m_elementSize = elementSize;
-                m_queryBuffer[m_currentBufferIndex] = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
-
-                // populate the buffer with the query position list
-                m_queryBuffer[m_currentBufferIndex]->UpdateData(m_irradianceQueries.data(), bufferSize);
+                m_queryBuffer.AdvanceCurrentBufferAndUpdateData(m_irradianceQueries);
 
                 // create the bufferview descriptor with the new number of elements
-                m_queryBufferViewDescriptor = RHI::BufferViewDescriptor::CreateStructured(0, numQueries, elementSize);
+                m_queryBufferViewDescriptor = m_queryBuffer.GetCurrentBuffer()->GetBufferViewDescriptor();
             }
         }
 

+ 3 - 4
Gems/DiffuseProbeGrid/Code/Source/Render/DiffuseProbeGridFeatureProcessor.h

@@ -11,6 +11,7 @@
 #include <DiffuseProbeGrid/DiffuseProbeGridFeatureProcessorInterface.h>
 #include <Atom/RHI/RayTracingBufferPools.h>
 #include <Atom/RHI/RayTracingAccelerationStructure.h>
+#include <Atom/RPI.Public/Buffer/RingBuffer.h>
 #include <Atom/RPI.Public/Model/Model.h>
 #include <Render/DiffuseProbeGrid.h>
 
@@ -112,7 +113,7 @@ namespace AZ
 
             // irradiance query accessors
             uint32_t GetIrradianceQueryCount() const { return aznumeric_cast<uint32_t>(m_irradianceQueries.size()); }
-            const Data::Instance<RPI::Buffer>& GetQueryBuffer() const { return m_queryBuffer[m_currentBufferIndex]; }
+            const Data::Instance<RPI::Buffer>& GetQueryBuffer() const { return m_queryBuffer.GetCurrentBuffer(); }
             const RHI::AttachmentId GetQueryBufferAttachmentId() const { return m_queryBufferAttachmentId; }
             const RHI::BufferViewDescriptor& GetQueryBufferViewDescriptor() const { return m_queryBufferViewDescriptor; }
 
@@ -222,9 +223,7 @@ namespace AZ
             IrradianceQueryVector m_irradianceQueries;
             RHI::BufferViewDescriptor m_queryBufferViewDescriptor;
             RHI::AttachmentId m_queryBufferAttachmentId;
-            static const uint32_t BufferFrameCount = 3;
-            Data::Instance<RPI::Buffer> m_queryBuffer[BufferFrameCount];
-            uint32_t m_currentBufferIndex = 0;
+            RPI::RingBuffer m_queryBuffer{ "DiffuseQueryBuffer", RPI::CommonBufferPoolType::ReadWrite, sizeof(IrradianceQuery) };
 
             // SSR state, for controlling the DiffuseProbeGridQueryPass in the SSR pipeline
             SpecularReflectionsFeatureProcessorInterface* m_specularReflectionsFeatureProcessor = nullptr;