Structure field type validation

Uniforms and varyings structures were simply validating
that both versions in fragment and vertex shaders were
structures, without validating that the fields actually
matched.

All the structures and data required to perform the
validation at link time was added.

Fixes:
dEQP-GLES3.functional.shaders.linkage.uniform.struct.type_conflict_1

Change-Id: Icbf888bbebf4ccf7d27f48cb98d4cd7ea5b42ca3
Reviewed-on: https://swiftshader-review.googlesource.com/15848
Tested-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/src/OpenGL/compiler/OutputASM.cpp b/src/OpenGL/compiler/OutputASM.cpp
index b50a9b4..8edd099 100644
--- a/src/OpenGL/compiler/OutputASM.cpp
+++ b/src/OpenGL/compiler/OutputASM.cpp
@@ -26,6 +26,183 @@
 
 #include <stdlib.h>
 
+namespace
+{
+	GLenum glVariableType(const TType &type)
+	{
+		switch(type.getBasicType())
+		{
+		case EbtFloat:
+			if(type.isScalar())
+			{
+				return GL_FLOAT;
+			}
+			else if(type.isVector())
+			{
+				switch(type.getNominalSize())
+				{
+				case 2: return GL_FLOAT_VEC2;
+				case 3: return GL_FLOAT_VEC3;
+				case 4: return GL_FLOAT_VEC4;
+				default: UNREACHABLE(type.getNominalSize());
+				}
+			}
+			else if(type.isMatrix())
+			{
+				switch(type.getNominalSize())
+				{
+				case 2:
+					switch(type.getSecondarySize())
+					{
+					case 2: return GL_FLOAT_MAT2;
+					case 3: return GL_FLOAT_MAT2x3;
+					case 4: return GL_FLOAT_MAT2x4;
+					default: UNREACHABLE(type.getSecondarySize());
+					}
+				case 3:
+					switch(type.getSecondarySize())
+					{
+					case 2: return GL_FLOAT_MAT3x2;
+					case 3: return GL_FLOAT_MAT3;
+					case 4: return GL_FLOAT_MAT3x4;
+					default: UNREACHABLE(type.getSecondarySize());
+					}
+				case 4:
+					switch(type.getSecondarySize())
+					{
+					case 2: return GL_FLOAT_MAT4x2;
+					case 3: return GL_FLOAT_MAT4x3;
+					case 4: return GL_FLOAT_MAT4;
+					default: UNREACHABLE(type.getSecondarySize());
+					}
+				default: UNREACHABLE(type.getNominalSize());
+				}
+			}
+			else UNREACHABLE(0);
+			break;
+		case EbtInt:
+			if(type.isScalar())
+			{
+				return GL_INT;
+			}
+			else if(type.isVector())
+			{
+				switch(type.getNominalSize())
+				{
+				case 2: return GL_INT_VEC2;
+				case 3: return GL_INT_VEC3;
+				case 4: return GL_INT_VEC4;
+				default: UNREACHABLE(type.getNominalSize());
+				}
+			}
+			else UNREACHABLE(0);
+			break;
+		case EbtUInt:
+			if(type.isScalar())
+			{
+				return GL_UNSIGNED_INT;
+			}
+			else if(type.isVector())
+			{
+				switch(type.getNominalSize())
+				{
+				case 2: return GL_UNSIGNED_INT_VEC2;
+				case 3: return GL_UNSIGNED_INT_VEC3;
+				case 4: return GL_UNSIGNED_INT_VEC4;
+				default: UNREACHABLE(type.getNominalSize());
+				}
+			}
+			else UNREACHABLE(0);
+			break;
+		case EbtBool:
+			if(type.isScalar())
+			{
+				return GL_BOOL;
+			}
+			else if(type.isVector())
+			{
+				switch(type.getNominalSize())
+				{
+				case 2: return GL_BOOL_VEC2;
+				case 3: return GL_BOOL_VEC3;
+				case 4: return GL_BOOL_VEC4;
+				default: UNREACHABLE(type.getNominalSize());
+				}
+			}
+			else UNREACHABLE(0);
+			break;
+		case EbtSampler2D:
+			return GL_SAMPLER_2D;
+		case EbtISampler2D:
+			return GL_INT_SAMPLER_2D;
+		case EbtUSampler2D:
+			return GL_UNSIGNED_INT_SAMPLER_2D;
+		case EbtSamplerCube:
+			return GL_SAMPLER_CUBE;
+		case EbtISamplerCube:
+			return GL_INT_SAMPLER_CUBE;
+		case EbtUSamplerCube:
+			return GL_UNSIGNED_INT_SAMPLER_CUBE;
+		case EbtSamplerExternalOES:
+			return GL_SAMPLER_EXTERNAL_OES;
+		case EbtSampler3D:
+			return GL_SAMPLER_3D_OES;
+		case EbtISampler3D:
+			return GL_INT_SAMPLER_3D;
+		case EbtUSampler3D:
+			return GL_UNSIGNED_INT_SAMPLER_3D;
+		case EbtSampler2DArray:
+			return GL_SAMPLER_2D_ARRAY;
+		case EbtISampler2DArray:
+			return GL_INT_SAMPLER_2D_ARRAY;
+		case EbtUSampler2DArray:
+			return GL_UNSIGNED_INT_SAMPLER_2D_ARRAY;
+		case EbtSampler2DShadow:
+			return GL_SAMPLER_2D_SHADOW;
+		case EbtSamplerCubeShadow:
+			return GL_SAMPLER_CUBE_SHADOW;
+		case EbtSampler2DArrayShadow:
+			return GL_SAMPLER_2D_ARRAY_SHADOW;
+		default:
+			UNREACHABLE(type.getBasicType());
+			break;
+		}
+
+		return GL_NONE;
+	}
+
+	GLenum glVariablePrecision(const TType &type)
+	{
+		if(type.getBasicType() == EbtFloat)
+		{
+			switch(type.getPrecision())
+			{
+			case EbpHigh:   return GL_HIGH_FLOAT;
+			case EbpMedium: return GL_MEDIUM_FLOAT;
+			case EbpLow:    return GL_LOW_FLOAT;
+			case EbpUndefined:
+				// Should be defined as the default precision by the parser
+			default: UNREACHABLE(type.getPrecision());
+			}
+		}
+		else if(type.getBasicType() == EbtInt)
+		{
+			switch(type.getPrecision())
+			{
+			case EbpHigh:   return GL_HIGH_INT;
+			case EbpMedium: return GL_MEDIUM_INT;
+			case EbpLow:    return GL_LOW_INT;
+			case EbpUndefined:
+				// Should be defined as the default precision by the parser
+			default: UNREACHABLE(type.getPrecision());
+			}
+		}
+
+		// Other types (boolean, sampler) don't have a precision
+		return GL_NONE;
+	}
+}
+
 namespace glsl
 {
 	// Integer to TString conversion
@@ -81,8 +258,21 @@
 		ConstantUnion constants[4];
 	};
 
