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? The Google Play Store is helpful here: it
lets you specify
different app packages for different target platforms, so devices will only
download textures in a compression format they natively support. Apple takes a
different approach: you upload a single IPA file that contains all assets, but
since iOS 9 you can use
app thinning
(slicing in particular) to choose which textures are delivered to which
devices.

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.

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.