//
// Copyright 2023 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// ProgramExecutableD3D.h: Implementation of ProgramExecutableImpl.

#ifndef LIBANGLE_RENDERER_D3D_PROGRAMEXECUTABLED3D_H_
#define LIBANGLE_RENDERER_D3D_PROGRAMEXECUTABLED3D_H_

#include "compiler/translator/hlsl/blocklayoutHLSL.h"
#include "libANGLE/ProgramExecutable.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/ProgramExecutableImpl.h"
#include "libANGLE/renderer/d3d/DynamicHLSL.h"

namespace rx
{
class RendererD3D;
class UniformStorageD3D;
class ShaderExecutableD3D;

#if !defined(ANGLE_COMPILE_OPTIMIZATION_LEVEL)
// WARNING: D3DCOMPILE_OPTIMIZATION_LEVEL3 may lead to a DX9 shader compiler hang.
// It should only be used selectively to work around specific bugs.
#    define ANGLE_COMPILE_OPTIMIZATION_LEVEL D3DCOMPILE_OPTIMIZATION_LEVEL1
#endif

enum class HLSLRegisterType : uint8_t
{
    None                = 0,
    Texture             = 1,
    UnorderedAccessView = 2
};

// Helper struct representing a single shader uniform
// TODO(jmadill): Make uniform blocks shared between all programs, so we don't need separate
// register indices.
struct D3DUniform : private angle::NonCopyable
{
    D3DUniform(GLenum type,
               HLSLRegisterType reg,
               const std::string &nameIn,
               const std::vector<unsigned int> &arraySizesIn,
               bool defaultBlock);
    ~D3DUniform();

    bool isSampler() const;
    bool isImage() const;
    bool isImage2D() const;
    bool isArray() const { return !arraySizes.empty(); }
    unsigned int getArraySizeProduct() const;
    bool isReferencedByShader(gl::ShaderType shaderType) const;

    const uint8_t *firstNonNullData() const;
    const uint8_t *getDataPtrToElement(size_t elementIndex) const;

    // Duplicated from the GL layer
    const gl::UniformTypeInfo &typeInfo;
    std::string name;  // Names of arrays don't include [0], unlike at the GL layer.
    std::vector<unsigned int> arraySizes;

    // Pointer to a system copies of the data. Separate pointers for each uniform storage type.
    gl::ShaderMap<uint8_t *> mShaderData;

    // Register information.
    HLSLRegisterType regType;
    gl::ShaderMap<unsigned int> mShaderRegisterIndexes;
    unsigned int registerCount;

    // Register "elements" are used for uniform structs in ES3, to appropriately identify single
    // uniforms
    // inside aggregate types, which are packed according C-like structure rules.
    unsigned int registerElement;

    // Special buffer for sampler values.
    std::vector<GLint> mSamplerData;
};

struct D3DInterfaceBlock
{
    D3DInterfaceBlock();
    D3DInterfaceBlock(const D3DInterfaceBlock &other);

    bool activeInShader(gl::ShaderType shaderType) const
    {
        return mShaderRegisterIndexes[shaderType] != GL_INVALID_INDEX;
    }

    gl::ShaderMap<unsigned int> mShaderRegisterIndexes;
};

struct D3DUniformBlock : D3DInterfaceBlock
{
    D3DUniformBlock();
    D3DUniformBlock(const D3DUniformBlock &other);

    gl::ShaderMap<bool> mUseStructuredBuffers;
    gl::ShaderMap<unsigned int> mByteWidths;
    gl::ShaderMap<unsigned int> mStructureByteStrides;
};

struct D3DUBOCache
{
    unsigned int registerIndex;
    int binding;
};

struct D3DUBOCacheUseSB : D3DUBOCache
{
    unsigned int byteWidth;
    unsigned int structureByteStride;
};

struct D3DVarying final
{
    D3DVarying();
    D3DVarying(const std::string &semanticNameIn,
               unsigned int semanticIndexIn,
               unsigned int componentCountIn,
               unsigned int outputSlotIn);