-	Uniform::Uniform(GLenum type, GLenum precision, const std::string &name, int arraySize, int registerIndex, int blockId, const BlockMemberInfo& blockMemberInfo) :
-		type(type), precision(precision), name(name), arraySize(arraySize), registerIndex(registerIndex), blockId(blockId), blockInfo(blockMemberInfo)
+	ShaderVariable::ShaderVariable(const TType& type, const std::string& name, int registerIndex) :
+		type(type.isStruct() ? GL_NONE : glVariableType(type)), precision(glVariablePrecision(type)),
+		name(name), arraySize(type.getArraySize()), registerIndex(registerIndex)
+	{
+		if(type.isStruct())
+		{
+			for(const auto& field : type.getStruct()->fields())
+			{
+				fields.push_back(ShaderVariable(*(field->type()), field->name().c_str(), -1));
+			}
+		}
+	}
+
+	Uniform::Uniform(const TType& type, const std::string &name, int registerIndex, int blockId, const BlockMemberInfo& blockMemberInfo) :
+		ShaderVariable(type, name, registerIndex), blockId(blockId), blockInfo(blockMemberInfo)
 	{
 	}
 
@@ -2925,15 +3115,15 @@
 				{
 					if(registerIndex >= 0)
 					{
-						ASSERT(v->reg < 0 || v->reg == registerIndex);
-						v->reg = registerIndex;
+						ASSERT(v->registerIndex < 0 || v->registerIndex == registerIndex);
+						v->registerIndex = registerIndex;
 					}
 
 					return;
 				}
 			}
 
