use core::arch::aarch64::*;
#[target_feature(enable = "neon")]
pub(crate) unsafe fn satd_neon(orig: &[u16], pred: &[u16], n: usize) -> u32 {
assert!(matches!(n, 4 | 8 | 16 | 32));
assert!(orig.len() >= n * n && pred.len() >= n * n);
debug_assert!(orig[..n * n].iter().all(|&sample| sample <= 4095));
debug_assert!(pred[..n * n].iter().all(|&sample| sample <= 4095));
if n == 4 {
return unsafe { satd_4x4(orig.as_ptr(), pred.as_ptr(), 4) };
}
let mut total = 0u32;
for by in (0..n).step_by(4) {
for bx in (0..n).step_by(8) {
let offset = by * n + bx;
total += unsafe { satd_4x8(orig.as_ptr().add(offset), pred.as_ptr().add(offset), n) };
}
}
total
}
#[inline]
#[target_feature(enable = "neon")]
fn hadamard4x2_s16(row: int16x8_t) -> int16x8_t {
let opposite = vreinterpretq_s16_s32(vrev64q_s32(vreinterpretq_s32_s16(row)));
let pair_add = vaddq_s16(row, opposite);
let pair_sub = vsubq_s16(row, opposite);
let butterfly = vreinterpretq_s16_s32(vtrn1q_s32(
vreinterpretq_s32_s16(pair_add),
vreinterpretq_s32_s16(pair_sub),
));
let adjacent = vrev32q_s16(butterfly);
vtrn1q_s16(
vaddq_s16(butterfly, adjacent),
vsubq_s16(butterfly, adjacent),
)
}
#[inline]
#[target_feature(enable = "neon")]
fn transpose4x2_s16(rows: [int16x8_t; 4]) -> [int16x8_t; 4] {
let t0 = vtrnq_s16(rows[0], rows[1]);
let t1 = vtrnq_s16(rows[2], rows[3]);
let even = vtrnq_s32(vreinterpretq_s32_s16(t0.0), vreinterpretq_s32_s16(t1.0));
let odd = vtrnq_s32(vreinterpretq_s32_s16(t0.1), vreinterpretq_s32_s16(t1.1));
[
vreinterpretq_s16_s32(even.0),
vreinterpretq_s16_s32(odd.0),
vreinterpretq_s16_s32(even.1),
vreinterpretq_s16_s32(odd.1),
]
}
#[inline]
#[target_feature(enable = "neon")]
fn hadamard4(row: int32x4_t) -> int32x4_t {
let lo = vget_low_s32(row);
let hi = vget_high_s32(row);
let pair_add = vadd_s32(lo, hi);
let pair_sub = vsub_s32(lo, hi);
let add_sum = vpadd_s32(pair_add, pair_add);
let add_difference = vsub_s32(pair_add, vrev64_s32(pair_add));
let sub_sum = vpadd_s32(pair_sub, pair_sub);
let sub_difference = vsub_s32(pair_sub, vrev64_s32(pair_sub));
vcombine_s32(
vzip1_s32(add_sum, add_difference),
vzip1_s32(sub_sum, sub_difference),
)
}
#[inline]
#[target_feature(enable = "neon")]
fn transpose4(rows: [int32x4_t; 4]) -> [int32x4_t; 4] {
let t0 = vtrnq_s32(rows[0], rows[1]);
let t1 = vtrnq_s32(rows[2], rows[3]);
[
vreinterpretq_s32_s64(vtrn1q_s64(
vreinterpretq_s64_s32(t0.0),
vreinterpretq_s64_s32(t1.0),
)),
vreinterpretq_s32_s64(vtrn1q_s64(
vreinterpretq_s64_s32(t0.1),
vreinterpretq_s64_s32(t1.1),
)),
vreinterpretq_s32_s64(vtrn2q_s64(
vreinterpretq_s64_s32(t0.0),
vreinterpretq_s64_s32(t1.0),
)),
vreinterpretq_s32_s64(vtrn2q_s64(
vreinterpretq_s64_s32(t0.1),
vreinterpretq_s64_s32(t1.1),
)),
]
}
#[target_feature(enable = "neon")]
unsafe fn satd_4x4(orig: *const u16, pred: *const u16, stride: usize) -> u32 {
let mut rows = [vdupq_n_s32(0); 4];
for (row, dst) in rows.iter_mut().enumerate() {
let o16 = unsafe { vld1_u16(orig.add(row * stride)) };
let p16 = unsafe { vld1_u16(pred.add(row * stride)) };
let difference = vreinterpretq_s32_u32(vsubl_u16(o16, p16));
*dst = hadamard4(difference);
}
let rows = transpose4(rows);
let mut coefficients = vdupq_n_u32(0);
for row in rows {
coefficients = vaddq_u32(
coefficients,
vreinterpretq_u32_s32(vabsq_s32(hadamard4(row))),
);
}
(vaddvq_u32(coefficients) + 1) >> 1
}
#[target_feature(enable = "neon")]
unsafe fn satd_4x8(orig: *const u16, pred: *const u16, stride: usize) -> u32 {
let mut rows = [vdupq_n_s16(0); 4];
for (row, dst) in rows.iter_mut().enumerate() {
let o16 = unsafe { vld1q_u16(orig.add(row * stride)) };
let p16 = unsafe { vld1q_u16(pred.add(row * stride)) };
let difference = vreinterpretq_s16_u16(vsubq_u16(o16, p16));
*dst = hadamard4x2_s16(difference);
}
let columns = transpose4x2_s16(rows);
let mut first = vdupq_n_u32(0);
let mut second = vdupq_n_u32(0);
for column in columns {
first = vaddq_u32(
first,
vreinterpretq_u32_s32(vabsq_s32(hadamard4(vmovl_s16(vget_low_s16(column))))),
);
second = vaddq_u32(
second,
vreinterpretq_u32_s32(vabsq_s32(hadamard4(vmovl_s16(vget_high_s16(column))))),
);
}
((vaddvq_u32(first) + 1) >> 1) + ((vaddvq_u32(second) + 1) >> 1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn satd_neon_matches_scalar() {
let mut orig = [0u16; 1024];
let mut pred = [0u16; 1024];
for seed in 0..32u32 {
let mut state = seed.wrapping_mul(747_796_405).wrapping_add(2_891_336_453);
for (index, (orig, pred)) in orig.iter_mut().zip(&mut pred).enumerate() {
state = state.wrapping_mul(747_796_405).wrapping_add(2_891_336_453);
*orig = if seed == 0 {
4095
} else {
((state >> 16) & 4095) as u16
};
state ^= (index as u32).wrapping_mul(277_803_737);
*pred = if seed == 0 {
0
} else {
((state >> 12) & 4095) as u16
};
}
for n in [4, 8, 16, 32] {
let scalar = crate::cost::satd_scalar(&orig[..n * n], &pred[..n * n], n);
let simd = unsafe { satd_neon(&orig[..n * n], &pred[..n * n], n) };
assert_eq!(simd, scalar, "SATD mismatch for seed={seed}, {n}x{n}");
}
}
}
}