Monday, November 15, 2010

Abusing C++ Syntax for GL State Management

One of my little side projects is writing a light graphics library that I can better express my visual intentions while avoiding the common pipeline of graphics from artist + magic glue = something that sorta works.  There's been already plenty of work on animating humans more realistically (for properly rigged meshes).

Anyhow - that's beyond the point of this post.  When dealing with OpenGL I have to explicitly manage the state - many points enabling or disabling a state within a rendering function and returning to the previous state once out of the block of code.

What automatically gets called at the end of a block of code?  A destructor of course!  So, I ended up creating myself a neat little object that sets the state of the GL but reverts it at the end of a block.  It has cleaned up my code quite a bit as I'm can now worry about how the state is inherited from the calling function rather than how the calling function permanently alters the state.

To avoid uselessly calling GL functions, I further optimized the code by keeping track of what the internal state is and what state is desired for the next rendering call.  This prevents useless calls to the GL...

But, back to abuse of syntax; I like the idea of creating my own objects whose entire point is to abuse the scoping rules of C++ objects.  So, in the back of my mind, I've been mulling the idea of using blocks and destructors to denote the equivalent of a glBegin and glEnd (being a bad programmer, I've reintroduced immediate mode on top of OpenGL ES)...

This object would allow me to do something like:
{
  BeginDrawing drawObj(GL_TRIANGLE_STRIP);
  drawObj.vertex(....)
  //implicit end of drawing + submission to the GL
}

Seem messy?  I'll argue it's the lesser of two evils.  The compiler will now explicitly tell you if a block isn't ended.  I've even done odder things:
{
   RenderToTarget t(myFrameBuffer);
   DisableBlendFunc s;
   //Rendering calls ...
   //Implicit returning of previous state of GL_BLEND enable option...
   //Implicit return to previous frame buffer...
}

Isn't that elegant?  I sure think so!


Update on February 24th, 2015 (fixed some wording, added the following text):

Time flies, and there are a few additions that I should add to this document thanks to C++11.  We can further simplify and reduce errors thanks to the changes to the language.

Consider the render to target example rewritten using C++11 lambdas:
RenderToTarget(myFrameBuffer, []
{
   // Set up your state.
   // Do some awesome rendering to your frame buffer.
   // More stuff!
});

Now, RenderToTarget is a function rather than a class, and it requires an explicit scoped set of operations.  The constructor and destructor are no longer abused, rather RenderToTarget can now do some setup, invoke the lambda, and tear down -- which can include restoring of state.

For general state, I'm hesitant to recommend lambdas as the number of nested blocks would explode thus hindering legibility.  Using objects (as described before) would work.  I'm more partial towards the following:
StateBlock([]
{
   // Set up state...
   // Render! Render! Render!
   // More stuff!
});

Well, now RenderToTarget is just a special case of StateBlock.  And StateBlock allows setting up arbitrary states, can be nested as needed, and at the end of the block undoes any state changes done within.

Obviously, for this to work, the simplified API must provide its own state altering calls, akin to the objects previously described.  And avoiding using scoped objects means that we don't have the odd issue of various dummy variables laying around simply to have their destructor get called at the end of the block.

No comments:

Post a Comment