Skip to content

feat: gamescope integration (FSR upscaling + frame pacing)#4

Open
Vower2993 wants to merge 32 commits into
mainfrom
gamescope
Open

feat: gamescope integration (FSR upscaling + frame pacing)#4
Vower2993 wants to merge 32 commits into
mainfrom
gamescope

Conversation

@Vower2993

Copy link
Copy Markdown
Owner

Adds gamescope as a nested Wayland compositor between Wine and the WaylandIE bridge.

Topology: WaylandIE bridge (wayland-0) ← gamescope ← Wine (gamescope-0)

Features:

  • FSR upscaling (720p internal → 1080p output)
  • Frame pacing
  • Relative-pointer grab for FPS games
  • Steam-Mode-style window management

Build: libgamescope.so (5.7 MB ARM64) cross-compiled with NDK r27c, packaged in jniLibs.

Status: BINARY BUILDS, Java wiring complete. Runtime untested — expect 5-10 iteration debug cycle.

Z User added 4 commits July 5, 2026 17:47
…frame pacing

Adds gamescope as a nested Wayland compositor between Wine and the WaylandIE
bridge, enabling FSR upscaling, frame pacing, and Steam-Mode-style window
management on Android.

Topology:
  WaylandIE bridge (wayland-0) ← gamescope ← Wine (gamescope-0)

When displayMode == "gamescope":
  1. WaylandBridgeComponent starts (binds wayland-0 socket)
  2. GamescopeComponent starts (launches libgamescope.so, binds gamescope-0)
  3. GuestProgramLauncherComponent launches Wine with WAYLAND_DISPLAY=gamescope-0
  4. Wine's winewayland.drv connects to gamescope-0
  5. DXVK renders via VK_KHR_wayland_surface to gamescope
  6. gamescope composites (FSR + frame pacing) and presents to wayland-0
  7. Bridge forwards dmabuf to JVM presenter → SurfaceFlinger

Build infrastructure:
  - tools/build-gamescope-stack.sh: cross-compiles wlroots 0.19 + gamescope
    for Android aarch64-bionic (NDK r27c, API 33)
  - tools/android_stubs.cpp: stub libinput/udev/libdecor/shm symbols
  - tools/x11_stubs.c: stub 100+ X11 function symbols (weak linkage)
  - tools/build-bionic-bridge.sh: bumped wayland 1.22.0 → 1.23.1 (wlroots 0.19
    requires >=1.23.1); fixed upstream URL (nicholasgasior fork → official
    gitlab.freedesktop.org)
  - app/src/main/jniLibs/arm64-v8a/libgamescope.so: 5.7 MB stripped ARM64
    ELF executable (packaged as lib*.so per AGP convention)

Runtime integration:
  - GamescopeComponent.java: new EnvironmentComponent that launches gamescope
    with a `sleep 999999999` placeholder child (gamescope REQUIRES a command
    after `--`; the placeholder keeps gamescope alive as a long-lived
    compositor). Uses /system/bin/sh (NOT /bin/sh) for Android compat.
  - GuestProgramLauncherComponent.java: added gamescopeEnabled flag + setters;
    when true, Wine gets WAYLAND_DISPLAY=gamescope-0 instead of wayland-0
  - XServerDisplayActivity.java: 4 displayMode=="wayland" checks updated to
    also accept "gamescope"; GamescopeComponent registered after
    WaylandBridgeComponent in the addComponent chain
  - Container.java: comment update (DEFAULT_DISPLAY_MODE still "wayland")

Android compat patches (in android_sysvshm/android_sysvshm.c):
  - Added POSIX shm_open/shm_unlink shim backed by memfd_create (raw syscall).
    Bionic libc does NOT implement shm_open — wlroots/gamescope segfault
    without this. The existing SysV shmget/shmat/shmdt/shmctl path is
    unchanged (no regression).

Known limitations:
  - gamescope's X11 stubs are link-time only; if any X11 function is actually
    called during gamescope init (likely, since steamcompmgr.cpp always runs),
    gamescope will segfault. This is the #1 expected failure mode.
  - The shm_open shim has a registry memory leak (no fd close on refcount
    decrement). Matches the existing SysV shim pattern; not a correctness bug
    for our use case (wlroots always shm_unlinks immediately after shm_open).
  - gamescopeInternalWidth/Height/Sharpness fields in GuestProgramLauncherComponent
    are unused (dead code from an earlier design where the launcher wrapped
    Wine with `libgamescope.so -- <cmd>`. The final design launches gamescope
    separately via GamescopeComponent. Left in place to minimize diff churn.)

