mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-28 10:11:58 +01:00
#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>
515 lines
14 KiB
TOML
515 lines
14 KiB
TOML
# Performance budgets for `cargo xtask perf` and the scorecard (#221).
|
||
#
|
||
# Targets come from #209 (interactive edits), #210 (long operations) and #211 (slow algorithms).
|
||
# Each [[scenario]] is measured by one row of one bench (`bench` + `row`, the row's exact name
|
||
# in the bench's `--json` report), judged on `metric` (p50 | p95 | max; default p95) against
|
||
# `budget_ms`. `enforce = false` marks a target the code doesn't meet yet: it is reported as
|
||
# "over budget" but only a regression fails the run; flip it to true in the PR that meets it.
|
||
# Scenarios the code can't support yet carry `not_measurable` (why) instead of a bench: never
|
||
# invent a number for them.
|
||
#
|
||
# Benches run in release with `--json <file>` appended. `quick_args` = also runs in
|
||
# `cargo xtask perf --quick` (small synthetic documents; budgets don't apply there).
|
||
|
||
[settings]
|
||
# A scenario regresses when its p50 grows by more than this share of the baseline…
|
||
regression_pct = 15.0
|
||
# …and by more than this many milliseconds (timer noise on very fast rows).
|
||
regression_min_ms = 1.0
|
||
|
||
[[bench]]
|
||
name = "perf_scenarios"
|
||
package = "photocraft-ui-egui"
|
||
quick_args = ["--quick"]
|
||
note = "the #209/#210/#211 rows: layered 20 MP, 150 layers, 16/32-bit, open, retouch, selection"
|
||
|
||
[[bench]]
|
||
name = "interactive_bench"
|
||
package = "photocraft-ui-egui"
|
||
args = ["--reps", "5"]
|
||
note = "36 MP photo: dabs, filters, adjustment previews, saves"
|
||
|
||
[[bench]]
|
||
name = "fx_bench"
|
||
package = "photocraft-ui-egui"
|
||
args = ["--no-cpu", "--reps", "5"]
|
||
note = "36 MP document with layer effects, GPU compositor"
|
||
|
||
[[bench]]
|
||
name = "bevel_bench"
|
||
package = "photocraft-ui-egui"
|
||
args = ["--reps", "9"]
|
||
quick_args = ["--size", "1200x800", "--reps", "3"]
|
||
note = "chisel bevel size change (a dragged slider) on a 12 MP ellipse and a text paragraph, GPU canvas and CPU"
|
||
|
||
[[bench]]
|
||
name = "camera_raw_bench"
|
||
package = "photocraft-ui-egui"
|
||
quick_args = ["--quick"]
|
||
note = "36 MP dense RGB layer: CPU develop, histogram, ICC vectorscope, clipping, nine Lab probes and egui tessellation; excludes GPU upload/presentation"
|
||
|
||
[[bench]]
|
||
name = "type_bench"
|
||
package = "photocraft-ui-egui"
|
||
quick_args = ["--size", "2000x1500", "--layers", "20", "--steps", "15"]
|
||
note = "font-size drag on 50 type layers"
|
||
|
||
[[bench]]
|
||
name = "large_image_bench"
|
||
package = "photocraft-ui-egui"
|
||
args = ["--size", "14000x14000", "--op", "refresh"]
|
||
note = "196 MP: open and full refresh"
|
||
|
||
# Hook: the layout-document bench lands with perf/layout-documents (#125/#128). It runs (and its
|
||
# rows are kept in results.json) as soon as the example exists; add scenarios for its rows then.
|
||
[[bench]]
|
||
name = "layout_bench"
|
||
package = "photocraft-ui-egui"
|
||
requires = "crates/ui-egui/examples/layout_bench.rs"
|
||
quick_args = []
|
||
note = "layout PSDs with many styled layers (#125/#128)"
|
||
|
||
# ---- #209: interactive edits on layered documents (20 MP, styled layers) ---------------------
|
||
|
||
[[scenario]]
|
||
id = "P1"
|
||
title = "Move a layer with layer styles (per drag update)"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "move styled layer 10 px + refresh"
|
||
budget_ms = 25
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P2"
|
||
title = "Change layer opacity"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "set layer opacity + refresh"
|
||
budget_ms = 20
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P3"
|
||
title = "Change layer fill"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "set layer fill + refresh"
|
||
budget_ms = 20
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P4"
|
||
title = "Change blend mode"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "set blend mode + refresh"
|
||
budget_ms = 20
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P5"
|
||
title = "Toggle visibility"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "toggle visibility + refresh"
|
||
budget_ms = 10
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P6"
|
||
title = "Undo a move"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "undo move + refresh"
|
||
budget_ms = 25
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P7"
|
||
title = "Redo a property change"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "redo opacity change + refresh"
|
||
budget_ms = 25
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P8"
|
||
title = "Duplicate a 12 MP layer (first)"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "duplicate 12 MP layer (first) + refresh"
|
||
budget_ms = 100
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P9"
|
||
title = "Duplicate a 12 MP layer (repeat)"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "duplicate 12 MP layer (repeat) + refresh"
|
||
budget_ms = 20
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P10"
|
||
title = "Paste a 12 MP layer (first)"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "paste 12 MP layer (first) + refresh"
|
||
budget_ms = 100
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P11"
|
||
title = "Paste a 12 MP layer (repeat)"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "paste 12 MP layer (repeat) + refresh"
|
||
budget_ms = 20
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P12"
|
||
title = "Arrow-key nudge in a 150-layer document"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "nudge 1 px in the many-layer document + refresh"
|
||
budget_ms = 30
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P51"
|
||
title = "New layer in a 150-layer document"
|
||
issue = 1771
|
||
bench = "perf_scenarios"
|
||
row = "new layer in the many-layer document + refresh"
|
||
budget_ms = 16
|
||
enforce = false # not in the baseline yet
|
||
|
||
[[scenario]]
|
||
id = "P13"
|
||
title = "Brush dab on a 15000×10000 16-bit document"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "brush dab 60 px on 16-bit 15000x10000 + refresh"
|
||
budget_ms = 80
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P14"
|
||
title = "Move a layer on a 15000×10000 16-bit document"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "move layer on 16-bit 15000x10000 + refresh"
|
||
budget_ms = 80
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P15"
|
||
title = "Zoom / pan frame time on a 15000×10000 16-bit document"
|
||
issue = 209
|
||
target = "≤ 16 ms per frame"
|
||
not_measurable = "no headless canvas frame harness yet (zoom and pan sample the GPU texture in the egui paint callback)"
|
||
|
||
[[scenario]]
|
||
id = "P16"
|
||
title = "Brush dab on a 32-bit document with 20 layers"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "brush dab 60 px on 32-bit 20 layers + refresh"
|
||
budget_ms = 100
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P17"
|
||
title = "Move a layer on a 32-bit document with 20 layers"
|
||
issue = 209
|
||
bench = "perf_scenarios"
|
||
row = "move layer on 32-bit 20 layers + refresh"
|
||
budget_ms = 100
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
# ---- #210: long operations ---------------------------------------------------------------------
|
||
|
||
[[scenario]]
|
||
id = "P18"
|
||
title = "Open a 20 MP layered PSD: first pixels (today: fully opened)"
|
||
issue = 210
|
||
bench = "perf_scenarios"
|
||
row = "open 20 MP layered PSD (decode + first composite)"
|
||
budget_ms = 1000
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P19"
|
||
title = "Open a 20 MP layered PSD: editable"
|
||
issue = 210
|
||
bench = "perf_scenarios"
|
||
row = "open 20 MP layered PSD (decode + first composite)"
|
||
budget_ms = 3000
|
||
|
||
[[scenario]]
|
||
id = "P20"
|
||
title = "Open a 2 GB PSD / 4–5 GB PSB"
|
||
issue = 210
|
||
target = "opens with progress, editable, never blank"
|
||
not_measurable = "opens run in the background with progress (#210), but there are no generated large-file fixtures yet (#216)"
|
||
|
||
[[scenario]]
|
||
id = "P21"
|
||
title = "Content-Aware Fill on 10 MP"
|
||
issue = 210
|
||
bench = "perf_scenarios"
|
||
row = "Content-Aware Fill 10 MP (200 px hole)"
|
||
budget_ms = 1500
|
||
|
||
[[scenario]]
|
||
id = "P22"
|
||
title = "Spot Healing stroke on 10 MP"
|
||
issue = 210
|
||
bench = "perf_scenarios"
|
||
row = "Spot Healing stroke 10 MP"
|
||
budget_ms = 500
|
||
|
||
[[scenario]]
|
||
id = "P23"
|
||
title = "Warp / Puppet / Perspective preview at 4K"
|
||
issue = 210
|
||
target = "≤ 16 ms per frame, commit in the background"
|
||
not_measurable = "the previews run inside the UI tools; no headless frame harness, no background commit"
|
||
|
||
[[scenario]]
|
||
id = "P24"
|
||
title = "Import 500 brushes (longest UI-thread step)"
|
||
issue = 210
|
||
bench = "perf_scenarios"
|
||
row = "import 500 brushes: longest UI-thread step"
|
||
metric = "max"
|
||
budget_ms = 100
|
||
|
||
[[scenario]]
|
||
id = "P25"
|
||
title = "Cancel a Gaussian Blur job on 24 MP (until the worker exits)"
|
||
issue = 210
|
||
bench = "perf_scenarios"
|
||
row = "cancel Gaussian Blur r 50 job on 24 MP"
|
||
metric = "max"
|
||
budget_ms = 200
|
||
|
||
[[scenario]]
|
||
id = "P49"
|
||
title = "Cancel a Content-Aware Fill job on 24 MP (until the worker exits)"
|
||
issue = 210
|
||
bench = "perf_scenarios"
|
||
row = "cancel Content-Aware Fill job on 24 MP"
|
||
metric = "max"
|
||
budget_ms = 200
|
||
|
||
[[scenario]]
|
||
id = "P50"
|
||
title = "App frame time while a 24 MP Gaussian Blur job runs"
|
||
issue = 210
|
||
bench = "perf_scenarios"
|
||
row = "app frame during 24 MP Gaussian Blur r 50 job"
|
||
budget_ms = 16
|
||
|
||
# ---- #211: slow algorithms ---------------------------------------------------------------------
|
||
|
||
[[scenario]]
|
||
id = "P26"
|
||
title = "Select › Modify › Smooth on 24 MP (radius 50)"
|
||
issue = 211
|
||
bench = "perf_scenarios"
|
||
row = "Select > Modify > Smooth r 50 on 24 MP"
|
||
budget_ms = 100
|
||
enforce = false # #211: fastest samples 30-33 ms; p95 still over budget at load avg > 100
|
||
|
||
[[scenario]]
|
||
id = "P27"
|
||
title = "Select › Modify › Feather on 24 MP (radius 50)"
|
||
issue = 211
|
||
bench = "perf_scenarios"
|
||
row = "Select > Modify > Feather r 50 on 24 MP"
|
||
budget_ms = 100
|
||
enforce = false # #211: fastest samples 30-33 ms; p95 still over budget at load avg > 100
|
||
|
||
[[scenario]]
|
||
id = "P28"
|
||
title = "Content-Aware Scale 24 MP (to 90 % width)"
|
||
issue = 211
|
||
bench = "perf_scenarios"
|
||
row = "Content-Aware Scale 24 MP to 90 % width"
|
||
budget_ms = 3000
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P29"
|
||
title = "Select Subject on 24 MP, repeated"
|
||
issue = 211
|
||
bench = "perf_scenarios"
|
||
row = "Select Subject 24 MP (repeat)"
|
||
budget_ms = 100
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P30"
|
||
title = "Liquify 800 px brush on 3500×2200 at full resolution"
|
||
issue = 211
|
||
target = "≤ 1 ms per update"
|
||
not_measurable = "the Liquify proxy is capped at 1600 px and the stroke path lives in the UI tool"
|
||
|
||
[[scenario]]
|
||
id = "P31"
|
||
title = "Camera Raw exposure change (proxy)"
|
||
issue = 211
|
||
target = "≤ 10 ms"
|
||
bench = "camera_raw_bench"
|
||
row = "exposure proxy update"
|
||
budget_ms = 10
|
||
enforce = false # not in the 2026-10-05 baseline yet; contributor M4 run 2026-10-06: p95 5.4 ms
|
||
|
||
[[scenario]]
|
||
id = "P32"
|
||
title = "Brush release on a long stroke"
|
||
issue = 211
|
||
target = "no hitch at mouse-up"
|
||
not_measurable = "the live renderer and the release re-render are UI-side; needs a frame harness"
|
||
|
||
[[scenario]]
|
||
id = "P33"
|
||
title = "Brush dab on A4 300 ppi CMYK (dab to screen)"
|
||
issue = 211
|
||
bench = "perf_scenarios"
|
||
row = "brush dab on A4 300 ppi CMYK + refresh"
|
||
budget_ms = 6
|
||
enforce = false # over budget in the 2026-10-05 baseline
|
||
|
||
[[scenario]]
|
||
id = "P34"
|
||
title = "Brush preview memory, small dab on an 8K viewport"
|
||
issue = 211
|
||
target = "≤ 2 MiB"
|
||
not_measurable = "the live brush preview buffers are UI-side; no headless counter yet"
|
||
|
||
[[scenario]]
|
||
id = "P35"
|
||
title = "Marquee drag on 24 MP with Alt/Shift"
|
||
issue = 211
|
||
target = "≤ 17 ms p95"
|
||
not_measurable = "Shift/Alt marquee modifiers don't exist yet (#208) and the drag preview is UI-side"
|
||
|
||
[[scenario]]
|
||
id = "P36"
|
||
title = "Camera Raw exposure change with all SDR scopes (CPU)"
|
||
issue = 211
|
||
bench = "camera_raw_bench"
|
||
row = "interactive scope update"
|
||
budget_ms = 10
|
||
enforce = false # not in the 2026-10-05 baseline yet; contributor M4 run 2026-10-06: p95 8.0 ms
|
||
|
||
# ---- tracked (no budget yet): regressions only -------------------------------------------------
|
||
|
||
[[scenario]]
|
||
id = "P40"
|
||
title = "Brush dab 60 px on 36 MP"
|
||
bench = "interactive_bench"
|
||
row = "brush dab 60 px (command + refresh)"
|
||
|
||
[[scenario]]
|
||
id = "P41"
|
||
title = "Gaussian Blur r 40 on 36 MP"
|
||
bench = "interactive_bench"
|
||
row = "Gaussian Blur r 40 (photo layer) + refresh"
|
||
|
||
[[scenario]]
|
||
id = "P42"
|
||
title = "Curves dialog change, layer preview, 36 MP"
|
||
bench = "interactive_bench"
|
||
row = "Curves dialog change, layer preview"
|
||
|
||
[[scenario]]
|
||
id = "P43"
|
||
title = "Save .psd, 36 MP, 3 layers"
|
||
bench = "interactive_bench"
|
||
row = "save .psd"
|
||
|
||
[[scenario]]
|
||
id = "P44"
|
||
title = "Open .psd, 36 MP, 3 layers"
|
||
bench = "interactive_bench"
|
||
row = "open .psd (3 layers) + refresh"
|
||
|
||
[[scenario]]
|
||
id = "P45"
|
||
title = "Full refresh with layer effects, warm caches, 36 MP (GPU)"
|
||
bench = "fx_bench"
|
||
row = "full refresh, warm caches (GPU)"
|
||
|
||
[[scenario]]
|
||
id = "P46"
|
||
title = "Brush dab on a layer with effects, 36 MP (GPU)"
|
||
bench = "fx_bench"
|
||
row = "dab on the effect layer (damage + reach) (GPU)"
|
||
|
||
[[scenario]]
|
||
id = "P47"
|
||
title = "Font-size drag step, 50 type layers"
|
||
bench = "type_bench"
|
||
row = "font size step: total"
|
||
|
||
[[scenario]]
|
||
id = "P48"
|
||
title = "Full refresh of a 14000×14000 document"
|
||
bench = "large_image_bench"
|
||
row = "full refresh"
|
||
|
||
[[scenario]]
|
||
id = "P52"
|
||
title = "Chisel bevel size change, 12 MP ellipse, size 8 (GPU)"
|
||
bench = "bevel_bench"
|
||
row = "bevel size change to 8, ellipse (GPU)"
|
||
|
||
[[scenario]]
|
||
id = "P53"
|
||
title = "Chisel bevel size change, 12 MP ellipse, size 50 (GPU)"
|
||
bench = "bevel_bench"
|
||
row = "bevel size change to 50, ellipse (GPU)"
|
||
|
||
[[scenario]]
|
||
id = "P54"
|
||
title = "Chisel bevel size change, 12 MP ellipse, size 250 (GPU)"
|
||
bench = "bevel_bench"
|
||
row = "bevel size change to 250, ellipse (GPU)"
|
||
|
||
[[scenario]]
|
||
id = "P55"
|
||
title = "Chisel bevel size change, 12 MP text paragraph, size 8 (GPU)"
|
||
bench = "bevel_bench"
|
||
row = "bevel size change to 8, text (GPU)"
|
||
|
||
[[scenario]]
|
||
id = "P56"
|
||
title = "Chisel bevel size change, 12 MP text paragraph, size 50 (GPU)"
|
||
bench = "bevel_bench"
|
||
row = "bevel size change to 50, text (GPU)"
|
||
|
||
[[scenario]]
|
||
id = "P57"
|
||
title = "Chisel bevel size change, 12 MP text paragraph, size 250 (GPU)"
|
||
bench = "bevel_bench"
|
||
row = "bevel size change to 250, text (GPU)"
|
||
|
||
[[scenario]]
|
||
id = "P58"
|
||
title = "Chisel bevel size change, 12 MP ellipse, size 8 (CPU)"
|
||
bench = "bevel_bench"
|
||
row = "bevel size change to 8, ellipse (CPU)"
|
||
|
||
[[scenario]]
|
||
id = "P59"
|
||
title = "Chisel bevel size change, 12 MP text paragraph, size 8 (CPU)"
|
||
bench = "bevel_bench"
|
||
row = "bevel size change to 8, text (CPU)"
|