Validate fragment shader outputs and produce a compile error on missing or conflicting assignments.

TRAC #22704

Signed-off-by: Geoff Lang
Signed-off-by: Nicolas Capens
Authored-by: Jamie Madill
diff --git a/src/compiler/Compiler.cpp b/src/compiler/Compiler.cpp
index 8aef072..7b3c646 100644
--- a/src/compiler/Compiler.cpp
+++ b/src/compiler/Compiler.cpp
@@ -14,6 +14,7 @@
 #include "compiler/RenameFunction.h"
 #include "compiler/ShHandle.h"
 #include "compiler/ValidateLimitations.h"
+#include "compiler/ValidateOutputs.h"
 #include "compiler/VariablePacker.h"
 #include "compiler/depgraph/DependencyGraph.h"
 #include "compiler/depgraph/DependencyGraphOutput.h"
@@ -143,6 +144,9 @@
         if (success)
             success = detectRecursion(root);
 
+        if (success && shaderVersion == 300 && shaderType == SH_FRAGMENT_SHADER)
+            success = validateOutputs(root);
+
         if (success && (compileOptions & SH_VALIDATE_LOOP_INDEXING))
             success = validateLimitations(root);
 
@@ -272,6 +276,13 @@
     }
 }
 
+bool TCompiler::validateOutputs(TIntermNode* root)
+{
+    ValidateOutputs validateOutputs(infoSink.info, compileResources.MaxDrawBuffers);
+    root->traverse(&validateOutputs);
+    return (validateOutputs.numErrors() == 0);
+}
+
 void TCompiler::rewriteCSSShader(TIntermNode* root)
 {
     RenameFunction renamer("main(", "css_main(");
diff --git a/src/compiler/ParseHelper.cpp b/src/compiler/ParseHelper.cpp
index a30bd27..31af70d 100644
--- a/src/compiler/ParseHelper.cpp
+++ b/src/compiler/ParseHelper.cpp
@@ -2320,17 +2320,16 @@
     }
     else
     {
+        // must check that location is non-negative
         if (intValue < 0)
         {
-            error(intValueLine, "out of range", intValueString.c_str(), "value must be non-negative and < MAX_DRAW_BUFFERS");
+            error(intValueLine, "out of range:", intValueString.c_str(), "location must be non-negative");
             recover();
         }
         else
         {
             qualifier.location = intValue;
         }
-
-        // TODO: must check that location is < MAX_DRAW_BUFFERS
     }
 
     return qualifier;
diff --git a/src/compiler/ShHandle.h b/src/compiler/ShHandle.h
index 5b70307..5f8d9d0 100644
--- a/src/compiler/ShHandle.h
+++ b/src/compiler/ShHandle.h
@@ -86,6 +86,8 @@
     void clearResults();
     // Return true if function recursion is detected.
     bool detectRecursion(TIntermNode* root);
+    // Returns true if a program has no conflicting or missing fragment outputs
+    bool validateOutputs(TIntermNode* root);
     // Rewrites a shader's intermediate tree according to the CSS Shaders spec.
     void rewriteCSSShader(TIntermNode* root);
     // Returns true if the given shader does not exceed the minimum
