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"