X86 SSE 16位矢量中的去交织图像通道

X86 SSE 16位矢量中的去交织图像通道,x86,sse,simd,intrinsics,sse2,X86,Sse,Simd,Intrinsics,Sse2,字节我有32个bpp的图像。我需要在不同的16位向量中去交织器G B颜色通道,我正在使用下面的代码来做这件事() 我们能提高效率吗?下面是没有去交错通道的高斯信号的代码。我发现它效率非常低 static inline void ConvertTo16Bits(__m128i& v1, __m128i& v2, const __m128i& v0) { __m128i const zero = _mm_setzero_si128();

字节我有32个bpp的图像。我需要在不同的16位向量中去交织器G B颜色通道,我正在使用下面的代码来做这件事()

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

    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);
    }
静态内联无效转换为16位(_m128i&v1,_m128i&v2,const _m128i&v0)
{
__m128i常量零=_mm_setzero_si128();
v1=_mm_explo_epi8(v0,零);
v2=_mm_exphi_epi8(v0,零);
}
静态内联无效多32位(_m128i和vh,_m128i和vl,//输出-2x4xint32
常量m128i和v0,常量m128i和v1)//输入-2x8xint16
{
常数m128i vhi=_mm_mulhi_epu16(v0,v1);
常数m128i vlo=_mm_mullo_epi16(v0,v1);
vh=_mm_unplo_epi16(vlo,vhi);
vl=_mm_unpachi_epi16(vlo,vhi);
}
结构像素
{
无符号字符r;
无符号字符g;
无符号字符b;
无符号字符a;
};
void computePixelvalue(无符号整数*像素数组,整数计数,无符号短*高斯射线,像素和输出)
{
__m128i-sumRGBA;
sumRGBA=_mm_set1_epi32(0);
unsigned int countMod4=计数%4;
无符号整数b,g,r,a;
constexpr int shuffle=_MM_shuffle(3,1,0,0);
而(计数>=4)
{
__m128i vrgba=_mm_loadu_si128((__m128i*)(像素阵列));
__m128i rgba12、rgba34;
转换为16比特(rgba12、rgba34、vrgba);
无符号短s1=*gaussArray++;
无符号短s2=*gaussArray++;
__m128i移位8=_mm_设置1_epi16(s1);
__m128i移位16=_mm_设置1_epi16(s2);
__m128i高斯向量=_mm_shuffle_epi32(_mm_unplo_epi32(移位8,移位16),shuffle);
__m128i multl,multh;
mul32位(multl、multh、rgba12、gaussVector);
sumRGBA=_mm_add_epi32(sumRGBA,multl);
sumRGBA=_mm_add_epi32(sumRGBA,multh);
s1=*高斯射线++;
s2=*高斯射线++;
移位8=_mm_设置1_epi16(s1);
移位16=_mm_set1_epi16(s2);
高斯向量=_mm_shuffle_epi32(_mm_unplo_epi32(移位8,移位16),shuffle);
mul32位(multl、multh、rgba34、gaussVector);
sumRGBA=_mm_add_epi32(sumRGBA,multl);
sumRGBA=_mm_add_epi32(sumRGBA,multh);
计数=计数-4;
像素阵列=像素阵列+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=重新解释投影(pixelArray);
无符号短k=静态_转换(*gaussArray++);
r++=*像素阵列字节+++*k;
g++=*像素阵列字节+++*k;
b++=*像素阵列字节+++*k;
a++=*像素阵列字节+++*k;
countMod4--;
}
out.r=静态_cast(r>>15);
out.g=静态_cast(g>>15);
out.b=静态(b>>15);
out.a=静态_cast(a>>15);
}

pshufb
{abg r…}
的向量转换为
{aba a b b b g g g g r r}
(每个源向量一个pshufb)

punpckldq
在两个无序源向量之间,以获得
{G2G2G2G2G2G1G1G1G1G1G1G1R2R2R2 R1R1}
pmovzxbw
低半部分,用零解压高半部分,得到g和r的向量

类似地,
punpckhdq
使用相同的两个源向量来获得
{a2a2a2a2a1a1a1b2b2b2b1b1b1b1b1}

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

  • 4x pshufb(均使用相同的控制掩码)
  • 2x蓬普克/升dq
  • 4x磅/升bw(或用pmovzxbw替换其中2个)
总共10条ALU指令,不包括任何复制,以避免破坏仍然需要的数据

这与mask/shift/pack方法所需的总共32条指令相比非常好。(如果没有AVX,这将需要相当多的复制来屏蔽相同的向量4种不同的方式。)这些指令中有8条是
pack
shuffle指令,因此它对shuffle端口的压力稍微小一点,以换取更多的总指令

Haswell只能在一个执行端口上洗牌,该端口与位移位不同。(和
\u mm_和
可以在三个向量执行端口中的任何一个上运行)。我很有信心,10次洗牌的方式将以公平的优势获胜,因为有太多的计算可能与之重叠


shufps
作为来自两个源向量的洗牌可能很有用,但是它有32位的粒度,所以我看不出它有什么用处。在Intel 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 }

用另一个
\u mm\u和
和一个
\u mm\u srli\u epi16
pshufb
{a b g r…}
的零扩展16位r和g向量分离为
{a a a b b b g g g r r}/code>的向量(每个源向量一个pshufb)

punpckldq
在两个无序源向量之间,以获得
{G2G2G2G2G2G1G1G1G1G1G1G1R2R2R2 R1R1}
pmovzxbw
低半部分,用零解压高半部分,得到g和r的向量

类似地,
punpckhdq
使用相同的两个源向量来获得
{a2a2a2a2a1a1a1b2b2b2b1b1b1b1b1}

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

  • 4x pshufb(均使用相同的控制掩码)
  • 2x蓬普克/升dq
  • 4x磅/升bw(或用pmovzxbw替换其中2个)
总共10条ALU指令,不包括任何
__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 }