iris: the GPU test's crash was the Vulkan loader unloading Mesa, not wgpu

`mask_sdf` SIGSEGVd after printing `test result: ok`, and the workaround
was to hand the device to the process with `mem::forget` on the reading
that "dropping a wgpu device on Venus segfaults". Every part of that
except the symptom was wrong.

`rigs/gpu-probe`'s new `teardown` bin is the experiment, one variable per
mode: the same open-and-close exits 0 on the main thread and SIGSEGVs on
a spawned one; it needs no GPU work and no device, only an instance; raw
`ash` does it with no wgpu involved at all; and keeping the instance
alive fixes it. Destroying the last VkInstance makes the loader dlclose
the ICD, and Mesa's ICD here registers a pthread_key_create destructor
into its own text without `-z nodelete`, so glibc calls it through
unmapped memory when the thread exits. libtest runs every #[test] on a
spawned thread, which is the whole reason this looked like a drop bug.
`VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING=1` confirms the mechanism.

So the fix is one `wgpu::Instance` for the process -- what wgpu asks for
anyway -- and the device, queue and everything else drop normally again.
The escape and its paragraph of reasons are gone.

Also: the machine-level graphics notes duplicated in docs/RUST.md,
run-headless.sh and two source comments now point at the
`this-machine-graphics` skill, which is the only copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-08 12:22:47 -04:00
1 parent f014e8d9cf
commit b9924e7617
6 files changed
+229 -160

No files matched your search

