【问题标题】:How to load a pixel struct into an SSE register?如何将像素结构加载到 SSE 寄存器中?
【发布时间】:2012-08-20 17:46:12
【问题描述】:

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

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

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

【问题讨论】:

    标签: c pixel x86-64 sse intrinsics


    【解决方案1】:

    使用 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 位 有符号 整数,但我想知道 [-127,127] 中的有符号像素有什么意义。但是,如果您的像素值确实可以是负数,则与零的交错将不起作用,因为它将负 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()));
    

    这将使用1s 正确扩展负数,使用0s 扩展正数。但是,当然,只有当您的初始 8 位像素值可能为负时,才需要这种额外的开销(可能以 2-4 条额外的 SSE 指令的形式),我仍然对此表示怀疑。但如果情况确实如此,您应该考虑signed char 而不是char,因为后者具有实现定义的符号(如果这些是常见的无符号[0,255]像素值,您应该使用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 个 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 chars 时确实需要截断而不是饱和,那么您需要自己执行此操作,因为 SSE 仅提供饱和打包指令。但这可以通过做一个简单的来实现:

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

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

    【讨论】:

    • 我的像素是无符号的。我需要将它们映射到那些有符号整数中的范围 [0..255]。
    • @FUZxxl 好的,那么第一个解决方案将完美运行(也出于完全相同的目的使用它)。但是就像说的那样,考虑使用unsigned chars 而不是chars。
    • 谢谢!顺便说一句,您还知道如何撤消该转换吗?
    • 谢谢!对于我的应用程序,我不需要饱和度。有没有使用截断代替的解决方案?
    • _mm_ cvtepi8_epi32 在这里很有用。或者你可以解压成单词的高字节,然后解压成 dwords 的高字节,然后右移 24 位。
    猜你喜欢
    • 2014-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-25
    • 1970-01-01
    • 2012-01-22
    • 1970-01-01
    相关资源
    最近更新 更多