ES31: Enable 'location' layout qualifier on shader interfaces in compiler

This patch enables 'location' layout qualifier for vertex outputs and
fragment shader inputs when the shader version is 3.1 in ANGLE GLSL
compiler and adds the check on location conflicts for these varyings.

According to GLSL ES 3.1 SPEC (Chapter 4.4.1 and Chapter 4.4.2),
'location' layout qualifier is allowed on both inputs and outputs of
vertex and fragment shaders.

'location' layout qualifier on shader interfaces is only valid on shaders
whose version is 3.1 and above. According to GLSL ES 3.0 SPEC, vertex shader
cannot have output layout qualifiers (Chapter 4.3.8.2) and fragment shader
cannot have input layout qualifiers (Chapter 4.3.8.1).

The 'location' qualifier on varyings is used in the shader interface
matching defined in OpenGL ES 3.1. (OpenGL ES 3.1 SPEC Chapter 7.4.1). This
new link rule will be added to Program.cpp in another patch.

For the OpenGL ES 3.1 extension GL_OES_geometry_shader, according to
GL_OES_shader_io_blocks SPEC (Chapter 4.4.1 and Chapter 4.4.2), 'location'
layout qualifier is both valid on geometry shader inputs and outputs. This
feature will be implemented together with other rules on geometry shader
inputs and outputs.

BUG=angleproject:2144
TEST=angle_unittests

Change-Id: I62d85f7144c177448321c2db36ed7aaeaa1fb205
Reviewed-on: https://chromium-review.googlesource.com/645366
Commit-Queue: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/compiler.gypi b/src/compiler.gypi
index 825160b..88cb456 100644
--- a/src/compiler.gypi
+++ b/src/compiler.gypi
@@ -154,6 +154,8 @@
             'compiler/translator/ValidateOutputs.h',
             'compiler/translator/ValidateSwitch.cpp',
             'compiler/translator/ValidateSwitch.h',
+            'compiler/translator/ValidateVaryingLocations.cpp',
+            'compiler/translator/ValidateVaryingLocations.h',
             'compiler/translator/VariablePacker.cpp',
             'compiler/translator/VariablePacker.h',
             'compiler/translator/blocklayout.cpp',
diff --git a/src/compiler/translator/CollectVariables.cpp b/src/compiler/translator/CollectVariables.cpp
index e846d95..5eccbf2 100644
--- a/src/compiler/translator/CollectVariables.cpp
+++ b/src/compiler/translator/CollectVariables.cpp
@@ -609,6 +609,7 @@
 
     Varying varying;
     setCommonVariableProperties(type, variable.getName(), &varying);
