Add shader translator support for OVR_multiview

The OVR_multiview and OVR_multiview2 extensions add gl_ViewID_OVR to
shaders. gl_ViewID_OVR can be translated either as is in GLSL output
or as a uniform by setting the SH_TRANSLATE_VIEWID_OVR_AS_UNIFORM
compiler flag.

If WebGL output is selected, the shaders will be validated according
to proposed rules in the WEBGL_multiview spec.

BUG=angleproject:1669
TEST=angle_unittests

Change-Id: I19ea3a6c8b4edb78be03f1a50a96bfef018870d0
Reviewed-on: https://chromium-review.googlesource.com/422848
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/ValidateMultiviewWebGL.cpp b/src/compiler/translator/ValidateMultiviewWebGL.cpp
new file mode 100644
index 0000000..11740c3
--- /dev/null
+++ b/src/compiler/translator/ValidateMultiviewWebGL.cpp
@@ -0,0 +1,394 @@
+//
+// Copyright (c) 2002-2016 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.
+//
+// ValidateMultiviewWebGL.cpp:
+//   Validate the AST according to rules in the WEBGL_multiview extension spec. Only covers those
+//   rules not already covered in ParseContext.
+//
+
+#include "compiler/translator/ValidateMultiviewWebGL.h"
+
+#include <array>
+
+#include "angle_gl.h"
+#include "compiler/translator/Diagnostics.h"
+#include "compiler/translator/FindSymbolNode.h"
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/SymbolTable.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class ValidateMultiviewTraverser : public TIntermTraverser
+{
+  public:
+    // Check for errors and write error messages to diagnostics. Returns true if there are no
+    // errors.
+    static bool validate(TIntermBlock *root,
+                         GLenum shaderType,
+                         bool multiview2,
+                         TDiagnostics *diagnostics);
+
+    bool isValid() { return mValid; }
+
+  protected:
+    void visitSymbol(TIntermSymbol *node) override;
+    bool visitBinary(Visit visit, TIntermBinary *node) override;
+    bool visitUnary(Visit visit, TIntermUnary *node) override;
+    bool visitIfElse(Visit visit, TIntermIfElse *node) override;
+    bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+
+  private:
+    ValidateMultiviewTraverser(GLenum shaderType, bool multiview2, TDiagnostics *diagnostics);
+
+    static bool IsGLPosition(TIntermNode *node);
+    static bool IsGLViewIDOVR(TIntermNode *node);
+    static bool IsSimpleAssignmentToGLPositionX(TIntermBinary *node);
+    static bool IsSimpleAssignmentToGLPosition(TIntermBinary *node);
+
+    void validateAndTraverseViewIDConditionalBlock(TIntermBlock *block, const char *token);
+
+    bool mValid;
+    bool mMultiview2;
+    GLenum mShaderType;
+
+    bool mInsideViewIDConditional;  // Only set if mMultiview2 is false.
+    bool mInsideRestrictedAssignment;
+    bool mGLPositionAllowed;
+    bool mViewIDOVRAllowed;
+
+    TDiagnostics *mDiagnostics;
+};
+
+bool ValidateMultiviewTraverser::validate(TIntermBlock *root,
+                                          GLenum shaderType,
+                                          bool multiview2,
+                                          TDiagnostics *diagnostics)
+{
+    ValidateMultiviewTraverser validate(shaderType, multiview2, diagnostics);
+    ASSERT(root);
+    root->traverse(&validate);
+    return validate.isValid();
+}
+
+ValidateMultiviewTraverser::ValidateMultiviewTraverser(GLenum shaderType,
+                                                       bool multiview2,
+                                                       TDiagnostics *diagnostics)
+    : TIntermTraverser(true, true, true),
+      mValid(true),
+      mMultiview2(multiview2),
+      mShaderType(shaderType),
+      mInsideViewIDConditional(false),
+      mInsideRestrictedAssignment(false),
+      mGLPositionAllowed(multiview2),
+      mViewIDOVRAllowed(multiview2),
+      mDiagnostics(diagnostics)
+{
+}
+
+bool ValidateMultiviewTraverser::IsGLPosition(TIntermNode *node)
+{
+    TIntermSymbol *symbolNode = node->getAsSymbolNode();
+    return (symbolNode && symbolNode->getName().getString() == "gl_Position");
+}
+
+bool ValidateMultiviewTraverser::IsGLViewIDOVR(TIntermNode *node)
+{
+    TIntermSymbol *symbolNode = node->getAsSymbolNode();
+    return (symbolNode && symbolNode->getName().getString() == "gl_ViewID_OVR");
+}
+
+bool ValidateMultiviewTraverser::IsSimpleAssignmentToGLPositionX(TIntermBinary *node)
+{
+    if (node->getOp() == EOpAssign)
+    {
+        TIntermSwizzle *leftAsSwizzle = node->getLeft()->getAsSwizzleNode();
+        if (leftAsSwizzle && IsGLPosition(leftAsSwizzle->getOperand()) &&
+            leftAsSwizzle->offsetsMatch(0))
+        {
+            return true;
+        }
+        TIntermBinary *leftAsBinary = node->getLeft()->getAsBinaryNode();
+        if (leftAsBinary && leftAsBinary->getOp() == EOpIndexDirect &&
+            IsGLPosition(leftAsBinary->getLeft()) &&
+            leftAsBinary->getRight()->getAsConstantUnion()->getIConst(0) == 0)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool ValidateMultiviewTraverser::IsSimpleAssignmentToGLPosition(TIntermBinary *node)
+{
+    if (node->getOp() == EOpAssign)
+    {
+        if (IsGLPosition(node->getLeft()))
+        {
+            return true;
+        }
+        TIntermSwizzle *leftAsSwizzle = node->getLeft()->getAsSwizzleNode();
+        if (leftAsSwizzle && IsGLPosition(leftAsSwizzle->getOperand()))
+        {
+            return true;
+        }
+        TIntermBinary *leftAsBinary = node->getLeft()->getAsBinaryNode();
+        if (leftAsBinary && leftAsBinary->getOp() == EOpIndexDirect &&
+            IsGLPosition(leftAsBinary->getLeft()))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+void ValidateMultiviewTraverser::visitSymbol(TIntermSymbol *node)
+{
+    if (IsGLPosition(node) && !mGLPositionAllowed)
+    {
+        ASSERT(!mMultiview2);
+        mDiagnostics->error(node->getLine(),
+                            "Disallowed use of gl_Position when using OVR_multiview",
+                            "gl_Position");
+        mValid = false;
+    }
+    else if (IsGLViewIDOVR(node) && !mViewIDOVRAllowed)
+    {
+        ASSERT(!mMultiview2);
+        mDiagnostics->error(node->getLine(),
+                            "Disallowed use of gl_ViewID_OVR when using OVR_multiview",
+                            "gl_ViewID_OVR");
+        mValid = false;
+    }
+    else if (!mMultiview2 && mShaderType == GL_FRAGMENT_SHADER)
+    {
+        std::array<const char *, 3> disallowedFragmentShaderSymbols{
+            {"gl_FragCoord", "gl_PointCoord", "gl_FrontFacing"}};
+        for (auto disallowedSymbol : disallowedFragmentShaderSymbols)
+        {
+            if (node->getSymbol() == disallowedSymbol)
+            {
+                mDiagnostics->error(
+                    node->getLine(),
+                    "Disallowed use of a built-in variable when using OVR_multiview",
+                    disallowedSymbol);
+                mValid = false;
+            }
+        }
+    }
+}
+
+bool ValidateMultiviewTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+    if (!mMultiview2 && mShaderType == GL_VERTEX_SHADER)
+    {
+        if (visit == PreVisit)
+        {
+            ASSERT(!mInsideViewIDConditional || mInsideRestrictedAssignment);
+            if (node->isAssignment())
+            {
+                if (mInsideRestrictedAssignment)
+                {
+                    mDiagnostics->error(node->getLine(),
+                                        "Disallowed assignment inside assignment to gl_Position.x "
+                                        "when using OVR_multiview",
+                                        GetOperatorString(node->getOp()));
+                    mValid = false;
+                }
+                else if (IsSimpleAssignmentToGLPositionX(node) &&
+                         FindSymbolNode(node->getRight(), "gl_ViewID_OVR", EbtUInt))
+                {
+                    if (!getParentNode()->getAsBlock())
+                    {
+                        mDiagnostics->error(node->getLine(),
+                                            "Disallowed use of assignment to gl_Position.x "
+                                            "when using OVR_multiview",
+                                            "=");
+                        mValid = false;
+                    }
+                    mInsideRestrictedAssignment = true;
+                    mViewIDOVRAllowed           = true;
+                    node->getRight()->traverse(this);
+                    mInsideRestrictedAssignment = false;
+                    mViewIDOVRAllowed           = false;
+                    return false;
+                }
+                else if (IsSimpleAssignmentToGLPosition(node))
+                {
+                    node->getRight()->traverse(this);
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+bool ValidateMultiviewTraverser::visitUnary(Visit visit, TIntermUnary *node)
+{
+    if (visit == PreVisit && !mMultiview2 && mInsideRestrictedAssignment)
+    {
+        if (node->isAssignment())
+        {
+            mDiagnostics->error(node->getLine(),
+                                "Disallowed unary operator inside assignment to gl_Position.x when "
+                                "using OVR_multiview",
+                                GetOperatorString(node->getOp()));
+            mValid = false;
+        }
+    }
+    return true;
+}
+
+void ValidateMultiviewTraverser::validateAndTraverseViewIDConditionalBlock(TIntermBlock *block,
+                                                                           const char *token)
+{
+    if (block->getSequence()->size() > 1)
+    {
+        mDiagnostics->error(block->getLine(),
+                            "Only one assignment to gl_Position allowed inside if block dependent "
+                            "on gl_ViewID_OVR when using OVR_multiview",
+                            token);
+        mValid = false;
+    }
+    else if (block->getSequence()->size() == 1)
+    {
+        TIntermBinary *assignment = block->getSequence()->at(0)->getAsBinaryNode();
+        if (assignment && IsSimpleAssignmentToGLPositionX(assignment))
+        {
+            mInsideRestrictedAssignment = true;
+            assignment->getRight()->traverse(this);
+            mInsideRestrictedAssignment = false;
+        }
+        else
+        {
+            mDiagnostics->error(block->getLine(),
+                                "Only one assignment to gl_Position.x allowed inside if block "
+                                "dependent on gl_ViewID_OVR when using OVR_multiview",
+                                token);
+            mValid = false;
+        }
+    }
+}
+
+bool ValidateMultiviewTraverser::visitIfElse(Visit visit, TIntermIfElse *node)
+{
+    if (!mMultiview2 && mShaderType == GL_VERTEX_SHADER)
+    {
+        ASSERT(visit == PreVisit);
+
+        // Check if the if statement refers to gl_ViewID_OVR and if it does so correctly
+        TIntermBinary *binaryCondition              = node->getCondition()->getAsBinaryNode();
+        bool conditionIsAllowedComparisonWithViewID = false;
+        if (binaryCondition && binaryCondition->getOp() == EOpEqual)
+        {
+            conditionIsAllowedComparisonWithViewID =
+                IsGLViewIDOVR(binaryCondition->getLeft()) &&
+                binaryCondition->getRight()->getAsConstantUnion() &&
+                binaryCondition->getRight()->getQualifier() == EvqConst;
+            if (!conditionIsAllowedComparisonWithViewID)
+            {
+                conditionIsAllowedComparisonWithViewID =
+                    IsGLViewIDOVR(binaryCondition->getRight()) &&
+                    binaryCondition->getLeft()->getAsConstantUnion() &&
+                    binaryCondition->getLeft()->getQualifier() == EvqConst;
+            }
+        }
+        if (conditionIsAllowedComparisonWithViewID)
+        {
+            mInsideViewIDConditional = true;
+            if (node->getTrueBlock())
+            {
+                validateAndTraverseViewIDConditionalBlock(node->getTrueBlock(), "if");
+            }
+            else
+            {
+                mDiagnostics->error(node->getLine(), "Expected assignment to gl_Position.x", "if");
+            }
+            if (node->getFalseBlock())
+            {
+                validateAndTraverseViewIDConditionalBlock(node->getFalseBlock(), "else");
+            }
+            mInsideViewIDConditional = false;
+        }
+        else
+        {
+            node->getCondition()->traverse(this);
+            if (node->getTrueBlock())
+            {
+                node->getTrueBlock()->traverse(this);
+            }
+            if (node->getFalseBlock())
+            {
+                node->getFalseBlock()->traverse(this);
+            }
+        }
+        return false;
+    }
+    return true;
+}
+
+bool ValidateMultiviewTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+    if (visit == PreVisit && !mMultiview2 && mInsideRestrictedAssignment)
+    {
+        // Check if the node is an user-defined function call or if an l-value is required, or if
+        // there are possible visible side effects, such as image writes.
+        if (node->getOp() == EOpFunctionCall)
+        {
+            if (node->isUserDefined())
+            {
+                mDiagnostics->error(node->getLine(),
+                                    "Disallowed user defined function call inside assignment to "
+                                    "gl_Position.x when using OVR_multiview",
+                                    GetOperatorString(node->getOp()));
+                mValid = false;
+            }
+            else if (TFunction::unmangleName(node->getFunctionSymbolInfo()->getName()) ==
+                     "imageStore")
+            {
+                // TODO(oetuaho@nvidia.com): Record which built-in functions have side effects in
+                // the symbol info instead.
+                mDiagnostics->error(node->getLine(),
+                                    "Disallowed function call with side effects inside assignment "
+                                    "to gl_Position.x when using OVR_multiview",
+                                    GetOperatorString(node->getOp()));
+                mValid = false;
+            }
+        }
+        else if (node->getOp() == EOpModf)
+        {
+            // TODO(oetuaho@nvidia.com): It's quite hacky to hard-code modf - should maybe refactor
+            // out parameter detecting functionality in LValueTrackingTraverser so that it could be
+            // used here as well?
+            // LValueTrackingTraverser itself seems like a bad fit with the needs of this traverser.
+            mDiagnostics->error(node->getLine(),
+                                "Disallowed use of a function with an out parameter inside "
+                                "assignment to gl_Position.x when using OVR_multiview",
+                                GetOperatorString(node->getOp()));
+            mValid = false;
+        }
+    }
+    return true;
+}
+
+}  // anonymous namespace
+
+bool ValidateMultiviewWebGL(TIntermBlock *root,
+                            GLenum shaderType,
+                            bool multiview2,
+                            TDiagnostics *diagnostics)
+{
+    if (shaderType == GL_VERTEX_SHADER && !FindSymbolNode(root, "gl_ViewID_OVR", EbtUInt))
+    {
+        return true;
+    }
+    return ValidateMultiviewTraverser::validate(root, shaderType, multiview2, diagnostics);
+}
+
+}  // namespace sh