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

Closed
UT
11d ago
+259-9

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.