Commit Graph

9 Commits

Author SHA1 Message Date
hxw 5630734832 LR-BEHAV-BG-TASKS: render export batches side by side (issue #496)
A batch ran its photos strictly one after another: each render is
row-parallel inside, but a photo's decode, its PNG/TIFF/WebP encode and
the wait on a GPU render are mostly serial, so a 32-core machine averaged
~9.5 busy cores on the CPU path and 1-2 with the GPU.

run_batch now keeps export_parallelism() photos in flight (cores / 4,
1..=4; one on wasm32) on scoped lane threads plus the calling thread.
Each holds an estimate of its working memory from a new export_gate
(twice the memory budget, separate from the decode gate it nests over),
so full-size 24 MP exports (~1.2 GB each) run two at a time by default.
Lanes run at most `lanes` photos past the last one placed, which also
bounds the finished files waiting for their turn. Paths, conflict
handling, original protection, writes, results and progress stay in
batch order (Placer), so the files are the same as one at a time; a
panic in a lane becomes that photo's error.

render_bench's batch scenario now goes through export_batch. 8 x 24 MP
JPEGs, 32 cores + RTX 4080 Laptop: CPU 3.87 s -> 2.35 s, GPU 4.21 s ->
1.82 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:00:59 -05:00
radialmonster 03bc1469f9 LR-EDIT-OPTICS-PROFILE: apply the distortion correction Panasonic/Leica record in RW2/RWL (#256)
The RW2 reader reads PanasonicRaw tag 0x0119 (16 LE words) and expresses the
camera's own correction as the equivalent DNG WarpRectilinear in OpcodeList3,
so embedded_lens() turns it into the existing EmbeddedLens/EmbeddedWarp and
"Enable Profile Corrections" applies it like a DNG's warp (no new warp path;
Convert to DNG keeps it). Rule, from our own measurements against the
embedded camera JPEGs (clean-room; ExifTool tag names only):
corrected = rho*(1 + (w8 rho^2 + w4 rho^4 + w11 rho^6)/32768), rho = r/w12
(w12 = active-area half diagonal), output = corrected / (1 + w5/32768),
on when (w7 & 0xf) == 1. Chosen on 38 GX85 photos, held out on 35 photos
from 12 other bodies / 9 other lenses; corrected renders line up with the
camera JPEG within 0.75 px at 1440 px at the corners (up to 13 % of the
half diagonal before). Implausible values leave the photo uncorrected.

The loader now removes lens opcodes before the RW2 camera-look fit (its
binned proxy needs an empty OpcodeList3); Opcode::is_lens_correction()
replaces the duplicated matches. RENDER_CACHE_VERSION 11 -> 12.
2026-10-08 00:13:58 -04:00
Brandon Thomas 64dfa8adbd Merge edit-panel parity (Point Color, Calibration, curve refine saturation + targeted adjustment, red/pet eye, visualize spots)
Fix-ups: diagnostic overlays never populate the view-preview cache; Photo Merge's RenderRequest gets
the new overlay field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:02:11 -04:00
Brandon Thomas 490fe943cd LR-EDIT-COLOR-POINTCOLOR: Point Color — sampled colours with shift, variance, range and visualize
Up to 8 OkLCh samples (pointColor.pick samples the colour after the mixer on a proxy render);
per sample hue/saturation/luminance shift, variance (compress/expand the spread around the
sample) and an overall + per-axis range with soft edges, applied after the colour mixer in
ColorOps and its WGSL twin (equivalence case). Indexed control specs
(pointColor.<i>.hueShift …) so develop.set/controls/MCP address each sample. Visualize range
is a render overlay (RenderRequest::overlay) drawn after either renderer: the sample's own
edit is left out and colours outside the range turn grey. Point Color flyout in the Color
section with swatches, eyedropper, sliders, visualize and delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:18:28 -04:00
Brandon Thomas ec6637f238 M5.4b: faster raw path — bin previews/thumbnails straight from the mosaic
- lightcraft-raw: RawImage::develop_binned(k) — k×k superpixel binning of the CFA
  (Bayer k even, X-Trans k multiple of 3), black/white normalised row by row, default
  crop only, clipping preserved for highlight reconstruction. 2560 px previews of a
  24 MP raw bin 2×, thumbnails 4–8×; exports / 1:1 still demosaic (AHD) at full size.
- highlight::reconstruct and its pyramid fill are rayon-parallel (bit-exact).
- X-Trans directional demosaic: precomputed per-phase neighbour offsets and no
  reflection in the interior, no per-pixel allocation (bit-exact, tested against the
  reference formulation): 2.9 s → 0.75 s CPU at 24 MP.
- files::load_bytes runs on a rayon worker so its short parallel loops don't each
  wait for the pool to wake (dominant cost on a loaded machine); LIGHTCRAFT_PROFILE
  prints per-stage source timings; files::bin_factor picks the block size.
- render_bench ONLY=source: per-file decode / normalise / demosaic / binning / load.
- Fix: u64 overflow on hostile IFD sizes in DNG/ARW/NEF raw-IFD selection (proptest).

load_bytes(2560) CPU time, ARW a7 III 24 MP: 2347 → 240 ms; X-T20: 3120 → 1220 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:01:53 -04:00
Brandon Thomas 30aac43fab M5.2e: renders and exports use the GPU; render_bench GPU column
`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>
2026-10-01 08:19:44 -04:00
Brandon Thomas 74b94e089a M5.3: compute independent spatial planes side by side
The guided base, clarity band, texture band and dark channel (and the paired blurs inside the
guided filter) are independent; each alone scales poorly (subsampled grids, short rows), so they now
run under raster::par_join. Same arithmetic, identical pixels. render_bench gains a per-feature
breakdown of the per-pixel stage (ONLY=finish).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:01:25 -04:00
Brandon Thomas 452521a16f M5.1: full-resolution source level for exports larger than a preview
Exports above 2560 px were rendered from the 2560 px preview proxy and upsampled. SourceLevel::Full
(native size; procedural demo scenes at their nominal size) is now selected for outputs larger than
a preview; the media cache keeps only the last full-size original. render_bench gains a batch-export
scenario (decode + render + JPEG per file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:03:48 -04:00
Brandon Thomas 42d2298118 M5.3: stage cache — slider drags redo only the stages the setting feeds; draft quality during drags
- pipeline: StageCache + render_cached: resampled source, white-balanced/retouched/denoised image
  and each spatial plane (log L, guided base, clarity, texture, dark channel + airlight) are keyed
  by exactly their inputs; keeps a draft-size and a full-size entry warm.
- Exposure moved after the spatial filters (applied per pixel; planes shifted by EV): identical up
  to rounding (test: <= 2 LSB vs. exposing the source), so exposure drags hit the cache too.
- engine: RenderJob::draft / with_stages; ui: loupe and before views own a StageCache, loupe
  renders at Draft quality while interacting.
- engine example render_bench (min-of-N wall + process CPU time).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:42:50 -04:00