| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtQuick/private/qsgcontext_p.h> |
| #include <private/qsgadaptationlayer_p.h> |
| #include <private/qquickitem_p.h> |
| #include <QtQuick/qsgnode.h> |
| #include <QtQuick/qsgtexture.h> |
| #include <QFile> |
| #include <QRandomGenerator> |
| #include "qquickimageparticle_p.h" |
| #include "qquickparticleemitter_p.h" |
| #include <private/qquicksprite_p.h> |
| #include <private/qquickspriteengine_p.h> |
| #include <QOpenGLFunctions> |
| #include <QSGRendererInterface> |
| #include <QtQuick/private/qsgshadersourcebuilder_p.h> |
| #include <QtQuick/private/qsgplaintexture_p.h> |
| #include <private/qqmlglobal_p.h> |
| #include <QtQml/qqmlinfo.h> |
| #include <cmath> |
| #include <QtGui/private/qrhi_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| // Must match the shader code |
| #define UNIFORM_ARRAY_SIZE 64 |
| |
| const qreal CONV = 0.017453292519943295; |
| |
| class ImageMaterialData |
| { |
| public: |
| ImageMaterialData() |
| : texture(nullptr), colorTable(nullptr) |
| {} |
| |
| ~ImageMaterialData(){ |
| delete texture; |
| delete colorTable; |
| } |
| |
| QSGTexture *texture; |
| QSGTexture *colorTable; |
| float sizeTable[UNIFORM_ARRAY_SIZE]; |
| float opacityTable[UNIFORM_ARRAY_SIZE]; |
| |
| qreal timestamp; |
| qreal entry; |
| QSizeF animSheetSize; |
| }; |
| |
| class TabledMaterialShader : public QSGMaterialShader |
| { |
| public: |
| TabledMaterialShader() |
| { |
| QSGShaderSourceBuilder builder; |
| const bool isES = QOpenGLContext::currentContext()->isOpenGLES(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert")); |
| builder.addDefinition(QByteArrayLiteral("TABLE")); |
| builder.addDefinition(QByteArrayLiteral("DEFORM")); |
| builder.addDefinition(QByteArrayLiteral("COLOR")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_vertex_code = builder.source(); |
| builder.clear(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag")); |
| builder.addDefinition(QByteArrayLiteral("TABLE")); |
| builder.addDefinition(QByteArrayLiteral("DEFORM")); |
| builder.addDefinition(QByteArrayLiteral("COLOR")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_fragment_code = builder.source(); |
| |
| Q_ASSERT(!m_vertex_code.isNull()); |
| Q_ASSERT(!m_fragment_code.isNull()); |
| } |
| |
| const char *vertexShader() const override { return m_vertex_code.constData(); } |
| const char *fragmentShader() const override { return m_fragment_code.constData(); } |
| |
| char const *const *attributeNames() const override |
| { |
| static const char *const attr[] = { "vPosTex", "vData", "vVec", "vColor", "vDeformVec", "vRotation", nullptr }; |
| return attr; |
| } |
| |
| void initialize() override { |
| program()->bind(); |
| program()->setUniformValue("_qt_texture", 0); |
| program()->setUniformValue("colortable", 1); |
| glFuncs = QOpenGLContext::currentContext()->functions(); |
| m_matrix_id = program()->uniformLocation("qt_Matrix"); |
| m_opacity_id = program()->uniformLocation("qt_Opacity"); |
| m_timestamp_id = program()->uniformLocation("timestamp"); |
| m_entry_id = program()->uniformLocation("entry"); |
| m_sizetable_id = program()->uniformLocation("sizetable"); |
| m_opacitytable_id = program()->uniformLocation("opacitytable"); |
| } |
| |
| void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state(); |
| |
| if (renderState.isMatrixDirty()) |
| program()->setUniformValue(m_matrix_id, renderState.combinedMatrix()); |
| if (renderState.isOpacityDirty() && m_opacity_id >= 0) |
| program()->setUniformValue(m_opacity_id, renderState.opacity()); |
| |
| glFuncs->glActiveTexture(GL_TEXTURE1); |
| state->colorTable->bind(); |
| |
| glFuncs->glActiveTexture(GL_TEXTURE0); |
| state->texture->bind(); |
| |
| program()->setUniformValue(m_timestamp_id, (float) state->timestamp); |
| program()->setUniformValue(m_entry_id, (float) state->entry); |
| program()->setUniformValueArray(m_sizetable_id, (const float*) state->sizeTable, UNIFORM_ARRAY_SIZE, 1); |
| program()->setUniformValueArray(m_opacitytable_id, (const float*) state->opacityTable, UNIFORM_ARRAY_SIZE, 1); |
| } |
| |
| int m_matrix_id; |
| int m_opacity_id; |
| int m_entry_id; |
| int m_timestamp_id; |
| int m_sizetable_id; |
| int m_opacitytable_id; |
| QByteArray m_vertex_code; |
| QByteArray m_fragment_code; |
| QOpenGLFunctions* glFuncs; |
| }; |
| |
| class TabledMaterialRhiShader : public QSGMaterialRhiShader |
| { |
| public: |
| TabledMaterialRhiShader() |
| { |
| setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_tabled.vert.qsb")); |
| setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_tabled.frag.qsb")); |
| } |
| |
| bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| QByteArray *buf = renderState.uniformData(); |
| Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4)); |
| |
| if (renderState.isMatrixDirty()) { |
| const QMatrix4x4 m = renderState.combinedMatrix(); |
| memcpy(buf->data(), m.constData(), 64); |
| } |
| |
| if (renderState.isOpacityDirty()) { |
| const float opacity = renderState.opacity(); |
| memcpy(buf->data() + 64, &opacity, 4); |
| } |
| |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| |
| float entry = float(state->entry); |
| memcpy(buf->data() + 68, &entry, 4); |
| |
| float timestamp = float(state->timestamp); |
| memcpy(buf->data() + 72, ×tamp, 4); |
| |
| float *p = reinterpret_cast<float *>(buf->data() + 80); |
| for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) { |
| *p = state->sizeTable[i]; |
| p += 4; |
| } |
| p = reinterpret_cast<float *>(buf->data() + 80 + (UNIFORM_ARRAY_SIZE * 4 * 4)); |
| for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) { |
| *p = state->opacityTable[i]; |
| p += 4; |
| } |
| |
| return true; |
| } |
| |
| void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture, |
| QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| if (binding == 2) { |
| state->colorTable->updateRhiTexture(renderState.rhi(), renderState.resourceUpdateBatch()); |
| *texture = state->colorTable; |
| } else if (binding == 1) { |
| state->texture->updateRhiTexture(renderState.rhi(), renderState.resourceUpdateBatch()); |
| *texture = state->texture; |
| } |
| } |
| }; |
| |
| class TabledMaterial : public ImageMaterial |
| { |
| public: |
| TabledMaterial() { setFlag(SupportsRhiShader, true); } |
| QSGMaterialShader *createShader() const override { |
| if (flags().testFlag(RhiShaderWanted)) |
| return new TabledMaterialRhiShader; |
| else |
| return new TabledMaterialShader; |
| } |
| QSGMaterialType *type() const override { return &m_type; } |
| |
| ImageMaterialData *state() override { return &m_state; } |
| |
| private: |
| static QSGMaterialType m_type; |
| ImageMaterialData m_state; |
| }; |
| |
| QSGMaterialType TabledMaterial::m_type; |
| |
| class DeformableMaterialShader : public QSGMaterialShader |
| { |
| public: |
| DeformableMaterialShader() |
| { |
| QSGShaderSourceBuilder builder; |
| const bool isES = QOpenGLContext::currentContext()->isOpenGLES(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert")); |
| builder.addDefinition(QByteArrayLiteral("DEFORM")); |
| builder.addDefinition(QByteArrayLiteral("COLOR")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_vertex_code = builder.source(); |
| builder.clear(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag")); |
| builder.addDefinition(QByteArrayLiteral("DEFORM")); |
| builder.addDefinition(QByteArrayLiteral("COLOR")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_fragment_code = builder.source(); |
| |
| Q_ASSERT(!m_vertex_code.isNull()); |
| Q_ASSERT(!m_fragment_code.isNull()); |
| } |
| |
| const char *vertexShader() const override { return m_vertex_code.constData(); } |
| const char *fragmentShader() const override { return m_fragment_code.constData(); } |
| |
| char const *const *attributeNames() const override |
| { |
| static const char *const attr[] = { "vPosTex", "vData", "vVec", "vColor", "vDeformVec", "vRotation", nullptr }; |
| return attr; |
| } |
| |
| void initialize() override { |
| program()->bind(); |
| program()->setUniformValue("_qt_texture", 0); |
| glFuncs = QOpenGLContext::currentContext()->functions(); |
| m_matrix_id = program()->uniformLocation("qt_Matrix"); |
| m_opacity_id = program()->uniformLocation("qt_Opacity"); |
| m_timestamp_id = program()->uniformLocation("timestamp"); |
| m_entry_id = program()->uniformLocation("entry"); |
| } |
| |
| void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state(); |
| |
| if (renderState.isMatrixDirty()) |
| program()->setUniformValue(m_matrix_id, renderState.combinedMatrix()); |
| if (renderState.isOpacityDirty() && m_opacity_id >= 0) |
| program()->setUniformValue(m_opacity_id, renderState.opacity()); |
| |
| state->texture->bind(); |
| |
| program()->setUniformValue(m_timestamp_id, (float) state->timestamp); |
| program()->setUniformValue(m_entry_id, (float) state->entry); |
| } |
| |
| int m_matrix_id; |
| int m_opacity_id; |
| int m_entry_id; |
| int m_timestamp_id; |
| QByteArray m_vertex_code; |
| QByteArray m_fragment_code; |
| QOpenGLFunctions* glFuncs; |
| }; |
| |
| class DeformableMaterialRhiShader : public QSGMaterialRhiShader |
| { |
| public: |
| DeformableMaterialRhiShader() |
| { |
| setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_deformed.vert.qsb")); |
| setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_deformed.frag.qsb")); |
| } |
| |
| bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| QByteArray *buf = renderState.uniformData(); |
| Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4)); |
| |
| if (renderState.isMatrixDirty()) { |
| const QMatrix4x4 m = renderState.combinedMatrix(); |
| memcpy(buf->data(), m.constData(), 64); |
| } |
| |
| if (renderState.isOpacityDirty()) { |
| const float opacity = renderState.opacity(); |
| memcpy(buf->data() + 64, &opacity, 4); |
| } |
| |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| |
| float entry = float(state->entry); |
| memcpy(buf->data() + 68, &entry, 4); |
| |
| float timestamp = float(state->timestamp); |
| memcpy(buf->data() + 72, ×tamp, 4); |
| |
| return true; |
| } |
| |
| void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture, |
| QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| if (binding == 1) { |
| state->texture->updateRhiTexture(renderState.rhi(), renderState.resourceUpdateBatch()); |
| *texture = state->texture; |
| } |
| } |
| }; |
| |
| class DeformableMaterial : public ImageMaterial |
| { |
| public: |
| DeformableMaterial() { setFlag(SupportsRhiShader, true); } |
| QSGMaterialShader *createShader() const override { |
| if (flags().testFlag(RhiShaderWanted)) |
| return new DeformableMaterialRhiShader; |
| else |
| return new DeformableMaterialShader; |
| } |
| QSGMaterialType *type() const override { return &m_type; } |
| |
| ImageMaterialData *state() override { return &m_state; } |
| |
| private: |
| static QSGMaterialType m_type; |
| ImageMaterialData m_state; |
| }; |
| |
| QSGMaterialType DeformableMaterial::m_type; |
| |
| class SpriteMaterialShader : public QSGMaterialShader |
| { |
| public: |
| SpriteMaterialShader() |
| { |
| QSGShaderSourceBuilder builder; |
| const bool isES = QOpenGLContext::currentContext()->isOpenGLES(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert")); |
| builder.addDefinition(QByteArrayLiteral("SPRITE")); |
| builder.addDefinition(QByteArrayLiteral("TABLE")); |
| builder.addDefinition(QByteArrayLiteral("DEFORM")); |
| builder.addDefinition(QByteArrayLiteral("COLOR")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_vertex_code = builder.source(); |
| builder.clear(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag")); |
| builder.addDefinition(QByteArrayLiteral("SPRITE")); |
| builder.addDefinition(QByteArrayLiteral("TABLE")); |
| builder.addDefinition(QByteArrayLiteral("DEFORM")); |
| builder.addDefinition(QByteArrayLiteral("COLOR")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_fragment_code = builder.source(); |
| |
| Q_ASSERT(!m_vertex_code.isNull()); |
| Q_ASSERT(!m_fragment_code.isNull()); |
| } |
| |
| const char *vertexShader() const override { return m_vertex_code.constData(); } |
| const char *fragmentShader() const override { return m_fragment_code.constData(); } |
| |
| char const *const *attributeNames() const override |
| { |
| static const char *const attr[] = { "vPosTex", "vData", "vVec", "vColor", "vDeformVec", "vRotation", |
| "vAnimData", "vAnimPos", nullptr }; |
| return attr; |
| } |
| |
| void initialize() override { |
| program()->bind(); |
| program()->setUniformValue("_qt_texture", 0); |
| program()->setUniformValue("colortable", 1); |
| glFuncs = QOpenGLContext::currentContext()->functions(); |
| m_matrix_id = program()->uniformLocation("qt_Matrix"); |
| m_opacity_id = program()->uniformLocation("qt_Opacity"); |
| //Don't actually expose the animSheetSize in the shader, it's currently only used for CPU calculations. |
| m_timestamp_id = program()->uniformLocation("timestamp"); |
| m_entry_id = program()->uniformLocation("entry"); |
| m_sizetable_id = program()->uniformLocation("sizetable"); |
| m_opacitytable_id = program()->uniformLocation("opacitytable"); |
| } |
| |
| void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state(); |
| |
| if (renderState.isMatrixDirty()) |
| program()->setUniformValue(m_matrix_id, renderState.combinedMatrix()); |
| if (renderState.isOpacityDirty() && m_opacity_id >= 0) |
| program()->setUniformValue(m_opacity_id, renderState.opacity()); |
| |
| glFuncs->glActiveTexture(GL_TEXTURE1); |
| state->colorTable->bind(); |
| |
| // make sure we end by setting GL_TEXTURE0 as active texture |
| glFuncs->glActiveTexture(GL_TEXTURE0); |
| state->texture->bind(); |
| |
| program()->setUniformValue(m_timestamp_id, (float) state->timestamp); |
| program()->setUniformValue(m_entry_id, (float) state->entry); |
| program()->setUniformValueArray(m_sizetable_id, (const float*) state->sizeTable, 64, 1); |
| program()->setUniformValueArray(m_opacitytable_id, (const float*) state->opacityTable, UNIFORM_ARRAY_SIZE, 1); |
| } |
| |
| int m_matrix_id; |
| int m_opacity_id; |
| int m_timestamp_id; |
| int m_entry_id; |
| int m_sizetable_id; |
| int m_opacitytable_id; |
| QByteArray m_vertex_code; |
| QByteArray m_fragment_code; |
| QOpenGLFunctions* glFuncs; |
| }; |
| |
| class SpriteMaterialRhiShader : public QSGMaterialRhiShader |
| { |
| public: |
| SpriteMaterialRhiShader() |
| { |
| setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_sprite.vert.qsb")); |
| setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_sprite.frag.qsb")); |
| } |
| |
| bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| QByteArray *buf = renderState.uniformData(); |
| Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4)); |
| |
| if (renderState.isMatrixDirty()) { |
| const QMatrix4x4 m = renderState.combinedMatrix(); |
| memcpy(buf->data(), m.constData(), 64); |
| } |
| |
| if (renderState.isOpacityDirty()) { |
| const float opacity = renderState.opacity(); |
| memcpy(buf->data() + 64, &opacity, 4); |
| } |
| |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| |
| float entry = float(state->entry); |
| memcpy(buf->data() + 68, &entry, 4); |
| |
| float timestamp = float(state->timestamp); |
| memcpy(buf->data() + 72, ×tamp, 4); |
| |
| float *p = reinterpret_cast<float *>(buf->data() + 80); |
| for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) { |
| *p = state->sizeTable[i]; |
| p += 4; |
| } |
| p = reinterpret_cast<float *>(buf->data() + 80 + (UNIFORM_ARRAY_SIZE * 4 * 4)); |
| for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) { |
| *p = state->opacityTable[i]; |
| p += 4; |
| } |
| |
| return true; |
| } |
| |
| void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture, |
| QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| if (binding == 2) { |
| state->colorTable->updateRhiTexture(renderState.rhi(), renderState.resourceUpdateBatch()); |
| *texture = state->colorTable; |
| } else if (binding == 1) { |
| state->texture->updateRhiTexture(renderState.rhi(), renderState.resourceUpdateBatch()); |
| *texture = state->texture; |
| } |
| } |
| }; |
| |
| class SpriteMaterial : public ImageMaterial |
| { |
| public: |
| SpriteMaterial() { setFlag(SupportsRhiShader, true); } |
| QSGMaterialShader *createShader() const override { |
| if (flags().testFlag(RhiShaderWanted)) |
| return new SpriteMaterialRhiShader; |
| else |
| return new SpriteMaterialShader; |
| } |
| QSGMaterialType *type() const override { return &m_type; } |
| |
| ImageMaterialData *state() override { return &m_state; } |
| |
| private: |
| static QSGMaterialType m_type; |
| ImageMaterialData m_state; |
| }; |
| |
| QSGMaterialType SpriteMaterial::m_type; |
| |
| class ColoredMaterialShader : public QSGMaterialShader |
| { |
| public: |
| ColoredMaterialShader() |
| { |
| QSGShaderSourceBuilder builder; |
| const bool isES = QOpenGLContext::currentContext()->isOpenGLES(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert")); |
| builder.addDefinition(QByteArrayLiteral("COLOR")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_vertex_code = builder.source(); |
| builder.clear(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag")); |
| builder.addDefinition(QByteArrayLiteral("COLOR")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_fragment_code = builder.source(); |
| |
| Q_ASSERT(!m_vertex_code.isNull()); |
| Q_ASSERT(!m_fragment_code.isNull()); |
| } |
| |
| const char *vertexShader() const override { return m_vertex_code.constData(); } |
| const char *fragmentShader() const override { return m_fragment_code.constData(); } |
| |
| char const *const *attributeNames() const override |
| { |
| static const char *const attr[] = { "vPos", "vData", "vVec", "vColor", nullptr }; |
| return attr; |
| } |
| |
| void initialize() override { |
| program()->bind(); |
| program()->setUniformValue("_qt_texture", 0); |
| glFuncs = QOpenGLContext::currentContext()->functions(); |
| m_matrix_id = program()->uniformLocation("qt_Matrix"); |
| m_opacity_id = program()->uniformLocation("qt_Opacity"); |
| m_timestamp_id = program()->uniformLocation("timestamp"); |
| m_entry_id = program()->uniformLocation("entry"); |
| } |
| |
| void activate() override { |
| #if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) |
| glEnable(GL_POINT_SPRITE); |
| glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); |
| #endif |
| } |
| |
| void deactivate() override { |
| #if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) |
| glDisable(GL_POINT_SPRITE); |
| glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); |
| #endif |
| } |
| |
| void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state(); |
| |
| if (renderState.isMatrixDirty()) |
| program()->setUniformValue(m_matrix_id, renderState.combinedMatrix()); |
| if (renderState.isOpacityDirty() && m_opacity_id >= 0) |
| program()->setUniformValue(m_opacity_id, renderState.opacity()); |
| |
| state->texture->bind(); |
| |
| program()->setUniformValue(m_timestamp_id, (float) state->timestamp); |
| program()->setUniformValue(m_entry_id, (float) state->entry); |
| } |
| |
| int m_matrix_id; |
| int m_opacity_id; |
| int m_timestamp_id; |
| int m_entry_id; |
| QByteArray m_vertex_code; |
| QByteArray m_fragment_code; |
| QOpenGLFunctions* glFuncs; |
| }; |
| |
| class ColoredMaterialRhiShader : public QSGMaterialRhiShader |
| { |
| public: |
| ColoredMaterialRhiShader() |
| { |
| setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_colored.vert.qsb")); |
| setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_colored.frag.qsb")); |
| } |
| |
| bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| QByteArray *buf = renderState.uniformData(); |
| Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4)); |
| |
| if (renderState.isMatrixDirty()) { |
| const QMatrix4x4 m = renderState.combinedMatrix(); |
| memcpy(buf->data(), m.constData(), 64); |
| } |
| |
| if (renderState.isOpacityDirty()) { |
| const float opacity = renderState.opacity(); |
| memcpy(buf->data() + 64, &opacity, 4); |
| } |
| |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| |
| float entry = float(state->entry); |
| memcpy(buf->data() + 68, &entry, 4); |
| |
| float timestamp = float(state->timestamp); |
| memcpy(buf->data() + 72, ×tamp, 4); |
| |
| return true; |
| } |
| |
| void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture, |
| QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| if (binding == 1) { |
| state->texture->updateRhiTexture(renderState.rhi(), renderState.resourceUpdateBatch()); |
| *texture = state->texture; |
| } |
| } |
| }; |
| |
| class ColoredMaterial : public ImageMaterial |
| { |
| public: |
| ColoredMaterial() { setFlag(SupportsRhiShader, true); } |
| QSGMaterialShader *createShader() const override { |
| if (flags().testFlag(RhiShaderWanted)) |
| return new ColoredMaterialRhiShader; |
| else |
| return new ColoredMaterialShader; |
| } |
| QSGMaterialType *type() const override { return &m_type; } |
| |
| ImageMaterialData *state() override { return &m_state; } |
| |
| private: |
| static QSGMaterialType m_type; |
| ImageMaterialData m_state; |
| }; |
| |
| QSGMaterialType ColoredMaterial::m_type; |
| |
| class SimpleMaterialShader : public QSGMaterialShader |
| { |
| public: |
| SimpleMaterialShader() |
| { |
| QSGShaderSourceBuilder builder; |
| const bool isES = QOpenGLContext::currentContext()->isOpenGLES(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.vert")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_vertex_code = builder.source(); |
| builder.clear(); |
| |
| builder.appendSourceFile(QStringLiteral(":/particles/shaders/imageparticle.frag")); |
| if (isES) |
| builder.removeVersion(); |
| |
| m_fragment_code = builder.source(); |
| |
| Q_ASSERT(!m_vertex_code.isNull()); |
| Q_ASSERT(!m_fragment_code.isNull()); |
| } |
| |
| const char *vertexShader() const override { return m_vertex_code.constData(); } |
| const char *fragmentShader() const override { return m_fragment_code.constData(); } |
| |
| char const *const *attributeNames() const override |
| { |
| static const char *const attr[] = { "vPos", "vData", "vVec", nullptr }; |
| return attr; |
| } |
| |
| void initialize() override { |
| program()->bind(); |
| program()->setUniformValue("_qt_texture", 0); |
| glFuncs = QOpenGLContext::currentContext()->functions(); |
| m_matrix_id = program()->uniformLocation("qt_Matrix"); |
| m_opacity_id = program()->uniformLocation("qt_Opacity"); |
| m_timestamp_id = program()->uniformLocation("timestamp"); |
| m_entry_id = program()->uniformLocation("entry"); |
| } |
| |
| void activate() override { |
| #if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) |
| glEnable(GL_POINT_SPRITE); |
| glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); |
| #endif |
| } |
| |
| void deactivate() override { |
| #if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) |
| glDisable(GL_POINT_SPRITE); |
| glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); |
| #endif |
| } |
| |
| void updateState(const RenderState &renderState, QSGMaterial *mat, QSGMaterial *) override { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(mat)->state(); |
| |
| if (renderState.isMatrixDirty()) |
| program()->setUniformValue(m_matrix_id, renderState.combinedMatrix()); |
| if (renderState.isOpacityDirty() && m_opacity_id >= 0) |
| program()->setUniformValue(m_opacity_id, renderState.opacity()); |
| |
| state->texture->bind(); |
| |
| program()->setUniformValue(m_timestamp_id, (float) state->timestamp); |
| program()->setUniformValue(m_entry_id, (float) state->entry); |
| } |
| |
| int m_matrix_id; |
| int m_opacity_id; |
| int m_timestamp_id; |
| int m_entry_id; |
| QByteArray m_vertex_code; |
| QByteArray m_fragment_code; |
| QOpenGLFunctions* glFuncs; |
| }; |
| |
| class SimpleMaterialRhiShader : public QSGMaterialRhiShader |
| { |
| public: |
| SimpleMaterialRhiShader() |
| { |
| setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_simple.vert.qsb")); |
| setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_simple.frag.qsb")); |
| } |
| |
| bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| QByteArray *buf = renderState.uniformData(); |
| Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4)); |
| |
| if (renderState.isMatrixDirty()) { |
| const QMatrix4x4 m = renderState.combinedMatrix(); |
| memcpy(buf->data(), m.constData(), 64); |
| } |
| |
| if (renderState.isOpacityDirty()) { |
| const float opacity = renderState.opacity(); |
| memcpy(buf->data() + 64, &opacity, 4); |
| } |
| |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| |
| float entry = float(state->entry); |
| memcpy(buf->data() + 68, &entry, 4); |
| |
| float timestamp = float(state->timestamp); |
| memcpy(buf->data() + 72, ×tamp, 4); |
| |
| return true; |
| } |
| |
| void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture, |
| QSGMaterial *newMaterial, QSGMaterial *) override |
| { |
| ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state(); |
| if (binding == 1) { |
| state->texture->updateRhiTexture(renderState.rhi(), renderState.resourceUpdateBatch()); |
| *texture = state->texture; |
| } |
| } |
| }; |
| |
| class SimpleMaterial : public ImageMaterial |
| { |
| public: |
| SimpleMaterial() { setFlag(SupportsRhiShader, true); } |
| QSGMaterialShader *createShader() const override { |
| if (flags().testFlag(RhiShaderWanted)) |
| return new SimpleMaterialRhiShader; |
| else |
| return new SimpleMaterialShader; |
| } |
| QSGMaterialType *type() const override { return &m_type; } |
| |
| ImageMaterialData *state() override { return &m_state; } |
| |
| private: |
| static QSGMaterialType m_type; |
| ImageMaterialData m_state; |
| }; |
| |
| QSGMaterialType SimpleMaterial::m_type; |
| |
| void fillUniformArrayFromImage(float* array, const QImage& img, int size) |
| { |
| if (img.isNull()){ |
| for (int i=0; i<size; i++) |
| array[i] = 1.0; |
| return; |
| } |
| QImage scaled = img.scaled(size,1); |
| for (int i=0; i<size; i++) |
| array[i] = qAlpha(scaled.pixel(i,0))/255.0; |
| } |
| |
| /*! |
| \qmltype ImageParticle |
| \instantiates QQuickImageParticle |
| \inqmlmodule QtQuick.Particles |
| \inherits ParticlePainter |
| \brief For visualizing logical particles using an image. |
| \ingroup qtquick-particles |
| |
| This element renders a logical particle as an image. The image can be |
| \list |
| \li colorized |
| \li rotated |
| \li deformed |
| \li a sprite-based animation |
| \endlist |
| |
| ImageParticles implictly share data on particles if multiple ImageParticles are painting |
| the same logical particle group. This is broken down along the four capabilities listed |
| above. So if one ImageParticle defines data for rendering the particles in one of those |
| capabilities, and the other does not, then both will draw the particles the same in that |
| aspect automatically. This is primarily useful when there is some random variation on |
| the particle which is supposed to stay with it when switching painters. If both ImageParticles |
| define how they should appear for that aspect, they diverge and each appears as it is defined. |
| |
| This sharing of data happens behind the scenes based off of whether properties were implicitly or explicitly |
| set. One drawback of the current implementation is that it is only possible to reset the capabilities as a whole. |
| So if you explicitly set an attribute affecting color, such as redVariation, and then reset it (by setting redVariation |
| to undefined), all color data will be reset and it will begin to have an implicit value of any shared color from |
| other ImageParticles. |
| |
| \note The maximum number of image particles is limited to 16383. |
| */ |
| /*! |
| \qmlproperty url QtQuick.Particles::ImageParticle::source |
| |
| The source image to be used. |
| |
| If the image is a sprite animation, use the sprite property instead. |
| |
| Since Qt 5.2, some default images are provided as resources to aid prototyping: |
| \table |
| \row |
| \li qrc:///particleresources/star.png |
| \li \inlineimage particles/star.png |
| \row |
| \li qrc:///particleresources/glowdot.png |
| \li \inlineimage particles/glowdot.png |
| \row |
| \li qrc:///particleresources/fuzzydot.png |
| \li \inlineimage particles/fuzzydot.png |
| \endtable |
| |
| Note that the images are white and semi-transparent, to allow colorization |
| and alpha levels to have maximum effect. |
| */ |
| /*! |
| \qmlproperty list<Sprite> QtQuick.Particles::ImageParticle::sprites |
| |
| The sprite or sprites used to draw this particle. |
| |
| Note that the sprite image will be scaled to a square based on the size of |
| the particle being rendered. |
| |
| For full details, see the \l{Sprite Animations} overview. |
| */ |
| /*! |
| \qmlproperty url QtQuick.Particles::ImageParticle::colorTable |
| |
| An image whose color will be used as a 1D texture to determine color over life. E.g. when |
| the particle is halfway through its lifetime, it will have the color specified halfway |
| across the image. |
| |
| This color is blended with the color property and the color of the source image. |
| */ |
| /*! |
| \qmlproperty url QtQuick.Particles::ImageParticle::sizeTable |
| |
| An image whose opacity will be used as a 1D texture to determine size over life. |
| |
| This property is expected to be removed shortly, in favor of custom easing curves to determine size over life. |
| */ |
| /*! |
| \qmlproperty url QtQuick.Particles::ImageParticle::opacityTable |
| |
| An image whose opacity will be used as a 1D texture to determine size over life. |
| |
| This property is expected to be removed shortly, in favor of custom easing curves to determine opacity over life. |
| */ |
| /*! |
| \qmlproperty color QtQuick.Particles::ImageParticle::color |
| |
| If a color is specified, the provided image will be colorized with it. |
| |
| Default is white (no change). |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::colorVariation |
| |
| This number represents the color variation applied to individual particles. |
| Setting colorVariation is the same as setting redVariation, greenVariation, |
| and blueVariation to the same number. |
| |
| Each channel can vary between particle by up to colorVariation from its usual color. |
| |
| Color is measured, per channel, from 0.0 to 1.0. |
| |
| Default is 0.0 |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::redVariation |
| The variation in the red color channel between particles. |
| |
| Color is measured, per channel, from 0.0 to 1.0. |
| |
| Default is 0.0 |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::greenVariation |
| The variation in the green color channel between particles. |
| |
| Color is measured, per channel, from 0.0 to 1.0. |
| |
| Default is 0.0 |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::blueVariation |
| The variation in the blue color channel between particles. |
| |
| Color is measured, per channel, from 0.0 to 1.0. |
| |
| Default is 0.0 |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::alpha |
| An alpha to be applied to the image. This value is multiplied by the value in |
| the image, and the value in the color property. |
| |
| Particles have additive blending, so lower alpha on single particles leads |
| to stronger effects when multiple particles overlap. |
| |
| Alpha is measured from 0.0 to 1.0. |
| |
| Default is 1.0 |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::alphaVariation |
| The variation in the alpha channel between particles. |
| |
| Alpha is measured from 0.0 to 1.0. |
| |
| Default is 0.0 |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::rotation |
| |
| If set the image will be rotated by this many degrees before it is drawn. |
| |
| The particle coordinates are not transformed. |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::rotationVariation |
| |
| If set the rotation of individual particles will vary by up to this much |
| between particles. |
| |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::rotationVelocity |
| |
| If set particles will rotate at this velocity in degrees/second. |
| */ |
| /*! |
| \qmlproperty real QtQuick.Particles::ImageParticle::rotationVelocityVariation |
| |
| If set the rotationVelocity of individual particles will vary by up to this much |
| between particles. |
| |
| */ |
| /*! |
| \qmlproperty bool QtQuick.Particles::ImageParticle::autoRotation |
| |
| If set to true then a rotation will be applied on top of the particles rotation, so |
| that it faces the direction of travel. So to face away from the direction of travel, |
| set autoRotation to true and rotation to 180. |
| |
| Default is false |
| */ |
| /*! |
| \qmlproperty StochasticDirection QtQuick.Particles::ImageParticle::xVector |
| |
| Allows you to deform the particle image when drawn. The rectangular image will |
| be deformed so that the horizontal sides are in the shape of this vector instead |
| of (1,0). |
| */ |
| /*! |
| \qmlproperty StochasticDirection QtQuick.Particles::ImageParticle::yVector |
| |
| Allows you to deform the particle image when drawn. The rectangular image will |
| be deformed so that the vertical sides are in the shape of this vector instead |
| of (0,1). |
| */ |
| /*! |
| \qmlproperty EntryEffect QtQuick.Particles::ImageParticle::entryEffect |
| |
| This property provides basic and cheap entrance and exit effects for the particles. |
| For fine-grained control, see sizeTable and opacityTable. |
| |
| Acceptable values are |
| \list |
| \li ImageParticle.None: Particles just appear and disappear. |
| \li ImageParticle.Fade: Particles fade in from 0 opacity at the start of their life, and fade out to 0 at the end. |
| \li ImageParticle.Scale: Particles scale in from 0 size at the start of their life, and scale back to 0 at the end. |
| \endlist |
| |
| Default value is Fade. |
| */ |
| /*! |
| \qmlproperty bool QtQuick.Particles::ImageParticle::spritesInterpolate |
| |
| If set to true, sprite particles will interpolate between sprite frames each rendered frame, making |
| the sprites look smoother. |
| |
| Default is true. |
| */ |
| |
| /*! |
| \qmlproperty Status QtQuick.Particles::ImageParticle::status |
| |
| The status of loading the image. |
| */ |
| |
| |
| QQuickImageParticle::QQuickImageParticle(QQuickItem* parent) |
| : QQuickParticlePainter(parent) |
| , m_color_variation(0.0) |
| , m_outgoingNode(nullptr) |
| , m_material(nullptr) |
| , m_alphaVariation(0.0) |
| , m_alpha(1.0) |
| , m_redVariation(0.0) |
| , m_greenVariation(0.0) |
| , m_blueVariation(0.0) |
| , m_rotation(0) |
| , m_rotationVariation(0) |
| , m_rotationVelocity(0) |
| , m_rotationVelocityVariation(0) |
| , m_autoRotation(false) |
| , m_xVector(nullptr) |
| , m_yVector(nullptr) |
| , m_spriteEngine(nullptr) |
| , m_spritesInterpolate(true) |
| , m_explicitColor(false) |
| , m_explicitRotation(false) |
| , m_explicitDeformation(false) |
| , m_explicitAnimation(false) |
| , m_bypassOptimizations(false) |
| , perfLevel(Unknown) |
| , m_lastLevel(Unknown) |
| , m_debugMode(false) |
| , m_entryEffect(Fade) |
| , m_startedImageLoading(0) |
| , m_rhi(nullptr) |
| , m_apiChecked(false) |
| { |
| setFlag(ItemHasContents); |
| } |
| |
| QQuickImageParticle::~QQuickImageParticle() |
| { |
| clearShadows(); |
| } |
| |
| QQmlListProperty<QQuickSprite> QQuickImageParticle::sprites() |
| { |
| return QQmlListProperty<QQuickSprite>(this, &m_sprites, spriteAppend, spriteCount, spriteAt, spriteClear); |
| } |
| |
| void QQuickImageParticle::sceneGraphInvalidated() |
| { |
| m_nodes.clear(); |
| m_material = nullptr; |
| delete m_outgoingNode; |
| m_outgoingNode = nullptr; |
| } |
| |
| void QQuickImageParticle::setImage(const QUrl &image) |
| { |
| if (image.isEmpty()){ |
| if (m_image) { |
| m_image.reset(); |
| emit imageChanged(); |
| } |
| return; |
| } |
| |
| if (!m_image) |
| m_image.reset(new ImageData); |
| if (image == m_image->source) |
| return; |
| m_image->source = image; |
| emit imageChanged(); |
| reset(); |
| } |
| |
| |
| void QQuickImageParticle::setColortable(const QUrl &table) |
| { |
| if (table.isEmpty()){ |
| if (m_colorTable) { |
| m_colorTable.reset(); |
| emit colortableChanged(); |
| } |
| return; |
| } |
| |
| if (!m_colorTable) |
| m_colorTable.reset(new ImageData); |
| if (table == m_colorTable->source) |
| return; |
| m_colorTable->source = table; |
| emit colortableChanged(); |
| reset(); |
| } |
| |
| void QQuickImageParticle::setSizetable(const QUrl &table) |
| { |
| if (table.isEmpty()){ |
| if (m_sizeTable) { |
| m_sizeTable.reset(); |
| emit sizetableChanged(); |
| } |
| return; |
| } |
| |
| if (!m_sizeTable) |
| m_sizeTable.reset(new ImageData); |
| if (table == m_sizeTable->source) |
| return; |
| m_sizeTable->source = table; |
| emit sizetableChanged(); |
| reset(); |
| } |
| |
| void QQuickImageParticle::setOpacitytable(const QUrl &table) |
| { |
| if (table.isEmpty()){ |
| if (m_opacityTable) { |
| m_opacityTable.reset(); |
| emit opacitytableChanged(); |
| } |
| return; |
| } |
| |
| if (!m_opacityTable) |
| m_opacityTable.reset(new ImageData); |
| if (table == m_opacityTable->source) |
| return; |
| m_opacityTable->source = table; |
| emit opacitytableChanged(); |
| reset(); |
| } |
| |
| void QQuickImageParticle::setColor(const QColor &color) |
| { |
| if (color == m_color) |
| return; |
| m_color = color; |
| emit colorChanged(); |
| m_explicitColor = true; |
| if (perfLevel < Colored) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setColorVariation(qreal var) |
| { |
| if (var == m_color_variation) |
| return; |
| m_color_variation = var; |
| emit colorVariationChanged(); |
| m_explicitColor = true; |
| if (perfLevel < Colored) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setAlphaVariation(qreal arg) |
| { |
| if (m_alphaVariation != arg) { |
| m_alphaVariation = arg; |
| emit alphaVariationChanged(arg); |
| } |
| m_explicitColor = true; |
| if (perfLevel < Colored) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setAlpha(qreal arg) |
| { |
| if (m_alpha != arg) { |
| m_alpha = arg; |
| emit alphaChanged(arg); |
| } |
| m_explicitColor = true; |
| if (perfLevel < Colored) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setRedVariation(qreal arg) |
| { |
| if (m_redVariation != arg) { |
| m_redVariation = arg; |
| emit redVariationChanged(arg); |
| } |
| m_explicitColor = true; |
| if (perfLevel < Colored) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setGreenVariation(qreal arg) |
| { |
| if (m_greenVariation != arg) { |
| m_greenVariation = arg; |
| emit greenVariationChanged(arg); |
| } |
| m_explicitColor = true; |
| if (perfLevel < Colored) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setBlueVariation(qreal arg) |
| { |
| if (m_blueVariation != arg) { |
| m_blueVariation = arg; |
| emit blueVariationChanged(arg); |
| } |
| m_explicitColor = true; |
| if (perfLevel < Colored) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setRotation(qreal arg) |
| { |
| if (m_rotation != arg) { |
| m_rotation = arg; |
| emit rotationChanged(arg); |
| } |
| m_explicitRotation = true; |
| if (perfLevel < Deformable) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setRotationVariation(qreal arg) |
| { |
| if (m_rotationVariation != arg) { |
| m_rotationVariation = arg; |
| emit rotationVariationChanged(arg); |
| } |
| m_explicitRotation = true; |
| if (perfLevel < Deformable) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setRotationVelocity(qreal arg) |
| { |
| if (m_rotationVelocity != arg) { |
| m_rotationVelocity = arg; |
| emit rotationVelocityChanged(arg); |
| } |
| m_explicitRotation = true; |
| if (perfLevel < Deformable) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setRotationVelocityVariation(qreal arg) |
| { |
| if (m_rotationVelocityVariation != arg) { |
| m_rotationVelocityVariation = arg; |
| emit rotationVelocityVariationChanged(arg); |
| } |
| m_explicitRotation = true; |
| if (perfLevel < Deformable) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setAutoRotation(bool arg) |
| { |
| if (m_autoRotation != arg) { |
| m_autoRotation = arg; |
| emit autoRotationChanged(arg); |
| } |
| m_explicitRotation = true; |
| if (perfLevel < Deformable) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setXVector(QQuickDirection* arg) |
| { |
| if (m_xVector != arg) { |
| m_xVector = arg; |
| emit xVectorChanged(arg); |
| } |
| m_explicitDeformation = true; |
| if (perfLevel < Deformable) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setYVector(QQuickDirection* arg) |
| { |
| if (m_yVector != arg) { |
| m_yVector = arg; |
| emit yVectorChanged(arg); |
| } |
| m_explicitDeformation = true; |
| if (perfLevel < Deformable) |
| reset(); |
| } |
| |
| void QQuickImageParticle::setSpritesInterpolate(bool arg) |
| { |
| if (m_spritesInterpolate != arg) { |
| m_spritesInterpolate = arg; |
| emit spritesInterpolateChanged(arg); |
| } |
| } |
| |
| void QQuickImageParticle::setBypassOptimizations(bool arg) |
| { |
| if (m_bypassOptimizations != arg) { |
| m_bypassOptimizations = arg; |
| emit bypassOptimizationsChanged(arg); |
| } |
| // Applies regardless of perfLevel |
| reset(); |
| } |
| |
| void QQuickImageParticle::setEntryEffect(EntryEffect arg) |
| { |
| if (m_entryEffect != arg) { |
| m_entryEffect = arg; |
| if (m_material) |
| getState(m_material)->entry = (qreal) m_entryEffect; |
| emit entryEffectChanged(arg); |
| } |
| } |
| |
| void QQuickImageParticle::resetColor() |
| { |
| m_explicitColor = false; |
| for (auto groupId : groupIds()) { |
| for (QQuickParticleData* d : qAsConst(m_system->groupData[groupId]->data)) { |
| if (d->colorOwner == this) { |
| d->colorOwner = nullptr; |
| } |
| } |
| } |
| m_color = QColor(); |
| m_color_variation = 0.0f; |
| m_redVariation = 0.0f; |
| m_blueVariation = 0.0f; |
| m_greenVariation = 0.0f; |
| m_alpha = 1.0f; |
| m_alphaVariation = 0.0f; |
| } |
| |
| void QQuickImageParticle::resetRotation() |
| { |
| m_explicitRotation = false; |
| for (auto groupId : groupIds()) { |
| for (QQuickParticleData* d : qAsConst(m_system->groupData[groupId]->data)) { |
| if (d->rotationOwner == this) { |
| d->rotationOwner = nullptr; |
| } |
| } |
| } |
| m_rotation = 0; |
| m_rotationVariation = 0; |
| m_rotationVelocity = 0; |
| m_rotationVelocityVariation = 0; |
| m_autoRotation = false; |
| } |
| |
| void QQuickImageParticle::resetDeformation() |
| { |
| m_explicitDeformation = false; |
| for (auto groupId : groupIds()) { |
| for (QQuickParticleData* d : qAsConst(m_system->groupData[groupId]->data)) { |
| if (d->deformationOwner == this) { |
| d->deformationOwner = nullptr; |
| } |
| } |
| } |
| if (m_xVector) |
| delete m_xVector; |
| if (m_yVector) |
| delete m_yVector; |
| m_xVector = nullptr; |
| m_yVector = nullptr; |
| } |
| |
| void QQuickImageParticle::reset() |
| { |
| QQuickParticlePainter::reset(); |
| m_pleaseReset = true; |
| update(); |
| } |
| |
| void QQuickImageParticle::createEngine() |
| { |
| if (m_spriteEngine) |
| delete m_spriteEngine; |
| if (m_sprites.count()) { |
| m_spriteEngine = new QQuickSpriteEngine(m_sprites, this); |
| connect(m_spriteEngine, SIGNAL(stateChanged(int)), |
| this, SLOT(spriteAdvance(int)), Qt::DirectConnection); |
| m_explicitAnimation = true; |
| } else { |
| m_spriteEngine = nullptr; |
| m_explicitAnimation = false; |
| } |
| reset(); |
| } |
| |
| static QSGGeometry::Attribute SimpleParticle_Attributes[] = { |
| QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true), // Position |
| QSGGeometry::Attribute::create(1, 4, GL_FLOAT), // Data |
| QSGGeometry::Attribute::create(2, 4, GL_FLOAT) // Vectors |
| }; |
| |
| static QSGGeometry::AttributeSet SimpleParticle_AttributeSet = |
| { |
| 3, // Attribute Count |
| ( 2 + 4 + 4 ) * sizeof(float), |
| SimpleParticle_Attributes |
| }; |
| |
| static QSGGeometry::Attribute ColoredParticle_Attributes[] = { |
| QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true), // Position |
| QSGGeometry::Attribute::create(1, 4, GL_FLOAT), // Data |
| QSGGeometry::Attribute::create(2, 4, GL_FLOAT), // Vectors |
| QSGGeometry::Attribute::create(3, 4, GL_UNSIGNED_BYTE), // Colors |
| }; |
| |
| static QSGGeometry::AttributeSet ColoredParticle_AttributeSet = |
| { |
| 4, // Attribute Count |
| ( 2 + 4 + 4 ) * sizeof(float) + 4 * sizeof(uchar), |
| ColoredParticle_Attributes |
| }; |
| |
| static QSGGeometry::Attribute DeformableParticle_Attributes[] = { |
| QSGGeometry::Attribute::create(0, 4, GL_FLOAT), // Position & TexCoord |
| QSGGeometry::Attribute::create(1, 4, GL_FLOAT), // Data |
| QSGGeometry::Attribute::create(2, 4, GL_FLOAT), // Vectors |
| QSGGeometry::Attribute::create(3, 4, GL_UNSIGNED_BYTE), // Colors |
| QSGGeometry::Attribute::create(4, 4, GL_FLOAT), // DeformationVectors |
| QSGGeometry::Attribute::create(5, 3, GL_FLOAT), // Rotation |
| }; |
| |
| static QSGGeometry::AttributeSet DeformableParticle_AttributeSet = |
| { |
| 6, // Attribute Count |
| (4 + 4 + 4 + 4 + 3) * sizeof(float) + 4 * sizeof(uchar), |
| DeformableParticle_Attributes |
| }; |
| |
| static QSGGeometry::Attribute SpriteParticle_Attributes[] = { |
| QSGGeometry::Attribute::create(0, 4, GL_FLOAT), // Position & TexCoord |
| QSGGeometry::Attribute::create(1, 4, GL_FLOAT), // Data |
| QSGGeometry::Attribute::create(2, 4, GL_FLOAT), // Vectors |
| QSGGeometry::Attribute::create(3, 4, GL_UNSIGNED_BYTE), // Colors |
| QSGGeometry::Attribute::create(4, 4, GL_FLOAT), // DeformationVectors |
| QSGGeometry::Attribute::create(5, 3, GL_FLOAT), // Rotation |
| QSGGeometry::Attribute::create(6, 3, GL_FLOAT), // Anim Data |
| QSGGeometry::Attribute::create(7, 4, GL_FLOAT) // Anim Pos |
| }; |
| |
| static QSGGeometry::AttributeSet SpriteParticle_AttributeSet = |
| { |
| 8, // Attribute Count |
| (4 + 4 + 4 + 4 + 3 + 3 + 4) * sizeof(float) + 4 * sizeof(uchar), |
| SpriteParticle_Attributes |
| }; |
| |
| void QQuickImageParticle::clearShadows() |
| { |
| foreach (const QVector<QQuickParticleData*> data, m_shadowData) |
| qDeleteAll(data); |
| m_shadowData.clear(); |
| } |
| |
| //Only call if you need to, may initialize the whole array first time |
| QQuickParticleData* QQuickImageParticle::getShadowDatum(QQuickParticleData* datum) |
| { |
| //Will return datum if the datum is a sentinel or uninitialized, to centralize that one check |
| if (datum->systemIndex == -1) |
| return datum; |
| QQuickParticleGroupData* gd = m_system->groupData[datum->groupId]; |
| if (!m_shadowData.contains(datum->groupId)) { |
| QVector<QQuickParticleData*> data; |
| const int gdSize = gd->size(); |
| data.reserve(gdSize); |
| for (int i = 0; i < gdSize; i++) { |
| QQuickParticleData* datum = new QQuickParticleData; |
| *datum = *(gd->data[i]); |
| data << datum; |
| } |
| m_shadowData.insert(datum->groupId, data); |
| } |
| //### If dynamic resize is added, remember to potentially resize the shadow data on out-of-bounds access request |
| |
| return m_shadowData[datum->groupId][datum->index]; |
| } |
| |
| bool QQuickImageParticle::loadingSomething() |
| { |
| return (m_image && m_image->pix.isLoading()) |
| || (m_colorTable && m_colorTable->pix.isLoading()) |
| || (m_sizeTable && m_sizeTable->pix.isLoading()) |
| || (m_opacityTable && m_opacityTable->pix.isLoading()) |
| || (m_spriteEngine && m_spriteEngine->isLoading()); |
| } |
| |
| void QQuickImageParticle::mainThreadFetchImageData() |
| { |
| if (m_image) {//ImageData created on setSource |
| m_image->pix.clear(this); |
| m_image->pix.load(qmlEngine(this), m_image->source); |
| } |
| |
| if (m_spriteEngine) |
| m_spriteEngine->startAssemblingImage(); |
| |
| if (m_colorTable) |
| m_colorTable->pix.load(qmlEngine(this), m_colorTable->source); |
| |
| if (m_sizeTable) |
| m_sizeTable->pix.load(qmlEngine(this), m_sizeTable->source); |
| |
| if (m_opacityTable) |
| m_opacityTable->pix.load(qmlEngine(this), m_opacityTable->source); |
| |
| m_startedImageLoading = 2; |
| } |
| |
| void QQuickImageParticle::buildParticleNodes(QSGNode** passThrough) |
| { |
| // Starts async parts, like loading images, on gui thread |
| // Not on individual properties, because we delay until system is running |
| if (*passThrough || loadingSomething()) |
| return; |
| |
| if (m_startedImageLoading == 0) { |
| m_startedImageLoading = 1; |
| //stage 1 is in gui thread |
| QQuickImageParticle::staticMetaObject.invokeMethod(this, "mainThreadFetchImageData", Qt::QueuedConnection); |
| } else if (m_startedImageLoading == 2) { |
| finishBuildParticleNodes(passThrough); //rest happens in render thread |
| } |
| |
| //No mutex, because it's slow and a compare that fails due to a race condition means just a dropped frame |
| } |
| |
| void QQuickImageParticle::finishBuildParticleNodes(QSGNode** node) |
| { |
| if (!m_rhi && !QOpenGLContext::currentContext()) |
| return; |
| |
| if (m_count * 4 > 0xffff) { |
| // Index data is ushort. |
| qmlInfo(this) << "ImageParticle: Too many particles - maximum 16383 per ImageParticle"; |
| return; |
| } |
| |
| if (count() <= 0) |
| return; |
| |
| m_debugMode = m_system->m_debugMode; |
| |
| if (m_sprites.count() || m_bypassOptimizations) { |
| perfLevel = Sprites; |
| } else if (m_colorTable || m_sizeTable || m_opacityTable) { |
| perfLevel = Tabled; |
| } else if (m_autoRotation || m_rotation || m_rotationVariation |
| || m_rotationVelocity || m_rotationVelocityVariation |
| || m_xVector || m_yVector) { |
| perfLevel = Deformable; |
| } else if (m_alphaVariation || m_alpha != 1.0 || m_color.isValid() || m_color_variation |
| || m_redVariation || m_blueVariation || m_greenVariation) { |
| perfLevel = Colored; |
| } else { |
| perfLevel = Simple; |
| } |
| |
| for (auto groupId : groupIds()) { |
| //For sharing higher levels, need to have highest used so it renders |
| for (QQuickParticlePainter* p : qAsConst(m_system->groupData[groupId]->painters)) { |
| QQuickImageParticle* other = qobject_cast<QQuickImageParticle*>(p); |
| if (other){ |
| if (other->perfLevel > perfLevel) { |
| if (other->perfLevel >= Tabled){//Deformable is the highest level needed for this, anything higher isn't shared (or requires your own sprite) |
| if (perfLevel < Deformable) |
| perfLevel = Deformable; |
| } else { |
| perfLevel = other->perfLevel; |
| } |
| } else if (other->perfLevel < perfLevel) { |
| other->reset(); |
| } |
| } |
| } |
| } |
| |
| if (!m_rhi) { // the RHI may be backed by GL but these checks should be obsolete in any case |
| #ifdef Q_OS_WIN |
| if (perfLevel < Deformable) //QTBUG-24540 , point sprite 'extension' isn't working on windows. |
| perfLevel = Deformable; |
| #endif |
| |
| #ifdef Q_OS_MAC |
| // macOS 10.8.3 introduced a bug in the AMD drivers, for at least the 2011 macbook pros, |
| // causing point sprites who read gl_PointCoord in the frag shader to come out as |
| // green-red blobs. |
| const GLubyte *glVendor = QOpenGLContext::currentContext()->functions()->glGetString(GL_VENDOR); |
| if (perfLevel < Deformable && glVendor && strstr((char *) glVendor, "ATI")) { |
| perfLevel = Deformable; |
| } |
| #endif |
| |
| #ifdef Q_OS_LINUX |
| // Nouveau drivers can potentially freeze a machine entirely when taking the point-sprite path. |
| const GLubyte *glVendor = QOpenGLContext::currentContext()->functions()->glGetString(GL_VENDOR); |
| if (perfLevel < Deformable && glVendor && strstr((const char *) glVendor, "nouveau")) |
| perfLevel = Deformable; |
| #endif |
| |
| } else { |
| // Points with a size other than 1 are an optional feature with QRhi |
| // because some of the underlying APIs have no support for this. |
| // Therefore, avoid the point sprite path with APIs like Direct3D. |
| if (perfLevel < Deformable && !m_rhi->isFeatureSupported(QRhi::VertexShaderPointSize)) |
| perfLevel = Deformable; |
| } |
| |
| if (perfLevel >= Colored && !m_color.isValid()) |
| m_color = QColor(Qt::white);//Hidden default, but different from unset |
| |
| clearShadows(); |
| if (m_material) |
| m_material = nullptr; |
| |
| //Setup material |
| QImage colortable; |
| QImage sizetable; |
| QImage opacitytable; |
| QImage image; |
| bool imageLoaded = false; |
| switch (perfLevel) {//Fallthrough intended |
| case Sprites: |
| { |
| if (!m_spriteEngine) { |
| qWarning() << "ImageParticle: No sprite engine..."; |
| //Sprite performance mode with static image is supported, but not advised |
| //Note that in this case it always uses shadow data |
| } else { |
| image = m_spriteEngine->assembledImage(); |
| if (image.isNull())//Warning is printed in engine |
| return; |
| imageLoaded = true; |
| } |
| m_material = new SpriteMaterial; |
| ImageMaterialData *state = getState(m_material); |
| if (imageLoaded) |
| state->texture = QSGPlainTexture::fromImage(image); |
| state->animSheetSize = QSizeF(image.size() / image.devicePixelRatioF()); |
| if (m_spriteEngine) |
| m_spriteEngine->setCount(m_count); |
| } |
| Q_FALLTHROUGH(); |
| case Tabled: |
| { |
| if (!m_material) |
| m_material = new TabledMaterial; |
| |
| if (m_colorTable) { |
| if (m_colorTable->pix.isReady()) |
| colortable = m_colorTable->pix.image(); |
| else |
| qmlWarning(this) << "Error loading color table: " << m_colorTable->pix.error(); |
| } |
| |
| if (m_sizeTable) { |
| if (m_sizeTable->pix.isReady()) |
| sizetable = m_sizeTable->pix.image(); |
| else |
| qmlWarning(this) << "Error loading size table: " << m_sizeTable->pix.error(); |
| } |
| |
| if (m_opacityTable) { |
| if (m_opacityTable->pix.isReady()) |
| opacitytable = m_opacityTable->pix.image(); |
| else |
| qmlWarning(this) << "Error loading opacity table: " << m_opacityTable->pix.error(); |
| } |
| |
| if (colortable.isNull()){//###Goes through image just for this |
| colortable = QImage(1,1,QImage::Format_ARGB32_Premultiplied); |
| colortable.fill(Qt::white); |
| } |
| ImageMaterialData *state = getState(m_material); |
| state->colorTable = QSGPlainTexture::fromImage(colortable); |
| fillUniformArrayFromImage(state->sizeTable, sizetable, UNIFORM_ARRAY_SIZE); |
| fillUniformArrayFromImage(state->opacityTable, opacitytable, UNIFORM_ARRAY_SIZE); |
| } |
| Q_FALLTHROUGH(); |
| case Deformable: |
| { |
| if (!m_material) |
| m_material = new DeformableMaterial; |
| } |
| Q_FALLTHROUGH(); |
| case Colored: |
| { |
| if (!m_material) |
| m_material = new ColoredMaterial; |
| } |
| Q_FALLTHROUGH(); |
| default://Also Simple |
| { |
| if (!m_material) |
| m_material = new SimpleMaterial; |
| ImageMaterialData *state = getState(m_material); |
| if (!imageLoaded) { |
| if (!m_image || !m_image->pix.isReady()) { |
| if (m_image) |
| qmlWarning(this) << m_image->pix.error(); |
| delete m_material; |
| return; |
| } |
| //state->texture //TODO: Shouldn't this be better? But not crash? |
| // = QQuickItemPrivate::get(this)->sceneGraphContext()->textureForFactory(m_imagePix.textureFactory()); |
| state->texture = QSGPlainTexture::fromImage(m_image->pix.image()); |
| } |
| state->texture->setFiltering(QSGTexture::Linear); |
| state->entry = (qreal) m_entryEffect; |
| m_material->setFlag(QSGMaterial::Blending | QSGMaterial::RequiresFullMatrix); |
| } |
| } |
| |
| m_nodes.clear(); |
| for (auto groupId : groupIds()) { |
| int count = m_system->groupData[groupId]->size(); |
| QSGGeometryNode* node = new QSGGeometryNode(); |
| node->setMaterial(m_material); |
| node->markDirty(QSGNode::DirtyMaterial); |
| |
| m_nodes.insert(groupId, node); |
| m_idxStarts.insert(groupId, m_lastIdxStart); |
| m_startsIdx.append(qMakePair<int,int>(m_lastIdxStart, groupId)); |
| m_lastIdxStart += count; |
| |
| //Create Particle Geometry |
| int vCount = count * 4; |
| int iCount = count * 6; |
| |
| QSGGeometry *g; |
| if (perfLevel == Sprites) |
| g = new QSGGeometry(SpriteParticle_AttributeSet, vCount, iCount); |
| else if (perfLevel == Tabled) |
| g = new QSGGeometry(DeformableParticle_AttributeSet, vCount, iCount); |
| else if (perfLevel == Deformable) |
| g = new QSGGeometry(DeformableParticle_AttributeSet, vCount, iCount); |
| else if (perfLevel == Colored) |
| g = new QSGGeometry(ColoredParticle_AttributeSet, count, 0); |
| else //Simple |
| g = new QSGGeometry(SimpleParticle_AttributeSet, count, 0); |
| |
| node->setFlag(QSGNode::OwnsGeometry); |
| node->setGeometry(g); |
| if (perfLevel <= Colored){ |
| g->setDrawingMode(QSGGeometry::DrawPoints); |
| if (m_debugMode) { |
| if (m_rhi) { |
| qDebug("Using point sprites"); |
| } else { |
| #if QT_CONFIG(opengl) |
| GLfloat pointSizeRange[2]; |
| QOpenGLContext::currentContext()->functions()->glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange); |
| qDebug() << "Using point sprites, GL_ALIASED_POINT_SIZE_RANGE " <<pointSizeRange[0] << ":" << pointSizeRange[1]; |
| #else |
| qDebug("Using point sprites"); |
| #endif |
| } |
| } |
| } else { |
| g->setDrawingMode(QSGGeometry::DrawTriangles); |
| } |
| |
| for (int p=0; p < count; ++p) |
| commit(groupId, p);//commit sets geometry for the node, has its own perfLevel switch |
| |
| if (perfLevel == Sprites) |
| initTexCoords<SpriteVertex>((SpriteVertex*)g->vertexData(), vCount); |
| else if (perfLevel == Tabled) |
| initTexCoords<DeformableVertex>((DeformableVertex*)g->vertexData(), vCount); |
| else if (perfLevel == Deformable) |
| initTexCoords<DeformableVertex>((DeformableVertex*)g->vertexData(), vCount); |
| |
| if (perfLevel > Colored){ |
| quint16 *indices = g->indexDataAsUShort(); |
| for (int i=0; i < count; ++i) { |
| int o = i * 4; |
| indices[0] = o; |
| indices[1] = o + 1; |
| indices[2] = o + 2; |
| indices[3] = o + 1; |
| indices[4] = o + 3; |
| indices[5] = o + 2; |
| indices += 6; |
| } |
| } |
| } |
| |
| if (perfLevel == Sprites) |
| spritesUpdate();//Gives all vertexes the initial sprite data, then maintained per frame |
| |
| foreach (QSGGeometryNode* node, m_nodes){ |
| if (node == *(m_nodes.begin())) |
| node->setFlag(QSGGeometryNode::OwnsMaterial);//Root node owns the material for memory management purposes |
| else |
| (*(m_nodes.begin()))->appendChildNode(node); |
| } |
| |
| *node = *(m_nodes.begin()); |
| update(); |
| } |
| |
| QSGNode *QQuickImageParticle::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) |
| { |
| if (!m_apiChecked || m_windowChanged) { |
| m_apiChecked = true; |
| m_windowChanged = false; |
| |
| QSGRenderContext *rc = QQuickItemPrivate::get(this)->sceneGraphRenderContext(); |
| QSGRendererInterface *rif = rc->sceneGraphContext()->rendererInterface(rc); |
| if (!rif) |
| return nullptr; |
| |
| QSGRendererInterface::GraphicsApi api = rif->graphicsApi(); |
| const bool isDirectOpenGL = api == QSGRendererInterface::OpenGL; |
| const bool isRhi = QSGRendererInterface::isApiRhiBased(api); |
| |
| if (!node && !isDirectOpenGL && !isRhi) |
| return nullptr; |
| |
| if (isRhi) |
| m_rhi = static_cast<QRhi *>(rif->getResource(m_window, QSGRendererInterface::RhiResource)); |
| else |
| m_rhi = nullptr; |
| |
| if (isRhi && !m_rhi) { |
| qWarning("Failed to query QRhi, particles disabled"); |
| return nullptr; |
| } |
| } |
| |
| if (m_pleaseReset){ |
| // Cannot just destroy the node and then return null (in case image |
| // loading is still in progress). Rather, keep track of the old node |
| // until we have a new one. |
| delete m_outgoingNode; |
| m_outgoingNode = node; |
| node = nullptr; |
| |
| m_lastLevel = perfLevel; |
| m_nodes.clear(); |
| |
| m_idxStarts.clear(); |
| m_startsIdx.clear(); |
| m_lastIdxStart = 0; |
| |
| m_material = nullptr; |
| |
| m_pleaseReset = false; |
| m_startedImageLoading = 0;//Cancel a part-way build (may still have a pending load) |
| } else if (!m_material) { |
| delete node; |
| node = nullptr; |
| } |
| |
| if (m_system && m_system->isRunning() && !m_system->isPaused()){ |
| prepareNextFrame(&node); |
| if (node) { |
| update(); |
| foreach (QSGGeometryNode* n, m_nodes) |
| n->markDirty(QSGNode::DirtyGeometry); |
| } else if (m_startedImageLoading < 2) { |
| update();//To call prepareNextFrame() again from the renderThread |
| } |
| } |
| |
| if (!node) { |
| node = m_outgoingNode; |
| m_outgoingNode = nullptr; |
| } |
| |
| return node; |
| } |
| |
| void QQuickImageParticle::prepareNextFrame(QSGNode **node) |
| { |
| if (*node == nullptr){//TODO: Staggered loading (as emitted) |
| buildParticleNodes(node); |
| if (m_debugMode) { |
| qDebug() << "QQuickImageParticle Feature level: " << perfLevel; |
| qDebug() << "QQuickImageParticle Nodes: "; |
| int count = 0; |
| for (auto it = m_nodes.keyBegin(), end = m_nodes.keyEnd(); it != end; ++it) { |
| qDebug() << "Group " << *it << " (" << m_system->groupData[*it]->size() |
| << " particles)"; |
| count += m_system->groupData[*it]->size(); |
| } |
| qDebug() << "Total count: " << count; |
| } |
| if (*node == nullptr) |
| return; |
| } |
| qint64 timeStamp = m_system->systemSync(this); |
| |
| qreal time = timeStamp / 1000.; |
| |
| switch (perfLevel){//Fall-through intended |
| case Sprites: |
| //Advance State |
| if (m_spriteEngine) |
| m_spriteEngine->updateSprites(timeStamp);//fires signals if anim changed |
| spritesUpdate(time); |
| Q_FALLTHROUGH(); |
| case Tabled: |
| case Deformable: |
| case Colored: |
| case Simple: |
| default: //Also Simple |
| getState(m_material)->timestamp = time; |
| break; |
| } |
| foreach (QSGGeometryNode* node, m_nodes) |
| node->markDirty(QSGNode::DirtyMaterial); |
| } |
| |
| void QQuickImageParticle::spritesUpdate(qreal time) |
| { |
| ImageMaterialData *state = getState(m_material); |
| // Sprite progression handled CPU side, so as to have per-frame control. |
| for (auto groupId : groupIds()) { |
| for (QQuickParticleData* mainDatum : qAsConst(m_system->groupData[groupId]->data)) { |
| QSGGeometryNode *node = m_nodes[groupId]; |
| if (!node) |
| continue; |
| //TODO: Interpolate between two different animations if it's going to transition next frame |
| // This is particularly important for cut-up sprites. |
| QQuickParticleData* datum = (mainDatum->animationOwner == this ? mainDatum : getShadowDatum(mainDatum)); |
| int spriteIdx = 0; |
| for (int i = 0; i<m_startsIdx.count(); i++) { |
| if (m_startsIdx[i].second == groupId){ |
| spriteIdx = m_startsIdx[i].first + datum->index; |
| break; |
| } |
| } |
| |
| double frameAt; |
| qreal progress = 0; |
| |
| if (datum->frameDuration > 0) { |
| qreal frame = (time - datum->animT)/(datum->frameDuration / 1000.0); |
| frame = qBound((qreal)0.0, frame, (qreal)((qreal)datum->frameCount - 1.0));//Stop at count-1 frames until we have between anim interpolation |
| if (m_spritesInterpolate) |
| progress = std::modf(frame,&frameAt); |
| else |
| std::modf(frame,&frameAt); |
| } else { |
| datum->frameAt++; |
| if (datum->frameAt >= datum->frameCount){ |
| datum->frameAt = 0; |
| m_spriteEngine->advance(spriteIdx); |
| } |
| frameAt = datum->frameAt; |
| } |
| if (m_spriteEngine->sprite(spriteIdx)->reverse())//### Store this in datum too? |
| frameAt = (datum->frameCount - 1) - frameAt; |
| QSizeF sheetSize = state->animSheetSize; |
| qreal y = datum->animY / sheetSize.height(); |
| qreal w = datum->animWidth / sheetSize.width(); |
| qreal h = datum->animHeight / sheetSize.height(); |
| qreal x1 = datum->animX / sheetSize.width(); |
| x1 += frameAt * w; |
| qreal x2 = x1; |
| if (frameAt < (datum->frameCount-1)) |
| x2 += w; |
| |
| SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData(); |
| spriteVertices += datum->index*4; |
| for (int i=0; i<4; i++) { |
| spriteVertices[i].animX1 = x1; |
| spriteVertices[i].animY1 = y; |
| spriteVertices[i].animX2 = x2; |
| spriteVertices[i].animY2 = y; |
| spriteVertices[i].animW = w; |
| spriteVertices[i].animH = h; |
| spriteVertices[i].animProgress = progress; |
| } |
| } |
| } |
| } |
| |
| void QQuickImageParticle::spriteAdvance(int spriteIdx) |
| { |
| if (!m_startsIdx.count())//Probably overly defensive |
| return; |
| |
| int gIdx = -1; |
| int i; |
| for (i = 0; i<m_startsIdx.count(); i++) { |
| if (spriteIdx < m_startsIdx[i].first) { |
| gIdx = m_startsIdx[i-1].second; |
| break; |
| } |
| } |
| if (gIdx == -1) |
| gIdx = m_startsIdx[i-1].second; |
| int pIdx = spriteIdx - m_startsIdx[i-1].first; |
| |
| QQuickParticleData* mainDatum = m_system->groupData[gIdx]->data[pIdx]; |
| QQuickParticleData* datum = (mainDatum->animationOwner == this ? mainDatum : getShadowDatum(mainDatum)); |
| |
| datum->animIdx = m_spriteEngine->spriteState(spriteIdx); |
| datum->animT = m_spriteEngine->spriteStart(spriteIdx)/1000.0; |
| datum->frameCount = m_spriteEngine->spriteFrames(spriteIdx); |
| datum->frameDuration = m_spriteEngine->spriteDuration(spriteIdx) / datum->frameCount; |
| datum->animX = m_spriteEngine->spriteX(spriteIdx); |
| datum->animY = m_spriteEngine->spriteY(spriteIdx); |
| datum->animWidth = m_spriteEngine->spriteWidth(spriteIdx); |
| datum->animHeight = m_spriteEngine->spriteHeight(spriteIdx); |
| } |
| |
| void QQuickImageParticle::reloadColor(const Color4ub &c, QQuickParticleData* d) |
| { |
| d->color = c; |
| //TODO: get index for reload - or make function take an index |
| } |
| |
| void QQuickImageParticle::initialize(int gIdx, int pIdx) |
| { |
| Color4ub color; |
| QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx]; |
| qreal redVariation = m_color_variation + m_redVariation; |
| qreal greenVariation = m_color_variation + m_greenVariation; |
| qreal blueVariation = m_color_variation + m_blueVariation; |
| int spriteIdx = 0; |
| if (m_spriteEngine) { |
| spriteIdx = m_idxStarts[gIdx] + datum->index; |
| if (spriteIdx >= m_spriteEngine->count()) |
| m_spriteEngine->setCount(spriteIdx+1); |
| } |
| |
| float rotation; |
| float rotationVelocity; |
| float autoRotate; |
| switch (perfLevel){//Fall-through is intended on all of them |
| case Sprites: |
| // Initial Sprite State |
| if (m_explicitAnimation && m_spriteEngine){ |
| if (!datum->animationOwner) |
| datum->animationOwner = this; |
| QQuickParticleData* writeTo = (datum->animationOwner == this ? datum : getShadowDatum(datum)); |
| writeTo->animT = writeTo->t; |
| //writeTo->animInterpolate = m_spritesInterpolate; |
| if (m_spriteEngine){ |
| m_spriteEngine->start(spriteIdx); |
| writeTo->frameCount = m_spriteEngine->spriteFrames(spriteIdx); |
| writeTo->frameDuration = m_spriteEngine->spriteDuration(spriteIdx) / writeTo->frameCount; |
| writeTo->animIdx = 0;//Always starts at 0 |
| writeTo->frameAt = -1; |
| writeTo->animX = m_spriteEngine->spriteX(spriteIdx); |
| writeTo->animY = m_spriteEngine->spriteY(spriteIdx); |
| writeTo->animWidth = m_spriteEngine->spriteWidth(spriteIdx); |
| writeTo->animHeight = m_spriteEngine->spriteHeight(spriteIdx); |
| } |
| } else { |
| ImageMaterialData *state = getState(m_material); |
| QQuickParticleData* writeTo = getShadowDatum(datum); |
| writeTo->animT = datum->t; |
| writeTo->frameCount = 1; |
| writeTo->frameDuration = 60000000.0; |
| writeTo->frameAt = -1; |
| writeTo->animIdx = 0; |
| writeTo->animT = 0; |
| writeTo->animX = writeTo->animY = 0; |
| writeTo->animWidth = state->animSheetSize.width(); |
| writeTo->animHeight = state->animSheetSize.height(); |
| } |
| Q_FALLTHROUGH(); |
| case Tabled: |
| case Deformable: |
| //Initial Rotation |
| if (m_explicitDeformation){ |
| if (!datum->deformationOwner) |
| datum->deformationOwner = this; |
| if (m_xVector){ |
| const QPointF &ret = m_xVector->sample(QPointF(datum->x, datum->y)); |
| if (datum->deformationOwner == this) { |
| datum->xx = ret.x(); |
| datum->xy = ret.y(); |
| } else { |
| getShadowDatum(datum)->xx = ret.x(); |
| getShadowDatum(datum)->xy = ret.y(); |
| } |
| } |
| if (m_yVector){ |
| const QPointF &ret = m_yVector->sample(QPointF(datum->x, datum->y)); |
| if (datum->deformationOwner == this) { |
| datum->yx = ret.x(); |
| datum->yy = ret.y(); |
| } else { |
| getShadowDatum(datum)->yx = ret.x(); |
| getShadowDatum(datum)->yy = ret.y(); |
| } |
| } |
| } |
| |
| if (m_explicitRotation){ |
| if (!datum->rotationOwner) |
| datum->rotationOwner = this; |
| rotation = |
| (m_rotation + (m_rotationVariation - 2*QRandomGenerator::global()->bounded(m_rotationVariation)) ) * CONV; |
| rotationVelocity = |
| (m_rotationVelocity + (m_rotationVelocityVariation - 2*QRandomGenerator::global()->bounded(m_rotationVelocityVariation)) ) * CONV; |
| autoRotate = m_autoRotation?1.0:0.0; |
| if (datum->rotationOwner == this) { |
| datum->rotation = rotation; |
| datum->rotationVelocity = rotationVelocity; |
| datum->autoRotate = autoRotate; |
| } else { |
| getShadowDatum(datum)->rotation = rotation; |
| getShadowDatum(datum)->rotationVelocity = rotationVelocity; |
| getShadowDatum(datum)->autoRotate = autoRotate; |
| } |
| } |
| Q_FALLTHROUGH(); |
| case Colored: |
| //Color initialization |
| // Particle color |
| if (m_explicitColor) { |
| if (!datum->colorOwner) |
| datum->colorOwner = this; |
| color.r = m_color.red() * (1 - redVariation) + QRandomGenerator::global()->bounded(256) * redVariation; |
| color.g = m_color.green() * (1 - greenVariation) + QRandomGenerator::global()->bounded(256) * greenVariation; |
| color.b = m_color.blue() * (1 - blueVariation) + QRandomGenerator::global()->bounded(256) * blueVariation; |
| color.a = m_alpha * m_color.alpha() * (1 - m_alphaVariation) + QRandomGenerator::global()->bounded(256) * m_alphaVariation; |
| if (datum->colorOwner == this) |
| datum->color = color; |
| else |
| getShadowDatum(datum)->color = color; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void QQuickImageParticle::commit(int gIdx, int pIdx) |
| { |
| if (m_pleaseReset) |
| return; |
| QSGGeometryNode *node = m_nodes[gIdx]; |
| if (!node) |
| return; |
| QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx]; |
| SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData(); |
| DeformableVertex *deformableVertices = (DeformableVertex *) node->geometry()->vertexData(); |
| ColoredVertex *coloredVertices = (ColoredVertex *) node->geometry()->vertexData(); |
| SimpleVertex *simpleVertices = (SimpleVertex *) node->geometry()->vertexData(); |
| switch (perfLevel){//No automatic fall through intended on this one |
| case Sprites: |
| spriteVertices += pIdx*4; |
| for (int i=0; i<4; i++){ |
| spriteVertices[i].x = datum->x - m_systemOffset.x(); |
| spriteVertices[i].y = datum->y - m_systemOffset.y(); |
| spriteVertices[i].t = datum->t; |
| spriteVertices[i].lifeSpan = datum->lifeSpan; |
| spriteVertices[i].size = datum->size; |
| spriteVertices[i].endSize = datum->endSize; |
| spriteVertices[i].vx = datum->vx; |
| spriteVertices[i].vy = datum->vy; |
| spriteVertices[i].ax = datum->ax; |
| spriteVertices[i].ay = datum->ay; |
| if (m_explicitDeformation && datum->deformationOwner != this) { |
| QQuickParticleData* shadow = getShadowDatum(datum); |
| spriteVertices[i].xx = shadow->xx; |
| spriteVertices[i].xy = shadow->xy; |
| spriteVertices[i].yx = shadow->yx; |
| spriteVertices[i].yy = shadow->yy; |
| } else { |
| spriteVertices[i].xx = datum->xx; |
| spriteVertices[i].xy = datum->xy; |
| spriteVertices[i].yx = datum->yx; |
| spriteVertices[i].yy = datum->yy; |
| } |
| if (m_explicitRotation && datum->rotationOwner != this) { |
| QQuickParticleData* shadow = getShadowDatum(datum); |
| spriteVertices[i].rotation = shadow->rotation; |
| spriteVertices[i].rotationVelocity = shadow->rotationVelocity; |
| spriteVertices[i].autoRotate = shadow->autoRotate; |
| } else { |
| spriteVertices[i].rotation = datum->rotation; |
| spriteVertices[i].rotationVelocity = datum->rotationVelocity; |
| spriteVertices[i].autoRotate = datum->autoRotate; |
| } |
| //Sprite-related vertices updated per-frame in spritesUpdate(), not on demand |
| if (m_explicitColor && datum->colorOwner != this) { |
| QQuickParticleData* shadow = getShadowDatum(datum); |
| spriteVertices[i].color.r = shadow->color.r; |
| spriteVertices[i].color.g = shadow->color.g; |
| spriteVertices[i].color.b = shadow->color.b; |
| spriteVertices[i].color.a = shadow->color.a; |
| } else { |
| spriteVertices[i].color.r = datum->color.r; |
| spriteVertices[i].color.g = datum->color.g; |
| spriteVertices[i].color.b = datum->color.b; |
| spriteVertices[i].color.a = datum->color.a; |
| } |
| } |
| break; |
| case Tabled: //Fall through until it has its own vertex class |
| case Deformable: |
| deformableVertices += pIdx*4; |
| for (int i=0; i<4; i++){ |
| deformableVertices[i].x = datum->x - m_systemOffset.x(); |
| deformableVertices[i].y = datum->y - m_systemOffset.y(); |
| deformableVertices[i].t = datum->t; |
| deformableVertices[i].lifeSpan = datum->lifeSpan; |
| deformableVertices[i].size = datum->size; |
| deformableVertices[i].endSize = datum->endSize; |
| deformableVertices[i].vx = datum->vx; |
| deformableVertices[i].vy = datum->vy; |
| deformableVertices[i].ax = datum->ax; |
| deformableVertices[i].ay = datum->ay; |
| if (m_explicitDeformation && datum->deformationOwner != this) { |
| QQuickParticleData* shadow = getShadowDatum(datum); |
| deformableVertices[i].xx = shadow->xx; |
| deformableVertices[i].xy = shadow->xy; |
| deformableVertices[i].yx = shadow->yx; |
| deformableVertices[i].yy = shadow->yy; |
| } else { |
| deformableVertices[i].xx = datum->xx; |
| deformableVertices[i].xy = datum->xy; |
| deformableVertices[i].yx = datum->yx; |
| deformableVertices[i].yy = datum->yy; |
| } |
| if (m_explicitRotation && datum->rotationOwner != this) { |
| QQuickParticleData* shadow = getShadowDatum(datum); |
| deformableVertices[i].rotation = shadow->rotation; |
| deformableVertices[i].rotationVelocity = shadow->rotationVelocity; |
| deformableVertices[i].autoRotate = shadow->autoRotate; |
| } else { |
| deformableVertices[i].rotation = datum->rotation; |
| deformableVertices[i].rotationVelocity = datum->rotationVelocity; |
| deformableVertices[i].autoRotate = datum->autoRotate; |
| } |
| if (m_explicitColor && datum->colorOwner != this) { |
| QQuickParticleData* shadow = getShadowDatum(datum); |
| deformableVertices[i].color.r = shadow->color.r; |
| deformableVertices[i].color.g = shadow->color.g; |
| deformableVertices[i].color.b = shadow->color.b; |
| deformableVertices[i].color.a = shadow->color.a; |
| } else { |
| deformableVertices[i].color.r = datum->color.r; |
| deformableVertices[i].color.g = datum->color.g; |
| deformableVertices[i].color.b = datum->color.b; |
| deformableVertices[i].color.a = datum->color.a; |
| } |
| } |
| break; |
| case Colored: |
| coloredVertices += pIdx*1; |
| for (int i=0; i<1; i++){ |
| coloredVertices[i].x = datum->x - m_systemOffset.x(); |
| coloredVertices[i].y = datum->y - m_systemOffset.y(); |
| coloredVertices[i].t = datum->t; |
| coloredVertices[i].lifeSpan = datum->lifeSpan; |
| coloredVertices[i].size = datum->size; |
| coloredVertices[i].endSize = datum->endSize; |
| coloredVertices[i].vx = datum->vx; |
| coloredVertices[i].vy = datum->vy; |
| coloredVertices[i].ax = datum->ax; |
| coloredVertices[i].ay = datum->ay; |
| if (m_explicitColor && datum->colorOwner != this) { |
| QQuickParticleData* shadow = getShadowDatum(datum); |
| coloredVertices[i].color.r = shadow->color.r; |
| coloredVertices[i].color.g = shadow->color.g; |
| coloredVertices[i].color.b = shadow->color.b; |
| coloredVertices[i].color.a = shadow->color.a; |
| } else { |
| coloredVertices[i].color.r = datum->color.r; |
| coloredVertices[i].color.g = datum->color.g; |
| coloredVertices[i].color.b = datum->color.b; |
| coloredVertices[i].color.a = datum->color.a; |
| } |
| } |
| break; |
| case Simple: |
| simpleVertices += pIdx*1; |
| for (int i=0; i<1; i++){ |
| simpleVertices[i].x = datum->x - m_systemOffset.x(); |
| simpleVertices[i].y = datum->y - m_systemOffset.y(); |
| simpleVertices[i].t = datum->t; |
| simpleVertices[i].lifeSpan = datum->lifeSpan; |
| simpleVertices[i].size = datum->size; |
| simpleVertices[i].endSize = datum->endSize; |
| simpleVertices[i].vx = datum->vx; |
| simpleVertices[i].vy = datum->vy; |
| simpleVertices[i].ax = datum->ax; |
| simpleVertices[i].ay = datum->ay; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquickimageparticle_p.cpp" |