perf(gamescope): ask X for the pointer only while a cursor is drawn

The gamepad-cursor port queried the X pointer on every vblank, a
blocking round trip to Xwayland on the paint thread. With no cursor
image the position comes from wlserver as upstream; while the cursor is
hidden for inactivity X is asked every 50 ms. The realtime-queue comment
now says it is a no-op on KGSL Turnip.
This commit is contained in:
maxjivi05 committed 2026-09-29 18:44:32 -04:00
1 parent a3f4c911e5
commit 736b7bed5e
2 files changed
+59 -35

No files matched your search

+5 -2
View File
@@ -14,8 +14,11 @@ library list, before anything is published.
- `0020-color-p3-red-is-wide-gamut.patch` - Armada, verbatim.
- `0100-realtime-queue-and-gamepad-cursor.patch` - this app, two of Armada's ported by hand onto
3.16.29: realtime-priority Vulkan queues on request (`GAMESCOPE_FORCE_VULKAN_REALTIME=1`)
without CAP_SYS_NICE, which proot can never have; and the gamepad-driven cursor sprite following
the X pointer that XTest moves (it sat frozen).
without CAP_SYS_NICE, which proot can never have (a no-op on KGSL Turnip, which has a single
submit-queue priority); and the gamepad-driven cursor sprite following the X pointer that XTest
moves (it sat frozen). The X pointer is asked for only while a cursor image is drawn - every
vblank while shown, every 50 ms while hidden for inactivity - since each ask is a blocking round
trip to Xwayland on the paint thread; with no image, wlserver's position is used as upstream does.
- `0110-wayland-backend-touch.patch` - this app: the nested Wayland backend bound only the host's
pointer and keyboard, so a finger on the phone's screen never reached Steam. It now binds
`wl_touch` too and hands each finger to wlserver's touch path (`wlserver_touchdown` / `motion` /
@@ -7,8 +7,8 @@ index 54e4efc..024af02 100644
float queuePriorities = 1.0f;
+ // Without CAP_SYS_NICE gamescope never asks for realtime-priority queues. Under proot on
+ // Android there is no capability to have, but the driver may still grant the priority (Turnip
+ // does on Adreno), and the existing fallback below covers a driver that refuses. Opt in with
+ // Android there is no capability to have; the fallback below covers a driver that refuses. KGSL
+ // Turnip accepts but ignores it (one submit-queue priority, contexts created alike). Opt in with
+ // GAMESCOPE_FORCE_VULKAN_REALTIME=1. (Armada's --force-vulkan-realtime, as an environment switch.)
+ const char *pszForceRealtime = getenv( "GAMESCOPE_FORCE_VULKAN_REALTIME" );
+ const bool useRealtimeVulkanQueue = gamescope::Process::HasCapSysNice() || ( pszForceRealtime && atoi( pszForceRealtime ) != 0 );
@@ -52,27 +52,10 @@ index 54e4efc..024af02 100644
fprintf(stderr, "vkCreateDevice failed with a high-priority queue (compute). Falling back to regular priority (all).\n");
queueCreateInfos[0].pNext = nullptr;
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 18b799e..16f7846 100644
index 10990b8..08afbc4 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -2020,23 +2020,41 @@ MouseCursor::MouseCursor(xwayland_ctx_t *ctx)
void MouseCursor::UpdatePosition()
{
+ // The gamepad-driven cursor is moved through XTest, which moves the X pointer and nothing
+ // else: wlserver's copy of the position never changes, so the sprite sat frozen while the
+ // pointer moved. Ask X where the pointer is. (Armada 0001, on 3.16.29.)
+ Window root_ret, child_ret;
+ int root_x = m_x, root_y = m_y, win_x = 0, win_y = 0;
+ unsigned int mask;
+ XQueryPointer( m_ctx->dpy, DefaultRootWindow( m_ctx->dpy ),
+ &root_ret, &child_ret,
+ &root_x, &root_y,
+ &win_x, &win_y,
+ &mask );
+
wlserver_lock();
struct wlr_pointer_constraint_v1 *pConstraint = wlserver.GetCursorConstraint();
@@ -2025,18 +2025,53 @@ void MouseCursor::UpdatePosition()
if ( pConstraint && pConstraint->current.cursor_hint.enabled )
{
m_bConstrained = true;
@@ -84,23 +67,61 @@ index 18b799e..16f7846 100644
}
- else
+ m_bConstrained = false;
+ wlserver_unlock();
+ if ( root_x != m_x || root_y != m_y )
+ // Nothing to draw (the gamepad UI, most games): the position only feeds focus and zoom, and
+ // wlserver's copy is what stock gamescope uses for those. No round trip to X.
+ if ( m_imageEmpty )
{
- m_bConstrained = false;
-
- m_x = wlserver.mouse_surface_cursorx;
- m_y = wlserver.mouse_surface_cursory;
m_x = wlserver.mouse_surface_cursorx;
m_y = wlserver.mouse_surface_cursory;
+ wlserver_unlock();
+ return;
}
wlserver_unlock();
+
+ // The gamepad-driven cursor is moved through XTest, which moves the X pointer and nothing
+ // else: wlserver's copy of the position never changes, so the sprite sat frozen while the
+ // pointer moved. Ask X where the pointer is. (Armada 0001, on 3.16.29.) That is a blocking
+ // round trip to Xwayland on the paint thread, which waits while Xwayland is busy drawing the
+ // client's interface: once per vblank only while the sprite is shown, and every 50 ms while
+ // it is hidden for inactivity - enough to bring it back within a few frames of a move.
+ if ( wlserver.bCursorHidden )
+ {
+ const uint64_t now = get_time_in_nanos();
+ if ( now - m_ulLastPointerQuery < 50'000'000ul )
+ return;
+ m_ulLastPointerQuery = now;
+ }
+ Window root_ret, child_ret;
+ int root_x = m_x, root_y = m_y, win_x = 0, win_y = 0;
+ unsigned int mask;
+ XQueryPointer( m_ctx->dpy, DefaultRootWindow( m_ctx->dpy ),
+ &root_ret, &child_ret,
+ &root_x, &root_y,
+ &win_x, &win_y,
+ &mask );
+
+ if ( root_x != m_x || root_y != m_y )
+ {
+ m_x = root_x;
+ m_y = root_y;
+ if ( !m_imageEmpty )
+ {
+ wlserver.ulLastMovedCursorTime = get_time_in_nanos();
+ wlserver.bCursorHidden = !wlserver.bCursorHasImage;
+ hasRepaintNonBasePlane = true;
+ }
}
- wlserver_unlock();
+ wlserver.ulLastMovedCursorTime = get_time_in_nanos();
+ wlserver.bCursorHidden = !wlserver.bCursorHasImage;
+ hasRepaintNonBasePlane = true;
+ }
}
void MouseCursor::checkSuspension()
diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp
index c428784..bd62fd1 100644
--- a/src/steamcompmgr.hpp
+++ b/src/steamcompmgr.hpp
@@ -109,6 +109,7 @@ private:
int m_x = 0, m_y = 0;
bool m_bConstrained = false;
+ uint64_t m_ulLastPointerQuery = 0;
int m_hotspotX = 0, m_hotspotY = 0;
gamescope::OwningRc<CVulkanTexture> m_texture;