Posts
67
Comments
120
Trackbacks
0
WP7, XNA, and XAP Size

This is just going to be a quick post on some techniques for getting your XAP size down when creating games for WP7. It is by no means exhaustive. First, XAP files are basically zip files with specific requirements re: mandatory content and directory layout. So any solutions that would involve zip-style compression will almost certainly result in a larger XAP sizes due to added code. So those type of solutions are off-the-table. But if you can shrink your source input in ways that zip compression cannot, you can shrink your XAP and that’s what I’m going to be discussing. Before doing any of this, do save backups of your assets & code

Images.

Several things.

First, it’s important to understand how images work by default. No matter what format you use (TGA, PNG, ultra-compressed JPG, etc.), the content pipeline reads in your image from your Content project, translates the whole thing to 4 byte (32-bit RGBA) pixels and wraps it with a small wrapper. So each 800x480 image will take up about 1,536,000 bytes (1.46 MB or ~1,500 KB). (N.b. doing a quick test, the “wrapper” takes up 187 bytes). In fact you can calculate the XNB size in bytes of any of your images as such: width * height * 4 + 187.

----------

So the first question to ask yourself is do you really need that (nearly) full screen image? Often times people will create big images with lots of empty space. What I mean by empty space is any single color space (e.g. black, olive green, cornflower blue, white, yellow, etc.) or transparent space. If you have an image with a lot of empty space in it, consider dividing it up into smaller images that leave out the empty space. If it’s a background, use one of the overloads of GraphicsDevice.Clear to create the empty space color you were using. If it’s not a background, create a 1px x 1px image with the sole pixel being opaque white (in RGBA terms, 255, 255, 255, 255). Draw that image scaled up to the appropriate size (using either a destination rectangle or else if you are positioning with Vector2 then using an overload of SpriteBatch.Draw that takes a Vector2 for a scale so you can specify width and height using the Vector2’s X and Y properties) with the color you desire set as the tint color. The tint color will work just fine with semi-transparent colors as long as you’re using alpha blending (which is on by default unless you turn it off using a SpriteBatch.Begin overload that lets you do that). Then draw the other images over top at their appropriate locations. Depending on the image, you may have saved many tens of thousands of pixels by doing it that way.

If you want to go a step further (and I recommend you do), take those subdivided up portions and pack them as densely as possible into one image. This creates what is known as a “sprite sheet”. You can use the source rectangle parameter in SpriteBatch.Draw to specify which part of the image SpriteBatch is supposed to draw. There are two benefits to doing this. First, your load time will increase: loading one image takes less time that loading lots of images. More importantly, though, your draw time will decrease. Every time the GPU has to switch textures it takes a small amount of time. If you can reduce the number of times it need to switch textures, you will increase performance.

----------

Another option (which you absolutely do not want to use if you are using a sprite sheet (unless you really know what you’re doing)) is to use Dxt compression. Dxt compression (originally known as S3 texture compression) is a technique for shrinking images. There are 5 possible levels but only Dxt1 and Dxt5 matter for our purposes. Dxt compression works by looking at an image as blocks of 4px x 4px chunks and using magic algorithms to shrink it down. It’s lossy so depending on the complexity of your image, the end results could range from fine to terrible. It’s almost definitely a no-go for images with text (though maybe you can get rid of the text from the image and draw it with SpriteBatch.DrawString ? It’s worth considering, at least). In a non-DxtCompressed image, each 4px x 4px chunk would take up 4*4*4 = 64 bytes. If the image has no alpha (or only a 1 bit alpha channel – this is rare, though) then Dxt1 is used. If the image has alpha then Dxt5 is used. In Dxt1, that 4px x 4px chunk is reduced down to 64 bits (yes, bits) meaning 8 bytes. That’s an 8x compression ratio. In Dxt5, it is reduced down to 128 bits meaning 16 bytes, which is still a 4x compression ratio.

