1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qquickcustomparticle_p.h"
43 #include <QtQuick/private/qquickshadereffectmesh_p.h>
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"
72 static const char qt_particles_default_vertex_code[] =
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"
82 " gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity; \n"
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
93 static QSGGeometry::AttributeSet PlainParticle_AttributeSet =
96 (2 + 2 + 4 + 4 + 1) * sizeof(float),
97 PlainParticle_Attributes
116 struct PlainVertices {
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.
131 QQuickCustomParticle::QQuickCustomParticle(QQuickItem* parent)
132 : QQuickParticlePainter(parent)
137 setFlag(QQuickItem::ItemHasContents);
140 class QQuickShaderEffectMaterialObject : public QObject, public QQuickShaderEffectMaterial { };
142 QQuickCustomParticle::~QQuickCustomParticle()
145 m_material->deleteLater();
148 void QQuickCustomParticle::componentComplete()
151 QQuickParticlePainter::componentComplete();
155 //Trying to keep the shader conventions the same as in qsgshadereffectitem
157 \qmlproperty string QtQuick.Particles2::CustomParticle::fragmentShader
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".
165 void QQuickCustomParticle::setFragmentShader(const QByteArray &code)
167 if (m_source.fragmentCode.constData() == code.constData())
169 m_source.fragmentCode = code;
170 if (isComponentComplete()) {
173 emit fragmentShaderChanged();
177 \qmlproperty string QtQuick.Particles2::CustomParticle::vertexShader
179 This property holds the vertex shader's GLSL source code.
181 The default shader passes the texture coordinate along to the fragment
182 shader as "varying highp vec2 qt_TexCoord0".
184 To aid writing a particle vertex shader, the following GLSL code is prepended
185 to your vertex shader:
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;
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.)
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);
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.
217 void QQuickCustomParticle::setVertexShader(const QByteArray &code)
219 if (m_source.vertexCode.constData() == code.constData())
221 m_source.vertexCode = code;
222 if (isComponentComplete()) {
225 emit vertexShaderChanged();
228 void QQuickCustomParticle::reset()
230 disconnectPropertySignals();
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();
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);
246 QQuickParticlePainter::reset();
247 m_pleaseReset = true;
252 void QQuickCustomParticle::changeSource(int index)
254 Q_ASSERT(index >= 0 && index < m_sources.size());
255 QVariant v = property(m_sources.at(index).name.constData());
259 void QQuickCustomParticle::updateData()
265 void QQuickCustomParticle::setSource(const QVariant &var, int index)
267 Q_ASSERT(index >= 0 && index < m_sources.size());
269 SourceData &source = m_sources[index];
274 } else if (!qVariantCanConvert<QObject *>(var)) {
275 qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
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());
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);
297 void QQuickCustomParticle::disconnectPropertySignals()
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);
307 void QQuickCustomParticle::connectPropertySignals()
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());
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().signature());
318 connect(this, signalName, this, SLOT(updateData()));
320 qWarning("QQuickCustomParticle: '%s' does not have a matching property!", it->constData());
323 for (int i = 0; i < m_sources.size(); ++i) {
324 SourceData &source = m_sources[i];
325 int pi = metaObject()->indexOfProperty(source.name.constData());
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)));
334 qWarning("QQuickCustomParticle: '%s' does not have a matching source!", source.name.constData());
339 void QQuickCustomParticle::updateProperties()
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;
349 m_source.attributeNames.clear();
350 m_source.attributeNames << "qt_ParticlePos"
356 lookThroughShaderCode(vertexCode);
357 lookThroughShaderCode(fragmentCode);
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\'.");
364 for (int i = 0; i < m_sources.size(); ++i) {
365 QVariant v = property(m_sources.at(i).name);
369 connectPropertySignals();
372 void QQuickCustomParticle::lookThroughShaderCode(const QByteArray &code)
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());
381 QString wideCode = QString::fromLatin1(code.constData(), code.size());
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
388 if (decl == "attribute") {
389 if (!m_source.attributeNames.contains(name))
390 qWarning() << "Custom Particle: Unknown attribute " << name;
392 Q_ASSERT(decl == "uniform");//TODO: Shouldn't assert
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
401 m_source.uniformNames.insert(name);
402 if (type == "sampler2D") {
404 d.mapper = new QSignalMapper;
414 QSGNode *QQuickCustomParticle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
419 //delete m_material;//Shader effect item doesn't regen material?
421 delete m_rootNode;//Automatically deletes children
424 m_pleaseReset = false;
428 if (m_system && m_system->isRunning() && !m_system->isPaused()){
432 foreach (QSGGeometryNode* node, m_nodes)
433 node->markDirty(QSGNode::DirtyGeometry);//done in buildData?
440 void QQuickCustomParticle::prepareNextFrame(){
442 m_rootNode = buildCustomNodes();
446 m_lastTime = m_system->systemSync(this) / 1000.;
447 if (m_dirtyData || true)//Currently this is how we update timestamp... potentially over expensive.
451 QQuickShaderEffectNode* QQuickCustomParticle::buildCustomNodes()
453 #ifdef QT_OPENGL_ES_2
454 if (m_count * 4 > 0xffff) {
455 printf("CustomParticle: Too many particles... \n");
461 printf("CustomParticle: Too few particles... \n");
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;
474 m_material = new QQuickShaderEffectMaterialObject;
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();
483 QQuickShaderEffectNode* node = new QQuickShaderEffectNode();
484 m_nodes.insert(gIdx, node);
486 node->setMaterial(m_material);
487 node->markDirty(QSGNode::DirtyMaterial);
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) {
511 quint16 *indices = g->indexDataAsUShort();
512 for (int i=0; i < count; ++i) {
523 foreach (QQuickShaderEffectNode* node, m_nodes){
524 if (node == *(m_nodes.begin()))
526 (*(m_nodes.begin()))->appendChildNode(node);
529 return *(m_nodes.begin());
533 void QQuickCustomParticle::buildData()
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;
544 foreach (QQuickShaderEffectNode* node, m_nodes)
545 disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
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));
552 foreach (QQuickShaderEffectNode* node, m_nodes)
553 connect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
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)));
559 values.append(qMakePair(timestampName, QVariant(m_lastTime)));
560 m_material->setUniforms(values);
561 m_material->setTextureProviders(textures);
563 foreach (QQuickShaderEffectNode* node, m_nodes)
564 node->markDirty(QSGNode::DirtyMaterial);
567 void QQuickCustomParticle::initialize(int gIdx, int pIdx)
569 QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx];
570 datum->r = rand()/(qreal)RAND_MAX;
573 void QQuickCustomParticle::commit(int gIdx, int pIdx)
575 if (m_nodes[gIdx] == 0)
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;