An error occurred while loading the file. Please try again.
-
Gunnar Sletta authored
This API is primarily a hook which is needed by the Qt WebEngine to set up sharing with the scene graph's OpenGL contexts. Change-Id: I5bb03abd9ab99f502db8e413fe838a8b30365b8d Reviewed-by:
Jocelyn Turcotte <jocelyn.turcotte@digia.com>
5b05a78d
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtQuick 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();
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
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_rc = new QSGRenderContext(m_sg);
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
}
QSGWindowsRenderLoop::~QSGWindowsRenderLoop()
{
delete m_rc;
delete m_sg;
}
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()
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
{
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());
if (QSGContext::sharedOpenGLContext())
m_gl->setShareContext(QSGContext::sharedOpenGLContext());
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);
QQuickWindowPrivate::get(window)->context->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;
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
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) {
QQuickWindowPrivate::get(window)->context->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) {
QQuickWindowPrivate::get(window)->context->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()) {
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
// 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)
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
{
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.
*/
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
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