SIMD用于Alpha混合-如何对第N个字节进行操作?

时间:2018-12-06 02:16:23

标签: c sse simd

我正在尝试使用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);

1 个答案:

答案 0 :(得分:3)

正如我看到的gRenderSurface,我想知道您是否应该仅在GPU上混合图像,例如使用GLSL着色器,否则,从渲染表面读取内存的速度可能会非常慢。无论如何,这是我使用SSE4.1的茶,因为我在评论中没有发现完全相似的链接。

该变量使用_aa将alpha字节混洗到所有颜色通道,并通过最后的遮罩进行“一减源alpha”混合。使用AVX2,它的性能要比标量实现高出约5.7倍,而具有单独的低位和高位四字处理的SSE4.1版本要比标量实现快3.14倍(两者均使用Intel Compiler 19.0进行测量)。

How to divide 16-bit integer by 255 with using SSE?除以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 );