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

#include "common/unsafe_buffers.h"
#include "test_utils/ANGLETest.h"

#include "test_utils/gl_raii.h"

using namespace angle;

class LineLoopTest : public ANGLETest<>
{
  protected:
    LineLoopTest()
    {
        setWindowWidth(256);
        setWindowHeight(256);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    void testSetUp() override
    {
        mProgram = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
        if (mProgram == 0)
        {
            FAIL() << "shader compilation failed.";
        }

        mPositionLocation = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib());
        mColorLocation    = glGetUniformLocation(mProgram, essl1_shaders::ColorUniform());

        glBlendFunc(GL_ONE, GL_ONE);
        glEnable(GL_BLEND);
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        ASSERT_GL_NO_ERROR();
    }

    void testTearDown() override { glDeleteProgram(mProgram); }

    void checkPixels()
    {
        std::vector<GLubyte> pixels(getWindowWidth() * getWindowHeight() * 4);
        glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                     &pixels[0]);

        ASSERT_GL_NO_ERROR();

        for (int y = 0; y < getWindowHeight(); y++)
        {
            for (int x = 0; x < getWindowWidth(); x++)
            {
                const GLubyte *pixel =
                    ANGLE_UNSAFE_TODO(&pixels[0] + ((y * getWindowWidth() + x) * 4));

                EXPECT_EQ(pixel[0], 0) << "Failed at " << x << ", " << y << std::endl;
                ANGLE_UNSAFE_TODO(EXPECT_EQ(pixel[1], pixel[2]))
                    << "Failed at " << x << ", " << y << std::endl;
                ANGLE_UNSAFE_TODO(ASSERT_EQ(pixel[3], 255))
                    << "Failed at " << x << ", " << y << std::endl;
            }
        }
    }

    void preTestUpdateBuffer(GLuint framebuffer, GLuint texture, GLuint buffer, GLsizei size)
    {
        GLsizei uboSize = std::max(size, 16);
        const std::vector<uint32_t> initialData((uboSize + 3) / 4, 0x1234567u);

        glBindTexture(GL_TEXTURE_2D, texture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);

        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture,
                               0);

        glBindBuffer(GL_UNIFORM_BUFFER, buffer);
        glBufferData(GL_UNIFORM_BUFFER, uboSize, initialData.data(), GL_DYNAMIC_DRAW);
        glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer);

        constexpr char kVerifyUBO[] = R"(#version 300 es
precision mediump float;
uniform block {
    uint data;
} ubo;
out vec4 colorOut;
void main()
{
    if (ubo.data == 0x1234567u)
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

        ANGLE_GL_PROGRAM(verifyUbo, essl3_shaders::vs::Simple(), kVerifyUBO);

        glDisable(GL_BLEND);
        drawQuad(verifyUbo, essl3_shaders::PositionAttrib(), 0.5);

        EXPECT_GL_NO_ERROR();

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    void runTestBlend(GLenum indexType, GLuint indexBuffer, const void *indexPtr)
    {
        glClear(GL_COLOR_BUFFER_BIT);

        static const GLfloat loopPositions[] = {0.0f,  0.0f, 0.0f, 0.0f, 0.0f, 0.0f,  0.0f,
                                                0.0f,  0.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f,
                                                -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f};

        static const GLfloat stripPositions[] = {-0.5f, -0.5f, -0.5f, 0.5f,
                                                 0.5f,  0.5f,  0.5f,  -0.5f};
        static const GLushort stripIndices[]  = {1, 0, 3, 2, 1};

        glEnable(GL_BLEND);

        glUseProgram(mProgram);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glEnableVertexAttribArray(mPositionLocation);
        glVertexAttribPointer(mPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, loopPositions);
        glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
        glDrawElements(GL_LINE_LOOP, 4, indexType, indexPtr);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glVertexAttribPointer(mPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, stripPositions);
        glUniform4f(mColorLocation, 0, 1, 0, 1);
        glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_SHORT, stripIndices);

        checkPixels();
    }

    void runTestNoBlend(GLenum indexType, GLuint indexBuffer, const void *indexPtr)
    {
        glClear(GL_COLOR_BUFFER_BIT);

        static const GLfloat loopPositions[] = {0.0f,  0.0f, 0.0f, 0.0f, 0.0f, 0.0f,  0.0f,
                                                0.0f,  0.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f,
                                                -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f};

        static const GLfloat stripPositions[] = {-0.5f, -0.5f, -0.5f, 0.5f,
                                                 0.5f,  0.5f,  0.5f,  -0.5f};
        static const GLushort stripIndices[]  = {1, 0, 3, 2, 1};

        std::vector<GLColor> expectedPixels(getWindowWidth() * getWindowHeight());
        std::vector<GLColor> renderedPixels(getWindowWidth() * getWindowHeight());

        glUseProgram(mProgram);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
        glEnableVertexAttribArray(mPositionLocation);
        glVertexAttribPointer(mPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, loopPositions);
        glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
        glDrawElements(GL_LINE_LOOP, 4, indexType, indexPtr);

        glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                     renderedPixels.data());

        glClear(GL_COLOR_BUFFER_BIT);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glVertexAttribPointer(mPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, stripPositions);
        glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
        glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_SHORT, stripIndices);

        glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                     expectedPixels.data());

        for (int y = 0; y < getWindowHeight(); ++y)
        {
            for (int x = 0; x < getWindowWidth(); ++x)
            {
                int idx = y * getWindowWidth() + x;
                EXPECT_EQ(expectedPixels[idx], renderedPixels[idx])
                    << "Expected pixel at " << x << ", " << y << " to be " << expectedPixels[idx]
                    << std::endl;
            }
        }
    }

    GLuint mProgram;
    GLint mPositionLocation;
    GLint mColorLocation;
};

// Line loop test that draws a loop and a strip, blends the colors, and checks they're correct. No
// index buffer is set.
TEST_P(LineLoopTest, LineLoopUByteIndicesBlend)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    // On Win7, the D3D SDK Layers emits a false warning for these tests.
    // This doesn't occur on Windows 10 (Version 1511) though.
    ignoreD3D11SDKLayersWarnings();

    static const GLubyte indices[] = {0, 7, 6, 9, 8, 0};
    runTestBlend(GL_UNSIGNED_BYTE, 0, ANGLE_UNSAFE_TODO(indices + 1));
}

// Line loop test that draws a loop and a strip, blends the colors, and checks they're correct. No
// index buffer is set.
TEST_P(LineLoopTest, LineLoopUShortIndicesBlend)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLushort indices[] = {0, 7, 6, 9, 8, 0};
    runTestBlend(GL_UNSIGNED_SHORT, 0, ANGLE_UNSAFE_TODO(indices + 1));
}

