//
// Copyright 2002 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.
//

// Program.h: Defines the gl::Program class. Implements GL program objects
// and related functionality. [OpenGL ES 2.0.24] section 2.10.3 page 28.

#ifndef LIBANGLE_PROGRAM_H_
#define LIBANGLE_PROGRAM_H_

#include <GLES2/gl2.h>
#include <GLSLANG/ShaderVars.h>

#include <array>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>

#include "common/Optional.h"
#include "common/SimpleMutex.h"
#include "common/angleutils.h"
#include "common/hash_containers.h"
#include "common/mathutil.h"
#include "common/utilities.h"

#include "libANGLE/Constants.h"
#include "libANGLE/Debug.h"
#include "libANGLE/Error.h"
#include "libANGLE/InfoLog.h"
#include "libANGLE/ProgramExecutable.h"
#include "libANGLE/ProgramLinkedResources.h"
#include "libANGLE/RefCountObject.h"
#include "libANGLE/Shader.h"
#include "libANGLE/Uniform.h"
#include "libANGLE/angletypes.h"

namespace rx
{
class GLImplFactory;
class ProgramImpl;
class LinkSubTask;
struct TranslatedAttribute;
}  // namespace rx

namespace gl
{
class Buffer;
class BinaryInputStream;
class BinaryOutputStream;
struct Caps;
class Context;
struct Extensions;
class Framebuffer;
class ProgramExecutable;
class ShaderProgramManager;
class State;
struct UnusedUniform;
struct Version;

extern const char *const g_fakepath;

enum class LinkMismatchError
{
    // Shared
    NO_MISMATCH,
    TYPE_MISMATCH,
    ARRAYNESS_MISMATCH,
    ARRAY_SIZE_MISMATCH,
    PRECISION_MISMATCH,
    STRUCT_NAME_MISMATCH,
    FIELD_NUMBER_MISMATCH,
    FIELD_NAME_MISMATCH,

    // Varying specific
    INTERPOLATION_TYPE_MISMATCH,
    INVARIANCE_MISMATCH,

    // Uniform specific
    BINDING_MISMATCH,
    LOCATION_MISMATCH,
    OFFSET_MISMATCH,
    INSTANCE_NAME_MISMATCH,
    FORMAT_MISMATCH,

    // Interface block specific
    LAYOUT_QUALIFIER_MISMATCH,
    MATRIX_PACKING_MISMATCH,

    // I/O block specific
    FIELD_LOCATION_MISMATCH,
    FIELD_STRUCT_NAME_MISMATCH,
};

void LogLinkMismatch(InfoLog &infoLog,
                     const std::string &variableName,
                     const char *variableType,
                     LinkMismatchError linkError,
                     const std::string &mismatchedStructOrBlockFieldName,
                     ShaderType shaderType1,
                     ShaderType shaderType2);

bool IsActiveInterfaceBlock(const sh::InterfaceBlock &interfaceBlock);

// Header prepended to the program binary blob.  All fields are uncompressed, followed by the
// payload.
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS
class ProgramBinaryHeader
{
  public:
    static constexpr uint32_t kProgramBinaryMagic = 0x414E474C;  // "ANGL"

    void setData(uint32_t magic, uint32_t crc, uint64_t uncompressedSize)
    {
        mMagic            = magic;
        mCRC              = crc;
        mUncompressedSize = uncompressedSize;
    }

    void getData(uint32_t *magicOut, uint32_t *crcOut, uint64_t *uncompressedSizeOut) const
    {
        *magicOut            = mMagic;
        *crcOut              = mCRC;
        *uncompressedSizeOut = mUncompressedSize;
    }

  private:
    uint32_t mMagic;
    uint32_t mCRC;
    uint64_t mUncompressedSize;
};
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

constexpr size_t kProgramBinaryHeaderSize = sizeof(ProgramBinaryHeader);
static_assert(kProgramBinaryHeaderSize == 16, "ProgramBinaryHeader size mismatch");

// Struct used for correlating uniforms/elements of uniform arrays to handles
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS
struct VariableLocation
{
    static constexpr unsigned int kUnused = GL_INVALID_INDEX;