+    varying.location = type.getLayoutQualifier().location;
 
     switch (type.getQualifier())
     {
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 424cf68..99f46b2 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -40,6 +40,7 @@
 #include "compiler/translator/ValidateLimitations.h"
 #include "compiler/translator/ValidateMaxParameters.h"
 #include "compiler/translator/ValidateOutputs.h"
+#include "compiler/translator/ValidateVaryingLocations.h"
 #include "compiler/translator/VariablePacker.h"
 #include "third_party/compiler/ArrayBoundsClamper.h"
 
@@ -396,6 +397,11 @@
         if (success)
             PruneEmptyDeclarations(root);
 
+        if (success && shaderVersion >= 310)
+        {
+            success = ValidateVaryingLocations(root, &mDiagnostics);
+        }
+
         if (success && shaderVersion >= 300 && shaderType == GL_FRAGMENT_SHADER)
         {
             success = ValidateOutputs(root, getExtensionBehavior(), compileResources.MaxDrawBuffers,
diff --git a/src/compiler/translator/OutputGLSLBase.cpp b/src/compiler/translator/OutputGLSLBase.cpp
index 4b66306..95dc947 100644
--- a/src/compiler/translator/OutputGLSLBase.cpp
+++ b/src/compiler/translator/OutputGLSLBase.cpp
@@ -63,7 +63,8 @@
 
     const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
 
-    if ((type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn) &&
+    if ((type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn ||
+         IsVarying(type.getQualifier())) &&
         layoutQualifier.location >= 0)
     {
         return true;
@@ -206,7 +207,8 @@
 
     CommaSeparatedListItemPrefixGenerator listItemPrefix;
 
-    if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn)
+    if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn ||
+        IsVarying(type.getQualifier()))
     {
         if (layoutQualifier.location >= 0)
         {
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 1c6160f..efa9021 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -901,7 +901,7 @@
         if (mShaderVersion >= 310)
         {
             errorMsg =
-                "invalid layout qualifier: only valid on program inputs, outputs, and uniforms";
+                "invalid layout qualifier: only valid on shader inputs, outputs, and uniforms";
         }
         error(location, errorMsg, "location");
     }
@@ -1221,9 +1221,9 @@
     }
 
     bool canHaveLocation = qualifier == EvqVertexIn || qualifier == EvqFragmentOut;
-    if (mShaderVersion >= 310 && qualifier == EvqUniform)
+    if (mShaderVersion >= 310)
     {
-        canHaveLocation = true;
+        canHaveLocation = canHaveLocation || qualifier == EvqUniform || IsVarying(qualifier);
         // We're not checking whether the uniform location is in range here since that depends on
         // the type of the variable.
         // The type can only be fully determined for non-empty declarations.
diff --git a/src/compiler/translator/ShaderVars.cpp b/src/compiler/translator/ShaderVars.cpp
index ccfb8dd..7a6e181 100644
--- a/src/compiler/translator/ShaderVars.cpp
+++ b/src/compiler/translator/ShaderVars.cpp
@@ -156,22 +156,27 @@
 }
 
 bool ShaderVariable::isSameVariableAtLinkTime(const ShaderVariable &other,
-                                              bool matchPrecision) const
+                                              bool matchPrecision,
+                                              bool matchName) const
 {
     if (type != other.type)
         return false;
     if (matchPrecision && precision != other.precision)
         return false;
-    if (name != other.name)
+    if (matchName && name != other.name)
         return false;
-    ASSERT(mappedName == other.mappedName);
+    ASSERT(!matchName || mappedName == other.mappedName);
     if (arraySize != other.arraySize)
         return false;
     if (fields.size() != other.fields.size())
         return false;
+
+    // [OpenGL ES 3.1 SPEC Chapter 7.4.1]
+    // Variables declared as structures are considered to match in type if and only if structure
+    // members match in name, type, qualification, and declaration order.
     for (size_t ii = 0; ii < fields.size(); ++ii)
     {
-        if (!fields[ii].isSameVariableAtLinkTime(other.fields[ii], matchPrecision))
+        if (!fields[ii].isSameVariableAtLinkTime(other.fields[ii], matchPrecision, true))
         {
             return false;
         }
@@ -224,7 +229,7 @@
     {
         return false;
     }
-    return VariableWithLocation::isSameVariableAtLinkTime(other, true);
+    return VariableWithLocation::isSameVariableAtLinkTime(other, true, true);
 }
 
 VariableWithLocation::VariableWithLocation() : location(-1)
@@ -326,7 +331,7 @@
 bool InterfaceBlockField::isSameInterfaceBlockFieldAtLinkTime(
     const InterfaceBlockField &other) const
 {
-    return (ShaderVariable::isSameVariableAtLinkTime(other, true) &&
+    return (ShaderVariable::isSameVariableAtLinkTime(other, true, true) &&
             isRowMajorLayout == other.isRowMajorLayout);
 }
 
@@ -339,13 +344,15 @@
 }
 
 Varying::Varying(const Varying &other)
-    : ShaderVariable(other), interpolation(other.interpolation), isInvariant(other.isInvariant)
+    : VariableWithLocation(other),
+      interpolation(other.interpolation),
+      isInvariant(other.isInvariant)
 {
 }
 
 Varying &Varying::operator=(const Varying &other)
 {
-    ShaderVariable::operator=(other);
+    VariableWithLocation::operator=(other);
     interpolation           = other.interpolation;
     isInvariant             = other.isInvariant;
     return *this;
@@ -353,7 +360,7 @@
 
 bool Varying::operator==(const Varying &other) const
 {
-    return (ShaderVariable::operator==(other) && interpolation == other.interpolation &&
+    return (VariableWithLocation::operator==(other) && interpolation == other.interpolation &&
             isInvariant == other.isInvariant);
 }
 
@@ -364,9 +371,11 @@
 
 bool Varying::isSameVaryingAtLinkTime(const Varying &other, int shaderVersion) const
 {
-    return (ShaderVariable::isSameVariableAtLinkTime(other, false) &&
+    return (ShaderVariable::isSameVariableAtLinkTime(other, false, false) &&
             InterpolationTypesMatch(interpolation, other.interpolation) &&
-            (shaderVersion >= 300 || isInvariant == other.isInvariant));
+            (shaderVersion >= 300 || isInvariant == other.isInvariant) &&
+            (location == other.location) &&
+            (name == other.name || (shaderVersion >= 310 && location >= 0)));
 }
 
 InterfaceBlock::InterfaceBlock()
diff --git a/src/compiler/translator/ValidateVaryingLocations.cpp b/src/compiler/translator/ValidateVaryingLocations.cpp
new file mode 100644
index 0000000..f76d408
--- /dev/null
+++ b/src/compiler/translator/ValidateVaryingLocations.cpp
@@ -0,0 +1,158 @@
+//
+// Copyright (c) 2002-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.
+//
+// The ValidateVaryingLocations function checks if there exists location conflicts on shader
+// varyings.
+//
+
+#include "ValidateVaryingLocations.h"
+
+#include "compiler/translator/Diagnostics.h"
+#include "compiler/translator/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+void error(const TIntermSymbol &symbol, const char *reason, TDiagnostics *diagnostics)
+{
+    diagnostics->error(symbol.getLine(), reason, symbol.getSymbol().c_str());
+}
+
+int GetLocationCount(const TIntermSymbol *varying)
+{
+    const auto &varyingType = varying->getType();
+    if (varyingType.getStruct() != nullptr)
+    {
+        ASSERT(!varyingType.isArray());
+        int totalLocation = 0;
+        for (const auto *field : varyingType.getStruct()->fields())
+        {
+            const auto *fieldType = field->type();
+            ASSERT(fieldType->getStruct() == nullptr && !fieldType->isArray());
+
+            totalLocation += fieldType->getSecondarySize();
+        }
+        return totalLocation;
+    }
+    else
+    {
+        return varyingType.getSecondarySize() * static_cast<int>(varyingType.getArraySizeProduct());
+    }
+}
+
+using VaryingVector = std::vector<const TIntermSymbol *>;
+
+void ValidateShaderInterface(TDiagnostics *diagnostics, VaryingVector &varyingVector)
+{
+    // Location conflicts can only happen when there are two or more varyings in varyingVector.
+    if (varyingVector.size() <= 1)
+    {
+        return;
+    }
+
+    std::map<int, const TIntermSymbol *> locationMap;
+    for (const TIntermSymbol *varying : varyingVector)
+    {
+        const int location = varying->getType().getLayoutQualifier().location;
+        ASSERT(location >= 0);
+
+        const int elementCount = GetLocationCount(varying);
+        for (int elementIndex = 0; elementIndex < elementCount; ++elementIndex)
+        {
+            const int offsetLocation = location + elementIndex;
+            if (locationMap.find(offsetLocation) != locationMap.end())
+            {
+                std::stringstream strstr;
+                strstr << "'" << varying->getSymbol()
+                       << "' conflicting location with previously defined '"
+                       << locationMap[offsetLocation]->getSymbol() << "'";
+                error(*varying, strstr.str().c_str(), diagnostics);
+            }
+            else
+            {
+                locationMap[offsetLocation] = varying;
+            }
+        }
+    }
+}
+
+class ValidateVaryingLocationsTraverser : public TIntermTraverser
+{
+  public:
+    ValidateVaryingLocationsTraverser();
+    void validate(TDiagnostics *diagnostics);
+
+  private:
+    bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
+    bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override;
+
+    VaryingVector mInputVaryingsWithLocation;
+    VaryingVector mOutputVaryingsWithLocation;
+};
+
+ValidateVaryingLocationsTraverser::ValidateVaryingLocationsTraverser()
+    : TIntermTraverser(true, false, false)
+{
+}
+
+bool ValidateVaryingLocationsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
+{
+    const TIntermSequence &sequence = *(node->getSequence());
+    ASSERT(!sequence.empty());
+
+    const TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
+    if (symbol == nullptr)
+    {
+        return false;
+    }
+
+    // Collect varyings that have explicit 'location' qualifiers.
+    const TQualifier qualifier = symbol->getQualifier();
+    if (symbol->getType().getLayoutQualifier().location != -1)
+    {
+        if (IsVaryingIn(qualifier))
+        {
+            mInputVaryingsWithLocation.push_back(symbol);
+        }
+        else if (IsVaryingOut(qualifier))
+        {
+            mOutputVaryingsWithLocation.push_back(symbol);
+        }
+    }
+
+    return false;
+}
+
+bool ValidateVaryingLocationsTraverser::visitFunctionDefinition(Visit visit,
+                                                                TIntermFunctionDefinition *node)
+{
+    // We stop traversing function definitions because varyings cannot be defined in a function.
+    return false;
+}
+
+void ValidateVaryingLocationsTraverser::validate(TDiagnostics *diagnostics)
+{
+    ASSERT(diagnostics);
+
+    ValidateShaderInterface(diagnostics, mInputVaryingsWithLocation);
+    ValidateShaderInterface(diagnostics, mOutputVaryingsWithLocation);
+}
+
+}  // anonymous namespace
+
+bool ValidateVaryingLocations(TIntermBlock *root, TDiagnostics *diagnostics)
+{
+    ValidateVaryingLocationsTraverser varyingValidator;
+    root->traverse(&varyingValidator);
+    int numErrorsBefore = diagnostics->numErrors();
+    varyingValidator.validate(diagnostics);
+    return (diagnostics->numErrors() == numErrorsBefore);
+}
+
+}  // namespace sh
\ No newline at end of file
diff --git a/src/compiler/translator/ValidateVaryingLocations.h b/src/compiler/translator/ValidateVaryingLocations.h
new file mode 100644
index 0000000..a4dbab1
--- /dev/null
+++ b/src/compiler/translator/ValidateVaryingLocations.h
@@ -0,0 +1,23 @@
+//
+// Copyright (c) 2002-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.
+//
+// The ValidateVaryingLocations function checks if there exists location conflicts on shader
+// varyings.
+//
+
+#ifndef COMPILER_TRANSLATOR_VALIDATEVARYINGLOCATIONS_H_
+#define COMPILER_TRANSLATOR_VALIDATEVARYINGLOCATIONS_H_
+
+namespace sh
+{
+
+class TIntermBlock;
+class TDiagnostics;
+
+bool ValidateVaryingLocations(TIntermBlock *root, TDiagnostics *diagnostics);
+
+}  // namespace sh
+
+#endif
\ No newline at end of file
diff --git a/src/tests/compiler_tests/CollectVariables_test.cpp b/src/tests/compiler_tests/CollectVariables_test.cpp
index e49320e..5f9f5aa 100644
--- a/src/tests/compiler_tests/CollectVariables_test.cpp
+++ b/src/tests/compiler_tests/CollectVariables_test.cpp
@@ -44,7 +44,7 @@
         initTranslator(resources);
     }
 
-    void initTranslator(const ShBuiltInResources &resources)
+    virtual void initTranslator(const ShBuiltInResources &resources)
     {
         mTranslator.reset(
             new TranslatorGLSL(mShaderType, SH_GLES3_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT));
@@ -141,10 +141,25 @@
       CollectFragmentVariablesTest() : CollectVariablesTest(GL_FRAGMENT_SHADER) {}
 };
 
-class CollectVariablesOESGeometryShaderTest : public CollectVariablesTest
+class CollectVariablesTestES31 : public CollectVariablesTest
 {
   public:
-    CollectVariablesOESGeometryShaderTest(sh::GLenum shaderType) : CollectVariablesTest(shaderType)
+    CollectVariablesTestES31(sh::GLenum shaderType) : CollectVariablesTest(shaderType) {}
+
+  protected:
+    void initTranslator(const ShBuiltInResources &resources) override
+    {
+        mTranslator.reset(
+            new TranslatorGLSL(mShaderType, SH_GLES3_1_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT));
+        ASSERT_TRUE(mTranslator->Init(resources));
+    }
+};
+
+class CollectVariablesOESGeometryShaderTest : public CollectVariablesTestES31
+{
+  public:
+    CollectVariablesOESGeometryShaderTest(sh::GLenum shaderType)
+        : CollectVariablesTestES31(shaderType)
     {
     }
 
@@ -157,13 +172,6 @@
 
         initTranslator(resources);
     }
-
-    void initTranslator(const ShBuiltInResources &resources)
-    {
-        mTranslator.reset(
-            new TranslatorGLSL(mShaderType, SH_GLES3_1_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT));
-        ASSERT_TRUE(mTranslator->Init(resources));
-    }
 };
 
 class CollectGeometryVariablesTest : public CollectVariablesOESGeometryShaderTest
@@ -196,6 +204,26 @@
         : CollectVariablesOESGeometryShaderTest(GL_FRAGMENT_SHADER)
     {
     }
+
+  protected:
+    void initTranslator(const ShBuiltInResources &resources)
+    {
+        mTranslator.reset(
+            new TranslatorGLSL(mShaderType, SH_GLES3_1_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT));
+        ASSERT_TRUE(mTranslator->Init(resources));
+    }
+};
+
+class CollectVertexVariablesES31Test : public CollectVariablesTestES31
+{
+  public:
+    CollectVertexVariablesES31Test() : CollectVariablesTestES31(GL_VERTEX_SHADER) {}
+};
+
+class CollectFragmentVariablesES31Test : public CollectVariablesTestES31
+{
+  public:
+    CollectFragmentVariablesES31Test() : CollectVariablesTestES31(GL_FRAGMENT_SHADER) {}
 };
 
 TEST_F(CollectFragmentVariablesTest, SimpleOutputVar)
