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"