1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickcustomparticle_p.h"
41#include <QtCore/qrandom.h>
42#include <QtGui/qopenglcontext.h>
43#include <QtQuick/private/qquickshadereffectmesh_p.h>
44#include <QtQuick/private/qsgshadersourcebuilder_p.h>
45#include <QtQml/qqmlinfo.h>
46
47QT_BEGIN_NAMESPACE
48
49static QSGGeometry::Attribute PlainParticle_Attributes[] = {
50 QSGGeometry::Attribute::create(pos: 0, tupleSize: 2, GL_FLOAT, isPosition: true), // Position
51 QSGGeometry::Attribute::create(pos: 1, tupleSize: 2, GL_FLOAT), // TexCoord
52 QSGGeometry::Attribute::create(pos: 2, tupleSize: 4, GL_FLOAT), // Data
53 QSGGeometry::Attribute::create(pos: 3, tupleSize: 4, GL_FLOAT), // Vectors
54 QSGGeometry::Attribute::create(pos: 4, tupleSize: 1, GL_FLOAT) // r
55};
56
57static QSGGeometry::AttributeSet PlainParticle_AttributeSet =
58{
59 .count: 5, // Attribute Count
60 .stride: (2 + 2 + 4 + 4 + 1) * sizeof(float),
61 .attributes: PlainParticle_Attributes
62};
63
64struct PlainVertex {
65 float x;
66 float y;
67 float tx;
68 float ty;
69 float t;
70 float lifeSpan;
71 float size;
72 float endSize;
73 float vx;
74 float vy;
75 float ax;
76 float ay;
77 float r;
78};
79
80struct PlainVertices {
81 PlainVertex v1;
82 PlainVertex v2;
83 PlainVertex v3;
84 PlainVertex v4;
85};
86
87/*!
88 \qmltype CustomParticle
89 \instantiates QQuickCustomParticle
90 \inqmlmodule QtQuick.Particles
91 \inherits ParticlePainter
92 \brief For specifying shaders to paint particles.
93 \ingroup qtquick-particles
94
95 \note The maximum number of custom particles is limited to 16383.
96*/
97
98QQuickCustomParticle::QQuickCustomParticle(QQuickItem* parent)
99 : QQuickParticlePainter(parent)
100 , m_common(this, [this](int mappedId){this->propertyChanged(mappedId);})
101 , m_myMetaObject(nullptr)
102 , m_dirtyUniforms(true)
103 , m_dirtyUniformValues(true)
104 , m_dirtyTextureProviders(true)
105 , m_dirtyProgram(true)
106{
107 setFlag(flag: QQuickItem::ItemHasContents);
108}
109
110void QQuickCustomParticle::sceneGraphInvalidated()
111{
112 m_nodes.clear();
113}
114
115QQuickCustomParticle::~QQuickCustomParticle()
116{
117}
118
119void QQuickCustomParticle::componentComplete()
120{
121 if (!m_myMetaObject)
122 m_myMetaObject = metaObject();
123
124 m_common.updateShader(item: this, itemMetaObject: m_myMetaObject, shaderType: Key::FragmentShader);
125 updateVertexShader();
126 reset();
127 QQuickParticlePainter::componentComplete();
128}
129
130
131//Trying to keep the shader conventions the same as in qsgshadereffectitem
132/*!
133 \qmlproperty string QtQuick.Particles::CustomParticle::fragmentShader
134
135 This property holds the fragment shader's GLSL source code.
136 The default shader expects the texture coordinate to be passed from the
137 vertex shader as "varying highp vec2 qt_TexCoord0", and it samples from a
138 sampler2D named "source".
139*/
140
141void QQuickCustomParticle::setFragmentShader(const QByteArray &code)
142{
143 if (m_common.source.sourceCode[Key::FragmentShader].constData() == code.constData())
144 return;
145 m_common.source.sourceCode[Key::FragmentShader] = code;
146 m_dirtyProgram = true;
147 if (isComponentComplete()) {
148 m_common.updateShader(item: this, itemMetaObject: m_myMetaObject, shaderType: Key::FragmentShader);
149 reset();
150 }
151 emit fragmentShaderChanged();
152}
153
154/*!
155 \qmlproperty string QtQuick.Particles::CustomParticle::vertexShader
156
157 This property holds the vertex shader's GLSL source code.
158
159 The default shader passes the texture coordinate along to the fragment
160 shader as "varying highp vec2 qt_TexCoord0".
161
162 To aid writing a particle vertex shader, the following GLSL code is prepended
163 to your vertex shader:
164 \code
165 attribute highp vec2 qt_ParticlePos;
166 attribute highp vec2 qt_ParticleTex;
167 attribute highp vec4 qt_ParticleData; // x = time, y = lifeSpan, z = size, w = endSize
168 attribute highp vec4 qt_ParticleVec; // x,y = constant velocity, z,w = acceleration
169 attribute highp float qt_ParticleR;
170 uniform highp mat4 qt_Matrix;
171 uniform highp float qt_Timestamp;
172 varying highp vec2 qt_TexCoord0;
173 void defaultMain() {
174 qt_TexCoord0 = qt_ParticleTex;
175 highp float size = qt_ParticleData.z;
176 highp float endSize = qt_ParticleData.w;
177 highp float t = (qt_Timestamp - qt_ParticleData.x) / qt_ParticleData.y;
178 highp float currentSize = mix(size, endSize, t * t);
179 if (t < 0. || t > 1.)
180 currentSize = 0.;
181 highp vec2 pos = qt_ParticlePos
182 - currentSize / 2. + currentSize * qt_ParticleTex // adjust size
183 + qt_ParticleVec.xy * t * qt_ParticleData.y // apply velocity vector..
184 + 0.5 * qt_ParticleVec.zw * pow(t * qt_ParticleData.y, 2.);
185 gl_Position = qt_Matrix * vec4(pos.x, pos.y, 0, 1);
186 }
187 \endcode
188
189 defaultMain() is the same code as in the default shader, you can call this for basic
190 particle functions and then add additional variables for custom effects. Note that
191 the vertex shader for particles is responsible for simulating the movement of particles
192 over time, the particle data itself only has the starting position and spawn time.
193*/
194
195void QQuickCustomParticle::setVertexShader(const QByteArray &code)
196{
197 if (m_common.source.sourceCode[Key::VertexShader].constData() == code.constData())
198 return;
199 m_common.source.sourceCode[Key::VertexShader] = code;
200
201 m_dirtyProgram = true;
202 if (isComponentComplete()) {
203 updateVertexShader();
204 reset();
205 }
206 emit vertexShaderChanged();
207}
208
209void QQuickCustomParticle::updateVertexShader()
210{
211 m_common.disconnectPropertySignals(item: this, shaderType: Key::VertexShader);
212 m_common.uniformData[Key::VertexShader].clear();
213 m_common.clearSignalMappers(shader: Key::VertexShader);
214 m_common.attributes.clear();
215 m_common.attributes.append(t: "qt_ParticlePos");
216 m_common.attributes.append(t: "qt_ParticleTex");
217 m_common.attributes.append(t: "qt_ParticleData");
218 m_common.attributes.append(t: "qt_ParticleVec");
219 m_common.attributes.append(t: "qt_ParticleR");
220
221 UniformData d;
222 d.name = "qt_Matrix";
223 d.specialType = UniformData::Matrix;
224 m_common.uniformData[Key::VertexShader].append(t: d);
225 m_common.signalMappers[Key::VertexShader].append(t: 0);
226
227 d.name = "qt_Timestamp";
228 d.specialType = UniformData::None;
229 m_common.uniformData[Key::VertexShader].append(t: d);
230 m_common.signalMappers[Key::VertexShader].append(t: 0);
231
232 const QByteArray &code = m_common.source.sourceCode[Key::VertexShader];
233 if (!code.isEmpty())
234 m_common.lookThroughShaderCode(item: this, itemMetaObject: m_myMetaObject, shaderType: Key::VertexShader, code);
235
236 m_common.connectPropertySignals(item: this, itemMetaObject: m_myMetaObject, shaderType: Key::VertexShader);
237}
238
239void QQuickCustomParticle::reset()
240{
241 QQuickParticlePainter::reset();
242 update();
243}
244
245QSGNode *QQuickCustomParticle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
246{
247 QQuickOpenGLShaderEffectNode *rootNode = static_cast<QQuickOpenGLShaderEffectNode *>(oldNode);
248 if (m_pleaseReset){
249 delete rootNode;//Automatically deletes children
250 rootNode = nullptr;
251 m_nodes.clear();
252 m_pleaseReset = false;
253 m_dirtyProgram = true;
254 }
255
256 if (m_system && m_system->isRunning() && !m_system->isPaused()){
257 rootNode = prepareNextFrame(rootNode);
258 if (rootNode) {
259 foreach (QSGGeometryNode* node, m_nodes)
260 node->markDirty(bits: QSGNode::DirtyGeometry);
261 update();
262 }
263 }
264
265 return rootNode;
266}
267
268QQuickOpenGLShaderEffectNode *QQuickCustomParticle::prepareNextFrame(QQuickOpenGLShaderEffectNode *rootNode)
269{
270 if (!rootNode)
271 rootNode = buildCustomNodes();
272
273 if (!rootNode)
274 return nullptr;
275
276 if (m_dirtyProgram) {
277 const bool isES = QOpenGLContext::currentContext()->isOpenGLES();
278
279 QQuickOpenGLShaderEffectMaterial *material = static_cast<QQuickOpenGLShaderEffectMaterial *>(rootNode->material());
280 Q_ASSERT(material);
281
282 Key s = m_common.source;
283 QSGShaderSourceBuilder builder;
284 if (s.sourceCode[Key::FragmentShader].isEmpty()) {
285 builder.appendSourceFile(QStringLiteral(":/particles/shaders/customparticle.frag"));
286 if (isES)
287 builder.removeVersion();
288 s.sourceCode[Key::FragmentShader] = builder.source();
289 builder.clear();
290 }
291
292 builder.appendSourceFile(QStringLiteral(":/particles/shaders/customparticletemplate.vert"));
293 if (isES)
294 builder.removeVersion();
295
296 if (s.sourceCode[Key::VertexShader].isEmpty())
297 builder.appendSourceFile(QStringLiteral(":/particles/shaders/customparticle.vert"));
298 s.sourceCode[Key::VertexShader] = builder.source() + s.sourceCode[Key::VertexShader];
299
300 material->setProgramSource(s);
301 material->attributes = m_common.attributes;
302 foreach (QQuickOpenGLShaderEffectNode* node, m_nodes)
303 node->markDirty(bits: QSGNode::DirtyMaterial);
304
305 m_dirtyProgram = false;
306 m_dirtyUniforms = true;
307 }
308
309 m_lastTime = m_system->systemSync(p: this) / 1000.;
310 if (true) //Currently this is how we update timestamp... potentially over expensive.
311 buildData(rootNode);
312 return rootNode;
313}
314
315QQuickOpenGLShaderEffectNode* QQuickCustomParticle::buildCustomNodes()
316{
317 typedef QHash<int, QQuickOpenGLShaderEffectNode*>::const_iterator NodeHashConstIt;
318
319 if (!QOpenGLContext::currentContext())
320 return nullptr;
321
322 if (m_count * 4 > 0xffff) {
323 // Index data is ushort.
324 qmlInfo(me: this) << "CustomParticle: Too many particles - maximum 16383 per CustomParticle";
325 return nullptr;
326 }
327
328 if (m_count <= 0) {
329 qmlInfo(me: this) << "CustomParticle: Too few particles";
330 return nullptr;
331 }
332
333 if (groups().isEmpty())
334 return nullptr;
335
336 QQuickOpenGLShaderEffectNode *rootNode = nullptr;
337 QQuickOpenGLShaderEffectMaterial *material = new QQuickOpenGLShaderEffectMaterial;
338 m_dirtyProgram = true;
339
340 for (auto groupId : groupIds()) {
341 int count = m_system->groupData[groupId]->size();
342
343 QQuickOpenGLShaderEffectNode* node = new QQuickOpenGLShaderEffectNode();
344 m_nodes.insert(key: groupId, value: node);
345
346 node->setMaterial(material);
347
348 //Create Particle Geometry
349 int vCount = count * 4;
350 int iCount = count * 6;
351 QSGGeometry *g = new QSGGeometry(PlainParticle_AttributeSet, vCount, iCount);
352 g->setDrawingMode(GL_TRIANGLES);
353 node->setGeometry(g);
354 node->setFlag(QSGNode::OwnsGeometry, true);
355 PlainVertex *vertices = (PlainVertex *) g->vertexData();
356 for (int p=0; p < count; ++p) {
357 commit(gIdx: groupId, pIdx: p);
358 vertices[0].tx = 0;
359 vertices[0].ty = 0;
360
361 vertices[1].tx = 1;
362 vertices[1].ty = 0;
363
364 vertices[2].tx = 0;
365 vertices[2].ty = 1;
366
367 vertices[3].tx = 1;
368 vertices[3].ty = 1;
369 vertices += 4;
370 }
371 quint16 *indices = g->indexDataAsUShort();
372 for (int i=0; i < count; ++i) {
373 int o = i * 4;
374 indices[0] = o;
375 indices[1] = o + 1;
376 indices[2] = o + 2;
377 indices[3] = o + 1;
378 indices[4] = o + 3;
379 indices[5] = o + 2;
380 indices += 6;
381 }
382 }
383
384 NodeHashConstIt it = m_nodes.cbegin();
385 rootNode = it.value();
386 rootNode->setFlag(QSGNode::OwnsMaterial, true);
387 NodeHashConstIt cend = m_nodes.cend();
388 for (++it; it != cend; ++it)
389 rootNode->appendChildNode(node: it.value());
390
391 return rootNode;
392}
393
394void QQuickCustomParticle::sourceDestroyed(QObject *object)
395{
396 m_common.sourceDestroyed(object);
397}
398
399void QQuickCustomParticle::propertyChanged(int mappedId)
400{
401 bool textureProviderChanged;
402 m_common.propertyChanged(item: this, itemMetaObject: m_myMetaObject, mappedId, textureProviderChanged: &textureProviderChanged);
403 m_dirtyTextureProviders |= textureProviderChanged;
404 m_dirtyUniformValues = true;
405 update();
406}
407
408
409void QQuickCustomParticle::buildData(QQuickOpenGLShaderEffectNode *rootNode)
410{
411 if (!rootNode)
412 return;
413 for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
414 for (int i = 0; i < m_common.uniformData[shaderType].size(); ++i) {
415 if (m_common.uniformData[shaderType].at(i).name == "qt_Timestamp")
416 m_common.uniformData[shaderType][i].value = QVariant::fromValue(value: m_lastTime);
417 }
418 }
419 m_common.updateMaterial(node: rootNode, material: static_cast<QQuickOpenGLShaderEffectMaterial *>(rootNode->material()),
420 updateUniforms: m_dirtyUniforms, updateUniformValues: true, updateTextureProviders: m_dirtyTextureProviders);
421 foreach (QQuickOpenGLShaderEffectNode* node, m_nodes)
422 node->markDirty(bits: QSGNode::DirtyMaterial);
423 m_dirtyUniforms = m_dirtyUniformValues = m_dirtyTextureProviders = false;
424}
425
426void QQuickCustomParticle::initialize(int gIdx, int pIdx)
427{
428 QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx];
429 datum->r = QRandomGenerator::global()->generateDouble();
430}
431
432void QQuickCustomParticle::commit(int gIdx, int pIdx)
433{
434 if (m_nodes[gIdx] == 0)
435 return;
436
437 QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx];
438 PlainVertices *particles = (PlainVertices *) m_nodes[gIdx]->geometry()->vertexData();
439 PlainVertex *vertices = (PlainVertex *)&particles[pIdx];
440 for (int i=0; i<4; ++i) {
441 vertices[i].x = datum->x - m_systemOffset.x();
442 vertices[i].y = datum->y - m_systemOffset.y();
443 vertices[i].t = datum->t;
444 vertices[i].lifeSpan = datum->lifeSpan;
445 vertices[i].size = datum->size;
446 vertices[i].endSize = datum->endSize;
447 vertices[i].vx = datum->vx;
448 vertices[i].vy = datum->vy;
449 vertices[i].ax = datum->ax;
450 vertices[i].ay = datum->ay;
451 vertices[i].r = datum->r;
452 }
453}
454
455void QQuickCustomParticle::itemChange(ItemChange change, const ItemChangeData &value)
456{
457 if (change == QQuickItem::ItemSceneChange)
458 m_common.updateWindow(window: value.window);
459 QQuickParticlePainter::itemChange(change, value);
460}
461
462
463QT_END_NAMESPACE
464
465#include "moc_qquickcustomparticle_p.cpp"
466

source code of qtdeclarative/src/particles/qquickcustomparticle.cpp