Initial import from qtquick2.
[profile/ivi/qtdeclarative.git] / src / declarative / items / qsgshadereffectitem.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 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <private/qsgshadereffectitem_p.h>
43 #include <private/qsgshadereffectnode_p.h>
44
45 #include "qsgmaterial.h"
46 #include "qsgitem_p.h"
47
48 #include <private/qsgcontext_p.h>
49 #include <private/qsgtextureprovider_p.h>
50 #include "qsgcanvas.h"
51
52 #include <QtCore/qsignalmapper.h>
53 #include <QtOpenGL/qglframebufferobject.h>
54
55 QT_BEGIN_NAMESPACE
56
57 static const char qt_default_vertex_code[] =
58     "uniform highp mat4 qt_ModelViewProjectionMatrix;               \n"
59     "attribute highp vec4 qt_Vertex;                                \n"
60     "attribute highp vec2 qt_MultiTexCoord0;                        \n"
61     "varying highp vec2 qt_TexCoord0;                               \n"
62     "void main() {                                                  \n"
63     "    qt_TexCoord0 = qt_MultiTexCoord0;                          \n"
64     "    gl_Position = qt_ModelViewProjectionMatrix * qt_Vertex;    \n"
65     "}";
66
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"
71     "void main() {                                                      \n"
72     "    gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;   \n"
73     "}";
74
75 static const char qt_position_attribute_name[] = "qt_Vertex";
76 static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0";
77
78 const char *qtPositionAttributeName()
79 {
80     return qt_position_attribute_name;
81 }
82
83 const char *qtTexCoordAttributeName()
84 {
85     return qt_texcoord_attribute_name;
86 }
87
88 QSGShaderEffectItem::QSGShaderEffectItem(QSGItem *parent)
89     : QSGItem(parent)
90     , m_mesh(0)
91     , m_cullMode(NoCulling)
92     , m_blending(true)
93     , m_dirtyData(true)
94     , m_programDirty(true)
95     , m_dirtyMesh(true)
96     , m_dirtyGeometry(true)
97 {
98     setFlag(QSGItem::ItemHasContents);
99 }
100
101 QSGShaderEffectItem::~QSGShaderEffectItem()
102 {
103     reset();
104 }
105
106 void QSGShaderEffectItem::componentComplete()
107 {
108     updateProperties();
109     QSGItem::componentComplete();
110 }
111
112 void QSGShaderEffectItem::setFragmentShader(const QByteArray &code)
113 {
114     if (m_source.fragmentCode.constData() == code.constData())
115         return;
116     m_source.fragmentCode = code;
117     if (isComponentComplete()) {
118         reset();
119         updateProperties();
120     }
121     emit fragmentShaderChanged();
122 }
123
124 void QSGShaderEffectItem::setVertexShader(const QByteArray &code)
125 {
126     if (m_source.vertexCode.constData() == code.constData())
127         return;
128     m_source.vertexCode = code;
129     if (isComponentComplete()) {
130         reset();
131         updateProperties();
132     }
133     emit vertexShaderChanged();
134 }
135
136 void QSGShaderEffectItem::setBlending(bool enable)
137 {
138     if (blending() == enable)
139         return;
140
141     m_blending = enable;
142     update();
143
144     emit blendingChanged();
145 }
146
147 void QSGShaderEffectItem::setMesh(QSGShaderEffectMesh *mesh)
148 {
149     if (mesh == m_mesh)
150         return;
151     if (m_mesh)
152         disconnect(m_mesh, SIGNAL(geometryChanged()), this, 0);
153     m_mesh = mesh;
154     if (m_mesh)
155         connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(updateGeometry()));
156     m_dirtyMesh = true;
157     update();
158     emit meshChanged();
159 }
160
161 void QSGShaderEffectItem::setCullMode(CullMode face)
162 {
163     if (face == m_cullMode)
164         return;
165     m_cullMode = face;
166     update();
167     emit cullModeChanged();
168 }
169
170 void QSGShaderEffectItem::changeSource(int index)
171 {
172     Q_ASSERT(index >= 0 && index < m_sources.size());
173     QVariant v = property(m_sources.at(index).name.constData());
174     setSource(v, index);
175 }
176
177 void QSGShaderEffectItem::updateData()
178 {
179     m_dirtyData = true;
180     update();
181 }
182
183 void QSGShaderEffectItem::updateGeometry()
184 {
185     m_dirtyGeometry = true;
186     update();
187 }
188
189 void QSGShaderEffectItem::setSource(const QVariant &var, int index)
190 {
191     Q_ASSERT(index >= 0 && index < m_sources.size());
192
193     SourceData &source = m_sources[index];
194
195     source.item = 0;
196     if (var.isNull()) {
197         return;
198     } else if (!qVariantCanConvert<QObject *>(var)) {
199         qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
200         return;
201     }
202
203     QObject *obj = qVariantValue<QObject *>(var);
204
205     QSGTextureProvider *int3rface = QSGTextureProvider::from(obj);
206     if (!int3rface) {
207         qWarning("Could not assign property '%s', did not implement QSGTextureProvider.", source.name.constData());
208     }
209
210     source.item = qobject_cast<QSGItem *>(obj);
211
212     // TODO: Find better solution.
213     // 'source.item' needs a canvas to get a scenegraph node.
214     // The easiest way to make sure it gets a canvas is to
215     // make it a part of the same item tree as 'this'.
216     if (source.item && source.item->parentItem() == 0) {
217         source.item->setParentItem(this);
218         source.item->setVisible(false);
219     }
220 }
221
222 void QSGShaderEffectItem::disconnectPropertySignals()
223 {
224     disconnect(this, 0, this, SLOT(updateData()));
225     for (int i = 0; i < m_sources.size(); ++i) {
226         SourceData &source = m_sources[i];
227         disconnect(this, 0, source.mapper, 0);
228         disconnect(source.mapper, 0, this, 0);
229     }
230 }
231
232 void QSGShaderEffectItem::connectPropertySignals()
233 {
234     QSet<QByteArray>::const_iterator it;
235     for (it = m_source.uniformNames.begin(); it != m_source.uniformNames.end(); ++it) {
236         int pi = metaObject()->indexOfProperty(it->constData());
237         if (pi >= 0) {
238             QMetaProperty mp = metaObject()->property(pi);
239             if (!mp.hasNotifySignal())
240                 qWarning("QSGShaderEffectItem: property '%s' does not have notification method!", it->constData());
241             QByteArray signalName("2");
242             signalName.append(mp.notifySignal().signature());
243             connect(this, signalName, this, SLOT(updateData()));
244         } else {
245             qWarning("QSGShaderEffectItem: '%s' does not have a matching property!", it->constData());
246         }
247     }
248     for (int i = 0; i < m_sources.size(); ++i) {
249         SourceData &source = m_sources[i];
250         int pi = metaObject()->indexOfProperty(source.name.constData());
251         if (pi >= 0) {
252             QMetaProperty mp = metaObject()->property(pi);
253             QByteArray signalName("2");
254             signalName.append(mp.notifySignal().signature());
255             connect(this, signalName, source.mapper, SLOT(map()));
256             source.mapper->setMapping(this, i);
257             connect(source.mapper, SIGNAL(mapped(int)), this, SLOT(changeSource(int)));
258         } else {
259             qWarning("QSGShaderEffectItem: '%s' does not have a matching source!", source.name.constData());
260         }
261     }
262 }
263
264 void QSGShaderEffectItem::reset()
265 {
266     disconnectPropertySignals();
267
268     m_source.attributeNames.clear();
269     m_source.uniformNames.clear();
270     m_source.respectsOpacity = false;
271     m_source.respectsMatrix = false;
272     m_source.className = metaObject()->className();
273
274     for (int i = 0; i < m_sources.size(); ++i) {
275         const SourceData &source = m_sources.at(i);
276         delete source.mapper;
277         if (source.item && source.item->parentItem() == this)
278             source.item->setParentItem(0);
279     }
280     m_sources.clear();
281
282     m_programDirty = true;
283     m_dirtyMesh = true;
284 }
285
286 void QSGShaderEffectItem::updateProperties()
287 {
288     QByteArray vertexCode = m_source.vertexCode;
289     QByteArray fragmentCode = m_source.fragmentCode;
290     if (vertexCode.isEmpty())
291         vertexCode = qt_default_vertex_code;
292     if (fragmentCode.isEmpty())
293         fragmentCode = qt_default_fragment_code;
294
295     lookThroughShaderCode(vertexCode);
296     lookThroughShaderCode(fragmentCode);
297
298     if (!m_mesh && !m_source.attributeNames.contains(qt_position_attribute_name))
299         qWarning("QSGShaderEffectItem: Missing reference to \'%s\'.", qt_position_attribute_name);
300     if (!m_mesh && !m_source.attributeNames.contains(qt_texcoord_attribute_name))
301         qWarning("QSGShaderEffectItem: Missing reference to \'%s\'.", qt_texcoord_attribute_name);
302     if (!m_source.respectsMatrix)
303         qWarning("QSGShaderEffectItem: Missing reference to \'qt_ModelViewProjectionMatrix\'.");
304     if (!m_source.respectsOpacity)
305         qWarning("QSGShaderEffectItem: Missing reference to \'qt_Opacity\'.");
306
307     for (int i = 0; i < m_sources.size(); ++i) {
308         QVariant v = property(m_sources.at(i).name);
309         setSource(v, i);
310     }
311
312     connectPropertySignals();
313 }
314
315 void QSGShaderEffectItem::lookThroughShaderCode(const QByteArray &code)
316 {
317     // Regexp for matching attributes and uniforms.
318     // In human readable form: attribute|uniform [lowp|mediump|highp] <type> <name>
319     static QRegExp re(QLatin1String("\\b(attribute|uniform)\\b\\s*\\b(?:lowp|mediump|highp)?\\b\\s*\\b(\\w+)\\b\\s*\\b(\\w+)"));
320     Q_ASSERT(re.isValid());
321
322     int pos = -1;
323
324     QString wideCode = QString::fromLatin1(code.constData(), code.size());
325
326     while ((pos = re.indexIn(wideCode, pos + 1)) != -1) {
327         QByteArray decl = re.cap(1).toLatin1(); // uniform or attribute
328         QByteArray type = re.cap(2).toLatin1(); // type
329         QByteArray name = re.cap(3).toLatin1(); // variable name
330
331         if (decl == "attribute") {
332             m_source.attributeNames.append(name);
333         } else {
334             Q_ASSERT(decl == "uniform");
335
336             if (name == "qt_ModelViewProjectionMatrix") {
337                 m_source.respectsMatrix = true;
338             } else if (name == "qt_Opacity") {
339                 m_source.respectsOpacity = true;
340             } else {
341                 m_source.uniformNames.insert(name);
342                 if (type == "sampler2D") {
343                     SourceData d;
344                     d.mapper = new QSignalMapper;
345                     d.name = name;
346                     d.item = 0;
347                     m_sources.append(d);
348                 }
349             }
350         }
351     }
352 }
353
354 void QSGShaderEffectItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
355 {
356     m_dirtyGeometry = true;
357     QSGItem::geometryChanged(newGeometry, oldGeometry);
358 }
359
360 QSGNode *QSGShaderEffectItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
361 {
362     QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode);
363
364     if (!node) {
365         node = new QSGShaderEffectNode;
366         node->setMaterial(&m_material);
367         m_programDirty = true;
368         m_dirtyData = true;
369         m_dirtyGeometry = true;
370     }
371
372     if (m_dirtyMesh) {
373         node->setGeometry(0);
374         m_dirtyMesh = false;
375         m_dirtyGeometry = true;
376     }
377
378     if (m_dirtyGeometry) {
379         node->setFlag(QSGNode::OwnsGeometry, false);
380         QSGGeometry *geometry = node->geometry();
381         QRectF rect(0, 0, width(), height());
382         QSGShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
383
384         geometry = mesh->updateGeometry(geometry, m_source.attributeNames, rect);
385         if (!geometry) {
386             delete node;
387             return 0;
388         }
389
390         node->setGeometry(geometry);
391         node->setFlag(QSGNode::OwnsGeometry, true);
392
393         m_dirtyGeometry = false;
394     }
395
396     if (m_programDirty) {
397         QSGShaderEffectProgram s = m_source;
398         if (s.fragmentCode.isEmpty())
399             s.fragmentCode = qt_default_fragment_code;
400         if (s.vertexCode.isEmpty())
401             s.vertexCode = qt_default_vertex_code;
402
403         m_material.setProgramSource(s);
404         node->markDirty(QSGNode::DirtyMaterial);
405         m_programDirty = false;
406     }
407
408     // Update blending
409     if (bool(m_material.flags() & QSGMaterial::Blending) != m_blending) {
410         m_material.setFlag(QSGMaterial::Blending, m_blending);
411         node->markDirty(QSGNode::DirtyMaterial);
412     }
413
414     if (int(m_material.cullMode()) != int(m_cullMode)) {
415         m_material.setCullMode(QSGShaderEffectMaterial::CullMode(m_cullMode));
416         node->markDirty(QSGNode::DirtyMaterial);
417     }
418
419     if (m_dirtyData) {
420         QVector<QPair<QByteArray, QVariant> > values;
421         QVector<QPair<QByteArray, QPointer<QSGItem> > > textures;
422         const QVector<QPair<QByteArray, QPointer<QSGItem> > > &oldTextures = m_material.textureProviders();
423
424         for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin(); 
425              it != m_source.uniformNames.end(); ++it) {
426             values.append(qMakePair(*it, property(*it)));
427         }
428         for (int i = 0; i < oldTextures.size(); ++i) {
429             QSGTextureProvider *oldSource = QSGTextureProvider::from(oldTextures.at(i).second);
430             if (oldSource && oldSource->textureChangedSignal())
431                 disconnect(oldTextures.at(i).second, oldSource->textureChangedSignal(), node, SLOT(markDirtyTexture()));
432         }
433         for (int i = 0; i < m_sources.size(); ++i) {
434             const SourceData &source = m_sources.at(i);
435             textures.append(qMakePair(source.name, source.item));
436             QSGTextureProvider *t = QSGTextureProvider::from(source.item);
437             if (t && t->textureChangedSignal())
438                 connect(source.item, t->textureChangedSignal(), node, SLOT(markDirtyTexture()));
439         }
440         m_material.setUniforms(values);
441         m_material.setTextureProviders(textures);
442         node->markDirty(QSGNode::DirtyMaterial);
443         m_dirtyData = false;
444     }
445
446     return node;
447 }
448
449 QT_END_NAMESPACE