summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/opengl/qgl.cpp29
-rw-r--r--src/opengl/qgl_p.h42
-rw-r--r--src/opengl/qgl_win.cpp3
-rw-r--r--tests/auto/auto.pro2
-rw-r--r--tests/auto/qgl_threads/tst_openglthreading.cpp452
-rw-r--r--tests/auto/qgl_threads/tst_openglthreading.h61
-rw-r--r--tests/auto/qgl_threads/tst_openglthreading.pro4
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