Handle multiview Draw* calls

Because the ANGLE_multiview extension uses instancing to multiply
geometry for each view, Draw* calls with an active multiview program
have to be handled in the follwing way:
1) Convert non-instanced Draw calls to their instanced versions.
2) Multiply the number of instances in an instanced Draw call by the
number of views.

BUG=angleproject:2062
TEST=angle_end2end_tests

Change-Id: I19119e8178f70336e5dbb1e5eed0658b5b9f43d7
Reviewed-on: https://chromium-review.googlesource.com/593657
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Martin Radev <mradev@nvidia.com>
diff --git a/src/tests/gl_tests/MultiviewDrawTest.cpp b/src/tests/gl_tests/MultiviewDrawTest.cpp
index 2c0aef1..010e271 100644
--- a/src/tests/gl_tests/MultiviewDrawTest.cpp
+++ b/src/tests/gl_tests/MultiviewDrawTest.cpp
@@ -84,6 +84,140 @@
     GLFramebuffer mFramebuffer;
 };
 
+class MultiviewSideBySideRenderTest : public MultiviewDrawTest
+{
+  protected:
+    void createFBO(int width, int height, int numViews)
+    {
+        // Assert that width is a multiple of numViews.
+        ASSERT_TRUE(width % numViews == 0);
+        const int widthPerView = width / numViews;
+
+        // Create color and depth textures.
+        glBindTexture(GL_TEXTURE_2D, mColorTexture);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        ASSERT_GL_NO_ERROR();
+
+        glBindTexture(GL_TEXTURE_2D, mDepthTexture);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0, GL_DEPTH_COMPONENT,
+                     GL_FLOAT, NULL);
+        glBindTexture(GL_TEXTURE_2D, 0);
+        ASSERT_GL_NO_ERROR();
+
+        // Create draw framebuffer to be used for side-by-side rendering.
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDrawFramebuffer);
+        std::vector<GLint> viewportOffsets(numViews * 2u);
+        for (int i = 0u; i < numViews; ++i)
+        {
+            viewportOffsets[i * 2]     = i * widthPerView;
+            viewportOffsets[i * 2 + 1] = 0;
+        }
+        glFramebufferTextureMultiviewSideBySideANGLE(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                                     mColorTexture, 0, numViews,
+                                                     &viewportOffsets[0]);
+        glFramebufferTextureMultiviewSideBySideANGLE(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                                                     mDepthTexture, 0, numViews,
+                                                     &viewportOffsets[0]);
+
+        GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
+        glDrawBuffers(1, DrawBuffers);
+        ASSERT_GL_NO_ERROR();
+        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER));
+
+        // Create read framebuffer to be used to retrieve the pixel information for testing
+        // purposes.
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer);
+        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                               mColorTexture, 0);
+        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_READ_FRAMEBUFFER));
+
+        // Clear the buffers.
+        glViewport(0, 0, width, height);
+        glScissor(0, 0, width, height);
+        glClearColor(0, 0, 0, 0);
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+        // Set viewport and scissor of each view.
+        glViewport(0, 0, widthPerView, height);
+        glScissor(0, 0, widthPerView, height);
+    }
+
+    GLTexture mColorTexture;
+    GLTexture mDepthTexture;
+    GLFramebuffer mDrawFramebuffer;
+    GLFramebuffer mReadFramebuffer;
+};
+
+class MultiviewSideBySideRenderDualViewTest : public MultiviewSideBySideRenderTest
+{
+  protected:
+    MultiviewSideBySideRenderDualViewTest() : mProgram(0u) {}
+    ~MultiviewSideBySideRenderDualViewTest()
+    {
+        if (mProgram != 0u)
+        {
+            glDeleteProgram(mProgram);
+        }
+    }
+
+    void SetUp() override
+    {
+        MultiviewSideBySideRenderTest::SetUp();
+
+        if (!requestMultiviewExtension())
+        {
+            return;
+        }
+
+        const std::string vsSource =
+            "#version 300 es\n"
+            "#extension GL_OVR_multiview : 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);\n"
+            "   gl_Position.yzw = vPosition.yzw;\n"
+            "}\n";
+
+        const std::string fsSource =
+            "#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";
+
+        createFBO(4, 1, 2);
+        createProgram(vsSource, fsSource);
+    }
+
+    void createProgram(const std::string &vs, const std::string &fs)
+    {
+        mProgram = CompileProgram(vs, fs);
+        if (mProgram == 0)
+        {
+            FAIL() << "shader compilation failed.";
+        }
+        glUseProgram(mProgram);
+        ASSERT_GL_NO_ERROR();
+    }
+
+    void checkOutput()
+    {
+        EXPECT_PIXEL_EQ(0, 0, 0, 0, 0, 0);
+        EXPECT_PIXEL_EQ(1, 0, 255, 0, 0, 0);
+        EXPECT_PIXEL_EQ(2, 0, 255, 0, 0, 0);
+        EXPECT_PIXEL_EQ(3, 0, 0, 0, 0, 0);
+    }
+
+    GLuint mProgram;
+};
+
 // 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.
