Подтвердить что ты не робот

Быстрый 24-битный массив → 32-битное преобразование массива?

Краткое описание:

У меня есть массив из 24-битных значений. Любое предложение о том, как быстро расширить отдельные 24-битные элементы массива в 32-битные элементы?

Детали:

Я обрабатываю входящие видеофрагменты в реальном времени, используя пиксельные шейдеры в DirectX 10. Блокирование камней состоит в том, что мои кадры поступают с захватного оборудования с 24-битными пикселями (либо как изображения YUV, либо RGB), но DX10 принимает 32-битные пиксельные текстуры. Таким образом, я должен расширить 24-битные значения до 32 бит, прежде чем я смогу загрузить их в графический процессор.

Мне действительно все равно, что я установил оставшиеся 8 бит или где входящие 24-биты находятся в этом 32-битном значении - я могу исправить все это в пиксельном шейдере. Но мне нужно сделать преобразование с 24-бит до 32-бит очень быстро.

Я не очень хорошо знаком с SIMD SSE-операциями, но из моего беглого взгляда не похоже, что я могу сделать расширение, используя их, учитывая, что мои чтения и записи не имеют одинакового размера. Какие-либо предложения? Или я придерживаюсь последовательного массирования этого набора данных?

Это кажется настолько глупым - я использую пиксельные шейдеры для parallelism, но перед этим я должен выполнить последовательную операцию на пиксель. Я должен упустить что-то очевидное...

4b9b3361

Ответ 1

Код ниже должен быть довольно быстрым. Он копирует 4 пикселя на каждой итерации, используя только 32-разрядные инструкции чтения/записи. Указатели источника и назначения должны быть выровнены с 32 битами.

uint32_t *src = ...;
uint32_t *dst = ...;

for (int i=0; i<num_pixels; i+=4) {
    uint32_t sa = src[0];
    uint32_t sb = src[1];
    uint32_t sc = src[2];

    dst[i+0] = sa;
    dst[i+1] = (sa>>24) | (sb<<8);
    dst[i+2] = (sb>>16) | (sc<<16);
    dst[i+3] = sc>>8;

    src += 3;
}

Edit:

Вот как это сделать, используя инструкции SSSE3 PSHUFB и PALIGNR. Код написан с использованием встроенных компиляторов, но при необходимости его сложно перевести на сборку. Он копирует 16 пикселей на каждой итерации. Указатели источника и адресата должны быть выровнены с 16 байтами, или это будет ошибкой. Если они не выровнены, вы можете заставить его работать, заменив _mm_load_si128 на _mm_loadu_si128 и _mm_store_si128 на _mm_storeu_si128, но это будет медленнее.

#include <emmintrin.h>
#include <tmmintrin.h>

__m128i *src = ...;
__m128i *dst = ...;
__m128i mask = _mm_setr_epi8(0,1,2,-1, 3,4,5,-1, 6,7,8,-1, 9,10,11,-1);

for (int i=0; i<num_pixels; i+=16) {
    __m128i sa = _mm_load_si128(src);
    __m128i sb = _mm_load_si128(src+1);
    __m128i sc = _mm_load_si128(src+2);

    __m128i val = _mm_shuffle_epi8(sa, mask);
    _mm_store_si128(dst, val);
    val = _mm_shuffle_epi8(_mm_alignr_epi8(sb, sa, 12), mask);
    _mm_store_si128(dst+1, val);
    val = _mm_shuffle_epi8(_mm_alignr_epi8(sc, sb, 8), mask);
    _mm_store_si128(dst+2, val);
    val = _mm_shuffle_epi8(_mm_alignr_epi8(sc, sc, 4), mask);
    _mm_store_si128(dst+3, val);

    src += 3;
    dst += 4;
}

SSSE3 (не путать с SSE3) потребует относительно новый процессор: Core 2 или более новый, и я считаю, что AMD еще не поддерживает его. Выполнение этого с помощью инструкций SSE2 займет гораздо больше операций и может не стоить того.

Ответ 2

