5d1c605122f94641d3410aca999ccc3195c2233a
[profile/ivi/qtdeclarative.git] / src / declarative / particles / qsgcustomparticle.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 "qsgcustomparticle_p.h"
43 #include <private/qsgshadereffectmesh_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, fTex) * 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 QSGCustomParticle
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 QSGCustomParticle::QSGCustomParticle(QSGItem* parent)
132     : QSGParticlePainter(parent)
133     , m_dirtyData(true)
134     , m_material(0)
135     , m_rootNode(0)
136 {
137     setFlag(QSGItem::ItemHasContents);
138 }
139
140 class QSGShaderEffectMaterialObject : public QObject, public QSGShaderEffectMaterial { };
141
142 QSGCustomParticle::~QSGCustomParticle()
143 {
144     if (m_material)
145         m_material->deleteLater();
146 }
147
148 void QSGCustomParticle::componentComplete()
149 {
150     reset();
151     QSGParticlePainter::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 QSGCustomParticle::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 QSGCustomParticle::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 QSGCustomParticle::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     QSGParticlePainter::reset();
247     m_pleaseReset = true;
248     update();
249 }
250
251
252 void QSGCustomParticle::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 QSGCustomParticle::updateData()
260 {
261     m_dirtyData = true;
262     update();
263 }
264
265 void QSGCustomParticle::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<QSGItem *>(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 QSGShaderEffect 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 QSGCustomParticle::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 QSGCustomParticle::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("QSGCustomParticle: property '%s' does not have notification method!", it->constData());
316             QByteArray signalName("2");
317             signalName.append(mp.notifySignal().signature());
318             connect(this, signalName, this, SLOT(updateData()));
319         } else {
320             qWarning("QSGCustomParticle: '%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().signature());
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("QSGCustomParticle: '%s' does not have a matching source!", source.name.constData());
335         }
336     }
337 }
338
339 void QSGCustomParticle::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("QSGCustomParticle: Missing reference to \'qt_Matrix\'.");
361     if (!m_source.respectsOpacity)
362         qWarning("QSGCustomParticle: 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 QSGCustomParticle::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 *QSGCustomParticle::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             //### Should I be using dirty geometry too/instead?
433             foreach (QSGGeometryNode* node, m_nodes)
434                 node->markDirty(QSGNode::DirtyMaterial);//done in buildData?
435         }
436     }
437
438     return m_rootNode;
439 }
440
441 void QSGCustomParticle::prepareNextFrame(){
442     if (!m_rootNode)
443         m_rootNode = buildCustomNodes();
444     if (!m_rootNode)
445         return;
446
447     m_lastTime = m_system->systemSync(this) / 1000.;
448     if (m_dirtyData || true)//Currently this is how we update timestamp... potentially over expensive.
449         buildData();
450 }
451
452 QSGShaderEffectNode* QSGCustomParticle::buildCustomNodes()
453 {
454 #ifdef QT_OPENGL_ES_2
455     if (m_count * 4 > 0xffff) {
456         printf("CustomParticle: Too many particles... \n");
457         return 0;
458     }
459 #endif
460
461     if (m_count <= 0) {
462         printf("CustomParticle: Too few particles... \n");
463         return 0;
464     }
465
466     updateProperties();
467
468     QSGShaderEffectProgram s = m_source;
469     if (s.fragmentCode.isEmpty())
470         s.fragmentCode = qt_particles_default_fragment_code;
471     if (s.vertexCode.isEmpty())
472         s.vertexCode = qt_particles_default_vertex_code;
473
474     if (!m_material) {
475         m_material = new QSGShaderEffectMaterialObject;
476     }
477
478     s.vertexCode = qt_particles_template_vertex_code + s.vertexCode;
479     m_material->setProgramSource(s);
480     foreach (const QString &str, m_groups){
481         int gIdx = m_system->m_groupIds[str];
482         int count = m_system->m_groupData[gIdx]->size();
483         //Create Particle Geometry
484         int vCount = count * 4;
485         int iCount = count * 6;
486         QSGGeometry *g = new QSGGeometry(PlainParticle_AttributeSet, vCount, iCount);
487         g->setDrawingMode(GL_TRIANGLES);
488         PlainVertex *vertices = (PlainVertex *) g->vertexData();
489         for (int p=0; p < count; ++p) {
490             commit(gIdx, p);
491             vertices[0].tx = 0;
492             vertices[0].ty = 0;
493
494             vertices[1].tx = 1;
495             vertices[1].ty = 0;
496
497             vertices[2].tx = 0;
498             vertices[2].ty = 1;
499
500             vertices[3].tx = 1;
501             vertices[3].ty = 1;
502             vertices += 4;
503         }
504         quint16 *indices = g->indexDataAsUShort();
505         for (int i=0; i < count; ++i) {
506             int o = i * 4;
507             indices[0] = o;
508             indices[1] = o + 1;
509             indices[2] = o + 2;
510             indices[3] = o + 1;
511             indices[4] = o + 3;
512             indices[5] = o + 2;
513             indices += 6;
514         }
515
516         QSGShaderEffectNode* node = new QSGShaderEffectNode();
517
518         node->setGeometry(g);
519         node->setMaterial(m_material);
520         node->markDirty(QSGNode::DirtyMaterial);
521
522         m_nodes.insert(gIdx, node);
523     }
524     foreach (QSGShaderEffectNode* node, m_nodes){
525         if (node == *(m_nodes.begin()))
526                 continue;
527         (*(m_nodes.begin()))->appendChildNode(node);
528     }
529
530     return *(m_nodes.begin());
531 }
532
533
534 void QSGCustomParticle::buildData()
535 {
536     if (!m_rootNode)
537         return;
538     const QByteArray timestampName("qt_Timestamp");
539     QVector<QPair<QByteArray, QVariant> > values;
540     QVector<QPair<QByteArray, QSGTextureProvider *> > textures;
541     const QVector<QPair<QByteArray, QSGTextureProvider *> > &oldTextures = m_material->textureProviders();
542     for (int i = 0; i < oldTextures.size(); ++i) {
543         QSGTextureProvider *t = oldTextures.at(i).second;
544         if (t)
545             foreach (QSGShaderEffectNode* node, m_nodes)
546                 disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
547     }
548     for (int i = 0; i < m_sources.size(); ++i) {
549         const SourceData &source = m_sources.at(i);
550         QSGTextureProvider *t = source.item->textureProvider();
551         textures.append(qMakePair(source.name, t));
552         if (t)
553             foreach (QSGShaderEffectNode* node, m_nodes)
554                 connect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
555     }
556     for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin();
557          it != m_source.uniformNames.end(); ++it) {
558         values.append(qMakePair(*it, property(*it)));
559     }
560     values.append(qMakePair(timestampName, QVariant(m_lastTime)));
561     m_material->setUniforms(values);
562     m_material->setTextureProviders(textures);
563     m_dirtyData = false;
564     foreach (QSGShaderEffectNode* node, m_nodes)
565         node->markDirty(QSGNode::DirtyMaterial);
566 }
567
568 void QSGCustomParticle::initialize(int gIdx, int pIdx)
569 {
570     QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx];
571     datum->r = rand()/(qreal)RAND_MAX;
572 }
573
574 void QSGCustomParticle::commit(int gIdx, int pIdx)
575 {
576     if (m_nodes[gIdx] == 0)
577         return;
578
579     QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx];
580     PlainVertices *particles = (PlainVertices *) m_nodes[gIdx]->geometry()->vertexData();
581     PlainVertex *vertices = (PlainVertex *)&particles[pIdx];
582     for (int i=0; i<4; ++i) {
583         vertices[i].x = datum->x - m_systemOffset.x();
584         vertices[i].y = datum->y - m_systemOffset.y();
585         vertices[i].t = datum->t;
586         vertices[i].lifeSpan = datum->lifeSpan;
587         vertices[i].size = datum->size;
588         vertices[i].endSize = datum->endSize;
589         vertices[i].vx = datum->vx;
590         vertices[i].vy = datum->vy;
591         vertices[i].ax = datum->ax;
592         vertices[i].ay = datum->ay;
593         vertices[i].r = datum->r;
594     }
595 }
596
597 QT_END_NAMESPACE