Add branch for viewport or layer selection in VS

The patch extends the behavior of
SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER so that either the viewport
or layer is selected based on the value of the internal uniform variable
MultiviewRenderPath.

BUG=angleproject:2062
TEST=angle_end2end_tests
TEST=angle_unittests

Change-Id: Ia311b12b1fed642dac78eba8732e2535242f34fd
Reviewed-on: https://chromium-review.googlesource.com/615260
Commit-Queue: Martin Radev <mradev@nvidia.com>
Reviewed-by: Olli Etuaho <oetuaho@nvidia.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/compiler/translator/BaseTypes.h b/src/compiler/translator/BaseTypes.h
index b1974c1..336f3b7 100644
--- a/src/compiler/translator/BaseTypes.h
+++ b/src/compiler/translator/BaseTypes.h
@@ -576,6 +576,7 @@
     // GLSL ES 3.1 extension OES_geometry_shader qualifiers
     EvqGeometryIn,
     EvqGeometryOut,
+    EvqLayer,  // gl_Layer
 
     // end of list
     EvqLast
@@ -807,6 +808,7 @@
     case EvqSecondaryFragDataEXT:   return "SecondaryFragDataEXT";
     case EvqViewIDOVR:              return "ViewIDOVR";
     case EvqViewportIndex:          return "ViewportIndex";
+    case EvqLayer:                  return "Layer";
     case EvqLastFragColor:          return "LastFragColor";
     case EvqLastFragData:           return "LastFragData";
     case EvqSmoothOut:              return "smooth out";
diff --git a/src/compiler/translator/DeclareAndInitBuiltinsForInstancedMultiview.cpp b/src/compiler/translator/DeclareAndInitBuiltinsForInstancedMultiview.cpp
index 94fd6bf..68e5d35 100644
--- a/src/compiler/translator/DeclareAndInitBuiltinsForInstancedMultiview.cpp
+++ b/src/compiler/translator/DeclareAndInitBuiltinsForInstancedMultiview.cpp
@@ -109,23 +109,50 @@
     globalSequence->insert(globalSequence->begin(), declaration);
 }
 
