Allow simple ShaderEffects to be batched by the renderer.
authorMichael Brasser <michael.brasser@live.com>
Wed, 23 Apr 2014 02:19:50 +0000 (21:19 -0500)
committerMichael Brasser <michael.brasser@live.com>
Thu, 7 Aug 2014 19:36:14 +0000 (21:36 +0200)
Identical ShaderEffects that use the standard vertex shader with a
single source texture, and that set supportsAtlasTextures, are now
candidates for batching.

Task-number: QTBUG-37914
Change-Id: Ib0ce58647a8c7c48e88bd84cf2645f1a8f28691f
Reviewed-by: Gunnar Sletta <gunnar.sletta@jollamobile.com>
src/quick/items/qquickitemsmodule.cpp
src/quick/items/qquickshadereffect.cpp
src/quick/items/qquickshadereffect_p.h
src/quick/items/qquickshadereffectmesh.cpp
src/quick/items/qquickshadereffectmesh_p.h
src/quick/items/qquickshadereffectnode.cpp
src/quick/items/qquickshadereffectnode_p.h

index 22a6eeb..2951fc7 100644 (file)
@@ -271,6 +271,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
 
     qmlRegisterType<QQuickItem, 2>(uri, 2, 4, "Item");
     qmlRegisterType<QQuickMouseArea, 1>(uri, 2, 4, "MouseArea");
+    qmlRegisterType<QQuickShaderEffect, 1>(uri, 2, 4, "ShaderEffect");
 }
 
 static void initResources()
