SSE 16 位向量中的去交错图像通道



字节 我有 32 bpp 的图像。我需要在不同的 16 位向量中解交错 R G B 颜色通道,我使用以下代码来做到这一点(如何在 SSE 中去交错图像通道)

  // deinterleave chaneel R, G, B ,A in 16 bits vectors
  {
     __m128i vrgba = _mm_loadu_si128((__m128i *)(pSrc));
     __m128i vr1 = _mm_and_si128(vrgba, _mm_set1_epi32(0xff));
     __m128i vg1 = _mm_and_si128(_mm_srli_epi32(vrgba, 8), _mm_set1_epi32(0xff));
     __m128i vb1 = _mm_and_si128(_mm_srli_epi32(vrgba, 16), _mm_set1_epi32(0xff));
     __m128i va1 = _mm_srli_epi32(vrgba, 24);
     vrgba = _mm_loadu_si128((__m128i *)(pSrc + 4));  // since pSrc is uint32_t type
     __m128i vr2 = _mm_and_si128(vrgba, _mm_set1_epi32(0xff));
     __m128i vg2 = _mm_and_si128(_mm_srli_epi32(vrgba, 8), _mm_set1_epi32(0xff));
     __m128i vb2 = _mm_and_si128(_mm_srli_epi32(vrgba, 16), _mm_set1_epi32(0xff));
     __m128i va2 = _mm_srli_epi32(vrgba, 24);
     vr = _mm_packs_epi32(vr1, vr2);
     vg = _mm_packs_epi32(vg1, vg2);
     vb = _mm_packs_epi32(vb1, vb2);
     va = _mm_packs_epi32(va1, va2);
  }

我们能提高效率吗?下面是没有去交错通道的高斯代码。我发现它效率非常低

    static inline void ConvertTo16Bits(__m128i& v1, __m128i& v2, const __m128i& v0)
    {
        __m128i const zero = _mm_setzero_si128();
        v1 = _mm_unpacklo_epi8(v0, zero);
        v2 = _mm_unpackhi_epi8(v0, zero);
    }
    static inline void mul32bits(__m128i &vh, __m128i &vl,           // output - 2x4xint32_t
        const __m128i& v0, const __m128i& v1) // input  - 2x8xint16_t
    {
        const __m128i vhi = _mm_mulhi_epu16(v0, v1);
        const __m128i vlo = _mm_mullo_epi16(v0, v1);
        vh = _mm_unpacklo_epi16(vlo, vhi);
        vl = _mm_unpackhi_epi16(vlo, vhi);
    }
    struct Pixel
    {
        unsigned char r;
        unsigned char g;
        unsigned char b;
        unsigned char a;
    };
    void computePixelvalue(unsigned int * pixelArray, int count, unsigned short * gaussArray, Pixel& out)
    {
        __m128i sumRGBA;
        sumRGBA = _mm_set1_epi32(0);
        unsigned int countMod4 = count % 4;
        unsigned int b, g, r, a;
        constexpr int shuffle = _MM_SHUFFLE(3, 1, 0, 0);
        while (count >= 4)
        {
            __m128i vrgba = _mm_loadu_si128((__m128i *)(pixelArray));
            __m128i rgba12, rgba34;
            ConvertTo16Bits(rgba12, rgba34, vrgba);
            unsigned short s1 = *gaussArray++;
            unsigned short s2 = *gaussArray++;
            __m128i shift8 = _mm_set1_epi16(s1);
            __m128i shift16 = _mm_set1_epi16(s2);
            __m128i gaussVector = _mm_shuffle_epi32(_mm_unpacklo_epi32(shift8, shift16), shuffle);
            __m128i multl, multh;
            mul32bits(multl, multh, rgba12, gaussVector);
            sumRGBA = _mm_add_epi32(sumRGBA, multl);
            sumRGBA = _mm_add_epi32(sumRGBA, multh);
            s1 = *gaussArray++;
            s2 = *gaussArray++;
            shift8 = _mm_set1_epi16(s1);
            shift16 = _mm_set1_epi16(s2);
            gaussVector = _mm_shuffle_epi32(_mm_unpacklo_epi32(shift8, shift16), shuffle);
            mul32bits(multl, multh, rgba34, gaussVector);
            sumRGBA = _mm_add_epi32(sumRGBA, multl);
            sumRGBA = _mm_add_epi32(sumRGBA, multh);
            count = count - 4;
            pixelArray = pixelArray + 4;
        }
        r = sumRGBA.m128i_u32[0];
        g = sumRGBA.m128i_u32[1];
        b = sumRGBA.m128i_u32[2];
        a = sumRGBA.m128i_u32[3];
        while (countMod4)
        {
            auto pixelArrayByte = reinterpret_cast<unsigned char*>(pixelArray);
            unsigned short k = static_cast<unsigned short>(*gaussArray++);
            r += *pixelArrayByte++ * k;
            g += *pixelArrayByte++ * k;
            b += *pixelArrayByte++ * k;
            a += *pixelArrayByte++ * k;
            countMod4--;
        }
        out.r = static_cast<unsigned char>(r >> 15);
        out.g = static_cast<unsigned char>(g >> 15);
        out.b = static_cast<unsigned char>(b >> 15);
        out.a = static_cast<unsigned char>(a >> 15);
    }

