Fix test fails related to QTBUG-22237
[profile/ivi/qtdeclarative.git] / src / declarative / items / qquickshadereffect.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
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.
17 **
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.
21 **
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.
29 **
30 ** Other Usage
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.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <private/qquickshadereffect_p.h>
43 #include <private/qquickshadereffectnode_p.h>
44
45 #include "qsgmaterial.h"
46 #include "qquickitem_p.h"
47
48 #include <private/qsgcontext_p.h>
49 #include <private/qsgtextureprovider_p.h>
50 #include "qquickcanvas.h"
51
52 #include "qquickimage_p.h"
53 #include "qquickshadereffectsource_p.h"
54
55 #include <QtCore/qsignalmapper.h>
56 #include <QtGui/qopenglframebufferobject.h>
57
58 QT_BEGIN_NAMESPACE
59
60 static const char qt_default_vertex_code[] =
61     "uniform highp mat4 qt_Matrix;                                  \n"
62     "attribute highp vec4 qt_Vertex;                                \n"
63     "attribute highp vec2 qt_MultiTexCoord0;                        \n"
64     "varying highp vec2 qt_TexCoord0;                               \n"
65     "void main() {                                                  \n"
66     "    qt_TexCoord0 = qt_MultiTexCoord0;                          \n"
67     "    gl_Position = qt_Matrix * qt_Vertex;                       \n"
68     "}";
69
70 static const char qt_default_fragment_code[] =
71     "varying highp vec2 qt_TexCoord0;                                   \n"
72     "uniform sampler2D source;                                          \n"
73     "uniform lowp float qt_Opacity;                                     \n"
74     "void main() {                                                      \n"
75     "    gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;   \n"
76     "}";
77
78 static const char qt_position_attribute_name[] = "qt_Vertex";
79 static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0";
80
81 const char *qtPositionAttributeName()
82 {
83     return qt_position_attribute_name;
84 }
85
86 const char *qtTexCoordAttributeName()
87 {
88     return qt_texcoord_attribute_name;
89 }
90
91 // TODO: Remove after grace period.
92 QQuickShaderEffectItem::QQuickShaderEffectItem(QQuickItem *parent)
93     : QQuickShaderEffect(parent)
94 {
95     qWarning("ShaderEffectItem has been deprecated. Use ShaderEffect instead.");
96 }
97
98
99 /*!
100     \qmlclass ShaderEffect QQuickShaderEffect
101     \inqmlmodule QtQuick 2
102     \ingroup qml-basic-visual-elements
103     \brief The ShaderEffect element applies custom shaders to a rectangle.
104     \inherits Item
105
106     The ShaderEffect element applies a custom OpenGL
107     \l{vertexShader}{vertex} and \l{fragmentShader}{fragment} shader to a
108     rectangle. It allows you to write effects such as drop shadow, blur,
109     colorize and page curl directly in QML.
110
111     There are two types of input to the \l vertexShader:
112     uniform variables and attributes. Some are predefined:
113     \list
114     \o uniform mat4 qt_Matrix - combined transformation
115        matrix, the product of the matrices from the root item to this
116        ShaderEffect, and an orthogonal projection.
117     \o uniform float qt_Opacity - combined opacity, the product of the
118        opacities from the root item to this ShaderEffect.
119     \o attribute vec4 qt_Vertex - vertex position, the top-left vertex has
120        position (0, 0), the bottom-right (\l{Item::width}{width},
121        \l{Item::height}{height}).
122     \o attribute vec2 qt_MultiTexCoord0 - texture coordinate, the top-left
123        coordinate is (0, 0), the bottom-right (1, 1).
124     \endlist
125
126     In addition, any property that can be mapped to an OpenGL Shading Language
127     (GLSL) type is available as a uniform variable. The following list shows
128     how properties are mapped to GLSL uniform variables:
129     \list
130     \o bool, int, qreal -> bool, int, float - If the type in the shader is not
131        the same as in QML, the value is converted automatically.
132     \o QColor -> vec4 - When colors are passed to the shader, they are first
133        premultiplied. Thus Qt.rgba(0.2, 0.6, 1.0, 0.5) becomes
134        vec4(0.1, 0.3, 0.5, 0.5) in the shader, for example.
135     \o QRect, QRectF -> vec4 - Qt.rect(x, y, w, h) becomes vec4(x, y, w, h) in
136        the shader.
137     \o QPoint, QPointF, QSize, QSizeF -> vec2
138     \o QVector3D -> vec3
139     \o QTransform -> mat4
140     \o \l Image, \l ShaderEffectSource -> sampler2D - Origin is in the top-left
141        corner, and the color values are premultiplied.
142     \endlist
143
144     The output from the \l fragmentShader should be premultiplied. If
145     \l blending is enabled, source-over blending is used. However, additive
146     blending can be achieved by outputting zero in the alpha channel.
147
148     \row
149     \o \image declarative-shadereffectitem.png
150     \o \qml
151         import QtQuick 2.0
152
153         Rectangle {
154             width: 200; height: 100
155             Row {
156                 Image { id: img; sourceSize { width: 100; height: 100 } source: "qt-logo.png" }
157                 ShaderEffect {
158                     width: 100; height: 100
159                     property variant src: img
160                     vertexShader: "
161                         uniform highp mat4 qt_Matrix;
162                         attribute highp vec4 qt_Vertex;
163                         attribute highp vec2 qt_MultiTexCoord0;
164                         varying highp vec2 coord;
165                         void main() {
166                             coord = qt_MultiTexCoord0;
167                             gl_Position = qt_Matrix * qt_Vertex;
168                         }"
169                     fragmentShader: "
170                         varying highp vec2 coord;
171                         uniform sampler2D src;
172                         uniform lowp float qt_Opacity;
173                         void main() {
174                             lowp vec4 tex = texture2D(src, coord);
175                             gl_FragColor = vec4(vec3(dot(tex.rgb, vec3(0.344, 0.5, 0.156))), tex.a) * qt_Opacity;
176                         }"
177                 }
178             }
179         }
180         \endqml
181     \endrow
182
183     By default, the ShaderEffect consists of four vertices, one for each
184     corner. For non-linear vertex transformations, like page curl, you can
185     specify a fine grid of vertices by specifying a \l mesh resolution.
186
187     \note Scene Graph textures have origin in the top-left corner rather than
188     bottom-left which is common in OpenGL.
189 */
190
191 QQuickShaderEffect::QQuickShaderEffect(QQuickItem *parent)
192     : QQuickItem(parent)
193     , m_meshResolution(1, 1)
194     , m_mesh(0)
195     , m_cullMode(NoCulling)
196     , m_blending(true)
197     , m_dirtyData(true)
198     , m_programDirty(true)
199     , m_dirtyMesh(true)
200     , m_dirtyGeometry(true)
201 {
202     setFlag(QQuickItem::ItemHasContents);
203 }
204
205 QQuickShaderEffect::~QQuickShaderEffect()
206 {
207     reset();
208 }
209
210 void QQuickShaderEffect::componentComplete()
211 {
212     updateProperties();
213     QQuickItem::componentComplete();
214 }
215
216 /*!
217     \qmlproperty string QtQuick2::ShaderEffect::fragmentShader
218
219     This property holds the fragment shader's GLSL source code.
220     The default shader passes the texture coordinate along to the fragment
221     shader as "varying highp vec2 qt_TexCoord0".
222 */
223
224 void QQuickShaderEffect::setFragmentShader(const QByteArray &code)
225 {
226     if (m_source.fragmentCode.constData() == code.constData())
227         return;
228     m_source.fragmentCode = code;
229     if (isComponentComplete()) {
230         reset();
231         updateProperties();
232     }
233     emit fragmentShaderChanged();
234 }
235
236 /*!
237     \qmlproperty string QtQuick2::ShaderEffect::vertexShader
238
239     This property holds the vertex shader's GLSL source code.
240     The default shader expects the texture coordinate to be passed from the
241     vertex shader as "varying highp vec2 qt_TexCoord0", and it samples from a
242     sampler2D named "source".
243 */
244
245 void QQuickShaderEffect::setVertexShader(const QByteArray &code)
246 {
247     if (m_source.vertexCode.constData() == code.constData())
248         return;
249     m_source.vertexCode = code;
250     if (isComponentComplete()) {
251         reset();
252         updateProperties();
253     }
254     emit vertexShaderChanged();
255 }
256
257 /*!
258     \qmlproperty bool QtQuick2::ShaderEffect::blending
259
260     If this property is true, the output from the \l fragmentShader is blended
261     with the background using source-over blend mode. If false, the background
262     is disregarded. Blending decreases the performance, so you should set this
263     property to false when blending is not needed. The default value is true.
264 */
265
266 void QQuickShaderEffect::setBlending(bool enable)
267 {
268     if (blending() == enable)
269         return;
270
271     m_blending = enable;
272     update();
273
274     emit blendingChanged();
275 }
276
277 /*!
278     \qmlproperty variant QtQuick2::ShaderEffect::mesh
279
280     This property defines the mesh used to draw the ShaderEffect. It can hold
281     any mesh object deriving from \l QQuickShaderEffectMesh, such as \l GridMesh.
282     If a size value is assigned to this property, the ShaderEffect implicitly
283     uses a \l GridMesh with the value as
284     \l{GridMesh::resolution}{mesh resolution}. By default, this property is
285     the size 1x1.
286
287     \sa GridMesh
288 */
289
290 QVariant QQuickShaderEffect::mesh() const
291 {
292     return m_mesh ? qVariantFromValue(static_cast<QObject *>(m_mesh))
293                   : qVariantFromValue(m_meshResolution);
294 }
295
296 void QQuickShaderEffect::setMesh(const QVariant &mesh)
297 {
298     QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(qVariantValue<QObject *>(mesh));
299     if (newMesh && newMesh == m_mesh)
300         return;
301     if (m_mesh)
302         disconnect(m_mesh, SIGNAL(geometryChanged()), this, 0);
303     m_mesh = newMesh;
304     if (m_mesh) {
305         connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(updateGeometry()));
306     } else {
307         if (qVariantCanConvert<QSize>(mesh)) {
308             m_meshResolution = mesh.toSize();
309         } else {
310             QList<QByteArray> res = mesh.toByteArray().split('x');
311             bool ok = res.size() == 2;
312             if (ok) {
313                 int w = res.at(0).toInt(&ok);
314                 if (ok) {
315                     int h = res.at(1).toInt(&ok);
316                     if (ok)
317                         m_meshResolution = QSize(w, h);
318                 }
319             }
320             if (!ok)
321                 qWarning("ShaderEffect: mesh property must be size or object deriving from QQuickShaderEffectMesh.");
322         }
323         m_defaultMesh.setResolution(m_meshResolution);
324     }
325
326     m_dirtyMesh = true;
327     update();
328     emit meshChanged();
329 }
330
331 /*!
332     \qmlproperty enumeration QtQuick2::ShaderEffect::cullMode
333
334     This property defines which sides of the element should be visible.
335
336     \list
337     \o ShaderEffect.NoCulling - Both sides are visible
338     \o ShaderEffect.BackFaceCulling - only front side is visible
339     \o ShaderEffect.FrontFaceCulling - only back side is visible
340     \endlist
341
342     The default is NoCulling.
343 */
344
345 void QQuickShaderEffect::setCullMode(CullMode face)
346 {
347     if (face == m_cullMode)
348         return;
349     m_cullMode = face;
350     update();
351     emit cullModeChanged();
352 }
353
354 void QQuickShaderEffect::changeSource(int index)
355 {
356     Q_ASSERT(index >= 0 && index < m_sources.size());
357     QVariant v = property(m_sources.at(index).name.constData());
358     setSource(v, index);
359 }
360
361 void QQuickShaderEffect::updateData()
362 {
363     m_dirtyData = true;
364     update();
365 }
366
367 void QQuickShaderEffect::updateGeometry()
368 {
369     m_dirtyGeometry = true;
370     update();
371 }
372
373 void QQuickShaderEffect::setSource(const QVariant &var, int index)
374 {
375     Q_ASSERT(index >= 0 && index < m_sources.size());
376
377     SourceData &source = m_sources[index];
378
379     source.sourceObject = 0;
380     if (var.isNull()) {
381         return;
382     } else if (!qVariantCanConvert<QObject *>(var)) {
383         qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
384         return;
385     }
386
387     QObject *obj = qVariantValue<QObject *>(var);
388     QQuickItem *item = qobject_cast<QQuickItem *>(obj);
389     if (!item || !item->isTextureProvider()) {
390         qWarning("ShaderEffect: source uniform [%s] is not assigned a valid texture provider: %s [%s]",
391                  source.name.constData(), qPrintable(obj->objectName()), obj->metaObject()->className());
392         return;
393     }
394
395     source.sourceObject = item;
396
397
398     // TODO: Find better solution.
399     // 'item' needs a canvas to get a scenegraph node.
400     // The easiest way to make sure it gets a canvas is to
401     // make it a part of the same item tree as 'this'.
402     if (item && item->parentItem() == 0) {
403         item->setParentItem(this);
404         item->setVisible(false);
405     }
406 }
407
408 void QQuickShaderEffect::disconnectPropertySignals()
409 {
410     disconnect(this, 0, this, SLOT(updateData()));
411     for (int i = 0; i < m_sources.size(); ++i) {
412         SourceData &source = m_sources[i];
413         disconnect(this, 0, source.mapper, 0);
414         disconnect(source.mapper, 0, this, 0);
415     }
416 }
417
418 void QQuickShaderEffect::connectPropertySignals()
419 {
420     QSet<QByteArray>::const_iterator it;
421     for (it = m_source.uniformNames.begin(); it != m_source.uniformNames.end(); ++it) {
422         int pi = metaObject()->indexOfProperty(it->constData());
423         if (pi >= 0) {
424             QMetaProperty mp = metaObject()->property(pi);
425             if (!mp.hasNotifySignal())
426                 qWarning("QQuickShaderEffect: property '%s' does not have notification method!", it->constData());
427             QByteArray signalName("2");
428             signalName.append(mp.notifySignal().signature());
429             connect(this, signalName, this, SLOT(updateData()));
430         } else {
431             qWarning("QQuickShaderEffect: '%s' does not have a matching property!", it->constData());
432         }
433     }
434     for (int i = 0; i < m_sources.size(); ++i) {
435         SourceData &source = m_sources[i];
436         int pi = metaObject()->indexOfProperty(source.name.constData());
437         if (pi >= 0) {
438             QMetaProperty mp = metaObject()->property(pi);
439             QByteArray signalName("2");
440             signalName.append(mp.notifySignal().signature());
441             connect(this, signalName, source.mapper, SLOT(map()));
442             source.mapper->setMapping(this, i);
443             connect(source.mapper, SIGNAL(mapped(int)), this, SLOT(changeSource(int)));
444         } else {
445             qWarning("QQuickShaderEffect: '%s' does not have a matching source!", source.name.constData());
446         }
447     }
448 }
449
450 void QQuickShaderEffect::reset()
451 {
452     disconnectPropertySignals();
453
454     m_source.attributeNames.clear();
455     m_source.uniformNames.clear();
456     m_source.respectsOpacity = false;
457     m_source.respectsMatrix = false;
458     m_source.className = metaObject()->className();
459
460     for (int i = 0; i < m_sources.size(); ++i) {
461         const SourceData &source = m_sources.at(i);
462         delete source.mapper;
463         QQuickItem *item = qobject_cast<QQuickItem *>(source.sourceObject);
464         if (item && item->parentItem() == this)
465             item->setParentItem(0);
466     }
467     m_sources.clear();
468
469     m_programDirty = true;
470     m_dirtyMesh = true;
471 }
472
473 void QQuickShaderEffect::updateProperties()
474 {
475     QByteArray vertexCode = m_source.vertexCode;
476     QByteArray fragmentCode = m_source.fragmentCode;
477     if (vertexCode.isEmpty())
478         vertexCode = qt_default_vertex_code;
479     if (fragmentCode.isEmpty())
480         fragmentCode = qt_default_fragment_code;
481
482     lookThroughShaderCode(vertexCode);
483     lookThroughShaderCode(fragmentCode);
484
485     if (!m_mesh && !m_source.attributeNames.contains(qt_position_attribute_name))
486         qWarning("QQuickShaderEffect: Missing reference to \'%s\'.", qt_position_attribute_name);
487     if (!m_mesh && !m_source.attributeNames.contains(qt_texcoord_attribute_name))
488         qWarning("QQuickShaderEffect: Missing reference to \'%s\'.", qt_texcoord_attribute_name);
489     if (!m_source.respectsMatrix)
490         qWarning("QQuickShaderEffect: Missing reference to \'qt_Matrix\'.");
491     if (!m_source.respectsOpacity)
492         qWarning("QQuickShaderEffect: Missing reference to \'qt_Opacity\'.");
493
494     for (int i = 0; i < m_sources.size(); ++i) {
495         QVariant v = property(m_sources.at(i).name);
496         setSource(v, i);
497     }
498
499     connectPropertySignals();
500 }
501
502 void QQuickShaderEffect::lookThroughShaderCode(const QByteArray &code)
503 {
504     // Regexp for matching attributes and uniforms.
505     // In human readable form: attribute|uniform [lowp|mediump|highp] <type> <name>
506     static QRegExp re(QLatin1String("\\b(attribute|uniform)\\b\\s*\\b(?:lowp|mediump|highp)?\\b\\s*\\b(\\w+)\\b\\s*\\b(\\w+)"));
507     Q_ASSERT(re.isValid());
508
509     int pos = -1;
510
511     QString wideCode = QString::fromLatin1(code.constData(), code.size());
512
513     while ((pos = re.indexIn(wideCode, pos + 1)) != -1) {
514         QByteArray decl = re.cap(1).toLatin1(); // uniform or attribute
515         QByteArray type = re.cap(2).toLatin1(); // type
516         QByteArray name = re.cap(3).toLatin1(); // variable name
517
518         if (decl == "attribute") {
519             m_source.attributeNames.append(name);
520         } else {
521             Q_ASSERT(decl == "uniform");
522
523             if (name == "qt_Matrix") {
524                 m_source.respectsMatrix = true;
525             } else if (name == "qt_ModelViewProjectionMatrix") {
526                 // TODO: Remove after grace period.
527                 qWarning("ShaderEffect: qt_ModelViewProjectionMatrix is deprecated. Use qt_Matrix instead.");
528                 m_source.respectsMatrix = true;
529             } else if (name == "qt_Opacity") {
530                 m_source.respectsOpacity = true;
531             } else {
532                 m_source.uniformNames.insert(name);
533                 if (type == "sampler2D") {
534                     SourceData d;
535                     d.mapper = new QSignalMapper;
536                     d.name = name;
537                     d.sourceObject = 0;
538                     m_sources.append(d);
539                 }
540             }
541         }
542     }
543 }
544
545 void QQuickShaderEffect::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
546 {
547     m_dirtyGeometry = true;
548     QQuickItem::geometryChanged(newGeometry, oldGeometry);
549 }
550
551 QSGNode *QQuickShaderEffect::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
552 {
553     QQuickShaderEffectNode *node = static_cast<QQuickShaderEffectNode *>(oldNode);
554
555     // In the case of a bad vertex shader, don't try to create a node...
556     if (m_source.attributeNames.isEmpty()) {
557         if (node)
558             delete node;
559         return 0;
560     }
561
562     if (!node) {
563         node = new QQuickShaderEffectNode;
564         m_programDirty = true;
565         m_dirtyData = true;
566         m_dirtyGeometry = true;
567     }
568
569     QQuickShaderEffectMaterial *material = node->shaderMaterial();
570
571     if (m_dirtyMesh) {
572         node->setGeometry(0);
573         m_dirtyMesh = false;
574         m_dirtyGeometry = true;
575     }
576
577     if (m_dirtyGeometry) {
578         node->setFlag(QSGNode::OwnsGeometry, false);
579         QSGGeometry *geometry = node->geometry();
580         QRectF rect(0, 0, width(), height());
581         QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
582
583         geometry = mesh->updateGeometry(geometry, m_source.attributeNames, rect);
584         if (!geometry) {
585             delete node;
586             return 0;
587         }
588
589         node->setGeometry(geometry);
590         node->setFlag(QSGNode::OwnsGeometry, true);
591
592         m_dirtyGeometry = false;
593     }
594
595     if (m_programDirty) {
596         QQuickShaderEffectProgram s = m_source;
597         if (s.fragmentCode.isEmpty())
598             s.fragmentCode = qt_default_fragment_code;
599         if (s.vertexCode.isEmpty())
600             s.vertexCode = qt_default_vertex_code;
601         s.className = metaObject()->className();
602
603         material->setProgramSource(s);
604         node->markDirty(QSGNode::DirtyMaterial);
605         m_programDirty = false;
606     }
607
608     // Update blending
609     if (bool(material->flags() & QSGMaterial::Blending) != m_blending) {
610         material->setFlag(QSGMaterial::Blending, m_blending);
611         node->markDirty(QSGNode::DirtyMaterial);
612     }
613
614     if (int(material->cullMode()) != int(m_cullMode)) {
615         material->setCullMode(QQuickShaderEffectMaterial::CullMode(m_cullMode));
616         node->markDirty(QSGNode::DirtyMaterial);
617     }
618
619     if (m_dirtyData) {
620         QVector<QPair<QByteArray, QVariant> > values;
621         QVector<QPair<QByteArray, QSGTextureProvider *> > textures;
622         const QVector<QPair<QByteArray, QSGTextureProvider *> > &oldTextures = material->textureProviders();
623
624         for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin();
625              it != m_source.uniformNames.end(); ++it) {
626             values.append(qMakePair(*it, property(*it)));
627         }
628         for (int i = 0; i < oldTextures.size(); ++i) {
629             QSGTextureProvider *t = oldTextures.at(i).second;
630             if (t)
631                 disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
632         }
633         for (int i = 0; i < m_sources.size(); ++i) {
634             const SourceData &source = m_sources.at(i);
635             QSGTextureProvider *t = source.sourceObject->textureProvider();
636             textures.append(qMakePair(source.name, t));
637             if (t)
638                 connect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
639         }
640         material->setUniforms(values);
641         material->setTextureProviders(textures);
642         node->markDirty(QSGNode::DirtyMaterial);
643         m_dirtyData = false;
644     }
645
646     return node;
647 }
648
649 QT_END_NAMESPACE