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
Итак, как должно выглядеть combine_<x>
? Если мы предположим, что d
просто s
уплотнено вместе, мы можем объединить два s
с маской и или:
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]))
где (1 означает выбор левого пикселя, 0 означает выбор правого пикселя): маска (0) = 111 111 111 111 000 0 маска (1) = 11 111 111 000 000 00 маска (2) = 1 111 000 000 000 000
combine_x(left, right)= (left & mask(x)) | (right & ~mask(x))
Но настоящие преобразования (f_<x>_low
, f_<x>_high
) на самом деле не так просты. Поскольку мы меняем местами и удаляем байты из исходного пикселя, фактическое преобразование (для краткости в качестве первого пункта назначения):
Если вы переведете приведенное выше в байтовые смещения от источника к месту назначения, вы получите: 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
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
(Если вы посмотрите на все смещения s [0], они соответствуют только маске перетасовки позера в обратном порядке.)
Теперь мы можем сгенерировать маску перемешивания, чтобы сопоставить каждый исходный байт с целевым байтом (X
означает, что нам все равно, что это за значение):
Мы можем дополнительно оптимизировать это, просмотрев маски, которые мы используем для каждого исходного пикселя. Если вы посмотрите на маски перемешивания, которые мы используем для s [1]:
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
Поскольку две маски перемешивания не перекрываются, мы можем объединить их и просто замаскировать нерелевантные пиксели в comb_, что мы уже сделали! Следующий код выполняет все эти оптимизации (плюс он предполагает, что адреса источника и назначения выровнены по 16 байт). Кроме того, маски записываются в коде в порядке MSB-> LSB, на случай, если вы запутаетесь с порядком.
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
РЕДАКТИРОВАТЬ: изменил хранилище на _mm_stream_si128
, поскольку вы, вероятно, делаете много записей, и мы не хотим обязательно очищать кеш. Кроме того, он должен быть выровнен в любом случае, чтобы получить бесплатную производительность!
Я немного опаздываю на вечеринку, кажется, что сообщество уже решило использовать pshufb-answer от Позера, но репутация 2000 года настолько велика, что я должен попробовать.
#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));
}
}
person
MSN
schedule
24.07.2011