+72 -104
View File
@@ -7499,118 +7499,28 @@ a correct `iris` build look like it built the wrong thing. Read the test
*names* in the log to tell two workspaces apart, not a path-relative *names* in the log to tell two workspaces apart, not a path-relative
size. size.
### Venus went away for an hour, and nothing said so (2026-09-08) ### The GPU went away, and nothing said so (2026-09-08)
**The machine-wide version of this is the `this-machine-graphics` **This machine's graphics live in the `this-machine-graphics` skill** --
skill**, written the same day so an agent in any repo gets it: what this what the VM's GPU is, the probe that asks it what it supports, the
VM's GPU is, the probe that asks it what it supports, this outage's transient outage that drops everything to llvmpipe, and the rule about
signature, the wgpu-on-Venus teardown crash, and the rule about never never `.expect()`ing a `PRIMARY` adapter. It is the only copy; what is
`.expect()`ing a `PRIMARY` adapter. What stays here is the part specific below is only what iris itself changed because of it.
to iris and to this port.
**It came back on its own and nothing in the host config changed.** Iris For about an hour on 2026-09-08 both paths through the virtio-gpu were
asked whether something had, since she still passes Venus as true. What refused by the host at once -- Venus for Vulkan and virgl for GL -- and
happened, and what was ruled out, so the next occurrence is not GL fell through to **llvmpipe**, which is what actually rendered that
re-investigated from scratch: hour's layer-2 screenshots. Two things in iris made that silent, and both
are the same rule: a degraded state must be distinguishable from a
The symptom, around 02:10 on 2026-09-08 with the VM at load 68 and healthy one.
several agents building: `vulkaninfo` reported `Failed to detect any
valid GPUs in the current config` and `vkEnumeratePhysicalDevices failed
with ERROR_INITIALIZATION_FAILED`; Mesa printed **`No virgl contexts
available on host`**; and `wgpu` reported `NotFound { active_backends:
VULKAN, no_adapter_backends: VULKAN, supported_backends: VULKAN | GL }`.
So **both** paths through the virtio-gpu died at once -- Venus for
Vulkan and virgl for GL -- and GL then fell through to llvmpipe, which
is what actually rendered that hour's layer-2 screenshots.
That string is Mesa's virgl DRM winsys, next to `DRM_IOCTL_VIRTGPU_
CONTEXT_INIT failed with %s` in `libgallium`: the *host* refused a new
context. After a reboot the same host config gives
`Virtio-GPU Venus (AMD Radeon RX 7900 XT (RADV NAVI31))`, Mesa 26.2.2,
driverID `MESA_VENUS`.
What was ruled out, by measurement rather than by reasoning:
- **A guest-side context cap.** 48 concurrent short-lived Vulkan clients
all succeed, and 120 concurrent *long-lived* ones (each holding a
`VkDevice` open at once, a throwaway holder) all succeed. So the
ceiling, if there is one, is not near the handful of GPU-using
processes that were running.
- **A Mesa upgrade.** `mesa 1:26.1.7 -> 1:26.2.2` landed 2026-09-05, two
days before Venus was last seen working here.
- **Anything in iris.** It was `vulkaninfo`'s answer too, from a
process that has never linked against this repo.
**What this host's virtio-gpu actually offers**, asked of the kernel
rather than assumed (`rigs/virtgpu-probe`): bitmask `0x16` --
**VIRGL, VIRGL2 and VENUS**. Capset 6, the **DRM "native context"**, is
not offered. That is the answer to "is there something to do with qemu
instead of Venus", asked by Iris 2026-09-08: native context is the thing
worth wanting -- RADV running *in the guest* against a passed-through DRM
context instead of Venus proxying every Vulkan call, and it is where
Mesa's effort has gone. Whether it would avoid the teardown crash below
is **untested** -- it is a different driver stack, so it is a reasonable
thing to try rather than a known fix -- but it needs the **host** side to
offer it (virglrenderer built with
its amdgpu DRM renderer, and a qemu that exposes `context_types=drm`;
crosvm has it further along). The guest would also need `vulkan-radeon`
installed, which it does not have today -- only `vulkan-virtio`. The
other two options are VFIO passthrough (complete, but the host loses the
GPU) and dropping `venus=true` (leaves virgl/GL only, i.e. no Vulkan at
all, which is the wrong direction since Vulkan is the phone's path).
So it is host-side and transient, and this VM cannot see the host to say
more: no `dmesg` (the guest's kernel buffer is not readable to this
user), and no view of the host's `amdgpu`. **The honest state is "we do
not know which host-side resource ran out"** -- worth capturing the host
side of it if it recurs, since that is the half that would answer it.
**A second, unrelated Venus fault, found the moment it came back
(2026-09-08).** `iris/tests/mask_sdf.rs` had passed the day before and
now `SIGSEGV`d -- and it had passed *because Venus was down*, so it
silently ran on GL. What it actually is, narrowed by measurement:
- **The test's work completes and its answer is right** (worst
CPU/shader disagreement 5.8e-6). The crash is at process teardown,
dropping wgpu's device: a call through an unmapped address on a
wgpu-created thread, per gdb.
- **It is wgpu's teardown, not Venus's device lifecycle.** A plain
Vulkan program creating and destroying five `VkDevice`s and its
instance on the same adapter exits cleanly (`rigs/virtgpu-probe`).
- **It is Venus-specific.** The same test binary, with Vulkan hidden
(`VK_DRIVER_FILES=/nonexistent`) so wgpu falls back to GL, exits
cleanly.
Worked around in the test rather than fixed, at `Gpu::leak` with the
reason written there: one device for the whole test, handed to the
process instead of dropped. **Compute was investigated and is not
involved** -- an early version of that test used a compute pass, which
was wrong for its own reason (the paragraph after this one), but the
render-pass rewrite crashes identically, and Venus here reports full
compute anyway (`maxComputeWorkGroupInvocations` 1024,
`maxComputeSharedMemorySize` 65536, Vulkan 1.4 -- `rigs/virtgpu-probe`
again). The 2026-09-05 "no compute" finding is about the **Android
emulator's SwiftShader GL path** reporting ES 3.0, which is a different
machine; it says nothing about this VM. This was got wrong out loud
first, so it is written down: the compute pass was blamed for the crash
before the rewrite showed the crash was not about compute at all.
The test was rewritten to a render pass regardless, and that part is not
a workaround: it now asks for `iris_core::device_limits()` -- what iris
itself requests -- and calls the function from the fragment stage, which
is where the renderer calls it. A test that needs a capability the thing
under test has never needed is testing the wrong device.
**What was fixed, because the failure was silent.** Two things, both the
rule that a degraded state must be distinguishable from a healthy one:
1. `default::render::UiRenderer::new` had the defect the Android backend 1. `default::render::UiRenderer::new` had the defect the Android backend
was fixed for in `85869d0` -- `Backends::PRIMARY` and an `.expect` -- was fixed for in `85869d0` -- `Backends::PRIMARY` and an `.expect` --
so layer 2 aborted with `Could not get adapter!` instead of falling so layer 2 aborted with `Could not get adapter!` instead of falling
back. It now probes and rebuilds on `Backends::GL`, as Android does. back. It now probes and rebuilds on `Backends::GL`, as Android does.
2. **The desktop had no logger at all**, so every `log::` call on that 2. **The desktop had no logger at all**, so every `log::` call on that
side -- including that new fallback warning -- went to `log`'s side -- including that new fallback warning -- went to `log`'s no-op
no-op default. `DefaultApp::run` installs a stderr logger now default. `DefaultApp::run` installs a stderr logger now
(`src/default/logging.rs`, no new dependency), and the renderer says (`src/default/logging.rs`, no new dependency), and the renderer says
which adapter won at `info`: which adapter won at `info`:
@@ -7624,6 +7534,64 @@ rule that a degraded state must be distinguishable from a healthy one:
screenshot is being taken to judge. **Check it before trusting a screenshot is being taken to judge. **Check it before trusting a
layer-2 screenshot or any frame number from that window.** layer-2 screenshot or any frame number from that window.**
### "wgpu crashes on drop" was the Vulkan loader unloading Mesa (2026-09-08)
`iris/tests/mask_sdf.rs` `SIGSEGV`d **after** printing `test result: ok`,
and the first reading of that -- written down here, then corrected -- was
"dropping a wgpu device on Venus segfaults", worked around by handing the
device to the process with `std::mem::forget`. That was wrong in every
part except the symptom, and `rigs/gpu-probe`'s `teardown` bin is the
experiment that says so. Its four modes, each one variable:
| mode | what it does | result |
|---|---|---|
| `main` | wgpu instance + device on the main thread, dropped | exits 0 |
| `thread` | the same on a spawned thread | **SIGSEGV** |
| `keep` | the same, but the instance is never dropped | exits 0 |
| `raw` | raw Vulkan (`ash`), instance + device, spawned thread | **SIGSEGV** |
So: **no wgpu is required** (raw `ash` does it), **no GPU work is
required** (no pipeline, no submit, no device even -- an instance is
enough), and it is **the thread's exit, not the drop**, that faults.
The mechanism, confirmed by three measurements. Destroying the last
`VkInstance` makes the Vulkan loader `dlclose` the ICD. Mesa's ICD here
(`/usr/lib/libvulkan_virtio.so`) calls `pthread_key_create` with a
destructor pointing into its own text and is **not** linked
`-z nodelete`, so the key outlives the mapping; glibc then calls that
destructor through an unmapped address when the thread exits. The
faulting PC (`0x7ffff77e6880`, gdb) lands inside that library's mapping
as reported before it was unloaded, the caller is glibc's thread-exit
path, and `VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING=1` makes every
mode exit cleanly. It is a long-standing incompatibility between
`pthread_key_create` and `dlclose` rather than anything about this VM;
Venus only got the blame because libtest runs each `#[test]` on a
spawned thread and this was the only GPU test.
**The fix is one `wgpu::Instance` for the process**, which is what wgpu
asks for anyway -- `mask_sdf.rs`'s `vulkan_instance()`, a `OnceLock`. The
device, queue and everything else drop normally again, and the
`mem::forget` escape and its paragraph of reasons are gone. Anything else
in this workspace that opens a GPU on a spawned thread wants the same
instance rather than its own; a second GPU test creating and dropping its
own instance brings the crash straight back.
Two notes for whoever reads this next. The desktop renderer's GLES
fallback is already safe by construction -- `instance = Instance::new(…)`
builds the replacement before the old one drops, so the ICD's refcount
never reaches zero -- and that is worth not undoing. And
`#![recursion_limit = "256"]` at the top of `mask_sdf.rs` is not related
to any of this: proving `OnceLock<wgpu::Instance>: Sync` overflows
rustc's default trait-recursion budget of 128.
**Compute was never involved**, which is written down because it was said
out loud first: an early version of the test used a compute pass, that
was wrong for its own reason (`device_limits()` deliberately zeroes the
compute limits, since nothing in iris writes a `@compute` stage), and the
render-pass rewrite crashed identically. The 2026-09-05 "no compute"
finding is about the **Android emulator's SwiftShader GL path** reporting
ES 3.0 -- a different machine entirely.
### Vulkan in the emulator (measured 2026-09-04) ### Vulkan in the emulator (measured 2026-09-04)
**Settled 2026-09-04: the guest gets Vulkan from SwiftShader, and the **Settled 2026-09-04: the guest gets Vulkan from SwiftShader, and the
+4 -4
View File
@@ -33,10 +33,10 @@
# through positionally without disturbing the existing `-- cargo args` # through positionally without disturbing the existing `-- cargo args`
# convention above. # convention above.
# #
# The VM has a virtio-gpu render node (Vulkan 1.4 through Venus, GL 4.6 # The VM has a real GPU and no display (the `this-machine-graphics` skill
# through virgl), so wgpu runs on the host's real GPU -- what is missing is # says what it is and how it fails), so what is missing here is only a
# only a compositor to give winit a surface. So: a headless sway, the same # compositor to give winit a surface. So: a headless sway, the same trick
# trick `emu` uses for the Android emulator, and `grim` to see the result. # `emu` uses for the Android emulator, and `grim` to see the result.
# #
# It is deliberately *not* `emu`'s compositor. sway tiles, so adding a window # It is deliberately *not* `emu`'s compositor. sway tiles, so adding a window
# to the one an emulator is sitting in resizes that emulator's window, and a # to the one an emulator is sitting in resizes that emulator's window, and a
+1 -1
View File
@@ -4,7 +4,7 @@
//! a no-op logger, and nothing in `desktop-app` or the examples ever //! a no-op logger, and nothing in `desktop-app` or the examples ever
//! installed a real one. That is how iris came to have a renderer that //! installed a real one. That is how iris came to have a renderer that
//! silently fell back to GLES (and, on this VM, on to llvmpipe when the //! silently fell back to GLES (and, on this VM, on to llvmpipe when the
//! host lost its virtio-gpu contexts) with **no record anywhere of what //! host took its GPU away) with **no record anywhere of what
//! drew the frame** -- a layer-2 screenshot off llvmpipe and one off the //! drew the frame** -- a layer-2 screenshot off llvmpipe and one off the
//! host GPU are the same PNG, and the difference is exactly what a //! host GPU are the same PNG, and the difference is exactly what a
//! screenshot is being taken to judge. //! screenshot is being taken to judge.
+5 -5
View File
@@ -100,9 +100,9 @@ impl UiRenderer {
// the same reason: a machine can advertise a Vulkan ICD with no // the same reason: a machine can advertise a Vulkan ICD with no
// device behind it, and refusing to draw at all because the only // device behind it, and refusing to draw at all because the only
// usable adapter is a GLES one is iris's bug rather than the // usable adapter is a GLES one is iris's bug rather than the
// machine's. On this VM the virtio-gpu Venus device disappears // machine's. On this VM the Vulkan device disappears whenever
// whenever the host runs out of virgl contexts, so `run-headless. // the host refuses a virtio-gpu context, so `run-headless.sh` --
// sh` -- layer 2 of the test rig -- aborted with `Could not get // layer 2 of the test rig -- aborted with `Could not get
// adapter!` while GL was sitting there working. Probed before the // adapter!` while GL was sitting there working. Probed before the
// surface exists, matching Android, where an instance carrying // surface exists, matching Android, where an instance carrying
// both backends fails worse than one carrying the wrong one. // both backends fails worse than one carrying the wrong one.
@@ -135,8 +135,8 @@ impl UiRenderer {
// Say which adapter won, the way the Android backend's own report // Say which adapter won, the way the Android backend's own report
// does. Without it a layer-2 screenshot or frame time from this // does. Without it a layer-2 screenshot or frame time from this
// window carries no record of what drew it, and the two cases that // window carries no record of what drew it, and the two cases that
// matter look identical in the PNG: the host GPU through Venus, // matter look identical in the PNG: the host's real GPU, and
// and llvmpipe after this VM lost its virtio-gpu contexts. That // llvmpipe after this VM lost its virtio-gpu contexts. That
// happened on 2026-09-08, and the only reason anyone noticed is // happened on 2026-09-08, and the only reason anyone noticed is
// that the fallback above did not exist yet and the app aborted // that the fallback above did not exist yet and the app aborted
// instead. A silent fallback needs this line to stay honest. // instead. A silent fallback needs this line to stay honest.
+45 -46
View File
@@ -28,10 +28,17 @@
//! a capability the thing under test has never needed is testing the //! a capability the thing under test has never needed is testing the
//! wrong device, which is reason enough. //! wrong device, which is reason enough.
//! //!
//! It is **not** why that version crashed, and the record should not say //! It is **not** why that version crashed; see [`vulkan_instance`] for
//! it was: the render-pass rewrite crashes in exactly the same place, and //! what that crash actually was and why nothing here has to work around
//! this VM's Venus reports full compute anyway. See `Gpu::leak` for what //! it any more.
//! the crash actually is.
// `OnceLock<wgpu::Instance>` needs `Instance: Sync`, and wgpu's type
// graph is deep enough that proving it overflows rustc's default trait
// recursion limit of 128. Nothing here is recursive; the limit is a
// compile-time budget, and this is the documented way to raise it.
#![recursion_limit = "256"]
use std::sync::OnceLock;
use iris_core::{SHAPE_SHADER, rounded_rect_coverage, util::Vec2}; use iris_core::{SHAPE_SHADER, rounded_rect_coverage, util::Vec2};
use pollster::FutureExt; use pollster::FutureExt;
@@ -98,8 +105,6 @@ fn mask_sdf_matches_the_shader() {
} }
} }
gpu.leak();
let (pos, radius, want, got) = worst_at; let (pos, radius, want, got) = worst_at;
assert!( assert!(
worst <= TOLERANCE, worst <= TOLERANCE,
@@ -263,27 +268,52 @@ fn run_shader(gpu: &Gpu, radius: f32) -> Vec<f32> {
coverage coverage
} }
/// One device for the whole test, **never dropped** -- see [`Gpu::open`]. /// One `wgpu::Instance` for the process, created on first use and never
/// destroyed.
///
/// **Why it is a static rather than a value the test owns.** Destroying
/// the last `VkInstance` makes the Vulkan loader `dlclose` the ICD, and
/// Mesa's ICD here registers a `pthread_key_create` destructor pointing
/// into its own text without being linked `-z nodelete`. glibc then calls
/// that destructor when the thread exits -- through an address that is no
/// longer mapped. libtest runs every `#[test]` on a spawned thread, so a
/// test that opens and closes an instance segfaults *after* printing its
/// result, which reads exactly like the test failing. Measured
/// 2026-09-08 with `rigs/gpu-probe`'s `teardown` bin: it
/// needs no wgpu (raw `ash` does it too), no GPU work, and no device --
/// an instance created and destroyed on a spawned thread is enough, and
/// keeping any one instance alive is enough to prevent it.
///
/// Devices, queues and everything else drop normally; only the instance
/// is held, which is what wgpu asks for anyway (one instance per
/// process). So this costs one instance for the length of a test binary
/// and buys ordinary drops everywhere else.
fn vulkan_instance() -> &'static wgpu::Instance {
static INSTANCE: OnceLock<wgpu::Instance> = OnceLock::new();
INSTANCE.get_or_init(wgpu::Instance::default)
}
/// The device this test draws with.
struct Gpu { struct Gpu {
instance: wgpu::Instance,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
} }
impl Gpu { impl Gpu {
/// Opens the device this test draws with, and reports which adapter /// Opens the device this test draws with, and reports which adapter
/// answered, because that is not a detail here: this same test passes /// answered, because that is not a detail here: a run on llvmpipe and
/// on GL and **`SIGSEGV`s on Venus**, so a run that does not say which /// a run on the host's GPU are otherwise indistinguishable in the
/// one it got cannot be read. /// log, and only one of them is a check of what the phone will do.
fn open() -> Self { fn open() -> Self {
let instance = wgpu::Instance::default(); let instance = vulkan_instance();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions::default()) .request_adapter(&wgpu::RequestAdapterOptions::default())
.block_on() .block_on()
.expect( .expect(
"no wgpu adapter on this machine, so the CPU/shader SDF agreement went \ "no wgpu adapter on this machine, so the CPU/shader SDF agreement went \
unchecked. This VM has a virtio-gpu render node (see iris/run-headless.sh); \ unchecked. This VM has a virtio-gpu render node (the `this-machine-graphics` \
if that is gone, fix it rather than deleting this test.", skill says what it is and how it fails); if that is gone, fix it rather \
than deleting this test.",
); );
let info = adapter.get_info(); let info = adapter.get_info();
eprintln!( eprintln!(
@@ -298,38 +328,7 @@ impl Gpu {
}) })
.block_on() .block_on()
.expect("could not get a device from the adapter"); .expect("could not get a device from the adapter");
Self { Self { device, queue }
instance,
device,
queue,
}
}
/// Hands the device to the process rather than dropping it.
///
/// **The escape, and why there is no alternative here.** Dropping a
/// `wgpu` device against this VM's Venus adapter segfaults: a call
/// through an unmapped address on a wgpu-created thread, measured
/// 2026-09-08 with gdb. It is `wgpu`'s teardown and not the driver's
/// device lifecycle -- a plain Vulkan program creating and destroying
/// five `VkDevice`s and its instance on the same adapter exits
/// cleanly, and this same test binary exits cleanly when Vulkan is
/// hidden and it falls back to GL. Nothing this test can do about
/// wgpu's drop order makes that call valid, and the alternative is a
/// test that reports a crash after it has already produced its
/// answer, which is indistinguishable from the test failing.
///
/// Safe because the process is about to exit: the leak is one device
/// and one instance, for the microseconds between here and `main`
/// returning. **Delete this the moment the teardown crash is fixed**
/// -- `cargo test -p iris --test mask_sdf` failing with `SIGSEGV`
/// after printing `test result: ok` is what it looks like when it is
/// still needed, and an ordinary pass is what it looks like when it
/// is not.
fn leak(self) {
std::mem::forget(self.queue);
std::mem::forget(self.device);
std::mem::forget(self.instance);
} }
} }
+102
View File
@@ -0,0 +1,102 @@
//! Why a GPU test segfaults *after* it has passed, and what stops it.
//!
//! Measured here 2026-09-08, on this VM's Venus adapter. Destroying the
//! last `VkInstance` makes the Vulkan loader `dlclose` the ICD; Mesa's
//! ICD (`/usr/lib/libvulkan_virtio.so`) registers a `pthread_key_create`
//! destructor pointing into its own text and is not linked `-z nodelete`,
//! so glibc calls that destructor through unmapped memory when the thread
//! that used Vulkan exits. libtest runs every `#[test]` on a spawned
//! thread, which is why it looked like "wgpu crashes on drop": the drop
//! itself completes, and the crash lands as the thread unwinds.
//!
//! The four modes are the experiment, and each is one variable:
//!
//! | mode | what it does | 2026-09-08 |
//! |---|---|---|
//! | `main` | wgpu instance + device on the main thread, dropped | exits 0 |
//! | `thread` | the same on a spawned thread | **SIGSEGV** |
//! | `keep` | the same, but the instance is never dropped | exits 0 |
//! | `raw` | raw Vulkan (`ash`), instance + device, spawned thread | **SIGSEGV** |
//!
//! `raw` is the one that says whose bug it is: no wgpu is involved, so
//! there is nothing for wgpu or a caller to fix in its drop order. `keep`
//! is the fix -- hold one `wgpu::Instance` for the process, which is what
//! wgpu asks for anyway. `iris/tests/mask_sdf.rs` does exactly that.
//!
//! `VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING=1` also makes every mode
//! exit cleanly, which is the confirmation that the unload is the
//! mechanism -- but it is an environment variable every caller would have
//! to remember, so it belongs in this comment rather than in a script.
use ash::vk;
use pollster::block_on;
use wgpu::*;
fn main() {
let mode = std::env::args().nth(1).unwrap_or_else(|| "thread".into());
let body = match mode.as_str() {
"main" => return wgpu_open_and_close(false),
"thread" => || wgpu_open_and_close(false),
"keep" => || wgpu_open_and_close(true),
"raw" => raw_vulkan_open_and_close,
other => panic!("unknown mode {other:?}: main | thread | keep | raw"),
};
std::thread::spawn(body).join().expect("the spawned thread");
// Not reached when the thread's exit takes the process with it.
eprintln!("thread joined");
}
/// A wgpu instance and device, opened and closed. `keep_instance` is the
/// fix under test: everything else still drops normally.
fn wgpu_open_and_close(keep_instance: bool) {
let instance = Instance::default();
let adapter =
block_on(instance.request_adapter(&RequestAdapterOptions::default())).expect("no adapter");
let info = adapter.get_info();
eprintln!(
"adapter: {} ({:?}, {})",
info.name, info.backend, info.driver
);
let (device, queue) =
block_on(adapter.request_device(&DeviceDescriptor::default())).expect("no device");
drop(queue);
drop(device);
drop(adapter);
if keep_instance {
std::mem::forget(instance);
} else {
drop(instance);
}
eprintln!("wgpu closed");
}
/// The same shape with no wgpu in it at all, which is what makes this a
/// loader/driver bug rather than a wgpu one.
fn raw_vulkan_open_and_close() {
unsafe {
let entry = ash::Entry::load().expect("vulkan loader");
let app = vk::ApplicationInfo::default().api_version(vk::make_api_version(0, 1, 1, 0));
let instance = entry
.create_instance(
&vk::InstanceCreateInfo::default().application_info(&app),
None,
)
.expect("instance");
let phys = instance.enumerate_physical_devices().expect("devices")[0];
let priorities = [1.0f32];
let queues = [vk::DeviceQueueCreateInfo::default()
.queue_family_index(0)
.queue_priorities(&priorities)];
let device = instance
.create_device(
phys,
&vk::DeviceCreateInfo::default().queue_create_infos(&queues),
None,
)
.expect("device");
device.destroy_device(None);
instance.destroy_instance(None);
}
eprintln!("raw vulkan closed");
}