// Line loop test that draws a loop and a strip, blends the colors, and checks they're correct. No
// index buffer is set.
TEST_P(LineLoopTest, LineLoopUIntIndicesBlend)
{
    if (!IsGLExtensionEnabled("GL_OES_element_index_uint"))
    {
        return;
    }

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLuint indices[] = {0, 7, 6, 9, 8, 0};
    runTestBlend(GL_UNSIGNED_INT, 0, ANGLE_UNSAFE_TODO(indices + 1));
}

// Line loop test that draws a loop and a strip, blends the colors, and checks they're correct.
// Index buffer is set.
TEST_P(LineLoopTest, LineLoopUByteIndexBufferBlend)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLubyte indices[] = {0, 7, 6, 9, 8, 0};

    GLBuffer buf;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    runTestBlend(GL_UNSIGNED_BYTE, buf, reinterpret_cast<const void *>(sizeof(GLubyte)));
}

// Line loop test that draws a loop and a strip, blends the colors, and checks they're correct.
// Index buffer is set.
TEST_P(LineLoopTest, LineLoopUShortIndexBufferBlend)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLushort indices[] = {0, 7, 6, 9, 8, 0};

    GLBuffer buf;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    runTestBlend(GL_UNSIGNED_SHORT, buf, reinterpret_cast<const void *>(sizeof(GLushort)));
}

// Line loop test that draws a loop and a strip, blends the colors, and checks they're correct.
// Index buffer is set.
TEST_P(LineLoopTest, LineLoopUIntIndexBufferBlend)
{
    if (!IsGLExtensionEnabled("GL_OES_element_index_uint"))
    {
        return;
    }

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLuint indices[] = {0, 7, 6, 9, 8, 0};

    GLBuffer buf;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    runTestBlend(GL_UNSIGNED_INT, buf, reinterpret_cast<const void *>(sizeof(GLuint)));
}

// Line loop test that draws a loop, reads it, then a strip, reads it, and confirms the pixels are
// the same. No index buffer is set.
TEST_P(LineLoopTest, LineLoopUByteIndicesNoBlend)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    // On Win7, the D3D SDK Layers emits a false warning for these tests.
    // This doesn't occur on Windows 10 (Version 1511) though.
    ignoreD3D11SDKLayersWarnings();

    static const GLubyte indices[] = {0, 7, 6, 9, 8, 0};
    runTestNoBlend(GL_UNSIGNED_BYTE, 0, ANGLE_UNSAFE_TODO(indices + 1));
}

// Line loop test that draws a loop, reads it, then a strip, reads it, and confirms the pixels are
// the same. No index buffer is set.
TEST_P(LineLoopTest, LineLoopUShortIndicesNoBlend)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLushort indices[] = {0, 7, 6, 9, 8, 0};
    runTestNoBlend(GL_UNSIGNED_SHORT, 0, ANGLE_UNSAFE_TODO(indices + 1));
}

// Line loop test that draws a loop, reads it, then a strip, reads it, and confirms the pixels are
// the same. No index buffer is set.
TEST_P(LineLoopTest, LineLoopUIntIndicesNoBlend)
{
    if (!IsGLExtensionEnabled("GL_OES_element_index_uint"))
    {
        return;
    }

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLuint indices[] = {0, 7, 6, 9, 8, 0};
    runTestNoBlend(GL_UNSIGNED_INT, 0, ANGLE_UNSAFE_TODO(indices + 1));
}

// Line loop test that draws a loop, reads it, then a strip, reads it, and confirms the pixels are
// the same. Index buffer is set.
TEST_P(LineLoopTest, LineLoopUByteIndexBufferNoBlend)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLubyte indices[] = {0, 7, 6, 9, 8, 0};

    GLBuffer buf;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    runTestNoBlend(GL_UNSIGNED_BYTE, buf, reinterpret_cast<const void *>(sizeof(GLubyte)));
}

// Line loop test that draws a loop, reads it, then a strip, reads it, and confirms the pixels are
// the same. Index buffer is set.
TEST_P(LineLoopTest, LineLoopUShortIndexBufferNoBlend)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLushort indices[] = {0, 7, 6, 9, 8, 0};

    GLBuffer buf;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    runTestNoBlend(GL_UNSIGNED_SHORT, buf, reinterpret_cast<const void *>(sizeof(GLushort)));
}

// Line loop test that draws a loop, reads it, then a strip, reads it, and confirms the pixels are
// the same. Index buffer is set.
TEST_P(LineLoopTest, LineLoopUIntIndexBufferNoBlend)
{
    if (!IsGLExtensionEnabled("GL_OES_element_index_uint"))
    {
        return;
    }

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLuint indices[] = {0, 7, 6, 9, 8, 0};

    GLBuffer buf;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    runTestNoBlend(GL_UNSIGNED_INT, buf, reinterpret_cast<const void *>(sizeof(GLuint)));
}

// Test that drawing elements between line loop arrays using the same array buffer does not result
// in incorrect rendering.
TEST_P(LineLoopTest, DrawTriangleElementsBetweenArrays)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLfloat positions[] = {-0.5f, -0.5f, -0.5f, 0.5f,  0.5f,  0.5f,
                                        0.5f,  -0.5f, -0.5f, -0.5f, -0.1f, 0.1f,
                                        -0.1f, -0.1f, 0.1f,  -0.1f, 0.1f,  0.1f};
    static const GLubyte indices[]   = {5, 6, 7, 5, 7, 8};

    GLBuffer arrayBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

    GLBuffer indexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    glEnableVertexAttribArray(mPositionLocation);
    glVertexAttribPointer(mPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnable(GL_BLEND);

    glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawArrays(GL_LINE_LOOP, 0, 4);

    glUniform4f(mColorLocation, 0.0f, 0.0f, 0.0f, 1.0f);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, nullptr);

    glUniform4f(mColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    glDrawArrays(GL_LINE_LOOP, 0, 4);

    checkPixels();
}

