Non-Adreno round 7: bridge command-buffer guard, Venus Zink trace, PowerVR mangoapp, Xclipse label

- vkbridge server: every command buffer a device hands out is tracked; Begin,
  End (stream replay) and QueueSubmit refuse one it never allocated or has
  freed (VK_ERROR_DEVICE_LOST, logged) instead of passing it to the driver,
  where Mali's vkBeginCommandBuffer crashed the app (SIGSEGV 0x10, Steam's
  Zink on an Edge 60 and a Tab S11).
- vkbridge server: a fault inside the driver while serving a request cuts
  that client off (SIGSEGV/SIGBUS guard per request) and leaves its objects
  alone; the app and the other clients carry on. Other faults go to the
  previous handler.
- vkbridge server: the pipeline cache directory is copied; the JNI string it
  pointed at was freed (garbage in the cache path).
- Venus: DROIDDECK_MALI_COMPAT_TRACE=1 makes the Mali compatibility layer log
  what an application queries before vkCreateDevice; a failed glprobe adds a
  traced run and vulkaninfo --summary under the same environment.
- PowerVR: mangoapp draws with llvmpipe (Zink could not make it a swapchain
  and it crashed at every start).
- Setup: an Xclipse GPU reads "Experimental: RADV or the system Xclipse
  driver", not the PowerVR line.
This commit is contained in:
The412Banner committed 2026-10-13 22:39:06 -04:00
1 parent ab54536886
commit e40cd36151
10 files changed
+324 -3

No files matched your search

