Here's a description of my rendering layer:

I use a material system, with an interface that exposes a list of typed parameters. The renderer acts as a material instance factory, and there's a separate XML DOM parser-let that can populate a material with property values based on text input.

The renderer also acts as a vertex- and index buffer allocator; the users of the renderer can specify two usage patterns: "changed very seldom" (for static geometry) and "probably changed every frame" (for particle systems, CPU skinned geometry, etc).

Last, the renderer can take "transform state" which turns out to be either a modelview matrix, or a chain of modelview matrices (for posed skeletons).

The actual renderer call for rendering and state management is ultra simple:


  virtual void begin() = 0;

  virtual void drawMeshWithMaterialAndState( 
    I_VertexBuffer * vb, I_IndexBuffer * ib, I_Material * m, 
    Matrix4 const * transform, size_t transformCount ) = 0;

  virtual void present() = 0;

That's it! That's the only way you can render stuff in my renderer. There are a bunch of separate classes that make certain kinds of rendering easier, like there's something which aggregates 2D quads (for UI) and, at the bottom, issues one or more buffers to the renderer based on how many materials are used.

There are global functions to allocate a C_DX9Renderer or a C_OGLRenderer to get it all working; these global functions just return the abstract I_Renderer interface, so the specific implementation is hidden inside each subsystem. Each renderer implementation sorts by material or vertex buffer or distance to camera or whatever based on what works best for that implementation. Gross transparency far-to-near sorting, and occlusion near-to-far sorting, goes here, inside present().

Having to pre-declare the materials you are going to use (with some tuneable variables such as alpha and color, or shader uniforms) is a major performance and code clarity win compared to systems that want to bang every little bit of the renderer state.

I'm working on offscreen render target support; it seems I'll have to add another function to my renderer interface to allocate a sub-render-target, and pass the specific render target to begin(), although it's not all worked out yet.

Note that camera, culling, and a bunch of similar higher-order functions go outside of the renderer; you can use a portal system, a scene graph, an octree, or a plain linked list of everything; that doesn't matter to the renderer!