From 1e39eb5f701977ac3ea4b6d66f2ce304931a1085 Mon Sep 17 00:00:00 2001 From: Gunnar Sletta Date: Fri, 13 Jun 2014 13:59:57 +0200 Subject: Separate renderer out in "OpenGL under QML" example. The example was promoting very bad practice. Change-Id: Ibb83780ec33e59ee5aabf65a775705dd0da681e6 Reviewed-by: Laszlo Agocs --- .../openglunderqml/doc/src/openglunderqml.qdoc | 113 ++++++++++----------- .../quick/scenegraph/openglunderqml/squircle.cpp | 74 +++++++------- .../quick/scenegraph/openglunderqml/squircle.h | 32 ++++-- 3 files changed, 115 insertions(+), 104 deletions(-) diff --git a/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc b/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc index 1f87412aa4..de023ff95c 100644 --- a/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc +++ b/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc @@ -50,54 +50,40 @@ in the QML file and this value is used by the OpenGL shader program that draws the squircles. + \snippet scenegraph/openglunderqml/squircle.h 2 + + First of all, we need an object we can expose to QML. This is a + subclass of QQuickItem so we can easily access \l QQuickItem::window(). + \snippet scenegraph/openglunderqml/squircle.h 1 - First of all, we need a QObject with a slot to connect the signals - to. We subclass QQuickItem in order to use the \l - QQuickItem::window() which holds the window instance we want to - connect to. - - We use two values of \c t. The variable \c m_t is the property - value as it exists in the GUI thread. The \c m_thread_t value is a - copy of \c m_t for use in the rendering thread. We need an - explicit copy because the scene graph can render in one thread - while updating properties on the GUI thread in preparation for the - next frame. If we had used only one value, the animation could - have updated the value to that of the next frame before we got a - chance to render it. - - \note In this example, a wrong value for \c t will have minimal - consequences, but we emphasize that rendering and GUI thread - objects and values must stay separate to avoid race conditions, - undesired behavior and in the worst case, crashes. + Then we need an object to take care of the rendering. This + instance needs to be separated from the QQuickItem because the + item lives in the GUI thread and the rendering potentially happens + on the render thread. Since we want to connect to \l + QQuickWindow::beforeRendering(), we make the renderer a QObject. + The renderer contains a copy of all the state it needs, + independent of the GUI thread. + + \note Don't be tempted to merge the two objects into + one. QQuickItems may be deleted on the GUI thread while the render + thread is rendering. Lets move on to the implementation. \snippet scenegraph/openglunderqml/squircle.cpp 7 The constructor of the \c Squircle class simply initializes the - values. The shader program will be initialized during rendering - later. - - \snippet scenegraph/openglunderqml/squircle.cpp 8 - - The property setter checks that the value has indeed changed - before updating its internal variable. It then calls \l - QQuickWindow::update() which will trigger another frame to be - rendered. Note that the setter might be called during - initialization, before the object has been entered into the scene - and before it has a window. + values and connects to the window changed signal which we will use + to prepare our renderer. \snippet scenegraph/openglunderqml/squircle.cpp 1 - \snippet scenegraph/openglunderqml/squircle.cpp 2 - For our paint function to be called, we need to connect to the - window's signals. When Squircle object is populated into the - scene, the windowChanged signal is emitted. In our handler, - we connect \l QQuickWindow::beforeRendering() to - \c paint() to do the rendering, and \l - QQuickWindow::beforeSynchronizing() to \c sync() to copy the state - of the \c t property for the upcoming frame. + Once we have a window, we attach to the \l + QQuickWindow::beforeSynchronizing() signal which we will use to + create the renderer and to copy state into it safely. We also + connect to the \l QQuickWindow::sceneGraphInvalidated() signal to + handle the cleanup of the renderer. \note Since the Squircle object has affinity to the GUI thread and the signals are emitted from the rendering thread, it is crucial @@ -113,18 +99,35 @@ graph, we need to turn this clearing off. This means that we need to clear ourselves in the \c paint() function. - \snippet scenegraph/openglunderqml/squircle.cpp 4 + \snippet scenegraph/openglunderqml/squircle.cpp 9 + + We use the \c sync() function to initialize the renderer and to + copy the state in our item into the renderer. When the renderer is + created, we also connect the \l QQuickWindow::beforeRendering() to + the renderer's \c paint() slot. - The first thing we do in the \c paint() function is to - initialize the shader program. By initializing the shader program - here, we make sure that the OpenGL context is bound and that we - are on the correct thread. + \note The \l QQuickWindow::beforeSynchronizing() signal is emitted + on the rendering thread while the GUI thread is blocked, so it is + safe to simply copy the value without any additional protection. - We also connect to the QOpenGLContext::aboutToBeDestroyed() - signal, so that we can clean up the shader program when the - context is destroyed. Again, this is a \l Qt::DirectConnection as - all rendering related operations must happen on the rendering - thread. + \snippet scenegraph/openglunderqml/squircle.cpp 6 + + In the \c cleanup() function we delete the renderer which in turn + cleans up its own resources. + + \snippet scenegraph/openglunderqml/squircle.cpp 8 + + When the value of \c t changes, we call \l QQuickWindow::update() + rather than \l QQuickItem::update() because the former will force + the entire window to be redrawn, even when the scene graph has not + changed since the last frame. + + \snippet scenegraph/openglunderqml/squircle.cpp 4 + + In the SquircleRenderer's \c paint() function we start by + initializing the shader program. By initializing the shader + program here, we make sure that the OpenGL context is bound and + that we are on the correct thread. \snippet scenegraph/openglunderqml/squircle.cpp 5 @@ -133,18 +136,10 @@ attributes we used so that the OpenGL context is in a "clean" state for the scene graph to pick it up. - \snippet scenegraph/openglunderqml/squircle.cpp 6 - - In the \c cleanup() function we delete the program. - - \snippet scenegraph/openglunderqml/squircle.cpp 9 - - We use the \c sync() function to copy the state of the - object in the GUI thread into the rendering thread. - - The signal is emitted on the rendering thread while the GUI - thread is blocked, so it is safe to simply copy the value without - any additional protection. + \note If tracking the changes in the OpenGL context's state is not + feasible, one can use the function \l + QQuickWindow::resetOpenGLState() which will reset all state that + the scene graph relies on. \snippet scenegraph/openglunderqml/main.cpp 1 diff --git a/examples/quick/scenegraph/openglunderqml/squircle.cpp b/examples/quick/scenegraph/openglunderqml/squircle.cpp index 91d69c90a4..4b892e3761 100644 --- a/examples/quick/scenegraph/openglunderqml/squircle.cpp +++ b/examples/quick/scenegraph/openglunderqml/squircle.cpp @@ -47,9 +47,8 @@ //! [7] Squircle::Squircle() - : m_program(0) - , m_t(0) - , m_thread_t(0) + : m_t(0) + , m_renderer(0) { connect(this, SIGNAL(windowChanged(QQuickWindow*)), this, SLOT(handleWindowChanged(QQuickWindow*))); } @@ -71,24 +70,46 @@ void Squircle::setT(qreal t) void Squircle::handleWindowChanged(QQuickWindow *win) { if (win) { -//! [1] - // Connect the beforeRendering signal to our paint function. - // Since this call is executed on the rendering thread it must be - // a Qt::DirectConnection -//! [2] - connect(win, SIGNAL(beforeRendering()), this, SLOT(paint()), Qt::DirectConnection); connect(win, SIGNAL(beforeSynchronizing()), this, SLOT(sync()), Qt::DirectConnection); -//! [2] - + connect(win, SIGNAL(sceneGraphInvalidated()), this, SLOT(cleanup()), Qt::DirectConnection); +//! [1] // If we allow QML to do the clearing, they would clear what we paint // and nothing would show. //! [3] win->setClearBeforeRendering(false); } } +//! [3] + +//! [6] +void Squircle::cleanup() +{ + if (m_renderer) { + delete m_renderer; + m_renderer = 0; + } +} -//! [3] //! [4] -void Squircle::paint() +SquircleRenderer::~SquircleRenderer() +{ + delete m_program; +} +//! [6] + +//! [9] +void Squircle::sync() +{ + if (!m_renderer) { + m_renderer = new SquircleRenderer(); + connect(window(), SIGNAL(beforeRendering()), m_renderer, SLOT(paint()), Qt::DirectConnection); + } + m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio()); + m_renderer->setT(m_t); +} +//! [9] + +//! [4] +void SquircleRenderer::paint() { if (!m_program) { m_program = new QOpenGLShaderProgram(); @@ -112,8 +133,6 @@ void Squircle::paint() m_program->bindAttributeLocation("vertices", 0); m_program->link(); - connect(window()->openglContext(), SIGNAL(aboutToBeDestroyed()), - this, SLOT(cleanup()), Qt::DirectConnection); } //! [4] //! [5] m_program->bind(); @@ -127,12 +146,9 @@ void Squircle::paint() 1, 1 }; m_program->setAttributeArray(0, GL_FLOAT, values, 2); - m_program->setUniformValue("t", (float) m_thread_t); + m_program->setUniformValue("t", (float) m_t); - qreal ratio = window()->devicePixelRatio(); - int w = int(ratio * window()->width()); - int h = int(ratio * window()->height()); - glViewport(0, 0, w, h); + glViewport(0, 0, m_viewportSize.width(), m_viewportSize.height()); glDisable(GL_DEPTH_TEST); @@ -148,21 +164,3 @@ void Squircle::paint() m_program->release(); } //! [5] - -//! [6] -void Squircle::cleanup() -{ - if (m_program) { - delete m_program; - m_program = 0; - } -} -//! [6] - -//! [9] -void Squircle::sync() -{ - m_thread_t = m_t; -} -//! [9] - diff --git a/examples/quick/scenegraph/openglunderqml/squircle.h b/examples/quick/scenegraph/openglunderqml/squircle.h index 449e02bbf1..aa50908242 100644 --- a/examples/quick/scenegraph/openglunderqml/squircle.h +++ b/examples/quick/scenegraph/openglunderqml/squircle.h @@ -45,11 +45,32 @@ #include #include + + +//! [1] +class SquircleRenderer : public QObject { + Q_OBJECT +public: + SquircleRenderer() : m_t(0), m_program(0) { } + ~SquircleRenderer(); + + void setT(qreal t) { m_t = t; } + void setViewportSize(const QSize &size) { m_viewportSize = size; } + +public slots: + void paint(); + +private: + QSize m_viewportSize; + qreal m_t; + QOpenGLShaderProgram *m_program; +}; //! [1] + +//! [2] class Squircle : public QQuickItem { Q_OBJECT - Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged) public: @@ -62,19 +83,16 @@ signals: void tChanged(); public slots: - void paint(); - void cleanup(); void sync(); + void cleanup(); private slots: void handleWindowChanged(QQuickWindow *win); private: - QOpenGLShaderProgram *m_program; - qreal m_t; - qreal m_thread_t; + SquircleRenderer *m_renderer; }; -//! [1] +//! [2] #endif // SQUIRCLE_H -- cgit v1.2.3