The Android Texture Decision
An early decision faced by any Android OpenGL ES 2.0 developer is whether to use texture compression, and if so which formats to support.
The first question has but one legitimate answer: Yes, of course you should.
On devices with limited memory, and even more restricted memory bandwidth (usually with no dedicated video memory to share the load), compressed textures can significantly reduce the footprint of assets (in transit, storage and in memory), and can theoretically lead to improved fill-rates.
ETC1 compression yields textures that are 1/6th the size of the original, as does DXT compression (aka S3TC). PVRTC offers similar performance. The result is imperceptibly changed from the original.
Only in a couple of specific cases is it reasonable to skip compression. If you have a dynamic texture where you algorithmically generate or alter the texels, for instance, texture compression is not an option.
Alas, Google did developers a big disservice when they published a tutorial on game development and simply avoided the whole question (going with RGBA). That sort of deferred basic optimization is exactly what is wrong with many applications on Android. Over in iPhone land — helped by a simplified hardware target — PVRTC is basically the default decision.
So why do so many Android developers avoid the question? Why do some apps not even bother?
Having faced and answered this question, I have an inkling of an answer.
- The official texture-compression format of OpenGL ES 2.0 is ETC1. A gaping-chasm in the functionality of which is the lack of support for an alpha channel. This is fine for traditionally wrapped 3D objects, but represents a problem when you’re essentially using OpenGL ES 2.0 as a 2D compositing system, as many (if not most) are.
- The formats that support an alpha-channel are the uncompressed RGBA, which is incredibly wasteful, or the non-standard formats of PVRTC, ATITC, and S3TC (DXT).
On Honeycomb tablets today you could technically settle on S3TC, at least for a couple of months. Then the OMAP 4 tablets, lacking support for S3TC (but adding support for PVRTC), start hitting shelves and your app will be horribly broken, as will many in the current “assume Android tablets run the Tegra2” era. - You could support all three of the non-standard formats to cover all bases, however then you need to segregate assets by device (and accommodate the extra complexity in your tool-chain and bundling process), or redundantly pull all formats on all devices, wasting transfer and storage capacity in the process. It adds complexity to your project.
If ETC1 had an alpha channel and didn’t represent a significant compromise over the other formats, it would be the only choice.
What do I mean when I mention the possibility of a significant compromise? I am referring to the purported advantage (one that I have never seen demonstrated or benchmarked) of using the “native” texture compression format on these devices.
That a PowerVR GPU will do best with PVRTC, the Tegra2 will shine with S3TC, and the Adreno-GPU powered Snapdragon prefers ATITC. There is a reasonable logic to such a supposition.
After doing significant testing of my own, however, I can state that I’ve found a negligible performance difference between S3TC versus ETC1 on the Tegra2. This doesn’t necessarily hold true for different GPUs and devices, but nvidia seems to have done a great job of treating ETC1 as a first-class compression format, with little penalty while yielding the benefits of a cross-hardware format.
I went into the benchmark expecting to see a significant advantage for S3TC, so the results actually refuted my bias.
What of the lack of alpha functionality, however?
For that I drew inspiration from the post by erique under the Unity3D thread. In that thread the author suggests to use two textures for ETC1 textures where you need an alpha channel, mixing them in the fragment shader. I followed their advice and the performance barely changed — the second texture source coming at almost no cost. The alternative would be to use an RGBA for those specific textures, but the dual-ETC textures still beats that by 3x.
void main() {
gl_FragColor.rgb = texture2D(sTexture1, vv2texCoord).rgb;
gl_FragColor.a = texture2D(sTexture2, vv2texCoord).g;
}
Load an RGBA image in Gimp, pick Colors/Components/Decompose, pick Alpha, and you’ll have a perfect second image to run through ETC1 compression (the single best compression tool I’ve found is PVRTexTool by Imagination Technologies. Despite their seeming conflict of interest, it has proven excellent at compressing against all formats), used as specified above.
Clearly it adds to the memory bandwidth and the original download, but for those specific cases where I absolutely need an alpha channel it is a golden solution that makes for a single set of assets that work across all OpenGL ES 2.0 devices, present and in the future.
Alternately, where only a binary opacity was needed I could swap out true-black for no opacity in the fragment shader.
EDIT: I tested this theory, evaluating the pixel color and if black setting the opacity to 0.0. ala-
fragColor = texture2D(sTexture1, vv2texCoord);
if (fragColor.rgb == 0.0)
{
fragColor.a = 0.0;
}
To my surprise this absolutely destroyed the performance, cutting the framerate to 1/3rd.
So at tewdew we’re throwing in with ETC1 for now, using a second compressed texture where an alpha channel is needed. This may change in the future if specific devices benefit from specialization, but for now I think this is the right approach.