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 if
s 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 if
s 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);
7 comments:
Hi!
Nice idea you got there. Do you know how the template affects the runtime speed of a release build, where you don't want to do any error checking?
I've been thinking about error checking with OpenGL too, and I'm leaning to the way JOGL does the DebugGL class. There's the initial writing to be done, but afterwards the application code looks the same no matter if you're using a debug version or not.
Then of course I'd have to do GLU, SDL, SDL_Image.... =)
Anyway, just something that came to mind after reading the post.
O.
Well, having played around with disassembly of this stuff, DMD seems quite capable of inlining the wrapped function call, so that doesn't really "cost" anything.
As for the error checking, I don't really know. I'd love to know how often Carmack checks glError (hmm... maybe I should go get the Q3 source). That said, it wouldn't be hard to wrap the error check in a debug {} statement so that the release version doesn't perform any checking, and have an explicit glAssert call in strategic locations.
Also, whilst a wrapper class like DebugGL is nice, it's a b*tch to write in the first place, never mind supporting GL extensions! The advantage of this method is that you can use it to wrap stuff as you need it. And if it gets too repetitive, you can always alias the wrapped functions to something shorter.
alias glSafe!(glClear) glsClear;
Yes, it's a b*tch to write. :) But them it would be done. Don't know about the extensions though.
Tested your template a little, but couldn't get a GL function working. Got one dummy function though, but only by giving the alias a different name from the original function. The typeof(Fn) doesn't do anything for me when using DerelictGL. Although it might be a Tango thing.
I'll look into this more later.
Got the template working with the help of larsivi and h3r3tic on #d.tango. :)
There's a sample version here.
I'll make a better example from the NeHe lesson 01 during the weekend, if I have time.
Only thing about your notes for Derelict is that you have to dereference the function, like typeof(*Fn), then it works. :)
Any idea how to get an opengl error? =)
Moved the sample version here.
Puuh! Could wait. =)
Made a version of lesson06 with the debug GL calls. You can see it here.
Oh, and the previous URL doesn't work anymore. =)
Hi Daniel,
A couple of months late, but this one is very nice, thanks!
Regarding the 'no void' variables deal, I usually can work around it by calling the function at the return point and using scope(exit):
ReturnTypeOf!(F) foo(alias F)(ParameterTupleOf!(F) args)
{
scope(exit) /* error checking for F here */
return F(args); /* works for void */
}
I've filed a ticket for the derelict 'alias bug' here, it's easy to solve:
http://www.dsource.org/projects/tango/ticket/729
Regards and thanks again.
Post a Comment