#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"