#![allow(dead_code)]
const TAPS: usize = 8;
const FILTER_BITS: u32 = 7;
pub const SUBPEL_FILTERS: [[[i32; TAPS]; 16]; 4] = [
[
[0, 0, 0, 128, 0, 0, 0, 0],
[0, 1, -5, 126, 8, -3, 1, 0],
[-1, 3, -10, 122, 18, -6, 2, 0],
[-1, 4, -13, 118, 27, -9, 3, -1],
[-1, 4, -16, 112, 37, -11, 4, -1],
[-1, 5, -18, 105, 48, -14, 4, -1],
[-1, 5, -19, 97, 58, -16, 5, -1],
[-1, 6, -19, 88, 68, -18, 5, -1],
[-1, 6, -19, 78, 78, -19, 6, -1],
[-1, 5, -18, 68, 88, -19, 6, -1],
[-1, 5, -16, 58, 97, -19, 5, -1],
[-1, 4, -14, 48, 105, -18, 5, -1],
[-1, 4, -11, 37, 112, -16, 4, -1],
[-1, 3, -9, 27, 118, -13, 4, -1],
[0, 2, -6, 18, 122, -10, 3, -1],
[0, 1, -3, 8, 126, -5, 1, 0],
],
[
[0, 0, 0, 128, 0, 0, 0, 0],
[-3, -1, 32, 64, 38, 1, -3, 0],
[-2, -2, 29, 63, 41, 2, -3, 0],
[-2, -2, 26, 63, 43, 4, -4, 0],
[-2, -3, 24, 62, 46, 5, -4, 0],
[-2, -3, 21, 60, 49, 7, -4, 0],
[-1, -4, 18, 59, 51, 9, -4, 0],
[-1, -4, 16, 57, 53, 12, -4, -1],
[-1, -4, 14, 55, 55, 14, -4, -1],
[-1, -4, 12, 53, 57, 16, -4, -1],
[0, -4, 9, 51, 59, 18, -4, -1],
[0, -4, 7, 49, 60, 21, -3, -2],
[0, -4, 5, 46, 62, 24, -3, -2],
[0, -4, 4, 43, 63, 26, -2, -2],
[0, -3, 2, 41, 63, 29, -2, -2],
[0, -3, 1, 38, 64, 32, -1, -3],
],
[
[0, 0, 0, 128, 0, 0, 0, 0],
[-1, 3, -7, 127, 8, -3, 1, 0],
[-2, 5, -13, 125, 17, -6, 3, -1],
[-3, 7, -17, 121, 27, -10, 5, -2],
[-4, 9, -20, 115, 37, -13, 6, -2],
[-4, 10, -23, 108, 48, -16, 8, -3],
[-4, 10, -24, 100, 59, -19, 9, -3],
[-4, 11, -24, 90, 70, -21, 10, -4],
[-4, 11, -23, 80, 80, -23, 11, -4],
[-4, 10, -21, 70, 90, -24, 11, -4],
[-3, 9, -19, 59, 100, -24, 10, -4],
[-3, 8, -16, 48, 108, -23, 10, -4],
[-2, 6, -13, 37, 115, -20, 9, -4],
[-2, 5, -10, 27, 121, -17, 7, -3],
[-1, 3, -6, 17, 125, -13, 5, -2],
[0, 1, -3, 8, 127, -7, 3, -1],
],
[
[0, 0, 0, 128, 0, 0, 0, 0],
[0, 0, 0, 120, 8, 0, 0, 0],
[0, 0, 0, 112, 16, 0, 0, 0],
[0, 0, 0, 104, 24, 0, 0, 0],
[0, 0, 0, 96, 32, 0, 0, 0],
[0, 0, 0, 88, 40, 0, 0, 0],
[0, 0, 0, 80, 48, 0, 0, 0],
[0, 0, 0, 72, 56, 0, 0, 0],
[0, 0, 0, 64, 64, 0, 0, 0],
[0, 0, 0, 56, 72, 0, 0, 0],
[0, 0, 0, 48, 80, 0, 0, 0],
[0, 0, 0, 40, 88, 0, 0, 0],
[0, 0, 0, 32, 96, 0, 0, 0],
[0, 0, 0, 24, 104, 0, 0, 0],
[0, 0, 0, 16, 112, 0, 0, 0],
[0, 0, 0, 8, 120, 0, 0, 0],
],
];
#[inline]
fn clip_pixel(v: i32, max: i32) -> u16 {
v.clamp(0, max) as u16
}
#[inline]
fn round_pow2(v: i32, n: u32) -> i32 {
(v + (1 << (n - 1))) >> n
}
thread_local! {
static MC_TMP: std::cell::RefCell<[u16; 71 * 64]> = const { std::cell::RefCell::new([0; 71 * 64]) };
}
pub struct RefPlane<'a> {
pub buf: &'a [u16],
pub stride: usize,
pub w: i32,
pub h: i32,
}
impl RefPlane<'_> {
#[inline]
fn px(&self, x: i32, y: i32) -> i32 {
let cx = x.clamp(0, (self.w - 1).max(0)) as usize;
let cy = y.clamp(0, (self.h - 1).max(0)) as usize;
self.buf.get(cy * self.stride + cx).copied().unwrap_or(0) as i32
}
}
#[cfg(target_arch = "x86_64")]
fn no_edge_tile() -> bool {
static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*E.get_or_init(|| std::env::var("VP9_NO_MC_EDGE_TILE").is_ok())
}
#[cfg(target_arch = "x86_64")]
#[inline]
fn has_avx2() -> bool {
std::is_x86_feature_detected!("avx2")
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn conv8_avx2(
src: *const u16,
tap_stride: usize,
f: &[i32; 8],
dst: *mut u16,
n: usize,
max: i32,
avg: bool,
) {
use std::arch::x86_64::*;
let round = _mm256_set1_epi32(64);
let one = _mm256_set1_epi32(1);
let maxv = _mm256_set1_epi32(max);
let zero = _mm256_setzero_si256();
let p01 = _mm256_set1_epi32((f[1] << 16) | (f[0] & 0xffff));
let p23 = _mm256_set1_epi32((f[3] << 16) | (f[2] & 0xffff));
let p45 = _mm256_set1_epi32((f[5] << 16) | (f[4] & 0xffff));
let p67 = _mm256_set1_epi32((f[7] << 16) | (f[6] & 0xffff));
let mut i = 0usize;
while i + 8 <= n {
let r0 = _mm_loadu_si128(src.add(i) as *const __m128i);
let r1 = _mm_loadu_si128(src.add(i + tap_stride) as *const __m128i);
let r2 = _mm_loadu_si128(src.add(i + 2 * tap_stride) as *const __m128i);
let r3 = _mm_loadu_si128(src.add(i + 3 * tap_stride) as *const __m128i);
let r4 = _mm_loadu_si128(src.add(i + 4 * tap_stride) as *const __m128i);
let r5 = _mm_loadu_si128(src.add(i + 5 * tap_stride) as *const __m128i);
let r6 = _mm_loadu_si128(src.add(i + 6 * tap_stride) as *const __m128i);
let r7 = _mm_loadu_si128(src.add(i + 7 * tap_stride) as *const __m128i);
let v01 = _mm256_set_m128i(_mm_unpackhi_epi16(r0, r1), _mm_unpacklo_epi16(r0, r1));
let v23 = _mm256_set_m128i(_mm_unpackhi_epi16(r2, r3), _mm_unpacklo_epi16(r2, r3));
let v45 = _mm256_set_m128i(_mm_unpackhi_epi16(r4, r5), _mm_unpacklo_epi16(r4, r5));
let v67 = _mm256_set_m128i(_mm_unpackhi_epi16(r6, r7), _mm_unpacklo_epi16(r6, r7));
let mut acc = _mm256_add_epi32(
_mm256_add_epi32(_mm256_madd_epi16(v01, p01), _mm256_madd_epi16(v23, p23)),
_mm256_add_epi32(_mm256_madd_epi16(v45, p45), _mm256_madd_epi16(v67, p67)),
);
acc = _mm256_srai_epi32::<7>(_mm256_add_epi32(acc, round));
acc = _mm256_min_epi32(_mm256_max_epi32(acc, zero), maxv);
if avg {
let d = _mm256_cvtepu16_epi32(_mm_loadu_si128(dst.add(i) as *const __m128i));
acc = _mm256_srai_epi32::<1>(_mm256_add_epi32(_mm256_add_epi32(acc, d), one));
}
let packed = _mm256_packus_epi32(acc, acc);
let perm = _mm256_permute4x64_epi64::<0x08>(packed);
_mm_storeu_si128(dst.add(i) as *mut __m128i, _mm256_castsi256_si128(perm));
i += 8;
}
if i + 4 <= n {
let r0 = _mm_loadl_epi64(src.add(i) as *const __m128i);
let r1 = _mm_loadl_epi64(src.add(i + tap_stride) as *const __m128i);
let r2 = _mm_loadl_epi64(src.add(i + 2 * tap_stride) as *const __m128i);
let r3 = _mm_loadl_epi64(src.add(i + 3 * tap_stride) as *const __m128i);
let r4 = _mm_loadl_epi64(src.add(i + 4 * tap_stride) as *const __m128i);
let r5 = _mm_loadl_epi64(src.add(i + 5 * tap_stride) as *const __m128i);
let r6 = _mm_loadl_epi64(src.add(i + 6 * tap_stride) as *const __m128i);
let r7 = _mm_loadl_epi64(src.add(i + 7 * tap_stride) as *const __m128i);
let mut acc = _mm_add_epi32(
_mm_add_epi32(
_mm_madd_epi16(_mm_unpacklo_epi16(r0, r1), _mm256_castsi256_si128(p01)),
_mm_madd_epi16(_mm_unpacklo_epi16(r2, r3), _mm256_castsi256_si128(p23)),
),
_mm_add_epi32(
_mm_madd_epi16(_mm_unpacklo_epi16(r4, r5), _mm256_castsi256_si128(p45)),
_mm_madd_epi16(_mm_unpacklo_epi16(r6, r7), _mm256_castsi256_si128(p67)),
),
);
acc = _mm_srai_epi32::<7>(_mm_add_epi32(acc, _mm256_castsi256_si128(round)));
acc = _mm_min_epi32(
_mm_max_epi32(acc, _mm256_castsi256_si128(zero)),
_mm256_castsi256_si128(maxv),
);
if avg {
let d = _mm_cvtepu16_epi32(_mm_loadl_epi64(dst.add(i) as *const __m128i));
acc = _mm_srai_epi32::<1>(_mm_add_epi32(
_mm_add_epi32(acc, d),
_mm256_castsi256_si128(one),
));
}
let packed = _mm_packus_epi32(acc, acc);
_mm_storel_epi64(dst.add(i) as *mut __m128i, packed);
i += 4;
}
while i < n {
let mut sum = 0i32;
for k in 0..8 {
sum += *src.add(i + k * tap_stride) as i32 * f[k];
}
let v = ((sum + 64) >> 7).clamp(0, max);
*dst.add(i) = if avg {
((v + *dst.add(i) as i32 + 1) >> 1) as u16
} else {
v as u16
};
i += 1;
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn predict8x8_hv_avx2(
refp: &RefPlane,
bx: i32,
by: i32,
fx: &[i32; 8],
fy: &[i32; 8],
dst: *mut u16,
dst_stride: usize,
max: i32,
) {
use std::arch::x86_64::*;
let round = _mm256_set1_epi32(64);
let maxv = _mm256_set1_epi32(max);
let zero = _mm256_setzero_si256();
let xp01 = _mm256_set1_epi32((fx[1] << 16) | (fx[0] & 0xffff));
let xp23 = _mm256_set1_epi32((fx[3] << 16) | (fx[2] & 0xffff));
let xp45 = _mm256_set1_epi32((fx[5] << 16) | (fx[4] & 0xffff));
let xp67 = _mm256_set1_epi32((fx[7] << 16) | (fx[6] & 0xffff));
let mut tmp = [0u16; 15 * 8];
let base = refp.buf.as_ptr();
for r in 0..15 {
let s = base.add((by + r as i32 - 3) as usize * refp.stride + (bx - 3) as usize);
let r0 = _mm_loadu_si128(s as *const __m128i);
let r1 = _mm_loadu_si128(s.add(1) as *const __m128i);
let r2 = _mm_loadu_si128(s.add(2) as *const __m128i);
let r3 = _mm_loadu_si128(s.add(3) as *const __m128i);
let r4 = _mm_loadu_si128(s.add(4) as *const __m128i);
let r5 = _mm_loadu_si128(s.add(5) as *const __m128i);
let r6 = _mm_loadu_si128(s.add(6) as *const __m128i);
let r7 = _mm_loadu_si128(s.add(7) as *const __m128i);
let v01 = _mm256_set_m128i(_mm_unpackhi_epi16(r0, r1), _mm_unpacklo_epi16(r0, r1));
let v23 = _mm256_set_m128i(_mm_unpackhi_epi16(r2, r3), _mm_unpacklo_epi16(r2, r3));
let v45 = _mm256_set_m128i(_mm_unpackhi_epi16(r4, r5), _mm_unpacklo_epi16(r4, r5));
let v67 = _mm256_set_m128i(_mm_unpackhi_epi16(r6, r7), _mm_unpacklo_epi16(r6, r7));
let mut acc = _mm256_add_epi32(
_mm256_add_epi32(_mm256_madd_epi16(v01, xp01), _mm256_madd_epi16(v23, xp23)),
_mm256_add_epi32(_mm256_madd_epi16(v45, xp45), _mm256_madd_epi16(v67, xp67)),
);
acc = _mm256_srai_epi32::<7>(_mm256_add_epi32(acc, round));
acc = _mm256_min_epi32(_mm256_max_epi32(acc, zero), maxv);
let packed = _mm256_packus_epi32(acc, acc);
let perm = _mm256_permute4x64_epi64::<0x08>(packed);
_mm_storeu_si128(
tmp.as_mut_ptr().add(r * 8) as *mut __m128i,
_mm256_castsi256_si128(perm),
);
}
let yp01 = _mm256_set1_epi32((fy[1] << 16) | (fy[0] & 0xffff));
let yp23 = _mm256_set1_epi32((fy[3] << 16) | (fy[2] & 0xffff));
let yp45 = _mm256_set1_epi32((fy[5] << 16) | (fy[4] & 0xffff));
let yp67 = _mm256_set1_epi32((fy[7] << 16) | (fy[6] & 0xffff));
for y in 0..8 {
let s = tmp.as_ptr().add(y * 8);
let r0 = _mm_loadu_si128(s as *const __m128i);
let r1 = _mm_loadu_si128(s.add(8) as *const __m128i);
let r2 = _mm_loadu_si128(s.add(16) as *const __m128i);
let r3 = _mm_loadu_si128(s.add(24) as *const __m128i);
let r4 = _mm_loadu_si128(s.add(32) as *const __m128i);
let r5 = _mm_loadu_si128(s.add(40) as *const __m128i);
let r6 = _mm_loadu_si128(s.add(48) as *const __m128i);
let r7 = _mm_loadu_si128(s.add(56) as *const __m128i);
let v01 = _mm256_set_m128i(_mm_unpackhi_epi16(r0, r1), _mm_unpacklo_epi16(r0, r1));
let v23 = _mm256_set_m128i(_mm_unpackhi_epi16(r2, r3), _mm_unpacklo_epi16(r2, r3));
let v45 = _mm256_set_m128i(_mm_unpackhi_epi16(r4, r5), _mm_unpacklo_epi16(r4, r5));
let v67 = _mm256_set_m128i(_mm_unpackhi_epi16(r6, r7), _mm_unpacklo_epi16(r6, r7));
let mut acc = _mm256_add_epi32(
_mm256_add_epi32(_mm256_madd_epi16(v01, yp01), _mm256_madd_epi16(v23, yp23)),
_mm256_add_epi32(_mm256_madd_epi16(v45, yp45), _mm256_madd_epi16(v67, yp67)),
);
acc = _mm256_srai_epi32::<7>(_mm256_add_epi32(acc, round));
acc = _mm256_min_epi32(_mm256_max_epi32(acc, zero), maxv);
let packed = _mm256_packus_epi32(acc, acc);
let perm = _mm256_permute4x64_epi64::<0x08>(packed);
_mm_storeu_si128(
dst.add(y * dst_stride) as *mut __m128i,
_mm256_castsi256_si128(perm),
);
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn predict8x8_h_avx2(
refp: &RefPlane,
bx: i32,
by: i32,
fx: &[i32; 8],
dst: *mut u16,
dst_stride: usize,
max: i32,
) {
use std::arch::x86_64::*;
let round = _mm256_set1_epi32(64);
let maxv = _mm256_set1_epi32(max);
let zero = _mm256_setzero_si256();
let p01 = _mm256_set1_epi32((fx[1] << 16) | (fx[0] & 0xffff));
let p23 = _mm256_set1_epi32((fx[3] << 16) | (fx[2] & 0xffff));
let p45 = _mm256_set1_epi32((fx[5] << 16) | (fx[4] & 0xffff));
let p67 = _mm256_set1_epi32((fx[7] << 16) | (fx[6] & 0xffff));
let base = refp.buf.as_ptr();
for y in 0..8 {
let s = base.add((by + y) as usize * refp.stride + (bx - 3) as usize);
let r0 = _mm_loadu_si128(s as *const __m128i);
let r1 = _mm_loadu_si128(s.add(1) as *const __m128i);
let r2 = _mm_loadu_si128(s.add(2) as *const __m128i);
let r3 = _mm_loadu_si128(s.add(3) as *const __m128i);
let r4 = _mm_loadu_si128(s.add(4) as *const __m128i);
let r5 = _mm_loadu_si128(s.add(5) as *const __m128i);
let r6 = _mm_loadu_si128(s.add(6) as *const __m128i);
let r7 = _mm_loadu_si128(s.add(7) as *const __m128i);
let v01 = _mm256_set_m128i(_mm_unpackhi_epi16(r0, r1), _mm_unpacklo_epi16(r0, r1));
let v23 = _mm256_set_m128i(_mm_unpackhi_epi16(r2, r3), _mm_unpacklo_epi16(r2, r3));
let v45 = _mm256_set_m128i(_mm_unpackhi_epi16(r4, r5), _mm_unpacklo_epi16(r4, r5));
let v67 = _mm256_set_m128i(_mm_unpackhi_epi16(r6, r7), _mm_unpacklo_epi16(r6, r7));
let mut acc = _mm256_add_epi32(
_mm256_add_epi32(_mm256_madd_epi16(v01, p01), _mm256_madd_epi16(v23, p23)),
_mm256_add_epi32(_mm256_madd_epi16(v45, p45), _mm256_madd_epi16(v67, p67)),
);
acc = _mm256_srai_epi32::<7>(_mm256_add_epi32(acc, round));
acc = _mm256_min_epi32(_mm256_max_epi32(acc, zero), maxv);
let packed = _mm256_packus_epi32(acc, acc);
let perm = _mm256_permute4x64_epi64::<0x08>(packed);
_mm_storeu_si128(
dst.add(y as usize * dst_stride) as *mut __m128i,
_mm256_castsi256_si128(perm),
);
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn predict8x8_v_avx2(
refp: &RefPlane,
bx: i32,
by: i32,
fy: &[i32; 8],
dst: *mut u16,
dst_stride: usize,
max: i32,
) {
use std::arch::x86_64::*;
let round = _mm256_set1_epi32(64);
let maxv = _mm256_set1_epi32(max);
let zero = _mm256_setzero_si256();
let p01 = _mm256_set1_epi32((fy[1] << 16) | (fy[0] & 0xffff));
let p23 = _mm256_set1_epi32((fy[3] << 16) | (fy[2] & 0xffff));
let p45 = _mm256_set1_epi32((fy[5] << 16) | (fy[4] & 0xffff));
let p67 = _mm256_set1_epi32((fy[7] << 16) | (fy[6] & 0xffff));
let base = refp.buf.as_ptr();
let row = |r: i32| -> __m128i {
_mm_loadu_si128(base.add((by + r) as usize * refp.stride + bx as usize) as *const __m128i)
};
let mut w = [
row(-3),
row(-2),
row(-1),
row(0),
row(1),
row(2),
row(3),
row(4),
];
for y in 0..8usize {
let v01 = _mm256_set_m128i(_mm_unpackhi_epi16(w[0], w[1]), _mm_unpacklo_epi16(w[0], w[1]));
let v23 = _mm256_set_m128i(_mm_unpackhi_epi16(w[2], w[3]), _mm_unpacklo_epi16(w[2], w[3]));
let v45 = _mm256_set_m128i(_mm_unpackhi_epi16(w[4], w[5]), _mm_unpacklo_epi16(w[4], w[5]));
let v67 = _mm256_set_m128i(_mm_unpackhi_epi16(w[6], w[7]), _mm_unpacklo_epi16(w[6], w[7]));
let mut acc = _mm256_add_epi32(
_mm256_add_epi32(_mm256_madd_epi16(v01, p01), _mm256_madd_epi16(v23, p23)),
_mm256_add_epi32(_mm256_madd_epi16(v45, p45), _mm256_madd_epi16(v67, p67)),
);
acc = _mm256_srai_epi32::<7>(_mm256_add_epi32(acc, round));
acc = _mm256_min_epi32(_mm256_max_epi32(acc, zero), maxv);
let packed = _mm256_packus_epi32(acc, acc);
let perm = _mm256_permute4x64_epi64::<0x08>(packed);
_mm_storeu_si128(
dst.add(y * dst_stride) as *mut __m128i,
_mm256_castsi256_si128(perm),
);
if y < 7 {
w.copy_within(1.., 0);
w[7] = row(5 + y as i32);
}
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
#[inline]
pub unsafe fn sad8x8_u8(s: *const u8, ss: usize, r: *const u8, rs: usize) -> u32 {
use std::arch::x86_64::*;
let mut acc = _mm_setzero_si128();
for y in (0..8).step_by(2) {
let sv = _mm_set_epi64x(
std::ptr::read_unaligned(s.add((y + 1) * ss) as *const i64),
std::ptr::read_unaligned(s.add(y * ss) as *const i64),
);
let rv = _mm_set_epi64x(
std::ptr::read_unaligned(r.add((y + 1) * rs) as *const i64),
std::ptr::read_unaligned(r.add(y * rs) as *const i64),
);
acc = _mm_add_epi64(acc, _mm_sad_epu8(sv, rv));
}
(_mm_cvtsi128_si64(acc) as u32) + (_mm_extract_epi64::<1>(acc) as u32)
}
#[cfg(target_arch = "x86_64")]
mod u8score {
use super::RefPlane8;
use std::arch::x86_64::*;
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn tp(f: &[i32; 8]) -> [__m256i; 4] {
std::array::from_fn(|k| {
let lo = (f[2 * k] as i8 as u8) as u16;
let hi = (f[2 * k + 1] as i8 as u8) as u16;
_mm256_set1_epi16(((hi << 8) | lo) as i16)
})
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn step2(m: [__m256i; 4], t: &[__m256i; 4]) -> __m128i {
let m0 = _mm256_maddubs_epi16(m[0], t[0]);
let m1 = _mm256_maddubs_epi16(m[1], t[1]);
let m2 = _mm256_maddubs_epi16(m[2], t[2]);
let m3 = _mm256_maddubs_epi16(m[3], t[3]);
let g1 = _mm256_adds_epi16(m0, m2);
let g2 = _mm256_adds_epi16(m1, m3);
let sum = _mm256_adds_epi16(g1, g2);
let r = _mm256_adds_epi16(sum, _mm256_set1_epi16(64));
let o = _mm256_srai_epi16::<7>(r);
let p = _mm256_packus_epi16(o, o); _mm_unpacklo_epi64(
_mm256_castsi256_si128(p),
_mm256_extracti128_si256::<1>(p),
)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn hwin2(p0: *const u8, p1: *const u8) -> [__m256i; 4] {
let w = _mm256_set_m128i(
_mm_loadu_si128(p1 as *const __m128i),
_mm_loadu_si128(p0 as *const __m128i),
);
[
_mm256_unpacklo_epi8(w, _mm256_srli_si256::<1>(w)),
_mm256_unpacklo_epi8(_mm256_srli_si256::<2>(w), _mm256_srli_si256::<3>(w)),
_mm256_unpacklo_epi8(_mm256_srli_si256::<4>(w), _mm256_srli_si256::<5>(w)),
_mm256_unpacklo_epi8(_mm256_srli_si256::<6>(w), _mm256_srli_si256::<7>(w)),
]
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn sad2(pred2: __m128i, s0: *const u8, s1: *const u8) -> u32 {
let s = _mm_set_epi64x(
std::ptr::read_unaligned(s1 as *const i64),
std::ptr::read_unaligned(s0 as *const i64),
);
let d = _mm_sad_epu8(pred2, s);
(_mm_cvtsi128_si32(d) as u32) + (_mm_extract_epi32::<2>(d) as u32)
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn score_hv(
refp: &RefPlane8,
bx: i32,
by: i32,
fx: &[i32; 8],
fy: &[i32; 8],
src: *const u8,
src_stride: usize,
) -> u32 {
let tx = tp(fx);
let ty = tp(fy);
let base = refp.buf.as_ptr();
let row = |r: i32| base.add((by + r) as usize * refp.stride + (bx - 3) as usize);
let mut tmp = [0u8; 16 * 8];
for i in 0..7 {
let two = step2(hwin2(row(2 * i as i32 - 3), row(2 * i as i32 - 2)), &tx);
_mm_storeu_si128(tmp.as_mut_ptr().add(16 * i) as *mut __m128i, two);
}
let last = step2(hwin2(row(11), row(11)), &tx);
_mm_storel_epi64(tmp.as_mut_ptr().add(16 * 7) as *mut __m128i, last);
let mut inter = [_mm_setzero_si128(); 14];
for (j, it) in inter.iter_mut().enumerate() {
let a = _mm_loadl_epi64(tmp.as_ptr().add(8 * j) as *const __m128i);
let b = _mm_loadl_epi64(tmp.as_ptr().add(8 * (j + 1)) as *const __m128i);
*it = _mm_unpacklo_epi8(a, b);
}
let mut sad = 0u32;
for y in (0..8usize).step_by(2) {
let m: [__m256i; 4] =
std::array::from_fn(|k| _mm256_set_m128i(inter[y + 1 + 2 * k], inter[y + 2 * k]));
let two = step2(m, &ty);
sad += sad2(two, src.add(y * src_stride), src.add((y + 1) * src_stride));
}
sad
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn score_h(
refp: &RefPlane8,
bx: i32,
by: i32,
fx: &[i32; 8],
src: *const u8,
src_stride: usize,
) -> u32 {
let tx = tp(fx);
let base = refp.buf.as_ptr();
let row = |r: i32| base.add((by + r) as usize * refp.stride + (bx - 3) as usize);
let mut sad = 0u32;
for y in (0..8).step_by(2) {
let two = step2(hwin2(row(y), row(y + 1)), &tx);
sad += sad2(
two,
src.add(y as usize * src_stride),
src.add((y + 1) as usize * src_stride),
);
}
sad
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn sse2(pred2: __m128i, s0: *const u8, s1: *const u8) -> u32 {
let s = _mm_set_epi64x(
std::ptr::read_unaligned(s1 as *const i64),
std::ptr::read_unaligned(s0 as *const i64),
);
let d = _mm_or_si128(_mm_subs_epu8(pred2, s), _mm_subs_epu8(s, pred2));
let d16 = _mm256_cvtepu8_epi16(d);
let sq = _mm256_madd_epi16(d16, d16);
let lo = _mm256_castsi256_si128(sq);
let hi = _mm256_extracti128_si256::<1>(sq);
let mut t = _mm_add_epi32(lo, hi);
t = _mm_add_epi32(t, _mm_shuffle_epi32::<0b11_10_11_10>(t));
t = _mm_add_epi32(t, _mm_shuffle_epi32::<0b01_01_01_01>(t));
_mm_cvtsi128_si32(t) as u32
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn sse_hv(
refp: &RefPlane8,
bx: i32,
by: i32,
fx: &[i32; 8],
fy: &[i32; 8],
src: *const u8,
src_stride: usize,
) -> u32 {
let tx = tp(fx);
let ty = tp(fy);
let base = refp.buf.as_ptr();
let row = |r: i32| base.add((by + r) as usize * refp.stride + (bx - 3) as usize);
let mut tmp = [0u8; 16 * 8];
for i in 0..7 {
let two = step2(hwin2(row(2 * i as i32 - 3), row(2 * i as i32 - 2)), &tx);
_mm_storeu_si128(tmp.as_mut_ptr().add(16 * i) as *mut __m128i, two);
}
let last = step2(hwin2(row(11), row(11)), &tx);
_mm_storel_epi64(tmp.as_mut_ptr().add(16 * 7) as *mut __m128i, last);
let mut inter = [_mm_setzero_si128(); 14];
for (j, it) in inter.iter_mut().enumerate() {
let a = _mm_loadl_epi64(tmp.as_ptr().add(8 * j) as *const __m128i);
let b = _mm_loadl_epi64(tmp.as_ptr().add(8 * (j + 1)) as *const __m128i);
*it = _mm_unpacklo_epi8(a, b);
}
let mut sse = 0u32;
for y in (0..8usize).step_by(2) {
let m: [__m256i; 4] =
std::array::from_fn(|k| _mm256_set_m128i(inter[y + 1 + 2 * k], inter[y + 2 * k]));
let two = step2(m, &ty);
sse += sse2(two, src.add(y * src_stride), src.add((y + 1) * src_stride));
}
sse
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn sse_h(
refp: &RefPlane8,
bx: i32,
by: i32,
fx: &[i32; 8],
src: *const u8,
src_stride: usize,
) -> u32 {
let tx = tp(fx);
let base = refp.buf.as_ptr();
let row = |r: i32| base.add((by + r) as usize * refp.stride + (bx - 3) as usize);
let mut sse = 0u32;
for y in (0..8).step_by(2) {
let two = step2(hwin2(row(y), row(y + 1)), &tx);
sse += sse2(
two,
src.add(y as usize * src_stride),
src.add((y + 1) as usize * src_stride),
);
}
sse
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn sse_v(
refp: &RefPlane8,
bx: i32,
by: i32,
fy: &[i32; 8],
src: *const u8,
src_stride: usize,
) -> u32 {
let ty = tp(fy);
let base = refp.buf.as_ptr();
let row = |r: i32| {
_mm_loadl_epi64(base.add((by + r) as usize * refp.stride + bx as usize) as *const __m128i)
};
let rows: [__m128i; 15] = std::array::from_fn(|j| row(j as i32 - 3));
let inter: [__m128i; 14] =
std::array::from_fn(|j| _mm_unpacklo_epi8(rows[j], rows[j + 1]));
let mut sse = 0u32;
for y in (0..8usize).step_by(2) {
let m: [__m256i; 4] =
std::array::from_fn(|k| _mm256_set_m128i(inter[y + 1 + 2 * k], inter[y + 2 * k]));
let two = step2(m, &ty);
sse += sse2(two, src.add(y * src_stride), src.add((y + 1) * src_stride));
}
sse
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn sse_copy(
r: *const u8,
rs: usize,
src: *const u8,
src_stride: usize,
) -> u32 {
let mut sse = 0u32;
for y in (0..8usize).step_by(2) {
let pred = _mm_set_epi64x(
std::ptr::read_unaligned(r.add((y + 1) * rs) as *const i64),
std::ptr::read_unaligned(r.add(y * rs) as *const i64),
);
sse += sse2(pred, src.add(y * src_stride), src.add((y + 1) * src_stride));
}
sse
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn score_v(
refp: &RefPlane8,
bx: i32,
by: i32,
fy: &[i32; 8],
src: *const u8,
src_stride: usize,
) -> u32 {
let ty = tp(fy);
let base = refp.buf.as_ptr();
let row = |r: i32| {
_mm_loadl_epi64(base.add((by + r) as usize * refp.stride + bx as usize) as *const __m128i)
};
let rows: [__m128i; 15] = std::array::from_fn(|j| row(j as i32 - 3));
let inter: [__m128i; 14] =
std::array::from_fn(|j| _mm_unpacklo_epi8(rows[j], rows[j + 1]));
let mut sad = 0u32;
for y in (0..8usize).step_by(2) {
let m: [__m256i; 4] =
std::array::from_fn(|k| _mm256_set_m128i(inter[y + 1 + 2 * k], inter[y + 2 * k]));
let two = step2(m, &ty);
sad += sad2(two, src.add(y * src_stride), src.add((y + 1) * src_stride));
}
sad
}
}
#[cfg(target_arch = "x86_64")]
mod u8bilin {
use super::RefPlane8;
use std::arch::x86_64::*;
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn bt(phase: usize) -> __m256i {
let a = (128 - 8 * phase as i32) as u8 as u16;
let b = (8 * phase as i32) as u8 as u16;
_mm256_set1_epi16(((b << 8) | a) as i16)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn bstep2(m: __m256i, t: __m256i) -> __m128i {
let v = _mm256_maddubs_epi16(m, t);
let r = _mm256_srai_epi16::<7>(_mm256_add_epi16(v, _mm256_set1_epi16(64)));
let p = _mm256_packus_epi16(r, r);
_mm_unpacklo_epi64(
_mm256_castsi256_si128(p),
_mm256_extracti128_si256::<1>(p),
)
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn bwin2(p0: *const u8, p1: *const u8) -> __m256i {
let w = _mm256_set_m128i(
_mm_loadu_si128(p1 as *const __m128i),
_mm_loadu_si128(p0 as *const __m128i),
);
_mm256_unpacklo_epi8(w, _mm256_srli_si256::<1>(w))
}
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn sad2(pred2: __m128i, s0: *const u8, s1: *const u8) -> u32 {
let s = _mm_set_epi64x(
std::ptr::read_unaligned(s1 as *const i64),
std::ptr::read_unaligned(s0 as *const i64),
);
let d = _mm_sad_epu8(pred2, s);
(_mm_cvtsi128_si32(d) as u32) + (_mm_extract_epi32::<2>(d) as u32)
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn score_hv(
refp: &RefPlane8,
bx: i32,
by: i32,
px: usize,
py: usize,
src: *const u8,
src_stride: usize,
) -> u32 {
let tx = bt(px);
let ty = bt(py);
let base = refp.buf.as_ptr();
let row = |r: i32| base.add((by + r) as usize * refp.stride + bx as usize);
let mut tmp = [0u8; 16 * 5];
for i in 0..4 {
let two = bstep2(bwin2(row(2 * i as i32), row(2 * i as i32 + 1)), tx);
_mm_storeu_si128(tmp.as_mut_ptr().add(16 * i) as *mut __m128i, two);
}
let last = bstep2(bwin2(row(8), row(8)), tx);
_mm_storel_epi64(tmp.as_mut_ptr().add(16 * 4) as *mut __m128i, last);
let mut sad = 0u32;
for y in (0..8usize).step_by(2) {
let i0 = {
let a = _mm_loadl_epi64(tmp.as_ptr().add(8 * y) as *const __m128i);
let b = _mm_loadl_epi64(tmp.as_ptr().add(8 * (y + 1)) as *const __m128i);
_mm_unpacklo_epi8(a, b)
};
let i1 = {
let a = _mm_loadl_epi64(tmp.as_ptr().add(8 * (y + 1)) as *const __m128i);
let b = _mm_loadl_epi64(tmp.as_ptr().add(8 * (y + 2)) as *const __m128i);
_mm_unpacklo_epi8(a, b)
};
let two = bstep2(_mm256_set_m128i(i1, i0), ty);
sad += sad2(two, src.add(y * src_stride), src.add((y + 1) * src_stride));
}
sad
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn score_h(
refp: &RefPlane8,
bx: i32,
by: i32,
px: usize,
src: *const u8,
src_stride: usize,
) -> u32 {
let tx = bt(px);
let base = refp.buf.as_ptr();
let row = |r: i32| base.add((by + r) as usize * refp.stride + bx as usize);
let mut sad = 0u32;
for y in (0..8).step_by(2) {
let two = bstep2(bwin2(row(y), row(y + 1)), tx);
sad += sad2(
two,
src.add(y as usize * src_stride),
src.add((y + 1) as usize * src_stride),
);
}
sad
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn score_v(
refp: &RefPlane8,
bx: i32,
by: i32,
py: usize,
src: *const u8,
src_stride: usize,
) -> u32 {
let ty = bt(py);
let base = refp.buf.as_ptr();
let row = |r: i32| {
_mm_loadl_epi64(base.add((by + r) as usize * refp.stride + bx as usize) as *const __m128i)
};
let rows: [__m128i; 9] = std::array::from_fn(|j| row(j as i32));
let mut sad = 0u32;
for y in (0..8usize).step_by(2) {
let i0 = _mm_unpacklo_epi8(rows[y], rows[y + 1]);
let i1 = _mm_unpacklo_epi8(rows[y + 1], rows[y + 2]);
let two = bstep2(_mm256_set_m128i(i1, i0), ty);
sad += sad2(two, src.add(y * src_stride), src.add((y + 1) * src_stride));
}
sad
}
}
#[cfg(target_arch = "x86_64")]
pub fn bilinear_score8x8_scalar(
refb: &[u8],
stride: usize,
bx: usize,
by: usize,
px: usize,
py: usize,
src: &[u8],
src_off: usize,
src_stride: usize,
) -> u32 {
let f = |a: u32, b: u32, p: usize| (a * (128 - 8 * p as u32) + b * (8 * p as u32) + 64) >> 7;
let mut sad = 0u32;
for y in 0..8usize {
for x in 0..8usize {
let at = |dy: usize, dx: usize| refb[(by + y + dy) * stride + bx + x + dx] as u32;
let pred = match (px != 0, py != 0) {
(false, false) => at(0, 0),
(true, false) => f(at(0, 0), at(0, 1), px),
(false, true) => f(at(0, 0), at(1, 0), py),
(true, true) => {
let t0 = f(at(0, 0), at(0, 1), px);
let t1 = f(at(1, 0), at(1, 1), px);
f(t0, t1, py)
}
};
let sv = src[src_off + y * src_stride + x] as u32;
sad += sv.abs_diff(pred);
}
}
sad
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn subpel_bilinear_score8x8_u8(
refp: &RefPlane8,
bx: i32,
by: i32,
px: usize,
py: usize,
src: *const u8,
src_stride: usize,
) -> u32 {
match (px != 0, py != 0) {
(false, false) => sad8x8_u8(
src,
src_stride,
refp.buf.as_ptr().add(by as usize * refp.stride + bx as usize),
refp.stride,
),
(true, false) => u8bilin::score_h(refp, bx, by, px, src, src_stride),
(false, true) => u8bilin::score_v(refp, bx, by, py, src, src_stride),
(true, true) => u8bilin::score_hv(refp, bx, by, px, py, src, src_stride),
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn sad8x8_x4_u8(
src: *const u8,
ss: usize,
refs: [*const u8; 4],
rs: usize,
) -> [u32; 4] {
use std::arch::x86_64::*;
let mut acc = [_mm_setzero_si128(); 4];
for y in (0..8).step_by(2) {
let sv = _mm_set_epi64x(
std::ptr::read_unaligned(src.add((y + 1) * ss) as *const i64),
std::ptr::read_unaligned(src.add(y * ss) as *const i64),
);
for (k, &r) in refs.iter().enumerate() {
let rv = _mm_set_epi64x(
std::ptr::read_unaligned(r.add((y + 1) * rs) as *const i64),
std::ptr::read_unaligned(r.add(y * rs) as *const i64),
);
acc[k] = _mm_add_epi64(acc[k], _mm_sad_epu8(sv, rv));
}
}
std::array::from_fn(|k| {
(_mm_cvtsi128_si64(acc[k]) as u32) + (_mm_extract_epi64::<1>(acc[k]) as u32)
})
}
pub struct RefPlane8<'a> {
pub buf: &'a [u8],
pub stride: usize,
pub w: i32,
pub h: i32,
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn subpel_score8x8_u8(
refp: &RefPlane8,
bx: i32,
by: i32,
subpel_x: usize,
subpel_y: usize,
filter: usize,
src: *const u8,
src_stride: usize,
) -> u32 {
let fx = &SUBPEL_FILTERS[filter][subpel_x];
let fy = &SUBPEL_FILTERS[filter][subpel_y];
match (subpel_x != 0, subpel_y != 0) {
(false, false) => sad8x8_u8(
src,
src_stride,
refp.buf.as_ptr().add(by as usize * refp.stride + bx as usize),
refp.stride,
),
(true, false) => u8score::score_h(refp, bx, by, fx, src, src_stride),
(false, true) => u8score::score_v(refp, bx, by, fy, src, src_stride),
(true, true) => u8score::score_hv(refp, bx, by, fx, fy, src, src_stride),
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn subpel_sse8x8_u8(
refp: &RefPlane8,
bx: i32,
by: i32,
subpel_x: usize,
subpel_y: usize,
filter: usize,
src: *const u8,
src_stride: usize,
) -> u32 {
let fx = &SUBPEL_FILTERS[filter][subpel_x];
let fy = &SUBPEL_FILTERS[filter][subpel_y];
match (subpel_x != 0, subpel_y != 0) {
(false, false) => u8score::sse_copy(
refp.buf.as_ptr().add(by as usize * refp.stride + bx as usize),
refp.stride,
src,
src_stride,
),
(true, false) => u8score::sse_h(refp, bx, by, fx, src, src_stride),
(false, true) => u8score::sse_v(refp, bx, by, fy, src, src_stride),
(true, true) => u8score::sse_hv(refp, bx, by, fx, fy, src, src_stride),
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn avg8_avx2(src: *const u16, dst: *mut u16, n: usize) {
use std::arch::x86_64::*;
let one = _mm256_set1_epi32(1);
let mut i = 0usize;
while i + 8 <= n {
let sv = _mm256_cvtepu16_epi32(_mm_loadu_si128(src.add(i) as *const __m128i));
let dv = _mm256_cvtepu16_epi32(_mm_loadu_si128(dst.add(i) as *const __m128i));
let a = _mm256_srai_epi32::<1>(_mm256_add_epi32(_mm256_add_epi32(sv, dv), one));
let packed = _mm256_packus_epi32(a, a);
let perm = _mm256_permute4x64_epi64::<0x08>(packed);
_mm_storeu_si128(dst.add(i) as *mut __m128i, _mm256_castsi256_si128(perm));
i += 8;
}
while i < n {
*dst.add(i) = ((*src.add(i) as i32 + *dst.add(i) as i32 + 1) >> 1) as u16;
i += 1;
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn predict_block_avx2(
refp: &RefPlane,
bx: i32,
by: i32,
fx: &[i32; 8],
fy: &[i32; 8],
subx: bool,
suby: bool,
dst: &mut [u16],
dst_stride: usize,
w: usize,
h: usize,
max: i32,
avg: bool,
) {
let buf = refp.buf.as_ptr();
let stride = refp.stride;
let dptr = dst.as_mut_ptr();
match (subx, suby) {
(false, false) => {
for y in 0..h {
let s = buf.add((by as usize + y) * stride + bx as usize);
let d = dptr.add(y * dst_stride);
if avg {
avg8_avx2(s, d, w);
} else {
std::ptr::copy_nonoverlapping(s, d, w);
}
}
}
(true, false) => {
if w == 8 && h == 8 && !avg {
predict8x8_h_avx2(refp, bx, by, fx, dptr, dst_stride, max);
return;
}
for y in 0..h {
let s = buf.add((by as usize + y) * stride + (bx - 3) as usize);
conv8_avx2(s, 1, fx, dptr.add(y * dst_stride), w, max, avg);
}
}
(false, true) => {
if w == 8 && h == 8 && !avg {
predict8x8_v_avx2(refp, bx, by, fy, dptr, dst_stride, max);
return;
}
for y in 0..h {
let s = buf.add((by + y as i32 - 3) as usize * stride + bx as usize);
conv8_avx2(s, stride, fy, dptr.add(y * dst_stride), w, max, avg);
}
}
(true, true) => {
if w == 8 && h == 8 && !avg {
predict8x8_hv_avx2(refp, bx, by, fx, fy, dptr, dst_stride, max);
return;
}
MC_TMP.with(|cell| {
let mut tmp = cell.borrow_mut();
let tmp_h = h + TAPS - 1;
let tptr = tmp.as_mut_ptr();
for r in 0..tmp_h {
let s = buf.add((by + r as i32 - 3) as usize * stride + (bx - 3) as usize);
conv8_avx2(s, 1, fx, tptr.add(r * w), w, max, false);
}
for y in 0..h {
conv8_avx2(
tptr.add(y * w) as *const u16,
w,
fy,
dptr.add(y * dst_stride),
w,
max,
avg,
);
}
});
}
}
}
#[cfg(target_arch = "aarch64")]
#[inline]
fn has_neon() -> bool {
std::arch::is_aarch64_feature_detected!("neon")
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn conv8_neon(
src: *const u16,
tap_stride: usize,
f: &[i32; 8],
dst: *mut u16,
n: usize,
max: i32,
avg: bool,
) {
use std::arch::aarch64::*;
let round = vdupq_n_s32(64);
let one = vdupq_n_s32(1);
let zero = vdupq_n_s32(0);
let maxv = vdupq_n_s32(max);
let mut i = 0usize;
while i + 4 <= n {
let mut acc = zero;
for k in 0..8 {
let s = vreinterpretq_s32_u32(vmovl_u16(vld1_u16(src.add(i + k * tap_stride))));
acc = vmlaq_n_s32(acc, s, f[k]);
}
acc = vshrq_n_s32::<7>(vaddq_s32(acc, round));
acc = vminq_s32(vmaxq_s32(acc, zero), maxv);
if avg {
let d = vreinterpretq_s32_u32(vmovl_u16(vld1_u16(dst.add(i))));
acc = vshrq_n_s32::<1>(vaddq_s32(vaddq_s32(acc, d), one));
}
vst1_u16(dst.add(i), vmovn_u32(vreinterpretq_u32_s32(acc)));
i += 4;
}
while i < n {
let mut sum = 0i32;
for k in 0..8 {
sum += *src.add(i + k * tap_stride) as i32 * f[k];
}
let v = ((sum + 64) >> 7).clamp(0, max);
*dst.add(i) = if avg {
((v + *dst.add(i) as i32 + 1) >> 1) as u16
} else {
v as u16
};
i += 1;
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn avg8_neon(src: *const u16, dst: *mut u16, n: usize) {
use std::arch::aarch64::*;
let one = vdupq_n_s32(1);
let mut i = 0usize;
while i + 4 <= n {
let s = vreinterpretq_s32_u32(vmovl_u16(vld1_u16(src.add(i))));
let d = vreinterpretq_s32_u32(vmovl_u16(vld1_u16(dst.add(i))));
let a = vshrq_n_s32::<1>(vaddq_s32(vaddq_s32(s, d), one));
vst1_u16(dst.add(i), vmovn_u32(vreinterpretq_u32_s32(a)));
i += 4;
}
while i < n {
*dst.add(i) = ((*src.add(i) as i32 + *dst.add(i) as i32 + 1) >> 1) as u16;
i += 1;
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn predict_block_neon(
refp: &RefPlane,
bx: i32,
by: i32,
fx: &[i32; 8],
fy: &[i32; 8],
subx: bool,
suby: bool,
dst: &mut [u16],
dst_stride: usize,
w: usize,
h: usize,
max: i32,
avg: bool,
) {
let buf = refp.buf.as_ptr();
let stride = refp.stride;
let dptr = dst.as_mut_ptr();
match (subx, suby) {
(false, false) => {
for y in 0..h {
let s = buf.add((by as usize + y) * stride + bx as usize);
let d = dptr.add(y * dst_stride);
if avg {
avg8_neon(s, d, w);
} else {
std::ptr::copy_nonoverlapping(s, d, w);
}
}
}
(true, false) => {
for y in 0..h {
let s = buf.add((by as usize + y) * stride + (bx - 3) as usize);
conv8_neon(s, 1, fx, dptr.add(y * dst_stride), w, max, avg);
}
}
(false, true) => {
for y in 0..h {
let s = buf.add((by + y as i32 - 3) as usize * stride + bx as usize);
conv8_neon(s, stride, fy, dptr.add(y * dst_stride), w, max, avg);
}
}
(true, true) => {
MC_TMP.with(|cell| {
let mut tmp = cell.borrow_mut();
let tmp_h = h + TAPS - 1;
let tptr = tmp.as_mut_ptr();
for r in 0..tmp_h {
let s = buf.add((by + r as i32 - 3) as usize * stride + (bx - 3) as usize);
conv8_neon(s, 1, fx, tptr.add(r * w), w, max, false);
}
for y in 0..h {
conv8_neon(
tptr.add(y * w) as *const u16,
w,
fy,
dptr.add(y * dst_stride),
w,
max,
avg,
);
}
});
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn predict_block(
refp: &RefPlane,
bx: i32,
by: i32,
subpel_x: usize,
subpel_y: usize,
filter: usize,
dst: &mut [u16],
dst_stride: usize,
w: usize,
h: usize,
avg: bool,
max: i32,
) {
let fx = &SUBPEL_FILTERS[filter][subpel_x];
let fy = &SUBPEL_FILTERS[filter][subpel_y];
#[cfg(target_arch = "x86_64")]
{
let (subx, suby) = (subpel_x != 0, subpel_y != 0);
let (nl, nr) = if subx { (3, 4) } else { (0, 0) };
let (nt, nb) = if suby { (3, 4) } else { (0, 0) };
let in_bounds = bx - nl >= 0
&& bx + w as i32 + nr <= refp.w
&& by - nt >= 0
&& by + h as i32 + nb <= refp.h;
if in_bounds && has_avx2() {
unsafe {
predict_block_avx2(
refp, bx, by, fx, fy, subx, suby, dst, dst_stride, w, h, max, avg,
);
}
return;
}
if has_avx2() && w <= 64 && h <= 64 && !no_edge_tile() {
const TS: usize = 72; thread_local! {
static TILE: std::cell::RefCell<[u16; TS * TS]> =
const { std::cell::RefCell::new([0; TS * TS]) };
}
let (tw, th) = (w + 7, h + 7);
return TILE.with(|cell| {
let mut tile = cell.borrow_mut();
for ty in 0..th {
let sy = by - 3 + ty as i32;
let row = ty * TS;
for tx in 0..tw {
tile[row + tx] = refp.px(bx - 3 + tx as i32, sy) as u16;
}
}
let tref = RefPlane {
buf: &tile[..],
stride: TS,
w: tw as i32,
h: th as i32,
};
unsafe {
predict_block_avx2(
&tref, 3, 3, fx, fy, subx, suby, dst, dst_stride, w, h, max, avg,
);
}
});
}
}
#[cfg(target_arch = "aarch64")]
{
let (subx, suby) = (subpel_x != 0, subpel_y != 0);
let (nl, nr) = if subx { (3, 4) } else { (0, 0) };
let (nt, nb) = if suby { (3, 4) } else { (0, 0) };
let in_bounds = bx - nl >= 0
&& bx + w as i32 + nr <= refp.w
&& by - nt >= 0
&& by + h as i32 + nb <= refp.h;
if in_bounds && has_neon() {
unsafe {
predict_block_neon(
refp, bx, by, fx, fy, subx, suby, dst, dst_stride, w, h, max, avg,
);
}
return;
}
}
let put = |dst: &mut [u16], o: usize, val: u16| {
dst[o] = if avg {
round_pow2(dst[o] as i32 + val as i32, 1) as u16
} else {
val
};
};
match (subpel_x != 0, subpel_y != 0) {
(false, false) => {
for y in 0..h {
for x in 0..w {
let v = refp.px(bx + x as i32, by + y as i32) as u16;
put(dst, y * dst_stride + x, v);
}
}
}
(true, false) => {
for y in 0..h {
for x in 0..w {
let mut sum = 0i32;
for (k, &f) in fx.iter().enumerate() {
sum += refp.px(bx + x as i32 + k as i32 - 3, by + y as i32) * f;
}
put(
dst,
y * dst_stride + x,
clip_pixel(round_pow2(sum, FILTER_BITS), max),
);
}
}
}
(false, true) => {
for y in 0..h {
for x in 0..w {
let mut sum = 0i32;
for (k, &f) in fy.iter().enumerate() {
sum += refp.px(bx + x as i32, by + y as i32 + k as i32 - 3) * f;
}
put(
dst,
y * dst_stride + x,
clip_pixel(round_pow2(sum, FILTER_BITS), max),
);
}
}
}
(true, true) => {
let tmp_h = h + TAPS - 1;
MC_TMP.with(|cell| {
let mut tmp = cell.borrow_mut();
for r in 0..tmp_h {
let sy = by + r as i32 - 3;
for x in 0..w {
let mut sum = 0i32;
for (k, &f) in fx.iter().enumerate() {
sum += refp.px(bx + x as i32 + k as i32 - 3, sy) * f;
}
tmp[r * w + x] = clip_pixel(round_pow2(sum, FILTER_BITS), max);
}
}
for y in 0..h {
for x in 0..w {
let mut sum = 0i32;
for (k, &f) in fy.iter().enumerate() {
sum += tmp[(y + k) * w + x] as i32 * f;
}
put(
dst,
y * dst_stride + x,
clip_pixel(round_pow2(sum, FILTER_BITS), max),
);
}
}
});
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn scaled_predict_block(
refp: &RefPlane,
bx: i32,
by: i32,
subpel_x: usize,
subpel_y: usize,
x_step_q4: i32,
y_step_q4: i32,
filter: usize,
dst: &mut [u16],
dst_stride: usize,
w: usize,
h: usize,
avg: bool,
max: i32,
) {
let fil = &SUBPEL_FILTERS[filter];
let int_h = (((h as i32 - 1) * y_step_q4 + subpel_y as i32) >> 4) as usize + TAPS;
let mut tmp = vec![0u16; int_h * w];
for r in 0..int_h {
let sy = by + r as i32 - 3;
let mut x_q4 = subpel_x as i32;
for x in 0..w {
let sx = bx + (x_q4 >> 4);
let f = &fil[(x_q4 & 15) as usize];
let mut sum = 0i32;
for (k, &c) in f.iter().enumerate() {
sum += refp.px(sx + k as i32 - 3, sy) * c;
}
tmp[r * w + x] = clip_pixel(round_pow2(sum, FILTER_BITS), max);
x_q4 += x_step_q4;
}
}
let put = |dst: &mut [u16], o: usize, val: u16| {
dst[o] = if avg {
round_pow2(dst[o] as i32 + val as i32, 1) as u16
} else {
val
};
};
for x in 0..w {
let mut y_q4 = subpel_y as i32;
for y in 0..h {
let row = (y_q4 >> 4) as usize;
let f = &fil[(y_q4 & 15) as usize];
let mut sum = 0i32;
for (k, &c) in f.iter().enumerate() {
sum += tmp[(row + k) * w + x] as i32 * c;
}
put(
dst,
y * dst_stride + x,
clip_pixel(round_pow2(sum, FILTER_BITS), max),
);
y_q4 += y_step_q4;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn filter_taps_sum_to_128() {
for f in &SUBPEL_FILTERS {
for phase in f {
assert_eq!(phase.iter().sum::<i32>(), 128);
}
}
for f in &SUBPEL_FILTERS {
assert_eq!(f[0], [0, 0, 0, 128, 0, 0, 0, 0]);
}
}
#[cfg(target_arch = "aarch64")]
#[test]
fn conv8_neon_matches_scalar() {
if !has_neon() {
return;
}
let stride = 80usize;
let mut s = 0x1234_5678u32;
let mut rng = || {
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
s
};
for &max in &[255i32, 1023, 4095] {
let src: Vec<u16> = (0..stride * 40)
.map(|_| (rng() % (max as u32 + 1)) as u16)
.collect();
for filter in 0..SUBPEL_FILTERS.len() {
for &phase in &[0usize, 1, 7, 8, 15] {
let f = &SUBPEL_FILTERS[filter][phase];
for &tap_stride in &[1usize, stride] {
for n in [1usize, 3, 4, 7, 8, 13, 16] {
let base = 5 * stride + 5;
let mut got = vec![0u16; n];
unsafe {
conv8_neon(
src.as_ptr().add(base),
tap_stride,
f,
got.as_mut_ptr(),
n,
max,
false,
);
}
let want: Vec<u16> = (0..n)
.map(|i| {
let mut sum = 0i32;
for k in 0..8 {
sum += src[base + i + k * tap_stride] as i32 * f[k];
}
((sum + 64) >> 7).clamp(0, max) as u16
})
.collect();
assert_eq!(got, want, "max={max} filter={filter} phase={phase} tap_stride={tap_stride} n={n}");
}
}
}
}
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn conv8_avx2_matches_scalar() {
if !has_avx2() {
return;
}
let stride = 80usize;
let mut s = 0x2468_1357u32;
let mut rng = || {
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
s
};
for &max in &[255i32, 1023, 4095] {
let src: Vec<u16> = (0..stride * 40)
.map(|_| (rng() % (max as u32 + 1)) as u16)
.collect();
for filter in 0..SUBPEL_FILTERS.len() {
for phase in 0..16 {
let f = &SUBPEL_FILTERS[filter][phase];
for &tap_stride in &[1usize, stride] {
for n in [1usize, 3, 7, 8, 9, 13, 16, 24] {
let base = 5 * stride + 5;
for &avg in &[false, true] {
let mut got = vec![100u16; n];
let mut want = got.clone();
unsafe {
conv8_avx2(
src.as_ptr().add(base),
tap_stride,
f,
got.as_mut_ptr(),
n,
max,
avg,
);
}
for (i, w) in want.iter_mut().enumerate() {
let mut sum = 0i32;
for k in 0..8 {
sum += src[base + i + k * tap_stride] as i32 * f[k];
}
let v = ((sum + 64) >> 7).clamp(0, max);
*w = if avg { ((v + *w as i32 + 1) >> 1) as u16 } else { v as u16 };
}
assert_eq!(got, want, "max={max} filter={filter} phase={phase} ts={tap_stride} n={n} avg={avg}");
}
}
}
}
}
}
}
#[test]
fn full_pel_copy_is_exact() {
let w = 8;
let buf: Vec<u16> = (0..64u16).collect();
let refp = RefPlane {
buf: &buf,
stride: w,
w: 8,
h: 8,
};
let mut dst = [0u16; 16];
predict_block(&refp, 1, 1, 0, 0, 0, &mut dst, 4, 4, 4, false, 255);
for y in 0..4 {
for x in 0..4 {
assert_eq!(dst[y * 4 + x], buf[(1 + y) * 8 + (1 + x)]);
}
}
}
#[test]
fn horiz_matches_manual_eighttap() {
let buf: Vec<u16> = (0..256).map(|i| i as u16).collect(); let refp = RefPlane {
buf: &buf,
stride: 16,
w: 16,
h: 16,
};
let mut dst = [0u16; 1];
predict_block(&refp, 5, 5, 8, 0, 0, &mut dst, 1, 1, 1, false, 255);
let f = &SUBPEL_FILTERS[0][8];
let mut sum = 0i32;
for (k, &c) in f.iter().enumerate() {
sum += buf[5 * 16 + (5 + k - 3)] as i32 * c;
}
assert_eq!(dst[0], clip_pixel(round_pow2(sum, FILTER_BITS), 255));
}
#[test]
fn avg_rounds_toward_existing() {
let buf = vec![200u16; 64];
let refp = RefPlane {
buf: &buf,
stride: 8,
w: 8,
h: 8,
};
let mut dst = [100u16; 16];
predict_block(&refp, 0, 0, 0, 0, 0, &mut dst, 4, 4, 4, true, 255);
assert!(dst.iter().take(4).all(|&v| v == 150));
}
#[cfg(target_arch = "x86_64")]
#[test]
fn predict8x8_fused_matches_scalar() {
if !has_avx2() {
return;
}
let mut s = 0x0f0f_5a5a_1234_9999u64;
let mut xs = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let (pw, ph, stride) = (48i32, 48i32, 48usize);
for &max in &[255i32, 1023, 4095] {
let buf: Vec<u16> = (0..stride * ph as usize)
.map(|_| (xs() % (max as u64 + 1)) as u16)
.collect();
let refp = RefPlane {
buf: &buf,
stride,
w: pw,
h: ph,
};
for filter in 0..4usize {
for phase_x in 1..16usize {
for phase_y in [1usize, 7, 8, 15] {
let (bx, by) = (10 + (xs() % 20) as i32, 10 + (xs() % 20) as i32);
let fx = &SUBPEL_FILTERS[filter][phase_x];
let fy = &SUBPEL_FILTERS[filter][phase_y];
let mut want = [0u16; 64];
for y in 0..8i32 {
for x in 0..8i32 {
let mut tmp_col = [0i32; 8];
for (k, t) in tmp_col.iter_mut().enumerate() {
let mut sum = 0i32;
for (j, &f) in fx.iter().enumerate() {
sum += refp.px(
bx + x + j as i32 - 3,
by + y + k as i32 - 3,
) * f;
}
*t = clip_pixel(round_pow2(sum, 7), max) as i32;
}
let mut sum = 0i32;
for (k, &f) in fy.iter().enumerate() {
sum += tmp_col[k] * f;
}
want[(y * 8 + x) as usize] =
clip_pixel(round_pow2(sum, 7), max);
}
}
let mut got = [0u16; 64];
unsafe {
predict8x8_hv_avx2(&refp, bx, by, fx, fy, got.as_mut_ptr(), 8, max);
}
assert_eq!(
got, want,
"filter={filter} px={phase_x} py={phase_y} max={max}"
);
}
}
}
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn predict8x8_single_pass_matches_scalar() {
if !has_avx2() {
return;
}
let mut s = 0x7777_1212_dead_4444u64;
let mut xs = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let (pw, ph, stride) = (48i32, 48i32, 48usize);
for &max in &[255i32, 1023, 4095] {
let buf: Vec<u16> = (0..stride * ph as usize)
.map(|_| (xs() % (max as u64 + 1)) as u16)
.collect();
let refp = RefPlane {
buf: &buf,
stride,
w: pw,
h: ph,
};
for filter in 0..4usize {
for phase in 1..16usize {
let (bx, by) = (10 + (xs() % 20) as i32, 10 + (xs() % 20) as i32);
let f = &SUBPEL_FILTERS[filter][phase];
let mut want = [0u16; 64];
for y in 0..8i32 {
for x in 0..8i32 {
let mut sum = 0i32;
for (k, &t) in f.iter().enumerate() {
sum += refp.px(bx + x + k as i32 - 3, by + y) * t;
}
want[(y * 8 + x) as usize] = clip_pixel(round_pow2(sum, 7), max);
}
}
let mut got = [0u16; 64];
unsafe { predict8x8_h_avx2(&refp, bx, by, f, got.as_mut_ptr(), 8, max) };
assert_eq!(got, want, "H filter={filter} phase={phase} max={max}");
for y in 0..8i32 {
for x in 0..8i32 {
let mut sum = 0i32;
for (k, &t) in f.iter().enumerate() {
sum += refp.px(bx + x, by + y + k as i32 - 3) * t;
}
want[(y * 8 + x) as usize] = clip_pixel(round_pow2(sum, 7), max);
}
}
unsafe { predict8x8_v_avx2(&refp, bx, by, f, got.as_mut_ptr(), 8, max) };
assert_eq!(got, want, "V filter={filter} phase={phase} max={max}");
}
}
}
}
#[test]
fn pmaddubsw_preconditions_hold() {
for f in &SUBPEL_FILTERS {
for phase in 1..16 {
let t = &f[phase];
let pos = |v: i32| if v > 0 { v } else { 0 };
for x in t {
assert!(x.abs() <= 127, "tap {x} does not fit i8");
}
for k in 0..4 {
let pair_pos = pos(t[2 * k]) + pos(t[2 * k + 1]);
assert!(255 * pair_pos <= 32767, "pair {k} saturates pmaddubsw");
}
let g1 = pos(t[0]) + pos(t[1]) + pos(t[4]) + pos(t[5]);
let g2 = pos(t[2]) + pos(t[3]) + pos(t[6]) + pos(t[7]);
assert!(255 * g1 <= 32767, "group (01+45) saturates");
assert!(255 * g2 <= 32767, "group (23+67) saturates");
let neg: i32 = t.iter().filter(|&&v| v < 0).sum();
assert!(255 * neg >= -32768, "negative reach saturates");
}
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn u8_score_saturation_adversarial() {
if !has_avx2() {
return;
}
let (pw, ph, stride) = (48i32, 48i32, 48usize);
for pattern in 0..6u32 {
let refbuf16: Vec<u16> = (0..stride * ph as usize)
.map(|i| {
let (x, y) = (i % stride, i / stride);
let bit = match pattern {
0 => x % 2 == 0,
1 => y % 2 == 0,
2 => (x + y) % 2 == 0,
3 => x % 4 < 2,
4 => y % 4 < 2,
_ => (x / 2 + y / 2) % 2 == 0,
};
if bit { 255 } else { 0 }
})
.collect();
let refbuf8: Vec<u8> = refbuf16.iter().map(|&v| v as u8).collect();
let srcbuf8: Vec<u8> = vec![128u8; stride * ph as usize];
let srcbuf16: Vec<u16> = srcbuf8.iter().map(|&v| v as u16).collect();
let refp16 = RefPlane { buf: &refbuf16, stride, w: pw, h: ph };
let refp8 = RefPlane8 { buf: &refbuf8, stride, w: pw, h: ph };
for filter in 0..4usize {
for px in 0..16usize {
for py in 0..16usize {
let (bx, by) = (16, 16);
let mut pred = [0u16; 64];
predict_block(
&refp16, bx, by, px, py, filter, &mut pred, 8, 8, 8, false, 255,
);
let mut want = 0u32;
for y in 0..8usize {
for x in 0..8usize {
let sv = srcbuf16[(20 + y) * stride + 20 + x] as i32;
want += (sv - pred[y * 8 + x] as i32).unsigned_abs();
}
}
let got = unsafe {
subpel_score8x8_u8(
&refp8,
bx,
by,
px,
py,
filter,
srcbuf8.as_ptr().add(20 * stride + 20),
stride,
)
};
assert_eq!(got, want, "pattern={pattern} filter={filter} px={px} py={py}");
}
}
}
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn bilinear_score_matches_scalar() {
if !has_avx2() {
return;
}
let mut st = 0xb111_2026_0716_ccccu64;
let mut xs = move || {
st ^= st << 13;
st ^= st >> 7;
st ^= st << 17;
st
};
let (pw, ph, stride) = (48i32, 48i32, 48usize);
for mode in 0..2u32 {
let refbuf8: Vec<u8> = (0..stride * ph as usize)
.map(|i| match mode {
0 => (xs() % 256) as u8,
_ => {
let (x, y) = (i % stride, i / stride);
if (x + y) % 2 == 0 { 255 } else { 0 }
}
})
.collect();
let srcbuf8: Vec<u8> = (0..stride * ph as usize).map(|_| (xs() % 256) as u8).collect();
let refp8 = RefPlane8 { buf: &refbuf8, stride, w: pw, h: ph };
for px in 0..16usize {
for py in 0..16usize {
let (bx, by) = (4 + (xs() % 30) as usize, 4 + (xs() % 30) as usize);
let (sx, sy) = (4 + (xs() % 30) as usize, 4 + (xs() % 30) as usize);
let want = bilinear_score8x8_scalar(
&refbuf8, stride, bx, by, px, py, &srcbuf8, sy * stride + sx, stride,
);
let got = unsafe {
subpel_bilinear_score8x8_u8(
&refp8,
bx as i32,
by as i32,
px,
py,
srcbuf8.as_ptr().add(sy * stride + sx),
stride,
)
};
assert_eq!(got, want, "mode={mode} px={px} py={py} bx={bx} by={by}");
}
}
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn u8_sse_matches_u16_path() {
if !has_avx2() {
return;
}
let mut st = 0x5ee5_0716_2026_bbbbu64;
let mut xs = move || {
st ^= st << 13;
st ^= st >> 7;
st ^= st << 17;
st
};
let (pw, ph, stride) = (48i32, 48i32, 48usize);
for mode in 0..3u32 {
let refbuf16: Vec<u16> = (0..stride * ph as usize)
.map(|i| match mode {
0 => (xs() % 256) as u16,
1 => {
let (x, y) = (i % stride, i / stride);
if (x + y) % 2 == 0 { 255 } else { 0 }
}
_ => if (i % stride) % 4 < 2 { 255 } else { 0 },
})
.collect();
let refbuf8: Vec<u8> = refbuf16.iter().map(|&v| v as u8).collect();
let srcbuf16: Vec<u16> =
(0..stride * ph as usize).map(|_| (xs() % 256) as u16).collect();
let srcbuf8: Vec<u8> = srcbuf16.iter().map(|&v| v as u8).collect();
let refp16 = RefPlane { buf: &refbuf16, stride, w: pw, h: ph };
let refp8 = RefPlane8 { buf: &refbuf8, stride, w: pw, h: ph };
for filter in 0..4usize {
for px in 0..16usize {
for py in [0usize, 1, 5, 8, 12, 15] {
let (bx, by) = (10 + (xs() % 18) as i32, 10 + (xs() % 18) as i32);
let (sx, sy) = (8 + (xs() % 18) as usize, 8 + (xs() % 18) as usize);
let mut pred = [0u16; 64];
predict_block(
&refp16, bx, by, px, py, filter, &mut pred, 8, 8, 8, false, 255,
);
let mut want = 0u32;
for y in 0..8usize {
for x in 0..8usize {
let d = srcbuf16[(sy + y) * stride + sx + x] as i32
- pred[y * 8 + x] as i32;
want += (d * d) as u32;
}
}
let got = unsafe {
subpel_sse8x8_u8(
&refp8,
bx,
by,
px,
py,
filter,
srcbuf8.as_ptr().add(sy * stride + sx),
stride,
)
};
assert_eq!(got, want, "mode={mode} filter={filter} px={px} py={py}");
}
}
}
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn u8_score_matches_u16_path() {
if !has_avx2() {
return;
}
let mut s = 0x0dd0_2026_0715_aaaau64;
let mut xs = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
let (pw, ph, stride) = (48i32, 48i32, 48usize);
let refbuf16: Vec<u16> = (0..stride * ph as usize).map(|_| (xs() % 256) as u16).collect();
let refbuf8: Vec<u8> = refbuf16.iter().map(|&v| v as u8).collect();
let srcbuf16: Vec<u16> = (0..stride * ph as usize).map(|_| (xs() % 256) as u16).collect();
let srcbuf8: Vec<u8> = srcbuf16.iter().map(|&v| v as u8).collect();
let refp16 = RefPlane { buf: &refbuf16, stride, w: pw, h: ph };
let refp8 = RefPlane8 { buf: &refbuf8, stride, w: pw, h: ph };
for filter in 0..4usize {
for px in 0..16usize {
for py in [0usize, 1, 7, 8, 15] {
let (bx, by) = (10 + (xs() % 20) as i32, 10 + (xs() % 20) as i32);
let (sx, sy) = (8 + (xs() % 20) as usize, 8 + (xs() % 20) as usize);
let mut pred = [0u16; 64];
predict_block(&refp16, bx, by, px, py, filter, &mut pred, 8, 8, 8, false, 255);
let mut want = 0u32;
for y in 0..8usize {
for x in 0..8usize {
let sv = srcbuf16[(sy + y) * stride + sx + x] as i32;
want += (sv - pred[y * 8 + x] as i32).unsigned_abs();
}
}
let got = unsafe {
subpel_score8x8_u8(
&refp8,
bx,
by,
px,
py,
filter,
srcbuf8.as_ptr().add(sy * stride + sx),
stride,
)
};
assert_eq!(got, want, "filter={filter} px={px} py={py} bx={bx} by={by}");
}
}
}
}
#[test]
fn compound_avg_matches_mc_blend() {
let stride = 48usize;
let buf: Vec<u16> = (0..stride * 48)
.map(|i| ((i * 7 + 13) % 256) as u16)
.collect();
let refp = RefPlane {
buf: &buf,
stride,
w: 48,
h: 48,
};
let max = 255;
for &(sx, sy) in &[(0usize, 0usize), (8, 0), (0, 8), (8, 8), (4, 12)] {
for &w in &[4usize, 8, 16] {
let h = w;
let (bx, by) = (12i32, 12i32); let dst0: Vec<u16> = (0..w * h).map(|i| ((i * 3 + 41) % 256) as u16).collect();
let mut dst_avg = dst0.clone();
predict_block(&refp, bx, by, sx, sy, 0, &mut dst_avg, w, w, h, true, max);
let mut mc = vec![0u16; w * h];
predict_block(&refp, bx, by, sx, sy, 0, &mut mc, w, w, h, false, max);
let manual: Vec<u16> = dst0
.iter()
.zip(&mc)
.map(|(&a, &b)| ((a as i32 + b as i32 + 1) >> 1) as u16)
.collect();
assert_eq!(
dst_avg, manual,
"compound mismatch: subpel ({sx},{sy}), w={w}"
);
}
}
}
}
#[cfg(test)]
mod mc_microbench {
use super::*;
fn rdtsc() -> u64 {
#[cfg(target_arch = "x86_64")]
unsafe {
std::arch::x86_64::_rdtsc()
}
#[cfg(not(target_arch = "x86_64"))]
0
}
fn bench(w: usize, h: usize, subx: usize, suby: usize, interior: bool) -> f64 {
let (rw, rh) = (352usize, 288usize);
let stride = 448usize;
let mut src = vec![0u16; stride * (rh + 80)];
let mut st = 0x1234_5678_9ABC_DEF0u64;
for p in src.iter_mut() {
st ^= st << 13;
st ^= st >> 7;
st ^= st << 17;
*p = (st % 256) as u16;
}
let refp = RefPlane {
buf: &src,
stride,
w: rw as i32,
h: rh as i32,
};
let mut dst = vec![128u16; stride * (h + 8)];
let (bx, by) = if interior { (40i32, 40i32) } else { (0i32, 0i32) };
let iters = 2000usize;
let mut best = f64::MAX;
for _ in 0..9 {
let t0 = rdtsc();
for _ in 0..iters {
predict_block(
&refp, bx, by, subx, suby, 0, &mut dst, stride, w, h, false, 255,
);
}
best = best.min((rdtsc() - t0) as f64 / iters as f64);
}
best
}
#[test]
#[ignore = "microbenchmark; run explicitly with --ignored --nocapture"]
fn profile_predict_block() {
let _ = bench(8, 8, 4, 4, true); println!("\npredict_block — best-of-9, cycles/call");
println!(" {:<10} {:>10} {:>10} {:>10} {:>12}", "size", "full-pel", "x-only", "xy", "xy(edge)");
for (w, h) in [(4usize, 4usize), (4, 8), (8, 8), (8, 16), (16, 16), (32, 32), (64, 64)] {
println!(
" {:<10} {:>10.1} {:>10.1} {:>10.1} {:>12.1}",
format!("{w}x{h}"),
bench(w, h, 0, 0, true),
bench(w, h, 4, 0, true),
bench(w, h, 4, 4, true),
bench(w, h, 4, 4, false),
);
}
}
}