如何用AVX512指令实现Alpha混合图像叠加?及性能优化疑问
ARGB图像Alpha混合的AVX512优化问题
我有两张以ARGB字节数组形式存储的图像A和B:
Image A: [a0, r0, g0, b0, a1, r1, g1, b1, ...] Image B: [a0, r0, g0, b0, a1, r1, g1, b1, ...]
我希望使用Alpha混合公式将图像B叠加到图像A之上。请问如何使用可批量处理多像素的AVX512指令实现该功能?若使用256替代255进行计算以简化实现,我也可以接受。
补充说明:
我基于StackOverflow的一篇回答实现了该功能,但发现AVX512版本的代码比逐像素处理的非AVX512代码运行速度更慢。我尝试移除了lazy_static!(因推测其使用了锁结构)并将常量传入函数,但性能仍未提升。难道这个问题并不适合用AVX512解决?看起来应该是适合的。
我的AVX512实现代码:
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] unsafe fn overlay_row_avx512(this_chunk: &mut [u8], image_chunk: &[u8]) { use std::arch::x86_64::*; let this_ptr = this_chunk.as_mut_ptr() as *mut i8; let image_ptr = image_chunk.as_ptr() as *const i8; let this_argb = _mm512_loadu_epi8(this_ptr); let image_argb = _mm512_loadu_epi8(image_ptr); // Pick out the upper 256-bits and calculate inv_alpha. let this_upper = _mm512_shuffle_epi8(this_argb, *UPPER_TO_U16); let image_upper = _mm512_shuffle_epi8(image_argb, *UPPER_TO_U16); let alpha_upper = _mm512_shuffle_epi8(image_argb, *UPPER_ALPHA_TO_U16); let inv_alpha_upper = _mm512_subs_epu8(*U8_MAX_VALUE, alpha_upper); // Apply the blend function and store the result in blended_upper_u8. let this_blended_upper = _mm512_mullo_epi16(this_upper, inv_alpha_upper); let image_blended_upper = _mm512_mullo_epi16(image_upper, alpha_upper); // TODO: premultiply alpha let blended_upper = _mm512_add_epi16(this_blended_upper, image_blended_upper); let blended_upper_u8 = _mm512_shuffle_epi8(blended_upper, *UPPER_U16_TO_U8); // Repeat for the lower 256-bits. let this_lower = _mm512_shuffle_epi8(this_argb, *LOWER_TO_U16); let image_lower = _mm512_shuffle_epi8(image_argb, *LOWER_TO_U16); let alpha_lower = _mm512_shuffle_epi8(image_argb, *LOWER_ALPHA_TO_U16); let inv_alpha_lower = _mm512_subs_epu8(*U8_MAX_VALUE, alpha_lower); let this_blended_lower = _mm512_mullo_epi16(this_lower, inv_alpha_lower); let image_blended_lower = _mm512_mullo_epi16(image_lower, alpha_lower); // TODO: premultiply alpha let blended_lower = _mm512_add_epi16(this_blended_lower, image_blended_lower); let blended_lower_u8 = _mm512_add_epi16(blended_lower, *LOWER_U16_TO_U8); // OR together the upper and lower 256-bits. let blended = _mm512_or_si512(blended_upper_u8, blended_lower_u8); _mm512_storeu_epi8(this_ptr, blended); } lazy_static! { static ref U8_MAX_VALUE: __m512i = unsafe { _mm512_set1_epi8(-1) }; static ref UPPER_TO_U16: __m512i = unsafe { _mm512_set_epi8( X, 63, X, 62, X, 61, X, 60, X, 59, X, 58, X, 57, X, 56, X, 55, X, 54, X, 53, X, 52, X, 51, X, 50, X, 49, X, 48, X, 47, X, 46, X, 45, X, 44, X, 43, X, 42, X, 41, X, 40, X, 39, X, 38, X, 37, X, 36, X, 35, X, 34, X, 33, X, 32, ) }; static ref LOWER_TO_U16: __m512i = unsafe { _mm512_set_epi8( X, 31, X, 30, X, 29, X, 28, X, 27, X, 26, X, 25, X, 24, X, 23, X, 22, X, 21, X, 20, X, 19, X, 18, X, 17, X, 16, X, 15, X, 14, X, 13, X, 12, X, 11, X, 10, X, 9, X, 8, X, 7, X, 6, X, 5, X, 4, X, 3, X, 2, X, 1, X, 0, ) }; static ref UPPER_ALPHA_TO_U16: __m512i = unsafe { _mm512_set_epi8( X, 63, X, 63, X, 63, X, 63, X, 59, X, 59, X, 59, X, 59, X, 55, X, 55, X, 55, X, 55, X, 51, X, 51, X, 51, X, 51, X, 47, X, 47, X, 47, X, 47, X, 43, X, 43, X, 43, X, 43, X, 39, X, 39, X, 39, X, 39, X, 35, X, 35, X, 35, X, 35, ) }; static ref LOWER_ALPHA_TO_U16: __m512i = unsafe { _mm512_set_epi8( X, 31, X, 31, X, 31, X, 31, X, 27, X, 27, X, 27, X, 27, X, 23, X, 23, X, 23, X, 23, X, 19, X, 19, X, 19, X, 19, X, 15, X, 15, X, 15, X, 15, X, 11, X, 11, X, 11, X, 11, X, 7, X, 7, X, 7, X, 7, X, 3, X, 3, X, 3, X, 3, ) }; // Pick out the upper 8-bits of each 16-bit u16. // This effectively divides by 256. static ref UPPER_U16_TO_U8: __m512i = unsafe { _mm512_set_epi8( 63, X, 62, X, 61, X, 60, X, 59, X, 58, X, 57, X, 56, X, 55, X, 54, X, 53, X, 52, X, 51, X, 50, X, 49, X, 48, X, 47, X, 46, X, 45, X, 44, X, 43, X, 42, X, 41, X, 40, X, 39, X, 38, X, 37, X, 36, X, 35, X, 34, X, 33, X, 32, X, ) }; static ref LOWER_U16_TO_U8: __m512i = unsafe { _mm512_set_epi8( 31, X, 30, X, 29, X, 28, X, 27, X, 26, X, 25, X, 24, X, 23, X, 22, X, 21, X, 20, X, 19, X, 18, X, 17, X, 16, X, 15, X, 14, X, 13, X, 12, X, 11, X, 10, X, 9, X, 8, X, 7, X, 6, X, 5, X, 4, X, 3, X, 2, X, 1, X, 0, X, ) }; }
作为对比,我的逐像素处理代码:
// A chunk is just 4 bytes in this case rather than 64 bytes. fn overlay_row_without_simd(this_chunk: &mut [u8], image_chunk: &[u8]) { let alpha = image_chunk[0] as u32; let inv_alpha = 255 - alpha; this_chunk[1] = ((this_chunk[1] as u32 * inv_alpha + image_chunk[1] as u32 * alpha) / 255) as u8; this_chunk[2] = ((this_chunk[2] as u32 * inv_alpha + image_chunk[2] as u32 * alpha) / 255) as u8; this_chunk[3] = ((this_chunk[3] as u32 * inv_alpha + image_chunk[3] as u32 * alpha) / 255) as u8; }
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

