How Decisions are Made in a Commercial Game Project
I always enjoy reading this kind of stuff from other teams; it's fascinating to see the parallels between our projects and others'. Sometimes we make similar decisions, other times our needs, preferences, and/or whims simply take us in other directions.
With the new project I have on my plate of massively overhauling a 3D game engine, I thought it would be interesting to catalog some of my own processes and decisions. This is partly useful to me as it provides a way to force myself to think through and rationalize each decision, for an audience likely to be far more objective about certain things than anyone on the team. It's also a great documentation dumping ground - I can come back here and refer to my decision-making process when the time comes to write it up formally for internal docs.
And who knows... maybe someone will find something interesting to read in all this lot of bilge [smile]
Determining what data format to use
One of the most significant challenges of building an engine into an existing game codebase is achieving interoperability. Decisions that would be utterly trivial to make in a new system can have really massive consequences when you're trying to work inside a large body of code - most of which you do not want to have to rewrite. In many cases, decisions that are hard enough by themselves become horrendously complicated by the fact that they can cause the rest of the project to spiral out of control. Much of the challenge is actually a form of damage control, where we have to minimize the ripple effects of our decisions and changes.
Getting Past Fixed Point Data
We have a tremendous amount of legacy code using fixed-point arithmetic. Back when that code was first developed, it was a good idea; certainly it was faster than floating-point. But now, times have changed - a lot - and fixed point operations like multiplication are, by necessity, going to be over twice as slow as floating-point.
Obviously, in a totally new system, that's a no-brainer: just use floating point! However, that option isn't so simple for our case. We have major systems which, by virtue of their design, just simply won't be able to use floating point values any time soon. So while we will be eliminating a lot of fixed-point code, much of it will have to remain. Thankfully, the system is factored well enough that we can define clear interfaces across which we have to convert from floating-point to fixed-point.
This issue took a couple of hours to talk over and figure out, but overall it wasn't too bad. The really nasty ones would come later.
Choosing Data Precision
OK, so we more or less settled on floating-point data. Unfortunately, it's not that easy. In the C++ environment we're developing in, we have two choices: float or double. floats are 32 bits, have no additional memory footprint above the existing fixed-point system, and are fairly effective for many purposes. doubles are 64 bits, which means extra storage space, and might possibly cause alignment problems. However, they do provide vastly better resolution and accuracy than floats, so they have their uses.
In fact, we know from (harsh) experience that we have to use double-precision values for many things. World-space coordinates, for instance, are huge in our game, and double is literally the only accessible data format that can cope with having vast distances and minute details in the same coordinate space. So many areas have to be double anyways.
Worldspace position coordinates, transformation matrices, even direction vectors are all fairly lightweight, so representing them in double is no problem at all. Where it gets tricky is the actual polygon mesh data which is passed to the hardware.
Our polygon meshes are huge, and doubling the storage requirements (by going from a 32-bit representation to double's 64 bits) would have very bad consequences for the game's memory demands. Obviously, it is in our best interests to minimize this storage size. There's one more chainsaw to juggle: our vertex buffer format only understands 32-bit floats; and we can't change that without totally screwing our minimum hardware requirements.
The solution, of course, lies in the fact that position and transformation data (worldspace coordinates, rotation values, etc.) is independent of the pre-transformed mesh. The hardware does the final transformations anyways, and we can pass it doubles without problems. In the worst-case scenario, we can convert back to float just before we send stuff to the hardware, and that will be rare enough that it won't totally kill performance.
We're not out of the woods yet, though. There are large subsystems - written to use fixed-point math, naturally - which need access to vertex data in order to do things like collision detection. Rewriting those modules is out of the question for now (unless we manage to discover another half-dozen programmers) and so we'll have to provide a way for them to access geometry data natively.
There are three options: store one copy of the mesh data in floating-point, and convert to fixed-point when needed by collision checks etc.; store one copy of the mesh data in fixed-point and convert it to floating point each frame before it is sent to the vertex buffer; or store parallel copies of the mesh, one in each format. The first two have severe performance implications, and the latter has severe memory implications.
However, the third option really isn't so bad. We only need to use a reduced LOD version of the mesh for those systems that read the mesh data, meaning it will be significantly smaller than the full renderable mesh data. We can also strip out some things like normals, texture coordinates (occasionally), and such. Even better, not everything that is rendered needs to have a readable mesh, so there will be some cases where we don't have to store the second copy of data at all. So from a storage standpoint, the impact isn't too bad - and the fact that it avoids major performance problems and major code rewrites makes it the clear winner.
That's already quite enough reading for one day I think [smile] Besides, the next issues I want to cover relate to designing the scenegraph itself, and there are still some things I haven't had a chance to think out fully yet.
So... more later. How much later I do not yet know, but I hope that between now and then there will be lots of sleeping.