diff --git a/crates/gpu/src/ctx.rs b/crates/gpu/src/ctx.rs index 73d7dc366..9897faf5f 100644 --- a/crates/gpu/src/ctx.rs +++ b/crates/gpu/src/ctx.rs @@ -758,9 +758,8 @@ fn refuse_adapter(device_type: wgpu::DeviceType, allow_software: bool) -> bool { } /// `LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1`: accept a software adapter. For tests on machines without a -/// GPU (CI runners: Mesa lavapipe on Linux, WARP on Windows), so the CPU ↔ GPU equivalence tests -/// run there instead of skipping; the app never sets it (software rendering is slower than the CPU -/// pipeline). +/// GPU (Mesa lavapipe on Linux, WARP on Windows), so the CPU ↔ GPU equivalence tests run there +/// instead of skipping; the app never sets it (software rendering is slower than the CPU pipeline). fn software_allowed() -> bool { std::env::var("LIGHTCRAFT_GPU_ALLOW_SOFTWARE").is_ok_and(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) } diff --git a/crates/gpu/tests/it/equivalence.rs b/crates/gpu/tests/it/equivalence.rs index b043152fc..6a1a8464c 100644 --- a/crates/gpu/tests/it/equivalence.rs +++ b/crates/gpu/tests/it/equivalence.rs @@ -2,8 +2,8 @@ //! `lightcraft_gpu::render`, compared in 8-bit sRGB. //! //! Bounds (documented in `docs/gpu-pipeline.md`): mean |Δ| < 0.5 LSB and max |Δ| ≤ 3 LSB per -//! channel for every case. Skips (passes with a note) when no GPU adapter exists; CI runs them on -//! software adapters with `LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1` (lavapipe, WARP) and on Metal. +//! channel for every case. Skips (passes with a note) when no GPU adapter exists; +//! `LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1` runs them on a software adapter (lavapipe, WARP) instead. use std::sync::Arc; @@ -899,7 +899,7 @@ static BIG_SOURCE_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(()); /// The big photo of these tests: 10.24 MP, a 117 MiB source. That is over what a view keeps with its /// stages (96 MiB, so it is shared) and within a 128 MiB storage binding (the WebGPU default, and -/// the limit of Mesa's lavapipe in CI), so the source goes to the device on every adapter. A source +/// the limit of Mesa's lavapipe), so the source goes to the device on every adapter. A source /// over the device's limit is resampled on the CPU instead and never uploaded, which would leave /// these tests nothing to check. const BIG: usize = 3200; diff --git a/docs/gpu-pipeline.md b/docs/gpu-pipeline.md index de246f432..736c20871 100644 --- a/docs/gpu-pipeline.md +++ b/docs/gpu-pipeline.md @@ -50,8 +50,8 @@ Overrides (read once at launch): - Both `LIGHTCRAFT_GPU=0` and `LIGHTCRAFT_GPU_BACKEND=off` also keep AI Denoise, the face models and the AI mask model (SAM 3 on Metal) on the CPU. - `LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1`: accept a software adapter (llvmpipe / lavapipe, WARP), which is - otherwise refused because the CPU pipeline is faster. For tests on machines without a GPU (CI); - the app never sets it. + otherwise refused because the CPU pipeline is faster. For running the GPU tests on a machine + without a GPU (below); the app never sets it. **DX12 shader compiler (issue #471).** Every instance (window and compute) compiles DX12 shaders with FXC (`d3dcompiler_47.dll`, part of Windows; `lightcraft_gpu::backend::backend_options`). wgpu's @@ -275,15 +275,30 @@ bit-identical, and one (the point colour range overlay) has max 2 LSB on 0.0002% mean 0.0003 LSB. That is the same picture as on Metal, inside the bounds (`cargo test -p lightcraft-gpu --release --test it equivalence:: -- --nocapture`). -**In CI** (`.github/workflows/ci.yml`, issue #1050) they run on every platform: on Metal on the macOS -runners, and on software adapters on Linux (Mesa lavapipe) and Windows (WARP, DX12 with FXC), which -`LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1` admits (tests only: the app never sets it). The step fails if they -skip. Measured on lavapipe (CI and locally) and WARP: all 134 compared renders within **max 1 LSB, -mean ≤ 0.0003 LSB**, as on the M4 Pro; on the macOS runner's (paravirtual) Metal device one case reaches -2 LSB (point colour range overlay, 0.0002% of pixels over 1), the rest max 1. The suite takes about -50 s on lavapipe, 30 s on the macOS runner and 2–7.5 min on WARP. lavapipe's -storage bindings stop at 128 MiB (the WebGPU default), so the two big-source tests use a 117 MiB -source: above what a view keeps with its stages (96 MiB) and small enough to be uploaded there. +**Running them without a GPU (issue #1050).** `Gpu::new` turns down software adapters, so on a +machine without a hardware GPU (a VM, a container) every GPU test skips. +`LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1` lets them through for one run (tests only: the app never sets it): + +```sh +rm -f /tmp/gpu-skips.jsonl # the log is appended to; an absolute path, as tests run in crates/gpu +LIGHTCRAFT_GPU_ALLOW_SOFTWARE=1 LIGHTCRAFT_SKIP_LOG=/tmp/gpu-skips.jsonl cargo test -p lightcraft-gpu --tests +grep '"reason":"no GPU adapter' /tmp/gpu-skips.jsonl # no output: nothing skipped for want of an adapter +``` + +- **Linux:** Mesa's lavapipe (Vulkan; `mesa-vulkan-drivers` on Debian and Ubuntu). Its storage + bindings stop at 128 MiB (the WebGPU default), so the two big-source tests use a 117 MiB source: + above what a view keeps with its stages (96 MiB) and small enough to be uploaded there. +- **Windows:** WARP, the DX12 software rasterizer that ships with Windows (shaders through FXC, as + above). +- **macOS:** Metal is a hardware adapter, so the tests run without the variable. + +A machine with a hardware GPU picks it over the software adapter, variable or not. Each test that +skips prints `skipped (gpu): …` and appends a line to the `LIGHTCRAFT_SKIP_LOG` file; with a software +adapter the only one left should be the device-loss test (`no GPU device, or one that can't be lost on +purpose here`). Measured 2026-10-11 on lavapipe and WARP, and again 2026-10-30 on lavapipe (Mesa's +llvmpipe on LLVM 20, 143 compared renders), every compared render stayed within **max 1 LSB, mean ≤ +0.0003 LSB**, as on the M4 Pro. The equivalence binary takes about 20 s on lavapipe and several +minutes on WARP. Remaining differences come from f32 vs f64 coordinate math, fast-math transcendental functions on Metal and summation order in the box filters — all far below one 8-bit step.