    VariableLocation();
    VariableLocation(unsigned int arrayIndex, unsigned int index);

    // If used is false, it means this location is only used to fill an empty space in an array,
    // and there is no corresponding uniform variable for this location. It can also mean the
    // uniform was optimized out by the implementation.
    bool used() const { return (index != kUnused); }
    void markUnused() { index = kUnused; }
    void markIgnored() { ignored = true; }

    bool operator==(const VariableLocation &other) const
    {
        return arrayIndex == other.arrayIndex && index == other.index;
    }

    // "index" is an index of the variable. The variable contains the indices for other than the
    // innermost GLSL arrays.
    uint32_t index;

    // "arrayIndex" stores the index of the innermost GLSL array. It's zero for non-arrays.
    uint32_t arrayIndex : 31;
    // If this location was bound to an unreferenced uniform.  Setting data on this uniform is a
    // no-op.
    uint32_t ignored : 1;
};
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

// Information about a variable binding.
// Currently used by CHROMIUM_path_rendering
struct BindingInfo
{
    // The type of binding, for example GL_FLOAT_VEC3.
    // This can be GL_NONE if the variable is optimized away.
    GLenum type;

    // This is the name of the variable in
    // the translated shader program. Note that
    // this can be empty in the case where the
    // variable has been optimized away.
    std::string name;

    // True if the binding is valid, otherwise false.
    bool valid;
};

struct ProgramBinding
{
    ProgramBinding() : location(GL_INVALID_INDEX), aliased(false) {}
    ProgramBinding(GLuint index) : location(index), aliased(false) {}

    GLuint location;
    // Whether another binding was set that may potentially alias this.
    bool aliased;
};

class ProgramBindings final : angle::NonCopyable
{
  public:
    ProgramBindings();
    ~ProgramBindings();

    void bindLocation(GLuint index, const std::string &name);
    int getBindingByName(const std::string &name) const;
    template <typename T>
    int getBinding(const T &variable) const;

    using const_iterator = angle::HashMap<std::string, GLuint>::const_iterator;
    const_iterator begin() const;
    const_iterator end() const;

    std::map<std::string, GLuint> getStableIterationMap() const;

  private:
    angle::HashMap<std::string, GLuint> mBindings;
};

enum class BindLocationPolicy
{
    AcceptIndexing,
    IgnoreIndexing,
};

// Uniforms and Fragment Outputs require special treatment due to array notation (e.g., "[0]")
class ProgramAliasedBindings final : angle::NonCopyable
{
  public:
    ProgramAliasedBindings();
    ~ProgramAliasedBindings();

    void bindLocation(GLuint index, const std::string &name, BindLocationPolicy policy);
    int getBindingByName(const std::string &name) const;
    int getBindingByLocation(GLuint location) const;
    template <typename T>
    int getBinding(const T &variable) const;

    using const_iterator = angle::HashMap<std::string, ProgramBinding>::const_iterator;
    const_iterator begin() const;
    const_iterator end() const;

    std::map<std::string, ProgramBinding> getStableIterationMap() const;

  private:
    angle::HashMap<std::string, ProgramBinding> mBindings;
};

class ProgramState final : angle::NonCopyable
{
  public:
    ProgramState(rx::GLImplFactory *factory);
    ~ProgramState();

    const std::string &getLabel();

    SharedCompiledShaderState getAttachedShader(ShaderType shaderType) const;
    const ShaderMap<SharedCompiledShaderState> &getAttachedShaders() const
    {
        return mAttachedShaders;
    }
    const std::vector<std::string> &getTransformFeedbackVaryingNames() const
    {
        return mTransformFeedbackVaryingNames;
    }
    GLint getTransformFeedbackBufferMode() const { return mTransformFeedbackBufferMode; }

    bool hasAnyAttachedShader() const;

