From 6385a182f0f93820c96cf274e50a56492491ee10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20R=C3=B8dal?= Date: Mon, 26 Nov 2012 12:57:09 +0100 Subject: 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 Reviewed-by: Gunnar Sletta --- dist/changes-5.0.0 | 9 +++ src/opengl/qgl.cpp | 45 ++++++++--- src/opengl/qgl.h | 4 +- src/opengl/qgl_p.h | 9 +-- src/opengl/qgl_qpa.cpp | 11 ++- src/opengl/qglpaintdevice.cpp | 7 +- src/opengl/qglpixelbuffer.cpp | 29 ++++++- src/opengl/qglpixelbuffer.h | 2 + src/opengl/qglpixelbuffer_p.h | 2 + tests/auto/opengl/qglthreads/qglthreads.pro | 5 +- tests/auto/opengl/qglthreads/tst_qglthreads.cpp | 103 ++++++++++++++++++++---- 11 files changed, 184 insertions(+), 42 deletions(-) diff --git a/dist/changes-5.0.0 b/dist/changes-5.0.0 index 056b7506f0..c76f216fb5 100644 --- a/dist/changes-5.0.0 +++ b/dist/changes-5.0.0 @@ -676,6 +676,15 @@ QtOpenGL which were deprecated in Qt 4 have been removed. * Previously deprecated default value listBase parameter has been removed from 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 -------- diff --git a/src/opengl/qgl.cpp b/src/opengl/qgl.cpp index c94c8f550e..583ecb69a8 100644 --- a/src/opengl/qgl.cpp +++ b/src/opengl/qgl.cpp @@ -3126,6 +3126,19 @@ void QGLContextPrivate::setCurrentContext(QGLContext *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) @@ -3195,16 +3208,18 @@ void QGLContextPrivate::setCurrentContext(QGLContext *context) In some very rare cases the underlying call may fail. If this 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 - Swaps the screen contents with an off-screen buffer. Only works if - the context is in double buffer mode. - - \sa QGLFormat::setDoubleBuffer() + Call this to finish a frame of OpenGL rendering, and make sure to + call makeCurrent() again before you begin a new frame. */ @@ -3362,13 +3377,18 @@ void QGLContextPrivate::setCurrentContext(QGLContext *context) 1. Call doneCurrent() in the main thread when the rendering is 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(). - 4. Call doneCurrent() in the swapping thread and notify the main - thread that swapping is done. + 5. Call doneCurrent() in the swapping thread. + + 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 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 a call to doneCurrent() is necessary to release the GL context 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 of it becomes exposed or needs redrawing. It is therefore 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. @@ -4483,7 +4506,7 @@ QGLFormat QGLWidget::format() const return d->glcx->format(); } -const QGLContext *QGLWidget::context() const +QGLContext *QGLWidget::context() const { Q_D(const QGLWidget); return d->glcx; diff --git a/src/opengl/qgl.h b/src/opengl/qgl.h index ab2fd8d203..1d21b42cd5 100644 --- a/src/opengl/qgl.h +++ b/src/opengl/qgl.h @@ -281,6 +281,8 @@ public: QGLFormat requestedFormat() const; void setFormat(const QGLFormat& format); + void moveToThread(QThread *thread); + virtual void makeCurrent(); virtual void doneCurrent(); @@ -413,7 +415,7 @@ public: QGLFormat format() const; void setFormat(const QGLFormat& format); - const QGLContext* context() const; + QGLContext* context() const; void setContext(QGLContext* context, const QGLContext* shareContext = 0, bool deleteOldContext = true); diff --git a/src/opengl/qgl_p.h b/src/opengl/qgl_p.h index df099ea281..904f3c1a3e 100644 --- a/src/opengl/qgl_p.h +++ b/src/opengl/qgl_p.h @@ -380,19 +380,18 @@ class Q_OPENGL_EXPORT QGLTextureDestroyer : public QObject Q_OBJECT public: QGLTextureDestroyer() : QObject() { - qRegisterMetaType(); - connect(this, SIGNAL(freeTexture(QGLContext *, QPlatformPixmap *, GLuint)), - this, SLOT(freeTexture_slot(QGLContext *, QPlatformPixmap *, GLuint))); + connect(this, SIGNAL(freeTexture(QGLContext *, QPlatformPixmap *, quint32)), + this, SLOT(freeTexture_slot(QGLContext *, QPlatformPixmap *, quint32))); } void emitFreeTexture(QGLContext *context, QPlatformPixmap *boundPixmap, GLuint id) { emit freeTexture(context, boundPixmap, id); } Q_SIGNALS: - void freeTexture(QGLContext *context, QPlatformPixmap *boundPixmap, GLuint id); + void freeTexture(QGLContext *context, QPlatformPixmap *boundPixmap, quint32 id); private slots: - void freeTexture_slot(QGLContext *context, QPlatformPixmap *boundPixmap, GLuint id) { + void freeTexture_slot(QGLContext *context, QPlatformPixmap *boundPixmap, quint32 id) { Q_UNUSED(boundPixmap); QGLShareContextScope scope(context); glDeleteTextures(1, &id); diff --git a/src/opengl/qgl_qpa.cpp b/src/opengl/qgl_qpa.cpp index f52beceaae..ba07f6121c 100644 --- a/src/opengl/qgl_qpa.cpp +++ b/src/opengl/qgl_qpa.cpp @@ -186,9 +186,14 @@ void QGLContext::reset() d->initDone = false; QGLContextGroup::removeShare(this); if (d->guiGlContext) { - if (d->ownContext) - delete d->guiGlContext; - else + if (QOpenGLContext::currentContext() == d->guiGlContext) + doneCurrent(); + if (d->ownContext) { + if (d->guiGlContext->thread() == QThread::currentThread()) + delete d->guiGlContext; + else + d->guiGlContext->deleteLater(); + } else d->guiGlContext->setQGLContextHandle(0,0); d->guiGlContext = 0; } diff --git a/src/opengl/qglpaintdevice.cpp b/src/opengl/qglpaintdevice.cpp index 5aa125f816..e870e45e15 100644 --- a/src/opengl/qglpaintdevice.cpp +++ b/src/opengl/qglpaintdevice.cpp @@ -43,6 +43,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -90,7 +91,7 @@ void QGLPaintDevice::beginPaint() if (m_previousFBO != 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 @@ -108,7 +109,7 @@ void QGLPaintDevice::ensureActiveTarget() if (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; @@ -120,7 +121,7 @@ void QGLPaintDevice::endPaint() QGLContext *ctx = context(); if (m_previousFBO != ctx->d_func()->current_fbo) { 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; diff --git a/src/opengl/qglpixelbuffer.cpp b/src/opengl/qglpixelbuffer.cpp index a5e748a1e7..dd13ef24eb 100644 --- a/src/opengl/qglpixelbuffer.cpp +++ b/src/opengl/qglpixelbuffer.cpp @@ -100,6 +100,7 @@ #include "gl2paintengineex/qpaintengineex_opengl2_p.h" +#include #include #include #include @@ -115,11 +116,23 @@ QGLContext* QGLPBufferGLPaintDevice::context() const return pbuf->d_func()->qctx; } -void QGLPBufferGLPaintDevice::endPaint() { +void QGLPBufferGLPaintDevice::beginPaint() +{ + pbuf->makeCurrent(); + QGLPaintDevice::beginPaint(); +} + +void QGLPBufferGLPaintDevice::endPaint() +{ glFlush(); QGLPaintDevice::endPaint(); } +void QGLPBufferGLPaintDevice::setFbo(GLuint fbo) +{ + m_thisFBO = fbo; +} + void QGLPBufferGLPaintDevice::setPBuffer(QGLPixelBuffer* pb) { pbuf = pb; @@ -221,6 +234,7 @@ bool QGLPixelBuffer::makeCurrent() format.setSamples(d->req_format.samples()); d->fbo = new QOpenGLFramebufferObject(d->req_size, format); d->fbo->bind(); + d->glDevice.setFbo(d->fbo->handle()); glViewport(0, 0, d->req_size.width(), d->req_size.height()); } return true; @@ -241,6 +255,15 @@ bool QGLPixelBuffer::doneCurrent() return true; } +/*! + Returns the context of this pixelbuffer. +*/ +QGLContext *QGLPixelBuffer::context() const +{ + Q_D(const QGLPixelBuffer); + return d->qctx; +} + /*! \fn GLuint QGLPixelBuffer::generateDynamicTexture() const @@ -366,6 +389,8 @@ QImage QGLPixelBuffer::toImage() const return QImage(); const_cast(this)->makeCurrent(); + if (d->fbo) + d->fbo->bind(); return qt_gl_read_framebuffer(d->req_size, d->format.alpha(), true); } @@ -615,7 +640,7 @@ GLuint QGLPixelBuffer::generateDynamicTexture() const bool QGLPixelBuffer::hasOpenGLPbuffers() { - return QOpenGLFramebufferObject::hasOpenGLFramebufferObjects(); + return QGLFramebufferObject::hasOpenGLFramebufferObjects(); } QT_END_NAMESPACE diff --git a/src/opengl/qglpixelbuffer.h b/src/opengl/qglpixelbuffer.h index 58cd470d3d..f597a48695 100644 --- a/src/opengl/qglpixelbuffer.h +++ b/src/opengl/qglpixelbuffer.h @@ -66,6 +66,8 @@ public: bool makeCurrent(); bool doneCurrent(); + QGLContext *context() const; + GLuint generateDynamicTexture() const; bool bindToDynamicTexture(GLuint texture); void releaseFromDynamicTexture(); diff --git a/src/opengl/qglpixelbuffer_p.h b/src/opengl/qglpixelbuffer_p.h index fd20cee07a..caa2e2f7d1 100644 --- a/src/opengl/qglpixelbuffer_p.h +++ b/src/opengl/qglpixelbuffer_p.h @@ -68,8 +68,10 @@ public: virtual QPaintEngine* paintEngine() const {return pbuf->paintEngine();} virtual QSize size() const {return pbuf->size();} virtual QGLContext* context() const; + virtual void beginPaint(); virtual void endPaint(); void setPBuffer(QGLPixelBuffer* pb); + void setFbo(GLuint fbo); private: QGLPixelBuffer* pbuf; }; diff --git a/tests/auto/opengl/qglthreads/qglthreads.pro b/tests/auto/opengl/qglthreads/qglthreads.pro index 754f494cfa..d5cbd0d9ed 100644 --- a/tests/auto/opengl/qglthreads/qglthreads.pro +++ b/tests/auto/opengl/qglthreads/qglthreads.pro @@ -1,7 +1,7 @@ CONFIG += testcase TARGET = tst_qglthreads requires(contains(QT_CONFIG,opengl)) -QT += opengl widgets testlib +QT += opengl widgets testlib gui-private core-private HEADERS += tst_qglthreads.h SOURCES += tst_qglthreads.cpp @@ -10,5 +10,6 @@ x11 { LIBS += $$QMAKE_LIBS_X11 } -CONFIG+=insignificant_test # QTBUG-22560 DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 + +win32:CONFIG+=insignificant_test # QTBUG-28264 diff --git a/tests/auto/opengl/qglthreads/tst_qglthreads.cpp b/tests/auto/opengl/qglthreads/tst_qglthreads.cpp index 267e3260f1..8535f177cd 100644 --- a/tests/auto/opengl/qglthreads/tst_qglthreads.cpp +++ b/tests/auto/opengl/qglthreads/tst_qglthreads.cpp @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #include #include "tst_qglthreads.h" @@ -74,7 +76,8 @@ class SwapThread : public QThread Q_OBJECT public: SwapThread(QGLWidget *widget) - : m_widget(widget) + : m_context(widget->context()) + , m_swapTriggered(false) { moveToThread(this); } @@ -84,25 +87,48 @@ public: time.start(); while (time.elapsed() < RUNNING_TIME) { lock(); - wait(); + waitForReadyToSwap(); - m_widget->makeCurrent(); - m_widget->swapBuffers(); - m_widget->doneCurrent(); + m_context->makeCurrent(); + m_context->swapBuffers(); + m_context->doneCurrent(); + + m_context->moveToThread(qApp->thread()); + + signalSwapDone(); unlock(); } + + m_swapTriggered = false; } void lock() { m_mutex.lock(); } void unlock() { m_mutex.unlock(); } - void wait() { m_wait_condition.wait(&m_mutex); } - void notify() { m_wait_condition.wakeAll(); } + void waitForSwapDone() { if (m_swapTriggered) m_swapDone.wait(&m_mutex); } + 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: - QGLWidget *m_widget; + QGLContext *m_context; QMutex m_mutex; - QWaitCondition m_wait_condition; + QWaitCondition m_readyToSwap; + QWaitCondition m_swapDone; + + bool m_swapTriggered; }; class ForegroundWidget : public QGLWidget @@ -117,6 +143,8 @@ public: void paintEvent(QPaintEvent *) { m_thread->lock(); + m_thread->waitForSwapDone(); + makeCurrent(); QPainter p(this); 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.end(); doneCurrent(); - m_thread->notify(); + + if (m_thread->isRunning()) { + context()->moveToThread(m_thread); + m_thread->signalReadyToSwap(); + } + m_thread->unlock(); update(); @@ -140,6 +173,8 @@ public: void tst_QGLThreads::swapInThread() { + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) + QSKIP("No platformsupport for ThreadedOpenGL"); QGLFormat format; format.setSwapInterval(1); ForegroundWidget widget(format); @@ -176,10 +211,12 @@ class CreateAndUploadThread : public QThread { Q_OBJECT public: - CreateAndUploadThread(QGLWidget *shareWidget) + CreateAndUploadThread(QGLWidget *shareWidget, QSemaphore *semaphore) + : m_semaphore(semaphore) { m_gl = new QGLWidget(0, shareWidget); moveToThread(this); + m_gl->context()->moveToThread(this); } ~CreateAndUploadThread() @@ -203,6 +240,8 @@ public: p.end(); m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption); + m_semaphore->acquire(1); + createdAndUploaded(image); } } @@ -212,12 +251,18 @@ signals: private: QGLWidget *m_gl; + QSemaphore *m_semaphore; }; class TextureDisplay : public QGLWidget { Q_OBJECT public: + TextureDisplay(QSemaphore *semaphore) + : m_semaphore(semaphore) + { + } + void paintEvent(QPaintEvent *) { QPainter p(this); for (int i=0; irelease(1); + if (m_images.size() > 100) { m_images.takeFirst(); m_positions.takeFirst(); @@ -241,12 +288,19 @@ public slots: private: QList m_images; QList m_positions; + + QSemaphore *m_semaphore; }; void tst_QGLThreads::textureUploadInThread() { - TextureDisplay display; - CreateAndUploadThread thread(&display); + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) + 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))); @@ -362,10 +416,9 @@ public: time.start(); failure = false; - m_widget->makeCurrent(); - while (time.elapsed() < RUNNING_TIME && !failure) { + m_widget->makeCurrent(); m_widget->mutex.lock(); QSize s = m_widget->newSize; @@ -416,6 +469,8 @@ void tst_QGLThreads::renderInThread_data() void tst_QGLThreads::renderInThread() { + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) + QSKIP("No platformsupport for ThreadedOpenGL"); QFETCH(bool, resize); QFETCH(bool, update); @@ -428,6 +483,8 @@ void tst_QGLThreads::renderInThread() QVERIFY(QTest::qWaitForWindowExposed(&widget)); widget.doneCurrent(); + widget.context()->moveToThread(&thread); + thread.start(); int value = 10; @@ -451,6 +508,7 @@ public: virtual ~Device() {} virtual QPaintDevice *realPaintDevice() = 0; virtual void prepareDevice() {} + virtual void moveToThread(QThread *) {} }; class GLWidgetWrapper : public Device @@ -463,6 +521,7 @@ public: widget.doneCurrent(); } QPaintDevice *realPaintDevice() { return &widget; } + void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); } ThreadSafeGLWidget widget; }; @@ -483,6 +542,7 @@ public: PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); } ~PixelBufferWrapper() { delete pbuffer; } QPaintDevice *realPaintDevice() { return pbuffer; } + void moveToThread(QThread *thread) { pbuffer->context()->moveToThread(thread); } QGLPixelBuffer *pbuffer; }; @@ -499,6 +559,7 @@ public: ~FrameBufferObjectWrapper() { delete fbo; } QPaintDevice *realPaintDevice() { return fbo; } void prepareDevice() { widget.makeCurrent(); } + void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); } ThreadSafeGLWidget widget; QGLFramebufferObject *fbo; @@ -545,6 +606,8 @@ public slots: QThread::msleep(20); } + device->moveToThread(qApp->thread()); + fail = beginFailed; QThread::currentThread()->quit(); } @@ -569,6 +632,7 @@ public: painters.append(new ThreadPainter(devices.at(i))); painters.at(i)->moveToThread(threads.at(i)); 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() { + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) + QSKIP("No platformsupport for ThreadedOpenGL"); if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_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() { + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL) + || !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps)) + QSKIP("No platformsupport for ThreadedOpenGL or ThreadedPixmaps"); #ifdef Q_WS_X11 QSKIP("Drawing text in threads onto X11 drawables currently crashes on some X11 servers."); #endif @@ -660,6 +729,8 @@ void tst_QGLThreads::painterOnPixmapInThread() */ void tst_QGLThreads::painterOnPboInThread() { + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) + QSKIP("No platformsupport for ThreadedOpenGL"); if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_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() { + if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) + QSKIP("No platformsupport for ThreadedOpenGL"); if (!((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) || (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0))) { QSKIP("The OpenGL based threaded QPainter tests requires OpenGL/ES 2.0."); -- cgit v1.2.3