Adapt to Qt5 meta-object changes
[profile/ivi/qtdeclarative.git] / src / quick / particles / qquickcustomparticle.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickcustomparticle_p.h"
43 #include <QtQuick/private/qquickshadereffectmesh_p.h>
44 #include <cstdlib>
45
46 QT_BEGIN_NAMESPACE
47
48 //Includes comments because the code isn't self explanatory
49 static const char qt_particles_template_vertex_code[] =
50         "attribute highp vec2 qt_ParticlePos;\n"
51         "attribute highp vec2 qt_ParticleTex;\n"
52         "attribute highp vec4 qt_ParticleData; //  x = time,  y = lifeSpan, z = size,  w = endSize\n"
53         "attribute highp vec4 qt_ParticleVec; // x,y = constant speed,  z,w = acceleration\n"
54         "attribute highp float qt_ParticleR;\n"
55         "uniform highp mat4 qt_Matrix;\n"
56         "uniform highp float qt_Timestamp;\n"
57         "varying highp vec2 qt_TexCoord0;\n"
58         "void defaultMain() {\n"
59         "    qt_TexCoord0 = qt_ParticleTex;\n"
60         "    highp float size = qt_ParticleData.z;\n"
61         "    highp float endSize = qt_ParticleData.w;\n"
62         "    highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y;\n"
63         "    highp float currentSize = mix(size, endSize, t * t);\n"
64         "    if (t < 0. || t > 1.)\n"
65         "        currentSize = 0.;\n"
66         "    highp vec2 pos = qt_ParticlePos\n"
67         "                   - currentSize / 2. + currentSize * qt_ParticleTex   // adjust size\n"
68         "                   + qt_ParticleVec.xy * t * qt_ParticleData.y         // apply speed vector..\n"
69         "                   + 0.5 * qt_ParticleVec.zw * pow(t * qt_ParticleData.y, 2.);\n"
70         "    gl_Position = qt_Matrix * vec4(pos.x, pos.y, 0, 1);\n"
71         "}";
72 static const char qt_particles_default_vertex_code[] =
73         "void main() {        \n"
74         "    defaultMain();   \n"
75         "}";
76
77 static const char qt_particles_default_fragment_code[] =
78         "uniform sampler2D source;                                  \n"
79         "varying highp vec2 qt_TexCoord0;                           \n"
80         "uniform lowp float qt_Opacity;                             \n"
81         "void main() {                                              \n"
82         "    gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity;   \n"
83         "}";
84
85 static QSGGeometry::Attribute PlainParticle_Attributes[] = {
86     QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true),       // Position
87     QSGGeometry::Attribute::create(1, 2, GL_FLOAT),             // TexCoord
88     QSGGeometry::Attribute::create(2, 4, GL_FLOAT),             // Data
89     QSGGeometry::Attribute::create(3, 4, GL_FLOAT),             // Vectors
90     QSGGeometry::Attribute::create(4, 1, GL_FLOAT)              // r
91 };
92
93 static QSGGeometry::AttributeSet PlainParticle_AttributeSet =
94 {
95     5, // Attribute Count
96     (2 + 2 + 4 + 4 + 1) * sizeof(float),
97     PlainParticle_Attributes
98 };
99
100 struct PlainVertex {
101     float x;
102     float y;
103     float tx;
104     float ty;
105     float t;
106     float lifeSpan;
107     float size;
108     float endSize;
109     float vx;
110     float vy;
111     float ax;
112     float ay;
113     float r;
114 };
115
116 struct PlainVertices {
117     PlainVertex v1;
118     PlainVertex v2;
119     PlainVertex v3;
120     PlainVertex v4;
121 };
122
123 /*!
124     \qmlclass CustomParticle QQuickCustomParticle
125     \inqmlmodule QtQuick.Particles 2
126     \inherits ParticlePainter
127     \brief The CustomParticle element allows you to specify your own shader to paint particles.
128
129 */
130
131 QQuickCustomParticle::QQuickCustomParticle(QQuickItem* parent)
132     : QQuickParticlePainter(parent)
133     , m_dirtyData(true)
134     , m_material(0)
135     , m_rootNode(0)
136 {
137     setFlag(QQuickItem::ItemHasContents);
138 }
139
140 class QQuickShaderEffectMaterialObject : public QObject, public QQuickShaderEffectMaterial { };
141
142 QQuickCustomParticle::~QQuickCustomParticle()
143 {
144     if (m_material)
145         m_material->deleteLater();
146 }
147
148 void QQuickCustomParticle::componentComplete()
149 {
150     reset();
151     QQuickParticlePainter::componentComplete();
152 }
153
154
155 //Trying to keep the shader conventions the same as in qsgshadereffectitem
156 /*!
157     \qmlproperty string QtQuick.Particles2::CustomParticle::fragmentShader
158
159     This property holds the fragment shader's GLSL source code.
160     The default shader expects the texture coordinate to be passed from the
161     vertex shader as "varying highp vec2 qt_TexCoord0", and it samples from a
162     sampler2D named "source".
163 */
164
165 void QQuickCustomParticle::setFragmentShader(const QByteArray &code)
166 {
167     if (m_source.fragmentCode.constData() == code.constData())
168         return;
169     m_source.fragmentCode = code;
170     if (isComponentComplete()) {
171         reset();
172     }
173     emit fragmentShaderChanged();
174 }
175
176 /*!
177     \qmlproperty string QtQuick.Particles2::CustomParticle::vertexShader
178
179     This property holds the vertex shader's GLSL source code.
180
181     The default shader passes the texture coordinate along to the fragment
182     shader as "varying highp vec2 qt_TexCoord0".
183
184     To aid writing a particle vertex shader, the following GLSL code is prepended
185     to your vertex shader:
186     \code
187         attribute highp vec2 qt_ParticlePos;
188         attribute highp vec2 qt_ParticleTex;
189         attribute highp vec4 qt_ParticleData; //  x = time,  y = lifeSpan, z = size,  w = endSize
190         attribute highp vec4 qt_ParticleVec; // x,y = constant speed,  z,w = acceleration
191         attribute highp float qt_ParticleR;
192         uniform highp mat4 qt_Matrix;
193         uniform highp float qt_Timestamp;
194         varying highp vec2 qt_TexCoord0;
195         void defaultMain() {
196             qt_TexCoord0 = qt_ParticleTex;
197             highp float size = qt_ParticleData.z;
198             highp float endSize = qt_ParticleData.w;
199             highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y;
200             highp float currentSize = mix(size, endSize, t * t);
201             if (t < 0. || t > 1.)
202                 currentSize = 0.;
203             highp vec2 pos = qt_ParticlePos
204                            - currentSize / 2. + currentSize * qt_ParticleTex   // adjust size
205                            + qt_ParticleVec.xy * t * qt_ParticleData.y         // apply speed vector..
206                            + 0.5 * qt_ParticleVec.zw * pow(t * qt_ParticleData.y, 2.);
207             gl_Position = qt_Matrix * vec4(pos.x, pos.y, 0, 1);
208         }
209     \endcode
210
211     defaultMain() is the same code as in the default shader, you can call this for basic
212     particle functions and then add additional variables for custom effects. Note that
213     the vertex shader for particles is responsible for simulating the movement of particles
214     over time, the particle data itself only has the starting position and spawn time.
215 */
216
217 void QQuickCustomParticle::setVertexShader(const QByteArray &code)
218 {
219     if (m_source.vertexCode.constData() == code.constData())
220         return;
221     m_source.vertexCode = code;
222     if (isComponentComplete()) {
223         reset();
224     }
225     emit vertexShaderChanged();
226 }
227
228 void QQuickCustomParticle::reset()
229 {
230     disconnectPropertySignals();
231
232     m_source.attributeNames.clear();
233     m_source.uniformNames.clear();
234     m_source.respectsOpacity = false;
235     m_source.respectsMatrix = false;
236     m_source.className = metaObject()->className();
237
238     for (int i = 0; i < m_sources.size(); ++i) {
239         const SourceData &source = m_sources.at(i);
240         delete source.mapper;
241         if (source.item && source.item->parentItem() == this)
242             source.item->setParentItem(0);
243     }
244     m_sources.clear();
245
246     QQuickParticlePainter::reset();
247     m_pleaseReset = true;
248     update();
249 }
250
251
252 void QQuickCustomParticle::changeSource(int index)
253 {
254     Q_ASSERT(index >= 0 && index < m_sources.size());
255     QVariant v = property(m_sources.at(index).name.constData());
256     setSource(v, index);
257 }
258
259 void QQuickCustomParticle::updateData()
260 {
261     m_dirtyData = true;
262     update();
263 }
264
265 void QQuickCustomParticle::setSource(const QVariant &var, int index)
266 {
267     Q_ASSERT(index >= 0 && index < m_sources.size());
268
269     SourceData &source = m_sources[index];
270
271     source.item = 0;
272     if (var.isNull()) {
273         return;
274     } else if (!qVariantCanConvert<QObject *>(var)) {
275         qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
276         return;
277     }
278
279     QObject *obj = qVariantValue<QObject *>(var);
280     source.item = qobject_cast<QQuickItem *>(obj);
281     if (!source.item || !source.item->isTextureProvider()) {
282         qWarning("ShaderEffect: source uniform [%s] is not assigned a valid texture provider: %s [%s]",
283                  source.name.constData(), qPrintable(obj->objectName()), obj->metaObject()->className());
284         return;
285     }
286
287     // TODO: Copy better solution in QQuickShaderEffect when they find it.
288     // 'source.item' needs a canvas to get a scenegraph node.
289     // The easiest way to make sure it gets a canvas is to
290     // make it a part of the same item tree as 'this'.
291     if (source.item && source.item->parentItem() == 0) {
292         source.item->setParentItem(this);
293         source.item->setVisible(false);
294     }
295 }
296
297 void QQuickCustomParticle::disconnectPropertySignals()
298 {
299     disconnect(this, 0, this, SLOT(updateData()));
300     for (int i = 0; i < m_sources.size(); ++i) {
301         SourceData &source = m_sources[i];
302         disconnect(this, 0, source.mapper, 0);
303         disconnect(source.mapper, 0, this, 0);
304     }
305 }
306
307 void QQuickCustomParticle::connectPropertySignals()
308 {
309     QSet<QByteArray>::const_iterator it;
310     for (it = m_source.uniformNames.begin(); it != m_source.uniformNames.end(); ++it) {
311         int pi = metaObject()->indexOfProperty(it->constData());
312         if (pi >= 0) {
313             QMetaProperty mp = metaObject()->property(pi);
314             if (!mp.hasNotifySignal())
315                 qWarning("QQuickCustomParticle: property '%s' does not have notification method!", it->constData());
316             QByteArray signalName("2");
317             signalName.append(mp.notifySignal().methodSignature());
318             connect(this, signalName, this, SLOT(updateData()));
319         } else {
320             qWarning("QQuickCustomParticle: '%s' does not have a matching property!", it->constData());
321         }
322     }
323     for (int i = 0; i < m_sources.size(); ++i) {
324         SourceData &source = m_sources[i];
325         int pi = metaObject()->indexOfProperty(source.name.constData());
326         if (pi >= 0) {
327             QMetaProperty mp = metaObject()->property(pi);
328             QByteArray signalName("2");
329             signalName.append(mp.notifySignal().methodSignature());
330             connect(this, signalName, source.mapper, SLOT(map()));
331             source.mapper->setMapping(this, i);
332             connect(source.mapper, SIGNAL(mapped(int)), this, SLOT(changeSource(int)));
333         } else {
334             qWarning("QQuickCustomParticle: '%s' does not have a matching source!", source.name.constData());
335         }
336     }
337 }
338
339 void QQuickCustomParticle::updateProperties()
340 {
341     QByteArray vertexCode = m_source.vertexCode;
342     QByteArray fragmentCode = m_source.fragmentCode;
343     if (vertexCode.isEmpty())
344         vertexCode = qt_particles_default_vertex_code;
345     if (fragmentCode.isEmpty())
346         fragmentCode = qt_particles_default_fragment_code;
347     vertexCode = qt_particles_template_vertex_code + vertexCode;
348
349     m_source.attributeNames.clear();
350     m_source.attributeNames << "qt_ParticlePos"
351                             << "qt_ParticleTex"
352                             << "qt_ParticleData"
353                             << "qt_ParticleVec"
354                             << "qt_ParticleR";
355
356     lookThroughShaderCode(vertexCode);
357     lookThroughShaderCode(fragmentCode);
358
359     if (!m_source.respectsMatrix)
360         qWarning("QQuickCustomParticle: Missing reference to \'qt_Matrix\'.");
361     if (!m_source.respectsOpacity)
362         qWarning("QQuickCustomParticle: Missing reference to \'qt_Opacity\'.");
363
364     for (int i = 0; i < m_sources.size(); ++i) {
365         QVariant v = property(m_sources.at(i).name);
366         setSource(v, i);
367     }
368
369     connectPropertySignals();
370 }
371
372 void QQuickCustomParticle::lookThroughShaderCode(const QByteArray &code)
373 {
374     // Regexp for matching attributes and uniforms.
375     // In human readable form: attribute|uniform [lowp|mediump|highp] <type> <name>
376     static QRegExp re(QLatin1String("\\b(attribute|uniform)\\b\\s*\\b(?:lowp|mediump|highp)?\\b\\s*\\b(\\w+)\\b\\s*\\b(\\w+)"));
377     Q_ASSERT(re.isValid());
378
379     int pos = -1;
380
381     QString wideCode = QString::fromLatin1(code.constData(), code.size());
382
383     while ((pos = re.indexIn(wideCode, pos + 1)) != -1) {
384         QByteArray decl = re.cap(1).toLatin1(); // uniform or attribute
385         QByteArray type = re.cap(2).toLatin1(); // type
386         QByteArray name = re.cap(3).toLatin1(); // variable name
387
388         if (decl == "attribute") {
389             if (!m_source.attributeNames.contains(name))
390                 qWarning() << "Custom Particle: Unknown attribute " << name;
391         } else {
392             Q_ASSERT(decl == "uniform");//TODO: Shouldn't assert
393
394             if (name == "qt_Matrix") {
395                 m_source.respectsMatrix = true;
396             } else if (name == "qt_Opacity") {
397                 m_source.respectsOpacity = true;
398             } else if (name == "qt_Timestamp") {
399                 //Not strictly necessary
400             } else {
401                 m_source.uniformNames.insert(name);
402                 if (type == "sampler2D") {
403                     SourceData d;
404                     d.mapper = new QSignalMapper;
405                     d.name = name;
406                     d.item = 0;
407                     m_sources.append(d);
408                 }
409             }
410         }
411     }
412 }
413
414 QSGNode *QQuickCustomParticle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
415 {
416     Q_UNUSED(oldNode);
417     if (m_pleaseReset){
418
419         //delete m_material;//Shader effect item doesn't regen material?
420
421         delete m_rootNode;//Automatically deletes children
422         m_rootNode = 0;
423         m_nodes.clear();
424         m_pleaseReset = false;
425         m_dirtyData = false;
426     }
427
428     if (m_system && m_system->isRunning() && !m_system->isPaused()){
429         prepareNextFrame();
430         if (m_rootNode) {
431             update();
432             foreach (QSGGeometryNode* node, m_nodes)
433                 node->markDirty(QSGNode::DirtyGeometry);//done in buildData?
434         }
435     }
436
437     return m_rootNode;
438 }
439
440 void QQuickCustomParticle::prepareNextFrame(){
441     if (!m_rootNode)
442         m_rootNode = buildCustomNodes();
443     if (!m_rootNode)
444         return;
445
446     m_lastTime = m_system->systemSync(this) / 1000.;
447     if (m_dirtyData || true)//Currently this is how we update timestamp... potentially over expensive.
448         buildData();
449 }
450
451 QQuickShaderEffectNode* QQuickCustomParticle::buildCustomNodes()
452 {
453 #ifdef QT_OPENGL_ES_2
454     if (m_count * 4 > 0xffff) {
455         printf("CustomParticle: Too many particles... \n");
456         return 0;
457     }
458 #endif
459
460     if (m_count <= 0) {
461         printf("CustomParticle: Too few particles... \n");
462         return 0;
463     }
464
465     updateProperties();
466
467     QQuickShaderEffectProgram s = m_source;
468     if (s.fragmentCode.isEmpty())
469         s.fragmentCode = qt_particles_default_fragment_code;
470     if (s.vertexCode.isEmpty())
471         s.vertexCode = qt_particles_default_vertex_code;
472
473     if (!m_material) {
474         m_material = new QQuickShaderEffectMaterialObject;
475     }
476
477     s.vertexCode = qt_particles_template_vertex_code + s.vertexCode;
478     m_material->setProgramSource(s);
479     foreach (const QString &str, m_groups){
480         int gIdx = m_system->groupIds[str];
481         int count = m_system->groupData[gIdx]->size();
482
483         QQuickShaderEffectNode* node = new QQuickShaderEffectNode();
484         m_nodes.insert(gIdx, node);
485
486         node->setMaterial(m_material);
487         node->markDirty(QSGNode::DirtyMaterial);
488
489         //Create Particle Geometry
490         int vCount = count * 4;
491         int iCount = count * 6;
492         QSGGeometry *g = new QSGGeometry(PlainParticle_AttributeSet, vCount, iCount);
493         g->setDrawingMode(GL_TRIANGLES);
494         node->setGeometry(g);
495         PlainVertex *vertices = (PlainVertex *) g->vertexData();
496         for (int p=0; p < count; ++p) {
497             commit(gIdx, p);
498             vertices[0].tx = 0;
499             vertices[0].ty = 0;
500
501             vertices[1].tx = 1;
502             vertices[1].ty = 0;
503
504             vertices[2].tx = 0;
505             vertices[2].ty = 1;
506
507             vertices[3].tx = 1;
508             vertices[3].ty = 1;
509             vertices += 4;
510         }
511         quint16 *indices = g->indexDataAsUShort();
512         for (int i=0; i < count; ++i) {
513             int o = i * 4;
514             indices[0] = o;
515             indices[1] = o + 1;
516             indices[2] = o + 2;
517             indices[3] = o + 1;
518             indices[4] = o + 3;
519             indices[5] = o + 2;
520             indices += 6;
521         }
522     }
523     foreach (QQuickShaderEffectNode* node, m_nodes){
524         if (node == *(m_nodes.begin()))
525                 continue;
526         (*(m_nodes.begin()))->appendChildNode(node);
527     }
528
529     return *(m_nodes.begin());
530 }
531
532
533 void QQuickCustomParticle::buildData()
534 {
535     if (!m_rootNode)
536         return;
537     const QByteArray timestampName("qt_Timestamp");
538     QVector<QPair<QByteArray, QVariant> > values;
539     QVector<QPair<QByteArray, QSGTextureProvider *> > textures;
540     const QVector<QPair<QByteArray, QSGTextureProvider *> > &oldTextures = m_material->textureProviders();
541     for (int i = 0; i < oldTextures.size(); ++i) {
542         QSGTextureProvider *t = oldTextures.at(i).second;
543         if (t)
544             foreach (QQuickShaderEffectNode* node, m_nodes)
545                 disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
546     }
547     for (int i = 0; i < m_sources.size(); ++i) {
548         const SourceData &source = m_sources.at(i);
549         QSGTextureProvider *t = source.item->textureProvider();
550         textures.append(qMakePair(source.name, t));
551         if (t)
552             foreach (QQuickShaderEffectNode* node, m_nodes)
553                 connect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
554     }
555     for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin();
556          it != m_source.uniformNames.end(); ++it) {
557         values.append(qMakePair(*it, property(*it)));
558     }
559     values.append(qMakePair(timestampName, QVariant(m_lastTime)));
560     m_material->setUniforms(values);
561     m_material->setTextureProviders(textures);
562     m_dirtyData = false;
563     foreach (QQuickShaderEffectNode* node, m_nodes)
564         node->markDirty(QSGNode::DirtyMaterial);
565 }
566
567 void QQuickCustomParticle::initialize(int gIdx, int pIdx)
568 {
569     QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx];
570     datum->r = rand()/(qreal)RAND_MAX;
571 }
572
573 void QQuickCustomParticle::commit(int gIdx, int pIdx)
574 {
575     if (m_nodes[gIdx] == 0)
576         return;
577
578     QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx];
579     PlainVertices *particles = (PlainVertices *) m_nodes[gIdx]->geometry()->vertexData();
580     PlainVertex *vertices = (PlainVertex *)&particles[pIdx];
581     for (int i=0; i<4; ++i) {
582         vertices[i].x = datum->x - m_systemOffset.x();
583         vertices[i].y = datum->y - m_systemOffset.y();
584         vertices[i].t = datum->t;
585         vertices[i].lifeSpan = datum->lifeSpan;
586         vertices[i].size = datum->size;
587         vertices[i].endSize = datum->endSize;
588         vertices[i].vx = datum->vx;
589         vertices[i].vy = datum->vy;
590         vertices[i].ax = datum->ax;
591         vertices[i].ay = datum->ay;
592         vertices[i].r = datum->r;
593     }
594 }
595
596 QT_END_NAMESPACE