diff --git a/trinity/Raytracing/Tr2RaytracingGeometry.cpp b/trinity/Raytracing/Tr2RaytracingGeometry.cpp index 85f8272b5..b0488db51 100644 --- a/trinity/Raytracing/Tr2RaytracingGeometry.cpp +++ b/trinity/Raytracing/Tr2RaytracingGeometry.cpp @@ -18,6 +18,11 @@ #include <../trinityal/dx12/Tr2BufferALDx12.h> #endif +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; @@ -613,6 +618,7 @@ void Tr2RaytracingGeometry::BeginSceneUpdate() { m_geometryData.clear(); m_usedResources.Clear(); + m_hasDeformedGeometry.store( false, std::memory_order_relaxed ); } @@ -642,80 +648,108 @@ void Tr2RaytracingGeometry::PrepareShaderTableDescription( Tr2RenderContext& ren TRINITY_STATS_ZONE( __FUNCTION__ ); for( int32_t i = 0; i < numRaycasters; i++ ) { - *shaderTableDescs[i] = Tr2RtShaderTableDescriptionAL(); + shaderTableDescs[i]->Clear(); shaderTableDescs[i]->Reserve( m_geometryData.size() ); } - std::vector>> seenLibraries; - seenLibraries.resize( numRaycasters ); + // 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 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 = std::find_if( begin( m_rtShaderStates ), end( m_rtShaderStates ), [&]( const RtShaderState& state ) { return state.shader == shader; } ); + if( shaderState == end( m_rtShaderStates ) ) { - continue; + 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() ) + { + 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 ) ) + { + 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->techniqueIndex = techniqueIndex; + shaderState->libIndex = uint32_t( libState - begin( m_rtLibraryStates ) ); + } + } } - auto& desc = shader->GetEffectDescription(); - if( desc.techniques[techniqueIndex].libraries.empty() ) + if( shaderState->libIndex == INVALID_MATERIAL ) { continue; } - auto& lib = desc.techniques[techniqueIndex].libraries[0]; + auto& libState = m_rtLibraryStates[shaderState->libIndex]; + auto& lib = *libState.lib; + auto hitGroupNames = &m_rtLibraryHitGroupNames[libState.hitGroupNamesOffset]; if( !lib.anyHitName.empty() ) { - it->isTransparent = true; + geometry.isTransparent = true; + } + if( 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->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] ) ) + libState.seen = true; + libState.materialIndex = materialIndex; + for( int32_t i = 0; i < numRaycasters; 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; + hitGroupNames[i] = pipelineManagers[i]->AddHitGroup( lib ); + shaderTableDescs[i]->AddHitGroup( hitGroupNames[i].c_str(), material ); } - else if( !lib.localInput.signature.registers.empty() ) - { - shaderTableDescs[i]->AddHitGroup( foundLib->second.first.c_str(), material ); - it->materialIndex = materialIndex; - consumedMaterialIndex = true; - } - else + geometry.materialIndex = materialIndex++; + } + else if( libState.addsRecords ) + { + for( int32_t i = 0; i < numRaycasters; i++ ) { - it->materialIndex = foundLib->second.second; + shaderTableDescs[i]->AddHitGroup( hitGroupNames[i].c_str(), material ); } + geometry.materialIndex = materialIndex++; } - if( consumedMaterialIndex ) + else { - materialIndex++; + geometry.materialIndex = libState.materialIndex; } } + CCP_STATS_ADD( rtShaderTableShaders, m_rtShaderStates.size() ); + CCP_STATS_ADD( rtShaderTableLibraries, m_rtLibraryStates.size() ); } namespace @@ -751,6 +785,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( !m_hasDeformedGeometry.load( std::memory_order_relaxed ) ) + { + CCP_STATS_INC( rtTransformMeshesSkipped ); + return; + } + for( auto& geom : m_geometryData ) { if( geom.materialIndex == INVALID_MATERIAL ) @@ -766,15 +815,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 +1071,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 +1107,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..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 ); @@ -161,11 +163,33 @@ 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 ); + 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; Tr2EffectPtr m_skinVerticesEffect; 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 724636e7a..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,25 +98,93 @@ 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; + 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 = FindShaderInfo( name ); if( !shaderInfo || !shaderInfo->shaderIdentifier ) { return E_INVALIDARG; @@ -157,21 +236,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 +306,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 +318,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 +333,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..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; @@ -31,8 +32,22 @@ class Tr2RtShaderTableAL : public Tr2DeviceResourceAL uint64_t GetHitGroupTableSize() const; private: + struct TableBuffer + { + CComPtr resource; + 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; 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..375071b25 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 @@ -61,6 +62,8 @@ 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; 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 f86516dac..95ece800f 100644 --- a/trinityal/src/Tr2RtShaderTableAL.cpp +++ b/trinityal/src/Tr2RtShaderTableAL.cpp @@ -35,6 +35,20 @@ ALResult Tr2RtShaderTableAL::Create( const Tr2RtShaderTableDescriptionAL& desc, 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->Update( desc, pipeline, renderContext ); + if( FAILED( hr ) ) + { + m_shaderTable = NullRtShaderTable(); + } + return hr; +} + bool Tr2RtShaderTableAL::IsValid() const { return m_shaderTable->IsValid(); @@ -56,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; @@ -114,3 +133,10 @@ void Tr2RtShaderTableDescriptionAL::Reserve( size_t hitGroupCount ) { m_hitGroupNames.reserve( hitGroupCount ); } + +void Tr2RtShaderTableDescriptionAL::Clear() +{ + m_rayGenNames.clear(); + m_missNames.clear(); + m_hitGroupNames.clear(); +} 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