//
// Copyright 2016 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.
//
// renderer_utils:
//   Helper methods pertaining to most or all back-ends.
//

#ifndef LIBANGLE_RENDERER_RENDERER_UTILS_H_
#define LIBANGLE_RENDERER_RENDERER_UTILS_H_

#include <cstdint>

#include <limits>
#include <map>

#include "common/angleutils.h"
#include "common/utilities.h"
#include "libANGLE/angletypes.h"

namespace angle
{
struct FeatureSetBase;
struct Format;
enum class FormatID;
}  // namespace angle

namespace gl
{
struct FormatType;
struct InternalFormat;
class State;
}  // namespace gl

namespace egl
{
class AttributeMap;
struct DisplayState;
}  // namespace egl

namespace rx
{
class ContextImpl;

// The possible rotations of the surface/draw framebuffer, particularly for the Vulkan back-end on
// Android.
enum class SurfaceRotation
{
    Identity,
    Rotated90Degrees,
    Rotated180Degrees,
    Rotated270Degrees,
    FlippedIdentity,
    FlippedRotated90Degrees,
    FlippedRotated180Degrees,
    FlippedRotated270Degrees,

    InvalidEnum,
    EnumCount = InvalidEnum,
};

void RotateRectangle(const SurfaceRotation rotation,
                     const bool flipY,
                     const int framebufferWidth,
                     const int framebufferHeight,
                     const gl::Rectangle &incoming,
                     gl::Rectangle *outgoing);

using MipGenerationFunction = void (*)(size_t sourceWidth,
                                       size_t sourceHeight,
                                       size_t sourceDepth,
                                       const uint8_t *sourceData,
                                       size_t sourceRowPitch,
                                       size_t sourceDepthPitch,
                                       uint8_t *destData,
                                       size_t destRowPitch,
                                       size_t destDepthPitch);

typedef void (*PixelReadFunction)(const uint8_t *source, uint8_t *dest);
typedef void (*PixelWriteFunction)(const uint8_t *source, uint8_t *dest);
typedef void (*PixelCopyFunction)(const uint8_t *source, uint8_t *dest);

class FastCopyFunctionMap
{
  public:
    struct Entry
    {
        angle::FormatID formatID;
        PixelCopyFunction func;
    };

    constexpr FastCopyFunctionMap() : FastCopyFunctionMap(nullptr, 0) {}

    constexpr FastCopyFunctionMap(const Entry *data, size_t size) : mSize(size), mData(data) {}

    bool has(angle::FormatID formatID) const;
    PixelCopyFunction get(angle::FormatID formatID) const;

  private:
    size_t mSize;
    const Entry *mData;
};

struct PackPixelsParams
{
    PackPixelsParams();
    PackPixelsParams(const gl::Rectangle &area,
                     const angle::Format &destFormat,
                     GLuint outputPitch,
                     bool reverseRowOrderIn,
                     gl::Buffer *packBufferIn,
                     ptrdiff_t offset);

    gl::Rectangle area;
    const angle::Format *destFormat;
    GLuint outputPitch;
    gl::Buffer *packBuffer;
    bool reverseRowOrder;
    ptrdiff_t offset;
    SurfaceRotation rotation;
};

void PackPixels(const PackPixelsParams &params,
                const angle::Format &sourceFormat,
                int inputPitch,
                const uint8_t *source,
                uint8_t *destination);

using InitializeTextureDataFunction = void (*)(size_t width,
                                               size_t height,
                                               size_t depth,
                                               uint8_t *output,
                                               size_t outputRowPitch,
                                               size_t outputDepthPitch);

using LoadImageFunction = void (*)(size_t width,
                                   size_t height,
                                   size_t depth,
                                   const uint8_t *input,
                                   size_t inputRowPitch,
                                   size_t inputDepthPitch,
                                   uint8_t *output,
                                   size_t outputRowPitch,
                                   size_t outputDepthPitch);

struct LoadImageFunctionInfo
{
    LoadImageFunctionInfo() : loadFunction(nullptr), requiresConversion(false) {}
    LoadImageFunctionInfo(LoadImageFunction loadFunction, bool requiresConversion)
        : loadFunction(loadFunction), requiresConversion(requiresConversion)
    {}