    const ProgramBindings &getAttributeBindings() const { return mAttributeBindings; }
    const ProgramAliasedBindings &getUniformLocationBindings() const
    {
        return mUniformLocationBindings;
    }
    const ProgramAliasedBindings &getFragmentOutputLocations() const
    {
        return mFragmentOutputLocations;
    }
    const ProgramAliasedBindings &getFragmentOutputIndexes() const
    {
        return mFragmentOutputIndexes;
    }

    const ProgramExecutable &getExecutable() const
    {
        ASSERT(mExecutable);
        return *mExecutable;
    }
    ProgramExecutable &getExecutable()
    {
        ASSERT(mExecutable);
        return *mExecutable;
    }

    const SharedProgramExecutable &getSharedExecutable() const
    {
        ASSERT(mExecutable);
        return mExecutable;
    }

    const std::string &getLabel() const { return mLabel; }

    bool hasBinaryRetrieveableHint() const { return mBinaryRetrieveableHint; }

    bool isSeparable() const { return mSeparable; }

    ShaderType getAttachedTransformFeedbackStage() const;

  private:
    friend class MemoryProgramCache;
    friend class Program;

    void updateActiveSamplers();
    void updateProgramInterfaceInputs();
    void updateProgramInterfaceOutputs();

    // Scans the sampler bindings for type conflicts with sampler 'textureUnitIndex'.
    void setSamplerUniformTextureTypeAndFormat(size_t textureUnitIndex);

    std::string mLabel;

    ShaderMap<SharedCompileJob> mShaderCompileJobs;
    ShaderMap<SharedCompiledShaderState> mAttachedShaders;

    std::vector<std::string> mTransformFeedbackVaryingNames;
    GLenum mTransformFeedbackBufferMode;

    bool mBinaryRetrieveableHint;
    bool mSeparable;

    ProgramBindings mAttributeBindings;

    // Note that this has nothing to do with binding layout qualifiers that can be set for some
    // uniforms in GLES3.1+. It is used to pre-set the location of uniforms.
    ProgramAliasedBindings mUniformLocationBindings;

    // EXT_blend_func_extended
    ProgramAliasedBindings mFragmentOutputLocations;
    ProgramAliasedBindings mFragmentOutputIndexes;

    InfoLog mInfoLog;

    // The result of the link.  State that is not the link output should remain in ProgramState,
    // while the link output should be placed in ProgramExecutable.
    //
    // This is a shared_ptr because it can be "installed" in the context as part of the rendering
    // context.  Similarly, it can be installed in a program pipeline.  Once the executable is
    // installed, the actual Program should not be referenced; it may have been unsuccessfully
    // relinked and its executable in an unusable state.
    SharedProgramExecutable mExecutable;
};

struct ProgramVaryingRef
{
    const sh::ShaderVariable *get(ShaderType stage) const
    {
        ASSERT(stage == frontShaderStage || stage == backShaderStage);
        const sh::ShaderVariable *ref = stage == frontShaderStage ? frontShader : backShader;
        ASSERT(ref);
        return ref;
    }

    const sh::ShaderVariable *frontShader = nullptr;
    const sh::ShaderVariable *backShader  = nullptr;
    ShaderType frontShaderStage           = ShaderType::InvalidEnum;
    ShaderType backShaderStage            = ShaderType::InvalidEnum;
};

using ProgramMergedVaryings = std::vector<ProgramVaryingRef>;

class Program final : public LabeledObject, public angle::Subject
{
  public:
    Program(rx::GLImplFactory *factory, ShaderProgramManager *manager, ShaderProgramID handle);
    void onDestroy(const Context *context);

    ShaderProgramID id() const;

    angle::Result setLabel(const Context *context, const std::string &label) override;
    const std::string &getLabel() const override;

    ANGLE_INLINE rx::ProgramImpl *getImplementation() const
    {
        ASSERT(!mLinkingState);
        return mProgram;
    }

    void attachShader(const Context *context, Shader *shader);
    void detachShader(const Context *context, Shader *shader);
    int getAttachedShadersCount() const;

