shim: the present mode follows the container, the cap runs on the vsync grid under mailbox
A demonstration for discussion. On gamescope's override route the shim now declares the container's present mode, or the guest's mode when there is no env. There are two exceptions:
- Non-FIFO mode under a cap at the display's rate: declared FIFO with no gate (
source=cap-rate gate=off), which is master's behaviour in that cell. gamescope latches one frame per vblank and its limiter (divisor 1) paces the guest. - No cap, and the guest asked for FIFO: the guest's FIFO wins over the container's stamped mailbox default.
Below the display's rate a non-FIFO mode stays as it is and the shim's gate enforces the cap on the display's real vblank grid; an env of fifo is master's path unchanged.
The daemon change (stamp lead in the VsyncNote) is required for the gate. A shim-only payload behaves exactly like master: until the lead arrives, a capped non-FIFO swapchain is declared FIFO under gamescope's limiter (source=cap-compat).
Problem
7bdcbae made the shim declare FIFO to gamescope whenever a cap is set, and it stopped reading MESA_VK_WSI_PRESENT_MODE on the override route. The idea was to let gamescope's divisor limiter enforce the cap. The cost:
- Default containers carry a 60 cap and
MESA_VK_WSI_PRESENT_MODE=mailbox. So every default session runs FIFO under gamescope's limiter. - Under FIFO, each frame is held to whole limited vblanks. A game below the cap therefore drops onto divisors of the refresh rate: it judders between refresh and refresh/2, and a game that waits on every present stays at refresh/2. This is worst on 90 and 120 Hz panels.
- The user's present-mode choice in the container is ignored on this route.
Before 7bdcbae, mailbox was declared and the shim's acquire gate enforced the cap. That gate ran on its own 1e9/N clock, which drifted against a 59.97 Hz panel and gave 57-59 fps on SurfaceFlinger at cap 60. That drift is why 7bdcbae moved to FIFO.
What changes
The declared mode now follows the container, with one exception: a cap at the display's rate. When the container's mode is not FIFO, the shim enforces the cap with a gate that releases on the display's real vblank grid.
| container mode | cap | declared | what limits the rate |
|---|---|---|---|
| mailbox (default) | none, guest asked for FIFO (in-game VSync on) | fifo (source=guest) | the display |
| mailbox (default) | none, guest asked for mailbox or immediate | mailbox | nobody |
| mailbox | at the display's rate (e.g. 60 on 59.97 Hz) | fifo (source=cap-rate) | gamescope's limiter (divisor 1), one FIFO commit per vblank; no gate (gate=off), as on master |
| mailbox | below the display's rate (e.g. 60 on 120 Hz, 30 on 60) | mailbox | the shim's acquire gate, on the display's vblank grid |
| fifo | none | fifo | the display |
| fifo | set | fifo | gamescope's divisor limiter (as today) |
| any, no stamp lead yet | set | fifo (source=cap-compat) | gamescope's limiter (as today) |
| any, frame generation on | any | mailbox | source-rate gate (unchanged) |
Why the guest's FIFO wins with no cap. Every container carries MESA_VK_WSI_PRESENT_MODE=mailbox as a stamped default that users did not choose. With no cap, honouring that default turned a game's own VSync (a FIFO request) into an uncapped mailbox session, which is neither master's behaviour nor what the user asked for.
- A FIFO request (FIFO-relaxed too) is a specific wish, so it is declared as asked.
- A mailbox or immediate request means "do not make me wait". The container's mailbox is the substitute for it, as on the X11 route; an uncapped immediate guest gets mailbox, not tearing.
- An env of
fifois explicit and wins in every case.
Why the display-rate cap declares FIFO instead of mailbox. At that cap the panel shows one frame per vblank under either mode. Under mailbox, though, the shim has to keep two commits out of one gamescope composite, and it cannot see when gamescope latches (see "Finding" below). On the RP6 (DTD, cap 60, VSync off, a guest pacing itself at ~59.3 fps on a 59.98 Hz panel):
- mailbox with the gate gave 58.3 fps on SurfaceFlinger, with ~1 commit/s replaced in gamescope's mailbox (B4);
- booking late frames by their predicted composite gave 58.3 (B5);
- a commit-window queue at a guessed boundary gave 59.0, with ~0.8 commits/s still replaced (B6);
- master's FIFO gives 59.22.
Under FIFO, gamescope latches one commit per vblank at its own wakeup, and its limiter's divisor at this cap is 1. No gate runs on top of it (2567925): FIFO's back-pressure already holds the guest, and the two stacked produced the regression below. Below the display's rate, mailbox with the gate stays, because the releases are at least two vblanks apart; Witcher 3 was not quantized there, whereas FIFO with gamescope's limiter quantized it to 30 at 60 on 120 Hz.
- Without the env, the guest's own mode is used.
fifo_relaxedandimmediateare gated like mailbox, because gamescope's limiter holds only FIFO declarations (wlserver_surface_is_fifo). - The gate's clock (
icd_shim/src/vsync_gate.rs): releases land on the daemon'sVsyncNotelatch grid (measured period and phase), moved earlier by the lead the daemon stamps flip events with. That is the grid gamescope composites from.- cap at the display's rate: one release per vblank, under the FIFO declaration. 60 on a 59.97 Hz panel gives 59.97.
- cap that divides the rate: every refresh/cap-th vblank (30 on 60, 60 on 120).
- any other cap below the rate: the vblank nearest each point of an ideal 1e9/cap timeline. 45 on 60 releases on 3 of every 4 vblanks.
- cap above the rate: the cap's own clock.
- A late frame starts at once and the schedule carries on, so a game below the cap keeps its own rate.
- The gate needs the daemon's stamp lead. Until a note with a non-zero
stamp_lead_nshas arrived (no note yet, or an older daemon), a capped swapchain whose mode is not FIFO is declared FIFO, and gamescope's limiter is the cap, exactly as on master. The census showssource=cap-compat. So a payload-only rollout keeps today's pacing until the app ships the new daemon.
- Daemon: the
VsyncNote's reserved word now carriesstamp_lead_ns, the flip-event lead. The wire size is unchanged. The daemon sends at least 1 ns while it stamps on a grid, so 0 always means "unknown". An older daemon sends 0, and the shim then falls back to FIFO under a cap (see above). An older shim ignores the field. - Slot count: on this route, the container's mode decides the extra mailbox slot only when the stamp lead is already known at swapchain creation. Otherwise the guest's own request decides it, as on master.
- Census:
present mode: declared= requested= source=env|guest|cap-rate|cap-compat route= cap= gate=vsync|clock|off lsfg=, whererequested=is the guest's own request.- It prints ungated once when frames first flow, and again whenever the declared mode, source, cap or gate changes.
- With
GNDRM_LOG_VKSHIM=1it also repeats every 10 s with the counters:acquires= commits= declared_fifo= declared_other= releases= feedback= presented= discarded= fallback= gate_holds= gate_late= skipped= slot_waits= commit_lat_us= spacing=[…]. - "Invalid MESA_VK_WSI_PRESENT_MODE value!" prints ungated, once.
What is unchanged
- The FIFO path when the user sets fifo: FIFO is declared, gamescope's limiter is the cap, and present wait is answered from presentation feedback.
- Frame generation: mailbox with the source-rate gate.
- The opt-in X11 Present route.
- The present-wait rules. A wait is answered at commit for mailbox and immediate, and on feedback, release or grace for FIFO.
- The daemon's flip-event timing and the cursor plane.
Commits
daemon: the vsync note carries the flip events' stamp leadshim: an acquire gate on the display's vblank grid, for caps gamescope's limiter does not hold. Nothing takes this path until commit 3.shim: the present mode follows the container on the gamescope route; a cap no longer forces FIFOdocs: ...(docs/10, docs/15, README)shim: no vblank-grid gate without the daemon's stamp lead; declare FIFO under a cap until it arrives. This follows from the RP6 run below.shim: book a late frame to the composite it will make, …(9fb3cda), later backed out byshim: back out the latency booking of late frames; keep its counters(93ab33c).shim: one commit per gamescope composite under the vblank-grid gate(8cf5834), later backed out byshim: back out the commit window; keep the commit-spacing histogram(476aa3e).shim: declare FIFO under a cap at the display's rate, with the vblank-grid gate kept(ce1f919).shim: test switch to keep mailbox at a cap at the display's rate (debug.gndrm.mailbox_at_rate)(b084ead). This is a test flag only; the default rule is unchanged. With the Android propertydebug.gndrm.mailbox_at_rate=1, or the envGNDRM_MAILBOX_AT_RATE=1when the property is unset, a cap at the display's rate declares the swapchain's mode with the vblank-grid gate kept. The census then readssource=env-at-rate, and the switch's value is logged once after the ICD loaded line. Artefact:libadrenotools_gamescope.sosha25633904656a669efc0ae66ddba4c487c03907eefdc649b4392711eed11bdce5f1d, build string202611030213b084ead. 87 shim unit tests pass and clippy is clean.shim: a clock-booked frame leaves the grid schedule an ideal instant it can use(47c11aa). This fixes the review blocker: after a booking on the clock, a cap below the display's rate stopped binding. A test now covers both clock-to-grid transitions.shim, docs: gate_late in the census, the present-mode env as the user's control, docs/15 and docs/17 as the code is(a4a7bb0).shim: with no cap, a guest's FIFO request wins over the container's default mode(1b708d5).shim: the present-mode line ungated at start and on a change, its counters behind GNDRM_LOG_VKSHIM; the mailbox-at-rate test switch removed(c9b7c31). This removes b084ead's switch; the measurement it served (H1, H2) is done.shim: no vblank-grid gate under the cap-rate FIFO declaration; a late start is released at the next grid instant(2567925). This fixes the regression in JT's capture (below).
Tests
cargo test -p adrenotools_gamescope --lib(Linux aarch64 container): 88 passed, 0 failed, at 1b708d5. The rule-table test now has the guest's request as a column.cargo test --workspace --no-fail-fast(same container, lld): 305 passed. The one failure wasshim_integrationa_native_driver_never_drives_a_container_icd, the pre-existing flake; that suite passed 3/3 on rerun.- Coverage of the shim tests:
- the rule table end to end:
- 60 on 59.97 Hz gives FIFO,
cap-rate, gate off; - 60 on 120 Hz and 30 on 60 give mailbox with the gate every second vblank;
- env fifo with a cap gives FIFO with no gate;
- no cap gives the env's mode with no gate;
- no lead gives
cap-compat; - frame generation gives mailbox on the source-rate clock;
- 60 on 59.97 Hz gives FIFO,
choose_live_modeand the gate per declaration;- the gate's schedule for cap == refresh (59.97 Hz), 30 on 60, 60 on 120 and 45 on 60;
- a late frame, a game below the cap, and a phase push;
- no note, a note with lead 0 and a note with a lead;
- caps above the rate;
- the clock-to-grid transition: a 30 cap on 60 Hz stays two vblanks apart after frames booked on frame generation's source-rate clock, and after a cap moves from above the panel's rate to below it.
- the rule table end to end:
cargo test -p gndrm-uapi -p gndrm-android -p gndrm-daemon: 182 passed. The note round-trip, an old note decoding as 0, and the stamp-lead push rule are new. The twoabi_ground_truthC-header probes did not run because the test container has nodrm/drm.h; this PR does not touch them.cargo clippy --target aarch64-linux-android -p adrenotools_gamescope -p gndrm-android -p gndrm-daemon --all-targets -- -D warnings: clean.
Artefacts
Release aarch64 builds, NDK 27.3:
libadrenotools_gamescope.sofrom 2567925: sha2566355ee9ecdd1d8d8d6e6128d5e06c68536c085bebeaaaa28e70af517b2af9035, build string2026110307122567925. It is the cap-rate regression fix, with the logging as in c9b7c31. The device results above were measured on ce1f919 (sha256b4bff42a…297c) and, for S1, on a4a7bb0. The N1-N4 results were measured on 1b708d5: Witcher 3 at 120 Hz (N3), env fifo (N2) and the no-cap rule (N1) are verified on it.libgamenative_drm.sofrom 46ccc70 (unchanged since): sha25608d6276c268ed9ff169a0cc76f059bf7e30dec2288f13617bfa64c6f4ff407f6
App delivery for testing: utkarshdalal/GameNative#2327 (branch gamescope-present-mode-from-container).
- Payload
gamescope-gamenative-20261103.4.tzst, sha2565126c4ee8f042a69e287a3bde16e3be5a79de0cc052aaeadff5554b6a09fe7b9. It is 20261102.4 with onlylib/libadrenotools_gamescope.soreplaced (the 2567925 build above); gamescope and Xwayland are unchanged. App commit 40dd84163. - Payload 20261103.3 (c9b7c31 shim,
cfe96ac2…213e), the one in JT's capture, stays on the CDN. - Payload 20261103.2 (1b708d5 shim,
a77a7b77…) was used for N4 and stays on the CDN. - The app carries the daemon
08d6276c…07f6above andGamescopePayload.VERSION=20261103.2. - In logcat, expect
Gamescope payload 20261103.2 readyandgn-vkshim: ICD loaded [shim 202611030337 1b708d5].
The daemon changed, so the vblank-grid gate needs an app build. With only the new shim in a payload, a capped mailbox container is declared FIFO, the same as master.
Regression found and fixed (2567925)
@joshuatam's capture found it: payload 20261103.3, shim c9b7c31, a 1280x960 device, DTD in game, cap 60, env mailbox, in-game VSync on.
- At 120 Hz:
declared=mailbox source=env gate=vsyncheld 60 fps, with acquire-to-commit around 7-16 ms. - After a live switch to 60 Hz: the census became
declared=fifo requested=fifo source=cap-rate cap=60 gate=vsync, and within ~10 s the game settled at 40 fps:- shim commits 400 per 10 s, equal to the daemon's 400/600 publishes, with
late=0; gate_holds0,gate_late400/400,skipped+200 per 10 s;- acquire-to-commit 24 ms.
- shim commits 400 per 10 s, equal to the daemon's 400/600 publishes, with
- Back at 120 Hz it recovered within a second.
Cause. FIFO's back-pressure and the vblank-grid gate stacked in the cap-rate cell. Once a frame took longer than a period from acquire to commit, every start was late against a grid the guest could no longer meet. The RP6 runs of this cell (B7, S1, N4) did not show it because their scene stayed near 12 ms.
Fix (2567925).
- The cap-rate cell keeps the FIFO declaration with the gate off (
source=cap-rate gate=off), which is master's behaviour in that cell. - Under a cap below the display's rate, the gate never books the release after a late start more than one grid instant after it.
Payload 20261103.4 carries it (sha256 5126c4ee…e7b9, utkarshdalal/GameNative#2327 at 40dd84163).
Device verification (Retroid Pocket 6, cap 60)
All runs are on the DTD title scene unless noted. The FIFO reference is master's shipped shim on the same scene.
| run | stack | result |
|---|---|---|
| B1 | DTD, VSync on (requested=fifo), new shim + new daemon, 59.983 Hz | Pass. 59.983 fps, 0 doubles, 3600/3600 acquires/commits, daemon late=0. Equal to the FIFO reference. |
| A4 | DTD, VSync off, new shim + old daemon (stamp_lead_ns = 0) | Fail, fixed by 46ccc70. 58.28 fps, 102 doubles. Releasing on the latch instant itself straddled gamescope's composite; at 120 Hz the daemon had late=818 (13 %). Without the lead the shim now uses master's FIFO path (cap-compat). |
| B4 | DTD, VSync off (requested=immediate), 1ffd476 (mailbox + gate) | Fail. 58.33 fps, 2.8 % doubles. The shim committed 59.3/s and the daemon published 58.3/s with late=0, so ~1 commit/s was merged in gamescope's mailbox. |
| B5 | same, 9fb3cda (late-frame booking) | Fail. 58.26 fps, 2.9 % doubles. ~75 % of acquires were late; the guest paces itself. |
| B6 | same, 8cf5834 (commit-window queue) | Fail. 59.0 fps median, 2.7 % doubles. ~0.8 commits/s still merged: the shim cannot see gamescope's latch point (Finding, below). |
| B7 | same, ce1f919 (FIFO at the display's rate + gate; the gate was dropped from this cell in 2567925), 59.97 Hz | Pass. Census declared=fifo requested=immediate source=cap-rate route=gamescope cap=60 gate=vsync, declared_other=0. Shim commits 592-594 per 10 s against daemon publishes 591-594 per 600 vsyncs, so the B4-B6 gap is gone. SurfaceFlinger over 60 s: 59.199 fps, 46 doubles (1.3 %), none short. FIFO reference: 59.22 fps, 46 doubles/min. slot_waits=0, daemon late=0, margin p50 +3.4 ms, lead 1.6-2.2 ms. The remaining shortfall is the game's own ~59.3 fps self-pacing. |
| C1 | Witcher 3, GPU-bound, 120 Hz, cap 60, 1ffd476 gate (the same below-rate mailbox + gate path as ce1f919) | 31.85 fps; intervals 3-vsync 25.1 %, 4-vsync 70.6 %. Not quantized. |
| C2 | same, shipped FIFO | 31.35 fps; 4-vsync 75.3 %, 2-vsync 8.6 %. Quantized toward 30. |
| SWD2 | GL title | Not on this path. |
| B8 | Witcher 3, 120 Hz | Not run on ce1f919; now verified on 1b708d5 as N3. |
| B9 | env MESA_VK_WSI_PRESENT_MODE=fifo | Not run on ce1f919; now verified on 1b708d5 as N2. |
| S1 | DTD, VSync on (requested=fifo), a4a7bb0 with the default rule, shipped gamescope, cap 60, 59.98 Hz | Pass. Census declared=fifo requested=fifo source=cap-rate cap=60 gate=vsync. SurfaceFlinger over 60 s: 59.978 fps, 3598 single intervals, 0 doubles. Commits 599-600 per 10 s, equal to the daemon's publishes; slot_waits=0. B1 reference: 59.983 fps, 0 doubles. |
| 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: utkarshdalal/GameNative#2327 APK (b70940340) + 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. |
Observation for review (S1). During S1 the shim alternated between two steady states, and SurfaceFlinger's output was identical in both:
| state | per 10 s | commit_lat_us |
|---|---|---|
| (a) | gate_holds +600, gate_late +11-16 | about 8.3-9.3 ms |
| (b) | gate_holds 0, gate_late +600 | about 16.6 ms |
It switched twice over 3 minutes; the census lines at 08:23:37 and 08:25:07 are the transitions. The guest's frame lands either just before its release point or just after it:
- in (a) the gate holds the frame for a moment;
- in (b) the frame is a hair late, so it starts at once and is booked to the point it just missed.
Under FIFO with the gate, the panel output does not change. The two states could still differ in input-to-display latency by up to a frame.
@joshuatam, an open question rather than a proposed change: should the gate treat a frame that arrives within a small window after its release point as on time, as the earlier ON_TIME_NS tolerance did for counting? Or is this alternation harmless?
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.
@joshuatam, another open question: should the gate's release point on a 120 Hz grid carry a larger lead?
| H1 | DTD, VSync off (requested=immediate), b084ead with debug.gndrm.mailbox_at_rate=1 (mailbox + gate at the display's rate) and gamescope A58 (GameNative/gamescope#31, debug.gamescope.gn_mailbox_hold=1), 59.95 Hz | Pass. Census declared=mailbox requested=immediate source=env-at-rate cap=60 gate=vsync, declared_fifo=0. Shim commits 592-594 per 10 s against daemon publishes 592-594, so nothing is merged. SurfaceFlinger over 60 s: 59.269 fps, 41/3556 doubles (1.2 %), against the FIFO reference's 59.22 / 1.3 % and B4-B6's 58.3-59.0 / 2.7-2.9 %. slot_waits=0. gamescope gn_mailbox_hold=1 source=prop, steady state held=38-164 replaced=0 per 600 vblanks (5 replaced in the whole run, all during loading). |
| H2 | DTD, in-game VSync on (requested=fifo), b084ead + debug.gndrm.mailbox_at_rate=1 + gamescope A58 52e1b1f, 59.98 Hz | Pass on rate, with one caveat. Census declared=mailbox requested=fifo source=env-at-rate cap=60 gate=vsync. SurfaceFlinger over 60 s: 59.984 fps, 3599 singles, 0 doubles. Shim +600 acquires/commits/releases per 10 s, gate_holds +600, late 0-4, skipped 0, slot_waits 0. Daemon 600/600, late=0, margin p50 +3.45 ms, lead ~2.2 ms. Caveat: gamescope's held=1200 per 600 vblanks means every commit was shown one paint late, a standing carry. GameNative/gamescope#31 now bounds it (9fad070, gn_mailbox_hold_max_carry, default 8); a re-run is pending. |
Finding: the shim cannot see gamescope's composite point under mailbox (B5, B6)
Run. DTD, cap 60, in-game VSync off (requested=immediate), 59.98 Hz panel. Results:
- The guest paces itself at about 59.3 fps (Unity frame-rate target).
- With 8cf5834 the shim commits 592-594 frames per 10 s.
- The daemon publishes 583-590 per 600 vsyncs, with
late=0. So about 0.8 commits per second are merged inside gamescope's mailbox: two commits land between two gamescope wakeups. - SurfaceFlinger shows 59.0 fps with 2.7 % doubles.
- The shim's one-commit-per-window queue (
win_holds18-48 per 10 s,win_replaced=0) removed only a few of those merges. - The growth in
skipped(7-8 per 10 s) is not a lost frame. The schedule only counts a skip when an acquire arrives a whole period after its release point. That count matches the guest's own shortfall, 600 − 593 per 10 s. The gate holds none of those frames.
Why measuring from the client cannot fix this. It comes from gamescope's own timing; line numbers are from gamescope src/:
- gamescope takes client commits at its wakeup. The wakeup is the target vblank minus
rolling draw time + red zone(vblankmanager.cpp142-199).- The rolling draw time holds peaks. A draw more than half a red zone above it replaces it at once (164-165), and it then decays per wakeup (167).
- "Draw time" runs from wakeup to page flip queued (
DRMBackend.cpp4465). On this stack that includes the commit to the daemon, so it is spiky. - So the latch point moves by milliseconds, jumps earlier on any spike, and drifts back over the following frames.
- The client only gets weak evidence. It sees
presentedcarrying the target vblank (steamcompmgr.cpp8140, 8168) anddiscarded. Each presented frame gives one inequality on the latch point.- The bound tightens only as the guest's commit phase sweeps the period. For 59.3 fps against 59.98 Hz, one sweep takes about 1.5 s, which is longer than the decay.
- A guest that runs at the grid rate never sweeps, so the bound never tightens where it matters.
- Any window boundary the client chooses therefore has a moving error band of a few ms. Commits that fall in it still merge, which is what B6 shows.
- Assigning
desired_present_timetargets (steamcompmgr.cpp8040) runs into the same band.
- Only gamescope knows its latch point. The protocol mechanism that uses it is the FIFO declaration: gamescope latches one FIFO commit per window per vblank (
steamcompmgr.cpp8028-8049).
Outcome. ce1f919 implements this: at the display's rate it declares FIFO and keeps the vblank-grid gate; below the display's rate it keeps mailbox with the gate. The commit window (8cf5834) is backed out.
With gamescope A58
GameNative/gamescope#31 (A58, draft) moves the hold that B6 attempted into gamescope, where the latch point is known. While gamescope's FPS limiter is engaged, the first non-FIFO commit for a paint is latched as today, a newer one is held for the next vblank, and a third replaces the held one. The client is never blocked, and FIFO commits are untouched.
- H1 shows that mailbox at the display's rate is then at least as good as FIFO: 59.269 fps with 1.2 % doubles, against FIFO's 59.22 with 1.3 %. Shim commits equal daemon publishes.
- When the payload carries A58, the cap-at-display-rate FIFO exception can be dropped. gamescope and the shim ship in the same archive, so a shim that declares the container's mode at the display's rate never meets a gamescope without the hold.
- Until then, this branch keeps the exception (
source=cap-rate). Thedebug.gndrm.mailbox_at_ratetest switch that H1 and H2 used (b084ead) is removed in c9b7c31. - H2 (DTD, in-game VSync on): 59.984 fps with 0 doubles. On A58's first revision, though, every commit was carried one paint, because a guest at exactly one commit per vblank never drains the hold. gamescope#31 9fad070 bounds the carry; H2 and H1 are being re-run on it.
@joshuatam, I'd value your read on two things:
- The rule table, in particular declaring FIFO, with the gate, for a non-FIFO container when the cap equals the display's rate.
- Whether that cap-rate FIFO case should be a user choice instead. For example, a "frame delivery: compositor-latched / shim-gated" option alongside the present mode, so that someone who prefers mailbox at the display's rate can keep it, accepting the occasional merged frame.