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 | |
65 | QT_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 | |
71 | extern 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 | |
75 | static 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 | |
86 | QSGWindowsRenderLoop::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 | |
108 | QSGWindowsRenderLoop::~QSGWindowsRenderLoop() |
109 | { |
110 | delete m_rc; |
111 | delete m_sg; |
112 | } |
113 | |
114 | bool QSGWindowsRenderLoop::interleaveIncubation() const |
115 | { |
116 | return m_animationDriver->isRunning() && anyoneShowing(); |
117 | } |
118 | |
119 | QSGWindowsRenderLoop::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 | |
129 | void 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 | */ |
141 | void 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 | |
154 | void 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 | |
164 | void 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 | |
214 | void 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 | |
229 | void 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 | |
277 | bool 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 | |
285 | void 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 | |
317 | void 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 | |
327 | QImage 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 | |
346 | void QSGWindowsRenderLoop::update(QQuickWindow *window) |
347 | { |
348 | RLDEBUG("update" ); |
349 | maybeUpdate(window); |
350 | } |
351 | |
352 | void 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 | |
364 | QSGRenderContext *QSGWindowsRenderLoop::createRenderContext(QSGContext *) const |
365 | { |
366 | return m_rc; |
367 | } |
368 | |
369 | bool 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 | */ |
395 | void 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 | */ |
447 | void 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 | |
541 | void 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 | |
550 | QT_END_NAMESPACE |
551 | |
552 | #include "moc_qsgwindowsrenderloop_p.cpp" |
553 | |