aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/qsgwindowsrenderloop.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/scenegraph/qsgwindowsrenderloop.cpp')
-rw-r--r--src/quick/scenegraph/qsgwindowsrenderloop.cpp469
1 files changed, 469 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgwindowsrenderloop.cpp b/src/quick/scenegraph/qsgwindowsrenderloop.cpp
new file mode 100644
index 0000000000..8e97f65ea5
--- /dev/null
+++ b/src/quick/scenegraph/qsgwindowsrenderloop.cpp
@@ -0,0 +1,469 @@
+/****************************************************************************
+**
+** 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 "qsgwindowsrenderloop_p.h"
+
+#include <QtCore/QCoreApplication>
+
+#include <QtGui/QScreen>
+#include <QtGui/QGuiApplication>
+
+#include <QtQuick/private/qsgcontext_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
+
+#include <QtQuick/QQuickWindow>
+
+#include <private/qqmlprofilerservice_p.h>
+
+QT_BEGIN_NAMESPACE
+
+extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
+
+// #define QSG_RENDER_LOOP_DEBUG
+
+#ifdef QSG_RENDER_LOOP_DEBUG
+static QElapsedTimer qsg_debug_timer;
+# define RLDEBUG(x) printf("(%6d) %s : %4d - %s\n", (int) qsg_debug_timer.elapsed(), __FILE__, __LINE__, x)
+#else
+# define RLDEBUG(x)
+#endif
+
+#ifndef QSG_NO_RENDER_TIMING
+static bool qsg_render_timing = !qgetenv("QSG_RENDER_TIMING").isEmpty();
+static QElapsedTimer qsg_render_timer;
+#define QSG_RENDER_TIMING_SAMPLE(sampleName) qint64 sampleName = 0; if (qsg_render_timing || QQmlProfilerService::enabled) sampleName = qsg_render_timer.nsecsElapsed()
+#else
+#define QSG_RENDER_TIMING_SAMPLE(sampleName)
+#endif
+
+
+QSGWindowsRenderLoop::QSGWindowsRenderLoop()
+ : m_gl(0)
+ , m_sg(QSGContext::createDefaultContext())
+ , m_updateTimer(0)
+ , m_animationTimer(0)
+{
+#ifdef QSG_RENDER_LOOP_DEBUG
+ qsg_debug_timer.start();
+#endif
+
+ m_animationDriver = m_sg->createAnimationDriver(m_sg);
+ m_animationDriver->install();
+
+ connect(m_animationDriver, SIGNAL(started()), this, SLOT(started()));
+ connect(m_animationDriver, SIGNAL(stopped()), this, SLOT(stopped()));
+
+ m_vsyncDelta = 1000 / QGuiApplication::primaryScreen()->refreshRate();
+ if (m_vsyncDelta <= 0)
+ m_vsyncDelta = 16;
+
+ RLDEBUG("Windows Render Loop created");
+
+#ifndef QSG_NO_RENDER_TIMIMG
+ qsg_render_timer.start();
+#endif
+}
+
+bool QSGWindowsRenderLoop::interleaveIncubation() const
+{
+ return m_animationDriver->isRunning() && anyoneShowing();
+}
+
+QSGWindowsRenderLoop::WindowData *QSGWindowsRenderLoop::windowData(QQuickWindow *window)
+{
+ for (int i=0; i<m_windows.size(); ++i) {
+ WindowData &wd = m_windows[i];
+ if (wd.window == window)
+ return &wd;
+ }
+ return 0;
+}
+
+void QSGWindowsRenderLoop::maybePostUpdateTimer()
+{
+ if (!m_updateTimer) {
+ RLDEBUG(" - posting event");
+ m_updateTimer = startTimer(m_vsyncDelta / 3);
+ }
+}
+
+/*
+ * If no windows are showing, start ticking animations using a timer,
+ * otherwise, start rendering
+ */
+void QSGWindowsRenderLoop::started()
+{
+ RLDEBUG("Animations started...");
+ if (!anyoneShowing()) {
+ if (m_animationTimer == 0) {
+ RLDEBUG(" - starting non-visual animation timer");
+ m_animationTimer = startTimer(m_vsyncDelta);
+ }
+ } else {
+ maybePostUpdateTimer();
+ }
+}
+
+void QSGWindowsRenderLoop::stopped()
+{
+ RLDEBUG("Animations stopped...");
+ if (m_animationTimer) {
+ RLDEBUG(" - stopping non-visual animation timer");
+ killTimer(m_animationTimer);
+ m_animationTimer = 0;
+ }
+}
+
+void QSGWindowsRenderLoop::show(QQuickWindow *window)
+{
+ RLDEBUG("show");
+ if (windowData(window) != 0)
+ return;
+
+ // This happens before the platform window is shown, but after
+ // it is created. Creating the GL context takes a lot of time
+ // (hundreds of milliseconds) and will prevent us from rendering
+ // the first frame in time for the initial show on screen.
+ // By preparing the GL context here, it is feasible (if the app
+ // is quick enough) to have a perfect first frame.
+ if (!m_gl) {
+ QSG_RENDER_TIMING_SAMPLE(time_start);
+
+ RLDEBUG(" - creating GL context");
+ m_gl = new QOpenGLContext();
+ m_gl->setFormat(window->requestedFormat());
+ m_gl->create();
+ QSG_RENDER_TIMING_SAMPLE(time_created);
+ RLDEBUG(" - making current");
+ m_gl->makeCurrent(window);
+ RLDEBUG(" - initializing SG");
+ QSG_RENDER_TIMING_SAMPLE(time_current);
+ m_sg->initialize(m_gl);
+
+#ifndef QSG_NO_RENDER_TIMING
+ if (qsg_render_timing) {
+ qDebug("WindowsRenderLoop: GL=%d ms, makeCurrent=%d ms, SG=%d ms",
+ int((time_created - time_start)/1000000),
+ int((time_current - time_created)/1000000),
+ int((qsg_render_timer.nsecsElapsed() - time_current)/1000000));
+ }
+ if (QQmlProfilerService::enabled) {
+ QQmlProfilerService::sceneGraphFrame(
+ QQmlProfilerService::SceneGraphWindowsRenderShow,
+ time_created - time_start,
+ time_current - time_created,
+ qsg_render_timer.nsecsElapsed() - time_current);
+ }
+#endif
+
+ }
+
+ WindowData data;
+ data.window = window;
+ data.pendingUpdate = false;
+ m_windows << data;
+
+ RLDEBUG(" - done with show");
+}
+
+void QSGWindowsRenderLoop::hide(QQuickWindow *window)
+{
+ RLDEBUG("hide");
+
+ for (int i=0; i<m_windows.size(); ++i) {
+ if (m_windows.at(i).window == window) {
+ m_windows.removeAt(i);
+ break;
+ }
+ }
+
+ // The expose event is queued while hide is sent synchronously, so
+ // the value might not be updated yet. (plus that the windows plugin
+ // sends exposed=true when it goes to hidden, so it is doubly broken)
+ // The check is made here, after the removal from m_windows, so
+ // anyoneShowing will report the right value.
+ if (window->isExposed())
+ handleObscurity();
+
+ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ cd->cleanupNodesOnShutdown();
+
+ // If this is the last tracked window, check for persistent SG and GL and
+ // potentially clean up.
+ if (m_windows.size() == 0) {
+ if (!cd->persistentSceneGraph) {
+ m_sg->invalidate();
+ if (!cd->persistentGLContext) {
+ delete m_gl;
+ m_gl = 0;
+ }
+ }
+ }
+}
+
+void QSGWindowsRenderLoop::windowDestroyed(QQuickWindow *window)
+{
+ RLDEBUG("windowDestroyed");
+ hide(window);
+
+ // If this is the last tracked window, clean up SG and GL.
+ if (m_windows.size() == 0) {
+ m_sg->invalidate();
+ delete m_gl;
+ m_gl = 0;
+ }
+}
+
+bool QSGWindowsRenderLoop::anyoneShowing() const
+{
+ foreach (const WindowData &wd, m_windows)
+ if (wd.window->isExposed() && wd.window->size().isValid())
+ return true;
+ return false;
+}
+
+void QSGWindowsRenderLoop::exposureChanged(QQuickWindow *window)
+{
+
+ if (windowData(window) == 0)
+ return;
+
+ if (window->isExposed()) {
+
+ // Stop non-visual animation timer as we now have a window rendering
+ if (m_animationTimer && anyoneShowing()) {
+ RLDEBUG(" - stopping non-visual animation timer");
+ killTimer(m_animationTimer);
+ m_animationTimer = 0;
+ }
+
+ RLDEBUG("exposureChanged - exposed");
+ WindowData *wd = windowData(window);
+ wd->pendingUpdate = true;
+
+ // If we have a pending timer and we get an expose, we need to stop it.
+ // Otherwise we get two frames and two animation ticks in the same time-interval.
+ if (m_updateTimer) {
+ RLDEBUG(" - killing pending update timer");
+ killTimer(m_updateTimer);
+ m_updateTimer = 0;
+ }
+ render();
+ } else {
+ handleObscurity();
+ }
+}
+
+void QSGWindowsRenderLoop::handleObscurity()
+{
+ RLDEBUG("handleObscurity");
+ // Potentially start the non-visual animation timer if nobody is rendering
+ if (m_animationDriver->isRunning() && !anyoneShowing() && !m_animationTimer) {
+ RLDEBUG(" - starting non-visual animation timer");
+ m_animationTimer = startTimer(m_vsyncDelta);
+ }
+}
+
+QImage QSGWindowsRenderLoop::grab(QQuickWindow *window)
+{
+ RLDEBUG("grab");
+ if (!m_gl)
+ return QImage();
+
+ m_gl->makeCurrent(window);
+
+ QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+ d->polishItems();
+ d->syncSceneGraph();
+ d->renderSceneGraph(window->size());
+
+ QImage image = qt_gl_read_framebuffer(window->size(), false, false);
+ return image;
+}
+
+void QSGWindowsRenderLoop::update(QQuickWindow *window)
+{
+ RLDEBUG("update");
+ maybeUpdate(window);
+}
+
+void QSGWindowsRenderLoop::maybeUpdate(QQuickWindow *window)
+{
+ RLDEBUG("maybeUpdate");
+
+ WindowData *wd = windowData(window);
+ if (!wd || !anyoneShowing())
+ return;
+
+ wd->pendingUpdate = true;
+ maybePostUpdateTimer();
+}
+
+bool QSGWindowsRenderLoop::event(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::Timer: {
+ QTimerEvent *te = static_cast<QTimerEvent *>(event);
+ if (te->timerId() == m_animationTimer) {
+ RLDEBUG("event : animation tick while nothing is showing");
+ m_animationDriver->advance();
+ } else if (te->timerId() == m_updateTimer) {
+ RLDEBUG("event : update");
+ killTimer(m_updateTimer);
+ m_updateTimer = 0;
+ render();
+ }
+ return true; }
+ default:
+ break;
+ }
+
+ return QObject::event(event);
+}
+
+/*
+ * Go through all windows we control and render them in turn.
+ * Then tick animations if active.
+ */
+void QSGWindowsRenderLoop::render()
+{
+ RLDEBUG("render");
+ foreach (const WindowData &wd, m_windows) {
+ if (wd.pendingUpdate) {
+ const_cast<WindowData &>(wd).pendingUpdate = false;
+ renderWindow(wd.window);
+ }
+ }
+
+ if (m_animationDriver->isRunning()) {
+ RLDEBUG("advancing animations");
+ QSG_RENDER_TIMING_SAMPLE(time_start);
+ m_animationDriver->advance();
+ RLDEBUG("animations advanced");
+
+#ifndef QSG_NO_RENDER_TIMING
+ if (qsg_render_timing) {
+ qDebug("WindowsRenderLoop: animations=%d ms",
+ int((qsg_render_timer.nsecsElapsed() - time_start)/1000000));
+ }
+ if (QQmlProfilerService::Enabled) {
+ QQmlProfilerService::sceneGraphFrame(
+ QQmlProfilerService::SceneGraphWindowsAnimations,
+ qsg_render_timer.nsecsElapsed() - time_start);
+ }
+#endif
+
+ // It is not given that animations triggered another maybeUpdate()
+ // and thus another render pass, so to keep things running,
+ // make sure there is another frame pending.
+ maybePostUpdateTimer();
+
+ emit timeToIncubate();
+ }
+}
+
+/*
+ * Render the contents of this window. First polish, then sync, render
+ * then finally swap.
+ *
+ * Note: This render function does not implement aborting
+ * the render call when sync step results in no scene graph changes,
+ * like the threaded renderer does.
+ */
+void QSGWindowsRenderLoop::renderWindow(QQuickWindow *window)
+{
+ RLDEBUG("renderWindow");
+ QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
+
+ if (!d->isRenderable())
+ return;
+
+ if (!m_gl->makeCurrent(window))
+ return;
+
+ QSG_RENDER_TIMING_SAMPLE(time_start);
+
+ RLDEBUG(" - polishing");
+ d->polishItems();
+ QSG_RENDER_TIMING_SAMPLE(time_polished);
+
+ RLDEBUG(" - syncing");
+ d->syncSceneGraph();
+ QSG_RENDER_TIMING_SAMPLE(time_synced);
+
+ RLDEBUG(" - rendering");
+ d->renderSceneGraph(window->size());
+ QSG_RENDER_TIMING_SAMPLE(time_rendered);
+
+ RLDEBUG(" - swapping");
+ m_gl->swapBuffers(window);
+ QSG_RENDER_TIMING_SAMPLE(time_swapped);
+
+ RLDEBUG(" - frameDone");
+ d->fireFrameSwapped();
+
+#ifndef QSG_NO_RENDER_TIMING
+ if (qsg_render_timing) {
+ qDebug("WindowsRenderLoop(t=%d): window=%p, polish=%d ms, sync=%d ms, render=%d ms, swap=%d ms",
+ int(qsg_render_timer.elapsed()),
+ window,
+ int((time_polished - time_start)/1000000),
+ int((time_synced - time_polished)/1000000),
+ int((time_rendered - time_synced)/1000000),
+ int((time_swapped - time_rendered)/1000000));
+ }
+ if (QQmlProfilerService::enabled) {
+ QQmlProfilerService::sceneGraphFrame(
+ QQmlProfilerService::SceneGraphWindowsPolishFrame,
+ time_polished - time_start
+ );
+
+ QQmlProfilerService::sceneGraphFrame(
+ QQmlProfilerService::SceneGraphRenderLoopFrame,
+ time_synced - time_polished,
+ time_rendered - time_synced,
+ time_swapped - time_rendered
+ );
+ }
+#endif
+}
+
+QT_END_NAMESPACE