2011-08-25 14 views
5

En un seguimiento a algunas de las preguntas anteriores sobre la conversión de RGB a RGBA, y ARGB a BGR, me gustaría acelerar un RGB a BGRA de conversión con SSE. Supongamos una máquina de 32 bits y nos gustaría usar intrinsics. Tengo dificultades para alinear los almacenamientos intermedios de origen y destino para trabajar con registros de 128 bits y buscar otras soluciones de vectorización inteligentes.rápida conversión de RGB a vectorizado BGRA

La rutina para ser vectorizado es el siguiente ...

void RGB8ToBGRX8(int w, const void *in, void *out) 
    { 
     int i; 
     int width = w; 
     const unsigned char *src= (const unsigned char*) in; 
     unsigned int *dst= (unsigned int*) out; 
     unsigned int invalue, outvalue; 

     for (i=0; i<width; i++, src+=3, dst++) 
     { 
       invalue = src[0]; 
       outvalue = (invalue<<16); 
       invalue = src[1]; 
       outvalue |= (invalue<<8); 
       invalue = src[2]; 
       outvalue |= (invalue); 
       *dst = outvalue | 0xff000000; 
     } 
     } 

Esta rutina se utiliza primariamente para grandes texturas (512KB), así que si puedo paralelizar algunas de las operaciones, puede ser beneficioso para el proceso más píxeles en un intento. Por supuesto, necesitaré un perfil. :)

Editar:

Mis argumentos de compilación ...

gcc -O2 main.c 
+1

¿Está utilizando el indicador de optimización para su compilador (¿cuál?)? El compilador a menudo hará un mejor trabajo al optimizar el código, _sin introducir lo incorrecto. ¿Qué datos de referencia ha recopilado? –

+0

No es una respuesta SSE, pero ¿ha intentado desenrollar su circuito 4 veces para que la entrada siempre comience en una dirección alineada? Luego puede leer la entrada de una palabra de máquina a la vez en lugar de hacerlo de manera manual, con desplazamiento y enmascaramiento especializado para cada posición relativa del píxel fuente. Como Dana menciona, vale la pena ver qué tan bien funciona el compilador en altos niveles de optimización (inspeccionar el código ensamblador generado, además de la evaluación comparativa), pero dudo que sea lo suficientemente agresivo para desenrollar el bucle _y_ divida el punto de entrada de acuerdo con el alineación de 'en' todo por sí mismo. –

+0

Grandes preguntas. Es simplemente "O2" (NO O3) con GCC4.6. Mi caso de referencia es una corrida de iteración de 10K con 512 como el lapso de "ancho". Gracias por las excelentes respuestas! – Rev316

Respuesta

8

Este es un ejemplo del uso de intrínsecos SSE3 para realizar la operación solicitada. Los punteros de entrada y salida deben estar alineados a 16 bytes, y funcionan en un bloque de 16 píxeles a la vez.

No creo que obtenga un impulso de velocidad significativo, sin embargo. Las operaciones realizadas en los píxeles son tan simples que domina el ancho de banda de la memoria.

#include <tmmintrin.h> 

/* in and out must be 16-byte aligned */ 
void rgb_to_bgrx_sse(unsigned w, const void *in, void *out) 
{ 
    const __m128i *in_vec = in; 
    __m128i *out_vec = out; 

    w /= 16; 

    while (w-- > 0) { 
     /*    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 
     * in_vec[0] Ra Ga Ba Rb Gb Bb Rc Gc Bc Rd Gd Bd Re Ge Be Rf 
     * in_vec[1] Gf Bf Rg Gg Bg Rh Gh Bh Ri Gi Bi Rj Gj Bj Rk Gk 
     * in_vec[2] Bk Rl Gl Bl Rm Gm Bm Rn Gn Bn Ro Go Bo Rp Gp Bp 
     */ 
     __m128i in1, in2, in3; 
     __m128i out; 

     in1 = in_vec[0]; 

     out = _mm_shuffle_epi8(in1, 
      _mm_set_epi8(0xff, 9, 10, 11, 0xff, 6, 7, 8, 0xff, 3, 4, 5, 0xff, 0, 1, 2)); 
     out = _mm_or_si128(out, 
      _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); 
     out_vec[0] = out; 

     in2 = in_vec[1]; 

     in1 = _mm_and_si128(in1, 
      _mm_set_epi8(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0)); 
     out = _mm_and_si128(in2, 
      _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 
     out = _mm_or_si128(out, in1); 
     out = _mm_shuffle_epi8(out, 
      _mm_set_epi8(0xff, 5, 6, 7, 0xff, 2, 3, 4, 0xff, 15, 0, 1, 0xff, 12, 13, 14)); 
     out = _mm_or_si128(out, 
      _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); 
     out_vec[1] = out; 

     in3 = in_vec[2]; 
     in_vec += 3; 

     in2 = _mm_and_si128(in2, 
      _mm_set_epi8(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0)); 
     out = _mm_and_si128(in3, 
      _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); 
     out = _mm_or_si128(out, in2); 
     out = _mm_shuffle_epi8(out, 
      _mm_set_epi8(0xff, 1, 2, 3, 0xff, 14, 15, 0, 0xff, 11, 12, 13, 0xff, 8, 9, 10)); 
     out = _mm_or_si128(out, 
      _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); 
     out_vec[2] = out; 

     out = _mm_shuffle_epi8(in3, 
      _mm_set_epi8(0xff, 13, 14, 15, 0xff, 10, 11, 12, 0xff, 7, 8, 9, 0xff, 4, 5, 6)); 
     out = _mm_or_si128(out, 
      _mm_set_epi8(0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0, 0xff, 0, 0, 0)); 
     out_vec[3] = out; 

     out_vec += 4; 
    } 
} 
2

que no tienen una comprensión completa de lo que está pidiendo, y estoy esperando con impaciencia una respuesta adecuada a tu pregunta Mientras tanto, he encontrado una implementación de la mañana que es aproximadamente entre un 8% y un 10% más rápida en promedio. Estoy ejecutando Win7 64 bits, utilizando VS2010, compilando con C++ para su lanzamiento con la opción rápida.

#pragma pack(push, 1) 
    struct RGB { 
     unsigned char r, g, b; 
    }; 

    struct BGRA { 
     unsigned char b, g, r, a; 
    }; 
#pragma pack(pop) 

    void RGB8ToBGRX8(int width, const void* in, void* out) 
    { 
     const RGB* src = (const RGB*)in; 
     BGRA* dst = (BGRA*)out; 
     do {   
      dst->r = src->r; 
      dst->g = src->g; 
      dst->b = src->b; 
      dst->a = 0xFF; 
      src++; 
      dst++; 
     } while (--width); 
    } 

Esto puede o no ayudar, pero espero que sí. Por favor, no me voten si no lo hace, solo intento mover esto.

Mi motivación para utilizar structs es permitir que el compilador desarrolle de la forma más eficiente posible los punteros src y dst. Otra motivación es limitar el número de operaciones aritméticas.

+0

¡No se preocupe, Jack! Si pudieras aclarar qué pieza no puedes entender, puedo intentar y elaborar. :) – Rev316

