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

source code of qtdeclarative/src/quick/scenegraph/qsgwindowsrenderloop.cpp