From a4d47cf33edcb6bf201118d6b8e6adac24e16f10 Mon Sep 17 00:00:00 2001 From: The412Banner Date: Thu, 15 Oct 2026 18:53:40 -0400 Subject: [PATCH] Non-Adreno round 8: bridge sharing fallback and crash detail, Venus diagnostics and 1.2 gate, compositor disconnect reasons, RADV upstream - Mali bridge: the self-test fails "memory sharing" when the server offers no host-visible memory type (no sharing method passed, RP4 Pro G77); the session then falls back to the PanVK pair like a failed draw, and Setup says the bridge cannot share memory with that driver. Without a pair to fall back on, the log says so. - Mali bridge: a guarded driver crash now logs the sub-command it was in, the fault address and pc (library + offset via dladdr), the command buffer's level, begin flags, pool flags and state, and the last 32 replayed commands. Begin on an executable buffer from a pool without RESET_COMMAND_BUFFER_BIT is logged once (implicit reset). - Venus: vulkan-summary.txt gets the device extension list and key features (full vulkaninfo in vulkan-full.txt); a failed GL probe runs once with VN_DEBUG=init,result, ZINK_DEBUG=mem, MESA_LOG_LEVEL=debug and the layer trace into glprobe-trace.txt. The Mali compatibility layer's trace covers every pre-device query (devices, memory, queue families, formats, image formats, external handles) and names the extensions. - Venus: off when the phone's driver reports Vulkan below 1.2 (gamescope skips such a device); Setup and the graphics report say why and the session uses the PanVK pair. - Compositor: libwayland's own disconnect reasons go to wayland.log, and a client's buffer may grow to 1 MB before a slow reader is dropped. A session that ended without teardown records Android's exit reasons. - Drivers: RADV-Xclipse upstream (radv-xclipse-upstream) is a manual choice for Xclipse; Auto keeps radv-xclipse. --- app/src/main/cpp/vkbridge/server_cbtrack.c | 152 +++++++++++- app/src/main/cpp/vkbridge/server_conn.c | 73 ++++++ app/src/main/cpp/vkbridge/vkb_server.h | 2 + app/src/main/cpp/waylandcomp/src/compositor.c | 14 ++ .../com/droiddeck/launcher/MainActivity.kt | 17 +- .../com/droiddeck/launcher/gpu/DriverPairs.kt | 6 +- .../droiddeck/launcher/gpu/TurnipReleases.kt | 2 +- .../launcher/session/GraphicsReport.kt | 16 +- .../launcher/session/SessionArtifacts.kt | 36 ++- .../launcher/session/SessionPrefs.kt | 12 +- .../launcher/session/SessionService.kt | 6 + .../launcher/session/VenusComponent.kt | 11 +- .../droiddeck/launcher/ui/FrontEndScreen.kt | 2 + .../droiddeck/launcher/ui/FrontEndSetup.kt | 10 +- app/src/main/res/values/strings.xml | 2 + .../droiddeck/launcher/gpu/DriverPairsTest.kt | 16 ++ .../overlay/usr/local/bin/droiddeck-session | 33 ++- tools/venus/layer/layer.c | 11 +- tools/venus/layer/mali_compat.h | 12 + tools/venus/layer/trace.c | 233 ++++++++++++++++++ tools/vkbridge/tests/smoke.c | 5 + 21 files changed, 646 insertions(+), 25 deletions(-) create mode 100644 tools/venus/layer/trace.c diff --git a/app/src/main/cpp/vkbridge/server_cbtrack.c b/app/src/main/cpp/vkbridge/server_cbtrack.c index 757401ef1..99196ba8a 100644 --- a/app/src/main/cpp/vkbridge/server_cbtrack.c +++ b/app/src/main/cpp/vkbridge/server_cbtrack.c @@ -10,21 +10,41 @@ #define _GNU_SOURCE #include "vkb_server.h" +#include #include #include #define CBT_BUCKETS 1024 +enum { CBT_INITIAL, CBT_RECORDING, CBT_EXECUTABLE }; + typedef struct cbt_entry { VkCommandBuffer cb; VkCommandPool pool; + VkCommandBufferLevel level; + int state; + VkCommandBufferUsageFlags begin_flags; + int inheritance; + uint32_t begins; struct cbt_entry *next; } cbt_entry; +typedef struct cbt_pool { + VkCommandPool pool; + VkCommandPoolCreateFlags flags; + uint32_t qfam; + struct cbt_pool *next; +} cbt_pool; + typedef struct vkb_cbtrack { pthread_mutex_t lock; cbt_entry *b[CBT_BUCKETS]; VkCommandBuffer last_refused; + cbt_pool *pools; + int warned_implicit_reset; + PFN_vkCreateCommandPool create_pool; + PFN_vkResetCommandPool reset_pool; + PFN_vkResetCommandBuffer reset_cb; PFN_vkAllocateCommandBuffers alloc; PFN_vkFreeCommandBuffers free_cbs; PFN_vkDestroyCommandPool destroy_pool; @@ -47,11 +67,55 @@ static vkb_cbtrack *cur(void) return t ? t->cbtrack : NULL; } -static int known_locked(vkb_cbtrack *k, VkCommandBuffer cb) +static cbt_entry *find_locked(vkb_cbtrack *k, VkCommandBuffer cb) { for (cbt_entry *e = k->b[bucket(cb)]; e; e = e->next) - if (e->cb == cb) return 1; - return 0; + if (e->cb == cb) return e; + return NULL; +} + +static int known_locked(vkb_cbtrack *k, VkCommandBuffer cb) { return find_locked(k, cb) != NULL; } + +static cbt_pool *pool_locked(vkb_cbtrack *k, VkCommandPool pool) +{ + for (cbt_pool *p = k->pools; p; p = p->next) + if (p->pool == pool) return p; + return NULL; +} + +static const char *state_name(int s) { return s == CBT_RECORDING ? "recording" : s == CBT_EXECUTABLE ? "executable" : "initial"; } + +void vkb_cbtrack_describe(vkb_srv_table *t, VkCommandBuffer cb, char *out, size_t n) +{ + vkb_cbtrack *k = t ? t->cbtrack : NULL; + if (!k) { + snprintf(out, n, "command buffer %p (not tracked)", (void *)cb); + return; + } + pthread_mutex_lock(&k->lock); + cbt_entry *e = find_locked(k, cb); + cbt_pool *p = e ? pool_locked(k, e->pool) : NULL; + if (!e) + snprintf(out, n, "command buffer %p: unknown to this device", (void *)cb); + else + snprintf(out, n, "command buffer %p: level %s, state %s, begin #%u flags 0x%x%s; pool %p flags %s (queue family %d)", + (void *)cb, e->level == VK_COMMAND_BUFFER_LEVEL_SECONDARY ? "secondary" : "primary", state_name(e->state), + e->begins, (unsigned)e->begin_flags, e->inheritance ? " with inheritance info" : "", (void *)e->pool, + p ? (p->flags & VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT ? "RESET_COMMAND_BUFFER" : "0") : "unknown", + p ? (int)p->qfam : -1); + if (p && (p->flags & ~VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT)) + snprintf(out + strlen(out), n - strlen(out), " +0x%x", (unsigned)(p->flags & ~VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT)); + pthread_mutex_unlock(&k->lock); +} + +void vkb_cbtrack_ended(vkb_srv_table *t, VkCommandBuffer cb) +{ + vkb_cbtrack *k = t ? t->cbtrack : NULL; + if (!k) return; + pthread_mutex_lock(&k->lock); + cbt_entry *e = find_locked(k, cb); + if (e) e->state = CBT_EXECUTABLE; + pthread_mutex_unlock(&k->lock); } int vkb_cbtrack_known(vkb_srv_table *t, VkCommandBuffer cb, const char *what) @@ -80,8 +144,10 @@ static VKAPI_ATTR VkResult VKAPI_CALL cbt_AllocateCommandBuffers(VkDevice device if (!out[i] || known_locked(k, out[i])) continue; cbt_entry *e = malloc(sizeof(*e)); if (!e) continue; + memset(e, 0, sizeof(*e)); e->cb = out[i]; e->pool = info->commandPool; + e->level = info->level; unsigned h = bucket(out[i]); e->next = k->b[h]; k->b[h] = e; @@ -122,15 +188,82 @@ static VKAPI_ATTR void VKAPI_CALL cbt_DestroyCommandPool(VkDevice device, VkComm if (pool) { pthread_mutex_lock(&k->lock); forget_locked(k, VK_NULL_HANDLE, pool); + for (cbt_pool **pp = &k->pools; *pp; pp = &(*pp)->next) { + if ((*pp)->pool == pool) { + cbt_pool *p = *pp; + *pp = p->next; + free(p); + break; + } + } pthread_mutex_unlock(&k->lock); } k->destroy_pool(device, pool, a); } +static VKAPI_ATTR VkResult VKAPI_CALL cbt_CreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo *info, + const VkAllocationCallbacks *a, VkCommandPool *out) +{ + vkb_cbtrack *k = cur(); + VkResult r = k->create_pool(device, info, a, out); + if (r != VK_SUCCESS || !info || !out) return r; + cbt_pool *p = calloc(1, sizeof(*p)); + if (!p) return r; + p->pool = *out; + p->flags = info->flags; + p->qfam = info->queueFamilyIndex; + pthread_mutex_lock(&k->lock); + p->next = k->pools; + k->pools = p; + pthread_mutex_unlock(&k->lock); + return r; +} + +static VKAPI_ATTR VkResult VKAPI_CALL cbt_ResetCommandPool(VkDevice device, VkCommandPool pool, VkCommandPoolResetFlags flags) +{ + vkb_cbtrack *k = cur(); + pthread_mutex_lock(&k->lock); + for (unsigned h = 0; h < CBT_BUCKETS; h++) + for (cbt_entry *e = k->b[h]; e; e = e->next) + if (e->pool == pool) e->state = CBT_INITIAL; + pthread_mutex_unlock(&k->lock); + return k->reset_pool(device, pool, flags); +} + +static VKAPI_ATTR VkResult VKAPI_CALL cbt_ResetCommandBuffer(VkCommandBuffer cb, VkCommandBufferResetFlags flags) +{ + vkb_cbtrack *k = cur(); + if (!vkb_cbtrack_known(vkb_srv_current_table(), cb, "vkResetCommandBuffer")) return VK_ERROR_DEVICE_LOST; + pthread_mutex_lock(&k->lock); + cbt_entry *e = find_locked(k, cb); + if (e) e->state = CBT_INITIAL; + pthread_mutex_unlock(&k->lock); + return k->reset_cb(cb, flags); +} + static VKAPI_ATTR VkResult VKAPI_CALL cbt_BeginCommandBuffer(VkCommandBuffer cb, const VkCommandBufferBeginInfo *info) { vkb_cbtrack *k = cur(); - if (!vkb_cbtrack_known(vkb_srv_current_table(), cb, "vkBeginCommandBuffer")) return VK_ERROR_DEVICE_LOST; + vkb_srv_table *t = vkb_srv_current_table(); + if (!vkb_cbtrack_known(t, cb, "vkBeginCommandBuffer")) return VK_ERROR_DEVICE_LOST; + pthread_mutex_lock(&k->lock); + cbt_entry *e = find_locked(k, cb); + int bad_reset = 0, prev = CBT_INITIAL; + if (e) { + cbt_pool *p = pool_locked(k, e->pool); + prev = e->state; + bad_reset = prev != CBT_INITIAL && p && !(p->flags & VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT) && !k->warned_implicit_reset; + if (bad_reset) k->warned_implicit_reset = 1; + e->state = CBT_RECORDING; + e->begins++; + e->begin_flags = info ? info->flags : 0; + e->inheritance = info && info->pInheritanceInfo; + } + pthread_mutex_unlock(&k->lock); + if (bad_reset) + VKB_WARN("device %u: vkBeginCommandBuffer on command buffer %p in the %s state, from a pool without " + "RESET_COMMAND_BUFFER_BIT (an implicit reset the spec does not allow; logged once)", t ? t->id : 0, (void *)cb, + state_name(prev)); return k->begin(cb, info); } @@ -162,6 +295,9 @@ void vkb_cbtrack_device_init(vkb_srv_table *t) vkb_cbtrack *k = calloc(1, sizeof(*k)); if (!k) return; pthread_mutex_init(&k->lock, NULL); + k->create_pool = t->dt.vkCreateCommandPool; + k->reset_pool = t->dt.vkResetCommandPool; + k->reset_cb = t->dt.vkResetCommandBuffer; k->alloc = t->dt.vkAllocateCommandBuffers; k->free_cbs = t->dt.vkFreeCommandBuffers; k->destroy_pool = t->dt.vkDestroyCommandPool; @@ -175,6 +311,9 @@ void vkb_cbtrack_device_init(vkb_srv_table *t) t->dt.vkBeginCommandBuffer = cbt_BeginCommandBuffer; t->dt.vkQueueSubmit = cbt_QueueSubmit; if (k->submit2) t->dt.vkQueueSubmit2 = cbt_QueueSubmit2; + if (k->create_pool) t->dt.vkCreateCommandPool = cbt_CreateCommandPool; + if (k->reset_pool) t->dt.vkResetCommandPool = cbt_ResetCommandPool; + if (k->reset_cb) t->dt.vkResetCommandBuffer = cbt_ResetCommandBuffer; } void vkb_cbtrack_device_destroy(vkb_srv_table *t) @@ -188,6 +327,11 @@ void vkb_cbtrack_device_destroy(vkb_srv_table *t) free(e); } } + while (k->pools) { + cbt_pool *p = k->pools; + k->pools = p->next; + free(p); + } pthread_mutex_destroy(&k->lock); free(k); t->cbtrack = NULL; diff --git a/app/src/main/cpp/vkbridge/server_conn.c b/app/src/main/cpp/vkbridge/server_conn.c index 6f66ba45e..6ac9b3568 100644 --- a/app/src/main/cpp/vkbridge/server_conn.c +++ b/app/src/main/cpp/vkbridge/server_conn.c @@ -7,12 +7,15 @@ #define _GNU_SOURCE #include "vkb_server.h" +#include #include #include #include +#include #include #include #include +#include #include #include @@ -211,6 +214,46 @@ static void order_done(vkb_srv_proc *p, uint64_t seq) /* ------------------------------------------------------------------ command buffer replay */ +#define REPLAY_RING 32 + +typedef struct replay_rec { + const char *name; + uint32_t len; + uint32_t nw; + uint64_t w[4]; +} replay_rec; + +static __thread replay_rec t_replay[REPLAY_RING]; +static __thread uint32_t t_replay_n; +static __thread VkCommandBuffer t_replay_cb; + +static void replay_note(const char *name, const uint8_t *payload, uint32_t len) +{ + replay_rec *r = &t_replay[t_replay_n++ % REPLAY_RING]; + r->name = name; + r->len = len; + r->nw = len / 8 < 4 ? len / 8 : 4; + for (uint32_t i = 0; i < r->nw; i++) memcpy(&r->w[i], payload + i * 8, 8); +} + +static void replay_dump(vkb_srv_table *t) +{ + char desc[320]; + vkb_cbtrack_describe(t, t_replay_cb, desc, sizeof(desc)); + VKB_ERR("crash detail: %s", desc); + uint32_t first = t_replay_n > REPLAY_RING ? t_replay_n - REPLAY_RING : 0; + VKB_ERR("crash detail: %u commands replayed, the last %u (oldest first; payload words after the command id):", t_replay_n, + t_replay_n - first); + for (uint32_t i = first; i < t_replay_n; i++) { + const replay_rec *r = &t_replay[i % REPLAY_RING]; + char words[96] = ""; + size_t o = 0; + for (uint32_t j = 0; j < r->nw && o < sizeof(words); j++) + o += (size_t)snprintf(words + o, sizeof(words) - o, " %llx", (unsigned long long)r->w[j]); + VKB_ERR("crash detail: #%u %s (%u bytes)%s", i, r->name, r->len, words); + } +} + static int is_stream_cmd(uint32_t id) { const char *n = vkb_cmd_names[id]; @@ -234,6 +277,8 @@ void vkb_sv_vkEndCommandBuffer(vkb_srv_call *c) } VkResult begin_r = VK_SUCCESS; size_t pos = 0; + t_replay_cb = cb; + t_replay_n = 0; vkb_enc scratch; vkb_enc_init(&scratch); uint32_t ncmds = 0; @@ -258,6 +303,7 @@ void vkb_sv_vkEndCommandBuffer(vkb_srv_call *c) sub.conn = c->conn; sub.in_stream = 1; vkb_srv_current_cmd = vkb_cmd_names[id]; + replay_note(vkb_cmd_names[id], s + pos, n); vkb_srv_handlers[id](&sub); vkb_srv_current_cmd = vkb_cmd_names[VKB_CMD_vkEndCommandBuffer]; scratch = sub.r; @@ -269,7 +315,12 @@ void vkb_sv_vkEndCommandBuffer(vkb_srv_call *c) ncmds++; } vkb_enc_free(&scratch); + replay_note("vkEndCommandBuffer (the driver's)", NULL, 0); + vkb_srv_current_cmd = "vkEndCommandBuffer (the driver's)"; VkResult r = c->dt->vkEndCommandBuffer(cb); + vkb_srv_current_cmd = vkb_cmd_names[VKB_CMD_vkEndCommandBuffer]; + t_replay_cb = VK_NULL_HANDLE; + if (r == VK_SUCCESS) vkb_cbtrack_ended(c->table, cb); if (begin_r != VK_SUCCESS) r = begin_r; if (vkb_verbose) VKB_DBG("command buffer %p: %u commands, %zu bytes -> %d", (void *)cb, ncmds, len, r); vkb_enc_u32(&c->r, (uint32_t)r); @@ -449,6 +500,8 @@ static int srv_recv(vkb_srv_conn *conn, vkb_msg_hdr *h, uint8_t **payload, int * */ static __thread sigjmp_buf *t_guard; static __thread volatile sig_atomic_t t_fault; +static __thread void *t_fault_addr; +static __thread uintptr_t t_fault_pc; static struct sigaction prev_segv, prev_bus; static pthread_once_t guard_once = PTHREAD_ONCE_INIT; @@ -458,6 +511,14 @@ static void guard_handler(int sig, siginfo_t *si, void *uc) if (g) { t_guard = NULL; t_fault = sig; + t_fault_addr = si ? si->si_addr : NULL; +#if defined(__aarch64__) + t_fault_pc = uc ? (uintptr_t)((ucontext_t *)uc)->uc_mcontext.pc : 0; +#elif defined(__x86_64__) + t_fault_pc = uc ? (uintptr_t)((ucontext_t *)uc)->uc_mcontext.gregs[REG_RIP] : 0; +#else + t_fault_pc = 0; +#endif siglongjmp(*g, 1); } struct sigaction *p = sig == SIGBUS ? &prev_bus : &prev_segv; @@ -532,6 +593,18 @@ static void *conn_main(void *arg) if (sigsetjmp(jb, 1)) { VKB_ERR("the GPU driver crashed (signal %d) in %s for process %d; that client is cut off, the app and " "the other clients carry on", (int)t_fault, vkb_cmd_names[h.cmd], conn->proc ? conn->proc->pid : -1); + { + Dl_info di; + memset(&di, 0, sizeof(di)); + int found = t_fault_pc && dladdr((void *)t_fault_pc, &di); + VKB_ERR("crash detail: inside %s, fault address %p, pc %p = %s+0x%lx (%s)", + vkb_srv_current_cmd ? vkb_srv_current_cmd : "?", t_fault_addr, (void *)t_fault_pc, + found && di.dli_fname ? di.dli_fname : "?", + found && di.dli_fbase ? (unsigned long)(t_fault_pc - (uintptr_t)di.dli_fbase) : 0ul, + found && di.dli_sname ? di.dli_sname : "no symbol"); + } + if (h.cmd == VKB_CMD_vkEndCommandBuffer && t_replay_cb) replay_dump(c.table); + t_replay_cb = VK_NULL_HANDLE; if (conn->proc) conn->proc->driver_crashed = 1; vkb_srv_current_cmd = NULL; vkb_srv_set_current_table(NULL); diff --git a/app/src/main/cpp/vkbridge/vkb_server.h b/app/src/main/cpp/vkbridge/vkb_server.h index edb8c6db2..e1be07086 100644 --- a/app/src/main/cpp/vkbridge/vkb_server.h +++ b/app/src/main/cpp/vkbridge/vkb_server.h @@ -103,6 +103,8 @@ void vkb_log_summary(void); void vkb_cbtrack_device_init(vkb_srv_table *t); void vkb_cbtrack_device_destroy(vkb_srv_table *t); int vkb_cbtrack_known(vkb_srv_table *t, VkCommandBuffer cb, const char *what); +void vkb_cbtrack_describe(vkb_srv_table *t, VkCommandBuffer cb, char *out, size_t n); +void vkb_cbtrack_ended(vkb_srv_table *t, VkCommandBuffer cb); /* server_spvfix.c */ void vkb_spvfix_device_init(vkb_srv_table *t); diff --git a/app/src/main/cpp/waylandcomp/src/compositor.c b/app/src/main/cpp/waylandcomp/src/compositor.c index bc268fd9d..145406264 100644 --- a/app/src/main/cpp/waylandcomp/src/compositor.c +++ b/app/src/main/cpp/waylandcomp/src/compositor.c @@ -152,6 +152,16 @@ static void on_client_destroyed(struct wl_listener *l, void *data) { free(ci); } +/* libwayland's own reasons for dropping a client (a failed read or flush, an invalid object, a + * malformed request) go through wl_log, which on Android writes to a closed stderr. */ +static void wl_log_to_session(const char *fmt, va_list ap) { + char line[512]; + vsnprintf(line, sizeof(line), fmt, ap); + size_t n = strlen(line); + while (n && (line[n - 1] == '\n' || line[n - 1] == '\r')) line[--n] = 0; + droiddeck_log("program", "libwayland: %s", line); +} + static void on_client_created(struct wl_listener *l, void *data) { struct wl_client *client = data; struct client_info *ci = calloc(1, sizeof(*ci)); @@ -3254,6 +3264,10 @@ int droiddeck_wayland_run(void) { return 1; } droiddeck_log("display", "Wayland compositor listening on %s/wayland-0", rt ? rt : "?"); + wl_log_set_handler_server(wl_log_to_session); + /* A client that falls behind reading (gamescope at a few fps) must not be dropped for a full + * event buffer: libwayland's default is 4 KB per direction. */ + wl_display_set_default_max_buffer_size(display, 1u << 20); wl_display_add_client_created_listener(display, &g_client_created); wl_global_create(display, &wl_compositor_interface, 6, NULL, bind_compositor); diff --git a/app/src/main/java/com/droiddeck/launcher/MainActivity.kt b/app/src/main/java/com/droiddeck/launcher/MainActivity.kt index afbc66ca5..e65f02a07 100644 --- a/app/src/main/java/com/droiddeck/launcher/MainActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/MainActivity.kt @@ -140,6 +140,7 @@ class MainActivity : ComponentActivity() { private var zinkLazy by mutableStateOf(false) private var vkBridge by mutableStateOf(false) private var venus by mutableStateOf(false) + private var venusHostVulkan by mutableStateOf(null) private val adrenoGpu by lazy { com.droiddeck.launcher.core.DeviceSupport.adreno() } private var noXalia by mutableStateOf(true) private var fastSync by mutableStateOf(false) @@ -359,6 +360,7 @@ class MainActivity : ComponentActivity() { private var graphicsLogs by mutableStateOf(false) private var steamGpuDrawing by mutableStateOf(false) private var bridgeFellBack by mutableStateOf(false) + private var bridgeFellBackMemory by mutableStateOf(false) private var showRoms by mutableStateOf(false) private var homeAppSelected by mutableStateOf(false) private var homeScreenEnabled by mutableStateOf(false) @@ -543,14 +545,14 @@ class MainActivity : ComponentActivity() { FrontEndScreen( FrontEndState( installed = installed, ready = ready, available = available?.version, removalPending = removalPending, runtimeActionsBlocked = runtimeChangesBlocked(), - vkBridge = vkBridge, venus = venus, adreno = adrenoGpu, + vkBridge = vkBridge, venus = venus, venusHostVulkan = venusHostVulkan, adreno = adrenoGpu, shortcutPicker = shortcutPicker, shortcutLibraryScanning = shortcutLibraryScanning, gameSyncFolder = gameSyncFolder, busy = busy, stage = stage, percent = percent, desktopInstalled = desktopInstalled, desktopPresent = desktopPresent, offlineAccount = offlineAccount, offline = offline, - frameGenLabel = frameGenLabel, romsDir = romsDir, logsEnabled = logsEnabled, agentCommands = agentCommands, graphicsLogs = graphicsLogs, steamGpuDrawing = steamGpuDrawing, bridgeFellBack = bridgeFellBack, + frameGenLabel = frameGenLabel, romsDir = romsDir, logsEnabled = logsEnabled, agentCommands = agentCommands, graphicsLogs = graphicsLogs, steamGpuDrawing = steamGpuDrawing, bridgeFellBack = bridgeFellBack, bridgeFellBackMemory = bridgeFellBackMemory, steamGames = steamGames, emulators = emulatorList, running = runningLabel, frameGen = FrameGen.mode(this), lossless = lossless, @@ -671,7 +673,14 @@ class MainActivity : ComponentActivity() { onRemovePackage = { id -> removePackage(id) }, onRuntime = { onRuntimeButton() }, onVkBridge = { on -> SessionPrefs.setVkBridge(this, on); vkBridge = on }, - onVenus = { on -> SessionPrefs.setVenus(this, on); venus = on }, + onVenus = { on -> + SessionPrefs.setVenus(this, on) + venus = on + if (on && !com.droiddeck.launcher.core.DeviceSupport.adreno()) Thread({ + com.droiddeck.launcher.gpu.VulkanInfo.query()["api"]?.let { SessionPrefs.setHostVulkanApi(this, it) } + runOnUiThread { venusHostVulkan = com.droiddeck.launcher.session.VenusComponent.hostVulkanTooOld(this) } + }, "venus-host-vulkan").start() + }, onFrameGenPick = { mode -> FrameGen.set(this, mode) frameGenLabel = FrameGen.label(this) @@ -1564,6 +1573,7 @@ class MainActivity : ComponentActivity() { zinkLazy = SessionPrefs.zinkLazy(this) vkBridge = SessionPrefs.vkBridge(this) venus = SessionPrefs.venus(this) + venusHostVulkan = com.droiddeck.launcher.session.VenusComponent.hostVulkanTooOld(this) noXalia = SessionPrefs.noXalia(this) fastSync = SessionPrefs.fastSync(this) syncFallback = SessionPrefs.syncFallback(this) @@ -1593,6 +1603,7 @@ class MainActivity : ComponentActivity() { graphicsLogs = SessionPrefs.detailedGraphicsLogs(this) steamGpuDrawing = SessionPrefs.steamGpuCompositing(this) bridgeFellBack = SessionPrefs.bridgeFellBack(this) + bridgeFellBackMemory = SessionPrefs.bridgeFellBackForMemory(this) runningLabel = if (SessionState.running) when (SessionState.mode) { SessionService.MODE_DESKTOP -> getString(R.string.rail_desktop) SessionService.MODE_RUN -> Library.nameForProgram(SessionState.program) diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/DriverPairs.kt b/app/src/main/java/com/droiddeck/launcher/gpu/DriverPairs.kt index ffffa4c92..a67f30b4f 100644 --- a/app/src/main/java/com/droiddeck/launcher/gpu/DriverPairs.kt +++ b/app/src/main/java/com/droiddeck/launcher/gpu/DriverPairs.kt @@ -15,6 +15,8 @@ object DriverPairs { /** The open non-Adreno drivers (The412Banner/droiddeck-nonadreno-drivers), one pair per driver. */ const val NON_ADRENO = "nad" const val NAD_RADV_XCLIPSE = "nad:radv-xclipse" + /** RADV-Xclipse rebased on JimVulkan's upstream: a manual choice, never Auto. */ + const val NAD_RADV_XCLIPSE_UPSTREAM = "nad:radv-xclipse-upstream" const val NAD_MALI_JM = "nad:panvk-mali-jm" const val NAD_MALI_G710 = "nad:panvk-mali-g710" const val NAD_MALI_G615 = "nad:panvk-mali-g615" @@ -38,6 +40,7 @@ object DriverPairs { private fun describeNonAdreno(key: String): Pair> = when (key) { NAD_RADV_XCLIPSE -> "RADV · Samsung Xclipse" to setOf(Family.POWERVR) + NAD_RADV_XCLIPSE_UPSTREAM -> "RADV upstream · Samsung Xclipse" to setOf(Family.POWERVR) NAD_MALI_JM -> "PanVK · Mali G52-G78" to setOf(Family.MALI_JM) NAD_MALI_G710 -> "PanVK · Mali-G610/G710" to setOf(Family.MALI_G610_G710) NAD_MALI_G615 -> "PanVK · Mali-G615" to setOf(Family.MALI_G615) @@ -64,7 +67,8 @@ object DriverPairs { get() = listOfNotNull(bundle?.tag, display?.tag, linux?.tag).distinct().joinToString(" + ") { it.removePrefix("linux-").removePrefix("DD-Turnip-") } fun suits(gpu: GpuInfo) = when { // The system-driver family covers PowerVR and Xclipse; RADV is only for the Xclipse. - key == NAD_RADV_XCLIPSE -> gpu.family == Family.POWERVR && gpu.name.contains("Xclipse", ignoreCase = true) + key == NAD_RADV_XCLIPSE || key == NAD_RADV_XCLIPSE_UPSTREAM -> + gpu.family == Family.POWERVR && gpu.name.contains("Xclipse", ignoreCase = true) // One binary for Mali v9-v13, not the older G52/G76 in the Job Manager family. key == NAD_MALI_KBASE -> gpu.family.mali && GpuInfo.maliKbaseCovered(gpu.name) key in DEVICE_BUILDS -> gpu.family.mali && GpuInfo.maliNumber(gpu.name) == DEVICE_BUILDS[key] diff --git a/app/src/main/java/com/droiddeck/launcher/gpu/TurnipReleases.kt b/app/src/main/java/com/droiddeck/launcher/gpu/TurnipReleases.kt index 09d7d9f4d..5493d6e32 100644 --- a/app/src/main/java/com/droiddeck/launcher/gpu/TurnipReleases.kt +++ b/app/src/main/java/com/droiddeck/launcher/gpu/TurnipReleases.kt @@ -57,7 +57,7 @@ object TurnipReleases { // Xclipse), both halves in one zip. One release per driver, replaced in place on each // rebuild; every one is a test build, published as a pre-release. Source("Non-Adreno", NON_ADRENO_REPO, prerelease = true) { name, _ -> - val m = Regex("""^(RADV-Xclipse|PanVK-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*?)-Pair\.zip$""").find(name) ?: return@Source null + val m = Regex("""^(RADV-Xclipse(?:-[A-Za-z0-9]+)*?|PanVK-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*?)-Pair\.zip$""").find(name) ?: return@Source null val key = DriverPairs.nonAdreno(m.groupValues[1]) Kind(false, "Android + Linux · " + DriverPairs.nonAdrenoName(key), key, bundle = true) }, diff --git a/app/src/main/java/com/droiddeck/launcher/session/GraphicsReport.kt b/app/src/main/java/com/droiddeck/launcher/session/GraphicsReport.kt index 52af5a21f..4462dc216 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/GraphicsReport.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/GraphicsReport.kt @@ -181,13 +181,23 @@ 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 fallbackLines = File(dir, "vkbridge-fallback").takeIf { it.isFile }?.readLines()?.map { it.trim() }?.filter { it.isNotEmpty() } + val fellBack = fallbackLines?.firstOrNull() + val fallbackReason = fallbackLines?.getOrNull(1).orEmpty() + runCatching { SessionPrefs.setBridgeFellBack(appContext ?: return@runCatching, fellBack != null, fallbackReason) } 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") } + fellBack?.let { + if (fallbackReason == "memory") appendLine("!! MALI BRIDGE FELL BACK: memory sharing = none (no method passed the server's self-test); the session ran on $it") + else appendLine("!! MALI BRIDGE FELL BACK: the self-test failed images or draws; the session ran on $it") + } + appContext?.let { ctx -> + if (SessionPrefs.venus(ctx)) VenusComponent.hostVulkanTooOld(ctx)?.let { + appendLine("!! VENUS OFF: the phone's driver reports Vulkan $it; gamescope needs 1.2 or newer, so the session did not use Venus") + } + } if (audio.contains("AAUDIO_ERROR_DISCONNECTED")) { appendLine("!! SOUND: Android refused the audio output (AAUDIO_ERROR_DISCONNECTED) - rebooting the phone usually fixes this") } diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionArtifacts.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionArtifacts.kt index 8a4927a90..77ae4d139 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionArtifacts.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionArtifacts.kt @@ -188,7 +188,8 @@ object SessionArtifacts { "audio.log, device.txt) were on disk already and are as they were left.\n" + (if (newest) "" else "Steam's logs are not included: a later session has overwritten them.\n") + "crash.log holds Android's crash buffer as of the next app start - if this\n" + - "session died of a crash, the entry is in there unless the device rebooted.\n" + "session died of a crash, the entry is in there unless the device rebooted.\n" + + (if (newest) exitReasons(context) else "") ) val record = Record(dir) if (newest) copySteamLogs(context, dir, record) @@ -203,6 +204,39 @@ object SessionArtifacts { } } + private fun exitReasons(context: Context): String { + if (android.os.Build.VERSION.SDK_INT < 30) return "" + return runCatching { + val am = context.getSystemService(android.app.ActivityManager::class.java) ?: return "" + val reasons = am.getHistoricalProcessExitReasons(context.packageName, 0, 3) + if (reasons.isEmpty()) return "" + val fmt = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.US) + buildString { + append("\nAndroid's record of how this app's recent processes ended (newest first):\n") + reasons.forEach { r -> + append(" ${fmt.format(java.util.Date(r.timestamp))} ${r.processName} pid ${r.pid}: reason ${reasonName(r.reason)}, " + + "status ${r.status}, importance ${r.importance}, pss ${r.pss / 1024} MB, rss ${r.rss / 1024} MB" + + (r.description?.let { " - $it" } ?: "") + "\n") + } + } + }.getOrDefault("") + } + + private fun reasonName(reason: Int): String = when (reason) { + android.app.ApplicationExitInfo.REASON_LOW_MEMORY -> "LOW_MEMORY (killed for memory)" + android.app.ApplicationExitInfo.REASON_SIGNALED -> "SIGNALED" + android.app.ApplicationExitInfo.REASON_CRASH -> "CRASH" + android.app.ApplicationExitInfo.REASON_CRASH_NATIVE -> "CRASH_NATIVE" + android.app.ApplicationExitInfo.REASON_ANR -> "ANR" + android.app.ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> "EXCESSIVE_RESOURCE_USAGE" + android.app.ApplicationExitInfo.REASON_USER_REQUESTED -> "USER_REQUESTED" + android.app.ApplicationExitInfo.REASON_USER_STOPPED -> "USER_STOPPED" + android.app.ApplicationExitInfo.REASON_EXIT_SELF -> "EXIT_SELF" + android.app.ApplicationExitInfo.REASON_DEPENDENCY_DIED -> "DEPENDENCY_DIED" + android.app.ApplicationExitInfo.REASON_OTHER -> "OTHER" + else -> reason.toString() + } + @Synchronized fun prune(context: Context) { val current = SessionPaths.current() diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt index 042dd337f..130261950 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt @@ -441,11 +441,19 @@ object SessionPrefs { */ fun venus(context: Context): Boolean = prefs(context).getBoolean("venus", false) + fun hostVulkanApi(context: Context): String = prefs(context).getString("hostVulkanApi", "").orEmpty() + + fun setHostVulkanApi(context: Context, api: String) { + if (api != hostVulkanApi(context)) prefs(context).edit().putString("hostVulkanApi", api).apply() + } + /** 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() + fun bridgeFellBackForMemory(context: Context): Boolean = prefs(context).getString("bridgeFallbackReason", "") == "memory" + + fun setBridgeFellBack(context: Context, on: Boolean, reason: String = "") { + prefs(context).edit().putBoolean("bridgeFellBack", on).putString("bridgeFallbackReason", if (on) reason else "").apply() } /** 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 f2cf04d6b..c4faa4a0e 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -297,6 +297,12 @@ class SessionService : Service() { private fun runSession(gen: Int) { SessionPrefs.ensurePowerVrAutoDrivers(this) + if (!com.droiddeck.launcher.core.DeviceSupport.adreno()) { + com.droiddeck.launcher.gpu.VulkanInfo.query()["api"]?.let { SessionPrefs.setHostVulkanApi(this, it) } + if (SessionPrefs.venus(this)) VenusComponent.hostVulkanTooOld(this)?.let { + Log.w(TAG, "venus: off for this session - the phone's driver reports Vulkan $it and gamescope needs 1.2 or newer; the session uses the PanVK pair or the bridge") + } + } SessionTerminal.clear() try { LinuxRuntime.writeAccounts(this) diff --git a/app/src/main/java/com/droiddeck/launcher/session/VenusComponent.kt b/app/src/main/java/com/droiddeck/launcher/session/VenusComponent.kt index 5fa6843b9..d4a3f188d 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/VenusComponent.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/VenusComponent.kt @@ -67,7 +67,16 @@ class VenusComponent(private val logFile: File?) : SessionPart() { @JvmStatic fun wanted(context: Context): Boolean = !DeviceSupport.adreno() && !com.droiddeck.launcher.gpu.SystemVulkanDriver.isAvailable() && - SessionPrefs.venus(context) && available(context) + SessionPrefs.venus(context) && available(context) && hostVulkanTooOld(context) == null + + /** The phone driver's Vulkan version when it is below 1.2: gamescope skips such a device, so Venus cannot run. */ + @JvmStatic + fun hostVulkanTooOld(context: Context): String? { + val api = com.droiddeck.launcher.gpu.VulkanInfo.cachedOrNull()?.get("api") ?: SessionPrefs.hostVulkanApi(context) + val parts = api.split('.').mapNotNull { it.trim().toIntOrNull() } + if (parts.size < 2) return null + return api.takeIf { parts[0] < 1 || (parts[0] == 1 && parts[1] < 2) } + } /** Whether this apk carries the Venus component (tools/venus, venus.tzst). */ @JvmStatic diff --git a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndScreen.kt b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndScreen.kt index cce0711fe..6bf15e254 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndScreen.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndScreen.kt @@ -101,6 +101,7 @@ class FrontEndState( val graphicsLogs: Boolean = false, val steamGpuDrawing: Boolean = false, val bridgeFellBack: Boolean = false, + val bridgeFellBackMemory: Boolean = false, val steamGames: List, val emulators: List, val running: String?, @@ -137,6 +138,7 @@ class FrontEndState( val vkBridge: Boolean = false, /** Setup's "Mali (Venus)" (SessionPrefs.venus). */ val venus: Boolean = false, + val venusHostVulkan: String? = null, val showPhantomGate: Boolean = false, val orientation: Int = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, val launcherFullscreen: Boolean = true, diff --git a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt index 66cc93480..0e9a98fc2 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt @@ -214,11 +214,13 @@ internal fun SetupPanel( when (gpu.support) { 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)) + if (!s.adreno && s.venus && s.venusHostVulkan != null) stringResource(R.string.setup_gpu_venus_host_too_old, gpu.displayName(ctx), s.venusHostVulkan) + else 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(if (s.bridgeFellBackMemory) R.string.setup_gpu_bridge_no_memory_sharing else 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.venus && s.venusHostVulkan != null) stringResource(R.string.setup_gpu_venus_host_too_old, gpuName, s.venusHostVulkan) + 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(if (s.bridgeFellBackMemory) R.string.setup_gpu_bridge_no_memory_sharing else 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) }, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6cca0eb52..516a7130f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -721,6 +721,7 @@ Steam interface: GPU drawing 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. The Mali bridge failed its self-test on %1$s last session (images or draws), so the session used the PanVK driver pair instead. + The Mali bridge found no way to share memory with the driver on %1$s last session, so the session used the PanVK driver pair instead. The bridge cannot work on this phone; turning it off keeps the PanVK pair. Each game\'s Proton, DXVK and driver logs in the session\'s logs. Always on when the GPU is not an Adreno. Latest session logs Saved session logs @@ -2237,6 +2238,7 @@ Achievement unlocked Mali (Venus): off Mali (Venus): on + The driver on %1$s reports Vulkan %2$s. Mali (Venus) needs Vulkan 1.2 or newer (gamescope does not start on older), so sessions skip Venus and use the PanVK driver pair instead. Mali bridge: off %1$s cannot use the Mali bridge (it needs a Mali GPU without a system-driver route), so this switch has no effect here. Mali bridge: on diff --git a/app/src/test/java/com/droiddeck/launcher/gpu/DriverPairsTest.kt b/app/src/test/java/com/droiddeck/launcher/gpu/DriverPairsTest.kt index 993e3800f..b5686401d 100644 --- a/app/src/test/java/com/droiddeck/launcher/gpu/DriverPairsTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/gpu/DriverPairsTest.kt @@ -201,6 +201,22 @@ class DriverPairsTest { assertFalse(DriverPairs.isNonAdreno(null)) } + @Test + fun upstreamRadvIsAManualXclipseChoiceAndAutoKeepsOurs() { + val asset = { id: String -> TurnipReleases.Asset("Non-Adreno", id.lowercase(), "$id-Pair.zip", "https://example/$id", 9_000_000, + false, "label", "0".repeat(64), DriverPairs.nonAdreno(id), bundle = true) } + val pairs = DriverPairs.from(TurnipReleases.Check(listOf(asset("RADV-Xclipse"), asset("RADV-Xclipse-upstream")), emptyList(), emptyList(), 0L)) + val xclipse = GpuInfo("Samsung Xclipse 940", 0, Family.POWERVR, "", false) + val upstream = pairs.single { it.key == DriverPairs.NAD_RADV_XCLIPSE_UPSTREAM } + assertEquals("nad:radv-xclipse-upstream", DriverPairs.nonAdreno("RADV-Xclipse-upstream")) + assertTrue(upstream.complete) + assertTrue(upstream.suits(xclipse)) + assertFalse(upstream.suits(GpuInfo("PowerVR", 0, Family.POWERVR, "", false))) + assertEquals("RADV upstream · Samsung Xclipse", upstream.name) + assertEquals(DriverPairs.NAD_RADV_XCLIPSE, DriverPairs.recommendedKey(xclipse, pairs)) + assertTrue(DriverPairs.isNonAdreno("RADV-Xclipse-upstream")) + } + @Test fun deviceBuildsAreOfferedOnlyForTheirMaliAndNeverByAuto() { fun bundle(id: String) = TurnipReleases.Asset("Non-Adreno", id.lowercase(), "$id-Pair.zip", "https://example/$id", 9_000_000, diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session index a49b9a40f..421100809 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session @@ -132,13 +132,26 @@ if [ "${BL_VKBRIDGE:-0}" = 1 ]; then step "vkbridge self-test" bl_vkb_smoke=$(timeout 60 /usr/local/bin/droiddeck-vkbridge-smoke 2>&1) printf '%s\n' "$bl_vkb_smoke" | sed 's/^/== vkbridge: /' - if printf '%s\n' "$bl_vkb_smoke" | grep -qE '^FAIL (image clear|draw)' && [ -r "${BL_VKBRIDGE_FALLBACK_ICD:-}" ]; then + bl_vkb_why= + if printf '%s\n' "$bl_vkb_smoke" | grep -qE '^FAIL memory sharing'; then + bl_vkb_why=memory + elif printf '%s\n' "$bl_vkb_smoke" | grep -qE '^FAIL (image clear|draw)'; then + bl_vkb_why=draw + fi + if [ -n "$bl_vkb_why" ] && [ -r "${BL_VKBRIDGE_FALLBACK_ICD:-}" ]; then export VK_DRIVER_FILES=$BL_VKBRIDGE_FALLBACK_ICD VK_ICD_FILENAMES=$BL_VKBRIDGE_FALLBACK_ICD BL_VKBRIDGE=0 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 + if [ "$bl_vkb_why" = memory ]; then + echo "== vkbridge: memory sharing = none (no method passed the server's self-test); this session uses the PanVK pair instead ($BL_VKBRIDGE_FALLBACK_ICD)" + else + echo "== vkbridge: self-test failed images or draws; this session uses the PanVK pair instead ($BL_VKBRIDGE_FALLBACK_ICD)" + fi + [ -n "${BL_DEBUG_DIR:-}" ] && printf '%s\n%s\n' "$BL_VKBRIDGE_FALLBACK_ICD" "$bl_vkb_why" > "$BL_DEBUG_DIR/vkbridge-fallback" 2>/dev/null + elif [ -n "$bl_vkb_why" ]; then + echo "== vkbridge: self-test failed ($bl_vkb_why) and no PanVK pair is installed to fall back on; expect a black screen or a lost device - set a Mali driver pair in Setup" fi + unset bl_vkb_why 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: /' @@ -172,6 +185,15 @@ if [ "${BL_GAME_LOGS:-0}" = 1 ] && [ -z "${BL_INSIDE:-}" ] && [ -n "${BL_DEBUG_D if command -v vulkaninfo >/dev/null 2>&1; then timeout 10 vulkaninfo --summary echo "== vulkaninfo exit: $?" + if timeout 20 vulkaninfo > "$BL_DEBUG_DIR/vulkan-full.txt" 2>&1; then + echo "== device extensions (full list in vulkan-full.txt):" + sed -n '/^Device Extensions:/,/^$/p' "$BL_DEBUG_DIR/vulkan-full.txt" | grep -o 'VK_[A-Za-z0-9_]*' | sort -u | tr '\n' ' ' | fold -s -w 200 + echo + echo "== key features:" + grep -E '^[[:space:]]+(robustBufferAccess2?|robustImageAccess2?|nullDescriptor|logicOp|fillModeNonSolid|shaderClipDistance|shaderCullDistance|dualSrcBlend|geometryShader|scalarBlockLayout|timelineSemaphore|dynamicRendering|synchronization2|maintenance4|maintenance5|maintenance6|shaderDrawParameters|descriptorIndexing|bufferDeviceAddress|vulkanMemoryModel|hostQueryReset|imagelessFramebuffer|samplerYcbcrConversion|shaderInt64|shaderFloat64|shaderInt16|shaderFloat16|storageBuffer16BitAccess|extendedDynamicState[23]?|vertexInputDynamicState|customBorderColors|customBorderColorWithoutFormat|depthClipEnable|multiViewport|textureCompressionBC|indexTypeUint8|rectangularLines|bresenhamLines|transformFeedback|geometryStreams|graphicsPipelineLibrary|pushDescriptor|primitiveTopologyListRestart|dynamicRenderingLocalRead|shaderDemoteToHelperInvocation|subgroupSizeControl|computeFullSubgroups|inlineUniformBlock|pipelineCreationCacheControl) = ' "$BL_DEBUG_DIR/vulkan-full.txt" | sed 's/^[[:space:]]*/ /' | sort -u + else + echo "== full vulkaninfo failed or timed out (exit $?)" + fi else echo "== vulkaninfo is not in this runtime; GPU nodes:" ls -l /dev/kgsl-3d0 /dev/mali0 /dev/dri 2>&1 @@ -994,7 +1016,10 @@ BUSCONF LIBGL_DEBUG=verbose MESA_LOG_LEVEL=debug timeout 20 /usr/local/bin/droiddeck-glprobe 2>&1 | grep -v drirc | head -60 | sed 's/^/== glprobe: /' if [ "${PIPESTATUS[0]}" = 1 ]; then VK_LOADER_DEBUG=error,warn timeout 20 /usr/local/bin/droiddeck-glprobe 2>&1 | grep -v drirc | head -40 | sed 's/^/== glprobe (loader): /' - DROIDDECK_MALI_COMPAT_TRACE=1 MESA_DEBUG=1 LIBGL_DEBUG=verbose timeout 20 /usr/local/bin/droiddeck-glprobe 2>&1 | grep -v drirc | head -80 | sed 's/^/== glprobe (trace): /' + DROIDDECK_MALI_COMPAT_TRACE=1 MESA_DEBUG=1 MESA_LOG_LEVEL=debug LIBGL_DEBUG=verbose ZINK_DEBUG=mem VN_DEBUG=init,result,log_ctx_info \ + timeout 30 /usr/local/bin/droiddeck-glprobe > "${BL_DEBUG_DIR:-/tmp}/glprobe-trace.txt" 2>&1 + grep -v drirc "${BL_DEBUG_DIR:-/tmp}/glprobe-trace.txt" | head -120 | sed 's/^/== glprobe (trace): /' + echo "== glprobe (trace): last pre-device query before Zink gave up: $(grep 'droiddeck-mali-compat: trace:' "${BL_DEBUG_DIR:-/tmp}/glprobe-trace.txt" | grep -v 'trace: extensions:' | tail -1)" command -v vulkaninfo >/dev/null 2>&1 && DROIDDECK_MALI_COMPAT_TRACE=1 timeout 20 vulkaninfo --summary 2>&1 | head -40 | sed 's/^/== glprobe (vulkaninfo): /' bl_gl_ok= for bl_variant in "ZINK_DEBUG=" "ZINK_DEBUG= ZINK_DESCRIPTORS=auto" "ZINK_DEBUG= LIBGL_KOPPER_DISABLE=true" "ZINK_DEBUG= DROIDDECK_MALI_COMPAT=0"; do diff --git a/tools/venus/layer/layer.c b/tools/venus/layer/layer.c index fbb973f0e..4df8a54ad 100644 --- a/tools/venus/layer/layer.c +++ b/tools/venus/layer/layer.c @@ -414,8 +414,10 @@ static VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysi inst->EnumerateDeviceExtensionProperties(physical_device, NULL, &own, all); memcpy(all + own, extra, extra_count * sizeof(*extra)); uint32_t total = own + extra_count; - if (tracing()) + if (tracing()) { layer_log("trace: vkEnumerateDeviceExtensionProperties: %u from the driver, %u added by the layer", own, extra_count); + trace_extension_names(all, total); + } uint32_t n = *count < total ? *count : total; memcpy(properties, all, n * sizeof(*all)); free(all); @@ -470,6 +472,8 @@ static VKAPI_ATTR VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo LOAD_KHR(GetPhysicalDeviceImageFormatProperties2) #undef LOAD #undef LOAD_KHR + if (tracing()) + trace_load(inst); /* Without the Vulkan 1.1 queries there is no way to answer the chained ones. */ if (!inst->GetPhysicalDeviceFeatures2 || !inst->GetPhysicalDeviceProperties2 || !inst->GetPhysicalDeviceFormatProperties2 || !inst->GetPhysicalDeviceImageFormatProperties2) @@ -781,6 +785,11 @@ static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance i if (!strcmp(name, "vkCreateDevice")) return (PFN_vkVoidFunction)CreateDevice; PFN_vkVoidFunction next = inst->gipa(instance, name); + if (next && tracing()) { + PFN_vkVoidFunction traced = trace_hook(inst, name); + if (traced) + return traced; + } if (!next || !inst->active) return next; for (size_t i = 0; i < sizeof(physical_device_hooks) / sizeof(physical_device_hooks[0]); i++) { diff --git a/tools/venus/layer/mali_compat.h b/tools/venus/layer/mali_compat.h index 9a356f1cb..055034ab6 100644 --- a/tools/venus/layer/mali_compat.h +++ b/tools/venus/layer/mali_compat.h @@ -70,6 +70,13 @@ struct instance { PFN_vkGetPhysicalDeviceFormatProperties2 GetPhysicalDeviceFormatProperties2; PFN_vkGetPhysicalDeviceImageFormatProperties GetPhysicalDeviceImageFormatProperties; PFN_vkGetPhysicalDeviceImageFormatProperties2 GetPhysicalDeviceImageFormatProperties2; + PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices; + PFN_vkGetPhysicalDeviceMemoryProperties2 GetPhysicalDeviceMemoryProperties2; + PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; + PFN_vkGetPhysicalDeviceQueueFamilyProperties2 GetPhysicalDeviceQueueFamilyProperties2; + PFN_vkGetPhysicalDeviceExternalBufferProperties GetPhysicalDeviceExternalBufferProperties; + PFN_vkGetPhysicalDeviceExternalSemaphoreProperties GetPhysicalDeviceExternalSemaphoreProperties; + PFN_vkGetPhysicalDeviceExternalFenceProperties GetPhysicalDeviceExternalFenceProperties; pthread_mutex_t lock; struct physical_device physical_devices[8]; uint32_t physical_device_count; @@ -273,6 +280,11 @@ struct instance *instance_from_handle(const void *dispatchable); /* What the GPU lacks of the features the layer can make up for. */ uint32_t physical_device_gaps(struct instance *inst, VkPhysicalDevice physical_device); +/* trace.c (DROIDDECK_MALI_COMPAT_TRACE=1) */ +void trace_load(struct instance *inst); +PFN_vkVoidFunction trace_hook(struct instance *inst, const char *name); +void trace_extension_names(const VkExtensionProperties *ext, uint32_t n); + /* draw.c */ VKAPI_ATTR void VKAPI_CALL CmdDrawIndirect(VkCommandBuffer cmd, VkBuffer buffer, VkDeviceSize offset, uint32_t draw_count, uint32_t stride); diff --git a/tools/venus/layer/trace.c b/tools/venus/layer/trace.c new file mode 100644 index 000000000..f02bab5c9 --- /dev/null +++ b/tools/venus/layer/trace.c @@ -0,0 +1,233 @@ +/* + * DROIDDECK_MALI_COMPAT_TRACE=1: the physical-device queries an application makes before + * vkCreateDevice, logged as they pass through. Zink on Venus over an older Mali driver gave up + * after vkGetPhysicalDeviceProperties2 without a word (its release builds print nothing there), + * so the last query in the log is where it stopped. + */ +#include "mali_compat.h" + +#include +#include +#include +#include + +static uint32_t format_queries; + +static bool format_worth_logging(uint32_t n, VkFormatFeatureFlags linear, VkFormatFeatureFlags optimal, VkFormatFeatureFlags buffer) +{ + return n == 1 || n % 25 == 0 || (!linear && !optimal && !buffer); +} + +static VKAPI_ATTR VkResult VKAPI_CALL TraceEnumeratePhysicalDevices(VkInstance instance, uint32_t *count, VkPhysicalDevice *devices) +{ + struct instance *inst = instance_from_handle(instance); + VkResult r = inst->EnumeratePhysicalDevices(instance, count, devices); + layer_log("trace: vkEnumeratePhysicalDevices (%s): %d, %u device(s)", devices ? "list" : "count", (int)r, count ? *count : 0); + return r; +} + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceMemoryProperties(VkPhysicalDevice pd, VkPhysicalDeviceMemoryProperties *p) +{ + struct instance *inst = instance_from_handle(pd); + inst->GetPhysicalDeviceMemoryProperties(pd, p); + char types[384] = ""; + size_t o = 0; + for (uint32_t i = 0; i < p->memoryTypeCount && o + 16 < sizeof(types); i++) + o += (size_t)snprintf(types + o, sizeof(types) - o, " %u:h%u/0x%x", i, p->memoryTypes[i].heapIndex, + (unsigned)p->memoryTypes[i].propertyFlags); + layer_log("trace: vkGetPhysicalDeviceMemoryProperties: %u heaps, %u types:%s", p->memoryHeapCount, p->memoryTypeCount, types); +} + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceMemoryProperties2(VkPhysicalDevice pd, VkPhysicalDeviceMemoryProperties2 *p) +{ + struct instance *inst = instance_from_handle(pd); + inst->GetPhysicalDeviceMemoryProperties2(pd, p); + char chain[160] = ""; + size_t o = 0; + for (VkBaseOutStructure *s = p->pNext; s && o + 16 < sizeof(chain); s = s->pNext) + o += (size_t)snprintf(chain + o, sizeof(chain) - o, " %d", (int)s->sType); + layer_log("trace: vkGetPhysicalDeviceMemoryProperties2: %u heaps, %u types, chain:%s", p->memoryProperties.memoryHeapCount, + p->memoryProperties.memoryTypeCount, chain); +} + +static void log_queue_families(const char *what, uint32_t n, const VkQueueFamilyProperties *(*at)(const void *, uint32_t), const void *arr) +{ + char fams[256] = ""; + size_t o = 0; + for (uint32_t i = 0; arr && i < n && o + 24 < sizeof(fams); i++) { + const VkQueueFamilyProperties *q = at(arr, i); + o += (size_t)snprintf(fams + o, sizeof(fams) - o, " %u:0x%x*%u", i, (unsigned)q->queueFlags, q->queueCount); + } + layer_log("trace: %s: %u famil%s%s", what, n, n == 1 ? "y" : "ies", fams); +} + +static const VkQueueFamilyProperties *qf1(const void *a, uint32_t i) { return &((const VkQueueFamilyProperties *)a)[i]; } +static const VkQueueFamilyProperties *qf2(const void *a, uint32_t i) { return &((const VkQueueFamilyProperties2 *)a)[i].queueFamilyProperties; } + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice pd, uint32_t *n, VkQueueFamilyProperties *p) +{ + struct instance *inst = instance_from_handle(pd); + inst->GetPhysicalDeviceQueueFamilyProperties(pd, n, p); + log_queue_families("vkGetPhysicalDeviceQueueFamilyProperties", n ? *n : 0, qf1, p); +} + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice pd, uint32_t *n, VkQueueFamilyProperties2 *p) +{ + struct instance *inst = instance_from_handle(pd); + inst->GetPhysicalDeviceQueueFamilyProperties2(pd, n, p); + log_queue_families("vkGetPhysicalDeviceQueueFamilyProperties2", n ? *n : 0, qf2, p); +} + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceFormatProperties(VkPhysicalDevice pd, VkFormat format, VkFormatProperties *p) +{ + struct instance *inst = instance_from_handle(pd); + if (inst->active) GetPhysicalDeviceFormatProperties(pd, format, p); + else inst->GetPhysicalDeviceFormatProperties(pd, format, p); + uint32_t n = __atomic_add_fetch(&format_queries, 1, __ATOMIC_RELAXED); + if (format_worth_logging(n, p->linearTilingFeatures, p->optimalTilingFeatures, p->bufferFeatures)) + layer_log("trace: vkGetPhysicalDeviceFormatProperties #%u: format %d linear 0x%x optimal 0x%x buffer 0x%x", n, (int)format, + (unsigned)p->linearTilingFeatures, (unsigned)p->optimalTilingFeatures, (unsigned)p->bufferFeatures); +} + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceFormatProperties2(VkPhysicalDevice pd, VkFormat format, VkFormatProperties2 *p) +{ + struct instance *inst = instance_from_handle(pd); + if (inst->active) GetPhysicalDeviceFormatProperties2(pd, format, p); + else inst->GetPhysicalDeviceFormatProperties2(pd, format, p); + uint32_t n = __atomic_add_fetch(&format_queries, 1, __ATOMIC_RELAXED); + const VkFormatProperties *f = &p->formatProperties; + if (format_worth_logging(n, f->linearTilingFeatures, f->optimalTilingFeatures, f->bufferFeatures)) + layer_log("trace: vkGetPhysicalDeviceFormatProperties2 #%u: format %d linear 0x%x optimal 0x%x buffer 0x%x", n, (int)format, + (unsigned)f->linearTilingFeatures, (unsigned)f->optimalTilingFeatures, (unsigned)f->bufferFeatures); +} + +static VKAPI_ATTR VkResult VKAPI_CALL TraceGetPhysicalDeviceImageFormatProperties(VkPhysicalDevice pd, VkFormat format, VkImageType type, + VkImageTiling tiling, VkImageUsageFlags usage, + VkImageCreateFlags flags, VkImageFormatProperties *p) +{ + struct instance *inst = instance_from_handle(pd); + VkResult r = inst->active ? GetPhysicalDeviceImageFormatProperties(pd, format, type, tiling, usage, flags, p) + : inst->GetPhysicalDeviceImageFormatProperties(pd, format, type, tiling, usage, flags, p); + if (r != VK_SUCCESS) + layer_log("trace: vkGetPhysicalDeviceImageFormatProperties: format %d type %d tiling %d usage 0x%x flags 0x%x -> %d", (int)format, + (int)type, (int)tiling, (unsigned)usage, (unsigned)flags, (int)r); + return r; +} + +static VKAPI_ATTR VkResult VKAPI_CALL TraceGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice pd, const VkPhysicalDeviceImageFormatInfo2 *info, + VkImageFormatProperties2 *p) +{ + struct instance *inst = instance_from_handle(pd); + VkResult r = inst->active ? GetPhysicalDeviceImageFormatProperties2(pd, info, p) : inst->GetPhysicalDeviceImageFormatProperties2(pd, info, p); + if (r != VK_SUCCESS) + layer_log("trace: vkGetPhysicalDeviceImageFormatProperties2: format %d type %d tiling %d usage 0x%x flags 0x%x -> %d", (int)info->format, + (int)info->type, (int)info->tiling, (unsigned)info->usage, (unsigned)info->flags, (int)r); + return r; +} + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceExternalBufferProperties(VkPhysicalDevice pd, const VkPhysicalDeviceExternalBufferInfo *info, + VkExternalBufferProperties *p) +{ + struct instance *inst = instance_from_handle(pd); + inst->GetPhysicalDeviceExternalBufferProperties(pd, info, p); + layer_log("trace: vkGetPhysicalDeviceExternalBufferProperties: handle 0x%x usage 0x%x -> features 0x%x", (unsigned)info->handleType, + (unsigned)info->usage, (unsigned)p->externalMemoryProperties.externalMemoryFeatures); +} + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceExternalSemaphoreProperties(VkPhysicalDevice pd, + const VkPhysicalDeviceExternalSemaphoreInfo *info, + VkExternalSemaphoreProperties *p) +{ + struct instance *inst = instance_from_handle(pd); + inst->GetPhysicalDeviceExternalSemaphoreProperties(pd, info, p); + layer_log("trace: vkGetPhysicalDeviceExternalSemaphoreProperties: handle 0x%x -> features 0x%x", (unsigned)info->handleType, + (unsigned)p->externalSemaphoreFeatures); +} + +static VKAPI_ATTR void VKAPI_CALL TraceGetPhysicalDeviceExternalFenceProperties(VkPhysicalDevice pd, const VkPhysicalDeviceExternalFenceInfo *info, + VkExternalFenceProperties *p) +{ + struct instance *inst = instance_from_handle(pd); + inst->GetPhysicalDeviceExternalFenceProperties(pd, info, p); + layer_log("trace: vkGetPhysicalDeviceExternalFenceProperties: handle 0x%x -> features 0x%x", (unsigned)info->handleType, + (unsigned)p->externalFenceFeatures); +} + +void trace_extension_names(const VkExtensionProperties *ext, uint32_t n) +{ + char line[400]; + size_t o = 0; + line[0] = 0; + for (uint32_t i = 0; i < n; i++) { + size_t len = strlen(ext[i].extensionName); + if (o && o + len + 2 > sizeof(line)) { + layer_log("trace: extensions:%s", line); + o = 0; + line[0] = 0; + } + o += (size_t)snprintf(line + o, sizeof(line) - o, " %s", ext[i].extensionName); + } + if (o) layer_log("trace: extensions:%s", line); +} + +void trace_load(struct instance *inst) +{ +#define T_LOAD(name) inst->name = (PFN_vk##name)inst->gipa(inst->handle, "vk" #name); +#define T_LOAD_KHR(name) \ + if (!inst->name) \ + inst->name = (PFN_vk##name)inst->gipa(inst->handle, "vk" #name "KHR"); + T_LOAD(EnumeratePhysicalDevices) + T_LOAD(GetPhysicalDeviceMemoryProperties2) + T_LOAD_KHR(GetPhysicalDeviceMemoryProperties2) + T_LOAD(GetPhysicalDeviceQueueFamilyProperties) + T_LOAD(GetPhysicalDeviceQueueFamilyProperties2) + T_LOAD_KHR(GetPhysicalDeviceQueueFamilyProperties2) + T_LOAD(GetPhysicalDeviceExternalBufferProperties) + T_LOAD_KHR(GetPhysicalDeviceExternalBufferProperties) + T_LOAD(GetPhysicalDeviceExternalSemaphoreProperties) + T_LOAD_KHR(GetPhysicalDeviceExternalSemaphoreProperties) + T_LOAD(GetPhysicalDeviceExternalFenceProperties) + T_LOAD_KHR(GetPhysicalDeviceExternalFenceProperties) +#undef T_LOAD +#undef T_LOAD_KHR +} + +struct trace_hook { + const char *name; + PFN_vkVoidFunction function; + size_t next_offset; +}; + +#define TH(fn, field) {"vk" #fn, (PFN_vkVoidFunction)Trace##field, offsetof(struct instance, field)} + +static const struct trace_hook trace_hooks[] = { + TH(EnumeratePhysicalDevices, EnumeratePhysicalDevices), + TH(GetPhysicalDeviceMemoryProperties, GetPhysicalDeviceMemoryProperties), + TH(GetPhysicalDeviceMemoryProperties2, GetPhysicalDeviceMemoryProperties2), + TH(GetPhysicalDeviceMemoryProperties2KHR, GetPhysicalDeviceMemoryProperties2), + TH(GetPhysicalDeviceQueueFamilyProperties, GetPhysicalDeviceQueueFamilyProperties), + TH(GetPhysicalDeviceQueueFamilyProperties2, GetPhysicalDeviceQueueFamilyProperties2), + TH(GetPhysicalDeviceQueueFamilyProperties2KHR, GetPhysicalDeviceQueueFamilyProperties2), + TH(GetPhysicalDeviceFormatProperties, GetPhysicalDeviceFormatProperties), + TH(GetPhysicalDeviceFormatProperties2, GetPhysicalDeviceFormatProperties2), + TH(GetPhysicalDeviceFormatProperties2KHR, GetPhysicalDeviceFormatProperties2), + TH(GetPhysicalDeviceImageFormatProperties, GetPhysicalDeviceImageFormatProperties), + TH(GetPhysicalDeviceImageFormatProperties2, GetPhysicalDeviceImageFormatProperties2), + TH(GetPhysicalDeviceImageFormatProperties2KHR, GetPhysicalDeviceImageFormatProperties2), + TH(GetPhysicalDeviceExternalBufferProperties, GetPhysicalDeviceExternalBufferProperties), + TH(GetPhysicalDeviceExternalBufferPropertiesKHR, GetPhysicalDeviceExternalBufferProperties), + TH(GetPhysicalDeviceExternalSemaphoreProperties, GetPhysicalDeviceExternalSemaphoreProperties), + TH(GetPhysicalDeviceExternalSemaphorePropertiesKHR, GetPhysicalDeviceExternalSemaphoreProperties), + TH(GetPhysicalDeviceExternalFenceProperties, GetPhysicalDeviceExternalFenceProperties), + TH(GetPhysicalDeviceExternalFencePropertiesKHR, GetPhysicalDeviceExternalFenceProperties), +}; + +PFN_vkVoidFunction trace_hook(struct instance *inst, const char *name) +{ + for (size_t i = 0; i < sizeof(trace_hooks) / sizeof(trace_hooks[0]); i++) { + if (strcmp(name, trace_hooks[i].name)) continue; + void *next = *(void **)((char *)inst + trace_hooks[i].next_offset); + return next ? trace_hooks[i].function : NULL; + } + return NULL; +} diff --git a/tools/vkbridge/tests/smoke.c b/tools/vkbridge/tests/smoke.c index 3db33567e..1913df1c9 100644 --- a/tools/vkbridge/tests/smoke.c +++ b/tools/vkbridge/tests/smoke.c @@ -473,6 +473,11 @@ int main(int argc, char **argv) vkGetPhysicalDeviceMemoryProperties(pd, &mp); for (uint32_t i = 0; i < mp.memoryTypeCount; i++) printf(" memory type %u: heap %u flags 0x%x\n", i, mp.memoryTypes[i].heapIndex, mp.memoryTypes[i].propertyFlags); + int host_visible = 0; + for (uint32_t i = 0; i < mp.memoryTypeCount; i++) + if (mp.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) host_visible = 1; + /* The server offers HOST_VISIBLE only through a memory-sharing method that passed its self-test. */ + check(host_visible, "memory sharing", host_visible ? "" : "none (no host-visible memory type; the server found no way to share memory with this driver)"); uint32_t nq = 0; vkGetPhysicalDeviceQueueFamilyProperties(pd, &nq, NULL); VkQueueFamilyProperties qp[16];