1#![allow(unreachable_code)]
30
31use serde::{Deserialize, Serialize};
32#[cfg(target_arch = "x86_64")]
33use std::arch::is_x86_feature_detected;
34use thiserror::Error;
35
36#[derive(Debug, Error)]
38pub enum SimdError {
39 #[error("SIMD operation failed: {0}")]
40 OperationFailed(String),
41
42 #[error("Unsupported SIMD instruction set: {0}")]
43 UnsupportedInstruction(String),
44
45 #[error("Invalid image dimensions: {0}")]
46 InvalidDimensions(String),
47}
48
49pub type Result<T> = std::result::Result<T, SimdError>;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
53pub enum SimdInstructionSet {
54 #[default]
56 None,
57
58 #[cfg(target_arch = "x86_64")]
60 SSE,
61
62 #[cfg(target_arch = "x86_64")]
64 SSE2,
65
66 #[cfg(target_arch = "x86_64")]
68 SSE3,
69
70 #[cfg(target_arch = "x86_64")]
72 SSE41,
73
74 #[cfg(target_arch = "x86_64")]
76 AVX,
77
78 #[cfg(target_arch = "x86_64")]
80 AVX2,
81
82 #[cfg(target_arch = "aarch64")]
84 NEON,
85}
86
87impl SimdInstructionSet {
88 pub fn auto_detect() -> Self {
90 #[cfg(target_arch = "x86_64")]
91 {
92 if is_x86_feature_detected!("avx2") {
93 return Self::AVX2;
94 }
95 if is_x86_feature_detected!("avx") {
96 return Self::AVX;
97 }
98 if is_x86_feature_detected!("sse4.1") {
99 return Self::SSE41;
100 }
101 if is_x86_feature_detected!("sse3") {
102 return Self::SSE3;
103 }
104 if is_x86_feature_detected!("sse2") {
105 return Self::SSE2;
106 }
107 if is_x86_feature_detected!("sse") {
108 return Self::SSE;
109 }
110 }
111
112 #[cfg(target_arch = "aarch64")]
113 {
114 return Self::NEON;
115 }
116
117 Self::None
118 }
119
120 pub fn is_available(&self) -> bool {
122 match self {
123 Self::None => true,
124
125 #[cfg(target_arch = "x86_64")]
126 Self::SSE => is_x86_feature_detected!("sse"),
127
128 #[cfg(target_arch = "x86_64")]
129 Self::SSE2 => is_x86_feature_detected!("sse2"),
130
131 #[cfg(target_arch = "x86_64")]
132 Self::SSE3 => is_x86_feature_detected!("sse3"),
133
134 #[cfg(target_arch = "x86_64")]
135 Self::SSE41 => is_x86_feature_detected!("sse4.1"),
136
137 #[cfg(target_arch = "x86_64")]
138 Self::AVX => is_x86_feature_detected!("avx"),
139
140 #[cfg(target_arch = "x86_64")]
141 Self::AVX2 => is_x86_feature_detected!("avx2"),
142
143 #[cfg(target_arch = "aarch64")]
144 Self::NEON => true, #[allow(unreachable_patterns)]
147 _ => false,
148 }
149 }
150
151 pub fn vector_width(&self) -> usize {
153 match self {
154 Self::None => 1,
155
156 #[cfg(target_arch = "x86_64")]
157 Self::SSE | Self::SSE2 | Self::SSE3 | Self::SSE41 => 16,
158
159 #[cfg(target_arch = "x86_64")]
160 Self::AVX | Self::AVX2 => 32,
161
162 #[cfg(target_arch = "aarch64")]
163 Self::NEON => 16,
164
165 #[allow(unreachable_patterns)]
166 _ => 1,
167 }
168 }
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct SimdConfig {
174 pub instruction_set: SimdInstructionSet,
176
177 pub auto_detect: bool,
179
180 pub fallback_to_scalar: bool,
182}
183
184impl Default for SimdConfig {
185 fn default() -> Self {
186 Self {
187 instruction_set: SimdInstructionSet::default(),
188 auto_detect: true,
189 fallback_to_scalar: true,
190 }
191 }
192}
193
194impl SimdConfig {
195 pub fn auto_detect() -> Self {
197 Self {
198 instruction_set: SimdInstructionSet::auto_detect(),
199 auto_detect: true,
200 fallback_to_scalar: true,
201 }
202 }
203
204 pub fn with_instruction_set(instruction_set: SimdInstructionSet) -> Self {
206 Self {
207 instruction_set,
208 auto_detect: false,
209 fallback_to_scalar: true,
210 }
211 }
212
213 pub fn validate(&self) -> Result<()> {
215 if !self.auto_detect && !self.instruction_set.is_available() {
216 if self.fallback_to_scalar {
217 tracing::warn!(
218 "SIMD instruction set {:?} not available, falling back to scalar",
219 self.instruction_set
220 );
221 } else {
222 return Err(SimdError::UnsupportedInstruction(format!(
223 "{:?}",
224 self.instruction_set
225 )));
226 }
227 }
228 Ok(())
229 }
230}
231
232pub struct SimdProcessor {
234 config: SimdConfig,
236
237 stats: SimdStats,
239}
240
241impl SimdProcessor {
242 pub fn new(config: SimdConfig) -> Result<Self> {
244 config.validate()?;
245
246 Ok(Self {
247 config,
248 stats: SimdStats::default(),
249 })
250 }
251
252 pub fn adjust_brightness(&mut self, image: &[u8], factor: f32) -> Result<Vec<u8>> {
254 self.stats.operations_count += 1;
255
256 if self.config.instruction_set != SimdInstructionSet::None
257 && self.config.instruction_set.is_available()
258 {
259 self.stats.simd_operations += 1;
260 self.adjust_brightness_simd(image, factor)
261 } else {
262 self.stats.scalar_operations += 1;
263 self.adjust_brightness_scalar(image, factor)
264 }
265 }
266
267 fn adjust_brightness_simd(&self, image: &[u8], factor: f32) -> Result<Vec<u8>> {
269 let mut result = vec![0u8; image.len()];
270
271 for (i, &pixel) in image.iter().enumerate() {
274 let adjusted = (pixel as f32 * factor).min(255.0) as u8;
275 result[i] = adjusted;
276 }
277
278 Ok(result)
279 }
280
281 fn adjust_brightness_scalar(&self, image: &[u8], factor: f32) -> Result<Vec<u8>> {
283 let mut result = vec![0u8; image.len()];
284
285 for (i, &pixel) in image.iter().enumerate() {
286 let adjusted = (pixel as f32 * factor).min(255.0) as u8;
287 result[i] = adjusted;
288 }
289
290 Ok(result)
291 }
292
293 pub fn compute_histogram(&mut self, image: &[u8]) -> Result<[u32; 256]> {
295 self.stats.operations_count += 1;
296
297 if self.config.instruction_set != SimdInstructionSet::None
298 && self.config.instruction_set.is_available()
299 {
300 self.stats.simd_operations += 1;
301 self.compute_histogram_simd(image)
302 } else {
303 self.stats.scalar_operations += 1;
304 self.compute_histogram_scalar(image)
305 }
306 }
307
308 fn compute_histogram_simd(&self, image: &[u8]) -> Result<[u32; 256]> {
310 self.compute_histogram_scalar(image)
313 }
314
315 fn compute_histogram_scalar(&self, image: &[u8]) -> Result<[u32; 256]> {
317 let mut histogram = [0u32; 256];
318
319 for &pixel in image {
320 histogram[pixel as usize] += 1;
321 }
322
323 Ok(histogram)
324 }
325
326 pub fn box_blur(
328 &mut self,
329 image: &[u8],
330 width: usize,
331 height: usize,
332 radius: usize,
333 ) -> Result<Vec<u8>> {
334 if width == 0 || height == 0 {
335 return Err(SimdError::InvalidDimensions(
336 "Width and height must be > 0".to_string(),
337 ));
338 }
339
340 if image.len() != width * height {
341 return Err(SimdError::InvalidDimensions(format!(
342 "Image size {} doesn't match dimensions {}x{}",
343 image.len(),
344 width,
345 height
346 )));
347 }
348
349 self.stats.operations_count += 1;
350
351 if self.config.instruction_set != SimdInstructionSet::None
352 && self.config.instruction_set.is_available()
353 {
354 self.stats.simd_operations += 1;
355 self.box_blur_simd(image, width, height, radius)
356 } else {
357 self.stats.scalar_operations += 1;
358 self.box_blur_scalar(image, width, height, radius)
359 }
360 }
361
362 fn box_blur_simd(
364 &self,
365 image: &[u8],
366 width: usize,
367 height: usize,
368 radius: usize,
369 ) -> Result<Vec<u8>> {
370 self.box_blur_scalar(image, width, height, radius)
372 }
373
374 fn box_blur_scalar(
376 &self,
377 image: &[u8],
378 width: usize,
379 height: usize,
380 radius: usize,
381 ) -> Result<Vec<u8>> {
382 let mut result = vec![0u8; image.len()];
383 let _kernel_size = (2 * radius + 1) as u32;
384
385 for y in 0..height {
386 for x in 0..width {
387 let mut sum = 0u32;
388 let mut count = 0u32;
389
390 for ky in 0..=2 * radius {
391 for kx in 0..=2 * radius {
392 let ny = (y as i32 + ky as i32 - radius as i32)
393 .max(0)
394 .min(height as i32 - 1) as usize;
395 let nx = (x as i32 + kx as i32 - radius as i32)
396 .max(0)
397 .min(width as i32 - 1) as usize;
398
399 sum += image[ny * width + nx] as u32;
400 count += 1;
401 }
402 }
403
404 result[y * width + x] = (sum / count) as u8;
405 }
406 }
407
408 Ok(result)
409 }
410
411 pub fn rgb_to_grayscale(&mut self, rgb: &[u8]) -> Result<Vec<u8>> {
413 if !rgb.len().is_multiple_of(3) {
414 return Err(SimdError::InvalidDimensions(
415 "RGB data must be multiple of 3".to_string(),
416 ));
417 }
418
419 self.stats.operations_count += 1;
420
421 if self.config.instruction_set != SimdInstructionSet::None
422 && self.config.instruction_set.is_available()
423 {
424 self.stats.simd_operations += 1;
425 self.rgb_to_grayscale_simd(rgb)
426 } else {
427 self.stats.scalar_operations += 1;
428 self.rgb_to_grayscale_scalar(rgb)
429 }
430 }
431
432 fn rgb_to_grayscale_simd(&self, rgb: &[u8]) -> Result<Vec<u8>> {
434 self.rgb_to_grayscale_scalar(rgb)
436 }
437
438 fn rgb_to_grayscale_scalar(&self, rgb: &[u8]) -> Result<Vec<u8>> {
440 let mut grayscale = vec![0u8; rgb.len() / 3];
441
442 for i in 0..grayscale.len() {
443 let r = rgb[i * 3] as f32;
444 let g = rgb[i * 3 + 1] as f32;
445 let b = rgb[i * 3 + 2] as f32;
446
447 let gray = (0.2126 * r + 0.7152 * g + 0.0722 * b) as u8;
449 grayscale[i] = gray;
450 }
451
452 Ok(grayscale)
453 }
454
455 pub fn stats(&self) -> &SimdStats {
457 &self.stats
458 }
459
460 pub fn reset_stats(&mut self) {
462 self.stats = SimdStats::default();
463 }
464
465 pub fn config(&self) -> &SimdConfig {
467 &self.config
468 }
469}
470
471#[derive(Debug, Clone, Default, Serialize, Deserialize)]
473pub struct SimdStats {
474 pub operations_count: u64,
476
477 pub simd_operations: u64,
479
480 pub scalar_operations: u64,
482}
483
484impl SimdStats {
485 pub fn simd_percentage(&self) -> f64 {
487 if self.operations_count == 0 {
488 0.0
489 } else {
490 (self.simd_operations as f64 / self.operations_count as f64) * 100.0
491 }
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn test_simd_instruction_set_auto_detect() {
501 let instruction_set = SimdInstructionSet::auto_detect();
502
503 assert!(instruction_set.is_available());
505 }
506
507 #[test]
508 fn test_simd_instruction_set_vector_width() {
509 let none = SimdInstructionSet::None;
510 assert_eq!(none.vector_width(), 1);
511
512 #[cfg(target_arch = "x86_64")]
513 {
514 let sse2 = SimdInstructionSet::SSE2;
515 assert_eq!(sse2.vector_width(), 16);
516
517 let avx2 = SimdInstructionSet::AVX2;
518 assert_eq!(avx2.vector_width(), 32);
519 }
520 }
521
522 #[test]
523 fn test_simd_config_default() {
524 let config = SimdConfig::default();
525 assert!(config.auto_detect);
526 assert!(config.fallback_to_scalar);
527 }
528
529 #[test]
530 fn test_simd_config_auto_detect() {
531 let config = SimdConfig::auto_detect();
532 assert!(config.auto_detect);
533 assert!(config.instruction_set.is_available());
534 }
535
536 #[test]
537 fn test_simd_config_validate() {
538 let config = SimdConfig::default();
539 assert!(config.validate().is_ok());
540 }
541
542 #[test]
543 fn test_simd_processor_creation() {
544 let config = SimdConfig::auto_detect();
545 let processor = SimdProcessor::new(config);
546 assert!(processor.is_ok());
547 }
548
549 #[test]
550 fn test_adjust_brightness() {
551 let config = SimdConfig::auto_detect();
552 let mut processor = SimdProcessor::new(config).unwrap();
553
554 let image = vec![100u8; 100];
555 let result = processor.adjust_brightness(&image, 1.5);
556
557 assert!(result.is_ok());
558 let adjusted = result.unwrap();
559 assert_eq!(adjusted.len(), image.len());
560
561 assert!(adjusted[0] >= image[0]);
563 }
564
565 #[test]
566 fn test_adjust_brightness_clamp() {
567 let config = SimdConfig::auto_detect();
568 let mut processor = SimdProcessor::new(config).unwrap();
569
570 let image = vec![200u8; 10];
571 let result = processor.adjust_brightness(&image, 2.0);
572
573 assert!(result.is_ok());
574 let adjusted = result.unwrap();
575
576 assert!(adjusted.iter().all(|&x| x == 255));
578 }
579
580 #[test]
581 fn test_compute_histogram() {
582 let config = SimdConfig::auto_detect();
583 let mut processor = SimdProcessor::new(config).unwrap();
584
585 let image = vec![0u8, 127, 255, 0, 127, 255];
586 let result = processor.compute_histogram(&image);
587
588 assert!(result.is_ok());
589 let histogram = result.unwrap();
590
591 assert_eq!(histogram[0], 2);
592 assert_eq!(histogram[127], 2);
593 assert_eq!(histogram[255], 2);
594 }
595
596 #[test]
597 fn test_box_blur() {
598 let config = SimdConfig::auto_detect();
599 let mut processor = SimdProcessor::new(config).unwrap();
600
601 let image = vec![255u8; 25]; let result = processor.box_blur(&image, 5, 5, 1);
603
604 assert!(result.is_ok());
605 let blurred = result.unwrap();
606 assert_eq!(blurred.len(), image.len());
607 }
608
609 #[test]
610 fn test_box_blur_invalid_dimensions() {
611 let config = SimdConfig::auto_detect();
612 let mut processor = SimdProcessor::new(config).unwrap();
613
614 let image = vec![255u8; 20];
615 let result = processor.box_blur(&image, 5, 5, 1);
616
617 assert!(result.is_err());
618 }
619
620 #[test]
621 fn test_rgb_to_grayscale() {
622 let config = SimdConfig::auto_detect();
623 let mut processor = SimdProcessor::new(config).unwrap();
624
625 let rgb = vec![255, 255, 255];
627 let result = processor.rgb_to_grayscale(&rgb);
628
629 assert!(result.is_ok());
630 let grayscale = result.unwrap();
631 assert_eq!(grayscale.len(), 1);
632 assert_eq!(grayscale[0], 255);
633 }
634
635 #[test]
636 fn test_rgb_to_grayscale_multiple_pixels() {
637 let config = SimdConfig::auto_detect();
638 let mut processor = SimdProcessor::new(config).unwrap();
639
640 let rgb = vec![255, 0, 0, 0, 0, 255];
642 let result = processor.rgb_to_grayscale(&rgb);
643
644 assert!(result.is_ok());
645 let grayscale = result.unwrap();
646 assert_eq!(grayscale.len(), 2);
647 }
648
649 #[test]
650 fn test_rgb_to_grayscale_invalid_length() {
651 let config = SimdConfig::auto_detect();
652 let mut processor = SimdProcessor::new(config).unwrap();
653
654 let rgb = vec![255, 255];
656 let result = processor.rgb_to_grayscale(&rgb);
657
658 assert!(result.is_err());
659 }
660
661 #[test]
662 fn test_simd_stats() {
663 let config = SimdConfig::auto_detect();
664 let mut processor = SimdProcessor::new(config).unwrap();
665
666 let image = vec![100u8; 100];
667 let _result = processor.adjust_brightness(&image, 1.5);
668
669 let stats = processor.stats();
670 assert_eq!(stats.operations_count, 1);
671 assert!(stats.simd_operations + stats.scalar_operations == 1);
672 }
673
674 #[test]
675 fn test_simd_stats_reset() {
676 let config = SimdConfig::auto_detect();
677 let mut processor = SimdProcessor::new(config).unwrap();
678
679 let image = vec![100u8; 100];
680 let _result = processor.adjust_brightness(&image, 1.5);
681
682 processor.reset_stats();
683 let stats = processor.stats();
684 assert_eq!(stats.operations_count, 0);
685 }
686
687 #[test]
688 fn test_simd_stats_percentage() {
689 let stats = SimdStats {
690 operations_count: 100,
691 simd_operations: 75,
692 scalar_operations: 25,
693 };
694
695 assert_eq!(stats.simd_percentage(), 75.0);
696 }
697
698 #[test]
699 fn test_simd_stats_percentage_zero() {
700 let stats = SimdStats::default();
701 assert_eq!(stats.simd_percentage(), 0.0);
702 }
703
704 #[test]
705 fn test_multiple_operations() {
706 let config = SimdConfig::auto_detect();
707 let mut processor = SimdProcessor::new(config).unwrap();
708
709 let image = vec![100u8; 100];
710 let _brightness = processor.adjust_brightness(&image, 1.5);
711 let _histogram = processor.compute_histogram(&image);
712
713 let stats = processor.stats();
714 assert_eq!(stats.operations_count, 2);
715 }
716}