Repository navigation
shim: at a cap at the display's rate, a guest's FIFO request keeps the container's mode with the vblank-grid gate - #48
Merged
Conversation
added 2 commits
November 3, 2026 15:19
…e container's mode with the vblank-grid gate @joshuatam's capture on payload 20261103.4 (shim 2567925; a 1280x960 device, DTD in game, cap 60, env mailbox, in-game VSync on, 60 Hz) still settled at 40 fps with the gate off in the cap-rate cell: declared FIFO, 400 commits per 10 s against 400/600 daemon publishes. The shipped stack (2bfe8b3) does the same after a live 120 to 60 Hz switch. A guest with VSync on waits for each frame's presentation before it starts the next. Declared FIFO, that wait is answered by gamescope's presented at its latch, so the guest's whole frame has to fit between two latches; with about 16 ms of work at 60 Hz and three images, frames alternate between one and two vblanks. At 120 Hz the same session was declared mailbox with the gate, its wait answered at commit, and held 60 fps; on the Retroid Pocket 6 the same cell declared mailbox with the gate gave 59.983 fps and no doubles (B1). A guest with VSync off paces itself and, declared mailbox at this cap, had ~1 frame a second replaced in gamescope's mailbox (B4-B6), so FIFO stays for it. At a cap at the display's rate, with the container's mode not FIFO: - the guest asked for FIFO or FIFO-relaxed: the container's mode with the vblank-grid gate, one release per vblank (source=cap-rate-vsync); the present wait is answered at commit, and the swapchain keeps that mode's image count, the spare image included; - the guest asked for mailbox or immediate: FIFO, no gate (source=cap-rate), unchanged. An env of fifo still wins everywhere; caps below the display's rate, no cap, frame generation and cap-compat are unchanged. The census's slot_waits and commit_lat_us now count whether or not the gate runs, so a FIFO cell's image waits and acquire-to-commit time show in a GNDRM_LOG_VKSHIM=1 capture. - icd_shim/src/proxy.rs: Declared::CapRateVsync in choose_live_mode and the census (source=cap-rate-vsync); the slot count keeps the container's mode for it; acquired_ns and slot waits recorded on every acquire; tests: 60 on 59.97 Hz + env mailbox + guest fifo or relaxed -> mailbox, gate every vblank; guest mailbox or immediate -> FIFO, gate off; env fifo -> FIFO, gate off; 60 on 120 and no cap unchanged. - docs/10, docs/15: the rule table and the reason.
…st queues it, not after its render fence Retroid Pocket 6, PR 48 shim (d61b614), DTD at 1080p, cap 60, in-game VSync on, 60 Hz, declared mailbox with the vblank-grid gate: 42 fps with 23 ms from acquire to commit; the game's main thread at 43 % of a core, no core saturated; the GPU at 65-69 % busy and 550-615 MHz with no clock cap. In a heavier spot earlier the frame took 18 ms at 680 MHz and 88 % busy; 18 ms x 680/550 is the 22-23 ms seen now. The frame is GPU-time-bound, but the GPU's governor sees bubbles and does not ramp. The bubbles come from the present wait. DXVK with VSync on waits for the present of frame N - L before starting frame N (L its frame latency) and calls vkWaitForPresentKHR because its own swapchain is FIFO; the shim's mailbox declaration is invisible to it. The shim answered that wait in commit_real, after the worker had waited the frame's render fence, so with L = 1 the CPU of frame N+1 could not start until the GPU had finished frame N: CPU and GPU alternated instead of overlapping. On the X11 route the same game overlaps and holds 60: the wrapper's Mesa X11 WSI completes the id on PresentCompleteNotify, which the app's X server sends while handling the PresentPixmap request (PresentExtension.java), before the GPU is done. On gamescope's route a wait on a frame that goes out as mailbox or immediate, or whose guest asked for either, is now answered in present_dispatch, when the guest queues the present. The acquire gate still holds the rate. FIFO and FIFO-relaxed declarations keep the answer from gamescope's presentation feedback; off the route the answer stays at the commit. Frame generation (mailbox) is answered at queue the same way. Each id is answered once: present_dispatch takes it off the slot under the swapchain lock, so the commit, a mailbox replacement and the release see no wait to answer, and the daemon's later latch report finds the id no longer armed (present::record_for). A slot whose mode changed between queue and commit is still answered at the commit. The periodic census gains at_queue=, the waits answered this way; presented, discarded and fallback keep counting the compositor's answers. - icd_shim/src/proxy.rs: WaitAnswer and present_wait_answer; the answer in present_dispatch; commit_real answers what is left; census at_queue=; test: the answer point for each declaration, on and off the route. - icd_shim/src/present.rs: answered_at_queue and its counter. - docs/10, docs/15: where the wait is answered, with the measurement.
3 of 8 tasks
8a5b695 answered every mailbox or immediate presentation's wait when the guest queued it. The measurement it rests on is one cell: Retroid Pocket 6, DTD at 1080p, cap 60, in-game VSync on, 60 Hz, declared mailbox with the vblank-grid gate (source=cap-rate-vsync): 42 fps with the wait answered after the render fence, 60.0 with it answered at queue (acquire to commit 23 -> 14.3 ms). Nothing measured the other mailbox and immediate cases. The answer at queue now applies only there: gamescope's route, a cap at the display's rate, a container mode other than FIFO, a guest that asked for FIFO or FIFO-relaxed. Every other presentation is answered exactly as on master: a frame going out as mailbox or immediate, or from a guest that asked for either (caps below the display's rate, no cap, frame generation, cap-rate and cap-compat with a self-paced guest), at the commit after the fence; FIFO and FIFO-relaxed from gamescope's presentation feedback. Each id is still answered once, and a frame whose declaration changed between queue and commit is answered at the commit. With this, both commits of #48 change one situation only: the limiter at the panel's rate, in-game VSync on, and a container on a present mode other than fifo. A container set to fifo never reaches it. - icd_shim/src/proxy.rs: present_wait_answer takes the declaration's reason; present_dispatch and commit_real pass it; test: at queue only for cap-rate-vsync, at commit for the container's mode, frame generation and a self-paced guest under cap-rate or cap-compat, the compositor for FIFO, master's answers off the route. - docs/10, docs/15: the answer at queue described as the one case.
utkarshdalal
pushed a commit
to utkarshdalal/GameNative
that referenced
this pull request
Nov 3, 2026
…c-on cell only Payload 20261103.6 is 20261102.4 with only lib/libadrenotools_gamescope.so replaced (d04525f2, build string 202611031208762e559), the shim from GameNative/gamenative-drm#48 at 762e559. It narrows 20261103.5: the present wait is answered when the game queues the present only for a game with in-game VSync on at a cap equal to the panel's rate in a container whose present mode is not fifo; every other case answers as on master. gamescope, Xwayland and libgamenative_drm.so are unchanged.
utkarshdalal
added a commit
to utkarshdalal/GameNative
that referenced
this pull request
Nov 3, 2026
… the vsync grid (#2332) * gamescope: payload 20261103.5, a VSync-on game at the panel's rate on the vsync grid Payload 20261103.5 is 20261102.4 with only lib/libadrenotools_gamescope.so replaced (a0bc3ab2, build string 2026110310298a5b695), the shim from GameNative/gamenative-drm#48 at 8a5b695. At a cap equal to the panel's rate a game with in-game VSync on is presented as mailbox with the shim's vsync-grid gate, and its present wait is answered when it queues the present, so its CPU and GPU work overlap instead of alternating. Games with VSync off and every other case are unchanged. gamescope, Xwayland and libgamenative_drm.so are unchanged. * gamescope: payload 20261103.6, the present wait at queue for the VSync-on cell only Payload 20261103.6 is 20261102.4 with only lib/libadrenotools_gamescope.so replaced (d04525f2, build string 202611031208762e559), the shim from GameNative/gamenative-drm#48 at 762e559. It narrows 20261103.5: the present wait is answered when the game queues the present only for a game with in-game VSync on at a cap equal to the panel's rate in a container whose present mode is not fifo; every other case answers as on master. gamescope, Xwayland and libgamenative_drm.so are unchanged. --------- Co-authored-by: Utkarsh Dalal <utkarsh.dalal@toptal.com>
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.
Status and merge criteria
Do not merge yet.
Verified on the RP6 (narrowed build 762e559). Setup: Dave the Diver at 1080p, 60 Hz, cap 60, in-game VSync on, in the heavy spot.
at_queueequal to commits, no slot waits.Latency, stated honestly. Source: the RP6 capture analysis, latency-compare.md.
Merge criteria:
@joshuatam, a follow-up to #46 and #47 for the 40 fps you still see on payload 20261103.4. I'd like your read on it.
Scope. Both changes here affect one situation only: the limiter at the panel's rate, in-game VSync on, and a container on a present mode other than fifo. Every other case presents and answers exactly as on master. Setting a game's container present mode to fifo restores master's behaviour for that game.
Problem
Your capture on 20261103.4 still settles at 40 fps. The setup is shim 2567925, a 1280x960 device, DTD in game, cap 60, env mailbox, in-game VSync on, at 60 Hz. The census reads
declared=fifo source=cap-rate gate=off, which is master's behaviour in this cell. The shim makes 400 commits per 10 s against the daemon's 400/600 publishes. The shipped stack (2bfe8b3) does the same after a live 120 to 60 Hz switch.A guest with VSync on waits for each frame's presentation before it starts the next one. Declared FIFO, gamescope's
presentedanswers that wait at its latch, so the guest's whole frame has to fit between two latches. With about 16 ms of work at 60 Hz and three images, frames alternate between one and two vblanks.At 120 Hz the same session was declared mailbox with the gate. Its wait was answered at commit and it held 60 fps. On the Retroid Pocket 6, the cap-rate cell declared mailbox with the gate gave 59.983 fps and 0 doubles (B1 in #46).
What changes
Only the cap-at-display-rate cell, when the container's mode is not FIFO, splits by what the guest asked for:
source=cap-rate-vsync)source=cap-rate)source=guest) / mailboxA self-paced guest (VSync off) stays on FIFO because, declared mailbox at this cap, it had about 1 frame a second replaced in gamescope's mailbox (B4-B6).
The census's
slot_waitsandcommit_lat_usnow count whether or not the gate runs, so aGNDRM_LOG_VKSHIM=1capture of a FIFO cell shows its image waits and its acquire-to-commit time.Present wait answered at queue in the cap-rate-vsync cell (8a5b695, narrowed in 762e559)
What was measured. On the RP6 with this PR's first shim (d61b614) the new cell ran at 42 fps. Setup: DTD at 1080p, cap 60, in-game VSync on, 60 Hz.
Why. DXVK with VSync on calls
vkWaitForPresentKHRbecause its own swapchain is FIFO; it cannot see that the shim declared mailbox. It waits for the present of frame N − L before it starts frame N. The shim answered that wait incommit_real, after the worker had waited for the frame's render fence. With L = 1, the CPU of frame N+1 therefore waited for the GPU to finish frame N.Why X11 holds 60. On X11 the same game overlaps CPU and GPU. The app's X server sends PresentCompleteNotify while handling the PresentPixmap request, before the GPU is done.
Change. In the cap-rate-vsync cell only (gamescope's route, cap at the panel's rate, non-fifo container mode, guest asked for FIFO or FIFO-relaxed), the present wait is answered in
present_dispatch, when the guest queues the present. The acquire gate still holds the rate.at_queue=.Tests
cargo test -p adrenotools_gamescope --lib(Linux aarch64 container): 89 passed at 762e559. The answer-point test checks that only cap-rate-vsync is answered at queue: the container's mode, frame generation and a self-paced guest under cap-rate or cap-compat are answered at the commit, FIFO by the compositor, and off the route as on master. New and updated cases:cargo clippy --target aarch64-linux-android -p adrenotools_gamescope -p gndrm-android -p gndrm-daemon --all-targets -- -D warnings: clean.Shim only; the daemon is unchanged.
Artefacts
d04525f26ad2693715b0f8e281d87be4be779631cb6232f664b493b03d433163, build string202611031208762e559. In payload 20261103.6.a0bc3ab2ed1135885a8dff94d691fa389f63286bc54ec44befea970e2fbbd26f, build string2026110310298a5b695.Shim only; the daemon is unchanged.
Verification
Measured on the RP6: Dave the Diver at 1920x1080, 60 Hz, cap 60, in-game VSync on, same spot. Census
declared=mailbox requested=fifo source=cap-rate-vsync cap=60 gate=vsync.commit_latat_queueStill pending: JT's device.
Delivery
gamescope-gamenative-20261103.5.tzst, sha256aa4425180fd893be843811506c4d46d5facd566ceed6bbc5ebf5569297a156cc. It is 20261102.4 with only the shim replaced by the 8a5b695 build above (a0bc3ab2…bd26f).gamescope-payload-20261103-5, commit 96d03dfc7). It depends on this PR and must not merge before it.gamescope-gamenative-20261103.6.tzst, sha256d8d40c58db928e25f59371eb3fe337638d140f13febef1d3facd4747c387c183) carries 762e559. gamescope: payload 20261103.6, a VSync-on game at the panel's rate on the vsync grid utkarshdalal/GameNative#2332 now ships it (commit 9a7991215). Payload 20261103.5 (8a5b695, the wider answer-at-queue) stays on the CDN.