// What this VM's virtio-gpu actually offers, asked of the kernel and the // driver rather than inferred from the host's qemu command line. // // cc -O2 -o virtgpu-probe virtgpu-probe.c -I/usr/include/libdrm -ldrm -lvulkan // ./virtgpu-probe // // Written 2026-09-08 for the question "Venus keeps causing problems, is // there something to do with qemu instead" (docs/RUST.md, "Venus went // away for an hour"). Three things, each of which was guessed wrong at // least once before being measured: // // 1. Which capsets the host offers. Capset 6 (DRM) is the "native // context" one -- RADV running in the guest against a passed-through // DRM context instead of Venus proxying every Vulkan call. Whether // it is available is a host-side fact this is the only way to read // from in here. // 2. Whether the Vulkan device has compute. It does, and assuming it // did not sent one investigation down the wrong path: the "no // compute" finding on record is about the *Android emulator's* // SwiftShader GL path, a different machine entirely. // 3. Whether plain Vulkan device teardown crashes on this adapter. It // does not -- which is what makes wgpu's teardown SIGSEGV wgpu's and // not the driver's, and is the kind of claim that is worthless // without the negative half. // // C rather than a Rust crate on purpose: two of the three are ioctl and // loader questions that a wgpu-shaped rig cannot ask, and this needs to // keep working when the thing under suspicion is wgpu itself. #include #include #include #include #include #include #include #include #include static const char *capset_name(int id) { switch (id) { case 1: return "VIRGL"; case 2: return "VIRGL2"; case 3: return "GFXSTREAM_VULKAN"; case 4: return "VENUS"; case 5: return "CROSS_DOMAIN"; case 6: return "DRM (native context)"; default: return "unknown"; } } static int capsets(void) { int fd = open("/dev/dri/renderD128", O_RDWR); if (fd < 0) { printf("capsets: cannot open /dev/dri/renderD128: %s\n", strerror(errno)); return 1; } struct drm_virtgpu_getparam gp; uint64_t mask = 0; memset(&gp, 0, sizeof gp); gp.param = VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs; gp.value = (uint64_t)(uintptr_t)&mask; int rc = drmIoctl(fd, DRM_IOCTL_VIRTGPU_GETPARAM, &gp); close(fd); if (rc) { // Not a virtio-gpu at all, or a kernel without the param: say // which, rather than printing an empty list that reads like "the // host offers nothing". printf("capsets: SUPPORTED_CAPSET_IDs unavailable: %s\n", strerror(errno)); return 1; } printf("capsets: bitmask 0x%llx\n", (unsigned long long)mask); for (int i = 1; i <= 8; i++) if (mask & (1ull << i)) printf(" %d: %s\n", i, capset_name(i)); if (!(mask & (1ull << 6))) printf(" (no capset 6: native context needs host-side virglrenderer + qemu support)\n"); return 0; } static int devices(void) { VkInstanceCreateInfo ici = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; VkInstance inst; if (vkCreateInstance(&ici, NULL, &inst) != VK_SUCCESS) { printf("vulkan: no instance -- the loader found no usable ICD\n"); return 1; } uint32_t n = 0; vkEnumeratePhysicalDevices(inst, &n, NULL); if (n == 0) { // The exact state this VM was in for an hour on 2026-09-08. printf("vulkan: instance ok but ZERO devices -- the host refused a context\n"); vkDestroyInstance(inst, NULL); return 1; } VkPhysicalDevice pd[8]; if (n > 8) n = 8; vkEnumeratePhysicalDevices(inst, &n, pd); for (uint32_t i = 0; i < n; i++) { VkPhysicalDeviceProperties p; vkGetPhysicalDeviceProperties(pd[i], &p); printf("vulkan: %s (api %u.%u.%u)\n", p.deviceName, VK_VERSION_MAJOR(p.apiVersion), VK_VERSION_MINOR(p.apiVersion), VK_VERSION_PATCH(p.apiVersion)); printf(" compute: %u invocations/workgroup, size %u,%u,%u, %u bytes shared\n", p.limits.maxComputeWorkGroupInvocations, p.limits.maxComputeWorkGroupSize[0], p.limits.maxComputeWorkGroupSize[1], p.limits.maxComputeWorkGroupSize[2], p.limits.maxComputeSharedMemorySize); } // The negative half of "wgpu's teardown crashes on Venus": five // devices and the instance, created and destroyed the plain way. float prio = 1.0f; VkDeviceQueueCreateInfo q = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = 0, .queueCount = 1, .pQueuePriorities = &prio}; VkDeviceCreateInfo dci = {.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .queueCreateInfoCount = 1, .pQueueCreateInfos = &q}; for (int i = 0; i < 5; i++) { VkDevice dev; if (vkCreateDevice(pd[0], &dci, NULL, &dev) != VK_SUCCESS) { printf("teardown: device %d could not be created\n", i); return 1; } vkDestroyDevice(dev, NULL); } vkDestroyInstance(inst, NULL); printf("teardown: 5 devices + instance created and destroyed cleanly\n"); return 0; } int main(void) { int bad = capsets(); bad |= devices(); return bad; }