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

// DrawBaseVertexBaseInstanceTest: Tests of GL_ANGLE_base_vertex_base_instance
// DrawBaseInstanceTest: Tests of GL_EXT_base_instance

#include "gpu_info_util/SystemInfo.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"

#include <numeric>

using namespace angle;

namespace
{

// Create a kWidth * kHeight canvas equally split into kCountX * kCountY tiles
// each containing a quad partially covering each tile
constexpr uint32_t kWidth                  = 256;
constexpr uint32_t kHeight                 = 256;
constexpr uint32_t kCountX                 = 8;
constexpr uint32_t kCountY                 = 8;
constexpr std::array<GLfloat, 2> kTileSize = {
    1.f / static_cast<GLfloat>(kCountX),
    1.f / static_cast<GLfloat>(kCountY),
};
constexpr std::array<uint32_t, 2> kTilePixelSize  = {kWidth / kCountX, kHeight / kCountY};
constexpr std::array<GLfloat, 2> kQuadRadius      = {0.25f * kTileSize[0], 0.25f * kTileSize[1]};
constexpr std::array<uint32_t, 2> kPixelCheckSize = {
    static_cast<uint32_t>(kQuadRadius[0] * kWidth),
    static_cast<uint32_t>(kQuadRadius[1] * kHeight)};

constexpr std::array<GLfloat, 2> getTileCenter(uint32_t x, uint32_t y)
{
    return {
        kTileSize[0] * (0.5f + static_cast<GLfloat>(x)),
        kTileSize[1] * (0.5f + static_cast<GLfloat>(y)),
    };
}
constexpr std::array<std::array<GLfloat, 3>, 4> getQuadVertices(uint32_t x, uint32_t y)
{
    const auto center = getTileCenter(x, y);
    return {
        std::array<GLfloat, 3>{center[0] - kQuadRadius[0], center[1] - kQuadRadius[1], 0.0f},
        std::array<GLfloat, 3>{center[0] + kQuadRadius[0], center[1] - kQuadRadius[1], 0.0f},
        std::array<GLfloat, 3>{center[0] + kQuadRadius[0], center[1] + kQuadRadius[1], 0.0f},
        std::array<GLfloat, 3>{center[0] - kQuadRadius[0], center[1] + kQuadRadius[1], 0.0f},
    };
}

enum class BaseVertexOption
{
    NoBaseVertex,
    UseBaseVertex
};

enum class BaseInstanceOption
{
    NoBaseInstance,
    UseBaseInstance
};

enum class BufferDataUsageOption
{
    StaticDraw,
    DynamicDraw
};

using DrawBaseVertexBaseInstanceTestParams = std::
    tuple<angle::PlatformParameters, BaseVertexOption, BaseInstanceOption, BufferDataUsageOption>;

struct PrintToStringParamName
{
    std::string operator()(
        const ::testing::TestParamInfo<DrawBaseVertexBaseInstanceTestParams> &info) const
    {
        ::std::stringstream ss;
        ss << std::get<0>(info.param) << "_"
           << (std::get<3>(info.param) == BufferDataUsageOption::StaticDraw ? "_StaticDraw"
                                                                            : "_DynamicDraw")
           << (std::get<2>(info.param) == BaseInstanceOption::UseBaseInstance ? "_UseBaseInstance"
                                                                              : "")
           << (std::get<1>(info.param) == BaseVertexOption::UseBaseVertex ? "_UseBaseVertex" : "");
        return ss.str();
    }
};

// These tests check correctness of the ANGLE_base_vertex_base_instance extension.
// An array of quads is drawn across the screen.
// gl_VertexID, gl_InstanceID, gl_BaseVertex, and gl_BaseInstance
// are checked by using them to select the color of the draw.
class DrawBaseVertexBaseInstanceTest
    : public ANGLETestBase,
      public ::testing::WithParamInterface<DrawBaseVertexBaseInstanceTestParams>
{
  protected:
    DrawBaseVertexBaseInstanceTest() : ANGLETestBase(std::get<0>(GetParam()))
    {
        setWindowWidth(kWidth);
        setWindowHeight(kHeight);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);

        // Rects in the same column are within a vertex array, testing gl_VertexID, gl_BaseVertex
        // Rects in the same row are drawn by instancing, testing gl_InstanceID, gl_BaseInstance

        mIndices = {0, 1, 2, 0, 2, 3};

        for (uint32_t y = 0; y < kCountY; ++y)
        {
            // v3 ---- v2
            // |       |
            // |       |
            // v0 ---- v1

            const auto vs = getQuadVertices(0, y);

            for (const auto &v : vs)
            {
                mVertices.insert(mVertices.end(), v.begin(), v.end());
            }

            for (GLushort i : mIndices)
            {
                mNonIndexedVertices.insert(mNonIndexedVertices.end(), vs[i].begin(), vs[i].end());
            }
        }

        mRegularIndices.resize(kCountY * mIndices.size());
        for (uint32_t i = 0; i < kCountY; i++)
        {
            uint32_t oi = 6 * i;
            uint32_t ov = 4 * i;
            for (uint32_t j = 0; j < 6; j++)
            {
                mRegularIndices[oi + j] = mIndices[j] + ov;
            }
        }

        std::iota(mInstancedArrayId.begin(), mInstancedArrayId.end(), 0.0f);
        std::reverse_copy(mInstancedArrayId.begin(), mInstancedArrayId.end(),
                          mInstancedArrayColorId.begin());
    }

    void SetUp() override { ANGLETestBase::ANGLETestSetUp(); }

    bool useBaseVertexBuiltin() const
    {
        return std::get<1>(GetParam()) == BaseVertexOption::UseBaseVertex;
    }

    bool useBaseInstanceBuiltin() const
    {
        return std::get<2>(GetParam()) == BaseInstanceOption::UseBaseInstance;
    }

    GLenum getBufferDataUsage() const
    {
        return std::get<3>(GetParam()) == BufferDataUsageOption::StaticDraw ? GL_STATIC_DRAW
                                                                            : GL_DYNAMIC_DRAW;
    }

    std::string vertexShaderSource300(bool isDrawArrays, bool isMultiDraw, bool divisorTest)
    {
        // Each color channel is to test the value of
        // R: gl_InstanceID and gl_BaseInstance
        // G: gl_VertexID and gl_BaseVertex
        // B: gl_BaseVertex

        std::stringstream shader;
        shader << ("#version 300 es\n")
               << (isMultiDraw ? "#extension GL_ANGLE_multi_draw : require\n" : "")
               << ("#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require\n")
               << "#define kCountX " << kCountX << "\n"
               << "#define kCountY " << kCountY << "\n"
               << R"(
in vec2 vPosition;
)" << (useBaseInstanceBuiltin() ? "" : "in float vInstanceID;\n")
               << (!divisorTest ? "" : "in float vInstanceColorID;\n") << R"(
out vec4 color;
void main()
{
    const float xStep = 1.0 / float(kCountX);
    const float yStep = 1.0 / float(kCountY);
    float x_id = )"
               << (useBaseInstanceBuiltin() ? " float(gl_InstanceID + gl_BaseInstance);"
                                            : "vInstanceID;")
               << "float x_color = "
               << (divisorTest ? "xStep * (vInstanceColorID + 1.0f);" : " 1.0 - xStep * x_id;")
               << R"(
    float y_id = float(gl_VertexID / )"
               << (isDrawArrays ? "6" : "4") << R"();

