A top-left readout for performance work: smoothed FPS and frame time, the worst frame in a rolling two-second window, the active view, player speed, a frame counter and the player's position in metres from the project origin. - F3 toggles it, through the console variable gigline.Debug so a packaged build can be checked without a rebuild (IA_ToggleDebug). - On automatically for -perf and -qaselftest, so a performance capture never depends on pressing a key first; off in the built demo, because a player should not see an FPS counter over Hollywood Boulevard. - The first two seconds are a warm-up window excluded from the worst-frame figure. Without it the readout showed "worst 400 ms" for a healthy run: the startup shader and streaming hitches dominated the rolling maximum. Measured cost on the self-test route at 1440p: 2.64 to 2.77 ms average, about 5 %, still 6x the floor. The release-check number stays the overlay-off run; the overlay-on series is committed beside it as Docs/perf/0.2.0-walk-frametimes-overlay.csv. Docs: controls (F3 row, overlay section, switch table), release checks (cost row), architecture building block view (HUD and input list).
122 lines
4.3 KiB
C++
122 lines
4.3 KiB
C++
#include "GigLineHUD.h"
|
|
|
|
#include "Engine/Canvas.h"
|
|
#include "Engine/Engine.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "GigLineCharacter.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
|
|
// Console variable so a packaged build can be checked without a rebuild:
|
|
// gigline.Debug 1
|
|
// The HUD reads it every frame; the F3 binding toggles it.
|
|
static TAutoConsoleVariable<int32> CVarGigLineDebug(
|
|
TEXT("gigline.Debug"),
|
|
0,
|
|
TEXT("Draw the performance and state overlay (0=off, 1=on). F3 toggles it."),
|
|
ECVF_Cheat);
|
|
|
|
void AGigLineHUD::UpdateFrameStats(float DeltaSeconds)
|
|
{
|
|
if (DeltaSeconds <= 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
++FramesDrawn;
|
|
const float Ms = DeltaSeconds * 1000.0f;
|
|
if (WarmupSeconds > 0.0f)
|
|
{
|
|
WarmupSeconds -= DeltaSeconds;
|
|
SmoothedFrameMs = FMath::Lerp(SmoothedFrameMs, Ms, 0.1f);
|
|
WorstFrameMs = Ms;
|
|
WorstWindowSeconds = 0.0f;
|
|
return;
|
|
}
|
|
// A 0.9/0.1 EMA settles in a few frames and still reacts to a stall.
|
|
SmoothedFrameMs = FMath::Lerp(SmoothedFrameMs, Ms, 0.1f);
|
|
WorstFrameMs = FMath::Max(WorstFrameMs, Ms);
|
|
// Rolling worst-frame window: a spike stays visible for two seconds, so a
|
|
// hitch is not missed between glances (and not averaged away).
|
|
WorstWindowSeconds += DeltaSeconds;
|
|
if (WorstWindowSeconds >= 2.0f)
|
|
{
|
|
WorstWindowSeconds = 0.0f;
|
|
WorstFrameMs = Ms;
|
|
}
|
|
}
|
|
|
|
void AGigLineHUD::DrawHUD()
|
|
{
|
|
Super::DrawHUD();
|
|
|
|
// Console variable is authoritative; the UPROPERTY is the per-instance default
|
|
// for tests that set it before BeginPlay.
|
|
if (IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(TEXT("gigline.Debug")))
|
|
{
|
|
bShowDebug = Var->GetInt() != 0;
|
|
}
|
|
UpdateFrameStats(GetWorld() ? GetWorld()->GetDeltaSeconds() : 0.0f);
|
|
|
|
if (!Canvas || AttributionLine.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Bottom-left, above any letterbox: the standard place for map/data credits.
|
|
const float LineHeight = Canvas->ClipY > 0 ? 22.0f * TextScale : 22.0f * TextScale;
|
|
const float X = MarginPx;
|
|
const float Y = Canvas->ClipY - MarginPx - LineHeight * 2.1f;
|
|
|
|
const FLinearColor Shadow(0.0f, 0.0f, 0.0f, 0.55f);
|
|
const FLinearColor Ink(1.0f, 1.0f, 1.0f, 0.92f);
|
|
|
|
// Shadow pass for legibility over bright pavement and sky.
|
|
DrawText(AttributionLine, Shadow, X + 1.0f, Y + 1.0f, nullptr, TextScale);
|
|
DrawText(LicenceLine, Shadow, X + 1.0f, Y + LineHeight + 1.0f, nullptr, TextScale * 0.82f);
|
|
|
|
DrawText(AttributionLine, Ink, X, Y, nullptr, TextScale);
|
|
DrawText(LicenceLine, Ink, X, Y + LineHeight, nullptr, TextScale * 0.82f);
|
|
|
|
// Control legend, bottom-right (0.1.0 release check: controls shown in-game).
|
|
if (!ControlsLine.IsEmpty())
|
|
{
|
|
float IgnoredW = 0.0f, ControlsH = 0.0f;
|
|
GetTextSize(ControlsLine, IgnoredW, ControlsH, nullptr, TextScale * 0.82f);
|
|
const float CX = Canvas->ClipX - MarginPx - IgnoredW;
|
|
const float CY = Canvas->ClipY - MarginPx - ControlsH;
|
|
DrawText(ControlsLine, Shadow, CX + 1.0f, CY + 1.0f, nullptr, TextScale * 0.82f);
|
|
DrawText(ControlsLine, Ink, CX, CY, nullptr, TextScale * 0.82f);
|
|
}
|
|
|
|
if (!bShowDebug)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Performance and state readout, top-left. Everything here is cheap enough to
|
|
// leave on during a capture: four formatted lines and no additional passes.
|
|
const APawn* Pawn = UGameplayStatics::GetPlayerPawn(this, 0);
|
|
const AGigLineCharacter* Character = Cast<AGigLineCharacter>(Pawn);
|
|
const float Fps = SmoothedFrameMs > 0.0f ? 1000.0f / SmoothedFrameMs : 0.0f;
|
|
const FVector Where = Pawn ? Pawn->GetActorLocation() / 100.0f : FVector::ZeroVector;
|
|
const float Speed = Pawn ? Pawn->GetVelocity().Size2D() / 100.0f : 0.0f;
|
|
|
|
const FString Header = FString::Printf(TEXT("%.0f FPS %.2f ms worst %.2f ms"), Fps, SmoothedFrameMs, WorstFrameMs);
|
|
const FString State = FString::Printf(TEXT("%s %.2f m/s frames %lld"),
|
|
Character && Character->IsThirdPerson() ? TEXT("third person") : TEXT("first person"),
|
|
Speed, FramesDrawn);
|
|
const FString Place = FString::Printf(TEXT("x %.1f y %.1f z %.1f m"), Where.X, Where.Y, Where.Z);
|
|
const FString Hint = TEXT("F3 hide");
|
|
|
|
const FLinearColor DebugInk(0.85f, 1.0f, 0.55f, 0.95f);
|
|
const FLinearColor DebugShadow(0.0f, 0.0f, 0.0f, 0.6f);
|
|
const float DX = MarginPx;
|
|
float DY = MarginPx;
|
|
for (const FString& Line : { Header, State, Place, Hint })
|
|
{
|
|
DrawText(Line, DebugShadow, DX + 1.0f, DY + 1.0f, nullptr, TextScale * 0.9f);
|
|
DrawText(Line, DebugInk, DX, DY, nullptr, TextScale * 0.9f);
|
|
DY += 22.0f * TextScale * 0.9f;
|
|
}
|
|
}
|