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

source code of qtdeclarative/src/quick/items/qquickframebufferobject.cpp