Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenGL ES on iOS texture loading - how do i get from a RGBA8888 .png file to a RGB565 texture?

so i'm working with a bunch of 2048x2048 sprite sheets which fill up memory rather quickly. As is, i'm using the following method (via Ray Wenderlich) to load a texture:

- (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;    
}

so my question is, how do i discard alpha information from the CGImage, then "downsample" the image to fewer bits per pixel, and finally tell OpenGL about it?

like image 774
Jakob Avatar asked Dec 28 '22 12:12

Jakob


2 Answers

I use this code to convert the data written by CGContextDrawImage to RGB565. It uses optimized NEON code to process 8 pixels at a time on devices with support NEON (every iOS device that runs armv7 code has such a chip). The data pointer that you see there is the same as your spriteData pointer, I was just too lazy to rename it.

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;
like image 88
JustSid Avatar answered Jan 03 '23 15:01

JustSid


Rather than simply changing the colorspace, I might suggest using a PVRTC-compressed texture here. That should use far less memory on-GPU than even the RGB565 version, because these textures remain compressed rather than expanding into uncompressed bitmaps.

In the "Best Practices for Working with Texture Data" section of the OpenGL ES Programming Guide for iOS, Apple says

Texture compression usually provides the best balance of memory savings and quality. OpenGL ES for iOS supports the PowerVR Texture Compression (PVRTC) format by implementing the GL_IMG_texture_compression_pvrtc extension. There are two levels of PVRTC compression, 4 bits per channel and 2 bits per channel, which offer a 8:1 and 16:1 compression ratio over the uncompressed 32-bit texture format respectively. A compressed PVRTC texture still provides a decent level of quality, particularly at the 4-bit level.

and

If your application cannot use compressed textures, consider using a lower precision pixel format.

which indicates that you'll get smaller in-memory sizes for the compressed textures than even the ones using a smaller colorspace.

Apple has a good example of this in their PVRTextureLoader sample application, and I reused some of that code to load PVRTC textures in a textured cube sample application I slapped together. You can look at the "Encode Images" build phase in the PVRTextureLoader for how to script the conversion of PNG textures into PVRTC ones at compile time.

like image 31
Brad Larson Avatar answered Jan 03 '23 13:01

Brad Larson