关于iOS纹理加载的OpenGL ES - 我如何从RGBA8888 .png文件获取RGB565纹理?

问题描述:

所以我正在处理一堆2048x2048精灵表,它们很快填满了内存。原样,我使用下面的方法(通过雷Wenderlich)加载纹理:关于iOS纹理加载的OpenGL ES - 我如何从RGBA8888 .png文件获取RGB565纹理?

- (GLuint)setupTexture:(NSString *)fileName 
{  
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage; 
    if (!spriteImage) 
    { 
     NSLog(@"Failed to load image %@", fileName); 
     exit(1); 
    } 
    size_t width = CGImageGetWidth(spriteImage); 
    size_t height = CGImageGetHeight(spriteImage); 
    GLubyte * spriteData = (GLubyte *) calloc(width*height*4, sizeof(GLubyte)); 
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8,  width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);  
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage); 
    CGContextRelease(spriteContext); 
    GLuint texName; 
    glGenTextures(1, &texName); 
    glBindTexture(GL_TEXTURE_2D, texName); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData); 
    GLenum err = glGetError(); 
    if (err != GL_NO_ERROR) 
     NSLog(@"Error uploading texture. glError: 0x%04X", err);  
    free(spriteData);   
    return texName;  
} 

所以我的问题是,我如何丢弃来自CGImageα信息,然后在“下采样”图像以更少的每像素位,最后告诉OpenGL呢?

我使用此代码将CGContextDrawImage写入的数据转换为RGB565。它使用优化的NEON代码在支持NEON的设备上每次处理8个像素(每个运行armv7代码的iOS设备都有这样的芯片)。您看到的指针data与您的spriteData指针相同,我只是懒得重命名它。

void *temp = malloc(width * height * 2); 
uint32_t *inPixel32 = (uint32_t *)data; 
uint16_t *outPixel16 = (uint16_t *)temp; 
uint32_t pixelCount = width * height; 

#ifdef __ARM_NEON__ 
for(uint32_t i=0; i<pixelCount; i+=8, inPixel32+=8, outPixel16+=8) 
{ 
    uint8x8x4_t rgba = vld4_u8((const uint8_t *)inPixel32); 

    uint8x8_t r = vshr_n_u8(rgba.val[0], 3); 
    uint8x8_t g = vshr_n_u8(rgba.val[1], 2); 
    uint8x8_t b = vshr_n_u8(rgba.val[2], 3); 

    uint16x8_t r16 = vmovl_u8(r); 
    uint16x8_t g16 = vmovl_u8(g); 
    uint16x8_t b16 = vmovl_u8(b); 

    r16 = vshlq_n_u16(r16, 11); 
    g16 = vshlq_n_u16(g16, 5); 

    uint16x8_t rg16 = vorrq_u16(r16, g16); 
    uint16x8_t result = vorrq_u16(rg16, b16); 

    vst1q_u16(outPixel16, result); 
} 
#else     
for(uint32_t i=0; i<pixelCount; i++, inPixel32++) 
{ 
    uint32_t r = (((*inPixel32 >> 0) & 0xFF) >> 3); 
    uint32_t g = (((*inPixel32 >> 8) & 0xFF) >> 2); 
    uint32_t b = (((*inPixel32 >> 16) & 0xFF) >> 3); 

    *outPixel16++ = (r << 11) | (g << 5) | (b << 0);    
} 
#endif 

free(data); 
data = temp; 
+1

'uint8x8_t'类型在'arm_neon.h'中定义# – brigadir

+0

精彩!不得不改变glTexImage2D线以下参数:'glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,宽度,高度,0,GL_RGB,GL_UNSIGNED_SHORT_5_6_5,spriteData);' – Jakob

+0

伟大的答案,你看不到太多NEON代码在那里。为了更加硬件无关,你有没有考虑过使用Accelerate框架来实现它? –

看看internalFormat参数。也许你可以指定GL_R3_G3_B2每个像素只使用8位,或者GL_RGB5可能有用。有几个可能有用的选项。

查看Texture Internal Formats了解更多信息。

而不是简单地改变色彩空间,我可能会建议在这里使用PVRTC压缩纹理。这应该比使用RGB565版本在GPU上使用更少的内存,因为这些纹理保持压缩而不是扩展到未压缩的位图。

在OpenGL ES的编程指南适用于iOS的“Best Practices for Working with Texture Data”一节中,苹果表示

纹理压缩通常提供的内存 储蓄和质量的最佳平衡。通过实施 GL_IMG_texture_compression_pvrtc扩展,OpenGL ES for iOS支持PowerVR纹理 压缩(PVRTC)格式。 PVRTC压缩有两个级别,每个通道4位和每个通道2位,其中 分别提供8:1和16:1的压缩比,分别比未压缩的32位 纹理格式高。压缩后的PVRTC纹理仍然提供了不错的质量水平,尤其是在4位的水平。

如果您的应用程序无法使用压缩的纹理,可以考虑使用 精度较低的像素格式。

这表明压缩纹理的内存大小比使用较小色彩空间的内存大小要小。

苹果在他们的PVRTextureLoader示例应用程序中有一个很好的例子,我重新使用了其中一些代码来加载我在一起的textured cube sample application中的PVRTC纹理。您可以查看PVRTextureLoader中的“编码图像”编译阶段,了解如何在编译时将PNG纹理转换为PVRTC文件。

+0

你的答案一直鼓励我在PVRTexturLoader例子再看看。我曾尝试在前一天晚上使用我的纹理,但失败了,但我现在已经开始工作了。巨大的内存占用改进!谢谢拉尔森先生 – Jakob