    Shader *getAttachedShader(ShaderType shaderType) const;

    void bindAttributeLocation(const Context *context, GLuint index, const char *name);
    void bindUniformLocation(const Context *context, UniformLocation location, const char *name);

    // EXT_blend_func_extended
    void bindFragmentOutputLocation(const Context *context, GLuint index, const char *name);
    void bindFragmentOutputIndex(const Context *context, GLuint index, const char *name);

    // KHR_parallel_shader_compile
    // Try to link the program asynchronously. As a result, background threads may be launched to
    // execute the linking tasks concurrently.
    angle::Result link(const Context *context, angle::JobResultExpectancy resultExpectancy);

    // Peek whether there is any running linking tasks.
    bool isLinking() const;
    bool hasLinkingState() const { return mLinkingState != nullptr; }

    bool isLinked() const
    {
        ASSERT(!mLinkingState);
        return mLinked;
    }
    bool isBinaryReady(const Context *context);
    ANGLE_INLINE void cacheProgramBinaryIfNecessary(const Context *context)
    {
        // This function helps ensure the program binary is cached, even if the backend waits for
        // post-link tasks without the knowledge of the front-end.
        if (!mIsBinaryCached && !mState.mExecutable->mBinaryRetrieveableHint &&
            mState.mExecutable->mPostLinkSubTasks.empty())
        {
            cacheProgramBinaryIfNotAlready(context);
        }
    }

    angle::Result setBinary(const Context *context,
                            GLenum binaryFormat,
                            const void *binary,
                            GLsizei length);
    angle::Result getBinary(Context *context,
                            GLenum *binaryFormat,
                            void *binary,
                            GLsizei bufSize,
                            GLsizei *length);
    GLint getBinaryLength(Context *context);
    void setBinaryRetrievableHint(bool retrievable);
    bool getBinaryRetrievableHint() const;

    angle::Result loadBinary(const Context *context,
                             const void *binary,
                             GLsizei length,
                             egl::CacheGetResult *resultOut);

    InfoLog &getInfoLog() { return mState.mInfoLog; }
    int getInfoLogLength() const;
    void getInfoLog(GLsizei bufSize, GLsizei *length, char *infoLog) const;

    void setSeparable(const Context *context, bool separable);
    bool isSeparable() const { return mState.mSeparable; }

    void getAttachedShaders(GLsizei maxCount, GLsizei *count, ShaderProgramID *shaders) const;

    void bindUniformBlock(UniformBlockIndex uniformBlockIndex, GLuint uniformBlockBinding);

    void setTransformFeedbackVaryings(const Context *context,
                                      GLsizei count,
                                      const GLchar *const *varyings,
                                      GLenum bufferMode);
    GLenum getTransformFeedbackBufferMode() const { return mState.mTransformFeedbackBufferMode; }

    ANGLE_INLINE void addRef() { mRefCount++; }

    ANGLE_INLINE void release(const Context *context)
    {
        mRefCount--;

        if (mRefCount == 0 && mDeleteStatus)
        {
            deleteSelf(context);
        }
    }

    unsigned int getRefCount() const;
    bool isInUse() const { return getRefCount() != 0; }
    void flagForDeletion();
    bool isFlaggedForDeletion() const;

    void validate(const Caps &caps);
    bool isValidated() const;

    const ProgramState &getState() const { return mState; }

    const ProgramBindings &getAttributeBindings() const { return mState.getAttributeBindings(); }
    const ProgramAliasedBindings &getUniformLocationBindings() const
    {
        return mState.getUniformLocationBindings();
    }
    const ProgramAliasedBindings &getFragmentOutputLocations() const
    {
        return mState.getFragmentOutputLocations();
    }
    const ProgramAliasedBindings &getFragmentOutputIndexes() const
    {
        return mState.getFragmentOutputIndexes();
    }

    // Try to resolve linking. Inlined to make sure its overhead is as low as possible.
    ANGLE_INLINE void resolveLink(const Context *context)
    {
        if (ANGLE_UNLIKELY(mLinkingState))
        {
            resolveLinkImpl(context);
        }
    }

