/****************************************************************************
**
** Copyright (C) 2016 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 <private/qquickopenglshadereffect_p.h>

#include <QtQuick/qsgmaterial.h>
#include <QtQuick/private/qsgshadersourcebuilder_p.h>
#include "qquickitem_p.h"

#include <QtQuick/private/qsgcontext_p.h>
#include <QtQuick/qsgtextureprovider.h>
#include "qquickwindow.h"

#include "qquickimage_p.h"
#include "qquickshadereffectsource_p.h"
#include "qquickshadereffectmesh_p.h"

#include <QtQml/qqmlfile.h>
#include <QtCore/qsignalmapper.h>
#include <QtCore/qfileselector.h>

QT_BEGIN_NAMESPACE

namespace {

    enum VariableQualifier {
        AttributeQualifier,
        UniformQualifier
    };

    inline bool qt_isalpha(char c)
    {
        char ch = c | 0x20;
        return (ch >= 'a' && ch <= 'z') || c == '_';
    }

    inline bool qt_isalnum(char c)
    {
        return qt_isalpha(c) || (c >= '0' && c <= '9');
    }

    inline bool qt_isspace(char c)
    {
        return c == ' ' || (c >= 0x09 && c <= 0x0d);
    }

    // Returns -1 if not found, returns index to first character after the name if found.
    int qt_search_for_variable(const char *s, int length, int index, VariableQualifier &decl,
                               int &typeIndex, int &typeLength,
                               int &nameIndex, int &nameLength,
                               QQuickOpenGLShaderEffectCommon::Key::ShaderType shaderType)
    {
        enum Identifier {
            QualifierIdentifier, // Base state
            PrecisionIdentifier,
            TypeIdentifier,
            NameIdentifier
        };
        Identifier expected = QualifierIdentifier;
        bool compilerDirectiveExpected = index == 0;

        while (index < length) {
            // Skip whitespace.
            while (qt_isspace(s[index])) {
                compilerDirectiveExpected |= s[index] == '\n';
                ++index;
            }

            if (qt_isalpha(s[index])) {
                // Read identifier.
                int idIndex = index;
                ++index;
                while (qt_isalnum(s[index]))
                    ++index;
                int idLength = index - idIndex;

                const int attrLen = sizeof("attribute") - 1;
                const int inLen = sizeof("in") - 1;
                const int uniLen = sizeof("uniform") - 1;
                const int loLen = sizeof("lowp") - 1;
                const int medLen = sizeof("mediump") - 1;
                const int hiLen = sizeof("highp") - 1;

                switch (expected) {
                case QualifierIdentifier:
                    if (idLength == attrLen && qstrncmp("attribute", s + idIndex, attrLen) == 0) {
                        decl = AttributeQualifier;
                        expected = PrecisionIdentifier;
                    } else if (shaderType == QQuickOpenGLShaderEffectCommon::Key::VertexShader
                               && idLength == inLen && qstrncmp("in", s + idIndex, inLen) == 0) {
                        decl = AttributeQualifier;
                        expected = PrecisionIdentifier;
                    } else if (idLength == uniLen && qstrncmp("uniform", s + idIndex, uniLen) == 0) {
                        decl = UniformQualifier;
                        expected = PrecisionIdentifier;
                    }
                    break;
                case PrecisionIdentifier:
                    if ((idLength == loLen && qstrncmp("lowp", s + idIndex, loLen) == 0)
                            || (idLength == medLen && qstrncmp("mediump", s + idIndex, medLen) == 0)
                            || (idLength == hiLen && qstrncmp("highp", s + idIndex, hiLen) == 0))
                    {
                        expected = TypeIdentifier;
                        break;
                    }
                    Q_FALLTHROUGH();
                case TypeIdentifier:
                    typeIndex = idIndex;
                    typeLength = idLength;
                    expected = NameIdentifier;
                    break;
                case NameIdentifier:
                    nameIndex = idIndex;
                    nameLength = idLength;
                    return index; // Attribute or uniform declaration found. Return result.
                default:
                    break;
                }
            } else if (s[index] == '#' && compilerDirectiveExpected) {
                // Skip compiler directives.
                ++index;
                while (index < length && (s[index] != '\n' || s[index - 1] == '\\'))
                    ++index;
            } else if (s[index] == '/' && s[index + 1] == '/') {
                // Skip comments.
                index += 2;
                while (index < length && s[index] != '\n')
                    ++index;
            } else if (s[index] == '/' && s[index + 1] == '*') {
                // Skip comments.
                index += 2;
                while (index < length && (s[index] != '*' || s[index + 1] != '/'))
                    ++index;
                if (index < length)
                    index += 2; // Skip star-slash.
            } else {
                expected = QualifierIdentifier;
                ++index;
            }
            compilerDirectiveExpected = false;
        }
        return -1;
    }
}

namespace QtPrivate {
class MappedSlotObject: public QtPrivate::QSlotObjectBase
{
public:
    typedef std::function<void()> PropChangedFunc;

    explicit MappedSlotObject(PropChangedFunc func)
        : QSlotObjectBase(&impl), _signalIndex(-1), func(func)
    { ref(); }

    void setSignalIndex(int idx) { _signalIndex = idx; }
    int signalIndex() const { return _signalIndex; }

private:
    int _signalIndex;
    PropChangedFunc func;

    static void impl(int which, QSlotObjectBase *this_, QObject *, void **a, bool *ret)
    {
        auto thiz = static_cast<MappedSlotObject*>(this_);
        switch (which) {
        case Destroy:
            delete thiz;
            break;
        case Call:
            thiz->func();
            break;
        case Compare:
            *ret = thiz == reinterpret_cast<MappedSlotObject *>(a[0]);
            break;
        case NumOperations: ;
        }
    }
};
}

QQuickOpenGLShaderEffectCommon::~QQuickOpenGLShaderEffectCommon()
{
    for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType)
        clearSignalMappers(shaderType);
}

void QQuickOpenGLShaderEffectCommon::disconnectPropertySignals(QQuickItem *item, Key::ShaderType shaderType)
{
    for (int i = 0; i < uniformData[shaderType].size(); ++i) {
        if (signalMappers[shaderType].at(i) == 0)
            continue;
        const UniformData &d = uniformData[shaderType].at(i);
        auto mapper = signalMappers[shaderType].at(i);
        void *a = mapper;
        QObjectPrivate::disconnect(item, mapper->signalIndex(), &a);
        if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
            QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
            if (source) {
                if (item->window())
                    QQuickItemPrivate::get(source)->derefWindow();
                QObject::disconnect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*)));
            }
        }
    }
}

void QQuickOpenGLShaderEffectCommon::connectPropertySignals(QQuickItem *item,
                                                            const QMetaObject *itemMetaObject,
                                                            Key::ShaderType shaderType)
{
    auto engine = qmlEngine(item);
    if (!engine)
        return;

    QQmlPropertyCache *propCache = QQmlData::ensurePropertyCache(engine, item);
    for (int i = 0; i < uniformData[shaderType].size(); ++i) {
        if (signalMappers[shaderType].at(i) == 0)
            continue;
        const UniformData &d = uniformData[shaderType].at(i);
        QQmlPropertyData *pd = propCache->property(QString::fromUtf8(d.name), nullptr, nullptr);
        if (pd && !pd->isFunction()) {
            if (pd->notifyIndex() == -1) {
                qWarning("QQuickOpenGLShaderEffect: property '%s' does not have notification method!", d.name.constData());
            } else {
                auto *mapper = signalMappers[shaderType].at(i);
                mapper->setSignalIndex(itemMetaObject->property(d.propertyIndex).notifySignal().methodIndex());
                Q_ASSERT(item->metaObject() == itemMetaObject);
                bool ok = QObjectPrivate::connectImpl(item, pd->notifyIndex(), item, nullptr, mapper,
                                                      Qt::AutoConnection, nullptr, itemMetaObject);
                if (!ok)
                    qWarning() << "Failed to connect to property" << itemMetaObject->property(d.propertyIndex).name()
                               << "(" << d.propertyIndex << ", signal index" << pd->notifyIndex()
                               << ") of item" << item;
            }
        } else {
            // If the source is set via a dynamic property, like the layer is, then we need this
            // check to disable the warning.
            if (!item->property(d.name).isValid())
                qWarning("QQuickOpenGLShaderEffect: '%s' does not have a matching property!", d.name.constData());
        }

        if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
            QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
            if (source) {
                if (item->window())
                    QQuickItemPrivate::get(source)->refWindow(item->window());
                QObject::connect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*)));
            }
        }
    }
}

void QQuickOpenGLShaderEffectCommon::updateParseLog(bool ignoreAttributes)
{
    parseLog.clear();
    if (!ignoreAttributes) {
        if (!attributes.contains(qtPositionAttributeName())) {
            parseLog += QLatin1String("Warning: Missing reference to \'")
                        + QLatin1String(qtPositionAttributeName())
                        + QLatin1String("\'.\n");
        }
        if (!attributes.contains(qtTexCoordAttributeName())) {
            parseLog += QLatin1String("Warning: Missing reference to \'")
                        + QLatin1String(qtTexCoordAttributeName())
                        + QLatin1String("\'.\n");
        }
    }
    bool respectsMatrix = false;
    bool respectsOpacity = false;
    for (int i = 0; i < uniformData[Key::VertexShader].size(); ++i)
        respectsMatrix |= uniformData[Key::VertexShader].at(i).specialType == UniformData::Matrix;
    for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
        for (int i = 0; i < uniformData[shaderType].size(); ++i)
            respectsOpacity |= uniformData[shaderType].at(i).specialType == UniformData::Opacity;
    }
    if (!respectsMatrix)
        parseLog += QLatin1String("Warning: Vertex shader is missing reference to \'qt_Matrix\'.\n");
    if (!respectsOpacity)
        parseLog += QLatin1String("Warning: Shaders are missing reference to \'qt_Opacity\'.\n");
}

void QQuickOpenGLShaderEffectCommon::lookThroughShaderCode(QQuickItem *item,
                                                           const QMetaObject *itemMetaObject,
                                                           Key::ShaderType shaderType,
                                                           const QByteArray &code)
{
    auto engine = qmlEngine(item);
    QQmlPropertyCache *propCache = (engine) ? QQmlData::ensurePropertyCache(engine, item) : nullptr;
    int index = 0;
    int typeIndex = -1;
    int typeLength = 0;
    int nameIndex = -1;
    int nameLength = 0;
    const char *s = code.constData();
    VariableQualifier decl = AttributeQualifier;
    while ((index = qt_search_for_variable(s, code.size(), index, decl, typeIndex, typeLength,
                                           nameIndex, nameLength, shaderType)) != -1)
    {
        if (decl == AttributeQualifier) {
            if (shaderType == Key::VertexShader)
                attributes.append(QByteArray(s + nameIndex, nameLength));
        } else {
            Q_ASSERT(decl == UniformQualifier);

            const int sampLen = sizeof("sampler2D") - 1;
            const int sampExtLen = sizeof("samplerExternalOES") - 1;
            const int opLen = sizeof("qt_Opacity") - 1;
            const int matLen = sizeof("qt_Matrix") - 1;
            const int srLen = sizeof("qt_SubRect_") - 1;

            UniformData d;
            QtPrivate::MappedSlotObject *mapper = nullptr;
            d.name = QByteArray(s + nameIndex, nameLength);
            if (nameLength == opLen && qstrncmp("qt_Opacity", s + nameIndex, opLen) == 0) {
                d.specialType = UniformData::Opacity;
            } else if (nameLength == matLen && qstrncmp("qt_Matrix", s + nameIndex, matLen) == 0) {
                d.specialType = UniformData::Matrix;
            } else if (nameLength > srLen && qstrncmp("qt_SubRect_", s + nameIndex, srLen) == 0) {
                d.specialType = UniformData::SubRect;
            } else {
                if (propCache) {
                    if (QQmlPropertyData *pd = propCache->property(QString::fromUtf8(d.name), nullptr, nullptr)) {
                        if (!pd->isFunction())
                            d.propertyIndex = pd->coreIndex();
                    }
                }
                const int mappedId = uniformData[shaderType].size() | (shaderType << 16);
                mapper = new QtPrivate::MappedSlotObject([this, mappedId](){
                    this->mappedPropertyChanged(mappedId);
                });
                if (typeLength == sampLen && qstrncmp("sampler2D", s + typeIndex, sampLen) == 0)
                    d.specialType = UniformData::Sampler;
                else if (typeLength == sampExtLen && qstrncmp("samplerExternalOES", s + typeIndex, sampExtLen) == 0)
                    d.specialType = UniformData::SamplerExternal;
                else
                    d.specialType = UniformData::None;
                d.setValueFromProperty(item, itemMetaObject);
            }
            uniformData[shaderType].append(d);
            signalMappers[shaderType].append(mapper);
        }
    }
}

void QQuickOpenGLShaderEffectCommon::updateShader(QQuickItem *item,
                                                  const QMetaObject *itemMetaObject,
                                                  Key::ShaderType shaderType)
{
    disconnectPropertySignals(item, shaderType);
    uniformData[shaderType].clear();
    clearSignalMappers(shaderType);
    if (shaderType == Key::VertexShader)
        attributes.clear();

    // A qrc or file URL means the shader source is to be read from the specified file.
    QUrl srcUrl(QString::fromUtf8(source.sourceCode[shaderType]));
    if (!srcUrl.scheme().compare(QLatin1String("qrc"), Qt::CaseInsensitive) || srcUrl.isLocalFile()) {
        if (!fileSelector) {
            fileSelector = new QFileSelector(item);
            // There may not be an OpenGL context accessible here. So rely on
            // the window's requestedFormat().
            if (item->window()
                    && item->window()->requestedFormat().profile() == QSurfaceFormat::CoreProfile) {
                fileSelector->setExtraSelectors(QStringList() << QStringLiteral("glslcore"));
            }
        }
        const QString fn = fileSelector->select(QQmlFile::urlToLocalFileOrQrc(srcUrl));
        QFile f(fn);
        if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            source.sourceCode[shaderType] = f.readAll();
            f.close();
        } else {
            qWarning("ShaderEffect: Failed to read %s", qPrintable(fn));
            source.sourceCode[shaderType] = QByteArray();
        }
    }

    const QByteArray &code = source.sourceCode[shaderType];
    if (code.isEmpty()) {
        // Optimize for default code.
        if (shaderType == Key::VertexShader) {
            attributes.append(QByteArray(qtPositionAttributeName()));
            attributes.append(QByteArray(qtTexCoordAttributeName()));
            UniformData d;
            d.name = "qt_Matrix";
            d.specialType = UniformData::Matrix;
            uniformData[Key::VertexShader].append(d);
            signalMappers[Key::VertexShader].append(0);
        } else if (shaderType == Key::FragmentShader) {
            UniformData d;
            d.name = "qt_Opacity";
            d.specialType = UniformData::Opacity;
            uniformData[Key::FragmentShader].append(d);
            signalMappers[Key::FragmentShader].append(0);
            auto mapper = new QtPrivate::MappedSlotObject([this](){
                mappedPropertyChanged(1 | (Key::FragmentShader << 16));
            });
            const char *sourceName = "source";
            d.name = sourceName;
            d.setValueFromProperty(item, itemMetaObject);
            d.specialType = UniformData::Sampler;
            uniformData[Key::FragmentShader].append(d);
            signalMappers[Key::FragmentShader].append(mapper);
        }
    } else {
        lookThroughShaderCode(item, itemMetaObject, shaderType, code);
    }

    connectPropertySignals(item, itemMetaObject, shaderType);
}

void QQuickOpenGLShaderEffectCommon::updateMaterial(QQuickOpenGLShaderEffectNode *node,
                                              QQuickOpenGLShaderEffectMaterial *material,
                                              bool updateUniforms, bool updateUniformValues,
                                              bool updateTextureProviders)
{
    if (updateUniforms) {
        for (int i = 0; i < material->textureProviders.size(); ++i) {
            QSGTextureProvider *t = material->textureProviders.at(i);
            if (t) {
                QObject::disconnect(t, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
                QObject::disconnect(t, SIGNAL(destroyed(QObject*)), node, SLOT(textureProviderDestroyed(QObject*)));
            }
        }

        // First make room in the textureProviders array. Set to proper value further down.
        int textureProviderCount = 0;
        for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
            for (int i = 0; i < uniformData[shaderType].size(); ++i) {
                if (uniformData[shaderType].at(i).specialType == UniformData::Sampler ||
                        uniformData[shaderType].at(i).specialType == UniformData::SamplerExternal)
                    ++textureProviderCount;
            }
            material->uniforms[shaderType] = uniformData[shaderType];
        }
        material->textureProviders.fill(0, textureProviderCount);
        updateUniformValues = false;
        updateTextureProviders = true;
    }

    if (updateUniformValues) {
        for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
            Q_ASSERT(uniformData[shaderType].size() == material->uniforms[shaderType].size());
            for (int i = 0; i < uniformData[shaderType].size(); ++i)
                material->uniforms[shaderType][i].value = uniformData[shaderType].at(i).value;
        }
    }

    if (updateTextureProviders) {
        int index = 0;
        for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
            for (int i = 0; i < uniformData[shaderType].size(); ++i) {
                const UniformData &d = uniformData[shaderType].at(i);
                if (d.specialType != UniformData::Sampler && d.specialType != UniformData::SamplerExternal)
                    continue;
                QSGTextureProvider *oldProvider = material->textureProviders.at(index);
                QSGTextureProvider *newProvider = nullptr;
                QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
                if (source && source->isTextureProvider())
                    newProvider = source->textureProvider();
                if (newProvider != oldProvider) {
                    if (oldProvider) {
                        QObject::disconnect(oldProvider, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
                        QObject::disconnect(oldProvider, SIGNAL(destroyed(QObject*)), node, SLOT(textureProviderDestroyed(QObject*)));
                    }
                    if (newProvider) {
                        Q_ASSERT_X(newProvider->thread() == QThread::currentThread(),
                                   "QQuickOpenGLShaderEffect::updatePaintNode",
                                   "Texture provider must belong to the rendering thread");
                        QObject::connect(newProvider, SIGNAL(textureChanged()), node, SLOT(markDirtyTexture()));
                        QObject::connect(newProvider, SIGNAL(destroyed(QObject*)), node, SLOT(textureProviderDestroyed(QObject*)));
                    } else {
                        const char *typeName = source ? source->metaObject()->className() : d.value.typeName();
                        qWarning("ShaderEffect: Property '%s' is not assigned a valid texture provider (%s).",
                                 d.name.constData(), typeName);
                    }
                    material->textureProviders[index] = newProvider;
                }
                ++index;
            }
        }
        Q_ASSERT(index == material->textureProviders.size());
    }
}

void QQuickOpenGLShaderEffectCommon::updateWindow(QQuickWindow *window)
{
    // See comment in QQuickOpenGLShaderEffectCommon::propertyChanged().
    if (window) {
        for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
            for (int i = 0; i < uniformData[shaderType].size(); ++i) {
                const UniformData &d = uniformData[shaderType].at(i);
                if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
                    QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
                    if (source)
                        QQuickItemPrivate::get(source)->refWindow(window);
                }
            }
        }
    } else {
        for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
            for (int i = 0; i < uniformData[shaderType].size(); ++i) {
                const UniformData &d = uniformData[shaderType].at(i);
                if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
                    QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
                    if (source)
                        QQuickItemPrivate::get(source)->derefWindow();
                }
            }
        }
    }
}

void QQuickOpenGLShaderEffectCommon::sourceDestroyed(QObject *object)
{
    for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
        for (int i = 0; i < uniformData[shaderType].size(); ++i) {
            UniformData &d = uniformData[shaderType][i];
            if ((d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) && d.value.canConvert<QObject *>()) {
                if (qvariant_cast<QObject *>(d.value) == object)
                    d.value = QVariant();
            }
        }
    }
}

static bool qquick_uniqueInUniformData(QQuickItem *source, const QVector<QQuickOpenGLShaderEffectMaterial::UniformData> *uniformData, int typeToSkip, int indexToSkip)
{
    for (int s=0; s<QQuickOpenGLShaderEffectMaterialKey::ShaderTypeCount; ++s) {
        for (int i=0; i<uniformData[s].size(); ++i) {
            if (s == typeToSkip && i == indexToSkip)
                continue;
            const QQuickOpenGLShaderEffectMaterial::UniformData &d = uniformData[s][i];
            if ((d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::Sampler || d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::SamplerExternal) && qvariant_cast<QObject *>(d.value) == source)
                return false;
        }
    }
    return true;
}

void QQuickOpenGLShaderEffectCommon::propertyChanged(QQuickItem *item,
                                                     const QMetaObject *itemMetaObject,
                                                     int mappedId, bool *textureProviderChanged)
{
    Key::ShaderType shaderType = Key::ShaderType(mappedId >> 16);
    int index = mappedId & 0xffff;
    UniformData &d = uniformData[shaderType][index];
    if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) {
        QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
        if (source) {
            if (item->window())
                QQuickItemPrivate::get(source)->derefWindow();

            // QObject::disconnect() will disconnect all matching connections. If the same
            // source has been attached to two separate samplers, then changing one of them
            // would trigger both to be disconnected. Without the connection we'll end up
            // with a dangling pointer in the uniformData.
            if (qquick_uniqueInUniformData(source, uniformData, shaderType, index))
                QObject::disconnect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*)));
        }

        d.setValueFromProperty(item, itemMetaObject);

        source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value));
        if (source) {
            // 'source' needs a window to get a scene graph node. It usually gets one through its
            // parent, but if the source item is "inline" rather than a reference -- i.e.
            // "property variant source: Image { }" instead of "property variant source: foo" -- it
            // will not get a parent. In those cases, 'source' should get the window from 'item'.
            if (item->window())
                QQuickItemPrivate::get(source)->refWindow(item->window());
            QObject::connect(source, SIGNAL(destroyed(QObject*)), host, SLOT(sourceDestroyed(QObject*)));
        }
        if (textureProviderChanged)
            *textureProviderChanged = true;
    } else {
        d.setValueFromProperty(item, itemMetaObject);
        if (textureProviderChanged)
            *textureProviderChanged = false;
    }
}

void QQuickOpenGLShaderEffectCommon::clearSignalMappers(int shader)
{
    for (auto mapper : qAsConst(signalMappers[shader])) {
        if (mapper)
            mapper->destroyIfLastRef();
    }
    signalMappers[shader].clear();
}

QQuickOpenGLShaderEffect::QQuickOpenGLShaderEffect(QQuickShaderEffect *item, QObject *parent)
    : QObject(parent)
    , m_item(item)
    , m_itemMetaObject(nullptr)
    , m_meshResolution(1, 1)
    , m_mesh(nullptr)
    , m_cullMode(QQuickShaderEffect::NoCulling)
    , m_status(QQuickShaderEffect::Uncompiled)
    , m_common(this, [this](int mappedId){this->propertyChanged(mappedId);})
    , m_blending(true)
    , m_dirtyUniforms(true)
    , m_dirtyUniformValues(true)
    , m_dirtyTextureProviders(true)
    , m_dirtyProgram(true)
    , m_dirtyParseLog(true)
    , m_dirtyMesh(true)
    , m_dirtyGeometry(true)
    , m_customVertexShader(false)
    , m_supportsAtlasTextures(false)
    , m_vertNeedsUpdate(true)
    , m_fragNeedsUpdate(true)
{
}

QQuickOpenGLShaderEffect::~QQuickOpenGLShaderEffect()
{
    for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType)
        m_common.disconnectPropertySignals(m_item, Key::ShaderType(shaderType));
}

void QQuickOpenGLShaderEffect::setFragmentShader(const QByteArray &code)
{
    if (m_common.source.sourceCode[Key::FragmentShader].constData() == code.constData())
        return;
    m_common.source.sourceCode[Key::FragmentShader] = code;
    m_dirtyProgram = true;
    m_dirtyParseLog = true;

    m_fragNeedsUpdate = true;
    if (m_item->isComponentComplete())
        maybeUpdateShaders();

    m_item->update();
    if (m_status != QQuickShaderEffect::Uncompiled) {
        m_status = QQuickShaderEffect::Uncompiled;
        emit m_item->statusChanged();
    }
    emit m_item->fragmentShaderChanged();
}

void QQuickOpenGLShaderEffect::setVertexShader(const QByteArray &code)
{
    if (m_common.source.sourceCode[Key::VertexShader].constData() == code.constData())
        return;
    m_common.source.sourceCode[Key::VertexShader] = code;
    m_dirtyProgram = true;
    m_dirtyParseLog = true;
    m_customVertexShader = true;

    m_vertNeedsUpdate = true;
    if (m_item->isComponentComplete())
        maybeUpdateShaders();

    m_item->update();
    if (m_status != QQuickShaderEffect::Uncompiled) {
        m_status = QQuickShaderEffect::Uncompiled;
        emit m_item->statusChanged();
    }
    emit m_item->vertexShaderChanged();
}

void QQuickOpenGLShaderEffect::setBlending(bool enable)
{
    if (blending() == enable)
        return;

    m_blending = enable;
    m_item->update();

    emit m_item->blendingChanged();
}

QVariant QQuickOpenGLShaderEffect::mesh() const
{
    return m_mesh ? qVariantFromValue(static_cast<QObject *>(m_mesh))
                  : qVariantFromValue(m_meshResolution);
}

void QQuickOpenGLShaderEffect::setMesh(const QVariant &mesh)
{
    QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(qvariant_cast<QObject *>(mesh));
    if (newMesh && newMesh == m_mesh)
        return;
    if (m_mesh)
        disconnect(m_mesh, SIGNAL(geometryChanged()), this, nullptr);
    m_mesh = newMesh;
    if (m_mesh) {
        connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(updateGeometry()));
    } else {
        if (mesh.canConvert<QSize>()) {
            m_meshResolution = mesh.toSize();
        } else {
            QList<QByteArray> res = mesh.toByteArray().split('x');
            bool ok = res.size() == 2;
            if (ok) {
                int w = res.at(0).toInt(&ok);
                if (ok) {
                    int h = res.at(1).toInt(&ok);
                    if (ok)
                        m_meshResolution = QSize(w, h);
                }
            }
            if (!ok)
                qWarning("ShaderEffect: mesh property must be size or object deriving from QQuickShaderEffectMesh.");
        }
        m_defaultMesh.setResolution(m_meshResolution);
    }

    m_dirtyMesh = true;
    m_dirtyParseLog = true;
    m_item->update();
    emit m_item->meshChanged();
}

void QQuickOpenGLShaderEffect::setCullMode(QQuickShaderEffect::CullMode face)
{
    if (face == m_cullMode)
        return;
    m_cullMode = face;
    m_item->update();
    emit m_item->cullModeChanged();
}

void QQuickOpenGLShaderEffect::setSupportsAtlasTextures(bool supports)
{
    if (supports == m_supportsAtlasTextures)
        return;
    m_supportsAtlasTextures = supports;
    updateGeometry();
    emit m_item->supportsAtlasTexturesChanged();
}

QString QQuickOpenGLShaderEffect::parseLog()
{
    maybeUpdateShaders(true);

    if (m_dirtyParseLog) {
        m_common.updateParseLog(m_mesh != nullptr);
        m_dirtyParseLog = false;
    }
    return m_common.parseLog;
}

void QQuickOpenGLShaderEffect::handleEvent(QEvent *event)
{
    if (event->type() == QEvent::DynamicPropertyChange) {
        QDynamicPropertyChangeEvent *e = static_cast<QDynamicPropertyChangeEvent *>(event);
        for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) {
            for (int i = 0; i < m_common.uniformData[shaderType].size(); ++i) {
                if (m_common.uniformData[shaderType].at(i).name == e->propertyName()) {
                    bool textureProviderChanged;
                    m_common.propertyChanged(m_item, m_itemMetaObject,
                                             (shaderType << 16) | i, &textureProviderChanged);
                    m_dirtyTextureProviders |= textureProviderChanged;
                    m_dirtyUniformValues = true;
                    m_item->update();
                }
            }
        }
    }
}

void QQuickOpenGLShaderEffect::updateGeometry()
{
    m_dirtyGeometry = true;
    m_item->update();
}

void QQuickOpenGLShaderEffect::updateGeometryIfAtlased()
{
    if (m_supportsAtlasTextures)
        updateGeometry();
}

void QQuickOpenGLShaderEffect::updateLogAndStatus(const QString &log, int status)
{
    m_log = parseLog() + log;
    m_status = QQuickShaderEffect::Status(status);
    emit m_item->logChanged();
    emit m_item->statusChanged();
}

void QQuickOpenGLShaderEffect::sourceDestroyed(QObject *object)
{
    m_common.sourceDestroyed(object);
}

void QQuickOpenGLShaderEffect::propertyChanged(int mappedId)
{
    bool textureProviderChanged;
    m_common.propertyChanged(m_item, m_itemMetaObject, mappedId, &textureProviderChanged);
    m_dirtyTextureProviders |= textureProviderChanged;
    m_dirtyUniformValues = true;
    m_item->update();
}

void QQuickOpenGLShaderEffect::handleGeometryChanged(const QRectF &, const QRectF &)
{
    m_dirtyGeometry = true;
}

QSGNode *QQuickOpenGLShaderEffect::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
    QQuickOpenGLShaderEffectNode *node = static_cast<QQuickOpenGLShaderEffectNode *>(oldNode);

    // In the case of zero-size or a bad vertex shader, don't try to create a node...
    if (m_common.attributes.isEmpty() || m_item->width() <= 0 || m_item->height() <= 0) {
        if (node)
            delete node;
        return nullptr;
    }

    if (!node) {
        node = new QQuickOpenGLShaderEffectNode;
        node->setMaterial(new QQuickOpenGLShaderEffectMaterial(node));
        node->setFlag(QSGNode::OwnsMaterial, true);
        m_dirtyProgram = true;
        m_dirtyUniforms = true;
        m_dirtyGeometry = true;
        connect(node, SIGNAL(logAndStatusChanged(QString,int)), this, SLOT(updateLogAndStatus(QString,int)));
        connect(node, &QQuickOpenGLShaderEffectNode::dirtyTexture,
                this, &QQuickOpenGLShaderEffect::updateGeometryIfAtlased);
    }

    QQuickOpenGLShaderEffectMaterial *material = static_cast<QQuickOpenGLShaderEffectMaterial *>(node->material());

    // Update blending
    if (bool(material->flags() & QSGMaterial::Blending) != m_blending) {
        material->setFlag(QSGMaterial::Blending, m_blending);
        node->markDirty(QSGNode::DirtyMaterial);
    }

    if (int(material->cullMode) != int(m_cullMode)) {
        material->cullMode = QQuickShaderEffect::CullMode(m_cullMode);
        node->markDirty(QSGNode::DirtyMaterial);
    }

    if (m_dirtyProgram) {
        Key s = m_common.source;
        QSGShaderSourceBuilder builder;
        if (s.sourceCode[Key::FragmentShader].isEmpty()) {
            builder.appendSourceFile(QStringLiteral(":/qt-project.org/items/shaders/shadereffect.frag"));
            s.sourceCode[Key::FragmentShader] = builder.source();
            builder.clear();
        }
        if (s.sourceCode[Key::VertexShader].isEmpty()) {
            builder.appendSourceFile(QStringLiteral(":/qt-project.org/items/shaders/shadereffect.vert"));
            s.sourceCode[Key::VertexShader] = builder.source();
        }

        material->setProgramSource(s);
        material->attributes = m_common.attributes;
        node->markDirty(QSGNode::DirtyMaterial);
        m_dirtyProgram = false;
        m_dirtyUniforms = true;
    }

    if (m_dirtyUniforms || m_dirtyUniformValues || m_dirtyTextureProviders) {
        m_common.updateMaterial(node, material, m_dirtyUniforms, m_dirtyUniformValues,
                                m_dirtyTextureProviders);
        node->markDirty(QSGNode::DirtyMaterial);
        m_dirtyUniforms = m_dirtyUniformValues = m_dirtyTextureProviders = false;
    }

    QRectF srcRect(0, 0, 1, 1);
    bool geometryUsesTextureSubRect = false;
    if (m_supportsAtlasTextures && material->textureProviders.size() == 1) {
        QSGTextureProvider *provider = material->textureProviders.at(0);
        if (provider && provider->texture()) {
            srcRect = provider->texture()->normalizedTextureSubRect();
            geometryUsesTextureSubRect = true;
        }
    }

    if (bool(material->flags() & QSGMaterial::RequiresFullMatrix) != m_customVertexShader) {
        material->setFlag(QSGMaterial::RequiresFullMatrix, m_customVertexShader);
        node->markDirty(QSGNode::DirtyMaterial);
    }

    if (material->geometryUsesTextureSubRect != geometryUsesTextureSubRect) {
        material->geometryUsesTextureSubRect = geometryUsesTextureSubRect;
        node->markDirty(QSGNode::DirtyMaterial);
    }

    if (m_dirtyMesh) {
        node->setGeometry(nullptr);
        m_dirtyMesh = false;
        m_dirtyGeometry = true;
    }

    if (m_dirtyGeometry) {
        node->setFlag(QSGNode::OwnsGeometry, false);
        QSGGeometry *geometry = node->geometry();
        QRectF rect(0, 0, m_item->width(), m_item->height());
        QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;

        int posIndex = 0;
        if (!mesh->validateAttributes(m_common.attributes, &posIndex)) {
            QString log = mesh->log();
            if (!log.isNull()) {
                m_log = parseLog() + QLatin1String("*** Mesh ***\n") + log;
                m_status = QQuickShaderEffect::Error;
                emit m_item->logChanged();
                emit m_item->statusChanged();
            }
            delete node;
            return nullptr;
        }

        geometry = mesh->updateGeometry(geometry, m_common.attributes.count(), posIndex, srcRect, rect);

        node->setGeometry(geometry);
        node->setFlag(QSGNode::OwnsGeometry, true);

        m_dirtyGeometry = false;
    }

    return node;
}

void QQuickOpenGLShaderEffect::maybeUpdateShaders(bool force)
{
    if (!m_itemMetaObject)
        m_itemMetaObject = m_item->metaObject();

    // Defer processing if a window is not yet associated with the item. This
    // is because the actual scenegraph backend is not known so conditions
    // based on GraphicsInfo.shaderType and similar evaluate to wrong results.
    if (!m_item->window() && !force) {
        m_item->polish();
        return;
    }

    if (m_vertNeedsUpdate) {
        m_vertNeedsUpdate = false;
        m_common.updateShader(m_item, m_itemMetaObject, Key::VertexShader);
    }

    if (m_fragNeedsUpdate) {
        m_fragNeedsUpdate = false;
        m_common.updateShader(m_item, m_itemMetaObject, Key::FragmentShader);
    }
}

void QQuickOpenGLShaderEffect::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
    if (change == QQuickItem::ItemSceneChange)
        m_common.updateWindow(value.window);
}

QT_END_NAMESPACE

#include "moc_qquickopenglshadereffect_p.cpp"