The draw back is that images using Dxt compression must be “power of 2” sized. This means that the width and the height each must be a power of 2. So 64x64, 128x16, 256x512, etc. Since it works in 4x4 chunks, it presumable should be at least 2^2 (i.e. 4), but I’ve never tested that.

Let’s say you have a full screen image, 800x480. As mentioned above, this will take up 1,536,000 bytes (1.46 MB). To use Dxt compression, you would need to add padding to the image to get each part up to the next power of 2 (SpriteBatch clips off the parts that go off screen so you needn’t worry about adding in filler – though if your image already has some transparency anyway, you might as well make the filler 100% transparent). The next nearest is 1024x512. A 1024x512 image using Dxt1 (no alpha or 1-bit alpha) would come to (1024 / 4) * (512 / 4) * 8 = 262,144 bytes (0.25MB). Using Dxt5 (with alpha) the file size would come to (1024/4) * (512/4) * 16 = 524,288 bytes (0.5 MB). As you can see, even adding all that padding in to get the image up to power of 2 sizes for width and height still next us a file that’s about 1/3 of the size of the original for Dxt5 and about 1/6 of the size for Dxt1.

To enable Dxt compression, in the Solution Explorer in Visual Studio, right click on the image file and select “Properties”. In the properties, you’ll see “Content Processor” with a little arrow to its left. Click the arrow to expand the category and under “Texture Format” click where it says “Color” and choose “DxtCompressed” from the drop-down menu. That’s it. It’s that simple. If you want, the content pipeline will even resize to the nearest power of 2 for you. Note, however, that it does this by scaling the image itself up in size (not by adding some padding in to the right and the bottom as you would do in an image editing program) which may not be what you want at all. You could do that and then draw it back down to the “proper” size by using a destination rectangle or Vector2-based scaling in SpriteBatch.Draw but between the lossful nature of Dxt to begin with and the further artifacts that down-scaling would introduce, the results would probably look terrible. If it’s an image where scaling it doesn’t matter, though, then letting the content pipeline do it is probably fine. Try it and see – you can always open up Photoshop, Gimp, Paint.NET, or whatever you use and pad the image yourself if the results aren’t satisfactory.

----------

Another thing to consider is if you have an image that repeats (e.g. a fence), you only need one section. Drawing the section in a loop all across where it needs to be is going to be just barely slower than drawing one big image and you could go easily from an 800x480 image to an 18x480 image (just like I did as a consequence of looking more closely at one of my backgrounds while creating this tip sheet). That’s a drop from 1.46 MB to 33.75 KB!

----------

Advanced Technique: It’s entirely possible to procedurally generate a texture at runtime. You create a new Texture2D field or property in your class, instantiate a new instance of it using its constructor, specifying a width, height, and some other parameters (I use the overload that takes more parameters so I know exactly what I’m getting). It’s then a matter of building a one-dimensional array of data (most likely Color[ ] ) of a size equal to width * height (starting at top left and going across row-by-row), and then using the SetData method of Texture2D to create it. Creating things like gradients shouldn’t be too hard. How much cooler your images are will depend entirely on how good you are at the kind of math necessary to do such things. Beyond a certain level of complexity, it likely loses its practicality.

Sound.

There are several tricks for shrinking your sound content. First up is your music. If you have in game music (and you really should – music sets the mood of a game) then you’re probably already using either mp3 or wma files. What you may not know is how the content pipeline handles these files by default. In Visual Studio, in the Solution Explorer, right click on your wma or mp3 (pick one if you have more than one) and choose “Properties”. In the properties windows, click the arrow to the left of “Content Processor” to reveal the “Compression Quality” property. This will be set to “best” by default. Now “best” means best quality, not best compression. Most specifically, it means that regardless of what file format you’ve set, it will convert it to a 192 kbps constant bitrate (CBR) WMA file. Setting it to “medium” will convert it to a 128 kbps CBR WMA file. Setting it to “low” will convert it to a 96 kbps CBR WMA file.

