Browse Source

Merge pull request #8945 from aws-lumberyard-dev/Atom/dmcdiar/ATOM-17517

RayTracing Performance Improvements
dmcdiarmid-ly 3 năm trước cách đây
mục cha
commit
febe143fee
23 tập tin đã thay đổi với 774 bổ sung271 xóa
  1. 17 5
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli
  2. 18 5
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli
  3. 13 5
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli
  4. 12 9
      Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli
  5. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader
  6. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_dx12_0.azshadervariant
  7. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_null_0.azshadervariant
  8. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_vulkan_0.azshadervariant
  9. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit.azshader
  10. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_dx12_0.azshadervariant
  11. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_null_0.azshadervariant
  12. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_vulkan_0.azshadervariant
  13. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss.azshader
  14. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_dx12_0.azshadervariant
  15. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_null_0.azshadervariant
  16. BIN
      Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_vulkan_0.azshadervariant
  17. 6 11
      Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridRayTracingPass.cpp
  18. 12 15
      Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp
  19. 294 196
      Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp
  20. 68 25
      Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h
  21. 166 0
      Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingIndexList.h
  22. 166 0
      Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingResourceList.h
  23. 2 0
      Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake

+ 17 - 5
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli

@@ -32,12 +32,24 @@ ShaderResourceGroup RayTracingMaterialSrg : SRG_RayTracingMaterial
     
     // hit shaders can retrieve the MaterialInfo for a mesh hit using: RayTracingMaterialSrg::m_materialInfo[InstanceIndex()]   
     StructuredBuffer<MaterialInfo> m_materialInfo;
-      
+
+    // indirection buffer containing indices into the material textures unbounded array
+    // Note 1: the index list for a particular mesh begins at MaterialInfo::m_textureStartIndex
+    // Note 2: the indirection buffer entries for multiple MaterialInfos will refer to the same texture in m_materialTextures when they are the same material
+    Buffer<uint> m_materialTextureIndices;
+    
+    // texture array index offsets from MaterialInfo::m_textureStartIndex
+    // Note: every material has all four entries in the index list, but the flag bits must be checked to determine if the texture is valid
+    #define MATERIAL_BASECOLOR_TEXTURE_OFFSET   0
+    #define MATERIAL_NORMAL_TEXTURE_OFFSET      1
+    #define MATERIAL_METALLIC_TEXTURE_OFFSET    2   
+    #define MATERIAL_ROUGHNESS_TEXTURE_OFFSET   3
+
     // texture flag bits indicating if optional textures are present
-    #define TEXTURE_FLAG_BASECOLOR      1
-    #define TEXTURE_FLAG_NORMAL         2
-    #define TEXTURE_FLAG_METALLIC       4
-    #define TEXTURE_FLAG_ROUGHNESS      8
+    #define TEXTURE_FLAG_BASECOLOR      (1 << 0)
+    #define TEXTURE_FLAG_NORMAL         (1 << 1)
+    #define TEXTURE_FLAG_METALLIC       (1 << 2)
+    #define TEXTURE_FLAG_ROUGHNESS      (1 << 3)
 
     // unbounded array of Material textures
     Texture2D m_materialTextures[];

+ 18 - 5
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli

@@ -18,12 +18,16 @@ TextureData GetHitTextureData(RayTracingMaterialSrg::MaterialInfo materialInfo,
 {
     TextureData textureData = (TextureData)0;
 
-    uint textureIndex = materialInfo.m_textureStartIndex;
+    // get the start of the index list for this material in the indirection buffer 
+    uint materialTextureStartIndex = RayTracingSceneSrg::m_meshBufferIndices[materialInfo.m_textureStartIndex];
 
     // base color
     if (materialInfo.m_textureFlags & TEXTURE_FLAG_BASECOLOR)
     {
-        textureData.m_baseColor = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0);
+        // array index of the baseColor texture for this material in the m_materialTextures unbounded array
+        uint baseColorTextureArrayIndex = materialTextureStartIndex + MATERIAL_BASECOLOR_TEXTURE_OFFSET;
+
+        textureData.m_baseColor = RayTracingMaterialSrg::m_materialTextures[baseColorTextureArrayIndex].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0);
     }
     else
     {
@@ -33,7 +37,10 @@ TextureData GetHitTextureData(RayTracingMaterialSrg::MaterialInfo materialInfo,
     // normal
     if (materialInfo.m_textureFlags & TEXTURE_FLAG_NORMAL)
     {
-        textureData.m_normal = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0);
+        // array index of the normal texture for this material in the m_materialTextures unbounded array
+        uint normalTextureArrayIndex = materialTextureStartIndex + MATERIAL_NORMAL_TEXTURE_OFFSET;
+
+        textureData.m_normal = RayTracingMaterialSrg::m_materialTextures[normalTextureArrayIndex].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0);
     }
     else
     {
@@ -43,7 +50,10 @@ TextureData GetHitTextureData(RayTracingMaterialSrg::MaterialInfo materialInfo,
     // metallic
     if (materialInfo.m_textureFlags & TEXTURE_FLAG_METALLIC)
     {
-        textureData.m_metallic = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0);
+        // array index of the metallic texture for this material in the m_materialTextures unbounded array
+        uint metallicTextureArrayIndex = materialTextureStartIndex + MATERIAL_METALLIC_TEXTURE_OFFSET;
+
+        textureData.m_metallic = RayTracingMaterialSrg::m_materialTextures[metallicTextureArrayIndex].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0);
     }
     else
     {
@@ -53,7 +63,10 @@ TextureData GetHitTextureData(RayTracingMaterialSrg::MaterialInfo materialInfo,
     // roughness
     if (materialInfo.m_textureFlags & TEXTURE_FLAG_ROUGHNESS)
     {
-        textureData.m_roughness = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0);
+        // array index of the roughness texture for this material in the m_materialTextures unbounded array
+        uint roughnessTextureArrayIndex = materialTextureStartIndex + MATERIAL_ROUGHNESS_TEXTURE_OFFSET;
+
+        textureData.m_roughness = RayTracingMaterialSrg::m_materialTextures[roughnessTextureArrayIndex].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0);
     }
     else
     {

+ 13 - 5
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli

@@ -146,19 +146,27 @@ ShaderResourceGroup RayTracingSceneSrg : SRG_RayTracingScene
     // hit shaders can retrieve the MeshInfo for a mesh hit using: RayTracingSceneSrg::m_meshInfo[InstanceIndex()]    
     StructuredBuffer<MeshInfo> m_meshInfo;
     
-    // buffer array index offsets for buffers that are always present for each mesh
+    // indirection buffer containing indices into the m_meshBuffers unbounded array
+    // Note 1: the index list for a particular mesh begins at MeshInfo::m_bufferStartIndex
+    // Note 2: multiple MeshInfos will refer to the same buffer in m_meshBuffers when they are instances of the same mesh
+    Buffer<uint> m_meshBufferIndices;
+
+    // buffer array index offsets from MeshInfo::m_bufferStartIndex in the m_meshBufferIndices indirection buffer
+    // Note: every mesh has all six entries in the index list, but only the Index, Position, and Normal buffers are guaranteed to be valid
     #define MESH_INDEX_BUFFER_OFFSET        0
     #define MESH_POSITION_BUFFER_OFFSET     1
     #define MESH_NORMAL_BUFFER_OFFSET       2   
+    #define MESH_TANGENT_BUFFER_OFFSET      3
+    #define MESH_BITANGENT_BUFFER_OFFSET    4
+    #define MESH_UV_BUFFER_OFFSET           5   
 
-    // buffer flag bits indicating if optional buffers are present (note: these are bit masks)
+    // buffer flag bits indicating if optional buffers are present
     #define MESH_BUFFER_FLAG_TANGENT        (1 << 0)
     #define MESH_BUFFER_FLAG_BITANGENT      (1 << 1)
     #define MESH_BUFFER_FLAG_UV             (1 << 2)
 
     // Unbounded array of mesh stream buffers:
-    // - Index, Position, Normal stream buffers are always present
-    // - Optional stream buffers such as Tangent, Bitangent, and UV are indicated in the MeshInfo.m_bufferFlags field
-    // - Buffers for a particular mesh start at MeshInfo.m_bufferStartIndex
+    // Note 1: Index, Position, and Normal stream buffers are guaranteed to be valid for each mesh
+    // Note 2: Optional stream buffers such as Tangent, Bitangent, and UV are indicated in the MeshInfo.m_bufferFlags field
     ByteAddressBuffer m_meshBuffers[];
 }

+ 12 - 9
Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli

@@ -18,8 +18,11 @@ float3 GetViewRayDirection(float4x4 viewProjectionInverseMatrix)
 // Note: usable only in a raytracing Hit shader
 uint3 GetHitIndices(RayTracingSceneSrg::MeshInfo meshInfo)
 {
+    // get the start of the index list for this mesh in the indirection buffer 
+    uint meshBufferStartIndex = RayTracingSceneSrg::m_meshBufferIndices[meshInfo.m_bufferStartIndex];
+
     // compute the array index of the index buffer for this mesh in the m_meshBuffers unbounded array
-    uint meshIndexBufferArrayIndex = meshInfo.m_bufferStartIndex + MESH_INDEX_BUFFER_OFFSET;
+    uint meshIndexBufferArrayIndex = meshBufferStartIndex + MESH_INDEX_BUFFER_OFFSET;
 
     // compute the offset into the index buffer for this primitve of the mesh
     uint offsetBytes = meshInfo.m_indexOffset + (PrimitiveIndex() * 12);
@@ -47,6 +50,9 @@ VertexData GetHitInterpolatedVertexData(RayTracingSceneSrg::MeshInfo meshInfo, f
     // compute barycentrics
     float3 barycentrics = float3((1.0f - builtInBarycentrics.x - builtInBarycentrics.y), builtInBarycentrics.x, builtInBarycentrics.y);    
 
+    // get the start of the index list for this mesh in the indirection buffer 
+    uint meshBufferStartIndex = RayTracingSceneSrg::m_meshBufferIndices[meshInfo.m_bufferStartIndex];
+
     // compute the vertex data using barycentric interpolation
     VertexData vertexData = (VertexData)0;
     for (uint i = 0; i < 3; ++i)
@@ -54,7 +60,7 @@ VertexData GetHitInterpolatedVertexData(RayTracingSceneSrg::MeshInfo meshInfo, f
         // position
         {
             // array index of the position buffer for this mesh in the m_meshBuffers unbounded array
-            uint meshVertexPositionArrayIndex = meshInfo.m_bufferStartIndex + MESH_POSITION_BUFFER_OFFSET;
+            uint meshVertexPositionArrayIndex = meshBufferStartIndex + MESH_POSITION_BUFFER_OFFSET;
 
             // offset into the position buffer for this vertex
             uint positionOffset = meshInfo.m_positionOffset + (indices[i] * 12);
@@ -66,7 +72,7 @@ VertexData GetHitInterpolatedVertexData(RayTracingSceneSrg::MeshInfo meshInfo, f
         // normal
         {
             // array index of the normal buffer for this mesh in the m_meshBuffers unbounded array
-            uint meshVertexNormalArrayIndex = meshInfo.m_bufferStartIndex + MESH_NORMAL_BUFFER_OFFSET;
+            uint meshVertexNormalArrayIndex = meshBufferStartIndex + MESH_NORMAL_BUFFER_OFFSET;
 
             // offset into the normal buffer for this vertex
             uint normalOffset = meshInfo.m_normalOffset + (indices[i] * 12);
@@ -75,14 +81,11 @@ VertexData GetHitInterpolatedVertexData(RayTracingSceneSrg::MeshInfo meshInfo, f
             vertexData.m_normal += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexNormalArrayIndex].Load3(normalOffset)) * barycentrics[i];
         }
 
-        // optional streams begin after MESH_NORMAL_BUFFER_OFFSET
-        uint optionalBufferOffset = MESH_NORMAL_BUFFER_OFFSET + 1;
-
         // tangent
         if (meshInfo.m_bufferFlags & MESH_BUFFER_FLAG_TANGENT)
         {
             // array index of the tangent buffer for this mesh in the m_meshBuffers unbounded array
-            uint meshVertexTangentArrayIndex = meshInfo.m_bufferStartIndex + optionalBufferOffset++;
+            uint meshVertexTangentArrayIndex = meshBufferStartIndex + MESH_TANGENT_BUFFER_OFFSET;
 
             // offset into the tangent buffer for this vertex
             uint tangentOffset = meshInfo.m_tangentOffset + (indices[i] * 16);
@@ -95,7 +98,7 @@ VertexData GetHitInterpolatedVertexData(RayTracingSceneSrg::MeshInfo meshInfo, f
         if (meshInfo.m_bufferFlags & MESH_BUFFER_FLAG_BITANGENT)
         {
             // array index of the bitangent buffer for this mesh in the m_meshBuffers unbounded array
-            uint meshVertexBitangentArrayIndex = meshInfo.m_bufferStartIndex + optionalBufferOffset++;
+            uint meshVertexBitangentArrayIndex = meshBufferStartIndex + MESH_BITANGENT_BUFFER_OFFSET;
 
             // offset into the bitangent buffer for this vertex
             uint bitangentOffset = meshInfo.m_bitangentOffset + (indices[i] * 12);
@@ -108,7 +111,7 @@ VertexData GetHitInterpolatedVertexData(RayTracingSceneSrg::MeshInfo meshInfo, f
         if (meshInfo.m_bufferFlags & MESH_BUFFER_FLAG_UV)
         {
             // array index of the UV buffer for this mesh in the m_meshBuffers unbounded array
-            uint meshVertexUVArrayIndex = meshInfo.m_bufferStartIndex + optionalBufferOffset++;
+            uint meshVertexUVArrayIndex = meshBufferStartIndex + MESH_UV_BUFFER_OFFSET;
     
             // offset into the UV buffer for this vertex
             uint uvOffset = meshInfo.m_uvOffset + (indices[i] * 8);    

BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_dx12_0.azshadervariant


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_null_0.azshadervariant


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_vulkan_0.azshadervariant


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit.azshader


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_dx12_0.azshadervariant


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_null_0.azshadervariant


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_vulkan_0.azshadervariant


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss.azshader


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_dx12_0.azshadervariant


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_null_0.azshadervariant


BIN
Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_vulkan_0.azshadervariant


+ 6 - 11
Gems/Atom/Feature/Common/Code/Source/DiffuseGlobalIllumination/DiffuseProbeGridRayTracingPass.cpp

@@ -290,10 +290,10 @@ namespace AZ
             RPI::Scene* scene = m_pipeline->GetScene();
             DiffuseProbeGridFeatureProcessor* diffuseProbeGridFeatureProcessor = scene->GetFeatureProcessor<DiffuseProbeGridFeatureProcessor>();
             RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor<RayTracingFeatureProcessor>();
-            const Data::Instance<RPI::Buffer> meshInfoBuffer = rayTracingFeatureProcessor->GetMeshInfoBuffer();
+            const Data::Instance<RPI::Buffer> meshInfoBuffer = rayTracingFeatureProcessor->GetMeshInfoGpuBuffer();
 
-            if (rayTracingFeatureProcessor->GetTlas()->GetTlasBuffer() &&
-                rayTracingFeatureProcessor->GetMeshInfoBuffer() &&
+            if (meshInfoBuffer &&
+                rayTracingFeatureProcessor->GetTlas()->GetTlasBuffer() &&
                 rayTracingFeatureProcessor->GetSubMeshCount())
             {
                 for (auto& diffuseProbeGrid : diffuseProbeGridFeatureProcessor->GetVisibleRealTimeProbeGrids())
@@ -316,15 +316,10 @@ namespace AZ
                 if (rayTracingFeatureProcessor->GetSubMeshCount())
                 {
                     // build the ray tracing shader table descriptor
-                    RHI::RayTracingShaderTableDescriptor* descriptorBuild = descriptor->Build(AZ::Name("RayTracingShaderTable"), m_rayTracingPipelineState)
+                    descriptor->Build(AZ::Name("RayTracingShaderTable"), m_rayTracingPipelineState)
                         ->RayGenerationRecord(AZ::Name("RayGen"))
-                        ->MissRecord(AZ::Name("Miss"));
-
-                    // add a hit group for each mesh to the shader table
-                    for (uint32_t i = 0; i < rayTracingFeatureProcessor->GetSubMeshCount(); ++i)
-                    {
-                        descriptorBuild->HitGroupRecord(AZ::Name("HitGroup"));
-                    }
+                        ->MissRecord(AZ::Name("Miss"))
+                        ->HitGroupRecord(AZ::Name("HitGroup"));
                 }
 
                 m_rayTracingShaderTable->Build(descriptor);

+ 12 - 15
Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp

@@ -60,28 +60,25 @@ namespace AZ
                 if (rayTracingFeatureProcessor->GetRevision() != m_rayTracingRevision)
                 {
                     RHI::RayTracingBufferPools& rayTracingBufferPools = rayTracingFeatureProcessor->GetBufferPools();
-                    RayTracingFeatureProcessor::MeshMap& rayTracingMeshes = rayTracingFeatureProcessor->GetMeshes();
+                    RayTracingFeatureProcessor::SubMeshVector& subMeshes = rayTracingFeatureProcessor->GetSubMeshes();
                     uint32_t rayTracingSubMeshCount = rayTracingFeatureProcessor->GetSubMeshCount();
 
                     // create the TLAS descriptor
                     RHI::RayTracingTlasDescriptor tlasDescriptor;
                     RHI::RayTracingTlasDescriptor* tlasDescriptorBuild = tlasDescriptor.Build();
 
-                    uint32_t blasIndex = 0;
-                    for (auto& rayTracingMesh : rayTracingMeshes)
+                    uint32_t instanceIndex = 0;
+                    for (auto& subMesh : subMeshes)
                     {
-                        for (auto& rayTracingSubMesh : rayTracingMesh.second.m_subMeshes)
-                        {
-                            tlasDescriptorBuild->Instance()
-                                ->InstanceID(blasIndex)
-                                ->HitGroupIndex(blasIndex)
-                                ->Blas(rayTracingSubMesh.m_blas)
-                                ->Transform(rayTracingMesh.second.m_transform)
-                                ->NonUniformScale(rayTracingMesh.second.m_nonUniformScale)
-                                ;
-                        }
-
-                        blasIndex++;
+                        tlasDescriptorBuild->Instance()
+                            ->InstanceID(instanceIndex)
+                            ->HitGroupIndex(0)
+                            ->Blas(subMesh.m_blas)
+                            ->Transform(subMesh.m_mesh->m_transform)
+                            ->NonUniformScale(subMesh.m_mesh->m_nonUniformScale)
+                            ;
+
+                        instanceIndex++;
                     }
 
                     // create the TLAS buffers based on the descriptor

+ 294 - 196
Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp

@@ -87,23 +87,42 @@ namespace AZ
             RHI::Ptr<RHI::Device> device = RHI::RHISystemInterface::Get()->GetDevice();
             uint32_t objectIndex = objectId.GetIndex();
 
-            // lock the mutex to protect the mesh and BLAS lists
-            AZStd::unique_lock<AZStd::mutex> lock(m_mutex);
-
+            // check to see if we already have this mesh
             MeshMap::iterator itMesh = m_meshes.find(objectIndex);
-            if (itMesh == m_meshes.end())
+            if (itMesh != m_meshes.end())
             {
-                m_meshes.insert(AZStd::make_pair(objectIndex, Mesh{ assetId, subMeshes }));
+                AZ_Assert(false, "SetMesh called on an existing Mesh objectId, call RemoveMesh first");
+                return;
             }
-            else
+
+            // lock the mutex to protect the mesh and BLAS lists
+            AZStd::unique_lock<AZStd::mutex> lock(m_mutex);
+
+            // add the mesh
+            m_meshes.insert(AZStd::make_pair(objectIndex, Mesh{ assetId }));
+            Mesh& mesh = m_meshes[objectIndex];
+
+            // add the subMeshes to the end of the global subMesh vector
+            // Note 1: the MeshInfo and MaterialInfo vectors are parallel with the subMesh vector
+            // Note 2: the list of indices for the subMeshes in the global vector are stored in the parent Mesh
+            IndexVector subMeshIndices;
+            uint32_t subMeshGlobalIndex = aznumeric_cast<uint32_t>(m_subMeshes.size());
+            for (uint32_t subMeshIndex = 0; subMeshIndex < subMeshes.size(); ++subMeshIndex, ++subMeshGlobalIndex)
             {
-                // updating an existing entry
-                // decrement the mesh count by the number of meshes in the existing entry in case the number of meshes changed
-                m_subMeshCount -= aznumeric_cast<uint32_t>(itMesh->second.m_subMeshes.size());
-                m_meshes[objectIndex].m_subMeshes = subMeshes;
+                SubMesh& subMesh = m_subMeshes.emplace_back(subMeshes[subMeshIndex]);
+                subMesh.m_mesh = &mesh;
+                subMesh.m_subMeshIndex = subMeshIndex;
+                subMesh.m_globalIndex = subMeshGlobalIndex;
+
+                // add to the list of global subMeshIndices, which will be stored in the Mesh
+                subMeshIndices.push_back(subMeshGlobalIndex);
+
+                // add MeshInfo and MaterialInfo entries
+                m_meshInfos.emplace_back();
+                m_materialInfos.emplace_back();
             }
 
-            Mesh& mesh = m_meshes[objectIndex];
+            mesh.m_subMeshIndices = subMeshIndices;
 
             // search for an existing BLAS instance entry for this mesh using the assetId
             BlasInstanceMap::iterator itMeshBlasInstance = m_blasInstanceMap.find(assetId);
@@ -112,7 +131,7 @@ namespace AZ
                 // make a new BLAS map entry for this mesh
                 MeshBlasInstance meshBlasInstance;
                 meshBlasInstance.m_count = 1;
-                meshBlasInstance.m_subMeshes.reserve(mesh.m_subMeshes.size());
+                meshBlasInstance.m_subMeshes.reserve(mesh.m_subMeshIndices.size());
                 itMeshBlasInstance = m_blasInstanceMap.insert({ assetId, meshBlasInstance }).first;
             }
             else
@@ -124,9 +143,9 @@ namespace AZ
             // Note: all sub-meshes must either create new BLAS objects or re-use existing ones, otherwise it's an error (it's the same model in both cases)
             // Note: the buffer is just reserved here, the BLAS is built in the RayTracingAccelerationStructurePass
             [[maybe_unused]] bool blasInstanceFound = false;
-            for (uint32_t subMeshIndex = 0; subMeshIndex < mesh.m_subMeshes.size(); ++subMeshIndex)
+            for (uint32_t subMeshIndex = 0; subMeshIndex < mesh.m_subMeshIndices.size(); ++subMeshIndex)
             {
-                SubMesh& subMesh = mesh.m_subMeshes[subMeshIndex];
+                SubMesh& subMesh = m_subMeshes[mesh.m_subMeshIndices[subMeshIndex]];
 
                 RHI::RayTracingBlasDescriptor blasDescriptor;
                 blasDescriptor.Build()
@@ -165,11 +184,62 @@ namespace AZ
             mesh.m_transform = m_transformServiceFeatureProcessor->GetTransformForId(objectId);
             mesh.m_nonUniformScale = m_transformServiceFeatureProcessor->GetNonUniformScaleForId(objectId);
 
+            AZ::Transform noScaleTransform = mesh.m_transform;
+            noScaleTransform.ExtractUniformScale();
+            AZ::Matrix3x3 rotationMatrix = Matrix3x3::CreateFromTransform(noScaleTransform);
+            rotationMatrix = rotationMatrix.GetInverseFull().GetTranspose();
+            Matrix3x4 worldInvTranspose3x4 = Matrix3x4::CreateFromMatrix3x3(rotationMatrix);
+
+            // store the mesh buffers and material textures in the resource lists
+            for (uint32_t subMeshIndex : mesh.m_subMeshIndices)
+            {
+                SubMesh& subMesh = m_subMeshes[subMeshIndex];
+                MeshInfo& meshInfo = m_meshInfos[subMesh.m_globalIndex];
+                MaterialInfo& materialInfo = m_materialInfos[subMesh.m_globalIndex];
+
+                subMesh.m_irradianceColor.StoreToFloat4(meshInfo.m_irradianceColor.data());
+                worldInvTranspose3x4.StoreToRowMajorFloat12(meshInfo.m_worldInvTranspose.data());
+                meshInfo.m_bufferFlags = subMesh.m_bufferFlags;
+
+                // add mesh buffers
+                meshInfo.m_bufferStartIndex = m_meshBufferIndices.AddEntry(
+                {
+                    m_meshBuffers.AddResource(subMesh.m_indexShaderBufferView.get()),
+                    m_meshBuffers.AddResource(subMesh.m_positionShaderBufferView.get()),
+                    m_meshBuffers.AddResource(subMesh.m_normalShaderBufferView.get()),
+                    m_meshBuffers.AddResource(subMesh.m_tangentShaderBufferView.get()),
+                    m_meshBuffers.AddResource(subMesh.m_bitangentShaderBufferView.get()),
+                    m_meshBuffers.AddResource(subMesh.m_uvShaderBufferView.get())
+                });
+                
+                meshInfo.m_indexByteOffset = subMesh.m_indexBufferView.GetByteOffset();
+                meshInfo.m_positionByteOffset = subMesh.m_positionVertexBufferView.GetByteOffset();
+                meshInfo.m_normalByteOffset = subMesh.m_normalVertexBufferView.GetByteOffset();
+                meshInfo.m_tangentByteOffset = subMesh.m_tangentShaderBufferView ? subMesh.m_tangentVertexBufferView.GetByteOffset() : 0;
+                meshInfo.m_tangentByteOffset = subMesh.m_bitangentShaderBufferView ? subMesh.m_bitangentVertexBufferView.GetByteOffset() : 0;
+                meshInfo.m_tangentByteOffset = subMesh.m_uvShaderBufferView ? subMesh.m_uvVertexBufferView.GetByteOffset() : 0;
+
+                // add material textures
+                subMesh.m_baseColor.StoreToFloat4(materialInfo.m_baseColor.data());
+                materialInfo.m_metallicFactor = subMesh.m_metallicFactor;
+                materialInfo.m_roughnessFactor = subMesh.m_roughnessFactor;
+                materialInfo.m_textureFlags = subMesh.m_textureFlags;
+
+                materialInfo.m_textureStartIndex = m_materialTextureIndices.AddEntry(
+                {
+                    m_materialTextures.AddResource(subMesh.m_baseColorImageView.get()),
+                    m_materialTextures.AddResource(subMesh.m_normalImageView.get()),
+                    m_materialTextures.AddResource(subMesh.m_metallicImageView.get()),
+                    m_materialTextures.AddResource(subMesh.m_roughnessImageView.get())
+                });
+            }
+
             m_revision++;
             m_subMeshCount += aznumeric_cast<uint32_t>(subMeshes.size());
 
             m_meshInfoBufferNeedsUpdate = true;
             m_materialInfoBufferNeedsUpdate = true;
+            m_indexListNeedsUpdate = true;
         }
 
         void RayTracingFeatureProcessor::RemoveMesh(const ObjectId objectId)
@@ -185,12 +255,10 @@ namespace AZ
             MeshMap::iterator itMesh = m_meshes.find(objectId.GetIndex());
             if (itMesh != m_meshes.end())
             {
-                m_subMeshCount -= aznumeric_cast<uint32_t>(itMesh->second.m_subMeshes.size());
-                m_meshes.erase(itMesh);
-                m_revision++;
+                Mesh& mesh = itMesh->second;
 
                 // decrement the count from the BLAS instances, and check to see if we can remove them
-                BlasInstanceMap::iterator itBlas = m_blasInstanceMap.find(itMesh->second.m_assetId);
+                BlasInstanceMap::iterator itBlas = m_blasInstanceMap.find(mesh.m_assetId);
                 if (itBlas != m_blasInstanceMap.end())
                 {
                     itBlas->second.m_count--;
@@ -199,10 +267,76 @@ namespace AZ
                         m_blasInstanceMap.erase(itBlas);
                     }
                 }
+
+                // remove the SubMeshes
+                for (auto& subMeshIndex : mesh.m_subMeshIndices)
+                {
+                    SubMesh& subMesh = m_subMeshes[subMeshIndex];
+                    uint32_t globalIndex = subMesh.m_globalIndex;
+
+                    MeshInfo& meshInfo = m_meshInfos[globalIndex];
+                    MaterialInfo& materialInfo = m_materialInfos[globalIndex];
+
+                    m_meshBufferIndices.RemoveEntry(meshInfo.m_bufferStartIndex);
+                    m_materialTextureIndices.RemoveEntry(materialInfo.m_textureStartIndex);
+
+                    m_meshBuffers.RemoveResource(subMesh.m_indexShaderBufferView.get());
+                    m_meshBuffers.RemoveResource(subMesh.m_positionShaderBufferView.get());
+                    m_meshBuffers.RemoveResource(subMesh.m_normalShaderBufferView.get());
+                    m_meshBuffers.RemoveResource(subMesh.m_tangentShaderBufferView.get());
+                    m_meshBuffers.RemoveResource(subMesh.m_bitangentShaderBufferView.get());
+                    m_meshBuffers.RemoveResource(subMesh.m_uvShaderBufferView.get());
+
+                    m_materialTextures.RemoveResource(subMesh.m_baseColorImageView.get());
+                    m_materialTextures.RemoveResource(subMesh.m_normalImageView.get());
+                    m_materialTextures.RemoveResource(subMesh.m_metallicImageView.get());
+                    m_materialTextures.RemoveResource(subMesh.m_roughnessImageView.get());
+                    
+                    if (globalIndex < m_subMeshes.size() - 1)
+                    {
+                        // the subMesh we're removing is in the middle of the global lists, remove by swapping the last element to its position in the list
+                        m_subMeshes[globalIndex] = m_subMeshes.back();
+                        m_meshInfos[globalIndex] = m_meshInfos.back();
+                        m_materialInfos[globalIndex] = m_materialInfos.back();
+
+                        // update the global index for the swapped subMesh
+                        m_subMeshes[globalIndex].m_globalIndex = globalIndex;
+
+                        // update the global index in the parent Mesh' subMesh list
+                        Mesh* swappedSubMeshParent = m_subMeshes[globalIndex].m_mesh;
+                        uint32_t swappedSubMeshIndex = m_subMeshes[globalIndex].m_subMeshIndex;
+                        swappedSubMeshParent->m_subMeshIndices[swappedSubMeshIndex] = globalIndex;
+                    }
+
+                    m_subMeshes.pop_back();
+                    m_meshInfos.pop_back();
+                    m_materialInfos.pop_back();
+                }
+
+                // remove from the Mesh list
+                m_subMeshCount -= aznumeric_cast<uint32_t>(mesh.m_subMeshIndices.size());
+                m_meshes.erase(itMesh);
+                m_revision++;
+
+                // reset all data structures if all meshes were removed (i.e., empty scene)
+                if (m_subMeshCount == 0)
+                {
+                    m_meshes.clear();
+                    m_subMeshes.clear();
+                    m_meshInfos.clear();
+                    m_materialInfos.clear();
+
+                    m_meshBufferIndices.Reset();
+                    m_materialTextureIndices.Reset();
+
+                    m_meshBuffers.Reset();
+                    m_materialTextures.Reset();
+                }
             }
 
             m_meshInfoBufferNeedsUpdate = true;
             m_materialInfoBufferNeedsUpdate = true;
+            m_indexListNeedsUpdate = true;
         }
 
         void RayTracingFeatureProcessor::SetMeshTransform(const ObjectId objectId, const AZ::Transform transform, const AZ::Vector3 nonUniformScale)
@@ -215,9 +349,24 @@ namespace AZ
             MeshMap::iterator itMesh = m_meshes.find(objectId.GetIndex());
             if (itMesh != m_meshes.end())
             {
-                itMesh->second.m_transform = transform;
-                itMesh->second.m_nonUniformScale = nonUniformScale;
+                Mesh& mesh = itMesh->second;
+                mesh.m_transform = transform;
+                mesh.m_nonUniformScale = nonUniformScale;
                 m_revision++;
+
+                // create a world inverse transpose 3x4 matrix
+                AZ::Transform noScaleTransform = mesh.m_transform;
+                noScaleTransform.ExtractUniformScale();
+                AZ::Matrix3x3 rotationMatrix = Matrix3x3::CreateFromTransform(noScaleTransform);
+                rotationMatrix = rotationMatrix.GetInverseFull().GetTranspose();
+                Matrix3x4 worldInvTranspose3x4 = Matrix3x4::CreateFromMatrix3x3(rotationMatrix);
+
+                // update all MeshInfos for this Mesh with the new transform
+                for (const auto& subMeshIndex : mesh.m_subMeshIndices)
+                {
+                    MeshInfo& meshInfo = m_meshInfos[subMeshIndex];
+                    worldInvTranspose3x4.StoreToRowMajorFloat12(meshInfo.m_worldInvTranspose.data());
+                }
             }
 
             m_meshInfoBufferNeedsUpdate = true;
@@ -236,31 +385,32 @@ namespace AZ
                 return;
             }
 
-            // update the mesh info buffer with the latest ray tracing enabled meshes
-            UpdateMeshInfoBuffer();
+            // lock the mutex to protect the mesh and BLAS lists
+            AZStd::unique_lock<AZStd::mutex> lock(m_mutex);
 
-            // update the material info buffer with the latest ray tracing enabled meshes
-            UpdateMaterialInfoBuffer();
+            if (m_subMeshCount > 0)
+            {
+                UpdateMeshInfoBuffer();
+                UpdateMaterialInfoBuffer();
+                UpdateIndexLists();
+            }
 
-            // update the RayTracingSceneSrg
             UpdateRayTracingSceneSrg();
-
-            // update the RayTracingMaterialSrg
             UpdateRayTracingMaterialSrg();
         }
 
         void RayTracingFeatureProcessor::UpdateMeshInfoBuffer()
         {
-            if (m_meshInfoBufferNeedsUpdate && (m_subMeshCount > 0))
+            if (m_meshInfoBufferNeedsUpdate)
             {
-                TransformServiceFeatureProcessor* transformFeatureProcessor = GetParentScene()->GetFeatureProcessor<TransformServiceFeatureProcessor>();
-
-                AZStd::vector<MeshInfo> meshInfos;
-                meshInfos.reserve(m_subMeshCount);
+                // 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];
                 uint32_t newMeshByteCount = m_subMeshCount * sizeof(MeshInfo);
 
-                if (m_meshInfoBuffer == nullptr)
+                if (currentMeshInfoGpuBuffer == nullptr)
                 {
                     // allocate the MeshInfo structured buffer
                     RPI::CommonBufferDescriptor desc;
@@ -268,119 +418,139 @@ namespace AZ
                     desc.m_bufferName = "RayTracingMeshInfo";
                     desc.m_byteCount = newMeshByteCount;
                     desc.m_elementSize = sizeof(MeshInfo);
-                    m_meshInfoBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
+                    currentMeshInfoGpuBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
                 }
-                else if (m_meshInfoBuffer->GetBufferSize() < newMeshByteCount)
+                else if (currentMeshInfoGpuBuffer->GetBufferSize() < newMeshByteCount)
                 {
                     // resize for the new sub-mesh count
-                    m_meshInfoBuffer->Resize(newMeshByteCount);
+                    currentMeshInfoGpuBuffer->Resize(newMeshByteCount);
                 }
 
-                // keep track of the start index of the buffers for each mesh, this is put into the MeshInfo
-                // entry for each mesh so it knows where to find the start of its buffers in the unbounded array
-                uint32_t bufferStartIndex = 0;
-
-                for (const auto& mesh : m_meshes)
-                {
-                    AZ::Transform meshTransform = transformFeatureProcessor->GetTransformForId(TransformServiceFeatureProcessorInterface::ObjectId(mesh.first));
-                    AZ::Transform noScaleTransform = meshTransform;
-                    noScaleTransform.ExtractUniformScale();
-                    AZ::Matrix3x3 rotationMatrix = Matrix3x3::CreateFromTransform(noScaleTransform);
-                    rotationMatrix = rotationMatrix.GetInverseFull().GetTranspose();
-
-                    const RayTracingFeatureProcessor::SubMeshVector& subMeshes = mesh.second.m_subMeshes;
-                    for (const auto& subMesh : subMeshes)
-                    {
-                        MeshInfo meshInfo;
-                        meshInfo.m_indexOffset = subMesh.m_indexBufferView.GetByteOffset();
-                        meshInfo.m_positionOffset = subMesh.m_positionVertexBufferView.GetByteOffset();
-                        meshInfo.m_normalOffset = subMesh.m_normalVertexBufferView.GetByteOffset();
-
-                        if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::Tangent))
-                        {
-                            meshInfo.m_tangentOffset = subMesh.m_tangentVertexBufferView.GetByteOffset();
-                        }
-
-                        if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::Bitangent))
-                        {
-                            meshInfo.m_bitangentOffset = subMesh.m_bitangentVertexBufferView.GetByteOffset();
-                        }
-
-                        if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::UV))
-                        {
-                            meshInfo.m_uvOffset = subMesh.m_uvVertexBufferView.GetByteOffset();
-                        }
-
-                        subMesh.m_irradianceColor.StoreToFloat4(meshInfo.m_irradianceColor.data());
-                        Matrix3x4 worldInvTranspose3x4 = Matrix3x4::CreateFromMatrix3x3(rotationMatrix);
-                        worldInvTranspose3x4.StoreToRowMajorFloat12(meshInfo.m_worldInvTranspose.data());
-                        meshInfo.m_bufferFlags = subMesh.m_bufferFlags;
-                        meshInfo.m_bufferStartIndex = bufferStartIndex;
-
-                        // add the count of buffers present in this subMesh to the start index for the next subMesh
-                        // note that the Index, Position, and Normal buffers are always counted since they are guaranteed
-                        static const uint32_t RayTracingSubMeshFixedStreamCount = 3;
-                        bufferStartIndex += (RayTracingSubMeshFixedStreamCount + RHI::CountBitsSet(aznumeric_cast<uint32_t>(meshInfo.m_bufferFlags)));
-
-                        meshInfos.emplace_back(meshInfo);
-                    }
-                }
+                currentMeshInfoGpuBuffer->UpdateData(m_meshInfos.data(), newMeshByteCount);
 
-                m_meshInfoBuffer->UpdateData(meshInfos.data(), newMeshByteCount);
                 m_meshInfoBufferNeedsUpdate = false;
             }
         }
 
         void RayTracingFeatureProcessor::UpdateMaterialInfoBuffer()
         {
-            if (m_materialInfoBufferNeedsUpdate && (m_subMeshCount > 0))
+            if (m_materialInfoBufferNeedsUpdate)
             {
-                AZStd::vector<MaterialInfo> materialInfos;
-                materialInfos.reserve(m_subMeshCount);
-
-                uint32_t newMaterialByteCount = m_subMeshCount * sizeof(MaterialInfo);
-
-                if (m_materialInfoBuffer == nullptr)
+                // 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];
+                uint32_t newMaterialInfoByteCount = m_subMeshCount * sizeof(MaterialInfo);
+        
+                if (currentMaterialInfoGpuBuffer == nullptr)
                 {
                     // allocate the MaterialInfo structured buffer
                     RPI::CommonBufferDescriptor desc;
                     desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly;
                     desc.m_bufferName = "RayTracingMaterialInfo";
-                    desc.m_byteCount = newMaterialByteCount;
+                    desc.m_byteCount = newMaterialInfoByteCount;
                     desc.m_elementSize = sizeof(MaterialInfo);
-                    m_materialInfoBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
+                    currentMaterialInfoGpuBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
                 }
-                else if (m_materialInfoBuffer->GetBufferSize() < newMaterialByteCount)
+                else if (currentMaterialInfoGpuBuffer->GetBufferSize() < newMaterialInfoByteCount)
                 {
                     // resize for the new sub-mesh count
-                    m_materialInfoBuffer->Resize(newMaterialByteCount);
+                    currentMaterialInfoGpuBuffer->Resize(newMaterialInfoByteCount);
                 }
+               
+                currentMaterialInfoGpuBuffer->UpdateData(m_materialInfos.data(), newMaterialInfoByteCount);
+
+                m_materialInfoBufferNeedsUpdate = false;
+            }
+        }
+
+        void RayTracingFeatureProcessor::UpdateIndexLists()
+        {
+            if (m_indexListNeedsUpdate)
+            {
+                // advance to the next buffer in the frame list
+                m_currentIndexListFrameIndex = (m_currentIndexListFrameIndex + 1) % BufferFrameCount;
 
-                // keep track of the start index of the textures for each mesh, this is put into the MaterialInfo
-                // entry for each mesh so it knows where to find the start of its textures in the unbounded array
-                uint32_t textureStartIndex = 0;
+                // update mesh buffer indices buffer
+                Data::Instance<RPI::Buffer>& currentMeshBufferIndicesGpuBuffer = m_meshBufferIndicesGpuBuffer[m_currentIndexListFrameIndex];
+                uint32_t newMeshBufferIndicesByteCount = aznumeric_cast<uint32_t>(m_meshBufferIndices.GetIndexList().size()) * sizeof(uint32_t);
 
-                for (const auto& mesh : m_meshes)
+                if (currentMeshBufferIndicesGpuBuffer == nullptr)
                 {
-                    const RayTracingFeatureProcessor::SubMeshVector& subMeshes = mesh.second.m_subMeshes;
-                    for (const auto& subMesh : subMeshes)
+                    // allocate the MeshBufferIndices buffer
+                    RPI::CommonBufferDescriptor desc;
+                    desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly;
+                    desc.m_bufferName = "RayTracingMeshBufferIndices";
+                    desc.m_byteCount = newMeshBufferIndicesByteCount;
+                    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);
+                }
+
+                // resolve to the true indices using the indirection list
+                // Note: this is done on the CPU to avoid double-indirection in the shader
+                IndexVector resolvedMeshBufferIndices(m_meshBufferIndices.GetIndexList().size());
+                uint32_t resolvedMeshBufferIndex = 0;
+                for (auto& meshBufferIndex : m_meshBufferIndices.GetIndexList())
+                {
+                    if (!m_meshBufferIndices.IsValidIndex(meshBufferIndex))
+                    {
+                        resolvedMeshBufferIndices[resolvedMeshBufferIndex++] = InvalidIndex;
+                    }
+                    else
                     {
-                        MaterialInfo materialInfo;
-                        subMesh.m_baseColor.StoreToFloat4(materialInfo.m_baseColor.data());
-                        materialInfo.m_metallicFactor = subMesh.m_metallicFactor;
-                        materialInfo.m_roughnessFactor = subMesh.m_roughnessFactor;
-                        materialInfo.m_textureFlags = subMesh.m_textureFlags;
-                        materialInfo.m_textureStartIndex = textureStartIndex;
+                        resolvedMeshBufferIndices[resolvedMeshBufferIndex++] = m_meshBuffers.GetIndirectionList()[meshBufferIndex];
+                    }
+                }
+
+                currentMeshBufferIndicesGpuBuffer->UpdateData(resolvedMeshBufferIndices.data(), newMeshBufferIndicesByteCount);
 
-                        // add the count of textures present in this subMesh to the start index for the next subMesh
-                        textureStartIndex += RHI::CountBitsSet(aznumeric_cast<uint32_t>(materialInfo.m_textureFlags));
+                // update material texture indices buffer
+                Data::Instance<RPI::Buffer>& currentMaterialTextureIndicesGpuBuffer = m_materialTextureIndicesGpuBuffer[m_currentIndexListFrameIndex];
+                uint32_t newMaterialTextureIndicesByteCount = aznumeric_cast<uint32_t>(m_materialTextureIndices.GetIndexList().size()) * sizeof(uint32_t);
 
-                        materialInfos.emplace_back(materialInfo);
+                if (currentMaterialTextureIndicesGpuBuffer == nullptr)
+                {
+                    // allocate the MaterialInfo structured buffer
+                    RPI::CommonBufferDescriptor desc;
+                    desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly;
+                    desc.m_bufferName = "RayTracingMaterialTextureIndices";
+                    desc.m_byteCount = newMaterialTextureIndicesByteCount;
+                    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);
+                }
+
+                // resolve to the true indices using the indirection list
+                // Note: this is done on the CPU to avoid double-indirection in the shader
+                IndexVector resolvedMaterialTextureIndices(m_materialTextureIndices.GetIndexList().size());
+                uint32_t resolvedMaterialTextureIndex = 0;
+                for (auto& materialTextureIndex : m_materialTextureIndices.GetIndexList())
+                {
+                    if (!m_materialTextureIndices.IsValidIndex(materialTextureIndex))
+                    {
+                        resolvedMaterialTextureIndices[resolvedMaterialTextureIndex++] = InvalidIndex;
+                    }
+                    else
+                    {
+                        resolvedMaterialTextureIndices[resolvedMaterialTextureIndex++] = m_materialTextures.GetIndirectionList()[materialTextureIndex];
                     }
                 }
 
-                m_materialInfoBuffer->UpdateData(materialInfos.data(), newMaterialByteCount);
-                m_materialInfoBufferNeedsUpdate = false;
+                currentMaterialTextureIndicesGpuBuffer->UpdateData(resolvedMaterialTextureIndices.data(), newMaterialTextureIndicesByteCount);
+
+                m_indexListNeedsUpdate = false;
             }
         }
 