+194
View File
@@ -0,0 +1,194 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
*
* The command buffers each device has handed out, so that a handle the driver never gave (or has
* since freed, with its pool) is refused here instead of reaching the driver. Handles arrive from
* clients as plain numbers; one the driver did not know took the whole app down inside Mali's
* vkBeginCommandBuffer (SIGSEGV at 0x10, Steam's Zink on an Edge 60 and a Tab S11). A refused
* command buffer fails its call with VK_ERROR_DEVICE_LOST, which the client reports, and is logged
* once per handle.
*/
#define _GNU_SOURCE
#include "vkb_server.h"
#include <stdlib.h>
#include <string.h>
#define CBT_BUCKETS 1024
typedef struct cbt_entry {
VkCommandBuffer cb;
VkCommandPool pool;
struct cbt_entry *next;
} cbt_entry;
typedef struct vkb_cbtrack {
pthread_mutex_t lock;
cbt_entry *b[CBT_BUCKETS];
VkCommandBuffer last_refused;
PFN_vkAllocateCommandBuffers alloc;
PFN_vkFreeCommandBuffers free_cbs;
PFN_vkDestroyCommandPool destroy_pool;
PFN_vkBeginCommandBuffer begin;
PFN_vkQueueSubmit submit;
PFN_vkQueueSubmit2 submit2;
} vkb_cbtrack;
static unsigned bucket(VkCommandBuffer cb)
{
uint64_t k = (uint64_t)(uintptr_t)cb;
k ^= k >> 31;
k *= 0x9e3779b97f4a7c15ull;
return (unsigned)(k >> 54) & (CBT_BUCKETS - 1);
}
static vkb_cbtrack *cur(void)
{
vkb_srv_table *t = vkb_srv_current_table();
return t ? t->cbtrack : NULL;
}
static int known_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;
}
int vkb_cbtrack_known(vkb_srv_table *t, VkCommandBuffer cb, const char *what)
{
vkb_cbtrack *k = t ? t->cbtrack : NULL;
if (!k) return cb != VK_NULL_HANDLE;
pthread_mutex_lock(&k->lock);
int ok = cb && known_locked(k, cb);
int first = !ok && k->last_refused != cb;
if (!ok) k->last_refused = cb;
pthread_mutex_unlock(&k->lock);
if (first)
VKB_ERR("device %u: %s on command buffer %p, which this device never allocated or has freed; refused (the client "
"gets VK_ERROR_DEVICE_LOST)", t->id, what, (void *)cb);
return ok;
}
static VKAPI_ATTR VkResult VKAPI_CALL cbt_AllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo *info,
VkCommandBuffer *out)
{
vkb_cbtrack *k = cur();
VkResult r = k->alloc(device, info, out);
if (r != VK_SUCCESS || !info) return r;
pthread_mutex_lock(&k->lock);
for (uint32_t i = 0; i < info->commandBufferCount; i++) {
if (!out[i] || known_locked(k, out[i])) continue;
cbt_entry *e = malloc(sizeof(*e));
if (!e) continue;
e->cb = out[i];
e->pool = info->commandPool;
unsigned h = bucket(out[i]);
e->next = k->b[h];
k->b[h] = e;
}
pthread_mutex_unlock(&k->lock);
return r;
}
static void forget_locked(vkb_cbtrack *k, VkCommandBuffer cb, VkCommandPool pool)
{
for (unsigned h = cb ? bucket(cb) : 0; h < CBT_BUCKETS; h = cb ? CBT_BUCKETS : h + 1) {
cbt_entry **pp = &k->b[h];
while (*pp) {
cbt_entry *e = *pp;
if ((cb && e->cb == cb) || (!cb && e->pool == pool)) {
*pp = e->next;
free(e);
} else {
pp = &e->next;
}
}
}
}
static VKAPI_ATTR void VKAPI_CALL cbt_FreeCommandBuffers(VkDevice device, VkCommandPool pool, uint32_t n, const VkCommandBuffer *cbs)
{
vkb_cbtrack *k = cur();
pthread_mutex_lock(&k->lock);
for (uint32_t i = 0; i < n; i++)
if (cbs[i]) forget_locked(k, cbs[i], pool);
pthread_mutex_unlock(&k->lock);
k->free_cbs(device, pool, n, cbs);
}
static VKAPI_ATTR void VKAPI_CALL cbt_DestroyCommandPool(VkDevice device, VkCommandPool pool, const VkAllocationCallbacks *a)
{
vkb_cbtrack *k = cur();
if (pool) {
pthread_mutex_lock(&k->lock);
forget_locked(k, VK_NULL_HANDLE, pool);
pthread_mutex_unlock(&k->lock);
}
k->destroy_pool(device, pool, a);
}
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;
return k->begin(cb, info);
}
static VKAPI_ATTR VkResult VKAPI_CALL cbt_QueueSubmit(VkQueue queue, uint32_t n, const VkSubmitInfo *s, VkFence fence)
{
vkb_cbtrack *k = cur();
vkb_srv_table *t = vkb_srv_current_table();
for (uint32_t i = 0; i < n; i++)
for (uint32_t j = 0; j < s[i].commandBufferCount; j++)
if (!vkb_cbtrack_known(t, s[i].pCommandBuffers[j], "vkQueueSubmit")) return VK_ERROR_DEVICE_LOST;
return k->submit(queue, n, s, fence);
}
static VKAPI_ATTR VkResult VKAPI_CALL cbt_QueueSubmit2(VkQueue queue, uint32_t n, const VkSubmitInfo2 *s, VkFence fence)
{
vkb_cbtrack *k = cur();
vkb_srv_table *t = vkb_srv_current_table();
for (uint32_t i = 0; i < n; i++)
for (uint32_t j = 0; j < s[i].commandBufferInfoCount; j++)
if (!vkb_cbtrack_known(t, s[i].pCommandBufferInfos[j].commandBuffer, "vkQueueSubmit2")) return VK_ERROR_DEVICE_LOST;
return k->submit2(queue, n, s, fence);
}
void vkb_cbtrack_device_init(vkb_srv_table *t)
{
if (!t->dt.vkAllocateCommandBuffers || !t->dt.vkFreeCommandBuffers || !t->dt.vkDestroyCommandPool || !t->dt.vkBeginCommandBuffer ||
!t->dt.vkQueueSubmit)
return;
vkb_cbtrack *k = calloc(1, sizeof(*k));
if (!k) return;
pthread_mutex_init(&k->lock, NULL);
k->alloc = t->dt.vkAllocateCommandBuffers;
k->free_cbs = t->dt.vkFreeCommandBuffers;
k->destroy_pool = t->dt.vkDestroyCommandPool;
k->begin = t->dt.vkBeginCommandBuffer;
k->submit = t->dt.vkQueueSubmit;
k->submit2 = t->dt.vkQueueSubmit2;
t->cbtrack = k;
t->dt.vkAllocateCommandBuffers = cbt_AllocateCommandBuffers;
t->dt.vkFreeCommandBuffers = cbt_FreeCommandBuffers;
t->dt.vkDestroyCommandPool = cbt_DestroyCommandPool;
t->dt.vkBeginCommandBuffer = cbt_BeginCommandBuffer;
t->dt.vkQueueSubmit = cbt_QueueSubmit;
if (k->submit2) t->dt.vkQueueSubmit2 = cbt_QueueSubmit2;
}
void vkb_cbtrack_device_destroy(vkb_srv_table *t)
{
vkb_cbtrack *k = t->cbtrack;
if (!k) return;
for (unsigned h = 0; h < CBT_BUCKETS; h++) {
while (k->b[h]) {
cbt_entry *e = k->b[h];
k->b[h] = e->next;
free(e);
}
}
pthread_mutex_destroy(&k->lock);
free(k);
t->cbtrack = NULL;
}
+74 -1
View File
@@ -8,6 +8,8 @@
#include "vkb_server.h"
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
@@ -102,8 +104,14 @@ void vkb_proc_cleanup(vkb_srv_proc *p)
if (mine) tables[i] = NULL;
pthread_mutex_unlock(&tables_lock);
if (!mine) continue;
if (t->is_device) {
if (p->driver_crashed) {
/* The driver faulted for this process: calling into it for its objects again could
* fault again, outside any guard. They are left to the driver. */
VKB_WARN("process %d: %s %u left as it is (the driver crashed serving it)", p->pid, t->is_device ? "device" : "instance", t->id);
if (t->is_device) vkb_cbtrack_device_destroy(t);
} else if (t->is_device) {
VKB_INFO("process %d exited without destroying device %u; cleaning up", p->pid, t->id);
vkb_cbtrack_device_destroy(t);
if (t->real.vkDeviceWaitIdle) t->real.vkDeviceWaitIdle(t->device);
vkb_spvfix_device_destroy(t);
vkb_dsmap_device_destroy(t);
@@ -218,6 +226,12 @@ void vkb_sv_vkEndCommandBuffer(vkb_srv_call *c)
vkb_srv_bad_message(c);
return;
}
/* A command buffer the device never handed out (or has freed) is not replayed: the driver would
* be given a pointer it does not own (server_cbtrack.c). */
if (!vkb_cbtrack_known(c->table, cb, "vkEndCommandBuffer")) {
vkb_enc_u32(&c->r, (uint32_t)VK_ERROR_DEVICE_LOST);
return;
}
VkResult begin_r = VK_SUCCESS;
size_t pos = 0;
vkb_enc scratch;
@@ -425,8 +439,55 @@ static int srv_recv(vkb_srv_conn *conn, vkb_msg_hdr *h, uint8_t **payload, int *
}
}
/* ------------------------------------------------------------------ driver fault guard */
/*
* A fault inside the GPU driver while serving one client used to take the whole app down (the
* server runs in its process). Each request runs under a guard: SIGSEGV/SIGBUS on that thread
* jumps back here, the client is cut off (it sees the bridge gone and fails or falls back), and
* the app and the other clients go on. Any other fault goes to the handler that was there before.
*/
static __thread sigjmp_buf *t_guard;
static __thread volatile sig_atomic_t t_fault;
static struct sigaction prev_segv, prev_bus;
static pthread_once_t guard_once = PTHREAD_ONCE_INIT;
static void guard_handler(int sig, siginfo_t *si, void *uc)
{
sigjmp_buf *g = t_guard;
if (g) {
t_guard = NULL;
t_fault = sig;
siglongjmp(*g, 1);
}
struct sigaction *p = sig == SIGBUS ? &prev_bus : &prev_segv;
if ((p->sa_flags & SA_SIGINFO) && p->sa_sigaction) {
p->sa_sigaction(sig, si, uc);
return;
}
if (!(p->sa_flags & SA_SIGINFO) && p->sa_handler == SIG_IGN) return;
if (!(p->sa_flags & SA_SIGINFO) && p->sa_handler && p->sa_handler != SIG_DFL) {
p->sa_handler(sig);
return;
}
signal(sig, SIG_DFL);
raise(sig);
}
static void guard_install(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = guard_handler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, &prev_segv);
sigaction(SIGBUS, &sa, &prev_bus);
}
static void *conn_main(void *arg)
{
pthread_once(&guard_once, guard_install);
vkb_srv_conn *conn = arg;
vkb_srv_call c;
vkb_enc_init(&c.r);
@@ -467,7 +528,19 @@ static void *conn_main(void *arg)
vkb_srv_set_current_table(c.table);
if (vkb_verbose) VKB_DBG("-> %s", vkb_cmd_names[h.cmd]);
vkb_srv_current_cmd = vkb_cmd_names[h.cmd];
sigjmp_buf jb;
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);
if (conn->proc) conn->proc->driver_crashed = 1;
vkb_srv_current_cmd = NULL;
vkb_srv_set_current_table(NULL);
order_done(conn->proc, h.seq);
break;
}
t_guard = &jb;
vkb_srv_handlers[h.cmd](&c);
t_guard = NULL;
vkb_srv_current_cmd = NULL;
vkb_srv_set_current_table(NULL);
if (st_period) stats_add(h.cmd, mono_ns() - t1, t1 - t0);
@@ -319,6 +319,7 @@ void vkb_sv_vkCreateDevice(vkb_srv_call *c)
vkb_pcache_device_init(t);
vkb_dsmap_device_init(t);
vkb_spvfix_device_init(t);
vkb_cbtrack_device_init(t); /* last: it wraps what the others installed */
VKB_INFO("device %u created on %s (%u extensions, memory sharing %s)", id, k ? k->name : "?", n,
vkb_mem_strategy_name(strategy));
for (uint32_t i = 0; i < n; i++) VKB_DBG(" device extension %s", exts[i]);
@@ -341,6 +342,7 @@ void vkb_sv_vkDestroyDevice(vkb_srv_call *c)
return;
}
vkb_table_unregister(t);
vkb_cbtrack_device_destroy(t);
vkb_spvfix_device_destroy(t);
vkb_dsmap_device_destroy(t);
vkb_pcache_device_destroy(t);
+5 -1
View File
@@ -46,7 +46,11 @@ void vkb_pcache_set_dir(const char *dir)
{
if (!dir || !*dir) return;
mkdir(dir, 0700);
pc_dir = dir;
/* A copy: the JNI entry frees its string once the server is up, and the pointer kept before
* printed "pipeline cache \xef\xbf\xbdRYp/13b5-..." and wrote the cache under that name. */
char *copy = strdup(dir);
if (!copy) return;
pc_dir = copy;
VKB_INFO("pipeline cache directory %s", dir);
}
+7
View File
@@ -33,6 +33,7 @@ typedef struct vkb_srv_proc {
uint64_t watermark; /* every async seq <= this has run */
uint8_t done[VKB_SEQ_RING]; /* seqs above the watermark that have run */
int conn_closed; /* a connection ended (a seq may never arrive) */
int driver_crashed; /* the driver faulted serving it: its objects are left alone */
} vkb_srv_proc;
typedef struct vkb_srv_conn {
@@ -88,6 +89,7 @@ typedef struct vkb_srv_table {
struct vkb_pcache *pcache; /* server_pcache.c */
struct vkb_dsmap *dsmap; /* server_dsmap.c */
struct vkb_spvfix *spvfix; /* server_spvfix.c */
struct vkb_cbtrack *cbtrack; /* server_cbtrack.c */
} vkb_srv_table;
/* server_main.c */
@@ -97,6 +99,11 @@ extern int vkb_verbose;
void vkb_log_summary(void);
/* server_cbtrack.c: command buffers the device handed out; others are refused, not passed on. */
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);
/* server_spvfix.c */
void vkb_spvfix_device_init(vkb_srv_table *t);
void vkb_spvfix_device_destroy(vkb_srv_table *t);
@@ -86,7 +86,7 @@ data class GpuInfo(
fun supportText(context: Context): String = context.getString(when (support) {
Support.TESTED -> R.string.gpuinfo_supported
Support.UNTESTED -> when (family) {
Family.POWERVR -> R.string.gpuinfo_powervr
Family.POWERVR -> if (name.contains("Xclipse", ignoreCase = true)) R.string.gpuinfo_xclipse else R.string.gpuinfo_powervr
Family.A7XX_LOW -> R.string.gpuinfo_experimental
Family.MALI_JM, Family.MALI_G610_G710, Family.MALI_G615, Family.MALI_G720, Family.MALI_LATEST, Family.MALI_OTHER -> R.string.gpuinfo_experimental
else -> R.string.gpuinfo_below_tested
+1
View File
@@ -1908,6 +1908,7 @@
<!-- GPU support line -->
<string name="gpuinfo_not_adreno">Not an Adreno GPU</string>
<string name="gpuinfo_powervr">System PowerVR driver: compatibility varies by device</string>
<string name="gpuinfo_xclipse">Experimental: RADV or the system Xclipse driver</string>
<string name="gpu_system_powervr">System PowerVR</string>
<string name="gpu_auto_system_hint">Uses the built-in drivers for this GPU</string>
<string name="drivers_powervr_builtin">The system PowerVR driver is selected for the runtime and display.</string>
@@ -1190,6 +1190,10 @@ BUSCONF
# The loader's view first, then the variants that change what Zink asks of the device;
# the first that starts is what the client gets.
VK_LOADER_DEBUG=error,warn timeout 20 /usr/local/bin/droiddeck-glprobe 2>&1 | grep -v drirc | head -40 | sed 's/^/== glprobe (loader): /'
# What Zink asked the device before it gave up (the Mali compatibility layer's trace), Mesa's
# own debug output, and the device as Vulkan programs see it under the same environment.
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): /'
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
if env $bl_variant timeout 20 /usr/local/bin/droiddeck-glprobe 2>&1 | grep -v drirc | head -20 | sed "s/^/== glprobe ($bl_variant): /"; [ "${PIPESTATUS[0]}" = 0 ]; then
+7
View File
@@ -18,4 +18,11 @@ if [ "$(wc -l < "$starts")" -gt 5 ]; then
echo "mangoapp: started $(wc -l < "$starts") times in 60 s; the performance overlay stays off for this session" >&2
exec sleep infinity
fi
# The system PowerVR route (BL_POWERVR_RENDER_NODE): Zink could not make mangoapp a swapchain there
# ("zink: could not create swapchain", then GLXBadCurrentWindow) and it crashed at every start. The
# overlay is a few text lines: llvmpipe draws it.
if [ -n "${BL_POWERVR_RENDER_NODE:-}" ]; then
export GALLIUM_DRIVER=llvmpipe LIBGL_ALWAYS_SOFTWARE=1
unset MESA_LOADER_DRIVER_OVERRIDE LIBGL_KOPPER_DRI2
fi
LD_LIBRARY_PATH=/usr/local/lib/mangoapp${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} exec /usr/local/lib/mangoapp/mangoapp "$@"
+29
View File
@@ -151,6 +151,18 @@ static bool wanted(void)
return getenv("WINEPREFIX") != NULL;
}
/* DROIDDECK_MALI_COMPAT_TRACE=1: what an application asks before it creates a device (Zink on a
* Mali-G615 through Venus gave up before vkCreateDevice without a word). */
static bool tracing(void)
{
static int on = -1;
if (on < 0) {
const char *v = getenv("DROIDDECK_MALI_COMPAT_TRACE");
on = v && *v && strcmp(v, "0") != 0;
}
return on;
}
static bool has_extension(struct instance *inst, VkPhysicalDevice physical_device, const char *name)
{
uint32_t count = 0;
@@ -337,6 +349,13 @@ static VKAPI_ATTR void VKAPI_CALL GetPhysicalDeviceFeatures2(VkPhysicalDevice ph
inst->GetPhysicalDeviceFeatures2(physical_device, features);
relink(dropped, dropped_count);
report_features(&features->features, gaps);
if (tracing()) {
char chain[512] = "";
size_t used = 0;
for (VkBaseOutStructure *s = features->pNext; s && used + 16 < sizeof(chain); s = s->pNext)
used += (size_t)snprintf(chain + used, sizeof(chain) - used, " %d", (int)s->sType);
layer_log("trace: vkGetPhysicalDeviceFeatures2 (gaps 0x%x, %u structures held back), chain:%s", gaps, dropped_count, chain);
}
for (VkBaseOutStructure *s = features->pNext; s; s = s->pNext) {
if (s->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT && (gaps & GAP_ROBUSTNESS2)) {
((VkPhysicalDeviceRobustness2FeaturesEXT *)s)->robustBufferAccess2 = VK_TRUE;
@@ -362,6 +381,10 @@ static VKAPI_ATTR void VKAPI_CALL GetPhysicalDeviceProperties2(VkPhysicalDevice
struct instance *inst = instance_from_handle(physical_device);
inst->GetPhysicalDeviceProperties2(physical_device, properties);
report_limits(&properties->properties.limits, physical_device_gaps(inst, physical_device));
if (tracing())
layer_log("trace: vkGetPhysicalDeviceProperties2: %s, Vulkan %u.%u.%u, type %d", properties->properties.deviceName,
VK_API_VERSION_MAJOR(properties->properties.apiVersion), VK_API_VERSION_MINOR(properties->properties.apiVersion),
VK_API_VERSION_PATCH(properties->properties.apiVersion), (int)properties->properties.deviceType);
}
/* The extensions the layer stands in for, added to the driver's list. */
@@ -391,6 +414,8 @@ 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())
layer_log("trace: vkEnumerateDeviceExtensionProperties: %u from the driver, %u added by the layer", own, extra_count);
uint32_t n = *count < total ? *count : total;
memcpy(properties, all, n * sizeof(*all));
free(all);
@@ -411,6 +436,10 @@ static VKAPI_ATTR VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo
PFN_vkCreateInstance create = (PFN_vkCreateInstance)gipa(VK_NULL_HANDLE, "vkCreateInstance");
VkResult result = create(info, allocator, instance);
if (tracing())
layer_log("trace: vkCreateInstance for %s (API %u.%u): %d, layer %s", info->pApplicationInfo && info->pApplicationInfo->pApplicationName ?
info->pApplicationInfo->pApplicationName : "?", info->pApplicationInfo ? VK_API_VERSION_MAJOR(info->pApplicationInfo->apiVersion) : 0,
info->pApplicationInfo ? VK_API_VERSION_MINOR(info->pApplicationInfo->apiVersion) : 0, (int)result, wanted() ? "active" : "idle");
if (result != VK_SUCCESS)
return result;