44#include " CoreMinimal.h"
55#include " Containers/Ticker.h"
66#include " GenericPlatform/GenericPlatformFile.h"
7- #include " HAL/X .h"
7+ #include " HAL/PlatformFilemanager .h"
88#include " HAL/PlatformProcess.h"
99#include " Misc/EngineVersion.h"
1010#include " Misc/CoreDelegates.h"
@@ -112,10 +112,12 @@ class FOptickPlugin : public IOptickPlugin
112112 uint64 Convert32bitCPUTimestamp (int64 timestamp) const ;
113113
114114#ifdef OPTICK_UE4_GPU
115+
115116 void OnEndFrameRT ();
116- uint64 ConvertGPUTimestamp (uint64 timestamp);
117+ uint64 ConvertGPUTimestamp (uint64 timestamp, int GPUIndex );
117118
118- FGPUTimingCalibrationTimestamp CalibrationTimestamp;
119+ bool UpdateCalibrationTimestamp (FRealtimeGPUProfilerFrameImpl* Frame, int GPUIndex);
120+ FGPUTimingCalibrationTimestamp CalibrationTimestamps[MAX_NUM_GPUS];
119121#endif
120122
121123public:
@@ -294,8 +296,6 @@ void FOptickPlugin::StartCapture()
294296 if (!IsCapturing)
295297 {
296298#ifdef OPTICK_UE4_GPU
297- CalibrationTimestamp = FGPUTiming::GetCalibrationTimestamp ();
298-
299299 GPUThreadStorage.Reset ();
300300 for (auto & pair : StorageMap)
301301 pair.Value ->Reset ();
@@ -393,7 +393,57 @@ uint64 FOptickPlugin::Convert32bitCPUTimestamp(int64 timestamp) const
393393}
394394
395395#ifdef OPTICK_UE4_GPU
396- #if UE_4_24_OR_LATER
396+
397+ bool FOptickPlugin::UpdateCalibrationTimestamp (FRealtimeGPUProfilerFrameImpl* Frame, int GPUIndex)
398+ {
399+ FGPUTimingCalibrationTimestamp& CalibrationTimestamp = CalibrationTimestamps[GPUIndex];
400+ CalibrationTimestamp = FGPUTimingCalibrationTimestamp{ 0 , 0 };
401+
402+ if (Frame->TimestampCalibrationQuery .IsValid ())
403+ {
404+ #if UE_4_27_OR_LATER
405+ CalibrationTimestamp.GPUMicroseconds = Frame->TimestampCalibrationQuery ->GPUMicroseconds [GPUIndex];
406+ CalibrationTimestamp.CPUMicroseconds = Frame->TimestampCalibrationQuery ->CPUMicroseconds [GPUIndex];
407+ #else
408+ CalibrationTimestamp.GPUMicroseconds = Frame->TimestampCalibrationQuery ->GPUMicroseconds ;
409+ CalibrationTimestamp.CPUMicroseconds = Frame->TimestampCalibrationQuery ->CPUMicroseconds ;
410+ #endif
411+ }
412+
413+ if (CalibrationTimestamp.GPUMicroseconds == 0 || CalibrationTimestamp.CPUMicroseconds == 0 ) // Unimplemented platforms, or invalid on the first frame
414+ {
415+ if (Frame->GpuProfilerEvents .Num () > 1 )
416+ {
417+ // Align CPU and GPU frames
418+ CalibrationTimestamp.GPUMicroseconds = Frame->GpuProfilerEvents [1 ].GetStartResultMicroseconds (0 );
419+ CalibrationTimestamp.CPUMicroseconds = FPlatformTime::ToSeconds64 (Frame->CPUFrameStartTimestamp ) * 1000 * 1000 ;
420+ }
421+ else
422+ {
423+ // Fallback to legacy
424+ CalibrationTimestamp = FGPUTiming::GetCalibrationTimestamp ();
425+ }
426+ }
427+
428+ return CalibrationTimestamp.GPUMicroseconds != 0 && CalibrationTimestamp.CPUMicroseconds != 0 ;
429+ }
430+
431+ struct TimeRange
432+ {
433+ uint64 Start;
434+ uint64 Finish;
435+ bool IsOverlap (TimeRange other) const
436+ {
437+ return !((Finish < other.Start ) || (other.Finish < Start));
438+ }
439+ bool IsValid () const
440+ {
441+ return Start != 0 && Finish != 0 && Finish > Start;
442+ }
443+ TimeRange () : Start(0 ), Finish(0 ) {}
444+ TimeRange (uint64 start, uint64 finish) : Start(start), Finish(finish) {}
445+ };
446+
397447void FOptickPlugin::OnEndFrameRT ()
398448{
399449 FScopeLock ScopeLock (&UpdateCriticalSection);
@@ -418,6 +468,7 @@ void FOptickPlugin::OnEndFrameRT()
418468
419469 if (!Event.GatherQueryResults (RHICmdList))
420470 {
471+
421472#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
422473 UE_LOG (OptickLog, Warning, TEXT (" Query is not ready." ));
423474#endif
@@ -426,162 +477,106 @@ void FOptickPlugin::OnEndFrameRT()
426477 }
427478 }
428479
429- for (int32 i = 1 ; i < NumEventsThisFramePlusOne; ++i)
430- {
431- FRealtimeGPUProfilerEventImpl& Event = Frame->GpuProfilerEvents [i];
432-
433- const FName Name = Event.Name ;
434-
435- Optick::EventDescription* Description = nullptr ;
436-
437- if (Optick::EventDescription** ppDescription = GPUDescriptionMap.Find (Name))
438- {
439- Description = *ppDescription;
440- }
441- else
442- {
443- Description = Optick::EventDescription::CreateShared (TCHAR_TO_ANSI (*Name.ToString ()));
444- GPUDescriptionMap.Add (Name, Description);
445- }
446-
447- uint64 startTimestamp = ConvertGPUTimestamp (Event.StartResultMicroseconds );
448- uint64 endTimestamp = ConvertGPUTimestamp (Event.EndResultMicroseconds );
480+ TArray<TimeRange> EventStack;
449481
450- if (Name == NAME_GPU_Unaccounted)
451- {
452- OPTICK_FRAME_FLIP (Optick::FrameType::GPU, startTimestamp);
482+ if (NumEventsThisFramePlusOne <= 1 )
483+ return ;
453484
454- if (GPUThreadStorage.LastTimestamp != 0 )
455- {
456- OPTICK_STORAGE_POP (GPUThreadStorage.EventStorage , GPUThreadStorage.LastTimestamp );
457- }
458-
459- OPTICK_STORAGE_PUSH (GPUThreadStorage.EventStorage , Optick::GetFrameDescription (Optick::FrameType::GPU), startTimestamp)
460- }
461- else
462- {
463- OPTICK_STORAGE_EVENT (GPUThreadStorage.EventStorage , Description, startTimestamp, endTimestamp);
464- }
465- GPUThreadStorage.LastTimestamp = FMath::Max<uint64>(GPUThreadStorage.LastTimestamp , endTimestamp);
466- }
467- }
468- }
469- }
470- #else
471- void FOptickPlugin::OnEndFrameRT ()
472- {
473- FScopeLock ScopeLock (&UpdateCriticalSection);
485+ // VS TODO: Add MGPU support
486+ uint32 GPUIndex = 0 ;
474487
475- if (!IsCapturing || !Optick::IsActive (Optick::Mode::GPU))
476- return ;
488+ // Can't collect GPU data without valid calibration between CPU and GPU timestamps
489+ if (!UpdateCalibrationTimestamp (Frame, GPUIndex))
490+ return ;
477491
478- QUICK_SCOPE_CYCLE_COUNTER (STAT_FOptickPlugin_UpdRT );
492+ uint64 lastTimeStamp = FMath::Max (CalibrationTimestamps[GPUIndex]. CPUMicroseconds , GPUThreadStorage. LastTimestamp );
479493
480- if (FRealtimeGPUProfilerImpl* gpuProfiler = reinterpret_cast <FRealtimeGPUProfilerImpl*>(FRealtimeGPUProfiler::Get ()))
481- {
482- if (FRealtimeGPUProfilerFrameImpl* Frame = gpuProfiler->Frames [gpuProfiler->ReadBufferIndex ])
483- {
484- FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList ();
494+ const FRealtimeGPUProfilerEventImpl& FirstEvent = Frame->GpuProfilerEvents [1 ];
495+ uint64 frameStartTimestamp = FMath::Max (ConvertGPUTimestamp (FirstEvent.GetStartResultMicroseconds (GPUIndex), GPUIndex), lastTimeStamp);
485496
486- bool bAnyEventFailed = false ;
487- bool bAllQueriesAllocated = true ;
497+ OPTICK_FRAME_FLIP (Optick::FrameType::GPU, frameStartTimestamp) ;
498+ OPTICK_STORAGE_PUSH (GPUThreadStorage. EventStorage , Optick::GetFrameDescription (Optick::FrameType::GPU), frameStartTimestamp)
488499
489- for (int i = 0 ; i < Frame-> GpuProfilerEvents . Num () ; ++i )
500+ for (int32 Idx = 1 ; Idx < NumEventsThisFramePlusOne ; ++Idx )
490501 {
491- FRealtimeGPUProfilerEventImpl* Event = Frame->GpuProfilerEvents [i];
492- check (Event != nullptr );
502+ const FRealtimeGPUProfilerEventImpl& Event = Frame->GpuProfilerEvents [Idx];
493503
494- if (! Event-> HasValidResult ( ))
504+ if (Event. GetGPUMask (). Contains (GPUIndex ))
495505 {
496- Event->GatherQueryResults (RHICmdList);
497- }
506+ const FName Name = Event.Name ;
498507
499- if (!Event->HasValidResult ())
500- {
501- #if UE_BUILD_DEBUG
502- UE_LOG (OptickLog, Warning, TEXT (" Query '%s' not ready." ), *Event->GetName ().ToString ());
503- #endif
504- // The frame isn't ready yet. Don't update stats - we'll try again next frame.
505- bAnyEventFailed = true ;
506- continue ;
507- }
508+ if (Name == NAME_GPU_Unaccounted)
509+ continue ;
508510
509- if (!Event->HasQueriesAllocated ())
510- {
511- bAllQueriesAllocated = false ;
512- }
513- }
514-
515- if (bAnyEventFailed)
516- {
517- return ;
518- }
511+ Optick::EventDescription* Description = nullptr ;
519512
520- if (!bAllQueriesAllocated)
521- {
522- static bool bWarned = false ;
513+ if (Optick::EventDescription** ppDescription = GPUDescriptionMap.Find (Name))
514+ {
515+ Description = *ppDescription;
516+ }
517+ else
518+ {
519+ Description = Optick::EventDescription::CreateShared (TCHAR_TO_ANSI (*Name.ToString ()));
520+ GPUDescriptionMap.Add (Name, Description);
521+ }
523522
524- if (!bWarned)
525- {
526- bWarned = true ;
527- UE_LOG (OptickLog, Warning, TEXT (" Ran out of GPU queries! Results for this frame will be incomplete" ));
528- }
529- }
523+ uint64 startTimestamp = ConvertGPUTimestamp (Event.GetStartResultMicroseconds (GPUIndex), GPUIndex);
524+ uint64 endTimestamp = ConvertGPUTimestamp (Event.GetEndResultMicroseconds (GPUIndex), GPUIndex);
530525
526+ // Fixing potential errors
527+ startTimestamp = FMath::Max (startTimestamp, lastTimeStamp);
528+ endTimestamp = FMath::Max (endTimestamp, startTimestamp);
531529
532- for (int i = 0 ; i < Frame->GpuProfilerEvents .Num (); ++i)
533- {
534- FRealtimeGPUProfilerEventImpl* Event = Frame->GpuProfilerEvents [i];
535- check (Event != nullptr );
530+ // Ensuring correct hierarchy
531+ while (EventStack.Num () && (EventStack.Last ().Finish <= startTimestamp))
532+ EventStack.Pop ();
536533
537- const FName Name = Event->Name ;
534+ // Discovered broken hierarchy, skipping event
535+ if (EventStack.Num () && (endTimestamp < EventStack.Last ().Start ))
536+ continue ;
538537
539- Optick::EventDescription* Description = nullptr ;
538+ // Clamp range against the parent counter
539+ if (EventStack.Num ())
540+ {
541+ TimeRange parent = EventStack.Last ();
542+ startTimestamp = FMath::Clamp (startTimestamp, parent.Start , parent.Finish );
543+ endTimestamp = FMath::Clamp (endTimestamp, parent.Start , parent.Finish );
544+ }
540545
541- if (Optick::EventDescription** ppDescription = GPUDescriptionMap.Find (Name))
542- {
543- Description = *ppDescription;
544- }
545- else
546- {
547- Description = Optick::EventDescription::CreateShared (TCHAR_TO_ANSI (*Name.ToString ()));
548- GPUDescriptionMap.Add (Name, Description);
549- }
546+ // Ignore invalid events
547+ if (startTimestamp == endTimestamp)
548+ continue ;
550549
551- uint64 startTimestamp = ConvertGPUTimestamp (Event->StartResultMicroseconds );
552- uint64 endTimestamp = ConvertGPUTimestamp (Event->EndResultMicroseconds );
550+ // if (Name == NAME_GPU_Unaccounted)
551+ // {
552+ // OPTICK_FRAME_FLIP(Optick::FrameType::GPU, startTimestamp);
553553
554- if (Name == NAME_GPU_Unaccounted)
555- {
556- OPTICK_FRAME_FLIP (Optick::FrameType::GPU, startTimestamp);
557554
558- if (GPUThreadStorage.LastTimestamp != 0 )
555+ // OPTICK_STORAGE_PUSH(GPUThreadStorage.EventStorage, Optick::GetFrameDescription(Optick::FrameType::GPU), startTimestamp)
556+ // }
557+ // else
559558 {
560- OPTICK_STORAGE_POP (GPUThreadStorage.EventStorage , GPUThreadStorage.LastTimestamp );
559+ EventStack.Add (TimeRange (startTimestamp, endTimestamp));
560+ OPTICK_STORAGE_EVENT (GPUThreadStorage.EventStorage , Description, startTimestamp, endTimestamp);
561561 }
562-
563- OPTICK_STORAGE_PUSH (GPUThreadStorage.EventStorage , Optick::GetFrameDescription (Optick::FrameType::GPU), startTimestamp)
564- }
565- else
566- {
567- OPTICK_STORAGE_EVENT (GPUThreadStorage.EventStorage , Description, startTimestamp, endTimestamp);
562+ lastTimeStamp = FMath::Max<uint64>(lastTimeStamp, endTimestamp);
568563 }
569- GPUThreadStorage.LastTimestamp = FMath::Max<uint64>(GPUThreadStorage.LastTimestamp , endTimestamp);
570564 }
565+
566+ OPTICK_STORAGE_POP (GPUThreadStorage.EventStorage , lastTimeStamp);
567+ GPUThreadStorage.LastTimestamp = lastTimeStamp;
571568 }
572569 }
573570}
574- #endif
575571
576- uint64 FOptickPlugin::ConvertGPUTimestamp (uint64 timestamp)
572+ uint64 FOptickPlugin::ConvertGPUTimestamp (uint64 timestamp, int GPUIndex )
577573{
578- if (CalibrationTimestamp .CPUMicroseconds == 0 || CalibrationTimestamp .GPUMicroseconds == 0 )
574+ if (CalibrationTimestamps[GPUIndex] .CPUMicroseconds == 0 || CalibrationTimestamps[GPUIndex] .GPUMicroseconds == 0 )
579575 {
580- CalibrationTimestamp.CPUMicroseconds = uint64 (FPlatformTime::ToSeconds64 (FPlatformTime::Cycles64 ()) * 1e6 );
581- CalibrationTimestamp.GPUMicroseconds = timestamp;
576+ return (uint64)-1 ;
582577 }
583578
584- const uint64 cpuTimestampUs = timestamp - CalibrationTimestamp .GPUMicroseconds + CalibrationTimestamp .CPUMicroseconds ;
579+ const uint64 cpuTimestampUs = timestamp - CalibrationTimestamps[GPUIndex] .GPUMicroseconds + CalibrationTimestamps[GPUIndex] .CPUMicroseconds ;
585580 const uint64 cpuTimestamp = cpuTimestampUs * 1e-6 / FPlatformTime::GetSecondsPerCycle64 ();
586581 return cpuTimestamp;
587582}
@@ -639,13 +634,22 @@ void FOptickPlugin::GetDataFromStatsThread(int64 CurrentFrame)
639634 const FName groupName = Item.NameAndInfo .GetGroupName ();
640635
641636 uint32 color = 0 ;
637+ uint32 filter = 0 ;
642638
643639 if (NAME_STATGROUP_CPUStalls == groupName)
644- color = Optick::Color::White;
640+ {
641+ color = Optick::Color::Tomato;
642+ filter = Optick::Filter::Wait;
643+ }
645644
646645 for (int i = 0 ; i < sizeof (NAME_Wait) / sizeof (NAME_Wait[0 ]); ++i)
646+ {
647647 if (NAME_Wait[i] == shortName)
648+ {
648649 color = Optick::Color::White;
650+ filter = Optick::Filter::Wait;
651+ }
652+ }
649653
650654 Description = Optick::EventDescription::CreateShared (TCHAR_TO_ANSI (*shortName.ToString ()), nullptr , 0 , color);
651655
0 commit comments