    color = vec4(
        x_color,
        1.0 - yStep * y_id,
        )" << (useBaseVertexBuiltin() ? "1.0 - yStep * float(gl_BaseVertex) / 4.0" : "1.0")
               << R"(,
        1);

    mat3 transform = mat3(1.0);
    transform[2][0] = x_id * xStep;

    gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1);
})";

        return shader.str();
    }

    std::string fragmentShaderSource300()
    {
        return
            R"(#version 300 es
precision mediump float;
in vec4 color;
out vec4 o_color;
void main()
{
    o_color = color;
})";
    }

    void setupProgram(GLProgram &program,
                      bool isDrawArrays  = true,
                      bool isMultiDraw   = false,
                      bool isDivisorTest = false)
    {
        program.makeRaster(vertexShaderSource300(isDrawArrays, isMultiDraw, isDivisorTest).c_str(),
                           fragmentShaderSource300().c_str());
        EXPECT_GL_NO_ERROR();
        ASSERT_TRUE(program.valid());
        glUseProgram(program);
        mPositionLoc = glGetAttribLocation(program, "vPosition");
        if (!useBaseInstanceBuiltin())
        {
            mInstanceIDLoc      = glGetAttribLocation(program, "vInstanceID");
            mInstanceColorIDLoc = glGetAttribLocation(program, "vInstanceColorID");
        }
    }

    void setupNonIndexedBuffers(GLBuffer &vertexBuffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mNonIndexedVertices.size(),
                     mNonIndexedVertices.data(), getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupIndexedBuffers(GLBuffer &vertexBuffer, GLBuffer &indexBuffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mVertices.size(), mVertices.data(),
                     getBufferDataUsage());

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mIndices.size(), mIndices.data(),
                     getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupInstanceIDBuffer(GLBuffer &instanceIDBuffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mInstancedArrayId.size(),
                     mInstancedArrayId.data(), getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupInstanceColorIDBuffer(GLBuffer &instanceIDBuffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mInstancedArrayColorId.size(),
                     mInstancedArrayColorId.data(), getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupRegularIndexedBuffer(GLBuffer &indexBuffer)
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mRegularIndices.size(),
                     mRegularIndices.data(), getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupPositionVertexAttribPointer()
    {
        glEnableVertexAttribArray(mPositionLoc);
        glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
    }

    void setupInstanceIDVertexAttribPointer(GLuint instanceIDLoc)
    {
        glEnableVertexAttribArray(instanceIDLoc);
        glVertexAttribPointer(instanceIDLoc, 1, GL_FLOAT, GL_FALSE, 0, 0);
        glVertexAttribDivisor(instanceIDLoc, 1);
    }

    void doDrawArraysInstancedBaseInstance()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        const uint32_t countPerDraw = kCountY * 6;

        for (uint32_t i = 0; i < kCountX; i += 2)
        {
            glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLES, 0, countPerDraw, 2, i);
        }
    }

    void doMultiDrawArraysInstancedBaseInstance()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        const uint32_t countPerDraw = kCountY * 6;

        const GLsizei drawCount = kCountX / 2;
        const std::vector<GLsizei> counts(drawCount, countPerDraw);
        const std::vector<GLsizei> firsts(drawCount, 0);
        const std::vector<GLsizei> instanceCounts(drawCount, 2);
        std::vector<GLuint> baseInstances(drawCount);
        for (size_t i = 0; i < drawCount; i++)
        {
            baseInstances[i] = i * 2;
        }
        glMultiDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLES, firsts.data(), counts.data(),
                                                    instanceCounts.data(), baseInstances.data(),
                                                    drawCount);
    }

    void doDrawElementsInstancedBaseVertexBaseInstance()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        const uint32_t countPerDraw = 6;

        for (uint32_t v = 0; v < kCountY; v++)
        {
            for (uint32_t i = 0; i < kCountX; i += 2)
            {
                glDrawElementsInstancedBaseVertexBaseInstanceANGLE(
                    GL_TRIANGLES, countPerDraw, GL_UNSIGNED_SHORT,
                    reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 2, v * 4, i);
            }
        }
    }

    // Call this after the *BaseVertexBaseInstance draw call to check if value of BaseVertex and
    // BaseInstance are reset to zero
    void doDrawArraysBaseInstanceReset()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6 * kCountY, 1);
    }

    void doDrawElementsBaseVertexBaseInstanceReset()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawElementsInstanced(GL_TRIANGLES, 6 * kCountY, GL_UNSIGNED_SHORT,
                                reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 1);
    }

    void doMultiDrawElementsInstancedBaseVertexBaseInstance()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        const GLsizei drawCount = kCountX * kCountY / 2;
        const std::vector<GLsizei> counts(drawCount, 6);
        const std::vector<GLsizei> instanceCounts(drawCount, 2);
        const std::vector<GLvoid *> indices(drawCount, 0);
        std::vector<GLint> baseVertices(drawCount);
        std::vector<GLuint> baseInstances(drawCount);

        GLsizei b = 0;
        for (uint32_t v = 0; v < kCountY; v++)
        {
            for (uint32_t i = 0; i < kCountX; i += 2)
            {
                baseVertices[b]  = v * 4;
                baseInstances[b] = i;
                b++;
            }
        }

        glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE(
            GL_TRIANGLES, counts.data(), GL_UNSIGNED_SHORT, indices.data(), instanceCounts.data(),
            baseVertices.data(), baseInstances.data(), drawCount);
    }

    void checkDrawResult(bool hasBaseVertex, bool oneColumn = false)
    {
        uint32_t numColums = oneColumn ? 1 : kCountX;
        for (uint32_t y = 0; y < kCountY; ++y)
        {
            for (uint32_t x = 0; x < numColums; ++x)
            {
                uint32_t center_x = x * kTilePixelSize[0] + kTilePixelSize[0] / 2;
                uint32_t center_y = y * kTilePixelSize[1] + kTilePixelSize[1] / 2;

                EXPECT_PIXEL_NEAR(center_x - kPixelCheckSize[0] / 2,
                                  center_y - kPixelCheckSize[1] / 2,
                                  256.0 * (1.0 - (float)x / (float)kCountX),
                                  256.0 * (1.0 - (float)y / (float)kCountY),
                                  useBaseVertexBuiltin() && hasBaseVertex && !oneColumn
                                      ? 256.0 * (1.0 - (float)y / (float)kCountY)
                                      : 255,
                                  255, 3);
            }
        }
    }

    void TearDown() override { ANGLETestBase::ANGLETestTearDown(); }

    bool requestDrawBaseVertexBaseInstanceExtension()
    {
        if (IsGLExtensionRequestable("GL_ANGLE_base_vertex_base_instance"))
        {
            glRequestExtensionANGLE("GL_ANGLE_base_vertex_base_instance");
        }

        if (!IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"))
        {
            return false;
        }

        if (IsGLExtensionRequestable("GL_ANGLE_base_vertex_base_instance_shader_builtin"))
        {
            glRequestExtensionANGLE("GL_ANGLE_base_vertex_base_instance_shader_builtin");
        }

        return true;
    }

    bool requestInstancedExtension()
    {
        if (IsGLExtensionRequestable("GL_ANGLE_instanced_arrays"))
        {
            glRequestExtensionANGLE("GL_ANGLE_instanced_arrays");
        }

        if (!IsGLExtensionEnabled("GL_ANGLE_instanced_arrays"))
        {
            return false;
        }

        return true;
    }

    bool requestExtensions()
    {
        if (getClientMajorVersion() <= 2)
        {
            if (!requestInstancedExtension())
            {
                return false;
            }
        }
        return requestDrawBaseVertexBaseInstanceExtension();
    }

    // Used for base vertex base instance draw calls
    std::vector<GLushort> mIndices;
    std::vector<GLfloat> mVertices;
    std::vector<GLfloat> mNonIndexedVertices;
    // Used when gl_BaseInstance is not used
    std::array<GLfloat, kCountX> mInstancedArrayId      = {};
    std::array<GLfloat, kCountX> mInstancedArrayColorId = {};
    // Used for regular draw calls without base vertex base instance
    std::vector<GLushort> mRegularIndices;
    GLint mPositionLoc         = 0;
    GLuint mInstanceIDLoc      = 0;
    GLuint mInstanceColorIDLoc = 0;
};

using DrawBaseInstanceTestParams = std::
    tuple<angle::PlatformParameters, BaseVertexOption, BaseInstanceOption, BufferDataUsageOption>;

// The tests in DrawBaseInstanceTest check the correctness
// of the EXT_base_instance extension.
// gl_VertexID, gl_InstanceID, gl_BaseVertex, and gl_BaseInstance
// are checked by using them to select the color of the draw.
class DrawBaseInstanceTest : public ANGLETestBase,
                             public ::testing::WithParamInterface<DrawBaseInstanceTestParams>
{
  protected:
    DrawBaseInstanceTest() : ANGLETestBase(std::get<0>(GetParam()))
    {
        setWindowWidth(kWidth);
        setWindowHeight(kHeight);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);

        // Rects in the same column are within a vertex array, testing gl_VertexID, gl_BaseVertex
        // Rects in the same row are drawn by instancing, testing gl_InstanceID, gl_BaseInstance

        mIndices = {0, 1, 2, 0, 2, 3};

        for (uint32_t y = 0; y < kCountY; ++y)
        {
            // v3 ---- v2
            // |       |
            // |       |
            // v0 ---- v1

            const auto vs = getQuadVertices(0, y);

            for (const auto &v : vs)
            {
                mVertices.insert(mVertices.end(), v.begin(), v.end());
            }

            for (GLushort i : mIndices)
            {
                mNonIndexedVertices.insert(mNonIndexedVertices.end(), vs[i].begin(), vs[i].end());
            }
        }

        mRegularIndices.resize(kCountY * mIndices.size());
        for (uint32_t i = 0; i < kCountY; i++)
        {
            uint32_t oi = 6 * i;
            uint32_t ov = 4 * i;
            for (uint32_t j = 0; j < 6; j++)
            {
                mRegularIndices[oi + j] = mIndices[j] + ov;
            }
        }

        std::iota(mInstancedArrayId.begin(), mInstancedArrayId.end(), 0.0f);
        std::reverse_copy(mInstancedArrayId.begin(), mInstancedArrayId.end(),
                          mInstancedArrayColorId.begin());
    }

    void SetUp() override { ANGLETestBase::ANGLETestSetUp(); }

    bool useBaseVertexBuiltin() const
    {
        return std::get<1>(GetParam()) == BaseVertexOption::UseBaseVertex;
    }

    bool useBaseInstanceBuiltin() const
    {
        return std::get<2>(GetParam()) == BaseInstanceOption::UseBaseInstance;
    }

    GLenum getBufferDataUsage() const
    {
        return std::get<3>(GetParam()) == BufferDataUsageOption::StaticDraw ? GL_STATIC_DRAW
                                                                            : GL_DYNAMIC_DRAW;
    }

    std::string vertexShaderSource300(bool isDrawArrays, bool divisorTest)
    {
        // Each color channel is to test the value of
        // R: gl_InstanceID and gl_BaseInstance
        // G: gl_VertexID and gl_BaseVertex
        // B: gl_BaseVertex

        std::stringstream shader;
        shader << ("#version 300 es\n")
               << ("#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require\n")
               << "#define kCountX " << kCountX << "\n"
               << "#define kCountY " << kCountY << "\n"
               << R"(
in vec2 vPosition;
)" << (useBaseInstanceBuiltin() ? "" : "in float vInstanceID;\n")
               << (!divisorTest ? "" : "in float vInstanceColorID;\n") << R"(
