Non-Adreno round 4: Mali GPU-drawing block, gamescope YCbCr retry, GL probe, bridge draw sync

- Setup's "Steam interface: GPU drawing" is hidden on Mali and a saved "on" is
  cleared: with PanVK it rebooted an Edge 60 (G615) as steamwebhelper started.
  Kept for Xclipse.
- gamescope 0130: a composite pipeline refused at start-up is retried with a
  layout without immutable YCbCr samplers (G615 through Venus refused every
  composite shader; their SPIR-V has no Float16, so fp16 was not the cause).
- Venus: droiddeck-glprobe opens GL through GLX the way the Steam client does,
  with the loader's reasons logged; when it fails the client's GL goes to
  llvmpipe.
- vkbridge self-test: the draw check waits for the render pass's stores before
  copying out (a tiler read zeros without the dependency).
This commit is contained in:
The412Banner committed 2026-10-13 09:27:50 -04:00
1 parent 3ef49faa26
commit 6c8a086190
11 files changed
+203 -5

No files matched your search

@@ -27,6 +27,14 @@ object DeviceSupport {
com.droiddeck.launcher.session.SessionPrefs.vkBridge(context)
/** Whether this phone can use the Mali bridge at all: a Mali (kbase) without a system-driver route. */
/** A Mali GPU (its kbase device is there, or the GPU reports a Mali family). Never an Adreno. */
fun mali(): Boolean = isMali
private val isMali by lazy {
!adreno() && (File("/dev/mali0").exists() ||
com.droiddeck.launcher.gpu.GpuInfo.detect().family.name.startsWith("MALI"))
}
fun bridgeUsable(): Boolean = !adreno() && !SystemVulkanDriver.isAvailable() && File("/dev/mali0").exists()
/**
@@ -154,6 +154,7 @@ object SessionFiles {
"usr/local/lib/vkbridge/libvulkan_droidbridge.so" to "usr/local/lib/vkbridge/libvulkan_droidbridge.so",
"usr/local/share/vkbridge/droidbridge_icd.json" to "usr/local/share/vkbridge/droidbridge_icd.json",
"usr/local/bin/droiddeck-vkbridge-smoke" to "usr/local/bin/droiddeck-vkbridge-smoke",
"usr/local/bin/droiddeck-glprobe" to "usr/local/bin/droiddeck-glprobe",
"usr/local/lib/droiddeck/uruntime" to "usr/local/lib/droiddeck/uruntime",
"usr/local/share/licenses/uruntime/LICENSE" to "usr/local/share/licenses/uruntime/LICENSE",
) + mangoapp + xclipse.map { it to it } + maliCompat + listOf("comet", "GalaxyCommunication.exe", "update-permissions.exe", "LICENSE", "version").map { "usr/local/lib/droiddeck-comet/$it" to "usr/local/lib/droiddeck-comet/$it" } + msitools.map { it to it } + fexPreloads).filter { (asset, _) ->
@@ -526,10 +526,23 @@ object SessionPrefs {
* 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 steamGpuCompositing(context: Context): Boolean {
if (!steamGpuCompositingAllowed()) {
// Never on a Mali: with PanVK it rebooted an Edge 60 (G615) twice as steamwebhelper
// started. A value saved by the build that offered it is cleared.
if (prefs(context).getBoolean("steamGpuCompositing", false))
prefs(context).edit().putBoolean("steamGpuCompositing", false).apply()
return false
}
return prefs(context).getBoolean("steamGpuCompositing", false)
}
/** Setup offers "Steam interface: GPU drawing" on Xclipse (RADV) and other non-Mali, non-Adreno GPUs. */
fun steamGpuCompositingAllowed(): Boolean =
!com.droiddeck.launcher.core.DeviceSupport.adreno() && !com.droiddeck.launcher.core.DeviceSupport.mali()
fun setSteamGpuCompositing(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("steamGpuCompositing", on).apply()
prefs(context).edit().putBoolean("steamGpuCompositing", on && steamGpuCompositingAllowed()).apply()
}
fun setVenus(context: Context, on: Boolean) {
@@ -422,7 +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() }
if (!s.adreno && com.droiddeck.launcher.session.SessionPrefs.steamGpuCompositingAllowed()) 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)
+1 -1
View File
@@ -681,7 +681,7 @@
<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_steam_gpu_drawing_hint">With the 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>
+7
View File
@@ -78,3 +78,10 @@ app's Wayland-hosted gamescope never reaches, or need a newer gamescope than the
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.
- `0130-composite-without-immutable-ycbcr-samplers.patch`: one composite pipeline is compiled at
start-up; when the driver refuses it, the descriptor layout is rebuilt without binding 4's immutable
YCbCr samplers (that binding then takes the slot's ordinary sampler) and the probe tried again, with
both outcomes in the log. `GAMESCOPE_NO_YCBCR_SAMPLERS=1` forces it. A Mali-G615 through Venus
refused every composite shader (blit, blur, RCAS: SPIR-V capabilities Shader, Sampled1D,
ImageQuery, no Float16) while EASU, NIS and the NV12 converter, which share the layout but never
touch binding 4, compiled.
@@ -0,0 +1,78 @@
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
index 103baee..d344a99 100644
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -319,6 +319,33 @@ bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface)
return false;
if (!createShaders())
return false;
+ {
+ // One composite pipeline up front: a driver that refuses it with the immutable YCbCr
+ // samplers gets the layout without them (NV12 textures then sample without conversion).
+ if ( getenv( "GAMESCOPE_NO_YCBCR_SAMPLERS" ) && *getenv( "GAMESCOPE_NO_YCBCR_SAMPLERS" ) == '1' )
+ {
+ vk_log.infof( "GAMESCOPE_NO_YCBCR_SAMPLERS=1: composite layout without immutable YCbCr samplers" );
+ vk.DestroyPipelineLayout( device(), m_pipelineLayout, nullptr );
+ vk.DestroyDescriptorSetLayout( device(), m_descriptorSetLayout, nullptr );
+ m_bYcbcrImmutableSamplers = false;
+ if ( !createLayouts() )
+ return false;
+ }
+ VkPipeline probe = compilePipeline( 1, 0, SHADER_TYPE_BLIT, 1, 0, 0, 0, false );
+ if ( probe == VK_NULL_HANDLE && m_bYcbcrImmutableSamplers )
+ {
+ vk_log.errorf( "composite pipeline refused; retrying without immutable YCbCr samplers" );
+ vk.DestroyPipelineLayout( device(), m_pipelineLayout, nullptr );
+ vk.DestroyDescriptorSetLayout( device(), m_descriptorSetLayout, nullptr );
+ m_bYcbcrImmutableSamplers = false;
+ if ( !createLayouts() )
+ return false;
+ probe = compilePipeline( 1, 0, SHADER_TYPE_BLIT, 1, 0, 0, 0, false );
+ vk_log.infof( "composite pipeline without immutable YCbCr samplers: %s", probe ? "accepted" : "STILL refused" );
+ }
+ if ( probe != VK_NULL_HANDLE )
+ vk.DestroyPipeline( device(), probe, nullptr );
+ }
if (!createScratchResources())
return false;
@@ -869,7 +896,7 @@ bool CVulkanDevice::createLayouts()
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = VKR_SAMPLER_SLOTS,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
- .pImmutableSamplers = ycbcrSamplers.data(),
+ .pImmutableSamplers = m_bYcbcrImmutableSamplers ? ycbcrSamplers.data() : nullptr,
},
VkDescriptorSetLayoutBinding {
.binding = 5,
@@ -1870,6 +1897,8 @@ void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z)
imageDescriptors[i].sampler = m_device->sampler(m_samplerState[i]);
imageDescriptors[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
ycbcrImageDescriptors[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ if ( !m_device->ycbcrImmutableSamplers() )
+ ycbcrImageDescriptors[i].sampler = imageDescriptors[i].sampler;
if (m_boundTextures[i] == nullptr)
continue;
diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp
index 901e6a6..e0ede11 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -876,6 +876,7 @@ public:
inline bool hasDrmPrimaryDevId() {return m_bHasDrmPrimaryDevId;}
inline dev_t primaryDevId() {return m_drmPrimaryDevId;}
inline bool supportsFp16() {return m_bSupportsFp16;}
+ inline bool ycbcrImmutableSamplers() {return m_bYcbcrImmutableSamplers;}
inline std::vector<VkExtensionProperties>& supportedExtensions() {return m_supportedExts;}
inline std::pair<void *, uint32_t> uploadBufferData(uint32_t size)
@@ -925,6 +926,9 @@ protected:
VkQueue m_generalQueue = nullptr;
VkSamplerYcbcrConversion m_ycbcrConversion = VK_NULL_HANDLE;
VkSampler m_ycbcrSampler = VK_NULL_HANDLE;
+ // Binding 4 carries immutable YCbCr samplers; off when the driver refuses every composite
+ // pipeline with them (Mali-G615 through Venus: VK_ERROR_INITIALIZATION_FAILED).
+ bool m_bYcbcrImmutableSamplers = true;
VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE;
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE;
@@ -1179,6 +1179,18 @@ BUSCONF
) &
stop_watcher=$!
fi
# Mali (Venus): the client's first window needs GL through GLX (Zink). On a Mali-G615 Zink
# failed with nothing but "failed to load driver: zink" even with nullDescriptor present, and
# the client quit ("UpdateUI GL not available"). One probe the way the client asks, with the
# loader's reasons in the log; when GL does not start, the client's GL goes to llvmpipe.
if [ "${BL_VENUS:-0}" = 1 ] && [ -x /usr/local/bin/droiddeck-glprobe ] && [ "${GALLIUM_DRIVER:-}" = zink ]; then
LIBGL_DEBUG=verbose MESA_LOG_LEVEL=debug timeout 20 /usr/local/bin/droiddeck-glprobe 2>&1 | head -60 | sed 's/^/== glprobe: /'
if [ "${PIPESTATUS[0]}" = 1 ]; then
export GALLIUM_DRIVER=llvmpipe LIBGL_ALWAYS_SOFTWARE=1
unset MESA_LOADER_DRIVER_OVERRIDE LIBGL_KOPPER_DRI2
echo "== venus: GL through Zink did not start (see glprobe above); the Steam client's GL uses llvmpipe (software)"
fi
fi
while :; do
rm -f "$HOME/.bl-restart-once"
if [ -f "$steam_beta" ] && [ "$(cat "$steam_beta" 2>/dev/null)" != "$steam_channel" ]; then
+3
View File
@@ -5,6 +5,7 @@
# usr/local/lib/vkbridge/libvulkan_droidbridge.so the Vulkan ICD
# usr/local/share/vkbridge/droidbridge_icd.json its manifest
# usr/local/bin/droiddeck-vkbridge-smoke the session-start self-test
# usr/local/bin/droiddeck-glprobe GLX start-up probe (Mali Venus route)
set -euo pipefail
d=$1
cc=${2:-aarch64-linux-gnu-gcc}
@@ -20,6 +21,8 @@ mkdir -p "$d/usr/local/lib/vkbridge" "$d/usr/local/share/vkbridge" "$d/usr/local
"$strip" --strip-unneeded "$d/usr/local/lib/vkbridge/libvulkan_droidbridge.so"
"$cc" -O2 -Wall $inc -I"$here/tests" -o "$d/usr/local/bin/droiddeck-vkbridge-smoke" "$here/tests/smoke.c" -ldl
"$strip" --strip-unneeded "$d/usr/local/bin/droiddeck-vkbridge-smoke"
"$cc" -O2 -Wall -o "$d/usr/local/bin/droiddeck-glprobe" "$here/tests/glprobe.c" -ldl
"$strip" --strip-unneeded "$d/usr/local/bin/droiddeck-glprobe"
cat > "$d/usr/local/share/vkbridge/droidbridge_icd.json" <<JSON
{"file_format_version": "1.0.0", "ICD": {"library_path": "/usr/local/lib/vkbridge/libvulkan_droidbridge.so", "api_version": "1.4.341"}}
JSON
+69
View File
@@ -0,0 +1,69 @@
/*
* droiddeck-glprobe: does OpenGL through GLX start on this session's display, the way the Steam
* client's first window asks for it (glXChooseVisual, then a context)? Run once before the client
* on the Mali (Venus) route, where Zink failed on a Mali-G615 with nothing but "failed to load
* driver: zink" and the client gave up ("UpdateUI GL not available"). Exit 0 when a visual and a
* context come back, 1 when GL does not start, 2 when there is no X display or no libGL.
* libX11 and libGL are opened at run time, so this builds without their headers.
*/
#include <dlfcn.h>
#include <stdio.h>
typedef void *(*open_display_fn)(const char *);
typedef int (*close_display_fn)(void *);
typedef int (*default_screen_fn)(void *);
typedef void *(*choose_visual_fn)(void *, int, int *);
typedef void *(*create_context_fn)(void *, void *, void *, int);
typedef void (*destroy_context_fn)(void *, void *);
typedef int (*free_fn)(void *);
#define GLX_RGBA 4
#define GLX_DOUBLEBUFFER 5
#define GLX_RED_SIZE 8
#define GLX_GREEN_SIZE 9
#define GLX_BLUE_SIZE 10
int main(void)
{
void *x11 = dlopen("libX11.so.6", RTLD_NOW | RTLD_GLOBAL);
void *gl = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL);
if (!x11 || !gl) {
printf("glprobe: cannot open %s: %s\n", x11 ? "libGL.so.1" : "libX11.so.6", dlerror());
return 2;
}
open_display_fn open_display = (open_display_fn)dlsym(x11, "XOpenDisplay");
close_display_fn close_display = (close_display_fn)dlsym(x11, "XCloseDisplay");
default_screen_fn default_screen = (default_screen_fn)dlsym(x11, "XDefaultScreen");
free_fn x_free = (free_fn)dlsym(x11, "XFree");
choose_visual_fn choose_visual = (choose_visual_fn)dlsym(gl, "glXChooseVisual");
create_context_fn create_context = (create_context_fn)dlsym(gl, "glXCreateContext");
destroy_context_fn destroy_context = (destroy_context_fn)dlsym(gl, "glXDestroyContext");
if (!open_display || !close_display || !default_screen || !x_free || !choose_visual || !create_context ||
!destroy_context) {
printf("glprobe: missing X11/GLX entry points\n");
return 2;
}
void *dpy = open_display(NULL);
if (!dpy) {
printf("glprobe: no X display\n");
return 2;
}
int attrs[] = {GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, 0};
void *visual = choose_visual(dpy, default_screen(dpy), attrs);
if (!visual) {
printf("glprobe: FAIL glXChooseVisual\n");
close_display(dpy);
return 1;
}
void *ctx = create_context(dpy, visual, NULL, 1);
x_free(visual);
if (!ctx) {
printf("glprobe: FAIL glXCreateContext\n");
close_display(dpy);
return 1;
}
destroy_context(dpy, ctx);
close_display(dpy);
printf("glprobe: OK (visual and context)\n");
return 0;
}
+8 -1
View File
@@ -325,7 +325,12 @@ static void test_draw(void)
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL};
VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkSubpassDescription sub = {0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, NULL, 1, &ref, NULL, NULL, 0, NULL};
VkRenderPassCreateInfo rpci = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, NULL, 0, 1, &att, 1, &sub, 0, NULL};
/* The copy after the pass reads what the pass stored. Without this dependency the implicit
* one to EXTERNAL makes nothing available to transfer reads: a tiler (Mali-G615) wrote the
* tile back after the copy had already read zeros ("pixel 0 = 0x00000000"). */
VkSubpassDependency dep = {0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, 0};
VkRenderPassCreateInfo rpci = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, NULL, 0, 1, &att, 1, &sub, 1, &dep};
VkRenderPass rp;
vkCreateRenderPass(dev, &rpci, NULL, &rp);
VkFramebufferCreateInfo fci = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, NULL, 0, rp, 1, &view, w, h, 1};
@@ -372,6 +377,8 @@ static void test_draw(void)
vkCmdPushConstants(cb, pl, VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, color);
vkCmdDraw(cb, 3, 1, 0, 0);
vkCmdEndRenderPass(cb);
VkMemoryBarrier mb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT};
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &mb, 0, NULL, 0, NULL);
VkBufferImageCopy c = {0, 0, 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {w, h, 1}};
vkCmdCopyImageToBuffer(cb, img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, rb.buf, 1, &c);
int ok = submit(cb);