Repository navigation
android: A53/A54 — vblank on the panel latch grid, compositor sleeps when idle - #23
Open
utkarshdalal wants to merge 2 commits into
Open
utkarshdalal wants to merge 2 commits into
utkarshdalal wants to merge 2 commits into
Conversation
added 2 commits
October 31, 2026 14:30
… when idle A53 asks gamenative-drm for page-flip stamps on SurfaceFlinger's latch grid (client cap GNLG, 0x474E4C47) when -r is a panel rate, takes live panel-rate changes through the gn_panel_refresh ConVar, gives limited clients the target's period as their refresh cycle (the whole-vblank divisor truncated on a coarse grid) and repaints once after a gamescope_private command. A54 stops re-arming the vblank timer after every wakeup: it re-arms on every loop pass while a repaint, a flip, a frame callback, a held commit, a fade or a visible cursor's auto-hide is pending, and stops after vblank_idle_ticks (default 2) idle wakeups. Every out-of-loop repaint request now nudges.
… of now are safe gn_panel_refresh is re-posted by the daemon on every panel report: a repeat of the current rate changes nothing and does not repaint, and a refused value is logged once. A commit done while the compositor is idle takes its earliest present time from the next grid point, and the present margin no longer wraps when the earliest present time is behind the latch time.
This was referenced Oct 31, 2026
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.
android: A53, A54 — vblank on the panel latch grid, compositor sleeps when idle
Branch
vblank-on-panel-latch-grid(8a0f100, 5d4c927), basegamenative. Two new patches in the Android train (android/patches/gamescope/), on top of A52 (#22):0051-A53-vblank-on-the-panel-latch-grid.patch0052-A54-compositor-sleeps-when-idle.patchCompanion PRs:
flip-stamps-on-the-panel-latch-grid: flip stamps on the latch grid,gn_panel_refreshforwarding. Needed for the phase lock.gamescope-panel-rate-vblank: passes-r <active panel Hz>instead of-r 1200. Needed for the panel-rate grid.present-wait-answered-at-commit-in-mailbox(ICD shim): the actual fix for the 47 fps at cap 60. Independent of this PR.Each half degrades safely without the others (see Compatibility).
What this PR does and does not fix
Background: the gap vs X11 is frames, not resources
The shipped stack (A52,
-r 1200) delivered 47.05 fps at cap 60 against 59.89 on X11. Per frame delivered, CPU and GPU cost tie (GPU 60.4 % at 47 fps vs 79.9 % at 60). The SF intervals alternate strictly between 2 and 3 panel periods. Cap 30 is exact. The cursor is composited by gamescope and SurfaceFlinger keeps DEVICE composition with no layout switch. gamescope itself costs 8.1 % of a core and about 1720 wakeups/s whatever the frame count, because the timerfd steps on the 1200 Hz grid on the steamcompmgr thread.Correction to the earlier draft of this PR. It explained the 47 fps as a two-frames-in-flight release loop through Xwayland, gamescope's release-on-next-flip and SurfaceFlinger's release, and expected the phase lock to help. That is refuted:
MESA_VK_WSI_PRESENT_MODE=mailbox), but DXVK still believes it has FIFO and waits onvkWaitForPresentKHRfor every frame;F = (a + p + 2P) / (L + 1)= 21.2 ms = 47 fps;Fix 1 could not change that: the grace is a fixed time after commit whatever gamescope's grid. The device run confirms it (46.91 fps). See the shim PR for the mechanism and the fix.
A53 — the vblank grid is the panel's latch grid (
0051)src/Backends/DRMBackend.cppinit_drm: under gamenative-drm, afterDRM_CLIENT_CAP_ATOMIC, it sets client capGNDRM_CLIENT_CAP_LATCH_GRID=0x474E4C47("GNLG") to 1. That happens only when-ris a panel rate (500 Hz or below, or unset); a legacy-r 1200session does not ask. One log line covers the result: accepted, rejected (errno), or legacy grid. An old daemon or a render node answersEINVAL, and that is the no-phase-lock fallback.src/vblankmanager.{hpp,cpp}: two new methods.CVBlankTimer::RequestRefreshmHzstores into an atomic and can be called from any thread.ApplyPendingRefreshruns on the steamcompmgr thread. It movesg_nNestedRefresh, logsgn_panel_refresh applied: vblank grid N mHz (was M mHz), and is a no-op for the current value.src/steamcompmgr.cpp:gn_panel_refresh <Hz>, posted by the daemon throughgamescope_private.execute.refresh * (refresh / target)in whole vblanks. That truncates on a coarse grid: 40 fps on 60 Hz read as 16.7 ms instead of 25. It is now the target's period, which matches A52's time grid.src/wlserver.cppgamescope_private_execute: a successful command callsforce_repaint(), sincescaling_filter,scaling_sharpnessanddebug_force_repaintchange the next frame with no commit behind them.gn_panel_refreshis the exception.GetNextVBlankonly steps forward. A stamp ahead skips the vblank the flip already occupies, which is correct.page_flip_handlerdelta is a debug print.A52 stays exact at the panel rate:
refresh <= targetmakes the limiter inert, so every tick releasesA54 — the compositor stops waking when nothing is pending (
0052)src/steamcompmgr.cpp:steamcompmgr_rearm_vblank. It runs on every loop pass, so a nudge from a commit, X event or input arms the next grid point.steamcompmgr_vblank_has_work): a repaint of any kind or a forwarded VR overlay; a focus fading out; a flip in flight; a window with a done commit whose frame callback has not gone out (steamcompmgr_any_window_owes_frame_done), or a done commit held for a later vblank; a shown cursor before its auto-hide time (plus 100 ms); VRR active or PipeWire streaming.vblank_idle_tickstimer wakeups with none of that (new ConVar, default 2), the timer stays off.vblank_idle_ticks 0restores the old behaviour. The flip handler still re-arms on every flip.steamcompmgr_next_refresh_time). The present margin saturates at 0 instead of wrapping.vblank_debug, idle/resume transitions are logged, rate-limited.src/wlserver.cpp: newwlserver_request_repaint()setshasRepaintand nudges once per painted frame. It replaces every barehasRepaint = truethere: cursor motion and buttons (wlserver_oncursorevent),wlserver_mousehide, theset_lookConCommand,gamescope_controlset_look/unset_look.src/steamcompmgr_shared.hppCScreenshotManager::TakeScreenshot: nudges.Before / after
Setup, every column. Retroid Pocket 6 (Adreno 740 class, 1080x1920 panel at 120 Hz, landscape), Dave the Diver (1868140), DXVK 2.7.1-gplasync, proton-10.0-arm64ec-2, container 1280x720, FPS limiter as listed, the same boat-deck scene. Segments are 180 s (launch 4: 60-120 s). "SF" = the game's SurfaceFlinger layer histogram; "PowerMetrics" = the app's own counter (5 s samples). gamescope-xwm is the steamcompmgr thread.
-r 1200)-r 1200)The fix 1 + shim fix column: SF fps and histogram from a 47 s cap-60 segment (launch 4); PowerMetrics 59.97 fps over 143 samples; game/gamescope CPU from the 60 s hidden-cursor segment of the cursor-plane run (fix 1 + shim + cursor layer, cursor hidden), where the SF fps was 60.00. Temperatures are loose: the X11 run started hot (CPU zone 92.7 °C at segment start).
Cap 60 with the cursor moving (fix 1 alone): SF 70.78 fps vs 61.75 shipped, gamescope 8.7 % / 1122 wakeups/s vs 10.8 % / 1747. The layer count includes cursor-only composites, so it is not a game frame rate.
Testing
gamescope-panel-rate-vblank(daemon0630e1ain jniLibs), payload 20261030.3 with this gamescope binary swapped in. Launch line shows-r 120; gamescope logsdrm: vblank: daemon latch-grid stamps on (GNLG accepted, grid 120000 mHz)and the daemonvblank: latch-grid flip stamps on (lead 2000us).EINVALfallback, the legacy-r 1200path with the new daemon, a 120 ↔ 60 Hz switch (gn_panel_refresh applied), a 60 Hz panel, idle sleep on a static menu, LSFG.Compatibility
-rpanel) + new daemon + this gamescopeEINVAL(one log line); timer at the panel rate, no phase lock; idle sleep-r 1200) + new daemongn_panel_refreshignored (logged once): the 1200 Hz timeline as today, plus idle sleepShipping needs the app PR (
-r= panel rate) and the daemon PR (phase lock), plus a gamescope payload containing this binary and the shim fix.Risks
gn_panel_refreshlands, gamescope ticks at 120 on a 60 grid: extra work, nothing visible. During 60 → 120 it stays capped at 60 until the ConVar arrives.