2023-07-31 17:25:08 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "MeshDrawCommandStats.h"
2023-08-02 19:48:00 -04:00
# include "InstanceCulling/InstanceCullingContext.h"
# include "MeshDrawCommandStatsSettings.h"
2023-09-08 14:27:36 -04:00
# include "ProfilingDebugging/CsvProfiler.h"
2023-08-02 19:48:00 -04:00
# include "RenderGraph.h"
# include "RendererModule.h"
# include "RendererOnScreenNotification.h"
2023-07-31 17:25:08 -04:00
# include "RHI.h"
# include "RHIGPUReadback.h"
2023-08-03 20:04:19 -04:00
# if MESH_DRAW_COMMAND_STATS
2023-07-31 17:25:08 -04:00
FMeshDrawCommandStatsManager * FMeshDrawCommandStatsManager : : Instance = nullptr ;
void FMeshDrawCommandStatsManager : : CreateInstance ( )
{
check ( Instance = = nullptr ) ;
Instance = new FMeshDrawCommandStatsManager ( ) ;
}
DECLARE_STATS_GROUP ( TEXT ( " MeshDrawCommandStats " ) , STATGROUP_Culling , STATCAT_Advanced ) ;
2023-08-02 19:48:00 -04:00
DECLARE_DWORD_COUNTER_STAT ( TEXT ( " Total Rendered Primitives " ) , STAT_Culling_TotalNumPrimitives , STATGROUP_Culling ) ;
2023-07-31 17:25:08 -04:00
DECLARE_DWORD_COUNTER_STAT ( TEXT ( " Total Rendered Instances " ) , STAT_Culling_TotalNumInstances , STATGROUP_Culling ) ;
2023-08-02 19:48:00 -04:00
DECLARE_DWORD_COUNTER_STAT ( TEXT ( " InstanceCulling Indirect Rendered Primitives " ) , STAT_Culling_InstanceCullingIndirectNumPrimitives , STATGROUP_Culling ) ;
2023-07-31 17:25:08 -04:00
DECLARE_DWORD_COUNTER_STAT ( TEXT ( " InstanceCulling Indirect Rendered Instances " ) , STAT_Culling_InstanceCullingIndirectNumInstances , STATGROUP_Culling ) ;
2023-08-02 19:48:00 -04:00
DECLARE_DWORD_COUNTER_STAT ( TEXT ( " Custom Indirect Rendered Primitives " ) , STAT_Culling_CustomIndirectNumPrimitives , STATGROUP_Culling ) ;
2023-07-31 17:25:08 -04:00
DECLARE_DWORD_COUNTER_STAT ( TEXT ( " Custom Indirect Rendered Instances " ) , STAT_Culling_CustomIndirectNumInstances , STATGROUP_Culling ) ;
2023-10-18 12:01:13 -04:00
CSV_DEFINE_CATEGORY ( MeshDrawCommandStats , false ) ;
2023-09-08 14:27:36 -04:00
2023-10-18 12:01:13 -04:00
enum class MeshDrawStatsCollection : int32
{
None ,
Pass ,
User ,
} ;
static TAutoConsoleVariable < int32 > CVarMeshDrawCommandStats (
2023-07-31 17:25:08 -04:00
TEXT ( " r.MeshDrawCommands.Stats " ) ,
0 ,
2023-08-03 20:04:19 -04:00
TEXT ( " Show on screen mesh draw command stats. \n " )
TEXT ( " The stats for visible triangles are post GPU culling. \n " )
2023-10-18 12:01:13 -04:00
TEXT ( " 1 = Show stats per pass. \n " )
TEXT ( " 2...N = Show the collection of stats matching the 'Collection' parameter in the ini file. \n " )
2023-08-03 20:04:19 -04:00
TEXT ( " You can also use 'stat culling' to see global culling stats. \n " ) ,
2023-07-31 17:25:08 -04:00
ECVF_RenderThreadSafe
) ;
FMeshDrawCommandStatsManager : : FFrameData : : ~ FFrameData ( )
{
// Collect set of unique readback buffers for deletion (can be shared between MDCs and passes)
2023-10-06 07:27:20 -04:00
TSet < FRHIGPUBufferReadback * > ReadbackBuffers ( RDGIndirectArgsReadbackBuffers ) ;
2023-07-31 17:25:08 -04:00
for ( FMeshDrawCommandPassStats * PassStats : PassData )
{
if ( PassStats - > InstanceCullingGPUBufferReadback )
{
2023-10-06 07:27:20 -04:00
check ( ReadbackBuffers . Contains ( PassStats - > InstanceCullingGPUBufferReadback ) ) ;
2023-07-31 17:25:08 -04:00
PassStats - > InstanceCullingGPUBufferReadback = nullptr ;
}
delete PassStats ;
}
for ( auto Iter = CustomIndirectArgsBufferResults . CreateIterator ( ) ; Iter ; + + Iter )
{
FIndirectArgsBufferResult & CustomArgsBufferResult = Iter . Value ( ) ;
if ( CustomArgsBufferResult . GPUBufferReadback )
{
ReadbackBuffers . Add ( CustomArgsBufferResult . GPUBufferReadback ) ;
CustomArgsBufferResult . GPUBufferReadback = nullptr ;
}
}
CustomIndirectArgsBufferResults . Empty ( ) ;
// delete all unique readback buffers
for ( FRHIGPUBufferReadback * ReadbackBuffer : ReadbackBuffers )
{
delete ReadbackBuffer ;
}
}
void FMeshDrawCommandStatsManager : : FFrameData : : Validate ( ) const
{
bool bHasIndirectArgs = false ;
// make sure that each pass which has indirect draws also has a gpu readback buffer to resolve the final used instance count
for ( const FMeshDrawCommandPassStats * PassStats : PassData )
{
if ( PassStats - > bBuildRenderingCommandsCalled )
{
bool bUsesInstantCullingIndirectBuffer = false ;
for ( const FVisibleMeshDrawCommandStatsData & DrawData : PassStats - > DrawData )
{
if ( DrawData . UseInstantCullingIndirectBuffer > 0 )
{
bUsesInstantCullingIndirectBuffer = true ;
bHasIndirectArgs = true ;
}
if ( DrawData . CustomIndirectArgsBuffer )
{
2023-08-07 11:54:17 -04:00
ensure ( PassStats - > CustomIndirectArgsBuffers . Contains ( DrawData . CustomIndirectArgsBuffer ) ) ;
2023-07-31 17:25:08 -04:00
bHasIndirectArgs = true ;
}
}
// either we don't use draw indirect or we don't have a readback buffer
check ( ! bUsesInstantCullingIndirectBuffer | | PassStats - > InstanceCullingGPUBufferReadback ! = nullptr ) ;
}
}
// Make sure readback has been requested
check ( ! bHasIndirectArgs | | bIndirectArgReadbackRequested ) ;
}
/**
* Make sure all GPU readback requests are finished before marking frame as complete
*/
bool FMeshDrawCommandStatsManager : : FFrameData : : IsCompleted ( )
{
for ( auto Iter = CustomIndirectArgsBufferResults . CreateIterator ( ) ; Iter ; + + Iter )
{
if ( ! Iter . Value ( ) . GPUBufferReadback - > IsReady ( ) )
{
return false ;
}
}
for ( FMeshDrawCommandPassStats * PassStats : PassData )
{
if ( PassStats - > InstanceCullingGPUBufferReadback & & ! PassStats - > InstanceCullingGPUBufferReadback - > IsReady ( ) )
{
return false ;
}
}
return true ;
}
FMeshDrawCommandStatsManager : : FMeshDrawCommandStatsManager ( )
{
// Tick on and of RT frame
FCoreDelegates : : OnEndFrameRT . AddRaw ( this , & FMeshDrawCommandStatsManager : : Update ) ;
// Is it fine to keep the screen message delegate always registered even if we are not showing anything?
ScreenMessageDelegate = FRendererOnScreenNotification : : Get ( ) . AddLambda ( [ this ] ( TMultiMap < FCoreDelegates : : EOnScreenMessageSeverity , FText > & OutMessages )
{
2024-01-19 15:01:13 -05:00
int32 TotalPrimitivesTracked = 0 ;
int32 TotalPrimitivesUntracked = 0 ;
2023-10-18 12:01:13 -04:00
const bool bShowStats = CVarMeshDrawCommandStats - > GetInt ( ) ! = ( int ) MeshDrawStatsCollection : : None ;
2023-07-31 17:25:08 -04:00
if ( bShowStats )
{
2023-08-02 19:48:00 -04:00
OutMessages . Add ( FCoreDelegates : : EOnScreenMessageSeverity : : Info , FText : : FromString ( FString : : Printf ( TEXT ( " MeshDrawCommandStats (Triangles / Budget - Category): " ) , Stats . TotalPrimitives / 1000 ) ) ) ;
2023-10-18 12:01:13 -04:00
int Collection = CVarMeshDrawCommandStats - > GetInt ( ) ;
2023-08-03 20:04:19 -04:00
// Show budgeted stats first.
const UMeshDrawCommandStatsSettings * Settings = GetDefault < UMeshDrawCommandStatsSettings > ( ) ;
for ( FMeshDrawCommandStatsBudget const & CategoryBudget : Settings - > Budgets )
2023-07-31 17:25:08 -04:00
{
2023-10-18 12:01:13 -04:00
if ( CategoryBudget . Collection = = Collection )
2023-08-02 19:48:00 -04:00
{
2023-10-18 12:01:13 -04:00
uint64 * PrimitiveCount = BudgetedPrimitives . Find ( CategoryBudget . CategoryName ) ;
if ( PrimitiveCount & & * PrimitiveCount > 0 )
{
2023-10-31 19:13:02 -04:00
FString & PassFriendlyNames = StatCollections [ CategoryBudget . Collection ] . CategoryPassFriendlyNames [ CategoryBudget . CategoryName ] ;
2023-10-18 12:01:13 -04:00
FCoreDelegates : : EOnScreenMessageSeverity Severity = CategoryBudget . PrimitiveBudget < * PrimitiveCount ? FCoreDelegates : : EOnScreenMessageSeverity : : Warning : FCoreDelegates : : EOnScreenMessageSeverity : : Info ;
2023-10-31 19:13:02 -04:00
OutMessages . Add ( Severity , FText : : FromString ( FString : : Printf ( TEXT ( " %5dK / %5dK - %s (%s) " ) ,
* PrimitiveCount / 1000 ,
CategoryBudget . PrimitiveBudget / 1000 ,
* ( CategoryBudget . CategoryName . ToString ( ) ) ,
* PassFriendlyNames
) ) ) ;
2024-01-19 15:01:13 -05:00
TotalPrimitivesTracked + = * PrimitiveCount ;
2023-10-18 12:01:13 -04:00
}
2023-08-02 19:48:00 -04:00
}
}
2023-09-08 14:27:36 -04:00
// Show remaining (non-zeroed) stats not coverted by Budgets
for ( const TPair < FName , uint64 > & Pair : UntrackedPrimitives )
2023-08-03 20:04:19 -04:00
{
2023-09-08 14:27:36 -04:00
const FName & Name = Pair . Key ;
uint64 PrimitiveCount = Pair . Value ;
if ( PrimitiveCount > 0 )
2023-08-03 20:04:19 -04:00
{
2023-09-08 14:27:36 -04:00
OutMessages . Add ( FCoreDelegates : : EOnScreenMessageSeverity : : Info , FText : : FromString ( FString : : Printf ( TEXT ( " \t %5dK - %s " ) , PrimitiveCount / 1000 , * ( Name . ToString ( ) ) ) ) ) ;
2023-08-03 20:04:19 -04:00
}
2024-01-19 15:01:13 -05:00
TotalPrimitivesUntracked + = PrimitiveCount ;
2023-08-03 20:04:19 -04:00
}
// Show total budget.
2024-01-19 15:01:13 -05:00
FStatCollection * StatCollection = StatCollections . Find ( Collection ) ;
const int32 PrimitiveBudget = StatCollection ! = nullptr ? StatCollection - > PrimitiveBudget : 0 ;
if ( PrimitiveBudget > 0 )
2023-08-02 19:48:00 -04:00
{
2024-01-19 15:01:13 -05:00
FCoreDelegates : : EOnScreenMessageSeverity Severity = PrimitiveBudget < Stats . TotalPrimitives ? FCoreDelegates : : EOnScreenMessageSeverity : : Warning : FCoreDelegates : : EOnScreenMessageSeverity : : Info ;
OutMessages . Add ( Severity , FText : : FromString ( FString : : Printf ( TEXT ( " %5dK / %5dK - Total Budgeted " ) , TotalPrimitivesTracked / 1000 , PrimitiveBudget / 1000 ) ) ) ;
if ( TotalPrimitivesUntracked )
{
OutMessages . Add ( Severity , FText : : FromString ( FString : : Printf ( TEXT ( " %5dK - Total Untracked " ) , TotalPrimitivesUntracked ) ) ) ;
}
2023-08-02 19:48:00 -04:00
}
else
{
OutMessages . Add ( FCoreDelegates : : EOnScreenMessageSeverity : : Info , FText : : FromString ( FString : : Printf ( TEXT ( " \t %5dK - TOTAL " ) , Stats . TotalPrimitives / 1000 ) ) ) ;
2023-07-31 17:25:08 -04:00
}
}
} ) ;
}
FMeshDrawCommandPassStats * FMeshDrawCommandStatsManager : : CreatePassStats ( FName PassName )
{
if ( ! bCollectStats )
{
return nullptr ;
}
FScopeLock ScopeLock ( & FrameDataCS ) ;
FFrameData * FrameData = GetOrAddFrameData ( ) ;
FMeshDrawCommandPassStats * PassStats = new FMeshDrawCommandPassStats ( PassName ) ;
FrameData - > PassData . Add ( PassStats ) ;
return PassStats ;
}
FRHIGPUBufferReadback * FMeshDrawCommandStatsManager : : QueueDrawRDGIndirectArgsReadback ( FRDGBuilder & GraphBuilder , FRDGBuffer * DrawIndirectArgsRDG )
{
// TODO: pool the readback buffers
FRHIGPUBufferReadback * GPUBufferReadback = new FRHIGPUBufferReadback ( TEXT ( " InstanceCulling.StatsReadbackQuery " ) ) ;
AddReadbackBufferPass ( GraphBuilder , RDG_EVENT_NAME ( " ReadbackIndirectArgs " ) , DrawIndirectArgsRDG ,
[ GPUBufferReadback , DrawIndirectArgsRDG ] ( FRHICommandList & RHICmdList )
{
GPUBufferReadback - > EnqueueCopy ( RHICmdList , DrawIndirectArgsRDG - > GetRHI ( ) , 0u ) ;
} ) ;
2023-10-06 07:27:20 -04:00
// Make sure the readback buffer is stored for later deletion because the batch could be empty and then readback buffer might never be deleted
{
FScopeLock ScopeLock ( & FrameDataCS ) ;
FFrameData * FrameData = GetOrAddFrameData ( ) ;
FrameData - > RDGIndirectArgsReadbackBuffers . Add ( GPUBufferReadback ) ;
}
2023-07-31 17:25:08 -04:00
return GPUBufferReadback ;
}
void FMeshDrawCommandStatsManager : : QueueCustomDrawIndirectArgsReadback ( FRHICommandListImmediate & CommandList )
{
if ( ! bCollectStats )
{
return ;
}
FScopeLock ScopeLock ( & FrameDataCS ) ;
FFrameData * FrameData = GetOrAddFrameData ( ) ;
FrameData - > bIndirectArgReadbackRequested = true ;
// Collect set of all unique custom indirect arg buffers
TSet < FRHIBuffer * > CustomIndirectArgsBuffers ;
for ( FMeshDrawCommandPassStats * PassStats : FrameData - > PassData )
{
CustomIndirectArgsBuffers . Append ( PassStats - > CustomIndirectArgsBuffers ) ;
}
for ( FRHIBuffer * CustomIndirectArgsBuffer : CustomIndirectArgsBuffers )
{
FRHIGPUBufferReadback * GPUBufferReadback = new FRHIGPUBufferReadback ( TEXT ( " CustomIndirectArgs.StatsReadbackQuery " ) ) ;
GPUBufferReadback - > EnqueueCopy ( CommandList , CustomIndirectArgsBuffer , 0u ) ;
FIndirectArgsBufferResult IndirectArgsBufferResult ;
IndirectArgsBufferResult . GPUBufferReadback = GPUBufferReadback ;
FrameData - > CustomIndirectArgsBufferResults . Add ( CustomIndirectArgsBuffer , IndirectArgsBufferResult ) ;
}
}
void FMeshDrawCommandStatsManager : : Update ( )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FMeshDrawCommandStatsManager : : Update ) ;
+ + CurrentFrameNumber ;
2023-10-18 12:01:13 -04:00
const bool bShowPassNameStats = CVarMeshDrawCommandStats - > GetInt ( ) = = ( int ) MeshDrawStatsCollection : : Pass ;
2023-08-03 20:04:19 -04:00
2023-07-31 17:25:08 -04:00
FScopeLock ScopeLock ( & FrameDataCS ) ;
bool bHasProcessedFrame = false ;
// TODO: might be more than one from a given frame. E.g., if it was using a scene capture, need to filter out those, or perhaps record them as a group actually.
for ( int32 Index = Frames . Num ( ) - 1 ; Index > = 0 ; - - Index )
{
FFrameData * FrameData = Frames [ Index ] ;
if ( FrameData - > IsCompleted ( ) )
{
if ( ! bHasProcessedFrame )
{
bHasProcessedFrame = true ;
// TODO: offload processing to async task to offload the rendering thread and time the FrameDataCS lock is taken
Stats . Reset ( ) ;
// Get custom indirect args data
for ( auto Iter = FrameData - > CustomIndirectArgsBufferResults . CreateIterator ( ) ; Iter ; + + Iter )
{
FIndirectArgsBufferResult & CustomArgsBufferResult = Iter . Value ( ) ;
CustomArgsBufferResult . DrawIndexedIndirectParameters = reinterpret_cast < const FRHIDrawIndexedIndirectParameters * > ( CustomArgsBufferResult . GPUBufferReadback - > Lock ( CustomArgsBufferResult . GPUBufferReadback - > GetGPUSizeBytes ( ) ) ) ;
}
2023-10-31 19:13:02 -04:00
using PassCategoryStats = TMap < FName , uint64 > ;
TMap < FName , PassCategoryStats > Passes ;
2023-07-31 17:25:08 -04:00
for ( FMeshDrawCommandPassStats * PassStats : FrameData - > PassData )
{
// make sure the pass was kicked
if ( ! PassStats - > bBuildRenderingCommandsCalled )
{
continue ;
}
2023-10-31 19:13:02 -04:00
PassCategoryStats & CategoryStats = Passes . FindOrAdd ( PassStats - > PassName ) ;
2023-07-31 17:25:08 -04:00
const uint8 * InstanceCullingReadBackData = PassStats - > InstanceCullingGPUBufferReadback ? reinterpret_cast < const uint8 * > ( PassStats - > InstanceCullingGPUBufferReadback - > Lock ( PassStats - > DrawData . Num ( ) ) ) : nullptr ;
const FRHIDrawIndexedIndirectParameters * IndirectArgsPtr = reinterpret_cast < const FRHIDrawIndexedIndirectParameters * > ( InstanceCullingReadBackData ) ;
for ( int32 CmdIndex = 0 ; CmdIndex < PassStats - > DrawData . Num ( ) ; + + CmdIndex )
{
FVisibleMeshDrawCommandStatsData & DrawData = PassStats - > DrawData [ CmdIndex ] ;
int32 IndirectCommandIndex = DrawData . IndirectArgsOffset / ( FInstanceCullingContext : : IndirectArgsNumWords * sizeof ( uint32 ) ) ;
if ( DrawData . CustomIndirectArgsBuffer )
{
2023-08-07 11:54:17 -04:00
ensure ( DrawData . PrimitiveCount = = 0 ) ;
2023-07-31 17:25:08 -04:00
FIndirectArgsBufferResult * IndirectArgsBufferResult = FrameData - > CustomIndirectArgsBufferResults . Find ( DrawData . CustomIndirectArgsBuffer ) ;
2023-08-07 11:54:17 -04:00
if ( ensure ( IndirectArgsBufferResult ) )
2023-07-31 17:25:08 -04:00
{
const FRHIDrawIndexedIndirectParameters & IndirectArgs = IndirectArgsBufferResult - > DrawIndexedIndirectParameters [ IndirectCommandIndex ] ;
DrawData . PrimitiveCount = IndirectArgs . IndexCountPerInstance / 3 ; //< Assume triangles here for now - primitive count is empty so can't be used
DrawData . VisibleInstanceCount = IndirectArgs . InstanceCount ;
DrawData . TotalInstanceCount = FMath : : Max ( DrawData . TotalInstanceCount , DrawData . VisibleInstanceCount ) ;
Stats . CustomIndirectInstances + = DrawData . VisibleInstanceCount ;
2023-08-02 19:48:00 -04:00
Stats . CustomIndirectPrimitives + = DrawData . VisibleInstanceCount * DrawData . PrimitiveCount ;
2023-07-31 17:25:08 -04:00
}
}
2023-11-03 22:31:19 -04:00
else if ( IndirectArgsPtr & & DrawData . UseInstantCullingIndirectBuffer > 0 & & InstanceCullingReadBackData )
2023-07-31 17:25:08 -04:00
{
const FRHIDrawIndexedIndirectParameters & IndirectArgs = IndirectArgsPtr [ PassStats - > IndirectArgParameterOffset + IndirectCommandIndex ] ;
2023-08-07 11:54:17 -04:00
ensure ( DrawData . PrimitiveCount = = IndirectArgs . IndexCountPerInstance / 3 ) ;
2023-07-31 17:25:08 -04:00
DrawData . VisibleInstanceCount = IndirectArgs . InstanceCount ;
2023-08-07 11:54:17 -04:00
ensure ( DrawData . VisibleInstanceCount < = DrawData . TotalInstanceCount ) ;
2023-07-31 17:25:08 -04:00
Stats . InstanceCullingIndirectInstances + = DrawData . VisibleInstanceCount ;
2023-08-02 19:48:00 -04:00
Stats . InstanceCullingIndirectPrimitives + = DrawData . VisibleInstanceCount * DrawData . PrimitiveCount ;
2023-07-31 17:25:08 -04:00
}
Stats . TotalInstances + = DrawData . VisibleInstanceCount ;
2023-08-02 19:48:00 -04:00
Stats . TotalPrimitives + = DrawData . VisibleInstanceCount * DrawData . PrimitiveCount ;
2023-07-31 17:25:08 -04:00
2023-08-03 20:04:19 -04:00
FName StatName = bShowPassNameStats ? PassStats - > PassName : DrawData . StatsData . CategoryName ;
uint64 & TotalCount = CategoryStats . FindOrAdd ( StatName ) ;
2023-07-31 17:25:08 -04:00
TotalCount + = DrawData . VisibleInstanceCount * DrawData . PrimitiveCount ;
}
if ( IndirectArgsPtr )
{
PassStats - > InstanceCullingGPUBufferReadback - > Unlock ( ) ;
}
}
for ( auto Iter = FrameData - > CustomIndirectArgsBufferResults . CreateIterator ( ) ; Iter ; + + Iter )
{
FIndirectArgsBufferResult & CustomArgsBufferResult = Iter . Value ( ) ;
CustomArgsBufferResult . GPUBufferReadback - > Unlock ( ) ;
CustomArgsBufferResult . DrawIndexedIndirectParameters = nullptr ;
}
2023-10-31 19:13:02 -04:00
for ( auto PassIter = Passes . CreateConstIterator ( ) ; PassIter ; + + PassIter )
2023-07-31 17:25:08 -04:00
{
2023-10-31 19:13:02 -04:00
PassCategoryStats CatMap = PassIter . Value ( ) ;
for ( auto CatIter = CatMap . CreateConstIterator ( ) ; CatIter ; + + CatIter )
{
Stats . CategoryStats . Add ( FStats : : FCategoryStats ( PassIter . Key ( ) , CatIter . Key ( ) , CatIter . Value ( ) ) ) ;
}
2023-07-31 17:25:08 -04:00
}
2023-08-03 20:04:19 -04:00
Algo : : Sort ( Stats . CategoryStats , [ this ] ( FStats : : FCategoryStats & LHS , FStats : : FCategoryStats & RHS ) { return LHS . CategoryName . ToString ( ) < RHS . CategoryName . ToString ( ) ; } ) ;
2023-07-31 17:25:08 -04:00
// Got new stats, so can dump them if requested
static bool bDumpStats = false ;
if ( bDumpStats | | bRequestDumpStats )
{
DumpStats ( FrameData ) ;
bDumpStats = false ;
bRequestDumpStats = false ;
}
}
// Could pool the frames for allocation effeciency
delete FrameData ;
// Ok, since we're interating backwards - must not use RemoveAtSwap because we depend on the order being the most recent last.
// there may be older frames further up that were not completed last frame, but we want to clear them out now.
Frames . RemoveAt ( Index ) ;
}
}
// We keep and set the value from the previous frame in case there are no readback, this avoids alternating values if for example two queries were consumed in one frame
// might be able to do this better perhaps.
2023-08-02 19:48:00 -04:00
SET_DWORD_STAT ( STAT_Culling_TotalNumPrimitives , Stats . TotalPrimitives ) ;
2023-07-31 17:25:08 -04:00
SET_DWORD_STAT ( STAT_Culling_TotalNumInstances , Stats . TotalInstances ) ;
2023-08-02 19:48:00 -04:00
SET_DWORD_STAT ( STAT_Culling_InstanceCullingIndirectNumPrimitives , Stats . InstanceCullingIndirectPrimitives ) ;
2023-07-31 17:25:08 -04:00
SET_DWORD_STAT ( STAT_Culling_InstanceCullingIndirectNumInstances , Stats . InstanceCullingIndirectInstances ) ;
2023-08-02 19:48:00 -04:00
SET_DWORD_STAT ( STAT_Culling_CustomIndirectNumPrimitives , Stats . CustomIndirectPrimitives ) ;
2023-07-31 17:25:08 -04:00
SET_DWORD_STAT ( STAT_Culling_CustomIndirectNumInstances , Stats . CustomIndirectInstances ) ;
// Collect stats during the next frame (check if STATGROUP_Culling is also visible somehow)
2023-10-18 12:01:13 -04:00
const bool bShowStats = CVarMeshDrawCommandStats - > GetInt ( ) ! = ( int ) MeshDrawStatsCollection : : None ;
2023-07-31 17:25:08 -04:00
bCollectStats = bShowStats | | bRequestDumpStats ;
2023-09-08 14:27:36 -04:00
# if CSV_PROFILER
2023-10-18 12:01:13 -04:00
const bool bCsvExport = FCsvProfiler : : Get ( ) - > IsCapturing_Renderthread ( ) & & FCsvProfiler : : Get ( ) - > IsCategoryEnabled ( CSV_CATEGORY_INDEX ( MeshDrawCommandStats ) ) ;
bCollectStats | = bCsvExport ;
2023-09-08 14:27:36 -04:00
# endif
if ( bCollectStats )
{
// First time - Build associative map for quick Stat -> Budget lookup
2023-10-18 12:01:13 -04:00
if ( ! StatCollections . Num ( ) )
2023-09-08 14:27:36 -04:00
{
const UMeshDrawCommandStatsSettings * Settings = GetDefault < UMeshDrawCommandStatsSettings > ( ) ;
for ( const FMeshDrawCommandStatsBudget & CategoryBudget : Settings - > Budgets )
{
2023-10-31 19:13:02 -04:00
FStatCollection & Collection = StatCollections . FindOrAdd ( CategoryBudget . Collection ) ;
FCollectionCategory & Category = Collection . Categories . AddDefaulted_GetRef ( ) ;
Category . Name = CategoryBudget . CategoryName ;
FString & FriendlyNames = Collection . CategoryPassFriendlyNames . FindOrAdd ( CategoryBudget . CategoryName ) ;
if ( CategoryBudget . Passes . Num ( ) )
{
for ( int i = 0 ; i < CategoryBudget . Passes . Num ( ) ; i + + )
{
const FName & Pass = CategoryBudget . Passes [ i ] ;
Category . Passes . Add ( Pass ) ;
FriendlyNames + = i ? " | " : " " ;
FriendlyNames + = Pass . ToString ( ) ;
}
}
else
{
FriendlyNames = " All Passes " ;
}
Category . LinkedNames . Add ( CategoryBudget . CategoryName ) ;
2023-09-08 14:27:36 -04:00
for ( FName Name : CategoryBudget . LinkedStatNames )
{
2023-10-31 19:13:02 -04:00
Category . LinkedNames . Add ( Name ) ;
2023-09-08 14:27:36 -04:00
}
}
2023-10-31 19:13:02 -04:00
2024-01-19 15:01:13 -05:00
for ( const FMeshDrawCommandStatsBudgetTotals & BudgetTotal : Settings - > BudgetTotals )
{
FStatCollection * Collection = StatCollections . Find ( BudgetTotal . Collection ) ;
if ( Collection )
{
Collection - > PrimitiveBudget = BudgetTotal . PrimitiveBudget ;
}
}
2023-10-31 19:13:02 -04:00
for ( TPair < int32 , FStatCollection > & Pair : StatCollections )
{
Pair . Value . Finish ( ) ;
}
2023-09-08 14:27:36 -04:00
}
// Total up Primitive across stats to their respective Budgets
BudgetedPrimitives . Reset ( ) ;
UntrackedPrimitives . Reset ( ) ;
2023-10-18 12:01:13 -04:00
int CollectionIdx = CVarMeshDrawCommandStats - > GetInt ( ) ;
2023-09-08 14:27:36 -04:00
2023-10-18 12:01:13 -04:00
# if CSV_PROFILER // If capturing for CSV, override the collection to the one requested in the ini file
if ( FCsvProfiler : : Get ( ) - > IsCapturing_Renderthread ( ) & & FCsvProfiler : : Get ( ) - > IsCategoryEnabled ( CSV_CATEGORY_INDEX ( MeshDrawCommandStats ) ) )
{
CollectionIdx = GetDefault < UMeshDrawCommandStatsSettings > ( ) - > CollectionForCsvProfiler ;
}
# endif
2023-10-31 19:13:02 -04:00
FStatCollection * Collection = StatCollections . Find ( CollectionIdx ) ;
2023-10-18 12:01:13 -04:00
if ( Collection | | CollectionIdx = = ( int ) MeshDrawStatsCollection : : Pass )
{
for ( const FStats : : FCategoryStats & CategoryStat : Stats . CategoryStats )
{
2023-10-31 19:13:02 -04:00
TArray < int > * CategoryIndices = nullptr ;
2023-10-18 12:01:13 -04:00
2023-10-31 19:13:02 -04:00
if ( Collection )
{
CategoryIndices = Collection - > CategoriesThatLinkStat ( CategoryStat . CategoryName ) ;
}
if ( CategoryIndices )
{
for ( int CategoryIndex : * CategoryIndices )
{
FCollectionCategory & Category = Collection - > Categories [ CategoryIndex ] ;
if ( ! Category . Passes . Num ( ) | | Category . Passes . Contains ( CategoryStat . PassName ) )
{
uint64 & Count = BudgetedPrimitives . FindOrAdd ( Category . Name ) ;
Count + = CategoryStat . PrimitiveCount ;
}
}
}
else
{
// No collection categories care about this stat, so it was probably missed
uint64 & Count = UntrackedPrimitives . FindOrAdd ( CategoryStat . CategoryName ) ;
Count + = CategoryStat . PrimitiveCount ;
}
2023-10-18 12:01:13 -04:00
}
2023-09-08 14:27:36 -04:00
}
}
# if CSV_PROFILER
2023-10-18 12:01:13 -04:00
if ( bCsvExport )
2023-09-08 14:27:36 -04:00
{
// Output Budget totals
for ( const TPair < FName , uint64 > & Pair : BudgetedPrimitives )
{
TRACE_CSV_PROFILER_INLINE_STAT ( TCHAR_TO_ANSI ( * Pair . Key . ToString ( ) ) , CSV_CATEGORY_INDEX ( MeshDrawCommandStats ) ) ;
FCsvProfiler : : RecordCustomStat ( Pair . Key , CSV_CATEGORY_INDEX ( MeshDrawCommandStats ) , IntCastChecked < int32 > ( Pair . Value ) , ECsvCustomStatOp : : Set ) ;
}
// Output Untracked totals as a single bucket
uint64 TotalUntracked = 0 ;
for ( const TPair < FName , uint64 > & Pair : UntrackedPrimitives )
{
TotalUntracked + = Pair . Value ;
}
const char * Name = " Untracked " ;
TRACE_CSV_PROFILER_INLINE_STAT ( Name , CSV_CATEGORY_INDEX ( MeshDrawCommandStats ) ) ;
FCsvProfiler : : RecordCustomStat ( Name , CSV_CATEGORY_INDEX ( MeshDrawCommandStats ) , IntCastChecked < int32 > ( TotalUntracked ) , ECsvCustomStatOp : : Set ) ;
}
# endif
2023-07-31 17:25:08 -04:00
}
void FMeshDrawCommandStatsManager : : DumpStats ( FFrameData * FrameData )
{
const FString Filename = FString : : Printf ( TEXT ( " %sMeshDrawCommandStats-%s.csv " ) , * FPaths : : ProfilingDir ( ) , * FDateTime : : Now ( ) . ToString ( ) ) ;
FArchive * CSVFile = IFileManager : : Get ( ) . CreateFileWriter ( * Filename , FILEWRITE_AllowRead ) ;
if ( CSVFile = = nullptr )
{
return ;
}
struct FStatEntry
{
FName PassName ;
2023-09-08 14:27:36 -04:00
int32 VisibilePrimitiveCount = 0 ;
int32 VisibleInstance = 0 ;
2023-08-03 20:04:19 -04:00
FName CategoryName ;
2023-07-31 17:25:08 -04:00
FName ResourceName ;
2023-09-08 14:27:36 -04:00
int32 LODIndex = 0 ;
int32 SegmentIndex = 0 ;
2023-07-31 17:25:08 -04:00
FString MaterialName ;
2023-09-08 14:27:36 -04:00
int32 PrimitiveCount = 0 ;
int32 TotalInstanceCount = 0 ;
int32 TotalPrimitiveCount = 0 ;
2023-07-31 17:25:08 -04:00
} ;
TArray < FStatEntry > StatEntries ;
for ( FMeshDrawCommandPassStats * PassStats : FrameData - > PassData )
{
for ( FVisibleMeshDrawCommandStatsData & DrawData : PassStats - > DrawData )
{
if ( DrawData . VisibleInstanceCount > 0 )
{
FStatEntry & StatEntry = StatEntries . Add_GetRef ( FStatEntry ( ) ) ;
StatEntry . PassName = PassStats - > PassName ;
StatEntry . VisibilePrimitiveCount = DrawData . VisibleInstanceCount * DrawData . PrimitiveCount ;
StatEntry . VisibleInstance = DrawData . VisibleInstanceCount ;
StatEntry . PrimitiveCount = DrawData . PrimitiveCount ;
StatEntry . TotalInstanceCount = DrawData . TotalInstanceCount ;
2023-08-02 19:48:00 -04:00
StatEntry . TotalPrimitiveCount = DrawData . TotalInstanceCount * DrawData . PrimitiveCount ;
2023-09-08 14:27:36 -04:00
StatEntry . CategoryName = DrawData . StatsData . CategoryName ;
# if MESH_DRAW_COMMAND_DEBUG_DATA
StatEntry . LODIndex = DrawData . LODIndex ;
StatEntry . SegmentIndex = DrawData . SegmentIndex ;
2023-07-31 17:25:08 -04:00
StatEntry . ResourceName = DrawData . ResourceName ;
StatEntry . MaterialName = DrawData . MaterialName ;
2023-09-08 14:27:36 -04:00
# endif
2023-07-31 17:25:08 -04:00
}
}
}
Algo : : Sort ( StatEntries , [ this ] ( FStatEntry & LHS , FStatEntry & RHS )
{
// first by pass
if ( LHS . PassName ! = RHS . PassName )
{
return LHS . PassName . ToString ( ) < RHS . PassName . ToString ( ) ;
}
// then by visible primitive count
return LHS . VisibilePrimitiveCount > RHS . VisibilePrimitiveCount ;
} ) ;
2023-08-03 20:04:19 -04:00
const TCHAR * Header = TEXT ( " Pass,VisiblePrimitiveCount,VisibleInstances,Category,ResourceName,LODIndex,SegmentIndex,MaterialName,PrimitiveCount,TotalInstanceCount,TotalPrimitiveCount \n " ) ;
2023-07-31 17:25:08 -04:00
CSVFile - > Serialize ( TCHAR_TO_ANSI ( Header ) , FPlatformString : : Strlen ( Header ) ) ;
TCHAR PassNameBuffer [ FName : : StringBufferSize ] ;
TCHAR ResourceNameBuffer [ FName : : StringBufferSize ] ;
TCHAR CategoryBuffer [ FName : : StringBufferSize ] ;
for ( FStatEntry & StatEntry : StatEntries )
{
StatEntry . PassName . ToString ( PassNameBuffer ) ;
2023-08-03 20:04:19 -04:00
StatEntry . CategoryName . ToString ( CategoryBuffer ) ;
2023-07-31 17:25:08 -04:00
StatEntry . ResourceName . ToString ( ResourceNameBuffer ) ;
2023-08-03 20:04:19 -04:00
FString Row = FString : : Printf ( TEXT ( " %s,%d,%d,%s,%s,%d,%d,%s,%d,%d,%d \n " ) ,
2023-07-31 17:25:08 -04:00
PassNameBuffer ,
StatEntry . VisibilePrimitiveCount ,
StatEntry . VisibleInstance ,
CategoryBuffer ,
ResourceNameBuffer ,
StatEntry . LODIndex ,
StatEntry . SegmentIndex ,
* StatEntry . MaterialName ,
StatEntry . PrimitiveCount ,
StatEntry . TotalInstanceCount ,
2023-08-02 19:48:00 -04:00
StatEntry . TotalPrimitiveCount ) ;
2023-07-31 17:25:08 -04:00
CSVFile - > Serialize ( TCHAR_TO_ANSI ( * Row ) , Row . Len ( ) ) ;
}
delete CSVFile ;
CSVFile = nullptr ;
}
2023-08-02 19:48:00 -04:00
static FAutoConsoleCommand GDumpMeshDrawCommandStatsCmd (
TEXT ( " r.MeshDrawCommands.DumpStats " ) ,
TEXT ( " Dumps all of the Mesh Draw Command stats for a single frame to a csv file in the saved profile directory. \n " ) ,
FConsoleCommandDelegate : : CreateStatic ( [ ] ( )
2023-07-31 17:25:08 -04:00
{
if ( FMeshDrawCommandStatsManager * Instance = FMeshDrawCommandStatsManager : : Get ( ) )
{
Instance - > RequestDumpStats ( ) ;
}
} ) ) ;
2023-09-08 14:27:36 -04:00
# endif // MESH_DRAW_COMMAND_STATS