// Tests drawing elements with line loop arrays and drawing elements with line strip arrays and
// confirms the draws are the same.
TEST_P(LineLoopTest, SimpleDrawArrays)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLfloat positions[] = {-0.5f, -0.5f, -0.5f, 0.5f,  0.5f,  0.5f,
                                        0.5f,  -0.5f, -0.5f, -0.5f, -0.1f, 0.1f,
                                        -0.1f, -0.1f, 0.1f,  -0.1f, 0.1f,  0.1f};

    std::vector<GLColor> expectedPixels(getWindowWidth() * getWindowHeight());
    std::vector<GLColor> renderedPixels(getWindowWidth() * getWindowHeight());

    GLBuffer arrayBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    glEnableVertexAttribArray(mPositionLocation);
    glVertexAttribPointer(mPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, nullptr);

    glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawArrays(GL_LINE_STRIP, 0, 5);
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 expectedPixels.data());

    glClear(GL_COLOR_BUFFER_BIT);

    glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawArrays(GL_LINE_LOOP, 0, 4);
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 renderedPixels.data());

    for (int y = 0; y < getWindowHeight(); ++y)
    {
        for (int x = 0; x < getWindowWidth(); ++x)
        {
            int idx = y * getWindowWidth() + x;
            EXPECT_EQ(expectedPixels[idx], renderedPixels[idx])
                << "Expected pixel at " << x << ", " << y << " to be " << expectedPixels[idx]
                << std::endl;
        }
    }
}

class LineLoopTestES3 : public LineLoopTest
{
  protected:
    void runTestInstanced(std::function<void(void)> definePositionBuffer,
                          std::function<void(void)> defineColorBuffer,
                          std::function<void(void)> draw)
    {
        // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
        ignoreD3D11SDKLayersWarnings();

        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);

        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 lines to account for precision issues.  This is the margin
        // from the middle of the lines.
        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);

        // Check the lines of the squares, leaving a margin for precision errors.
        std::vector<GLColor> result(w * h);
        glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, result.data());

        auto checkHorizontalLine = [&result, w](int x, int y, int width, GLColor expect) {
            for (int c = 0; c < width; ++c)
            {
                if (result[y * w + x + c] != expect)
                {
                    return false;
                }
            }
            return true;
        };
        auto checkVerticalLine = [&result, w](int x, int y, int height, GLColor expect) {
            for (int r = 0; r < height; ++r)
            {
                if (result[(y + r) * w + x] != expect)
                {
                    return false;
                }
            }
            return true;
        };

        for (int x : {w / 8, w / 2 - w / 8, w / 2 + w / 8, w - w / 8})
        {
            const int y      = h / 8 + m;
            const int length = h / 4 - m * 2;
            // The line may not exactly land on |x|, check around it too, one of them should be
            // non-black.
            EXPECT_TRUE(checkVerticalLine(x - 1, y, length, GLColor::red) ^
                        checkVerticalLine(x, y, length, GLColor::red) ^
                        checkVerticalLine(x + 1, y, length, GLColor::red));
            EXPECT_TRUE(checkVerticalLine(x - 1, h / 2 + y, length, green) ^
                        checkVerticalLine(x, h / 2 + y, length, green) ^
                        checkVerticalLine(x + 1, h / 2 + y, length, green));
        }
        for (int y : {h / 8, h / 2 - h / 8, h / 2 + h / 8, h - h / 8})
        {
            const int x          = w / 8 + m;
            const int length     = w / 4 - m * 2;
            const GLColor expect = y < h / 2 ? GLColor::red : green;
            EXPECT_PIXEL_RECT_EQ(x, y, length, 1, expect);
            EXPECT_PIXEL_RECT_EQ(w / 2 + x, y, length, 1, expect);

            EXPECT_TRUE(checkHorizontalLine(x, y - 1, length, expect) ^
                        checkHorizontalLine(x, y, length, expect) ^
                        checkHorizontalLine(x, y + 1, length, expect));
            EXPECT_TRUE(checkHorizontalLine(w / 2 + x, y - 1, length, expect) ^
                        checkHorizontalLine(w / 2 + x, y, length, expect) ^
                        checkHorizontalLine(w / 2 + x, y + 1, length, expect));
        }

        ASSERT_GL_NO_ERROR();
    }
};

// Test that uploading data to buffer that's in use then using it for line loop elements works.
TEST_P(LineLoopTestES3, UseAsUBOThenUpdateThenLineLoopUByteIndexBuffer)
{
    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLubyte indices[] = {0, 7, 6, 9, 8, 0};

    GLFramebuffer framebuffer;
    GLTexture texture;

    GLBuffer buf;

    preTestUpdateBuffer(framebuffer, texture, buf, sizeof(indices));

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(indices), indices);

    runTestBlend(GL_UNSIGNED_BYTE, buf, reinterpret_cast<const void *>(sizeof(GLubyte)));

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test glDrawArraysInstanced with a line loop and a non-zero divisor.
TEST_P(LineLoopTestES3, LineLoopDrawArraysInstanced)
{
    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, bottom, right, top,
    };
    constexpr GLfloat kColors[] = {
        // Top
        1.0f,
        0.0f,
        // Bottom
        0.2f,
        0.6f,
    };
    runTestInstanced(
        [&kPositions]() {
            glBufferData(GL_ARRAY_BUFFER, sizeof(kPositions), kPositions, GL_STATIC_DRAW);
        },
        [&kColors]() { glBufferData(GL_ARRAY_BUFFER, sizeof(kColors), kColors, GL_STATIC_DRAW); },
        []() { glDrawArraysInstanced(GL_LINE_LOOP, 0, 4, 4); });
}

// Test glDrawArraysInstancedBaseInstance with a line loop and a non-zero divisor.
TEST_P(LineLoopTestES3, LineLoopDrawArraysInstancedBaseInstance)
{
    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, bottom, right, top,
    };
    constexpr GLfloat kColors[] = {
        // 5 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,
        // Top
        1.0f, 0.0f,
        // Bottom
        0.2f, 0.6f,
        // clang-format on
    };
    runTestInstanced(
        [&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_LINE_LOOP, 0, 4, 4, 5);
            }
            else
            {
                glDrawArraysInstancedBaseInstanceANGLE(GL_LINE_LOOP, 0, 4, 4, 5);
            }
        });
}

// Test glDrawElementsInstancedBaseVertexBaseInstance with a line loop and a non-zero divisor.
TEST_P(LineLoopTestES3, LineLoopDrawElementsInstancedBaseVertexBaseInstance)
{
    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 2nd and 3rd vertices are swapped, but are reordered by the index buffer.
        left, top, right, bottom, left, bottom, right, top,
        // clang-format on
    };
    constexpr GLfloat kColors[] = {
        // 9 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,
        // 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);

    runTestInstanced(
        [&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_LINE_LOOP, 4, GL_UNSIGNED_INT,
                                                                 nullptr, 4, 5, 9);
            }
            else
            {
                glDrawElementsInstancedBaseVertexBaseInstanceANGLE(GL_LINE_LOOP, 4, GL_UNSIGNED_INT,
                                                                   nullptr, 4, 5, 9);
            }
        });
}