@@ -469,49 +639,13 @@ namespace AZ
             }
 
             bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_meshInfo"));
-            m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_meshInfoBuffer->GetBufferView());
-
-            if (m_subMeshCount)
-            {
-                AZStd::vector<const RHI::BufferView*> meshBuffers;
-                for (const auto& mesh : m_meshes)
-                {
-                    const SubMeshVector& subMeshes = mesh.second.m_subMeshes;
-                    for (const auto& subMesh : subMeshes)
-                    {
-                        // add the stream buffers for this sub-mesh to the mesh buffer list,
-                        // this is sent to the shader as an unbounded array in the Srg
-                        meshBuffers.push_back(subMesh.m_indexShaderBufferView.get());
-                        meshBuffers.push_back(subMesh.m_positionShaderBufferView.get());
-                        meshBuffers.push_back(subMesh.m_normalShaderBufferView.get());
-
-                        if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::Tangent))
-                        {
-                            meshBuffers.push_back(subMesh.m_tangentShaderBufferView.get());
-                        }
-
-                        if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::Bitangent))
-                        {
-                            meshBuffers.push_back(subMesh.m_bitangentShaderBufferView.get());
-                        }
-
-                        if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::UV))
-                        {
-                            meshBuffers.push_back(subMesh.m_uvShaderBufferView.get());
-                        }
-                    }
-                }
+            m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_meshInfoGpuBuffer[m_currentMeshInfoFrameIndex]->GetBufferView());
 
