From 649e45dd8ab27d6ac98dd58eb05d9c0bf6c45d81 Mon Sep 17 00:00:00 2001 From: Utkarsh Dalal Date: Sun, 1 Nov 2026 15:09:03 +0530 Subject: [PATCH 1/4] =?UTF-8?q?android:=20A56=20=E2=80=94=20release=20the?= =?UTF-8?q?=20framebuffers=20a=20flip=20replaced=20at=20the=20next=20flip?= =?UTF-8?q?=20on=20gamenative-drm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-release-framebuffers-one-flip-later.patch | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 android/patches/gamescope/0052-A56-release-framebuffers-one-flip-later.patch diff --git a/android/patches/gamescope/0052-A56-release-framebuffers-one-flip-later.patch b/android/patches/gamescope/0052-A56-release-framebuffers-one-flip-later.patch new file mode 100644 index 000000000..133f9cfb3 --- /dev/null +++ b/android/patches/gamescope/0052-A56-release-framebuffers-one-flip-later.patch @@ -0,0 +1,108 @@ +A56 — release the framebuffers replaced by the previous flip, not this one, on gamenative-drm + +On KMS a page-flip event means the new framebuffer is on screen and the one it +replaced is free, so page_flip_handler drops the FBs that just left the screen +and their client buffers go back to the producer (wl_buffer.release, Present +idle). On gamenative-drm the event arrives when SurfaceFlinger latches the new +frame, but the buffer it replaced is still scanned out until its release fence +signals, about one vsync later. A producer handed that buffer at the event +renders into it while it is on the panel: a straight tear, new frame above, +old below (SteamWorld Dig 2 on the Retroid Pocket 6, about 16 per 30 s). + +The buffer replaced by flip N is safely free at flip N+1's event. So under +gamenative-drm page_flip_handler keeps the FBs a flip took off screen in +m_RetiredFbIds and drops them at the next flip; a flip that leaves nothing on +the plane (plane off) drops both sets at once, since no later flip is coming, +and finish_drm drops everything. Only the release moves; the event, the vblank +timestamp and the queued/visible bookkeeping are unchanged. A modeset commit is +a flip like any other and releases the previous retirees. + +Swapchain depth: each producer keeps one more buffer held by gamescope (on +screen + retired) for at most one flip, and none starves: +- the ICD shim's pool mailbox has 5 slots: 2 held, 3 for the guest; +- DXVK's 3 images: one on screen, one retired (free at the next flip), one to + render; gamescope flips at most once per vblank, so a new image frees every + flip and the guest never waits more than one; +- Xwayland allocates window buffers on demand, so it adds one if it needs it; +- gamescope's composite output ring of 3 (rendervulkan nOutImage % 3) is reused + by index, not by release, and the image written next was replaced two frames + earlier, so the deferral does not touch it. + +On by default under gamenative-drm (GAMENATIVE_DRM_SOCKET set); +GAMESCOPE_GN_RELEASE_AT_FLIP=1 restores the KMS behaviour. One line at start: +"gamescope: A56 release replaced framebuffers one flip later: on|off". + +diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp +index e25ec21..4435264 100644 +--- a/src/Backends/DRMBackend.cpp ++++ b/src/Backends/DRMBackend.cpp +@@ -66,6 +66,22 @@ static bool BUsesGameNativeDrm() + return bUsing; + } + ++// gamenative A56: release the FBs a flip replaced at the following flip (see ++// page_flip_handler). On by default under gamenative-drm; ++// GAMESCOPE_GN_RELEASE_AT_FLIP=1 restores the KMS behaviour. ++static bool BReleaseOneFlipLater() ++{ ++ static const bool bOn = []() ++ { ++ const char *pszOff = getenv( "GAMESCOPE_GN_RELEASE_AT_FLIP" ); ++ const bool bOn = BUsesGameNativeDrm() && !( pszOff && strcmp( pszOff, "1" ) == 0 ); ++ if ( BUsesGameNativeDrm() ) ++ fprintf( stderr, "gamescope: A56 release replaced framebuffers one flip later: %s\n", bOn ? "on" : "off" ); ++ return bOn; ++ }(); ++ return bOn; ++} ++ + #include "wlr_begin.hpp" + #include + #include +@@ -170,6 +186,9 @@ struct drm_t { + // Accessed only on page flip handler thread. + std::mutex m_mutVisibleFbIds; + std::vector> m_VisibleFbIds; ++ // gamenative A56: FBs the last flip took off screen, released at the next ++ // flip (guarded by m_mutVisibleFbIds). See page_flip_handler. ++ std::vector> m_RetiredFbIds; + + std::atomic < uint32_t > uPendingFlipCount = { 0 }; + +@@ -848,8 +867,26 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi + + { + std::scoped_lock lock{ g_DRM.m_QueuedFbIdsMutex, g_DRM.m_mutVisibleFbIds }; +- // Swap and clear from queue -> visible to avoid allocations. +- g_DRM.m_VisibleFbIds.swap( g_DRM.m_QueuedFbIds ); ++ if ( BReleaseOneFlipLater() ) ++ { ++ // gamenative A56: on gamenative-drm the flip event arrives when ++ // SurfaceFlinger latches the new frame, but the buffer it replaced ++ // stays on the panel until its release fence signals, about one ++ // vsync later. So the FBs this flip took off screen are released ++ // at the NEXT flip, and the ones the previous flip retired go now. ++ // A plane turned off (nothing queued) releases both at once, as no ++ // later flip is coming to do it. ++ g_DRM.m_RetiredFbIds.swap( g_DRM.m_VisibleFbIds ); ++ g_DRM.m_VisibleFbIds.clear(); ++ g_DRM.m_VisibleFbIds.swap( g_DRM.m_QueuedFbIds ); ++ if ( g_DRM.m_VisibleFbIds.empty() ) ++ g_DRM.m_RetiredFbIds.clear(); ++ } ++ else ++ { ++ // Swap and clear from queue -> visible to avoid allocations. ++ g_DRM.m_VisibleFbIds.swap( g_DRM.m_QueuedFbIds ); ++ } + g_DRM.m_QueuedFbIds.clear(); + } + +@@ -1943,6 +1980,7 @@ void finish_drm(struct drm_t *drm) + { + std::unique_lock lock( drm->m_mutVisibleFbIds ); + drm->m_VisibleFbIds.clear(); ++ drm->m_RetiredFbIds.clear(); + } + drm->sdr_static_metadata = nullptr; + drm->current = drm_t::drm_state_t{}; From dbbca409cfe7f801f7086d4a08cc90c6b5440ba2 Mon Sep 17 00:00:00 2001 From: Utkarsh Dalal Date: Sun, 1 Nov 2026 16:21:19 +0530 Subject: [PATCH 2/4] ci: build Xwayland from gn-no-implicit-sync and upload it (test branch) --- .github/workflows/android-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 7bfc92ba1..0e9f3f0ed 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -115,7 +115,7 @@ jobs: uses: actions/checkout@v4 with: repository: GameNative/xserver - ref: gamenative + ref: gn-no-implicit-sync path: xserver - name: Build Xwayland @@ -185,6 +185,7 @@ jobs: path: | build-android/src/gamescope build-android/src/gamescopereaper + xserver/build-android/hw/xwayland/Xwayland if-no-files-found: error - name: ccache stats From b454f48ebdaf5bd2ee0d1e076d4f8803509dc520 Mon Sep 17 00:00:00 2001 From: Utkarsh Dalal Date: Sun, 1 Nov 2026 17:08:01 +0530 Subject: [PATCH 3/4] ci: build libxcvt unversioned (soname libxcvt.so) like the shipped payload --- android/ci/termux-prefix.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/ci/termux-prefix.sh b/android/ci/termux-prefix.sh index d75e04885..0ab2b2275 100755 --- a/android/ci/termux-prefix.sh +++ b/android/ci/termux-prefix.sh @@ -150,7 +150,7 @@ strip = '$TOOLCHAIN/llvm-strip' pkgconfig = '$(command -v pkg-config)' [host_machine] -system = 'linux' +system = 'android' cpu_family = 'aarch64' cpu = 'armv8-a' endian = 'little' From 68241cadc07ea4c65f16fed93a4506eb4a0f1807 Mon Sep 17 00:00:00 2001 From: Utkarsh Dalal Date: Sun, 1 Nov 2026 17:37:00 +0530 Subject: [PATCH 4/4] ci: build Xwayland from xwayland-no-implicit-sync-gamenative and ship it in the artifact --- .github/workflows/android-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 0e9f3f0ed..d3e701645 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -115,7 +115,7 @@ jobs: uses: actions/checkout@v4 with: repository: GameNative/xserver - ref: gn-no-implicit-sync + ref: xwayland-no-implicit-sync-gamenative path: xserver - name: Build Xwayland