// Use a mediump uniform, it can get sorted before the gl_BaseVertex/Instance emulated uniforms
uniform mediump vec2 zero;
out vec4 color;
void main()
{
    const float xStep = 1.0 / float(kCountX);
    const float yStep = 1.0 / float(kCountY);
    float x_id = )"
               << (useBaseInstanceBuiltin() ? " float(gl_InstanceID + gl_BaseInstance);"
                                            : "vInstanceID;")
               << "float x_color = "
               << (divisorTest ? "xStep * (vInstanceColorID + 1.0f);" : " 1.0 - xStep * x_id;")
               << R"(
    float y_id = float(gl_VertexID / )"
               << (isDrawArrays ? "6" : "4") << R"();

    color = vec4(
        x_color,
        1.0 - yStep * y_id,
        )" << (useBaseVertexBuiltin() ? "1.0 - yStep * float(gl_BaseVertex) / 4.0" : "1.0")
               << R"(,
        1);
    color.xy += zero;

    mat3 transform = mat3(1.0);
    transform[2][0] = x_id * xStep;

    gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1);
})";

        return shader.str();
    }

    std::string fragmentShaderSource300()
    {
        return
            R"(#version 300 es
precision mediump float;
in vec4 color;
out vec4 o_color;
void main()
{
    o_color = color;
})";
    }

    void setupProgram(GLProgram &program, bool isDrawArrays = true, bool isDivisorTest = false)
    {
        program.makeRaster(vertexShaderSource300(isDrawArrays, isDivisorTest).c_str(),
                           fragmentShaderSource300().c_str());
        EXPECT_GL_NO_ERROR();
        ASSERT_TRUE(program.valid());
        glUseProgram(program);
        mPositionLoc = glGetAttribLocation(program, "vPosition");
        if (!useBaseInstanceBuiltin())
        {
            mInstanceIDLoc      = glGetAttribLocation(program, "vInstanceID");
            mInstanceColorIDLoc = glGetAttribLocation(program, "vInstanceColorID");
        }
    }

    void setupNonIndexedBuffers(GLBuffer &vertexBuffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mNonIndexedVertices.size(),
                     mNonIndexedVertices.data(), getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupIndexedBuffers(GLBuffer &vertexBuffer, GLBuffer &indexBuffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mVertices.size(), mVertices.data(),
                     getBufferDataUsage());

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mIndices.size(), mIndices.data(),
                     getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupInstanceIDBuffer(GLBuffer &instanceIDBuffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mInstancedArrayId.size(),
                     mInstancedArrayId.data(), getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupInstanceColorIDBuffer(GLBuffer &instanceIDBuffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mInstancedArrayColorId.size(),
                     mInstancedArrayColorId.data(), getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupRegularIndexedBuffer(GLBuffer &indexBuffer)
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mRegularIndices.size(),
                     mRegularIndices.data(), getBufferDataUsage());

        ASSERT_GL_NO_ERROR();
    }

    void setupPositionVertexAttribPointer()
    {
        glEnableVertexAttribArray(mPositionLoc);
        glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
    }

    void setupInstanceIDVertexAttribPointer(GLuint instanceIDLoc)
    {
        glEnableVertexAttribArray(instanceIDLoc);
        glVertexAttribPointer(instanceIDLoc, 1, GL_FLOAT, GL_FALSE, 0, 0);
        glVertexAttribDivisor(instanceIDLoc, 1);
    }

    void doDrawArraysInstancedBaseInstance()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        const uint32_t countPerDraw = kCountY * 6;

        for (uint32_t i = 0; i < kCountX; i += 2)
        {
            glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLES, 0, countPerDraw, 2, i);
        }
    }

    void doDrawElementsInstancedBaseInstance()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        const uint32_t countPerDraw = 6;

        for (uint32_t v = 0; v < kCountY; v++)
        {
            for (uint32_t i = 0; i < kCountX; i += 2)
            {
                glDrawElementsInstancedBaseInstanceEXT(GL_TRIANGLES, countPerDraw,
                                                       GL_UNSIGNED_SHORT,
                                                       (void *)(v * 6 * sizeof(GLushort)), 2, i);
            }
        }
    }

    void doDrawElementsInstancedBaseVertexBaseInstance()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        const uint32_t countPerDraw = 6;

        for (uint32_t v = 0; v < kCountY; v++)
        {
            for (uint32_t i = 0; i < kCountX; i += 2)
            {
                glDrawElementsInstancedBaseVertexBaseInstanceEXT(
                    GL_TRIANGLES, countPerDraw, GL_UNSIGNED_SHORT,
                    reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 2, v * 4, i);
            }
        }
    }

    // Call this after the *BaseInstance draw call to check
    // if value of BaseInstance is reset to zero
    void doDrawArraysBaseInstanceReset()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6 * kCountY, 1);
    }

    void doDrawElementsInstancedBaseInstanceReset()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawElementsInstanced(GL_TRIANGLES, 6 * kCountY, GL_UNSIGNED_SHORT,
                                reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 1);
    }

    void doDrawElementsBaseVertexBaseInstanceReset()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawElementsInstanced(GL_TRIANGLES, 6 * kCountY, GL_UNSIGNED_SHORT,
                                reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 1);
    }

    void checkDrawResult(bool hasBaseVertex, bool oneColumn = false)
    {
        uint32_t numColums = oneColumn ? 1 : kCountX;
        for (uint32_t y = 0; y < kCountY; ++y)
        {
            for (uint32_t x = 0; x < numColums; ++x)
            {
                uint32_t center_x = x * kTilePixelSize[0] + kTilePixelSize[0] / 2;
                uint32_t center_y = y * kTilePixelSize[1] + kTilePixelSize[1] / 2;

                EXPECT_PIXEL_NEAR(center_x - kPixelCheckSize[0] / 2,
                                  center_y - kPixelCheckSize[1] / 2,
                                  256.0 * (1.0 - (float)x / (float)kCountX),
                                  256.0 * (1.0 - (float)y / (float)kCountY),
                                  useBaseVertexBuiltin() && hasBaseVertex && !oneColumn
                                      ? 256.0 * (1.0 - (float)y / (float)kCountY)
                                      : 255,
                                  255, 3);
            }
        }
    }

    void TearDown() override { ANGLETestBase::ANGLETestTearDown(); }

    bool requestExtensions()
    {
        if (IsGLExtensionRequestable("GL_ANGLE_base_vertex_base_instance_shader_builtin"))
        {
            glRequestExtensionANGLE("GL_ANGLE_base_vertex_base_instance_shader_builtin");
        }

        if (!IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance_shader_builtin"))
        {
            return false;
        }

        return EnsureGLExtensionEnabled("GL_EXT_base_instance");
    }

    // Used for base vertex base instance draw calls
    std::vector<GLushort> mIndices;
    std::vector<GLfloat> mVertices;
    std::vector<GLfloat> mNonIndexedVertices;
    // Used when gl_BaseInstance is not used
    std::array<GLfloat, kCountX> mInstancedArrayId      = {};
    std::array<GLfloat, kCountX> mInstancedArrayColorId = {};
    // Used for regular draw calls without base vertex base instance
    std::vector<GLushort> mRegularIndices;
    GLint mPositionLoc         = 0;
    GLuint mInstanceIDLoc      = 0;
    GLuint mInstanceColorIDLoc = 0;
};

// Tests that compile a program with the extension succeeds
TEST_P(DrawBaseVertexBaseInstanceTest, CanCompile)
{
    ANGLE_SKIP_TEST_IF(!requestExtensions());
    GLProgram p0;
    setupProgram(p0, true, false);
    GLProgram p1;
    setupProgram(p1, true, true);
    GLProgram p2;
    setupProgram(p2, false, false);
    GLProgram p3;
    setupProgram(p3, false, true);
}

