#[inline]
pub fn median3(a: i32, b: i32, c: i32) -> i32 {
a + b + c - a.min(b).min(c) - a.max(b).max(c)
}
#[derive(Clone, Copy)]
pub struct MvNeighbor {
pub available: bool,
pub mv: (i32, i32),
pub ref_idx: i32,
}
impl MvNeighbor {
pub const NONE: MvNeighbor = MvNeighbor {
available: false,
mv: (0, 0),
ref_idx: -1,
};
}
pub fn predict_mv(a: MvNeighbor, b: MvNeighbor, c: MvNeighbor, cur_ref: i32) -> (i32, i32) {
let resolve = |n: MvNeighbor| -> ((i32, i32), i32) {
if n.available && n.ref_idx >= 0 {
(n.mv, n.ref_idx)
} else {
((0, 0), -1)
}
};
let (mva, ra) = resolve(a);
let (mut mvb, mut rb) = resolve(b);
let (mut mvc, mut rc) = resolve(c);
if !b.available && !c.available && a.available {
mvb = mva;
rb = ra;
mvc = mva;
rc = ra;
}
let matches = (ra == cur_ref) as i32 + (rb == cur_ref) as i32 + (rc == cur_ref) as i32;
if matches == 1 {
if ra == cur_ref {
mva
} else if rb == cur_ref {
mvb
} else {
mvc
}
} else {
(median3(mva.0, mvb.0, mvc.0), median3(mva.1, mvb.1, mvc.1))
}
}
pub fn predict_partition_mv(
mode: u8,
part: usize,
a: MvNeighbor,
b: MvNeighbor,
c: MvNeighbor,
cur_ref: i32,
) -> (i32, i32) {
let m = |n: MvNeighbor| n.available && n.ref_idx == cur_ref;
match (mode, part) {
(1, 0) if m(b) => b.mv, (1, 1) if m(a) => a.mv, (2, 0) if m(a) => a.mv, (2, 1) if m(c) => c.mv, _ => predict_mv(a, b, c, cur_ref),
}
}
pub fn inter_partitions(mode: u8) -> &'static [(usize, usize, usize, usize)] {
match mode {
1 => &[(0, 0, 16, 8), (0, 8, 16, 8)], 2 => &[(0, 0, 8, 16), (8, 0, 8, 16)], 3 => &[(0, 0, 8, 8), (8, 0, 8, 8), (0, 8, 8, 8), (8, 8, 8, 8)],
_ => &[(0, 0, 16, 16)], }
}
#[inline]
fn at(reference: &[u8], cw: usize, ch: usize, x: isize, y: isize) -> i32 {
let xx = x.clamp(0, cw as isize - 1) as usize;
let yy = y.clamp(0, ch as isize - 1) as usize;
reference.get(yy * cw + xx).copied().unwrap_or(0) as i32
}
#[inline]
fn clip_u8(v: i32) -> i32 {
v.clamp(0, 255)
}
#[cfg(test)]
fn luma_sample(reference: &[u8], cw: usize, ch: usize, ix: isize, iy: isize, fx: i32, fy: i32) -> i32 {
let g = |dx: isize, dy: isize| at(reference, cw, ch, ix + dx, iy + dy);
if fx == 0 && fy == 0 {
return g(0, 0);
}
let hor6 = |dy: isize| g(-2, dy) - 5 * g(-1, dy) + 20 * g(0, dy) + 20 * g(1, dy) - 5 * g(2, dy) + g(3, dy);
let ver6 = |dx: isize| g(dx, -2) - 5 * g(dx, -1) + 20 * g(dx, 0) + 20 * g(dx, 1) - 5 * g(dx, 2) + g(dx, 3);
let b = || clip_u8((hor6(0) + 16) >> 5);
let h = || clip_u8((ver6(0) + 16) >> 5);
let m = || clip_u8((ver6(1) + 16) >> 5);
let s = || clip_u8((hor6(1) + 16) >> 5);
let j = || {
let j1 = hor6(-2) - 5 * hor6(-1) + 20 * hor6(0) + 20 * hor6(1) - 5 * hor6(2) + hor6(3);
clip_u8((j1 + 512) >> 10)
};
match (fx, fy) {
(1, 0) => (g(0, 0) + b() + 1) >> 1,
(2, 0) => b(),
(3, 0) => (g(1, 0) + b() + 1) >> 1,
(0, 1) => (g(0, 0) + h() + 1) >> 1,
(1, 1) => (b() + h() + 1) >> 1,
(2, 1) => (b() + j() + 1) >> 1,
(3, 1) => (b() + m() + 1) >> 1,
(0, 2) => h(),
(1, 2) => (h() + j() + 1) >> 1,
(2, 2) => j(),
(3, 2) => (j() + m() + 1) >> 1,
(0, 3) => (g(0, 1) + h() + 1) >> 1,
(1, 3) => (h() + s() + 1) >> 1,
(2, 3) => (j() + s() + 1) >> 1,
_ => (m() + s() + 1) >> 1,
}
}
const LUMA_TILE: usize = 21;
struct McScratch {
tile: [u8; LUMA_TILE * LUMA_TILE],
a: [u8; 256],
b: [u8; 256],
}
thread_local! {
static MC_SCRATCH: core::cell::RefCell<McScratch> = const {
core::cell::RefCell::new(McScratch {
tile: [0; LUMA_TILE * LUMA_TILE],
a: [0; 256],
b: [0; 256],
})
};
}
#[cfg(feature = "profile")]
pub mod mcstats {
use core::sync::atomic::{AtomicU64, Ordering};
pub const SITES: [&str; 5] = ["untagged", "recon", "search-fallback", "skip-check", "bdirect"];
pub static SITE_COUNTS: [AtomicU64; 5] = [const { AtomicU64::new(0) }; 5];
pub static SITE_CYCLES: [AtomicU64; 5] = [const { AtomicU64::new(0) }; 5];
thread_local! {
pub static SITE: core::cell::Cell<usize> = const { core::cell::Cell::new(0) };
}
pub struct SiteTag(usize);
impl SiteTag {
pub fn new(s: usize) -> Self {
let prev = SITE.with(|c| c.replace(s));
SiteTag(prev)
}
}
impl Drop for SiteTag {
fn drop(&mut self) {
SITE.with(|c| c.set(self.0));
}
}
#[inline]
pub(super) fn site_add(c: u64) {
let s = SITE.with(|c| c.get());
SITE_COUNTS[s].fetch_add(1, Ordering::Relaxed);
SITE_CYCLES[s].fetch_add(c, Ordering::Relaxed);
}
pub fn site_snapshot() -> Vec<(&'static str, u64, u64)> {
(0..5).map(|i| (SITES[i], SITE_COUNTS[i].load(Ordering::Relaxed),
SITE_CYCLES[i].load(Ordering::Relaxed))).collect()
}
pub const PHASES: [&str; 4] = ["fullpel", "half-HV", "half-ctr", "quarter"];
pub const SIZES: [&str; 6] = ["16x16", "16x8/8x16", "8x8", "8x4/4x8", "4x4", "other"];
pub static COUNTS: [AtomicU64; 24] = [const { AtomicU64::new(0) }; 24];
pub static CYCLES: [AtomicU64; 24] = [const { AtomicU64::new(0) }; 24];
#[inline]
pub(super) fn bucket(bw: usize, bh: usize, fx: i32, fy: i32) -> usize {
let size = match (bw, bh) {
(16, 16) => 0,
(16, 8) | (8, 16) => 1,
(8, 8) => 2,
(8, 4) | (4, 8) => 3,
(4, 4) => 4,
_ => 5,
};
let phase = match (fx, fy) {
(0, 0) => 0,
(2, 2) => 2,
(fx, fy) if fx % 2 == 0 && fy % 2 == 0 => 1,
_ => 3,
};
size * 4 + phase
}
#[inline]
pub(super) fn add_cycles(b: usize, c: u64) {
CYCLES[b].fetch_add(c, Ordering::Relaxed);
}
#[inline]
pub(super) fn record(bw: usize, bh: usize, fx: i32, fy: i32) {
let size = match (bw, bh) {
(16, 16) => 0,
(16, 8) | (8, 16) => 1,
(8, 8) => 2,
(8, 4) | (4, 8) => 3,
(4, 4) => 4,
_ => 5,
};
let phase = match (fx, fy) {
(0, 0) => 0,
(2, 2) => 2,
(fx, fy) if fx % 2 == 0 && fy % 2 == 0 => 1,
_ => 3,
};
COUNTS[size * 4 + phase].fetch_add(1, Ordering::Relaxed);
}
pub fn reset() {
for c in COUNTS.iter() {
c.store(0, Ordering::Relaxed);
}
for c in CYCLES.iter() {
c.store(0, Ordering::Relaxed);
}
for c in SITE_COUNTS.iter() {
c.store(0, Ordering::Relaxed);
}
for c in SITE_CYCLES.iter() {
c.store(0, Ordering::Relaxed);
}
}
pub fn snapshot_cycles() -> Vec<(&'static str, &'static str, u64, u64)> {
let mut v = Vec::new();
for i in 0..24 {
let n = COUNTS[i].load(Ordering::Relaxed);
if n != 0 {
v.push((SIZES[i / 4], PHASES[i % 4], n, CYCLES[i].load(Ordering::Relaxed)));
}
}
v
}
pub fn snapshot() -> Vec<(&'static str, &'static str, u64)> {
let mut v = Vec::new();
for (i, c) in COUNTS.iter().enumerate() {
let n = c.load(Ordering::Relaxed);
if n > 0 {
v.push((SIZES[i / 4], PHASES[i % 4], n));
}
}
v
}
}
fn luma_tile_into(
t: &mut [u8],
reference: &[u8],
cw: usize,
ch: usize,
ix0: isize,
iy0: isize,
bw: usize,
bh: usize,
) -> usize {
let ts = bw + 5;
if ix0 - 2 >= 0
&& iy0 - 2 >= 0
&& ix0 - 2 + ts as isize <= cw as isize
&& iy0 - 2 + (bh + 5) as isize <= ch as isize
&& reference.len() >= cw * ch
{
let (rx0, ry0) = ((ix0 - 2) as usize, (iy0 - 2) as usize);
for ty in 0..bh + 5 {
let src = (ry0 + ty) * cw + rx0;
t[ty * ts..ty * ts + ts].copy_from_slice(&reference[src..src + ts]);
}
return ts;
}
for ty in 0..bh + 5 {
let ry = (iy0 - 2 + ty as isize).clamp(0, ch as isize - 1) as usize * cw;
for tx in 0..ts {
let rx = (ix0 - 2 + tx as isize).clamp(0, cw as isize - 1) as usize;
t[ty * ts + tx] = reference.get(ry + rx).copied().unwrap_or(0);
}
}
ts
}
fn luma_h(t: &[u8], ts: usize, bw: usize, bh: usize, dr: usize, dc: usize, dst: &mut [u8]) {
#[cfg(accel)]
if bw == 16 || bw == 8 {
rusty_h264_accel::mc_hor20(t, (2 + dr) * ts + 2 + dc, ts, dst, bw, bh);
return;
}
for r in 0..bh {
let base = (2 + r + dr) * ts + 2 + dc;
for c in 0..bw {
let p = base + c;
let f = t[p - 2] as i32 - 5 * t[p - 1] as i32 + 20 * t[p] as i32 + 20 * t[p + 1] as i32
- 5 * t[p + 2] as i32
+ t[p + 3] as i32;
dst[r * bw + c] = clip_u8((f + 16) >> 5) as u8;
}
}
}
fn luma_v(t: &[u8], ts: usize, bw: usize, bh: usize, dr: usize, dc: usize, dst: &mut [u8]) {
#[cfg(accel)]
if bw == 16 || bw == 8 {
rusty_h264_accel::mc_ver02(t, (2 + dr) * ts + 2 + dc, ts, dst, bw, bh);
return;
}
for r in 0..bh {
let base = (2 + r + dr) * ts + 2 + dc;
for c in 0..bw {
let p = base + c;
let f = t[p - 2 * ts] as i32 - 5 * t[p - ts] as i32 + 20 * t[p] as i32
+ 20 * t[p + ts] as i32
- 5 * t[p + 2 * ts] as i32
+ t[p + 3 * ts] as i32;
dst[r * bw + c] = clip_u8((f + 16) >> 5) as u8;
}
}
}
fn luma_centre(t: &[u8], ts: usize, bw: usize, bh: usize, dst: &mut [u8]) {
#[cfg(accel)]
if bw == 16 || bw == 8 {
rusty_h264_accel::mc_centre(t, ts, dst, bw, bh);
return;
}
let mut itmp = [0i32; LUMA_TILE];
for r in 0..bh {
let base = (2 + r) * ts;
for (j, slot) in itmp[..bw + 5].iter_mut().enumerate() {
let p = base + j;
*slot = t[p - 2 * ts] as i32 - 5 * t[p - ts] as i32 + 20 * t[p] as i32
+ 20 * t[p + ts] as i32
- 5 * t[p + 2 * ts] as i32
+ t[p + 3 * ts] as i32;
}
for c in 0..bw {
let f = itmp[c] - 5 * itmp[c + 1] + 20 * itmp[c + 2] + 20 * itmp[c + 3] - 5 * itmp[c + 4]
+ itmp[c + 5];
dst[r * bw + c] = clip_u8((f + 512) >> 10) as u8;
}
}
}
fn pixel_avg(a: &[u8], b: &[u8], n: usize, dst: &mut [u8]) {
for i in 0..n {
dst[i] = ((a[i] as i32 + b[i] as i32 + 1) >> 1) as u8;
}
}
fn avg_full(t: &[u8], ts: usize, bw: usize, bh: usize, dr: usize, dc: usize, half: &[u8], dst: &mut [u8]) {
for r in 0..bh {
let base = (2 + r + dr) * ts + 2 + dc;
for c in 0..bw {
dst[r * bw + c] = ((t[base + c] as i32 + half[r * bw + c] as i32 + 1) >> 1) as u8;
}
}
}
#[allow(clippy::too_many_arguments)]
fn mc_luma_subpel(
t: &[u8],
ts: usize,
bw: usize,
bh: usize,
fx: i32,
fy: i32,
a: &mut [u8],
b: &mut [u8],
out: &mut [u8],
) {
let n = bw * bh;
match (fx, fy) {
(2, 0) => luma_h(t, ts, bw, bh, 0, 0, out),
(0, 2) => luma_v(t, ts, bw, bh, 0, 0, out),
(2, 2) => luma_centre(t, ts, bw, bh, out),
(1, 0) => {
luma_h(t, ts, bw, bh, 0, 0, a);
avg_full(t, ts, bw, bh, 0, 0, a, out);
}
(3, 0) => {
luma_h(t, ts, bw, bh, 0, 0, a);
avg_full(t, ts, bw, bh, 0, 1, a, out);
}
(0, 1) => {
luma_v(t, ts, bw, bh, 0, 0, a);
avg_full(t, ts, bw, bh, 0, 0, a, out);
}
(0, 3) => {
luma_v(t, ts, bw, bh, 0, 0, a);
avg_full(t, ts, bw, bh, 1, 0, a, out);
}
(1, 1) => {
luma_h(t, ts, bw, bh, 0, 0, a);
luma_v(t, ts, bw, bh, 0, 0, b);
pixel_avg(a, b, n, out);
}
(3, 1) => {
luma_h(t, ts, bw, bh, 0, 0, a);
luma_v(t, ts, bw, bh, 0, 1, b);
pixel_avg(a, b, n, out);
}
(1, 3) => {
luma_h(t, ts, bw, bh, 1, 0, a);
luma_v(t, ts, bw, bh, 0, 0, b);
pixel_avg(a, b, n, out);
}
(3, 3) => {
luma_h(t, ts, bw, bh, 1, 0, a);
luma_v(t, ts, bw, bh, 0, 1, b);
pixel_avg(a, b, n, out);
}
(2, 1) => {
luma_h(t, ts, bw, bh, 0, 0, a);
luma_centre(t, ts, bw, bh, b);
pixel_avg(a, b, n, out);
}
(2, 3) => {
luma_h(t, ts, bw, bh, 1, 0, a);
luma_centre(t, ts, bw, bh, b);
pixel_avg(a, b, n, out);
}
(1, 2) => {
luma_v(t, ts, bw, bh, 0, 0, a);
luma_centre(t, ts, bw, bh, b);
pixel_avg(a, b, n, out);
}
(3, 2) => {
luma_v(t, ts, bw, bh, 0, 1, a);
luma_centre(t, ts, bw, bh, b);
pixel_avg(a, b, n, out);
}
_ => unreachable!("(0,0) is the full-pel path"),
}
}
#[derive(Clone, Debug)]
pub struct HpelPlanes {
pub f: Vec<u8>,
pub h: Vec<u8>,
pub v: Vec<u8>,
pub c: Vec<u8>,
pub stride: usize,
pub pad: usize,
pub pw: usize,
pub ph: usize,
pub cw: usize,
pub ch: usize,
}
pub const HPEL_PAD_DEFAULT: usize = 32;
pub fn hpel_pad() -> usize {
use std::sync::OnceLock;
static P: OnceLock<usize> = OnceLock::new();
*P.get_or_init(|| {
std::env::var("RFF_HPEL_PAD")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(HPEL_PAD_DEFAULT)
.clamp(0, 128)
})
}
pub fn pad_plane(src: &[u8], w: usize, h: usize, pad: usize) -> Vec<u8> {
let (pw, ph) = (w + 2 * pad, h + 2 * pad);
let mut f = vec![0u8; pw * ph];
for y in 0..ph {
let sy = (y as isize - pad as isize).clamp(0, h as isize - 1) as usize;
let row = &src[sy * w..sy * w + w];
let d = &mut f[y * pw..y * pw + pw];
d[..pad].fill(row[0]);
d[pad..pad + w].copy_from_slice(row);
d[pad + w..].fill(row[w - 1]);
}
f
}
pub fn build_hpel_planes(reference: &[u8], cw: usize, ch: usize) -> HpelPlanes {
let _g = crate::prof::scope(crate::prof::Stage::MeHpelBuild);
let pad = hpel_pad();
let (pw, ph) = (cw + 2 * pad, ch + 2 * pad);
let f = pad_plane(reference, cw, ch, pad);
let mut h = vec![0u8; pw * ph];
let mut v = vec![0u8; pw * ph];
let mut c = vec![0u8; pw * ph];
#[cfg(accel)]
let done = !hpel_fused_forced_off() && rusty_h264_accel::hpel_fused(&f, pw, ph, &mut h, &mut v, &mut c);
#[cfg(not(accel))]
let done = false;
if !done {
if hpel_fused_enabled() {
build_hpel_fused(&f, pw, ph, &mut h, &mut v, &mut c);
} else {
build_hpel_tiles(&f, pw, ph, &mut h, &mut v, &mut c);
}
}
HpelPlanes { f, h, v, c, stride: pw, pad, pw, ph, cw, ch }
}
fn hpel_fused_enabled() -> bool {
use std::sync::OnceLock;
static E: OnceLock<bool> = OnceLock::new();
*E.get_or_init(|| std::env::var("RFF_HPEL_FUSED").map(|s| s == "1").unwrap_or(false))
}
#[cfg(accel)]
fn hpel_fused_forced_off() -> bool {
use std::sync::OnceLock;
static E: OnceLock<bool> = OnceLock::new();
*E.get_or_init(|| std::env::var("RFF_HPEL_AVX2").map(|s| s == "0").unwrap_or(false))
}
fn build_hpel_tiles(f: &[u8], pw: usize, ph: usize, h: &mut [u8], v: &mut [u8], c: &mut [u8]) {
let mut tile = [0u8; LUMA_TILE * LUMA_TILE];
let mut blk = [0u8; 256];
let mut by = 0;
while by < ph {
let mut bx = 0;
while bx < pw {
let bh = 16.min(ph - by);
let bwid = 16.min(pw - bx);
let ts = luma_tile_into(&mut tile, f, pw, ph, bx as isize, by as isize, bwid, bh);
for kind in 0u8..3 {
let plane: &mut [u8] = match kind {
0 => &mut *h,
1 => &mut *v,
_ => &mut *c,
};
match kind {
0 => luma_h(&tile, ts, bwid, bh, 0, 0, &mut blk),
1 => luma_v(&tile, ts, bwid, bh, 0, 0, &mut blk),
_ => luma_centre(&tile, ts, bwid, bh, &mut blk),
}
for r in 0..bh {
plane[(by + r) * pw + bx..][..bwid].copy_from_slice(&blk[r * bwid..][..bwid]);
}
}
bx += 16;
}
by += 16;
}
}
fn build_hpel_fused(f: &[u8], pw: usize, ph: usize, h: &mut [u8], v: &mut [u8], c: &mut [u8]) {
let cl = |i: isize, hi: usize| i.clamp(0, hi as isize - 1) as usize;
let mut vt = vec![0i32; pw + 5]; let mut hb = vec![0u8; pw + 5]; for y in 0..ph {
let (ym2, ym1, y0, yp1, yp2, yp3) = (
cl(y as isize - 2, ph) * pw,
cl(y as isize - 1, ph) * pw,
y * pw,
cl(y as isize + 1, ph) * pw,
cl(y as isize + 2, ph) * pw,
cl(y as isize + 3, ph) * pw,
);
for j in 0..2 {
let x = 0usize;
vt[j] = f[ym2 + x] as i32 - 5 * f[ym1 + x] as i32 + 20 * f[y0 + x] as i32
+ 20 * f[yp1 + x] as i32
- 5 * f[yp2 + x] as i32
+ f[yp3 + x] as i32;
hb[j] = f[y0 + x];
}
for j in 2..pw + 2 {
let x = j - 2;
vt[j] = f[ym2 + x] as i32 - 5 * f[ym1 + x] as i32 + 20 * f[y0 + x] as i32
+ 20 * f[yp1 + x] as i32
- 5 * f[yp2 + x] as i32
+ f[yp3 + x] as i32;
hb[j] = f[y0 + x];
}
for j in pw + 2..pw + 5 {
let x = pw - 1;
vt[j] = f[ym2 + x] as i32 - 5 * f[ym1 + x] as i32 + 20 * f[y0 + x] as i32
+ 20 * f[yp1 + x] as i32
- 5 * f[yp2 + x] as i32
+ f[yp3 + x] as i32;
hb[j] = f[y0 + x];
}
let hrow = &mut h[y0..y0 + pw];
let vrow = &mut v[y0..y0 + pw];
let crow = &mut c[y0..y0 + pw];
for x in 0..pw {
vrow[x] = clip_u8((vt[x + 2] + 16) >> 5) as u8;
}
for x in 0..pw {
let s = vt[x] - 5 * vt[x + 1] + 20 * vt[x + 2] + 20 * vt[x + 3] - 5 * vt[x + 4]
+ vt[x + 5];
crow[x] = clip_u8((s + 512) >> 10) as u8;
}
for x in 0..pw {
let s = hb[x] as i32 - 5 * hb[x + 1] as i32 + 20 * hb[x + 2] as i32
+ 20 * hb[x + 3] as i32
- 5 * hb[x + 4] as i32
+ hb[x + 5] as i32;
hrow[x] = clip_u8((s + 16) >> 5) as u8;
}
}
}
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "profile")]
pub mod hpelphase {
use core::sync::atomic::{AtomicU64, Ordering};
pub static C: [AtomicU64; 2] = [const { AtomicU64::new(0) }; 2];
#[inline]
pub fn bump(i: usize) { C[i].fetch_add(1, Ordering::Relaxed); }
pub fn reset() { for c in C.iter() { c.store(0, Ordering::Relaxed); } }
pub fn snapshot() -> Vec<u64> { C.iter().map(|c| c.load(Ordering::Relaxed)).collect() }
}
#[inline]
pub fn hpel_ref<'a>(
p: &'a HpelPlanes,
x0: usize,
y0: usize,
bw: usize,
bh: usize,
mvx: i32,
mvy: i32,
) -> Option<(&'a [u8], usize, usize)> {
let (fx, fy) = (mvx & 3, mvy & 3);
let plane = match (fx, fy) {
(2, 0) => &p.h,
(0, 2) => &p.v,
(2, 2) => &p.c,
(0, 0) => &p.f,
_ => return None,
};
let (ix0, iy0) = (x0 as isize + (mvx >> 2) as isize, y0 as isize + (mvy >> 2) as isize);
let (px, py) = (ix0 + p.pad as isize, iy0 + p.pad as isize);
if px < 0 || py < 0 || px + bw as isize + 1 > p.pw as isize || py + bh as isize + 1 > p.ph as isize {
return None;
}
let base = py as usize * p.stride + px as usize;
if base + (bh - 1) * p.stride + bw > plane.len() {
return None;
}
Some((plane, base, p.stride))
}
#[inline]
pub fn hpel_qpel_refs<'a>(
p: &'a HpelPlanes,
x0: usize,
y0: usize,
bw: usize,
bh: usize,
mvx: i32,
mvy: i32,
) -> Option<(&'a [u8], usize, &'a [u8], usize, usize)> {
let (fx, fy) = (mvx & 3, mvy & 3);
if fx & 1 == 0 && fy & 1 == 0 {
return None;
}
let (ix0, iy0) = (x0 as isize + (mvx >> 2) as isize, y0 as isize + (mvy >> 2) as isize);
let (px, py) = (ix0 + p.pad as isize, iy0 + p.pad as isize);
if px < 0 || py < 0 || px + bw as isize + 1 > p.pw as isize || py + bh as isize + 1 > p.ph as isize {
return None;
}
let stride = p.stride;
let base = py as usize * stride + px as usize;
let g: &[u8] = &p.f;
let (pa, oa, pb, ob): (&[u8], usize, &[u8], usize) = match (fx, fy) {
(1, 0) => (g, 0, &p.h, 0),
(3, 0) => (g, 1, &p.h, 0),
(0, 1) => (g, 0, &p.v, 0),
(0, 3) => (g, stride, &p.v, 0),
(1, 1) => (&p.h, 0, &p.v, 0),
(3, 1) => (&p.h, 0, &p.v, 1),
(1, 3) => (&p.h, stride, &p.v, 0),
(3, 3) => (&p.h, stride, &p.v, 1),
(2, 1) => (&p.h, 0, &p.c, 0),
(2, 3) => (&p.h, stride, &p.c, 0),
(1, 2) => (&p.v, 0, &p.c, 0),
_ => (&p.v, 1, &p.c, 0), };
if base + oa + (bh - 1) * stride + bw > pa.len() || base + ob + (bh - 1) * stride + bw > pb.len() {
return None;
}
Some((pa, base + oa, pb, base + ob, stride))
}
pub fn hpel_block(
p: &HpelPlanes,
x0: usize,
y0: usize,
bw: usize,
bh: usize,
mvx: i32,
mvy: i32,
out: &mut [u8],
) -> bool {
let _g = crate::prof::scope(crate::prof::Stage::MeHpel);
let (ix0, iy0) = (x0 as isize + (mvx >> 2) as isize, y0 as isize + (mvy >> 2) as isize);
let (fx, fy) = (mvx & 3, mvy & 3);
if fx == 0 && fy == 0 {
return false; }
let (px, py) = (ix0 + p.pad as isize, iy0 + p.pad as isize);
if px < 0 || py < 0 || px + bw as isize + 1 > p.pw as isize || py + bh as isize + 1 > p.ph as isize {
return false;
}
let stride = p.stride;
let base = py as usize * stride + px as usize;
let single = match (fx, fy) {
(2, 0) => Some(&p.h),
(0, 2) => Some(&p.v),
(2, 2) => Some(&p.c),
_ => None,
};
#[cfg(feature = "profile")]
crate::inter::hpelphase::bump(if single.is_some() { 0 } else { 1 });
if let Some(src) = single {
for r in 0..bh {
out[r * bw..r * bw + bw].copy_from_slice(&src[base + r * stride..][..bw]);
}
return true;
}
let Some((pa, ba, pb, bb, qstride)) = hpel_qpel_refs(p, x0, y0, bw, bh, mvx, mvy) else {
return false;
};
match bw {
16 => avg_rows::<16>(pa, ba, pb, bb, qstride, bh, out),
8 => avg_rows::<8>(pa, ba, pb, bb, qstride, bh, out),
4 => avg_rows::<4>(pa, ba, pb, bb, qstride, bh, out),
_ => return false,
}
true
}
#[inline]
fn avg_rows<const BW: usize>(pa: &[u8], oa: usize, pb: &[u8], ob: usize, cw: usize, bh: usize, out: &mut [u8]) {
for r in 0..bh {
let sa = &pa[oa + r * cw..][..BW];
let sb = &pb[ob + r * cw..][..BW];
let o = &mut out[r * BW..r * BW + BW];
for i in 0..BW {
o[i] = ((sa[i] as u16 + sb[i] as u16 + 1) >> 1) as u8;
}
}
}
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "profile")]
struct McCycleGuard {
b: usize,
t: u64,
}
#[cfg(feature = "profile")]
impl Drop for McCycleGuard {
fn drop(&mut self) {
let c = crate::prof::tick().wrapping_sub(self.t);
mcstats::add_cycles(self.b, c);
mcstats::site_add(c);
}
}
pub fn mc_luma(
reference: &[u8],
cw: usize,
ch: usize,
x0: usize,
y0: usize,
bw: usize,
bh: usize,
mvx: i32,
mvy: i32,
out: &mut [u8],
) {
let _g = crate::prof::scope(crate::prof::Stage::InterMc);
let (ix0, iy0) = (x0 as isize + (mvx >> 2) as isize, y0 as isize + (mvy >> 2) as isize);
let (fx, fy) = (mvx & 3, mvy & 3);
#[cfg(feature = "profile")]
mcstats::record(bw, bh, fx, fy);
#[cfg(feature = "profile")]
let _mcg = McCycleGuard { b: mcstats::bucket(bw, bh, fx, fy), t: crate::prof::tick() };
if fx == 0 && fy == 0 {
if ix0 >= 0
&& iy0 >= 0
&& ix0 + bw as isize <= cw as isize
&& iy0 + bh as isize <= ch as isize
&& reference.len() >= cw * ch
{
let (rx, ry) = (ix0 as usize, iy0 as usize);
if bw == 16 && bh == 16 {
for dy in 0..16 {
let s = &reference[(ry + dy) * cw + rx..];
out[dy * 16..dy * 16 + 16].copy_from_slice(&s[..16]);
}
} else {
for dy in 0..bh {
out[dy * bw..dy * bw + bw]
.copy_from_slice(&reference[(ry + dy) * cw + rx..][..bw]);
}
}
} else {
for dy in 0..bh {
for dx in 0..bw {
out[dy * bw + dx] =
at(reference, cw, ch, ix0 + dx as isize, iy0 + dy as isize) as u8;
}
}
}
return;
}
MC_SCRATCH.with(|s| {
let McScratch { tile, a, b } = &mut *s.borrow_mut();
let ts = luma_tile_into(tile, reference, cw, ch, ix0, iy0, bw, bh);
mc_luma_subpel(tile, ts, bw, bh, fx, fy, a, b, out);
});
}
#[allow(clippy::too_many_arguments)]
pub fn mc_chroma(
reference: &[u8],
cw: usize,
ch: usize,
x0: usize,
y0: usize,
bw: usize,
bh: usize,
mvx: i32,
mvy: i32,
out: &mut [u8],
) {
let _g = crate::prof::scope(crate::prof::Stage::InterMc);
let (ix0, iy0) = (x0 as isize + (mvx >> 3) as isize, y0 as isize + (mvy >> 3) as isize);
let (fx, fy) = (mvx & 7, mvy & 7);
if fx == 0
&& fy == 0
&& ix0 >= 0
&& iy0 >= 0
&& ix0 + bw as isize <= cw as isize
&& iy0 + bh as isize <= ch as isize
&& reference.len() >= cw * ch
{
let (rx, ry) = (ix0 as usize, iy0 as usize);
if bw == 8 && bh == 8 {
for dy in 0..8 {
let src = &reference[(ry + dy) * cw + rx..];
out[dy * 8..dy * 8 + 8].copy_from_slice(&src[..8]);
}
} else {
for dy in 0..bh {
let src = &reference[(ry + dy) * cw + rx..][..bw];
out[dy * bw..dy * bw + bw].copy_from_slice(src);
}
}
return;
}
let ts = bw + 1;
let mut t = [0u8; 9 * 9];
if ix0 >= 0
&& iy0 >= 0
&& ix0 + ts as isize <= cw as isize
&& iy0 + (bh + 1) as isize <= ch as isize
&& reference.len() >= cw * ch
{
let (rx0, ry0) = (ix0 as usize, iy0 as usize);
for ty in 0..bh + 1 {
let src = (ry0 + ty) * cw + rx0;
t[ty * ts..ty * ts + ts].copy_from_slice(&reference[src..src + ts]);
}
} else {
for ty in 0..bh + 1 {
let ry = (iy0 + ty as isize).clamp(0, ch as isize - 1) as usize * cw;
for tx in 0..ts {
let rx = (ix0 + tx as isize).clamp(0, cw as isize - 1) as usize;
t[ty * ts + tx] = reference.get(ry + rx).copied().unwrap_or(0);
}
}
}
let (wa, wb, wc, wd) = ((8 - fx) * (8 - fy), fx * (8 - fy), (8 - fx) * fy, fx * fy);
#[cfg(accel)]
if bw == 8 {
let abcd = [wa as u8, wb as u8, wc as u8, wd as u8];
rusty_h264_accel::mc_chroma_w8(&t, ts, out, bw, &abcd, bh);
return;
}
for r in 0..bh {
for c in 0..bw {
let p = r * ts + c;
let v = wa * t[p] as i32
+ wb * t[p + 1] as i32
+ wc * t[p + ts] as i32
+ wd * t[p + ts + 1] as i32;
out[r * bw + c] = ((v + 32) >> 6) as u8;
}
}
}
pub const PAD_L: usize = 32;
pub const PAD_C: usize = 16;
pub fn expand_plane(buf: &mut [u8], stride: usize, pad: usize, pw: usize, ph: usize) {
for y in 0..ph {
let row = (y + pad) * stride;
let (left, right) = (buf[row + pad], buf[row + pad + pw - 1]);
for x in 0..pad {
buf[row + x] = left;
buf[row + pad + pw + x] = right;
}
}
let first = pad * stride;
let last = (pad + ph - 1) * stride;
for y in 0..pad {
buf.copy_within(first..first + stride, y * stride);
buf.copy_within(last..last + stride, (pad + ph + y) * stride);
}
}
#[allow(clippy::too_many_arguments)]
pub fn mc_luma_padded(
padded: &[u8],
stride: usize,
pad: usize,
pw: usize,
ph: usize,
x0: usize,
y0: usize,
bw: usize,
bh: usize,
mvx: i32,
mvy: i32,
out: &mut [u8],
) {
let (ix0, iy0) = (x0 as isize + (mvx >> 2) as isize, y0 as isize + (mvy >> 2) as isize);
let (fx, fy) = (mvx & 3, mvy & 3);
let p = pad as isize;
let (lo_x, lo_y) = (ix0 - 2, iy0 - 2);
let intact = stride >= pw + 2 * pad && padded.len() >= stride * (ph + 2 * pad);
let in_range = intact
&& lo_x >= -p
&& lo_y >= -p
&& lo_x + (bw + 5) as isize <= pw as isize + p
&& lo_y + (bh + 5) as isize <= ph as isize + p;
if in_range {
if fx == 0 && fy == 0 {
for dy in 0..bh {
let src = ((iy0 + dy as isize + p) as usize) * stride + (ix0 + p) as usize;
out[dy * bw..dy * bw + bw].copy_from_slice(&padded[src..src + bw]);
}
} else {
let halo = ((lo_y + p) as usize) * stride + (lo_x + p) as usize;
MC_SCRATCH.with(|s| {
let McScratch { a, b, .. } = &mut *s.borrow_mut();
mc_luma_subpel(&padded[halo..], stride, bw, bh, fx, fy, a, b, out);
});
}
return;
}
let ts = bw + 5;
let mut t = [0u8; LUMA_TILE * LUMA_TILE];
for ty in 0..bh + 5 {
let py = (lo_y + ty as isize).clamp(0, ph as isize - 1) as usize;
let ry = (py + pad) * stride;
for tx in 0..ts {
let px = (lo_x + tx as isize).clamp(0, pw as isize - 1) as usize;
t[ty * ts + tx] = padded.get(ry + px + pad).copied().unwrap_or(128);
}
}
if fx == 0 && fy == 0 {
for dy in 0..bh {
let s = (dy + 2) * ts + 2;
out[dy * bw..dy * bw + bw].copy_from_slice(&t[s..s + bw]);
}
} else {
MC_SCRATCH.with(|s| {
let McScratch { a, b, .. } = &mut *s.borrow_mut();
mc_luma_subpel(&t, ts, bw, bh, fx, fy, a, b, out);
});
}
}
#[allow(clippy::too_many_arguments)]
pub fn mc_chroma_padded(
padded: &[u8],
stride: usize,
pad: usize,
pw: usize,
ph: usize,
x0: usize,
y0: usize,
bw: usize,
bh: usize,
mvx: i32,
mvy: i32,
out: &mut [u8],
) {
let (ix0, iy0) = (x0 as isize + (mvx >> 3) as isize, y0 as isize + (mvy >> 3) as isize);
let (fx, fy) = (mvx & 7, mvy & 7);
let p = pad as isize;
let intact = stride >= pw + 2 * pad && padded.len() >= stride * (ph + 2 * pad);
let in_range = intact
&& ix0 >= -p
&& iy0 >= -p
&& ix0 + (bw + 1) as isize <= pw as isize + p
&& iy0 + (bh + 1) as isize <= ph as isize + p;
let (wa, wb, wc, wd) = ((8 - fx) * (8 - fy), fx * (8 - fy), (8 - fx) * fy, fx * fy);
if in_range {
if fx == 0 && fy == 0 {
for dy in 0..bh {
let src = ((iy0 + dy as isize + p) as usize) * stride + (ix0 + p) as usize;
out[dy * bw..dy * bw + bw].copy_from_slice(&padded[src..src + bw]);
}
return;
}
let halo = ((iy0 + p) as usize) * stride + (ix0 + p) as usize;
#[cfg(accel)]
{
let abcd = [wa as u8, wb as u8, wc as u8, wd as u8];
if bw == 8 {
rusty_h264_accel::mc_chroma_w8(&padded[halo..], stride, out, bw, &abcd, bh);
return;
}
if bw == 4 {
rusty_h264_accel::mc_chroma_w4(&padded[halo..], stride, out, bw, &abcd, bh);
return;
}
}
for r in 0..bh {
for c in 0..bw {
let pp = halo + r * stride + c;
let v = wa * padded[pp] as i32
+ wb * padded[pp + 1] as i32
+ wc * padded[pp + stride] as i32
+ wd * padded[pp + stride + 1] as i32;
out[r * bw + c] = ((v + 32) >> 6) as u8;
}
}
return;
}
let ts = bw + 1;
let mut t = [0u8; 9 * 9];
for ty in 0..bh + 1 {
let py = (iy0 + ty as isize).clamp(0, ph as isize - 1) as usize;
let ry = (py + pad) * stride;
for tx in 0..ts {
let px = (ix0 + tx as isize).clamp(0, pw as isize - 1) as usize;
t[ty * ts + tx] = padded.get(ry + px + pad).copied().unwrap_or(128);
}
}
for r in 0..bh {
for c in 0..bw {
let pp = r * ts + c;
let v = wa * t[pp] as i32
+ wb * t[pp + 1] as i32
+ wc * t[pp + ts] as i32
+ wd * t[pp + ts + 1] as i32;
out[r * bw + c] = ((v + 32) >> 6) as u8;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rand_plane(pw: usize, ph: usize, seed: u32) -> Vec<u8> {
let mut s = seed;
(0..pw * ph)
.map(|_| {
s = s.wrapping_mul(1103515245).wrapping_add(12345);
(s >> 16) as u8
})
.collect()
}
fn make_padded(exact: &[u8], pw: usize, ph: usize, pad: usize) -> (Vec<u8>, usize) {
let stride = pw + 2 * pad;
let mut padded = vec![0u8; stride * (ph + 2 * pad)];
for y in 0..ph {
let d = (y + pad) * stride + pad;
padded[d..d + pw].copy_from_slice(&exact[y * pw..y * pw + pw]);
}
expand_plane(&mut padded, stride, pad, pw, ph);
(padded, stride)
}
#[test]
fn mc_luma_padded_matches_exact() {
let (pw, ph) = (48usize, 32usize);
let exact = rand_plane(pw, ph, 0x77);
let (padded, stride) = make_padded(&exact, pw, ph, PAD_L);
for &(bw, bh) in &[(16usize, 16usize), (8, 8), (16, 8), (8, 16), (4, 4)] {
for x0 in [0usize, 8, pw - bw] {
for y0 in [0usize, 8, ph - bh] {
for mvx in [-40i32, -20, -3, 0, 1, 2, 3, 7, 20, 40] {
for mvy in [-40i32, -20, -3, 0, 1, 2, 3, 7, 20, 40] {
let mut a = vec![0u8; bw * bh];
let mut b = vec![0u8; bw * bh];
mc_luma(&exact, pw, ph, x0, y0, bw, bh, mvx, mvy, &mut a);
mc_luma_padded(
&padded, stride, PAD_L, pw, ph, x0, y0, bw, bh, mvx, mvy, &mut b,
);
assert_eq!(a, b, "luma bw={bw} x0={x0} y0={y0} mv=({mvx},{mvy})");
}
}
}
}
}
}
#[test]
fn mc_chroma_padded_matches_exact() {
let (pw, ph) = (24usize, 16usize);
let exact = rand_plane(pw, ph, 0x99);
let (padded, stride) = make_padded(&exact, pw, ph, PAD_C);
for &(bw, bh) in &[(8usize, 8usize), (4, 4), (8, 4), (4, 8), (2, 2)] {
for x0 in [0usize, 4, pw - bw] {
for y0 in [0usize, 4, ph - bh] {
for mvx in [-40i32, -16, -3, 0, 1, 5, 8, 16, 40] {
for mvy in [-40i32, -16, -3, 0, 1, 5, 8, 16, 40] {
let mut a = vec![0u8; bw * bh];
let mut b = vec![0u8; bw * bh];
mc_chroma(&exact, pw, ph, x0, y0, bw, bh, mvx, mvy, &mut a);
mc_chroma_padded(
&padded, stride, PAD_C, pw, ph, x0, y0, bw, bh, mvx, mvy, &mut b,
);
assert_eq!(a, b, "chroma bw={bw} x0={x0} y0={y0} mv=({mvx},{mvy})");
}
}
}
}
}
}
#[test]
fn fused_hpel_builder_matches_tiles() {
let mut st = 0x5eed_f00d_dead_beefu64;
let mut lcg = move || {
st = st.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
(st >> 33) as u8
};
for &(cw, ch, pad) in &[(64usize, 48usize, 16usize), (80, 64, 12), (176, 144, 32)] {
let reference: Vec<u8> = (0..cw * ch).map(|_| lcg()).collect();
let (pw, ph) = (cw + 2 * pad, ch + 2 * pad);
let mut f = vec![0u8; pw * ph];
for y in 0..ph {
let sy = (y as isize - pad as isize).clamp(0, ch as isize - 1) as usize;
let row = &reference[sy * cw..sy * cw + cw];
let d = &mut f[y * pw..y * pw + pw];
d[..pad].fill(row[0]);
d[pad..pad + cw].copy_from_slice(row);
d[pad + cw..].fill(row[cw - 1]);
}
let (mut h1, mut v1, mut c1) = (vec![0u8; pw * ph], vec![0u8; pw * ph], vec![0u8; pw * ph]);
let (mut h2, mut v2, mut c2) = (vec![0u8; pw * ph], vec![0u8; pw * ph], vec![0u8; pw * ph]);
build_hpel_tiles(&f, pw, ph, &mut h1, &mut v1, &mut c1);
build_hpel_fused(&f, pw, ph, &mut h2, &mut v2, &mut c2);
assert_eq!(h1, h2, "H plane differs at {cw}x{ch} pad {pad}");
assert_eq!(v1, v2, "V plane differs at {cw}x{ch} pad {pad}");
assert_eq!(c1, c2, "C plane differs at {cw}x{ch} pad {pad}");
#[cfg(accel)]
{
let (mut h3, mut v3, mut c3) =
(vec![0u8; pw * ph], vec![0u8; pw * ph], vec![0u8; pw * ph]);
if rusty_h264_accel::hpel_fused(&f, pw, ph, &mut h3, &mut v3, &mut c3) {
assert_eq!(h1, h3, "AVX2 H plane differs at {cw}x{ch} pad {pad}");
assert_eq!(v1, v3, "AVX2 V plane differs at {cw}x{ch} pad {pad}");
assert_eq!(c1, c3, "AVX2 C plane differs at {cw}x{ch} pad {pad}");
}
}
}
}
#[test]
fn hpel_block_matches_mc_luma_exactly() {
let (cw, ch) = (96usize, 64usize);
let mut reference = vec![0u8; cw * ch];
let mut s: u32 = 0x9E37_79B9;
for p in reference.iter_mut() {
s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
*p = (s >> 24) as u8;
}
let planes = build_hpel_planes(&reference, cw, ch);
let mut checked = 0;
for (bw, bh) in [(16, 16), (16, 8), (8, 16), (8, 8), (8, 4), (4, 8), (4, 4)] {
for fy in 0..4i32 {
for fx in 0..4i32 {
if fx == 0 && fy == 0 {
continue; }
for y0 in (0..ch - bh).step_by(7) {
for x0 in (0..cw - bw).step_by(5) {
for &(dx, dy) in &[(0i32, 0i32), (4, 0), (0, 4), (-4, -4), (8, 8), (-64, -64), (96, 40), (-96, 60), (40, -96)] {
let (mvx, mvy) = (dx + fx, dy + fy);
let mut want = [0u8; 256];
mc_luma(&reference, cw, ch, x0, y0, bw, bh, mvx, mvy, &mut want);
let mut got = [0u8; 256];
if !hpel_block(&planes, x0, y0, bw, bh, mvx, mvy, &mut got) {
continue; }
assert_eq!(
&got[..bw * bh],
&want[..bw * bh],
"bw={bw} bh={bh} fx={fx} fy={fy} x0={x0} y0={y0} mv=({mvx},{mvy})"
);
checked += 1;
}
}
}
}
}
}
assert!(checked > 5_000, "too few positions exercised: {checked}");
}
#[test]
fn mc_luma_block_kernels_match_per_pixel() {
let (cw, ch) = (40usize, 32usize);
let mut state = 0x1357_9bdfu32;
let mut next = || {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(state >> 24) as u8
};
let reference: Vec<u8> = (0..cw * ch).map(|_| next()).collect();
for &(bw, bh) in &[(16, 16), (8, 8), (4, 4), (8, 16), (16, 8)] {
for &(x0, y0) in &[(8usize, 8usize), (0, 0), (cw - bw, ch - bh)] {
for mvx in -9..=9 {
for mvy in -9..=9 {
let mut got = vec![0u8; bw * bh];
mc_luma(&reference, cw, ch, x0, y0, bw, bh, mvx, mvy, &mut got);
let ix0 = x0 as isize + (mvx >> 2) as isize;
let iy0 = y0 as isize + (mvy >> 2) as isize;
let (fx, fy) = (mvx & 3, mvy & 3);
for dy in 0..bh {
for dx in 0..bw {
let want = luma_sample(
&reference, cw, ch,
ix0 + dx as isize, iy0 + dy as isize, fx, fy,
) as u8;
assert_eq!(
got[dy * bw + dx], want,
"bw{bw}x{bh} at ({x0},{y0}) mv({mvx},{mvy}) px({dx},{dy})"
);
}
}
}
}
}
}
}
#[test]
fn mc_chroma_block_matches_per_pixel() {
let (cw, ch) = (24usize, 20usize);
let mut state = 0xabcd_1234u32;
let mut next = || {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(state >> 24) as u8
};
let reference: Vec<u8> = (0..cw * ch).map(|_| next()).collect();
let pp = |ix: isize, iy: isize, fx: i32, fy: i32| -> u8 {
let a = at(&reference, cw, ch, ix, iy);
let b = at(&reference, cw, ch, ix + 1, iy);
let c = at(&reference, cw, ch, ix, iy + 1);
let d = at(&reference, cw, ch, ix + 1, iy + 1);
(((8 - fx) * (8 - fy) * a + fx * (8 - fy) * b + (8 - fx) * fy * c + fx * fy * d + 32) >> 6)
as u8
};
for &(bw, bh) in &[(8, 8), (4, 4), (8, 4), (4, 8)] {
for &(x0, y0) in &[(4usize, 4usize), (0, 0), (cw - bw, ch - bh)] {
for mvx in -12..=12 {
for mvy in -12..=12 {
let mut got = vec![0u8; bw * bh];
mc_chroma(&reference, cw, ch, x0, y0, bw, bh, mvx, mvy, &mut got);
let ix0 = x0 as isize + (mvx >> 3) as isize;
let iy0 = y0 as isize + (mvy >> 3) as isize;
let (fx, fy) = (mvx & 7, mvy & 7);
for dy in 0..bh {
for dx in 0..bw {
assert_eq!(
got[dy * bw + dx],
pp(ix0 + dx as isize, iy0 + dy as isize, fx, fy),
"bw{bw}x{bh} at ({x0},{y0}) mv({mvx},{mvy}) px({dx},{dy})"
);
}
}
}
}
}
}
}
#[test]
fn median_of_three() {
assert_eq!(median3(1, 2, 3), 2);
assert_eq!(median3(3, 1, 2), 2);
assert_eq!(median3(-5, 0, 5), 0);
assert_eq!(median3(7, 7, 2), 7);
}
#[test]
fn mv_predict_single_neighbor_uses_it() {
let a = MvNeighbor { available: true, mv: (8, -4), ref_idx: 0 };
assert_eq!(predict_mv(a, MvNeighbor::NONE, MvNeighbor::NONE, 0), (8, -4));
}
#[test]
fn mv_predict_median_when_all_inter() {
let a = MvNeighbor { available: true, mv: (4, 0), ref_idx: 0 };
let b = MvNeighbor { available: true, mv: (8, 0), ref_idx: 0 };
let c = MvNeighbor { available: true, mv: (12, 0), ref_idx: 0 };
assert_eq!(predict_mv(a, b, c, 0), (8, 0));
}
#[test]
fn mv_predict_one_matching_ref_wins() {
let a = MvNeighbor { available: true, mv: (0, 0), ref_idx: -1 };
let b = MvNeighbor { available: true, mv: (5, 7), ref_idx: 0 };
let c = MvNeighbor { available: true, mv: (0, 0), ref_idx: -1 };
assert_eq!(predict_mv(a, b, c, 0), (5, 7));
}
#[test]
fn mv_predict_distinguishes_refs() {
let a = MvNeighbor { available: true, mv: (4, 4), ref_idx: 1 };
let b = MvNeighbor { available: true, mv: (5, 7), ref_idx: 0 };
let c = MvNeighbor { available: true, mv: (0, 0), ref_idx: -1 };
assert_eq!(predict_mv(a, b, c, 0), (5, 7));
assert_eq!(predict_mv(a, b, c, 1), (4, 4));
}
#[test]
fn mc_luma_zero_mv_copies() {
let reference = vec![
0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23, 30, 31, 32, 33,
];
let mut out = [0u8; 4];
mc_luma(&reference, 4, 4, 1, 1, 2, 2, 0, 0, &mut out);
assert_eq!(out, [11, 12, 21, 22]);
}
#[test]
fn mc_luma_clamps_at_edges() {
let reference = vec![5, 6, 7, 8];
let mut out = [0u8; 4];
mc_luma(&reference, 2, 2, 0, 0, 2, 2, -40, -40, &mut out);
assert_eq!(out, [5, 5, 5, 5]);
}
#[test]
fn mc_luma_halfpel_on_flat_is_flat() {
let reference = vec![100u8; 8 * 8];
let mut out = [0u8; 16];
for &(fx, fy) in &[(2, 0), (0, 2), (2, 2), (1, 1), (3, 3)] {
mc_luma(&reference, 8, 8, 2, 2, 4, 4, fx, fy, &mut out);
assert!(out.iter().all(|&p| p == 100), "frac ({fx},{fy})");
}
}
#[test]
fn mc_chroma_zero_mv_copies() {
let reference = vec![0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23, 30, 31, 32, 33];
let mut out = [0u8; 4];
mc_chroma(&reference, 4, 4, 1, 1, 2, 2, 0, 0, &mut out);
assert_eq!(out, [11, 12, 21, 22]);
}
#[test]
fn mc_chroma_bilinear_midpoint() {
let reference = vec![0u8, 8, 0, 8];
let mut out = [0u8; 1];
mc_chroma(&reference, 2, 2, 0, 0, 1, 1, 4, 0, &mut out);
assert_eq!(out[0], 4);
}
}