c-使用SIMD,如何有条件地仅移动alpha通道值为255的像素



我目前正在矢量化一些代码,以使用AVX2内部函数存储32位像素数据。由于AVX2寄存器是256位,所以我可以同时对8个像素进行操作。我目前的代码是从一个缓冲区加载8个像素,然后将它们存储到另一个缓冲:

// Load 256 bits (8 pixels) from memory into register YMMx           
BitmapOctoPixel = _mm256_load_si256((const __m256i*)((PIXEL32*)GameBitmap->Memory + BitmapOffset));
// adjust the colors
// As an example, the YMM0 register currently holds these pixels:
//        AARRGGBBAARRGGBB-AARRGGBBAARRGGBB-AARRGGBBAARRGGBB-AARRGGBBAARRGGBB
// YMM0 = FF33281EFF000000-FF33281E00FFFFFF-00FFFFFF00FFFFFF-00FFFFFF00FFFFFF
// store the result into the destination buffer
_mm256_store_si256((__m256i*)((PIXEL32*)gBackBuffer.Memory + MemoryOffset), BitmapOctoPixel);

现在我只想移动阿尔法通道("AA"分量(为255的像素。我不想做阿尔法混合。我只想将具有0xFF的像素存储为alpha值。

我想我可以使用掩码和_mm256_maskstore_epi32()函数来实现这一点,但经过几个小时的尝试,我还没能弄清楚。

感谢

首先,请注意,_mm256_maskstore_epi32在AMD Zen/Zen2上相当慢,比如19个uops和每6个周期一个吞吐量。(https://uops.info/)。屏蔽加载可以,但屏蔽存储仅在英特尔硬件上有效您可能希望与原始值混合,并进行完整的矢量存储。


maskstore使用32位元素的高位作为存储或不存储的控制
因此,您需要创建一个向量,该向量在alpha恰好为==0xFF时设置该位。

方便的是,8位alpha已经位于32位元素的顶部,因此它的高位是整个32位元素中的控制位。我们可以使用打包的8位比较来实现相等,根据整个阿尔法字节为0xFF,将阿尔法通道的所有比特(包括高位(设置为0或1maskstore根本不关心掩码中的其他位,所以像素其他部分的8位比较结果基本上是垃圾也没关系。


void store_opaque_only(void *dst, __m256i pixels)
{
// As an example, the YMM0 register currently holds these pixels:
//        AARRGGBBAARRGGBB-AARRGGBBAARRGGBB-AARRGGBBAARRGGBB-AARRGGBBAARRGGBB
// YMM0 = FF33281EFF000000-FF33281E00FFFFFF-00FFFFFF00FFFFFF-00FFFFFF00FFFFFF
__m256i opaque = _mm256_cmpeq_epi8(pixels, _mm256_set1_epi8(-1));
_mm256_maskstore_epi32(dst, opaque, pixels);
}

set1_epi8(-1)而不是set1_epi32(0xFF000000)使常数的创建成本更低:编译器可以通过将寄存器与自身进行比较来创建全1,而不是从内存加载常数。(Godbolt;当然,这个函数将在实际用例中内联。(

# gcc10.2 -O3 -march=skylake
store_opaque_only:
vpcmpeqd        ymm1, ymm1, ymm1           # all-ones
vpcmpeqb        ymm1, ymm0, ymm1           # opaque =  pixels == -1
vpmaskmovd      YMMWORD PTR [rdi], ymm1, ymm0
ret

内联之后,all-ones向量可以从循环中取出。


如果您不需要完全相等,例如alpha >= 0xF0,您可能必须在vpcmpgtb_mm256_cmpgt_epi8之前将范围移位到有符号(通过减去或异或取0x80(。调整后,您可以进行双字整数比较以创建32位掩码元素,因此可以将其与vpblendvb(整数字节混合(一起使用。

如果alpha在32位元素中处于不同的位置,请在比较之前左移。

顺便说一句,如果你把像素存储在找到它们的地方,你也可以考虑在常规存储之前使用原始数据的vblendvps,而不是maskstore。

没有32位粒度的整数混合,所以必须使用_mm256_castsi256_ps才能让编译器对在__m256i变量上使用_mm256_blendv_ps感到满意。

在大多数CPU上,FP混合将花费一个额外的周期或2个旁路延迟,但只要OoO exec可以隐藏延迟,就不会产生吞吐量成本,这可能是在处理独立的像素向量时发生的。但是,与vpxor/vpcmpgtd相比,这样做可以节省为vpblendvb设置的指令。

避免maskstore对AMD非常好

我不确定这是否完全回答了您的问题,但这种比较与__m256_maskstore_epi32()兼容,我假设out_ptr指向您想要存储的位置:

// As an example, the YMM0 register currently holds these pixels:
//        AARRGGBBAARRGGBB-AARRGGBBAARRGGBB-AARRGGBBAARRGGBB-AARRGGBBAARRGGBB
// YMM0 = FF33281EFF000000-FF33281E00FFFFFF-00FFFFFF00FFFFFF-00FFFFFF00FFFFFF
// compare every 8-bit value against 0xFF; for pixels that have this value in their alpha
// channel, the corresponding byte in alpha_mask will be 0xFF
__m256i mask = _mm256_cmpeq_epi8(BitmapOctoPixel, _mm256_set1_epi8(0xFF));
// now, you can use the masked store directly; the high bit in each 32-bit pixel is used
// to determine whether to do the store
__m256_maskstore_si256((__m256i *) out_ptr, mask, BitmapOctoPixel);

但是,这将在输出缓冲区中留下间隙,其中像素没有0xFFalpha值。这就是你想要的吗?还是要连续存储通过测试的所有像素?在这种情况下,您可能希望获得AVX512中_mm256_mask_compressstoreu_epi32()的效果,这在AVX2中需要模拟更多的工作。

最新更新