Avoid direct GL calls in Quick
[qt:qtdeclarative.git] / src / quick / items / qquickshadereffectsource.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
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 Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickshadereffectsource_p.h"
43
44 #include "qquickitem_p.h"
45 #include "qquickwindow_p.h"
46 #include <private/qsgadaptationlayer_p.h>
47 #include <QtQuick/private/qsgrenderer_p.h>
48 #include <qsgsimplerectnode.h>
49
50 #include "qopenglframebufferobject.h"
51 #include "qmath.h"
52 #include <QtQuick/private/qsgtexture_p.h>
53
54 QT_BEGIN_NAMESPACE
55
56 DEFINE_BOOL_CONFIG_OPTION(qmlFboOverlay, QML_FBO_OVERLAY)
57 DEFINE_BOOL_CONFIG_OPTION(qmlFboFlushBeforeDetach, QML_FBO_FLUSH_BEFORE_DETACH)
58
59 namespace
60 {
61     class BindableFbo : public QSGBindable
62     {
63     public:
64         BindableFbo(QOpenGLFramebufferObject *fbo, QSGDepthStencilBuffer *depthStencil);
65         virtual ~BindableFbo();
66         virtual void bind() const;
67     private:
68         QOpenGLFramebufferObject *m_fbo;
69         QSGDepthStencilBuffer *m_depthStencil;
70     };
71
72     BindableFbo::BindableFbo(QOpenGLFramebufferObject *fbo, QSGDepthStencilBuffer *depthStencil)
73         : m_fbo(fbo)
74         , m_depthStencil(depthStencil)
75     {
76     }
77
78     BindableFbo::~BindableFbo()
79     {
80         if (qmlFboFlushBeforeDetach())
81             QOpenGLContext::currentContext()->functions()->glFlush();
82         if (m_depthStencil)
83             m_depthStencil->detach();
84     }
85
86     void BindableFbo::bind() const
87     {
88         m_fbo->bind();
89         if (m_depthStencil)
90             m_depthStencil->attach();
91     }
92 }
93
94 class QQuickShaderEffectSourceTextureProvider : public QSGTextureProvider
95 {
96     Q_OBJECT
97 public:
98     QQuickShaderEffectSourceTextureProvider()
99         : sourceTexture(0)
100         , mipmapFiltering(QSGTexture::None)
101         , filtering(QSGTexture::Nearest)
102         , horizontalWrap(QSGTexture::ClampToEdge)
103         , verticalWrap(QSGTexture::ClampToEdge)
104     {
105     }
106
107     QSGTexture *texture() const {
108         sourceTexture->setMipmapFiltering(mipmapFiltering);
109         sourceTexture->setFiltering(filtering);
110         sourceTexture->setHorizontalWrapMode(horizontalWrap);
111         sourceTexture->setVerticalWrapMode(verticalWrap);
112         return sourceTexture;
113     }
114
115     QQuickShaderEffectTexture *sourceTexture;
116
117     QSGTexture::Filtering mipmapFiltering;
118     QSGTexture::Filtering filtering;
119     QSGTexture::WrapMode horizontalWrap;
120     QSGTexture::WrapMode verticalWrap;
121 };
122 #include "qquickshadereffectsource.moc"
123
124
125 QQuickShaderEffectSourceNode::QQuickShaderEffectSourceNode()
126 {
127     setFlag(UsePreprocess, true);
128 }
129
130 void QQuickShaderEffectSourceNode::markDirtyTexture()
131 {
132     markDirty(DirtyMaterial);
133 }
134
135
136 QQuickShaderEffectTexture::QQuickShaderEffectTexture(QQuickItem *shaderSource)
137     : QSGDynamicTexture()
138     , m_item(0)
139     , m_device_pixel_ratio(1)
140     , m_format(GL_RGBA)
141     , m_renderer(0)
142     , m_fbo(0)
143     , m_secondaryFbo(0)
144     , m_transparentTexture(0)
145 #ifdef QSG_DEBUG_FBO_OVERLAY
146     , m_debugOverlay(0)
147 #endif
148     , m_context(QQuickItemPrivate::get(shaderSource)->sceneGraphRenderContext())
149     , m_mipmap(false)
150     , m_live(true)
151     , m_recursive(false)
152     , m_dirtyTexture(true)
153     , m_multisamplingChecked(false)
154     , m_multisampling(false)
155     , m_grab(false)
156 {
157 }
158
159 QQuickShaderEffectTexture::~QQuickShaderEffectTexture()
160 {
161     invalidated();
162 }
163
164 void QQuickShaderEffectTexture::invalidated()
165 {
166     delete m_renderer;
167     m_renderer = 0;
168     delete m_fbo;
169     delete m_secondaryFbo;
170     m_fbo = m_secondaryFbo = 0;
171 #ifdef QSG_DEBUG_FBO_OVERLAY
172     delete m_debugOverlay;
173     m_debugOverlay = 0;
174 #endif
175     if (m_transparentTexture) {
176         QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_transparentTexture);
177         m_transparentTexture = 0;
178     }
179 }
180
181 int QQuickShaderEffectTexture::textureId() const
182 {
183     return m_fbo ? m_fbo->texture() : 0;
184 }
185
186 bool QQuickShaderEffectTexture::hasAlphaChannel() const
187 {
188     return m_format != GL_RGB;
189 }
190
191 bool QQuickShaderEffectTexture::hasMipmaps() const
192 {
193     return m_mipmap;
194 }
195
196
197 void QQuickShaderEffectTexture::bind()
198 {
199 #ifndef QT_NO_DEBUG
200     if (!m_recursive && m_fbo && ((m_multisampling && m_secondaryFbo->isBound()) || m_fbo->isBound()))
201         qWarning("ShaderEffectSource: \'recursive\' must be set to true when rendering recursively.");
202 #endif
203     QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
204     if (!m_fbo && m_format == GL_RGBA) {
205         if (m_transparentTexture == 0) {
206             funcs->glGenTextures(1, &m_transparentTexture);
207             funcs->glBindTexture(GL_TEXTURE_2D, m_transparentTexture);
208             const uint zero = 0;
209             funcs->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &zero);
210         } else {
211             funcs->glBindTexture(GL_TEXTURE_2D, m_transparentTexture);
212         }
213     } else {
214         funcs->glBindTexture(GL_TEXTURE_2D, m_fbo ? m_fbo->texture() : 0);
215         updateBindOptions();
216     }
217 }
218
219 bool QQuickShaderEffectTexture::updateTexture()
220 {
221     bool doGrab = (m_live || m_grab) && m_dirtyTexture;
222     if (doGrab)
223         grab();
224     if (m_grab)
225         emit scheduledUpdateCompleted();
226     m_grab = false;
227     return doGrab;
228 }
229
230 void QQuickShaderEffectTexture::setHasMipmaps(bool mipmap)
231 {
232     if (mipmap == m_mipmap)
233         return;
234     m_mipmap = mipmap;
235     if (m_mipmap && m_fbo && !m_fbo->format().mipmap())
236         markDirtyTexture();
237 }
238
239
240 void QQuickShaderEffectTexture::setItem(QSGNode *item)
241 {
242     if (item == m_item)
243         return;
244     m_item = item;
245
246     if (m_live && !m_item) {
247         delete m_fbo;
248         delete m_secondaryFbo;
249         m_fbo = m_secondaryFbo = 0;
250         m_depthStencilBuffer.clear();
251     }
252
253     markDirtyTexture();
254 }
255
256 void QQuickShaderEffectTexture::setRect(const QRectF &rect)
257 {
258     if (rect == m_rect)
259         return;
260     m_rect = rect;
261     markDirtyTexture();
262 }
263
264 void QQuickShaderEffectTexture::setSize(const QSize &size)
265 {
266     if (size == m_size)
267         return;
268     m_size = size;
269
270     if (m_live && m_size.isNull()) {
271         delete m_fbo;
272         delete m_secondaryFbo;
273         m_fbo = m_secondaryFbo = 0;
274         m_depthStencilBuffer.clear();
275     }
276
277     markDirtyTexture();
278 }
279
280 void QQuickShaderEffectTexture::setFormat(GLenum format)
281 {
282     if (format == m_format)
283         return;
284     m_format = format;
285     markDirtyTexture();
286 }
287
288 void QQuickShaderEffectTexture::setLive(bool live)
289 {
290     if (live == m_live)
291         return;
292     m_live = live;
293
294     if (m_live && (!m_item || m_size.isNull())) {
295         delete m_fbo;
296         delete m_secondaryFbo;
297         m_fbo = m_secondaryFbo = 0;
298         m_depthStencilBuffer.clear();
299     }
300
301     markDirtyTexture();
302 }
303
304 void QQuickShaderEffectTexture::scheduleUpdate()
305 {
306     if (m_grab)
307         return;
308     m_grab = true;
309     if (m_dirtyTexture)
310         emit updateRequested();
311 }
312
313 void QQuickShaderEffectTexture::setRecursive(bool recursive)
314 {
315     m_recursive = recursive;
316 }
317
318 void QQuickShaderEffectTexture::markDirtyTexture()
319 {
320     m_dirtyTexture = true;
321     if (m_live || m_grab)
322         emit updateRequested();
323 }
324
325 void QQuickShaderEffectTexture::grab()
326 {
327     if (!m_item || m_size.isNull()) {
328         delete m_fbo;
329         delete m_secondaryFbo;
330         m_fbo = m_secondaryFbo = 0;
331         m_depthStencilBuffer.clear();
332         m_dirtyTexture = false;
333         return;
334     }
335     QSGNode *root = m_item;
336     while (root->firstChild() && root->type() != QSGNode::RootNodeType)
337         root = root->firstChild();
338     if (root->type() != QSGNode::RootNodeType)
339         return;
340
341     if (!m_renderer) {
342         m_renderer = m_context->createRenderer();
343         connect(m_renderer, SIGNAL(sceneGraphChanged()), this, SLOT(markDirtyTexture()));
344     }
345     m_renderer->setDevicePixelRatio(m_device_pixel_ratio);
346     m_renderer->setRootNode(static_cast<QSGRootNode *>(root));
347
348     QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
349     bool deleteFboLater = false;
350     if (!m_fbo || m_fbo->size() != m_size || m_fbo->format().internalTextureFormat() != m_format
351         || (!m_fbo->format().mipmap() && m_mipmap))
352     {
353         if (!m_multisamplingChecked) {
354             if (m_context->openglContext()->format().samples() <= 1) {
355                 m_multisampling = false;
356             } else {
357                 const QSet<QByteArray> extensions = m_context->openglContext()->extensions();
358                 m_multisampling = extensions.contains(QByteArrayLiteral("GL_EXT_framebuffer_multisample"))
359                     && extensions.contains(QByteArrayLiteral("GL_EXT_framebuffer_blit"));
360             }
361             m_multisamplingChecked = true;
362         }
363         if (m_multisampling) {
364             // Don't delete the FBO right away in case it is used recursively.
365             deleteFboLater = true;
366             delete m_secondaryFbo;
367             QOpenGLFramebufferObjectFormat format;
368
369             format.setInternalTextureFormat(m_format);
370             format.setSamples(m_context->openglContext()->format().samples());
371             m_secondaryFbo = new QOpenGLFramebufferObject(m_size, format);
372             m_depthStencilBuffer = m_context->depthStencilBufferForFbo(m_secondaryFbo);
373         } else {
374             QOpenGLFramebufferObjectFormat format;
375             format.setInternalTextureFormat(m_format);
376             format.setMipmap(m_mipmap);
377             if (m_recursive) {
378                 deleteFboLater = true;
379                 delete m_secondaryFbo;
380                 m_secondaryFbo = new QOpenGLFramebufferObject(m_size, format);
381                 funcs->glBindTexture(GL_TEXTURE_2D, m_secondaryFbo->texture());
382                 updateBindOptions(true);
383                 m_depthStencilBuffer = m_context->depthStencilBufferForFbo(m_secondaryFbo);
384             } else {
385                 delete m_fbo;
386                 delete m_secondaryFbo;
387                 m_fbo = new QOpenGLFramebufferObject(m_size, format);
388                 m_secondaryFbo = 0;
389                 funcs->glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
390                 updateBindOptions(true);
391                 m_depthStencilBuffer = m_context->depthStencilBufferForFbo(m_fbo);
392             }
393         }
394     }
395
396     if (m_recursive && !m_secondaryFbo) {
397         // m_fbo already created, m_recursive was just set.
398         Q_ASSERT(m_fbo);
399         Q_ASSERT(!m_multisampling);
400
401         m_secondaryFbo = new QOpenGLFramebufferObject(m_size, m_fbo->format());
402         funcs->glBindTexture(GL_TEXTURE_2D, m_secondaryFbo->texture());
403         updateBindOptions(true);
404     }
405
406     // Render texture.
407     root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update.
408     m_renderer->nodeChanged(root, QSGNode::DirtyForceUpdate); // Force render list update.
409
410 #ifdef QSG_DEBUG_FBO_OVERLAY
411     if (qmlFboOverlay()) {
412         if (!m_debugOverlay)
413             m_debugOverlay = new QSGSimpleRectNode();
414         m_debugOverlay->setRect(QRectF(0, 0, m_size.width(), m_size.height()));
415         m_debugOverlay->setColor(QColor(0xff, 0x00, 0x80, 0x40));
416         root->appendChildNode(m_debugOverlay);
417     }
418 #endif
419
420     m_dirtyTexture = false;
421
422     m_renderer->setDeviceRect(m_size);
423     m_renderer->setViewportRect(m_size);
424     QRectF mirrored(m_rect.left(), m_rect.bottom(), m_rect.width(), -m_rect.height());
425     m_renderer->setProjectionMatrixToRect(mirrored);
426     m_renderer->setClearColor(Qt::transparent);
427
428     if (m_multisampling) {
429         m_renderer->renderScene(BindableFbo(m_secondaryFbo, m_depthStencilBuffer.data()));
430
431         if (deleteFboLater) {
432             delete m_fbo;
433             QOpenGLFramebufferObjectFormat format;
434             format.setInternalTextureFormat(m_format);
435             format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
436             format.setMipmap(m_mipmap);
437             format.setSamples(0);
438             m_fbo = new QOpenGLFramebufferObject(m_size, format);
439             funcs->glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
440             updateBindOptions(true);
441         }
442
443         QRect r(QPoint(), m_size);
444         QOpenGLFramebufferObject::blitFramebuffer(m_fbo, r, m_secondaryFbo, r);
445     } else {
446         if (m_recursive) {
447             m_renderer->renderScene(BindableFbo(m_secondaryFbo, m_depthStencilBuffer.data()));
448
449             if (deleteFboLater) {
450                 delete m_fbo;
451                 QOpenGLFramebufferObjectFormat format;
452                 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
453                 format.setInternalTextureFormat(m_format);
454                 format.setMipmap(m_mipmap);
455                 m_fbo = new QOpenGLFramebufferObject(m_size, format);
456                 funcs->glBindTexture(GL_TEXTURE_2D, m_fbo->texture());
457                 updateBindOptions(true);
458             }
459             qSwap(m_fbo, m_secondaryFbo);
460         } else {
461             m_renderer->renderScene(BindableFbo(m_fbo, m_depthStencilBuffer.data()));
462         }
463     }
464
465     if (m_mipmap) {
466         funcs->glBindTexture(GL_TEXTURE_2D, textureId());
467         funcs->glGenerateMipmap(GL_TEXTURE_2D);
468     }
469
470     root->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip, opacity and render list update.
471
472 #ifdef QSG_DEBUG_FBO_OVERLAY
473     if (qmlFboOverlay())
474         root->removeChildNode(m_debugOverlay);
475 #endif
476     if (m_recursive)
477         markDirtyTexture(); // Continuously update if 'live' and 'recursive'.
478 }
479
480 QImage QQuickShaderEffectTexture::toImage() const
481 {
482     if (m_fbo)
483         return m_fbo->toImage();
484
485     return QImage();
486 }
487
488 /*!
489     \qmltype ShaderEffectSource
490     \instantiates QQuickShaderEffectSource
491     \inqmlmodule QtQuick
492     \since 5.0
493     \inherits Item
494     \ingroup qtquick-effects
495     \brief Renders a \l {Qt Quick} item into a texture and displays it
496
497     The ShaderEffectSource type renders \l sourceItem into a texture and
498     displays it in the scene. \l sourceItem is drawn into the texture as though
499     it was a fully opaque root item. Thus \l sourceItem itself can be
500     invisible, but still appear in the texture.
501
502     ShaderEffectSource can be used as:
503     \list
504     \li a texture source in a \l ShaderEffect.
505        This allows you to apply custom shader effects to any \l {Qt Quick} item.
506     \li a cache for a complex item.
507        The complex item can be rendered once into the texture, which can
508        then be animated freely without the need to render the complex item
509        again every frame.
510     \li an opacity layer.
511        ShaderEffectSource allows you to apply an opacity to items as a group
512        rather than each item individually.
513     \endlist
514
515     \table
516     \row
517     \li \image declarative-shadereffectsource.png
518     \li \qml
519         import QtQuick 2.0
520
521         Rectangle {
522             width: 200
523             height: 100
524             gradient: Gradient {
525                 GradientStop { position: 0; color: "white" }
526                 GradientStop { position: 1; color: "black" }
527             }
528             Row {
529                 opacity: 0.5
530                 Item {
531                     id: foo
532                     width: 100; height: 100
533                     Rectangle { x: 5; y: 5; width: 60; height: 60; color: "red" }
534                     Rectangle { x: 20; y: 20; width: 60; height: 60; color: "orange" }
535                     Rectangle { x: 35; y: 35; width: 60; height: 60; color: "yellow" }
536                 }
537                 ShaderEffectSource {
538                     width: 100; height: 100
539                     sourceItem: foo
540                 }
541             }
542         }
543         \endqml
544     \endtable
545
546     The ShaderEffectSource type does not redirect any mouse or keyboard
547     input to \l sourceItem. If you hide the \l sourceItem by setting
548     \l{Item::visible}{visible} to false or \l{Item::opacity}{opacity} to zero,
549     it will no longer react to input. In cases where the ShaderEffectSource is
550     meant to replace the \l sourceItem, you typically want to hide the
551     \l sourceItem while still handling input. For this, you can use
552     the \l hideSource property.
553
554     \note If \l sourceItem is a \l Rectangle with border, by default half the
555     border width falls outside the texture. To get the whole border, you can
556     extend the \l sourceRect.
557
558     \note The ShaderEffectSource relies on FBO multisampling support
559     to antialias edges. If the underlying hardware does not support this,
560     which is the case for most embedded graphics chips, edges rendered
561     inside a ShaderEffectSource will not be antialiased. One way to remedy
562     this is to double the size of the effect source and render it with
563     \c {smooth: true} (this is the default value of smooth).
564     This will be equivalent to 4x multisampling, at the cost of lower performance
565     and higher memory use.
566
567     \warning In most cases, using a ShaderEffectSource will decrease
568     performance, and in all cases, it will increase video memory usage.
569     Rendering through a ShaderEffectSource might also lead to lower quality
570     since some OpenGL implementations support multisampled backbuffer,
571     but not multisampled framebuffer objects.
572 */
573
574 QQuickShaderEffectSource::QQuickShaderEffectSource(QQuickItem *parent)
575     : QQuickItem(parent)
576     , m_provider(0)
577     , m_texture(0)
578     , m_wrapMode(ClampToEdge)
579     , m_sourceItem(0)
580     , m_textureSize(0, 0)
581     , m_format(RGBA)
582     , m_live(true)
583     , m_hideSource(false)
584     , m_mipmap(false)
585     , m_recursive(false)
586     , m_grab(true)
587 {
588     setFlag(ItemHasContents);
589 }
590
591 QQuickShaderEffectSource::~QQuickShaderEffectSource()
592 {
593     if (m_texture)
594         m_texture->deleteLater();
595
596     if (m_provider)
597         m_provider->deleteLater();
598
599     if (m_sourceItem) {
600         QQuickItemPrivate *sd = QQuickItemPrivate::get(m_sourceItem);
601         sd->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
602         sd->derefFromEffectItem(m_hideSource);
603         if (window())
604             sd->derefWindow();
605     }
606 }
607
608 void QQuickShaderEffectSource::ensureTexture()
609 {
610     if (m_texture)
611         return;
612
613     Q_ASSERT_X(QQuickItemPrivate::get(this)->window
614                && QQuickItemPrivate::get(this)->sceneGraphRenderContext()
615                && QThread::currentThread() == QQuickItemPrivate::get(this)->sceneGraphRenderContext()->thread(),
616                "QQuickShaderEffectSource::ensureTexture",
617                "Cannot be used outside the rendering thread");
618
619     m_texture = new QQuickShaderEffectTexture(this);
620     connect(QQuickItemPrivate::get(this)->window, SIGNAL(sceneGraphInvalidated()), m_texture, SLOT(invalidated()), Qt::DirectConnection);
621     connect(m_texture, SIGNAL(updateRequested()), this, SLOT(update()));
622     connect(m_texture, SIGNAL(scheduledUpdateCompleted()), this, SIGNAL(scheduledUpdateCompleted()));
623 }
624
625 static void get_wrap_mode(QQuickShaderEffectSource::WrapMode mode, QSGTexture::WrapMode *hWrap, QSGTexture::WrapMode *vWrap);
626
627 QSGTextureProvider *QQuickShaderEffectSource::textureProvider() const
628 {
629     const QQuickItemPrivate *d = QQuickItemPrivate::get(this);
630     if (!d->window || !d->sceneGraphRenderContext() || QThread::currentThread() != d->sceneGraphRenderContext()->thread()) {
631         qWarning("QQuickShaderEffectSource::textureProvider: can only be queried on the rendering thread of an exposed window");
632         return 0;
633     }
634
635     if (!m_provider) {
636         const_cast<QQuickShaderEffectSource *>(this)->m_provider = new QQuickShaderEffectSourceTextureProvider();
637         const_cast<QQuickShaderEffectSource *>(this)->ensureTexture();
638         connect(m_texture, SIGNAL(updateRequested()), m_provider, SIGNAL(textureChanged()));
639
640         get_wrap_mode(m_wrapMode, &m_provider->horizontalWrap, &m_provider->verticalWrap);
641         m_provider->mipmapFiltering = mipmap() ? QSGTexture::Linear : QSGTexture::None;
642         m_provider->filtering = smooth() ? QSGTexture::Linear : QSGTexture::Nearest;
643         m_provider->sourceTexture = m_texture;
644     }
645     return m_provider;
646 }
647
648 /*!
649     \qmlproperty enumeration QtQuick::ShaderEffectSource::wrapMode
650
651     This property defines the OpenGL wrap modes associated with the texture.
652     Modifying this property makes most sense when the item is used as a
653     source texture of a \l ShaderEffect.
654
655     \list
656     \li ShaderEffectSource.ClampToEdge - GL_CLAMP_TO_EDGE both horizontally and vertically
657     \li ShaderEffectSource.RepeatHorizontally - GL_REPEAT horizontally, GL_CLAMP_TO_EDGE vertically
658     \li ShaderEffectSource.RepeatVertically - GL_CLAMP_TO_EDGE horizontally, GL_REPEAT vertically
659     \li ShaderEffectSource.Repeat - GL_REPEAT both horizontally and vertically
660     \endlist
661
662     \note Some OpenGL ES 2 implementations do not support the GL_REPEAT
663     wrap mode with non-power-of-two textures.
664 */
665
666 QQuickShaderEffectSource::WrapMode QQuickShaderEffectSource::wrapMode() const
667 {
668     return m_wrapMode;
669 }
670
671 void QQuickShaderEffectSource::setWrapMode(WrapMode mode)
672 {
673     if (mode == m_wrapMode)
674         return;
675     m_wrapMode = mode;
676     update();
677     emit wrapModeChanged();
678 }
679
680 /*!
681     \qmlproperty Item QtQuick::ShaderEffectSource::sourceItem
682
683     This property holds the item to be rendered into the texture.
684     Setting this to null while \l live is true, will release the texture
685     resources.
686 */
687
688 QQuickItem *QQuickShaderEffectSource::sourceItem() const
689 {
690     return m_sourceItem;
691 }
692
693 void QQuickShaderEffectSource::itemGeometryChanged(QQuickItem *item, const QRectF &newRect, const QRectF &oldRect)
694 {
695     Q_ASSERT(item == m_sourceItem);
696     Q_UNUSED(item);
697     if (newRect.size() != oldRect.size())
698         update();
699 }
700
701 void QQuickShaderEffectSource::setSourceItem(QQuickItem *item)
702 {
703     if (item == m_sourceItem)
704         return;
705     if (m_sourceItem) {
706         QQuickItemPrivate *d = QQuickItemPrivate::get(m_sourceItem);
707         d->derefFromEffectItem(m_hideSource);
708         d->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
709         disconnect(m_sourceItem, SIGNAL(destroyed(QObject*)), this, SLOT(sourceItemDestroyed(QObject*)));
710         if (window())
711             d->derefWindow();
712     }
713
714     if (window() == item->window()) {
715         m_sourceItem = item;
716     } else {
717         qWarning("ShaderEffectSource: sourceItem and ShaderEffectSource must both be children of the same window.");
718         m_sourceItem = 0;
719     }
720
721     if (m_sourceItem) {
722         QQuickItemPrivate *d = QQuickItemPrivate::get(item);
723         // 'item' needs a window to get a scene graph node. It usually gets one through its
724         // parent, but if the source item is "inline" rather than a reference -- i.e.
725         // "sourceItem: Item { }" instead of "sourceItem: foo" -- it will not get a parent.
726         // In those cases, 'item' should get the window from 'this'.
727         if (window())
728             d->refWindow(window());
729         d->refFromEffectItem(m_hideSource);
730         d->addItemChangeListener(this, QQuickItemPrivate::Geometry);
731         connect(m_sourceItem, SIGNAL(destroyed(QObject*)), this, SLOT(sourceItemDestroyed(QObject*)));
732     }
733     update();
734     emit sourceItemChanged();
735 }
736
737 void QQuickShaderEffectSource::sourceItemDestroyed(QObject *item)
738 {
739     Q_ASSERT(item == m_sourceItem);
740     Q_UNUSED(item);
741     m_sourceItem = 0;
742     update();
743     emit sourceItemChanged();
744 }
745
746
747 /*!
748     \qmlproperty rect QtQuick::ShaderEffectSource::sourceRect
749
750     This property defines which rectangular area of the \l sourceItem to
751     render into the texture. The source rectangle can be larger than
752     \l sourceItem itself. If the rectangle is null, which is the default,
753     the whole \l sourceItem is rendered to texture.
754 */
755
756 QRectF QQuickShaderEffectSource::sourceRect() const
757 {
758     return m_sourceRect;
759 }
760
761 void QQuickShaderEffectSource::setSourceRect(const QRectF &rect)
762 {
763     if (rect == m_sourceRect)
764         return;
765     m_sourceRect = rect;
766     update();
767     emit sourceRectChanged();
768 }
769
770 /*!
771     \qmlproperty size QtQuick::ShaderEffectSource::textureSize
772
773     This property holds the requested size of the texture. If it is empty,
774     which is the default, the size of the source rectangle is used.
775
776     \note Some platforms have a limit on how small framebuffer objects can be,
777     which means the actual texture size might be larger than the requested
778     size.
779 */
780
781 QSize QQuickShaderEffectSource::textureSize() const
782 {
783     return m_textureSize;
784 }
785
786 void QQuickShaderEffectSource::setTextureSize(const QSize &size)
787 {
788     if (size == m_textureSize)
789         return;
790     m_textureSize = size;
791     update();
792     emit textureSizeChanged();
793 }
794
795 /*!
796     \qmlproperty enumeration QtQuick::ShaderEffectSource::format
797
798     This property defines the internal OpenGL format of the texture.
799     Modifying this property makes most sense when the item is used as a
800     source texture of a \l ShaderEffect. Depending on the OpenGL
801     implementation, this property might allow you to save some texture memory.
802
803     \list
804     \li ShaderEffectSource.Alpha - GL_ALPHA
805     \li ShaderEffectSource.RGB - GL_RGB
806     \li ShaderEffectSource.RGBA - GL_RGBA
807     \endlist
808
809     \note Some OpenGL implementations do not support the GL_ALPHA format.
810 */
811
812 QQuickShaderEffectSource::Format QQuickShaderEffectSource::format() const
813 {
814     return m_format;
815 }
816
817 void QQuickShaderEffectSource::setFormat(QQuickShaderEffectSource::Format format)
818 {
819     if (format == m_format)
820         return;
821     m_format = format;
822     update();
823     emit formatChanged();
824 }
825
826 /*!
827     \qmlproperty bool QtQuick::ShaderEffectSource::live
828
829     If this property is true, the texture is updated whenever the
830     \l sourceItem updates. Otherwise, it will be a frozen image, even if
831     \l sourceItem is assigned a new item. The property is true by default.
832 */
833
834 bool QQuickShaderEffectSource::live() const
835 {
836     return m_live;
837 }
838
839 void QQuickShaderEffectSource::setLive(bool live)
840 {
841     if (live == m_live)
842         return;
843     m_live = live;
844     update();
845     emit liveChanged();
846 }
847
848 /*!
849     \qmlproperty bool QtQuick::ShaderEffectSource::hideSource
850
851     If this property is true, the \l sourceItem is hidden, though it will still
852     be rendered into the texture. As opposed to hiding the \l sourceItem by
853     setting \l{Item::visible}{visible} to false, setting this property to true
854     will not prevent mouse or keyboard input from reaching \l sourceItem.
855     The property is useful when the ShaderEffectSource is anchored on top of,
856     and meant to replace the \l sourceItem.
857 */
858
859 bool QQuickShaderEffectSource::hideSource() const
860 {
861     return m_hideSource;
862 }
863
864 void QQuickShaderEffectSource::setHideSource(bool hide)
865 {
866     if (hide == m_hideSource)
867         return;
868     if (m_sourceItem) {
869         QQuickItemPrivate::get(m_sourceItem)->refFromEffectItem(hide);
870         QQuickItemPrivate::get(m_sourceItem)->derefFromEffectItem(m_hideSource);
871     }
872     m_hideSource = hide;
873     update();
874     emit hideSourceChanged();
875 }
876
877 /*!
878     \qmlproperty bool QtQuick::ShaderEffectSource::mipmap
879
880     If this property is true, mipmaps are generated for the texture.
881
882     \note Some OpenGL ES 2 implementations do not support mipmapping of
883     non-power-of-two textures.
884 */
885
886 bool QQuickShaderEffectSource::mipmap() const
887 {
888     return m_mipmap;
889 }
890
891 void QQuickShaderEffectSource::setMipmap(bool enabled)
892 {
893     if (enabled == m_mipmap)
894         return;
895     m_mipmap = enabled;
896     update();
897     emit mipmapChanged();
898 }
899
900 /*!
901     \qmlproperty bool QtQuick::ShaderEffectSource::recursive
902
903     Set this property to true if the ShaderEffectSource has a dependency on
904     itself. ShaderEffectSources form a dependency chain, where one
905     ShaderEffectSource can be part of the \l sourceItem of another.
906     If there is a loop in this chain, a ShaderEffectSource could end up trying
907     to render into the same texture it is using as source, which is not allowed
908     by OpenGL. When this property is set to true, an extra texture is allocated
909     so that ShaderEffectSource can keep a copy of the texture from the previous
910     frame. It can then render into one texture and use the texture from the
911     previous frame as source.
912
913     Setting both this property and \l live to true will cause the scene graph
914     to render continuously. Since the ShaderEffectSource depends on itself,
915     updating it means that it immediately becomes dirty again.
916 */
917
918 bool QQuickShaderEffectSource::recursive() const
919 {
920     return m_recursive;
921 }
922
923 void QQuickShaderEffectSource::setRecursive(bool enabled)
924 {
925     if (enabled == m_recursive)
926         return;
927     m_recursive = enabled;
928     emit recursiveChanged();
929 }
930
931 /*!
932     \qmlmethod QtQuick::ShaderEffectSource::scheduleUpdate()
933
934     Schedules a re-rendering of the texture for the next frame.
935     Use this to update the texture when \l live is false.
936 */
937
938 void QQuickShaderEffectSource::scheduleUpdate()
939 {
940     if (m_grab)
941         return;
942     m_grab = true;
943     update();
944 }
945
946 static void get_wrap_mode(QQuickShaderEffectSource::WrapMode mode, QSGTexture::WrapMode *hWrap, QSGTexture::WrapMode *vWrap)
947 {
948     switch (mode) {
949     case QQuickShaderEffectSource::RepeatHorizontally:
950         *hWrap = QSGTexture::Repeat;
951         *vWrap = QSGTexture::ClampToEdge;
952         break;
953     case QQuickShaderEffectSource::RepeatVertically:
954         *vWrap = QSGTexture::Repeat;
955         *hWrap = QSGTexture::ClampToEdge;
956         break;
957     case QQuickShaderEffectSource::Repeat:
958         *hWrap = *vWrap = QSGTexture::Repeat;
959         break;
960     default:
961         // QQuickShaderEffectSource::ClampToEdge
962         *hWrap = *vWrap = QSGTexture::ClampToEdge;
963         break;
964     }
965 }
966
967
968 void QQuickShaderEffectSource::releaseResources()
969 {
970     if (m_texture) {
971         m_texture->deleteLater();
972         m_texture = 0;
973     }
974     if (m_provider) {
975         m_provider->deleteLater();
976         m_provider = 0;
977     }
978 }
979
980 QSGNode *QQuickShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
981 {
982     if (!m_sourceItem || m_sourceItem->width() <= 0 || m_sourceItem->height() <= 0) {
983         if (m_texture)
984             m_texture->setItem(0);
985         delete oldNode;
986         return 0;
987     }
988
989     ensureTexture();
990
991     m_texture->setLive(m_live);
992     m_texture->setItem(QQuickItemPrivate::get(m_sourceItem)->itemNode());
993     QRectF sourceRect = m_sourceRect.width() == 0 || m_sourceRect.height() == 0
994                       ? QRectF(0, 0, m_sourceItem->width(), m_sourceItem->height())
995                       : m_sourceRect;
996     m_texture->setRect(sourceRect);
997     QSize textureSize = m_textureSize.isEmpty()
998                       ? QSize(qCeil(qAbs(sourceRect.width())), qCeil(qAbs(sourceRect.height())))
999                       : m_textureSize;
1000     Q_ASSERT(!textureSize.isEmpty());
1001
1002     QQuickItemPrivate *d = static_cast<QQuickItemPrivate *>(QObjectPrivate::get(this));
1003
1004     // Crate large textures on high-dpi displays.
1005     if (sourceItem())
1006         textureSize *= d->window->devicePixelRatio();
1007
1008     const QSize minTextureSize = d->sceneGraphContext()->minimumFBOSize();
1009     // Keep power-of-two by doubling the size.
1010     while (textureSize.width() < minTextureSize.width())
1011         textureSize.rwidth() *= 2;
1012     while (textureSize.height() < minTextureSize.height())
1013         textureSize.rheight() *= 2;
1014
1015     m_texture->setDevicePixelRatio(d->window->devicePixelRatio());
1016     m_texture->setSize(textureSize);
1017     m_texture->setRecursive(m_recursive);
1018     m_texture->setFormat(GLenum(m_format));
1019     m_texture->setHasMipmaps(m_mipmap);
1020
1021     if (m_grab)
1022         m_texture->scheduleUpdate();
1023     m_grab = false;
1024
1025     QSGTexture::Filtering filtering = QQuickItemPrivate::get(this)->smooth
1026                                             ? QSGTexture::Linear
1027                                             : QSGTexture::Nearest;
1028     QSGTexture::Filtering mmFiltering = m_mipmap ? filtering : QSGTexture::None;
1029     QSGTexture::WrapMode hWrap, vWrap;
1030     get_wrap_mode(m_wrapMode, &hWrap, &vWrap);
1031
1032     if (m_provider) {
1033         m_provider->mipmapFiltering = mmFiltering;
1034         m_provider->filtering = filtering;
1035         m_provider->horizontalWrap = hWrap;
1036         m_provider->verticalWrap = vWrap;
1037     }
1038
1039     // Don't create the paint node if we're not spanning any area
1040     if (width() == 0 || height() == 0) {
1041         delete oldNode;
1042         return 0;
1043     }
1044
1045     QQuickShaderEffectSourceNode *node = static_cast<QQuickShaderEffectSourceNode *>(oldNode);
1046     if (!node) {
1047         node = new QQuickShaderEffectSourceNode;
1048         node->setTexture(m_texture);
1049         connect(m_texture, SIGNAL(updateRequested()), node, SLOT(markDirtyTexture()));
1050     }
1051
1052     // If live and recursive, update continuously.
1053     if (m_live && m_recursive)
1054         node->markDirty(QSGNode::DirtyMaterial);
1055
1056     node->setMipmapFiltering(mmFiltering);
1057     node->setFiltering(filtering);
1058     node->setHorizontalWrapMode(hWrap);
1059     node->setVerticalWrapMode(vWrap);
1060     node->setTargetRect(QRectF(0, 0, width(), height()));
1061     node->setInnerTargetRect(QRectF(0, 0, width(), height()));
1062     node->update();
1063
1064     return node;
1065 }
1066
1067 void QQuickShaderEffectSource::itemChange(ItemChange change, const ItemChangeData &value)
1068 {
1069     if (change == QQuickItem::ItemSceneChange && m_sourceItem) {
1070         // See comment in QQuickShaderEffectSource::setSourceItem().
1071         if (value.window)
1072             QQuickItemPrivate::get(m_sourceItem)->refWindow(value.window);
1073         else
1074             QQuickItemPrivate::get(m_sourceItem)->derefWindow();
1075     }
1076     QQuickItem::itemChange(change, value);
1077 }
1078
1079 QT_END_NAMESPACE