Added explicit threading API to QtOpenGL.

Since QtOpenGL/QGLContext is implemented in terms of
QtGui/QOpenGLContext which has stricter requirements about how it's
supposed to be used, we need to apply these requirements to QGLContext
as well.

This change adds QGLContext::moveToThread(QThread *) and documents it as
a necessity for making a context current on another thread.

Also introduces QGLPixelbuffer::context() to access the QGLContext of a
pixelbuffer, and made QGLWidget::context() return a non-const
QGLContext, since there's no good reason why it shouldn't, and it leads
to less const_cast clutter.

We could have introduced a backdoor in QOpenGLContext instead, making it
loosen its requirements, but that would have made it harder / impossible
to fully support threaded OpenGL in all the platforms.

Task-number: QTBUG-22560
Change-Id: Ibb6f65f342e7c963e80cc42ab5664c5f1cab30b0
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Gunnar Sletta <gunnar.sletta@digia.com>
This commit is contained in:
Samuel Rødal 2012-11-26 12:57:09 +01:00 committed by The Qt Project
parent 3ee48926e6
commit 6385a182f0
11 changed files with 184 additions and 42 deletions

9
dist/changes-5.0.0 vendored
View File

@ -676,6 +676,15 @@ QtOpenGL
which were deprecated in Qt 4 have been removed. which were deprecated in Qt 4 have been removed.
* Previously deprecated default value listBase parameter has been removed from * Previously deprecated default value listBase parameter has been removed from
both QGLWidget::renderText() functions. both QGLWidget::renderText() functions.
* In order to ensure support on more platforms, stricter requirements have been
introduced for doing threaded OpenGL. First, you must call makeCurrent() at
least once per swapBuffers() call, so that the platform has a chance to
synchronize resizes to the OpenGL surface. Second, before doing makeCurrent()
or swapBuffers() in a separate thread, you must call
QGLContext::moveToThread(QThread *) to explicitly let Qt know in which thread
a QGLContext is currently being used. You also need to make sure that the
context is not current in the current thread before moving it to a different
thread.
QtScript QtScript
-------- --------

View File

