在iOS 4+上优化调色板纹理到RGB565的转换(使用查找表)

时间:2012-06-26 20:47:12

标签: ios c performance optimization

我正在转换的游戏是在8位调色板纹理上运行,几乎每一帧我都要将该纹理的部分更新为OpenGL纹理以进行渲染。它看起来像这样:

unsigned short RGB565PaletteLookupTable[256];   // Lookup table

unsigned char* Src;                             // Source data
unsigned short* Dst;                            // Destination buffer
int SrcPitch;                                   // Source data row length
int OriginX, OriginY, Width, Height;            // Subrectangle to copy

assert( Width % 4 == 0 );

int SrcOffset = SrcPitch-Width;
Src += OriginY*SrcPitch+OriginX;

int x, y;

for( y = OriginY; y < OriginY+Height; ++y, Src += SrcOffset )
{
    for( x = OriginX; x < OriginX+Width; x += 4 )
    {
        *Dst++ = RGB565PaletteLookupTable[*Src++];
        *Dst++ = RGB565PaletteLookupTable[*Src++];
        *Dst++ = RGB565PaletteLookupTable[*Src++];
        *Dst++ = RGB565PaletteLookupTable[*Src++];
    }
}

此代码在游戏过程中占用主线程时间的17%,因此我正在寻找加快速度的方法。数据直接转到glTexSubImage2D(),因此我无法更改目标缓冲区中的任何内容。它来自游戏中的代码,这些代码很古老而且没有记录,也没有人知道它是如何工作的,所以我也不能搞得太多。查询表也是由这个古老的代码提供的,并且可以在游戏中改变。

使用Accelerate框架/汇编指令/任何其他方法是否可以加速此代码?我阅读了将RGB888直接转换为RGB565的示例,但这些不需要使用查找表。我应该在哪里学习如何最佳地加速它?

更新:我发现OriginX也是4对齐的,并且能够以这种方式优化代码:

unsigned long RGB565PaletteLookupTable[256];   // Lookup table

unsigned char* Src;                             // Source data
unsigned long* Dst;                            // Destination buffer
int SrcPitch;                                   // Source data row length
int OriginX, OriginY, Width, Height;            // Subrectangle to copy

assert( Width % 4 == 0 );

int SrcOffset = SrcPitch-Width;
Src += OriginY*SrcPitch+OriginX;
SrcOffset >>= 2;

int x, y;

unsigned long* LSrc = (unsigned long*)Src;

for( y = OriginY; y < OriginY+Height; ++y, LSrc += SrcOffset )
{
    for( x = OriginX; x < OriginX+Width; x += 4 )
    {
        unsigned long Indexes = *LSrc++;
        unsigned long Result = RGB565PaletteLookupTable[ Indexes & 0xFF ];
        Indexes >>= 8;
        Result |= ( RGB565PaletteLookupTable[ Indexes & 0xFF ] << 16 );
        *Dst++ = Result;
        Indexes >>= 8;
        Result = RGB565PaletteLookupTable[ Indexes & 0xFF ];
        Indexes >>= 8;
        Result |= ( RGB565PaletteLookupTable[ Indexes & 0xFF ] << 16 );
        *Dst++ = Result;
    }
}

这个代码并没有我所知,使用任何未对齐的内存访问。它稍微提高了性能,也就是说,它现在占主线程时间的15.5%。我希望能提高速度。

理论上,每个查找表操作都独立于先前的和后续的(除了它们每个都从相同的查找表中读取的事实),所以我期待会有一些SIMD指令,或者也许汇编指令允许并行查找许多像素。像

这样的东西
_mm_movemask_ps( _mm_cmpneq_ps( _mm_loadu_ps( cmp1 ), _mm_loadu_ps( cmp2 ) ) ) )

在Mac上与memcmp(cmp1,cmp2,​​16)的功能相同,只有8倍。

我现在继续寻找它。

更新:我确定似乎无法使用NEON指令集加速表查找。该表需要512字节大,没有办法完全适合ARM寄存器,VTBX NEON指令一次最多可处理32个字节,并且它还假设查找结果的大小必须等于指数。有些东西可以解决http://forums.arm.com/index.php?/topic/15521-8bit-look-up-table-by-neon-code/中描述的类似问题的解决方案,但它不适合我的。因此,确保所有操作数的对齐正确似乎是解决此问题的最佳方法。

2 个答案:

答案 0 :(得分:3)

问题在于缓存。你从Src做了大量的读操作,如果它没有对齐4(可能就是这种情况,因为OriginX很可能是任意的),(* Src ++)浪费了未对齐读取的循环。

尝试强制执行(OriginX%4 == 0)并复制主循环外的剩余(OriginX%4)像素。

与“* Dst ++ =”相同 - 是Dst未对齐,很糟糕。尝试将RGB565对(两个连续* Dst写入)组合成一个32位副本。您甚至可以尝试覆盖更多像素以使循环更简单,然后处理边框像素。

希望你明白这一点。

第二种方式:将转换卸载到GPU。

为RGB565PaletteLookupTable创建1D纹理并编写一个简单的片段着色器,它接受(Src + RGB565PaletteLookupTable)并输出Dst(然后glTexImage2D将更新Src纹理,而不是像现在那样更新Dst)

答案 1 :(得分:0)

由于您只是从RGB565PaletteLookupTable复制到Dst,因此您可以考虑使用memcpy,这可能会利用最快的方式在您的平台上实际复制缓冲区。这可以让您利用每个周期复制更多字节的指令,而无需手动编写代码。