14

簡単な要約:

24 ビット値の配列があります。個々の 24 ビット配列要素を 32 ビット要素にすばやく拡張する方法について何か提案はありますか?

詳細:

DirectX 10 のピクセル シェーダーを使用して、着信ビデオ フレームをリアルタイムで処理しています。つまずきは、私のフレームが 24 ビット ピクセル (YUV または RGB 画像として) のキャプチャ ハードウェアから受信されていることですが、DX10 は 32 ビットを使用します。ピクセル テクスチャ。そのため、24 ビット値を GPU にロードする前に 32 ビットに拡張する必要があります。

残りの 8 ビットを何に設定したか、または受信した 24 ビットがその 32 ビット値のどこにあるかは気にしません。ピクセル シェーダーですべてを修正できます。しかし、24 ビットから 32 ビットへの変換は非常に迅速に行う必要があります

私は SIMD SSE 操作にあまり詳しくありませんが、読み取りと書き込みのサイズが同じでないことを考えると、ざっと見ただけでは、それらを使用して拡張を実行できるようには見えません。助言がありますか?それとも、このデータセットを順番にマッサージしているのでしょうか?

これは非常にばかげているように感じます。並列処理のためにピクセル シェーダーを使用していますが、その前にピクセルごとの順次操作を行う必要があります。明らかな何かが欠けているに違いない...

4

4 に答える 4

25

以下のコードはかなり高速です。32 ビットの読み取り/書き込み命令のみを使用して、反復ごとに 4 ピクセルをコピーします。ソース ポインタとデスティネーション ポインタは 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;
}

編集:

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 命令のみで実行すると、さらに多くの操作が必要になり、その価値がない場合があります。

于 2010-06-04T12:52:27.787 に答える
6

SSE3 は素晴らしいですが、何らかの理由でそれを使用できない人のために、x86 アセンブラーでの変換を紹介します。完全を期すために、RGB32->RGB24 および RGB24->RGB32 の両方向で変換を行います。

interjay の C コードは、宛先ピクセルの 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 をサポートする CPU を持っている場合にのみ機能します (可能性は高いですが、確認することをお勧めします)。組み込み関数は必ずしもこれほど効率的なものを出力するとは限らないことに注意してください。これは、使用するツールとコンパイルするプラットフォームに完全に依存します。ここでは、それは簡単です: あなたが見るものはあなたが得るものです. このコードは、上記の 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
于 2012-03-31T20:57:31.180 に答える
1

さまざまな入出力サイズは、simd を使用する上での障害ではなく、スピード バンプにすぎません。完全な simd ワード (16 バイト) で読み書きできるように、データをチャンクする必要があります。

この場合、3 つの SIMD ワード (48 バイト == 16 RGB ピクセル) を読み取り、展開を行ってから、4 つの SIMD ワードを書き込みます。

SIMD を使用できると言っているだけで、使用すべきだと言っているわけではありません。単語のさまざまな部分で不均一なシフトサイズがあるため、中間のビットである展開はまだ注意が必要です。

于 2010-06-04T13:06:13.543 に答える
-1

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
于 2012-06-22T08:26:15.210 に答える