summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGunnar Sletta <gunnar.sletta@nokia.com>2010-12-21 15:38:55 +0100
committerGunnar Sletta <gunnar.sletta@nokia.com>2010-12-21 15:38:55 +0100
commite0d01169b279a528616688efd946561512b837e3 (patch)
tree8334e184522b1a16b0ab8d38cebb1a34ea29598a
parent0783353952758777b1935a9ae068e2201b486a97 (diff)
Threaded texture manager.
-rw-r--r--src/adaptationlayers/adaptationlayers.pri6
-rw-r--r--src/adaptationlayers/qsgthreadedtexturemanager.cpp236
-rw-r--r--src/adaptationlayers/qsgthreadedtexturemanager.h31
-rw-r--r--src/scenegraph/coreapi/qsgtexturemanager.cpp3
-rw-r--r--tests/auto/texturemanager/tst_texturemanagertest.cpp3
5 files changed, 277 insertions, 2 deletions
diff --git a/src/adaptationlayers/adaptationlayers.pri b/src/adaptationlayers/adaptationlayers.pri
index 7e96cc3..a67da7c 100644
--- a/src/adaptationlayers/adaptationlayers.pri
+++ b/src/adaptationlayers/adaptationlayers.pri
@@ -7,7 +7,8 @@ HEADERS += \
$$PWD/default/default_glyphnode.h \
$$PWD/default/default_glyphnode_p.h \
# $$PWD/threadedtexturemanager.h
- adaptationlayers/qsgpartialuploadtexturemanager.h
+ adaptationlayers/qsgpartialuploadtexturemanager.h \
+ adaptationlayers/qsgthreadedtexturemanager.h
SOURCES += \
$$PWD/adaptationlayer.cpp \
@@ -16,7 +17,8 @@ SOURCES += \
$$PWD/default/default_glyphnode.cpp \
$$PWD/default/default_glyphnode_p.cpp \
# $$PWD/threadedtexturemanager.cpp
- adaptationlayers/qsgpartialuploadtexturemanager.cpp
+ adaptationlayers/qsgpartialuploadtexturemanager.cpp \
+ adaptationlayers/qsgthreadedtexturemanager.cpp
#macx:{
# SOURCES += adaptationlayers/mactexturemanager.cpp
diff --git a/src/adaptationlayers/qsgthreadedtexturemanager.cpp b/src/adaptationlayers/qsgthreadedtexturemanager.cpp
new file mode 100644
index 0000000..83e64db
--- /dev/null
+++ b/src/adaptationlayers/qsgthreadedtexturemanager.cpp
@@ -0,0 +1,236 @@
+#include "qsgthreadedtexturemanager.h"
+#include "qsgtexturemanager_p.h"
+
+#include <QThread>
+#include <QMutex>
+#include <QWaitCondition>
+
+class QSGThreadedTexture : public QSGTexture
+{
+ Q_OBJECT
+public:
+ QSGThreadedTexture(QSGThreadedTextureManager *m)
+ : manager(m)
+ {
+ }
+ ~QSGThreadedTexture();
+
+ QSGThreadedTextureManager *manager;
+ QImage image;
+};
+
+
+class QSGThreadedTextureManagerThread : public QThread
+{
+public:
+ QSGThreadedTextureManagerThread(QSGThreadedTextureManager *m)
+ : manager(m)
+ , currentlyUploading(0)
+ {
+ manager->createThreadContext();
+ start();
+ }
+
+ void run() {
+
+ manager->makeThreadContextCurrent();
+
+ while (true) {
+
+ mutex.lock();
+ currentlyUploading = 0;
+ condition.wakeOne();
+
+ if (requests.isEmpty()) {
+ condition.wait(&mutex);
+ }
+
+ if (requests.isEmpty()) {
+ mutex.unlock();
+ continue;
+ }
+
+ currentlyUploading = requests.takeFirst();
+ mutex.unlock();
+
+ manager->uploadInThread(currentlyUploading->image, currentlyUploading);
+ }
+ }
+
+ QSGThreadedTextureManager *manager;
+ QSGThreadedTexture *currentlyUploading;
+
+ QWaitCondition condition;
+ QMutex mutex;
+
+ QList<QSGThreadedTexture *> requests;
+};
+
+
+class QSGThreadedTextureManagerPrivate : public QSGTextureManagerPrivate
+{
+public:
+ QSGThreadedTextureManagerThread *thread;
+
+ QGLWidget *widget;
+ QGLContext *context;
+};
+
+
+QSGThreadedTexture::~QSGThreadedTexture()
+{
+ QSGThreadedTextureManagerPrivate *d = manager->d_func();
+ d->thread->mutex.lock();
+ d->thread->requests.removeOne(this);
+ while (d->thread->currentlyUploading == this)
+ d->thread->condition.wait(&d->thread->mutex);
+ d->thread->mutex.unlock();
+
+ d->removeTextureFromCache(this);
+}
+
+
+QSGThreadedTextureManager::QSGThreadedTextureManager()
+ : QSGTextureManager(*(new QSGThreadedTextureManagerPrivate))
+{
+ Q_D(QSGThreadedTextureManager);
+ d->thread = 0;
+ d->widget = 0;
+ d->context = 0;
+}
+
+QSGTextureRef QSGThreadedTextureManager::upload(const QImage &image)
+{
+ Q_D(QSGThreadedTextureManager);
+
+ QSGTextureCacheKey key = { image.cacheKey() };
+ QSGTexture *texture = d->cache.value(key);
+
+ if (texture) {
+
+ QSGThreadedTexture *ttex = qobject_cast<QSGThreadedTexture *>(texture);
+ if ((ttex && ttex->status() == QSGTexture::Ready) || !ttex) {
+ // Already fully uploaded... Just return
+ return QSGTextureRef(texture);
+
+ } else {
+
+ // Lock and wait for the texture to uploaded...
+ d->thread->mutex.lock();
+ while (ttex->status() == QSGTexture::Loading) {
+ d->thread->condition.wait(&d->thread->mutex);
+ }
+ d->thread->mutex.unlock();
+
+ return QSGTextureRef(ttex);
+ }
+ }
+
+ return d->upload(image, 0, 0);
+}
+
+QSGTextureRef QSGThreadedTextureManager::requestUpload(const QImage &image, const QObject *listener, const char *slot)
+{
+ Q_D(QSGThreadedTextureManager);
+
+ QSGTextureCacheKey key = { image.cacheKey() };
+ QSGTexture *texture = d->cache.value(key);
+ if (texture)
+ return QSGTextureRef(texture);
+
+ QSGThreadedTexture *ttex = new QSGThreadedTexture(this);
+ ttex->image = image;
+ if (listener && slot)
+ connect(ttex, SIGNAL(statusChanged(int)), listener, slot);
+
+ ttex->setStatus(QSGTexture::Loading);
+
+ d->cache.insert(key, ttex);
+
+ if (!d->thread) {
+ d->thread = new QSGThreadedTextureManagerThread(this);
+ }
+
+ d->thread->mutex.lock();
+ d->thread->requests << ttex;
+ d->thread->condition.wakeOne();
+ d->thread->mutex.unlock();
+
+ return QSGTextureRef(ttex);
+}
+
+/*!
+ Called by the rendering thread to create a new context that can be used in the
+ upload thread.
+ */
+void QSGThreadedTextureManager::createThreadContext()
+{
+ Q_D(QSGThreadedTextureManager);
+
+ QGLContext *ctx = const_cast<QGLContext *>(QGLContext::currentContext());
+
+ // Getting the share widget from the current context is rather nasty and
+ // will not work for lighthouse based
+ Q_ASSERT(ctx->device() && ctx->device()->devType() == QInternal::Widget);
+ QGLWidget *share = static_cast<QGLWidget *>(ctx->device());
+
+ d->widget = new QGLWidget(0, share);
+ d->widget->resize(8, 8);
+
+ d->context = const_cast<QGLContext *>(d->widget->context());
+ if (!d->context)
+ qFatal("QSGThreadedTextureManager: failed to create thread context...");
+
+ d->widget->doneCurrent();
+
+ ctx->makeCurrent();
+}
+
+
+/*!
+ Reimplement this function to make the threaded context current.
+
+ This function is called from the background thread. The default
+ implementation makes the context created in createThreadContext
+ current.
+ */
+void QSGThreadedTextureManager::makeThreadContextCurrent()
+{
+ Q_D(QSGThreadedTextureManager);
+ d->context->makeCurrent();
+}
+
+
+/*!
+ Reimplement this function to upload images in the background thread.
+
+ This function is called from the background thread. The default
+ implementation does this using a single glTexImage2D call.
+ */
+
+void QSGThreadedTextureManager::uploadInThread(const QImage &image, QSGTexture *texture)
+{
+ GLuint id;
+ glGenTextures(1, &id);
+ glBindTexture(GL_TEXTURE_2D, id);
+
+ QImage copy = image;
+// swizzleBGRAToRGBA(&copy);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
+
+ bool fail = glGetError() != GL_NO_ERROR;
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ if (fail) {
+ texture->setStatus(QSGTexture::Null);
+ glDeleteTextures(1, &id);
+ } else {
+ texture->setTextureId(id);
+ texture->setTextureSize(image.size());
+ texture->setAlphaChannel(image.hasAlphaChannel());
+ texture->setStatus(QSGTexture::Ready);
+ }
+}
+
+#include "qsgthreadedtexturemanager.moc"
diff --git a/src/adaptationlayers/qsgthreadedtexturemanager.h b/src/adaptationlayers/qsgthreadedtexturemanager.h
new file mode 100644
index 0000000..4d16f17
--- /dev/null
+++ b/src/adaptationlayers/qsgthreadedtexturemanager.h
@@ -0,0 +1,31 @@
+#ifndef QSGTHREADEDTEXTUREMANAGER_H
+#define QSGTHREADEDTEXTUREMANAGER_H
+
+#include "qsgtexturemanager.h"
+
+class QSGThreadedTextureManagerThread;
+class QSGThreadedTextureManagerPrivate;
+class QSGThreadedTexture;
+
+class QSGThreadedTextureManager : public QSGTextureManager
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QSGThreadedTextureManager);
+
+public:
+ QSGThreadedTextureManager();
+
+ QSGTextureRef upload(const QImage &image);
+ QSGTextureRef requestUpload(const QImage &image, const QObject *listener, const char *slot);
+
+protected:
+ virtual void createThreadContext();
+ virtual void makeThreadContextCurrent();
+ virtual void uploadInThread(const QImage &image, QSGTexture *texture);
+
+private:
+ friend class QSGThreadedTextureManagerThread;
+ friend class QSGThreadedTexture;
+};
+
+#endif // QSGTHREADEDTEXTUREMANAGER_H
diff --git a/src/scenegraph/coreapi/qsgtexturemanager.cpp b/src/scenegraph/coreapi/qsgtexturemanager.cpp
index bb1a2e2..bb987d3 100644
--- a/src/scenegraph/coreapi/qsgtexturemanager.cpp
+++ b/src/scenegraph/coreapi/qsgtexturemanager.cpp
@@ -87,6 +87,9 @@ QSGTexture::~QSGTexture()
void QSGTexture::setStatus(Status s)
{
+ if (m_status == s)
+ return;
+
m_status = s;
Q_ASSERT(s != Ready || (m_texture_id > 0 && !m_texture_size.isEmpty()));
emit statusChanged(s);
diff --git a/tests/auto/texturemanager/tst_texturemanagertest.cpp b/tests/auto/texturemanager/tst_texturemanagertest.cpp
index 9805478..4fb8d49 100644
--- a/tests/auto/texturemanager/tst_texturemanagertest.cpp
+++ b/tests/auto/texturemanager/tst_texturemanagertest.cpp
@@ -292,6 +292,9 @@ void TextureManagerTest::uploadAfterRequestUpload()
QVERIFY(sync.isReady());
QVERIFY(async.isReady());
+
+ QTest::qWait(100); // If the signal is emitted asynchronously, we need to wait for it...
+
QCOMPARE(status, int(QSGTexture::Ready));
QVERIFY(async.texture() == sync.texture());