Implement ES 2,3 parts of EXT_blend_func_extended for shader translation

Exposes gl_SecondaryFragColor, glSecondaryFragData[] and
gl_MaxDualSourceDrawBuffers to GLES SL 1.0.

Relaxes rules for undefined output locations for GLES SL 3.0
and exposes gl_MaxDualSourceDrawBuffers.

If the output GL context is GL ES 2.0 or 3.0:
The emulation layer is expected to turn on EXT_blend_func_extended
if the output GL context supports it.

If the output GL context is GL:
The emulation layer is expected to turn on EXT_blend_func_extended
if the output GL context supports ARB_blend_func_extended or if GL
context is 3.2 or later.

If the source shader spec is GLES SL 2.0: The emulation layer is
expected to inspect the shader compilation output variables upon
linking. If output target is GL SL, the emulation layer should bind
color location 0, index 1 to "angle_SecondaryFragColor" if variable
"gl_SecondaryFragColorEXT" is used. Alternatively, emulation layer
should bind "angle_SecondaryFragData" to locations 0,1,2,3,..., all
color index 1, if "gl_SecondaryFragData" array is used.
(The caller can not bind the locations or specify output variables.)

If the source shader spec is GLES SL 3.0:
The emulation layer is expected to do location auto-resolve of the
the output variables that have undefined output locations that have
not been bound by the caller.
(The caller can not use gl_ built-ins, so nothing to do with those.)

BUG=angleproject:1085
TEST=angle_unittest