Tested:
  - android_sysvshm.c compiles clean with NDK r27c aarch64-linux-android33-clang
  - GamescopeComponent.java compiles clean with javac 21
  - XServerDisplayActivity.java + GuestProgramLauncherComponent.java pass
    syntax check (no Java syntax errors; only "package not found" errors
    from missing Android SDK, which is expected)
  - libgamescope.so is a valid ARM64 ELF executable (verified with `file`)

Not yet tested:
  - Actual on-device runtime behavior. Expect 5-10 iterations of debugging
    before the full pipeline (Wine → gamescope → bridge → SurfaceFlinger)
    renders a frame.
…ortcut settings

Adds 'Gamescope (FSR Upscaling + Frame Pacing) [Experimental]' as a third
option in the Display Mode dropdown, alongside X11 and Wayland.

Changes:
- strings.xml: new display_mode_gamescope string resource
- arrays.xml: add 'gamescope' to display_mode_entries + display_mode_values
- ShortcutSettingsComposeDialog.kt: add 'gamescope' to displayModeValues list
  (per-game override)
- ContainerSettingsComposeDialog.kt: add 'gamescope' to displayModeValues list
  (container default)

The dropdown UI is already generic (SettingDropdown reads from
state.displayModeEntries which is populated from R.array.display_mode_entries),
so adding the third array entry automatically makes the option appear in both:
  1. Container Settings → Display Mode (container default)
  2. Game/Shortcut Settings → Display Mode (per-game override)

Selecting 'Gamescope' sets displayMode='gamescope', which triggers:
  - WaylandBridgeComponent + GamescopeComponent startup
  - Wine launched with WAYLAND_DISPLAY=gamescope-0
  - FSR upscaling (720p internal → 1080p output) + frame pacing

Marked [Experimental] because the runtime path is untested — expect crashes.
… race condition

Root cause of gamescope mode failure on first device test:

  19:58:21.225  GamescopeComponent: Bridge socket wayland-0 not found → BAILS
  19:58:21.519  WayLandIE/Bridge: server=ready socket=wayland-0 ← 294ms too late
  19:58:24.253  Wine launched with WAYLAND_DISPLAY=gamescope-0 → no gamescope running
  19:58:27      nodrv_CreateWindow: no driver could be loaded → Wine exits

WaylandBridgeComponent.start() returns immediately after launching the bridge
subprocess, but the subprocess takes ~300ms to bind the wayland-0 socket.
GamescopeComponent.start() checked for the socket ONCE and bailed — losing
the race every time.

Fix: poll for the wayland-0 socket every 100ms for up to 10 seconds before
giving up. Mirrors the 3s wait pattern already used by WaylandBridgeComponent
itself, just longer to be safe.

Also increased the gamescope-0 socket wait from 5s to 15s with progress
logging every 2s — gamescope needs to init wlroots + Vulkan which may take
longer on first launch (shader compilation).
Z User and others added 25 commits July 5, 2026 19:31
Rebuilds wlroots with -Dxwayland=enabled and gamescope against it, so
gamescope can spawn its own Xwayland server for Wine to connect to via
DISPLAY=:1. This is the first phase of Path A — getting the Wine desktop
to render through gamescope via Xwayland.

Changes:
- tools/build-gamescope-stack.sh: -Dxwayland=disabled → -Dxwayland=enabled
- tools/android_stubs.cpp: removed wlr_xwayland_server_create/destroy stubs
  (the real wlroots implementations are now linked in from libwlroots-0.19.a)
- app/src/main/jniLibs/arm64-v8a/libgamescope.so: rebuilt binary (5.7 MB)
  now includes wlroots xwayland backend code

Build infrastructure added (in /tmp, not committed):
- xcb headers from Debian libxcb-dev packages (xcb, xcb-composite, xcb-ewmh,
  xcb-icccm, xcb-render, xcb-res, xcb-xfixes)
- Empty stub .a libraries for all xcb packages (linker-only — the actual xcb
  function implementations will be provided by the Xwayland binary at runtime)
- xwayland.pc stub with xwayland path + feature flags
- xcb/shape.h stub header

Backwards compatible: if no Xwayland binary is available at runtime,
gamescope logs an error and continues with Wayland-only clients. The
WLR_XWAYLAND env var can be set to override the Xwayland binary path.

Remaining Path A work (not yet done):
- Source Xwayland arm64 binary + glibc libraries (Debian mirror download
  issues — will try alternative mirrors)
- Create wrapper script that invokes glibc linker to run Xwayland
- Set WLR_XWAYLAND env var in GamescopeComponent
- Update Java env vars for gamescope mode: DISPLAY=:1, winex11.drv
- Skip binary patching (VK_KHR_wayland_surface → VK_KHR_xlib_surface) in
  gamescope mode since Wine uses winex11.drv, not winewayland.drv
