| /**************************************************************************** |
| ** |
| ** 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" |