-// Adds the expression gl_ViewportIndex = int(ViewID_OVR) to the end of the initializers' sequence.
-void SelectViewportIndexInVertexShader(TIntermTyped *viewIDSymbol, TIntermSequence *initializers)
+// Adds a branch to write int(ViewID_OVR) to either gl_ViewportIndex or gl_Layer. The branch is
+// added to the end of the initializers' sequence.
+void SelectViewIndexInVertexShader(TIntermTyped *viewIDSymbol,
+                                   TIntermTyped *multiviewBaseViewLayerIndexSymbol,
+                                   TIntermSequence *initializers)
 {
-    // Create a gl_ViewportIndex node.
-    TIntermSymbol *viewportIndexSymbol =
-        new TIntermSymbol(0, "gl_ViewportIndex", TType(EbtInt, EbpHigh, EvqViewportIndex));
-
     // Create an int(ViewID_OVR) node.
     TIntermSequence *viewIDSymbolCastArguments = new TIntermSequence();
     viewIDSymbolCastArguments->push_back(viewIDSymbol);
     TIntermAggregate *viewIDAsInt = TIntermAggregate::CreateConstructor(
         TType(EbtInt, EbpHigh, EvqTemporary), viewIDSymbolCastArguments);
 
-    // Create a gl_ViewportIndex = int(ViewID_OVR) node.
-    TIntermBinary *viewIDInitializer =
-        new TIntermBinary(EOpAssign, viewportIndexSymbol, viewIDAsInt);
-    initializers->push_back(viewIDInitializer);
+    // Create a gl_ViewportIndex node.
+    TIntermSymbol *viewportIndexSymbol =
+        new TIntermSymbol(0, "gl_ViewportIndex", TType(EbtInt, EbpHigh, EvqViewportIndex));
+
+    // Create a { gl_ViewportIndex = int(ViewID_OVR) } node.
+    TIntermBlock *viewportIndexInitializerInBlock = new TIntermBlock();
+    viewportIndexInitializerInBlock->appendStatement(
+        new TIntermBinary(EOpAssign, viewportIndexSymbol, viewIDAsInt));
+
+    // Create a gl_Layer node.
+    TIntermSymbol *layerSymbol = new TIntermSymbol(0, "gl_Layer", TType(EbtInt, EbpHigh, EvqLayer));
+
+    // Create an int(ViewID_OVR) + multiviewBaseViewLayerIndex node
+    TIntermBinary *sumOfViewIDAndBaseViewIndex =
+        new TIntermBinary(EOpAdd, viewIDAsInt->deepCopy(), multiviewBaseViewLayerIndexSymbol);
+
+    // Create a { gl_Layer = int(ViewID_OVR) + multiviewBaseViewLayerIndex } node.
+    TIntermBlock *layerInitializerInBlock = new TIntermBlock();
+    layerInitializerInBlock->appendStatement(
+        new TIntermBinary(EOpAssign, layerSymbol, sumOfViewIDAndBaseViewIndex));
+
+    // Create a node to compare whether the base view index uniform is less than zero.
+    TIntermBinary *multiviewBaseViewLayerIndexZeroComparison =
+        new TIntermBinary(EOpLessThan, multiviewBaseViewLayerIndexSymbol->deepCopy(),
+                          CreateZeroNode(TType(EbtInt, EbpHigh, EvqConst)));
+
+    // Create an if-else statement to select the code path.
+    TIntermIfElse *multiviewBranch =
+        new TIntermIfElse(multiviewBaseViewLayerIndexZeroComparison,
+                          viewportIndexInitializerInBlock, layerInitializerInBlock);
+
+    initializers->push_back(multiviewBranch);
 }
 
 }  // namespace
@@ -162,15 +189,24 @@
 
         // The AST transformation which adds the expression to select the viewport index should
         // be done only for the GLSL and ESSL output.
-        const bool selectViewport =
-            (compileOptions & SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER) != 0u;
-        // Assert that if the viewport is selected in the vertex shader, then the output is
+        const bool selectView = (compileOptions & SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER) != 0u;
+        // Assert that if the view is selected in the vertex shader, then the output is
         // either GLSL or ESSL.
-        ASSERT(!selectViewport || IsOutputGLSL(shaderOutput) || IsOutputESSL(shaderOutput));
-        if (selectViewport)
+        ASSERT(!selectView || IsOutputGLSL(shaderOutput) || IsOutputESSL(shaderOutput));
+        if (selectView)
         {
-            // Setting a value to gl_ViewportIndex should happen after ViewID_OVR's initialization.
-            SelectViewportIndexInVertexShader(viewIDSymbol->deepCopy(), initializers);
+            // Add a uniform to switch between side-by-side and layered rendering.
+            TIntermSymbol *multiviewBaseViewLayerIndexSymbol =
+                new TIntermSymbol(symbolTable->nextUniqueId(), "multiviewBaseViewLayerIndex",
+                                  TType(EbtInt, EbpHigh, EvqUniform));
+            multiviewBaseViewLayerIndexSymbol->setInternal(true);
+            DeclareGlobalVariable(root, multiviewBaseViewLayerIndexSymbol);
+
+            // Setting a value to gl_ViewportIndex or gl_Layer should happen after ViewID_OVR's
+            // initialization.
+            SelectViewIndexInVertexShader(viewIDSymbol->deepCopy(),
+                                          multiviewBaseViewLayerIndexSymbol->deepCopy(),
+                                          initializers);
         }
 
         // Insert initializers at the beginning of main().
