mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
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:
1 parent
7464bd7697
commit
55b30fbf1d
17 files changed
+282
-18
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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:]]
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user