photocraft/crates
Nicolas Falesy 5f65579ecb
Some checks are pending
Documentation / mdBook build (push) Waiting to run
Nix / eval (push) Waiting to run
Packaging lint / lint (push) Waiting to run
Packaging lint / Verified AppImage inputs (push) Waiting to run
perf(panorama): Photomerge 4× faster, bit for bit the same (#6489)
N is the whole algo-side Photomerge pipeline on 3 × 8 MP photos (register at the engine's
scale with the Auto layout, gain + vignette fit, seams, multi-band blend), 16 threads:
1042 ms before, 264 ms after, 3.9×, rounded to 4×. The engine's own glue (reading regions,
building weights, writing layers) is not in this number.

Registration and multi-band blending ran almost entirely on one thread per image (four
images on 16 threads), so most of the machine sat idle. Every output is bit for bit what
the serial code produced: a probe that hashes the public Photomerge entry points
(detect_many, match_pairs, align for every layout, photometric, seam_labels, multiband at
1 to 7 levels with 1 and 3 channels, with holes in the alpha, with images sticking out of
the canvas, plus direct harris / describe calls) matches on all 349 outputs before and
after, on 4 × 2 MP, 3 × 8 MP, 97×61, 211×157, 333×251, 320×240 two-image and 40×35 sets,
at 16, 3 and 1 threads.

- features `blur` (Harris window and BRIEF smoothing): the row pass runs rows in
  parallel; the column pass cuts the image into bands of 64 columns, each holding the
  `&mut` row segments of its own columns (no unsafe), run in parallel. Every column keeps
  its own f32 running sum, started from its first window summed in order and updated in
  the same order, so only the loop nest changes.
- `harris`: Sobel, the corner response and the non-maximum scan are parallel by rows (the
  candidates are fully ordered by score, y, x afterwards, so arrival order is irrelevant).
  `describe`: one descriptor per corner in parallel, kept in corner order.
- `reduce`, `expand`, `push_pull`: per-row maps in parallel. `reduce`'s column pass adds
  five whole rows in tap order (same per-element sum, now vectorised); `expand` works out
  its source columns once and truncates instead of calling libm `floor` (the clamped
  position is never negative); `push_pull` builds each coarse row from its two fine rows in
  raster order, which is the order the serial scatter-add used.
- `multiband`: grid fill per image, Laplacian difference, band accumulation (every row
  walks the bands in the original order), normalisation, collapse and the coverage union
  are parallel. Float additions are never reordered.

Left serial on purpose: the peak fold in `harris` (about 1 ms, an order-free max but not
worth the proof), the corner spacing grid, `seam_labels` (a dynamic-programming path whose
rows depend on the previous one), `photometric` sample gathering, and the pyramid levels
themselves (each level needs the one above).

Median of 5 (8 MP rows: 7), release, ms, interleaved before/after runs. The laptop was
busy with unrelated builds the whole time (load average 11 to 18 on 16 threads), so the
1-thread rows are noisy (a single thread is time-sliced against them) and rows can move
by 10 to 20 percent.

| Case (probe)                   | before (16 thr) | after (16 thr) | speedup | before (1 thr) | after (1 thr) |
|--------------------------------|----------------:|---------------:|--------:|---------------:|--------------:|
| register, 4 × 2 MP             |             154 |             60 |    2.6× |            336 |           167 |
| multiband, 4 × 2 MP, 3 ch      |             407 |            108 |    3.8× |            591 |           407 |
| multiband, 4 × 2 MP, 1 ch      |             307 |             73 |    4.2× |            813 |           566 |
| whole pipeline, 4 × 2 MP       |             437 |            160 |    2.7× |            716 |           504 |
| register, 3 × 8 MP (full size) |             327 |            124 |    2.6× |            978 |           391 |
| multiband, 3 × 8 MP, 3 ch      |            2348 |            916 |    2.6× |           1742 |          1150 |
| whole pipeline, 3 × 8 MP       |            1042 |            264 |    3.9× |           2264 |          1280 |

