From f2233c725078d49f6b185e642325dacb47b33240 Mon Sep 17 00:00:00 2001 From: Gunnar Sletta Date: Mon, 23 Sep 2013 09:53:58 +0200 Subject: Convenience class for FBO rendering in Qt Quick Change-Id: I5712bbfa0cde9f2fe5fddc35c4cc23597717065c Reviewed-by: Lars Knoll --- src/quick/items/items.pri | 6 +- src/quick/items/qquickframebufferobject.cpp | 397 ++++++++++++++++++++++++++++ src/quick/items/qquickframebufferobject.h | 98 +++++++ 3 files changed, 499 insertions(+), 2 deletions(-) create mode 100644 src/quick/items/qquickframebufferobject.cpp create mode 100644 src/quick/items/qquickframebufferobject.h (limited to 'src/quick/items') diff --git a/src/quick/items/items.pri b/src/quick/items/items.pri index 5aaf7d3ac6..cbdc5fd80f 100644 --- a/src/quick/items/items.pri +++ b/src/quick/items/items.pri @@ -72,7 +72,8 @@ HEADERS += \ $$PWD/qquickitemview_p_p.h \ $$PWD/qquickitemviewtransition_p.h \ $$PWD/qquickscreen_p.h \ - $$PWD/qquickwindowmodule_p.h + $$PWD/qquickwindowmodule_p.h \ + $$PWD/qquickframebufferobject.h SOURCES += \ $$PWD/qquickevents.cpp \ @@ -123,7 +124,8 @@ SOURCES += \ $$PWD/qquickitemview.cpp \ $$PWD/qquickitemviewtransition.cpp \ $$PWD/qquickwindowmodule.cpp \ - $$PWD/qquickscreen.cpp + $$PWD/qquickscreen.cpp \ + $$PWD/qquickframebufferobject.cpp SOURCES += \ $$PWD/qquickshadereffect.cpp \ diff --git a/src/quick/items/qquickframebufferobject.cpp b/src/quick/items/qquickframebufferobject.cpp new file mode 100644 index 0000000000..268c2d5342 --- /dev/null +++ b/src/quick/items/qquickframebufferobject.cpp @@ -0,0 +1,397 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickframebufferobject.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QQuickFramebufferObjectPrivate : public QQuickItemPrivate +{ + Q_DECLARE_PUBLIC(QQuickFramebufferObject) +public: + QQuickFramebufferObjectPrivate() + : followsItemSize(true) + { + } + + bool followsItemSize; +}; + +/*! + * \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. + * + * On most platforms, the rendering will occur on a 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(). + * + * \sa {Scene Graph - Rendering FBOs} + */ + +/*! + * 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; +} + +/*! + * \internal + */ +void QQuickFramebufferObject::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QQuickItem::geometryChanged(newGeometry, oldGeometry); + + Q_D(QQuickFramebufferObject); + if (newGeometry.size() != oldGeometry.size() && d->followsItemSize) + update(); +} + +class QSGFramebufferObjectNode : public QObject, public QSGSimpleTextureNode +{ + Q_OBJECT + +public: + QSGFramebufferObjectNode() + : window(0) + , fbo(0) + , msDisplayFbo(0) + , renderer(0) + , renderPending(true) + , invalidatePending(false) + { + + } + + ~QSGFramebufferObjectNode() + { + delete renderer; + delete texture(); + delete fbo; + delete msDisplayFbo; + } + + void scheduleRender() + { + renderPending = true; + window->update(); + } + +public Q_SLOTS: + void render() + { + if (renderPending) { + renderPending = false; + fbo->bind(); + glViewport(0, 0, fbo->width(), fbo->height()); + renderer->render(); + fbo->bindDefault(); + + if (msDisplayFbo) + QOpenGLFramebufferObject::blitFramebuffer(msDisplayFbo, fbo); + } + } + +public: + QQuickWindow *window; + QOpenGLFramebufferObject *fbo; + QOpenGLFramebufferObject *msDisplayFbo; + QQuickFramebufferObject::Renderer *renderer; + + bool renderPending; + bool invalidatePending; +}; + +/*! + * \internal + */ +QSGNode *QQuickFramebufferObject::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) +{ + QSGFramebufferObjectNode *n = static_cast(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 0; + + Q_D(QQuickFramebufferObject); + + if (!n) { + n = new QSGFramebufferObjectNode; + n->window = window(); + } + + if (!n->renderer) { + n->renderer = createRenderer(); + n->renderer->data = n; + connect(window(), SIGNAL(beforeRendering()), n, SLOT(render())); + } + + n->renderer->synchronize(this); + + if (n->fbo && (d->followsItemSize || n->invalidatePending)) { + if (n->fbo->width() != width() || n->fbo->height() != height()) { + delete n->fbo; + n->fbo = 0; + delete n->msDisplayFbo; + n->msDisplayFbo = 0; + } + } + + if (!n->fbo) { + QSize minFboSize = d->sceneGraphContext()->minimumFBOSize(); + QSize fboSize(qMax(minFboSize.width(), width()), + qMax(minFboSize.height(), height())); + n->fbo = n->renderer->createFramebufferObject(fboSize); + + GLuint displayTexture = n->fbo->texture(); + + if (n->fbo->format().samples() > 0) { + n->msDisplayFbo = new QOpenGLFramebufferObject(n->fbo->size()); + displayTexture = n->msDisplayFbo->texture(); + } + + n->setTexture(window()->createTextureFromId(displayTexture, + n->fbo->size(), + QQuickWindow::TextureHasAlphaChannel)); + } + + n->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest); + n->setRect(0, 0, width(), height()); + + n->scheduleRender(); + + return n; +} + +/*! + * \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(0) +{ +} + +/*! + * \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 : 0; +} + +/*! + * \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. + */ + +/*! + * 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. + */ +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(); +} + + +#include "qquickframebufferobject.moc" + +QT_END_NAMESPACE diff --git a/src/quick/items/qquickframebufferobject.h b/src/quick/items/qquickframebufferobject.h new file mode 100644 index 0000000000..789679d74c --- /dev/null +++ b/src/quick/items/qquickframebufferobject.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKFRAMEBUFFEROBJECT_H +#define QQUICKFRAMEBUFFEROBJECT_H + +#include + +QT_BEGIN_NAMESPACE + + +class QOpenGLFramebufferObject; +class QQuickFramebufferObjectPrivate; +class QSGFramebufferObjectNode; + +class Q_QUICK_EXPORT QQuickFramebufferObject : public QQuickItem +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickFramebufferObject) + + Q_PROPERTY(bool textureFollowsItemSize READ textureFollowsItemSize WRITE setTextureFollowsItemSize NOTIFY textureFollowsItemSizeChanged) + +public: + + class Q_QUICK_EXPORT Renderer { + protected: + Renderer(); + virtual ~Renderer(); + virtual void render() = 0; + virtual QOpenGLFramebufferObject *createFramebufferObject(const QSize &size); + virtual void synchronize(QQuickFramebufferObject *); + QOpenGLFramebufferObject *framebufferObject() const; + void update(); + void invalidateFramebufferObject(); + private: + friend class QSGFramebufferObjectNode; + friend class QQuickFramebufferObject; + void *data; + }; + + QQuickFramebufferObject(QQuickItem *parent = 0); + + bool textureFollowsItemSize() const; + void setTextureFollowsItemSize(bool follows); + + virtual Renderer *createRenderer() const = 0; + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + +protected: + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + +Q_SIGNALS: + void textureFollowsItemSizeChanged(bool); +}; + +QT_END_NAMESPACE + +#endif // QQUICKFRAMEBUFFEROBJECT_H -- cgit v1.2.3