Game Design, Programming and running a one-man games business…

Rolling your own 2D Animation compressor in directx FOR FUN

This is the short ‘story so far’ on animation compression for Production Line, my next game. I use my own engine that runs on directx9,a nd the game is isometyric in style, so uses a lot of spritesheet-style animation frames. In short, this is about how I enable animations like the one below (excuse the crappy gif compression, actual image is less pixelated in the real game), to fit into tiny amounts of video memory. (The disk version of that anim is 4MB, compressed for download its 355k). This is an ‘idle’ anim of a marketing manager in the game checking his phone to make all-important marketing related phonecalls…

marketing

You might think that cannot possibly be a 4MB animation, but you would be dead wrong. The actual source graphic is 128 wide by 256 high, after cropping, meaning that a frame of it gets delivered from the artist looking like this:

people_business_phone_sw_0218

Thats no problem, but a complete animation cycle is 300 frames long, and I need two versions, one to look towards, one away (I can flip in X axis for the others). 128 x 256 x 300 is 9.8 million pixels, or a 4096 x 2,400 pixel sprite sheet, which takes up 39MB of VRAM. Assume both directions are in use = 80MB, 3 different characters is 240MB. Thats with one skin color and one gender. Ouch. So obviously we need to compress it.

I rolled my own compression system for fun, and to give me total control over how it works. The first step is to cut out any dead space and remove any duplicates. When our little dude is on the phone, the image doesn’t change, so we have a lot of duplication in that colossal theoretical sprite sheet. How did I approach this?

I decided upon a 64 chunk ‘grid’ for any image that would be animated, dividing it into 8 sections horizontally and vertically. With the raw image I get given, that gives me this:

grid

Actually this is annoying because his hair only just clips into another row, which is inefficient… never mind. The most obvious thing here is that a lot of the animation frame is just empty space. In order to kill both the ’empty space’ and the ‘same as last frame’ problems in one go, I work out a CRC (basically a unique signature) for each of those grid squares, for each frame, and store it in a great big list of data as a pre-processing step.

With all that data in memory, I then go through each chunk of each frame, and look for previous frame chunks with the same CRC. If I don’t find any, I mark it as a ‘used’ chunk. if I do, I just make a note of which earlier chunk to use, and increment the use count on the old chunk. Once this is done, I can go through all the chunks and discard the ones that have a zero use count (or are blank).
Stage two is to create a whacking great spritesheet of all of the chunks that I actually *do* use, which looks like a weird mashup of imagery like this:

sheet

(It also has an alpha channel). I can then go through all of those ‘used’ chunks and make a note of the UV values for each of them in my new spritesheet. Now, the final stage is to go through every frame of the animation, and make a note of which chunks actually get drawn, and the index into my list of ‘used’ chunks. This means I now have a big text file for the animation that tells me which of the 64 maximum chunks of the spritesheet I need to draw in each frame. In the case of this phone-dude, that shrinks the actual texture memory usage by 95%, meaning I can easily have a bunch of different animations.

Thats where I am right now, and its pretty fluid, and works without bugs. The next step will be optimizing the code, rather than the source. An optimisation I managed today was to actually successfully only draw the non transparent chunks when I do the rendering of each frame, which reduces the number of polys and draw calls. The next obvious optimization is to spot when a chunks ‘source id’ is the same as the previous frame, and then not bother updating it at all. Right now, I redo the UV lookup and apply the values every frame for this dudes legs, even though they don’t move during the whole animation. Thats a bit too much processing for my liking.

I’m sure middleware *does* exist that does this, but I like to code stuff myself so I know 100% how it works, 100% how fast/slow it is, and can ensure compatibility with all the rest of my code and workflow. I’ve probably spent a week doing this, maybe a bit more, but I have a pretty cool system now that lets me knock up a 30 secon text file when I get a new bunch of animation source files, then hit the launch button and the game will do all the preprocessing and give me the compressed data automatically. Yay.

This also means that with only 4MB+4MB of VRAM for an animation for a character, I could double the characters, and have both genders without running out of VRAM. This also makes the game more usable on low end PCs. Now the limit is my art budget, rather than the hardware :D Anyway here is a reminder of the final video, and also that this is for my car factory game: Production Line

marketing


9 thoughts on Rolling your own 2D Animation compressor in directx FOR FUN

  1. Hi, what about bruteforcing the grid chunk size for each separate animation? You are using 8×8 but in some circumstances I guess it will work better with other sizes. Offline CPU time is cheap.

  2. Interesting idea, although that involves a bit of code change as the actual in-game renderer uses 8×8 as a fixed quantity right now. Very interesting suggestion though, there is definitely a lot more low hanging fruit to grab performance-wise.

  3. Are there more details to the animation than what you’ve shown here? 300 frames at 60 fps = 5 seconds, and I only count “one mississippi” between the time when he starts moving and when he has the phone to his ear. At all other times, he’s stationary. Could you cut the spritesheet down to only the key frames and then keep a timer to know which frame to draw at a particular time?

    1. yeah a lot of it is idle time, which is why my compressor cuts 95% of it out, but like I say, I need to completely idle the animation during those frames rather than re-calculating them with no change. the texture memory has been saved, the CPU…not yet.

  4. So huh, literally all you did was turn it into a tilemap? Because this is pretty much how backgrounds in old systems work. Also Neo Geo sprites (which are just glorified tilemaps, really). All you’re missing is the ability to flip tiles (or is that there?). Note that you *can* do 90º rotations too if you allow swapping the axes.

    You may want to switch to 16×16 tiles (would give you a 8×16 split in this case), sounds like a nice number that could work better for all graphics in general in the long term (and you’d measure graphics in tile dimensions). Also would reduce the waste in the hair and feet here =P

    I once wondered about doing something like this, but on top of that also using paletted graphics (i.e. 8bpp instead of 32bpp – note that this is per graphic, not the whole screen). The downside is that then you’d need to do texture filtering on your own (since the GPU’s isn’t useful anymore), the upside is that recoloring becomes pretty easy (just change the palette). Seeing as you mentioned skin tones, you may care about that =P (also can recolor the hair and clothes too)

  5. Are most of your animations like this one, where part of the sprite is moving while another part is static?

    Otherwise, I think most popular texture packers, can give you whitespace elimination, duplicate alias, and you would get similar results, with a simpler solution.
    You can also in your animation code preprocess the animation and if there is a series of aliased frames, then just remove the aliases and make the duration of the frame larger.

    1. Yup I presume I’m only doing what others are doing, but I like to have total control over exactly how it works, so it plays nicely with my own engine, my renderer, my vram management and so on.

        1. Just tried it. its ok…. but:
          1) it doesn’t support dds files as import
          2) if i select pow2, it gives me square textures even when the source stuff fits into a 2048×1024 texture, so pretty wasteful :D

          The actual spritesheet part of my code is the easy bit, its the writing out of a custom animation data file that has taken the work 9and the debugging!)
          its also nice to have spritesheet packing built into the game, so its all one exe I can run, it can update and launch

Comments are currently closed.