From c09e0511c8e684db89ebccc25077f354960651fe Mon Sep 17 00:00:00 2001 From: The412Banner Date: Mon, 12 Oct 2026 22:26:00 -0400 Subject: [PATCH] Sound: nothing that can block in the daemon's start-up; Steam's web compositing in software on open pairs - The S24 FE (US) daemon hung loading the plain AAudio sink from default.pa (its AAudio open blocks rather than fails), so it never finished starting. default.pa is back to DirectAudio alone inside .nofail; the fallbacks are loaded from the app once the daemon answers, each pactl bounded by its timeout (DirectAudio, then the plain sink with and without low latency). The guard waits 30 s and logs, with timestamps, how long the daemon took, whether its socket and pactl exist, and what it finally got. - With an open driver pair on a GPU that is not an Adreno, Steam's CEF composites in software (-cef-disable-gpu-compositing) with GPU raster kept: RADV on Xclipse showed stale, half-written tiles, as #570 saw on PowerVR. The session log says so. --- .../launcher/audio/PulseAudioComponent.java | 35 +++++++++++-------- .../launcher/session/SessionService.kt | 4 +++ .../overlay/usr/local/bin/droiddeck-session | 5 +++ 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/droiddeck/launcher/audio/PulseAudioComponent.java b/app/src/main/java/com/droiddeck/launcher/audio/PulseAudioComponent.java index 754080b27..a66c491a2 100644 --- a/app/src/main/java/com/droiddeck/launcher/audio/PulseAudioComponent.java +++ b/app/src/main/java/com/droiddeck/launcher/audio/PulseAudioComponent.java @@ -138,16 +138,6 @@ public class PulseAudioComponent extends SessionPart { // AAudio sink in its place. config.add(".nofail"); config.add("load-module module-directaudio-native-sink sink_name=DirectAudio performance_mode=1 adaptive=1 decay=0 limiter=1 watchdog=1 volume=1.0"); - if (!com.droiddeck.launcher.core.DeviceSupport.INSTANCE.adreno()) { - // Not an Adreno: the daemon makes its own fallback, without waiting for pactl. A Galaxy - // S24 FE's AAudio refuses DirectAudio's low-latency stream (AAUDIO_ERROR_DISCONNECTED), - // so the plain AAudio sink, without low latency, is loaded as a default underneath it. - // (Loading DirectAudio a second time in the same daemon after its failed first load - // hung the daemon's start-up on that phone, so it is not retried here.) The last - // set-default-sink that names a sink which exists wins. - config.add("load-module module-aaudio-sink sink_name=AAudioSink performance_mode=0 adaptive=1 volume=1.0"); - config.add("set-default-sink AAudioSink"); - } config.add("set-default-sink DirectAudio"); config.add(".fail"); if (micFifoPath != null && !micFifoPath.isEmpty()) { @@ -193,15 +183,24 @@ public class PulseAudioComponent extends SessionPart { */ private void ensureClientSink(java.io.PrintWriter out) { try { - long deadline = System.currentTimeMillis() + 15000L; + // Nothing that may block (a sink whose AAudio open hangs: a Galaxy S24 FE (US) hung the + // daemon's start-up loading one from default.pa) is loaded by the daemon itself; the + // fallbacks are loaded here, after it is up, each pactl call bounded by its timeout. + long started = System.currentTimeMillis(); + long deadline = started + 30000L; String sinks = null; + boolean socketSeen = false; while (System.currentTimeMillis() < deadline && pid > 1) { - if (socket().exists() && (sinks = pactl("list", "short", "sinks")) != null) break; + socketSeen |= socket().exists(); + if (socketSeen && (sinks = pactl("list", "short", "sinks")) != null) break; try { Thread.sleep(250L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } + note(out, "client sink: daemon " + (sinks != null ? "answered" : "did not answer") + " after " + + (System.currentTimeMillis() - started) + " ms (socket " + (socketSeen ? "present" : "never appeared") + ", pactl " + + (new File(workingDir, "pactl").isFile() ? "present" : "MISSING") + ", daemon pid " + pid + ")"); String line; if (sinks == null) { - line = "== client sink: the daemon did not answer in 15 s; no fallback attempted"; + line = "== client sink: the daemon did not answer in 30 s; no fallback attempted"; } else if (sinks.contains("\tDirectAudio\t") || sinks.startsWith("DirectAudio\t") || sinks.contains("\tDirectAudio\n") || sinks.matches("(?s).*\\bDirectAudio\\b.*")) { line = "== client sink: DirectAudio (module-directaudio-native-sink)"; } else { @@ -215,7 +214,8 @@ public class PulseAudioComponent extends SessionPart { "adaptive=1", "decay=0", "limiter=1", "watchdog=1", "volume=1.0") != null && pactl("set-default-sink", "DirectAudio") != null) { loadedAs = "DirectAudio on attempt " + attempt; - } else if (pactl("load-module", "module-aaudio-sink", "sink_name=AAudioSink", "performance_mode=1", "adaptive=1", "volume=1.0") != null + } else if (pactl("load-module", "module-aaudio-sink", "sink_name=AAudioSink", + "performance_mode=" + (attempt == 1 ? "1" : "0"), "adaptive=1", "volume=1.0") != null && pactl("set-default-sink", "AAudioSink") != null) { loadedAs = "the plain AAudio sink (AAudioSink) on attempt " + attempt; } @@ -231,6 +231,13 @@ public class PulseAudioComponent extends SessionPart { } } + /** A timestamped line in the session's audio log. */ + private void note(java.io.PrintWriter out, String what) { + String line = "== " + new java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.US).format(new java.util.Date()) + " " + what; + Log.i(TAG, line); + if (out != null) synchronized (out) { out.println(line); out.flush(); } + } + /** Runs pactl against this daemon; its stdout, or null when it failed or timed out. */ private String pactl(String... args) { File pactl = new File(workingDir, "pactl"); diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index 9fbbf64a1..efa56ce65 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -685,6 +685,10 @@ class SessionService : Service() { } } if (!bridge && com.droiddeck.launcher.core.DeviceSupport.openDriverActive(this)) { + // CEF's accelerated shared-texture output showed stale, half-written tiles on RADV + // (Xclipse), as #570 found on PowerVR: web compositing in software, GPU raster kept. + guest.add("BL_STEAM_SOFT_COMPOSITING=1") + Log.i(TAG, "steam: software web compositing for the open driver pair (GPU raster kept)") LinuxRuntime.gpuStandIn(this)?.takeIf { it == LinuxRuntime.MALI_DEVICE }?.let { guest.add("BL_DRM_STANDIN=$it") } } // Turnip's own debug switches, for the runtime's driver and everything on it. The file in diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session index f7d5ad96d..ad349d23b 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session @@ -885,6 +885,11 @@ BUSCONF # and the selected game driver still use the system GPU. bl_steam_presentation=(-cef-disable-gpu-compositing) echo "== steam: system PowerVR uses OpenGL/DRI3 through Zink with software CEF compositing" + elif [ "${BL_STEAM_SOFT_COMPOSITING:-}" = 1 ]; then + # An open driver pair on a GPU that is not an Adreno (RADV on Xclipse, PanVK on Mali): CEF's + # accelerated shared-texture output showed stale tiles there; raster stays on the GPU. + bl_steam_presentation=(-cef-disable-gpu-compositing) + echo "== steam: open driver pair - software CEF compositing, GPU raster" fi # The client's CEF flags for this presentation; the UI choice below may replace them. _bl_cef=(-cef-force-gpu "${bl_steam_presentation[@]}" -cef-use-gl=angle "-cef-use-angle=$bl_steam_angle")