// Copyright Epic Games, Inc. All Rights Reserved. #include "TimingGraphTrack.h" // TraceInsightsCore #include "InsightsCore/Common/PaintUtils.h" #include "InsightsCore/Common/TimeUtils.h" // TraceInsights #include "Insights/InsightsManager.h" #include "Insights/TimingProfiler/GraphTracks/CounterGraphSeries.h" #include "Insights/TimingProfiler/GraphTracks/FrameGraphSeries.h" #include "Insights/TimingProfiler/GraphTracks/FrameStatsTimerGraphSeries.h" #include "Insights/TimingProfiler/GraphTracks/TimerGraphSeries.h" #include "Insights/TimingProfiler/TimingProfilerManager.h" #include "Insights/TimingProfiler/Tracks/ThreadTimingTrack.h" #include "Insights/TimingProfiler/Widgets/STimingProfilerWindow.h" #include "Insights/ViewModels/AxisViewportDouble.h" #include "Insights/Widgets/STimingView.h" #include #define LOCTEXT_NAMESPACE "UE::Insights::TimingProfiler::FTimingGraphTrack" namespace UE::Insights::TimingProfiler { //////////////////////////////////////////////////////////////////////////////////////////////////// // FTimingGraphTrack //////////////////////////////////////////////////////////////////////////////////////////////////// INSIGHTS_IMPLEMENT_RTTI(FTimingGraphTrack) //////////////////////////////////////////////////////////////////////////////////////////////////// FTimingGraphTrack::FTimingGraphTrack(TSharedPtr InTimingView) : FGraphTrack() , TimingView(InTimingView) { LoadDefaultSettings(); // Add non editable options. EnabledOptions |= EGraphOptions::ShowBaseline | EGraphOptions::ShowThresholds | EGraphOptions::ShowVerticalAxisGrid | EGraphOptions::ShowHeader; bNotifyTimersOnDestruction = InTimingView->GetName() == FInsightsManagerTabs::TimingProfilerTabId; } //////////////////////////////////////////////////////////////////////////////////////////////////// FTimingGraphTrack::~FTimingGraphTrack() { if (OnTrackVisibilityChangedHandle.IsValid()) { UnregisterTimingViewCallbacks(); } if (GameFrameSeriesVisibilityHandle.IsValid()) { TSharedPtr GameFramesSeries = GetFrameSeries(ETraceFrameType::TraceFrameType_Game); if (GameFramesSeries.IsValid()) { GameFramesSeries->VisibilityChangedDelegate.Remove(GameFrameSeriesVisibilityHandle); } } if (RenderingFrameSeriesVisibilityHandle.IsValid()) { TSharedPtr RenderingFramesSeries = GetFrameSeries(ETraceFrameType::TraceFrameType_Rendering); if (RenderingFramesSeries.IsValid()) { RenderingFramesSeries->VisibilityChangedDelegate.Remove(RenderingFrameSeriesVisibilityHandle); } } if (bNotifyTimersOnDestruction) { TSharedPtr ProfilerWindow = FTimingProfilerManager::Get()->GetProfilerWindow(); if (ProfilerWindow.IsValid()) { TArray TimerIds; for (TSharedPtr& Series : AllSeries) { if (Series->Is()) { TimerIds.Add(Series->As().GetTimerId()); } else if (Series->Is()) { TimerIds.Add(Series->As().GetTimerId()); } } AllSeries.Reset(); for (uint32 TimerId : TimerIds) { ProfilerWindow->OnTimerAddedToGraphsChanged(TimerId); } } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::RegisterTimingViewCallbacks() { TSharedPtr TimingViewPtr = TimingView.Pin(); if (TimingViewPtr.IsValid()) { auto OnTrackAddedRemovedLambda = [this](const TSharedPtr Track) { if (Track->Is()) { // If there are more series than the default frame series. if (AllSeries.Num() > ETraceFrameType::TraceFrameType_Count) { SetDirtyFlag(); } } }; OnTrackAddedHandle = TimingViewPtr->OnTrackAdded().AddLambda(OnTrackAddedRemovedLambda); OnTrackRemovedHandle = TimingViewPtr->OnTrackRemoved().AddLambda(OnTrackAddedRemovedLambda); OnTrackVisibilityChangedHandle = TimingViewPtr->OnTrackVisibilityChanged().AddLambda( [this]() { // If there are more series than the default frame series. if (AllSeries.Num() > ETraceFrameType::TraceFrameType_Count) { SetDirtyFlag(); } }); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::UnregisterTimingViewCallbacks() { TSharedPtr TimingViewPtr = TimingView.Pin(); if (TimingView.IsValid()) { TimingViewPtr->OnTrackAdded().Remove(OnTrackAddedHandle); TimingViewPtr->OnTrackRemoved().Remove(OnTrackRemovedHandle); TimingViewPtr->OnTrackVisibilityChanged().Remove(OnTrackVisibilityChangedHandle); } OnTrackAddedHandle.Reset(); OnTrackRemovedHandle.Reset(); OnTrackVisibilityChangedHandle.Reset(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::Update(const ITimingTrackUpdateContext& Context) { FGraphTrack::Update(Context); if (!OnTrackVisibilityChangedHandle.IsValid()) { RegisterTimingViewCallbacks(); } const bool bIsEntireGraphTrackDirty = IsDirty() || Context.GetViewport().IsHorizontalViewportDirty(); bool bNeedsUpdate = bIsEntireGraphTrackDirty; if (!bNeedsUpdate) { for (TSharedPtr& Series : AllSeries) { if (Series->IsVisible() && Series->IsDirty()) { // At least one series is dirty. bNeedsUpdate = true; break; } } } if (bNeedsUpdate) { ClearDirtyFlag(); NumAddedEvents = 0; const FTimingTrackViewport& Viewport = Context.GetViewport(); for (TSharedPtr& Series : AllSeries) { if (Series->IsVisible() && (bIsEntireGraphTrackDirty || Series->IsDirty()) && Series->Is()) { // Clear the flag before updating, because the update itself may further need to set the series as dirty. Series->ClearDirtyFlag(); Series->As().Update(*this, Viewport); } } UpdateStats(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Frame Series //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::AddDefaultFrameSeries() { TSharedPtr TimingViewPtr = TimingView.Pin(); TSharedRef GameFramesSeries = CreateGameFrameGraphSeries(SharedValueViewport); TSharedRef RenderingFramesSeries = CreateRenderingFrameGraphSeries(SharedValueViewport); if (TimingViewPtr.IsValid() && TimingViewPtr->GetName() == FInsightsManagerTabs::TimingProfilerTabId) { const FInsightsSettings& Settings = UE::Insights::FInsightsManager::Get()->GetSettings(); GameFramesSeries->SetVisibility(Settings.GetTimingViewMainGraphShowGameFrames()); GameFrameSeriesVisibilityHandle = GameFramesSeries->VisibilityChangedDelegate.AddLambda( [](bool bOnOff) { FInsightsSettings& Settings = UE::Insights::FInsightsManager::Get()->GetSettings(); Settings.SetAndSaveTimingViewMainGraphShowGameFrames(bOnOff); }); RenderingFramesSeries->SetVisibility(Settings.GetTimingViewMainGraphShowRenderingFrames()); RenderingFrameSeriesVisibilityHandle = RenderingFramesSeries->VisibilityChangedDelegate.AddLambda( [](bool bOnOff) { FInsightsSettings& Settings = UE::Insights::FInsightsManager::Get()->GetSettings(); Settings.SetAndSaveTimingViewMainGraphShowRenderingFrames(bOnOff); }); } AllSeries.Add(GameFramesSeries.ToSharedPtr()); AllSeries.Add(RenderingFramesSeries.ToSharedPtr()); } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FTimingGraphTrack::GetFrameSeries(ETraceFrameType FrameType) { TSharedPtr* Ptr = AllSeries.FindByPredicate( [FrameType] (const TSharedPtr& Series) { return Series->Is() && Series->As().GetFrameType() == FrameType; }); return (Ptr != nullptr) ? StaticCastSharedPtr(*Ptr) : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// // Timer Series //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FTimingGraphTrack::GetTimerSeries(uint32 TimerId) { TSharedPtr* Ptr = AllSeries.FindByPredicate( [TimerId] (const TSharedPtr& Series) { return Series->Is() && Series->As().GetTimerId() == TimerId; }); return (Ptr != nullptr) ? StaticCastSharedPtr(*Ptr) : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FTimingGraphTrack::AddTimerSeries(uint32 TimerId, FLinearColor Color) { TSharedRef NewSeries = MakeShared(TimerId); FTimerGraphSeries& Series = *NewSeries; Series.SetName(TEXT("")); Series.SetDescription(TEXT("Timer series")); const FLinearColor BorderColor(Color.R + 0.4f, Color.G + 0.4f, Color.B + 0.4f, 1.0f); Series.SetColor(Color, BorderColor); // Use shared viewport. Series.SetBaselineY(SharedValueViewport.GetBaselineY()); Series.SetScaleY(SharedValueViewport.GetScaleY()); Series.EnableSharedViewport(); AllSeries.Add(NewSeries); return NewSeries; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::RemoveTimerSeries(uint32 TimerId) { AllSeries.RemoveAll( [TimerId] (const TSharedPtr& Series) { return Series->Is() && Series->As().GetTimerId() == TimerId; }); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Frame Stats Timer Series //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FTimingGraphTrack::GetFrameStatsTimerSeries(uint32 TimerId, ETraceFrameType FrameType) { TSharedPtr* Ptr = AllSeries.FindByPredicate( [TimerId, FrameType] (const TSharedPtr& Series) { return Series->Is() && Series->As().GetTimerId() == TimerId && Series->As().GetFrameType() == FrameType; }); return (Ptr != nullptr) ? StaticCastSharedPtr(*Ptr) : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FTimingGraphTrack::AddFrameStatsTimerSeries(uint32 TimerId, ETraceFrameType FrameType, FLinearColor Color) { TSharedRef NewSeries = MakeShared(TimerId, FrameType); FFrameStatsTimerGraphSeries& Series = *NewSeries; Series.SetName(TEXT("")); Series.SetDescription(TEXT("Frame Stats Timer series")); const FLinearColor BorderColor(Color.R + 0.4f, Color.G + 0.4f, Color.B + 0.4f, 1.0f); Series.SetColor(Color, BorderColor); // Use shared viewport. Series.SetBaselineY(SharedValueViewport.GetBaselineY()); Series.SetScaleY(SharedValueViewport.GetScaleY()); Series.EnableSharedViewport(); AllSeries.Add(NewSeries); return NewSeries; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::RemoveFrameStatsTimerSeries(uint32 TimerId, ETraceFrameType FrameType) { AllSeries.RemoveAll( [TimerId, FrameType] (const TSharedPtr& Series) { return Series->Is() && Series->As().GetTimerId() == TimerId && Series->As().GetFrameType() == FrameType; }); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Counter Series //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FTimingGraphTrack::GetStatsCounterSeries(uint32 CounterId) { TSharedPtr* Ptr = AllSeries.FindByPredicate( [CounterId] (const TSharedPtr& Series) { return Series->Is() && Series->As().GetCounterId() == CounterId; }); return (Ptr != nullptr) ? StaticCastSharedPtr(*Ptr) : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FTimingGraphTrack::AddStatsCounterSeries(uint32 CounterId, FLinearColor Color) { TSharedRef NewSeries = MakeShared(CounterId); FCounterGraphSeries& Series = *NewSeries; Series.SetName(TEXT("")); Series.SetDescription(TEXT("Stats counter series")); FLinearColor BorderColor(Color.R + 0.4f, Color.G + 0.4f, Color.B + 0.4f, 1.0f); Series.SetColor(Color, BorderColor); Series.SetBaselineY(GetHeight() - 1.0f); Series.SetScaleY(1.0); Series.EnableAutoZoom(); Series.InitFromProvider(); AllSeries.Add(NewSeries); return NewSeries; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::RemoveStatsCounterSeries(uint32 CounterId) { AllSeries.RemoveAll( [CounterId] (const TSharedPtr& Series) { return Series->Is() && Series->As().GetCounterId() == CounterId; }); } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FTimingGraphTrack::GetThreadTimingSharedState() const { TSharedPtr TimingViewPtr = TimingView.Pin(); if (!TimingViewPtr.IsValid()) { return nullptr; } return TimingViewPtr->GetThreadTimingSharedState(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::GetVisibleTimelineIndexes(TSet& TimelineIndexes) { TSharedPtr ThreadSharedState = GetThreadTimingSharedState(); if (ThreadSharedState) { ThreadSharedState->GetVisibleTimelineIndexes(TimelineIndexes); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::GetVisibleCpuSamplingThreads(TSet& Threads) { TSharedPtr ThreadSharedState = GetThreadTimingSharedState(); if (ThreadSharedState) { ThreadSharedState->GetVisibleCpuSamplingThreads(Threads); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::ContextMenu_ToggleOption_Execute(EGraphOptions Option) { FGraphTrack::ContextMenu_ToggleOption_Execute(Option); TSharedPtr TimingViewPtr = TimingView.Pin(); if (!TimingViewPtr.IsValid()) { return; } if (TimingViewPtr->GetName() != FInsightsManagerTabs::TimingProfilerTabId) { return; } FInsightsSettings& Settings = UE::Insights::FInsightsManager::Get()->GetSettings(); if (EnumHasAnyFlags(Option, EGraphOptions::ShowPoints)) { Settings.SetAndSaveTimingViewMainGraphShowPoints(EnumHasAnyFlags(EnabledOptions, EGraphOptions::ShowPoints)); } if (EnumHasAnyFlags(Option, EGraphOptions::ShowPointsWithBorder)) { Settings.SetAndSaveTimingViewMainGraphShowPointsWithBorder(EnumHasAnyFlags(EnabledOptions, EGraphOptions::ShowPointsWithBorder)); } if (EnumHasAnyFlags(Option, EGraphOptions::ShowLines)) { Settings.SetAndSaveTimingViewMainGraphShowConnectedLines(EnumHasAnyFlags(EnabledOptions, EGraphOptions::ShowLines)); } if (EnumHasAnyFlags(Option, EGraphOptions::ShowPolygon)) { Settings.SetAndTimingViewMainGraphShowPolygons(EnumHasAnyFlags(EnabledOptions, EGraphOptions::ShowPolygon)); } if (EnumHasAnyFlags(Option, EGraphOptions::UseEventDuration)) { Settings.SetAndSaveTimingViewMainGraphShowEventDuration(EnumHasAnyFlags(EnabledOptions, EGraphOptions::UseEventDuration)); } if (EnumHasAnyFlags(Option, EGraphOptions::ShowBars)) { Settings.SetAndSaveTimingViewMainGraphShowBars(EnumHasAnyFlags(EnabledOptions, EGraphOptions::ShowBars)); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::LoadDefaultSettings() { TSharedPtr TimingViewPtr = TimingView.Pin(); if (TimingViewPtr.IsValid() && TimingViewPtr->GetName() == FInsightsManagerTabs::TimingProfilerTabId) { const FInsightsSettings& Settings = UE::Insights::FInsightsManager::Get()->GetSettings(); if (Settings.GetTimingViewMainGraphShowPoints()) { EnabledOptions |= EGraphOptions::ShowPoints; } if (Settings.GetTimingViewMainGraphShowPointsWithBorder()) { EnabledOptions |= EGraphOptions::ShowPointsWithBorder; } if (Settings.GetTimingViewMainGraphShowConnectedLines()) { EnabledOptions |= EGraphOptions::ShowLines; } if (Settings.GetTimingViewMainGraphShowPolygons()) { EnabledOptions |= EGraphOptions::ShowPolygon; } if (Settings.GetTimingViewMainGraphShowEventDuration()) { EnabledOptions |= EGraphOptions::UseEventDuration; } if (Settings.GetTimingViewMainGraphShowBars()) { EnabledOptions |= EGraphOptions::ShowBars; } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void FTimingGraphTrack::DrawVerticalAxisGrid(const ITimingTrackDrawContext& Context) const { FTimingGraphSeries* FirstTimeUnitSeries = nullptr; for (const TSharedPtr& Series : AllSeries) { if (Series->IsVisible() && Series->Is() && Series->As().IsTimeUnit()) { FirstTimeUnitSeries = &Series->As(); break; } } if (!FirstTimeUnitSeries) { return; } FAxisViewportDouble ViewportY; ViewportY.SetSize(GetHeight()); ViewportY.SetScaleLimits(std::numeric_limits::min(), std::numeric_limits::max()); ViewportY.SetScale(SharedValueViewport.GetScaleY()); ViewportY.ScrollAtPos(static_cast(SharedValueViewport.GetBaselineY()) - GetHeight()); const float ViewWidth = Context.GetViewport().GetWidth(); const float RoundedViewHeight = FMath::RoundToFloat(GetHeight()); const float X0 = ViewWidth - 12.0f; // let some space for the vertical scrollbar const float Y0 = GetPosY(); constexpr float MinDY = 32.0f; // min vertical distance between horizontal grid lines constexpr float TextH = 14.0f; // label height UE::Insights::FDrawContext& DrawContext = Context.GetDrawContext(); const FSlateBrush* Brush = Context.GetHelper().GetWhiteBrush(); //const FSlateFontInfo& Font = Context.GetHelper().GetEventFont(); const TSharedRef FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); const float FontScale = DrawContext.Geometry.Scale; const double TopValue = ViewportY.GetValueAtOffset(RoundedViewHeight); const double GridValue = ViewportY.GetValueAtOffset(MinDY); const double BottomValue = ViewportY.GetValueAtOffset(0.0f); const double Delta = GridValue - BottomValue; if (Delta > 0.0) { const double Thresholds[] = { 1.0e-9, // 1ns 1.0e-8, // 10ns 1.0e-7, // 100ns 1.0e-6, // 1us 1.0e-5, // 10us 0.0001, // 100us 0.001, // 1ms 0.01, // 10ms 0.1, // 100ms 1.0, // 1s 10.0, // 10s 60.0, // 1m 600.0, // 10m 3600.0, // 1h 36000.0,// 10h 86400.0 // 1d }; constexpr int32 NumThresholds = sizeof(Thresholds) / sizeof(double); int32 Index = static_cast(Algo::LowerBound(Thresholds, Delta)); if (Index > 0) { Index--; } double TickUnit = Thresholds[Index]; int64 DeltaTicks = static_cast(FMath::CeilToDouble(Delta / TickUnit)); if (Index < NumThresholds - 1) { const double NextTickUnit = Thresholds[Index + 1]; if (NextTickUnit <= static_cast(DeltaTicks + 1) * TickUnit) { TickUnit = NextTickUnit; DeltaTicks = 1; } else if (DeltaTicks != 1 && DeltaTicks != 5 && DeltaTicks % 2 == 1) // prefer even grid values { DeltaTicks++; } } const double Grid = static_cast(DeltaTicks) * TickUnit; const double StartValue = FMath::GridSnap(BottomValue, Grid); const FLinearColor GridColor(0.0f, 0.0f, 0.0f, 0.1f); const FLinearColor TextBgColor(0.05f, 0.05f, 0.05f, 1.0f); const FLinearColor TextColor = FirstTimeUnitSeries->GetColor().CopyWithNewOpacity(1.0f); for (double Value = StartValue; Value < TopValue; Value += Grid) { const float Y = Y0 + RoundedViewHeight - FMath::RoundToFloat(ViewportY.GetOffsetForValue(Value)); const FString LabelText = UE::Insights::FormatTimeAuto(Value); // Draw horizontal grid line. DrawContext.DrawBox(0, Y, ViewWidth, 1, Brush, GridColor); const FVector2D LabelTextSize = FontMeasureService->Measure(LabelText, Font, FontScale) / FontScale; const float LabelX = X0 - static_cast(LabelTextSize.X) - 4.0f; const float LabelY = FMath::Min(Y0 + GetHeight() - TextH, FMath::Max(Y0, Y - TextH / 2)); // Draw background for value text. DrawContext.DrawBox(LabelX, LabelY, static_cast(LabelTextSize.X) + 4.0f, TextH, Brush, TextBgColor); // Draw value text. DrawContext.DrawText(LabelX + 2.0f, LabelY + 1.0f, LabelText, Font, TextColor); } DrawContext.LayerId++; } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool FTimingGraphTrack::HasAnySeriesForTimer(uint32 TimerId) const { for (const TSharedPtr& Series : AllSeries) { if (Series->Is() && Series->As().IsTimer(TimerId)) { return true; } } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// uint32 FTimingGraphTrack::GetNumSeriesForTimer(uint32 TimerId) const { uint32 NumSeries = 0; for (const TSharedPtr& Series : AllSeries) { if (Series->Is() && Series->As().IsTimer(TimerId)) { ++NumSeries; } } return NumSeries; } //////////////////////////////////////////////////////////////////////////////////////////////////// } // namespace UE::Insights::TimingProfiler #undef LOCTEXT_NAMESPACE