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

// Shader.h: Defines the abstract gl::Shader class and its concrete derived
// classes VertexShader and FragmentShader. Implements GL shader objects and
// related functionality. [OpenGL ES 2.0.24] section 2.10 page 24 and section
// 3.8 page 84.

#ifndef LIBANGLE_SHADER_H_
#define LIBANGLE_SHADER_H_

#include <list>
#include <memory>
#include <string>
#include <vector>

#include <GLSLANG/ShaderLang.h>
#include "angle_gl.h"

#include "common/BinaryStream.h"
#include "common/CompiledShaderState.h"
#include "common/MemoryBuffer.h"
#include "common/Optional.h"
#include "common/angleutils.h"
#include "libANGLE/BlobCache.h"
#include "libANGLE/Caps.h"
#include "libANGLE/Compiler.h"
#include "libANGLE/Debug.h"
#include "libANGLE/angletypes.h"

namespace rx
{
class GLImplFactory;
class ShaderImpl;
class ShaderSh;
class WaitableCompileEvent;
}  // namespace rx

namespace angle
{
class WaitableEvent;
class WorkerThreadPool;
}  // namespace angle

namespace gl
{
class CompileTask;
class Context;
class ShaderProgramManager;
class State;
class BinaryInputStream;
class BinaryOutputStream;

// We defer the compile until link time, or until properties are queried.
enum class CompileStatus
{
    NOT_COMPILED,
    COMPILE_REQUESTED,
    COMPILED,
};

class ShaderState final : angle::NonCopyable
{
  public:
    ShaderState(ShaderType shaderType);
    ~ShaderState();

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

    const std::string &getSource() const { return mSource; }
    bool isCompiledToBinary() const { return !mCompiledShaderState.compiledBinary.empty(); }
    const std::string &getTranslatedSource() const { return mCompiledShaderState.translatedSource; }
    const sh::BinaryBlob &getCompiledBinary() const { return mCompiledShaderState.compiledBinary; }

    ShaderType getShaderType() const { return mCompiledShaderState.shaderType; }
    int getShaderVersion() const { return mCompiledShaderState.shaderVersion; }

    const std::vector<sh::ShaderVariable> &getInputVaryings() const
    {
        return mCompiledShaderState.inputVaryings;
    }
    const std::vector<sh::ShaderVariable> &getOutputVaryings() const
    {
        return mCompiledShaderState.outputVaryings;
    }
    const std::vector<sh::ShaderVariable> &getUniforms() const
    {
        return mCompiledShaderState.uniforms;
    }
    const std::vector<sh::InterfaceBlock> &getUniformBlocks() const
    {
        return mCompiledShaderState.uniformBlocks;
    }
    const std::vector<sh::InterfaceBlock> &getShaderStorageBlocks() const
    {
        return mCompiledShaderState.shaderStorageBlocks;
    }
    const std::vector<sh::ShaderVariable> &getActiveAttributes() const
    {
        return mCompiledShaderState.activeAttributes;
    }
    const std::vector<sh::ShaderVariable> &getAllAttributes() const
    {
        return mCompiledShaderState.allAttributes;
    }
    const std::vector<sh::ShaderVariable> &getActiveOutputVariables() const
    {
        return mCompiledShaderState.activeOutputVariables;
    }

    bool compilePending() const { return mCompileStatus == CompileStatus::COMPILE_REQUESTED; }

    const sh::WorkGroupSize &getLocalSize() const { return mCompiledShaderState.localSize; }

    bool hasClipDistance() const { return mCompiledShaderState.hasClipDistance; }
    bool hasDiscard() const { return mCompiledShaderState.hasDiscard; }
    bool enablesPerSampleShading() const { return mCompiledShaderState.enablesPerSampleShading; }
    rx::SpecConstUsageBits getSpecConstUsageBits() const
    {
        return mCompiledShaderState.specConstUsageBits;
    }

    int getNumViews() const { return mCompiledShaderState.numViews; }

