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

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