use crate::hevc_transform::{DEQUANT_SCALE, MAX_TB};
use core::arch::x86_64::*;
#[target_feature(enable = "avx2")]
pub(crate) unsafe fn dequantize_avx2(
level: &[i16],
n: usize,
qp: u8,
bit_depth: u8,
out: &mut [i32; MAX_TB],
) {
let len = n * n;
let bd_shift = bit_depth as i32 + n.trailing_zeros() as i32 - 5;
let net_shift = qp as i32 / 6 + 4 - bd_shift;
debug_assert!(qp as i32 <= 51 + 6 * (bit_depth as i32 - 8));
let scale = _mm256_set1_epi32(DEQUANT_SCALE[(qp % 6) as usize] as i32);
let min = _mm256_set1_epi32(-32768);
let max = _mm256_set1_epi32(32767);
if net_shift < 0 {
dequantize_right(level, out, len, scale, -net_shift, min, max);
} else {
dequantize_left(level, out, len, scale, net_shift, min, max);
}
}
#[inline]
#[target_feature(enable = "avx2")]
fn dequantize_left(
level: &[i16],
out: &mut [i32; MAX_TB],
len: usize,
scale: __m256i,
shift: i32,
min: __m256i,
max: __m256i,
) {
let shift = _mm256_set1_epi32(shift);
let (level, level_remainder) = level[..len].as_chunks::<16>();
let (out, out_remainder) = out[..len].as_chunks_mut::<16>();
debug_assert!(level_remainder.is_empty() && out_remainder.is_empty());
for (level, out) in level.iter().zip(out) {
let packed = unsafe { _mm256_loadu_si256(level.as_ptr().cast()) };
let low = _mm256_cvtepi16_epi32(_mm256_castsi256_si128(packed));
let high = _mm256_cvtepi16_epi32(_mm256_extracti128_si256::<1>(packed));
let low = clamp(
_mm256_sllv_epi32(_mm256_mullo_epi32(low, scale), shift),
min,
max,
);
let high = clamp(
_mm256_sllv_epi32(_mm256_mullo_epi32(high, scale), shift),
min,
max,
);
unsafe {
_mm256_storeu_si256(out.as_mut_ptr().cast(), low);
_mm256_storeu_si256(out[8..].as_mut_ptr().cast(), high);
}
}
}
#[inline]
#[target_feature(enable = "avx2")]
fn dequantize_right(
level: &[i16],
out: &mut [i32; MAX_TB],
len: usize,
scale: __m256i,
shift: i32,
min: __m256i,
max: __m256i,
) {
let add = _mm256_set1_epi32(1 << (shift - 1));
let shift = _mm256_set1_epi32(shift);
let (level, level_remainder) = level[..len].as_chunks::<16>();
let (out, out_remainder) = out[..len].as_chunks_mut::<16>();
debug_assert!(level_remainder.is_empty() && out_remainder.is_empty());
for (level, out) in level.iter().zip(out) {
let packed = unsafe { _mm256_loadu_si256(level.as_ptr().cast()) };
let low = _mm256_cvtepi16_epi32(_mm256_castsi256_si128(packed));
let high = _mm256_cvtepi16_epi32(_mm256_extracti128_si256::<1>(packed));
let low = clamp(
_mm256_srav_epi32(_mm256_add_epi32(_mm256_mullo_epi32(low, scale), add), shift),
min,
max,
);
let high = clamp(
_mm256_srav_epi32(
_mm256_add_epi32(_mm256_mullo_epi32(high, scale), add),
shift,
),
min,
max,
);
unsafe {
_mm256_storeu_si256(out.as_mut_ptr().cast(), low);
_mm256_storeu_si256(out[8..].as_mut_ptr().cast(), high);
}
}
}
#[inline]
#[target_feature(enable = "avx2")]
fn clamp(value: __m256i, min: __m256i, max: __m256i) -> __m256i {
_mm256_min_epi32(_mm256_max_epi32(value, min), max)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dequantize_avx2_matches_scalar() {
if !std::is_x86_feature_detected!("avx2") {
return;
}
for n in [4, 8, 16, 32] {
for bit_depth in [8, 10, 12] {
let max_qp = 51 + 6 * (bit_depth - 8);
for qp in [0, 1, 5, 6, 22, max_qp] {
let mut level = [0i16; MAX_TB];
for (i, value) in level[..n * n].iter_mut().enumerate() {
*value = match i % 11 {
0 => i16::MIN,
1 => i16::MAX,
_ => ((i as i32 * 7919 + 104_729) as i16).wrapping_sub(16384),
};
}
let mut expected = [0x1234_5678; MAX_TB];
let mut actual = expected;
crate::hevc_transform::dequantize_into(&level, n, qp, bit_depth, &mut expected);
unsafe { dequantize_avx2(&level, n, qp, bit_depth, &mut actual) };
assert_eq!(actual, expected, "n={n}, bit_depth={bit_depth}, qp={qp}");
}
}
}
}
}