Edit

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

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2018-12-29 10:29:33
    Hash : ba319ba3
    Message : Re-land "Load entry points dynamically in tests and samples." Fixes the Android/ChromeOS/Fuchsia builds by using consistent EGL headers. This CL adds a dynamic loader generator based on XML files. It also refactors the entry point generation script to move the XML parsing into a helper class. Additionally this includes a new GLES 1.0 base header. The new header allows for function pointer types and hiding prototypes. All tests and samples now load ANGLE dynamically. In the future this will be extended to load entry points from the driver directly when possible. This will allow us to perform more accurate A/B testing. The new build configuration leads to some tests having more warnings applied. The CL includes fixes for the new warnings. Bug: angleproject:2995 Change-Id: I5a8772f41a0f89570b3736b785f44b7de1539b57 Reviewed-on: https://chromium-review.googlesource.com/c/1392382 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Jamie Madill <jmadill@chromium.org>

  • src/tests/gl_tests/MultiviewDrawTest.cpp
  • //
    // Copyright 2017 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.
    //
    // Multiview draw tests:
    // Test issuing multiview Draw* commands.
    //
    
    #include "platform/WorkaroundsD3D.h"
    #include "test_utils/MultiviewTest.h"
    #include "test_utils/gl_raii.h"
    
    using namespace angle;
    
    namespace
    {
    
    std::vector<Vector2> ConvertPixelCoordinatesToClipSpace(const std::vector<Vector2I> &pixels,
                                                            int width,
                                                            int height)
    {
        std::vector<Vector2> result(pixels.size());
        for (size_t i = 0; i < pixels.size(); ++i)
        {
            const auto &pixel          = pixels[i];
            float pixelCenterRelativeX = (static_cast<float>(pixel.x()) + .5f) / width;
            float pixelCenterRelativeY = (static_cast<float>(pixel.y()) + .5f) / height;
            float xInClipSpace         = 2.f * pixelCenterRelativeX - 1.f;
            float yInClipSpace         = 2.f * pixelCenterRelativeY - 1.f;
            result[i]                  = Vector2(xInClipSpace, yInClipSpace);
        }
        return result;
    }
    }  // namespace
    
    struct MultiviewRenderTestParams final : public MultiviewImplementationParams
    {
        MultiviewRenderTestParams(GLenum multiviewLayout,
                                  int samples,
                                  const MultiviewImplementationParams &implementationParams)
            : MultiviewImplementationParams(implementationParams),
              mMultiviewLayout(multiviewLayout),
              mSamples(samples)
        {
            EXPECT_TRUE(multiviewLayout == GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE ||
                        multiviewLayout == GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE);
        }
        GLenum mMultiviewLayout;
        int mSamples;
    };
    
    std::ostream &operator<<(std::ostream &os, const MultiviewRenderTestParams &params)
    {
        const MultiviewImplementationParams &base =
            static_cast<const MultiviewImplementationParams &>(params);
        os << base;
        switch (params.mMultiviewLayout)
        {
            case GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE:
                os << "_layered";
                break;
            case GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE:
                os << "_side_by_side";
                break;
            default:
                os << "_error";
                break;
        }
        if (params.mSamples > 0)
        {
            os << "_samples_" << params.mSamples;
        }
        return os;
    }
    
    class MultiviewFramebufferTestBase : public MultiviewTestBase
    {
      protected:
        MultiviewFramebufferTestBase(const PlatformParameters &params,
                                     GLenum multiviewLayout,
                                     int samples)
            : MultiviewTestBase(params),
              mViewWidth(0),
              mViewHeight(0),
              mNumViews(0),
              mColorTexture(0u),
              mDepthTexture(0u),
              mDrawFramebuffer(0u),
              mMultiviewLayout(multiviewLayout),
              mSamples(samples),
              mResolveTexture(0u)
        {}
    
        void FramebufferTestSetUp() { MultiviewTestBase::MultiviewTestBaseSetUp(); }
    
        void FramebufferTestTearDown()
        {
            freeFBOs();
            MultiviewTestBase::MultiviewTestBaseTearDown();
        }
    
        void updateFBOs(int viewWidth, int height, int numViews, int numLayers, int baseViewIndex)
        {
            ASSERT_TRUE(numViews + baseViewIndex <= numLayers);
    
            freeFBOs();
    
            mViewWidth  = viewWidth;
            mViewHeight = height;
            mNumViews   = numViews;
    
            glGenTextures(1, &mColorTexture);
            glGenTextures(1, &mDepthTexture);
    
            CreateMultiviewBackingTextures(mMultiviewLayout, mSamples, viewWidth, height, numLayers,
                                           mColorTexture, mDepthTexture, 0u);
    
            glGenFramebuffers(1, &mDrawFramebuffer);
    
            // Create draw framebuffer to be used for multiview rendering.
            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDrawFramebuffer);
            AttachMultiviewTextures(GL_DRAW_FRAMEBUFFER, mMultiviewLayout, viewWidth, numViews,
                                    baseViewIndex, mColorTexture, mDepthTexture, 0u);
    
            ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER));
    
            // Create read framebuffer to be used to retrieve the pixel information for testing
            // purposes.
            switch (mMultiviewLayout)
            {
                case GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE:
                    mReadFramebuffer.resize(1);
                    glGenFramebuffers(1, mReadFramebuffer.data());
                    glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer[0]);
                    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                           mColorTexture, 0);
                    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE,
                                     glCheckFramebufferStatus(GL_READ_FRAMEBUFFER));
                    break;
                case GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE:
                    mReadFramebuffer.resize(numLayers);
                    glGenFramebuffers(static_cast<GLsizei>(mReadFramebuffer.size()),
                                      mReadFramebuffer.data());
                    for (int i = 0; i < numLayers; ++i)
                    {
                        glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer[i]);
                        glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                  mColorTexture, 0, i);
                        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE,
                                         glCheckFramebufferStatus(GL_READ_FRAMEBUFFER));
                    }
                    break;
                default:
                    ASSERT_TRUE(false);
            }
    
            // Clear the buffers.
            glViewport(0, 0, viewWidth, height);
            if (mMultiviewLayout == GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE)
            {
                // Enable the scissor test only for side-by-side framebuffers.
                glEnable(GL_SCISSOR_TEST);
                glScissor(0, 0, viewWidth, height);
            }
        }
    
        void updateFBOs(int viewWidth, int height, int numViews)
        {
            updateFBOs(viewWidth, height, numViews, numViews, 0);
        }
    
        void bindMemberDrawFramebuffer() { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDrawFramebuffer); }
    
        // In case we have a multisampled framebuffer, creates and binds a resolve framebuffer as the
        // draw framebuffer, and resolves the read framebuffer to it.
        void resolveMultisampledFBO()
        {
            if (mSamples == 0)
            {
                return;
            }
            int numLayers = mReadFramebuffer.size();
            if (mResolveFramebuffer.empty())
            {
                ASSERT_TRUE(mResolveTexture == 0u);
                glGenTextures(1, &mResolveTexture);
                CreateMultiviewBackingTextures(mMultiviewLayout, 0, mViewWidth, mViewHeight, numLayers,
                                               mResolveTexture, 0u, 0u);
    
                mResolveFramebuffer.resize(numLayers);
                glGenFramebuffers(static_cast<GLsizei>(mResolveFramebuffer.size()),
                                  mResolveFramebuffer.data());
                for (int i = 0; i < numLayers; ++i)
                {
                    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mResolveFramebuffer[i]);
                    glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                              mResolveTexture, 0, i);
                    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE,
                                     glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER));
                }
            }
            for (int i = 0; i < numLayers; ++i)
            {
                glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer[i]);
                glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mResolveFramebuffer[i]);
                glBlitFramebuffer(0, 0, mViewWidth, mViewHeight, 0, 0, mViewWidth, mViewHeight,
                                  GL_COLOR_BUFFER_BIT, GL_NEAREST);
            }
        }
    
        GLColor GetViewColor(int x, int y, int view)
        {
            switch (mMultiviewLayout)
            {
                case GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE:
                    glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer[0]);
                    return ReadColor(view * mViewWidth + x, y);
                case GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE:
                    EXPECT_TRUE(static_cast<size_t>(view) < mReadFramebuffer.size());
                    if (mSamples > 0)
                    {
                        EXPECT_TRUE(static_cast<size_t>(view) < mResolveFramebuffer.size());
                        glBindFramebuffer(GL_READ_FRAMEBUFFER, mResolveFramebuffer[view]);
                    }
                    else
                    {
                        glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer[view]);
                    }
                    return ReadColor(x, y);
                default:
                    EXPECT_TRUE(false);
            }
            return GLColor(0, 0, 0, 0);
        }
    
        bool isMultisampled() { return mSamples > 0; }
    
        int mViewWidth;
        int mViewHeight;
        int mNumViews;
    
        GLuint mColorTexture;
        GLuint mDepthTexture;
    
      private:
        GLuint mDrawFramebuffer;
        std::vector<GLuint> mReadFramebuffer;
        GLenum mMultiviewLayout;
        int mSamples;
    
        // For reading back multisampled framebuffer.
        std::vector<GLuint> mResolveFramebuffer;
        GLuint mResolveTexture;
    
        void freeFBOs()
        {
            if (mDrawFramebuffer)
            {
                glDeleteFramebuffers(1, &mDrawFramebuffer);
                mDrawFramebuffer = 0;
            }
            if (!mReadFramebuffer.empty())
            {
                GLsizei framebufferCount = static_cast<GLsizei>(mReadFramebuffer.size());
                glDeleteFramebuffers(framebufferCount, mReadFramebuffer.data());
                mReadFramebuffer.clear();
            }
            if (!mResolveFramebuffer.empty())
            {
                GLsizei framebufferCount = static_cast<GLsizei>(mResolveFramebuffer.size());
                glDeleteFramebuffers(framebufferCount, mResolveFramebuffer.data());
                mResolveFramebuffer.clear();
            }
            if (mDepthTexture)
            {
                glDeleteTextures(1, &mDepthTexture);
                mDepthTexture = 0;
            }
            if (mColorTexture)
            {
                glDeleteTextures(1, &mColorTexture);
                mColorTexture = 0;
            }
            if (mResolveTexture)
            {
                glDeleteTextures(1, &mResolveTexture);
                mResolveTexture = 0;
            }
        }
    };
    
    class MultiviewRenderTest : public MultiviewFramebufferTestBase,
                                public ::testing::TestWithParam<MultiviewRenderTestParams>
    {
      protected:
        MultiviewRenderTest()
            : MultiviewFramebufferTestBase(GetParam(), GetParam().mMultiviewLayout, GetParam().mSamples)
        {}
        void SetUp() override { MultiviewFramebufferTestBase::FramebufferTestSetUp(); }
        void TearDown() override { MultiviewFramebufferTestBase::FramebufferTestTearDown(); }
    
        void overrideWorkaroundsD3D(WorkaroundsD3D *workarounds) override
        {
            workarounds->selectViewInGeometryShader = GetParam().mForceUseGeometryShaderOnD3D;
        }
    };
    
    constexpr char kDualViewVSSource[] = R"(#version 300 es
    #extension GL_OVR_multiview : require
    layout(num_views = 2) in;
    in vec4 vPosition;
    void main()
    {
       gl_Position.x = (gl_ViewID_OVR == 0u ? vPosition.x * 0.5 + 0.5 : vPosition.x * 0.5 - 0.5);
       gl_Position.yzw = vPosition.yzw;
    })";
    
    constexpr char kDualViewFSSource[] = R"(#version 300 es
    #extension GL_OVR_multiview : require
    precision mediump float;
    out vec4 col;
    void main()
    {
       col = vec4(0,1,0,1);
    })";
    
    class MultiviewRenderDualViewTest : public MultiviewRenderTest
    {
      protected:
        MultiviewRenderDualViewTest() : mProgram(0u) {}
    
        void SetUp() override
        {
            MultiviewRenderTest::SetUp();
    
            if (!requestMultiviewExtension(isMultisampled()))
            {
                return;
            }
    
            updateFBOs(2, 1, 2);
            mProgram = CompileProgram(kDualViewVSSource, kDualViewFSSource);
            ASSERT_NE(mProgram, 0u);
            glUseProgram(mProgram);
            ASSERT_GL_NO_ERROR();
        }
    
        void TearDown() override
        {
            if (mProgram != 0u)
            {
                glDeleteProgram(mProgram);
                mProgram = 0u;
            }
    
            MultiviewRenderTest::TearDown();
        }
    
        void checkOutput()
        {
            resolveMultisampledFBO();
            EXPECT_EQ(GLColor::transparentBlack, GetViewColor(0, 0, 0));
            EXPECT_EQ(GLColor::green, GetViewColor(1, 0, 0));
            EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
            EXPECT_EQ(GLColor::transparentBlack, GetViewColor(1, 0, 1));
        }
    
        GLuint mProgram;
    };
    
    // Base class for tests that care mostly about draw call validity and not rendering results.
    class MultiviewDrawValidationTest : public MultiviewTest
    {
      protected:
        MultiviewDrawValidationTest() : MultiviewTest() {}
    
        void initOnePixelColorTexture2D(GLuint texId)
        {
            glBindTexture(GL_TEXTURE_2D, texId);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        }
    
        // This initializes a simple VAO with a valid vertex buffer and index buffer with three
        // vertices.
        void initVAO(GLuint vao, GLuint vertexBuffer, GLuint indexBuffer)
        {
            glBindVertexArray(vao);
    
            const float kVertexData[3] = {0.0f};
            glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
            glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3u, &kVertexData[0], GL_STATIC_DRAW);
    
            const unsigned int kIndices[3] = {0u, 1u, 2u};
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * 3, &kIndices[0],
                         GL_STATIC_DRAW);
            ASSERT_GL_NO_ERROR();
        }
    };
    
    class MultiviewOcclusionQueryTest : public MultiviewRenderTest
    {
      protected:
        MultiviewOcclusionQueryTest() {}
    
        bool requestOcclusionQueryExtension()
        {
            if (extensionRequestable("GL_EXT_occlusion_query_boolean"))
            {
                glRequestExtensionANGLE("GL_EXT_occlusion_query_boolean");
            }
    
            if (!extensionEnabled("GL_EXT_occlusion_query_boolean"))
            {
                std::cout << "Test skipped due to missing GL_EXT_occlusion_query_boolean." << std::endl;
                return false;
            }
            return true;
        }
    
        GLuint drawAndRetrieveOcclusionQueryResult(GLuint program)
        {
            GLQueryEXT query;
            glBeginQueryEXT(GL_ANY_SAMPLES_PASSED, query);
            drawQuad(program, "vPosition", 0.0f, 1.0f, true);
            glEndQueryEXT(GL_ANY_SAMPLES_PASSED);
    
            GLuint result = GL_TRUE;
            glGetQueryObjectuivEXT(query, GL_QUERY_RESULT, &result);
            return result;
        }
    };
    
    class MultiviewProgramGenerationTest : public MultiviewTest
    {
      protected:
        MultiviewProgramGenerationTest() {}
    };
    
    class MultiviewRenderPrimitiveTest : public MultiviewRenderTest
    {
      protected:
        MultiviewRenderPrimitiveTest() : mVBO(0u) {}
    
        void SetUp() override
        {
            MultiviewRenderTest::SetUp();
            glGenBuffers(1, &mVBO);
        }
    
        void TearDown() override
        {
            if (mVBO)
            {
                glDeleteBuffers(1, &mVBO);
                mVBO = 0u;
            }
            MultiviewRenderTest::TearDown();
        }
    
        void setupGeometry(const std::vector<Vector2> &vertexData)
        {
            glBindBuffer(GL_ARRAY_BUFFER, mVBO);
            glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(Vector2), vertexData.data(),
                         GL_STATIC_DRAW);
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
        }
    
        void checkGreenChannel(const GLubyte expectedGreenChannelData[])
        {
            for (int view = 0; view < mNumViews; ++view)
            {
                for (int w = 0; w < mViewWidth; ++w)
                {
                    for (int h = 0; h < mViewHeight; ++h)
                    {
                        size_t flatIndex =
                            static_cast<size_t>(view * mViewWidth * mViewHeight + mViewWidth * h + w);
                        EXPECT_EQ(GLColor(0, expectedGreenChannelData[flatIndex], 0,
                                          expectedGreenChannelData[flatIndex]),
                                  GetViewColor(w, h, view));
                    }
                }
            }
        }
        GLuint mVBO;
    };
    
    class MultiviewSideBySideRenderTest : public MultiviewFramebufferTestBase,
                                          public ::testing::TestWithParam<MultiviewImplementationParams>
    {
      protected:
        MultiviewSideBySideRenderTest()
            : MultiviewFramebufferTestBase(GetParam(), GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE, 0)
        {}
    
        void SetUp() final { MultiviewFramebufferTestBase::FramebufferTestSetUp(); }
        void TearDown() final { MultiviewFramebufferTestBase::FramebufferTestTearDown(); }
        void overrideWorkaroundsD3D(WorkaroundsD3D *workarounds) final
        {
            workarounds->selectViewInGeometryShader = GetParam().mForceUseGeometryShaderOnD3D;
        }
    };
    
    class MultiviewLayeredRenderTest : public MultiviewFramebufferTestBase,
                                       public ::testing::TestWithParam<MultiviewImplementationParams>
    {
      protected:
        MultiviewLayeredRenderTest()
            : MultiviewFramebufferTestBase(GetParam(), GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE, 0)
        {}
        void SetUp() final { MultiviewFramebufferTestBase::FramebufferTestSetUp(); }
        void TearDown() final { MultiviewFramebufferTestBase::FramebufferTestTearDown(); }
        void overrideWorkaroundsD3D(WorkaroundsD3D *workarounds) final
        {
            workarounds->selectViewInGeometryShader = GetParam().mForceUseGeometryShaderOnD3D;
        }
    };
    
    // The test verifies that glDraw*Indirect:
    // 1) generates an INVALID_OPERATION error if the number of views in the draw framebuffer is greater
    // than 1.
    // 2) does not generate any error if the draw framebuffer has exactly 1 view.
    TEST_P(MultiviewDrawValidationTest, IndirectDraw)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension());
    
        const GLint viewportOffsets[4] = {0, 0, 2, 0};
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "void main()\n"
            "{}\n";
    
        GLVertexArray vao;
        GLBuffer vertexBuffer;
        GLBuffer indexBuffer;
        initVAO(vao, vertexBuffer, indexBuffer);
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    
        GLBuffer commandBuffer;
        glBindBuffer(GL_DRAW_INDIRECT_BUFFER, commandBuffer);
        const GLuint commandData[] = {1u, 1u, 0u, 0u, 0u};
        glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(GLuint) * 5u, &commandData[0], GL_STATIC_DRAW);
        ASSERT_GL_NO_ERROR();
    
        GLTexture tex2D;
        initOnePixelColorTexture2D(tex2D);
    
        // Check for a GL_INVALID_OPERATION error with the framebuffer having 2 views.
        {
            constexpr char kVS[] =
                "#version 300 es\n"
                "#extension GL_OVR_multiview : require\n"
                "layout(num_views = 2) in;\n"
                "void main()\n"
                "{}\n";
            ANGLE_GL_PROGRAM(program, kVS, kFS);
            glUseProgram(program);
    
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         2, &viewportOffsets[0]);
    
            glDrawArraysIndirect(GL_TRIANGLES, nullptr);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
            glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        }
    
        // Check that no errors are generated if the number of views is 1.
        {
            constexpr char kVS[] =
                "#version 300 es\n"
                "#extension GL_OVR_multiview : require\n"
                "layout(num_views = 1) in;\n"
                "void main()\n"
                "{}\n";
            ANGLE_GL_PROGRAM(program, kVS, kFS);
            glUseProgram(program);
    
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         1, &viewportOffsets[0]);
    
            glDrawArraysIndirect(GL_TRIANGLES, nullptr);
            EXPECT_GL_NO_ERROR();
    
            glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr);
            EXPECT_GL_NO_ERROR();
        }
    }
    
    // The test verifies that glDraw*:
    // 1) generates an INVALID_OPERATION error if the number of views in the active draw framebuffer and
    // program differs.
    // 2) does not generate any error if the number of views is the same.
    TEST_P(MultiviewDrawValidationTest, NumViewsMismatch)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension());
    
        const GLint viewportOffsets[4] = {0, 0, 2, 0};
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "void main()\n"
            "{}\n";
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "void main()\n"
            "{}\n";
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        glUseProgram(program);
    
        GLVertexArray vao;
        GLBuffer vertexBuffer;
        GLBuffer indexBuffer;
        initVAO(vao, vertexBuffer, indexBuffer);
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    
        GLTexture tex2D;
        initOnePixelColorTexture2D(tex2D);
    
        // Check for a GL_INVALID_OPERATION error with the framebuffer and program having different
        // number of views.
        {
            // The framebuffer has only 1 view.
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         1, &viewportOffsets[0]);
    
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
            glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        }
    
        // Check that no errors are generated if the number of views in both program and draw
        // framebuffer matches.
        {
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         2, &viewportOffsets[0]);
    
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_NO_ERROR();
    
            glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);
            EXPECT_GL_NO_ERROR();
        }
    }
    
    // The test verifies that glDraw* generates an INVALID_OPERATION error if the program does not use
    // the multiview extension, but the active draw framebuffer has more than one view.
    TEST_P(MultiviewDrawValidationTest, NumViewsMismatchForNonMultiviewProgram)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "void main()\n"
            "{}\n";
        constexpr char kFS[] =
            "#version 300 es\n"
            "precision mediump float;\n"
            "void main()\n"
            "{}\n";
        ANGLE_GL_PROGRAM(programNoMultiview, kVS, kFS);
        glUseProgram(programNoMultiview);
    
        GLVertexArray vao;
        GLBuffer vertexBuffer;
        GLBuffer indexBuffer;
        initVAO(vao, vertexBuffer, indexBuffer);
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    
        GLTexture tex2D;
        initOnePixelColorTexture2D(tex2D);
    
        const GLint viewportOffsets[4] = {0, 0, 2, 0};
        glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 2,
                                                     &viewportOffsets[0]);
    
        glDrawArrays(GL_TRIANGLES, 0, 3);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    }
    
    // The test verifies that glDraw*:
    // 1) generates an INVALID_OPERATION error if the number of views in the active draw framebuffer is
    // greater than 1 and there is an active not paused transform feedback object.
    // 2) does not generate any error if the number of views in the draw framebuffer is 1.
    TEST_P(MultiviewDrawValidationTest, ActiveTransformFeedback)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension());
    
        const GLint viewportOffsets[4] = {0, 0, 2, 0};
    
        constexpr char kVS[] = R"(#version 300 es
    out float tfVarying;
    void main()
    {
        tfVarying = 1.0;
    })";
    
        constexpr char kFS[] = R"(#version 300 es
    precision mediump float;
    void main()
    {})";
    
        std::vector<std::string> tfVaryings;
        tfVaryings.emplace_back("tfVarying");
        ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(singleViewProgram, kVS, kFS, tfVaryings,
                                            GL_SEPARATE_ATTRIBS);
    
        std::vector<std::string> dualViewTFVaryings;
        dualViewTFVaryings.emplace_back("gl_Position");
        ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(dualViewProgram, kDualViewVSSource, kDualViewFSSource,
                                            dualViewTFVaryings, GL_SEPARATE_ATTRIBS);
    
        GLVertexArray vao;
        GLBuffer vertexBuffer;
        GLBuffer indexBuffer;
        initVAO(vao, vertexBuffer, indexBuffer);
    
        GLBuffer tbo;
        glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tbo);
        glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(float) * 16u, nullptr, GL_STATIC_DRAW);
    
        GLTransformFeedback transformFeedback;
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedback);
    
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);
    
        glUseProgram(dualViewProgram);
        glBeginTransformFeedback(GL_TRIANGLES);
        ASSERT_GL_NO_ERROR();
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    
        GLTexture tex2D;
        initOnePixelColorTexture2D(tex2D);
    
        // Check that drawArrays generates an error when there is an active transform feedback object
        // and the number of views in the draw framebuffer is greater than 1.
        {
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         2, &viewportOffsets[0]);
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        }
    
        glEndTransformFeedback();
    
        // Ending transform feedback should allow the draw to succeed.
        {
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_NO_ERROR();
        }
    
        // A paused transform feedback should not trigger an error.
        glBeginTransformFeedback(GL_TRIANGLES);
        glPauseTransformFeedback();
        ASSERT_GL_NO_ERROR();
    
        glDrawArrays(GL_TRIANGLES, 0, 3);
        ASSERT_GL_NO_ERROR();
    
        // Unbind transform feedback - should succeed.
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        ASSERT_GL_NO_ERROR();
    
        // Rebind paused transform feedback - should succeed.
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedback);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        ASSERT_GL_NO_ERROR();
    
        glResumeTransformFeedback();
        glEndTransformFeedback();
    
        glUseProgram(singleViewProgram);
        glBeginTransformFeedback(GL_TRIANGLES);
        ASSERT_GL_NO_ERROR();
    
        // Check that drawArrays does not generate an error when the number of views in the draw
        // framebuffer is 1.
        {
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         1, &viewportOffsets[0]);
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_NO_ERROR();
        }
    
        glEndTransformFeedback();
    }
    
    // The test verifies that glDraw*:
    // 1) generates an INVALID_OPERATION error if the number of views in the active draw framebuffer is
    // greater than 1 and there is an active query for target GL_TIME_ELAPSED_EXT.
    // 2) does not generate any error if the number of views in the draw framebuffer is 1.
    TEST_P(MultiviewDrawValidationTest, ActiveTimeElapsedQuery)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension());
        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_EXT_disjoint_timer_query"));
    
        ANGLE_GL_PROGRAM(dualViewProgram, kDualViewVSSource, kDualViewFSSource);
    
        const GLint viewportOffsets[4] = {0, 0, 2, 0};
        constexpr char kVS[] =
            "#version 300 es\n"
            "void main()\n"
            "{}\n";
        constexpr char kFS[] =
            "#version 300 es\n"
            "precision mediump float;\n"
            "void main()\n"
            "{}\n";
        ANGLE_GL_PROGRAM(singleViewProgram, kVS, kFS);
        glUseProgram(singleViewProgram);
    
        GLVertexArray vao;
        GLBuffer vertexBuffer;
        GLBuffer indexBuffer;
        initVAO(vao, vertexBuffer, indexBuffer);
    
        GLuint query = 0u;
        glGenQueriesEXT(1, &query);
        glBeginQueryEXT(GL_TIME_ELAPSED_EXT, query);
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    
        GLTexture tex2D;
        initOnePixelColorTexture2D(tex2D);
    
        // Check first case.
        {
            glUseProgram(dualViewProgram);
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         2, &viewportOffsets[0]);
            glClear(GL_COLOR_BUFFER_BIT);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        }
    
        // Check second case.
        {
            glUseProgram(singleViewProgram);
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         1, &viewportOffsets[0]);
            glClear(GL_COLOR_BUFFER_BIT);
            EXPECT_GL_NO_ERROR();
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_NO_ERROR();
        }
    
        glEndQueryEXT(GL_TIME_ELAPSED_EXT);
        glDeleteQueries(1, &query);
    
        // Check starting a query after a successful draw.
        {
            glUseProgram(dualViewProgram);
            glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0,
                                                         2, &viewportOffsets[0]);
            glClear(GL_COLOR_BUFFER_BIT);
            EXPECT_GL_NO_ERROR();
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_NO_ERROR();
    
            glGenQueriesEXT(1, &query);
            glBeginQueryEXT(GL_TIME_ELAPSED_EXT, query);
    
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
            glEndQueryEXT(GL_TIME_ELAPSED_EXT);
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_NO_ERROR();
    
            glDeleteQueries(1, &query);
        }
    }
    
    // The test checks that glDrawArrays can be used to render into two views.
    TEST_P(MultiviewRenderDualViewTest, DrawArrays)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled()));
    
        drawQuad(mProgram, "vPosition", 0.0f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        checkOutput();
    }
    
    // The test checks that glDrawElements can be used to render into two views.
    TEST_P(MultiviewRenderDualViewTest, DrawElements)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled()));
    
        drawIndexedQuad(mProgram, "vPosition", 0.0f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        checkOutput();
    }
    
    // The test checks that glDrawRangeElements can be used to render into two views.
    TEST_P(MultiviewRenderDualViewTest, DrawRangeElements)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled()));
    
        drawIndexedQuad(mProgram, "vPosition", 0.0f, 1.0f, true, true);
        ASSERT_GL_NO_ERROR();
    
        checkOutput();
    }
    
    // The test checks that glDrawArrays can be used to render into four views.
    TEST_P(MultiviewRenderTest, DrawArraysFourViews)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled()));
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 4) in;\n"
            "in vec4 vPosition;\n"
            "void main()\n"
            "{\n"
            "   if (gl_ViewID_OVR == 0u) {\n"
            "       gl_Position.x = vPosition.x*0.25 - 0.75;\n"
            "   } else if (gl_ViewID_OVR == 1u) {\n"
            "       gl_Position.x = vPosition.x*0.25 - 0.25;\n"
            "   } else if (gl_ViewID_OVR == 2u) {\n"
            "       gl_Position.x = vPosition.x*0.25 + 0.25;\n"
            "   } else {\n"
            "       gl_Position.x = vPosition.x*0.25 + 0.75;\n"
            "   }"
            "   gl_Position.yzw = vPosition.yzw;\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(0,1,0,1);\n"
            "}\n";
    
        updateFBOs(4, 1, 4);
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        drawQuad(program, "vPosition", 0.0f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        resolveMultisampledFBO();
        for (int i = 0; i < 4; ++i)
        {
            for (int j = 0; j < 4; ++j)
            {
                if (i == j)
                {
                    EXPECT_EQ(GLColor::green, GetViewColor(j, 0, i));
                }
                else
                {
                    EXPECT_EQ(GLColor::transparentBlack, GetViewColor(j, 0, i));
                }
            }
        }
        EXPECT_GL_NO_ERROR();
    }
    
    // The test checks that glDrawArraysInstanced can be used to render into two views.
    TEST_P(MultiviewRenderTest, DrawArraysInstanced)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled()));
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "in vec4 vPosition;\n"
            "void main()\n"
            "{\n"
            "       vec4 p = vPosition;\n"
            "       if (gl_InstanceID == 1){\n"
            "               p.y = p.y * 0.5 + 0.5;\n"
            "       } else {\n"
            "               p.y = p.y * 0.5 - 0.5;\n"
            "       }\n"
            "       gl_Position.x = (gl_ViewID_OVR == 0u ? p.x * 0.5 + 0.5 : p.x * 0.5 - 0.5);\n"
            "       gl_Position.yzw = p.yzw;\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(0,1,0,1);\n"
            "}\n";
    
        const int kViewWidth  = 2;
        const int kViewHeight = 2;
        const int kNumViews   = 2;
        updateFBOs(kViewWidth, kViewHeight, kNumViews);
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        drawQuadInstanced(program, "vPosition", 0.0f, 1.0f, true, 2u);
        ASSERT_GL_NO_ERROR();
    
        resolveMultisampledFBO();
    
        const GLubyte expectedGreenChannel[kNumViews][kViewHeight][kViewWidth] = {{{0, 255}, {0, 255}},
                                                                                  {{255, 0}, {255, 0}}};
    
        for (int view = 0; view < 2; ++view)
        {
            for (int y = 0; y < 2; ++y)
            {
                for (int x = 0; x < 2; ++x)
                {
                    EXPECT_EQ(GLColor(0, expectedGreenChannel[view][y][x], 0,
                                      expectedGreenChannel[view][y][x]),
                              GetViewColor(x, y, view));
                }
            }
        }
    }
    
    // The test verifies that the attribute divisor is correctly adjusted when drawing with a multi-view
    // program. The test draws 4 instances of a quad each of which covers a single pixel. The x and y
    // offset of each quad are passed as separate attributes which are indexed based on the
    // corresponding attribute divisors. A divisor of 1 is used for the y offset to have all quads
    // drawn vertically next to each other. A divisor of 3 is used for the x offset to have the last
    // quad offsetted by one pixel to the right. Note that the number of views is divisible by 1, but
    // not by 3.
    TEST_P(MultiviewRenderTest, AttribDivisor)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled()));
    
        // Looks like an incorrect D3D debug layer message is generated on Windows AMD and NVIDIA.
        // May be specific to Windows 7 / Windows Server 2008. http://anglebug.com/2778
        if (IsWindows() && IsD3D11())
        {
            ignoreD3D11SDKLayersWarnings();
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "in vec3 vPosition;\n"
            "in float offsetX;\n"
            "in float offsetY;\n"
            "void main()\n"
            "{\n"
            "       vec4 p = vec4(vPosition, 1.);\n"
            "       p.xy = p.xy * 0.25 - vec2(0.75) + vec2(offsetX, offsetY);\n"
            "       gl_Position.x = (gl_ViewID_OVR == 0u ? p.x : p.x + 1.0);\n"
            "       gl_Position.yzw = p.yzw;\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(0,1,0,1);\n"
            "}\n";
    
        const int kViewWidth  = 4;
        const int kViewHeight = 4;
        const int kNumViews   = 2;
        updateFBOs(kViewWidth, kViewHeight, kNumViews);
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        GLBuffer xOffsetVBO;
        glBindBuffer(GL_ARRAY_BUFFER, xOffsetVBO);
        const GLfloat xOffsetData[4] = {0.0f, 0.5f, 1.0f, 1.0f};
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4, xOffsetData, GL_STATIC_DRAW);
        GLint xOffsetLoc = glGetAttribLocation(program, "offsetX");
        glVertexAttribPointer(xOffsetLoc, 1, GL_FLOAT, GL_FALSE, 0, 0);
        glVertexAttribDivisor(xOffsetLoc, 3);
        glEnableVertexAttribArray(xOffsetLoc);
    
        GLBuffer yOffsetVBO;
        glBindBuffer(GL_ARRAY_BUFFER, yOffsetVBO);
        const GLfloat yOffsetData[4] = {0.0f, 0.5f, 1.0f, 1.5f};
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4, yOffsetData, GL_STATIC_DRAW);
        GLint yOffsetLoc = glGetAttribLocation(program, "offsetY");
        glVertexAttribDivisor(yOffsetLoc, 1);
        glVertexAttribPointer(yOffsetLoc, 1, GL_FLOAT, GL_FALSE, 0, 0);
        glEnableVertexAttribArray(yOffsetLoc);
    
        drawQuadInstanced(program, "vPosition", 0.0f, 1.0f, true, 4u);
        ASSERT_GL_NO_ERROR();
    
        resolveMultisampledFBO();
    
        const GLubyte expectedGreenChannel[kNumViews][kViewHeight][kViewWidth] = {
            {{255, 0, 0, 0}, {255, 0, 0, 0}, {255, 0, 0, 0}, {0, 255, 0, 0}},
            {{0, 0, 255, 0}, {0, 0, 255, 0}, {0, 0, 255, 0}, {0, 0, 0, 255}}};
        for (int view = 0; view < 2; ++view)
        {
            for (int row = 0; row < 4; ++row)
            {
                for (int col = 0; col < 4; ++col)
                {
                    EXPECT_EQ(GLColor(0, expectedGreenChannel[view][row][col], 0,
                                      expectedGreenChannel[view][row][col]),
                              GetViewColor(col, row, view));
                }
            }
        }
    }
    
    // Test that different sequences of vertexAttribDivisor, useProgram and bindVertexArray in a
    // multi-view context propagate the correct divisor to the driver.
    TEST_P(MultiviewRenderTest, DivisorOrderOfOperation)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension(isMultisampled()));
    
        updateFBOs(1, 1, 2);
    
        // Create multiview program.
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "layout(location = 0) in vec2 vPosition;\n"
            "layout(location = 1) in float offsetX;\n"
            "void main()\n"
            "{\n"
            "       vec4 p = vec4(vPosition, 0.0, 1.0);\n"
            "       p.x += offsetX;\n"
            "       gl_Position = p;\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(0,1,0,1);\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        constexpr char kDummyVS[] =
            "#version 300 es\n"
            "layout(location = 0) in vec2 vPosition;\n"
            "layout(location = 1) in float offsetX;\n"
            "void main()\n"
            "{\n"
            "       gl_Position = vec4(vPosition, 0.0, 1.0);\n"
            "}\n";
    
        constexpr char kDummyFS[] =
            "#version 300 es\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(0,0,0,1);\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(dummyProgram, kDummyVS, kDummyFS);
    
        GLBuffer xOffsetVBO;
        glBindBuffer(GL_ARRAY_BUFFER, xOffsetVBO);
        const GLfloat xOffsetData[12] = {0.0f, 4.0f, 4.0f, 4.0f, 4.0f, 4.0f,
                                         4.0f, 4.0f, 4.0f, 4.0f, 4.0f, 4.0f};
        glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 12, xOffsetData, GL_STATIC_DRAW);
    
        GLBuffer vertexVBO;
        glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
        Vector2 kQuadVertices[6] = {Vector2(-1.f, -1.f), Vector2(1.f, -1.f), Vector2(1.f, 1.f),
                                    Vector2(-1.f, -1.f), Vector2(1.f, 1.f),  Vector2(-1.f, 1.f)};
        glBufferData(GL_ARRAY_BUFFER, sizeof(kQuadVertices), kQuadVertices, GL_STATIC_DRAW);
    
        GLVertexArray vao[2];
        for (size_t i = 0u; i < 2u; ++i)
        {
            glBindVertexArray(vao[i]);
    
            glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
            glEnableVertexAttribArray(0);
    
            glBindBuffer(GL_ARRAY_BUFFER, xOffsetVBO);
            glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, 0);
            glEnableVertexAttribArray(1);
        }
        ASSERT_GL_NO_ERROR();
    
        glViewport(0, 0, 1, 1);
        glScissor(0, 0, 1, 1);
        glEnable(GL_SCISSOR_TEST);
        glClearColor(0, 0, 0, 1);
    
        // Clear the buffers, propagate divisor to the driver, bind the vao and keep it active.
        // It is necessary to call draw, so that the divisor is propagated and to guarantee that dirty
        // bits are cleared.
        glUseProgram(dummyProgram);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glBindVertexArray(vao[0]);
        glVertexAttribDivisor(1, 0);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);
        glUseProgram(0);
        ASSERT_GL_NO_ERROR();
    
        // Check that vertexAttribDivisor uses the number of views to update the divisor.
        bindMemberDrawFramebuffer();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glUseProgram(program);
        glVertexAttribDivisor(1, 1);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);
    
        resolveMultisampledFBO();
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 0));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
    
        // Clear the buffers and propagate divisor to the driver.
        // We keep the vao active and propagate the divisor to guarantee that there are no unresolved
        // dirty bits when useProgram is called.
        glUseProgram(dummyProgram);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glVertexAttribDivisor(1, 1);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);
        glUseProgram(0);
        ASSERT_GL_NO_ERROR();
    
        // Check that useProgram uses the number of views to update the divisor.
        bindMemberDrawFramebuffer();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glUseProgram(program);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);
    
        resolveMultisampledFBO();
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 0));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
    
        // We go through similar steps as before.
        glUseProgram(dummyProgram);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glVertexAttribDivisor(1, 1);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);
        glUseProgram(0);
        ASSERT_GL_NO_ERROR();
    
        // Check that bindVertexArray uses the number of views to update the divisor.
        {
            // Call useProgram with vao[1] being active to guarantee that useProgram will adjust the
            // divisor for vao[1] only.
            bindMemberDrawFramebuffer();
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glBindVertexArray(vao[1]);
            glUseProgram(program);
            glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);
            glBindVertexArray(0);
            ASSERT_GL_NO_ERROR();
        }
        // Bind vao[0] after useProgram is called to ensure that bindVertexArray is the call which
        // adjusts the divisor.
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glBindVertexArray(vao[0]);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 1);
    
        resolveMultisampledFBO();
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 0));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
    }
    
    // Test that no fragments pass the occlusion query for a multi-view vertex shader which always
    // transforms geometry to be outside of the clip region.
    TEST_P(MultiviewOcclusionQueryTest, OcclusionQueryNothingVisible)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension());
        ANGLE_SKIP_TEST_IF(!requestOcclusionQueryExtension());
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "in vec3 vPosition;\n"
            "void main()\n"
            "{\n"
            "       gl_Position.x = 2.0;\n"
            "       gl_Position.yzw = vec3(vPosition.yz, 1.);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(1,0,0,0);\n"
            "}\n";
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        updateFBOs(1, 1, 2);
    
        GLuint result = drawAndRetrieveOcclusionQueryResult(program);
        ASSERT_GL_NO_ERROR();
        EXPECT_GL_FALSE(result);
    }
    
    // Test that there are fragments passing the occlusion query if only view 0 can produce
    // output.
    TEST_P(MultiviewOcclusionQueryTest, OcclusionQueryOnlyLeftVisible)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension());
        ANGLE_SKIP_TEST_IF(!requestOcclusionQueryExtension());
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "in vec3 vPosition;\n"
            "void main()\n"
            "{\n"
            "       gl_Position.x = gl_ViewID_OVR == 0u ? vPosition.x : 2.0;\n"
            "       gl_Position.yzw = vec3(vPosition.yz, 1.);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(1,0,0,0);\n"
            "}\n";
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        updateFBOs(1, 1, 2);
    
        GLuint result = drawAndRetrieveOcclusionQueryResult(program);
        ASSERT_GL_NO_ERROR();
        EXPECT_GL_TRUE(result);
    }
    
    // Test that there are fragments passing the occlusion query if only view 1 can produce
    // output.
    TEST_P(MultiviewOcclusionQueryTest, OcclusionQueryOnlyRightVisible)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension());
        ANGLE_SKIP_TEST_IF(!requestOcclusionQueryExtension());
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "in vec3 vPosition;\n"
            "void main()\n"
            "{\n"
            "       gl_Position.x = gl_ViewID_OVR == 1u ? vPosition.x : 2.0;\n"
            "       gl_Position.yzw = vec3(vPosition.yz, 1.);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(1,0,0,0);\n"
            "}\n";
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        updateFBOs(1, 1, 2);
    
        GLuint result = drawAndRetrieveOcclusionQueryResult(program);
        ASSERT_GL_NO_ERROR();
        EXPECT_GL_TRUE(result);
    }
    
    // Test that a simple multi-view program which doesn't use gl_ViewID_OVR in neither VS nor FS
    // compiles and links without an error.
    TEST_P(MultiviewProgramGenerationTest, SimpleProgram)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "void main()\n"
            "{\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "void main()\n"
            "{\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        glUseProgram(program);
    
        EXPECT_GL_NO_ERROR();
    }
    
    // Test that a simple multi-view program which uses gl_ViewID_OVR only in VS compiles and links
    // without an error.
    TEST_P(MultiviewProgramGenerationTest, UseViewIDInVertexShader)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "void main()\n"
            "{\n"
            "   if (gl_ViewID_OVR == 0u) {\n"
            "       gl_Position = vec4(1,0,0,1);\n"
            "   } else {\n"
            "       gl_Position = vec4(-1,0,0,1);\n"
            "   }\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "void main()\n"
            "{\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        glUseProgram(program);
    
        EXPECT_GL_NO_ERROR();
    }
    
    // Test that a simple multi-view program which uses gl_ViewID_OVR only in FS compiles and links
    // without an error.
    TEST_P(MultiviewProgramGenerationTest, UseViewIDInFragmentShader)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "void main()\n"
            "{\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "   if (gl_ViewID_OVR == 0u) {\n"
            "       col = vec4(1,0,0,1);\n"
            "   } else {\n"
            "       col = vec4(-1,0,0,1);\n"
            "   }\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        glUseProgram(program);
    
        EXPECT_GL_NO_ERROR();
    }
    
    // The test checks that GL_POINTS is correctly rendered.
    TEST_P(MultiviewRenderPrimitiveTest, Points)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        // Test failing on P400 graphics card (anglebug.com/2228)
        ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D11() && IsNVIDIA());
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "layout(location=0) in vec2 vPosition;\n"
            "void main()\n"
            "{\n"
            "   gl_PointSize = 1.0;\n"
            "   gl_Position = vec4(vPosition.xy, 0.0, 1.0);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "   col = vec4(0,1,0,1);\n"
            "}\n";
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        glUseProgram(program);
    
        const int kViewWidth  = 4;
        const int kViewHeight = 2;
        const int kNumViews   = 2;
        updateFBOs(kViewWidth, kViewHeight, kNumViews);
    
        std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(3, 1)};
        std::vector<Vector2> vertexDataInClipSpace =
            ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 2);
        setupGeometry(vertexDataInClipSpace);
    
        glDrawArrays(GL_POINTS, 0, 2);
    
        const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = {
            {{255, 0, 0, 0}, {0, 0, 0, 255}}, {{255, 0, 0, 0}, {0, 0, 0, 255}}};
        checkGreenChannel(expectedGreenChannelData[0][0]);
    }
    
    // The test checks that GL_LINES is correctly rendered.
    // The behavior of this test is not guaranteed by the spec:
    // OpenGL ES 3.0.5 (November 3, 2016), Section 3.5.1 Basic Line Segment Rasterization:
    // "The coordinates of a fragment produced by the algorithm may not deviate by more than one unit in
    // either x or y window coordinates from a corresponding fragment produced by the diamond-exit
    // rule."
    TEST_P(MultiviewRenderPrimitiveTest, Lines)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        GLuint program = CreateSimplePassthroughProgram(2);
        ASSERT_NE(program, 0u);
        glUseProgram(program);
        ASSERT_GL_NO_ERROR();
    
        const int kViewWidth  = 4;
        const int kViewHeight = 2;
        const int kNumViews   = 2;
        updateFBOs(kViewWidth, kViewHeight, kNumViews);
    
        std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(4, 0)};
        std::vector<Vector2> vertexDataInClipSpace =
            ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 2);
        setupGeometry(vertexDataInClipSpace);
    
        glDrawArrays(GL_LINES, 0, 2);
    
        const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = {
            {{255, 255, 255, 255}, {0, 0, 0, 0}}, {{255, 255, 255, 255}, {0, 0, 0, 0}}};
        checkGreenChannel(expectedGreenChannelData[0][0]);
    
        glDeleteProgram(program);
    }
    
    // The test checks that GL_LINE_STRIP is correctly rendered.
    // The behavior of this test is not guaranteed by the spec:
    // OpenGL ES 3.0.5 (November 3, 2016), Section 3.5.1 Basic Line Segment Rasterization:
    // "The coordinates of a fragment produced by the algorithm may not deviate by more than one unit in
    // either x or y window coordinates from a corresponding fragment produced by the diamond-exit
    // rule."
    TEST_P(MultiviewRenderPrimitiveTest, LineStrip)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        GLuint program = CreateSimplePassthroughProgram(2);
        ASSERT_NE(program, 0u);
        glUseProgram(program);
        ASSERT_GL_NO_ERROR();
    
        const int kViewWidth  = 4;
        const int kViewHeight = 2;
        const int kNumViews   = 2;
        updateFBOs(kViewWidth, kViewHeight, kNumViews);
    
        std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(3, 0), Vector2I(3, 2)};
        std::vector<Vector2> vertexDataInClipSpace =
            ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 2);
        setupGeometry(vertexDataInClipSpace);
    
        glDrawArrays(GL_LINE_STRIP, 0, 3);
    
        const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = {
            {{255, 255, 255, 255}, {0, 0, 0, 255}}, {{255, 255, 255, 255}, {0, 0, 0, 255}}};
        checkGreenChannel(expectedGreenChannelData[0][0]);
    
        glDeleteProgram(program);
    }
    
    // The test checks that GL_LINE_LOOP is correctly rendered.
    // The behavior of this test is not guaranteed by the spec:
    // OpenGL ES 3.0.5 (November 3, 2016), Section 3.5.1 Basic Line Segment Rasterization:
    // "The coordinates of a fragment produced by the algorithm may not deviate by more than one unit in
    // either x or y window coordinates from a corresponding fragment produced by the diamond-exit
    // rule."
    TEST_P(MultiviewRenderPrimitiveTest, LineLoop)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        GLuint program = CreateSimplePassthroughProgram(2);
        ASSERT_NE(program, 0u);
        glUseProgram(program);
        ASSERT_GL_NO_ERROR();
    
        const int kViewWidth  = 4;
        const int kViewHeight = 4;
        const int kNumViews   = 2;
        updateFBOs(kViewWidth, kViewHeight, kNumViews);
    
        std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(3, 0), Vector2I(3, 3),
                                                   Vector2I(0, 3)};
        std::vector<Vector2> vertexDataInClipSpace =
            ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 4);
        setupGeometry(vertexDataInClipSpace);
    
        glDrawArrays(GL_LINE_LOOP, 0, 4);
    
        const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = {
            {{255, 255, 255, 255}, {255, 0, 0, 255}, {255, 0, 0, 255}, {255, 255, 255, 255}},
            {{255, 255, 255, 255}, {255, 0, 0, 255}, {255, 0, 0, 255}, {255, 255, 255, 255}}};
        checkGreenChannel(expectedGreenChannelData[0][0]);
    
        glDeleteProgram(program);
    }
    
    // The test checks that GL_TRIANGLE_STRIP is correctly rendered.
    TEST_P(MultiviewRenderPrimitiveTest, TriangleStrip)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        GLuint program = CreateSimplePassthroughProgram(2);
        ASSERT_NE(program, 0u);
        glUseProgram(program);
        ASSERT_GL_NO_ERROR();
    
        std::vector<Vector2> vertexDataInClipSpace = {Vector2(1.0f, 0.0f), Vector2(0.0f, 0.0f),
                                                      Vector2(1.0f, 1.0f), Vector2(0.0f, 1.0f)};
        setupGeometry(vertexDataInClipSpace);
    
        const int kViewWidth  = 2;
        const int kViewHeight = 2;
        const int kNumViews   = 2;
        updateFBOs(kViewWidth, kViewHeight, kNumViews);
    
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
        const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = {
            {{0, 0}, {0, 255}}, {{0, 0}, {0, 255}}};
        checkGreenChannel(expectedGreenChannelData[0][0]);
    
        glDeleteProgram(program);
    }
    
    // The test checks that GL_TRIANGLE_FAN is correctly rendered.
    TEST_P(MultiviewRenderPrimitiveTest, TriangleFan)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        GLuint program = CreateSimplePassthroughProgram(2);
        ASSERT_NE(program, 0u);
        glUseProgram(program);
        ASSERT_GL_NO_ERROR();
    
        std::vector<Vector2> vertexDataInClipSpace = {Vector2(0.0f, 0.0f), Vector2(0.0f, 1.0f),
                                                      Vector2(1.0f, 1.0f), Vector2(1.0f, 0.0f)};
        setupGeometry(vertexDataInClipSpace);
    
        const int kViewWidth  = 2;
        const int kViewHeight = 2;
        const int kNumViews   = 2;
        updateFBOs(kViewWidth, kViewHeight, kNumViews);
    
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    
        const GLubyte expectedGreenChannelData[kNumViews][kViewHeight][kViewWidth] = {
            {{0, 0}, {0, 255}}, {{0, 0}, {0, 255}}};
        checkGreenChannel(expectedGreenChannelData[0][0]);
    
        glDeleteProgram(program);
    }
    
    // Test that rendering enlarged points and lines does not leak fragments outside of the views'
    // bounds. The test does not rely on the actual line width being greater than 1.0.
    TEST_P(MultiviewSideBySideRenderTest, NoLeakingFragments)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        GLTexture colorTexture;
    
        CreateMultiviewBackingTextures(GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE, 0, 2, 1, 2,
                                       colorTexture, 0u, 0u);
    
        GLFramebuffer drawFramebuffer;
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFramebuffer);
        GLint viewportOffsets[4] = {1, 0, 3, 0};
        glFramebufferTextureMultiviewSideBySideANGLE(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                     colorTexture, 0, 2, &viewportOffsets[0]);
    
        GLFramebuffer readFramebuffer;
        glBindFramebuffer(GL_READ_FRAMEBUFFER, readFramebuffer);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture,
                               0);
    
        ASSERT_GL_NO_ERROR();
    
        glViewport(0, 0, 1, 1);
        glScissor(0, 0, 1, 1);
        glEnable(GL_SCISSOR_TEST);
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "layout(location=0) in vec2 vPosition;\n"
            "void main()\n"
            "{\n"
            "   gl_PointSize = 10.0;\n"
            "   gl_Position = vec4(vPosition.xy, 0.0, 1.0);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "   if (gl_ViewID_OVR == 0u) {\n"
            "       col = vec4(1,0,0,1);\n"
            "   } else {\n"
            "       col = vec4(0,1,0,1);\n"
            "   }\n"
            "}\n";
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        glUseProgram(program);
    
        const std::vector<Vector2I> &windowCoordinates = {Vector2I(0, 0), Vector2I(2, 0)};
        const std::vector<Vector2> &vertexDataInClipSpace =
            ConvertPixelCoordinatesToClipSpace(windowCoordinates, 1, 1);
    
        GLBuffer vbo;
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, vertexDataInClipSpace.size() * sizeof(Vector2),
                     vertexDataInClipSpace.data(), GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
    
        // Test rendering points.
        {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glDrawArrays(GL_POINTS, 0, 2);
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
            EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::red);
            EXPECT_PIXEL_COLOR_EQ(2, 0, GLColor::transparentBlack);
            EXPECT_PIXEL_COLOR_EQ(3, 0, GLColor::green);
        }
    
        // Test rendering lines.
        {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glLineWidth(10.f);
            glDrawArrays(GL_LINES, 0, 2);
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
            EXPECT_PIXEL_COLOR_EQ(1, 0, GLColor::red);
            EXPECT_PIXEL_COLOR_EQ(2, 0, GLColor::transparentBlack);
            EXPECT_PIXEL_COLOR_EQ(3, 0, GLColor::green);
        }
    }
    
    // Verify that re-linking a program adjusts the attribute divisor.
    // The test uses instacing to draw for each view a strips of two red quads and two blue quads next
    // to each other. The quads' position and color depend on the corresponding attribute divisors.
    TEST_P(MultiviewRenderTest, ProgramRelinkUpdatesAttribDivisor)
    {
        if (!requestMultiviewExtension(isMultisampled()))
        {
            return;
        }
    
        // Looks like an incorrect D3D debug layer message is generated on Windows AMD and NVIDIA.
        // May be specific to Windows 7 / Windows Server 2008. http://anglebug.com/2778
        if (IsWindows() && IsD3D11())
        {
            ignoreD3D11SDKLayersWarnings();
        }
    
        const int kViewWidth  = 4;
        const int kViewHeight = 1;
        const int kNumViews   = 2;
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "in vec4 oColor;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = oColor;\n"
            "}\n";
    
        auto generateVertexShaderSource = [](int numViews) -> std::string {
            std::string source =
                "#version 300 es\n"
                "#extension GL_OVR_multiview : require\n"
                "layout(num_views = " +
                ToString(numViews) +
                ") in;\n"
                "in vec3 vPosition;\n"
                "in float vOffsetX;\n"
                "in vec4 vColor;\n"
                "out vec4 oColor;\n"
                "void main()\n"
                "{\n"
                "       vec4 p = vec4(vPosition, 1.);\n"
                "       p.x = p.x * 0.25 - 0.75 + vOffsetX;\n"
                "       oColor = vColor;\n"
                "       gl_Position = p;\n"
                "}\n";
            return source;
        };
    
        std::string vsSource = generateVertexShaderSource(kNumViews);
        ANGLE_GL_PROGRAM(program, vsSource.c_str(), kFS);
        glUseProgram(program);
    
        GLint positionLoc;
        GLBuffer xOffsetVBO;
        GLint xOffsetLoc;
        GLBuffer colorVBO;
        GLint colorLoc;
    
        {
            // Initialize buffers and setup attributes.
            glBindBuffer(GL_ARRAY_BUFFER, xOffsetVBO);
            const GLfloat kXOffsetData[4] = {0.0f, 0.5f, 1.0f, 1.5f};
            glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4, kXOffsetData, GL_STATIC_DRAW);
            xOffsetLoc = glGetAttribLocation(program, "vOffsetX");
            glVertexAttribPointer(xOffsetLoc, 1, GL_FLOAT, GL_FALSE, 0, 0);
            glVertexAttribDivisor(xOffsetLoc, 1);
            glEnableVertexAttribArray(xOffsetLoc);
    
            glBindBuffer(GL_ARRAY_BUFFER, colorVBO);
            const GLColor kColors[2] = {GLColor::red, GLColor::blue};
            glBufferData(GL_ARRAY_BUFFER, sizeof(GLColor) * 2, kColors, GL_STATIC_DRAW);
            colorLoc = glGetAttribLocation(program, "vColor");
            glVertexAttribDivisor(colorLoc, 2);
            glVertexAttribPointer(colorLoc, 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, 0);
            glEnableVertexAttribArray(colorLoc);
    
            positionLoc = glGetAttribLocation(program, "vPosition");
        }
    
        {
            updateFBOs(kViewWidth, kViewHeight, kNumViews);
    
            drawQuadInstanced(program, "vPosition", 0.0f, 1.0f, true, 4u);
            ASSERT_GL_NO_ERROR();
    
            resolveMultisampledFBO();
            EXPECT_EQ(GLColor::red, GetViewColor(0, 0, 0));
            EXPECT_EQ(GLColor::red, GetViewColor(1, 0, 0));
            EXPECT_EQ(GLColor::blue, GetViewColor(2, 0, 0));
            EXPECT_EQ(GLColor::blue, GetViewColor(3, 0, 0));
        }
    
        {
            const int kNewNumViews = 3;
            vsSource               = generateVertexShaderSource(kNewNumViews);
            updateFBOs(kViewWidth, kViewHeight, kNewNumViews);
    
            GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource.c_str());
            ASSERT_NE(0u, vs);
            GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
            ASSERT_NE(0u, fs);
    
            GLint numAttachedShaders = 0;
            glGetProgramiv(program, GL_ATTACHED_SHADERS, &numAttachedShaders);
    
            GLuint attachedShaders[2] = {0u};
            glGetAttachedShaders(program, numAttachedShaders, nullptr, attachedShaders);
            for (int i = 0; i < 2; ++i)
            {
                glDetachShader(program, attachedShaders[i]);
            }
    
            glAttachShader(program, vs);
            glDeleteShader(vs);
    
            glAttachShader(program, fs);
            glDeleteShader(fs);
    
            glBindAttribLocation(program, positionLoc, "vPosition");
            glBindAttribLocation(program, xOffsetLoc, "vOffsetX");
            glBindAttribLocation(program, colorLoc, "vColor");
    
            glLinkProgram(program);
    
            drawQuadInstanced(program, "vPosition", 0.0f, 1.0f, true, 4u);
            ASSERT_GL_NO_ERROR();
    
            resolveMultisampledFBO();
            for (int i = 0; i < kNewNumViews; ++i)
            {
                EXPECT_EQ(GLColor::red, GetViewColor(0, 0, i));
                EXPECT_EQ(GLColor::red, GetViewColor(1, 0, i));
                EXPECT_EQ(GLColor::blue, GetViewColor(2, 0, i));
                EXPECT_EQ(GLColor::blue, GetViewColor(3, 0, i));
            }
        }
    }
    
    // Test that useProgram applies the number of views in computing the final value of the attribute
    // divisor.
    TEST_P(MultiviewRenderTest, DivisorUpdatedOnProgramChange)
    {
        if (!requestMultiviewExtension(isMultisampled()))
        {
            return;
        }
    
        // Test failing on P400 graphics card (anglebug.com/2228)
        ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D11() && IsNVIDIA());
    
        // Looks like an incorrect D3D debug layer message is generated on Windows / AMD.
        // May be specific to Windows 7 / Windows Server 2008. http://anglebug.com/2778
        if (IsWindows() && IsD3D11())
        {
            ignoreD3D11SDKLayersWarnings();
        }
    
        GLVertexArray vao;
        glBindVertexArray(vao);
        GLBuffer vbo;
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        std::vector<Vector2I> windowCoordinates = {Vector2I(0, 0), Vector2I(1, 0), Vector2I(2, 0),
                                                   Vector2I(3, 0)};
        std::vector<Vector2> vertexDataInClipSpace =
            ConvertPixelCoordinatesToClipSpace(windowCoordinates, 4, 1);
        // Fill with x positions so that the resulting clip space coordinate fails the clip test.
        glBufferData(GL_ARRAY_BUFFER, sizeof(Vector2) * vertexDataInClipSpace.size(),
                     vertexDataInClipSpace.data(), GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, nullptr);
        glVertexAttribDivisor(0, 1);
        ASSERT_GL_NO_ERROR();
    
        // Create a program and fbo with N views and draw N instances of a point horizontally.
        for (int numViews = 2; numViews <= 4; ++numViews)
        {
            updateFBOs(4, 1, numViews);
            ASSERT_GL_NO_ERROR();
    
            GLuint program = CreateSimplePassthroughProgram(numViews);
            ASSERT_NE(program, 0u);
            glUseProgram(program);
            ASSERT_GL_NO_ERROR();
    
            glDrawArraysInstanced(GL_POINTS, 0, 1, numViews);
    
            resolveMultisampledFBO();
            for (int view = 0; view < numViews; ++view)
            {
                for (int j = 0; j < numViews; ++j)
                {
                    EXPECT_EQ(GLColor::green, GetViewColor(j, 0, view));
                }
                for (int j = numViews; j < 4; ++j)
                {
                    EXPECT_EQ(GLColor::transparentBlack, GetViewColor(j, 0, view));
                }
            }
    
            glDeleteProgram(program);
        }
    }
    
    // The test checks that gl_ViewID_OVR is correctly propagated to the fragment shader.
    TEST_P(MultiviewRenderTest, SelectColorBasedOnViewIDOVR)
    {
        if (!requestMultiviewExtension(isMultisampled()))
        {
            return;
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 3) in;\n"
            "in vec3 vPosition;\n"
            "void main()\n"
            "{\n"
            "   gl_Position = vec4(vPosition, 1.);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    if (gl_ViewID_OVR == 0u) {\n"
            "       col = vec4(1,0,0,1);\n"
            "    } else if (gl_ViewID_OVR == 1u) {\n"
            "       col = vec4(0,1,0,1);\n"
            "    } else if (gl_ViewID_OVR == 2u) {\n"
            "       col = vec4(0,0,1,1);\n"
            "    } else {\n"
            "       col = vec4(0,0,0,0);\n"
            "    }\n"
            "}\n";
    
        updateFBOs(1, 1, 3);
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        drawQuad(program, "vPosition", 0.0f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        resolveMultisampledFBO();
        EXPECT_EQ(GLColor::red, GetViewColor(0, 0, 0));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
        EXPECT_EQ(GLColor::blue, GetViewColor(0, 0, 2));
    }
    
    // The test checks that the inactive layers of a 2D texture array are not written to by a
    // multi-view program.
    TEST_P(MultiviewLayeredRenderTest, RenderToSubrangeOfLayers)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "in vec3 vPosition;\n"
            "void main()\n"
            "{\n"
            "   gl_Position = vec4(vPosition, 1.);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "     col = vec4(0,1,0,1);\n"
            "}\n";
    
        updateFBOs(1, 1, 2, 4, 1);
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        drawQuad(program, "vPosition", 0.0f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        resolveMultisampledFBO();
        EXPECT_EQ(GLColor::transparentBlack, GetViewColor(0, 0, 0));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 2));
        EXPECT_EQ(GLColor::transparentBlack, GetViewColor(0, 0, 3));
    }
    
    // The D3D11 renderer uses a GS whenever the varyings are flat interpolated which can cause
    // potential bugs if the view is selected in the VS. The test contains a program in which the
    // gl_InstanceID is passed as a flat varying to the fragment shader where it is used to discard the
    // fragment if its value is negative. The gl_InstanceID should never be negative and that branch is
    // never taken. One quad is drawn and the color is selected based on the ViewID - red for view 0 and
    // green for view 1.
    TEST_P(MultiviewRenderTest, FlatInterpolation)
    {
        if (!requestMultiviewExtension(isMultisampled()))
        {
            return;
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "in vec3 vPosition;\n"
            "flat out int oInstanceID;\n"
            "void main()\n"
            "{\n"
            "   gl_Position = vec4(vPosition, 1.);\n"
            "   oInstanceID = gl_InstanceID;\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "flat in int oInstanceID;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    if (oInstanceID < 0) {\n"
            "       discard;\n"
            "    }\n"
            "    if (gl_ViewID_OVR == 0u) {\n"
            "       col = vec4(1,0,0,1);\n"
            "    } else {\n"
            "       col = vec4(0,1,0,1);\n"
            "    }\n"
            "}\n";
    
        updateFBOs(1, 1, 2);
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        drawQuad(program, "vPosition", 0.0f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        resolveMultisampledFBO();
        EXPECT_EQ(GLColor::red, GetViewColor(0, 0, 0));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
    }
    
    // This test assigns gl_ViewID_OVR to a flat int varying and then sets the color based on that
    // varying in the fragment shader.
    TEST_P(MultiviewRenderTest, FlatInterpolation2)
    {
        if (!requestMultiviewExtension(isMultisampled()))
        {
            return;
        }
    
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "in vec3 vPosition;\n"
            "flat out int flatVarying;\n"
            "void main()\n"
            "{\n"
            "   gl_Position = vec4(vPosition, 1.);\n"
            "   flatVarying = int(gl_ViewID_OVR);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "flat in int flatVarying;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    if (flatVarying == 0) {\n"
            "       col = vec4(1,0,0,1);\n"
            "    } else {\n"
            "       col = vec4(0,1,0,1);\n"
            "    }\n"
            "}\n";
    
        updateFBOs(1, 1, 2);
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        drawQuad(program, "vPosition", 0.0f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        resolveMultisampledFBO();
        EXPECT_EQ(GLColor::red, GetViewColor(0, 0, 0));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
    }
    
    // The test is added to cover a bug which resulted in the viewport/scissor and viewport offsets not
    // being correctly applied.
    TEST_P(MultiviewSideBySideRenderTest, ViewportOffsetsAppliedBugCoverage)
    {
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        updateFBOs(1, 1, 2);
    
        // Create multiview program.
        constexpr char kVS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "layout(num_views = 2) in;\n"
            "layout(location = 0) in vec3 vPosition;\n"
            "void main()\n"
            "{\n"
            "       gl_Position = vec4(vPosition, 1.0);\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "#extension GL_OVR_multiview : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "    col = vec4(0,1,0,1);\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        glViewport(0, 0, 1, 1);
        glScissor(0, 0, 1, 1);
        glEnable(GL_SCISSOR_TEST);
        glClearColor(0, 0, 0, 1);
    
        // Bind the default FBO and make sure that the state is synchronized.
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        ASSERT_GL_NO_ERROR();
    
        // Draw and check that both views are rendered to.
        bindMemberDrawFramebuffer();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        drawQuad(program, "vPosition", 0.0f, 1.0f, true);
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 0));
        EXPECT_EQ(GLColor::green, GetViewColor(0, 0, 1));
    }
    
    MultiviewRenderTestParams SideBySideVertexShaderOpenGL(GLint majorVersion = 3,
                                                           GLint minorVersion = 0)
    {
        return MultiviewRenderTestParams(GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE, 0,
                                         VertexShaderOpenGL(majorVersion, minorVersion));
    }
    
    MultiviewRenderTestParams LayeredVertexShaderOpenGL()
    {
        return MultiviewRenderTestParams(GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE, 0,
                                         VertexShaderOpenGL(3, 0));
    }
    
    MultiviewRenderTestParams SideBySideGeomShaderD3D11()
    {
        return MultiviewRenderTestParams(GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE, 0,
                                         GeomShaderD3D11(3, 0));
    }
    
    MultiviewRenderTestParams LayeredGeomShaderD3D11()
    {
        return MultiviewRenderTestParams(GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE, 0,
                                         GeomShaderD3D11(3, 0));
    }
    
    MultiviewRenderTestParams SideBySideVertexShaderD3D11(GLint majorVersion = 3,
                                                          GLint minorVersion = 0)
    {
        return MultiviewRenderTestParams(GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE, 0,
                                         VertexShaderD3D11(majorVersion, minorVersion));
    }
    
    MultiviewRenderTestParams LayeredVertexShaderD3D11()
    {
        return MultiviewRenderTestParams(GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE, 0,
                                         VertexShaderD3D11(3, 0));
    }
    
    MultiviewRenderTestParams LayeredMultisampledVertexShaderOpenGL()
    {
        return MultiviewRenderTestParams(GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE, 2,
                                         VertexShaderOpenGL(3, 1));
    }
    
    MultiviewRenderTestParams LayeredMultisampledVertexShaderD3D11()
    {
        return MultiviewRenderTestParams(GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE, 2,
                                         VertexShaderD3D11(3, 1));
    }
    
    ANGLE_INSTANTIATE_TEST(MultiviewDrawValidationTest,
                           VertexShaderOpenGL(3, 1),
                           VertexShaderD3D11(3, 1));
    ANGLE_INSTANTIATE_TEST(MultiviewRenderDualViewTest,
                           SideBySideVertexShaderOpenGL(),
                           LayeredVertexShaderOpenGL(),
                           LayeredMultisampledVertexShaderOpenGL(),
                           SideBySideGeomShaderD3D11(),
                           SideBySideVertexShaderD3D11(),
                           LayeredGeomShaderD3D11(),
                           LayeredVertexShaderD3D11(),
                           LayeredMultisampledVertexShaderD3D11());
    ANGLE_INSTANTIATE_TEST(MultiviewRenderTest,
                           SideBySideVertexShaderOpenGL(),
                           LayeredVertexShaderOpenGL(),
                           LayeredMultisampledVertexShaderOpenGL(),
                           SideBySideGeomShaderD3D11(),
                           SideBySideVertexShaderD3D11(),
                           LayeredGeomShaderD3D11(),
                           LayeredVertexShaderD3D11(),
                           LayeredMultisampledVertexShaderD3D11());
    ANGLE_INSTANTIATE_TEST(MultiviewOcclusionQueryTest,
                           SideBySideVertexShaderOpenGL(),
                           LayeredVertexShaderOpenGL(),
                           SideBySideGeomShaderD3D11(),
                           SideBySideVertexShaderD3D11(),
                           LayeredGeomShaderD3D11(),
                           LayeredVertexShaderD3D11());
    ANGLE_INSTANTIATE_TEST(MultiviewProgramGenerationTest,
                           VertexShaderOpenGL(3, 0),
                           GeomShaderD3D11(3, 0),
                           VertexShaderD3D11(3, 0));
    ANGLE_INSTANTIATE_TEST(MultiviewRenderPrimitiveTest,
                           SideBySideVertexShaderOpenGL(),
                           LayeredVertexShaderOpenGL(),
                           SideBySideGeomShaderD3D11(),
                           SideBySideVertexShaderD3D11(),
                           LayeredGeomShaderD3D11(),
                           LayeredVertexShaderD3D11());
    ANGLE_INSTANTIATE_TEST(MultiviewSideBySideRenderTest,
                           VertexShaderOpenGL(3, 0),
                           GeomShaderD3D11(3, 0));
    ANGLE_INSTANTIATE_TEST(MultiviewLayeredRenderTest, VertexShaderOpenGL(3, 0), GeomShaderD3D11(3, 0));