Add possibility to mirror ShaderEffectSource generated textures
authorMiikka Heikkinen <miikka.heikkinen@theqtcompany.com>
Mon, 10 Aug 2015 11:33:14 +0000 (14:33 +0300)
committerMiikka Heikkinen <miikka.heikkinen@theqtcompany.com>
Thu, 20 Aug 2015 09:49:18 +0000 (09:49 +0000)
Using textures generated by ShaderEffectSource items (or Item.layer)
with custom OpenGL code was non-intuitive due to mismatching coordinate
systems, so added a possibility to control the generated texture
orientation.

[ChangeLog][QtQuick][ShaderEffectSource] Added possibility to mirror
generated OpenGL texture.

Change-Id: I7c03d8b6fbfc43d69812c15d244200fb8e7c7bb9
Reviewed-by: Gunnar Sletta <gunnar@sletta.org>
src/quick/items/qquickitem.cpp
src/quick/items/qquickitem_p.h
src/quick/items/qquickitemsmodule.cpp
src/quick/items/qquickshadereffectsource.cpp
src/quick/items/qquickshadereffectsource_p.h
src/quick/scenegraph/qsgadaptationlayer_p.h
src/quick/scenegraph/qsgdefaultlayer.cpp
src/quick/scenegraph/qsgdefaultlayer_p.h
tests/auto/quick/qquickitemlayer/data/TextureMirroring.qml [new file with mode: 0644]
tests/auto/quick/qquickitemlayer/qquickitemlayer.pro
tests/auto/quick/qquickitemlayer/tst_qquickitemlayer.cpp

index 32c3e651dd70febd340b2b102358d48b5363e2a2..0c9ee4fe73f5fe77504780f4f2f6cb4549a2567e 100644 (file)
@@ -7488,6 +7488,7 @@ QQuickItemLayer::QQuickItemLayer(QQuickItem *item)
     , m_effectComponent(0)
     , m_effect(0)
     , m_effectSource(0)
+    , m_textureMirroring(QQuickShaderEffectSource::MirrorVertically)
 {
 }
 
@@ -7559,6 +7560,7 @@ void QQuickItemLayer::activate()
     m_effectSource->setMipmap(m_mipmap);
     m_effectSource->setWrapMode(m_wrapMode);
     m_effectSource->setFormat(m_format);
+    m_effectSource->setTextureMirroring(m_textureMirroring);
 
     if (m_effectComponent)
         activateEffect();
@@ -7811,6 +7813,35 @@ void QQuickItemLayer::setWrapMode(QQuickShaderEffectSource::WrapMode mode)
     emit wrapModeChanged(mode);
 }
 
