- Once the current photo's loupe render is done, the next and previous photos in filmstrip
order get a background loupe job (Slot::Prefetch, priority below on-screen thumbnails,
once per settings): the decoded 2560 px source is kept in the engine's bounded preview
LRU and the view render is cached, so stepping to a neighbour shows its developed image
in ~50 ms and then re-renders only on the GPU.
- lightcraft_gpu::warm_up (called when the app is created) builds the device and kernels on
a background thread; gpu::ready() answers without blocking and ui.inspect uses it, so the
UI thread never waits for device creation.
- docs/gpu-pipeline.md: opening-a-photo section.
Stepping through 24 MP raws in the loupe (real app): full render 220–580 ms → 45–62 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`media::develop` renders on the GPU when available (views, render_now, exports;
thumbnails stay on the CPU) and falls back to the CPU otherwise. `app.gpu
{enabled?}` toggles it at runtime, `LIGHTCRAFT_GPU=0` disables it for the process,
`ui.inspect` reports the adapter under perf.gpu.
Performance: dropped device buffers are recycled once their thread has submitted
(fresh 100-300 MB allocations per pass cost as much as the passes), Gaussian
passes ping-pong between two buffers and read all channels of a pixel at once,
and long box radii slide over longer chunks. LIGHTCRAFT_PROFILE prints real GPU
stage timings. render_bench prints CPU and GPU columns and the CPU/GPU difference.
24 MP ARW, loaded machine, min of 7: cold loupe 415 -> 32 ms, exposure drag
22-42 -> 3.9 ms, NR drag (1920) 301+ -> 18 ms, export render 1037+ -> 291 ms;
max 1 LSB difference. docs/gpu-pipeline.md describes the implementation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>