diff --git a/src/compiler/translator/DeclareAndInitBuiltinsForInstancedMultiview.h b/src/compiler/translator/DeclareAndInitBuiltinsForInstancedMultiview.h
index 201f2a2..b4ab05f 100644
--- a/src/compiler/translator/DeclareAndInitBuiltinsForInstancedMultiview.h
+++ b/src/compiler/translator/DeclareAndInitBuiltinsForInstancedMultiview.h
@@ -14,8 +14,14 @@
 // - Add initializers of ViewID_OVR and InstanceID to the beginning of the body of main. The pass
 // should be executed before any variables get collected so that usage of gl_InstanceID is recorded.
 // - If the output is ESSL or GLSL and the SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER option is
-// enabled, the expression "gl_ViewportIndex = int(ViewID_OVR)" is added after ViewID and InstanceID
-// are initialized.
+// enabled, the expression
+// "if (multiviewBaseViewLayerIndex < 0) {
+//      gl_ViewportIndex = int(ViewID_OVR);
+//  } else {
+//      gl_Layer = int(ViewID_OVR) + multiviewBaseViewLayerIndex;
+//  }"
+// is added after ViewID and InstanceID are initialized. Also, MultiviewRenderPath is added as a
+// uniform.
 //
 
 #ifndef COMPILER_TRANSLATOR_DECLAREANDINITBUILTINSFORINSTANCEDMULTIVIEW_H_
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index 202abbc..3892f1f 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -483,6 +483,16 @@
     return attachment->getMultiviewLayout();
 }
 
+int FramebufferState::getBaseViewIndex() const
+{
+    const FramebufferAttachment *attachment = getFirstNonNullAttachment();
+    if (attachment == nullptr)
+    {
+        return GL_NONE;
+    }
+    return attachment->getBaseViewIndex();
+}
+
 Framebuffer::Framebuffer(const Caps &caps, rx::GLImplFactory *factory, GLuint id)
     : mState(caps),
       mImpl(factory->createFramebuffer(mState)),
@@ -1652,6 +1662,11 @@
     return mState.getNumViews();
 }
 
+GLint Framebuffer::getBaseViewIndex() const
+{
+    return mState.getBaseViewIndex();
+}
+
 const std::vector<Offset> *Framebuffer::getViewportOffsets() const
 {
     return mState.getViewportOffsets();
diff --git a/src/libANGLE/Framebuffer.h b/src/libANGLE/Framebuffer.h
index cad55f0..1d0191c 100644
--- a/src/libANGLE/Framebuffer.h
+++ b/src/libANGLE/Framebuffer.h
@@ -95,6 +95,7 @@
     GLenum getMultiviewLayout() const;
     GLsizei getNumViews() const;
     const std::vector<Offset> *getViewportOffsets() const;
+    GLint getBaseViewIndex() const;
 
   private:
     friend class Framebuffer;
@@ -185,6 +186,7 @@
     const FramebufferAttachment *getAttachment(GLenum attachment) const;
     GLenum getMultiviewLayout() const;
     GLsizei getNumViews() const;
+    GLint getBaseViewIndex() const;
     const std::vector<Offset> *getViewportOffsets() const;
 
     size_t getDrawbufferStateCount() const;
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index 8d3db69..3690fb7 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -280,6 +280,9 @@
     GLuint getSamplerIndexFromUniformIndex(GLuint uniformIndex) const;
     GLuint getAttributeLocation(const std::string &name) const;
 
+    int getNumViews() const { return mNumViews; }
+    bool usesMultiview() const { return mNumViews != -1; }
+
   private:
     friend class MemoryProgramCache;
     friend class Program;
@@ -537,8 +540,8 @@
     const Bindings &getUniformLocationBindings() const { return mUniformLocationBindings; }
     const Bindings &getFragmentInputBindings() const { return mFragmentInputBindings; }
 
-    int getNumViews() const { return mState.mNumViews; }
-    bool usesMultiview() const { return mState.mNumViews != -1; }
+    int getNumViews() const { return mState.getNumViews(); }
+    bool usesMultiview() const { return mState.usesMultiview(); }
 
   private:
     ~Program();
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index 7b6a258..ffd1a60 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -95,12 +95,14 @@
 
 void RetrieveMultiviewFieldsFromAttachment(const gl::FramebufferAttachment *attachment,
                                            const std::vector<gl::Offset> **viewportOffsets,
-                                           GLenum *multiviewLayout)
+                                           GLenum *multiviewLayout,
+                                           int *baseViewIndex)
 {
     if (attachment)
     {
         *viewportOffsets = &attachment->getMultiviewViewportOffsets();
         *multiviewLayout = attachment->getMultiviewLayout();
+        *baseViewIndex   = attachment->getBaseViewIndex();
     }
 }
 