// Test that uploading data to buffer that's in use then using it for line loop elements works.
TEST_P(LineLoopTestES3, UseAsUBOThenUpdateThenLineLoopUShortIndexBuffer)
{
    // http://anglebug.com/42264370
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsQualcomm());

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLushort indices[] = {0, 7, 6, 9, 8, 0};

    GLFramebuffer framebuffer;
    GLTexture texture;

    GLBuffer buf;

    preTestUpdateBuffer(framebuffer, texture, buf, sizeof(indices));

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(indices), indices);

    runTestBlend(GL_UNSIGNED_SHORT, buf, reinterpret_cast<const void *>(sizeof(GLushort)));

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that uploading data to buffer that's in use then using it for line loop elements works.
TEST_P(LineLoopTestES3, UseAsUBOThenUpdateThenLineLoopUIntIndexBuffer)
{
    if (!IsGLExtensionEnabled("GL_OES_element_index_uint"))
    {
        return;
    }

    // http://anglebug.com/42264370
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsQualcomm());

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    static const GLuint indices[] = {0, 7, 6, 9, 8, 0};

    GLFramebuffer framebuffer;
    GLTexture texture;

    GLBuffer buf;

    preTestUpdateBuffer(framebuffer, texture, buf, sizeof(indices));

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(indices), indices);

    runTestBlend(GL_UNSIGNED_INT, buf, reinterpret_cast<const void *>(sizeof(GLuint)));

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test an edge case in the D3D11 backend where a large index array of GL_UNSIGNED_BYTE
// drawn as a GL_LINE_LOOP is widened to 32-bits internally and overflows.
// Disabled because it slow and triggers an internal error.
TEST_P(LineLoopTestES3, DISABLED_LargeLoopUnsignedByte)
{
    size_t count = 805306368;

    glLinkProgram(mProgram);
    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    GLBuffer ebo;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, count, nullptr, GL_STATIC_DRAW);
    ASSERT_GL_NO_ERROR();

    glDrawElements(GL_LINE_LOOP, count, GL_UNSIGNED_BYTE, 0);
    EXPECT_GL_ERROR(GL_OUT_OF_MEMORY);
}

// Test an edge case in the D3D11 backend where a large index array of GL_UNSIGNED_BYTE
// drawn as a GL_TRIANGLE_FAN is widened to 32-bits internally and overflows.
// Disabled because it slow and triggers an internal error.
TEST_P(LineLoopTestES3, DISABLED_LargeFanUnsignedByte)
{
    size_t count = 805306368;

    glLinkProgram(mProgram);
    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    GLBuffer ebo;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, count, nullptr, GL_STATIC_DRAW);
    ASSERT_GL_NO_ERROR();

    glDrawElements(GL_TRIANGLE_FAN, count, GL_UNSIGNED_BYTE, 0);
    EXPECT_GL_ERROR(GL_OUT_OF_MEMORY);
}

// Tests an edge case with a very large line loop element count.
// Disabled because it is slow and triggers an internal error.
TEST_P(LineLoopTest, DISABLED_DrawArraysWithLargeCount)
{
    constexpr char kVS[] = "void main() { gl_Position = vec4(0); }";
    constexpr char kFS[] = "void main() { gl_FragColor = vec4(0, 1, 0, 1); }";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glDrawArrays(GL_LINE_LOOP, 0, 0x3FFFFFFE);
    EXPECT_GL_ERROR(GL_OUT_OF_MEMORY);

    glDrawArrays(GL_LINE_LOOP, 0, 0x1FFFFFFE);
    EXPECT_GL_NO_ERROR();
}

class LineLoopPrimitiveRestartTest : public ANGLETest<>
{
  protected:
    LineLoopPrimitiveRestartTest()
    {
        setWindowWidth(64);
        setWindowHeight(64);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }
};

TEST_P(LineLoopPrimitiveRestartTest, LineLoopWithPrimitiveRestart)
{
    constexpr char kVS[] = R"(#version 300 es
in vec2 a_position;
// x,y = offset, z = scale
in vec3 a_transform;

invariant gl_Position;
void main()
{
    vec2 v_position = a_transform.z * a_position + a_transform.xy;
    gl_Position = vec4(v_position, 0.0, 1.0);
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
layout (location=0) out vec4 fragColor;
void main()
{
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glBindAttribLocation(program, 0, "a_position");
    glBindAttribLocation(program, 1, "a_transform");
    glLinkProgram(program);
    glUseProgram(program);
    ASSERT_GL_NO_ERROR();

    // clang-format off
    constexpr GLfloat vertices[] = {
        0.1, 0.1, -0.1, 0.1, -0.1, -0.1, 0.1, -0.1,
        0.1, 0.1, -0.1, 0.1, -0.1, -0.1, 0.1, -0.1,
        0.1, 0.1, -0.1, 0.1, -0.1, -0.1, 0.1, -0.1,
        0.1, 0.1, -0.1, 0.1, -0.1, -0.1, 0.1, -0.1,
    };

    constexpr GLfloat transform[] = {
        // first loop transform
        0, 0, 9,
        0, 0, 9,
        0, 0, 9,
        0, 0, 9,
        // second loop transform
        0.2, 0.1, 2,
        0.2, 0.1, 2,
        0.2, 0.1, 2,
        0.2, 0.1, 2,
        // third loop transform
        0.5, -0.2, 3,
        0.5, -0.2, 3,
        0.5, -0.2, 3,
        0.5, -0.2, 3,
        // forth loop transform
        -0.8, -0.5, 1,
        -0.8, -0.5, 1,
        -0.8, -0.5, 1,
        -0.8, -0.5, 1,
    };

    constexpr GLushort lineloopAsStripIndices[] = {
        // first strip
        0, 1, 2, 3, 0,
        // second strip
        4, 5, 6, 7, 4,
        // third strip
        8, 9, 10, 11, 8,
        // forth strip
        12, 13, 14, 15, 12 };

    constexpr GLushort lineloopWithRestartIndices[] = {
        // first loop
        0, 1, 2, 3, 0xffff,
        // second loop
        4, 5, 6, 7, 0xffff,
        // third loop
        8, 9, 10, 11, 0xffff,
        // forth loop
        12, 13, 14, 15,
    };
    // clang-format on

    std::vector<GLColor> expectedPixels(getWindowWidth() * getWindowHeight());
    std::vector<GLColor> renderedPixels(getWindowWidth() * getWindowHeight());

    // Draw in non-primitive restart way
    glClear(GL_COLOR_BUFFER_BIT);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    for (int loop = 0; loop < 4; ++loop)
    {
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, ANGLE_UNSAFE_TODO(vertices + 8 * loop));
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0,
                              ANGLE_UNSAFE_TODO(transform + 12 * loop));

        glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_SHORT, lineloopAsStripIndices);
    }

    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 expectedPixels.data());
    ASSERT_GL_NO_ERROR();

    // Draw line loop with primitive restart:
    glClear(GL_COLOR_BUFFER_BIT);

    GLBuffer vertexBuffer[2];
    GLBuffer indexBuffer;

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(lineloopWithRestartIndices),
                 lineloopWithRestartIndices, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(transform), transform, GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);

    glClear(GL_COLOR_BUFFER_BIT);
    glDrawElements(GL_LINE_LOOP, ArraySize(lineloopWithRestartIndices), GL_UNSIGNED_SHORT, 0);

    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 renderedPixels.data());

    for (int y = 0; y < getWindowHeight(); ++y)
    {
        for (int x = 0; x < getWindowWidth(); ++x)
        {
            int idx = y * getWindowWidth() + x;
            EXPECT_EQ(expectedPixels[idx], renderedPixels[idx])
                << "Expected pixel at " << x << ", " << y << " to be " << expectedPixels[idx]
                << std::endl;
        }
    }
}

