2010-06-04 22 views
12

Resumen rápida:Arreglo rápido de 24 bits -> conversión de matriz de 32 bits?

I tiene una matriz de valores de 24 bits. ¿Alguna sugerencia sobre cómo expandir rápidamente los elementos de matriz individuales de 24 bits en elementos de 32 bits?

Detalles:

Estoy procesamiento de fotogramas de vídeo entrantes en tiempo real utilizando Pixel Shaders en DirectX 10. Un escollo es que mis cuadros están llegando desde el hardware de captura con píxeles de 24 bits (ya sea como imágenes YUV o RGB), pero DX10 tiene texturas de píxeles de 32 bits. Entonces, tengo que expandir los valores de 24 bits a 32 bits antes de poder cargarlos en la GPU.

Realmente no me importa para qué establezco los 8 bits restantes, o donde los 24 bits entrantes están en ese valor de 32 bits. Puedo arreglar todo eso en un sombreador de píxeles. Pero necesito hacer la conversión de 24 bits a 32 bits realmente rápidamente.

No estoy muy familiarizado con las operaciones SIMD SSE, pero desde mi mirada superficial no parece que pueda hacer la expansión usándolas, dado que mis lecturas y escrituras no son del mismo tamaño. ¿Alguna sugerencia? ¿O estoy atrapado masajeando secuencialmente este conjunto de datos?

Esto parece muy tonto: estoy usando los sombreadores de píxeles para el paralelismo, pero antes tengo que hacer una operación secuencial por píxel. I debe faltar falta algo obvio ...

+0

¿Seguro que no puede configurar su fuente de vídeo para que te de byte extra? –

+0

Bastante, Matti. Lo cual es terriblemente desafortunado, estoy de acuerdo. :( – Clippy

Respuesta

20

El código siguiente debe ser bastante rápido. Copia 4 píxeles en cada iteración, utilizando solo instrucciones de lectura/escritura de 32 bits. Los punteros de origen y destino deben estar alineados a 32 bits.

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; 
} 

Editar:

Aquí está una manera de hacer esto utilizando la SSSE3 instrucciones PSHUFB y PALIGNR. El código está escrito usando intrínsecos del compilador, pero no debería ser difícil traducirlo a ensamblaje si es necesario. Copia 16 píxeles en cada iteración. Los punteros de origen y de destino deben alinearse a 16 bytes, o será un error. Si no están alineados, puede hacerlo funcionar reemplazando _mm_load_si128 con _mm_loadu_si128 y _mm_store_si128 con _mm_storeu_si128, pero esto será más lento.

#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 (que no debe confundirse con SSE3) requerirá un tiempo relativamente nuevo procesador: Core 2 o posterior, y creo que AMD no lo soporta todavía. Realizar esto con las instrucciones SSE2 solo requerirá muchas más operaciones, y puede que no valga la pena.

+0

Wow - gracias interjay! Es genial saber que estaba equivocado, y las operaciones SIMD están disponibles que pueden hacer lo que necesito. ¡Y esa muestra SSSE3 es fantástica! Tengo control total de la plataforma en la que se ejecuta este código, y puedo restrinja las opciones de hardware a los procesadores con capacidad SSSE3. – Clippy

+0

Me pregunto ... ¿la primera solución hace la diferencia para los procesadores pequeños y grandes? –

+0

@TheQuantumPhysicist Supone un procesador little-endian. La misma idea funcionaría para big-endian, pero los cambios deben ser cambiados. – interjay

1

Los diferentes tamaños de entrada/salida no son una barrera para usar simd, solo un aumento de velocidad. Debería dividir los datos para que pueda leer y escribir en palabras simd completas (16 bytes).

En este caso, leería 3 palabras SIMD (48 bytes == 16 píxeles rbb), haga la expansión, luego escriba 4 palabras SIMD.

sólo estoy diciendo que puede uso SIMD, no estoy diciendo que usted debe .La parte media, la expansión, sigue siendo complicada ya que tiene tamaños de cambio no uniformes en diferentes partes de la palabra.

+0

Gracias Mark - Es genial saber que estaba equivocado, y hay disponibles operaciones SIMD que pueden hacer lo que necesito. Puede que no sea más rápido, pero vale la pena mirarlo para estar seguro. :) – Clippy

5

SSE3 es impresionante, pero para aquellos que no pueden usarlo por alguna razón, aquí está la conversión en ensamblador x86, optimizada a mano por los tuyos. Para completar, doy la conversión en ambas direcciones: RGB32-> RGB24 y RGB24-> RGB32.

Tenga en cuenta que el código C de interjay deja basura en el MSB (el canal alfa) de los píxeles de destino. Puede que esto no importe en algunas aplicaciones, pero sí en las mías, por lo tanto, mi código RGB24-> RGB32 obliga al MSB a cero. Del mismo modo, mi código RGB32-> RGB24 ignora el MSB; esto evita la salida de basura si los datos de origen tienen un canal alfa distinto de cero. Estas funciones no cuestan casi nada en términos de rendimiento, según lo verificado por los puntos de referencia.

Para RGB32-> RGB24 Pude vencer al optimizador de VC++ en aproximadamente un 20%. Para RGB24-> RGB32 la ganancia fue insignificante. La evaluación comparativa se realizó en un i5 2500K. Omito el código de evaluación comparativa aquí, pero si alguien lo quiere lo proporcionaré. La optimización más importante fue golpear el puntero fuente lo antes posible (ver el comentario ASAP). Mi mejor suposición es que esto aumenta el paralelismo al permitir que la tubería de instrucción realice una búsqueda previa antes. Aparte de eso, simplemente reordené algunas instrucciones para reducir las dependencias y superponer los accesos a la memoria con ataques de bits.

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 
} 

Y ya que estamos en ello, aquí son las mismas conversiones en el montaje SSE3 real. Esto solo funciona si tiene un ensamblador (FASM es gratis) y tiene una CPU que admite SSE3 (probablemente, pero es mejor verificarlo). Tenga en cuenta que los elementos intrínsecos no necesariamente generan algo tan eficiente, sino que depende totalmente de las herramientas que usa y de la plataforma para la que está compilando. Aquí, es sencillo: lo que ves es lo que obtienes. Este código genera el mismo resultado que el código x86 anterior, y es aproximadamente 1.5 veces más rápido (en un 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

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 
+2

Esto es aproximadamente 4 veces más ineficiente que la respuesta de interjays. – hirschhornsalz

Cuestiones relacionadas