/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) 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.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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qquick3dshaderutils_p.h"

#include <QtCore/qfile.h>

#include <QtQuick3D/private/qquick3dmaterial_p.h>
#include <QtQuick3D/private/qquick3deffect_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmltype Shader
    \inherits Object
    \inqmlmodule QtQuick3D
    \brief Container component for defining shader code used by CustomMaterials and Effects.
*/
/*!
    \qmlproperty string Shader::shader
    Specifies the name of the shader source file.
*/
/*!
    \qmlproperty enumeration Shader::stage
    Specifies the shader stage. The default is \c Shader.Shared

    \value Shader.Shared The shader can be shared among different stages
    \value Shader.Vertex The shader is a vertex shader
    \value Shader.Fragment The shader is a fragment shader
    \value Shader.Geometry The shader is a geometry shader
    \value Shader.Compute The shader is a compute shader
*/

/*!
    \qmltype ShaderInfo
    \inherits Object
    \inqmlmodule QtQuick3D
    \brief Defines basic information about custom shader code for CustomMaterials.
*/
/*!
    \qmlproperty string ShaderInfo::version
    Specifies the shader code version.
*/
/*!
    \qmlproperty string ShaderInfo::type
    Specifies the shader code type.
*/
/*!
    \qmlproperty string ShaderInfo::shaderKey
    Specifies the options used by the shader using the combination of shader key values.

    \value ShaderInfo.Diffuse The shader uses diffuse lighting.
    \value ShaderInfo.Specular The shader uses specular lighting.
    \value ShaderInfo.Cutout The shader uses alpha cutout.
    \value ShaderInfo.Refraction The shader uses refraction.
    \value ShaderInfo.Transparent The shader uses transparency.
    \value ShaderInfo.Displace The shader uses displacement mapping.
    \value ShaderInfo.Transmissive The shader uses transmissiveness.
    \value ShaderInfo.Glossy The shader is default glossy. This is a combination of \c ShaderInfo.Diffuse and
        \c ShaderInfo.Specular.
*/

/*!
    \qmltype TextureInput
    \inherits Object
    \inqmlmodule QtQuick3D
    \brief Defines a texture channel for a Custom Material or an Effect.
*/
/*!
    \qmlproperty Texture TextureInput::texture
    Specifies the Texture to input.
*/
/*!
    \qmlproperty bool TextureInput::enabled
    The property determines if this TextureInput is enabled.
*/

/*!
    \qmltype Pass
    \inherits Object
    \inqmlmodule QtQuick3D
    \brief Defines a render pass in the CustomMaterial or the Effect.
*/
/*!
    \qmlproperty Buffer Pass::output
    Specifies the output \l {Buffer}{buffer} of the pass.
*/
/*!
    \qmlproperty list Pass::commands
    Specifies the list of render \l {Command}{commands} of the pass.
*/
/*!
    \qmlproperty list Pass::shaders
    Specifies the list of \l {Shader}{shaders} of the pass.
*/

/*!
    \qmltype Command
    \inherits Object
    \inqmlmodule QtQuick3D
    \brief Defines a command to be performed in a pass of a CustomMaterial or an Effect.
*/

/*!
    \qmltype BufferInput
    \inherits Command
    \inqmlmodule QtQuick3D
    \brief Defines an input buffer to be used for a pass of a CustomMaterial or an Effect.
*/
/*!
    \qmlproperty Buffer BufferInput::buffer
    Specifies the \l {Buffer}{buffer} used for the parameter.
*/
/*!
    \qmlproperty string BufferInput::param
    Specifies the name of the input parameter in the shader.
*/

/*!
    \qmltype BufferBlit
    \inherits Command
    \inqmlmodule QtQuick3D
    \brief Defines a copy operation between two buffers in a pass of a CustomMaterial or an Effect.
*/
/*!
    \qmlproperty Buffer BufferBlit::source
    Specifies the source \l {Buffer}{buffer} of the copy operation.
*/
/*!
    \qmlproperty Buffer BufferBlit::destination
    Specifies the destination \l {Buffer}{buffer} of the copy operation.
*/

