/****************************************************************************
**
** Copyright (C) 2008-2012 NVIDIA Corporation.
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:COMM$
**
** 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.
**
** $QT_END_LICENSE$
**
**
**
**
**
**
**
**
**
****************************************************************************/

#ifndef QSSG_RENDER_CUSTOM_MATERIAL_SYSTEM_H
#define QSSG_RENDER_CUSTOM_MATERIAL_SYSTEM_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QtQuick3DRuntimeRender/private/qtquick3druntimerenderglobal_p.h>

#include <QtQuick3DRuntimeRender/private/qssgrenderdynamicobjectsystem_p.h>
#include <QtQuick3DRuntimeRender/private/qssgvertexpipelineimpl_p.h>

#include <QtCore/qhash.h>

#include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h> // Make it possible to forward declare the nested TextureProperty

QT_BEGIN_NAMESPACE

namespace dynamic {
struct QSSGCommand;
}

struct QSSGCustomMaterialRenderContext;
struct QSSGRenderCustomMaterial;
class QSSGMaterialSystem;
struct QSSGRenderSubset;
struct QSSGShaderMapKey;
struct QSSGRenderCustomMaterialShader;
struct QSSGMaterialClass;
struct QSSGCustomMaterialTextureData;
struct QSSGRenderCustomMaterialBuffer;
struct QSSGMaterialOrComputeShader;
namespace dynamic {
struct QSSGBindShader;
struct QSSGApplyInstanceValue;
struct QSSGApplyBlending;
struct QSSGAllocateBuffer;
struct QSSGApplyBufferValue;
struct QSSGBindBuffer;
struct QSSGApplyBlitFramebuffer;
struct QSSGApplyRenderState;
}

// How to handle blend modes?
struct QSSGRenderModel;

class Q_QUICK3DRUNTIMERENDER_EXPORT QSSGMaterialSystem
{
    Q_DISABLE_COPY(QSSGMaterialSystem)
public:
    QAtomicInt ref;

private:
    typedef QHash<QSSGShaderMapKey, QSSGRef<QSSGRenderCustomMaterialShader>> ShaderMap;
    typedef QPair<QByteArray, QByteArray> TStrStrPair;
    typedef QPair<QByteArray, QSSGRef<QSSGCustomMaterialTextureData>> CustomMaterialTextureEntry;

    QSSGRenderContextInterface *context = nullptr;
    ShaderMap shaderMap;
    QVector<CustomMaterialTextureEntry> textureEntries;
    QVector<QSSGRenderCustomMaterialBuffer> allocatedBuffers;
    bool useFastBlits = true;
    QString shaderNameBuilder;
#ifdef QQ3D_UNUSED_TIMER
    QElapsedTimer lastFrameTime;
    float msSinceLastFrame = 0;
#endif // QQ3D_UNUSED_TIMER

    void releaseBuffer(qint32 inIdx);

    QSSGRef<QSSGRenderShaderProgram> getShader(QSSGCustomMaterialRenderContext &inRenderContext,
                                                   const QSSGRenderCustomMaterial &inMaterial,
                                                   const dynamic::QSSGBindShader &inCommand,
                                                   const ShaderFeatureSetList &inFeatureSet,
                                                   const dynamic::QSSGDynamicShaderProgramFlags &inFlags);

    QSSGMaterialOrComputeShader bindShader(QSSGCustomMaterialRenderContext &inRenderContext,
                                             const QSSGRenderCustomMaterial &inMaterial,
                                             const dynamic::QSSGBindShader &inCommand,
                                             const ShaderFeatureSetList &inFeatureSet);

    void doApplyInstanceValue(QSSGRenderCustomMaterial &inMaterial,
                              const QByteArray &propertyName,
                              const QVariant &propertyValue,
                              QSSGRenderShaderDataType inPropertyType,
                              const QSSGRef<QSSGRenderShaderProgram> &inShader);

    void applyInstanceValue(QSSGRenderCustomMaterial &inMaterial,
                            const QSSGRef<QSSGRenderShaderProgram> &inShader,
                            const dynamic::QSSGApplyInstanceValue &inCommand);

    void applyBlending(const dynamic::QSSGApplyBlending &inCommand);
    void applyCullMode(const dynamic::QSSGApplyCullMode &inCommand);

    void applyRenderStateValue(const dynamic::QSSGApplyRenderState &inCommand);

    // we currently only bind a source texture
    QSSGRef<QSSGRenderTexture2D> applyBufferValue(const QSSGRenderCustomMaterial &inMaterial,
                                                            const QSSGRef<QSSGRenderShaderProgram> &inShader,
                                                            const dynamic::QSSGApplyBufferValue &inCommand,
                                                            const QSSGRef<QSSGRenderTexture2D> &inSourceTexture);

    void allocateBuffer(const dynamic::QSSGAllocateBuffer &inCommand, const QSSGRef<QSSGRenderFrameBuffer> &inTarget);

