threadrenderer.cpp Example File | Qt Quick 5.12.12

- Qt 5.12
- Qt Quick
- Scene Graph - Rendering FBOs in a thread
- threadrenderer.cpp Example File
- Qt 5.12.12 Reference Documentation

# threadrenderer.cpp Example File

scenegraph/textureinthread/threadrenderer.cpp

     /****************************************************************************
     **
     ** Copyright (C) 2017 The Qt Company Ltd.
     ** Contact: https://www.qt.io/licensing/
     **
     ** This file is part of the examples of the Qt Toolkit.
     **
     ** $QT_BEGIN_LICENSE:BSD$
     ** Commercial License Usage
     ** Licensees holding valid commercial Qt licenses may use this file in
     ** accordance with the commercial license agreement provided with the
     ** Software or, alternatively, in accordance with the terms contained in
     ** a written agreement between you and The Qt Company. For licensing terms
     ** and conditions see https://www.qt.io/terms-conditions. For further
     ** information use the contact form at https://www.qt.io/contact-us.
     **
     ** BSD License Usage
     ** Alternatively, you may use this file under the terms of the BSD license
     ** as follows:
     **
     ** "Redistribution and use in source and binary forms, with or without
     ** modification, are permitted provided that the following conditions are
     ** met:
     **   * Redistributions of source code must retain the above copyright
     **     notice, this list of conditions and the following disclaimer.
     **   * Redistributions in binary form must reproduce the above copyright
     **     notice, this list of conditions and the following disclaimer in
     **     the documentation and/or other materials provided with the
     **     distribution.
     **   * Neither the name of The Qt Company Ltd nor the names of its
     **     contributors may be used to endorse or promote products derived
     **     from this software without specific prior written permission.
     **
     **
     ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
     **
     ** $QT_END_LICENSE$
     **
     ****************************************************************************/

     #include "threadrenderer.h"
     #include "logorenderer.h"

     #include <QtCore/QMutex>
     #include <QtCore/QThread>

     #include <QtGui/QOpenGLContext>
     #include <QtGui/QOpenGLFramebufferObject>
     #include <QtGui/QGuiApplication>
     #include <QtGui/QOffscreenSurface>

     #include <QtQuick/QQuickWindow>
     #include <qsgsimpletexturenode.h>

     QList<QThread *> ThreadRenderer::threads;

     /*
      * The render thread shares a context with the scene graph and will
      * render into two separate FBOs, one to use for display and one
      * to use for rendering
      */
     class RenderThread : public QThread
     {
         Q_OBJECT
     public:
         RenderThread(const QSize &size)
             : surface(nullptr)
             , context(nullptr)
             , m_renderFbo(nullptr)
             , m_displayFbo(nullptr)
             , m_logoRenderer(nullptr)
             , m_size(size)
         {
             ThreadRenderer::threads << this;
         }

         QOffscreenSurface *surface;
         QOpenGLContext *context;

     public slots:
         void renderNext()
         {
             context->makeCurrent(surface);

             if (!m_renderFbo) {
                 // Initialize the buffers and renderer
                 QOpenGLFramebufferObjectFormat format;
                 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
                 m_renderFbo = new QOpenGLFramebufferObject(m_size, format);
                 m_displayFbo = new QOpenGLFramebufferObject(m_size, format);
                 m_logoRenderer = new LogoRenderer();
                 m_logoRenderer->initialize();
             }

             m_renderFbo->bind();
             context->functions()->glViewport(0, 0, m_size.width(), m_size.height());

             m_logoRenderer->render();

             // We need to flush the contents to the FBO before posting
             // the texture to the other thread, otherwise, we might
             // get unexpected results.
             context->functions()->glFlush();

             m_renderFbo->bindDefault();
             qSwap(m_renderFbo, m_displayFbo);

             emit textureReady(m_displayFbo->texture(), m_size);
         }

         void shutDown()
         {
             context->makeCurrent(surface);
             delete m_renderFbo;
             delete m_displayFbo;
             delete m_logoRenderer;
             context->doneCurrent();
             delete context;

             // schedule this to be deleted only after we're done cleaning up
             surface->deleteLater();

             // Stop event processing, move the thread to GUI and make sure it is deleted.
             exit();
             moveToThread(QGuiApplication::instance()->thread());
         }

     signals:
         void textureReady(int id, const QSize &size);

     private:
         QOpenGLFramebufferObject *m_renderFbo;
         QOpenGLFramebufferObject *m_displayFbo;

         LogoRenderer *m_logoRenderer;
         QSize m_size;
     };

     class TextureNode : public QObject, public QSGSimpleTextureNode
     {
         Q_OBJECT

     public:
         TextureNode(QQuickWindow *window)
             : m_id(0)
             , m_size(0, 0)
             , m_texture(nullptr)
             , m_window(window)
         {
             // Our texture node must have a texture, so use the default 0 texture.
             m_texture = m_window->createTextureFromId(0, QSize(1, 1));
             setTexture(m_texture);
             setFiltering(QSGTexture::Linear);
         }

         ~TextureNode() override
         {
             delete m_texture;
         }

     signals:
         void textureInUse();
         void pendingNewTexture();

     public slots:

         // This function gets called on the FBO rendering thread and will store the
         // texture id and size and schedule an update on the window.
         void newTexture(int id, const QSize &size) {
             m_mutex.lock();
             m_id = id;
             m_size = size;
             m_mutex.unlock();

             // We cannot call QQuickWindow::update directly here, as this is only allowed
             // from the rendering thread or GUI thread.
             emit pendingNewTexture();
         }

         // Before the scene graph starts to render, we update to the pending texture
         void prepareNode() {
             m_mutex.lock();
             int newId = m_id;
             QSize size = m_size;
             m_id = 0;
             m_mutex.unlock();
             if (newId) {
                 delete m_texture;
                 // note: include QQuickWindow::TextureHasAlphaChannel if the rendered content
                 // has alpha.
                 m_texture = m_window->createTextureFromId(newId, size);
                 setTexture(m_texture);

                 markDirty(DirtyMaterial);

                 // This will notify the rendering thread that the texture is now being rendered
                 // and it can start rendering to the other one.
                 emit textureInUse();
             }
         }

     private:

         int m_id;
         QSize m_size;

         QMutex m_mutex;

         QSGTexture *m_texture;
         QQuickWindow *m_window;
     };

     ThreadRenderer::ThreadRenderer()
         : m_renderThread(nullptr)
     {
         setFlag(ItemHasContents, true);
         m_renderThread = new RenderThread(QSize(512, 512));
     }

     void ThreadRenderer::ready()
     {
         m_renderThread->surface = new QOffscreenSurface();
         m_renderThread->surface->setFormat(m_renderThread->context->format());
         m_renderThread->surface->create();

         m_renderThread->moveToThread(m_renderThread);

         connect(window(), &QQuickWindow::sceneGraphInvalidated, m_renderThread, &RenderThread::shutDown, Qt::QueuedConnection);

         m_renderThread->start();
         update();
     }

     QSGNode *ThreadRenderer::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
     {
         TextureNode *node = static_cast<TextureNode *>(oldNode);

         if (!m_renderThread->context) {
             QOpenGLContext *current = window()->openglContext();
             // Some GL implementations requres that the currently bound context is
             // made non-current before we set up sharing, so we doneCurrent here
             // and makeCurrent down below while setting up our own context.
             current->doneCurrent();

             m_renderThread->context = new QOpenGLContext();
             m_renderThread->context->setFormat(current->format());
             m_renderThread->context->setShareContext(current);
             m_renderThread->context->create();
             m_renderThread->context->moveToThread(m_renderThread);

             current->makeCurrent(window());

             QMetaObject::invokeMethod(this, "ready");
             return nullptr;
         }

         if (!node) {
             node = new TextureNode(window());

             /* Set up connections to get the production of FBO textures in sync with vsync on the
              * rendering thread.
              *
              * When a new texture is ready on the rendering thread, we use a direct connection to
              * the texture node to let it know a new texture can be used. The node will then
              * emit pendingNewTexture which we bind to QQuickWindow::update to schedule a redraw.
              *
              * When the scene graph starts rendering the next frame, the prepareNode() function
              * is used to update the node with the new texture. Once it completes, it emits
              * textureInUse() which we connect to the FBO rendering thread's renderNext() to have
              * it start producing content into its current "back buffer".
              *
              * This FBO rendering pipeline is throttled by vsync on the scene graph rendering thread.
              */
             connect(m_renderThread, &RenderThread::textureReady, node, &TextureNode::newTexture, Qt::DirectConnection);
             connect(node, &TextureNode::pendingNewTexture, window(), &QQuickWindow::update, Qt::QueuedConnection);
             connect(window(), &QQuickWindow::beforeRendering, node, &TextureNode::prepareNode, Qt::DirectConnection);
             connect(node, &TextureNode::textureInUse, m_renderThread, &RenderThread::renderNext, Qt::QueuedConnection);

             // Get the production of FBO textures started..
             QMetaObject::invokeMethod(m_renderThread, "renderNext", Qt::QueuedConnection);
         }

         node->setRect(boundingRect());

         return node;
     }

     #include "threadrenderer.moc"