One of the things I am currently working on is an architectural change to the engine- previously, our most basic graphic object would be rendered with a 2-component vector designating position (translation in OpenGL terms) and another 2-component vector designating orientation, or rotation and scale. This has worked fine for us so far, but it means we have to modify the vertex geometry of the object if we want to do nonuniform scaling or shearing. Petri and Martin talked about “juice” in a great GDC talk from a few years back, and in order to make the game more juicy I thought about the 12 principles of animation – more specifically squash/stretch – which meant we needed more options. Thus, the basic graphic transformation is now described by a 2×3 matrix just like fixed-function pipeline uses 3×4 (4×4 actually, usually only the first three columns are relevant for world transform) to give us more options.
Now, I normally do not like to talk about features that are in such early stages of development – there is simply nothing here to look at, and if it doesn’t pan out there’s not much to learn from it. We do a lot of experiments in code as well as in design, and a good chunk of that work will never see the light of day. Doing this change, however, gives me an opportunity to talk a little bit about the history of the engine and the KISS principle which, when I have interviewed and evaluated engineers in the past, has been a common problem for inexperienced software engineers.
Way back in 2009 when we started working on the Backworld prototype, I made the decision to use an engine I had written for a different project – this project had mostly unique art that was already drawn in place so the requirements were pretty low in terms of rendering functionality. Everything was rendered upright and with pixel-perfect art – graphic objects only needed the position at which they were to be rendered.
The first couple of things I changed was adding the aforementioned orientation vector and support for ARB_FRAGMENT_PROGRAM, or low-level pixel shaders. We knew we could not rely on the fixed-function pipeline to get everything we needed for the painting so I took the solution that was quickest to implement – as opposed to high-level shading languages ARB_FRAGMENT_PROGRAM only requires the equivalent of an assembler rather than compile and linking steps, and there is no need to resolve symbols. My main reason for using low-level shaders, however, was for the sake of compatibility. A year prior I was hard at work on the PC version of The Chronicles of Riddick – Assault on Dark Athena and most of my time was spent on figuring out why different drivers had problem with different shader functionality. I wanted to avoid dealing with this for what was just a one-month competition project so I opted for the language that was simpler and had been around for longer.
At a later time, I opted to add support for ARB_VERTEX_PROGRAM – previously, we had simply relied on the fixed-function pipeline but some effects that we added made that really ineffecient. Since we had been adding features and content for quite some time, this took a couple of days’ work. The change to 2×3 matrices was simpler but we had even more code that needed to be changed, so that took a little bit more than a day as well.
Now, that is time I would not have had to spend if I’d simply used the final system to begin with – but at the same time, there are literally hundreds of knobs in a proper render wrapper that we are unlikely ever to use. Implementing all of it would take take months, would create performance overhead and additional overhead in working with the graphics objects – and this is just referring to graphics. Rewriting code as our needs change is simply saving time in the long run. When looking over engineering applications in my day job, I have frequently come across code samples where the engineer tries to be generic and instead prepares for situations that are unlikely to ever occur. While it can be difficult to ascertain in job applications, good code requires context and does exactly what the context calls for, no more.
The fine folks over at bitsquid have a blog where they write about simple structures to solve common game programming problems in a modern environment, if you are at all interested in the subject you should give it a read!