如何将像素结构加载到SSE寄存器中?

fuz*_*fuz 11 c sse x86-64 pixel intrinsics

我有一个8位像素数据的结构:

struct __attribute__((aligned(4))) pixels {
    char r;
    char g;
    char b;
    char a;
}
Run Code Online (Sandbox Code Playgroud)

我想使用SSE指令来计算这些像素上的某些东西(即Paeth变换).如何将这些像​​素作为32位无符号整数加载到SSE寄存器中?

Chr*_*ica 19

使用SSE2解压缩无符号像素

好的,使用SSE2整数内在函数<emmintrin.h>首先将东西加载到寄存器的低32位:

__m128i xmm0 = _mm_cvtsi32_si128(*(const int*)&pixel);
Run Code Online (Sandbox Code Playgroud)

然后首先将这些8位值解压缩到寄存器的低64位中的16位值,并将它们与0交错:

xmm0 = _mm_unpacklo_epi8(xmm0, _mm_setzero_si128());
Run Code Online (Sandbox Code Playgroud)

再次将这些16位值解压缩为32位值:

xmm0 = _mm_unpacklo_epi16(xmm0, _mm_setzero_si128());
Run Code Online (Sandbox Code Playgroud)

您现在应该在SSE寄存器的相应4个组件中将每个像素设置为32位整数.


使用SSE2解压缩带符号的像素

我刚才读到,你想要将这些值作为32位有符号整数,但我想知道[-127,127]中有符号像素的含义.但是,如果您的像素值确实可以为负,则使用零进行交错将不起作用,因为它将负8位数字转换为正16位数(因此将您的数字解释为无符号像素值).负数必须用1s而不是0s 来扩展,但不幸的是,这必须在逐个组件的基础上动态决定,SSE不是那么好.

你可以做的是比较否定性的值并使用结果掩码(幸运的是1...1用于true和0...0false)作为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()));
Run Code Online (Sandbox Code Playgroud)

这将适当地扩展带有1s的负数和带有s的正数0.但是,如果你的初始8位像素值可能是负数,那么这个额外的开销(可能是2-4个额外的SSE指令)只是必要的,我仍然怀疑.但如果确实如此,你应该考虑signed char一下char,因为后者有实现定义的符号(unsigned char如果那些是常见的无符号[0,255]像素值,你应该使用相同的方式).


替代SSE2使用轮班拆包

虽然,澄清,你不需要签名8位到32位转换,但为了完整性,哈罗德对基于SSE2的符号扩展有另一个非常好的想法,而不是使用上面提到的基于比较版.我们首先将8位值解压缩到32位值的高字节而不是低位字节.由于我们不关心较低的部分,我们只需再次使用8位值,这使我们无需额外的零寄存器和额外的移动:

xmm0 = _mm_unpacklo_epi8(xmm0, xmm0);
xmm0 = _mm_unpacklo_epi16(xmm0, xmm0);
Run Code Online (Sandbox Code Playgroud)

现在我们只需要将高位字节右移到低位字节,并对负值进行正确的符号扩展:

xmm0 = _mm_srai_epi32(xmm0, 24);
Run Code Online (Sandbox Code Playgroud)

这应该比我上面的SSE2版本更多的指令数和寄存器效率.

因为它甚至应该在单个像素的指令计数中相等(尽管在多个像素上分摊时多一个指令)和更高的寄存器效率(由于没有额外的零寄存器)与上述零扩展相比,它甚至可能是如果寄存器很少,则用于无符号到符号的转换,但是使用逻辑shift(_mm_srli_epi32)而不是算术移位.


改进了SSE4的拆包

感谢哈罗德的评论,对于第一次8到32次转换,甚至有更好的选择.如果你有SSE4支持(准确的SSE4.1),它有完成从寄存器低32位的4个压缩8位值到整个寄存器中的4个32位值的完整转换的指令,两者都是有符号和无符号的8位值:

xmm0 = _mm_cvtepu8_epi32(xmm0);   //or _mm_cvtepi8_epi32 for signed 8-bit values
Run Code Online (Sandbox Code Playgroud)

用SSE2打包像素

至于反转此转换的后续操作,首先我们将带符号的32位整数打包成带符号的16位整数并使其饱和:

xmm0 = _mm_packs_epi32(xmm0, xmm0);
Run Code Online (Sandbox Code Playgroud)

然后我们使用饱和将这些16位值打包成无符号的8位值:

xmm0 = _mm_packus_epi16(xmm0, xmm0);
Run Code Online (Sandbox Code Playgroud)

然后我们最终可以从寄存器的低32位获取像素:

*(int*)&pixel = _mm_cvtsi128_si32(xmm0);
Run Code Online (Sandbox Code Playgroud)

由于饱和度,整个过程将自动将任何负值映射到0大于255to的任何值255,这通常用于处理彩色像素时.

如果在将32位值打包回unsigned chars 时实际上需要截断而不是饱和,那么您需要自己执行此操作,因为SSE仅提供饱和打包指令.但这可以通过简单的方法来实现:

xmm0 = _mm_and_si128(xmm0, _mm_set1_epi32(0xFF));
Run Code Online (Sandbox Code Playgroud)

在上述包装程序之前.这应该只相当于2个额外的SSE指令,或者在许多像素上分摊时只有1个附加指令.

  • `_mm_ cvtepi8_epi32`在这里很有用.或者你可以解压缩到高字节的单词,然后打开dwords的高字,然后签名 - 右移24. (2认同)