Three findings from one morning, all of them things that were invisible rather than wrong. docs/RUST.md's two new sections have the full account. **`cargo test --workspace` was taking half an hour, and it was debug info.** rustc's default `debug = true`, times eight test binaries each statically linking the whole wgpu + naga + winit + parley graph, means every one of them gets a private copy of that graph's DWARF written into it: the linkers for one run had written ~54 GB between them and were still going at thirty minutes -- the worst single one 16.9 GB for one test binary -- leaving an 88 GB target/. It was not CPU: the machine was 87% idle, and rust-lld's threads were in D state in btrfs `handle_reserve_ticket`, blocked on space reservation at 83% full. So `debug = "line-tables-only"` on both `profile.dev` and `profile.test` -- both, because `cargo test` builds dependencies under one and the test targets under the other. Cold, with all 19 suites run: 69 s and a 3.7 GB target. Backtraces keep file and line; `RUSTFLAGS="-C debuginfo=2"` per run buys back variable inspection when a debugger actually needs it. **The desktop had no logger at all**, so every `log::` call on that side went to `log`'s no-op default -- including the GLES fallback warning added hours earlier. `DefaultApp::run` installs a stderr logger (`src/default/logging.rs`, no new dependency: a level and a line is a page of code against env_logger plus its filter dialect), and the renderer now says which adapter won at `info`. That line is the point: with a silent fallback, a layer-2 screenshot rendered by llvmpipe and one rendered by the host's GPU are the same PNG, and which one it was is exactly what the screenshot is being taken to judge. **`tests/mask_sdf.rs` is a render pass now, not a compute pass.** It asked for `adapter.limits()` because `iris_core::device_limits()` deliberately zeroes the six `max_compute_*` fields -- a decision on record since 2026-09-05, which this quietly worked around instead of following. It now asks for what iris asks for and calls the function from the fragment stage, where the renderer calls it. The compute pass was *not* why it crashed, and the record should not say it was: the rewrite crashes identically. What the crash is: dropping a wgpu device on this VM's Venus adapter segfaults, after the test has produced its answer (worst CPU/shader disagreement 5.8e-6). Narrowed -- plain Vulkan creating and destroying five VkDevices on the same adapter is clean, and the same binary with Vulkan hidden falls back to GL and exits clean. Worked around at `Gpu::leak`, with the reason and the delete-me condition written there. `rigs/virtgpu-probe` is the new rig behind the Venus half: which capsets the host offers (0x16 -- VIRGL, VIRGL2, VENUS; no capset 6, so no DRM native context without host-side work), whether the device has compute (it does: 1024 invocations/workgroup -- the "no compute" finding on record is about the Android emulator's SwiftShader, a different machine), and whether plain Vulkan teardown is clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
5.3 KiB
C
134 lines
5.3 KiB
C
// 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 <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <drm/virtgpu_drm.h>
|
|
#include <vulkan/vulkan.h>
|
|
#include <xf86drm.h>
|
|
|
|
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;
|
|
}
|