Skip to content

shim: a late start keeps its slot's timeline (FPS limiter ignored at 120 Hz) - #49

Merged
utkarshdalal merged 1 commit into
masterfrom
shim-cap-gate-late-start-keeps-slot
Nov 3, 2026
Merged

utkarshdalal merged 1 commit into
masterfrom
shim-cap-gate-late-start-keeps-slot

Conversation

@utkarshdalal

@utkarshdalal utkarshdalal commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Report

App master with payload 20261103.6 (shim 762e559): the FPS limiter set to 20 under gamescope on a 120 Hz panel left MGS V: The Phantom Pain at 40-50 fps. At 60 Hz the cap held.

Reproduced

Retroid Pocket 6 at 120 Hz, DTD 1920x1080, cap 20, container env mailbox:

present mode: declared=mailbox requested=immediate source=env route=gamescope cap=20 gate=vsync ... gate_holds=188 gate_late=8014 skipped=13325 ... spacing=[<15:0 15-16.2:0 16.2-17.2:0 17.2-20:524 >20:4]
present mode: ... gate_holds=188 gate_late=8541 skipped=14000 ...
present mode: ... gate_holds=188 gate_late=9068 skipped=14675 ...

The cap reached the shim. The gate held 188 frames, then never held another one: gate_late grows by one per frame and the game runs at its own ~53 fps.

Cause

#47 (1cc98e1) carried two changes. The fix for JT's 40 fps capture, the gate off under the cap-rate FIFO declaration, stays. The second, "the release after a late start is never more than one grid instant after it", is the bug: a cap below the display's rate holds the guest for a whole slot (six vblanks at 20 on 120 Hz), and after that rule fired once the next release was one vblank after the late start. A guest whose frame outlasts one period plus ON_TIME_NS (0.5 ms) is late for that release too, and for every one after it, so the gate never holds again. At 120 Hz (8.3 ms) that is every game; at 60 Hz a guest faster than ~58 fps recovers, which is why a 60 Hz panel hides it.

Change

Schedule::book (icd_shim/src/vsync_gate.rs): a start late by less than a period is booked to its release point, as before. A start late by a period or more but still inside its slot is booked to the grid instant it is in and keeps the slot's ideal timeline, so the release after it is where the cap puts it, at most the rest of the slot away. A start past the whole slot restarts the timeline from the instant it is in, so a stall carries no debt. The cap-rate cell is unchanged (slot == period), so the #47 capture stays fixed.

Trade: a late frame's successor can be held for the rest of its slot instead of coming out one vblank later; that is what a limiter does on a hitch, and the cap holds on average.

Tests: the late-frame-on-a-divisor-cap test now expects the slot's release (two vblanks at 30 on 60) and covers late by a whole vblank and past the slot; a new test runs a guest with 18 ms frames, 1 ms late wake-ups and one 80 ms hitch under cap 20 on 120 Hz (and 22 ms frames under 20 on 60 Hz) and requires the 6- (3-) vblank cadence apart from the frame after the hitch. The 16 ms guest under cap 60 on 59.97 Hz still gets every vblank.

docs/15 states the rule and why the next-grid-instant release is gone.

Verification

  • Android cross-build of the shim: OK.
  • Unit tests: 90 of 90 (adrenotools_gamescope lib suite) on the RP6 through an adb cargo runner.
  • Device: RP6 at 120 Hz, DTD, caps 20/40/60 live: 20.0 / 40.0 / 53 (game-bound); 100 s run 20.02 fps. See the comments.

Delivery: payload gamescope-gamenative-20261103.7.tzst (sha256 1b2dc730…4f55, shim b10cb509…9122 from 5d1e0ae) is on both CDN hosts; app PR utkarshdalal/GameNative#2336 bumps GamescopePayload.VERSION and depends on this PR.
EOF
)

…ant release after a late start

Reported on app master with payload 20261103.6 (shim 762e559): the FPS
limiter set to 20 under gamescope on a 120 Hz panel left MGS V: The
Phantom Pain at 40-50 fps; at 60 Hz the cap held. Reproduced on the
Retroid Pocket 6 at 120 Hz with DTD at 1920x1080, cap 20, env mailbox:
census `declared=mailbox requested=immediate source=env cap=20
gate=vsync`, `gate_holds` frozen at 188 for the rest of the session while
`gate_late` grew by one per frame, frame spacing 17.2-20 ms (DTD's own
~53 fps). The cap reached the shim; the gate stopped holding.

