Быстрый способ скопировать память с переводом - ARGB в BGR - PullRequest
65 голосов
/ 24 июля 2011

Обзор

У меня есть буфер изображения, который мне нужно преобразовать в другой формат. Исходный буфер изображения состоит из четырех каналов, 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 - и хотя для этого требуется переместить весь буфер в память GPU, а затем переместить результат обратно, тот факт, что OpenCL может работать одновременно на многих частях изображения, и тот факт, что большой блок памяти ходы на самом деле довольно эффективны, может сделать это исследование полезным.

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

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

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

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

Ответы [ 11 ]

55 голосов
/ 24 июля 2011

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

Первая версия использует цикл C для преобразования каждого пикселя отдельно, используя OSSwapInt32 function (которая компилируется в инструкцию 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"
    );
}

Третья версия - это модифицированная версия , просто ответ позера .Я преобразовал встроенные функции в эквиваленты 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 Mac, которые не поддерживают SSSE3.

Редактировать: liori askдля информации о стандартном отклонении.К сожалению, я не сохранил точки данных, поэтому я провел еще один тест с 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
25 голосов
/ 24 июля 2011

Очевидное, используя 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));
    }
}
15 голосов
/ 24 июля 2011

Комбинируя только ответы позера и Джитамаро, если вы предполагаете, что входы и выходы выровнены по 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 с маской и или:

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

где (1 означает выбор левого пикселя, 0означает выбор правильного пикселя): mask (0) = 111 111 111 111 000 0 mask (1) = 11 111 111 000 000 00 mask (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

Если вы переведете вышеприведенное в смещения байтов от источника к месту назначения, вы получите:d [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

Поскольку две маски тасования не перекрываются, мы можем объединить их и просто замаскировать нерелевантные пиксели в объединении_что мы уже сделали!Следующий код выполняет все эти оптимизации (плюс предполагается, что адреса источника и назначения выровнены по 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));
    }
}
11 голосов
/ 24 июля 2011

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

Вотмоя версия без встроенных платформенных или машинно-зависимых ассемблеров, я включил некоторый кроссплатформенный тайминг-код, показывающий 4x ускорение , если вы выполняете оба бит-тиддлинга, как я И активирует компилятор-оптимизация (оптимизация регистров, циклическое развертывание):

#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 subnotebook):

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 голосов
/ 24 июля 2011

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

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);
}
6 голосов
/ 24 июля 2011

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

Код для 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 операции записи.Метод тот же, но реализация делает его более эффективным.

6 голосов
/ 24 июля 2011

Вы хотите использовать устройство Даффа: http://en.wikipedia.org/wiki/Duff%27s_device. Это также работает в JavaScript. Однако, это сообщение немного забавно читать http://lkml.indiana.edu/hypermail/linux/kernel/0008.2/0171.html. Представьте себе устройство Duff с 512 Кбайт ходов.

4 голосов
/ 28 июля 2011

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

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

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

Как это:

//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, но я уверен, что это самый быстрый подход из всех, без использования ассемблера.

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

3 голосов
/ 03 августа 2011

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

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

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

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

Обратите внимание, что у меня нет опыта в том, как вернуть данные.Теоретически 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

3 голосов
/ 28 июля 2011

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

This kind of operations can be done fasted with GPU.

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

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

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