    D3DVarying(const D3DVarying &)            = default;
    D3DVarying &operator=(const D3DVarying &) = default;

    std::string semanticName;
    unsigned int semanticIndex;
    unsigned int componentCount;
    unsigned int outputSlot;
};

using D3DUniformMap = std::map<std::string, D3DUniform *>;

class D3DVertexExecutable
{
  public:
    enum HLSLAttribType
    {
        FLOAT,
        UNSIGNED_INT,
        SIGNED_INT,
    };

    typedef std::vector<HLSLAttribType> Signature;

    D3DVertexExecutable(const gl::InputLayout &inputLayout,
                        const Signature &signature,
                        ShaderExecutableD3D *shaderExecutable);
    ~D3DVertexExecutable();

    bool matchesSignature(const Signature &signature) const;
    static void getSignature(RendererD3D *renderer,
                             const gl::InputLayout &inputLayout,
                             Signature *signatureOut);

    const gl::InputLayout &inputs() const { return mInputs; }
    const Signature &signature() const { return mSignature; }
    ShaderExecutableD3D *shaderExecutable() const { return mShaderExecutable; }

  private:
    static HLSLAttribType GetAttribType(GLenum type);

    gl::InputLayout mInputs;
    Signature mSignature;
    ShaderExecutableD3D *mShaderExecutable;
};

class D3DPixelExecutable
{
  public:
    D3DPixelExecutable(const std::vector<GLenum> &outputSignature,
                       const gl::ImageUnitTextureTypeMap &image2DSignature,
                       ShaderExecutableD3D *shaderExecutable);
    ~D3DPixelExecutable();

    bool matchesSignature(const std::vector<GLenum> &outputSignature,
                          const gl::ImageUnitTextureTypeMap &image2DSignature) const
    {
        return mOutputSignature == outputSignature && mImage2DSignature == image2DSignature;
    }

    const std::vector<GLenum> &outputSignature() const { return mOutputSignature; }

    const gl::ImageUnitTextureTypeMap &image2DSignature() const { return mImage2DSignature; }

    ShaderExecutableD3D *shaderExecutable() const { return mShaderExecutable; }

  private:
    const std::vector<GLenum> mOutputSignature;
    const gl::ImageUnitTextureTypeMap mImage2DSignature;
    ShaderExecutableD3D *mShaderExecutable;
};

struct D3DSampler
{
    D3DSampler();

    bool active;
    GLint logicalTextureUnit;
    gl::TextureType textureType;
};

struct D3DImage
{
    D3DImage();
    bool active;
    GLint logicalImageUnit;
};

class ProgramExecutableD3D : public ProgramExecutableImpl
{
  public:
    ProgramExecutableD3D(const gl::ProgramExecutable *executable);
    ~ProgramExecutableD3D() override;

    void destroy(const gl::Context *context) override;

    void setUniform1fv(GLint location, GLsizei count, const GLfloat *v) override;
    void setUniform2fv(GLint location, GLsizei count, const GLfloat *v) override;
    void setUniform3fv(GLint location, GLsizei count, const GLfloat *v) override;
    void setUniform4fv(GLint location, GLsizei count, const GLfloat *v) override;
    void setUniform1iv(GLint location, GLsizei count, const GLint *v) override;
    void setUniform2iv(GLint location, GLsizei count, const GLint *v) override;
    void setUniform3iv(GLint location, GLsizei count, const GLint *v) override;
    void setUniform4iv(GLint location, GLsizei count, const GLint *v) override;
    void setUniform1uiv(GLint location, GLsizei count, const GLuint *v) override;
    void setUniform2uiv(GLint location, GLsizei count, const GLuint *v) override;
    void setUniform3uiv(GLint location, GLsizei count, const GLuint *v) override;
    void setUniform4uiv(GLint location, GLsizei count, const GLuint *v) override;
    void setUniformMatrix2fv(GLint location,
                             GLsizei count,
                             GLboolean transpose,
                             const GLfloat *value) override;
    void setUniformMatrix3fv(GLint location,
                             GLsizei count,
                             GLboolean transpose,
                             const GLfloat *value) override;
    void setUniformMatrix4fv(GLint location,
                             GLsizei count,
                             GLboolean transpose,
                             const GLfloat *value) override;
    void setUniformMatrix2x3fv(GLint location,
                               GLsizei count,
                               GLboolean transpose,
                               const GLfloat *value) override;
    void setUniformMatrix3x2fv(GLint location,
                               GLsizei count,
                               GLboolean transpose,
                               const GLfloat *value) override;
    void setUniformMatrix2x4fv(GLint location,
                               GLsizei count,
                               GLboolean transpose,
                               const GLfloat *value) override;
    void setUniformMatrix4x2fv(GLint location,
                               GLsizei count,
                               GLboolean transpose,
                               const GLfloat *value) override;
    void setUniformMatrix3x4fv(GLint location,
                               GLsizei count,
                               GLboolean transpose,
                               const GLfloat *value) override;
    void setUniformMatrix4x3fv(GLint location,
                               GLsizei count,
                               GLboolean transpose,
                               const GLfloat *value) override;

