Saturday, March 22, 2014

Android Texture Optimization

tldr: We increased the terrain texture resolution for Android Big Mountain Snowboarding from 256 to 1024 by making some changes.

Colt McAnlis gave a talk at GDC on shrinking your png use in Android games that inspired me to go over some of our assets and code.  Our current setup is to use high res hardware compressed textures like pvr4 or dxt on platforms that have consistency, and use lower res uncompressed textures on platforms that do not.  For Big Mountain Snowboarding this means 2048x2048 textures on iOS and Windows are 256x256 on Android.  Sorry Android people!

Part of the size difference is because BMS is still supporting phones that no one uses.  When we originally released the game, phones didn't have enough texture memory to support the larger uncompressed textures.  This appears to not be the case anymore since we have released 4 newer maps with higher res textures without hearing many complaints.

However, file size of the apk prevents us from increasing the size of all maps.  We can't make all 16 maps be 1024x1024 without going over the 50 meg Google Play limit unless we make some changes to how we handle textures.

So at the start of this experiment, our assets folder is 32 megs, and the compressed apk is 33 megs.  We have some room to make the apk bigger, but not enough to increase the terrain resolution.  The first thing I tried was export for web from photoshop.

Export for Web:
8-bit pngs are not going to work.  These are very large pixels in world space due to the way we're handling terrain, so the banding is going to be very obvious.  It's hard to tell in that small image but it's there!  Using save for web with 24 bit pngs was not a big enough savings to worry about.

PNGCrush (lossless):
Using the option -reduce for lossless compression, the largest texture was reduced from 1469KB to 1344KB.  It's the right direction, but not really enough for me to increase the texture resolution.  Using -reduce -brute brought it to 1231KB.  I thought this might be enough to do a (time consuming) full run on all the larger terrain png files to see what it does to the apk, and 2 textures later decided to look for better options. (lossy): is a site that provides free lossy compression of png files.  Here we are throwing away color information to favor a smaller disk size.  The site was able to reduce a 1.4 meg png down to 700k.
The difference is noticeable, which is unfortunate.  The unacceptable banding from the 8 bit version does not exist though!  Considering this amount of savings would let us increase the texture size of all maps I consider it worth it.  After a short meeting with our art director (me) and our tech director (me) where there was much arguing and throwing stuff, both departments agreed that it was a worthwhile change to make.

Doing this for all 4 of the large resolution maps brought our assets folder down to 21.9 megs (from 32), and our apk to 23 megs.

Parkinson's Law:
We now have approximately 27 megs to fill up!  Our older maps are using 256x256 textures on Android, and we have 12 of those.  A 1024x1024 mountain takes up about 2 megs of space, so it looks like we have room to bring all the old maps up to 2013.  Our pipeline lets us generate as much detail as we like but I can save time by starting with the OSX versions which are 2048x2048 and shrink them down.  With increased texture sizes our assets folder is 75 megs.  After compression the assets folder is 49 megs, which pushes our apk over the 50 meg limit.  Dropping the resolution of the two biggest maps brings us to 45 megs, but they are still higher resolution than the original game.

But what about the gpu?
None of this stuff matters on the gpu, where the textures will be uncompressed.  We've had some 1024x1024 maps on the market for a while without hearing about too many issues, so the phone quality of the average user has gone up a lot in 4 years.

I stuck in a breakpoint and all our png textures are loading as RGBA_8888.  Our big terrain textures would benefit a lot in memory size from being RGB_565.  The textures are 3 megs as uncompressed 32 bit and we have 4 of them per map.
     //bitmap = BitmapFactory.decodeStream(filestream);
     BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
     bmpOptions.inPreferredConfig = Bitmap.Config.RGB_565;
     bitmap = BitmapFactory.decodeStream(filestream, null, bmpOptions);
 According to my breakpoints, this code loads in all non-alpha textures as 16 bit, which seems to look ok.  If it doesn't look ok for some specific textures we'll have to add a way to disable the code.