mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-27 03:17:05 -06:00
* PSD plane shuffles in bands; the document histogram computes off the UI thread interleave/deinterleave walked every pixel of a 20 MP image on one thread during PSD open and save. Both now split the rows into one band per core (the file's own par_map/bands helpers) and merge the band results in order: 20 MP RGB u16 interleave 152 -> 101 ms, deinterleave 461 -> 160 ms (2.9x); f32 rgba 329/730 -> 220/396 ms. Behaviour is pinned by a parity test against the sequential reference at odd pixel counts (band boundaries), all depths, with a missing plane in the mix. The Histogram panel computed its payload inline in the frame every 250 ms while stale - a 41.5 ms UI-thread stall measured on a 24 MP document, on top of brush work. compute_histograms now runs on a worker thread; the panel keeps showing the previous histogram with the existing cached-data warning until the result lands, and a stale result cannot overwrite a newer revision. Pinned by a panel test that drives one egui frame and asserts the job is scheduled, delivers off thread and respects revisions. * Histogram panel: compute inline when no thread can be spawned; drop the benchmark test std::thread spawn fails on wasm32 (and can fail natively), which left hist_job waiting on a disconnected receiver forever. Without a worker the histogram is now computed inline and delivered through the same channel, a disconnected job is cleared, and the perf span is measured in the worker. Removes the dead t0 and the wall-clock benchmark test; adds a dead-worker test. 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>
55 lines
2.4 KiB
Rust
55 lines
2.4 KiB
Rust
//! BEFORE/AFTER helper for the banded interleave/deinterleave: `--serial` runs the old
|
|
//! sequential implementation inline, the default runs the current parallel one.
|
|
fn main() {
|
|
use photocraft_color::SampleType;
|
|
let serial = std::env::args().any(|a| a == "--serial");
|
|
for (label, s, ch) in [("u16 rgb", SampleType::U16, 3usize), ("f32 rgba", SampleType::F32, 4)] {
|
|
let bps = s.bytes();
|
|
let n = 20_000_000usize;
|
|
let planes: Vec<Vec<u8>> = (0..ch).map(|c| (0..n * bps).map(|i| (i * 7 + c) as u8).collect()).collect();
|
|
let refs: Vec<Option<&[u8]>> = planes.iter().map(|p| Some(&p[..])).collect();
|
|
let fill: Vec<Vec<u8>> = (0..ch).map(|_| vec![0u8; bps]).collect();
|
|
let invert = [false; 4];
|
|
let t0 = std::time::Instant::now();
|
|
let interleaved = if serial {
|
|
let mut out = vec![0u8; n * ch * bps];
|
|
for (c, plane) in refs.iter().enumerate() {
|
|
for i in 0..n {
|
|
let dst = &mut out[(i * ch + c) * bps..(i * ch + c + 1) * bps];
|
|
if let Some(p) = plane
|
|
&& p.len() >= (i + 1) * bps
|
|
{
|
|
let src = &p[i * bps..(i + 1) * bps];
|
|
for k in 0..bps {
|
|
dst[k] = src[bps - 1 - k];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
out
|
|
} else {
|
|
photocraft_io::pixels::interleave(&refs, &fill, n, s, &invert)
|
|
};
|
|
let t1 = t0.elapsed().as_secs_f64() * 1000.0;
|
|
let t2 = std::time::Instant::now();
|
|
if serial {
|
|
let mut acc: Vec<Vec<u8>> = Vec::new();
|
|
for _ in 0..ch {
|
|
acc.push(Vec::with_capacity(n * bps));
|
|
}
|
|
let mut tmp = [0u8; 4];
|
|
for i in 0..n {
|
|
for (c, plane) in acc.iter_mut().enumerate() {
|
|
let src = &interleaved[(i * ch + c) * bps..(i * ch + c + 1) * bps];
|
|
tmp[..bps].copy_from_slice(src);
|
|
plane.extend(tmp[..bps].iter().rev());
|
|
}
|
|
}
|
|
} else {
|
|
let _ = photocraft_io::pixels::deinterleave(&interleaved, ch, s, &invert);
|
|
}
|
|
let t3 = t2.elapsed().as_secs_f64() * 1000.0;
|
|
println!("{label}: interleave {t1:.1} ms, deinterleave {t3:.1} ms (20 MP, release, {})", if serial { "serial (before)" } else { "parallel (after)" });
|
|
}
|
|
}
|