diff options
Diffstat (limited to 'src/quick/scenegraph/qsgrenderloop.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgrenderloop.cpp | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp new file mode 100644 index 0000000000..a55658bf71 --- /dev/null +++ b/src/quick/scenegraph/qsgrenderloop.cpp @@ -0,0 +1,382 @@ +/**************************************************************************** +** +** 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) { + if (!cd->persistentSceneGraph) { + sg->invalidate(); + if (!cd->persistentGLContext) { + delete gl; + gl = 0; + } + } + } +} + +void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window) +{ + hide(window); + if (m_windows.size() == 0) { + sg->invalidate(); + delete gl; + gl = 0; + } +} + +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; + } + + bool current = false; + + if (!gl) { + gl = new QOpenGLContext(); + gl->setFormat(masterWindow->requestedFormat()); + if (!gl->create()) { + delete gl; + gl = 0; + } + current = gl->makeCurrent(masterWindow); + if (current) + sg->initialize(gl); + } else { + current = gl->makeCurrent(masterWindow); + } + + bool alsoSwap = data.updatePending; + data.updatePending = false; + + if (!current) + return; + + 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()) { + m_windows[window].updatePending = true; + renderWindow(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 |