1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qsgshadereffectsource_p.h"
44 #include "qsgitem_p.h"
45 #include "qsgcanvas_p.h"
46 #include <private/qsgadaptationlayer_p.h>
47 #include <private/qsgrenderer_p.h>
49 #include "qglframebufferobject.h"
51 #include <private/qsgtexture_p.h>
55 DEFINE_BOOL_CONFIG_OPTION(qmlFboOverlay, QML_FBO_OVERLAY)
57 class QSGShaderEffectSourceTextureProvider : public QSGTextureProvider
61 QSGShaderEffectSourceTextureProvider()
66 QSGTexture *texture() const {
67 sourceTexture->setMipmapFiltering(mipmapFiltering);
68 sourceTexture->setFiltering(filtering);
69 sourceTexture->setHorizontalWrapMode(horizontalWrap);
70 sourceTexture->setVerticalWrapMode(verticalWrap);
74 QSGShaderEffectTexture *sourceTexture;
76 QSGTexture::Filtering mipmapFiltering;
77 QSGTexture::Filtering filtering;
78 QSGTexture::WrapMode horizontalWrap;
79 QSGTexture::WrapMode verticalWrap;
81 #include "qsgshadereffectsource.moc"
84 QSGShaderEffectSourceNode::QSGShaderEffectSourceNode()
86 setFlag(UsePreprocess, true);
89 void QSGShaderEffectSourceNode::markDirtyTexture()
91 markDirty(DirtyMaterial);
95 QSGShaderEffectTexture::QSGShaderEffectTexture(QSGItem *shaderSource)
99 , m_shaderSource(shaderSource)
103 #ifdef QSG_DEBUG_FBO_OVERLAY
110 , m_dirtyTexture(true)
111 , m_multisamplingSupportChecked(false)
112 , m_multisampling(false)
117 QSGShaderEffectTexture::~QSGShaderEffectTexture()
121 delete m_secondaryFbo;
122 #ifdef QSG_DEBUG_FBO_OVERLAY
123 delete m_debugOverlay;
127 void QSGShaderEffectTexture::scheduleForCleanup()
130 m_context->scheduleTextureForCleanup(this);
132 // Never really been used, hence we can delete it right away..
139 int QSGShaderEffectTexture::textureId() const
141 return m_fbo ? m_fbo->texture() : 0;
144 bool QSGShaderEffectTexture::hasAlphaChannel() const
146 return m_format != GL_RGB;
149 bool QSGShaderEffectTexture::hasMipmaps() const
155 void QSGShaderEffectTexture::bind()
158 if (!m_recursive && m_fbo && ((m_multisampling && m_secondaryFbo->isBound()) || m_fbo->isBound()))
159 qWarning("ShaderEffectSource: \'recursive\' must be set to true when rendering recursively.");
161 glBindTexture(GL_TEXTURE_2D, m_fbo ? m_fbo->texture() : 0);
165 bool QSGShaderEffectTexture::updateTexture()
167 if ((m_live || m_grab) && m_dirtyTexture) {
175 void QSGShaderEffectTexture::setHasMipmaps(bool mipmap)
177 if (mipmap == m_mipmap)
180 if (m_mipmap && m_fbo && !m_fbo->format().mipmap())
185 void QSGShaderEffectTexture::setItem(QSGNode *item)
193 void QSGShaderEffectTexture::setRect(const QRectF &rect)
201 void QSGShaderEffectTexture::setSize(const QSize &size)
209 void QSGShaderEffectTexture::setFormat(GLenum format)
211 if (format == m_format)
217 void QSGShaderEffectTexture::setLive(bool live)
225 void QSGShaderEffectTexture::scheduleUpdate()
231 emit textureChanged();
234 void QSGShaderEffectTexture::setRecursive(bool recursive)
236 m_recursive = recursive;
239 void QSGShaderEffectTexture::markDirtyTexture()
241 m_dirtyTexture = true;
242 if (m_live || m_grab)
243 emit textureChanged();
246 void QSGShaderEffectTexture::grab()
248 if (!m_item || m_size.isNull()) {
250 delete m_secondaryFbo;
251 m_fbo = m_secondaryFbo = 0;
252 m_dirtyTexture = false;
255 QSGNode *root = m_item;
256 while (root->firstChild() && root->type() != QSGNode::RootNodeType)
257 root = root->firstChild();
258 if (root->type() != QSGNode::RootNodeType)
261 if (m_size.isEmpty()) {
263 delete m_secondaryFbo;
264 m_secondaryFbo = m_fbo = 0;
269 m_context = QSGItemPrivate::get(m_shaderSource)->sceneGraphContext();
270 Q_ASSERT(QSGItemPrivate::get(m_shaderSource)->sceneGraphContext() == m_context);
273 m_renderer = m_context->createRenderer();
274 connect(m_renderer, SIGNAL(sceneGraphChanged()), this, SLOT(markDirtyTexture()), Qt::DirectConnection);
276 m_renderer->setRootNode(static_cast<QSGRootNode *>(root));
278 bool deleteFboLater = false;
279 if (!m_fbo || m_fbo->size() != m_size || m_fbo->format().internalTextureFormat() != m_format
280 || (!m_fbo->format().mipmap() && m_mipmap))
282 if (!m_multisamplingSupportChecked) {
283 QList<QByteArray> extensions = QByteArray((const char *)glGetString(GL_EXTENSIONS)).split(' ');
284 m_multisampling = extensions.contains("GL_EXT_framebuffer_multisample")
285 && extensions.contains("GL_EXT_framebuffer_blit");
286 m_multisamplingSupportChecked = true;
288 if (m_multisampling) {
289 // Don't delete the FBO right away in case it is used recursively.
290 deleteFboLater = true;
291 delete m_secondaryFbo;
292 QGLFramebufferObjectFormat format;
294 format.setAttachment(QGLFramebufferObject::CombinedDepthStencil);
295 format.setInternalTextureFormat(m_format);
296 format.setSamples(8);
297 m_secondaryFbo = new QGLFramebufferObject(m_size, format);
299 QGLFramebufferObjectFormat format;
300 format.setAttachment(QGLFramebufferObject::CombinedDepthStencil);
301 format.setInternalTextureFormat(m_format);
302 format.setMipmap(m_mipmap);
304 deleteFboLater = true;
305 delete m_secondaryFbo;
306 m_secondaryFbo = new QGLFramebufferObject(m_size, format);
307 glBindTexture(GL_TEXTURE_2D, m_secondaryFbo->texture());
308 updateBindOptions(true);
311 delete m_secondaryFbo;
312 m_fbo = new QGLFramebufferObject(m_size, format);
314 glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
315 updateBindOptions(true);
320 if (m_recursive && !m_secondaryFbo) {
321 // m_fbo already created, m_recursive was just set.
323 Q_ASSERT(!m_multisampling);
325 m_secondaryFbo = new QGLFramebufferObject(m_size, m_fbo->format());
326 glBindTexture(GL_TEXTURE_2D, m_secondaryFbo->texture());
327 updateBindOptions(true);
331 root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
332 m_renderer->nodeChanged(root, QSGNode::DirtyForceUpdate); // Force render list update.
334 #ifdef QSG_DEBUG_FBO_OVERLAY
335 if (qmlFboOverlay()) {
337 m_debugOverlay = m_context->createRectangleNode();
338 m_debugOverlay->setRect(QRectF(0, 0, m_size.width(), m_size.height()));
339 m_debugOverlay->setColor(QColor(0xff, 0x00, 0x80, 0x40));
340 m_debugOverlay->setPenColor(QColor());
341 m_debugOverlay->setPenWidth(0);
342 m_debugOverlay->setRadius(0);
343 m_debugOverlay->update();
344 root->appendChildNode(m_debugOverlay);
348 m_dirtyTexture = false;
350 const QGLContext *ctx = m_context->glContext();
351 m_renderer->setDeviceRect(m_size);
352 m_renderer->setViewportRect(m_size);
353 QRectF mirrored(m_rect.left(), m_rect.bottom(), m_rect.width(), -m_rect.height());
354 m_renderer->setProjectionMatrixToRect(mirrored);
355 m_renderer->setClearColor(Qt::transparent);
357 if (m_multisampling) {
358 m_renderer->renderScene(QSGBindableFbo(m_secondaryFbo));
360 if (deleteFboLater) {
362 QGLFramebufferObjectFormat format;
363 format.setInternalTextureFormat(m_format);
364 format.setAttachment(QGLFramebufferObject::NoAttachment);
365 format.setMipmap(m_mipmap);
366 format.setSamples(0);
367 m_fbo = new QGLFramebufferObject(m_size, format);
368 glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
369 updateBindOptions(true);
372 QRect r(QPoint(), m_size);
373 QGLFramebufferObject::blitFramebuffer(m_fbo, r, m_secondaryFbo, r);
376 m_renderer->renderScene(QSGBindableFbo(m_secondaryFbo));
378 if (deleteFboLater) {
380 QGLFramebufferObjectFormat format;
381 format.setAttachment(QGLFramebufferObject::CombinedDepthStencil);
382 format.setInternalTextureFormat(m_format);
383 format.setMipmap(m_mipmap);
384 m_fbo = new QGLFramebufferObject(m_size, format);
385 glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
386 updateBindOptions(true);
388 qSwap(m_fbo, m_secondaryFbo);
390 m_renderer->renderScene(QSGBindableFbo(m_fbo));
395 glBindTexture(GL_TEXTURE_2D, textureId());
396 ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D);
399 root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update.
401 #ifdef QSG_DEBUG_FBO_OVERLAY
403 root->removeChildNode(m_debugOverlay);
406 markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
410 \qmlclass ShaderEffectSource QSGShaderEffectSource
412 \ingroup qml-basic-visual-elements
413 \brief The ShaderEffectSource element renders a QML element into a texture
417 The ShaderEffectSource element renders \l sourceItem into a texture and
418 displays it in the scene. \l sourceItem is drawn into the texture as though
419 it was a fully opaque root element. Thus \l sourceItem itself can be
420 invisible, but still appear in the texture.
422 ShaderEffectSource can be used as:
424 \o a texture source in a \l ShaderEffect.
425 This allows you to apply custom shader effects to any QML element.
426 \o a cache for a complex element.
427 The complex element can be rendered once into the texture, which can
428 then be animated freely without the need to render the complex element
431 ShaderEffectSource allows you to apply an opacity to elements as a group
432 rather than each element individually.
437 \o \image declarative-shadereffectsource.png
445 GradientStop { position: 0; color: "white" }
446 GradientStop { position: 1; color: "black" }
452 width: 100; height: 100
453 Rectangle { x: 5; y: 5; width: 60; height: 60; color: "red" }
454 Rectangle { x: 20; y: 20; width: 60; height: 60; color: "orange" }
455 Rectangle { x: 35; y: 35; width: 60; height: 60; color: "yellow" }
458 width: 100; height: 100
467 The ShaderEffectSource element does not redirect any mouse or keyboard
468 input to \l sourceItem. If you hide the \l sourceItem by setting
469 \l{Item::visible}{visible} to false or \l{Item::opacity}{opacity} to zero,
470 it will no longer react to input. In cases where the ShaderEffectSource is
471 meant to replace the \l sourceItem, you typically want to hide the
472 \l sourceItem while still handling input. For this, you can use
473 the \l hideSource property.
475 \note If \l sourceItem is a \l Rectangle with border, by default half the
476 border width falls outside the texture. To get the whole border, you can
477 extend the \l sourceRect.
479 \warning In most cases, using a ShaderEffectSource will decrease
480 performance, and in all cases, it will increase video memory usage.
481 Rendering through a ShaderEffectSource might also lead to lower quality
482 since some OpenGL implementations support multisampled backbuffer,
483 but not multisampled framebuffer objects.
486 QSGShaderEffectSource::QSGShaderEffectSource(QSGItem *parent)
489 , m_wrapMode(ClampToEdge)
491 , m_textureSize(0, 0)
494 , m_hideSource(false)
499 setFlag(ItemHasContents);
500 m_texture = new QSGShaderEffectTexture(this);
501 connect(m_texture, SIGNAL(textureChanged()), this, SLOT(update()));
504 QSGShaderEffectSource::~QSGShaderEffectSource()
506 m_texture->scheduleForCleanup();
509 m_provider->deleteLater();
512 QSGItemPrivate::get(m_sourceItem)->derefFromEffectItem(m_hideSource);
515 QSGTextureProvider *QSGShaderEffectSource::textureProvider() const
518 // Make sure it gets thread affinity on the rendering thread so deletion works properly..
519 Q_ASSERT_X(QSGItemPrivate::get(this)->canvas
520 && QSGItemPrivate::get(this)->sceneGraphContext()
521 && QThread::currentThread() == QSGItemPrivate::get(this)->sceneGraphContext()->thread(),
522 "QSGShaderEffectSource::textureProvider",
523 "Cannot be used outside the GUI thread");
524 const_cast<QSGShaderEffectSource *>(this)->m_provider = new QSGShaderEffectSourceTextureProvider();
525 connect(m_texture, SIGNAL(textureChanged()), m_provider, SIGNAL(textureChanged()), Qt::DirectConnection);
526 m_provider->sourceTexture = m_texture;
532 \qmlproperty enumeration ShaderEffectSource::wrapMode
534 This property defines the OpenGL wrap modes associated with the texture.
535 Modifying this property makes most sense when the element is used as a
536 source texture of a \l ShaderEffect.
539 \o ShaderEffectSource.ClampToEdge - GL_CLAMP_TO_EDGE both horizontally and vertically
540 \o ShaderEffectSource.RepeatHorizontally - GL_REPEAT horizontally, GL_CLAMP_TO_EDGE vertically
541 \o ShaderEffectSource.RepeatVertically - GL_CLAMP_TO_EDGE horizontally, GL_REPEAT vertically
542 \o ShaderEffectSource.Repeat - GL_REPEAT both horizontally and vertically
545 \note Some OpenGL ES 2 implementations do not support the GL_REPEAT
546 wrap mode with non-power-of-two textures.
549 QSGShaderEffectSource::WrapMode QSGShaderEffectSource::wrapMode() const
554 void QSGShaderEffectSource::setWrapMode(WrapMode mode)
556 if (mode == m_wrapMode)
560 emit wrapModeChanged();
564 \qmlproperty Item ShaderEffectSource::sourceItem
566 This property holds the element to be rendered into the texture.
569 QSGItem *QSGShaderEffectSource::sourceItem() const
574 void QSGShaderEffectSource::setSourceItem(QSGItem *item)
576 if (item == m_sourceItem)
579 QSGItemPrivate::get(m_sourceItem)->derefFromEffectItem(m_hideSource);
582 // TODO: Find better solution.
583 // 'm_sourceItem' needs a canvas to get a scenegraph node.
584 // The easiest way to make sure it gets a canvas is to
585 // make it a part of the same item tree as 'this'.
586 if (m_sourceItem->parentItem() == 0) {
587 m_sourceItem->setParentItem(this);
588 m_sourceItem->setVisible(false);
590 QSGItemPrivate::get(m_sourceItem)->refFromEffectItem(m_hideSource);
593 emit sourceItemChanged();
597 \qmlproperty rect ShaderEffectSource::sourceRect
599 This property defines which rectangular area of the \l sourceItem to
600 render into the texture. The source rectangle can be larger than
601 \l sourceItem itself. If the rectangle is null, which is the default,
602 the whole \l sourceItem is rendered to texture.
605 QRectF QSGShaderEffectSource::sourceRect() const
610 void QSGShaderEffectSource::setSourceRect(const QRectF &rect)
612 if (rect == m_sourceRect)
616 emit sourceRectChanged();
620 \qmlproperty size ShaderEffectSource::textureSize
622 This property holds the size of the texture. If it is empty, which is the
623 default, the size of the source rectangle is used.
626 QSize QSGShaderEffectSource::textureSize() const
628 return m_textureSize;
631 void QSGShaderEffectSource::setTextureSize(const QSize &size)
633 if (size == m_textureSize)
635 m_textureSize = size;
637 emit textureSizeChanged();
641 \qmlproperty enumeration ShaderEffectSource::format
643 This property defines the internal OpenGL format of the texture.
644 Modifying this property makes most sense when the element is used as a
645 source texture of a \l ShaderEffect. Depending on the OpenGL
646 implementation, this property might allow you to save some texture memory.
649 \o ShaderEffectSource.Alpha - GL_ALPHA
650 \o ShaderEffectSource.RGB - GL_RGB
651 \o ShaderEffectSource.RGBA - GL_RGBA
654 \note Some OpenGL implementations do not support the GL_ALPHA format.
657 QSGShaderEffectSource::Format QSGShaderEffectSource::format() const
662 void QSGShaderEffectSource::setFormat(QSGShaderEffectSource::Format format)
664 if (format == m_format)
668 emit formatChanged();
672 \qmlproperty bool ShaderEffectSource::live
674 If this property is true, the texture is updated whenever the
675 \l sourceItem changes. Otherwise, it will be a frozen image of the
676 \l sourceItem. The property is true by default.
679 bool QSGShaderEffectSource::live() const
684 void QSGShaderEffectSource::setLive(bool live)
694 \qmlproperty bool ShaderEffectSource::hideSource
696 If this property is true, the \l sourceItem is hidden, though it will still
697 be rendered into the texture. As opposed to hiding the \l sourceItem by
698 setting \l{Item::visible}{visible} to false, setting this property to true
699 will not prevent mouse or keyboard input from reaching \l sourceItem.
700 The property is useful when the ShaderEffectSource is anchored on top of,
701 and meant to replace the \l sourceItem.
704 bool QSGShaderEffectSource::hideSource() const
709 void QSGShaderEffectSource::setHideSource(bool hide)
711 if (hide == m_hideSource)
714 QSGItemPrivate::get(m_sourceItem)->refFromEffectItem(hide);
715 QSGItemPrivate::get(m_sourceItem)->derefFromEffectItem(m_hideSource);
719 emit hideSourceChanged();
723 \qmlproperty bool ShaderEffectSource::mipmap
725 If this property is true, mipmaps are generated for the texture.
727 \note Some OpenGL ES 2 implementations do not support mipmapping of
728 non-power-of-two textures.
731 bool QSGShaderEffectSource::mipmap() const
736 void QSGShaderEffectSource::setMipmap(bool enabled)
738 if (enabled == m_mipmap)
742 emit mipmapChanged();
746 \qmlproperty bool ShaderEffectSource::recursive
748 Set this property to true if the ShaderEffectSource has a dependency on
749 itself. ShaderEffectSources form a dependency chain, where one
750 ShaderEffectSource can be part of the \l sourceItem of another.
751 If there is a loop in this chain, a ShaderEffectSource could end up trying
752 to render into the same texture it is using as source, which is not allowed
753 by OpenGL. When this property is set to true, an extra texture is allocated
754 so that ShaderEffectSource can keep a copy of the texture from the previous
755 frame. It can then render into one texture and use the texture from the
756 previous frame as source.
758 Setting both this property and \l live to true will cause the scene graph
759 to render continuously. Since the ShaderEffectSource depends on itself,
760 updating it means that it immediately becomes dirty again.
763 bool QSGShaderEffectSource::recursive() const
768 void QSGShaderEffectSource::setRecursive(bool enabled)
770 if (enabled == m_recursive)
772 m_recursive = enabled;
773 emit recursiveChanged();
777 \qmlmethod ShaderEffectSource::scheduleUpdate()
779 Schedules a re-rendering of the texture for the next frame.
780 Use this to update the texture when \l live is false.
783 void QSGShaderEffectSource::scheduleUpdate()
791 static void get_wrap_mode(QSGShaderEffectSource::WrapMode mode, QSGTexture::WrapMode *hWrap, QSGTexture::WrapMode *vWrap)
794 case QSGShaderEffectSource::RepeatHorizontally:
795 *hWrap = QSGTexture::Repeat;
796 *vWrap = QSGTexture::ClampToEdge;
798 case QSGShaderEffectSource::RepeatVertically:
799 *vWrap = QSGTexture::Repeat;
800 *hWrap = QSGTexture::ClampToEdge;
802 case QSGShaderEffectSource::Repeat:
803 *hWrap = *vWrap = QSGTexture::Repeat;
806 // QSGShaderEffectSource::ClampToEdge
807 *hWrap = *vWrap = QSGTexture::ClampToEdge;
813 QSGNode *QSGShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
820 QSGShaderEffectTexture *tex = qobject_cast<QSGShaderEffectTexture *>(m_texture);
821 tex->setLive(m_live);
822 tex->setItem(QSGItemPrivate::get(m_sourceItem)->itemNode());
823 QRectF sourceRect = m_sourceRect.isNull()
824 ? QRectF(0, 0, m_sourceItem->width(), m_sourceItem->height())
826 tex->setRect(sourceRect);
827 QSize textureSize = m_textureSize.isEmpty()
828 ? QSize(qCeil(qAbs(sourceRect.width())), qCeil(qAbs(sourceRect.height())))
830 tex->setSize(textureSize);
831 tex->setRecursive(m_recursive);
832 tex->setFormat(GLenum(m_format));
833 tex->setHasMipmaps(m_mipmap);
836 tex->scheduleUpdate();
839 QSGTexture::Filtering filtering = QSGItemPrivate::get(this)->smooth
841 : QSGTexture::Nearest;
842 QSGTexture::Filtering mmFiltering = m_mipmap ? filtering : QSGTexture::None;
843 QSGTexture::WrapMode hWrap, vWrap;
844 get_wrap_mode(m_wrapMode, &hWrap, &vWrap);
847 m_provider->mipmapFiltering = mmFiltering;
848 m_provider->filtering = filtering;
849 m_provider->horizontalWrap = hWrap;
850 m_provider->verticalWrap = vWrap;
853 // Don't create the paint node if we're not spanning any area
854 if (width() == 0 || height() == 0) {
859 QSGShaderEffectSourceNode *node = static_cast<QSGShaderEffectSourceNode *>(oldNode);
861 node = new QSGShaderEffectSourceNode;
862 node->setTexture(m_texture);
863 connect(m_texture, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
866 // If live and recursive, update continuously.
867 if (m_live && m_recursive)
868 node->markDirty(QSGNode::DirtyMaterial);
870 node->setMipmapFiltering(mmFiltering);
871 node->setFiltering(filtering);
872 node->setHorizontalWrapMode(hWrap);
873 node->setVerticalWrapMode(vWrap);
874 node->setTargetRect(QRectF(0, 0, width(), height()));
875 node->setSourceRect(QRectF(0, 0, 1, 1));