OSDN Git Service

Add proper in/out shader types
authorAlexis Hetu <sugoi@google.com>
Wed, 3 Jun 2015 20:03:48 +0000 (16:03 -0400)
committerAlexis Hétu <sugoi@google.com>
Mon, 8 Jun 2015 14:06:35 +0000 (14:06 +0000)
Fragment and Vertex inputs and outputs were treated as OpenGL ES2.0
attributes and varyings, but OpenGL ES3.0 inputs and outputs have
different limitations and must be treated differently. This cl simply
introduces the new types, without modifying the ES2.0 behavior and
only modifying ES3.0 to allow integer varyings when they are flat.

Change-Id: I965cb576bab3f505602af9e055438bcc7c18cdfd
Reviewed-on: https://swiftshader-review.googlesource.com/3371
Tested-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <capn@google.com>
src/OpenGL/compiler/BaseTypes.h
src/OpenGL/compiler/OutputASM.cpp
src/OpenGL/compiler/ParseHelper.cpp
src/OpenGL/compiler/ParseHelper.h
src/OpenGL/compiler/Types.h
src/OpenGL/compiler/glslang.y
src/OpenGL/compiler/glslang_tab.cpp

index 93d598c..baf3cc0 100644 (file)
@@ -342,6 +342,11 @@ enum TQualifier : unsigned char
     EvqInvariantVaryingOut,    // 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
+
     // pack/unpack input and output
     EvqInput,
     EvqOutput,
@@ -420,6 +425,10 @@ inline const char *getQualifierString(TQualifier qualifier)
     case EvqInvariantVaryingIn: return "invariant varying";    break;
     case EvqInvariantVaryingOut:return "invariant varying";    break;
     case EvqUniform:        return "uniform";        break;
+    case EvqVertexIn:       return "in";             break;
+    case EvqFragmentOut:    return "out";            break;
+    case EvqVertexOut:      return "out";            break;
+    case EvqFragmentIn:     return "in";             break;
     case EvqIn:             return "in";             break;
     case EvqOut:            return "out";            break;
     case EvqInOut:          return "inout";          break;