-                // Check if buffer view data changed from previous frame.
-                // Look into making 'm_meshBuffers != meshBuffers' faster by possibly building a crc and doing a crc check.
-                if (m_meshBuffers.size() != meshBuffers.size() || m_meshBuffers != meshBuffers)
-                {
-                    m_meshBuffers = meshBuffers;
-                    RHI::ShaderInputBufferUnboundedArrayIndex bufferUnboundedArrayIndex = srgLayout->FindShaderInputBufferUnboundedArrayIndex(AZ::Name("m_meshBuffers"));
-                    m_rayTracingSceneSrg->SetBufferViewUnboundedArray(bufferUnboundedArrayIndex, m_meshBuffers);
-                }
-            }
+            bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_meshBufferIndices"));
+            m_rayTracingSceneSrg->SetBufferView(bufferIndex, m_meshBufferIndicesGpuBuffer[m_currentIndexListFrameIndex]->GetBufferView());
 
+            RHI::ShaderInputBufferUnboundedArrayIndex bufferUnboundedArrayIndex = srgLayout->FindShaderInputBufferUnboundedArrayIndex(AZ::Name("m_meshBuffers"));
+            m_rayTracingSceneSrg->SetBufferViewUnboundedArray(bufferUnboundedArrayIndex, m_meshBuffers.GetResourceList());
             m_rayTracingSceneSrg->Compile();
         }
 
