use core::sync::atomic::{AtomicU8, Ordering};
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum Path {
Unknown = 0,
Scalar = 1,
Avx2 = 2,
}
static DISPATCH: AtomicU8 = AtomicU8::new(Path::Unknown as u8);
fn select_path() -> Path {
if std::env::var_os("OXIDEAV_PIXFMT_FORCE_SCALAR").is_some() {
return Path::Scalar;
}
#[cfg(all(target_arch = "x86_64", not(miri)))]
{
if std::is_x86_feature_detected!("avx2") {
return Path::Avx2;
}
}
Path::Scalar
}
fn path() -> Path {
let v = DISPATCH.load(Ordering::Relaxed);
if v != Path::Unknown as u8 {
return if v == Path::Avx2 as u8 {
Path::Avx2
} else {
Path::Scalar
};
}
let p = select_path();
DISPATCH.store(p as u8, Ordering::Relaxed);
p
}
pub(crate) fn swizzle4_perm(src: &[u8], dst: &mut [u8], pixels: usize, perm: [u8; 4]) {
match path() {
#[cfg(target_arch = "x86_64")]
Path::Avx2 => unsafe { avx2_swizzle4(src, dst, pixels, perm) },
_ => scalar_swizzle4(src, dst, pixels, perm),
}
}
#[inline(always)]
fn scalar_swizzle4(src: &[u8], dst: &mut [u8], pixels: usize, perm: [u8; 4]) {
let p0 = (perm[0] & 3) as usize;
let p1 = (perm[1] & 3) as usize;
let p2 = (perm[2] & 3) as usize;
let p3 = (perm[3] & 3) as usize;
for i in 0..pixels {
let s = i * 4;
let d = i * 4;
dst[d] = src[s + p0];
dst[d + 1] = src[s + p1];
dst[d + 2] = src[s + p2];
dst[d + 3] = src[s + p3];
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn avx2_swizzle4(src: &[u8], dst: &mut [u8], pixels: usize, perm: [u8; 4]) {
use core::arch::x86_64::*;
let mut mask_bytes = [0u8; 32];
for lane in 0..2 {
for pix in 0..4 {
let base = pix * 4;
for j in 0..4 {
mask_bytes[lane * 16 + base + j] = (base as u8) + perm[j];
}
}
}
let mask = _mm256_loadu_si256(mask_bytes.as_ptr() as *const __m256i);
let chunks = pixels / 8;
let tail_start = chunks * 8;
for c in 0..chunks {
let off = c * 32;
let v = _mm256_loadu_si256(src.as_ptr().add(off) as *const __m256i);
let out = _mm256_shuffle_epi8(v, mask);
_mm256_storeu_si256(dst.as_mut_ptr().add(off) as *mut __m256i, out);
}
if tail_start < pixels {
scalar_swizzle4(
&src[tail_start * 4..],
&mut dst[tail_start * 4..],
pixels - tail_start,
perm,
);
}
}
pub(crate) fn swizzle3_perm(src: &[u8], dst: &mut [u8], pixels: usize, perm: [u8; 3]) {
match path() {
#[cfg(target_arch = "x86_64")]
Path::Avx2 => unsafe { avx2_swizzle3(src, dst, pixels, perm) },
_ => scalar_swizzle3(src, dst, pixels, perm),
}
}
#[inline(always)]
fn scalar_swizzle3(src: &[u8], dst: &mut [u8], pixels: usize, perm: [u8; 3]) {
let p0 = (perm[0] % 3) as usize;
let p1 = (perm[1] % 3) as usize;
let p2 = (perm[2] % 3) as usize;
for i in 0..pixels {
let s = i * 3;
let d = i * 3;
dst[d] = src[s + p0];
dst[d + 1] = src[s + p1];
dst[d + 2] = src[s + p2];
}
}
pub(crate) fn rgb3_to_rgba4_perm(src: &[u8], dst: &mut [u8], pixels: usize, perm3: [u8; 4]) {
match path() {
#[cfg(target_arch = "x86_64")]
Path::Avx2 => unsafe { avx2_rgb3_to_rgba4(src, dst, pixels, perm3) },
_ => scalar_rgb3_to_rgba4(src, dst, pixels, perm3),
}
}
#[inline(always)]
fn scalar_rgb3_to_rgba4(src: &[u8], dst: &mut [u8], pixels: usize, perm3: [u8; 4]) {
for i in 0..pixels {
let s = i * 3;
let d = i * 4;
for j in 0..4 {
let p = perm3[j];
dst[d + j] = if p == 0xFF {
255
} else {
src[s + (p & 3) as usize]
};
}
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn avx2_rgb3_to_rgba4(src: &[u8], dst: &mut [u8], pixels: usize, perm3: [u8; 4]) {
use core::arch::x86_64::*;
let mut mask_bytes = [0u8; 16];
let mut alpha_or = [0u8; 16];
for pix in 0..4 {
let base = (pix * 3) as u8;
for (slot, &p) in perm3.iter().enumerate() {
let idx = pix * 4 + slot;
if p == 0xFF {
mask_bytes[idx] = 0x80;
alpha_or[idx] = 0xFF;
} else {
mask_bytes[idx] = base + p;
}
}
}
let mask = _mm_loadu_si128(mask_bytes.as_ptr() as *const __m128i);
let alpha = _mm_loadu_si128(alpha_or.as_ptr() as *const __m128i);
let simd_blocks = if pixels >= 4 { (pixels - 4) / 4 } else { 0 };
for c in 0..simd_blocks {
let soff = c * 12;
let doff = c * 16;
let v = _mm_loadu_si128(src.as_ptr().add(soff) as *const __m128i);
let shuffled = _mm_shuffle_epi8(v, mask);
let out = _mm_or_si128(shuffled, alpha);
_mm_storeu_si128(dst.as_mut_ptr().add(doff) as *mut __m128i, out);
}
let tail_start = simd_blocks * 4;
scalar_rgb3_to_rgba4(
&src[tail_start * 3..],
&mut dst[tail_start * 4..],
pixels - tail_start,
perm3,
);
}
pub(crate) fn rgba4_to_rgb3_perm(src: &[u8], dst: &mut [u8], pixels: usize, perm4: [u8; 3]) {
match path() {
#[cfg(target_arch = "x86_64")]
Path::Avx2 => unsafe { avx2_rgba4_to_rgb3(src, dst, pixels, perm4) },
_ => scalar_rgba4_to_rgb3(src, dst, pixels, perm4),
}
}
#[inline(always)]
fn scalar_rgba4_to_rgb3(src: &[u8], dst: &mut [u8], pixels: usize, perm4: [u8; 3]) {
let p0 = (perm4[0] & 3) as usize;
let p1 = (perm4[1] & 3) as usize;
let p2 = (perm4[2] & 3) as usize;
for i in 0..pixels {
let s = i * 4;
let d = i * 3;
dst[d] = src[s + p0];
dst[d + 1] = src[s + p1];
dst[d + 2] = src[s + p2];
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn avx2_rgba4_to_rgb3(src: &[u8], dst: &mut [u8], pixels: usize, perm4: [u8; 3]) {
use core::arch::x86_64::*;
let mut mask_bytes = [0x80u8; 16];
for pix in 0..4 {
let sbase = (pix * 4) as u8;
let dbase = pix * 3;
for j in 0..3 {
mask_bytes[dbase + j] = sbase + perm4[j];
}
}
let mask = _mm_loadu_si128(mask_bytes.as_ptr() as *const __m128i);
let simd_blocks = if pixels >= 4 { (pixels - 4) / 4 } else { 0 };
for c in 0..simd_blocks {
let soff = c * 16;
let doff = c * 12;
let v = _mm_loadu_si128(src.as_ptr().add(soff) as *const __m128i);
let out = _mm_shuffle_epi8(v, mask);
_mm_storeu_si128(dst.as_mut_ptr().add(doff) as *mut __m128i, out);
}
let tail_start = simd_blocks * 4;
scalar_rgba4_to_rgb3(
&src[tail_start * 4..],
&mut dst[tail_start * 3..],
pixels - tail_start,
perm4,
);
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn avx2_swizzle3(src: &[u8], dst: &mut [u8], pixels: usize, perm: [u8; 3]) {
use core::arch::x86_64::*;
let mut mask_bytes = [0x80u8; 16];
for pix in 0..5 {
let base = pix * 3;
for j in 0..3 {
mask_bytes[base + j] = (base as u8) + perm[j];
}
}
let mask = _mm_loadu_si128(mask_bytes.as_ptr() as *const __m128i);
let avail = pixels;
let simd_blocks = if avail >= 5 { (avail - 5) / 5 } else { 0 };
for c in 0..simd_blocks {
let soff = c * 15;
let doff = c * 15;
let v = _mm_loadu_si128(src.as_ptr().add(soff) as *const __m128i);
let out = _mm_shuffle_epi8(v, mask);
_mm_storeu_si128(dst.as_mut_ptr().add(doff) as *mut __m128i, out);
}
let tail_start = simd_blocks * 5;
scalar_swizzle3(
&src[tail_start * 3..],
&mut dst[tail_start * 3..],
pixels - tail_start,
perm,
);
}
#[cfg(test)]
mod tests {
use super::*;
fn ramp(n: usize) -> Vec<u8> {
(0..n).map(|i| (i & 0xff) as u8).collect()
}
#[test]
fn swizzle3_does_not_write_past_dst() {
let perm = [0u8, 1, 2];
for pixels in 1..=80 {
let src = ramp(pixels * 3);
let mut dst = vec![0u8; pixels * 3];
swizzle3_perm(&src, &mut dst, pixels, perm);
assert_eq!(dst, src[..pixels * 3], "swizzle3 pixels={pixels}");
}
}
#[test]
fn rgba4_to_rgb3_does_not_write_past_dst() {
let perm4 = [0u8, 1, 2];
for pixels in 1..=80 {
let src = ramp(pixels * 4);
let mut dst = vec![0u8; pixels * 3];
rgba4_to_rgb3_perm(&src, &mut dst, pixels, perm4);
for i in 0..pixels {
assert_eq!(dst[i * 3], src[i * 4], "rgba4→rgb3 px {i}/{pixels}");
assert_eq!(dst[i * 3 + 1], src[i * 4 + 1], "rgba4→rgb3 px {i}/{pixels}");
assert_eq!(dst[i * 3 + 2], src[i * 4 + 2], "rgba4→rgb3 px {i}/{pixels}");
}
}
}
#[test]
fn rgb3_to_rgba4_does_not_read_or_write_past_bounds() {
let perm3 = [0u8, 1, 2, 0xFF];
for pixels in 1..=80 {
let src = ramp(pixels * 3);
let mut dst = vec![0u8; pixels * 4];
rgb3_to_rgba4_perm(&src, &mut dst, pixels, perm3);
for i in 0..pixels {
assert_eq!(dst[i * 4], src[i * 3], "rgb3→rgba4 px {i}/{pixels}");
assert_eq!(dst[i * 4 + 1], src[i * 3 + 1], "rgb3→rgba4 px {i}/{pixels}");
assert_eq!(dst[i * 4 + 2], src[i * 3 + 2], "rgb3→rgba4 px {i}/{pixels}");
assert_eq!(dst[i * 4 + 3], 255, "rgb3→rgba4 alpha px {i}/{pixels}");
}
}
}
#[test]
fn swizzle4_does_not_write_past_dst() {
let perm = [0u8, 1, 2, 3];
for pixels in 1..=80 {
let src = ramp(pixels * 4);
let mut dst = vec![0u8; pixels * 4];
swizzle4_perm(&src, &mut dst, pixels, perm);
assert_eq!(dst, src[..pixels * 4], "swizzle4 pixels={pixels}");
}
}
#[test]
fn swizzle_simd_alloc_churn_does_not_corrupt_heap() {
for _ in 0..2000 {
let s3 = vec![0xa5u8; 33 * 3];
let mut d3 = vec![0u8; 33 * 3];
swizzle3_perm(&s3, &mut d3, 33, [0, 1, 2]);
drop(d3);
let s4 = vec![0x5au8; 33 * 4];
let mut d4 = vec![0u8; 33 * 3];
rgba4_to_rgb3_perm(&s4, &mut d4, 33, [0, 1, 2]);
drop(d4);
}
}
}