/*!
    \qmltype Blending
    \inherits Command
    \inqmlmodule QtQuick3D
    \brief Defines the blending state in a pass of a CustomMaterial or an Effect.
*/
/*!
    \qmlproperty enumeration Blending::srcBlending
    Specifies the source blending function.

    \value Blending.Unknown
    \value Blending.Zero
    \value Blending.One
    \value Blending.SrcColor
    \value Blending.OneMinusSrcColor
    \value Blending.DstColor
    \value Blending.OneMinusDstColor
    \value Blending.SrcAlpha
    \value Blending.OneMinusSrcAlpha
    \value Blending.DstAlpha
    \value Blending.OneMinusDstAlpha
    \value Blending.ConstantColor
    \value Blending.OneMinusConstantColor
    \value Blending.ConstantAlpha
    \value Blending.OneMinusConstantAlpha
    \value Blending.SrcAlphaSaturate

*/
/*!
    \qmlproperty enumeration Blending::destBlending
    Specifies the destination blending function.

    \value Blending.Unknown
    \value Blending.Zero
    \value Blending.One
    \value Blending.SrcColor
    \value Blending.OneMinusSrcColor
    \value Blending.DstColor
    \value Blending.OneMinusDstColor
    \value Blending.SrcAlpha
    \value Blending.OneMinusSrcAlpha
    \value Blending.DstAlpha
    \value Blending.OneMinusDstAlpha
    \value Blending.ConstantColor
    \value Blending.OneMinusConstantColor
    \value Blending.ConstantAlpha
    \value Blending.OneMinusConstantAlpha
*/

/*!
    \qmltype Buffer
    \inherits Object
    \inqmlmodule QtQuick3D
    \brief Defines a buffer to be used for a pass of a CustomMaterial or an Effect.
*/
/*!
    \qmlproperty enumeration Buffer::format
    Specifies the buffer format.

    \value Buffer.Unknown
    \value Buffer.R8
    \value Buffer.R16
    \value Buffer.R16F
    \value Buffer.R32I
    \value Buffer.R32UI
    \value Buffer.R32F
    \value Buffer.RG8
    \value Buffer.RGBA8
    \value Buffer.RGB8
    \value Buffer.SRGB8
    \value Buffer.SRGB8A8
    \value Buffer.RGB565
    \value Buffer.RGBA16F
    \value Buffer.RG16F
    \value Buffer.RG32F
    \value Buffer.RGB32F
    \value Buffer.RGBA32F
    \value Buffer.R11G11B10
    \value Buffer.RGB9E5
    \value Buffer.Depth16
    \value Buffer.Depth24
    \value Buffer.Depth32
    \value Buffer.Depth24Stencil8
*/
/*!
    \qmlproperty enumeration Buffer::textureFilterOperation
    Specifies the filter operation when a render \l {Pass}{pass} is reading the buffer that is
    different size as the current output buffer.

    \value Buffer.Unknown Value not set.
    \value Buffer.Nearest Use nearest-neighbor.
    \value Buffer.Linear Use linear filtering.
*/
/*!
    \qmlproperty enumeration Buffer::textureCoordOperation
    Specifies the texture coordinate operation for coordinates outside [0, 1] range.

    \value Buffer.Unknown Value not set.
    \value Buffer.ClampToEdge Clamp coordinate to edge.
    \value Buffer.MirroredRepeat Repeat the coordinate, but flip direction at the beginning and end.
    \value Buffer.Repeat Repeat the coordinate always from the beginning.
*/
/*!
    \qmlproperty real Buffer::sizeMultiplier
    Specifies the size multiplier of the buffer. \c 1.0 creates buffer with the same size while
    \c 0.5 creates buffer with width and height halved.
*/
/*!
    \qmlproperty enumeration Buffer::bufferFlags
    Specifies the buffer allocation flags.

    \value Buffer.None Value not set.
    \value Buffer.SceneLifetime The buffer is allocated for the whole lifetime of the scene.
*/
/*!
    \qmlproperty string Buffer::name
    Specifies the name of the buffer
*/

/*!
    \qmltype RenderState
    \inherits Command
    \inqmlmodule QtQuick3D
    \brief Defines the render state to be disabled in a pass of a CustomMaterial or an Effect.
*/
/*!
    \qmlproperty enumeration RenderState::renderState
    Specifies the render state to enable/disable in a \l {Pass}{pass}.

    \value RenderState.Unknown
    \value RenderState.Blend
    \value RenderState.CullFace
    \value RenderState.DepthTest
    \value RenderState.StencilTest
    \value RenderState.ScissorTest
    \value RenderState.DepthWrite
    \value RenderState.Multisample
*/
/*!
    \qmlproperty bool RenderState::enable
    Specifies if the state is enabled or disabled.
*/

