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.