Repository navigation
Full-rate frames ready early wait for the refresh the others land on - #552
Merged
Merged
Conversation
|
Instruction counts (cachegrind, exact) against
|
wingleeio
force-pushed
the
full-rate-tiles
branch
from
October 26, 2026 02:52
30bed9b to
7f02cd2
Compare
wingleeio
force-pushed
the
flush/tile-align
branch
from
October 26, 2026 03:45
327d5e7 to
c537dbb
Compare
wingleeio
force-pushed
the
flush/tile-align
branch
from
October 26, 2026 05:41
c537dbb to
7f9380b
Compare
At 120 Hz the display link fires about 2.5 ms before a refresh point and a frame is ready about 1.5 ms before it, which the window server shows on the refresh after. A few (about one frame in twenty) are ready earlier, up to 3 ms before, and show on that refresh instead: two frames land in one refresh, the next shows nothing new, and animate's p99 interval reads 17.7 ms rather than 13.6. Those few now wait until 1.6 ms before the point (align_wait), one short sleep; the rest go at once. Only frames a display refresh asked for, at a refresh under 12 ms, outside a resize's settle: input's frames, 60 Hz and the latch's steady frames are as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wingleeio
force-pushed
the
flush/tile-align
branch
from
October 26, 2026 05:54
7f9380b to
280c058
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #551, which makes full-rate frames plain tiles. I'll retarget this to main when #551 merges.
What was wrong. At 120 Hz the display link fires about 2.5 ms before a refresh point. A frame takes about 0.8 ms to render and wait for the GPU, so it's ready about 1.5 ms before the point, and the window server shows it on the refresh after. About one frame in twenty is ready earlier, 1.7–3.2 ms before the point, and shows on that refresh instead. Two frames then land in one refresh and the next shows nothing new. That's why tiles alone still read a 17.7 ms p99 interval.
Measured with host-clock stamps of each tile commit against the display link's output grid (93% of commits at 1.4–1.6 ms before a point). The same phase holds on the AV path, where the AV renderer's own
copyDisplayedPixelBuffershowed it taking 99.5% of frames: the loss is in which composite a frame reaches.The change.
align_wait: a frame ready 1.85–4.5 ms before a point waits until 1.6 ms before it, in one short sleep. About 5% of frames wait, each under 1.5 ms. Commits then all land 1.0–1.75 ms before a point, and handover intervals run 7.9–8.8 ms (p1–p99).Applies only when all of these hold:
refresh_frame): input's frames are drawn and shown at once, as before;refresh_grid), are untouched;Why not a hold on every frame. Holding every frame past the point gave p99 12.7–12.8 ms, but on the AV layer it cost +0.41 points of CPU (two wakeups and a split frame every refresh). Sleeping once didn't change that, and neither did timed AV presentation (PTS on the refresh grid, p99 only −1.4 ms). Only the early tail needs moving.
Measurements. One quiet window, Zeron hidden, one binary (tiles = #551's path), 8 interleaved ScreenCaptureKit captures each (
scripts/framepace.swift), and 4 interleaved suite sampler runs (scripts/macsample.swift) each. The desktop was busy, so compare within the run.No variant had long intervals (>25 ms).
Offline (10 s runs,
/usr/bin/time -l, 3 alternating rounds), against tiles: involuntary context switches 10.3K vs 10.3K, instructions 2.25 G vs 2.25 G per run.Checks:
only_early_frames_at_a_fast_refresh_wait_for_the_others_pointcoversalign_wait: the early tail waits toALIGN_AT, the main cluster and late frames don't, frames a refresh early don't, and 60 Hz never waits.ZGX_DAMAGE_CHECK=1: no stale pixels, a full repaint at each resize.sample: 0, against 5 withZGX_PRESENT_VIDEO=1).-D warnings, and the zgx-desktop tests (176 passed).The input-latency probe (
macos_latency_probe, typing) is in the shared verification window with #551. Typing frames aren't refresh frames, so they never wait.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.