// Tests that negative baseVertex works properly.
TEST_P(DrawBaseVertexBaseInstanceTest, NegativeBaseVertex)
{
    ANGLE_SKIP_TEST_IF(!requestExtensions());

    GLProgram program;
    setupProgram(program, false, false);

    GLBuffer vertexBuffer;
    GLBuffer indexBuffer;
    setupIndexedBuffers(vertexBuffer, indexBuffer);
    setupPositionVertexAttribPointer();

    // Create a new index buffer with shifted indices: {4, 5, 6, 4, 6, 7}
    // These indices point to the second quad (vertices 4, 5, 6, 7).
    std::vector<GLushort> shiftedIndices = {4, 5, 6, 4, 6, 7};
    GLBuffer shiftedIndexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shiftedIndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * shiftedIndices.size(),
                 shiftedIndices.data(), GL_STATIC_DRAW);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Draw using baseVertex = -4.
    // This should shift the indices back to {0, 1, 2, 0, 2, 3}, drawing the first quad!
    glDrawElementsInstancedBaseVertexBaseInstanceANGLE(
        GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)),
        1, -4, 0);

    ASSERT_GL_NO_ERROR();

    // Check that the first quad was drawn as white.
    EXPECT_PIXEL_NEAR(16, 16, 255, 255, 255, 255, 3);
}

// Tests if baseInstance works properly with instanced array with non-zero divisor
TEST_P(DrawBaseVertexBaseInstanceTest, BaseInstanceDivisor)
{
    ANGLE_SKIP_TEST_IF(!requestExtensions());
    ANGLE_SKIP_TEST_IF(useBaseInstanceBuiltin());

    GLProgram program;
    setupProgram(program, true, false, true);

    GLBuffer nonIndexedVertexBuffer;
    setupNonIndexedBuffers(nonIndexedVertexBuffer);
    setupPositionVertexAttribPointer();

    GLBuffer instanceIDBuffer;
    GLBuffer instanceColorIDBuffer;

    setupInstanceIDBuffer(instanceIDBuffer);
    setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    setupInstanceColorIDBuffer(instanceColorIDBuffer);
    setupInstanceIDVertexAttribPointer(mInstanceColorIDLoc);

    doDrawArraysInstancedBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false);

    doDrawArraysBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false, true);

    GLProgram programIndexed;
    setupProgram(programIndexed, false, false, true);

    GLBuffer indexBuffer;
    GLBuffer vertexBuffer;
    setupIndexedBuffers(vertexBuffer, indexBuffer);
    setupPositionVertexAttribPointer();

    glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer);
    setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    glBindBuffer(GL_ARRAY_BUFFER, instanceColorIDBuffer);
    setupInstanceIDVertexAttribPointer(mInstanceColorIDLoc);

    doDrawElementsInstancedBaseVertexBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(true);

    setupRegularIndexedBuffer(indexBuffer);
    doDrawElementsBaseVertexBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false, true);
}

// Tests basic functionality of glDrawArraysInstancedBaseInstanceANGLE
TEST_P(DrawBaseVertexBaseInstanceTest, DrawArraysInstancedBaseInstance)
{
    // TODO(shrekshao): Temporarily skip this test
    // before we could try updating win AMD bot driver version
    // after Lab team fixed issues with ssh into bot machines
    // Currently this test fail on certain Win7/Win2008Server AMD GPU
    // with driver version 23.20.185.235 when using OpenGL backend.
    // This failure couldn't be produced on local Win10 AMD machine with latest driver installed
    // Same for the MultiDrawArraysInstancedBaseInstance test
    if (IsAMD() && IsWindows() && IsDesktopOpenGL())
    {
        SystemInfo *systemInfo = GetTestSystemInfo();
        if (!systemInfo->gpus.empty())
        {
            ANGLE_SKIP_TEST_IF(0x6613 == systemInfo->gpus[systemInfo->activeGPUIndex].deviceId);
        }
    }

    ANGLE_SKIP_TEST_IF(!requestExtensions());

    GLProgram program;
    setupProgram(program, true);

    GLBuffer vertexBuffer;
    setupNonIndexedBuffers(vertexBuffer);
    setupPositionVertexAttribPointer();

    GLBuffer instanceIDBuffer;
    if (!useBaseInstanceBuiltin())
    {
        setupInstanceIDBuffer(instanceIDBuffer);
        setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    }

    doDrawArraysInstancedBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false);

    doDrawArraysBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false, true);
}

// Tests basic drawcount validation
TEST_P(DrawBaseVertexBaseInstanceTest, MultiDrawValidation)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_multi_draw"));

    const GLint first           = 0;
    const GLsizei count         = 0;
    const GLsizei instanceCount = 0;
    const GLint baseVertex      = 0;
    const GLuint baseInstance   = 0;
    const GLvoid *const indices[1]{nullptr};

    glMultiDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLES, &first, &count, &instanceCount,
                                                &baseInstance, -1);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    glMultiDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLES, &first, &count, &instanceCount,
                                                &baseInstance, 0);
    EXPECT_GL_NO_ERROR();

    glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE(GL_TRIANGLES, &count, GL_UNSIGNED_SHORT,
                                                            indices, &instanceCount, &baseVertex,
                                                            &baseInstance, -1);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE(GL_TRIANGLES, &count, GL_UNSIGNED_SHORT,
                                                            indices, &instanceCount, &baseVertex,
                                                            &baseInstance, 0);
    EXPECT_GL_NO_ERROR();
}

// Tests basic functionality of glMultiDrawArraysInstancedBaseInstance
TEST_P(DrawBaseVertexBaseInstanceTest, MultiDrawArraysInstancedBaseInstance)
{
    if (IsAMD() && IsWindows() && IsDesktopOpenGL())
    {
        SystemInfo *systemInfo = GetTestSystemInfo();
        if (!(systemInfo->activeGPUIndex < 0 || systemInfo->gpus.empty()))
        {
            ANGLE_SKIP_TEST_IF(0x6613 == systemInfo->gpus[systemInfo->activeGPUIndex].deviceId);
        }
    }

    ANGLE_SKIP_TEST_IF(!requestExtensions());

    GLProgram program;
    setupProgram(program, true, true);

    GLBuffer vertexBuffer;
    setupNonIndexedBuffers(vertexBuffer);
    setupPositionVertexAttribPointer();

    GLBuffer instanceIDBuffer;
    if (!useBaseInstanceBuiltin())
    {
        setupInstanceIDBuffer(instanceIDBuffer);
        setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    }

    doMultiDrawArraysInstancedBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false);

    doDrawArraysBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false, true);
}

// Tests basic functionality of glDrawElementsInstancedBaseVertexBaseInstanceANGLE
TEST_P(DrawBaseVertexBaseInstanceTest, DrawElementsInstancedBaseVertexBaseInstance)
{
    ANGLE_SKIP_TEST_IF(!requestExtensions());

    GLProgram program;
    setupProgram(program, false);

    GLBuffer indexBuffer;
    GLBuffer vertexBuffer;
    setupIndexedBuffers(vertexBuffer, indexBuffer);
    setupPositionVertexAttribPointer();

    GLBuffer instanceIDBuffer;
    if (!useBaseInstanceBuiltin())
    {
        setupInstanceIDBuffer(instanceIDBuffer);
        setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    }

    doDrawElementsInstancedBaseVertexBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(true);

    setupRegularIndexedBuffer(indexBuffer);
    doDrawElementsBaseVertexBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(true, true);
}

// Tests basic functionality of glMultiDrawElementsInstancedBaseVertexBaseInstance
TEST_P(DrawBaseVertexBaseInstanceTest, MultiDrawElementsInstancedBaseVertexBaseInstance)
{
    ANGLE_SKIP_TEST_IF(!requestExtensions());

    GLProgram program;
    setupProgram(program, false, true);

    GLBuffer indexBuffer;
    GLBuffer vertexBuffer;
    setupIndexedBuffers(vertexBuffer, indexBuffer);
    setupPositionVertexAttribPointer();

    GLBuffer instanceIDBuffer;
    if (!useBaseInstanceBuiltin())
    {
        setupInstanceIDBuffer(instanceIDBuffer);
        setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    }

    doMultiDrawElementsInstancedBaseVertexBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(true);

    setupRegularIndexedBuffer(indexBuffer);
    doDrawElementsBaseVertexBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(true, true);
}

// Tests if baseInstance works properly with instanced array with non-zero divisor
TEST_P(DrawBaseInstanceTest, BaseInstanceDivisor)
{
    ANGLE_SKIP_TEST_IF(!requestExtensions());
    ANGLE_SKIP_TEST_IF(useBaseInstanceBuiltin());

    GLProgram program;
    setupProgram(program, true, true);

    GLBuffer nonIndexedVertexBuffer;
    setupNonIndexedBuffers(nonIndexedVertexBuffer);
    setupPositionVertexAttribPointer();

    GLBuffer instanceIDBuffer;
    GLBuffer instanceColorIDBuffer;

    setupInstanceIDBuffer(instanceIDBuffer);
    setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    setupInstanceColorIDBuffer(instanceColorIDBuffer);
    setupInstanceIDVertexAttribPointer(mInstanceColorIDLoc);

    doDrawArraysInstancedBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false);

    doDrawArraysBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false, true);

    GLProgram programIndexed;
    setupProgram(programIndexed, false, true);

    GLBuffer indexBuffer;
    GLBuffer vertexBuffer;
    setupIndexedBuffers(vertexBuffer, indexBuffer);
    setupPositionVertexAttribPointer();

    glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer);
    setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    glBindBuffer(GL_ARRAY_BUFFER, instanceColorIDBuffer);
    setupInstanceIDVertexAttribPointer(mInstanceColorIDLoc);

    doDrawElementsInstancedBaseVertexBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(true);

    setupRegularIndexedBuffer(indexBuffer);
    doDrawElementsBaseVertexBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false, true);
}