Now I wouldn’t personally drop music down to 96 kbps, but in the late 90s, 128 kbps was the standard compression that almost everyone who had digital music used. And for file size, it’s a big difference. My first WP7 game has a nice 2 minute harp piece in counterpoint that my brother composed. The original was a WAV file. I used Expression Encoder 4 (a very nice free tool from Microsoft) to turn it into a WMA file. The original 20,799 KB WAV shrank down to 1902 KB at 128 kbps while it came in at 2854 KB at 192 kbps. That’s a 0.92 MB difference! However if you don’t change the content processor “Compression Quality” option to “medium”, it’ll take your 128 kbps work and re-encode it at 192 kbps. The same is true for MP3 files which it also turns into WMA files – yet another reason that I prefer to work with EE4 to go to WMAs from the outset rather than bouncing from MP3 to WMA and losing a small bit of sound quality in the process. It’s worth noting that the content processor will look at your source file and just pass it through unaltered if it is already a WMA at the correct compression quality. And just for completeness, I did run it through once at “low” – the resulting file size was 1430 KB. It’s a savings of close to half a megabyte more on a 2:00 song, but at that point I think you lose too much quality. Though depending on the piece, even 96 kbps might not matter.

----------

The other part of sound is your sound effects. These are very likely going to be WAV files. Even a few seconds of WAV data can be quite large, though. The content pipeline can once again help! The XNA Creators Club website has a Sound and Music sample for WP7 up right now (it’s a neat sample that I recommend checking out). It comes with several WAV files, one of which is EngineLoop.wav. This is a 4.867 second sample that comes in at 480k original size. I decided to do my tests with this since anyone who wishes can grab the sample and try it out.

By default, the content pipeline is set to “best” (same procedure as above: right click on the file in Solution Explorer, choose “Properties”, expand “Content Processor” and look at “Compression Quality”) which as best I can determine means that it will take whatever WAV file format you have and convert it to a 16-bit signed PCM WAV file at 44.1 kHz. It’s only a guess, but what comes out of the content pipeline at “best” is a 457 KB XNB file and what comes out of Audacity (a great little program for creating, editing, and exporting sounds of all types – I use the 1.3.12 (Beta) without any issues) when I export the file in the aforementioned format is also 457 KB.

Now I have no idea what exactly the content pipeline does for “medium”, but what comes out is a 125 KB XNB file that sounds virtually the same as the 457 KB version to me. What I mean is that while I believe that I hear a slight difference, listening to it closely I really don’t think I do actually notice a difference. In other words, I might only be noticing a difference because I know that it’s changed. That’s a big difference. Especially if you have lots of sound effects or a couple of longer ones. You may also wish to experiment with “low” on this. Setting EngineLoop.wav to “low” produced a 63 KB file which, given the nature of the effect, wasn’t markedly different from the original. Unlike with music, a sound effect can often be degraded quite a bit without any serious loss since the goal is usually some sort of punctuation or emphasis of an action. But it’s very dependent on the sound itself as some effects will suffer worse than others.

Conclusions.

There are undoubtedly other ways to shrink your initial XAP size. Creative reuse of assets is another avenue worth exploring, though that’ll be much more game dependent. Things like building enemies from components and using SpriteBatch’s tint color to shade different pieces in different ways (remember the original Double Dragon? Many NES games for that matter?), using pitch to alter sound effects (e.g. to make your gun sound different than enemy guns), and whatever else you can think of. I’m still plugging away on some things in the background, but wanted to get this out now while the topic was still fresh in my mind (a fellow XNAer was contemplating it on Twitter last night and it got me to thinking). Lots to do, not least of which is to get another game and an app done (hopefully in time for launch). And on the off chance that anyone “important” reads this – I was quite surprised that there was no “learning” or “education” category in the Marketplace App Categories. I guess “Productivity” covers a fair amount of ground and might work for most such things. Still – bit of a surprise. It’d also be nice to see descriptions of what one should expect to find in the various categories and subcategories so that we developers can make sure to get things in the right place. Though it’s perhaps a bit early yet for that. Until next time, good luck!