    void getUniformfv(const gl::Context *context, GLint location, GLfloat *params) const override;
    void getUniformiv(const gl::Context *context, GLint location, GLint *params) const override;
    void getUniformuiv(const gl::Context *context, GLint location, GLuint *params) const override;

    void updateCachedInputLayoutFromShader(RendererD3D *renderer,
                                           const gl::SharedCompiledShaderState &vertexShader);
    void updateCachedOutputLayoutFromShader();
    void updateCachedImage2DBindLayoutFromShader(gl::ShaderType shaderType);

    void updateCachedInputLayout(RendererD3D *renderer,
                                 UniqueSerial associatedSerial,
                                 const gl::State &state);
    void updateCachedOutputLayout(const gl::Context *context, const gl::Framebuffer *framebuffer);
    void updateCachedImage2DBindLayout(const gl::Context *context, const gl::ShaderType shaderType);
    void updateUniformBufferCache(const gl::Caps &caps);

    // Checks if we need to recompile certain shaders.
    bool hasVertexExecutableForCachedInputLayout();
    bool hasGeometryExecutableForPrimitiveType(RendererD3D *renderer,
                                               const gl::State &state,
                                               gl::PrimitiveMode drawMode);
    bool hasPixelExecutableForCachedOutputLayout();

    bool anyShaderUniformsDirty() const { return mShaderUniformsDirty.any(); }
    bool areShaderUniformsDirty(gl::ShaderType shaderType) const
    {
        return mShaderUniformsDirty[shaderType];
    }
    void dirtyAllUniforms();
    void markUniformsClean();

    const std::vector<PixelShaderOutputVariable> &getPixelShaderKey() { return mPixelShaderKey; }

    void assignImage2DRegisters(gl::ShaderType shaderType,
                                unsigned int startImageIndex,
                                int startLogicalImageUnit,
                                bool readonly);

    const std::vector<D3DUniform *> &getD3DUniforms() const { return mD3DUniforms; }
    UniformStorageD3D *getShaderUniformStorage(gl::ShaderType shaderType) const
    {
        return mShaderUniformStorages[shaderType].get();
    }
    const AttribIndexArray &getAttribLocationToD3DSemantics() const
    {
        return mAttribLocationToD3DSemantic;
    }
    const std::vector<D3DUBOCache> &getShaderUniformBufferCache(gl::ShaderType shaderType) const;
    const std::vector<D3DUBOCacheUseSB> &getShaderUniformBufferCacheUseSB(
        gl::ShaderType shaderType) const;
    GLint getSamplerMapping(gl::ShaderType type,
                            unsigned int samplerIndex,
                            const gl::Caps &caps) const;
    gl::TextureType getSamplerTextureType(gl::ShaderType type, unsigned int samplerIndex) const;
    gl::RangeUI getUsedSamplerRange(gl::ShaderType type) const;

    bool isSamplerMappingDirty() const { return mDirtySamplerMapping; }
    void updateSamplerMapping();

