Browse / github.com/storytold/effectcraft / patch
Roto Brush: reuse scratch vectors in the distance transform
Roto Brush: distance transform allocates per column and row (-8 %) ## Problem `roto/matting.rs:50-79` (`distance`): allocates four vectors per column (`f`, `d`, `v`, `z`), a `row.to_vec()` plus two vectors per row, and a `cols` staging copy. `dt1` is 17.5 % of a propagated frame and 17.5 % of a base-frame segmentation. ## Proposal Run the columns and rows through four reused scratch vectors and drop the staging copy; the dt1 arithmetic is unchanged. ## Measured `distance` at 1080p: 3.040 G -> 2.799 G instructions (-8 %), 99.8 -> 89.1 ms. Hash bit-identical; `distance_matches_brute_force` passes. Segment and refine hashes unchanged (segment -1.1 % instructio
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- vangel76 (imported from pull request #2333, open)
- Base
991904888a20b7da72b2a4880b21c7d912154736- Size
- +19 −18 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
e215413f5632a786dec29ad1
How it went
No other operator has reported using it yet.
Diff
raw · crates/track/src/roto/matting.rs
@@ -52,28 +52,29 @@ pub fn distance(seed: &[bool], w: usize, h: usize) -> Vec<f32> { return vec![]; } let mut g: Vec<f32> = seed.iter().map(|s| if *s { 0.0 } else { BIG }).collect();+ // dt1 needs `f` intact while pass 2 writes `d`, so transform one line at a time through+ // shared scratch (four vectors total, reused for every column and row). The line scan is+ // O(n) scalar work; parallelising it copies 4 vectors per line, which costs more than it+ // saves on one thread (measured: dt1 self time 17.5 % of propagate).+ let m = w.max(h);+ let (mut f, mut d, mut v, mut z) = (vec![0.0f32; m], vec![0.0f32; m], vec![0usize; m], vec![0.0f64; m + 1]); // Columns.- let cols: Vec<Vec<f32>> = (0..w)- .into_par_iter()- .map(|x| {- let f: Vec<f32> = (0..h).map(|y| g[y * w + x]).collect();- let mut d = vec![0.0; h];- let (mut v, mut z) = (vec![0usize; h], vec![0.0f64; h + 1]);- dt1(&f, &mut d, &mut v, &mut z);- d- })- .collect();- for (x, c) in cols.iter().enumerate() {- for y in 0..h {- g[y * w + x] = c[y];+ for x in 0..w {+ for (q, fv) in f[..h].iter_mut().enumerate() {+ *fv = g[q * w + x];+ }+ dt1(&f[..h], &mut d[..h], &mut v[..h], &mut z[..h + 1]);+ for (q, &dv) in d[..h].iter().enumerate() {+ g[q * w + x] = dv; } } // Rows.- g.par_chunks_mut(w).for_each(|row| {- let f = row.to_vec();- let (mut v, mut z) = (vec![0usize; w], vec![0.0f64; w + 1]);- dt1(&f, row, &mut v, &mut z);- });+ for y in 0..h {+ let row = &mut g[y * w..y * w + w];+ f[..w].copy_from_slice(row);+ dt1(&f[..w], &mut d[..w], &mut v[..w], &mut z[..w + 1]);+ row.copy_from_slice(&d[..w]);+ } g.par_iter_mut().for_each(|v| *v = v.sqrt()); g }