fix: draw the gamescope cursor only when that option is on

The extra plane costs frames on a phone, so compositing stays off unless Performance asks for it.
This commit is contained in:
0x1Nawaf authored and Kurt Himebauch committed 2026-10-23 13:30:42 -04:00
1 parent b76f13f7ac
commit 7c51e6e904
8 files changed
+41 -10

No files matched your search

@@ -155,6 +155,7 @@ class FrontEndActivity : ComponentActivity() {
private var gpuClockPin by mutableStateOf(false)
private var prootNoSeccomp by mutableStateOf(false)
private var prootFastPath by mutableStateOf(true)
private var cursorComposite by mutableStateOf(false)
private var guestHostname by mutableStateOf(SessionPrefs.DEFAULT_GUEST_HOSTNAME)
private var phantomWarning by mutableStateOf<String?>(null)
private var phantomProcessStatus by mutableStateOf(PhantomProcessStatus.NOT_APPLICABLE)
@@ -1771,7 +1772,8 @@ class FrontEndActivity : ComponentActivity() {
fsyncFirst = fsyncFirst,
gamescopeRealtime = gamescopeRealtime,
gpuClockPin = gpuClockPin,
prootNoSeccomp = prootNoSeccomp, prootFastPath = prootFastPath, guestHostname = guestHostname, phantomWarning = phantomWarning,
prootNoSeccomp = prootNoSeccomp, prootFastPath = prootFastPath, cursorComposite = cursorComposite,
guestHostname = guestHostname, phantomWarning = phantomWarning,
onClientOverride = { on -> SessionPrefs.setClientCpusOverride(this, on); clientOverride = on },
onTuSysmem = { on -> SessionPrefs.setTuSysmem(this, on); tuSysmem = on },
onZinkLazy = { on -> SessionPrefs.setZinkLazy(this, on); zinkLazy = on },
@@ -1785,6 +1787,7 @@ class FrontEndActivity : ComponentActivity() {
onGpuClockPin = { on -> SessionPrefs.setGpuClockPin(this, on); gpuClockPin = on },
onProotNoSeccomp = { on -> SessionPrefs.setProotNoSeccomp(this, on); prootNoSeccomp = on },
onProotFastPath = { on -> SessionPrefs.setProotFastPath(this, on); prootFastPath = on },
onCursorComposite = { on -> SessionPrefs.setCursorComposite(this, on); cursorComposite = on },
onGuestHostname = { name -> SessionPrefs.setGuestHostname(this, name) },
onClientCore = { core, on ->
clientCores = if (on) clientCores + core else clientCores - core
@@ -1967,6 +1970,7 @@ class FrontEndActivity : ComponentActivity() {
gpuClockPin = SessionPrefs.gpuClockPin(this)
prootNoSeccomp = SessionPrefs.prootNoSeccomp(this)
prootFastPath = SessionPrefs.prootFastPath(this)
cursorComposite = SessionPrefs.cursorComposite(this)
guestHostname = SessionPrefs.guestHostname(this)
refreshPhantomStatus()
}
@@ -472,6 +472,13 @@ object SessionPrefs {
prefs(context).edit().putBoolean("prootFastPath", on).apply()
}
/** gamescope draws the cursor itself. Off by default: on a phone that plane costs frames. */
fun cursorComposite(context: Context): Boolean = prefs(context).getBoolean("cursorComposite", false)
fun setCursorComposite(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("cursorComposite", on).apply()
}
const val DEFAULT_GUEST_HOSTNAME = "DroidDeck"
@JvmStatic
@@ -720,6 +720,7 @@ class SessionService : Service() {
guest.add("BL_SYNC_FALLBACK=" + (if (SessionPrefs.syncFallback(this)) "1" else "0"))
guest.add("BL_FSYNC_FIRST=" + (if (SessionPrefs.fsyncFirst(this)) "1" else "0"))
guest.add("BL_GAMESCOPE_REALTIME=" + (if (SessionPrefs.gamescopeRealtime(this)) "1" else "0"))
if (SessionPrefs.cursorComposite(this)) guest.add("BL_CURSOR_COMPOSITE=1")
if (com.droiddeck.launcher.core.ZramSupport.supported()) guest.add("BL_ZRAM=1")
guest.add("BANNER_AUDIO_DIRECT_DECAY=0")
val extra = extraEnv() + com.droiddeck.launcher.agent.AgentEnv.beginSession(this)
@@ -20,7 +20,7 @@ object SettingsReset {
"gpuDriverMode", "androidDriver", "linuxDriver", "linuxDriver.steam", "linuxDriver.desktop",
"tuSysmem", "glThread", "noGlError", "zinkLazy", "mangoapp", "gpuClockPin", "gamescopeRealtime",
"directAudio", "mic", "wifiDiscovery", "clientCpusOverride", "clientCpus", "gameCpus",
"noXalia", "fastSync", "fsyncFirst", "syncFallback", "prootNoSeccomp", "prootFastPath",
"noXalia", "fastSync", "fsyncFirst", "syncFallback", "prootNoSeccomp", "prootFastPath", "cursorComposite",
"guestHostname", "fexPreset", "forceSsbs", "logs", "storageDiagnostics",
"upscaler", "upscaleSharpness", "screenEffects", "textureAnisotropy", "textureLodBias",
"steamDownloadsInBackground", "steam_offline", "bootMovie", "bootMovieSound", "bootMovieVolume", "bootMovieSeconds",
@@ -46,6 +46,7 @@ fun PerformancePage(
gpuClockPin: Boolean,
prootNoSeccomp: Boolean,
prootFastPath: Boolean,
cursorComposite: Boolean,
guestHostname: String,
phantomWarning: String?,
onClientOverride: (Boolean) -> Unit,
@@ -61,6 +62,7 @@ fun PerformancePage(
onGpuClockPin: (Boolean) -> Unit,
onProotNoSeccomp: (Boolean) -> Unit,
onProotFastPath: (Boolean) -> Unit,
onCursorComposite: (Boolean) -> Unit,
onGuestHostname: (String) -> Unit,
onClientCore: (Int, Boolean) -> Unit,
onGameCore: (Int, Boolean) -> Unit,
@@ -158,6 +160,11 @@ fun PerformancePage(
else stringResource(R.string.perf_fastpath_hint),
prootFastPath && !prootNoSeccomp, enabled = !prootNoSeccomp, onChange = onProotFastPath,
)
ToggleRow(
host, "cursorcomposite", stringResource(R.string.perf_cursor_composite),
stringResource(R.string.perf_cursor_composite_hint),
cursorComposite, onChange = onCursorComposite,
)
}
SettingsGroup(stringResource(R.string.perf_identity)) {
var draft by remember(guestHostname) { mutableStateOf(guestHostname) }
+2
View File
@@ -568,6 +568,8 @@
<string name="perf_fastpath">proot fast path</string>
<string name="perf_fastpath_needs_seccomp">Needs proot\'s seccomp filter.</string>
<string name="perf_fastpath_hint">Faster file lookups and less stutter. Turn off if a program can\'t find its files.</string>
<string name="perf_cursor_composite">Draw the cursor inside gamescope</string>
<string name="perf_cursor_composite_hint">Gamescope paints the pointer itself instead of handing it to Android. Turn on for a controller-driven cursor. Off is faster on a phone. Applies next session.</string>
<string name="perf_identity">Session identity</string>
<string name="perf_hostname">Host name</string>
<string name="perf_hostname_hint">The name Steam sees for this device. Blank restores %1$s.</string>
@@ -227,7 +227,9 @@ if [ -z "${BL_INSIDE:-}" ]; then
echo "== gamescope: games at ${nested}x${height} stretched to the ${width}x${height} output"
fi
fi
if [ "$mode" = steam ]; then
# Off unless Performance asks for it. Compositing the cursor here adds a second
# plane on the phone's compositor; a controller-driven cursor is the reason to turn it on.
if [ "$mode" = steam ] && [ "${BL_CURSOR_COMPOSITE:-0}" = 1 ]; then
export gamescope_cursor_composite=2
fi
args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$nested" -h "$height")
+15 -7
View File
@@ -1,4 +1,4 @@
"""The XTest cursor path is enabled only for Steam sessions."""
"""Gamescope composites the cursor only when a Steam session asks for it."""
import os
from pathlib import Path
import re
@@ -8,29 +8,37 @@ import unittest
SESSION = Path(__file__).resolve().parents[1] / 'linuxfs/overlay/usr/local/bin/droiddeck-session'
SOURCE = SESSION.read_text()
MATCH = re.search(
r'^ if \[ "\$mode" = steam \]; then\n.*?^ fi\n', SOURCE, re.M | re.S)
r'^ if \[ "\$mode" = steam \] && \[ "\$\{BL_CURSOR_COMPOSITE:-0\}" = 1 \]; then\n.*?^ fi\n',
SOURCE, re.M | re.S)
assert MATCH is not None
CURSOR_SETUP = MATCH.group(0)
class SessionCursorRoutingTest(unittest.TestCase):
def run_setup(self, mode, initial=None):
def run_setup(self, mode, initial=None, composite=None):
env = dict(os.environ, mode=mode)
if initial is not None:
env['gamescope_cursor_composite'] = initial
else:
env.pop('gamescope_cursor_composite', None)
if composite is not None:
env['BL_CURSOR_COMPOSITE'] = composite
else:
env.pop('BL_CURSOR_COMPOSITE', None)
result = subprocess.run(
['bash', '-c', CURSOR_SETUP + 'printf "%s\n" "${gamescope_cursor_composite-}"'],
env=env, text=True, capture_output=True, check=True)
return result.stdout.strip()
def test_steam_enables_manual_composition_for_the_nested_cursor(self):
self.assertEqual('2', self.run_setup('steam'))
def test_steam_leaves_the_cursor_with_the_host_unless_asked(self):
self.assertEqual('', self.run_setup('steam'))
def test_steam_composites_the_cursor_when_the_setting_is_on(self):
self.assertEqual('2', self.run_setup('steam', composite='1'))
def test_other_sessions_keep_their_existing_cursor_composition(self):
self.assertEqual('', self.run_setup('desktop'))
self.assertEqual('1', self.run_setup('run', '1'))
self.assertEqual('', self.run_setup('desktop', composite='1'))
self.assertEqual('1', self.run_setup('run', '1', composite='1'))
if __name__ == '__main__':