Thursday, 14 June 2007

Wrapping functions for fun and profit

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);
Wrap me up, in your love, your love takes me higher...

8 comments:

OdeFu said...

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.

Dk said...

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;

OdeFu said...

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.

OdeFu said...

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? =)

OdeFu said...

Moved the sample version here.

OdeFu said...

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. =)

Lutger said...

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.

titanların öfkesi said...

thank you