SIMD для альфа-смешивания - как работать с каждым N-м байтом? - PullRequest
0 голосов
/ 06 декабря 2018

Я пытаюсь оптимизировать мой альфа-код смешивания с помощью SIMD.SSE2, в частности.

Сначала я надеялся на SSE2, но на этом этапе я согласился бы на SSE4.2, если это будет проще.Причина в том, что если я использую SSE4.2 вместо SSE2, я исключаю значительное количество старых процессоров, которые могут выполнять этот код.Но в этот момент я пошел бы на компромисс.

Я блефовал спрайтом на экране.Все в полном 32-битном цвете, ARGB или BGRA, в зависимости от того, в каком направлении вы его читаете.

Я прочитал все другие, казалось бы, связанные вопросы по SO и все, что я мог найти в Интернете, но до сих пор не сделалсмог полностью обернуть мой мозг вокруг этой единственной концепции, и я был бы признателен за некоторую помощь.Я занимаюсь этим уже несколько дней.

Ниже мой код.Этот код работает, поскольку он производит визуальный эффект, который я хочу.Растровое изображение рисуется в фоновом буфере с альфа-смешением.Все выглядит хорошо и, как и ожидалось.

Но вы увидите, что, несмотря на то, что он работает, мой код полностью пропускает смысл SIMD.Он работает с каждым байтом по одному, точно так же, как если бы он был полностью сериализован, и поэтому код не видит преимущества в производительности по сравнению с моим более традиционным кодом, который работает только с одним пикселем за раз.С SIMD я, очевидно, хочу работать с 4 пикселями (или с каждым каналом в один пиксель - 128 бит) одновременно, параллельно.(Я профилирую, измеряя количество кадров, отображаемых в секунду.)

Я хочу просто запустить формулу один раз для каждого канала, т. Е. Смешать все красные каналы сразу, все зеленые каналы сразу, всесинего канала сразу и всего альфа-канала сразу.Или, в качестве альтернативы, каждый канал (RGBA) одного из пикселей одновременно.

Тогда я должен начать видеть все преимущества SIMD.

Мне кажется, что мне, вероятно, нужно кое-что сделатьс масками, но ничто из того, что я пробовал, не привело меня туда.

Я был бы очень признателен за помощь.

(Это внутренний цикл. Он обрабатывает только 4 пикселя. Я помещаю это внутрьцикла, где я перебираю более 4 пикселей за раз с XPixel + = 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 голосов
/ 10 декабря 2018

Как я вижу gRenderSurface, мне интересно, следует ли вам просто смешивать изображения на GPU, например, используя шейдер GLSL, или, если нет, считывание памяти с поверхности рендеринга может быть очень медленным.В любом случае, вот моя чашка чая с использованием SSE4.1, так как я не нашел полностью похожих ссылок в комментариях.

Этот перетасовывает альфа-байты на все цветовые каналы, используя _aa, и делает "один минус источник альфа«Смешивание по окончательной маскировке.С AVX2 он превосходит скалярную реализацию в ~ 5,7 раза, в то время как версия SSE4.1 с раздельной обработкой низкого и высокого четырехсловных слов примерно в 3,14 раза быстрее, чем скалярная реализация (обе измерены с помощью Intel Compiler 19.0).

Делениена 255 от Как разделить 16-битное целое на 255 с помощью SSE?

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 );
...