// Tests basic functionality of glDrawArraysInstancedBaseInstanceEXT
TEST_P(DrawBaseInstanceTest, DrawArraysInstancedBaseInstance)
{
    // This test fails on certain Win7/Win2008Server AMD GPU
    // with driver version 23.20.185.235 when using OpenGL backend.
    // See comments under the ANGLE test of DrawArraysInstancedBaseInstance above.
    if (IsAMD() && IsWindows() && IsDesktopOpenGL())
    {
        SystemInfo *systemInfo = GetTestSystemInfo();
        if (!systemInfo->gpus.empty())
        {
            ANGLE_SKIP_TEST_IF(0x6613 == systemInfo->gpus[systemInfo->activeGPUIndex].deviceId);
        }
    }

    ANGLE_SKIP_TEST_IF(!requestExtensions());

    GLProgram program;
    setupProgram(program);

    GLBuffer vertexBuffer;
    setupNonIndexedBuffers(vertexBuffer);
    setupPositionVertexAttribPointer();

    GLBuffer instanceIDBuffer;
    if (!useBaseInstanceBuiltin())
    {
        setupInstanceIDBuffer(instanceIDBuffer);
        setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    }

    doDrawArraysInstancedBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false);

    doDrawArraysBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false, true);
}

// Tests basic functionality of glDrawElementsInstancedBaseInstanceEXT
TEST_P(DrawBaseInstanceTest, DrawElementsInstancedBaseInstance)
{
    ANGLE_SKIP_TEST_IF(!requestExtensions());

    GLProgram program;
    setupProgram(program, false);

    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mVertices.size(), mVertices.data(),
                 getBufferDataUsage());
    EXPECT_GL_NO_ERROR();
    setupPositionVertexAttribPointer();

    GLBuffer indexBuffer;
    setupRegularIndexedBuffer(indexBuffer);

    GLBuffer instanceIDBuffer;
    if (!useBaseInstanceBuiltin())
    {
        setupInstanceIDBuffer(instanceIDBuffer);
        setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    }

    doDrawElementsInstancedBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false);

    doDrawElementsInstancedBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(false, true);
}

// Tests basic functionality of glDrawElementsInstancedBaseVertexBaseInstanceEXT
TEST_P(DrawBaseInstanceTest, DrawElementsInstancedBaseVertexBaseInstance)
{
    ANGLE_SKIP_TEST_IF(!requestExtensions());

    GLProgram program;
    setupProgram(program, false);

    GLBuffer indexBuffer;
    GLBuffer vertexBuffer;
    setupIndexedBuffers(vertexBuffer, indexBuffer);
    setupPositionVertexAttribPointer();

    GLBuffer instanceIDBuffer;
    if (!useBaseInstanceBuiltin())
    {
        setupInstanceIDBuffer(instanceIDBuffer);
        setupInstanceIDVertexAttribPointer(mInstanceIDLoc);
    }

    doDrawElementsInstancedBaseVertexBaseInstance();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(true);

    setupRegularIndexedBuffer(indexBuffer);
    doDrawElementsBaseVertexBaseInstanceReset();
    EXPECT_GL_NO_ERROR();
    checkDrawResult(true, true);
}

class DrawBaseVertexBaseInstanceTest_ES3 : public ANGLETest<>
{
  public:
    DrawBaseVertexBaseInstanceTest_ES3()
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    void runTestNonZeroDivisor(std::function<void(void)> definePositionBuffer,
                               std::function<void(void)> defineColorBuffer,
                               std::function<void(void)> draw)
    {
        constexpr char kVS[] = R"(#version 300 es
precision mediump float;
in vec2 position;
in vec2 colorIn;
out vec2 color;
void main()
{
    gl_Position = vec4(position, 0, 1);
    if ((gl_InstanceID / 2) % 2 == 1)
    {
        gl_Position.y += 1.0;
    }
    if (gl_InstanceID % 2 == 1)
    {
        gl_Position.x += 1.0;
    }
    color = colorIn;
})";

        constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in vec2 color;
out vec4 colorOut;
void main()
{
    colorOut = vec4(color, 0, 1);
})";

        ANGLE_GL_PROGRAM(program, kVS, kFS);
        glUseProgram(program);

        const GLint posLoc = glGetAttribLocation(program, "position");
        const GLint colLoc = glGetAttribLocation(program, "colorIn");

        // Draw 4 squares.  With a divisor of 2 on color, 2 of them will be one color, two the
        // other.
        GLBuffer position;
        glBindBuffer(GL_ARRAY_BUFFER, position);
        definePositionBuffer();
        glEnableVertexAttribArray(posLoc);
        glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);

        GLBuffer color;
        glBindBuffer(GL_ARRAY_BUFFER, color);
        defineColorBuffer();
        glEnableVertexAttribArray(colLoc);
        glVertexAttribPointer(colLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
        glVertexAttribDivisor(colLoc, 2);

        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        draw();

        // Verify pixels.  The result looks like the following:
        //
        //      +-----------------------+
        //      |                       |
        //      |   +----+     +----+   |
        //      |   |    |     |    |   |  <-- two red squares
        //      |   |    |     |    |   |
        //      |   +----+     +----+   |
        //      |                       |
        //      |                       |
        //      |   +----+     +----+   |
        //      |   |    |     |    |   |  <-- two green squares
        //      |   |    |     |    |   |
        //      |   +----+     +----+   |
        //      |                       |
        //      +-----------------------+
        //            |
        //            V
        //  Black inside and outside the squares
        //
        const int w = getWindowWidth();
        const int h = getWindowHeight();
        // Don't check too close to the edges to account for precision issues.  This is the margin
        // from the edge.
        const int m = 2;
        const GLColor green(51, 153, 0, 255);

        // Left of squares
        EXPECT_PIXEL_RECT_EQ(0, 0, w / 8 - m, h, GLColor::black);
        // Between squares
        EXPECT_PIXEL_RECT_EQ(w / 2 - w / 8 + m, 0, w / 4 - m * 2, h, GLColor::black);
        // Right of squares
        EXPECT_PIXEL_RECT_EQ(w - w / 8 + m, 0, w / 8 - m, h, GLColor::black);

        // Above squares
        EXPECT_PIXEL_RECT_EQ(0, 0, w, h / 8 - m, GLColor::black);
        // Between squares
        EXPECT_PIXEL_RECT_EQ(0, h / 2 - h / 8 + m, w, h / 4 - m * 2, GLColor::black);
        // Below squares
        EXPECT_PIXEL_RECT_EQ(0, h - h / 8 + m, w, h / 8 - m, GLColor::black);

        // Squares
        EXPECT_PIXEL_RECT_EQ(w / 8 + m, h / 8 + m, w / 4 - 4, h / 4 - 4, GLColor::red);
        EXPECT_PIXEL_RECT_EQ(w / 2 + w / 8 + m, h / 8 + m, w / 4 - 4, h / 4 - 4, GLColor::red);
        EXPECT_PIXEL_RECT_EQ(w / 8 + m, h / 2 + h / 8 + m, w / 4 - 4, h / 4 - 4, green);
        EXPECT_PIXEL_RECT_EQ(w / 2 + w / 8 + m, h / 2 + h / 8 + m, w / 4 - 4, h / 4 - 4, green);
    }
};

// gl_BaseVertex and gl_BaseInstance are translated to angle_BaseVertex and angle_BaseInstance
// internally.  Check that a user-defined angle_BaseVertex or angle_BaseInstance is permitted
TEST_P(DrawBaseVertexBaseInstanceTest_ES3, AllowsUserDefinedANGLEDrawID)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"));
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance_shader_builtin"));

    constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
in vec2 position;
uniform int angle_BaseVertex;
uniform int angle_BaseInstance;
out vec4 verified;

void main()
{
    // Expect gl_BaseVertex and gl_BaseInstance to be untouched when angle_BaseVertex and
    // angle_BaseInstance are not.
    verified = vec4(gl_BaseVertex == 2, gl_BaseInstance == 0,
                    angle_BaseVertex == 3, angle_BaseInstance == 5);
    gl_Position = vec4(position, 0, 1);
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in vec4 verified;
out vec4 color;

void main()
{
    color = verified;
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glUniform1i(glGetUniformLocation(program, "angle_BaseVertex"), 3);
    glUniform1i(glGetUniformLocation(program, "angle_BaseInstance"), 5);

    constexpr std::array<GLfloat, 5 * 2> kVertexData = {
        // Vertex 0, unused
        10000,
        10000,
        // Vertex 1, unused
        10000,
        10000,
        // Vertices 2, 3 and 4 define the triangle because base vertex is 2.
        -1,
        -1,
        3,
        -1,
        -1,
        3,
    };
    constexpr std::array<GLushort, 3> kIndexData = {
        0,
        1,
        2,
    };

    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(kVertexData), kVertexData.data(), GL_STATIC_DRAW);
    const GLint positionLoc = glGetAttribLocation(program, "position");
    glEnableVertexAttribArray(positionLoc);
    glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);

    GLBuffer indexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(kIndexData), kIndexData.data(), GL_STATIC_DRAW);
    ASSERT_GL_NO_ERROR();

    glDrawElementsInstancedBaseVertexBaseInstanceANGLE(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr,
                                                       1, 2, 0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    ASSERT_GL_NO_ERROR();
}

