使用Qt 5.1时,共享QGLWidgets的线程化OpenGL问题

8
我使用了两个QGLWidgets,一个用于加载纹理,另一个用于渲染,但是它没有起作用。我使用了来自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进行了不同的设计和实现? - László Papp
我在我的程序代码中没有做太多改动,只有一些为了移植到QT 5.1的行。我使用QT OpenGL版本(而不是ANGLE),仍然有两个QGLWidgets来共享上下文。我想今天会进行一些调试。昨天我尝试将myTextureWidget的上下文移动到TextureLoadingThread中。然后我只在开始时得到makeCurrent错误,尽管在错误发生之前我从未在源代码中调用过makeCurrent。但是当我忽略错误时,我可以随意调用makeCurrent,并且不再出现错误。 - riv333
你找到解决方案了吗?我也有完全相同的问题... - user2950911
2个回答

4
在启动新线程并调用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

1
嗯,我已经尝试过了,但是我仍然得到相同的错误。但我同意这也很重要。我有一种感觉,在视口初始化之前我不能调用makeCurrent。 - riv333
我为你和一些同事制作了一个示例,几个小时后我会发布它。只需要添加一些注释 :) - omgodie
谢谢你的示例,但我已经理解了你的解决方案,但它并不完全是我的程序问题。我使用带有OpenGL视口的QGraphicsView,在其中渲染标准QT组件。错误也从未出现在我的makeCurrent调用(当我将上下文移动到线程时),它出现在QT调用makeCurrent方法的API的任何地方。但我认为这个教程对于其他做标准OpenGL的人非常有帮助,所以再次感谢;)也许这是QT的一个bug,当我有时间时,我会扩展你的示例以确保那是一个bug。 - riv333
我遇到了同样的问题。最终只得出一个糟糕的解决方案:while (!_widget->isVisible()) { } _widget->makeCurrent();或许可以使用 QWindow 来测试它是否已经暴露出来。 - Luciano Lorenti

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接