@@ -521,49 +655,13 @@ namespace AZ
             RHI::ShaderInputBufferIndex bufferIndex;
 
             bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_materialInfo"));
-            m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialInfoBuffer->GetBufferView());
+            m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialInfoGpuBuffer[m_currentMaterialInfoFrameIndex]->GetBufferView());
 
-            if (m_subMeshCount)
-            {
-                AZStd::vector<const RHI::ImageView*> materialTextures;
-                for (const auto& mesh : m_meshes)
-                {
-                    const SubMeshVector& subMeshes = mesh.second.m_subMeshes;
-                    for (const auto& subMesh : subMeshes)
-                    {
-                        // add the baseColor, normal, metallic, and roughness images for this sub-mesh to the material texture list,
-                        // this is sent to the shader as an unbounded array in the Srg
-                        if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::BaseColor))
-                        {
-                            materialTextures.push_back(subMesh.m_baseColorImageView.get());
-                        }
-
-                        if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Normal))
-                        {
-                            materialTextures.push_back(subMesh.m_normalImageView.get());
-                        }
-
-                        if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Metallic))
-                        {
-                            materialTextures.push_back(subMesh.m_metallicImageView.get());
-                        }
-
-                        if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Roughness))
-                        {
-                            materialTextures.push_back(subMesh.m_roughnessImageView.get());
-                        }
-                    }
-                }
-
-                // Check if image view data changed from previous frame. 
-                if (m_materialTextures.size() != materialTextures.size() || m_materialTextures != materialTextures)
-                {
-                    m_materialTextures = materialTextures;
-                    RHI::ShaderInputImageUnboundedArrayIndex textureUnboundedArrayIndex = srgLayout->FindShaderInputImageUnboundedArrayIndex(AZ::Name("m_materialTextures"));
-                    m_rayTracingMaterialSrg->SetImageViewUnboundedArray(textureUnboundedArrayIndex, materialTextures);
-                }
-            }
+            bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_materialTextureIndices"));
+            m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialTextureIndicesGpuBuffer[m_currentIndexListFrameIndex]->GetBufferView());
 
