use core::arch::x86_64::{
__m128i, _mm_add_epi16, _mm_and_si128, _mm_cvtsi64_si128, _mm_loadl_epi64, _mm_setr_epi16,
_mm_setr_epi8, _mm_srli_epi16,
_mm_or_si128, _mm_set1_epi32, _mm_set_epi32, _mm_unpacklo_epi64, _mm_loadu_si128, _mm_mullo_epi16, _mm_packus_epi16,
_mm_set1_epi16, _mm_set_epi16, _mm_set_epi64x, _mm_shuffle_epi8, _mm_srai_epi16,
_mm_storel_epi64, _mm_storeu_si128, _mm_unpackhi_epi16, _mm_unpackhi_epi8,
_mm_unpacklo_epi16, _mm_unpacklo_epi8,
};
pub(super) use super::interp_pack::{pack4, pack_bd3, pack_bd4};
#[cfg(test)]
pub(super) use super::interp_pack::{pack3_opaque_base, pack3_opaque_delta};
#[inline(always)]
pub(super) fn write2(b0: i64, d0: i64, b1: i64, d1: i64, w0: i16, w1: i16, dst: &mut [u8]) {
debug_assert!(dst.len() >= 8, "write2 needs eight bytes");
unsafe {
let base = _mm_set_epi64x(b1, b0);
let delta = _mm_set_epi64x(d1, d0);
let w = _mm_set_epi16(w1, w1, w1, w1, w0, w0, w0, w0);
let v = _mm_add_epi16(base, _mm_mullo_epi16(delta, w));
let v = _mm_srai_epi16(v, 6);
let packed = _mm_packus_epi16(v, v);
_mm_storel_epi64(dst.as_mut_ptr() as *mut __m128i, packed);
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
pub(super) fn write2_split(
b0: i64,
d0: i64,
b1: i64,
d1: i64,
wc: (i16, i16),
wa: (i16, i16),
alpha_lane: usize,
dst: &mut [u8],
) {
debug_assert!(dst.len() >= 8, "write2_split needs eight bytes");
debug_assert!(alpha_lane < 4);
unsafe {
let base = _mm_set_epi64x(b1, b0);
let delta = _mm_set_epi64x(d1, d0);
let (c0, a0, c1, a1) = (wc.0, wa.0, wc.1, wa.1);
let w = match alpha_lane {
0 => _mm_set_epi16(c1, c1, c1, a1, c0, c0, c0, a0),
1 => _mm_set_epi16(c1, c1, a1, c1, c0, c0, a0, c0),
2 => _mm_set_epi16(c1, a1, c1, c1, c0, a0, c0, c0),
_ => _mm_set_epi16(a1, c1, c1, c1, a0, c0, c0, c0),
};
let v = _mm_add_epi16(base, _mm_mullo_epi16(delta, w));
let v = _mm_srai_epi16(v, 6);
let packed = _mm_packus_epi16(v, v);
_mm_storel_epi64(dst.as_mut_ptr() as *mut __m128i, packed);
}
}
#[inline(always)]
fn probe(cache: &std::sync::atomic::AtomicU8, detect: impl FnOnce() -> bool) -> bool {
use std::sync::atomic::Ordering;
match cache.load(Ordering::Relaxed) {
0 => {
let v = if detect() { 2 } else { 1 };
cache.store(v, Ordering::Relaxed);
v == 2
}
v => v == 2,
}
}
#[inline]
pub(super) fn has_ssse3() -> bool {
static OK: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
probe(&OK, || {
std::arch::is_x86_feature_detected!("ssse3")
&& std::arch::is_x86_feature_detected!("sse4.1")
})
}
#[target_feature(enable = "ssse3")]
unsafe fn idx_spread_ssse3(w: u64) -> __m128i {
let sel_lo = _mm_setr_epi8(0, -1, 0, -1, 0, 1, 1, -1, 1, -1, 1, 2, 2, -1, 2, -1);
let sel_hi = _mm_setr_epi8(3, -1, 3, -1, 3, 4, 4, -1, 4, -1, 4, 5, 5, -1, 5, -1);
let mult = _mm_setr_epi16(8192, 1024, 128, 4096, 512, 64, 2048, 256);
let src = _mm_cvtsi64_si128(w as i64);
let lo = _mm_srli_epi16(_mm_mullo_epi16(_mm_shuffle_epi8(src, sel_lo), mult), 13);
let hi = _mm_srli_epi16(_mm_mullo_epi16(_mm_shuffle_epi8(src, sel_hi), mult), 13);
_mm_packus_epi16(lo, hi)
}
#[target_feature(enable = "ssse3,sse4.1")]
unsafe fn bc4_palette_xmm(a0: u8, a1: u8, is_signed: bool) -> __m128i {
use core::arch::x86_64::{
_mm_add_epi32, _mm_blend_epi16, _mm_mullo_epi32, _mm_packs_epi32, _mm_setr_epi32,
_mm_srai_epi32,
};
let (e0, e1) = if is_signed {
((a0 as i8 as i32).max(-127), (a1 as i8 as i32).max(-127))
} else {
(a0 as i32, a1 as i32)
};
let dv = _mm_set1_epi32(e1 - e0);
let add = _mm_set1_epi32((e0 << 16) + 32768);
let take = _mm_setr_epi8(0, 2, 4, 6, 8, 10, 12, 14, -1, -1, -1, -1, -1, -1, -1, -1);
let entries = |w: __m128i| _mm_srai_epi32(_mm_add_epi32(_mm_mullo_epi32(dv, w), add), 16);
if e0 > e1 {
let lo = entries(_mm_setr_epi32(0, 65536, 9363, 18724));
let hi = entries(_mm_setr_epi32(28086, 37450, 46812, 56173));
_mm_shuffle_epi8(_mm_packs_epi32(lo, hi), take)
} else {
let lo = entries(_mm_setr_epi32(0, 65536, 13107, 26215));
let hi = entries(_mm_setr_epi32(39321, 52429, 0, 0));
let tail = if is_signed {
_mm_setr_epi16(0, 0, 0, 0, 0, 0, -127, 127)
} else {
_mm_setr_epi16(0, 0, 0, 0, 0, 0, 0, 255)
};
let v = _mm_blend_epi16(_mm_packs_epi32(lo, hi), tail, 0b1100_0000);
_mm_shuffle_epi8(v, take)
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn bc5_gather(
r0: u8,
r1: u8,
green: Option<(u8, u8)>,
ir: u64,
ig: u64,
is_signed: bool,
out: &mut [u8],
pitch: usize,
) -> bool {
if !has_ssse3() {
return false;
}
debug_assert!(out.len() >= 3 * pitch + 16);
unsafe { bc5_gather_ssse3(r0, r1, green, ir, ig, is_signed, out.as_mut_ptr(), pitch) }
true
}
#[allow(clippy::too_many_arguments)]
#[target_feature(enable = "ssse3,sse4.1")]
unsafe fn bc5_gather_ssse3(
r0: u8,
r1: u8,
green: Option<(u8, u8)>,
ir: u64,
ig: u64,
is_signed: bool,
dst: *mut u8,
pitch: usize,
) {
let idx_vec = |w: u64| idx_spread_ssse3(w);
let pal_r = bc4_palette_xmm(r0, r1, is_signed);
let pal_g = match green {
Some((g0, g1)) => bc4_palette_xmm(g0, g1, is_signed),
None => core::arch::x86_64::_mm_setzero_si128(),
};
let rv = _mm_shuffle_epi8(pal_r, idx_vec(ir));
let gv = _mm_shuffle_epi8(pal_g, idx_vec(ig));
let ba = _mm_set1_epi16(0xFF00u16 as i16);
let rg_lo = _mm_unpacklo_epi8(rv, gv); let rg_hi = _mm_unpackhi_epi8(rv, gv); let rows = [
_mm_unpacklo_epi16(rg_lo, ba),
_mm_unpackhi_epi16(rg_lo, ba),
_mm_unpacklo_epi16(rg_hi, ba),
_mm_unpackhi_epi16(rg_hi, ba),
];
for (r, row) in rows.into_iter().enumerate() {
_mm_storeu_si128(dst.add(r * pitch) as *mut __m128i, row);
}
}
#[inline]
pub(super) fn has_f16c() -> bool {
static OK: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
probe(&OK, || {
std::arch::is_x86_feature_detected!("f16c") && std::arch::is_x86_feature_detected!("avx")
})
}
pub(super) fn half48_to_f32(src: &[u16; 48], dst: &mut [f32; 48]) -> bool {
if !has_f16c() {
return false;
}
unsafe { half48_to_f32_f16c(src, dst) }
true
}
#[target_feature(enable = "f16c,avx")]
unsafe fn half48_to_f32_f16c(src: &[u16; 48], dst: &mut [f32; 48]) {
use core::arch::x86_64::{_mm256_cvtph_ps, _mm256_storeu_ps};
for i in 0..6usize {
let h = _mm_loadu_si128(src.as_ptr().add(i * 8) as *const __m128i);
_mm256_storeu_ps(dst.as_mut_ptr().add(i * 8), _mm256_cvtph_ps(h));
}
}
#[inline]
pub(super) fn has_pshufb() -> bool {
static OK: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
probe(&OK, || std::arch::is_x86_feature_detected!("ssse3"))
}
pub(crate) use crate::simd_tables::BC1_SEL;
#[target_feature(enable = "ssse3")]
pub(super) unsafe fn bc1_blocks_ssse3(
data: &[u8],
grid_x: usize,
run_x: usize,
run_y: usize,
out: &mut [u8],
out_w: usize,
) {
let pitch = out_w * 4;
let src = data.as_ptr();
let dst = out.as_mut_ptr();
for by in 0..run_y {
for bx in 0..run_x {
let bi = (by * grid_x + bx) * 8;
let blk = core::slice::from_raw_parts(src.add(bi), 8);
let pal = super::bcn::bc1_palette(blk, false);
let p = _mm_set_epi32(
pal[3] as i32,
pal[2] as i32,
pal[1] as i32,
pal[0] as i32,
);
let idx = u32::from_le_bytes([blk[4], blk[5], blk[6], blk[7]]);
let o = (by * 4 * out_w + bx * 4) * 4;
for row in 0..4usize {
let sel = _mm_loadu_si128(
BC1_SEL[((idx >> (8 * row)) & 0xff) as usize].as_ptr() as *const __m128i,
);
_mm_storeu_si128(
dst.add(o + row * pitch) as *mut __m128i,
_mm_shuffle_epi8(p, sel),
);
}
}
}
}
const RGB_MASK: i32 = 0x00ff_ffff;
const fn build_bc2_alpha() -> [[u8; 8]; 256] {
let mut t = [[0u8; 8]; 256];
let mut b = 0usize;
while b < 256 {
t[b][3] = ((b & 0x0f) * 17) as u8;
t[b][7] = (((b >> 4) & 0x0f) * 17) as u8;
b += 1;
}
t
}
static BC2_ALPHA: [[u8; 8]; 256] = build_bc2_alpha();
const fn build_bc3_sel() -> [[u8; 8]; 64] {
let mut t = [[0x80u8; 8]; 64];
let mut b = 0usize;
while b < 64 {
t[b][3] = (b & 0x7) as u8;
t[b][7] = ((b >> 3) & 0x7) as u8;
b += 1;
}
t
}
static BC3_SEL: [[u8; 8]; 64] = build_bc3_sel();
#[target_feature(enable = "ssse3")]
pub(super) unsafe fn bc2_blocks_ssse3(
data: &[u8],
grid_x: usize,
run_x: usize,
run_y: usize,
out: &mut [u8],
out_w: usize,
) {
let pitch = out_w * 4;
let src = data.as_ptr();
let dst = out.as_mut_ptr();
let keep = _mm_set1_epi32(RGB_MASK);
for by in 0..run_y {
for bx in 0..run_x {
let bi = (by * grid_x + bx) * 16;
let blk = core::slice::from_raw_parts(src.add(bi), 16);
let pal = super::bcn::bc1_palette(&blk[8..16], true);
let p = _mm_set_epi32(pal[3] as i32, pal[2] as i32, pal[1] as i32, pal[0] as i32);
let idx = u32::from_le_bytes([blk[12], blk[13], blk[14], blk[15]]);
let o = (by * 4 * out_w + bx * 4) * 4;
for row in 0..4usize {
let colour = _mm_shuffle_epi8(
p,
_mm_loadu_si128(
BC1_SEL[((idx >> (8 * row)) & 0xff) as usize].as_ptr() as *const __m128i
),
);
let alpha = _mm_unpacklo_epi64(
_mm_loadl_epi64(
BC2_ALPHA[blk[row * 2] as usize].as_ptr() as *const __m128i
),
_mm_loadl_epi64(
BC2_ALPHA[blk[row * 2 + 1] as usize].as_ptr() as *const __m128i
),
);
_mm_storeu_si128(
dst.add(o + row * pitch) as *mut __m128i,
_mm_or_si128(_mm_and_si128(colour, keep), alpha),
);
}
}
}
}
#[target_feature(enable = "ssse3")]
unsafe fn bc3_alpha_xmm(a0: u8, a1: u8) -> __m128i {
use core::arch::x86_64::{_mm_insert_epi16, _mm_mulhi_epu16};
let a0v = _mm_set1_epi16(a0 as i16);
let a1v = _mm_set1_epi16(a1 as i16);
let take = _mm_setr_epi8(0, 2, 4, 6, 8, 10, 12, 14, -1, -1, -1, -1, -1, -1, -1, -1);
let numer = |wa: __m128i, wb: __m128i| {
_mm_add_epi16(
_mm_add_epi16(_mm_mullo_epi16(wa, a0v), _mm_mullo_epi16(wb, a1v)),
_mm_set1_epi16(1),
)
};
if a0 > a1 {
let n = numer(
_mm_setr_epi16(7, 0, 6, 5, 4, 3, 2, 1),
_mm_setr_epi16(0, 7, 1, 2, 3, 4, 5, 6),
);
_mm_shuffle_epi8(_mm_mulhi_epu16(n, _mm_set1_epi16(9363)), take)
} else {
let n = numer(
_mm_setr_epi16(5, 0, 4, 3, 2, 1, 0, 0),
_mm_setr_epi16(0, 5, 1, 2, 3, 4, 0, 0),
);
let e = _mm_mulhi_epu16(n, _mm_set1_epi16(13108));
_mm_shuffle_epi8(_mm_insert_epi16(e, 255, 7), take)
}
}
#[target_feature(enable = "ssse3")]
pub(super) unsafe fn bc3_blocks_ssse3(
data: &[u8],
grid_x: usize,
run_x: usize,
run_y: usize,
out: &mut [u8],
out_w: usize,
) {
let pitch = out_w * 4;
let src = data.as_ptr();
let dst = out.as_mut_ptr();
let keep = _mm_set1_epi32(RGB_MASK);
for by in 0..run_y {
for bx in 0..run_x {
let bi = (by * grid_x + bx) * 16;
let blk = core::slice::from_raw_parts(src.add(bi), 16);
let pal = super::bcn::bc1_palette(&blk[8..16], true);
let p = _mm_set_epi32(pal[3] as i32, pal[2] as i32, pal[1] as i32, pal[0] as i32);
let cidx = u32::from_le_bytes([blk[12], blk[13], blk[14], blk[15]]);
let apal = bc3_alpha_xmm(blk[0], blk[1]);
let aidx = u64::from_le_bytes([
blk[0], blk[1], blk[2], blk[3], blk[4], blk[5], blk[6], blk[7],
]) >> 16;
let o = (by * 4 * out_w + bx * 4) * 4;
for row in 0..4usize {
let colour = _mm_shuffle_epi8(
p,
_mm_loadu_si128(
BC1_SEL[((cidx >> (8 * row)) & 0xff) as usize].as_ptr() as *const __m128i
),
);
let sh = 12 * row;
let sel = _mm_unpacklo_epi64(
_mm_loadl_epi64(
BC3_SEL[((aidx >> sh) & 0x3f) as usize].as_ptr() as *const __m128i
),
_mm_loadl_epi64(
BC3_SEL[((aidx >> (sh + 6)) & 0x3f) as usize].as_ptr() as *const __m128i
),
);
let alpha = _mm_shuffle_epi8(apal, sel);
_mm_storeu_si128(
dst.add(o + row * pitch) as *mut __m128i,
_mm_or_si128(_mm_and_si128(colour, keep), alpha),
);
}
}
}
}
#[inline]
pub(super) fn has_avx2() -> bool {
static OK: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
probe(&OK, || std::arch::is_x86_feature_detected!("avx2"))
}
pub(super) fn bc6h_interp_avx2(
base: &[i32; 3],
delta: &[i32; 3],
w: &[i32; 16],
out: &mut [u16; 48],
) -> bool {
if !has_avx2() {
return false;
}
unsafe { bc6h_interp_avx2_impl(base, delta, w, out) }
true
}
#[target_feature(enable = "avx2")]
unsafe fn bc6h_interp_avx2_impl(
base: &[i32; 3],
delta: &[i32; 3],
w: &[i32; 16],
out: &mut [u16; 48],
) {
use core::arch::x86_64::{
__m256i, _mm256_add_epi32, _mm256_castsi256_si128, _mm256_loadu_si256,
_mm256_mullo_epi32, _mm256_packus_epi32, _mm256_permute4x64_epi64, _mm256_set1_epi32,
_mm256_srai_epi32,
};
let wv = [
_mm256_loadu_si256(w.as_ptr() as *const __m256i),
_mm256_loadu_si256(w.as_ptr().add(8) as *const __m256i),
];
let s31 = _mm256_set1_epi32(31);
for ch in 0..3usize {
let bv = _mm256_set1_epi32(base[ch]);
let dv = _mm256_set1_epi32(delta[ch]);
for half in 0..2usize {
let v = _mm256_srai_epi32(
_mm256_add_epi32(bv, _mm256_mullo_epi32(dv, wv[half])),
6,
);
let v = _mm256_srai_epi32(_mm256_mullo_epi32(v, s31), 6);
let packed = _mm256_permute4x64_epi64(_mm256_packus_epi32(v, v), 0b0000_1000);
_mm_storeu_si128(
out.as_mut_ptr().add(ch * 16 + half * 8) as *mut __m128i,
_mm256_castsi256_si128(packed),
);
}
}
}
#[target_feature(enable = "ssse3,sse4.1")]
pub(super) unsafe fn bc5_blocks_ssse3(
data: &[u8],
grid_x: usize,
run_x: usize,
run_y: usize,
out: &mut [u8],
out_w: usize,
is_signed: bool,
) {
let pitch = out_w * 4;
let src = data.as_ptr();
let dst = out.as_mut_ptr();
for by in 0..run_y {
for bx in 0..run_x {
let bi = (by * grid_x + bx) * 16;
let blk = core::slice::from_raw_parts(src.add(bi), 16);
let ir = super::bcn::bc4_indices(&blk[..8]);
let ig = super::bcn::bc4_indices(&blk[8..16]);
let o = (by * 4 * out_w + bx * 4) * 4;
bc5_gather_ssse3(
blk[0],
blk[1],
Some((blk[8], blk[9])),
ir,
ig,
is_signed,
dst.add(o),
pitch,
);
}
}
}
#[target_feature(enable = "ssse3,sse4.1")]
pub(super) unsafe fn bc4_blocks_ssse3(
data: &[u8],
grid_x: usize,
run_x: usize,
run_y: usize,
out: &mut [u8],
out_w: usize,
is_signed: bool,
) {
let pitch = out_w * 4;
let src = data.as_ptr();
let dst = out.as_mut_ptr();
for by in 0..run_y {
for bx in 0..run_x {
let bi = (by * grid_x + bx) * 8;
let blk = core::slice::from_raw_parts(src.add(bi), 8);
let ir = super::bcn::bc4_indices(blk);
let o = (by * 4 * out_w + bx * 4) * 4;
bc5_gather_ssse3(blk[0], blk[1], None, ir, 0, is_signed, dst.add(o), pitch);
}
}
}
pub(super) unsafe fn bc6h_planar_to_rgba(src: &[u16; 48], dst: *mut f32, pitch: usize) -> bool {
if !has_f16c() {
return false;
}
bc6h_planar_to_rgba_f16c(src, dst, pitch);
true
}
#[target_feature(enable = "f16c,avx")]
unsafe fn bc6h_planar_to_rgba_f16c(src: &[u16; 48], dst: *mut f32, pitch: usize) {
use core::arch::x86_64::{
_mm256_cvtph_ps, _mm256_permute2f128_ps, _mm256_set1_ps, _mm256_shuffle_ps,
_mm256_storeu_ps, _mm256_unpackhi_ps, _mm256_unpacklo_ps,
};
let one = _mm256_set1_ps(1.0);
for g in 0..2usize {
let ld = |ch: usize| {
_mm256_cvtph_ps(_mm_loadu_si128(
src.as_ptr().add(ch * 16 + g * 8) as *const __m128i,
))
};
let (rf, gf, bf) = (ld(0), ld(1), ld(2));
let t0 = _mm256_unpacklo_ps(rf, gf);
let t1 = _mm256_unpackhi_ps(rf, gf);
let t2 = _mm256_unpacklo_ps(bf, one);
let t3 = _mm256_unpackhi_ps(bf, one);
let q0 = _mm256_shuffle_ps(t0, t2, 0x44);
let q1 = _mm256_shuffle_ps(t0, t2, 0xEE);
let q2 = _mm256_shuffle_ps(t1, t3, 0x44);
let q3 = _mm256_shuffle_ps(t1, t3, 0xEE);
let row = dst.add(g * 2 * pitch);
_mm256_storeu_ps(row, _mm256_permute2f128_ps(q0, q1, 0x20));
_mm256_storeu_ps(row.add(8), _mm256_permute2f128_ps(q2, q3, 0x20));
let row = row.add(pitch);
_mm256_storeu_ps(row, _mm256_permute2f128_ps(q0, q1, 0x31));
_mm256_storeu_ps(row.add(8), _mm256_permute2f128_ps(q2, q3, 0x31));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bc4_palette_xmm_matches_scalar_exhaustively() {
if !has_ssse3() {
eprintln!("SSSE3/SSE4.1/BMI2 gate not passed; skipping");
return;
}
for a0 in 0..=255u8 {
for a1 in 0..=255u8 {
for signed in [false, true] {
let want = super::super::bcn::bc4_palette_packed(a0, a1, signed);
let got = unsafe {
let v = bc4_palette_xmm(a0, a1, signed);
let mut out = [0u8; 16];
_mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, v);
u64::from_le_bytes(out[..8].try_into().unwrap())
};
assert_eq!(
got, want,
"a0={a0} a1={a1} signed={signed}: {got:#018x} != {want:#018x}"
);
}
}
}
}
#[test]
fn idx_spread_matches_scalar() {
if !has_ssse3() {
eprintln!("SSSE3/SSE4.1 gate not passed; skipping");
return;
}
let check = |w: u64| {
let got = unsafe {
let v = idx_spread_ssse3(w);
let mut out = [0u8; 16];
_mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, v);
out
};
let mut want = [0u8; 16];
for (p, slot) in want.iter_mut().enumerate() {
*slot = ((w >> (3 * p)) & 7) as u8;
}
assert_eq!(got, want, "w = {w:#014x}");
};
check(0);
check(0x0000_FFFF_FFFF_FFFF);
for p in 0..16u32 {
for v in 0..8u64 {
check(v << (3 * p));
check((0x0000_FFFF_FFFF_FFFFu64 & !(7 << (3 * p))) | (v << (3 * p)));
}
}
let mut state = 0x52c2_9e37_79b9u64 | 1;
for _ in 0..200_000 {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
check(state & 0x0000_FFFF_FFFF_FFFF);
}
}
#[test]
fn bc3_alpha_xmm_matches_scalar_exhaustively() {
if !has_pshufb() {
eprintln!("SSSE3 not available; skipping");
return;
}
for a0 in 0..=255u8 {
for a1 in 0..=255u8 {
let want = super::super::bcn::bc3_alpha_palette_packed(a0, a1);
let got = unsafe {
let v = bc3_alpha_xmm(a0, a1);
let mut out = [0u8; 16];
_mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, v);
u64::from_le_bytes(out[..8].try_into().unwrap())
};
assert_eq!(got, want, "a0={a0} a1={a1}: {got:#018x} != {want:#018x}");
}
}
}
#[test]
fn mode11_interp_vector_matches_scalar() {
if !has_avx2() {
eprintln!("AVX2 not available; skipping");
return;
}
const W4: [i32; 16] = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
let uq = |v: i32| {
if v == 0 {
0
} else if v == 1023 {
0xFFFF
} else {
((v << 16) + 0x8000) >> 10
}
};
let mut state = 0x6bc6_d155_a7c4_2026u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..60_000u32 {
let (e0, e1): ([i32; 3], [i32; 3]) = match case {
0 => ([0; 3], [0; 3]),
1 => ([1023; 3], [1023; 3]),
2 => ([0; 3], [1023; 3]),
3 => ([1023; 3], [0; 3]),
_ => {
let r = next();
let s = next();
(
[
(r & 0x3ff) as i32,
((r >> 10) & 0x3ff) as i32,
((r >> 20) & 0x3ff) as i32,
],
[
(s & 0x3ff) as i32,
((s >> 10) & 0x3ff) as i32,
((s >> 20) & 0x3ff) as i32,
],
)
}
};
let a = [uq(e0[0]), uq(e0[1]), uq(e0[2])];
let c = [uq(e1[0]), uq(e1[1]), uq(e1[2])];
let base = [a[0] * 64 + 32, a[1] * 64 + 32, a[2] * 64 + 32];
let delta = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let mut w = [0i32; 16];
for wp in w.iter_mut() {
*wp = W4[(next() & 0xf) as usize];
}
let mut fast = [0u16; 48];
assert!(bc6h_interp_avx2(&base, &delta, &w, &mut fast));
let mut slow = [0u16; 48];
for (p, &wp) in w.iter().enumerate() {
for ch in 0..3 {
let v = (base[ch] + wp * delta[ch]) >> 6;
slow[ch * 16 + p] = ((v * 31) >> 6) as u16;
}
}
assert_eq!(fast, slow, "case {case}: e0={e0:?} e1={e1:?} w={w:?}");
}
}
#[test]
fn write2_matches_scalar_over_the_full_domain() {
const WEIGHTS: [i16; 6] = [0, 9, 21, 43, 60, 64];
for &e0 in &[0u32, 1, 63, 127, 128, 254, 255] {
for &e1 in &[0u32, 1, 63, 127, 128, 254, 255] {
let base = [e0 as i32 * 64 + 32; 4];
let delta = [e1 as i32 - e0 as i32; 4];
let (bp, dp) = (pack4(base), pack4(delta));
for &w0 in &WEIGHTS {
for &w1 in &WEIGHTS {
let mut got = [0u8; 8];
write2(bp, dp, bp, dp, w0, w1, &mut got);
for (k, w) in [w0, w1].into_iter().enumerate() {
let want =
((base[0] + w as i32 * delta[0]) >> 6) as u8;
for c in 0..4 {
assert_eq!(
got[k * 4 + c],
want,
"e0={e0} e1={e1} w={w} channel {c}"
);
}
}
}
}
}
}
}
#[test]
fn opaque_alpha_is_constant_across_weights() {
let base = pack3_opaque_base([100 * 64 + 32, 0 + 32, 255 * 64 + 32]);
let delta = pack3_opaque_delta([50, -50, 0]);
for w in 0..=64i16 {
let mut got = [0u8; 8];
write2(base, delta, base, delta, w, w, &mut got);
assert_eq!(got[3], 255, "alpha moved at w={w}");
assert_eq!(got[7], 255, "alpha moved at w={w}");
}
}
#[test]
fn write2_split_honours_the_alpha_lane() {
let base = pack4([10 * 64 + 32, 20 * 64 + 32, 30 * 64 + 32, 40 * 64 + 32]);
let delta = pack4([100, 100, 100, 100]);
for alpha_lane in 0..4usize {
let mut got = [0u8; 8];
write2_split(base, delta, base, delta, (0, 0), (64, 64), alpha_lane, &mut got);
let starts = [10, 20, 30, 40];
for lane in 0..4usize {
let want = if lane == alpha_lane {
((starts[lane] * 64 + 32 + 64 * 100) >> 6) as u8
} else {
starts[lane] as u8
};
assert_eq!(got[lane], want, "alpha_lane {alpha_lane}, lane {lane}");
}
}
}
#[test]
fn bc1_blocks_ssse3_matches_scalar() {
if !has_pshufb() {
return;
}
let mut state = 0x1357_9bdf_2468_ace0u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
const BX: usize = 2;
const BY: usize = 2;
for case in 0..20_000u32 {
let mut data = [0u8; BX * BY * 8];
match case {
0 => {}
1 => data.iter_mut().for_each(|x| *x = 0xff),
2 => {
for b in 0..BX * BY {
data[b * 8..b * 8 + 4].copy_from_slice(&[0x34, 0x12, 0x34, 0x12]);
}
}
_ => {
for b in 0..BX * BY {
data[b * 8..b * 8 + 8].copy_from_slice(&next().to_le_bytes());
}
}
}
let out_w = BX * 4;
let mut got = vec![0u8; out_w * BY * 4 * 4];
unsafe { bc1_blocks_ssse3(&data, BX, BX, BY, &mut got, out_w) };
let mut want = vec![0u8; out_w * BY * 4 * 4];
let pitch = out_w * 4;
for by in 0..BY {
for bx in 0..BX {
let bi = (by * BX + bx) * 8;
let o = (by * 4 * out_w + bx * 4) * 4;
super::super::bcn::bc1_color_block_for_test(
&data[bi..bi + 8],
&mut want[o..],
pitch,
false,
);
}
}
assert_eq!(got, want, "case {case}");
}
}
#[test]
fn bc23_blocks_ssse3_match_scalar() {
if !has_pshufb() {
return;
}
let mut state = 0x0bad_c0de_1234_5678u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
const BX: usize = 2;
const BY: usize = 2;
const N: usize = BX * BY * 16;
for case in 0..20_000u32 {
let mut data = [0u8; N];
match case {
0 => {}
1 => data.iter_mut().for_each(|x| *x = 0xff),
2 => {
for b in 0..BX * BY {
data[b * 16] = 3;
data[b * 16 + 1] = 200;
}
}
3 => {
for b in 0..BX * BY {
data[b * 16 + 8..b * 16 + 12]
.copy_from_slice(&[0x34, 0x12, 0x34, 0x12]);
}
}
_ => {
for c in data.chunks_exact_mut(8) {
c.copy_from_slice(&next().to_le_bytes());
}
}
}
let out_w = BX * 4;
let pitch = out_w * 4;
let len = out_w * BY * 4 * 4;
for which in 0..2 {
let mut got = vec![0u8; len];
unsafe {
if which == 0 {
bc2_blocks_ssse3(&data, BX, BX, BY, &mut got, out_w)
} else {
bc3_blocks_ssse3(&data, BX, BX, BY, &mut got, out_w)
}
}
let mut want = vec![0u8; len];
for by in 0..BY {
for bx in 0..BX {
let bi = (by * BX + bx) * 16;
let o = (by * 4 * out_w + bx * 4) * 4;
if which == 0 {
super::super::bcn::bc2_block_rgba_for_test(
&data[bi..bi + 16],
&mut want[o..],
pitch,
);
} else {
super::super::bcn::bc3_block_rgba_for_test(
&data[bi..bi + 16],
&mut want[o..],
pitch,
);
}
}
}
assert_eq!(got, want, "case {case}, {}", if which == 0 { "bc2" } else { "bc3" });
}
}
}
#[test]
fn bc45_blocks_ssse3_match_scalar() {
if !has_ssse3() {
return;
}
let mut state = 0x45_45_c0ffee_1234u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
const BX: usize = 2;
const BY: usize = 2;
for case in 0..20_000u32 {
for (bb, is_bc5) in [(8usize, false), (16usize, true)] {
let mut data = vec![0u8; BX * BY * bb];
match case {
0 => {}
1 => data.iter_mut().for_each(|x| *x = 0xff),
2 => {
for b in 0..BX * BY {
data[b * bb] = 3;
data[b * bb + 1] = 200;
if is_bc5 {
data[b * bb + 8] = 5;
data[b * bb + 9] = 180;
}
}
}
_ => {
for c in data.chunks_exact_mut(8) {
c.copy_from_slice(&next().to_le_bytes());
}
}
}
for is_signed in [false, true] {
let out_w = BX * 4;
let pitch = out_w * 4;
let len = out_w * BY * 4 * 4;
let mut got = vec![0u8; len];
unsafe {
if is_bc5 {
bc5_blocks_ssse3(&data, BX, BX, BY, &mut got, out_w, is_signed)
} else {
bc4_blocks_ssse3(&data, BX, BX, BY, &mut got, out_w, is_signed)
}
}
let mut want = vec![0u8; len];
for by in 0..BY {
for bx in 0..BX {
let bi = (by * BX + bx) * bb;
let o = (by * 4 * out_w + bx * 4) * 4;
if is_bc5 {
super::super::bcn::bc5_block_rgba_for_test(
&data[bi..bi + bb],
&mut want[o..],
pitch,
is_signed,
);
} else {
super::super::bcn::bc4_block_rgba_for_test(
&data[bi..bi + bb],
&mut want[o..],
pitch,
is_signed,
);
}
}
}
assert_eq!(
got, want,
"case {case}, {} signed={is_signed}",
if is_bc5 { "bc5" } else { "bc4" }
);
}
}
}
}
#[test]
fn bc6h_planar_to_rgba_matches_two_pass() {
if !has_f16c() {
return;
}
const PITCH: usize = 40;
const MAX: u32 = 0x7BFF;
let check = |src: &[u16; 48], label: &str| {
let mut got = vec![0f32; 3 * PITCH + 16];
unsafe { assert!(bc6h_planar_to_rgba(src, got.as_mut_ptr(), PITCH)) };
let mut want = vec![0f32; 3 * PITCH + 16];
for p in 0..16usize {
let o = (p / 4) * PITCH + (p % 4) * 4;
want[o] = super::super::bc6h::half_to_f32(src[p]);
want[o + 1] = super::super::bc6h::half_to_f32(src[16 + p]);
want[o + 2] = super::super::bc6h::half_to_f32(src[32 + p]);
want[o + 3] = 1.0;
}
for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
assert_eq!(a.to_bits(), b.to_bits(), "{label}, element {i}");
}
};
for v in 0..=MAX {
check(&[v as u16; 48], &format!("uniform {v:#06x}"));
}
let mut state = 0x6b6b_f00d_1234_5678u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..20_000u32 {
let mut src = [0u16; 48];
match case {
0 => {
for (i, v) in src.iter_mut().enumerate() {
*v = (i as u16 + 1) * 97;
}
}
1 => src = [MAX as u16; 48],
_ => {
for v in src.iter_mut() {
*v = (next() % (MAX as u64 + 1)) as u16;
}
}
}
check(&src, &format!("case {case}"));
}
}
}