Flatten "#pragma STDGL invariant(all)" into varying variables.

This is implemented as a compiler option which is enabled by default
when outputting to desktop GLSL version 130 and greater, which does
not support this #pragma in fragment shaders. As a workaround, and for
better compatibility on desktop OpenGL drivers, this pragma is also
flattened into the outputs of vertex shaders, and the inputs of ESSL
1.00 fragment shaders.

TEST=conformance/glsl/misc/shaders-with-invariance.html with --enable-unsafe-es3-apis
BUG=629622, angleproject:1293

Change-Id: Ib040230915e639971505ed496d26e804c9d64e68
Reviewed-on: https://chromium-review.googlesource.com/361792
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Yuly Novikov <ynovikov@chromium.org>
Commit-Queue: Kenneth Russell <kbr@chromium.org>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 55e74a1..06dc6cd 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -140,7 +140,8 @@
 }
 
 TCompiler::TCompiler(sh::GLenum type, ShShaderSpec spec, ShShaderOutput output)
-    : shaderType(type),
+    : variablesCollected(false),
+      shaderType(type),
       shaderSpec(spec),
       outputType(output),
       maxUniformVectors(0),
@@ -408,12 +409,20 @@
     return NULL;
 }
 
-bool TCompiler::compile(const char* const shaderStrings[],
-    size_t numStrings, int compileOptions)
+bool TCompiler::compile(const char *const shaderStrings[], size_t numStrings, int compileOptionsIn)
 {
     if (numStrings == 0)
         return true;
 
+    int compileOptions = compileOptionsIn;
+
+    // Apply key workarounds.
+    if (shouldFlattenPragmaStdglInvariantAll())
+    {
+        // This should be harmless to do in all cases, but for the moment, do it only conditionally.
+        compileOptions |= SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL;
+    }
+
     TScopedPoolAllocator scopedAlloc(&allocator);
     TIntermNode *root = compileTreeImpl(shaderStrings, numStrings, compileOptions);
 
@@ -577,6 +586,7 @@
     expandedUniforms.clear();
     varyings.clear();
     interfaceBlocks.clear();
+    variablesCollected = false;
 
     builtInFunctionEmulator.Cleanup();
 
@@ -833,12 +843,16 @@
 
 void TCompiler::collectVariables(TIntermNode* root)
 {
-    sh::CollectVariables collect(&attributes, &outputVariables, &uniforms, &varyings,
-                                 &interfaceBlocks, hashFunction, symbolTable, extensionBehavior);
-    root->traverse(&collect);
+    if (!variablesCollected)
+    {
+        sh::CollectVariables collect(&attributes, &outputVariables, &uniforms, &varyings,
+                                     &interfaceBlocks, hashFunction, symbolTable, extensionBehavior);
+        root->traverse(&collect);
 
-    // This is for enforcePackingRestriction().
-    sh::ExpandUniforms(uniforms, &expandedUniforms);
+        // This is for enforcePackingRestriction().
+        sh::ExpandUniforms(uniforms, &expandedUniforms);
+        variablesCollected = true;
+    }
 }
 
 bool TCompiler::enforcePackingRestrictions()
@@ -907,9 +921,26 @@
     return builtInFunctionEmulator;
 }
 
-void TCompiler::writePragma()
+void TCompiler::writePragma(int compileOptions)
 {
-    TInfoSinkBase &sink = infoSink.obj;
-    if (mPragma.stdgl.invariantAll)
-        sink << "#pragma STDGL invariant(all)\n";
+    if (!(compileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL))
+    {
+        TInfoSinkBase &sink = infoSink.obj;
+        if (mPragma.stdgl.invariantAll)
+            sink << "#pragma STDGL invariant(all)\n";
+    }
+}
+
+bool TCompiler::isVaryingDefined(const char *varyingName)
+{
+    ASSERT(variablesCollected);
+    for (size_t ii = 0; ii < varyings.size(); ++ii)
+    {
+        if (varyings[ii].name == varyingName)
+        {
+            return true;
+        }
+    }
+
+    return false;
 }
