Is there a way to safely bind texture using OpenGL in a worker thread of Qt GUI application?
Asked Answered
S

1

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?

Szechwan answered 13/9, 2011 at 9:21 Comment(0)
H
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)
Haemoid answered 13/9, 2011 at 9:59 Comment(9)
Don't forget that buffers only work for pixel uploads if you bind them to the GL_UNPACK_BUFFER target.Blinders
@datenwolf: Sorry I do not quite understand the pseudocode. Are all these signal handlers called on the worker thread or main thread?Szechwan
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.Szechwan
@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.Haemoid
Thanks for the clarification. I will try to implement the method you have suggested in Qt and see how it goes.Szechwan
@ksming: I recommend reading #638751 to get the grips how to send signals between Qt threads.Haemoid
@Szechwan Since Qt 4.7 there is a class for VBOs and PBOs (the ones you need), called QGLBuffer. But it surely does no harm to first understand GL buffer objects before using the Qt wrapper.Potentiate
@Christian Ray: Oh, didn't know about them. Thanks for the heads up.Haemoid
A relevant post with (very) useful references: devgurus.amd.com/message/1285135Advisedly

© 2022 - 2024 — McMap. All rights reserved.