@@ -1207,3 +1235,56 @@
     EXPECT_GLENUM_EQ(GL_HIGH_INT, varying->precision);
     EXPECT_GLENUM_EQ(GL_INT, varying->type);
 }
+
+// Test collecting the location of vertex shader outputs.
+TEST_F(CollectVertexVariablesES31Test, CollectOutputWithLocation)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "out vec4 v_output1;\n"
+        "layout (location = 1) out vec4 v_output2;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    compile(shaderString);
+
+    const auto &outputVaryings = mTranslator->getOutputVaryings();
+    ASSERT_EQ(2u, outputVaryings.size());
+
+    const Varying *varying1 = &outputVaryings[0];
+    EXPECT_EQ("v_output1", varying1->name);
+    EXPECT_EQ(-1, varying1->location);
+
+    const Varying *varying2 = &outputVaryings[1];
+    EXPECT_EQ("v_output2", varying2->name);
+    EXPECT_EQ(1, varying2->location);
+}
+
+// Test collecting the location of fragment shader inputs.
+TEST_F(CollectFragmentVariablesES31Test, CollectInputWithLocation)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "precision mediump float;\n"
+        "in vec4 f_input1;\n"
+        "layout (location = 1) in vec4 f_input2;\n"
+        "layout (location = 0) out vec4 o_color;\n"
+        "void main()\n"
+        "{\n"
+        "    o_color = f_input2;\n"
+        "}\n";
+
+    compile(shaderString);
+
+    const auto &inputVaryings = mTranslator->getInputVaryings();
+    ASSERT_EQ(2u, inputVaryings.size());
+
+    const Varying *varying1 = &inputVaryings[0];
+    EXPECT_EQ("f_input1", varying1->name);
+    EXPECT_EQ(-1, varying1->location);
+
+    const Varying *varying2 = &inputVaryings[1];
+    EXPECT_EQ("f_input2", varying2->name);
+    EXPECT_EQ(1, varying2->location);
+}
diff --git a/src/tests/compiler_tests/ShaderValidation_test.cpp b/src/tests/compiler_tests/ShaderValidation_test.cpp
index c52a8a1..c345196 100644
--- a/src/tests/compiler_tests/ShaderValidation_test.cpp
+++ b/src/tests/compiler_tests/ShaderValidation_test.cpp
@@ -4833,4 +4833,182 @@
     {
         FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
     }
