Skip to content

gamescope: the present mode follows the container, the cap on the display's vsync grid (payload 20261103.1) - #2327

Merged
utkarshdalal merged 3 commits into
masterfrom
gamescope-present-mode-from-container
Nov 3, 2026
Merged

utkarshdalal merged 3 commits into
masterfrom
gamescope-present-mode-from-container

Conversation

@utkarshdalal

@utkarshdalal utkarshdalal commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Dependency update: GameNative/gamenative-drm#46 merged at c9b7c31 before the cap-rate regression fix reached its branch; that fix is GameNative/gamenative-drm#47 (commit 1cc98e1, the same change as 2567925). Payload 20261103.4 already carries it. GameNative/gamenative-drm#47 is merged (master bf2757b), so this PR is ready: payload 20261103.4 is the master shim, the daemon in jniLibs is the master daemon.

Description

Depends on GameNative/gamenative-drm#46 (under review by @joshuatam); do not merge before it.

This ships GameNative/gamenative-drm#46 (branch shim-present-mode-from-container, head 2567925). The daemon from that branch goes in jniLibs, and the shim from the same head ships as gamescope payload 20261103.4 (app commit 40dd841). App master runs payload 20261102.4.

What changes in this PR

Nothing else in the app changes.

What changes for users

Today every default container (cap 60, MESA_VK_WSI_PRESENT_MODE=mailbox) runs FIFO under gamescope's divisor limiter. A game below the cap therefore drops onto divisors of the refresh rate, which is worst on 90/120 Hz panels, and the container's present mode is ignored on this route. With this PR:

container present mode FPS cap what happens
mailbox (default) none, game's VSync on (FIFO request) FIFO, the game's own mode (source=guest): vsynced at the panel rate, as on master
mailbox (default) none, game's VSync off (mailbox/immediate) mailbox, nothing limits the rate (source=env)
mailbox at the panel's rate (60 on a 59.97 Hz panel) FIFO is declared, paced by gamescope's limiter with the shim's gate off (source=cap-rate gate=off). Same as today.
mailbox below the panel's rate (60 on 120 Hz, 30 on 60 Hz) mailbox; the shim's gate enforces the cap on the display's real vblank grid. A game below the cap keeps its own rate instead of quantizing to refresh/2.
fifo (env) any unchanged: FIFO, gamescope's limiter is the cap
any, frame generation on any unchanged: mailbox with the source-rate gate
  • The container's present mode is honoured again on the gamescope route, and an in-game VSync request still wins when no cap is set.
  • A shim-only payload (old daemon) behaves exactly like master: until the stamp lead arrives, a capped non-FIFO swapchain is declared FIFO (source=cap-compat). That is why the daemon ships in this PR.

Build (NDK 27.3, rustc 1.98.1 via rustup, SOURCE_DATE_EPOCH = commit time, rustflags from tools/build-android.sh)

  • Shim at 2567925: cargo test -p adrenotools_gamescope --lib in rust:1-bookworm (aarch64): 88 passed.
  • cargo clippy --target aarch64-linux-android -p adrenotools_gamescope -p gndrm-android -p gndrm-daemon --all-targets -- -D warnings is clean.
  • The shim exports only the three vk_icd* entry points.
  • The shim's build string is 2026110307122567925 (sha256 6355ee9e…9035).
  • It fixes the regression JT's capture found on 20261103.3: at a cap equal to the panel's rate, after a live 120 to 60 Hz switch, FIFO plus the shim's gate settled DTD at 40 fps. The cap-rate cell now runs with the gate off, which is master's behaviour; see shim: the present mode follows the container, the cap runs on the vsync grid under mailbox GameNative/gamenative-drm#46.
  • Logging is as in c9b7c31: the census prints at start and on changes, and GNDRM_LOG_VKSHIM=1 enables the periodic line.

Payload 20261103.4

