#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod avx {
use pulp::{f32x8, x86::V3};
const A: f32 = 3.535536e-1;
const B: f32 = 4.903927e-1;
const C: f32 = 4.619398e-1;
const D: f32 = 4.157349e-1;
const E: f32 = 2.777855e-1;
const F: f32 = 1.913422e-1;
const G: f32 = 9.754573e-2;
struct Coefficients {
a: f32x8,
na: f32x8,
b: f32x8,
nb: f32x8,
c: f32x8,
nc: f32x8,
d: f32x8,
e: f32x8,
ne: f32x8,
f: f32x8,
nf: f32x8,
g: f32x8,
ng: f32x8,
}
impl Coefficients {
#[inline(always)]
fn new(v3: V3) -> Self {
Self {
a: v3.splat_f32x8(A),
na: v3.splat_f32x8(-A),
b: v3.splat_f32x8(B),
nb: v3.splat_f32x8(-B),
c: v3.splat_f32x8(C),
nc: v3.splat_f32x8(-C),
d: v3.splat_f32x8(D),
e: v3.splat_f32x8(E),
ne: v3.splat_f32x8(-E),
f: v3.splat_f32x8(F),
nf: v3.splat_f32x8(-F),
g: v3.splat_f32x8(G),
ng: v3.splat_f32x8(-G),
}
}
}
#[inline(always)] fn row_pass(v3: V3, coef: &Coefficients, input: [f32x8; 8]) -> [f32x8; 8] {
let mul = |a, b| v3.mul_f32x8(a, b);
let add = |a, b| v3.add_f32x8(a, b);
let sub = |a, b| v3.sub_f32x8(a, b);
let (in0, in2, in4, in6) = (input[0], input[2], input[4], input[6]);
let (in1, in3, in5, in7) = (input[1], input[3], input[5], input[7]);
let even0 =
add(add(mul(in4, coef.a), mul(in6, coef.f)), add(mul(in0, coef.a), mul(in2, coef.c)));
let even1 =
add(add(mul(in4, coef.na), mul(in6, coef.nc)), add(mul(in0, coef.a), mul(in2, coef.f)));
let even2 =
add(add(mul(in4, coef.na), mul(in6, coef.c)), add(mul(in0, coef.a), mul(in2, coef.nf)));
let even3 =
add(add(mul(in4, coef.a), mul(in6, coef.nf)), add(mul(in0, coef.a), mul(in2, coef.nc)));
let odd0 =
add(add(mul(in5, coef.e), mul(in7, coef.g)), add(mul(in1, coef.b), mul(in3, coef.d)));
let odd1 = add(
add(mul(in5, coef.nb), mul(in7, coef.ne)),
add(mul(in1, coef.d), mul(in3, coef.ng)),
);
let odd2 =
add(add(mul(in5, coef.g), mul(in7, coef.d)), add(mul(in1, coef.e), mul(in3, coef.nb)));
let odd3 =
add(add(mul(in5, coef.d), mul(in7, coef.nb)), add(mul(in1, coef.g), mul(in3, coef.ne)));
[
add(even0, odd0),
add(even1, odd1),
add(even2, odd2),
add(even3, odd3),
sub(even3, odd3),
sub(even2, odd2),
sub(even1, odd1),
sub(even0, odd0),
]
}
#[inline(always)] fn column_pass(v3: V3, coef: &Coefficients, input: [f32x8; 8]) -> [f32x8; 8] {
let mul = |a, b| v3.mul_f32x8(a, b);
let add = |a, b| v3.add_f32x8(a, b);
let sub = |a, b| v3.sub_f32x8(a, b);
let (in0, in1, in2, in3, in4, in5, in6, in7) =
(input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7]);
let beta0 =
add(add(mul(coef.g, in7), mul(coef.e, in5)), add(mul(coef.d, in3), mul(coef.b, in1)));
let beta1 =
sub(sub(mul(coef.d, in1), add(mul(coef.b, in5), mul(coef.g, in3))), mul(coef.e, in7));
let beta2 =
add(mul(coef.d, in7), add(mul(coef.g, in5), sub(mul(coef.e, in1), mul(coef.b, in3))));
let beta3 =
sub(add(mul(coef.d, in5), mul(coef.g, in1)), add(mul(coef.b, in7), mul(coef.e, in3)));
let theta0 = add(mul(coef.a, in4), mul(coef.a, in0));
let theta3 = sub(mul(coef.a, in0), mul(coef.a, in4));
let theta1 = add(mul(coef.f, in6), mul(coef.c, in2));
let gamma0 = add(theta1, theta0);
let gamma3 = sub(theta0, theta1);
let theta2 = sub(mul(coef.f, in2), mul(coef.c, in6));
let gamma1 = add(theta3, theta2);
let gamma2 = sub(theta3, theta2);
[
add(gamma0, beta0),
add(gamma1, beta1),
add(gamma2, beta2),
add(gamma3, beta3),
sub(gamma3, beta3),
sub(gamma2, beta2),
sub(gamma1, beta1),
sub(gamma0, beta0),
]
}
#[cfg(any(feature = "avx2-tests", feature = "simd-benches"))]
pub fn dct_inverse_8x8(v3: V3, data: &mut [f32; 64]) {
dct_inverse_8x8_batch(v3, std::iter::once(data));
}
pub fn dct_inverse_8x8_batch<'a>(v3: V3, blocks: impl Iterator<Item = &'a mut [f32; 64]>) {
v3.vectorize(move || {
let coef = Coefficients::new(v3);
for data in blocks {
let columns: [f32x8; 8] = std::array::from_fn(|k| {
f32x8(
data[k],
data[8 + k],
data[16 + k],
data[24 + k],
data[32 + k],
data[40 + k],
data[48 + k],
data[56 + k],
)
});
let rows_out = row_pass(v3, &coef, columns);
for (column, result) in rows_out.iter().enumerate() {
let r = [
result.0, result.1, result.2, result.3, result.4, result.5, result.6,
result.7,
];
for (row, value) in r.iter().enumerate() {
data[row * 8 + column] = *value;
}
}
let rows: [f32x8; 8] = std::array::from_fn(|row| {
let b = row * 8;
f32x8(
data[b],
data[b + 1],
data[b + 2],
data[b + 3],
data[b + 4],
data[b + 5],
data[b + 6],
data[b + 7],
)
});
let columns_out = column_pass(v3, &coef, rows);
for (row, result) in columns_out.iter().enumerate() {
let b = row * 8;
data[b] = result.0;
data[b + 1] = result.1;
data[b + 2] = result.2;
data[b + 3] = result.3;
data[b + 4] = result.4;
data[b + 5] = result.5;
data[b + 6] = result.6;
data[b + 7] = result.7;
}
}
});
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod sse2 {
use pulp::{f32x4, x86::V1};
const A: f32 = 3.535536e-1;
const B: f32 = 4.903927e-1;
const C: f32 = 4.619398e-1;
const D: f32 = 4.157349e-1;
const E: f32 = 2.777855e-1;
const F: f32 = 1.913422e-1;
const G: f32 = 9.754573e-2;
struct RowCoefficients {
c0: f32x4,
c1: f32x4,
c2: f32x4,
c3: f32x4,
c4: f32x4,
c5: f32x4,
c6: f32x4,
c7: f32x4,
}
impl RowCoefficients {
fn new() -> Self {
Self {
c0: f32x4(A, A, A, A),
c1: f32x4(C, F, -F, -C),
c2: f32x4(A, -A, -A, A),
c3: f32x4(F, -C, C, -F),
c4: f32x4(B, D, E, G),
c5: f32x4(D, -G, -B, -E),
c6: f32x4(E, -B, G, D),
c7: f32x4(G, -E, D, -B),
}
}
}
struct ColumnCoefficients {
a: f32x4,
b: f32x4,
c: f32x4,
d: f32x4,
e: f32x4,
f: f32x4,
g: f32x4,
}
impl ColumnCoefficients {
fn new(v1: V1) -> Self {
Self {
a: v1.splat_f32x4(A),
b: v1.splat_f32x4(B),
c: v1.splat_f32x4(C),
d: v1.splat_f32x4(D),
e: v1.splat_f32x4(E),
f: v1.splat_f32x4(F),
g: v1.splat_f32x4(G),
}
}
}
fn row_pass(v1: V1, coef: &RowCoefficients, row: &[f32]) -> (f32x4, f32x4) {
let mul = |a, b| v1.mul_f32x4(a, b);
let add = |a, b| v1.add_f32x4(a, b);
let sub = |a, b| v1.sub_f32x4(a, b);
let broadcast = |v: f32| v1.splat_f32x4(v);
let x0 = mul(broadcast(row[0]), coef.c0);
let x2 = mul(broadcast(row[2]), coef.c1);
let x4 = mul(broadcast(row[4]), coef.c2);
let x6 = mul(broadcast(row[6]), coef.c3);
let x1 = mul(broadcast(row[1]), coef.c4);
let x3 = mul(broadcast(row[3]), coef.c5);
let x5 = mul(broadcast(row[5]), coef.c6);
let x7 = mul(broadcast(row[7]), coef.c7);
let zero = v1.splat_f32x4(0.0);
let even = add(add(add(add(zero, x0), x2), x4), x6);
let odd = add(add(add(add(zero, x1), x3), x5), x7);
let lo = add(even, odd);
let hi = sub(even, odd);
(lo, f32x4(hi.3, hi.2, hi.1, hi.0))
}
fn column_pass(v1: V1, coef: &ColumnCoefficients, input: [f32x4; 8]) -> [f32x4; 8] {
let mul = |a, b| v1.mul_f32x4(a, b);
let add = |a, b| v1.add_f32x4(a, b);
let sub = |a, b| v1.sub_f32x4(a, b);
let (in0, in1, in2, in3, in4, in5, in6, in7) =
(input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7]);
let beta0 =
add(add(mul(in1, coef.b), mul(in3, coef.d)), add(mul(in5, coef.e), mul(in7, coef.g)));
let beta1 =
sub(sub(mul(in1, coef.d), mul(in3, coef.g)), add(mul(in5, coef.b), mul(in7, coef.e)));
let beta2 =
add(sub(mul(in1, coef.e), mul(in3, coef.b)), add(mul(in5, coef.g), mul(in7, coef.d)));
let beta3 =
add(sub(mul(in1, coef.g), mul(in3, coef.e)), sub(mul(in5, coef.d), mul(in7, coef.b)));
let theta0 = mul(coef.a, add(in0, in4));
let theta3 = mul(coef.a, sub(in0, in4));
let alpha0 = mul(coef.c, in2);
let alpha1 = mul(coef.f, in2);
let alpha2 = mul(coef.c, in6);
let alpha3 = mul(coef.f, in6);
let theta1 = add(alpha0, alpha3);
let theta2 = sub(alpha1, alpha2);
let gamma0 = add(theta0, theta1);
let gamma1 = add(theta3, theta2);
let gamma2 = sub(theta3, theta2);
let gamma3 = sub(theta0, theta1);
[
add(gamma0, beta0),
add(gamma1, beta1),
add(gamma2, beta2),
add(gamma3, beta3),
sub(gamma3, beta3),
sub(gamma2, beta2),
sub(gamma1, beta1),
sub(gamma0, beta0),
]
}
pub fn dct_inverse_8x8(v1: V1, data: &mut [f32; 64]) {
let row_coef = RowCoefficients::new();
for row in 0..8 {
let base = row * 8;
let (lo, hi) = row_pass(v1, &row_coef, &data[base..base + 8]);
data[base] = lo.0;
data[base + 1] = lo.1;
data[base + 2] = lo.2;
data[base + 3] = lo.3;
data[base + 4] = hi.0;
data[base + 5] = hi.1;
data[base + 6] = hi.2;
data[base + 7] = hi.3;
}
let col_coef = ColumnCoefficients::new(v1);
for half in 0..2 {
let offset = half * 4;
let input: [f32x4; 8] = std::array::from_fn(|row| {
let b = row * 8 + offset;
f32x4(data[b], data[b + 1], data[b + 2], data[b + 3])
});
let out = column_pass(v1, &col_coef, input);
for (row, result) in out.iter().enumerate() {
let b = row * 8 + offset;
data[b] = result.0;
data[b + 1] = result.1;
data[b + 2] = result.2;
data[b + 3] = result.3;
}
}
}
}
fn dct_inverse_8x8_scalar(data: &mut [f32; 64]) {
const PI: f32 = 3.14159;
let a = 0.5 * (PI / 4.0).cos();
let b = 0.5 * (PI / 16.0).cos();
let c = 0.5 * (PI / 8.0).cos();
let d = 0.5 * ((3.0 * PI) / 16.0).cos();
let e = 0.5 * ((5.0 * PI) / 16.0).cos();
let f = 0.5 * ((3.0 * PI) / 8.0).cos();
let g = 0.5 * ((7.0 * PI) / 16.0).cos();
let mut alpha = [0f32; 4];
let mut beta = [0f32; 4];
let mut theta = [0f32; 4];
let mut gamma = [0f32; 4];
for row in 0..8 {
let base = row * 8;
let row_ptr = &mut data[base..base + 8];
alpha[0] = c * row_ptr[2];
alpha[1] = f * row_ptr[2];
alpha[2] = c * row_ptr[6];
alpha[3] = f * row_ptr[6];
beta[0] = b * row_ptr[1] + d * row_ptr[3] + e * row_ptr[5] + g * row_ptr[7];
beta[1] = d * row_ptr[1] - g * row_ptr[3] - b * row_ptr[5] - e * row_ptr[7];
beta[2] = e * row_ptr[1] - b * row_ptr[3] + g * row_ptr[5] + d * row_ptr[7];
beta[3] = g * row_ptr[1] - e * row_ptr[3] + d * row_ptr[5] - b * row_ptr[7];
theta[0] = a * (row_ptr[0] + row_ptr[4]);
theta[3] = a * (row_ptr[0] - row_ptr[4]);
theta[1] = alpha[0] + alpha[3];
theta[2] = alpha[1] - alpha[2];
gamma[0] = theta[0] + theta[1];
gamma[1] = theta[3] + theta[2];
gamma[2] = theta[3] - theta[2];
gamma[3] = theta[0] - theta[1];
row_ptr[0] = gamma[0] + beta[0];
row_ptr[1] = gamma[1] + beta[1];
row_ptr[2] = gamma[2] + beta[2];
row_ptr[3] = gamma[3] + beta[3];
row_ptr[4] = gamma[3] - beta[3];
row_ptr[5] = gamma[2] - beta[2];
row_ptr[6] = gamma[1] - beta[1];
row_ptr[7] = gamma[0] - beta[0];
}
for column in 0..8 {
alpha[0] = c * data[16 + column];
alpha[1] = f * data[16 + column];
alpha[2] = c * data[48 + column];
alpha[3] = f * data[48 + column];
beta[0] = b * data[8 + column]
+ d * data[24 + column]
+ e * data[40 + column]
+ g * data[56 + column];
beta[1] = d * data[8 + column]
- g * data[24 + column]
- b * data[40 + column]
- e * data[56 + column];
beta[2] = e * data[8 + column] - b * data[24 + column]
+ g * data[40 + column]
+ d * data[56 + column];
beta[3] = g * data[8 + column] - e * data[24 + column] + d * data[40 + column]
- b * data[56 + column];
theta[0] = a * (data[column] + data[32 + column]);
theta[3] = a * (data[column] - data[32 + column]);
theta[1] = alpha[0] + alpha[3];
theta[2] = alpha[1] - alpha[2];
gamma[0] = theta[0] + theta[1];
gamma[1] = theta[3] + theta[2];
gamma[2] = theta[3] - theta[2];
gamma[3] = theta[0] - theta[1];
data[column] = gamma[0] + beta[0];
data[8 + column] = gamma[1] + beta[1];
data[16 + column] = gamma[2] + beta[2];
data[24 + column] = gamma[3] + beta[3];
data[32 + column] = gamma[3] - beta[3];
data[40 + column] = gamma[2] - beta[2];
data[48 + column] = gamma[1] - beta[1];
data[56 + column] = gamma[0] - beta[0];
}
}
#[cfg(any(feature = "avx2-tests", feature = "sse2-tests"))]
pub fn dct_inverse_8x8(data: &mut [f32; 64]) {
dct_inverse_8x8_batch(std::iter::once(data));
}
pub fn dct_inverse_8x8_batch<'a>(blocks: impl Iterator<Item = &'a mut [f32; 64]>) {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
use pulp::x86::{V1, V3};
if let Some(v3) = V3::try_new() {
avx::dct_inverse_8x8_batch(v3, blocks);
return;
}
if let Some(v1) = V1::try_new() {
for data in blocks {
sse2::dct_inverse_8x8(v1, data);
}
return;
}
}
for data in blocks {
dct_inverse_8x8_scalar(data);
}
}
pub fn dct_inverse_8x8_dc_only(data: &mut [f32; 64]) {
let val = data[0] * 0.3535536f32 * 0.3535536f32;
for v in data.iter_mut() {
*v = val;
}
}
#[cfg(any(feature = "avx2-tests", feature = "sse2-tests", feature = "simd-benches"))]
fn pseudo_random_blocks(count: usize) -> Vec<[f32; 64]> {
let mut state: u64 = 0x9e3779b97f4a7c15;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
(((state >> 40) as i32 as f32) / (i32::MAX as f32)) * 1024.0
};
(0..count).map(|_| std::array::from_fn(|_| next())).collect()
}
#[cfg(feature = "simd-benches")]
#[allow(missing_docs)]
pub mod simd_bench_support {
use super::*;
pub fn bench_blocks(count: usize) -> Vec<[f32; 64]> {
pseudo_random_blocks(count)
}
pub fn dct_inverse_8x8_forced_scalar(data: &mut [f32; 64]) {
dct_inverse_8x8_scalar(data);
}
pub fn dct_inverse_8x8_forced_sse2(data: &mut [f32; 64]) -> bool {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if let Some(v1) = pulp::x86::V1::try_new() {
sse2::dct_inverse_8x8(v1, data);
return true;
}
#[allow(unreachable_code)]
false
}
pub fn dct_inverse_8x8_forced_avx2(data: &mut [f32; 64]) -> bool {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if let Some(v3) = pulp::x86::V3::try_new() {
avx::dct_inverse_8x8(v3, data);
return true;
}
#[allow(unreachable_code)]
false
}
pub fn dct_inverse_8x8_forced_avx2_batch<'a>(
blocks: impl Iterator<Item = &'a mut [f32; 64]>,
) -> bool {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if let Some(v3) = pulp::x86::V3::try_new() {
avx::dct_inverse_8x8_batch(v3, blocks);
return true;
}
#[allow(unreachable_code)]
{
let _ = blocks;
false
}
}
}
#[cfg(any(feature = "avx2-tests", feature = "sse2-tests"))]
#[allow(dead_code, missing_docs)]
pub mod simd_test_support {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimdTier {
Avx2,
Sse2,
Scalar,
}
pub fn selected_simd_tier() -> SimdTier {
if has_avx2_tier() {
return SimdTier::Avx2;
}
if has_sse2_tier() {
return SimdTier::Sse2;
}
SimdTier::Scalar
}
fn has_avx2_tier() -> bool {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
return pulp::x86::V3::try_new().is_some();
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
false
}
}
fn has_sse2_tier() -> bool {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
return pulp::x86::V1::try_new().is_some();
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
false
}
}
fn assert_close_to_scalar_reference(kernel: impl Fn(&mut [f32; 64])) {
for mut expected in pseudo_random_blocks(64) {
let mut actual = expected;
dct_inverse_8x8_scalar(&mut expected);
kernel(&mut actual);
for (e, a) in expected.iter().zip(actual.iter()) {
let tolerance = 1e-2 * e.abs().max(1.0);
assert!(
(e - a).abs() <= tolerance,
"expected {e}, got {a} (diff {})",
(e - a).abs()
);
}
}
}
#[cfg(feature = "avx2-tests")]
pub fn assert_avx2_available() {
assert!(
has_avx2_tier(),
"AVX2 SIMD test requested, but the AVX2/FMA tier is unavailable; selected tier: {:?}",
selected_simd_tier()
);
}
#[cfg(feature = "avx2-tests")]
pub fn assert_avx2_close_to_scalar_reference() {
assert_avx2_available();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let v3 = pulp::x86::V3::try_new().expect("AVX2 tier checked above");
assert_close_to_scalar_reference(|data| avx::dct_inverse_8x8(v3, data));
}
}
#[cfg(feature = "avx2-tests")]
pub fn assert_dispatch_picks_avx2() {
assert_avx2_available();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let v3 = pulp::x86::V3::try_new().expect("AVX2 tier checked above");
for mut expected in pseudo_random_blocks(16) {
let mut actual = expected;
avx::dct_inverse_8x8(v3, &mut expected);
dct_inverse_8x8(&mut actual);
assert_eq!(expected, actual);
}
}
}
#[cfg(feature = "sse2-tests")]
pub fn assert_sse2_available() {
assert!(
has_sse2_tier(),
"SSE2 SIMD test requested, but the SSE2 tier is unavailable; selected tier: {:?}",
selected_simd_tier()
);
}
#[cfg(feature = "sse2-tests")]
pub fn assert_sse2_close_to_scalar_reference() {
assert_sse2_available();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let v1 = pulp::x86::V1::try_new().expect("SSE2 tier checked above");
assert_close_to_scalar_reference(|data| sse2::dct_inverse_8x8(v1, data));
}
}
#[cfg(feature = "sse2-tests")]
pub fn assert_dispatch_picks_sse2_without_avx2() {
assert_sse2_available();
assert!(
!has_avx2_tier(),
"SSE2 dispatch test must run with AVX2 hidden; selected tier: {:?}",
selected_simd_tier()
);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let v1 = pulp::x86::V1::try_new().expect("SSE2 tier checked above");
for mut expected in pseudo_random_blocks(16) {
let mut actual = expected;
sse2::dct_inverse_8x8(v1, &mut expected);
dct_inverse_8x8(&mut actual);
assert_eq!(expected, actual);
}
}
}
}