#include "IronsPerformanceSubsystem.h" #include "Engine/GameInstance.h" #include "Engine/World.h" #include "CoreGlobals.h" #include "HAL/PlatformTime.h" #include "RenderTimer.h" #include "RHI.h" #include "DynamicRHI.h" #include "RHICommandList.h" namespace { bool Valid(double Value) { return FMath::IsFinite(Value)&&Value>=0; } double CyclesMs(uint32 Cycles) { return Cycles>0?FPlatformTime::ToMilliseconds(Cycles):-1; } } void FIronsPerformanceWindow::Reset() { Samples.Reset(); } void FIronsPerformanceWindow::Add(double End,double Seconds,double Game,double Render,double Rhi,double Gpu) { if(!FMath::IsFinite(End)||!FMath::IsFinite(Seconds)||Seconds<=0)return; // Reject backwards clocks rather than mixing two timelines. if(!Samples.IsEmpty()&&End=2048)Samples.RemoveAt(0,1,EAllowShrinking::No); Samples.Add({End,Seconds,Game,Render,Rhi,Gpu}); } FIronsPerformanceSnapshot FIronsPerformanceWindow::Read(double Now) const { FIronsPerformanceSnapshot Result; double Seconds=0,Sums[4]={};int32 Counts[4]={}; for(const auto& S:Samples){ if(S.End<=Now-1.0||S.End>Now)continue; Seconds+=S.Seconds;++Result.Samples; const double Values[]={S.Game,S.Render,S.Rhi,S.Gpu}; for(int32 I=0;I<4;++I)if(Valid(Values[I])){Sums[I]+=Values[I];++Counts[I];} } if(Result.Samples>0&&Seconds>0){Result.FrameMs=Seconds*1000/Result.Samples;Result.Fps=Result.Samples/Seconds;} double* Values[]={&Result.GameMs,&Result.RenderMs,&Result.RhiMs,&Result.GpuMs}; for(int32 I=0;I<4;++I)if(Counts[I]>0)*Values[I]=Sums[I]/Counts[I]; // Do not keep showing an old GPU/RHI value after the source becomes unavailable. if(!Samples.IsEmpty()){ const auto& Last=Samples.Last(); if(!Valid(Last.Game))Result.GameMs=-1;if(!Valid(Last.Render))Result.RenderMs=-1; if(!Valid(Last.Rhi))Result.RhiMs=-1;if(!Valid(Last.Gpu))Result.GpuMs=-1; } return Result; } void UIronsPerformanceSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection);bInitialized=true; } void UIronsPerformanceSubsystem::Deinitialize() { bInitialized=false;Window.Reset();SampleWorld.Reset();Super::Deinitialize(); } bool UIronsPerformanceSubsystem::IsTickable() const { return bInitialized&&!IsTemplate(); } UWorld* UIronsPerformanceSubsystem::GetTickableGameObjectWorld() const { return GetGameInstance()?GetGameInstance()->GetWorld():nullptr; } TStatId UIronsPerformanceSubsystem::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(IronsPerformance,STATGROUP_Tickables); } void UIronsPerformanceSubsystem::Tick(float DeltaTime) { if(LastFrame==GFrameCounter)return;LastFrame=GFrameCounter; UWorld* World=GetTickableGameObjectWorld();const double Now=FPlatformTime::Seconds(); if(!World||SampleWorld.Get()!=World){ SampleWorld=World;Window.Reset();LastSampleTime=Now;LastPublishTime=Now; const uint64 Revision=Snapshot.Revision+1;Snapshot={};Snapshot.Revision=Revision;Raw={};return; } const double Seconds=Now-LastSampleTime;LastSampleTime=Now; Raw.FrameMs=Seconds*1000;Raw.Fps=Seconds>0?1/Seconds:-1; Raw.GameMs=CyclesMs(GGameThreadTime);Raw.RenderMs=CyclesMs(GRenderThreadTime); Raw.RhiMs=IsRunningRHIInSeparateThread()?CyclesMs(GRHIThreadTime):-1; Raw.GpuMs=GDynamicRHI&&GSupportsTimestampRenderQueries?CyclesMs(RHIGetGPUFrameCycles()):-1; Window.Add(Now,Seconds,Raw.GameMs,Raw.RenderMs,Raw.RhiMs,Raw.GpuMs); if(Now-LastPublishTime>=.25){ const uint64 Revision=Snapshot.Revision+1;Snapshot=Window.Read(Now);Snapshot.Revision=Revision;LastPublishTime=Now; } }