// Test glDrawArraysInstancedBaseInstance with a non-zero divisor.
TEST_P(DrawBaseVertexBaseInstanceTest_ES3, NonZeroDivisorBaseInstance)
{
    const bool hasEXT   = IsGLExtensionEnabled("GL_EXT_base_instance");
    const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance");
    ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE);

    const int w        = getWindowWidth();
    const int h        = getWindowHeight();
    const float left   = static_cast<float>(w / 8) / (w - 1) * 2.0 - 1.0;
    const float right  = static_cast<float>(w / 2 - w / 8) / (w - 1) * 2.0 - 1.0;
    const float top    = static_cast<float>(h / 8) / (h - 1) * 2.0 - 1.0;
    const float bottom = static_cast<float>(h / 2 - h / 8) / (h - 1) * 2.0 - 1.0;

    const GLfloat kPositions[] = {
        // Top left.  Instances 1 and 3 shift this square to the right.  Instances 2 and 3 shift it
        // down.
        left, top, left, bottom, right, top, right, bottom,
    };
    constexpr GLfloat kColors[] = {
        // 11 unused attributes, skipped by base instance
        // clang-format off
        0.1f, 0.2f,
        0.3f, 0.4f,
        0.5f, 0.6f,
        0.7f, 0.8f,
        0.9f, 0.95f,
        0.85f, 0.75f,
        0.65f, 0.55f,
        0.45f, 0.35f,
        0.25f, 0.15f,
        0.05f, 0.15f,
        0.25f, 0.35f,
        // Top
        1.0f, 0.0f,
        // Bottom
        0.2f, 0.6f,
        // clang-format on
    };
    runTestNonZeroDivisor(
        [&kPositions]() {
            glBufferData(GL_ARRAY_BUFFER, sizeof(kPositions), kPositions, GL_STATIC_DRAW);
        },
        [&kColors]() { glBufferData(GL_ARRAY_BUFFER, sizeof(kColors), kColors, GL_STATIC_DRAW); },
        [hasEXT]() {
            if (hasEXT)
            {
                glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLE_STRIP, 0, 4, 4, 11);
            }
            else
            {
                glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 4, 4, 11);
            }
        });
}

// Test glDrawElementsInstancedBaseVertexBaseInstance with a non-zero divisor.
TEST_P(DrawBaseVertexBaseInstanceTest_ES3, NonZeroDivisorBaseVertexBaseInstance)
{
    const bool hasEXT   = IsGLExtensionEnabled("GL_EXT_base_instance");
    const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance");
    ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE);

    const int w        = getWindowWidth();
    const int h        = getWindowHeight();
    const float left   = static_cast<float>(w / 8) / (w - 1) * 2.0 - 1.0;
    const float right  = static_cast<float>(w / 2 - w / 8) / (w - 1) * 2.0 - 1.0;
    const float top    = static_cast<float>(h / 8) / (h - 1) * 2.0 - 1.0;
    const float bottom = static_cast<float>(h / 2 - h / 8) / (h - 1) * 2.0 - 1.0;

    const GLfloat kPositions[] = {
        // 5 unused vertices, skipped by base vertex
        // clang-format off
        0.1f, 0.4f,
        0.3f, 0.6f,
        0.5f, 0.8f,
        0.7f, 0.6f,
        0.9f, 0.4f,
        // Top left.  Instances 1 and 3 shift this square to the right.  Instances 2 and 3 shift it
        // down.  The vertices are out of order, but are reordered by the index buffer.
        right, bottom, left, bottom, left, top, right, top,
        // clang-format on
    };
    constexpr GLfloat kColors[] = {
        // 7 unused attributes, skipped by base instance.
        // clang-format off
        0.1f, 0.2f,
        0.3f, 0.4f,
        0.5f, 0.6f,
        0.7f, 0.8f,
        0.9f, 0.95f,
        0.85f, 0.75f,
        0.65f, 0.55f,
        // Top
        1.0f, 0.0f,
        // Bottom
        0.2f, 0.6f,
        // clang-format on
    };
    constexpr GLint kIndices[] = {
        // The square
        2,
        1,
        3,
        0,
    };

    GLBuffer index;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(kIndices), kIndices, GL_STATIC_DRAW);

    runTestNonZeroDivisor(
        [&kPositions]() {
            glBufferData(GL_ARRAY_BUFFER, sizeof(kPositions), kPositions, GL_STATIC_DRAW);
        },
        [&kColors]() { glBufferData(GL_ARRAY_BUFFER, sizeof(kColors), kColors, GL_STATIC_DRAW); },
        [hasEXT]() {
            if (hasEXT)
            {
                glDrawElementsInstancedBaseVertexBaseInstanceEXT(GL_TRIANGLE_STRIP, 4,
                                                                 GL_UNSIGNED_INT, nullptr, 4, 5, 7);
            }
            else
            {
                glDrawElementsInstancedBaseVertexBaseInstanceANGLE(
                    GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, nullptr, 4, 5, 7);
            }
        });
}

// Test that gl_InstanceID does not include base instance.
TEST_P(DrawBaseVertexBaseInstanceTest_ES3, InstanceIDDoesNotIncludeBaseInstance)
{
    const bool hasEXT   = IsGLExtensionEnabled("GL_EXT_base_instance");
    const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance");
    ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE);

    // Draw 4 triangles to cover the left half of the screen, each triangle is the result of a
    // different instance ID.  If instance ID is larger than expected (because it includes base
    // instance), draw red to the right half of the screen.
    constexpr char kVS[] = R"(#version 300 es
precision mediump float;
out vec2 color;
void main()
{
    // Take the following points:
    //
    //    P0     P1
    //     +------+------+
    //     |    _-|      |
    //     |  _-  |      |
    //     |_-    |      |
    //  P2 +------+ P3   |
    //     |    _-|      |
    //     |  _-  |      |
    //     |_-    |      |
    //     +------+------+
    //    P4     P5
    //
    // Instances generate:
    //
    // * Instance 0: P0, P1, P2
    // * Instance 1: P1, P2, P3
    // * Instance 2: P2, P3, P4
    // * Instance 3: P3, P4, P5
    //
    // So effectively the output position is P[gl_VertexID + gl_InstanceID].
    //
    // To make sure there are no seams, the first vertex is always offset by -(0, epsilon) and the
    // third vertex is offset by (0, epsilon).
    vec2 P[6] = vec2[6](
        vec2(-1.0, -1.0),
        vec2( 0.0, -1.0),
        vec2(-1.0,  0.0),
        vec2( 0.0,  0.0),
        vec2(-1.0,  1.0),
        vec2( 0.0,  1.0)
    );

    if (gl_InstanceID < 4)
    {
        gl_Position = vec4(P[gl_VertexID + gl_InstanceID], 0, 1);
        if (gl_VertexID == 0)
            gl_Position.y -= 0.01;
        else if (gl_VertexID == 2)
            gl_Position.y += 0.01;
        color = vec2(0, 1);
    }
    else
    {
        switch (gl_VertexID) {
            case 0: gl_Position = vec4(0, -2, 0, 1); break;
            case 1: gl_Position = vec4(2, 0, 0, 1); break;
            case 2: gl_Position = vec4(0, 2, 0, 1); break;
        };
        color = vec2(1, 0);
    }
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in vec2 color;
out vec4 colorOut;
void main()
{
    colorOut = vec4(color, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    if (hasEXT)
    {
        glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLE_STRIP, 0, 3, 4, 15);
    }
    else
    {
        glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 3, 4, 15);
    }

    // Left half should be all green, right half should be all black.
    const int w = getWindowWidth();
    const int h = getWindowHeight();
    EXPECT_PIXEL_RECT_EQ(0, 0, w / 2 - 1, h, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(w / 2 + 1, 0, w - (w / 2 + 1), h, GLColor::black);
}

// Test base instance in combination with divisor
TEST_P(DrawBaseVertexBaseInstanceTest_ES3, BaseInstanceDivisorIndexing)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_base_instance") ||
                       !EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"));

    // Vertex shader: pass per-instance integer value through a flat varying.
    constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
in vec4 a_position;
in int a_instValue;
flat out int v_instValue;
flat out int v_instanceID;
flat out int v_baseInstance;
void main()
{
    v_instValue = a_instValue;
    v_instanceID = gl_InstanceID;
    v_baseInstance = gl_BaseInstance;
    gl_Position = a_position;
})";

    // Fragment shader: output the instanced value as an integer.
    constexpr char kFS[] = R"(#version 300 es
