mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-15 14:43:28 +00:00
Loading the Intel Vulkan driver on Windows (igvk64.dll) crashes the process with STATUS_ACCESS_VIOLATION (#4). The app leaves Vulkan out on Intel, but the tests use wgpu's defaults, so on such a machine `cargo test` dies in the first GPU test (ui-egui's background_redraw, adjust_preview_gpu, ...). WGPU_BACKEND=dx12 avoids it for the kittest harnesses, but seven tests and four benches built their instance with wgpu::Instance::default(), which takes every backend and ignores the environment. They now use InstanceDescriptor::new_without_display_handle_from_env(): the same defaults with WGPU_BACKEND and friends applied, so nothing changes where it is unset. Document the workaround in docs/development.md and the testkit driver notes.
72 lines
2.9 KiB
Rust
72 lines
2.9 KiB
Rust
//! One GPU harness at a time per test process.
|
|
//!
|
|
//! `cargo test` runs a binary's tests on parallel threads, and several graphics drivers crash
|
|
//! when one process drives wgpu devices from more than one thread at once:
|
|
//!
|
|
//! - **WARP** (Windows): egui_kittest prefers a CPU adapter, which on Windows is WARP over DX12,
|
|
//! so every kittest GPU test there runs on it. Two or more WARP devices rendering in parallel
|
|
//! fault inside `d3d10warp.dll` (`STATUS_ACCESS_VIOLATION`); the GitHub Windows runners have no
|
|
//! GPU, so CI runs on WARP too.
|
|
//! - **Mesa** llvmpipe over GL and RADV (#194).
|
|
//! - **libvulkan** with the NVIDIA and lavapipe drivers both installed: `wgpu::Instance::new` on
|
|
//! several threads segfaults.
|
|
//!
|
|
//! Separately, the **Intel Vulkan driver** on Windows (`igvk64.dll`) crashes a process that so
|
|
//! much as creates a wgpu instance with Vulkan in it (#4), even on one thread. The app leaves
|
|
//! Vulkan out on Intel; tests use wgpu's defaults, so on such a machine run them with
|
|
//! `WGPU_BACKEND=dx12`. Tests that build their own instance read it through
|
|
//! `InstanceDescriptor::new_without_display_handle_from_env`; `wgpu::Instance::default()`
|
|
//! ignores it.
|
|
//!
|
|
//! So every test that builds a GPU harness (or its own wgpu device) holds [`gpu_lock`] for its
|
|
//! whole run, harness included. Under nextest each test is its own process and the lock costs
|
|
//! nothing; `.config/nextest.toml` caps how many of those run at once instead.
|
|
|
|
use std::sync::{Mutex, MutexGuard, PoisonError};
|
|
|
|
static GPU_LOCK: Mutex<()> = Mutex::new(());
|
|
|
|
/// Hold this for as long as the test uses a GPU harness or device: take it on the test's first
|
|
/// line (`let _gpu = gpu_lock();`) so it is released last. A test that failed while holding it
|
|
/// doesn't block the rest.
|
|
pub fn gpu_lock() -> MutexGuard<'static, ()> {
|
|
GPU_LOCK.lock().unwrap_or_else(PoisonError::into_inner)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn a_panicked_holder_does_not_block_the_next_test() {
|
|
let _ = std::thread::spawn(|| {
|
|
let _gpu = gpu_lock();
|
|
panic!("a GPU test failed");
|
|
})
|
|
.join();
|
|
drop(gpu_lock());
|
|
}
|
|
|
|
#[test]
|
|
fn holders_take_turns() {
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
static INSIDE: AtomicUsize = AtomicUsize::new(0);
|
|
static MAX: AtomicUsize = AtomicUsize::new(0);
|
|
let threads: Vec<_> = (0..8)
|
|
.map(|_| {
|
|
std::thread::spawn(|| {
|
|
let _gpu = gpu_lock();
|
|
let now = INSIDE.fetch_add(1, Ordering::SeqCst) + 1;
|
|
MAX.fetch_max(now, Ordering::SeqCst);
|
|
std::thread::sleep(std::time::Duration::from_millis(2));
|
|
INSIDE.fetch_sub(1, Ordering::SeqCst);
|
|
})
|
|
})
|
|
.collect();
|
|
for t in threads {
|
|
assert!(t.join().is_ok());
|
|
}
|
|
assert_eq!(MAX.load(Ordering::SeqCst), 1);
|
|
}
|
|
}
|