Skip to content

shim: answer the panel's latch grid to gamescope's frame-rate limit - #33

Closed
utkarshdalal wants to merge 4 commits into
fix/gamescope-resolution-scanoutfrom
fps-cap-on-the-latch-grid
Closed

utkarshdalal wants to merge 4 commits into
fix/gamescope-resolution-scanoutfrom
fps-cap-on-the-latch-grid

Conversation

@utkarshdalal

Copy link
Copy Markdown
Contributor

What this fixes

With the FPS limiter at 60 under gamescope on a 60 Hz panel, gamescope commits ~59.4 frames/s but SurfaceFlinger shows ~56.7: about 105 double-period gaps per 30 s on the Odin 3 with Dave the Diver. At 120 Hz the same session shows every commit (59.46 shown, 59.46 committed), so the frames are lost at the latch: the limiter's release grid is free-running against the panel (the daemon stamps flip events on its own 1200 Hz timeline, VblankTimeline), two commits land on one latch point and one is replaced.

The daemon already measures the panel's latch grid from the latch stats (vsync.rs) and pushes it to shims subscribed with CAP_VSYNC_NOTE, which the Vulkan ICD in the gamescope process is. This PR makes that grid reachable from gamescope so its limiter (GameNative/gamescope#21, patch A52) can place releases on it:

  • ICD (libadrenotools_gamescope.so): exports gamenative_drm_icd_latch_grid(anchor_ns, period_ns, lead_ns), which answers the latest VsyncNote the ICD holds (the same values lsfg::latch_grid() reads), 0 before the first note.
  • LD_PRELOAD shim (libgndrm_shim.so): exports gamenative_drm_latch_grid(...), the symbol gamescope binds weakly, like A51's gamenative_drm_scanout_pitch. It resolves the ICD's getter from the already-loaded ICD (found by its mapped path, since the ICD carries no DT_SONAME; RTLD_NOLOAD only, never a load), logs once where it answered from, and returns 0 until the ICD is loaded.
  • Export allowlists updated: libgndrm_shim.so 15 exports, the ICD 4. check-exports.sh passes on both.

No daemon change: gamescope steers its own lead from where each commit lands on the grid, so the daemon's LSFG-only lead loop is left alone.

Verification

  • Both libraries cross-built with the Mac NDK recipe; check-exports.sh PASS for both.
  • On the Odin 3 (payload 20261030.1 with these two libraries and the A52 gamescope swapped in) the ICD logged lsfg: panel grid period=8333us lead=5166us and the C shim found the symbol. That run also showed the grid's period is the panel's maximum rate (120 Hz) while the panel runs at 60 Hz, because the app reports detectMaxRefreshRateHz; snapping to that lattice puts commits on the edge of the real latch window, so the fix only works with the active rate reported. App side: utkarshdalal/GameNative PR (stacked on #2283) reports the active rate and re-reports on display changes. Device verification of the three together is pending an app build.

Stacked on #32 (base branch fix/gamescope-resolution-scanout) so the three ship in one payload.

@utkarshdalal

Copy link
Copy Markdown
Contributor Author

Superseded by the standalone PR on master: the gate fix is independent of the scan-out-size branch, and the gamescope-side grid export in this branch is dead code (the daemon serves one scan-out connection at a time, and gamescope's ICD is not it).

@joshuatam
joshuatam deleted the fps-cap-on-the-latch-grid branch October 30, 2026 15:29
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