Repository navigation
Commit b586fad
fps: Derive the headline rate from frame cost instead of driving the frame loop (longbridge#2954)
## Summary
The HUD asked for a frame after every render, and the doc claimed that
being its own view kept the repaint to the HUD's subtree. It does not —
a dirty view schedules a **window** draw, and GPUI re-renders every view
outside an `Entity::cached` boundary. So each frame the HUD asked for
was a full layout and paint of the application, reported one row down as
the application's CPU.
The rate it was buying is already in the samples: `FRAME` is what a full
redraw costs, so its reciprocal is the rate those redraws could sustain,
and nothing has to be drawn to find it.
| Table page, focused, untouched | Before | After |
| --- | --- | --- |
| Process CPU | **~62%** | **~0%** |
| HUD reads | 60 FPS / CPU 27% | MAX 115 FPS / FRAME 8.7ms / DROP 0.0% |
## Changes
**Headline is two rates, right-click to switch, `MAX` says which.** `MAX
FPS` is `1 / FRAME`; `FPS` is presents per second. Both come from the
same samples, so switching is free — which is the point: the only
expensive way to make the first number meaningful is to stop the second
from being readable. The marker goes in the box that already balanced
the unit, so the figure does not move. Click still collapses.
**`MAX` is capped by the display.** Counting presents had that ceiling
for free (frames go to the compositor on vsync, see longbridge#2944); a derived
figure does not, and a frame drawn in 3ms reads as 333. GPUI does not
expose the refresh rate, so the sampler infers it from the gaps between
presents: only gaps that could be a refresh (3–50ms), only ones that
recur, the mean of the busiest group *and its neighbours* (bucketing
truncates the group it measures — that read 149 on a 144Hz panel),
preferring a group at least twice as fast when one arrives in bulk (a
ProMotion window resting at 60 must still be capped at 120), snapped to
a standard rate within 2.5%. Measured on a 143.999Hz panel: estimate
146.6, reads **144**.
**The cold start is not measured.** Mounting the HUD drained everything
GPUI had recorded since process start — shaders, glyph atlas, icons,
every cache cold. One of those frames is 100ms against a budget of 16,
which a HUD that has seen eight frames reports as a twelfth of the
window's work, in amber, before the reader has done anything. Both the
backlog and the first frames after it are now dropped, so a window that
just opened reads healthy.
**`continuous` is gone.** Its only effect was to manufacture the thing
it was measuring. Removed from `gpui-fps` and from the script API —
`show_fps_monitor({ continuous })`, `.continuous()`, the typing and the
key list. **This is a breaking change for scripts**, but the option can
no longer select anything.
**A readout clock replaces the resource timer.** Nothing else wakes a
HUD that has stopped asking for frames; `show_resources(false)` used to
freeze the figures at whatever the application last drew.
**story drops `inactive_frame_interval: 500ms`**, which existed only to
hold the HUD's self-driven redraws to 2 FPS in the background.
**Docs**: `website/docs/fps.md` and its zh-CN mirror, with the reasoning
rather than just the behaviour — `MAX FPS` is a rate nothing was clocked
at, and without that written down the next reader concludes the
framework cannot hold a frame rate.
## Test plan
- [ ] HUD on, window focused, untouched: CPU near zero, `MAX` matches
`1000/FRAME`, `DROP` and `P95` green
- [ ] Right-click switches to `FPS` (single digits while idle) and back
- [ ] After scrolling, `MAX` stops at the panel's rate — 60 on a 60Hz
display
- [ ] macOS ProMotion: caps at 120 while scrolling, and still 120 after
it settles
- [ ] A heavy page (Table / DataTable) reads a visibly lower `MAX`
- [ ] `show_resources(false)` still refreshes rather than freezing
- [ ] `fps_monitor().continuous(false)` in a script now errors as an
unknown property
- [ ] Animated stories no longer step at 2 FPS in a background window
## AI Assistance
> 🤖 Code and docs generated by Claude Code. The direction — derive MAX
from `FRAME`, drop `continuous`, right-click between the two rates,
never exceed the display, open healthy — is the author's. Verified
locally on Linux/Wayland with 144Hz and 60Hz panels (debug build,
`/proc/<pid>/stat` sampling, screenshots, temporary instrumentation for
the refresh estimate); `cargo test -p gpui-fps` (31) and `-p gpui-shell`
(695+4+4) pass, clippy `--deny warnings` clean, wasm target builds.
**ProMotion is covered by a unit test only — not verified on macOS
hardware.**
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01USSMRpQ5W58YP3UKUCzrri
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent a1275a9 commit b586fad
16 files changed
Lines changed: 757 additions & 190 deletions
File tree
- crates
- fps/src
- shell/src
- engine/quickjs
- materialize/components
- tests
- story/src
- website
- docs
- zh-CN/docs
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
10 | 12 | | |
11 | 13 | | |
12 | 14 | | |
| |||
25 | 27 | | |
26 | 28 | | |
27 | 29 | | |
28 | | - | |
29 | | - | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
| |||
0 commit comments