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)
133 , m_pleaseReset(true)
138 setFlag(QSGItem::ItemHasContents);
141 class QSGShaderEffectMaterialObject : public QObject, public QSGShaderEffectMaterial { };
143 QSGCustomParticle::~QSGCustomParticle()
146 m_material->deleteLater();
149 void QSGCustomParticle::componentComplete()
152 QSGParticlePainter::componentComplete();
156 //Trying to keep the shader conventions the same as in qsgshadereffectitem
158 \qmlproperty string QtQuick.Particles2::CustomParticle::fragmentShader
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".
166 void QSGCustomParticle::setFragmentShader(const QByteArray &code)
168 if (m_source.fragmentCode.constData() == code.constData())
170 m_source.fragmentCode = code;
171 if (isComponentComplete()) {
174 emit fragmentShaderChanged();
178 \qmlproperty string QtQuick.Particles2::CustomParticle::vertexShader
180 This property holds the vertex shader's GLSL source code.
182 The default shader passes the texture coordinate along to the fragment
183 shader as "varying highp vec2 qt_TexCoord0".
185 To aid writing a particle vertex shader, the following GLSL code is prepended
186 to your vertex shader:
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;
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.)
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);
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.
218 void QSGCustomParticle::setVertexShader(const QByteArray &code)
220 if (m_source.vertexCode.constData() == code.constData())
222 m_source.vertexCode = code;
223 if (isComponentComplete()) {
226 emit vertexShaderChanged();
229 void QSGCustomParticle::reset()
231 disconnectPropertySignals();
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();
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);
247 QSGParticlePainter::reset();
248 m_pleaseReset = true;
253 void QSGCustomParticle::changeSource(int index)
255 Q_ASSERT(index >= 0 && index < m_sources.size());
256 QVariant v = property(m_sources.at(index).name.constData());
260 void QSGCustomParticle::updateData()
266 void QSGCustomParticle::setSource(const QVariant &var, int index)
268 Q_ASSERT(index >= 0 && index < m_sources.size());
270 SourceData &source = m_sources[index];
275 } else if (!qVariantCanConvert<QObject *>(var)) {
276 qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData());
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());
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);
298 void QSGCustomParticle::disconnectPropertySignals()
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);
308 void QSGCustomParticle::connectPropertySignals()
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());
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()));
321 qWarning("QSGCustomParticle: '%s' does not have a matching property!", it->constData());
324 for (int i = 0; i < m_sources.size(); ++i) {
325 SourceData &source = m_sources[i];
326 int pi = metaObject()->indexOfProperty(source.name.constData());
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)));
335 qWarning("QSGCustomParticle: '%s' does not have a matching source!", source.name.constData());
340 void QSGCustomParticle::updateProperties()
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;
350 m_source.attributeNames.clear();
351 m_source.attributeNames << "qt_ParticlePos"
357 lookThroughShaderCode(vertexCode);
358 lookThroughShaderCode(fragmentCode);
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\'.");
365 for (int i = 0; i < m_sources.size(); ++i) {
366 QVariant v = property(m_sources.at(i).name);
370 connectPropertySignals();
373 void QSGCustomParticle::lookThroughShaderCode(const QByteArray &code)
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());
382 QString wideCode = QString::fromLatin1(code.constData(), code.size());
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
389 if (decl == "attribute") {
390 if (!m_source.attributeNames.contains(name))
391 qWarning() << "Custom Particle: Unknown attribute " << name;
393 Q_ASSERT(decl == "uniform");//TODO: Shouldn't assert
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
402 m_source.uniformNames.insert(name);
403 if (type == "sampler2D") {
405 d.mapper = new QSignalMapper;
415 QSGNode *QSGCustomParticle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
420 //delete m_material;//Shader effect item doesn't regen material?
422 delete m_rootNode;//Automatically deletes children
425 m_pleaseReset = false;
429 if (m_system && m_system->isRunning() && !m_system->isPaused()){
433 //### Should I be using dirty geometry too/instead?
434 foreach (QSGGeometryNode* node, m_nodes)
435 node->markDirty(QSGNode::DirtyMaterial);//done in buildData?
442 void QSGCustomParticle::prepareNextFrame(){
444 m_rootNode = buildCustomNodes();
448 m_lastTime = m_system->systemSync(this) / 1000.;
449 if (m_dirtyData || true)//Currently this is how we update timestamp... potentially over expensive.
453 QSGShaderEffectNode* QSGCustomParticle::buildCustomNodes()
455 #ifdef QT_OPENGL_ES_2
456 if (m_count * 4 > 0xffff) {
457 printf("CustomParticle: Too many particles... \n");
463 printf("CustomParticle: Too few particles... \n");
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;
476 m_material = new QSGShaderEffectMaterialObject;
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) {
505 quint16 *indices = g->indexDataAsUShort();
506 for (int i=0; i < count; ++i) {
517 QSGShaderEffectNode* node = new QSGShaderEffectNode();
519 node->setGeometry(g);
520 node->setMaterial(m_material);
521 node->markDirty(QSGNode::DirtyMaterial);
523 m_nodes.insert(gIdx, node);
525 foreach (QSGShaderEffectNode* node, m_nodes){
526 if (node == *(m_nodes.begin()))
528 (*(m_nodes.begin()))->appendChildNode(node);
531 return *(m_nodes.begin());
535 void QSGCustomParticle::buildData()
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;
546 foreach (QSGShaderEffectNode* node, m_nodes)
547 disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
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));
554 foreach (QSGShaderEffectNode* node, m_nodes)
555 connect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()), Qt::DirectConnection);
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)));
561 values.append(qMakePair(timestampName, QVariant(m_lastTime)));
562 m_material->setUniforms(values);
563 m_material->setTextureProviders(textures);
565 foreach (QSGShaderEffectNode* node, m_nodes)
566 node->markDirty(QSGNode::DirtyMaterial);
569 void QSGCustomParticle::initialize(int gIdx, int pIdx)
571 QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx];
572 datum->r = rand()/(qreal)RAND_MAX;
575 void QSGCustomParticle::commit(int gIdx, int pIdx)
577 if (m_nodes[gIdx] == 0)
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;