1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qsgcustomparticle_p.h"
43 #include <private/qsgshadereffectmesh_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, fTex) * 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 QSGCustomParticle
125 \inqmlmodule QtQuick.Particles 2
126 \inherits ParticlePainter
127 \brief The CustomParticle element allows you to specify your own shader to paint particles.
131 QSGCustomParticle::QSGCustomParticle(QSGItem* parent)
132 : QSGParticlePainter(parent)
137 setFlag(QSGItem::ItemHasContents);
140 class QSGShaderEffectMaterialObject : public QObject, public QSGShaderEffectMaterial { };
142 QSGCustomParticle::~QSGCustomParticle()
145 m_material->deleteLater();
148 void QSGCustomParticle::componentComplete()
151 QSGParticlePainter::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 QSGCustomParticle::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 QSGCustomParticle::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 QSGCustomParticle::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 QSGParticlePainter::reset();
247 m_pleaseReset = true;
252 void QSGCustomParticle::changeSource(int index)
254 Q_ASSERT(index >= 0 && index < m_sources.size());
255 QVariant v = property(m_sources.at(index).name.constData());
259 void QSGCustomParticle::updateData()
265 void QSGCustomParticle::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<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());
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);
297 void QSGCustomParticle::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 QSGCustomParticle::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("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()));
320 qWarning("QSGCustomParticle: '%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("QSGCustomParticle: '%s' does not have a matching source!", source.name.constData());
339 void QSGCustomParticle::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("QSGCustomParticle: Missing reference to \'qt_Matrix\'.");
361 if (!m_source.respectsOpacity)
362 qWarning("QSGCustomParticle: 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 QSGCustomParticle::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 *QSGCustomParticle::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 //### Should I be using dirty geometry too/instead?
433 foreach (QSGGeometryNode* node, m_nodes)
434 node->markDirty(QSGNode::DirtyMaterial);//done in buildData?
441 void QSGCustomParticle::prepareNextFrame(){
443 m_rootNode = buildCustomNodes();
447 m_lastTime = m_system->systemSync(this) / 1000.;
448 if (m_dirtyData || true)//Currently this is how we update timestamp... potentially over expensive.
452 QSGShaderEffectNode* QSGCustomParticle::buildCustomNodes()
454 #ifdef QT_OPENGL_ES_2
455 if (m_count * 4 > 0xffff) {
456 printf("CustomParticle: Too many particles... \n");
462 printf("CustomParticle: Too few particles... \n");
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;
475 m_material = new QSGShaderEffectMaterialObject;
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) {
504 quint16 *indices = g->indexDataAsUShort();
505 for (int i=0; i < count; ++i) {
516 QSGShaderEffectNode* node = new QSGShaderEffectNode();
518 node->setGeometry(g);
519 node->setMaterial(m_material);
520 node->markDirty(QSGNode::DirtyMaterial);
522 m_nodes.insert(gIdx, node);
524 foreach (QSGShaderEffectNode* node, m_nodes){
525 if (node == *(m_nodes.begin()))
527 (*(m_nodes.begin()))->appendChildNode(node);
530 return *(m_nodes.begin());
534 void QSGCustomParticle::buildData()
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;
545 foreach (QSGShaderEffectNode* node, m_nodes)
546 disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
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));
553 foreach (QSGShaderEffectNode* node, m_nodes)
554 connect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
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)));
560 values.append(qMakePair(timestampName, QVariant(m_lastTime)));
561 m_material->setUniforms(values);
562 m_material->setTextureProviders(textures);
564 foreach (QSGShaderEffectNode* node, m_nodes)
565 node->markDirty(QSGNode::DirtyMaterial);
568 void QSGCustomParticle::initialize(int gIdx, int pIdx)
570 QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx];
571 datum->r = rand()/(qreal)RAND_MAX;
574 void QSGCustomParticle::commit(int gIdx, int pIdx)
576 if (m_nodes[gIdx] == 0)
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;