From d862da1be6f28cf14f564c80c682009a817e648f Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Fri, 16 Oct 2026 12:24:12 +0100 Subject: [PATCH 1/3] Cache RT shader table state, reuse shader table buffers, and skip unused RT work Cache per-shader technique and per-library hit-group state in RT shader table preparation. Reuse per-back-buffer shader table buffers and cache shader info in RT shader table creation. Skip TransformMeshes when no skinned or morphed geometry was added this frame. Skip RT material data application for shader-table entries whose library adds no records. --- trinity/Raytracing/Tr2RaytracingGeometry.cpp | 251 +++++++++++++++---- trinity/Raytracing/Tr2RaytracingGeometry.h | 2 + trinityal/dx12/Tr2RtShaderTableALDx12.cpp | 147 +++++++++-- trinityal/dx12/Tr2RtShaderTableALDx12.h | 13 + trinityal/include/Tr2RtShaderTableAL.h | 3 + trinityal/src/Tr2RtShaderTableAL.cpp | 18 +- 6 files changed, 364 insertions(+), 70 deletions(-) diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.cpp b/trinity/Raytracing/Tr2RaytracingGeometry.cpp index 85f8272b5..be9684f5d 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.cpp +++ b/trinity/Raytracing/Tr2RaytracingGeometry.cpp @@ -13,14 +13,44 @@ #include "Tr2MeshArea.h" #include "ITr2TextureProvider.h" +#include "TriSettingsRegistrar.h" #if TRINITY_PLATFORM == TRINITY_DIRECTX12 #include <../trinityal/dx12/Tr2BufferALDx12.h> #endif +bool g_rtShaderTableMaterialCache = true; +TRI_REGISTER_SETTING( "rtShaderTableMaterialCache", g_rtShaderTableMaterialCache ); +bool& g_rtShaderTableReuse = Tr2RtShaderTableAL::s_reuseBuffers; +TRI_REGISTER_SETTING( "rtShaderTableReuse", g_rtShaderTableReuse ); +bool g_rtTransformMeshesEarlyOut = true; +TRI_REGISTER_SETTING( "rtTransformMeshesEarlyOut", g_rtTransformMeshesEarlyOut ); +bool g_rtSkipUnusedMaterialApply = true; +TRI_REGISTER_SETTING( "rtSkipUnusedMaterialApply", g_rtSkipUnusedMaterialApply ); + +CCP_STATS_DECLARE( rtShaderTableShaders, "Trinity/RT/shaderTableShaders", true, CST_COUNTER_HIGH, "Distinct shaders resolved for the shader table this frame." ); +CCP_STATS_DECLARE( rtShaderTableLibraries, "Trinity/RT/shaderTableLibraries", true, CST_COUNTER_HIGH, "Distinct raytracing libraries seen by the shader table this frame." ); +CCP_STATS_DECLARE( rtTransformMeshesSkipped, "Trinity/RT/transformMeshesSkipped", true, CST_COUNTER_HIGH, "TransformMeshes returned early because no skinned or morphed geometry was added this frame." ); +CCP_STATS_DECLARE( rtShaderTableMaterialApplies, "Trinity/RT/shaderTableMaterialApplies", true, CST_COUNTER_HIGH, "Shader table entries whose RT material data was built and applied this frame." ); + namespace { std::mutex s_geometryConstantsMutex; + +struct RtLibraryState +{ + const Tr2EffectLibrary* lib = nullptr; + std::vector hitGroupNames; + uint32_t materialIndex = 0; + bool seen = false; + bool addsRecords = false; +}; + +struct RtShaderState +{ + uint32_t techniqueIndex = 0; + RtLibraryState* lib = nullptr; +}; } //*********************************************************** @@ -613,6 +643,7 @@ void Tr2RaytracingGeometry::BeginSceneUpdate() { m_geometryData.clear(); m_usedResources.Clear(); + m_hasDeformedGeometry.store( false, std::memory_order_relaxed ); } @@ -642,80 +673,184 @@ void Tr2RaytracingGeometry::PrepareShaderTableDescription( Tr2RenderContext& ren TRINITY_STATS_ZONE( __FUNCTION__ ); for( int32_t i = 0; i < numRaycasters; i++ ) { - *shaderTableDescs[i] = Tr2RtShaderTableDescriptionAL(); + if( g_rtShaderTableReuse ) + { + shaderTableDescs[i]->Clear(); + } + else + { + *shaderTableDescs[i] = Tr2RtShaderTableDescriptionAL(); + } shaderTableDescs[i]->Reserve( m_geometryData.size() ); } - std::vector>> seenLibraries; - seenLibraries.resize( numRaycasters ); + if( !g_rtShaderTableMaterialCache ) + { + std::vector>> seenLibraries; + seenLibraries.resize( numRaycasters ); + + uint32_t materialIndex = 0; + for( auto it = begin( m_geometryData ); it != end( m_geometryData ); ++it ) + { + Tr2RtLocalMaterialDescriptionAL material; + if( !it->material ) + { + continue; + } + auto shader = it->material->GetShaderStateInterface(); + if( !shader ) + { + continue; + } + uint32_t techniqueIndex; + if( !shader->GetTechniqueIndex( m_rtShadowTechniqueName, techniqueIndex ) ) + { + continue; + } + auto& desc = shader->GetEffectDescription(); + if( desc.techniques[techniqueIndex].libraries.empty() ) + { + continue; + } + auto& lib = desc.techniques[techniqueIndex].libraries[0]; + if( !lib.anyHitName.empty() ) + { + it->isTransparent = true; + } + + if( it->perObjectData ) + { + material.SetConstants( Tr2Renderer::GetPerObjectPSStartRegister(), *it->perObjectData ); + } + if( it->vertexBufferData ) + { + material.SetConstants( Tr2Renderer::GetPerObjectRTVertexBufferDataRegister(), *it->vertexBufferData ); + } + it->material->ApplyMaterialDataForRtMaterial( techniqueIndex, material, renderContext ); + + bool consumedMaterialIndex = false; + for( int32_t i = 0; i < numRaycasters; i++ ) + { + auto foundLib = seenLibraries[i].find( lib.libraryHandle ); + if( foundLib == end( seenLibraries[i] ) ) + { + auto hitGroupName = pipelineManagers[i]->AddHitGroup( lib ); + seenLibraries[i][lib.libraryHandle] = std::make_pair( hitGroupName, materialIndex ); + shaderTableDescs[i]->AddHitGroup( hitGroupName.c_str(), material ); + it->materialIndex = materialIndex; + consumedMaterialIndex = true; + } + else if( !lib.localInput.signature.registers.empty() ) + { + shaderTableDescs[i]->AddHitGroup( foundLib->second.first.c_str(), material ); + it->materialIndex = materialIndex; + consumedMaterialIndex = true; + } + else + { + it->materialIndex = foundLib->second.second; + } + } + if( consumedMaterialIndex ) + { + materialIndex++; + } + } + return; + } + + std::unordered_map shaders; + std::map libraries; uint32_t materialIndex = 0; - for( auto it = begin( m_geometryData ); it != end( m_geometryData ); ++it ) + for( auto& geometry : m_geometryData ) { - Tr2RtLocalMaterialDescriptionAL material; - if( !it->material ) + if( !geometry.material ) { continue; } - auto shader = it->material->GetShaderStateInterface(); + auto shader = geometry.material->GetShaderStateInterface(); if( !shader ) { continue; } - uint32_t techniqueIndex; - if( !shader->GetTechniqueIndex( m_rtShadowTechniqueName, techniqueIndex ) ) + + auto shaderState = shaders.find( shader ); + if( shaderState == end( shaders ) ) { - continue; + shaderState = shaders.emplace( shader, RtShaderState() ).first; + uint32_t techniqueIndex; + if( shader->GetTechniqueIndex( m_rtShadowTechniqueName, techniqueIndex ) ) + { + auto& libs = shader->GetEffectDescription().techniques[techniqueIndex].libraries; + if( !libs.empty() ) + { + auto& libState = libraries[libs[0].libraryHandle]; + if( !libState.lib ) + { + libState.lib = &libs[0]; + libState.hitGroupNames.resize( numRaycasters ); + libState.addsRecords = !libs[0].localInput.signature.registers.empty(); + } + shaderState->second.techniqueIndex = techniqueIndex; + shaderState->second.lib = &libState; + } + } } - auto& desc = shader->GetEffectDescription(); - if( desc.techniques[techniqueIndex].libraries.empty() ) + auto libState = shaderState->second.lib; + if( !libState ) { continue; } - auto& lib = desc.techniques[techniqueIndex].libraries[0]; + auto& lib = *libState->lib; if( !lib.anyHitName.empty() ) { - it->isTransparent = true; + geometry.isTransparent = true; + } + if( g_rtSkipUnusedMaterialApply && libState->seen && !libState->addsRecords ) + { + geometry.materialIndex = libState->materialIndex; + continue; } - if( it->perObjectData ) + CCP_STATS_INC( rtShaderTableMaterialApplies ); + Tr2RtLocalMaterialDescriptionAL material; + if( geometry.perObjectData ) { - material.SetConstants( Tr2Renderer::GetPerObjectPSStartRegister(), *it->perObjectData ); + material.SetConstants( Tr2Renderer::GetPerObjectPSStartRegister(), *geometry.perObjectData ); } - if( it->vertexBufferData ) + if( geometry.vertexBufferData ) { - material.SetConstants( Tr2Renderer::GetPerObjectRTVertexBufferDataRegister(), *it->vertexBufferData ); + material.SetConstants( Tr2Renderer::GetPerObjectRTVertexBufferDataRegister(), *geometry.vertexBufferData ); } - it->material->ApplyMaterialDataForRtMaterial( techniqueIndex, material, renderContext ); + geometry.material->ApplyMaterialDataForRtMaterial( shaderState->second.techniqueIndex, material, renderContext ); - bool consumedMaterialIndex = false; - for( int32_t i = 0; i < numRaycasters; i++ ) + if( !libState->seen ) { - auto foundLib = seenLibraries[i].find( lib.libraryHandle ); - if( foundLib == end( seenLibraries[i] ) ) - { - auto hitGroupName = pipelineManagers[i]->AddHitGroup( lib ); - seenLibraries[i][lib.libraryHandle] = std::make_pair( hitGroupName, materialIndex ); - shaderTableDescs[i]->AddHitGroup( hitGroupName.c_str(), material ); - it->materialIndex = materialIndex; - consumedMaterialIndex = true; - } - else if( !lib.localInput.signature.registers.empty() ) + libState->seen = true; + libState->materialIndex = materialIndex; + for( int32_t i = 0; i < numRaycasters; i++ ) { - shaderTableDescs[i]->AddHitGroup( foundLib->second.first.c_str(), material ); - it->materialIndex = materialIndex; - consumedMaterialIndex = true; + libState->hitGroupNames[i] = pipelineManagers[i]->AddHitGroup( lib ); + shaderTableDescs[i]->AddHitGroup( libState->hitGroupNames[i].c_str(), material ); } - else + geometry.materialIndex = materialIndex++; + } + else if( libState->addsRecords ) + { + for( int32_t i = 0; i < numRaycasters; i++ ) { - it->materialIndex = foundLib->second.second; + shaderTableDescs[i]->AddHitGroup( libState->hitGroupNames[i].c_str(), material ); } + geometry.materialIndex = materialIndex++; } - if( consumedMaterialIndex ) + else { - materialIndex++; + geometry.materialIndex = libState->materialIndex; } } + CCP_STATS_ADD( rtShaderTableShaders, shaders.size() ); + CCP_STATS_ADD( rtShaderTableLibraries, libraries.size() ); } namespace @@ -751,6 +886,21 @@ void Tr2RaytracingGeometry::TransformMeshes( Tr2RenderContext& renderContext ) ON_BLOCK_EXIT( [&] { renderContext.PopDisableUAVBarriersDx12(); } ); #endif + { + TRINITY_STATS_ZONE( "Prepare BoneTransformBuffer" ); + Tr2RingBuffer::GetInstance().PrepareBuffer( renderContext ); + } + { + TRINITY_STATS_ZONE( "Prepare MorphTargetAnimationDataBuffer" ); + Tr2RingBuffer::GetInstance().PrepareBuffer( renderContext ); + } + + if( g_rtTransformMeshesEarlyOut && !m_hasDeformedGeometry.load( std::memory_order_relaxed ) ) + { + CCP_STATS_INC( rtTransformMeshesSkipped ); + return; + } + for( auto& geom : m_geometryData ) { if( geom.materialIndex == INVALID_MATERIAL ) @@ -766,15 +916,6 @@ void Tr2RaytracingGeometry::TransformMeshes( Tr2RenderContext& renderContext ) std::vector outdatedMeshes; outdatedMeshes.reserve( m_geometryData.size() ); - { - TRINITY_STATS_ZONE( "Prepare BoneTransformBuffer" ); - Tr2RingBuffer::GetInstance().PrepareBuffer( renderContext ); - } - { - TRINITY_STATS_ZONE( "Prepare MorphTargetAnimationDataBuffer" ); - Tr2RingBuffer::GetInstance().PrepareBuffer( renderContext ); - } - uint32_t skinnedVertexCount = 0; for( auto it = begin( m_geometryData ); it != end( m_geometryData ); ++it ) @@ -1031,6 +1172,21 @@ void Tr2RaytracingGeometry::AddGeometry( Tr2RaytracingMesh& mesh, Tr2RaytracingM obj.isTransparent = false; obj.bakedMorphOffset = bakedMorphOffset; m_threadLocalGeometryData.local().push_back( obj ); + NoteDeformedGeometry( mesh, area ); +} + +void Tr2RaytracingGeometry::NoteDeformedGeometry( Tr2RaytracingMesh& mesh, Tr2RaytracingMeshArea& area ) +{ + auto lod = mesh.GetCurrentLodData(); + if( !lod ) + { + return; + } + auto& lodArea = lod->m_areas[area.GetAreaIndex()]; + if( lodArea.m_isSkinned || lodArea.m_isMorphed ) + { + m_hasDeformedGeometry.store( true, std::memory_order_relaxed ); + } } void Tr2RaytracingGeometry::AddGeometry( Tr2RaytracingMesh& mesh, Tr2RaytracingMeshArea& area, Tr2Material* material, const Tr2ConstantBufferAL* perObjectData, const Tr2ConstantBufferAL* vertexBufferData, const Float4x3* worldTransforms, size_t instanceCount, uint32_t bakedMorphOffset ) @@ -1052,6 +1208,7 @@ void Tr2RaytracingGeometry::AddGeometry( Tr2RaytracingMesh& mesh, Tr2RaytracingM obj.isTransparent = false; obj.bakedMorphOffset = bakedMorphOffset; m_threadLocalGeometryData.local().push_back( obj ); + NoteDeformedGeometry( mesh, area ); } bool Tr2RaytracingGeometry::HasGeometry() const diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.h b/trinity/Raytracing/Tr2RaytracingGeometry.h index 44aeefcbf..38df3d8d3 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.h +++ b/trinity/Raytracing/Tr2RaytracingGeometry.h @@ -161,11 +161,13 @@ BLUE_CLASS( Tr2RaytracingGeometry ) : void PrepareShaderTableDescription( Tr2RenderContext & renderContext, int32_t numRaycasters, Tr2RtShaderTableDescriptionAL** shaderTableDescs, Tr2RaytracingPipelineStateManager** pipelineManagers ); void TransformMeshes( Tr2RenderContext & renderContext ); void BuildAccelerationStructures( Tr2RenderContext & renderContext ); + void NoteDeformedGeometry( Tr2RaytracingMesh & mesh, Tr2RaytracingMeshArea & area ); VtxOffsets FindOffsets( unsigned declHandle ); std::vector m_geometryData; Tr2EnumerableThreadSpecific> m_threadLocalGeometryData; + std::atomic m_hasDeformedGeometry{ false }; Tr2RtTopLevelAccelerationStructureAL m_tlas; Tr2EffectPtr m_skinVerticesEffect; diff --git a/trinityal/dx12/Tr2RtShaderTableALDx12.cpp b/trinityal/dx12/Tr2RtShaderTableALDx12.cpp index 724636e7a..c6ea1c97b 100644 --- a/trinityal/dx12/Tr2RtShaderTableALDx12.cpp +++ b/trinityal/dx12/Tr2RtShaderTableALDx12.cpp @@ -87,25 +87,94 @@ ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, size_t entrySize = Align( D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES + signatureSize, D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT ); auto size = ( desc.m_rayGenNames.size() + desc.m_missNames.size() + desc.m_hitGroupNames.size() ) * entrySize; + const bool reuseBuffers = ::Tr2RtShaderTableAL::s_reuseBuffers; + if( m_owner && ( m_owner != &renderContext || !reuseBuffers ) ) + { + Destroy(); + } + CComPtr table; - auto heap = HeapDesc( D3D12_HEAP_TYPE_UPLOAD ); - auto scratchDesc = BufferDesc( Align( size, D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT ), D3D12_RESOURCE_FLAG_NONE ); - CR_RETURN_HR( renderContext.m_device->CreateCommittedResource( - &heap, - D3D12_HEAP_FLAG_NONE, - &scratchDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, - nullptr, - IID_PPV_ARGS( &table ) ) ); - - uint8_t* tableData; - CR_RETURN_HR( table->Map( 0, nullptr, (void**)&tableData ) ); - ON_BLOCK_EXIT( [&] { table->Unmap( 0, nullptr ); } ); + uint8_t* tableData = nullptr; + if( reuseBuffers ) + { + m_owner = &renderContext; + if( m_buffers.empty() ) + { + if( m_table ) + { + RELEASE_LATER( m_owner, m_table ); + m_table = nullptr; + } + m_buffers.resize( renderContext.GetBackBufferCount() ); + } + auto& buffer = m_buffers[renderContext.GetCurrentBackBufferIndex()]; + const size_t alignedSize = Align( size, D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT ); + if( buffer.capacity < alignedSize ) + { + if( buffer.resource ) + { + buffer.resource->Unmap( 0, nullptr ); + RELEASE_LATER( m_owner, buffer.resource ); + buffer.resource = nullptr; + buffer.mapped = nullptr; + buffer.capacity = 0; + } + const size_t capacity = Align( alignedSize + alignedSize / 4, 65536 ); + auto heap = HeapDesc( D3D12_HEAP_TYPE_UPLOAD ); + auto bufferDesc = BufferDesc( capacity, D3D12_RESOURCE_FLAG_NONE ); + CR_RETURN_HR( renderContext.m_device->CreateCommittedResource( + &heap, + D3D12_HEAP_FLAG_NONE, + &bufferDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS( &buffer.resource ) ) ); + CR_RETURN_HR( buffer.resource->Map( 0, nullptr, (void**)&buffer.mapped ) ); + buffer.capacity = capacity; + } + table = buffer.resource; + m_staging.resize( size ); + tableData = m_staging.data(); + } + else + { + auto heap = HeapDesc( D3D12_HEAP_TYPE_UPLOAD ); + auto scratchDesc = BufferDesc( Align( size, D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT ), D3D12_RESOURCE_FLAG_NONE ); + CR_RETURN_HR( renderContext.m_device->CreateCommittedResource( + &heap, + D3D12_HEAP_FLAG_NONE, + &scratchDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS( &table ) ) ); + CR_RETURN_HR( table->Map( 0, nullptr, (void**)&tableData ) ); + } + ON_BLOCK_EXIT( [&] { if( !reuseBuffers ) table->Unmap( 0, nullptr ); } ); + uint8_t* const tableStart = tableData; uint32_t samplerHeapIncrement = renderContext.m_device->GetDescriptorHandleIncrementSize( D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER ); + struct CachedShaderInfo + { + const wchar_t* name; + const TrinityALImpl::Tr2RtPipelineStateAL::ShaderInfo* info; + }; + std::vector shaderInfos; + auto FindShaderInfo = [&]( const wchar_t* name ) { + for( auto& cached : shaderInfos ) + { + if( cached.name == name ) + { + return cached.info; + } + } + auto info = pipeline.TrinityALImpl_GetObject()->GetShaderInfo( name ); + shaderInfos.push_back( { name, info } ); + return info; + }; + auto FillRecord = [&]( uint8_t* tableData, const wchar_t* name, const Tr2RtLocalMaterialDescriptionAL& material ) -> ALResult { - auto shaderInfo = pipeline.TrinityALImpl_GetObject()->GetShaderInfo( name ); + auto shaderInfo = reuseBuffers ? FindShaderInfo( name ) : pipeline.TrinityALImpl_GetObject()->GetShaderInfo( name ); if( !shaderInfo || !shaderInfo->shaderIdentifier ) { return E_INVALIDARG; @@ -157,21 +226,55 @@ ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, tableData += entrySize; } + if( reuseBuffers ) + { + memcpy( m_buffers[renderContext.GetCurrentBackBufferIndex()].mapped, tableStart, size ); + } + else + { + m_desc = desc; + } + m_rayGenNames.clear(); + for( auto& record : desc.m_rayGenNames ) + { + m_rayGenNames.push_back( record.name ); + } + m_missCount = desc.m_missNames.size(); + m_hitGroupCount = desc.m_hitGroupNames.size(); m_table = table; - m_desc = desc; m_entrySize = entrySize; m_owner = &renderContext; return S_OK; } +void Tr2RtShaderTableAL::ReleaseBuffers() +{ + for( auto& buffer : m_buffers ) + { + if( buffer.resource ) + { + buffer.resource->Unmap( 0, nullptr ); + RELEASE_LATER( m_owner, buffer.resource ); + } + } + m_buffers.clear(); +} + void Tr2RtShaderTableAL::Destroy() { if( m_owner ) { - RELEASE_LATER( m_owner, m_table ); + if( m_buffers.empty() ) + { + RELEASE_LATER( m_owner, m_table ); + } + ReleaseBuffers(); m_owner = nullptr; m_table = nullptr; m_desc = Tr2RtShaderTableDescriptionAL(); + m_rayGenNames.clear(); + m_missCount = 0; + m_hitGroupCount = 0; m_entrySize = 0; } } @@ -193,9 +296,9 @@ void Tr2RtShaderTableAL::Describe( Tr2DeviceResourceDescriptionAL& description ) D3D12_GPU_VIRTUAL_ADDRESS Tr2RtShaderTableAL::GetRayGenShader( const wchar_t* name ) const { - for( size_t i = 0; i < m_desc.m_rayGenNames.size(); ++i ) + for( size_t i = 0; i < m_rayGenNames.size(); ++i ) { - if( m_desc.m_rayGenNames[i].name == name ) + if( m_rayGenNames[i] == name ) { return m_table->GetGPUVirtualAddress() + i * GetEntrySize(); } @@ -205,12 +308,12 @@ D3D12_GPU_VIRTUAL_ADDRESS Tr2RtShaderTableAL::GetRayGenShader( const wchar_t* na D3D12_GPU_VIRTUAL_ADDRESS Tr2RtShaderTableAL::GetMissShaders() const { - return m_table->GetGPUVirtualAddress() + m_desc.m_rayGenNames.size() * GetEntrySize(); + return m_table->GetGPUVirtualAddress() + m_rayGenNames.size() * GetEntrySize(); } D3D12_GPU_VIRTUAL_ADDRESS Tr2RtShaderTableAL::GetHitGroupShaders() const { - return m_table->GetGPUVirtualAddress() + ( m_desc.m_rayGenNames.size() + m_desc.m_missNames.size() ) * GetEntrySize(); + return m_table->GetGPUVirtualAddress() + ( m_rayGenNames.size() + m_missCount ) * GetEntrySize(); } uint64_t Tr2RtShaderTableAL::GetEntrySize() const @@ -220,12 +323,12 @@ uint64_t Tr2RtShaderTableAL::GetEntrySize() const uint64_t Tr2RtShaderTableAL::GetMissShaderTableSize() const { - return GetEntrySize() * m_desc.m_missNames.size(); + return GetEntrySize() * m_missCount; } uint64_t Tr2RtShaderTableAL::GetHitGroupTableSize() const { - return GetEntrySize() * m_desc.m_hitGroupNames.size(); + return GetEntrySize() * m_hitGroupCount; } } diff --git a/trinityal/dx12/Tr2RtShaderTableALDx12.h b/trinityal/dx12/Tr2RtShaderTableALDx12.h index c70a965c6..b3f6e0032 100644 --- a/trinityal/dx12/Tr2RtShaderTableALDx12.h +++ b/trinityal/dx12/Tr2RtShaderTableALDx12.h @@ -31,8 +31,21 @@ class Tr2RtShaderTableAL : public Tr2DeviceResourceAL uint64_t GetHitGroupTableSize() const; private: + struct TableBuffer + { + CComPtr resource; + uint8_t* mapped = nullptr; + size_t capacity = 0; + }; + void ReleaseBuffers(); + Tr2RtShaderTableDescriptionAL m_desc; CComPtr m_table; + std::vector m_buffers; + std::vector m_staging; + std::vector m_rayGenNames; + size_t m_missCount = 0; + size_t m_hitGroupCount = 0; Tr2PrimaryRenderContextAL* m_owner; uint64_t m_entrySize; }; diff --git a/trinityal/include/Tr2RtShaderTableAL.h b/trinityal/include/Tr2RtShaderTableAL.h index d29e6f119..0facdde24 100644 --- a/trinityal/include/Tr2RtShaderTableAL.h +++ b/trinityal/include/Tr2RtShaderTableAL.h @@ -40,6 +40,7 @@ class Tr2RtShaderTableDescriptionAL void AddHitGroup( const wchar_t* name, const Tr2RtLocalMaterialDescriptionAL& material ); void Reserve( size_t hitGroupCount ); + void Clear(); private: struct ShaderRecord @@ -65,6 +66,8 @@ class Tr2RtShaderTableAL TrinityALImpl::Tr2RtShaderTableAL* TrinityALImpl_GetObject() const; + static bool s_reuseBuffers; + private: std::shared_ptr m_shaderTable; }; diff --git a/trinityal/src/Tr2RtShaderTableAL.cpp b/trinityal/src/Tr2RtShaderTableAL.cpp index f86516dac..2dc0cc2a8 100644 --- a/trinityal/src/Tr2RtShaderTableAL.cpp +++ b/trinityal/src/Tr2RtShaderTableAL.cpp @@ -4,6 +4,12 @@ #include "../include/Tr2RtShaderTableAL.h" #include "include/Tr2CapsAL.h" +#if TRINITY_PLATFORM == TRINITY_DIRECTX12 +bool Tr2RtShaderTableAL::s_reuseBuffers = true; +#else +bool Tr2RtShaderTableAL::s_reuseBuffers = false; +#endif + #if TRINITY_PLATFORM_SUPPORTS_RAY_TRACING #include TRINITY_AL_PLATFORM_INCLUDE( Tr2RtShaderTableAL ) @@ -26,7 +32,10 @@ Tr2RtShaderTableAL::Tr2RtShaderTableAL() : ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, const Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ) { - m_shaderTable = std::make_shared(); + if( !s_reuseBuffers || m_shaderTable == NullRtShaderTable() ) + { + m_shaderTable = std::make_shared(); + } auto hr = m_shaderTable->Create( desc, pipeline, renderContext ); if( FAILED( hr ) ) { @@ -114,3 +123,10 @@ void Tr2RtShaderTableDescriptionAL::Reserve( size_t hitGroupCount ) { m_hitGroupNames.reserve( hitGroupCount ); } + +void Tr2RtShaderTableDescriptionAL::Clear() +{ + m_rayGenNames.clear(); + m_missNames.clear(); + m_hitGroupNames.clear(); +} From ed9112037adff646f8677d395db31ee010f87cbe Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Fri, 23 Oct 2026 09:39:53 +0100 Subject: [PATCH 2/3] Replace RT shader table reuse toggles with an explicit Update method Removed the runtime settings. Add Tr2RtShaderTableAL::Update for in-place rebuilds (DX12 reuses per-back-buffer upload buffers, Metal forwards to Create) and use it from the light manager and raytracing manager. Add a test that updates one table across frames with changing constants and record count growth. --- trinity/Raytracing/Tr2RaytracingGeometry.cpp | 98 +--------- trinity/Raytracing/Tr2RaytracingManager.cpp | 2 +- trinity/Tr2LightManager.cpp | 2 +- trinityal/dx12/Tr2RtShaderTableALDx12.cpp | 16 +- trinityal/dx12/Tr2RtShaderTableALDx12.h | 2 + trinityal/include/Tr2RtShaderTableAL.h | 4 +- trinityal/metal/Tr2RtShaderTableALMetal.h | 3 + trinityal/metal/Tr2RtShaderTableALMetal.mm | 7 + trinityal/src/Tr2RtShaderTableAL.cpp | 26 ++- trinityal/tests/Raytracing.cpp | 186 +++++++++++++++++++ 10 files changed, 236 insertions(+), 110 deletions(-) diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.cpp b/trinity/Raytracing/Tr2RaytracingGeometry.cpp index be9684f5d..a1090cae5 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.cpp +++ b/trinity/Raytracing/Tr2RaytracingGeometry.cpp @@ -13,21 +13,11 @@ #include "Tr2MeshArea.h" #include "ITr2TextureProvider.h" -#include "TriSettingsRegistrar.h" #if TRINITY_PLATFORM == TRINITY_DIRECTX12 #include <../trinityal/dx12/Tr2BufferALDx12.h> #endif -bool g_rtShaderTableMaterialCache = true; -TRI_REGISTER_SETTING( "rtShaderTableMaterialCache", g_rtShaderTableMaterialCache ); -bool& g_rtShaderTableReuse = Tr2RtShaderTableAL::s_reuseBuffers; -TRI_REGISTER_SETTING( "rtShaderTableReuse", g_rtShaderTableReuse ); -bool g_rtTransformMeshesEarlyOut = true; -TRI_REGISTER_SETTING( "rtTransformMeshesEarlyOut", g_rtTransformMeshesEarlyOut ); -bool g_rtSkipUnusedMaterialApply = true; -TRI_REGISTER_SETTING( "rtSkipUnusedMaterialApply", g_rtSkipUnusedMaterialApply ); - CCP_STATS_DECLARE( rtShaderTableShaders, "Trinity/RT/shaderTableShaders", true, CST_COUNTER_HIGH, "Distinct shaders resolved for the shader table this frame." ); CCP_STATS_DECLARE( rtShaderTableLibraries, "Trinity/RT/shaderTableLibraries", true, CST_COUNTER_HIGH, "Distinct raytracing libraries seen by the shader table this frame." ); CCP_STATS_DECLARE( rtTransformMeshesSkipped, "Trinity/RT/transformMeshesSkipped", true, CST_COUNTER_HIGH, "TransformMeshes returned early because no skinned or morphed geometry was added this frame." ); @@ -673,92 +663,10 @@ void Tr2RaytracingGeometry::PrepareShaderTableDescription( Tr2RenderContext& ren TRINITY_STATS_ZONE( __FUNCTION__ ); for( int32_t i = 0; i < numRaycasters; i++ ) { - if( g_rtShaderTableReuse ) - { - shaderTableDescs[i]->Clear(); - } - else - { - *shaderTableDescs[i] = Tr2RtShaderTableDescriptionAL(); - } + shaderTableDescs[i]->Clear(); shaderTableDescs[i]->Reserve( m_geometryData.size() ); } - if( !g_rtShaderTableMaterialCache ) - { - std::vector>> seenLibraries; - seenLibraries.resize( numRaycasters ); - - uint32_t materialIndex = 0; - for( auto it = begin( m_geometryData ); it != end( m_geometryData ); ++it ) - { - Tr2RtLocalMaterialDescriptionAL material; - if( !it->material ) - { - continue; - } - auto shader = it->material->GetShaderStateInterface(); - if( !shader ) - { - continue; - } - uint32_t techniqueIndex; - if( !shader->GetTechniqueIndex( m_rtShadowTechniqueName, techniqueIndex ) ) - { - continue; - } - auto& desc = shader->GetEffectDescription(); - if( desc.techniques[techniqueIndex].libraries.empty() ) - { - continue; - } - auto& lib = desc.techniques[techniqueIndex].libraries[0]; - if( !lib.anyHitName.empty() ) - { - it->isTransparent = true; - } - - if( it->perObjectData ) - { - material.SetConstants( Tr2Renderer::GetPerObjectPSStartRegister(), *it->perObjectData ); - } - if( it->vertexBufferData ) - { - material.SetConstants( Tr2Renderer::GetPerObjectRTVertexBufferDataRegister(), *it->vertexBufferData ); - } - it->material->ApplyMaterialDataForRtMaterial( techniqueIndex, material, renderContext ); - - bool consumedMaterialIndex = false; - for( int32_t i = 0; i < numRaycasters; i++ ) - { - auto foundLib = seenLibraries[i].find( lib.libraryHandle ); - if( foundLib == end( seenLibraries[i] ) ) - { - auto hitGroupName = pipelineManagers[i]->AddHitGroup( lib ); - seenLibraries[i][lib.libraryHandle] = std::make_pair( hitGroupName, materialIndex ); - shaderTableDescs[i]->AddHitGroup( hitGroupName.c_str(), material ); - it->materialIndex = materialIndex; - consumedMaterialIndex = true; - } - else if( !lib.localInput.signature.registers.empty() ) - { - shaderTableDescs[i]->AddHitGroup( foundLib->second.first.c_str(), material ); - it->materialIndex = materialIndex; - consumedMaterialIndex = true; - } - else - { - it->materialIndex = foundLib->second.second; - } - } - if( consumedMaterialIndex ) - { - materialIndex++; - } - } - return; - } - std::unordered_map shaders; std::map libraries; @@ -807,7 +715,7 @@ void Tr2RaytracingGeometry::PrepareShaderTableDescription( Tr2RenderContext& ren { geometry.isTransparent = true; } - if( g_rtSkipUnusedMaterialApply && libState->seen && !libState->addsRecords ) + if( libState->seen && !libState->addsRecords ) { geometry.materialIndex = libState->materialIndex; continue; @@ -895,7 +803,7 @@ void Tr2RaytracingGeometry::TransformMeshes( Tr2RenderContext& renderContext ) Tr2RingBuffer::GetInstance().PrepareBuffer( renderContext ); } - if( g_rtTransformMeshesEarlyOut && !m_hasDeformedGeometry.load( std::memory_order_relaxed ) ) + if( !m_hasDeformedGeometry.load( std::memory_order_relaxed ) ) { CCP_STATS_INC( rtTransformMeshesSkipped ); return; diff --git a/trinity/Raytracing/Tr2RaytracingManager.cpp b/trinity/Raytracing/Tr2RaytracingManager.cpp index 824178f5a..ad4573d69 100644 --- a/trinity/Raytracing/Tr2RaytracingManager.cpp +++ b/trinity/Raytracing/Tr2RaytracingManager.cpp @@ -131,7 +131,7 @@ Tr2GpuResourcePool::Texture Tr2RaytracingManager::RenderShadows( TRINITY_STATS_ZONE( "Create shader table" ); m_shaderTableDesc.AddRayGenShader( rayGenName.c_str() ); m_shaderTableDesc.AddMissShader( missName.c_str() ); - m_shadowShaderTable.Create( m_shaderTableDesc, pipelineState, renderContext.GetPrimaryRenderContext() ); + m_shadowShaderTable.Update( m_shaderTableDesc, pipelineState, renderContext.GetPrimaryRenderContext() ); } if( !m_shadowPerFrameData.IsValid() ) diff --git a/trinity/Tr2LightManager.cpp b/trinity/Tr2LightManager.cpp index 34e38630c..6f9bfd238 100644 --- a/trinity/Tr2LightManager.cpp +++ b/trinity/Tr2LightManager.cpp @@ -928,7 +928,7 @@ Tr2GpuResourcePool::Texture Tr2LightManager::RenderRaytracedShadows( m_Raytracing.m_shaderTableDesc.AddRayGenShader( rayGenName.c_str(), material ); m_Raytracing.m_shaderTableDesc.AddMissShader( missName.c_str(), material ); - m_Raytracing.m_shaderTable.Create( m_Raytracing.m_shaderTableDesc, pipelineState, renderContext.GetPrimaryRenderContext() ); + m_Raytracing.m_shaderTable.Update( m_Raytracing.m_shaderTableDesc, pipelineState, renderContext.GetPrimaryRenderContext() ); } const uint32_t clearValue[] = { 0, 0, 0, 0 }; diff --git a/trinityal/dx12/Tr2RtShaderTableALDx12.cpp b/trinityal/dx12/Tr2RtShaderTableALDx12.cpp index c6ea1c97b..ae3fa0ef3 100644 --- a/trinityal/dx12/Tr2RtShaderTableALDx12.cpp +++ b/trinityal/dx12/Tr2RtShaderTableALDx12.cpp @@ -58,8 +58,19 @@ Tr2RtShaderTableAL::~Tr2RtShaderTableAL() Destroy(); } -// Shader tables contain shader records. Shader records contain a shader identifier and root arguments used to look up resources ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, const ::Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ) +{ + return Build( desc, pipeline, renderContext, false ); +} + +ALResult Tr2RtShaderTableAL::Update( const Tr2RtShaderTableDescriptionAL& desc, const ::Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ) +{ + return Build( desc, pipeline, renderContext, true ); +} + +// Shader tables contain shader records. Shader records contain a shader identifier and root arguments used to look up resources +// With reuseBuffers the records go into a persistent per-back-buffer upload buffer instead of a fresh resource +ALResult Tr2RtShaderTableAL::Build( const Tr2RtShaderTableDescriptionAL& desc, const ::Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext, bool reuseBuffers ) { if( !renderContext.IsValid() ) { @@ -87,7 +98,6 @@ ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, size_t entrySize = Align( D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES + signatureSize, D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT ); auto size = ( desc.m_rayGenNames.size() + desc.m_missNames.size() + desc.m_hitGroupNames.size() ) * entrySize; - const bool reuseBuffers = ::Tr2RtShaderTableAL::s_reuseBuffers; if( m_owner && ( m_owner != &renderContext || !reuseBuffers ) ) { Destroy(); @@ -174,7 +184,7 @@ ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, }; auto FillRecord = [&]( uint8_t* tableData, const wchar_t* name, const Tr2RtLocalMaterialDescriptionAL& material ) -> ALResult { - auto shaderInfo = reuseBuffers ? FindShaderInfo( name ) : pipeline.TrinityALImpl_GetObject()->GetShaderInfo( name ); + auto shaderInfo = FindShaderInfo( name ); if( !shaderInfo || !shaderInfo->shaderIdentifier ) { return E_INVALIDARG; diff --git a/trinityal/dx12/Tr2RtShaderTableALDx12.h b/trinityal/dx12/Tr2RtShaderTableALDx12.h index b3f6e0032..f174e48f8 100644 --- a/trinityal/dx12/Tr2RtShaderTableALDx12.h +++ b/trinityal/dx12/Tr2RtShaderTableALDx12.h @@ -17,6 +17,7 @@ class Tr2RtShaderTableAL : public Tr2DeviceResourceAL ~Tr2RtShaderTableAL(); ALResult Create( const Tr2RtShaderTableDescriptionAL& desc, const ::Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ); + ALResult Update( const Tr2RtShaderTableDescriptionAL& desc, const ::Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ); void Destroy(); bool IsValid() const; @@ -37,6 +38,7 @@ class Tr2RtShaderTableAL : public Tr2DeviceResourceAL uint8_t* mapped = nullptr; size_t capacity = 0; }; + ALResult Build( const Tr2RtShaderTableDescriptionAL& desc, const ::Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext, bool reuseBuffers ); void ReleaseBuffers(); Tr2RtShaderTableDescriptionAL m_desc; diff --git a/trinityal/include/Tr2RtShaderTableAL.h b/trinityal/include/Tr2RtShaderTableAL.h index 0facdde24..375071b25 100644 --- a/trinityal/include/Tr2RtShaderTableAL.h +++ b/trinityal/include/Tr2RtShaderTableAL.h @@ -62,12 +62,12 @@ class Tr2RtShaderTableAL Tr2RtShaderTableAL(); ALResult Create( const Tr2RtShaderTableDescriptionAL& desc, const Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ); + // Rebuilds the table in place, reusing the underlying buffers where the platform supports it + ALResult Update( const Tr2RtShaderTableDescriptionAL& desc, const Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ); bool IsValid() const; TrinityALImpl::Tr2RtShaderTableAL* TrinityALImpl_GetObject() const; - static bool s_reuseBuffers; - private: std::shared_ptr m_shaderTable; }; diff --git a/trinityal/metal/Tr2RtShaderTableALMetal.h b/trinityal/metal/Tr2RtShaderTableALMetal.h index 84b2b6f59..465e196a5 100644 --- a/trinityal/metal/Tr2RtShaderTableALMetal.h +++ b/trinityal/metal/Tr2RtShaderTableALMetal.h @@ -27,6 +27,9 @@ class Tr2RtShaderTableAL : public Tr2DeviceResourceAL ALResult Create( const Tr2RtShaderTableDescriptionAL& desc, const ::Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ); + ALResult Update( const Tr2RtShaderTableDescriptionAL& desc, + const ::Tr2RtPipelineStateAL& pipeline, + Tr2PrimaryRenderContextAL& renderContext ); void Destroy(); bool IsValid() const; diff --git a/trinityal/metal/Tr2RtShaderTableALMetal.mm b/trinityal/metal/Tr2RtShaderTableALMetal.mm index 27ce1f1a6..555613721 100644 --- a/trinityal/metal/Tr2RtShaderTableALMetal.mm +++ b/trinityal/metal/Tr2RtShaderTableALMetal.mm @@ -184,6 +184,13 @@ } } +ALResult Tr2RtShaderTableAL::Update( const Tr2RtShaderTableDescriptionAL& desc, + const ::Tr2RtPipelineStateAL& pipeline, + Tr2PrimaryRenderContextAL& renderContext ) +{ + return Create( desc, pipeline, renderContext ); +} + void Tr2RtShaderTableAL::Destroy() { if( @available( macOS 11.0, * ) ) diff --git a/trinityal/src/Tr2RtShaderTableAL.cpp b/trinityal/src/Tr2RtShaderTableAL.cpp index 2dc0cc2a8..95ece800f 100644 --- a/trinityal/src/Tr2RtShaderTableAL.cpp +++ b/trinityal/src/Tr2RtShaderTableAL.cpp @@ -4,12 +4,6 @@ #include "../include/Tr2RtShaderTableAL.h" #include "include/Tr2CapsAL.h" -#if TRINITY_PLATFORM == TRINITY_DIRECTX12 -bool Tr2RtShaderTableAL::s_reuseBuffers = true; -#else -bool Tr2RtShaderTableAL::s_reuseBuffers = false; -#endif - #if TRINITY_PLATFORM_SUPPORTS_RAY_TRACING #include TRINITY_AL_PLATFORM_INCLUDE( Tr2RtShaderTableAL ) @@ -32,11 +26,22 @@ Tr2RtShaderTableAL::Tr2RtShaderTableAL() : ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, const Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ) { - if( !s_reuseBuffers || m_shaderTable == NullRtShaderTable() ) + m_shaderTable = std::make_shared(); + auto hr = m_shaderTable->Create( desc, pipeline, renderContext ); + if( FAILED( hr ) ) + { + m_shaderTable = NullRtShaderTable(); + } + return hr; +} + +ALResult Tr2RtShaderTableAL::Update( const Tr2RtShaderTableDescriptionAL& desc, const Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ) +{ + if( m_shaderTable == NullRtShaderTable() ) { m_shaderTable = std::make_shared(); } - auto hr = m_shaderTable->Create( desc, pipeline, renderContext ); + auto hr = m_shaderTable->Update( desc, pipeline, renderContext ); if( FAILED( hr ) ) { m_shaderTable = NullRtShaderTable(); @@ -65,6 +70,11 @@ ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, return E_FAIL; } +ALResult Tr2RtShaderTableAL::Update( const Tr2RtShaderTableDescriptionAL& desc, const Tr2RtPipelineStateAL& pipeline, Tr2PrimaryRenderContextAL& renderContext ) +{ + return E_FAIL; +} + bool Tr2RtShaderTableAL::IsValid() const { return false; diff --git a/trinityal/tests/Raytracing.cpp b/trinityal/tests/Raytracing.cpp index 4c62399ca..25a254b76 100644 --- a/trinityal/tests/Raytracing.cpp +++ b/trinityal/tests/Raytracing.cpp @@ -885,6 +885,192 @@ TEST_F( Raytracing, CanUseLocalConstants ) RunLoop( frame ); } +TEST_F( Raytracing, CanUpdateShaderTable ) +{ + if( !renderContext->GetCaps().SupportsRaytracing() ) + { + GTEST_SKIP() << "Raytracing not supported on this device"; + } + + uint8_t rayGenCode[] = { +#include INCLUDE_SHADER_CODE( RayGen.rs ) + }; + + uint8_t missCode[] = { +#include INCLUDE_SHADER_CODE( Miss.rs ) + }; + + uint8_t closestHitCode[] = { +#include INCLUDE_SHADER_CODE( ClosestHitWithMaterial.rs ) + }; + + Tr2ShaderSignatureAL globalSignature; + globalSignature.Add( Tr2ShaderRegisterAL::CONSTANT_BUFFER, 0 ); //uniforms + globalSignature.Add( Tr2ShaderRegisterAL::UAV_TEXTURE2D, 0 ); //RTOutput + globalSignature.Add( Tr2ShaderRegisterAL::SRV_BUFFER, 1 ); //AccelStruct + + Tr2ShaderSignatureAL localSignature; + localSignature.Add( Tr2ShaderRegisterAL::CONSTANT_BUFFER, 1 ); + + const uint32_t PAYLOAD_SIZE = 4 * sizeof( float ); + + Tr2RtPipelineStateDescriptionAL stateDesc; + stateDesc.AddShader( L"RayGen_12", Tr2ShaderBytecodeAL( rayGenCode ), L"RayGen", PAYLOAD_SIZE ); + stateDesc.AddShader( L"Miss_5", Tr2ShaderBytecodeAL( missCode ), L"Miss", PAYLOAD_SIZE ); + stateDesc.AddShader( L"ClosestHit_76", Tr2ShaderBytecodeAL( closestHitCode ), L"ClosestHit", PAYLOAD_SIZE ); + stateDesc.AddHitGroup( L"HitGroup", nullptr, L"ClosestHit_76", nullptr, localSignature ); + stateDesc.AddGlobalSignature( globalSignature ); + + Tr2RtPipelineStateAL state; + ASSERT_HRESULT_SUCCEEDED( state.CreateRtPipelineState( stateDesc, renderContext->GetPrimaryRenderContext() ) ); + + const float magenta[] = { 1, 0, 1, 1 }; + const float yellow[] = { 1, 1, 0, 1 }; + + float* materialData; + + Tr2ConstantBufferAL cbMagenta; + cbMagenta.Create( 4 * 4, *renderContext ); + cbMagenta.Lock( (void**)&materialData, *renderContext ); + memcpy( materialData, magenta, sizeof( magenta ) ); + cbMagenta.Unlock( *renderContext ); + + Tr2ConstantBufferAL cbYellow; + cbYellow.Create( 4 * 4, *renderContext ); + cbYellow.Lock( (void**)&materialData, *renderContext ); + memcpy( materialData, yellow, sizeof( yellow ) ); + cbYellow.Unlock( *renderContext ); + + const uint32_t WIDTH = 512; + const uint32_t HEIGHT = 512; + + Tr2ConstantBufferAL cb; + cb.Create( 6 * 4 * sizeof( float ), *renderContext ); + + Tr2TextureAL result; + ASSERT_HRESULT_SUCCEEDED( result.Create( Tr2BitmapDimensions( WIDTH, HEIGHT, 1, Tr2RenderContextEnum::PIXEL_FORMAT_R32G32B32A32_FLOAT ), Tr2GpuUsage::UNORDERED_ACCESS | Tr2GpuUsage::SHADER_RESOURCE, Tr2CpuUsage::READ, *renderContext ) ); + + Tr2BufferAL vb, ib; + ASSERT_HRESULT_SUCCEEDED( vb.Create( sizeof( Vector3 ), 8, Tr2GpuUsage::VERTEX_BUFFER | Tr2GpuUsage::SHADER_RESOURCE, Tr2CpuUsage::NONE, cubeVertices, *renderContext ) ); + ASSERT_HRESULT_SUCCEEDED( ib.Create( Tr2RenderContextEnum::PIXEL_FORMAT_R16_UINT, sizeof( cubeIndices ) / sizeof( cubeIndices[0] ), Tr2GpuUsage::INDEX_BUFFER | Tr2GpuUsage::SHADER_RESOURCE, Tr2CpuUsage::NONE, cubeIndices, *renderContext ) ); + + Tr2RtBottomLevelAccelerationStructureAL blas; + ASSERT_HRESULT_SUCCEEDED( blas.Create( { Tr2RtPositionStreamAL( vb ), Tr2RtIndicesStreamAL( ib ) }, Tr2RtBlasGeometryFlags::OPAQUE_GEOMETRY, Tr2RtBuildFlags::PREFER_FAST_TRACE, *renderContext ) ); + + Tr2RtInstanceAL instances[2]; + instances[0].blas = blas.TrinityALImpl_GetObject(); + memset( instances[0].transform, 0, sizeof( instances[0].transform ) ); + instances[0].transform[0][0] = 0.5f; + instances[0].transform[1][1] = 0.5f; + instances[0].transform[2][2] = 0.5f; + instances[0].transform[0][3] = -0.6f; + instances[0].materialIndex = 0; + + instances[1].blas = blas.TrinityALImpl_GetObject(); + memset( instances[1].transform, 0, sizeof( instances[1].transform ) ); + instances[1].transform[0][0] = 0.5f; + instances[1].transform[1][1] = 0.5f; + instances[1].transform[2][2] = 0.5f; + instances[1].transform[0][3] = 0.6f; + instances[1].materialIndex = 1; + + Tr2RtTopLevelAccelerationStructureAL tlas; + ASSERT_HRESULT_SUCCEEDED( tlas.Create( 2, instances, Tr2RtBuildFlags::PREFER_FAST_TRACE, *renderContext ) ); + + // One table kept alive across more than a full back-buffer cycle: the materials swap every frame + // and the record count grows past the initial buffer capacity half way through + const uint32_t FRAMES = 8; + const uint32_t GROW_FROM_FRAME = 4; + const size_t EXTRA_HIT_GROUPS = 4096; + + Tr2RtShaderTableAL shaderTable; + Tr2RtShaderTableDescriptionAL shaderTableDesc; + + for( uint32_t frame = 0; frame < FRAMES; ++frame ) + { + const bool swapped = ( frame & 1 ) != 0; + auto& leftMaterial = swapped ? cbYellow : cbMagenta; + auto& rightMaterial = swapped ? cbMagenta : cbYellow; + + shaderTableDesc.Clear(); + shaderTableDesc.AddRayGenShader( L"RayGen_12" ); + shaderTableDesc.AddMissShader( L"Miss_5" ); + shaderTableDesc.AddHitGroup( L"HitGroup", Tr2RtLocalMaterialDescriptionAL().SetConstants( 1, leftMaterial ) ); + shaderTableDesc.AddHitGroup( L"HitGroup", Tr2RtLocalMaterialDescriptionAL().SetConstants( 1, rightMaterial ) ); + if( frame >= GROW_FROM_FRAME ) + { + for( size_t i = 0; i < EXTRA_HIT_GROUPS; ++i ) + { + shaderTableDesc.AddHitGroup( L"HitGroup", Tr2RtLocalMaterialDescriptionAL().SetConstants( 1, cbMagenta ) ); + } + } + + ASSERT_HRESULT_SUCCEEDED( renderContext->BeginScene() ); + ASSERT_HRESULT_SUCCEEDED( renderContext->Clear( Tr2RenderContextEnum::CLEARFLAGS_TARGET, 0xff000000, 1.0f ) ); + + ASSERT_HRESULT_SUCCEEDED( shaderTable.Update( shaderTableDesc, state, *renderContext ) ); + ASSERT_TRUE( shaderTable.IsValid() ); + + RayGenData* cbData; + ASSERT_HRESULT_SUCCEEDED( cb.Lock( (void**)&cbData, *renderContext ) ); + memset( cbData, 0, cb.GetSize() ); + cbData->viewMatrix[0][0] = 1; + cbData->viewMatrix[1][1] = 1; + cbData->viewMatrix[2][2] = 1; + cbData->viewMatrix[3][3] = 1; + cbData->viewOrigin[2] = -3; + cbData->tanHalfFOV = 1; + cbData->width = float( WIDTH ); + cbData->height = float( HEIGHT ); + cb.Unlock( *renderContext ); + + const float clearColor[] = { 0, 0, 0, 0 }; + ASSERT_HRESULT_SUCCEEDED( renderContext->ClearUav( result, 0, clearColor ) ); + + ASSERT_HRESULT_SUCCEEDED( renderContext->SetRtPipelineState( state, L"RayGen_12" ) ); + ASSERT_HRESULT_SUCCEEDED( renderContext->SetConstants( cb, Tr2RenderContextEnum::COMPUTE_SHADER, 0 ) ); + ASSERT_HRESULT_SUCCEEDED( renderContext->SetSrv( Tr2RenderContextEnum::COMPUTE_SHADER, 1, tlas.GetBuffer() ) ); + ASSERT_HRESULT_SUCCEEDED( renderContext->SetUav( Tr2RenderContextEnum::COMPUTE_SHADER, 0, result ) ); + + renderContext->UseAccelerationStructure( tlas ); + + ASSERT_HRESULT_SUCCEEDED( renderContext->DispatchRays( state, shaderTable, L"RayGen_12", WIDTH, HEIGHT, 1 ) ); + + ASSERT_HRESULT_SUCCEEDED( renderContext->EndScene() ); + ASSERT_HRESULT_SUCCEEDED( renderContext->Present() ); + } + + // Last frame was swapped: left cube yellow, right cube magenta. + // RayGen adds ViewMat[0].x (1) to green and the miss shader writes red, so on screen + // a yellow hit is (1,2,0), a magenta hit (1,1,1) and a miss (1,1,0) + const float yellowHit[] = { 1, 2, 0 }; + const float magentaHit[] = { 1, 1, 1 }; + const float miss[] = { 1, 1, 0 }; + + const void* data; + uint32_t pitch; + ASSERT_HRESULT_SUCCEEDED( result.MapForReading( Tr2TextureSubresource( 0 ), data, pitch, *renderContext ) ); + ON_BLOCK_EXIT( [&] { result.UnmapForReading( *renderContext ); } ); + + const float* row = reinterpret_cast( static_cast( data ) + ( HEIGHT / 2 ) * pitch ); + uint32_t leftHits = 0, rightHits = 0; + for( uint32_t x = 0; x < WIDTH; ++x ) + { + const float* pixel = row + x * 4; + if( pixel[0] == miss[0] && pixel[1] == miss[1] && pixel[2] == miss[2] ) + { + continue; + } + const float* expected = x < WIDTH / 2 ? yellowHit : magentaHit; + ASSERT_EQ( expected[0], pixel[0] ) << "at x=" << x; + ASSERT_EQ( expected[1], pixel[1] ) << "at x=" << x; + ASSERT_EQ( expected[2], pixel[2] ) << "at x=" << x; + ( x < WIDTH / 2 ? leftHits : rightHits )++; + } + EXPECT_GT( leftHits, 0u ); + EXPECT_GT( rightHits, 0u ); +} + #if TRINITY_PLATFORM == TRINITY_METAL struct PerObjectData From 5ca499f19e1baba9ce2bbef14deee89fee467f5a Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Fri, 23 Oct 2026 10:33:40 +0100 Subject: [PATCH 3/3] Use reused vectors instead of maps for RT shader table state --- trinity/Raytracing/Tr2RaytracingGeometry.cpp | 77 +++++++++----------- trinity/Raytracing/Tr2RaytracingGeometry.h | 22 ++++++ 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.cpp b/trinity/Raytracing/Tr2RaytracingGeometry.cpp index a1090cae5..b0488db51 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.cpp +++ b/trinity/Raytracing/Tr2RaytracingGeometry.cpp @@ -26,21 +26,6 @@ CCP_STATS_DECLARE( rtShaderTableMaterialApplies, "Trinity/RT/shaderTableMaterial namespace { std::mutex s_geometryConstantsMutex; - -struct RtLibraryState -{ - const Tr2EffectLibrary* lib = nullptr; - std::vector hitGroupNames; - uint32_t materialIndex = 0; - bool seen = false; - bool addsRecords = false; -}; - -struct RtShaderState -{ - uint32_t techniqueIndex = 0; - RtLibraryState* lib = nullptr; -}; } //*********************************************************** @@ -667,8 +652,10 @@ void Tr2RaytracingGeometry::PrepareShaderTableDescription( Tr2RenderContext& ren shaderTableDescs[i]->Reserve( m_geometryData.size() ); } - std::unordered_map shaders; - std::map libraries; + // A handful of shaders and libraries per frame, so linear search over reused vectors beats maps + m_rtShaderStates.clear(); + m_rtLibraryStates.clear(); + m_rtLibraryHitGroupNames.clear(); uint32_t materialIndex = 0; for( auto& geometry : m_geometryData ) @@ -683,41 +670,45 @@ void Tr2RaytracingGeometry::PrepareShaderTableDescription( Tr2RenderContext& ren continue; } - auto shaderState = shaders.find( shader ); - if( shaderState == end( shaders ) ) + auto shaderState = std::find_if( begin( m_rtShaderStates ), end( m_rtShaderStates ), [&]( const RtShaderState& state ) { return state.shader == shader; } ); + if( shaderState == end( m_rtShaderStates ) ) { - shaderState = shaders.emplace( shader, RtShaderState() ).first; + m_rtShaderStates.push_back( { shader } ); + shaderState = std::prev( end( m_rtShaderStates ) ); uint32_t techniqueIndex; if( shader->GetTechniqueIndex( m_rtShadowTechniqueName, techniqueIndex ) ) { auto& libs = shader->GetEffectDescription().techniques[techniqueIndex].libraries; if( !libs.empty() ) { - auto& libState = libraries[libs[0].libraryHandle]; - if( !libState.lib ) + const uint32_t handle = libs[0].libraryHandle; + auto libState = std::find_if( begin( m_rtLibraryStates ), end( m_rtLibraryStates ), [&]( const RtLibraryState& state ) { return state.handle == handle; } ); + if( libState == end( m_rtLibraryStates ) ) { - libState.lib = &libs[0]; - libState.hitGroupNames.resize( numRaycasters ); - libState.addsRecords = !libs[0].localInput.signature.registers.empty(); + m_rtLibraryStates.push_back( { handle, &libs[0], uint32_t( m_rtLibraryHitGroupNames.size() ) } ); + libState = std::prev( end( m_rtLibraryStates ) ); + libState->addsRecords = !libs[0].localInput.signature.registers.empty(); + m_rtLibraryHitGroupNames.resize( m_rtLibraryHitGroupNames.size() + numRaycasters ); } - shaderState->second.techniqueIndex = techniqueIndex; - shaderState->second.lib = &libState; + shaderState->techniqueIndex = techniqueIndex; + shaderState->libIndex = uint32_t( libState - begin( m_rtLibraryStates ) ); } } } - auto libState = shaderState->second.lib; - if( !libState ) + if( shaderState->libIndex == INVALID_MATERIAL ) { continue; } - auto& lib = *libState->lib; + auto& libState = m_rtLibraryStates[shaderState->libIndex]; + auto& lib = *libState.lib; + auto hitGroupNames = &m_rtLibraryHitGroupNames[libState.hitGroupNamesOffset]; if( !lib.anyHitName.empty() ) { geometry.isTransparent = true; } - if( libState->seen && !libState->addsRecords ) + if( libState.seen && !libState.addsRecords ) { - geometry.materialIndex = libState->materialIndex; + geometry.materialIndex = libState.materialIndex; continue; } @@ -731,34 +722,34 @@ void Tr2RaytracingGeometry::PrepareShaderTableDescription( Tr2RenderContext& ren { material.SetConstants( Tr2Renderer::GetPerObjectRTVertexBufferDataRegister(), *geometry.vertexBufferData ); } - geometry.material->ApplyMaterialDataForRtMaterial( shaderState->second.techniqueIndex, material, renderContext ); + geometry.material->ApplyMaterialDataForRtMaterial( shaderState->techniqueIndex, material, renderContext ); - if( !libState->seen ) + if( !libState.seen ) { - libState->seen = true; - libState->materialIndex = materialIndex; + libState.seen = true; + libState.materialIndex = materialIndex; for( int32_t i = 0; i < numRaycasters; i++ ) { - libState->hitGroupNames[i] = pipelineManagers[i]->AddHitGroup( lib ); - shaderTableDescs[i]->AddHitGroup( libState->hitGroupNames[i].c_str(), material ); + hitGroupNames[i] = pipelineManagers[i]->AddHitGroup( lib ); + shaderTableDescs[i]->AddHitGroup( hitGroupNames[i].c_str(), material ); } geometry.materialIndex = materialIndex++; } - else if( libState->addsRecords ) + else if( libState.addsRecords ) { for( int32_t i = 0; i < numRaycasters; i++ ) { - shaderTableDescs[i]->AddHitGroup( libState->hitGroupNames[i].c_str(), material ); + shaderTableDescs[i]->AddHitGroup( hitGroupNames[i].c_str(), material ); } geometry.materialIndex = materialIndex++; } else { - geometry.materialIndex = libState->materialIndex; + geometry.materialIndex = libState.materialIndex; } } - CCP_STATS_ADD( rtShaderTableShaders, shaders.size() ); - CCP_STATS_ADD( rtShaderTableLibraries, libraries.size() ); + CCP_STATS_ADD( rtShaderTableShaders, m_rtShaderStates.size() ); + CCP_STATS_ADD( rtShaderTableLibraries, m_rtLibraryStates.size() ); } namespace diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.h b/trinity/Raytracing/Tr2RaytracingGeometry.h index 38df3d8d3..1c08b98fb 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.h +++ b/trinity/Raytracing/Tr2RaytracingGeometry.h @@ -7,6 +7,8 @@ #include "../TbbStub.h" #include "./Tr2RingBuffer.h" +class Tr2Shader; + BLUE_DECLARE( Tr2MeshArea ); BLUE_DECLARE_VECTOR( Tr2MeshArea ); @@ -165,8 +167,28 @@ BLUE_CLASS( Tr2RaytracingGeometry ) : VtxOffsets FindOffsets( unsigned declHandle ); + struct RtLibraryState + { + uint32_t handle = 0; + const Tr2EffectLibrary* lib = nullptr; + uint32_t hitGroupNamesOffset = 0; + uint32_t materialIndex = 0; + bool seen = false; + bool addsRecords = false; + }; + + struct RtShaderState + { + const Tr2Shader* shader = nullptr; + uint32_t techniqueIndex = 0; + uint32_t libIndex = INVALID_MATERIAL; + }; + std::vector m_geometryData; Tr2EnumerableThreadSpecific> m_threadLocalGeometryData; + std::vector m_rtShaderStates; + std::vector m_rtLibraryStates; + std::vector m_rtLibraryHitGroupNames; std::atomic m_hasDeformedGeometry{ false }; Tr2RtTopLevelAccelerationStructureAL m_tlas;