Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickframebufferobject.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
6
7#include <QOpenGLFramebufferObject>
8#include <QOpenGLFunctions>
9#include <private/qquickitem_p.h>
10#include <private/qsgadaptationlayer_p.h>
11#include <qsgtextureprovider.h>
12#include <rhi/qrhi.h>
13
14#include <QSGSimpleTextureNode>
15#include <QSGRendererInterface>
16#include <QQuickOpenGLUtils>
17
19
21{
22 Q_DECLARE_PUBLIC(QQuickFramebufferObject)
23public:
25 : followsItemSize(true)
26 , mirrorVertically(false)
27 , node(nullptr)
28 {
29 }
30
34};
35
36/*!
37 * \class QQuickFramebufferObject
38 * \inmodule QtQuick
39 * \since 5.2
40 *
41 * \brief The QQuickFramebufferObject class is a convenience class
42 * for integrating OpenGL rendering using a framebuffer object (FBO)
43 * with Qt Quick.
44 *
45 * \warning This class is only functional when Qt Quick is rendering via
46 * OpenGL. It is not compatible with other graphics APIs, such as Vulkan or
47 * Metal. It should be treated as a legacy class that is only present in order
48 * to enable Qt 5 applications to function without source compatibility breaks
49 * as long as they tie themselves to OpenGL.
50 *
51 * On most platforms, the rendering will occur on a \l {Scene Graph and Rendering}{dedicated thread}.
52 * For this reason, the QQuickFramebufferObject class enforces a strict
53 * separation between the item implementation and the FBO rendering. All
54 * item logic, such as properties and UI-related helper functions needed by
55 * QML should be located in a QQuickFramebufferObject class subclass.
56 * Everything that relates to rendering must be located in the
57 * QQuickFramebufferObject::Renderer class.
58 *
59 * To avoid race conditions and read/write issues from two threads
60 * it is important that the renderer and the item never read or
61 * write shared variables. Communication between the item and the renderer
62 * should primarily happen via the
63 * QQuickFramebufferObject::Renderer::synchronize() function. This function
64 * will be called on the render thread while the GUI thread is blocked.
65 *
66 * Using queued connections or events for communication between item
67 * and renderer is also possible.
68 *
69 * Both the Renderer and the FBO are memory managed internally.
70 *
71 * To render into the FBO, the user should subclass the Renderer class
72 * and reimplement its Renderer::render() function. The Renderer subclass
73 * is returned from createRenderer().
74 *
75 * The size of the FBO will by default adapt to the size of
76 * the item. If a fixed size is preferred, set textureFollowsItemSize
77 * to \c false and return a texture of your choosing from
78 * QQuickFramebufferObject::Renderer::createFramebufferObject().
79 *
80 * Starting Qt 5.4, the QQuickFramebufferObject class is a
81 * \l{QSGTextureProvider}{texture provider}
82 * and can be used directly in \l {ShaderEffect}{ShaderEffects} and other
83 * classes that consume texture providers.
84 *
85 * \sa {Scene Graph and Rendering}
86 */
87
88/*!
89 * Constructs a new QQuickFramebufferObject with parent \a parent.
90 */
91QQuickFramebufferObject::QQuickFramebufferObject(QQuickItem *parent) :
92 QQuickItem(*new QQuickFramebufferObjectPrivate, parent)
93{
94 setFlag(ItemHasContents);
95}
96
97/*!
98 * \property QQuickFramebufferObject::textureFollowsItemSize
99 *
100 * This property controls if the size of the FBO's texture should follow
101 * the dimensions of the QQuickFramebufferObject item. When this property
102 * is false, the FBO will be created once the first time it is displayed.
103 * If it is set to true, the FBO will be recreated every time the dimensions
104 * of the item change.
105 *
106 * The default value is \c {true}.
107 */
108
109void QQuickFramebufferObject::setTextureFollowsItemSize(bool follows)
110{
111 Q_D(QQuickFramebufferObject);
112 if (d->followsItemSize == follows)
113 return;
114 d->followsItemSize = follows;
115 emit textureFollowsItemSizeChanged(d->followsItemSize);
116}
117
118bool QQuickFramebufferObject::textureFollowsItemSize() const
119{
120 Q_D(const QQuickFramebufferObject);
121 return d->followsItemSize;
122}
123
124/*!
125 * \property QQuickFramebufferObject::mirrorVertically
126 *
127 * This property controls if the size of the FBO's contents should be mirrored
128 * vertically when drawing. This allows easy integration of third-party
129 * rendering code that does not follow the standard expectations.
130 *
131 * The default value is \c {false}.
132 *
133 * \since 5.6
134 */
135
136void QQuickFramebufferObject::setMirrorVertically(bool enable)
137{
138 Q_D(QQuickFramebufferObject);
139 if (d->mirrorVertically == enable)
140 return;
141 d->mirrorVertically = enable;
142 emit mirrorVerticallyChanged(d->mirrorVertically);
143 update();
144}
145
146bool QQuickFramebufferObject::mirrorVertically() const
147{
148 Q_D(const QQuickFramebufferObject);
149 return d->mirrorVertically;
150}
151
152/*!
153 * \internal
154 */
155void QQuickFramebufferObject::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
156{
157 QQuickItem::geometryChange(newGeometry, oldGeometry);
158
159 Q_D(QQuickFramebufferObject);
160 if (newGeometry.size() != oldGeometry.size() && d->followsItemSize)
161 update();
162}
163
165{
167
168public:
170 : window(nullptr)
171 , fbo(nullptr)
172 , msDisplayFbo(nullptr)
173 , renderer(nullptr)
174 , renderPending(true)
175 , invalidatePending(false)
177 {
178 qsgnode_set_description(this, QStringLiteral("fbonode"));
179 }
180
182 {
183 delete renderer;
184 delete texture();
185 delete fbo;
186 delete msDisplayFbo;
187 }
188
190 {
191 renderPending = true;
192 window->update();
193 }
194
196 {
198 }
199
200public Q_SLOTS:
223
231
232public:
234 QOpenGLFramebufferObject *fbo;
235 QOpenGLFramebufferObject *msDisplayFbo;
238
241
243};
244
245static inline bool isOpenGL(QSGRenderContext *rc)
246{
247 QSGRendererInterface *rif = rc->sceneGraphContext()->rendererInterface(rc);
248 return rif && rif->graphicsApi() == QSGRendererInterface::OpenGL;
249}
250
251/*!
252 * \internal
253 */
254QSGNode *QQuickFramebufferObject::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
255{
256 QSGFramebufferObjectNode *n = static_cast<QSGFramebufferObjectNode *>(node);
257
258 // We only abort if we never had a node before. This is so that we
259 // don't recreate the renderer object if the thing becomes tiny. In
260 // terms of API it would be horrible if the renderer would go away
261 // that easily so with this logic, the renderer only goes away when
262 // the scenegraph is invalidated or it is removed from the scene.
263 if (!n && (width() <= 0 || height() <= 0))
264 return nullptr;
265
266 Q_D(QQuickFramebufferObject);
267
268 if (!n) {
269 if (!isOpenGL(d->sceneGraphRenderContext()))
270 return nullptr;
271 if (!d->node)
272 d->node = new QSGFramebufferObjectNode;
273 n = d->node;
274 }
275
276 if (!n->renderer) {
277 n->window = window();
278 n->renderer = createRenderer();
279 n->renderer->data = n;
280 n->quickFbo = this;
281 connect(window(), SIGNAL(beforeRendering()), n, SLOT(render()));
282 connect(window(), SIGNAL(screenChanged(QScreen*)), n, SLOT(handleScreenChange()));
283 }
284
285 n->renderer->synchronize(this);
286
287 QSize minFboSize = d->sceneGraphContext()->minimumFBOSize();
288 QSize desiredFboSize(qMax<int>(minFboSize.width(), width()),
289 qMax<int>(minFboSize.height(), height()));
290
291 n->devicePixelRatio = d->effectiveDevicePixelRatio();
292 desiredFboSize *= n->devicePixelRatio;
293
294 if (n->fbo && ((d->followsItemSize && n->fbo->size() != desiredFboSize) || n->invalidatePending)) {
295 delete n->texture();
296 delete n->fbo;
297 n->fbo = nullptr;
298 delete n->msDisplayFbo;
299 n->msDisplayFbo = nullptr;
300 n->invalidatePending = false;
301 }
302
303 if (!n->fbo) {
304 n->fbo = n->renderer->createFramebufferObject(desiredFboSize);
305
306 GLuint displayTexture = n->fbo->texture();
307
308 if (n->fbo->format().samples() > 0) {
309 n->msDisplayFbo = new QOpenGLFramebufferObject(n->fbo->size());
310 displayTexture = n->msDisplayFbo->texture();
311 }
312
313 QSGTexture *wrapper = QNativeInterface::QSGOpenGLTexture::fromNative(displayTexture,
314 window(),
315 n->fbo->size(),
316 QQuickWindow::TextureHasAlphaChannel);
317 n->setTexture(wrapper);
318 }
319
320 n->setTextureCoordinatesTransform(d->mirrorVertically ? QSGSimpleTextureNode::MirrorVertically : QSGSimpleTextureNode::NoTransform);
321 n->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
322 n->setRect(0, 0, width(), height());
323
324 n->scheduleRender();
325
326 return n;
327}
328
329/*!
330 \reimp
331*/
332bool QQuickFramebufferObject::isTextureProvider() const
333{
334 return true;
335}
336
337/*!
338 \reimp
339*/
340QSGTextureProvider *QQuickFramebufferObject::textureProvider() const
341{
342 // When Item::layer::enabled == true, QQuickItem will be a texture
343 // provider. In this case we should prefer to return the layer rather
344 // than the fbo texture.
345 if (QQuickItem::isTextureProvider())
346 return QQuickItem::textureProvider();
347
348 Q_D(const QQuickFramebufferObject);
349 QQuickWindow *w = window();
350 if (!w || !w->isSceneGraphInitialized() || QThread::currentThread() != QQuickWindowPrivate::get(w)->context->thread()) {
351 qWarning("QQuickFramebufferObject::textureProvider: can only be queried on the rendering thread of an exposed window");
352 return nullptr;
353 }
354 if (!isOpenGL(d->sceneGraphRenderContext()))
355 return nullptr;
356 if (!d->node)
357 d->node = new QSGFramebufferObjectNode;
358 return d->node;
359}
360
361/*!
362 \reimp
363*/
364void QQuickFramebufferObject::releaseResources()
365{
366 // When release resources is called on the GUI thread, we only need to
367 // forget about the node. Since it is the node we returned from updatePaintNode
368 // it will be managed by the scene graph.
369 Q_D(QQuickFramebufferObject);
370 d->node = nullptr;
371}
372
373void QQuickFramebufferObject::invalidateSceneGraph()
374{
375 Q_D(QQuickFramebufferObject);
376 d->node = nullptr;
377}
378
379/*!
380 * \class QQuickFramebufferObject::Renderer
381 * \inmodule QtQuick
382 * \since 5.2
383 *
384 * The QQuickFramebufferObject::Renderer class is used to implement the
385 * rendering logic of a QQuickFramebufferObject.
386 */
387
388/*!
389 * Constructs a new renderer.
390 *
391 * This function is called during the scene graph sync phase when the
392 * GUI thread is blocked.
393 */
394QQuickFramebufferObject::Renderer::Renderer()
395 : data(nullptr)
396{
397}
398
399/*!
400 * \fn QQuickFramebufferObject::Renderer *QQuickFramebufferObject::createRenderer() const
401 *
402 * Reimplement this function to create a renderer used to render into the FBO.
403 *
404 * This function will be called on the rendering thread while the GUI thread is
405 * blocked.
406 */
407
408/*!
409 * The Renderer is automatically deleted when the scene graph resources
410 * for the QQuickFramebufferObject item is cleaned up.
411 *
412 * This function is called on the rendering thread.
413 */
414QQuickFramebufferObject::Renderer::~Renderer()
415{
416}
417
418/*!
419 * Returns the framebuffer object currently being rendered to.
420 */
421QOpenGLFramebufferObject *QQuickFramebufferObject::Renderer::framebufferObject() const
422{
423 return data ? ((QSGFramebufferObjectNode *) data)->fbo : nullptr;
424}
425
426/*!
427 * \fn void QQuickFramebufferObject::Renderer::render()
428 *
429 * This function is called when the FBO should be rendered into. The framebuffer
430 * is bound at this point and the \c glViewport has been set up to match
431 * the FBO size.
432 *
433 * The FBO will be automatically unbound after the function returns.
434 *
435 * \note Do not assume that the OpenGL state is all set to the defaults when
436 * this function is invoked, or that it is maintained between calls. Both the Qt
437 * Quick renderer and the custom rendering code uses the same OpenGL
438 * context. This means that the state might have been modified by Quick before
439 * invoking this function.
440 *
441 * \note It is recommended to call QQuickOpenGLUtils::resetOpenGLState() before
442 * returning. This resets OpenGL state used by the Qt Quick renderer and thus
443 * avoids interference from the state changes made by the rendering code in this
444 * function.
445 */
446
447/*!
448 * This function is called as a result of QQuickFramebufferObject::update().
449 *
450 * Use this function to update the renderer with changes that have occurred
451 * in the item. \a item is the item that instantiated this renderer. The function
452 * is called once before the FBO is created.
453 *
454 * \e {For instance, if the item has a color property which is controlled by
455 * QML, one should call QQuickFramebufferObject::update() and use
456 * synchronize() to copy the new color into the renderer so that it can be
457 * used to render the next frame.}
458 *
459 * This function is the only place when it is safe for the renderer and the
460 * item to read and write each others members.
461 */
462void QQuickFramebufferObject::Renderer::synchronize(QQuickFramebufferObject *item)
463{
464 Q_UNUSED(item);
465}
466
467/*!
468 * Call this function during synchronize() to invalidate the current FBO. This
469 * will result in a new FBO being created with createFramebufferObject().
470 */
471void QQuickFramebufferObject::Renderer::invalidateFramebufferObject()
472{
473 if (data)
474 ((QSGFramebufferObjectNode *) data)->invalidatePending = true;
475}
476
477/*!
478 * This function is called when a new FBO is needed. This happens on the
479 * initial frame. If QQuickFramebufferObject::textureFollowsItemSize is set to true,
480 * it is called again every time the dimensions of the item changes.
481 *
482 * The returned FBO can have any attachment. If the QOpenGLFramebufferObjectFormat
483 * indicates that the FBO should be multisampled, the internal implementation
484 * of the Renderer will allocate a second FBO and blit the multisampled FBO
485 * into the FBO used to display the texture.
486 *
487 * \note Some hardware has issues with small FBO sizes. \a size takes that into account, so
488 * be cautious when overriding the size with a fixed size. A minimal size of 64x64 should
489 * always work.
490 *
491 * \note \a size takes the device pixel ratio into account, meaning that it is
492 * already multiplied by the correct scale factor. When moving the window
493 * containing the QQuickFramebufferObject item to a screen with different
494 * settings, the FBO is automatically recreated and this function is invoked
495 * with the correct size.
496 */
497QOpenGLFramebufferObject *QQuickFramebufferObject::Renderer::createFramebufferObject(const QSize &size)
498{
499 return new QOpenGLFramebufferObject(size);
500}
501
502/*!
503 * Call this function when the FBO should be rendered again.
504 *
505 * This function can be called from render() to force the FBO to be rendered
506 * again before the next frame.
507 *
508 * \note This function should be used from inside the renderer. To update
509 * the item on the GUI thread, use QQuickFramebufferObject::update().
510 */
511void QQuickFramebufferObject::Renderer::update()
512{
513 if (data)
514 ((QSGFramebufferObjectNode *) data)->scheduleRender();
515}
516
517QT_END_NAMESPACE
518
519#include "qquickframebufferobject.moc"
520#include "moc_qquickframebufferobject.cpp"
QOpenGLFramebufferObject * msDisplayFbo
QOpenGLFramebufferObject * fbo
QQuickFramebufferObject * quickFbo
static bool isOpenGL(QSGRenderContext *rc)