| 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 | |