@ -3126,6 +3126,19 @@ void QGLContextPrivate::setCurrentContext(QGLContext *context)
Q_UNUSED(context); Q_UNUSED(context);
} }
/*!
Moves the QGLContext to the given \a thread.
Enables calling swapBuffers() and makeCurrent() on the context in
the given thread.
*/
void QGLContext::moveToThread(QThread *thread)
{
Q_D(QGLContext);
if (d->guiGlContext)
d->guiGlContext->moveToThread(thread);
}
/*! /*!
\fn bool QGLContext::chooseContext(const QGLContext* shareContext = 0) \fn bool QGLContext::chooseContext(const QGLContext* shareContext = 0)
@ -3195,16 +3208,18 @@ void QGLContextPrivate::setCurrentContext(QGLContext *context)
In some very rare cases the underlying call may fail. If this In some very rare cases the underlying call may fail. If this
occurs an error message is output to stderr. occurs an error message is output to stderr.
If you call this from a thread other than the main UI thread,
make sure you've first pushed the context to the relevant thread
from the UI thread using moveToThread().
*/ */
/*! /*!
\fn void QGLContext::swapBuffers() const \fn void QGLContext::swapBuffers() const
Swaps the screen contents with an off-screen buffer. Only works if Call this to finish a frame of OpenGL rendering, and make sure to
the context is in double buffer mode. call makeCurrent() again before you begin a new frame.
\sa QGLFormat::setDoubleBuffer()
*/ */
@ -3362,13 +3377,18 @@ void QGLContextPrivate::setCurrentContext(QGLContext *context)
1. Call doneCurrent() in the main thread when the rendering is 1. Call doneCurrent() in the main thread when the rendering is
finished. finished.
2. Notify the swapping thread that it can grab the context. 2. Call QGLContext::moveToThread(swapThread) to transfer ownership
of the context to the swapping thread.
3. Make the rendering context current in the swapping thread with 3. Notify the swapping thread that it can grab the context.
4. Make the rendering context current in the swapping thread with
makeCurrent() and then call swapBuffers(). makeCurrent() and then call swapBuffers().
4. Call doneCurrent() in the swapping thread and notify the main 5. Call doneCurrent() in the swapping thread.
thread that swapping is done.
6. Call QGLContext::moveToThread(qApp->thread()) and notify the
main thread that swapping is done.
Doing this will free up the main thread so that it can continue Doing this will free up the main thread so that it can continue
with, for example, handling UI events or network requests. Even if with, for example, handling UI events or network requests. Even if
@ -3400,7 +3420,10 @@ void QGLContextPrivate::setCurrentContext(QGLContext *context)
QGLWidgets can only be created in the main GUI thread. This means QGLWidgets can only be created in the main GUI thread. This means
a call to doneCurrent() is necessary to release the GL context a call to doneCurrent() is necessary to release the GL context
from the main thread, before the widget can be drawn into by from the main thread, before the widget can be drawn into by
another thread. Also, the main GUI thread will dispatch resize and another thread. You then need to call QGLContext::moveToThread()
to transfer ownership of the context to the thread in which you
want to make it current.
Also, the main GUI thread will dispatch resize and
paint events to a QGLWidget when the widget is resized, or parts paint events to a QGLWidget when the widget is resized, or parts
of it becomes exposed or needs redrawing. It is therefore of it becomes exposed or needs redrawing. It is therefore
necessary to handle those events because the default necessary to handle those events because the default
@ -3749,7 +3772,7 @@ void QGLWidget::setFormat(const QGLFormat &format)
/*! /*!
\fn const QGLContext *QGLWidget::context() const \fn QGLContext *QGLWidget::context() const
Returns the context of this widget. Returns the context of this widget.
@ -4483,7 +4506,7 @@ QGLFormat QGLWidget::format() const
return d->glcx->format(); return d->glcx->format();
} }
const QGLContext *QGLWidget::context() const QGLContext *QGLWidget::context() const
{ {
Q_D(const QGLWidget); Q_D(const QGLWidget);
return d->glcx; return d->glcx;

View File

@ -281,6 +281,8 @@ public:
QGLFormat requestedFormat() const; QGLFormat requestedFormat() const;
void setFormat(const QGLFormat& format); void setFormat(const QGLFormat& format);
void moveToThread(QThread *thread);
virtual void makeCurrent(); virtual void makeCurrent();
virtual void doneCurrent(); virtual void doneCurrent();
@ -413,7 +415,7 @@ public:
QGLFormat format() const; QGLFormat format() const;
void setFormat(const QGLFormat& format); void setFormat(const QGLFormat& format);
const QGLContext* context() const; QGLContext* context() const;
void setContext(QGLContext* context, const QGLContext* shareContext = 0, void setContext(QGLContext* context, const QGLContext* shareContext = 0,
bool deleteOldContext = true); bool deleteOldContext = true);

View File

@ -380,19 +380,18 @@ class Q_OPENGL_EXPORT QGLTextureDestroyer : public QObject
Q_OBJECT Q_OBJECT
public: public:
QGLTextureDestroyer() : QObject() { QGLTextureDestroyer() : QObject() {
qRegisterMetaType<GLuint>(); connect(this, SIGNAL(freeTexture(QGLContext *, QPlatformPixmap *, quint32)),
connect(this, SIGNAL(freeTexture(QGLContext *, QPlatformPixmap *, GLuint)), this, SLOT(freeTexture_slot(QGLContext *, QPlatformPixmap *, quint32)));
this, SLOT(freeTexture_slot(QGLContext *, QPlatformPixmap *, GLuint)));
} }
void emitFreeTexture(QGLContext *context, QPlatformPixmap *boundPixmap, GLuint id) { void emitFreeTexture(QGLContext *context, QPlatformPixmap *boundPixmap, GLuint id) {
emit freeTexture(context, boundPixmap, id); emit freeTexture(context, boundPixmap, id);
} }
Q_SIGNALS: Q_SIGNALS:
void freeTexture(QGLContext *context, QPlatformPixmap *boundPixmap, GLuint id); void freeTexture(QGLContext *context, QPlatformPixmap *boundPixmap, quint32 id);
private slots: private slots:
void freeTexture_slot(QGLContext *context, QPlatformPixmap *boundPixmap, GLuint id) { void freeTexture_slot(QGLContext *context, QPlatformPixmap *boundPixmap, quint32 id) {
Q_UNUSED(boundPixmap); Q_UNUSED(boundPixmap);
QGLShareContextScope scope(context); QGLShareContextScope scope(context);
glDeleteTextures(1, &id); glDeleteTextures(1, &id);

View File

@ -186,9 +186,14 @@ void QGLContext::reset()
d->initDone = false; d->initDone = false;
QGLContextGroup::removeShare(this); QGLContextGroup::removeShare(this);
if (d->guiGlContext) { if (d->guiGlContext) {
if (d->ownContext) if (QOpenGLContext::currentContext() == d->guiGlContext)
doneCurrent();
if (d->ownContext) {
if (d->guiGlContext->thread() == QThread::currentThread())
delete d->guiGlContext; delete d->guiGlContext;
else else
d->guiGlContext->deleteLater();
} else
d->guiGlContext->setQGLContextHandle(0,0); d->guiGlContext->setQGLContextHandle(0,0);
d->guiGlContext = 0; d->guiGlContext = 0;
} }

View File

@ -43,6 +43,7 @@
#include <private/qgl_p.h> #include <private/qgl_p.h>
#include <private/qglpixelbuffer_p.h> #include <private/qglpixelbuffer_p.h>
#include <private/qglframebufferobject_p.h> #include <private/qglframebufferobject_p.h>
#include <qopenglfunctions.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -90,7 +91,7 @@ void QGLPaintDevice::beginPaint()
if (m_previousFBO != m_thisFBO) { if (m_previousFBO != m_thisFBO) {
ctx->d_ptr->current_fbo = m_thisFBO; ctx->d_ptr->current_fbo = m_thisFBO;
glBindFramebuffer(GL_FRAMEBUFFER, m_thisFBO); ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_thisFBO);
} }
// Set the default fbo for the context to m_thisFBO so that // Set the default fbo for the context to m_thisFBO so that
@ -108,7 +109,7 @@ void QGLPaintDevice::ensureActiveTarget()
if (ctx->d_ptr->current_fbo != m_thisFBO) { if (ctx->d_ptr->current_fbo != m_thisFBO) {
ctx->d_ptr->current_fbo = m_thisFBO; ctx->d_ptr->current_fbo = m_thisFBO;
glBindFramebuffer(GL_FRAMEBUFFER, m_thisFBO); ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_thisFBO);
} }
ctx->d_ptr->default_fbo = m_thisFBO; ctx->d_ptr->default_fbo = m_thisFBO;
@ -120,7 +121,7 @@ void QGLPaintDevice::endPaint()
QGLContext *ctx = context(); QGLContext *ctx = context();
if (m_previousFBO != ctx->d_func()->current_fbo) { if (m_previousFBO != ctx->d_func()->current_fbo) {
ctx->d_ptr->current_fbo = m_previousFBO; ctx->d_ptr->current_fbo = m_previousFBO;
glBindFramebuffer(GL_FRAMEBUFFER, m_previousFBO); ctx->contextHandle()->functions()->glBindFramebuffer(GL_FRAMEBUFFER, m_previousFBO);
} }
ctx->d_ptr->default_fbo = 0; ctx->d_ptr->default_fbo = 0;

View File

@ -100,6 +100,7 @@
#include "gl2paintengineex/qpaintengineex_opengl2_p.h" #include "gl2paintengineex/qpaintengineex_opengl2_p.h"
#include <qglframebufferobject.h>
#include <qglpixelbuffer.h> #include <qglpixelbuffer.h>
#include <private/qglpixelbuffer_p.h> #include <private/qglpixelbuffer_p.h>
#include <private/qfont_p.h> #include <private/qfont_p.h>
@ -115,11 +116,23 @@ QGLContext* QGLPBufferGLPaintDevice::context() const
return pbuf->d_func()->qctx; return pbuf->d_func()->qctx;
} }
void QGLPBufferGLPaintDevice::endPaint() { void QGLPBufferGLPaintDevice::beginPaint()
{
pbuf->makeCurrent();
QGLPaintDevice::beginPaint();
}
void QGLPBufferGLPaintDevice::endPaint()
{
glFlush(); glFlush();
QGLPaintDevice::endPaint(); QGLPaintDevice::endPaint();
} }
void QGLPBufferGLPaintDevice::setFbo(GLuint fbo)
{
m_thisFBO = fbo;
}
void QGLPBufferGLPaintDevice::setPBuffer(QGLPixelBuffer* pb) void QGLPBufferGLPaintDevice::setPBuffer(QGLPixelBuffer* pb)
{ {
pbuf = pb; pbuf = pb;
@ -221,6 +234,7 @@ bool QGLPixelBuffer::makeCurrent()
format.setSamples(d->req_format.samples()); format.setSamples(d->req_format.samples());
d->fbo = new QOpenGLFramebufferObject(d->req_size, format); d->fbo = new QOpenGLFramebufferObject(d->req_size, format);
d->fbo->bind(); d->fbo->bind();
d->glDevice.setFbo(d->fbo->handle());
glViewport(0, 0, d->req_size.width(), d->req_size.height()); glViewport(0, 0, d->req_size.width(), d->req_size.height());
} }
return true; return true;
@ -241,6 +255,15 @@ bool QGLPixelBuffer::doneCurrent()
return true; return true;
} }
/*!
Returns the context of this pixelbuffer.
*/
QGLContext *QGLPixelBuffer::context() const
{
Q_D(const QGLPixelBuffer);
return d->qctx;
}
/*! /*!
\fn GLuint QGLPixelBuffer::generateDynamicTexture() const \fn GLuint QGLPixelBuffer::generateDynamicTexture() const
@ -366,6 +389,8 @@ QImage QGLPixelBuffer::toImage() const
return QImage(); return QImage();
const_cast<QGLPixelBuffer *>(this)->makeCurrent(); const_cast<QGLPixelBuffer *>(this)->makeCurrent();
if (d->fbo)
d->fbo->bind();
return qt_gl_read_framebuffer(d->req_size, d->format.alpha(), true); return qt_gl_read_framebuffer(d->req_size, d->format.alpha(), true);
} }
@ -615,7 +640,7 @@ GLuint QGLPixelBuffer::generateDynamicTexture() const
bool QGLPixelBuffer::hasOpenGLPbuffers() bool QGLPixelBuffer::hasOpenGLPbuffers()
{ {
return QOpenGLFramebufferObject::hasOpenGLFramebufferObjects(); return QGLFramebufferObject::hasOpenGLFramebufferObjects();
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -66,6 +66,8 @@ public:
bool makeCurrent(); bool makeCurrent();
bool doneCurrent(); bool doneCurrent();
QGLContext *context() const;
GLuint generateDynamicTexture() const; GLuint generateDynamicTexture() const;
bool bindToDynamicTexture(GLuint texture); bool bindToDynamicTexture(GLuint texture);
void releaseFromDynamicTexture(); void releaseFromDynamicTexture();

View File

@ -68,8 +68,10 @@ public:
virtual QPaintEngine* paintEngine() const {return pbuf->paintEngine();} virtual QPaintEngine* paintEngine() const {return pbuf->paintEngine();}
virtual QSize size() const {return pbuf->size();} virtual QSize size() const {return pbuf->size();}
virtual QGLContext* context() const; virtual QGLContext* context() const;
virtual void beginPaint();
virtual void endPaint(); virtual void endPaint();
void setPBuffer(QGLPixelBuffer* pb); void setPBuffer(QGLPixelBuffer* pb);
void setFbo(GLuint fbo);
private: private:
QGLPixelBuffer* pbuf; QGLPixelBuffer* pbuf;
}; };

View File

@ -1,7 +1,7 @@
CONFIG += testcase CONFIG += testcase
TARGET = tst_qglthreads TARGET = tst_qglthreads
requires(contains(QT_CONFIG,opengl)) requires(contains(QT_CONFIG,opengl))
QT += opengl widgets testlib QT += opengl widgets testlib gui-private core-private
HEADERS += tst_qglthreads.h HEADERS += tst_qglthreads.h
SOURCES += tst_qglthreads.cpp SOURCES += tst_qglthreads.cpp
@ -10,5 +10,6 @@ x11 {
LIBS += $$QMAKE_LIBS_X11 LIBS += $$QMAKE_LIBS_X11
} }
CONFIG+=insignificant_test # QTBUG-22560
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
win32:CONFIG+=insignificant_test # QTBUG-28264

View File

@ -42,6 +42,8 @@
#include <QtTest/QtTest> #include <QtTest/QtTest>
#include <QtCore/QtCore> #include <QtCore/QtCore>
#include <QtGui/QtGui> #include <QtGui/QtGui>
#include <private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
#include <QtOpenGL/QtOpenGL> #include <QtOpenGL/QtOpenGL>
#include "tst_qglthreads.h" #include "tst_qglthreads.h"
@ -74,7 +76,8 @@ class SwapThread : public QThread
Q_OBJECT Q_OBJECT
public: public:
SwapThread(QGLWidget *widget) SwapThread(QGLWidget *widget)
: m_widget(widget) : m_context(widget->context())
, m_swapTriggered(false)
{ {
moveToThread(this); moveToThread(this);
} }
@ -84,25 +87,48 @@ public:
time.start(); time.start();
while (time.elapsed() < RUNNING_TIME) { while (time.elapsed() < RUNNING_TIME) {
lock(); lock();
wait(); waitForReadyToSwap();
m_widget->makeCurrent(); m_context->makeCurrent();
m_widget->swapBuffers(); m_context->swapBuffers();
m_widget->doneCurrent(); m_context->doneCurrent();
m_context->moveToThread(qApp->thread());
signalSwapDone();
unlock(); unlock();
} }
m_swapTriggered = false;
} }
void lock() { m_mutex.lock(); } void lock() { m_mutex.lock(); }
void unlock() { m_mutex.unlock(); } void unlock() { m_mutex.unlock(); }
void wait() { m_wait_condition.wait(&m_mutex); } void waitForSwapDone() { if (m_swapTriggered) m_swapDone.wait(&m_mutex); }
void notify() { m_wait_condition.wakeAll(); } void waitForReadyToSwap() { if (!m_swapTriggered) m_readyToSwap.wait(&m_mutex); }
void signalReadyToSwap()
{
if (!isRunning())
return;
m_readyToSwap.wakeAll();
m_swapTriggered = true;
}
void signalSwapDone()
{
m_swapTriggered = false;
m_swapDone.wakeAll();
}
private: private:
QGLWidget *m_widget; QGLContext *m_context;
QMutex m_mutex; QMutex m_mutex;
QWaitCondition m_wait_condition; QWaitCondition m_readyToSwap;
QWaitCondition m_swapDone;
bool m_swapTriggered;
}; };
class ForegroundWidget : public QGLWidget class ForegroundWidget : public QGLWidget
@ -117,6 +143,8 @@ public:
void paintEvent(QPaintEvent *) void paintEvent(QPaintEvent *)
{ {
m_thread->lock(); m_thread->lock();
m_thread->waitForSwapDone();
makeCurrent(); makeCurrent();
QPainter p(this); QPainter p(this);
p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256)); p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256));
@ -125,7 +153,12 @@ public:
p.drawText(rect(), Qt::AlignCenter, "This is an autotest"); p.drawText(rect(), Qt::AlignCenter, "This is an autotest");
p.end(); p.end();
doneCurrent(); doneCurrent();
m_thread->notify();
if (m_thread->isRunning()) {
context()->moveToThread(m_thread);
m_thread->signalReadyToSwap();
}
m_thread->unlock(); m_thread->unlock();
update(); update();
@ -140,6 +173,8 @@ public:
void tst_QGLThreads::swapInThread() void tst_QGLThreads::swapInThread()
{ {
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
QSKIP("No platformsupport for ThreadedOpenGL");
QGLFormat format; QGLFormat format;
format.setSwapInterval(1); format.setSwapInterval(1);
ForegroundWidget widget(format); ForegroundWidget widget(format);
@ -176,10 +211,12 @@ class CreateAndUploadThread : public QThread
{ {
Q_OBJECT Q_OBJECT
public: public:
CreateAndUploadThread(QGLWidget *shareWidget) CreateAndUploadThread(QGLWidget *shareWidget, QSemaphore *semaphore)
: m_semaphore(semaphore)
{ {
m_gl = new QGLWidget(0, shareWidget); m_gl = new QGLWidget(0, shareWidget);
moveToThread(this); moveToThread(this);
m_gl->context()->moveToThread(this);
} }
~CreateAndUploadThread() ~CreateAndUploadThread()
@ -203,6 +240,8 @@ public:
p.end(); p.end();
m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption); m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption);
m_semaphore->acquire(1);
createdAndUploaded(image); createdAndUploaded(image);
} }
} }
@ -212,12 +251,18 @@ signals:
private: private:
QGLWidget *m_gl; QGLWidget *m_gl;
QSemaphore *m_semaphore;
}; };
class TextureDisplay : public QGLWidget class TextureDisplay : public QGLWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
TextureDisplay(QSemaphore *semaphore)
: m_semaphore(semaphore)
{
}
void paintEvent(QPaintEvent *) { void paintEvent(QPaintEvent *) {
QPainter p(this); QPainter p(this);
for (int i=0; i<m_images.size(); ++i) { for (int i=0; i<m_images.size(); ++i) {
@ -232,6 +277,8 @@ public slots:
m_images << image; m_images << image;
m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2); m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2);
m_semaphore->release(1);
if (m_images.size() > 100) { if (m_images.size() > 100) {
m_images.takeFirst(); m_images.takeFirst();
m_positions.takeFirst(); m_positions.takeFirst();
@ -241,12 +288,19 @@ public slots:
private: private:
QList <QImage> m_images; QList <QImage> m_images;
QList <QPoint> m_positions; QList <QPoint> m_positions;
QSemaphore *m_semaphore;
}; };
void tst_QGLThreads::textureUploadInThread() void tst_QGLThreads::textureUploadInThread()
{ {
TextureDisplay display; if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
CreateAndUploadThread thread(&display); QSKIP("No platformsupport for ThreadedOpenGL");
// prevent producer thread from queuing up too many images
QSemaphore semaphore(100);
TextureDisplay display(&semaphore);
CreateAndUploadThread thread(&display, &semaphore);
connect(&thread, SIGNAL(createdAndUploaded(QImage)), &display, SLOT(receiveImage(QImage))); connect(&thread, SIGNAL(createdAndUploaded(QImage)), &display, SLOT(receiveImage(QImage)));
@ -362,10 +416,9 @@ public:
time.start(); time.start();
failure = false; failure = false;
m_widget->makeCurrent();
while (time.elapsed() < RUNNING_TIME && !failure) { while (time.elapsed() < RUNNING_TIME && !failure) {
m_widget->makeCurrent();
m_widget->mutex.lock(); m_widget->mutex.lock();
QSize s = m_widget->newSize; QSize s = m_widget->newSize;
@ -416,6 +469,8 @@ void tst_QGLThreads::renderInThread_data()
void tst_QGLThreads::renderInThread() void tst_QGLThreads::renderInThread()
{ {
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
QSKIP("No platformsupport for ThreadedOpenGL");
QFETCH(bool, resize); QFETCH(bool, resize);
QFETCH(bool, update); QFETCH(bool, update);
@ -428,6 +483,8 @@ void tst_QGLThreads::renderInThread()
QVERIFY(QTest::qWaitForWindowExposed(&widget)); QVERIFY(QTest::qWaitForWindowExposed(&widget));
widget.doneCurrent(); widget.doneCurrent();
widget.context()->moveToThread(&thread);
thread.start(); thread.start();
int value = 10; int value = 10;
@ -451,6 +508,7 @@ public:
virtual ~Device() {} virtual ~Device() {}
virtual QPaintDevice *realPaintDevice() = 0; virtual QPaintDevice *realPaintDevice() = 0;
virtual void prepareDevice() {} virtual void prepareDevice() {}
virtual void moveToThread(QThread *) {}
}; };
class GLWidgetWrapper : public Device class GLWidgetWrapper : public Device
@ -463,6 +521,7 @@ public:
widget.doneCurrent(); widget.doneCurrent();
} }
QPaintDevice *realPaintDevice() { return &widget; } QPaintDevice *realPaintDevice() { return &widget; }
void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); }
ThreadSafeGLWidget widget; ThreadSafeGLWidget widget;
}; };
@ -483,6 +542,7 @@ public:
PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); } PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); }
~PixelBufferWrapper() { delete pbuffer; } ~PixelBufferWrapper() { delete pbuffer; }
QPaintDevice *realPaintDevice() { return pbuffer; } QPaintDevice *realPaintDevice() { return pbuffer; }
void moveToThread(QThread *thread) { pbuffer->context()->moveToThread(thread); }
QGLPixelBuffer *pbuffer; QGLPixelBuffer *pbuffer;
}; };
@ -499,6 +559,7 @@ public:
~FrameBufferObjectWrapper() { delete fbo; } ~FrameBufferObjectWrapper() { delete fbo; }
QPaintDevice *realPaintDevice() { return fbo; } QPaintDevice *realPaintDevice() { return fbo; }
void prepareDevice() { widget.makeCurrent(); } void prepareDevice() { widget.makeCurrent(); }
void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); }
ThreadSafeGLWidget widget; ThreadSafeGLWidget widget;
QGLFramebufferObject *fbo; QGLFramebufferObject *fbo;
@ -545,6 +606,8 @@ public slots:
QThread::msleep(20); QThread::msleep(20);
} }
device->moveToThread(qApp->thread());
fail = beginFailed; fail = beginFailed;
QThread::currentThread()->quit(); QThread::currentThread()->quit();
} }
@ -569,6 +632,7 @@ public:
painters.append(new ThreadPainter(devices.at(i))); painters.append(new ThreadPainter(devices.at(i)));
painters.at(i)->moveToThread(threads.at(i)); painters.at(i)->moveToThread(threads.at(i));
painters.at(i)->connect(threads.at(i), SIGNAL(started()), painters.at(i), SLOT(draw())); painters.at(i)->connect(threads.at(i), SIGNAL(started()), painters.at(i), SLOT(draw()));
devices.at(i)->moveToThread(threads.at(i));
} }
} }
@ -621,6 +685,8 @@ private:
*/ */
void tst_QGLThreads::painterOnGLWidgetInThread() void tst_QGLThreads::painterOnGLWidgetInThread()
{ {
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
QSKIP("No platformsupport for ThreadedOpenGL");
if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0."); QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");
@ -642,6 +708,9 @@ void tst_QGLThreads::painterOnGLWidgetInThread()
*/ */
void tst_QGLThreads::painterOnPixmapInThread() void tst_QGLThreads::painterOnPixmapInThread()
{ {
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)
|| !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps))
QSKIP("No platformsupport for ThreadedOpenGL or ThreadedPixmaps");
#ifdef Q_WS_X11 #ifdef Q_WS_X11
QSKIP("Drawing text in threads onto X11 drawables currently crashes on some X11 servers."); QSKIP("Drawing text in threads onto X11 drawables currently crashes on some X11 servers.");
#endif #endif
@ -660,6 +729,8 @@ void tst_QGLThreads::painterOnPixmapInThread()
*/ */
void tst_QGLThreads::painterOnPboInThread() void tst_QGLThreads::painterOnPboInThread()
{ {
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
QSKIP("No platformsupport for ThreadedOpenGL");
if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0."); QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");
@ -685,6 +756,8 @@ void tst_QGLThreads::painterOnPboInThread()
*/ */
void tst_QGLThreads::painterOnFboInThread() void tst_QGLThreads::painterOnFboInThread()
{ {
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
QSKIP("No platformsupport for ThreadedOpenGL");
if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) ||
(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) {
QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0."); QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0.");