Change-Id: I5cafe205b0c29478b0dcd24aa89a7b0000f5d046
Reviewed-on: https://chromium-review.googlesource.com/287580
Reviewed-by: Zhenyao Mo <zmo@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Kimmo Kinnunen <kkinnunen@nvidia.com>
diff --git a/src/compiler/translator/BaseTypes.h b/src/compiler/translator/BaseTypes.h
index e43c236..2adb1d3 100644
--- a/src/compiler/translator/BaseTypes.h
+++ b/src/compiler/translator/BaseTypes.h
@@ -290,18 +290,18 @@
 //
 enum TQualifier
 {
-    EvqTemporary,     // For temporaries (within a function), read/write
-    EvqGlobal,        // For globals read/write
-    EvqConst,         // User defined constants and non-output parameters in functions
-    EvqAttribute,     // Readonly
-    EvqVaryingIn,     // readonly, fragment shaders only
-    EvqVaryingOut,    // vertex shaders only  read/write
-    EvqUniform,       // Readonly, vertex and fragment
+    EvqTemporary,   // For temporaries (within a function), read/write
+    EvqGlobal,      // For globals read/write
+    EvqConst,       // User defined constants and non-output parameters in functions
+    EvqAttribute,   // Readonly
+    EvqVaryingIn,   // readonly, fragment shaders only
+    EvqVaryingOut,  // vertex shaders only  read/write
+    EvqUniform,     // Readonly, vertex and fragment
 
-    EvqVertexIn,      // Vertex shader input
-    EvqFragmentOut,   // Fragment shader output
-    EvqVertexOut,     // Vertex shader output
-    EvqFragmentIn,    // Fragment shader input
+    EvqVertexIn,     // Vertex shader input
+    EvqFragmentOut,  // Fragment shader output
+    EvqVertexOut,    // Vertex shader output
+    EvqFragmentIn,   // Fragment shader input
 
     // parameters
     EvqIn,
@@ -325,20 +325,22 @@
     EvqFragColor,
     EvqFragData,
     EvqFragDepth,
+    EvqSecondaryFragColorEXT,  // EXT_blend_func_extended
+    EvqSecondaryFragDataEXT,   // EXT_blend_func_extended
 
     // built-ins written by the shader_framebuffer_fetch extension(s)
     EvqLastFragColor,
     EvqLastFragData,
 
     // GLSL ES 3.0 vertex output and fragment input
-    EvqSmooth,        // Incomplete qualifier, smooth is the default
-    EvqFlat,          // Incomplete qualifier
+    EvqSmooth,  // Incomplete qualifier, smooth is the default
+    EvqFlat,    // Incomplete qualifier
     EvqSmoothOut = EvqSmooth,
-    EvqFlatOut = EvqFlat,
-    EvqCentroidOut,   // Implies smooth
+    EvqFlatOut   = EvqFlat,
+    EvqCentroidOut,  // Implies smooth
     EvqSmoothIn,
     EvqFlatIn,
-    EvqCentroidIn,    // Implies smooth
+    EvqCentroidIn,  // Implies smooth
 
     // end of list
     EvqLast
@@ -413,6 +415,12 @@
     case EvqFragColor:      return "FragColor";      break;
     case EvqFragData:       return "FragData";       break;
     case EvqFragDepth:      return "FragDepth";      break;
+    case EvqSecondaryFragColorEXT:
+        return "SecondaryFragColorEXT";
+        break;
+    case EvqSecondaryFragDataEXT:
+        return "SecondaryFragDataEXT";
+        break;
     case EvqLastFragColor:  return "LastFragColor";  break;
     case EvqLastFragData:   return "LastFragData";   break;
     case EvqSmoothOut:      return "smooth out";     break;
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 6fac29a..396b390 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -461,6 +461,7 @@
               << ":FragmentPrecisionHigh:" << compileResources.FragmentPrecisionHigh
               << ":MaxExpressionComplexity:" << compileResources.MaxExpressionComplexity
               << ":MaxCallStackDepth:" << compileResources.MaxCallStackDepth
+              << ":EXT_blend_func_extended:" << compileResources.EXT_blend_func_extended
               << ":EXT_frag_depth:" << compileResources.EXT_frag_depth
               << ":EXT_shader_texture_lod:" << compileResources.EXT_shader_texture_lod
               << ":EXT_shader_framebuffer_fetch:" << compileResources.EXT_shader_framebuffer_fetch
@@ -470,6 +471,7 @@
               << ":MaxFragmentInputVectors:" << compileResources.MaxFragmentInputVectors
               << ":MinProgramTexelOffset:" << compileResources.MinProgramTexelOffset
               << ":MaxProgramTexelOffset:" << compileResources.MaxProgramTexelOffset
+              << ":MaxDualSourceDrawBuffers:" << compileResources.MaxDualSourceDrawBuffers
               << ":NV_draw_buffers:" << compileResources.NV_draw_buffers
               << ":WEBGL_debug_shader_precision:" << compileResources.WEBGL_debug_shader_precision;
 
@@ -660,9 +662,9 @@
 
 bool TCompiler::validateOutputs(TIntermNode* root)
 {
-    ValidateOutputs validateOutputs(infoSink.info, compileResources.MaxDrawBuffers);
+    ValidateOutputs validateOutputs(getExtensionBehavior(), compileResources.MaxDrawBuffers);
     root->traverse(&validateOutputs);
-    return (validateOutputs.numErrors() == 0);
+    return (validateOutputs.validateAndCountErrors(infoSink.info) == 0);
 }
 
 void TCompiler::rewriteCSSShader(TIntermNode* root)
diff --git a/src/compiler/translator/ExtensionBehavior.h b/src/compiler/translator/ExtensionBehavior.h
index cf4d7fb..782c1c9 100644
--- a/src/compiler/translator/ExtensionBehavior.h
+++ b/src/compiler/translator/ExtensionBehavior.h
@@ -34,4 +34,10 @@
 // Mapping between extension name and behavior.
 typedef std::map<std::string, TBehavior> TExtensionBehavior;
 
+inline bool IsExtensionEnabled(const TExtensionBehavior &extBehavior, const char *extension)
+{
+    auto iter = extBehavior.find(extension);
+    return iter != extBehavior.end() && (iter->second == EBhEnable || iter->second == EBhRequire);
+}
+
 #endif // COMPILER_TRANSLATOR_EXTENSIONBEHAVIOR_H_
diff --git a/src/compiler/translator/Initialize.cpp b/src/compiler/translator/Initialize.cpp
index 6cb2e22..a5e51dc 100644
--- a/src/compiler/translator/Initialize.cpp
+++ b/src/compiler/translator/Initialize.cpp
@@ -464,6 +464,12 @@
     if (spec != SH_CSS_SHADERS_SPEC)
     {
         symbolTable.insertConstInt(COMMON_BUILTINS, "gl_MaxDrawBuffers", resources.MaxDrawBuffers);
+        if (resources.EXT_blend_func_extended)
+        {
+            symbolTable.insertConstIntExt(COMMON_BUILTINS, "GL_EXT_blend_func_extended",
+                                          "gl_MaxDualSourceDrawBuffersEXT",
+                                          resources.MaxDualSourceDrawBuffers);
+        }
     }
 
     symbolTable.insertConstInt(ESSL3_BUILTINS, "gl_MaxVertexOutputVectors", resources.MaxVertexOutputVectors);
@@ -502,6 +508,19 @@
             fragData.setArraySize(resources.MaxDrawBuffers);
             symbolTable.insert(ESSL1_BUILTINS, new TVariable(NewPoolTString("gl_FragData"), fragData));
 
+            if (resources.EXT_blend_func_extended)
+            {
+                symbolTable.insert(
+                    ESSL1_BUILTINS, "GL_EXT_blend_func_extended",
+                    new TVariable(NewPoolTString("gl_SecondaryFragColorEXT"),
+                                  TType(EbtFloat, EbpMedium, EvqSecondaryFragColorEXT, 4)));
+                TType secondaryFragData(EbtFloat, EbpMedium, EvqSecondaryFragDataEXT, 4, 1, true);
+                secondaryFragData.setArraySize(resources.MaxDualSourceDrawBuffers);
+                symbolTable.insert(
+                    ESSL1_BUILTINS, "GL_EXT_blend_func_extended",
+                    new TVariable(NewPoolTString("gl_SecondaryFragDataEXT"), secondaryFragData));
+            }
+
             if (resources.EXT_frag_depth)
             {
                 symbolTable.insert(ESSL1_BUILTINS, "GL_EXT_frag_depth", new TVariable(NewPoolTString("gl_FragDepthEXT"),
@@ -567,6 +586,8 @@
         extBehavior["GL_OES_EGL_image_external"] = EBhUndefined;
     if (resources.ARB_texture_rectangle)
         extBehavior["GL_ARB_texture_rectangle"] = EBhUndefined;
+    if (resources.EXT_blend_func_extended)
+        extBehavior["GL_EXT_blend_func_extended"] = EBhUndefined;
     if (resources.EXT_draw_buffers)
         extBehavior["GL_EXT_draw_buffers"] = EBhUndefined;
     if (resources.EXT_frag_depth)
diff --git a/src/compiler/translator/OutputGLSL.cpp b/src/compiler/translator/OutputGLSL.cpp
index e9266ee..c1675ce 100644
--- a/src/compiler/translator/OutputGLSL.cpp
+++ b/src/compiler/translator/OutputGLSL.cpp
@@ -45,6 +45,14 @@
     {
         out << "webgl_FragData";
     }
+    else if (symbol == "gl_SecondaryFragColorEXT")
+    {
+        out << "angle_SecondaryFragColor";
+    }
+    else if (symbol == "gl_SecondaryFragDataEXT")
+    {
+        out << "angle_SecondaryFragData";
+    }
     else
     {
         TOutputGLSLBase::visitSymbol(node);
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index e7069e4..3700a9c 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -1143,15 +1143,7 @@
 
 bool TParseContext::isExtensionEnabled(const char *extension) const
 {
-    const TExtensionBehavior &extbehavior   = extensionBehavior();
-    TExtensionBehavior::const_iterator iter = extbehavior.find(extension);
-
-    if (iter == extbehavior.end())
-    {
-        return false;
-    }
-
-    return (iter->second == EBhEnable || iter->second == EBhRequire);
+    return ::IsExtensionEnabled(extensionBehavior(), extension);
 }
 
 void TParseContext::handleExtensionDirective(const TSourceLoc &loc,
@@ -1210,14 +1202,18 @@
 
         // Reject shaders using both gl_FragData and gl_FragColor
         TQualifier qualifier = variable->getType().getQualifier();
-        if (qualifier == EvqFragData)
+        if (qualifier == EvqFragData || qualifier == EvqSecondaryFragDataEXT)
         {
             mUsesFragData = true;
         }
-        else if (qualifier == EvqFragColor)
+        else if (qualifier == EvqFragColor || qualifier == EvqSecondaryFragColorEXT)
         {
             mUsesFragColor = true;
         }
+        if (qualifier == EvqSecondaryFragDataEXT || qualifier == EvqSecondaryFragColorEXT)
+        {
+            mUsesSecondaryOutputs = true;
+        }
 
         // This validation is not quite correct - it's only an error to write to
         // both FragData and FragColor. For simplicity, and because users shouldn't
@@ -1225,7 +1221,14 @@
         // if they are both referenced, rather than assigned.
         if (mUsesFragData && mUsesFragColor)
         {
-            error(location, "cannot use both gl_FragData and gl_FragColor", name->c_str());
+            const char *errorMessage = "cannot use both gl_FragData and gl_FragColor";
+            if (mUsesSecondaryOutputs)
+            {
+                errorMessage =
+                    "cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT)"
+                    " and (gl_FragColor, gl_SecondaryFragColorEXT)";
+            }
+            error(location, errorMessage, name->c_str());
             recover();
         }
     }
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index ea9e12c..3b0b206 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -58,7 +58,8 @@
           mPreprocessor(&mDiagnostics, &mDirectiveHandler),
           mScanner(nullptr),
           mUsesFragData(false),
-          mUsesFragColor(false)
+          mUsesFragColor(false),
+          mUsesSecondaryOutputs(false)
     {
     }
 
@@ -352,6 +353,8 @@
     void *mScanner;
     bool mUsesFragData; // track if we are using both gl_FragData and gl_FragColor
     bool mUsesFragColor;
+    bool mUsesSecondaryOutputs;  // Track if we are using either gl_SecondaryFragData or
+                                 // gl_Secondary FragColor or both.
 };
 
 int PaParseStrings(
diff --git a/src/compiler/translator/ShaderLang.cpp b/src/compiler/translator/ShaderLang.cpp
index ae9c3a4..8bd426f 100644
--- a/src/compiler/translator/ShaderLang.cpp
+++ b/src/compiler/translator/ShaderLang.cpp
@@ -154,6 +154,7 @@
     resources->OES_standard_derivatives = 0;
     resources->OES_EGL_image_external = 0;
     resources->ARB_texture_rectangle = 0;
+    resources->EXT_blend_func_extended      = 0;
     resources->EXT_draw_buffers = 0;
     resources->EXT_frag_depth = 0;
     resources->EXT_shader_texture_lod = 0;
@@ -173,6 +174,9 @@
     resources->MinProgramTexelOffset = -8;
     resources->MaxProgramTexelOffset = 7;
 
+    // Extensions constants.
+    resources->MaxDualSourceDrawBuffers = 0;
+
     // Disable name hashing by default.
     resources->HashFunction = NULL;
 
diff --git a/src/compiler/translator/SymbolTable.h b/src/compiler/translator/SymbolTable.h
index 4e3a203..ba578d7 100644
--- a/src/compiler/translator/SymbolTable.h
+++ b/src/compiler/translator/SymbolTable.h
@@ -412,6 +412,14 @@
         return insert(level, constant);
     }
 
+    bool insertConstIntExt(ESymbolLevel level, const char *ext, const char *name, int value)
+    {
+        TVariable *constant =
+            new TVariable(NewPoolTString(name), TType(EbtInt, EbpUndefined, EvqConst, 1));
+        constant->getConstPointer()->setIConst(value);
+        return insert(level, ext, constant);
+    }
+
     void insertBuiltIn(ESymbolLevel level, TOperator op, const char *ext, const TType *rvalue, const char *name,
                        const TType *ptype1, const TType *ptype2 = 0, const TType *ptype3 = 0, const TType *ptype4 = 0, const TType *ptype5 = 0);
 
diff --git a/src/compiler/translator/TranslatorGLSL.cpp b/src/compiler/translator/TranslatorGLSL.cpp
index 86b3919..305e6bb 100644
--- a/src/compiler/translator/TranslatorGLSL.cpp
+++ b/src/compiler/translator/TranslatorGLSL.cpp
@@ -64,30 +64,70 @@
 
     // Declare gl_FragColor and glFragData as webgl_FragColor and webgl_FragData
     // if it's core profile shaders and they are used.
-    if (getShaderType() == GL_FRAGMENT_SHADER && IsGLSL130OrNewer(getOutputType()))
+    if (getShaderType() == GL_FRAGMENT_SHADER)
     {
-        bool usesGLFragColor = false;
-        bool usesGLFragData = false;
-        for (auto outputVar : outputVariables)
+        const bool mayHaveESSL1SecondaryOutputs =
+            IsExtensionEnabled(getExtensionBehavior(), "GL_EXT_blend_func_extended") &&
+            getShaderVersion() == 100;
+        const bool declareGLFragmentOutputs = IsGLSL130OrNewer(getOutputType());
+
+        bool hasGLFragColor          = false;
+        bool hasGLFragData           = false;
+        bool hasGLSecondaryFragColor = false;
+        bool hasGLSecondaryFragData  = false;
+
+        for (const auto &outputVar : outputVariables)
         {
-            if (outputVar.name == "gl_FragColor")
+            if (declareGLFragmentOutputs)
             {
-                usesGLFragColor = true;
+                if (outputVar.name == "gl_FragColor")
+                {
+                    ASSERT(!hasGLFragColor);
+                    hasGLFragColor = true;
+                    continue;
+                }
+                else if (outputVar.name == "gl_FragData")
+                {
+                    ASSERT(!hasGLFragData);
+                    hasGLFragData = true;
+                    continue;
+                }
             }
-            else if (outputVar.name == "gl_FragData")
+            if (mayHaveESSL1SecondaryOutputs)
             {
-                usesGLFragData = true;
+                if (outputVar.name == "gl_SecondaryFragColorEXT")
+                {
+                    ASSERT(!hasGLSecondaryFragColor);
+                    hasGLSecondaryFragColor = true;
+                    continue;
+                }
+                else if (outputVar.name == "gl_SecondaryFragDataEXT")
+                {
+                    ASSERT(!hasGLSecondaryFragData);
+                    hasGLSecondaryFragData = true;
+                    continue;
+                }
             }
         }
-        ASSERT(!(usesGLFragColor && usesGLFragData));
-        if (usesGLFragColor)
+        ASSERT(!((hasGLFragColor || hasGLSecondaryFragColor) &&
+                 (hasGLFragData || hasGLSecondaryFragData)));
+        if (hasGLFragColor)
         {
             sink << "out vec4 webgl_FragColor;\n";
         }
-        if (usesGLFragData)
+        if (hasGLFragData)
         {
             sink << "out vec4 webgl_FragData[gl_MaxDrawBuffers];\n";
         }
+        if (hasGLSecondaryFragColor)
+        {
+            sink << "out vec4 angle_SecondaryFragColor;\n";
+        }
+        if (hasGLSecondaryFragData)
+        {
+            sink << "out vec4 angle_SecondaryFragData[" << getResources().MaxDualSourceDrawBuffers
+                 << "];\n";
+        }
     }
 
     // Write translated shader.
diff --git a/src/compiler/translator/ValidateOutputs.cpp b/src/compiler/translator/ValidateOutputs.cpp
index 6976309..cd37aea 100644
--- a/src/compiler/translator/ValidateOutputs.cpp
+++ b/src/compiler/translator/ValidateOutputs.cpp
@@ -9,12 +9,23 @@
 #include "compiler/translator/InitializeParseContext.h"
 #include "compiler/translator/ParseContext.h"
 
-ValidateOutputs::ValidateOutputs(TInfoSinkBase& sink, int maxDrawBuffers)
+namespace
+{
+void error(int *errorCount, TInfoSinkBase &sink, const TIntermSymbol &symbol, const char *reason)
+{
+    sink.prefix(EPrefixError);
+    sink.location(symbol.getLine());
+    sink << "'" << symbol.getSymbol() << "' : " << reason << "\n";
+    (*errorCount)++;
+}
+
+}  // namespace
+
+ValidateOutputs::ValidateOutputs(const TExtensionBehavior &extBehavior, int maxDrawBuffers)
     : TIntermTraverser(true, false, false),
-      mSink(sink),
       mMaxDrawBuffers(maxDrawBuffers),
-      mNumErrors(0),
-      mHasUnspecifiedOutputLocation(false)
+      mAllowUnspecifiedOutputLocationResolution(
+          IsExtensionEnabled(extBehavior, "GL_EXT_blend_func_extended"))
 {
 }
 
@@ -30,51 +41,68 @@
 
     if (qualifier == EvqFragmentOut)
     {
-        const TType &type = symbol->getType();
-        const int location = type.getLayoutQualifier().location;
-        const bool isUnspecifiedOutputLocation = location == -1;
-
-        if (mHasUnspecifiedOutputLocation || (isUnspecifiedOutputLocation && !mOutputMap.empty()))
+        if (symbol->getType().getLayoutQualifier().location == -1)
         {
-            error(symbol->getLine(), "must explicitly specify all locations when using multiple fragment outputs", name.c_str());
-        }
-        else if (isUnspecifiedOutputLocation)
-        {
-            mHasUnspecifiedOutputLocation = true;
+            mUnspecifiedLocationOutputs.push_back(symbol);
         }
         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());
-            }
+            mOutputs.push_back(symbol);
         }
     }
 }
 
