Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qquickframebufferobject.h"
++
++ #include <QOpenGLFramebufferObject>
++ #include <QOpenGLFunctions>
++ #include <private/qquickitem_p.h>
++ #include <private/qsgadaptationlayer_p.h>
++ #include <qsgtextureprovider.h>
++ #include <rhi/qrhi.h>
++
++ #include <QSGSimpleTextureNode>
++ #include <QSGRendererInterface>
++ #include <QQuickOpenGLUtils>
++
++ QT_BEGIN_NAMESPACE
++
++ class QQuickFramebufferObjectPrivate : public QQuickItemPrivate
++ {
++ Q_DECLARE_PUBLIC(QQuickFramebufferObject)
++ public:
++ QQuickFramebufferObjectPrivate()
++ : followsItemSize(true)
++ , mirrorVertically(false)
++ , node(nullptr)
++ {
++ }
++
++ bool followsItemSize;
++ bool mirrorVertically;
++ mutable QSGFramebufferObjectNode *node;
++ };
++
++ /*!
++ * \class QQuickFramebufferObject
++ * \inmodule QtQuick
++ * \since 5.2
++ *
++ * \brief The QQuickFramebufferObject class is a convenience class
++ * for integrating OpenGL rendering using a framebuffer object (FBO)
++ * with Qt Quick.
++ *
++ * \warning This class is only functional when Qt Quick is rendering via
++ * OpenGL. It is not compatible with other graphics APIs, such as Vulkan or
++ * Metal. It should be treated as a legacy class that is only present in order
++ * to enable Qt 5 applications to function without source compatibility breaks
++ * as long as they tie themselves to OpenGL.
++ *
++ * On most platforms, the rendering will occur on a \l {Scene Graph and Rendering}{dedicated thread}.
++ * For this reason, the QQuickFramebufferObject class enforces a strict
++ * separation between the item implementation and the FBO rendering. All
++ * item logic, such as properties and UI-related helper functions needed by
++ * QML should be located in a QQuickFramebufferObject class subclass.
++ * Everything that relates to rendering must be located in the
++ * QQuickFramebufferObject::Renderer class.
++ *
++ * To avoid race conditions and read/write issues from two threads
++ * it is important that the renderer and the item never read or
++ * write shared variables. Communication between the item and the renderer
++ * should primarily happen via the
++ * QQuickFramebufferObject::Renderer::synchronize() function. This function
++ * will be called on the render thread while the GUI thread is blocked.
++ *
++ * Using queued connections or events for communication between item
++ * and renderer is also possible.
++ *
++ * Both the Renderer and the FBO are memory managed internally.
++ *
++ * To render into the FBO, the user should subclass the Renderer class
++ * and reimplement its Renderer::render() function. The Renderer subclass
++ * is returned from createRenderer().
++ *
++ * The size of the FBO will by default adapt to the size of
++ * the item. If a fixed size is preferred, set textureFollowsItemSize
++ * to \c false and return a texture of your choosing from
++ * QQuickFramebufferObject::Renderer::createFramebufferObject().
++ *
++ * Starting Qt 5.4, the QQuickFramebufferObject class is a
++ * \l{QSGTextureProvider}{texture provider}
++ * and can be used directly in \l {ShaderEffect}{ShaderEffects} and other
++ * classes that consume texture providers.
++ *
++ * \sa {Scene Graph and Rendering}
++ */
++
++ /*!
++ * Constructs a new QQuickFramebufferObject with parent \a parent.
++ */
++ QQuickFramebufferObject::QQuickFramebufferObject(QQuickItem *parent) :
++ QQuickItem(*new QQuickFramebufferObjectPrivate, parent)
++ {
++ setFlag(ItemHasContents);
++ }
++
++ /*!
++ * \property QQuickFramebufferObject::textureFollowsItemSize
++ *
++ * This property controls if the size of the FBO's texture should follow
++ * the dimensions of the QQuickFramebufferObject item. When this property
++ * is false, the FBO will be created once the first time it is displayed.
++ * If it is set to true, the FBO will be recreated every time the dimensions
++ * of the item change.
++ *
++ * The default value is \c {true}.
++ */
++
++ void QQuickFramebufferObject::setTextureFollowsItemSize(bool follows)
++ {
++ Q_D(QQuickFramebufferObject);
++ if (d->followsItemSize == follows)
++ return;
++ d->followsItemSize = follows;
++ emit textureFollowsItemSizeChanged(d->followsItemSize);
++ }
++
++ bool QQuickFramebufferObject::textureFollowsItemSize() const
++ {
++ Q_D(const QQuickFramebufferObject);
++ return d->followsItemSize;
++ }
++
++ /*!
++ * \property QQuickFramebufferObject::mirrorVertically
++ *
++ * This property controls if the size of the FBO's contents should be mirrored
++ * vertically when drawing. This allows easy integration of third-party
++ * rendering code that does not follow the standard expectations.
++ *
++ * The default value is \c {false}.
++ *
++ * \since 5.6
++ */
++
++ void QQuickFramebufferObject::setMirrorVertically(bool enable)
++ {
++ Q_D(QQuickFramebufferObject);
++ if (d->mirrorVertically == enable)
++ return;
++ d->mirrorVertically = enable;
++ emit mirrorVerticallyChanged(d->mirrorVertically);
++ update();
++ }
++
++ bool QQuickFramebufferObject::mirrorVertically() const
++ {
++ Q_D(const QQuickFramebufferObject);
++ return d->mirrorVertically;
++ }
++
++ /*!
++ * \internal
++ */
++ void QQuickFramebufferObject::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
++ {
++ QQuickItem::geometryChange(newGeometry, oldGeometry);
++
++ Q_D(QQuickFramebufferObject);
++ if (newGeometry.size() != oldGeometry.size() && d->followsItemSize)
++ update();
++ }
++
++ class QSGFramebufferObjectNode : public QSGTextureProvider, public QSGSimpleTextureNode
++ {
++ Q_OBJECT
++
++ public:
++ QSGFramebufferObjectNode()
++ : window(nullptr)
++ , fbo(nullptr)
++ , msDisplayFbo(nullptr)
++ , renderer(nullptr)
++ , renderPending(true)
++ , invalidatePending(false)
++ , devicePixelRatio(1)
++ {
++ qsgnode_set_description(this, QStringLiteral("fbonode"));
++ }
++
++ ~QSGFramebufferObjectNode()
++ {
++ delete renderer;
++ delete texture();
++ delete fbo;
++ delete msDisplayFbo;
++ }
++
++ void scheduleRender()
++ {
++ renderPending = true;
++ window->update();
++ }
++
++ QSGTexture *texture() const override
++ {
++ return QSGSimpleTextureNode::texture();
++ }
++
++ public Q_SLOTS:
++ void render()
++ {
++ if (renderPending) {
++ renderPending = false;
++
++ window->beginExternalCommands();
++ QQuickOpenGLUtils::resetOpenGLState();
++
++ fbo->bind();
++ QOpenGLContext::currentContext()->functions()->glViewport(0, 0, fbo->width(), fbo->height());
++ renderer->render();
++ fbo->bindDefault();
++
++ if (msDisplayFbo)
++ QOpenGLFramebufferObject::blitFramebuffer(msDisplayFbo, fbo);
++
++ window->endExternalCommands();
++
++ markDirty(QSGNode::DirtyMaterial);
++ emit textureChanged();
++ }
++ }
++
++ void handleScreenChange()
++ {
++ if (window->effectiveDevicePixelRatio() != devicePixelRatio) {
++ renderer->invalidateFramebufferObject();
++ quickFbo->update();
++ }
++ }
++
++ public:
++ QQuickWindow *window;
++ QOpenGLFramebufferObject *fbo;
++ QOpenGLFramebufferObject *msDisplayFbo;
++ QQuickFramebufferObject::Renderer *renderer;
++ QQuickFramebufferObject *quickFbo;
++
++ bool renderPending;
++ bool invalidatePending;
++
++ qreal devicePixelRatio;
++ };
++
++ static inline bool isOpenGL(QSGRenderContext *rc)
++ {
++ QSGRendererInterface *rif = rc->sceneGraphContext()->rendererInterface(rc);
++ return rif && rif->graphicsApi() == QSGRendererInterface::OpenGL;
++ }
++
++ /*!
++ * \internal
++ */
++ QSGNode *QQuickFramebufferObject::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
++ {
++ QSGFramebufferObjectNode *n = static_cast<QSGFramebufferObjectNode *>(node);
++
++ // We only abort if we never had a node before. This is so that we
++ // don't recreate the renderer object if the thing becomes tiny. In
++ // terms of API it would be horrible if the renderer would go away
++ // that easily so with this logic, the renderer only goes away when
++ // the scenegraph is invalidated or it is removed from the scene.
++ if (!n && (width() <= 0 || height() <= 0))
++ return nullptr;
++
++ Q_D(QQuickFramebufferObject);
++
++ if (!n) {
++ if (!isOpenGL(d->sceneGraphRenderContext()))
++ return nullptr;
++ if (!d->node)
++ d->node = new QSGFramebufferObjectNode;
++ n = d->node;
++ }
++
++ if (!n->renderer) {
++ n->window = window();
++ n->renderer = createRenderer();
++ n->renderer->data = n;
++ n->quickFbo = this;
++ connect(window(), SIGNAL(beforeRendering()), n, SLOT(render()));
++ connect(window(), SIGNAL(screenChanged(QScreen*)), n, SLOT(handleScreenChange()));
++ }
++
++ n->renderer->synchronize(this);
++
++ n->devicePixelRatio = d->effectiveDevicePixelRatio();
++
++ const QSize minFboSize = d->sceneGraphContext()->minimumFBOSize();
++ const QSize itemPixelSize = (QSizeF(width(), height()) * n->devicePixelRatio).toSize();
++ const QSize desiredFboSize(qMax(minFboSize.width(), itemPixelSize.width()),
++ qMax(minFboSize.height(), itemPixelSize.height()));
++
++ if (n->fbo && ((d->followsItemSize && n->fbo->size() != desiredFboSize) || n->invalidatePending)) {
++ delete n->texture();
++ delete n->fbo;
++ n->fbo = nullptr;
++ delete n->msDisplayFbo;
++ n->msDisplayFbo = nullptr;
++ n->invalidatePending = false;
++ }
++
++ if (!n->fbo) {
++ n->fbo = n->renderer->createFramebufferObject(desiredFboSize);
++
++ GLuint displayTexture = n->fbo->texture();
++
++ if (n->fbo->format().samples() > 0) {
++ n->msDisplayFbo = new QOpenGLFramebufferObject(n->fbo->size());
++ displayTexture = n->msDisplayFbo->texture();
++ }
++
++ QSGTexture *wrapper = QNativeInterface::QSGOpenGLTexture::fromNative(displayTexture,
++ window(),
++ n->fbo->size(),
++ QQuickWindow::TextureHasAlphaChannel);
++ n->setTexture(wrapper);
++ }
++
++ n->setTextureCoordinatesTransform(d->mirrorVertically ? QSGSimpleTextureNode::MirrorVertically : QSGSimpleTextureNode::NoTransform);
++ n->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
++ n->setRect(0, 0, width(), height());
++
++ n->scheduleRender();
++
++ return n;
++ }
++
++ /*!
++ \reimp
++ */
++ bool QQuickFramebufferObject::isTextureProvider() const
++ {
++ return true;
++ }
++
++ /*!
++ \reimp
++ */
++ QSGTextureProvider *QQuickFramebufferObject::textureProvider() const
++ {
++ // When Item::layer::enabled == true, QQuickItem will be a texture
++ // provider. In this case we should prefer to return the layer rather
++ // than the fbo texture.
++ if (QQuickItem::isTextureProvider())
++ return QQuickItem::textureProvider();
++
++ Q_D(const QQuickFramebufferObject);
++ QQuickWindow *w = window();
++ if (!w || !w->isSceneGraphInitialized() || QThread::currentThread() != QQuickWindowPrivate::get(w)->context->thread()) {
++ qWarning("QQuickFramebufferObject::textureProvider: can only be queried on the rendering thread of an exposed window");
++ return nullptr;
++ }
++ if (!isOpenGL(d->sceneGraphRenderContext()))
++ return nullptr;
++ if (!d->node)
++ d->node = new QSGFramebufferObjectNode;
++ return d->node;
++ }
++
++ /*!
++ \reimp
++ */
++ void QQuickFramebufferObject::releaseResources()
++ {
++ // When release resources is called on the GUI thread, we only need to
++ // forget about the node. Since it is the node we returned from updatePaintNode
++ // it will be managed by the scene graph.
++ Q_D(QQuickFramebufferObject);
++ d->node = nullptr;
++ }
++
++ void QQuickFramebufferObject::invalidateSceneGraph()
++ {
++ Q_D(QQuickFramebufferObject);
++ d->node = nullptr;
++ }
++
++ /*!
++ * \class QQuickFramebufferObject::Renderer
++ * \inmodule QtQuick
++ * \since 5.2
++ *
++ * The QQuickFramebufferObject::Renderer class is used to implement the
++ * rendering logic of a QQuickFramebufferObject.
++ */
++
++ /*!
++ * Constructs a new renderer.
++ *
++ * This function is called during the scene graph sync phase when the
++ * GUI thread is blocked.
++ */
++ QQuickFramebufferObject::Renderer::Renderer()
++ : data(nullptr)
++ {
++ }
++
++ /*!
++ * \fn QQuickFramebufferObject::Renderer *QQuickFramebufferObject::createRenderer() const
++ *
++ * Reimplement this function to create a renderer used to render into the FBO.
++ *
++ * This function will be called on the rendering thread while the GUI thread is
++ * blocked.
++ */
++
++ /*!
++ * The Renderer is automatically deleted when the scene graph resources
++ * for the QQuickFramebufferObject item is cleaned up.
++ *
++ * This function is called on the rendering thread.
++ */
++ QQuickFramebufferObject::Renderer::~Renderer()
++ {
++ }
++
++ /*!
++ * Returns the framebuffer object currently being rendered to.
++ */
++ QOpenGLFramebufferObject *QQuickFramebufferObject::Renderer::framebufferObject() const
++ {
++ return data ? ((QSGFramebufferObjectNode *) data)->fbo : nullptr;
++ }
++
++ /*!
++ * \fn void QQuickFramebufferObject::Renderer::render()
++ *
++ * This function is called when the FBO should be rendered into. The framebuffer
++ * is bound at this point and the \c glViewport has been set up to match
++ * the FBO size.
++ *
++ * The FBO will be automatically unbound after the function returns.
++ *
++ * \note Do not assume that the OpenGL state is all set to the defaults when
++ * this function is invoked, or that it is maintained between calls. Both the Qt
++ * Quick renderer and the custom rendering code uses the same OpenGL
++ * context. This means that the state might have been modified by Quick before
++ * invoking this function.
++ *
++ * \note It is recommended to call QQuickOpenGLUtils::resetOpenGLState() before
++ * returning. This resets OpenGL state used by the Qt Quick renderer and thus
++ * avoids interference from the state changes made by the rendering code in this
++ * function.
++ */
++
++ /*!
++ * This function is called as a result of QQuickFramebufferObject::update().
++ *
++ * Use this function to update the renderer with changes that have occurred
++ * in the item. \a item is the item that instantiated this renderer. The function
++ * is called once before the FBO is created.
++ *
++ * \e {For instance, if the item has a color property which is controlled by
++ * QML, one should call QQuickFramebufferObject::update() and use
++ * synchronize() to copy the new color into the renderer so that it can be
++ * used to render the next frame.}
++ *
++ * This function is the only place when it is safe for the renderer and the
++ * item to read and write each others members.
++ */
++ void QQuickFramebufferObject::Renderer::synchronize(QQuickFramebufferObject *item)
++ {
++ Q_UNUSED(item);
++ }
++
++ /*!
++ * Call this function during synchronize() to invalidate the current FBO. This
++ * will result in a new FBO being created with createFramebufferObject().
++ */
++ void QQuickFramebufferObject::Renderer::invalidateFramebufferObject()
++ {
++ if (data)
++ ((QSGFramebufferObjectNode *) data)->invalidatePending = true;
++ }
++
++ /*!
++ * This function is called when a new FBO is needed. This happens on the
++ * initial frame. If QQuickFramebufferObject::textureFollowsItemSize is set to true,
++ * it is called again every time the dimensions of the item changes.
++ *
++ * The returned FBO can have any attachment. If the QOpenGLFramebufferObjectFormat
++ * indicates that the FBO should be multisampled, the internal implementation
++ * of the Renderer will allocate a second FBO and blit the multisampled FBO
++ * into the FBO used to display the texture.
++ *
++ * \note Some hardware has issues with small FBO sizes. \a size takes that into account, so
++ * be cautious when overriding the size with a fixed size. A minimal size of 64x64 should
++ * always work.
++ *
++ * \note \a size takes the device pixel ratio into account, meaning that it is
++ * already multiplied by the correct scale factor. When moving the window
++ * containing the QQuickFramebufferObject item to a screen with different
++ * settings, the FBO is automatically recreated and this function is invoked
++ * with the correct size.
++ */
++ QOpenGLFramebufferObject *QQuickFramebufferObject::Renderer::createFramebufferObject(const QSize &size)
++ {
++ return new QOpenGLFramebufferObject(size);
++ }
++
++ /*!
++ * Call this function when the FBO should be rendered again.
++ *
++ * This function can be called from render() to force the FBO to be rendered
++ * again before the next frame.
++ *
++ * \note This function should be used from inside the renderer. To update
++ * the item on the GUI thread, use QQuickFramebufferObject::update().
++ */
++ void QQuickFramebufferObject::Renderer::update()
++ {
++ if (data)
++ ((QSGFramebufferObjectNode *) data)->scheduleRender();
++ }
++
++ QT_END_NAMESPACE
++
++ #include "qquickframebufferobject.moc"
++ #include "moc_qquickframebufferobject.cpp"