Compiler: Allow deferred array sizing in geometry shaders

Based on work by Brandon Schade <b.schade@samsung.com>.

When a shader sets an array input size in a GS after declaring input
variables, compilation would fail.

Example shader -

 in vec3 normal[];
 in vec3 view_dir[];
 in float patch_dist[];
 in float e_patch_wire_scale[];
 ...
 layout(triangles) in;
 layout(triangle_strip, max_vertices=3) out;
 void main() {
 ...

Update translator to handle such cases.

Also add a new check that there are no remaining unsized
arrays when compilation has completed.

Test: GeometryShaderTest.DeferredSetOfArrayInputSize
Bug: angleproject:3571
Bug: angleproject:7125
Change-Id: I4853832c27f9551284bcca92b98cbf5f3a63aaf5
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3564259
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Cody Northrop <cnorthrop@google.com>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 62d231c..01cae10 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -461,7 +461,7 @@
         return nullptr;
     }
 
-    if (parseContext.getTreeRoot() == nullptr)
+    if (!postParseChecks(parseContext))
     {
         return nullptr;
     }
@@ -1119,6 +1119,29 @@
     return true;
 }
 
+bool TCompiler::postParseChecks(const TParseContext &parseContext)
+{
+    std::stringstream errorMessage;
+
+    if (parseContext.getTreeRoot() == nullptr)
+    {
+        errorMessage << "Shader parsing failed (mTreeRoot == nullptr)";
+    }
+
+    for (TType *type : parseContext.getDeferredArrayTypesToSize())
+    {
+        errorMessage << "Unsized global array type: " << type->getBasicString();
+    }
+
+    if (!errorMessage.str().empty())
+    {
+        mDiagnostics.globalError(errorMessage.str().c_str());
+        return false;
+    }
+
+    return true;
+}
+
 bool TCompiler::compile(const char *const shaderStrings[],
                         size_t numStrings,
                         ShCompileOptions compileOptionsIn)
diff --git a/src/compiler/translator/Compiler.h b/src/compiler/translator/Compiler.h
index f3ca016..6f90b19 100644
--- a/src/compiler/translator/Compiler.h
+++ b/src/compiler/translator/Compiler.h
@@ -291,6 +291,8 @@
                              const TParseContext &parseContext,
                              ShCompileOptions compileOptions);
 
+    bool postParseChecks(const TParseContext &parseContext);
+
     sh::GLenum mShaderType;
     ShShaderSpec mShaderSpec;
     ShShaderOutput mOutputType;
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 20788cb..80226fb 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -1238,9 +1238,11 @@
         }
     }
 
-    // Implicitly declared arrays are disallowed for shaders other than tessellation shaders.
-    if (mShaderType != GL_TESS_CONTROL_SHADER && mShaderType != GL_TESS_EVALUATION_SHADER &&
-        type->isArray())
+    // Implicitly declared arrays are only allowed with tessellation or geometry shader inputs
+    if (type->isArray() &&
+        ((mShaderType != GL_TESS_CONTROL_SHADER && mShaderType != GL_TESS_EVALUATION_SHADER &&
+          mShaderType != GL_GEOMETRY_SHADER) ||
+         (mShaderType == GL_GEOMETRY_SHADER && type->getQualifier() == EvqGeometryOut)))
     {
         const TSpan<const unsigned int> &arraySizes = type->getArraySizes();
         for (unsigned int size : arraySizes)
@@ -1248,8 +1250,8 @@
             if (size == 0)
             {
                 error(line,
-                      "implicitly sized arrays disallowed for shaders that are not tessellation "
-                      "shaders",
+                      "implicitly sized arrays only allowed for tessellation shaders "
+                      "or geometry shader inputs",
                       identifier);
             }
         }
@@ -2818,10 +2820,11 @@
                 // [GLSL ES 3.2 SPEC Chapter 4.4.1.2]
                 // An input can be declared without an array size if there is a previous layout
                 // which specifies the size.
-                error(location,
-                      "Missing a valid input primitive declaration before declaring an unsized "
-                      "array input",
-                      token);
+                warning(location,
+                        "Missing a valid input primitive declaration before declaring an unsized "
+                        "array input",
+                        "Deferred");
+                mDeferredArrayTypesToSize.push_back(type);
             }
         }
         else if (type->isArray())
@@ -2881,7 +2884,7 @@
                 // declared, this is deferred until such time as it does.
                 if (mTessControlShaderOutputVertices == 0)
                 {
-                    mTessControlDeferredArrayTypesToSize.push_back(type);
+                    mDeferredArrayTypesToSize.push_back(type);
                 }
                 else
                 {
@@ -3449,6 +3452,14 @@
                   "layout");
             return false;
         }
