Conversation
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).
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/.
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.
Adds gamescope as a nested Wayland compositor between Wine and the WaylandIE bridge.
Topology: WaylandIE bridge (wayland-0) ← gamescope ← Wine (gamescope-0)
Features:
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.