diff --git a/src/compiler/translator/Compiler.h b/src/compiler/translator/Compiler.h
index b982556..db5ec26 100644
--- a/src/compiler/translator/Compiler.h
+++ b/src/compiler/translator/Compiler.h
@@ -154,24 +154,29 @@
     const TExtensionBehavior& getExtensionBehavior() const;
     const char *getSourcePath() const;
     const TPragma& getPragma() const { return mPragma; }
-    void writePragma();
+    void writePragma(int compileOptions);
     unsigned int *getTemporaryIndex() { return &mTemporaryIndex; }
+    // Relies on collectVariables having been called.
+    bool isVaryingDefined(const char *varyingName);
 
     const ArrayBoundsClamper& getArrayBoundsClamper() const;
     ShArrayIndexClampingStrategy getArrayIndexClampingStrategy() const;
     const BuiltInFunctionEmulator& getBuiltInFunctionEmulator() const;
 
+    virtual bool shouldCollectVariables(int compileOptions)
+    {
+        return (compileOptions & SH_VARIABLES) != 0;
+    }
+
+    virtual bool shouldFlattenPragmaStdglInvariantAll() = 0;
+
     std::vector<sh::Attribute> attributes;
     std::vector<sh::OutputVariable> outputVariables;
     std::vector<sh::Uniform> uniforms;
     std::vector<sh::ShaderVariable> expandedUniforms;
     std::vector<sh::Varying> varyings;
     std::vector<sh::InterfaceBlock> interfaceBlocks;
-
-    virtual bool shouldCollectVariables(int compileOptions)
-    {
-        return (compileOptions & SH_VARIABLES) != 0;
-    }
+    bool variablesCollected;
 
   private:
     // Creates the function call DAG for further analysis, returning false if there is a recursion
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 2a5b60e..3476d88 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -1517,8 +1517,35 @@
                                                         const TSourceLoc &identifierOrTypeLocation,
                                                         const TString &identifier)
 {
-    TIntermSymbol *symbol =
-        intermediate.addSymbol(0, identifier, TType(publicType), identifierOrTypeLocation);
+    TType type(publicType);
+    if ((mCompileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL) &&
+        mDirectiveHandler.pragma().stdgl.invariantAll)
+    {
+        TQualifier qualifier = type.getQualifier();
+
+        // The directive handler has already taken care of rejecting invalid uses of this pragma
+        // (for example, in ESSL 3.00 fragment shaders), so at this point, flatten it into all
+        // affected variable declarations:
+        //
+        // 1. Built-in special variables which are inputs to the fragment shader. (These are handled
+        // elsewhere, in TranslatorGLSL.)
+        //
+        // 2. Outputs from vertex shaders in ESSL 1.00 and 3.00 (EvqVaryingOut and EvqVertexOut). It
+        // is actually less likely that there will be bugs in the handling of ESSL 3.00 shaders, but
+        // the way this is currently implemented we have to enable this compiler option before
+        // parsing the shader and determining the shading language version it uses. If this were
+        // implemented as a post-pass, the workaround could be more targeted.
+        //
+        // 3. Inputs in ESSL 1.00 fragment shaders (EvqVaryingIn). This is somewhat in violation of
+        // the specification, but there are desktop OpenGL drivers that expect that this is the
+        // behavior of the #pragma when specified in ESSL 1.00 fragment shaders.
+        if (qualifier == EvqVaryingOut || qualifier == EvqVertexOut || qualifier == EvqVaryingIn)
+        {
+            type.setInvariant(true);
+        }
+    }
+
+    TIntermSymbol *symbol = intermediate.addSymbol(0, identifier, type, identifierOrTypeLocation);
 
     bool emptyDeclaration = (identifier == "");
 
@@ -1541,7 +1568,7 @@
         checkCanBeDeclaredWithoutInitializer(identifierOrTypeLocation, identifier, &publicType);
 
         TVariable *variable = nullptr;
-        declareVariable(identifierOrTypeLocation, identifier, TType(publicType), &variable);
+        declareVariable(identifierOrTypeLocation, identifier, type, &variable);
 
         if (variable && symbol)
             symbol->setId(variable->getUniqueId());
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index ccc7e21..0fb6e37 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -42,6 +42,7 @@
           mDeferredSingleDeclarationErrorCheck(false),
           mShaderType(type),
           mShaderSpec(spec),
+          mCompileOptions(options),
           mShaderVersion(100),
           mTreeRoot(nullptr),
           mLoopNestingLevel(0),
@@ -406,6 +407,7 @@
 
     sh::GLenum mShaderType;              // vertex or fragment language (future: pack or unpack)
     ShShaderSpec mShaderSpec;              // The language specification compiler conforms to - GLES2 or WebGL.
+    int mCompileOptions;                   // Options passed to TCompiler
     int mShaderVersion;
     TIntermNode *mTreeRoot;       // root of parse tree being created
     int mLoopNestingLevel;       // 0 if outside all loops
diff --git a/src/compiler/translator/TranslatorESSL.cpp b/src/compiler/translator/TranslatorESSL.cpp
index c04b13d..392ea09 100644
--- a/src/compiler/translator/TranslatorESSL.cpp
+++ b/src/compiler/translator/TranslatorESSL.cpp
@@ -25,7 +25,8 @@
     }
 }
 
