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



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

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

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

用SSE2解包无符号像素

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

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

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

xmm0 = _mm_unpacklo_epi8(xmm0, _mm_setzero_si128());

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

xmm0 = _mm_unpacklo_epi16(xmm0, _mm_setzero_si128());

现在,SSE寄存器的4个组件中的每个像素都应该是32位整数。


使用SSE2解包签名像素

我刚刚读到,您希望将这些值作为32位有符号整数,尽管我想知道[-127127127]中的有符号像素有什么意义。但是,如果你的像素值确实是负的,那么与零交织就不起作用了,因为它会将负的8位数字变成正的16位数字(从而将你的数字解释为无符号像素值)。负数必须用1s而不是0s来扩展,但不幸的是,这必须在逐个组件的基础上动态决定,在这种情况下SSE不是那么好。

你可以做的是比较负值,并使用得到的掩码(幸运的是,它使用1...1表示真,0...0表示假)作为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 s的负数和0 s的正数。但是,当然,只有当您的初始8位像素值可能是负数时,这种额外的开销(可能是2-4个额外的SSE指令的形式)才是必要的,我仍然对此表示怀疑。但如果真的是这样的话,你应该考虑signed char而不是char,因为后者具有实现定义的有符号性(同样,如果这些是常见的无符号[0255]像素值,你应该使用unsigned char)。


替代SSE2使用轮班拆包

尽管如前所述,您不需要签名8位到32位的转换,但为了完整性harold对基于SSE2的符号扩展有另一个非常好的想法,而不是使用上述基于比较的版本。我们首先将8位值解包到32位值的高位字节,而不是低位字节。由于我们不关心较低的部分,我们只需要再次使用8位值,这使我们不需要额外的零寄存器和额外的移动:

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

现在,我们只需要执行从上字节到下字节的算术右移,这对负值进行了正确的符号扩展:

xmm0 = _mm_srai_epi32(xmm0, 24);

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

由于与上述零扩展相比,它甚至应该在单个像素的指令计数上相等(尽管在多个像素上摊销时多出1条指令),并且寄存器效率更高(由于没有额外的零寄存器),因此,如果寄存器很少,它甚至可以用于无符号到有符号的转换,但随后使用逻辑移位(_mm_srli_epi32)而不是算术移位。


使用SSE4改进开箱

多亏了harold的评论,第一个8到32的转换甚至有更好的选择。如果您支持SSE4(确切地说是SSE4.1),它有指令可以将寄存器低32位中的4个压缩8位值完全转换为整个寄存器中的4位值,包括有符号和无符号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));

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

最新更新