【发布时间】:2020-02-15 23:10:40
【问题描述】:
我有一个 c++(或类 c)函数,我正在尝试对其进行矢量化。该功能是图像合成的众多变体之一,它采用具有色度 444 子采样的 Y、U 或 V 图像平面,并将 src 图像合成/叠加到 dst 图像上(其中 src 图像还包含 alpha 透明度)。
#include <cstdint>
void composite(uint8_t *__restrict__ pSrc, // Source plane
uint8_t *__restrict__ pSrcA, // Source alpha plane
uint8_t *__restrict__ pDst, // Destination plane
const std::size_t nCount) // Number of component pixels to process.
{
for (std::size_t k = 0; k < nCount; ++k)
{
uint16_t w = (pSrc[k] * pSrcA[k]);
uint16_t x = (255 - pSrcA[k]) * pDst[k];
uint16_t y = w+x;
uint16_t z = y / uint16_t{255};
pDst[k] = static_cast<uint8_t>(z);
}
}
在 AVX2 矢量化等效项中,我正在努力理解如何有效地读取 8 位转换为 16 位以及(在处理/合成之后)最终将 16 位样本转换回 8 位以存储回来到记忆中。在读取方面,我使用的是中间 xmm 寄存器——这似乎不是最好的方法;我猜在混合寄存器系列时会有性能损失。
我想出了(不完整):
#include <cstdint>
#include <immintrin.h>
#include <emmintrin.h>
///////////////////////////////////////////////////////////////////////////
// Credit: https://stackoverflow.com/questions/35285324/how-to-divide-16-bit-integer-by-255-with-using-sse
#define AVX2_DIV255_U16(x) _mm256_srli_epi16(_mm256_mulhi_epu16(x, _mm256_set1_epi16((short)0x8081)), 7)
///////////////////////////////////////////////////////////////////////////
/// Blends/composites/overlays two planes of Y, U, or V plane with 4:4:4 chroma subsampling over the other.
/// \param d The destination Y, U , or V component
/// \param s The source Y, U, or V component
/// \param sa The source alpha component
/// \param pixels The number of pixels that require processing.
/// \return The number of pixels processed.
int blend_plane_pixels_444_vectorized(uint8_t *__restrict__ d,
uint8_t *__restrict__ s,
uint8_t *__restrict__ sa,
const int pixels)
{
int n = 0; // Return number of component pixels processed.
for (int k = 0; k + 32 <= pixels; k += 32)
{
// Load first 16 (unaligned) of d, s, sa
// TODO: This efficient mixing xmm registers with ymm??
auto vecD0 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i_u *)d));
auto vecS0 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i_u *)s));
auto vecSa0 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i_u *)sa));
// Load second 16 (unaligned) of d, s, sa
auto vd1 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i_u *)d+16));
auto vs1 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i_u *)s+16));
auto vsa1 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i_u *)sa+16));
// Load 255 into register
auto vec255 = _mm256_set1_epi16(255);
// uint16_t w = (pSrc[k] * pSrcA[k]);
auto vecW0 = _mm256_mullo_epi16(vecS0, vecSa0);
auto vecW1 = _mm256_mullo_epi16(vs1, vsa1);
// uint16_t x = (255 - pSrcA[k]) * pDst[k];
auto vecX0 = _mm256_mullo_epi16(_mm256_subs_epu16(vec255, vecSa0), vecD0);
auto vecX1 = _mm256_mullo_epi16(_mm256_subs_epu16(vec255, vsa1), vd1);
// Load 127 into register
auto vec127 = _mm256_set1_epi16(127);
// uint16_t y = w+x;
auto vecY0 = _mm256_adds_epu16(_mm256_adds_epu16(vecW0, vecX0), vec127);
auto vecY1 = _mm256_adds_epu16(_mm256_adds_epu16(vecW1, vecX1), vec127);
// uint16_t z = y / uint16_t{255};
auto vecZ0 = AVX2_DIV255_U16(vecY0);
auto vecZ1 = AVX2_DIV255_U16(vecY1);
// TODO: How to get this back into 8-bit samples so that it can be stored
// back into array.
auto vecResult = _mm256_blendv_epi8(vecZ0, vecZ1, _mm256_set1_epi16(127));
// Write data back to memory (unaligned)
_mm256_storeu_si256((__m256i*)d, vecResult);
d += 32;
s += 32;
sa += 32;
n += 32;
}
return n;
}
SIMD 不是我的强项,我需要在这方面做得更好 - 请保持温和。我想可能有很多调整可以应用于当前的矢量化代码(欢迎提出建议!)
开发环境:
- Linux Ubuntu 18.04
- G++ v8.3.0
- c++14
【问题讨论】:
-
通常你需要一个
vpackuswb+ shuffle 来解释车道交叉。或者首先使用_mm256_setzero_si256()解压缩 lo/hi 一对 256 位向量,这样重新打包就在通道内vpackuswb。 -
或者为了节省指令,你可能想要交错(?)来设置
vpmaddubsw,如果你能做到的话。 (它将一个输入视为有符号,另一个视为无符号,因此对于像素 * alpha 来说它不会很容易工作,除非可能将范围转移到有符号然后调整?但饱和度是一个问题,所以不,我不这么想)。 -
感谢指点!前一个选项似乎更有吸引力,但我会看看。
标签: c++ simd intrinsics avx avx2