+
+        // Size any implicitly sized arrays that have already been declared.
+        for (TType *type : mDeferredArrayTypesToSize)
+        {
+            type->sizeOutermostUnsizedArray(
+                symbolTable.getGlInVariableWithArraySize()->getType().getOutermostArraySize());
+        }
+        mDeferredArrayTypesToSize.clear();
     }
 
     // Set mGeometryInvocations if exists
@@ -3539,10 +3550,11 @@
         mTessControlShaderOutputVertices = layoutQualifier.vertices;
 
         // Size any implicitly sized arrays that have already been declared.
-        for (TType *type : mTessControlDeferredArrayTypesToSize)
+        for (TType *type : mDeferredArrayTypesToSize)
         {
             type->sizeOutermostUnsizedArray(mTessControlShaderOutputVertices);
         }
+        mDeferredArrayTypesToSize.clear();
     }
     else
     {
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index 8057697..fa3e56a 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -499,6 +499,11 @@
         return mTessEvaluationShaderInputPointType;
     }
 
+    const TVector<TType *> &getDeferredArrayTypesToSize() const
+    {
+        return mDeferredArrayTypesToSize;
+    }
+
     void markShaderHasPrecise() { mHasAnyPreciseType = true; }
     bool hasAnyPreciseType() const { return mHasAnyPreciseType; }
     AdvancedBlendEquations getAdvancedBlendEquations() const { return mAdvancedBlendEquations; }
@@ -738,7 +743,7 @@
     TLayoutTessEvaluationType mTessEvaluationShaderInputPointType;
     // List of array declarations without an explicit size that have come before layout(vertices=N).
     // Once the vertex count is specified, these arrays are sized.
-    TVector<TType *> mTessControlDeferredArrayTypesToSize;
+    TVector<TType *> mDeferredArrayTypesToSize;
     // Whether the |precise| keyword has been seen in the shader.
     bool mHasAnyPreciseType;
 
diff --git a/src/tests/compiler_tests/GeometryShader_test.cpp b/src/tests/compiler_tests/GeometryShader_test.cpp
index 14c0624..f69120e 100644
--- a/src/tests/compiler_tests/GeometryShader_test.cpp
+++ b/src/tests/compiler_tests/GeometryShader_test.cpp
@@ -1220,8 +1220,8 @@
     }
 }
 
-// Verify that it is a compile error to declare an unsized Geometry Shader input before a valid
-// input primitive declaration.
+// Verify that compilation errors do not occur even if declaring an unsized Geometry Shader input
+// before a valid input primitive declaration.
 TEST_F(GeometryShaderTest, DeclareUnsizedInputBeforeInputPrimitive)
 {
     const std::string &shaderString1 =
@@ -1249,9 +1249,9 @@
             int length = texcoord2.length();
         })";
 
-    if (compile(shaderString1) || compile(shaderString2))
+    if (!compile(shaderString1) || !compile(shaderString2))
     {
-        FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
+        FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
     }
 }
 
diff --git a/src/tests/gl_tests/GeometryShaderTest.cpp b/src/tests/gl_tests/GeometryShaderTest.cpp
index 41ebfbe..6ac89d2 100644
--- a/src/tests/gl_tests/GeometryShaderTest.cpp
+++ b/src/tests/gl_tests/GeometryShaderTest.cpp
@@ -144,6 +144,42 @@
     EXPECT_GL_NO_ERROR();
 }
 
+// Verify that Geometry Shader can be compiled when geometry shader array input size
+// is set after shader input variables.
+// http://anglebug.com/7125 GFXBench Car Chase uses this pattern
+TEST_P(GeometryShaderTest, DeferredSetOfArrayInputSize)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
+    // "layout (invocations = 3, triangles) in;" should be declared before "in vec4 texcoord [];",
+    // but here it is declared later.
+    constexpr char kGS[]  = R"(#version 310 es
+#extension GL_EXT_geometry_shader : require
+in vec4 texcoord[];
+out vec4 o_texcoord;
+layout (invocations = 3, triangles_adjacency) in;
+layout (triangle_strip, max_vertices = 3) out;
+void main()
+{
+    int n;
+    for (n = 0; n < gl_in.length(); n++)
+    {
+        gl_Position = gl_in[n].gl_Position;
+        gl_Layer   = gl_InvocationID;
+        o_texcoord = texcoord[n];
+        EmitVertex();
+    }
+    EndPrimitive();
+})";
+    GLuint geometryShader = CompileShader(GL_GEOMETRY_SHADER_EXT, kGS);
+    EXPECT_NE(0u, geometryShader);
+    GLuint programID = glCreateProgram();
+    glAttachShader(programID, geometryShader);
+    glDetachShader(programID, geometryShader);
+    glDeleteShader(geometryShader);
+    glDeleteProgram(programID);
+    EXPECT_GL_NO_ERROR();
+}
+
 // Verify that all the implementation dependent geometry shader related resource limits meet the
 // requirement of GL_EXT_geometry_shader SPEC.
 TEST_P(GeometryShaderTest, GeometryShaderImplementationDependentLimits)