Reputation: 71
I have widget:
class Main_Widget : public QWidget
{
Q_OBJECT
public:
explicit Main_Widget(QWidget *parent = 0);
~Main_Widget();
private:
MyOGLWidget *mOGL;
QThread *mThread;
QPushButton *mStart;
QPushButton *mStop;
}
Then, I created all as following:
mOGL = new MyOGLWidget();
mThread = new QThread();
mStart = new QPushButton();
mStop = new QPushButton();
//move to other thread
mOGL->moveToThread(mThread);
I want to use animation at the mOGL
. For this idea, I have a following code:
class MyOGLWindow : public QGLWidget
{
private:
bool mEnd; //default false
//...
public:
void doLoop()
{
while(mEnd)
{
//animation
updateGL();
}
}
public slots:
void slotStart()
{
mEnd = true;
}
void slotStop()
{
mEnd = false;
}
}
And I do connect my two buttons to slotStart()
, slotStop()
. But If I use start button(which causes slotStart()
), my Main_Widget
is freezes, but my animation is successfully works. How I can start my infinity loop and not to freeze my Main_Window
, and how to stop?
Upvotes: 2
Views: 1548
Reputation: 93478
The proper way to handle animation on a QGLWidget
is to use a QTimer
, not a QThread
!
Overwrite QGLWidget
's initialization method, initializeGL()
, to start the timer. You can use something like this, which calls _tick()
every 33ms:
QTimer::singleShot(33, this, SLOT(_tick()));
I just want to clarify that singleShot()
is a static method of QTimer
, which fires only once. That means that when _tick()
(private slot) is called, it should do a few things:
paintGL()
indirectly, by calling updateGL()
;Start a new singleShot timer.
void GLWidget::_tick()
{
/* 1- Update state variables */
/* 2- Call paintGL() indirectly to do the drawing */
updateGL();
/* 3- Set a new singleShot timer to invoke this method again, 33ms from now */
QTimer::singleShot(33, this, SLOT(_tick()));
}
That's how you should handle animation with QGLWidget
.
Upvotes: 1
Reputation: 98525
The UI, other than the OpenGL view, freezes since you don't return to the event loop. It is an error to move any class that derives from QWidget
, including QGLWidget
, to another thread.
To do rendering from another thread, you need to move the QGLContext
(and not QGLWidget
) to the render thread. Follow the documentation of QGLWidget
in this respect. The widget's paint event handler must do nothing - since otherwise it would use the GL context from the wrong (GUI) thread. You can use a zero-duration timer in a QObject
running in the render thread to obviate the need for a custom end
flag. Whenever the thread's event loop is finished by invoking QThread::quit()
, the object will stop executing.
You also need to use a thread class that's safe to destruct. Properly designed C++ classes are always destructible. QThread
is a weirdo - we fix it here.
As a matter of style, it is not necessary to have widget children allocated on the heap. It's a minor waste of heap, in fact, since heap blocks have overhead comparable to the size of a QObject
instance.
The below shows a sketch of things that need to be addressed for multi-threaded OpenGL in Qt.
class Thread : public QThread {
using QThread::run; // final
public:
~QThread() { quit(); wait(); }
};
class ThreadGLWidget : public QGLWidget
{
void paintEvent(QPaintEvent *) {}
void resizeEvent(QResizeEvent *) { emit resized(size()); }
public:
explicit ThreadGLWidget(QWidget * parent = 0) : QGLWidget(parent) {
// Release the context in this thread.
doneCurrent();
}
Q_SIGNAL void resized(QSize);
};
class Animator : public QObject
{
Q_OBJECT
QBasicTimer mTimer;
QSize mWidgetSize;
QPointer<QGLContext> mGLContext;
void nextFrame() {
mGLContext.makeCurrent();
...
updateGL();
}
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() == mTimer.timerId()) nextFrame();
}
public:
explicit Animator(QGLContext * ctx, QObject * parent = 0) :
QObject(parent), mGLContext(ctx)
{
// The use of the timer obviates the custom stop flag. Our
// thread's event loop is not blocked and is quittable.
mTimer.start(0, this);
}
Q_SLOT void setSize(QSize size) { mWidgetSize = size; }
};
class Main_Widget : public QWidget
{
Q_OBJECT
public:
explicit Main_Widget(QWidget *parent = 0) : QWidget(parent),
mLayout(this), mStart("Start"), mStop("Stop"),
mAnimator(mOGL.context())
{
mLayout.addWidget(&mOGL, 0, 0, 1, 2);
mLayout.addWidget(&mOGL, 1, 0);
mLayout.addWidget(&mOGL, 1, 1);
mAnimator.setSize(mOGL.size());
mOGL.context()->moveToThread(&mThread);
mAnimator.moveToThread(&mThread);
mAnimator.connect(&mOGL, SIGNAL(resized(QSize)), SLOT(setSize(QSize)));
mThread.start();
}
private:
QGridLayout mLayout;
ThreadGLWidget mOGL;
QPushButton mStart;
QPushButton mStop;
Animator mAnimator; // must be declared before its thread and after the GL widget
QThread mThread;
// Destruction order of GL-related objects:
// 1. mThread - stops the animation, makes the animator and context threadless
// 2. mAnimator - now threadless, can be destructed from our thread
// 3. mOGL - its context is threadless and can be destructed from our thread
}
Upvotes: 1
Reputation: 1779
All GUI operations must run in one thread.
http://qt-project.org/doc/qt-4.8/thread-basics.html#gui-thread-and-worker-thread
GUI Thread and Worker Thread
As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.
This is also true in Qt 5,
http://qt-project.org/doc/qt-5/thread-basics.html#gui-thread-and-worker-thread
Upvotes: -1