SSE3 является удивительным, но для тех, кто не может использовать его по какой-либо причине, здесь преобразование в ассемблере x86, оптимизированное вручную по-настоящему. Для полноты я передаю преобразование в обоих направлениях: RGB32- > RGB24 и RGB24- > RGB32.

Обратите внимание, что код Interjay C оставляет мусор в MSB (альфа-канал) целевых пикселей. Это может не иметь значения в некоторых приложениях, но это важно для меня, поэтому мой код RGB24- > RGB32 заставляет MSB равняться нулю. Аналогично, мой RGB32- > RGB24 код игнорирует MSB; это позволяет избежать вывода мусора, если исходные данные имеют ненулевой альфа-канал. Эти функции почти ничего не стоят с точки зрения производительности, как проверено в тестах.

Для RGB32- > RGB24 я смог победить оптимизатор VС++ примерно на 20%. Для RGB24- > RGB32 выигрыш был незначителен. Бенчмаркинг проводился на i5 2500K. Я опускаю здесь бенчмаркинг, но если кто-то захочет, я его предоставил. Самая важная оптимизация - как можно скорее ударить указатель источника (см. Комментарий ASAP). Мое лучшее предположение, что это увеличивает parallelism, позволяя конвейеру команд предварительно запрограммировать выборку раньше. Помимо этого, я просто переупорядочил некоторые инструкции для уменьшения зависимостей и перекрытия доступа к памяти с бит-биением.

void ConvRGB32ToRGB24(const UINT *Src, UINT *Dst, UINT Pixels)
{
#if !USE_ASM
    for (UINT i = 0; i < Pixels; i += 4) {
        UINT    sa = Src[i + 0] & 0xffffff;
        UINT    sb = Src[i + 1] & 0xffffff;
        UINT    sc = Src[i + 2] & 0xffffff;
        UINT    sd = Src[i + 3];
        Dst[0] = sa | (sb << 24);
        Dst[1] = (sb >> 8) | (sc << 16);
        Dst[2] = (sc >> 16) | (sd << 8);
        Dst += 3;
    }
#else
    __asm {
        mov     ecx, Pixels
        shr     ecx, 2              // 4 pixels at once
        jz      ConvRGB32ToRGB24_$2
        mov     esi, Src
        mov     edi, Dst
ConvRGB32ToRGB24_$1:
        mov     ebx, [esi + 4]      // sb
        and     ebx, 0ffffffh       // sb & 0xffffff
        mov     eax, [esi + 0]      // sa
        and     eax, 0ffffffh       // sa & 0xffffff
        mov     edx, ebx            // copy sb
        shl     ebx, 24             // sb << 24
        or      eax, ebx            // sa | (sb << 24)
        mov     [edi + 0], eax      // Dst[0]
        shr     edx, 8              // sb >> 8
        mov     eax, [esi + 8]      // sc
        and     eax, 0ffffffh       // sc & 0xffffff
        mov     ebx, eax            // copy sc
        shl     eax, 16             // sc << 16
        or      eax, edx            // (sb >> 8) | (sc << 16)
        mov     [edi + 4], eax      // Dst[1]
        shr     ebx, 16             // sc >> 16
        mov     eax, [esi + 12]     // sd
        add     esi, 16             // Src += 4 (ASAP)
        shl     eax, 8              // sd << 8
        or      eax, ebx            // (sc >> 16) | (sd << 8)
        mov     [edi + 8], eax      // Dst[2]
        add     edi, 12             // Dst += 3
        dec     ecx
        jnz     SHORT ConvRGB32ToRGB24_$1
ConvRGB32ToRGB24_$2:
    }
#endif
}