-void ValidateOutputs::error(TSourceLoc loc, const char *reason, const char* token)
+int ValidateOutputs::validateAndCountErrors(TInfoSinkBase &sink) const
 {
-    mSink.prefix(EPrefixError);
-    mSink.location(loc);
-    mSink << "'" << token << "' : " << reason << "\n";
-    mNumErrors++;
+    OutputVector validOutputs(mMaxDrawBuffers);
+    int errorCount = 0;
+
+    for (const auto &symbol : mOutputs)
+    {
+        const TType &type         = symbol->getType();
+        const size_t elementCount = static_cast<size_t>(type.isArray() ? type.getArraySize() : 1);
+        const size_t location     = static_cast<size_t>(type.getLayoutQualifier().location);
+
+        ASSERT(type.getLayoutQualifier().location != -1);
+
+        if (location + elementCount <= validOutputs.size())
+        {
+            for (size_t elementIndex = 0; elementIndex < elementCount; elementIndex++)
+            {
+                const size_t offsetLocation = location + elementIndex;
+                if (validOutputs[offsetLocation])
+                {
+                    std::stringstream strstr;
+                    strstr << "conflicting output locations with previously defined output '"
+                           << validOutputs[offsetLocation]->getSymbol() << "'";
+                    error(&errorCount, sink, *symbol, strstr.str().c_str());
+                }
+                else
+                {
+                    validOutputs[offsetLocation] = symbol;
+                }
+            }
+        }
+        else
+        {
+            if (elementCount > 0)
+            {
+                error(&errorCount, sink, *symbol,
+                      elementCount > 1 ? "output array locations would exceed MAX_DRAW_BUFFERS"
+                                       : "output location must be < MAX_DRAW_BUFFERS");
+            }
+        }
+    }
+
+    if (!mAllowUnspecifiedOutputLocationResolution &&
+        ((!mOutputs.empty() && !mUnspecifiedLocationOutputs.empty()) ||
+         mUnspecifiedLocationOutputs.size() > 1))
+    {
+        for (const auto &symbol : mUnspecifiedLocationOutputs)
+        {
+            error(&errorCount, sink, *symbol,
+                  "must explicitly specify all locations when using multiple fragment outputs");
+        }
+    }
+    return errorCount;
 }