-void TranslatorESSL::translate(TIntermNode *root, int) {
+void TranslatorESSL::translate(TIntermNode *root, int compileOptions)
+{
     TInfoSinkBase& sink = getInfoSink().obj;
 
     int shaderVer = getShaderVersion();
@@ -39,7 +40,7 @@
 
     // Write pragmas after extensions because some drivers consider pragmas
     // like non-preprocessor tokens.
-    writePragma();
+    writePragma(compileOptions);
 
     bool precisionEmulation = getResources().WEBGL_debug_shader_precision && getPragma().debugShaderPrecision;
 
@@ -90,6 +91,12 @@
     root->traverse(&outputESSL);
 }
 
+bool TranslatorESSL::shouldFlattenPragmaStdglInvariantAll()
+{
+    // Not necessary when translating to ESSL.
+    return false;
+}
+
 void TranslatorESSL::writeExtensionBehavior() {
     TInfoSinkBase& sink = getInfoSink().obj;
     const TExtensionBehavior& extBehavior = getExtensionBehavior();
diff --git a/src/compiler/translator/TranslatorESSL.h b/src/compiler/translator/TranslatorESSL.h
index 2cc6107..0fbc47d 100644
--- a/src/compiler/translator/TranslatorESSL.h
+++ b/src/compiler/translator/TranslatorESSL.h
@@ -18,6 +18,7 @@
     void initBuiltInFunctionEmulator(BuiltInFunctionEmulator *emu, int compileOptions) override;
 
     void translate(TIntermNode *root, int compileOptions) override;
+    bool shouldFlattenPragmaStdglInvariantAll() override;
 
   private:
     void writeExtensionBehavior();
diff --git a/src/compiler/translator/TranslatorGLSL.cpp b/src/compiler/translator/TranslatorGLSL.cpp
index 7174233..dc45410 100644
--- a/src/compiler/translator/TranslatorGLSL.cpp
+++ b/src/compiler/translator/TranslatorGLSL.cpp
@@ -42,7 +42,38 @@
 
     // Write pragmas after extensions because some drivers consider pragmas
     // like non-preprocessor tokens.
-    writePragma();
+    writePragma(compileOptions);
+
+    // If flattening the global invariant pragma, write invariant declarations for built-in
+    // variables. It should be harmless to do this twice in the case that the shader also explicitly
+    // did this. However, it's important to emit invariant qualifiers only for those built-in
+    // variables that are actually used, to avoid affecting the behavior of the shader.
+    if ((compileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL) && getPragma().stdgl.invariantAll)
+    {
+        collectVariables(root);
+
+        switch (getShaderType())
+        {
+            case GL_VERTEX_SHADER:
+                sink << "invariant gl_Position;\n";
+
+                // gl_PointSize should be declared invariant in both ESSL 1.00 and 3.00 fragment
+                // shaders if it's statically referenced.
+                conditionallyOutputInvariantDeclaration("gl_PointSize");
+                break;
+            case GL_FRAGMENT_SHADER:
+                // The preprocessor will reject this pragma if it's used in ESSL 3.00 fragment
+                // shaders, so we can use simple logic to determine whether to declare these
+                // variables invariant.
+                conditionallyOutputInvariantDeclaration("gl_FragCoord");
+                conditionallyOutputInvariantDeclaration("gl_PointCoord");
+                break;
+            default:
+                // Currently not reached, but leave this in for future expansion.
+                ASSERT(false);
+                break;
+        }
+    }
 
     bool precisionEmulation = getResources().WEBGL_debug_shader_precision && getPragma().debugShaderPrecision;
 
@@ -152,6 +183,13 @@
     root->traverse(&outputGLSL);
 }
 
+bool TranslatorGLSL::shouldFlattenPragmaStdglInvariantAll()
+{
+    // Required when outputting to any GLSL version greater than 1.20, but since ANGLE doesn't
+    // translate to that version, return true for the next higher version.
+    return IsGLSL130OrNewer(getOutputType());
+}
+
 void TranslatorGLSL::writeVersion(TIntermNode *root)
 {
     TVersionGLSL versionGLSL(getShaderType(), getPragma(), getOutputType());
@@ -230,3 +268,12 @@
         sink << "#extension " << ext << " : require\n";
     }
 }
