有没有办法在 Qt GUI 应用程序的工作线程中使用 OpenGL 安全地绑定纹理?

发布于 2024-12-04 10:32:22 字数 518 浏览 1 评论 0原文

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

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

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

任何人都可以给我一些关于如何实现加载线程以将资源加载到OpenGL场景?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

烟沫凡尘 2024-12-11 10:32:22

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

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

今天我们有更好的东西:Buffer 对象。缓冲区对象允许您以异步方式将数据发送到 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)

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文