Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Correct way to synchronize QOpenGLWidget repaint from a worker thread
Forum Updated to NodeBB v4.3 + New Features

Correct way to synchronize QOpenGLWidget repaint from a worker thread

Scheduled Pinned Locked Moved Unsolved General and Desktop
12 Posts 3 Posters 948 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Christian EhrlicherC Christian Ehrlicher

    @febiodeveloper said in Correct way to synchronize QOpenGLWidget repaint from a worker thread:

    n. Most of the time, the UI just seems to freeze, and then suddenly tries to "catch up" and renders several updates,

    This does not look correct - looks like you're somehow blocking the ui. Please show some minimal code.

    febiodeveloperF Offline
    febiodeveloperF Offline
    febiodeveloper
    wrote on last edited by
    #3

    @Christian-Ehrlicher The code is complicated, and I've tried many different ways, but let me share my initial idea (which I know is flawed, but I guess I'm not entirely sure I understand why).
    At some point, the user pushes a button, which starts a new thread. At some point during the execution of this thread, some data is processed and then a signal is sent:

    // this is called from the second "worker" thread
    void processData() 
    {
      // some data is prepared here
      doSomeProcessing(); 
    
      // inform the main thread that data is ready
      emit dataReady(); 
    }
    

    The signal is caught in the main thread by the parent UI element that contains the QOpenGLWidget as a child:

    void MainUI::on_dataReady()
    {
      repaint();
    }
    

    My expectation was that this then redraws all UI elements, including the QOpenGLWidget, and then continues. But that does not happen. Instead, I get the "freeze", "catch-up", then "freeze again" for undetermined amounts of time.
    I also want to point out that the processData function can be called many times in the time it takes the render the scene in the GL widget. But despite my many attempts using mutexes, wait conditions, etc., to synchronize this, I just can't get the behavior I want, which is that when the signal is emitted from the worker thread, I want this thread to wait, until the UI, including the GL widget is completely updated. Only then can the worker thread continue. I would greatly appreciate any ideas on how to synchronize the two threads to achieve this. I hope this helps to clarify the problem. Thanks!

    SGaistS 1 Reply Last reply
    0
    • febiodeveloperF febiodeveloper

      @Christian-Ehrlicher The code is complicated, and I've tried many different ways, but let me share my initial idea (which I know is flawed, but I guess I'm not entirely sure I understand why).
      At some point, the user pushes a button, which starts a new thread. At some point during the execution of this thread, some data is processed and then a signal is sent:

      // this is called from the second "worker" thread
      void processData() 
      {
        // some data is prepared here
        doSomeProcessing(); 
      
        // inform the main thread that data is ready
        emit dataReady(); 
      }
      

      The signal is caught in the main thread by the parent UI element that contains the QOpenGLWidget as a child:

      void MainUI::on_dataReady()
      {
        repaint();
      }
      

      My expectation was that this then redraws all UI elements, including the QOpenGLWidget, and then continues. But that does not happen. Instead, I get the "freeze", "catch-up", then "freeze again" for undetermined amounts of time.
      I also want to point out that the processData function can be called many times in the time it takes the render the scene in the GL widget. But despite my many attempts using mutexes, wait conditions, etc., to synchronize this, I just can't get the behavior I want, which is that when the signal is emitted from the worker thread, I want this thread to wait, until the UI, including the GL widget is completely updated. Only then can the worker thread continue. I would greatly appreciate any ideas on how to synchronize the two threads to achieve this. I hope this helps to clarify the problem. Thanks!

      SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #4

      Hi,

      How are you sharing the data between your thread and UI ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      Christian EhrlicherC febiodeveloperF 2 Replies Last reply
      0
      • SGaistS SGaist

        Hi,

        How are you sharing the data between your thread and UI ?

        Christian EhrlicherC Offline
        Christian EhrlicherC Offline
        Christian Ehrlicher
        Lifetime Qt Champion
        wrote on last edited by
        #5

        ... and is processData() really executed in another thread?

        Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
        Visit the Qt Academy at https://academy.qt.io/catalog

        febiodeveloperF 1 Reply Last reply
        0
        • SGaistS SGaist

          Hi,

          How are you sharing the data between your thread and UI ?

          febiodeveloperF Offline
          febiodeveloperF Offline
          febiodeveloper
          wrote on last edited by
          #6

          @SGaist Both the main thread and worker thread have access to the same object. Access is controlled via mutexes. I'm not getting any deadlocks. I also tried using a QWaitCondition in the worker thread to see if that helps with synchronization, but it did not.

          Christian EhrlicherC 1 Reply Last reply
          0
          • Christian EhrlicherC Christian Ehrlicher

            ... and is processData() really executed in another thread?

            febiodeveloperF Offline
            febiodeveloperF Offline
            febiodeveloper
            wrote on last edited by
            #7

            @Christian-Ehrlicher Yes, I am positive that the processData() is called from the worker thread.

            1 Reply Last reply
            0
            • febiodeveloperF febiodeveloper

              @SGaist Both the main thread and worker thread have access to the same object. Access is controlled via mutexes. I'm not getting any deadlocks. I also tried using a QWaitCondition in the worker thread to see if that helps with synchronization, but it did not.

              Christian EhrlicherC Offline
              Christian EhrlicherC Offline
              Christian Ehrlicher
              Lifetime Qt Champion
              wrote on last edited by
              #8

              @febiodeveloper said in Correct way to synchronize QOpenGLWidget repaint from a worker thread:

              ccess is controlled via mutexes.

              So the ui is blocked when the other thread modifies it because it is waiting for the lock? Why use a thread then at all?

              Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
              Visit the Qt Academy at https://academy.qt.io/catalog

              febiodeveloperF 1 Reply Last reply
              0
              • Christian EhrlicherC Christian Ehrlicher

                @febiodeveloper said in Correct way to synchronize QOpenGLWidget repaint from a worker thread:

                ccess is controlled via mutexes.

                So the ui is blocked when the other thread modifies it because it is waiting for the lock? Why use a thread then at all?

                febiodeveloperF Offline
                febiodeveloperF Offline
                febiodeveloper
                wrote on last edited by
                #9

                @Christian-Ehrlicher I've made some progress here. First, regarding your question, I'm using a worker thread, because I want the UI to remain responsive while the worker thread does its calculation. Only when the worker thread has a result, it should inform the main thread so it can update the displayed results.
                The progress I made is the realization that the issue seems to be that calling repaint on the parent widget is not causing the OpenGLWidget to be repainted. (The odd behavior I described above was actually the result of a repaint in the mouse move event. So, when I moved my mouse, the GL widget was repainting and showing updated results, but when I stopped moving the mouse or left the GL widget, it would stop updating.) I confirmed this by calling the repaint on the GL widget directly.

                void MainUI::on_dataReady()
                {
                  glw->repaint(); // calling repaint on the QOpenGLWidget works
                }
                

                This works, but since I plan on adding other (non-GL) widgets for displaying additional results, I was hoping that if I call a repaint on the parent widget, all children would be updated as well, but that does not appear to be the case.

                So, I guess my new question is whether the described behavior is expected (i.e. calling repaint() on a parent widget, does not necessarily repaint all the children.), and if so, is there a way to force a repaint on all the child widgets?

                febiodeveloperF 1 Reply Last reply
                0
                • febiodeveloperF febiodeveloper

                  @Christian-Ehrlicher I've made some progress here. First, regarding your question, I'm using a worker thread, because I want the UI to remain responsive while the worker thread does its calculation. Only when the worker thread has a result, it should inform the main thread so it can update the displayed results.
                  The progress I made is the realization that the issue seems to be that calling repaint on the parent widget is not causing the OpenGLWidget to be repainted. (The odd behavior I described above was actually the result of a repaint in the mouse move event. So, when I moved my mouse, the GL widget was repainting and showing updated results, but when I stopped moving the mouse or left the GL widget, it would stop updating.) I confirmed this by calling the repaint on the GL widget directly.

                  void MainUI::on_dataReady()
                  {
                    glw->repaint(); // calling repaint on the QOpenGLWidget works
                  }
                  

                  This works, but since I plan on adding other (non-GL) widgets for displaying additional results, I was hoping that if I call a repaint on the parent widget, all children would be updated as well, but that does not appear to be the case.

                  So, I guess my new question is whether the described behavior is expected (i.e. calling repaint() on a parent widget, does not necessarily repaint all the children.), and if so, is there a way to force a repaint on all the child widgets?

                  febiodeveloperF Offline
                  febiodeveloperF Offline
                  febiodeveloper
                  wrote on last edited by
                  #10

                  @febiodeveloper I was able to reproduce the issue in a small test application. I can't seem to upload the code, so I have to copy/paste it here. There are two files (GLTest.h and GLTest.c)). This code starts a thread when you push the Start button. The thread then emits a signal every second. The slot that receives it (MainUI::onDataReady()) calls a repaint on itself, but that does not repaint the child QOpenGLWidget. However, calling repaint on the GL widget works as expected. But I don't understand why calling repaint() on the parent (MainUI) does not result in a repaint of the GL widget.

                  GLTest.h

                  #pragma once
                  #include <QtCore/QThread>
                  #include <QtWidgets/QBoxLayout>
                  #include <QtWidgets/QPushButton>
                  #include <QtOpenGLWidgets/QOpenGLWidget>
                  #include <stdlib.h>
                  
                  class MyThread : public QThread
                  {
                  	Q_OBJECT
                  
                  public:
                  	MyThread() {}
                  	void run() override {
                  		while (true) {
                  			sleep(1);
                  			emit dataReady();
                  		}
                  	}
                  
                  signals:
                  	void dataReady();
                  };
                  
                  class MyGLWidget : public QOpenGLWidget
                  {
                  public:
                  	MyGLWidget(QWidget* parent = nullptr) : QOpenGLWidget(parent) {}
                  
                  	void paintGL() override
                  	{
                  		glClearColor((float)rand() / RAND_MAX, (float)rand() / RAND_MAX, (float)rand() / RAND_MAX, 1.f);
                  		glClear(GL_COLOR_BUFFER_BIT);
                  	}
                  };
                  
                  class MainUI : public QWidget
                  {
                  	Q_OBJECT
                  
                  private:
                  	MyGLWidget* glw;
                  	QPushButton* pb;
                  
                  public:
                  	MainUI(QWidget* parent) : QWidget(parent) {
                  		QVBoxLayout* l = new QVBoxLayout;
                  		pb = new QPushButton("Start");
                  		l->addWidget(pb);
                  		l->addWidget(glw = new MyGLWidget);
                  		setLayout(l);
                  		connect(pb, &QPushButton::clicked, this, &MainUI::startThread);
                  	}
                  
                  	void startThread() {
                  		pb->setDisabled(true);
                  		MyThread* thread = new MyThread();
                  		connect(thread, &MyThread::dataReady, this, &MainUI::onDataReady);
                  		thread->start();
                  	}
                  
                  public slots:
                  	void onDataReady() {
                  		repaint();
                  //		glw->repaint(); // Calling repaint directly on QOpenGLWidget works
                  	}
                  };
                  

                  and the .cpp file:

                  #include <QtWidgets/QApplication>
                  #include <QtWidgets/QMainWindow>
                  #include "GLTest.h"
                  
                  int main(int nargs, char** argv)
                  {
                  	QApplication app(nargs, argv);
                  
                  	QMainWindow w;
                  	w.setCentralWidget(new MainUI(&w));
                  	w.show();
                  
                  	return app.exec();
                  }
                  
                  Christian EhrlicherC 1 Reply Last reply
                  1
                  • febiodeveloperF febiodeveloper

                    @febiodeveloper I was able to reproduce the issue in a small test application. I can't seem to upload the code, so I have to copy/paste it here. There are two files (GLTest.h and GLTest.c)). This code starts a thread when you push the Start button. The thread then emits a signal every second. The slot that receives it (MainUI::onDataReady()) calls a repaint on itself, but that does not repaint the child QOpenGLWidget. However, calling repaint on the GL widget works as expected. But I don't understand why calling repaint() on the parent (MainUI) does not result in a repaint of the GL widget.

                    GLTest.h

                    #pragma once
                    #include <QtCore/QThread>
                    #include <QtWidgets/QBoxLayout>
                    #include <QtWidgets/QPushButton>
                    #include <QtOpenGLWidgets/QOpenGLWidget>
                    #include <stdlib.h>
                    
                    class MyThread : public QThread
                    {
                    	Q_OBJECT
                    
                    public:
                    	MyThread() {}
                    	void run() override {
                    		while (true) {
                    			sleep(1);
                    			emit dataReady();
                    		}
                    	}
                    
                    signals:
                    	void dataReady();
                    };
                    
                    class MyGLWidget : public QOpenGLWidget
                    {
                    public:
                    	MyGLWidget(QWidget* parent = nullptr) : QOpenGLWidget(parent) {}
                    
                    	void paintGL() override
                    	{
                    		glClearColor((float)rand() / RAND_MAX, (float)rand() / RAND_MAX, (float)rand() / RAND_MAX, 1.f);
                    		glClear(GL_COLOR_BUFFER_BIT);
                    	}
                    };
                    
                    class MainUI : public QWidget
                    {
                    	Q_OBJECT
                    
                    private:
                    	MyGLWidget* glw;
                    	QPushButton* pb;
                    
                    public:
                    	MainUI(QWidget* parent) : QWidget(parent) {
                    		QVBoxLayout* l = new QVBoxLayout;
                    		pb = new QPushButton("Start");
                    		l->addWidget(pb);
                    		l->addWidget(glw = new MyGLWidget);
                    		setLayout(l);
                    		connect(pb, &QPushButton::clicked, this, &MainUI::startThread);
                    	}
                    
                    	void startThread() {
                    		pb->setDisabled(true);
                    		MyThread* thread = new MyThread();
                    		connect(thread, &MyThread::dataReady, this, &MainUI::onDataReady);
                    		thread->start();
                    	}
                    
                    public slots:
                    	void onDataReady() {
                    		repaint();
                    //		glw->repaint(); // Calling repaint directly on QOpenGLWidget works
                    	}
                    };
                    

                    and the .cpp file:

                    #include <QtWidgets/QApplication>
                    #include <QtWidgets/QMainWindow>
                    #include "GLTest.h"
                    
                    int main(int nargs, char** argv)
                    {
                    	QApplication app(nargs, argv);
                    
                    	QMainWindow w;
                    	w.setCentralWidget(new MainUI(&w));
                    	w.show();
                    
                    	return app.exec();
                    }
                    
                    Christian EhrlicherC Offline
                    Christian EhrlicherC Offline
                    Christian Ehrlicher
                    Lifetime Qt Champion
                    wrote on last edited by
                    #11

                    Thx for the reproducer. I would say it's by design but can't find any documentation about why an opengl widget is not updated when the parent received an update(). You may fill a bug report with your testcase to get a clear answer why this is not documented.

                    Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                    Visit the Qt Academy at https://academy.qt.io/catalog

                    febiodeveloperF 1 Reply Last reply
                    0
                    • Christian EhrlicherC Christian Ehrlicher

                      Thx for the reproducer. I would say it's by design but can't find any documentation about why an opengl widget is not updated when the parent received an update(). You may fill a bug report with your testcase to get a clear answer why this is not documented.

                      febiodeveloperF Offline
                      febiodeveloperF Offline
                      febiodeveloper
                      wrote on last edited by
                      #12

                      @Christian-Ehrlicher Thanks for looking into this. I will post a bug report.

                      1 Reply Last reply
                      0

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved