Create a gist now

Instantly share code, notes, and snippets.

@nilium /dispatch_glfw.md Secret
Last active Dec 13, 2015

Using GLFW with libdispatch (Grand Central Dispatch) on OS X

Using GLFW with libdispatch on OS X

One of the benefits of using GLFW is that it largely strips out a lot of the work you'd need to do on OS X to create a GL window and so on (this isn't a large amount of work, granted, but it's boilerplate crap nobody likes doing). Unfortunately, libdispatch doesn't play nice with it or really any other UI library without knowing a little about how OS X does windowing and how libdispatch expects you to do things.

When creating an application that uses libdispatch, your first instinct might be to queue up a task that would otherwise contain the entry point/initialization routine of your program and call dispatch_main() to give the process over to libdispatch. This works extremely well for command-line utilities where you don't need to know anything but you do need to work with the main queue (if you're not using the main queue, you don't need to call dispatch_main(), but this is unlikely in game dev). This doesn't work well in other scenarios because dispatch_main terminates thread zero, the thread OS X expects all UI code to run on. So, it plays hell with OS X and, as a result, also screws with GLFW.

The trick is to keep the main queue on thread zero. The downside is this requires a little platform-specific code. Specifically, you'll want to use Cocoa's NSRunLoop to kick off the application's main run loop, which will in turn receive events and process the main dispatch queue. This is as simple as calling [[NSRunLoop mainRunLoop] run]. You may also be able to use CFRunLoopRun(), but I recommend using Cocoa instead of Core Foundation where possible (it makes life easier).

So, with the NSRunLoop working, your next instinct might be to again throw your initialization task on the main queue and begin your frame loop in that task, but that would then block the main queue, which you'll need in order to poll for events and so on. Instead, you want to create a new thread for your frame loop, then using libdispatch, create tasks on the main thread to poll for events as needed. Make note of what has to be done on the main thread and push that off to tasks run on the main thread.

Once the frame loop thread has started, you can leave your init code and let the main queue handle whatever it needs to via the NSRunLoop. This includes windowing, events, and so on that your frame loop can blissfully ignore until it's needed. In other words, it prevents the UI from blocking game logic except where needed and it prevents game logic from blocking the UI. Most everything ends up being relatively lag-free.

Your frame loop can then handle input, rendering, and whatever else. Note that you do not have to do rendering on the frameloop thread. You might want to create a serial queue specifically for working with OpenGL, thereby allowing your rendering and game logic to run concurrently without blocking one another. You'll also need to create a thread-safe way to send events to the frame loop thread, but that's easy enough and makes it a lot safer to work with GLFW events (without worrying about callback restrictions). I've already done all that, so it's not hard, especially given what libdispatch lets you do to simplify synchronization.

Because I'm crap at explaining all this, however, I've also included the source code for a very bare-bones main source file using libdispatch and GLFW 3.0 on OS X. This code sample requires a C++11 compiler. I use Clang and libc++, but really the most prevalent compilers now should support enough of C++11 for this. The sample also assumes you are defining TARGET_OS_MAC and passing -ObjC++ (so you can make use of ObjC features) in your compiler flags. The example is attached below.

And for those wondering how to handle using libdispatch on other operating systems, you might want to check out xdispatch (link below, not attached), which ports it to other OSes. Granted, the other OSes won't have the same performance as OS X and the BSDs since they lack kqueue, which allows all of this to be done very, very quickly. Still, it's a handy tool and something to keep in mind.