@@ -596,6 +598,7 @@
 
     const std::vector<gl::Offset> *attachmentViewportOffsets = nullptr;
     GLenum multiviewLayout                                   = GL_NONE;
+    int baseViewIndex                                        = -1;
     bool isAttachmentModified                                = false;
 
     for (auto dirtyBit : dirtyBits)
@@ -606,14 +609,16 @@
                 BindFramebufferAttachment(mFunctions, GL_DEPTH_ATTACHMENT,
                                           mState.getDepthAttachment());
                 RetrieveMultiviewFieldsFromAttachment(mState.getDepthAttachment(),
-                                                      &attachmentViewportOffsets, &multiviewLayout);
+                                                      &attachmentViewportOffsets, &multiviewLayout,
+                                                      &baseViewIndex);
                 isAttachmentModified = true;
                 break;
             case Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT:
                 BindFramebufferAttachment(mFunctions, GL_STENCIL_ATTACHMENT,
                                           mState.getStencilAttachment());
                 RetrieveMultiviewFieldsFromAttachment(mState.getStencilAttachment(),
-                                                      &attachmentViewportOffsets, &multiviewLayout);
+                                                      &attachmentViewportOffsets, &multiviewLayout,
+                                                      &baseViewIndex);
                 isAttachmentModified = true;
                 break;
             case Framebuffer::DIRTY_BIT_DRAW_BUFFERS:
@@ -654,7 +659,8 @@
                                           static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + index),
                                           mState.getColorAttachment(index));
                 RetrieveMultiviewFieldsFromAttachment(mState.getColorAttachment(index),
-                                                      &attachmentViewportOffsets, &multiviewLayout);
+                                                      &attachmentViewportOffsets, &multiviewLayout,
+                                                      &baseViewIndex);
                 isAttachmentModified = true;
                 break;
             }
@@ -674,6 +680,9 @@
             mStateManager->setViewportOffsets(
                 FramebufferAttachment::GetDefaultViewportOffsetVector());
         }
+
+        mStateManager->updateMultiviewBaseViewLayerIndexUniform(context->getGLState().getProgram(),
+                                                                getState());
     }
 }
 
diff --git a/src/libANGLE/renderer/gl/ProgramGL.cpp b/src/libANGLE/renderer/gl/ProgramGL.cpp
index 2ea74e2..3c1fe40 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.cpp
+++ b/src/libANGLE/renderer/gl/ProgramGL.cpp
@@ -35,6 +35,7 @@
       mWorkarounds(workarounds),
       mStateManager(stateManager),
       mEnablePathRendering(enablePathRendering),
+      mMultiviewBaseViewLayerIndexUniformLocation(-1),
       mProgramID(0)
 {
     ASSERT(mFunctions);
@@ -590,6 +591,8 @@
     mUniformRealLocationMap.clear();
     mUniformBlockRealLocationMap.clear();
     mPathRenderingFragmentInputs.clear();
+
+    mMultiviewBaseViewLayerIndexUniformLocation = -1;
 }
 
 bool ProgramGL::checkLinkStatus(gl::InfoLog &infoLog)