    LoadImageFunction loadFunction;
    bool requiresConversion;
};

using LoadFunctionMap = LoadImageFunctionInfo (*)(GLenum);

bool ShouldUseDebugLayers(const egl::AttributeMap &attribs);
bool ShouldUseVirtualizedContexts(const egl::AttributeMap &attribs, bool defaultValue);

void CopyImageCHROMIUM(const uint8_t *sourceData,
                       size_t sourceRowPitch,
                       size_t sourcePixelBytes,
                       size_t sourceDepthPitch,
                       PixelReadFunction pixelReadFunction,
                       uint8_t *destData,
                       size_t destRowPitch,
                       size_t destPixelBytes,
                       size_t destDepthPitch,
                       PixelWriteFunction pixelWriteFunction,
                       GLenum destUnsizedFormat,
                       GLenum destComponentType,
                       size_t width,
                       size_t height,
                       size_t depth,
                       bool unpackFlipY,
                       bool unpackPremultiplyAlpha,
                       bool unpackUnmultiplyAlpha);

// Incomplete textures are 1x1 textures filled with black, used when samplers are incomplete.
// This helper class encapsulates handling incomplete textures. Because the GL back-end
// can take advantage of the driver's incomplete textures, and because clearing multisample
// textures is so difficult, we can keep an instance of this class in the back-end instead
// of moving the logic to the Context front-end.

// This interface allows us to call-back to init a multisample texture.
class MultisampleTextureInitializer
{
  public:
    virtual ~MultisampleTextureInitializer() {}
    virtual angle::Result initializeMultisampleTextureToBlack(const gl::Context *context,
                                                              gl::Texture *glTexture) = 0;
};

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

    void onDestroy(const gl::Context *context);

    angle::Result getIncompleteTexture(const gl::Context *context,
                                       gl::TextureType type,
                                       MultisampleTextureInitializer *multisampleInitializer,
                                       gl::Texture **textureOut);

  private:
    gl::TextureMap mIncompleteTextures;
};

// Helpers to set a matrix uniform value based on GLSL or HLSL semantics.
// The return value indicate if the data was updated or not.
template <int cols, int rows>
struct SetFloatUniformMatrixGLSL
{
    static void Run(unsigned int arrayElementOffset,
                    unsigned int elementCount,
                    GLsizei countIn,
                    GLboolean transpose,
                    const GLfloat *value,
                    uint8_t *targetData);
};

template <int cols, int rows>
struct SetFloatUniformMatrixHLSL
{
    static void Run(unsigned int arrayElementOffset,
                    unsigned int elementCount,
                    GLsizei countIn,
                    GLboolean transpose,
                    const GLfloat *value,
                    uint8_t *targetData);
};

// Helper method to de-tranpose a matrix uniform for an API query.
void GetMatrixUniform(GLenum type, GLfloat *dataOut, const GLfloat *source, bool transpose);

template <typename NonFloatT>
void GetMatrixUniform(GLenum type, NonFloatT *dataOut, const NonFloatT *source, bool transpose);

const angle::Format &GetFormatFromFormatType(GLenum format, GLenum type);

angle::Result ComputeStartVertex(ContextImpl *contextImpl,
                                 const gl::IndexRange &indexRange,
                                 GLint baseVertex,
                                 GLint *firstVertexOut);

angle::Result GetVertexRangeInfo(const gl::Context *context,
                                 GLint firstVertex,
                                 GLsizei vertexOrIndexCount,
                                 gl::DrawElementsType indexTypeOrInvalid,
                                 const void *indices,
                                 GLint baseVertex,
                                 GLint *startVertexOut,
                                 size_t *vertexCountOut);

gl::Rectangle ClipRectToScissor(const gl::State &glState, const gl::Rectangle &rect, bool invertY);

// Helper method to intialize a FeatureSet with overrides from the DisplayState
void ApplyFeatureOverrides(angle::FeatureSetBase *features, const egl::DisplayState &state);

