Fixed error in implementation of MulDiv::divide_alpha() and MulDiv::divide_alpha_inplace() for U16x4 pixels with optimisation with helps of SSE4.1 and AVX2.

This commit is contained in:
Kirill Kuzminykh
2022-11-09 03:22:13 +04:00
parent 4ecb03336c
commit f2ef3d1b04
6 changed files with 86 additions and 120 deletions
+3 -1
View File
@@ -4,10 +4,12 @@
- Added method `CpuExtensions::is_supported(&self)`.
- Internals of `PixelComponentMapper` changed to use heap to store its data.
- Fixed link to documentation page in `README.md` file.
- Added support of optimisation with helps of `NEON SIMD` for convolution of `U16x4` images.
- Added optimisation for processing `U16x4` images by `MulDiv` with
helps of `NEON SIMD` instructions.
- Fixed link to documentation page in `README.md` file.
- Fixed error in implementation of `MulDiv::divide_alpha()` and `MulDiv::divide_alpha_inplace()`
for `U16x4` pixels with optimisation with helps of `SSE4.1` and `AVX2`.
## [2.0.0] - 2022-10-28
+1 -1
View File
@@ -156,7 +156,7 @@ unsafe fn divide_alpha_four_pixels(src: *const U16x4, dst: *mut U16x4) {
let alpha1_f32x8 = _mm256_cvtepi32_ps(_mm256_shuffle_epi8(src_pixels, alpha32_sh1));
let pix0_f32x8 = _mm256_cvtepi32_ps(_mm256_unpacklo_epi16(src_pixels, zero));
let pix1_f32x8 = _mm256_cvtepi32_ps(_mm256_unpacklo_epi16(src_pixels, zero));
let pix1_f32x8 = _mm256_cvtepi32_ps(_mm256_unpackhi_epi16(src_pixels, zero));
let scaled_pix0_f32x8 = _mm256_mul_ps(pix0_f32x8, alpha_max);
let scaled_pix1_f32x8 = _mm256_mul_ps(pix1_f32x8, alpha_max);
+1 -1
View File
@@ -139,7 +139,7 @@ unsafe fn divide_alpha_two_pixels(src: *const U16x4, dst: *mut U16x4) {
let alpha1_f32x4 = _mm_cvtepi32_ps(_mm_shuffle_epi8(src_pixels, alpha32_sh1));
let pix0_f32x4 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(src_pixels, zero));
let pix1_f32x4 = _mm_cvtepi32_ps(_mm_unpacklo_epi16(src_pixels, zero));
let pix1_f32x4 = _mm_cvtepi32_ps(_mm_unpackhi_epi16(src_pixels, zero));
let scaled_pix0_f32x4 = _mm_mul_ps(pix0_f32x4, alpha_max);
let scaled_pix1_f32x4 = _mm_mul_ps(pix1_f32x4, alpha_max);
+6 -6
View File
@@ -80,7 +80,7 @@ impl<const N: usize> GetCountOfValues for Values<N> {
/// Information about one component of pixel.
pub trait PixelComponent
where
Self: Sized + Copy + Debug + 'static,
Self: Sized + Copy + Debug + PartialEq + 'static,
{
/// Type that provides information about a count of
/// available values of one pixel's component
@@ -112,7 +112,7 @@ pub trait IntoPixelType {
/// Additional information about pixel type.
pub trait PixelExt
where
Self: Copy + Clone + Sized + Debug + IntoPixelType,
Self: Copy + Clone + Sized + Debug + PartialEq + IntoPixelType,
{
/// Type of pixel components
type Component: PixelComponent;
@@ -158,19 +158,19 @@ where
}
/// Generic type of pixel.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub struct Pixel<T, C, const COUNT_OF_COMPONENTS: usize>(
pub T,
PhantomData<[C; COUNT_OF_COMPONENTS]>,
)
where
T: Sized + Copy + Clone + Debug + 'static,
T: Sized + Copy + Clone + Debug + PartialEq + 'static,
C: PixelComponent;
impl<T, C, const COUNT_OF_COMPONENTS: usize> Pixel<T, C, COUNT_OF_COMPONENTS>
where
T: Sized + Copy + Clone + Debug + 'static,
T: Sized + Copy + Clone + Debug + PartialEq + 'static,
C: PixelComponent,
{
#[inline(always)]
@@ -182,7 +182,7 @@ where
impl<T, C, const COUNT_OF_COMPONENTS: usize> PixelExt for Pixel<T, C, COUNT_OF_COMPONENTS>
where
Self: IntoPixelType,
T: Sized + Copy + Clone + Debug + 'static,
T: Sized + Copy + Clone + Debug + PartialEq + 'static,
C: PixelComponent,
{
type Component = C;
+74 -110
View File
@@ -22,8 +22,12 @@ enum Oper {
// Multiplies by alpha
fn mul_div_alpha_test<P>(oper: Oper, src_pixel: P, result_pixel: P, cpu_extensions: CpuExtensions)
where
fn mul_div_alpha_test<P, const N: usize>(
oper: Oper,
src_pixels_tpl: [P; N],
expected_pixels_tpl: [P; N],
cpu_extensions: CpuExtensions,
) where
P: PixelExt + 'static,
for<'a> ImageView<'a, P>: Into<DynamicImageView<'a>>,
for<'a> ImageViewMut<'a, P>: Into<DynamicImageViewMut<'a>>,
@@ -39,7 +43,13 @@ where
let height: u32 = 3;
let src_size = width as usize * height as usize;
let mut src_pixels = vec![src_pixel; src_size];
let src_pixels: Vec<P> = src_pixels_tpl
.iter()
.copied()
.cycle()
.take(src_size)
.collect();
let mut dst_pixels = src_pixels.clone();
let src_dyn_image_view = ImageView::from_pixels(
NonZeroU32::new(width).unwrap(),
@@ -49,12 +59,13 @@ where
.unwrap()
.into();
let mut dst_image = Image::new(
let mut dst_dyn_image_view = ImageViewMut::from_pixels(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
P::pixel_type(),
);
let mut dst_image_view = dst_image.view_mut();
&mut dst_pixels,
)
.unwrap()
.into();
let mut alpha_mul_div: MulDiv = Default::default();
unsafe {
@@ -63,28 +74,37 @@ where
match oper {
Oper::Mul => alpha_mul_div
.multiply_alpha(&src_dyn_image_view, &mut dst_image_view)
.multiply_alpha(&src_dyn_image_view, &mut dst_dyn_image_view)
.unwrap(),
Oper::Div => alpha_mul_div
.divide_alpha(&src_dyn_image_view, &mut dst_image_view)
.divide_alpha(&src_dyn_image_view, &mut dst_dyn_image_view)
.unwrap(),
}
let res_pixels: Vec<P> = vec![result_pixel; src_size];
let res_buffer = unsafe { res_pixels.align_to::<u8>().1 };
let dst_buffer = dst_image.buffer();
assert!(
dst_buffer.iter().zip(res_buffer).all(|(&d, &r)| d == r),
"failed test: src={:?}, expected_result={:?}",
src_pixel,
result_pixel
);
let expected_pixels: Vec<P> = expected_pixels_tpl
.iter()
.copied()
.cycle()
.take(src_size)
.collect();
for ((s, r), e) in src_pixels
.iter()
.zip(dst_pixels)
.zip(expected_pixels.iter())
{
assert_eq!(
r, *e,
"failed test: src={:?}, result={:?}, expected_result={:?}",
s, r, e
);
}
// Inplace
let mut src_pixels_clone = src_pixels.clone();
let mut image_view = ImageViewMut::from_pixels(
NonZeroU32::new(width).unwrap(),
NonZeroU32::new(height).unwrap(),
&mut src_pixels,
&mut src_pixels_clone,
)
.unwrap()
.into();
@@ -96,13 +116,13 @@ where
Oper::Div => alpha_mul_div.divide_alpha_inplace(&mut image_view).unwrap(),
}
let src_buffer = unsafe { src_pixels.align_to::<u8>().1 };
assert!(
src_buffer.iter().zip(res_buffer).all(|(&s, &r)| s == r),
"failed inplace test: src={:?}, expected_result={:?}",
src_pixel,
result_pixel
);
for ((s, r), e) in src_pixels.iter().zip(src_pixels_clone).zip(expected_pixels) {
assert_eq!(
r, e,
"failed inplace test: src={:?}, result={:?}, expected_result={:?}",
s, r, e
);
}
}
#[cfg(test)]
@@ -119,32 +139,24 @@ mod multiply_alpha_u8x4 {
#[cfg(target_arch = "x86_64")]
#[test]
fn avx2_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Avx2);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Avx2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn sse4_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Sse4_1);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Sse4_1);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn neon_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Neon);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Neon);
}
#[test]
fn native_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::None);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::None);
}
}
@@ -178,24 +190,18 @@ mod multiply_alpha_u8x2 {
#[cfg(target_arch = "x86_64")]
#[test]
fn avx2_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Avx2);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Avx2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn sse4_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Sse4_1);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Sse4_1);
}
#[test]
fn native_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::None);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::None);
}
}
@@ -230,24 +236,18 @@ mod multiply_alpha_u16x2 {
#[cfg(target_arch = "x86_64")]
#[test]
fn avx2_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Avx2);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Avx2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn sse4_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Sse4_1);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Sse4_1);
}
#[test]
fn native_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::None);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::None);
}
}
@@ -270,32 +270,24 @@ mod multiply_alpha_u16x4 {
#[cfg(target_arch = "x86_64")]
#[test]
fn avx2_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Avx2);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Avx2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn sse4_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Sse4_1);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Sse4_1);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn neon_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::Neon);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::Neon);
}
#[test]
fn native_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(Oper::Mul, s, r, CpuExtensions::None);
}
mul_div_alpha_test(Oper::Mul, SRC_PIXELS, RES_PIXELS, CpuExtensions::None);
}
}
@@ -316,32 +308,24 @@ mod divide_alpha_u8x4 {
#[cfg(target_arch = "x86_64")]
#[test]
fn avx2_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Avx2);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::Avx2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn sse4_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Sse4_1);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::Sse4_1);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn neon_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Neon);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::Neon);
}
#[test]
fn native_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::None);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::None);
}
}
@@ -376,24 +360,18 @@ mod divide_alpha_u8x2 {
#[cfg(target_arch = "x86_64")]
#[test]
fn avx2_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Avx2);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::Avx2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn sse4_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Sse4_1);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::Sse4_1);
}
#[test]
fn native_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::None);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::None);
}
}
@@ -439,24 +417,18 @@ mod divide_alpha_u16x2 {
#[cfg(target_arch = "x86_64")]
#[test]
fn avx2_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(SIMD_RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Avx2);
}
mul_div_alpha_test(OPER, SRC_PIXELS, SIMD_RES_PIXELS, CpuExtensions::Avx2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn sse4_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(SIMD_RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Sse4_1);
}
mul_div_alpha_test(OPER, SRC_PIXELS, SIMD_RES_PIXELS, CpuExtensions::Sse4_1);
}
#[test]
fn native_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::None);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::None);
}
}
@@ -484,32 +456,24 @@ mod divide_alpha_u16x4 {
#[cfg(target_arch = "x86_64")]
#[test]
fn avx2_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(SIMD_RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Avx2);
}
mul_div_alpha_test(OPER, SRC_PIXELS, SIMD_RES_PIXELS, CpuExtensions::Avx2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn sse4_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(SIMD_RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Sse4_1);
}
mul_div_alpha_test(OPER, SRC_PIXELS, SIMD_RES_PIXELS, CpuExtensions::Sse4_1);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn neon_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::Neon);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::Neon);
}
#[test]
fn native_test() {
for (s, r) in SRC_PIXELS.into_iter().zip(RES_PIXELS) {
mul_div_alpha_test(OPER, s, r, CpuExtensions::None);
}
mul_div_alpha_test(OPER, SRC_PIXELS, RES_PIXELS, CpuExtensions::None);
}
}
+1 -1
View File
@@ -91,7 +91,7 @@ trait ResizeTest<const CC: usize> {
impl<T, C, const CC: usize> ResizeTest<CC> for Pixel<T, C, CC>
where
Self: PixelTestingExt,
T: Sized + Copy + Clone + Debug + 'static,
T: Sized + Copy + Clone + Debug + PartialEq + 'static,
C: PixelComponent,
{
fn downscale_test(resize_alg: ResizeAlg, cpu_extensions: CpuExtensions, checksum: [u64; CC]) {