use core::arch::x86_64::{
__m128i, _mm_add_epi16, _mm_and_si128, _mm_cvtsi64_si128, _mm_loadl_epi64,
_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_set_epi64x as _set64, _mm_storel_epi64, _mm_storeu_si128, _mm_unpackhi_epi16, _mm_unpackhi_epi8,
_mm_unpacklo_epi16, _mm_unpacklo_epi8,
};
#[inline(always)]
pub(super) fn pack4(v: [i32; 4]) -> i64 {
((v[0] as u16 as u64)
| ((v[1] as u16 as u64) << 16)
| ((v[2] as u16 as u64) << 32)
| ((v[3] as u16 as u64) << 48)) as i64
}
#[inline(always)]
pub(super) fn pack3_opaque_base(v: [i32; 3]) -> i64 {
pack4([v[0], v[1], v[2], 255 << 6])
}
#[inline(always)]
pub(super) fn pack3_opaque_delta(v: [i32; 3]) -> i64 {
pack4([v[0], v[1], v[2], 0])
}
#[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)]
pub(super) fn pack_bd3(bd: &[([i32; 3], [i32; 3])], pairs: usize) -> [(i64, i64); 4] {
let mut out = [(0i64, 0i64); 4];
for (k, slot) in out.iter_mut().enumerate().take(pairs) {
slot.0 = pack3_opaque_base(bd[k].0);
slot.1 = pack3_opaque_delta(bd[k].1);
}
out
}
#[inline(always)]
pub(super) fn pack_bd4(bd: &[([i32; 4], [i32; 4])], pairs: usize) -> [(i64, i64); 2] {
let mut out = [(0i64, 0i64); 2];
for (k, slot) in out.iter_mut().enumerate().take(pairs) {
slot.0 = pack4(bd[k].0);
slot.1 = pack4(bd[k].1);
}
out
}
#[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!("bmi2")
&& has_fast_pdep()
})
}
fn has_fast_pdep() -> bool {
let (vendor, family) = {
let v = core::arch::x86_64::__cpuid(0);
let f = core::arch::x86_64::__cpuid(1);
((v.ebx, v.edx, v.ecx), f.eax)
};
let is_amd = vendor == (0x6874_7541, 0x6974_6e65, 0x444d_4163);
if !is_amd {
return true;
}
let base = (family >> 8) & 0xf;
let display = if base == 0xf {
base + ((family >> 20) & 0xff)
} else {
base
};
display >= 0x19
}
pub(super) fn bc5_gather(
pr: u64,
pg: u64,
ir: u64,
ig: u64,
out: &mut [u8],
pitch: usize,
) -> bool {
if !has_ssse3() {
return false;
}
debug_assert!(out.len() >= 3 * pitch + 16);
unsafe { bc5_gather_ssse3(pr, pg, ir, ig, out.as_mut_ptr(), pitch) }
true
}
#[target_feature(enable = "ssse3,bmi2")]
unsafe fn bc5_gather_ssse3(
pr: u64,
pg: u64,
ir: u64,
ig: u64,
dst: *mut u8,
pitch: usize,
) {
const SPREAD: u64 = 0x0707_0707_0707_0707;
let idx_vec = |w: u64| {
_set64(
core::arch::x86_64::_pdep_u64(w >> 24, SPREAD) as i64,
core::arch::x86_64::_pdep_u64(w, SPREAD) as i64,
)
};
let pal_r = core::arch::x86_64::_mm_cvtsi64_si128(pr as i64);
let pal_g = core::arch::x86_64::_mm_cvtsi64_si128(pg as i64);
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"))
}
const fn build_bc1_sel() -> [[u8; 16]; 256] {
let mut t = [[0u8; 16]; 256];
let mut b = 0usize;
while b < 256 {
let mut k = 0usize;
while k < 4 {
let e = ((b >> (2 * k)) & 3) as u8;
let mut c = 0usize;
while c < 4 {
t[b][k * 4 + c] = e * 4 + c as u8;
c += 1;
}
k += 1;
}
b += 1;
}
t
}
pub(crate) static BC1_SEL: [[u8; 16]; 256] = build_bc1_sel();
#[target_feature(enable = "ssse3")]
pub(super) unsafe fn bc1_blocks_ssse3(
data: &[u8],
blocks_x: usize,
blocks_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..blocks_y {
for bx in 0..blocks_x {
let bi = (by * blocks_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],
blocks_x: usize,
blocks_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..blocks_y {
for bx in 0..blocks_x {
let bi = (by * blocks_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")]
pub(super) unsafe fn bc3_blocks_ssse3(
data: &[u8],
blocks_x: usize,
blocks_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..blocks_y {
for bx in 0..blocks_x {
let bi = (by * blocks_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 = _mm_cvtsi64_si128(super::bcn::bc3_alpha_palette_packed(blk[0], blk[1]) as i64);
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,bmi2")]
pub(super) unsafe fn bc5_blocks_ssse3(
data: &[u8],
blocks_x: usize,
blocks_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..blocks_y {
for bx in 0..blocks_x {
let bi = (by * blocks_x + bx) * 16;
let blk = core::slice::from_raw_parts(src.add(bi), 16);
let pr = super::bcn::bc4_palette_packed(blk[0], blk[1], is_signed);
let pg = super::bcn::bc4_palette_packed(blk[8], blk[9], is_signed);
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(pr, pg, ir, ig, dst.add(o), pitch);
}
}
}
#[target_feature(enable = "ssse3,bmi2")]
pub(super) unsafe fn bc4_blocks_ssse3(
data: &[u8],
blocks_x: usize,
blocks_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..blocks_y {
for bx in 0..blocks_x {
let bi = (by * blocks_x + bx) * 8;
let blk = core::slice::from_raw_parts(src.add(bi), 8);
let pr = super::bcn::bc4_palette_packed(blk[0], blk[1], is_signed);
let ir = super::bcn::bc4_indices(blk);
let o = (by * 4 * out_w + bx * 4) * 4;
bc5_gather_ssse3(pr, 0, ir, 0, 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 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, 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, BY, &mut got, out_w)
} else {
bc3_blocks_ssse3(&data, 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, BY, &mut got, out_w, is_signed)
} else {
bc4_blocks_ssse3(&data, 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}"));
}
}
}