Skip to content

fps: Cap the headline by asking the platform what the panel runs at - #2956

Merged
huacnlee merged 3 commits into
mainfrom
fps-cap-only-on-a-sustained-cadence
Sep 5, 2026
Merged

fps: Cap the headline by asking the platform what the panel runs at#2956
huacnlee merged 3 commits into
mainfrom
fps-cap-only-on-a-sustained-cadence

Conversation

@huacnlee

@huacnlee huacnlee commented Sep 4, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #2954. MAX FPS was capped by a refresh rate inferred from the gaps between presents. That inference cannot work: those gaps are whole multiples of the panel's period, so they bound it from below and never from above — 41.7ms is six refreshes at 144Hz and one at 24Hz, and nothing in the timing distinguishes them.

Four estimators, four wrong readings on real windows, the last three found in longbridge-lite:

Estimator Read Should have been
Shortest gap ever seen 169 144 — a compositor catch-up is not a refresh
Densest group of gaps 149 144 — bucketing truncates the distribution it measures
Densest group of gaps 75 ~137 — the densest thing a demand-drawing window does is idle
Fastest sustained run 24 ~116 — the application's own timer fired every 41.7ms
Frame budget until established 60 ~167 — a ceiling under the truth hides the figure entirely

A steady 41.7ms application tick is indistinguishable from a 24Hz panel. No tuning gets past that.

Changes

The guessing is gone, and the platform is asked instead. GPUI hands out the platform's own display handle through DisplayId, and the HUD takes it from there:

  • macOSCGDisplayCopyDisplayMode on the CGDirectDisplayID
  • WindowsEnumDisplaySettingsW on the monitor's device name
  • Wayland — object ids are per-connection and mean nothing across one, so the outputs are enumerated again and matched to GPUI's displays by the identity it derives from their names
  • X11 and everything else — no query, so no cap

A panel that reports no fixed rate — which is what ProMotion honestly is through CoreGraphics — is read as no cap. Where nobody will say, the reading is left uncapped rather than held to a guess.

The answer is re-asked when the window moves to another display, and not otherwise: it is a property of the panel, and on some platforms asking is a round trip.

The warm-up stays. Frames from before the HUD was mounted, and the cold ones right after it, are dropped rather than measured, so a window that just opened still reads healthy.

Verified

On Wayland against two panels, with a temporary probe:

output 1492e027-… -> 143.998 Hz     (compositor: 143.999)
output 09e7b298-… -> 59.997 Hz      (compositor: 59.997)
window on the first  -> 143.998 Hz

and the HUD on that window, FRAME 4.3ms — 232 uncapped — reading MAX 144, from the moment it opened and with no interaction.

Not verified on hardware: the macOS and Windows queries could not be compiled here, let alone run. The Wayland path and everything above it is measured.

Test plan

  • macOS: MAX stops at the panel's rate on an external display; a built-in ProMotion panel reads uncapped
  • Windows: MAX stops at the panel's rate
  • Drag the window between two monitors of different rates: MAX follows
  • A window with a regular timer (quote ticks, a clock) is not capped by its own cadence
  • A window that just opened still reads DROP 0.0% and a green P95

AI Assistance

🤖 Code and docs generated by Claude Code; the wrong readings were caught by the author on real windows. Verified locally on Linux/Wayland with 144Hz and 60Hz panels; cargo test -p gpui-fps (28) passes, clippy --deny warnings clean. The macOS and Windows queries are unverified — they cannot be built on this machine.

🤖 Generated with Claude Code

https://claude.ai/code/session_01USSMRpQ5W58YP3UKUCzrri

`MAX FPS` was capped by a refresh rate inferred from the gaps between
presents. It cannot be: those gaps are whole multiples of the panel's period,
so they bound it from below and never from above — 41.7ms is six refreshes at
144Hz and one at 24Hz, and nothing in the timing says which.

Four estimators, four wrong readings on real windows. The shortest gap ever
seen read 169 on a 144Hz panel, because a compositor catch-up is not a
refresh. The densest group of gaps read 149, because bucketing truncates the
distribution it measures, and 75 on a window drawing every other refresh,
because the densest thing a window that draws on demand does is idle. The
fastest sustained run read 24 on an application whose own timer fired every
41.7ms, which is a cadence held perfectly steady and has nothing to do with
the display. Falling back to the frame budget until a cadence was established
read 60 against a 6ms frame — a ceiling under the truth, which hides the
figure the reader came for.

So the headline is what the frame cost can prove and claims nothing more.
Capping it needs the refresh rate from the platform, which every backend
already has — xrandr mode info on X11, `CVTimeStamp`'s video refresh period
on macOS, the `wl_output` mode event on Wayland — and which the display trait
does not carry.

The warm-up stays: the frames from before the HUD was mounted, and the cold
ones right after, are still dropped rather than measured, so a window that
just opened still reads healthy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USSMRpQ5W58YP3UKUCzrri
@huacnlee
huacnlee force-pushed the fps-cap-only-on-a-sustained-cadence branch from 8160774 to cecb4e7 Compare September 4, 2026 16:28
@huacnlee huacnlee changed the title fps: Cap the headline only by a cadence the window actually held fps: Stop guessing the display's refresh rate Sep 4, 2026
Nothing about a window's own frames can establish its display's refresh rate.
The gaps between presents are whole multiples of the panel's period, so they
bound it from below and never from above — 41.7ms is six refreshes at 144Hz
and one at 24Hz — and every estimate tried read a real window wrong: 169 and
149 from the shortest and the densest gaps, 75 from a window drawing every
other refresh, and 24 from an application whose own timer fired every 41.7ms.

So the platform is asked. GPUI hands out its display handle through
`DisplayId`: a `CGDirectDisplayID` on macOS, an `HMONITOR` on Windows. Wayland
gives out per-connection object ids that mean nothing to a second connection,
so the outputs are enumerated again there and matched to GPUI's displays by
the identity it derives from their names. X11 and everything else have no
query and stay uncapped, as does a panel that reports no fixed rate — which is
what a ProMotion display honestly is.

The answer is re-asked when the window moves to another display, and not
otherwise: it is a property of the panel, and on some platforms asking is a
round trip.

Measured on Wayland against two panels: 143.998Hz and 59.997Hz discovered,
against 143.999 and 59.997 from the compositor, and a window whose frames cost
4.3ms — 232 uncapped — reading MAX 144 on the faster one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USSMRpQ5W58YP3UKUCzrri
@huacnlee huacnlee changed the title fps: Stop guessing the display's refresh rate fps: Cap the headline by asking the platform what the panel runs at Sep 4, 2026
Co-authored-by: Codex <codex@openai.com>
@huacnlee
huacnlee enabled auto-merge (squash) September 5, 2026 02:16
@huacnlee
huacnlee merged commit bc0ac76 into main Sep 5, 2026
9 checks passed
@huacnlee
huacnlee deleted the fps-cap-only-on-a-sustained-cadence branch September 5, 2026 02:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant