From a47b90e7bfbd7d4e3548ccf61b89bf9d7f83500a Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 30 Sep 2026 06:57:35 -0400 Subject: [PATCH] M2 perf: skip internal deblocking edges of uniform-motion macroblocks Co-Authored-By: Claude Opus 5.5 --- crates/h264/README.md | 4 ++-- crates/h264/src/deblock.rs | 8 ++++++++ crates/h264/src/slicedec.rs | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/h264/README.md b/crates/h264/README.md index 8b76198e9..ec9897282 100644 --- a/crates/h264/README.md +++ b/crates/h264/README.md @@ -143,8 +143,8 @@ Pro (14 cores) while the machine was heavily shared (load average 40-180), so th | threads | fps | |---|---| -| 1 | ~100-115 (≈ 36 Mcycles per frame) | -| 14 | ~570-600 | +| 1 | ~105-122 (≈ 33 Mcycles per frame) | +| 14 | ~500-600 | The example `cargo run --release -p filmcraft-h264 --example h264dec -- in.h264 out.yuv` decodes a file; `H264_BENCH_ITERS=n` (and `H264_THREADS=t`) turns it into a benchmark, `H264_STATS=1` prints diff --git a/crates/h264/src/deblock.rs b/crates/h264/src/deblock.rs index 9ef61cfb9..015bbc5fb 100644 --- a/crates/h264/src/deblock.rs +++ b/crates/h264/src/deblock.rs @@ -231,6 +231,10 @@ pub fn deblock_mb(pic: &mut PicState, addr: usize, mb_w: usize) { } } else { let qids = ref_ids8(q, sq); + let q_uniform = (0..2).all(|l| { + let r = q.ref_idx[l]; + r[1] == r[0] && r[2] == r[0] && r[3] == r[0] && q.mv[l].iter().all(|m| *m == q.mv[l][0]) + }); let qm: [Motion; 16] = std::array::from_fn(|r| block_motion(q, &qids, r)); for dir in 0..2 { let neighbor = if dir == 0 { left } else { top }; @@ -247,6 +251,10 @@ pub fn deblock_mb(pic: &mut PicState, addr: usize, mb_w: usize) { } } } + if q_uniform && q.nz_mask == 0 { + // one motion for the whole MB and no coefficients: all internal edges have bS 0 + continue; + } for e in 1..4 { if t8 && e % 2 == 1 { continue; diff --git a/crates/h264/src/slicedec.rs b/crates/h264/src/slicedec.rs index 3edbfc79c..75f192b4b 100644 --- a/crates/h264/src/slicedec.rs +++ b/crates/h264/src/slicedec.rs @@ -64,7 +64,10 @@ impl PicState { /// Reuse the buffers of a finished picture of the same size for a new frame. pub fn reset(&mut self, frame: FrameRef) { debug_assert_eq!((self.mb_w, self.mb_h), (frame.mb_w, frame.mb_h())); - self.mbs.fill(MbState::default()); + // start_mb() re-initialises each macroblock before use; only availability must be reset + for m in &mut self.mbs { + m.slice_num = u32::MAX; + } self.slices.clear(); self.poc = frame.poc; self.decoded_mbs = 0;