diff --git a/src/compiler/translator/ValidateOutputs.h b/src/compiler/translator/ValidateOutputs.h
index 1538e0f..4ba85d8 100644
--- a/src/compiler/translator/ValidateOutputs.h
+++ b/src/compiler/translator/ValidateOutputs.h
@@ -7,6 +7,7 @@
 #ifndef COMPILER_TRANSLATOR_VALIDATEOUTPUTS_H_
 #define COMPILER_TRANSLATOR_VALIDATEOUTPUTS_H_
 
+#include "compiler/translator/ExtensionBehavior.h"
 #include "compiler/translator/IntermNode.h"
 
 #include <set>
@@ -16,23 +17,20 @@
 class ValidateOutputs : public TIntermTraverser
 {
   public:
-    ValidateOutputs(TInfoSinkBase& sink, int maxDrawBuffers);
+    ValidateOutputs(const TExtensionBehavior &extBehavior, int maxDrawBuffers);
 
-    int numErrors() const { return mNumErrors; }
+    int validateAndCountErrors(TInfoSinkBase &sink) const;
 
     virtual void visitSymbol(TIntermSymbol*);
 
   private:
-    TInfoSinkBase& mSink;
     int mMaxDrawBuffers;
-    int mNumErrors;
-    bool mHasUnspecifiedOutputLocation;
+    bool mAllowUnspecifiedOutputLocationResolution;
 
-    typedef std::map<int, TIntermSymbol*> OutputMap;
-    OutputMap mOutputMap;
+    typedef std::vector<TIntermSymbol *> OutputVector;
+    OutputVector mOutputs;
+    OutputVector mUnspecifiedLocationOutputs;
     std::set<TString> mVisitedSymbols;
-
-    void error(TSourceLoc loc, const char *reason, const char* token);
 };
 
 #endif // COMPILER_TRANSLATOR_VALIDATEOUTPUTS_H_
