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
--------
}
/*!
+ 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)
This semi-internal function is called by create(). It creates a
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.
*/
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
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
/*!
- \fn const QGLContext *QGLWidget::context() const
+ \fn QGLContext *QGLWidget::context() const
Returns the context of this widget.
return d->glcx->format();
}
-const QGLContext *QGLWidget::context() const
+QGLContext *QGLWidget::context() const
{
Q_D(const QGLWidget);
return d->glcx;
QGLFormat requestedFormat() const;
void setFormat(const QGLFormat& format);
+ void moveToThread(QThread *thread);
+
virtual void makeCurrent();
virtual void doneCurrent();
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);
Q_OBJECT
public:
QGLTextureDestroyer() : QObject() {
- qRegisterMetaType<GLuint>();
- 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);
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;
}
#include <private/qgl_p.h>
#include <private/qglpixelbuffer_p.h>
#include <private/qglframebufferobject_p.h>
+#include <qopenglfunctions.h>
QT_BEGIN_NAMESPACE
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
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;
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;
#include "gl2paintengineex/qpaintengineex_opengl2_p.h"
+#include <qglframebufferobject.h>
#include <qglpixelbuffer.h>
#include <private/qglpixelbuffer_p.h>
#include <private/qfont_p.h>
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;
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;
}
/*!
+ Returns the context of this pixelbuffer.
+*/
+QGLContext *QGLPixelBuffer::context() const
+{
+ Q_D(const QGLPixelBuffer);
+ return d->qctx;
+}
+
+/*!
\fn GLuint QGLPixelBuffer::generateDynamicTexture() const
Generates and binds a 2D GL texture that is the same size as the
return QImage();
const_cast<QGLPixelBuffer *>(this)->makeCurrent();
+ if (d->fbo)
+ d->fbo->bind();
return qt_gl_read_framebuffer(d->req_size, d->format.alpha(), true);
}
bool QGLPixelBuffer::hasOpenGLPbuffers()
{
- return QOpenGLFramebufferObject::hasOpenGLFramebufferObjects();
+ return QGLFramebufferObject::hasOpenGLFramebufferObjects();
}
QT_END_NAMESPACE
bool makeCurrent();
bool doneCurrent();
+ QGLContext *context() const;
+
GLuint generateDynamicTexture() const;
bool bindToDynamicTexture(GLuint texture);
void releaseFromDynamicTexture();
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;
};
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
LIBS += $$QMAKE_LIBS_X11
}
-CONFIG+=insignificant_test # QTBUG-22560
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
+
+win32:CONFIG+=insignificant_test # QTBUG-28264
#include <QtTest/QtTest>
#include <QtCore/QtCore>
#include <QtGui/QtGui>
+#include <private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
#include <QtWidgets/QApplication>
#include <QtOpenGL/QtOpenGL>
#include "tst_qglthreads.h"
Q_OBJECT
public:
SwapThread(QGLWidget *widget)
- : m_widget(widget)
+ : m_context(widget->context())
+ , m_swapTriggered(false)
{
moveToThread(this);
}
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
void paintEvent(QPaintEvent *)
{
m_thread->lock();
+ m_thread->waitForSwapDone();
+
makeCurrent();
QPainter p(this);
p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256));
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();
void tst_QGLThreads::swapInThread()
{
+ if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
+ QSKIP("No platformsupport for ThreadedOpenGL");
QGLFormat format;
format.setSwapInterval(1);
ForegroundWidget widget(format);
{
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()
p.end();
m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption);
+ m_semaphore->acquire(1);
+
createdAndUploaded(image);
}
}
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; i<m_images.size(); ++i) {
m_images << image;
m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2);
+ m_semaphore->release(1);
+
if (m_images.size() > 100) {
m_images.takeFirst();
m_positions.takeFirst();
private:
QList <QImage> m_images;
QList <QPoint> 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)));
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;
void tst_QGLThreads::renderInThread()
{
+ if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
+ QSKIP("No platformsupport for ThreadedOpenGL");
QFETCH(bool, resize);
QFETCH(bool, update);
QVERIFY(QTest::qWaitForWindowExposed(&widget));
widget.doneCurrent();
+ widget.context()->moveToThread(&thread);
+
thread.start();
int value = 10;
virtual ~Device() {}
virtual QPaintDevice *realPaintDevice() = 0;
virtual void prepareDevice() {}
+ virtual void moveToThread(QThread *) {}
};
class GLWidgetWrapper : public Device
widget.doneCurrent();
}
QPaintDevice *realPaintDevice() { return &widget; }
+ void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); }
ThreadSafeGLWidget widget;
};
PixelBufferWrapper() { pbuffer = new QGLPixelBuffer(512, 512); }
~PixelBufferWrapper() { delete pbuffer; }
QPaintDevice *realPaintDevice() { return pbuffer; }
+ void moveToThread(QThread *thread) { pbuffer->context()->moveToThread(thread); }
QGLPixelBuffer *pbuffer;
};
~FrameBufferObjectWrapper() { delete fbo; }
QPaintDevice *realPaintDevice() { return fbo; }
void prepareDevice() { widget.makeCurrent(); }
+ void moveToThread(QThread *thread) { widget.context()->moveToThread(thread); }
ThreadSafeGLWidget widget;
QGLFramebufferObject *fbo;
QThread::msleep(20);
}
+ device->moveToThread(qApp->thread());
+
fail = beginFailed;
QThread::currentThread()->quit();
}
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));
}
}
*/
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.");
*/
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
*/
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.");
*/
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.");