makepad/skills/makepad-aaa-graphics-builder/references/render-budget.md
Arena Agent fe21c07d84 skills: add Makepad game skills pack ported from threejs-game-skills
Nine agent skills for building Makepad/Rust games, ported from
majidmanzarpour/threejs-game-skills. Same director-routed workflow and
premium bar; runtime rewritten for this fork's game crates.

- makepad-game-director        entrypoint, routing, continuity, asset probe
- makepad-gameplay-systems     loop, movers vs rigid bodies, input, camera, netplay
- makepad-aaa-graphics-builder lighting, shaders, budget, visual scorecard
- makepad-game-ui-designer     HUD, menus, touch and XR UI
- makepad-debug-profiler       defect bisection and profiling
- makepad-qa-release           verification ladder, evidence, packaging
- makepad-3d-generator         CC0 model search, casts, procedural geometry
- makepad-image-generator      texgen textures, palettes, sky, icons
- makepad-audio-generator      sample bank, mixer, material impacts, 3D audio

Written against the real APIs in libs/game/*, libs/sim and apps/arcade:
the game.* verb table, GameRenderer adaptive quality, the packed 6-float
GameMeshVertex layout, script_mod! splash styling, makepad-test driving,
and the BUDGETS.md numbers. No paid generation API is required - the CC0
library plus seeded makepad-game-gen replaces them.

Includes install.sh (Codex/Claude), validate-skills.sh and a repo-aware
probe_assets.sh; both scripts verified against this checkout.
2026-09-05 21:18:58 +00:00

87 lines
3.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Render budget
`apps/arcade/BUDGETS.md` is the authority. This is the working summary and the method.
## What the numbers say
Measured on an M-class Mac, release. The Quest column in BUDGETS.md is a conservative **estimate** until run on device — do not quote it as measured.
| thing | cost |
| --- | --- |
| 100 movers + 50 rigid bodies + 65×65 terrain | **0.038 ms/tick** |
| 60 Hz frame budget | 16.6 ms |
| script per tick | ≤ 2 ms, one cumulative 500k-instruction pool |
| entity lookup | O(log n) |
**Simulation is ~0.2% of the frame. It is never your performance problem.** Draw calls and CPU skinning are.
## Bandwidth
| stream | before | after |
| --- | --- | --- |
| cube instance | 44 floats / 176 B | **32 floats / 128 B** (27%) |
| skinned character vertex | 16 floats / 64 B | **6 floats / 24 B** (62%) |
| shadow mesh vertex | 16 floats / 64 B | **6 floats / 24 B** (62%) |
| terrain vertex | 16 floats / 64 B | unchanged (uploaded once per revision) |
The skinned vertex matters most because it re-uploads **every frame**: the Knight at 3716 verts went from 238 KB/frame to 89 KB/frame. On a bandwidth-bound tiler that is the largest single saving available.
The instance saving came from moving `sun_color`, `sun_sky`, `sun_ground`, `fog_color` off the instance stream into uniforms — 12 floats of pure per-cube duplication.
**Quest is bandwidth-bound before it is ALU-bound.** Optimise bytes per frame first.
## Measuring
Never estimate. Read it:
```rust
renderer.report_frame_ms(ms); // -> bool: did adaptive quality change?
renderer.frame_p90_ms(); // Option<f32> - report p90, not mean
renderer.quality_level();
renderer.quality_reason();
stats.instance_floats; // RenderStats: from the compiled shader
renderer.model_triangles(id);
```
Report **p90, not average**. A 16 ms average with a 40 ms p90 stutters, and the average hides it.
Headless evidence:
```bash
cargo run -p makepad-arcade --example bigworld_probe --release
```
Always measure in `--release`. A debug-build frame time tells you nothing about shipping.
## Cutting, in order
1. **Instance/vertex bytes** — packed layout everywhere, batch-constant values as uniforms.
2. **Draw calls** — batch by material and mesh. Many small draws beat nothing.
3. **Skinned character count** — the most expensive category per unit. Cap it; degrade distant characters.
4. **Shadow casters**`set_shadow_budget(n)`; bake AO for the rest.
5. **Terrain resolution** — uploaded once per revision, so this is a memory and vertex-count question more than a per-frame one.
6. **Particle count** — cheap individually, unbounded collectively. Cap the pool.
7. **Fragment work** — last, and only with a measurement showing you are ALU-bound.
## Adaptive quality
`GameRenderer` owns it: `report_frame_ms` each frame, `set_refresh_hz` for the device (72 Hz on Quest, not 60), `set_shadow_budget`, `Stage` / `StageMode`, and `quality_reason()` to explain a drop.
**Do not build a second frame pacer.** Two controllers oscillate against each other and produce worse frame times than either alone. If quality is dropping unexpectedly, read `quality_reason()` — that is what it is for.
## Budget template
Fill this in for the target device before optimising, so you know when to stop:
```
target device Quest 2, 72 Hz -> 13.9 ms
sim 0.1 ms (measured)
skinned chars 8 x ? ms
shadow casters 24 x ? ms
opaque draws ? calls ? ms
particles ? max ? ms
HUD ? ms
headroom >= 20%
```
Twenty percent headroom is not padding: it is what absorbs the worst wave, the busiest camera angle, and thermal throttling on a mobile part after ten minutes of play.