+}
+
+// Test that it isn't allowed to use 'location' layout qualifier on GLSL ES 3.0 vertex shader
+// outputs.
+TEST_F(VertexShaderValidationTest, UseLocationOnVertexOutES30)
+{
+    const std::string &shaderString =
+        "#version 300 es\n"
+        "in vec4 v1;\n"
+        "layout (location = 1) out vec4 o_color;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
+
+// Test that using 'location' layout qualifier on vertex shader outputs is legal in GLSL ES 3.1
+// shaders.
+TEST_F(VertexShaderValidationTest, UseLocationOnVertexOutES31)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "in vec4 v1;\n"
+        "layout (location = 1) out vec4 o_color1;\n"
+        "layout (location = 2) out vec4 o_color2;\n"
+        "out vec3 v3;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that it isn't allowed to use 'location' layout qualifier on GLSL ES 3.0 fragment shader
+// inputs.
+TEST_F(FragmentShaderValidationTest, UseLocationOnFragmentInES30)
+{
+    const std::string &shaderString =
+        "#version 300 es\n"
+        "precision mediump float;\n"
+        "layout (location = 0) in vec4 v_color1;\n"
+        "layout (location = 0) out vec4 o_color;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
+
+// Test that using 'location' layout qualifier on fragment shader inputs is legal in GLSL ES 3.1
+// shaders.
+TEST_F(FragmentShaderValidationTest, UseLocationOnFragmentInES31)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "precision mediump float;\n"
+        "layout (location = 0) in mat4 v_mat;\n"
+        "layout (location = 4) in vec4 v_color1;\n"
+        "in vec2 v_color2;\n"
+        "layout (location = 0) out vec4 o_color;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (!compile(shaderString))
+    {
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
+    }
+}
+
+// Test that declaring outputs of a vertex shader with same location causes a compile error.
+TEST_F(VertexShaderValidationTest, DeclareSameLocationOnVertexOut)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "in float i_value;\n"
+        "layout (location = 1) out vec4 o_color1;\n"
+        "layout (location = 1) out vec4 o_color2;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
+
+// Test that declaring inputs of a fragment shader with same location causes a compile error.
+TEST_F(FragmentShaderValidationTest, DeclareSameLocationOnFragmentIn)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "precision mediump float;\n"
+        "in float i_value;\n"
+        "layout (location = 1) in vec4 i_color1;\n"
+        "layout (location = 1) in vec4 i_color2;\n"
+        "layout (location = 0) out vec4 o_color;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
+
+// Test that the location of an element of an array conflicting with other output varyings in a
+// vertex shader causes a compile error.
+TEST_F(VertexShaderValidationTest, LocationConflictsnOnArrayElement)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "in float i_value;\n"
+        "layout (location = 0) out vec4 o_color1[3];\n"
+        "layout (location = 1) out vec4 o_color2;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
+
+// Test that the location of an element of a matrix conflicting with other output varyings in a
+// vertex shader causes a compile error.
+TEST_F(VertexShaderValidationTest, LocationConflictsOnMatrixElement)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "in float i_value;\n"
+        "layout (location = 0) out mat4 o_mvp;\n"
+        "layout (location = 2) out vec4 o_color;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
+}
+
+// Test that the location of an element of a struct conflicting with other output varyings in a
+// vertex shader causes a compile error.
+TEST_F(VertexShaderValidationTest, LocationConflictsOnStructElement)
+{
+    const std::string &shaderString =
+        "#version 310 es\n"
+        "in float i_value;\n"
+        "struct S\n"
+        "{\n"
+        "    float value1;\n"
+        "    vec3 value2;\n"
+        "};\n"
+        "layout (location = 0) out S s_in;"
+        "layout (location = 1) out vec4 o_color;\n"
+        "void main()\n"
+        "{\n"
+        "}\n";
+
+    if (compile(shaderString))
+    {
+        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+    }
 }
