From e0d01169b279a528616688efd946561512b837e3 Mon Sep 17 00:00:00 2001 From: Gunnar Sletta Date: Tue, 21 Dec 2010 15:38:55 +0100 Subject: Threaded texture manager. --- src/adaptationlayers/adaptationlayers.pri | 6 +- src/adaptationlayers/qsgthreadedtexturemanager.cpp | 236 +++++++++++++++++++++ src/adaptationlayers/qsgthreadedtexturemanager.h | 31 +++ src/scenegraph/coreapi/qsgtexturemanager.cpp | 3 + .../auto/texturemanager/tst_texturemanagertest.cpp | 3 + 5 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 src/adaptationlayers/qsgthreadedtexturemanager.cpp create mode 100644 src/adaptationlayers/qsgthreadedtexturemanager.h 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 +#include +#include + +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 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(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::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(ctx->device()); + + d->widget = new QGLWidget(0, share); + d->widget->resize(8, 8); + + d->context = const_cast(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(©); + 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()); -- cgit v1.2.3