From 7d77e9212a28fa512583f238c97a2727559b1c1c Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Fri, 16 Oct 2026 12:24:31 +0100 Subject: [PATCH 1/4] Gather per-object data and outdated RT animations in parallel with chunked loops Walk shadow casters in parallel when marking outdated RT animations. Gather per-object data in parallel with a thread-safe pool allocator. Chunk the per-object parallel loops in EveSpaceScene. --- trinity/Eve/EveSpaceScene.cpp | 119 +++++++++--- .../Eve/SpaceObject/Children/EveChildMesh.h | 4 + trinity/Eve/SpaceObject/EveSpaceObject2.h | 4 + trinity/ITr2Renderable.h | 6 + trinity/TriPoolAllocator.cpp | 183 +++++++++++------- trinity/TriPoolAllocator.h | 31 ++- 6 files changed, 243 insertions(+), 104 deletions(-) diff --git a/trinity/Eve/EveSpaceScene.cpp b/trinity/Eve/EveSpaceScene.cpp index dad66eb2d..0ae76cda2 100644 --- a/trinity/Eve/EveSpaceScene.cpp +++ b/trinity/Eve/EveSpaceScene.cpp @@ -572,12 +572,12 @@ void EveSpaceScene::Update( Be::Time realTime, Be::Time simTime ) Tr2ParallelTaskGroup taskGroup = {}; m_updateContext.SetTaskGroup( &taskGroup ); - for( auto& object : m_objects ) - { - taskGroup.run( [object, this] { - object->UpdateAsyncronous( m_updateContext ); - } ); - } + Tr2ParallelFor( Tr2BlockedRange( 0, m_objects.size(), 32 ), [&]( Tr2BlockedRange range ) { + for( size_t i = range.begin(); i != range.end(); ++i ) + { + m_objects[i]->UpdateAsyncronous( m_updateContext ); + } + } ); for( auto& object : m_uiObjects ) { taskGroup.run( [object, this] { @@ -883,6 +883,18 @@ void GetBatchesFromRenderables( ITr2Renderable** const objectRenderables, const } } +bool g_gatherPerObjectDataParallel = true; +TRI_REGISTER_SETTING( "gatherPerObjectDataParallel", g_gatherPerObjectDataParallel ); + +namespace +{ +enum PerObjectFlags : uint8_t +{ + PER_OBJECT_DEFERRED = 1, + PER_OBJECT_TRANSPARENT = 2, +}; +} + void GetBatchesFromRenderables( ITr2Renderable** const objectRenderables, const unsigned renderableCount, @@ -895,27 +907,57 @@ void GetBatchesFromRenderables( { TRINITY_STATS_ZONE( __FUNCTION__ ); - std::vector perObjectData; - perObjectData.reserve( renderableCount ); + std::vector perObjectData( renderableCount, nullptr ); + std::vector flags( renderableCount, 0 ); + ITriRenderBatchAccumulator* const opaqueBatches = batches[TRIBATCHTYPE_OPAQUE]; + const bool wantTransparencies = objectsWithTransparencies != nullptr; { TRINITY_STATS_ZONE( "PerObjectData" ); + if( g_gatherPerObjectDataParallel ) + { + Tr2ParallelFor( Tr2BlockedRange( 0, renderableCount, 64 ), [&]( Tr2BlockedRange range ) { + for( unsigned i = range.begin(); i != range.end(); ++i ) + { + ITr2Renderable* r = objectRenderables[i]; + if( !r->SupportsParallelPerObjectData() ) + { + flags[i] = PER_OBJECT_DEFERRED; + continue; + } + perObjectData[i] = r->GetPerObjectData( opaqueBatches ); + if( wantTransparencies && r->HasTransparentBatches() ) + { + flags[i] = PER_OBJECT_TRANSPARENT; + } + } + } ); + } for( unsigned i = 0; i != renderableCount; ++i ) { + if( g_gatherPerObjectDataParallel && flags[i] != PER_OBJECT_DEFERRED ) + { + continue; + } ITr2Renderable* r = objectRenderables[i]; - - perObjectData.push_back( r->GetPerObjectData( batches[TRIBATCHTYPE_OPAQUE] ) ); + perObjectData[i] = r->GetPerObjectData( opaqueBatches ); + flags[i] = wantTransparencies && r->HasTransparentBatches() ? PER_OBJECT_TRANSPARENT : 0; } } { TRINITY_STATS_ZONE( "GetBatches" ); + Tr2EnumerableThreadSpecific* threadBatches[TRIBATCHTYPE_COUNT_OF_BATCH_TYPES]; + for( unsigned type = 0; type != batchTypeCount; ++type ) + { + threadBatches[type] = &perThreadBatches[batchTypes[type]]; + } Tr2ParallelFor( unsigned( 0 ), renderableCount, [&]( unsigned i ) { ITr2Renderable* r = objectRenderables[i]; for( unsigned type = 0; type != batchTypeCount; ++type ) { - r->GetBatches( &perThreadBatches[batchTypes[type]].local(), batchTypes[type], perObjectData[i], reason ); + r->GetBatches( &threadBatches[type]->local(), batchTypes[type], perObjectData[i], reason ); } } ); } @@ -935,13 +977,11 @@ void GetBatchesFromRenderables( { TRINITY_STATS_ZONE( "Transparencies" ); - objectsWithTransparencies->reserve( renderableCount ); - for( unsigned i = 0; i != renderableCount; ++i ) { - ITr2Renderable* r = objectRenderables[i]; - if( r->HasTransparentBatches() ) + if( flags[i] == PER_OBJECT_TRANSPARENT ) { + ITr2Renderable* r = objectRenderables[i]; ITr2RenderableEntry entry; entry.m_object = r; entry.m_distance = r->GetSortValue(); @@ -1440,8 +1480,11 @@ void EveSpaceScene::GatherBatches( bool includeDistortions, Tr2RenderContext& re { TRINITY_STATS_ZONE( "UpdateVisibility" ); - Tr2ParallelDo( m_objects.begin(), m_objects.end(), [&]( IEveSpaceObject2* obj ) { - obj->UpdateVisibility( m_updateContext, identity ); + Tr2ParallelFor( Tr2BlockedRange( 0, m_objects.size(), 32 ), [&]( Tr2BlockedRange range ) { + for( size_t i = range.begin(); i != range.end(); ++i ) + { + m_objects[i]->UpdateVisibility( m_updateContext, identity ); + } } ); m_cameraAttachmentParent->SetTransform( Tr2Renderer::GetInverseViewTransform() ); @@ -1540,9 +1583,15 @@ void EveSpaceScene::PrepareRaytracedShadows( Tr2RenderContext& renderContext ) ProcessOutdatedRTAnimations( renderContext ); auto& shadowCasters = m_componentRegistry->GetComponents(); - Tr2ParallelDo( begin( shadowCasters ), end( shadowCasters ), [&]( auto caster ) { - caster->PushRtGeometry( *m_rtManager ); - } ); + { + TRINITY_STATS_ZONE( "PushRtGeometry" ); + Tr2ParallelFor( Tr2BlockedRange( 0, shadowCasters.size(), 32 ), [&]( Tr2BlockedRange range ) { + for( size_t i = range.begin(); i != range.end(); ++i ) + { + shadowCasters[i]->PushRtGeometry( *m_rtManager ); + } + } ); + } Tr2RtShaderTableDescriptionAL* shaderTableDescs[3]; Tr2RaytracingPipelineStateManager* pipelineManagers[3]; @@ -4181,8 +4230,12 @@ void EveSpaceScene::GetLightMatrices( const Tr2LightManager::PerLightData& light view = LookAtMatrix( lightData.position, lightData.position - lightData.direction, up ); } +bool g_rtOutdatedAnimationsParallel = true; +TRI_REGISTER_SETTING( "rtOutdatedAnimationsParallel", g_rtOutdatedAnimationsParallel ); + void EveSpaceScene::ProcessOutdatedRTAnimations( Tr2RenderContext& renderContext ) { + TRINITY_STATS_ZONE( __FUNCTION__ ); TriFrustumOrtho sunShadowFrustum; auto sunDir = m_sunData.DirWorld; @@ -4245,7 +4298,11 @@ void EveSpaceScene::ProcessOutdatedRTAnimations( Tr2RenderContext& renderContext } // Find all casters and mark those that are casting shadows as dirty so RT can rebuild it - m_componentRegistry->ProcessComponents( [&]( IEveShadowCaster* caster ) -> void { + const TriFrustum& cameraFrustum = m_updateContext.GetFrustum(); + const TriShadowOrthoFrustum sunOrthoFrustum( sunShadowFrustum, SHADOW_MAP_SIZE, sunDir ); + std::vector spotShadowFrustums( begin( spotLightFrustums ), end( spotLightFrustums ) ); + + auto processCaster = [&]( IEveShadowCaster* caster ) -> void { if( caster->IsShadowCastingDirty() ) { return; @@ -4253,7 +4310,7 @@ void EveSpaceScene::ProcessOutdatedRTAnimations( Tr2RenderContext& renderContext { float radius; - if( caster->IsCastingShadow( m_updateContext.GetFrustum(), TriShadowOrthoFrustum( sunShadowFrustum, SHADOW_MAP_SIZE, sunDir ), TR2RENDERREASON_NORMAL, radius ) ) + if( caster->IsCastingShadow( cameraFrustum, sunOrthoFrustum, TR2RENDERREASON_NORMAL, radius ) ) { caster->MarkRtDirty(); return; @@ -4263,17 +4320,17 @@ void EveSpaceScene::ProcessOutdatedRTAnimations( Tr2RenderContext& renderContext for( auto lightData : pointsLightDatas ) { - if( caster->IsCastingShadow( m_updateContext.GetFrustum(), lightData->position, lightData->radius, TR2RENDERREASON_NORMAL ) ) + if( caster->IsCastingShadow( cameraFrustum, lightData->position, lightData->radius, TR2RENDERREASON_NORMAL ) ) { caster->MarkRtDirty(); return; } } - for( const auto& shadowFrustum : spotLightFrustums ) + for( const auto& shadowFrustum : spotShadowFrustums ) { float sizeInShadow = 0.0f; - caster->IsCastingShadow( m_updateContext.GetFrustum(), TriShadowFrustum( shadowFrustum ), TR2RENDERREASON_NORMAL, sizeInShadow ); + caster->IsCastingShadow( cameraFrustum, shadowFrustum, TR2RENDERREASON_NORMAL, sizeInShadow ); // special threshold check if( sizeInShadow > 5.0f ) { @@ -4281,7 +4338,17 @@ void EveSpaceScene::ProcessOutdatedRTAnimations( Tr2RenderContext& renderContext return; } } - } ); + }; + + if( g_rtOutdatedAnimationsParallel ) + { + auto& casters = m_componentRegistry->GetComponents(); + Tr2ParallelDo( begin( casters ), end( casters ), processCaster ); + } + else + { + m_componentRegistry->ProcessComponents( processCaster ); + } } void RegisterWithVariableStore( const EveSpaceScene::ShadowResources& shadowResources, Tr2GpuResourcePool& gpuResourcePool ) diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.h b/trinity/Eve/SpaceObject/Children/EveChildMesh.h index 130d9f8c7..c7ab67c09 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.h +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.h @@ -113,6 +113,10 @@ BLUE_CLASS( EveChildMesh ) : void GetBatchesFromOverlayVector( ITriRenderBatchAccumulator * batches, const Tr2PerObjectData* perObjectData, TriBatchType batchType ); virtual float GetSortValue(); virtual Tr2PerObjectData* GetPerObjectData( ITriRenderBatchAccumulator * accumulator ); + bool SupportsParallelPerObjectData() const override + { + return true; + } virtual bool IsVisible( const EveUpdateContext& updateContext ) const override; ///////////////////////////////////////////////////////////////////////////////////// diff --git a/trinity/Eve/SpaceObject/EveSpaceObject2.h b/trinity/Eve/SpaceObject/EveSpaceObject2.h index a1ce39d09..b603c40fc 100644 --- a/trinity/Eve/SpaceObject/EveSpaceObject2.h +++ b/trinity/Eve/SpaceObject/EveSpaceObject2.h @@ -334,6 +334,10 @@ BLUE_CLASS( EveSpaceObject2 ) : virtual void GetBatches( ITriRenderBatchAccumulator * batches, TriBatchType batchType, const Tr2PerObjectData* perObjectData, Tr2RenderReason reason = TR2RENDERREASON_NORMAL ); virtual float GetSortValue(); virtual Tr2PerObjectData* GetPerObjectData( ITriRenderBatchAccumulator * accumulator ); + bool SupportsParallelPerObjectData() const override + { + return true; + } virtual bool IsVisible( const EveUpdateContext& updateContext ) const override; ///////////////////////////////////////////////////////////////////////////////////// diff --git a/trinity/ITr2Renderable.h b/trinity/ITr2Renderable.h index 3c08d247f..ff236fc4e 100644 --- a/trinity/ITr2Renderable.h +++ b/trinity/ITr2Renderable.h @@ -54,6 +54,12 @@ BLUE_INTERFACE( ITr2Renderable ) : virtual float GetSortValue() = 0; virtual Tr2PerObjectData* GetPerObjectData( ITriRenderBatchAccumulator * accumulator ) = 0; + + // True when GetPerObjectData and HasTransparentBatches only touch this object's own state and may run on worker threads. + virtual bool SupportsParallelPerObjectData() const + { + return false; + } }; REGISTER_COMPONENT_TYPE( "ReflectionRenderable", ITr2Renderable ); diff --git a/trinity/TriPoolAllocator.cpp b/trinity/TriPoolAllocator.cpp index d9809e71e..e5d103248 100644 --- a/trinity/TriPoolAllocator.cpp +++ b/trinity/TriPoolAllocator.cpp @@ -3,123 +3,168 @@ #include "StdAfx.h" #include "TriPoolAllocator.h" +namespace +{ +size_t RoundChunkSize( size_t size ) +{ + size >>= 8; + size += 1; + size <<= 8; + return size; +} +} + TriPoolAllocator::TriPoolAllocator() : - m_totalBytesAllocated( 0 ), m_totalSystemMemoryAllocated( 0 ), - m_pool( NULL ), - m_poolEnd( NULL ), - m_poolCurrent( NULL ), - m_chunkSize( 256 * 1024 ) + m_chunkSize( 256 * 1024 ), + m_chunk( nullptr ) { } +TriPoolAllocator::TriPoolAllocator( TriPoolAllocator&& other ) noexcept : + m_totalSystemMemoryAllocated( other.m_totalSystemMemoryAllocated ), + m_chunkSize( other.m_chunkSize ), + m_chunk( other.m_chunk.load() ), + m_chunks( std::move( other.m_chunks ) ) +{ + other.m_chunk = nullptr; + other.m_chunks.clear(); + other.m_totalSystemMemoryAllocated = 0; +} + +TriPoolAllocator& TriPoolAllocator::operator=( TriPoolAllocator&& other ) noexcept +{ + if( this != &other ) + { + for( Chunk* chunk : m_chunks ) + { + FreeChunk( chunk ); + } + m_totalSystemMemoryAllocated = other.m_totalSystemMemoryAllocated; + m_chunkSize = other.m_chunkSize; + m_chunk = other.m_chunk.load(); + m_chunks = std::move( other.m_chunks ); + other.m_chunk = nullptr; + other.m_chunks.clear(); + other.m_totalSystemMemoryAllocated = 0; + } + return *this; +} + TriPoolAllocator::~TriPoolAllocator() { - for( ChunkList_t::const_iterator it = m_previousPools.begin(); it != m_previousPools.end(); ++it ) + for( Chunk* chunk : m_chunks ) { - CCP_FREE( *it ); + FreeChunk( chunk ); } - CCP_FREE( m_pool ); - m_pool = m_poolCurrent = m_poolEnd = NULL; + m_chunks.clear(); + m_chunk = nullptr; } void* TriPoolAllocator::Allocate( size_t size ) { - // Align size to 16 bytes - pool starts out 16byte aligned - this ensures + // Align size to 16 bytes - chunks start out 16byte aligned - this ensures // that all allocations are always 16 byte aligned. size = CCP_ALIGN( size, 16 ); - if( m_poolCurrent + size > m_poolEnd ) + for( ;; ) { - GetMoreSystemMemory( size ); - if( m_poolCurrent + size > m_poolEnd ) + Chunk* chunk = m_chunk.load( std::memory_order_acquire ); + if( chunk ) + { + uint8_t* p = chunk->current.fetch_add( size, std::memory_order_relaxed ); + if( p + size <= chunk->end ) + { + return p; + } + } + + std::lock_guard lock( m_mutex ); + if( m_chunk.load( std::memory_order_relaxed ) == chunk && !AddChunk( size ) ) { return NULL; } } - - void* ret = m_poolCurrent; - - m_poolCurrent += size; - m_totalBytesAllocated += size; - - return ret; } void TriPoolAllocator::Clear() { - if( !m_previousPools.empty() ) + size_t totalBytesAllocated = 0; + for( Chunk* chunk : m_chunks ) { - for( ChunkList_t::const_iterator it = m_previousPools.begin(); it != m_previousPools.end(); ++it ) + totalBytesAllocated += UsedBytes( *chunk ); + } + + Chunk* last = m_chunks.empty() ? nullptr : m_chunks.back(); + for( Chunk* chunk : m_chunks ) + { + if( chunk != last ) { - // doing the explicit iterator cast allows you to debug this more easily - uint8_t* previousPool = *it; - CCP_FREE( previousPool ); + FreeChunk( chunk ); } - m_previousPools.clear(); } + m_chunks.clear(); + m_chunk = nullptr; + m_totalSystemMemoryAllocated = 0; - size_t curChunkSize = m_poolEnd - m_pool; - if( m_totalBytesAllocated < curChunkSize / 2 ) + if( !last ) + { + return; + } + if( totalBytesAllocated < last->size / 2 ) { // Pool is too large - free it and shrink the chunk size - CCP_FREE( m_pool ); - m_pool = m_poolCurrent = m_poolEnd = NULL; - m_chunkSize = curChunkSize / 2; - m_chunkSize >>= 8; - m_chunkSize += 1; - m_chunkSize <<= 8; - m_totalSystemMemoryAllocated = 0; + m_chunkSize = RoundChunkSize( last->size / 2 ); + FreeChunk( last ); } - else if( m_totalBytesAllocated > m_chunkSize ) + else if( totalBytesAllocated > m_chunkSize ) { // Pool is too small - free it and grow the chunk size - CCP_FREE( m_pool ); - m_pool = m_poolCurrent = m_poolEnd = NULL; - m_chunkSize = m_totalBytesAllocated; - m_chunkSize >>= 8; - m_chunkSize += 1; - m_chunkSize <<= 8; - m_totalSystemMemoryAllocated = 0; + m_chunkSize = RoundChunkSize( totalBytesAllocated ); + FreeChunk( last ); } else { // Pool seems to be of the right size. - // Align pool to 16 bytes - size of allocations are also aligned to - // 16 bytes - this ensures that all allocations are always 16 byte aligned. - m_poolCurrent = (uint8_t*)( CCP_ALIGN( (uintptr_t)m_pool, 16 ) ); - m_totalSystemMemoryAllocated = curChunkSize; + last->current = (uint8_t*)( CCP_ALIGN( (uintptr_t)last->memory, 16 ) ); + m_chunks.push_back( last ); + m_chunk = last; + m_totalSystemMemoryAllocated = last->size; } - - m_totalBytesAllocated = 0; } -void TriPoolAllocator::GetMoreSystemMemory( size_t size ) +TriPoolAllocator::Chunk* TriPoolAllocator::AddChunk( size_t size ) { - if( m_pool ) - { - m_previousPools.push_back( m_pool ); - } - size_t sizeToRequest = m_chunkSize; - while( sizeToRequest < size ) + while( sizeToRequest < size + 16 ) { sizeToRequest += m_chunkSize; } - // Align pool to 16 bytes - size of allocations are also aligned to - // 16 bytes - this ensures that all allocations are always 16 byte aligned. - m_pool = (uint8_t*)CCP_MALLOC( "TriPoolAllocator/chunk", sizeToRequest ); - - if( m_pool ) + uint8_t* memory = (uint8_t*)CCP_MALLOC( "TriPoolAllocator/chunk", sizeToRequest ); + if( !memory ) { - m_poolEnd = m_pool + sizeToRequest; - m_poolCurrent = (uint8_t*)( CCP_ALIGN( (uintptr_t)m_pool, 16 ) ); - - m_totalSystemMemoryAllocated += sizeToRequest; - } - else - { - m_poolCurrent = m_poolEnd = NULL; + return nullptr; } + Chunk* chunk = new Chunk; + chunk->memory = memory; + chunk->end = memory + sizeToRequest; + chunk->size = sizeToRequest; + chunk->current = (uint8_t*)( CCP_ALIGN( (uintptr_t)memory, 16 ) ); + m_chunks.push_back( chunk ); + m_totalSystemMemoryAllocated += sizeToRequest; + m_chunk.store( chunk, std::memory_order_release ); + return chunk; +} + +size_t TriPoolAllocator::UsedBytes( const Chunk& chunk ) +{ + uint8_t* current = chunk.current.load( std::memory_order_relaxed ); + return size_t( std::min( current, chunk.end ) - chunk.memory ); +} + +void TriPoolAllocator::FreeChunk( Chunk* chunk ) +{ + CCP_FREE( chunk->memory ); + delete chunk; } diff --git a/trinity/TriPoolAllocator.h b/trinity/TriPoolAllocator.h index 42b81069f..1489bb9b3 100644 --- a/trinity/TriPoolAllocator.h +++ b/trinity/TriPoolAllocator.h @@ -4,12 +4,19 @@ #ifndef TRIPOOLALLOCATOR_H #define TRIPOOLALLOCATOR_H +#include +#include +#include + // See http://core/wiki/TriPoolAllocator +// Allocate is safe to call from several threads at once; Clear is not. class TriPoolAllocator { public: TriPoolAllocator(); + TriPoolAllocator( TriPoolAllocator&& other ) noexcept; + TriPoolAllocator& operator=( TriPoolAllocator&& other ) noexcept; ~TriPoolAllocator(); // Allocates 'size' bytes, aligned to 16 bytes @@ -33,21 +40,27 @@ class TriPoolAllocator void Clear(); private: - void GetMoreSystemMemory( size_t size ); + struct Chunk + { + uint8_t* memory; + uint8_t* end; + size_t size; + std::atomic current; + }; + + Chunk* AddChunk( size_t size ); + static size_t UsedBytes( const Chunk& chunk ); + static void FreeChunk( Chunk* chunk ); private: - size_t m_totalBytesAllocated; size_t m_totalSystemMemoryAllocated; size_t m_chunkSize; - uint8_t* m_pool; - uint8_t* m_poolEnd; - uint8_t* m_poolCurrent; - - typedef std::list ChunkList_t; - ChunkList_t m_previousPools; + std::atomic m_chunk; + std::vector m_chunks; + std::mutex m_mutex; }; -#endif \ No newline at end of file +#endif From 731d713ed44352ffe8207e965964e78f757d72dd Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Tue, 27 Oct 2026 16:27:00 +0000 Subject: [PATCH 2/4] Resolving Comments - Ring buffer GPU resources are now recreated on the main thread, not from workers. - Pool allocator no longer advances its cursor past the end of a chunk. - Parallel loop grain scales with object count so small scenes still spread across threads. - Removed the gatherPerObjectDataParallel and rtOutdatedAnimationsParallel toggles. --- trinity/Eve/EveSpaceScene.cpp | 78 +++++++++++++++++------------------ trinity/Tr2RingBuffer.cpp | 14 ++++++- trinity/Tr2RingBuffer.h | 4 ++ trinity/TriPoolAllocator.cpp | 12 +++--- 4 files changed, 62 insertions(+), 46 deletions(-) diff --git a/trinity/Eve/EveSpaceScene.cpp b/trinity/Eve/EveSpaceScene.cpp index 0ae76cda2..a5f7e8788 100644 --- a/trinity/Eve/EveSpaceScene.cpp +++ b/trinity/Eve/EveSpaceScene.cpp @@ -440,6 +440,24 @@ bool EveSpaceScene::OnPrepareResources() return true; } +#if TBB_ENABLED +extern size_t g_currentThreadCount; +#endif + +namespace +{ +// Blocks of up to maxGrainSize objects, but never fewer than four tasks per thread so small scenes still spread out. +size_t GrainSize( size_t count, size_t maxGrainSize ) +{ +#if TBB_ENABLED + size_t grain = count / ( 4 * std::max( g_currentThreadCount, 1 ) ); + return std::clamp( grain, 1, maxGrainSize ); +#else + return maxGrainSize; +#endif +} +} + void EveSpaceScene::Update( Be::Time realTime, Be::Time simTime ) { TRINITY_STATS_ZONE( __FUNCTION__ ); @@ -572,7 +590,7 @@ void EveSpaceScene::Update( Be::Time realTime, Be::Time simTime ) Tr2ParallelTaskGroup taskGroup = {}; m_updateContext.SetTaskGroup( &taskGroup ); - Tr2ParallelFor( Tr2BlockedRange( 0, m_objects.size(), 32 ), [&]( Tr2BlockedRange range ) { + Tr2ParallelFor( Tr2BlockedRange( 0, m_objects.size(), GrainSize( m_objects.size(), 32 ) ), [&]( Tr2BlockedRange range ) { for( size_t i = range.begin(); i != range.end(); ++i ) { m_objects[i]->UpdateAsyncronous( m_updateContext ); @@ -883,9 +901,6 @@ void GetBatchesFromRenderables( ITr2Renderable** const objectRenderables, const } } -bool g_gatherPerObjectDataParallel = true; -TRI_REGISTER_SETTING( "gatherPerObjectDataParallel", g_gatherPerObjectDataParallel ); - namespace { enum PerObjectFlags : uint8_t @@ -915,28 +930,25 @@ void GetBatchesFromRenderables( { TRINITY_STATS_ZONE( "PerObjectData" ); - if( g_gatherPerObjectDataParallel ) - { - Tr2ParallelFor( Tr2BlockedRange( 0, renderableCount, 64 ), [&]( Tr2BlockedRange range ) { - for( unsigned i = range.begin(); i != range.end(); ++i ) + Tr2ParallelFor( Tr2BlockedRange( 0, renderableCount, unsigned( GrainSize( renderableCount, 64 ) ) ), [&]( Tr2BlockedRange range ) { + for( unsigned i = range.begin(); i != range.end(); ++i ) + { + ITr2Renderable* r = objectRenderables[i]; + if( !r->SupportsParallelPerObjectData() ) { - ITr2Renderable* r = objectRenderables[i]; - if( !r->SupportsParallelPerObjectData() ) - { - flags[i] = PER_OBJECT_DEFERRED; - continue; - } - perObjectData[i] = r->GetPerObjectData( opaqueBatches ); - if( wantTransparencies && r->HasTransparentBatches() ) - { - flags[i] = PER_OBJECT_TRANSPARENT; - } + flags[i] = PER_OBJECT_DEFERRED; + continue; } - } ); - } + perObjectData[i] = r->GetPerObjectData( opaqueBatches ); + if( wantTransparencies && r->HasTransparentBatches() ) + { + flags[i] = PER_OBJECT_TRANSPARENT; + } + } + } ); for( unsigned i = 0; i != renderableCount; ++i ) { - if( g_gatherPerObjectDataParallel && flags[i] != PER_OBJECT_DEFERRED ) + if( flags[i] != PER_OBJECT_DEFERRED ) { continue; } @@ -1480,7 +1492,7 @@ void EveSpaceScene::GatherBatches( bool includeDistortions, Tr2RenderContext& re { TRINITY_STATS_ZONE( "UpdateVisibility" ); - Tr2ParallelFor( Tr2BlockedRange( 0, m_objects.size(), 32 ), [&]( Tr2BlockedRange range ) { + Tr2ParallelFor( Tr2BlockedRange( 0, m_objects.size(), GrainSize( m_objects.size(), 32 ) ), [&]( Tr2BlockedRange range ) { for( size_t i = range.begin(); i != range.end(); ++i ) { m_objects[i]->UpdateVisibility( m_updateContext, identity ); @@ -1585,7 +1597,7 @@ void EveSpaceScene::PrepareRaytracedShadows( Tr2RenderContext& renderContext ) auto& shadowCasters = m_componentRegistry->GetComponents(); { TRINITY_STATS_ZONE( "PushRtGeometry" ); - Tr2ParallelFor( Tr2BlockedRange( 0, shadowCasters.size(), 32 ), [&]( Tr2BlockedRange range ) { + Tr2ParallelFor( Tr2BlockedRange( 0, shadowCasters.size(), GrainSize( shadowCasters.size(), 32 ) ), [&]( Tr2BlockedRange range ) { for( size_t i = range.begin(); i != range.end(); ++i ) { shadowCasters[i]->PushRtGeometry( *m_rtManager ); @@ -4230,9 +4242,6 @@ void EveSpaceScene::GetLightMatrices( const Tr2LightManager::PerLightData& light view = LookAtMatrix( lightData.position, lightData.position - lightData.direction, up ); } -bool g_rtOutdatedAnimationsParallel = true; -TRI_REGISTER_SETTING( "rtOutdatedAnimationsParallel", g_rtOutdatedAnimationsParallel ); - void EveSpaceScene::ProcessOutdatedRTAnimations( Tr2RenderContext& renderContext ) { TRINITY_STATS_ZONE( __FUNCTION__ ); @@ -4302,7 +4311,8 @@ void EveSpaceScene::ProcessOutdatedRTAnimations( Tr2RenderContext& renderContext const TriShadowOrthoFrustum sunOrthoFrustum( sunShadowFrustum, SHADOW_MAP_SIZE, sunDir ); std::vector spotShadowFrustums( begin( spotLightFrustums ), end( spotLightFrustums ) ); - auto processCaster = [&]( IEveShadowCaster* caster ) -> void { + auto& casters = m_componentRegistry->GetComponents(); + Tr2ParallelDo( begin( casters ), end( casters ), [&]( IEveShadowCaster* caster ) { if( caster->IsShadowCastingDirty() ) { return; @@ -4338,17 +4348,7 @@ void EveSpaceScene::ProcessOutdatedRTAnimations( Tr2RenderContext& renderContext return; } } - }; - - if( g_rtOutdatedAnimationsParallel ) - { - auto& casters = m_componentRegistry->GetComponents(); - Tr2ParallelDo( begin( casters ), end( casters ), processCaster ); - } - else - { - m_componentRegistry->ProcessComponents( processCaster ); - } + } ); } void RegisterWithVariableStore( const EveSpaceScene::ShadowResources& shadowResources, Tr2GpuResourcePool& gpuResourcePool ) diff --git a/trinity/Tr2RingBuffer.cpp b/trinity/Tr2RingBuffer.cpp index 7c5fa6d5b..8cc7fb53f 100644 --- a/trinity/Tr2RingBuffer.cpp +++ b/trinity/Tr2RingBuffer.cpp @@ -56,6 +56,10 @@ uint32_t Tr2RingBuffer::UploadTransforms( const T* data, uint32_t dataCount ) void Tr2RingBuffer::PrepareBuffer( Tr2RenderContext& renderContext ) { + if( m_gpuBufferStale ) + { + CreateGpuBuffer( renderContext.GetPrimaryRenderContext() ); + } for( auto& region : m_dirtyRegions ) { if( region.size ) @@ -102,10 +106,14 @@ void Tr2RingBuffer::Resize( uint32_t size ) m_head = m_size; m_size = size; m_tail = m_size; + m_gpuBufferStale = true; +} - USE_MAIN_THREAD_RENDER_CONTEXT(); - m_buffer.Create( m_stride, size, Tr2GpuUsage::SHADER_RESOURCE, Tr2CpuUsage::WRITE_OFTEN | Tr2CpuUsage::NON_SYNCRONIZED_WRITE, nullptr, renderContext ); +void Tr2RingBuffer::CreateGpuBuffer( Tr2PrimaryRenderContextAL& renderContext ) +{ + m_buffer.Create( m_stride, m_size, Tr2GpuUsage::SHADER_RESOURCE, Tr2CpuUsage::WRITE_OFTEN | Tr2CpuUsage::NON_SYNCRONIZED_WRITE, nullptr, renderContext ); m_buffer.SetName( m_name.c_str() ); + m_gpuBufferStale = false; } template @@ -120,6 +128,7 @@ Tr2RingBuffer& Tr2RingBuffer::GetInstance() self->m_stride = sizeof( T ); self->SetFrameNumbers( renderContext.GetRecordingFrameNumber(), renderContext.GetRenderedFrameNumber() ); self->Resize( INITIAL_SIZE ); + self->CreateGpuBuffer( renderContext ); } CCP_ASSERT_M( sizeof( T ) == self->m_stride, "Stride has to match size of datatype!" ); @@ -147,6 +156,7 @@ bool Tr2RingBuffer::OnPrepareResources() USE_MAIN_THREAD_RENDER_CONTEXT(); m_buffer.Create( m_stride, m_size, Tr2GpuUsage::SHADER_RESOURCE, Tr2CpuUsage::WRITE_OFTEN | Tr2CpuUsage::NON_SYNCRONIZED_WRITE, m_mirror.data(), renderContext ); m_buffer.SetName( m_name.c_str() ); + m_gpuBufferStale = false; } return true; } diff --git a/trinity/Tr2RingBuffer.h b/trinity/Tr2RingBuffer.h index 575663a08..2b54a4b16 100644 --- a/trinity/Tr2RingBuffer.h +++ b/trinity/Tr2RingBuffer.h @@ -48,13 +48,17 @@ BLUE_CLASS( Tr2RingBuffer ) : void ReleaseResources( TriStorage s ) override; bool OnPrepareResources() override; + // UploadTransforms may run on worker threads, so Resize only grows the mirror; + // the GPU buffer is recreated in PrepareBuffer on the main thread. void Resize( uint32_t size ); + void CreateGpuBuffer( Tr2PrimaryRenderContextAL& renderContext ); std::string m_name; uint32_t m_stride; std::mutex m_mutex; Tr2BufferAL m_buffer; + bool m_gpuBufferStale = false; std::vector m_mirror; uint64_t m_frame = 0; diff --git a/trinity/TriPoolAllocator.cpp b/trinity/TriPoolAllocator.cpp index e5d103248..3be819af6 100644 --- a/trinity/TriPoolAllocator.cpp +++ b/trinity/TriPoolAllocator.cpp @@ -72,10 +72,13 @@ void* TriPoolAllocator::Allocate( size_t size ) Chunk* chunk = m_chunk.load( std::memory_order_acquire ); if( chunk ) { - uint8_t* p = chunk->current.fetch_add( size, std::memory_order_relaxed ); - if( p + size <= chunk->end ) + uint8_t* p = chunk->current.load( std::memory_order_relaxed ); + while( size_t( chunk->end - p ) >= size ) { - return p; + if( chunk->current.compare_exchange_weak( p, p + size, std::memory_order_relaxed ) ) + { + return p; + } } } @@ -159,8 +162,7 @@ TriPoolAllocator::Chunk* TriPoolAllocator::AddChunk( size_t size ) size_t TriPoolAllocator::UsedBytes( const Chunk& chunk ) { - uint8_t* current = chunk.current.load( std::memory_order_relaxed ); - return size_t( std::min( current, chunk.end ) - chunk.memory ); + return size_t( chunk.current.load( std::memory_order_relaxed ) - chunk.memory ); } void TriPoolAllocator::FreeChunk( Chunk* chunk ) From 7a064913d524798981050f4509fd0586e8083d04 Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Tue, 27 Oct 2026 16:32:00 +0000 Subject: [PATCH 3/4] Reset sizeInShadow per caster when gathering shadow batches The cloud-shadow and spot-light batch loops declared sizeInShadow once outside the loop, so a caster whose IsCastingShadow returns false without writing it inherited the previous caster's value and could have shadow batches gathered. Declare it per iteration, matching the RT animation loop. --- trinity/Eve/EveSpaceScene.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trinity/Eve/EveSpaceScene.cpp b/trinity/Eve/EveSpaceScene.cpp index a5f7e8788..e91e9b013 100644 --- a/trinity/Eve/EveSpaceScene.cpp +++ b/trinity/Eve/EveSpaceScene.cpp @@ -2452,9 +2452,9 @@ void EveSpaceScene::RenderIntoCloudShadowMap( Tr2RenderContext& renderContext, c { TRINITY_STATS_ZONE( "get shadowbatches for volumetrics" ); auto shadowFrustum = TriShadowOrthoFrustum( cloudShadowInformation->shadowFrustum, cloudShadowInformation->shadowMapSize, m_sunData.DirWorld ); - float sizeInShadow = 0.0f; for( auto& caster : shadowCasters ) { + float sizeInShadow = 0.0f; caster->IsCastingShadow( frustum, shadowFrustum, TR2RENDERREASON_NORMAL, sizeInShadow ); // special threshold check if( sizeInShadow > 5.0f ) @@ -2578,9 +2578,9 @@ void EveSpaceScene::RenderShadowMapForSpotLight( shadowFrustum.DeriveFrustum( &view, &lightPosition, &projection, renderContext.m_esm.GetViewport() ); { TRINITY_STATS_ZONE( "get shadowbatches for light" ); - float sizeInShadow = 0.0f; for( auto& caster : shadowCasters ) { + float sizeInShadow = 0.0f; caster->IsCastingShadow( m_updateContext.GetFrustum(), TriShadowFrustum( shadowFrustum ), TR2RENDERREASON_NORMAL, sizeInShadow ); // special threshold check if( sizeInShadow > 5.0f ) From bd79c650b85ac5d45ce6f798534b666de0f5aeaa Mon Sep 17 00:00:00 2001 From: JohnGreenCCP Date: Tue, 27 Oct 2026 18:01:16 +0000 Subject: [PATCH 4/4] Fix clang-format on CreateGpuBuffer declaration --- trinity/Tr2RingBuffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trinity/Tr2RingBuffer.h b/trinity/Tr2RingBuffer.h index 2b54a4b16..b08c83543 100644 --- a/trinity/Tr2RingBuffer.h +++ b/trinity/Tr2RingBuffer.h @@ -51,7 +51,7 @@ BLUE_CLASS( Tr2RingBuffer ) : // UploadTransforms may run on worker threads, so Resize only grows the mirror; // the GPU buffer is recreated in PrepareBuffer on the main thread. void Resize( uint32_t size ); - void CreateGpuBuffer( Tr2PrimaryRenderContextAL& renderContext ); + void CreateGpuBuffer( Tr2PrimaryRenderContextAL & renderContext ); std::string m_name;