template <typename In>
uint32_t LineLoopRestartIndexCountHelper(GLsizei indexCount, const uint8_t *srcPtr)
{
    constexpr In restartIndex = gl::GetPrimitiveRestartIndexFromType<In>();
    const In *inIndices       = reinterpret_cast<const In *>(srcPtr);
    uint32_t numIndices       = 0;
    // See CopyLineLoopIndicesWithRestart() below for more info on how
    // numIndices is calculated.
    GLsizei loopStartIndex = 0;
    for (GLsizei curIndex = 0; curIndex < indexCount; curIndex++)
    {
        In vertex = inIndices[curIndex];
        if (vertex != restartIndex)
        {
            numIndices++;
        }
        else
        {
            if (curIndex > loopStartIndex)
            {
                numIndices += 2;
            }
            loopStartIndex = curIndex + 1;
        }
    }
    if (indexCount > loopStartIndex)
    {
        numIndices++;
    }
    return numIndices;
}

inline uint32_t GetLineLoopWithRestartIndexCount(gl::DrawElementsType glIndexType,
                                                 GLsizei indexCount,
                                                 const uint8_t *srcPtr)
{
    switch (glIndexType)
    {
        case gl::DrawElementsType::UnsignedByte:
            return LineLoopRestartIndexCountHelper<uint8_t>(indexCount, srcPtr);
        case gl::DrawElementsType::UnsignedShort:
            return LineLoopRestartIndexCountHelper<uint16_t>(indexCount, srcPtr);
        case gl::DrawElementsType::UnsignedInt:
            return LineLoopRestartIndexCountHelper<uint32_t>(indexCount, srcPtr);
        default:
            UNREACHABLE();
            return 0;
    }
}

// Writes the line-strip vertices for a line loop to outPtr,
// where outLimit is calculated as in GetPrimitiveRestartIndexCount.
template <typename In, typename Out>
void CopyLineLoopIndicesWithRestart(GLsizei indexCount, const uint8_t *srcPtr, uint8_t *outPtr)
{
    constexpr In restartIndex     = gl::GetPrimitiveRestartIndexFromType<In>();
    constexpr Out outRestartIndex = gl::GetPrimitiveRestartIndexFromType<Out>();
    const In *inIndices           = reinterpret_cast<const In *>(srcPtr);
    Out *outIndices               = reinterpret_cast<Out *>(outPtr);
    GLsizei loopStartIndex        = 0;
    for (GLsizei curIndex = 0; curIndex < indexCount; curIndex++)
    {
        In vertex = inIndices[curIndex];
        if (vertex != restartIndex)
        {
            *(outIndices++) = static_cast<Out>(vertex);
        }
        else
        {
            if (curIndex > loopStartIndex)
            {
                // Emit an extra vertex only if the loop is not empty.
                *(outIndices++) = inIndices[loopStartIndex];
                // Then restart the strip.
                *(outIndices++) = outRestartIndex;
            }
            loopStartIndex = curIndex + 1;
        }
    }
    if (indexCount > loopStartIndex)
    {
        // Close the last loop if not empty.
        *(outIndices++) = inIndices[loopStartIndex];
    }
}

void GetSamplePosition(GLsizei sampleCount, size_t index, GLfloat *xy);

angle::Result MultiDrawArraysGeneral(ContextImpl *contextImpl,
                                     const gl::Context *context,
                                     gl::PrimitiveMode mode,
                                     const GLint *firsts,
                                     const GLsizei *counts,
                                     GLsizei drawcount);
angle::Result MultiDrawArraysInstancedGeneral(ContextImpl *contextImpl,
                                              const gl::Context *context,
                                              gl::PrimitiveMode mode,
                                              const GLint *firsts,
                                              const GLsizei *counts,
                                              const GLsizei *instanceCounts,
                                              GLsizei drawcount);
angle::Result MultiDrawElementsGeneral(ContextImpl *contextImpl,
                                       const gl::Context *context,
                                       gl::PrimitiveMode mode,
                                       const GLsizei *counts,
                                       gl::DrawElementsType type,
                                       const GLvoid *const *indices,
                                       GLsizei drawcount);