+0

¿Qué quiere decir con el uso de SSE? Creo que significa instruir al compilador para que use técnicas de optimización específicas, y si ese es el caso, quizás no valga la pena modificar el código a mano. También dices que te gustaría usar intrínsecos, ¿qué quieres decir? Sin embargo, tengo una buena comprensión de la paralelización. – Jack

+0

Oh. Me estaba refiriendo a las intrisas de vectorización del uso de SSE2/3 o SSSEE. Principalmente las operaciones de relleno/enmascaramiento, ya que he visto soluciones elegantes con otras conversiones de imagen. Ahora, sé que GCC4.x tiene varios indicadores de compilación que ayudan aquí, pero no estoy seguro de cuál y/o si es mejor. Tal vez su experiencia sería útil aquí. – Rev316

2

Personalmente encontré que implementar lo siguiente me dio el mejor resultado para convertir BGR-24 a ARGB-32.

Este código se ejecuta a aproximadamente 8.8ms en una imagen, mientras que el código de vectorización de 128 bits presentado arriba llega a 14.5ms por imagen.

void PixelFix(u_int32_t *buff,unsigned char *diskmem) 
{ 
    int i,j; 
    int picptr, srcptr; 
    int w = 1920; 
    int h = 1080; 

    for (j=0; j<h; j++) { 
     for (i=0; i<w; i++) { 
      buff[picptr++]=(diskmem[srcptr]<<24) | (diskmem[srcptr+1]<<16) | diskmem[srcptr+2]<<8 | 0xff; 
      srcptr+=3; 
     } 
    } 
} 

Anteriormente, había estado usando esta rutina (aproximadamente 13.2ms por imagen). Aquí, buff es un char * sin signo.

for (j=0; j<h; j++) { 
    int srcptr = (h-j-1)*w*3; // remove if you don't want vertical flipping 
    for (i=0; i<w; i++) { 
     buff[picptr+3]=diskmem[srcptr++]; // b 
     buff[picptr+2]=diskmem[srcptr++]; // g 
     buff[picptr+1]=diskmem[srcptr++]; // r 
     buff[picptr+0]=255;    // a 
     picptr+=4; 
    } 
} 

Ejecutando un 2012 MacMini 2.6ghz/i7.

+0

Además de esto, uno puede desear mirar en la reciente API de conversión de vImage de Apple ..., específicamente rutinas como "vImageConvert_RGB888toARGB8888" para convertir de 24 bits RGB a 32 bits ARGB (o BGRA). https://developer.apple.com/library/mac/documentation/Performance/Reference/vImage_conversion/Reference/reference.html#//apple_ref/c/func/vImageConvert_RGB888toARGB8888 – zzyzy

2

Ummm ... el uso de vImageConvert_RGB888toARGB8888 es MUY MUY rápido (15X de aceleración).

código

Por encima de PixelFix (≈6ms por imagen, ahora en el nuevo hardware)


  1. 6.373520 ms
  2. 6.383363 ms
  3. 6.413560 ms
  4. 6.278606 ms
  5. 6.293607 ms
  6. 6.368118 ms
  7. 6.338904 ms
  8. 6.389385 ms
  9. 6.365495 ms

Uso de vImageConvert_RGB888toARGB888, roscados (en hardware más nuevo)


  1. 0.563649 ms
  2. 0.400387 ms
  3. 0.375198 ms
  4. 0.360898 ms
  5. 0.391278 ms
  6. 0.396797 ms
  7. 0.405534 ms
  8. 0.386495 ms
  9. 0.367621 ms

Necesito decir más?

+1

Una continuación ... usando el subproceso único El código de vector de 128 bits "rgb_to_bgrx_sse" anterior dio resultados en el rango de 11 ms para los búferes de E/S del mismo tamaño. vImage es el claro ganador aquí. – zzyzy