-			activeVaryings.push_back(glsl::Varying(glVariableType(type), name, type.getArraySize(), type.getQualifier(), registerIndex, 0));
+			activeVaryings.push_back(glsl::Varying(type, name, registerIndex, 0));
 		}
 	}
 
@@ -3316,10 +3506,9 @@
 					shader->declareSampler(fieldRegisterIndex + i);
 				}
 			}
-			if(!isSampler || samplersOnly)
+			if(isSampler == samplersOnly)
 			{
-				activeUniforms.push_back(Uniform(glVariableType(type), glVariablePrecision(type), name.c_str(), type.getArraySize(),
-				                                 fieldRegisterIndex, blockId, blockInfo));
+				activeUniforms.push_back(Uniform(type, name.c_str(), fieldRegisterIndex, blockId, blockInfo));
 			}
 		}
 		else if(block)
@@ -3357,6 +3546,9 @@
 		}
 		else
 		{
+			// Store struct for program link time validation
+			shaderObject->activeUniformStructs.push_back(Uniform(type, name.c_str(), registerIndex, -1, BlockMemberInfo::getDefaultBlockInfo()));
+
 			int fieldRegisterIndex = registerIndex;
 
 			const TFieldList& fields = structure->fields();
@@ -3406,180 +3598,6 @@
 		}
 	}
 