/*!
    \qmltype CullMode
    \inherits Command
    \inqmlmodule QtQuick3D
    \brief Defines the cull mode for render pass.
    \since 5.15

    \note This command can only be used with the CustomMaterial.
*/
/*!
    \qmlproperty enumeration CullMode::cullMode
    Specifies the culling mode in a \l {Pass}{pass} when \c RenderState.CullFace is enabled.
    The material culling mode is overridden.

    \value Material.BackFaceCulling
    \value Material.FrontFaceCulling
    \value Material.NoCulling
*/

/*!
    \qmltype DepthInput
    \inherits Command
    \inqmlmodule QtQuick3D
    \brief Defines the output texture for the depth buffer.
    \since 5.15
*/
/*!
    \qmlproperty string DepthInput::param
    Specifies the name of the texture the depth buffer will bind to.
*/

/*!
    \qmltype SetUniformValue
    \inherits Command
    \inqmlmodule QtQuick3D
    \brief Defines a value to be set during a single \l {Pass}{pass}.
    \since 5.15

    \note The value set by this command is will only be set during the \l {Pass}{pass} it occurs in.
    For consecutive passes the value will be revert to the initial value of the uniform as it
    was defined in the effect or custom material item.
*/
/*!
    \qmlproperty string SetUniformValue::target
    Specifies the name of the uniform that will have its value changed during the \l {Pass}{pass}.
*/
/*!
    \qmlproperty Variant SetUniformValue::value
    Specifies the value that will be set on the \c target uniform.
*/

void QSSGShaderUtils::addSnapperSampler(const QByteArray &texName, QByteArray &shaderPrefix)
{
    const char *filter = "linear";
    const char *clamp = "clamp";
    // Output macro so we can change the set of variables used for this
    // independent of the
    // meta data system.
    shaderPrefix.append("SNAPPER_SAMPLER2D(");
    shaderPrefix.append(texName);
    shaderPrefix.append(", ");
    shaderPrefix.append(texName);
    shaderPrefix.append(", ");
    shaderPrefix.append(filter);
    shaderPrefix.append(", ");
    shaderPrefix.append(clamp);
    shaderPrefix.append(", ");
    shaderPrefix.append("false )\n");
}

QByteArray QSSGShaderUtils::resolveShader(const QByteArray &shader, QByteArray &shaderPath)
{
    if (!shaderPath.isEmpty())
        shaderPath.append('>');

    int offset = -1;
    if (shader.startsWith("qrc:/"))
        offset = 3;
    else if (shader.startsWith("file:/"))
        offset = 6;
    else if (shader.startsWith(":/"))
        offset = 0;

    QString path;
    if (offset == -1) {
        QUrl u(QString::fromUtf8(shader));
        if (u.isLocalFile())
            path = u.toLocalFile();
    }

    if (offset == -1 && path.isEmpty())
        path = QString::fromLatin1(":/") + QString::fromLocal8Bit(shader);
    else
        path = QString::fromLocal8Bit(shader.constData() + offset);

    QFile f(path);
    if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
        shaderPath += path.toLatin1();
        return f.readAll();
    }

    shaderPath += QByteArrayLiteral("Inline_") + QByteArray::number(qHash(shader, uint(qGlobalQHashSeed())));

    return shader;
}

QByteArray QSSGShaderUtils::mergeShaderCode(const QByteArray &shared,
                                            const QByteArray &uniforms,
                                            const QByteArray &textures,
                                            const QByteArray &vertex,
                                            const QByteArray &geometry,
                                            const QByteArray &fragment)
{
    QByteArray shaderCode;
    // Shared
    if (!shared.isEmpty())
        shaderCode.append(shared);

    if (!textures.isEmpty())
        shaderCode.append(textures);

    if (!uniforms.isEmpty())
        shaderCode.append(uniforms);

    // Vetex
    shaderCode.append(QByteArrayLiteral("\n#ifdef VERTEX_SHADER\n"));
    if (!vertex.isEmpty())
        shaderCode.append(vertex);
    else
        shaderCode.append(QByteArrayLiteral("void vert(){}"));
    shaderCode.append(QByteArrayLiteral("\n#endif\n"));

    // Geometry
    if (!geometry.isEmpty()) {
        shaderCode.append(QByteArrayLiteral("\n#ifdef USER_GEOMETRY_SHADER\n"));
        shaderCode.append(geometry);
        shaderCode.append(QByteArrayLiteral("\n#endif\n"));
    }

    // Fragment
    shaderCode.append(QByteArrayLiteral("\n#ifdef FRAGMENT_SHADER\n"));
    if (!fragment.isEmpty())
        shaderCode.append(fragment);
    else
        shaderCode.append(QByteArrayLiteral("void frag(){}"));
    shaderCode.append(QByteArrayLiteral("\n#endif\n"));

    return shaderCode;
}

