Files
PiMinusMax 3943279105 Colour noise reduction: joint linear chroma filter with speck brake, CPU and GPU (#815) (#1348)
* WIP: colour NR v2 prototype (env LIGHTCRAFT_NR_V2, CPU only)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: colour NR v2 on by default (LIGHTCRAFT_NR_V2=0 turns it off), GPU renders read back for it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: cache renders with the new colour NR apart from older ones

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: colour NR v2 on the GPU (fused down / apply kernels), coefficient grid sigma / 2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: colour NR v2 on the CPU without full-size intermediates (row-streamed passes)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: report peak device-buffer use per GPU stage with LIGHTCRAFT_PROFILE

* WIP: colour NR v2 linear mode (NRV2_LINEAR=1: c - Y, additive, noise model), Jensen term, mean-drift probe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: colour NR v2 noise model with equal-count luminance bands and a non-negative fit; probe asserts the mean over 3 noise realisations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: colour NR v2 linear mode by default (per channel, K 64, coarse pass smooths only the coarse part); CPU on GPU renders; real-colour probe

Known: tests_window::noise_reduction_does_not_depend_on_the_window fails (the noise is measured in the
rendered window; the square-root mode failed it too).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: colour NR v2 joint guide (NRV2_JOINT=1, opt-in), luminance guide knobs, leaf-next-to-black probe, camera-look guard from 858

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: colour NR v2 reads its env knobs once (per-pixel env reads made the linear mode 11x slower); joint stage timings with LIGHTCRAFT_PROFILE

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: joint colour NR without redundant passes (fused paths + next weights, none after the last scale, delta-1 weights from the path), two-component buffers, parallel reconstruction

Bit-identical to 8b89526d (test joint_matches_the_reference_bit_for_bit against a test-only copy).
Cat 32.7 MP, CPU: 6.2 -> 5.0 s at Color 25, 7.0 -> 5.6 s at 100; working buffers 2.36 -> 1.44 GB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: joint colour NR with separate H/V slice loops and zero-difference shortcuts (no division, weight 1 without exp)

Still bit-identical to 8b89526d. Interleaved 24 MP bench: 2.72 s vs 4.52 s for the reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: joint colour NR on the GPU (nrcj.wgsl), default on; whole-image noise model from samples, halo bands above 2 GB

Kernels mirror nrc_joint step for step (same scale plan, noise model fitted on the CPU from GPU samples).
Cat 32.7 MP on a Quadro M2200: wb/nr 1.2-1.3 s vs 4-5 s CPU; against the CPU max 1 LSB; bands = single.
Waits after dropping large buffers (freed memory otherwise piled up to the whole 4 GB). Cache key includes joint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: GPU joint NR plans its bands from a budget that includes the output buffer and the device buffer limit; halo 2*sum(delta) + max delta + 8; a band that cannot fit runs only this stage on the CPU

Bands vs one piece: at most 1 LSB in a few dozen pixels (running-sum boxes round differently per band start).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: GPU joint NR budgets its bands from the free device memory where it can be asked without unsafe (nvidia-smi, amdgpu sysfs), cached 3 s; fixed 2 GB otherwise

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: docs/nr-color-v2-history.md: every approach tried, measured and dropped, speed numbers, known limits (no outlier brake yet)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: history doc: speck behaviour stated qualitatively until measured on full-size renders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: joint colour NR speck brake: compact colour specks (chroma far past the noise, small luminance step, shift not reaching the 5x5 border) lose their colour before the guide is built; weak at Color 25, full at 100; CPU and GPU

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: joint NR CPU 5.2 -> 3.5 s (33 MP), bit for bit: guide as planes with branch-free distance rows, exponentiated paths, pilot straight into planes, speck brake in place with a min/max pre-test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: joint NR GPU: path and weight pairs instead of quads (48 instead of 60 bytes per pixel, 33 MP in one piece), speck and pilot kernels without loop-indexed private arrays, noise fit cached for identical samples; Quadro M2200 2.15 -> 1.47 s, HD 630 5.66 -> 4.26 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* WIP: speck brake: compactness from the second largest border share (a line or edge crosses the 5x5 border twice; one noisy border pixel no longer protects a speck)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #815: colour noise reduction as process V3

The joint method (linear R−Y / B−Y, edge-aware guide, speck brake) is process V3 and the latest; edits on V1 and V2 keep main's blurred chromaticity. The prototype switches (LIGHTCRAFT_NR_V2, NRV2_*) and the square-root and per-channel variants they selected are gone, with their GPU kernels; nrc_params uses the former defaults as constants, so V3 renders as the prototype did. The GPU falls back to the CPU joint filter when no band fits. vram is native only (wasm build). Docs: process-versions, nr-color-joint, parity checklist, what's new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #815: process routing test initialises its settings in place (clippy field_reassign_with_default)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #815: one GPU band plan at a time, with a reserve for every render on the device

An export batch renders two photos side by side (`export_parallelism`). Each joint colour NR planned
its bands from the device memory free at that moment and counted the shared buffer pool as its own,
so two plans made at once could together ask for more than there is. Measured on a Quadro M2200
(4 GB): two 33 MP exports side by side peaked at 3761 of 4096 MiB.

The plan and its bands now run under one lock (`BANDS`), and the budget leaves `VRAM_RESERVE` for
each render on the device (`RENDERS`), not just for this one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #815: the joint colour NR's noise fit and pilot no longer switch at a hard bound

With main's preview-scaled luminance NR (#960) the CPU and GPU renders of a 640-px preview differed
by up to 10 levels (gpu equivalence `output_spaces_match`). Their images after luminance NR differ by
9e-7 rms, but one luminance band of the noise fit sat at exactly 4 times the first fit: the CPU's
samples dropped it, the GPU's kept it, and the fitted noise came out 2.6 % apart. The pilot's axis
choice then switched at a few pixels, and the edge-aware weights carried that on.

- robust_line: a band's weight fades to 0 between 4 and 5 times the fit instead of dropping at 4
  (identical below 4).
- pilot_pick: the smoother-axis mean fades in between 0.8 and 1.25 times its bound instead of
  switching at it (identical outside), on the CPU, in nrcj.wgsl and in the test reference.

All 19 CPU/GPU comparisons of the equivalence suite are within 1 level again. The new test
noise_line_does_not_jump_when_a_band_crosses_the_outlier_bound uses the bands of that preview and
fails on the old fit (5.6 % jump).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #815: the joint colour NR measures the whole frame's noise and is keyed by process

A window measured the noise model on the pixels it held, so a loupe window
could smooth differently from the same pixels of the full render or the
export. The model now comes from fixed tiles of the whole frame
(`lightcraft_pipeline::colour_noise`, `nrc_joint::tiles`), the same for every
window, for the CPU and the GPU, and is kept in the stage cache.

The linear stage's cache key now includes the colour filter in use, so a
process change (Update to Current Process, undo) no longer reuses the other
filter's denoised image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M-NR: deep windows match the whole render; the VRAM query no longer stalls renders

- A U16 / F32 window with the joint colour NR is grown by the filter's reach and
  cut back, like an 8-bit one (DeepImage::crop).
- The free device memory is refreshed in the background once the cached answer is
  stale; only the first question for an adapter waits. The joint NR asks before it
  takes the BANDS lock.
- nvidia-smi starts without a console window on Windows (CREATE_NO_WINDOW).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M-NR: the VRAM query finds the right card and keeps one answer per adapter

- A card is found by its PCI address (wgpu device_pci_bus_id, nvidia-smi pci.bus_id,
  the amdgpu sysfs directory) where both sides report one; else by a device id that
  only one card has. Two cards of one model without an address give no answer, and
  the fixed band budget applies.
- Each adapter keeps its own cached answer, so a late background refresh cannot
  replace another adapter's answer. A refresh not done after 10 s is retried.
- The cache tests wait on signals instead of fixed sleeps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M-NR: the VRAM cache tests no longer depend on timing

The two cache tests used a 20 ms TTL: on a slow machine a refreshed answer could go
stale again between assertions and start an extra query. They now use a 30 s TTL and
age an answer explicitly (Cache::expire, test only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* GPU: allow joint_band's eight arguments, as color_nr_joint does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-06 09:44:58 +01:00
..