fn satd4x4_scalar(a: &[u8], sa: usize, b: &[u8], sb: usize) -> i32 {
let mut m = [[0i32; 4]; 4];
for i in 0..4 {
for j in 0..4 {
m[i][j] = a[i * sa + j] as i32 - b[i * sb + j] as i32;
}
}
for row in m.iter_mut() {
let (s0, s1, s2, s3) = (row[0] + row[2], row[1] + row[3], row[0] - row[2], row[1] - row[3]);
*row = [s0 + s1, s2 + s3, s2 - s3, s0 - s1];
}
let mut sum = 0i32;
for j in 0..4 {
let (s0, s1, s2, s3) =
(m[0][j] + m[2][j], m[1][j] + m[3][j], m[0][j] - m[2][j], m[1][j] - m[3][j]);
let (c0, c1, c2, c3) = (s0 + s1, s2 + s3, s2 - s3, s0 - s1);
sum += c0.abs() + c1.abs() + c2.abs() + c3.abs();
}
(sum + 1) >> 1
}
fn satd_region_scalar(a: &[u8], sa: usize, b: &[u8], sb: usize, w: usize, h: usize) -> i32 {
let mut s = 0;
let mut by = 0;
while by < h {
let mut bx = 0;
while bx < w {
s += satd4x4_scalar(&a[by * sa + bx..], sa, &b[by * sb + bx..], sb);
bx += 4;
}
by += 4;
}
s
}
fn sad_scalar(a: &[u8], sa: usize, b: &[u8], sb: usize, w: usize, h: usize) -> i32 {
let mut s = 0i32;
for i in 0..h {
for j in 0..w {
s += (a[i * sa + j] as i32 - b[i * sb + j] as i32).abs();
}
}
s
}
#[cfg(target_arch = "x86_64")]
pub(crate) mod x86 {
use std::arch::x86_64::*;
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn diff16(a: *const u8, b: *const u8) -> __m256i {
let va = _mm256_cvtepu8_epi16(_mm_loadu_si128(a as *const __m128i));
let vb = _mm256_cvtepu8_epi16(_mm_loadu_si128(b as *const __m128i));
_mm256_sub_epi16(va, vb)
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn diff8x2(a: *const u8, b: *const u8, sa: usize, sb: usize) -> __m256i {
let lo = _mm_unpacklo_epi64(
_mm_loadl_epi64(a as *const __m128i),
_mm_loadl_epi64(a.add(4 * sa) as *const __m128i));
let hi = _mm_unpacklo_epi64(
_mm_loadl_epi64(b as *const __m128i),
_mm_loadl_epi64(b.add(4 * sb) as *const __m128i));
_mm256_sub_epi16(_mm256_cvtepu8_epi16(lo), _mm256_cvtepu8_epi16(hi))
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn finish_band(acc: __m256i) -> i32 {
let pair = _mm256_hadd_epi32(acc, acc); let r = _mm256_srai_epi32::<1>(_mm256_add_epi32(pair, _mm256_set1_epi32(1)));
_mm256_extract_epi32::<0>(r) + _mm256_extract_epi32::<1>(r)
+ _mm256_extract_epi32::<4>(r) + _mm256_extract_epi32::<5>(r)
}
#[target_feature(enable = "avx2")]
pub unsafe fn satd_w16(a: *const u8, sa: usize, b: *const u8, sb: usize, h: usize) -> i32 {
let mut total = 0;
let mut r = 0;
while r < h {
let acc = crate::x86_asm::satd_avg::hadamard4_abs_acc(
diff16(a.add(r * sa), b.add(r * sb)),
diff16(a.add((r + 1) * sa), b.add((r + 1) * sb)),
diff16(a.add((r + 2) * sa), b.add((r + 2) * sb)),
diff16(a.add((r + 3) * sa), b.add((r + 3) * sb)),
_mm256_setzero_si256(),
);
total += finish_band(acc);
r += 4;
}
total
}
#[target_feature(enable = "avx2")]
pub unsafe fn satd_w8(a: *const u8, sa: usize, b: *const u8, sb: usize, h: usize) -> i32 {
let mut total = 0;
let mut r = 0;
while r < h {
let acc = crate::x86_asm::satd_avg::hadamard4_abs_acc(
diff8x2(a.add(r * sa), b.add(r * sb), sa, sb),
diff8x2(a.add((r + 1) * sa), b.add((r + 1) * sb), sa, sb),
diff8x2(a.add((r + 2) * sa), b.add((r + 2) * sb), sa, sb),
diff8x2(a.add((r + 3) * sa), b.add((r + 3) * sb), sa, sb),
_mm256_setzero_si256(),
);
total += finish_band(acc);
r += 8;
}
total
}
#[inline]
pub unsafe fn sad(a: *const u8, sa: usize, b: *const u8, sb: usize, w: usize, h: usize) -> i32 {
let mut acc = _mm_setzero_si128();
for r in 0..h {
let (pa, pb) = (a.add(r * sa), b.add(r * sb));
if w == 16 {
acc = _mm_add_epi32(acc, _mm_sad_epu8(
_mm_loadu_si128(pa as *const __m128i), _mm_loadu_si128(pb as *const __m128i)));
} else {
acc = _mm_add_epi32(acc, _mm_sad_epu8(
_mm_loadl_epi64(pa as *const __m128i), _mm_loadl_epi64(pb as *const __m128i)));
}
}
_mm_cvtsi128_si32(acc) + _mm_extract_epi16::<4>(acc)
}
}
#[cfg(target_arch = "aarch64")]
mod arm {
use std::arch::aarch64::*;
#[inline]
#[target_feature(enable = "neon")]
pub unsafe fn sad(a: *const u8, sa: usize, b: *const u8, sb: usize, w: usize, h: usize) -> i32 {
let mut acc = vdupq_n_u32(0);
for r in 0..h {
let (pa, pb) = (a.add(r * sa), b.add(r * sb));
if w == 16 {
let d = vabdq_u8(vld1q_u8(pa), vld1q_u8(pb));
acc = vpadalq_u16(acc, vpaddlq_u8(d));
} else {
let d = vabd_u8(vld1_u8(pa), vld1_u8(pb));
acc = vpadalq_u16(acc, vcombine_u16(vpaddl_u8(d), vdup_n_u16(0)));
}
}
vaddvq_u32(acc) as i32
}
}
macro_rules! satd_fn {
($name:ident, $w:expr, $h:expr, $simd:ident) => {
#[doc = concat!("SATD of two ", stringify!($w), "x", stringify!($h),
" blocks: `Σ_4x4 ((Σ|H·d| + 1) >> 1)`.")]
pub fn $name(a: &[u8], sa: usize, b: &[u8], sb: usize) -> i32 {
assert!(a.len() >= ($h - 1) * sa + $w && b.len() >= ($h - 1) * sb + $w);
#[cfg(target_arch = "x86_64")]
if std::is_x86_feature_detected!("avx2") {
return unsafe { x86::$simd(a.as_ptr(), sa, b.as_ptr(), sb, $h) };
}
satd_region_scalar(a, sa, b, sb, $w, $h)
}
};
}
satd_fn!(satd_16x16, 16, 16, satd_w16);
satd_fn!(satd_16x8, 16, 8, satd_w16);
satd_fn!(satd_8x16, 8, 16, satd_w8);
satd_fn!(satd_8x8, 8, 8, satd_w8);
pub fn satd_4x4(a: &[u8], sa: usize, b: &[u8], sb: usize) -> i32 {
assert!(a.len() >= 3 * sa + 4 && b.len() >= 3 * sb + 4);
satd4x4_scalar(a, sa, b, sb)
}
macro_rules! sad_fn {
($name:ident, $w:expr, $h:expr) => {
#[doc = concat!("SAD of two ", stringify!($w), "x", stringify!($h), " blocks.")]
pub fn $name(a: &[u8], sa: usize, b: &[u8], sb: usize) -> i32 {
assert!(a.len() >= ($h - 1) * sa + $w && b.len() >= ($h - 1) * sb + $w);
#[cfg(target_arch = "x86_64")]
return unsafe { x86::sad(a.as_ptr(), sa, b.as_ptr(), sb, $w, $h) };
#[cfg(target_arch = "aarch64")]
if std::arch::is_aarch64_feature_detected!("neon") {
return unsafe { arm::sad(a.as_ptr(), sa, b.as_ptr(), sb, $w, $h) };
}
#[allow(unreachable_code)]
sad_scalar(a, sa, b, sb, $w, $h)
}
};
}
sad_fn!(sad_16x16, 16, 16);
sad_fn!(sad_16x8, 16, 8);
sad_fn!(sad_8x16, 8, 16);
#[cfg(target_arch = "x86_64")]
pub(crate) mod cshim {
macro_rules! shim {
($name:ident, $inner:ident, $h:expr) => {
pub(crate) unsafe extern "C" fn $name(p1: *const u8, s1: i32, p2: *const u8, s2: i32) -> i32 {
super::x86::$inner(p1, s1 as usize, p2, s2 as usize, $h)
}
};
}
shim!(satd16x16, satd_w16, 16);
shim!(satd16x8, satd_w16, 8);
shim!(satd8x16, satd_w8, 16);
shim!(satd8x8, satd_w8, 8);
}
#[cfg(test)]
mod tests {
use super::*;
fn corpus(seed: usize) -> (Vec<u8>, Vec<u8>) {
let (mut a, mut b) = (vec![0u8; 16 * 16], vec![0u8; 16 * 16]);
for i in 0..16 {
for j in 0..16 {
a[i * 16 + j] = ((i * 37 + j * 101 + seed * 3) & 0xff) as u8;
b[i * 16 + j] = ((i * 53 + j * 17 + seed * 29 + 7) & 0xff) as u8;
}
}
(a, b)
}
#[test]
fn satd_family_matches_scalar() {
for seed in 0..96 {
let (a, b) = corpus(seed);
assert_eq!(satd_16x16(&a, 16, &b, 16), satd_region_scalar(&a, 16, &b, 16, 16, 16), "16x16 {seed}");
assert_eq!(satd_16x8(&a, 16, &b, 16), satd_region_scalar(&a, 16, &b, 16, 16, 8), "16x8 {seed}");
assert_eq!(satd_8x16(&a, 16, &b, 16), satd_region_scalar(&a, 16, &b, 16, 8, 16), "8x16 {seed}");
assert_eq!(satd_8x8(&a, 16, &b, 16), satd_region_scalar(&a, 16, &b, 16, 8, 8), "8x8 {seed}");
assert_eq!(satd_4x4(&a, 16, &b, 16), satd4x4_scalar(&a, 16, &b, 16), "4x4 {seed}");
}
}
#[test]
fn sad_family_matches_scalar() {
for seed in 0..96 {
let (a, b) = corpus(seed);
assert_eq!(sad_16x16(&a, 16, &b, 16), sad_scalar(&a, 16, &b, 16, 16, 16), "16x16 {seed}");
assert_eq!(sad_16x8(&a, 16, &b, 16), sad_scalar(&a, 16, &b, 16, 16, 8), "16x8 {seed}");
assert_eq!(sad_8x16(&a, 16, &b, 16), sad_scalar(&a, 16, &b, 16, 8, 16), "8x16 {seed}");
}
}
#[test]
fn extremes_match_scalar() {
for (va, vb) in [(0u8, 255u8), (255, 0), (0, 0), (255, 255)] {
let (a, b) = (vec![va; 16 * 16], vec![vb; 16 * 16]);
assert_eq!(satd_16x16(&a, 16, &b, 16), satd_region_scalar(&a, 16, &b, 16, 16, 16));
assert_eq!(sad_16x16(&a, 16, &b, 16), sad_scalar(&a, 16, &b, 16, 16, 16));
}
let mut a = vec![0u8; 16 * 16];
let mut b = vec![0u8; 16 * 16];
for i in 0..16 {
for j in 0..16 {
a[i * 16 + j] = if (i + j) % 2 == 0 { 255 } else { 0 };
b[i * 16 + j] = if i % 2 == 0 { 0 } else { 255 };
}
}
assert_eq!(satd_16x16(&a, 16, &b, 16), satd_region_scalar(&a, 16, &b, 16, 16, 16));
assert_eq!(satd_8x8(&a, 16, &b, 16), satd_region_scalar(&a, 16, &b, 16, 8, 8));
}
}