precision highp int;
flat in int v_instValue;
flat in int v_instanceID;
flat in int v_baseInstance;
out ivec4 o_color;
void main()
{
    o_color = ivec4(v_instValue, v_instanceID, v_baseInstance, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    GLint posLoc  = glGetAttribLocation(program, "a_position");
    GLint instLoc = glGetAttribLocation(program, "a_instValue");
    ASSERT_NE(-1, posLoc);
    ASSERT_NE(-1, instLoc);

    // Render to a GL_RGBA32I texture via FBO for exact integer readback.
    GLTexture intTex;
    glBindTexture(GL_TEXTURE_2D, intTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32I, getWindowWidth(), getWindowHeight(), 0,
                 GL_RGBA_INTEGER, GL_INT, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, intTex, 0);
    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));

    // Position: a full-viewport triangle strip (4 vertices).
    const float positions[] = {
        -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f,
        -1.0f, 1.0f,  0.0f, 1.0f, 1.0f, 1.0f,  0.0f, 1.0f,
    };
    GLBuffer posBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, posBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);
    glEnableVertexAttribArray(posLoc);
    glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, nullptr);

    // Per-instance attribute: 8 distinct integer values, divisor = 3.
    // Each value is a unique identifier for its buffer index.
    const GLint instValues[] = {0, 1, 2, 3, 4, 5, 6, 7};
    GLBuffer instBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, instBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(instValues), instValues, GL_STATIC_DRAW);
    glEnableVertexAttribArray(instLoc);
    glVertexAttribIPointer(instLoc, 1, GL_INT, 0, nullptr);
    glVertexAttribDivisor(instLoc, 3);
    ASSERT_GL_NO_ERROR();

    glViewport(0, 0, getWindowWidth(), getWindowHeight());
    struct Subcase
    {
        GLsizei instanceCount;
        GLuint baseInstance;
    } subcases[]{
        {.instanceCount = 1, .baseInstance = 0},
        {.instanceCount = 1, .baseInstance = 4},
        {.instanceCount = 1, .baseInstance = 7},
        {.instanceCount = 6, .baseInstance = 3},
    };

    for (auto const &subcase : subcases)
    {
        SCOPED_TRACE(testing::Message()
                     << "Subcase subcase{.instanceCount = " << subcase.instanceCount
                     << ", .baseInstance = " << subcase.baseInstance << "}");
        const GLint clearValue[] = {0, 0, 0, 0};
        glClearBufferiv(GL_COLOR, 0, clearValue);
        glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 4, subcase.instanceCount,
                                               subcase.baseInstance);
        ASSERT_GL_NO_ERROR();
        GLint lastInstanceAttrIndex = subcase.baseInstance + (subcase.instanceCount - 1) / 3;
        // SAFETY: Values chosen in the test cases, max value within 0..7.
        GLint lastInstValue  = ANGLE_UNSAFE_BUFFERS(instValues[lastInstanceAttrIndex]);
        GLint lastInstanceID = subcase.instanceCount - 1;
        // R == instValue (per-instance attribute), G == gl_InstanceID, B == gl_BaseInstance.
        GLColor32I expected(lastInstValue, lastInstanceID, static_cast<GLint>(subcase.baseInstance),
                            1);
        EXPECT_PIXEL_32I_COLOR(0, 0, expected);
    }
}

// Test base instance with divisor >= 256
TEST_P(DrawBaseVertexBaseInstanceTest_ES3, BaseInstanceLargeDivisor)
{
    const bool hasEXT   = IsGLExtensionEnabled("GL_EXT_base_instance");
    const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance");
    ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE);

    constexpr uint32_t kBaseInstance = 3;
    constexpr uint32_t kRegionCount  = 4;
    constexpr uint32_t kDivisor      = 256;

    // Create an attribute that will be instanced.
    const std::array<GLColor, kBaseInstance + kRegionCount> kData = {
        GLColor(10, 20, 30, 40), GLColor(11, 21, 31, 41), GLColor(12, 22, 32, 42), GLColor::red,
        GLColor::green,          GLColor::blue,           GLColor::yellow,
    };

    GLBuffer instancedData;
    glBindBuffer(GL_ARRAY_BUFFER, instancedData);
    glBufferData(GL_ARRAY_BUFFER, sizeof(kData), kData.data(), GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_UNSIGNED_BYTE, true, 4, nullptr);
    glVertexAttribDivisor(0, kDivisor);

    // The divisor of the data should be high (256) to trigger a specific path in the Vulkan
    // backend.  The test renders 256*4 instances with a base instance of 3.  To simplify
    // validation, the output is divided into four corners, where every 256 instances overdraw the
    // same corner.
    //
    //                      P0
    //                      +
    //                    / | \
    //                  /   |   \
    //                /     |     \
    //              /       |       \
    //             +--------+--------+
    //           / |        |        | \
    //         /   |        |        |   \
    //       /     |        | O      |     \
    //   P1 +------+--------+--------+------+ P3
    //       \     |        |        |     /
    //         \   |        |        |   /
    //           \ |        |        | /
    //             +--------+--------+
    //              \       |       /
    //                \     |     /
    //                  \   |   /
    //                    \ | /
    //                      +
    //                     P2
    //
    // Instances generate:
    //
    // * Instances [0 * divisor, 1 * divisor): O, P0, P1
    // * Instances [1 * divisor, 2 * divisor): O, P1, P2
    // * Instances [2 * divisor, 3 * divisor): O, P2, P3
    // * Instances [3 * divisor, 4 * divisor): O, P3, P0
    //
    // So the vertices are always as follows, given i = gl_InstanceID / divisor:
    //
    // * gl_VertexID 0: P[0]
    // * gl_VertexID 1: P[i]
    // * gl_VertexID 2: P[(i + 1) % 4]
    //
    constexpr char kVS[] = R"(#version 300 es
precision mediump float;
in vec4 instanceData;
out vec4 color;
void main()
{
    vec2 P[4] = vec2[4](
        vec2( 0.0, -2.0),
        vec2(-2.0,  0.0),
        vec2( 0.0,  2.0),
        vec2( 2.0,  0.0)
    );

    int index = gl_InstanceID / 256;
    switch (gl_VertexID % 3)
    {
    case 0:
        gl_Position = vec4(0, 0, 0, 1);
        break;
    case 1:
        gl_Position = vec4(P[index], 0, 1);
        break;
    default:
        gl_Position = vec4(P[(index + 1) % 4], 0, 1);
        break;
    }

    color = instanceData;
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in vec4 color;
out vec4 colorOut;
void main()
{
    colorOut = color;
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    if (hasEXT)
    {
        glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLE_STRIP, 0, 3, kDivisor * kRegionCount,
                                             kBaseInstance);
    }
    else
    {
        glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 3, kDivisor * kRegionCount,
                                               kBaseInstance);
    }

    const int w = getWindowWidth();
    const int h = getWindowHeight();
    EXPECT_PIXEL_RECT_EQ(0, 0, w / 2 - 1, h / 2 - 1, kData[kBaseInstance]);
    EXPECT_PIXEL_RECT_EQ(0, h / 2 + 1, w / 2 - 1, h - (h / 2 + 1), kData[kBaseInstance + 1]);
    EXPECT_PIXEL_RECT_EQ(w / 2 + 1, h / 2 + 1, w - (w / 2 + 1), h - (h / 2 + 1),
                         kData[kBaseInstance + 2]);
    EXPECT_PIXEL_RECT_EQ(w / 2 + 1, 0, w - (w / 2 + 1), h / 2, kData[kBaseInstance + 3]);
}

// Test base instance with divisor < 256 where vertex attribute data is sourced from client memory.
TEST_P(DrawBaseVertexBaseInstanceTest_ES3, BaseInstanceSmallDivisorClientMemory)
{
    const bool hasEXT   = IsGLExtensionEnabled("GL_EXT_base_instance");
    const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance");
    ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE);

    constexpr uint32_t kBaseInstance = 3;
    constexpr uint32_t kRegionCount  = 4;
    constexpr uint32_t kDivisor      = 11;

    // Create an attribute that will be instanced.
    const std::array<GLColor, kBaseInstance + kRegionCount> kData = {
        GLColor(10, 20, 30, 40), GLColor(11, 21, 31, 41), GLColor(12, 22, 32, 42), GLColor::red,
        GLColor::green,          GLColor::blue,           GLColor::yellow,
    };

    // Data needs to be sourced from a client buffer to trigger a specific path in the Vulkan
    // backend.
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_UNSIGNED_BYTE, true, 4, kData.data());
    glVertexAttribDivisor(0, kDivisor);

    // The divisor of the data should be low (<256) to trigger a specific path in the Vulkan
    // backend.  The test renders 11*4 instances with a base instance of 3.  To simplify
    // validation, the output is divided as such:
    //
    //                      P0
    //                      +
    //                    / | \
    //                  /   |   \
    //                /     |     \
    //              /       |       \
    //             +--------+--------+
    //           / |        |        | \
    //         /   |        |        |   \
    //       /     |        | O      |     \
    //   P1 +------+--------+--------+------+ P3
    //       \     |        |        |     /
    //         \   |        |        |   /
    //           \ |        |        | /
    //             +--------+--------+
    //              \       |       /
    //                \     |     /
    //                  \   |   /
    //                    \ | /
    //                      +
    //                     P2
    //
    // Instances generate:
    //
    // * Instances [0 * divisor, 1 * divisor): O, P0, P1
    // * Instances [1 * divisor, 2 * divisor): O, P1, P2
    // * Instances [2 * divisor, 3 * divisor): O, P2, P3
    // * Instances [3 * divisor, 4 * divisor): O, P3, P0
    //
    // So the vertices are always as follows, given i = gl_InstanceID / divisor:
    //
    // * gl_VertexID 0: P[0]
    // * gl_VertexID 1: P[i]
    // * gl_VertexID 2: P[(i + 1) % 4]
    //
    constexpr char kVS[] = R"(#version 300 es
