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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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