Edit

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

Branch :

  • Show log

    Commit

  • Author : Tibor Dusnoki
    Date : 2020-01-31 15:05:35
    Hash : 4546c5ce
    Message : Skip failing end2end tests on Windows on ARM There are multiple failing tests on Windows on ARM which are related to drawing differences. They seem to be similar to an already reported issue (anglebug.com/3748). Bug: angleproject:4356 Bug: angleproject:4357 Change-Id: Ide5cc2e6f42d4c4b6fb88352833d20e517005c14 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2033067 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 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/FeaturesD3D.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(int samples,
                                  const MultiviewImplementationParams &implementationParams)
            : MultiviewImplementationParams(implementationParams), mSamples(samples)
        {}
        int mSamples;
    };
    
    std::ostream &operator<<(std::ostream &os, const MultiviewRenderTestParams &params)
    {
        const MultiviewImplementationParams &base =
            static_cast<const MultiviewImplementationParams &>(params);
        os << base;
        os << "_layered";
    
        if (params.mSamples > 0)
        {
            os << "_samples_" << params.mSamples;
        }
        return os;
    }
    
    class MultiviewFramebufferTestBase : public MultiviewTestBase,
                                         public ::testing::TestWithParam<MultiviewRenderTestParams>
    {
      protected:
        MultiviewFramebufferTestBase(const PlatformParameters &params, int samples)
            : MultiviewTestBase(params),
              mViewWidth(0),
              mViewHeight(0),
              mNumViews(0),
              mColorTexture(0u),
              mDepthTexture(0u),
              mDrawFramebuffer(0u),
              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(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, 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.
            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));
            }
    
            // Clear the buffers.
            glViewport(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(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)
        {
            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);
        }
    
        // Requests the OVR_multiview(2) extension and returns true if the operation succeeds.
        bool requestMultiviewExtension(bool requireMultiviewMultisample)
        {
            if (!EnsureGLExtensionEnabled(extensionName()))
            {
                std::cout << "Test skipped due to missing " << extensionName() << "." << std::endl;
                return false;
            }
    
            if (requireMultiviewMultisample)
            {
                if (!EnsureGLExtensionEnabled("GL_OES_texture_storage_multisample_2d_array"))
                {
                    std::cout << "Test skipped due to missing GL_ANGLE_multiview_multisample."
                              << std::endl;
                    return false;
                }
    
                if (!EnsureGLExtensionEnabled("GL_ANGLE_multiview_multisample"))
                {
                    std::cout << "Test skipped due to missing GL_ANGLE_multiview_multisample."
                              << std::endl;
                    return false;
                }
            }
            return true;
        }
    
        bool requestMultiviewExtension() { return requestMultiviewExtension(false); }
        std::string extensionName()
        {
            switch (GetParam().mMultiviewExtension)
            {
                case multiview:
                    return "GL_OVR_multiview";
                case multiview2:
                    return "GL_OVR_multiview2";
                default:
                    // Ignore unknown.
                    return "";
            }
        }
    
        bool isMultisampled() { return mSamples > 0; }
    
        int mViewWidth;
        int mViewHeight;
        int mNumViews;
    
        GLuint mColorTexture;
        GLuint mDepthTexture;
    
      private:
        GLuint mDrawFramebuffer;
        std::vector<GLuint> mReadFramebuffer;
        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
    {
      protected:
        MultiviewRenderTest() : MultiviewFramebufferTestBase(GetParam(), GetParam().mSamples) {}
    
        void overrideWorkaroundsD3D(FeaturesD3D *features) override
        {
            features->overrideFeatures({"select_view_in_geometry_shader"},
                                       GetParam().mForceUseGeometryShaderOnD3D);
        }
    
        virtual void testSetUp() {}
        virtual void testTearDown() {}
    
      private:
        void SetUp() override
        {
            MultiviewFramebufferTestBase::FramebufferTestSetUp();
            testSetUp();
        }
        void TearDown() override
        {
            testTearDown();
            MultiviewFramebufferTestBase::FramebufferTestTearDown();
        }
    };
    
    std::string DualViewVS(ExtensionName multiviewExtension)
    {
        std::string ext;
        switch (multiviewExtension)
        {
            case multiview:
                ext = "GL_OVR_multiview";
                break;
            case multiview2:
                ext = "GL_OVR_multiview2";
                break;
        }
    
        std::string dualViewVSSource =
            "#version 300 es\n"
            "#extension " +
            ext +
            " : require\n"
            "layout(num_views = 2) in;\n"
            "in vec4 vPosition;\n"
            "void main()\n"
            "{\n"
            "   gl_Position.x = (gl_ViewID_OVR == 0u ? vPosition.x * 0.5 + 0.5 : vPosition.x * 0.5 - "
            "0.5);\n"
            "   gl_Position.yzw = vPosition.yzw;\n"
            "}\n";
        return dualViewVSSource;
    }
    
    std::string DualViewFS(ExtensionName multiviewExtension)
    {
        std::string ext;
        switch (multiviewExtension)
        {
            case multiview:
                ext = "GL_OVR_multiview";
                break;
            case multiview2:
                ext = "GL_OVR_multiview2";
                break;
        }
    
        std::string dualViewFSSource =
            "#version 300 es\n"
            "#extension " +
            ext +
            " : require\n"
            "precision mediump float;\n"
            "out vec4 col;\n"
            "void main()\n"
            "{\n"
            "  col = vec4(0,1,0,1);\n"
            "}\n";
        return dualViewFSSource;
    }
    
    class MultiviewRenderDualViewTest : public MultiviewRenderTest
    {
      protected:
        MultiviewRenderDualViewTest() : mProgram(0u) {}
    
        void testSetUp() override
        {
            if (!requestMultiviewExtension(isMultisampled()))
            {
                return;
            }
    
            updateFBOs(2, 1, 2);
            mProgram = CompileProgram(DualViewVS(GetParam().mMultiviewExtension).c_str(),
                                      DualViewFS(GetParam().mMultiviewExtension).c_str());
            ASSERT_NE(mProgram, 0u);
            glUseProgram(mProgram);
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            if (mProgram != 0u)
            {
                glDeleteProgram(mProgram);
                mProgram = 0u;
            }
        }
    
        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 initOnePixelColorTexture2DSingleLayered(GLuint texId)
        {
            glBindTexture(GL_TEXTURE_2D_ARRAY, texId);
            glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         nullptr);
        }
    
        void initOnePixelColorTexture2DMultiLayered(GLuint texId)
        {
            glBindTexture(GL_TEXTURE_2D_ARRAY, texId);
            glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 1, 1, 2, 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 (!EnsureGLExtensionEnabled("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 testSetUp() override { glGenBuffers(1, &mVBO); }
    
        void testTearDown() override
        {
            if (mVBO)
            {
                glDeleteBuffers(1, &mVBO);
                mVBO = 0u;
            }
        }
    
        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 MultiviewLayeredRenderTest : public MultiviewFramebufferTestBase
    {
      protected:
        MultiviewLayeredRenderTest() : MultiviewFramebufferTestBase(GetParam(), 0) {}
        void SetUp() final { MultiviewFramebufferTestBase::FramebufferTestSetUp(); }
        void TearDown() final { MultiviewFramebufferTestBase::FramebufferTestTearDown(); }
        void overrideWorkaroundsD3D(FeaturesD3D *features) final
        {
            features->overrideFeatures({"select_view_in_geometry_shader"},
                                       GetParam().mForceUseGeometryShaderOnD3D);
        }
    };
    
    // The test verifies that glDraw*Indirect works for any number of views.
    TEST_P(MultiviewDrawValidationTest, IndirectDraw)
    {
        ANGLE_SKIP_TEST_IF(!requestMultiviewExtension());
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "precision mediump float;\n"
            "out vec4 color;\n"
            "void main()\n"
            "{color = vec4(1);}\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();
    
        // Check that no errors are generated with the framebuffer having 2 views.
        {
            const std::string VS =
                "#version 300 es\n"
                "#extension " +
                extensionName() +
                ": require\n"
                "layout(num_views = 2) in;\n"
                "void main()\n"
                "{}\n";
            ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str());
            glUseProgram(program);
    
            GLTexture tex2DArray;
            initOnePixelColorTexture2DMultiLayered(tex2DArray);
    
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 2);
    
            glDrawArraysIndirect(GL_TRIANGLES, nullptr);
            EXPECT_GL_NO_ERROR();
    
            glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr);
            EXPECT_GL_NO_ERROR();
        }
    
        // Check that no errors are generated if the number of views is 1.
        {
            const std::string VS =
                "#version 300 es\n"
                "#extension " +
                extensionName() +
                ": require\n"
                "layout(num_views = 1) in;\n"
                "void main()\n"
                "{}\n";
            ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str());
            glUseProgram(program);
    
            GLTexture tex2D;
            initOnePixelColorTexture2DSingleLayered(tex2D);
    
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0, 1);
    
            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 std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "layout(num_views = 2) in;\n"
            "void main()\n"
            "{}\n";
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "precision mediump float;\n"
            "out vec4 color;\n"
            "void main()\n"
            "{color = vec4(1);}\n";
        ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str());
        glUseProgram(program);
    
        GLVertexArray vao;
        GLBuffer vertexBuffer;
        GLBuffer indexBuffer;
        initVAO(vao, vertexBuffer, indexBuffer);
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    
        // Check for a GL_INVALID_OPERATION error with the framebuffer and program having different
        // number of views.
        {
            GLTexture tex2D;
            initOnePixelColorTexture2DSingleLayered(tex2D);
    
            // The framebuffer has only 1 view.
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0, 1);
    
            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.
        {
            GLTexture tex2DArray;
            initOnePixelColorTexture2DMultiLayered(tex2DArray);
    
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 2);
    
            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 tex2DArray;
        initOnePixelColorTexture2DMultiLayered(tex2DArray);
    
        glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 2);
    
        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());
    
        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,
                                            DualViewVS(GetParam().mMultiviewExtension).c_str(),
                                            DualViewFS(GetParam().mMultiviewExtension).c_str(),
                                            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 tex2DArray;
        initOnePixelColorTexture2DMultiLayered(tex2DArray);
    
        GLenum bufs[] = {GL_NONE};
        glDrawBuffers(1, bufs);
    
        // 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.
        {
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 2);
            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();
    
        GLTexture tex2D;
        initOnePixelColorTexture2DSingleLayered(tex2D);
    
        // Check that drawArrays does not generate an error when the number of views in the draw
        // framebuffer is 1.
        {
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0, 1);
            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(!EnsureGLExtensionEnabled("GL_EXT_disjoint_timer_query"));
    
        ANGLE_GL_PROGRAM(dualViewProgram, DualViewVS(GetParam().mMultiviewExtension).c_str(),
                         DualViewFS(GetParam().mMultiviewExtension).c_str());
    
        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 tex2DArr;
        initOnePixelColorTexture2DMultiLayered(tex2DArr);
    
        GLenum bufs[] = {GL_NONE};
        glDrawBuffers(1, bufs);
    
        // Check first case.
        {
            glUseProgram(dualViewProgram);
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArr, 0, 0, 2);
            glClear(GL_COLOR_BUFFER_BIT);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
            glDrawArrays(GL_TRIANGLES, 0, 3);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        }
    
        GLTexture tex2D;
        initOnePixelColorTexture2DSingleLayered(tex2D);
    
        // Check second case.
        {
            glUseProgram(singleViewProgram);
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D, 0, 0, 1);
            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);
            glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArr, 0, 0, 2);
            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()));
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        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()));
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        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()));
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        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()));
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            " : 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            " : 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, VS.c_str(), FS.c_str());
    
        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()));
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
    
        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()));
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        // 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 std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            " : 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
    
        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()));
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        updateFBOs(1, 1, 2);
    
        // Create multiview program.
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            " : 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, VS.c_str(), FS.c_str());
    
        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());
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            " : 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, VS.c_str(), FS.c_str());
        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());
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
        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());
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
        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;
        }
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "layout(num_views = 2) in;\n"
            "void main()\n"
            "{\n"
            "}\n";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "precision mediump float;\n"
            "void main()\n"
            "{\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str());
        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;
        }
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "precision mediump float;\n"
            "void main()\n"
            "{\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(program, VS.c_str(), FS.c_str());
        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;
        }
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "layout(num_views = 2) in;\n"
            "void main()\n"
            "{\n"
            "}\n";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
        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());
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
        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;
        }
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension);
        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;
        }
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension);
        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;
        }
        // Only this subtest fails on intel-hd-630-ubuntu-stable. Driver bug?
        // https://bugs.chromium.org/p/angleproject/issues/detail?id=3472
        ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension);
        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;
        }
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension);
        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;
        }
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        GLuint program = CreateSimplePassthroughProgram(2, GetParam().mMultiviewExtension);
        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);
    }
    
    // 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)
    {
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
        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;
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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 extensionName) -> std::string {
            std::string source =
                "#version 300 es\n"
                "#extension " +
                extensionName +
                ": 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, extensionName());
        ANGLE_GL_PROGRAM(program, vsSource.c_str(), FS.c_str());
        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, extensionName());
            updateFBOs(kViewWidth, kViewHeight, kNewNumViews);
    
            GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource.c_str());
            ASSERT_NE(0u, vs);
            GLuint fs = CompileShader(GL_FRAGMENT_SHADER, FS.c_str());
            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());
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        // 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, GetParam().mMultiviewExtension);
            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;
        }
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "layout(num_views = 3) in;\n"
            "in vec3 vPosition;\n"
            "void main()\n"
            "{\n"
            "   gl_Position = vec4(vPosition, 1.);\n"
            "}\n";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
    
        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)
    {
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
        if (!requestMultiviewExtension())
        {
            return;
        }
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": require\n"
            "layout(num_views = 2) in;\n"
            "in vec3 vPosition;\n"
            "void main()\n"
            "{\n"
            "   gl_Position = vec4(vPosition, 1.);\n"
            "}\n";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
    
        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;
        }
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
    
        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;
        }
    
        const std::string VS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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";
    
        const std::string FS =
            "#version 300 es\n"
            "#extension " +
            extensionName() +
            ": 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, VS.c_str(), FS.c_str());
    
        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));
    }
    
    MultiviewRenderTestParams VertexShaderOpenGL(ExtensionName multiviewExtension)
    {
        return MultiviewRenderTestParams(0, VertexShaderOpenGL(3, 0, multiviewExtension));
    }
    
    MultiviewRenderTestParams GeomShaderD3D11(ExtensionName multiviewExtension)
    {
        return MultiviewRenderTestParams(0, GeomShaderD3D11(3, 0, multiviewExtension));
    }
    
    MultiviewRenderTestParams VertexShaderD3D11(ExtensionName multiviewExtension)
    {
        return MultiviewRenderTestParams(0, VertexShaderD3D11(3, 0, multiviewExtension));
    }
    
    MultiviewRenderTestParams MultisampledVertexShaderOpenGL(ExtensionName multiviewExtension)
    {
        return MultiviewRenderTestParams(2, VertexShaderOpenGL(3, 1, multiviewExtension));
    }
    
    MultiviewRenderTestParams MultisampledVertexShaderD3D11(ExtensionName multiviewExtension)
    {
        return MultiviewRenderTestParams(2, VertexShaderD3D11(3, 1, multiviewExtension));
    }
    
    ANGLE_INSTANTIATE_TEST(MultiviewDrawValidationTest,
                           VertexShaderOpenGL(3, 1, ExtensionName::multiview),
                           VertexShaderD3D11(3, 1, ExtensionName::multiview),
                           VertexShaderOpenGL(3, 1, ExtensionName::multiview2),
                           VertexShaderD3D11(3, 1, ExtensionName::multiview2));
    ANGLE_INSTANTIATE_TEST(MultiviewRenderDualViewTest,
                           VertexShaderOpenGL(ExtensionName::multiview),
                           MultisampledVertexShaderOpenGL(ExtensionName::multiview),
                           GeomShaderD3D11(ExtensionName::multiview),
                           VertexShaderD3D11(ExtensionName::multiview),
                           MultisampledVertexShaderD3D11(ExtensionName::multiview),
                           VertexShaderOpenGL(ExtensionName::multiview2),
                           MultisampledVertexShaderOpenGL(ExtensionName::multiview2),
                           GeomShaderD3D11(ExtensionName::multiview2),
                           VertexShaderD3D11(ExtensionName::multiview2),
                           MultisampledVertexShaderD3D11(ExtensionName::multiview2));
    ANGLE_INSTANTIATE_TEST(MultiviewRenderTest,
                           VertexShaderOpenGL(ExtensionName::multiview),
                           MultisampledVertexShaderOpenGL(ExtensionName::multiview),
                           GeomShaderD3D11(ExtensionName::multiview),
                           VertexShaderD3D11(ExtensionName::multiview),
                           MultisampledVertexShaderD3D11(ExtensionName::multiview),
                           VertexShaderOpenGL(ExtensionName::multiview2),
                           MultisampledVertexShaderOpenGL(ExtensionName::multiview2),
                           GeomShaderD3D11(ExtensionName::multiview2),
                           VertexShaderD3D11(ExtensionName::multiview2),
                           MultisampledVertexShaderD3D11(ExtensionName::multiview2));
    ANGLE_INSTANTIATE_TEST(MultiviewOcclusionQueryTest,
                           VertexShaderOpenGL(ExtensionName::multiview),
                           GeomShaderD3D11(ExtensionName::multiview),
                           VertexShaderD3D11(ExtensionName::multiview),
                           VertexShaderOpenGL(ExtensionName::multiview2),
                           GeomShaderD3D11(ExtensionName::multiview2),
                           VertexShaderD3D11(ExtensionName::multiview2));
    ANGLE_INSTANTIATE_TEST(MultiviewProgramGenerationTest,
                           VertexShaderOpenGL(3, 0, ExtensionName::multiview),
                           GeomShaderD3D11(3, 0, ExtensionName::multiview),
                           VertexShaderD3D11(3, 0, ExtensionName::multiview),
                           VertexShaderOpenGL(3, 0, ExtensionName::multiview2),
                           GeomShaderD3D11(3, 0, ExtensionName::multiview2),
                           VertexShaderD3D11(3, 0, ExtensionName::multiview2));
    ANGLE_INSTANTIATE_TEST(MultiviewRenderPrimitiveTest,
                           VertexShaderOpenGL(ExtensionName::multiview),
                           GeomShaderD3D11(ExtensionName::multiview),
                           VertexShaderD3D11(ExtensionName::multiview),
                           VertexShaderOpenGL(ExtensionName::multiview2),
                           GeomShaderD3D11(ExtensionName::multiview2),
                           VertexShaderD3D11(ExtensionName::multiview2));
    ANGLE_INSTANTIATE_TEST(MultiviewLayeredRenderTest,
                           VertexShaderOpenGL(ExtensionName::multiview),
                           GeomShaderD3D11(ExtensionName::multiview),
                           VertexShaderOpenGL(ExtensionName::multiview2),
                           GeomShaderD3D11(ExtensionName::multiview2));