-	GLenum OutputASM::glVariableType(const TType &type)
-	{
-		switch(type.getBasicType())
-		{
-		case EbtFloat:
-			if(type.isScalar())
-			{
-				return GL_FLOAT;
-			}
-			else if(type.isVector())
-			{
-				switch(type.getNominalSize())
-				{
-				case 2: return GL_FLOAT_VEC2;
-				case 3: return GL_FLOAT_VEC3;
-				case 4: return GL_FLOAT_VEC4;
-				default: UNREACHABLE(type.getNominalSize());
-				}
-			}
-			else if(type.isMatrix())
-			{
-				switch(type.getNominalSize())
-				{
-				case 2:
-					switch(type.getSecondarySize())
-					{
-					case 2: return GL_FLOAT_MAT2;
-					case 3: return GL_FLOAT_MAT2x3;
-					case 4: return GL_FLOAT_MAT2x4;
-					default: UNREACHABLE(type.getSecondarySize());
-					}
-				case 3:
-					switch(type.getSecondarySize())
-					{
-					case 2: return GL_FLOAT_MAT3x2;
-					case 3: return GL_FLOAT_MAT3;
-					case 4: return GL_FLOAT_MAT3x4;
-					default: UNREACHABLE(type.getSecondarySize());
-					}
-				case 4:
-					switch(type.getSecondarySize())
-					{
-					case 2: return GL_FLOAT_MAT4x2;
-					case 3: return GL_FLOAT_MAT4x3;
-					case 4: return GL_FLOAT_MAT4;
-					default: UNREACHABLE(type.getSecondarySize());
-					}
-				default: UNREACHABLE(type.getNominalSize());
-				}
-			}
-			else UNREACHABLE(0);
-			break;
-		case EbtInt:
-			if(type.isScalar())
-			{
-				return GL_INT;
-			}
-			else if(type.isVector())
-			{
-				switch(type.getNominalSize())
-				{
-				case 2: return GL_INT_VEC2;
-				case 3: return GL_INT_VEC3;
-				case 4: return GL_INT_VEC4;
-				default: UNREACHABLE(type.getNominalSize());
-				}
-			}
-			else UNREACHABLE(0);
-			break;
-		case EbtUInt:
-			if(type.isScalar())
-			{
-				return GL_UNSIGNED_INT;
-			}
-			else if(type.isVector())
-			{
-				switch(type.getNominalSize())
-				{
-				case 2: return GL_UNSIGNED_INT_VEC2;
-				case 3: return GL_UNSIGNED_INT_VEC3;
-				case 4: return GL_UNSIGNED_INT_VEC4;
-				default: UNREACHABLE(type.getNominalSize());
-				}
-			}
-			else UNREACHABLE(0);
-			break;
-		case EbtBool:
-			if(type.isScalar())
-			{
-				return GL_BOOL;
-			}
-			else if(type.isVector())
-			{
-				switch(type.getNominalSize())
-				{
-				case 2: return GL_BOOL_VEC2;
-				case 3: return GL_BOOL_VEC3;
-				case 4: return GL_BOOL_VEC4;
-				default: UNREACHABLE(type.getNominalSize());
-				}
-			}
-			else UNREACHABLE(0);
-			break;
-		case EbtSampler2D:
-			return GL_SAMPLER_2D;
-		case EbtISampler2D:
-			return GL_INT_SAMPLER_2D;
-		case EbtUSampler2D:
-			return GL_UNSIGNED_INT_SAMPLER_2D;
-		case EbtSamplerCube:
-			return GL_SAMPLER_CUBE;
-		case EbtISamplerCube:
-			return GL_INT_SAMPLER_CUBE;
-		case EbtUSamplerCube:
-			return GL_UNSIGNED_INT_SAMPLER_CUBE;
-		case EbtSamplerExternalOES:
-			return GL_SAMPLER_EXTERNAL_OES;
-		case EbtSampler3D:
-			return GL_SAMPLER_3D_OES;
-		case EbtISampler3D:
-			return GL_INT_SAMPLER_3D;
-		case EbtUSampler3D:
-			return GL_UNSIGNED_INT_SAMPLER_3D;
-		case EbtSampler2DArray:
-			return GL_SAMPLER_2D_ARRAY;
-		case EbtISampler2DArray:
-			return GL_INT_SAMPLER_2D_ARRAY;
-		case EbtUSampler2DArray:
-			return GL_UNSIGNED_INT_SAMPLER_2D_ARRAY;
-		case EbtSampler2DShadow:
-			return GL_SAMPLER_2D_SHADOW;
-		case EbtSamplerCubeShadow:
-			return GL_SAMPLER_CUBE_SHADOW;
-		case EbtSampler2DArrayShadow:
-			return GL_SAMPLER_2D_ARRAY_SHADOW;
-		default:
-			UNREACHABLE(type.getBasicType());
-			break;
-		}
-
-		return GL_NONE;
-	}
-
-	GLenum OutputASM::glVariablePrecision(const TType &type)
-	{
-		if(type.getBasicType() == EbtFloat)
-		{
-			switch(type.getPrecision())
-			{
-			case EbpHigh:   return GL_HIGH_FLOAT;
-			case EbpMedium: return GL_MEDIUM_FLOAT;
-			case EbpLow:    return GL_LOW_FLOAT;
-			case EbpUndefined:
-				// Should be defined as the default precision by the parser
-			default: UNREACHABLE(type.getPrecision());
-			}
-		}
-		else if(type.getBasicType() == EbtInt)
-		{
-			switch(type.getPrecision())
-			{
-			case EbpHigh:   return GL_HIGH_INT;
-			case EbpMedium: return GL_MEDIUM_INT;
-			case EbpLow:    return GL_LOW_INT;
-			case EbpUndefined:
-				// Should be defined as the default precision by the parser
-			default: UNREACHABLE(type.getPrecision());
-			}
-		}
-
-		// Other types (boolean, sampler) don't have a precision
-		return GL_NONE;
-	}
-
 	int OutputASM::dim(TIntermNode *v)
 	{
 		TIntermTyped *vector = v->getAsTyped();
diff --git a/src/OpenGL/compiler/OutputASM.h b/src/OpenGL/compiler/OutputASM.h
index 67c49db..641ec50 100644
--- a/src/OpenGL/compiler/OutputASM.h
+++ b/src/OpenGL/compiler/OutputASM.h
@@ -55,9 +55,9 @@
 		bool isRowMajorMatrix;
 	};
 
