diff options
-rw-r--r-- | src/opengl/qgl.cpp | 29 | ||||
-rw-r--r-- | src/opengl/qgl_p.h | 42 | ||||
-rw-r--r-- | src/opengl/qgl_win.cpp | 3 | ||||
-rw-r--r-- | tests/auto/auto.pro | 2 | ||||
-rw-r--r-- | tests/auto/qgl_threads/tst_openglthreading.cpp | 452 | ||||
-rw-r--r-- | tests/auto/qgl_threads/tst_openglthreading.h | 61 | ||||
-rw-r--r-- | tests/auto/qgl_threads/tst_openglthreading.pro | 4 |
7 files changed, 575 insertions, 18 deletions
diff --git a/src/opengl/qgl.cpp b/src/opengl/qgl.cpp index cfcbbf8..f3c66f2 100644 --- a/src/opengl/qgl.cpp +++ b/src/opengl/qgl.cpp @@ -1708,6 +1708,7 @@ QGLTextureCache::~QGLTextureCache() void QGLTextureCache::insert(QGLContext* ctx, qint64 key, QGLTexture* texture, int cost) { + QWriteLocker locker(&m_lock); if (m_cache.totalCost() + cost > m_cache.maxCost()) { // the cache is full - make an attempt to remove something const QList<qint64> keys = m_cache.keys(); @@ -1725,6 +1726,7 @@ void QGLTextureCache::insert(QGLContext* ctx, qint64 key, QGLTexture* texture, i bool QGLTextureCache::remove(QGLContext* ctx, GLuint textureId) { + QWriteLocker locker(&m_lock); QList<qint64> keys = m_cache.keys(); for (int i = 0; i < keys.size(); ++i) { QGLTexture *tex = m_cache.object(keys.at(i)); @@ -1739,6 +1741,7 @@ bool QGLTextureCache::remove(QGLContext* ctx, GLuint textureId) void QGLTextureCache::removeContextTextures(QGLContext* ctx) { + QWriteLocker locker(&m_lock); QList<qint64> keys = m_cache.keys(); for (int i = 0; i < keys.size(); ++i) { const qint64 &key = keys.at(i); @@ -1761,9 +1764,6 @@ QGLTextureCache* QGLTextureCache::instance() */ void QGLTextureCache::imageCleanupHook(qint64 cacheKey) { - // ### remove when the GL texture cache becomes thread-safe - if (qApp->thread() != QThread::currentThread()) - return; QGLTexture *texture = instance()->getTexture(cacheKey); if (texture && texture->options & QGLContext::MemoryManagedBindOption) instance()->remove(cacheKey); @@ -1772,13 +1772,10 @@ void QGLTextureCache::imageCleanupHook(qint64 cacheKey) void QGLTextureCache::cleanupTextures(QPixmapData* pmd) { - // ### remove when the GL texture cache becomes thread-safe - if (qApp->thread() == QThread::currentThread()) { - const qint64 cacheKey = pmd->cacheKey(); - QGLTexture *texture = instance()->getTexture(cacheKey); - if (texture && texture->options & QGLContext::MemoryManagedBindOption) - instance()->remove(cacheKey); - } + const qint64 cacheKey = pmd->cacheKey(); + QGLTexture *texture = instance()->getTexture(cacheKey); + if (texture && texture->options & QGLContext::MemoryManagedBindOption) + instance()->remove(cacheKey); } void QGLTextureCache::cleanupBeforePixmapDestruction(QPixmapData* pmd) @@ -2393,7 +2390,7 @@ QGLTexture* QGLContextPrivate::bindTexture(const QImage &image, GLenum target, G #ifndef QT_NO_DEBUG GLenum error = glGetError(); if (error != GL_NO_ERROR) { - qWarning(" - texture upload failed, error code 0x%x\n", error); + qWarning(" - texture upload failed, error code 0x%x, enum: %d (%x)\n", error, target, target); } #endif @@ -3292,6 +3289,7 @@ void QGLContextPrivate::setCurrentContext(QGLContext *context) */ + /***************************************************************************** QGLWidget implementation *****************************************************************************/ @@ -3412,6 +3410,15 @@ void QGLContextPrivate::setCurrentContext(QGLContext *context) One approach to doing this is shown in the \l{Overpainting Example}{Overpainting} example. + \section1 Threading + + It is possible to render into a QGLWidget from another thread, but it + requires that all access to the GL context is safe guarded. The Qt GUI + thread will try to use the context in resizeEvent and paintEvent, so in + order for threaded rendering using a GL widget to work, these functions + need to be intercepted in the GUI thread and handled accordingly in the + application. + \e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other countries.} diff --git a/src/opengl/qgl_p.h b/src/opengl/qgl_p.h index efd2090..5c4638e 100644 --- a/src/opengl/qgl_p.h +++ b/src/opengl/qgl_p.h @@ -528,14 +528,14 @@ public: ~QGLTextureCache(); void insert(QGLContext *ctx, qint64 key, QGLTexture *texture, int cost); - void remove(quint64 key) { m_cache.remove(key); } + inline void remove(quint64 key); + inline int size(); + inline void setMaxCost(int newMax); + inline int maxCost(); + inline QGLTexture* getTexture(quint64 key); + bool remove(QGLContext *ctx, GLuint textureId); void removeContextTextures(QGLContext *ctx); - int size() { return m_cache.size(); } - void setMaxCost(int newMax) { m_cache.setMaxCost(newMax); } - int maxCost() {return m_cache.maxCost(); } - QGLTexture* getTexture(quint64 key) { return m_cache.object(key); } - static QGLTextureCache *instance(); static void deleteIfEmpty(); static void imageCleanupHook(qint64 cacheKey); @@ -544,8 +544,38 @@ public: private: QCache<qint64, QGLTexture> m_cache; + QReadWriteLock m_lock; }; +int QGLTextureCache::size() { + QReadLocker locker(&m_lock); + return m_cache.size(); +} + +void QGLTextureCache::setMaxCost(int newMax) +{ + QWriteLocker locker(&m_lock); + m_cache.setMaxCost(newMax); +} + +int QGLTextureCache::maxCost() +{ + QReadLocker locker(&m_lock); + return m_cache.maxCost(); +} + +QGLTexture* QGLTextureCache::getTexture(quint64 key) +{ + QReadLocker locker(&m_lock); + return m_cache.object(key); +} + +void QGLTextureCache::remove(quint64 key) +{ + QWriteLocker locker(&m_lock); + m_cache.remove(key); +} + extern Q_OPENGL_EXPORT QPaintEngine* qt_qgl_paint_engine(); diff --git a/src/opengl/qgl_win.cpp b/src/opengl/qgl_win.cpp index b5f952e..5ab944a 100644 --- a/src/opengl/qgl_win.cpp +++ b/src/opengl/qgl_win.cpp @@ -1280,8 +1280,9 @@ void QGLContext::reset() void QGLContext::makeCurrent() { Q_D(QGLContext); - if (d->rc == wglGetCurrentContext() || !d->valid) // already current + if (d->rc == wglGetCurrentContext() || !d->valid) // already current return; + if (d->win) { d->dc = GetDC(d->win); if (!d->dc) { diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 9c4b4f0..8bfd151 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -153,6 +153,8 @@ SUBDIRS += \ qfontmetrics \ qftp \ qgetputenv \ + qgl \ + qgl_threads \ qglobal \ qgraphicseffect \ qgraphicseffectsource \ diff --git a/tests/auto/qgl_threads/tst_openglthreading.cpp b/tests/auto/qgl_threads/tst_openglthreading.cpp new file mode 100644 index 0000000..f0c889c --- /dev/null +++ b/tests/auto/qgl_threads/tst_openglthreading.cpp @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtCore/QtCore> +#include <QtGui/QtGui> +#include <QtOpenGL/QtOpenGL> +#include "tst_openglthreading.h" + +#define RUNNING_TIME 5000 + +tst_OpenGLThreading::tst_OpenGLThreading(QObject *parent) + : QObject(parent) +{ +} + + + +/* + + swapInThread + + The purpose of this testcase is to verify that it is possible to do rendering into + a GL context from the GUI thread, then swap the contents in from a background thread. + + The usecase for this is to have the background thread do the waiting for vertical + sync while the GUI thread is idle. + + Currently the locking is handled directly in the paintEvent(). For the actual usecase + in Qt, the locking is done in the windowsurface before starting any drawing while + unlocking is done after all drawing has been done. + */ + + +class SwapThread : public QThread +{ + Q_OBJECT +public: + SwapThread(QGLWidget *widget) + : m_widget(widget) + { + moveToThread(this); + } + + void run() { + QTime time; + time.start(); + while (time.elapsed() < RUNNING_TIME) { + lock(); + wait(); + + m_widget->makeCurrent(); + m_widget->swapBuffers(); + m_widget->doneCurrent(); + unlock(); + } + } + + void lock() { m_mutex.lock(); } + void unlock() { m_mutex.unlock(); } + + void wait() { m_wait_condition.wait(&m_mutex); } + void notify() { m_wait_condition.wakeAll(); } + +private: + QGLWidget *m_widget; + QMutex m_mutex; + QWaitCondition m_wait_condition; +}; + +class ForegroundWidget : public QGLWidget +{ +public: + ForegroundWidget(const QGLFormat &format) + : QGLWidget(format), m_thread(0) + { + setAutoBufferSwap(false); + } + + void paintEvent(QPaintEvent *) + { + m_thread->lock(); + makeCurrent(); + QPainter p(this); + p.fillRect(rect(), QColor(rand() % 256, rand() % 256, rand() % 256)); + p.setPen(Qt::red); + p.setFont(QFont("SansSerif", 24)); + p.drawText(rect(), Qt::AlignCenter, "This is an autotest"); + p.end(); + doneCurrent(); + m_thread->notify(); + m_thread->unlock(); + + update(); + } + + void setThread(SwapThread *thread) { + m_thread = thread; + } + + SwapThread *m_thread; +}; + +void tst_OpenGLThreading::swapInThread() +{ + QGLFormat format; + format.setSwapInterval(1); + ForegroundWidget widget(format); + SwapThread thread(&widget); + widget.setThread(&thread); + widget.show(); + + QTest::qWaitForWindowShown(&widget); + thread.start(); + + while (thread.isRunning()) { + qApp->processEvents(); + } + + widget.hide(); + + QVERIFY(true); +} + + + + + + + +/* + textureUploadInThread + + The purpose of this testcase is to verify that doing texture uploads in a background + thread is possible and that it works. + */ + +class CreateAndUploadThread : public QThread +{ + Q_OBJECT +public: + CreateAndUploadThread(QGLWidget *shareWidget) + { + m_gl = new QGLWidget(0, shareWidget); + moveToThread(this); + } + + ~CreateAndUploadThread() + { + delete m_gl; + } + + void run() { + m_gl->makeCurrent(); + QTime time; + time.start(); + while (time.elapsed() < RUNNING_TIME) { + QImage image(400, 300, QImage::Format_RGB32); + QPainter p(&image); + p.fillRect(image.rect(), QColor(rand() % 256, rand() % 256, rand() % 256)); + p.setPen(Qt::red); + p.setFont(QFont("SansSerif", 24)); + p.drawText(image.rect(), Qt::AlignCenter, "This is an autotest"); + p.end(); + m_gl->bindTexture(image, GL_TEXTURE_2D, GL_RGBA, QGLContext::InternalBindOption); + createdAndUploaded(image); + } + } + +signals: + void createdAndUploaded(const QImage &image); + +private: + QGLWidget *m_gl; +}; + +class TextureDisplay : public QGLWidget +{ + Q_OBJECT +public: + void paintEvent(QPaintEvent *) { + QPainter p(this); + for (int i=0; i<m_images.size(); ++i) { + p.drawImage(m_positions.at(i), m_images.at(i)); + m_positions[i] += QPoint(1, 1); + } + update(); + } + +public slots: + void receiveImage(const QImage &image) { + m_images << image; + m_positions << QPoint(-rand() % width() / 2, -rand() % height() / 2); + + if (m_images.size() > 100) { + m_images.takeFirst(); + m_positions.takeFirst(); + } + } + +private: + QList <QImage> m_images; + QList <QPoint> m_positions; +}; + +void tst_OpenGLThreading::textureUploadInThread() +{ + TextureDisplay display; + CreateAndUploadThread thread(&display); + + connect(&thread, SIGNAL(createdAndUploaded(QImage)), &display, SLOT(receiveImage(QImage))); + + display.show(); + QTest::qWaitForWindowShown(&display); + + thread.start(); + + while (thread.isRunning()) { + qApp->processEvents(); + } + + QVERIFY(true); +} + + + + + + +/* + renderInThread + + This test sets up a scene and renders it in a different thread. + For simplicity, the scene is simply a bunch of rectangles, but + if that works, we're in good shape.. + */ + +static inline float random() { return (rand() % 100) / 100.f; } + +void renderAScene(int w, int h) +{ +#ifdef QT_OPENGL_ES_2 + QGLShaderProgram program; + program.addShaderFromSourceCode(QGLShader::Vertex, "attribute highp vec2 pos; void main() { gl_Position = position; }"); + program.addShaderFromSourceCode(QGLShader::Fragment, "uniform lowp vec4 color; void main() { gl_FragColor = color; }"); + program.bind(); + int colorId = program.uniformLocation("color"); + program.bindAttributeLocation("pos", 0); + + glEnableClientState(GL_VERTEX_ARRAY); + + for (int i=0; i<1000; ++i) { + float pos[] = { + (rand() % 100) / 100., + (rand() % 100) / 100., + (rand() % 100) / 100., + (rand() % 100) / 100., + (rand() % 100) / 100., + (rand() % 100) / 100. + }; + + glVertexAttributePointer(0, 2, GL_FLOAT, false, 0, pos); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); + } +#else + glViewport(0, 0, w, h); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum(0, w, h, 0, 1, 100); + glTranslated(0, 0, -1); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + for (int i=0;i<1000; ++i) { + glBegin(GL_TRIANGLES); + glColor3f(random(), random(), random()); + glVertex2f(random() * w, random() * h); + glColor3f(random(), random(), random()); + glVertex2f(random() * w, random() * h); + glColor3f(random(), random(), random()); + glVertex2f(random() * w, random() * h); + glEnd(); + } +#endif +} + +class ThreadSafeGLWidget : public QGLWidget +{ +public: + void paintEvent(QPaintEvent *) + { + // ignored as we're anyway swapping as fast as we can + }; + + void resizeEvent(QResizeEvent *e) + { + mutex.lock(); + newSize = e->size(); + mutex.unlock(); + }; + + void sendResizeEvent(QResizeEvent *e) { + QGLWidget::resizeEvent(e); + } + + QMutex mutex; + QSize newSize; +}; + +class SceneRenderingThread : public QThread +{ + Q_OBJECT +public: + SceneRenderingThread(ThreadSafeGLWidget *widget) + : m_widget(widget) + { + moveToThread(this); + m_size = widget->size(); + } + + void run() { + QTime time; + time.start(); + failure = false; + + m_widget->makeCurrent(); + + while (time.elapsed() < RUNNING_TIME && !failure) { + + m_widget->mutex.lock(); + QSize s = m_widget->newSize; + m_widget->mutex.unlock(); + if (s != m_size) { + QResizeEvent e(s, m_size); + m_widget->sendResizeEvent(&e); + m_size = s; + } + + if (QGLContext::currentContext() != m_widget->context()) { + failure = true; + break; + } + + glClear(GL_COLOR_BUFFER_BIT); + + int w = m_widget->width(); + int h = m_widget->height(); + + renderAScene(w, h); + + int color; + glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color); + + m_widget->swapBuffers(); + + + } + } + + bool failure; + +private: + ThreadSafeGLWidget *m_widget; + QSize m_size; +}; + +void tst_OpenGLThreading::renderInThread_data() +{ + QTest::addColumn<bool>("resize"); + QTest::addColumn<bool>("update"); + + QTest::newRow("basic") << false << false; + QTest::newRow("with-resize") << true << false; + QTest::newRow("with-update") << false << true; + QTest::newRow("with-resize-and-update") << true << true; +} + +void tst_OpenGLThreading::renderInThread() +{ + QFETCH(bool, resize); + QFETCH(bool, update); + + ThreadSafeGLWidget widget; + widget.resize(200, 200); + SceneRenderingThread thread(&widget); + + widget.show(); + QTest::qWaitForWindowShown(&widget); + widget.doneCurrent(); + + thread.start(); + + int value = 10; + while (thread.isRunning()) { + if (resize) + widget.resize(200 + value, 200 + value); + if (update) + widget.update(100 + value, 100 + value, 20, 20); + qApp->processEvents(); + value = -value; + } + + QVERIFY(!thread.failure); +} + + + + + +QTEST_MAIN(tst_OpenGLThreading); + +#include "tst_openglthreading.moc" diff --git a/tests/auto/qgl_threads/tst_openglthreading.h b/tests/auto/qgl_threads/tst_openglthreading.h new file mode 100644 index 0000000..c4b55cd --- /dev/null +++ b/tests/auto/qgl_threads/tst_openglthreading.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_OPENGLTHREADING_H +#define TST_OPENGLTHREADING_H + +#include <QObject> + +class tst_OpenGLThreading : public QObject +{ +Q_OBJECT +public: + explicit tst_OpenGLThreading(QObject *parent = 0); + +private slots: + void swapInThread(); + void textureUploadInThread(); + + void renderInThread_data(); + void renderInThread(); +}; + +#endif // TST_OPENGLTHREADING_H diff --git a/tests/auto/qgl_threads/tst_openglthreading.pro b/tests/auto/qgl_threads/tst_openglthreading.pro new file mode 100644 index 0000000..b60bc10 --- /dev/null +++ b/tests/auto/qgl_threads/tst_openglthreading.pro @@ -0,0 +1,4 @@ +HEADERS += tst_openglthreading.h +SOURCES += tst_openglthreading.cpp + +QT += testlib opengl |