{ a b g r ... } 的向量pshufb 个向量转换为 { a a a a b b b b g g g g r r r r } 向量(每个源向量一个 pshufb)。

在两个随机的源向量之间punpckldq以获得{ g2g2g2g2 g1g1g1g1 r2r2r2r2 r1r1r1r1 }pmovzxbw低半部分,用零解开高半部分,得到 g 和 r 的向量。

同样,punpckhdq相同的两个源向量来获得{ a2a2a2a2 a1a1a1a1 b2b2b2b2 b1b1b1b1 }

因此,每 4 个输入向量(产生 8 个输出向量),即:

  • 4x pshufb(全部使用相同的控制掩码)
  • 2x 平普卡/升深
  • 4x punpckhh/l bw(或用 pmovzxbw 替换其中 2 个)

总共 10 条 ALU 指令,不包括任何复制以避免破坏仍然需要的数据。

这与掩模/移位/包装方法所需的总共 32 条指令相比相当不错。 (如果没有 AVX,这将涉及相当多的复制,以 4 种不同的方式掩盖相同的矢量。 这些指令中有 8 条是pack随机指令,因此在随机播放端口上的压力会稍微小一点,以换取更多的总指令。

Haswell 只能在一个执行端口上洗牌,该端口与位移不同。 (_mm_and可以在三个矢量执行端口中的任何一个上运行)。 我非常有信心 10 次洗牌方式将以相当的优势获胜,因为更多的计算可以与之重叠。


shufps作为来自两个源向量的随机播放可能很有用,但它具有 32 位粒度,所以我看不到它的用途。 在英特尔 SnB 家族和 AMD 推土机家族上,在整数向量指令之间使用它不会受到任何惩罚。


另一个想法:

__m128i rgba1 = _mm_loadu_si128((__m128i *)(pSrc));   // { a1.4 b1.4 g1.4 r1.4 ... a1.1 b1.1 g1.1 r1.1 }
__m128i rgba2 = _mm_loadu_si128((__m128i *)(pSrc+4)); // { a2.4 b2.4 ... g2.1 r2.1 }
 __m128i rg1 = _mm_and_si128 (rgba1, _mm_set1_epi32(0xffff));
 __m128i rg2 = _mm_slli_epi32(rgba2, 16);
 __m128i rg_interleaved = _mm_or_si128(rg2, rg1);    // { g2.4 r2.4  g1.4 r1.4 ... g2.1 r2.1  g1.1 r1.1 }

rg_interleaved分离为具有另一个_mm_and_si128和一个_mm_srli_epi16的零扩展 16 位 r 和 g 向量。

最新更新