用于 alpha 混合的 SIMD - 如何对每个第 N 个字节进行操作?
SIMD for alpha blending - how to operate on every Nth byte?
我正在尝试使用 SIMD 优化我的 alpha 混合代码。 SSE2,特别是。
起初我希望使用 SSE2,但现在我会选择 SSE4.2,如果它更容易的话。原因是,如果我使用 SSE4.2 而不是 SSE2,我将删除大量可以 运行 此代码的旧处理器。但在这一点上我会采取妥协。
我正在将精灵传送到屏幕上。一切都是全 32 位颜色,ARGB 或 BGRA,具体取决于您阅读的方向。
我已经阅读了关于 SO 的所有其他看似相关的问题以及我在网上可以找到的所有内容,但我仍然无法完全理解这个特定概念,我将不胜感激。我已经在这里好几天了。
下面是我的代码。这段代码有效,因为它产生了我想要的视觉效果。使用 alpha 混合将位图绘制到背景缓冲区上。一切看起来都很好,符合预期。
但是您会发现,尽管它可以工作,但我的代码完全忽略了 SIMD 的要点。它一次对每个字节进行操作,就好像它是完全序列化的一样,因此与我一次只对一个像素进行操作的更传统的代码相比,该代码没有任何性能优势。使用 SIMD,我显然想同时处理 4 个像素(或一个像素的每个通道 - 128 位)。 (我通过测量每秒渲染的帧数进行分析。)
我只想 运行 每个通道的公式一次,即一次混合所有红色通道,一次混合所有绿色通道,一次混合所有蓝色通道,等等一次的 alpha 通道。或者,一次像素之一的每个通道 (RGBA)。
然后我应该开始看到 SIMD 的全部好处了。
我觉得我可能需要用面具做一些事情,但我没有尝试过。
非常感谢您的帮助。
(这是内部循环。它只处理 4 个像素。我把它放在一个循环中,我用 XPixel+=4 一次迭代 4 个像素。)
__m128i BitmapQuadPixel = _mm_load_si128((uint32_t*)Bitmap->Memory + BitmapOffset);
__m128i BackgroundQuadPixel = _mm_load_si128((uint32_t*)gRenderSurface.Memory + MemoryOffset);;
__m128i BlendedQuadPixel;
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// R G B A R G B A R G B A R G B A
// This is the red component of the first pixel.
BlendedQuadPixel.m128i_u8[0] = BitmapQuadPixel.m128i_u8[0] * BitmapQuadPixel.m128i_u8[3] / 255 + BackgroundQuadPixel.m128i_u8[0] * (255 - BitmapQuadPixel.m128i_u8[3]) / 255;
// This is the green component of the first pixel.
BlendedQuadPixel.m128i_u8[1] = BitmapQuadPixel.m128i_u8[1] * BitmapQuadPixel.m128i_u8[3] / 255 + BackgroundQuadPixel.m128i_u8[1] * (255 - BitmapQuadPixel.m128i_u8[3]) / 255;
// And so on...
BlendedQuadPixel.m128i_u8[2] = BitmapQuadPixel.m128i_u8[2] * BitmapQuadPixel.m128i_u8[3] / 255 + BackgroundQuadPixel.m128i_u8[2] * (255 - BitmapQuadPixel.m128i_u8[3]) / 255;
BlendedQuadPixel.m128i_u8[4] = BitmapQuadPixel.m128i_u8[4] * BitmapQuadPixel.m128i_u8[7] / 255 + BackgroundQuadPixel.m128i_u8[4] * (255 - BitmapQuadPixel.m128i_u8[7]) / 255;
BlendedQuadPixel.m128i_u8[5] = BitmapQuadPixel.m128i_u8[5] * BitmapQuadPixel.m128i_u8[7] / 255 + BackgroundQuadPixel.m128i_u8[5] * (255 - BitmapQuadPixel.m128i_u8[7]) / 255;
BlendedQuadPixel.m128i_u8[6] = BitmapQuadPixel.m128i_u8[6] * BitmapQuadPixel.m128i_u8[7] / 255 + BackgroundQuadPixel.m128i_u8[6] * (255 - BitmapQuadPixel.m128i_u8[7]) / 255;
BlendedQuadPixel.m128i_u8[8] = BitmapQuadPixel.m128i_u8[8] * BitmapQuadPixel.m128i_u8[11] / 255 + BackgroundQuadPixel.m128i_u8[8] * (255 - BitmapQuadPixel.m128i_u8[11]) / 255;
BlendedQuadPixel.m128i_u8[9] = BitmapQuadPixel.m128i_u8[9] * BitmapQuadPixel.m128i_u8[11] / 255 + BackgroundQuadPixel.m128i_u8[9] * (255 - BitmapQuadPixel.m128i_u8[11]) / 255;
BlendedQuadPixel.m128i_u8[10] = BitmapQuadPixel.m128i_u8[10] * BitmapQuadPixel.m128i_u8[11] / 255 + BackgroundQuadPixel.m128i_u8[10] * (255 - BitmapQuadPixel.m128i_u8[11]) / 255;
BlendedQuadPixel.m128i_u8[12] = BitmapQuadPixel.m128i_u8[12] * BitmapQuadPixel.m128i_u8[15] / 255 + BackgroundQuadPixel.m128i_u8[12] * (255 - BitmapQuadPixel.m128i_u8[15]) / 255;
BlendedQuadPixel.m128i_u8[13] = BitmapQuadPixel.m128i_u8[13] * BitmapQuadPixel.m128i_u8[15] / 255 + BackgroundQuadPixel.m128i_u8[13] * (255 - BitmapQuadPixel.m128i_u8[15]) / 255;
BlendedQuadPixel.m128i_u8[14] = BitmapQuadPixel.m128i_u8[14] * BitmapQuadPixel.m128i_u8[15] / 255 + BackgroundQuadPixel.m128i_u8[14] * (255 - BitmapQuadPixel.m128i_u8[15]) / 255;
_mm_store_si128((uint32_t*)gRenderSurface.Memory + MemoryOffset, BlendedQuadPixel);
据我所知 gRenderSurface
,我想知道您是否应该只在 GPU 上混合图像,例如,使用 GLSL 着色器,或者如果不这样做,从渲染表面读回内存可能会非常慢。无论如何,这是我使用 SSE4.1 的一杯茶,因为我没有在评论中找到完全相似的链接。
这个使用 _aa
将 alpha 字节混洗到所有颜色通道,并通过最终遮罩进行 "one minus source alpha" 混合。使用 AVX2,它比标量实现高出约 5.7 倍,而具有单独的低和高四字处理的 SSE4.1 版本比标量实现快约 3.14 倍(均使用英特尔编译器 19.0 测量)。
从
除以 255
const __m128i _aa = _mm_set_epi8( 15,15,15,15, 11,11,11,11, 7,7,7,7, 3,3,3,3 );
const __m128i _mask1 = _mm_set_epi16(-1,0,0,0, -1,0,0,0);
const __m128i _mask2 = _mm_set_epi16(0,-1,-1,-1, 0,-1,-1,-1);
const __m128i _v255 = _mm_set1_epi8( -1 );
const __m128i _v1 = _mm_set1_epi16( 1 );
const int xmax = 4*source.cols-15;
for ( int y=0;y<source.rows;++y )
{
// OpenCV CV_8UC4 input
const unsigned char * pS = source.ptr<unsigned char>( y );
const unsigned char * pD = dest.ptr<unsigned char>( y );
unsigned char *pOut = out.ptr<unsigned char>( y );
for ( int x=0;x<xmax;x+=16 )
{
__m128i _src = _mm_loadu_si128( (__m128i*)( pS+x ) );
__m128i _src_a = _mm_shuffle_epi8( _src, _aa );
__m128i _dst = _mm_loadu_si128( (__m128i*)( pD+x ) );
__m128i _dst_a = _mm_shuffle_epi8( _dst, _aa );
__m128i _one_minus_src_a = _mm_subs_epu8( _v255, _src_a );
__m128i _s_a = _mm_cvtepu8_epi16( _src_a );
__m128i _s = _mm_cvtepu8_epi16( _src );
__m128i _d = _mm_cvtepu8_epi16( _dst );
__m128i _d_a = _mm_cvtepu8_epi16( _one_minus_src_a );
__m128i _out = _mm_adds_epu16( _mm_mullo_epi16( _s, _s_a ), _mm_mullo_epi16( _d, _d_a ) );
_out = _mm_srli_epi16( _mm_adds_epu16( _mm_adds_epu16( _v1, _out ), _mm_srli_epi16( _out, 8 ) ), 8 );
_out = _mm_or_si128( _mm_and_si128(_out,_mask2), _mm_and_si128( _mm_adds_epu16(_s_a, _mm_cvtepu8_epi16(_dst_a)),_mask1) );
__m128i _out2;
// compute _out2 using high quadword of of the _src and _dst
//...
__m128i _ret = _mm_packus_epi16( _out, _out2 );
_mm_storeu_si128( (__m128i*)(pOut+x), _ret );
我正在尝试使用 SIMD 优化我的 alpha 混合代码。 SSE2,特别是。
起初我希望使用 SSE2,但现在我会选择 SSE4.2,如果它更容易的话。原因是,如果我使用 SSE4.2 而不是 SSE2,我将删除大量可以 运行 此代码的旧处理器。但在这一点上我会采取妥协。
我正在将精灵传送到屏幕上。一切都是全 32 位颜色,ARGB 或 BGRA,具体取决于您阅读的方向。
我已经阅读了关于 SO 的所有其他看似相关的问题以及我在网上可以找到的所有内容,但我仍然无法完全理解这个特定概念,我将不胜感激。我已经在这里好几天了。
下面是我的代码。这段代码有效,因为它产生了我想要的视觉效果。使用 alpha 混合将位图绘制到背景缓冲区上。一切看起来都很好,符合预期。
但是您会发现,尽管它可以工作,但我的代码完全忽略了 SIMD 的要点。它一次对每个字节进行操作,就好像它是完全序列化的一样,因此与我一次只对一个像素进行操作的更传统的代码相比,该代码没有任何性能优势。使用 SIMD,我显然想同时处理 4 个像素(或一个像素的每个通道 - 128 位)。 (我通过测量每秒渲染的帧数进行分析。)
我只想 运行 每个通道的公式一次,即一次混合所有红色通道,一次混合所有绿色通道,一次混合所有蓝色通道,等等一次的 alpha 通道。或者,一次像素之一的每个通道 (RGBA)。
然后我应该开始看到 SIMD 的全部好处了。
我觉得我可能需要用面具做一些事情,但我没有尝试过。
非常感谢您的帮助。
(这是内部循环。它只处理 4 个像素。我把它放在一个循环中,我用 XPixel+=4 一次迭代 4 个像素。)
__m128i BitmapQuadPixel = _mm_load_si128((uint32_t*)Bitmap->Memory + BitmapOffset);
__m128i BackgroundQuadPixel = _mm_load_si128((uint32_t*)gRenderSurface.Memory + MemoryOffset);;
__m128i BlendedQuadPixel;
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// R G B A R G B A R G B A R G B A
// This is the red component of the first pixel.
BlendedQuadPixel.m128i_u8[0] = BitmapQuadPixel.m128i_u8[0] * BitmapQuadPixel.m128i_u8[3] / 255 + BackgroundQuadPixel.m128i_u8[0] * (255 - BitmapQuadPixel.m128i_u8[3]) / 255;
// This is the green component of the first pixel.
BlendedQuadPixel.m128i_u8[1] = BitmapQuadPixel.m128i_u8[1] * BitmapQuadPixel.m128i_u8[3] / 255 + BackgroundQuadPixel.m128i_u8[1] * (255 - BitmapQuadPixel.m128i_u8[3]) / 255;
// And so on...
BlendedQuadPixel.m128i_u8[2] = BitmapQuadPixel.m128i_u8[2] * BitmapQuadPixel.m128i_u8[3] / 255 + BackgroundQuadPixel.m128i_u8[2] * (255 - BitmapQuadPixel.m128i_u8[3]) / 255;
BlendedQuadPixel.m128i_u8[4] = BitmapQuadPixel.m128i_u8[4] * BitmapQuadPixel.m128i_u8[7] / 255 + BackgroundQuadPixel.m128i_u8[4] * (255 - BitmapQuadPixel.m128i_u8[7]) / 255;
BlendedQuadPixel.m128i_u8[5] = BitmapQuadPixel.m128i_u8[5] * BitmapQuadPixel.m128i_u8[7] / 255 + BackgroundQuadPixel.m128i_u8[5] * (255 - BitmapQuadPixel.m128i_u8[7]) / 255;
BlendedQuadPixel.m128i_u8[6] = BitmapQuadPixel.m128i_u8[6] * BitmapQuadPixel.m128i_u8[7] / 255 + BackgroundQuadPixel.m128i_u8[6] * (255 - BitmapQuadPixel.m128i_u8[7]) / 255;
BlendedQuadPixel.m128i_u8[8] = BitmapQuadPixel.m128i_u8[8] * BitmapQuadPixel.m128i_u8[11] / 255 + BackgroundQuadPixel.m128i_u8[8] * (255 - BitmapQuadPixel.m128i_u8[11]) / 255;
BlendedQuadPixel.m128i_u8[9] = BitmapQuadPixel.m128i_u8[9] * BitmapQuadPixel.m128i_u8[11] / 255 + BackgroundQuadPixel.m128i_u8[9] * (255 - BitmapQuadPixel.m128i_u8[11]) / 255;
BlendedQuadPixel.m128i_u8[10] = BitmapQuadPixel.m128i_u8[10] * BitmapQuadPixel.m128i_u8[11] / 255 + BackgroundQuadPixel.m128i_u8[10] * (255 - BitmapQuadPixel.m128i_u8[11]) / 255;
BlendedQuadPixel.m128i_u8[12] = BitmapQuadPixel.m128i_u8[12] * BitmapQuadPixel.m128i_u8[15] / 255 + BackgroundQuadPixel.m128i_u8[12] * (255 - BitmapQuadPixel.m128i_u8[15]) / 255;
BlendedQuadPixel.m128i_u8[13] = BitmapQuadPixel.m128i_u8[13] * BitmapQuadPixel.m128i_u8[15] / 255 + BackgroundQuadPixel.m128i_u8[13] * (255 - BitmapQuadPixel.m128i_u8[15]) / 255;
BlendedQuadPixel.m128i_u8[14] = BitmapQuadPixel.m128i_u8[14] * BitmapQuadPixel.m128i_u8[15] / 255 + BackgroundQuadPixel.m128i_u8[14] * (255 - BitmapQuadPixel.m128i_u8[15]) / 255;
_mm_store_si128((uint32_t*)gRenderSurface.Memory + MemoryOffset, BlendedQuadPixel);
据我所知 gRenderSurface
,我想知道您是否应该只在 GPU 上混合图像,例如,使用 GLSL 着色器,或者如果不这样做,从渲染表面读回内存可能会非常慢。无论如何,这是我使用 SSE4.1 的一杯茶,因为我没有在评论中找到完全相似的链接。
这个使用 _aa
将 alpha 字节混洗到所有颜色通道,并通过最终遮罩进行 "one minus source alpha" 混合。使用 AVX2,它比标量实现高出约 5.7 倍,而具有单独的低和高四字处理的 SSE4.1 版本比标量实现快约 3.14 倍(均使用英特尔编译器 19.0 测量)。
从
const __m128i _aa = _mm_set_epi8( 15,15,15,15, 11,11,11,11, 7,7,7,7, 3,3,3,3 );
const __m128i _mask1 = _mm_set_epi16(-1,0,0,0, -1,0,0,0);
const __m128i _mask2 = _mm_set_epi16(0,-1,-1,-1, 0,-1,-1,-1);
const __m128i _v255 = _mm_set1_epi8( -1 );
const __m128i _v1 = _mm_set1_epi16( 1 );
const int xmax = 4*source.cols-15;
for ( int y=0;y<source.rows;++y )
{
// OpenCV CV_8UC4 input
const unsigned char * pS = source.ptr<unsigned char>( y );
const unsigned char * pD = dest.ptr<unsigned char>( y );
unsigned char *pOut = out.ptr<unsigned char>( y );
for ( int x=0;x<xmax;x+=16 )
{
__m128i _src = _mm_loadu_si128( (__m128i*)( pS+x ) );
__m128i _src_a = _mm_shuffle_epi8( _src, _aa );
__m128i _dst = _mm_loadu_si128( (__m128i*)( pD+x ) );
__m128i _dst_a = _mm_shuffle_epi8( _dst, _aa );
__m128i _one_minus_src_a = _mm_subs_epu8( _v255, _src_a );
__m128i _s_a = _mm_cvtepu8_epi16( _src_a );
__m128i _s = _mm_cvtepu8_epi16( _src );
__m128i _d = _mm_cvtepu8_epi16( _dst );
__m128i _d_a = _mm_cvtepu8_epi16( _one_minus_src_a );
__m128i _out = _mm_adds_epu16( _mm_mullo_epi16( _s, _s_a ), _mm_mullo_epi16( _d, _d_a ) );
_out = _mm_srli_epi16( _mm_adds_epu16( _mm_adds_epu16( _v1, _out ), _mm_srli_epi16( _out, 8 ) ), 8 );
_out = _mm_or_si128( _mm_and_si128(_out,_mask2), _mm_and_si128( _mm_adds_epu16(_s_a, _mm_cvtepu8_epi16(_dst_a)),_mask1) );
__m128i _out2;
// compute _out2 using high quadword of of the _src and _dst
//...
__m128i _ret = _mm_packus_epi16( _out, _out2 );
_mm_storeu_si128( (__m128i*)(pOut+x), _ret );