Pages

Mar 24, 2014

How to move from PNG to GPU Textures on Android (and keep your sanity)

For most Android game developers, PNGs are a simple, easy to use texture format that offers ‘good enough’ compression alongside alpha support, which is important since the dominant form of game in the mobile market is 2D based. But the truth is that while PNGs may help with distribution, they don’t help you when it comes to GPU residency; they are still full, fat textures eating up your available memory. But, it doesn’t have to be that way.




GPU residency is a scarce resource for Android devices. Big, uncompressed textures take up a lot of space, and make it difficult to support the federation of unique devices.


PNG to GPU
One of the worst parts about PNG files is that they are not friendly for GPU residency. Sure they help you with distribution, but they still take up uncompressed 32bpp or 24bpp space on the GPU, which is less than ideal.


Trust me, I understand the whole GPU texture fragmentation thing. PVRTC, DXT, ETC, ASTC; it gets crazy when you try to segment your APKs based on what textures you’re using.


But this doesn’t mean you have to settle for 32bpp by default. There’s an array of GPU compatible formats that support smaller bytes per pixel, and work on the large majority of GPUs. For example, take a look at the 16 bits per pixel formats below:


BITS PER PIXEL
CHANNELS
GL ENUM
32 bpp
RGBA - 8888
GL_UNSIGNED_BYTE
24 bpp
RGB - 888
GL_UNSIGNED_BYTE
16 bpp
RGBA - 4444
GL_UNSIGNED_SHORT_4_4_4_4
16 bpp
RGBA - 5551
GL_UNSIGNED_SHORT_5_5_5_1
16 bpp
RGB - 565
GL_UNSIGNED_SHORT_5_6_5



Making these formats is pretty easy from any of the plethora of PNG compressors; most will support this output option, heck, even texture packer will do it for you.


And as a disclaimer, it’s important to note that 16 bit pixel format is not supported by the PNG format group directly, nor the W3C, but this doesn’t mean that it’s unsupported in the file format itself. Really, even a fast conversion from 32bpp to 16bpp at load time would be enough of a win.


One of the side effects of using 16bit textures is the insertion of visual quality issues. In the image above, you can see the side-by-side difference between RGBA32, RGB565, RGBA5551 and RGBA4444. Easily, the 4bit per channel texture produces the most inserted quantization in smooth gradients, but it’s difficult to see the difference in the high-noise areas.



GPU Texture support on Android

THe truth is that if you want real savings for texture distribution, you need to embrace using one of the many GPU specific texture compression formats. These formats offer supiror compression sizes, alongside hardware sampling support (so speed is not an issue). Sadly, due to the state of the industry, there’s LOTS of options when it comes to hardware support for compressed textures. For example, here’s a few of the more popular ones.
FORMAT
GL ENUM
SUPPORT
ETC1
GL_OES_compressed_ETC1_RGB8_texture
This format is supported by all Android phones. But no alpha support.
ATITC
GL_AMD_compressed_ATC_texture
Used in devices with Adreno GPU from Qualcomm (Nexus One...).
PVRTC
GL_IMG_texture_compression_pvrtc
Supported by devices with PowerVR GPUs (Nexus S, Kindle fire...).
DXT1
GL_EXT_texture_compression_dxt1
This texture compression is used in the NVIDIA chipset integrated devices (Motorola Xoom...)


So here’ becomes a disconnect when trying to determine distribution, since some devices won’t have support for your target texture type. But it’s easy to determine what texture formats are supported once the app loads, just check for the above enumerations against the GL extension string on app load. In addition, when loading your texture data, you use glCompressedTexImage2D, instead of using glTexImage2D.
You can generally see that the quality difference in GPU compressed formats is high.

Note : ETC1 is supported by all Android devices.



Using PNG to avoid APK management?

Some devs have been claiming that they use PNG in order to reduce the burden of needing to send separate APKs to devices depending on what texture format type they support. To this, I say Hogwash, and here’s way:

Consider the sizes of gpu texture for this 256x256 cropping of the parrot head. You can see that if you add up the sizes of the DXT, PVR, and ETC versions of the texture together, it’s still LESS SIZE than the PNG file, both on disk, and resident in GPU memory.
The takeaway here is that if you add up the footprint for DXT, PVR and ETC, you end up with an overall texture footprint that’s lower than the size of the web-exported version of the PNG file. AND they have better GPU residency! Or, to say it in a much more “blog friendly” way :


Instead of using PNG, export DXT, PVR and ETC, versions of your texture. Bundle all 3 in your APK, you’ll get a smaller distribution footprint AND smaller GPU residency.


Bare minimum, use ETC1 on all opaque textures.



Conclusion

Sure, PNG is an easy format to use; However it’s compression savings ends at distribution, meaning that you lose all those savings when it comes to GPU residency. Using 16bpp PNG textures are a quick hack, and supported by OpenglES2 directly. And if you’re feeling daring, go all the way and just bundle all your GPU specific versions of the texture into your apk; You’re still going to be saving distribution size, and GPU residency.


~Main

You can find Colt McAnlis here:

  

1 comment:

  1. Thanks, found this useful (looking at reducing PNG size in an Andriod app)

    ReplyDelete