diff options
-rw-r--r-- | TODO | 31 | ||||
-rw-r--r-- | qt-scene-graph.pro | 3 | ||||
-rw-r--r-- | src/adaptationlayers/default/default_texturenode.cpp | 3 | ||||
-rw-r--r-- | src/scenegraph/convenience/texturematerial.cpp | 18 | ||||
-rw-r--r-- | src/scenegraph/convenience/texturematerial.h | 12 | ||||
-rw-r--r-- | src/scenegraph/coreapi/qsgtexturemanager.cpp | 71 | ||||
-rw-r--r-- | src/scenegraph/coreapi/qsgtexturemanager.h | 2 | ||||
-rwxr-xr-x | tests/auto/run_tests.sh | 18 | ||||
-rw-r--r-- | tests/auto/texturemanager/texturemanager.pro | 17 | ||||
-rw-r--r-- | tests/auto/texturemanager/tst_texturemanagertest.cpp | 239 |
10 files changed, 398 insertions, 16 deletions
@@ -12,7 +12,6 @@ declarative/ canvas/ view - animation driver items/ all the qxitems scenegraph/ @@ -34,6 +33,9 @@ adaptation interfaces default implementations qmlrenderer + animation driver + + - Make QxImage push normalized source coords into TextureNode so we don't have to renormalize for every geometry change. It also makes a lot more sense, now that TextureNode doesn't know the @@ -48,3 +50,30 @@ does a texture memcpy for the first glDrawElements() that uses the texture in the UI thread. I guess that only leaves PBO uploads, but that won't remedy the problem, I recon... EDIT: CLIENT_STORAGE + TEXTURE_STORAGE as SHARED + seems to kill ALL performance problems + + +- Scene Graph + - Product Finalization: + - API Review and proper d-ptr implementations where required + - Documentation for all public classes + - Autotests for all public classes + - NodeInterfaces, Scene Graph structure, QSGContext, Texture classes, Renderer + - Solve the texture problem. Convenient base class, asynchronous uploads (threading?), atlasses? + - Multimedia, through Qt Mobility or directly to native API's like gstreamer + - We have phonon, but it doesn't work for devices + - Gstreamer, would work on meego / linux + - Lacking solution on symbian + - Upgrade to proper QML Items + - Examples for most common usecases usecases + - Solve the text problem + - Investigate mipmapping + - Investigate distance field + - Investigate curve rendering on gpu + - Scalable unhinted text layout + - text as geometry over a physical size + - Optimal backends for SGX, CTS79 and Mali 400. + - Pluggable vsync animation + - Wayland based + - eglSwapBuffers based + - signal based? + diff --git a/qt-scene-graph.pro b/qt-scene-graph.pro index ab9a0bd..30287dd 100644 --- a/qt-scene-graph.pro +++ b/qt-scene-graph.pro @@ -1,3 +1,4 @@ TEMPLATE = subdirs -SUBDIRS += src tools plugins examples +SUBDIRS += src tools plugins examples \ + tests/auto/texturemanager CONFIG += ordered diff --git a/src/adaptationlayers/default/default_texturenode.cpp b/src/adaptationlayers/default/default_texturenode.cpp index 077f7de..85c6eeb 100644 --- a/src/adaptationlayers/default/default_texturenode.cpp +++ b/src/adaptationlayers/default/default_texturenode.cpp @@ -227,4 +227,7 @@ void DefaultTextureNode::updateTexture() m_material.setTexture(m_texture, opaque); m_materialO.setTexture(m_texture, opaque); setMaterial(m_opacity == 1 ? &m_material : &m_materialO); // Indicate that the material state has changed. + + m_material.setClampToEdge(m_clamp_to_edge); + m_materialO.setClampToEdge(m_clamp_to_edge); } diff --git a/src/scenegraph/convenience/texturematerial.cpp b/src/scenegraph/convenience/texturematerial.cpp index 8e03884..2c431e7 100644 --- a/src/scenegraph/convenience/texturematerial.cpp +++ b/src/scenegraph/convenience/texturematerial.cpp @@ -128,6 +128,12 @@ void TextureMaterialData::updateEffectState(Renderer *renderer, AbstractEffect * glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); } + + if (oldEffect == 0 || tx->clampToEdge() != oldTx->clampToEdge()) { + int wrapMode = tx->clampToEdge() ? GL_CLAMP_TO_EDGE : GL_REPEAT; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode); + } } @@ -226,20 +232,12 @@ AbstractEffectProgram *TextureMaterialWithOpacity::createProgram() const void TextureMaterialWithOpacityData::updateEffectState(Renderer *renderer, AbstractEffect *newEffect, AbstractEffect *oldEffect) { + TextureMaterialData::updateEffectState(renderer, newEffect, oldEffect); + Q_ASSERT(oldEffect == 0 || newEffect->type() == oldEffect->type()); TextureMaterialWithOpacity *tx = static_cast<TextureMaterialWithOpacity *>(newEffect); TextureMaterialWithOpacity *oldTx = static_cast<TextureMaterialWithOpacity *>(oldEffect); - if (oldEffect == 0 || tx->texture().texture() != oldTx->texture().texture()) { - renderer->setTexture(0, tx->texture()); - oldEffect = 0; // Force filtering update. - } - if (oldEffect == 0 || tx->linearFiltering() != oldTx->linearFiltering()) { - int filtering = tx->linearFiltering() ? GL_LINEAR : GL_NEAREST; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); - } - if (oldTx == 0 || tx->opacity() != oldTx->opacity()) m_program.setUniformValue(m_opacity_id, (GLfloat) tx->opacity()); } diff --git a/src/scenegraph/convenience/texturematerial.h b/src/scenegraph/convenience/texturematerial.h index 166bca1..6a43cb5 100644 --- a/src/scenegraph/convenience/texturematerial.h +++ b/src/scenegraph/convenience/texturematerial.h @@ -48,7 +48,13 @@ class QT_SCENEGRAPH_EXPORT TextureMaterial : public AbstractEffect { public: - TextureMaterial() : m_texture(0), m_opaque(true), m_linear_filtering(false) { } + TextureMaterial() + : m_texture(0) + , m_opaque(true) + , m_linear_filtering(false) + , m_clamp_to_edge(true) + { + } virtual AbstractEffectType *type() const; virtual AbstractEffectProgram *createProgram() const; @@ -61,12 +67,16 @@ public: void setLinearFiltering(bool linearFiltering) { m_linear_filtering = linearFiltering; } bool linearFiltering() const { return m_linear_filtering; } + void setClampToEdge(bool clamp) { m_clamp_to_edge = clamp; } + bool clampToEdge() const { return m_clamp_to_edge; } + static bool is(const AbstractEffect *effect); protected: QSGTextureRef m_texture; bool m_opaque; bool m_linear_filtering; + bool m_clamp_to_edge; }; class TextureMaterialData : public AbstractShaderEffectProgram diff --git a/src/scenegraph/coreapi/qsgtexturemanager.cpp b/src/scenegraph/coreapi/qsgtexturemanager.cpp index d8c08f8..a26d353 100644 --- a/src/scenegraph/coreapi/qsgtexturemanager.cpp +++ b/src/scenegraph/coreapi/qsgtexturemanager.cpp @@ -120,13 +120,29 @@ public: int uploadTimer; QTime lastUpload; + + int maxTextureSize; }; +/*! + The QSGTextureManager class is responsible for converting QImages into texture ids + inside the QML scene graph. + The QSGTextureManager is created by the QSGContext class after a GL context is + available, so the QSGTextureManager and subclasses can safely assume that a + GL context is bound and make GL calls. + */ QSGTextureManager::QSGTextureManager() : d(new QSGTextureManagerPrivate) { + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &d->maxTextureSize); +} + + +int QSGTextureManager::maxTextureSize() const +{ + return d->maxTextureSize; } @@ -186,6 +202,9 @@ QSGTextureRef QSGTextureManager::upload(const QImage &image) if (texture) return QSGTextureRef(texture); + while (glGetError() != GL_NO_ERROR) {} + + GLuint id; glGenTextures(1, &id); glBindTexture(GL_TEXTURE_2D, id); @@ -198,6 +217,16 @@ QSGTextureRef QSGTextureManager::upload(const QImage &image) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); #endif + // Gracefully fail in case of an error... + GLuint error = glGetError(); + if (error != GL_NO_ERROR) { + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &id); + if (error != GL_OUT_OF_MEMORY) + qWarning("QSGTextureManager::upload failed, OpenGL error code: %x", error); + return QSGTextureRef(); + } + texture = new QSGTexture; texture->setTextureId(id); texture->setTextureSize(image.size()); @@ -218,9 +247,29 @@ QSGTextureRef QSGTextureManager::requestUpload(const QImage &image, const char *slot) { Q_ASSERT(!image.isNull()); + QSGTexture *t = 0; - QSGTexture *t = new QSGTexture(); - connect(t, SIGNAL(statusChanged(int)), listener, slot); + + // 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; i<d->asyncUploads.size(); ++i) { + const QSGTextureAsyncUpload &work = d->asyncUploads.at(i); + if (work.image.cacheKey() == image.cacheKey()) { + return QSGTextureRef(work.texture); + } + } + + + 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; @@ -282,14 +331,30 @@ void QSGTextureManager::processAsyncTextures() // 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); + 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()); - t->setStatus(QSGTexture::Loading); // printf("ASYNC: created texture %p with id=%d\n", t, id); } else { glBindTexture(GL_TEXTURE_2D, t->textureId()); diff --git a/src/scenegraph/coreapi/qsgtexturemanager.h b/src/scenegraph/coreapi/qsgtexturemanager.h index 6eb25ad..2b065f3 100644 --- a/src/scenegraph/coreapi/qsgtexturemanager.h +++ b/src/scenegraph/coreapi/qsgtexturemanager.h @@ -177,6 +177,8 @@ public: static void swizzleBGRAToRGBA(QImage *image); + int maxTextureSize() const; + protected: void timerEvent(QTimerEvent *); diff --git a/tests/auto/run_tests.sh b/tests/auto/run_tests.sh new file mode 100755 index 0000000..87025c0 --- /dev/null +++ b/tests/auto/run_tests.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +function execute_test() +{ + cd $1 + qmake + make + ./tst_$1 + cd .. +} + +for val in * +do + if [ -d "$val" ] + then + execute_test $val + fi +done diff --git a/tests/auto/texturemanager/texturemanager.pro b/tests/auto/texturemanager/texturemanager.pro new file mode 100644 index 0000000..e203d26 --- /dev/null +++ b/tests/auto/texturemanager/texturemanager.pro @@ -0,0 +1,17 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2010-12-16T06:09:44 +# +#------------------------------------------------- + +QT += opengl testlib + +TARGET = tst_texturemanager +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += tst_texturemanagertest.cpp + +include(../../../src/scenegraph_include.pri) diff --git a/tests/auto/texturemanager/tst_texturemanagertest.cpp b/tests/auto/texturemanager/tst_texturemanagertest.cpp new file mode 100644 index 0000000..886dfa0 --- /dev/null +++ b/tests/auto/texturemanager/tst_texturemanagertest.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt scene graph research project. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QString> +#include <QtTest/QtTest> +#include <QtCore/QCoreApplication> + +#include <qsgcontext.h> +#include <qsgtexturemanager.h> + +class TextureManagerTest : public QObject +{ + Q_OBJECT + +public: + TextureManagerTest(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void upload(); + void uploadSameImageTwice(); + + void requestUpload(); + void requestUploadSameImageTwice(); + + void requestUploadAfterSyncUpload(); + + void gracefullyRunOutOfMemory(); + void gracefullyFailOnTooLarge(); + + void maxTextureSize(); + +private: + QSGContext *context; + QGLWidget *glWidget; + QSGTextureManager *tm; +}; + + +TextureManagerTest::TextureManagerTest() +{ +} + + +void TextureManagerTest::initTestCase() +{ + glWidget = new QGLWidget(); + glWidget->resize(300, 200); + glWidget->show(); + + context = new QSGContext(); + context->initialize(const_cast<QGLContext *>(glWidget->context())); + + tm = context->textureManager(); +} + + +void TextureManagerTest::cleanupTestCase() +{ + delete context; + delete glWidget; +} + + +/*! + Verify that uploads work and are synchronous and that + size of returned texture must be at least image dims. + */ +void TextureManagerTest::upload() +{ + QImage image(100, 100, QImage::Format_ARGB32_Premultiplied); + QSGTextureRef t = tm->upload(image); + + QVERIFY(!t.isNull()); + QVERIFY(t.isReady()); + QVERIFY(t->textureId() > 0); + + QVERIFY(t->textureSize().width() >= image.width()); + QVERIFY(t->textureSize().height() >= image.height()); +} + + +/*! + Verify that two requests of the same image returns the same texture. + */ +void TextureManagerTest::uploadSameImageTwice() +{ + QImage image(100, 100, QImage::Format_ARGB32_Premultiplied); + QSGTextureRef t = tm->upload(image); + + QImage shallowCopy = image; + QSGTextureRef t2 = tm->upload(shallowCopy); + QCOMPARE(t.texture(), t2.texture()); +} + + +void TextureManagerTest::requestUpload() +{ + QImage image(100, 100, QImage::Format_ARGB32_Premultiplied); + QSGTextureRef t = tm->requestUpload(image, 0, 0); + + QVERIFY(t->status() == QSGTexture::Ready || t->status() == QSGTexture::Loading); + + int maxWait = 1000; + while (t->status() == QSGTexture::Loading) { + QTest::qWait(50); + QApplication::processEvents(); + } + + QVERIFY(t->status() == QSGTexture::Ready); + QVERIFY(t->textureId() > 0); + QVERIFY(t->textureSize().width() >= image.width()); + QVERIFY(t->textureSize().height() >= image.height()); +} + + +/*! + Test that we don't fail horribly when allocating large amounts of + texture memory. Since some drivers page graphics memory out to disk + and thus never run out, cap the test at 128Mb to not run forever + */ +void TextureManagerTest::gracefullyRunOutOfMemory() +{ + QImage image(512, 512, QImage::Format_ARGB32_Premultiplied); + QList<QSGTextureRef> refs; + + int i = 128; + while (--i) { + QSGTextureRef t = tm->upload(image); + refs << t; + if (t.isNull()) + break; + } + + QVERIFY(true); +} + + +/*! + Test that we don't crash when we try to allocate a texture that is + too large for GL. + */ +void TextureManagerTest::gracefullyFailOnTooLarge() +{ + QImage image(32000, 2, QImage::Format_ARGB32_Premultiplied); + QSGTextureRef t = tm->upload(image); + QVERIFY(t.isNull()); +} + + +void TextureManagerTest::maxTextureSize() +{ + int size = tm->maxTextureSize(); + + if (size < 1024) { + qWarning("Texture limit is only %d", size); + } + + // 64 is the lowest allowed by the spec... + // Any lower number would indicate an error + QVERIFY(size >=64); +} + + + +/*! + Verify that two consequitive requests return the same + texture instance so we don't upload images twice... + */ +void TextureManagerTest::requestUploadSameImageTwice() +{ + QImage image(100, 100, QImage::Format_ARGB32_Premultiplied); + QSGTextureRef t1 = tm->requestUpload(image, 0, 0); + QSGTextureRef t2 = tm->requestUpload(image, 0, 0); + + QVERIFY(t1.texture() == t2.texture()); +} + + + +/*! + Verify that an async upload of a sync upload returns a ready + texture that is the same as the sync one. + */ +void TextureManagerTest::requestUploadAfterSyncUpload() +{ + QImage image(100, 100, QImage::Format_ARGB32_Premultiplied); + QSGTextureRef t1 = tm->upload(image); + QSGTextureRef t2 = tm->requestUpload(image, 0, 0); + + QVERIFY(t1.texture() == t2.texture()); + QVERIFY(t2.isReady()); +} + + + +QTEST_MAIN(TextureManagerTest); + +#include "tst_texturemanagertest.moc" |