Skip to content

Commit f431b60

Browse files
committed
Add a test verifying that AVX2 result is identical to scalar
1 parent fc320d8 commit f431b60

File tree

1 file changed

+76
-0
lines changed

1 file changed

+76
-0
lines changed

src/avx2/ycbcr.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,79 @@ ycbcr_image_avx2!(RgbImageAVX2, 3, 0, 1, 2);
151151
ycbcr_image_avx2!(RgbaImageAVX2, 4, 0, 1, 2);
152152
ycbcr_image_avx2!(BgrImageAVX2, 3, 2, 1, 0);
153153
ycbcr_image_avx2!(BgraImageAVX2, 4, 2, 1, 0);
154+
155+
#[cfg(test)]
156+
mod tests {
157+
use super::*;
158+
use std::vec::Vec;
159+
160+
// A very basic linear congruential generator (LCG) to avoid external dependencies.
161+
pub struct SimpleRng {
162+
state: u64,
163+
}
164+
165+
impl SimpleRng {
166+
/// Create a new RNG with a given seed.
167+
pub fn new(seed: u64) -> Self {
168+
Self { state: seed }
169+
}
170+
171+
/// Generate the next random u64 value.
172+
pub fn next_u64(&mut self) -> u64 {
173+
// Constants from Numerical Recipes
174+
self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1);
175+
self.state
176+
}
177+
178+
/// Generate a random byte in 0..=255
179+
pub fn next_byte(&mut self) -> u8 {
180+
(self.next_u64() & 0xFF) as u8
181+
}
182+
183+
/// Fill a Vec<u8> with random bytes of the given length.
184+
pub fn random_bytes(&mut self, len: usize) -> Vec<u8> {
185+
(0..len).map(|_| self.next_byte()).collect()
186+
}
187+
}
188+
189+
#[test]
190+
#[cfg(feature = "simd")]
191+
fn avx_matches_scalar_rgb() {
192+
let mut rng = SimpleRng::new(42);
193+
let width = 512 + 3; // power of two plus a bit to stress remainder handling
194+
let height = 1;
195+
let bpp = 3;
196+
197+
let input = rng.random_bytes(width * height * bpp); // power of two plus a bit to exercise remainder handling
198+
199+
let scalar_result: Vec<[u8; 3]> = input
200+
.chunks_exact(bpp)
201+
.map(|chunk| {
202+
let [r, g, b, ..] = chunk else { unreachable!() };
203+
let (y, cb, cr) = rgb_to_ycbcr(*r, *g, *b);
204+
[y, cb, cr]
205+
})
206+
.collect();
207+
208+
let mut buffers = [Vec::new(), Vec::new(), Vec::new(), Vec::new()];
209+
let avx_input = RgbImageAVX2(
210+
&input,
211+
width.try_into().unwrap(),
212+
height.try_into().unwrap(),
213+
);
214+
unsafe {
215+
let avx_result = avx_input.fill_buffers_avx2(0, &mut buffers);
216+
}
217+
218+
for i in 0..3 {
219+
assert_eq!(buffers[i].len(), input.len() / 3);
220+
}
221+
222+
for (i, pixel) in scalar_result.iter().copied().enumerate() {
223+
let avx_pixel: [u8; 3] = [buffers[0][i], buffers[1][i], buffers[2][i]];
224+
if pixel != avx_pixel {
225+
panic!("Mismatch at index {i}: scalar result is {pixel:?}, avx result is {avx_pixel:?}");
226+
}
227+
}
228+
}
229+
}

0 commit comments

Comments
 (0)