Measure what the mission view's frame is actually spent on #33

Merged
icub3d merged 1 commit from 29-render-resolution-scaling into main 2026-09-13 17:25:18 +00:00
Owner

ADR 0004 proposed dropping PBR on the strength of one number — 31 fps on a tablet — and
was filed Proposed because the measurement that would justify it had not been taken.
This takes it, and refutes it.

What was measured

src/world/perf.rs, behind a new perf feature, steps the mission camera's viewport
through fractions of the window and logs the frame time at each rung, with vsync off so
the curve is visible rather than quantised.

Pixel Tablet, Android 17, Mali-G710, 2560x1600, release build, proving-ground mission —
96 terrain columns, four units, one light, nothing animating:

Configuration Full-res frame Attributable cost
As shipped (MSAA 4x, shadows) 19.43 ms —
Shadows off 18.50 ms shadows 0.93 ms
MSAA off 15.84 ms MSAA 4x 3.59 ms
4.10 Mpx → 0.37 Mpx — pixel count ≤1.42 ms

An eleven-fold cut in pixels buys 1.2 ms of a 19.4 ms frame. The fit is
18.13 ms + 1.42 ms × pixel share.

What it means

93% of the frame does not depend on how many pixels are shaded, or how expensively
each one is shaded.
Unlit materials, toon shading, and a resolution cap all attack the
same 1.4 ms. ADR 0004 proposed spending the project's shader budget — the most
version-fragile code a Bevy project can own — to win 7% of a frame.

ADR 0005 supersedes it. PBR stays, shadows stay, and the art direction is freed from a
performance argument it turns out never to have had. If the cartoony direction is still
wanted, it needs a reason of its own.

