Handle Clear* commands for side-by-side framebuffers

Clear* commands for side-by-side framebuffers require special handling
because only the scissor rectangle of the first viewport is used in the
scissor test as defined in the OpenGL 4.1+ specs.

To enable clearing of each view of a side-by-side framebuffer all views
are iterated over, the corresponding scissor rectangle is set as first,
and a Clear* call is made to the driver. Afterwards the scissor state is
restored.

BUG=angleproject:2062
TEST=angle_end2end_tests

Change-Id: I138663ea61b4f0c9302872108e7dfbadf451b3ec
Reviewed-on: https://chromium-review.googlesource.com/590233
Commit-Queue: Martin Radev <mradev@nvidia.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index a53aa7d..4a9a413 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -103,6 +103,12 @@
     }
 }
 
+bool RequiresMultipleClears(const FramebufferAttachment *attachment)
+{
+    return attachment != nullptr &&
+           attachment->getMultiviewLayout() == GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE;
+}
+
 }  // namespace
 
 FramebufferGL::FramebufferGL(const FramebufferState &state,
@@ -213,7 +219,16 @@
 {
     syncClearState(context, mask);
     mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
-    mFunctions->clear(mask);
+
+    if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+    {
+        genericSideBySideClear(context, ClearCommandType::Clear, mask, GL_NONE, 0, nullptr, 0.0f,
+                               0);
+    }
+    else
+    {
+        mFunctions->clear(mask);
+    }
 
     return gl::NoError();
 }
@@ -225,7 +240,17 @@
 {
     syncClearBufferState(context, buffer, drawbuffer);
     mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
-    mFunctions->clearBufferfv(buffer, drawbuffer, values);
+
+    if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+    {
+        genericSideBySideClear(context, ClearCommandType::ClearBufferfv,
+                               static_cast<GLbitfield>(0u), buffer, drawbuffer,
+                               reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
+    }
+    else
+    {
+        mFunctions->clearBufferfv(buffer, drawbuffer, values);
+    }
 
     return gl::NoError();
 }
@@ -237,7 +262,17 @@
 {
     syncClearBufferState(context, buffer, drawbuffer);
     mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
-    mFunctions->clearBufferuiv(buffer, drawbuffer, values);
+
+    if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+    {
+        genericSideBySideClear(context, ClearCommandType::ClearBufferuiv,
+                               static_cast<GLbitfield>(0u), buffer, drawbuffer,
+                               reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
+    }
+    else
+    {
+        mFunctions->clearBufferuiv(buffer, drawbuffer, values);
+    }
 
     return gl::NoError();
 }
@@ -249,7 +284,17 @@
 {
     syncClearBufferState(context, buffer, drawbuffer);
     mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
-    mFunctions->clearBufferiv(buffer, drawbuffer, values);
+
+    if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+    {
+        genericSideBySideClear(context, ClearCommandType::ClearBufferiv,
+                               static_cast<GLbitfield>(0u), buffer, drawbuffer,
+                               reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
+    }
+    else
+    {
+        mFunctions->clearBufferiv(buffer, drawbuffer, values);
+    }
 
     return gl::NoError();
 }
@@ -262,11 +307,70 @@
 {
     syncClearBufferState(context, buffer, drawbuffer);
     mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
-    mFunctions->clearBufferfi(buffer, drawbuffer, depth, stencil);
+
+    if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+    {
+        genericSideBySideClear(context, ClearCommandType::ClearBufferfi,
+                               static_cast<GLbitfield>(0u), buffer, drawbuffer, nullptr, depth,
+                               stencil);
+    }
+    else
+    {
+        mFunctions->clearBufferfi(buffer, drawbuffer, depth, stencil);
+    }
 
     return gl::NoError();
 }
 
+void FramebufferGL::genericSideBySideClear(const gl::Context *context,
+                                           ClearCommandType clearCommandType,
+                                           GLbitfield mask,
+                                           GLenum buffer,
+                                           GLint drawbuffer,
+                                           const uint8_t *values,
+                                           GLfloat depth,
+                                           GLint stencil)
+{
+    // For side-by-side framebuffers we have to go over each view and set its scissor rectangle
+    // as the first one and just then call Clear*. This is necessary because Clear* commands use
+    // only the first viewport's scissor rectangle.
+    const auto &scissorBase                 = context->getGLState().getScissor();
+    const FramebufferAttachment *attachment = mState.getFirstNonNullAttachment();
+    ASSERT(attachment != nullptr);
+    const auto &viewportOffsets = attachment->getMultiviewViewportOffsets();
+
+    for (size_t i = 0u; i < viewportOffsets.size(); ++i)
+    {
+        gl::Rectangle scissor(scissorBase.x + viewportOffsets[i].x,
+                              scissorBase.y + viewportOffsets[i].y, scissorBase.width,
+                              scissorBase.height);
+        mStateManager->setScissorIndexed(0u, scissor);
+        switch (clearCommandType)
+        {
+            case ClearCommandType::Clear:
+                mFunctions->clear(mask);
+                break;
+            case ClearCommandType::ClearBufferfv:
+                mFunctions->clearBufferfv(buffer, drawbuffer,
+                                          reinterpret_cast<const GLfloat *>(values));
+                break;
+            case ClearCommandType::ClearBufferuiv:
+                mFunctions->clearBufferuiv(buffer, drawbuffer,
+                                           reinterpret_cast<const GLuint *>(values));
+                break;
+            case ClearCommandType::ClearBufferiv:
+                mFunctions->clearBufferiv(buffer, drawbuffer,
+                                          reinterpret_cast<const GLint *>(values));
+                break;
+            case ClearCommandType::ClearBufferfi:
+                mFunctions->clearBufferfi(buffer, drawbuffer, depth, stencil);
+                break;
+            default:
+                UNREACHABLE();
+        }
+    }
+}
+
 GLenum FramebufferGL::getImplementationColorReadFormat(const gl::Context *context) const
 {
     const auto *readAttachment = mState.getReadAttachment();
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.h b/src/libANGLE/renderer/gl/FramebufferGL.h
index ea6f4af..64b21b6 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.h
+++ b/src/libANGLE/renderer/gl/FramebufferGL.h
@@ -95,6 +95,17 @@
     void maskOutInactiveOutputDrawBuffers(gl::DrawBufferMask maxSet);
 
   private:
+    // Enum containing the different types of Clear* commands.
+    enum class ClearCommandType
+    {
+        Clear,
+        ClearBufferfv,
+        ClearBufferuiv,
+        ClearBufferiv,
+        ClearBufferfi
+    };
+
+  private:
     void syncClearState(const gl::Context *context, GLbitfield mask);
     void syncClearBufferState(const gl::Context *context, GLenum buffer, GLint drawBuffer);
 
@@ -118,6 +129,15 @@
                                   GLubyte *pixels,
                                   bool readLastRowSeparately) const;
 
+    void genericSideBySideClear(const gl::Context *context,
+                                ClearCommandType clearCommandType,
+                                GLbitfield mask,
+                                GLenum buffer,
+                                GLint drawbuffer,
+                                const uint8_t *values,
+                                GLfloat depth,
+                                GLint stencil);
+
     const FunctionsGL *mFunctions;
     StateManagerGL *mStateManager;
     const WorkaroundsGL &mWorkarounds;
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.cpp b/src/libANGLE/renderer/gl/StateManagerGL.cpp
index 98561f9..dbaad8b 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.cpp
+++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp
@@ -1069,6 +1069,18 @@
     }
 }
 
+void StateManagerGL::setScissorIndexed(GLuint index, const gl::Rectangle &scissor)
+{
+    ASSERT(mFunctions->scissorIndexed != nullptr);
+    ASSERT(static_cast<size_t>(index) < mScissors.size());
+    if (mScissors[index] != scissor)
+    {
+        mScissors[index] = scissor;
+        mFunctions->scissorIndexed(index, scissor.x, scissor.y, scissor.width, scissor.height);
+        mLocalDirtyBits.set(gl::State::DIRTY_BIT_SCISSOR);
+    }
+}
+
 void StateManagerGL::setViewport(const gl::Rectangle &viewport)
 {
     if (!AllRectanglesMatch(viewport, mViewports))
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.h b/src/libANGLE/renderer/gl/StateManagerGL.h
index 57b8272..f1c4694 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.h
+++ b/src/libANGLE/renderer/gl/StateManagerGL.h
@@ -76,6 +76,7 @@
 
     void setScissorTestEnabled(bool enabled);
     void setScissor(const gl::Rectangle &scissor);
+    void setScissorIndexed(GLuint index, const gl::Rectangle &scissor);
     void setScissorArrayv(GLuint first, const std::vector<gl::Rectangle> &viewports);
 
     void setViewport(const gl::Rectangle &viewport);
diff --git a/src/tests/gl_tests/FramebufferMultiviewTest.cpp b/src/tests/gl_tests/FramebufferMultiviewTest.cpp
index f6c4a90..d65705a 100644
--- a/src/tests/gl_tests/FramebufferMultiviewTest.cpp
+++ b/src/tests/gl_tests/FramebufferMultiviewTest.cpp
@@ -14,15 +14,24 @@
 namespace
 {
 
-GLuint CreateTexture2D(GLenum internalFormat, GLenum format, GLenum type)
+GLuint CreateTexture2D(GLenum internalFormat,
+                       GLenum format,
+                       GLenum type,
+                       GLsizei width,
+                       GLsizei height)
 {
     GLuint tex;
     glGenTextures(1, &tex);
     glBindTexture(GL_TEXTURE_2D, tex);
-    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, format, type, nullptr);
+    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, nullptr);
     return tex;
 }
 
+GLuint CreateTexture2D(GLenum internalFormat, GLenum format, GLenum type)
+{
+    return CreateTexture2D(internalFormat, format, type, 1, 1);
+}
+
 }  // namespace
 
 class FramebufferMultiviewTest : public ANGLETest
@@ -481,4 +490,56 @@
     EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
 }
 