diff --git a/src/compiler/translator/VariableInfo.cpp b/src/compiler/translator/VariableInfo.cpp
index c1c4c2d..d61dc40 100644
--- a/src/compiler/translator/VariableInfo.cpp
+++ b/src/compiler/translator/VariableInfo.cpp
@@ -151,6 +151,8 @@
       mFragColorAdded(false),
       mFragDataAdded(false),
       mFragDepthAdded(false),
+      mSecondaryFragColorEXTAdded(false),
+      mSecondaryFragDataEXTAdded(false),
       mHashFunction(hashFunction),
       mSymbolTable(symbolTable)
 {
@@ -420,6 +422,39 @@
                   mFragDepthAdded = true;
               }
               return;
+          case EvqSecondaryFragColorEXT:
+              if (!mSecondaryFragColorEXTAdded)
+              {
+                  Attribute info;
+                  const char kName[] = "gl_SecondaryFragColorEXT";
+                  info.name          = kName;
+                  info.mappedName    = kName;
+                  info.type          = GL_FLOAT_VEC4;
+                  info.arraySize     = 0;
+                  info.precision     = GL_MEDIUM_FLOAT;  // Defined by spec.
+                  info.staticUse = true;
+                  mOutputVariables->push_back(info);
+                  mSecondaryFragColorEXTAdded = true;
+              }
+              return;
+          case EvqSecondaryFragDataEXT:
+              if (!mSecondaryFragDataEXTAdded)
+              {
+                  Attribute info;
+                  const char kName[] = "gl_SecondaryFragDataEXT";
+                  info.name          = kName;
+                  info.mappedName    = kName;
+                  info.type          = GL_FLOAT_VEC4;
+
+                  const TVariable *maxDualSourceDrawBuffersVar = static_cast<const TVariable *>(
+                      mSymbolTable.findBuiltIn("gl_MaxDualSourceDrawBuffersEXT", 100));
+                  info.arraySize = maxDualSourceDrawBuffersVar->getConstPointer()->getIConst();
+                  info.precision = GL_MEDIUM_FLOAT;  // Defined by spec.
+                  info.staticUse = true;
+                  mOutputVariables->push_back(info);
+                  mSecondaryFragDataEXTAdded = true;
+              }
+              return;
           default:
             break;
         }
diff --git a/src/compiler/translator/VariableInfo.h b/src/compiler/translator/VariableInfo.h
index 028c699..af7ceae 100644
--- a/src/compiler/translator/VariableInfo.h
+++ b/src/compiler/translator/VariableInfo.h
@@ -59,6 +59,8 @@
     bool mFragColorAdded;
     bool mFragDataAdded;
     bool mFragDepthAdded;
+    bool mSecondaryFragColorEXTAdded;
+    bool mSecondaryFragDataEXTAdded;
 
     ShHashFunction64 mHashFunction;