Integration: Camera Raw GPU tests fail on a shader that doesn't build (#6923)

The tests' setup returned None, without a message, when the device
request or the pipeline build failed, so every test passed early: with
a type error appended to camera_raw.wgsl all 13 tests still passed on a
machine with a GPU. A pipeline that doesn't build on a supported adapter
now fails the tests, a missing device skips with a message (and fails
with WGPU_BACKEND set, as in parity.rs), and a naga validation of the
shader fails on runners without a GPU too.

Found by CodeRabbit on #9386.

Co-authored-by: p0rps <134095787+fjamesprice@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Mann
2026-10-25 21:43:24 +01:00
parent 0f8521851e
commit bf3ff9ce60
+31 -4
View File
@@ -1,5 +1,8 @@
//! Camera Raw's GPU preview pass against the CPU develop (the oracle), stage by stage, at 8, 16
//! and 32 bits. Skips when there is no adapter or it can't render `Rgba32Float`.
//! and 32 bits. Skips, with a message, when there is no adapter or device or it can't render
//! `Rgba32Float`; with `WGPU_BACKEND` set those fail instead, as in `parity.rs`. A pipeline that
//! doesn't build on an adapter that can run it fails. The shader itself is validated without a
//! GPU ([`the_shader_validates_without_a_gpu`]), so a WGSL error fails on every runner.
use photocraft_algo::camera_raw::{CameraRaw, Wheel, develop, per_pixel, white_black_points};
use photocraft_gpu::{CameraRawDevelop, DeviceHealth, Fault};
@@ -20,6 +23,7 @@ struct Gpu {
fn gpu() -> Option<Gpu> {
let lock = crate::GPU_LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let required = std::env::var_os("WGPU_BACKEND").is_some();
// `WGPU_BACKEND` picks the backend; `PHOTOCRAFT_TEST_FALLBACK_ADAPTER=1` the software one (WARP).
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let fallback = std::env::var_os("PHOTOCRAFT_TEST_FALLBACK_ADAPTER").is_some();
@@ -27,20 +31,43 @@ fn gpu() -> Option<Gpu> {
let adapter = match pollster::block_on(instance.request_adapter(&options)) {
Ok(a) => a,
Err(e) => {
assert!(!required, "WGPU_BACKEND is set but has no adapter: {e}");
eprintln!("skipping Camera Raw GPU tests: no adapter ({e})");
return None;
}
};
let info = adapter.get_info();
if !CameraRawDevelop::supported(&adapter) {
assert!(!required, "WGPU_BACKEND is set but {} can't render Rgba32Float", info.name);
eprintln!("skipping Camera Raw GPU tests: adapter can't render Rgba32Float");
return None;
}
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()?;
eprintln!("Camera Raw GPU tests on {:?} ({})", adapter.get_info().backend, adapter.get_info().name);
let dev = CameraRawDevelop::new(&device).ok()?;
let (device, queue) = match pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())) {
Ok(d) => d,
Err(e) => {
assert!(!required, "WGPU_BACKEND is set but {} has no device: {e}", info.name);
eprintln!("skipping Camera Raw GPU tests: no device ({e})");
return None;
}
};
eprintln!("Camera Raw GPU tests on {:?} ({})", info.backend, info.name);
// The adapter can run the pass: a pipeline that doesn't build is a bug in it, not a skip.
let dev = CameraRawDevelop::new(&device).unwrap_or_else(|e| panic!("the Camera Raw pipeline didn't build on {}: {}", info.name, e.0));
Some(Gpu { device, queue, dev, _lock: lock })
}
/// The Camera Raw shader parses and validates (naga, as wgpu does before any driver sees it),
/// with no adapter: the GPU tests below skip on runners without one.
#[test]
fn the_shader_validates_without_a_gpu() {
let src = photocraft_gpu::CAMERA_RAW_SHADER;
let module = wgpu::naga::front::wgsl::parse_str(src).unwrap_or_else(|e| panic!("{}", e.emit_to_string(src)));
wgpu::naga::valid::Validator::new(wgpu::naga::valid::ValidationFlags::all(), wgpu::naga::valid::Capabilities::empty())
.validate(&module)
.unwrap_or_else(|e| panic!("{}", e.emit_to_string(src)));
assert!(!module.entry_points.is_empty());
}
#[derive(Clone, Copy, Debug)]
enum Depth {
Eight,