Merge approved PhotoCraft PR #9110: test: GPU tests honour WGPU_BACKEND, so they run on Windows with an Intel GPU (#4)

This commit is contained in:
FredApps
2026-10-29 03:29:47 +01:00
13 changed files with 27 additions and 12 deletions
+2 -1
View File
@@ -17,7 +17,8 @@ fn render(comp: &mut Compositor, device: &wgpu::Device, queue: &wgpu::Queue, doc
fn main() -> Result<(), Box<dyn std::error::Error>> {
let quick = std::env::args().any(|a| a == "--quick");
let size = if quick { Size::new(1024, 768) } else { Size::new(6000, 4000) };
let adapter = pollster::block_on(wgpu::Instance::default().request_adapter(&Default::default()))?;
let adapter =
pollster::block_on(wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()).request_adapter(&Default::default()))?;
let (device, queue) = pollster::block_on(adapter.request_device(&Default::default()))?;
let format = Compositor::preferred_acc_format(&adapter);
println!("adapter={:?}, accumulation={format:?}, size={}x{}", adapter.get_info(), size.width, size.height);
+1 -1
View File
@@ -12,7 +12,7 @@ use photocraft_raster::Surface;
use crate::GPU_LOCK;
fn device() -> Option<(wgpu::Adapter, wgpu::Device, wgpu::Queue)> {
let instance = wgpu::Instance::default();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let adapter = match pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) {
Ok(a) => a,
Err(e) => {
+4 -1
View File
@@ -136,7 +136,10 @@ fn gpu() -> Option<Gpu> {
/// The returned guard holds [`GPU_LOCK`] until the device is dropped.
fn any_device() -> Option<(wgpu::Adapter, wgpu::Device, wgpu::Queue, std::sync::MutexGuard<'static, ()>)> {
let lock = GPU_LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let adapter = pollster::block_on(wgpu::Instance::default().request_adapter(&wgpu::RequestAdapterOptions::default())).ok()?;
let adapter = pollster::block_on(
wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()).request_adapter(&wgpu::RequestAdapterOptions::default()),
)
.ok()?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()?;
Some((adapter, device, queue, lock))
}
+1 -1
View File
@@ -38,7 +38,7 @@ fn block_on<F: std::future::Future>(f: F) -> F::Output {
/// A real GPU with timestamp queries, or `None` with the reason printed.
fn gpu() -> Option<(wgpu::Device, wgpu::Queue, Compositor)> {
let options = wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, ..Default::default() };
let Ok(adapter) = block_on(wgpu::Instance::default().request_adapter(&options)) else {
let Ok(adapter) = block_on(wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()).request_adapter(&options)) else {
eprintln!("SKIP gpu timing: no adapter");
return None;
};
+1 -1
View File
@@ -20,7 +20,7 @@ fn fills_size(count: usize, size: Size) -> Document {
}
fn constrained_device() -> Option<(wgpu::Adapter, wgpu::Device, wgpu::Queue)> {
let instance = wgpu::Instance::default();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let adapter = match pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) {
Ok(adapter) => adapter,
Err(error) => {
+1 -1
View File
@@ -6,7 +6,7 @@ use photocraft_doc::{Adjustment, Document, Effect, Layer, LayerContent, LayerMas
use photocraft_geom::Rect;
fn gpu() -> Option<(wgpu::Device, wgpu::Queue, photocraft_gpu::Compositor)> {
let instance = wgpu::Instance::default();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())).ok()?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()?;
// Exact comparison needs 32-bit float targets (see parity.rs); skip on adapters without them.
+7
View File
@@ -11,6 +11,13 @@
//! - **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.
+1 -1
View File
@@ -101,7 +101,7 @@ struct Gpu {
}
fn gpu() -> Option<Gpu> {
let instance = wgpu::Instance::default();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let adapter =
block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, ..Default::default() }))
.ok()?;
+1 -1
View File
@@ -136,7 +136,7 @@ struct Gpu {
}
fn gpu() -> Option<Gpu> {
let instance = wgpu::Instance::default();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let adapter =
block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, ..Default::default() }))
.ok()?;
+1 -1
View File
@@ -44,7 +44,7 @@ struct Gpu {
}
fn gpu() -> Option<Gpu> {
let instance = wgpu::Instance::default();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let adapter =
block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, ..Default::default() }))
.ok()?;
@@ -23,7 +23,9 @@ fn block_on<F: std::future::Future>(f: F) -> F::Output {
}
fn gpu() -> Option<(wgpu::Device, wgpu::Queue, Compositor)> {
let adapter = match block_on(wgpu::Instance::default().request_adapter(&wgpu::RequestAdapterOptions::default())) {
let adapter = match block_on(
wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()).request_adapter(&wgpu::RequestAdapterOptions::default()),
) {
Ok(a) => a,
Err(e) => {
eprintln!("skipping adjustment preview GPU parity: no adapter ({e})");
+3 -1
View File
@@ -232,7 +232,9 @@ fn layout_document_composites_on_the_gpu_like_the_cpu() {
std::thread::yield_now();
}
}
let Ok(adapter) = block_on(wgpu::Instance::default().request_adapter(&wgpu::RequestAdapterOptions::default())) else {
let Ok(adapter) =
block_on(wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()).request_adapter(&wgpu::RequestAdapterOptions::default()))
else {
eprintln!("skipping: no GPU adapter");
return;
};
+1 -1
View File
@@ -158,7 +158,7 @@ By default PhotoCraft's own crates log at `info` and everything else at `warn`.
| `PHOTOCRAFT_CPU_CANVAS=1` | Force the CPU canvas path instead of the wgpu shader canvas |
| `PHOTOCRAFT_NATIVE_WAYLAND=1` | Linux: stay on native Wayland when a pen is attached (by default the window then opens through Xwayland, because Wayland gives the app no pen input; #639). A pen is an input device with `BTN_TOOL_PEN` and the `ABS_X`/`ABS_Y` axes; virtual keyboards that claim every key are not pens (#1072) |
| `PHOTOCRAFT_GPU_ADAPTER=nvidia` | Linux: use the graphics adapter whose name contains this (case-insensitive); overrides `performance.gpuAdapter` for one launch |
| `WGPU_BACKEND=dx12` | Pick the wgpu backend(s) (`vulkan`, `dx12`, `metal`, `gl`); overrides `performance.gpuBackend` and the startup fallback |
| `WGPU_BACKEND=dx12` | Pick the wgpu backend(s) (`vulkan`, `dx12`, `metal`, `gl`); overrides `performance.gpuBackend` and the startup fallback. Tests read it too: on Windows with an Intel GPU, run them with `WGPU_BACKEND=dx12`, since loading the Intel Vulkan driver crashes the test process with `STATUS_ACCESS_VIOLATION` (#4) |
| `WGPU_DX12_COMPILER=fxc` | DX12 shader compiler (`fxc`, `dxc`, `auto`); `dxc` and `auto` look `dxcompiler.dll` up through the DLL search path |
| `PHOTOCRAFT_GPU_TILE=2048` | Force GPU canvas tiling (tests tile seams) |
| `PHOTOCRAFT_TEXEL_GUARD=1` (or `=0`) | Force on (off) uploading egui's fully transparent texels as alpha 1/255. On by default only for Intel Iris Xe on DX12, whose driver shows stale memory in all-zero texture blocks: red squares in brush previews and tool icons (`ui-egui/src/texel_guard.rs`; check a GPU with `cargo run --release -p photocraft-gpu --example zero_block_probe -- dx12 400`) |