Error handling sucks. I mean, I've got code to write! I don't want to have to waste my time making sure my function calls actually worked. After all, nothing ever goes wrong when I'm running it, so any problems are the user's fault.
Or something along those lines. That's one thing that always annoyed me about C-style libraries: you had to sit there writing all this code to handle errors on every single damn call. I can't count the number of times I've seen OpenGL examples where they never do a single error check, apparently because it's too much work.
So when I started writing GL code in D, I realised pretty quickly that this
was sub-optimal. After all, you want to know when something's gone
wrong (if you don't find out, how can you ever fix it?) but having to write
all those ifs was out of
the question on account of me being supremely lazy.
One way around this is to have a function that takes the error code from a
function, and throws an exception if its something other than
NO_ERROR. Sadly, OpenGL doesn't use return values; it has a
separate function called glError that tells you if an error's
occured.
OK, we can deal with this; we just need a templated function that checks for an error, throws an exception if there was one, or passes back what we pass into it.
T glCheck(T)(T result)
{
if( glError() == GL_NO_ERROR )
return result;
else
throw new GLException("OH NOES!");
}
And that does work. Well, except for functions that have void return types. That's
when it starts to get a little ugly; we need to have a different function that
we call afterwards.
And this is all well and good if you happen to like simple solutions to problems. Not me, though. I wanted something that I could stick in front of any GL call and have it do error checking. I also wanted to try and remove the double closing paren problem (every time you nest an expression, it gets just that tiny bit uglier).
So let's change things around a bit. Instead of a function that we pass the GL call's result to, let's create a function that wraps the GL call.
ReturnType!(Fn) glCheck(alias Fn)(ParameterTypeTuple!(Fn) args)
{
alias ReturnType!(Fn) returnT;
static if( is( returnT == void ) )
Fn(args);
else
auto result = Fn(args);
glCheckError();
static if( !is( returnT == void ) )
return result;
}
What we're doing here is creating a function that has the exact same
signature as the function we want to call. When we call this wrapper
function, it calls the underlying function, checks for errors (throwing an
exception as necessary: that's the job of glCheckError), and returning the result.
Those static ifs are
there because you can't declare a variable of type void in D, which kinda sucks. You'd
use the above function like this:
glCheck!(glClear)(GL_COLOR_BUFFER_BIT);
For those keeping count, that's one character longer than the "pass-through" style. The nice thing is that this works uniformly with any function, no matter its return type.
However, we can still improve this. For instance, since we have an alias
to the function being called, we can improve the call to glCheckError to this:
glCheckError((&Fn).stringof)
This allows us to report the exact GL call that failed (normally, all we would get is an exception telling us which error code we got). Even cooler, however, is we can use this information to actually log our GL calls as they happen:
version( gl_LogCalls )
{
log.writef("%s",(&tFn).stringof[2..$]);
log.writef("(");
static if( args.length > 0 )
{
log.writef("%s", args[0]);
foreach( arg ; args[1..$] )
log.writef(", %s", arg);
}
log.writeLine(")");
version( gl_LogCalls_Flush )
log.flush();
}
If we place that in our glCheck function just before
we call the function itself, it gives us the ability to trace through our GL
code without having to hunt through functions. This can be really useful when
you've got some weird behaviour, and can't figure out what's causing it.
One last improvement: in OpenGL, there are times where calling glError can itself cause an error. The most obvious of
these are between glBegin and glEnd calls. You can
solve this by either building some logic in to glCheck to account
for glBegin/glEnd blocks, or you can do what I did
and split the function into two: glSafe which does the error
checking and glRaw which doesn't.
Before I go, one small note: if you are using DerelictGL, you need to
replace the Fn in ReturnType!(Fn) and ParameterTypeTuple!(Fn) with typeof(Fn) because of a weird bug with
specialising templates on aliases to function pointers, and replace the line
that logs the name of the function with:
log.writef("%s",tFn.stringof);