如何快速混合 RGBA 无符号字节颜色?

新手上路,请多包涵

我正在使用 c++ ,我想使用以下代码进行 alpha 混合。

 #define CLAMPTOBYTE(color) \
    if ((color) & (~255)) { \
        color = (BYTE)((-(color)) >> 31); \
    } else { \
        color = (BYTE)(color); \
    }
#define GET_BYTE(accessPixel, x, y, scanline, bpp) \
    ((BYTE*)((accessPixel) + (y) * (scanline) + (x) * (bpp)))

    for (int y = top ; y < bottom; ++y)
    {
        BYTE* resultByte = GET_BYTE(resultBits, left, y, stride, bytepp);
        BYTE* srcByte = GET_BYTE(srcBits, left, y, stride, bytepp);
        BYTE* srcByteTop = GET_BYTE(srcBitsTop, left, y, stride, bytepp);
        BYTE* maskCurrent = GET_GREY(maskSrc, left, y, width);
        int alpha = 0;
        int red = 0;
        int green = 0;
        int blue = 0;
        for (int x = left; x < right; ++x)
        {
            alpha = *maskCurrent;
            red = (srcByteTop[R] * alpha + srcByte[R] * (255 - alpha)) / 255;
            green = (srcByteTop[G] * alpha + srcByte[G] * (255 - alpha)) / 255;
            blue = (srcByteTop[B] * alpha + srcByte[B] * (255 - alpha)) / 255;
            CLAMPTOBYTE(red);
            CLAMPTOBYTE(green);
            CLAMPTOBYTE(blue);
            resultByte[R] = red;
            resultByte[G] = green;
            resultByte[B] = blue;
            srcByte += bytepp;
            srcByteTop += bytepp;
            resultByte += bytepp;
            ++maskCurrent;
        }
    }

但是我发现它仍然很慢,合成两个 600 * 600 图像大约需要 40 - 60 毫秒。有什么方法可以将速度提高到 16ms 以下?

任何机构都可以帮助我加快这段代码的速度吗?非常感谢!

原文由 user25749 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 565
2 个回答

使用 SSE - 从第 131 页开始。

基本工作流程

  1. 从 src 加载 4 个像素(16 个 1 字节数字) RGBA RGBA RGBA RGBA(流式加载)

  2. 再加载 4 个要与 srcbytetop RGBx RGBx RGBx RGBx 混合的内容

  3. 做一些 swizzling,使 1 中的 A 项填满每个插槽,即

xxxA xxxB xxxC xxxD -> AAAA BBBB CCCC DDDD

在下面的解决方案中,我选择重新使用现有的“maskcurrent”数组,但是将 alpha 集成到 1 的“A”字段中将需要更少的内存负载,因此速度更快。在这种情况下,Swizzling 可能是:并使用掩码选择 A、B、C、D。右移 8,或使用原始,右移 16,或再次。

  1. 将上述内容添加到每个插槽中全为 -255 的向量中

  2. 乘以 1 * 4(带有 255-alpha 的源)和 2 * 3(带有 alpha 的结果)。

您应该能够为此使用“乘以并丢弃底部 8 位”SSE2 指令。

  1. 将这两个(4和5)加在一起

  2. 将它们存放在其他地方(如果可能)或目的地顶部(如果必须)

这是您的起点:

     //Define your image with __declspec(align(16)) i.e char __declspec(align(16)) image[640*480]
    // so the first byte is aligned correctly for SIMD.
    // Stride must be a multiple of 16.

    for (int y = top ; y < bottom; ++y)
    {
        BYTE* resultByte = GET_BYTE(resultBits, left, y, stride, bytepp);
        BYTE* srcByte = GET_BYTE(srcBits, left, y, stride, bytepp);
        BYTE* srcByteTop = GET_BYTE(srcBitsTop, left, y, stride, bytepp);
        BYTE* maskCurrent = GET_GREY(maskSrc, left, y, width);
        for (int x = left; x < right; x += 4)
        {
            //If you can't align, use _mm_loadu_si128()
            // Step 1
            __mm128i src = _mm_load_si128(reinterpret_cast<__mm128i*>(srcByte))
            // Step 2
            __mm128i srcTop = _mm_load_si128(reinterpret_cast<__mm128i*>(srcByteTop))

            // Step 3
            // Fill the 4 positions for the first pixel with maskCurrent[0], etc
            // Could do better with shifts and so on, but this is clear
            __mm128i mask = _mm_set_epi8(maskCurrent[0],maskCurrent[0],maskCurrent[0],maskCurrent[0],
                                        maskCurrent[1],maskCurrent[1],maskCurrent[1],maskCurrent[1],
                                        maskCurrent[2],maskCurrent[2],maskCurrent[2],maskCurrent[2],
                                        maskCurrent[3],maskCurrent[3],maskCurrent[3],maskCurrent[3],
                                        )

            // step 4
            __mm128i maskInv = _mm_subs_epu8(_mm_set1_epu8(255), mask)

            //Todo : Multiply, with saturate - find correct instructions for 4..6
            //note you can use Multiply and add _mm_madd_epi16

            alpha = *maskCurrent;
            red = (srcByteTop[R] * alpha + srcByte[R] * (255 - alpha)) / 255;
            green = (srcByteTop[G] * alpha + srcByte[G] * (255 - alpha)) / 255;
            blue = (srcByteTop[B] * alpha + srcByte[B] * (255 - alpha)) / 255;
            CLAMPTOBYTE(red);
            CLAMPTOBYTE(green);
            CLAMPTOBYTE(blue);
            resultByte[R] = red;
            resultByte[G] = green;
            resultByte[B] = blue;
            //----

            // Step 7 - store result.
            //Store aligned if output is aligned on 16 byte boundrary
            _mm_store_si128(reinterpret_cast<__mm128i*>(resultByte), result)
            //Slow version if you can't guarantee alignment
            //_mm_storeu_si128(reinterpret_cast<__mm128i*>(resultByte), result)

            //Move pointers forward 4 places
            srcByte += bytepp * 4;
            srcByteTop += bytepp * 4;
            resultByte += bytepp * 4;
            maskCurrent += 4;
        }
    }

要了解哪些 AMD 处理器将运行此代码(目前它使用 SSE2 指令),请参阅 维基百科的 AMD Turion 微处理器列表。您还可以查看 Wikipedia 上的其他处理器列表,但我的研究表明,大约 4 年前的 AMD cpu 至少都支持 SSE2。

您应该期望一个好的 SSE2 实现比您当前的代码快 8-16 倍。这是因为我们消除了循环中的分支,一次处理 4 个像素(或 12 个通道),并通过使用流指令提高缓存性能。作为 SSE 的替代方案,您可以通过消除用于饱和度的 if 检查来使现有代码运行得更快。除此之外,我还需要对您的工作负载运行分析器。

当然,最好的解决方案是使用硬件支持(即在 DirectX 中编码您的问题)并在显卡上完成。

原文由 Tom Leys 发布,翻译遵循 CC BY-SA 2.5 许可协议

您可以在两个图像中使用每个像素 4 个字节(用于内存对齐),然后使用 SSE 指令一起处理所有通道。搜索“visual studio sse 内在函数”。

原文由 Eric Bainville 发布,翻译遵循 CC BY-SA 2.5 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题