提问人:John Smith 提问时间:10/26/2023 最后编辑:Peter CordesJohn Smith 更新时间:10/29/2023 访问量:136
SSE/AVX:如何将一组 16 位像素(打包 RGB)拆分为位平面
SSE/AVX: How to split a set of 16-bit pixels (packed RGB) into bitplanes
问:
我有一些基本的 SSE 知识,并编写了一些加速函数。但是这个问题让我难住了,我想知道是否真的有一种加速的 SIMD 方法来处理它。
我有一张包含 3 个颜色通道的图像。每个颜色通道的宽度高达 16 位。数据类型始终为 ,但取决于配置的颜色深度,仅作为位的子集可能是有效的。现在,我想将图像拆分为组成位平面。uint16_t
这意味着我想要一个仅包含像素中每个通道的第一位的缓冲区。另一个包含第二个位的缓冲区。包含第三位等的缓冲区。
基本上,在简化的代码中,我有这个:
#include <inttypes.h>
// img_width is always divisable by 8
// img contains RGB pixels. Each channel is one uint16_t
// where color_depth contains how many bits are valid
// the bitplanes_* are outputs
void extract_bitplanes(
uint16_t* img,
uint16_t img_width,
uint16_t img_height,
uint8_t color_depth,
uint8_t** bitplanes_r,
uint8_t** bitplanes_g,
uint8_t** bitplanes_b
)
{
for (uint16_t y = 0; y < img_height; ++y)
{
for (uint16_t x = 0; x < img_width; x += 8)
{
uint16_t* img_start = img + 3 * (img_width * y + x);
// Get 8 pixels to use. This is done since 8 pixels
// means we can create a full byte in the color channel iamge
uint16_t* p0 = img_start;
uint16_t r0 = p0[0];
uint16_t g0 = p0[1];
uint16_t b0 = p0[2];
uint16_t* p1 = img_start + 3;
uint16_t r1 = p1[0];
uint16_t g1 = p1[1];
uint16_t b1 = p1[2];
uint16_t* p2 = img_start + 6;
uint16_t r2 = p2[0];
uint16_t g2 = p2[1];
uint16_t b2 = p2[2];
uint16_t* p3 = img_start + 9;
uint16_t r3 = p3[0];
uint16_t g3 = p3[1];
uint16_t b3 = p3[2];
uint16_t* p4 = img_start + 12;
uint16_t r4 = p4[0];
uint16_t g4 = p4[1];
uint16_t b4 = p4[2];
uint16_t* p5 = img_start + 15;
uint16_t r5 = p5[0];
uint16_t g5 = p5[1];
uint16_t b5 = p5[2];
uint16_t* p6 = img_start + 18;
uint16_t r6 = p6[0];
uint16_t g6 = p6[1];
uint16_t b6 = p6[2];
uint16_t* p7 = img_start + 21;
uint16_t r7 = p7[0];
uint16_t g7 = p7[1];
uint16_t b7 = p7[2];
for (uint8_t c = 0; c < color_depth; ++c) {
uint32_t plane_offset = (y * img_width + x) / 8;
bitplanes_r[c][plane_offset] = (((r0 >> c) & 1) << 0) | (((r1 >> c) & 1) << 1) | (((r2 >> c) & 1) << 2)
| (((r3 >> c) & 1) << 3) | (((r4 >> c) & 1) << 4) | (((r5 >> c) & 1) << 5)
| (((r6 >> c) & 1) << 6) | (((r7 >> c) & 1) << 7);
bitplanes_g[c][plane_offset] = (((g0 >> c) & 1) << 0) | (((g1 >> c) & 1) << 1) | (((g2 >> c) & 1) << 2)
| (((g3 >> c) & 1) << 3) | (((g4 >> c) & 1) << 4) | (((g5 >> c) & 1) << 5)
| (((g6 >> c) & 1) << 6) | (((g7 >> c) & 1) << 7);
bitplanes_b[c][plane_offset] = (((b0 >> c) & 1) << 0) | (((b1 >> c) & 1) << 1) | (((b2 >> c) & 1) << 2)
| (((b3 >> c) & 1) << 3) | (((b4 >> c) & 1) << 4) | (((b5 >> c) & 1) << 5)
| (((b6 >> c) & 1) << 6) | (((b7 >> c) & 1) << 7);
}
}
}
}
这做了很多工作,理论上可以完全并行化。但我似乎无法弄清楚如何将其映射到 SIMD 内部函数。是否有可能使用内部函数来加速这一点?还是这个问题要专业化?
任何帮助都是值得赞赏的。
答: 暂无答案
评论
pmovmskb
是从每个字节元素中提取位并打包到位掩码中的常用方法。或者从每个 32 位元素中获取一点。如果将一些未对齐的加载结果混合在一起,则可以从“绿色”像素分量的顶部获得一个包含 15 或 16 字节数据的向量,并可以将它们按正确的顺序排列。+ 序列可以提取连续位平面的块。(以不同的方式混合以获取其他组件的顶部字节和低字节。这需要大量的洗牌/混合;其他策略可能会更好movmskps
pshufb
paddb same,same
pmovmskb
uint8_t** bitplanes_r
- 指针到指针?为什么不是 2D 数组,或者在平面数组中手动进行 2D 索引?(或每个通道的平面阵列)。可能无关紧要,因为您总是希望从同一位平面累积至少 8 个连续位来存储在某个地方,但应该将一些标量开销转换为循环中的指针增量。pmovmskb
pshufb
punpcklwd
pshufb
unpcklbw
pshufb