1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsgwindowsrenderloop_p.h"
41#include <QtCore/QCoreApplication>
42#include <QtCore/QLibraryInfo>
43#include <QtCore/QThread>
44
45#include <QtGui/QScreen>
46#include <QtGui/QGuiApplication>
47#include <QtGui/QOffscreenSurface>
48
49#include <QtQuick/private/qsgcontext_p.h>
50#include <QtQuick/private/qquickwindow_p.h>
51#include <QtQuick/private/qsgrenderer_p.h>
52#include <QtQuick/private/qsgdefaultrendercontext_p.h>
53
54#include <QtQuick/QQuickWindow>
55
56#include <private/qquickprofiler_p.h>
57#include <private/qquickanimatorcontroller_p.h>
58
59#if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl)
60#include <private/qquickopenglshadereffectnode_p.h>
61#endif
62
63QT_BEGIN_NAMESPACE
64
65// Single-threaded render loop with a custom animation driver. Like a
66// combination of basic+threaded but still working on the main thread. Only
67// compatible with direct OpenGL, no RHI support here.
68
69extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
70
71#define RLDEBUG(x) qCDebug(QSG_LOG_RENDERLOOP, x)
72
73static QElapsedTimer qsg_render_timer;
74#define QSG_LOG_TIME_SAMPLE(sampleName) \
75 qint64 sampleName = 0; \
76 if (QSG_LOG_TIME_RENDERLOOP().isDebugEnabled()) \
77 sampleName = qsg_render_timer.nsecsElapsed(); \
78
79#define QSG_RENDER_TIMING_SAMPLE(frameType, sampleName, position) \
80 QSG_LOG_TIME_SAMPLE(sampleName) \
81 Q_QUICK_SG_PROFILE_RECORD(frameType, position);
82
83
84QSGWindowsRenderLoop::QSGWindowsRenderLoop()
85 : m_gl(nullptr)
86 , m_sg(QSGContext::createDefaultContext())
87 , m_updateTimer(0)
88 , m_animationTimer(0)
89{
90 m_rc = static_cast<QSGDefaultRenderContext *>(m_sg->createRenderContext());
91
92 m_vsyncDelta = 1000 / QGuiApplication::primaryScreen()->refreshRate();
93 if (m_vsyncDelta <= 0)
94 m_vsyncDelta = 16;
95
96 RLDEBUG("Windows Render Loop created");
97
98 m_animationDriver = m_sg->createAnimationDriver(m_sg);
99 connect(m_animationDriver, SIGNAL(started()), this, SLOT(started()));
100 connect(m_animationDriver, SIGNAL(stopped()), this, SLOT(stopped()));
101 m_animationDriver->install();
102
103 qsg_render_timer.start();
104}
105
106QSGWindowsRenderLoop::~QSGWindowsRenderLoop()
107{
108 delete m_rc;
109 delete m_sg;
110}
111
112bool QSGWindowsRenderLoop::interleaveIncubation() const
113{
114 return m_animationDriver->isRunning() && anyoneShowing();
115}
116
117QSGWindowsRenderLoop::WindowData *QSGWindowsRenderLoop::windowData(QQuickWindow *window)
118{
119 for (int i=0; i<m_windows.size(); ++i) {
120 WindowData &wd = m_windows[i];
121 if (wd.window == window)
122 return &wd;
123 }
124 return nullptr;
125}
126
127void QSGWindowsRenderLoop::maybePostUpdateTimer()
128{
129 if (!m_updateTimer) {
130 RLDEBUG(" - posting event");
131 m_updateTimer = startTimer(m_vsyncDelta / 3);
132 }
133}
134
135/*
136 * If no windows are showing, start ticking animations using a timer,
137 * otherwise, start rendering
138 */
139void QSGWindowsRenderLoop::started()
140{
141 RLDEBUG("Animations started...");
142 if (!anyoneShowing()) {
143 if (m_animationTimer == 0) {
144 RLDEBUG(" - starting non-visual animation timer");
145 m_animationTimer = startTimer(m_vsyncDelta);
146 }
147 } else {
148 maybePostUpdateTimer();
149 }
150}
151
152void QSGWindowsRenderLoop::stopped()
153{
154 RLDEBUG("Animations stopped...");
155 if (m_animationTimer) {
156 RLDEBUG(" - stopping non-visual animation timer");
157 killTimer(m_animationTimer);
158 m_animationTimer = 0;
159 }
160}
161
162void QSGWindowsRenderLoop::show(QQuickWindow *window)
163{
164 RLDEBUG("show");
165 if (windowData(window) != nullptr)
166 return;
167
168 // This happens before the platform window is shown, but after
169 // it is created. Creating the GL context takes a lot of time
170 // (hundreds of milliseconds) and will prevent us from rendering
171 // the first frame in time for the initial show on screen.
172 // By preparing the GL context here, it is feasible (if the app
173 // is quick enough) to have a perfect first frame.
174 if (!m_gl) {
175 RLDEBUG(" - creating GL context");
176 m_gl = new QOpenGLContext();
177 m_gl->setFormat(window->requestedFormat());
178 m_gl->setScreen(window->screen());
179 if (qt_gl_global_share_context())
180 m_gl->setShareContext(qt_gl_global_share_context());
181 bool created = m_gl->create();
182 if (!created) {
183 const bool isEs = m_gl->isOpenGLES();
184 delete m_gl;
185 m_gl = nullptr;
186 handleContextCreationFailure(window, isEs);
187 return;
188 }
189
190 QQuickWindowPrivate::get(window)->fireOpenGLContextCreated(m_gl);
191
192 RLDEBUG(" - making current");
193 bool current = m_gl->makeCurrent(window);
194 RLDEBUG(" - initializing SG");
195 if (current) {
196 QSGDefaultRenderContext::InitParams rcParams;
197 rcParams.sampleCount = qMax(1, m_gl->format().samples());
198 rcParams.openGLContext = m_gl;
199 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
200 rcParams.maybeSurface = window;
201 m_rc->initialize(&rcParams);
202 }
203 }
204
205 WindowData data;
206 data.window = window;
207 data.pendingUpdate = false;
208 m_windows << data;
209
210 RLDEBUG(" - done with show");
211}
212
213void QSGWindowsRenderLoop::hide(QQuickWindow *window)
214{
215 RLDEBUG("hide");
216 // The expose event is queued while hide is sent synchronously, so
217 // the value might not be updated yet. (plus that the windows plugin
218 // sends exposed=true when it goes to hidden, so it is doubly broken)
219 // The check is made here, after the removal from m_windows, so
220 // anyoneShowing will report the right value.
221 if (window->isExposed())
222 handleObscurity();
223 if (!m_gl)
224 return;
225 QQuickWindowPrivate::get(window)->fireAboutToStop();
226}
227
228void QSGWindowsRenderLoop::windowDestroyed(QQuickWindow *window)
229{
230 RLDEBUG("windowDestroyed");
231 for (int i=0; i<m_windows.size(); ++i) {
232 if (m_windows.at(i).window == window) {
233 m_windows.removeAt(i);
234 break;
235 }
236 }
237
238 hide(window);
239
240 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
241
242 bool current = false;
243 QScopedPointer<QOffscreenSurface> offscreenSurface;
244 if (m_gl) {
245 QSurface *surface = window;
246 // There may be no platform window if the window got closed.
247 if (!window->handle()) {
248 offscreenSurface.reset(new QOffscreenSurface);
249 offscreenSurface->setFormat(m_gl->format());
250 offscreenSurface->create();
251 surface = offscreenSurface.data();
252 }
253 current = m_gl->makeCurrent(surface);
254 }
255 if (Q_UNLIKELY(!current))
256 RLDEBUG("cleanup without an OpenGL context");
257
258#if QT_CONFIG(quick_shadereffect) && QT_CONFIG(opengl)
259 if (current)
260 QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache();
261#endif
262
263 d->cleanupNodesOnShutdown();
264 if (m_windows.size() == 0) {
265 d->context->invalidate();
266 delete m_gl;
267 m_gl = nullptr;
268 } else if (m_gl && current) {
269 m_gl->doneCurrent();
270 }
271
272 delete d->animationController;
273}
274
275bool QSGWindowsRenderLoop::anyoneShowing() const
276{
277 for (const WindowData &wd : qAsConst(m_windows))
278 if (wd.window->isVisible() && wd.window->isExposed() && wd.window->size().isValid())
279 return true;
280 return false;
281}
282
283void QSGWindowsRenderLoop::exposureChanged(QQuickWindow *window)
284{
285
286 if (windowData(window) == nullptr)
287 return;
288
289 if (window->isExposed() && window->isVisible()) {
290
291 // Stop non-visual animation timer as we now have a window rendering
292 if (m_animationTimer && anyoneShowing()) {
293 RLDEBUG(" - stopping non-visual animation timer");
294 killTimer(m_animationTimer);
295 m_animationTimer = 0;
296 }
297
298 RLDEBUG("exposureChanged - exposed");
299 WindowData *wd = windowData(window);
300 wd->pendingUpdate = true;
301
302 // If we have a pending timer and we get an expose, we need to stop it.
303 // Otherwise we get two frames and two animation ticks in the same time-interval.
304 if (m_updateTimer) {
305 RLDEBUG(" - killing pending update timer");
306 killTimer(m_updateTimer);
307 m_updateTimer = 0;
308 }
309 render();
310 } else {
311 handleObscurity();
312 }
313}
314
315void QSGWindowsRenderLoop::handleObscurity()
316{
317 RLDEBUG("handleObscurity");
318 // Potentially start the non-visual animation timer if nobody is rendering
319 if (m_animationDriver->isRunning() && !anyoneShowing() && !m_animationTimer) {
320 RLDEBUG(" - starting non-visual animation timer");
321 m_animationTimer = startTimer(m_vsyncDelta);
322 }
323}
324
325QImage QSGWindowsRenderLoop::grab(QQuickWindow *window)
326{
327 RLDEBUG("grab");
328 if (!m_gl)
329 return QImage();
330
331 m_gl->makeCurrent(window);
332
333 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
334 d->polishItems();
335 d->syncSceneGraph();
336 d->renderSceneGraph(window->size());
337
338 bool alpha = window->format().alphaBufferSize() > 0 && window->color().alpha() != 255;
339 QImage image = qt_gl_read_framebuffer(window->size() * window->effectiveDevicePixelRatio(), alpha, alpha);
340 image.setDevicePixelRatio(window->effectiveDevicePixelRatio());
341 return image;
342}
343
344void QSGWindowsRenderLoop::update(QQuickWindow *window)
345{
346 RLDEBUG("update");
347 maybeUpdate(window);
348}
349
350void QSGWindowsRenderLoop::maybeUpdate(QQuickWindow *window)
351{
352 RLDEBUG("maybeUpdate");
353
354 WindowData *wd = windowData(window);
355 if (!wd || !anyoneShowing())
356 return;
357
358 wd->pendingUpdate = true;
359 maybePostUpdateTimer();
360}
361
362QSGRenderContext *QSGWindowsRenderLoop::createRenderContext(QSGContext *) const
363{
364 return m_rc;
365}
366
367bool QSGWindowsRenderLoop::event(QEvent *event)
368{
369 switch (event->type()) {
370 case QEvent::Timer: {
371 QTimerEvent *te = static_cast<QTimerEvent *>(event);
372 if (te->timerId() == m_animationTimer) {
373 RLDEBUG("event : animation tick while nothing is showing");
374 m_animationDriver->advance();
375 } else if (te->timerId() == m_updateTimer) {
376 RLDEBUG("event : update");
377 killTimer(m_updateTimer);
378 m_updateTimer = 0;
379 render();
380 }
381 return true; }
382 default:
383 break;
384 }
385
386 return QObject::event(event);
387}
388
389/*
390 * Go through all windows we control and render them in turn.
391 * Then tick animations if active.
392 */
393void QSGWindowsRenderLoop::render()
394{
395 RLDEBUG("render");
396 bool rendered = false;
397 for (const WindowData &wd : qAsConst(m_windows)) {
398 if (wd.pendingUpdate) {
399 const_cast<WindowData &>(wd).pendingUpdate = false;
400 renderWindow(wd.window);
401 rendered = true;
402 }
403 }
404
405 if (!rendered) {
406 RLDEBUG("no changes, sleep");
407 QThread::msleep(m_vsyncDelta);
408 }
409
410 if (m_animationDriver->isRunning()) {
411 RLDEBUG("advancing animations");
412 QSG_LOG_TIME_SAMPLE(time_start);
413 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphWindowsAnimations);
414 m_animationDriver->advance();
415 RLDEBUG("animations advanced");
416
417 qCDebug(QSG_LOG_TIME_RENDERLOOP,
418 "animations ticked in %dms",
419 int((qsg_render_timer.nsecsElapsed() - time_start)/1000000));
420
421 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphWindowsAnimations, 1);
422
423 // It is not given that animations triggered another maybeUpdate()
424 // and thus another render pass, so to keep things running,
425 // make sure there is another frame pending.
426 maybePostUpdateTimer();
427
428 emit timeToIncubate();
429 }
430}
431
432/*
433 * Render the contents of this window. First polish, then sync, render
434 * then finally swap.
435 *
436 * Note: This render function does not implement aborting
437 * the render call when sync step results in no scene graph changes,
438 * like the threaded renderer does.
439 */
440void QSGWindowsRenderLoop::renderWindow(QQuickWindow *window)
441{
442 RLDEBUG("renderWindow");
443 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
444
445 if (!d->isRenderable())
446 return;
447
448 if (!m_gl->makeCurrent(window)) {
449 // Check for context loss.
450 if (!m_gl->isValid()) {
451 d->cleanupNodesOnShutdown();
452 m_rc->invalidate();
453 if (m_gl->create() && m_gl->makeCurrent(window)) {
454 QSGDefaultRenderContext::InitParams rcParams;
455 rcParams.sampleCount = qMax(1, m_gl->format().samples());
456 rcParams.openGLContext = m_gl;
457 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
458 rcParams.maybeSurface = window;
459 m_rc->initialize(&rcParams);
460 } else {
461 return;
462 }
463 }
464 }
465
466 bool lastDirtyWindow = true;
467 for (int i=0; i<m_windows.size(); ++i) {
468 if ( m_windows[i].pendingUpdate) {
469 lastDirtyWindow = false;
470 break;
471 }
472 }
473
474 d->flushFrameSynchronousEvents();
475 // Event delivery or processing has caused the window to stop rendering.
476 if (!windowData(window))
477 return;
478
479 QSG_LOG_TIME_SAMPLE(time_start);
480 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishFrame);
481
482 RLDEBUG(" - polishing");
483 d->polishItems();
484 QSG_LOG_TIME_SAMPLE(time_polished);
485 Q_QUICK_SG_PROFILE_SWITCH(QQuickProfiler::SceneGraphPolishFrame,
486 QQuickProfiler::SceneGraphRenderLoopFrame,
487 QQuickProfiler::SceneGraphPolishPolish);
488
489 emit window->afterAnimating();
490
491 RLDEBUG(" - syncing");
492 d->syncSceneGraph();
493 if (lastDirtyWindow)
494 m_rc->endSync();
495 QSG_RENDER_TIMING_SAMPLE(QQuickProfiler::SceneGraphRenderLoopFrame, time_synced,
496 QQuickProfiler::SceneGraphRenderLoopSync);
497
498 RLDEBUG(" - rendering");
499 d->renderSceneGraph(window->size());
500 QSG_RENDER_TIMING_SAMPLE(QQuickProfiler::SceneGraphRenderLoopFrame, time_rendered,
501 QQuickProfiler::SceneGraphRenderLoopRender);
502
503 RLDEBUG(" - swapping");
504 if (!d->customRenderStage || !d->customRenderStage->swap())
505 m_gl->swapBuffers(window);
506 QSG_RENDER_TIMING_SAMPLE(QQuickProfiler::SceneGraphRenderLoopFrame, time_swapped,
507 QQuickProfiler::SceneGraphRenderLoopSwap);
508
509 RLDEBUG(" - frameDone");
510 d->fireFrameSwapped();
511
512 qCDebug(QSG_LOG_TIME_RENDERLOOP()).nospace()
513 << "Frame rendered with 'windows' renderloop in: " << (time_swapped - time_start) / 1000000 << "ms"
514 << ", polish=" << (time_polished - time_start) / 1000000
515 << ", sync=" << (time_synced - time_polished) / 1000000
516 << ", render=" << (time_rendered - time_synced) / 1000000
517 << ", swap=" << (time_swapped - time_rendered) / 1000000
518 << " - " << window;
519
520 Q_QUICK_SG_PROFILE_REPORT(QQuickProfiler::SceneGraphRenderLoopFrame,
521 QQuickProfiler::SceneGraphRenderLoopSwap);
522}
523
524void QSGWindowsRenderLoop::releaseResources(QQuickWindow *w)
525{
526 // No full invalidation of the rendercontext, just clear some caches.
527 RLDEBUG("releaseResources");
528 QQuickWindowPrivate *d = QQuickWindowPrivate::get(w);
529 if (d->renderer)
530 d->renderer->releaseCachedResources();
531}
532
533QT_END_NAMESPACE
534
535#include "moc_qsgwindowsrenderloop_p.cpp"
536