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 aTimer
to it. Depending on theClonerItem.live
property, the timer either calls ClonerItem.update() constantly or not at all. (basically the same thelive
property of Qt'sShaderEffectSource
) - In
main.qml
I create a "producer-consumer-chain" of 3 items in aRowLayout
.
- The first item is a rotating rectangle with
layer.enabled: true
. - The second item is a
ClonerItemQml
(withlive: true
) that has the previous one as a texture source. - The third item is a
ClonerItemQml
(called "snapshotter
", withlive: false
) that has the the previous one as a texture source.
- "
snapshotter
" has itsupdate
method connected to 2 signals:
- A
firstRedraw
signal provided by item "2", which is called at the end of the first redraw of item "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 theif(m_live) update();
inrender
, 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'sShaderEffectSource
, is this Qt bug.