    Optional<PrimitiveMode> getGeometryShaderInputPrimitiveType() const
    {
        return mCompiledShaderState.geometryShaderInputPrimitiveType;
    }

    Optional<PrimitiveMode> getGeometryShaderOutputPrimitiveType() const
    {
        return mCompiledShaderState.geometryShaderOutputPrimitiveType;
    }

    Optional<GLint> getGeometryShaderMaxVertices() const
    {
        return mCompiledShaderState.geometryShaderMaxVertices;
    }

    Optional<GLint> getGeometryShaderInvocations() const
    {
        return mCompiledShaderState.geometryShaderInvocations;
    }

    CompileStatus getCompileStatus() const { return mCompileStatus; }

  private:
    friend class Shader;

    std::string mLabel;
    std::string mSource;
    size_t mSourceHash = 0;

    gl::CompiledShaderState mCompiledShaderState;

    // Indicates if this shader has been successfully compiled
    CompileStatus mCompileStatus = CompileStatus::NOT_COMPILED;
};

class Shader final : angle::NonCopyable, public LabeledObject
{
  public:
    Shader(ShaderProgramManager *manager,
           rx::GLImplFactory *implFactory,
           const gl::Limitations &rendererLimitations,
           ShaderType type,
           ShaderProgramID handle);

    void onDestroy(const Context *context);

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

    ShaderType getType() const { return mType; }
    ShaderProgramID getHandle() const;

    rx::ShaderImpl *getImplementation() const { return mImplementation.get(); }

    void setSource(const Context *context,
                   GLsizei count,
                   const char *const *string,
                   const GLint *length);
    int getInfoLogLength(const Context *context);
    void getInfoLog(const Context *context, GLsizei bufSize, GLsizei *length, char *infoLog);
    std::string getInfoLogString() const { return mInfoLog; }
    int getSourceLength() const;
    const std::string &getSourceString() const { return mState.getSource(); }
    void getSource(GLsizei bufSize, GLsizei *length, char *buffer) const;
    int getTranslatedSourceLength(const Context *context);
    int getTranslatedSourceWithDebugInfoLength(const Context *context);
    const std::string &getTranslatedSource(const Context *context);
    void getTranslatedSource(const Context *context,
                             GLsizei bufSize,
                             GLsizei *length,
                             char *buffer);
    void getTranslatedSourceWithDebugInfo(const Context *context,
                                          GLsizei bufSize,
                                          GLsizei *length,
                                          char *buffer);
    const sh::BinaryBlob &getCompiledBinary(const Context *context);

    size_t getSourceHash() const;

    void compile(const Context *context);
    bool isCompiled(const Context *context);
    bool isCompleted();

    void addRef();
    void release(const Context *context);
    unsigned int getRefCount() const;
    bool isFlaggedForDeletion() const;
    void flagForDeletion();
    bool hasClipDistance() const { return mState.mCompiledShaderState.hasClipDistance; }
    bool hasDiscard() const { return mState.mCompiledShaderState.hasDiscard; }
    bool enablesPerSampleShading() const
    {
        return mState.mCompiledShaderState.enablesPerSampleShading;
    }
    BlendEquationBitSet getAdvancedBlendEquations() const
    {
        return mState.mCompiledShaderState.advancedBlendEquations;
    }
    rx::SpecConstUsageBits getSpecConstUsageBits() const
    {
        return mState.mCompiledShaderState.specConstUsageBits;
    }

    int getShaderVersion(const Context *context);

    const std::vector<sh::ShaderVariable> &getInputVaryings(const Context *context);
    const std::vector<sh::ShaderVariable> &getOutputVaryings(const Context *context);
    const std::vector<sh::ShaderVariable> &getUniforms(const Context *context);
    const std::vector<sh::InterfaceBlock> &getUniformBlocks(const Context *context);
    const std::vector<sh::InterfaceBlock> &getShaderStorageBlocks(const Context *context);
    const std::vector<sh::ShaderVariable> &getActiveAttributes(const Context *context);
    const std::vector<sh::ShaderVariable> &getAllAttributes(const Context *context);
    const std::vector<sh::ShaderVariable> &getActiveOutputVariables(const Context *context);