index 8bcd2e6..9dfce3f 100644 (file)
@@ -565,7 +565,8 @@ void QQuickShaderEffectCommon::propertyChanged(QQuickItem *item, int mappedId,
        position (0, 0), the bottom-right (\l{Item::width}{width},
        \l{Item::height}{height}).
     \li attribute vec2 qt_MultiTexCoord0 - texture coordinate, the top-left
-       coordinate is (0, 0), the bottom-right (1, 1).
+       coordinate is (0, 0), the bottom-right (1, 1). If \l supportsAtlasTextures
+       is true, coordinates will be based on position in the atlas instead.
     \endlist
 
     In addition, any property that can be mapped to an OpenGL Shading Language
@@ -594,7 +595,8 @@ void QQuickShaderEffectCommon::propertyChanged(QQuickItem *item, int mappedId,
     it is by default copied from the texture atlas into a stand-alone texture
     so that the texture coordinates span from 0 to 1, and you get the expected
     wrap modes. However, this will increase the memory usage. To avoid the
-    texture copy, you can for each "uniform sampler2D <name>" declare a
+    texture copy, set \l supportsAtlasTextures for simple shaders using
+    qt_MultiTexCoord0, or for each "uniform sampler2D <name>" declare a
     "uniform vec4 qt_SubRect_<name>" which will be assigned the texture's
     normalized source rectangle. For stand-alone textures, the source rectangle
     is [0, 1]x[0, 1]. For textures in an atlas, the source rectangle corresponds
@@ -665,6 +667,8 @@ QQuickShaderEffect::QQuickShaderEffect(QQuickItem *parent)
     , m_dirtyParseLog(true)
     , m_dirtyMesh(true)
     , m_dirtyGeometry(true)
+    , m_customVertexShader(false)
+    , m_supportsAtlasTextures(false)
 {
     setFlag(QQuickItem::ItemHasContents);
 }
@@ -718,6 +722,7 @@ void QQuickShaderEffect::setVertexShader(const QByteArray &code)
     m_common.source.sourceCode[Key::VertexShader] = code;
     m_dirtyProgram = true;
     m_dirtyParseLog = true;
+    m_customVertexShader = true;
 
     if (isComponentComplete())
         m_common.updateShader(this, Key::VertexShader);
@@ -828,6 +833,31 @@ void QQuickShaderEffect::setCullMode(CullMode face)
     emit cullModeChanged();
 }
 
+/*!
+    \qmlproperty bool QtQuick::ShaderEffect::supportsAtlasTextures
+
+    Set this property true to indicate that the ShaderEffect is able to
+    use the default source texture without first removing it from an atlas.
+    In this case the range of qt_MultiTexCoord0 will based on the position of
+    the texture within the atlas, rather than (0,0) to (1,1).
+
+    Setting this to true may enable some optimizations.
+
+    The default value is false.
+
+    \since 5.4
+    \since QtQuick 2.4
+*/
+
+void QQuickShaderEffect::setSupportsAtlasTextures(bool supports)
+{
+    if (supports == m_supportsAtlasTextures)
+        return;
+    m_supportsAtlasTextures = supports;
+    updateGeometry();
+    emit supportsAtlasTexturesChanged();
+}
+
 QString QQuickShaderEffect::parseLog()
 {
     if (m_dirtyParseLog) {
@@ -940,39 +970,6 @@ QSGNode *QQuickShaderEffect::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDa
         connect(node, SIGNAL(logAndStatusChanged(QString,int)), this, SLOT(updateLogAndStatus(QString,int)));
     }
 
-    if (m_dirtyMesh) {
-        node->setGeometry(0);
-        m_dirtyMesh = false;
-        m_dirtyGeometry = true;
-    }
-
-    if (m_dirtyGeometry) {
-        node->setFlag(QSGNode::OwnsGeometry, false);
-        QSGGeometry *geometry = node->geometry();
-        QRectF rect(0, 0, width(), height());
-        QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
-
-        geometry = mesh->updateGeometry(geometry, m_common.attributes, rect);
-        if (!geometry) {
-            QString log = mesh->log();
-            if (!log.isNull()) {
-                m_log = parseLog();
-                m_log += QLatin1String("*** Mesh ***\n");
-                m_log += log;
-                m_status = Error;
-                emit logChanged();
-                emit statusChanged();
-            }
-            delete node;
-            return 0;
-        }
-
-        node->setGeometry(geometry);
-        node->setFlag(QSGNode::OwnsGeometry, true);
-
-        m_dirtyGeometry = false;
-    }
-
     QQuickShaderEffectMaterial *material = static_cast<QQuickShaderEffectMaterial *>(node->material());
 
     // Update blending
@@ -1014,6 +1011,60 @@ QSGNode *QQuickShaderEffect::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDa
         m_dirtyUniforms = m_dirtyUniformValues = m_dirtyTextureProviders = false;
     }
 
+    int textureCount = material->textureProviders.size();
+    bool preventBatching = m_customVertexShader || textureCount > 1 || (textureCount > 0 && !m_supportsAtlasTextures);
+
+    QRectF srcRect(0, 0, 1, 1);
+    if (m_supportsAtlasTextures && textureCount != 0) {
+        if (QSGTextureProvider *provider = material->textureProviders.at(0)) {
+            if (provider->texture())
+                srcRect = provider->texture()->normalizedTextureSubRect();
+        }
+    }
+
+    if (bool(material->flags() & QSGMaterial::RequiresFullMatrix) != preventBatching) {
+        material->setFlag(QSGMaterial::RequiresFullMatrix, preventBatching);
+        node->markDirty(QSGNode::DirtyMaterial);
+    }
+
+    if (material->supportsAtlasTextures != m_supportsAtlasTextures) {
+        material->supportsAtlasTextures = m_supportsAtlasTextures;
+        node->markDirty(QSGNode::DirtyMaterial);
+    }
+
+    if (m_dirtyMesh) {
+        node->setGeometry(0);
+        m_dirtyMesh = false;
+        m_dirtyGeometry = true;
+    }
+
+    if (m_dirtyGeometry) {
+        node->setFlag(QSGNode::OwnsGeometry, false);
+        QSGGeometry *geometry = node->geometry();
+        QRectF rect(0, 0, width(), height());
+        QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
+
+        geometry = mesh->updateGeometry(geometry, m_common.attributes, srcRect, rect);
+        if (!geometry) {
+            QString log = mesh->log();
+            if (!log.isNull()) {
+                m_log = parseLog();
+                m_log += QLatin1String("*** Mesh ***\n");
+                m_log += log;
+                m_status = Error;
+                emit logChanged();
+                emit statusChanged();
+            }
+            delete node;
+            return 0;
+        }
+
+        node->setGeometry(geometry);
+        node->setFlag(QSGNode::OwnsGeometry, true);
+
+        m_dirtyGeometry = false;
+    }
+
     return node;
 }
 