+            RHI::ShaderInputImageUnboundedArrayIndex textureUnboundedArrayIndex = srgLayout->FindShaderInputImageUnboundedArrayIndex(AZ::Name("m_materialTextures"));
+            m_rayTracingMaterialSrg->SetImageViewUnboundedArray(textureUnboundedArrayIndex, m_materialTextures.GetResourceList());
             m_rayTracingMaterialSrg->Compile();
         }
     }        

+ 68 - 25
Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h

@@ -8,6 +8,8 @@
 
 #pragma once
 
+#include <RayTracing/RayTracingResourceList.h>
+#include <RayTracing/RayTracingIndexList.h>
 #include <Atom/Feature/TransformService/TransformServiceFeatureProcessor.h>
 #include <Atom/RHI/RayTracingAccelerationStructure.h>
 #include <Atom/RHI/RayTracingBufferPools.h>
@@ -62,7 +64,9 @@ namespace AZ
             // FeatureProcessor overrides ...
             void Activate() override;
 
-            //! Contains data for a single sub-mesh
+            struct Mesh;
+
+            //! Contains data for a single subMesh
             struct SubMesh
             {
                 // vertex streams
@@ -112,8 +116,19 @@ namespace AZ
                 RHI::Ptr<const RHI::ImageView> m_normalImageView;
                 RHI::Ptr<const RHI::ImageView> m_metallicImageView;
                 RHI::Ptr<const RHI::ImageView> m_roughnessImageView;
+
+                // parent mesh
+                Mesh* m_mesh = nullptr;
+
+                // index of this mesh in the subMesh list, also applies to the MeshInfo and MaterialInfo entries
+                uint32_t m_globalIndex = InvalidIndex;
+
+                // index of this mesh in the parent Mesh's subMesh list
+                uint32_t m_subMeshIndex = InvalidIndex;
             };
+
             using SubMeshVector = AZStd::vector<SubMesh>;
+            using IndexVector = AZStd::vector<uint32_t>;
 
             //! Contains data for the top level mesh, including the list of sub-meshes
             struct Mesh
@@ -121,8 +136,8 @@ namespace AZ
                 // assetId of the model
                 AZ::Data::AssetId m_assetId = AZ::Data::AssetId{};
 
-                // sub-mesh list
-                SubMeshVector m_subMeshes;
+                // indices of subMeshes in the subMesh list
+                IndexVector m_subMeshIndices;
 
                 // mesh transform
                 AZ::Transform m_transform = AZ::Transform::CreateIdentity();
@@ -131,7 +146,6 @@ namespace AZ
                 AZ::Vector3 m_nonUniformScale = AZ::Vector3::CreateOne();
             };
 
-            using MeshMap = AZStd::map<uint32_t, Mesh>;
             using ObjectId = TransformServiceFeatureProcessorInterface::ObjectId;
 
             //! Sets ray tracing data for a mesh.
@@ -147,9 +161,9 @@ namespace AZ
             void SetMeshTransform(const ObjectId objectId, const AZ::Transform transform,
                 const AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne());
 
-            //! Retrieves ray tracing data for all meshes in the scene
-            const MeshMap& GetMeshes() const { return m_meshes; }
-            MeshMap& GetMeshes() { return m_meshes; }
+            //! Retrieves the map of all subMeshes in the scene
+            const SubMeshVector& GetSubMeshes() const { return m_subMeshes; }
+            SubMeshVector& GetSubMeshes() { return m_subMeshes; }
 
             //! Retrieves the RayTracingSceneSrg
             Data::Instance<RPI::ShaderResourceGroup> GetRayTracingSceneSrg() const { return m_rayTracingSceneSrg; }