    // Returns mapped name of a transform feedback varying. The original name may contain array
    // brackets with an index inside, which will get copied to the mapped name. The varying must be
    // known to be declared in the shader.
    std::string getTransformFeedbackVaryingMappedName(const Context *context,
                                                      const std::string &tfVaryingName);

    const sh::WorkGroupSize &getWorkGroupSize(const Context *context);

    int getNumViews(const Context *context);

    Optional<PrimitiveMode> getGeometryShaderInputPrimitiveType(const Context *context);
    Optional<PrimitiveMode> getGeometryShaderOutputPrimitiveType(const Context *context);
    int getGeometryShaderInvocations(const Context *context);
    Optional<GLint> getGeometryShaderMaxVertices(const Context *context);
    int getTessControlShaderVertices(const Context *context);
    GLenum getTessGenMode(const Context *context);
    GLenum getTessGenSpacing(const Context *context);
    GLenum getTessGenVertexOrder(const Context *context);
    GLenum getTessGenPointMode(const Context *context);

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

    GLuint getCurrentMaxComputeWorkGroupInvocations() const
    {
        return mCurrentMaxComputeWorkGroupInvocations;
    }

    unsigned int getMaxComputeSharedMemory() const { return mMaxComputeSharedMemory; }
    bool hasBeenDeleted() const { return mDeleteStatus; }

    // Block until compiling is finished and resolve it.
    void resolveCompile(const Context *context);

    // Writes a shader's binary to the output memory buffer.
    angle::Result serialize(const Context *context, angle::MemoryBuffer *binaryOut) const;
    angle::Result deserialize(BinaryInputStream &stream);

    // Load a binary from shader cache.
    angle::Result loadBinary(const Context *context, const void *binary, GLsizei length);
    // Load a binary from a glShaderBinary call.
    angle::Result loadShaderBinary(const Context *context, const void *binary, GLsizei length);

    void writeShaderKey(BinaryOutputStream *streamOut) const
    {
        ASSERT(streamOut && !mShaderHash.empty());
        streamOut->writeBytes(mShaderHash.data(), egl::BlobCache::kKeyLength);
        return;
    }

  private:
    struct CompilingState;

    ~Shader() override;
    static std::string joinShaderSources(GLsizei count,
                                         const char *const *string,
                                         const GLint *length);
    static void GetSourceImpl(const std::string &source,
                              GLsizei bufSize,
                              GLsizei *length,
                              char *buffer);
    angle::Result loadBinaryImpl(const Context *context,
                                 const void *binary,
                                 GLsizei length,
                                 bool generatedWithOfflineCompiler);

    // Compute a key to uniquely identify the shader object in memory caches.
    void setShaderKey(const Context *context,
                      const ShCompileOptions &compileOptions,
                      const ShShaderOutput &outputType,
                      const ShBuiltInResources &resources);

    ShaderState mState;
    std::unique_ptr<rx::ShaderImpl> mImplementation;
    const gl::Limitations mRendererLimitations;
    const ShaderProgramID mHandle;
    const ShaderType mType;
    unsigned int mRefCount;  // Number of program objects this shader is attached to
    bool mDeleteStatus;  // Flag to indicate that the shader can be deleted when no longer in use
    std::string mInfoLog;

    // We keep a reference to the translator in order to defer compiles while preserving settings.
    BindingPointer<Compiler> mBoundCompiler;
    std::unique_ptr<CompilingState> mCompilingState;
    egl::BlobCache::Key mShaderHash;

    ShaderProgramManager *mResourceManager;

    GLuint mCurrentMaxComputeWorkGroupInvocations;
    unsigned int mMaxComputeSharedMemory;
};

const char *GetShaderTypeString(ShaderType type);
std::string GetShaderDumpFileDirectory();
std::string GetShaderDumpFileName(size_t shaderHash);

}  // namespace gl

#endif  // LIBANGLE_SHADER_H_
