Non-Adreno round 3: bridge images, Venus GL, shared buffers, Steam GPU drawing

- vkbridge: the memory model skips lazily allocated types. Mali-G615 lists
  DEVICE_LOCAL|LAZILY_ALLOCATED first; taken as the device-local type it left
  images with no memory type (self-test "image clear/draw: setup" FAIL,
  gamescope "No compatible image memory type").
- Session: a bridge self-test that fails images or draws falls back to the
  runtime's driver pair for that session (BL_VKBRIDGE_FALLBACK_ICD); Setup
  and the graphics report say so.
- Venus: the Mali compatibility layer runs in every process but gamescope
  (DROIDDECK_MALI_COMPAT=1), so Zink in Steam and Xwayland gets
  nullDescriptor; null texel-buffer views get stand-ins too. Without
  nullDescriptor the session's GL goes to llvmpipe.
- gamescope 0129: a refused compute pipeline is named with its SPIR-V
  capabilities.
- Compositor: a buffer on two surfaces at once (gamescope's planes while a
  notification is up) is released only when the last one lets go.
- Open driver pairs: processes.txt timeline for the first three minutes;
  Setup "Steam interface: GPU drawing" turns off software web compositing.
This commit is contained in:
The412Banner committed 2026-10-13 08:44:45 -04:00
1 parent 7464bd7697
commit 55b30fbf1d
17 files changed
+282 -18

No files matched your search