What it did not answer

  • 4x MSAA is Bevy's default and costs 3.59 ms — the largest single cost identified. Not
    just a switch to flip: the mission view is hard-edged primitives and aliasing on those
    edges is a readability cost, so it is a visual decision (#32).
  • Roughly 15 ms per frame responds to neither pixel count nor shading, on 96 boxes
    and four units. That is the shape of a CPU bound, and it is the next thing to measure
    (#31).
  • The game still does not hit 60 fps. Only the proposed cause is refuted.

Method note

Measure free-running milliseconds, not vsync-locked fps. Shadows-off (18.50 ms) and
MSAA-off (15.84 ms) both read as "37 fps" under vsync despite being 2.7 ms apart — the
quantisation hides exactly the differences worth acting on.

The viewport method has a stated limit: it leaves the full-resolution clear,
post-processing and present in place, so it measures the 3D pass's pixel sensitivity
rather than everything a true resolution cap would save. Recorded in ADR 0005; not
enough to change the conclusion.

Verification

cargo fmt --check, cargo clippy --all-targets --features perf -- -D warnings, and
cargo test (114 passing) are clean, and the default build is checked too.

No rendering behaviour changes. The shadow and MSAA probes that produced these
numbers were reverted; what lands is the harness and the record.

Closes #29

🤖 Generated with Claude Code

https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh

ADR 0004 proposed dropping PBR on the strength of one number — 31 fps on a tablet — and was filed Proposed because the measurement that would justify it had not been taken. This takes it, and refutes it. ## What was measured `src/world/perf.rs`, behind a new `perf` feature, steps the mission camera's viewport through fractions of the window and logs the frame time at each rung, with vsync off so the curve is visible rather than quantised. Pixel Tablet, Android 17, Mali-G710, 2560x1600, release build, proving-ground mission — 96 terrain columns, four units, one light, nothing animating: | Configuration | Full-res frame | Attributable cost | | --- | --- | --- | | As shipped (MSAA 4x, shadows) | 19.43 ms | — | | Shadows off | 18.50 ms | shadows 0.93 ms | | MSAA off | 15.84 ms | MSAA 4x **3.59 ms** | | 4.10 Mpx → 0.37 Mpx | — | pixel count ≤1.42 ms | An eleven-fold cut in pixels buys 1.2 ms of a 19.4 ms frame. The fit is `18.13 ms + 1.42 ms × pixel share`. ## What it means **93% of the frame does not depend on how many pixels are shaded, or how expensively each one is shaded.** Unlit materials, toon shading, and a resolution cap all attack the same 1.4 ms. ADR 0004 proposed spending the project's shader budget — the most version-fragile code a Bevy project can own — to win 7% of a frame. ADR 0005 supersedes it. PBR stays, shadows stay, and the art direction is freed from a performance argument it turns out never to have had. If the cartoony direction is still wanted, it needs a reason of its own. ## What it did not answer - 4x MSAA is Bevy's default and costs 3.59 ms — the largest single cost identified. Not just a switch to flip: the mission view is hard-edged primitives and aliasing on those edges is a readability cost, so it is a visual decision (#32). - Roughly **15 ms per frame responds to neither pixel count nor shading**, on 96 boxes and four units. That is the shape of a CPU bound, and it is the next thing to measure (#31). - The game still does not hit 60 fps. Only the proposed cause is refuted. ## Method note Measure free-running milliseconds, not vsync-locked fps. Shadows-off (18.50 ms) and MSAA-off (15.84 ms) both read as "37 fps" under vsync despite being 2.7 ms apart — the quantisation hides exactly the differences worth acting on. The viewport method has a stated limit: it leaves the full-resolution clear, post-processing and present in place, so it measures the 3D pass's pixel sensitivity rather than everything a true resolution cap would save. Recorded in ADR 0005; not enough to change the conclusion. ## Verification `cargo fmt --check`, `cargo clippy --all-targets --features perf -- -D warnings`, and `cargo test` (114 passing) are clean, and the default build is checked too. **No rendering behaviour changes.** The shadow and MSAA probes that produced these numbers were reverted; what lands is the harness and the record. Closes #29 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh
ADR 0004 proposed dropping PBR on the strength of one number — 31 fps on a
tablet — and was filed Proposed because the measurement that would justify it
had not been taken. This takes it, and refutes it.

`src/world/perf.rs`, behind a new `perf` feature, steps the mission camera's
viewport through fractions of the window and logs the frame time at each rung.
The feature also drops vsync, because a 60 Hz cap quantises every rung fast
enough to reach it into the same 16.7 ms and hides the curve. It is separate
from `dev` because `dev` watches the filesystem, which an APK's assets are not.

Shrinking a viewport leaves the picture in the corner of a mostly empty screen.
That looks wrong and does not matter: the fragment work for the 3D pass scales
as it would under a real resolution cap, which is the only quantity measured.

On a Pixel Tablet at 2560x1600, free-running, the proving-ground mission:

    as shipped                19.43 ms
    shadows off               18.50 ms    shadows      0.93 ms
    MSAA off                  15.84 ms    MSAA 4x      3.59 ms
    4.10 Mpx -> 0.37 Mpx                  pixel count  1.42 ms

Eleven times fewer pixels buys 1.2 ms of a 19.4 ms frame. The fit is
`18.13 ms + 1.42 ms x pixel share`: 93% of the frame does not depend on how
many pixels are shaded or how expensively each one is shaded. Unlit materials,
toon shading, and a resolution cap all attack the same 1.4 ms, and ADR 0004
proposed spending the project's shader budget — the most version-fragile code a
Bevy project can own — to win 7% of a frame.

ADR 0005 supersedes it. PBR stays, shadows stay, and the art direction is freed
from a performance argument it never had.

Two things the measurement found rather than answered are filed: 4x MSAA is on
by default and costs 3.59 ms, which is a visual decision and not just a switch
to flip (#32); and roughly 15 ms per frame responds to neither pixel count nor
shading, on a scene of 96 boxes and four units, which is the shape of a CPU
bound and is the next thing to measure (#31).

No rendering behaviour changes here. The probes that produced these numbers
were reverted; what lands is the harness and the record.

Closes #29

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TA4hJHkRSU3XBxZYtMKXdh
icub3d merged commit 7bb279b442 into main 2026-09-13 17:25:18 +00:00
icub3d deleted branch 29-render-resolution-scaling 2026-09-13 17:25:18 +00:00
Sign in to join this conversation.
No description provided.