index 8b613b4..14d11a7 100644 (file)
@@ -99,6 +99,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickShaderEffect : public QQuickItem
     Q_PROPERTY(CullMode cullMode READ cullMode WRITE setCullMode NOTIFY cullModeChanged)
     Q_PROPERTY(QString log READ log NOTIFY logChanged)
     Q_PROPERTY(Status status READ status NOTIFY statusChanged)
+    Q_PROPERTY(bool supportsAtlasTextures READ supportsAtlasTextures WRITE setSupportsAtlasTextures NOTIFY supportsAtlasTexturesChanged REVISION 1)
     Q_ENUMS(CullMode)
     Q_ENUMS(Status)
 
@@ -138,6 +139,9 @@ public:
     QString log() const { return m_log; }
     Status status() const { return m_status; }
 
+    bool supportsAtlasTextures() const { return m_supportsAtlasTextures; }
+    void setSupportsAtlasTextures(bool supports);
+
     QString parseLog();
 
     virtual bool event(QEvent *);
@@ -150,6 +154,7 @@ Q_SIGNALS:
     void cullModeChanged();
     void logChanged();
     void statusChanged();
+    void supportsAtlasTexturesChanged();
 
 protected:
     virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry);
@@ -187,6 +192,8 @@ private:
     uint m_dirtyParseLog : 1;
     uint m_dirtyMesh : 1;
     uint m_dirtyGeometry : 1;
+    uint m_customVertexShader : 1;
+    uint m_supportsAtlasTextures : 1;
 };
 
 QT_END_NAMESPACE
index bc97a42..cf2f8fa 100644 (file)
@@ -70,7 +70,7 @@ QQuickGridMesh::QQuickGridMesh(QObject *parent)
     connect(this, SIGNAL(resolutionChanged()), this, SIGNAL(geometryChanged()));
 }
 
-QSGGeometry *QQuickGridMesh::updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &dstRect)
+QSGGeometry *QQuickGridMesh::updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &srcRect, const QRectF &dstRect)
 {
     int vmesh = m_resolution.height();
     int hmesh = m_resolution.width();
@@ -125,7 +125,6 @@ QSGGeometry *QQuickGridMesh::updateGeometry(QSGGeometry *geometry, const QVector
 
     QSGGeometry::Point2D *vdata = static_cast<QSGGeometry::Point2D *>(geometry->vertexData());
 
-    QRectF srcRect(0, 0, 1, 1);
     for (int iy = 0; iy <= vmesh; ++iy) {
         float fy = iy / float(vmesh);
         float y = float(dstRect.top()) + fy * float(dstRect.height());
index ea92490..99dbaca 100644 (file)
@@ -62,7 +62,7 @@ class QQuickShaderEffectMesh : public QObject
 public:
     QQuickShaderEffectMesh(QObject *parent = 0);
     // If 'geometry' != 0, 'attributes' is the same as last time the function was called.
-    virtual QSGGeometry *updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &rect) = 0;
+    virtual QSGGeometry *updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &srcRect, const QRectF &rect) = 0;
     // If updateGeometry() fails, the reason should appear in the log.
     virtual QString log() const { return QString(); }
 
@@ -77,7 +77,7 @@ class QQuickGridMesh : public QQuickShaderEffectMesh
     Q_PROPERTY(QSize resolution READ resolution WRITE setResolution NOTIFY resolutionChanged)
 public:
     QQuickGridMesh(QObject *parent = 0);
-    virtual QSGGeometry *updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &rect);
+    virtual QSGGeometry *updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &srcRect, const QRectF &rect);
     virtual QString log() const { return m_log; }
 
     void setResolution(const QSize &res);
index fcf5591..95d4d58 100644 (file)
@@ -41,7 +41,6 @@
 
 #include <private/qquickshadereffectnode_p.h>
 
