Escaping glutMainLoop

John Tsiombikas nuclear@mutantstargoat.com

14 May 2011

Let's say you’re writing a distinctly glut-like window-system abstraction library for OpenGL context creation, event handling, etc. For those not familliar with the way one uses OpenGL to draw graphics, what happens is you talk to the native window system (X11, Win32 API, etc) to create a window and process events, then you create an OpenGL context and you bind it to that window using again platform-specific calls (GLX, WGL, etc).

So let's say you’re writing that code, but you decided your library will allow the user to keep control of the main loop, so you provide a funcion called something like process_events to run a single iteration of your event processing, so that the user may call it in a loop. How do you implement that on top of glut, which has a single glutMainLoop function that doesn't ever return?

By the way, for those curious on why would you do that in the first place, the reason to write a glut backend for this library, would be as a catch-all fallback to be able to run on platforms for which no native backend is yet written.

On GNU/Linux systems generally we don't have the original GLUT, but rather FreeGLUT, which is nice enough to provide a glutMainLoopEvent function which runs a single iteration of the event loop, so we just call that from process_events and we're done. But I have actually written an X11/GLX backend for my library, so I don't need GLUT there, I need it on other systems. So how to break the chains of glutMainLoop and return after each iteration of the event handling loop?

The solution is obvious, use setjmp/longjmp. In process_events we call setjmp which obviously returns 0 the first time around, in which case glutMainLoop is called. Now glut enters its infinite loop and waits for events from the window system. As soon as all pending events are processed, or if there are no events to be processed, it calls our idle callback, then when that returns it loops back to the top again, and again, and again.

Of course we set up an idle callback that doesn't actually return. It calls the user's idle callback if there's one registered, and then calls longjmp which unwinds the stack until we end up back into process_events at which point setjmp returns non-zero and we return execution to the user.

One minor issue that needs to be addressed is that since we set an idle function, if the user didn't set one with our library, we're wasting cpu cycles busy-looping because GLUT will never block waiting for events when there's an idle callback. This again is easily remedied. If the user didn't register an idle function with us, we don't actually set our idle function to GLUT a-priori, instead we wait for one of the other event callbacks to trigger, and at the end of those callbacks we set the idle callback, and remove it again when it gets called, before longjmping back to the user.

Here are a few snippets of the actual code demonstrating the above:

static int process_events(void)
{
#ifdef FREEGLUT
    glutMainLoopEvent();
#else
    if(setjmp(jbuf) == 0) {
        glutMainLoop();
    }
    /* ok ... what happens is any callback that kicks in will set the idle func
     * if it's not set,  and then the idle func will longjmp right back here...
     */
#endif
    return 0;
}

static void reshape_cb(int x, int y)
{
    sgl_reshape_callback_t func = sgl_get_callback(SGL_RESHAPE);
    func(x, y);

#ifndef FREEGLUT
    glutIdleFunc(idle_cb);
#endif
}

static void idle_cb(void)
{
    sgl_idle_callback_t func = sgl_get_callback(SGL_IDLE);
    if(func) {
        func();
#ifndef FREEGLUT
    } else {
        /* this was just the longjmp trick so restore the lack of idle func */
        glutIdleFunc(0);
#endif
    }

#ifndef FREEGLUT
    longjmp(jbuf, 0);
#endif
}

This was initially posted in my old wordpress blog. Visit the original version to see any comments.


Discuss this post

Back to my blog