aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgrenderloop.cpp
diff options
context:
space:
mode:
authorGunnar Sletta <gunnar.sletta@digia.com>2012-12-05 06:27:47 -0800
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-01-18 12:26:55 +0100
commitebe8b9408cfcd953fae80514aa67e49221541bed (patch)
treea9728a64f5f462dc45dabce591f67140738b0edc /src/quick/scenegraph/qsgrenderloop.cpp
parent9c54d0ef8f6442e32d5762edccef46db80b68681 (diff)
Complete rewrite of threaded render loop.
This change starts using the superior implementation of the scene graph render loop which has been worked on in the scenegraph-playground project for a while. It uses a far more straightforward locking/sync paradigm compared to the existing one and is less deadlock and error prone. It also enables the scene graph thread to run on its own when the GUI thread is blocked, enabling threaded animations. This changes also introduces a naming change inside Qt Quick from "Window Manager" -> "Render Loop" as that fits better to what the code does. Change-Id: I1c2170ee04fcbef79660bd7dae6cace647cdb276 Reviewed-by: Samuel Rødal <samuel.rodal@digia.com>
Diffstat (limited to 'src/quick/scenegraph/qsgrenderloop.cpp')
-rw-r--r--src/quick/scenegraph/qsgrenderloop.cpp363
1 files changed, 363 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp
new file mode 100644
index 0000000000..9117b2cffa
--- /dev/null
+++ b/src/quick/scenegraph/qsgrenderloop.cpp
@@ -0,0 +1,363 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsgrenderloop_p.h"
+#include "qsgthreadedrenderloop_p.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QTime>
+#include <QtCore/private/qabstractanimation_p.h>
+
+#include <QtGui/QOpenGLContext>
+#include <QtGui/private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
+
+#include <QtQml/private/qqmlglobal_p.h>
+
+#include <QtQuick/QQuickWindow>
+#include <QtQuick/private/qquickwindow_p.h>
+#include <QtQuick/private/qsgcontext_p.h>
+
+QT_BEGIN_NAMESPACE
+
+DEFINE_BOOL_CONFIG_OPTION(qquick_render_timing, QML_RENDER_TIMING)
+
+extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
+
+/*!
+ expectations for this manager to work:
+ - one opengl context to render multiple windows
+ - OpenGL pipeline will not block for vsync in swap
+ - OpenGL pipeline will block based on a full buffer queue.
+ - Multiple screens can share the OpenGL context
+ - Animations are advanced for all windows once per swap
+ */
+
+DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP);
+DEFINE_BOOL_CONFIG_OPTION(qmlForceThreadedRenderer, QML_FORCE_THREADED_RENDERER); // Might trigger graphics driver threading bugs, use at own risk
+
+QSGRenderLoop *QSGRenderLoop::s_instance = 0;
+
+QSGRenderLoop::~QSGRenderLoop()
+{
+}
+
+class QSGGuiThreadRenderLoop : public QObject, public QSGRenderLoop
+{
+ Q_OBJECT
+public:
+ QSGGuiThreadRenderLoop();
+
+ void show(QQuickWindow *window);
+ void hide(QQuickWindow *window);
+
+ void windowDestroyed(QQuickWindow *window);
+
+ void initializeGL();
+ void renderWindow(QQuickWindow *window);
+ void exposureChanged(QQuickWindow *window);
+ QImage grab(QQuickWindow *window);
+ void resize(QQuickWindow *window, const QSize &size);
+
+ void maybeUpdate(QQuickWindow *window);
+ void update(QQuickWindow *window) { maybeUpdate(window); } // identical for this implementation.
+
+ void releaseResources(QQuickWindow *) { }
+
+ QAnimationDriver *animationDriver() const { return 0; }
+
+ QSGContext *sceneGraphContext() const;
+
+ bool event(QEvent *);
+
+ struct WindowData {
+ bool updatePending : 1;
+ bool grabOnly : 1;
+ };
+
+ QHash<QQuickWindow *, WindowData> m_windows;
+
+ QOpenGLContext *gl;
+ QSGContext *sg;
+
+ QImage grabContent;
+
+ bool eventPending;
+};
+
+
+QSGRenderLoop *QSGRenderLoop::instance()
+{
+ if (!s_instance) {
+
+ s_instance = QSGContext::createWindowManager();
+
+ bool bufferQueuing = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::BufferQueueingOpenGL);
+#ifdef Q_OS_WIN
+ bool fancy = false; // QTBUG-28037
+#else
+ bool fancy = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL);
+#endif
+ if (qmlNoThreadedRenderer())
+ fancy = false;
+ else if (qmlForceThreadedRenderer())
+ fancy = true;
+
+ // Enable fixed animation steps...
+ QByteArray fixed = qgetenv("QML_FIXED_ANIMATION_STEP");
+ bool fixedAnimationSteps = bufferQueuing;
+ if (fixed == "no")
+ fixedAnimationSteps = false;
+ else if (fixed.length())
+ fixedAnimationSteps = true;
+ if (fixedAnimationSteps)
+ QUnifiedTimer::instance(true)->setConsistentTiming(true);
+
+ if (!s_instance) {
+ s_instance = fancy
+ ? (QSGRenderLoop*) new QSGThreadedRenderLoop
+ : (QSGRenderLoop*) new QSGGuiThreadRenderLoop;
+ }
+ }
+ return s_instance;
+}
+
+void QSGRenderLoop::setInstance(QSGRenderLoop *instance)
+{
+ Q_ASSERT(!s_instance);
+ s_instance = instance;
+}
+
+QSGGuiThreadRenderLoop::QSGGuiThreadRenderLoop()
+ : gl(0)
+ , eventPending(false)
+{
+ sg = QSGContext::createDefaultContext();
+}
+
+
+void QSGGuiThreadRenderLoop::show(QQuickWindow *window)
+{
+ WindowData data;
+ data.updatePending = false;
+ data.grabOnly = false;
+ m_windows[window] = data;
+
+ maybeUpdate(window);
+}
+
+void QSGGuiThreadRenderLoop::hide(QQuickWindow *window)
+{
+ if (!m_windows.contains(window))
+ return;
+
+ m_windows.remove(window);
+ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ cd->cleanupNodesOnShutdown();
+
+ if (m_windows.size() == 0) {
+ sg->invalidate();
+ delete gl;
+ gl = 0;
+ }
+}
+
+void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window)
+{
+ hide(window);
+}
+
+void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
+{
+ bool renderWithoutShowing = QQuickWindowPrivate::get(window)->renderWithoutShowing;
+ if ((!window->isExposed() && !renderWithoutShowing) || !m_windows.contains(window))
+ return;
+
+ WindowData &data = const_cast<WindowData &>(m_windows[window]);
+
+ QQuickWindow *masterWindow = 0;
+ if (!window->isVisible() && !renderWithoutShowing) {
+ // Find a "proper surface" to bind...
+ for (QHash<QQuickWindow *, WindowData>::const_iterator it = m_windows.constBegin();
+ it != m_windows.constEnd() && !masterWindow; ++it) {
+ if (it.key()->isVisible())
+ masterWindow = it.key();
+ }
+ } else {
+ masterWindow = window;
+ }
+
+ if (!masterWindow)
+ return;
+
+ if (!QQuickWindowPrivate::get(masterWindow)->isRenderable()) {
+ qWarning().nospace()
+ << "Unable to find a renderable master window "
+ << masterWindow << "when trying to render"
+ << window << " (" << window->geometry() << ").";
+ return;
+ }
+
+ if (!gl) {
+ gl = new QOpenGLContext();
+ gl->setFormat(masterWindow->requestedFormat());
+ gl->create();
+ if (!gl->makeCurrent(masterWindow))
+ qWarning("QQuickWindow: makeCurrent() failed...");
+ sg->initialize(gl);
+ } else {
+ gl->makeCurrent(masterWindow);
+ }
+
+ bool alsoSwap = data.updatePending;
+ data.updatePending = false;
+
+ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ cd->polishItems();
+
+ int renderTime = 0, syncTime = 0;
+ QTime renderTimer;
+ if (qquick_render_timing())
+ renderTimer.start();
+
+ cd->syncSceneGraph();
+
+ if (qquick_render_timing())
+ syncTime = renderTimer.elapsed();
+
+ cd->renderSceneGraph(window->size());
+
+ if (qquick_render_timing())
+ renderTime = renderTimer.elapsed() - syncTime;
+
+ if (data.grabOnly) {
+ grabContent = qt_gl_read_framebuffer(window->size(), false, false);
+ data.grabOnly = false;
+ }
+
+ if (alsoSwap && window->isVisible()) {
+ gl->swapBuffers(window);
+ cd->fireFrameSwapped();
+ }
+
+ if (qquick_render_timing()) {
+ static QTime lastFrameTime = QTime::currentTime();
+ const int swapTime = renderTimer.elapsed() - renderTime - syncTime;
+ qDebug() << "- Breakdown of frame time; sync:" << syncTime
+ << "ms render:" << renderTime << "ms swap:" << swapTime
+ << "ms total:" << swapTime + renderTime + syncTime
+ << "ms time since last frame:" << (lastFrameTime.msecsTo(QTime::currentTime()))
+ << "ms";
+ lastFrameTime = QTime::currentTime();
+ }
+
+ // Might have been set during syncSceneGraph()
+ if (data.updatePending)
+ maybeUpdate(window);
+}
+
+void QSGGuiThreadRenderLoop::exposureChanged(QQuickWindow *window)
+{
+ if (window->isExposed())
+ maybeUpdate(window);
+}
+
+QImage QSGGuiThreadRenderLoop::grab(QQuickWindow *window)
+{
+ if (!m_windows.contains(window))
+ return QImage();
+
+ m_windows[window].grabOnly = true;
+
+ renderWindow(window);
+
+ QImage grabbed = grabContent;
+ grabContent = QImage();
+ return grabbed;
+}
+
+
+
+void QSGGuiThreadRenderLoop::resize(QQuickWindow *, const QSize &)
+{
+}
+
+
+
+void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window)
+{
+ if (!m_windows.contains(window))
+ return;
+
+ m_windows[window].updatePending = true;
+
+ if (!eventPending) {
+ QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+ eventPending = true;
+ }
+}
+
+
+
+QSGContext *QSGGuiThreadRenderLoop::sceneGraphContext() const
+{
+ return sg;
+}
+
+
+bool QSGGuiThreadRenderLoop::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ eventPending = false;
+ for (QHash<QQuickWindow *, WindowData>::const_iterator it = m_windows.constBegin();
+ it != m_windows.constEnd(); ++it) {
+ const WindowData &data = it.value();
+ if (data.updatePending)
+ renderWindow(it.key());
+ }
+ return true;
+ }
+ return QObject::event(e);
+}
+
+#include "qsgrenderloop.moc"
+
+QT_END_NAMESPACE