pixie_anim_lib/simd/
mod.rs

1//! SIMD acceleration module for performance-critical operations.
2
3use crate::quant::Rgb;
4#[cfg(target_arch = "x86_64")]
5use std::sync::LazyLock;
6
7#[cfg(target_arch = "x86_64")]
8pub mod x86_64;
9
10#[cfg(target_arch = "x86_64")]
11pub use x86_64::PlanarLabPalette;
12
13/// Fallback scalar implementations.
14pub mod fallback;
15
16#[cfg(target_arch = "x86_64")]
17enum SimdLevel {
18    Scalar,
19    Avx2,
20}
21
22#[cfg(target_arch = "x86_64")]
23static SIMD_LEVEL: LazyLock<SimdLevel> = LazyLock::new(|| {
24    if is_x86_feature_detected!("avx2") {
25        SimdLevel::Avx2
26    } else {
27        SimdLevel::Scalar
28    }
29});
30
31/// A color palette stored in a planar layout for SIMD efficiency.
32#[cfg(not(target_arch = "x86_64"))]
33pub struct PlanarLabPalette {
34    /// L components
35    pub l: Vec<f32>,
36    /// a components
37    pub a: Vec<f32>,
38    /// b components
39    pub b: Vec<f32>,
40    /// Number of colors in the palette
41    pub len: usize,
42}
43
44#[cfg(not(target_arch = "x86_64"))]
45impl PlanarLabPalette {
46    /// Creates a planar palette from a slice of Lab colors.
47    pub fn from_lab(colors: &[crate::color::Lab]) -> Self {
48        let len = colors.len();
49        let mut l = Vec::with_capacity(len);
50        let mut a = Vec::with_capacity(len);
51        let mut b = Vec::with_capacity(len);
52        for c in colors {
53            l.push(c.l);
54            a.push(c.a);
55            b.push(c.b);
56        }
57        Self { l, a, b, len }
58    }
59}
60
61/// Find the index of the nearest color in Lab space using the best available implementation.
62#[inline]
63pub fn find_nearest_color_lab(pixel: crate::color::Lab, palette: &PlanarLabPalette) -> usize {
64    #[cfg(target_arch = "x86_64")]
65    {
66        match *SIMD_LEVEL {
67            SimdLevel::Avx2 => unsafe {
68                x86_64::find_nearest_color_lab_planar_avx2(pixel, palette)
69            },
70            SimdLevel::Scalar => fallback::find_nearest_color_lab_planar(pixel, palette),
71        }
72    }
73
74    #[cfg(not(target_arch = "x86_64"))]
75    fallback::find_nearest_color_lab_planar(pixel, palette)
76}
77
78/// Find the index of the nearest color in a palette using Euclidean distance.
79#[inline]
80pub fn find_nearest_color(pixel: Rgb, palette: &[Rgb]) -> usize {
81    #[cfg(target_arch = "x86_64")]
82    {
83        match *SIMD_LEVEL {
84            SimdLevel::Avx2 => unsafe { x86_64::find_nearest_color_avx2(pixel, palette) },
85            SimdLevel::Scalar => fallback::find_nearest_color(pixel, palette),
86        }
87    }
88
89    #[cfg(not(target_arch = "x86_64"))]
90    fallback::find_nearest_color(pixel, palette)
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_simd_vs_scalar() {
99        let palette = vec![
100            Rgb { r: 255, g: 0, b: 0 },
101            Rgb { r: 0, g: 255, b: 0 },
102            Rgb { r: 0, g: 0, b: 255 },
103            Rgb {
104                r: 128,
105                g: 128,
106                b: 128,
107            },
108            Rgb {
109                r: 10,
110                g: 10,
111                b: 10,
112            },
113            Rgb {
114                r: 200,
115                g: 200,
116                b: 200,
117            },
118            Rgb {
119                r: 50,
120                g: 150,
121                b: 250,
122            },
123            Rgb {
124                r: 250,
125                g: 150,
126                b: 50,
127            },
128            Rgb {
129                r: 20,
130                g: 30,
131                b: 40,
132            },
133        ];
134        let pixel = Rgb {
135            r: 240,
136            g: 10,
137            b: 10,
138        };
139
140        let scalar_idx = fallback::find_nearest_color(pixel, &palette);
141        let simd_idx = find_nearest_color(pixel, &palette);
142
143        assert_eq!(scalar_idx, simd_idx);
144        assert_eq!(scalar_idx, 0); // Should be Red
145    }
146}