Быстрый метод копирования памяти с переводом - 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
Ответ 5
Вы хотите использовать устройство Duff: http://en.wikipedia.org/wiki/Duff%27s_device. Он также работает в JavaScript. Это сообщение, однако, немного забавно читать http://lkml.indiana.edu/hypermail/linux/kernel/0008.2/0171.html. Представьте себе устройство Duff с 512 килобайтами ходов.
Ответ 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]()