// Tests that drawing an element buffer with primitive restart indices only
// does not crash.
TEST_P(LineLoopPrimitiveRestartTest, PrimitiveRestartRestartOnlyIndicesNoCrash)
{
    constexpr char kVS[] = "void main() { gl_Position = vec4(0); }";
    constexpr char kFS[] = "void main() { gl_FragColor = vec4(0, 1, 0, 1); }";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    ASSERT_GL_NO_ERROR();
    std::vector<GLshort> indices(0x1000, static_cast<GLshort>(0xFFFFF));
    GLBuffer indexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(indices[0]), &indices[0],
                 GL_STATIC_DRAW);
    glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
    glDrawElements(GL_LINE_LOOP, 0x800, GL_UNSIGNED_SHORT, 0);
    ASSERT_GL_NO_ERROR();
}

class LineLoopPrimitiveRestartXfbTest : public ANGLETest<>
{
  protected:
    LineLoopPrimitiveRestartXfbTest()
    {
        setWindowWidth(64);
        setWindowHeight(64);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }
};

// Test that it works when there is only one vertex before or after restart index.
TEST_P(LineLoopPrimitiveRestartXfbTest, OneVertexBeforeRestartIndex)
{
    constexpr char kVS[] = R"(#version 300 es
in vec2 a_position;
// x,y = offset, z = scale
in vec3 a_transform;
out float out_float;

invariant gl_Position;
void main()
{
    vec2 v_position = a_transform.z * a_position + a_transform.xy;
    out_float = a_position.x;
    gl_Position = vec4(v_position, 0.0, 1.0);
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
layout (location=0) out vec4 fragColor;
void main()
{
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glBindAttribLocation(program, 0, "a_position");
    glBindAttribLocation(program, 1, "a_transform");
    const char *varyings[] = {"out_float"};
    glTransformFeedbackVaryings(program, 1, varyings, GL_INTERLEAVED_ATTRIBS);
    glLinkProgram(program);
    glUseProgram(program);
    ASSERT_GL_NO_ERROR();

    // clang-format off
    constexpr GLfloat vertices[] = {
        0.1, 0.1, -0.1, 0.1, -0.1, -0.1, 0.1, -0.1,
        0.4, 0.1, -0.5, 0.1, -0.6, -0.1, 0.7, -0.1,
        0.8, 0.1, -0.9, 0.1, -1.0, -0.1, 1.1, -0.1,
        0.1, 0.1, -0.1, 0.1, -0.1, -0.1, 0.1, -0.1,
    };

    constexpr GLfloat transform[] = {
        // first loop transform
        0, 0, 9,
        0, 0, 9,
        0, 0, 9,
        0, 0, 9,
        // second loop transform
        0.2, 0.1, 2,
        0.2, 0.1, 2,
        0.2, 0.1, 2,
        0.2, 0.1, 2,
        // third loop transform
        0.5, -0.2, 3,
        0.5, -0.2, 3,
        0.5, -0.2, 3,
        0.5, -0.2, 3,
        // forth loop transform
        -0.8, -0.5, 1,
        -0.8, -0.5, 1,
        -0.8, -0.5, 1,
        -0.8, -0.5, 1,
    };

    constexpr GLushort lineloopAsStripIndices[] = {
        // first strip
        0, 1, 2, 3, 0,
        // second strip
        4, 5, 6, 7, 4,
        // third strip
        8, 9, 10, 11, 8,
        // forth strip
        12, 13, 14, 15, 12 };

    constexpr GLushort lineloopWithRestartIndices[] = {
        // first loop
        0, 0xffff,
        4, 5, 6, 7, 0xffff,
        8, 9, 10, 11, 0xffff,
        15,
    };
    // clang-format on

    std::vector<GLColor> expectedPixels(getWindowWidth() * getWindowHeight());
    std::vector<GLColor> renderedPixels(getWindowWidth() * getWindowHeight());

    // Draw in non-primitive restart way
    glClear(GL_COLOR_BUFFER_BIT);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    for (int loop = 1; loop < 3; ++loop)
    {
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, ANGLE_UNSAFE_TODO(vertices + 8 * loop));
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0,
                              ANGLE_UNSAFE_TODO(transform + 12 * loop));

        glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_SHORT, lineloopAsStripIndices);
    }

    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 expectedPixels.data());
    ASSERT_GL_NO_ERROR();

    // Draw line loop with primitive restart:
    glClear(GL_COLOR_BUFFER_BIT);

    GLBuffer vertexBuffer[2];
    GLBuffer indexBuffer;

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(lineloopWithRestartIndices),
                 lineloopWithRestartIndices, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(transform), transform, GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);

    // For xfb
    const GLfloat expected[16] = {0.4, -0.5, -0.5, -0.6, -0.6, 0.7, 0.7, 0.4,
                                  0.8, -0.9, -0.9, -1.0, -1.0, 1.1, 1.1, 0.8};
    unsigned expected_count    = sizeof(expected) / sizeof(expected[0]);
    GLuint xfbBuffer;
    glGenBuffers(1, &xfbBuffer);
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(expected), nullptr, GL_STATIC_READ);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);

    glBeginTransformFeedback(GL_LINES);
    glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);

    glClear(GL_COLOR_BUFFER_BIT);
    glDrawElements(GL_LINE_LOOP, ArraySize(lineloopWithRestartIndices), GL_UNSIGNED_SHORT, 0);
    glDisable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
    glEndTransformFeedback();

    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 renderedPixels.data());

    for (int y = 0; y < getWindowHeight(); ++y)
    {
        for (int x = 0; x < getWindowWidth(); ++x)
        {
            int idx = y * getWindowWidth() + x;
            EXPECT_EQ(expectedPixels[idx], renderedPixels[idx])
                << "Expected pixel at " << x << ", " << y << " to be " << expectedPixels[idx]
                << std::endl;
        }
    }

    GLfloat *mappedBufferData = nullptr;
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
    mappedBufferData = (GLfloat *)glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
                                                   sizeof(expected), GL_MAP_READ_BIT);
    if (mappedBufferData != nullptr)
    {
        for (unsigned j = 0; j < expected_count; j++)
        {
            ANGLE_UNSAFE_TODO(EXPECT_EQ(mappedBufferData[j], expected[j]))
                << "Expected pixel at " << j << " to be " << ANGLE_UNSAFE_TODO(expected[j])
                << std::endl;
        }
    }

    ASSERT_GL_NO_ERROR();
}

