use core::arch::aarch64::*;
use crate::intra::{DC, INTRA_PRED_ANGLE, INV_ANGLE, PLANAR, predict_into_scalar};
#[inline]
fn supported_n(n: usize) -> bool {
matches!(n, 2 | 4 | 8 | 16 | 32)
}
#[inline]
fn supported_predictor(mode: u8, n: usize) -> bool {
supported_n(n) && (mode == PLANAR || mode == DC || (2..=34).contains(&mode))
}
#[inline]
#[target_feature(enable = "neon")]
fn load_u16x8(src: &[u16]) -> uint16x8_t {
debug_assert!(src.len() >= 8);
unsafe { vld1q_u16(src.as_ptr()) }
}
#[inline]
#[target_feature(enable = "neon")]
fn load_u16x4(src: &[u16]) -> uint16x8_t {
debug_assert!(src.len() >= 4);
unsafe { vcombine_u16(vld1_u16(src.as_ptr()), vdup_n_u16(0)) }
}
#[inline]
#[target_feature(enable = "neon")]
fn store_u16x8(dst: &mut [u16], v: uint16x8_t) {
debug_assert!(dst.len() >= 8);
unsafe { vst1q_u16(dst.as_mut_ptr(), v) }
}
#[inline]
#[target_feature(enable = "neon")]
fn store_u16x4(dst: &mut [u16], v: uint16x8_t) {
debug_assert!(dst.len() >= 4);
unsafe { vst1_u16(dst.as_mut_ptr(), vget_low_u16(v)) }
}
#[inline]
#[target_feature(enable = "neon")]
fn store_u16x2(dst: &mut [u16], v: uint16x8_t) {
debug_assert!(dst.len() >= 2);
unsafe {
vst1q_lane_u32::<0>(dst.as_mut_ptr().cast(), vreinterpretq_u32_u16(v));
}
}
#[inline]
#[target_feature(enable = "neon")]
fn mul_const(v: int32x4_t, c: i32) -> int32x4_t {
vmulq_s32(v, vdupq_n_s32(c))
}
#[inline]
#[target_feature(enable = "neon")]
fn round_shift_s32x4(v: int32x4_t, add: i32, shift: i32) -> int32x4_t {
vshlq_s32(vaddq_s32(v, vdupq_n_s32(add)), vdupq_n_s32(-shift))
}
#[inline]
#[target_feature(enable = "neon")]
fn pack_u16x8(lo: int32x4_t, hi: int32x4_t) -> uint16x8_t {
vcombine_u16(
vqmovn_u32(vreinterpretq_u32_s32(lo)),
vqmovn_u32(vreinterpretq_u32_s32(hi)),
)
}
const X_LANE_OFFSETS: [i32; 4] = [1, 2, 3, 4];
const NX_LANE_OFFSETS: [i32; 4] = [0, 1, 2, 3];
#[inline]
#[target_feature(enable = "neon")]
fn x_lane_offsets() -> int32x4_t {
unsafe { vld1q_s32(X_LANE_OFFSETS.as_ptr()) }
}
#[inline]
#[target_feature(enable = "neon")]
fn nx_lane_offsets() -> int32x4_t {
unsafe { vld1q_s32(NX_LANE_OFFSETS.as_ptr()) }
}
#[inline]
#[target_feature(enable = "neon")]
fn x_lanes(base: usize) -> int32x4_t {
vaddq_s32(vdupq_n_s32(base as i32), x_lane_offsets())
}
#[inline]
#[target_feature(enable = "neon")]
fn nx_lanes(n: usize, base: usize) -> int32x4_t {
vsubq_s32(vdupq_n_s32((n - 1 - base) as i32), nx_lane_offsets())
}
#[inline]
#[target_feature(enable = "neon")]
fn widen_lo_u16x4(v: uint16x8_t) -> int32x4_t {
vreinterpretq_s32_u32(vmovl_u16(vget_low_u16(v)))
}
#[inline]
#[target_feature(enable = "neon")]
fn widen_hi_u16x4(v: uint16x8_t) -> int32x4_t {
vreinterpretq_s32_u32(vmovl_u16(vget_high_u16(v)))
}
#[inline]
#[allow(clippy::too_many_arguments)]
#[target_feature(enable = "neon")]
fn planar4_neon(
above: int32x4_t,
x: usize,
n: usize,
left_y: i32,
tr: i32,
bl_part: i32,
ny: i32,
add: i32,
shift: i32,
) -> int32x4_t {
let h = vaddq_s32(
vmulq_s32(nx_lanes(n, x), vdupq_n_s32(left_y)),
mul_const(x_lanes(x), tr),
);
let v = vaddq_s32(mul_const(above, ny), vdupq_n_s32(bl_part));
round_shift_s32x4(vaddq_s32(h, v), add, shift)
}
#[inline]
#[target_feature(enable = "neon")]
fn store_planar_row_neon(
out: &mut [u16],
above: &[u16],
left_y: i32,
tr: i32,
bl: i32,
y: usize,
n: usize,
) {
let shift = n.trailing_zeros() as i32 + 1;
let add = n as i32;
let ny = (n - 1 - y) as i32;
let bl_part = (y + 1) as i32 * bl;
if n == 2 {
let a = load_u16x4(&above[1..]);
let lo = planar4_neon(widen_lo_u16x4(a), 0, n, left_y, tr, bl_part, ny, add, shift);
store_u16x2(out, pack_u16x8(lo, vdupq_n_s32(0)));
return;
}
let mut x = 0usize;
while x + 8 <= n {
let a = load_u16x8(&above[1 + x..]);
let lo = planar4_neon(widen_lo_u16x4(a), x, n, left_y, tr, bl_part, ny, add, shift);
let hi = planar4_neon(
widen_hi_u16x4(a),
x + 4,
n,
left_y,
tr,
bl_part,
ny,
add,
shift,
);
store_u16x8(&mut out[x..], pack_u16x8(lo, hi));
x += 8;
}
if x < n {
let a = load_u16x8(&above[1 + x..]);
let lo = planar4_neon(widen_lo_u16x4(a), x, n, left_y, tr, bl_part, ny, add, shift);
store_u16x4(&mut out[x..], pack_u16x8(lo, vdupq_n_s32(0)));
}
}
#[inline]
#[target_feature(enable = "neon")]
fn fill_row_neon(dst: &mut [u16], n: usize, v: u16) {
let v = vdupq_n_u16(v);
if n == 2 {
store_u16x2(dst, v);
} else if n == 4 {
store_u16x4(dst, v);
} else {
let mut x = 0usize;
while x < n {
store_u16x8(&mut dst[x..], v);
x += 8;
}
}
}
#[inline]
#[target_feature(enable = "neon")]
fn copy_row_neon(dst: &mut [u16], src: &[u16], n: usize) {
if n == 2 {
let v = load_u16x4(src);
store_u16x2(dst, v);
} else if n == 4 {
let v = load_u16x8(src);
store_u16x4(dst, v);
} else {
let mut x = 0usize;
while x < n {
let v = load_u16x8(&src[x..]);
store_u16x8(&mut dst[x..], v);
x += 8;
}
}
}
#[inline]
#[target_feature(enable = "neon")]
fn load_i32x4(src: &[i32]) -> int32x4_t {
debug_assert!(src.len() >= 4);
unsafe { vld1q_s32(src.as_ptr()) }
}
#[inline]
#[target_feature(enable = "neon")]
fn clip_s32x4(v: int32x4_t, max_v: int32x4_t) -> int32x4_t {
vminq_s32(vmaxq_s32(v, vdupq_n_s32(0)), max_v)
}
#[inline]
#[target_feature(enable = "neon")]
fn round_weighted_s32x4(r0: int32x4_t, r1: int32x4_t, frac: i32) -> int32x4_t {
let a = vmulq_s32(r0, vdupq_n_s32(32 - frac));
let b = vmulq_s32(r1, vdupq_n_s32(frac));
vshrq_n_s32::<5>(vaddq_s32(vaddq_s32(a, b), vdupq_n_s32(16)))
}
#[inline]
#[target_feature(enable = "neon")]
fn round_weighted_var_s32x4(r0: int32x4_t, r1: int32x4_t, frac: int32x4_t) -> int32x4_t {
let inv_frac = vsubq_s32(vdupq_n_s32(32), frac);
let a = vmulq_s32(r0, inv_frac);
let b = vmulq_s32(r1, frac);
vshrq_n_s32::<5>(vaddq_s32(vaddq_s32(a, b), vdupq_n_s32(16)))
}
#[inline]
#[target_feature(enable = "neon")]
fn store_i32x4_as_u16(dst: &mut [u16], v: int32x4_t, max_v: int32x4_t) {
let v = clip_s32x4(v, max_v);
store_u16x4(dst, pack_u16x8(v, vdupq_n_s32(0)));
}
#[inline]
#[target_feature(enable = "neon")]
fn store_i32x2_as_u16(dst: &mut [u16], v: int32x4_t, max_v: int32x4_t) {
let v = clip_s32x4(v, max_v);
store_u16x2(dst, pack_u16x8(v, vdupq_n_s32(0)));
}
#[inline]
#[target_feature(enable = "neon")]
fn store_i32x8_as_u16(dst: &mut [u16], lo: int32x4_t, hi: int32x4_t, max_v: int32x4_t) {
let lo = clip_s32x4(lo, max_v);
let hi = clip_s32x4(hi, max_v);
store_u16x8(dst, pack_u16x8(lo, hi));
}
fn prepare_angular_refs_neon(
mode: u8,
above: &[u16],
left: &[u16],
n: usize,
refs_ang: &mut [i32],
) -> (i32, bool) {
let angle = INTRA_PRED_ANGLE[mode as usize - 2];
let vertical = mode >= 18;
let main = if vertical { above } else { left };
let side = if vertical { left } else { above };
let refs = &mut refs_ang[..3 * n + 1];
for (dst, &main) in refs[n..=3 * n].iter_mut().zip(main[..=2 * n].iter()) {
*dst = main as i32;
}
if angle < 0 {
let inv = INV_ANGLE[mode as usize - 11];
let lim = (n as i32 * angle) >> 5;
let mut k = -1i32;
while k >= lim {
let idx = ((k * inv + 128) >> 8).min(2 * n as i32);
refs[(k + n as i32) as usize] = side[idx.max(0) as usize] as i32;
k -= 1;
}
}
(angle, vertical)
}
#[inline]
#[target_feature(enable = "neon")]
fn set2_i32x4_neon(a: i32, b: i32) -> int32x4_t {
let v = vdupq_n_s32(0);
let v = vsetq_lane_s32::<0>(a, v);
vsetq_lane_s32::<1>(b, v)
}
#[inline]
#[target_feature(enable = "neon")]
fn predict_angular_vertical2_row_neon(
dst: &mut [u16],
refs: &[i32],
n: usize,
row: usize,
angle: i32,
max_v: int32x4_t,
) {
debug_assert_eq!(n, 2);
let base = n as i32;
let pos = (row as i32 + 1) * angle;
let i_idx = pos >> 5;
let frac = pos & 31;
let idx = (i_idx + 1 + base) as usize;
let r0 = set2_i32x4_neon(refs[idx], refs[idx + 1]);
let v = if frac == 0 {
r0
} else {
let r1 = set2_i32x4_neon(refs[idx + 1], refs[idx + 2]);
round_weighted_s32x4(r0, r1, frac)
};
store_i32x2_as_u16(dst, v, max_v);
}
#[inline]
#[target_feature(enable = "neon")]
fn predict_angular_vertical_row_neon(
dst: &mut [u16],
refs: &[i32],
n: usize,
row: usize,
angle: i32,
max_v: int32x4_t,
) {
let base = n as i32;
let pos = (row as i32 + 1) * angle;
let i_idx = pos >> 5;
let frac = pos & 31;
let mut x = 0usize;
while x + 8 <= n {
let idx = (x as i32 + i_idx + 1 + base) as usize;
let r0_lo = load_i32x4(&refs[idx..]);
let r0_hi = load_i32x4(&refs[idx + 4..]);
let (lo, hi) = if frac == 0 {
(r0_lo, r0_hi)
} else {
let r1_lo = load_i32x4(&refs[idx + 1..]);
let r1_hi = load_i32x4(&refs[idx + 5..]);
(
round_weighted_s32x4(r0_lo, r1_lo, frac),
round_weighted_s32x4(r0_hi, r1_hi, frac),
)
};
store_i32x8_as_u16(&mut dst[x..], lo, hi, max_v);
x += 8;
}
if x < n {
let idx = (x as i32 + i_idx + 1 + base) as usize;
let r0 = load_i32x4(&refs[idx..]);
let v = if frac == 0 {
r0
} else {
let r1 = load_i32x4(&refs[idx + 1..]);
round_weighted_s32x4(r0, r1, frac)
};
store_i32x4_as_u16(&mut dst[x..], v, max_v);
}
}
#[inline]
#[target_feature(enable = "neon")]
fn angular_horizontal4_neon(refs: &[i32], n: usize, y: usize, x: usize, angle: i32) -> int32x4_t {
let base = n as i32;
let mut r0 = [0i32; 4];
let mut r1 = [0i32; 4];
let mut frac = [0i32; 4];
for lane in 0..4 {
let pos = (x as i32 + lane as i32 + 1) * angle;
let i_idx = pos >> 5;
let f = pos & 31;
let idx = (y as i32 + i_idx + 1 + base) as usize;
r0[lane] = refs[idx];
r1[lane] = if f == 0 { 0 } else { refs[idx + 1] };
frac[lane] = f;
}
let r0 = unsafe { vld1q_s32(r0.as_ptr()) };
let r1 = unsafe { vld1q_s32(r1.as_ptr()) };
let frac = unsafe { vld1q_s32(frac.as_ptr()) };
round_weighted_var_s32x4(r0, r1, frac)
}
#[inline]
#[target_feature(enable = "neon")]
fn angular_horizontal2_neon(refs: &[i32], n: usize, y: usize, angle: i32) -> int32x4_t {
debug_assert_eq!(n, 2);
let base = n as i32;
let pos0 = angle;
let i0 = pos0 >> 5;
let f0 = pos0 & 31;
let idx0 = (y as i32 + i0 + 1 + base) as usize;
let pos1 = angle << 1;
let i1 = pos1 >> 5;
let f1 = pos1 & 31;
let idx1 = (y as i32 + i1 + 1 + base) as usize;
let r0 = set2_i32x4_neon(refs[idx0], refs[idx1]);
let r1 = set2_i32x4_neon(
if f0 == 0 { 0 } else { refs[idx0 + 1] },
if f1 == 0 { 0 } else { refs[idx1 + 1] },
);
let frac = set2_i32x4_neon(f0, f1);
round_weighted_var_s32x4(r0, r1, frac)
}
#[target_feature(enable = "neon")]
fn predict_angular_neon(
mode: u8,
above: &[u16],
left: &[u16],
n: usize,
bit_depth: u8,
out: &mut [u16],
refs_ang: &mut [i32],
) {
debug_assert!((2..=34).contains(&mode));
debug_assert!(supported_n(n));
debug_assert!(above.len() > 2 * n);
debug_assert!(left.len() > 2 * n);
debug_assert!(out.len() >= n * n);
let (angle, vertical) = prepare_angular_refs_neon(mode, above, left, n, refs_ang);
let refs = &refs_ang[..3 * n + 1];
let max_v = vdupq_n_s32(((1i32 << bit_depth) - 1).max(0));
let out = &mut out[..n * n];
if vertical {
if n == 2 {
for (y, row) in out.chunks_exact_mut(n).enumerate() {
predict_angular_vertical2_row_neon(row, refs, n, y, angle, max_v);
}
} else {
for (y, row) in out.chunks_exact_mut(n).enumerate() {
predict_angular_vertical_row_neon(row, refs, n, y, angle, max_v);
}
}
} else {
if n == 2 {
for (y, row) in out.chunks_exact_mut(n).enumerate() {
let v = angular_horizontal2_neon(refs, n, y, angle);
store_i32x2_as_u16(row, v, max_v);
}
return;
}
for (y, row) in out.chunks_exact_mut(n).enumerate() {
let mut x = 0usize;
while x + 8 <= n {
let lo = angular_horizontal4_neon(refs, n, y, x, angle);
let hi = angular_horizontal4_neon(refs, n, y, x + 4, angle);
store_i32x8_as_u16(&mut row[x..], lo, hi, max_v);
x += 8;
}
if x < n {
let v = angular_horizontal4_neon(refs, n, y, x, angle);
store_i32x4_as_u16(&mut row[x..], v, max_v);
}
}
}
}
#[target_feature(enable = "neon")]
fn predict_planar_neon(above: &[u16], left: &[u16], n: usize, out: &mut [u16]) {
debug_assert!(supported_n(n));
debug_assert!(above.len() > 2 * n);
debug_assert!(left.len() > 2 * n);
debug_assert!(out.len() >= n * n);
let tr = above[n + 1] as i32;
let bl = left[n + 1] as i32;
for y in 0..n {
let row = &mut out[y * n..y * n + n];
store_planar_row_neon(row, above, left[y + 1] as i32, tr, bl, y, n);
}
}
#[target_feature(enable = "neon")]
fn predict_dc_neon(above: &[u16], left: &[u16], n: usize, is_luma: bool, out: &mut [u16]) {
debug_assert!(supported_n(n));
debug_assert!(above.len() > n);
debug_assert!(left.len() > n);
debug_assert!(out.len() >= n * n);
let mut sum = 0i32;
for (&above, &left) in above[1..=n].iter().zip(left[1..=n].iter()) {
sum += above as i32 + left as i32;
}
let dc = (sum + n as i32) >> ((n as u32).trailing_zeros() + 1);
let dc_u16 = dc as u16;
for row in out.chunks_exact_mut(n).take(n) {
fill_row_neon(row, n, dc_u16);
}
if is_luma && n < 32 {
out[0] = ((left[1] as i32 + 2 * dc + above[1] as i32 + 2) >> 2) as u16;
for (dst, &above) in out[1..n].iter_mut().zip(above[2..n + 1].iter()) {
*dst = ((above as i32 + 3 * dc + 2) >> 2) as u16;
}
for (y, &left) in (1..n).zip(left[2..n + 1].iter()) {
out[y * n] = ((left as i32 + 3 * dc + 2) >> 2) as u16;
}
}
}
#[target_feature(enable = "neon")]
fn predict_mode26_neon(
above: &[u16],
left: &[u16],
n: usize,
is_luma: bool,
bit_depth: u8,
out: &mut [u16],
) {
debug_assert!(supported_n(n));
debug_assert!(above.len() > n);
debug_assert!(left.len() > n);
debug_assert!(out.len() >= n * n);
for row in out.chunks_exact_mut(n).take(n) {
copy_row_neon(row, &above[1..], n);
}
if is_luma && n < 32 {
let max = ((1i32 << bit_depth) - 1).max(0);
for (y, &left) in left[1..n + 1].iter().enumerate() {
let v = above[1] as i32 + ((left as i32 - above[0] as i32) >> 1);
out[y * n] = v.clamp(0, max) as u16;
}
}
}
#[target_feature(enable = "neon")]
fn predict_mode10_neon(
above: &[u16],
left: &[u16],
n: usize,
is_luma: bool,
bit_depth: u8,
out: &mut [u16],
) {
debug_assert!(supported_n(n));
debug_assert!(above.len() > n);
debug_assert!(left.len() > n);
debug_assert!(out.len() >= n * n);
for (y, row) in out.chunks_exact_mut(n).take(n).enumerate() {
fill_row_neon(row, n, left[y + 1]);
}
if is_luma && n < 32 {
let max = ((1i32 << bit_depth) - 1).max(0);
for (dst, &above_p1) in out[..n].iter_mut().zip(above[1..n + 1].iter()) {
let v = left[1] as i32 + ((above_p1 as i32 - above[0] as i32) >> 1);
*dst = v.clamp(0, max) as u16;
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn predict_into_neon(
mode: u8,
above: &[u16],
left: &[u16],
n: usize,
is_luma: bool,
bit_depth: u8,
out: &mut [u16],
refs_ang: &mut [i32],
) {
if !supported_predictor(mode, n) {
predict_into_scalar(mode, above, left, n, is_luma, bit_depth, out, refs_ang);
return;
}
unsafe {
match mode {
PLANAR => predict_planar_neon(above, left, n, out),
DC => predict_dc_neon(above, left, n, is_luma, out),
10 => predict_mode10_neon(above, left, n, is_luma, bit_depth, out),
26 => predict_mode26_neon(above, left, n, is_luma, bit_depth, out),
2..=34 => predict_angular_neon(mode, above, left, n, bit_depth, out, refs_ang),
_ => predict_into_scalar(mode, above, left, n, is_luma, bit_depth, out, refs_ang),
}
}
}