feat(hud): F3 debug overlay — FPS, frame time, worst frame, state
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 so a packaged build can be checked without a rebuild (). - **On automatically** for and , so the 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 -> 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).
This commit is contained in:
1 parent
12272d6b96
commit
73bce53a2b
12 files changed
+904
-8
No files matched your search
@@ -15,6 +15,7 @@
|
||||
#include "Engine/World.h"
|
||||
#include "TimerManager.h"
|
||||
#include "Containers/Ticker.h"
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include "Misc/Parse.h"
|
||||
@@ -132,6 +133,8 @@ AGigLineCharacter::AGigLineCharacter()
|
||||
TEXT("/Game/Input/IA_Jump.IA_Jump"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> ResetF(
|
||||
TEXT("/Game/Input/IA_Reset.IA_Reset"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> DebugF(
|
||||
TEXT("/Game/Input/IA_ToggleDebug.IA_ToggleDebug"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> CamF(
|
||||
TEXT("/Game/Input/IA_ToggleCamera.IA_ToggleCamera"));
|
||||
static ConstructorHelpers::FObjectFinder<UInputAction> PauseF(
|
||||
@@ -146,6 +149,7 @@ AGigLineCharacter::AGigLineCharacter()
|
||||
JumpAction = JumpF.Object;
|
||||
ResetAction = ResetF.Object;
|
||||
ToggleCameraAction = CamF.Object;
|
||||
ToggleDebugAction = DebugF.Object;
|
||||
PauseAction = PauseF.Object;
|
||||
|
||||
if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction
|
||||
@@ -257,6 +261,28 @@ void AGigLineCharacter::BeginPlay()
|
||||
// with `-thirdperson` so a capture can show the walk clip mid-stride: an idle
|
||||
// shot cannot prove the animation set, and a still cannot show a stride.
|
||||
bWalkForever = FParse::Param(FCommandLine::Get(), TEXT("walk"));
|
||||
|
||||
// `-perf` starts the overlay on: the performance capture and any manual frame
|
||||
// check should not depend on someone pressing F3 first. The built demo has
|
||||
// neither switch, so the counter stays hidden for players.
|
||||
if (FParse::Param(FCommandLine::Get(), TEXT("perf")) || FParse::Param(FCommandLine::Get(), TEXT("qaselftest")))
|
||||
{
|
||||
if (IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(TEXT("gigline.Debug")))
|
||||
{
|
||||
Var->Set(1, ECVF_SetByConsole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AGigLineCharacter::ToggleDebugOverlay()
|
||||
{
|
||||
static IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(TEXT("gigline.Debug"));
|
||||
if (Var)
|
||||
{
|
||||
const bool bNowOn = Var->GetInt() == 0;
|
||||
Var->Set(bNowOn ? 1 : 0, ECVF_SetByConsole);
|
||||
UE_LOG(LogTemp, Log, TEXT("GigLine: debug overlay %s"), bNowOn ? TEXT("on") : TEXT("off"));
|
||||
}
|
||||
}
|
||||
|
||||
void AGigLineCharacter::SetReviewWalk(bool bEnable)
|
||||
@@ -658,6 +684,10 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
|
||||
{
|
||||
Input->BindAction(ToggleCameraAction, ETriggerEvent::Started, this, &AGigLineCharacter::ToggleCamera);
|
||||
}
|
||||
if (ToggleDebugAction)
|
||||
{
|
||||
Input->BindAction(ToggleDebugAction, ETriggerEvent::Started, this, &AGigLineCharacter::ToggleDebugOverlay);
|
||||
}
|
||||
if (JumpAction)
|
||||
{
|
||||
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
||||
|
||||
@@ -42,6 +42,9 @@ protected:
|
||||
void TogglePause();
|
||||
|
||||
public:
|
||||
/** True while the third-person rig is the active view (for the debug overlay). */
|
||||
bool IsThirdPerson() const { return bThirdPerson; }
|
||||
|
||||
/** Review harness: hold or release forward input from the screenshot actor. */
|
||||
void SetReviewWalk(bool bEnable);
|
||||
|
||||
@@ -172,6 +175,13 @@ protected:
|
||||
/** Applies the `-walk` forward input when the flag is set and the pawn is grounded. */
|
||||
void WalkForever();
|
||||
|
||||
/** Debug-overlay action (F3). */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
||||
TObjectPtr<UInputAction> ToggleDebugAction;
|
||||
|
||||
/** Flips the gigline.Debug console variable that the HUD reads. */
|
||||
void ToggleDebugOverlay();
|
||||
|
||||
/** Toggle-camera action (V). */
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
||||
TObjectPtr<UInputAction> ToggleCameraAction;
|
||||
|
||||
@@ -2,11 +2,61 @@
|
||||
|
||||
#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;
|
||||
@@ -37,4 +87,35 @@ void AGigLineHUD::DrawHUD()
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,19 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
||||
FString LicenceLine = TEXT("Data \u00A9 OpenStreetMap contributors, ODbL 1.0 \u2014 openstreetmap.org/copyright");
|
||||
|
||||
/**
|
||||
* Performance and state readout, top-left, off by default.
|
||||
*
|
||||
* Toggle in game with **F3**, or start with it on by passing `-perf` (the
|
||||
* performance capture does). The built demo ships with it hidden so a player
|
||||
* does not see an FPS counter over Hollywood Boulevard.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Debug")
|
||||
bool bShowDebug = false;
|
||||
|
||||
/** Smoothed FPS and a rolling frame-time window, refreshed every frame. */
|
||||
void UpdateFrameStats(float DeltaSeconds);
|
||||
|
||||
/** Screen-space margin in pixels. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
||||
float MarginPx = 18.0f;
|
||||
@@ -47,4 +60,25 @@ public:
|
||||
/** Text scale relative to the default canvas font. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
||||
float TextScale = 1.45f;
|
||||
|
||||
private:
|
||||
/** Exponential moving average of frame time, in milliseconds. */
|
||||
float SmoothedFrameMs = 16.7f;
|
||||
|
||||
/** Worst frame in the rolling window, so a hitch is visible without a log. */
|
||||
float WorstFrameMs = 0.0f;
|
||||
|
||||
/** Seconds until the rolling worst-frame window resets. */
|
||||
float WorstWindowSeconds = 0.0f;
|
||||
|
||||
/** Frames drawn, so the overlay can show that the counter is live. */
|
||||
int64 FramesDrawn = 0;
|
||||
|
||||
/**
|
||||
* Seconds left in the warm-up window. The first frames of a session include
|
||||
* shader and streaming hitches of several hundred milliseconds; without this
|
||||
* the "worst" figure read 400 ms for the whole first two seconds and made a
|
||||
* healthy run look broken.
|
||||
*/
|
||||
float WarmupSeconds = 2.0f;
|
||||
};
|
||||
Reference in new issue
Block a user