#![allow(unsafe_code)]
#[cfg(target_arch = "x86_64")]
#[inline]
#[cfg(target_arch = "x86_64")]
pub(super) fn alpha_fixed_sse_avx2(samples: &[u8; 16], palette: &[u8; 8], bits: u64) -> i32 {
debug_assert!(has_avx2());
unsafe { alpha_fixed_sse_avx2_impl(samples, palette, bits) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn alpha_fixed_sse_avx2_impl(samples: &[u8; 16], palette: &[u8; 8], bits: u64) -> i32 {
use std::arch::x86_64::*;
let sh = _mm256_setr_epi32(0, 3, 6, 9, 12, 15, 18, 21);
let seven = _mm256_set1_epi32(7);
let lo = _mm256_and_si256(
_mm256_srlv_epi32(_mm256_set1_epi32(bits as u32 as i32), sh),
seven,
);
let hi = _mm256_and_si256(
_mm256_srlv_epi32(_mm256_set1_epi32((bits >> 24) as u32 as i32), sh),
seven,
);
let i0 = _mm_packs_epi32(
_mm256_castsi256_si128(lo),
_mm256_extracti128_si256(lo, 1),
);
let i1 = _mm_packs_epi32(
_mm256_castsi256_si128(hi),
_mm256_extracti128_si256(hi, 1),
);
let idx = _mm_packs_epi16(i0, i1);
let pal = _mm_loadl_epi64(palette.as_ptr() as *const __m128i);
let rec = _mm_shuffle_epi8(pal, idx);
let src = _mm_loadu_si128(samples.as_ptr() as *const __m128i);
let d = _mm256_sub_epi16(_mm256_cvtepu8_epi16(rec), _mm256_cvtepu8_epi16(src));
let sq = _mm256_madd_epi16(d, d);
let h = _mm256_hadd_epi32(sq, sq);
let h = _mm256_hadd_epi32(h, h);
_mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
))
}
#[cfg(target_arch = "x86_64")]
pub(super) fn alpha_minmax_avx2(samples: &[u8; 16]) -> (u8, u8) {
debug_assert!(has_avx2());
unsafe { alpha_minmax_avx2_impl(samples) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn alpha_minmax_avx2_impl(samples: &[u8; 16]) -> (u8, u8) {
use std::arch::x86_64::*;
let v = _mm_loadu_si128(samples.as_ptr() as *const __m128i);
let mn = _mm_min_epu8(v, _mm_srli_si128(v, 8));
let mn = _mm_min_epu8(mn, _mm_srli_si128(mn, 4));
let mn = _mm_min_epu8(mn, _mm_srli_si128(mn, 2));
let mn = _mm_min_epu8(mn, _mm_srli_si128(mn, 1));
let mx = _mm_max_epu8(v, _mm_srli_si128(v, 8));
let mx = _mm_max_epu8(mx, _mm_srli_si128(mx, 4));
let mx = _mm_max_epu8(mx, _mm_srli_si128(mx, 2));
let mx = _mm_max_epu8(mx, _mm_srli_si128(mx, 1));
(
(_mm_cvtsi128_si32(mn) & 0xFF) as u8,
(_mm_cvtsi128_si32(mx) & 0xFF) as u8,
)
}
pub(super) fn has_avx2() -> bool {
use std::sync::atomic::{AtomicU8, Ordering};
static F: AtomicU8 = AtomicU8::new(0);
match F.load(Ordering::Relaxed) {
0 => {
let v = detect_avx2();
F.store(v, Ordering::Relaxed);
v == 2
}
v => v == 2,
}
}
#[cold]
#[inline(never)]
fn detect_avx2() -> u8 {
if std::is_x86_feature_detected!("avx2") {
2
} else {
1
}
}
#[cfg(not(target_arch = "x86_64"))]
#[inline]
pub(super) fn has_avx2() -> bool {
false
}
#[cfg(target_arch = "x86_64")]
pub(super) fn fit_indices_mode6_avx2(
pixels: &[[u8; 4]; 16],
pal: &[[u8; 4]; 16],
) -> ([u8; 16], i64) {
debug_assert!(has_avx2());
unsafe { fit_indices_mode6_avx2_impl(pixels, pal) }
}
#[inline]
#[target_feature(enable = "avx2")]
#[target_feature(enable = "avx2")]
unsafe fn fit_indices_mode6_avx2_impl(
pixels: &[[u8; 4]; 16],
pal: &[[u8; 4]; 16],
) -> ([u8; 16], i64) {
use std::arch::x86_64::*;
let base = pixels.as_ptr() as *const u8;
let perm = _mm256_setr_epi32(0, 1, 4, 5, 2, 3, 6, 7);
let q0 = _mm256_cvtepu8_epi16(_mm_loadu_si128(base as *const __m128i));
let q1 = _mm256_cvtepu8_epi16(_mm_loadu_si128(base.add(16) as *const __m128i));
let q2 = _mm256_cvtepu8_epi16(_mm_loadu_si128(base.add(32) as *const __m128i));
let q3 = _mm256_cvtepu8_epi16(_mm_loadu_si128(base.add(48) as *const __m128i));
let pb = pal.as_ptr() as *const u8;
let mut pal16 = [0i16; 64];
for h in 0..4usize {
_mm256_storeu_si256(
pal16.as_mut_ptr().add(h * 16) as *mut __m256i,
_mm256_cvtepu8_epi16(_mm_loadu_si128(pb.add(h * 16) as *const __m128i)),
);
}
let mut best_lo = _mm256_set1_epi32(i32::MAX);
let mut best_hi = _mm256_set1_epi32(i32::MAX);
let mut idx_lo = _mm256_setzero_si256();
let mut idx_hi = _mm256_setzero_si256();
let one = _mm256_set1_epi32(1);
let mut kv = _mm256_setzero_si256();
for kk in 0..4usize {
for k in [kk * 4, kk * 4 + 1, kk * 4 + 2, kk * 4 + 3] {
let pv = _mm256_set1_epi64x(*(pal16.as_ptr().add(k * 4) as *const i64));
let da = _mm256_sub_epi16(q0, pv);
let db = _mm256_sub_epi16(q1, pv);
let cur_lo = _mm256_hadd_epi32(_mm256_madd_epi16(da, da), _mm256_madd_epi16(db, db));
let dc = _mm256_sub_epi16(q2, pv);
let dd = _mm256_sub_epi16(q3, pv);
let cur_hi = _mm256_hadd_epi32(_mm256_madd_epi16(dc, dc), _mm256_madd_epi16(dd, dd));
let m_lo = _mm256_cmpgt_epi32(best_lo, cur_lo);
let m_hi = _mm256_cmpgt_epi32(best_hi, cur_hi);
best_lo = _mm256_blendv_epi8(best_lo, cur_lo, m_lo);
best_hi = _mm256_blendv_epi8(best_hi, cur_hi, m_hi);
idx_lo = _mm256_blendv_epi8(idx_lo, kv, m_lo);
idx_hi = _mm256_blendv_epi8(idx_hi, kv, m_hi);
kv = _mm256_add_epi32(kv, one);
}
}
let s = _mm256_add_epi32(best_lo, best_hi);
let h = _mm256_hadd_epi32(s, s);
let h = _mm256_hadd_epi32(h, h);
let err = _mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
)) as i64;
let idx_lo = _mm256_permutevar8x32_epi32(idx_lo, perm);
let idx_hi = _mm256_permutevar8x32_epi32(idx_hi, perm);
let i0 = _mm_packs_epi32(
_mm256_castsi256_si128(idx_lo),
_mm256_extracti128_si256(idx_lo, 1),
);
let i1 = _mm_packs_epi32(
_mm256_castsi256_si128(idx_hi),
_mm256_extracti128_si256(idx_hi, 1),
);
let mut best_i = [0u8; 16];
_mm_storeu_si128(best_i.as_mut_ptr() as *mut __m128i, _mm_packs_epi16(i0, i1));
(best_i, err)
}
#[cfg(target_arch = "x86_64")]
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Copy)]
pub(super) struct Bc1Pal {
p8: [i16; 16],
cst: [i32; 4],
}
#[cfg(target_arch = "x86_64")]
impl Bc1Pal {
pub(super) const ZERO: Self = Self {
p8: [0; 16],
cst: [0; 4],
};
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn prep_palette_bytes(v: std::arch::x86_64::__m128i) -> Bc1Pal {
use std::arch::x86_64::*;
let (p8v, cst4) = prep_palette_regs(v);
let mut p8 = [0i16; 16];
_mm256_storeu_si256(p8.as_mut_ptr() as *mut __m256i, p8v);
let mut cst = [0i32; 4];
_mm_storeu_si128(cst.as_mut_ptr() as *mut __m128i, cst4);
Bc1Pal { p8, cst }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn prep_palette_regs(
v: std::arch::x86_64::__m128i,
) -> (std::arch::x86_64::__m256i, std::arch::x86_64::__m128i) {
use std::arch::x86_64::*;
let w = _mm256_cvtepu8_epi16(v);
let hp = _mm256_hadd_epi32(_mm256_madd_epi16(w, w), _mm256_madd_epi16(w, w));
let q4 = _mm_unpacklo_epi64(
_mm256_castsi256_si128(hp),
_mm256_extracti128_si256(hp, 1),
);
(
_mm256_slli_epi16(w, 3),
_mm_add_epi32(_mm_slli_epi32(q4, 2), _mm_setr_epi32(0, 1, 2, 3)),
)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn widen_palette(colors: &[[u8; 3]; 4]) -> Bc1Pal {
prep_palette_bytes(restride_palette_3to4(colors))
}
#[cfg(target_arch = "x86_64")]
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn restride_palette_3to4(colors: &[[u8; 3]; 4]) -> std::arch::x86_64::__m128i {
use std::arch::x86_64::*;
let base = colors.as_ptr() as *const u8;
let lo = (base as *const u64).read_unaligned();
let hi = (base.add(8) as *const u32).read_unaligned();
let v = _mm_insert_epi32(_mm_cvtsi64_si128(lo as i64), hi as i32, 2);
_mm_shuffle_epi8(
v,
_mm_setr_epi8(0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11, -1),
)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_widen_palette(colors: &[[u8; 3]; 4]) -> Bc1Pal {
debug_assert!(has_avx2());
unsafe { widen_palette(colors) }
}
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_fit_4color_pre_avx2(
pixels: &[[u8; 4]; 16],
pal: &Bc1Pal,
psq: i32,
err_limit: i32,
) -> Option<(u32, i32)> {
debug_assert!(has_avx2());
unsafe { bc1_fit_4color_pre_avx2_impl(pixels, pal, psq, err_limit) }
}
pub(super) fn bc1_fit_4color_avx2(
pixels: &[[u8; 4]; 16],
colors: &[[u8; 3]; 4],
err_limit: i32,
) -> Option<(u32, i32)> {
debug_assert!(has_avx2());
unsafe { bc1_fit_4color_avx2_impl(pixels, colors, err_limit) }
}
#[target_feature(enable = "avx2")]
unsafe fn bc1_fit_4color_avx2_impl(
pixels: &[[u8; 4]; 16],
colors: &[[u8; 3]; 4],
err_limit: i32,
) -> Option<(u32, i32)> {
let (p8, cst4) = prep_palette_regs(restride_palette_3to4(colors));
bc1_fit_core_avx2(pixels, p8, cst4, bc1_psq_rgb_avx2_impl(pixels), err_limit)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn alpha_channel_avx2(pixels: &[[u8; 4]; 16]) -> [u8; 16] {
debug_assert!(has_avx2());
unsafe { alpha_channel_avx2_impl(pixels) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn alpha_channel_avx2_impl(pixels: &[[u8; 4]; 16]) -> [u8; 16] {
use std::arch::x86_64::*;
let base = pixels.as_ptr() as *const u8;
let m = _mm_setr_epi8(3, 7, 11, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
let row = |off: usize| {
_mm_shuffle_epi8(_mm_loadu_si128(base.add(off) as *const __m128i), m)
};
let (r0, r1, r2, r3) = (row(0), row(16), row(32), row(48));
let v = _mm_unpacklo_epi64(
_mm_unpacklo_epi32(r0, r1),
_mm_unpacklo_epi32(r2, r3),
);
let mut out = [0u8; 16];
_mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, v);
out
}
#[cfg(target_arch = "x86_64")]
pub(super) fn alpha_pack_indices_avx2(indices: &[u8; 16]) -> u64 {
debug_assert!(has_avx2());
unsafe { alpha_pack_indices_avx2_impl(indices) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn alpha_pack_indices_avx2_impl(indices: &[u8; 16]) -> u64 {
use std::arch::x86_64::*;
let iv = _mm_loadu_si128(indices.as_ptr() as *const __m128i);
let g6 = _mm_maddubs_epi16(
iv,
_mm_setr_epi8(1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8),
);
let g12 = _mm_madd_epi16(g6, _mm_setr_epi16(1, 64, 1, 64, 1, 64, 1, 64));
let q0 = _mm_extract_epi32::<0>(g12) as u32 as u64;
let q1 = _mm_extract_epi32::<1>(g12) as u32 as u64;
let q2 = _mm_extract_epi32::<2>(g12) as u32 as u64;
let q3 = _mm_extract_epi32::<3>(g12) as u32 as u64;
q0 | (q1 << 12) | (q2 << 24) | (q3 << 36)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_psq_rgb_avx2(pixels: &[[u8; 4]; 16]) -> i32 {
debug_assert!(has_avx2());
unsafe { bc1_psq_rgb_avx2_impl(pixels) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_psq_rgb_avx2_impl(pixels: &[[u8; 4]; 16]) -> i32 {
use std::arch::x86_64::*;
let base = pixels.as_ptr() as *const u8;
let keep = _mm256_set1_epi64x(
u64::from_le_bytes([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0]) as i64,
);
let ld = |off: usize| {
_mm256_and_si256(
_mm256_cvtepu8_epi16(_mm_loadu_si128(base.add(off) as *const __m128i)),
keep,
)
};
let (p0, p1, p2, p3) = (ld(0), ld(16), ld(32), ld(48));
let v = _mm256_add_epi32(
_mm256_add_epi32(_mm256_madd_epi16(p0, p0), _mm256_madd_epi16(p1, p1)),
_mm256_add_epi32(_mm256_madd_epi16(p2, p2), _mm256_madd_epi16(p3, p3)),
);
let h = _mm256_hadd_epi32(v, v);
let h = _mm256_hadd_epi32(h, h);
_mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
))
}
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_fit_565_avx2(
pixels: &[[u8; 4]; 16],
c0: u16,
c1: u16,
psq: i32,
err_limit: i32,
) -> Option<(u32, i32)> {
debug_assert!(has_avx2());
unsafe { bc1_fit_565_avx2_impl(pixels, c0, c1, psq, err_limit) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_fit_565_avx2_impl(
pixels: &[[u8; 4]; 16],
c0: u16,
c1: u16,
psq: i32,
err_limit: i32,
) -> Option<(u32, i32)> {
let (p8, cst4) = prep_palette_regs(bc1_palette_565_avx2(c0, c1));
bc1_fit_core_avx2(pixels, p8, cst4, psq, err_limit)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_fit_4color_pre_avx2_impl(
pixels: &[[u8; 4]; 16],
pal: &Bc1Pal,
psq: i32,
err_limit: i32,
) -> Option<(u32, i32)> {
use std::arch::x86_64::*;
bc1_fit_core_avx2(
pixels,
_mm256_loadu_si256(pal.p8.as_ptr() as *const __m256i),
_mm_loadu_si128(pal.cst.as_ptr() as *const __m128i),
psq,
err_limit,
)
}
#[cfg(target_arch = "x86_64")]
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn bc1_fit_core_avx2(
pixels: &[[u8; 4]; 16],
p8: std::arch::x86_64::__m256i,
cst4: std::arch::x86_64::__m128i,
psq: i32,
err_limit: i32,
) -> Option<(u32, i32)> {
use std::arch::x86_64::*;
let base = pixels.as_ptr() as *const u8;
let perm = _mm256_setr_epi32(0, 1, 4, 5, 2, 3, 6, 7);
let ld = |off: usize| _mm256_cvtepu8_epi16(_mm_loadu_si128(base.add(off) as *const __m128i));
let (p0, p1, p2, p3) = (ld(0), ld(16), ld(32), ld(48));
let mut best_lo = _mm256_set1_epi32(i32::MAX);
let mut best_hi = _mm256_set1_epi32(i32::MAX);
macro_rules! step {
($k:literal) => {{
const IMM: i32 = $k | ($k << 2) | ($k << 4) | ($k << 6);
let pv = _mm256_permute4x64_epi64::<IMM>(p8);
let cv = _mm256_broadcastd_epi32(_mm_shuffle_epi32::<IMM>(cst4));
let lo = _mm256_sub_epi32(
cv,
_mm256_hadd_epi32(_mm256_madd_epi16(p0, pv), _mm256_madd_epi16(p1, pv)),
);
let hi = _mm256_sub_epi32(
cv,
_mm256_hadd_epi32(_mm256_madd_epi16(p2, pv), _mm256_madd_epi16(p3, pv)),
);
best_lo = _mm256_min_epi32(best_lo, lo);
best_hi = _mm256_min_epi32(best_hi, hi);
}};
}
step!(0);
step!(1);
step!(2);
step!(3);
let s = _mm256_add_epi32(_mm256_srai_epi32(best_lo, 2), _mm256_srai_epi32(best_hi, 2));
let h = _mm256_hadd_epi32(s, s);
let h = _mm256_hadd_epi32(h, h);
let err = psq
+ _mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
));
if err >= err_limit {
return None;
}
let tag = _mm256_set1_epi32(3);
let idx_lo = _mm256_permutevar8x32_epi32(_mm256_and_si256(best_lo, tag), perm);
let idx_hi = _mm256_permutevar8x32_epi32(_mm256_and_si256(best_hi, tag), perm);
let i0 = _mm_packs_epi32(
_mm256_castsi256_si128(idx_lo),
_mm256_extracti128_si256(idx_lo, 1),
);
let i1 = _mm_packs_epi32(
_mm256_castsi256_si128(idx_hi),
_mm256_extracti128_si256(idx_hi, 1),
);
let iv = _mm_packs_epi16(i0, i1);
let g8 = _mm_maddubs_epi16(
iv,
_mm_setr_epi8(1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4),
);
let g16 = _mm_madd_epi16(g8, _mm_setr_epi16(1, 16, 1, 16, 1, 16, 1, 16));
let packed = _mm_packus_epi16(
_mm_packus_epi32(g16, _mm_setzero_si128()),
_mm_setzero_si128(),
);
let table = _mm_cvtsi128_si32(packed) as u32;
Some((table, err))
}
#[cfg(target_arch = "x86_64")]
pub(super) fn alpha_fit_avx2(palette: &[u8; 8], samples: &[u8; 16]) -> ([u8; 16], i32) {
debug_assert!(has_avx2());
unsafe { alpha_fit_avx2_impl(palette, samples) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn alpha_fit_avx2_impl(palette: &[u8; 8], samples: &[u8; 16]) -> ([u8; 16], i32) {
use std::arch::x86_64::*;
let sv = _mm256_cvtepu8_epi16(_mm_loadu_si128(samples.as_ptr() as *const __m128i));
let mut best = _mm256_set1_epi16(i16::MAX);
let mut idx = _mm256_setzero_si256();
for (k, &p) in palette.iter().enumerate() {
let pv = _mm256_set1_epi16(p as i16);
let d = _mm256_abs_epi16(_mm256_sub_epi16(pv, sv));
let m = _mm256_cmpgt_epi16(best, d);
best = _mm256_blendv_epi8(best, d, m);
idx = _mm256_blendv_epi8(idx, _mm256_set1_epi16(k as i16), m);
}
let sq = _mm256_madd_epi16(best, best);
let mut parts = [0i32; 8];
_mm256_storeu_si256(parts.as_mut_ptr() as *mut __m256i, sq);
let err: i32 = parts.iter().sum();
let mut iw = [0i16; 16];
_mm256_storeu_si256(iw.as_mut_ptr() as *mut __m256i, idx);
let mut out = [0u8; 16];
for i in 0..16 {
out[i] = iw[i] as u8;
}
(out, err)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn alpha_nbhd_avx2<const N: usize>(
alpha: &[u8; 16],
s0: u8,
s1: u8,
clamp_hi: i32,
seed_err: i32,
) -> (u8, u8, i32) {
debug_assert!(has_avx2());
debug_assert!(N == 4 || N == 8);
unsafe { alpha_nbhd_avx2_impl::<N>(alpha, s0, s1, clamp_hi, seed_err) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn alpha_nbhd_avx2_impl<const N: usize>(
alpha: &[u8; 16],
s0: u8,
s1: u8,
clamp_hi: i32,
seed_err: i32,
) -> (u8, u8, i32) {
use std::arch::x86_64::*;
const W2: [u32; 4] = [0, 21, 43, 64];
const W3: [u32; 8] = [0, 9, 18, 27, 37, 46, 55, 64];
let sv = _mm256_cvtepu8_epi16(_mm_loadu_si128(alpha.as_ptr() as *const __m128i));
let mut best = (s0, s1, seed_err);
for d0 in -2i32..=2 {
for d1 in -2i32..=2 {
if d0 == 0 && d1 == 0 {
continue;
}
let c0 = (s0 as i32 + d0).clamp(0, clamp_hi) as u8;
let c1 = (s1 as i32 + d1).clamp(0, clamp_hi) as u8;
let (u0, u1) = if N == 8 {
((c0 << 2) | (c0 >> 4), (c1 << 2) | (c1 >> 4))
} else {
(c0, c1)
};
let mut mn = _mm256_set1_epi16(i16::MAX);
for k in 0..N {
let w = if N == 8 { W3[k] } else { W2[k] };
let pe = (((64 - w) * u0 as u32 + w * u1 as u32 + 32) / 64) as i16;
let d = _mm256_abs_epi16(_mm256_sub_epi16(_mm256_set1_epi16(pe), sv));
mn = _mm256_min_epi16(mn, d);
}
let sq = _mm256_madd_epi16(mn, mn);
let h = _mm256_hadd_epi32(sq, sq);
let h = _mm256_hadd_epi32(h, h);
let err = _mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
));
if err < best.2 {
best = (c0, c1, err);
}
}
}
best
}
#[cfg(target_arch = "x86_64")]
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_palette_565_avx2(c0: u16, c1: u16) -> std::arch::x86_64::__m128i {
use std::arch::x86_64::*;
let sh = _mm_setr_epi32(11, 5, 0, 0);
let msk = _mm_setr_epi32(31, 63, 31, 0);
let up = _mm_setr_epi32(3, 2, 3, 0);
let dn = _mm_setr_epi32(2, 4, 2, 0);
let expand = |c: u16| {
let t = _mm_and_si128(_mm_srlv_epi32(_mm_set1_epi32(c as i32), sh), msk);
_mm_or_si128(_mm_sllv_epi32(t, up), _mm_srlv_epi32(t, dn))
};
let e = expand(c0);
let f = expand(c1);
let sum = _mm_add_epi32(e, f);
let third = _mm_set1_epi32(21846);
let (p2, p3) = if c0 > c1 {
(
_mm_mulhi_epu16(_mm_add_epi32(sum, e), third),
_mm_mulhi_epu16(_mm_add_epi32(sum, f), third),
)
} else {
(_mm_srli_epi32(sum, 1), _mm_setzero_si128())
};
_mm_packus_epi16(
_mm_packus_epi32(e, f),
_mm_packus_epi32(p2, p3),
)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_palette_565_i16_avx2(c0: u16, c1: u16) -> Bc1Pal {
debug_assert!(has_avx2());
unsafe { bc1_palette_565_i16_avx2_impl(c0, c1) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_palette_565_i16_avx2_impl(c0: u16, c1: u16) -> Bc1Pal {
prep_palette_bytes(bc1_palette_565_avx2(c0, c1))
}
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_fixed_sse_565_avx2(
pixels: &[[u8; 4]; 16],
c0: u16,
c1: u16,
table: u32,
) -> i32 {
debug_assert!(has_avx2());
unsafe { bc1_fixed_sse_565_avx2_impl(pixels, c0, c1, table) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_fixed_sse_565_avx2_impl(
pixels: &[[u8; 4]; 16],
c0: u16,
c1: u16,
table: u32,
) -> i32 {
bc1_sse_from_pal(pixels, bc1_palette_565_avx2(c0, c1), table)
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_sse_from_pal(
pixels: &[[u8; 4]; 16],
p: std::arch::x86_64::__m128i,
table: u32,
) -> i32 {
use std::arch::x86_64::*;
let src = pixels.as_ptr() as *const u8;
let keep = _mm256_set1_epi64x(
u64::from_le_bytes([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0]) as i64,
);
let mut acc = _mm256_setzero_si256();
for g in 0..4usize {
let sel = _mm_loadu_si128(
crate::decode::simd::BC1_SEL[((table >> (8 * g)) & 0xff) as usize].as_ptr()
as *const __m128i,
);
let rec = _mm256_cvtepu8_epi16(_mm_shuffle_epi8(p, sel));
let want = _mm256_and_si256(
_mm256_cvtepu8_epi16(_mm_loadu_si128(src.add(g * 16) as *const __m128i)),
keep,
);
let d = _mm256_sub_epi16(rec, want);
acc = _mm256_add_epi32(acc, _mm256_madd_epi16(d, d));
}
let h = _mm256_hadd_epi32(acc, acc);
let h = _mm256_hadd_epi32(h, h);
_mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
))
}
#[cfg(target_arch = "x86_64")]
pub(super) fn mode6_chan_sse_avx2(px: &[u8; 16], w: &[i16; 16], v0: u8, v1: u8) -> i64 {
debug_assert!(has_avx2());
unsafe { mode6_chan_sse_avx2_impl(px, w, v0, v1) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn mode6_chan_sse_avx2_impl(px: &[u8; 16], w: &[i16; 16], v0: u8, v1: u8) -> i64 {
use std::arch::x86_64::*;
let base = _mm256_set1_epi16(v0 as i16 * 64 + 32);
let delta = _mm256_set1_epi16(v1 as i16 - v0 as i16);
let wv = _mm256_loadu_si256(w.as_ptr() as *const __m256i);
let v = _mm256_srai_epi16(
_mm256_add_epi16(base, _mm256_mullo_epi16(delta, wv)),
6,
);
let pv = _mm256_cvtepu8_epi16(_mm_loadu_si128(px.as_ptr() as *const __m128i));
let d = _mm256_sub_epi16(v, pv);
let sq = _mm256_madd_epi16(d, d);
let h = _mm256_hadd_epi32(sq, sq);
let h = _mm256_hadd_epi32(h, h);
_mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
)) as i64
}
#[cfg(target_arch = "x86_64")]
#[cfg(target_arch = "x86_64")]
#[cfg(test)]
pub(super) fn ls_accum_sse(pxv: &[[f32; 8]; 16], uw: &[[f32; 8]; 16]) -> ([f32; 4], [f32; 4]) {
debug_assert!(has_avx2());
unsafe { ls_accum_sse_impl(pxv, uw) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
#[cfg(test)]
unsafe fn ls_accum_sse_impl(
pxv: &[[f32; 8]; 16],
uw: &[[f32; 8]; 16],
) -> ([f32; 4], [f32; 4]) {
use std::arch::x86_64::*;
let mut acc = _mm256_setzero_ps();
for i in 0..16usize {
let wv = _mm256_loadu_ps(uw.as_ptr().add(i) as *const f32);
let px = _mm256_loadu_ps(pxv.as_ptr().add(i) as *const f32);
acc = _mm256_add_ps(acc, _mm256_mul_ps(wv, px));
}
let mut o0 = [0f32; 4];
let mut o1 = [0f32; 4];
_mm_storeu_ps(o0.as_mut_ptr(), _mm256_castps256_ps128(acc));
_mm_storeu_ps(o1.as_mut_ptr(), _mm256_extractf128_ps(acc, 1));
(o0, o1)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn ls_pixels_mode6(pixels: &[[u8; 4]; 16]) -> [[f32; 8]; 16] {
debug_assert!(has_avx2());
unsafe { ls_pixels_mode6_impl(pixels) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn ls_pixels_mode6_impl(pixels: &[[u8; 4]; 16]) -> [[f32; 8]; 16] {
use std::arch::x86_64::*;
let dup0 = _mm256_setr_epi32(0, 0, 1, 1, 2, 2, 3, 3);
let dup1 = _mm256_setr_epi32(4, 4, 5, 5, 6, 6, 7, 7);
let src = pixels.as_ptr() as *const u8;
let mut out = [[0f32; 8]; 16];
for i in (0..16usize).step_by(2) {
let two = _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadl_epi64(
src.add(i * 4) as *const __m128i,
)));
_mm256_storeu_ps(out[i].as_mut_ptr(), _mm256_permutevar8x32_ps(two, dup0));
_mm256_storeu_ps(out[i + 1].as_mut_ptr(), _mm256_permutevar8x32_ps(two, dup1));
}
out
}
#[cfg(target_arch = "x86_64")]
pub(super) fn ls_pixels(pixels: &[[u8; 4]; 16]) -> [[f32; 8]; 16] {
debug_assert!(has_avx2());
unsafe { ls_pixels_impl(pixels) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn ls_pixels_impl(pixels: &[[u8; 4]; 16]) -> [[f32; 8]; 16] {
use std::arch::x86_64::*;
let dup0 = _mm256_setr_epi32(0, 1, 2, 3, 0, 1, 2, 3);
let dup1 = _mm256_setr_epi32(4, 5, 6, 7, 4, 5, 6, 7);
let src = pixels.as_ptr() as *const u8;
let mut out = [[0f32; 8]; 16];
for i in (0..16usize).step_by(2) {
let two = _mm256_cvtepi32_ps(_mm256_cvtepu8_epi32(_mm_loadl_epi64(
src.add(i * 4) as *const __m128i,
)));
_mm256_storeu_ps(out[i].as_mut_ptr(), _mm256_permutevar8x32_ps(two, dup0));
_mm256_storeu_ps(out[i + 1].as_mut_ptr(), _mm256_permutevar8x32_ps(two, dup1));
}
out
}
const fn build_sel1() -> [[u8; 4]; 256] {
let mut t = [[0u8; 4]; 256];
let mut b = 0usize;
while b < 256 {
let mut k = 0usize;
while k < 4 {
t[b][k] = ((b >> (2 * k)) & 3) as u8;
k += 1;
}
b += 1;
}
t
}
static SEL1: [[u8; 4]; 256] = build_sel1();
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_chan_sse_avx2(px: &[u8; 16], cols: [u8; 4], table: u32) -> i32 {
debug_assert!(has_avx2());
unsafe { bc1_chan_sse_avx2_impl(px, cols, table) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_chan_sse_avx2_impl(px: &[u8; 16], cols: [u8; 4], table: u32) -> i32 {
use std::arch::x86_64::*;
let b = table.to_le_bytes();
let sel = _mm_setr_epi32(
i32::from_le_bytes(SEL1[b[0] as usize]),
i32::from_le_bytes(SEL1[b[1] as usize]),
i32::from_le_bytes(SEL1[b[2] as usize]),
i32::from_le_bytes(SEL1[b[3] as usize]),
);
let pal = _mm_cvtsi32_si128(i32::from_le_bytes(cols));
let rec = _mm256_cvtepu8_epi16(_mm_shuffle_epi8(pal, sel));
let want = _mm256_cvtepu8_epi16(_mm_loadu_si128(px.as_ptr() as *const __m128i));
let d = _mm256_sub_epi16(rec, want);
let sq = _mm256_madd_epi16(d, d);
let h = _mm256_hadd_epi32(sq, sq);
let h = _mm256_hadd_epi32(h, h);
_mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
))
}
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_ls_solve(
b0: [f32; 4],
b1: [f32; 4],
a00: f32,
a01: f32,
a11: f32,
det: f32,
) -> ([u8; 4], [u8; 4]) {
debug_assert!(has_avx2());
unsafe { bc1_ls_solve_impl(b0, b1, a00, a01, a11, det) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn solve_pair(
b0: [f32; 4],
b1: [f32; 4],
a00: f32,
a01: f32,
a11: f32,
det: f32,
) -> (std::arch::x86_64::__m128, std::arch::x86_64::__m128) {
use std::arch::x86_64::*;
let v0 = _mm_loadu_ps(b0.as_ptr());
let v1 = _mm_loadu_ps(b1.as_ptr());
let dv = _mm_set1_ps(det);
let e0 = _mm_div_ps(
_mm_sub_ps(_mm_mul_ps(_mm_set1_ps(a11), v0), _mm_mul_ps(_mm_set1_ps(a01), v1)),
dv,
);
let e1 = _mm_div_ps(
_mm_sub_ps(_mm_mul_ps(_mm_set1_ps(a00), v1), _mm_mul_ps(_mm_set1_ps(a01), v0)),
dv,
);
(e0, e1)
}
unsafe fn bc1_ls_solve_impl(
b0: [f32; 4],
b1: [f32; 4],
a00: f32,
a01: f32,
a11: f32,
det: f32,
) -> ([u8; 4], [u8; 4]) {
let (e0, e1) = solve_pair(b0, b1, a00, a01, a11, det);
(round_pack(e0), round_pack(e1))
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn round_pack(v: std::arch::x86_64::__m128) -> [u8; 4] {
use std::arch::x86_64::*;
let i = round_lanes(v);
let mut out = [0i32; 4];
_mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, i);
[out[0] as u8, out[1] as u8, out[2] as u8, out[3] as u8]
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn round_lanes(v: std::arch::x86_64::__m128) -> std::arch::x86_64::__m128i {
use std::arch::x86_64::*;
let c = _mm_min_ps(_mm_max_ps(v, _mm_setzero_ps()), _mm_set1_ps(255.0));
let d = _mm256_add_pd(_mm256_cvtps_pd(c), _mm256_set1_pd(0.5));
_mm256_cvttpd_epi32(d)
}
#[cfg(target_arch = "x86_64")]
static UW6: [[f32; 2]; 16] = [
[1.0, 0.0],
[0.9375, 0.0625],
[0.859375, 0.140625],
[0.796875, 0.203125],
[0.734375, 0.265625],
[0.671875, 0.328125],
[0.59375, 0.40625],
[0.53125, 0.46875],
[0.46875, 0.53125],
[0.40625, 0.59375],
[0.328125, 0.671875],
[0.265625, 0.734375],
[0.203125, 0.796875],
[0.140625, 0.859375],
[0.0625, 0.9375],
[0.0, 1.0]
];
#[cfg(target_arch = "x86_64")]
static AW6: [[f32; 4]; 16] = [
[1.0, 0.0, 0.0, 0.0],
[0.87890625, 0.05859375, 0.00390625, 0.0],
[0.738525390625, 0.120849609375, 0.019775390625, 0.0],
[0.635009765625, 0.161865234375, 0.041259765625, 0.0],
[0.539306640625, 0.195068359375, 0.070556640625, 0.0],
[0.451416015625, 0.220458984375, 0.107666015625, 0.0],
[0.3525390625, 0.2412109375, 0.1650390625, 0.0],
[0.2822265625, 0.2490234375, 0.2197265625, 0.0],
[0.2197265625, 0.2490234375, 0.2822265625, 0.0],
[0.1650390625, 0.2412109375, 0.3525390625, 0.0],
[0.107666015625, 0.220458984375, 0.451416015625, 0.0],
[0.070556640625, 0.195068359375, 0.539306640625, 0.0],
[0.041259765625, 0.161865234375, 0.635009765625, 0.0],
[0.019775390625, 0.120849609375, 0.738525390625, 0.0],
[0.00390625, 0.05859375, 0.87890625, 0.0],
[0.0, 0.0, 1.0, 0.0]
];
#[cfg(target_arch = "x86_64")]
pub(super) fn ls_accum_mode6(
pxv: &[[f32; 8]; 16],
indices: &[u8; 16],
) -> ([f32; 4], [f32; 4], [f32; 4]) {
debug_assert!(has_avx2());
unsafe { ls_accum_mode6_impl(pxv, indices) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn ls_accum_mode6_impl(
pxv: &[[f32; 8]; 16],
indices: &[u8; 16],
) -> ([f32; 4], [f32; 4], [f32; 4]) {
use std::arch::x86_64::*;
let mut acc = _mm256_setzero_ps();
let mut aacc = _mm_setzero_ps();
for i in 0..16usize {
let k = indices[i] as usize;
debug_assert!(k < 16);
let wv = _mm256_castpd_ps(_mm256_broadcast_sd(&*(UW6.as_ptr().add(k) as *const f64)));
let px = _mm256_loadu_ps(pxv.as_ptr().add(i) as *const f32);
acc = _mm256_add_ps(acc, _mm256_mul_ps(wv, px));
aacc = _mm_add_ps(aacc, _mm_loadu_ps(AW6.as_ptr().add(k) as *const f32));
}
let acc = _mm256_permutevar8x32_ps(acc, _mm256_setr_epi32(0, 2, 4, 6, 1, 3, 5, 7));
let mut a = [0f32; 4];
let mut o0 = [0f32; 4];
let mut o1 = [0f32; 4];
_mm_storeu_ps(a.as_mut_ptr(), aacc);
_mm_storeu_ps(o0.as_mut_ptr(), _mm256_castps256_ps128(acc));
_mm_storeu_ps(o1.as_mut_ptr(), _mm256_extractf128_ps(acc, 1));
(a, o0, o1)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn mode6_chan_sse_pair_avx2(
px: &[u8; 16],
w: &[i16; 16],
a: u8,
b: u8,
dbase: i16,
ddelta: i16,
) -> (i64, i64) {
debug_assert!(has_avx2());
unsafe { mode6_chan_sse_pair_avx2_impl(px, w, a, b, dbase, ddelta) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn mode6_chan_sse_pair_avx2_impl(
px: &[u8; 16],
w: &[i16; 16],
a: u8,
b: u8,
dbase: i16,
ddelta: i16,
) -> (i64, i64) {
use std::arch::x86_64::*;
let wv = _mm256_loadu_si256(w.as_ptr() as *const __m256i);
let pv = _mm256_cvtepu8_epi16(_mm_loadu_si128(px.as_ptr() as *const __m128i));
let base0 = _mm256_set1_epi16(a as i16 * 64 + 32);
let delta0 = _mm256_set1_epi16(b as i16 - a as i16);
let base1 = _mm256_add_epi16(base0, _mm256_set1_epi16(dbase));
let delta1 = _mm256_add_epi16(delta0, _mm256_set1_epi16(ddelta));
let sq = |base: __m256i, delta: __m256i| {
let v = _mm256_srai_epi16(_mm256_add_epi16(base, _mm256_mullo_epi16(delta, wv)), 6);
let d = _mm256_sub_epi16(v, pv);
_mm256_madd_epi16(d, d)
};
let h = _mm256_hadd_epi32(
_mm256_hadd_epi32(sq(base0, delta0), sq(base1, delta1)),
_mm256_setzero_si256(),
);
let t = _mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
);
(_mm_cvtsi128_si32(t) as i64, _mm_extract_epi32(t, 1) as i64)
}
#[cfg(target_arch = "x86_64")]
static W6M_REP: [[i16; 16]; 4] = [
[0, 0, 0, 0, 4, 4, 4, 4, 9, 9, 9, 9, 13, 13, 13, 13],
[17, 17, 17, 17, 21, 21, 21, 21, 26, 26, 26, 26, 30, 30, 30, 30],
[34, 34, 34, 34, 38, 38, 38, 38, 43, 43, 43, 43, 47, 47, 47, 47],
[51, 51, 51, 51, 55, 55, 55, 55, 60, 60, 60, 60, 64, 64, 64, 64]
];
#[cfg(target_arch = "x86_64")]
pub(super) fn palette_mode6_avx2(base: [i32; 4], c0: [u8; 4], c1: [u8; 4]) -> [[u8; 4]; 16] {
debug_assert!(has_avx2());
unsafe { palette_mode6_avx2_impl(base, c0, c1) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn palette_mode6_avx2_impl(base: [i32; 4], c0: [u8; 4], c1: [u8; 4]) -> [[u8; 4]; 16] {
use std::arch::x86_64::*;
let b = _mm256_broadcastq_epi64(_mm_packs_epi32(
_mm_loadu_si128(base.as_ptr() as *const __m128i),
_mm_setzero_si128(),
));
let d = _mm256_broadcastq_epi64(_mm_sub_epi16(
_mm_cvtepu8_epi16(_mm_cvtsi32_si128(u32::from_le_bytes(c1) as i32)),
_mm_cvtepu8_epi16(_mm_cvtsi32_si128(u32::from_le_bytes(c0) as i32)),
));
let zero = _mm256_setzero_si256();
let mut out = [[0u8; 4]; 16];
let op = out.as_mut_ptr() as *mut u8;
for g in 0..4usize {
let wv = _mm256_loadu_si256(W6M_REP.as_ptr().add(g) as *const __m256i);
let v = _mm256_srai_epi16(_mm256_add_epi16(b, _mm256_mullo_epi16(d, wv)), 6);
let p = _mm256_permute4x64_epi64(_mm256_packus_epi16(v, zero), 0b00_00_10_00);
_mm_storeu_si128(op.add(g * 16) as *mut __m128i, _mm256_castsi256_si128(p));
}
out
}
#[cfg(target_arch = "x86_64")]
pub(super) fn mode6_chan_errs_avx2(
planar: &[[u8; 16]; 4],
w: &[i16; 16],
v: &[(u8, u8); 4],
) -> [i64; 4] {
debug_assert!(has_avx2());
unsafe { mode6_chan_errs_avx2_impl(planar, w, v) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn mode6_chan_errs_avx2_impl(
planar: &[[u8; 16]; 4],
w: &[i16; 16],
v: &[(u8, u8); 4],
) -> [i64; 4] {
use std::arch::x86_64::*;
let wv = _mm256_loadu_si256(w.as_ptr() as *const __m256i);
let sq = |c: usize| {
let (a, b) = v[c];
let pv = _mm256_cvtepu8_epi16(_mm_loadu_si128(planar[c].as_ptr() as *const __m128i));
let base = _mm256_set1_epi16(a as i16 * 64 + 32);
let delta = _mm256_set1_epi16(b as i16 - a as i16);
let val = _mm256_srai_epi16(_mm256_add_epi16(base, _mm256_mullo_epi16(delta, wv)), 6);
let d = _mm256_sub_epi16(val, pv);
_mm256_madd_epi16(d, d)
};
let h = _mm256_hadd_epi32(
_mm256_hadd_epi32(sq(0), sq(1)),
_mm256_hadd_epi32(sq(2), sq(3)),
);
let t = _mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
);
let mut o = [0i32; 4];
_mm_storeu_si128(o.as_mut_ptr() as *mut __m128i, t);
[o[0] as i64, o[1] as i64, o[2] as i64, o[3] as i64]
}
#[cfg(target_arch = "x86_64")]
pub(super) fn palette_fit_mode6_avx2(
pixels: &[[u8; 4]; 16],
base: [i32; 4],
c0: [u8; 4],
c1: [u8; 4],
) -> ([u8; 16], i64) {
debug_assert!(has_avx2());
unsafe { palette_fit_mode6_avx2_impl(pixels, base, c0, c1) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn palette_fit_mode6_avx2_impl(
pixels: &[[u8; 4]; 16],
base: [i32; 4],
c0: [u8; 4],
c1: [u8; 4],
) -> ([u8; 16], i64) {
use std::arch::x86_64::*;
let b = _mm256_broadcastq_epi64(_mm_packs_epi32(
_mm_loadu_si128(base.as_ptr() as *const __m128i),
_mm_setzero_si128(),
));
let d = _mm256_broadcastq_epi64(_mm_sub_epi16(
_mm_cvtepu8_epi16(_mm_cvtsi32_si128(u32::from_le_bytes(c1) as i32)),
_mm_cvtepu8_epi16(_mm_cvtsi32_si128(u32::from_le_bytes(c0) as i32)),
));
let mut pal16 = [0i16; 64];
for g in 0..4usize {
let wv = _mm256_loadu_si256(W6M_REP.as_ptr().add(g) as *const __m256i);
let v = _mm256_srai_epi16(_mm256_add_epi16(b, _mm256_mullo_epi16(d, wv)), 6);
_mm256_storeu_si256(pal16.as_mut_ptr().add(g * 16) as *mut __m256i, v);
}
let src = pixels.as_ptr() as *const u8;
let perm = _mm256_setr_epi32(0, 1, 4, 5, 2, 3, 6, 7);
let q0 = _mm256_cvtepu8_epi16(_mm_loadu_si128(src as *const __m128i));
let q1 = _mm256_cvtepu8_epi16(_mm_loadu_si128(src.add(16) as *const __m128i));
let q2 = _mm256_cvtepu8_epi16(_mm_loadu_si128(src.add(32) as *const __m128i));
let q3 = _mm256_cvtepu8_epi16(_mm_loadu_si128(src.add(48) as *const __m128i));
let mut best_lo = _mm256_set1_epi32(i32::MAX);
let mut best_hi = _mm256_set1_epi32(i32::MAX);
let mut idx_lo = _mm256_setzero_si256();
let mut idx_hi = _mm256_setzero_si256();
let one = _mm256_set1_epi32(1);
let mut kv = _mm256_setzero_si256();
for kk in 0..4usize {
for k in [kk * 4, kk * 4 + 1, kk * 4 + 2, kk * 4 + 3] {
let pv = _mm256_set1_epi64x(*(pal16.as_ptr().add(k * 4) as *const i64));
let da = _mm256_sub_epi16(q0, pv);
let db = _mm256_sub_epi16(q1, pv);
let cur_lo = _mm256_hadd_epi32(_mm256_madd_epi16(da, da), _mm256_madd_epi16(db, db));
let dc = _mm256_sub_epi16(q2, pv);
let dd = _mm256_sub_epi16(q3, pv);
let cur_hi = _mm256_hadd_epi32(_mm256_madd_epi16(dc, dc), _mm256_madd_epi16(dd, dd));
let m_lo = _mm256_cmpgt_epi32(best_lo, cur_lo);
let m_hi = _mm256_cmpgt_epi32(best_hi, cur_hi);
best_lo = _mm256_blendv_epi8(best_lo, cur_lo, m_lo);
best_hi = _mm256_blendv_epi8(best_hi, cur_hi, m_hi);
idx_lo = _mm256_blendv_epi8(idx_lo, kv, m_lo);
idx_hi = _mm256_blendv_epi8(idx_hi, kv, m_hi);
kv = _mm256_add_epi32(kv, one);
}
}
let sum = _mm256_add_epi32(best_lo, best_hi);
let h = _mm256_hadd_epi32(sum, sum);
let h = _mm256_hadd_epi32(h, h);
let err = _mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
)) as i64;
let idx_lo = _mm256_permutevar8x32_epi32(idx_lo, perm);
let idx_hi = _mm256_permutevar8x32_epi32(idx_hi, perm);
let i0 = _mm_packs_epi32(
_mm256_castsi256_si128(idx_lo),
_mm256_extracti128_si256(idx_lo, 1),
);
let i1 = _mm_packs_epi32(
_mm256_castsi256_si128(idx_hi),
_mm256_extracti128_si256(idx_hi, 1),
);
let mut best_i = [0u8; 16];
_mm_storeu_si128(best_i.as_mut_ptr() as *mut __m128i, _mm_packs_epi16(i0, i1));
(best_i, err)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn ls_accum_solve_565(
pxv: &[[f32; 8]; 16],
uw: &[[f32; 8]; 16],
a00: f32,
a01: f32,
a11: f32,
det: f32,
) -> (u16, u16) {
debug_assert!(has_avx2());
unsafe { ls_accum_solve_565_impl(pxv, uw, a00, a01, a11, det) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn ls_accum_solve_565_impl(
pxv: &[[f32; 8]; 16],
uw: &[[f32; 8]; 16],
a00: f32,
a01: f32,
a11: f32,
det: f32,
) -> (u16, u16) {
use std::arch::x86_64::*;
let mut acc = _mm256_setzero_ps();
for i in 0..16usize {
let wv = _mm256_loadu_ps(uw.as_ptr().add(i) as *const f32);
let px = _mm256_loadu_ps(pxv.as_ptr().add(i) as *const f32);
acc = _mm256_add_ps(acc, _mm256_mul_ps(wv, px));
}
let v0 = _mm256_castps256_ps128(acc);
let v1 = _mm256_extractf128_ps(acc, 1);
let dv = _mm_set1_ps(det);
let e0 = _mm_div_ps(
_mm_sub_ps(_mm_mul_ps(_mm_set1_ps(a11), v0), _mm_mul_ps(_mm_set1_ps(a01), v1)),
dv,
);
let e1 = _mm_div_ps(
_mm_sub_ps(_mm_mul_ps(_mm_set1_ps(a00), v1), _mm_mul_ps(_mm_set1_ps(a01), v0)),
dv,
);
let half = _mm256_set1_pd(0.5);
let lo = _mm_setzero_ps();
let hi = _mm_set1_ps(255.0);
let r0 = _mm256_cvttpd_epi32(_mm256_add_pd(
_mm256_cvtps_pd(_mm_min_ps(_mm_max_ps(e0, lo), hi)),
half,
));
let r1 = _mm256_cvttpd_epi32(_mm256_add_pd(
_mm256_cvtps_pd(_mm_min_ps(_mm_max_ps(e1, lo), hi)),
half,
));
let sh = _mm_setr_epi32(3, 2, 3, 0);
let wt = _mm_setr_epi32(2048, 32, 1, 0);
let p565 = |v: __m128i| {
let w = _mm_mullo_epi32(_mm_srlv_epi32(v, sh), wt);
let h = _mm_hadd_epi32(w, w);
_mm_cvtsi128_si32(_mm_hadd_epi32(h, h)) as u16
};
(p565(r0), p565(r1))
}
#[cfg(target_arch = "x86_64")]
pub(super) fn extrema_rgba_avx2(pixels: &[[u8; 4]; 16]) -> ([u8; 4], [u8; 4]) {
debug_assert!(has_avx2());
unsafe { extrema_rgba_avx2_impl(pixels) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn extrema_rgba_avx2_impl(pixels: &[[u8; 4]; 16]) -> ([u8; 4], [u8; 4]) {
use std::arch::x86_64::*;
let src = pixels.as_ptr() as *const u8;
let wv = _mm256_set1_epi16(1);
let perm = _mm256_setr_epi32(0, 1, 4, 5, 2, 3, 6, 7);
let lum = |off: usize| {
let a = _mm256_cvtepu8_epi16(_mm_loadu_si128(src.add(off) as *const __m128i));
let b = _mm256_cvtepu8_epi16(_mm_loadu_si128(src.add(off + 16) as *const __m128i));
_mm256_permutevar8x32_epi32(
_mm256_hadd_epi32(_mm256_madd_epi16(a, wv), _mm256_madd_epi16(b, wv)),
perm,
)
};
let l0 = lum(0);
let l1 = lum(32);
let s0 = _mm256_slli_epi32(l0, 4);
let s1 = _mm256_slli_epi32(l1, 4);
let kmin = _mm256_min_epi32(
_mm256_or_si256(s0, _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7)),
_mm256_or_si256(s1, _mm256_setr_epi32(8, 9, 10, 11, 12, 13, 14, 15)),
);
let kmax = _mm256_max_epi32(
_mm256_or_si256(s0, _mm256_setr_epi32(15, 14, 13, 12, 11, 10, 9, 8)),
_mm256_or_si256(s1, _mm256_setr_epi32(7, 6, 5, 4, 3, 2, 1, 0)),
);
let fold = |v: __m256i, is_min: bool| -> i32 {
let a = _mm256_castsi256_si128(v);
let b = _mm256_extracti128_si256(v, 1);
let r = if is_min { _mm_min_epi32(a, b) } else { _mm_max_epi32(a, b) };
let r2 = _mm_shuffle_epi32(r, 0b01_00_11_10);
let r = if is_min { _mm_min_epi32(r, r2) } else { _mm_max_epi32(r, r2) };
let r3 = _mm_shuffle_epi32(r, 0b10_11_00_01);
let r = if is_min { _mm_min_epi32(r, r3) } else { _mm_max_epi32(r, r3) };
_mm_cvtsi128_si32(r)
};
let imin = (fold(kmin, true) & 15) as usize;
let imax = (15 - (fold(kmax, false) & 15)) as usize;
(pixels[imax], pixels[imin])
}
#[cfg(target_arch = "x86_64")]
pub(super) fn extrema_opaque_avx2(pixels: &[[u8; 4]; 16]) -> ([u8; 3], [u8; 3]) {
debug_assert!(has_avx2());
unsafe { extrema_opaque_avx2_impl(pixels) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn extrema_opaque_avx2_impl(pixels: &[[u8; 4]; 16]) -> ([u8; 3], [u8; 3]) {
use std::arch::x86_64::*;
let src = pixels.as_ptr() as *const u8;
let wv = _mm256_setr_epi16(2, 3, 1, 0, 2, 3, 1, 0, 2, 3, 1, 0, 2, 3, 1, 0);
let perm = _mm256_setr_epi32(0, 1, 4, 5, 2, 3, 6, 7);
let lum = |off: usize| {
let a = _mm256_cvtepu8_epi16(_mm_loadu_si128(src.add(off) as *const __m128i));
let b = _mm256_cvtepu8_epi16(_mm_loadu_si128(src.add(off + 16) as *const __m128i));
_mm256_permutevar8x32_epi32(
_mm256_hadd_epi32(_mm256_madd_epi16(a, wv), _mm256_madd_epi16(b, wv)),
perm,
)
};
let l0 = lum(0);
let l1 = lum(32);
let s0 = _mm256_slli_epi32(l0, 4);
let s1 = _mm256_slli_epi32(l1, 4);
let kmin = _mm256_min_epi32(
_mm256_or_si256(s0, _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7)),
_mm256_or_si256(s1, _mm256_setr_epi32(8, 9, 10, 11, 12, 13, 14, 15)),
);
let kmax = _mm256_max_epi32(
_mm256_or_si256(s0, _mm256_setr_epi32(15, 14, 13, 12, 11, 10, 9, 8)),
_mm256_or_si256(s1, _mm256_setr_epi32(7, 6, 5, 4, 3, 2, 1, 0)),
);
let fold = |v: __m256i, is_min: bool| -> i32 {
let a = _mm256_castsi256_si128(v);
let b = _mm256_extracti128_si256(v, 1);
let r = if is_min { _mm_min_epi32(a, b) } else { _mm_max_epi32(a, b) };
let r2 = _mm_shuffle_epi32(r, 0b01_00_11_10);
let r = if is_min { _mm_min_epi32(r, r2) } else { _mm_max_epi32(r, r2) };
let r3 = _mm_shuffle_epi32(r, 0b10_11_00_01);
let r = if is_min { _mm_min_epi32(r, r3) } else { _mm_max_epi32(r, r3) };
_mm_cvtsi128_si32(r)
};
let imin = (fold(kmin, true) & 15) as usize;
let imax = (15 - (fold(kmax, false) & 15)) as usize;
let (mx, mn) = (pixels[imax], pixels[imin]);
([mx[0], mx[1], mx[2]], [mn[0], mn[1], mn[2]])
}
#[cfg(target_arch = "x86_64")]
pub(super) fn channel_minmax_avx2(pixels: &[[u8; 4]; 16]) -> ([u8; 4], [u8; 4]) {
debug_assert!(has_avx2());
unsafe { channel_minmax_avx2_impl(pixels) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn channel_minmax_avx2_impl(pixels: &[[u8; 4]; 16]) -> ([u8; 4], [u8; 4]) {
use std::arch::x86_64::*;
let src = pixels.as_ptr() as *const u8;
let v0 = _mm_loadu_si128(src as *const __m128i);
let v1 = _mm_loadu_si128(src.add(16) as *const __m128i);
let v2 = _mm_loadu_si128(src.add(32) as *const __m128i);
let v3 = _mm_loadu_si128(src.add(48) as *const __m128i);
let mn = _mm_min_epu8(_mm_min_epu8(v0, v1), _mm_min_epu8(v2, v3));
let mx = _mm_max_epu8(_mm_max_epu8(v0, v1), _mm_max_epu8(v2, v3));
let mn = _mm_min_epu8(mn, _mm_srli_si128(mn, 8));
let mn = _mm_min_epu8(mn, _mm_srli_si128(mn, 4));
let mx = _mm_max_epu8(mx, _mm_srli_si128(mx, 8));
let mx = _mm_max_epu8(mx, _mm_srli_si128(mx, 4));
(
(_mm_cvtsi128_si32(mx) as u32).to_le_bytes(),
(_mm_cvtsi128_si32(mn) as u32).to_le_bytes(),
)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn planar_avx2(pixels: &[[u8; 4]; 16]) -> [[u8; 16]; 4] {
debug_assert!(has_avx2());
unsafe { planar_avx2_impl(pixels) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn planar_avx2_impl(pixels: &[[u8; 4]; 16]) -> [[u8; 16]; 4] {
use std::arch::x86_64::*;
let src = pixels.as_ptr() as *const u8;
let sh = _mm_setr_epi8(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15);
let g = |o: usize| _mm_shuffle_epi8(_mm_loadu_si128(src.add(o) as *const __m128i), sh);
let (s0, s1, s2, s3) = (g(0), g(16), g(32), g(48));
let lo01 = _mm_unpacklo_epi32(s0, s1);
let lo23 = _mm_unpacklo_epi32(s2, s3);
let hi01 = _mm_unpackhi_epi32(s0, s1);
let hi23 = _mm_unpackhi_epi32(s2, s3);
let mut out = [[0u8; 16]; 4];
_mm_storeu_si128(out[0].as_mut_ptr() as *mut __m128i, _mm_unpacklo_epi64(lo01, lo23));
_mm_storeu_si128(out[1].as_mut_ptr() as *mut __m128i, _mm_unpackhi_epi64(lo01, lo23));
_mm_storeu_si128(out[2].as_mut_ptr() as *mut __m128i, _mm_unpacklo_epi64(hi01, hi23));
_mm_storeu_si128(out[3].as_mut_ptr() as *mut __m128i, _mm_unpackhi_epi64(hi01, hi23));
out
}
#[cfg(target_arch = "x86_64")]
pub(super) fn alpha_select_avx2(pal_u: &[u8; 8], samples: &[u8; 16]) -> ([u8; 16], i32) {
debug_assert!(has_avx2());
unsafe { alpha_select_avx2_impl(pal_u, samples) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn alpha_select_avx2_impl(pal_u: &[u8; 8], samples: &[u8; 16]) -> ([u8; 16], i32) {
use std::arch::x86_64::*;
let sv = _mm256_cvtepu8_epi16(_mm_loadu_si128(samples.as_ptr() as *const __m128i));
let mut best = _mm256_set1_epi16(0x7FFF);
for j in 0..8i16 {
let pv = _mm256_set1_epi16(pal_u[j as usize] as i16);
let d = _mm256_abs_epi16(_mm256_sub_epi16(pv, sv));
let key = _mm256_add_epi16(_mm256_slli_epi16(d, 3), _mm256_set1_epi16(j));
best = _mm256_min_epi16(best, key);
}
let idx16 = _mm256_and_si256(best, _mm256_set1_epi16(7));
let d16 = _mm256_srli_epi16(best, 3);
let sq = _mm256_madd_epi16(d16, d16);
let h = _mm256_hadd_epi32(sq, sq);
let h = _mm256_hadd_epi32(h, h);
let err = _mm_cvtsi128_si32(_mm_add_epi32(
_mm256_castsi256_si128(h),
_mm256_extracti128_si256(h, 1),
));
let packed = _mm256_permute4x64_epi64(_mm256_packus_epi16(idx16, idx16), 0b00_00_10_00);
let mut out = [0u8; 16];
_mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, _mm256_castsi256_si128(packed));
(out, err)
}
#[cfg(target_arch = "x86_64")]
pub(super) fn bc1_ls_endpoints_avx2(
pixels: &[[u8; 4]; 16],
table: u32,
) -> Option<([u8; 4], [u8; 4])> {
debug_assert!(has_avx2());
unsafe { bc1_ls_endpoints_avx2_impl(pixels, table) }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn bc1_ls_endpoints_avx2_impl(
pixels: &[[u8; 4]; 16],
table: u32,
) -> Option<([u8; 4], [u8; 4])> {
use std::arch::x86_64::*;
const W: [f32; 4] = [0.0, 1.0, 1.0 / 3.0, 2.0 / 3.0];
let src = pixels.as_ptr() as *const u8;
let mut acc = _mm256_setzero_ps();
let (mut a00, mut a01, mut a11) = (0f32, 0f32, 0f32);
for i in 0..16usize {
let wgt = W[((table >> (2 * i)) & 3) as usize];
let u = 1.0 - wgt;
a00 += u * u;
a01 += u * wgt;
a11 += wgt * wgt;
let wv = _mm256_blend_ps(
_mm256_set1_ps(u),
_mm256_set1_ps(wgt),
0b1111_0000,
);
let px = _mm_cvtepi32_ps(_mm_cvtepu8_epi32(_mm_cvtsi32_si128(
*(src.add(i * 4) as *const i32),
)));
let pv = _mm256_permutevar8x32_ps(
_mm256_castps128_ps256(px),
_mm256_setr_epi32(0, 1, 2, 3, 0, 1, 2, 3),
);
acc = _mm256_add_ps(acc, _mm256_mul_ps(wv, pv));
}
let det = a00 * a11 - a01 * a01;
if det.abs() < 1e-4 {
return None;
}
let mut v0 = [0f32; 4];
let mut v1 = [0f32; 4];
_mm_storeu_ps(v0.as_mut_ptr(), _mm256_castps256_ps128(acc));
_mm_storeu_ps(v1.as_mut_ptr(), _mm256_extractf128_ps(acc, 1));
Some(bc1_ls_solve(v0, v1, a00, a01, a11, det))
}
#[cfg(test)]
mod oracle {
#[cfg(target_arch = "x86_64")]
#[test]
fn mode6_avx2_matches_scalar_exhaustive() {
if !super::has_avx2() {
eprintln!("AVX2 not available; skipping");
return;
}
let mut state = 0xA5A5F00DDEADBEEFu64;
let mut rng = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..200_000u32 {
let mut px = [[0u8; 4]; 16];
let mut pal = [[0u8; 4]; 16];
for p in px.iter_mut() {
let r = rng();
*p = [(r) as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8];
}
for p in pal.iter_mut() {
let r = rng();
*p = [(r) as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8];
}
let fast = super::fit_indices_mode6_avx2(&px, &pal);
let slow = super::super::fit_indices_mode6_exhaustive(&px, &pal);
assert_eq!(fast, slow, "case {case}");
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn alpha_channel_matches_scalar() {
use super::*;
if !has_avx2() {
return;
}
let mut state = 0x77c1_0e35_bb92_4416u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let mut px = [[0u8; 4]; 16];
for p in px.iter_mut() {
for q in p.iter_mut() {
*q = (next() >> 11) as u8;
}
}
let got = alpha_channel_avx2(&px);
let want: [u8; 16] = px.map(|p| p[3]);
assert_eq!(got, want, "case {case}");
}
}
#[test]
fn alpha_pack_indices_matches_scalar() {
use super::*;
if !has_avx2() {
return;
}
let mut state = 0x1d3b_77aa_5c19_e024u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let mut idx = [0u8; 16];
for (i, q) in idx.iter_mut().enumerate() {
*q = match case {
0 => 0,
1 => 7,
_ => ((next() >> 11) as u8) & 7,
};
let _ = i;
}
let got = alpha_pack_indices_avx2(&idx);
let mut want = 0u64;
for (i, &v) in idx.iter().enumerate() {
want |= (v as u64) << (3 * i);
}
assert_eq!(got, want, "case {case}");
}
}
#[test]
fn alpha_fixed_sse_matches_scalar() {
use super::*;
if !has_avx2() {
return;
}
let mut state = 0x5ee0_a1fa_3311_9977u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let mut samples = [0u8; 16];
for q in samples.iter_mut() {
*q = (next() >> 11) as u8;
}
let mut palette = [0u8; 8];
for q in palette.iter_mut() {
*q = (next() >> 11) as u8;
}
let bits = match case {
0 => 0u64,
1 => (1u64 << 48) - 1,
_ => next() & ((1u64 << 48) - 1),
};
let got = alpha_fixed_sse_avx2(&samples, &palette, bits);
let mut want = 0i32;
for i in 0..16 {
let idx = ((bits >> (3 * i)) & 7) as usize;
let d = palette[idx] as i32 - samples[i] as i32;
want += d * d;
}
assert_eq!(got, want, "case {case}");
}
}
#[test]
fn bc1_chan_sse_matches_scalar() {
use super::*;
if !has_avx2() {
return;
}
let mut state = 0xc1a5_5e50_7788_1122u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let mut px = [0u8; 16];
for q in px.iter_mut() {
*q = (next() >> 11) as u8;
}
let r = next();
let cols = [r as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8];
let table = match case {
0 => 0,
1 => u32::MAX,
_ => next() as u32,
};
let got = bc1_chan_sse_avx2(&px, cols, table);
let mut want = 0i32;
for (i, &x) in px.iter().enumerate() {
let d = cols[((table >> (2 * i)) & 3) as usize] as i32 - x as i32;
want += d * d;
}
assert_eq!(got, want, "case {case}");
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn bc1_ls_solve_matches_scalar_bitwise() {
use super::*;
if !has_avx2() {
return;
}
let mut state = 0x501e_1234_abcd_5678u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
let mut f = move || (next() as u32 as f32) / 1.0e5 - 20_000.0;
for case in 0..60_000u32 {
let narrow = case % 2 == 1;
let mut g = || {
if narrow {
(f() % 300.0).abs()
} else {
f()
}
};
let b0 = [g(), g(), g(), g()];
let b1 = [g(), g(), g(), g()];
let (a00, a01, a11) = (g(), g(), g());
let det = if case == 0 { 1.0 } else { g() };
if det == 0.0 || !det.is_finite() {
continue;
}
let (g0, g1) = bc1_ls_solve(b0, b1, a00, a01, a11, det);
for c in 0..4 {
let w0 = (a11 * b0[c] - a01 * b1[c]) / det;
let w1 = (a00 * b1[c] - a01 * b0[c]) / det;
if !w0.is_finite() || !w1.is_finite() {
continue; }
let want0 = crate::encode::blocks::round_clamp_u8(w0);
let want1 = crate::encode::blocks::round_clamp_u8(w1);
assert_eq!(g0[c], want0, "case {case} e0[{c}] from {w0:?}");
assert_eq!(g1[c], want1, "case {case} e1[{c}] from {w1:?}");
}
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn ls_accum_matches_scalar_bitwise() {
use super::*;
if !has_avx2() {
return;
}
let mut state = 0x15ac_c072_9090_3131u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
const W: [f32; 4] = [0.0, 1.0, 1.0 / 3.0, 2.0 / 3.0];
for case in 0..60_000u32 {
let mut px = [[0u8; 4]; 16];
for q in px.iter_mut() {
let r = next();
*q = [r as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8];
}
let table = if case == 0 { 0 } else { next() as u32 };
let mut uw = [[0f32; 8]; 16];
for (i, slot) in uw.iter_mut().enumerate() {
let w = W[((table >> (2 * i)) & 3) as usize];
*slot = [1.0 - w, 1.0 - w, 1.0 - w, 1.0 - w, w, w, w, w];
}
let pxv = ls_pixels(&px);
for (i, q) in px.iter().enumerate() {
for c in 0..4 {
assert_eq!(pxv[i][c], q[c] as f32, "ls_pixels lo [{i}][{c}]");
assert_eq!(pxv[i][c + 4], q[c] as f32, "ls_pixels hi [{i}][{c}]");
}
}
let (g0, g1) = ls_accum_sse(&pxv, &uw);
let mut b0 = [0f32; 3];
let mut b1 = [0f32; 3];
for (i, p) in px.iter().enumerate() {
let (u, wgt) = (uw[i][0], uw[i][4]);
for c in 0..3 {
let x = p[c] as f32;
b0[c] += u * x;
b1[c] += wgt * x;
}
}
for c in 0..3 {
assert_eq!(g0[c].to_bits(), b0[c].to_bits(), "case {case} b0[{c}]");
assert_eq!(g1[c].to_bits(), b1[c].to_bits(), "case {case} b1[{c}]");
}
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn mode6_chan_sse_matches_scalar() {
use super::*;
if !has_avx2() {
return;
}
const W6M: [u32; 16] = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
let mut state = 0x6d0d_6c1a_5151_2727u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..80_000u32 {
let mut px = [0u8; 16];
let mut w = [0i16; 16];
let (v0, v1) = match case {
0 => (0u8, 0u8),
1 => (255, 255),
2 => (0, 255),
3 => (255, 0),
_ => (next() as u8, (next() >> 8) as u8),
};
for k in 0..16usize {
px[k] = (next() >> 16) as u8;
w[k] = W6M[(next() >> 24) as usize % 16] as i16;
}
let got = mode6_chan_sse_avx2(&px, &w, v0, v1);
let base = v0 as i32 * 64 + 32;
let delta = v1 as i32 - v0 as i32;
let mut want = 0i64;
for k in 0..16usize {
let v = ((base + w[k] as i32 * delta) >> 6) as u8;
let d = v as i64 - px[k] as i64;
want += d * d;
}
assert_eq!(got, want, "case {case}");
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn bc1_fixed_sse_matches_scalar() {
use super::*;
if !has_avx2() {
return;
}
let mut state = 0xb17e_55e0_4444_9999u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let mut px = [[0u8; 4]; 16];
for q in px.iter_mut() {
let r = next();
*q = [r as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8];
}
let (c0, c1) = match case {
0 => (0u16, 0u16),
1 => (u16::MAX, 0),
2 => (0, u16::MAX),
3 => (u16::MAX, u16::MAX),
_ => (next() as u16, (next() >> 16) as u16),
};
let table = match case {
0 => 0,
1 => u32::MAX,
_ => next() as u32,
};
let ex5 = |v: u32| ((v << 3) | (v >> 2)) as u8;
let ex6 = |v: u32| ((v << 2) | (v >> 4)) as u8;
let unp = |c: u16| {
let c = c as u32;
[ex5((c >> 11) & 31), ex6((c >> 5) & 63), ex5(c & 31)]
};
let (a, b) = (unp(c0), unp(c1));
let mut rgb = [[0u8; 3]; 4];
rgb[0] = a;
rgb[1] = b;
for k in 0..3 {
if c0 > c1 {
rgb[2][k] = ((2 * a[k] as u32 + b[k] as u32) / 3) as u8;
rgb[3][k] = ((a[k] as u32 + 2 * b[k] as u32) / 3) as u8;
} else {
rgb[2][k] = ((a[k] as u32 + b[k] as u32) / 2) as u8;
rgb[3][k] = 0;
}
}
let got = bc1_fixed_sse_565_avx2(&px, c0, c1, table);
let mut want = 0i32;
for (i, q) in px.iter().enumerate() {
let c = rgb[((table >> (2 * i)) & 3) as usize];
for k in 0..3 {
let d = c[k] as i32 - q[k] as i32;
want += d * d;
}
}
assert_eq!(got, want, "case {case}");
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn alpha_nbhd_avx2_matches_scalar() {
use super::*;
if !has_avx2() {
return;
}
const W2: [u32; 4] = [0, 21, 43, 64];
const W3: [u32; 8] = [0, 9, 18, 27, 37, 46, 55, 64];
fn scalar<const N: usize>(
alpha: &[u8; 16],
s0: u8,
s1: u8,
clamp_hi: i32,
seed_err: i32,
) -> (u8, u8, i32) {
let mut best = (s0, s1, seed_err);
for d0 in -2i32..=2 {
for d1 in -2i32..=2 {
if d0 == 0 && d1 == 0 {
continue;
}
let c0 = (s0 as i32 + d0).clamp(0, clamp_hi) as u8;
let c1 = (s1 as i32 + d1).clamp(0, clamp_hi) as u8;
let (u0, u1) = if N == 8 {
((c0 << 2) | (c0 >> 4), (c1 << 2) | (c1 >> 4))
} else {
(c0, c1)
};
let mut pal = [0u8; N];
for k in 0..N {
let w = if N == 8 { W3[k] } else { W2[k] };
pal[k] = (((64 - w) * u0 as u32 + w * u1 as u32 + 32) / 64) as u8;
}
let mut err = 0i32;
for &a in alpha.iter() {
let mut be = i32::MAX;
for &pe in pal.iter() {
let d = (pe as i32 - a as i32).pow(2);
if d < be {
be = d;
}
}
err += be;
}
if err < best.2 {
best = (c0, c1, err);
}
}
}
best
}
let mut state = 0xb7a1_0ba0_5eed_1234u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let mut alpha = [0u8; 16];
match case {
0 => {}
1 => alpha = [255; 16],
2 => alpha = [77; 16],
_ => {
alpha[..8].copy_from_slice(&next().to_le_bytes());
alpha[8..].copy_from_slice(&next().to_le_bytes());
}
}
let r = next();
let (s0, s1) = ((r >> 3) as u8, (r >> 19) as u8);
let seed = (next() % 40_000) as i32;
assert_eq!(
alpha_nbhd_avx2::<4>(&alpha, s0, s1, 255, seed),
scalar::<4>(&alpha, s0, s1, 255, seed),
"N=4 case {case}"
);
let (q0, q1) = (s0 & 63, s1 & 63);
assert_eq!(
alpha_nbhd_avx2::<8>(&alpha, q0, q1, 63, seed),
scalar::<8>(&alpha, q0, q1, 63, seed),
"N=8 case {case}"
);
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn alpha_avx2_matches_scalar() {
use super::*;
if !has_avx2() {
return;
}
fn scalar(palette: &[u8; 8], samples: &[u8; 16]) -> ([u8; 16], i32) {
let mut idx = [0u8; 16];
let mut err = 0i32;
for (i, &s) in samples.iter().enumerate() {
let mut best = 0u8;
let mut best_d = i32::MAX;
for (j, &p) in palette.iter().enumerate() {
let d = (p as i32 - s as i32).abs();
if d < best_d {
best_d = d;
best = j as u8;
}
}
idx[i] = best;
let diff = palette[best as usize] as i32 - s as i32;
err += diff * diff;
}
(idx, err)
}
let mut state = 0xfeed_1234_9876_abcdu64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..200_000u32 {
let mut pal = [0u8; 8];
let mut sm = [0u8; 16];
match case {
0 => {}
1 => {
pal = [255; 8];
sm = [255; 16];
}
2 => {
pal = [7, 7, 7, 7, 200, 200, 200, 200];
sm = [7, 200, 100, 0, 255, 7, 200, 3, 9, 199, 201, 6, 8, 128, 64, 32];
}
_ => {
let a = next().to_le_bytes();
pal.copy_from_slice(&a);
let b = next().to_le_bytes();
let c = next().to_le_bytes();
sm[..8].copy_from_slice(&b);
sm[8..].copy_from_slice(&c);
}
}
assert_eq!(alpha_fit_avx2(&pal, &sm), scalar(&pal, &sm), "case {case}");
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn bc1_avx2_matches_scalar() {
if !super::has_avx2() {
eprintln!("AVX2 not available; skipping");
return;
}
let mut state = 0x0123456789ABCDEFu64;
let mut rng = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..200_000u32 {
let mut px = [[0u8; 4]; 16];
for p in px.iter_mut() {
let r = rng();
*p = [(r) as u8, (r >> 8) as u8, (r >> 16) as u8, (r >> 24) as u8];
}
let mut colors = [[0u8; 3]; 4];
for c in colors.iter_mut() {
let r = rng();
*c = [(r) as u8, (r >> 8) as u8, (r >> 16) as u8];
}
let fast = super::bc1_fit_4color_avx2(&px, &colors, i32::MAX);
let slow = super::super::bc1_fit_4color_scalar(&px, &colors, i32::MAX);
assert_eq!(fast, slow, "case {case}");
if let Some((_, e)) = slow {
let lim = (e / 2).max(1);
assert_eq!(
super::bc1_fit_4color_avx2(&px, &colors, lim).is_none(),
super::super::bc1_fit_4color_scalar(&px, &colors, lim).is_none(),
"abort parity (case {case})"
);
}
}
}
}