Home » Windows » Facilities for asynchronous texture loading

Facilities for asynchronous texture loading

Posted by: admin February 27, 2018 Leave a comment

Questions:

How would one go about loading textures asynchronously while a 3D application is running? What I do understand is that OpenGL contexts are not thread safe and that I should separate them on different threads.

But my primary problem is picking a proper multithreading facility/framework to actually implement this with Windows and C++, I’ve heard a lot about C++11 including threading support in its standard lbrary, but could someone lay out just the basic steps?

What would be the safest way to do it? And how does one update the state of the other context that it registers the changes done on the other thread? I suspect glFlush and glBind*?

Answers:

The most time-consuming part of texture loading is typically the disk access and any format conversion, both of which are independent of OpenGL and thus can safely take place on another thread. Once the texture is read into memory and in the desired format, the actual copy into an OpenGL buffer is fairly quick.

Going into the details of threaded programming is entirely too complex for this answer, but there’s a good bit of documentation around and once it clicks in your head, it’s pretty easy (like pointers and memory).

The general concept here is to create a list of texture holder objects (containing, say, the file/name, an initially-null buffer, and a loading-complete flag) and pass that to your loading thread when it’s created. The loading thread then goes through the list, opens each file, loads it into memory and attaches the buffer to the list entry, then sets the loaded flag and maybe increments a counter. The main thread takes the newly-loaded texture, copies it into an OpenGL texture, and increases the progress bar or whatever your loading indicator is. Once all the textures in the list have buffers and are flagged as loaded, the other thread’s work is complete and it can be stopped (or left alive to load future textures).

The main advantage to that model is due to not having to share the actual graphics context. In APIs that can be thread-safe (DirectX), there’s a performance penalty to doing so, and OpenGL requires a decent bit of work on your part to have multiple contexts or make sure you’re sharing them properly. The heavy lifting when loading textures is typically the file read and parameter checking, unless you do format conversion or rotation, which may dwarf even disk access. The actual copy to video memory is highly optimized and not likely to be a bottleneck (if you’re worried about that, try profiling in a tool capable of recognizing GPU call cost, and see). None of this work depends directly on OpenGL, so you can push it off to another thread with very little worry.

If you’re on Windows specifically, there are built-in threading functions that can be used, working off of a simple callback model (provide a function and initial params, in this case your texture list, and make the API call). I’m not personally familiar with C++11’s threading support or how it works in Visual Studio, if at all, so you’d have to check on that.

Questions:
Answers:

The answer by @ssube is correct about the complexity breakdown of the task, but assumes that “OpenGL requires a decent bit of work on your part to have multiple contexts or make sure you’re sharing them properly”, and I disagree.

An easy solution is to create a main context (for drawing) and a secondary context (for texture loading), at the start of the program, for example with:

m_hRCDrawing = wglCreateContext(m_hDC);
m_hRCSecondary = wglCreateContext(m_hDC);

And then sharing the data between the m_hRCSecondary and the m_hRCDrawing contexts can be done with:

wglShareLists(m_hRCSecondary, m_hRCDrawing);

Finally, when you are about to read a texture from a “texture loading” thread that has no GL context, you can simply call:

wglMakeCurrent(m_hDC, m_hRCSecondary); 

after which any assets loaded within this thread are shared by the drawing thread’s context.

A slightly more thorough explanation is available here.