From 6fbb1c5d91e04d19312318964e0c0bc8ffafb5f7 Mon Sep 17 00:00:00 2001 From: CamWight <91156642+CamWight@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:22:50 +0100 Subject: [PATCH] The offscreen GPU tests start one backend, as the app does (#1342) Offscreen GPU test devices start the backend the app would choose (WGPU_BACKEND, else one hardware backend on Windows) instead of every backend at once. Test-only. Fixes #1330. --- crates/ui-egui/src/gpu.rs | 51 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/gpu.rs b/crates/ui-egui/src/gpu.rs index 842155c4..531cb3b0 100644 --- a/crates/ui-egui/src/gpu.rs +++ b/crates/ui-egui/src/gpu.rs @@ -1314,10 +1314,36 @@ impl Resources { } } -/// A device of its own for an offscreen render or check (`None` without a GPU adapter). +/// The backends the offscreen tests start, chosen once per test process as the app chooses them +/// (`apps/solidcraft/src/startup.rs`, `choose_backends`): what `WGPU_BACKEND` names; on Windows one +/// backend, the first of D3D12, Vulkan and OpenGL with a hardware adapter (else D3D12); elsewhere +/// wgpu's default. wgpu's default on Windows starts Vulkan, D3D12 and OpenGL together, and tests +/// starting them side by side hung or crashed the test process inside the Vulkan driver. +#[cfg(all(test, not(target_arch = "wasm32")))] +pub(crate) fn offscreen_backends() -> wgpu::Backends { + static CHOSEN: std::sync::OnceLock = std::sync::OnceLock::new(); + *CHOSEN.get_or_init(|| { + let default = wgpu::InstanceDescriptor::new_without_display_handle_from_env().backends; + if !cfg!(target_os = "windows") || std::env::var("WGPU_BACKEND").is_ok_and(|v| !v.trim().is_empty()) { + return default; + } + let hardware = |backends: wgpu::Backends| { + let mut desc = wgpu::InstanceDescriptor::new_without_display_handle(); + desc.backends = backends; + let adapters = pollster::block_on(wgpu::Instance::new(desc).enumerate_adapters(backends)); + adapters.iter().any(|a| a.get_info().device_type != wgpu::DeviceType::Cpu) + }; + [wgpu::Backends::DX12, wgpu::Backends::VULKAN, wgpu::Backends::GL].into_iter().find(|b| hardware(*b)).unwrap_or(wgpu::Backends::DX12) + }) +} + +/// A device of its own for an offscreen render or check (`None` without a GPU adapter), on +/// [`offscreen_backends`]. #[cfg(all(test, not(target_arch = "wasm32")))] fn offscreen_device() -> Option<(wgpu::Adapter, wgpu::Device, wgpu::Queue)> { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env()); + let mut desc = wgpu::InstanceDescriptor::new_without_display_handle_from_env(); + desc.backends = offscreen_backends(); + let instance = wgpu::Instance::new(desc); let adapter = pollster::block_on(instance.request_adapter(&Default::default())).ok()?; let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { required_limits: wgpu::Limits::downlevel_defaults().using_resolution(adapter.limits()), @@ -1572,6 +1598,27 @@ mod tests { assert!(!super::hangs_on_gpu_tessellation(wgpu::Backend::Vulkan, "AMD FirePro W5100")); } + /// The offscreen tests start the backends the app would: on Windows without `WGPU_BACKEND` + /// exactly one, from the app's order (D3D12, Vulkan, OpenGL); otherwise wgpu's default. + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn offscreen_tests_start_the_backend_the_app_would() { + use egui_wgpu::wgpu::Backends; + let chosen = super::offscreen_backends(); + let env = std::env::var("WGPU_BACKEND").is_ok_and(|v| !v.trim().is_empty()); + if cfg!(target_os = "windows") && !env { + assert!([Backends::DX12, Backends::VULKAN, Backends::GL].contains(&chosen), "one backend, not {chosen:?}"); + } else { + assert_eq!(chosen, egui_wgpu::wgpu::InstanceDescriptor::new_without_display_handle_from_env().backends); + } + assert_eq!(super::offscreen_backends(), chosen, "chosen once per test process"); + // And the offscreen device is made on them (skipped without a GPU adapter). + if let Some((adapter, _, _)) = super::offscreen_device() { + let used = Backends::from(adapter.get_info().backend); + assert!(chosen.contains(used), "the offscreen device runs on {used:?}, not on {chosen:?}"); + } + } + /// The viewport shader parses and validates (a bad shader would only fail at run time). #[test] fn shader_is_valid() {