From e9365b6bb7c73ba5e696581e13ff47a204502014 Mon Sep 17 00:00:00 2001 From: Gunnar Sletta Date: Mon, 20 Dec 2010 13:20:47 +0100 Subject: Start refactoring texture manager to make way for partial uploader and threaded Simple texture manager now works according to spec --- TODO | 4 +- src/adaptationlayers/adaptationlayers.pri | 2 + .../qsgpartialuploadtexturemanager.cpp | 110 +++++++++++ .../qsgpartialuploadtexturemanager.h | 21 ++ src/scenegraph/coreapi/qsgtexturemanager.cpp | 211 ++------------------- src/scenegraph/coreapi/qsgtexturemanager.h | 13 +- 6 files changed, 161 insertions(+), 200 deletions(-) create mode 100644 src/adaptationlayers/qsgpartialuploadtexturemanager.cpp create mode 100644 src/adaptationlayers/qsgpartialuploadtexturemanager.h diff --git a/TODO b/TODO index bfcecc9..23cb6dd 100644 --- a/TODO +++ b/TODO @@ -77,6 +77,8 @@ - Use source to grab static image of subtree for later use - Feed arbitrary texture values into shader effects? - Adaptation layer plugin infrastructure? + - Outstanding bugs: + - Vsync driver never pauses - TextureManager: - In base class: @@ -91,4 +93,4 @@ - default: partial in-thread upload - mac: fast all sync uploads - dumb: - + - add tests for that createTexture does indeed return something... diff --git a/src/adaptationlayers/adaptationlayers.pri b/src/adaptationlayers/adaptationlayers.pri index e28ddfd..7e96cc3 100644 --- a/src/adaptationlayers/adaptationlayers.pri +++ b/src/adaptationlayers/adaptationlayers.pri @@ -7,6 +7,7 @@ HEADERS += \ $$PWD/default/default_glyphnode.h \ $$PWD/default/default_glyphnode_p.h \ # $$PWD/threadedtexturemanager.h + adaptationlayers/qsgpartialuploadtexturemanager.h SOURCES += \ $$PWD/adaptationlayer.cpp \ @@ -15,6 +16,7 @@ SOURCES += \ $$PWD/default/default_glyphnode.cpp \ $$PWD/default/default_glyphnode_p.cpp \ # $$PWD/threadedtexturemanager.cpp + adaptationlayers/qsgpartialuploadtexturemanager.cpp #macx:{ # SOURCES += adaptationlayers/mactexturemanager.cpp diff --git a/src/adaptationlayers/qsgpartialuploadtexturemanager.cpp b/src/adaptationlayers/qsgpartialuploadtexturemanager.cpp new file mode 100644 index 0000000..dd8af85 --- /dev/null +++ b/src/adaptationlayers/qsgpartialuploadtexturemanager.cpp @@ -0,0 +1,110 @@ +#include "qsgpartialuploadtexturemanager.h" + + + +QSGPartialUploadTextureManager::QSGPartialUploadTextureManager() +{ +} + + + + +void QSGPartialUploadTextureManager::timerEvent(QTimerEvent *) +{ +// // ### gunnar: +// // In the future, I forsee us starting / stopping this timer based +// // on wether the vsync animation driver is running or not. +// // Then we can also skip the "time since last upload" logic which +// // is currently kinda messy and unpredictable. +// if (d->lastUpload.elapsed() > 50) { +// processAsyncTextures(); +// } +} + + + +void QSGPartialUploadTextureManager::processAsyncTextures() +{ +// QTime time; +// time.start(); + +// while (!d->requests.isEmpty()) { + +// UploadRequest *request = d->requests.first(); + +// int w = request->image.width(); +// int h = request->image.height(); + +// int hChunkCount = (w + d->uploadChunkSize - 1) / d->uploadChunkSize; +// int vChunkCount = (h + d->uploadChunkSize - 1) / d->uploadChunkSize; +// int chunkCount = hChunkCount * vChunkCount; +// QSGTexture *t = request->texture; + +//// printf("\nASYNC: texture: %p, id=%d, size=(%dx%d), progress: %d / %d (%dx%d)\n", +//// t, +//// t->textureId(), +//// w, h, +//// request->progress, chunkCount, hChunkCount, vChunkCount); + +// // Create or bind the texture... +// if (request->texture->textureId() == 0) { +// while (glGetError() != GL_NO_ERROR) {} +// GLuint id; +// glGenTextures(1, &id); +// glBindTexture(GL_TEXTURE_2D, id); +// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + +// // Clean up +// // Gracefully fail in case of an error... +// GLuint error = glGetError(); +// if (error != GL_NO_ERROR) { +// glBindTexture(GL_TEXTURE_2D, 0); +// glDeleteTextures(1, &id); +// d->requests.dequeue(); +// t->setStatus(QSGTexture::Null); +// delete request; +// return; +// } + +// t->setTextureId(id); +// t->setTextureSize(QSize(w, h)); +// t->setAlphaChannel(request->image.hasAlphaChannel()); +//// printf("ASYNC: created texture %p with id=%d\n", t, id); +// } else { +// glBindTexture(GL_TEXTURE_2D, t->textureId()); +// } + +// if (time.elapsed() > d->maxUploadTime) +// break; + +// while (request->progress < chunkCount && time.elapsed() < d->maxUploadTime) { +// int x = (request->progress % hChunkCount) * d->uploadChunkSize; +// int y = (request->progress / hChunkCount) * d->uploadChunkSize; + +// QRect area = QRect(x, y, d->uploadChunkSize, d->uploadChunkSize) & request->image.rect(); +// QImage subImage = request->image.copy(area); +//// printf("ASYNC: - doing another batch: %d (x=%d, y=%d, w=%d, h=%d\n", +//// request->progress, +//// x, y, subImage.width(), subImage.height()); + +// swizzleBGRAToRGBA(&subImage); +// glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, subImage.width(), subImage.height(), GL_RGBA, GL_UNSIGNED_BYTE, subImage.constBits()); + +// ++request->progress; +// } + +// if (request->progress == chunkCount) { +// t->setStatus(QSGTexture::Ready); +// QSGTextureCacheKey key = { request->image.cacheKey() }; +// d->cache.insert(key, t); +// d->requests.dequeue(); +// delete request; +// if (d->requests.size() == 0) { +// killTimer(d->uploadTimer); +// d->uploadTimer = 0; +// } +// } +// } + +// glBindTexture(GL_TEXTURE_2D, 0); +} diff --git a/src/adaptationlayers/qsgpartialuploadtexturemanager.h b/src/adaptationlayers/qsgpartialuploadtexturemanager.h new file mode 100644 index 0000000..c9f7773 --- /dev/null +++ b/src/adaptationlayers/qsgpartialuploadtexturemanager.h @@ -0,0 +1,21 @@ +#ifndef QSGPARTIALUPLOADTEXTUREMANAGER_H +#define QSGPARTIALUPLOADTEXTUREMANAGER_H + +#include "qsgtexturemanager.h" + +class QSGPartialUploadTextureManager : public QSGTextureManager +{ +public: + QSGPartialUploadTextureManager(); + + void setContext(QSGContext *context); + + QSGTextureRef requestUpload(const QImage &image, const QObject *listener, const char *slot); + +protected: + void timerEvent(QTimerEvent *); + void processAsyncTextures(); + +}; + +#endif // QSGPARTIALUPLOADTEXTUREMANAGER_H diff --git a/src/scenegraph/coreapi/qsgtexturemanager.cpp b/src/scenegraph/coreapi/qsgtexturemanager.cpp index 11228e5..9a4d1f0 100644 --- a/src/scenegraph/coreapi/qsgtexturemanager.cpp +++ b/src/scenegraph/coreapi/qsgtexturemanager.cpp @@ -88,13 +88,6 @@ struct QSGTextureCacheKey { } }; -struct QSGTextureAsyncUpload { - QImage image; - int progress; - QSGTexture *texture; -}; - - uint qHash(const QSGTextureCacheKey &key) { return (key.cacheKey >> 32) ^ uint(key.cacheKey); @@ -105,23 +98,17 @@ class QSGTextureManagerPrivate public: QSGTextureManagerPrivate() : context(0) - , maxUploadTime(5) - , uploadChunkSize(64) - , uploadTimer(0) { } + QSGTextureRef upload(const QImage &image, const QObject *listener, const char *slot); + QSGContext *context; QHash cache; - QQueue asyncUploads; - int maxUploadTime; - int uploadChunkSize; - - int uploadTimer; - QTime lastUpload; - int maxTextureSize; + + QSGTextureManager *q; }; @@ -136,6 +123,7 @@ public: QSGTextureManager::QSGTextureManager() : d(new QSGTextureManagerPrivate) { + d->q = this; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &d->maxTextureSize); } @@ -149,9 +137,7 @@ int QSGTextureManager::maxTextureSize() const void QSGTextureManager::setContext(QSGContext *context) { Q_ASSERT(!d->context); - d->context = context; - connect(d->context, SIGNAL(aboutToRenderNextFrame()), this, SLOT(processAsyncTextures())); } QSGContext *QSGTextureManager::context() const @@ -162,13 +148,6 @@ QSGContext *QSGTextureManager::context() const void QSGTextureManager::textureDestroyed(QObject *destroyed) { - for (int i=0; iasyncUploads.size(); ++i) { - if (destroyed == d->asyncUploads[i].texture) { - d->asyncUploads.removeAt(i); - break; - } - } - for (QHash::iterator it = d->cache.begin(); it != d->cache.end(); ++it) { if (it.value() == destroyed) { @@ -176,7 +155,6 @@ void QSGTextureManager::textureDestroyed(QObject *destroyed) break; } } - } @@ -192,30 +170,16 @@ void QSGTextureManager::swizzleBGRAToRGBA(QImage *image) } - -QSGTextureRef QSGTextureManager::upload(const QImage &image) +QSGTextureRef QSGTextureManagerPrivate::upload(const QImage &image, const QObject *listener, const char *slot) { Q_ASSERT(!image.isNull()); // Check if the image is already uploaded and cached QSGTextureCacheKey key = { image.cacheKey() }; - QSGTexture *texture = d->cache.value(key); + QSGTexture *texture = cache.value(key); if (texture) return QSGTextureRef(texture); - // Check if the image is already scheduled for asynchronous upload... - // If so, kill the partial texture and upload in one go below using a new texture.. - for (int i=0; iasyncUploads.size(); ++i) { - const QSGTextureAsyncUpload &work = d->asyncUploads.at(i); - if (work.image.cacheKey() == image.cacheKey()) { - texture = work.texture; - GLuint tid = texture->textureId(); - if (tid) - glDeleteTextures(1, &tid); - d->asyncUploads.removeAt(i); - } - } - // image not already scheduled, upload normally... while (glGetError() != GL_NO_ERROR) {} @@ -241,174 +205,37 @@ QSGTextureRef QSGTextureManager::upload(const QImage &image) return QSGTextureRef(); } - if (!texture) - texture = new QSGTexture; + texture = new QSGTexture; + if (listener && slot) + QObject::connect(texture, SIGNAL(statusChanged(int)), listener, slot); texture->setTextureId(id); texture->setTextureSize(image.size()); texture->setAlphaChannel(image.hasAlphaChannel()); texture->setStatus(QSGTexture::Ready); - connect(texture, SIGNAL(destroyed(QObject*)), this, SLOT(textureDestroyed(QObject*))); + QObject::connect(texture, SIGNAL(destroyed(QObject*)), q, SLOT(textureDestroyed(QObject*))); - d->cache.insert(key, texture); + cache.insert(key, texture); QSGTextureRef ref(texture); return ref; } +QSGTextureRef QSGTextureManager::upload(const QImage &image) +{ + return d->upload(image, 0, 0); +} + + /*! Schedules \a image to be uploaded. - The function returns a texture reference which is - */ QSGTextureRef QSGTextureManager::requestUpload(const QImage &image, const QObject *listener, const char *slot) { - Q_ASSERT(!image.isNull()); - QSGTexture *t = 0; - - // Check if the image is already uploaded and thus part of the cache.. - QSGTextureCacheKey key = { image.cacheKey() }; - t = d->cache.value(key); - if (t) - return QSGTextureRef(t); - - // Check if the image is already scheduled for asynchronous upload... - for (int i=0; iasyncUploads.size(); ++i) { - const QSGTextureAsyncUpload &work = d->asyncUploads.at(i); - if (work.image.cacheKey() == image.cacheKey()) { - return QSGTextureRef(work.texture); - } - } - - // Not present in any caches, upload normally... - t = new QSGTexture(); - t->setStatus(QSGTexture::Loading); - if (listener && slot) - connect(t, SIGNAL(statusChanged(int)), listener, slot); - connect(t, SIGNAL(destroyed(QObject*)), this, SLOT(textureDestroyed(QObject*))); - - QSGTextureAsyncUpload work; - work.image = image; - work.progress = 0; - work.texture = t; - - d->asyncUploads << work; - - if (d->uploadTimer == 0) { - d->uploadTimer = startTimer(30); - } - - return QSGTextureRef(t); -} - - -void QSGTextureManager::timerEvent(QTimerEvent *) -{ - // ### gunnar: - // In the future, I forsee us starting / stopping this timer based - // on wether the vsync animation driver is running or not. - // Then we can also skip the "time since last upload" logic which - // is currently kinda messy and unpredictable. - if (d->lastUpload.elapsed() > 50) { - // Its been a while since the last frame tick, so we are pausing... - // Upload a "big" chunk... - int old = d->maxUploadTime; - d->maxUploadTime = 50; - processAsyncTextures(); - d->maxUploadTime = old; - } + return d->upload(image, listener, slot); } -void QSGTextureManager::processAsyncTextures() -{ - QTime time; - time.start(); - - d->lastUpload.restart(); - while (!d->asyncUploads.isEmpty()) { - - QSGTextureAsyncUpload &upload = d->asyncUploads.first(); - - int w = upload.image.width(); - int h = upload.image.height(); - - int hChunkCount = (w + d->uploadChunkSize - 1) / d->uploadChunkSize; - int vChunkCount = (h + d->uploadChunkSize - 1) / d->uploadChunkSize; - int chunkCount = hChunkCount * vChunkCount; - QSGTexture *t = upload.texture; - -// printf("\nASYNC: texture: %p, id=%d, size=(%dx%d), progress: %d / %d (%dx%d)\n", -// t, -// t->textureId(), -// w, h, -// upload.progress, chunkCount, hChunkCount, vChunkCount); - - // Create or bind the texture... - if (upload.texture->textureId() == 0) { - while (glGetError() != GL_NO_ERROR) {} - GLuint id; - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_2D, id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - - // Clean up - // Gracefully fail in case of an error... - GLuint error = glGetError(); - if (error != GL_NO_ERROR) { - glBindTexture(GL_TEXTURE_2D, 0); - glDeleteTextures(1, &id); - d->asyncUploads.dequeue(); - if (error != GL_OUT_OF_MEMORY) { - qWarning("QSGTextureManager::async upload failed, OpenGL error code: %x", error); - t->setStatus(QSGTexture::Error); - } else { - t->setStatus(QSGTexture::Null); - } - return; - } - - t->setTextureId(id); - t->setTextureSize(QSize(w, h)); - t->setAlphaChannel(upload.image.hasAlphaChannel()); -// printf("ASYNC: created texture %p with id=%d\n", t, id); - } else { - glBindTexture(GL_TEXTURE_2D, t->textureId()); - } - - if (time.elapsed() > d->maxUploadTime) - break; - - while (upload.progress < chunkCount && time.elapsed() < d->maxUploadTime) { - int x = (upload.progress % hChunkCount) * d->uploadChunkSize; - int y = (upload.progress / hChunkCount) * d->uploadChunkSize; - - QRect area = QRect(x, y, d->uploadChunkSize, d->uploadChunkSize) & upload.image.rect(); - QImage subImage = upload.image.copy(area); -// printf("ASYNC: - doing another batch: %d (x=%d, y=%d, w=%d, h=%d\n", -// upload.progress, -// x, y, subImage.width(), subImage.height()); - - swizzleBGRAToRGBA(&subImage); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, subImage.width(), subImage.height(), GL_RGBA, GL_UNSIGNED_BYTE, subImage.constBits()); - - ++upload.progress; - } - - if (upload.progress == chunkCount) { - t->setStatus(QSGTexture::Ready); - QSGTextureCacheKey key = { upload.image.cacheKey() }; - d->cache.insert(key, t); - d->asyncUploads.dequeue(); - if (d->asyncUploads.size() == 0) { - killTimer(d->uploadTimer); - d->uploadTimer = 0; - } - } - } - - glBindTexture(GL_TEXTURE_2D, 0); -} diff --git a/src/scenegraph/coreapi/qsgtexturemanager.h b/src/scenegraph/coreapi/qsgtexturemanager.h index 2b065f3..45f0170 100644 --- a/src/scenegraph/coreapi/qsgtexturemanager.h +++ b/src/scenegraph/coreapi/qsgtexturemanager.h @@ -47,6 +47,7 @@ #include #include +#include class QSGTextureManagerPrivate; @@ -58,8 +59,7 @@ public: enum Status { Null, Loading, - Ready, - Error + Ready }; QSGTexture(); @@ -122,7 +122,8 @@ public: : m_sub_rect(other.m_sub_rect) { m_texture = other.m_texture; - ++m_texture->m_ref_count; + if (m_texture) + ++m_texture->m_ref_count; } ~QSGTextureRef() @@ -179,14 +180,12 @@ public: int maxTextureSize() const; -protected: - void timerEvent(QTimerEvent *); - private slots: - void processAsyncTextures(); void textureDestroyed(QObject *texture); private: + friend class QSGTextureManagerPrivate; + QSGTextureManagerPrivate *d; }; -- cgit v1.2.3