+/*!
+    \qmlproperty enumeration QtQuick::Item::layer.textureMirroring
+    \since 5.6
+
+    This property defines how the generated OpenGL texture should be mirrored.
+    The default value is \c{ShaderEffectSource.MirrorVertically}.
+    Custom mirroring can be useful if the generated texture is directly accessed by custom shaders,
+    such as those specified by ShaderEffect. If no effect is specified for the layered
+    item, mirroring has no effect on the UI representation of the item.
+
+    \list
+    \li ShaderEffectSource.NoMirroring - No mirroring
+    \li ShaderEffectSource.MirrorHorizontally - The generated texture is flipped along X-axis.
+    \li ShaderEffectSource.MirrorVertically - The generated texture is flipped along Y-axis.
+    \endlist
+ */
+
+void QQuickItemLayer::setTextureMirroring(QQuickShaderEffectSource::TextureMirroring mirroring)
+{
+    if (mirroring == m_textureMirroring)
+        return;
+    m_textureMirroring = mirroring;
+
+    if (m_effectSource)
+        m_effectSource->setTextureMirroring(m_textureMirroring);
+
+    emit textureMirroringChanged(mirroring);
+}
+
 /*!
     \qmlproperty string QtQuick::Item::layer.samplerName
 
index 5e0246c32ee31e3c3c76bc822dc337500644d034..942b51bf689e680060697e36b7f90e7311dd1b07 100644 (file)
@@ -143,6 +143,7 @@ class QQuickItemLayer : public QObject, public QQuickItemChangeListener
     Q_PROPERTY(QQuickShaderEffectSource::Format format READ format WRITE setFormat NOTIFY formatChanged)
     Q_PROPERTY(QByteArray samplerName READ name WRITE setName NOTIFY nameChanged)
     Q_PROPERTY(QQmlComponent *effect READ effect WRITE setEffect NOTIFY effectChanged)
+    Q_PROPERTY(QQuickShaderEffectSource::TextureMirroring textureMirroring READ textureMirroring WRITE setTextureMirroring NOTIFY textureMirroringChanged)
 public:
     QQuickItemLayer(QQuickItem *item);
     ~QQuickItemLayer();
@@ -177,6 +178,9 @@ public:
     QQmlComponent *effect() const { return m_effectComponent; }
     void setEffect(QQmlComponent *effect);
 
+    QQuickShaderEffectSource::TextureMirroring textureMirroring() const { return m_textureMirroring; }
+    void setTextureMirroring(QQuickShaderEffectSource::TextureMirroring mirroring);
+
     QQuickShaderEffectSource *effectSource() const { return m_effectSource; }
 
     void itemGeometryChanged(QQuickItem *, const QRectF &, const QRectF &) Q_DECL_OVERRIDE;
@@ -200,6 +204,7 @@ Q_SIGNALS:
     void smoothChanged(bool smooth);
     void formatChanged(QQuickShaderEffectSource::Format format);
     void sourceRectChanged(const QRectF &sourceRect);
+    void textureMirroringChanged(QQuickShaderEffectSource::TextureMirroring mirroring);
 
 private:
     friend class QQuickTransformAnimatorJob;
@@ -223,6 +228,7 @@ private:
     QQmlComponent *m_effectComponent;
     QQuickItem *m_effect;
     QQuickShaderEffectSource *m_effectSource;
+    QQuickShaderEffectSource::TextureMirroring m_textureMirroring;
 };
 
 class Q_QUICK_PRIVATE_EXPORT QQuickItemPrivate : public QObjectPrivate
index 4df1ef038c329fab7d3caaee221a08f86f240f3e..62e0adcb0adbdcfa47da5f65e2f028a08dde2220 100644 (file)
@@ -273,6 +273,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
     qmlRegisterType<QQuickFlow, 6>(uri, 2, 6, "Flow");
     qmlRegisterUncreatableType<QQuickEnterKeyAttached, 6>(uri, 2, 6, "EnterKey",
                                                            QQuickEnterKeyAttached::tr("EnterKey is only available via attached properties"));
+    qmlRegisterType<QQuickShaderEffectSource, 1>(uri, 2, 6, "ShaderEffectSource");
 }
 
 static void initResources()
index bf69fe42775637bc6c70656a91198fa66f797172..2effc0d0ae6e34594ee1074b73411e8c720c36d0 100644 (file)
@@ -189,6 +189,7 @@ QQuickShaderEffectSource::QQuickShaderEffectSource(QQuickItem *parent)
     , m_mipmap(false)
     , m_recursive(false)
     , m_grab(true)
+    , m_textureMirroring(MirrorVertically)
 {
     setFlag(ItemHasContents);
 }
@@ -542,6 +543,37 @@ void QQuickShaderEffectSource::setRecursive(bool enabled)
     emit recursiveChanged();
 }
 
+/*!
+    \qmlproperty enumeration QtQuick::ShaderEffectSource::textureMirroring
+    \since 5.6
+
+    This property defines how the generated OpenGL texture should be mirrored.
+    The default value is \c{ShaderEffectSource.MirrorVertically}.
+    Custom mirroring can be useful if the generated texture is directly accessed by custom shaders,
+    such as those specified by ShaderEffect. Mirroring has no effect on the UI representation of
+    the ShaderEffectSource item itself.
+
+    \list
+    \li ShaderEffectSource.NoMirroring - No mirroring
+    \li ShaderEffectSource.MirrorHorizontally - The generated texture is flipped along X-axis.
+    \li ShaderEffectSource.MirrorVertically - The generated texture is flipped along Y-axis.
+    \endlist
+*/
+
+QQuickShaderEffectSource::TextureMirroring QQuickShaderEffectSource::textureMirroring() const
+{
+    return QQuickShaderEffectSource::TextureMirroring(m_textureMirroring);
+}
+
+void QQuickShaderEffectSource::setTextureMirroring(TextureMirroring mirroring)
+{
+    if (mirroring == QQuickShaderEffectSource::TextureMirroring(m_textureMirroring))
+        return;
+    m_textureMirroring = mirroring;
+    update();
+    emit textureMirroringChanged();
+}
+
 /*!
     \qmlmethod QtQuick::ShaderEffectSource::scheduleUpdate()
 
@@ -642,6 +674,8 @@ QSGNode *QQuickShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaint
     m_texture->setRecursive(m_recursive);
     m_texture->setFormat(GLenum(m_format));
     m_texture->setHasMipmaps(m_mipmap);
+    m_texture->setMirrorHorizontal(m_textureMirroring & MirrorHorizontally);
+    m_texture->setMirrorVertical(m_textureMirroring & MirrorVertically);
 
     if (m_grab)
         m_texture->scheduleUpdate();
index 94bb31556690f981d63e7298e77161bd99437442..629acf0f55a02504aaabd0348e2e1c196fdae8a6 100644 (file)
@@ -66,6 +66,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickShaderEffectSource : public QQuickItem, publi
     Q_PROPERTY(bool hideSource READ hideSource WRITE setHideSource NOTIFY hideSourceChanged)
     Q_PROPERTY(bool mipmap READ mipmap WRITE setMipmap NOTIFY mipmapChanged)
     Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
+    Q_PROPERTY(TextureMirroring textureMirroring READ textureMirroring WRITE setTextureMirroring NOTIFY textureMirroringChanged REVISION 1)
 
 public:
     enum WrapMode {
@@ -83,6 +84,13 @@ public:
     };
     Q_ENUM(Format)
 
+    enum TextureMirroring {
+        NoMirroring        = 0x00,
+        MirrorHorizontally = 0x01,
+        MirrorVertically   = 0x02
+    };
+    Q_ENUM(TextureMirroring)
+
     QQuickShaderEffectSource(QQuickItem *parent = 0);
     ~QQuickShaderEffectSource();
 
@@ -113,6 +121,9 @@ public:
     bool recursive() const;
     void setRecursive(bool enabled);
 
+    TextureMirroring textureMirroring() const;
+    void setTextureMirroring(TextureMirroring mirroring);
+
     bool isTextureProvider() const Q_DECL_OVERRIDE { return true; }
     QSGTextureProvider *textureProvider() const Q_DECL_OVERRIDE;
 
@@ -128,6 +139,7 @@ Q_SIGNALS:
     void hideSourceChanged();
     void mipmapChanged();
     void recursiveChanged();
+    void textureMirroringChanged();
 
     void scheduledUpdateCompleted();
 
@@ -157,6 +169,7 @@ private:
     uint m_mipmap : 1;
     uint m_recursive : 1;
     uint m_grab : 1;
+    uint m_textureMirroring : 2; // Stores TextureMirroring enum
 };
 
 QT_END_NAMESPACE
index 1253711a943baff8c6ffde8e0990d8745e16430e..fde3fa06b27f0f0a61bb42e376d9c55e96ccaaa4 100644 (file)
@@ -195,6 +195,8 @@ public:
     virtual void setFormat(GLenum format) = 0;
     virtual void setHasMipmaps(bool mipmap) = 0;
     virtual void setDevicePixelRatio(qreal ratio) = 0;
+    virtual void setMirrorHorizontal(bool mirror) = 0;
+    virtual void setMirrorVertical(bool mirror) = 0;
     Q_SLOT virtual void markDirtyTexture() = 0;
     Q_SLOT virtual void invalidated() = 0;
 
index cca0712ece069e384bc2c94770de2e26ef9746a0..fa69f911dd8da1f25a85f0ab4479690995b54e32 100644 (file)
@@ -97,6 +97,8 @@ QSGDefaultLayer::QSGDefaultLayer(QSGRenderContext *context)
     , m_multisamplingChecked(false)
     , m_multisampling(false)
     , m_grab(false)
+    , m_mirrorHorizontal(false)
+    , m_mirrorVertical(true)
 {
 }
 
@@ -259,6 +261,16 @@ void QSGDefaultLayer::setRecursive(bool recursive)
     m_recursive = recursive;
 }
 
+void QSGDefaultLayer::setMirrorHorizontal(bool mirror)
+{
+    m_mirrorHorizontal = mirror;
+}
+
+void QSGDefaultLayer::setMirrorVertical(bool mirror)
+{
+    m_mirrorVertical = mirror;
+}
+
 void QSGDefaultLayer::markDirtyTexture()
 {
     m_dirtyTexture = true;
@@ -365,7 +377,10 @@ void QSGDefaultLayer::grab()
 
     m_renderer->setDeviceRect(m_size);
     m_renderer->setViewportRect(m_size);
-    QRectF mirrored(m_rect.left(), m_rect.bottom(), m_rect.width(), -m_rect.height());
+    QRectF mirrored(m_mirrorHorizontal ? m_rect.right() : m_rect.left(),
+                    m_mirrorVertical ? m_rect.bottom() : m_rect.top(),
+                    m_mirrorHorizontal ? -m_rect.width() : m_rect.width(),
+                    m_mirrorVertical ? -m_rect.height() : m_rect.height());
     m_renderer->setProjectionMatrixToRect(mirrored);
     m_renderer->setClearColor(Qt::transparent);
 
@@ -428,3 +443,11 @@ QImage QSGDefaultLayer::toImage() const
 
     return QImage();
 }
+
+QRectF QSGDefaultLayer::normalizedTextureSubRect() const
+{
+    return QRectF(m_mirrorHorizontal ? 1 : 0,
+                  m_mirrorVertical ? 0 : 1,
+                  m_mirrorHorizontal ? -1 : 1,
+                  m_mirrorVertical ? 1 : -1);
+}
index 0ba7109ef638e444be226da235379759b41f6851..7baaed5f6762fd5cad28c62d232e97ff713f0a68 100644 (file)
@@ -78,10 +78,18 @@ public:
 
     void setDevicePixelRatio(qreal ratio) Q_DECL_OVERRIDE { m_device_pixel_ratio = ratio; }
 
+    bool mirrorHorizontal() const { return bool(m_mirrorHorizontal); }
+    void setMirrorHorizontal(bool mirror) Q_DECL_OVERRIDE;
+
+    bool mirrorVertical() const { return bool(m_mirrorVertical); }
+    void setMirrorVertical(bool mirror) Q_DECL_OVERRIDE;
+
     void scheduleUpdate() Q_DECL_OVERRIDE;
 
     QImage toImage() const Q_DECL_OVERRIDE;
 
+    QRectF normalizedTextureSubRect() const Q_DECL_OVERRIDE;
+
 public Q_SLOTS:
     void markDirtyTexture() Q_DECL_OVERRIDE;
     void invalidated() Q_DECL_OVERRIDE;
@@ -115,6 +123,8 @@ private:
     uint m_multisamplingChecked : 1;
     uint m_multisampling : 1;
     uint m_grab : 1;
+    uint m_mirrorHorizontal : 1;
+    uint m_mirrorVertical : 1;
 };
 
 #endif // QSGDEFAULTLAYER_P_H
diff --git a/tests/auto/quick/qquickitemlayer/data/TextureMirroring.qml b/tests/auto/quick/qquickitemlayer/data/TextureMirroring.qml
new file mode 100644 (file)
index 0000000..2827960
--- /dev/null
@@ -0,0 +1,159 @@
+import QtQuick 2.6
+
+Item
+{
+    width: 250
+    height: 50
+
+    property int mirroring: 0
+
+    // Layered box without effect. Mirroring should not affect how it looks.
+    Rectangle {
+        x: 0
+        y: 0
+        width: 50
+        height: 50
+        layer.enabled: true
+        layer.textureMirroring: mirroring
+        Rectangle {
+            x: 0
+            y: 0
+            width: 25
+            height: 25
+            color: "#000000"
+        }
+        Rectangle {
+            x: 25
+            y: 0
+            width: 25
+            height: 25
+            color: "#ff0000"
+        }
+        Rectangle {
+            x: 0
+            y: 25
+            width: 25
+            height: 25
+            color: "#00ff00"
+        }
+        Rectangle {
+            x: 25
+            y: 25
+            width: 25
+            height: 25
+            color: "#0000ff"
+        }
+    }
+
+    // Layered box with effect. Mirroring should affect how it looks.
+    Rectangle {
+        id: layeredEffectBox
+        x: 50
+        y: 0
+        width: 50
+        height: 50
+        layer.enabled: true
+        layer.textureMirroring: mirroring
+        layer.samplerName: "source"
+        layer.effect: ShaderEffect {
+            property variant source: layeredEffectBox
+            fragmentShader: "
+                uniform lowp sampler2D source;
+                varying highp vec2 qt_TexCoord0;
+                void main() {
+                    gl_FragColor = texture2D(source, qt_TexCoord0);
+                }"
+
+        }
+
+        Rectangle {
+            x: 0
+            y: 0
+            width: 25
+            height: 25
+            color: "#000000"
+        }
+        Rectangle {
+            x: 25
+            y: 0
+            width: 25
+            height: 25
+            color: "#ff0000"
+        }
+        Rectangle {
+            x: 0
+            y: 25
+            width: 25
+            height: 25
+            color: "#00ff00"
+        }
+        Rectangle {
+            x: 25
+            y: 25
+            width: 25
+            height: 25
+            color: "#0000ff"
+        }
+    }
+
+    // Non-layered source item for ShaderEffectSource. Mirroring should not affect how it looks.
+    Rectangle {
+        id: box2
+        x: 100
+        y: 0
+        width: 50
+        height: 50
+        Rectangle {
+            x: 0
+            y: 0
+            width: 25
+            height: 25
+            color: "#000000"
+        }
+        Rectangle {
+            x: 25
+            y: 0
+            width: 25
+            height: 25
+            color: "#ff0000"
+        }
+        Rectangle {
+            x: 0
+            y: 25
+            width: 25
+            height: 25
+            color: "#00ff00"
+        }
+        Rectangle {
+            x: 25
+            y: 25
+            width: 25
+            height: 25
+            color: "#0000ff"
+        }
+    }
+    // ShaderEffectSource item. Mirroring should not affect how it looks.
+    ShaderEffectSource {
+        id: theSource
+        x: 150
+        y: 0
+        width: 50
+        height: 50
+        sourceItem: box2
+        textureMirroring: mirroring
+    }
+    // ShaderEffect item. Mirroring should affect how it looks.
+    ShaderEffect {
+        x: 200
+        y: 0
+        width: 50
+        height: 50
+        property variant source: theSource
+        fragmentShader: "
+            uniform lowp sampler2D source;
+            varying highp vec2 qt_TexCoord0;
+            void main() {
+                gl_FragColor = texture2D(source, qt_TexCoord0);
+            }"
+    }
+}
index 999f0cf23d03f54379a51d72a6d12d815f7b4aab..a087948f6d76e179b87cf448cb8ce9db520f9a58 100644 (file)
@@ -25,5 +25,6 @@ OTHER_FILES += \
     data/DisableLayer.qml \
     data/SamplerNameChange.qml \
     data/ItemEffect.qml \
-    data/RectangleEffect.qml
+    data/RectangleEffect.qml \
+    data/TextureMirroring.qml
 DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
index 25a75c05802e6404d45f0b9f038a69786b79da7a..094b69c07f9f030d621bcfd13883e705065aa7cb 100644 (file)
@@ -87,7 +87,12 @@ private slots:
     void itemEffect();
     void rectangleEffect();
 
+    void textureMirroring_data();
+    void textureMirroring();
+
 private:
+    void mirroringCheck(int mirroring, int x, bool shouldMirror, const QImage &fb);
+
     bool m_isMesaSoftwareRasterizer;
     int m_mesaVersion;
 };
@@ -434,6 +439,94 @@ void tst_QQuickItemLayer::rectangleEffect()
     QCOMPARE(fb.pixel(0, 100), qRgb(0, 0, 0xff));
 }
 
+void tst_QQuickItemLayer::textureMirroring_data()
+{
+    QTest::addColumn<int>("mirroring");
+
+    QTest::newRow("no mirroring") << 0;
+    QTest::newRow("horizontal") << 1;
+    QTest::newRow("vertical") << 2;
+    QTest::newRow("horizontal | vertical") << 3;
+}
+
+void tst_QQuickItemLayer::textureMirroring()
+{
+    QFETCH(int, mirroring);
+
+    QQuickView view;
+    view.setSource(testFileUrl("TextureMirroring.qml"));
+
+    QQuickItem *child = view.contentItem()->childItems().at(0);
+    child->setProperty("mirroring", mirroring);
+
+    view.show();
+
+    QTest::qWaitForWindowExposed(&view);
+
+    QImage fb = view.grabWindow();
+
+    // Mirroring should have no visual effect on layered item without shader effect
+    mirroringCheck(mirroring, 0, false, fb);
+
+    // Mirroring should have visual effect on layered item with shader effect
+    mirroringCheck(mirroring, 50, true, fb);
+
+    // Mirroring should have no visual effect on source item for ShaderEffectSource
+    mirroringCheck(mirroring, 100, false, fb);
+
+    // Mirroring should have no visual effect on ShaderEffectSource item
+    mirroringCheck(mirroring, 150, false, fb);
+
+    // Mirroring should have visual effect on ShaderEffect item itself
+    mirroringCheck(mirroring, 200, true, fb);
+}
+
+void tst_QQuickItemLayer::mirroringCheck(int mirroring, int x, bool shouldMirror, const QImage &fb)
+{
+    int offset = 10;
+    int spacing = 25;
+
+    if (shouldMirror) {
+        switch (mirroring) {
+        case 0: { // No mirroring -> Visually Y gets swapped, X is default
+            QCOMPARE(fb.pixel(x + offset, offset), qRgb(0, 0xff, 0));
+            QCOMPARE(fb.pixel(x + offset + spacing, offset), qRgb(0, 0, 0xff));
+            QCOMPARE(fb.pixel(x + offset, offset + spacing), qRgb(0, 0, 0));
+            QCOMPARE(fb.pixel(x + offset + spacing, offset + spacing), qRgb(0xff, 0, 0));
+            break;
+        }
+        case 1: { // Horizontal mirroring -> Visually both X and Y get swapped, as neither is default
+            QCOMPARE(fb.pixel(x + offset, offset), qRgb(0, 0, 0xff));
+            QCOMPARE(fb.pixel(x + offset + spacing, offset), qRgb(0, 0xff, 0));
+            QCOMPARE(fb.pixel(x + offset, offset + spacing), qRgb(0xff, 0, 0));
+            QCOMPARE(fb.pixel(x + offset + spacing, offset + spacing), qRgb(0, 0, 0));
+            break;
+        }
+        case 2: { // Vertical mirroring -> The default case, nothing gets swapped
+            QCOMPARE(fb.pixel(x + offset, offset), qRgb(0, 0, 0));
+            QCOMPARE(fb.pixel(x + offset + spacing, offset), qRgb(0xff, 0, 0));
+            QCOMPARE(fb.pixel(x + offset, offset + spacing), qRgb(0, 0xff, 0));
+            QCOMPARE(fb.pixel(x + offset + spacing, offset + spacing), qRgb(0, 0, 0xff));
+            break;
+        }
+        case 3: { // Both axes mirrored -> Visually X gets swapped, Y is default
+            QCOMPARE(fb.pixel(x + offset, offset), qRgb(0xff, 0, 0));
+            QCOMPARE(fb.pixel(x + offset + spacing, offset), qRgb(0, 0, 0));
+            QCOMPARE(fb.pixel(x + offset, offset + spacing), qRgb(0, 0, 0xff));
+            QCOMPARE(fb.pixel(x + offset + spacing, offset + spacing), qRgb(0, 0xff, 0));
+            break;
+        }
+        default:
+            qWarning() << "Invalid case!";
+            break;
+        }
+    } else {
+        QCOMPARE(fb.pixel(x + offset, offset), qRgb(0, 0, 0));
+        QCOMPARE(fb.pixel(x + offset + spacing, offset), qRgb(0xff, 0, 0));
+        QCOMPARE(fb.pixel(x + offset, offset + spacing), qRgb(0, 0xff, 0));
+        QCOMPARE(fb.pixel(x + offset + spacing, offset + spacing), qRgb(0, 0, 0xff));
+    }
+}
 
 QTEST_MAIN(tst_QQuickItemLayer)