@@ -175,10 +189,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> GetMeshInfoBuffer() const { return m_meshInfoBuffer; }
+            const Data::Instance<RPI::Buffer> GetMeshInfoGpuBuffer() const { return m_meshInfoGpuBuffer[m_currentMeshInfoFrameIndex]; }
 
             //! Retrieves the GPU buffer containing information for all ray tracing materials.
-            const Data::Instance<RPI::Buffer> GetMaterialInfoBuffer() const { return m_materialInfoBuffer; }
+            const Data::Instance<RPI::Buffer> GetMaterialInfoGpuBuffer() const { return m_materialInfoGpuBuffer[m_currentMaterialInfoFrameIndex]; }
 
             //! Updates the RayTracingSceneSrg and RayTracingMaterialSrg, called after the TLAS is allocated in the RayTracingAccelerationStructurePass
             void UpdateRayTracingSrgs();
@@ -206,6 +220,7 @@ namespace AZ
 
             void UpdateMeshInfoBuffer();
             void UpdateMaterialInfoBuffer();
+            void UpdateIndexLists();
             void UpdateRayTracingSceneSrg();
             void UpdateRayTracingMaterialSrg();
 
@@ -214,7 +229,9 @@ namespace AZ
 
             // mesh data for meshes that should be included in ray tracing operations,
             // this is a map of the mesh object Id to the ray tracing data for the sub-meshes
+            using MeshMap = AZStd::map<uint32_t, Mesh>;
             MeshMap m_meshes;
+            SubMeshVector m_subMeshes;
 
             // buffer pools used in ray tracing operations
             RHI::Ptr<RHI::RayTracingBufferPools> m_bufferPools;
@@ -245,12 +262,13 @@ namespace AZ
             // structure for data in the m_meshInfoBuffer, shaders that use the buffer must match this type
             struct MeshInfo
             {
-                uint32_t m_indexOffset;
-                uint32_t m_positionOffset;
-                uint32_t m_normalOffset;
-                uint32_t m_tangentOffset;
-                uint32_t m_bitangentOffset;
-                uint32_t m_uvOffset;
+                // byte offsets into the mesh buffer views
+                uint32_t m_indexByteOffset = 0;
+                uint32_t m_positionByteOffset = 0;
+                uint32_t m_normalByteOffset = 0;
+                uint32_t m_tangentByteOffset = 0;
+                uint32_t m_bitangentByteOffset = 0;
+                uint32_t m_uvByteOffset = 0;
 
                 RayTracingSubMeshBufferFlags m_bufferFlags = RayTracingSubMeshBufferFlags::None;
                 uint32_t m_bufferStartIndex = 0;
@@ -259,8 +277,12 @@ namespace AZ
                 AZStd::array<float, 12> m_worldInvTranspose; // float3x4
             };
 
-            // buffer containing a MeshInfo for each sub-mesh
-            Data::Instance<RPI::Buffer> m_meshInfoBuffer;
+            // 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;
 
             // structure for data in the m_materialInfoBuffer, shaders that use the buffer must match this type
             struct MaterialInfo
@@ -272,21 +294,42 @@ namespace AZ
                 uint32_t m_textureStartIndex = 0;
             };
 
-            // buffer containing a MaterialInfo for each sub-mesh
-            Data::Instance<RPI::Buffer> m_materialInfoBuffer;
+            // vector of MaterialInfo, transferred to the materialInfoGpuBuffer
+            using MaterialInfoVector = AZStd::vector<MaterialInfo>;
+            MaterialInfoVector m_materialInfos;
+            Data::Instance<RPI::Buffer> m_materialInfoGpuBuffer[BufferFrameCount];
+            uint32_t m_currentMaterialInfoFrameIndex = 0;
 
-            // flag indicating we need to update the meshInfo buffer
+            // update flags
             bool m_meshInfoBufferNeedsUpdate = false;
-
-            // flag indicating we need to update the materialInfo buffer
             bool m_materialInfoBufferNeedsUpdate = false;
+            bool m_indexListNeedsUpdate = false;
 
             // side list for looking up existing BLAS objects so they can be re-used when the same mesh is added multiple times
             BlasInstanceMap m_blasInstanceMap;
 
-            // Cache view pointers so we dont need to update them if none changed from frame to frame.
-            AZStd::vector<const RHI::BufferView*> m_meshBuffers;
-            AZStd::vector<const RHI::ImageView*> m_materialTextures;
+            // Mesh buffer and material texture resources are managed with a RayTracingResourceList, which contains an internal
+            // indirection list.  This allows resource entries to be swapped inside the RayTracingResourceList when removing entries,
+            // without invalidating the indices held here in the m_meshBufferIndices and m_materialTextureIndices lists.
+            //
+            // RayTracingIndexList implements an internal freelist chain stored inside the list itself, allowing entries to be
+            // reused after elements are removed.
+            
+            // mesh buffer and material texture resource lists, accessed by the shader through an unbounded array
+            RayTracingResourceList<RHI::BufferView> m_meshBuffers;
+            RayTracingResourceList<const RHI::ImageView> m_materialTextures;
+
+            // mesh buffer and material texture index lists, these are the indices into the resource lists
+            static const uint32_t NumMeshBuffersPerMesh = 6;
+            RayTracingIndexList<NumMeshBuffersPerMesh> m_meshBufferIndices;
+
+            static const uint32_t NumMaterialTexturesPerMesh = 4;
+            RayTracingIndexList<NumMaterialTexturesPerMesh> m_materialTextureIndices;
+
+            // Gpu buffers for the mesh and material resources
+            Data::Instance<RPI::Buffer> m_meshBufferIndicesGpuBuffer[BufferFrameCount];
+            Data::Instance<RPI::Buffer> m_materialTextureIndicesGpuBuffer[BufferFrameCount];
+            uint32_t m_currentIndexListFrameIndex = 0;
         };
     }
 }

+ 166 - 0
Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingIndexList.h