class LineLoopIndirectTest : public LineLoopTest
{
  protected:
    struct DrawCommand
    {
        GLuint count;
        GLuint primCount;
        GLuint firstIndex;
        GLint baseVertex;
        GLuint reservedMustBeZero;
    };

    void initUpdateBuffers(GLuint vertexArray,
                           GLuint vertexBuffer,
                           GLuint indexBuffer,
                           const void *positions,
                           uint32_t positionsSize,
                           const void *indices,
                           uint32_t indicesSize)
    {
        glBindVertexArray(vertexArray);
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

        glBufferData(GL_ARRAY_BUFFER, positionsSize, positions, GL_STATIC_DRAW);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesSize, indices, GL_STATIC_DRAW);
    }

    void preTestUBOAndInitUpdateBuffers(GLuint vertexArray,
                                        GLuint vertexBuffer,
                                        GLuint indexBuffer,
                                        const void *positions,
                                        GLsizei positionsSize,
                                        const void *indices,
                                        GLsizei indicesSize,
                                        GLuint arrayUpdateFbo,
                                        GLuint arrayUpdateTexture,
                                        GLuint elementUpdateFbo,
                                        GLuint elementUpdateTexture)
    {
        preTestUpdateBuffer(arrayUpdateFbo, arrayUpdateTexture, vertexBuffer, positionsSize);
        preTestUpdateBuffer(elementUpdateFbo, elementUpdateTexture, indexBuffer, indicesSize);

        glBindVertexArray(vertexArray);
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

        glBufferSubData(GL_ARRAY_BUFFER, 0, positionsSize, positions);
        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indicesSize, indices);
    }

    void initIndirectBuffer(GLuint indirectBuffer, GLuint firstIndex)
    {
        DrawCommand indirectData = {};
        indirectData.count       = 4;
        indirectData.firstIndex  = firstIndex;
        indirectData.primCount   = 1;

        glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer);
        glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand), &indirectData, GL_STATIC_DRAW);
        ASSERT_GL_NO_ERROR();
    }

    void setVertexAttribs(const void *positions)
    {
        glEnableVertexAttribArray(mPositionLocation);
        glVertexAttribPointer(mPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, positions);
        ASSERT_GL_NO_ERROR();
    }

    static constexpr GLfloat kLoopPositions[]  = {0.0f,  0.0f, 0.0f, 0.0f, 0.0f, 0.0f,  0.0f,
                                                  0.0f,  0.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f,
                                                  -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f};
    static constexpr GLfloat kStripPositions[] = {-0.5f, -0.5f, -0.5f, 0.5f,
                                                  0.5f,  0.5f,  0.5f,  -0.5f};
    static constexpr GLubyte kStripIndices[]   = {1, 0, 3, 2, 1};
};

// Test that drawing a line loop using an index buffer of unsigned bytes works.
TEST_P(LineLoopIndirectTest, UByteIndexIndirectBuffer)
{
    // Old drivers buggy with optimized ConvertIndexIndirectLineLoop shader.
    // http://anglebug.com/40096699
    ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsVulkan());

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    // Start at index 1.
    GLuint firstIndex       = 1;
    const GLubyte indices[] = {0, 7, 6, 9, 8, 0};

    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    GLVertexArray vertexArray;
    GLBuffer vertexBuffer;
    GLBuffer indexBuffer;

    initUpdateBuffers(vertexArray, vertexBuffer, indexBuffer, kLoopPositions,
                      sizeof(kLoopPositions), indices, sizeof(indices));

    GLBuffer indirectBuffer;
    initIndirectBuffer(indirectBuffer, firstIndex);

    glEnable(GL_BLEND);
    setVertexAttribs(nullptr);
    glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawElementsIndirect(GL_LINE_LOOP, GL_UNSIGNED_BYTE, nullptr);
    ASSERT_GL_NO_ERROR();

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    setVertexAttribs(kStripPositions);
    glUniform4f(mColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_BYTE, kStripIndices);
    ASSERT_GL_NO_ERROR();

    checkPixels();
}

// Test that drawing a line loop using an index buffer of unsigned short values works.
TEST_P(LineLoopIndirectTest, UShortIndexIndirectBuffer)
{
    // Old drivers buggy with optimized ConvertIndexIndirectLineLoop shader.
    // http://anglebug.com/40096699
    ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsVulkan());

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    // Start at index 1.
    GLuint firstIndex        = 1;
    const GLushort indices[] = {0, 7, 6, 9, 8, 0};

    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    GLVertexArray vertexArray;
    GLBuffer vertexBuffer;
    GLBuffer indexBuffer;

    initUpdateBuffers(vertexArray, vertexBuffer, indexBuffer, kLoopPositions,
                      sizeof(kLoopPositions), indices, sizeof(indices));

    GLBuffer indirectBuffer;
    initIndirectBuffer(indirectBuffer, firstIndex);

    glEnable(GL_BLEND);
    setVertexAttribs(nullptr);
    glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawElementsIndirect(GL_LINE_LOOP, GL_UNSIGNED_SHORT, nullptr);
    ASSERT_GL_NO_ERROR();

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    setVertexAttribs(kStripPositions);
    glUniform4f(mColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_BYTE, kStripIndices);
    ASSERT_GL_NO_ERROR();

    checkPixels();
}

