使用 SSE2 模拟 packusdw 功能



我正在根据英特尔描述的算法在 pixman 中实现快速 x888 -> 565 像素转换功能[pdf]。他们的代码将 x888 转换为 -> 555,而我想转换为 565。不幸的是,转换为 565 意味着设置了高位,这意味着我无法使用符号饱和包指令。未签名的包指令,packusdw直到 SSE4.1 才添加。我想用SSE2实现它的功能,或者找到另一种方法。

此功能采用两个 XMM 寄存器,每个寄存器包含 4 个 32 位像素,并输出一个包含 8 个转换后的 RGB565 像素的 XMM 寄存器。

static force_inline __m128i
pack_565_2packedx128_128 (__m128i lo, __m128i hi)
{
    __m128i rb0 = _mm_and_si128 (lo, mask_565_rb);
    __m128i rb1 = _mm_and_si128 (hi, mask_565_rb);
    __m128i t0 = _mm_madd_epi16 (rb0, mask_565_pack_multiplier);
    __m128i t1 = _mm_madd_epi16 (rb1, mask_565_pack_multiplier);
    __m128i g0 = _mm_and_si128 (lo, mask_green);
    __m128i g1 = _mm_and_si128 (hi, mask_green);
    t0 = _mm_or_si128 (t0, g0);
    t1 = _mm_or_si128 (t1, g1);
    t0 = _mm_srli_epi32 (t0, 5);
    t1 = _mm_srli_epi32 (t1, 5);
    /* XXX: maybe there's a way to do this relatively efficiently with SSE2? */
    return _mm_packus_epi32 (t0, t1);
}

我想到的想法:

  • 减去0x8000,_mm_packs_epi32,将0x8000重新添加到每个 565 个像素。我已经尝试过了,但我无法完成这项工作。

      t0 = _mm_sub_epi16 (t0, mask_8000);
      t1 = _mm_sub_epi16 (t1, mask_8000);
      t0 = _mm_packs_epi32 (t0, t1);
      return _mm_add_epi16 (t0, mask_8000);
    
  • 随机播放数据而不是打包数据。适用于 MMX,但由于 SSE 16 位洗牌仅适用于高位或低 64 位,因此它会变得混乱。

  • 保存高位,将它们设置为零,执行打包,然后恢复它们。似乎很乱。

还有其他(希望更有效)的方法可以做到这一点吗?

您可以

先对值进行签名扩展,然后使用_mm_packs_epi32

t0 = _mm_slli_epi32 (t0, 16);
t0 = _mm_srai_epi32 (t0, 16);
t1 = _mm_slli_epi32 (t1, 16);
t1 = _mm_srai_epi32 (t1, 16);
t0 = _mm_packs_epi32 (t0, t1);

您实际上可以将其与前面的班次结合起来以保存两条指令:

t0 = _mm_slli_epi32 (t0, 16 - 5);
t0 = _mm_srai_epi32 (t0, 16);
t1 = _mm_slli_epi32 (t1, 16 - 5);
t1 = _mm_srai_epi32 (t1, 16);
t0 = _mm_packs_epi32 (t0, t1);

最新更新