@@ -0,0 +1,166 @@
+/*
+ * 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 <AzCore/std/containers/vector.h>
+#include <AzCore/std/containers/map.h>
+#include <AzCore/Casting/numeric_cast.h>
+
+namespace AZ
+{
+    namespace Render
+    {
+        static const uint32_t InvalidIndex = aznumeric_cast<uint32_t>(-1);
+
+        //! Manages an index list used by RayTracing, with an internal freelist chain.
+        //! 
+        //! Indices are stored in a flat array, and new indices are either added to the end
+        //! or in the first available free index.
+        //! 
+        //! The freelist chain is stored inside array itself, with each entry in the chain pointing
+        //! to the next free index, and terminated with InvalidIndex.  Free list entries have
+        //! FreeListThreshold added to their value to indicate they are part of the freelist.
+        template<uint32_t BlockSize>
+        class RayTracingIndexList
+        {
+        public:
+
+            RayTracingIndexList() = default;
+            ~RayTracingIndexList() = default;
+
+            // add a BlockSize set of entries to the index list
+            uint32_t AddEntry(AZStd::array<uint32_t, BlockSize> entry);
+
+            // add an entry, non-array version for use when BlockSize == 1
+            uint32_t AddEntry(uint32_t entry);
+
+            // set a BlockSize set of entries at the specified index
+            void SetEntry(uint32_t index, AZStd::array<uint32_t, BlockSize> entry);
+
+            // set an existing entry, non-array version for use when BlockSize == 1
+            void SetEntry(uint32_t index, uint32_t entry);
+
+            // remove BlockSize entries starting at the specified index
+            void RemoveEntry(uint32_t index);
+
+            // returns the index list
+            using IndexVector = AZStd::vector<uint32_t>;
+            const IndexVector& GetIndexList() const { return m_indices; }
+
+            // returns true if the index is valid
+            bool IsValidIndex(uint32_t index) const;
+
+            // clears the index list and all associated state
+            void Reset();
+
+        private:           
+
+            // freelist index entries are at or above this value
+            static const uint32_t FreeListThreshold = 1000000000;
+
+            // helper functions to encode/decode freelist chain indices
+            uint32_t EncodeFreeListIndex(uint32_t index) const { return (index == InvalidIndex) ? InvalidIndex : (index + FreeListThreshold); }
+            uint32_t DecodeFreeListIndex(uint32_t index) const { return (index == InvalidIndex) ? InvalidIndex : (index - FreeListThreshold); }
+
+            // list of indices
+            IndexVector m_indices;
+
+            // starting index of the freelist chain
+            uint32_t m_freeStartIndex = InvalidIndex;
+        };
+
+        template<uint32_t BlockSize>
+        uint32_t RayTracingIndexList<BlockSize>::AddEntry(AZStd::array<uint32_t, BlockSize> entry)
+        {
+            uint32_t index = 0;
+
+            if (m_freeStartIndex == InvalidIndex)
+            {
+                // no free entries, insert at the end of the index list
+                index = aznumeric_cast<uint32_t>(m_indices.size());
+                m_indices.insert(m_indices.end(), entry.begin(), entry.end());
+            }
+            else
+            {
+                // get the next free index from the list
+                uint32_t nextFreeIndex = m_indices[m_freeStartIndex];
+
+                // overwrite the indices list with the new entries at the free index
+                index = m_freeStartIndex;
+                AZStd::copy(entry.begin(), entry.end(), m_indices.begin() + index);
+
+                // move the start of the free index chain to nextFreeIndex
+                m_freeStartIndex = DecodeFreeListIndex(nextFreeIndex);
+            }
+
+            return index;
+        }
+
+        template<uint32_t BlockSize>
+        uint32_t RayTracingIndexList<BlockSize>::AddEntry(uint32_t entry)
+        {
+            return AddEntry(AZStd::array<uint32_t, 1>{entry});
+        }
+
+        template<uint32_t BlockSize>
+        void RayTracingIndexList<BlockSize>::SetEntry(uint32_t index, AZStd::array<uint32_t, BlockSize> entry)
+        {
+            AZ_Assert(index < m_indices.size(), "Index passed to SetEntry exceeds list size");
+            AZ_Assert(index % BlockSize == 0, "Index passed must be a multiple of the BlockSize");
+
+            AZStd::copy(entry.begin(), entry.end(), m_indices.begin() + index);
+        }
+
+        template<uint32_t BlockSize>
+        void RayTracingIndexList<BlockSize>::SetEntry(uint32_t index, uint32_t entry)
+        {
+            SetEntry(index, AZStd::array<uint32_t, 1>{entry});
+        }
+
+        template<uint32_t BlockSize>
+        void RayTracingIndexList<BlockSize>::RemoveEntry(uint32_t index)
+        {
+            if (m_freeStartIndex == InvalidIndex)
+            {
+                // no free entries, just set the start index to this entry
+                m_freeStartIndex = index;
+            }
+            else
+            {
+                // walk the free index chain until we reach the end (marked by InvalidIndex)
+                uint32_t currentIndex = m_freeStartIndex;
+                uint32_t nextIndex = DecodeFreeListIndex(m_indices[currentIndex]);
+                while (nextIndex != InvalidIndex)
+                {
+                    currentIndex = nextIndex;
+                    nextIndex = DecodeFreeListIndex(m_indices[nextIndex]);
+                }
+
+                // store the index 
+                m_indices[currentIndex] = EncodeFreeListIndex(index);
+            }
+
+            // terminate the free index chain by setting the last entry to InvalidIndex
+            m_indices[index] = InvalidIndex;
+        }
+
+        template<uint32_t BlockSize>
+        bool RayTracingIndexList<BlockSize>::IsValidIndex(uint32_t index) const
+        {
+            return (index != m_freeStartIndex && index < FreeListThreshold);
+        }
+
+        template<uint32_t BlockSize>
+        void RayTracingIndexList<BlockSize>::Reset()
+        {
+            m_indices.clear();
+            m_freeStartIndex = InvalidIndex;
+        }
+    }
+}

+ 166 - 0
Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingResourceList.h

@@ -0,0 +1,166 @@
+/*
+ * 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 <AzCore/std/containers/vector.h>
+#include <AzCore/std/containers/map.h>
+#include <AzCore/Casting/numeric_cast.h>
+#include <RayTracing/RayTracingIndexList.h>
+
+namespace AZ
+{
+    namespace Render
+    {
+        //! Manages a resource list used by RayTracing.
+        //!
+        //! Resources are stored in a flat array.  There is also a map that stores the index of the resource
+        //! in the array, its reference count, and the index in the indirection list.  This map is used to determine
+        //! if the resource is already known, and how to locate its entries in the resource and indirection lists.
+        //! 
+        //! The indirection list provides a stable index for resources, and is returned to clients of this class.
+        //! This allows resources to be moved in the resource array without affecting externally held indices,
+        //! since these refer to the indirection list, which in turn points to the resource list.
+        template<class TResource>
+        class RayTracingResourceList
+        {
+        public:
+
+            using ResourceVector = AZStd::vector<TResource*>;
+            using IndexVector = AZStd::vector<uint32_t>;
+
+            RayTracingResourceList() = default;
+            ~RayTracingResourceList() = default;
+
+            // adds a resource to the list, or increments the reference count, and returns the index of the resource
+            // Note: the index returned is an indirection index, meaning it is stable when other entries are removed
+            uint32_t AddResource(TResource* resource);
+
+            // removes a resource from the list, or decrements the reference count
+            // Note: removing a resource will not affect any previously returned indices for other resources
+            void RemoveResource(TResource* resource);
+
+            // returns the resource list
+            ResourceVector& GetResourceList() { return m_resources; }
+
+            // returns the indirection list
+            const IndexVector& GetIndirectionList() const { return m_indirectionList.GetIndexList(); }
+
+            // clears the resource list and all associated state
+            void Reset();
+
+        private:
+
+            struct IndexMapEntry
+            {
+                // index of the entry in the main list
+                uint32_t m_index = InvalidIndex;
+
+                // index of the entry in the indirection list
+                uint32_t m_indirectionIndex = InvalidIndex;
+
+                // reference count
+                uint32_t m_count = 0;
+            };
+
+            using ResourceMap = AZStd::map<TResource*, IndexMapEntry>;
+
+            ResourceVector m_resources;
+            ResourceMap m_resourceMap;
+            RayTracingIndexList<1> m_indirectionList;
+        };
+
+        template<class TResource>
+        uint32_t RayTracingResourceList<TResource>::AddResource(TResource* resource)
+        {
+            if (resource == nullptr)
+            {
+                return InvalidIndex;
+            }
+
+            uint32_t indirectionIndex = 0;
+            typename ResourceMap::iterator it = m_resourceMap.find(resource);
+            if (it == m_resourceMap.end())
+            {
+                // resource not found, add it to the resource list and the indirection list
+                m_resources.push_back(resource);
+                uint32_t resourceIndex = aznumeric_cast<uint32_t>(m_resources.size()) - 1;
+
+                indirectionIndex = m_indirectionList.AddEntry(resourceIndex);
+
+                // add the resource map entry containing the true index, indirection index, and reference count
+                IndexMapEntry entry;
+                entry.m_index = resourceIndex;
+                entry.m_indirectionIndex = indirectionIndex;
+                entry.m_count = 1;
+                m_resourceMap.insert({ resource, entry });
+            }
+            else
+            {
+                // resource is already known, update the reference count and return the indirection index
+                it->second.m_count++;
+                indirectionIndex = it->second.m_indirectionIndex;
+            }
+
+            return indirectionIndex;
+        }
+
+        template<class TResource>
+        void RayTracingResourceList<TResource>::RemoveResource(TResource* resource)
+        {
+            if (resource == nullptr)
+            {
+                return;
+            }
+
+            typename ResourceMap::iterator it = m_resourceMap.find(resource);
+            AZ_Assert(it != m_resourceMap.end(), "Unable to find resource in the ResourceMap");
+
+            // decrement reference count
+            it->second.m_count--;
+
+            // if the reference count is zero then remove the entry from both the map and the main resource list
+            if (it->second.m_count == 0)
+            {
+                uint32_t resourceIndex = it->second.m_index;
+
+                if (resourceIndex < m_resources.size() - 1)
+                {
+                    // the resource we're removing is in the middle of the list - swap the last entry to this position
+                    typename ResourceMap::iterator itLast = m_resourceMap.find(m_resources.back());
+                    AZ_Assert(itLast != m_resourceMap.end(), "Unable to find the last resource in the ResourceMap");
+                    m_resources[resourceIndex] = m_resources.back();
+
+                    // update the swapped entry with its new index in the resource list
+                    itLast->second.m_index = resourceIndex;
+
+                    // update the indirection vector entry of the swapped entry to point to the new position
+                    // Note: any indirection indices returned by AddResource for other resources remain stable, this just updates the indirection entry
+                    m_indirectionList.SetEntry(itLast->second.m_indirectionIndex, resourceIndex);                 
+                }
+
+                // remove the last entry from the resource list
+                m_resources.pop_back();
+
+                // remove the entry from the resource map
+                m_resourceMap.erase(it);
+
+                // remove the entry from the indirection list
+                m_indirectionList.RemoveEntry(it->second.m_indirectionIndex);
+            }
+        }
+
+        template<class TResource>
+        void RayTracingResourceList<TResource>::Reset()
+        {
+            m_resources.clear();
+            m_resourceMap.clear();
+            m_indirectionList.Reset();
+        }
+    }
+}

+ 2 - 0
Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake

@@ -306,6 +306,8 @@ set(FILES
     Source/PostProcessing/TaaPass.cpp
     Source/RayTracing/RayTracingFeatureProcessor.h
     Source/RayTracing/RayTracingFeatureProcessor.cpp
+    Source/RayTracing/RayTracingResourceList.h
+    Source/RayTracing/RayTracingIndexList.h
     Source/RayTracing/RayTracingAccelerationStructurePass.cpp
     Source/RayTracing/RayTracingAccelerationStructurePass.h
     Source/RayTracing/RayTracingPass.cpp