iris: cut test debug info, say which adapter drew, and log on the desktop

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>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-08 12:06:02 -04:00
1 parent c6da735134
commit 0ccc444246
8 files changed
+722 -112

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
// 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;
}