precision mediump float;
in vec4 instanceData;
out vec4 color;
void main()
{
    vec2 P[4] = vec2[4](
        vec2( 0.0, -2.0),
        vec2(-2.0,  0.0),
        vec2( 0.0,  2.0),
        vec2( 2.0,  0.0)
    );

    int index = gl_InstanceID / 11;
    switch (gl_VertexID % 3)
    {
    case 0:
        gl_Position = vec4(0, 0, 0, 1);
        break;
    case 1:
        gl_Position = vec4(P[index], 0, 1);
        break;
    default:
        gl_Position = vec4(P[(index + 1) % 4], 0, 1);
        break;
    }

    color = instanceData;
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in vec4 color;
out vec4 colorOut;
void main()
{
    colorOut = color;
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    if (hasEXT)
    {
        glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLE_STRIP, 0, 3, kDivisor * kRegionCount,
                                             kBaseInstance);
    }
    else
    {
        glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 3, kDivisor * kRegionCount,
                                               kBaseInstance);
    }

    const int w = getWindowWidth();
    const int h = getWindowHeight();
    EXPECT_PIXEL_RECT_EQ(0, 0, w / 2 - 1, h / 2 - 1, kData[kBaseInstance]);
    EXPECT_PIXEL_RECT_EQ(0, h / 2 + 1, w / 2 - 1, h - (h / 2 + 1), kData[kBaseInstance + 1]);
    EXPECT_PIXEL_RECT_EQ(w / 2 + 1, h / 2 + 1, w - (w / 2 + 1), h - (h / 2 + 1),
                         kData[kBaseInstance + 2]);
    EXPECT_PIXEL_RECT_EQ(w / 2 + 1, 0, w - (w / 2 + 1), h / 2, kData[kBaseInstance + 3]);
}

// Test that drawing with baseInstance and divisor where (instances mod divisor != 0) and
// (baseInstance mod divisor != 0) works.
TEST_P(DrawBaseVertexBaseInstanceTest_ES3, DivisorDoesNotDivideBaseInstance)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"));

    // The test divides the screen horizontally into 4 columns/strips for the first 4 instances
    // Draw within viewport only for instances 0, 1, 2, 3, other instances are discarded.
    // Each of the 4 instances is drawn as a vertical strip in the viewport.
    constexpr char kVS[] = R"(#version 300 es
in vec4 a_position;
in vec4 a_color;
out vec4 v_color;
void main()
{
    if (gl_InstanceID >= 4)
    {
        gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
    }
    else
    {
        gl_Position = a_position;
        gl_Position.x = (float(gl_InstanceID) * 0.5) - 0.75 + a_position.x * 0.25;
    }
    v_color = a_color;
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in vec4 v_color;
out vec4 o_color;
void main()
{
    o_color = v_color;
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLint posLoc = glGetAttribLocation(program, "a_position");
    GLint colLoc = glGetAttribLocation(program, "a_color");
    ASSERT_NE(-1, posLoc);
    ASSERT_NE(-1, colLoc);

    const float positions[] = {
        -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f,
        -1.0f, 1.0f,  0.0f, 1.0f, 1.0f, 1.0f,  0.0f, 1.0f,
    };
    GLBuffer posBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, posBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);
    glEnableVertexAttribArray(posLoc);
    glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, nullptr);

    // Use GL_FLOAT 4-components (16 bytes per element).
    // Both kInstances and kBaseInstance are chosen to be odd numbers so
    // they are not multiples of the divisor
    // kInstances is kept small to prevent tsan timeouts on slow software renderers (SwiftShader).
    // kBaseInstance is chosen to be large to force the D3D11 backend to resize the buffer,
    constexpr GLsizei kInstances   = 5;
    constexpr GLuint kBaseInstance = 131071;
    constexpr GLuint kDivisor      = 2;
    constexpr size_t kCopyCount =
        kBaseInstance + rx::UnsignedCeilDivide(static_cast<unsigned int>(kInstances), kDivisor);
    constexpr size_t kComponents = 4;

    std::vector<float> colors(kCopyCount * kComponents);
    for (size_t i = 0; i < kCopyCount; ++i)
    {
        colors[i * kComponents + 0] = 1.0f;  // R
        colors[i * kComponents + 1] = 1.0f;  // G
        colors[i * kComponents + 2] = 1.0f;  // B
        colors[i * kComponents + 3] = 1.0f;  // A
    }
    // Index kBaseInstance: Yellow
    colors[kBaseInstance * kComponents + 0] = 1.0f;
    colors[kBaseInstance * kComponents + 1] = 1.0f;
    colors[kBaseInstance * kComponents + 2] = 0.0f;
    colors[kBaseInstance * kComponents + 3] = 1.0f;
    // Index kBaseInstance + 1: Cyan
    colors[(kBaseInstance + 1) * kComponents + 0] = 0.0f;
    colors[(kBaseInstance + 1) * kComponents + 1] = 1.0f;
    colors[(kBaseInstance + 1) * kComponents + 2] = 1.0f;
    colors[(kBaseInstance + 1) * kComponents + 3] = 1.0f;

    GLBuffer colBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, colBuffer);
    glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(float), colors.data(), GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(colLoc);
    glVertexAttribPointer(colLoc, kComponents, GL_FLOAT, GL_FALSE, 0, nullptr);
    glVertexAttribDivisor(colLoc, kDivisor);

    const GLushort indices[] = {0, 1, 2, 1, 2, 3};
    GLBuffer indexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glDrawElementsInstancedBaseVertexBaseInstanceANGLE(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr,
                                                       kInstances, 0, kBaseInstance);
    ASSERT_GL_NO_ERROR();

    const int w        = getWindowWidth();
    const int h        = getWindowHeight();
    const int y        = h / 2;
    const int colWidth = w / 8;

    // Verify each of the 4 columns
    // Column 0 (Instance 0): index = 3 + 0/2 = 3 (Yellow)
    EXPECT_PIXEL_COLOR_EQ(colWidth, y, GLColor::yellow);
    // Column 1 (Instance 1): index = 3 + 1/2 = 3 (Yellow)
    EXPECT_PIXEL_COLOR_EQ(3 * colWidth, y, GLColor::yellow);
    // Column 2 (Instance 2): index = 3 + 2/2 = 4 (Cyan)
    EXPECT_PIXEL_COLOR_EQ(5 * colWidth, y, GLColor::cyan);
    // Column 3 (Instance 3): index = 3 + 3/2 = 4 (Cyan)
    EXPECT_PIXEL_COLOR_EQ(7 * colWidth, y, GLColor::cyan);
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrawBaseVertexBaseInstanceTest);

#define ANGLE_ALL_BASEVERTEXBASEINTANCE_TEST_PLATFORMS_ES3                                 \
    ES3_D3D11().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),                  \
        ES3_OPENGL().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),             \
        ES3_OPENGLES().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),           \
        ES3_VULKAN().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),             \
        ES3_VULKAN().disable(Feature::SupportsVertexInputDynamicState),                    \
        ES3_VULKAN_SWIFTSHADER().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions), \
        ES3_METAL().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions)

ANGLE_INSTANTIATE_TEST_COMBINE_3(
    DrawBaseVertexBaseInstanceTest,
    PrintToStringParamName(),
    testing::Values(BaseVertexOption::NoBaseVertex, BaseVertexOption::UseBaseVertex),
    testing::Values(BaseInstanceOption::NoBaseInstance, BaseInstanceOption::UseBaseInstance),
    testing::Values(BufferDataUsageOption::StaticDraw, BufferDataUsageOption::DynamicDraw),
    ANGLE_ALL_BASEVERTEXBASEINTANCE_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_3(
    DrawBaseInstanceTest,
    PrintToStringParamName(),
    testing::Values(BaseVertexOption::NoBaseVertex, BaseVertexOption::UseBaseVertex),
    testing::Values(BaseInstanceOption::NoBaseInstance, BaseInstanceOption::UseBaseInstance),
    testing::Values(BufferDataUsageOption::StaticDraw, BufferDataUsageOption::DynamicDraw),
    ANGLE_ALL_BASEVERTEXBASEINTANCE_TEST_PLATFORMS_ES3);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrawBaseVertexBaseInstanceTest_ES3);
ANGLE_INSTANTIATE_TEST(DrawBaseVertexBaseInstanceTest_ES3,
                       ANGLE_ALL_BASEVERTEXBASEINTANCE_TEST_PLATFORMS_ES3);

}  // namespace
