Быстрый метод копирования памяти с переводом - ARGB в BGR

Обзор

У меня есть буфер изображения, который мне нужно преобразовать в другой формат. Буфер исходного изображения - четыре канала, 8 бит на канал, альфа, красный, зеленый и синий. Буфер назначения - три канала, 8 бит на канал, синий, зеленый и красный.

Таким образом, метод грубой силы:

// Assume a 32 x 32 pixel image
#define IMAGESIZE (32*32)

typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

ARGB orig[IMAGESIZE];
BGR  dest[IMAGESIZE];

for(x = 0; x < IMAGESIZE; x++)
{
     dest[x].Red = orig[x].Red;
     dest[x].Green = orig[x].Green;
     dest[x].Blue = orig[x].Blue;
}

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

Дополнительная информация

Каждое изображение кратно не менее 4 пикселей. Таким образом, мы могли бы адресовать 16 ARGB-байтов и переместить их в 12 RGB-байтов на цикл. Возможно, этот факт может быть использован для ускорения работы, тем более, что он красиво падает на 32-битные границы.

У меня есть доступ к OpenCL - и в то время как для этого требуется переместить весь буфер в память графического процессора, а затем переместить результат обратно, тот факт, что OpenCL может работать на многих участках изображения одновременно, и тот факт, что большой блок памяти ходы на самом деле довольно эффективны, может сделать это полезным исследованием.

Пока я привел пример небольших буферов выше, я действительно перемещаю HD-видео (1920x1080) и иногда большие, в основном меньшие буферы, поэтому, в то время как ситуация 32x32 может быть тривиальной, копирование 8,3 Мбайта байт данных изображения by byte действительно, очень плохо.

Работа на процессорах Intel (Core 2 и выше), и, следовательно, есть команды потоковой передачи и обработки данных, о которых я знаю, но не знаю - возможно, указатели на то, где искать специализированные инструкции по обработке данных, были бы хорошими.

Это происходит в приложении OS X, и я использую XCode 4. Если сборка безболезненна и очевидный способ пойти, я отлично разбираюсь по этому пути, но не сделал этого на этой установке, прежде чем сделать я опасаюсь погрузиться в это слишком много времени.

Псевдокод в порядке - я не ищу полного решения, просто алгоритм и объяснение любых обманщиков, которые могут быть не сразу понятны.

Ответ 1

Я написал 4 разных версии, которые работают путем замены байтов. Я скомпилировал их с помощью gcc 4.2.1 с -O3 -mssse3, выполнил их 10 раз по 32 МБ случайных данных и нашел средние значения.

В первой версии используется цикл C для преобразования каждого пикселя отдельно, используя функцию OSSwapInt32 (которая скомпилируется с инструкцией bswap с -O3).

void swap1(ARGB *orig, BGR *dest, unsigned imageSize) {
    unsigned x;
    for(x = 0; x < imageSize; x++) {
        *((uint32_t*)(((uint8_t*)dest)+x*3)) = OSSwapInt32(((uint32_t*)orig)[x]);
    }
}

Второй метод выполняет ту же операцию, но использует цикл встроенной сборки вместо цикла C.

void swap2(ARGB *orig, BGR *dest, unsigned imageSize) {
    asm (
        "0:\n\t"
        "movl   (%1),%%eax\n\t"
        "bswapl %%eax\n\t"
        "movl   %%eax,(%0)\n\t"
        "addl   $4,%1\n\t"
        "addl   $3,%0\n\t"
        "decl   %2\n\t"
        "jnz    0b"
        :: "D" (dest), "S" (orig), "c" (imageSize)
        : "flags", "eax"
    );
}

Третья версия - это измененная версия только ответ poseur. Я преобразовал встроенные функции в эквиваленты GCC и использовал встроенную функцию lddqu, так что входной аргумент не нужно выравнивать.

typedef uint8_t v16qi __attribute__ ((vector_size (16)));
void swap3(uint8_t *orig, uint8_t *dest, size_t imagesize) {
    v16qi mask = __builtin_ia32_lddqu((const char[]){3,2,1,7,6,5,11,10,9,15,14,13,0xFF,0xFF,0xFF,0XFF});
    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 16, dest += 12) {
        __builtin_ia32_storedqu(dest,__builtin_ia32_pshufb128(__builtin_ia32_lddqu(orig),mask));
    }
}

Наконец, четвертая версия представляет собой встроенную сборку, эквивалентную третьей.

