// 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 #define LOCTEXT_NAMESPACE "MemoryGraphTrack" //////////////////////////////////////////////////////////////////////////////////////////////////// // FMemoryGraphSeries //////////////////////////////////////////////////////////////////////////////////////////////////// FString FMemoryGraphSeries::FormatValue(double Value) const { const int64 MemValue = static_cast(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::infinity()), DefaultMaxValue(-std::numeric_limits::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(EMemoryTrackHeightMode::Count); ++Mode) { SetAvailableTrackHeight(static_cast(Mode), 100.0f * (Mode + 1)); } SetHeight(200.0f); } //////////////////////////////////////////////////////////////////////////////////////////////////// void FMemoryGraphTrack::ResetDefaultValueRange() { DefaultMinValue = +std::numeric_limits::infinity(); DefaultMaxValue = -std::numeric_limits::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& 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& Series: AllSeries) { if (Series->IsVisible()) { // TODO: if (Series->Is()) TSharedPtr MemorySeries = StaticCastSharedPtr(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& Series: AllSeries) { if (Series->IsVisible()) { if (Series->UpdateAutoZoomEx(TopY, BottomY, AllSeriesMinValue, AllSeriesMaxValue, true)) { Series->SetDirtyFlag(); } } } } } for (TSharedPtr& 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()) TSharedPtr MemorySeries = StaticCastSharedPtr(Series); UpdateMemTagSeries(*MemorySeries, Viewport); if (Series->IsAutoZoomDirty()) { Series->SetDirtyFlag(); } } } UpdateStats(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // LLM Tag Series //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FMemoryGraphTrack::GetMemTagSeries(Insights::FMemoryTagId MemTagId) { TSharedPtr* Ptr = AllSeries.FindByPredicate([MemTagId](const TSharedPtr& Series) { // TODO: if (Series->Is()) const TSharedPtr MemorySeries = StaticCastSharedPtr(Series); return MemorySeries->GetTagId() == MemTagId; }); return (Ptr != nullptr) ? StaticCastSharedPtr(*Ptr) : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FMemoryGraphTrack::AddMemTagSeries(Insights::FMemoryTrackerId MemTrackerId, Insights::FMemoryTagId MemTagId) { TSharedPtr Series = GetMemTagSeries(MemTagId); if (!Series.IsValid()) { Series = MakeShared(); 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& GraphSeries) { // TODO: if (Series->Is()) const TSharedPtr Series = StaticCastSharedPtr(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 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::infinity(); double MaxValue = -std::numeric_limits::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(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 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(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& Series: AllSeries) { if (Series->IsVisible()) { if (Y > MaxY) { break; } // TODO: if (Series->Is()) TSharedPtr MemorySeries = StaticCastSharedPtr(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 Series = MainSeries; if (!Series.IsValid() && AllSeries.Num() > 0) { // TODO: if (Series->Is()) Series = StaticCastSharedPtr(AllSeries[0]); } if (!Series.IsValid()) { return; } FAxisViewportDouble ViewportY; ViewportY.SetSize(GetHeight()); ViewportY.SetScaleLimits(std::numeric_limits::min(), std::numeric_limits::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 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(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(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(InMode) < static_cast(EMemoryTrackHeightMode::Count)); AvailableTrackHeights[static_cast(InMode)] = InTrackHeight; } //////////////////////////////////////////////////////////////////////////////////////////////////// void FMemoryGraphTrack::SetCurrentTrackHeight(EMemoryTrackHeightMode InMode) { check(static_cast(InMode) < static_cast(EMemoryTrackHeightMode::Count)); SetHeight(AvailableTrackHeights[static_cast(InMode)]); const float TrackHeight = GetHeight(); for (TSharedPtr 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()) { const FGraphTrackEvent& TooltipEvent = InTooltipEvent.As(); const TSharedRef Series = StaticCastSharedRef(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