Files
photocraft/crates/engine/examples/bench_marquee.rs
Valentin KleinandClaude Opus 5.5 614d06892c perf: marquee selections on large documents are built row by row (#2888) (#2907)
The Elliptical Marquee tested and wrote every pixel one at a time
(Surface::write_pixel), single-threaded, with no shared full tiles. The
add/subtract/intersect modes read both selections over the whole canvas
into f32 buffers. On 6000x4000 an ellipse took ~0.6-1.1 s and a mode
combination ~0.3-0.5 s.

ellipse_surface builds the shape per row: a run of fully selected pixels
(bounded analytically, checked with the exact f32 tests at both ends)
and edge pixels that get the original per-pixel coverage test. Tiles are
built in parallel, and fully selected tiles share one tile.
combine_surfaces maps two GRAY8 selections tile by tile through a
256x256 byte table, sharing tiles that come through unchanged. Both
produce exactly the old bytes (equivalence test against the old
per-pixel code).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:11:17 -04:00

46 lines
2.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! End-to-end timing of `select.rect` (rectangular and elliptical marquee) on a 6000×4000
//! document. `cargo run --release -p photocraft-engine --example bench_marquee [reps]`
use std::time::{Duration, Instant};
use photocraft_engine::Session;
use serde_json::{Value, json};
fn main() {
let reps: usize = std::env::args().nth(1).and_then(|v| v.parse().ok()).unwrap_or(5);
let mut s = Session::new();
s.execute("file.new", json!({"width": 6000, "height": 4000})).unwrap();
let base = json!({"x": 1000, "y": 500, "width": 3000, "height": 2000});
let rect = |x: i32, y: i32, w: i32, h: i32, extra: Value| {
let mut p = json!({"x": x, "y": y, "width": w, "height": h});
if let (Some(m), Some(e)) = (p.as_object_mut(), extra.as_object()) {
m.extend(e.clone());
}
p
};
let cases: Vec<(&str, Option<Value>, Value)> = vec![
("rect replace 5800x3800", None, rect(100, 100, 5800, 3800, json!({}))),
("ellipse replace 5800x3800 aa", None, rect(100, 100, 5800, 3800, json!({"ellipse": true}))),
("ellipse replace 5800x3800 no aa", None, rect(100, 100, 5800, 3800, json!({"ellipse": true, "antiAlias": false}))),
("ellipse replace 1000x800 aa", None, rect(2000, 1500, 1000, 800, json!({"ellipse": true}))),
("ellipse off-canvas 8000x6000 aa", None, rect(-1000, -1000, 8000, 6000, json!({"ellipse": true}))),
("ellipse feather 20", None, rect(100, 100, 5800, 3800, json!({"ellipse": true, "feather": 20}))),
("rect add", Some(base.clone()), rect(2500, 1500, 3000, 2000, json!({"mode": "add"}))),
("ellipse add", Some(base.clone()), rect(2500, 1500, 3000, 2000, json!({"ellipse": true, "mode": "add"}))),
("ellipse subtract", Some(base.clone()), rect(2500, 1500, 3000, 2000, json!({"ellipse": true, "mode": "subtract"}))),
("ellipse intersect", Some(base.clone()), rect(2500, 1500, 3000, 2000, json!({"ellipse": true, "mode": "intersect"}))),
];
for (label, pre, p) in cases {
let mut best = Duration::MAX;
for _ in 0..reps {
s.execute("select.deselect", json!({})).ok();
if let Some(pre) = &pre {
s.execute("select.rect", pre.clone()).unwrap();
}
let t = Instant::now();
s.execute("select.rect", p.clone()).unwrap();
best = best.min(t.elapsed());
}
println!("{label:34} best of {reps}: {best:.2?}");
}
}