agentleFS
mattstrublemattstruble/skills/game-performance/SKILL.md

game-performance

Skill2 starsChanged 4 months agoMITView on GitHub

You MUST consult this skill when diagnosing or fixing game performance problems — profiling GPU/CPU/memory bottlenecks, improving frame rate, reducing battery consumption, fixing hitches or frame drops, or optimizing for constrained hardware (handheld, mobile, integrated graphics). Also trigger when a game runs at target FPS but utilization is dangerously high, or when frame pacing is inconsistent. NOT for network performance (latency, tick rate, bandwidth), loading time optimization, architectural patterns that prevent performance problems (see game-patterns), shader authoring (see godot-shader), or engine-specific profiling tool configuration.

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

10 rules. About 0 are common, 4 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Distinctive:

    Leave headroom: Never target 100% utilization.

    line 19
  2. Distinctive:

    Before touching anything, record:

    line 54
  3. Very specific:

    No change → shader ALU bound → see references/gpu-profiling.md § Shader Complexity

    line 96
  4. Very specific:

    No change → compute passes, rogue cameras, or unnecessary render passes → see references/gpu-profiling.md § Fixed Overhead

    line 103
  5. Distinctive:

    Check GC allocation rate per frame first — GC spikes are the most common managed-language performance killer

    line 114
  6. Very specific:

    If allocation rate is high, see references/cpu-profiling.md § GC Pressure

    line 115
  7. Very specific:

    If allocation is low, check which subsystem dominates and see references/cpu-profiling.md for that subsystem

    line 116
  8. Distinctive:

    Check cache miss rate in hot loops first — pointer chasing and random memory access are the most common native performance killers

    line 119
  9. Very specific:

    If cache miss rate is high, see references/cpu-profiling.md § Cache Inefficiency

    line 120
  10. Very specific:

    If cache misses are low, check which subsystem dominates and see references/cpu-profiling.md for that subsystem

    line 121

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Game Performance

Systematic methodology for diagnosing and fixing game performance problems. Profile first, fix the root cause, leave headroom.


Core Principles

  • Profile before optimizing. Measure, don't guess. The bottleneck is almost never where you think it is. Every optimization session starts with a profiler, not a hunch.
  • Fix the root cause, not the symptom. Upscaling masks fill-rate problems but doesn't fix them — you still pay the thermal and battery cost. Fixing the source gives you FPS gains and thermal/battery gains.
  • Isolate variables. Change one thing at a time, observe the metric, conclude. Changing three things simultaneously makes it impossible to know which one helped.
  • Leave headroom. Never target 100% utilization. Background OS tasks, worst-case game moments (explosions, many entities), and thermal variance all eat into margin. Target 60–75% utilization at your FPS goal on min-spec hardware.
  • Utilization ≠ performance. ~29% GPU at 1600MHz delivers the same FPS as 55% GPU at 830MHz — with roughly half the power draw. When the hardware has spare capacity, it downclocks to save power. Lower utilization at a lower clock is better than high utilization at a high clock.
  • Flat profiles are the mature case. Jonathan Blow observes that once obvious hotspots are eliminated, a mature profile is "flat" — many items each at ~0.3–1%, no single dominant win. If one change does give a huge speedup at that stage, you were probably previously negligent. Your options become "accept the profile" or "make an architectural change"; grinding down many small contributors is the expensive, incremental reality. The diagnostic decision tree below assumes a hotspot exists; this principle covers the flat case where it doesn't.
  • Measure work per unit time, not raw throughput. Don't compare raw rates (fps, draw calls/sec) across different workloads — compare work and features delivered per unit time. A rich scene running at 120fps and an empty room running at 2000fps are not comparable; the empty room number tells you nothing about your game's actual budget. Evaluate performance in the context of what is being rendered.

Performance Glossary

TermDefinition
Frame budgetThe fixed time available per frame. 16.6ms at 60fps, 33.3ms at 30fps. Every subsystem (GPU, CPU, audio, physics) must finish within this window.
Fill-rateGPU pixel throughput: resolution × overdraw × per-pixel shader cost. When this exceeds the GPU's capacity, frames take longer.
OverdrawPixels rendered multiple times per frame due to overlapping geometry or effects. A pixel rendered 4× costs 4× the fill-rate budget.
Bandwidth-boundLimited by memory read/write speed, not compute. The GPU is waiting for texture data, not running out of ALU capacity.
Submission-boundThe CPU can't feed the GPU fast enough. Too many draw calls or state changes per frame; the GPU starves while waiting for work.
Vertex-boundLimited by geometry processing throughput. Dense meshes or tessellation saturate the geometry pipeline.
Compute-boundLimited by shader ALU operations. Complex fragment shaders with many math ops saturate compute units.
HeadroomMargin between current utilization and capacity. A safety buffer for variance, thermal throttling, and worst-case scenes.
Frame pacingConsistency of frame delivery timing, distinct from average FPS. A game averaging 60fps with frames delivered at 8ms/25ms/8ms/25ms has poor frame pacing — it feels choppy despite the average.
HitchingIndividual frame spikes amid otherwise smooth performance. Caused by GC collection, streaming stalls, shader compilation, autosave.
Thermal throttleHardware reducing clock speeds to manage heat. Sustained load causes chip temperature to rise; the hardware reduces clocks to stay within thermal design power (TDP). Performance degrades over time.
Power envelopeTotal watts available, shared between CPU and GPU on mobile/handheld. Reducing CPU work can free power for the GPU and vice versa.
Dynamic resolutionAdjusting internal render resolution at runtime to maintain frame targets. Trades image quality for consistent frame time.
LODLevel of Detail — reducing geometry or texture quality for distant objects. The primary lever for vertex and bandwidth budgets.
Mip biasSkipping high-resolution mipmap levels to reduce bandwidth. A negative mip bias forces higher-res mips; a positive bias forces lower-res mips.

