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 | |
47 | QT_BEGIN_NAMESPACE |
48 | |
49 | static 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 | |
57 | static 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 | |
64 | struct 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 | |
80 | struct 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 | |
98 | QQuickCustomParticle::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 | |
110 | void QQuickCustomParticle::sceneGraphInvalidated() |
111 | { |
112 | m_nodes.clear(); |
113 | } |
114 | |
115 | QQuickCustomParticle::~QQuickCustomParticle() |
116 | { |
117 | } |
118 | |
119 | void 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 | |
141 | void 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 | |
195 | void 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 | |
209 | void 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 | |
239 | void QQuickCustomParticle::reset() |
240 | { |
241 | QQuickParticlePainter::reset(); |
242 | update(); |
243 | } |
244 | |
245 | QSGNode *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 | |
268 | QQuickOpenGLShaderEffectNode *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 | |
315 | QQuickOpenGLShaderEffectNode* 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 | |
394 | void QQuickCustomParticle::sourceDestroyed(QObject *object) |
395 | { |
396 | m_common.sourceDestroyed(object); |
397 | } |
398 | |
399 | void 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 | |
409 | void 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 | |
426 | void QQuickCustomParticle::initialize(int gIdx, int pIdx) |
427 | { |
428 | QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx]; |
429 | datum->r = QRandomGenerator::global()->generateDouble(); |
430 | } |
431 | |
432 | void 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 | |
455 | void 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 | |
463 | QT_END_NAMESPACE |
464 | |
465 | #include "moc_qquickcustomparticle_p.cpp" |
466 | |