Cause: #47 (1cc98e1) added, next to the fix for
the cap-rate FIFO cell (gate off under that declaration, kept here), a
rule that the release after a late start is never more than one grid
instant after it. A cap below the display's rate holds the guest for a
whole slot (six vblanks at 20 on 120 Hz); after that rule fired once the
next release was one vblank after the late start, a guest whose frame
outlasts one period plus ON_TIME_NS (0.5 ms) was late for that release
too, and so for every release after: the gate never held again. At 120 Hz
(8.3 ms period) that is every game; at 60 Hz a guest faster than ~58 fps
recovers, which is why the 60 Hz panel hid it.

- icd_shim/src/vsync_gate.rs: Schedule::book: a start late by less than a
  period is booked to its release point as before; a start late by a
  period or more but still inside its slot (before `ideal + interval`) is
  booked to the grid instant it is in and keeps the slot's ideal timeline,
  so the release after it is where the cap puts it, at most the rest of
  the slot away; a start past the whole slot restarts the timeline from
  the instant it is in, so a stall carries no debt. The cap-rate cell is
  unchanged (slot == period). Tests: the late-frame-on-a-divisor-cap test
  now expects the slot's release (two vblanks at 30 on 60) and covers late
  by a whole vblank and past the slot; a new test runs a guest with 18 ms
  frames, 1 ms late wake-ups and one 80 ms hitch under cap 20 on 120 Hz
  (and 22 ms frames under 20 on 60 Hz) and requires the 6- (3-) vblank
  cadence everywhere but the three releases after the hitch, which catch
  the slot's timeline up, and never more releases than the cap over the
  run. The existing 16 ms guest on a 59.97 Hz grid under cap 60 still
  gets every vblank.
- docs/15: the gate paragraph states the rule and why the next-grid-instant
  release is gone.
@utkarshdalal
utkarshdalal force-pushed the shim-cap-gate-late-start-keeps-slot branch from 983bed0 to 5d1e0ae Compare November 3, 2026 16:50
@utkarshdalal

Copy link
Copy Markdown
Contributor Author

Device verification, Retroid Pocket 6 at 120 Hz, DTD 1920x1080, env mailbox, shim from this branch (sha256 0339ed44…), Utkarsh switching the Quick Menu limiter live:

cap shim acquires/s (per-second release: lines) gate_holds gate_late
20 20.0 steady (201 commits per 10 s census) +1 per frame flat
40 40.0 steady +1 per frame flat
60 52-53 (DTD's own GPU-bound rate, above the cap is impossible) flat +1 per frame
40 again 40.0 +1 per frame flat
20 again 20.0 +1 per frame flat

Each switch settled within the same second (40 → 20: 26.8 fps for the partial second, 20.0 from the next; 20 → 40: 24.8 then 40.6). Before this change the same device, scene and cap 20 read gate_holds=188 frozen with gate_late +1 per frame at ~53 fps.

The 3-4 s stretches with almost no acquires in the log are the Quick Menu being open (the session is stopped while it is up); the HUD's 2 s frame window then takes a couple of seconds to show the new rate after the menu closes. That is the app's HUD, unchanged here.

Unit tests (the whole adrenotools_gamescope lib suite, run on the RP6 through an adb cargo runner since the crate's tests need Linux): 89 of 90 passed at 983bed0; the one failure was the new test's assertion around the simulated 80 ms hitch, where the two releases after the stall catch the slot's timeline up (3 and 4 vblanks) before the 6-vblank cadence resumes, and the release count never exceeds the cap. 5d1e0ae asserts exactly that; its rerun on the device follows this comment.

@utkarshdalal

Copy link
Copy Markdown
Contributor Author

Unit tests at 5d1e0ae on the RP6: 90 passed, 0 failed. Independent 100 s gameplay run with the branch shim (DTD boat overworld, 120 Hz, cap 20): 2010 commits in 100.4 s = 20.02 fps, gate_holds +2010, gate_late +16, log in gn-testlogs/mailbox-vsync-gate-20261102/runs/capgate-1/.

@utkarshdalal
utkarshdalal merged commit a2295b8 into master Nov 3, 2026
utkarshdalal added a commit to utkarshdalal/GameNative that referenced this pull request Nov 3, 2026
…panel rate again (#2336)

Ships GameNative/gamenative-drm#49: the shim's vsync-grid gate no longer
releases the frame after a late start at the next grid instant, a rule
from GameNative/gamenative-drm#47 that made every cap below the panel's
rate stop holding once a frame outlasted one vblank (MGS V at cap 20 on a
120 Hz panel ran at 40-50 fps; DTD at 53). Only GamescopePayload.VERSION
changes.

Co-authored-by: Utkarsh Dalal <utkarsh.dalal@toptal.com>
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