我目前正在矢量化一些代码,以使用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或1。maskstore
根本不关心掩码中的其他位,所以像素其他部分的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);
但是,这将在输出缓冲区中留下间隙,其中像素没有0xFF
alpha值。这就是你想要的吗?还是要连续存储通过测试的所有像素?在这种情况下,您可能希望获得AVX512中_mm256_mask_compressstoreu_epi32()
的效果,这在AVX2中需要模拟更多的工作。