collective

Browse / github.com/storytold/filmcraft / patch

h264enc: SATD on the half-pel plane, const sad8, fixed-size MC

h264enc: SATD on the half-pel plane, const sad8 and fixed-size MC Found while auditing `crates/h264enc` at upstream/main bfc23840 (Ryzen 7800X3D; clips: m1080 mandelbrot zoom with pan and film grain, t1080 testsrc2 + noise, m2160; export defaults: High, Balanced, VBR 20/30 Mbit/s, 2 B-frames, `slices = ceil(mb_rows/4)` = 17 at 1080p). Baseline 1080p single thread: 4.51 fps, 3.7 G instructions/frame. "BD-rate" is bits at equal PSNR-Y over a QP 22/27/32/37 sweep; "bit-exact" means the output is byte-identical (`cmp`) to the current encoder. Gains marked "estimated" were not measured; the rest come from prototypes. **Kernel overheads: runtime-sized SAD and cop

Status
open
Score
0.00
Adopters
0 other operators
Author
vangel76 (imported from pull request #3204, open)
Base
7428957ae0ece1139ae3af7ea76e97cacdb69570
Size
+70 −13 in 4 files
Review
pending
Shared
2026-10-28
Id
ffb0bf003e3b76b2cbc71be0

How it went

No other operator has reported using it yet.

Diff

raw · crates/h264enc/src/dsp.rs crates/h264enc/src/picture.rs crates/h264enc/src/slice.rs crates/h264enc/src/slice/motion.rs

diff --git a/crates/h264enc/src/dsp.rs b/crates/h264enc/src/dsp.rs--- a/crates/h264enc/src/dsp.rs+++ b/crates/h264enc/src/dsp.rs@@ -14,13 +14,24 @@ pub fn sad(a: &[u8], sa: usize, b: &[u8], sb: usize, w: usize, h: usize) -> u32 /// SAD of a 16-wide block (fast path). #[inline] pub fn sad16(a: &[u8], sa: usize, b: &[u8], sb: usize, h: usize) -> u32 {+    sad_n::<16>(a, sa, b, sb, h)+}++/// SAD of an 8-wide block (fast path).+#[inline]+pub fn sad8(a: &[u8], sa: usize, b: &[u8], sb: usize, h: usize) -> u32 {+    sad_n::<8>(a, sa, b, sb, h)+}++#[inline(always)]+fn sad_n<const N: usize>(a: &[u8], sa: usize, b: &[u8], sb: usize, h: usize) -> u32 {     let mut s = 0u32;     for r in 0..h {-        let (Some(ra), Some(rb)) = (a.get(r * sa..).and_then(|x| x.first_chunk::<16>()), b.get(r * sb..).and_then(|x| x.first_chunk::<16>())) else {+        let (Some(ra), Some(rb)) = (a.get(r * sa..).and_then(|x| x.first_chunk::<N>()), b.get(r * sb..).and_then(|x| x.first_chunk::<N>())) else {             break;         };         let mut acc = 0u16;-        for i in 0..16 {+        for i in 0..N {             acc += ra[i].abs_diff(rb[i]) as u16;         }         s += acc as u32;diff --git a/crates/h264enc/src/picture.rs b/crates/h264enc/src/picture.rs--- a/crates/h264enc/src/picture.rs+++ b/crates/h264enc/src/picture.rs@@ -93,10 +93,10 @@ pub fn build_hpel(f: &Plane, reuse: Option<[Plane; 3]>) -> [Plane; 3] {     });     let rows = f.data.len() / f.stride;     let band = 16usize;-    let work = |b: usize, hrows: &mut [u8], vrows: &mut [u8], crows: &mut [u8]| {+    let work = |b: usize, hrows: &mut [u8], vrows: &mut [u8], crows: &mut [u8], scratch: &mut HpelScratch| {         let r0 = b * band;         let r1 = (r0 + band).min(rows);-        hpel_rows(f, r0, r1, hrows, vrows, crows);+        hpel_rows(f, r0, r1, hrows, vrows, crows, scratch);     };     let chunk = band * f.stride;     #[cfg(feature = "threads")]@@ -107,25 +107,34 @@ pub fn build_hpel(f: &Plane, reuse: Option<[Plane; 3]>) -> [Plane; 3] {             .zip(vp.data.par_chunks_mut(chunk))             .zip(cp.data.par_chunks_mut(chunk))             .enumerate()-            .for_each(|(b, ((h, v), c))| work(b, h, v, c));+            .for_each_init(HpelScratch::default, |s, (b, ((h, v), c))| work(b, h, v, c, s));     }     #[cfg(not(feature = "threads"))]     {+        let mut s = HpelScratch::default();         for (b, ((h, v), c)) in hp.data.chunks_mut(chunk).zip(vp.data.chunks_mut(chunk)).zip(cp.data.chunks_mut(chunk)).enumerate() {-            work(b, h, v, c);+            work(b, h, v, c, &mut s);         }     }     [hp, vp, cp] } -fn hpel_rows(f: &Plane, r0: usize, r1: usize, hout: &mut [u8], vout: &mut [u8], cout: &mut [u8]) {+/// Per-worker buffers for `hpel_rows`, reused across bands.+#[derive(Default)]+struct HpelScratch {+    hs: Vec<i16>,+    ext: Vec<u8>,+}++fn hpel_rows(f: &Plane, r0: usize, r1: usize, hout: &mut [u8], vout: &mut [u8], cout: &mut [u8], scratch: &mut HpelScratch) {     let stride = f.stride;     let rows = f.data.len() / stride;     let clampr = |r: isize| r.clamp(0, rows as isize - 1) as usize;     // Horizontal intermediate (unclipped, unshifted) for rows r0-2 .. r1+3.     let nrows = r1 - r0 + 5;-    let mut hs = vec![0i16; nrows * stride];-    let mut ext = vec![0u8; stride + 5];+    scratch.hs.resize(nrows * stride, 0);+    scratch.ext.resize(stride + 5, 0);+    let (hs, ext) = (&mut scratch.hs[..], &mut scratch.ext[..]);     for k in 0..nrows {         let r = clampr(r0 as isize - 2 + k as isize);         let row = &f.data[r * stride..r * stride + stride];@@ -198,8 +207,32 @@ impl RefPic {         }     } +    /// Luma prediction of a w x h block at (x, y) displaced by `mv`, as (samples, stride). Full- and half-sample+    /// positions are read straight from their plane; other positions are interpolated into `buf` (stride 16).+    #[inline]+    pub fn luma_ref<'s>(&'s self, x: usize, y: usize, mv: Mv, w: usize, h: usize, buf: &'s mut [u8]) -> (&'s [u8], usize) {+        let [(pa, dxa, dya), (pb, dxb, dyb)] = QPEL_SRC[((mv.y & 3) * 4 + (mv.x & 3)) as usize];+        if pa == pb && dxa == dxb && dya == dyb {+            let a = self.plane(pa);+            let i = a.idx(x as isize + (mv.x as isize >> 2) + dxa as isize, y as isize + (mv.y as isize >> 2) + dya as isize);+            (&a.data[i..], a.stride)+        } else {+            self.mc_luma(x, y, mv, w, h, buf, 16);+            (buf, 16)+        }+    }+     /// Luma prediction of a w x h block at picture position (x, y) displaced by `mv` (quarter samples).     pub fn mc_luma(&self, x: usize, y: usize, mv: Mv, w: usize, h: usize, out: &mut [u8], ostride: usize) {+        match w {+            16 => self.mc_luma_w(x, y, mv, 16, h, out, ostride),+            8 => self.mc_luma_w(x, y, mv, 8, h, out, ostride),+            _ => self.mc_luma_w(x, y, mv, w, h, out, ostride),+        }+    }++    #[inline(always)]+    fn mc_luma_w(&self, x: usize, y: usize, mv: Mv, w: usize, h: usize, out: &mut [u8], ostride: usize) {         let xi = x as isize + (mv.x as isize >> 2);         let yi = y as isize + (mv.y as isize >> 2);         let q = ((mv.y & 3) * 4 + (mv.x & 3)) as usize;@@ -227,6 +260,15 @@ impl RefPic {      /// Chroma prediction (both planes) of a w x h chroma block at chroma position (x, y); mv in luma quarter units.     pub fn mc_chroma(&self, x: usize, y: usize, mv: Mv, w: usize, h: usize, out_u: &mut [u8], out_v: &mut [u8], ostride: usize) {+        match w {+            8 => self.mc_chroma_w(x, y, mv, 8, h, out_u, out_v, ostride),+            4 => self.mc_chroma_w(x, y, mv, 4, h, out_u, out_v, ostride),+            _ => self.mc_chroma_w(x, y, mv, w, h, out_u, out_v, ostride),+        }+    }++    #[inline(always)]+    fn mc_chroma_w(&self, x: usize, y: usize, mv: Mv, w: usize, h: usize, out_u: &mut [u8], out_v: &mut [u8], ostride: usize) {         let xi = x as isize + (mv.x as isize >> 3);         let yi = y as isize + (mv.y as isize >> 3);         let fx = (mv.x & 7) as u32;diff --git a/crates/h264enc/src/slice.rs b/crates/h264enc/src/slice.rs--- a/crates/h264enc/src/slice.rs+++ b/crates/h264enc/src/slice.rs@@ -3,7 +3,7 @@ use filmcraft_bitstream::BitWriter;  use crate::cabac::{BinSink, CabacEncoder, CabacEstimator, Contexts, CostTable};-use crate::dsp::{sad, sad16, satd, ssd};+use crate::dsp::{sad, sad8, sad16, satd, ssd}; use crate::intra::{Edge4, Edge8, filter8, mode_available, pred_chroma, pred4x4, pred8x8, pred16x16}; use crate::mbinfo::{MbInfo, MbKind, Mv, Part}; use crate::nal::{SliceHeader, SliceType, nal};diff --git a/crates/h264enc/src/slice/motion.rs b/crates/h264enc/src/slice/motion.rs--- a/crates/h264enc/src/slice/motion.rs+++ b/crates/h264enc/src/slice/motion.rs@@ -44,7 +44,11 @@ impl<'a, 'b> SliceEnc<'a, 'b> {         let p = self.f.p;         let fcost = |mv: Mv| -> u32 {             let i = pl.idx(bx as isize + (mv.x as isize >> 2), by as isize + (mv.y as isize >> 2));-            let d = if w == 16 { sad16(src, sstride, &pl.data[i..], pl.stride, h) } else { sad(src, sstride, &pl.data[i..], pl.stride, w, h) };+            let d = match w {+                16 => sad16(src, sstride, &pl.data[i..], pl.stride, h),+                8 => sad8(src, sstride, &pl.data[i..], pl.stride, h),+                _ => sad(src, sstride, &pl.data[i..], pl.stride, w, h),+            };             d + ((lam * mv_bits(mv, mvp)) >> 8)         };         let mut best = Mv::new((mvp.x as i32 + 2) & !3, (mvp.y as i32 + 2) & !3);@@ -113,8 +117,8 @@ impl<'a, 'b> SliceEnc<'a, 'b> {         // sub-pel refinement with SATD         let mut buf = [0u8; 256];         let scost = |mv: Mv, buf: &mut [u8; 256]| -> u32 {-            r.mc_luma(bx, by, mv, w, h, buf, 16);-            satd(src, sstride, buf, 16, w, h) + ((lam * mv_bits(mv, mvp)) >> 8)+            let (pr, ps) = r.luma_ref(bx, by, mv, w, h, buf);+            satd(src, sstride, pr, ps, w, h) + ((lam * mv_bits(mv, mvp)) >> 8)         };         let mut bs = scost(best, &mut buf);         let mut seen = Visited::<2>::new(best, 4, 1);