-	struct Uniform
+	struct ShaderVariable
 	{
-		Uniform(GLenum type, GLenum precision, const std::string &name, int arraySize, int registerIndex, int blockId, const BlockMemberInfo& blockMemberInfo);
+		ShaderVariable(const TType& type, const std::string& name, int registerIndex);
 
 		GLenum type;
 		GLenum precision;
@@ -66,6 +66,13 @@
 
 		int registerIndex;
 
+		std::vector<ShaderVariable> fields;
+	};
+
+	struct Uniform : public ShaderVariable
+	{
+		Uniform(const TType& type, const std::string &name, int registerIndex, int blockId, const BlockMemberInfo& blockMemberInfo);
+
 		int blockId;
 		BlockMemberInfo blockInfo;
 	};
@@ -150,10 +157,10 @@
 
 	typedef std::vector<Attribute> ActiveAttributes;
 
-	struct Varying
+	struct Varying : public ShaderVariable
 	{
-		Varying(GLenum type, const std::string &name, int arraySize, TQualifier qualifier, int reg = -1, int col = -1)
-			: type(type), name(name), arraySize(arraySize), qualifier(qualifier), reg(reg), col(col)
+		Varying(const TType& type, const std::string &name, int reg = -1, int col = -1)
+			: ShaderVariable(type, name, reg), qualifier(type.getQualifier()), col(col)
 		{
 		}
 
@@ -167,12 +174,7 @@
 			return arraySize > 0 ? arraySize : 1;
 		}
 
-		GLenum type;
-		std::string name;
-		int arraySize;
 		TQualifier qualifier;
-
-		int reg;    // First varying register, assigned during link
 		int col;    // First register element, assigned during link
 	};
 
@@ -191,6 +193,7 @@
 	protected:
 		VaryingList varyings;
 		ActiveUniforms activeUniforms;
+		ActiveUniforms activeUniformStructs;
 		ActiveAttributes activeAttributes;
 		ActiveUniformBlocks activeUniformBlocks;
 		int shaderVersion;
@@ -310,8 +313,6 @@
 		void free(VariableArray &list, TIntermTyped *variable);
 
 		void declareUniform(const TType &type, const TString &name, int registerIndex, bool samplersOnly, int blockId = -1, BlockLayoutEncoder* encoder = nullptr);
-		GLenum glVariableType(const TType &type);
-		GLenum glVariablePrecision(const TType &type);
 
 		static int dim(TIntermNode *v);
 		static int dim2(TIntermNode *m);
diff --git a/src/OpenGL/libGLESv2/Program.cpp b/src/OpenGL/libGLESv2/Program.cpp
index 02846ee..e427531 100644
--- a/src/OpenGL/libGLESv2/Program.cpp
+++ b/src/OpenGL/libGLESv2/Program.cpp
@@ -61,11 +61,11 @@
 		}
 	}
 
-	Uniform::Uniform(GLenum type, GLenum precision, const std::string &name, unsigned int arraySize,
-	                 const BlockInfo &blockInfo)
-	 : type(type), precision(precision), name(name), arraySize(arraySize), blockInfo(blockInfo)
+	Uniform::Uniform(const glsl::Uniform &uniform, const BlockInfo &blockInfo)
+	 : type(uniform.type), precision(uniform.precision), name(uniform.name),
+	   arraySize(uniform.arraySize), blockInfo(blockInfo), fields(uniform.fields)
 	{
-		if(blockInfo.index == -1)
+		if((blockInfo.index == -1) && uniform.fields.empty())
 		{
 			size_t bytes = UniformTypeSize(type) * size();
 			data = new unsigned char[bytes];
@@ -267,7 +267,7 @@
 				{
 					int rowCount = VariableRowCount(input.type);
 					int colCount = VariableColumnCount(input.type);
-					return (subscript == GL_INVALID_INDEX) ? input.reg : input.reg + (rowCount > 1 ? colCount * subscript : subscript);
+					return (subscript == GL_INVALID_INDEX) ? input.registerIndex : input.registerIndex + (rowCount > 1 ? colCount * subscript : subscript);
 				}
 			}
 		}
@@ -1337,6 +1337,11 @@
 						return false;
 					}
 
+					if(!areMatchingFields(input.fields, output.fields, input.name))
+					{
+						return false;
+					}
+
 					matched = true;
 					break;
 				}
