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/qsgshadereffect_p.h>
43 #include <private/qsgshadereffectnode_p.h>
45 #include "qsgmaterial.h"
46 #include "qsgitem_p.h"
48 #include <private/qsgcontext_p.h>
49 #include <private/qsgtextureprovider_p.h>
50 #include "qsgcanvas.h"
52 #include <QtCore/qsignalmapper.h>
53 #include <QtOpenGL/qglframebufferobject.h>
57 static const char qt_default_vertex_code[] =
58 "uniform highp mat4 qt_Matrix; \n"
59 "attribute highp vec4 qt_Vertex; \n"
60 "attribute highp vec2 qt_MultiTexCoord0; \n"
61 "varying highp vec2 qt_TexCoord0; \n"
63 " qt_TexCoord0 = qt_MultiTexCoord0; \n"
64 " gl_Position = qt_Matrix * qt_Vertex; \n"
67 static const char qt_default_fragment_code[] =
68 "varying highp vec2 qt_TexCoord0; \n"
69 "uniform sampler2D source; \n"
70 "uniform lowp float qt_Opacity; \n"
72 " gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity; \n"
75 static const char qt_position_attribute_name[] = "qt_Vertex";
76 static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0";
78 const char *qtPositionAttributeName()
80 return qt_position_attribute_name;
83 const char *qtTexCoordAttributeName()
85 return qt_texcoord_attribute_name;
88 // TODO: Remove after grace period.
89 QSGShaderEffectItem::QSGShaderEffectItem(QSGItem *parent)
90 : QSGShaderEffect(parent)
92 qWarning("ShaderEffectItem has been deprecated. Use ShaderEffect instead.");
97 \qmlclass ShaderEffect QSGShaderEffect
99 \ingroup qml-basic-visual-elements
100 \brief The ShaderEffect element applies custom shaders to a rectangle.
103 The ShaderEffect element applies a custom OpenGL
104 \l{vertexShader}{vertex} and \l{fragmentShader}{fragment} shader to a
105 rectangle. It allows you to write effects such as drop shadow, blur,
106 colorize and page curl directly in QML.
108 There are two types of input to the \l vertexShader:
109 uniform variables and attributes. Some are predefined:
111 \o uniform mat4 qt_Matrix - combined transformation
112 matrix, the product of the matrices from the root item to this
113 ShaderEffect, and an orthogonal projection.
114 \o uniform float qt_Opacity - combined opacity, the product of the
115 opacities from the root item to this ShaderEffect.
116 \o attribute vec4 qt_Vertex - vertex position, the top-left vertex has
117 position (0, 0), the bottom-right (\l{Item::width}{width},
118 \l{Item::height}{height}).
119 \o attribute vec2 qt_MultiTexCoord0 - texture coordinate, the top-left
120 coordinate is (0, 0), the bottom-right (1, 1).
123 In addition, any property that can be mapped to an OpenGL Shading Language
124 (GLSL) type is available as a uniform variable. The following list shows
125 how properties are mapped to GLSL uniform variables:
127 \o bool, int, qreal -> bool, int, float - If the type in the shader is not
128 the same as in QML, the value is converted automatically.
129 \o QColor -> vec4 - When colors are passed to the shader, they are first
130 premultiplied. Thus Qt.rgba(0.2, 0.6, 1.0, 0.5) becomes
131 vec4(0.1, 0.3, 0.5, 0.5) in the shader, for example.
132 \o QRect, QRectF -> vec4 - Qt.rect(x, y, w, h) becomes vec4(x, y, w, h) in
134 \o QPoint, QPointF, QSize, QSizeF -> vec2
136 \o QTransform -> mat4
137 \o \l Image, \l ShaderEffectSource -> sampler2D - Origin is in the top-left
138 corner, and the color values are premultiplied.
141 The output from the \l fragmentShader should be premultiplied. If
142 \l blending is enabled, source-over blending is used. However, additive
143 blending can be achieved by outputting zero in the alpha channel.
146 \o \image declarative-shadereffectitem.png
151 width: 200; height: 100
153 Image { id: img; sourceSize { width: 100; height: 100 } source: "qt-logo.png" }
155 width: 100; height: 100
156 property variant src: img
158 uniform highp mat4 qt_Matrix;
159 attribute highp vec4 qt_Vertex;
160 attribute highp vec2 qt_MultiTexCoord0;
161 varying highp vec2 coord;
163 coord = qt_MultiTexCoord0;
164 gl_Position = qt_Matrix * qt_Vertex;
167 varying highp vec2 coord;
168 uniform sampler2D src;
169 uniform lowp float qt_Opacity;
171 lowp vec4 tex = texture2D(src, coord);
172 gl_FragColor = vec4(vec3(dot(tex.rgb, vec3(0.344, 0.5, 0.156))), tex.a) * qt_Opacity;
180 By default, the ShaderEffect consists of four vertices, one for each
181 corner. For non-linear vertex transformations, like page curl, you can
182 specify a fine grid of vertices by specifying a \l mesh resolution.
184 \note Scene Graph textures have origin in the top-left corner rather than
185 bottom-left which is common in OpenGL.
188 QSGShaderEffect::QSGShaderEffect(QSGItem *parent)
190 , m_meshResolution(1, 1)
191 , m_deprecatedMesh(0)
192 , m_cullMode(NoCulling)
195 , m_programDirty(true)
197 , m_dirtyGeometry(true)
199 setFlag(QSGItem::ItemHasContents);
202 QSGShaderEffect::~QSGShaderEffect()
207 void QSGShaderEffect::componentComplete()
210 QSGItem::componentComplete();
214 \qmlproperty string ShaderEffect::fragmentShader
216 This property holds the fragment shader's GLSL source code.
217 The default shader passes the texture coordinate along to the fragment
218 shader as "varying highp vec2 qt_TexCoord0".
221 void QSGShaderEffect::setFragmentShader(const QByteArray &code)
223 if (m_source.fragmentCode.constData() == code.constData())
225 m_source.fragmentCode = code;
226 if (isComponentComplete()) {
230 emit fragmentShaderChanged();
234 \qmlproperty string ShaderEffect::vertexShader
236 This property holds the vertex shader's GLSL source code.
237 The default shader expects the texture coordinate to be passed from the
238 vertex shader as "varying highp vec2 qt_TexCoord0", and it samples from a
239 sampler2D named "source".
242 void QSGShaderEffect::setVertexShader(const QByteArray &code)
244 if (m_source.vertexCode.constData() == code.constData())
246 m_source.vertexCode = code;
247 if (isComponentComplete()) {
251 emit vertexShaderChanged();
255 \qmlproperty bool ShaderEffect::blending
257 If this property is true, the output from the \l fragmentShader is blended
258 with the background using source-over blend mode. If false, the background
259 is disregarded. Blending decreases the performance, so you should set this
260 property to false when blending is not needed. The default value is true.
263 void QSGShaderEffect::setBlending(bool enable)
265 if (blending() == enable)
271 emit blendingChanged();
275 \qmlproperty size ShaderEffect::mesh
277 This property holds the mesh resolution. The default resolution is 1x1
278 which is the minimum and corresponds to a mesh with four vertices.
279 For non-linear vertex transformations, you probably want to set the
283 \o \image declarative-gridmesh.png
290 mesh: Qt.size(20, 20)
291 property variant source: Image {
292 source: "qt-logo.png"
293 sourceSize { width: 200; height: 200 }
297 uniform highp mat4 qt_Matrix;
298 attribute highp vec4 qt_Vertex;
299 attribute highp vec2 qt_MultiTexCoord0;
300 varying highp vec2 qt_TexCoord0;
301 uniform highp float width;
303 highp vec4 pos = qt_Vertex;
304 highp float d = .5 * smoothstep(0., 1., qt_MultiTexCoord0.y);
305 pos.x = width * mix(d, 1.0 - d, qt_MultiTexCoord0.x);
306 gl_Position = qt_Matrix * pos;
307 qt_TexCoord0 = qt_MultiTexCoord0;
314 QVariant QSGShaderEffect::mesh() const
316 return m_deprecatedMesh ? qVariantFromValue(static_cast<QObject *>(m_deprecatedMesh))
317 : qVariantFromValue(m_meshResolution);
320 void QSGShaderEffect::setMesh(const QVariant &mesh)
322 // TODO: Replace QVariant with QSize after grace period.
323 QSGShaderEffectMesh *newMesh = qobject_cast<QSGShaderEffectMesh *>(qVariantValue<QObject *>(mesh));
324 if (newMesh && newMesh == m_deprecatedMesh)
326 if (m_deprecatedMesh)
327 disconnect(m_deprecatedMesh, SIGNAL(geometryChanged()), this, 0);
328 m_deprecatedMesh = newMesh;
329 if (m_deprecatedMesh) {
330 qWarning("ShaderEffect: Setting the mesh to something other than a size is deprecated.");
331 connect(m_deprecatedMesh, SIGNAL(geometryChanged()), this, SLOT(updateGeometry()));
333 if (qVariantCanConvert<QSize>(mesh)) {
334 m_meshResolution = mesh.toSize();
336 QList<QByteArray> res = mesh.toByteArray().split('x');
337 bool ok = res.size() == 2;
339 int w = res.at(0).toInt(&ok);
341 int h = res.at(1).toInt(&ok);
343 m_meshResolution = QSize(w, h);
347 qWarning("ShaderEffect: mesh resolution must be a size.");
349 m_defaultMesh.setResolution(m_meshResolution);
358 \qmlproperty enumeration ShaderEffect::cullMode
360 This property defines which sides of the element should be visible.
363 \o ShaderEffect.NoCulling - Both sides are visible
364 \o ShaderEffect.BackFaceCulling - only front side is visible
365 \o ShaderEffect.FrontFaceCulling - only back side is visible
368 The default is NoCulling.
371 void QSGShaderEffect::setCullMode(CullMode face)
373 if (face == m_cullMode)
377 emit cullModeChanged();
380 void QSGShaderEffect::changeSource(int index)
382 Q_ASSERT(index >= 0 && index < m_sources.size());
383 QVariant v = property(m_sources.at(index).name.constData());
387 void QSGShaderEffect::updateData()
393 void QSGShaderEffect::updateGeometry()
395 m_dirtyGeometry = true;
399 void QSGShaderEffect::setSource(const QVariant &var, int index)
401 Q_ASSERT(index >= 0 && index < m_sources.size());
403 SourceData &source = m_sources[index];
408 } else if (!qVariantCanConvert<QObject *>(var)) {
409 qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
413 QObject *obj = qVariantValue<QObject *>(var);
415 QSGTextureProvider *int3rface = QSGTextureProvider::from(obj);
417 qWarning("Could not assign property '%s', did not implement QSGTextureProvider.", source.name.constData());
420 source.item = qobject_cast<QSGItem *>(obj);
422 // TODO: Find better solution.
423 // 'source.item' needs a canvas to get a scenegraph node.
424 // The easiest way to make sure it gets a canvas is to
425 // make it a part of the same item tree as 'this'.
426 if (source.item && source.item->parentItem() == 0) {
427 source.item->setParentItem(this);
428 source.item->setVisible(false);
432 void QSGShaderEffect::disconnectPropertySignals()
434 disconnect(this, 0, this, SLOT(updateData()));
435 for (int i = 0; i < m_sources.size(); ++i) {
436 SourceData &source = m_sources[i];
437 disconnect(this, 0, source.mapper, 0);
438 disconnect(source.mapper, 0, this, 0);
442 void QSGShaderEffect::connectPropertySignals()
444 QSet<QByteArray>::const_iterator it;
445 for (it = m_source.uniformNames.begin(); it != m_source.uniformNames.end(); ++it) {
446 int pi = metaObject()->indexOfProperty(it->constData());
448 QMetaProperty mp = metaObject()->property(pi);
449 if (!mp.hasNotifySignal())
450 qWarning("QSGShaderEffect: property '%s' does not have notification method!", it->constData());
451 QByteArray signalName("2");
452 signalName.append(mp.notifySignal().signature());
453 connect(this, signalName, this, SLOT(updateData()));
455 qWarning("QSGShaderEffect: '%s' does not have a matching property!", it->constData());
458 for (int i = 0; i < m_sources.size(); ++i) {
459 SourceData &source = m_sources[i];
460 int pi = metaObject()->indexOfProperty(source.name.constData());
462 QMetaProperty mp = metaObject()->property(pi);
463 QByteArray signalName("2");
464 signalName.append(mp.notifySignal().signature());
465 connect(this, signalName, source.mapper, SLOT(map()));
466 source.mapper->setMapping(this, i);
467 connect(source.mapper, SIGNAL(mapped(int)), this, SLOT(changeSource(int)));
469 qWarning("QSGShaderEffect: '%s' does not have a matching source!", source.name.constData());
474 void QSGShaderEffect::reset()
476 disconnectPropertySignals();
478 m_source.attributeNames.clear();
479 m_source.uniformNames.clear();
480 m_source.respectsOpacity = false;
481 m_source.respectsMatrix = false;
482 m_source.className = metaObject()->className();
484 for (int i = 0; i < m_sources.size(); ++i) {
485 const SourceData &source = m_sources.at(i);
486 delete source.mapper;
487 if (source.item && source.item->parentItem() == this)
488 source.item->setParentItem(0);
492 m_programDirty = true;
496 void QSGShaderEffect::updateProperties()
498 QByteArray vertexCode = m_source.vertexCode;
499 QByteArray fragmentCode = m_source.fragmentCode;
500 if (vertexCode.isEmpty())
501 vertexCode = qt_default_vertex_code;
502 if (fragmentCode.isEmpty())
503 fragmentCode = qt_default_fragment_code;
505 lookThroughShaderCode(vertexCode);
506 lookThroughShaderCode(fragmentCode);
508 // TODO: Remove !m_deprecatedMesh check after grace period.
509 if (!m_deprecatedMesh && !m_source.attributeNames.contains(qt_position_attribute_name))
510 qWarning("QSGShaderEffect: Missing reference to \'%s\'.", qt_position_attribute_name);
511 if (!m_deprecatedMesh && !m_source.attributeNames.contains(qt_texcoord_attribute_name))
512 qWarning("QSGShaderEffect: Missing reference to \'%s\'.", qt_texcoord_attribute_name);
513 if (!m_source.respectsMatrix)
514 qWarning("QSGShaderEffect: Missing reference to \'qt_Matrix\'.");
515 if (!m_source.respectsOpacity)
516 qWarning("QSGShaderEffect: Missing reference to \'qt_Opacity\'.");
518 for (int i = 0; i < m_sources.size(); ++i) {
519 QVariant v = property(m_sources.at(i).name);
523 connectPropertySignals();
526 void QSGShaderEffect::lookThroughShaderCode(const QByteArray &code)
528 // Regexp for matching attributes and uniforms.
529 // In human readable form: attribute|uniform [lowp|mediump|highp] <type> <name>
530 static QRegExp re(QLatin1String("\\b(attribute|uniform)\\b\\s*\\b(?:lowp|mediump|highp)?\\b\\s*\\b(\\w+)\\b\\s*\\b(\\w+)"));
531 Q_ASSERT(re.isValid());
535 QString wideCode = QString::fromLatin1(code.constData(), code.size());
537 while ((pos = re.indexIn(wideCode, pos + 1)) != -1) {
538 QByteArray decl = re.cap(1).toLatin1(); // uniform or attribute
539 QByteArray type = re.cap(2).toLatin1(); // type
540 QByteArray name = re.cap(3).toLatin1(); // variable name
542 if (decl == "attribute") {
543 m_source.attributeNames.append(name);
545 Q_ASSERT(decl == "uniform");
547 if (name == "qt_Matrix") {
548 m_source.respectsMatrix = true;
549 } else if (name == "qt_ModelViewProjectionMatrix") {
550 // TODO: Remove after grace period.
551 qWarning("ShaderEffect: qt_ModelViewProjectionMatrix is deprecated. Use qt_Matrix instead.");
552 m_source.respectsMatrix = true;
553 } else if (name == "qt_Opacity") {
554 m_source.respectsOpacity = true;
556 m_source.uniformNames.insert(name);
557 if (type == "sampler2D") {
559 d.mapper = new QSignalMapper;
569 void QSGShaderEffect::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
571 m_dirtyGeometry = true;
572 QSGItem::geometryChanged(newGeometry, oldGeometry);
575 QSGNode *QSGShaderEffect::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
577 QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode);
580 node = new QSGShaderEffectNode;
581 node->setMaterial(&m_material);
582 m_programDirty = true;
584 m_dirtyGeometry = true;
588 node->setGeometry(0);
590 m_dirtyGeometry = true;
593 if (m_dirtyGeometry) {
594 node->setFlag(QSGNode::OwnsGeometry, false);
595 QSGGeometry *geometry = node->geometry();
596 QRectF rect(0, 0, width(), height());
597 QSGShaderEffectMesh *mesh = m_deprecatedMesh ? m_deprecatedMesh : &m_defaultMesh;
599 geometry = mesh->updateGeometry(geometry, m_source.attributeNames, rect);
605 node->setGeometry(geometry);
606 node->setFlag(QSGNode::OwnsGeometry, true);
608 m_dirtyGeometry = false;
611 if (m_programDirty) {
612 QSGShaderEffectProgram s = m_source;
613 if (s.fragmentCode.isEmpty())
614 s.fragmentCode = qt_default_fragment_code;
615 if (s.vertexCode.isEmpty())
616 s.vertexCode = qt_default_vertex_code;
617 s.className = metaObject()->className();
619 m_material.setProgramSource(s);
620 node->markDirty(QSGNode::DirtyMaterial);
621 m_programDirty = false;
625 if (bool(m_material.flags() & QSGMaterial::Blending) != m_blending) {
626 m_material.setFlag(QSGMaterial::Blending, m_blending);
627 node->markDirty(QSGNode::DirtyMaterial);
630 if (int(m_material.cullMode()) != int(m_cullMode)) {
631 m_material.setCullMode(QSGShaderEffectMaterial::CullMode(m_cullMode));
632 node->markDirty(QSGNode::DirtyMaterial);
636 QVector<QPair<QByteArray, QVariant> > values;
637 QVector<QPair<QByteArray, QPointer<QSGItem> > > textures;
638 const QVector<QPair<QByteArray, QPointer<QSGItem> > > &oldTextures = m_material.textureProviders();
640 for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin();
641 it != m_source.uniformNames.end(); ++it) {
642 values.append(qMakePair(*it, property(*it)));
644 for (int i = 0; i < oldTextures.size(); ++i) {
645 QSGTextureProvider *oldSource = QSGTextureProvider::from(oldTextures.at(i).second);
646 if (oldSource && oldSource->textureChangedSignal())
647 disconnect(oldTextures.at(i).second, oldSource->textureChangedSignal(), node, SLOT(markDirtyTexture()));
649 for (int i = 0; i < m_sources.size(); ++i) {
650 const SourceData &source = m_sources.at(i);
651 textures.append(qMakePair(source.name, source.item));
652 QSGTextureProvider *t = QSGTextureProvider::from(source.item);
653 if (t && t->textureChangedSignal())
654 connect(source.item, t->textureChangedSignal(), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
656 m_material.setUniforms(values);
657 m_material.setTextureProviders(textures);
658 node->markDirty(QSGNode::DirtyMaterial);