@@ -658,6 +661,13 @@
         mUniformRealLocationMap[uniformLocation] = realLocation;
     }
 
+    if (mState.usesMultiview())
+    {
+        mMultiviewBaseViewLayerIndexUniformLocation =
+            mFunctions->getUniformLocation(mProgramID, "webgl_angle_multiviewBaseViewLayerIndex");
+        ASSERT(mMultiviewBaseViewLayerIndexUniformLocation != -1);
+    }
+
     // Discover CHROMIUM_path_rendering fragment inputs if enabled.
     if (!mEnablePathRendering)
         return;
@@ -724,4 +734,23 @@
     }
 }
 
+void ProgramGL::enableSideBySideRenderingPath() const
+{
+    ASSERT(mState.usesMultiview());
+    ASSERT(mMultiviewBaseViewLayerIndexUniformLocation != -1);
+
+    ASSERT(mFunctions->programUniform1i != nullptr);
+    mFunctions->programUniform1i(mProgramID, mMultiviewBaseViewLayerIndexUniformLocation, -1);
+}
+
+void ProgramGL::enableLayeredRenderingPath(int baseViewIndex) const
+{
+    ASSERT(mState.usesMultiview());
+    ASSERT(mMultiviewBaseViewLayerIndexUniformLocation != -1);
+
+    ASSERT(mFunctions->programUniform1i != nullptr);
+    mFunctions->programUniform1i(mProgramID, mMultiviewBaseViewLayerIndexUniformLocation,
+                                 baseViewIndex);
+}
+
 }  // namespace rx
diff --git a/src/libANGLE/renderer/gl/ProgramGL.h b/src/libANGLE/renderer/gl/ProgramGL.h
index 29ffb1d..ea8acf8 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.h
+++ b/src/libANGLE/renderer/gl/ProgramGL.h
@@ -78,6 +78,9 @@
 
     GLuint getProgramID() const;
 
+    void enableSideBySideRenderingPath() const;
+    void enableLayeredRenderingPath(int baseViewIndex) const;
+
   private:
     void preLink();
     bool checkLinkStatus(gl::InfoLog &infoLog);
@@ -102,6 +105,7 @@
     std::vector<PathRenderingFragmentInput> mPathRenderingFragmentInputs;
 
     bool mEnablePathRendering;
+    GLint mMultiviewBaseViewLayerIndexUniformLocation;
 
     GLuint mProgramID;
 };
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.cpp b/src/libANGLE/renderer/gl/StateManagerGL.cpp
index 9918f6f..72c816e 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.cpp
+++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp
@@ -1885,8 +1885,13 @@
                 // TODO(jmadill): implement this
                 break;
             case gl::State::DIRTY_BIT_DRAW_FRAMEBUFFER_BINDING:
+            {
                 // TODO(jmadill): implement this
+                updateMultiviewBaseViewLayerIndexUniform(
+                    state.getProgram(),
+                    state.getDrawFramebuffer()->getImplementation()->getState());
                 break;
+            }
             case gl::State::DIRTY_BIT_RENDERBUFFER_BINDING:
                 // TODO(jmadill): implement this
                 break;
@@ -1902,6 +1907,9 @@
                 // TODO(jmadill): implement this
                 propagateNumViewsToVAO(state.getProgram(),
                                        GetImplAs<VertexArrayGL>(state.getVertexArray()));
+                updateMultiviewBaseViewLayerIndexUniform(
+                    state.getProgram(),
+                    state.getDrawFramebuffer()->getImplementation()->getState());
                 break;
             case gl::State::DIRTY_BIT_PROGRAM_EXECUTABLE:
                 // TODO(jmadill): implement this
@@ -2159,4 +2167,25 @@
         vao->applyNumViewsToDivisor(programNumViews);
     }
 }
