Repository navigation
android: A52 — release limited frames on the vblank time grid, not on every Nth wakeup - #22
Merged
Merged
Conversation
… every Nth wakeup The non-VRR frame-rate limit counted compositor wakeups: a window got its frame callback on every (refresh / target)th wakeup, which equals every Nth vblank only while no wakeup is ever late. The timer re-arms by stepping to the next vblank after "now", so a wakeup that comes more than one vblank late skips the vblanks in between, and those never count. On the 1200 Hz grid a session runs on (-r 1200, the nested refresh), a wakeup is late by a vblank after 0.83 ms of scheduling delay, and every late one holds the next frame a whole vblank longer: a 60 cap then reads 50-57, a 30 cap 25-28, in proportion to the wakeups lost, and it sinks further as the device throttles and the compositor thread is scheduled later still. The limiter now keeps a release grid in time: the next release is the previous one plus the target period, and a frame is released on the first vblank whose nominal time reaches it, with half a vblank of tolerance for the anchor moving. A late wakeup delays that one release by the lateness, and the grid absorbs it on the next, so the average rate is the target. A stall longer than a period is a lost frame, never a debt to repay with back-to-back releases. The grid is the nested refresh and the target alone; the panel's rate plays no part here. The VRR path is untouched: it already schedules by time.
5 of 8 tasks
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.
Rebased replacement for #21, which GitHub closed when its base branch (
fix/gamescope-resolution-scanout, merged as #20) was deleted. One patch,0050-A52-fps-limit-on-the-vblank-time-grid.patch, on top of A51. The latch-grid snap that #21 carried for a while is gone: the grid is the nested refresh and the target alone.What this fixes
The non-VRR frame-rate limit counts compositor wakeups: a window gets its frame callback on every
refresh / targetth wakeup (steamcompmgr_should_vblank_window,vblank_idx % nVblankDivisor). That equals every Nth vblank only while no wakeup is ever late.CVBlankTimer::GetNextVBlanksteps to the next vblank after now, so a wakeup that lands more than one vblank late skips the vblanks in between, andvblank_idxnever counts them. A session runs on the 1200 Hz grid (-r 1200), where a wakeup is late by a vblank after 0.83 ms of scheduling delay, so every late wakeup holds the next release a whole vblank longer and the cap lands below the target in proportion to the wakeups lost.The change
The limiter keeps a release grid in time: the next release is the previous one plus the target period, and a frame is released on the first vblank whose nominal time (
VBlankTime::schedule.ulTargetVBlank) reaches it, with half a vblank of tolerance for the anchor moving. A late wakeup delays that one release by the lateness and the grid absorbs it on the next, so the average rate is the target. A stall longer than a period is a lost frame, never a debt repaid with back-to-back releases. The VRR path is untouched.Measured (Retroid Pocket 6, 8 Gen 2 / Adreno 740, Android 13, SteamWorld Dig 2 windowed, GL via zink, cap 60, payload 20261030.3 with only
bin/gamescopeswapped)Frames counted at SurfaceFlinger (
dumpsys SurfaceFlinger --latency), 30 s per run, same in-game scene; the app's on-screen counter agreed in every run.Swapped shipped → A52 → shipped → A52; it reproduced both ways. The loss follows the 1200 Hz nested grid, not the panel rate (same ~10 % at 60 Hz and at 120 Hz). A52 held 60 with the app's power tuner at minimum clocks (prime core 595 MHz), so CPU frequency is not the cause. At 120 Hz, A52's releases are still not phase-locked to the panel (the 10x/30x pairs); that is a separate pacing matter, not the cap.