stat unit shows GPU time exceeds frame budget (>16.6 ms at 60 Hz, >11.1 ms at 90 Hz, >8.3 ms at 120 Hz). Confirm: bump r.ScreenPercentage up — if GPU time scales linearly, you are genuinely GPU-bound. If unchanged, jump to Tree 2.Three flowcharts you can follow at 2 a.m. when QA reports a regression. Each starts with the symptom you can see in stat unit or a crash log, branches to the right diagnostic, and ends with a one-line fix or a tutorial deep-dive link.
gpu-bound:lumen, cpu-bound:tick, memory:texture-pool — click through directly to the matching tree branch.
stat unit shows GPU time exceeds frame budget (>16.6 ms at 60 Hz, >11.1 ms at 90 Hz, >8.3 ms at 120 Hz). Confirm: bump r.ScreenPercentage up — if GPU time scales linearly, you are genuinely GPU-bound. If unchanged, jump to Tree 2.Run stat gpu for live text breakdown, or ProfileGPU (Ctrl+Shift+,) for a single-frame waterfall. UE 5.6 GPU Profiler 2.0 splits Graphics vs Compute queues. Look for the largest cost in: Lumen*, Nanite*, ShadowDepths / VirtualShadowMaps, Translucency, PostProcessing, Reflections, Niagara*, BasePass.
Lumen.SceneLighting, Lumen.ScreenProbeGather, Lumen.ReflectionsHWRT):
r.Lumen.ScreenProbeGather.IntegrateDownsampleFactor=2 — 5.6 default 3× faster integrate.r.Lumen.ScreenProbeGather.SpatialFilterNumPasses 3 → 1 or 2.r.Lumen.ScreenProbeGather.StochasticInterpolation 1 — ~30% gain on RDNA.Nanite.BasePass, Nanite.CullRasterize):
NaniteVisualize MaterialComplexity to find heavy materials.r.Nanite.MinPixelsPerEdgeHW (32 on high-end, lower on weaker GPUs).ShadowDepths, VirtualShadowMapCacheUpdate):
r.Shadow.Virtual.UseReceiverMask 1 (off by default in 5.6).r.Shadow.Virtual.SMRT.SamplesPerRayLocal from 8 toward 4.r.Lumen.Reflections.DownsampleFactor (1 → 2).r.Lumen.Reflections.MaxRoughnessToTrace.fx.Niagara.LogParticleCounts 1 and FX Performance window.r.TSR.History.ScreenPercentage from 200 to 100 at Epic AA (1.2 ms at 4K).Re-run stat unit and stat gpu. Still bound? Repeat from Step 1 — the second-largest pass is now the new bottleneck.
stat unit shows Game or Render thread > frame budget while GPU < budget. The screen-percentage test does not change frame time.Run stat scenerendering and stat rhi. Probable causes:
r.SceneCulling 1.r.RDG.ParallelExecute 1.Run stat slow -ms=0.5 to catch any cycle counter > 0.5 ms. Then branch:
STAT_TickActor, STAT_TickComponent heavy):
dumpticks — lists every actor with registered tick. Filter to tick-enabled actors that don't need it.PrimaryActorTick.bCanEverTick = false).tick.AllowBatchedTicks 1 (UE 5.5+).stat anim heavy):
STAT_AnimGameThreadTime, STAT_PoseUpdate, STAT_AnimGraphEvaluate.stat physics / stat chaos heavy):
p.Chaos.Solver.Joint.UseSimd 1 (UE 5.5).gc.MultithreadedDestructionEnabled 1.gc.TimeBetweenPurgingPendingKillObjects.STAT_BlueprintTime, STAT_ScriptVM):
BlockTillLevelStreamingCompleted stalls (5.5 narrowed scope).s.UseUnifiedTimeBudgetForStreaming.stat dumphitches writes any frame > t.HitchFrameTimeThreshold to log. Re-run scenario; confirm targeted hitch is gone.
stat memory shows Used Physical near platform limit.Run stat streaming — watch Streaming Pool Used, Wanted Mips, NonStreaming Mips %.
r.Streaming.PoolSize proportional to VRAM (3000–4000 for 8 GB GPUs, 16000+ for 16 GB). Silences warning; masks issue.DumpTextureStreamingStats.Run memreport -full → look at RHI Resource Memory. Use stat LLM, stat LLMFULL for tagged breakdown.
r.Shadow.Virtual.PhysicalPagePoolSize.r.LumenScene.SurfaceCache.AtlasSize or r.Lumen.SceneCaptureCacheResolution.r.Nanite.MaxNodes, r.Nanite.MaxVisibleClusters.memreport -full — top sections: Object Memory by Class, Asset Memory, Audio, World/Levels. Add -llmcsv for hierarchical CSV.
Re-run scenario. Compare two memreports side-by-side. Confirm stat streaming pool used < pool size and no OOM after a 30-min soak.
stat slow is clean), capture an Insights trace with -trace=cpu,gpu,frame,bookmark,memory_light and step into the Timing Insights view.
PerfGuard regression reports auto-tag failures with gpu-bound:lumen, cpu-bound:tick, memory:texture-pool — surface a "Triage this" button that deep-links into the matching tree branch. Future pg-triage <regression-id> CLI prints the exact branch to terminal during CI failure.