@@ -1358,8 +1363,8 @@
 			{
 				if(output.name == input.name)
 				{
-					int in = input.reg;
-					int out = output.reg;
+					int in = input.registerIndex;
+					int out = output.registerIndex;
 					int components = VariableRegisterSize(output.type);
 					int registers = VariableRegisterCount(output.type) * output.size();
 
@@ -1406,7 +1411,7 @@
 
 					if(tfVaryingName == output.name)
 					{
-						int out = output.reg;
+						int out = output.registerIndex;
 						int components = VariableRegisterSize(output.type);
 						int registers = VariableRegisterCount(output.type) * output.size();
 
@@ -1484,7 +1489,7 @@
 
 					totalComponents += componentCount;
 
-					int reg = varying.reg;
+					int reg = varying.registerIndex;
 					if(hasSubscript)
 					{
 						reg += rowCount > 1 ? colCount * subscript : subscript;
@@ -1683,12 +1688,8 @@
 
 	bool Program::linkUniforms(const Shader *shader)
 	{
-		const glsl::ActiveUniforms &activeUniforms = shader->activeUniforms;
-
-		for(unsigned int uniformIndex = 0; uniformIndex < activeUniforms.size(); uniformIndex++)
+		for(const auto &uniform : shader->activeUniforms)
 		{
-			const glsl::Uniform &uniform = activeUniforms[uniformIndex];
-
 			unsigned int blockIndex = GL_INVALID_INDEX;
 			if(uniform.blockId >= 0)
 			{
@@ -1697,7 +1698,15 @@
 				blockIndex = getUniformBlockIndex(activeUniformBlocks[uniform.blockId].name);
 				ASSERT(blockIndex != GL_INVALID_INDEX);
 			}
-			if(!defineUniform(shader->getType(), uniform.type, uniform.precision, uniform.name, uniform.arraySize, uniform.registerIndex, Uniform::BlockInfo(uniform, blockIndex)))
+			if(!defineUniform(shader->getType(), uniform, Uniform::BlockInfo(uniform, blockIndex)))
+			{
+				return false;
+			}
+		}
+
+		for(const auto &uniformStruct : shader->activeUniformStructs)
+		{
+			if(!validateUniformStruct(shader->getType(), uniformStruct))
 			{
 				return false;
 			}
@@ -1706,11 +1715,11 @@
 		return true;
 	}
 
-	bool Program::defineUniform(GLenum shader, GLenum type, GLenum precision, const std::string &name, unsigned int arraySize, int registerIndex, const Uniform::BlockInfo& blockInfo)
+	bool Program::defineUniform(GLenum shader, const glsl::Uniform &glslUniform, const Uniform::BlockInfo& blockInfo)
 	{
-		if(IsSamplerUniform(type))
+		if(IsSamplerUniform(glslUniform.type))
 	    {
-			int index = registerIndex;
+			int index = glslUniform.registerIndex;
 
 			do
 			{
@@ -1720,9 +1729,9 @@
 					{
 						samplersVS[index].active = true;
 
-						switch(type)
+						switch(glslUniform.type)
 						{
-						default:                      UNREACHABLE(type);
+						default:                      UNREACHABLE(glslUniform.type);
 						case GL_INT_SAMPLER_2D:
 						case GL_UNSIGNED_INT_SAMPLER_2D:
 						case GL_SAMPLER_2D_SHADOW:
@@ -1755,9 +1764,9 @@
 					{
 						samplersPS[index].active = true;
 
-						switch(type)
+						switch(glslUniform.type)
 						{
-						default:                      UNREACHABLE(type);
+						default:                      UNREACHABLE(glslUniform.type);
 						case GL_INT_SAMPLER_2D:
 						case GL_UNSIGNED_INT_SAMPLER_2D:
 						case GL_SAMPLER_2D_SHADOW:
@@ -1788,31 +1797,36 @@
 
 				index++;
 			}
-			while(index < registerIndex + static_cast<int>(arraySize));
+			while(index < glslUniform.registerIndex + static_cast<int>(glslUniform.arraySize));
 	    }
 
 		Uniform *uniform = 0;
-		GLint location = getUniformLocation(name);
+		GLint location = getUniformLocation(glslUniform.name);
 
 		if(location >= 0)   // Previously defined, types must match
 		{
 			uniform = uniforms[uniformIndex[location].index];
 
-			if(uniform->type != type)
+			if(uniform->type != glslUniform.type)
 			{
 				appendToInfoLog("Types for uniform %s do not match between the vertex and fragment shader", uniform->name.c_str());
 				return false;
 			}
 
-			if(uniform->precision != precision)
+			if(uniform->precision != glslUniform.precision)
 			{
 				appendToInfoLog("Precisions for uniform %s do not match between the vertex and fragment shader", uniform->name.c_str());
 				return false;
 			}
+
+			if(!areMatchingFields(uniform->fields, glslUniform.fields, uniform->name))
+			{
+				return false;
+			}
 		}
 		else
 		{
-			uniform = new Uniform(type, precision, name, arraySize, blockInfo);
+			uniform = new Uniform(glslUniform, blockInfo);
 		}
 
 		if(!uniform)
@@ -1822,28 +1836,28 @@
 
 		if(shader == GL_VERTEX_SHADER)
 		{
-			uniform->vsRegisterIndex = registerIndex;
+			uniform->vsRegisterIndex = glslUniform.registerIndex;
 		}
 		else if(shader == GL_FRAGMENT_SHADER)
 		{
-			uniform->psRegisterIndex = registerIndex;
+			uniform->psRegisterIndex = glslUniform.registerIndex;
 		}
 		else UNREACHABLE(shader);
 
-		if(!isUniformDefined(name))
+		if(!isUniformDefined(glslUniform.name))
 		{
 			uniforms.push_back(uniform);
 			unsigned int index = (blockInfo.index == -1) ? static_cast<unsigned int>(uniforms.size() - 1) : GL_INVALID_INDEX;
 
 			for(int i = 0; i < uniform->size(); i++)
 			{
-				uniformIndex.push_back(UniformLocation(name, i, index));
+				uniformIndex.push_back(UniformLocation(glslUniform.name, i, index));
 			}
 		}
 
 		if(shader == GL_VERTEX_SHADER)
 		{
-			if(registerIndex + uniform->registerCount() > MAX_VERTEX_UNIFORM_VECTORS)
+			if(glslUniform.registerIndex + uniform->registerCount() > MAX_VERTEX_UNIFORM_VECTORS)
 			{
 				appendToInfoLog("Vertex shader active uniforms exceed GL_MAX_VERTEX_UNIFORM_VECTORS (%d)", MAX_VERTEX_UNIFORM_VECTORS);
 				return false;
@@ -1851,7 +1865,7 @@
 		}
 		else if(shader == GL_FRAGMENT_SHADER)
 		{
-			if(registerIndex + uniform->registerCount() > MAX_FRAGMENT_UNIFORM_VECTORS)
+			if(glslUniform.registerIndex + uniform->registerCount() > MAX_FRAGMENT_UNIFORM_VECTORS)
 			{
 				appendToInfoLog("Fragment shader active uniforms exceed GL_MAX_FRAGMENT_UNIFORM_VECTORS (%d)", MAX_FRAGMENT_UNIFORM_VECTORS);
 				return false;
@@ -1862,6 +1876,21 @@
 		return true;
 	}
 
+	bool Program::validateUniformStruct(GLenum shader, const glsl::Uniform &newUniformStruct)
+	{
+		for(const auto &uniformStruct : uniformStructs)
+		{
+			if(uniformStruct.name == newUniformStruct.name)
+			{
+				return areMatchingFields(uniformStruct.fields, newUniformStruct.fields, newUniformStruct.name);
+			}
+		}
+
+		uniformStructs.push_back(Uniform(newUniformStruct, Uniform::BlockInfo(newUniformStruct, -1)));
+
+		return true;
+	}
+
 	bool Program::areMatchingUniformBlocks(const glsl::UniformBlock &block1, const glsl::UniformBlock &block2, const Shader *shader1, const Shader *shader2)
 	{
 		// validate blocks for the same member types
@@ -1915,6 +1944,41 @@
 		return true;
 	}
 
+	bool Program::areMatchingFields(const std::vector<glsl::ShaderVariable>& fields1, const std::vector<glsl::ShaderVariable>& fields2, const std::string& name)
+	{
+		if(fields1.size() != fields2.size())
+		{
+			appendToInfoLog("Structure lengths for %s differ between vertex and fragment shaders", name.c_str());
+			return false;
+		}
+
+		for(int i = 0; i < fields1.size(); ++i)
+		{
+			if(fields1[i].name != fields2[i].name)
+			{
+				appendToInfoLog("Name mismatch for field '%d' of %s: ('%s', '%s')",
+				                i, name.c_str(), fields1[i].name.c_str(), fields2[i].name.c_str());
+				return false;
+			}
+			if(fields1[i].type != fields2[i].type)
+			{
+				appendToInfoLog("Type for %s.%s differ between vertex and fragment shaders", name.c_str(), fields1[i].name.c_str());
+				return false;
+			}
+			if(fields1[i].arraySize != fields2[i].arraySize)
+			{
+				appendToInfoLog("Array size for %s.%s differ between vertex and fragment shaders", name.c_str(), fields1[i].name.c_str());
+				return false;
+			}
+			if(!areMatchingFields(fields1[i].fields, fields2[i].fields, fields1[i].name))
+			{
+				return false;
+			}
+		}
+
+		return true;
+	}
+
 	bool Program::linkUniformBlocks(const Shader *vertexShader, const Shader *fragmentShader)
 	{
 		const glsl::ActiveUniformBlocks &vertexUniformBlocks = vertexShader->activeUniformBlocks;
diff --git a/src/OpenGL/libGLESv2/Program.h b/src/OpenGL/libGLESv2/Program.h
index 451b2dc..4d89d7a 100644
--- a/src/OpenGL/libGLESv2/Program.h
+++ b/src/OpenGL/libGLESv2/Program.h
@@ -49,8 +49,7 @@
 			bool isRowMajorMatrix;
 		};
 
-		Uniform(GLenum type, GLenum precision, const std::string &name, unsigned int arraySize,
-		        const BlockInfo &blockInfo);
+		Uniform(const glsl::Uniform &uniform, const BlockInfo &blockInfo);
 
 		~Uniform();
 
@@ -63,6 +62,7 @@
 		const std::string name;
 		const unsigned int arraySize;
 		const BlockInfo blockInfo;
+		std::vector<glsl::ShaderVariable> fields;
 
 		unsigned char *data;
 		bool dirty;
@@ -234,7 +234,9 @@
 		bool linkUniforms(const Shader *shader);
 		bool linkUniformBlocks(const Shader *vertexShader, const Shader *fragmentShader);
 		bool areMatchingUniformBlocks(const glsl::UniformBlock &block1, const glsl::UniformBlock &block2, const Shader *shader1, const Shader *shader2);
-		bool defineUniform(GLenum shader, GLenum type, GLenum precision, const std::string &_name, unsigned int arraySize, int registerIndex, const Uniform::BlockInfo& blockInfo);
+		bool areMatchingFields(const std::vector<glsl::ShaderVariable>& fields1, const std::vector<glsl::ShaderVariable>& fields2, const std::string& name);
+		bool validateUniformStruct(GLenum shader, const glsl::Uniform &newUniformStruct);
+		bool defineUniform(GLenum shader, const glsl::Uniform &uniform, const Uniform::BlockInfo& blockInfo);
 		bool defineUniformBlock(const Shader *shader, const glsl::UniformBlock &block);
 		bool applyUniform(Device *device, GLint location, float* data);
 		bool applyUniform1bv(Device *device, GLint location, GLsizei count, const GLboolean *v);
@@ -303,6 +305,8 @@
 
 		typedef std::vector<Uniform*> UniformArray;
 		UniformArray uniforms;
+		typedef std::vector<Uniform> UniformStructArray;
+		UniformStructArray uniformStructs;
 		typedef std::vector<UniformLocation> UniformIndex;
 		UniformIndex uniformIndex;
 		typedef std::vector<UniformBlock*> UniformBlockArray;