angle::Result MultiDrawElementsInstancedGeneral(ContextImpl *contextImpl,
                                                const gl::Context *context,
                                                gl::PrimitiveMode mode,
                                                const GLsizei *counts,
                                                gl::DrawElementsType type,
                                                const GLvoid *const *indices,
                                                const GLsizei *instanceCounts,
                                                GLsizei drawcount);
angle::Result MultiDrawArraysInstancedBaseInstanceGeneral(ContextImpl *contextImpl,
                                                          const gl::Context *context,
                                                          gl::PrimitiveMode mode,
                                                          const GLint *firsts,
                                                          const GLsizei *counts,
                                                          const GLsizei *instanceCounts,
                                                          const GLuint *baseInstances,
                                                          GLsizei drawcount);
angle::Result MultiDrawElementsInstancedBaseVertexBaseInstanceGeneral(ContextImpl *contextImpl,
                                                                      const gl::Context *context,
                                                                      gl::PrimitiveMode mode,
                                                                      const GLsizei *counts,
                                                                      gl::DrawElementsType type,
                                                                      const GLvoid *const *indices,
                                                                      const GLsizei *instanceCounts,
                                                                      const GLint *baseVertices,
                                                                      const GLuint *baseInstances,
                                                                      GLsizei drawcount);

// RAII object making sure reset uniforms is called no matter whether there's an error in draw calls
class ResetBaseVertexBaseInstance : angle::NonCopyable
{
  public:
    ResetBaseVertexBaseInstance(gl::Program *programObject,
                                bool resetBaseVertex,
                                bool resetBaseInstance);

    ~ResetBaseVertexBaseInstance();

  private:
    gl::Program *mProgramObject;
    bool mResetBaseVertex;
    bool mResetBaseInstance;
};

}  // namespace rx

// MultiDraw macro patterns
// These macros are to avoid too much code duplication as we don't want to have if detect for
// hasDrawID/BaseVertex/BaseInstance inside for loop in a multiDraw call Part of these are put in
// the header as we want to share with specialized context impl on some platforms for multidraw
#define ANGLE_SET_DRAW_ID_UNIFORM_0(drawID) \
    {}
#define ANGLE_SET_DRAW_ID_UNIFORM_1(drawID) programObject->setDrawIDUniform(drawID)
#define ANGLE_SET_DRAW_ID_UNIFORM(cond) ANGLE_SET_DRAW_ID_UNIFORM_##cond

#define ANGLE_SET_BASE_VERTEX_UNIFORM_0(baseVertex) \
    {}
#define ANGLE_SET_BASE_VERTEX_UNIFORM_1(baseVertex) programObject->setBaseVertexUniform(baseVertex);
#define ANGLE_SET_BASE_VERTEX_UNIFORM(cond) ANGLE_SET_BASE_VERTEX_UNIFORM_##cond

#define ANGLE_SET_BASE_INSTANCE_UNIFORM_0(baseInstance) \
    {}
#define ANGLE_SET_BASE_INSTANCE_UNIFORM_1(baseInstance) \
    programObject->setBaseInstanceUniform(baseInstance)
#define ANGLE_SET_BASE_INSTANCE_UNIFORM(cond) ANGLE_SET_BASE_INSTANCE_UNIFORM_##cond

#define ANGLE_NOOP_DRAW_ context->noopDraw(mode, counts[drawID])
#define ANGLE_NOOP_DRAW_INSTANCED \
    context->noopDrawInstanced(mode, counts[drawID], instanceCounts[drawID])
#define ANGLE_NOOP_DRAW(_instanced) ANGLE_NOOP_DRAW##_instanced

#define ANGLE_MARK_TRANSFORM_FEEDBACK_USAGE_ \
    gl::MarkTransformFeedbackBufferUsage(context, counts[drawID], 1)
#define ANGLE_MARK_TRANSFORM_FEEDBACK_USAGE_INSTANCED \
    gl::MarkTransformFeedbackBufferUsage(context, counts[drawID], instanceCounts[drawID])
#define ANGLE_MARK_TRANSFORM_FEEDBACK_USAGE(instanced) \
    ANGLE_MARK_TRANSFORM_FEEDBACK_USAGE##instanced

#endif  // LIBANGLE_RENDERER_RENDERER_UTILS_H_
