GL thread taming

· by Steve · Read in about 3 min · (462 Words)

Well, I finished fighting with GL over creating GPU resources in the background last night (edit: note that D3D9 was already working some time ago). Contexts in GL are local to the thread, and although you can exchange them between threads you can only activate a context on one thread at a time. This of course meant I needed an additional context for the background loading thread, and so to make that as clean as possible across platforms (since Win32 and GLX have different context creating schemes) I enhanced the context abstraction classes and their use in Ogre to allow them to be simply cloned. So far so good.

However, each context keeps its hardware resources local to it by default, meaning that textures and meshes loaded in the background thread would be invisible to the main rendering thread - not too useful there. Luckily you can explicitly choose to share these resources (unfortunately all the docs misleadingly refer to this as ‘sharing display lists’, even though it actually covers much more than GL display lists), but there are very explicit timing considerations which are not really discussed anywhere. The docs do tell you that you mustn’t have created any resources on the context you want to share with before sharing, but the other consideration is that you must disable the context in the main thread until the background thread has successfully created its context and chosen to share the resources. Unfortunately all timing-related issues in this initialisation process manifest themselves as a rather vague ‘Resource is in use’ error, at least in Win32. It took a couple of evenings of experimentation to get the sequence exactly right:

  1. Create main rendering thread context
  2. Disable main rendering thread context
  3. Lock background init condition mutex
  4. Start background thread
  5. Main thread waits for init condition (this releases the init mutex and blocks the main thread)
  6. Background thread clones context, sets up resource sharing and enables its own context
  7. Background thread locks the init mutex, notifies parent, releases init mutex, then continues independently
  8. Main thread wakes up, re-enables its own context, and carries on too

Those unfamiliar with threading might not grok the importance of locking the init mutex when dealing with the sync between threads. If this didn’t happen, if the thread started and completed it’s actions too quickly, the ‘notify’ call could happen before the main thread’s ‘wait’, so the parent would wait forever when it finally called it. Luckily good libs like boost::thread force you to do this; I personally got most of my practical threading experience in Java (and most of my theory from a very dry book by Jean Bacon that I had to wade through for my degree) but all the concepts are the same across all languages and platforms.