819 lines
32 KiB
C++
819 lines
32 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MemoryGraphTrack.h"
|
|
|
|
#include "Fonts/FontMeasure.h"
|
|
#include "Fonts/SlateFontInfo.h"
|
|
#include "Styling/SlateBrush.h"
|
|
#include "TraceServices/AnalysisService.h"
|
|
#include "TraceServices/Model/TimingProfiler.h"
|
|
#include "TraceServices/Model/Counters.h"
|
|
#include "TraceServices/Model/Memory.h"
|
|
|
|
// Insights
|
|
#include "Insights/Common/PaintUtils.h"
|
|
#include "Insights/Common/TimeUtils.h"
|
|
#include "Insights/InsightsManager.h"
|
|
#include "Insights/MemoryProfiler/ViewModels/MemorySharedState.h"
|
|
#include "Insights/ViewModels/AxisViewportDouble.h"
|
|
#include "Insights/ViewModels/GraphTrackBuilder.h"
|
|
#include "Insights/ViewModels/ITimingViewDrawHelper.h"
|
|
#include "Insights/ViewModels/TimingTrackViewport.h"
|
|
#include "Insights/ViewModels/TooltipDrawState.h"
|
|
|
|
#include <limits>
|
|
|
|
#define LOCTEXT_NAMESPACE "MemoryGraphTrack"
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// FMemoryGraphSeries
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FString FMemoryGraphSeries::FormatValue(double Value) const
|
|
{
|
|
const int64 MemValue = static_cast<int64>(Value);
|
|
if (MemValue == 0)
|
|
{
|
|
return TEXT("0");
|
|
}
|
|
else if (MemValue < 0)
|
|
{
|
|
return FString::Printf(TEXT("-%s (%s bytes)"), *FText::AsMemory(-MemValue).ToString(), *FText::AsNumber(MemValue).ToString());
|
|
}
|
|
else
|
|
{
|
|
return FString::Printf(TEXT("%s (%s bytes)"), *FText::AsMemory(MemValue).ToString(), *FText::AsNumber(MemValue).ToString());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// FMemoryGraphTrack
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
INSIGHTS_IMPLEMENT_RTTI(FMemoryGraphTrack)
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FMemoryGraphTrack::FMemoryGraphTrack(FMemorySharedState& InSharedState)
|
|
: FGraphTrack(), SharedState(InSharedState), LabelUnit(EGraphTrackLabelUnit::Auto), LabelDecimalDigitCount(-1), DefaultMinValue(+std::numeric_limits<double>::infinity()), DefaultMaxValue(-std::numeric_limits<double>::infinity()), AllSeriesMinValue(0.0), AllSeriesMaxValue(0.0), bAutoZoom(false), bIsStacked(false)
|
|
{
|
|
EnabledOptions = // EGraphOptions::ShowDebugInfo |
|
|
// EGraphOptions::ShowPoints |
|
|
EGraphOptions::ShowPointsWithBorder |
|
|
EGraphOptions::ShowLines |
|
|
EGraphOptions::ShowPolygon |
|
|
EGraphOptions::UseEventDuration |
|
|
// EGraphOptions::ShowBars |
|
|
// EGraphOptions::ShowBaseline |
|
|
EGraphOptions::ShowVerticalAxisGrid |
|
|
EGraphOptions::ShowHeader |
|
|
EGraphOptions::None;
|
|
|
|
for (uint32 Mode = 0; Mode < static_cast<uint32>(EMemoryTrackHeightMode::Count); ++Mode)
|
|
{
|
|
SetAvailableTrackHeight(static_cast<EMemoryTrackHeightMode>(Mode), 100.0f * (Mode + 1));
|
|
}
|
|
SetHeight(200.0f);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::ResetDefaultValueRange()
|
|
{
|
|
DefaultMinValue = +std::numeric_limits<double>::infinity();
|
|
DefaultMaxValue = -std::numeric_limits<double>::infinity();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FMemoryGraphTrack::~FMemoryGraphTrack()
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::Update(const ITimingTrackUpdateContext& Context)
|
|
{
|
|
FGraphTrack::Update(Context);
|
|
|
|
const bool bIsEntireGraphTrackDirty = IsDirty() || Context.GetViewport().IsHorizontalViewportDirty();
|
|
bool bNeedsUpdate = bIsEntireGraphTrackDirty;
|
|
|
|
if (!bNeedsUpdate)
|
|
{
|
|
for (TSharedPtr<FGraphSeries>& Series: AllSeries)
|
|
{
|
|
if (Series->IsVisible() && Series->IsDirty())
|
|
{
|
|
// At least one series is dirty.
|
|
bNeedsUpdate = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bNeedsUpdate)
|
|
{
|
|
ClearDirtyFlag();
|
|
|
|
NumAddedEvents = 0;
|
|
|
|
const FTimingTrackViewport& Viewport = Context.GetViewport();
|
|
|
|
AllSeriesMinValue = DefaultMinValue;
|
|
AllSeriesMaxValue = DefaultMaxValue;
|
|
|
|
for (TSharedPtr<FGraphSeries>& Series: AllSeries)
|
|
{
|
|
if (Series->IsVisible())
|
|
{
|
|
// TODO: if (Series->Is<FMemoryGraphSeries>())
|
|
TSharedPtr<FMemoryGraphSeries> MemorySeries = StaticCastSharedPtr<FMemoryGraphSeries>(Series);
|
|
|
|
if (bIsEntireGraphTrackDirty || Series->IsDirty())
|
|
{
|
|
PreUpdateMemTagSeries(*MemorySeries, Viewport);
|
|
}
|
|
|
|
const double CurrentSeriesMinValue = MemorySeries->GetMinValue();
|
|
const double CurrentSeriesMaxValue = MemorySeries->GetMaxValue();
|
|
if (CurrentSeriesMinValue < AllSeriesMinValue)
|
|
{
|
|
AllSeriesMinValue = CurrentSeriesMinValue;
|
|
}
|
|
if (CurrentSeriesMaxValue > AllSeriesMaxValue)
|
|
{
|
|
AllSeriesMaxValue = CurrentSeriesMaxValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAutoZoom)
|
|
{
|
|
const float TopY = 4.0f;
|
|
const float BottomY = GetHeight() - 4.0f;
|
|
if (TopY < BottomY)
|
|
{
|
|
for (TSharedPtr<FGraphSeries>& Series: AllSeries)
|
|
{
|
|
if (Series->IsVisible())
|
|
{
|
|
if (Series->UpdateAutoZoomEx(TopY, BottomY, AllSeriesMinValue, AllSeriesMaxValue, true))
|
|
{
|
|
Series->SetDirtyFlag();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TSharedPtr<FGraphSeries>& Series: AllSeries)
|
|
{
|
|
if (Series->IsVisible() && (bIsEntireGraphTrackDirty || Series->IsDirty()))
|
|
{
|
|
// Clear the flag before updating, because the update itself may further need to set the series as dirty.
|
|
Series->ClearDirtyFlag();
|
|
|
|
// TODO: if (Series->Is<FMemoryGraphSeries>())
|
|
TSharedPtr<FMemoryGraphSeries> MemorySeries = StaticCastSharedPtr<FMemoryGraphSeries>(Series);
|
|
UpdateMemTagSeries(*MemorySeries, Viewport);
|
|
|
|
if (Series->IsAutoZoomDirty())
|
|
{
|
|
Series->SetDirtyFlag();
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateStats();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// LLM Tag Series
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<FMemoryGraphSeries> FMemoryGraphTrack::GetMemTagSeries(Insights::FMemoryTagId MemTagId)
|
|
{
|
|
TSharedPtr<FGraphSeries>* Ptr = AllSeries.FindByPredicate([MemTagId](const TSharedPtr<FGraphSeries>& Series)
|
|
{
|
|
// TODO: if (Series->Is<FMemoryGraphSeries>())
|
|
const TSharedPtr<FMemoryGraphSeries> MemorySeries = StaticCastSharedPtr<FMemoryGraphSeries>(Series);
|
|
return MemorySeries->GetTagId() == MemTagId;
|
|
});
|
|
return (Ptr != nullptr) ? StaticCastSharedPtr<FMemoryGraphSeries>(*Ptr) : nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedPtr<FMemoryGraphSeries> FMemoryGraphTrack::AddMemTagSeries(Insights::FMemoryTrackerId MemTrackerId, Insights::FMemoryTagId MemTagId)
|
|
{
|
|
TSharedPtr<FMemoryGraphSeries> Series = GetMemTagSeries(MemTagId);
|
|
|
|
if (!Series.IsValid())
|
|
{
|
|
Series = MakeShared<FMemoryGraphSeries>();
|
|
|
|
Series->SetName(TEXT("LLM Tag"));
|
|
Series->SetDescription(TEXT("Low Level Memory Tag"));
|
|
|
|
Series->SetTrackerId(MemTrackerId);
|
|
Series->SetTagId(MemTagId);
|
|
Series->SetValueRange(0.0f, 0.0f);
|
|
|
|
Series->SetBaselineY(GetHeight() - 1.0f);
|
|
Series->SetScaleY(1.0);
|
|
|
|
AllSeries.Add(Series);
|
|
SetDirtyFlag();
|
|
}
|
|
|
|
return Series;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int32 FMemoryGraphTrack::RemoveMemTagSeries(Insights::FMemoryTagId MemTagId)
|
|
{
|
|
SetDirtyFlag();
|
|
return AllSeries.RemoveAll([MemTagId](const TSharedPtr<FGraphSeries>& GraphSeries)
|
|
{
|
|
// TODO: if (Series->Is<FMemoryGraphSeries>())
|
|
const TSharedPtr<FMemoryGraphSeries> Series = StaticCastSharedPtr<FMemoryGraphSeries>(GraphSeries);
|
|
return Series->GetTagId() == MemTagId;
|
|
});
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int32 FMemoryGraphTrack::RemoveAllMemTagSeries()
|
|
{
|
|
SetDirtyFlag();
|
|
int32 Count = AllSeries.Num();
|
|
AllSeries.Reset();
|
|
return Count;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::PreUpdateMemTagSeries(FMemoryGraphSeries& Series, const FTimingTrackViewport& Viewport)
|
|
{
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid())
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
// Compute Min/Max values.
|
|
const Trace::IMemoryProvider& MemoryProvider = Trace::ReadMemoryProvider(*Session.Get());
|
|
const uint64 TotalSampleCount = MemoryProvider.GetTagSampleCount(Series.GetTrackerId(), Series.GetTagId());
|
|
if (TotalSampleCount > 0)
|
|
{
|
|
double MinValue = +std::numeric_limits<double>::infinity();
|
|
double MaxValue = -std::numeric_limits<double>::infinity();
|
|
|
|
MemoryProvider.EnumerateTagSamples(Series.GetTrackerId(), Series.GetTagId(), Viewport.GetStartTime(), Viewport.GetEndTime(), true,
|
|
[&MinValue, &MaxValue](double Time, double Duration, const Trace::FMemoryTagSample& Sample)
|
|
{
|
|
const double Value = static_cast<double>(Sample.Value);
|
|
if (Value < MinValue)
|
|
{
|
|
MinValue = Value;
|
|
}
|
|
if (Value > MaxValue)
|
|
{
|
|
MaxValue = Value;
|
|
}
|
|
});
|
|
|
|
Series.SetValueRange(MinValue, MaxValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::UpdateMemTagSeries(FMemoryGraphSeries& Series, const FTimingTrackViewport& Viewport)
|
|
{
|
|
FGraphTrackBuilder Builder(*this, Series, Viewport);
|
|
|
|
TSharedPtr<const Trace::IAnalysisSession> Session = FInsightsManager::Get()->GetSession();
|
|
if (Session.IsValid())
|
|
{
|
|
Trace::FAnalysisSessionReadScope SessionReadScope(*Session.Get());
|
|
|
|
const Trace::IMemoryProvider& MemoryProvider = Trace::ReadMemoryProvider(*Session.Get());
|
|
|
|
const uint64 TotalSampleCount = MemoryProvider.GetTagSampleCount(Series.GetTrackerId(), Series.GetTagId());
|
|
|
|
if (TotalSampleCount > 0)
|
|
{
|
|
const float TopY = 4.0f;
|
|
const float BottomY = GetHeight() - 4.0f;
|
|
|
|
if (Series.IsAutoZoomEnabled() && TopY < BottomY)
|
|
{
|
|
Series.UpdateAutoZoom(TopY, BottomY, Series.GetMinValue(), Series.GetMaxValue());
|
|
}
|
|
|
|
MemoryProvider.EnumerateTagSamples(Series.GetTrackerId(), Series.GetTagId(), Viewport.GetStartTime(), Viewport.GetEndTime(), true,
|
|
[this, &Builder](double Time, double Duration, const Trace::FMemoryTagSample& Sample)
|
|
{
|
|
Builder.AddEvent(Time, Duration, static_cast<double>(Sample.Value));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::Draw(const ITimingTrackDrawContext& Context) const
|
|
{
|
|
FGraphTrack::Draw(Context);
|
|
|
|
// Show warnings for series using llm tag id with incompatible tracker.
|
|
{
|
|
int WarningCount = 0;
|
|
float X = 12.0f;
|
|
float Y = GetPosY() + 12.0f;
|
|
const float MaxY = GetPosY() + GetHeight() - 8.0f;
|
|
for (const TSharedPtr<FGraphSeries>& Series: AllSeries)
|
|
{
|
|
if (Series->IsVisible())
|
|
{
|
|
if (Y > MaxY)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// TODO: if (Series->Is<FMemoryGraphSeries>())
|
|
TSharedPtr<FMemoryGraphSeries> MemorySeries = StaticCastSharedPtr<FMemoryGraphSeries>(Series);
|
|
|
|
const Insights::FMemoryTag* Tag = SharedState.GetTagList().GetTagById(MemorySeries->GetTagId());
|
|
const uint64 TrackerFlag = 1ULL << MemorySeries->GetTrackerId();
|
|
|
|
if ((Tag->GetTrackers() & TrackerFlag) != TrackerFlag)
|
|
{
|
|
FDrawContext& DrawContext = Context.GetDrawContext();
|
|
const FSlateBrush* Brush = Context.GetHelper().GetWhiteBrush();
|
|
|
|
if (WarningCount == 0)
|
|
{
|
|
const FText WarningText1 = FText::Format(LOCTEXT("WarningFmt1", "Warning: One or more LLM tags in this graph are not used by the current tracker ({0})!"),
|
|
SharedState.GetCurrentTracker() ? FText::FromString(SharedState.GetCurrentTracker()->GetName()) : FText::GetEmpty());
|
|
const FLinearColor WarningTextColor1(1.0f, 0.2f, 0.2f, 1.0f);
|
|
DrawContext.DrawText(DrawContext.LayerId, X, Y, WarningText1.ToString(), Font, WarningTextColor1);
|
|
Y += 12.0f;
|
|
|
|
const FText WarningText2 = FText::Format(LOCTEXT("WarningLine2", "In order to see graph series for these LLM tags, change the current tracker to one that uses the LLM tag."),
|
|
SharedState.GetCurrentTracker() ? FText::FromString(SharedState.GetCurrentTracker()->GetName()) : FText::GetEmpty());
|
|
const FLinearColor WarningTextColor2(0.75f, 0.5f, 0.25f, 1.0f);
|
|
DrawContext.DrawText(DrawContext.LayerId, X, Y, WarningText2.ToString(), Font, WarningTextColor2);
|
|
Y += 12.0f;
|
|
}
|
|
|
|
if (Tag->GetTrackers() != 0)
|
|
{
|
|
const FText Conjunction = LOCTEXT("WarningTrackersConjunction", " and the ");
|
|
const FText Text = FText::Format(LOCTEXT("WarningFmt2", "The {0} LLM tag is only used by the {1} tracker(s)."),
|
|
FText::FromString(Tag->GetStatName()),
|
|
FText::FromString(SharedState.TrackersToString(Tag->GetTrackers(), *Conjunction.ToString())));
|
|
const FLinearColor TextColor(1.0f, 0.75f, 0.5f, 1.0f);
|
|
DrawContext.DrawText(DrawContext.LayerId, X, Y, Text.ToString(), Font, TextColor);
|
|
Y += 12.0f;
|
|
}
|
|
else
|
|
{
|
|
const FText Text = FText::Format(LOCTEXT("WarningFmt3", "The {0} LLM tag is not used by any tracker."),
|
|
FText::FromString(Tag->GetStatName()));
|
|
const FLinearColor TextColor(1.0f, 0.75f, 0.5f, 1.0f);
|
|
DrawContext.DrawText(DrawContext.LayerId, X, Y, Text.ToString(), Font, TextColor);
|
|
Y += 12.0f;
|
|
}
|
|
|
|
++WarningCount;
|
|
}
|
|
}
|
|
}
|
|
if (WarningCount > 0)
|
|
{
|
|
FDrawContext& DrawContext = Context.GetDrawContext();
|
|
DrawContext.LayerId++;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::DrawVerticalAxisGrid(const ITimingTrackDrawContext& Context) const
|
|
{
|
|
TSharedPtr<FMemoryGraphSeries> Series = MainSeries;
|
|
|
|
if (!Series.IsValid() && AllSeries.Num() > 0)
|
|
{
|
|
// TODO: if (Series->Is<FMemoryGraphSeries>())
|
|
Series = StaticCastSharedPtr<FMemoryGraphSeries>(AllSeries[0]);
|
|
}
|
|
|
|
if (!Series.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FAxisViewportDouble ViewportY;
|
|
ViewportY.SetSize(GetHeight());
|
|
ViewportY.SetScaleLimits(std::numeric_limits<double>::min(), std::numeric_limits<double>::max());
|
|
ViewportY.SetScale(Series->GetScaleY());
|
|
ViewportY.ScrollAtPos(Series->GetBaselineY() - GetHeight());
|
|
|
|
const float ViewWidth = Context.GetViewport().GetWidth();
|
|
const float RoundedViewHeight = FMath::RoundToFloat(GetHeight());
|
|
|
|
const float X0 = ViewWidth - 12.0f; // let some space for the vertical scrollbar
|
|
const float Y0 = GetPosY();
|
|
|
|
constexpr float MinDY = 32.0f; // min vertical distance between horizontal grid lines
|
|
constexpr float TextH = 14.0f; // label height
|
|
|
|
const float MinLabelY = Y0 + 1.0f;
|
|
const float MaxLabelY = Y0 + RoundedViewHeight - TextH;
|
|
|
|
float MinValueY = Y0 - MinDY; // a value below the track
|
|
float MaxValueY = Y0 + RoundedViewHeight + MinDY; // a value above the track
|
|
float ActualMinValueY = MinValueY;
|
|
float ActualMaxValueY = MaxValueY;
|
|
|
|
const bool bHasMinMax = (AllSeriesMinValue <= AllSeriesMaxValue);
|
|
if (bHasMinMax)
|
|
{
|
|
const float MinValueOffset = ViewportY.GetOffsetForValue(AllSeriesMinValue);
|
|
const float MinValueRoundedOffset = FMath::RoundToFloat(MinValueOffset);
|
|
ActualMinValueY = Y0 + RoundedViewHeight - MinValueRoundedOffset;
|
|
MinValueY = FMath::Min(MaxLabelY, FMath::Max(MinLabelY, ActualMinValueY - TextH / 2));
|
|
|
|
const float MaxValueOffset = ViewportY.GetOffsetForValue(AllSeriesMaxValue);
|
|
const float MaxValueRoundedOffset = FMath::RoundToFloat(MaxValueOffset);
|
|
ActualMaxValueY = Y0 + RoundedViewHeight - MaxValueRoundedOffset;
|
|
MaxValueY = FMath::Min(MaxLabelY, FMath::Max(MinLabelY, ActualMaxValueY - TextH / 2));
|
|
}
|
|
|
|
FDrawContext& DrawContext = Context.GetDrawContext();
|
|
const FSlateBrush* Brush = Context.GetHelper().GetWhiteBrush();
|
|
// const FSlateFontInfo& Font = Context.GetHelper().GetEventFont();
|
|
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
|
|
const double TopValue = ViewportY.GetValueAtOffset(RoundedViewHeight);
|
|
const double GridValue = ViewportY.GetValueAtOffset(MinDY);
|
|
const double BottomValue = ViewportY.GetValueAtOffset(0.0f);
|
|
const double Delta = GridValue - BottomValue;
|
|
|
|
double Precision = FMath::Abs(TopValue - BottomValue);
|
|
for (int32 Digit = FMath::Abs(LabelDecimalDigitCount); Digit > 0; --Digit)
|
|
{
|
|
Precision *= 10.0;
|
|
}
|
|
|
|
if (Delta > 0.0)
|
|
{
|
|
int64 DeltaBytes = static_cast<int64>(Delta);
|
|
if (DeltaBytes <= 0)
|
|
{
|
|
DeltaBytes = 1;
|
|
}
|
|
|
|
// Compute rounding based on magnitude of visible range of values (Delta).
|
|
int64 Delta2 = DeltaBytes;
|
|
int64 Power2 = 1;
|
|
while (Delta2 > 0)
|
|
{
|
|
Delta2 >>= 1;
|
|
Power2 <<= 1;
|
|
}
|
|
if (Power2 >> 1 == DeltaBytes)
|
|
{
|
|
Power2 >>= 1;
|
|
}
|
|
const double Grid = static_cast<double>(Power2);
|
|
const double StartValue = FMath::GridSnap(BottomValue, Grid);
|
|
|
|
TDrawHorizontalAxisLabelParams Params(DrawContext, Brush, FontMeasureService);
|
|
Params.TextBgColor = FLinearColor(0.05f, 0.05f, 0.05f, 1.0f);
|
|
Params.TextColor = FLinearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
Params.X = X0;
|
|
// Params.Y;
|
|
// Params.Value;
|
|
Params.Precision = Precision;
|
|
|
|
const FLinearColor GridColor(0.0f, 0.0f, 0.0f, 0.1f);
|
|
|
|
for (double Value = StartValue; Value < TopValue; Value += Grid)
|
|
{
|
|
const float Y = Y0 + RoundedViewHeight - FMath::RoundToFloat(ViewportY.GetOffsetForValue(Value));
|
|
|
|
const float LabelY = FMath::Min(MaxLabelY, FMath::Max(MinLabelY, Y - TextH / 2));
|
|
|
|
// Do not overlap with the min/max values.
|
|
if (bHasMinMax && (FMath::Abs(LabelY - MinValueY) < TextH || FMath::Abs(LabelY - MaxValueY) < TextH))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Draw horizontal grid line.
|
|
DrawContext.DrawBox(0, Y, ViewWidth, 1, Brush, GridColor);
|
|
|
|
// Draw label.
|
|
Params.Y = LabelY;
|
|
Params.Value = Value;
|
|
DrawHorizontalAxisLabel(Params);
|
|
}
|
|
}
|
|
|
|
if (bHasMinMax && GetHeight() >= TextH)
|
|
{
|
|
TDrawHorizontalAxisLabelParams Params(DrawContext, Brush, FontMeasureService);
|
|
|
|
if (MainSeries.IsValid() || AllSeries.Num() == 1)
|
|
{
|
|
Params.TextBgColor = (Series->GetColor() * 0.05f).CopyWithNewOpacity(1.0f);
|
|
Params.TextColor = Series->GetBorderColor().CopyWithNewOpacity(1.0f);
|
|
}
|
|
else
|
|
{
|
|
Params.TextBgColor = FLinearColor(0.02f, 0.02f, 0.02f, 1.0f);
|
|
Params.TextColor = FLinearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
}
|
|
|
|
Params.X = X0;
|
|
// Params.Y;
|
|
// Params.Value;
|
|
Params.Precision = -Precision; // format with detailed text
|
|
|
|
int32 MinMaxAxis = 0;
|
|
|
|
// Draw horizontal axis at the max value.
|
|
if (MaxValueY >= Y0 && MaxValueY <= Y0 + RoundedViewHeight)
|
|
{
|
|
Params.Value = AllSeriesMaxValue;
|
|
Params.Y = MaxValueY;
|
|
DrawHorizontalAxisLabel(Params);
|
|
++MinMaxAxis;
|
|
}
|
|
|
|
// Draw horizontal axis at the min value.
|
|
if (MinValueY >= Y0 && MinValueY <= Y0 + RoundedViewHeight && FMath::Abs(MaxValueY - MinValueY) > TextH)
|
|
{
|
|
Params.Y = MinValueY;
|
|
Params.Value = AllSeriesMinValue;
|
|
DrawHorizontalAxisLabel(Params);
|
|
++MinMaxAxis;
|
|
}
|
|
|
|
if (MinMaxAxis == 2)
|
|
{
|
|
const float MX = Context.GetMousePosition().X;
|
|
const float MY = Context.GetMousePosition().Y;
|
|
|
|
// constexpr float MX1 = 80.0f; // start fading out
|
|
constexpr float MX2 = 120.0f; // completly faded out
|
|
|
|
if (MX > ViewWidth - MX2 && MY >= MaxValueY && MY < MinValueY + TextH)
|
|
{
|
|
// const float Alpha = FMath::Min(1.0f, (MX - ViewWidth + MX2) / (MX2 - MX1));
|
|
// Params.TextBgColor.A = Alpha;
|
|
// Params.TextColor.A = Alpha;
|
|
|
|
const float LineX = MX - 16.0f;
|
|
DrawContext.DrawBox(DrawContext.LayerId + 1, LineX, ActualMaxValueY, X0 - LineX, 1.0f, Params.Brush, Params.TextBgColor);
|
|
DrawContext.DrawBox(DrawContext.LayerId + 1, LineX, ActualMaxValueY, 1.0f, ActualMinValueY - ActualMaxValueY, Params.Brush, Params.TextBgColor);
|
|
DrawContext.DrawBox(DrawContext.LayerId + 1, LineX, ActualMinValueY, X0 - LineX, 1.0f, Params.Brush, Params.TextBgColor);
|
|
|
|
DrawContext.LayerId += 3; // ensure to draw on top of other labels
|
|
|
|
Params.X = MX;
|
|
Params.Y = MY - TextH / 2;
|
|
Params.Value = AllSeriesMaxValue - AllSeriesMinValue;
|
|
Params.Precision = -Precision * 0.1; // format with detailed text and increased precision
|
|
Params.Prefix = TEXT("\u0394=");
|
|
DrawHorizontalAxisLabel(Params);
|
|
}
|
|
}
|
|
}
|
|
|
|
DrawContext.LayerId += 3;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::DrawHorizontalAxisLabel(const TDrawHorizontalAxisLabelParams& Params) const
|
|
{
|
|
FString LabelText = Params.Prefix;
|
|
|
|
if (FMath::IsNearlyZero(Params.Value, 0.5))
|
|
{
|
|
LabelText += TEXT("0");
|
|
}
|
|
else
|
|
{
|
|
double UnitValue;
|
|
const TCHAR* UnitText;
|
|
GetUnit(LabelUnit, FMath::Abs(Params.Precision), UnitValue, UnitText);
|
|
|
|
LabelText += FormatValue(Params.Value, UnitValue, UnitText, LabelDecimalDigitCount);
|
|
|
|
if (Params.Precision < 0 && LabelUnit == EGraphTrackLabelUnit::Auto)
|
|
{
|
|
double ValueUnitValue;
|
|
const TCHAR* ValueUnitText;
|
|
GetUnit(LabelUnit, Params.Value, ValueUnitValue, ValueUnitText);
|
|
if (ValueUnitValue > UnitValue)
|
|
{
|
|
FString LabelTextDetail = FormatValue(Params.Value, ValueUnitValue, ValueUnitText, LabelDecimalDigitCount);
|
|
LabelText += TEXT(" (");
|
|
LabelText += LabelTextDetail;
|
|
LabelText += TEXT(")");
|
|
}
|
|
}
|
|
}
|
|
|
|
const FVector2D TextSize = Params.FontMeasureService->Measure(LabelText, Font);
|
|
constexpr float TextH = 14.0f;
|
|
|
|
// Draw background for value text.
|
|
Params.DrawContext.DrawBox(Params.DrawContext.LayerId + 1, Params.X - TextSize.X - 4.0f, Params.Y, TextSize.X + 4.0f, TextH, Params.Brush, Params.TextBgColor);
|
|
|
|
// Draw value text.
|
|
Params.DrawContext.DrawText(Params.DrawContext.LayerId + 2, Params.X - TextSize.X - 2.0f, Params.Y + 1.0f, LabelText, Font, Params.TextColor);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::GetUnit(const EGraphTrackLabelUnit InLabelUnit, const double InPrecision, double& OutUnitValue, const TCHAR*& OutUnitText)
|
|
{
|
|
constexpr double KiB = (double)(1LL << 10); // 2^10 bytes
|
|
constexpr double MiB = (double)(1LL << 20); // 2^20 bytes
|
|
constexpr double GiB = (double)(1LL << 30); // 2^30 bytes
|
|
constexpr double TiB = (double)(1LL << 40); // 2^40 bytes
|
|
constexpr double PiB = (double)(1LL << 50); // 2^50 bytes
|
|
constexpr double EiB = (double)(1LL << 60); // 2^60 bytes
|
|
|
|
switch (InLabelUnit)
|
|
{
|
|
case EGraphTrackLabelUnit::Auto: {
|
|
if (InPrecision >= EiB)
|
|
{
|
|
OutUnitValue = EiB;
|
|
OutUnitText = TEXT("EiB");
|
|
}
|
|
else if (InPrecision >= PiB)
|
|
{
|
|
OutUnitValue = PiB;
|
|
OutUnitText = TEXT("PiB");
|
|
}
|
|
else if (InPrecision >= TiB)
|
|
{
|
|
OutUnitValue = TiB;
|
|
OutUnitText = TEXT("TiB");
|
|
}
|
|
else if (InPrecision >= GiB)
|
|
{
|
|
OutUnitValue = GiB;
|
|
OutUnitText = TEXT("GiB");
|
|
}
|
|
else if (InPrecision >= MiB)
|
|
{
|
|
OutUnitValue = MiB;
|
|
OutUnitText = TEXT("MiB");
|
|
}
|
|
else if (InPrecision >= KiB)
|
|
{
|
|
OutUnitValue = KiB;
|
|
OutUnitText = TEXT("KiB");
|
|
}
|
|
else
|
|
{
|
|
OutUnitValue = 1.0;
|
|
OutUnitText = TEXT("B");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EGraphTrackLabelUnit::KiB:
|
|
OutUnitValue = KiB;
|
|
OutUnitText = TEXT("KiB");
|
|
break;
|
|
|
|
case EGraphTrackLabelUnit::MiB:
|
|
OutUnitValue = MiB;
|
|
OutUnitText = TEXT("MiB");
|
|
break;
|
|
|
|
case EGraphTrackLabelUnit::GiB:
|
|
OutUnitValue = GiB;
|
|
OutUnitText = TEXT("GiB");
|
|
break;
|
|
|
|
case EGraphTrackLabelUnit::TiB:
|
|
OutUnitValue = TiB;
|
|
OutUnitText = TEXT("TiB");
|
|
break;
|
|
|
|
case EGraphTrackLabelUnit::PiB:
|
|
OutUnitValue = PiB;
|
|
OutUnitText = TEXT("PiB");
|
|
break;
|
|
|
|
case EGraphTrackLabelUnit::EiB:
|
|
OutUnitValue = EiB;
|
|
OutUnitText = TEXT("EiB");
|
|
break;
|
|
|
|
case EGraphTrackLabelUnit::Byte:
|
|
default:
|
|
OutUnitValue = 1.0;
|
|
OutUnitText = TEXT("B");
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FString FMemoryGraphTrack::FormatValue(const double InValue, const double InUnitValue, const TCHAR* InUnitText, const int32 InDecimalDigitCount)
|
|
{
|
|
FString OutText;
|
|
|
|
TCHAR FormatString[32];
|
|
FCString::Snprintf(FormatString, sizeof(FormatString), TEXT("%%.%df"), FMath::Abs(InDecimalDigitCount));
|
|
OutText = FString::Printf(FormatString, InValue / InUnitValue);
|
|
|
|
if (InDecimalDigitCount < 0)
|
|
{
|
|
// Remove ending 0s.
|
|
while (OutText.Len() > 0 && OutText[OutText.Len() - 1] == TEXT('0'))
|
|
{
|
|
OutText.RemoveAt(OutText.Len() - 1, 1);
|
|
}
|
|
// Remove ending dot.
|
|
if (OutText.Len() > 0 && OutText[OutText.Len() - 1] == TEXT('.'))
|
|
{
|
|
OutText.RemoveAt(OutText.Len() - 1, 1);
|
|
}
|
|
}
|
|
|
|
OutText += TEXT(' ');
|
|
OutText += InUnitText;
|
|
|
|
return OutText;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::SetAvailableTrackHeight(EMemoryTrackHeightMode InMode, float InTrackHeight)
|
|
{
|
|
check(static_cast<uint32>(InMode) < static_cast<uint32>(EMemoryTrackHeightMode::Count));
|
|
AvailableTrackHeights[static_cast<uint32>(InMode)] = InTrackHeight;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::SetCurrentTrackHeight(EMemoryTrackHeightMode InMode)
|
|
{
|
|
check(static_cast<uint32>(InMode) < static_cast<uint32>(EMemoryTrackHeightMode::Count));
|
|
SetHeight(AvailableTrackHeights[static_cast<uint32>(InMode)]);
|
|
|
|
const float TrackHeight = GetHeight();
|
|
for (TSharedPtr<FGraphSeries> Series: AllSeries)
|
|
{
|
|
Series->SetBaselineY(TrackHeight - 1.0f);
|
|
Series->SetDirtyFlag();
|
|
}
|
|
|
|
SetDirtyFlag();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FMemoryGraphTrack::InitTooltip(FTooltipDrawState& InOutTooltip, const ITimingEvent& InTooltipEvent) const
|
|
{
|
|
if (InTooltipEvent.CheckTrack(this) && InTooltipEvent.Is<FGraphTrackEvent>())
|
|
{
|
|
const FGraphTrackEvent& TooltipEvent = InTooltipEvent.As<FGraphTrackEvent>();
|
|
const TSharedRef<const FMemoryGraphSeries> Series = StaticCastSharedRef<const FMemoryGraphSeries, const FGraphSeries>(TooltipEvent.GetSeries());
|
|
|
|
InOutTooltip.ResetContent();
|
|
InOutTooltip.AddTitle(Series->GetName().ToString(), Series->GetColor());
|
|
FString SubTitle = FString::Printf(TEXT("(tag id %lli, tracker id %lli)"), (int64)Series->GetTagId(), (int64)Series->GetTrackerId());
|
|
InOutTooltip.AddTitle(SubTitle, Series->GetColor());
|
|
|
|
InOutTooltip.AddNameValueTextLine(TEXT("Time:"), TimeUtils::FormatTimeAuto(TooltipEvent.GetStartTime()));
|
|
if (Series->HasEventDuration())
|
|
{
|
|
InOutTooltip.AddNameValueTextLine(TEXT("Duration:"), TimeUtils::FormatTimeAuto(TooltipEvent.GetDuration()));
|
|
}
|
|
InOutTooltip.AddNameValueTextLine(TEXT("Value:"), Series->FormatValue(TooltipEvent.GetValue()));
|
|
InOutTooltip.UpdateLayout();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#undef LOCTEXT_NAMESPACE
|