collective

Browse / github.com/storytold/effectcraft / patch

av1enc: motion search with visited set and early-exit SAD, fixed-size SATD, region-only snapshots

av1enc: half-pel planes, motion search, SATD and decoded-flag snapshot in the decision code (-21 % together with #2373/#2374) ## Where the time goes At the export default, mode decision is 90-95 % of the encode: `try_tx` 50 % (inverse transform 21 %, forward 11 %), intra checks 45 % (including intra tried inside inter frames), motion search 15-20 %, SATD 9 % (24 % on key frames), half-pel planes 4-6 %, loop filter 2 %, entropy coding 1 %. Baseline: about 1 frame per second at 1080p. ## Proposal (all output-identical) - A-I4 (`pred.rs`, `Decider::new`): build the half-pel planes in one pass. - A-I5 (`decide.rs`): motion search with a visited set and a partial-SAD early exit. - A-I6: SATD with a fixed-size Hadamard (`

Status
open
Score
0.00
Adopters
0 other operators
Author
vangel76 (imported from pull request #2406, open)
Base
991904888a20b7da72b2a4880b21c7d912154736
Size
+166 −48 in 1 file
Review
pending
Shared
2026-10-28
Id
256d2acd22aa29cfce094fd3

How it went

No other operator has reported using it yet.

Diff

raw · crates/av1enc/src/decide.rs

diff --git a/crates/av1enc/src/decide.rs b/crates/av1enc/src/decide.rs--- a/crates/av1enc/src/decide.rs+++ b/crates/av1enc/src/decide.rs@@ -110,7 +110,8 @@ struct TxResult { /// Saved encoder state of a block area (reconstruction, BlockDecoded flags). struct Snapshot {     planes: [Vec<u16>; 3],-    decoded: Box<[[[bool; 35]; 35]; 3]>,+    /// BlockDecoded flags of the block area only (the only flags coding inside it can change).+    decoded: [[[bool; 16]; 16]; 3], }  pub(crate) struct Decider<'a> {@@ -211,6 +212,13 @@ impl<'a> Decider<'a> {         }     } +    /// The BlockDecoded cells `mark_decoded` can set for blocks inside (r, c, n4): rows and+    /// columns `by + 1 .. by + 1 + step` / `bx + 1 ..` of `decoded[plane]`.+    fn decoded_region(plane: usize, r: usize, c: usize, n4: usize) -> (usize, usize, usize) {+        let s = (plane > 0) as usize;+        ((r & 15) >> s, (c & 15) >> s, (n4 >> s).max(1))+    }+     fn snapshot(&self, r: usize, c: usize, n4: usize) -> Snapshot {         let planes = std::array::from_fn(|p| {             let s = (p > 0) as usize;@@ -222,7 +230,14 @@ impl<'a> Decider<'a> {             }             v         });-        Snapshot { planes, decoded: self.decoded.clone() }+        let mut decoded = [[[false; 16]; 16]; 3];+        for (p, d) in decoded.iter_mut().enumerate() {+            let (by, bx, step) = Self::decoded_region(p, r, c, n4);+            for i in 0..step {+                d[i][..step].copy_from_slice(&self.decoded[p][by + i + 1][bx + 1..bx + 1 + step]);+            }+        }+        Snapshot { planes, decoded }     }      fn restore(&mut self, snap: &Snapshot, r: usize, c: usize, n4: usize) {@@ -234,7 +249,12 @@ impl<'a> Decider<'a> {                 pl.data[y * pl.stride + x0..y * pl.stride + x0 + n].copy_from_slice(&snap.planes[p][i * n..i * n + n]);             }         }-        self.decoded.clone_from(&snap.decoded);+        for p in 0..3 {+            let (by, bx, step) = Self::decoded_region(p, r, c, n4);+            for i in 0..step {+                self.decoded[p][by + i + 1][bx + 1..bx + 1 + step].copy_from_slice(&snap.decoded[p][i][..step]);+            }+        }     }      fn set_mvs(&mut self, r: usize, c: usize, n4: usize, mv: [i32; 2]) {@@ -519,15 +539,22 @@ impl<'a> Decider<'a> {             let comp = |d: i32| if d == 0 { 0.5 } else { 2.0 + 2.0 * ((d.abs() / 8) as f64 + 1.0).log2() };             comp(m[0] - pred_mv[0]) + comp(m[1] - pred_mv[1])         };-        let cost =-            |m: [i32; 2]| -> f64 { self.sad_ref(&refr.planes[0], x0 + (m[1] >> 3), y0 + (m[0] >> 3), c * 4, r * 4, n) as f64 + self.lambda_sad * mv_bits(m) };+        // `bound` is the cost to beat: the SAD stops early once the cost cannot be below it (the+        // callers only accept a strictly lower cost, so the result is unchanged)+        let cost = |m: [i32; 2], bound: f64| -> f64 {+            let lb = self.lambda_sad * mv_bits(m);+            self.sad_ref(&refr.planes[0], x0 + (m[1] >> 3), y0 + (m[0] >> 3), c * 4, r * 4, n, lb, bound) as f64 + lb+        };+        // positions already evaluated cannot beat `best` again (costs are deterministic)+        let mut seen: Vec<[i32; 2]> = Vec::with_capacity(64);         let mut best = ([0, 0], f64::MAX);         for &m in &cands {             let m = [m[0] & !7, m[1] & !7];-            if !valid(m) {+            if !valid(m) || seen.contains(&m) {                 continue;             }-            let cst = cost(m);+            seen.push(m);+            let cst = cost(m, best.1);             if cst < best.1 {                 best = (m, cst);             }@@ -548,10 +575,11 @@ impl<'a> Decider<'a> {                 let center = best.0;                 for d in [[-step, 0], [step, 0], [0, -step], [0, step]] {                     let m = [center[0] + d[0], center[1] + d[1]];-                    if !valid(m) {+                    if !valid(m) || seen.contains(&m) {                         continue;                     }-                    let cst = cost(m);+                    seen.push(m);+                    let cst = cost(m, best.1);                     if cst < best.1 {                         best = (m, cst);                         moved = true;@@ -562,15 +590,18 @@ impl<'a> Decider<'a> {         }         // half- then quarter-sample refinement (plus the exact neighbour candidates)         let mut buf = vec![0u16; n * n];-        let sub_cost = |m: [i32; 2], buf: &mut Vec<u16>| -> f64 {+        let sub_cost = |m: [i32; 2], buf: &mut Vec<u16>, bound: f64| -> f64 {             self.search_block(m, c * 4, r * 4, n, buf);-            self.sad(0, c * 4, r * 4, n, buf) as f64 + self.lambda_sad * mv_bits(m)+            let lb = self.lambda_sad * mv_bits(m);+            self.sad_bounded(0, c * 4, r * 4, n, buf, lb, bound) as f64 + lb         };+        let mut seen_sub: Vec<[i32; 2]> = Vec::with_capacity(24);         for m in cands {             if m[0] & 7 != 0 || m[1] & 7 != 0 {                 let m = [m[0] & !1, m[1] & !1];-                if valid(m) {-                    let cst = sub_cost(m, &mut buf);+                if valid(m) && !seen_sub.contains(&m) {+                    seen_sub.push(m);+                    let cst = sub_cost(m, &mut buf, best.1);                     if cst < best.1 {                         best = (m, cst);                     }@@ -581,10 +612,11 @@ impl<'a> Decider<'a> {             let center = best.0;             for d in [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]] {                 let m = [center[0] + d[0] * step, center[1] + d[1] * step];-                if !valid(m) {+                if !valid(m) || seen_sub.contains(&m) {                     continue;                 }-                let cst = sub_cost(m, &mut buf);+                seen_sub.push(m);+                let cst = sub_cost(m, &mut buf, best.1);                 if cst < best.1 {                     best = (m, cst);                 }@@ -670,7 +702,8 @@ impl<'a> Decider<'a> {     }      /// SAD of the source block at (x, y) (luma) against the reference block at (rx, ry).-    fn sad_ref(&self, refp: &Plane, rx: i32, ry: i32, x: usize, y: usize, n: usize) -> u64 {+    /// Stops after a row once `sad + lb >= bound` (the cost can no longer win).+    fn sad_ref(&self, refp: &Plane, rx: i32, ry: i32, x: usize, y: usize, n: usize, lb: f64, bound: f64) -> u64 {         let (vw, vh) = (self.g.width, self.g.height);         let w = n.min(vw.saturating_sub(x));         let h = n.min(vh.saturating_sub(y));@@ -690,57 +723,78 @@ impl<'a> Decider<'a> {                     sad += (s[j] as i32 - b as i32).unsigned_abs() as u64;                 }             }+            if sad as f64 + lb >= bound {+                return sad;+            }         }         sad     } +    /// SAD of a prediction against the source over the visible part of the block. Stops after a row+    /// once `sad + lb >= bound` (the cost can no longer win).+    fn sad_bounded(&self, p: usize, x: usize, y: usize, n: usize, pred: &[u16], lb: f64, bound: f64) -> u64 {+        let (vw, vh) = self.g.visible(p);+        let w = n.min(vw.saturating_sub(x));+        let h = n.min(vh.saturating_sub(y));+        let src = &self.src[p];+        let mut s = 0u64;+        for i in 0..h {+            let row = &src.row(y + i)[x..x + w];+            s += row.iter().zip(&pred[i * n..i * n + w]).map(|(&a, &b)| (a as i32 - b as i32).unsigned_abs()).sum::<u32>() as u64;+            if s as f64 + lb >= bound {+                return s;+            }+        }+        s+    }+     /// Sum of absolute Hadamard-transformed differences (4x4 or 8x8 sub-blocks) of a prediction     /// against the source, scaled to about the size of the SAD.     fn satd(&self, p: usize, x: usize, y: usize, n: usize, pred: &[u16]) -> u64 {+        if n >= 8 { self.satd_k::<8>(p, x, y, n, pred) } else { self.satd_k::<4>(p, x, y, n, pred) }+    }++    /// `satd` with K x K Hadamard sub-blocks (fixed sizes so the butterflies vectorise).+    fn satd_k<const K: usize>(&self, p: usize, x: usize, y: usize, n: usize, pred: &[u16]) -> u64 {         let src = &self.src[p];-        let k = n.min(8);         let mut total = 0u64;-        let mut d = [0i32; 64];-        for by in (0..n).step_by(k) {-            for bx in (0..n).step_by(k) {-                for i in 0..k {-                    let row = &src.row(y + by + i)[x + bx..x + bx + k];-                    for j in 0..k {-                        d[i * k + j] = row[j] as i32 - pred[(by + i) * n + bx + j] as i32;+        for by in (0..n).step_by(K) {+            for bx in (0..n).step_by(K) {+                // d[i] holds row i of the differences+                let mut d = [[0i32; K]; K];+                for (i, di) in d.iter_mut().enumerate() {+                    let row = &src.row(y + by + i)[x + bx..x + bx + K];+                    let pr = &pred[(by + i) * n + bx..(by + i) * n + bx + K];+                    for j in 0..K {+                        di[j] = row[j] as i32 - pr[j] as i32;                     }                 }-                for i in 0..k {-                    hadamard(&mut d[i * k..i * k + k]);+                // vertical butterflies across rows (all columns at once)+                let mut h = 1;+                while h < K {+                    for i0 in (0..K).step_by(2 * h) {+                        for i in i0..i0 + h {+                            let (a, b) = (d[i], d[i + h]);+                            for j in 0..K {+                                d[i][j] = a[j] + b[j];+                                d[i + h][j] = a[j] - b[j];+                            }+                        }+                    }+                    h *= 2;                 }-                let mut col = [0i32; 8];+                // horizontal butterflies within each row                 let mut s = 0u64;-                for j in 0..k {-                    for i in 0..k {-                        col[i] = d[i * k + j];-                    }-                    hadamard(&mut col[..k]);-                    s += col[..k].iter().map(|v| v.unsigned_abs() as u64).sum::<u64>();+                for di in d.iter_mut() {+                    hadamard(di);+                    s += di.iter().map(|v| v.unsigned_abs() as u64).sum::<u64>();                 }-                total += s / (k as u64 / 2);+                total += s / (K as u64 / 2);             }         }         total     } -    /// SAD of a prediction against the source over the visible part of the block.-    fn sad(&self, p: usize, x: usize, y: usize, n: usize, pred: &[u16]) -> u64 {-        let (vw, vh) = self.g.visible(p);-        let w = n.min(vw.saturating_sub(x));-        let h = n.min(vh.saturating_sub(y));-        let src = &self.src[p];-        let mut s = 0u64;-        for i in 0..h {-            let row = &src.row(y + i)[x..x + w];-            s += row.iter().zip(&pred[i * n..i * n + w]).map(|(&a, &b)| (a as i32 - b as i32).unsigned_abs()).sum::<u32>() as u64;-        }-        s-    }-     fn sse(&self, p: usize, x: usize, y: usize, n: usize, blk: &[u16]) -> f64 {         let (vw, vh) = self.g.visible(p);         let w = n.min(vw.saturating_sub(x));@@ -889,3 +943,67 @@ pub(crate) fn scan_for(tx_sz: usize) -> &'static [u16] {         _ => &DEFAULT_SCAN_32X32,     } }++#[cfg(test)]+mod tests {+    use super::*;++    /// The SATD as it was before the fixed-size version: runtime block size, column gather.+    fn satd_ref(src: &Plane, x: usize, y: usize, n: usize, pred: &[u16]) -> u64 {+        let k = n.min(8);+        let mut total = 0u64;+        let mut d = [0i32; 64];+        for by in (0..n).step_by(k) {+            for bx in (0..n).step_by(k) {+                for i in 0..k {+                    let row = &src.row(y + by + i)[x + bx..x + bx + k];+                    for j in 0..k {+                        d[i * k + j] = row[j] as i32 - pred[(by + i) * n + bx + j] as i32;+                    }+                }+                for i in 0..k {+                    hadamard(&mut d[i * k..i * k + k]);+                }+                let mut col = [0i32; 8];+                let mut s = 0u64;+                for j in 0..k {+                    for i in 0..k {+                        col[i] = d[i * k + j];+                    }+                    hadamard(&mut col[..k]);+                    s += col[..k].iter().map(|v| v.unsigned_abs() as u64).sum::<u64>();+                }+                total += s / (k as u64 / 2);+            }+        }+        total+    }++    #[test]+    fn satd_fixed_size_matches_runtime_size() {+        let g = FrameGeom::new(100, 80);+        let mut state = 0x9e37_79b9_7f4a_7c15u64;+        let mut next = move || {+            state ^= state << 13;+            state ^= state >> 7;+            state ^= state << 17;+            state+        };+        for bd in [8u32, 10] {+            let max = (1u64 << bd) - 1;+            let mut mk = |w: usize, h: usize| {+                let mut p = Plane::new(w, h);+                p.data.iter_mut().for_each(|v| *v = (next() % (max + 1)) as u16);+                p+            };+            let src = [mk(128, 128), mk(64, 64), mk(64, 64)];+            let dec = Decider::new(&g, bd, 100, true, &src, None);+            for n in [4usize, 8, 16, 32] {+                let pred: Vec<u16> = (0..n * n).map(|_| (next() % (max + 1)) as u16).collect();+                for (p, x, y) in [(0, 0, 0), (0, 32, 40), (1, 8, 4)] {+                    assert_eq!(dec.satd(p, x, y, n, &pred), satd_ref(&src[p], x, y, n, &pred), "n {n} plane {p}");+                }+            }+        }+    }+}