#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
const PASS1_BITS: i32 = 2;
const CONST_BITS: i32 = 13;
const PASS1_SHIFT: i32 = CONST_BITS - PASS1_BITS; const PASS2_SHIFT: i32 = CONST_BITS + PASS1_BITS + 3;
const fn pair(a: i16, b: i16) -> i32 {
(((b as u16 as u32) << 16) | (a as u16 as u32)) as i32
}
mod k {
use super::pair;
pub const EVEN_04_ADD: i32 = pair(8192, 8192);
pub const EVEN_04_SUB: i32 = pair(8192, -8192);
pub const EVEN_26_13: i32 = pair(10703, 4433);
pub const EVEN_26_12: i32 = pair(4433, -10704);
pub const ODD0_75: i32 = pair(-11363, 9633);
pub const ODD0_31: i32 = pair(-6436, 2260);
pub const ODD1_75: i32 = pair(9633, 2261);
pub const ODD1_31: i32 = pair(-11362, 6437);
pub const ODD2_75: i32 = pair(-6436, -11362);
pub const ODD2_31: i32 = pair(-2259, 9633);
pub const ODD3_75: i32 = pair(2260, 6437);
pub const ODD3_31: i32 = pair(9633, 11363);
}
macro_rules! transpose8x8_i16 {
($r0:expr, $r1:expr, $r2:expr, $r3:expr, $r4:expr, $r5:expr, $r6:expr, $r7:expr) => {{
let a0 = _mm_unpacklo_epi16($r0, $r1);
let a1 = _mm_unpackhi_epi16($r0, $r1);
let a2 = _mm_unpacklo_epi16($r2, $r3);
let a3 = _mm_unpackhi_epi16($r2, $r3);
let a4 = _mm_unpacklo_epi16($r4, $r5);
let a5 = _mm_unpackhi_epi16($r4, $r5);
let a6 = _mm_unpacklo_epi16($r6, $r7);
let a7 = _mm_unpackhi_epi16($r6, $r7);
let b0 = _mm_unpacklo_epi32(a0, a2);
let b1 = _mm_unpackhi_epi32(a0, a2);
let b2 = _mm_unpacklo_epi32(a1, a3);
let b3 = _mm_unpackhi_epi32(a1, a3);
let b4 = _mm_unpacklo_epi32(a4, a6);
let b5 = _mm_unpackhi_epi32(a4, a6);
let b6 = _mm_unpacklo_epi32(a5, a7);
let b7 = _mm_unpackhi_epi32(a5, a7);
$r0 = _mm_unpacklo_epi64(b0, b4);
$r1 = _mm_unpackhi_epi64(b0, b4);
$r2 = _mm_unpacklo_epi64(b1, b5);
$r3 = _mm_unpackhi_epi64(b1, b5);
$r4 = _mm_unpacklo_epi64(b2, b6);
$r5 = _mm_unpackhi_epi64(b2, b6);
$r6 = _mm_unpacklo_epi64(b3, b7);
$r7 = _mm_unpackhi_epi64(b3, b7);
}};
}
#[inline(always)]
unsafe fn idct_pass(s: &[__m128i; 8], bias: __m128i) -> [(__m128i, __m128i); 8] {
let p04l = _mm_unpacklo_epi16(s[0], s[4]);
let p04h = _mm_unpackhi_epi16(s[0], s[4]);
let p26l = _mm_unpacklo_epi16(s[2], s[6]);
let p26h = _mm_unpackhi_epi16(s[2], s[6]);
let p75l = _mm_unpacklo_epi16(s[7], s[5]);
let p75h = _mm_unpackhi_epi16(s[7], s[5]);
let p31l = _mm_unpacklo_epi16(s[3], s[1]);
let p31h = _mm_unpackhi_epi16(s[3], s[1]);
let madd = |v, c: i32| _mm_madd_epi16(v, _mm_set1_epi32(c));
let t10l = madd(p04l, k::EVEN_04_ADD);
let t10h = madd(p04h, k::EVEN_04_ADD);
let t11l = madd(p04l, k::EVEN_04_SUB);
let t11h = madd(p04h, k::EVEN_04_SUB);
let t13l = madd(p26l, k::EVEN_26_13);
let t13h = madd(p26h, k::EVEN_26_13);
let t12l = madd(p26l, k::EVEN_26_12);
let t12h = madd(p26h, k::EVEN_26_12);
let b10l = _mm_add_epi32(t10l, bias);
let b10h = _mm_add_epi32(t10h, bias);
let b11l = _mm_add_epi32(t11l, bias);
let b11h = _mm_add_epi32(t11h, bias);
let e0 = (_mm_add_epi32(b10l, t13l), _mm_add_epi32(b10h, t13h));
let e3 = (_mm_sub_epi32(b10l, t13l), _mm_sub_epi32(b10h, t13h));
let e1 = (_mm_add_epi32(b11l, t12l), _mm_add_epi32(b11h, t12h));
let e2 = (_mm_sub_epi32(b11l, t12l), _mm_sub_epi32(b11h, t12h));
let o0 = (
_mm_add_epi32(madd(p75l, k::ODD0_75), madd(p31l, k::ODD0_31)),
_mm_add_epi32(madd(p75h, k::ODD0_75), madd(p31h, k::ODD0_31)),
);
let o1 = (
_mm_add_epi32(madd(p75l, k::ODD1_75), madd(p31l, k::ODD1_31)),
_mm_add_epi32(madd(p75h, k::ODD1_75), madd(p31h, k::ODD1_31)),
);
let o2 = (
_mm_add_epi32(madd(p75l, k::ODD2_75), madd(p31l, k::ODD2_31)),
_mm_add_epi32(madd(p75h, k::ODD2_75), madd(p31h, k::ODD2_31)),
);
let o3 = (
_mm_add_epi32(madd(p75l, k::ODD3_75), madd(p31l, k::ODD3_31)),
_mm_add_epi32(madd(p75h, k::ODD3_75), madd(p31h, k::ODD3_31)),
);
[
(_mm_add_epi32(e0.0, o3.0), _mm_add_epi32(e0.1, o3.1)),
(_mm_add_epi32(e1.0, o2.0), _mm_add_epi32(e1.1, o2.1)),
(_mm_add_epi32(e2.0, o1.0), _mm_add_epi32(e2.1, o1.1)),
(_mm_add_epi32(e3.0, o0.0), _mm_add_epi32(e3.1, o0.1)),
(_mm_sub_epi32(e3.0, o0.0), _mm_sub_epi32(e3.1, o0.1)),
(_mm_sub_epi32(e2.0, o1.0), _mm_sub_epi32(e2.1, o1.1)),
(_mm_sub_epi32(e1.0, o2.0), _mm_sub_epi32(e1.1, o2.1)),
(_mm_sub_epi32(e0.0, o3.0), _mm_sub_epi32(e0.1, o3.1)),
]
}
pub fn idct_8x8_sse2(coeffs: &[i16; 64], quant: &[u16; 64], output: &mut [u8], stride: usize) {
assert!(output.len() >= 7 * stride + 8, "IDCT output too small");
unsafe { idct_8x8_sse2_inner(coeffs, quant, output, stride) }
}
#[target_feature(enable = "sse2")]
unsafe fn idct_8x8_sse2_inner(
coeffs: &[i16; 64],
quant: &[u16; 64],
output: &mut [u8],
stride: usize,
) {
islow_body(coeffs, quant, output, stride)
}
#[inline(always)]
pub(super) unsafe fn islow_body(
coeffs: &[i16; 64],
quant: &[u16; 64],
output: &mut [u8],
stride: usize,
) {
let c = coeffs.as_ptr() as *const __m128i;
let q = quant.as_ptr() as *const __m128i;
let mut r = [_mm_setzero_si128(); 8];
for (i, slot) in r.iter_mut().enumerate() {
*slot = _mm_mullo_epi16(_mm_loadu_si128(c.add(i)), _mm_loadu_si128(q.add(i)));
}
let p1 = idct_pass(&r, _mm_set1_epi32(1 << (PASS1_SHIFT - 1)));
for (slot, (lo, hi)) in r.iter_mut().zip(p1) {
*slot = _mm_packs_epi32(
_mm_srai_epi32::<PASS1_SHIFT>(lo),
_mm_srai_epi32::<PASS1_SHIFT>(hi),
);
}
transpose8x8_i16!(r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7]);
let bias = _mm_set1_epi32((1 << (PASS2_SHIFT - 1)) + (128 << PASS2_SHIFT));
let p2 = idct_pass(&r, bias);
for (slot, (lo, hi)) in r.iter_mut().zip(p2) {
*slot = _mm_packs_epi32(
_mm_srai_epi32::<PASS2_SHIFT>(lo),
_mm_srai_epi32::<PASS2_SHIFT>(hi),
);
}
transpose8x8_i16!(r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7]);
for (row, v) in r.into_iter().enumerate() {
let bytes = _mm_packus_epi16(v, v);
_mm_storel_epi64(output.as_mut_ptr().add(row * stride) as *mut __m128i, bytes);
}
}
pub fn idct_dc_only_sse2(dc_value: i32, output: &mut [u8], stride: usize) {
let round = 1 << (PASS2_SHIFT - 1);
let bias = round + (128 << PASS2_SHIFT);
let scaled = dc_value.wrapping_shl((CONST_BITS + PASS1_BITS) as u32);
let val = (scaled.wrapping_add(bias) >> PASS2_SHIFT).clamp(0, 255) as u8;
assert!(output.len() >= 7 * stride + 8, "IDCT output too small");
unsafe {
let fill = _mm_set1_epi8(val as i8);
for row in 0..8 {
_mm_storel_epi64(output.as_mut_ptr().add(row * stride) as *mut __m128i, fill);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jpeg::idct::scalar::{idct_8x8_scalar, idct_dc_only_scalar};
fn rng(seed: &mut u64) -> u64 {
*seed ^= *seed << 13;
*seed ^= *seed >> 7;
*seed ^= *seed << 17;
*seed
}
#[test]
fn parity_vs_scalar() {
let mut seed = 0x1234_5678_9ABC_DEF0u64;
for case in 0..512 {
let mut coeffs = [0i16; 64];
for (i, c) in coeffs.iter_mut().enumerate() {
let mag = if case < 64 { 32 } else { 256 };
let v = (rng(&mut seed) % (2 * mag)) as i32 - mag as i32;
*c = if i < 16 || rng(&mut seed).is_multiple_of(4) {
v as i16
} else {
0
};
}
let mut quant = [1u16; 64];
for q in quant.iter_mut() {
*q = (rng(&mut seed) % 4 + 1) as u16;
}
let stride = 24;
let mut got = vec![0xAAu8; stride * 8];
let mut want = vec![0xAAu8; stride * 8];
idct_8x8_sse2(&coeffs, &quant, &mut got, stride);
idct_8x8_scalar(&coeffs, &quant, &mut want, stride);
assert_eq!(got, want, "case {case} diverged from scalar reference");
}
}
#[test]
fn zero_block_and_stride() {
let stride = 16;
let mut out = vec![0xFFu8; stride * 8];
idct_8x8_sse2(&[0i16; 64], &[1u16; 64], &mut out, stride);
for row in 0..8 {
for col in 0..8 {
assert_eq!(out[row * stride + col], 128);
}
for col in 8..stride {
assert_eq!(out[row * stride + col], 0xFF, "wrote past column 8");
}
}
}
#[test]
fn dc_only_parity_vs_scalar() {
for dc in [0i32, 8, 64, 128, -64, 255, -255, 4095, -4095] {
let mut want = [0u8; 64];
let mut got = [0u8; 64];
idct_dc_only_scalar(dc, &mut want, 8);
idct_dc_only_sse2(dc, &mut got, 8);
assert_eq!(got, want, "dc_only mismatch at dc={dc}");
}
}
}