use std::sync::OnceLock;
pub(crate) type SatdFn = unsafe fn(&[u16], &[u16], usize) -> u32;
static SATD: OnceLock<SatdFn> = OnceLock::new();
#[inline]
pub(crate) fn resolve_satd() -> SatdFn {
*SATD.get_or_init(|| {
#[cfg(all(target_arch = "aarch64", feature = "neon"))]
{
crate::neon::satd_neon as SatdFn
}
#[cfg(all(target_arch = "x86_64", feature = "avx"))]
{
let mut f = satd_scalar as SatdFn;
if std::is_x86_feature_detected!("avx2") {
f = crate::avx::satd_avx2 as SatdFn;
}
f
}
#[cfg(not(any(
all(target_arch = "aarch64", feature = "neon"),
all(target_arch = "x86_64", feature = "avx")
)))]
{
satd_scalar as SatdFn
}
})
}
#[inline]
#[cfg_attr(
any(
all(target_arch = "aarch64", feature = "neon"),
all(target_arch = "x86_64", feature = "avx")
),
allow(dead_code)
)]
pub(crate) fn satd_scalar(orig: &[u16], pred: &[u16], n: usize) -> u32 {
match n {
4 => satd_scalar_n::<4>(orig, pred),
8 => satd_scalar_n::<8>(orig, pred),
16 => satd_scalar_n::<16>(orig, pred),
32 => satd_scalar_n::<32>(orig, pred),
_ => panic!("unsupported SATD block size {n}"),
}
}
#[inline]
#[cfg_attr(
any(
all(target_arch = "aarch64", feature = "neon"),
all(target_arch = "x86_64", feature = "avx")
),
allow(dead_code)
)]
fn satd_scalar_n<const N: usize>(orig: &[u16], pred: &[u16]) -> u32 {
assert!(orig.len() >= N * N && pred.len() >= N * N);
let mut total = 0u32;
let mut diff = [0i32; 16];
for (orig_band, pred_band) in orig[..N * N]
.chunks_exact(N * 4)
.zip(pred[..N * N].chunks_exact(N * 4))
{
for bx in (0..N).step_by(4) {
for ((dst_row, orig_row), pred_row) in diff
.as_chunks_mut::<4>()
.0
.iter_mut()
.zip(orig_band.as_chunks::<N>().0.iter())
.zip(pred_band.as_chunks::<N>().0.iter())
{
for ((dst, &orig), &pred) in dst_row
.iter_mut()
.zip(&orig_row[bx..bx + 4])
.zip(&pred_row[bx..bx + 4])
{
*dst = orig as i32 - pred as i32;
}
}
for row in diff.as_chunks_mut::<4>().0 {
let a0 = row[0] + row[2];
let a1 = row[1] + row[3];
let a2 = row[0] - row[2];
let a3 = row[1] - row[3];
row[0] = a0 + a1;
row[1] = a0 - a1;
row[2] = a2 + a3;
row[3] = a2 - a3;
}
for col in 0..4 {
let a0 = diff[col] + diff[8 + col];
let a1 = diff[4 + col] + diff[12 + col];
let a2 = diff[col] - diff[8 + col];
let a3 = diff[4 + col] - diff[12 + col];
diff[col] = a0 + a1;
diff[4 + col] = a0 - a1;
diff[8 + col] = a2 + a3;
diff[12 + col] = a2 - a3;
}
let sum: u32 = diff.iter().map(|value| value.unsigned_abs()).sum();
total += (sum + 1) >> 1;
}
}
total
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn satd_known_4x4_dc_difference() {
let orig = [12u16; 16];
let pred = [4u16; 16];
assert_eq!(unsafe { satd_scalar(&orig, &pred, 4) }, 64);
}
#[test]
fn resolved_satd_matches_scalar() {
let mut orig = [0u16; 1024];
let mut pred = [0u16; 1024];
for i in 0..orig.len() {
orig[i] = ((i * 67 + i / 7 * 131) & 4095) as u16;
pred[i] = ((i * 29 + i / 3 * 47 + 11) & 4095) as u16;
}
let resolved = resolve_satd();
for n in [4, 8, 16, 32] {
assert_eq!(
unsafe { resolved(&orig[..n * n], &pred[..n * n], n) },
unsafe { satd_scalar(&orig[..n * n], &pred[..n * n], n) },
);
}
}
}