Edit

kc3-lang/angle/src/tests/gl_tests/SimpleOperationTest.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2020-01-24 17:08:30
    Hash : 3712b2e4
    Message : Vulkan: Command graph linearization (Step 1). This initial prototype introduces a new feature to the Vulkan back-end that disables the deferred command recording. The intent is to have a lower CPU overhead during submission calls which currently walk a DAG. The feature is not complete. Currently it only passes the ANGLE SimpleOperationTests. Moreover it is extremely simple and only allows use of one command buffer at a time. In the future we'll allow open command buffers for recording outside and inside render pass commands at the same time. We'll also support collapsing RenderPasses together for some use cases. Currently the prototype only passes "SimpleOperationTest". There are quite a few unimplemented features like queries, XFB, etc. Bug: angleproject:4029 Change-Id: I82760986683f55e37ac4ea559de6f4cffb6ef84e Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1953485 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Tobin Ehlis <tobine@google.com>

  • src/tests/gl_tests/SimpleOperationTest.cpp
  • //
    // 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.
    //
    // SimpleOperationTest:
    //   Basic GL commands such as linking a program, initializing a buffer, etc.
    
    #include "test_utils/ANGLETest.h"
    
    #include <vector>
    
    #include "test_utils/gl_raii.h"
    #include "util/EGLWindow.h"
    #include "util/random_utils.h"
    
    using namespace angle;
    
    namespace
    {
    constexpr char kBasicVertexShader[] =
        R"(attribute vec3 position;
    void main()
    {
        gl_Position = vec4(position, 1);
    })";
    
    constexpr char kGreenFragmentShader[] =
        R"(void main()
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    })";
    
    class SimpleOperationTest : public ANGLETest
    {
      protected:
        SimpleOperationTest()
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        void verifyBuffer(const std::vector<uint8_t> &data, GLenum binding);
    
        template <typename T>
        void testDrawElementsLineLoopUsingClientSideMemory(GLenum indexType,
                                                           int windowWidth,
                                                           int windowHeight);
    };
    
    void SimpleOperationTest::verifyBuffer(const std::vector<uint8_t> &data, GLenum binding)
    {
        if (!IsGLExtensionEnabled("GL_EXT_map_buffer_range"))
        {
            return;
        }
    
        uint8_t *mapPointer =
            static_cast<uint8_t *>(glMapBufferRangeEXT(GL_ARRAY_BUFFER, 0, 1024, GL_MAP_READ_BIT));
        ASSERT_GL_NO_ERROR();
    
        std::vector<uint8_t> readbackData(data.size());
        memcpy(readbackData.data(), mapPointer, data.size());
        glUnmapBufferOES(GL_ARRAY_BUFFER);
    
        EXPECT_EQ(data, readbackData);
    }
    
    // Validates if culling rasterization states work. Simply draws a quad with
    // cull face enabled and make sure we still render correctly.
    TEST_P(SimpleOperationTest, CullFaceEnabledState)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        glUseProgram(program);
    
        glClear(GL_COLOR_BUFFER_BIT);
        glEnable(GL_CULL_FACE);
    
        drawQuad(program.get(), "position", 0.0f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Validates if culling rasterization states work. Simply draws a quad with
    // cull face enabled with cullface front and make sure the face have not been rendered.
    TEST_P(SimpleOperationTest, CullFaceFrontEnabledState)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        glUseProgram(program);
    
        glClear(GL_COLOR_BUFFER_BIT);
        glEnable(GL_CULL_FACE);
    
        // Should make the quad disappear since we draw it front facing.
        glCullFace(GL_FRONT);
    
        drawQuad(program.get(), "position", 0.0f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
    }
    
    // Validates if blending render states work. Simply draws twice and verify the color have been
    // added in the final output.
    TEST_P(SimpleOperationTest, BlendingRenderState)
    {
        // The precision when blending isn't perfect and some tests fail with a color of 254 instead
        // of 255 on the green component. This is why we need 0.51 green instead of .5
        constexpr char halfGreenFragmentShader[] =
            R"(void main()
    {
        gl_FragColor = vec4(0, 0.51, 0, 1);
    })";
    
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, halfGreenFragmentShader);
        glUseProgram(program);
    
        glClear(GL_COLOR_BUFFER_BIT);
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glBlendEquation(GL_FUNC_ADD);
    
        auto vertices = GetQuadVertices();
    
        const GLint positionLocation = glGetAttribLocation(program, "position");
        ASSERT_NE(-1, positionLocation);
    
        GLBuffer vertexBuffer;
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer.get());
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
                     GL_STATIC_DRAW);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
    
        glEnableVertexAttribArray(positionLocation);
    
        // Drawing a quad once will give 0.51 green, but if we enable blending
        // with additive function we should end up with full green of 1.0 with
        // a clamping func of 1.0.
        glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(vertices.size()));
        glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(vertices.size()));
    
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    TEST_P(SimpleOperationTest, CompileVertexShader)
    {
        GLuint shader = CompileShader(GL_VERTEX_SHADER, kBasicVertexShader);
        EXPECT_NE(shader, 0u);
        glDeleteShader(shader);
    
        ASSERT_GL_NO_ERROR();
    }
    
    TEST_P(SimpleOperationTest, CompileFragmentShaderSingleVaryingInput)
    {
        constexpr char kFS[] = R"(precision mediump float;
    varying vec4 v_input;
    void main()
    {
        gl_FragColor = v_input;
    })";
    
        GLuint shader = CompileShader(GL_FRAGMENT_SHADER, kFS);
        EXPECT_NE(shader, 0u);
        glDeleteShader(shader);
    
        ASSERT_GL_NO_ERROR();
    }
    
    // Covers a simple bug in Vulkan to do with dependencies between the Surface and the default
    // Framebuffer.
    TEST_P(SimpleOperationTest, ClearAndSwap)
    {
        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        swapBuffers();
    
        // Can't check the pixel result after the swap, and checking the pixel result affects the
        // behaviour of the test on the Vulkan back-end, so don't bother checking correctness.
        ASSERT_GL_NO_ERROR();
        ASSERT_FALSE(getGLWindow()->hasError());
    }
    
    // Simple case of setting a scissor, enabled or disabled.
    TEST_P(SimpleOperationTest, ScissorTest)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
    
        glClear(GL_COLOR_BUFFER_BIT);
        glEnable(GL_SCISSOR_TEST);
        glScissor(getWindowWidth() / 4, getWindowHeight() / 4, getWindowWidth() / 2,
                  getWindowHeight() / 2);
    
        // Fill the whole screen with a quad.
        drawQuad(program.get(), "position", 0.0f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
    
        // Test outside the scissor test, pitch black.
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
    
        // Test inside, green of the fragment shader.
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
    }
    
    TEST_P(SimpleOperationTest, LinkProgramShadersNoInputs)
    {
        constexpr char kVS[] = "void main() { gl_Position = vec4(1.0, 1.0, 1.0, 1.0); }";
        constexpr char kFS[] = "void main() { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); }";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        ASSERT_GL_NO_ERROR();
    }
    
    TEST_P(SimpleOperationTest, LinkProgramWithUniforms)
    {
        constexpr char kVS[] = R"(void main()
    {
        gl_Position = vec4(1.0, 1.0, 1.0, 1.0);
    })";
        constexpr char kFS[] = R"(precision mediump float;
    uniform vec4 u_input;
    void main()
    {
        gl_FragColor = u_input;
    })";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        const GLint uniformLoc = glGetUniformLocation(program, "u_input");
        EXPECT_NE(-1, uniformLoc);
    
        ASSERT_GL_NO_ERROR();
    }
    
    TEST_P(SimpleOperationTest, LinkProgramWithAttributes)
    {
        constexpr char kVS[] = R"(attribute vec4 a_input;
    void main()
    {
        gl_Position = a_input;
    })";
    
        ANGLE_GL_PROGRAM(program, kVS, kGreenFragmentShader);
    
        const GLint attribLoc = glGetAttribLocation(program, "a_input");
        EXPECT_NE(-1, attribLoc);
    
        ASSERT_GL_NO_ERROR();
    }
    
    TEST_P(SimpleOperationTest, BufferDataWithData)
    {
        GLBuffer buffer;
        glBindBuffer(GL_ARRAY_BUFFER, buffer.get());
    
        std::vector<uint8_t> data(1024);
        FillVectorWithRandomUBytes(&data);
        glBufferData(GL_ARRAY_BUFFER, data.size(), &data[0], GL_STATIC_DRAW);
    
        verifyBuffer(data, GL_ARRAY_BUFFER);
    
        ASSERT_GL_NO_ERROR();
    }
    
    TEST_P(SimpleOperationTest, BufferDataWithNoData)
    {
        GLBuffer buffer;
        glBindBuffer(GL_ARRAY_BUFFER, buffer.get());
        glBufferData(GL_ARRAY_BUFFER, 1024, nullptr, GL_STATIC_DRAW);
    
        ASSERT_GL_NO_ERROR();
    }
    
    TEST_P(SimpleOperationTest, BufferSubData)
    {
        GLBuffer buffer;
        glBindBuffer(GL_ARRAY_BUFFER, buffer.get());
    
        constexpr size_t bufferSize = 1024;
        std::vector<uint8_t> data(bufferSize);
        FillVectorWithRandomUBytes(&data);
    
        glBufferData(GL_ARRAY_BUFFER, bufferSize, nullptr, GL_STATIC_DRAW);
    
        constexpr size_t subDataCount = 16;
        constexpr size_t sliceSize    = bufferSize / subDataCount;
        for (size_t i = 0; i < subDataCount; i++)
        {
            size_t offset = i * sliceSize;
            glBufferSubData(GL_ARRAY_BUFFER, offset, sliceSize, &data[offset]);
        }
    
        verifyBuffer(data, GL_ARRAY_BUFFER);
    
        ASSERT_GL_NO_ERROR();
    }
    
    // Simple quad test.
    TEST_P(SimpleOperationTest, DrawQuad)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
    
        drawQuad(program.get(), "position", 0.5f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Simple quad test with data in client memory, not vertex buffer.
    TEST_P(SimpleOperationTest, DrawQuadFromClientMemory)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
    
        drawQuad(program.get(), "position", 0.5f, 1.0f, false);
    
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Simple double quad test.
    TEST_P(SimpleOperationTest, DrawQuadTwice)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
    
        drawQuad(program.get(), "position", 0.5f, 1.0f, true);
        drawQuad(program.get(), "position", 0.5f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Simple line test.
    TEST_P(SimpleOperationTest, DrawLine)
    {
        // We assume in the test the width and height are equal and we are tracing
        // the line from bottom left to top right. Verify that all pixels along that line
        // have been traced with green.
        ASSERT_EQ(getWindowWidth(), getWindowHeight());
    
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        glUseProgram(program);
    
        std::vector<Vector3> vertices = {{-1.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}};
    
        const GLint positionLocation = glGetAttribLocation(program, "position");
        ASSERT_NE(-1, positionLocation);
    
        GLBuffer vertexBuffer;
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
                     GL_STATIC_DRAW);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnableVertexAttribArray(positionLocation);
    
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(vertices.size()));
    
        glDisableVertexAttribArray(positionLocation);
    
        ASSERT_GL_NO_ERROR();
    
        for (auto x = 0; x < getWindowWidth(); x++)
        {
            EXPECT_PIXEL_COLOR_EQ(x, x, GLColor::green);
        }
    }
    
    // Simple line test that will use a very large offset in the vertex attributes.
    TEST_P(SimpleOperationTest, DrawLineWithLargeAttribPointerOffset)
    {
        // We assume in the test the width and height are equal and we are tracing
        // the line from bottom left to top right. Verify that all pixels along that line
        // have been traced with green.
        ASSERT_EQ(getWindowWidth(), getWindowHeight());
    
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        glUseProgram(program);
    
        int kOffset = 3315;
        std::vector<Vector3> vertices(kOffset);
        Vector3 vector1{-1.0f, -1.0f, 0.0f};
        Vector3 vector2{1.0f, 1.0f, 0.0f};
        vertices.emplace_back(vector1);
        vertices.emplace_back(vector2);
    
        const GLint positionLocation = glGetAttribLocation(program, "position");
        ASSERT_NE(-1, positionLocation);
    
        GLBuffer vertexBuffer;
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
                     GL_STATIC_DRAW);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0,
                              reinterpret_cast<const void *>(kOffset * sizeof(vertices[0])));
        glEnableVertexAttribArray(positionLocation);
    
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_LINES, 0, 2);
    
        glDisableVertexAttribArray(positionLocation);
    
        ASSERT_GL_NO_ERROR();
    
        for (auto x = 0; x < getWindowWidth(); x++)
        {
            EXPECT_PIXEL_COLOR_EQ(x, x, GLColor::green);
        }
    }
    
    // Simple line strip test.
    TEST_P(SimpleOperationTest, DrawLineStrip)
    {
        // We assume in the test the width and height are equal and we are tracing
        // the line from bottom left to center, then from center to bottom right.
        // Verify that all pixels along these lines have been traced with green.
        ASSERT_EQ(getWindowWidth(), getWindowHeight());
    
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        glUseProgram(program);
    
        auto vertices =
            std::vector<Vector3>{{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, -1.0f, 0.0f}};
    
        const GLint positionLocation = glGetAttribLocation(program, "position");
        ASSERT_NE(-1, positionLocation);
    
        GLBuffer vertexBuffer;
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer.get());
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
                     GL_STATIC_DRAW);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
    
        glEnableVertexAttribArray(positionLocation);
    
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_LINE_STRIP, 0, static_cast<GLsizei>(vertices.size()));
    
        ASSERT_GL_NO_ERROR();
    
        const auto centerX = getWindowWidth() / 2;
        const auto centerY = getWindowHeight() / 2;
    
        for (auto x = 0; x < centerX; x++)
        {
            EXPECT_PIXEL_COLOR_EQ(x, x, GLColor::green);
        }
    
        for (auto x = centerX, y = centerY - 1; x < getWindowWidth() && y >= 0; x++, y--)
        {
            EXPECT_PIXEL_COLOR_EQ(x, y, GLColor::green);
        }
    }
    
    // Simple triangle fans test.
    TEST_P(SimpleOperationTest, DrawTriangleFan)
    {
        // We assume in the test the width and height are equal and we are tracing
        // 2 triangles to cover half the surface like this:
        ASSERT_EQ(getWindowWidth(), getWindowHeight());
    
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        glUseProgram(program);
    
        auto vertices = std::vector<Vector3>{
            {-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}};
    
        const GLint positionLocation = glGetAttribLocation(program, "position");
        ASSERT_NE(-1, positionLocation);
    
        GLBuffer vertexBuffer;
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer.get());
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
                     GL_STATIC_DRAW);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
        glEnableVertexAttribArray(positionLocation);
    
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_TRIANGLE_FAN, 0, static_cast<GLsizei>(vertices.size()));
    
        glDisableVertexAttribArray(positionLocation);
    
        EXPECT_GL_NO_ERROR();
    
        // Check 4 lines accross de triangles to make sure we filled it.
        // Don't check every pixel as it would slow down our tests.
        for (auto x = 0; x < getWindowWidth(); x++)
        {
            EXPECT_PIXEL_COLOR_EQ(x, x, GLColor::green);
        }
    
        for (auto x = getWindowWidth() / 3, y = 0; x < getWindowWidth(); x++, y++)
        {
            EXPECT_PIXEL_COLOR_EQ(x, y, GLColor::green);
        }
    
        for (auto x = getWindowWidth() / 2, y = 0; x < getWindowWidth(); x++, y++)
        {
            EXPECT_PIXEL_COLOR_EQ(x, y, GLColor::green);
        }
    
        for (auto x = (getWindowWidth() / 4) * 3, y = 0; x < getWindowWidth(); x++, y++)
        {
            EXPECT_PIXEL_COLOR_EQ(x, y, GLColor::green);
        }
    }
    
    // Simple repeated draw and swap test.
    TEST_P(SimpleOperationTest, DrawQuadAndSwap)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
    
        for (int i = 0; i < 8; ++i)
        {
            drawQuad(program.get(), "position", 0.5f, 1.0f, true);
            ASSERT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
            swapBuffers();
        }
    
        ASSERT_GL_NO_ERROR();
    }
    
    // Simple indexed quad test.
    TEST_P(SimpleOperationTest, DrawIndexedQuad)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
    
        drawIndexedQuad(program.get(), "position", 0.5f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Simple repeated indexed draw and swap test.
    TEST_P(SimpleOperationTest, DrawIndexedQuadAndSwap)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
    
        // 32 iterations is an arbitrary number. The more iterations, the more flaky syncronization
        // issues will reproduce consistently.
        for (int i = 0; i < 32; ++i)
        {
            drawIndexedQuad(program.get(), "position", 0.5f, 1.0f, true);
            ASSERT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
            swapBuffers();
        }
    
        ASSERT_GL_NO_ERROR();
    }
    
    // Draw with a fragment uniform.
    TEST_P(SimpleOperationTest, DrawQuadWithFragmentUniform)
    {
        constexpr char kFS[] =
            "uniform mediump vec4 color;\n"
            "void main()\n"
            "{\n"
            "    gl_FragColor = color;\n"
            "}";
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kFS);
    
        GLint location = glGetUniformLocation(program, "color");
        ASSERT_NE(-1, location);
    
        glUseProgram(program);
        glUniform4f(location, 0.0f, 1.0f, 0.0f, 1.0f);
    
        drawQuad(program.get(), "position", 0.5f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Draw with a vertex uniform.
    TEST_P(SimpleOperationTest, DrawQuadWithVertexUniform)
    {
        constexpr char kVS[] =
            "attribute vec3 position;\n"
            "uniform vec4 color;\n"
            "varying vec4 vcolor;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = vec4(position, 1);\n"
            "    vcolor = color;\n"
            "}";
        constexpr char kFS[] =
            "varying mediump vec4 vcolor;\n"
            "void main()\n"
            "{\n"
            "    gl_FragColor = vcolor;\n"
            "}";
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        const GLint location = glGetUniformLocation(program, "color");
        ASSERT_NE(-1, location);
    
        glUseProgram(program);
        glUniform4f(location, 0.0f, 1.0f, 0.0f, 1.0f);
    
        drawQuad(program.get(), "position", 0.5f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Draw with two uniforms.
    TEST_P(SimpleOperationTest, DrawQuadWithTwoUniforms)
    {
        constexpr char kVS[] =
            "attribute vec3 position;\n"
            "uniform vec4 color1;\n"
            "varying vec4 vcolor1;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = vec4(position, 1);\n"
            "    vcolor1 = color1;\n"
            "}";
        constexpr char kFS[] =
            "uniform mediump vec4 color2;\n"
            "varying mediump vec4 vcolor1;\n"
            "void main()\n"
            "{\n"
            "    gl_FragColor = vcolor1 + color2;\n"
            "}";
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        const GLint location1 = glGetUniformLocation(program, "color1");
        ASSERT_NE(-1, location1);
    
        const GLint location2 = glGetUniformLocation(program, "color2");
        ASSERT_NE(-1, location2);
    
        glUseProgram(program);
        glUniform4f(location1, 0.0f, 1.0f, 0.0f, 1.0f);
        glUniform4f(location2, 1.0f, 0.0f, 0.0f, 1.0f);
    
        drawQuad(program.get(), "position", 0.5f, 1.0f, true);
    
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
    }
    
    // Tests a shader program with more than one vertex attribute, with vertex buffers.
    TEST_P(SimpleOperationTest, ThreeVertexAttributes)
    {
        constexpr char kVS[] = R"(attribute vec2 position;
    attribute vec4 color1;
    attribute vec4 color2;
    varying vec4 color;
    void main()
    {
        gl_Position = vec4(position, 0, 1);
        color = color1 + color2;
    })";
    
        constexpr char kFS[] = R"(precision mediump float;
    varying vec4 color;
    void main()
    {
        gl_FragColor = color;
    }
    )";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        glUseProgram(program);
    
        const GLint color1Loc = glGetAttribLocation(program, "color1");
        const GLint color2Loc = glGetAttribLocation(program, "color2");
        ASSERT_NE(-1, color1Loc);
        ASSERT_NE(-1, color2Loc);
    
        const auto &indices = GetQuadIndices();
    
        // Make colored corners with red == x or 1 -x , and green = y or 1 - y.
    
        std::array<GLColor, 4> baseColors1 = {
            {GLColor::black, GLColor::red, GLColor::green, GLColor::yellow}};
        std::array<GLColor, 4> baseColors2 = {
            {GLColor::yellow, GLColor::green, GLColor::red, GLColor::black}};
    
        std::vector<GLColor> colors1;
        std::vector<GLColor> colors2;
    
        for (GLushort index : indices)
        {
            colors1.push_back(baseColors1[index]);
            colors2.push_back(baseColors2[index]);
        }
    
        GLBuffer color1Buffer;
        glBindBuffer(GL_ARRAY_BUFFER, color1Buffer);
        glBufferData(GL_ARRAY_BUFFER, colors1.size() * sizeof(GLColor), colors1.data(), GL_STATIC_DRAW);
        glVertexAttribPointer(color1Loc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, nullptr);
        glEnableVertexAttribArray(color1Loc);
    
        GLBuffer color2Buffer;
        glBindBuffer(GL_ARRAY_BUFFER, color2Buffer);
        glBufferData(GL_ARRAY_BUFFER, colors2.size() * sizeof(GLColor), colors2.data(), GL_STATIC_DRAW);
        glVertexAttribPointer(color2Loc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, nullptr);
        glEnableVertexAttribArray(color2Loc);
    
        // Draw a non-indexed quad with all vertex buffers. Should draw yellow to the entire window.
        drawQuad(program, "position", 0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::yellow);
    }
    
    // Creates a 2D texture, no other operations.
    TEST_P(SimpleOperationTest, CreateTexture2DNoData)
    {
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();
    }
    
    // Creates a 2D texture, no other operations.
    TEST_P(SimpleOperationTest, CreateTexture2DWithData)
    {
        std::vector<GLColor> colors(16 * 16, GLColor::red);
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
        ASSERT_GL_NO_ERROR();
    }
    
    // Creates a cube texture, no other operations.
    TEST_P(SimpleOperationTest, CreateTextureCubeNoData)
    {
        GLTexture texture;
        glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
        for (GLenum cubeFace : kCubeFaces)
        {
            glTexImage2D(cubeFace, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        }
        ASSERT_GL_NO_ERROR();
    }
    
    // Creates a cube texture, no other operations.
    TEST_P(SimpleOperationTest, CreateTextureCubeWithData)
    {
        std::vector<GLColor> colors(16 * 16, GLColor::red);
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
        for (GLenum cubeFace : kCubeFaces)
        {
            glTexImage2D(cubeFace, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
        }
        ASSERT_GL_NO_ERROR();
    }
    
    // Creates a program with a texture.
    TEST_P(SimpleOperationTest, LinkProgramWithTexture)
    {
        ASSERT_NE(0u, get2DTexturedQuadProgram());
        ASSERT_GL_NO_ERROR();
    }
    
    // Creates a program with a 2D texture and renders with it.
    TEST_P(SimpleOperationTest, DrawWith2DTexture)
    {
        std::array<GLColor, 4> colors = {
            {GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
    
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        draw2DTexturedQuad(0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        int w = getWindowWidth() - 2;
        int h = getWindowHeight() - 2;
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::blue);
        EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow);
    }
    
    template <typename T>
    void SimpleOperationTest::testDrawElementsLineLoopUsingClientSideMemory(GLenum indexType,
                                                                            int windowWidth,
                                                                            int windowHeight)
    {
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        glUseProgram(program);
    
        // We expect to draw a square with these 4 vertices with a drawArray call.
        std::vector<Vector3> vertices;
        CreatePixelCenterWindowCoords({{32, 96}, {32, 32}, {96, 32}, {96, 96}}, windowWidth,
                                      windowHeight, &vertices);
    
        // If we use these indices to draw however, we should be drawing an hourglass.
        std::vector<T> indices{3, 2, 1, 0};
    
        GLint positionLocation = glGetAttribLocation(program, "position");
        ASSERT_NE(-1, positionLocation);
    
        GLBuffer vertexBuffer;
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
                     GL_STATIC_DRAW);
    
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnableVertexAttribArray(positionLocation);
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawElements(GL_LINE_LOOP, 4, indexType, indices.data());
        glDisableVertexAttribArray(positionLocation);
    
        ASSERT_GL_NO_ERROR();
    
        int quarterWidth  = windowWidth / 4;
        int quarterHeight = windowHeight / 4;
    
        // Bottom left
        EXPECT_PIXEL_COLOR_EQ(quarterWidth, quarterHeight, GLColor::green);
    
        // Top left
        EXPECT_PIXEL_COLOR_EQ(quarterWidth, (quarterHeight * 3), GLColor::green);
    
        // Top right
        EXPECT_PIXEL_COLOR_EQ((quarterWidth * 3), (quarterHeight * 3) - 1, GLColor::green);
    
        // Verify line is closed between the 2 last vertices
        EXPECT_PIXEL_COLOR_EQ((quarterWidth * 2), quarterHeight, GLColor::green);
    }
    
    // Draw a line loop using a drawElement call and client side memory.
    TEST_P(SimpleOperationTest, DrawElementsLineLoopUsingUShortClientSideMemory)
    {
        testDrawElementsLineLoopUsingClientSideMemory<GLushort>(GL_UNSIGNED_SHORT, getWindowWidth(),
                                                                getWindowHeight());
    }
    
    // Draw a line loop using a drawElement call and client side memory.
    TEST_P(SimpleOperationTest, DrawElementsLineLoopUsingUByteClientSideMemory)
    {
        testDrawElementsLineLoopUsingClientSideMemory<GLubyte>(GL_UNSIGNED_BYTE, getWindowWidth(),
                                                               getWindowHeight());
    }
    
    // Creates a program with a cube texture and renders with it.
    TEST_P(SimpleOperationTest, DrawWithCubeTexture)
    {
        std::array<Vector2, 6 * 4> positions = {{
            {0, 1}, {1, 1}, {1, 2}, {0, 2} /* first face */,
            {1, 0}, {2, 0}, {2, 1}, {1, 1} /* second face */,
            {1, 1}, {2, 1}, {2, 2}, {1, 2} /* third face */,
            {1, 2}, {2, 2}, {2, 3}, {1, 3} /* fourth face */,
            {2, 1}, {3, 1}, {3, 2}, {2, 2} /* fifth face */,
            {3, 1}, {4, 1}, {4, 2}, {3, 2} /* sixth face */,
        }};
    
        const float w4 = 1.0f / 4.0f;
        const float h3 = 1.0f / 3.0f;
    
        // This draws a "T" shape based on the four faces of the cube. The window is divided into four
        // tiles horizontally and three tiles vertically (hence the w4 and h3 variable naming).
        for (Vector2 &pos : positions)
        {
            pos.data()[0] = pos.data()[0] * w4 * 2.0f - 1.0f;
            pos.data()[1] = pos.data()[1] * h3 * 2.0f - 1.0f;
        }
    
        const Vector3 posX(1, 0, 0);
        const Vector3 negX(-1, 0, 0);
        const Vector3 posY(0, 1, 0);
        const Vector3 negY(0, -1, 0);
        const Vector3 posZ(0, 0, 1);
        const Vector3 negZ(0, 0, -1);
    
        std::array<Vector3, 6 * 4> coords = {{
            posX, posX, posX, posX /* first face */, negX, negX, negX, negX /* second face */,
            posY, posY, posY, posY /* third face */, negY, negY, negY, negY /* fourth face */,
            posZ, posZ, posZ, posZ /* fifth face */, negZ, negZ, negZ, negZ /* sixth face */,
        }};
    
        const std::array<std::array<GLColor, 4>, 6> colors = {{
            {GLColor::red, GLColor::red, GLColor::red, GLColor::red},
            {GLColor::green, GLColor::green, GLColor::green, GLColor::green},
            {GLColor::blue, GLColor::blue, GLColor::blue, GLColor::blue},
            {GLColor::yellow, GLColor::yellow, GLColor::yellow, GLColor::yellow},
            {GLColor::cyan, GLColor::cyan, GLColor::cyan, GLColor::cyan},
            {GLColor::magenta, GLColor::magenta, GLColor::magenta, GLColor::magenta},
        }};
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
    
        for (size_t faceIndex = 0; faceIndex < kCubeFaces.size(); ++faceIndex)
        {
            glTexImage2D(kCubeFaces[faceIndex], 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         colors[faceIndex].data());
        }
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        constexpr char kVertexShader[] = R"(attribute vec2 pos;
    attribute vec3 coord;
    varying vec3 texCoord;
    void main()
    {
        gl_Position = vec4(pos, 0, 1);
        texCoord = coord;
    })";
    
        constexpr char kFragmentShader[] = R"(precision mediump float;
    varying vec3 texCoord;
    uniform samplerCube tex;
    void main()
    {
        gl_FragColor = textureCube(tex, texCoord);
    })";
    
        ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader);
        GLint samplerLoc = glGetUniformLocation(program, "tex");
        ASSERT_EQ(samplerLoc, 0);
    
        glUseProgram(program);
    
        GLint posLoc = glGetAttribLocation(program, "pos");
        ASSERT_NE(-1, posLoc);
    
        GLint coordLoc = glGetAttribLocation(program, "coord");
        ASSERT_NE(-1, coordLoc);
    
        GLBuffer posBuffer;
        glBindBuffer(GL_ARRAY_BUFFER, posBuffer);
        glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(Vector2), positions.data(),
                     GL_STATIC_DRAW);
        glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnableVertexAttribArray(posLoc);
    
        GLBuffer coordBuffer;
        glBindBuffer(GL_ARRAY_BUFFER, coordBuffer);
        glBufferData(GL_ARRAY_BUFFER, coords.size() * sizeof(Vector3), coords.data(), GL_STATIC_DRAW);
        glVertexAttribPointer(coordLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnableVertexAttribArray(coordLoc);
    
        auto quadIndices = GetQuadIndices();
        std::array<GLushort, 6 * 6> kElementsData;
        for (GLushort quadIndex = 0; quadIndex < 6; ++quadIndex)
        {
            for (GLushort elementIndex = 0; elementIndex < 6; ++elementIndex)
            {
                kElementsData[quadIndex * 6 + elementIndex] = quadIndices[elementIndex] + 4 * quadIndex;
            }
        }
    
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
    
        GLBuffer elementBuffer;
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, kElementsData.size() * sizeof(GLushort),
                     kElementsData.data(), GL_STATIC_DRAW);
        glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(kElementsData.size()), GL_UNSIGNED_SHORT,
                       nullptr);
        ASSERT_GL_NO_ERROR();
    
        for (int faceIndex = 0; faceIndex < 6; ++faceIndex)
        {
            int index      = faceIndex * 4;
            Vector2 center = (positions[index] + positions[index + 1] + positions[index + 2] +
                              positions[index + 3]) /
                             4.0f;
            center *= 0.5f;
            center += Vector2(0.5f);
            center *= Vector2(getWindowWidth(), getWindowHeight());
            EXPECT_PIXEL_COLOR_EQ(static_cast<GLint>(center.x()), static_cast<GLint>(center.y()),
                                  colors[faceIndex][0]);
        }
    }
    
    // Tests rendering to a user framebuffer.
    TEST_P(SimpleOperationTest, RenderToTexture)
    {
        constexpr int kSize = 16;
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        glViewport(0, 0, kSize, kSize);
    
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        drawQuad(program, "position", 0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Create a simple basic Renderbuffer.
    TEST_P(SimpleOperationTest, CreateRenderbuffer)
    {
        GLRenderbuffer renderbuffer;
        glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 16, 16);
        ASSERT_GL_NO_ERROR();
    }
    
    // Render to a simple color Renderbuffer.
    TEST_P(SimpleOperationTest, RenderbufferAttachment)
    {
        constexpr int kSize = 16;
    
        GLRenderbuffer renderbuffer;
        glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSize, kSize);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        glViewport(0, 0, kSize, kSize);
    
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        drawQuad(program, "position", 0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Tests that using desktop GL_QUADS/GL_POLYGONS enums generate the correct error.
    TEST_P(SimpleOperationTest, PrimitiveModeNegativeTest)
    {
        // Draw a correct quad.
        ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
        glUseProgram(program);
    
        GLint positionLocation = glGetAttribLocation(program, "position");
        ASSERT_NE(-1, positionLocation);
    
        setupQuadVertexBuffer(0.5f, 1.0f);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
        glEnableVertexAttribArray(positionLocation);
    
        // Tests that TRIANGLES works.
        glDrawArrays(GL_TRIANGLES, 0, 6);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        // Tests that specific invalid enums don't work.
        glDrawArrays(static_cast<GLenum>(7), 0, 6);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    
        glDrawArrays(static_cast<GLenum>(8), 0, 6);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    
        glDrawArrays(static_cast<GLenum>(9), 0, 6);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    }
    
    // Use this to select which configurations (e.g. which renderer, which GLES major version) these
    // tests should be run against.
    ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(SimpleOperationTest, WithNoCommandGraph(ES2_VULKAN()));
    
    }  // namespace