-#include "qquickshadereffectmesh_p.h"
 #include "qquickshadereffect_p.h"
 #include <QtQuick/qsgtextureprovider.h>
 #include <QtQuick/private/qsgrenderer_p.h>
@@ -67,7 +66,6 @@ protected:
 
     const QQuickShaderEffectMaterialKey m_key;
     QVector<const char *> m_attributeNames;
-    const QVector<QByteArray> m_attributes;
     QString m_log;
     bool m_compiled;
 
@@ -77,7 +75,6 @@ protected:
 
 QQuickCustomMaterialShader::QQuickCustomMaterialShader(const QQuickShaderEffectMaterialKey &key, const QVector<QByteArray> &attributes)
     : m_key(key)
-    , m_attributes(attributes)
     , m_compiled(false)
     , m_initialized(false)
 {
@@ -146,7 +143,7 @@ void QQuickCustomMaterialShader::updateState(const RenderState &state, QSGMateri
                         if (loc >= 0) {
                             QRectF r = texture->normalizedTextureSubRect();
                             program()->setUniformValue(loc, r.x(), r.y(), r.width(), r.height());
-                        } else if (texture->isAtlasTexture()) {
+                        } else if (texture->isAtlasTexture() && (idx != 0 || !material->supportsAtlasTextures)) {
                             texture = texture->removedFromAtlas();
                         }
                         texture->bind();
@@ -346,6 +343,7 @@ QHash<QQuickShaderEffectMaterialKey, QSharedPointer<QSGMaterialType> > QQuickSha
 
 QQuickShaderEffectMaterial::QQuickShaderEffectMaterial(QQuickShaderEffectNode *node)
     : cullMode(NoCulling)
+    , supportsAtlasTextures(false)
     , m_node(node)
     , m_emittedLogChanged(false)
 {
@@ -362,9 +360,36 @@ QSGMaterialShader *QQuickShaderEffectMaterial::createShader() const
     return new QQuickCustomMaterialShader(m_source, attributes);
 }
 
-int QQuickShaderEffectMaterial::compare(const QSGMaterial *other) const
+bool QQuickShaderEffectMaterial::UniformData::operator == (const UniformData &other) const
 {
-    return this - static_cast<const QQuickShaderEffectMaterial *>(other);
+    if (specialType != other.specialType)
+        return false;
+    if (name != other.name)
+        return false;
+
+    if (specialType == UniformData::Sampler) {
+        QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(value));
+        QQuickItem *otherSource = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(other.value));
+        if (!source || !otherSource || !source->isTextureProvider() || !otherSource->isTextureProvider())
+            return false;
+        return source->textureProvider()->texture()->textureId() == otherSource->textureProvider()->texture()->textureId();
+    } else {
+        return value == other.value;
+    }
+}
+
+int QQuickShaderEffectMaterial::compare(const QSGMaterial *o) const
+{
+    const QQuickShaderEffectMaterial *other = static_cast<const QQuickShaderEffectMaterial *>(o);
+    if (cullMode != other->cullMode)
+        return 1;
+    if (supportsAtlasTextures != other->supportsAtlasTextures)
+        return 1;
+    for (int shaderType = 0; shaderType < QQuickShaderEffectMaterialKey::ShaderTypeCount; ++shaderType) {
+        if (uniforms[shaderType] != other->uniforms[shaderType])
+            return 1;
+    }
+    return 0;
 }
 
 void QQuickShaderEffectMaterial::setProgramSource(const QQuickShaderEffectMaterialKey &source)
index 232dd86..53c2cb9 100644 (file)
@@ -82,6 +82,8 @@ public:
         QByteArray name;
         QVariant value;
         SpecialType specialType;
+
+        bool operator == (const UniformData &other) const;
     };
 
     enum CullMode
@@ -100,6 +102,7 @@ public:
     QVector<UniformData> uniforms[QQuickShaderEffectMaterialKey::ShaderTypeCount];
     QVector<QSGTextureProvider *> textureProviders;
     CullMode cullMode;
+    bool supportsAtlasTextures;
 
     void setProgramSource(const QQuickShaderEffectMaterialKey &source);
     void updateTextures() const;