    GLint getImageMapping(gl::ShaderType type,
                          unsigned int imageIndex,
                          bool readonly,
                          const gl::Caps &caps) const;
    gl::RangeUI getUsedImageRange(gl::ShaderType type, bool readonly) const;

    bool usesPointSize() const { return mUsesPointSize; }
    bool usesPointSpriteEmulation() const;
    bool usesGeometryShader(RendererD3D *renderer,
                            const gl::ProvokingVertexConvention provokingVertex,
                            gl::PrimitiveMode drawMode) const;
    bool usesGeometryShaderForPointSpriteEmulation() const;

    angle::Result getVertexExecutableForCachedInputLayout(d3d::Context *context,
                                                          RendererD3D *renderer,
                                                          ShaderExecutableD3D **outExectuable,
                                                          gl::InfoLog *infoLog);
    angle::Result getGeometryExecutableForPrimitiveType(
        d3d::Context *errContext,
        RendererD3D *renderer,
        const gl::Caps &caps,
        gl::ProvokingVertexConvention provokingVertex,
        gl::PrimitiveMode drawMode,
        ShaderExecutableD3D **outExecutable,
        gl::InfoLog *infoLog);
    angle::Result getPixelExecutableForCachedOutputLayout(d3d::Context *context,
                                                          RendererD3D *renderer,
                                                          ShaderExecutableD3D **outExectuable,
                                                          gl::InfoLog *infoLog);

    bool hasShaderStage(gl::ShaderType shaderType) const
    {
        return mExecutable->getLinkedShaderStages()[shaderType];
    }

    bool hasNamedUniform(const std::string &name);

    bool usesVertexID() const { return mUsesVertexID; }

    angle::Result loadBinaryShaderExecutables(d3d::Context *contextD3D,
                                              RendererD3D *renderer,
                                              gl::BinaryInputStream *stream);

    unsigned int getSerial() const { return mSerial; }

  private:
    friend class ProgramD3D;

    bool load(const gl::Context *context, RendererD3D *renderer, gl::BinaryInputStream *stream);
    void save(const gl::Context *context, RendererD3D *renderer, gl::BinaryOutputStream *stream);

    template <typename DestT>
    void getUniformInternal(GLint location, DestT *dataOut) const;

    template <typename T>
    void setUniformImpl(D3DUniform *targetUniform,
                        const gl::VariableLocation &locationInfo,
                        GLsizei count,
                        const T *v,
                        uint8_t *targetData,
                        GLenum uniformType);

    template <typename T>
    void setUniformInternal(GLint location, GLsizei count, const T *v, GLenum uniformType);

    template <int cols, int rows>
    void setUniformMatrixfvInternal(GLint location,
                                    GLsizei count,
                                    GLboolean transpose,
                                    const GLfloat *value);

    void initAttribLocationsToD3DSemantic(const gl::SharedCompiledShaderState &vertexShader);

    void reset();
    void initializeUniformBlocks();
    void initializeUniformStorage(RendererD3D *renderer,
                                  const gl::ShaderBitSet &availableShaderStages);

    void updateCachedVertexExecutableIndex();
    void updateCachedPixelExecutableIndex();

    void defineUniformsAndAssignRegisters(
        RendererD3D *renderer,
        const gl::ShaderMap<gl::SharedCompiledShaderState> &shaders);
    void defineUniformBase(gl::ShaderType shaderType,
                           const sh::ShaderVariable &uniform,
                           D3DUniformMap *uniformMap);
    void assignAllSamplerRegisters(const gl::ShaderMap<gl::SharedCompiledShaderState> &shaders);
    void assignSamplerRegisters(const gl::ShaderMap<gl::SharedCompiledShaderState> &shaders,
                                size_t uniformIndex);

    static void AssignSamplers(unsigned int startSamplerIndex,
                               const gl::UniformTypeInfo &typeInfo,
                               unsigned int samplerCount,
                               std::vector<D3DSampler> &outSamplers,
                               gl::RangeUI *outUsedRange);