The 8 MP multiband row at 16 threads is the noisiest (minimum of 7: 959 to 224 ms, 4.3×);
the whole-pipeline row is steady. Max RSS for the 3 × 8 MP whole pipeline (the probe's
inputs are about 1.05 GB of it): 2.79 GB before, 2.79 GB after (three runs each).

Tests: the serial `blur`, `reduce`, `expand`, `push_pull` and `multiband` are kept in the
test modules as references and compared bit for bit over sizes that cross every band and
row boundary (1×1, 1-wide, 1-high, odd sizes, 63 / 64 / 65 / 129 columns, radius 0 to
wider than the image, holes, partial and zero weights, ROIs outside the canvas, 1 to 7
levels). Hard-coded hashes recorded from the old code cover multiband, push_pull,
reduce + expand everywhere (plain f32 arithmetic), and harris + describe on Linux x86-64
(they go through the platform's hypot / ln / cos / atan2 / sin_cos).


Claude-Session: https://claude.ai/code/session_01AfRHB1wHUjrJx6agBYGnVu

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:20:22 -06:00
..
affinity chore: fix workspace Clippy collapsible_match warnings (#5190) 2026-10-16 17:42:36 -07:00
algo perf(panorama): Photomerge 4× faster, bit for bit the same (#6489) 2026-10-16 20:20:22 -06:00
automation fix(automation): refuse a group/world-readable control-token file (#4966) (#5459) 2026-10-16 20:20:03 -06:00
cms docs: write product name as PhotoCraft in crate module docs (#5455) 2026-10-16 16:58:50 -06:00
codecs Targa: RLE packets end with their scanline (#6382) (#6386) 2026-10-16 20:15:32 -06:00
color fix(blend): keep 32-bit Hue/Saturation/Color/Luminosity above 1 (#1735) 2026-10-10 13:15:18 -04:00
compose fix(compose): serialize effect identity cache misses 2026-10-15 08:18:21 -06:00
doc docs: write product name as PhotoCraft in crate module docs (#5455) 2026-10-16 16:58:50 -06:00
engine Linked smart objects: don't refresh a document while a background job runs on it (#4973) (#5074) 2026-10-16 20:18:17 -06:00
format Dependencies: require ruzstd 0.9.1 for RUSTSEC-2026-0344 (#4952) (#5225) 2026-10-15 13:37:56 -06:00
geom docs: write product name as PhotoCraft in crate module docs (#5455) 2026-10-16 16:58:50 -06:00
gpu GPU composite: lower-stack cache chunks have no texture view (#5423) 2026-10-16 19:19:53 -06:00
heif fix(heif): a grid's ICC profile is inherited from its tiles (#5553) 2026-10-14 15:04:55 -07:00
io fix(io): SVG text placed by a transform keeps it on import (#4806) (#5334) 2026-10-16 19:19:47 -06:00
ops perf: keep effect-cache pins and History snapshots inside the memory budgets (#5336) 2026-10-15 00:44:55 -04:00
paint docs: write product name as PhotoCraft in crate module docs (#5455) 2026-10-16 16:58:50 -06:00
plugins chore: fix workspace Clippy collapsible_match warnings (#5190) 2026-10-16 17:42:36 -07:00
psd fix(psd): recover layered imports with damaged merged data (#6170) 2026-10-15 19:14:05 -06:00
raster raster: a shifted surface copies in one pass per row without pre-filled tiles (#2667) 2026-10-14 13:28:48 -04:00
raw chore: fix workspace Clippy collapsible_match warnings (#5190) 2026-10-16 17:42:36 -07:00
tablet Tests: monitor_macos answers nextest's --list 2026-10-14 19:22:14 +03:00
testkit perf(testkit): sample RSS from /proc/self/statm, not smaps, on Linux (#5399) 2026-10-16 17:00:43 -06:00
text PSD type: clamp warp bend and distortion before the f32 cast (#6856) (#6857) 2026-10-16 20:17:27 -06:00
ui-egui Save: ask before overwriting a file another app changed (#6596) (#6652) 2026-10-16 20:19:32 -06:00
vector Vector: bound flattening memory, keep over-budget dashes, cheaper edge build (#5195) (#5204) 2026-10-14 15:02:09 -07:00