QQuick3DShaderUtilsBuffer::TextureFormat QQuick3DShaderUtilsBuffer::mapRenderTextureFormat(QSSGRenderTextureFormat::Format fmt)
{
    using TextureFormat = QQuick3DShaderUtilsBuffer::TextureFormat;
    switch (fmt) {
    case QSSGRenderTextureFormat::R8: return TextureFormat::R8;
    case QSSGRenderTextureFormat::R16: return TextureFormat::R16;
    case QSSGRenderTextureFormat::R16F: return TextureFormat::R16F;
    case QSSGRenderTextureFormat::R32I: return TextureFormat::R32I;
    case QSSGRenderTextureFormat::R32UI: return TextureFormat::R32UI;
    case QSSGRenderTextureFormat::R32F: return TextureFormat::R32F;
    case QSSGRenderTextureFormat::RG8: return TextureFormat::RG8;
    case QSSGRenderTextureFormat::RGBA8: return TextureFormat::RGBA8;
    case QSSGRenderTextureFormat::RGB8: return TextureFormat::RGB8;
    case QSSGRenderTextureFormat::SRGB8: return TextureFormat::SRGB8;
    case QSSGRenderTextureFormat::SRGB8A8: return TextureFormat::SRGB8A8;
    case QSSGRenderTextureFormat::RGB565: return TextureFormat::RGB565;
    case QSSGRenderTextureFormat::RGBA16F: return TextureFormat::RGBA16F;
    case QSSGRenderTextureFormat::RG16F: return TextureFormat::RG16F;
    case QSSGRenderTextureFormat::RG32F: return TextureFormat::RG32F;
    case QSSGRenderTextureFormat::RGB32F: return TextureFormat::RGB32F;
    case QSSGRenderTextureFormat::RGBA32F: return TextureFormat::RGBA32F;
    case QSSGRenderTextureFormat::R11G11B10: return TextureFormat::R11G11B10;
    case QSSGRenderTextureFormat::RGB9E5: return TextureFormat::RGB9E5;
    case QSSGRenderTextureFormat::Depth16: return TextureFormat::Depth16;
    case QSSGRenderTextureFormat::Depth24: return TextureFormat::Depth24;
    case QSSGRenderTextureFormat::Depth32: return TextureFormat::Depth32;
    case QSSGRenderTextureFormat::Depth24Stencil8: return TextureFormat::Depth24Stencil8;
    default:
        break;
    }
    return TextureFormat::Unknown;
}

QSSGRenderTextureFormat::Format QQuick3DShaderUtilsBuffer::mapTextureFormat(QQuick3DShaderUtilsBuffer::TextureFormat fmt)
{
    using TextureFormat = QQuick3DShaderUtilsBuffer::TextureFormat;
    switch (fmt) {
    case TextureFormat::R8: return QSSGRenderTextureFormat::R8;
    case TextureFormat::R16: return QSSGRenderTextureFormat::R16;
    case TextureFormat::R16F: return QSSGRenderTextureFormat::R16F;
    case TextureFormat::R32I: return QSSGRenderTextureFormat::R32I;
    case TextureFormat::R32UI: return QSSGRenderTextureFormat::R32UI;
    case TextureFormat::R32F: return QSSGRenderTextureFormat::R32F;
    case TextureFormat::RG8: return QSSGRenderTextureFormat::RG8;
    case TextureFormat::RGBA8: return QSSGRenderTextureFormat::RGBA8;
    case TextureFormat::RGB8: return QSSGRenderTextureFormat::RGB8;
    case TextureFormat::SRGB8: return QSSGRenderTextureFormat::SRGB8;
    case TextureFormat::SRGB8A8: return QSSGRenderTextureFormat::SRGB8A8;
    case TextureFormat::RGB565: return QSSGRenderTextureFormat::RGB565;
    case TextureFormat::RGBA16F: return QSSGRenderTextureFormat::RGBA16F;
    case TextureFormat::RG16F: return QSSGRenderTextureFormat::RG16F;
    case TextureFormat::RG32F: return QSSGRenderTextureFormat::RG32F;
    case TextureFormat::RGB32F: return QSSGRenderTextureFormat::RGB32F;
    case TextureFormat::RGBA32F: return QSSGRenderTextureFormat::RGBA32F;
    case TextureFormat::R11G11B10: return QSSGRenderTextureFormat::R11G11B10;
    case TextureFormat::RGB9E5: return QSSGRenderTextureFormat::RGB9E5;
    case TextureFormat::Depth16: return QSSGRenderTextureFormat::Depth16;
    case TextureFormat::Depth24: return QSSGRenderTextureFormat::Depth24;
    case TextureFormat::Depth32: return QSSGRenderTextureFormat::Depth32;
    case TextureFormat::Depth24Stencil8: return QSSGRenderTextureFormat::Depth24Stencil8;
    default:
        break;
    }
    return QSSGRenderTextureFormat::Unknown;
}

