Как загрузить структуру пикселей в регистр SSE?

У меня есть структура 8-битных данных:

struct __attribute__((aligned(4))) pixels {
    char r;
    char g;
    char b;
    char a;
}

Я хочу использовать инструкции SSE для вычисления определенных вещей на этих пикселях (а именно, преобразования Paeth). Как загрузить эти пиксели в регистр SSE в виде 32-разрядных целых чисел без знака?

Ответ 1

Распаковка неподписанных пикселей с помощью SSE2

Хорошо, используя SSE2 integer intrinsics от <emmintrin.h>, сначала загрузите вещь в младшие 32 бита регистра:

__m128i xmm0 = _mm_cvtsi32_si128(*(const int*)&pixel);

Затем сначала распакуйте эти 8-битные значения в 16-битные значения в младших 64 битах регистра, чередуя их с 0s:

xmm0 = _mm_unpacklo_epi8(xmm0, _mm_setzero_si128());

И снова распакуйте эти 16-битные значения в 32-битные значения:

xmm0 = _mm_unpacklo_epi16(xmm0, _mm_setzero_si128());

Теперь вы должны иметь каждый пиксель как 32-битное целое число в соответствующих 4 компонентах регистра SSE.


Распаковка подписанных пикселей с помощью SSE2

Я просто прочитал, что вы хотите получить эти значения в виде 32-битных подписанных целых чисел, хотя мне интересно, какой смысл имеет подписанный пиксель в [-127,127]. Но если ваши значения пикселей могут быть отрицательными, чередование с нулями не будет работать, поскольку оно делает отрицательное 8-битное число в положительное 16-битное число (таким образом, интерпретирует ваши числа как значения без знака). Отрицательное число должно быть расширено с помощью 1 вместо 0 s, но, к сожалению, это должно решаться динамически на основе компонента по компонентам, при котором SSE не так хорош.

Что вы можете сделать, это сравнить значения отрицательности и использовать полученную маску (которая, к счастью, использует 1...1 для true и 0...0 для false) как interleavand вместо нулевого регистра:

xmm0 = _mm_unpacklo_epi8(xmm0, _mm_cmplt_epi8(xmm0, _mm_setzero_si128()));
xmm0 = _mm_unpacklo_epi16(xmm0, _mm_cmplt_epi16(xmm0, _mm_setzero_si128()));

Это будет правильно расширять отрицательные числа с помощью 1 и положительных значений с помощью 0 s. Но, конечно, эти дополнительные накладные расходы (в виде, вероятно, 2-4 дополнительных инструкций SSE) необходимы только в том случае, если ваши начальные 8-битные значения пикселей могут быть отрицательными, и я все еще сомневаюсь. Но если это действительно так, вам стоит рассмотреть signed char над char, так как последний имеет определенную приложением подпись (таким же образом вы должны использовать unsigned char, если это обычные значения unsigned [0,255] пикселей).


Альтернативная распаковка SSE2 с использованием сдвигов

Хотя, как выяснилось, вам не требуется преобразование с 8-битным и 32-битным символом с 8-битным, но для полноты гарольда была еще одна очень хорошая идея для расширения знака на основе SSE2, вместо использования вышеперечисленного упомянутый сравнение на основе версия. Сначала мы распаковываем 8-битные значения в верхний байт 32-битных значений вместо младшего байта. Поскольку мы не заботимся о нижних частях, мы снова используем 8-битные значения, что освобождает нас от необходимости добавления нулевого регистра и дополнительного перемещения:

xmm0 = _mm_unpacklo_epi8(xmm0, xmm0);
xmm0 = _mm_unpacklo_epi16(xmm0, xmm0);

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

xmm0 = _mm_srai_epi32(xmm0, 24);

Это должно быть больше количества команд и регистрации, чем моя выше версия SSE2.

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


Улучшена распаковка с помощью SSE4

Благодаря замечанию Harold, есть даже лучший вариант для первого преобразования от 8 до 32. Если у вас есть поддержка SSE4 (точнее, SSE4.1), в которой есть инструкции для полного преобразования из 4 упакованных 8-битных значений в младших 32 битах регистра в 4 32-битных значения во всем регистре, как для подписанные и неподписанные 8-битные значения:

xmm0 = _mm_cvtepu8_epi32(xmm0);   //or _mm_cvtepi8_epi32 for signed 8-bit values

Упаковочные пиксели с SSE2

Что касается последующего изменения этого преобразования, сначала мы собираем подписанные 32-разрядные целые числа в 16-битные целочисленные числа и насыщаем:

xmm0 = _mm_packs_epi32(xmm0, xmm0);

Затем мы собираем эти 16-битные значения в 8-битные значения без знака с использованием насыщения:

xmm0 = _mm_packus_epi16(xmm0, xmm0);

Наконец, мы можем взять наш пиксель из нижних 32-бит регистра:

*(int*)&pixel = _mm_cvtsi128_si32(xmm0);

Из-за насыщенности весь этот процесс автоматически сопоставляет любые отрицательные значения с 0 и любыми значениями, превышающими 255 до 255, которые обычно используются при работе с цветными пикселями.

Если вам понадобится усечение вместо насыщения при упаковывании 32-битных значений обратно в unsigned char s, вам нужно будет сделать это самостоятельно, поскольку SSE предоставляет только насыщающие инструкции по упаковке. Но это может быть достигнуто путем простого:

xmm0 = _mm_and_si128(xmm0, _mm_set1_epi32(0xFF));

непосредственно перед вышеуказанной процедурой упаковки. Это должно составлять всего две дополнительные инструкции SSE или только 1 дополнительная инструкция при амортизации по многим пикселям.