\ No newline at end of file
diff --git a/src/tests/compiler_tests/ShaderVariable_test.cpp b/src/tests/compiler_tests/ShaderVariable_test.cpp
index 58eaedd..2c06400 100644
--- a/src/tests/compiler_tests/ShaderVariable_test.cpp
+++ b/src/tests/compiler_tests/ShaderVariable_test.cpp
@@ -422,4 +422,53 @@
     sh::Destruct(compiler);
 }
 
+// Verify in ES3.1 two varyings with either same name or same declared location can match.
+TEST(ShaderVariableTest, IsSameVaryingWithDifferentName)
+{
+    // Varying float vary1;
+    Varying vx;
+    vx.type        = GL_FLOAT;
+    vx.arraySize   = 0;
+    vx.precision   = GL_MEDIUM_FLOAT;
+    vx.name        = "vary1";
+    vx.mappedName  = "m_vary1";
+    vx.staticUse   = true;
+    vx.isInvariant = false;
+
+    // Varying float vary2;
+    Varying fx;
+    fx.type        = GL_FLOAT;
+    fx.arraySize   = 0;
+    fx.precision   = GL_MEDIUM_FLOAT;
+    fx.name        = "vary2";
+    fx.mappedName  = "m_vary2";
+    fx.staticUse   = true;
+    fx.isInvariant = false;
+
+    // ESSL3 behavior: name must match
+    EXPECT_FALSE(vx.isSameVaryingAtLinkTime(fx, 300));
+
+    // ESSL3.1 behavior:
+    // [OpenGL ES 3.1 SPEC Chapter 7.4.1]
+    // An output variable is considered to match an input variable in the subsequent shader if:
+    // - the two variables match in name, type, and qualification; or
+    // - the two variables are declared with the same location qualifier and match in type and
+    //   qualification.
+    vx.location = 0;
+    fx.location = 0;
+    EXPECT_TRUE(vx.isSameVaryingAtLinkTime(fx, 310));
+
+    fx.name       = vx.name;
+    fx.mappedName = vx.mappedName;
+
+    fx.location = -1;
+    EXPECT_FALSE(vx.isSameVaryingAtLinkTime(fx, 310));
+
+    fx.location = 1;
+    EXPECT_FALSE(vx.isSameVaryingAtLinkTime(fx, 310));
+
+    fx.location = 0;
+    EXPECT_TRUE(vx.isSameVaryingAtLinkTime(fx, 310));
+}
+
 }  // namespace sh