From f7cebee7eed0223fb9bd64ba7d10ffbaf48d04f5 Mon Sep 17 00:00:00 2001 From: The412Banner Date: Wed, 28 Oct 2026 00:50:21 +0000 Subject: [PATCH] perf(gamescope): handle a finished frame at import (0124) with upstream fence fixes (0123) 0123 backports upstream 0efe47c and 666c52b by Matthew Schwartz (commit completion list lifetime, no Rc to a dying commit). 0124 polls a new commit's fence at import; a finished frame is marked done at once and handled in the same steamcompmgr pass instead of a second wake through the image waiter. commit_ready_check (default on) turns it off. --- tools/gamescope/PATCHES.md | 18 ++ .../0123-commit-fence-lifetime-upstream.patch | 157 ++++++++++++++++++ .../patches/0124-commit-done-at-import.patch | 103 ++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 tools/gamescope/patches/0123-commit-fence-lifetime-upstream.patch create mode 100644 tools/gamescope/patches/0124-commit-done-at-import.patch diff --git a/tools/gamescope/PATCHES.md b/tools/gamescope/PATCHES.md index 0d7956456..a57c3bc82 100644 --- a/tools/gamescope/PATCHES.md +++ b/tools/gamescope/PATCHES.md @@ -181,3 +181,21 @@ The nested Wayland backend's input thread reads the host display, but steamcompm does not watch that file descriptor, so buffer releases and presentation feedback queued by that read could wait up to one refresh for steamcompmgr's next poll. The input thread now wakes steamcompmgr after each read. + +## 0123 commit: fence lifetime fixes from upstream + +Backport of upstream commits 0efe47c and 666c52b by Matthew Schwartz (ValveSoftware/gamescope). +Pending commits share ownership of their completion list with the Xwayland context, so a late +fence signal can no longer append to a list freed with the context; and a commit being destroyed +no longer hands the image waiter a new reference to itself. Both touch the code 0124 changes. + +## 0124 commit: mark a finished frame done at import + +Every game frame took two passes of steamcompmgr's loop: the import duplicated the buffer's +fence and handed the commit to the image waiter (epoll add), then the waiter thread saw the fence +signalled, removed and closed it and woke steamcompmgr again to handle the finished commit. +`update_wayland_res` now polls the fence without waiting first. A frame that is already finished +is marked done at once - no duplicate fence, no epoll add or delete, no waiter wake - and the main +loop handles it in the same pass, after the import (FIFO commits still wait for a vblank). The +`commit_ready_check` convar (default on, `gamescopectl commit_ready_check 0`) turns it off; the +`xwm` log counts fences ready at import and waited for, every 10 s. diff --git a/tools/gamescope/patches/0123-commit-fence-lifetime-upstream.patch b/tools/gamescope/patches/0123-commit-fence-lifetime-upstream.patch new file mode 100644 index 000000000..75e93b68a --- /dev/null +++ b/tools/gamescope/patches/0123-commit-fence-lifetime-upstream.patch @@ -0,0 +1,157 @@ +diff --git a/src/commit.cpp b/src/commit.cpp +index c9d0001..f2e0f0c 100644 +--- a/src/commit.cpp ++++ b/src/commit.cpp +@@ -105,14 +105,15 @@ bool commit_t::CloseFenceInternal() + if ( m_nCommitFence < 0 ) + return false; + +- // Will automatically remove from epoll! +- g_ImageWaiter.RemoveWaitable( this ); ++ // Do not recreate an Rc after deletion has started. ++ if ( HasLiveReferences() ) ++ g_ImageWaiter.RemoveWaitable( this ); + close( m_nCommitFence ); + m_nCommitFence = -1; + return true; + } + +-void commit_t::SetFence( int nFence, bool bMangoNudge, uint32_t uMangoMsgType, CommitDoneList_t *pDoneCommits ) ++void commit_t::SetFence( int nFence, bool bMangoNudge, uint32_t uMangoMsgType, const std::shared_ptr &pDoneCommits ) + { + std::unique_lock lock( m_WaitableCommitStateMutex ); + CloseFenceInternal(); +diff --git a/src/commit.h b/src/commit.h +index 20f881d..8603a9a 100644 +--- a/src/commit.h ++++ b/src/commit.h +@@ -1,6 +1,7 @@ + #include "steamcompmgr_shared.hpp" + #include "Utils/NonCopyable.h" + ++#include + #include + #include "main.hpp" + #include "rendervulkan.hpp" +@@ -34,7 +35,7 @@ struct commit_t final : public gamescope::RcObject, public gamescope::IWaitable, + + // Returns true if we had a fence that was closed. + bool CloseFenceInternal(); +- void SetFence( int nFence, bool bMangoNudge, uint32_t uMangoMsgType, CommitDoneList_t *pDoneCommits ); ++ void SetFence( int nFence, bool bMangoNudge, uint32_t uMangoMsgType, const std::shared_ptr &pDoneCommits ); + + bool ShouldPreemptivelyUpscale( GamescopeUpscaleFilter eFilter, GamescopeUpscaleScaler eScaler ); + +@@ -81,5 +82,5 @@ struct commit_t final : public gamescope::RcObject, public gamescope::IWaitable, + bool m_bMangoNudge = false; + // Typed mangoapp stream to nudge, 0 for none, never the legacy type. + uint32_t m_uMangoMsgType = 0; +- CommitDoneList_t *m_pDoneCommits = nullptr; // I hate this +-}; +\ No newline at end of file ++ std::shared_ptr m_pDoneCommits; // I hate this ++}; +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 5fb9086..22aa907 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -932,7 +932,7 @@ constexpr const T& clamp( const T& x, const T& min, const T& max ) + + extern bool g_bForceRelativeMouse; + +-CommitDoneList_t g_steamcompmgr_xdg_done_commits; ++std::shared_ptr g_steamcompmgr_xdg_done_commits = std::make_shared(); + + + static std::mutex s_KeysToCloseMutex; +@@ -8073,7 +8073,7 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co + // TODO: Merge these two functions. + void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vblank_idx ) + { +- std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); ++ std::lock_guard lock( ctx->doneCommits->listCommitsDoneLock ); + + uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; + +@@ -8090,7 +8090,7 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vb + uint64_t now = get_time_in_nanos(); + + // very fast loop yes +- for ( auto& entry : ctx->doneCommits.listCommitsDone ) ++ for ( auto& entry : ctx->doneCommits->listCommitsDone ) + { + steamcompmgr_win_t *entry_win = nullptr; + for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next ) +@@ -8130,12 +8130,12 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vb + } + } + +- ctx->doneCommits.listCommitsDone.swap( commits_before_their_time ); ++ ctx->doneCommits->listCommitsDone.swap( commits_before_their_time ); + } + + void handle_done_commits_xdg( bool vblank, uint64_t vblank_idx ) + { +- std::lock_guard lock( g_steamcompmgr_xdg_done_commits.listCommitsDoneLock ); ++ std::lock_guard lock( g_steamcompmgr_xdg_done_commits->listCommitsDoneLock ); + + uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; + +@@ -8152,7 +8152,7 @@ void handle_done_commits_xdg( bool vblank, uint64_t vblank_idx ) + uint64_t now = get_time_in_nanos(); + + // very fast loop yes +- for ( auto& entry : g_steamcompmgr_xdg_done_commits.listCommitsDone ) ++ for ( auto& entry : g_steamcompmgr_xdg_done_commits->listCommitsDone ) + { + steamcompmgr_win_t *entry_win = nullptr; + for (const auto& xdg_win : g_steamcompmgr_xdg_wins) +@@ -8192,7 +8192,7 @@ void handle_done_commits_xdg( bool vblank, uint64_t vblank_idx ) + } + } + +- g_steamcompmgr_xdg_done_commits.listCommitsDone.swap( commits_before_their_time ); ++ g_steamcompmgr_xdg_done_commits->listCommitsDone.swap( commits_before_their_time ); + } + + gamescope::ConVar cv_mangoapp_use_output_timing{ "mangoapp_use_output_timing", true }; +@@ -8359,7 +8359,7 @@ static TempUpscaleImage_t *GetTempUpscaleImage( global_focus_t *pFocus, uint32_t + + gamescope::ConVar cv_surface_update_force_only_current_surface( "surface_update_force_only_current_surface", false, "Force updates to apply only to the current surface, ignoring commits for other surfaces." ); + +-void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, ResListEntry_t& reslistentry) ++void update_wayland_res(const std::shared_ptr &doneCommits, steamcompmgr_win_t *w, ResListEntry_t& reslistentry) + { + struct wlr_buffer *buf = reslistentry.buf; + +@@ -8632,7 +8632,7 @@ void check_new_xwayland_res(xwayland_ctx_t *ctx) + for ( uint32_t i = 0; i < tmp_queue.size(); i++ ) + { + steamcompmgr_win_t *w = find_win( ctx, tmp_queue[ i ].surf ); +- update_wayland_res( &ctx->doneCommits, w, tmp_queue[ i ]); ++ update_wayland_res( ctx->doneCommits, w, tmp_queue[ i ]); + } + } + +@@ -8645,7 +8645,7 @@ void check_new_xdg_res() + { + if ( xdg_win->xdg().surface.main_surface == tmp_queue[ i ].surf ) + { +- update_wayland_res( &g_steamcompmgr_xdg_done_commits, xdg_win.get(), tmp_queue[ i ] ); ++ update_wayland_res( g_steamcompmgr_xdg_done_commits, xdg_win.get(), tmp_queue[ i ] ); + break; + } + } +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 81910a1..3a75650 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -92,7 +92,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + + std::unique_ptr cursor; + +- CommitDoneList_t doneCommits; ++ std::shared_ptr doneCommits = std::make_shared(); + + double accum_x = 0.0; + double accum_y = 0.0; diff --git a/tools/gamescope/patches/0124-commit-done-at-import.patch b/tools/gamescope/patches/0124-commit-done-at-import.patch new file mode 100644 index 000000000..9ac818e4e --- /dev/null +++ b/tools/gamescope/patches/0124-commit-done-at-import.patch @@ -0,0 +1,103 @@ +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 22aa907..0aef598 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -8359,6 +8359,33 @@ static TempUpscaleImage_t *GetTempUpscaleImage( global_focus_t *pFocus, uint32_t + + gamescope::ConVar cv_surface_update_force_only_current_surface( "surface_update_force_only_current_surface", false, "Force updates to apply only to the current surface, ignoring commits for other surfaces." ); + ++gamescope::ConVar cv_commit_ready_check( "commit_ready_check", true, "Check a new commit's fence at import; a finished frame is marked done in the same pass instead of through the image waiter." ); ++ ++// Set when update_wayland_res marks a commit done itself, so the main loop handles it in the same pass. ++static bool s_bXwaylandCommitsDoneAtImport = false; ++static bool s_bXdgCommitsDoneAtImport = false; ++ ++static bool commit_fence_ready_now( int nFd ) ++{ ++ struct pollfd pfd = { .fd = nFd, .events = POLLIN, .revents = 0 }; ++ return poll( &pfd, 1, 0 ) == 1 && ( pfd.revents & POLLIN ); ++} ++ ++static void note_commit_fence( bool bReadyAtImport ) ++{ ++ static uint64_t s_ulReady = 0, s_ulWaited = 0, s_ulLastLog = 0; ++ ( bReadyAtImport ? s_ulReady : s_ulWaited )++; ++ uint64_t now = get_time_in_nanos(); ++ if ( !s_ulLastLog ) ++ s_ulLastLog = now; ++ if ( now - s_ulLastLog >= 10'000'000'000ul ) ++ { ++ xwm_log.infof( "commit fences, last 10 s: %lu ready at import, %lu waited for", s_ulReady, s_ulWaited ); ++ s_ulReady = s_ulWaited = 0; ++ s_ulLastLog = now; ++ } ++} ++ + void update_wayland_res(const std::shared_ptr &doneCommits, steamcompmgr_win_t *w, ResListEntry_t& reslistentry) + { + struct wlr_buffer *buf = reslistentry.buf; +@@ -8602,7 +8629,11 @@ void update_wayland_res(const std::shared_ptr &doneCommits, st + struct wlr_dmabuf_attributes dmabuf = {0}; + if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) + { +- fence = dup( dmabuf.fd[0] ); ++ // A finished buffer needs no fence of its own. ++ if ( cv_commit_ready_check && commit_fence_ready_now( dmabuf.fd[0] ) ) ++ bKnownReady = true; ++ else ++ fence = dup( dmabuf.fd[0] ); + } + else + { +@@ -8610,11 +8641,22 @@ void update_wayland_res(const std::shared_ptr &doneCommits, st + } + } + ++ if ( cv_commit_ready_check ) ++ { ++ if ( !bKnownReady && fence >= 0 && commit_fence_ready_now( fence ) ) ++ bKnownReady = true; ++ note_commit_fence( bKnownReady ); ++ } ++ + gpuvis_trace_printf( "pushing wait for commit %lu win %lx", newCommit->commitID, w->type == steamcompmgr_win_type_t::XWAYLAND ? w->xwayland().id : 0 ); + { + newCommit->SetFence( fence, mango_nudge, uMangoMsgType, doneCommits ); + if ( bKnownReady ) ++ { + newCommit->Signal(); ++ if ( cv_commit_ready_check ) ++ ( w->type == steamcompmgr_win_type_t::XWAYLAND ? s_bXwaylandCommitsDoneAtImport : s_bXdgCommitsDoneAtImport ) = true; ++ } + else + g_ImageWaiter.AddWaitable( newCommit.get() ); + } +@@ -9400,6 +9442,13 @@ void steamcompmgr_check_xdg(bool vblank, uint64_t vblank_idx) + } + + check_new_xdg_res(); ++ ++ // Commits marked done at import: handle them now rather than on the next wake. FIFO commits still wait for a vblank. ++ if ( s_bXdgCommitsDoneAtImport ) ++ { ++ s_bXdgCommitsDoneAtImport = false; ++ handle_done_commits_xdg( false, vblank_idx ); ++ } + } + + void update_edid_prop() +@@ -10512,6 +10561,14 @@ steamcompmgr_main(int argc, char **argv) + gamescope_xwayland_server_t *server = NULL; + for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) + check_new_xwayland_res(server->ctx.get()); ++ ++ // Commits marked done at import: handle them now rather than on the next wake. FIFO commits still wait for a vblank. ++ if ( s_bXwaylandCommitsDoneAtImport ) ++ { ++ s_bXwaylandCommitsDoneAtImport = false; ++ for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) ++ handle_done_commits_xwayland(server->ctx.get(), false, vblank_idx); ++ } + } + +