Record native preview benchmarks and review evidence

This commit is contained in:
Vlad Grankovsky
2026-10-25 18:54:49 +01:00
parent 009c4fa612
commit 1cec8bce81
10 changed files with 338 additions and 12 deletions
+2 -2
View File
@@ -90,8 +90,8 @@ The other built-in ICC profiles and the generated LUT looks are produced by code
| `docs/images/preferences-apply-*.png` | Preferences before and after adding Apply (no artwork) | PhotoCraft contributors | PhotoCraft control-channel capture and offscreen `snapshot` example | MIT OR Apache-2.0 | | `docs/images/preferences-apply-*.png` | Preferences before and after adding Apply (no artwork) | PhotoCraft contributors | PhotoCraft control-channel capture and offscreen `snapshot` example | MIT OR Apache-2.0 |
| `docs/images/gradient-overlay-method-*.png` | Gradient Overlay Method selector, before and after (synthetic gradient) | @oRE-o | Rendered offscreen with the `snapshot` example; no external artwork | MIT OR Apache-2.0 | | `docs/images/gradient-overlay-method-*.png` | Gradient Overlay Method selector, before and after (synthetic gradient) | @oRE-o | Rendered offscreen with the `snapshot` example; no external artwork | MIT OR Apache-2.0 |
| `docs/images/cpu-pixel-alignment/*.png` | CPU canvas pixel alignment, before and after #8321 | PhotoCraft contributors | Original synthetic noise rendered with the offscreen `snapshot` example; see the adjacent README | MIT OR Apache-2.0, adjacent `LICENSE-MIT` and `LICENSE-APACHE` | | `docs/images/cpu-pixel-alignment/*.png` | CPU canvas pixel alignment, before and after #8321 | PhotoCraft contributors | Original synthetic noise rendered with the offscreen `snapshot` example; see the adjacent README | MIT OR Apache-2.0, adjacent `LICENSE-MIT` and `LICENSE-APACHE` |
| `docs/images/multi-channel-editing/{curves,curves-choices,levels,drawing}.png` | Selected colour channel editing (synthetic gradient) | @hidoba | Original synthetic gradient rendered with the offscreen `snapshot` example; adjacent JSON scripts | MIT OR Apache-2.0, adjacent `LICENSE-MIT` and `LICENSE-APACHE` | | `docs/images/multi-channel-editing/{curves,curves-choices,levels,drawing,mask-view-before,mask-view-after}.png` | Selected colour channel editing (synthetic gradient) | @hidoba | Original synthetic gradient rendered with the offscreen `snapshot` example; adjacent JSON scripts | MIT OR Apache-2.0, adjacent `LICENSE-MIT` and `LICENSE-APACHE` |
| `docs/images/multi-channel-editing/great-wave.jpg`, `rgb.png`, `cmyk.png`, `lab.png` | *The Great Wave off Kanagawa*, c. 1830–1832, and PhotoCraft channel-selection captures | Katsushika Hokusai (artwork); PhotoCraft contributors (UI) | [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Tsunami_by_hokusai_19th_century.jpg), Metropolitan Museum reproduction; adjacent JSON capture scripts | Public domain artwork (PD-Art), adjacent `LICENSE-PUBLIC-DOMAIN.txt`; UI/scripts MIT OR Apache-2.0 | | `docs/images/multi-channel-editing/great-wave.jpg`, `rgb.png`, `cmyk.png`, `lab.png`, `gallery-channel-preview-{before,after}.png` | *The Great Wave off Kanagawa*, c. 1830–1832, and PhotoCraft channel-selection captures | Katsushika Hokusai (artwork); PhotoCraft contributors (UI) | [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Tsunami_by_hokusai_19th_century.jpg), Metropolitan Museum reproduction; adjacent JSON capture scripts | Public domain artwork (PD-Art), adjacent `LICENSE-PUBLIC-DOMAIN.txt`; UI/scripts MIT OR Apache-2.0 |
Artwork shown in the screenshots (all public domain, via Wikimedia Commons; details in Artwork shown in the screenshots (all public domain, via Wikimedia Commons; details in
[`docs/images/SOURCES.md`](docs/images/SOURCES.md)): [`docs/images/SOURCES.md`](docs/images/SOURCES.md)):
@@ -19,5 +19,27 @@
"candidate_source_commit": "aeff53449221e8b42a628d832abc595c9503a146", "candidate_source_commit": "aeff53449221e8b42a628d832abc595c9503a146",
"candidate_tone_source_sha256": "da42e686db09ed4e5bf2c19316a6b47261c97185aec83c6bba86831e29e39c6e", "candidate_tone_source_sha256": "da42e686db09ed4e5bf2c19316a6b47261c97185aec83c6bba86831e29e39c6e",
"before_fused_rerun_resources": "elapsed_seconds=16.47 peak_rss_kib=690516", "before_fused_rerun_resources": "elapsed_seconds=16.47 peak_rss_kib=690516",
"before_fused_commit": "15bf0873ba03622e54189e668df20a1403e41d56" "before_fused_commit": "15bf0873ba03622e54189e668df20a1403e41d56",
"liquify_preview": {
"source_content_commit": "009c4fa6128dad1e564d731656a304953d879842",
"build_git_head": "a37d21de7e1a90451d5afcad6772ae358f69e07a",
"binary_sha256": "309bc5e882403080ade9410103fba6eb73b296da897274a7816380f50e413c3a",
"files_sha256": {
"crates/ui-egui/examples/multi_channel_preview_bench.rs": "84f4e6fd4a0f79a35da706a06144401ad06509a06608ea5ed89729d394f6e0b1",
"crates/ui-egui/src/liquify_ui.rs": "1179cb7403fa189a8e0d1872212e150bd98b10844d189140e954cd0cdd279f29",
"crates/ui-egui/src/gallery_ui.rs": "118249270993f1443882df4808fd0f6e05b129a091441f3560bdcc0e4d80a4ca",
"crates/ui-egui/src/wide_angle_ui.rs": "6ebf9f1d291642dc3cc42c72980830f79e9f82c6d9b743c145e9048adddb1b36",
"crates/engine/src/color_channels.rs": "e6737a5dfb79a5324f08173d18cf271a539dddb386b9488249c150810537313e"
},
"measured_at_utc": "2026-10-25T17:45:11.612486+00:00",
"profile": "release, thin LTO",
"rayon_threads": 8,
"measurement": "CPU field deformation plus dirty native proxy render. GPU texture upload/presentation excluded. One warmup plus six interleaved updates; p50 is upper middle; max_ms is max of six. open_ms is one sequential setup timing per mode, not a median.",
"resources": {
"elapsed_seconds": 0.72,
"peak_rss_kib": 389664
},
"isolation": "Measured after final tests, builds and cargo xtask perf --quick ended; no Cargo or rustc processes were running.",
"correctness_proof": "Each workspace reports a nonzero Liquify maxDisplacement after the measured updates."
}
} }
@@ -0,0 +1,54 @@
{
"depth": 16,
"operation": "Liquify 120px live move; native proxy preview",
"repeats": 6,
"rows": [
{
"max_ms": 0.120119,
"open_ms": 22.648719,
"p50_ms": 0.110929,
"samples_ms": [
0.111069,
0.097649,
0.10152,
0.120119,
0.10755999999999999,
0.110929
],
"target": "all"
},
{
"max_ms": 0.36013799999999996,
"open_ms": 246.61311,
"p50_ms": 0.34547799999999995,
"samples_ms": [
0.310718,
0.30684799999999995,
0.320127,
0.352098,
0.34547799999999995,
0.36013799999999996
],
"target": "red"
},
{
"max_ms": 0.461107,
"open_ms": 317.70011900000003,
"p50_ms": 0.447017,
"samples_ms": [
0.397908,
0.394428,
0.416098,
0.448617,
0.447017,
0.461107
],
"target": "red_blue"
}
],
"size": [
6000,
4000
],
"warmups": 1
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

@@ -0,0 +1,51 @@
[
[
"engine.execute",
{
"command": "image.imageSize",
"params": {
"width": 1000,
"resample": "bicubic"
}
}
],
[
"engine.execute",
{
"command": "channel.target",
"params": {
"colors": [
0,
2
]
}
}
],
[
"engine.execute",
{
"command": "channel.setVisible",
"params": {
"channel": "composite",
"visible": true
}
}
],
[
"ui.menu.invoke",
{
"id": "filter.filterGallery"
}
],
[
"ui.menu.invoke",
{
"id": "filter.filterGallery",
"params": {
"ui": {
"filter": "cutout"
}
}
}
]
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

@@ -0,0 +1,128 @@
[
[
"engine.execute",
{
"command": "file.new",
"params": {
"width": 800,
"height": 560,
"depth": 16,
"background": "white"
}
}
],
[
"engine.execute",
{
"command": "paint.gradient",
"params": {
"from": [
0,
0
],
"to": [
800,
560
],
"colors": [
"#143b9b",
"#f6b552"
]
}
}
],
[
"engine.execute",
{
"command": "layer.layerMask.revealAll",
"params": {}
}
],
[
"engine.execute",
{
"command": "channel.target",
"params": {
"colors": [
0,
2
]
}
}
],
[
"engine.execute",
{
"command": "view.layerMask",
"params": {
"mode": "gray"
}
}
],
[
"ui.set",
{
"tool": "brush",
"activeTabs": {
"middle": "channels"
},
"fit": true
}
],
[
"engine.execute",
{
"command": "tools.setColors",
"params": {
"foreground": [
0,
0,
0,
1
]
}
}
],
[
"engine.execute",
{
"command": "tools.setBrush",
"params": {
"brush": {
"size": 70,
"hardness": 1.0,
"smoothing": {
"amount": 0.0
}
}
}
}
],
[
"ui.pointer",
{
"events": [
{
"kind": "down",
"x": 150,
"y": 200
},
{
"kind": "move",
"x": 400,
"y": 280
},
{
"kind": "move",
"x": 650,
"y": 200
},
{
"kind": "up",
"x": 650,
"y": 200
}
]
}
]
]
+80 -9
View File
@@ -146,15 +146,30 @@ Raw samples: [original pre-feature base](benchmarks/multi-channel-editing/baseli
[environment and binary hashes](benchmarks/multi-channel-editing/environment.json). [environment and binary hashes](benchmarks/multi-channel-editing/environment.json).
The three `candidate*-before-fused-original.json` files preserve the original feature run. The three `candidate*-before-fused-original.json` files preserve the original feature run.
Validation: 6,472 engine and UI tests passed (38 ignored), including 53 focused engine Validation after the main merge and review fixes: 6,542 engine and UI tests passed
regressions. Strict all-target Clippy, formatting, layering, (38 ignored), including 58 focused engine regressions. Strict all-target Clippy,
wasm, parity generation and the adversarial command panic hunt passed. The complete formatting, layering, wasm, parity generation and the adversarial command panic hunt passed.
`cargo xtask perf --quick` runner was attempted: its layout fixture fails because The complete `cargo xtask perf --quick` runner now finishes successfully after the main
it tries to convert an already imported Smart Object. This unrelated runner failure is not merge; its report still lists unrelated, non-enforced budget overruns. Six further local
reported as a passing full performance gate. The feature-specific benchmark above review/fix rounds followed the latest CodeRabbit review; the last found no actionable issues.
completed successfully. Eleven original review/fix rounds and four follow-up reviews were completed; the All visual captures were rendered offscreen and inspected.
latest review found no actionable issues. Visual captures were rendered offscreen
and inspected. The additional 24 MP RGB16 Liquify benchmark uses
`crates/ui-egui/examples/multi_channel_preview_bench.rs`, with eight Rayon workers and
no concurrent builds, tests or performance scenarios. It measures field deformation plus
the dirty CPU proxy render; GPU uploads/presentation are excluded. One warmup and six
measured updates use interleaved target modes. The field's nonzero displacement is checked.
| Liquify, 120 px brush | All channels | Red only | Red + Blue |
|---|---:|---:|---:|
| Preview update p50 | 0.111 ms | 0.345 ms | 0.447 ms |
| Maximum of six updates | 0.120 ms | 0.360 ms | 0.461 ms |
| Open workspace, one setup measurement | 22.6 ms | 246.6 ms | 317.7 ms |
Opening a component workspace builds its native proxy once. Open timings are sequential
single observations, not medians; setup is excluded from update timings. The whole process,
including all three workspaces, peaked at 380.5 MiB RSS. [Raw samples](benchmarks/multi-channel-editing/liquify-preview.json)
and [source/binary hashes](benchmarks/multi-channel-editing/environment.json) are retained.
## Visual evidence ## Visual evidence
@@ -200,3 +215,59 @@ retouch-preview restriction issues are fixed, with regression tests. The propose
clipboard inversion is unnecessary: destination extraction and scatter already convert clipboard inversion is unnecessary: destination extraction and scatter already convert
between tone and ink. Tests cover implicit/explicit singleton copying into explicit targets between tone and ink. Tests cover implicit/explicit singleton copying into explicit targets
and normal/Paste Into/Paste Outside paths at 8/16/32-bit depths without adding an inversion. and normal/Paste Into/Paste Outside paths at 8/16/32-bit depths without adding an inversion.
The post-merge review identified missing Copy/Cut parameter documentation; both commands
now describe explicit colour targets and layer ids/names. Auditing that contract also found
that Copy ignored an explicit layer with an implicit single-component target. Copy now
resolves that layer just as Cut does, with a regression that failed before the correction.
The proposed Lab histogram index change is unnecessary: `HistSource::Colors` reserves
slot 0 for the combined selection and stores native components at `k + 1`, including Lab.
Ordinary Lab histograms instead use slots 0–2 for L/a/b. The histogram accessor returns
empty bins for an unavailable slot, rather than panicking. New tests use actual native
subset histograms for components `[0, 2]`, render every choice in both editors, and check
Levels Auto against those bins in RGB/CMYK/Lab at 8/16/32-bit depths.
The next local review found that viewing a layer mask after selecting colour channels
could still send canvas strokes to those colours. Mask view now takes priority in canvas
painting, implicit engine commands and captured tone-dialog targets. Regressions cover
live previews and commits plus adjustments/filters at all depths, while explicit native
targets can still override the viewed mask. Selecting colour rows again leaves mask view
and restores their editing target and visibility.
A further review caught mask Cut/Clear falling back to layer pixels, dialog capture retaining
the old colour set over a viewed mask, and Desaturate dropping explicit target parameters.
Cut/Clear now copy/fill only the selected mask samples, respecting locks and feathering.
Adjustment/filter dialogs preserve their captured mask through preview, confirmation and
Curves Reset, and Desaturate forwards its target and layer parameters. Tests cover pixel and
clipboard preservation, undo/redo, refused edits and changed panel selections.
Move/Free Transform still work on the viewed unlinked mask when colour rows remain selected
behind it; native colour subsets retain their geometry restriction. Curves canvas-point
gestures also honour the captured scalar target after the panel selection changes.
The named-destination visibility guard now resolves layer names just as painting does.
Filter Gallery, Liquify and Adaptive Wide Angle capture the native colour target, preserve
unselected components and alpha in their specialized previews, and retain the target for OK.
Liquify samples only the dirty proxy or detail viewport, with a native proxy built once.
These full-window workspaces require layer pixels or native colour channels; masks, saved
channels and Quick Mask remain available through ordinary filter commands/dialogs.
Geometric writeback retains the original alpha layout, copying selected native bytes even
when the warp produces an alpha-bearing surface. Changed defaults materialize only within
the supplied document/preview extent, under the same bounded-pixel limit.
The public-domain Filter Gallery comparison uses R+B with Green manually made visible.
The original preview filters Green too; the corrected preview retains its original detail.
The [capture script](images/multi-channel-editing/gallery-channel-preview-script.json)
records that explicit visibility override.
![Before: Cutout also changes Green](images/multi-channel-editing/gallery-channel-preview-before.png)
![After: Cutout preserves unselected Green](images/multi-channel-editing/gallery-channel-preview-after.png)
The same synthetic gradient and [pointer script](images/multi-channel-editing/mask-view-script.json)
show the correction: before, the mask stays white while the stroke changes the layer;
after, the stroke paints the mask while the layer's gradient stays intact.
![Before: the displayed mask stays white](images/multi-channel-editing/mask-view-before.png)
![After: the displayed mask receives the stroke](images/multi-channel-editing/mask-view-after.png)