6

我目前正在开发一个使用 Qt 可视化 3D 场景的 GUI 软件项目。GUI 允许用户将批量 3D 数据文件(例如具有某些 .mtl 支持的 .obj 和 .stl 以及 2D 图像文件作为 SceneObject 类对象加载到场景中,这些对象在 QGLWidget 派生的小部件上呈现。

然而,当我在主 GUI 线程上批量加载它们时,较长的加载时间会导致 GUI 冻结,这很难看。我曾尝试在单独的线程上执行加载,但有一个大问题:加载 .obj 纹理或图像文件时,我还将在加载每个图像或纹理后立即使用 OpenGL glBindtexture() 执行绑定,这样我只需要保存每个 SceneObject 实例中的纹理 ID。当我试图在工作线程中执行加载时,整个程序就会崩溃。

我读过每个线程只能访问一个 OGL 上下文,并且跨线程的上下文切换是实现我想要做的一种但危险的方式。另一种可能的方法是在加载完成后在 GUI 线程上执行纹理绑定,但这意味着对我的 SceneObject 类进行完全重新设计:(

谁能给我一些关于如何实现将资产加载到 OpenGL 场景中的加载线程的建议?

4

1 回答 1

5

我还将在加载每个图像或纹理后立即使用 OpenGL glBindtexture() 执行绑定,这样我只需要在每个 SceneObject 实例中保存纹理 ID。当我试图在工作线程中执行加载时,整个程序就会崩溃。

OpenGL 上下文一次只能在一个线程中处于活动状态。一般来说,多线程 OpenGL 操作通常会成为一场噩梦,要正确处理。在您的情况下,您打算做的是委派资源加载。在过去,在缓冲区对象出现之前,您可以通过创建辅助上下文并与主上下文共享其“列表”来完成此操作。

今天我们有更好的东西:缓冲对象。缓冲区对象允许您以异步方式向 OpenGL 发送数据。它像这样

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

要理解的重要部分是,glMapBuffer 分配的地址空间是跨线程共享的。因此,您可以告诉主线程中的 OpenGL 上下文映射一个缓冲区对象,向您的工作线程发送信号,并进行分配。然后工作线程填充数据并在完成后向 OpenGL 上下文线程发送信号以取消映射。

编辑多线程

所以要做到这一点,你需要在两边实现一些信号处理程序(伪代码)

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)
于 2011-09-13T09:59:33.037 回答