…bridge fixes)

Merges the following fixes from main into the gamescope branch:
- fix: route game presentation through bridge + fix bionic-bridge wayland clone URL
- fix: frame rate limiting for SHM desktop frames — prevents GPU memory exhaustion
- fix: remove diagnostic patch that broke winewayland.so build
- fix: keep DISPLAY=:0 + Graphics=wayland only — explorer needs display config
- fix: remove broken sed command + add stdio.h to waylanddrv_main.c patch
- diag: deep diagnostic logging in winewayland.drv DllMain + wayland_process_init
- fix: create arm64ec-windows dir if missing — was skipping winewayland.drv copy
- fix: copy winewayland.drv to arm64ec-windows — explorer.exe couldn't load it
- fix: dismiss preloader on first bridge frame — was stuck on 'starting wine'
- fix: restore ALL pre-seeded cross-compile cache vars — winewayland.drv wasn't built

Conflict resolution: took main's version of build-bionic-bridge.sh (better
tarball download logic with fallback to git clone).
Activates the game-presentation-through-bridge path from vulkan.c (commit
6821f87 on main) for gamescope mode. When enabled:

1. DXVK calls vkCreateWin32SurfaceKHR
2. winewayland.drv's wayland_vulkan_surface_create intercepts
3. If WAYLANDIE_GAME_VIA_BRIDGE=1: tries vkCreateWaylandSurfaceKHR on
   gamescope-0's wl_display (set via WAYLAND_DISPLAY=gamescope-0)
4. If Turnip supports VK_KHR_wayland_surface: game's swapchain binds to
   a Wayland surface on gamescope → gamescope composites (FSR + frame
   pacing) → presents to bridge → SurfaceFlinger
5. If Turnip doesn't support it: falls back to xlib_surface (old path,
   game bypasses gamescope — safe degradation)

This is the key env var that makes game rendering flow through gamescope
instead of bypassing it via adrenotools. Combined with the race condition
fix (commit 1af4583), the full pipeline should be:

  Game DXVK → VK_KHR_wayland_surface on gamescope-0
    → gamescope composites (FSR 720p→1080p, frame pacing)
      → gamescope presents to wayland-0 (bridge) via zwp_linux_dmabuf_v1
        → bridge forwards dmabuf to JVM presenter
          → SurfaceFlinger → display

Whether Turnip supports VK_KHR_wayland_surface on Android is the open
question. If it does, we get the full gamescope pipeline. If not, the
game still renders (just without FSR/frame pacing) and the desktop still
goes through gamescope via winewayland.drv's wl_shm path.
Root cause of gamescope failing to start on device:
  CANNOT LINK EXECUTABLE libgamescope.so: library 'libc++_shared.so' not found

libgamescope.so is a C++ binary built with NDK r27c's libc++. It links
against libc++_shared.so (the shared C++ runtime). The app's CMake-built
native libs (libwaylandie_bridge_exe.so etc.) use -DANDROID_STL=c++_shared
and AGP automatically bundles libc++_shared.so for them. But libgamescope.so
is a PRE-BUILT binary in jniLibs/ — AGP doesn't auto-detect its STL needs.

Fix: copy libc++_shared.so from the NDK sysroot into jniLibs/arm64-v8a/
alongside libgamescope.so. Both are extracted to the same nativeLibraryDir
at install time (useLegacyPackaging=true), so the dynamic linker finds
libc++_shared.so when loading libgamescope.so.

The libc++_shared.so is from NDK r27c (aarch64-linux-android), API 21+,
1.8 MB. It's the same version the CMake-built libs use.
The previous commit bundled libc++_shared.so in jniLibs/ for libgamescope.so,
but CMake also produces libc++_shared.so (because -DANDROID_STL=c++_shared is
set in build.gradle). This caused:

  > Task :app:mergeLudashiDebugNativeLibs FAILED
  > 2 files found with path 'lib/arm64-v8a/libc++_shared.so' from inputs:
    - jniLibs/arm64-v8a/libc++_shared.so
    - intermediates/cxx/.../obj/arm64-v8a/libc++_shared.so

Fix: add pickFirsts += 'lib/arm64-v8a/libc++_shared.so' to packagingOptions.
AGP picks the first occurrence (jniLibs/) and ignores the CMake-produced one.
Both are the same NDK r27c version, so it doesn't matter which is picked.
Lists all lib/arm64-v8a/ files in the built APK and verifies that
critical libs (libc++_shared, libgamescope, libwaylandie) are present.
This will help diagnose why the device test still shows 'libc++_shared.so
not found' despite the file being committed to jniLibs/.
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