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/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);