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

384 lines
9.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Insights/ViewModels/GraphTrackBuilder.h"
// TraceInsights
#include "Insights/ViewModels/GraphSeries.h"
#include "Insights/ViewModels/GraphTrack.h"
#include "Insights/ViewModels/GraphTrackEvent.h"
#include "Insights/ViewModels/TimingTrackViewport.h"
#define LOCTEXT_NAMESPACE "GraphTrack"
////////////////////////////////////////////////////////////////////////////////////////////////////
FGraphTrackBuilder::FGraphTrackBuilder(FGraphTrack& InTrack, FGraphSeries& InSeries, const FTimingTrackViewport& InViewport)
: Track(InTrack)
, Series(InSeries)
, Viewport(InViewport)
{
Series.Events.Reset();
BeginPoints();
BeginConnectedLines();
BeginBoxes();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FGraphTrackBuilder::~FGraphTrackBuilder()
{
EndPoints();
EndConnectedLines();
EndBoxes();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::AddEvent(double Time, double Duration, double Value, bool bConnected)
{
Track.NumAddedEvents++;
bool bKeepEvent = (Viewport.GetViewportDXForDuration(Duration) > 1.0f); // always keep the events wider than 1px
//if (Track.IsAnyOptionEnabled(EGraphOptions::ShowPoints))
{
bKeepEvent |= AddPoint(Time, Value);
}
if (Track.IsAnyOptionEnabled(EGraphOptions::ShowLines | EGraphOptions::ShowPolygon))
{
AddConnectedLine(Time, Value, !bConnected);
if (Track.IsAnyOptionEnabled(EGraphOptions::UseEventDuration) && Duration != 0.0)
{
AddConnectedLine(Time + Duration, Value, false);
}
}
if (Track.IsAnyOptionEnabled(EGraphOptions::ShowBars))
{
AddBox(Time, Duration, Value);
}
if (bKeepEvent)
{
Series.Events.Add({ Time, Duration, Value });
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// FGraphTrackBuilder - Points
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::BeginPoints()
{
Series.Points.Reset();
PointsCurrentX = -DBL_MAX;
PointsAtCurrentX.Reset();
int32 MaxPointsPerLineScan = FMath::CeilToInt(Track.GetHeight() / FGraphTrack::PointSizeY) + 1;
if (MaxPointsPerLineScan > 0)
{
PointsAtCurrentX.AddDefaulted(MaxPointsPerLineScan);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool FGraphTrackBuilder::AddPoint(double Time, double Value)
{
const float X = Viewport.TimeToSlateUnitsRounded(Time);
if (X < -FGraphTrack::PointVisualSize / 2.0f || X >= Viewport.GetWidth() + FGraphTrack::PointVisualSize / 2.0f)
{
return false;
}
// Align the X with a grid of GraphTrackPointDX pixels in size, in the global space (i.e. scroll independent).
const double AlignedX = FMath::RoundToDouble(Time * Viewport.GetScaleX() / FGraphTrack::PointSizeX) * FGraphTrack::PointSizeX;
if (AlignedX > PointsCurrentX + FGraphTrack::PointSizeX - 0.5)
{
FlushPoints();
PointsCurrentX = AlignedX;
}
const float Y = Series.GetRoundedYForValue(Value);
bool bIsNewVisiblePoint = false;
int32 Index = FMath::RoundToInt(Y / FGraphTrack::PointSizeY);
if (Index >= 0 && Index < PointsAtCurrentX.Num())
{
FPointInfo& Pt = PointsAtCurrentX[Index];
bIsNewVisiblePoint = !Pt.bValid;
Pt.bValid = true;
Pt.X = X;
Pt.Y = Y;
}
return bIsNewVisiblePoint;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::FlushPoints()
{
for (int32 Index = 0; Index < PointsAtCurrentX.Num(); ++Index)
{
FPointInfo& Pt = PointsAtCurrentX[Index];
if (Pt.bValid)
{
Pt.bValid = false;
Series.Points.Add(FVector2D(Pt.X, Pt.Y));
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::EndPoints()
{
FlushPoints();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// FGraphTrackBuilder - Connected Lines
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::BeginConnectedLines()
{
Series.LinePoints.Reset();
Series.LinePoints.AddDefaulted();
LinesCurrentX = -FLT_MAX;
LinesMinY = FLT_MAX;
LinesMaxY = -FLT_MAX;
LinesFirstY = FLT_MAX;
LinesLastY = FLT_MAX;
bIsLastLineAdded = false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::AddConnectedLine(double Time, double Value, bool bNewBatch)
{
if (bIsLastLineAdded)
{
return;
}
const float X = FMath::Max(LinesCurrentX, Viewport.TimeToSlateUnitsRounded(Viewport.RestrictEndTime(Time)));
const float Y = Series.GetRoundedYForValue(Value);
//ensure(X >= LinesCurrentX); // we are assuming events are already sorted by Time
// Start a new batch
if (bNewBatch)
{
if (Series.LinePoints.Num() > 0 && Series.LinePoints.Last().Num() > 0)
{
Series.LinePoints.AddDefaulted();
}
// Reset the reduction
LinesCurrentX = -FLT_MAX;
LinesMinY = FLT_MAX;
LinesMaxY = -FLT_MAX;
LinesFirstY = FLT_MAX;
LinesLastY = FLT_MAX;
}
if (X < 0)
{
LinesCurrentX = X;
LinesLastY = Y;
return;
}
if (X >= Viewport.GetWidth())
{
if (!bIsLastLineAdded)
{
bIsLastLineAdded = true;
if (LinesLastY != FLT_MAX)
{
FlushConnectedLine();
AddConnectedLinePoint(LinesCurrentX, LinesLastY);
AddConnectedLinePoint(X, Y);
}
// Reset the "reduction line" so last FlushConnectedLine() call will do nothing.
LinesMinY = Y;
LinesMaxY = Y;
}
return;
}
if (X > LinesCurrentX)
{
if (LinesLastY != FLT_MAX)
{
FlushConnectedLine();
AddConnectedLinePoint(LinesCurrentX, LinesLastY);
AddConnectedLinePoint(X, Y);
}
// Advance the "reduction line".
LinesCurrentX = X;
LinesMinY = Y;
LinesMaxY = Y;
LinesFirstY = Y;
LinesLastY = Y;
}
else
{
// Merge current line with the "reduction line".
if (Y < LinesMinY)
{
LinesMinY = Y;
}
if (Y > LinesMaxY)
{
LinesMaxY = Y;
}
LinesLastY = Y;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::FlushConnectedLine()
{
if (LinesCurrentX >= 0.0f && LinesMinY != LinesMaxY)
{
AddConnectedLinePoint(LinesCurrentX, LinesMaxY);
AddConnectedLinePoint(LinesCurrentX, LinesMinY);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::AddConnectedLinePoint(float X, float Y)
{
TArray<FVector2D>& LinePoints = Series.LinePoints.Last();
if (LinePoints.Num() > 0)
{
const FVector2D& LastPoint = LinePoints.Last();
if (X == LastPoint.X && Y == LastPoint.Y)
{
return;
}
}
LinePoints.Add(FVector2D(X, Y));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::EndConnectedLines()
{
FlushConnectedLine();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// FGraphTrackBuilder - Boxes
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::BeginBoxes()
{
Series.Boxes.Reset();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::AddBox(double Time, double Duration, double Value)
{
float X1 = Viewport.TimeToSlateUnitsRounded(Time);
if (X1 > Viewport.GetWidth())
{
return;
}
double EndTime = Viewport.RestrictEndTime(Time + Duration);
float X2 = Viewport.TimeToSlateUnitsRounded(EndTime);
if (X2 < 0)
{
return;
}
float W = X2 - X1;
ensure(W >= 0); // we expect events to be sorted
// Timing events are displayed with minimum 1px (including empty ones).
if (W == 0)
{
W = 1.0f;
}
const float Y = Series.GetRoundedYForValue(Value);
// simple reduction
if (W == 1.0f && Series.Boxes.Num() > 0)
{
FGraphSeries::FBox& LastBox = Series.Boxes.Last();
if (LastBox.W == 1.0f && LastBox.X == X1)
{
const float RoundedBaselineY = static_cast<float>(FMath::RoundToFloat(Series.GetBaselineY()));
if (LastBox.Y < RoundedBaselineY)
{
if (Y < RoundedBaselineY)
{
// Merge current box with previous one.
if (Y < LastBox.Y)
{
LastBox.Y = Y;
}
return;
}
}
else
{
if (Y >= RoundedBaselineY)
{
// Merge current box with previous one.
if (Y > LastBox.Y)
{
LastBox.Y = Y;
}
return;
}
}
}
}
FGraphSeries::FBox Box;
Box.X = X1;
Box.W = W;
Box.Y = Y;
Series.Boxes.Add(Box);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::FlushBox()
{
// TODO: reduction algorithm
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FGraphTrackBuilder::EndBoxes()
{
FlushBox();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE