1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick 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 <QtQuick/private/qsgcontext_p.h>
41#include <private/qsgadaptationlayer_p.h>
42#include <private/qquickitem_p.h>
43#include <QtQuick/qsgnode.h>
44#include <QtQuick/qsgtexture.h>
45#include <QFile>
46#include <QRandomGenerator>
47#include "qquickimageparticle_p.h"
48#include "qquickparticleemitter_p.h"
49#include <private/qquicksprite_p.h>
50#include <private/qquickspriteengine_p.h>
51#include <QOpenGLFunctions>
52#include <QSGRendererInterface>
53#include <QtQuick/private/qsgshadersourcebuilder_p.h>
54#include <QtQuick/private/qsgplaintexture_p.h>
55#include <private/qqmlglobal_p.h>
56#include <QtQml/qqmlinfo.h>
57#include <cmath>
58#include <QtGui/private/qrhi_p.h>
59
60QT_BEGIN_NAMESPACE
61
62// Must match the shader code
63#define UNIFORM_ARRAY_SIZE 64
64
65const qreal CONV = 0.017453292519943295;
66
67class ImageMaterialData
68{
69 public:
70 ImageMaterialData()
71 : texture(nullptr), colorTable(nullptr)
72 {}
73
74 ~ImageMaterialData(){
75 delete texture;
76 delete colorTable;
77 }
78
79 QSGTexture *texture;
80 QSGTexture *colorTable;
81 float sizeTable[UNIFORM_ARRAY_SIZE];
82 float opacityTable[UNIFORM_ARRAY_SIZE];
83
84 qreal timestamp;
85 qreal entry;
86 QSizeF animSheetSize;
87};
88
89class TabledMaterialShader : public QSGMaterialShader
90{
91public:
92 TabledMaterialShader()
93 {
94 QSGShaderSourceBuilder builder;
95 const bool isES = QOpenGLContext::currentContext()->isOpenGLES();
96
97 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert"));
98 builder.addDefinition(QByteArrayLiteral("TABLE"));
99 builder.addDefinition(QByteArrayLiteral("DEFORM"));
100 builder.addDefinition(QByteArrayLiteral("COLOR"));
101 if (isES)
102 builder.removeVersion();
103
104 m_vertex_code = builder.source();
105 builder.clear();
106
107 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag"));
108 builder.addDefinition(QByteArrayLiteral("TABLE"));
109 builder.addDefinition(QByteArrayLiteral("DEFORM"));
110 builder.addDefinition(QByteArrayLiteral("COLOR"));
111 if (isES)
112 builder.removeVersion();
113
114 m_fragment_code = builder.source();
115
116 Q_ASSERT(!m_vertex_code.isNull());
117 Q_ASSERT(!m_fragment_code.isNull());
118 }
119
120 const char *vertexShader() const override { return m_vertex_code.constData(); }
121 const char *fragmentShader() const override { return m_fragment_code.constData(); }
122
123 char const *const *attributeNames() const override
124 {
125 static const char *const attr[] = { "vPosTex", "vData", "vVec", "vColor", "vDeformVec", "vRotation", nullptr };
126 return attr;
127 }
128
129 void initialize() override {
130 program()->bind();
131 program()->setUniformValue(name: "_qt_texture", value: 0);
132 program()->setUniformValue(name: "colortable", value: 1);
133 glFuncs = QOpenGLContext::currentContext()->functions();
134 m_matrix_id = program()->uniformLocation(name: "qt_Matrix");
135 m_opacity_id = program()->uniformLocation(name: "qt_Opacity");
136 m_timestamp_id = program()->uniformLocation(name: "timestamp");
137 m_entry_id = program()->uniformLocation(name: "entry");
138 m_sizetable_id = program()->uniformLocation(name: "sizetable");
139 m_opacitytable_id = program()->uniformLocation(name: "opacitytable");
140 }
141
142 void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override {
143 ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state();
144
145 if (renderState.isMatrixDirty())
146 program()->setUniformValue(location: m_matrix_id, value: renderState.combinedMatrix());
147 if (renderState.isOpacityDirty() && m_opacity_id >= 0)
148 program()->setUniformValue(location: m_opacity_id, value: renderState.opacity());
149
150 glFuncs->glActiveTexture(GL_TEXTURE1);
151 state->colorTable->bind();
152
153 glFuncs->glActiveTexture(GL_TEXTURE0);
154 state->texture->bind();
155
156 program()->setUniformValue(location: m_timestamp_id, value: (float) state->timestamp);
157 program()->setUniformValue(location: m_entry_id, value: (float) state->entry);
158 program()->setUniformValueArray(location: m_sizetable_id, values: (const float*) state->sizeTable, UNIFORM_ARRAY_SIZE, tupleSize: 1);
159 program()->setUniformValueArray(location: m_opacitytable_id, values: (const float*) state->opacityTable, UNIFORM_ARRAY_SIZE, tupleSize: 1);
160 }
161
162 int m_matrix_id;
163 int m_opacity_id;
164 int m_entry_id;
165 int m_timestamp_id;
166 int m_sizetable_id;
167 int m_opacitytable_id;
168 QByteArray m_vertex_code;
169 QByteArray m_fragment_code;
170 QOpenGLFunctions* glFuncs;
171};
172
173class TabledMaterialRhiShader : public QSGMaterialRhiShader
174{
175public:
176 TabledMaterialRhiShader()
177 {
178 setShaderFileName(stage: VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_tabled.vert.qsb"));
179 setShaderFileName(stage: FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_tabled.frag.qsb"));
180 }
181
182 bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
183 {
184 QByteArray *buf = renderState.uniformData();
185 Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
186
187 if (renderState.isMatrixDirty()) {
188 const QMatrix4x4 m = renderState.combinedMatrix();
189 memcpy(dest: buf->data(), src: m.constData(), n: 64);
190 }
191
192 if (renderState.isOpacityDirty()) {
193 const float opacity = renderState.opacity();
194 memcpy(dest: buf->data() + 64, src: &opacity, n: 4);
195 }
196
197 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
198
199 float entry = float(state->entry);
200 memcpy(dest: buf->data() + 68, src: &entry, n: 4);
201
202 float timestamp = float(state->timestamp);
203 memcpy(dest: buf->data() + 72, src: &timestamp, n: 4);
204
205 float *p = reinterpret_cast<float *>(buf->data() + 80);
206 for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) {
207 *p = state->sizeTable[i];
208 p += 4;
209 }
210 p = reinterpret_cast<float *>(buf->data() + 80 + (UNIFORM_ARRAY_SIZE * 4 * 4));
211 for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) {
212 *p = state->opacityTable[i];
213 p += 4;
214 }
215
216 return true;
217 }
218
219 void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
220 QSGMaterial *newMaterial, QSGMaterial *) override
221 {
222 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
223 if (binding == 2) {
224 state->colorTable->updateRhiTexture(rhi: renderState.rhi(), resourceUpdates: renderState.resourceUpdateBatch());
225 *texture = state->colorTable;
226 } else if (binding == 1) {
227 state->texture->updateRhiTexture(rhi: renderState.rhi(), resourceUpdates: renderState.resourceUpdateBatch());
228 *texture = state->texture;
229 }
230 }
231};
232
233class TabledMaterial : public ImageMaterial
234{
235public:
236 TabledMaterial() { setFlag(flags: SupportsRhiShader, on: true); }
237 QSGMaterialShader *createShader() const override {
238 if (flags().testFlag(flag: RhiShaderWanted))
239 return new TabledMaterialRhiShader;
240 else
241 return new TabledMaterialShader;
242 }
243 QSGMaterialType *type() const override { return &m_type; }
244
245 ImageMaterialData *state() override { return &m_state; }
246
247private:
248 static QSGMaterialType m_type;
249 ImageMaterialData m_state;
250};
251
252QSGMaterialType TabledMaterial::m_type;
253
254class DeformableMaterialShader : public QSGMaterialShader
255{
256public:
257 DeformableMaterialShader()
258 {
259 QSGShaderSourceBuilder builder;
260 const bool isES = QOpenGLContext::currentContext()->isOpenGLES();
261
262 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert"));
263 builder.addDefinition(QByteArrayLiteral("DEFORM"));
264 builder.addDefinition(QByteArrayLiteral("COLOR"));
265 if (isES)
266 builder.removeVersion();
267
268 m_vertex_code = builder.source();
269 builder.clear();
270
271 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag"));
272 builder.addDefinition(QByteArrayLiteral("DEFORM"));
273 builder.addDefinition(QByteArrayLiteral("COLOR"));
274 if (isES)
275 builder.removeVersion();
276
277 m_fragment_code = builder.source();
278
279 Q_ASSERT(!m_vertex_code.isNull());
280 Q_ASSERT(!m_fragment_code.isNull());
281 }
282
283 const char *vertexShader() const override { return m_vertex_code.constData(); }
284 const char *fragmentShader() const override { return m_fragment_code.constData(); }
285
286 char const *const *attributeNames() const override
287 {
288 static const char *const attr[] = { "vPosTex", "vData", "vVec", "vColor", "vDeformVec", "vRotation", nullptr };
289 return attr;
290 }
291
292 void initialize() override {
293 program()->bind();
294 program()->setUniformValue(name: "_qt_texture", value: 0);
295 glFuncs = QOpenGLContext::currentContext()->functions();
296 m_matrix_id = program()->uniformLocation(name: "qt_Matrix");
297 m_opacity_id = program()->uniformLocation(name: "qt_Opacity");
298 m_timestamp_id = program()->uniformLocation(name: "timestamp");
299 m_entry_id = program()->uniformLocation(name: "entry");
300 }
301
302 void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override {
303 ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state();
304
305 if (renderState.isMatrixDirty())
306 program()->setUniformValue(location: m_matrix_id, value: renderState.combinedMatrix());
307 if (renderState.isOpacityDirty() && m_opacity_id >= 0)
308 program()->setUniformValue(location: m_opacity_id, value: renderState.opacity());
309
310 state->texture->bind();
311
312 program()->setUniformValue(location: m_timestamp_id, value: (float) state->timestamp);
313 program()->setUniformValue(location: m_entry_id, value: (float) state->entry);
314 }
315
316 int m_matrix_id;
317 int m_opacity_id;
318 int m_entry_id;
319 int m_timestamp_id;
320 QByteArray m_vertex_code;
321 QByteArray m_fragment_code;
322 QOpenGLFunctions* glFuncs;
323};
324
325class DeformableMaterialRhiShader : public QSGMaterialRhiShader
326{
327public:
328 DeformableMaterialRhiShader()
329 {
330 setShaderFileName(stage: VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_deformed.vert.qsb"));
331 setShaderFileName(stage: FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_deformed.frag.qsb"));
332 }
333
334 bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
335 {
336 QByteArray *buf = renderState.uniformData();
337 Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
338
339 if (renderState.isMatrixDirty()) {
340 const QMatrix4x4 m = renderState.combinedMatrix();
341 memcpy(dest: buf->data(), src: m.constData(), n: 64);
342 }
343
344 if (renderState.isOpacityDirty()) {
345 const float opacity = renderState.opacity();
346 memcpy(dest: buf->data() + 64, src: &opacity, n: 4);
347 }
348
349 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
350
351 float entry = float(state->entry);
352 memcpy(dest: buf->data() + 68, src: &entry, n: 4);
353
354 float timestamp = float(state->timestamp);
355 memcpy(dest: buf->data() + 72, src: &timestamp, n: 4);
356
357 return true;
358 }
359
360 void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
361 QSGMaterial *newMaterial, QSGMaterial *) override
362 {
363 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
364 if (binding == 1) {
365 state->texture->updateRhiTexture(rhi: renderState.rhi(), resourceUpdates: renderState.resourceUpdateBatch());
366 *texture = state->texture;
367 }
368 }
369};
370
371class DeformableMaterial : public ImageMaterial
372{
373public:
374 DeformableMaterial() { setFlag(flags: SupportsRhiShader, on: true); }
375 QSGMaterialShader *createShader() const override {
376 if (flags().testFlag(flag: RhiShaderWanted))
377 return new DeformableMaterialRhiShader;
378 else
379 return new DeformableMaterialShader;
380 }
381 QSGMaterialType *type() const override { return &m_type; }
382
383 ImageMaterialData *state() override { return &m_state; }
384
385private:
386 static QSGMaterialType m_type;
387 ImageMaterialData m_state;
388};
389
390QSGMaterialType DeformableMaterial::m_type;
391
392class ParticleSpriteMaterialShader : public QSGMaterialShader
393{
394public:
395 ParticleSpriteMaterialShader()
396 {
397 QSGShaderSourceBuilder builder;
398 const bool isES = QOpenGLContext::currentContext()->isOpenGLES();
399
400 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert"));
401 builder.addDefinition(QByteArrayLiteral("SPRITE"));
402 builder.addDefinition(QByteArrayLiteral("TABLE"));
403 builder.addDefinition(QByteArrayLiteral("DEFORM"));
404 builder.addDefinition(QByteArrayLiteral("COLOR"));
405 if (isES)
406 builder.removeVersion();
407
408 m_vertex_code = builder.source();
409 builder.clear();
410
411 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag"));
412 builder.addDefinition(QByteArrayLiteral("SPRITE"));
413 builder.addDefinition(QByteArrayLiteral("TABLE"));
414 builder.addDefinition(QByteArrayLiteral("DEFORM"));
415 builder.addDefinition(QByteArrayLiteral("COLOR"));
416 if (isES)
417 builder.removeVersion();
418
419 m_fragment_code = builder.source();
420
421 Q_ASSERT(!m_vertex_code.isNull());
422 Q_ASSERT(!m_fragment_code.isNull());
423 }
424
425 const char *vertexShader() const override { return m_vertex_code.constData(); }
426 const char *fragmentShader() const override { return m_fragment_code.constData(); }
427
428 char const *const *attributeNames() const override
429 {
430 static const char *const attr[] = { "vPosTex", "vData", "vVec", "vColor", "vDeformVec", "vRotation",
431 "vAnimData", "vAnimPos", nullptr };
432 return attr;
433 }
434
435 void initialize() override {
436 program()->bind();
437 program()->setUniformValue(name: "_qt_texture", value: 0);
438 program()->setUniformValue(name: "colortable", value: 1);
439 glFuncs = QOpenGLContext::currentContext()->functions();
440 m_matrix_id = program()->uniformLocation(name: "qt_Matrix");
441 m_opacity_id = program()->uniformLocation(name: "qt_Opacity");
442 //Don't actually expose the animSheetSize in the shader, it's currently only used for CPU calculations.
443 m_timestamp_id = program()->uniformLocation(name: "timestamp");
444 m_entry_id = program()->uniformLocation(name: "entry");
445 m_sizetable_id = program()->uniformLocation(name: "sizetable");
446 m_opacitytable_id = program()->uniformLocation(name: "opacitytable");
447 }
448
449 void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override {
450 ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state();
451
452 if (renderState.isMatrixDirty())
453 program()->setUniformValue(location: m_matrix_id, value: renderState.combinedMatrix());
454 if (renderState.isOpacityDirty() && m_opacity_id >= 0)
455 program()->setUniformValue(location: m_opacity_id, value: renderState.opacity());
456
457 glFuncs->glActiveTexture(GL_TEXTURE1);
458 state->colorTable->bind();
459
460 // make sure we end by setting GL_TEXTURE0 as active texture
461 glFuncs->glActiveTexture(GL_TEXTURE0);
462 state->texture->bind();
463
464 program()->setUniformValue(location: m_timestamp_id, value: (float) state->timestamp);
465 program()->setUniformValue(location: m_entry_id, value: (float) state->entry);
466 program()->setUniformValueArray(location: m_sizetable_id, values: (const float*) state->sizeTable, count: 64, tupleSize: 1);
467 program()->setUniformValueArray(location: m_opacitytable_id, values: (const float*) state->opacityTable, UNIFORM_ARRAY_SIZE, tupleSize: 1);
468 }
469
470 int m_matrix_id;
471 int m_opacity_id;
472 int m_timestamp_id;
473 int m_entry_id;
474 int m_sizetable_id;
475 int m_opacitytable_id;
476 QByteArray m_vertex_code;
477 QByteArray m_fragment_code;
478 QOpenGLFunctions* glFuncs;
479};
480
481class ParticleSpriteMaterialRhiShader : public QSGMaterialRhiShader
482{
483public:
484 ParticleSpriteMaterialRhiShader()
485 {
486 setShaderFileName(stage: VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_sprite.vert.qsb"));
487 setShaderFileName(stage: FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_sprite.frag.qsb"));
488 }
489
490 bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
491 {
492 QByteArray *buf = renderState.uniformData();
493 Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
494
495 if (renderState.isMatrixDirty()) {
496 const QMatrix4x4 m = renderState.combinedMatrix();
497 memcpy(dest: buf->data(), src: m.constData(), n: 64);
498 }
499
500 if (renderState.isOpacityDirty()) {
501 const float opacity = renderState.opacity();
502 memcpy(dest: buf->data() + 64, src: &opacity, n: 4);
503 }
504
505 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
506
507 float entry = float(state->entry);
508 memcpy(dest: buf->data() + 68, src: &entry, n: 4);
509
510 float timestamp = float(state->timestamp);
511 memcpy(dest: buf->data() + 72, src: &timestamp, n: 4);
512
513 float *p = reinterpret_cast<float *>(buf->data() + 80);
514 for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) {
515 *p = state->sizeTable[i];
516 p += 4;
517 }
518 p = reinterpret_cast<float *>(buf->data() + 80 + (UNIFORM_ARRAY_SIZE * 4 * 4));
519 for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) {
520 *p = state->opacityTable[i];
521 p += 4;
522 }
523
524 return true;
525 }
526
527 void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
528 QSGMaterial *newMaterial, QSGMaterial *) override
529 {
530 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
531 if (binding == 2) {
532 state->colorTable->updateRhiTexture(rhi: renderState.rhi(), resourceUpdates: renderState.resourceUpdateBatch());
533 *texture = state->colorTable;
534 } else if (binding == 1) {
535 state->texture->updateRhiTexture(rhi: renderState.rhi(), resourceUpdates: renderState.resourceUpdateBatch());
536 *texture = state->texture;
537 }
538 }
539};
540
541class SpriteMaterial : public ImageMaterial
542{
543public:
544 SpriteMaterial() { setFlag(flags: SupportsRhiShader, on: true); }
545 QSGMaterialShader *createShader() const override {
546 if (flags().testFlag(flag: RhiShaderWanted))
547 return new ParticleSpriteMaterialRhiShader;
548 else
549 return new ParticleSpriteMaterialShader;
550 }
551 QSGMaterialType *type() const override { return &m_type; }
552
553 ImageMaterialData *state() override { return &m_state; }
554
555private:
556 static QSGMaterialType m_type;
557 ImageMaterialData m_state;
558};
559
560QSGMaterialType SpriteMaterial::m_type;
561
562class ColoredMaterialShader : public QSGMaterialShader
563{
564public:
565 ColoredMaterialShader()
566 {
567 QSGShaderSourceBuilder builder;
568 const bool isES = QOpenGLContext::currentContext()->isOpenGLES();
569
570 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert"));
571 builder.addDefinition(QByteArrayLiteral("COLOR"));
572 if (isES)
573 builder.removeVersion();
574
575 m_vertex_code = builder.source();
576 builder.clear();
577
578 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag"));
579 builder.addDefinition(QByteArrayLiteral("COLOR"));
580 if (isES)
581 builder.removeVersion();
582
583 m_fragment_code = builder.source();
584
585 Q_ASSERT(!m_vertex_code.isNull());
586 Q_ASSERT(!m_fragment_code.isNull());
587 }
588
589 const char *vertexShader() const override { return m_vertex_code.constData(); }
590 const char *fragmentShader() const override { return m_fragment_code.constData(); }
591
592 char const *const *attributeNames() const override
593 {
594 static const char *const attr[] = { "vPos", "vData", "vVec", "vColor", nullptr };
595 return attr;
596 }
597
598 void initialize() override {
599 program()->bind();
600 program()->setUniformValue(name: "_qt_texture", value: 0);
601 glFuncs = QOpenGLContext::currentContext()->functions();
602 m_matrix_id = program()->uniformLocation(name: "qt_Matrix");
603 m_opacity_id = program()->uniformLocation(name: "qt_Opacity");
604 m_timestamp_id = program()->uniformLocation(name: "timestamp");
605 m_entry_id = program()->uniformLocation(name: "entry");
606 }
607
608 void activate() override {
609#if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN)
610 glEnable(GL_POINT_SPRITE);
611 glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
612#endif
613 }
614
615 void deactivate() override {
616#if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN)
617 glDisable(GL_POINT_SPRITE);
618 glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
619#endif
620 }
621
622 void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override {
623 ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state();
624
625 if (renderState.isMatrixDirty())
626 program()->setUniformValue(location: m_matrix_id, value: renderState.combinedMatrix());
627 if (renderState.isOpacityDirty() && m_opacity_id >= 0)
628 program()->setUniformValue(location: m_opacity_id, value: renderState.opacity());
629
630 state->texture->bind();
631
632 program()->setUniformValue(location: m_timestamp_id, value: (float) state->timestamp);
633 program()->setUniformValue(location: m_entry_id, value: (float) state->entry);
634 }
635
636 int m_matrix_id;
637 int m_opacity_id;
638 int m_timestamp_id;
639 int m_entry_id;
640 QByteArray m_vertex_code;
641 QByteArray m_fragment_code;
642 QOpenGLFunctions* glFuncs;
643};
644
645class ColoredMaterialRhiShader : public QSGMaterialRhiShader
646{
647public:
648 ColoredMaterialRhiShader()
649 {
650 setShaderFileName(stage: VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_colored.vert.qsb"));
651 setShaderFileName(stage: FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_colored.frag.qsb"));
652 }
653
654 bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
655 {
656 QByteArray *buf = renderState.uniformData();
657 Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
658
659 if (renderState.isMatrixDirty()) {
660 const QMatrix4x4 m = renderState.combinedMatrix();
661 memcpy(dest: buf->data(), src: m.constData(), n: 64);
662 }
663
664 if (renderState.isOpacityDirty()) {
665 const float opacity = renderState.opacity();
666 memcpy(dest: buf->data() + 64, src: &opacity, n: 4);
667 }
668
669 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
670
671 float entry = float(state->entry);
672 memcpy(dest: buf->data() + 68, src: &entry, n: 4);
673
674 float timestamp = float(state->timestamp);
675 memcpy(dest: buf->data() + 72, src: &timestamp, n: 4);
676
677 return true;
678 }
679
680 void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
681 QSGMaterial *newMaterial, QSGMaterial *) override
682 {
683 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
684 if (binding == 1) {
685 state->texture->updateRhiTexture(rhi: renderState.rhi(), resourceUpdates: renderState.resourceUpdateBatch());
686 *texture = state->texture;
687 }
688 }
689};
690
691class ColoredMaterial : public ImageMaterial
692{
693public:
694 ColoredMaterial() { setFlag(flags: SupportsRhiShader, on: true); }
695 QSGMaterialShader *createShader() const override {
696 if (flags().testFlag(flag: RhiShaderWanted))
697 return new ColoredMaterialRhiShader;
698 else
699 return new ColoredMaterialShader;
700 }
701 QSGMaterialType *type() const override { return &m_type; }
702
703 ImageMaterialData *state() override { return &m_state; }
704
705private:
706 static QSGMaterialType m_type;
707 ImageMaterialData m_state;
708};
709
710QSGMaterialType ColoredMaterial::m_type;
711
712class SimpleMaterialShader : public QSGMaterialShader
713{
714public:
715 SimpleMaterialShader()
716 {
717 QSGShaderSourceBuilder builder;
718 const bool isES = QOpenGLContext::currentContext()->isOpenGLES();
719
720 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert"));
721 if (isES)
722 builder.removeVersion();
723
724 m_vertex_code = builder.source();
725 builder.clear();
726
727 builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag"));
728 if (isES)
729 builder.removeVersion();
730
731 m_fragment_code = builder.source();
732
733 Q_ASSERT(!m_vertex_code.isNull());
734 Q_ASSERT(!m_fragment_code.isNull());
735 }
736
737 const char *vertexShader() const override { return m_vertex_code.constData(); }
738 const char *fragmentShader() const override { return m_fragment_code.constData(); }
739
740 char const *const *attributeNames() const override
741 {
742 static const char *const attr[] = { "vPos", "vData", "vVec", nullptr };
743 return attr;
744 }
745
746 void initialize() override {
747 program()->bind();
748 program()->setUniformValue(name: "_qt_texture", value: 0);
749 glFuncs = QOpenGLContext::currentContext()->functions();
750 m_matrix_id = program()->uniformLocation(name: "qt_Matrix");
751 m_opacity_id = program()->uniformLocation(name: "qt_Opacity");
752 m_timestamp_id = program()->uniformLocation(name: "timestamp");
753 m_entry_id = program()->uniformLocation(name: "entry");
754 }
755
756 void activate() override {
757#if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN)
758 glEnable(GL_POINT_SPRITE);
759 glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
760#endif
761 }
762
763 void deactivate() override {
764#if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN)
765 glDisable(GL_POINT_SPRITE);
766 glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
767#endif
768 }
769
770 void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override {
771 ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state();
772
773 if (renderState.isMatrixDirty())
774 program()->setUniformValue(location: m_matrix_id, value: renderState.combinedMatrix());
775 if (renderState.isOpacityDirty() && m_opacity_id >= 0)
776 program()->setUniformValue(location: m_opacity_id, value: renderState.opacity());
777
778 state->texture->bind();
779
780 program()->setUniformValue(location: m_timestamp_id, value: (float) state->timestamp);
781 program()->setUniformValue(location: m_entry_id, value: (float) state->entry);
782 }
783
784 int m_matrix_id;
785 int m_opacity_id;
786 int m_timestamp_id;
787 int m_entry_id;
788 QByteArray m_vertex_code;
789 QByteArray m_fragment_code;
790 QOpenGLFunctions* glFuncs;
791};
792
793class SimpleMaterialRhiShader : public QSGMaterialRhiShader
794{
795public:
796 SimpleMaterialRhiShader()
797 {
798 setShaderFileName(stage: VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_simple.vert.qsb"));
799 setShaderFileName(stage: FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_simple.frag.qsb"));
800 }
801
802 bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
803 {
804 QByteArray *buf = renderState.uniformData();
805 Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
806
807 if (renderState.isMatrixDirty()) {
808 const QMatrix4x4 m = renderState.combinedMatrix();
809 memcpy(dest: buf->data(), src: m.constData(), n: 64);
810 }
811
812 if (renderState.isOpacityDirty()) {
813 const float opacity = renderState.opacity();
814 memcpy(dest: buf->data() + 64, src: &opacity, n: 4);
815 }
816
817 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
818
819 float entry = float(state->entry);
820 memcpy(dest: buf->data() + 68, src: &entry, n: 4);
821
822 float timestamp = float(state->timestamp);
823 memcpy(dest: buf->data() + 72, src: &timestamp, n: 4);
824
825 return true;
826 }
827
828 void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
829 QSGMaterial *newMaterial, QSGMaterial *) override
830 {
831 ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
832 if (binding == 1) {
833 state->texture->updateRhiTexture(rhi: renderState.rhi(), resourceUpdates: renderState.resourceUpdateBatch());
834 *texture = state->texture;
835 }
836 }
837};
838
839class SimpleMaterial : public ImageMaterial
840{
841public:
842 SimpleMaterial() { setFlag(flags: SupportsRhiShader, on: true); }
843 QSGMaterialShader *createShader() const override {
844 if (flags().testFlag(flag: RhiShaderWanted))
845 return new SimpleMaterialRhiShader;
846 else
847 return new SimpleMaterialShader;
848 }
849 QSGMaterialType *type() const override { return &m_type; }
850
851 ImageMaterialData *state() override { return &m_state; }
852
853private:
854 static QSGMaterialType m_type;
855 ImageMaterialData m_state;
856};
857
858QSGMaterialType SimpleMaterial::m_type;
859
860void fillUniformArrayFromImage(float* array, const QImage& img, int size)
861{
862 if (img.isNull()){
863 for (int i=0; i<size; i++)
864 array[i] = 1.0;
865 return;
866 }
867 QImage scaled = img.scaled(w: size,h: 1);
868 for (int i=0; i<size; i++)
869 array[i] = qAlpha(rgb: scaled.pixel(x: i,y: 0))/255.0;
870}
871
872/*!
873 \qmltype ImageParticle
874 \instantiates QQuickImageParticle
875 \inqmlmodule QtQuick.Particles
876 \inherits ParticlePainter
877 \brief For visualizing logical particles using an image.
878 \ingroup qtquick-particles
879
880 This element renders a logical particle as an image. The image can be
881 \list
882 \li colorized
883 \li rotated
884 \li deformed
885 \li a sprite-based animation
886 \endlist
887
888 ImageParticles implictly share data on particles if multiple ImageParticles are painting
889 the same logical particle group. This is broken down along the four capabilities listed
890 above. So if one ImageParticle defines data for rendering the particles in one of those
891 capabilities, and the other does not, then both will draw the particles the same in that
892 aspect automatically. This is primarily useful when there is some random variation on
893 the particle which is supposed to stay with it when switching painters. If both ImageParticles
894 define how they should appear for that aspect, they diverge and each appears as it is defined.
895
896 This sharing of data happens behind the scenes based off of whether properties were implicitly or explicitly
897 set. One drawback of the current implementation is that it is only possible to reset the capabilities as a whole.
898 So if you explicitly set an attribute affecting color, such as redVariation, and then reset it (by setting redVariation
899 to undefined), all color data will be reset and it will begin to have an implicit value of any shared color from
900 other ImageParticles.
901
902 \note The maximum number of image particles is limited to 16383.
903*/
904/*!
905 \qmlproperty url QtQuick.Particles::ImageParticle::source
906
907 The source image to be used.
908
909 If the image is a sprite animation, use the sprite property instead.
910
911 Since Qt 5.2, some default images are provided as resources to aid prototyping:
912 \table
913 \row
914 \li qrc:///particleresources/star.png
915 \li \inlineimage particles/star.png
916 \row
917 \li qrc:///particleresources/glowdot.png
918 \li \inlineimage particles/glowdot.png
919 \row
920 \li qrc:///particleresources/fuzzydot.png
921 \li \inlineimage particles/fuzzydot.png
922 \endtable
923
924 Note that the images are white and semi-transparent, to allow colorization
925 and alpha levels to have maximum effect.
926*/
927/*!
928 \qmlproperty list<Sprite> QtQuick.Particles::ImageParticle::sprites
929
930 The sprite or sprites used to draw this particle.
931
932 Note that the sprite image will be scaled to a square based on the size of
933 the particle being rendered.
934
935 For full details, see the \l{Sprite Animations} overview.
936*/
937/*!
938 \qmlproperty url QtQuick.Particles::ImageParticle::colorTable
939
940 An image whose color will be used as a 1D texture to determine color over life. E.g. when
941 the particle is halfway through its lifetime, it will have the color specified halfway
942 across the image.
943
944 This color is blended with the color property and the color of the source image.
945*/
946/*!
947 \qmlproperty url QtQuick.Particles::ImageParticle::sizeTable
948
949 An image whose opacity will be used as a 1D texture to determine size over life.
950
951 This property is expected to be removed shortly, in favor of custom easing curves to determine size over life.
952*/
953/*!
954 \qmlproperty url QtQuick.Particles::ImageParticle::opacityTable
955
956 An image whose opacity will be used as a 1D texture to determine size over life.
957
958 This property is expected to be removed shortly, in favor of custom easing curves to determine opacity over life.
959*/
960/*!
961 \qmlproperty color QtQuick.Particles::ImageParticle::color
962
963 If a color is specified, the provided image will be colorized with it.
964
965 Default is white (no change).
966*/
967/*!
968 \qmlproperty real QtQuick.Particles::ImageParticle::colorVariation
969
970 This number represents the color variation applied to individual particles.
971 Setting colorVariation is the same as setting redVariation, greenVariation,
972 and blueVariation to the same number.
973
974 Each channel can vary between particle by up to colorVariation from its usual color.
975
976 Color is measured, per channel, from 0.0 to 1.0.
977
978 Default is 0.0
979*/
980/*!
981 \qmlproperty real QtQuick.Particles::ImageParticle::redVariation
982 The variation in the red color channel between particles.
983
984 Color is measured, per channel, from 0.0 to 1.0.
985
986 Default is 0.0
987*/
988/*!
989 \qmlproperty real QtQuick.Particles::ImageParticle::greenVariation
990 The variation in the green color channel between particles.
991
992 Color is measured, per channel, from 0.0 to 1.0.
993
994 Default is 0.0
995*/
996/*!
997 \qmlproperty real QtQuick.Particles::ImageParticle::blueVariation
998 The variation in the blue color channel between particles.
999
1000 Color is measured, per channel, from 0.0 to 1.0.
1001
1002 Default is 0.0
1003*/
1004/*!
1005 \qmlproperty real QtQuick.Particles::ImageParticle::alpha
1006 An alpha to be applied to the image. This value is multiplied by the value in
1007 the image, and the value in the color property.
1008
1009 Particles have additive blending, so lower alpha on single particles leads
1010 to stronger effects when multiple particles overlap.
1011
1012 Alpha is measured from 0.0 to 1.0.
1013
1014 Default is 1.0
1015*/
1016/*!
1017 \qmlproperty real QtQuick.Particles::ImageParticle::alphaVariation
1018 The variation in the alpha channel between particles.
1019
1020 Alpha is measured from 0.0 to 1.0.
1021
1022 Default is 0.0
1023*/
1024/*!
1025 \qmlproperty real QtQuick.Particles::ImageParticle::rotation
1026
1027 If set the image will be rotated by this many degrees before it is drawn.
1028
1029 The particle coordinates are not transformed.
1030*/
1031/*!
1032 \qmlproperty real QtQuick.Particles::ImageParticle::rotationVariation
1033
1034 If set the rotation of individual particles will vary by up to this much
1035 between particles.
1036
1037*/
1038/*!
1039 \qmlproperty real QtQuick.Particles::ImageParticle::rotationVelocity
1040
1041 If set particles will rotate at this velocity in degrees/second.
1042*/
1043/*!
1044 \qmlproperty real QtQuick.Particles::ImageParticle::rotationVelocityVariation
1045
1046 If set the rotationVelocity of individual particles will vary by up to this much
1047 between particles.
1048
1049*/
1050/*!
1051 \qmlproperty bool QtQuick.Particles::ImageParticle::autoRotation
1052
1053 If set to true then a rotation will be applied on top of the particles rotation, so
1054 that it faces the direction of travel. So to face away from the direction of travel,
1055 set autoRotation to true and rotation to 180.
1056
1057 Default is false
1058*/
1059/*!
1060 \qmlproperty StochasticDirection QtQuick.Particles::ImageParticle::xVector
1061
1062 Allows you to deform the particle image when drawn. The rectangular image will
1063 be deformed so that the horizontal sides are in the shape of this vector instead
1064 of (1,0).
1065*/
1066/*!
1067 \qmlproperty StochasticDirection QtQuick.Particles::ImageParticle::yVector
1068
1069 Allows you to deform the particle image when drawn. The rectangular image will
1070 be deformed so that the vertical sides are in the shape of this vector instead
1071 of (0,1).
1072*/
1073/*!
1074 \qmlproperty EntryEffect QtQuick.Particles::ImageParticle::entryEffect
1075
1076 This property provides basic and cheap entrance and exit effects for the particles.
1077 For fine-grained control, see sizeTable and opacityTable.
1078
1079 Acceptable values are
1080 \list
1081 \li ImageParticle.None: Particles just appear and disappear.
1082 \li ImageParticle.Fade: Particles fade in from 0 opacity at the start of their life, and fade out to 0 at the end.
1083 \li ImageParticle.Scale: Particles scale in from 0 size at the start of their life, and scale back to 0 at the end.
1084 \endlist
1085
1086 Default value is Fade.
1087*/
1088/*!
1089 \qmlproperty bool QtQuick.Particles::ImageParticle::spritesInterpolate
1090
1091 If set to true, sprite particles will interpolate between sprite frames each rendered frame, making
1092 the sprites look smoother.
1093
1094 Default is true.
1095*/
1096
1097/*!
1098 \qmlproperty Status QtQuick.Particles::ImageParticle::status
1099
1100 The status of loading the image.
1101*/
1102
1103
1104QQuickImageParticle::QQuickImageParticle(QQuickItem* parent)
1105 : QQuickParticlePainter(parent)
1106 , m_color_variation(0.0)
1107 , m_outgoingNode(nullptr)
1108 , m_material(nullptr)
1109 , m_alphaVariation(0.0)
1110 , m_alpha(1.0)
1111 , m_redVariation(0.0)
1112 , m_greenVariation(0.0)
1113 , m_blueVariation(0.0)
1114 , m_rotation(0)
1115 , m_rotationVariation(0)
1116 , m_rotationVelocity(0)
1117 , m_rotationVelocityVariation(0)
1118 , m_autoRotation(false)
1119 , m_xVector(nullptr)
1120 , m_yVector(nullptr)
1121 , m_spriteEngine(nullptr)
1122 , m_spritesInterpolate(true)
1123 , m_explicitColor(false)
1124 , m_explicitRotation(false)
1125 , m_explicitDeformation(false)
1126 , m_explicitAnimation(false)
1127 , m_bypassOptimizations(false)
1128 , perfLevel(Unknown)
1129 , m_lastLevel(Unknown)
1130 , m_debugMode(false)
1131 , m_entryEffect(Fade)
1132 , m_startedImageLoading(0)
1133 , m_rhi(nullptr)
1134 , m_apiChecked(false)
1135 , m_previousActive(false)
1136{
1137 setFlag(flag: ItemHasContents);
1138}
1139
1140QQuickImageParticle::~QQuickImageParticle()
1141{
1142 clearShadows();
1143}
1144
1145QQmlListProperty<QQuickSprite> QQuickImageParticle::sprites()
1146{
1147 return QQmlListProperty<QQuickSprite>(this, &m_sprites,
1148 spriteAppend, spriteCount, spriteAt,
1149 spriteClear, spriteReplace, spriteRemoveLast);
1150}
1151
1152void QQuickImageParticle::sceneGraphInvalidated()
1153{
1154 m_nodes.clear();
1155 m_material = nullptr;
1156 delete m_outgoingNode;
1157 m_outgoingNode = nullptr;
1158}
1159
1160void QQuickImageParticle::setImage(const QUrl &image)
1161{
1162 if (image.isEmpty()){
1163 if (m_image) {
1164 m_image.reset();
1165 emit imageChanged();
1166 }
1167 return;
1168 }
1169
1170 if (!m_image)
1171 m_image.reset(other: new ImageData);
1172 if (image == m_image->source)
1173 return;
1174 m_image->source = image;
1175 emit imageChanged();
1176 reset();
1177}
1178
1179
1180void QQuickImageParticle::setColortable(const QUrl &table)
1181{
1182 if (table.isEmpty()){
1183 if (m_colorTable) {
1184 m_colorTable.reset();
1185 emit colortableChanged();
1186 }
1187 return;
1188 }
1189
1190 if (!m_colorTable)
1191 m_colorTable.reset(other: new ImageData);
1192 if (table == m_colorTable->source)
1193 return;
1194 m_colorTable->source = table;
1195 emit colortableChanged();
1196 reset();
1197}
1198
1199void QQuickImageParticle::setSizetable(const QUrl &table)
1200{
1201 if (table.isEmpty()){
1202 if (m_sizeTable) {
1203 m_sizeTable.reset();
1204 emit sizetableChanged();
1205 }
1206 return;
1207 }
1208
1209 if (!m_sizeTable)
1210 m_sizeTable.reset(other: new ImageData);
1211 if (table == m_sizeTable->source)
1212 return;
1213 m_sizeTable->source = table;
1214 emit sizetableChanged();
1215 reset();
1216}
1217
1218void QQuickImageParticle::setOpacitytable(const QUrl &table)
1219{
1220 if (table.isEmpty()){
1221 if (m_opacityTable) {
1222 m_opacityTable.reset();
1223 emit opacitytableChanged();
1224 }
1225 return;
1226 }
1227
1228 if (!m_opacityTable)
1229 m_opacityTable.reset(other: new ImageData);
1230 if (table == m_opacityTable->source)
1231 return;
1232 m_opacityTable->source = table;
1233 emit opacitytableChanged();
1234 reset();
1235}
1236
1237void QQuickImageParticle::setColor(const QColor &color)
1238{
1239 if (color == m_color)
1240 return;
1241 m_color = color;
1242 emit colorChanged();
1243 m_explicitColor = true;
1244 if (perfLevel < Colored)
1245 reset();
1246}
1247
1248void QQuickImageParticle::setColorVariation(qreal var)
1249{
1250 if (var == m_color_variation)
1251 return;
1252 m_color_variation = var;
1253 emit colorVariationChanged();
1254 m_explicitColor = true;
1255 if (perfLevel < Colored)
1256 reset();
1257}
1258
1259void QQuickImageParticle::setAlphaVariation(qreal arg)
1260{
1261 if (m_alphaVariation != arg) {
1262 m_alphaVariation = arg;
1263 emit alphaVariationChanged(arg);
1264 }
1265 m_explicitColor = true;
1266 if (perfLevel < Colored)
1267 reset();
1268}
1269
1270void QQuickImageParticle::setAlpha(qreal arg)
1271{
1272 if (m_alpha != arg) {
1273 m_alpha = arg;
1274 emit alphaChanged(arg);
1275 }
1276 m_explicitColor = true;
1277 if (perfLevel < Colored)
1278 reset();
1279}
1280
1281void QQuickImageParticle::setRedVariation(qreal arg)
1282{
1283 if (m_redVariation != arg) {
1284 m_redVariation = arg;
1285 emit redVariationChanged(arg);
1286 }
1287 m_explicitColor = true;
1288 if (perfLevel < Colored)
1289 reset();
1290}
1291
1292void QQuickImageParticle::setGreenVariation(qreal arg)
1293{
1294 if (m_greenVariation != arg) {
1295 m_greenVariation = arg;
1296 emit greenVariationChanged(arg);
1297 }
1298 m_explicitColor = true;
1299 if (perfLevel < Colored)
1300 reset();
1301}
1302
1303void QQuickImageParticle::setBlueVariation(qreal arg)
1304{
1305 if (m_blueVariation != arg) {
1306 m_blueVariation = arg;
1307 emit blueVariationChanged(arg);
1308 }
1309 m_explicitColor = true;
1310 if (perfLevel < Colored)
1311 reset();
1312}
1313
1314void QQuickImageParticle::setRotation(qreal arg)
1315{
1316 if (m_rotation != arg) {
1317 m_rotation = arg;
1318 emit rotationChanged(arg);
1319 }
1320 m_explicitRotation = true;
1321 if (perfLevel < Deformable)
1322 reset();
1323}
1324
1325void QQuickImageParticle::setRotationVariation(qreal arg)
1326{
1327 if (m_rotationVariation != arg) {
1328 m_rotationVariation = arg;
1329 emit rotationVariationChanged(arg);
1330 }
1331 m_explicitRotation = true;
1332 if (perfLevel < Deformable)
1333 reset();
1334}
1335
1336void QQuickImageParticle::setRotationVelocity(qreal arg)
1337{
1338 if (m_rotationVelocity != arg) {
1339 m_rotationVelocity = arg;
1340 emit rotationVelocityChanged(arg);
1341 }
1342 m_explicitRotation = true;
1343 if (perfLevel < Deformable)
1344 reset();
1345}
1346
1347void QQuickImageParticle::setRotationVelocityVariation(qreal arg)
1348{
1349 if (m_rotationVelocityVariation != arg) {
1350 m_rotationVelocityVariation = arg;
1351 emit rotationVelocityVariationChanged(arg);
1352 }
1353 m_explicitRotation = true;
1354 if (perfLevel < Deformable)
1355 reset();
1356}
1357
1358void QQuickImageParticle::setAutoRotation(bool arg)
1359{
1360 if (m_autoRotation != arg) {
1361 m_autoRotation = arg;
1362 emit autoRotationChanged(arg);
1363 }
1364 m_explicitRotation = true;
1365 if (perfLevel < Deformable)
1366 reset();
1367}
1368
1369void QQuickImageParticle::setXVector(QQuickDirection* arg)
1370{
1371 if (m_xVector != arg) {
1372 m_xVector = arg;
1373 emit xVectorChanged(arg);
1374 }
1375 m_explicitDeformation = true;
1376 if (perfLevel < Deformable)
1377 reset();
1378}
1379
1380void QQuickImageParticle::setYVector(QQuickDirection* arg)
1381{
1382 if (m_yVector != arg) {
1383 m_yVector = arg;
1384 emit yVectorChanged(arg);
1385 }
1386 m_explicitDeformation = true;
1387 if (perfLevel < Deformable)
1388 reset();
1389}
1390
1391void QQuickImageParticle::setSpritesInterpolate(bool arg)
1392{
1393 if (m_spritesInterpolate != arg) {
1394 m_spritesInterpolate = arg;
1395 emit spritesInterpolateChanged(arg);
1396 }
1397}
1398
1399void QQuickImageParticle::setBypassOptimizations(bool arg)
1400{
1401 if (m_bypassOptimizations != arg) {
1402 m_bypassOptimizations = arg;
1403 emit bypassOptimizationsChanged(arg);
1404 }
1405 // Applies regardless of perfLevel
1406 reset();
1407}
1408
1409void QQuickImageParticle::setEntryEffect(EntryEffect arg)
1410{
1411 if (m_entryEffect != arg) {
1412 m_entryEffect = arg;
1413 if (m_material)
1414 getState(m: m_material)->entry = (qreal) m_entryEffect;
1415 emit entryEffectChanged(arg);
1416 }
1417}
1418
1419void QQuickImageParticle::resetColor()
1420{
1421 m_explicitColor = false;
1422 for (auto groupId : groupIds()) {
1423 for (QQuickParticleData* d : qAsConst(t&: m_system->groupData[groupId]->data)) {
1424 if (d->colorOwner == this) {
1425 d->colorOwner = nullptr;
1426 }
1427 }
1428 }
1429 m_color = QColor();
1430 m_color_variation = 0.0f;
1431 m_redVariation = 0.0f;
1432 m_blueVariation = 0.0f;
1433 m_greenVariation = 0.0f;
1434 m_alpha = 1.0f;
1435 m_alphaVariation = 0.0f;
1436}
1437
1438void QQuickImageParticle::resetRotation()
1439{
1440 m_explicitRotation = false;
1441 for (auto groupId : groupIds()) {
1442 for (QQuickParticleData* d : qAsConst(t&: m_system->groupData[groupId]->data)) {
1443 if (d->rotationOwner == this) {
1444 d->rotationOwner = nullptr;
1445 }
1446 }
1447 }
1448 m_rotation = 0;
1449 m_rotationVariation = 0;
1450 m_rotationVelocity = 0;
1451 m_rotationVelocityVariation = 0;
1452 m_autoRotation = false;
1453}
1454
1455void QQuickImageParticle::resetDeformation()
1456{
1457 m_explicitDeformation = false;
1458 for (auto groupId : groupIds()) {
1459 for (QQuickParticleData* d : qAsConst(t&: m_system->groupData[groupId]->data)) {
1460 if (d->deformationOwner == this) {
1461 d->deformationOwner = nullptr;
1462 }
1463 }
1464 }
1465 if (m_xVector)
1466 delete m_xVector;
1467 if (m_yVector)
1468 delete m_yVector;
1469 m_xVector = nullptr;
1470 m_yVector = nullptr;
1471}
1472
1473void QQuickImageParticle::reset()
1474{
1475 QQuickParticlePainter::reset();
1476 m_pleaseReset = true;
1477 update();
1478}
1479
1480void QQuickImageParticle::createEngine()
1481{
1482 if (m_spriteEngine)
1483 delete m_spriteEngine;
1484 if (m_sprites.count()) {
1485 m_spriteEngine = new QQuickSpriteEngine(m_sprites, this);
1486 connect(sender: m_spriteEngine, SIGNAL(stateChanged(int)),
1487 receiver: this, SLOT(spriteAdvance(int)), Qt::DirectConnection);
1488 m_explicitAnimation = true;
1489 } else {
1490 m_spriteEngine = nullptr;
1491 m_explicitAnimation = false;
1492 }
1493 reset();
1494}
1495
1496static QSGGeometry::Attribute SimpleParticle_Attributes[] = {
1497 QSGGeometry::Attribute::create(pos: 0, tupleSize: 2, GL_FLOAT, isPosition: true), // Position
1498 QSGGeometry::Attribute::create(pos: 1, tupleSize: 4, GL_FLOAT), // Data
1499 QSGGeometry::Attribute::create(pos: 2, tupleSize: 4, GL_FLOAT) // Vectors
1500};
1501
1502static QSGGeometry::AttributeSet SimpleParticle_AttributeSet =
1503{
1504 .count: 3, // Attribute Count
1505 .stride: ( 2 + 4 + 4 ) * sizeof(float),
1506 .attributes: SimpleParticle_Attributes
1507};
1508
1509static QSGGeometry::Attribute ColoredParticle_Attributes[] = {
1510 QSGGeometry::Attribute::create(pos: 0, tupleSize: 2, GL_FLOAT, isPosition: true), // Position
1511 QSGGeometry::Attribute::create(pos: 1, tupleSize: 4, GL_FLOAT), // Data
1512 QSGGeometry::Attribute::create(pos: 2, tupleSize: 4, GL_FLOAT), // Vectors
1513 QSGGeometry::Attribute::create(pos: 3, tupleSize: 4, GL_UNSIGNED_BYTE), // Colors
1514};
1515
1516static QSGGeometry::AttributeSet ColoredParticle_AttributeSet =
1517{
1518 .count: 4, // Attribute Count
1519 .stride: ( 2 + 4 + 4 ) * sizeof(float) + 4 * sizeof(uchar),
1520 .attributes: ColoredParticle_Attributes
1521};
1522
1523static QSGGeometry::Attribute DeformableParticle_Attributes[] = {
1524 QSGGeometry::Attribute::create(pos: 0, tupleSize: 4, GL_FLOAT), // Position & TexCoord
1525 QSGGeometry::Attribute::create(pos: 1, tupleSize: 4, GL_FLOAT), // Data
1526 QSGGeometry::Attribute::create(pos: 2, tupleSize: 4, GL_FLOAT), // Vectors
1527 QSGGeometry::Attribute::create(pos: 3, tupleSize: 4, GL_UNSIGNED_BYTE), // Colors
1528 QSGGeometry::Attribute::create(pos: 4, tupleSize: 4, GL_FLOAT), // DeformationVectors
1529 QSGGeometry::Attribute::create(pos: 5, tupleSize: 3, GL_FLOAT), // Rotation
1530};
1531
1532static QSGGeometry::AttributeSet DeformableParticle_AttributeSet =
1533{
1534 .count: 6, // Attribute Count
1535 .stride: (4 + 4 + 4 + 4 + 3) * sizeof(float) + 4 * sizeof(uchar),
1536 .attributes: DeformableParticle_Attributes
1537};
1538
1539static QSGGeometry::Attribute SpriteParticle_Attributes[] = {
1540 QSGGeometry::Attribute::create(pos: 0, tupleSize: 4, GL_FLOAT), // Position & TexCoord
1541 QSGGeometry::Attribute::create(pos: 1, tupleSize: 4, GL_FLOAT), // Data
1542 QSGGeometry::Attribute::create(pos: 2, tupleSize: 4, GL_FLOAT), // Vectors
1543 QSGGeometry::Attribute::create(pos: 3, tupleSize: 4, GL_UNSIGNED_BYTE), // Colors
1544 QSGGeometry::Attribute::create(pos: 4, tupleSize: 4, GL_FLOAT), // DeformationVectors
1545 QSGGeometry::Attribute::create(pos: 5, tupleSize: 3, GL_FLOAT), // Rotation
1546 QSGGeometry::Attribute::create(pos: 6, tupleSize: 3, GL_FLOAT), // Anim Data
1547 QSGGeometry::Attribute::create(pos: 7, tupleSize: 4, GL_FLOAT) // Anim Pos
1548};
1549
1550static QSGGeometry::AttributeSet SpriteParticle_AttributeSet =
1551{
1552 .count: 8, // Attribute Count
1553 .stride: (4 + 4 + 4 + 4 + 3 + 3 + 4) * sizeof(float) + 4 * sizeof(uchar),
1554 .attributes: SpriteParticle_Attributes
1555};
1556
1557void QQuickImageParticle::clearShadows()
1558{
1559 foreach (const QVector<QQuickParticleData*> data, m_shadowData)
1560 qDeleteAll(c: data);
1561 m_shadowData.clear();
1562}
1563
1564//Only call if you need to, may initialize the whole array first time
1565QQuickParticleData* QQuickImageParticle::getShadowDatum(QQuickParticleData* datum)
1566{
1567 //Will return datum if the datum is a sentinel or uninitialized, to centralize that one check
1568 if (datum->systemIndex == -1)
1569 return datum;
1570 QQuickParticleGroupData* gd = m_system->groupData[datum->groupId];
1571 if (!m_shadowData.contains(key: datum->groupId)) {
1572 QVector<QQuickParticleData*> data;
1573 const int gdSize = gd->size();
1574 data.reserve(size: gdSize);
1575 for (int i = 0; i < gdSize; i++) {
1576 QQuickParticleData* datum = new QQuickParticleData;
1577 *datum = *(gd->data[i]);
1578 data << datum;
1579 }
1580 m_shadowData.insert(key: datum->groupId, value: data);
1581 }
1582 //### If dynamic resize is added, remember to potentially resize the shadow data on out-of-bounds access request
1583
1584 return m_shadowData[datum->groupId][datum->index];
1585}
1586
1587bool QQuickImageParticle::loadingSomething()
1588{
1589 return (m_image && m_image->pix.isLoading())
1590 || (m_colorTable && m_colorTable->pix.isLoading())
1591 || (m_sizeTable && m_sizeTable->pix.isLoading())
1592 || (m_opacityTable && m_opacityTable->pix.isLoading())
1593 || (m_spriteEngine && m_spriteEngine->isLoading());
1594}
1595
1596void QQuickImageParticle::mainThreadFetchImageData()
1597{
1598 if (m_image) {//ImageData created on setSource
1599 m_image->pix.clear(this);
1600 m_image->pix.load(qmlEngine(this), m_image->source);
1601 }
1602
1603 if (m_spriteEngine)
1604 m_spriteEngine->startAssemblingImage();
1605
1606 if (m_colorTable)
1607 m_colorTable->pix.load(qmlEngine(this), m_colorTable->source);
1608
1609 if (m_sizeTable)
1610 m_sizeTable->pix.load(qmlEngine(this), m_sizeTable->source);
1611
1612 if (m_opacityTable)
1613 m_opacityTable->pix.load(qmlEngine(this), m_opacityTable->source);
1614
1615 m_startedImageLoading = 2;
1616}
1617
1618void QQuickImageParticle::buildParticleNodes(QSGNode** passThrough)
1619{
1620 // Starts async parts, like loading images, on gui thread
1621 // Not on individual properties, because we delay until system is running
1622 if (*passThrough || loadingSomething())
1623 return;
1624
1625 if (m_startedImageLoading == 0) {
1626 m_startedImageLoading = 1;
1627 //stage 1 is in gui thread
1628 QQuickImageParticle::staticMetaObject.invokeMethod(obj: this, member: "mainThreadFetchImageData", type: Qt::QueuedConnection);
1629 } else if (m_startedImageLoading == 2) {
1630 finishBuildParticleNodes(n: passThrough); //rest happens in render thread
1631 }
1632
1633 //No mutex, because it's slow and a compare that fails due to a race condition means just a dropped frame
1634}
1635
1636void QQuickImageParticle::finishBuildParticleNodes(QSGNode** node)
1637{
1638 if (!m_rhi && !QOpenGLContext::currentContext())
1639 return;
1640
1641 if (m_count * 4 > 0xffff) {
1642 // Index data is ushort.
1643 qmlInfo(me: this) << "ImageParticle: Too many particles - maximum 16383 per ImageParticle";
1644 return;
1645 }
1646
1647 if (count() <= 0)
1648 return;
1649
1650 m_debugMode = m_system->m_debugMode;
1651
1652 if (m_sprites.count() || m_bypassOptimizations) {
1653 perfLevel = Sprites;
1654 } else if (m_colorTable || m_sizeTable || m_opacityTable) {
1655 perfLevel = Tabled;
1656 } else if (m_autoRotation || m_rotation || m_rotationVariation
1657 || m_rotationVelocity || m_rotationVelocityVariation
1658 || m_xVector || m_yVector) {
1659 perfLevel = Deformable;
1660 } else if (m_alphaVariation || m_alpha != 1.0 || m_color.isValid() || m_color_variation
1661 || m_redVariation || m_blueVariation || m_greenVariation) {
1662 perfLevel = Colored;
1663 } else {
1664 perfLevel = Simple;
1665 }
1666
1667 for (auto groupId : groupIds()) {
1668 //For sharing higher levels, need to have highest used so it renders
1669 for (QQuickParticlePainter* p : qAsConst(t&: m_system->groupData[groupId]->painters)) {
1670 QQuickImageParticle* other = qobject_cast<QQuickImageParticle*>(object: p);
1671 if (other){
1672 if (other->perfLevel > perfLevel) {
1673 if (other->perfLevel >= Tabled){//Deformable is the highest level needed for this, anything higher isn't shared (or requires your own sprite)
1674 if (perfLevel < Deformable)
1675 perfLevel = Deformable;
1676 } else {
1677 perfLevel = other->perfLevel;
1678 }
1679 } else if (other->perfLevel < perfLevel) {
1680 other->reset();
1681 }
1682 }
1683 }
1684 }
1685
1686 if (!m_rhi) { // the RHI may be backed by GL but these checks should be obsolete in any case
1687#ifdef Q_OS_WIN
1688 if (perfLevel < Deformable) //QTBUG-24540 , point sprite 'extension' isn't working on windows.
1689 perfLevel = Deformable;
1690#endif
1691
1692#ifdef Q_OS_MAC
1693 // macOS 10.8.3 introduced a bug in the AMD drivers, for at least the 2011 macbook pros,
1694 // causing point sprites who read gl_PointCoord in the frag shader to come out as
1695 // green-red blobs.
1696 const GLubyte *glVendor = QOpenGLContext::currentContext()->functions()->glGetString(GL_VENDOR);
1697 if (perfLevel < Deformable && glVendor && strstr((char *) glVendor, "ATI")) {
1698 perfLevel = Deformable;
1699 }
1700#endif
1701
1702#ifdef Q_OS_LINUX
1703 // Nouveau drivers can potentially freeze a machine entirely when taking the point-sprite path.
1704 const GLubyte *glVendor = QOpenGLContext::currentContext()->functions()->glGetString(GL_VENDOR);
1705 if (perfLevel < Deformable && glVendor && strstr(haystack: (const char *) glVendor, needle: "nouveau"))
1706 perfLevel = Deformable;
1707#endif
1708
1709 } else {
1710 // Points with a size other than 1 are an optional feature with QRhi
1711 // because some of the underlying APIs have no support for this.
1712 // Therefore, avoid the point sprite path with APIs like Direct3D.
1713 if (perfLevel < Deformable && !m_rhi->isFeatureSupported(feature: QRhi::VertexShaderPointSize))
1714 perfLevel = Deformable;
1715 }
1716
1717 if (perfLevel >= Colored && !m_color.isValid())
1718 m_color = QColor(Qt::white);//Hidden default, but different from unset
1719
1720 clearShadows();
1721 if (m_material)
1722 m_material = nullptr;
1723
1724 //Setup material
1725 QImage colortable;
1726 QImage sizetable;
1727 QImage opacitytable;
1728 QImage image;
1729 bool imageLoaded = false;
1730 switch (perfLevel) {//Fallthrough intended
1731 case Sprites:
1732 {
1733 if (!m_spriteEngine) {
1734 qWarning() << "ImageParticle: No sprite engine...";
1735 //Sprite performance mode with static image is supported, but not advised
1736 //Note that in this case it always uses shadow data
1737 } else {
1738 image = m_spriteEngine->assembledImage();
1739 if (image.isNull())//Warning is printed in engine
1740 return;
1741 imageLoaded = true;
1742 }
1743 m_material = new SpriteMaterial;
1744 ImageMaterialData *state = getState(m: m_material);
1745 if (imageLoaded)
1746 state->texture = QSGPlainTexture::fromImage(image);
1747 state->animSheetSize = QSizeF(image.size() / image.devicePixelRatioF());
1748 if (m_spriteEngine)
1749 m_spriteEngine->setCount(m_count);
1750 }
1751 Q_FALLTHROUGH();
1752 case Tabled:
1753 {
1754 if (!m_material)
1755 m_material = new TabledMaterial;
1756
1757 if (m_colorTable) {
1758 if (m_colorTable->pix.isReady())
1759 colortable = m_colorTable->pix.image();
1760 else
1761 qmlWarning(me: this) << "Error loading color table: " << m_colorTable->pix.error();
1762 }
1763
1764 if (m_sizeTable) {
1765 if (m_sizeTable->pix.isReady())
1766 sizetable = m_sizeTable->pix.image();
1767 else
1768 qmlWarning(me: this) << "Error loading size table: " << m_sizeTable->pix.error();
1769 }
1770
1771 if (m_opacityTable) {
1772 if (m_opacityTable->pix.isReady())
1773 opacitytable = m_opacityTable->pix.image();
1774 else
1775 qmlWarning(me: this) << "Error loading opacity table: " << m_opacityTable->pix.error();
1776 }
1777
1778 if (colortable.isNull()){//###Goes through image just for this
1779 colortable = QImage(1,1,QImage::Format_ARGB32_Premultiplied);
1780 colortable.fill(color: Qt::white);
1781 }
1782 ImageMaterialData *state = getState(m: m_material);
1783 state->colorTable = QSGPlainTexture::fromImage(image: colortable);
1784 fillUniformArrayFromImage(array: state->sizeTable, img: sizetable, UNIFORM_ARRAY_SIZE);
1785 fillUniformArrayFromImage(array: state->opacityTable, img: opacitytable, UNIFORM_ARRAY_SIZE);
1786 }
1787 Q_FALLTHROUGH();
1788 case Deformable:
1789 {
1790 if (!m_material)
1791 m_material = new DeformableMaterial;
1792 }
1793 Q_FALLTHROUGH();
1794 case Colored:
1795 {
1796 if (!m_material)
1797 m_material = new ColoredMaterial;
1798 }
1799 Q_FALLTHROUGH();
1800 default://Also Simple
1801 {
1802 if (!m_material)
1803 m_material = new SimpleMaterial;
1804 ImageMaterialData *state = getState(m: m_material);
1805 if (!imageLoaded) {
1806 if (!m_image || !m_image->pix.isReady()) {
1807 if (m_image)
1808 qmlWarning(me: this) << m_image->pix.error();
1809 delete m_material;
1810 return;
1811 }
1812 //state->texture //TODO: Shouldn't this be better? But not crash?
1813 // = QQuickItemPrivate::get(this)->sceneGraphContext()->textureForFactory(m_imagePix.textureFactory());
1814 state->texture = QSGPlainTexture::fromImage(image: m_image->pix.image());
1815 }
1816 state->texture->setFiltering(QSGTexture::Linear);
1817 state->entry = (qreal) m_entryEffect;
1818 m_material->setFlag(flags: QSGMaterial::Blending | QSGMaterial::RequiresFullMatrix);
1819 }
1820 }
1821
1822 m_nodes.clear();
1823 for (auto groupId : groupIds()) {
1824 int count = m_system->groupData[groupId]->size();
1825 QSGGeometryNode* node = new QSGGeometryNode();
1826 node->setMaterial(m_material);
1827 node->markDirty(bits: QSGNode::DirtyMaterial);
1828
1829 m_nodes.insert(key: groupId, value: node);
1830 m_idxStarts.insert(key: groupId, value: m_lastIdxStart);
1831 m_startsIdx.append(t: qMakePair<int,int>(x: m_lastIdxStart, y: groupId));
1832 m_lastIdxStart += count;
1833
1834 //Create Particle Geometry
1835 int vCount = count * 4;
1836 int iCount = count * 6;
1837
1838 QSGGeometry *g;
1839 if (perfLevel == Sprites)
1840 g = new QSGGeometry(SpriteParticle_AttributeSet, vCount, iCount);
1841 else if (perfLevel == Tabled)
1842 g = new QSGGeometry(DeformableParticle_AttributeSet, vCount, iCount);
1843 else if (perfLevel == Deformable)
1844 g = new QSGGeometry(DeformableParticle_AttributeSet, vCount, iCount);
1845 else if (perfLevel == Colored)
1846 g = new QSGGeometry(ColoredParticle_AttributeSet, count, 0);
1847 else //Simple
1848 g = new QSGGeometry(SimpleParticle_AttributeSet, count, 0);
1849
1850 node->setFlag(QSGNode::OwnsGeometry);
1851 node->setGeometry(g);
1852 if (perfLevel <= Colored){
1853 g->setDrawingMode(QSGGeometry::DrawPoints);
1854 if (m_debugMode) {
1855 if (m_rhi) {
1856 qDebug(msg: "Using point sprites");
1857 } else {
1858#if QT_CONFIG(opengl)
1859 GLfloat pointSizeRange[2];
1860 QOpenGLContext::currentContext()->functions()->glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, params: pointSizeRange);
1861 qDebug() << "Using point sprites, GL_ALIASED_POINT_SIZE_RANGE " <<pointSizeRange[0] << ":" << pointSizeRange[1];
1862#else
1863 qDebug("Using point sprites");
1864#endif
1865 }
1866 }
1867 } else {
1868 g->setDrawingMode(QSGGeometry::DrawTriangles);
1869 }
1870
1871 for (int p=0; p < count; ++p)
1872 commit(gIdx: groupId, pIdx: p);//commit sets geometry for the node, has its own perfLevel switch
1873
1874 if (perfLevel == Sprites)
1875 initTexCoords<SpriteVertex>(v: (SpriteVertex*)g->vertexData(), count: vCount);
1876 else if (perfLevel == Tabled)
1877 initTexCoords<DeformableVertex>(v: (DeformableVertex*)g->vertexData(), count: vCount);
1878 else if (perfLevel == Deformable)
1879 initTexCoords<DeformableVertex>(v: (DeformableVertex*)g->vertexData(), count: vCount);
1880
1881 if (perfLevel > Colored){
1882 quint16 *indices = g->indexDataAsUShort();
1883 for (int i=0; i < count; ++i) {
1884 int o = i * 4;
1885 indices[0] = o;
1886 indices[1] = o + 1;
1887 indices[2] = o + 2;
1888 indices[3] = o + 1;
1889 indices[4] = o + 3;
1890 indices[5] = o + 2;
1891 indices += 6;
1892 }
1893 }
1894 }
1895
1896 if (perfLevel == Sprites)
1897 spritesUpdate();//Gives all vertexes the initial sprite data, then maintained per frame
1898
1899 foreach (QSGGeometryNode* node, m_nodes){
1900 if (node == *(m_nodes.begin()))
1901 node->setFlag(QSGGeometryNode::OwnsMaterial);//Root node owns the material for memory management purposes
1902 else
1903 (*(m_nodes.begin()))->appendChildNode(node);
1904 }
1905
1906 *node = *(m_nodes.begin());
1907 update();
1908}
1909
1910QSGNode *QQuickImageParticle::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
1911{
1912 if (!m_apiChecked || m_windowChanged) {
1913 m_apiChecked = true;
1914 m_windowChanged = false;
1915
1916 QSGRenderContext *rc = QQuickItemPrivate::get(item: this)->sceneGraphRenderContext();
1917 QSGRendererInterface *rif = rc->sceneGraphContext()->rendererInterface(renderContext: rc);
1918 if (!rif)
1919 return nullptr;
1920
1921 QSGRendererInterface::GraphicsApi api = rif->graphicsApi();
1922 const bool isDirectOpenGL = api == QSGRendererInterface::OpenGL;
1923 const bool isRhi = QSGRendererInterface::isApiRhiBased(api);
1924
1925 if (!node && !isDirectOpenGL && !isRhi)
1926 return nullptr;
1927
1928 if (isRhi)
1929 m_rhi = static_cast<QRhi *>(rif->getResource(window: m_window, resource: QSGRendererInterface::RhiResource));
1930 else
1931 m_rhi = nullptr;
1932
1933 if (isRhi && !m_rhi) {
1934 qWarning(msg: "Failed to query QRhi, particles disabled");
1935 return nullptr;
1936 }
1937 }
1938
1939 if (m_pleaseReset){
1940 // Cannot just destroy the node and then return null (in case image
1941 // loading is still in progress). Rather, keep track of the old node
1942 // until we have a new one.
1943 delete m_outgoingNode;
1944 m_outgoingNode = node;
1945 node = nullptr;
1946
1947 m_lastLevel = perfLevel;
1948 m_nodes.clear();
1949
1950 m_idxStarts.clear();
1951 m_startsIdx.clear();
1952 m_lastIdxStart = 0;
1953
1954 m_material = nullptr;
1955
1956 m_pleaseReset = false;
1957 m_startedImageLoading = 0;//Cancel a part-way build (may still have a pending load)
1958 } else if (!m_material) {
1959 delete node;
1960 node = nullptr;
1961 }
1962
1963 if (m_system && m_system->isRunning() && !m_system->isPaused()){
1964 bool dirty = prepareNextFrame(&node);
1965 if (node) {
1966 update();
1967 if (dirty) {
1968 foreach (QSGGeometryNode* n, m_nodes)
1969 n->markDirty(bits: QSGNode::DirtyGeometry);
1970 }
1971 } else if (m_startedImageLoading < 2) {
1972 update();//To call prepareNextFrame() again from the renderThread
1973 }
1974 }
1975
1976 if (!node) {
1977 node = m_outgoingNode;
1978 m_outgoingNode = nullptr;
1979 }
1980
1981 return node;
1982}
1983
1984bool QQuickImageParticle::prepareNextFrame(QSGNode **node)
1985{
1986 if (*node == nullptr){//TODO: Staggered loading (as emitted)
1987 buildParticleNodes(passThrough: node);
1988 if (m_debugMode) {
1989 qDebug() << "QQuickImageParticle Feature level: " << perfLevel;
1990 qDebug() << "QQuickImageParticle Nodes: ";
1991 int count = 0;
1992 for (auto it = m_nodes.keyBegin(), end = m_nodes.keyEnd(); it != end; ++it) {
1993 qDebug() << "Group " << *it << " (" << m_system->groupData[*it]->size()
1994 << " particles)";
1995 count += m_system->groupData[*it]->size();
1996 }
1997 qDebug() << "Total count: " << count;
1998 }
1999 if (*node == nullptr)
2000 return false;
2001 }
2002 qint64 timeStamp = m_system->systemSync(p: this);
2003
2004 qreal time = timeStamp / 1000.;
2005
2006 switch (perfLevel){//Fall-through intended
2007 case Sprites:
2008 //Advance State
2009 if (m_spriteEngine)
2010 m_spriteEngine->updateSprites(time: timeStamp);//fires signals if anim changed
2011 spritesUpdate(time);
2012 Q_FALLTHROUGH();
2013 case Tabled:
2014 case Deformable:
2015 case Colored:
2016 case Simple:
2017 default: //Also Simple
2018 getState(m: m_material)->timestamp = time;
2019 break;
2020 }
2021
2022 bool active = false;
2023 for (auto groupId : groupIds()) {
2024 if (m_system->groupData[groupId]->isActive()) {
2025 active = true;
2026 break;
2027 }
2028 }
2029
2030 const bool dirty = active || m_previousActive;
2031 if (dirty) {
2032 foreach (QSGGeometryNode* node, m_nodes)
2033 node->markDirty(bits: QSGNode::DirtyMaterial);
2034 }
2035
2036 m_previousActive = active;
2037 return dirty;
2038}
2039
2040void QQuickImageParticle::spritesUpdate(qreal time)
2041{
2042 ImageMaterialData *state = getState(m: m_material);
2043 // Sprite progression handled CPU side, so as to have per-frame control.
2044 for (auto groupId : groupIds()) {
2045 for (QQuickParticleData* mainDatum : qAsConst(t&: m_system->groupData[groupId]->data)) {
2046 QSGGeometryNode *node = m_nodes[groupId];
2047 if (!node)
2048 continue;
2049 //TODO: Interpolate between two different animations if it's going to transition next frame
2050 // This is particularly important for cut-up sprites.
2051 QQuickParticleData* datum = (mainDatum->animationOwner == this ? mainDatum : getShadowDatum(datum: mainDatum));
2052 int spriteIdx = 0;
2053 for (int i = 0; i<m_startsIdx.count(); i++) {
2054 if (m_startsIdx[i].second == groupId){
2055 spriteIdx = m_startsIdx[i].first + datum->index;
2056 break;
2057 }
2058 }
2059
2060 double frameAt;
2061 qreal progress = 0;
2062
2063 if (datum->frameDuration > 0) {
2064 qreal frame = (time - datum->animT)/(datum->frameDuration / 1000.0);
2065 frame = qBound(min: (qreal)0.0, val: frame, max: (qreal)((qreal)datum->frameCount - 1.0));//Stop at count-1 frames until we have between anim interpolation
2066 if (m_spritesInterpolate)
2067 progress = std::modf(x: frame,iptr: &frameAt);
2068 else
2069 std::modf(x: frame,iptr: &frameAt);
2070 } else {
2071 datum->frameAt++;
2072 if (datum->frameAt >= datum->frameCount){
2073 datum->frameAt = 0;
2074 m_spriteEngine->advance(index: spriteIdx);
2075 }
2076 frameAt = datum->frameAt;
2077 }
2078 if (m_spriteEngine->sprite(sprite: spriteIdx)->reverse())//### Store this in datum too?
2079 frameAt = (datum->frameCount - 1) - frameAt;
2080 QSizeF sheetSize = state->animSheetSize;
2081 qreal y = datum->animY / sheetSize.height();
2082 qreal w = datum->animWidth / sheetSize.width();
2083 qreal h = datum->animHeight / sheetSize.height();
2084 qreal x1 = datum->animX / sheetSize.width();
2085 x1 += frameAt * w;
2086 qreal x2 = x1;
2087 if (frameAt < (datum->frameCount-1))
2088 x2 += w;
2089
2090 SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData();
2091 spriteVertices += datum->index*4;
2092 for (int i=0; i<4; i++) {
2093 spriteVertices[i].animX1 = x1;
2094 spriteVertices[i].animY1 = y;
2095 spriteVertices[i].animX2 = x2;
2096 spriteVertices[i].animY2 = y;
2097 spriteVertices[i].animW = w;
2098 spriteVertices[i].animH = h;
2099 spriteVertices[i].animProgress = progress;
2100 }
2101 }
2102 }
2103}
2104
2105void QQuickImageParticle::spriteAdvance(int spriteIdx)
2106{
2107 if (!m_startsIdx.count())//Probably overly defensive
2108 return;
2109
2110 int gIdx = -1;
2111 int i;
2112 for (i = 0; i<m_startsIdx.count(); i++) {
2113 if (spriteIdx < m_startsIdx[i].first) {
2114 gIdx = m_startsIdx[i-1].second;
2115 break;
2116 }
2117 }
2118 if (gIdx == -1)
2119 gIdx = m_startsIdx[i-1].second;
2120 int pIdx = spriteIdx - m_startsIdx[i-1].first;
2121
2122 QQuickParticleData* mainDatum = m_system->groupData[gIdx]->data[pIdx];
2123 QQuickParticleData* datum = (mainDatum->animationOwner == this ? mainDatum : getShadowDatum(datum: mainDatum));
2124
2125 datum->animIdx = m_spriteEngine->spriteState(sprite: spriteIdx);
2126 datum->animT = m_spriteEngine->spriteStart(sprite: spriteIdx)/1000.0;
2127 datum->frameCount = m_spriteEngine->spriteFrames(sprite: spriteIdx);
2128 datum->frameDuration = m_spriteEngine->spriteDuration(sprite: spriteIdx) / datum->frameCount;
2129 datum->animX = m_spriteEngine->spriteX(sprite: spriteIdx);
2130 datum->animY = m_spriteEngine->spriteY(sprite: spriteIdx);
2131 datum->animWidth = m_spriteEngine->spriteWidth(sprite: spriteIdx);
2132 datum->animHeight = m_spriteEngine->spriteHeight(sprite: spriteIdx);
2133}
2134
2135void QQuickImageParticle::reloadColor(const Color4ub &c, QQuickParticleData* d)
2136{
2137 d->color = c;
2138 //TODO: get index for reload - or make function take an index
2139}
2140
2141void QQuickImageParticle::initialize(int gIdx, int pIdx)
2142{
2143 Color4ub color;
2144 QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx];
2145 qreal redVariation = m_color_variation + m_redVariation;
2146 qreal greenVariation = m_color_variation + m_greenVariation;
2147 qreal blueVariation = m_color_variation + m_blueVariation;
2148 int spriteIdx = 0;
2149 if (m_spriteEngine) {
2150 spriteIdx = m_idxStarts[gIdx] + datum->index;
2151 if (spriteIdx >= m_spriteEngine->count())
2152 m_spriteEngine->setCount(spriteIdx+1);
2153 }
2154
2155 float rotation;
2156 float rotationVelocity;
2157 float autoRotate;
2158 switch (perfLevel){//Fall-through is intended on all of them
2159 case Sprites:
2160 // Initial Sprite State
2161 if (m_explicitAnimation && m_spriteEngine){
2162 if (!datum->animationOwner)
2163 datum->animationOwner = this;
2164 QQuickParticleData* writeTo = (datum->animationOwner == this ? datum : getShadowDatum(datum));
2165 writeTo->animT = writeTo->t;
2166 //writeTo->animInterpolate = m_spritesInterpolate;
2167 if (m_spriteEngine){
2168 m_spriteEngine->start(index: spriteIdx);
2169 writeTo->frameCount = m_spriteEngine->spriteFrames(sprite: spriteIdx);
2170 writeTo->frameDuration = m_spriteEngine->spriteDuration(sprite: spriteIdx) / writeTo->frameCount;
2171 writeTo->animIdx = 0;//Always starts at 0
2172 writeTo->frameAt = -1;
2173 writeTo->animX = m_spriteEngine->spriteX(sprite: spriteIdx);
2174 writeTo->animY = m_spriteEngine->spriteY(sprite: spriteIdx);
2175 writeTo->animWidth = m_spriteEngine->spriteWidth(sprite: spriteIdx);
2176 writeTo->animHeight = m_spriteEngine->spriteHeight(sprite: spriteIdx);
2177 }
2178 } else {
2179 ImageMaterialData *state = getState(m: m_material);
2180 QQuickParticleData* writeTo = getShadowDatum(datum);
2181 writeTo->animT = datum->t;
2182 writeTo->frameCount = 1;
2183 writeTo->frameDuration = 60000000.0;
2184 writeTo->frameAt = -1;
2185 writeTo->animIdx = 0;
2186 writeTo->animT = 0;
2187 writeTo->animX = writeTo->animY = 0;
2188 writeTo->animWidth = state->animSheetSize.width();
2189 writeTo->animHeight = state->animSheetSize.height();
2190 }
2191 Q_FALLTHROUGH();
2192 case Tabled:
2193 case Deformable:
2194 //Initial Rotation
2195 if (m_explicitDeformation){
2196 if (!datum->deformationOwner)
2197 datum->deformationOwner = this;
2198 if (m_xVector){
2199 const QPointF &ret = m_xVector->sample(from: QPointF(datum->x, datum->y));
2200 if (datum->deformationOwner == this) {
2201 datum->xx = ret.x();
2202 datum->xy = ret.y();
2203 } else {
2204 getShadowDatum(datum)->xx = ret.x();
2205 getShadowDatum(datum)->xy = ret.y();
2206 }
2207 }
2208 if (m_yVector){
2209 const QPointF &ret = m_yVector->sample(from: QPointF(datum->x, datum->y));
2210 if (datum->deformationOwner == this) {
2211 datum->yx = ret.x();
2212 datum->yy = ret.y();
2213 } else {
2214 getShadowDatum(datum)->yx = ret.x();
2215 getShadowDatum(datum)->yy = ret.y();
2216 }
2217 }
2218 }
2219
2220 if (m_explicitRotation){
2221 if (!datum->rotationOwner)
2222 datum->rotationOwner = this;
2223 rotation =
2224 (m_rotation + (m_rotationVariation - 2*QRandomGenerator::global()->bounded(highest: m_rotationVariation)) ) * CONV;
2225 rotationVelocity =
2226 (m_rotationVelocity + (m_rotationVelocityVariation - 2*QRandomGenerator::global()->bounded(highest: m_rotationVelocityVariation)) ) * CONV;
2227 autoRotate = m_autoRotation?1.0:0.0;
2228 if (datum->rotationOwner == this) {
2229 datum->rotation = rotation;
2230 datum->rotationVelocity = rotationVelocity;
2231 datum->autoRotate = autoRotate;
2232 } else {
2233 getShadowDatum(datum)->rotation = rotation;
2234 getShadowDatum(datum)->rotationVelocity = rotationVelocity;
2235 getShadowDatum(datum)->autoRotate = autoRotate;
2236 }
2237 }
2238 Q_FALLTHROUGH();
2239 case Colored:
2240 //Color initialization
2241 // Particle color
2242 if (m_explicitColor) {
2243 if (!datum->colorOwner)
2244 datum->colorOwner = this;
2245 color.r = m_color.red() * (1 - redVariation) + QRandomGenerator::global()->bounded(highest: 256) * redVariation;
2246 color.g = m_color.green() * (1 - greenVariation) + QRandomGenerator::global()->bounded(highest: 256) * greenVariation;
2247 color.b = m_color.blue() * (1 - blueVariation) + QRandomGenerator::global()->bounded(highest: 256) * blueVariation;
2248 color.a = m_alpha * m_color.alpha() * (1 - m_alphaVariation) + QRandomGenerator::global()->bounded(highest: 256) * m_alphaVariation;
2249 if (datum->colorOwner == this)
2250 datum->color = color;
2251 else
2252 getShadowDatum(datum)->color = color;
2253 }
2254 default:
2255 break;
2256 }
2257}
2258
2259void QQuickImageParticle::commit(int gIdx, int pIdx)
2260{
2261 if (m_pleaseReset)
2262 return;
2263 QSGGeometryNode *node = m_nodes[gIdx];
2264 if (!node)
2265 return;
2266 QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx];
2267 SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData();
2268 DeformableVertex *deformableVertices = (DeformableVertex *) node->geometry()->vertexData();
2269 ColoredVertex *coloredVertices = (ColoredVertex *) node->geometry()->vertexData();
2270 SimpleVertex *simpleVertices = (SimpleVertex *) node->geometry()->vertexData();
2271 switch (perfLevel){//No automatic fall through intended on this one
2272 case Sprites:
2273 spriteVertices += pIdx*4;
2274 for (int i=0; i<4; i++){
2275 spriteVertices[i].x = datum->x - m_systemOffset.x();
2276 spriteVertices[i].y = datum->y - m_systemOffset.y();
2277 spriteVertices[i].t = datum->t;
2278 spriteVertices[i].lifeSpan = datum->lifeSpan;
2279 spriteVertices[i].size = datum->size;
2280 spriteVertices[i].endSize = datum->endSize;
2281 spriteVertices[i].vx = datum->vx;
2282 spriteVertices[i].vy = datum->vy;
2283 spriteVertices[i].ax = datum->ax;
2284 spriteVertices[i].ay = datum->ay;
2285 if (m_explicitDeformation && datum->deformationOwner != this) {
2286 QQuickParticleData* shadow = getShadowDatum(datum);
2287 spriteVertices[i].xx = shadow->xx;
2288 spriteVertices[i].xy = shadow->xy;
2289 spriteVertices[i].yx = shadow->yx;
2290 spriteVertices[i].yy = shadow->yy;
2291 } else {
2292 spriteVertices[i].xx = datum->xx;
2293 spriteVertices[i].xy = datum->xy;
2294 spriteVertices[i].yx = datum->yx;
2295 spriteVertices[i].yy = datum->yy;
2296 }
2297 if (m_explicitRotation && datum->rotationOwner != this) {
2298 QQuickParticleData* shadow = getShadowDatum(datum);
2299 spriteVertices[i].rotation = shadow->rotation;
2300 spriteVertices[i].rotationVelocity = shadow->rotationVelocity;
2301 spriteVertices[i].autoRotate = shadow->autoRotate;
2302 } else {
2303 spriteVertices[i].rotation = datum->rotation;
2304 spriteVertices[i].rotationVelocity = datum->rotationVelocity;
2305 spriteVertices[i].autoRotate = datum->autoRotate;
2306 }
2307 //Sprite-related vertices updated per-frame in spritesUpdate(), not on demand
2308 if (m_explicitColor && datum->colorOwner != this) {
2309 QQuickParticleData* shadow = getShadowDatum(datum);
2310 spriteVertices[i].color.r = shadow->color.r;
2311 spriteVertices[i].color.g = shadow->color.g;
2312 spriteVertices[i].color.b = shadow->color.b;
2313 spriteVertices[i].color.a = shadow->color.a;
2314 } else {
2315 spriteVertices[i].color.r = datum->color.r;
2316 spriteVertices[i].color.g = datum->color.g;
2317 spriteVertices[i].color.b = datum->color.b;
2318 spriteVertices[i].color.a = datum->color.a;
2319 }
2320 }
2321 break;
2322 case Tabled: //Fall through until it has its own vertex class
2323 case Deformable:
2324 deformableVertices += pIdx*4;
2325 for (int i=0; i<4; i++){
2326 deformableVertices[i].x = datum->x - m_systemOffset.x();
2327 deformableVertices[i].y = datum->y - m_systemOffset.y();
2328 deformableVertices[i].t = datum->t;
2329 deformableVertices[i].lifeSpan = datum->lifeSpan;
2330 deformableVertices[i].size = datum->size;
2331 deformableVertices[i].endSize = datum->endSize;
2332 deformableVertices[i].vx = datum->vx;
2333 deformableVertices[i].vy = datum->vy;
2334 deformableVertices[i].ax = datum->ax;
2335 deformableVertices[i].ay = datum->ay;
2336 if (m_explicitDeformation && datum->deformationOwner != this) {
2337 QQuickParticleData* shadow = getShadowDatum(datum);
2338 deformableVertices[i].xx = shadow->xx;
2339 deformableVertices[i].xy = shadow->xy;
2340 deformableVertices[i].yx = shadow->yx;
2341 deformableVertices[i].yy = shadow->yy;
2342 } else {
2343 deformableVertices[i].xx = datum->xx;
2344 deformableVertices[i].xy = datum->xy;
2345 deformableVertices[i].yx = datum->yx;
2346 deformableVertices[i].yy = datum->yy;
2347 }
2348 if (m_explicitRotation && datum->rotationOwner != this) {
2349 QQuickParticleData* shadow = getShadowDatum(datum);
2350 deformableVertices[i].rotation = shadow->rotation;
2351 deformableVertices[i].rotationVelocity = shadow->rotationVelocity;
2352 deformableVertices[i].autoRotate = shadow->autoRotate;
2353 } else {
2354 deformableVertices[i].rotation = datum->rotation;
2355 deformableVertices[i].rotationVelocity = datum->rotationVelocity;
2356 deformableVertices[i].autoRotate = datum->autoRotate;
2357 }
2358 if (m_explicitColor && datum->colorOwner != this) {
2359 QQuickParticleData* shadow = getShadowDatum(datum);
2360 deformableVertices[i].color.r = shadow->color.r;
2361 deformableVertices[i].color.g = shadow->color.g;
2362 deformableVertices[i].color.b = shadow->color.b;
2363 deformableVertices[i].color.a = shadow->color.a;
2364 } else {
2365 deformableVertices[i].color.r = datum->color.r;
2366 deformableVertices[i].color.g = datum->color.g;
2367 deformableVertices[i].color.b = datum->color.b;
2368 deformableVertices[i].color.a = datum->color.a;
2369 }
2370 }
2371 break;
2372 case Colored:
2373 coloredVertices += pIdx*1;
2374 for (int i=0; i<1; i++){
2375 coloredVertices[i].x = datum->x - m_systemOffset.x();
2376 coloredVertices[i].y = datum->y - m_systemOffset.y();
2377 coloredVertices[i].t = datum->t;
2378 coloredVertices[i].lifeSpan = datum->lifeSpan;
2379 coloredVertices[i].size = datum->size;
2380 coloredVertices[i].endSize = datum->endSize;
2381 coloredVertices[i].vx = datum->vx;
2382 coloredVertices[i].vy = datum->vy;
2383 coloredVertices[i].ax = datum->ax;
2384 coloredVertices[i].ay = datum->ay;
2385 if (m_explicitColor && datum->colorOwner != this) {
2386 QQuickParticleData* shadow = getShadowDatum(datum);
2387 coloredVertices[i].color.r = shadow->color.r;
2388 coloredVertices[i].color.g = shadow->color.g;
2389 coloredVertices[i].color.b = shadow->color.b;
2390 coloredVertices[i].color.a = shadow->color.a;
2391 } else {
2392 coloredVertices[i].color.r = datum->color.r;
2393 coloredVertices[i].color.g = datum->color.g;
2394 coloredVertices[i].color.b = datum->color.b;
2395 coloredVertices[i].color.a = datum->color.a;
2396 }
2397 }
2398 break;
2399 case Simple:
2400 simpleVertices += pIdx*1;
2401 for (int i=0; i<1; i++){
2402 simpleVertices[i].x = datum->x - m_systemOffset.x();
2403 simpleVertices[i].y = datum->y - m_systemOffset.y();
2404 simpleVertices[i].t = datum->t;
2405 simpleVertices[i].lifeSpan = datum->lifeSpan;
2406 simpleVertices[i].size = datum->size;
2407 simpleVertices[i].endSize = datum->endSize;
2408 simpleVertices[i].vx = datum->vx;
2409 simpleVertices[i].vy = datum->vy;
2410 simpleVertices[i].ax = datum->ax;
2411 simpleVertices[i].ay = datum->ay;
2412 }
2413 break;
2414 default:
2415 break;
2416 }
2417}
2418
2419
2420
2421QT_END_NAMESPACE
2422
2423#include "moc_qquickimageparticle_p.cpp"
2424

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