diff options
Diffstat (limited to 'src/adaptationlayers/qsgthreadedtexturemanager.cpp')
-rw-r--r-- | src/adaptationlayers/qsgthreadedtexturemanager.cpp | 236 |
1 files changed, 236 insertions, 0 deletions
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(©); + 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" |