6

I am currently working on a GUI software project for visualizing 3D scenes using Qt. The GUI allows user to load batches of 3D data files such as .obj with some .mtl support and .stl as well as 2D image files into the scene as SceneObject-class objects which is rendered on a QGLWidget-derived widget.

When I load them in batches on the main GUI thread however, the long loading time causes the GUI to freeze, which is ugly. I have tried performing the loading on a separate thread but there is one big catch: when loading .obj textures or image files, I will also perform binding using OpenGL glBindtexture() immediately after loading each image or texture so that I only need to save texture IDs in each SceneObject instance. When I tried to perform the load in a worker thread, the whole program would just crash.

I have read that each thread can only access one OGL context and context switching across threads is one but dangerous way to achieve what I wanted to do. Another possible way would be to perform texture binding on the GUI thread after loading is completed but that would mean a complete re-design on my SceneObject class :(

Can anyone give me some advice on how to implement a loading thread for loading assets into a OpenGL scene?

1 Answer 1

6

I will also perform binding using OpenGL glBindtexture() immediately after loading each image or texture so that I only need to save texture IDs in each SceneObject instance. When I tried to perform the load in a worker thread, the whole program would just crash.

A OpenGL context may be active in only one thread at a time. In general multithreaded OpenGL operation usually becomes a major nightmare, to get right. In your case what you intend to do is delegating resource loading. In the old days, before there were buffer object you'd have done this by creating a helper context and share its "lists" with the main context.

Today we have something better: Buffer objects. A buffer object allows you to send data to OpenGL in a asynchronous way. It goes along the likes of

glGenBuffers(...);
glBindBuffer(...);
glBufferData(..., size, usage);
void *p = glMapBuffer(...);
memcpy(p, data, size);
glUnmapBuffer(...);
glTexImage / glDrawPixels / etc.

The important part to understand is, that the address space allocated by glMapBuffer is shared across threads. So you can tell the OpenGL context in the main thread to map a buffer object, sending a signal to your worker thread, with the allocation. The worker thread then fills in the data and upon finishing sends a signal to the OpenGL context thread to unmap.

EDIT for multithreading

So to do this you'd implement some signal handlers on both sides (pseudocode)

signal OpenGLThread::mapPixelBufferObjectForWrite(ImageLoader il):
    glGenBuffers(1, &self.bufferId)
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, self.bufferId)
    glBufferData(GL_PIXEL_UNPACK_BUFFER, il.unpackedImageSize, NULL, GL_STATIC_DRAW)
    BufferObjectMapping map(glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY))
    send_signal(target_thread = workerthread, target_signal_handler = workerthread::loadImageToBuffer(map, il), signal_on_finish = self.unmapPixelBufferObjectWriteFinishedGenTexture(map))

signal IOThread::loadImageToBuffer(BufferObjectMapping map, ImageLoader il):
    /* ... */

signal OpenGLThread::unmapPixelBufferObjectWriteFinishedGenTexture(BufferObjectMapping map, ImageLoader il):
    if(map.mapping_target == GL_PIXEL_UNPACK_BUFFER)
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, self.bufferId)
    glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)
    glGenTextures(1, &il.textureId)
    glBindTexture(il.target, il.textureId)
    for mipmaplevel in il.levels
        glTexImage2D(il.target, mipmaplevel, il.internalformat, il.width, il.height, il.border, il.format, il.type, mipmaplevel.offset)
9
  • Don't forget that buffers only work for pixel uploads if you bind them to the GL_UNPACK_BUFFER target. Sep 13, 2011 at 10:19
  • @datenwolf: Sorry I do not quite understand the pseudocode. Are all these signal handlers called on the worker thread or main thread?
    – ksming
    Sep 14, 2011 at 1:52
  • Does the use of Buffer Objects require me to add OpenGL extensions via GLEW for example to my Qt project? I'm trying to avoid adding external libraries and make use of Qt OpenGL module instead.
    – ksming
    Sep 14, 2011 at 6:02
  • @ksming: I addeed namespace designators to the pseudocode, to highlight it, but actually if looking closely to the signal sending you could tell from those, already. +++ Yes you'll need to use the OpenGL extension mechanism on Windows to make use of Buffer Objects, like you need to use extensions for any kind of modern OpenGL. You can compile and link GLEW statically into your program.
    – datenwolf
    Sep 14, 2011 at 7:33
  • Thanks for the clarification. I will try to implement the method you have suggested in Qt and see how it goes.
    – ksming
    Sep 14, 2011 at 7:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.