使用SSE或MMX加速RGB平面到RGBA交错转换

4

我需要将从一种专有设备SDK检索到的医学图像数据传递给来自第二个供应商的另一个专有设备SDK中的图像处理函数。

第一个函数以平面RGB格式给出图像:

int mrcpgk_retrieve_frame(uint16_t *r, uint16_t *g, uint16_t *b, int w, int h);

uint16_t的原因是设备可以切换为输出每个颜色值编码为16位浮点值。但是,我正在“字节模式”下运行,因此每个颜色值的前8位始终为零。

另一个设备SDK的第二个函数定义如下:

BOOL process_cpgk_image(const PBYTE rgba, DWORD width, DWORD height);

因此,我们将以下位填充到三个缓冲区中:(16位平面RGB)

R: 0000000 rrrrrrrr  00000000 rrrrrrrr ...
G: 0000000 gggggggg  00000000 gggggggg ...
B: 0000000 bbbbbbbb  00000000 bbbbbbbb ...

期望的输出以比特为单位表示:

RGBA: rrrrrrrrggggggggbbbbbbbb00000000 rrrrrrrrggggggggbbbbbbbb00000000 ....

我们无法访问这些函数的源代码,也无法更改环境。目前,我们已经实现了以下基本的“桥接”来连接这两个设备:

void process_frames(int width, int height)
{
    uint16_t *r = (uint16_t*)malloc(width*height*sizeof(uint16_t));
    uint16_t *g = (uint16_t*)malloc(width*height*sizeof(uint16_t));
    uint16_t *b = (uint16_t*)malloc(width*height*sizeof(uint16_t));
    uint8_t *rgba = (uint8_t*)malloc(width*height*4);
    int i;

    memset(rgba, 0, width*height*4);

    while ( mrcpgk_retrieve_frame(r, g, b, width, height) != 0 )
    {
        for (i=0; i<width*height; i++)
        {
            rgba[4*i+0] = (uint8_t)r[i];
            rgba[4*i+1] = (uint8_t)g[i];
            rgba[4*i+2] = (uint8_t)b[i];
        }

        process_cpgk_image(rgba, width, height);
    }
    free(r);
    free(g);
    free(b);
    free(rgba);
}

这段代码本身运行良好,但是处理许多高分辨率图像时会花费很长时间。 处理和检索的两个功能非常快速,而我们的桥梁目前成为了瓶颈。
我知道如何使用SSE2内在函数进行基本算术,逻辑和移位操作,但不知道如何使用MMX,SSE2或[S] SSE3加速这种16位平面rgb到打包rgba的转换?
(首选SSE2,因为仍然有一些2005年以前的设备在使用)。

你真的只限于使用SSE2吗?如果你可以使用SSSE3(至少在英特尔CPU上已经标准化7年了),那么字节重排会变得更容易。 - Paul R
不一定。修改问题以接受[S]SSE3。但是SSE2会更好。 - Veterinarian
2个回答

3

这里有一个简单的SSE2实现:

#include <emmintrin.h>            // SSE2 intrinsics

assert((width*height)%8 == 0);    // NB: total pixels must be multiple of 8

for (i=0; i<width*height; i+=8)
{
    __m128i vr = _mm_load_si128((__m128i *)&r[i]);    // load 8 pixels from r[i]
    __m128i vg = _mm_load_si128((__m128i *)&g[i]);    // load 8 pixels from g[i]
    __m128i vb = _mm_load_si128((__m128i *)&b[i]);    // load 8 pixels from b[i]
    __m128i vrg = _mm_or_si128(vr, _mm_slli_epi16(vg, 8));
                                                      // merge r/g
    __m128i vrgba = _mm_unpacklo_epi16(vrg, vb);      // permute first 4 pixels
    _mm_store_si128((__m128i *)&rgba[4*i], vrgba);    // store first 4 pixels to rgba[4*i]
    vrgba = _mm_unpackhi_epi16(vrg, vb);              // permute second 4 pixels
    _mm_store_si128((__m128i *)&rgba[4*i+16], vrgba); // store second 4 pixels to rgba[4*i+16]
}

这很简单。正如你所写的,排列步骤可能很棘手,这就是我在有限的simd经验中失败的地方。如果真的可行(并且在循环次数方面有意义),则SSE2 / MMX将是首选,因为我们仍在使用一些2005年之前的设备。但是,任何使用SSSE3解决方案的加速都非常受欢迎。 - Veterinarian
1
OK - SSE2 比我预想的要容易,上面的代码已经测试过了,似乎工作正常。 - Paul R
确实非常不错!快速测试程序证实它可以工作。我在设备可用时将测试性能改进。 - Veterinarian
我进行了一些阅读,以便完全理解您的解决方案,并发现了缓存可能存在的问题。您使用_mm_store_si128而不是_mm_stream_si128有特定的原因吗?那么在r、g和b缓冲区上使用_mm_prefetch呢? - Veterinarian
我怀疑 _mm_prefetch 不会产生任何影响。_mm_stream_si128 可能 有所帮助 - 可以尝试一下,但由于你的访问模式非常简单,我认为它不会有任何效果,至少对于现代 CPU 来说是这样。不过还是可以尝试一下,但请注意结果可能取决于 CPU。 - Paul R

2
使用AVX2指令的参考实现:
#include <immintrin.h>            // AVX2 intrinsics

assert((width*height)%16 == 0);    // total pixels count must be multiple of 16
assert(r%32 == 0 && g%32 == 0 && b%32 == 0 && rgba% == 0); // all pointers must to have 32-byte alignment

for (i=0; i<width*height; i+=16)
{
    __m256i vr = _mm256_permute4x64_epi64(_mm265_load_si256((__m256i *)(r + i)), 0xD8);    // load 16 pixels from r[i]
    __m256i vg = _mm256_permute4x64_epi64(_mm265_load_si256((__m256i *)(g + i)), 0xD8);    // load 16 pixels from g[i]
    __m256i vb = _mm256_permute4x64_epi64(_mm265_load_si256((__m256i *)(b + i)), 0xD8);    // load 16 pixels from b[i]
    __m256i vrg = _mm256_or_si256(vr, _mm256_slli_si256(vg, 1));// merge r/g
    __m256i vrgba = _mm256_unpacklo_epi16(vrg, vb);      // permute first 8 pixels
    _mm256_store_si256((__m256i *)(rgba + 4*i), vrgba);    // store first 8 pixels to rgba[4*i]
    vrgba = _mm256_unpackhi_epi16(vrg, vb);              // permute second 8 pixels
    _mm256_store_si256((__m256i *)(rgba + 4*i+32), vrgba); // store second 8 pixels to rgba[4*i + 32]
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接