+// Test that glClear clears only the contents of each view.
+TEST_P(FramebufferMultiviewTest, SideBySideClear)
+{
+    if (!requestMultiviewExtension())
+    {
+        return;
+    }
+
+    mTexture2D                     = CreateTexture2D(GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, 4, 2);
+    const GLint viewportOffsets[4] = {1, 0, 3, 0};
+    glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture2D,
+                                                 0, 2, &viewportOffsets[0]);
+
+    // Create and bind a normal framebuffer to access the 2D texture.
+    GLuint fbo;
+    glGenFramebuffers(1, &fbo);
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture2D, 0);
+
+    // Clear the contents of the texture.
+    glClearColor(0, 0, 0, 0);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    // Bind and specify viewport/scissor dimensions for each view.
+    glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
+    glViewport(0, 0, 1, 2);
+    glScissor(0, 0, 1, 2);
+
+    glClearColor(1, 0, 0, 0);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+    // column 0
+    EXPECT_PIXEL_EQ(0, 0, 0, 0, 0, 0);
+    EXPECT_PIXEL_EQ(0, 1, 0, 0, 0, 0);
+
+    // column 1
+    EXPECT_PIXEL_EQ(1, 0, 255, 0, 0, 0);
+    EXPECT_PIXEL_EQ(1, 1, 255, 0, 0, 0);
+
+    // column 2
+    EXPECT_PIXEL_EQ(2, 0, 0, 0, 0, 0);
+    EXPECT_PIXEL_EQ(2, 1, 0, 0, 0, 0);
+
+    // column 3
+    EXPECT_PIXEL_EQ(3, 0, 255, 0, 0, 0);
+    EXPECT_PIXEL_EQ(3, 1, 255, 0, 0, 0);
+
+    glDeleteFramebuffers(1, &fbo);
+}
+
 ANGLE_INSTANTIATE_TEST(FramebufferMultiviewTest, ES3_OPENGL());
\ No newline at end of file