webp_screenshot_rust/encoder/
simd.rs1#[cfg(target_arch = "x86_64")]
5use std::arch::x86_64::*;
6
7#[cfg(target_arch = "aarch64")]
8use std::arch::aarch64::*;
9
10pub struct SimdConverter {
12 has_avx2: bool,
13 has_sse41: bool,
14 has_ssse3: bool,
15 has_neon: bool,
16}
17
18impl SimdConverter {
19 pub fn new() -> Self {
21 Self {
22 #[cfg(target_arch = "x86_64")]
23 has_avx2: is_x86_feature_detected!("avx2"),
24 #[cfg(not(target_arch = "x86_64"))]
25 has_avx2: false,
26
27 #[cfg(target_arch = "x86_64")]
28 has_sse41: is_x86_feature_detected!("sse4.1"),
29 #[cfg(not(target_arch = "x86_64"))]
30 has_sse41: false,
31
32 #[cfg(target_arch = "x86_64")]
33 has_ssse3: is_x86_feature_detected!("ssse3"),
34 #[cfg(not(target_arch = "x86_64"))]
35 has_ssse3: false,
36
37 #[cfg(target_arch = "aarch64")]
38 has_neon: true, #[cfg(not(target_arch = "aarch64"))]
40 has_neon: false,
41 }
42 }
43
44 pub fn convert_bgra_to_rgba(&self, data: &mut [u8]) {
46 #[cfg(target_arch = "x86_64")]
47 {
48 if self.has_avx2 {
49 unsafe { self.convert_bgra_to_rgba_avx2(data) }
50 } else if self.has_ssse3 {
51 unsafe { self.convert_bgra_to_rgba_ssse3(data) }
52 } else {
53 self.convert_bgra_to_rgba_scalar(data)
54 }
55 }
56
57 #[cfg(target_arch = "aarch64")]
58 {
59 if self.has_neon {
60 unsafe { self.convert_bgra_to_rgba_neon(data) }
61 } else {
62 self.convert_bgra_to_rgba_scalar(data)
63 }
64 }
65
66 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
67 {
68 self.convert_bgra_to_rgba_scalar(data)
69 }
70 }
71
72 fn convert_bgra_to_rgba_scalar(&self, data: &mut [u8]) {
74 for chunk in data.chunks_exact_mut(4) {
75 chunk.swap(0, 2); }
77 }
78
79 #[cfg(target_arch = "x86_64")]
81 #[target_feature(enable = "avx2")]
82 unsafe fn convert_bgra_to_rgba_avx2(&self, data: &mut [u8]) {
83 let shuffle_mask = _mm256_setr_epi8(
85 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15,
86 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15,
87 );
88
89 let len = data.len();
90 let simd_len = len & !31; for i in (0..simd_len).step_by(32) {
93 let ptr = data.as_mut_ptr().add(i);
94 let pixels = _mm256_loadu_si256(ptr as *const __m256i);
95 let shuffled = _mm256_shuffle_epi8(pixels, shuffle_mask);
96 _mm256_storeu_si256(ptr as *mut __m256i, shuffled);
97 }
98
99 for chunk in data[simd_len..].chunks_exact_mut(4) {
101 chunk.swap(0, 2);
102 }
103 }
104
105 #[cfg(target_arch = "x86_64")]
107 #[target_feature(enable = "ssse3")]
108 unsafe fn convert_bgra_to_rgba_ssse3(&self, data: &mut [u8]) {
109 let shuffle_mask = _mm_setr_epi8(
110 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15,
111 );
112
113 let len = data.len();
114 let simd_len = len & !15; for i in (0..simd_len).step_by(16) {
117 let ptr = data.as_mut_ptr().add(i);
118 let pixels = _mm_loadu_si128(ptr as *const __m128i);
119 let shuffled = _mm_shuffle_epi8(pixels, shuffle_mask);
120 _mm_storeu_si128(ptr as *mut __m128i, shuffled);
121 }
122
123 for chunk in data[simd_len..].chunks_exact_mut(4) {
125 chunk.swap(0, 2);
126 }
127 }
128
129 #[cfg(target_arch = "aarch64")]
131 unsafe fn convert_bgra_to_rgba_neon(&self, data: &mut [u8]) {
132 use std::arch::aarch64::*;
133
134 let len = data.len();
135 let simd_len = len & !15; for i in (0..simd_len).step_by(16) {
138 let ptr = data.as_mut_ptr().add(i);
139
140 let bgra = vld4q_u8(ptr);
142
143 let rgba = uint8x16x4_t(bgra.2, bgra.1, bgra.0, bgra.3);
145
146 vst4q_u8(ptr, rgba);
148 }
149
150 for chunk in data[simd_len..].chunks_exact_mut(4) {
152 chunk.swap(0, 2);
153 }
154 }
155
156 pub fn convert_bgr_to_rgb(&self, data: &mut [u8]) {
158 #[cfg(target_arch = "x86_64")]
159 {
160 if self.has_avx2 {
161 unsafe { self.convert_bgr_to_rgb_avx2(data) }
162 } else if self.has_ssse3 {
163 unsafe { self.convert_bgr_to_rgb_ssse3(data) }
164 } else {
165 self.convert_bgr_to_rgb_scalar(data)
166 }
167 }
168
169 #[cfg(not(target_arch = "x86_64"))]
170 {
171 self.convert_bgr_to_rgb_scalar(data)
172 }
173 }
174
175 fn convert_bgr_to_rgb_scalar(&self, data: &mut [u8]) {
177 for chunk in data.chunks_exact_mut(3) {
178 chunk.swap(0, 2); }
180 }
181
182 #[cfg(target_arch = "x86_64")]
184 #[target_feature(enable = "avx2")]
185 unsafe fn convert_bgr_to_rgb_avx2(&self, data: &mut [u8]) {
186 let len = data.len();
190 let pixels = len / 3;
191 let simd_pixels = pixels & !15; for i in (0..simd_pixels).step_by(16) {
194 let offset = i * 3;
195 let ptr = data.as_mut_ptr().add(offset);
196
197 let _chunk1 = _mm256_loadu_si256(ptr as *const __m256i);
199 let _chunk2 = _mm_loadu_si128(ptr.add(32) as *const __m128i);
200
201 for j in 0..16 {
206 let pixel_offset = offset + j * 3;
207 data.swap(pixel_offset, pixel_offset + 2);
208 }
209 }
210
211 for i in (simd_pixels * 3..len).step_by(3) {
213 if i + 2 < len {
214 data.swap(i, i + 2);
215 }
216 }
217 }
218
219 #[cfg(target_arch = "x86_64")]
221 #[target_feature(enable = "ssse3")]
222 unsafe fn convert_bgr_to_rgb_ssse3(&self, data: &mut [u8]) {
223 self.convert_bgr_to_rgb_scalar(data);
226 }
227
228 pub fn convert_rgba_to_rgb(&self, src: &[u8], dst: &mut [u8]) {
230 #[cfg(target_arch = "x86_64")]
231 {
232 if self.has_avx2 {
233 unsafe { self.convert_rgba_to_rgb_avx2(src, dst) }
234 } else if self.has_sse41 {
235 unsafe { self.convert_rgba_to_rgb_sse41(src, dst) }
236 } else {
237 self.convert_rgba_to_rgb_scalar(src, dst)
238 }
239 }
240
241 #[cfg(not(target_arch = "x86_64"))]
242 {
243 self.convert_rgba_to_rgb_scalar(src, dst)
244 }
245 }
246
247 fn convert_rgba_to_rgb_scalar(&self, src: &[u8], dst: &mut [u8]) {
249 let mut dst_idx = 0;
250 for chunk in src.chunks_exact(4) {
251 dst[dst_idx] = chunk[0]; dst[dst_idx + 1] = chunk[1]; dst[dst_idx + 2] = chunk[2]; dst_idx += 3;
255 }
256 }
257
258 #[cfg(target_arch = "x86_64")]
260 #[target_feature(enable = "avx2")]
261 unsafe fn convert_rgba_to_rgb_avx2(&self, src: &[u8], dst: &mut [u8]) {
262 let src_len = src.len();
263 let pixels = src_len / 4;
264 let simd_pixels = pixels & !7; let mut src_idx = 0;
267 let mut dst_idx = 0;
268
269 for _ in 0..simd_pixels / 8 {
270 let _rgba = _mm256_loadu_si256(src.as_ptr().add(src_idx) as *const __m256i);
272
273 for _ in 0..8 {
278 dst[dst_idx] = src[src_idx];
279 dst[dst_idx + 1] = src[src_idx + 1];
280 dst[dst_idx + 2] = src[src_idx + 2];
281 src_idx += 4;
282 dst_idx += 3;
283 }
284 }
285
286 while src_idx + 3 < src_len {
288 dst[dst_idx] = src[src_idx];
289 dst[dst_idx + 1] = src[src_idx + 1];
290 dst[dst_idx + 2] = src[src_idx + 2];
291 src_idx += 4;
292 dst_idx += 3;
293 }
294 }
295
296 #[cfg(target_arch = "x86_64")]
298 #[target_feature(enable = "sse4.1")]
299 unsafe fn convert_rgba_to_rgb_sse41(&self, src: &[u8], dst: &mut [u8]) {
300 self.convert_rgba_to_rgb_scalar(src, dst);
302 }
303
304 pub fn capabilities(&self) -> String {
306 let mut caps = Vec::new();
307
308 if self.has_avx2 {
309 caps.push("AVX2");
310 }
311 if self.has_sse41 {
312 caps.push("SSE4.1");
313 }
314 if self.has_ssse3 {
315 caps.push("SSSE3");
316 }
317 if self.has_neon {
318 caps.push("NEON");
319 }
320
321 if caps.is_empty() {
322 "None (scalar)".to_string()
323 } else {
324 caps.join(", ")
325 }
326 }
327
328 pub fn benchmark_conversion(&self, size: usize) -> std::time::Duration {
330 let mut data = vec![0u8; size];
331
332 for (i, byte) in data.iter_mut().enumerate() {
334 *byte = (i % 256) as u8;
335 }
336
337 let start = std::time::Instant::now();
338
339 for _ in 0..100 {
341 self.convert_bgra_to_rgba(&mut data);
342 }
343
344 start.elapsed() / 100
345 }
346}
347
348impl Default for SimdConverter {
349 fn default() -> Self {
350 Self::new()
351 }
352}
353
354pub fn global_simd_converter() -> &'static SimdConverter {
356 static CONVERTER: once_cell::sync::Lazy<SimdConverter> =
357 once_cell::sync::Lazy::new(SimdConverter::new);
358 &CONVERTER
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_simd_detection() {
367 let converter = SimdConverter::new();
368 println!("SIMD capabilities: {}", converter.capabilities());
369 }
370
371 #[test]
372 fn test_bgra_to_rgba_conversion() {
373 let converter = SimdConverter::new();
374 let mut data = vec![0, 1, 2, 3, 4, 5, 6, 7]; converter.convert_bgra_to_rgba(&mut data);
377
378 assert_eq!(data, vec![2, 1, 0, 3, 6, 5, 4, 7]); }
380
381 #[test]
382 fn test_rgba_to_rgb_conversion() {
383 let converter = SimdConverter::new();
384 let src = vec![255, 128, 64, 255, 128, 64, 32, 255]; let mut dst = vec![0u8; 6];
386
387 converter.convert_rgba_to_rgb(&src, &mut dst);
388
389 assert_eq!(dst, vec![255, 128, 64, 128, 64, 32]); }
391}