The Diagnostic Decision Tree

This is the core of the skill. Follow the numbered steps in order. Each test isolates one variable and tells you where to look next.

Step 1: Establish Baseline Metrics

Before touching anything, record:

  • FPS (average and 1% low)
  • GPU utilization % and GPU clock speed
  • CPU utilization % per core
  • VRAM usage and system RAM usage
  • Frame time graph over 30+ seconds (not just average FPS)

Critical question: Is the problem sustained low FPS, intermittent hitches, or both?

  • Sustained low FPS only → proceed to Step 2
  • Intermittent hitches only → proceed to Step 5
  • Both → Step 2 first (fix the sustained bottleneck), then return to Step 5

Step 2: Sustained Low FPS — Identify the Primary Bottleneck

Compare GPU utilization vs. CPU utilization:

Note: If your engine uses a separate render thread (Godot 4, Unity multithreaded rendering), check render thread utilization separately from main thread utilization.

ObservationConclusionNext Step
GPU ~100%, CPU < 80%GPU-boundStep 3
CPU ~100%, GPU < 80%CPU-boundStep 4
Both ~100%Submission-bound (CPU preparing too much GPU work)Step 4 (CPU side), then Step 3
Main thread < 80%, render thread ~100%, GPU < 80%Render thread submission-boundStep 4 (render thread submission section)
Neither high (both < 70%)Vsync-limited, frame cap, or external throttleCheck frame cap settings; verify vsync isn't locking to a lower multiple

Submission-bound is a special case: the CPU is spending its budget generating draw calls and state changes, so the GPU starves. It looks like both are high, but the GPU has "bubbles" — it's not actually running at capacity. The fix is on the CPU side (batching, instancing) not the GPU side.


Step 3: GPU-Bound — Classify the GPU Bottleneck

Test A: Halve render resolution (set render scale to 0.5×)

