I apologize in advance for the long post [smile].
I'm starting work on a software renderer. So far I have only implemented wireframe rendering, but I'm starting to plan ahead a little bit, and I'm thinking about how I should handle render states. For now let's only consider backface culling and blending. Let's say that the options for backface culling are: None, CW, CCW, and for blending: Copy, ColorKey (maybe blending is not the right term here but that's beside the point).
The obvious way to handle this is to simply use switch statements, but that would mean using a conditional for every triangle (for backface culling) and for every pixel (for blending), which will incur a significant performance overhead (especially as more render states are added). For the same reason, I don't want to use function pointers or virtual functions (something like the strategy pattern). I want any such conditional code to be done at the object level.
I own Tricks of the 3D Game Programming Gurus and there it is (partially) accomplished by writing dozens of different functions that look almost identical (to avoid the conditionals), and using a
huge block of if statements to decide which function to call. Needless to say, this isn't pretty, and as the number of render states and rendering options increases, this becomes completely unmanageable.
This article uses a policy-based design to solve this problem, and I thought it would be perfect for this, but I then realized that I have no idea how I would choose the policy at run-time. Basically what I want to do is this:
if (cullMode == CW)
CullPolicyCW::cull(...);
if (blendMode == Copy)
BlendPolicyCopy::blend(...);
but I want this to happen at the object level, that is, I want to have a drawTriMesh() function that uses policies to render a triangle mesh, and "plug" the correct polices based on the current render state, like this (think of this as pseudo-code):
if (cullMode == CW)
cullPolicy = CullPolicyCW;
if (blendMode == Copy)
blendPolicy = BlendPolicyCW;
//...
Renderer<cullPolicy, blendPolicy>::drawTriMesh(...);
And now comes my problem - I don't know how to implement this in a good way. The above is "pseudo-code" because I can't have a variable that stores a type (I think). I could implement this with virtual functions and the strategy pattern, but the whole point is to avoid this for performance reasons.
I had one "neat" idea, but because this post is long enough I will just list the source code for it, and answer any questions that come up. Note that this is just prototype code quickly throw together to test the idea:
#include <iostream>
#include <map>
#include <cassert>
using namespace std;
enum CullMode {
None,
CW,
CCW,
};
enum BlendMode {
Copy,
ColorKey,
};
class Renderer {
public:
virtual ~Renderer() {}
virtual void render() = 0;
};
struct CullPolicyNone {
static void cull() { cout << "None" << endl; }
};
struct CullPolicyCW {
static void cull() { cout << "CW" << endl; }
};
struct CullPolicyCCW {
static void cull() { cout << "CCW" << endl; }
};
struct BlendPolicyCopy {
static void blend() { cout << "Copy" << endl; }
};
struct BlendPolicyColorKey {
static void blend() { cout << "ColorKey" << endl; }
};
template <class C, class B>
class RendererImpl : public Renderer {
public:
void render() {
C::cull();
B::blend();
}
};
struct RenderState {
explicit RenderState(CullMode c, BlendMode b) {
cullMode = c;
blendMode = b;
}
friend bool operator<(const RenderState &l, const RenderState &r) {
int d = l.cullMode - r.cullMode;
if (d == 0)
return (l.blendMode - r.blendMode) < 0;
return d < 0;
}
CullMode cullMode;
BlendMode blendMode;
};
class Device {
public:
Device():rs(CullMode::CW, BlendMode::Copy) {
renderMap[RenderState(None, Copy)] = new RendererImpl<CullPolicyNone, BlendPolicyCopy>();
renderMap[RenderState(CW, Copy)] = new RendererImpl<CullPolicyCW, BlendPolicyCopy>();
renderMap[RenderState(CCW, Copy)] = new RendererImpl<CullPolicyCCW, BlendPolicyCopy>();
renderMap[RenderState(None, ColorKey)] = new RendererImpl<CullPolicyNone, BlendPolicyColorKey>();
renderMap[RenderState(CW, ColorKey)] = new RendererImpl<CullPolicyCW, BlendPolicyColorKey>();
renderMap[RenderState(CCW, ColorKey)] = new RendererImpl<CullPolicyCCW, BlendPolicyColorKey>();
}
~Device() {
for (RenderMap::iterator it = renderMap.begin(); it != renderMap.end(); ++it)
delete it->second;
}
void render() {
Renderer *r = renderMap[rs];
r->render();
}
public:
typedef map<RenderState, Renderer *> RenderMap;
RenderMap renderMap;
RenderState rs;
};
void testRenderer() {
Device *r = new Device();
r->render();
r->rs.cullMode = None;
r->rs.blendMode = ColorKey;
r->render();
delete r;
}
int main() {
testRenderer();
}
Using this approach, I don't have to write many functions or use a huge block of if statements, but as you can see, the renderMap initialization code will quickly grow as more render states are added.
I don't really know much about advanced template techniques so maybe I'm missing something obvious, and maybe there's another solution altogether (how did people who wrote software renderers in pure C handle this?). Any help is greatly appreciated.