Files
UnrealEngine/Engine/Source/Developer/TraceInsights/Private/Insights/TimingProfiler/Tracks/RegionsTimingTrack.cpp
2025-05-18 13:04:45 +08:00

356 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RegionsTimingTrack.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "HAL/PlatformApplicationMisc.h"
// TraceServices
#include "Common/ProviderLock.h"
#include "TraceServices/Model/Regions.h"
// TraceInsightsCore
#include "InsightsCore/Common/Log.h"
#include "InsightsCore/Common/TimeUtils.h"
#include "InsightsCore/Filter/ViewModels/FilterConfigurator.h"
#include "InsightsCore/Filter/ViewModels/Filters.h"
// TraceInsights
#include "Insights/Common/InsightsMenuBuilder.h"
#include "Insights/InsightsManager.h"
#include "Insights/InsightsStyle.h"
#include "Insights/ITimingViewSession.h"
#include "Insights/TimingProfilerCommon.h"
#include "Insights/TimingProfiler/ViewModels/TimingRegionsSharedState.h"
#include "Insights/ViewModels/TimingTrackViewport.h"
#include "Insights/ViewModels/TimingViewDrawHelper.h"
#include "Insights/Widgets/STimingView.h"
#define LOCTEXT_NAMESPACE "UE::Insights::TimingProfiler::TimingRegions"
namespace UE::Insights::TimingProfiler
{
////////////////////////////////////////////////////////////////////////////////////////////////////
// FTimingRegionsTrack
////////////////////////////////////////////////////////////////////////////////////////////////////
INSIGHTS_IMPLEMENT_RTTI(FTimingRegionsTrack)
FTimingRegionsTrack::~FTimingRegionsTrack()
{
}
void FTimingRegionsTrack::SetRegionsCategory(const TCHAR* InRegionsCategory)
{
RegionsCategory = InRegionsCategory;
SetName(FString(TEXT("Timing Regions - ")) + InRegionsCategory);
}
void FTimingRegionsTrack::BuildContextMenu(FMenuBuilder& MenuBuilder)
{
FTimingEventsTrack::BuildContextMenu(MenuBuilder);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FTimingRegionsTrack::InitTooltip(FTooltipDrawState& InOutTooltip, const ITimingEvent& InTooltipEvent) const
{
if (InTooltipEvent.CheckTrack(this) && InTooltipEvent.Is<FTimingEvent>())
{
const FTimingEvent& TooltipEvent = InTooltipEvent.As<FTimingEvent>();
auto MatchEvent = [this, &TooltipEvent](double InStartTime, double InEndTime, uint32 InDepth)
{
return InDepth == TooltipEvent.GetDepth()
&& InStartTime == TooltipEvent.GetStartTime()
&& InEndTime == TooltipEvent.GetEndTime();
};
FTimingEventSearchParameters SearchParameters(TooltipEvent.GetStartTime(), TooltipEvent.GetEndTime(), ETimingEventSearchFlags::StopAtFirstMatch, MatchEvent);
FindRegionEvent(SearchParameters, [this, &InOutTooltip, &TooltipEvent](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const TraceServices::FTimeRegion& InRegion)
{
InOutTooltip.Reset();
InOutTooltip.AddTitle(InRegion.Text, FLinearColor::White);
InOutTooltip.AddNameValueTextLine(TEXT("Duration:"), FormatTimeAuto(InRegion.EndTime - InRegion.BeginTime));
InOutTooltip.AddNameValueTextLine(TEXT("Depth:"), FString::FromInt(InRegion.Depth));
if (InRegion.Category)
{
InOutTooltip.AddNameValueTextLine(TEXT("Category:"), InRegion.Category);
}
InOutTooltip.UpdateLayout();
});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FTimingRegionsTrack::BuildDrawState(ITimingEventsTrackDrawStateBuilder& Builder,
const ITimingTrackUpdateContext& Context)
{
const FTimingTrackViewport& Viewport = Context.GetViewport();
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
const TraceServices::IRegionProvider& RegionProvider = TraceServices::ReadRegionProvider(*Session);
TraceServices::FProviderReadScopeLock RegionProviderScopedLock(RegionProvider);
FStopwatch Stopwatch;
Stopwatch.Start();
// We are counting only non-empty lanes, so we can collapse empty ones in the visualization.
int32 CurDepth = 0;
const TraceServices::IRegionTimeline* Timeline = RegionProvider.GetTimelineForCategory(RegionsCategory);
check(Timeline)
Timeline->EnumerateLanes([this, Viewport, &CurDepth, &Builder](const TraceServices::FRegionLane& Lane, const int32 Depth)
{
bool RegionHadEvents = false;
Lane.EnumerateRegions(Viewport.GetStartTime(), Viewport.GetEndTime(), [this, &Builder, &RegionHadEvents, &CurDepth](const TraceServices::FTimeRegion& Region) -> bool
{
RegionHadEvents = true;
uint32 EventColor = SharedState.bColorRegionsByCategory ? FTimingEvent::ComputeEventColor(Region.Category) : FTimingEvent::ComputeEventColor(Region.Text);
Builder.AddEvent(Region.BeginTime, Region.EndTime,CurDepth, Region.Text, 0 , EventColor);
return true;
});
if (RegionHadEvents) CurDepth++;
});
Stopwatch.Stop();
const double TotalTime = Stopwatch.GetAccumulatedTime();
UE_CLOG(TotalTime > 1.0, LogTimingProfiler, Verbose, TEXT("[Regions] Updated draw state in %s."), *FormatTimeAuto(TotalTime));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FTimingRegionsTrack::BuildFilteredDrawState(ITimingEventsTrackDrawStateBuilder& Builder, const ITimingTrackUpdateContext& Context)
{
const TSharedPtr<ITimingEventFilter> EventFilterPtr = Context.GetEventFilter();
if (EventFilterPtr.IsValid() && EventFilterPtr->FilterTrack(*this))
{
bool bFilterOnlyByEventType = false; // this is the most often use case, so the below code tries to optimize it
TCHAR* FilterEventType = 0;
if (EventFilterPtr->Is<FTimingEventFilterByEventType>())
{
bFilterOnlyByEventType = true;
const FTimingEventFilterByEventType& EventFilter = EventFilterPtr->As<FTimingEventFilterByEventType>();
FilterEventType = reinterpret_cast<TCHAR*>(EventFilter.GetEventType());
}
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session.IsValid())
{
const TraceServices::IRegionProvider& RegionProvider = TraceServices::ReadRegionProvider(*Session);
TraceServices::FProviderReadScopeLock RegionProviderScopedLock(RegionProvider);
const FTimingTrackViewport& Viewport = Context.GetViewport();
if (bFilterOnlyByEventType)
{
int32 CurDepth = 0;
const TraceServices::IRegionTimeline* Timeline = RegionProvider.GetTimelineForCategory(RegionsCategory);
check(Timeline)
Timeline->EnumerateLanes([this, Viewport, &CurDepth, &Builder, FilterEventType](const TraceServices::FRegionLane& Lane, const int32 Depth)
{
bool RegionHadEvents = false;
Lane.EnumerateRegions(Viewport.GetStartTime(), Viewport.GetEndTime(), [&Builder, &RegionHadEvents, &CurDepth, FilterEventType](const TraceServices::FTimeRegion& Region) -> bool
{
RegionHadEvents = true;
if (Region.Text == FilterEventType)
{
Builder.AddEvent(Region.BeginTime, Region.EndTime, CurDepth, Region.Text);
}
return true;
});
if (RegionHadEvents) CurDepth++;
});
}
else // generic filter
{
//TODO: if (EventFilterPtr->FilterEvent(TimingEvent))
}
}
}
if (HasCustomFilter())
{
if (!FilterConfigurator.IsValid())
{
return;
}
FFilterContext FilterContext;
FilterContext.SetReturnValueForUnsetFilters(false);
FilterContext.AddFilterData<double>(static_cast<int32>(EFilterField::StartTime), 0.0f);
FilterContext.AddFilterData<double>(static_cast<int32>(EFilterField::EndTime), 0.0f);
FilterContext.AddFilterData<double>(static_cast<int32>(EFilterField::Duration), 0.0f);
FilterContext.AddFilterData<FString>(static_cast<int32>(EFilterField::TrackName), this->GetName());
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session.IsValid())
{
const TraceServices::IRegionProvider& RegionProvider = TraceServices::ReadRegionProvider(*Session);
TraceServices::FProviderReadScopeLock RegionProviderScopedLock(RegionProvider);
const FTimingTrackViewport& Viewport = Context.GetViewport();
int32 CurDepth = 0;
const TraceServices::IRegionTimeline* Timeline = RegionProvider.GetTimelineForCategory(RegionsCategory);
check(Timeline)
Timeline->EnumerateLanes([this, Viewport, &CurDepth, &Builder, &FilterContext](const TraceServices::FRegionLane& Lane, const int32 Depth)
{
bool RegionHadEvents = false;
Lane.EnumerateRegions(Viewport.GetStartTime(), Viewport.GetEndTime(), [&Builder, &RegionHadEvents, &CurDepth, &FilterContext, this](const TraceServices::FTimeRegion& Region) -> bool
{
FilterContext.SetFilterData<double>(static_cast<int32>(EFilterField::StartTime), Region.BeginTime);
FilterContext.SetFilterData<double>(static_cast<int32>(EFilterField::EndTime), Region.EndTime);
FilterContext.SetFilterData<double>(static_cast<int32>(EFilterField::Duration), Region.EndTime - Region.BeginTime);
RegionHadEvents = true;
if (FilterConfigurator->ApplyFilters(FilterContext))
{
Builder.AddEvent(Region.BeginTime, Region.EndTime, CurDepth, Region.Text);
}
return true;
});
if (RegionHadEvents) CurDepth++;
});
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const TSharedPtr<const ITimingEvent> FTimingRegionsTrack::SearchEvent(
const FTimingEventSearchParameters& InSearchParameters) const
{
TSharedPtr<const ITimingEvent> FoundEvent;
FindRegionEvent(InSearchParameters, [this, &FoundEvent](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const TraceServices::FTimeRegion& InEvent)
{
FoundEvent = MakeShared<FTimingEvent>(SharedThis(this), InFoundStartTime, InFoundEndTime, InFoundDepth, reinterpret_cast<uint64>(InEvent.Text));
});
return FoundEvent;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool FTimingRegionsTrack::FindRegionEvent(const FTimingEventSearchParameters& InParameters,
TFunctionRef<void(double, double, uint32, const TraceServices::FTimeRegion&)> InFoundPredicate) const
{
// If the query start time is larger than the end of the session return false.
{
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
if (Session.IsValid() && InParameters.StartTime > Session->GetDurationSeconds())
{
return false;
}
}
FFilterContext FilterConfiguratorContext;
FilterConfiguratorContext.SetReturnValueForUnsetFilters(false);
FilterConfiguratorContext.AddFilterData<double>(static_cast<int32>(EFilterField::StartTime), 0.0f);
FilterConfiguratorContext.AddFilterData<double>(static_cast<int32>(EFilterField::EndTime), 0.0f);
FilterConfiguratorContext.AddFilterData<double>(static_cast<int32>(EFilterField::Duration), 0.0f);
FilterConfiguratorContext.AddFilterData<FString>(static_cast<int32>(EFilterField::TrackName), this->GetName());
FilterConfiguratorContext.AddFilterData<int64>(static_cast<int32>(EFilterField::RegionName), 0);
return TTimingEventSearch<TraceServices::FTimeRegion>::Search(
InParameters,
// Search...
[this](TTimingEventSearch<TraceServices::FTimeRegion>::FContext& InContext)
{
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
const TraceServices::IRegionProvider& RegionProvider = TraceServices::ReadRegionProvider(*Session);
TraceServices::FProviderReadScopeLock RegionProviderScopedLock(RegionProvider);
const TraceServices::IRegionTimeline* Timeline = RegionProvider.GetTimelineForCategory(RegionsCategory);
check(Timeline)
Timeline->EnumerateRegions(InContext.GetParameters().StartTime, InContext.GetParameters().EndTime, [&InContext](const TraceServices::FTimeRegion& Region)
{
InContext.Check(Region.BeginTime, Region.EndTime, Region.Depth, Region);
if (!InContext.ShouldContinueSearching())
{
return false;
}
return true;
});
},
[&FilterConfiguratorContext, &InParameters](double EventStartTime, double EventEndTime, uint32 EventDepth, const TraceServices::FTimeRegion& Region)
{
if (!InParameters.FilterExecutor.IsValid())
{
return true;
}
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
if (Session.IsValid())
{
FilterConfiguratorContext.SetFilterData<double>(static_cast<int32>(EFilterField::StartTime), EventStartTime);
FilterConfiguratorContext.SetFilterData<double>(static_cast<int32>(EFilterField::EndTime), EventEndTime);
FilterConfiguratorContext.SetFilterData<double>(static_cast<int32>(EFilterField::Duration), EventEndTime - EventStartTime);
FilterConfiguratorContext.SetFilterData<int64>(static_cast<int32>(EFilterField::RegionName), reinterpret_cast<int64>(Region.Text));
return InParameters.FilterExecutor->ApplyFilters(FilterConfiguratorContext);
}
return false;
},
[&InFoundPredicate](double InFoundStartTime, double InFoundEndTime, uint32 InFoundDepth, const TraceServices::FTimeRegion& InEvent)
{
InFoundPredicate(InFoundStartTime, InFoundEndTime, InFoundDepth, InEvent);
},
TTimingEventSearch<TraceServices::FTimeRegion>::NoMatch);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FTimingRegionsTrack::SetFilterConfigurator(TSharedPtr<FFilterConfigurator> InFilterConfigurator)
{
if (FilterConfigurator != InFilterConfigurator)
{
FilterConfigurator = InFilterConfigurator;
SetDirtyFlag();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool FTimingRegionsTrack::HasCustomFilter() const
{
return FilterConfigurator.IsValid() && !FilterConfigurator->IsEmpty();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FTimingRegionsTrack::OnClipboardCopyEvent(const ITimingEvent& InSelectedEvent) const
{
if (InSelectedEvent.CheckTrack(this) && InSelectedEvent.Is<FTimingEvent>())
{
const FTimingEvent& TrackEvent = InSelectedEvent.As<FTimingEvent>();
// The pointer should be safe to access because it is stored in the Session string store.
FString EventName(reinterpret_cast<const TCHAR*>(TrackEvent.GetType()));
FTimingEventsTrackDrawStateBuilder::AppendDurationToEventName(EventName, TrackEvent.GetDuration());
FPlatformApplicationMisc::ClipboardCopy(*EventName);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace UE::Insights::TimingProfiler
#undef LOCTEXT_NAMESPACE