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 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | class QQuickFramebufferObjectPrivate : public QQuickItemPrivate |
20 | { |
21 | Q_DECLARE_PUBLIC(QQuickFramebufferObject) |
22 | public: |
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 | */ |
90 | QQuickFramebufferObject::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 | |
108 | void 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 | |
117 | bool 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 | |
135 | void 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 | |
145 | bool QQuickFramebufferObject::mirrorVertically() const |
146 | { |
147 | Q_D(const QQuickFramebufferObject); |
148 | return d->mirrorVertically; |
149 | } |
150 | |
151 | /*! |
152 | * \internal |
153 | */ |
154 | void 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 | |
163 | class QSGFramebufferObjectNode : public QSGTextureProvider, public QSGSimpleTextureNode |
164 | { |
165 | Q_OBJECT |
166 | |
167 | public: |
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 | |
199 | public 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 | |
231 | public: |
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 | |
244 | static 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 | */ |
253 | QSGNode *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 | */ |
331 | bool QQuickFramebufferObject::isTextureProvider() const |
332 | { |
333 | return true; |
334 | } |
335 | |
336 | /*! |
337 | \reimp |
338 | */ |
339 | QSGTextureProvider *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 | */ |
363 | void 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 | |
372 | void 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 | */ |
393 | QQuickFramebufferObject::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 | */ |
413 | QQuickFramebufferObject::Renderer::~Renderer() |
414 | { |
415 | } |
416 | |
417 | /*! |
418 | * Returns the framebuffer object currently being rendered to. |
419 | */ |
420 | QOpenGLFramebufferObject *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 | */ |
461 | void 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 | */ |
470 | void 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 | */ |
496 | QOpenGLFramebufferObject *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 | */ |
510 | void QQuickFramebufferObject::Renderer::update() |
511 | { |
512 | if (data) |
513 | ((QSGFramebufferObjectNode *) data)->scheduleRender(); |
514 | } |
515 | |
516 | QT_END_NAMESPACE |
517 | |
518 | #include "qquickframebufferobject.moc" |
519 | #include "moc_qquickframebufferobject.cpp" |
520 | |