    void assignAllImageRegisters();
    void assignImageRegisters(size_t uniformIndex);
    static void AssignImages(unsigned int startImageIndex,
                             int startLogicalImageUnit,
                             unsigned int imageCount,
                             std::vector<D3DImage> &outImages,
                             gl::RangeUI *outUsedRange);

    void gatherTransformFeedbackVaryings(
        const gl::VaryingPacking &varyings,
        const std::vector<std::string> &transformFeedbackVaryingNames,
        const BuiltinInfo &builtins);
    D3DUniform *getD3DUniformFromLocation(const gl::VariableLocation &locationInfo);
    const D3DUniform *getD3DUniformFromLocation(const gl::VariableLocation &locationInfo) const;

    gl::ShaderMap<SharedCompiledShaderStateD3D> mAttachedShaders;

    std::vector<std::unique_ptr<D3DVertexExecutable>> mVertexExecutables;
    std::vector<std::unique_ptr<D3DPixelExecutable>> mPixelExecutables;
    angle::PackedEnumMap<gl::PrimitiveMode, std::unique_ptr<ShaderExecutableD3D>>
        mGeometryExecutables;

    gl::ShaderMap<std::string> mShaderHLSL;
    gl::ShaderMap<CompilerWorkaroundsD3D> mShaderWorkarounds;

    FragDepthUsage mFragDepthUsage;
    bool mUsesSampleMask;
    bool mHasMultiviewEnabled;
    bool mUsesVertexID;
    bool mUsesViewID;
    std::vector<PixelShaderOutputVariable> mPixelShaderKey;

    // Common code for all dynamic geometry shaders. Consists mainly of the GS input and output
    // structures, built from the linked varying info. We store the string itself instead of the
    // packed varyings for simplicity.
    std::string mGeometryShaderPreamble;

    bool mUsesPointSize;
    bool mUsesFlatInterpolation;

    gl::ShaderMap<std::unique_ptr<UniformStorageD3D>> mShaderUniformStorages;

    gl::ShaderMap<std::vector<D3DSampler>> mShaderSamplers;
    gl::ShaderMap<gl::RangeUI> mUsedShaderSamplerRanges;
    bool mDirtySamplerMapping;

    gl::ShaderMap<std::vector<D3DImage>> mImages;
    gl::ShaderMap<std::vector<D3DImage>> mReadonlyImages;
    gl::ShaderMap<gl::RangeUI> mUsedImageRange;
    gl::ShaderMap<gl::RangeUI> mUsedReadonlyImageRange;

    // Cache for pixel shader output layout to save reallocations.
    std::vector<GLenum> mPixelShaderOutputLayoutCache;
    Optional<size_t> mCachedPixelExecutableIndex;

    AttribIndexArray mAttribLocationToD3DSemantic;

    gl::ShaderMap<std::vector<D3DUBOCache>> mShaderUBOCaches;
    gl::ShaderMap<std::vector<D3DUBOCacheUseSB>> mShaderUBOCachesUseSB;
    D3DVertexExecutable::Signature mCachedVertexSignature;
    gl::InputLayout mCachedInputLayout;
    Optional<size_t> mCachedVertexExecutableIndex;

    std::vector<D3DVarying> mStreamOutVaryings;
    std::vector<D3DUniform *> mD3DUniforms;
    std::map<std::string, int> mImageBindingMap;
    std::vector<D3DUniformBlock> mD3DUniformBlocks;

    gl::ShaderMap<std::vector<sh::ShaderVariable>> mImage2DUniforms;
    gl::ShaderMap<gl::ImageUnitTextureTypeMap> mImage2DBindLayoutCache;

    gl::ShaderBitSet mShaderUniformsDirty;

    UniqueSerial mCurrentVertexArrayStateSerial;

    unsigned int mSerial;

    static unsigned int issueSerial();
    static unsigned int mCurrentSerial;
};

}  // namespace rx

#endif  // LIBANGLE_RENDERER_D3D_PROGRAMEXECUTABLED3D_H_