void swap2_2(uint8_t *orig, uint8_t *dest, size_t imagesize) {
    int8_t mask[16] = {3,2,1,7,6,5,11,10,9,15,14,13,0xFF,0xFF,0xFF,0XFF};//{0xFF, 0xFF, 0xFF, 0xFF, 13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3};
    asm (
        "lddqu  (%3),%%xmm1\n\t"
        "0:\n\t"
        "lddqu  (%1),%%xmm0\n\t"
        "pshufb %%xmm1,%%xmm0\n\t"
        "movdqu %%xmm0,(%0)\n\t"
        "add    $16,%1\n\t"
        "add    $12,%0\n\t"
        "sub    $4,%2\n\t"
        "jnz    0b"
        :: "r" (dest), "r" (orig), "r" (imagesize), "r" (mask)
        : "flags", "xmm0", "xmm1"
    );
}

В моем MacBook Pro 2010 года, 2,4 ГГц i5, 4 ГБ оперативной памяти это были средние времена для каждого:

Version 1: 10.8630 milliseconds
Version 2: 11.3254 milliseconds
Version 3:  9.3163 milliseconds
Version 4:  9.3584 milliseconds

Как вы можете видеть, компилятор достаточно хорош в оптимизации, что вам не нужно писать сборку. Кроме того, векторные функции были на 1,5 миллисекунды быстрее на 32 МБ данных, поэтому это не навредит, если вы хотите поддерживать самые ранние макинтоши Intel, которые не поддерживают SSSE3.

Изменить: liori запросил информацию об стандартном отклонении. К сожалению, я не сохранил данные, поэтому я провел еще один тест с 25 итерациями.

              Average    | Standard Deviation
Brute force: 18.01956 ms | 1.22980 ms (6.8%)
Version 1:   11.13120 ms | 0.81076 ms (7.3%)
Version 2:   11.27092 ms | 0.66209 ms (5.9%)
Version 3:    9.29184 ms | 0.27851 ms (3.0%)
Version 4:    9.40948 ms | 0.32702 ms (3.5%)

Кроме того, вот исходные данные из новых тестов, если кто-то захочет этого. Для каждой итерации набор данных 32 МБ генерировался случайным образом и выполнялся через четыре функции. Время выполнения каждой функции в микросекундах приведено ниже.

Brute force: 22173 18344 17458 17277 17508 19844 17093 17116 19758 17395 18393 17075 17499 19023 19875 17203 16996 17442 17458 17073 17043 18567 17285 17746 17845
Version 1:   10508 11042 13432 11892 12577 10587 11281 11912 12500 10601 10551 10444 11655 10421 11285 10554 10334 10452 10490 10554 10419 11458 11682 11048 10601
Version 2:   10623 12797 13173 11130 11218 11433 11621 10793 11026 10635 11042 11328 12782 10943 10693 10755 11547 11028 10972 10811 11152 11143 11240 10952 10936
Version 3:    9036  9619  9341  8970  9453  9758  9043 10114  9243  9027  9163  9176  9168  9122  9514  9049  9161  9086  9064  9604  9178  9233  9301  9717  9156
Version 4:    9339 10119  9846  9217  9526  9182  9145 10286  9051  9614  9249  9653  9799  9270  9173  9103  9132  9550  9147  9157  9199  9113  9699  9354  9314

Ответ 2

Очевидное, используя pshufb.

#include <assert.h>
#include <inttypes.h>
#include <tmmintrin.h>

// needs:
// orig is 16-byte aligned
// imagesize is a multiple of 4
// dest has 4 trailing scratch bytes
void convert(uint8_t *orig, size_t imagesize, uint8_t *dest) {
    assert((uintptr_t)orig % 16 == 0);
    assert(imagesize % 4 == 0);
    __m128i mask = _mm_set_epi8(-128, -128, -128, -128, 13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3);
    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 16, dest += 12) {
        _mm_storeu_si128((__m128i *)dest, _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig), mask));
    }
}

Ответ 3

Объединяя только ответы poseur и Jitamaro, если вы предполагаете, что входы и выходы согласованы по 16 байт, и если вы обрабатываете пиксели 4 за раз, вы можете использовать комбинацию тасований, масок и т.д., а также для хранения используя ориентированные магазины. Основная идея состоит в том, чтобы сгенерировать четыре промежуточных набора данных, затем или вместе с масками, чтобы выбрать соответствующие значения пикселей и выписать 3 16-байтовых набора данных пикселя. Обратите внимание, что я не компилировал это или вообще не запускал его.