    QSSGRef<QSSGRenderFrameBuffer> bindBuffer(const QSSGRenderCustomMaterial &inMaterial,
                                                  const dynamic::QSSGBindBuffer &inCommand,
                                                  bool &outClearTarget,
                                                  QVector2D &outDestSize);
    void computeScreenCoverage(QSSGCustomMaterialRenderContext &inRenderContext, qint32 *xMin, qint32 *yMin, qint32 *xMax, qint32 *yMax);
    void blitFramebuffer(QSSGCustomMaterialRenderContext &inRenderContext,
                         const dynamic::QSSGApplyBlitFramebuffer &inCommand,
                         const QSSGRef<QSSGRenderFrameBuffer> &inTarget);
    QSSGLayerGlobalRenderProperties getLayerGlobalRenderProperties(QSSGCustomMaterialRenderContext &inRenderContext);
    void renderPass(QSSGCustomMaterialRenderContext &inRenderContext,
                    const QSSGRef<QSSGRenderCustomMaterialShader> &inShader,
                    const QSSGRef<QSSGRenderTexture2D> & /* inSourceTexture */,
                    const QSSGRef<QSSGRenderFrameBuffer> &inFrameBuffer,
                    bool inRenderTargetNeedsClear,
                    const QSSGRef<QSSGRenderInputAssembler> &inAssembler,
                    quint32 inCount,
                    quint32 inOffset,
                    bool applyCullMode);
    void doRenderCustomMaterial(QSSGCustomMaterialRenderContext &inRenderContext,
                                const QSSGRenderCustomMaterial &inMaterial,
                                const ShaderFeatureSetList &inFeatureSet);
    void prepareDisplacementForRender(QSSGRenderCustomMaterial &inMaterial);
    void prepareMaterialForRender(QSSGRenderCustomMaterial &inMaterial);

    qint32 findBuffer(const QByteArray &inName) const;
    bool textureNeedsMips(const QSSGRenderCustomMaterial::TextureProperty *inPropDec, QSSGRenderTexture2D *inTexture);
    void setTexture(const QSSGRef<QSSGRenderShaderProgram> &inShader,
                    const QByteArray &inPropName,
                    const QSSGRef<QSSGRenderTexture2D> &inTexture,
                    const QSSGRenderCustomMaterial::TextureProperty *inPropDec = nullptr,
                    bool needMips = false);

public:
    QSSGMaterialSystem(QSSGRenderContextInterface *ct);

    ~QSSGMaterialSystem();

    void setMaterialClassShader(const QByteArray &inName,
                                const QByteArray &inShaderType,
                                const QByteArray &inShaderVersion,
                                const QByteArray &inShaderData,
                                bool inHasGeomShader,
                                bool inIsComputeShader);

    void setRenderContextInterface(QSSGRenderContextInterface *inContext);

    // Returns true if the material is dirty and thus will produce a different render result
    // than previously.  This effects things like progressive AA.
    bool prepareForRender(const QSSGRenderModel &inModel,
                          const QSSGRenderSubset &inSubset,
                          QSSGRenderCustomMaterial &inMaterial);

    bool renderDepthPrepass(const QMatrix4x4 &inMVP, const QSSGRenderCustomMaterial &inMaterial, const QSSGRenderSubset &inSubset);
    void renderSubset(QSSGCustomMaterialRenderContext &inRenderContext, const ShaderFeatureSetList &inFeatureSet);

    // get shader name
    QByteArray getShaderName(const QSSGRenderCustomMaterial &inMaterial);
    // apply property values
    void applyShaderPropertyValues(const QSSGRenderCustomMaterial &inMaterial, const QSSGRef<QSSGRenderShaderProgram> &inProgram);
    // Called by the uiccontext so this system can clear any per-frame render information.
    void endFrame();
};

struct Q_QUICK3DRUNTIMERENDER_EXPORT QSSGCustomMaterialVertexPipeline : public QSSGVertexPipelineImpl
{
    QSSGRenderContextInterface *m_context;
    TessellationModeValues m_tessMode;

    QSSGCustomMaterialVertexPipeline(QSSGRenderContextInterface *inContext, TessellationModeValues inTessMode);
    void initializeTessControlShader();
    void initializeTessEvaluationShader();
    void finalizeTessControlShader();
    void finalizeTessEvaluationShader();

    // Responsible for beginning all vertex and fragment generation (void main() { etc).
    virtual void beginVertexGeneration(const QSSGShaderDefaultMaterialKey &inKey, quint32 displacementImageIdx, QSSGRenderableImage *displacementImage) override;
    // The fragment shader expects a floating point constant, objectOpacity to be defined
    // post this method.
    virtual void beginFragmentGeneration() override;
    // Output variables may be mangled in some circumstances so the shader generation
    // system needs an abstraction mechanism around this.
    virtual void assignOutput(const QByteArray &inVarName, const QByteArray &inVarValue) override;
    virtual void generateEnvMapReflection(const QSSGShaderDefaultMaterialKey &inKey) override { Q_UNUSED(inKey); }
    virtual void generateViewVector() override {}
    virtual void generateUVCoords(const QSSGShaderDefaultMaterialKey &inKey, quint32 inUVSet) override;
    virtual void generateWorldNormal(const QSSGShaderDefaultMaterialKey &inKey) override;
    virtual void generateObjectNormal() override;
    virtual void generateVarTangentAndBinormal(const QSSGShaderDefaultMaterialKey &inKey) override;
    virtual void generateWorldPosition() override;
    // responsible for closing all vertex and fragment generation
    virtual void endVertexGeneration(bool customShader) override;
    virtual void endFragmentGeneration(bool customShader) override;
    virtual QSSGShaderStageGeneratorInterface &activeStage() override;
    virtual void addInterpolationParameter(const QByteArray &inName, const QByteArray &inType) override;
    virtual void doGenerateUVCoords(quint32 inUVSet) override;
    virtual void doGenerateWorldNormal() override;
    virtual void doGenerateObjectNormal() override;
    virtual void doGenerateWorldPosition() override;
    virtual void doGenerateVarTangent() override;
    virtual void doGenerateVarBinormal() override;
    virtual void doGenerateVertexColor(const QSSGShaderDefaultMaterialKey &inKey) override;
};
QT_END_NAMESPACE
#endif