@@ -344,4 +478,161 @@
     glDeleteQueries(1, &query);
 }
 
-ANGLE_INSTANTIATE_TEST(MultiviewDrawValidationTest, ES31_OPENGL());
\ No newline at end of file
+// The test checks that glDrawArrays can be used to render into two views.
+TEST_P(MultiviewSideBySideRenderDualViewTest, DrawArrays)
+{
+    if (!requestMultiviewExtension())
+    {
+        return;
+    }
+    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(MultiviewSideBySideRenderDualViewTest, DrawElements)
+{
+    if (!requestMultiviewExtension())
+    {
+        return;
+    }
+    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(MultiviewSideBySideRenderDualViewTest, DrawRangeElements)
+{
+    if (!requestMultiviewExtension())
+    {
+        return;
+    }
+    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(MultiviewSideBySideRenderTest, DrawArraysFourViews)
+{
+    if (!requestMultiviewExtension())
+    {
+        return;
+    }
+
+    const std::string vsSource =
+        "#version 300 es\n"
+        "#extension GL_OVR_multiview2 : 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 fsSource =
+        "#version 300 es\n"
+        "#extension GL_OVR_multiview2 : require\n"
+        "precision mediump float;\n"
+        "out vec4 col;\n"
+        "void main()\n"
+        "{\n"
+        "    col = vec4(1,0,0,0);\n"
+        "}\n";
+
+    createFBO(16, 1, 4);
+    ANGLE_GL_PROGRAM(program, vsSource, fsSource);
+    glUseProgram(program);
+
+    drawQuad(program, "vPosition", 0.0f, 1.0f, true);
+    ASSERT_GL_NO_ERROR();
+
+    for (int i = 0; i < 4; ++i)
+    {
+        for (int j = 0; j < 4; ++j)
+        {
+            const int arrayIndex = i * 4 + j;
+            if (i == j)
+            {
+                EXPECT_PIXEL_EQ(arrayIndex, 0, 255, 0, 0, 0);
+            }
+            else
+            {
+                EXPECT_PIXEL_EQ(arrayIndex, 0, 0, 0, 0, 0);
+            }
+        }
+    }
+    EXPECT_GL_NO_ERROR();
+}
+
+// The test checks that glDrawArraysInstanced can be used to render into two views.
+TEST_P(MultiviewSideBySideRenderTest, DrawArraysInstanced)
+{
+    if (!requestMultiviewExtension())
+    {
+        return;
+    }
+
+    const std::string vsSource =
+        "#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 = .5*p.y + .5;\n"
+        "       } else {\n"
+        "               p.y = p.y*.5;\n"
+        "       }\n"
+        "       gl_Position.x = (gl_ViewID_OVR == 0u ? p.x*0.5 + 0.5 : p.x*0.5);\n"
+        "       gl_Position.yzw = p.yzw;\n"
+        "}\n";
+
+    const std::string fsSource =
+        "#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";
+
+    createFBO(4, 2, 2);
+    ANGLE_GL_PROGRAM(program, vsSource, fsSource);
+    glUseProgram(program);
+
+    drawQuad(program, "vPosition", 0.0f, 1.0f, true, true, 2);
+    ASSERT_GL_NO_ERROR();
+
+    const GLubyte expectedResult[4][8] = {
+        {0, 255, 255, 0}, {0, 255, 255, 0},
+    };
+    for (int row = 0; row < 2; ++row)
+    {
+        for (int col = 0; col < 4; ++col)
+        {
+            EXPECT_PIXEL_EQ(col, row, expectedResult[row][col], 0, 0, 0);
+        }
+    }
+}
+
+ANGLE_INSTANTIATE_TEST(MultiviewDrawValidationTest, ES31_OPENGL());
+ANGLE_INSTANTIATE_TEST(MultiviewSideBySideRenderDualViewTest, ES3_OPENGL());
+ANGLE_INSTANTIATE_TEST(MultiviewSideBySideRenderTest, ES3_OPENGL());
\ No newline at end of file