fix declarative to work after broken refactor
[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_pleaseReset(true)
134     , m_dirtyData(true)
135     , m_material(0)
136     , m_rootNode(0)
137 {
138     setFlag(QSGItem::ItemHasContents);
139 }
140
141 class QSGShaderEffectMaterialObject : public QObject, public QSGShaderEffectMaterial { };
142
143 QSGCustomParticle::~QSGCustomParticle()
144 {
145     if (m_material)
146         m_material->deleteLater();
147 }
148
149 void QSGCustomParticle::componentComplete()
150 {
151     reset();
152     QSGParticlePainter::componentComplete();
153 }
154
155
156 //Trying to keep the shader conventions the same as in qsgshadereffectitem
157 /*!
158     \qmlproperty string QtQuick.Particles2::CustomParticle::fragmentShader
159
160     This property holds the fragment shader's GLSL source code.
161     The default shader expects the texture coordinate to be passed from the
162     vertex shader as "varying highp vec2 qt_TexCoord0", and it samples from a
163     sampler2D named "source".
164 */
165
166 void QSGCustomParticle::setFragmentShader(const QByteArray &code)
167 {
168     if (m_source.fragmentCode.constData() == code.constData())
169         return;
170     m_source.fragmentCode = code;
171     if (isComponentComplete()) {
172         reset();
173     }
174     emit fragmentShaderChanged();
175 }
176
177 /*!
178     \qmlproperty string QtQuick.Particles2::CustomParticle::vertexShader
179
180     This property holds the vertex shader's GLSL source code.
181
182     The default shader passes the texture coordinate along to the fragment
183     shader as "varying highp vec2 qt_TexCoord0".
184
185     To aid writing a particle vertex shader, the following GLSL code is prepended
186     to your vertex shader:
187     \code
188         attribute highp vec2 qt_ParticlePos;
189         attribute highp vec2 qt_ParticleTex;
190         attribute highp vec4 qt_ParticleData; //  x = time,  y = lifeSpan, z = size,  w = endSize
191         attribute highp vec4 qt_ParticleVec; // x,y = constant speed,  z,w = acceleration
192         attribute highp float qt_ParticleR;
193         uniform highp mat4 qt_Matrix;
194         uniform highp float qt_Timestamp;
195         varying highp vec2 qt_TexCoord0;
196         void defaultMain() {
197             qt_TexCoord0 = qt_ParticleTex;
198             highp float size = qt_ParticleData.z;
199             highp float endSize = qt_ParticleData.w;
200             highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y;
201             highp float currentSize = mix(size, endSize, t * t);
202             if (t < 0. || t > 1.)
203                 currentSize = 0.;
204             highp vec2 pos = qt_ParticlePos
205                            - currentSize / 2. + currentSize * qt_ParticleTex   // adjust size
206                            + qt_ParticleVec.xy * t * qt_ParticleData.y         // apply speed vector..
207                            + 0.5 * qt_ParticleVec.zw * pow(t * qt_ParticleData.y, 2.);
208             gl_Position = qt_Matrix * vec4(pos.x, pos.y, 0, 1);
209         }
210     \endcode
211
212     defaultMain() is the same code as in the default shader, you can call this for basic
213     particle functions and then add additional variables for custom effects. Note that
214     the vertex shader for particles is responsible for simulating the movement of particles
215     over time, the particle data itself only has the starting position and spawn time.
216 */
217
218 void QSGCustomParticle::setVertexShader(const QByteArray &code)
219 {
220     if (m_source.vertexCode.constData() == code.constData())
221         return;
222     m_source.vertexCode = code;
223     if (isComponentComplete()) {
224         reset();
225     }
226     emit vertexShaderChanged();
227 }
228
229 void QSGCustomParticle::reset()
230 {
231     disconnectPropertySignals();
232
233     m_source.attributeNames.clear();
234     m_source.uniformNames.clear();
235     m_source.respectsOpacity = false;
236     m_source.respectsMatrix = false;
237     m_source.className = metaObject()->className();
238
239     for (int i = 0; i < m_sources.size(); ++i) {
240         const SourceData &source = m_sources.at(i);
241         delete source.mapper;
242         if (source.item && source.item->parentItem() == this)
243             source.item->setParentItem(0);
244     }
245     m_sources.clear();
246
247     QSGParticlePainter::reset();
248     m_pleaseReset = true;
249     update();
250 }
251
252
253 void QSGCustomParticle::changeSource(int index)
254 {
255     Q_ASSERT(index >= 0 && index < m_sources.size());
256     QVariant v = property(m_sources.at(index).name.constData());
257     setSource(v, index);
258 }
259
260 void QSGCustomParticle::updateData()
261 {
262     m_dirtyData = true;
263     update();
264 }
265
266 void QSGCustomParticle::setSource(const QVariant &var, int index)
267 {
268     Q_ASSERT(index >= 0 && index < m_sources.size());
269
270     SourceData &source = m_sources[index];
271
272     source.item = 0;
273     if (var.isNull()) {
274         return;
275     } else if (!qVariantCanConvert<QObject *>(var)) {
276         qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
277         return;
278     }
279
280     QObject *obj = qVariantValue<QObject *>(var);
281     source.item = qobject_cast<QSGItem *>(obj);
282     if (!source.item || !source.item->isTextureProvider()) {
283         qWarning("ShaderEffect: source uniform [%s] is not assigned a valid texture provider: %s [%s]",
284                  source.name.constData(), qPrintable(obj->objectName()), obj->metaObject()->className());
285         return;
286     }
287
288     // TODO: Copy better solution in QSGShaderEffect when they find it.
289     // 'source.item' needs a canvas to get a scenegraph node.
290     // The easiest way to make sure it gets a canvas is to
291     // make it a part of the same item tree as 'this'.
292     if (source.item && source.item->parentItem() == 0) {
293         source.item->setParentItem(this);
294         source.item->setVisible(false);
295     }
296 }
297
298 void QSGCustomParticle::disconnectPropertySignals()
299 {
300     disconnect(this, 0, this, SLOT(updateData()));
301     for (int i = 0; i < m_sources.size(); ++i) {
302         SourceData &source = m_sources[i];
303         disconnect(this, 0, source.mapper, 0);
304         disconnect(source.mapper, 0, this, 0);
305     }
306 }
307
308 void QSGCustomParticle::connectPropertySignals()
309 {
310     QSet<QByteArray>::const_iterator it;
311     for (it = m_source.uniformNames.begin(); it != m_source.uniformNames.end(); ++it) {
312         int pi = metaObject()->indexOfProperty(it->constData());
313         if (pi >= 0) {
314             QMetaProperty mp = metaObject()->property(pi);
315             if (!mp.hasNotifySignal())
316                 qWarning("QSGCustomParticle: property '%s' does not have notification method!", it->constData());
317             QByteArray signalName("2");
318             signalName.append(mp.notifySignal().signature());
319             connect(this, signalName, this, SLOT(updateData()));
320         } else {
321             qWarning("QSGCustomParticle: '%s' does not have a matching property!", it->constData());
322         }
323     }
324     for (int i = 0; i < m_sources.size(); ++i) {
325         SourceData &source = m_sources[i];
326         int pi = metaObject()->indexOfProperty(source.name.constData());
327         if (pi >= 0) {
328             QMetaProperty mp = metaObject()->property(pi);
329             QByteArray signalName("2");
330             signalName.append(mp.notifySignal().signature());
331             connect(this, signalName, source.mapper, SLOT(map()));
332             source.mapper->setMapping(this, i);
333             connect(source.mapper, SIGNAL(mapped(int)), this, SLOT(changeSource(int)));
334         } else {
335             qWarning("QSGCustomParticle: '%s' does not have a matching source!", source.name.constData());
336         }
337     }
338 }
339
340 void QSGCustomParticle::updateProperties()
341 {
342     QByteArray vertexCode = m_source.vertexCode;
343     QByteArray fragmentCode = m_source.fragmentCode;
344     if (vertexCode.isEmpty())
345         vertexCode = qt_particles_default_vertex_code;
346     if (fragmentCode.isEmpty())
347         fragmentCode = qt_particles_default_fragment_code;
348     vertexCode = qt_particles_template_vertex_code + vertexCode;
349
350     m_source.attributeNames.clear();
351     m_source.attributeNames << "qt_ParticlePos"
352                             << "qt_ParticleTex"
353                             << "qt_ParticleData"
354                             << "qt_ParticleVec"
355                             << "qt_ParticleR";
356
357     lookThroughShaderCode(vertexCode);
358     lookThroughShaderCode(fragmentCode);
359
360     if (!m_source.respectsMatrix)
361         qWarning("QSGCustomParticle: Missing reference to \'qt_Matrix\'.");
362     if (!m_source.respectsOpacity)
363         qWarning("QSGCustomParticle: Missing reference to \'qt_Opacity\'.");
364
365     for (int i = 0; i < m_sources.size(); ++i) {
366         QVariant v = property(m_sources.at(i).name);
367         setSource(v, i);
368     }
369
370     connectPropertySignals();
371 }
372
373 void QSGCustomParticle::lookThroughShaderCode(const QByteArray &code)
374 {
375     // Regexp for matching attributes and uniforms.
376     // In human readable form: attribute|uniform [lowp|mediump|highp] <type> <name>
377     static QRegExp re(QLatin1String("\\b(attribute|uniform)\\b\\s*\\b(?:lowp|mediump|highp)?\\b\\s*\\b(\\w+)\\b\\s*\\b(\\w+)"));
378     Q_ASSERT(re.isValid());
379
380     int pos = -1;
381
382     QString wideCode = QString::fromLatin1(code.constData(), code.size());
383
384     while ((pos = re.indexIn(wideCode, pos + 1)) != -1) {
385         QByteArray decl = re.cap(1).toLatin1(); // uniform or attribute
386         QByteArray type = re.cap(2).toLatin1(); // type
387         QByteArray name = re.cap(3).toLatin1(); // variable name
388
389         if (decl == "attribute") {
390             if (!m_source.attributeNames.contains(name))
391                 qWarning() << "Custom Particle: Unknown attribute " << name;
392         } else {
393             Q_ASSERT(decl == "uniform");//TODO: Shouldn't assert
394
395             if (name == "qt_Matrix") {
396                 m_source.respectsMatrix = true;
397             } else if (name == "qt_Opacity") {
398                 m_source.respectsOpacity = true;
399             } else if (name == "qt_Timestamp") {
400                 //Not strictly necessary
401             } else {
402                 m_source.uniformNames.insert(name);
403                 if (type == "sampler2D") {
404                     SourceData d;
405                     d.mapper = new QSignalMapper;
406                     d.name = name;
407                     d.item = 0;
408                     m_sources.append(d);
409                 }
410             }
411         }
412     }
413 }
414
415 QSGNode *QSGCustomParticle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
416 {
417     Q_UNUSED(oldNode);
418     if (m_pleaseReset){
419
420         //delete m_material;//Shader effect item doesn't regen material?
421
422         delete m_rootNode;//Automatically deletes children
423         m_rootNode = 0;
424         m_nodes.clear();
425         m_pleaseReset = false;
426         m_dirtyData = false;
427     }
428
429     if (m_system && m_system->isRunning() && !m_system->isPaused()){
430         prepareNextFrame();
431         if (m_rootNode) {
432             update();
433             //### Should I be using dirty geometry too/instead?
434             foreach (QSGGeometryNode* node, m_nodes)
435                 node->markDirty(QSGNode::DirtyMaterial);//done in buildData?
436         }
437     }
438
439     return m_rootNode;
440 }
441
442 void QSGCustomParticle::prepareNextFrame(){
443     if (!m_rootNode)
444         m_rootNode = buildCustomNodes();
445     if (!m_rootNode)
446         return;
447
448     m_lastTime = m_system->systemSync(this) / 1000.;
449     if (m_dirtyData || true)//Currently this is how we update timestamp... potentially over expensive.
450         buildData();
451 }
452
453 QSGShaderEffectNode* QSGCustomParticle::buildCustomNodes()
454 {
455 #ifdef QT_OPENGL_ES_2
456     if (m_count * 4 > 0xffff) {
457         printf("CustomParticle: Too many particles... \n");
458         return 0;
459     }
460 #endif
461
462     if (m_count <= 0) {
463         printf("CustomParticle: Too few particles... \n");
464         return 0;
465     }
466
467     updateProperties();
468
469     QSGShaderEffectProgram s = m_source;
470     if (s.fragmentCode.isEmpty())
471         s.fragmentCode = qt_particles_default_fragment_code;
472     if (s.vertexCode.isEmpty())
473         s.vertexCode = qt_particles_default_vertex_code;
474
475     if (!m_material) {
476         m_material = new QSGShaderEffectMaterialObject;
477     }
478
479     s.vertexCode = qt_particles_template_vertex_code + s.vertexCode;
480     m_material->setProgramSource(s);
481     foreach (const QString &str, m_groups){
482         int gIdx = m_system->m_groupIds[str];
483         int count = m_system->m_groupData[gIdx]->size();
484         //Create Particle Geometry
485         int vCount = count * 4;
486         int iCount = count * 6;
487         QSGGeometry *g = new QSGGeometry(PlainParticle_AttributeSet, vCount, iCount);
488         g->setDrawingMode(GL_TRIANGLES);
489         PlainVertex *vertices = (PlainVertex *) g->vertexData();
490         for (int p=0; p < count; ++p) {
491             commit(gIdx, p);
492             vertices[0].tx = 0;
493             vertices[0].ty = 0;
494
495             vertices[1].tx = 1;
496             vertices[1].ty = 0;
497
498             vertices[2].tx = 0;
499             vertices[2].ty = 1;
500
501             vertices[3].tx = 1;
502             vertices[3].ty = 1;
503             vertices += 4;
504         }
505         quint16 *indices = g->indexDataAsUShort();
506         for (int i=0; i < count; ++i) {
507             int o = i * 4;
508             indices[0] = o;
509             indices[1] = o + 1;
510             indices[2] = o + 2;
511             indices[3] = o + 1;
512             indices[4] = o + 3;
513             indices[5] = o + 2;
514             indices += 6;
515         }
516
517         QSGShaderEffectNode* node = new QSGShaderEffectNode();
518
519         node->setGeometry(g);
520         node->setMaterial(m_material);
521         node->markDirty(QSGNode::DirtyMaterial);
522
523         m_nodes.insert(gIdx, node);
524     }
525     foreach (QSGShaderEffectNode* node, m_nodes){
526         if (node == *(m_nodes.begin()))
527                 continue;
528         (*(m_nodes.begin()))->appendChildNode(node);
529     }
530
531     return *(m_nodes.begin());
532 }
533
534
535 void QSGCustomParticle::buildData()
536 {
537     if (!m_rootNode)
538         return;
539     const QByteArray timestampName("qt_Timestamp");
540     QVector<QPair<QByteArray, QVariant> > values;
541     QVector<QPair<QByteArray, QSGTextureProvider *> > textures;
542     const QVector<QPair<QByteArray, QSGTextureProvider *> > &oldTextures = m_material->textureProviders();
543     for (int i = 0; i < oldTextures.size(); ++i) {
544         QSGTextureProvider *t = oldTextures.at(i).second;
545         if (t)
546             foreach (QSGShaderEffectNode* node, m_nodes)
547                 disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
548     }
549     for (int i = 0; i < m_sources.size(); ++i) {
550         const SourceData &source = m_sources.at(i);
551         QSGTextureProvider *t = source.item->textureProvider();
552         textures.append(qMakePair(source.name, t));
553         if (t)
554             foreach (QSGShaderEffectNode* node, m_nodes)
555                 connect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
556     }
557     for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin();
558          it != m_source.uniformNames.end(); ++it) {
559         values.append(qMakePair(*it, property(*it)));
560     }
561     values.append(qMakePair(timestampName, QVariant(m_lastTime)));
562     m_material->setUniforms(values);
563     m_material->setTextureProviders(textures);
564     m_dirtyData = false;
565     foreach (QSGShaderEffectNode* node, m_nodes)
566         node->markDirty(QSGNode::DirtyMaterial);
567 }
568
569 void QSGCustomParticle::initialize(int gIdx, int pIdx)
570 {
571     QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx];
572     datum->r = rand()/(qreal)RAND_MAX;
573 }
574
575 void QSGCustomParticle::commit(int gIdx, int pIdx)
576 {
577     if (m_nodes[gIdx] == 0)
578         return;
579
580     QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx];
581     PlainVertices *particles = (PlainVertices *) m_nodes[gIdx]->geometry()->vertexData();
582     PlainVertex *vertices = (PlainVertex *)&particles[pIdx];
583     for (int i=0; i<4; ++i) {
584         vertices[i].x = datum->x - m_systemOffset.x();
585         vertices[i].y = datum->y - m_systemOffset.y();
586         vertices[i].t = datum->t;
587         vertices[i].lifeSpan = datum->lifeSpan;
588         vertices[i].size = datum->size;
589         vertices[i].endSize = datum->endSize;
590         vertices[i].vx = datum->vx;
591         vertices[i].vy = datum->vy;
592         vertices[i].ax = datum->ax;
593         vertices[i].ay = datum->ay;
594         vertices[i].r = datum->r;
595     }
596 }
597
598 QT_END_NAMESPACE