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
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.
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.
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!