在一个单独的线程中管理OpenGL上下文

QT Managing OpenGL context in a separate thread

本文关键字:线程 管理 OpenGL 上下文 单独 一个      更新时间:2023-10-16

我已经了解了如何在这里,这里和这里为Qt QGLWidget设置单独的渲染线程。我还设法获得了一种"工作"设置:清除视口中的颜色。看起来还行。但是我得到了以下警告:

QOpenGLContext::swapBuffers()调用非暴露窗口,行为是未定义的

我首先创建了一个继承自QGLWidget的小部件。我还设置了OpenGL格式:

在Widget构造函数中:

  QGLFormat format;
  format.setProfile(QGLFormat::CompatibilityProfile);
  format.setVersion(4,3);
  format.setDoubleBuffer(true);
  format.setSwapInterval(1);
  setFormat(format);
  setAutoBufferSwap(false);

然后在同一个Widget中初始化呈现线程:

void GLThreadedWidget::initRenderThread(void){
   doneCurrent();
    context()->moveToThread(&m_renderThread);
    m_renderThread.start();
}

,从那个点开始,整个渲染在线程内完成:

RenderThread构造函数:

RenderThread::RenderThread(GLThreadedWidget *parent)
    :QThread(),glWidget(parent)
{
    doRendering = true;
}

RenderThread run()方法

    void RenderThread::run(){
         glWidget->makeCurrent();
         GLenum err = glewInit();
         if (GLEW_OK != err) {
             printf("GLEW error: %sn", glewGetErrorString(err));
         } else {
             printf("Glew loaded; using version %sn", glewGetString(GLEW_VERSION));
         }
          glInit();
         while (doRendering){
             glWidget->makeCurrent();
             glClear(GL_COLOR_BUFFER_BIT );
             paintGL(); // render actual frame

            glWidget->swapBuffers();
            glWidget->doneCurrent();
            msleep(16);
        }
    }

谁能指出问题在哪里?如果这个信息可以被丢弃呢?此外,在Qt中对渲染线程设置进行简单明了的解释将非常有帮助。使用Qt 5.2(桌面OpenGL构建)

根据您所展示的内容,看起来您得到的消息处理程序警告是因为您在窗口设置序列中"过早"开始触发缓冲区交换,直接通过QGLContext::/QOpenGLContext::swapBuffers()或间接通过许多可能的方法,这些方法在手动调试之外都无法真正检测到。我说的太早是指在小部件的父窗口被标记为"暴露"之前(在它被窗口系统显示之前)。

至于消息是否可以丢弃,它可以…但是这样做是不安全的,因为有可能在你这样做的前几帧左右得到未定义的行为,而窗口还没有准备好(特别是如果你在启动时立即调整大小到不同的程度,而不是你的.ui文件指定的)。Qt文档说,在你的窗口暴露之前,Qt基本上必须告诉OpenGL根据什么是有效的不可信范围来绘制。我不确定这是所有可能发生的,尽管我个人。

对于您所展示的代码,有一个简单的修复—避免启动您的呈现逻辑,直到您的窗口说它是暴露的。不过,使用QGLWidget检测暴露并不明显。这里有一个类似于我使用的例子,假设你的QGLWidget的子类类似于'OGLRocksWidget',它是一个中心小部件的子类,而这个中心小部件是你的QMainWindow实现的子类(这样你的小部件就必须调用parentWidget()->parentWidget()来获取它的QMainWindow):

OGLRocksWidget::paintGL()
{
    QMainWindow *window_ptr = 
        dynamic_cast<QMainWindow *>(parentWidget() ? parentWidget()->parentWidget() : 0);
    QWindow *qwindow_ptr = (window_ptr ? window_ptr->windowHandle() : 0);
    if (qwindow_ptr && qwindow_ptr->isExposed())
    {
        // don't start rendering until you can get in here, just return...
        // probably even better to make sure QGLWidget::isVisible() too
    }
}

当然,在QGLWidget::paintGL()的实现中您不必这样做,但在您的特定设置中,您最好不要启动渲染线程,直到您的窗口告诉您它已暴露。

看起来你可能有比这更大的问题。与QGLWidget的意图相比,您没有将正确的GL活动挂接到代码中的正确位置。我对你所处的位置感到同情,因为关于这方面的文件有点零散。对于这一部分,QGLWidget的详细描述"这里是QGLWidget子类可能看起来的大致轮廓"是了解这个想法的好地方。您将需要覆盖其中的任何与您有相关代码的关键虚拟,并将它们移动到这些调用中。

因此,例如,您的小部件的构造函数正在进行设置工作,这可能比放入initializeGL()覆盖更安全,因为QGLWidget的目的是通知您何时可以通过该调用安全地执行该操作。我在这里所说的更安全的意思是,你不会得到看似随机的调试异常(在发布版本中可能会默默地对你的运行时稳定性造成严重破坏)。

侧建议:安装Qt源代码,指向你的调试器,并观察你的代码运行,包括进入Qt.你的setFormat()调用,上次我看它,实际上删除当前底层QOpenGLContext。知道这一点可能很好,因为您很快就会想要创建一个新的,或者至少测试一下您的选项。

不稳定的风险是为什么我试图在一年后在这里给出至少某种答案的原因。我只是通过大量(太多)调试了解到这一点。我喜欢Qt团队所做的一切,但Qt将会更好,当他们完成迁移到QOpenGL*调用(或任何他们认为最终合适的地方为他们的OpenGL支持,包括永久考虑它和窗口支持在一起)。

QOpenglWidget自带上下文。如果您希望后台线程执行渲染,则必须将共享上下文传递给线程并执行一些正确的步骤。详情见:https://stackoverflow.com/a/50368372/3082081