Drawing QQuickItem's texture in a "draw-chain" through 2 QQuickFramebufferObjects fails



  • First off, sorry for the long source code but I couldn't reduce it any further while still having the bug happen. Most of it is uninteresting boilerplate though, so you can mostly get away with just my human-readable summary below.

    Abbreviations used in this post:

    • QQFBO = QQuickFramebufferObject

    Observed result: Two rotating squares, that's all.

    What's missing from the observed result: A third, additional, non-rotating square, which is actually a static snapshot from the second one.

    What my code does, in short:

    • class ClonerItem is a QQFBO just fetches a texture from a QQuickItem and draws it to the screen.
    • The ClonerItemQml is a Qml wrapper of ClonerItem that adds a Timer to it. Depending on the ClonerItem.live property, the timer either calls ClonerItem.update() constantly or not at all. (basically the same the live property of Qt's ShaderEffectSource)
    • In main.qml I create a "producer-consumer-chain" of 3 items in a RowLayout.
    1. The first item is a rotating rectangle with layer.enabled: true.
    2. The second item is a ClonerItemQml (with live: true) that has the previous one as a texture source.
    3. The third item is a ClonerItemQml (called "snapshotter", with live: false) that has the the previous one as a texture source.
    • "snapshotter" has its update method connected to 2 signals:
    1. A firstRedraw signal provided by item "2", which is called at the end of the first redraw of item "2"
    2. For testing, the clicked signal for "snapshotter", so you can see that the "snapshotter" does work correctly when you force it to update later during the program's execution.

    Any hints what I'm doing wrong?

    Note: When I remove the Timer and instead of it, uncomment the if(m_live) update(); in render, it works fine.

    My code:

    main.cpp:

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQuickFramebufferObject>
    #include <QOpenGLShaderProgram>
    #include <QOpenGLFunctions>
    #include <QOpenGLFramebufferObject>
    #include <QQuickWindow>
    // propertyhelper.h is from http://syncor.blogspot.bg/2014/11/qt-auto-property.html
    #include "propertyhelper.h"
    #include <QSGTextureProvider>
    
    class ClonerItem : public QQuickFramebufferObject {
        Q_OBJECT
    
        AUTO_PROPERTY(QQuickItem*, sourceItem)
        AUTO_PROPERTY(bool, live)
    
    public:
        Renderer* createRenderer() const;
    
    signals:
        void firstRedraw();
    };
    
    class ClonerItemRenderer : public QObject, public QQuickFramebufferObject::Renderer, protected QOpenGLFunctions {
        Q_OBJECT
    
    public:
        ClonerItemRenderer() {
            initializeOpenGLFunctions();
    
            m_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader.vert.glsl");
            m_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shader.frag.glsl");
            m_program.link();
    
            createGeometry();
        }
    
        void render() {
            glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    
            glClear(GL_COLOR_BUFFER_BIT);
    
            glDisable(GL_CULL_FACE);
            glDisable(GL_DEPTH_TEST);
    
            m_program.bind();
            glActiveTexture(GL_TEXTURE0);
            m_tex->bind();
            m_program.setUniformValue("uTex", 0);
            paintGeometry();
            glFlush();
            glFinish();
            m_window->resetOpenGLState();
            if(!m_haveRedrawnAtLeastOnce)
                emit firstRedraw();
            m_haveRedrawnAtLeastOnce = true;
    
            //if(m_live)
            //    update();
        }
    
        void paintGeometry() {
            m_program.enableAttributeArray("aPos");
            m_program.setAttributeArray("aPos", m_vertices.constData());
            glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
        }
    
        void createGeometry() {
            m_vertices << QVector2D(-1, -1) << QVector2D(-1, +1) << QVector2D(+1, +1);
            m_vertices << QVector2D(-1, -1) << QVector2D(+1, -1) << QVector2D(+1, +1);
        }
    
        QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) {
            QOpenGLFramebufferObjectFormat format;
            format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
            return new QOpenGLFramebufferObject(size, format);
        }
    
    private:
        QVector<QVector2D> m_vertices;
        QOpenGLShaderProgram m_program;
        QQuickWindow* m_window;
        QSGTexture* m_tex;
        bool m_haveRedrawnAtLeastOnce = false;
        bool m_live;
    
    protected:
        void synchronize(QQuickFramebufferObject* qqfbo) {
            ClonerItem* parentItem = (ClonerItem*)qqfbo;
            m_window = parentItem->window();
            m_live = parentItem->live();
    
            // fetch texture pointer
            QQuickItem* sourceItem = parentItem->sourceItem();
            QSGTextureProvider* sourceTexProvider = sourceItem->textureProvider();
            m_tex = sourceTexProvider->texture();
        }
    
    signals:
        void firstRedraw();
    };
    
    QQuickFramebufferObject::Renderer* ClonerItem::createRenderer() const {
        auto renderer = new ClonerItemRenderer();
        connect(renderer, &ClonerItemRenderer::firstRedraw, this, &ClonerItem::firstRedraw);
        return renderer;
    }
    
    int main(int argc, char **argv) {
        QGuiApplication app(argc, argv);
    
        qmlRegisterType<ClonerItem>("ClonerItem", 1, 0, "ClonerItem");
    
        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
        return app.exec();
    }
    
    #include "main.moc"
    

    ClonerItem.qml:

    import QtQuick 2.0
    import ClonerItem 1.0
    
    ClonerItem {
        id: root
    
        Timer {
            id: renderTimer
            running: root.live
            interval: 1000/60
            repeat: true
    
            onTriggered: {
                root.update();
            }
        }
    }
    

    main.qml:

    import QtQuick 2.0
    import ClonerItem 1.0
    import QtQuick.Window 2.2
    import QtQuick.Layouts 1.3
    
    Window {
        visible: true
        width: 600
        height: 200
    
        RowLayout {
            id: rowLayout
            anchors.fill: parent
            Item {
                id: original
                layer.enabled: true
                Layout.fillWidth: true
                Layout.fillHeight: true
                RotatingSquare { }
            }
            ClonerItemQml {
                id: liveClone
                layer.enabled: true
                sourceItem: original
                live: true
                Layout.fillWidth: true
                Layout.fillHeight: true
                onFirstRedraw: {
                    snapshotter.update();
                }
            }
            ClonerItemQml {
                id: snapshotter
                sourceItem: liveClone
                live: false
                Layout.fillWidth: true
                Layout.fillHeight: true
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        snapshotter.update();
                    }
                }
            }
        }
    }
    

    RotatingSquare.qml:

    import QtQuick 2.0
    
    Rectangle {
        color: "red"
        border.color: "black"
        width: parent.width / 2
        height: parent.height / 2
        anchors.centerIn: parent
        NumberAnimation on rotation {
            running: true
            loops: Animation.Infinite
            duration: 1000
            from: 0
            to: 360
        }
    }
    

    shader.frag.glsl:

    varying highp vec2 vTexCoord;
    uniform sampler2D uTex;
    
    void main() {
        gl_FragColor = texture2D(uTex, vTexCoord);
    }
    

    shader.vert.glsl

    attribute highp vec2 aPos;
    varying highp vec2 vTexCoord;
    
    void main() {
        gl_Position = vec4(aPos, 0.0, 1.0);
        // aPos's components are in [-1, 1], transform them to the range [0, 1] for use as texcoords.
        vTexCoord = aPos * .5 + vec2(.5);
    }
    

    If you're wondering why I'm doing such an elaborate/weird setup, I can elucidate: in my real app the second item in the "chain" is not a simple "cloner" but actually uses its source texture(s) for some more-complex rendering (which is also memory-hungry). And the third item in the "chain" is necessary to snapshot the result of the second one, so the second one can be destroyed when not animated, freeing up memory.

    The reason I implement my own ClonerItem component, which does basically the same as Qt's ShaderEffectSource, is this Qt bug.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.