QQuick3DShaderUtilsBuffer::TextureFormat QQuick3DShaderUtilsBuffer::format() const
{
    return mapRenderTextureFormat(command.m_format.format);
}

void QQuick3DShaderUtilsBuffer::setFormat(TextureFormat format)
{
    command.m_format = mapTextureFormat(format);
}

void QQuick3DShaderUtilsRenderPass::qmlAppendCommand(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list,
                                                     QQuick3DShaderUtilsRenderCommand *command)
{
    if (!command)
        return;

    QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
    that->m_commands.push_back(command);
}

QQuick3DShaderUtilsRenderCommand *QQuick3DShaderUtilsRenderPass::qmlCommandAt(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list,
                                                                              int index)
{
    QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
    return that->m_commands.at(index);
}

int QQuick3DShaderUtilsRenderPass::qmlCommandCount(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
{
    QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
    return that->m_commands.count();
}

QQmlListProperty<QQuick3DShaderUtilsRenderCommand> QQuick3DShaderUtilsRenderPass::commands()
{
    return QQmlListProperty<QQuick3DShaderUtilsRenderCommand>(this,
                                                             nullptr,
                                                             QQuick3DShaderUtilsRenderPass::qmlAppendCommand,
                                                             QQuick3DShaderUtilsRenderPass::qmlCommandCount,
                                                             QQuick3DShaderUtilsRenderPass::qmlCommandAt,
                                                             nullptr);
}

void QQuick3DShaderUtilsRenderPass::qmlAppendShader(QQmlListProperty<QQuick3DShaderUtilsShader> *list,
                                                    QQuick3DShaderUtilsShader *shader)
{
    if (!shader)
        return;

    QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
    that->m_shaders[int(shader->stage)] = shader;
}

QQuick3DShaderUtilsShader *QQuick3DShaderUtilsRenderPass::qmlShaderAt(QQmlListProperty<QQuick3DShaderUtilsShader> *list,
                                                                      int index)
{
    QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
    return that->m_shaders.at(index);
}

int QQuick3DShaderUtilsRenderPass::qmlShaderCount(QQmlListProperty<QQuick3DShaderUtilsShader> *list)
{
    QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
    return that->m_shaders.count();
}

void QQuick3DShaderUtilsRenderPass::qmlShaderClear(QQmlListProperty<QQuick3DShaderUtilsShader> *list)
{
    QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
    auto it = that->m_shaders.begin();
    const auto end = that->m_shaders.end();
    for (;it != end; ++it)
        *it = nullptr;
}

QQmlListProperty<QQuick3DShaderUtilsShader> QQuick3DShaderUtilsRenderPass::shaders()
{
    return QQmlListProperty<QQuick3DShaderUtilsShader>(this,
                                                      nullptr,
                                                      QQuick3DShaderUtilsRenderPass::qmlAppendShader,
                                                      QQuick3DShaderUtilsRenderPass::qmlShaderCount,
                                                      QQuick3DShaderUtilsRenderPass::qmlShaderAt,
                                                      QQuick3DShaderUtilsRenderPass::qmlShaderClear);
}

void QQuick3DShaderUtilsTextureInput::setTexture(QQuick3DTexture *texture)
{
    if (m_texture == texture)
        return;

    QObject *p = parent();
    while (p != nullptr) {
        if (QQuick3DMaterial *mat = qobject_cast<QQuick3DMaterial *>(p)) {
            mat->setDynamicTextureMap(texture, name);
            break;
        } else if (QQuick3DEffect *efx = qobject_cast<QQuick3DEffect *>(p)) {
            efx->setDynamicTextureMap(texture, name);
            break;
        }
        p = p->parent();
    }

    if (p == nullptr) {
        qWarning("A texture was defined out of Material or Effect");
    }

    m_texture = texture;
    Q_EMIT textureDirty(this);
}

QT_END_NAMESPACE
