#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[cfg(target_arch = "aarch64")]
use std::arch::aarch64::*;
pub struct SimdConverter {
has_avx2: bool,
has_sse41: bool,
has_ssse3: bool,
has_neon: bool,
}
impl SimdConverter {
pub fn new() -> Self {
Self {
#[cfg(target_arch = "x86_64")]
has_avx2: is_x86_feature_detected!("avx2"),
#[cfg(not(target_arch = "x86_64"))]
has_avx2: false,
#[cfg(target_arch = "x86_64")]
has_sse41: is_x86_feature_detected!("sse4.1"),
#[cfg(not(target_arch = "x86_64"))]
has_sse41: false,
#[cfg(target_arch = "x86_64")]
has_ssse3: is_x86_feature_detected!("ssse3"),
#[cfg(not(target_arch = "x86_64"))]
has_ssse3: false,
#[cfg(target_arch = "aarch64")]
has_neon: true, #[cfg(not(target_arch = "aarch64"))]
has_neon: false,
}
}
pub fn convert_bgra_to_rgba(&self, data: &mut [u8]) {
#[cfg(target_arch = "x86_64")]
{
if self.has_avx2 {
unsafe { self.convert_bgra_to_rgba_avx2(data) }
} else if self.has_ssse3 {
unsafe { self.convert_bgra_to_rgba_ssse3(data) }
} else {
self.convert_bgra_to_rgba_scalar(data)
}
}
#[cfg(target_arch = "aarch64")]
{
if self.has_neon {
unsafe { self.convert_bgra_to_rgba_neon(data) }
} else {
self.convert_bgra_to_rgba_scalar(data)
}
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
self.convert_bgra_to_rgba_scalar(data)
}
}
fn convert_bgra_to_rgba_scalar(&self, data: &mut [u8]) {
for chunk in data.chunks_exact_mut(4) {
chunk.swap(0, 2); }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn convert_bgra_to_rgba_avx2(&self, data: &mut [u8]) {
let shuffle_mask = _mm256_setr_epi8(
2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15,
2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15,
);
let len = data.len();
let simd_len = len & !31;
for i in (0..simd_len).step_by(32) {
let ptr = data.as_mut_ptr().add(i);
let pixels = _mm256_loadu_si256(ptr as *const __m256i);
let shuffled = _mm256_shuffle_epi8(pixels, shuffle_mask);
_mm256_storeu_si256(ptr as *mut __m256i, shuffled);
}
for chunk in data[simd_len..].chunks_exact_mut(4) {
chunk.swap(0, 2);
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
unsafe fn convert_bgra_to_rgba_ssse3(&self, data: &mut [u8]) {
let shuffle_mask = _mm_setr_epi8(
2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15,
);
let len = data.len();
let simd_len = len & !15;
for i in (0..simd_len).step_by(16) {
let ptr = data.as_mut_ptr().add(i);
let pixels = _mm_loadu_si128(ptr as *const __m128i);
let shuffled = _mm_shuffle_epi8(pixels, shuffle_mask);
_mm_storeu_si128(ptr as *mut __m128i, shuffled);
}
for chunk in data[simd_len..].chunks_exact_mut(4) {
chunk.swap(0, 2);
}
}
#[cfg(target_arch = "aarch64")]
unsafe fn convert_bgra_to_rgba_neon(&self, data: &mut [u8]) {
use std::arch::aarch64::*;
let len = data.len();
let simd_len = len & !15;
for i in (0..simd_len).step_by(16) {
let ptr = data.as_mut_ptr().add(i);
let bgra = vld4q_u8(ptr);
let rgba = uint8x16x4_t(bgra.2, bgra.1, bgra.0, bgra.3);
vst4q_u8(ptr, rgba);
}
for chunk in data[simd_len..].chunks_exact_mut(4) {
chunk.swap(0, 2);
}
}
pub fn convert_bgr_to_rgb(&self, data: &mut [u8]) {
#[cfg(target_arch = "x86_64")]
{
if self.has_avx2 {
unsafe { self.convert_bgr_to_rgb_avx2(data) }
} else if self.has_ssse3 {
unsafe { self.convert_bgr_to_rgb_ssse3(data) }
} else {
self.convert_bgr_to_rgb_scalar(data)
}
}
#[cfg(not(target_arch = "x86_64"))]
{
self.convert_bgr_to_rgb_scalar(data)
}
}
fn convert_bgr_to_rgb_scalar(&self, data: &mut [u8]) {
for chunk in data.chunks_exact_mut(3) {
chunk.swap(0, 2); }
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn convert_bgr_to_rgb_avx2(&self, data: &mut [u8]) {
let len = data.len();
let pixels = len / 3;
let simd_pixels = pixels & !15;
for i in (0..simd_pixels).step_by(16) {
let offset = i * 3;
let ptr = data.as_mut_ptr().add(offset);
let _chunk1 = _mm256_loadu_si256(ptr as *const __m256i);
let _chunk2 = _mm_loadu_si128(ptr.add(32) as *const __m128i);
for j in 0..16 {
let pixel_offset = offset + j * 3;
data.swap(pixel_offset, pixel_offset + 2);
}
}
for i in (simd_pixels * 3..len).step_by(3) {
if i + 2 < len {
data.swap(i, i + 2);
}
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
unsafe fn convert_bgr_to_rgb_ssse3(&self, data: &mut [u8]) {
self.convert_bgr_to_rgb_scalar(data);
}
pub fn convert_rgba_to_rgb(&self, src: &[u8], dst: &mut [u8]) {
#[cfg(target_arch = "x86_64")]
{
if self.has_avx2 {
unsafe { self.convert_rgba_to_rgb_avx2(src, dst) }
} else if self.has_sse41 {
unsafe { self.convert_rgba_to_rgb_sse41(src, dst) }
} else {
self.convert_rgba_to_rgb_scalar(src, dst)
}
}
#[cfg(not(target_arch = "x86_64"))]
{
self.convert_rgba_to_rgb_scalar(src, dst)
}
}
fn convert_rgba_to_rgb_scalar(&self, src: &[u8], dst: &mut [u8]) {
let mut dst_idx = 0;
for chunk in src.chunks_exact(4) {
dst[dst_idx] = chunk[0]; dst[dst_idx + 1] = chunk[1]; dst[dst_idx + 2] = chunk[2]; dst_idx += 3;
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn convert_rgba_to_rgb_avx2(&self, src: &[u8], dst: &mut [u8]) {
let src_len = src.len();
let pixels = src_len / 4;
let simd_pixels = pixels & !7;
let mut src_idx = 0;
let mut dst_idx = 0;
for _ in 0..simd_pixels / 8 {
let _rgba = _mm256_loadu_si256(src.as_ptr().add(src_idx) as *const __m256i);
for _ in 0..8 {
dst[dst_idx] = src[src_idx];
dst[dst_idx + 1] = src[src_idx + 1];
dst[dst_idx + 2] = src[src_idx + 2];
src_idx += 4;
dst_idx += 3;
}
}
while src_idx + 3 < src_len {
dst[dst_idx] = src[src_idx];
dst[dst_idx + 1] = src[src_idx + 1];
dst[dst_idx + 2] = src[src_idx + 2];
src_idx += 4;
dst_idx += 3;
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse4.1")]
unsafe fn convert_rgba_to_rgb_sse41(&self, src: &[u8], dst: &mut [u8]) {
self.convert_rgba_to_rgb_scalar(src, dst);
}
pub fn capabilities(&self) -> String {
let mut caps = Vec::new();
if self.has_avx2 {
caps.push("AVX2");
}
if self.has_sse41 {
caps.push("SSE4.1");
}
if self.has_ssse3 {
caps.push("SSSE3");
}
if self.has_neon {
caps.push("NEON");
}
if caps.is_empty() {
"None (scalar)".to_string()
} else {
caps.join(", ")
}
}
pub fn benchmark_conversion(&self, size: usize) -> std::time::Duration {
let mut data = vec![0u8; size];
for (i, byte) in data.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
let start = std::time::Instant::now();
for _ in 0..100 {
self.convert_bgra_to_rgba(&mut data);
}
start.elapsed() / 100
}
}
impl Default for SimdConverter {
fn default() -> Self {
Self::new()
}
}
pub fn global_simd_converter() -> &'static SimdConverter {
static CONVERTER: once_cell::sync::Lazy<SimdConverter> =
once_cell::sync::Lazy::new(SimdConverter::new);
&CONVERTER
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simd_detection() {
let converter = SimdConverter::new();
println!("SIMD capabilities: {}", converter.capabilities());
}
#[test]
fn test_bgra_to_rgba_conversion() {
let converter = SimdConverter::new();
let mut data = vec![0, 1, 2, 3, 4, 5, 6, 7];
converter.convert_bgra_to_rgba(&mut data);
assert_eq!(data, vec![2, 1, 0, 3, 6, 5, 4, 7]); }
#[test]
fn test_rgba_to_rgb_conversion() {
let converter = SimdConverter::new();
let src = vec![255, 128, 64, 255, 128, 64, 32, 255]; let mut dst = vec![0u8; 6];
converter.convert_rgba_to_rgb(&src, &mut dst);
assert_eq!(dst, vec![255, 128, 64, 128, 64, 32]); }
}