Commit Graph
35 Commits
Author SHA1 Message Date
aiongg e2f9ae6bfb Merge main into perf/9328-slow-frames
Conflicts in perf/budgets.toml and docs/scorecard.md: both sides added perf rows (P80 here, P81 and P82 from #9942). Kept all three; scorecard regenerated with cargo xtask scorecard.
2026-10-28 20:30:50 +00:00
Brennan StehlingandClaude Opus 5.5 a9c26551f1 perf: add P81/P82 redraw-area rows on full-canvas layers (#9329)
Track-only rows that check they hit the whole-document-redraw path and
print the refreshed pixel count: undo of a brush dab on a 16-bit
15000x10000 full-canvas layer (reps capped at 5) and a fill of a 2 %
selection on a 24 MP full-canvas layer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-27 23:08:30 -07:00
Brennan Stehling 00522c852e Perf: P80, app frame time for a full CPU refresh of a 24 MP document (#9328)
Track-only row, the slow-frame recorder's worst case. A 24 MP photo plus
three full-canvas blended copies (Multiply, Screen, Overlay); each sample
toggles the top copy and times one harness.step(), so every frame
recomposites all 24 MP on the CPU (the photo's tiles are never uniform,
which keeps the reduced render's one-colour shortcut out of the way).
The row errors unless each frame recorded a CPU refresh covering at least
95 % of the canvas, so it can't silently take a fast path. 10 samples.
2026-10-27 23:06:56 -07:00
aiongg a9e6f73ac5 Merge remote-tracking branch 'upstream/main' into HEAD 2026-10-28 02:31:52 +00:00
Brennan StehlingandClaude Opus 5.5 285944de8d Perf: P79's row name leaves out the document size, which --quick changes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-27 14:38:45 -07:00
Brennan StehlingandClaude Opus 5.5 e43762ac0d Perf: rename P79 to the reduced composite it times (#7494)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-27 14:38:45 -07:00
Brennan Stehling 6beffc0fb9 Perf: P78 forces the GPU budget to 512 MB, so hiding the top adjustment layer takes the CPU fallback (#7494) 2026-10-27 14:38:45 -07:00
Brennan StehlingandClaude Opus 5.5 8cecf418e5 Perf: cliff rows for the CPU compositor, Advanced Blending, adjustment toggles and a huge document (#7494)
Reported only (enforce = false), so the off-happy-path cliffs have a "before":
- perf_scenarios_cpu: the layered rows on the forced CPU compositor (P68-P74, #9330)
- P75 opacity with Advanced Blending on another layer (#6689), P76 adjustment toggle (#6535)
- P77 hiding an adjustment among many full-canvas layers (#6533), added after P60/P61 run
- P78 zoomed-out reduced refresh of a 30000x30000 painted document (#1015, #9330)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-27 14:38:45 -07:00
Brennan Stehling 43581abc5f perf budgets: judge P25 on p95, keep P16 unenforced (#7494)
P25 broke at 299 ms once on the contended runner (2026-10-18). It now uses
the p95 metric with the same 200 ms budget; with n <= 20 the p95 rank equals
the max, so on the runner (n = 8) this has no effect until reps >= 21.
P16 stays unenforced: the new --from enforce report over the 2026-10-18..25
nightlies lists it as flaky (p95 103.7 ms on 2026-10-18).
2026-10-25 16:04:30 -07:00
Brennan Stehling 9335daaf41 xtask perf: a baseline knows how trustworthy it is (#7494)
--update-baseline refuses a contended run (1-minute load at the start above
settings.max_load_per_core per core) unless --allow-contended, which labels it.
--update-baseline --from builds the baseline from several saved results.json
files: the median p50 per scenario and the highest as its noise ceiling, which
regressions are now judged against. It also reports which budgets could be
enforced and which enforced ones are flaky. The single-run path is the N = 1
case of the same function.
2026-10-25 16:04:30 -07:00
David Mann 8fe70c95d2 Merge PR #5236: perf(algo): Content-Aware Scale 2x faster, within its P28 budget (#211)
# Conflicts:
#	crates/algo/examples/bench_seam.rs
#	crates/algo/src/seam.rs
#	docs/gaps.md
#	docs/target-app-parity.md
#	perf/budgets.toml
2026-10-25 14:28:59 +01:00
David Mann e6cf04cf42 Merge PR #7873: perf(photomerge): track multiband timing and peak memory
# Conflicts:
#	docs/scorecard.md
#	perf/budgets.toml
2026-10-25 10:15:10 +01:00
ebosido444-aiandebosido444-ai b07b7fab55 perf(seam): the shrink proxy drops its original-column map, same pixels (−15 % at 24 MP) (#8526)
* perf(seam): drop the original-column map from the shrink proxy, same pixels (-15 % at 24 MP)

Content-Aware Scale's proxy seam search tracked each panel column's original
column in an idx map, one usize per proxy pixel, shifted by every seam. The
original column is x plus the number of columns already taken at or left of it,
so a per-row list of the taken columns answers the same question without a
buffer the size of the energy map.

The recorded P28 budget number predates the proxies: the 2026-10-05 baseline
commit carves at full height and recomputes energy per seam, and measures
266 s for the 24 MP case on the machine this was timed on, against 2.6 s now.
Documented in docs/seam-carving-performance.md; enforce stays off until a
budgeted-class run can vouch for the whole row.

* review: generate exactly ch samples per pixel in bench_seam, reject ch > 4

The fixture flattened four samples per pixel and then truncated to w*h*ch, so a
three-channel run grouped samples across pixel boundaries and any ch above four
built a buffer shorter than the rows carve_width indexes.

Generate ch samples inside the per-pixel closure and refuse ch outside 1..=4.
The three-channel row is dropped from the perf note rather than re-measured
here: it moved inside the timer noise either way, and the other agents' builds
on this machine make a fresh number worthless. The four-channel hashes are
unchanged, which is the check that this edit touched only the fixture.

* perf(seam): find the removed column by binary search, not a linear scan

Recording a seam recovered each removed original column by scanning the row's
list of already-taken columns, so a deep shrink paid a comparison per removed
column on every row and every seam: quadratic in the seam count, and worse than
the index map this PR replaced once a shrink got deep enough.

`o - i` never decreases along the row's sorted list, so that count is a binary
search, and the original column belongs where the search stopped; the rows are
independent and update in parallel, like the pixel shift. What is left per
removed column is one insert.

Output is unchanged: same columns, same order, same hashes (10 % and 50 % width
cases below), and `counted_removed_columns_match_the_tracked_map` now runs a
tracking and a counting carver side by side to one column and compares every
row's list after every seam.

docs/seam-carving-performance.md gains those cases, states that the ~104x
figure is the two example runs rather than the baseline row, and names
perf/baseline.json as the source of the 954 s.

---------

Co-authored-by: ebosido444-ai <ebosido444-ai@users.noreply.github.com>
2026-10-24 11:14:00 -04:00
David Mannersoynarin-codecoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>Claude Opus 5.5
6b867deba2 Integration: renumber the Photomerge scenario, measure its peak on every OS (#7873)
- P62 is taken on main (File > New, #4017), and P63-P67 by aiongg/next
  and #8529: with main merged, `cargo xtask scorecard --check` failed
  with "duplicate scenario id P62". The scenario is now P68, listed
  after the others.
- Peak RSS came from /proc on Linux and a powershell.exe call on
  Windows, and was always empty on macOS, where the nightly perf runs.
  The bench now uses photocraft_testkit::perf (RssSampler, row, report),
  like the other --json benches.
- `--json --reps 5` took `--reps` as the output path (CodeRabbit); a
  following option is now rejected.
- The printed median was the upper middle sample for an even --reps
  (CodeRabbit); it now prints the same nearest-rank p50 xtask reports.
- The old `[n] [mp]` usage accepted 36 MP again (AGENTS.md benchmarks
  at 24-36 MP); the PR had capped it at 24.

Tests: the new cases `--json --reps 5` (rejects_bad_benchmark_args)
and `4 36` (parses_old_positional_args_and_perf_runner_args) fail with
the fix reverted; `2 37` pins the new upper bound.

Co-authored-by: ersoynarin-code <340219279+ersoynarin-code@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:03:55 +02:00
David MannandClaude Opus 5.5 af802da608 Integration: merge main, resolve conflicts (#7873)
docs/scorecard.md conflicted: main's version is kept here and
regenerated in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:00:26 +02:00
ebosido444-ai 1601148b31 perf(bench): run bench_filters2 from cargo xtask perf, with four #211 rows (P64-P67)
bench_filters2 timings for the heavy algo filters were invisible to the perf
harness: the example was never listed in perf/budgets.toml, so no budget, no
baseline and no regression check covered Oil Paint, Relight, Reduce Noise,
Smart Blur, the blur gallery, Crystallize or Color Halftone.

The example now takes --reps, --quick and --json (the report shape the harness
reads, hand-written so the crate does not grow a serde dependency), and four
scenarios track the worst rows at a 1 s target: Oil Paint 8.8 s, Smart Blur
r12 11.6 s, Relight 3.7 s, Reduce Noise 2.8 s on a 24 MP layer.
2026-10-22 14:59:13 +03:00
David MannandClaude Opus 5.5 52640de2b5 Address CodeRabbit review: state each large-file test's memory need
large_file_bench needs about 30 GB (its PSB, as its doc comment says), not 16 GB; the opt-in
tests need about 12 GB (2 GB PSD) and 25 GB (4.4 GB PSB), as their #[ignore] reasons say.
Regenerated docs/scorecard.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:22:16 +02:00
David MannandClaude Opus 5.5 c69e6efe07 Merge main into feat/file-216-2-large-fixtures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:19:59 +02:00
ersoynarin-code 7bf4cc0739 perf(photomerge): track multiband timing and peak memory 2026-10-20 01:54:27 +03:00
David Mann 9d38b28da6 Merge remote-tracking branch 'origin/main' into feat/file-216-2-large-fixtures
# Conflicts:
#	docs/scorecard.md
2026-10-17 01:19:35 +02:00
David Mann 0739ff48c5 Merge remote-tracking branch 'origin/main' into fix/4017-huge-new-document
# Conflicts:
#	crates/ui-egui/src/gpu_canvas.rs
#	docs/scorecard.md
#	perf/budgets.toml
2026-10-17 01:07:37 +02:00
Tanish Manku 6d3443c098 GPU memory: discrete cards aren't charged for the document; GPU Memory preference; GPU Memory Full status (#5973)
* GPU memory: a graphics card's own memory isn't charged for the document; GPU Memory preference; GPU Memory Full in the status bar

Many layers hit a cliff: past what the GPU memory budget leaves for layer pages, every edit
that redraws the whole document (opacity, visibility, a nudge) goes to the CPU compositor.
Measured on 3000x2000 full-canvas layers (RTX 5090, 31 GB RAM, Memory Usage 8 GB): 150
layers took 2.6 s per such edit, 300 layers 6 s. The budget was Memory Usage minus the
document's pixels and History, which is right where the GPU shares system memory, but a
discrete card's memory doesn't hold the document, so 150 layers of 6 MP (3.6 GB) squeezed
the budget below what they needed.

- Automatic budget: discrete adapters are no longer charged for the document's pixels
  (integrated and software adapters still are). 150 layers: 2.6 s -> 25 ms.
- Preferences › Performance › GPU Memory (MB, 0 = Automatic; performance.gpuMemoryMb) sets
  the budget outright. 300 layers with 12 GB: 6 s -> 45-72 ms.
- The status bar shows "GPU Memory Full" while the active document's layers don't fit
  (both status bar styles); hovering gives the numbers, clicking opens Preferences ›
  Performance. Its Graphics rows show the budget and what the layers need. The slow CPU
  redraw notice gives the numbers and points at GPU Memory.
- Headless: the preference is a normal pref (prefs.set), and ui.state's perf.timings
  reports gpuLayerFit {doc, needed, budget} next to gpuBudgetMb.

Next (owner-approved plan): after #5370, read the card's real limit from the driver
(wgpu memory budget thresholds) and shrink on out-of-memory; then redraw the visible
area on the GPU first when layers still don't fit.

* GPU memory: typed discrete check; the status and budget follow a GPU Memory change at once (measure_fit); perf rows P60/P61 for whole-document edits among 150 full-canvas layers under the app's budget

* GPU memory on top of #5234: memory_budget takes the card's memory (vram) and the GPU Memory value; the document's pixels are charged unless the card's own memory is known; a set GPU Memory is capped at the card's share; benches use video_memory
2026-10-15 21:22:46 -07:00
Claude e4dd35ce23 docs: Content-Aware Scale (P28) measured within its budget
docs/content-aware-scale-performance.md records what changed, the
exactness contract (bit-identical wherever the energy stays a number;
NaN and infinities now deterministic), and interleaved before/after
timings on a 4-core VM: the carve of P28's 24 MP photo 2013 -> 556 ms,
the P28 row 3281 -> 1639 ms against its 3 s budget, smaller images
1.65-2.3x faster, peak memory unchanged or lower.

gaps.md G2 and target-app-parity.md get the dated measurement;
budgets.toml's P28 comment says the target is met. Its enforce flag and
the committed baseline stay as they are until the reference machine
reruns cargo xtask perf --update-baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LELc5BQzgV5dA7dxdMejkK
2026-10-13 19:00:14 +00:00
David MannandClaude Opus 5.5 ebd3c12270 Large documents: a gigapixel New Document draws without freezing (#4017)
The reduced CPU canvas composites one-colour tiles once per colour instead
of every document pixel, and GPU canvas textures the device refuses fall
back to the CPU canvas instead of disabling the GPU for the session.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:21:45 +02:00
David MannandClaude Opus 5.5 8eef90bf7e Tests: generated large-file fixtures and a P20 number (#5107)
photocraft_psd::fixtures (feature testgen) generates the FILE-216-2
fixtures at test time: a 20 MP layered PSD and a 150-layer A4 layout
(groups, masks, hidden layers, drop shadows, known merged colours), a
2.02 GB PSD just under Photoshop's cap and the 4-5 GB PSBs, streamed row
by row. io tests/large_files.rs opens, checks, saves and re-opens each
(the 2 GB PSD and 4.38 GB PSB are opt-in). The ui-egui large_file_bench
times opening the big two, wired to P20 and a new P60.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 17:58:38 +02:00
Valentin KleinandClaude Opus 5.5 9b3c6359ae perf: Select > Modify > Feather/Smooth on 24 MP within budget (#2873) (#2882)
The blur itself was already parallel (9-15 ms on 24 MP); most of the
command was serial glue around it. Modify copied the mask into a padded
buffer and back out sample by sample even with no padding, and combine
built a full-canvas byte buffer, scanned it, then copied it into tiles
on one thread.

- modify: use the mask as is when the selection is clear of the canvas
  edge (pad 0); pad and crop row by row otherwise.
- combine / mask_to_surface: fill selection tiles in parallel straight
  from the mask, a row at a time; fully selected tiles share one
  copy-on-write tile.

Bit-identical: the per-sample oracles (combine, now with whole tiles,
and the old pad/crop) are compared in tests.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:48:51 -04:00
Valentin KleinandClaude Opus 5.5 a030d19437 perf: repeated Select Subject answers from the last mask (#2865) (#2870)
Select Subject recomputed superpixels, saliency, GrabCut and the
full-resolution re-cut on every call (P29: 260-370 ms on 24 MP here,
693 ms p95 in the baseline). The session now keeps the last result,
keyed by the pixels it came from: `same_pixels` (tile pointers) against
a weakly held document, plus the sampled target (composite or layer).
Selection-only edits such as Deselect keep it valid; the mode
(replace/add/subtract/intersect) is still applied each time. One entry
per session, dropped on pixel edits and when its document closes.

Repeat: 7-10 ms p95 (was 270-370 ms). First run unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:44:00 -04:00
094d0776d1 Improve zoomed Liquify preview detail (#2715)
* Improve zoomed Liquify preview detail

* Record Liquify quick benchmark results

* Keep the roadmap as on main; record the Liquify numbers in target-app-parity.md

The branch carried an old copy of the roadmap's parity tables, which have moved
to docs/target-app-parity.md on main.

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 05:03:10 -04:00
Tanish Manku c6a525cc99 Releasing a brush stroke no longer freezes: the commit takes the pixels the live stroke already drew (P32: 136 ms → 0.8 ms) (#2131)
* Releasing a brush stroke no longer freezes: the commit takes the pixels the live stroke already drew (P32: 136 ms → 0.8 ms)

Brush phase 4. On release the canvas dropped its live stroke and ran `paint.stroke` over every
point again (the whole stroke re-rendered), then re-composited the stroke's bounding box, although
the canvas already showed exactly those pixels. Long strokes froze for 100–250 ms at mouse-up
(#2068).

- `Session::offer_live_stroke` hands the finished live stroke to the next command. A
  `paint.stroke` / `paint.pencil` whose command, document revision, params, seed and points match
  it exactly takes its pixels instead of rendering (result `"fromLive": true`). Anything else
  ignores the offer and renders as before. It still runs through the normal dispatch
  (authorization, journal, one history step), and a replay renders identical pixels.
- The canvas feeds the release frame's last points to the live stroke first, then remembers the
  stroke's per-step damage for one revision (`CommittedStroke`): each canvas cache re-composites
  only the steps it hadn't shown, usually nothing, instead of the bounding box.
- P32 (brush release on a long stroke) becomes measurable: perf_scenarios times a 600-point
  200 px soft stroke on 24 MP. 136 ms → 0.83 ms on the GPU canvas, 257 ms → 0.71 ms on the CPU
  canvas (RTX 5090, release). Budget 16 ms, not enforced until a baseline run.
- Scorecard: TOOL-213-10 (⌃⌥-drag resize with readout) is done; TOOL-213-7 (live strokes for every
  brush-based retouch tool) is partial (Spot Healing, Mixer Brush, Background Eraser and Pattern
  Stamp still aren't live).

* Mask view test: a commit that takes the live stroke uploads nothing it already showed
2026-10-11 02:42:12 -04:00
Sir StarchandBrandon Thomas 819bd47bf1 perf: bevel_bench, the cost of changing a chisel bevel's size (#2006 follow-up) (#2118)
#2006 made a bevel size change dearer on purpose (+10-75% on the GPU canvas,
2x for CPU text bevels) and asked for a bench of it under perf/.

`bevel_bench` renders a 12 MP document with one Chisel Hard inner bevel on
an anti-aliased ellipse (a smooth outline) or on a text paragraph (many thin
edges). Each case warms the caches one pixel below the size, then times
refreshes with the size growing by one pixel each, so the GPU canvas
rebuilds its distance fields every time (it keeps them while the size
shrinks) and the CPU compositor misses its map cache. Sizes 8, 50 and 250,
GPU and CPU, with `--scene`, `--size`, `--reps`, `--no-cpu` and `--json`.

Registered in perf/budgets.toml as P52-P59 with no budgets (track only,
like P45 and P46): GPU for ellipse and text at 8, 50 and 250, CPU at 8.
docs/scorecard.md is regenerated; the rows read "not run" until the next
baseline.

RTX 4080 SUPER, release, 9 reps, p50 ms, before #2006 (3bc49f5e) -> after:
ellipse GPU 88 -> 105 (8), 169 -> 199 (50), 305 -> 460 (250); text GPU
119 -> 140, 141 -> 252, 238 -> 557; CPU text at 8 402 -> 797. Runs vary by
about 20% on this machine.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 19:39:45 -04:00
David MannandClaude Opus 5.5 d7078f3623 Layers: a new empty layer no longer recomposites every layer (#1846)
step_damage pairs layers by id and steps over a layer only one side has when it draws nothing
(an empty pixel layer or group in Normal/Pass Through, no masks, effects or Advanced Blending,
not a clipping base), so New Layer, New Group and their undo/redo report no damage. Adds the
P51 perf scenario (new layer in the 150-layer document). Fixes #1771.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:59:12 -04:00
cc5de414dc Camera Raw: interactive histogram, clipping warnings and scopes (part of #215) (#407)
* Camera Raw: interactive histogram, clipping warnings and scopes (part of #215)

- RGB histogram of the preview proxy (algo::histogram), drawn as RGB ribbons;
  Before/After, five draggable tonal zones, shadow/highlight clipping warnings
  (U/O) and Alt-drag channel diagnostics.
- Pixel hover readout, up to nine RGB/Lab colour samplers (S), an ICC-managed
  vectorscope (algo::vectorscope) with skin-tone line, and a floating scope panel.
- Settings regrouped as Light, Color, Effects, Curve, Color Mixer, Color Grading
  and Detail; Camera Raw labels translated through the cameraRaw context.
- One point-curve editor (point_curve.rs) shared by Camera Raw, the Curves dialog
  and adjustment-layer Properties; fixes Camera Raw curve points not dragging.
- Double-clicking a Camera Raw smart filter reopens the Camera Raw dialog with its
  settings and updates that filter, instead of the generic parameter form.
- New point curves are validated (2-16 points, increasing inputs); stored Smart
  Filters keep loading.
- camera_raw_bench for P31/P36 (not enforced until a baseline includes them).

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

* Histogram tests: as_chunks for triangle indices (clippy 1.99)

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

* Regenerate docs/scorecard.md after rebasing onto main

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

* i18n tests: close the Mixer Brush test before the Camera Raw one (rebase)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 07:40:55 -04:00
Brandon ThomasandClaude Opus 5.5 909efc0f6d Background jobs in the app: progress, Cancel, open-as-job (part of #210) (#373)
Re-lands #290, which was merged into its stacked base branch (jobs/engine-210) instead of main.
Same tree as the gated #290 commit 39152db.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:44:40 -04:00
Brandon Thomas 3423864502 Fast Select › Modify › Smooth and Feather (part of #211) (#280) (#280) 2026-10-05 19:59:11 -04:00
Brandon Thomas 0f955e6c82 Scorecard: cargo xtask perf + scorecard, budgets, checklists, nightly perf (#221) (#239) (#239) 2026-10-05 10:53:02 -04:00