diff --git a/src/compiler/ValidateOutputs.cpp b/src/compiler/ValidateOutputs.cpp
new file mode 100644
index 0000000..d7f294c
--- /dev/null
+++ b/src/compiler/ValidateOutputs.cpp
@@ -0,0 +1,78 @@
+//
+// Copyright (c) 2013 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.
+//
+
+#include "compiler/ValidateOutputs.h"
+#include "compiler/InfoSink.h"
+#include "compiler/InitializeParseContext.h"
+#include "compiler/ParseHelper.h"
+
+ValidateOutputs::ValidateOutputs(TInfoSinkBase& sink, int maxDrawBuffers)
+    : mSink(sink),
+      mMaxDrawBuffers(maxDrawBuffers),
+      mNumErrors(0),
+      mHasUnspecifiedOutputLocation(false)
+{
+}
+
+void ValidateOutputs::visitSymbol(TIntermSymbol *symbol)
+{
+    TString name = symbol->getSymbol();
+    TQualifier qualifier = symbol->getQualifier();
+
+    if (mVisitedSymbols.count(name) == 1)
+        return;
+
+    mVisitedSymbols.insert(name);
+
+    if (qualifier == EvqFragmentOutput)
+    {
+        const TType &type = symbol->getType();
+        const int location = type.getLayoutQualifier().location;
+
+        if (mHasUnspecifiedOutputLocation)
+        {
+            error(symbol->getLine(), "must explicitly specify all locations when using multiple fragment outputs", name.c_str());
+        }
+        else if (location == -1)
+        {
+            mHasUnspecifiedOutputLocation = true;
+        }
+        else
+        {
+            OutputMap::iterator mapEntry = mOutputMap.find(location);
+            if (mapEntry == mOutputMap.end())
+            {
+                const int elementCount = type.isArray() ? type.getArraySize() : 1;
+                if (location + elementCount > mMaxDrawBuffers)
+                {
+                    error(symbol->getLine(), "output location must be < MAX_DRAW_BUFFERS", name.c_str());
+                }
+
+                for (int elementIndex = 0; elementIndex < elementCount; elementIndex++)
+                {
+                    const int offsetLocation = location + elementIndex;
+                    mOutputMap[offsetLocation] = symbol;
+                }
+            }
+            else
+            {
+                std::stringstream strstr;
+                strstr << "conflicting output locations with previously defined output '"
+                       << mapEntry->second->getSymbol() << "'";
+
+                error(symbol->getLine(), strstr.str().c_str(), name.c_str());
+            }
+        }
+    }
+}
+
+void ValidateOutputs::error(TSourceLoc loc, const char *reason, const char* token)
+{
+    mSink.prefix(EPrefixError);
+    mSink.location(loc);
+    mSink << "'" << token << "' : " << reason << "\n";
+    mNumErrors++;
+}
diff --git a/src/compiler/ValidateOutputs.h b/src/compiler/ValidateOutputs.h
new file mode 100644
index 0000000..1f81124
--- /dev/null
+++ b/src/compiler/ValidateOutputs.h
@@ -0,0 +1,34 @@
+//
+// Copyright (c) 2013 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.
+//
+
+#include "GLSLANG/ShaderLang.h"
+#include "compiler/intermediate.h"
+
+#include <set>
+
+class TInfoSinkBase;
+
+class ValidateOutputs : public TIntermTraverser
+{
+  public:
+    ValidateOutputs(TInfoSinkBase& sink, int maxDrawBuffers);
+
+    int numErrors() const { return mNumErrors; }
+
+    virtual void visitSymbol(TIntermSymbol*);
+
+  private:
+    TInfoSinkBase& mSink;
+    int mMaxDrawBuffers;
+    int mNumErrors;
+    bool mHasUnspecifiedOutputLocation;
+
+    typedef std::map<int, TIntermSymbol*> OutputMap;
+    OutputMap mOutputMap;
+    std::set<TString> mVisitedSymbols;
+
+    void error(TSourceLoc loc, const char *reason, const char* token);
+};
diff --git a/src/compiler/translator_common.vcxproj b/src/compiler/translator_common.vcxproj
index ac4d4c5..2899354 100644
--- a/src/compiler/translator_common.vcxproj
+++ b/src/compiler/translator_common.vcxproj
@@ -164,6 +164,7 @@
     <ClCompile Include="SymbolTable.cpp" />

     <ClCompile Include="util.cpp" />

     <ClCompile Include="ValidateLimitations.cpp" />

+    <ClCompile Include="ValidateOutputs.cpp" />

     <ClCompile Include="VariableInfo.cpp" />

     <ClCompile Include="VariablePacker.cpp" />

     <ClCompile Include="glslang_lex.cpp" />

@@ -258,6 +259,7 @@
     <ClInclude Include="Types.h" />

     <ClInclude Include="util.h" />

     <ClInclude Include="ValidateLimitations.h" />

+    <ClInclude Include="ValidateOutputs.h" />

     <ClInclude Include="VariableInfo.h" />

     <ClInclude Include="VariablePacker.h" />

     <ClInclude Include="glslang_tab.h" />

diff --git a/src/compiler/translator_common.vcxproj.filters b/src/compiler/translator_common.vcxproj.filters
index 7dc64e4..93b9df6 100644
--- a/src/compiler/translator_common.vcxproj.filters
+++ b/src/compiler/translator_common.vcxproj.filters
@@ -140,6 +140,9 @@
     <ClCompile Include="..\common\utilities.cpp">

       <Filter>Source Files</Filter>

     </ClCompile>

+    <ClCompile Include="ValidateOutputs.cpp">

+      <Filter>Source Files</Filter>

+    </ClCompile>

   </ItemGroup>

   <ItemGroup>

     <ClInclude Include="BaseTypes.h">

@@ -262,6 +265,9 @@
     <ClInclude Include="..\third_party\compiler\ArrayBoundsClamper.h">

       <Filter>Header Files</Filter>

     </ClInclude>

+    <ClInclude Include="ValidateOutputs.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

   </ItemGroup>

   <ItemGroup>

     <CustomBuild Include="glslang.l">