Files
deckcraft/crates/hevc/src/inter.rs
T
Dylan M. TaylorandClaude Sonnet 5.5 d94790d97a refactor: replace redundant closures with method paths (SonarCloud S1612) (#1261)
Skipped where the receiver type is generic or not evident without a compiler (wrapping_neg, total_cmp, exists, to_vec, as_ref, split_first, Option::into_inner).

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:27:07 -04:00

196 lines
6.8 KiB
Rust

//! Fractional sample interpolation (8.5.3.3.3) and weighted sample prediction (8.5.3.3.4).
use crate::picture::Frame;
use crate::spec_tables::{CHROMA_FILTER, LUMA_FILTER};
/// Max prediction block size + filter margin.
const WIN: usize = 64 + 7;
/// Reference windows and first-pass rows, reused between blocks (no per-block zeroing).
pub struct McScratch {
win: Vec<i16>,
tmp: Vec<i16>,
}
impl Default for McScratch {
fn default() -> Self {
McScratch { win: vec![0; WIN * WIN], tmp: vec![0; WIN * WIN] }
}
}
/// `out[j] = (sum_k f[k] * src[j + k * step]) >> shift` for `W` outputs: taps outer, outputs
/// inner, so each tap is one vector multiply-add across the row (exact integer sums).
#[inline(always)]
fn filt_w<const T: usize, const W: usize>(src: &[i16], step: usize, f: &[i8; T], shift: u32, out: &mut [i16]) {
let mut acc = [0i32; W];
for k in 0..T {
let c = f[k] as i32;
let Some(p) = src.get(k * step..).and_then(<[_]>::first_chunk::<W>) else {
return;
};
for j in 0..W {
acc[j] += c * p[j] as i32;
}
}
let Some(o) = out.first_chunk_mut::<W>() else {
return;
};
for j in 0..W {
o[j] = (acc[j] >> shift) as i16;
}
}
/// [`filt_w`] for a run of `w` outputs (prediction block widths are fixed sizes).
#[inline(always)]
fn filt<const T: usize>(src: &[i16], step: usize, f: &[i8; T], shift: u32, out: &mut [i16], w: usize) {
match w {
2 => filt_w::<T, 2>(src, step, f, shift, out),
4 => filt_w::<T, 4>(src, step, f, shift, out),
6 => filt_w::<T, 6>(src, step, f, shift, out),
8 => filt_w::<T, 8>(src, step, f, shift, out),
12 => filt_w::<T, 12>(src, step, f, shift, out),
16 => filt_w::<T, 16>(src, step, f, shift, out),
24 => filt_w::<T, 24>(src, step, f, shift, out),
32 => filt_w::<T, 32>(src, step, f, shift, out),
48 => filt_w::<T, 48>(src, step, f, shift, out),
64 => filt_w::<T, 64>(src, step, f, shift, out),
_ => {
for (j, o) in out[..w].iter_mut().enumerate() {
let s: i32 = (0..T).map(|k| f[k] as i32 * src[j + k * step] as i32).sum();
*o = (s >> shift) as i16;
}
}
}
}
/// Separable sub-sample interpolation of a w x h block from `win` (stride WIN, the block's
/// top-left sample at (m, m) for a T-tap filter with m = T / 2 - 1).
#[allow(clippy::too_many_arguments)]
fn interpolate<const T: usize>(
win: &[i16],
tmp: &mut [i16],
fx: usize,
fy: usize,
hf: &[i8; T],
vf: &[i8; T],
w: usize,
h: usize,
shift1: u32,
shift3: u32,
out: &mut [i16],
) {
let m = T / 2 - 1;
if fx == 0 && fy == 0 {
for r in 0..h {
for (o, &s) in out[r * w..r * w + w].iter_mut().zip(&win[(r + m) * WIN + m..]) {
*o = s << shift3;
}
}
} else if fy == 0 {
for r in 0..h {
filt(&win[(r + m) * WIN..], 1, hf, shift1, &mut out[r * w..], w);
}
} else if fx == 0 {
for r in 0..h {
filt(&win[r * WIN + m..], WIN, vf, shift1, &mut out[r * w..], w);
}
} else {
for r in 0..h + T - 1 {
filt(&win[r * WIN..], 1, hf, shift1, &mut tmp[r * WIN..], w);
}
for r in 0..h {
filt(&tmp[r * WIN..], WIN, vf, 6, &mut out[r * w..], w);
}
}
}
/// Luma prediction samples (14-bit intermediate precision) of a w x h block at (x, y) displaced by the
/// quarter-sample motion vector `mv`, into `out` (stride w).
#[allow(clippy::too_many_arguments)]
pub fn mc_luma(f: &Frame, x: i32, y: i32, mv: [i16; 2], w: usize, h: usize, out: &mut [i16], sc: &mut McScratch) {
let bd = f.bit_depth;
let (fx, fy) = ((mv[0] & 3) as usize, (mv[1] & 3) as usize);
let xi = x + (mv[0] as i32 >> 2);
let yi = y + (mv[1] as i32 >> 2);
let shift1 = bd.min(12) - 8;
let shift3 = 14 - bd;
f.luma_window(xi - 3, yi - 3, w + 7, h + 7, &mut sc.win, WIN);
interpolate::<8>(&sc.win, &mut sc.tmp, fx, fy, &LUMA_FILTER[fx], &LUMA_FILTER[fy], w, h, shift1, shift3, out);
}
/// Chroma prediction samples of a w x h chroma block at chroma position (x, y) with a 1/8-sample
/// vector (4:2:0).
#[allow(clippy::too_many_arguments)]
pub fn mc_chroma(f: &Frame, c: usize, x: i32, y: i32, mv: [i16; 2], w: usize, h: usize, out: &mut [i16], sc: &mut McScratch) {
let bd = f.bit_depth_c;
let (fx, fy) = ((mv[0] & 7) as usize, (mv[1] & 7) as usize);
let xi = x + (mv[0] as i32 >> 3);
let yi = y + (mv[1] as i32 >> 3);
let shift1 = bd.min(12) - 8;
let shift3 = 14 - bd;
f.chroma_window(c, xi - 1, yi - 1, w + 3, h + 3, &mut sc.win, WIN);
interpolate::<4>(&sc.win, &mut sc.tmp, fx, fy, &CHROMA_FILTER[fx], &CHROMA_FILTER[fy], w, h, shift1, shift3, out);
}
/// Default weighted prediction, one list (8-262).
pub fn put_uni(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usize) {
let shift = 14 - bd;
let off = if shift > 0 { 1 << (shift - 1) } else { 0 };
let max = (1i32 << bd) - 1;
// Rows as slices and min / max clipping (`clamp` asserts its bounds) so the loop vectorises.
for (d, s) in dst.chunks_mut(ds).zip(p[..w * h].chunks(w)) {
for (d, &s) in d[..w].iter_mut().zip(s) {
*d = ((s as i32 + off) >> shift).max(0).min(max) as u16;
}
}
}
/// Default weighted prediction, bi-prediction (8-263).
pub fn put_bi(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usize) {
let shift = 15 - bd;
let off = 1 << (shift - 1);
let max = (1i32 << bd) - 1;
for ((d, a), b) in dst.chunks_mut(ds).zip(p0[..w * h].chunks(w)).zip(p1[..w * h].chunks(w)) {
for ((d, &a), &b) in d[..w].iter_mut().zip(a).zip(b) {
*d = ((a as i32 + b as i32 + off) >> shift).max(0).min(max) as u16;
}
}
}
/// Explicit weighted prediction, one list (8-265). `o` is the offset already scaled to the bit depth.
pub fn put_weighted_uni(p: &[i16], w: usize, h: usize, bd: u32, log2wd: u32, wt: i32, o: i32, dst: &mut [u16], ds: usize) {
let max = (1i32 << bd) - 1;
for r in 0..h {
for c in 0..w {
let v = p[r * w + c] as i32 * wt;
let v = if log2wd >= 1 { ((v + (1 << (log2wd - 1))) >> log2wd) + o } else { v + o };
dst[r * ds + c] = v.max(0).min(max) as u16;
}
}
}
/// Explicit weighted bi-prediction (8-266).
pub fn put_weighted_bi(
p0: &[i16],
p1: &[i16],
w: usize,
h: usize,
bd: u32,
log2wd: u32,
w0: i32,
w1: i32,
o0: i32,
o1: i32,
dst: &mut [u16],
ds: usize,
) {
let max = (1i32 << bd) - 1;
for r in 0..h {
for c in 0..w {
let i = r * w + c;
let v = (p0[i] as i32 * w0 + p1[i] as i32 * w1 + ((o0 + o1 + 1) << log2wd)) >> (log2wd + 1);
dst[r * ds + c] = v.max(0).min(max) as u16;
}
}
}