1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include <QtQuick/private/qsgcontext_p.h> |
5 | #include <QtQuick/private/qsgtexture_p.h> |
6 | #include <QtQuick/private/qsgrenderer_p.h> |
7 | #include <QtQuick/private/qquickpixmapcache_p.h> |
8 | #include <QtQuick/private/qsgadaptationlayer_p.h> |
9 | |
10 | #include <QGuiApplication> |
11 | #include <QScreen> |
12 | #include <QQuickWindow> |
13 | |
14 | #include <private/qqmlglobal_p.h> |
15 | |
16 | #include <QtQuick/private/qsgtexture_p.h> |
17 | #include <QtGui/private/qguiapplication_p.h> |
18 | #include <QtCore/private/qabstractanimation_p.h> |
19 | |
20 | #include <private/qobject_p.h> |
21 | #include <qmutex.h> |
22 | |
23 | /* |
24 | Comments about this class from Gunnar: |
25 | |
26 | The QSGContext class is right now two things.. The first is the |
27 | adaptation layer and central storage ground for all the things |
28 | in the scene graph, like textures and materials. This part really |
29 | belongs inside the scene graph coreapi. |
30 | |
31 | The other part is the QML adaptation classes, like how to implement |
32 | rectangle nodes. This is not part of the scene graph core API, but |
33 | more part of the QML adaptation of scene graph. |
34 | |
35 | If we ever move the scene graph core API into its own thing, this class |
36 | needs to be split in two. Right now its one because we're lazy when it comes |
37 | to defining plugin interfaces.. |
38 | */ |
39 | |
40 | QT_BEGIN_NAMESPACE |
41 | |
42 | // Used for very high-level info about the renderering and gl context |
43 | // Includes GL_VERSION, type of render loop, atlas size, etc. |
44 | Q_LOGGING_CATEGORY(QSG_LOG_INFO, "qt.scenegraph.general" ) |
45 | |
46 | // Used to debug the renderloop logic. Primarily useful for platform integrators |
47 | // and when investigating the render loop logic. |
48 | Q_LOGGING_CATEGORY(QSG_LOG_RENDERLOOP, "qt.scenegraph.renderloop" ) |
49 | |
50 | |
51 | // GLSL shader compilation |
52 | Q_LOGGING_CATEGORY(QSG_LOG_TIME_COMPILATION, "qt.scenegraph.time.compilation" ) |
53 | |
54 | // polish, animations, sync, render and swap in the render loop |
55 | Q_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERLOOP, "qt.scenegraph.time.renderloop" ) |
56 | |
57 | // Texture uploads and swizzling |
58 | Q_LOGGING_CATEGORY(QSG_LOG_TIME_TEXTURE, "qt.scenegraph.time.texture" ) |
59 | |
60 | // Glyph preparation (only for distance fields atm) |
61 | Q_LOGGING_CATEGORY(QSG_LOG_TIME_GLYPH, "qt.scenegraph.time.glyph" ) |
62 | |
63 | // Timing inside the renderer base class |
64 | Q_LOGGING_CATEGORY(QSG_LOG_TIME_RENDERER, "qt.scenegraph.time.renderer" ) |
65 | |
66 | // Applicable for render loops that install their own animation driver, such as |
67 | // the 'threaded' loop. This env.var. is documented in the scenegraph docs. |
68 | DEFINE_BOOL_CONFIG_OPTION(useElapsedTimerBasedAnimationDriver, QSG_USE_SIMPLE_ANIMATION_DRIVER); |
69 | |
70 | bool qsg_useConsistentTiming() |
71 | { |
72 | int use = -1; |
73 | if (use < 0) { |
74 | use = !qEnvironmentVariableIsEmpty(varName: "QSG_FIXED_ANIMATION_STEP" ) && qgetenv(varName: "QSG_FIXED_ANIMATION_STEP" ) != "no" |
75 | ? 1 : 0; |
76 | qCDebug(QSG_LOG_INFO, "Using %s" , bool(use) ? "fixed animation steps" : "sg animation driver" ); |
77 | } |
78 | return bool(use); |
79 | } |
80 | |
81 | class QSGAnimationDriver : public QAnimationDriver |
82 | { |
83 | public: |
84 | QSGAnimationDriver(QObject *parent) |
85 | : QAnimationDriver(parent) |
86 | { |
87 | QScreen *screen = QGuiApplication::primaryScreen(); |
88 | if (screen) { |
89 | qreal refreshRate = screen->refreshRate(); |
90 | // To work around that some platforms wrongfully return 0 or something |
91 | // bogus for the refresh rate. |
92 | if (refreshRate < 1) |
93 | refreshRate = 60; |
94 | m_vsync = 1000.0f / float(refreshRate); |
95 | } else { |
96 | m_vsync = 16.67f; |
97 | } |
98 | } |
99 | |
100 | float vsyncInterval() const { return m_vsync; } |
101 | |
102 | virtual bool isVSyncDependent() const = 0; |
103 | |
104 | protected: |
105 | float m_vsync = 0; |
106 | }; |
107 | |
108 | // default as in default for the threaded render loop |
109 | class QSGDefaultAnimationDriver : public QSGAnimationDriver |
110 | { |
111 | Q_OBJECT |
112 | public: |
113 | enum Mode { |
114 | VSyncMode, |
115 | TimerMode |
116 | }; |
117 | |
118 | QSGDefaultAnimationDriver(QObject *parent) |
119 | : QSGAnimationDriver(parent) |
120 | , m_time(0) |
121 | , m_mode(VSyncMode) |
122 | , m_lag(0) |
123 | , m_bad(0) |
124 | , m_good(0) |
125 | { |
126 | QScreen *screen = QGuiApplication::primaryScreen(); |
127 | if (screen && !qsg_useConsistentTiming()) { |
128 | if (m_vsync <= 0) |
129 | m_mode = TimerMode; |
130 | } else { |
131 | m_mode = TimerMode; |
132 | if (qsg_useConsistentTiming()) |
133 | QUnifiedTimer::instance(create: true)->setConsistentTiming(true); |
134 | } |
135 | if (m_mode == VSyncMode) |
136 | qCDebug(QSG_LOG_INFO, "Animation Driver: using vsync: %.2f ms" , m_vsync); |
137 | else |
138 | qCDebug(QSG_LOG_INFO, "Animation Driver: using walltime" ); |
139 | } |
140 | |
141 | void start() override |
142 | { |
143 | m_time = 0; |
144 | m_timer.start(); |
145 | m_wallTime.restart(); |
146 | QAnimationDriver::start(); |
147 | } |
148 | |
149 | qint64 elapsed() const override |
150 | { |
151 | return m_mode == VSyncMode |
152 | ? qint64(m_time) |
153 | : qint64(m_time) + m_wallTime.elapsed(); |
154 | } |
155 | |
156 | void advance() override |
157 | { |
158 | qint64 delta = m_timer.restart(); |
159 | |
160 | if (m_mode == VSyncMode) { |
161 | // If a frame is skipped, either because rendering was slow or because |
162 | // the QML was slow, we accept it and continue advancing with a single |
163 | // vsync tick. The reason for this is that by the time we notice this |
164 | // on the GUI thread, the temporal distortion has already gone to screen |
165 | // and by catching up, we will introduce a second distortion which will |
166 | // worse. We accept that the animation time falls behind wall time because |
167 | // it comes out looking better. |
168 | // Only when multiple bad frames are hit in a row, do we consider |
169 | // switching. A few really bad frames and we switch right away. For frames |
170 | // just above the vsync delta, we tolerate a bit more since a buffered |
171 | // driver can have vsync deltas on the form: 4, 21, 21, 2, 23, 16, and |
172 | // still manage to put the frames to screen at 16 ms intervals. In addition |
173 | // to that, we tolerate a 25% margin of error on the value of m_vsync |
174 | // reported from the system as this value is often not precise. |
175 | |
176 | m_time += m_vsync; |
177 | |
178 | if (delta > m_vsync * 1.25f) { |
179 | m_lag += (delta / m_vsync); |
180 | m_bad++; |
181 | // We tolerate one bad frame without resorting to timer based. This is |
182 | // done to cope with a slow loader frame followed by smooth animation. |
183 | // However, on the second frame with massive lag, we switch. |
184 | if (m_lag > 10 && m_bad > 2) { |
185 | m_mode = TimerMode; |
186 | qCDebug(QSG_LOG_INFO, "animation driver switched to timer mode" ); |
187 | m_wallTime.restart(); |
188 | } |
189 | } else { |
190 | m_lag = 0; |
191 | m_bad = 0; |
192 | } |
193 | |
194 | } else { |
195 | if (delta < 1.25f * m_vsync) { |
196 | ++m_good; |
197 | } else { |
198 | m_good = 0; |
199 | } |
200 | |
201 | // We've been solid for a while, switch back to vsync mode. Tolerance |
202 | // for switching back is lower than switching to timer mode, as we |
203 | // want to stay in vsync mode as much as possible. |
204 | if (m_good > 10 && !qsg_useConsistentTiming()) { |
205 | m_time = elapsed(); |
206 | m_mode = VSyncMode; |
207 | m_bad = 0; |
208 | m_lag = 0; |
209 | qCDebug(QSG_LOG_INFO, "animation driver switched to vsync mode" ); |
210 | } |
211 | } |
212 | |
213 | advanceAnimation(); |
214 | } |
215 | |
216 | bool isVSyncDependent() const override |
217 | { |
218 | return true; |
219 | } |
220 | |
221 | double m_time; |
222 | Mode m_mode; |
223 | QElapsedTimer m_timer; |
224 | QElapsedTimer m_wallTime; |
225 | float m_lag; |
226 | int m_bad; |
227 | int m_good; |
228 | }; |
229 | |
230 | // Advance based on QElapsedTimer. (so like the TimerMode of QSGDefaultAnimationDriver) |
231 | // Does not depend on vsync-based throttling. |
232 | // |
233 | // NB this is not the same as not installing a QAnimationDriver: the built-in |
234 | // approach in QtCore is to rely on 16 ms timer events which are potentially a |
235 | // lot less accurate. |
236 | // |
237 | // This has the benefits of: |
238 | // - not needing any of the infrastructure for falling back to a |
239 | // QTimer when there are multiple windows, |
240 | // - needing no heuristics trying determine if vsync-based throttling |
241 | // is missing or broken, |
242 | // - being compatible with any kind of temporal drifts in vsync throttling |
243 | // which is reportedly happening in various environments and platforms |
244 | // still, |
245 | // - not being tied to the primary screen's refresh rate, i.e. this is |
246 | // correct even if the window is on some secondary screen with a |
247 | // different refresh rate, |
248 | // - not having to worry about the potential effects of variable refresh |
249 | // rate solutions, |
250 | // - render thread animators work correctly regardless of vsync. |
251 | // |
252 | // On the downside, some animations might appear less smooth (compared to the |
253 | // ideal single window case of QSGDefaultAnimationDriver). |
254 | // |
255 | class QSGElapsedTimerAnimationDriver : public QSGAnimationDriver |
256 | { |
257 | public: |
258 | QSGElapsedTimerAnimationDriver(QObject *parent) |
259 | : QSGAnimationDriver(parent) |
260 | { |
261 | qCDebug(QSG_LOG_INFO, "Animation Driver: using QElapsedTimer, thread %p %s" , |
262 | QThread::currentThread(), |
263 | QThread::currentThread() == qGuiApp->thread() ? "(gui/main thread)" : "(render thread)" ); |
264 | } |
265 | |
266 | void start() override |
267 | { |
268 | m_wallTime.restart(); |
269 | QAnimationDriver::start(); |
270 | } |
271 | |
272 | qint64 elapsed() const override |
273 | { |
274 | return m_wallTime.elapsed(); |
275 | } |
276 | |
277 | void advance() override |
278 | { |
279 | advanceAnimation(); |
280 | } |
281 | |
282 | bool isVSyncDependent() const override |
283 | { |
284 | return false; |
285 | } |
286 | |
287 | private: |
288 | QElapsedTimer m_wallTime; |
289 | }; |
290 | |
291 | /*! |
292 | \class QSGContext |
293 | |
294 | \brief The QSGContext holds the scene graph entry points for one QML engine. |
295 | |
296 | The context is not ready for use until it has a QRhi. Once that happens, |
297 | the scene graph population can start. |
298 | |
299 | \internal |
300 | */ |
301 | |
302 | QSGContext::QSGContext(QObject *parent) : |
303 | QObject(parent) |
304 | { |
305 | } |
306 | |
307 | QSGContext::~QSGContext() |
308 | { |
309 | } |
310 | |
311 | void QSGContext::renderContextInitialized(QSGRenderContext *) |
312 | { |
313 | } |
314 | |
315 | void QSGContext::renderContextInvalidated(QSGRenderContext *) |
316 | { |
317 | } |
318 | |
319 | |
320 | /*! |
321 | Convenience factory function for creating a colored rectangle with the given geometry. |
322 | */ |
323 | QSGInternalRectangleNode *QSGContext::createInternalRectangleNode(const QRectF &rect, const QColor &c) |
324 | { |
325 | QSGInternalRectangleNode *node = createInternalRectangleNode(); |
326 | node->setRect(rect); |
327 | node->setColor(c); |
328 | node->update(); |
329 | return node; |
330 | } |
331 | |
332 | /*! |
333 | Creates a new shader effect helper instance. This function is called on the |
334 | GUI thread, unlike the others. This is necessary in order to provide |
335 | adaptable, backend-specific shader effect functionality to the GUI thread too. |
336 | */ |
337 | QSGGuiThreadShaderEffectManager *QSGContext::createGuiThreadShaderEffectManager() |
338 | { |
339 | return nullptr; |
340 | } |
341 | |
342 | /*! |
343 | Creates a new shader effect node. The default of returning nullptr is |
344 | valid as long as the backend does not claim SupportsShaderEffectNode or |
345 | ignoring ShaderEffect elements is acceptable. |
346 | */ |
347 | QSGShaderEffectNode *QSGContext::createShaderEffectNode(QSGRenderContext *) |
348 | { |
349 | return nullptr; |
350 | } |
351 | |
352 | /*! |
353 | Creates a new animation driver. |
354 | */ |
355 | QAnimationDriver *QSGContext::createAnimationDriver(QObject *parent) |
356 | { |
357 | if (useElapsedTimerBasedAnimationDriver()) |
358 | return new QSGElapsedTimerAnimationDriver(parent); |
359 | |
360 | return new QSGDefaultAnimationDriver(parent); |
361 | } |
362 | |
363 | /*! |
364 | \return the vsync rate (such as, 16.68 ms or similar), if applicable, for |
365 | the \a driver that was created by createAnimationDriver(). |
366 | */ |
367 | float QSGContext::vsyncIntervalForAnimationDriver(QAnimationDriver *driver) |
368 | { |
369 | return static_cast<QSGAnimationDriver *>(driver)->vsyncInterval(); |
370 | } |
371 | |
372 | /*! |
373 | \return true if \a driver relies on vsync-based throttling in some form. |
374 | */ |
375 | bool QSGContext::isVSyncDependent(QAnimationDriver *driver) |
376 | { |
377 | return static_cast<QSGAnimationDriver *>(driver)->isVSyncDependent(); |
378 | } |
379 | |
380 | QSize QSGContext::minimumFBOSize() const |
381 | { |
382 | return QSize(1, 1); |
383 | } |
384 | |
385 | /*! |
386 | Returns a pointer to the (presumably) global renderer interface. |
387 | |
388 | \note This function may be called on the GUI thread in order to get access |
389 | to QSGRendererInterface::graphicsApi() and other getters. |
390 | |
391 | \note it is expected that the simple queries (graphicsApi, shaderType, |
392 | etc.) are available regardless of the render context validity (i.e. |
393 | scenegraph status). This does not apply to engine-specific getters like |
394 | getResource(). In the end this means that this function must always return |
395 | a valid object in subclasses, even when renderContext->isValid() is false. |
396 | The typical pattern is to implement the QSGRendererInterface in the |
397 | QSGContext or QSGRenderContext subclass itself, whichever is more suitable. |
398 | */ |
399 | QSGRendererInterface *QSGContext::rendererInterface(QSGRenderContext *renderContext) |
400 | { |
401 | Q_UNUSED(renderContext); |
402 | qWarning(msg: "QSGRendererInterface not implemented" ); |
403 | return nullptr; |
404 | } |
405 | |
406 | QSGRenderContext::QSGRenderContext(QSGContext *context) |
407 | : m_sg(context) |
408 | { |
409 | } |
410 | |
411 | QSGRenderContext::~QSGRenderContext() |
412 | { |
413 | } |
414 | |
415 | void QSGRenderContext::initialize(const InitParams *params) |
416 | { |
417 | Q_UNUSED(params); |
418 | } |
419 | |
420 | void QSGRenderContext::invalidate() |
421 | { |
422 | } |
423 | |
424 | void QSGRenderContext::prepareSync(qreal devicePixelRatio, |
425 | QRhiCommandBuffer *cb, |
426 | const QQuickGraphicsConfiguration &config) |
427 | { |
428 | Q_UNUSED(devicePixelRatio); |
429 | Q_UNUSED(cb); |
430 | Q_UNUSED(config); |
431 | } |
432 | |
433 | void QSGRenderContext::beginNextFrame(QSGRenderer *renderer, const QSGRenderTarget &renderTarget, |
434 | RenderPassCallback mainPassRecordingStart, |
435 | RenderPassCallback mainPassRecordingEnd, |
436 | void *callbackUserData) |
437 | { |
438 | renderer->setRenderTarget(renderTarget); |
439 | Q_UNUSED(mainPassRecordingStart); |
440 | Q_UNUSED(mainPassRecordingEnd); |
441 | Q_UNUSED(callbackUserData); |
442 | } |
443 | |
444 | void QSGRenderContext::endNextFrame(QSGRenderer *renderer) |
445 | { |
446 | Q_UNUSED(renderer); |
447 | } |
448 | |
449 | void QSGRenderContext::endSync() |
450 | { |
451 | qDeleteAll(c: m_texturesToDelete); |
452 | m_texturesToDelete.clear(); |
453 | } |
454 | |
455 | /*! |
456 | Do necessary preprocessing before the frame |
457 | */ |
458 | void QSGRenderContext::preprocess() |
459 | { |
460 | } |
461 | |
462 | /*! |
463 | Factory function for scene graph backends of the distance-field glyph cache. |
464 | */ |
465 | QSGDistanceFieldGlyphCache *QSGRenderContext::distanceFieldGlyphCache(const QRawFont &, int) |
466 | { |
467 | return nullptr; |
468 | } |
469 | |
470 | void QSGRenderContext::invalidateGlyphCaches() |
471 | { |
472 | |
473 | } |
474 | |
475 | void QSGRenderContext::registerFontengineForCleanup(QFontEngine *engine) |
476 | { |
477 | engine->ref.ref(); |
478 | m_fontEnginesToClean[engine]++; |
479 | } |
480 | |
481 | void QSGRenderContext::unregisterFontengineForCleanup(QFontEngine *engine) |
482 | { |
483 | m_fontEnginesToClean[engine]--; |
484 | Q_ASSERT(m_fontEnginesToClean.value(engine) >= 0); |
485 | } |
486 | |
487 | QRhi *QSGRenderContext::rhi() const |
488 | { |
489 | return nullptr; |
490 | } |
491 | |
492 | /*! |
493 | Factory function for the scene graph renderers. |
494 | |
495 | The renderers are used for the toplevel renderer and once for every |
496 | QQuickShaderEffectSource used in the QML scene. |
497 | */ |
498 | |
499 | QSGTexture *QSGRenderContext::textureForFactory(QQuickTextureFactory *factory, QQuickWindow *window) |
500 | { |
501 | if (!factory) |
502 | return nullptr; |
503 | |
504 | m_mutex.lock(); |
505 | QSGTexture *texture = m_textures.value(key: factory); |
506 | m_mutex.unlock(); |
507 | |
508 | if (!texture) { |
509 | texture = factory->createTexture(window); |
510 | |
511 | m_mutex.lock(); |
512 | m_textures.insert(key: factory, value: texture); |
513 | m_mutex.unlock(); |
514 | |
515 | connect(sender: factory, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(textureFactoryDestroyed(QObject*)), Qt::DirectConnection); |
516 | } |
517 | return texture; |
518 | } |
519 | |
520 | void QSGRenderContext::textureFactoryDestroyed(QObject *o) |
521 | { |
522 | m_mutex.lock(); |
523 | m_texturesToDelete << m_textures.take(key: o); |
524 | m_mutex.unlock(); |
525 | } |
526 | |
527 | /*! |
528 | Return the texture corresponding to a texture factory. |
529 | |
530 | This may optionally manipulate the texture in some way; for example by returning |
531 | an atlased texture. |
532 | |
533 | This function is not a replacement for textureForFactory; both should be used |
534 | for a single texture (this might atlas, while the other might cache). |
535 | */ |
536 | QSGTexture *QSGRenderContext::compressedTextureForFactory(const QSGCompressedTextureFactory *) const |
537 | { |
538 | return nullptr; |
539 | } |
540 | |
541 | QT_END_NAMESPACE |
542 | |
543 | #include "qsgcontext.moc" |
544 | #include "moc_qsgcontext_p.cpp" |
545 | |