Properly validate struct uniform linking between the vertex and pixel shaders.

This also implements proper struct validation for uniform blocks.

TRAC #22932

Signed-off-by: Nicolas Capens
Signed-off-by: Geoff Lang
Author: Jamie Madill

git-svn-id: https://angleproject.googlecode.com/svn/branches/es3proto@2336 736b8ea6-26fd-11df-bfd4-992fa37f6226
diff --git a/src/compiler/OutputHLSL.cpp b/src/compiler/OutputHLSL.cpp
index fd5daa9..3b6ad2d 100644
--- a/src/compiler/OutputHLSL.cpp
+++ b/src/compiler/OutputHLSL.cpp
@@ -3094,50 +3094,38 @@
     return index;
 }
 
-void OutputHLSL::declareUniform(const TType &type, const TString &name, int index)
+void OutputHLSL::declareUniformToList(const TType &type, const TString &name, int index, ActiveUniforms& output)
 {
     const TTypeList *structure = type.getStruct();
 
     if (!structure)
     {
-        mActiveUniforms.push_back(Uniform(glVariableType(type), glVariablePrecision(type), name.c_str(), (unsigned int)type.getArraySize(), (unsigned int)index));
+        output.push_back(Uniform(glVariableType(type), glVariablePrecision(type), name.c_str(), (unsigned int)type.getArraySize(), (unsigned int)index));
     }
     else
     {
-        if (type.isArray())
+        Uniform structUniform(GL_NONE, GL_NONE, name.c_str(), (unsigned int)type.getArraySize(), (unsigned int)index);
+
+        int fieldIndex = index;
+
+        for (size_t i = 0; i < structure->size(); i++)
         {
-            int elementIndex = index;
+            const TType &fieldType = *(*structure)[i].type;
+            const TString &fieldName = fieldType.getFieldName();
 
-            for (int i = 0; i < type.getArraySize(); i++)
-            {
-                for (size_t j = 0; j < structure->size(); j++)
-                {
-                    const TType &fieldType = *(*structure)[j].type;
-                    const TString &fieldName = fieldType.getFieldName();
-
-                    const TString uniformName = name + "[" + str(i) + "]." + fieldName;
-                    declareUniform(fieldType, uniformName, elementIndex);
-                    elementIndex += fieldType.totalRegisterCount();
-                }
-            }
+            declareUniformToList(fieldType, fieldName, fieldIndex, structUniform.fields);
+            fieldIndex += fieldType.totalRegisterCount();
         }
-        else
-        {
-            int fieldIndex = index;
 
-            for (size_t i = 0; i < structure->size(); i++)
-            {
-                const TType &fieldType = *(*structure)[i].type;
-                const TString &fieldName = fieldType.getFieldName();
-
-                const TString uniformName = name + "." + fieldName;
-                declareUniform(fieldType, uniformName, fieldIndex);
-                fieldIndex += fieldType.totalRegisterCount();
-            }
-        }
+        output.push_back(structUniform);
     }
 }
 
+void OutputHLSL::declareUniform(const TType &type, const TString &name, int index)
+{
+    declareUniformToList(type, name, index, mActiveUniforms);
+}
+
 GLenum OutputHLSL::glVariableType(const TType &type)
 {
     if (type.getBasicType() == EbtFloat)
diff --git a/src/compiler/OutputHLSL.h b/src/compiler/OutputHLSL.h
index ddcf27b..4e6e600 100644
--- a/src/compiler/OutputHLSL.h
+++ b/src/compiler/OutputHLSL.h
@@ -168,6 +168,7 @@
     TString registerString(TIntermSymbol *operand);
     int samplerRegister(TIntermSymbol *sampler);
     int uniformRegister(TIntermSymbol *uniform);
+    void declareUniformToList(const TType &type, const TString &name, int index, ActiveUniforms& output);
     void declareUniform(const TType &type, const TString &name, int index);
     
     static GLenum glVariableType(const TType &type);
diff --git a/src/compiler/Uniform.h b/src/compiler/Uniform.h
index 91cefb6..5b22de5 100644
--- a/src/compiler/Uniform.h
+++ b/src/compiler/Uniform.h
@@ -27,6 +27,8 @@
     unsigned int arraySize;
 
     unsigned int registerIndex;
+
+    std::vector<Uniform> fields;
 };
 
 typedef std::vector<Uniform> ActiveUniforms;
