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?
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With