Qt 5.1 带有共享 QGLWidgets 的线程化 OpenGL

Posted

技术标签:

【中文标题】Qt 5.1 带有共享 QGLWidgets 的线程化 OpenGL【英文标题】:Threaded OpenGL with shared QGLWidgets issue with Qt 5.1 【发布时间】:2013-10-12 16:45:32 【问题描述】:

我使用了两个 QGLWidget。一种用于加载纹理,一种用于渲染,但它不起作用。

我使用了以下解释 http://blog.qt.digia.com/blog/2011/06/03/threaded-opengl-in-4-8/

纹理上传线程 上传许多(或大)纹理通常是一项昂贵的操作,因为要推送到 GPU 的数据量很大。同样,这是可能不必要地阻塞主线程的操作之一。在 4.8 中你可以通过创建一对共享的 QGLWidgets 来解决这个问题。其中一个小部件在单独的线程中成为当前的,但永远不会在屏幕上显示。主线程通知上传线程要上传哪些图片,上传线程只需对每张图片调用 bindTexture(),然后在每张图片完成后通知主线程,以便将其绘制到屏幕上。

使用带有 MinGW 的 Qt 4.8 可以正常工作,但现在我使用带有 MSVC 的 Qt 5.1。当我想让线程中的小部件成为当前小部件时出现错误:

不能使 QOpenGLContext 在不同的线程中成为当前的

我了解错误,但我该如何解决。当我不将小部件设置为当前时,我无法加载纹理(在 bindTexture() 函数处冻结)。我也想知道,为什么它适用于我的旧 QT 版本。当错误出现时,我可以按“忽略错误”,程序无论如何都会加载纹理。

这里是一些示例代码:

加载纹理:

GLContext::GLContext(QWidget *parent, QGLWidget *myDisplayWidget) :
  QGLWidget(parent,myDisplayWidget)


...

GLContext* myTextureWidget = new GLContext(this,myDisplayWidget);

...

void TextureLoadingThread::run()
    
    makeCurrent(); //Here is the bug!
    QImage *im = new QImage(filename);
    GLuint textid = myTextureWidget->bindTexture(*im, GL_TEXTURE_2D, GL_RGBA);

编辑:

当我将 myTextureWidget 的上下文移动到它工作的线程时,但是当 GUI 将构建时我从 API 获得 makeCurrent 错误(堆栈跟踪在 QT5Widgetsd 中的 QLineEdit::setPlaceHolderText 函数中说)。当我在显示主窗口几秒钟后将 myTextureWidget 移动到线程时,一切正常。但是我怎么知道qt什么时候完成了所有的GUI构建东西?我将 GUI 绘制到带有 QGLWidget 视口的 QGraphicsView。

myTextureWidget->context()->moveToThread(myTextureLoadingThread);

【问题讨论】:

Qt 4.8 中没有“QOpenGLContext”,所以我不确定“它工作正常”是什么意思。也许您的意思是使用 QtOpenGL 进行不同的设计和实现? 我的程序代码没有太大变化,除了移植到 QT 5.1 的一些行。我使用 QT OpenGL 版本(不是 ANGLE)和两个 QGLWidgets 来共享上下文。我想我今天会做一些调试。我昨天尝试将 myTextureWidget 的上下文移动到 TextureLoadingThread。然后我只在开始时得到 makeCurrent 错误,尽管在错误发生之前我从未在我的源代码中调用过 makeCurrent 。但是当我忽略错误时,我可以根据需要经常调用 makeCurrent 并且错误不再发生。 您找到解决方案了吗?我也有同样的问题... 【参考方案1】:

可能为时已晚,但我遇到了同样的问题并找到了解决方案,所以这就是我所做的,希望它对未来的编码员有所帮助:

Omgodie 走在了正确的轨道上。我认为你仍然会遇到同样的错误,因为主线程也在调用paintEvent(),它可能试图使上下文成为当前的。但是,您的第二个线程中已经存在相同的上下文,因此会出现错误。

因此,您基本上需要在第二个线程处于活动状态时阻止主线程尝试在您的小部件中进行渲染。我通过向我的 QGLWidget 添加一个布尔属性并在创建我的第二个线程之前将其设置为 true 并在我的线程完成时将其设置为 false 来做到这一点。然后我修改了我的小部件的 paintEvent() 以仅在布尔值设置为 false 时呈现。最后,我从第二个线程手动调用渲染函数。 这是一些代码:

//GLWidget derives from QGLWidget:
void GLWidget::paintEvent(QPaintEvent *e) 
      if ( !_second_thread_active )
           render();


//Then in your thread:
void Thread::doWork() 
      //Do stuff
      render();

一旦你的线程完成,不要忘记将上下文从第二个线程发送回主线程!

doneCurrent();
context()->moveToThread(&qapp->thread());

HTH

【讨论】:

【参考方案2】:

在启动新线程并调用 makeCurrent() 之前,您必须启动 doneCurrent() 例如

void QGLWidget::startRendering()

    doneCurrent();
    context()->moveToThread(mTextureLoadingThread);

然后调用

void TextureLoadingThread::run()
    
    makeCurrent(); //Here is the bug!
    ...

这就是我为解决此错误所做的工作。不幸的是,我没有使用线程进行渲染的完美解决方案。

// 编辑

我上传了一个例子:https://dl.dropboxusercontent.com/u/165223/thread_example.zip

【讨论】:

嗯我已经试过了,但我仍然得到同样的错误。但我同意这也很重要。我感觉在视口初始化之前我无法调用 makeCurrent。 我为你和一些工作中的人做了一个例子,我会在几个小时后发布。只需要添加 cmets :) 谢谢你的例子,但我已经理解了你的解决方案,但这并不是我程序的问题,我使用带有 OpenGL 视口的 QGraphicsview,在其中渲染标准 QT 组件。该错误也从未出现在我的 makeCurrent 调用中(当我将上下文移动到线程时),它出现在 API 中 QT 调用 makeCurrent 方法的任何位置。但我认为本教程对其他人非常有帮助,他们做标准 OPenGL 的东西,所以再次感谢 ;) 也许这是 QT 的一个错误,当我有时间时,我会扩展你的示例以确保那是一个错误。 我遇到了 smae 问题。我以一个糟糕的解决方案结束 while (!_widget->isVisible()) _widget->makeCurrent();也许使用 QWindow 可以测试 itExposed()

以上是关于Qt 5.1 带有共享 QGLWidgets 的线程化 OpenGL的主要内容,如果未能解决你的问题,请参考以下文章

带有 OpenGL 的 Qt MDI 应用程序:如何获取有效的屏幕截图?

多个 QGLWidgets 和 gluUnProject

带有自定义共享库的 Qt 项目交叉编译 Qt

Qt 5.1 for OSX 安装只包含 clang_64 目录,如何使用 macports gcc 编译?

OpenGL 纹理映射内存泄漏

在 Qt 中创建原始 GL 上下文?