Brooks Bishop — October 1, 2012
I finally renewed my App Hub account, meaning it was time for me to make sure everything is working on the actual Xbox hardware. When I went to see how my current build was running… let’s just say things were pretty bad. So I’ve been in the process of getting the game up to snuff for its console home, and I thought it might be nice to outline a few of the specifics.
So glad I paid attention in Linear Algebra
Aeternum doesn’t use the built in XNA SpriteBatch. My engine uses a custom group of batched primitive drawing tools. One reason for this, being that the SpriteBatch simply isn’t powerful enough for the amount and types of drawing I’m doing.
The Bullet engine is one of these, although it actually uses a custom Vertex and Pixel shader combo, similar to the 3d Particle effect demo to move most of the drawing math to the GPU.
However, the bulk of the sprite drawing is done by filling a large dynamic vertex buffer with quads having vertices calculated by the CPU, just like the SpriteBatch (to the best of my knowledge). My original implementation used the XNA Matrix structure to perform these calculations, as they’re pretty simple to get going, and are already built to interact with the Vector2 object for transformation, etc. However, the sheer bulk of math being done in the Matrix routines was starting to cause problems. I first tried using the ref/out functions to pass around pre-made Matrix objects by reference, but it just got to be unwieldy and wasn’t helping much.
My answer was to rip that out and start from scratch writing the transformation math myself inline. What’s nice in this case, is that since my engine is purely in 2D the math is actually pretty simple, and all I needed was a translation, a rotation, a scale, and a final translation to generate vertices. It looks a bit like this, very simplified:
float c = (float)System.Math.Cos(sprite.Rotation); float s = (float)System.Math.Sin(sprite.Rotation); float x1 = -sprite.Origin.X; float x2 = sprite.Texture.Width - sprite.Origin.X; float y1 = -sprite.Origin.Y; float y2 = sprite.Texture.Height - sprite.Origin.Y; Vector2 topLeft = new Vector2( (x1 * c - y1 * s) * sprite.Scale.X + sprite.Position.X, (y1 * c + x1 * s) * sprite.Scale.Y + sprite.Position.Y); Vector2 topRight = new Vector2( (x2 * c - y1 * s) * sprite.Scale.X + sprite.Position.X, (y1 * c + x2 * s) * sprite.Scale.Y + sprite.Position.Y); Vector2 bottomLeft = new Vector2( (x1 * c - y2 * s) * sprite.Scale.X + sprite.Position.X, (y2 * c + x1 * s) * sprite.Scale.Y + sprite.Position.Y); Vector2 bottomRight = new Vector2( (x2 * c - y2 * s) * sprite.Scale.X + sprite.Position.X, (y2 * c + x2 * s) * sprite.Scale.Y + sprite.Position.Y);
Having said this, I would now like to say: you probably shouldn’t do this. This is something I did because I enjoy the challenge, and I wanted to see if it would really help. In my case, I think it did. But is it the best way to do this? I can’t really say. What I can say is that those linear algebra courses I took as part of my degree really paid off in their own way.
Scaling up is hard to do
One of my particle effects included a circular ring that was stretched to a large size while rotating. While very cool looking, and effective on PCs, with their beefy stretchy-rotaty power, the Xbox GPU was having a hard time coping. In this case, it was mostly the stretching outward that was hurting my frame rates, so my response was to reverse my intuition.
While initially I was using a small texture to save memory and let processing make up the slack, I instead went back and made a much larger texture, utilized mip-mapping in the content processor, and rewrote my scale interpolators to base off the new larger size. So instead of starting at 30% and scaling to 300%, I started at 10% and went to 100% of a texture that was three times bigger. So I effectively traded more texture memory for frames per second, which I think is a good deal in this case.
It’s full of bullets
This isn’t something I did lately, but I figure I would bring it up as an important optimization.
Aeternum as an engine generates no garbage. Although its technical heart is a custom scripting engine specifically designed for optimized handling of complex actions on large numbers of objects, namely bullets, I wrote before how hard I worked from the outset to make the whole system garbage free. Right now, it has a hard cap of 2000 simultaneous bullets on screen, although I’ve never written a usable pattern script that fills the screen up quite that much. I think it would be unplayable.
That’s not to say there isn’t garbage happening. The level scripts themselves allocate whatever they need, like new enemy instances. But the amounts are negligible, and I think I have everything working to where there won’t ever be a garbage collection in the middle of a stage.
The point here, is that thanks to the work I did in effectively managing object creation (most importantly in this case aggressive pooling) I’m not having to worry about garbage collection at all in keeping the Xbox build running smoothly.
With these bigger things out of the way, and the rest of the game appearing to run smoothly, I’m in the process of implementing the actual stage scripts and boss fights, and extensively testing them to make sure everything works properly on the Xbox without compromising frame rates. From there I’ll get into making sure the options and score table saves are working. That shouldn’t be too big of a jump since I’m already using the more up to date version of Nick Gravelyn’s EasyStorage library.
In all, I think it’s good progress. I probably should have gotten into making things work on the hardware earlier, but the financials didn’t line up until just recently.