+
+void StateManagerGL::updateMultiviewBaseViewLayerIndexUniform(
+    const gl::Program *program,
+    const gl::FramebufferState &drawFramebufferState) const
+{
+    if (mIsMultiviewEnabled && program != nullptr && program->usesMultiview())
+    {
+        const ProgramGL *programGL = GetImplAs<ProgramGL>(program);
+        switch (drawFramebufferState.getMultiviewLayout())
+        {
+            case GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE:
+                programGL->enableSideBySideRenderingPath();
+                break;
+            case GL_FRAMEBUFFER_MULTIVIEW_LAYERED_ANGLE:
+                programGL->enableLayeredRenderingPath(drawFramebufferState.getBaseViewIndex());
+                break;
+            default:
+                break;
+        }
+    }
+}
 }
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.h b/src/libANGLE/renderer/gl/StateManagerGL.h
index 8fb9a7f..98a42be 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.h
+++ b/src/libANGLE/renderer/gl/StateManagerGL.h
@@ -22,6 +22,7 @@
 struct Caps;
 class ContextState;
 class State;
+class FramebufferState;
 }
 
 namespace rx
@@ -180,6 +181,10 @@
 
     void syncState(const gl::Context *context, const gl::State::DirtyBits &glDirtyBits);
 
+    void updateMultiviewBaseViewLayerIndexUniform(
+        const gl::Program *program,
+        const gl::FramebufferState &drawFramebufferState) const;
+
   private:
     // Set state that's common among draw commands and compute invocations.
     void setGenericShaderState(const gl::Context *context);
diff --git a/src/tests/compiler_tests/WEBGL_multiview_test.cpp b/src/tests/compiler_tests/WEBGL_multiview_test.cpp
index 3175bbf..976f84e 100644
--- a/src/tests/compiler_tests/WEBGL_multiview_test.cpp
+++ b/src/tests/compiler_tests/WEBGL_multiview_test.cpp
@@ -931,4 +931,34 @@
     EXPECT_LT(viewIDOVRAssignmentLoc, glViewportIndexAssignmentLoc);
 }
 
+// The test checks that the layer is selected after the initialization of ViewID_OVR for
+// GLSL and ESSL ouputs.
+TEST_F(WEBGLMultiviewVertexShaderOutputCodeTest, GlLayerIsSet)
+{
+    const std::string &shaderString =
+        "#version 300 es\n"
+        "#extension GL_OVR_multiview : require\n"
+        "layout(num_views = 3) in;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+    compile(shaderString, SH_INITIALIZE_BUILTINS_FOR_INSTANCED_MULTIVIEW |
+                              SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER);
+    const char glLayerAssignment[] =
+        "gl_Layer = (int(webgl_angle_ViewID_OVR) + webgl_angle_multiviewBaseViewLayerIndex)";
+
+    // Check that the layer is selected.
+    EXPECT_TRUE(foundInAllGLSLCode(glLayerAssignment));
+
+    // Setting gl_Layer must happen after ViewID_OVR's initialization.
+    const char viewIDOVRAssignment[] = "webgl_angle_ViewID_OVR = (uint(gl_InstanceID) % 3u)";
+    size_t viewIDOVRAssignmentLoc = findInCode(SH_GLSL_COMPATIBILITY_OUTPUT, viewIDOVRAssignment);
+    size_t glLayerAssignmentLoc   = findInCode(SH_GLSL_COMPATIBILITY_OUTPUT, glLayerAssignment);
+    EXPECT_LT(viewIDOVRAssignmentLoc, glLayerAssignmentLoc);
+
+    viewIDOVRAssignmentLoc = findInCode(SH_ESSL_OUTPUT, viewIDOVRAssignment);
+    glLayerAssignmentLoc   = findInCode(SH_ESSL_OUTPUT, glLayerAssignment);
+    EXPECT_LT(viewIDOVRAssignmentLoc, glLayerAssignmentLoc);
+}
+
 }  // namespace
\ No newline at end of file