gamescope-gamenative-20261103.4.tzst, 14307540 B, sha256 5126c4ee8f042a69e287a3bde16e3be5a79de0cc052aaeadff5554b6a09fe7b9.

  • It is uploaded and verified on both CDN hosts, including the members of the served copy.
  • It is 20261102.4 with only the shim replaced: 1014 members, the same order and modes, and every other member byte-identical.
  • 20261102.4, 20261103.1 (a4a7bb0 shim), 20261103.2 (1b708d5 shim, used for N4) and 20261103.3 (c9b7c31 shim) all stay on the CDN.
member sha256 from
lib/libadrenotools_gamescope.so 6355ee9ecdd1d8d8d6e6128d5e06c68536c085bebeaaaa28e70af517b2af9035 GameNative/gamenative-drm#46 at 2567925, build string 2026110307122567925
bin/gamescope (unchanged) 65d024fcee6af1b1f81b2281cc5223ee07615daa05aa8d9ecebe865526f8df69 as in 20261102.4
bin/Xwayland (unchanged) 4dded8566661423ee3c91490523d96240c85906bd00e0774adc912df9dd38698 as in 20261102.4

Measured (Retroid Pocket 6, cap 60, from GameNative/gamenative-drm#46's verification)

run stack result
B1 Dave the Diver, VSync on, new shim + new daemon, 59.983 Hz 59.983 fps, 0 doubles, 3600/3600 acquires/commits, daemon late=0. Equal to master's FIFO.
B7 Dave the Diver, VSync off (requested=immediate), ce1f919 (FIFO at the panel's rate + gate), 59.97 Hz Census declared=fifo source=cap-rate gate=vsync. SurfaceFlinger 60 s: 59.199 fps, 46 doubles (1.3 %). Master's FIFO: 59.22 fps, 46 doubles. Shim commits = daemon publishes, late=0.
S1 Dave the Diver, VSync on, a4a7bb0, shipped gamescope, 59.98 Hz Census declared=fifo requested=fifo source=cap-rate cap=60 gate=vsync. SurfaceFlinger 60 s: 59.978 fps, 3598 single intervals, 0 doubles; slot_waits=0.
C1 Witcher 3, GPU-bound, 120 Hz, cap 60, the below-rate mailbox + gate path 31.85 fps; 3-vsync intervals 25.1 %, 4-vsync 70.6 %. Not quantized.
C2 same, master's FIFO 31.35 fps; 4-vsync 75.3 %, 2-vsync 8.6 %. Quantized toward 30.
N1 DTD, limiter off (no cap), VSync on (requested=fifo), 1b708d5 + 46ccc70 daemon, shipped gamescope, 60 Hz Pass (the no-cap rule, 1b708d5). Census declared=fifo requested=fifo source=guest route=gamescope cap=0 gate=off. SurfaceFlinger 60 s: 59.973 fps, 3598 single intervals, 0 doubles. Commits +600 per 10 s, equal to the daemon's 600/600 publishes, late=0. presented tracks commits (9942/9943); slot_waits=0.
N2 DTD, env MESA_VK_WSI_PRESENT_MODE=fifo, cap 60, VSync on, 1b708d5, 60 Hz Pass. Census declared=fifo requested=fifo source=env cap=60 gate=off. SurfaceFlinger 59.980 fps, 3598 single intervals, 0 doubles. Commits equal publishes, 600/600.
N3 Witcher 3, hillside scene, 120 Hz panel (120.127 Hz measured), cap 60, 1b708d5 Pass on fps and histogram. Census declared=mailbox requested=fifo source=env cap=60 gate=vsync. SurfaceFlinger 32.624 fps (per-second median 33, p5 32). Intervals in 120 Hz periods: 2 = 1.1 %, 3 = 33.5 %, 4 = 61.7 %, 5 = 3.5 %, 6 = 0.2 % (C1: 3 = 25.1 %, 4 = 70.6 %; C2 FIFO: 2 = 8.6 %, 3 = 7.4 %, 4 = 75.3 %, 5 = 7.3 %). Shim commits 319-331 per 10 s, equal to the daemon's 319-332 publishes per ~1200 vsyncs. commit_lat_us ~30-31.5 ms; slot_waits=0. See the N3 observation below.
N4 Delivery smoke: #2327 APK (b709403) + payload 20261103.2 from the CDN Pass. Payload downloaded in ~3.0 s (Gamescope payload 20261103.2 ready), with the stamp and shim sha (a77a7b77…) as expected; ICD loaded [shim 202611030337 1b708d5]. The file tree before and after differed only in the stamp and the shim. Census declared=fifo requested=fifo source=cap-rate cap=60 gate=vsync. SurfaceFlinger 59.966 fps, 3596 single intervals, 1 double. Commits equal publishes.

B1-C2 were capped and ran on earlier heads. N1-N4 ran on 1b708d5, the shim in payload 20261103.2 (N4 from the payload itself), with the 46ccc70 daemon. The 20261103.4 shim (2567925) drops the gate from the cap-rate cell; the B7, S1 and N4 rows above were measured with it on.

Observation for review (N3). On the below-rate mailbox path at 120 Hz, the daemon logged late=77-175 per ~1200 vsyncs:

  • daemon margin p10 was about −1.5 to −1.8 ms, with the lead at about +2.7 ms;
  • C1, on the earlier gate, showed the same (67-158);
  • master's FIFO shim had 0-24;
  • it did not show in SurfaceFlinger's interval histogram.

Open question for @joshuatam in GameNative/gamenative-drm#46: should the gate's release point on a 120 Hz grid carry a larger lead?

Not verified on the final build

  • The no-cap row for a game that asks for mailbox or immediate (in-game VSync off with the limiter off; expected declared=mailbox source=env gate=off). N1 covers the no-cap row for a game asking for FIFO.

How JT can test

  1. Install the APK from this branch: the CI artifact, or a local ./gradlew :app:assembleModernDebug (app/build/outputs/apk/modern/debug/).
  2. Open a game in a container with the display server set to gamescope. On first launch payload 20261103.4 downloads. In logcat:
    • expect Gamescope payload 20261103.4 ready;
    • in the game process, expect gn-vkshim: ICD loaded [shim 202611030712 2567925];
    • gn-vkshim: present mode: declared=… requested=… source=… route=gamescope cap=… gate=… lsfg=… prints once when frames start, and again only when the declared mode, source, cap or gate changes (for example, a limiter change in the QuickMenu);
    • for the periodic line with the counters (acquires= commits= … gate_holds= gate_late= skipped= slot_waits= commit_lat_us= spacing=[…], every 10 s), set GNDRM_LOG_VKSHIM=1 in the container's environment;
    • a misspelt MESA_VK_WSI_PRESENT_MODE logs Invalid MESA_VK_WSI_PRESENT_MODE value! once.
  3. Check each case against the census line:
setup expect
limiter 60, 60 Hz panel declared=fifo … source=cap-rate … gate=off, about 60 fps, gndrm-daemon: vsync: check … late=0; still about 60 after a live 120 to 60 Hz switch
limiter off, in-game VSync on declared=fifo … source=guest … gate=off, a vsynced 60
120 Hz panel, limiter 60 declared=mailbox … source=env … gate=vsync; a game running below 60 is not quantized to 30 (frame intervals mix 2, 3 and 4 vsyncs instead of locking to 4)
env MESA_VK_WSI_PRESENT_MODE=fifo with a cap declared=fifo … source=env … gate=off, the same as master

A source=cap-compat line means the old daemon is loaded, i.e. the APK is not from this branch.

  1. Regression check: Dave the Diver with in-game VSync on still holds about 60 at cap 60.

Recording

Type of Change

  • Bug fix
  • Performance / stability improvement
  • Compatibility improvements
  • Other (requires prior approval)

Checklist

  • If I have access to #code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.
  • This change aligns with the current project scope (core functionality, stability, or performance). If not, it has been explicitly approved beforehand.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by CodeRabbit

  • Updates
    • Updated the Gamescope payload version used by the app. Installed payloads are now checked against the updated version, so the app’s readiness status reflects whether the current Gamescope payload is installed. This keeps payload detection aligned with the version included in this update. The change applies to Gamescope payload recognition and does not alter other app features.

…play's vsync grid, payload 20261103.1

libgamenative_drm.so is rebuilt (08d6276c) from gamenative-drm
shim-present-mode-from-container (#46) at a4a7bb0: the daemon's vsync note
carries the flip events' stamp lead, which the shim needs before it gates a
cap on the display's real vblank grid. Daemon sources are unchanged since
46ccc70.

Payload 20261103.1 is 20261102.4 with only lib/libadrenotools_gamescope.so
replaced (fa50add9), the shim from the same head: on the gamescope route it
declares the container's present mode; a non-FIFO mode under a cap at the
display's rate is declared FIFO with the vblank-grid gate kept, and a cap
below the display's rate stays mailbox, enforced by the gate on the real
vblank grid instead of gamescope's divisor limiter. gamescope and Xwayland
are unchanged.
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

The Gamescope payload version changes from 20261102.4 to 20261103.3. The archive filename derives from this version.

Changes: Gamescope payload

Layer / File(s) Summary
Update payload version
app/src/main/java/app/gamenative/display/GamescopePayload.kt
GamescopePayload.VERSION changes to 20261103.3.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Change: Bug fix

Suggested reviewers: joshuatam

Merge Risk: Minimal · up to c262f

The selected payload is published on both download hosts, and the APK also updates its bundled daemon. No concrete merge-blocking issue remains established; normal build and presentation checks still apply.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly describes the main present-mode and display-cap behavior. It references payload 20261103.1, while the pull request updates the payload to 20261103.3, so the payload version is stale …
Description check Passed The description is detailed and covers the change, rationale, testing, user impact, build information, and checklist. The Recording section is not completed, and the required code-change discussion an…
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title clearly describes the main present-mode and display-cap behavior. It references payload 20261103.1, while the pull request updates the payload to 20261103.3, so the payload version is stale but the title remains related to the changes.

Full details: Description check

Explanation

The description is detailed and covers the change, rationale, testing, user impact, build information, and checklist. The Recording section is not completed, and the required code-change discussion and recording checklist items remain unchecked, but the description is mostly complete.

Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 1 files.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

ast-grep (0.51.1)

ast-grep could not parse rule config: /ast-grep-rules/ast-grep-essentials/rules/c/security/weak-cipher-openssl-c.yml

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4307952f-830f-48f6-bd03-84b4a6d25697
Commits

Reviewing files that changed from the base of the PR and between b709403 and c262f01.

Files selected for processing: 1
  • app/src/main/java/app/gamenative/display/GamescopePayload.kt

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

View guided diff | Turn on auto-fix | Re-trigger cubic

Payload 20261103.2 is 20261102.4 with only lib/libadrenotools_gamescope.so
replaced (a77a7b77), the shim from GameNative/gamenative-drm#46 at 1b708d5:
with no cap, a guest's FIFO request now wins over the container's default
mode. gamescope and Xwayland are unchanged; libgamenative_drm.so is
unchanged (the daemon sources have not changed since 46ccc70).
Payload 20261103.3 is 20261102.4 with only lib/libadrenotools_gamescope.so
replaced (cfe96ac2, build string 202611030510c9b7c31), the shim from
GameNative/gamenative-drm#46 at c9b7c31: the same present-mode rule as
1b708d5, with the shim's logging gated for shipping. The present-mode line
prints once when frames start and again when the declared mode, source, cap
or gate changes; its 10 s repeat with the counters needs GNDRM_LOG_VKSHIM=1.
The mailbox-at-display-rate test switch is gone. gamescope, Xwayland and
libgamenative_drm.so are unchanged.
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