    // Writes a program's binary to |mBinary|.
    angle::Result serialize(const Context *context);
    const angle::MemoryBuffer &getSerializedBinary() const { return mBinary; }

    rx::UniqueSerial serial() const { return mSerial; }

    const ProgramExecutable &getExecutable() const { return mState.getExecutable(); }
    ProgramExecutable &getExecutable() { return mState.getExecutable(); }
    const SharedProgramExecutable &getSharedExecutable() const
    {
        return mState.getSharedExecutable();
    }

  private:
    class MainLinkLoadTask;
    class MainLoadTask;
    class MainLinkTask;
    class MainLinkLoadEvent;

    friend class ProgramPipeline;
    friend class MainLinkLoadTask;
    friend class MainLoadTask;
    friend class MainLinkTask;

    struct LinkingState;
    ~Program() override;

    // Loads program state according to the specified binary blob.  Returns true on success.
    bool deserialize(const Context *context, BinaryInputStream &stream);

    void unlink();
    void setupExecutableForLink(const Context *context);
    void syncExecutableOnSuccessfulLink();
    void deleteSelf(const Context *context);

    angle::Result linkJobImpl(const Caps &caps,
                              const Limitations &limitations,
                              const Version &clientVersion,
                              bool isWebGL,
                              LinkingVariables *linkingVariables,
                              ProgramLinkedResources *resources,
                              ProgramMergedVaryings *mergedVaryingsOut);

    void makeNewExecutable(const Context *context);

    bool linkValidateShaders();
    void linkShaders();
    bool linkAttributes(const Caps &caps, const Limitations &limitations, bool webglCompatibility);
    bool linkVaryings();

    bool linkUniforms(const Caps &caps,
                      const Version &clientVersion,
                      std::vector<UnusedUniform> *unusedUniformsOutOrNull,
                      GLuint *combinedImageUniformsOut);

    void updateLinkedShaderStages();

    // Block until linking is finished and resolve it.
    void resolveLinkImpl(const Context *context);
    // Block until post-link tasks are finished.
    void waitForPostLinkTasks(const Context *context);

    void postResolveLink(const Context *context);
    void cacheProgramBinaryIfNotAlready(const Context *context);

    void dumpProgramInfo(const Context *context) const;

    rx::UniqueSerial mSerial;
    ProgramState mState;
    rx::ProgramImpl *mProgram;

    bool mValidated;
    // Flag to indicate that the program can be deleted when no longer in use
    bool mDeleteStatus;
    // Whether the program binary is implicitly cached yet.  This is usually done in
    // |resolveLinkImpl|, but may be deferred in the presence of post-link tasks.  In that case,
    // |waitForPostLinkTasks| would cache the binary.  However, if the wait on the tasks is done by
    // the backend itself, this caching will not be done.  This flag is used to make sure the binary
    // is eventually cached at some point in the future.
    bool mIsBinaryCached;

    bool mLinked;
    std::unique_ptr<LinkingState> mLinkingState;

    egl::BlobCache::Key mProgramHash;

    unsigned int mRefCount;

    ShaderProgramManager *mResourceManager;
    const ShaderProgramID mHandle;

    // ProgramState::mAttachedShaders holds a reference to shaders' compiled state, which is all the
    // program and the backends require after link.  The actual shaders linked to the program are
    // stored here to support shader attach/detach and link without providing access to them in the
    // backends.
    ShaderMap<Shader *> mAttachedShaders;

    // A cache of the program binary, prepared by |serialize()|.  OpenGL requires the application to
    // query the length of the binary first (requiring a call to |serialize()|), and then get the
    // actual binary.  This cache ensures the second call does not need to call |serialize()| again.
    angle::MemoryBuffer mBinary;

    angle::SimpleMutex mHistogramMutex;
};
}  // namespace gl

#endif  // LIBANGLE_PROGRAM_H_
