Быстрый 24-битный массив -> 32-битное преобразование массива? - PullRequest
13 голосов
/ 04 июня 2010

Краткое резюме:

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

подробности:

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

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

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

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

Ответы [ 4 ]

22 голосов
/ 04 июня 2010

Код ниже должен быть довольно быстрым. Он копирует 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 потребует намного больше операций и, возможно, того не стоит.

5 голосов
/ 01 апреля 2012

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

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

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

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
1 голос
/ 04 июня 2010

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

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

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

0 голосов
/ 22 июня 2012

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