+
+void TranslatorGLSL::conditionallyOutputInvariantDeclaration(const char *builtinVaryingName)
+{
+    if (isVaryingDefined(builtinVaryingName))
+    {
+        TInfoSinkBase &sink = getInfoSink().obj;
+        sink << "invariant " << builtinVaryingName << ";\n";
+    }
+}
diff --git a/src/compiler/translator/TranslatorGLSL.h b/src/compiler/translator/TranslatorGLSL.h
index 4f07b21..91df588 100644
--- a/src/compiler/translator/TranslatorGLSL.h
+++ b/src/compiler/translator/TranslatorGLSL.h
@@ -18,10 +18,12 @@
     void initBuiltInFunctionEmulator(BuiltInFunctionEmulator *emu, int compileOptions) override;
 
     void translate(TIntermNode *root, int compileOptions) override;
+    bool shouldFlattenPragmaStdglInvariantAll() override;
 
   private:
     void writeVersion(TIntermNode *root);
     void writeExtensionBehavior(TIntermNode *root);
+    void conditionallyOutputInvariantDeclaration(const char *builtinVaryingName);
 };
 
 #endif  // COMPILER_TRANSLATOR_TRANSLATORGLSL_H_
diff --git a/src/compiler/translator/TranslatorHLSL.cpp b/src/compiler/translator/TranslatorHLSL.cpp
index 15ab7af..7273da9 100644
--- a/src/compiler/translator/TranslatorHLSL.cpp
+++ b/src/compiler/translator/TranslatorHLSL.cpp
@@ -99,6 +99,12 @@
     mUniformRegisterMap = outputHLSL.getUniformRegisterMap();
 }
 
+bool TranslatorHLSL::shouldFlattenPragmaStdglInvariantAll()
+{
+    // Not necessary when translating to HLSL.
+    return false;
+}
+
 bool TranslatorHLSL::hasInterfaceBlock(const std::string &interfaceBlockName) const
 {
     return (mInterfaceBlockRegisterMap.count(interfaceBlockName) > 0);
@@ -113,4 +119,4 @@
 const std::map<std::string, unsigned int> *TranslatorHLSL::getUniformRegisterMap() const
 {
     return &mUniformRegisterMap;
-}
\ No newline at end of file
+}
diff --git a/src/compiler/translator/TranslatorHLSL.h b/src/compiler/translator/TranslatorHLSL.h
index 40cfd70..f96c18d 100644
--- a/src/compiler/translator/TranslatorHLSL.h
+++ b/src/compiler/translator/TranslatorHLSL.h
@@ -22,6 +22,7 @@
 
   protected:
     void translate(TIntermNode *root, int compileOptions) override;
+    bool shouldFlattenPragmaStdglInvariantAll() override;
 
     // collectVariables needs to be run always so registers can be assigned.
     bool shouldCollectVariables(int compileOptions) override { return true; }
diff --git a/src/compiler/translator/Types.h b/src/compiler/translator/Types.h
index 6295fd4..2a85e01 100644
--- a/src/compiler/translator/Types.h
+++ b/src/compiler/translator/Types.h
@@ -326,6 +326,8 @@
         return invariant;
     }
 
+    void setInvariant(bool i) { invariant = i; }
+
     TLayoutQualifier getLayoutQualifier() const
     {
         return layoutQualifier;