void ConvRGB24ToRGB32(const UINT *Src, UINT *Dst, UINT Pixels)
{
#if !USE_ASM
    for (UINT i = 0; i < Pixels; i += 4) {
        UINT    sa = Src[0];
        UINT    sb = Src[1];
        UINT    sc = Src[2];
        Dst[i + 0] = sa & 0xffffff;
        Dst[i + 1] = ((sa >> 24) | (sb << 8)) & 0xffffff;
        Dst[i + 2] = ((sb >> 16) | (sc << 16)) & 0xffffff;
        Dst[i + 3] = sc >> 8;
        Src += 3;
    }
#else
    __asm {
        mov     ecx, Pixels
        shr     ecx, 2              // 4 pixels at once
        jz      SHORT ConvRGB24ToRGB32_$2
        mov     esi, Src
        mov     edi, Dst
        push    ebp
ConvRGB24ToRGB32_$1:
        mov     ebx, [esi + 4]      // sb
        mov     edx, ebx            // copy sb
        mov     eax, [esi + 0]      // sa
        mov     ebp, eax            // copy sa
        and     ebx, 0ffffh         // sb & 0xffff
        shl     ebx, 8              // (sb & 0xffff) << 8
        and     eax, 0ffffffh       // sa & 0xffffff
        mov     [edi + 0], eax      // Dst[0]
        shr     ebp, 24             // sa >> 24
        or      ebx, ebp            // (sa >> 24) | ((sb & 0xffff) << 8)
        mov     [edi + 4], ebx      // Dst[1]
        shr     edx, 16             // sb >> 16
        mov     eax, [esi + 8]      // sc
        add     esi, 12             // Src += 12 (ASAP)
        mov     ebx, eax            // copy sc
        and     eax, 0ffh           // sc & 0xff
        shl     eax, 16             // (sc & 0xff) << 16
        or      eax, edx            // (sb >> 16) | ((sc & 0xff) << 16)
        mov     [edi + 8], eax      // Dst[2]
        shr     ebx, 8              // sc >> 8
        mov     [edi + 12], ebx     // Dst[3]
        add     edi, 16             // Dst += 16
        dec     ecx
        jnz     SHORT ConvRGB24ToRGB32_$1
        pop     ebp
ConvRGB24ToRGB32_$2:
    }
#endif
}

И пока мы находимся в этом, вот те же самые преобразования в сборке SSE3. Это работает только в том случае, если у вас есть ассемблер (FASM является бесплатным) и имеет процессор, поддерживающий SSE3 (скорее всего, но лучше проверить). Обратите внимание, что встроенные функции не обязательно выводят что-то такое эффективное, это полностью зависит от используемых вами инструментов и от какой платформы вы компилируете. Здесь это просто: то, что вы видите, это то, что вы получаете. Этот код генерирует тот же результат, что и код x86 выше, и он примерно в 1,5 раза быстрее (на i5 2500K).

format MS COFF

section '.text' code readable executable

public _ConvRGB32ToRGB24SSE3

;   ebp + 8     Src (*RGB32, 16-byte aligned)
;   ebp + 12    Dst (*RGB24, 16-byte aligned)
;   ebp + 16    Pixels

_ConvRGB32ToRGB24SSE3:
    push    ebp
    mov     ebp, esp
    mov     eax, [ebp + 8]
    mov     edx, [ebp + 12]
    mov     ecx, [ebp + 16]
    shr     ecx, 4
    jz      done1
    movupd  xmm7, [mask1]

top1:
    movupd  xmm0, [eax + 0]     ; sa = Src[0]
    pshufb  xmm0, xmm7          ; sa = _mm_shuffle_epi8(sa, mask)
    movupd  xmm1, [eax + 16]    ; sb = Src[1]
    pshufb  xmm1, xmm7          ; sb = _mm_shuffle_epi8(sb, mask)
    movupd  xmm2, xmm1          ; sb1 = sb
    pslldq  xmm1, 12            ; sb = _mm_slli_si128(sb, 12)
    por     xmm0, xmm1          ; sa = _mm_or_si128(sa, sb)
    movupd  [edx + 0], xmm0     ; Dst[0] = sa
    psrldq  xmm2, 4             ; sb1 = _mm_srli_si128(sb1, 4)
    movupd  xmm0, [eax + 32]    ; sc = Src[2]
    pshufb  xmm0, xmm7          ; sc = _mm_shuffle_epi8(sc, mask)
    movupd  xmm1, xmm0          ; sc1 = sc
    pslldq  xmm0, 8             ; sc = _mm_slli_si128(sc, 8)
    por     xmm0, xmm2          ; sc = _mm_or_si128(sb1, sc)
    movupd  [edx + 16], xmm0    ; Dst[1] = sc
    psrldq  xmm1, 8             ; sc1 = _mm_srli_si128(sc1, 8)
    movupd  xmm0, [eax + 48]    ; sd = Src[3]
    pshufb  xmm0, xmm7          ; sd = _mm_shuffle_epi8(sd, mask)
    pslldq  xmm0, 4             ; sd = _mm_slli_si128(sd, 4)
    por     xmm0, xmm1          ; sd = _mm_or_si128(sc1, sd)
    movupd  [edx + 32], xmm0    ; Dst[2] = sd
    add     eax, 64
    add     edx, 48
    dec     ecx
    jnz     top1

