Multithread texture loading using OpenGL and WGL

Hi.

I was unable to write anything here for some time.

BUT, finally I was able to find a way to make multithread image loading using OpenGL with Windows extensions for multiple context creation.

It was quite a struggle to find anything useful in the web, only some minor things at few forums and mailing groups. But using that with some thinking and reading between lines I finally found the solution I’d like to share, as it can save (in my opinion) much time of searching and experimenting for others.

So what do you need if you’d like to create multithread texture loading in OpenGL ?

It’s tricky. First of all you have to know, that you can’t do it straight forward, as you need OpenGL context for each thread you’d like to use OpenGL procedures.

To create new rendering context (as it’s called), you need device context first. You can get it from your HWND.

For me it was not straight forward, as main window is created using SDL. But fortunately it’s quite easy to get it from there too.

#include <wingdi.h>// for wgl* stuff
#include<SDL_syswm.h>//for SDL stuff
//...
SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
if(SDL_GetWMInfo(&wmInfo) < 0) //returns (-1) on error
{
std::cout << "error in SDL_GetWMInfo");
}
HWND hWnd = wmInfo.window;

So, now we have HWND. To get device context, you only need to call
HDC hDC = GetDC(hWnd);.
OK, that was easy.
Now we need more OpenGL contexts.
To create those, we need some Windows extension for OpenGL.

To be honest, to create contexts we need only one – wglCreateContext.
This procedure takes device context as parameter and returns new rendering context (or NULL).
No matter what people says in forums – you CAN have more than one rendering context for only ONE device context. More than that – in my opinion it’s very natural.  And it works just fine. Let’s create two of those contexts.

HGLRC rendContext1 = wglCreateContext(hDC);
HGLRC rendContext2 = wglCreateContext(hDC);

Cool. We have two OpenGL rendering contexts. What we would like to do now is:

use one context for images loading, textures creation etc. and let second context in main thread do rendering stuff etc. This way if you have multiple images to load, you can simply load them in background thread and in rendering thread start showing loaded one. The good thing is – you can load textures in background and it won’t freeze your interface, as it’s done in other thread.

But it’s not as easy as it looks like, because you have to fix sharing of textures between contexts. Normally, if you create a texture at one context, it’s not available in second one and vice versa.

Here is another procedure from Windows extension pool:

wglShareLists(rendContext2, rendContext1);

I didn’t try different order of arguments, probably it’s not meaningless (but MSDN is useless in most important things and sadly wrong in too many places :( ). The safest solution is to do this BEFORE you load anything in any context. In documentation you can find, that you can’t have nothing loaded in second of passed contexts (in our case it would be rendContext1). To be sure, call this before you start messing with OpenGL.
Now, you have to set default context for each of your threads, so it can work properly with OpenGL calls. You don’t need to call it every time, only once in a thread.

renderingThread()
{
//...
wglMakeCurrent(hDC, rendContext1);
//...
}

loadingThread()
{
//...
wglMakeCurrent(hDC, rendContext2);
//...
}

If you have this written in above manner, you can simply ‘fork’ loading thread from main thread using e.g. boost::thread class. Again, according to some OpenGL forums, it’s important to create ‘loading thread’ from rendering thread, but I didn’t check that either. It’s important, that wglShareLists will work only in the space of one process, not between processes, so only way for parallel execution is using threads.

Calling wglMakeCurrent only makes one of rendering contexts default to the thread in which is called, so it doesn’t matter if the call is done exactly in the same time in both threads. Nevertheless, it’s important to have different contexts for each thread.

After making above steps, you should be able call whole loading stuff in loadingThread thread (as e.g. loading data from disk [ilLoadImage], then creating texture name using glGenTextures(), set parameters glTexParameteri() and finally load data to OpenGL using gluBuild2DMipmaps or glTexImage2D), and in renderingThread thread you will have access to textures loaded this way.
If you’d like to also load data in renderingThread (e.g. for blocking image loading), you have to remember that some image loading libraries (like DevIL,  indirectly mentioned earlier) doesn’t have support for multithreading, so you need mutex to provide atomic calls to it’s methods.

I didn’t try to render from both contexts, but according to some resources found in the web, it’s possible but pointless, as in that case OpenGL would have to switch between contexts (calling wglShareLists shares bunch of stuff, but not the rendering context itself), which is very memory intensive and creates unnecessary overhead.

The most important thing is waiting for as at the end of the road ;-)

Don’t forget to release created contexts. In each of threads you should call default rendering context to none using wglMakeCurrent(NULL, NULL).
Deinitialization step in main thread should look similar to code below

wglMakeCurrent(NULL, NULL);
wglDeleteContext(rendContext2);
wglDeleteContext(rendContext1);

Don’t forget to call this NULLed-wglMakeCurrent in loadingThread on exit from it.

Hopefully I covered most of important things in multithread and multicontext image/texture handling using OpenGL and WGL.

Please, do not hesitate to ask a question regarding those problems.
If you find a bug or something, that looks weird to you, let me know ASAP – maybe I’m missing something (or you are ;-) )

Till next time…


19 responses to “Multithread texture loading using OpenGL and WGL

Leave a reply to ColacX Cancel reply