diff --git a/src/libGLESv2/ProgramBinary.cpp b/src/libGLESv2/ProgramBinary.cpp
index fa3c178..9298c89 100644
--- a/src/libGLESv2/ProgramBinary.cpp
+++ b/src/libGLESv2/ProgramBinary.cpp
@@ -1936,8 +1936,78 @@
     return true;
 }
 
+bool ProgramBinary::areMatchingUniforms(InfoLog &infoLog, const std::string &uniformName, const sh::Uniform &vertexUniform, const sh::Uniform &fragmentUniform)
+{
+    if (vertexUniform.type != fragmentUniform.type)
+    {
+        infoLog.append("Types for %s differ between vertex and fragment shaders", uniformName.c_str());
+        return false;
+    }
+    else if (vertexUniform.arraySize != fragmentUniform.arraySize)
+    {
+        infoLog.append("Array sizes for %s differ between vertex and fragment shaders", uniformName.c_str());
+        return false;
+    }
+    else if (vertexUniform.precision != fragmentUniform.precision)
+    {
+        infoLog.append("Precisions for %s differ between vertex and fragment shaders", uniformName.c_str());
+        return false;
+    }
+    else if (vertexUniform.fields.size() != fragmentUniform.fields.size())
+    {
+        infoLog.append("Structure lengths for %s differ between vertex and fragment shaders", uniformName.c_str());
+    }
+
+    const unsigned int numMembers = vertexUniform.fields.size();
+    for (unsigned int memberIndex = 0; memberIndex < numMembers; memberIndex++)
+    {
+        const sh::Uniform &vertexMember = vertexUniform.fields[memberIndex];
+        const sh::Uniform &fragmentMember = fragmentUniform.fields[memberIndex];
+
+        if (vertexMember.name != fragmentMember.name)
+        {
+            infoLog.append("Name mismatch for field %d of %s: (in vertex: '%s', in fragment: '%s')",
+                           memberIndex, uniformName.c_str(), vertexMember.name.c_str(), fragmentMember.name.c_str());
+            return false;
+        }
+
+        const std::string memberName = uniformName + "." + vertexUniform.name;
+        if (!areMatchingUniforms(infoLog, memberName, vertexMember, fragmentMember))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
 bool ProgramBinary::linkUniforms(InfoLog &infoLog, const sh::ActiveUniforms &vertexUniforms, const sh::ActiveUniforms &fragmentUniforms)
 {
+    // Check that uniforms defined in the vertex and fragment shaders are identical
+    typedef std::map<std::string, const sh::Uniform*> UniformMap;
+    UniformMap linkedUniforms;
+
+    for (unsigned int vertexUniformIndex = 0; vertexUniformIndex < vertexUniforms.size(); vertexUniformIndex++)
+    {
+        const sh::Uniform &vertexUniform = vertexUniforms[vertexUniformIndex];
+        linkedUniforms[vertexUniform.name] = &vertexUniform;
+    }
+
+    for (unsigned int fragmentUniformIndex = 0; fragmentUniformIndex < fragmentUniforms.size(); fragmentUniformIndex++)
+    {
+        const sh::Uniform &fragmentUniform = fragmentUniforms[fragmentUniformIndex];
+        UniformMap::const_iterator entry = linkedUniforms.find(fragmentUniform.name);
+        if (entry != linkedUniforms.end())
+        {
+            const sh::Uniform &vertexUniform = *entry->second;
+            const std::string &uniformName = "uniform " + vertexUniform.name;
+            if (!areMatchingUniforms(infoLog, uniformName, vertexUniform, fragmentUniform))
+            {
+                return false;
+            }
+        }
+    }
+
     for (sh::ActiveUniforms::const_iterator uniform = vertexUniforms.begin(); uniform != vertexUniforms.end(); uniform++)
     {
         if (!defineUniform(GL_VERTEX_SHADER, *uniform, infoLog))
@@ -1957,8 +2027,71 @@
     return true;
 }
 
+int totalRegisterCount(const sh::Uniform &uniform)
+{
+    int registerCount = 0;
+
+    if (!uniform.fields.empty())
+    {
+        for (unsigned int fieldIndex = 0; fieldIndex < uniform.fields.size(); fieldIndex++)
+        {
+            registerCount += totalRegisterCount(uniform.fields[fieldIndex]);
+        }
+    }
+    else
+    {
+        registerCount = 1;
+    }
+
+    return (uniform.arraySize > 0) ? uniform.arraySize * registerCount : registerCount;
+}
+
 bool ProgramBinary::defineUniform(GLenum shader, const sh::Uniform &constant, InfoLog &infoLog)
 {
+    if (!constant.fields.empty())
+    {
+        if (constant.arraySize > 0)
+        {
+            unsigned int elementRegisterIndex = constant.registerIndex;
+
+            for (unsigned int elementIndex = 0; elementIndex < constant.arraySize; elementIndex++)
+            {
+                for (size_t fieldIndex = 0; fieldIndex < constant.fields.size(); fieldIndex++)
+                {
+                    const sh::Uniform &field = constant.fields[fieldIndex];
+                    const std::string &uniformName = constant.name + "[" + str(elementIndex) + "]." + field.name;
+                    const sh::Uniform fieldUniform(field.type, field.precision, uniformName.c_str(), field.arraySize, elementRegisterIndex);
+                    if (!defineUniform(shader, fieldUniform, infoLog))
+                    {
+                        return false;
+                    }
+                    elementRegisterIndex += totalRegisterCount(field);
+                }
+            }
+        }
+        else
+        {
+            unsigned int fieldRegisterIndex = constant.registerIndex;
+
+            for (size_t fieldIndex = 0; fieldIndex < constant.fields.size(); fieldIndex++)
+            {
+                const sh::Uniform &field = constant.fields[fieldIndex];
+                const std::string &uniformName = constant.name + "." + field.name;
+
+                sh::Uniform fieldUniform(field.type, field.precision, uniformName.c_str(), field.arraySize, fieldRegisterIndex);
+                fieldUniform.fields = field.fields;
+
+                if (!defineUniform(shader, fieldUniform, infoLog))
+                {
+                    return false;
+                }
+                fieldRegisterIndex += totalRegisterCount(field);
+            }
+        }
+
+        return true;
+    }
+
     if (constant.type == GL_SAMPLER_2D ||
         constant.type == GL_SAMPLER_CUBE)
     {
@@ -2009,18 +2142,6 @@
     if (location >= 0)   // Previously defined, type and precision must match
     {
         uniform = mUniforms[mUniformIndex[location].index];
-
-        if (uniform->type != constant.type)
-        {
-            infoLog.append("Types for uniform %s do not match between the vertex and fragment shader", uniform->name.c_str());
-            return false;
-        }
-
-        if (uniform->precision != constant.precision)
-        {
-            infoLog.append("Precisions for uniform %s do not match between the vertex and fragment shader", uniform->name.c_str());
-            return false;
-        }
     }
     else
     {
diff --git a/src/libGLESv2/ProgramBinary.h b/src/libGLESv2/ProgramBinary.h
index 328ffe2..02782f9 100644
--- a/src/libGLESv2/ProgramBinary.h
+++ b/src/libGLESv2/ProgramBinary.h
@@ -150,6 +150,7 @@
 
     bool linkAttributes(InfoLog &infoLog, const AttributeBindings &attributeBindings, FragmentShader *fragmentShader, VertexShader *vertexShader);
 
+    bool areMatchingUniforms(InfoLog &infoLog, const std::string& uniformName, const sh::Uniform &vertexUniform, const sh::Uniform &fragmentUniform);
     bool linkUniforms(InfoLog &infoLog, const sh::ActiveUniforms &vertexUniforms, const sh::ActiveUniforms &fragmentUniforms);
     bool defineUniform(GLenum shader, const sh::Uniform &constant, InfoLog &infoLog);