Texture compression on mobile demystified

If you’re developing a game for mobile devices, chances are you have run into the words “texture compression”. Texture compression helps to keep video memory usage down, reduce download sizes and loading times, and may even reduce power consumption. In this article, I take a comprehensive look at what the options are.

This post was prompted by a discussion on Reddit whether you should use pngquant to compress your game’s PNG assets. Because I’ve been able to get away with PNG so far, I have never really looked into texture compression. Now seemed as good a time as any to learn something new that I might be able to apply in the future. Note that this is all sourced from various websites, not from first-hand experience.

Everything in this article is up to date as of February 2017, but is bound to change as new formats are invented and support for existing formats is extended or dropped. If you have any updates or requests for updates, please post them in the comments, and I will do my best to incorporate them.

Technicalities

All the formats I’m talking about are lossy compression formats: compression is achieved by sacrificing some image quality. We have to distinguish between compression on disk versus compression in video memory (VRAM), where it is directly accessed by the graphics processing unit (GPU).

Unlike JPEG, PNG or GIF, all GPU texture compression formats work with a fixed bitrate (number of bits per pixel). This means you have little to no control over image quality. It’s a tradeoff to allow the GPU random access to texels; with a variable bitrate, it would be much more difficult for the hardware to know where in memory a particular texel resides.

Different GPUs support different texture compression formats. Textures compressed in one format cannot be directly uploaded to a GPU that does not support that format. All GPUs understand uncompressed textures (for example 24-bit RGB, 32-bit RGBA, or 16-bit RGB565), but these take up more VRAM than necessary.

On disk, you have more liberty with which formats you use, as long as you can somehow convert them into something that the GPU understands. PNG or even JPEG are both fine choices depending on circumstance, but they have one major drawback: no GPU understands them. So at load time, you either convert these into (big) uncompressed textures, or you recompress them using a GPU-specific format. Neither is a great option, so ideally, you should store the textures on disk in a format that the GPU supports.

But with texture formats and GPU support being all over the map, which format do you choose for on-disk storage? This is where the app stores (Apple App Store and Google Play Store) are helpful. Both let you specify different app packages for different target platforms, so devices will only download textures in a compression format they natively support.

That only leaves the trivial matter of selecting the particular compression format for your game. Or formats – there’s no reason you couldn’t mix and match, playing to each format’s strengths.

GPU compression formats

The table below summarises the most common texture compression formats that are directly supported by GPUs. It was surprisingly hard to scrape all this data together, and I hope I got it right. As said, any corrections are welcome.

Format Compression ratio Alpha channel Device support
ETC1 6x for RGB none OpenGL ES 2.0:
• Android 2.2 and up
• iOS 5 or later in iPad, iPad Mini, iPhone 3GS or later, and iPod Touch 3rd generation or later
ETC2 6x for RGB
8x for RGBA
8 bits OpenGL ES 3.0:
• Android 4.3, on devices with appropriate hardware
• iOS 7 or later, on iPhone 5s, iPad Pro, iPad Air, iPad mini 2, iPod touch 6 or later
PVRTC 6x for RGB
8x for RGBA
3 bits • Android devices running PowerVR SGX530/540 GPU, such as Motorola DROID series; Samsung Galaxy S, Nexus S, and Galaxy Tab; and others
• All iPhone, iPad and iPod devices
PVRTC2 6x for RGB
8x for RGBA
3 bits • Android devices running newer PowerVR GPUs
• Not supported by iOS (though supported by recent hardware)
S3TC a.k.a. DXT or DXTC 6x for RGB
4x for RGBA
8 bits • Android devices running Nvidia Tegra2 platform, including Motorala Xoom, Motorola Atrix, Droid Bionic, and others
• Not supported by iOS
ATC a.k.a. ATITC 6x for RGB
4x for RGBA
8 bits • Android devices with Adreno GPU, including HTC Nexus One, Droid Incredible, EVO, and others
• Not supported by iOS
ASTC 3x–27x (configurable) 8 bits • Android 5.0, on devices with appropriate hardware
• iPhone 6 and later

I didn’t include 3Dc because it’s specifically optimised for normal maps.

What to use?

Most recommendations around the web point towards ETC1, as it’s nearly universally supported. The biggest drawback is that ETC1 does not support an alpha channel, so you will have to supply that in a second texture, then do some magic in the fragment shader to piece the two together again.

However, there is a reason for the existence of all these other formats: ETC1 does not preserve image quality particularly well. If this is a problem for you, move to ETC2 if you can afford to only support newer devices.

If you also need to support older devices, it gets trickier. I couldn’t find any statistics on how well particular formats are supported on devices “in the wild”, but from the table, it looks like you’d want to use at least PVRTC for iOS and some Android devices, and a combination of ETC2, S3TC and maybe ATC to cover most of the Android spectrum.

Another option might be to use PNG inside your app package to get the smallest possible download size, then convert this to an appropriate format for the GPU when the app is first run, caching the converted textures on the device. This could work well for PNG, but for JPEG and other lossy input formats you need to keep in mind that the texture will be compressed lossily twice.

Bottom line: there is no one-size-fits-all solution, but I hope this article has given you the data you need to make an informed decision.

References