Mobile/TBDR caveat: On tile-based GPU architectures (all iOS, most Android — Mali, Adreno, PowerVR), internal framebuffer bandwidth is handled on-chip. Halving resolution may show little improvement even when texture bandwidth is the bottleneck. On these platforms, proceed to Test B regardless of Test A result.

  • GPU load drops significantly → fill-rate or bandwidth bound
    • Test B1: Replace textures with solid-color constants (keep shader math, remove texture reads — use a hardcoded vec4(1.0) in the shader instead of a texture sample)
      • GPU load drops → bandwidth/texture bound → see references/gpu-profiling.md § Bandwidth
      • No change → shader ALU bound → see references/gpu-profiling.md § Shader Complexity
    • Test B2 (if B1 showed bandwidth-bound): Re-enable textures but use a single 1×1 solid-color texture for all materials
      • GPU load stays low → data bandwidth is the bottleneck (the sampler hardware is cheap; the data volume was the problem); proceed with compression and mip bias
      • GPU load returns to near-original → sampler-count or TMU-bound (too many texture units active regardless of data size); reduce distinct texture samples per pixel
  • No change → fixed-cost overhead (resolution doesn't affect it)
    • Test C: Halve geometry count (aggressive LOD, or disable half the scene objects)
      • GPU load drops → vertex-bound → see references/gpu-profiling.md § Vertex
      • No change → compute passes, rogue cameras, or unnecessary render passes → see references/gpu-profiling.md § Fixed Overhead

Step 4: CPU-Bound — Classify the CPU Bottleneck

Open your profiler and identify which subsystem dominates frame time. Common categories: script/logic, physics, animation, audio, render thread submission.

Branch on language runtime:

[Managed: C#, GDScript, Lua]

  1. Check GC allocation rate per frame first — GC spikes are the most common managed-language performance killer
  2. If allocation rate is high, see references/cpu-profiling.md § GC Pressure
  3. If allocation is low, check which subsystem dominates and see references/cpu-profiling.md for that subsystem

[Native: C++, Rust]

  1. Check cache miss rate in hot loops first — pointer chasing and random memory access are the most common native performance killers
  2. If cache miss rate is high, see references/cpu-profiling.md § Cache Inefficiency
  3. If cache misses are low, check which subsystem dominates and see references/cpu-profiling.md for that subsystem

Step 5: Intermittent Hitches — Frame Pacing Investigation

Record a frame time graph over 60+ seconds. Characterize the pattern:

PatternLikely CauseInvestigation
Periodic (every N seconds)GC collection, autosave, background streaming checksCheck allocation rate; look for periodic disk writes
Situational (entering areas, spawning)Streaming stalls, shader compilation on first use, bulk entity spawningPre-warm shaders; async streaming; stagger spawns
RandomOS background tasks, driver issues, thermal throttlingCheck CPU temperature; look for OS scheduler interference

See references/cpu-profiling.md § Hitching for the full investigation per category.


Step 6: Thermal and Power Issues

  • Target FPS met but utilization > 80% → headroom problem; see references/thermal-power.md § Headroom
  • Performance degrades over a 20–30 minute play session → thermal throttling; see references/thermal-power.md § Thermal Throttling
  • Battery drains too fast on handheld/mobile → power optimization needed; see references/thermal-power.md § Battery Life

Worked Example: Steam Deck GPU Bottleneck

Scenario: A 2D action game hits 60fps on Steam Deck but at 90% GPU utilization and 1600MHz clock speed. The developer wants to ship with headroom for worst-case scenes.

Step 1: Baseline metrics

  • FPS: 60 (vsync-locked), 1% low: 58
  • GPU: 90% utilization, 1600MHz
  • CPU: 45% utilization
  • Frame time graph: mostly flat at 16.6ms, occasional 18ms spikes

Conclusion: sustained GPU-bound problem. CPU has headroom. Proceed to Step 2.

Step 2: Primary bottleneck GPU ~90%, CPU ~45% → clearly GPU-bound. Proceed to Step 3.

Step 3: Classify GPU bottleneck

Test A: Set render scale to 0.5× (halve resolution).

  • Result: GPU drops from 90% to 55%. Significant drop.
  • Conclusion: fill-rate or bandwidth bound. Proceed to Test B1.

Test B1: Replace textures with solid-color constants in shaders (keep lighting math, remove texture reads).

  • Result: GPU drops from 55% to 35%.
  • Conclusion: bandwidth/texture bound. The texture reads are the bottleneck.

Test B2: Re-enable textures but use a single 1×1 solid-color texture for all materials.

  • Result: GPU stays low at ~35% (same as B1 — the 1×1 texture does not bring load back).
  • Conclusion: data bandwidth is the bottleneck (not sampler count). Proceed with compression and mip bias.

Investigation (from references/gpu-profiling.md § Bandwidth):

  • Audit texture compression: several 2048×2048 textures were imported as uncompressed RGBA8. Converting to BC7 (desktop) / ASTC 4×4 (mobile) reduces texture memory bandwidth by 4–8×.
  • Found two render targets using RGBA16F precision when RGBA8 was sufficient.
  • Found a shadow map at 4096×4096 for a 2D game — reduced to 1024×1024.

Result after fixes:

  • GPU: drops from 90% to ~60% utilization at 1600MHz
  • DVFS kicks in: hardware downclocks to ~1100MHz where the same workload runs at ~80% utilization
  • FPS: still 60fps (vsync-locked)
  • Power draw: reduced by ~35–40%
  • Thermal: chip now runs 10–12°C cooler; no throttling in 30-minute sessions

Key insight: The FPS didn't change, but the game is now thermally sustainable and has headroom for worst-case scenes. The fix was invisible to the player but critical for handheld shipping. The hardware chose a lower clock because the workload no longer required maximum clock to meet the frame budget.


References

FileContentsRead when...
references/gpu-profiling.mdFill-rate, bandwidth, shader complexity, vertex, fixed overhead — mechanism, confirmation test, levers, common culpritsStep 3 identifies a GPU bottleneck
references/cpu-profiling.mdFrame budget anatomy, script/logic, physics, GC pressure, cache inefficiency, hitchingStep 4 identifies a CPU bottleneck, or Step 5 identifies hitches
references/memory-profiling.mdBudget planning, VRAM vs system RAM, streaming strategies, allocation patterns, finding what's eating memoryVRAM or RAM is near capacity, or you suspect a memory leak
references/thermal-power.mdPower model, thermal throttling, battery optimization, adaptive quality, headroom philosophyGame degrades over time, battery drains fast, or utilization is high despite meeting FPS target

Relationship to Other Skills

game-patterns — Architectural patterns that prevent performance problems: Object Pool (eliminate GC pressure), Data Locality (fix cache misses), Spatial Partition (reduce O(n²) queries), Dirty Flag (avoid redundant recomputation). Use game-patterns when you've identified the bottleneck category and need the implementation pattern. Use this skill to identify the bottleneck first.

godot / love2d — Engine-specific profiling tools and their configuration. This skill is engine-agnostic; your engine skill handles how to open the profiler, what the columns mean, and engine-specific optimization APIs.

godot-shader — Shader authoring and optimization. When this skill identifies shader complexity as the bottleneck, godot-shader handles the implementation of simpler or more efficient shaders.


Further Reading

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.