// Test that uploading data to buffer that's in use then using it for line loop elements works.
TEST_P(LineLoopIndirectTest, UseAsUBOThenUpdateThenUByteIndexIndirectBuffer)
{
    // http://anglebug.com/42264370
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsQualcomm());

    // Old drivers buggy with optimized ConvertIndexIndirectLineLoop shader.
    // http://anglebug.com/40096699
    ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsVulkan());

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    // Start at index 1.
    GLuint firstIndex       = 1;
    const GLubyte indices[] = {0, 7, 6, 9, 8, 0};

    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    GLVertexArray vertexArray;
    GLFramebuffer arrayUpdateFbo, elementUpdateFbo;
    GLTexture arrayUpdateTex, elementUpdateTex;
    GLBuffer vertexBuffer;
    GLBuffer indexBuffer;

    preTestUBOAndInitUpdateBuffers(vertexArray, vertexBuffer, indexBuffer, kLoopPositions,
                                   sizeof(kLoopPositions), indices, sizeof(indices), arrayUpdateFbo,
                                   arrayUpdateTex, elementUpdateFbo, elementUpdateTex);

    GLBuffer indirectBuffer;
    initIndirectBuffer(indirectBuffer, firstIndex);

    glEnable(GL_BLEND);
    setVertexAttribs(nullptr);
    glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawElementsIndirect(GL_LINE_LOOP, GL_UNSIGNED_BYTE, nullptr);
    ASSERT_GL_NO_ERROR();

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    setVertexAttribs(kStripPositions);
    glUniform4f(mColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_BYTE, kStripIndices);
    ASSERT_GL_NO_ERROR();

    checkPixels();

    glBindFramebuffer(GL_FRAMEBUFFER, arrayUpdateFbo);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    glBindFramebuffer(GL_FRAMEBUFFER, elementUpdateFbo);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that uploading data to buffer that's in use then using it for line loop elements works.
TEST_P(LineLoopIndirectTest, UseAsUBOThenUpdateThenUShortIndexIndirectBuffer)
{
    // http://anglebug.com/42264370
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsQualcomm());

    // Old drivers buggy with optimized ConvertIndexIndirectLineLoop shader.
    // http://anglebug.com/40096699
    ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsVulkan());

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    // Start at index 1.
    GLuint firstIndex       = 1;
    const GLshort indices[] = {0, 7, 6, 9, 8, 0};

    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    GLVertexArray vertexArray;
    GLFramebuffer arrayUpdateFbo, elementUpdateFbo;
    GLTexture arrayUpdateTex, elementUpdateTex;
    GLBuffer vertexBuffer;
    GLBuffer indexBuffer;

    preTestUBOAndInitUpdateBuffers(vertexArray, vertexBuffer, indexBuffer, kLoopPositions,
                                   sizeof(kLoopPositions), indices, sizeof(indices), arrayUpdateFbo,
                                   arrayUpdateTex, elementUpdateFbo, elementUpdateTex);

    GLBuffer indirectBuffer;
    initIndirectBuffer(indirectBuffer, firstIndex);

    glEnable(GL_BLEND);
    setVertexAttribs(nullptr);
    glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawElementsIndirect(GL_LINE_LOOP, GL_UNSIGNED_SHORT, nullptr);
    ASSERT_GL_NO_ERROR();

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    setVertexAttribs(kStripPositions);
    glUniform4f(mColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_BYTE, kStripIndices);
    ASSERT_GL_NO_ERROR();

    checkPixels();

    glBindFramebuffer(GL_FRAMEBUFFER, arrayUpdateFbo);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    glBindFramebuffer(GL_FRAMEBUFFER, elementUpdateFbo);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that two indirect draws drawing lineloop and sharing same index buffer works.
TEST_P(LineLoopIndirectTest, TwoIndirectDrawsShareIndexBuffer)
{
    // http://anglebug.com/42264370
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsQualcomm());

    // Old drivers buggy with optimized ConvertIndexIndirectLineLoop shader.
    // http://anglebug.com/40096699
    ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsVulkan());

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    // Start at index 1.
    GLuint firstIndex        = 1;
    const GLushort indices[] = {0, 7, 6, 9, 8, 0};

    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    GLVertexArray vertexArray;
    GLBuffer vertexBuffer;
    GLBuffer indexBuffer;

    initUpdateBuffers(vertexArray, vertexBuffer, indexBuffer, kLoopPositions,
                      sizeof(kLoopPositions), indices, sizeof(indices));

    GLBuffer indirectBuffer;
    initIndirectBuffer(indirectBuffer, firstIndex);

    glEnable(GL_BLEND);
    setVertexAttribs(nullptr);
    glUniform4f(mColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawElementsIndirect(GL_LINE_LOOP, GL_UNSIGNED_SHORT, nullptr);
    ASSERT_GL_NO_ERROR();
    glDrawElementsIndirect(GL_LINE_LOOP, GL_UNSIGNED_SHORT, nullptr);
    ASSERT_GL_NO_ERROR();

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    setVertexAttribs(kStripPositions);
    glUniform4f(mColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_BYTE, kStripIndices);
    ASSERT_GL_NO_ERROR();

    checkPixels();
}

// Test that one indirect line-loop followed by one non-line-loop draw that share the same index
// buffer works.
TEST_P(LineLoopIndirectTest, IndirectAndElementDrawsShareIndexBuffer)
{
    // http://anglebug.com/42264370
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsQualcomm());

    // Old drivers buggy with optimized ConvertIndexIndirectLineLoop shader.
    // http://anglebug.com/40096699
    ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsVulkan());

    // http://anglebug.com/42265165: Disable D3D11 SDK Layers warnings checks.
    ignoreD3D11SDKLayersWarnings();

    GLuint firstIndex                    = 10;
    static const GLubyte indices[]       = {0, 6, 9, 8, 7, 6, 0, 0, 9, 1, 2, 3, 4, 1, 0};
    static const GLfloat loopPositions[] = {0.0f,  0.0f, 0.5f,  0.0f, 0.0f, 0.5f,  -0.5f,
                                            0.0f,  0.0f, -0.5f, 0.0f, 0.0f, -0.5f, -0.5f,
                                            -0.5f, 0.5f, 0.5f,  0.5f, 0.5f, -0.5f};

    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    GLVertexArray vertexArray;
    GLBuffer vertexBuffer;
    GLBuffer indexBuffer;

    initUpdateBuffers(vertexArray, vertexBuffer, indexBuffer, loopPositions, sizeof(loopPositions),
                      indices, sizeof(indices));

    GLBuffer indirectBuffer;
    initIndirectBuffer(indirectBuffer, firstIndex);

    glEnable(GL_BLEND);
    setVertexAttribs(nullptr);
    glUniform4f(mColorLocation, 1.0f, 0.0f, 1.0f, 1.0f);

    glViewport(0, 0, getWindowWidth() / 2, getWindowHeight());
    glDrawElementsIndirect(GL_LINE_LOOP, GL_UNSIGNED_BYTE, nullptr);
    ASSERT_GL_NO_ERROR();

    glViewport(getWindowWidth() / 2, 0, getWindowWidth() / 2, getWindowHeight());
    glUniform4f(mColorLocation, 0.0, 1.0, 1.0, 1.0);
    glDrawElements(GL_LINE_STRIP, 5, GL_UNSIGNED_BYTE, reinterpret_cast<const void *>(1));
    ASSERT_GL_NO_ERROR();

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    // Check pixels.
    std::vector<GLubyte> pixels(getWindowWidth() * getWindowHeight() * 4);
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE, &pixels[0]);
    ASSERT_GL_NO_ERROR();

    for (int y = 0; y < getWindowHeight(); y++)
    {
        for (int x = 0; x < getWindowWidth() / 2; x++)
        {
            const GLubyte *pixel = ANGLE_UNSAFE_TODO(&pixels[0] + ((y * getWindowWidth() + x) * 4));

            ANGLE_UNSAFE_TODO(EXPECT_TRUE(pixel[0] == pixel[2]))
                << "Failed at " << x << ", " << y << std::endl;
            ANGLE_UNSAFE_TODO(EXPECT_TRUE(pixel[1] == 0))
                << "Failed at " << x << ", " << y << std::endl;
            ANGLE_UNSAFE_TODO(ASSERT_EQ(pixel[3], 255))
                << "Failed at " << x << ", " << y << std::endl;
        }
        for (int x = getWindowWidth() / 2; x < getWindowWidth(); x++)
        {
            const GLubyte *pixel = ANGLE_UNSAFE_TODO(&pixels[0] + ((y * getWindowWidth() + x) * 4));

            EXPECT_TRUE(pixel[0] == 0) << "Failed at " << x << ", " << y << std::endl;
            ANGLE_UNSAFE_TODO(EXPECT_TRUE(pixel[1] == pixel[2]))
                << "Failed at " << x << ", " << y << std::endl;
            ANGLE_UNSAFE_TODO(ASSERT_EQ(pixel[3], 255))
                << "Failed at " << x << ", " << y << std::endl;
        }
    }
}

