2232 lines
68 KiB
C++
2232 lines
68 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SFrameTrack.h"
|
|
|
|
#include "Fonts/FontMeasure.h"
|
|
#include "Fonts/SlateFontInfo.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "HAL/PlatformTime.h"
|
|
#include "Misc/StringBuilder.h"
|
|
#include "Rendering/DrawElements.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SEditableTextBox.h"
|
|
#include "Widgets/Layout/SScrollBar.h"
|
|
|
|
// TraceServices
|
|
#include "TraceServices/Model/Frames.h"
|
|
#include "TraceServices/Model/TimingProfiler.h"
|
|
|
|
// TraceInsightsCore
|
|
#include "InsightsCore/Common/PaintUtils.h"
|
|
#include "InsightsCore/Common/Stopwatch.h"
|
|
#include "InsightsCore/Common/TimeUtils.h"
|
|
|
|
// TraceInsights
|
|
#include "Insights/InsightsManager.h"
|
|
#include "Insights/InsightsStyle.h"
|
|
#include "Insights/Log.h"
|
|
#include "Insights/TimingProfiler/TimingProfilerManager.h"
|
|
#include "Insights/TimingProfiler/Tracks/ThreadTimingTrack.h"
|
|
#include "Insights/TimingProfiler/ViewModels/FrameStatsHelper.h"
|
|
#include "Insights/TimingProfiler/ViewModels/FrameTrackHelper.h"
|
|
#include "Insights/TimingProfiler/ViewModels/ThreadTimingSharedState.h"
|
|
#include "Insights/TimingProfiler/Widgets/STimingProfilerWindow.h"
|
|
#include "Insights/TimingProfilerCommon.h"
|
|
#include "Insights/Widgets/SLogView.h"
|
|
#include "Insights/Widgets/STimingView.h"
|
|
|
|
#include <limits>
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define LOCTEXT_NAMESPACE "UE::Insights::TimingProfiler::SFrameTrack"
|
|
|
|
namespace UE::Insights::TimingProfiler
|
|
{
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SFrameTrack::SFrameTrack()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SFrameTrack::~SFrameTrack()
|
|
{
|
|
TSharedPtr<STimingProfilerWindow> ProfilerWindow = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
|
|
if (OnTrackVisibilityChangedHandle.IsValid())
|
|
{
|
|
if (ProfilerWindow.IsValid())
|
|
{
|
|
TSharedPtr<STimingView> TimingView = ProfilerWindow->GetTimingView();
|
|
if (TimingView.IsValid())
|
|
{
|
|
if (TimingView.Get() == RegisteredTimingView)
|
|
{
|
|
TimingView->OnTrackVisibilityChanged().Remove(OnTrackVisibilityChangedHandle);
|
|
TimingView->OnTrackAdded().Remove(OnTrackAddedHandle);
|
|
TimingView->OnTrackRemoved().Remove(OnTrackRemovedHandle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ProfilerWindow.IsValid())
|
|
{
|
|
TArray<uint32> TimerIds;
|
|
for (TSharedPtr<FFrameTrackSeries>& Series : AllSeries)
|
|
{
|
|
if (Series.IsValid() &&
|
|
Series->Is<FTimerFrameStatsTrackSeries>())
|
|
{
|
|
TimerIds.Add(Series->As<FTimerFrameStatsTrackSeries>().GetTimerId());
|
|
}
|
|
}
|
|
AllSeries.Reset();
|
|
for (uint32 TimerId : TimerIds)
|
|
{
|
|
ProfilerWindow->OnTimerAddedToGraphsChanged(TimerId);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::Reset()
|
|
{
|
|
const FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
|
|
Viewport.Reset();
|
|
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
ViewportX.SetScaleLimits(0.0001f, 16.0f); // 10000 [sample/px] to 16 [px/sample]
|
|
ViewportX.SetScale(16.0f);
|
|
FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
|
|
ViewportY.SetScaleLimits(0.01, 1000000.0);
|
|
ViewportY.SetScale(1500.0);
|
|
bIsViewportDirty = true;
|
|
|
|
bIsStateDirty = true;
|
|
|
|
bIsAutoZoomEnabled = true;
|
|
AutoZoomViewportPos = ViewportX.GetPos();
|
|
AutoZoomViewportScale = ViewportX.GetScale();
|
|
AutoZoomViewportSize = 0.0f;
|
|
|
|
bZoomTimingViewOnFrameSelection = Settings.IsAutoZoomOnFrameSelectionEnabled();
|
|
|
|
AnalysisSyncNextTimestamp = 0;
|
|
|
|
bShowUpperThresholdLine = Settings.IsShowUpperThresholdLineEnabled();
|
|
bShowLowerThresholdLine = Settings.IsShowLowerThresholdLineEnabled();
|
|
|
|
UpperThresholdTime = FMath::Clamp(Settings.GetUpperThresholdTime(), MinThresholdTime, MaxThresholdTime);
|
|
LowerThresholdTime = FMath::Clamp(Settings.GetLowerThresholdTime(), MinThresholdTime, MaxThresholdTime);
|
|
|
|
bShowUpperThresholdAsFps = Settings.IsShowUpperThresholdAsFpsEnabled();
|
|
bShowLowerThresholdAsFps = Settings.IsShowLowerThresholdAsFpsEnabled();
|
|
|
|
MousePosition = FVector2D::ZeroVector;
|
|
|
|
MousePositionOnButtonDown = FVector2D::ZeroVector;
|
|
ViewportPosXOnButtonDown = 0.0f;
|
|
|
|
MousePositionOnButtonUp = FVector2D::ZeroVector;
|
|
|
|
bIsLMB_Pressed = false;
|
|
bIsRMB_Pressed = false;
|
|
|
|
bIsScrolling = false;
|
|
|
|
bDrawVerticalAxisLabelsOnLeftSide = false;
|
|
|
|
HoveredSample.Reset();
|
|
SelectedSample.Reset();
|
|
TooltipOpacity = 0.0f;
|
|
TooltipSizeX = 70.0f;
|
|
|
|
//ThisGeometry
|
|
|
|
CursorType = ECursorType::Default;
|
|
|
|
NumUpdatedFrames = 0;
|
|
UpdateDurationHistory.Reset();
|
|
DrawDurationHistory.Reset();
|
|
OnPaintDurationHistory.Reset();
|
|
LastOnPaintTime = FPlatformTime::Cycles64();
|
|
|
|
AllSeries.Empty();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::Construct(const FArguments& InArgs)
|
|
{
|
|
ChildSlot
|
|
[
|
|
SNew(SOverlay)
|
|
.Visibility(EVisibility::SelfHitTestInvisible)
|
|
|
|
+ SOverlay::Slot()
|
|
.VAlign(VAlign_Top)
|
|
.Padding(FMargin(0, 0, 0, 0))
|
|
[
|
|
SAssignNew(HorizontalScrollBar, SScrollBar)
|
|
.Orientation(Orient_Horizontal)
|
|
.AlwaysShowScrollbar(false)
|
|
.Visibility(EVisibility::Visible)
|
|
.Thickness(FVector2D(5.0f, 5.0f))
|
|
.RenderOpacity(0.75)
|
|
.OnUserScrolled(this, &SFrameTrack::HorizontalScrollBar_OnUserScrolled)
|
|
]
|
|
];
|
|
|
|
UpdateHorizontalScrollBar();
|
|
|
|
BindCommands();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
|
{
|
|
TSharedPtr<STimingProfilerWindow> TimingWindow = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
if (TimingWindow.IsValid())
|
|
{
|
|
TSharedPtr<STimingView> TimingView = TimingWindow->GetTimingView();
|
|
if (TimingView.IsValid())
|
|
{
|
|
if (!OnTrackVisibilityChangedHandle.IsValid() || TimingView.Get() != RegisteredTimingView)
|
|
{
|
|
RegisteredTimingView = TimingView.Get();
|
|
this->bIsStateDirty = true;
|
|
|
|
auto OnTrackAddedRemovedLamda = [this](const TSharedPtr<const FBaseTimingTrack> Track)
|
|
{
|
|
if (Track->Is<FThreadTimingTrack>())
|
|
{
|
|
// If there are more series than the default frame series.
|
|
if (this->AllSeries.Num() > ETraceFrameType::TraceFrameType_Count)
|
|
{
|
|
this->bIsStateDirty = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
OnTrackAddedHandle = TimingView->OnTrackAdded().AddLambda(OnTrackAddedRemovedLamda);
|
|
OnTrackRemovedHandle = TimingView->OnTrackRemoved().AddLambda(OnTrackAddedRemovedLamda);
|
|
|
|
OnTrackVisibilityChangedHandle = TimingView->OnTrackVisibilityChanged().AddLambda([this]()
|
|
{
|
|
if (this->AllSeries.Num() > ETraceFrameType::TraceFrameType_Count)
|
|
{
|
|
this->bIsStateDirty = true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ThisGeometry != AllottedGeometry || bIsViewportDirty)
|
|
{
|
|
bIsViewportDirty = false;
|
|
const float ViewWidth = static_cast<float>(AllottedGeometry.GetLocalSize().X);
|
|
const float ViewHeight = static_cast<float>(AllottedGeometry.GetLocalSize().Y);
|
|
Viewport.SetSize(ViewWidth, ViewHeight);
|
|
bIsStateDirty = true;
|
|
}
|
|
|
|
ThisGeometry = AllottedGeometry;
|
|
|
|
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
|
|
if (!bIsScrolling)
|
|
{
|
|
// Elastic snap to horizontal limits.
|
|
if (ViewportX.UpdatePosWithinLimits())
|
|
{
|
|
bIsStateDirty = true;
|
|
}
|
|
}
|
|
|
|
// Disable auto-zoom if viewport's position or scale has changed.
|
|
if (AutoZoomViewportPos != ViewportX.GetPos() ||
|
|
AutoZoomViewportScale != ViewportX.GetScale())
|
|
{
|
|
bIsAutoZoomEnabled = false;
|
|
}
|
|
|
|
// Update auto-zoom if viewport size has changed.
|
|
bool bAutoZoom = bIsAutoZoomEnabled && AutoZoomViewportSize != ViewportX.GetSize();
|
|
|
|
const uint64 Time = FPlatformTime::Cycles64();
|
|
if (Time > AnalysisSyncNextTimestamp)
|
|
{
|
|
const uint64 WaitTime = static_cast<uint64>(0.1 / FPlatformTime::GetSecondsPerCycle64()); // 100ms
|
|
AnalysisSyncNextTimestamp = Time + WaitTime;
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid())
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const TraceServices::IFrameProvider& FramesProvider = TraceServices::ReadFrameProvider(*Session.Get());
|
|
|
|
for (int32 FrameType = 0; FrameType < TraceFrameType_Count; ++FrameType)
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> SeriesPtr = FindOrAddSeries(static_cast<ETraceFrameType>(FrameType));
|
|
|
|
int32 NumFrames = static_cast<int32>(FramesProvider.GetFrameCount(static_cast<ETraceFrameType>(FrameType)));
|
|
if (NumFrames > ViewportX.GetMaxValue())
|
|
{
|
|
ViewportX.SetMinMaxInterval(0, NumFrames);
|
|
UpdateHorizontalScrollBar();
|
|
bIsStateDirty = true;
|
|
|
|
if (bIsAutoZoomEnabled)
|
|
{
|
|
bAutoZoom = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAutoZoom)
|
|
{
|
|
AutoZoom();
|
|
}
|
|
|
|
if (bIsStateDirty)
|
|
{
|
|
bIsStateDirty = false;
|
|
UpdateState();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<FFrameTrackSeries> SFrameTrack::FindOrAddSeries(ETraceFrameType FrameType)
|
|
{
|
|
TSharedPtr<FFrameTrackSeries>* ExistingSeries = AllSeries.FindByPredicate([FrameType](TSharedPtr<FFrameTrackSeries> Series)
|
|
{
|
|
return !Series->Is<FTimerFrameStatsTrackSeries>() &&
|
|
Series->GetFrameType() == FrameType;
|
|
});
|
|
|
|
if (ExistingSeries != nullptr)
|
|
{
|
|
return ExistingSeries->ToSharedRef();
|
|
}
|
|
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
|
|
TSharedRef<FFrameTrackSeries> SeriesRef = MakeShared<FFrameTrackSeries>(FrameType);
|
|
SeriesRef->SetColor(FFrameTrackDrawHelper::GetColorByFrameType(FrameType));
|
|
SeriesRef->SetName(FFrameTrackDrawHelper::FrameTypeToText(FrameType));
|
|
AllSeries.Add(SeriesRef);
|
|
return SeriesRef;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<FFrameTrackSeries> SFrameTrack::FindSeries(ETraceFrameType FrameType) const
|
|
{
|
|
const TSharedPtr<FFrameTrackSeries>* ExistingSeries = AllSeries.FindByPredicate([FrameType](TSharedPtr<FFrameTrackSeries> Series)
|
|
{
|
|
return !Series->Is<FTimerFrameStatsTrackSeries>() &&
|
|
Series->GetFrameType() == FrameType;
|
|
});
|
|
|
|
if (ExistingSeries != nullptr)
|
|
{
|
|
return *ExistingSeries;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<FFrameTrackSeries> SFrameTrack::FindFrameStatsSeries(ETraceFrameType FrameType, uint32 TimerId) const
|
|
{
|
|
const TSharedPtr<FFrameTrackSeries>* ExistingSeries = AllSeries.FindByPredicate([FrameType, TimerId](TSharedPtr<FFrameTrackSeries> Series)
|
|
{
|
|
return Series->Is<FTimerFrameStatsTrackSeries>() &&
|
|
Series->As<FTimerFrameStatsTrackSeries>().GetFrameType() == FrameType &&
|
|
Series->As<FTimerFrameStatsTrackSeries>().GetTimerId() == TimerId;
|
|
});
|
|
|
|
if (ExistingSeries)
|
|
{
|
|
return *ExistingSeries;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::UpdateState()
|
|
{
|
|
FStopwatch Stopwatch;
|
|
Stopwatch.Start();
|
|
|
|
// Reset stats.
|
|
for (TSharedPtr<FFrameTrackSeries> Series : AllSeries)
|
|
{
|
|
Series->SetNumAggregatedFrames(0);
|
|
}
|
|
NumUpdatedFrames = 0;
|
|
|
|
TSharedPtr<const TraceServices::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid())
|
|
{
|
|
TraceServices::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const TraceServices::IFrameProvider& FramesProvider = TraceServices::ReadFrameProvider(*Session.Get());
|
|
|
|
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
|
|
const uint64 StartIndex = static_cast<uint64>(FMath::Max(0, ViewportX.GetValueAtOffset(0.0f)));
|
|
const uint64 EndIndex = static_cast<uint64>(ViewportX.GetValueAtOffset(ViewportX.GetSize()));
|
|
|
|
for (int32 FrameType = 0; FrameType < TraceFrameType_Count; ++FrameType)
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> SeriesPtr = FindOrAddSeries(static_cast<ETraceFrameType>(FrameType));
|
|
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
FFrameTrackSeriesBuilder Builder(*SeriesPtr, Viewport);
|
|
|
|
FramesProvider.EnumerateFrames(static_cast<ETraceFrameType>(FrameType), StartIndex, EndIndex, [&Builder](const TraceServices::FFrame& Frame)
|
|
{
|
|
Builder.AddFrame(Frame);
|
|
});
|
|
|
|
NumUpdatedFrames += Builder.GetNumAddedFrames();
|
|
}
|
|
|
|
for (int32 Index = 0; Index < AllSeries.Num(); ++Index)
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> Series = AllSeries[Index];
|
|
if (!Series->Is<FTimerFrameStatsTrackSeries>())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FTimerFrameStatsTrackSeries& TimerSeries = Series->As<FTimerFrameStatsTrackSeries>();
|
|
TArray<FFrameStatsCachedEvent> Frames;
|
|
FramesProvider.EnumerateFrames(static_cast<ETraceFrameType>(Series->GetFrameType()), StartIndex, EndIndex, [&Frames](const TraceServices::FFrame& Frame)
|
|
{
|
|
FFrameStatsCachedEvent Event;
|
|
Event.FrameStartTime = Frame.StartTime;
|
|
Event.FrameEndTime = Frame.EndTime;
|
|
Event.Duration.store(0.0f);
|
|
Frames.Add(Event);
|
|
});
|
|
|
|
FFrameTrackSeriesBuilder Builder(*Series, Viewport);
|
|
|
|
bool bTimingViewExists = false;
|
|
TSet<uint32> Timelines;
|
|
|
|
TSharedPtr<STimingProfilerWindow> TimingWindow = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
|
|
// Attempt to compute only from visible timelines.
|
|
if (TimingWindow.IsValid())
|
|
{
|
|
TSharedPtr<STimingView> TimingView = TimingWindow->GetTimingView();
|
|
if (TimingView.IsValid())
|
|
{
|
|
TSharedPtr<FThreadTimingSharedState> ThreadSharedState = TimingView->GetThreadTimingSharedState();
|
|
if (ThreadSharedState.IsValid())
|
|
{
|
|
ThreadSharedState->GetVisibleTimelineIndexes(Timelines);
|
|
FFrameStatsHelper::ComputeFrameStatsForTimer(Frames, TimerSeries.GetTimerId(), Timelines);
|
|
bTimingViewExists = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bTimingViewExists)
|
|
{
|
|
// Compute the stats for all timelines.
|
|
FFrameStatsHelper::ComputeFrameStatsForTimer(Frames, TimerSeries.GetTimerId());
|
|
}
|
|
|
|
uint64 CurrentIndex = StartIndex;
|
|
for (FFrameStatsCachedEvent& Event : Frames)
|
|
{
|
|
TraceServices::FFrame NewFrame;
|
|
NewFrame.StartTime = Event.FrameStartTime;
|
|
NewFrame.EndTime = Event.FrameStartTime + Event.Duration.load();
|
|
NewFrame.Index = CurrentIndex++;
|
|
Builder.AddFrame(NewFrame);
|
|
}
|
|
|
|
NumUpdatedFrames += Builder.GetNumAddedFrames();
|
|
}
|
|
}
|
|
|
|
Stopwatch.Stop();
|
|
UpdateDurationHistory.AddValue(Stopwatch.AccumulatedTime);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FFrameTrackSampleRef SFrameTrack::GetSampleAtMousePosition(double X, double Y)
|
|
{
|
|
if (!bIsStateDirty)
|
|
{
|
|
float SampleW = Viewport.GetSampleWidth();
|
|
int32 SampleIndex = FMath::FloorToInt(static_cast<float>(X) / SampleW);
|
|
if (SampleIndex >= 0)
|
|
{
|
|
const float MY = static_cast<float>(Y);
|
|
|
|
// Search in reverse paint order.
|
|
for (int32 SeriesIndex = AllSeries.Num() - 1; SeriesIndex >= 0; --SeriesIndex)
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> SeriesPtr = AllSeries[SeriesIndex];
|
|
|
|
if (!SeriesPtr->IsVisible())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (SeriesPtr.IsValid())
|
|
{
|
|
if (SeriesPtr->GetNumAggregatedFrames() > 0 &&
|
|
SampleIndex < SeriesPtr->GetNumSamples())
|
|
{
|
|
const FFrameTrackSample& Sample = SeriesPtr->GetSample(SampleIndex);
|
|
if (Sample.NumFrames > 0)
|
|
{
|
|
const FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
|
|
|
|
const float ViewHeight = FMath::RoundToFloat(Viewport.GetHeight());
|
|
const float BaselineY = FMath::RoundToFloat(ViewportY.GetOffsetForValue(0.0));
|
|
|
|
float ValueY;
|
|
if (Sample.LargestFrameDuration == std::numeric_limits<double>::infinity())
|
|
{
|
|
ValueY = ViewHeight;
|
|
}
|
|
else
|
|
{
|
|
ValueY = FMath::RoundToFloat(ViewportY.GetOffsetForValue(Sample.LargestFrameDuration));
|
|
}
|
|
|
|
constexpr float ToleranceY = 3.0f; // [pixels]
|
|
|
|
const float BottomY = FMath::Min(ViewHeight, ViewHeight - BaselineY + ToleranceY);
|
|
const float TopY = FMath::Max(0.0f, ViewHeight - ValueY - ToleranceY);
|
|
|
|
if (MY >= TopY && MY < BottomY)
|
|
{
|
|
LLM_SCOPE_BYTAG(Insights);
|
|
return FFrameTrackSampleRef(SeriesPtr, MakeShared<FFrameTrackSample>(Sample));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return FFrameTrackSampleRef(nullptr, nullptr);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::SelectFrameAtMousePosition(double X, double Y, bool JoinCurrentSelection)
|
|
{
|
|
FFrameTrackSampleRef SampleRef = GetSampleAtMousePosition(X, Y);
|
|
if (!SampleRef.IsValid())
|
|
{
|
|
SampleRef = GetSampleAtMousePosition(X - 1.0, Y);
|
|
}
|
|
if (!SampleRef.IsValid())
|
|
{
|
|
SampleRef = GetSampleAtMousePosition(X + 1.0, Y);
|
|
}
|
|
|
|
if (SampleRef.IsValid())
|
|
{
|
|
TSharedPtr<STimingProfilerWindow> Window = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
if (Window.IsValid())
|
|
{
|
|
TSharedPtr<STimingView> TimingView = Window->GetTimingView();
|
|
if (TimingView.IsValid())
|
|
{
|
|
double StartTime = SampleRef.Sample->LargestFrameStartTime;
|
|
double Duration = SampleRef.Sample->LargestFrameDuration;
|
|
|
|
if (JoinCurrentSelection)
|
|
{
|
|
double EndTime = StartTime + Duration;
|
|
StartTime = FMath::Min(StartTime, TimingView->GetSelectionStartTime());
|
|
EndTime = FMath::Max(EndTime, TimingView->GetSelectionEndTime());
|
|
Duration = EndTime - StartTime;
|
|
}
|
|
|
|
TimingView->SetAutoScroll(false);
|
|
|
|
if (bZoomTimingViewOnFrameSelection)
|
|
{
|
|
const double EndTime = FMath::Min(StartTime + Duration, TimingView->GetViewport().GetMaxValidTime());
|
|
const double AdjustedDuration = EndTime - StartTime;
|
|
TimingView->ZoomOnTimeInterval(StartTime - AdjustedDuration * 0.1, AdjustedDuration * 1.2);
|
|
}
|
|
else
|
|
{
|
|
TimingView->CenterOnTimeInterval(StartTime, Duration);
|
|
}
|
|
|
|
TimingView->SelectTimeInterval(StartTime, Duration);
|
|
FSlateApplication::Get().SetKeyboardFocus(TimingView);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int32 SFrameTrack::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
|
{
|
|
const bool bEnabled = ShouldBeEnabled(bParentEnabled);
|
|
const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
FDrawContext DrawContext(AllottedGeometry, MyCullingRect, InWidgetStyle, DrawEffects, OutDrawElements, LayerId);
|
|
|
|
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
FSlateFontInfo SummaryFont = FAppStyle::Get().GetFontStyle("SmallFont");
|
|
|
|
const FSlateBrush* WhiteBrush = FAppStyle::Get().GetBrush("WhiteBrush");
|
|
|
|
const float ViewWidth = static_cast<float>(AllottedGeometry.Size.X);
|
|
const float ViewHeight = static_cast<float>(AllottedGeometry.Size.Y);
|
|
|
|
int32 NumDrawSamples = 0;
|
|
|
|
//////////////////////////////////////////////////
|
|
{
|
|
FStopwatch Stopwatch;
|
|
Stopwatch.Start();
|
|
|
|
FFrameTrackDrawHelper Helper(DrawContext, Viewport);
|
|
|
|
Helper.SetThresholds(UpperThresholdTime, LowerThresholdTime);
|
|
|
|
Helper.DrawBackground();
|
|
|
|
// Draw the horizontal axis grid (background layer).
|
|
DrawHorizontalAxisGrid(DrawContext, WhiteBrush, SummaryFont, true);
|
|
|
|
// Draw frames, for each visible Series.
|
|
for (TSharedPtr<FFrameTrackSeries> Series : AllSeries)
|
|
{
|
|
if (!Series->IsVisible())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (Series.IsValid())
|
|
{
|
|
Helper.DrawCached(*Series);
|
|
}
|
|
}
|
|
|
|
NumDrawSamples = Helper.GetNumDrawSamples();
|
|
|
|
TSharedPtr<FFrameTrackSeries> GameFrameSeries = FindSeries(ETraceFrameType::TraceFrameType_Game);
|
|
if (GameFrameSeries.IsValid())
|
|
{
|
|
TSharedPtr<STimingProfilerWindow> Window = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
if (Window)
|
|
{
|
|
TSharedPtr<STimingView> TimingView = Window->GetTimingView();
|
|
if (TimingView)
|
|
{
|
|
// Highlight the area corresponding to viewport of Timing View.
|
|
const double StartTime = TimingView->GetViewport().GetStartTime();
|
|
const double EndTime = TimingView->GetViewport().GetEndTime();
|
|
Helper.DrawHighlightedInterval(*GameFrameSeries, StartTime, EndTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw the horizontal axis grid (foreground layer).
|
|
DrawHorizontalAxisGrid(DrawContext, WhiteBrush, SummaryFont, false);
|
|
|
|
if (bShowLowerThresholdLine)
|
|
{
|
|
const FLinearColor LineColor(0.1f, 0.7f, 0.1f, 1.0f);
|
|
const FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
|
|
const float RoundedViewHeight = FMath::RoundToFloat(ViewportY.GetSize());
|
|
const float LineY = RoundedViewHeight - FMath::RoundToFloat(ViewportY.GetOffsetForValue(LowerThresholdTime));
|
|
DrawContext.DrawBox(0.0, LineY, ViewWidth, 1.0, WhiteBrush, LineColor);
|
|
DrawContext.LayerId++;
|
|
}
|
|
if (bShowUpperThresholdLine)
|
|
{
|
|
const FLinearColor LineColor(1.0f, 0.1f, 0.1f, 1.0f);
|
|
const FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
|
|
const float RoundedViewHeight = FMath::RoundToFloat(ViewportY.GetSize());
|
|
const float LineY = RoundedViewHeight - FMath::RoundToFloat(ViewportY.GetOffsetForValue(UpperThresholdTime));
|
|
DrawContext.DrawBox(0.0, LineY, ViewWidth, 1.0, WhiteBrush, LineColor);
|
|
DrawContext.LayerId++;
|
|
}
|
|
|
|
// Draw the vertical axis grid.
|
|
DrawVerticalAxisGrid(DrawContext, WhiteBrush, SummaryFont);
|
|
|
|
// Highlight the mouse hovered sample (frame).
|
|
if (HoveredSample.IsValid())
|
|
{
|
|
Helper.DrawHoveredSample(*HoveredSample.Sample);
|
|
}
|
|
|
|
// Draw tooltip for hovered sample (frame).
|
|
if (HoveredSample.IsValid())
|
|
{
|
|
constexpr float TooltipDesiredOpacity = 1.0f;
|
|
if (TooltipOpacity < TooltipDesiredOpacity)
|
|
{
|
|
// slow fade in
|
|
TooltipOpacity = TooltipOpacity * 0.9f + TooltipDesiredOpacity * 0.1f;
|
|
}
|
|
else
|
|
{
|
|
// fast fade out
|
|
TooltipOpacity = TooltipOpacity * 0.75f + TooltipDesiredOpacity * 0.25f;
|
|
}
|
|
|
|
// First line: "Rendering Frame 1,234"
|
|
TStringBuilder<512> StringBuilder;
|
|
|
|
StringBuilder.Append(HoveredSample.Series->GetName().ToString());
|
|
StringBuilder.Append(TEXT(" "));
|
|
StringBuilder.Append(FText::AsNumber(HoveredSample.Sample->LargestFrameIndex).ToString());
|
|
const FString Text1(StringBuilder);
|
|
|
|
// Second line: "1m 2.34s + 16.67ms (60 fps)"
|
|
StringBuilder.Reset();
|
|
StringBuilder.Append(FormatTimeAuto(HoveredSample.Sample->LargestFrameStartTime, HoveredSample.Sample->LargestFrameStartTime > 60.0 ? 3 : 2));
|
|
StringBuilder.Append(TEXT(" + "));
|
|
StringBuilder.Append(FormatTimeAuto(HoveredSample.Sample->LargestFrameDuration, 2));
|
|
StringBuilder.Appendf(TEXT(" (%.1f fps)"), 1.0 / HoveredSample.Sample->LargestFrameDuration);
|
|
const FString Text2(StringBuilder);
|
|
|
|
const float FontScale = DrawContext.Geometry.Scale;
|
|
const FVector2f TextSize1(FontMeasureService->Measure(Text1, SummaryFont, FontScale) / FontScale);
|
|
const FVector2f TextSize2(FontMeasureService->Measure(Text2, SummaryFont, FontScale) / FontScale);
|
|
|
|
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
|
|
const float FrameX = ViewportX.GetOffsetForValue(HoveredSample.Sample->LargestFrameIndex);
|
|
const float CX0 = FMath::RoundToFloat(FrameX + Viewport.GetSampleWidth() / 2.0f);
|
|
|
|
constexpr float DX = 3.0f;
|
|
const float DX1 = FMath::RoundToFloat(TextSize1.X / 2.0f);
|
|
const float DX2 = FMath::RoundToFloat(TextSize2.X / 2.0f);
|
|
const float TooltipDesiredSizeX = FMath::Max(DX1, DX2) + DX;
|
|
|
|
if (TooltipSizeX != TooltipDesiredSizeX)
|
|
{
|
|
TooltipSizeX = TooltipSizeX * 0.75f + TooltipDesiredSizeX * 0.25f;
|
|
|
|
if (FMath::IsNearlyEqual(TooltipSizeX, TooltipDesiredSizeX))
|
|
{
|
|
TooltipSizeX = TooltipDesiredSizeX;
|
|
}
|
|
}
|
|
|
|
float CX = CX0;
|
|
if (CX > ViewportX.GetSize() - TooltipSizeX)
|
|
{
|
|
CX = FMath::RoundToFloat(ViewportX.GetSize() - TooltipSizeX);
|
|
}
|
|
if (CX - TooltipSizeX < 0)
|
|
{
|
|
CX = TooltipSizeX;
|
|
}
|
|
|
|
constexpr float BoxY = 11.0f;
|
|
constexpr float BoxH = 26.0f;
|
|
constexpr float LineDY = 12.0f;
|
|
|
|
const FLinearColor BackgroundColor(0.9f, 0.9f, 0.9f, TooltipOpacity);
|
|
DrawContext.DrawBox(CX - TooltipSizeX, BoxY, 2 * TooltipSizeX, BoxH, WhiteBrush, BackgroundColor);
|
|
const int32 ArrowSize = 4;
|
|
for (int32 ArrowY = 0; ArrowY < ArrowSize; ++ArrowY)
|
|
{
|
|
const int32 LineWidth = ArrowSize - ArrowY;
|
|
DrawContext.DrawBox(CX0 - float(LineWidth), BoxY + BoxH + float(ArrowY), float(2 * LineWidth - 1), 1.0f, WhiteBrush, BackgroundColor);
|
|
}
|
|
DrawContext.LayerId++;
|
|
|
|
const FLinearColor TextColor1 =
|
|
HoveredSample.Series->GetFrameType() == TraceFrameType_Rendering ?
|
|
FLinearColor(0.5f, 0.1f, 0.1f, TooltipOpacity) :
|
|
HoveredSample.Series->GetFrameType() == TraceFrameType_Game ?
|
|
FLinearColor(0.1f, 0.1f, 0.5f, TooltipOpacity) :
|
|
FLinearColor(0.1f, 0.1f, 0.1f, TooltipOpacity);
|
|
const FLinearColor TextColor2(0.05f, 0.05f, 0.05f, TooltipOpacity);
|
|
DrawContext.DrawText(CX - DX1, BoxY + 1.0f, Text1, SummaryFont, TextColor1);
|
|
DrawContext.DrawText(CX - DX2, BoxY + LineDY + 1.0f, Text2, SummaryFont, TextColor2);
|
|
DrawContext.LayerId++;
|
|
}
|
|
else
|
|
{
|
|
TooltipOpacity = 0.0f;
|
|
}
|
|
|
|
Stopwatch.Stop();
|
|
DrawDurationHistory.AddValue(Stopwatch.AccumulatedTime);
|
|
}
|
|
//////////////////////////////////////////////////
|
|
|
|
const bool bShouldDisplayDebugInfo = FInsightsManager::Get()->IsDebugInfoEnabled();
|
|
if (bShouldDisplayDebugInfo)
|
|
{
|
|
const float FontScale = DrawContext.Geometry.Scale;
|
|
const float MaxFontCharHeight = static_cast<float>(FontMeasureService->Measure(TEXT("!"), SummaryFont, FontScale).Y / FontScale);
|
|
const float DbgDY = MaxFontCharHeight;
|
|
|
|
const float DbgW = 280.0f;
|
|
const float DbgH = DbgDY * 4 + 3.0f;
|
|
const float DbgX = ViewWidth - DbgW - 20.0f;
|
|
float DbgY = 7.0f;
|
|
|
|
const FLinearColor DbgBackgroundColor(1.0f, 1.0f, 1.0f, 0.9f);
|
|
const FLinearColor DbgTextColor(0.0f, 0.0f, 0.0f, 0.9f);
|
|
|
|
DrawContext.LayerId++;
|
|
DrawContext.DrawBox(DbgX - 2.0f, DbgY - 2.0f, DbgW, DbgH, WhiteBrush, DbgBackgroundColor);
|
|
DrawContext.LayerId++;
|
|
|
|
// Time interval since last OnPaint call.
|
|
const uint64 CurrentTime = FPlatformTime::Cycles64();
|
|
const uint64 OnPaintDuration = CurrentTime - LastOnPaintTime;
|
|
LastOnPaintTime = CurrentTime;
|
|
OnPaintDurationHistory.AddValue(OnPaintDuration); // saved for last 32 OnPaint calls
|
|
const uint64 AvgOnPaintDuration = OnPaintDurationHistory.ComputeAverage();
|
|
const uint64 AvgOnPaintDurationMs = FStopwatch::Cycles64ToMilliseconds(AvgOnPaintDuration);
|
|
const double AvgOnPaintFps = AvgOnPaintDurationMs != 0 ? 1.0 / FStopwatch::Cycles64ToSeconds(AvgOnPaintDuration) : 0.0;
|
|
|
|
const uint64 AvgUpdateDurationMs = FStopwatch::Cycles64ToMilliseconds(UpdateDurationHistory.ComputeAverage());
|
|
const uint64 AvgDrawDurationMs = FStopwatch::Cycles64ToMilliseconds(DrawDurationHistory.ComputeAverage());
|
|
|
|
// Draw performance info.
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Printf(TEXT("U: %" UINT64_FMT " ms, D: %" UINT64_FMT " ms + %" UINT64_FMT " ms = %" UINT64_FMT " ms (%" INT64_FMT " fps)"),
|
|
AvgUpdateDurationMs, // caching time
|
|
AvgDrawDurationMs, // drawing time
|
|
AvgOnPaintDurationMs - AvgDrawDurationMs, // other overhead to OnPaint calls
|
|
AvgOnPaintDurationMs, // average time between two OnPaint calls
|
|
FMath::RoundToInt(AvgOnPaintFps)), // framerate of OnPaint calls
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
|
|
// Draw number of draw calls.
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
FString::Printf(TEXT("U: %s frames, D: %s samples"),
|
|
*FText::AsNumber(NumUpdatedFrames).ToString(),
|
|
*FText::AsNumber(NumDrawSamples).ToString()),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
|
|
// Draw viewport's horizontal info.
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
Viewport.GetHorizontalAxisViewport().ToDebugString(TEXT("X"), TEXT("frame")),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
|
|
// Draw viewport's vertical info.
|
|
DrawContext.DrawText
|
|
(
|
|
DbgX, DbgY,
|
|
Viewport.GetVerticalAxisViewport().ToDebugString(TEXT("Y")),
|
|
SummaryFont, DbgTextColor
|
|
);
|
|
DbgY += DbgDY;
|
|
}
|
|
|
|
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled && IsEnabled());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::DrawVerticalAxisGrid(FDrawContext& DrawContext, const FSlateBrush* Brush, const FSlateFontInfo& Font) const
|
|
{
|
|
const float ViewWidth = Viewport.GetWidth();
|
|
|
|
const FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
|
|
const float RoundedViewHeight = FMath::RoundToFloat(ViewportY.GetSize());
|
|
|
|
const FLinearColor GridColor(0.0f, 0.0f, 0.0f, 0.1f);
|
|
const FLinearColor TextBgColor(0.05f, 0.05f, 0.05f, 1.0f);
|
|
|
|
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
const float FontScale = DrawContext.Geometry.Scale;
|
|
|
|
// Available axis, pre-ordered by value.
|
|
struct FAxis
|
|
{
|
|
int32 Priority; // lower value means higher priority
|
|
double Value; // time value
|
|
};
|
|
const FAxis AvailableAxis[] =
|
|
{
|
|
{ 0, 0.0 },
|
|
{ 3, 0.001 }, // 1 ms (1000 fps)
|
|
{ 3, 0.002 }, // 2 ms (500 fps)
|
|
{ 3, 0.003 }, // 3 ms (333 fps)
|
|
{ 3, 0.004 }, // 4 ms (250 fps)
|
|
{ 2, 0.005 }, // 5 ms (200 fps)
|
|
{ 2, 1.0 / 150.0 }, // 6.6 ms (150 fps)
|
|
{ 1, 1.0 / 120.0 }, // 8.3 ms (120 fps)
|
|
{ 2, 1.0 / 100.0 }, // 10 ms (100 fps)
|
|
{ 3, 1.0 / 90.0 }, // 11.1 ms (90 fps)
|
|
{ 4, 1.0 / 80.0 }, // 12.5 ms (80 fps)
|
|
{ 4, 1.0 / 70.0 }, // 14.3 ms (70 fps)
|
|
{ 1, 1.0 / 60.0 }, // 16.7 ms (60 fps)
|
|
{ 2, 1.0 / 50.0 }, // 20 ms (50 fps)
|
|
{ 3, 1.0 / 40.0 }, // 25 ms (40 fps)
|
|
{ 1, 1.0 / 30.0 }, // 33.3 ms (30 fps)
|
|
{ 2, 1.0 / 20.0 }, // 50 ms (20 fps)
|
|
{ 3, 1.0 / 15.0 }, // 66.7 ms (15 fps)
|
|
{ 2, 1.0 / 10.0 }, // 100 ms (10 fps)
|
|
{ 3, 1.0 / 5.0 }, // 200 ms (5 fps)
|
|
{ 3, 1.0 }, // 1s
|
|
{ 3, 10.0 }, // 10s
|
|
{ 3, 60.0 }, // 1m
|
|
{ 3, 600.0 }, // 10m
|
|
{ 3, 3600.0 }, // 1h
|
|
};
|
|
constexpr int32 NumAvailableAxis = UE_ARRAY_COUNT(AvailableAxis);
|
|
|
|
struct FVisibleAxis
|
|
{
|
|
double Value;
|
|
float Y;
|
|
float LabelY;
|
|
};
|
|
FVisibleAxis VisibleAxis[NumAvailableAxis];
|
|
int32 NumVisibleAxis = 0;
|
|
|
|
constexpr float TextH = 14.0f;
|
|
constexpr float MinDY = 13.0f; // min vertical distance between horizontal grid lines
|
|
|
|
int32 PreviousPriority = 0;
|
|
float PreviousLabelY = -MinDY;
|
|
for (int32 Index = 0; Index < NumAvailableAxis; ++Index)
|
|
{
|
|
const FAxis& Axis = AvailableAxis[Index];
|
|
|
|
const float Y = RoundedViewHeight - FMath::RoundToFloat(ViewportY.GetOffsetForValue(Axis.Value));
|
|
const float LabelY = FMath::Clamp(Y - TextH / 2, 0.0f, RoundedViewHeight - TextH);
|
|
|
|
if (Y < 0)
|
|
{
|
|
break; // we are done; the rest of axis are offscreen
|
|
}
|
|
if (Y > RoundedViewHeight + TextH)
|
|
{
|
|
continue; // skip the current axis
|
|
}
|
|
|
|
// Does the label overlaps with the label of the previous axis?
|
|
if (FMath::Abs(PreviousLabelY - LabelY) < MinDY)
|
|
{
|
|
if (Axis.Priority < PreviousPriority)
|
|
{
|
|
--NumVisibleAxis; // the current axis replaces the previous axis
|
|
}
|
|
else
|
|
{
|
|
continue; // skip the current axis
|
|
}
|
|
}
|
|
|
|
PreviousPriority = Axis.Priority;
|
|
PreviousLabelY = LabelY;
|
|
|
|
FVisibleAxis& CurrentVisibleAxis = VisibleAxis[NumVisibleAxis++];
|
|
CurrentVisibleAxis.Value = Axis.Value;
|
|
CurrentVisibleAxis.Y = Y;
|
|
CurrentVisibleAxis.LabelY = LabelY;
|
|
}
|
|
|
|
for (int32 Index = 0; Index < NumVisibleAxis; ++Index)
|
|
{
|
|
const FVisibleAxis& Axis = VisibleAxis[Index];
|
|
|
|
FLinearColor TextColor;
|
|
if (Axis.Value <= LowerThresholdTime)
|
|
{
|
|
TextColor = FLinearColor(0.5f, 1.0f, 0.5f, 1.0f);
|
|
}
|
|
else if (Axis.Value <= UpperThresholdTime)
|
|
{
|
|
TextColor = FLinearColor(1.0f, 1.0f, 0.5f, 1.0f);
|
|
}
|
|
else
|
|
{
|
|
TextColor = FLinearColor(1.0f, 0.5f, 0.5f, 1.0f);
|
|
}
|
|
|
|
// Draw horizontal grid line.
|
|
DrawContext.DrawBox(0, Axis.Y, ViewWidth, 1.0f, Brush, GridColor);
|
|
|
|
const FString LabelText = (Axis.Value == 0.0) ? TEXT("0") :
|
|
(Axis.Value <= 1.0) ? FString::Printf(TEXT("%s (%.0f fps)"), *FormatTimeAuto(Axis.Value), 1.0 / Axis.Value) :
|
|
FormatTimeAuto(Axis.Value);
|
|
|
|
const float LabelTextWidth = static_cast<float>(FontMeasureService->Measure(LabelText, Font, FontScale).X / FontScale);
|
|
const float LabelX = bDrawVerticalAxisLabelsOnLeftSide ? 0.0f : ViewWidth - LabelTextWidth - 4.0f;
|
|
|
|
// Draw background for value text.
|
|
DrawContext.DrawBox(LabelX, Axis.LabelY, LabelTextWidth + 4.0f, TextH, Brush, TextBgColor);
|
|
|
|
// Draw value text.
|
|
DrawContext.DrawText(LabelX + 2.0f, Axis.LabelY + 1.0f, LabelText, Font, TextColor);
|
|
}
|
|
DrawContext.LayerId++;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::DrawHorizontalAxisGrid(FDrawContext& DrawContext, const FSlateBrush* Brush, const FSlateFontInfo& Font, bool bDrawBackgroundLayer) const
|
|
{
|
|
const FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
|
|
const float RoundedViewWidth = FMath::RoundToFloat(ViewportX.GetSize());
|
|
|
|
constexpr float MinDX = 125.0f; // min horizontal distance between vertical grid lines
|
|
|
|
const int32 LeftIndex = ViewportX.GetValueAtOffset(0.0f);
|
|
const int32 GridIndex = ViewportX.GetValueAtOffset(MinDX);
|
|
const int32 RightIndex = ViewportX.GetValueAtOffset(RoundedViewWidth);
|
|
const int32 Delta = GridIndex - LeftIndex;
|
|
|
|
if (Delta > 0)
|
|
{
|
|
// Compute rounding based on magnitude of visible range of samples (Delta).
|
|
int32 Power10 = 1;
|
|
int32 Delta10 = Delta;
|
|
while (Delta10 > 0)
|
|
{
|
|
Delta10 /= 10;
|
|
Power10 *= 10;
|
|
}
|
|
if (Power10 >= 100)
|
|
{
|
|
Power10 /= 100;
|
|
}
|
|
else
|
|
{
|
|
Power10 = 1;
|
|
}
|
|
|
|
const int32 Grid = ((Delta + Power10 - 1) / Power10) * Power10; // next value divisible with a multiple of 10
|
|
|
|
// Skip grid lines for negative indices.
|
|
int32 StartIndex = ((LeftIndex + Grid - 1) / Grid) * Grid;
|
|
while (StartIndex < 0)
|
|
{
|
|
StartIndex += Grid;
|
|
}
|
|
|
|
if (bDrawBackgroundLayer)
|
|
{
|
|
const float ViewHeight = Viewport.GetHeight();
|
|
|
|
// Draw vertical grid lines.
|
|
const FLinearColor GridColor(0.0f, 0.0f, 0.0f, 0.1f);
|
|
for (int32 Index = StartIndex; Index < RightIndex; Index += Grid)
|
|
{
|
|
const float X = FMath::RoundToFloat(ViewportX.GetOffsetForValue(Index));
|
|
DrawContext.DrawBox(X, 0.0f, 1.0f, ViewHeight, Brush, GridColor);
|
|
}
|
|
DrawContext.LayerId++;
|
|
}
|
|
else
|
|
{
|
|
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
const float FontScale = DrawContext.Geometry.Scale;
|
|
|
|
// Draw labels.
|
|
const FLinearColor LabelBoxColor(0.05f, 0.05f, 0.05f, 1.0f);
|
|
const FLinearColor LabelTextColor(1.0f, 1.0f, 1.0f, 0.7f);
|
|
for (int32 Index = StartIndex; Index < RightIndex; Index += Grid)
|
|
{
|
|
const float X = FMath::RoundToFloat(ViewportX.GetOffsetForValue(Index));
|
|
const FString LabelText = FText::AsNumber(Index).ToString();
|
|
const float LabelTextWidth = static_cast<float>(FontMeasureService->Measure(LabelText, Font, FontScale).X / FontScale);
|
|
DrawContext.DrawBox(X, 10.0f, LabelTextWidth + 4.0f, 12.0f, Brush, LabelBoxColor);
|
|
DrawContext.DrawText(X + 2.0f, 10.0f, LabelText, Font, LabelTextColor);
|
|
}
|
|
DrawContext.LayerId++;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply SFrameTrack::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
MousePositionOnButtonDown = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
ViewportPosXOnButtonDown = Viewport.GetHorizontalAxisViewport().GetPos();
|
|
|
|
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
|
{
|
|
bIsLMB_Pressed = true;
|
|
|
|
// Capture mouse.
|
|
Reply = FReply::Handled().CaptureMouse(SharedThis(this));
|
|
}
|
|
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
|
{
|
|
bIsRMB_Pressed = true;
|
|
|
|
// Capture mouse, so we can scroll outside this widget.
|
|
Reply = FReply::Handled().CaptureMouse(SharedThis(this));
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply SFrameTrack::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
MousePositionOnButtonUp = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
|
|
const bool bIsValidForMouseClick = MousePositionOnButtonUp.Equals(MousePositionOnButtonDown, MOUSE_SNAP_DISTANCE);
|
|
|
|
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
|
|
{
|
|
if (bIsLMB_Pressed)
|
|
{
|
|
if (bIsScrolling)
|
|
{
|
|
bIsScrolling = false;
|
|
CursorType = ECursorType::Default;
|
|
}
|
|
else if (bIsValidForMouseClick)
|
|
{
|
|
const bool JoinCurrentSelection = MouseEvent.IsShiftDown();
|
|
|
|
if (!JoinCurrentSelection)
|
|
{
|
|
SelectedSample = HoveredSample;
|
|
}
|
|
|
|
SelectFrameAtMousePosition(
|
|
static_cast<float>(MousePositionOnButtonUp.X),
|
|
static_cast<float>(MousePositionOnButtonUp.Y),
|
|
JoinCurrentSelection);
|
|
}
|
|
|
|
bIsLMB_Pressed = false;
|
|
|
|
// Release the mouse.
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
}
|
|
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
|
{
|
|
if (bIsRMB_Pressed)
|
|
{
|
|
if (bIsScrolling)
|
|
{
|
|
bIsScrolling = false;
|
|
CursorType = ECursorType::Default;
|
|
}
|
|
else if (bIsValidForMouseClick)
|
|
{
|
|
SelectedSample = HoveredSample;
|
|
ShowContextMenu(MouseEvent);
|
|
}
|
|
|
|
bIsRMB_Pressed = false;
|
|
|
|
// Release mouse as we no longer scroll.
|
|
Reply = FReply::Handled().ReleaseMouseCapture();
|
|
}
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply SFrameTrack::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
|
|
if (!MouseEvent.GetCursorDelta().IsZero())
|
|
{
|
|
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) ||
|
|
MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton))
|
|
{
|
|
if (HasMouseCapture())
|
|
{
|
|
if (!bIsScrolling)
|
|
{
|
|
bIsScrolling = true;
|
|
CursorType = ECursorType::Hand;
|
|
|
|
HoveredSample.Reset();
|
|
}
|
|
|
|
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
const float PosX = ViewportPosXOnButtonDown + static_cast<float>(MousePositionOnButtonDown.X - MousePosition.X);
|
|
ViewportX.ScrollAtValue(ViewportX.GetValueAtPos(PosX)); // align viewport position with sample (frame index)
|
|
UpdateHorizontalScrollBar();
|
|
bIsStateDirty = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!HoveredSample.IsValid())
|
|
{
|
|
TooltipOpacity = 0.0f;
|
|
}
|
|
HoveredSample = GetSampleAtMousePosition(MousePosition.X, MousePosition.Y);
|
|
if (!HoveredSample.IsValid())
|
|
{
|
|
HoveredSample = GetSampleAtMousePosition(MousePosition.X - 1.0, MousePosition.Y);
|
|
}
|
|
if (!HoveredSample.IsValid())
|
|
{
|
|
HoveredSample = GetSampleAtMousePosition(MousePosition.X + 1.0, MousePosition.Y);
|
|
}
|
|
if (HoveredSample.IsValid())
|
|
{
|
|
constexpr float VerticalAxisLabelAreaWidth = 100.0f;
|
|
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
if (MousePosition.X > ViewportX.GetSize() - VerticalAxisLabelAreaWidth)
|
|
{
|
|
bDrawVerticalAxisLabelsOnLeftSide = true;
|
|
}
|
|
else if (MousePosition.X < VerticalAxisLabelAreaWidth)
|
|
{
|
|
bDrawVerticalAxisLabelsOnLeftSide = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
Reply = FReply::Handled();
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::OnMouseLeave(const FPointerEvent& MouseEvent)
|
|
{
|
|
if (!HasMouseCapture())
|
|
{
|
|
bIsLMB_Pressed = false;
|
|
bIsRMB_Pressed = false;
|
|
|
|
HoveredSample.Reset();
|
|
|
|
CursorType = ECursorType::Default;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply SFrameTrack::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
|
|
|
|
if (MouseEvent.GetModifierKeys().IsShiftDown())
|
|
{
|
|
FAxisViewportDouble& ViewportY = Viewport.GetVerticalAxisViewport();
|
|
|
|
// Zoom in/out vertically.
|
|
const double Delta = MouseEvent.GetWheelDelta();
|
|
constexpr double ZoomStep = 0.25; // as percent
|
|
double ScaleY;
|
|
|
|
if (Delta > 0)
|
|
{
|
|
ScaleY = ViewportY.GetScale() * FMath::Pow(1.0 + ZoomStep, Delta);
|
|
}
|
|
else
|
|
{
|
|
ScaleY = ViewportY.GetScale() * FMath::Pow(1.0 / (1.0 + ZoomStep), -Delta);
|
|
}
|
|
|
|
ViewportY.SetScale(ScaleY);
|
|
//UpdateVerticalScrollBar();
|
|
}
|
|
else //if (MouseEvent.GetModifierKeys().IsControlDown())
|
|
{
|
|
// Zoom in/out horizontally.
|
|
const float Delta = MouseEvent.GetWheelDelta();
|
|
ZoomHorizontally(Delta, static_cast<float>(MousePosition.X));
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::ZoomHorizontally(const float Delta, const float X)
|
|
{
|
|
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
ViewportX.RelativeZoomWithFixedOffset(Delta, X);
|
|
ViewportX.ScrollAtValue(ViewportX.GetValueAtPos(ViewportX.GetPos())); // align viewport position with sample (frame index)
|
|
UpdateHorizontalScrollBar();
|
|
bIsStateDirty = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FReply SFrameTrack::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FCursorReply SFrameTrack::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
|
|
{
|
|
FCursorReply CursorReply = FCursorReply::Unhandled();
|
|
|
|
if (CursorType == ECursorType::Arrow)
|
|
{
|
|
CursorReply = FCursorReply::Cursor(EMouseCursor::ResizeLeftRight);
|
|
}
|
|
else if (CursorType == ECursorType::Hand)
|
|
{
|
|
CursorReply = FCursorReply::Cursor(EMouseCursor::GrabHand);
|
|
}
|
|
|
|
return CursorReply;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::ShowContextMenu(const FPointerEvent& MouseEvent)
|
|
{
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, NULL);
|
|
|
|
auto CreateSeriesMenuWidget = [](FLinearColor InIconColor, FText InText) -> TSharedRef<SWidget>
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(InText)
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(8.0f, 0.0f, 8.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.FilledCircle"))
|
|
.DesiredSizeOverride(FVector2D(12.0, 12.0))
|
|
.ColorAndOpacity(InIconColor)
|
|
];
|
|
};
|
|
|
|
MenuBuilder.BeginSection("Frames", LOCTEXT("ContextMenu_Section_Frames", "Frames"));
|
|
{
|
|
struct FLocal
|
|
{
|
|
static bool ReturnFalse()
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
FUIAction Action_ShowGameFrames
|
|
(
|
|
FExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ShowGameFrames_Execute),
|
|
FCanExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ShowGameFrames_CanExecute),
|
|
FIsActionChecked::CreateSP(this, &SFrameTrack::ContextMenu_ShowGameFrames_IsChecked)
|
|
);
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
Action_ShowGameFrames,
|
|
CreateSeriesMenuWidget(
|
|
FLinearColor(0.3f, 0.3f, 0.7f, 1.0f),
|
|
LOCTEXT("ContextMenu_ShowGameFrames", "Game Frames")),
|
|
NAME_None,
|
|
LOCTEXT("ContextMenu_ShowGameFrames_Desc", "Shows/hides the Game frames."),
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
|
|
FUIAction Action_ShowRenderingFrames
|
|
(
|
|
FExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ShowRenderingFrames_Execute),
|
|
FCanExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ShowRenderingFrames_CanExecute),
|
|
FIsActionChecked::CreateSP(this, &SFrameTrack::ContextMenu_ShowRenderingFrames_IsChecked)
|
|
);
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
Action_ShowRenderingFrames,
|
|
CreateSeriesMenuWidget(
|
|
FLinearColor(0.7f, 0.3f, 0.3f, 1.0f),
|
|
LOCTEXT("ContextMenu_ShowRenderingFrames", "Rendering Frames")),
|
|
NAME_None,
|
|
LOCTEXT("ContextMenu_ShowRenderingFrames_Desc", "Shows/hides the Rendering frames."),
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("Timers", LOCTEXT("ContextMenu_Section_Timers", "Timers"));
|
|
|
|
for (TSharedPtr<FFrameTrackSeries> Series : AllSeries)
|
|
{
|
|
if (!Series->Is<FTimerFrameStatsTrackSeries>())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
FTimerFrameStatsTrackSeries& TimerSeries = Series->As<FTimerFrameStatsTrackSeries>();
|
|
ETraceFrameType FrameType = static_cast<ETraceFrameType>(TimerSeries.GetFrameType());
|
|
FUIAction Action_ShowFrameStatsSeries
|
|
(
|
|
FExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ShowFrameStats_Execute, FrameType, TimerSeries.GetTimerId()),
|
|
FCanExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ShowFrameStats_CanExecute, FrameType, TimerSeries.GetTimerId()),
|
|
FIsActionChecked::CreateSP(this, &SFrameTrack::ContextMenu_ShowFrameStats_IsChecked, FrameType, TimerSeries.GetTimerId())
|
|
);
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
Action_ShowFrameStatsSeries,
|
|
CreateSeriesMenuWidget(TimerSeries.GetColor(), TimerSeries.GetName()),
|
|
NAME_None,
|
|
FText::Format(LOCTEXT("ContextMenu_ShowFrameStatsSeries_Desc", "Shows/hides the {0} timer series."), TimerSeries.GetName()),
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
}
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.AddSeparator();
|
|
|
|
MenuBuilder.AddSubMenu
|
|
(
|
|
LOCTEXT("ContextMenu_ThresholdsSubMenu", "Setup Thresholds"),
|
|
LOCTEXT("ContextMenu_ThresholdsSubMenu_Desc", "Setup thresholds."),
|
|
FNewMenuDelegate::CreateSP(this, &SFrameTrack::CreateThresholdsMenu),
|
|
false,
|
|
FSlateIcon()
|
|
);
|
|
|
|
MenuBuilder.BeginSection("Zoom", LOCTEXT("ContextMenu_Section_Zoom", "Zoom"));
|
|
{
|
|
FUIAction Action_AutoZoom
|
|
(
|
|
FExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_AutoZoom_Execute),
|
|
FCanExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_AutoZoom_CanExecute),
|
|
FIsActionChecked::CreateSP(this, &SFrameTrack::ContextMenu_AutoZoom_IsChecked)
|
|
);
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
LOCTEXT("ContextMenu_AutoZoom", "Auto Zoom"),
|
|
LOCTEXT("ContextMenu_AutoZoom_Desc", "Enables auto zoom. Makes the entire session time range to fit into the Frames track's view."),
|
|
FSlateIcon(),
|
|
Action_AutoZoom,
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
|
|
FUIAction Action_ZoomTimingViewOnFrameSelection
|
|
(
|
|
FExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ZoomTimingViewOnFrameSelection_Execute),
|
|
FCanExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ZoomTimingViewOnFrameSelection_CanExecute),
|
|
FIsActionChecked::CreateSP(this, &SFrameTrack::ContextMenu_ZoomTimingViewOnFrameSelection_IsChecked)
|
|
);
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
LOCTEXT("ContextMenu_ZoomTimingViewOnFrameSelection", "Zoom Timing View on Frame Selection"),
|
|
LOCTEXT("ContextMenu_ZoomTimingViewOnFrameSelection_Desc", "If enabled, the Timing view will also be zoomed when a frame is selected.\n(This option is persistent to the UnrealInsightsSettings.ini file.)"),
|
|
FSlateIcon(),
|
|
Action_ZoomTimingViewOnFrameSelection,
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
CreateSelectedFrameMenu(MenuBuilder);
|
|
|
|
TSharedRef<SWidget> MenuWidget = MenuBuilder.MakeWidget();
|
|
|
|
FWidgetPath EventPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
|
|
const FVector2D ScreenSpacePosition = MouseEvent.GetScreenSpacePosition();
|
|
FSlateApplication::Get().PushMenu(SharedThis(this), EventPath, MenuWidget, ScreenSpacePosition, FPopupTransitionEffect::ContextMenu);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::CreateThresholdsMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
MenuBuilder.BeginSection("Thresholds");// , LOCTEXT("ContextMenu_Section_Thresholds", "Thresholds"));
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
LOCTEXT("ContextMenu_ShowUpperThresholdLine", "Show Upper Threshold Line"),
|
|
LOCTEXT("ContextMenu_ShowUpperThresholdLine_Desc", "Shows/hides the red horizontal line for the upper threshold."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateLambda(
|
|
[this]()
|
|
{
|
|
bShowUpperThresholdLine = !bShowUpperThresholdLine;
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSaveShowUpperThresholdLineEnabled(bShowUpperThresholdLine);
|
|
}),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateLambda(
|
|
[this]() -> bool
|
|
{
|
|
return bShowUpperThresholdLine;
|
|
})),
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
LOCTEXT("ContextMenu_ShowLowerThresholdLine", "Show Lower Threshold Line"),
|
|
LOCTEXT("ContextMenu_ShowLowerThresholdLine_Desc", "Shows/hides the green horizontal line for the lower threshold."),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateLambda(
|
|
[this]()
|
|
{
|
|
bShowLowerThresholdLine = !bShowLowerThresholdLine;
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSaveShowLowerThresholdLineEnabled(bShowLowerThresholdLine);
|
|
}),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateLambda(
|
|
[this]() -> bool
|
|
{
|
|
return bShowLowerThresholdLine;
|
|
})),
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
|
|
MenuBuilder.AddSeparator();
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
FUIAction(FExecuteAction(), FCanExecuteAction()),
|
|
CreateUpperThresholdWidget(),
|
|
NAME_None,
|
|
LOCTEXT("UpperThresholdCustomTooltip", "Upper Threshold\nFrames with duration longer than this threshold will have a red color tint.\nCan be specified as a frame duration, in seconds [0.001 .. 1.0] or as a framerate [1 fps ... 1000 fps]."),
|
|
EUserInterfaceActionType::None
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
FUIAction(FExecuteAction(), FCanExecuteAction()),
|
|
CreateLowerThresholdWidget(),
|
|
NAME_None,
|
|
LOCTEXT("LowerThresholdCustomTooltip", "Lower Threshold\nFrames with duration shorter than this threshold will have a green color tint.\nCan be specified as a frame duration, in seconds [0.001 .. 1.0] or as a framerate [1 fps ... 1000 fps]."),
|
|
EUserInterfaceActionType::None
|
|
);
|
|
|
|
MenuBuilder.AddSeparator();
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
FUIAction(FExecuteAction(), FCanExecuteAction()),
|
|
CreateThresholdPresetsWidget(),
|
|
NAME_None,
|
|
LOCTEXT("ThresholdPresetsTooltip", "Threshold Presets"),
|
|
EUserInterfaceActionType::None
|
|
);
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> SFrameTrack::CreateUpperThresholdWidget()
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.MinDesiredWidth(110.0f)
|
|
.Text(LOCTEXT("UpperThresholdText", "Upper Threshold:"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.MinDesiredWidth(50.0f)
|
|
.HintText(LOCTEXT("UpperThresholdCustomHint", "30 fps"))
|
|
.Text_Lambda([this]
|
|
{
|
|
if (bShowUpperThresholdAsFps)
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("%g fps"), 1.0 / UpperThresholdTime));
|
|
}
|
|
else
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("%g"), UpperThresholdTime));
|
|
}
|
|
})
|
|
.OnTextChanged_Lambda([this](const FText& InText)
|
|
{
|
|
FString ValueStr = InText.ToString().TrimStartAndEnd();
|
|
if (ValueStr.IsEmpty())
|
|
{
|
|
ValueStr = TEXT("30 fps");
|
|
}
|
|
if (ValueStr.EndsWith(TEXT("fps")))
|
|
{
|
|
double FPS = FCString::Atof(*ValueStr);
|
|
UpperThresholdTime = 1.0 / FMath::Clamp(FPS, 1.0 / MaxThresholdTime, 1.0 / MinThresholdTime);
|
|
bShowUpperThresholdAsFps = true;
|
|
}
|
|
else
|
|
{
|
|
double Time = FCString::Atof(*ValueStr);
|
|
UpperThresholdTime = FMath::Clamp(Time, MinThresholdTime, MaxThresholdTime);
|
|
bShowUpperThresholdAsFps = false;
|
|
}
|
|
if (LowerThresholdTime > UpperThresholdTime)
|
|
{
|
|
LowerThresholdTime = UpperThresholdTime;
|
|
}
|
|
SaveThresholds();
|
|
})
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(8.0f, 0.0f, 12.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text_Lambda([this]
|
|
{
|
|
FString ThresholdTimeStr = FormatTimeAuto(UpperThresholdTime, 2);
|
|
if (bShowUpperThresholdAsFps)
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("%s"), *ThresholdTimeStr));
|
|
}
|
|
else
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("%s (%.2f fps)"), *ThresholdTimeStr, 1.0 / UpperThresholdTime));
|
|
}
|
|
})
|
|
];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> SFrameTrack::CreateLowerThresholdWidget()
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.MinDesiredWidth(110.0f)
|
|
.Text(LOCTEXT("LowerThresholdText", "Lower Threshold:"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.MinDesiredWidth(50.0f)
|
|
.HintText(LOCTEXT("LowerThresholdCustomHint", "60 fps"))
|
|
.Text_Lambda([this]
|
|
{
|
|
if (bShowLowerThresholdAsFps)
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("%g fps"), 1.0 / LowerThresholdTime));
|
|
}
|
|
else
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("%g"), LowerThresholdTime));
|
|
}
|
|
})
|
|
.OnTextChanged_Lambda([this](const FText& InText)
|
|
{
|
|
FString ValueStr = InText.ToString().TrimStartAndEnd();
|
|
if (ValueStr.IsEmpty())
|
|
{
|
|
ValueStr = TEXT("60 fps");
|
|
}
|
|
if (ValueStr.EndsWith(TEXT("fps")))
|
|
{
|
|
double FPS = FCString::Atof(*ValueStr);
|
|
LowerThresholdTime = 1.0 / FMath::Clamp(FPS, 1.0 / MaxThresholdTime, 1.0 / MinThresholdTime);
|
|
bShowLowerThresholdAsFps = true;
|
|
}
|
|
else
|
|
{
|
|
double Time = FCString::Atof(*ValueStr);
|
|
LowerThresholdTime = FMath::Clamp(Time, MinThresholdTime, MaxThresholdTime);
|
|
bShowLowerThresholdAsFps = false;
|
|
}
|
|
if (UpperThresholdTime < LowerThresholdTime)
|
|
{
|
|
UpperThresholdTime = LowerThresholdTime;
|
|
}
|
|
SaveThresholds();
|
|
})
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(8.0f, 0.0f, 12.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text_Lambda([this]
|
|
{
|
|
FString ThresholdTimeStr = FormatTimeAuto(LowerThresholdTime, 2);
|
|
if (bShowLowerThresholdAsFps)
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("%s"), *ThresholdTimeStr));
|
|
}
|
|
else
|
|
{
|
|
return FText::FromString(FString::Printf(TEXT("%s (%.2f fps)"), *ThresholdTimeStr, 1.0 / LowerThresholdTime));
|
|
}
|
|
})
|
|
];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::SaveThresholds()
|
|
{
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSaveThresholds(UpperThresholdTime, LowerThresholdTime, bShowUpperThresholdAsFps, bShowLowerThresholdAsFps);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::SetThresholdsFPS(double InUpperThresholdFPS, double InLowerThresholdFPS)
|
|
{
|
|
UpperThresholdTime = 1.0 / InUpperThresholdFPS;
|
|
LowerThresholdTime = 1.0 / InLowerThresholdFPS;
|
|
bShowUpperThresholdAsFps = true;
|
|
bShowLowerThresholdAsFps = true;
|
|
SaveThresholds();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<SWidget> SFrameTrack::CreateThresholdPresetsWidget()
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(-30.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ContentPadding(FMargin(-6.0f, 0.0f, -6.0f, 0.0f))
|
|
.Text(LOCTEXT("ThresholdPreset20fps", "15/20 fps"))
|
|
.OnClicked_Lambda([this]() -> FReply
|
|
{
|
|
SetThresholdsFPS(15.0, 20.0);
|
|
return FReply::Handled();
|
|
})
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ContentPadding(FMargin(-6.0f, 0.0f, -6.0f, 0.0f))
|
|
.Text(LOCTEXT("ThresholdPreset30fps", "20/30 fps"))
|
|
.OnClicked_Lambda([this]() -> FReply
|
|
{
|
|
SetThresholdsFPS(20.0, 30.0);
|
|
return FReply::Handled();
|
|
})
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(4.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ContentPadding(FMargin(-6.0f, 0.0f, -6.0f, 0.0f))
|
|
.Text(LOCTEXT("ThresholdPreset60fps", "30/60 fps"))
|
|
.OnClicked_Lambda([this]() -> FReply
|
|
{
|
|
SetThresholdsFPS(30.0, 60.0);
|
|
return FReply::Handled();
|
|
})
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(4.0f, 0.0f, 9.0f, 0.0f))
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ContentPadding(FMargin(-6.0f, 0.0f, -6.0f, 0.0f))
|
|
.Text(LOCTEXT("ThresholdPreset120fps", "60/120 fps"))
|
|
.OnClicked_Lambda([this]() -> FReply
|
|
{
|
|
SetThresholdsFPS(60.0, 120.0);
|
|
return FReply::Handled();
|
|
})
|
|
];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::CreateSelectedFrameMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
FText SelectedFrameSectionName;
|
|
if (SelectedSample.IsValid())
|
|
{
|
|
SelectedFrameSectionName = FText::Format(LOCTEXT("ContextMenu_Section_SelectedFrame_Fmt", "{0} {1}"),
|
|
FFrameTrackDrawHelper::FrameTypeToText(SelectedSample.Series->GetFrameType()),
|
|
FText::AsNumber(SelectedSample.Sample->LargestFrameIndex));
|
|
}
|
|
else
|
|
{
|
|
SelectedFrameSectionName = LOCTEXT("ContextMenu_Section_NoFrameSelected", "No Frame Selected");
|
|
}
|
|
MenuBuilder.BeginSection("SelectedFrame", SelectedFrameSectionName);
|
|
|
|
FUIAction Action_ScrollLogView
|
|
(
|
|
FExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ScrollLogView_Execute),
|
|
FCanExecuteAction::CreateSP(this, &SFrameTrack::ContextMenu_ScrollLogView_CanExecute)
|
|
);
|
|
FText Label;
|
|
if (SelectedSample.IsValid())
|
|
{
|
|
FText StartTimeText = FText::FromString(FormatTimeAuto(SelectedSample.Sample->LargestFrameStartTime, 2));
|
|
Label = FText::Format(LOCTEXT("ContextMenu_ScrollLogView_Fmt", "Scroll Log View (\u2192 {0})"), StartTimeText);
|
|
}
|
|
else
|
|
{
|
|
Label = LOCTEXT("ContextMenu_ScrollLogView", "Scroll Log View");
|
|
}
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
Label,
|
|
LOCTEXT("ContextMenu_ScrollLogView_Desc", "Scrolls the Log View at the message with the closest timestamp to the start time of the selected frame."),
|
|
FSlateIcon(FInsightsStyle::GetStyleSetName(), "Icons.LogView"),
|
|
Action_ScrollLogView,
|
|
NAME_None,
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::ContextMenu_ShowGameFrames_Execute()
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> Series = FindSeries(ETraceFrameType::TraceFrameType_Game);
|
|
if (Series.IsValid())
|
|
{
|
|
Series->SetVisibility(!Series->IsVisible());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ShowGameFrames_CanExecute()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ShowGameFrames_IsChecked()
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> Series = FindSeries(ETraceFrameType::TraceFrameType_Game);
|
|
return Series.IsValid() ? Series->IsVisible() : false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::ContextMenu_ShowRenderingFrames_Execute()
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> Series = FindSeries(ETraceFrameType::TraceFrameType_Rendering);
|
|
if (Series.IsValid())
|
|
{
|
|
Series->SetVisibility(!Series->IsVisible());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ShowRenderingFrames_CanExecute()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ShowRenderingFrames_IsChecked()
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> Series = FindSeries(ETraceFrameType::TraceFrameType_Rendering);
|
|
return Series.IsValid() ? Series->IsVisible() : false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::ContextMenu_ShowFrameStats_Execute(ETraceFrameType FrameType, uint32 TimerId)
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> Series = FindFrameStatsSeries(FrameType, TimerId);
|
|
if (Series.IsValid())
|
|
{
|
|
Series->SetVisibility(!Series->IsVisible());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ShowFrameStats_CanExecute(ETraceFrameType FrameType, uint32 TimerId)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ShowFrameStats_IsChecked(ETraceFrameType FrameType, uint32 TimerId)
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> Series = FindFrameStatsSeries(FrameType, TimerId);
|
|
return Series.IsValid() ? Series->IsVisible() : false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::ContextMenu_AutoZoom_Execute()
|
|
{
|
|
bIsAutoZoomEnabled = !bIsAutoZoomEnabled;
|
|
|
|
if (bIsAutoZoomEnabled)
|
|
{
|
|
AutoZoom();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_AutoZoom_CanExecute()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_AutoZoom_IsChecked()
|
|
{
|
|
return bIsAutoZoomEnabled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::AutoZoom()
|
|
{
|
|
FAxisViewportInt32& ViewportX = Viewport.GetHorizontalAxisViewport();
|
|
|
|
AutoZoomViewportPos = ViewportX.GetMinPos();
|
|
ViewportX.ScrollAtPos(AutoZoomViewportPos);
|
|
|
|
AutoZoomViewportSize = ViewportX.GetSize();
|
|
|
|
if (AutoZoomViewportSize > 0.0f &&
|
|
ViewportX.GetMaxValue() - ViewportX.GetMinValue() > 0)
|
|
{
|
|
float DX = ViewportX.GetMaxPos() - ViewportX.GetMinPos();
|
|
|
|
// Auto zoom in.
|
|
while (DX < AutoZoomViewportSize)
|
|
{
|
|
const float OldScale = ViewportX.GetScale();
|
|
ViewportX.RelativeZoomWithFixedOffset(+0.1f, 0.0f);
|
|
ViewportX.ScrollAtPos(AutoZoomViewportPos);
|
|
DX = ViewportX.GetMaxPos() - ViewportX.GetMinPos();
|
|
if (OldScale == ViewportX.GetScale())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Auto zoom out (until entire session frame range fits into view).
|
|
while (DX > AutoZoomViewportSize)
|
|
{
|
|
const float OldScale = ViewportX.GetScale();
|
|
ViewportX.RelativeZoomWithFixedOffset(-0.1f, 0.0f);
|
|
ViewportX.ScrollAtPos(AutoZoomViewportPos);
|
|
DX = ViewportX.GetMaxPos() - ViewportX.GetMinPos();
|
|
if (OldScale == ViewportX.GetScale())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
AutoZoomViewportScale = ViewportX.GetScale();
|
|
|
|
UpdateHorizontalScrollBar();
|
|
bIsStateDirty = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::ContextMenu_ZoomTimingViewOnFrameSelection_Execute()
|
|
{
|
|
bZoomTimingViewOnFrameSelection = !bZoomTimingViewOnFrameSelection;
|
|
|
|
// Persistent option. Save it to the config file.
|
|
FInsightsSettings& Settings = FInsightsManager::Get()->GetSettings();
|
|
Settings.SetAndSaveAutoZoomOnFrameSelection(bZoomTimingViewOnFrameSelection);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ZoomTimingViewOnFrameSelection_CanExecute()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ZoomTimingViewOnFrameSelection_IsChecked()
|
|
{
|
|
return bZoomTimingViewOnFrameSelection;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::ContextMenu_ScrollLogView_Execute()
|
|
{
|
|
if (!SelectedSample.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<STimingProfilerWindow> TimingWindow = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
if (!TimingWindow.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<SLogView> LogView = TimingWindow->GetLogView();
|
|
if (!LogView.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
LogView->SelectLogMessageByClosestTime(SelectedSample.Sample->LargestFrameStartTime);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::ContextMenu_ScrollLogView_CanExecute()
|
|
{
|
|
if (!SelectedSample.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<STimingProfilerWindow> TimingWindow = FTimingProfilerManager::Get()->GetProfilerWindow();
|
|
if (!TimingWindow.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<SLogView> LogView = TimingWindow->GetLogView();
|
|
if (!LogView.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::BindCommands()
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::HorizontalScrollBar_OnUserScrolled(float ScrollOffset)
|
|
{
|
|
Viewport.GetHorizontalAxisViewport().OnUserScrolled(HorizontalScrollBar, ScrollOffset);
|
|
bIsStateDirty = true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SFrameTrack::UpdateHorizontalScrollBar()
|
|
{
|
|
Viewport.GetHorizontalAxisViewport().UpdateScrollBar(HorizontalScrollBar);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::HasFrameStatSeries(ETraceFrameType FrameType, uint32 TimerId) const
|
|
{
|
|
const TSharedPtr<FFrameTrackSeries>* ExistingSeries = AllSeries.FindByPredicate([FrameType, TimerId](TSharedPtr<FFrameTrackSeries> Series)
|
|
{
|
|
return Series->Is<FTimerFrameStatsTrackSeries>() &&
|
|
Series->As<FTimerFrameStatsTrackSeries>().GetFrameType() == FrameType &&
|
|
Series->As<FTimerFrameStatsTrackSeries>().GetTimerId() == TimerId;
|
|
});
|
|
|
|
return ExistingSeries != nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FTimerFrameStatsTrackSeries& SFrameTrack::AddTimerFrameStatSeries(ETraceFrameType FrameType, uint32 TimerId, FLinearColor Color, FText Name)
|
|
{
|
|
TSharedPtr<FFrameTrackSeries> ExistingSeries = FindFrameStatsSeries(FrameType, TimerId);
|
|
|
|
if (ExistingSeries.IsValid())
|
|
{
|
|
return ExistingSeries->As<FTimerFrameStatsTrackSeries>();
|
|
}
|
|
TSharedRef<FTimerFrameStatsTrackSeries> SeriesRef = MakeShared<FTimerFrameStatsTrackSeries>(FrameType, TimerId);
|
|
SeriesRef->SetColor(Color);
|
|
SeriesRef->SetName(Name);
|
|
AllSeries.Add(SeriesRef);
|
|
|
|
bIsStateDirty = true;
|
|
|
|
return *SeriesRef;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::RemoveTimerFrameStatSeries(ETraceFrameType FrameType, uint32 TimerId)
|
|
{
|
|
int32 NumRemoved = AllSeries.RemoveAll([FrameType, TimerId](TSharedPtr<FFrameTrackSeries> Series)
|
|
{
|
|
return Series->Is<FTimerFrameStatsTrackSeries>() &&
|
|
Series->As<FTimerFrameStatsTrackSeries>().GetFrameType() == FrameType &&
|
|
Series->As<FTimerFrameStatsTrackSeries>().GetTimerId() == TimerId;
|
|
});
|
|
|
|
ensure(NumRemoved == 1);
|
|
|
|
bIsStateDirty = true;
|
|
|
|
return NumRemoved >= 1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SFrameTrack::HasAnySeriesForTimer(uint32 TimerId) const
|
|
{
|
|
for (const TSharedPtr<FFrameTrackSeries>& Series : AllSeries)
|
|
{
|
|
if (Series->Is<FTimerFrameStatsTrackSeries>())
|
|
{
|
|
if (Series->As<FTimerFrameStatsTrackSeries>().GetTimerId() == TimerId)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
uint32 SFrameTrack::GetNumSeriesForTimer(uint32 TimerId) const
|
|
{
|
|
uint32 NumSeries = 0;
|
|
|
|
for (const TSharedPtr<FFrameTrackSeries>& Series : AllSeries)
|
|
{
|
|
if (Series->Is<FTimerFrameStatsTrackSeries>())
|
|
{
|
|
if (Series->As<FTimerFrameStatsTrackSeries>().GetTimerId() == TimerId)
|
|
{
|
|
++NumSeries;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NumSeries;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
} // namespace UE::Insights::TimingProfiler
|
|
|
|
#undef LOCTEXT_NAMESPACE
|