posted on Thursday, September 2, 2010 1:40 PM Print
Comments
Gravatar
# re: WP7, XNA, and XAP Size
Jeremy Brayton
9/2/2010 4:14 PM
I'd be interested in a WP7, SL, and XAP Size companion myself. Spritesheets in XAML sound doable but I haven't bothered attempting an implementation.

I mainly wanted to comment on sound. If your sound file is particularly voice only, you can almost always use the lowest setting. Podcasts and others often get away with ultra low 64k mp3 or wma and work rather well. Personally, I can hear the difference between 192k and 320kbit mp3s so I may be extremely biased on fidelity loss.

Having said that, nothing is stopping you from storing certain bits 'in the cloud' and just retrieving it. Why store the final level music when you can just as easily stream it to your IsoStorage while the game is playing? I believe nothing is stopping you from doing this with *any* content as I've had the idea to just push 'levels' to the web and not even bother packing them in with various 'games' but I've not even attempted a prototype. Everything I've seen at least screams 'possible'.

Definitely a post I intend on keeping in my toolbelt so to speak. Thanks.
Gravatar
# DLC
MikeBMcL
9/3/2010 4:03 AM
I shied away from the idea of downloading additional content based on what I remembered of the App Cert Reqs at the time. Looking at them again, it seems like it might be permissible (though you get into disclosure issues depending on the size of the additional material) and customer expectations that when they buy and download the game, they'll have the whole game. I do think that it'd be something worth exploring for add-on content and for optional content (e.g. additional tracks in a "music" game).

With the present Application Policies in the App Cert Reqs, it seems as though offering pay DLC for games is a no-go, such that hosting costs for anything you offered for download would have to be priced in at the outset. That'd be my other reason for being leery of having required content as DLC - while I fully plan to be making games for years, dealing with server issues (including the possibility of someone hacking the server and messing with the DLC) along with the fact that you'd be locking yourself in to maintaining that for 4-5+ years (though you could always push an update later that just included all content if problems arose) are things that give me pause about such a solution. If the content is worth pushing past the 20 MB OTA (over the air) XAP limit then gamers should (hopefully) prove willing to deal with DLing it through Wi-Fi or tethered.

Still, definitely another thing for people to consider. Thanks for reading and sharing (especially the bit about voice content - that's a great tip), and I'm glad you liked it.
Gravatar
# re: WP7, XNA, and XAP Size
Barnaby Smith
9/4/2010 7:43 AM
Great post Michael.

Ronimo Games, the creators of Swords & Soldiers did a fantastic article on how they compressed the game from several hundred meg to 20 meg. (Their textures alone went from 141 to 8 meg).

http://cmpmedia.vo.llnwd.net/o1/vault/gdceurope09/slides/J_vanDongen_Programming_CompressingLoadsOf.pdf
Gravatar
# re: WP7, XNA, and XAP Size
Steve 'Sly' Williams
9/5/2010 7:50 PM
Nice article, but it just skims the surface. To get our splash screens up as quick as possible, we keep our full-screen images as png and tell the content project to not compile them. Then we use Texture2D.CreateFromStream() and pass the png file directly to it. This has several advantages:
- no need for POW2 sizes. Keep your splash screen at 800x480.
- no compression artifacts as png uses zlib which is a loss-less compression format.
- super quick to load. Splash screens tend to have lots of empty space which png compresses extremely well. Our initial 800x480 splash screen which contains copyright info is less than 7KB. Another splash screen which has graphics covering the entire 800x480 surface is 343KB, but that is perfectly acceptable as it still has zero compression artifacts.
Comments have been closed on this topic.
Bob Taco Industries is an ISV focused on game and app development for Microsoft platforms headed up by Michael B. McLaughlin. Mike is a Microsoft Visual C++ MVP (previously an XNA/DirectX MVP from 2011-2013), a developer, a writer, a consultant, and a retired lawyer. If you're a developer who is just getting started, consider checking out the BTI website's section for developers for links to code samples and other helpful sites.
Tag Cloud