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 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | class QQuickFramebufferObjectPrivate : public QQuickItemPrivate |
54 | { |
55 | Q_DECLARE_PUBLIC(QQuickFramebufferObject) |
56 | public: |
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 | */ |
123 | QQuickFramebufferObject::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 | |
141 | void 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 | |
150 | bool 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 | |
168 | void 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 | |
178 | bool QQuickFramebufferObject::mirrorVertically() const |
179 | { |
180 | Q_D(const QQuickFramebufferObject); |
181 | return d->mirrorVertically; |
182 | } |
183 | |
184 | /*! |
185 | * \internal |
186 | */ |
187 | void 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 | |
196 | class QSGFramebufferObjectNode : public QSGTextureProvider, public QSGSimpleTextureNode |
197 | { |
198 | Q_OBJECT |
199 | |
200 | public: |
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 | |
232 | public 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 | |
268 | public: |
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 | |
281 | static 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 | */ |
291 | QSGNode *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 | */ |
369 | bool QQuickFramebufferObject::isTextureProvider() const |
370 | { |
371 | return true; |
372 | } |
373 | |
374 | /*! |
375 | \reimp |
376 | */ |
377 | QSGTextureProvider *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 | */ |
401 | void 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 | |
410 | void 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 | */ |
431 | QQuickFramebufferObject::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 | */ |
451 | QQuickFramebufferObject::Renderer::~Renderer() |
452 | { |
453 | } |
454 | |
455 | /*! |
456 | * Returns the framebuffer object currently being rendered to. |
457 | */ |
458 | QOpenGLFramebufferObject *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 | */ |
499 | void 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 | */ |
508 | void 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 | */ |
534 | QOpenGLFramebufferObject *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 | */ |
548 | void QQuickFramebufferObject::Renderer::update() |
549 | { |
550 | if (data) |
551 | ((QSGFramebufferObjectNode *) data)->scheduleRender(); |
552 | } |
553 | |
554 | |
555 | #include "qquickframebufferobject.moc" |
556 | #include "moc_qquickframebufferobject.cpp" |
557 | |
558 | QT_END_NAMESPACE |
559 | |