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
@@ -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;@@ -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;@@ -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};@@ -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);