diff options
Diffstat (limited to 'examples/quick/scenegraph/opengltextureinthread/threadrenderer.cpp')
-rw-r--r-- | examples/quick/scenegraph/opengltextureinthread/threadrenderer.cpp | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/examples/quick/scenegraph/opengltextureinthread/threadrenderer.cpp b/examples/quick/scenegraph/opengltextureinthread/threadrenderer.cpp new file mode 100644 index 0000000000..8c07785926 --- /dev/null +++ b/examples/quick/scenegraph/opengltextureinthread/threadrenderer.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** 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 <QOpenGLContext> +#include <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. + GLuint id = 0; + m_texture = m_window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, &id, 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->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, &newId, 0, QSize(1, 1)); + 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" |