Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Threaded OpenGL with shared QGLWidgets issue with Qt 5.1

I use two QGLWidgets. One for loading textures and one for rendering, but it is not working.

I used the following explanation from http://blog.qt.digia.com/blog/2011/06/03/threaded-opengl-in-4-8/

Texture uploading thread Uploading many (or large) textures is typically an expensive operation because of the amount of data being pushed to the GPU. Again, this is one of those operations that can unnecessarily block your main thread. In 4.8 you can solve this problem by creating a pair of shared QGLWidgets. One of the widgets is made current in a separate thread, but is never made visible on screen. The main thread informs the uploading thread which images to upload and the uploading thread simply calls bindTexture() on each of these images and then notifies the main thread when each one has finished so it can be drawn to screen.

With Qt 4.8 with MinGW it works fine, but now i use Qt 5.1 with MSVC. I am getting an error when I want make the widget in the thread current:

Cannot make QOpenGLContext current in a different thread

I understand the error but how can i fix it. When i don't set the widget current i can't load textures (freezed at the bindTexture() function). I also wondering, why it works with my old QT Version. When the error appears i can press "ignore error" and the programm loads the textures anyway.

Here is some sample code:

Loading textures:

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);
}

EDIT:

When i move the context of the myTextureWidget to the thread it works, but then i get the makeCurrent Error from the API when the GUI will build (the stack trace said at the QLineEdit::setPlaceHolderText function in QT5Widgetsd). When i move the myTextureWidget to the thread some seconds after the mainwindow has been shown, all works fine. But how can i know when qt finished all GUI building Stuff? I draw the GUI to a QGraphicsView with a QGLWidget viewport.

myTextureWidget->context()->moveToThread(myTextureLoadingThread);
like image 540
riv333 Avatar asked Oct 05 '13 20:10

riv333


2 Answers

Before you start the new Thread and call makeCurrent() you have to initiate doneCurrent() e.g.

void QGLWidget::startRendering()
{
    doneCurrent();
    context()->moveToThread(mTextureLoadingThread);
}

and then call

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

This is what i've done to work around this error. Unfortunately i don't have the perfect solution for using a thread for rendering.

// EDIT

I have uploaded an example: https://dl.dropboxusercontent.com/u/165223/thread_example.zip

like image 117
omgodie Avatar answered Nov 18 '22 14:11

omgodie


It's probably too late but I have had the same problem and found the solution, so here is what I did, hoping that it will help future coders:

Omgodie was on the right track. I think you still get the same error because the main thread is also calling paintEvent() which probably tries to make the context current. However, the same context is already current in your second thread, hence the error.

So you basically need to stop the main thread from trying to render in your widget while your second thread is active. I did that by adding a boolean attribute to my QGLWidget and set it to true before creating my second thread, and back to false when my thread was done. Then I modified the paintEvent() of my widget to only render when the boolean is set to false. Finally, I call the render function manually from the second thread. Here is some code:

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

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

Once your thread is done, don't forget to send the context back from the second thread to the main thread!

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

HTH

like image 28
Gpack Avatar answered Nov 18 '22 14:11

Gpack