mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
4.8 KiB
4.8 KiB
GPU develop pipeline — design note (M5.2, not implemented yet)
The CPU pipeline (crates/pipeline) is the reference ("oracle"). A lightcraft-gpu crate (L3) will
evaluate the same stages with wgpu compute shaders (WGSL), on native (Metal/Vulkan/DX12) and on the
web (WebGPU). This note records the plan so the CPU work in M5 stays compatible with it.
Why, and what the CPU path already does
- CPU numbers (24 MP source, 2560 px preview, ~2.5 MP loupe; see
crates/engine/examples/render_bench.rs): the per-pixel stage (finish) costs ~50 ns/px of CPU time with typical edits (+~65 ns/px with vibrance/saturation: the OkLCh round trip), noise reduction + spatial planes another ~80 ns/px when they have to be recomputed. With all cores and the stage cache this meets the CPU budgets for tone/colour drags (~7 ms draft, ~18 ms full at 2.5 MP); a GPU makes cold full-quality renders, NR drags, 4–8K displays and 1:1 zoom of 60 MP files interactive, and frees the CPU for previews/import. - The CPU path is already structured the way the GPU path wants it:
- Stage cache (
StageCache): resampled source → white-balanced/denoised image → spatial planes (log luminance, guided base, clarity/texture bands, dark channel) → per-pixel stage. Each stage is keyed by the settings that feed it. On the GPU the same keys decide which textures are still valid; a tone/colour/exposure drag re-runs one full-screen compute pass. - Exposure after the spatial filters (shift-equivariant on log luminance), so exposure is a per-pixel uniform, not a cache invalidation.
- Draft quality during drags (smaller output, capped base radius), full on release.
- Stage cache (
Architecture
lightcraft-gpuowns awgpu::Device(shared with eframe's when running in the desktop app, so the loupe texture never leaves the GPU; headless device for CLI/MCP/export).- Resident per-photo state: the source pyramid as
rgba16floattextures (levels /2, /4, … down to the thumbnail), uploaded once per photo; the stage textures mirrorStageCacheentries (r32floatplanes,rgba16floatimages). - Kernels (one WGSL module each, workgroups of 16×16, storage textures):
sample: orientation + crop/rotate + flips from the nearest pyramid level (bilinear on the level ≥ output scale — matches the CPU's area-prefiltered resample within tolerance).wb: 3×3 matrix (+ spots as instanced quads: copy/blend from the source offset).nr: guided filter on log luminance (box sums via two-pass separable running sums in shared memory, or a summed-area table for large radii) + chroma Gaussian.planes: log luminance; guided base / clarity (fast guided filter: coefficients on a /s subsampled grid, bilinear upsample — exactly the CPUguided_faststructure); texture Gaussian (3 box passes); dark channel Gaussian + airlight (parallel reduction: histogram of the dark channel → 99.5th percentile).masks: one pass per mask shape (analytic shapes evaluate per pixel; brushes rasterized as instanced soft discs; range masks read the image/planes).finish: the per-pixel stage, a straight port offinish.rs(tone LUT and curve LUTs as 1D textures, colour ops in OkLCh, gamut map, sRGB encode, grain hash). Outputrgba8unormfor display,rgba16float/rgba32floatfor export.histogram: atomics into a storage buffer (256 bins × 4).
- Readback only for export and for
ui.screenshot/MCP renders.
Correctness: CPU oracle and equivalence tests
- Every kernel has a CPU twin; tests render the synthetic scenes (
lightcraft-scenes) on both and require max |Δ| ≤ 1/1023 in display-encoded values (architecture §1.6), plus the existing pipeline tests run against the GPU renderer behind a feature flag. Tests skip (not fail) when no adapter is available (CI machines), with a software adapter (wgpu's GL/lavapipe) where possible. - Float determinism: avoid fast-math-sensitive constructs (fused multiply-add differences are within tolerance); reductions (airlight, histogram) use integer atomics.
Scheduling
- One render queue per view; a new revision cancels queued work for the old one (CPU already drops superseded jobs per slot). Drafts during drags at display size (GPU can afford full size), full quality (larger NR/guided radii at source resolution for 1:1) on release.
- Fallback order: GPU → CPU (
render_cached) when no adapter, on device loss, or for wasm builds without WebGPU.
Open questions
- f16 vs f32 storage for planes (bandwidth vs. guided-filter precision: start with f32).
- Sharing the device with eframe on the web (single WebGPU device per canvas).
- Large exports (60 MP+) exceed texture limits on some adapters (8192/16384): tile with overlap equal to the largest filter radius (the planes' radii are known per settings).