done1:
    pop     ebp
    ret

public _ConvRGB24ToRGB32SSE3

;   ebp + 8     Src (*RGB24, 16-byte aligned)
;   ebp + 12    Dst (*RGB32, 16-byte aligned)
;   ebp + 16    Pixels

_ConvRGB24ToRGB32SSE3:
    push    ebp
    mov     ebp, esp
    mov     eax, [ebp + 8]
    mov     edx, [ebp + 12]
    mov     ecx, [ebp + 16]
    shr     ecx, 4
    jz      done2
    movupd  xmm7, [mask2]

top2:
    movupd  xmm0, [eax + 0]     ; sa = Src[0]
    movupd  xmm1, [eax + 16]    ; sb = Src[1]
    movupd  xmm2, [eax + 32]    ; sc = Src[2]
    movupd  xmm3, xmm0          ; sa1 = sa
    pshufb  xmm0, xmm7          ; sa = _mm_shuffle_epi8(sa, mask)
    movupd  [edx], xmm0         ; Dst[0] = sa
    movupd  xmm4, xmm1          ; sb1 = sb
    palignr xmm1, xmm3, 12      ; sb = _mm_alignr_epi8(sb, sa1, 12)
    pshufb  xmm1, xmm7          ; sb = _mm_shuffle_epi8(sb, mask);
    movupd  [edx + 16], xmm1    ; Dst[1] = sb
    movupd  xmm3, xmm2          ; sc1 = sc
    palignr xmm2, xmm4, 8       ; sc = _mm_alignr_epi8(sc, sb1, 8)
    pshufb  xmm2, xmm7          ; sc = _mm_shuffle_epi8(sc, mask)
    movupd  [edx + 32], xmm2    ; Dst[2] = sc
    palignr xmm3, xmm3, 4       ; sc1 = _mm_alignr_epi8(sc1, sc1, 4)
    pshufb  xmm3, xmm7          ; sc1 = _mm_shuffle_epi8(sc1, mask)
    movupd  [edx + 48], xmm3    ; Dst[3] = sc1
    add     eax, 48
    add     edx, 64
    dec     ecx
    jnz     top2

done2:
    pop     ebp
    ret

section '.data' data readable writeable align 16

label mask1 dqword 
    db  0,1,2,4, 5,6,8,9, 10,12,13,14, -1,-1,-1,-1
label mask2 dqword 
    db  0,1,2,-1, 3,4,5,-1, 6,7,8,-1, 9,10,11,-1

Ответ 3

Различные размеры входных/выходных сигналов не являются препятствием для использования simd, как раз для скорости. Вам нужно будет вырезать данные так, чтобы вы читали и записывали полные слова simd (16 байт).

В этом случае вы будете читать 3 SIMD слова (48 байтов == 16 rgb пикселей), сделать расширение, а затем записать 4 SIMD слова.

Я просто говорю, что вы можете использовать SIMD, я не говорю, что вам нужно. Средний бит, расширение, по-прежнему сложный, так как у вас есть неравномерные размеры сдвига в разных частях слова.

Ответ 4

SSE 4.1.ASM:

PINSRD  XMM0,  DWORD PTR[ESI],   0
PINSRD  XMM0,  DWORD PTR[ESI+3], 1
PINSRD  XMM0,  DWORD PTR[ESI+6], 2
PINSRD  XMM0,  DWORD PTR[ESI+9], 3
PSLLD   XMM0,  8                    
PSRLD   XMM0,  8
MOVNTDQ [EDI], XMM1
add     ESI,   12
add     EDI,   16