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.
This commit is contained in:
The412Banner committed 2026-10-28 00:50:21 +00:00
1 parent 4ef6b53eb0
commit f7cebee7ee
3 files changed
+278

No files matched your search

+18
View File
@@ -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.
@@ -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<CommitDoneList_t> &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 <memory>
#include <optional>
#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<CommitDoneList_t> &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<CommitDoneList_t> 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<CommitDoneList_t> g_steamcompmgr_xdg_done_commits = std::make_shared<CommitDoneList_t>();
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<std::mutex> lock( ctx->doneCommits.listCommitsDoneLock );
+ std::lock_guard<std::mutex> 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<std::mutex> lock( g_steamcompmgr_xdg_done_commits.listCommitsDoneLock );
+ std::lock_guard<std::mutex> 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<bool> 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<bool> 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<CommitDoneList_t> &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<MouseCursor> cursor;
- CommitDoneList_t doneCommits;
+ std::shared_ptr<CommitDoneList_t> doneCommits = std::make_shared<CommitDoneList_t>();
double accum_x = 0.0;
double accum_y = 0.0;
@@ -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<bool> 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<bool> 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<CommitDoneList_t> &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<CommitDoneList_t> &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<CommitDoneList_t> &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);
+ }
}