index c2a42b6..55c512e 100644 (file)
@@ -166,7 +166,7 @@ namespace glsl
        {\r
                // Vertex varyings don't have to be actively used to successfully link\r
                // against pixel shaders that use them. So make sure they're declared.\r
-               if(symbol->getQualifier() == EvqVaryingOut || symbol->getQualifier() == EvqInvariantVaryingOut)\r
+               if(symbol->getQualifier() == EvqVaryingOut || symbol->getQualifier() == EvqInvariantVaryingOut || symbol->getQualifier() == EvqVertexOut)\r
                {\r
                        if(symbol->getBasicType() != EbtInvariant)   // Typeless declarations are not new varyings\r
                        {\r
@@ -1857,6 +1857,10 @@ namespace glsl
                case EvqAttribute:           return sw::Shader::PARAMETER_INPUT;\r
                case EvqVaryingIn:           return sw::Shader::PARAMETER_INPUT;\r
                case EvqVaryingOut:          return sw::Shader::PARAMETER_OUTPUT;\r
+               case EvqVertexIn:            return sw::Shader::PARAMETER_INPUT;\r
+               case EvqFragmentOut:         return sw::Shader::PARAMETER_COLOROUT;\r
+               case EvqVertexOut:           return sw::Shader::PARAMETER_OUTPUT;\r
+               case EvqFragmentIn:          return sw::Shader::PARAMETER_INPUT;\r
                case EvqInvariantVaryingIn:  return sw::Shader::PARAMETER_INPUT;    // FIXME: Guarantee invariance at the backend\r
                case EvqInvariantVaryingOut: return sw::Shader::PARAMETER_OUTPUT;   // FIXME: Guarantee invariance at the backend \r
                case EvqUniform:             return sw::Shader::PARAMETER_CONST;\r
@@ -1893,6 +1897,10 @@ namespace glsl
                case EvqAttribute:           return attributeRegister(operand);\r
                case EvqVaryingIn:           return varyingRegister(operand);\r
                case EvqVaryingOut:          return varyingRegister(operand);\r
+               case EvqVertexIn:            return attributeRegister(operand);\r
+               case EvqFragmentOut:         return 0;\r
+               case EvqVertexOut:           return varyingRegister(operand);\r
+               case EvqFragmentIn:          return varyingRegister(operand);\r
                case EvqInvariantVaryingIn:  return varyingRegister(operand);\r
                case EvqInvariantVaryingOut: return varyingRegister(operand);\r
                case EvqUniform:             return uniformRegister(operand);\r
index 4fda827..b403feb 100644 (file)
@@ -314,6 +314,8 @@ bool TParseContext::lValueErrorCheck(int line, const char* op, TIntermTyped* nod
     case EvqConstExpr:      message = "can't modify a const";        break;
     case EvqConstReadOnly:  message = "can't modify a const";        break;
     case EvqAttribute:      message = "can't modify an attribute";   break;
+    case EvqFragmentIn:     message = "can't modify an input";       break;
+    case EvqVertexIn:       message = "can't modify an input";       break;
     case EvqUniform:        message = "can't modify a uniform";      break;
     case EvqSmoothIn:
     case EvqFlatIn:
@@ -624,6 +626,8 @@ bool TParseContext::structQualifierErrorCheck(int line, const TPublicType& pType
        case EvqFlatIn:
        case EvqCentroidIn:
        case EvqAttribute:
+       case EvqVertexIn:
+       case EvqFragmentOut:
                if(pType.type == EbtStruct)
                {
                        error(line, "cannot be used with a structure", getQualifierString(pType.qualifier));
@@ -638,9 +642,29 @@ bool TParseContext::structQualifierErrorCheck(int line, const TPublicType& pType
     if (pType.qualifier != EvqUniform && samplerErrorCheck(line, pType, "samplers must be uniform"))
         return true;
 
+       // check for layout qualifier issues
+       const TLayoutQualifier layoutQualifier = pType.layoutQualifier;
+
+       if (pType.qualifier != EvqVertexIn && pType.qualifier != EvqFragmentOut &&
+           layoutLocationErrorCheck(line, pType.layoutQualifier))
+       {
+               return true;
+       }
+
     return false;
 }
 
+bool TParseContext::layoutLocationErrorCheck(const TSourceLoc &location, const TLayoutQualifier &layoutQualifier)\r
+{\r
+       if(layoutQualifier.location != -1)\r
+       {\r
+               error(location, "invalid layout qualifier:", "location", "only valid on program inputs and outputs");\r
+               return true;\r
+       }\r
+\r
+       return false;\r
+}\r
+
 bool TParseContext::parameterSamplerErrorCheck(int line, TQualifier qualifier, const TType& type)
 {
     if ((qualifier == EvqOut || qualifier == EvqInOut) &&
@@ -717,7 +741,7 @@ bool TParseContext::arraySizeErrorCheck(int line, TIntermTyped* expr, int& size)
 //
 bool TParseContext::arrayQualifierErrorCheck(int line, TPublicType type)
 {
-    if ((type.qualifier == EvqAttribute) || (type.qualifier == EvqConstExpr)) {
+    if ((type.qualifier == EvqAttribute) || (type.qualifier == EvqVertexIn) || (type.qualifier == EvqConstExpr)) {
         error(line, "cannot declare arrays of this qualifier", TType(type).getCompleteString().c_str());
         return true;
     }
@@ -1122,6 +1146,75 @@ bool TParseContext::areAllChildConst(TIntermAggregate* aggrNode)
     return allConstant;
 }
 
+TPublicType TParseContext::addFullySpecifiedType(TQualifier qualifier, bool invariant, TLayoutQualifier layoutQualifier, const TPublicType &typeSpecifier)
+{
+       TPublicType returnType = typeSpecifier;
+       returnType.qualifier = qualifier;
+       returnType.invariant = invariant;
+       returnType.layoutQualifier = layoutQualifier;
+
+       if(typeSpecifier.array)
+       {
+               error(typeSpecifier.line, "not supported", "first-class array");
+               recover();
+               returnType.clearArrayness();
+       }
+
+       if(shaderVersion < 300)
+       {
+               if(qualifier == EvqAttribute && (typeSpecifier.type == EbtBool || typeSpecifier.type == EbtInt))
+               {
+                       error(typeSpecifier.line, "cannot be bool or int", getQualifierString(qualifier));
+                       recover();
+               }
+
+               if((qualifier == EvqVaryingIn || qualifier == EvqVaryingOut) &&
+                       (typeSpecifier.type == EbtBool || typeSpecifier.type == EbtInt))
+               {
+                       error(typeSpecifier.line, "cannot be bool or int", getQualifierString(qualifier));
+                       recover();
+               }
+       }
+       else
+       {
+               switch(qualifier)
+               {
+               case EvqSmoothIn:
+               case EvqSmoothOut:
+               case EvqVertexOut:
+               case EvqFragmentIn:
+               case EvqCentroidOut:
+               case EvqCentroidIn:
+                       if(typeSpecifier.type == EbtBool)
+                       {
+                               error(typeSpecifier.line, "cannot be bool", getQualifierString(qualifier));
+                               recover();
+                       }
+                       if(typeSpecifier.type == EbtInt || typeSpecifier.type == EbtUInt)
+                       {
+                               error(typeSpecifier.line, "must use 'flat' interpolation here", getQualifierString(qualifier));
+                               recover();
+                       }
+                       break;
+
+               case EvqVertexIn:
+               case EvqFragmentOut:
+               case EvqFlatIn:
+               case EvqFlatOut:
+                       if(typeSpecifier.type == EbtBool)
+                       {
+                               error(typeSpecifier.line, "cannot be bool", getQualifierString(qualifier));
+                               recover();
+                       }
+                       break;
+
+               default: break;
+               }
+       }
+
+       return returnType;
+}
+
 // This function is used to test for the correctness of the parameters passed to various constructor functions
 // and also convert them to the right datatype if it is allowed and required.
 //
@@ -1406,7 +1499,7 @@ TPublicType TParseContext::joinInterpolationQualifiers(const TSourceLoc &interpo
 {
        TQualifier mergedQualifier = EvqSmoothIn;
 
-       if(storageQualifier == EvqVaryingIn) {
+       if(storageQualifier == EvqFragmentIn) {
                if(interpolationQualifier == EvqSmooth)
                        mergedQualifier = EvqSmoothIn;
                else if(interpolationQualifier == EvqFlat)
@@ -1420,7 +1513,7 @@ TPublicType TParseContext::joinInterpolationQualifiers(const TSourceLoc &interpo
                        mergedQualifier = EvqFlatIn;
                else UNREACHABLE();
        }
-       else if(storageQualifier == EvqVaryingOut) {
+       else if(storageQualifier == EvqVertexOut) {
                if(interpolationQualifier == EvqSmooth)
                        mergedQualifier = EvqSmoothOut;
                else if(interpolationQualifier == EvqFlat)
index c494572..149791e 100644 (file)
@@ -102,6 +102,7 @@ struct TParseContext {
     bool nonInitErrorCheck(int line, TString& identifier, TPublicType& type, TVariable*& variable);
     bool paramErrorCheck(int line, TQualifier qualifier, TQualifier paramQualifier, TType* type);
     bool extensionErrorCheck(int line, const TString&);
+    bool layoutLocationErrorCheck(const TSourceLoc &location, const TLayoutQualifier &layoutQualifier);
 
     const TExtensionBehavior& extensionBehavior() const { return directiveHandler.extensionBehavior(); }
     bool supportsExtension(const char* extension);
@@ -115,6 +116,8 @@ struct TParseContext {
     const TFunction* findFunction(int line, TFunction* pfnCall, bool *builtIn = 0);
     bool executeInitializer(TSourceLoc line, TString& identifier, TPublicType& pType,
                             TIntermTyped* initializer, TIntermNode*& intermNode, TVariable* variable = 0);
+
+    TPublicType addFullySpecifiedType(TQualifier qualifier, bool invariant, TLayoutQualifier layoutQualifier, const TPublicType &typeSpecifier);
     bool arraySetMaxSize(TIntermSymbol*, TType*, int, bool, TSourceLoc);
 
     TIntermTyped* addConstructor(TIntermNode*, const TType*, TOperator, TFunction*, TSourceLoc);
index de16d51..b89c860 100644 (file)
@@ -275,6 +275,7 @@ struct TPublicType
     TBasicType type;
     TLayoutQualifier layoutQualifier;
     TQualifier qualifier;
+    bool invariant;
     TPrecision precision;
     int primarySize;          // size of vector or matrix, not size of array
        int secondarySize;        // 1 for scalars/vectors, >1 for matrices
@@ -288,6 +289,7 @@ struct TPublicType
         type = bt;
         layoutQualifier = TLayoutQualifier::create();
         qualifier = q;
+        invariant = false;
         precision = EbpUndefined;
                primarySize = 1;
                secondarySize = 1;
@@ -315,6 +317,12 @@ struct TPublicType
         arraySize = s;
     }
 
+       void clearArrayness()
+       {
+               array = false;
+               arraySize = 0;
+       }
+
     bool isStructureContainingArrays() const
     {
         if (!userDef)
index ce04736..b7d7a06 100644 (file)
@@ -1573,30 +1573,14 @@ fully_specified_type
         $$ = $1;
 
         if ($1.array) {
-            context->error($1.line, "not supported", "first-class array");
-            context->recover();
-            $1.setArray(false);
+            ES3_ONLY("[]", $1.line);
+            if (context->getShaderVersion() != 300) {
+                $1.clearArrayness();
+            }
         }
     }
     | type_qualifier type_specifier  {
-        if ($2.array) {
-            context->error($2.line, "not supported", "first-class array");
-            context->recover();
-            $2.setArray(false);
-        }
-
-        if ($1.qualifier == EvqAttribute &&
-            ($2.type == EbtBool || $2.type == EbtInt)) {
-            context->error($2.line, "cannot be bool or int", getQualifierString($1.qualifier));
-            context->recover();
-        }
-        if (($1.qualifier == EvqVaryingIn || $1.qualifier == EvqVaryingOut) &&
-            ($2.type == EbtBool || $2.type == EbtInt)) {
-            context->error($2.line, "cannot be bool or int", getQualifierString($1.qualifier));
-            context->recover();
-        }
-        $$ = $2;
-        $$.qualifier = $1.qualifier;
+        $$ = context->addFullySpecifiedType($1.qualifier, $1.invariant, $1.layoutQualifier, $2);
     }
     ;
 
@@ -1671,24 +1655,32 @@ storage_qualifier
     }
     | IN_QUAL {
                ES3_ONLY("in", $1.line);
-        $$.qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqVaryingIn : EvqAttribute;
+        $$.qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragmentIn : EvqVertexIn;
                $$.line = $1.line;
     }
     | OUT_QUAL {
                ES3_ONLY("out", $1.line);
-        $$.qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragColor : EvqVaryingOut;
+        $$.qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragmentOut : EvqVertexOut;
                $$.line = $1.line;
     }
     | CENTROID IN_QUAL {
-               ES3_ONLY("in", $1.line);
-           // FIXME: Handle centroid qualifier
-        $$.qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqVaryingIn : EvqAttribute;
+               ES3_ONLY("centroid in", $1.line);
+        if (context->shaderType == GL_VERTEX_SHADER)
+        {
+            context->error($1.line, "invalid storage qualifier", "it is an error to use 'centroid in' in the vertex shader");
+            context->recover();
+        }
+        $$.qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqCentroidIn : EvqVertexIn;
                $$.line = $2.line;
     }
        | CENTROID OUT_QUAL {
-               ES3_ONLY("out", $1.line);
-           // FIXME: Handle centroid qualifier
-        $$.qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragColor : EvqVaryingOut;
+               ES3_ONLY("centroid out", $1.line);
+        if (context->shaderType == GL_FRAGMENT_SHADER)
+        {
+            context->error($1.line, "invalid storage qualifier", "it is an error to use 'centroid out' in the fragment shader");
+            context->recover();
+        }
+        $$.qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragmentOut : EvqCentroidOut;
                $$.line = $2.line;
     }
        | UNIFORM {
index 0a5f2a5..e3777f0 100644 (file)
@@ -741,21 +741,21 @@ static const yytype_uint16 yyrline[] =
     1051,  1053,  1058,  1061,  1072,  1080,  1107,  1112,  1122,  1160,
     1163,  1170,  1178,  1199,  1220,  1231,  1260,  1265,  1275,  1280,
     1290,  1293,  1296,  1299,  1305,  1312,  1315,  1337,  1355,  1379,
-    1402,  1406,  1424,  1432,  1464,  1484,  1572,  1581,  1604,  1607,
-    1613,  1619,  1626,  1635,  1644,  1647,  1650,  1657,  1661,  1668,
-    1672,  1677,  1682,  1688,  1694,  1703,  1713,  1720,  1723,  1726,
-    1732,  1739,  1742,  1748,  1751,  1754,  1760,  1763,  1778,  1782,
-    1786,  1790,  1794,  1798,  1803,  1808,  1813,  1818,  1823,  1828,
-    1833,  1838,  1843,  1848,  1853,  1858,  1864,  1870,  1876,  1882,
-    1888,  1894,  1900,  1906,  1912,  1917,  1922,  1931,  1936,  1941,
-    1946,  1951,  1956,  1961,  1966,  1971,  1976,  1981,  1986,  1991,
-    1996,  2001,  2014,  2014,  2028,  2028,  2037,  2040,  2055,  2087,
-    2091,  2097,  2105,  2121,  2125,  2129,  2130,  2136,  2137,  2138,
-    2139,  2140,  2144,  2145,  2145,  2145,  2155,  2156,  2160,  2160,
-    2161,  2161,  2166,  2169,  2179,  2182,  2188,  2189,  2193,  2201,
-    2205,  2215,  2220,  2237,  2237,  2242,  2242,  2249,  2249,  2257,
-    2260,  2266,  2269,  2275,  2279,  2286,  2293,  2300,  2307,  2318,
-    2327,  2331,  2338,  2341,  2347,  2347
+    1402,  1406,  1424,  1432,  1464,  1484,  1572,  1582,  1588,  1591,
+    1597,  1603,  1610,  1619,  1628,  1631,  1634,  1641,  1645,  1652,
+    1656,  1661,  1666,  1676,  1686,  1695,  1705,  1712,  1715,  1718,
+    1724,  1731,  1734,  1740,  1743,  1746,  1752,  1755,  1770,  1774,
+    1778,  1782,  1786,  1790,  1795,  1800,  1805,  1810,  1815,  1820,
+    1825,  1830,  1835,  1840,  1845,  1850,  1856,  1862,  1868,  1874,
+    1880,  1886,  1892,  1898,  1904,  1909,  1914,  1923,  1928,  1933,
+    1938,  1943,  1948,  1953,  1958,  1963,  1968,  1973,  1978,  1983,
+    1988,  1993,  2006,  2006,  2020,  2020,  2029,  2032,  2047,  2079,
+    2083,  2089,  2097,  2113,  2117,  2121,  2122,  2128,  2129,  2130,
+    2131,  2132,  2136,  2137,  2137,  2137,  2147,  2148,  2152,  2152,
+    2153,  2153,  2158,  2161,  2171,  2174,  2180,  2181,  2185,  2193,
+    2197,  2207,  2212,  2229,  2229,  2234,  2234,  2241,  2241,  2249,
+    2252,  2258,  2261,  2267,  2271,  2278,  2285,  2292,  2299,  2310,
+    2319,  2323,  2330,  2333,  2339,  2339
 };
 #endif
 
@@ -3940,9 +3940,10 @@ yyreduce:
         (yyval.interm.type) = (yyvsp[(1) - (1)].interm.type);
 
         if ((yyvsp[(1) - (1)].interm.type).array) {
-            context->error((yyvsp[(1) - (1)].interm.type).line, "not supported", "first-class array");
-            context->recover();
-            (yyvsp[(1) - (1)].interm.type).setArray(false);
+            ES3_ONLY("[]", (yyvsp[(1) - (1)].interm.type).line);
+            if (context->getShaderVersion() != 300) {
+                (yyvsp[(1) - (1)].interm.type).clearArrayness();
+            }
         }
     }
     break;
@@ -3950,24 +3951,7 @@ yyreduce:
   case 117:
 
     {
-        if ((yyvsp[(2) - (2)].interm.type).array) {
-            context->error((yyvsp[(2) - (2)].interm.type).line, "not supported", "first-class array");
-            context->recover();
-            (yyvsp[(2) - (2)].interm.type).setArray(false);
-        }
-
-        if ((yyvsp[(1) - (2)].interm.type).qualifier == EvqAttribute &&
-            ((yyvsp[(2) - (2)].interm.type).type == EbtBool || (yyvsp[(2) - (2)].interm.type).type == EbtInt)) {
-            context->error((yyvsp[(2) - (2)].interm.type).line, "cannot be bool or int", getQualifierString((yyvsp[(1) - (2)].interm.type).qualifier));
-            context->recover();
-        }
-        if (((yyvsp[(1) - (2)].interm.type).qualifier == EvqVaryingIn || (yyvsp[(1) - (2)].interm.type).qualifier == EvqVaryingOut) &&
-            ((yyvsp[(2) - (2)].interm.type).type == EbtBool || (yyvsp[(2) - (2)].interm.type).type == EbtInt)) {
-            context->error((yyvsp[(2) - (2)].interm.type).line, "cannot be bool or int", getQualifierString((yyvsp[(1) - (2)].interm.type).qualifier));
-            context->recover();
-        }
-        (yyval.interm.type) = (yyvsp[(2) - (2)].interm.type);
-        (yyval.interm.type).qualifier = (yyvsp[(1) - (2)].interm.type).qualifier;
+        (yyval.interm.type) = context->addFullySpecifiedType((yyvsp[(1) - (2)].interm.type).qualifier, (yyvsp[(1) - (2)].interm.type).invariant, (yyvsp[(1) - (2)].interm.type).layoutQualifier, (yyvsp[(2) - (2)].interm.type));
     }
     break;
 
@@ -4082,7 +4066,7 @@ yyreduce:
 
     {
                ES3_ONLY("in", (yyvsp[(1) - (1)].lex).line);
-        (yyval.interm.type).qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqVaryingIn : EvqAttribute;
+        (yyval.interm.type).qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragmentIn : EvqVertexIn;
                (yyval.interm.type).line = (yyvsp[(1) - (1)].lex).line;
     }
     break;
@@ -4091,7 +4075,7 @@ yyreduce:
 
     {
                ES3_ONLY("out", (yyvsp[(1) - (1)].lex).line);
-        (yyval.interm.type).qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragColor : EvqVaryingOut;
+        (yyval.interm.type).qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragmentOut : EvqVertexOut;
                (yyval.interm.type).line = (yyvsp[(1) - (1)].lex).line;
     }
     break;
@@ -4099,9 +4083,13 @@ yyreduce:
   case 132:
 
     {
-               ES3_ONLY("in", (yyvsp[(1) - (2)].lex).line);
-           // FIXME: Handle centroid qualifier
-        (yyval.interm.type).qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqVaryingIn : EvqAttribute;
+               ES3_ONLY("centroid in", (yyvsp[(1) - (2)].lex).line);
+        if (context->shaderType == GL_VERTEX_SHADER)
+        {
+            context->error((yyvsp[(1) - (2)].lex).line, "invalid storage qualifier", "it is an error to use 'centroid in' in the vertex shader");
+            context->recover();
+        }
+        (yyval.interm.type).qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqCentroidIn : EvqVertexIn;
                (yyval.interm.type).line = (yyvsp[(2) - (2)].lex).line;
     }
     break;
@@ -4109,9 +4097,13 @@ yyreduce:
   case 133:
 
     {
-               ES3_ONLY("out", (yyvsp[(1) - (2)].lex).line);
-           // FIXME: Handle centroid qualifier
-        (yyval.interm.type).qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragColor : EvqVaryingOut;
+               ES3_ONLY("centroid out", (yyvsp[(1) - (2)].lex).line);
+        if (context->shaderType == GL_FRAGMENT_SHADER)
+        {
+            context->error((yyvsp[(1) - (2)].lex).line, "invalid storage qualifier", "it is an error to use 'centroid out' in the fragment shader");
+            context->recover();
+        }
+        (yyval.interm.type).qualifier = (context->shaderType == GL_FRAGMENT_SHADER) ? EvqFragmentOut : EvqCentroidOut;
                (yyval.interm.type).line = (yyvsp[(2) - (2)].lex).line;
     }
     break;