+21 -1
View File
@@ -420,6 +420,19 @@ static void send_buffer_release_at(struct wl_resource *buffer, int line) {
if (!buffer_release_blocked(buffer)) { buf_trace_release(buffer, line); wl_buffer_send_release(buffer); }
}
/* Whether a surface other than `except` is showing this buffer right now. gamescope's Wayland
* backend moves one buffer between its planes (the game's frame goes from the window to a
* subsurface above a black backing while a Steam notification is up): releasing it when the first
* surface lets go handed gamescope a buffer still on screen ("Compositor released us but we were
* not acquired"). Only the last surface to let go releases it. */
static int buffer_shown_elsewhere(struct wl_resource *buffer, const struct surface *except) {
struct surface *o;
if (!buffer) return 0;
wl_list_for_each(o, &g_surfaces, link)
if (o != except && o->dmabuf == buffer) return 1;
return 0;
}
static void pending_release_free(struct pending_release *pr) {
if (pr->surface && pr->surface->releases_pending > 0) pr->surface->releases_pending--;
wl_list_remove(&pr->destroy.link);
@@ -459,6 +472,11 @@ static int on_release_timer(int fd, uint32_t mask, void *data) {
if (read(fd, &expirations, sizeof(expirations)) < 0 && errno != EAGAIN) return 0;
wl_list_for_each_safe(pr, tmp, &g_pending_releases, link) {
if (pr->at_ns <= now && !buffer_release_blocked(pr->buffer)) {
/* Attached again since (to its surface or another): that surface releases it later. */
if (buffer_shown_elsewhere(pr->buffer, NULL)) {
pending_release_free(pr);
continue;
}
send_buffer_release(pr->buffer);
perf_note_release(pr->since_ns);
pending_release_free(pr);
@@ -735,7 +753,9 @@ static void drop_dmabuf(struct surface *s, int paced) {
wl_list_remove(&s->dmabuf_destroy.link);
/* A buffer on the zero-copy layer is the display's until SurfaceFlinger says otherwise:
* ahb_swapchain.c releases it then. */
if (!ahb_swapchain_defer_release(s->dmabuf_buf, s->dmabuf, s, paced)) {
if (buffer_shown_elsewhere(s->dmabuf, s)) {
/* still on another surface: released when that one lets go */
} else if (!ahb_swapchain_defer_release(s->dmabuf_buf, s->dmabuf, s, paced)) {
if (paced) release_buffer(s, s->dmabuf, now_ns());
else send_buffer_release(s->dmabuf);
}
@@ -370,6 +370,8 @@ class MainActivity : ComponentActivity() {
private var logsEnabled by mutableStateOf(true)
private var agentCommands by mutableStateOf(false)
private var graphicsLogs by mutableStateOf(false)
private var steamGpuDrawing by mutableStateOf(false)
private var bridgeFellBack by mutableStateOf(false)
private var showRoms by mutableStateOf(false)
private var homeAppSelected by mutableStateOf(false)
private var homeScreenEnabled by mutableStateOf(false)
@@ -565,7 +567,7 @@ class MainActivity : ComponentActivity() {
busy = busy, stage = stage, percent = percent,
desktopInstalled = desktopInstalled, desktopPresent = desktopPresent,
offlineAccount = offlineAccount, offline = offline,
frameGenLabel = frameGenLabel, romsDir = romsDir, logsEnabled = logsEnabled, agentCommands = agentCommands, graphicsLogs = graphicsLogs,
frameGenLabel = frameGenLabel, romsDir = romsDir, logsEnabled = logsEnabled, agentCommands = agentCommands, graphicsLogs = graphicsLogs, steamGpuDrawing = steamGpuDrawing, bridgeFellBack = bridgeFellBack,
steamGames = steamGames, emulators = emulatorList, running = runningLabel,
frameGen = FrameGen.mode(this),
lossless = lossless,
@@ -712,6 +714,10 @@ class MainActivity : ComponentActivity() {
SessionPrefs.setLogsEnabled(this, !SessionPrefs.logsEnabled(this))
logsEnabled = SessionPrefs.logsEnabled(this)
},
onSteamGpuDrawing = {
SessionPrefs.setSteamGpuCompositing(this, !SessionPrefs.steamGpuCompositing(this))
steamGpuDrawing = SessionPrefs.steamGpuCompositing(this)
},
onGraphicsLogs = {
SessionPrefs.setDetailedGraphicsLogs(this, !SessionPrefs.detailedGraphicsLogs(this))
graphicsLogs = SessionPrefs.detailedGraphicsLogs(this)
@@ -1615,6 +1621,8 @@ class MainActivity : ComponentActivity() {
logsEnabled = SessionPrefs.logsEnabled(this)
agentCommands = com.droiddeck.launcher.agent.AgentAccess.commandsEnabled(this)
graphicsLogs = SessionPrefs.detailedGraphicsLogs(this)
steamGpuDrawing = SessionPrefs.steamGpuCompositing(this)
bridgeFellBack = SessionPrefs.bridgeFellBack(this)
runningLabel = if (SessionState.running) when (SessionState.mode) {
SessionService.MODE_DESKTOP -> getString(R.string.rail_desktop)
SessionService.MODE_RUN -> Library.nameForProgram(SessionState.program)
@@ -41,8 +41,12 @@ object GraphicsReport {
/** Per-game graphics logs and the guest's Vulkan summary: on for a non-Adreno GPU, or by Setup's switch. */
fun detailed(context: Context): Boolean = !DeviceSupport.adreno() || SessionPrefs.detailedGraphicsLogs(context)
/** For finish(), which runs from the session's end without a context of its own. */
@Volatile private var appContext: Context? = null
/** Writes the start of the report; never throws. */
fun begin(context: Context, dir: File, guest: List<String>) {
appContext = context.applicationContext
runCatching { File(dir, FILE).writeText(start(context, guest)) }
.onFailure { android.util.Log.w("GraphicsReport", "could not write $FILE", it) }
}
@@ -177,10 +181,13 @@ object GraphicsReport {
val guestLines = File(dir, "session.log").takeIf { it.isFile }?.useLines { seq ->
seq.filter { GUEST.containsMatchIn(it) }.take(200).toList()
}.orEmpty()
val fellBack = File(dir, "vkbridge-fallback").takeIf { it.isFile }?.readText()?.trim()
runCatching { SessionPrefs.setBridgeFellBack(appContext ?: return@runCatching, fellBack != null) }
val limits = File(dir, "limits.txt").takeIf { it.isFile }?.readText()?.trim()
val audio = File(dir, "audio.log").takeIf { it.isFile }?.readText().orEmpty()
report.appendText(buildString {
limits?.let { appendLine("open files limit: $it") }
fellBack?.let { appendLine("!! MALI BRIDGE FELL BACK: the self-test failed images or draws; the session ran on $it") }
if (audio.contains("AAUDIO_ERROR_DISCONNECTED")) {
appendLine("!! SOUND: Android refused the audio output (AAUDIO_ERROR_DISCONNECTED) - rebooting the phone usually fixes this")
}
@@ -515,12 +515,29 @@ object SessionPrefs {
*/
fun venus(context: Context): Boolean = prefs(context).getBoolean("venus", false)
/** The last session's Mali bridge self-test failed and it ran on the pair instead (Setup says so). */
fun bridgeFellBack(context: Context): Boolean = prefs(context).getBoolean("bridgeFellBack", false)
fun setBridgeFellBack(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("bridgeFellBack", on).apply()
}
/**
* Setup's "Steam interface: GPU drawing": with an open driver pair the Steam client composites
* its web views in software (stale tiles on RADV/Xclipse); this turns GPU compositing back on.
*/
fun steamGpuCompositing(context: Context): Boolean = prefs(context).getBoolean("steamGpuCompositing", false)
fun setSteamGpuCompositing(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("steamGpuCompositing", on).apply()
}
fun setVenus(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("venus", on).apply()
}
fun setVkBridge(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("vkBridge", on).apply()
prefs(context).edit().putBoolean("vkBridge", on).putBoolean("bridgeFellBack", false).apply()
}
fun setZinkLazy(context: Context, on: Boolean) {
@@ -700,10 +700,15 @@ 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")
// Setup's "Steam interface: GPU drawing" turns GPU compositing back on (Xclipse users).
if (SessionPrefs.steamGpuCompositing(this)) {
Log.i(TAG, "steam: GPU web compositing (Setup's \"Steam interface: GPU drawing\")")
} else {
guest.add("BL_STEAM_SOFT_COMPOSITING=1")
Log.i(TAG, "steam: software web compositing for the open driver pair (GPU raster kept)")
}
// droiddeck-game-env leaves vkd3d-proton to report the driver's own caps on this path.
guest.add("BL_OPEN_DRIVER=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
@@ -786,6 +791,12 @@ class SessionService : Service() {
components.add(it)
}
guest.add("BL_VKBRIDGE=1")
if (SessionPrefs.steamGpuCompositing(this)) guest.add("BL_STEAM_GPU_COMPOSITING=1")
// If the bridge's self-test fails images or draws, the session script falls back to the
// Mali driver pair set for the runtime (its ICD here), rather than crashing gamescope.
SessionPrefs.linuxDriver(this).takeIf { it.isNotEmpty() }
?.let { runCatching { LinuxVulkanDriver.resolveIcdPath(this, it) }.getOrNull() }
?.let { guest.add("BL_VKBRIDGE_FALLBACK_ICD=$it") }
guest.add("VKBRIDGE_SOCKET=" + socket.path)
guest.add("VKBRIDGE_CLIENT_LOG=" + File(sessionDir, "vkbridge-client.log").path)
// The render node gamescope sees is Mali's device standing in (LinuxRuntime.bindGpuNode):
@@ -89,6 +89,11 @@ class VenusComponent(private val logFile: File?) : SessionPart() {
"VTEST_SOCKET_NAME=" + socket(context).path,
// Xwayland has no DRI3 without a DRM node, so swapchains present through shared memory.
"MESA_VK_WSI_DEBUG=sw",
// The Mali compatibility layer in every process but gamescope, not only Wine: Zink
// (Steam's GL, Xwayland's glamor) requires robustness2's nullDescriptor, which the
// layer stands in for. The session script checks it and falls back to llvmpipe.
"DROIDDECK_MALI_COMPAT=1",
"BL_VENUS=1",
)
}
@@ -100,6 +100,8 @@ class FrontEndState(
/** Setup's Debugging tools toggle (AgentAccess): off unless the user turns it on. */
val agentCommands: Boolean = false,
val graphicsLogs: Boolean = false,
val steamGpuDrawing: Boolean = false,
val bridgeFellBack: Boolean = false,
val steamGames: List<Library.SteamGame>,
val emulators: List<Library.Emulator>,
val running: String?,
@@ -213,6 +215,7 @@ class FrontEndActions(
val onLogs: () -> Unit,
val onAgentCommands: () -> Unit = {},
val onGraphicsLogs: () -> Unit = {},
val onSteamGpuDrawing: () -> Unit = {},
val onShareLogs: () -> Unit = {},
val onClearLogs: () -> Unit = {},
val onOffline: () -> Unit,
@@ -213,8 +213,10 @@ internal fun SetupPanel(
com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED -> stringResource(R.string.setup_gpu_detail, gpu.displayName(ctx), gpuName)
com.droiddeck.launcher.gpu.GpuInfo.Support.UNTESTED ->
if (!s.adreno && s.vkBridge && !com.droiddeck.launcher.core.DeviceSupport.bridgeUsable()) stringResource(R.string.setup_gpu_bridge_unusable, gpu.displayName(ctx))
else if (!s.adreno && s.vkBridge && s.bridgeFellBack) stringResource(R.string.setup_gpu_bridge_fell_back, gpu.displayName(ctx))
else stringResource(R.string.setup_gpu_untested_detail, gpu.displayName(ctx), gpu.supportText(ctx).replaceFirstChar { it.lowercase() })
else -> if (s.vkBridge && !com.droiddeck.launcher.core.DeviceSupport.bridgeUsable()) stringResource(R.string.setup_gpu_bridge_unusable, gpuName)
else if (s.vkBridge && s.bridgeFellBack) stringResource(R.string.setup_gpu_bridge_fell_back, gpuName)
else if (s.vkBridge) stringResource(R.string.setup_gpu_bridge_detail, gpuName)
else stringResource(R.string.setup_gpu_unsupported_detail, gpuName)
},
@@ -420,6 +422,7 @@ internal fun SetupPanel(
ToggleRow(host, "logs", stringResource(R.string.setup_logs), stringResource(R.string.setup_logs_hint), s.logsEnabled) { a.onLogs() }
ToggleRow(host, "agent-commands", stringResource(R.string.setup_agent_commands), null, s.agentCommands) { a.onAgentCommands() }
ToggleRow(host, "graphics-logs", stringResource(R.string.setup_graphics_logs), stringResource(R.string.setup_graphics_logs_hint), s.graphicsLogs) { a.onGraphicsLogs() }
if (!s.adreno) ToggleRow(host, "steam-gpu-drawing", stringResource(R.string.setup_steam_gpu_drawing), stringResource(R.string.setup_steam_gpu_drawing_hint), s.steamGpuDrawing) { a.onSteamGpuDrawing() }
ActionRow(stringResource(R.string.setup_latest_logs), stringResource(R.string.drawer_logs_hint), stringResource(R.string.drawer_share_logs), a.onShareLogs,
progress = com.droiddeck.launcher.session.SessionLogShare.progress)
ActionRow(stringResource(R.string.setup_saved_logs), stringResource(R.string.setup_saved_logs_hint, com.droiddeck.launcher.session.SessionPaths.KEEP_SESSIONS), stringResource(R.string.setup_clear_logs), a.onClearLogs)
+3
View File
@@ -680,6 +680,9 @@
<string name="setup_logs_hint">Saved after each session</string>
<string name="setup_agent_commands">Debugging tools</string>
<string name="setup_graphics_logs">Detailed graphics logs</string>
<string name="setup_steam_gpu_drawing">Steam interface: GPU drawing</string>
<string name="setup_steam_gpu_drawing_hint">With a Mali or Xclipse driver pair, Steam draws its pages in software (GPU drawing showed broken tiles). Turn on for a faster Steam interface if it looks right on this phone.</string>
<string name="setup_gpu_bridge_fell_back">The Mali bridge failed its self-test on %1$s last session (images or draws), so the session used the PanVK driver pair instead.</string>
<string name="setup_graphics_logs_hint">Each game\'s Proton, DXVK and driver logs in the session\'s logs. Always on when the GPU is not an Adreno.</string>
<string name="setup_latest_logs">Latest session logs</string>
<string name="setup_saved_logs">Saved session logs</string>
+4
View File
@@ -74,3 +74,7 @@ app's Wayland-hosted gamescope never reaches, or need a newer gamescope than the
import, so the screen cycled through the first few frames forever while Steam ran on behind them.
Only dma-buf textures are memoized now; a shm buffer is uploaded on every commit. Adreno never
takes this path, since its Xwayland hands over dma-bufs. Also what XWAYLAND_NO_GLAMOR sessions need.
- `0129-log-failing-compute-pipeline.patch`: when `vkCreateComputePipelines` fails, one extra line
per shader type names the pipeline (shader type, layer count, ycbcr/colorspace masks, fp16) and the
SPIR-V capabilities its shader declares. A Mali-G615 through Venus refused all 119 composite
pipelines with VK_ERROR_INITIALIZATION_FAILED and nothing said which feature it objected to.
@@ -0,0 +1,55 @@
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
index 42b2ec6..103baee 100644
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -994,6 +994,8 @@ bool CVulkanDevice::createPools()
return true;
}
+static std::string s_shaderCaps[SHADER_TYPE_COUNT];
+
bool CVulkanDevice::createShaders()
{
struct ShaderInfo_t
@@ -1040,6 +1042,25 @@ bool CVulkanDevice::createShaders()
.pCode = shaderInfos[i].spirv,
};
+ // The SPIR-V capabilities each shader declares, for the log when its pipeline is refused.
+ {
+ const uint32_t *pWords = shaderInfos[i].spirv;
+ size_t nWords = shaderInfos[i].size / sizeof(uint32_t);
+ std::string &sCaps = s_shaderCaps[i];
+ sCaps.clear();
+ for ( size_t w = 5; w < nWords; )
+ {
+ uint32_t uLen = pWords[w] >> 16;
+ if ( !uLen )
+ break;
+ if ( ( pWords[w] & 0xffffu ) == 17 /* OpCapability */ && w + 1 < nWords )
+ sCaps += " " + std::to_string( pWords[w + 1] );
+ else if ( ( pWords[w] & 0xffffu ) == 54 /* OpFunction: past the header */ )
+ break;
+ w += uLen;
+ }
+ }
+
VkResult res = vk.CreateShaderModule(device(), &shaderCreateInfo, nullptr, &m_shaderModules[i]);
if ( res != VK_SUCCESS )
{
@@ -1240,6 +1261,15 @@ VkPipeline CVulkanDevice::compilePipeline(uint32_t layerCount, uint32_t ycbcrMas
VkResult res = vk.CreateComputePipelines(device(), VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &result);
if (res != VK_SUCCESS) {
vk_errorf( res, "vkCreateComputePipelines failed" );
+ // Which pipeline, once per shader type: a vendor driver through Venus may refuse one variant only.
+ static uint32_t s_uLoggedTypes = 0;
+ if ( !( s_uLoggedTypes & ( 1u << ( (uint32_t)type & 31u ) ) ) )
+ {
+ s_uLoggedTypes |= 1u << ( (uint32_t)type & 31u );
+ vk_log.errorf( " failing compute pipeline: shader type %u, layers %u, ycbcr mask 0x%x, blur layers %u, colorspace mask 0x%x, output eotf %u, itm %u, fp16 %d, SPIR-V capabilities:%s",
+ (uint32_t)type, layerCount, ycbcrMask, blur_layer_count, colorspace_mask, (uint32_t)output_eotf, (uint32_t)itm_enable, m_bSupportsFp16 ? 1 : 0,
+ (uint32_t)type < SHADER_TYPE_COUNT ? s_shaderCaps[type].c_str() : "" );
+ }
return VK_NULL_HANDLE;
}
@@ -162,17 +162,50 @@ if [ "${BL_VKBRIDGE:-0}" = 1 ]; then
unset TU_DEBUG
if [ -z "${BL_INSIDE:-}" ]; then
step "vkbridge self-test"
timeout 60 /usr/local/bin/droiddeck-vkbridge-smoke 2>&1 | sed 's/^/== vkbridge: /'
bl_vkb_smoke=$(timeout 60 /usr/local/bin/droiddeck-vkbridge-smoke 2>&1)
printf '%s\n' "$bl_vkb_smoke" | sed 's/^/== vkbridge: /'
# Images or draws failing the self-test mean gamescope cannot start on the bridge (it
# asserted in CVulkanTexture::BInit): this session falls back to the Mali driver pair the
# app named, and says so (the app shows it in Setup).
if printf '%s\n' "$bl_vkb_smoke" | grep -qE '^FAIL (image clear|draw)' && [ -r "${BL_VKBRIDGE_FALLBACK_ICD:-}" ]; then
export VK_DRIVER_FILES=$BL_VKBRIDGE_FALLBACK_ICD VK_ICD_FILENAMES=$BL_VKBRIDGE_FALLBACK_ICD BL_VKBRIDGE=0
# What the app sets for a pair session (SessionService): the bridge already bound /dev/mali0
# as the stand-in render node, which is what PanVK reports too.
export BL_OPEN_DRIVER=1 BL_DRM_STANDIN=/dev/mali0
[ "${BL_STEAM_GPU_COMPOSITING:-0}" = 1 ] || export BL_STEAM_SOFT_COMPOSITING=1
echo "== vkbridge: self-test failed images or draws; this session uses the PanVK pair instead ($BL_VKBRIDGE_FALLBACK_ICD)"
[ -n "${BL_DEBUG_DIR:-}" ] && echo "$BL_VKBRIDGE_FALLBACK_ICD" > "$BL_DEBUG_DIR/vkbridge-fallback" 2>/dev/null
fi
unset bl_vkb_smoke
if command -v vulkaninfo >/dev/null 2>&1; then
timeout 30 vulkaninfo --summary 2>&1 | sed -n '/^Devices:/,$p' | head -40 | sed 's/^/== vulkaninfo: /'
fi
fi
echo "== vulkan driver: Mali bridge ($bl_vkb_icd, server at $VKBRIDGE_SOCKET)"
[ "${BL_VKBRIDGE:-0}" = 1 ] && echo "== vulkan driver: Mali bridge ($bl_vkb_icd, server at $VKBRIDGE_SOCKET)"
else
echo "== vulkan driver: Mali bridge requested but NOT usable (icd $bl_vkb_icd, socket ${VKBRIDGE_SOCKET:-unset}); this GPU will show a black screen"
fi
unset bl_vkb_icd
fi
# Mali (Venus): Steam's GL and Xwayland's glamor run on Zink, which refuses a device without
# robustness2's nullDescriptor (Mali has none; the Mali compatibility layer stands in for it). When
# the session's Vulkan still lacks it, GL goes to llvmpipe instead: slower, but Steam starts rather
# than sitting at 0% with "glXChooseVisual failed".
if [ "${BL_VENUS:-0}" = 1 ] && [ -z "${BL_INSIDE:-}" ] && command -v vulkaninfo >/dev/null 2>&1; then
if bl_vn_info=$(timeout 30 vulkaninfo 2>/dev/null); then
case $(printf '%s\n' "$bl_vn_info" | grep -m1 'nullDescriptor') in
*true*) echo "== venus: nullDescriptor present (Zink keeps the GPU)" ;;
*)
export GALLIUM_DRIVER=llvmpipe LIBGL_ALWAYS_SOFTWARE=1
unset MESA_LOADER_DRIVER_OVERRIDE LIBGL_KOPPER_DRI2
echo "== venus: no nullDescriptor on this device even with the Mali compatibility layer; Steam's GL and Xwayland use llvmpipe (software)"
;;
esac
else
echo "== venus: vulkaninfo failed or timed out; GL left on Zink"
fi
unset bl_vn_info
fi
# Detailed graphics logs (BL_GAME_LOGS=1: a GPU that is not an Adreno, or Setup's switch): the
# session's own view of Vulkan, with the driver exactly as exported above, once per session. In the
# background with a timeout, so a driver that hangs here never holds the session up.
@@ -213,6 +246,38 @@ nap() {
[ -n "${bl_nap_fd:-}" ] || exec {bl_nap_fd}<> <(:)
read -r -t "$1" -u "$bl_nap_fd" _ 2>/dev/null || :
}
# Which program a driver error came from: Mesa's lines ("radv/amdgpu: The CS has been rejected",
# "ZINK: vkQueueWaitIdle failed") carry no process name. For the first three minutes of an open
# driver pair's session, every process that appears goes into processes.txt with the session log's
# line count at that moment, so an error's line number in session.log points at what had just
# started. Pure bash over /proc (no execs: see nap).
watch_processes() {
local out=$BL_DEBUG_DIR/processes.txt log=$BL_DEBUG_DIR/session.log i p pid comm lines
local -a argv new
local -A seen=()
echo "# seconds session.log-lines pid name command line" > "$out" 2>/dev/null || return 0
for ((i = 0; i < 360; i++)); do
new=()
for p in /proc/[0-9]*; do
pid=${p#/proc/}
[ -n "${seen[$pid]:-}" ] && continue
seen[$pid]=1
read -r comm < "$p/comm" 2>/dev/null || continue
argv=()
mapfile -d '' -t argv < "$p/cmdline" 2>/dev/null
new+=("$pid"$'\t'"$comm"$'\t'"${argv[*]:0:8}")
done
if [ ${#new[@]} -gt 0 ]; then
lines=0
[ -r "$log" ] && { mapfile -t argv < "$log"; lines=${#argv[@]}; argv=(); }
for p in "${new[@]}"; do printf '%s\t%s\t%.200s\n' "$SECONDS" "$lines" "$p"; done >> "$out"
fi
nap 0.5
done
}
if [ "${BL_OPEN_DRIVER:-0}" = 1 ] && [ -z "${BL_INSIDE:-}" ] && [ -n "${BL_DEBUG_DIR:-}" ]; then
watch_processes &
fi
export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
+4
View File
@@ -47,6 +47,10 @@ class VkDmabufQuarantineTest(unittest.TestCase):
helpers += [function(comp, 'send_buffer_release')]
else:
helpers = ['static int __attribute__((unused)) buffer_release_blocked(struct wl_resource *b) { (void)b; return 0; }']
if re.search(r'\bstatic int buffer_shown_elsewhere\(', comp):
# One surface per buffer in these scenarios: never shown elsewhere.
helpers += ['static int buffer_shown_elsewhere(struct wl_resource *b, const struct surface *e) '
'{ (void)b; (void)e; return 0; }']
query = function(vk, 'vkp_completion_unknown') if 'int vkp_completion_unknown(' in vk else \
'int vkp_completion_unknown(void) { return g_dev_state == -3; }'
comp_functions = [function(comp, names[0])] + helpers + [function(comp, n) for n in names[1:]]
+46 -5
View File
@@ -6,7 +6,8 @@
* - robustness2's nullDescriptor: VK_NULL_HANDLE buffers, image views and vertex buffers in
* descriptor writes (plain, template and push) and vertex-buffer binds are replaced with small
* stand-ins the layer owns. robustBufferAccess2 itself maps to robustBufferAccess (layer.c).
* A texel-buffer view stays null: its format is not known here.
* A null texel-buffer view gets an RGBA8 (uniform) or R32_UINT (storage) view of the stand-in
* buffer: its format is not known here, but Zink leaves them unbound and never reads them.
*/
#include <stdlib.h>
#include <string.h>
@@ -72,7 +73,8 @@ static bool null_resources(struct device *dev)
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = NULL_BUFFER_SIZE,
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
if (dev->CreateBuffer(dev->handle, &binfo, NULL, &dev->null_buffer) != VK_SUCCESS)
@@ -130,6 +132,18 @@ static bool null_resources(struct device *dev)
};
if (dev->CreateImageView(dev->handle, &vinfo, NULL, &dev->null_view) != VK_SUCCESS)
return false;
/* Texel views are optional: a write that needs one and has none keeps its null. */
VkBufferViewCreateInfo tinfo = {
.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
.buffer = dev->null_buffer,
.format = VK_FORMAT_R8G8B8A8_UNORM,
.range = VK_WHOLE_SIZE,
};
if (dev->CreateBufferView(dev->handle, &tinfo, NULL, &dev->null_uniform_texel) != VK_SUCCESS)
dev->null_uniform_texel = VK_NULL_HANDLE;
tinfo.format = VK_FORMAT_R32_UINT;
if (dev->CreateBufferView(dev->handle, &tinfo, NULL, &dev->null_storage_texel) != VK_SUCCESS)
dev->null_storage_texel = VK_NULL_HANDLE;
dev->null_ready = 1;
layer_log("nullDescriptor: stand-in buffer and 1x1 image ready");
return true;
@@ -145,6 +159,8 @@ void null_resources_destroy(struct device *dev)
}
}
map_free(&dev->templates);
if (dev->null_uniform_texel) dev->DestroyBufferView(dev->handle, dev->null_uniform_texel, NULL);
if (dev->null_storage_texel) dev->DestroyBufferView(dev->handle, dev->null_storage_texel, NULL);
if (dev->null_view) dev->DestroyImageView(dev->handle, dev->null_view, NULL);
if (dev->null_image) dev->DestroyImage(dev->handle, dev->null_image, NULL);
if (dev->null_image_memory) dev->FreeMemory(dev->handle, dev->null_image_memory, NULL);
@@ -164,6 +180,18 @@ static bool is_image_type(VkDescriptorType t)
t == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || t == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
}
static bool is_texel_type(VkDescriptorType t)
{
return t == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER || t == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
}
static void fix_texel(struct device *dev, VkDescriptorType t, VkBufferView *view)
{
if (*view != VK_NULL_HANDLE || !null_resources(dev))
return;
*view = t == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER ? dev->null_storage_texel : dev->null_uniform_texel;
}
static void fix_buffer(struct device *dev, VkDescriptorBufferInfo *info)
{
if (info->buffer != VK_NULL_HANDLE || !null_resources(dev))
@@ -194,6 +222,8 @@ static VkWriteDescriptorSet *fix_writes(struct device *dev, uint32_t count, cons
if (is_image_type(w->descriptorType) && w->pImageInfo && w->pImageInfo[j].imageView == VK_NULL_HANDLE &&
w->descriptorType != VK_DESCRIPTOR_TYPE_SAMPLER)
null_found = true;
if (is_texel_type(w->descriptorType) && w->pTexelBufferView && w->pTexelBufferView[j] == VK_NULL_HANDLE)
null_found = true;
}
if (!null_found)
continue;
@@ -209,6 +239,13 @@ static VkWriteDescriptorSet *fix_writes(struct device *dev, uint32_t count, cons
fix_buffer(dev, &b[j]);
copies[i].pBufferInfo = b;
(*scratch)[(*scratch_count)++] = b;
} else if (is_texel_type(w->descriptorType)) {
VkBufferView *v = malloc(w->descriptorCount * sizeof(*v));
memcpy(v, w->pTexelBufferView, w->descriptorCount * sizeof(*v));
for (uint32_t j = 0; j < w->descriptorCount; j++)
fix_texel(dev, w->descriptorType, &v[j]);
copies[i].pTexelBufferView = v;
(*scratch)[(*scratch_count)++] = v;
} else {
VkDescriptorImageInfo *im = malloc(w->descriptorCount * sizeof(*im));
memcpy(im, w->pImageInfo, w->descriptorCount * sizeof(*im));
@@ -320,13 +357,15 @@ static void *fix_template_data(struct device *dev, VkDescriptorUpdateTemplate tm
const VkDescriptorUpdateTemplateEntry *e = &t->entries[i];
bool buffer = is_buffer_type(e->descriptorType);
bool image = is_image_type(e->descriptorType);
if (!buffer && !image)
bool texel = is_texel_type(e->descriptorType);
if (!buffer && !image && !texel)
continue;
for (uint32_t j = 0; j < e->descriptorCount; j++) {
size_t at = e->offset + (size_t)j * e->stride;
const char *src = (const char *)(copy ? copy : data) + at;
bool null_handle = buffer ? ((const VkDescriptorBufferInfo *)src)->buffer == VK_NULL_HANDLE
: ((const VkDescriptorImageInfo *)src)->imageView == VK_NULL_HANDLE;
bool null_handle = buffer ? ((const VkDescriptorBufferInfo *)src)->buffer == VK_NULL_HANDLE
: texel ? *(const VkBufferView *)src == VK_NULL_HANDLE
: ((const VkDescriptorImageInfo *)src)->imageView == VK_NULL_HANDLE;
if (!null_handle)
continue;
if (!copy) {
@@ -335,6 +374,8 @@ static void *fix_template_data(struct device *dev, VkDescriptorUpdateTemplate tm
}
if (buffer)
fix_buffer(dev, (VkDescriptorBufferInfo *)((char *)copy + at));
else if (texel)
fix_texel(dev, e->descriptorType, (VkBufferView *)((char *)copy + at));
else
fix_image(dev, (VkDescriptorImageInfo *)((char *)copy + at));
}
+12 -4
View File
@@ -1,5 +1,7 @@
#define _GNU_SOURCE
#include "mali_compat.h"
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -133,13 +135,19 @@ void map_free(struct handle_map *map)
*map = (struct handle_map){0};
}
/* Wine processes only: the Steam client and gamescope run fine without the layer, and their
* Zink would otherwise start using the made-up features too. */
/* Wine processes by default. DROIDDECK_MALI_COMPAT=1 (the app's Venus route) turns it on everywhere
* but gamescope, so Zink in the Steam client and Xwayland gets robustness2's nullDescriptor, which it
* requires; gamescope runs fine on the driver's own features. =all includes gamescope too. */
static bool wanted(void)
{
const char *setting = getenv("DROIDDECK_MALI_COMPAT");
if (setting)
return strcmp(setting, "0") != 0;
if (setting) {
if (strcmp(setting, "0") == 0)
return false;
if (strcmp(setting, "all") == 0)
return true;
return strcmp(program_invocation_short_name, "gamescope") != 0;
}
return getenv("WINEPREFIX") != NULL;
}
+5
View File
@@ -119,6 +119,8 @@ struct instance {
X(GetImageMemoryRequirements) \
X(BindImageMemory) \
X(DestroyImageView) \
X(CreateBufferView) \
X(DestroyBufferView) \
X(UpdateDescriptorSets) \
X(CmdPushDescriptorSet) \
X(CreateDescriptorUpdateTemplate) \
@@ -157,6 +159,9 @@ struct device {
VkImage null_image;
VkDeviceMemory null_image_memory;
VkImageView null_view;
/* Texel-buffer stand-ins: RGBA8 for uniform (sampled) texel buffers, R32_UINT for storage. */
VkBufferView null_uniform_texel;
VkBufferView null_storage_texel;
struct device *next;
};
+7 -2
View File
@@ -30,8 +30,12 @@ static void build_memory_model(vkb_physdev *pd)
int pure_dl = -1;
for (uint32_t t = 0; t < s->memoryTypeCount; t++) {
VkMemoryPropertyFlags f = s->memoryTypes[t].propertyFlags;
/* Not a lazily allocated type (Mali lists one first, for transient attachments): an
* ordinary image cannot use it, and taking it as "the" device-local type left images with
* no memory type at all (self-test "image clear: setup", gamescope "No compatible image
* memory type"). */
if ((f & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && !(f & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
!(f & VK_MEMORY_PROPERTY_PROTECTED_BIT)) {
!(f & VK_MEMORY_PROPERTY_PROTECTED_BIT) && !(f & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT)) {
pure_dl = (int)t;
break;
}
@@ -55,7 +59,8 @@ static void build_memory_model(vkb_physdev *pd)
/* A device-local type with no host access, backed by the first device-local type. */
for (uint32_t t = 0; t < s->memoryTypeCount; t++) {
VkMemoryPropertyFlags f = s->memoryTypes[t].propertyFlags;
if ((f & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && !(f & VK_MEMORY_PROPERTY_PROTECTED_BIT)) {
if ((f & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && !(f & VK_MEMORY_PROPERTY_PROTECTED_BIT) &&
!(f & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT)) {
cands[nc++] = (struct cand){VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, s->memoryTypes[t].heapIndex, t, 0};
break;
}