#![allow(clippy::undocumented_unsafe_blocks)]
use std::{arch::x86_64::*, num::NonZeroUsize};
use crate::util::Pixel;
#[target_feature(enable = "avx2")]
pub(super) fn reduce_quadratic<T: Pixel>(
dest: &mut [T],
src: &[T],
dest_pitch: NonZeroUsize,
src_pitch: NonZeroUsize,
dest_width: NonZeroUsize,
dest_height: NonZeroUsize,
) {
debug_assert!(src.len() >= src_pitch.get() * dest_height.get() * 2);
debug_assert!(dest.len() >= dest_pitch.get() * dest_height.get());
match size_of::<T>() {
1 => unsafe {
reduce_quadratic_vertical_u8(
dest.as_mut_ptr() as *mut u8,
src.as_ptr() as *const u8,
dest_pitch,
src_pitch,
dest_width.saturating_mul(NonZeroUsize::new_unchecked(2)),
dest_height,
);
reduce_quadratic_horizontal_inplace_u8(
dest.as_mut_ptr() as *mut u8,
dest_pitch,
dest_width,
dest_height,
);
},
2 => unsafe {
reduce_quadratic_vertical_u16(
dest.as_mut_ptr() as *mut u16,
src.as_ptr() as *const u16,
dest_pitch,
src_pitch,
dest_width.saturating_mul(NonZeroUsize::new_unchecked(2)),
dest_height,
);
reduce_quadratic_horizontal_inplace_u16(
dest.as_mut_ptr() as *mut u16,
dest_pitch,
dest_width,
dest_height,
);
},
_ => unreachable!(),
}
}
#[target_feature(enable = "avx2")]
unsafe fn reduce_quadratic_vertical_u8(
dest: *mut u8,
src: *const u8,
dest_pitch: NonZeroUsize,
src_pitch: NonZeroUsize,
dest_width: NonZeroUsize,
dest_height: NonZeroUsize,
) {
let dest_pitch_val = dest_pitch.get();
let src_pitch_val = src_pitch.get();
let dest_width_val = dest_width.get();
let dest_height_val = dest_height.get();
let w01 = _mm256_set1_epi16(0x0901);
let w23 = _mm256_set1_epi16(0x1616);
let w45 = _mm256_set1_epi16(0x0109);
let round16 = _mm256_set1_epi16(32);
{
let dest_row = dest;
let src_row0 = src;
let src_row1 = src.add(src_pitch_val);
let mut x = 0;
while x + 32 <= dest_width_val {
let a = _mm256_loadu_si256(src_row0.add(x) as *const __m256i);
let b = _mm256_loadu_si256(src_row1.add(x) as *const __m256i);
let a_lo = _mm256_unpacklo_epi8(a, _mm256_setzero_si256());
let a_hi = _mm256_unpackhi_epi8(a, _mm256_setzero_si256());
let b_lo = _mm256_unpacklo_epi8(b, _mm256_setzero_si256());
let b_hi = _mm256_unpackhi_epi8(b, _mm256_setzero_si256());
let sum_lo = _mm256_add_epi16(a_lo, b_lo);
let sum_hi = _mm256_add_epi16(a_hi, b_hi);
let one = _mm256_set1_epi16(1);
let sum_lo_rounded = _mm256_add_epi16(sum_lo, one);
let sum_hi_rounded = _mm256_add_epi16(sum_hi, one);
let result_lo = _mm256_srli_epi16(sum_lo_rounded, 1);
let result_hi = _mm256_srli_epi16(sum_hi_rounded, 1);
let result = _mm256_packus_epi16(result_lo, result_hi);
_mm256_storeu_si256(dest_row.add(x) as *mut __m256i, result);
x += 32;
}
while x < dest_width_val {
let a = *src_row0.add(x) as u16;
let b = *src_row1.add(x) as u16;
*dest_row.add(x) = ((a + b + 1) / 2) as u8;
x += 1;
}
}
for y in 1..(dest_height_val - 1) {
let dest_row = dest.add(y * dest_pitch_val);
let src_row_offset = y * 2 * src_pitch_val;
let src_m2 = src.add(src_row_offset - src_pitch_val * 2);
let src_m1 = src.add(src_row_offset - src_pitch_val);
let src_p0 = src.add(src_row_offset);
let src_p1 = src.add(src_row_offset + src_pitch_val);
let src_p2 = src.add(src_row_offset + src_pitch_val * 2);
let src_p3 = src.add(src_row_offset + src_pitch_val * 3);
let mut x = 0;
while x + 32 <= dest_width_val {
let m0 = _mm256_loadu_si256(src_m2.add(x) as *const __m256i);
let m1 = _mm256_loadu_si256(src_m1.add(x) as *const __m256i);
let m2 = _mm256_loadu_si256(src_p0.add(x) as *const __m256i);
let m3 = _mm256_loadu_si256(src_p1.add(x) as *const __m256i);
let m4 = _mm256_loadu_si256(src_p2.add(x) as *const __m256i);
let m5 = _mm256_loadu_si256(src_p3.add(x) as *const __m256i);
let p01_lo = _mm256_unpacklo_epi8(m0, m1);
let p01_hi = _mm256_unpackhi_epi8(m0, m1);
let p23_lo = _mm256_unpacklo_epi8(m2, m3);
let p23_hi = _mm256_unpackhi_epi8(m2, m3);
let p45_lo = _mm256_unpacklo_epi8(m4, m5);
let p45_hi = _mm256_unpackhi_epi8(m4, m5);
let term01_lo = _mm256_maddubs_epi16(p01_lo, w01);
let term01_hi = _mm256_maddubs_epi16(p01_hi, w01);
let term23_lo = _mm256_maddubs_epi16(p23_lo, w23);
let term23_hi = _mm256_maddubs_epi16(p23_hi, w23);
let term45_lo = _mm256_maddubs_epi16(p45_lo, w45);
let term45_hi = _mm256_maddubs_epi16(p45_hi, w45);
let sum_lo = _mm256_add_epi16(_mm256_add_epi16(term01_lo, term23_lo), term45_lo);
let sum_hi = _mm256_add_epi16(_mm256_add_epi16(term01_hi, term23_hi), term45_hi);
let result_lo = _mm256_srli_epi16(_mm256_add_epi16(sum_lo, round16), 6);
let result_hi = _mm256_srli_epi16(_mm256_add_epi16(sum_hi, round16), 6);
let result = _mm256_packus_epi16(result_lo, result_hi);
_mm256_storeu_si256(dest_row.add(x) as *mut __m256i, result);
x += 32;
}
while x < dest_width_val {
let mut m0 = *src_m2.add(x) as u16;
let mut m1 = *src_m1.add(x) as u16;
let mut m2 = *src_p0.add(x) as u16;
let m3 = *src_p1.add(x) as u16;
let m4 = *src_p2.add(x) as u16;
let m5 = *src_p3.add(x) as u16;
m2 = (m2 + m3) * 22;
m1 = (m1 + m4) * 9;
m0 += m5 + m2 + m1 + 32;
m0 >>= 6;
*dest_row.add(x) = (m0.min(255)) as u8;
x += 1;
}
}
if dest_height_val > 1 {
let dest_row = dest.add((dest_height_val - 1) * dest_pitch_val);
let src_row_offset = (dest_height_val - 1) * 2 * src_pitch_val;
let src_row0 = src.add(src_row_offset);
let src_row1 = src.add(src_row_offset + src_pitch_val);
let mut x = 0;
while x + 32 <= dest_width_val {
let a = _mm256_loadu_si256(src_row0.add(x) as *const __m256i);
let b = _mm256_loadu_si256(src_row1.add(x) as *const __m256i);
let a_lo = _mm256_unpacklo_epi8(a, _mm256_setzero_si256());
let a_hi = _mm256_unpackhi_epi8(a, _mm256_setzero_si256());
let b_lo = _mm256_unpacklo_epi8(b, _mm256_setzero_si256());
let b_hi = _mm256_unpackhi_epi8(b, _mm256_setzero_si256());
let sum_lo = _mm256_add_epi16(a_lo, b_lo);
let sum_hi = _mm256_add_epi16(a_hi, b_hi);
let one = _mm256_set1_epi16(1);
let sum_lo_rounded = _mm256_add_epi16(sum_lo, one);
let sum_hi_rounded = _mm256_add_epi16(sum_hi, one);
let result_lo = _mm256_srli_epi16(sum_lo_rounded, 1);
let result_hi = _mm256_srli_epi16(sum_hi_rounded, 1);
let result = _mm256_packus_epi16(result_lo, result_hi);
_mm256_storeu_si256(dest_row.add(x) as *mut __m256i, result);
x += 32;
}
while x < dest_width_val {
let a = *src_row0.add(x) as u16;
let b = *src_row1.add(x) as u16;
*dest_row.add(x) = ((a + b + 1) / 2) as u8;
x += 1;
}
}
}
#[target_feature(enable = "avx2")]
unsafe fn reduce_quadratic_horizontal_inplace_u8(
dest: *mut u8,
dest_pitch: NonZeroUsize,
dest_width: NonZeroUsize,
dest_height: NonZeroUsize,
) {
let dest_pitch_val = dest_pitch.get();
let dest_width_val = dest_width.get();
let dest_height_val = dest_height.get();
let w01 = _mm256_set1_epi16(0x0901);
let w23 = _mm256_set1_epi16(0x1616);
let w45 = _mm256_set1_epi16(0x0109);
let round16 = _mm256_set1_epi16(32);
for y in 0..dest_height_val {
let dest_row = dest.add(y * dest_pitch_val);
let a = *dest_row as u16;
let b = *dest_row.add(1) as u16;
let src0 = ((a + b + 1) / 2) as u8;
let middle_end = dest_width_val - 1;
let mut x = 1;
while x + 16 <= middle_end {
let p01 = _mm256_loadu_si256(dest_row.add(x * 2 - 2) as *const __m256i);
let p23 = _mm256_loadu_si256(dest_row.add(x * 2) as *const __m256i);
let p45 = _mm256_loadu_si256(dest_row.add(x * 2 + 2) as *const __m256i);
let term01 = _mm256_maddubs_epi16(p01, w01);
let term23 = _mm256_maddubs_epi16(p23, w23);
let term45 = _mm256_maddubs_epi16(p45, w45);
let sum = _mm256_add_epi16(_mm256_add_epi16(term01, term23), term45);
let reduced = _mm256_srli_epi16(_mm256_add_epi16(sum, round16), 6);
let packed = _mm256_packus_epi16(reduced, reduced);
let reordered = _mm256_permute4x64_epi64(packed, 0b0000_1000);
_mm_storeu_si128(
dest_row.add(x) as *mut __m128i,
_mm256_castsi256_si128(reordered),
);
x += 16;
}
while x < middle_end {
let mut m0 = *dest_row.add(x * 2 - 2) as u16;
let mut m1 = *dest_row.add(x * 2 - 1) as u16;
let mut m2 = *dest_row.add(x * 2) as u16;
let m3 = *dest_row.add(x * 2 + 1) as u16;
let m4 = *dest_row.add(x * 2 + 2) as u16;
let m5 = *dest_row.add(x * 2 + 3) as u16;
m2 = (m2 + m3) * 22;
m1 = (m1 + m4) * 9;
m0 += m5 + m2 + m1 + 32;
m0 >>= 6;
*dest_row.add(x) = (m0.min(255)) as u8;
x += 1;
}
*dest_row = src0;
if dest_width_val > 1 {
let x = dest_width_val - 1;
let a = *dest_row.add(x * 2) as u16;
let b = *dest_row.add(x * 2 + 1) as u16;
*dest_row.add(x) = ((a + b + 1) / 2) as u8;
}
}
}
#[target_feature(enable = "avx2")]
unsafe fn reduce_quadratic_vertical_u16(
dest: *mut u16,
src: *const u16,
dest_pitch: NonZeroUsize,
src_pitch: NonZeroUsize,
dest_width: NonZeroUsize,
dest_height: NonZeroUsize,
) {
let dest_pitch_val = dest_pitch.get();
let src_pitch_val = src_pitch.get();
let dest_width_val = dest_width.get();
let dest_height_val = dest_height.get();
let zero = _mm256_setzero_si256();
let weight_9 = _mm256_set1_epi32(9);
let weight_22 = _mm256_set1_epi32(22);
let bias_32 = _mm256_set1_epi32(32);
{
let dest_row = dest;
let src_row0 = src;
let src_row1 = src.add(src_pitch_val);
let mut x = 0;
while x + 16 <= dest_width_val {
let a = _mm256_loadu_si256(src_row0.add(x) as *const __m256i);
let b = _mm256_loadu_si256(src_row1.add(x) as *const __m256i);
let a_lo = _mm256_unpacklo_epi16(a, _mm256_setzero_si256());
let a_hi = _mm256_unpackhi_epi16(a, _mm256_setzero_si256());
let b_lo = _mm256_unpacklo_epi16(b, _mm256_setzero_si256());
let b_hi = _mm256_unpackhi_epi16(b, _mm256_setzero_si256());
let sum_lo = _mm256_add_epi32(a_lo, b_lo);
let sum_hi = _mm256_add_epi32(a_hi, b_hi);
let one = _mm256_set1_epi32(1);
let sum_lo_rounded = _mm256_add_epi32(sum_lo, one);
let sum_hi_rounded = _mm256_add_epi32(sum_hi, one);
let result_lo = _mm256_srli_epi32(sum_lo_rounded, 1);
let result_hi = _mm256_srli_epi32(sum_hi_rounded, 1);
let result = _mm256_packus_epi32(result_lo, result_hi);
_mm256_storeu_si256(dest_row.add(x) as *mut __m256i, result);
x += 16;
}
while x < dest_width_val {
let a = *src_row0.add(x) as u32;
let b = *src_row1.add(x) as u32;
*dest_row.add(x) = ((a + b + 1) / 2) as u16;
x += 1;
}
}
for y in 1..(dest_height_val - 1) {
let dest_row = dest.add(y * dest_pitch_val);
let src_row_offset = y * 2 * src_pitch_val;
let src_m2 = src.add(src_row_offset - src_pitch_val * 2);
let src_m1 = src.add(src_row_offset - src_pitch_val);
let src_p0 = src.add(src_row_offset);
let src_p1 = src.add(src_row_offset + src_pitch_val);
let src_p2 = src.add(src_row_offset + src_pitch_val * 2);
let src_p3 = src.add(src_row_offset + src_pitch_val * 3);
let mut x = 0;
while x + 16 <= dest_width_val {
let m0 = _mm256_loadu_si256(src_m2.add(x) as *const __m256i);
let m1 = _mm256_loadu_si256(src_m1.add(x) as *const __m256i);
let m2 = _mm256_loadu_si256(src_p0.add(x) as *const __m256i);
let m3 = _mm256_loadu_si256(src_p1.add(x) as *const __m256i);
let m4 = _mm256_loadu_si256(src_p2.add(x) as *const __m256i);
let m5 = _mm256_loadu_si256(src_p3.add(x) as *const __m256i);
let m0_lo = _mm256_unpacklo_epi16(m0, zero);
let m1_lo = _mm256_unpacklo_epi16(m1, zero);
let m2_lo = _mm256_unpacklo_epi16(m2, zero);
let m3_lo = _mm256_unpacklo_epi16(m3, zero);
let m4_lo = _mm256_unpacklo_epi16(m4, zero);
let m5_lo = _mm256_unpacklo_epi16(m5, zero);
let sum_05_lo = _mm256_add_epi32(m0_lo, m5_lo);
let sum_14_lo = _mm256_mullo_epi32(_mm256_add_epi32(m1_lo, m4_lo), weight_9);
let sum_23_lo = _mm256_mullo_epi32(_mm256_add_epi32(m2_lo, m3_lo), weight_22);
let result_lo = _mm256_srli_epi32(
_mm256_add_epi32(
_mm256_add_epi32(sum_05_lo, sum_14_lo),
_mm256_add_epi32(sum_23_lo, bias_32),
),
6,
);
let m0_hi = _mm256_unpackhi_epi16(m0, zero);
let m1_hi = _mm256_unpackhi_epi16(m1, zero);
let m2_hi = _mm256_unpackhi_epi16(m2, zero);
let m3_hi = _mm256_unpackhi_epi16(m3, zero);
let m4_hi = _mm256_unpackhi_epi16(m4, zero);
let m5_hi = _mm256_unpackhi_epi16(m5, zero);
let sum_05_hi = _mm256_add_epi32(m0_hi, m5_hi);
let sum_14_hi = _mm256_mullo_epi32(_mm256_add_epi32(m1_hi, m4_hi), weight_9);
let sum_23_hi = _mm256_mullo_epi32(_mm256_add_epi32(m2_hi, m3_hi), weight_22);
let result_hi = _mm256_srli_epi32(
_mm256_add_epi32(
_mm256_add_epi32(sum_05_hi, sum_14_hi),
_mm256_add_epi32(sum_23_hi, bias_32),
),
6,
);
let packed_result = _mm256_packus_epi32(result_lo, result_hi);
_mm256_storeu_si256(dest_row.add(x) as *mut __m256i, packed_result);
x += 16;
}
while x < dest_width_val {
let mut m0 = *src_m2.add(x) as u32;
let mut m1 = *src_m1.add(x) as u32;
let mut m2 = *src_p0.add(x) as u32;
let m3 = *src_p1.add(x) as u32;
let m4 = *src_p2.add(x) as u32;
let m5 = *src_p3.add(x) as u32;
m2 = (m2 + m3) * 22;
m1 = (m1 + m4) * 9;
m0 += m5 + m2 + m1 + 32;
m0 >>= 6;
*dest_row.add(x) = (m0.min(65535)) as u16;
x += 1;
}
}
if dest_height_val > 1 {
let dest_row = dest.add((dest_height_val - 1) * dest_pitch_val);
let src_row_offset = (dest_height_val - 1) * 2 * src_pitch_val;
let src_row0 = src.add(src_row_offset);
let src_row1 = src.add(src_row_offset + src_pitch_val);
let mut x = 0;
while x + 16 <= dest_width_val {
let a = _mm256_loadu_si256(src_row0.add(x) as *const __m256i);
let b = _mm256_loadu_si256(src_row1.add(x) as *const __m256i);
let a_lo = _mm256_unpacklo_epi16(a, _mm256_setzero_si256());
let a_hi = _mm256_unpackhi_epi16(a, _mm256_setzero_si256());
let b_lo = _mm256_unpacklo_epi16(b, _mm256_setzero_si256());
let b_hi = _mm256_unpackhi_epi16(b, _mm256_setzero_si256());
let sum_lo = _mm256_add_epi32(a_lo, b_lo);
let sum_hi = _mm256_add_epi32(a_hi, b_hi);
let one = _mm256_set1_epi32(1);
let sum_lo_rounded = _mm256_add_epi32(sum_lo, one);
let sum_hi_rounded = _mm256_add_epi32(sum_hi, one);
let result_lo = _mm256_srli_epi32(sum_lo_rounded, 1);
let result_hi = _mm256_srli_epi32(sum_hi_rounded, 1);
let result = _mm256_packus_epi32(result_lo, result_hi);
_mm256_storeu_si256(dest_row.add(x) as *mut __m256i, result);
x += 16;
}
while x < dest_width_val {
let a = *src_row0.add(x) as u32;
let b = *src_row1.add(x) as u32;
*dest_row.add(x) = ((a + b + 1) / 2) as u16;
x += 1;
}
}
}
#[target_feature(enable = "avx2")]
unsafe fn reduce_quadratic_horizontal_inplace_u16(
dest: *mut u16,
dest_pitch: NonZeroUsize,
dest_width: NonZeroUsize,
dest_height: NonZeroUsize,
) {
let dest_pitch_val = dest_pitch.get();
let dest_width_val = dest_width.get();
let dest_height_val = dest_height.get();
let zero = _mm256_setzero_si256();
let mask_lo16 = _mm256_set1_epi32(0x0000_FFFF);
let weight_9 = _mm256_set1_epi32(9);
let weight_22 = _mm256_set1_epi32(22);
let bias_32 = _mm256_set1_epi32(32);
for y in 0..dest_height_val {
let dest_row = dest.add(y * dest_pitch_val);
let a = *dest_row as u32;
let b = *dest_row.add(1) as u32;
let src0 = ((a + b + 1) / 2) as u16;
let middle_end = dest_width_val - 1;
let mut x = 1;
while x + 8 <= middle_end {
let v01 = _mm256_loadu_si256(dest_row.add(x * 2 - 2) as *const __m256i);
let v23 = _mm256_loadu_si256(dest_row.add(x * 2) as *const __m256i);
let v45 = _mm256_loadu_si256(dest_row.add(x * 2 + 2) as *const __m256i);
let m0 = _mm256_and_si256(v01, mask_lo16);
let m1 = _mm256_srli_epi32(v01, 16);
let m2 = _mm256_and_si256(v23, mask_lo16);
let m3 = _mm256_srli_epi32(v23, 16);
let m4 = _mm256_and_si256(v45, mask_lo16);
let m5 = _mm256_srli_epi32(v45, 16);
let sum_05 = _mm256_add_epi32(m0, m5);
let sum_14 = _mm256_mullo_epi32(_mm256_add_epi32(m1, m4), weight_9);
let sum_23 = _mm256_mullo_epi32(_mm256_add_epi32(m2, m3), weight_22);
let result = _mm256_srli_epi32(
_mm256_add_epi32(
_mm256_add_epi32(sum_05, sum_14),
_mm256_add_epi32(sum_23, bias_32),
),
6,
);
let packed = _mm256_packus_epi32(result, zero);
let lower_half = _mm256_castsi256_si128(packed);
let upper_half = _mm256_extracti128_si256(packed, 1);
let combined = _mm_unpacklo_epi64(lower_half, upper_half);
_mm_storeu_si128(dest_row.add(x) as *mut __m128i, combined);
x += 8;
}
while x < middle_end {
let mut m0 = *dest_row.add(x * 2 - 2) as u32;
let mut m1 = *dest_row.add(x * 2 - 1) as u32;
let mut m2 = *dest_row.add(x * 2) as u32;
let m3 = *dest_row.add(x * 2 + 1) as u32;
let m4 = *dest_row.add(x * 2 + 2) as u32;
let m5 = *dest_row.add(x * 2 + 3) as u32;
m2 = (m2 + m3) * 22;
m1 = (m1 + m4) * 9;
m0 += m5 + m2 + m1 + 32;
m0 >>= 6;
*dest_row.add(x) = (m0.min(65535)) as u16;
x += 1;
}
*dest_row = src0;
if dest_width_val > 1 {
let x = dest_width_val - 1;
let a = *dest_row.add(x * 2) as u32;
let b = *dest_row.add(x * 2 + 1) as u32;
*dest_row.add(x) = ((a + b + 1) / 2) as u16;
}
}
}