EDIT2: более подробная информация о базовой структуре кода:

С SSE2 вы получаете лучшую производительность с 16-байтовыми выровненными чтениями и записью 16 байтов. Поскольку ваш 3-байтовый пиксель выровнен только по 16-байтам на каждые 16 пикселей, мы едим 16 пикселей за раз, используя комбинацию тасований и масок и орлов из 16 входных пикселей за раз.

От LSB до MSB входы выглядят так, игнорируя конкретные компоненты:

s[0]: 0000 0000 0000 0000
s[1]: 1111 1111 1111 1111
s[2]: 2222 2222 2222 2222
s[3]: 3333 3333 3333 3333

а выписки выглядят следующим образом:

d[0]: 000 000 000 000 111 1
d[1]:  11 111 111 222 222 22
d[2]:   2 222 333 333 333 333

Итак, чтобы сгенерировать эти выходы, вам нужно сделать следующее (я буду указывать фактические преобразования позже):

d[0]= combine_0(f_0_low(s[0]), f_0_high(s[1]))
d[1]= combine_1(f_1_low(s[1]), f_1_high(s[2]))
d[2]= combine_2(f_1_low(s[2]), f_1_high(s[3]))

Теперь, что должно выглядеть combine_<x>? Если мы предположим, что d просто s уплотнено вместе, мы можем объединить два s с маской и a или:

combine_x(left, right)= (left & mask(x)) | (right & ~mask(x))

где (1 означает выбор левого пикселя, 0 означает выбор правильного пикселя):   маска (0) = 111 111 111 111 000 0   маска (1) = 11 111 111 000 000 00   маска (2) = 1 111 000 000 000 000

Но фактические преобразования (f_<x>_low, f_<x>_high) на самом деле не так просты. Поскольку мы реверсируем и удаляем байты из исходного пикселя, фактическое преобразование (для первого назначения для краткости):

d[0]= 
    s[0][0].Blue s[0][0].Green s[0][0].Red 
    s[0][1].Blue s[0][1].Green s[0][1].Red 
    s[0][2].Blue s[0][2].Green s[0][2].Red 
    s[0][3].Blue s[0][3].Green s[0][3].Red
    s[1][0].Blue s[1][0].Green s[1][0].Red
    s[1][1].Blue

Если вы переводите приведенное выше в байтовые смещения от источника к dest, вы получаете:   д [0] =       & s [0] +3 & s [0] +2 & s [0] +1
      & s [0] +7 & s [0] +6 & s [0] +5       & s [0] +11 & s [0] +10 & s [0] +9       & s [0] +15 & s [0] +14 & s [0] +13
      & s [1] +3 & s [1] +2 & s [1] +1
      & Амп; s [1]: +7

(Если вы посмотрите на все смещения s [0], они совпадают с маской перетасовки в обратном порядке.)

Теперь мы можем создать маску тасования для сопоставления каждого исходного байта с байтом назначения (X означает, что нам все равно, что это за значение):

f_0_low=  3 2 1  7 6 5  11 10 9  15 14 13  X X X  X
f_0_high= X X X  X X X   X  X X   X  X  X  3 2 1  7

f_1_low=    6 5  11 10 9  15 14 13  X X X   X X X  X  X
f_1_high=   X X   X  X X   X  X  X  3 2 1   7 6 5  11 10

f_2_low=      9  15 14 13  X  X  X  X X X   X  X  X  X  X  X
f_2_high=     X   X  X  X  3  2  1  7 6 5   11 10 9  15 14 13

Мы можем дополнительно оптимизировать это, посмотрев маски, которые мы используем для каждого пикселя источника. Если вы посмотрите на маски тасов, которые мы используем для s [1]:

f_0_high=  X  X  X  X  X  X  X  X  X  X  X  X  3  2  1  7
f_1_low=   6  5 11 10  9 15 14 13  X  X  X  X  X  X  X  X

Так как две маски тасовки не перекрываются, мы можем комбинировать их и просто маскировать ненужные пиксели в comb_, которые мы уже сделали! Следующий код выполняет все эти оптимизации (плюс предполагает, что исходный и целевой адреса выравниваются по 16 байт). Кроме того, маски выписываются в коде в порядке MSB- > LSB, если вы путаетесь с порядком.

EDIT: изменил хранилище на _mm_stream_si128, так как вы, вероятно, много записываете, и мы не хотим, чтобы он скрывал кеш. Плюс он должен быть выровнен так или иначе, чтобы вы получили бесплатный перфект!