// Test that two indirect draws using glDrawArraysIndirect with GL_LINE_LOOP mode
// and different vertex ranges work correctly.
TEST_P(LineLoopIndirectTest, TwoIndirectDrawsInDifferentIndirectBuffer)
{
    // EXT_geometry_shader allows transform feedback to work with draw indirect.
    // Otherwise, INVALID_OPERATION will be generated.
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));

    GLVertexArray vertexArray;
    GLBuffer vertexBuffer, xfbBuffer, indirectBuffer[2];
    const std::vector<GLfloat> vertices = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0};
    const std::vector<GLfloat> expected = {
        2.0, 1.0, 4.0,  4.0,  4.0,  4.0,  6.0,  9.0,  6.0,  9.0,  8.0,  16.0, 8.0,  16.0,
        2.0, 1.0, 10.0, 25.0, 12.0, 36.0, 12.0, 36.0, 14.0, 49.0, 14.0, 49.0, 10.0, 25.0};
    constexpr char kVS[] = R"(#version 300 es
in float in_val;
out float out_val;
out float out_val2;

void main()
{
    out_val = in_val * 2.0;
    out_val2 = in_val * in_val;
    gl_Position = vec4(in_val);
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
in float out_val;
in float out_val2;
out vec4 fragColor;
void main()
{
    fragColor = vec4(1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    std::vector<const char *> varyings = {"out_val", "out_val2"};
    glTransformFeedbackVaryings(program, static_cast<GLsizei>(varyings.size()), varyings.data(),
                                GL_INTERLEAVED_ATTRIBS);
    glLinkProgram(program);
    glUseProgram(program);
    ASSERT_GL_NO_ERROR();

    glBindVertexArray(vertexArray);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat), vertices.data(),
                 GL_STATIC_DRAW);
    ASSERT_GL_NO_ERROR();

    GLuint pos = glGetAttribLocation(program, "in_val");
    glEnableVertexAttribArray(pos);
    glVertexAttribPointer(pos, 1, GL_FLOAT, GL_FALSE, 0, 0);
    ASSERT_GL_NO_ERROR();

    std::vector<GLfloat> initialData(expected.size(), 0.0);
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, expected.size() * sizeof(GLfloat),
                 initialData.data(), GL_STATIC_READ);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);
    ASSERT_GL_NO_ERROR();

    glBeginTransformFeedback(GL_LINES);
    ASSERT_GL_NO_ERROR();

    DrawCommand indirectDrawArgs1 = {};
    indirectDrawArgs1.count       = 4;
    indirectDrawArgs1.firstIndex  = 0;
    indirectDrawArgs1.primCount   = 1;
    DrawCommand indirectDrawArgs2 = {};
    indirectDrawArgs2.count       = 3;
    indirectDrawArgs2.firstIndex  = 4;
    indirectDrawArgs2.primCount   = 1;

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer[0]);
    glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand), &indirectDrawArgs1, GL_STATIC_DRAW);
    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer[1]);
    glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand), &indirectDrawArgs2, GL_STATIC_DRAW);
    ASSERT_GL_NO_ERROR();

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer[0]);
    glDrawArraysIndirect(GL_LINE_LOOP, nullptr);
    ASSERT_GL_NO_ERROR();
    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer[1]);
    glDrawArraysIndirect(GL_LINE_LOOP, nullptr);
    ASSERT_GL_NO_ERROR();

    glEndTransformFeedback();
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
    GLfloat *mapped = (GLfloat *)glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
                                                  expected.size() * sizeof(float), GL_MAP_READ_BIT);
    ASSERT_GL_NO_ERROR();
    for (size_t i = 0; i < expected.size(); i++)
    {
        ANGLE_UNSAFE_TODO(EXPECT_EQ(mapped[i], expected[i]));
    }
}
ANGLE_INSTANTIATE_TEST_ES2_AND(LineLoopTest,
                               ES2_OPENGLES().enable(Feature::ForcePassthroughShaders));
ANGLE_INSTANTIATE_TEST_ES3(LineLoopTestES3);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LineLoopPrimitiveRestartTest);
ANGLE_INSTANTIATE_TEST_ES3_AND(
    LineLoopPrimitiveRestartTest,
    ES3_METAL().enable(Feature::ForceBufferGPUStorage),
    ES3_METAL().disable(Feature::HasExplicitMemBarrier).disable(Feature::HasCheapRenderPass),
    ES3_WEBGPU());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LineLoopIndirectTest);
ANGLE_INSTANTIATE_TEST_ES31(LineLoopIndirectTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LineLoopPrimitiveRestartXfbTest);
ANGLE_INSTANTIATE_TEST_ES32(LineLoopPrimitiveRestartXfbTest);
