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 <private/qquickshadereffect_p.h>
43 #include <private/qquickshadereffectnode_p.h>
45 #include "qsgmaterial.h"
46 #include "qquickitem_p.h"
48 #include <private/qsgcontext_p.h>
49 #include <private/qsgtextureprovider_p.h>
50 #include "qquickcanvas.h"
52 #include "qquickimage_p.h"
53 #include "qquickshadereffectsource_p.h"
55 #include <QtCore/qsignalmapper.h>
56 #include <QtGui/qopenglframebufferobject.h>
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"
66 " qt_TexCoord0 = qt_MultiTexCoord0; \n"
67 " gl_Position = qt_Matrix * qt_Vertex; \n"
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"
75 " gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity; \n"
78 static const char qt_position_attribute_name[] = "qt_Vertex";
79 static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0";
81 const char *qtPositionAttributeName()
83 return qt_position_attribute_name;
86 const char *qtTexCoordAttributeName()
88 return qt_texcoord_attribute_name;
91 // TODO: Remove after grace period.
92 QQuickShaderEffectItem::QQuickShaderEffectItem(QQuickItem *parent)
93 : QQuickShaderEffect(parent)
95 qWarning("ShaderEffectItem has been deprecated. Use ShaderEffect instead.");
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.
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.
111 There are two types of input to the \l vertexShader:
112 uniform variables and attributes. Some are predefined:
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).
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:
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
137 \o QPoint, QPointF, QSize, QSizeF -> vec2
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.
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.
149 \o \image declarative-shadereffectitem.png
154 width: 200; height: 100
156 Image { id: img; sourceSize { width: 100; height: 100 } source: "qt-logo.png" }
158 width: 100; height: 100
159 property variant src: img
161 uniform highp mat4 qt_Matrix;
162 attribute highp vec4 qt_Vertex;
163 attribute highp vec2 qt_MultiTexCoord0;
164 varying highp vec2 coord;
166 coord = qt_MultiTexCoord0;
167 gl_Position = qt_Matrix * qt_Vertex;
170 varying highp vec2 coord;
171 uniform sampler2D src;
172 uniform lowp float qt_Opacity;
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;
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.
187 \note Scene Graph textures have origin in the top-left corner rather than
188 bottom-left which is common in OpenGL.
191 QQuickShaderEffect::QQuickShaderEffect(QQuickItem *parent)
193 , m_meshResolution(1, 1)
195 , m_cullMode(NoCulling)
198 , m_programDirty(true)
200 , m_dirtyGeometry(true)
202 setFlag(QQuickItem::ItemHasContents);
205 QQuickShaderEffect::~QQuickShaderEffect()
210 void QQuickShaderEffect::componentComplete()
213 QQuickItem::componentComplete();
217 \qmlproperty string QtQuick2::ShaderEffect::fragmentShader
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".
224 void QQuickShaderEffect::setFragmentShader(const QByteArray &code)
226 if (m_source.fragmentCode.constData() == code.constData())
228 m_source.fragmentCode = code;
229 if (isComponentComplete()) {
233 emit fragmentShaderChanged();
237 \qmlproperty string QtQuick2::ShaderEffect::vertexShader
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".
245 void QQuickShaderEffect::setVertexShader(const QByteArray &code)
247 if (m_source.vertexCode.constData() == code.constData())
249 m_source.vertexCode = code;
250 if (isComponentComplete()) {
254 emit vertexShaderChanged();
258 \qmlproperty bool QtQuick2::ShaderEffect::blending
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.
266 void QQuickShaderEffect::setBlending(bool enable)
268 if (blending() == enable)
274 emit blendingChanged();
278 \qmlproperty variant QtQuick2::ShaderEffect::mesh
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
290 QVariant QQuickShaderEffect::mesh() const
292 return m_mesh ? qVariantFromValue(static_cast<QObject *>(m_mesh))
293 : qVariantFromValue(m_meshResolution);
296 void QQuickShaderEffect::setMesh(const QVariant &mesh)
298 QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(qVariantValue<QObject *>(mesh));
299 if (newMesh && newMesh == m_mesh)
302 disconnect(m_mesh, SIGNAL(geometryChanged()), this, 0);
305 connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(updateGeometry()));
307 if (qVariantCanConvert<QSize>(mesh)) {
308 m_meshResolution = mesh.toSize();
310 QList<QByteArray> res = mesh.toByteArray().split('x');
311 bool ok = res.size() == 2;
313 int w = res.at(0).toInt(&ok);
315 int h = res.at(1).toInt(&ok);
317 m_meshResolution = QSize(w, h);
321 qWarning("ShaderEffect: mesh property must be size or object deriving from QQuickShaderEffectMesh.");
323 m_defaultMesh.setResolution(m_meshResolution);
332 \qmlproperty enumeration QtQuick2::ShaderEffect::cullMode
334 This property defines which sides of the element should be visible.
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
342 The default is NoCulling.
345 void QQuickShaderEffect::setCullMode(CullMode face)
347 if (face == m_cullMode)
351 emit cullModeChanged();
354 void QQuickShaderEffect::changeSource(int index)
356 Q_ASSERT(index >= 0 && index < m_sources.size());
357 QVariant v = property(m_sources.at(index).name.constData());
361 void QQuickShaderEffect::updateData()
367 void QQuickShaderEffect::updateGeometry()
369 m_dirtyGeometry = true;
373 void QQuickShaderEffect::setSource(const QVariant &var, int index)
375 Q_ASSERT(index >= 0 && index < m_sources.size());
377 SourceData &source = m_sources[index];
379 source.sourceObject = 0;
382 } else if (!qVariantCanConvert<QObject *>(var)) {
383 qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
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());
395 source.sourceObject = item;
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);
408 void QQuickShaderEffect::disconnectPropertySignals()
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);
418 void QQuickShaderEffect::connectPropertySignals()
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());
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()));
431 qWarning("QQuickShaderEffect: '%s' does not have a matching property!", it->constData());
434 for (int i = 0; i < m_sources.size(); ++i) {
435 SourceData &source = m_sources[i];
436 int pi = metaObject()->indexOfProperty(source.name.constData());
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)));
445 qWarning("QQuickShaderEffect: '%s' does not have a matching source!", source.name.constData());
450 void QQuickShaderEffect::reset()
452 disconnectPropertySignals();
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();
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);
469 m_programDirty = true;
473 void QQuickShaderEffect::updateProperties()
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;
482 lookThroughShaderCode(vertexCode);
483 lookThroughShaderCode(fragmentCode);
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\'.");
494 for (int i = 0; i < m_sources.size(); ++i) {
495 QVariant v = property(m_sources.at(i).name);
499 connectPropertySignals();
502 void QQuickShaderEffect::lookThroughShaderCode(const QByteArray &code)
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());
511 QString wideCode = QString::fromLatin1(code.constData(), code.size());
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
518 if (decl == "attribute") {
519 m_source.attributeNames.append(name);
521 Q_ASSERT(decl == "uniform");
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;
532 m_source.uniformNames.insert(name);
533 if (type == "sampler2D") {
535 d.mapper = new QSignalMapper;
545 void QQuickShaderEffect::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
547 m_dirtyGeometry = true;
548 QQuickItem::geometryChanged(newGeometry, oldGeometry);
551 QSGNode *QQuickShaderEffect::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
553 QQuickShaderEffectNode *node = static_cast<QQuickShaderEffectNode *>(oldNode);
555 // In the case of a bad vertex shader, don't try to create a node...
556 if (m_source.attributeNames.isEmpty()) {
563 node = new QQuickShaderEffectNode;
564 m_programDirty = true;
566 m_dirtyGeometry = true;
569 QQuickShaderEffectMaterial *material = node->shaderMaterial();
572 node->setGeometry(0);
574 m_dirtyGeometry = true;
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;
583 geometry = mesh->updateGeometry(geometry, m_source.attributeNames, rect);
589 node->setGeometry(geometry);
590 node->setFlag(QSGNode::OwnsGeometry, true);
592 m_dirtyGeometry = false;
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();
603 material->setProgramSource(s);
604 node->markDirty(QSGNode::DirtyMaterial);
605 m_programDirty = false;
609 if (bool(material->flags() & QSGMaterial::Blending) != m_blending) {
610 material->setFlag(QSGMaterial::Blending, m_blending);
611 node->markDirty(QSGNode::DirtyMaterial);
614 if (int(material->cullMode()) != int(m_cullMode)) {
615 material->setCullMode(QQuickShaderEffectMaterial::CullMode(m_cullMode));
616 node->markDirty(QSGNode::DirtyMaterial);
620 QVector<QPair<QByteArray, QVariant> > values;
621 QVector<QPair<QByteArray, QSGTextureProvider *> > textures;
622 const QVector<QPair<QByteArray, QSGTextureProvider *> > &oldTextures = material->textureProviders();
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)));
628 for (int i = 0; i < oldTextures.size(); ++i) {
629 QSGTextureProvider *t = oldTextures.at(i).second;
631 disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
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));
638 connect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
640 material->setUniforms(values);
641 material->setTextureProviders(textures);
642 node->markDirty(QSGNode::DirtyMaterial);