(I originally posted this on SoCoder, but will also throw it on Google+ since I don't actually know a lot of folks who frequent SoCoder.)

Relevant Links

#include <atomic>
#include <stdexcept>
#ifdef TARGET_OS_MAC
#include <dispatch/dispatch.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/Foundation.h>
#endif
#include <gl/glfw3.h>
#include <pthread.h>
// Main queue - used for GLFW calls and event pumping
static dispatch_queue_t g_main_queue;
// Frame loop running bool (uses atomic<bool> instead of atomic_bool because
// libc++ doesn't provide an atomic_bool yet)
static std::atomic<bool> g_frameloop_running { true };
// Does what it says
static void sys_terminate(void *ctx);
// Handles frame logic and sends rendering tasks to the GL queue (or just does
// the rendering itself)
static void *sys_frameloop(void *ctx);
// Main routine - initializes stuff & kicks off the frame loop
static void sys_initialize(void *ctx);
// Tells the main loop to quit. Thread-safe.
static void sys_quit();
int main(int argc, char const *argv[])
{
// Bootstrap
#ifdef TARGET_OS_MAC
// Play nice with ARC even if ARC isn't enabled (it isn't for this unit).
@autoreleasepool {
#endif
g_main_queue = dispatch_get_main_queue();
// Queue up the actual main routine
dispatch_async_f(g_main_queue, NULL, sys_initialize);
#ifdef TARGET_OS_MAC
// On OS X, do NOT call dispatch_main, as this will terminate thread 0 (the UI
// thread), causing all manner of hell to break loose when you attempt to
// create a window. Instead, use NSRunLoop to kick off the main run-loop,
// which in turn keeps the main dispatch queue on thread 0 and allows OS X's
// UI to function as intended.
[[NSRunLoop mainRunLoop] run];
} // @autoreleasepool
#else
// Other OSes may require their own unique bootstrapping.
dispatch_main();
#endif
return 0;
}
// must be run on main queue
static void sys_initialize(void *ctx)
{
pthread_t frame_thread;
pthread_attr_t thread_attr;
GLFWwindow *window = nullptr;
if (glfwInit()) {
atexit(glfwTerminate);
} else {
throw std::runtime_error("Failed to initialize GLFW");
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
window = glfwCreateWindow(800, 600, "Snow", NULL, NULL);
if (!window) {
throw std::runtime_error("Failed to create GLFW window");
}
// Launch frameloop thread - do not just call sys_frameloop because we want to
// keep the main queue unblocked -- it has to be free so we can poll for
// events and so on later.
if (pthread_attr_init(&thread_attr)) {
throw std::runtime_error("Failed to init frame thread attributes");
}
if (pthread_create(&frame_thread, &thread_attr,
sys_frameloop, static_cast<void *>(window))) {
throw std::runtime_error("Failed to start frame loop thread");
}
pthread_attr_destroy(&thread_attr);
}
static void sys_quit()
{
bool from = g_frameloop_running;
while (!g_frameloop_running.compare_exchange_weak(from, false));
}
// Run as single thread
static void *sys_frameloop(void *ctx)
{
GLFWwindow *window = static_cast<GLFWwindow *>(ctx);
// Make this thread own the window's GL context.
glfwMakeContextCurrent(window);
// Initialize GL here
#warning TODO: Initialize OpenGL
while (g_frameloop_running) {
// Poll events from the main thread. You'll likely want to do this using
// dispatch_sync instead of dispatch_async.
dispatch_sync(g_main_queue, [] { glfwPollEvents(); });
// Update game, handle events, etc. - presumably you have a thread-safe way
// of doing this. It's not hard, I've already done it, so I can guarantee
// it's easy enough. You might want to push this off to yet another thread
// or use dispatch queues to handle it asynchronously though. Depends on
// what you're doing. At some point, call sys_quit to kill the frame loop.
#warning TODO: Update game logic
glClear(GL_COLOR_BUFFER_BIT);
#warning TODO: Render game scene
glfwSwapBuffers(window);
} // while (running)
// Go back to the main thread and kill the program cleanly.
dispatch_async_f(g_main_queue, window, sys_terminate);
return NULL;
}
// must be run on main queue
static void sys_terminate(void *ctx)
{
if (ctx) {
glfwDestroyWindow((GLFWwindow *)ctx);
}
exit(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment