使用 SIMD,如何有条件地仅移动 alpha 通道值为 255 的像素?
Using SIMD, how do I conditionally move only the pixels with an alpha channel value of 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);
现在我只想移动 alpha 通道(“AA”组件)为 255 的像素。我不想进行 alpha 混合。我只想将具有 0xFF 的像素存储为 alpha 值。
我想我可以使用掩码和 _mm256_maskstore_epi32()
函数来做到这一点,但经过几个小时的尝试我还没有弄明白。
谢谢
我不确定这是否完全回答了您的问题,但此比较与 __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 中需要更多的工作来模拟。
首先,请注意 _mm256_maskstore_epi32
在 AMD Zen / Zen2 上非常慢,例如 19 微指令和每 6 周期吞吐量 1。 (https://uops.info/)。掩码加载很好,但 掩码存储仅在 Intel 硬件上有效。您可能想要与原始值混合并进行完整矢量存储。
maskstore以32位元素的高位作为是否存储的控制
因此,您需要创建一个向量,当 alpha 恰好 == 0xFF
.
时设置该位
方便的是8位alpha已经在32位元素的顶部,所以它的高位是整个32位元素的控制位。我们可以只使用 packed-8-bit 比较相等性来将 alpha 通道的所有位(包括高位)设置为 0 或 1,根据整个 alpha 字节是 0xFF
。 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)
使得常量的创建成本更低:编译器可以通过将寄存器与自身进行比较来创建 all-ones,而不是从内存中加载常量。 (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
,您可能不得不 range-shift 在 vpcmpgtb
_mm256_cmpgt_epi8 之前签名(通过减法或异或运算 0x80
)。调整后,您可以进行双字整数比较以创建 32 位掩码元素,因此您 可以 将其与 vpblendvb
(整数 byte-blend)一起使用。
如果 alpha 在 32 位元素中处于不同的位置,left-shift 在比较之前。
顺便说一句,如果您将像素存储在找到它们的位置,您还可以考虑 vblendvps
在常规存储之前使用原始数据,而不是 maskstore。
没有 32 位粒度整数混合,因此您必须 _mm256_castsi256_ps
让编译器对 __m256i
变量使用 _mm256_blendv_ps
感到满意。
在大多数 CPU 上,FP 混合将花费一个额外的周期或 2 个旁路延迟,但只要 OoO exec 可以隐藏该延迟,就不会产生吞吐量成本,这在您处理独立的像素向量时很可能发生。但是这样做比 vpxor
/ vpcmpgtd
节省指令以设置 vpblendvb
.
避免 maskstore 在 AMD 上非常好。
我目前正在矢量化一些代码以使用 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);
现在我只想移动 alpha 通道(“AA”组件)为 255 的像素。我不想进行 alpha 混合。我只想将具有 0xFF 的像素存储为 alpha 值。
我想我可以使用掩码和 _mm256_maskstore_epi32()
函数来做到这一点,但经过几个小时的尝试我还没有弄明白。
谢谢
我不确定这是否完全回答了您的问题,但此比较与 __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 中需要更多的工作来模拟。
首先,请注意 _mm256_maskstore_epi32
在 AMD Zen / Zen2 上非常慢,例如 19 微指令和每 6 周期吞吐量 1。 (https://uops.info/)。掩码加载很好,但 掩码存储仅在 Intel 硬件上有效。您可能想要与原始值混合并进行完整矢量存储。
maskstore以32位元素的高位作为是否存储的控制
因此,您需要创建一个向量,当 alpha 恰好 == 0xFF
.
方便的是8位alpha已经在32位元素的顶部,所以它的高位是整个32位元素的控制位。我们可以只使用 packed-8-bit 比较相等性来将 alpha 通道的所有位(包括高位)设置为 0 或 1,根据整个 alpha 字节是 0xFF
。 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)
使得常量的创建成本更低:编译器可以通过将寄存器与自身进行比较来创建 all-ones,而不是从内存中加载常量。 (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
,您可能不得不 range-shift 在 vpcmpgtb
_mm256_cmpgt_epi8 之前签名(通过减法或异或运算 0x80
)。调整后,您可以进行双字整数比较以创建 32 位掩码元素,因此您 可以 将其与 vpblendvb
(整数 byte-blend)一起使用。
如果 alpha 在 32 位元素中处于不同的位置,left-shift 在比较之前。
顺便说一句,如果您将像素存储在找到它们的位置,您还可以考虑 vblendvps
在常规存储之前使用原始数据,而不是 maskstore。
没有 32 位粒度整数混合,因此您必须 _mm256_castsi256_ps
让编译器对 __m256i
变量使用 _mm256_blendv_ps
感到满意。
在大多数 CPU 上,FP 混合将花费一个额外的周期或 2 个旁路延迟,但只要 OoO exec 可以隐藏该延迟,就不会产生吞吐量成本,这在您处理独立的像素向量时很可能发生。但是这样做比 vpxor
/ vpcmpgtd
节省指令以设置 vpblendvb
.
避免 maskstore 在 AMD 上非常好。