#include <assert.h>
#include <inttypes.h>
#include <tmmintrin.h>

// needs:
// orig is 16-byte aligned
// imagesize is a multiple of 4
// dest has 4 trailing scratch bytes
void convert(uint8_t *orig, size_t imagesize, uint8_t *dest) {
    assert((uintptr_t)orig % 16 == 0);
    assert(imagesize % 16 == 0);

    __m128i shuf0 = _mm_set_epi8(
        -128, -128, -128, -128, // top 4 bytes are not used
        13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3); // bottom 12 go to the first pixel

    __m128i shuf1 = _mm_set_epi8(
        7, 1, 2, 3, // top 4 bytes go to the first pixel
    -128, -128, -128, -128, // unused
        13, 14, 15, 9, 10, 11, 5, 6); // bottom 8 go to second pixel

    __m128i shuf2 = _mm_set_epi8(
        10, 11, 5, 6, 7, 1, 2, 3, // top 8 go to second pixel
    -128, -128, -128, -128, // unused
        13, 14, 15, 9); // bottom 4 go to third pixel

    __m128i shuf3 = _mm_set_epi8(
        13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3, // top 12 go to third pixel
        -128, -128, -128, -128); // unused

    __m128i mask0 = _mm_set_epi32(0, -1, -1, -1);
    __m128i mask1 = _mm_set_epi32(0,  0, -1, -1);
    __m128i mask2 = _mm_set_epi32(0,  0,  0, -1);

    uint8_t *end = orig + imagesize * 4;
    for (; orig != end; orig += 64, dest += 48) {
        __m128i a= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig), shuf0);
        __m128i b= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 1), shuf1);
        __m128i c= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 2), shuf2);
        __m128i d= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 3), shuf3);

        _mm_stream_si128((__m128i *)dest, _mm_or_si128(_mm_and_si128(a, mask0), _mm_andnot_si128(b, mask0));
        _mm_stream_si128((__m128i *)dest + 1, _mm_or_si128(_mm_and_si128(b, mask1), _mm_andnot_si128(c, mask1));
        _mm_stream_si128((__m128i *)dest + 2, _mm_or_si128(_mm_and_si128(c, mask2), _mm_andnot_si128(d, mask2));
    }
}

Ответ 4

Я немного опаздываю на вечеринку, похоже, что сообщество уже решило для poseur pshufb-answer, но распределяя репутацию 2000 года, это так великодушно, что я должен попробовать.

Здесь моя версия без специфичных для платформы встроенных или машинных приложений asm, я включил некоторый кросс-платформенный код времени, показывающий 4x speedup, если вы выполняете как бит-скрипинг вроде me AND активировать оптимизацию компилятора (оптимизация регистров, циклическое разворачивание):

#include "stdlib.h"
#include "stdio.h"
#include "time.h"

#define UInt8 unsigned char

#define IMAGESIZE (1920*1080) 
int main() {
    time_t  t0, t1;
    int frames;
    int frame; 
    typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
    typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

    ARGB* orig = malloc(IMAGESIZE*sizeof(ARGB));
    if(!orig) {printf("nomem1");}
    BGR* dest = malloc(IMAGESIZE*sizeof(BGR));
    if(!dest) {printf("nomem2");}

    printf("to start original hit a key\n");
    getch();
    t0 = time(0);
    frames = 1200;
    for(frame = 0; frame<frames; frame++) {
        int x; for(x = 0; x < IMAGESIZE; x++) {
            dest[x].Red = orig[x].Red;
            dest[x].Green = orig[x].Green;
            dest[x].Blue = orig[x].Blue;
            x++;
        }
    }
    t1 = time(0);
    printf("finished original of %u frames in %u seconds\n", frames, t1-t0);

    // on my core 2 subnotebook the original took 16 sec 
    // (8 sec with compiler optimization -O3) so at 60 FPS 
    // (instead of the 1200) this would be faster than realtime 
    // (if you disregard any other rendering you have to do). 
    // However if you either want to do other/more processing 
    // OR want faster than realtime processing for e.g. a video-conversion 
    // program then this would have to be a lot faster still.

    printf("to start alternative hit a key\n");
    getch();
    t0 = time(0);
    frames = 1200;
    unsigned int* reader;
    unsigned int* end = reader+IMAGESIZE;
    unsigned int cur; // your question guarantees 32 bit cpu
    unsigned int next;
    unsigned int temp;
    unsigned int* writer;
    for(frame = 0; frame<frames; frame++) {
        reader = (void*)orig;
        writer = (void*)dest;
        next = *reader;
        reader++;
        while(reader<end) {
            cur = next;
            next = *reader;         
            // in the following the numbers are of course the bitmasks for 
            // 0-7 bits, 8-15 bits and 16-23 bits out of the 32
            temp = (cur&255)<<24 | (cur&65280)<<16|(cur&16711680)<<8|(next&255); 
            *writer = temp;
            reader++;
            writer++;
            cur = next;
            next = *reader;
            temp = (cur&65280)<<24|(cur&16711680)<<16|(next&255)<<8|(next&65280);
            *writer = temp;
            reader++;
            writer++;
            cur = next;
            next = *reader;
            temp = (cur&16711680)<<24|(next&255)<<16|(next&65280)<<8|(next&16711680);
            *writer = temp;
            reader++;
            writer++;
        }
    }
    t1 = time(0);
    printf("finished alternative of %u frames in %u seconds\n", frames, t1-t0);

    // on my core 2 subnotebook this alternative took 10 sec 
    // (4 sec with compiler optimization -O3)

}

Результаты этих (в моем основном 2-м субноутбуке):

F:\>gcc b.c -o b.exe

F:\>b
to start original hit a key
finished original of 1200 frames in 16 seconds
to start alternative hit a key
finished alternative of 1200 frames in 10 seconds

F:\>gcc b.c -O3 -o b.exe

F:\>b
to start original hit a key
finished original of 1200 frames in 8 seconds
to start alternative hit a key
finished alternative of 1200 frames in 4 seconds

Ответ 6

Эта функция сборки должна делать, однако я не знаю, хотите ли вы сохранить старые данные или нет, эта функция отменяет ее.

Код для MinGW GCC с ассемблером Intel, вам придется изменить его в соответствии с вашим компилятором/ассемблером.

extern "C" {
    int convertARGBtoBGR(uint buffer, uint size);
    __asm(
        ".globl _convertARGBtoBGR\n"
        "_convertARGBtoBGR:\n"
        "  push ebp\n"
        "  mov ebp, esp\n"
        "  sub esp, 4\n"
        "  mov esi, [ebp + 8]\n"
        "  mov edi, esi\n"
        "  mov ecx, [ebp + 12]\n"
        "  cld\n"
        "  convertARGBtoBGR_loop:\n"
        "    lodsd          ; load value from [esi] (4byte) to eax, increment esi by 4\n"
        "    bswap eax ; swap eax ( A R G B ) to ( B G R A )\n"
        "    stosd          ; store 4 bytes to [edi], increment  edi by 4\n"
        "    sub edi, 1; move edi 1 back down, next time we will write over A byte\n"
        "    loop convertARGBtoBGR_loop\n"
        "  leave\n"
        "  ret\n"
    );
}

Вы должны называть его так:

convertARGBtoBGR( &buffer, IMAGESIZE );

Эта функция обращается к памяти только дважды на пиксель/пакет (1 чтение, 1 запись) по сравнению с вашим методом грубой силы, который имел (по крайней мере/при условии, что он был скомпилирован для регистрации) 3 чтения и 3 операции записи. Метод тот же, но реализация делает его более эффективным.

Ответ 7

В сочетании с одной из функций быстрого преобразования здесь, при доступе к Core 2s, было бы разумно разделить трансляцию на потоки, которые работают на их, скажем, четвертую часть данных, как в этом psudeocode:

void bulk_bgrFromArgb(byte[] dest, byte[] src, int n)
{
       thread threads[] = {
           create_thread(bgrFromArgb, dest, src, n/4),
           create_thread(bgrFromArgb, dest+n/4, src+n/4, n/4),
           create_thread(bgrFromArgb, dest+n/2, src+n/2, n/4),
           create_thread(bgrFromArgb, dest+3*n/4, src+3*n/4, n/4),
       }
       join_threads(threads);
}

Ответ 8

Вы можете сделать это в кусках 4 пикселя, перемещая 32 бита с беззнаковыми длинными указателями. Просто подумайте, что с 4 32-битными пикселями вы можете построить путем смещения и OR/AND, 3 слова, представляющих 4 24-битных пикселя, например:

//col0 col1 col2 col3
//ARGB ARGB ARGB ARGB 32bits reading (4 pixels)
//BGRB GRBG RBGR  32 bits writing (4 pixels)

Операции переключения всегда выполняются с помощью 1 цикла инструкций во всех современных 32/64 битных процессорах (метод смещения ствола), поэтому его быстрый способ построения этих трех слов для записи, побитового И и ИЛИ также быстро растет.

Вот так:

//assuming we have 4 ARGB1 ... ARGB4 pixels and  3 32 bits words,  W1, W2 and W3 to write
// and *dest  its an unsigned long pointer for destination
W1 = ((ARGB1 & 0x000f) << 24) | ((ARGB1 & 0x00f0) << 8) | ((ARGB1 & 0x0f00) >> 8) | (ARGB2 & 0x000f);
*dest++ = W1;

и т.д.... со следующими пикселями в цикле.

Вам понадобится корректировка с изображениями, которые не кратно 4, но я уверен, что это самый быстрый подход для всех, без использования ассемблера.

И btw, забудьте об использовании структур и индексированного доступа, это SLOWER способы перемещения данных, просто взгляните на демонстрационный список скомпилированной С++-программы, и вы согласитесь со мной.

Ответ 9

typedef struct{ UInt8 Alpha; UInt8 Red; UInt8 Green; UInt8 Blue; } ARGB;
typedef struct{ UInt8 Blue; UInt8 Green; UInt8 Red; } BGR;

Помимо встроенных функций компилятора, я могу попытаться сделать следующее, очень тщательно проверив конечное поведение, так как некоторые из них (с учетом профсоюзов) скорее всего будут зависимыми от реализации компилятора:

union uARGB
{
   struct ARGB argb;
   UInt32 x;
};
union uBGRA
{
   struct 
   {
     BGR bgr;
     UInt8 Alpha;
   } bgra;
   UInt32 x;
};

а затем для вашего ядра кода, при этом любая разводка цикла будет подходящей:

inline void argb2bgr(BGR* pbgr, ARGB* pargb)
{
    uARGB* puargb = (uARGB*)pargb;
    uBGRA ubgra;
    ubgra.x = __byte_reverse_32(pargb->x);
    *pbgr = ubgra.bgra.bgr;
}

где __byte_reverse_32() предполагает существование встроенного компилятора, который меняет байты 32-битного слова.

Подводя итог базовому подходу:

  • просмотр структуры ARGB как 32-битного целого
  • отмените 32-разрядное целое число
  • просмотр 32-битного целого числа в обратном направлении как структуры (BGR).
  • пусть компилятор скопирует (BGR) часть структуры (BGR) A

Ответ 10

Хотя вы можете использовать некоторые трюки, основанные на использовании ЦП,

This kind of operations can be done fasted with GPU.

Кажется, что вы используете C/С++... Поэтому ваши альтернативы для программирования графического процессора могут быть (на платформе Windows)

Вкратце используйте GPU для такого рода операций с массивами, чтобы сделать более быстрые вычисления. Они предназначены для этого.

Ответ 11

Я не видел, чтобы кто-нибудь показывал пример того, как это сделать на графическом процессоре.

Некоторое время назад я написал что-то похожее на вашу проблему. Я получил данные с камеры video4linux2 в формате YUV и хотел нарисовать ее как серые уровни на экране (только компонент Y). Я также хотел рисовать области, которые слишком темные в синих и перенасыщенных областях в красном.

Я начал с примера smooth_opengl3.c из freeglut.

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

Обратите внимание, что у меня нет опыта в том, как вы возвращаете данные. Теоретически glReadPixels должен прочитать данные назад, но я никогда не измерял его производительность.

OpenCL может быть более легким подходом, но тогда я только начну разрабатывать для него, когда у меня есть ноутбук, который его поддерживает.

(defparameter *vertex-shader*
"void main(){
    gl_Position    = gl_ModelViewProjectionMatrix * gl_Vertex;
    gl_FrontColor  = gl_Color;
    gl_TexCoord[0] = gl_MultiTexCoord0;
}
")

(progn
 (defparameter *fragment-shader*
   "uniform sampler2D textureImage;
void main()
{
  vec4 q=texture2D( textureImage, gl_TexCoord[0].st);
  float v=q.z;
  if(int(gl_FragCoord.x)%2 == 0)
     v=q.x; 
  float x=0; // 1./255.;
  v-=.278431;
  v*=1.7;
  if(v>=(1.0-x))
    gl_FragColor = vec4(255,0,0,255);
  else if (v<=x)
    gl_FragColor = vec4(0,0,255,255);
  else
    gl_FragColor = vec4(v,v,v,255); 
}
")

enter image description here