Re-land "Move sampler validation to the GL layer."

This previously was D3D-only, but is required for every draw call.
This completes the work of removing the D3D-specific Impl methods
from ProgramImpl.

Also add several regression tests to cover texture and sampler
validation.

Re-land with a fix for duplicate sampler active uniforms.

BUG=angleproject:1123

Change-Id: Iefef06e7901873c98bf2ba7864efd16a4c6435d3
Reviewed-on: https://chromium-review.googlesource.com/301581
Tryjob-Request: Jamie Madill <jmadill@chromium.org>
Tested-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index 6922adf..14a50ba 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -174,6 +174,17 @@
     }
 }
 
+bool UniformInList(const std::vector<LinkedUniform> &list, const std::string &name)
+{
+    for (const LinkedUniform &uniform : list)
+    {
+        if (uniform.name == name)
+            return true;
+    }
+
+    return false;
+}
+
 }  // anonymous namespace
 
 AttributeBindings::AttributeBindings()
@@ -359,7 +370,8 @@
       mDeleteStatus(false),
       mRefCount(0),
       mResourceManager(manager),
-      mHandle(handle)
+      mHandle(handle),
+      mSamplerUniformRange(0, 0)
 {
     ASSERT(mProgram);
 
@@ -674,12 +686,15 @@
     {
         int locationIndex = stream.readInt<int>();
         VariableLocation locationData;
-        locationData.element                  = stream.readInt<unsigned int>();
-        locationData.index                    = stream.readInt<unsigned int>();
-        locationData.name                     = stream.readString();
+        stream.readInt(&locationData.element);
+        stream.readInt(&locationData.index);
+        stream.readString(&locationData.name);
         mData.mOutputVariables[locationIndex] = locationData;
     }
 
+    stream.readInt(&mSamplerUniformRange.start);
+    stream.readInt(&mSamplerUniformRange.end);
+
     rx::LinkResult result = mProgram->load(mInfoLog, &stream);
     if (result.error.isError() || !result.linkSuccess)
     {
@@ -769,6 +784,9 @@
         stream.writeString(outputPair.second.name);
     }
 
+    stream.writeInt(mSamplerUniformRange.start);
+    stream.writeInt(mSamplerUniformRange.end);
+
     gl::Error error = mProgram->save(&stream);
     if (error.isError())
     {
@@ -1297,7 +1315,78 @@
 
 bool Program::validateSamplers(InfoLog *infoLog, const Caps &caps)
 {
-    return mProgram->validateSamplers(infoLog, caps);
+    // Skip cache if we're using an infolog, so we get the full error.
+    // Also skip the cache if the sample mapping has changed, or if we haven't ever validated.
+    if (infoLog == nullptr && mCachedValidateSamplersResult.valid())
+    {
+        return mCachedValidateSamplersResult.value();
+    }
+
+    if (mTextureUnitTypesCache.empty())
+    {
+        mTextureUnitTypesCache.resize(caps.maxCombinedTextureImageUnits, GL_NONE);
+    }
+    else
+    {
+        std::fill(mTextureUnitTypesCache.begin(), mTextureUnitTypesCache.end(), GL_NONE);
+    }
+
+    // if any two active samplers in a program are of different types, but refer to the same
+    // texture image unit, and this is the current program, then ValidateProgram will fail, and
+    // DrawArrays and DrawElements will issue the INVALID_OPERATION error.
+    for (unsigned int samplerIndex = mSamplerUniformRange.start;
+         samplerIndex < mSamplerUniformRange.end; ++samplerIndex)
+    {
+        const LinkedUniform &uniform = mData.mUniforms[samplerIndex];
+        ASSERT(uniform.isSampler());
+
+        if (!uniform.staticUse)
+            continue;
+
+        const GLuint *dataPtr = reinterpret_cast<const GLuint *>(uniform.getDataPtrToElement(0));
+        GLenum textureType    = SamplerTypeToTextureType(uniform.type);
+
+        for (unsigned int arrayElement = 0; arrayElement < uniform.elementCount(); ++arrayElement)
+        {
+            GLuint textureUnit = dataPtr[arrayElement];
+
+            if (textureUnit >= caps.maxCombinedTextureImageUnits)
+            {
+                if (infoLog)
+                {
+                    (*infoLog) << "Sampler uniform (" << textureUnit
+                               << ") exceeds GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS ("
+                               << caps.maxCombinedTextureImageUnits << ")";
+                }
+
+                mCachedValidateSamplersResult = false;
+                return false;
+            }
+
+            if (mTextureUnitTypesCache[textureUnit] != GL_NONE)
+            {
+                if (textureType != mTextureUnitTypesCache[textureUnit])
+                {
+                    if (infoLog)
+                    {
+                        (*infoLog) << "Samplers of conflicting types refer to the same texture "
+                                      "image unit ("
+                                   << textureUnit << ").";
+                    }
+
+                    mCachedValidateSamplersResult = false;
+                    return false;
+                }
+            }
+            else
+            {
+                mTextureUnitTypesCache[textureUnit] = textureType;
+            }
+        }
+    }
+
+    mCachedValidateSamplersResult = true;
+    return true;
 }
 
 bool Program::isValidated() const
@@ -2086,11 +2175,13 @@
     const gl::Shader *vertexShader = mData.getAttachedVertexShader();
     VectorAndSamplerCount vsCounts;
 
+    std::vector<LinkedUniform> samplerUniforms;
+
     for (const sh::Uniform &uniform : vertexShader->getUniforms())
     {
         if (uniform.staticUse)
         {
-            vsCounts += flattenUniform(uniform, uniform.name);
+            vsCounts += flattenUniform(uniform, uniform.name, &samplerUniforms);
         }
     }
 
@@ -2115,7 +2206,7 @@
     {
         if (uniform.staticUse)
         {
-            fsCounts += flattenUniform(uniform, uniform.name);
+            fsCounts += flattenUniform(uniform, uniform.name, &samplerUniforms);
         }
     }
 
@@ -2133,11 +2224,18 @@
         return false;
     }
 
+    mSamplerUniformRange.start = static_cast<unsigned int>(mData.mUniforms.size());
+    mSamplerUniformRange.end =
+        mSamplerUniformRange.start + static_cast<unsigned int>(samplerUniforms.size());
+
+    mData.mUniforms.insert(mData.mUniforms.end(), samplerUniforms.begin(), samplerUniforms.end());
+
     return true;
 }
 
 Program::VectorAndSamplerCount Program::flattenUniform(const sh::ShaderVariable &uniform,
-                                                       const std::string &fullName)
+                                                       const std::string &fullName,
+                                                       std::vector<LinkedUniform> *samplerUniforms)
 {
     VectorAndSamplerCount vectorAndSamplerCount;
 
@@ -2152,7 +2250,7 @@
                 const sh::ShaderVariable &field  = uniform.fields[fieldIndex];
                 const std::string &fieldFullName = (fullName + elementString + "." + field.name);
 
-                vectorAndSamplerCount += flattenUniform(field, fieldFullName);
+                vectorAndSamplerCount += flattenUniform(field, fieldFullName, samplerUniforms);
             }
         }
 
@@ -2160,18 +2258,28 @@
     }
 
     // Not a struct
-    if (mData.getUniformByName(fullName) == nullptr)
+    bool isSampler = IsSamplerType(uniform.type);
+    if (!UniformInList(mData.getUniforms(), fullName) && !UniformInList(*samplerUniforms, fullName))
     {
         gl::LinkedUniform linkedUniform(uniform.type, uniform.precision, fullName,
                                         uniform.arraySize, -1,
                                         sh::BlockMemberInfo::getDefaultBlockInfo());
         linkedUniform.staticUse = true;
-        mData.mUniforms.push_back(linkedUniform);
+
+        // Store sampler uniforms separately, so we'll append them to the end of the list.
+        if (isSampler)
+        {
+            samplerUniforms->push_back(linkedUniform);
+        }
+        else
+        {
+            mData.mUniforms.push_back(linkedUniform);
+        }
     }
 
-    vectorAndSamplerCount.vectorCount =
-        (VariableRegisterCount(uniform.type) * uniform.elementCount());
-    vectorAndSamplerCount.samplerCount = (IsSamplerType(uniform.type) ? uniform.elementCount() : 0);
+    unsigned int elementCount          = uniform.elementCount();
+    vectorAndSamplerCount.vectorCount  = (VariableRegisterCount(uniform.type) * elementCount);
+    vectorAndSamplerCount.samplerCount = (isSampler ? elementCount : 0);
 
     return vectorAndSamplerCount;
 }
@@ -2294,6 +2402,12 @@
     }
     else
     {
+        // Invalide the validation cache if we modify the sampler data.
+        if (linkedUniform->isSampler() && memcmp(destPointer, v, sizeof(T) * count) != 0)
+        {
+            mCachedValidateSamplersResult.reset();
+        }
+
         memcpy(destPointer, v, sizeof(T) * count);
     }
 }
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index 3dd7c46..6e9d682 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -10,13 +10,6 @@
 #ifndef LIBANGLE_PROGRAM_H_
 #define LIBANGLE_PROGRAM_H_
 
-#include "libANGLE/angletypes.h"
-#include "libANGLE/Constants.h"
-#include "libANGLE/Error.h"
-#include "libANGLE/RefCountObject.h"
-
-#include "common/angleutils.h"
-
 #include <GLES2/gl2.h>
 #include <GLSLANG/ShaderLang.h>
 
@@ -25,6 +18,15 @@
 #include <string>
 #include <vector>
 
+#include "common/angleutils.h"
+#include "common/mathutil.h"
+#include "common/Optional.h"
+
+#include "libANGLE/angletypes.h"
+#include "libANGLE/Constants.h"
+#include "libANGLE/Error.h"
+#include "libANGLE/RefCountObject.h"
+
 namespace rx
 {
 class ImplFactory;
@@ -215,6 +217,11 @@
         std::vector<sh::Attribute> mAttributes;
         std::bitset<MAX_VERTEX_ATTRIBS> mActiveAttribLocationsMask;
 
+        // Uniforms are sorted in order:
+        //  1. Non-sampler uniforms
+        //  2. Sampler uniforms
+        //  3. Uniform block uniforms
+        // This makes sampler validation easier, since we don't need a separate list.
         std::vector<LinkedUniform> mUniforms;
         std::vector<VariableLocation> mUniformLocations;
         std::vector<UniformBlock> mUniformBlocks;
@@ -384,7 +391,8 @@
     };
 
     VectorAndSamplerCount flattenUniform(const sh::ShaderVariable &uniform,
-                                         const std::string &fullName);
+                                         const std::string &fullName,
+                                         std::vector<LinkedUniform> *samplerUniforms);
 
     void gatherInterfaceBlockInfo();
     void defineUniformBlock(const sh::InterfaceBlock &interfaceBlock, GLenum shaderType);
@@ -414,6 +422,11 @@
     const GLuint mHandle;
 
     InfoLog mInfoLog;
+
+    // Cache for sampler validation
+    Optional<bool> mCachedValidateSamplersResult;
+    std::vector<GLenum> mTextureUnitTypesCache;
+    RangeUI mSamplerUniformRange;
 };
 }
 
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
index 752f995..d51015a 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -633,6 +633,7 @@
 {
     const auto it = mSamplerTextures.find(type);
     ASSERT(it != mSamplerTextures.end());
+    ASSERT(sampler < it->second.size());
     return it->second[sampler].get();
 }
 
@@ -640,6 +641,7 @@
 {
     const auto it = mSamplerTextures.find(type);
     ASSERT(it != mSamplerTextures.end());
+    ASSERT(sampler < it->second.size());
     return it->second[sampler].id();
 }
 
diff --git a/src/libANGLE/Uniform.h b/src/libANGLE/Uniform.h
index 040c978..1b21825 100644
--- a/src/libANGLE/Uniform.h
+++ b/src/libANGLE/Uniform.h
@@ -64,6 +64,7 @@
 
     std::vector<unsigned int> memberUniformIndexes;
 
+    // TODO(jmadill): Make D3D-only.
     unsigned int psRegisterIndex;
     unsigned int vsRegisterIndex;
 };
diff --git a/src/libANGLE/renderer/ProgramImpl.h b/src/libANGLE/renderer/ProgramImpl.h
index 47fb9be..a47b18e 100644
--- a/src/libANGLE/renderer/ProgramImpl.h
+++ b/src/libANGLE/renderer/ProgramImpl.h
@@ -66,10 +66,6 @@
     virtual void setUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) = 0;
     virtual void setUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) = 0;
 
-    // TODO: The following functions are possibly only applicable to D3D backends. The should be carefully evaluated to
-    // determine if they can be removed from this interface.
-    virtual bool validateSamplers(gl::InfoLog *infoLog, const gl::Caps &caps) = 0;
-
     // Gather uniform block active uniform indices, and uniform block offset info.
     virtual void gatherUniformBlockInfo(std::vector<gl::UniformBlock> *uniformBlocks,
                                         std::vector<gl::LinkedUniform> *uniforms) = 0;
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index 6cb5c14..4819ca0 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -28,36 +28,6 @@
 namespace
 {
 
-GLenum GetTextureType(GLenum samplerType)
-{
-    switch (samplerType)
-    {
-      case GL_SAMPLER_2D:
-      case GL_INT_SAMPLER_2D:
-      case GL_UNSIGNED_INT_SAMPLER_2D:
-      case GL_SAMPLER_2D_SHADOW:
-        return GL_TEXTURE_2D;
-      case GL_SAMPLER_3D:
-      case GL_INT_SAMPLER_3D:
-      case GL_UNSIGNED_INT_SAMPLER_3D:
-        return GL_TEXTURE_3D;
-      case GL_SAMPLER_CUBE:
-      case GL_SAMPLER_CUBE_SHADOW:
-        return GL_TEXTURE_CUBE_MAP;
-      case GL_INT_SAMPLER_CUBE:
-      case GL_UNSIGNED_INT_SAMPLER_CUBE:
-        return GL_TEXTURE_CUBE_MAP;
-      case GL_SAMPLER_2D_ARRAY:
-      case GL_INT_SAMPLER_2D_ARRAY:
-      case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
-      case GL_SAMPLER_2D_ARRAY_SHADOW:
-        return GL_TEXTURE_2D_ARRAY;
-      default: UNREACHABLE();
-    }
-
-    return GL_TEXTURE_2D;
-}
-
 gl::InputLayout GetDefaultInputLayoutFromShader(const gl::Shader *vertexShader)
 {
     gl::InputLayout defaultLayout;
@@ -338,7 +308,6 @@
       mUsedVertexSamplerRange(0),
       mUsedPixelSamplerRange(0),
       mDirtySamplerMapping(true),
-      mTextureUnitTypesCache(renderer->getRendererCaps().maxCombinedTextureImageUnits),
       mShaderVersion(100),
       mSerial(issueSerial())
 {
@@ -486,106 +455,6 @@
     }
 }
 
-bool ProgramD3D::validateSamplers(gl::InfoLog *infoLog, const gl::Caps &caps)
-{
-    // Skip cache if we're using an infolog, so we get the full error.
-    // Also skip the cache if the sample mapping has changed, or if we haven't ever validated.
-    if (!mDirtySamplerMapping && infoLog == nullptr && mCachedValidateSamplersResult.valid())
-    {
-        return mCachedValidateSamplersResult.value();
-    }
-
-    // if any two active samplers in a program are of different types, but refer to the same
-    // texture image unit, and this is the current program, then ValidateProgram will fail, and
-    // DrawArrays and DrawElements will issue the INVALID_OPERATION error.
-    updateSamplerMapping();
-
-    std::fill(mTextureUnitTypesCache.begin(), mTextureUnitTypesCache.end(), GL_NONE);
-
-    for (unsigned int i = 0; i < mUsedPixelSamplerRange; ++i)
-    {
-        if (mSamplersPS[i].active)
-        {
-            unsigned int unit = mSamplersPS[i].logicalTextureUnit;
-
-            if (unit >= caps.maxCombinedTextureImageUnits)
-            {
-                if (infoLog)
-                {
-                    (*infoLog) << "Sampler uniform (" << unit
-                               << ") exceeds GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS ("
-                               << caps.maxCombinedTextureImageUnits << ")";
-                }
-
-                mCachedValidateSamplersResult = false;
-                return false;
-            }
-
-            if (mTextureUnitTypesCache[unit] != GL_NONE)
-            {
-                if (mSamplersPS[i].textureType != mTextureUnitTypesCache[unit])
-                {
-                    if (infoLog)
-                    {
-                        (*infoLog) << "Samplers of conflicting types refer to the same texture image unit ("
-                                   << unit << ").";
-                    }
-
-                    mCachedValidateSamplersResult = false;
-                    return false;
-                }
-            }
-            else
-            {
-                mTextureUnitTypesCache[unit] = mSamplersPS[i].textureType;
-            }
-        }
-    }
-
-    for (unsigned int i = 0; i < mUsedVertexSamplerRange; ++i)
-    {
-        if (mSamplersVS[i].active)
-        {
-            unsigned int unit = mSamplersVS[i].logicalTextureUnit;
-
-            if (unit >= caps.maxCombinedTextureImageUnits)
-            {
-                if (infoLog)
-                {
-                    (*infoLog) << "Sampler uniform (" << unit
-                               << ") exceeds GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS ("
-                               << caps.maxCombinedTextureImageUnits << ")";
-                }
-
-                mCachedValidateSamplersResult = false;
-                return false;
-            }
-
-            if (mTextureUnitTypesCache[unit] != GL_NONE)
-            {
-                if (mSamplersVS[i].textureType != mTextureUnitTypesCache[unit])
-                {
-                    if (infoLog)
-                    {
-                        (*infoLog) << "Samplers of conflicting types refer to the same texture image unit ("
-                                   << unit << ").";
-                    }
-
-                    mCachedValidateSamplersResult = false;
-                    return false;
-                }
-            }
-            else
-            {
-                mTextureUnitTypesCache[unit] = mSamplersVS[i].textureType;
-            }
-        }
-    }
-
-    mCachedValidateSamplersResult = true;
-    return true;
-}
-
 LinkResult ProgramD3D::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream)
 {
     reset();
@@ -1173,7 +1042,7 @@
 
     initSemanticIndex();
 
-    assignUniformRegisters();
+    defineUniformsAndAssignRegisters();
 
     gatherTransformFeedbackVaryings(linkedVaryings);
 
@@ -1187,10 +1056,10 @@
     return LinkResult(true, gl::Error(GL_NO_ERROR));
 }
 
-GLboolean ProgramD3D::validate(const gl::Caps &caps, gl::InfoLog *infoLog)
+GLboolean ProgramD3D::validate(const gl::Caps & /*caps*/, gl::InfoLog * /*infoLog*/)
 {
-    applyUniforms();
-    return validateSamplers(infoLog, caps);
+    // TODO(jmadill): Do something useful here?
+    return GL_TRUE;
 }
 
 void ProgramD3D::gatherUniformBlockInfo(std::vector<gl::UniformBlock> *uniformBlocks,
@@ -1475,17 +1344,19 @@
     setUniform(location, count, v, GL_UNSIGNED_INT_VEC4);
 }
 
-void ProgramD3D::assignUniformRegisters()
+void ProgramD3D::defineUniformsAndAssignRegisters()
 {
     const gl::Shader *vertexShader   = mData.getAttachedVertexShader();
     const ShaderD3D *vertexShaderD3D = GetImplAs<ShaderD3D>(vertexShader);
 
+    D3DUniformMap uniformMap;
+
     for (const sh::Uniform &vertexUniform : vertexShader->getUniforms())
 
     {
         if (vertexUniform.staticUse)
         {
-            assignUniformRegistersBase(vertexShaderD3D, vertexUniform);
+            defineUniformBase(vertexShaderD3D, vertexUniform, &uniformMap);
         }
     }
 
@@ -1496,19 +1367,32 @@
     {
         if (fragmentUniform.staticUse)
         {
-            assignUniformRegistersBase(fragmentShaderD3D, fragmentUniform);
+            defineUniformBase(fragmentShaderD3D, fragmentUniform, &uniformMap);
         }
     }
 
+    // Initialize the D3DUniform list to mirror the indexing of the GL layer.
+    for (const gl::LinkedUniform &glUniform : mData.getUniforms())
+    {
+        if (!glUniform.isInDefaultBlock())
+            continue;
+
+        auto mapEntry = uniformMap.find(glUniform.name);
+        ASSERT(mapEntry != uniformMap.end());
+        mD3DUniforms.push_back(mapEntry->second);
+    }
+
     assignAllSamplerRegisters();
     initializeUniformStorage();
 }
 
-void ProgramD3D::assignUniformRegistersBase(const ShaderD3D *shader, const sh::Uniform &uniform)
+void ProgramD3D::defineUniformBase(const ShaderD3D *shader,
+                                   const sh::Uniform &uniform,
+                                   D3DUniformMap *uniformMap)
 {
     if (uniform.isBuiltIn())
     {
-        assignUniformRegisters(shader, uniform, uniform.name, nullptr);
+        defineUniform(shader, uniform, uniform.name, nullptr, uniformMap);
         return;
     }
 
@@ -1517,7 +1401,7 @@
     sh::HLSLBlockEncoder encoder(sh::HLSLBlockEncoder::GetStrategyFor(outputType));
     encoder.skipRegisters(startRegister);
 
-    assignUniformRegisters(shader, uniform, uniform.name, &encoder);
+    defineUniform(shader, uniform, uniform.name, &encoder, uniformMap);
 }
 
 D3DUniform *ProgramD3D::getD3DUniformByName(const std::string &name)
@@ -1533,10 +1417,11 @@
     return nullptr;
 }
 
-void ProgramD3D::assignUniformRegisters(const ShaderD3D *shader,
-                                        const sh::ShaderVariable &uniform,
-                                        const std::string &fullName,
-                                        sh::HLSLBlockEncoder *encoder)
+void ProgramD3D::defineUniform(const ShaderD3D *shader,
+                               const sh::ShaderVariable &uniform,
+                               const std::string &fullName,
+                               sh::HLSLBlockEncoder *encoder,
+                               D3DUniformMap *uniformMap)
 {
     if (uniform.isStruct())
     {
@@ -1552,7 +1437,7 @@
                 const sh::ShaderVariable &field = uniform.fields[fieldIndex];
                 const std::string &fieldFullName = (fullName + elementString + "." + field.name);
 
-                assignUniformRegisters(shader, field, fieldFullName, encoder);
+                defineUniform(shader, field, fieldFullName, encoder, uniformMap);
             }
 
             if (encoder)
@@ -1572,27 +1457,23 @@
         encoder ? encoder->encodeType(uniform.type, uniform.arraySize, false)
                 : sh::BlockMemberInfo::getDefaultBlockInfo();
 
-    D3DUniform *d3dUniform = getD3DUniformByName(fullName);
+    auto uniformMapEntry   = uniformMap->find(fullName);
+    D3DUniform *d3dUniform = nullptr;
 
-    if (!d3dUniform)
+    if (uniformMapEntry != uniformMap->end())
     {
-        // We're building the list twice, make sure we use the same indexing. Special case
-        // built-ins.
-        ASSERT(fullName.compare(0, 3, "gl_") == 0 ||
-               mData.getUniformIndex(fullName) == static_cast<GLint>(mD3DUniforms.size()));
-
+        d3dUniform = uniformMapEntry->second;
+    }
+    else
+    {
         d3dUniform = new D3DUniform(uniform.type, fullName, uniform.arraySize, true);
-        mD3DUniforms.push_back(d3dUniform);
-
-        if (encoder)
-        {
-            d3dUniform->registerElement =
-                static_cast<unsigned int>(sh::HLSLBlockEncoder::getBlockRegisterElement(blockInfo));
-        }
+        (*uniformMap)[fullName] = d3dUniform;
     }
 
     if (encoder)
     {
+        d3dUniform->registerElement =
+            static_cast<unsigned int>(sh::HLSLBlockEncoder::getBlockRegisterElement(blockInfo));
         unsigned int reg =
             static_cast<unsigned int>(sh::HLSLBlockEncoder::getBlockRegister(blockInfo));
         if (shader->getShaderType() == GL_FRAGMENT_SHADER)
@@ -1871,7 +1752,7 @@
         ASSERT(samplerIndex < outSamplers.size());
         Sampler *sampler            = &outSamplers[samplerIndex];
         sampler->active             = true;
-        sampler->textureType        = GetTextureType(samplerType);
+        sampler->textureType        = gl::SamplerTypeToTextureType(samplerType);
         sampler->logicalTextureUnit = 0;
         *outUsedRange               = std::max(samplerIndex + 1, *outUsedRange);
         samplerIndex++;
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/libANGLE/renderer/d3d/ProgramD3D.h
index 5e86516..2183961 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -12,7 +12,6 @@
 #include <string>
 #include <vector>
 
-#include "common/Optional.h"
 #include "compiler/translator/blocklayoutHLSL.h"
 #include "libANGLE/Constants.h"
 #include "libANGLE/formatutils.h"
@@ -33,7 +32,7 @@
 #endif
 
 // Helper struct representing a single shader uniform
-struct D3DUniform
+struct D3DUniform : angle::NonCopyable
 {
     D3DUniform(GLenum typeIn,
                const std::string &nameIn,
@@ -84,7 +83,6 @@
     GLenum getSamplerTextureType(gl::SamplerType type, unsigned int samplerIndex) const;
     GLint getUsedSamplerRange(gl::SamplerType type) const;
     void updateSamplerMapping();
-    bool validateSamplers(gl::InfoLog *infoLog, const gl::Caps &caps);
 
     bool usesPointSize() const { return mUsesPointSize; }
     bool usesPointSpriteEmulation() const;
@@ -198,14 +196,18 @@
         GLenum textureType;
     };
 
+    typedef std::map<std::string, D3DUniform *> D3DUniformMap;
     typedef std::map<std::string, sh::BlockMemberInfo> BlockInfoMap;
 
-    void assignUniformRegisters();
-    void assignUniformRegistersBase(const ShaderD3D *shader, const sh::Uniform &uniform);
-    void assignUniformRegisters(const ShaderD3D *shader,
-                                const sh::ShaderVariable &uniform,
-                                const std::string &fullName,
-                                sh::HLSLBlockEncoder *encoder);
+    void defineUniformsAndAssignRegisters();
+    void defineUniformBase(const ShaderD3D *shader,
+                           const sh::Uniform &uniform,
+                           D3DUniformMap *uniformMap);
+    void defineUniform(const ShaderD3D *shader,
+                       const sh::ShaderVariable &uniform,
+                       const std::string &fullName,
+                       sh::HLSLBlockEncoder *encoder,
+                       D3DUniformMap *uniformMap);
     void assignAllSamplerRegisters();
     void assignSamplerRegisters(const D3DUniform *d3dUniform);
 
@@ -262,9 +264,6 @@
     GLuint mUsedPixelSamplerRange;
     bool mDirtySamplerMapping;
 
-    // Cache for validateSamplers
-    std::vector<GLenum> mTextureUnitTypesCache;
-
     // Cache for getPixelExecutableForFramebuffer
     std::vector<GLenum> mPixelShaderOutputFormatCache;
 
@@ -275,8 +274,6 @@
 
     unsigned int mSerial;
 
-    Optional<bool> mCachedValidateSamplersResult;
-
     std::vector<GLint> mVertexUBOCache;
     std::vector<GLint> mFragmentUBOCache;
     VertexExecutable::Signature mCachedVertexSignature;
diff --git a/src/libANGLE/renderer/gl/ProgramGL.cpp b/src/libANGLE/renderer/gl/ProgramGL.cpp
index bae8389..bbb4540 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.cpp
+++ b/src/libANGLE/renderer/gl/ProgramGL.cpp
@@ -299,12 +299,6 @@
     mFunctions->uniformMatrix4x3fv(uniLoc(location), count, transpose, value);
 }
 
-bool ProgramGL::validateSamplers(gl::InfoLog *infoLog, const gl::Caps &caps)
-{
-    //UNIMPLEMENTED();
-    return true;
-}
-
 void ProgramGL::reset()
 {
     mUniformRealLocationMap.clear();
diff --git a/src/libANGLE/renderer/gl/ProgramGL.h b/src/libANGLE/renderer/gl/ProgramGL.h
index 9cc0a14..982d5bb 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.h
+++ b/src/libANGLE/renderer/gl/ProgramGL.h
@@ -62,8 +62,6 @@
     void setUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override;
     void setUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) override;
 
-    bool validateSamplers(gl::InfoLog *infoLog, const gl::Caps &caps) override;
-
     void gatherUniformBlockInfo(std::vector<gl::UniformBlock> *uniformBlocks,
                                 std::vector<gl::LinkedUniform> *uniforms) override;
 
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index e9f871d..acb511f 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -703,8 +703,366 @@
     ASSERT_GL_NO_ERROR();
 }
 
+class TextureLimitsTest : public ANGLETest
+{
+  protected:
+    struct RGBA8
+    {
+        uint8_t R, G, B, A;
+    };
+
+    TextureLimitsTest()
+        : mProgram(0), mMaxVertexTextures(0), mMaxFragmentTextures(0), mMaxCombinedTextures(0)
+    {
+        setWindowWidth(128);
+        setWindowHeight(128);
+        setConfigRedBits(8);
+        setConfigGreenBits(8);
+        setConfigBlueBits(8);
+        setConfigAlphaBits(8);
+    }
+
+    ~TextureLimitsTest()
+    {
+        if (mProgram != 0)
+        {
+            glDeleteProgram(mProgram);
+            mProgram = 0;
+
+            if (!mTextures.empty())
+            {
+                glDeleteTextures(static_cast<GLsizei>(mTextures.size()), &mTextures[0]);
+            }
+        }
+    }
+
+    void SetUp() override
+    {
+        ANGLETest::SetUp();
+
+        glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mMaxVertexTextures);
+        glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxFragmentTextures);
+        glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mMaxCombinedTextures);
+
+        ASSERT_GL_NO_ERROR();
+    }
+
+    void compileProgramWithTextureCounts(const std::string &vertexPrefix,
+                                         GLint vertexTextureCount,
+                                         GLint vertexActiveTextureCount,
+                                         const std::string &fragPrefix,
+                                         GLint fragmentTextureCount,
+                                         GLint fragmentActiveTextureCount)
+    {
+        std::stringstream vertexShaderStr;
+        vertexShaderStr << "attribute vec2 position;\n"
+                        << "varying vec4 color;\n"
+                        << "varying vec2 texCoord;\n";
+
+        for (GLint textureIndex = 0; textureIndex < vertexTextureCount; ++textureIndex)
+        {
+            vertexShaderStr << "uniform sampler2D " << vertexPrefix << textureIndex << ";\n";
+        }
+
+        vertexShaderStr << "void main() {\n"
+                        << "  gl_Position = vec4(position, 0, 1);\n"
+                        << "  texCoord = (position * 0.5) + 0.5;\n"
+                        << "  color = vec4(0);\n";
+
+        for (GLint textureIndex = 0; textureIndex < vertexActiveTextureCount; ++textureIndex)
+        {
+            vertexShaderStr << "  color += texture2D(" << vertexPrefix << textureIndex
+                            << ", texCoord);\n";
+        }
+
+        vertexShaderStr << "}";
+
+        std::stringstream fragmentShaderStr;
+        fragmentShaderStr << "varying mediump vec4 color;\n"
+                          << "varying mediump vec2 texCoord;\n";
+
+        for (GLint textureIndex = 0; textureIndex < fragmentTextureCount; ++textureIndex)
+        {
+            fragmentShaderStr << "uniform sampler2D " << fragPrefix << textureIndex << ";\n";
+        }
+
+        fragmentShaderStr << "void main() {\n"
+                          << "  gl_FragColor = color;\n";
+
+        for (GLint textureIndex = 0; textureIndex < fragmentActiveTextureCount; ++textureIndex)
+        {
+            fragmentShaderStr << "  gl_FragColor += texture2D(" << fragPrefix << textureIndex
+                              << ", texCoord);\n";
+        }
+
+        fragmentShaderStr << "}";
+
+        const std::string &vertexShaderSource   = vertexShaderStr.str();
+        const std::string &fragmentShaderSource = fragmentShaderStr.str();
+
+        mProgram = CompileProgram(vertexShaderSource, fragmentShaderSource);
+    }
+
+    RGBA8 getPixel(GLint texIndex)
+    {
+        RGBA8 pixel = {static_cast<uint8_t>(texIndex & 0x7u), static_cast<uint8_t>(texIndex >> 3),
+                       0, 255u};
+        return pixel;
+    }
+
+    void initTextures(GLint tex2DCount, GLint texCubeCount)
+    {
+        GLint totalCount = tex2DCount + texCubeCount;
+        mTextures.assign(totalCount, 0);
+        glGenTextures(totalCount, &mTextures[0]);
+        ASSERT_GL_NO_ERROR();
+
+        std::vector<RGBA8> texData(16 * 16);
+
+        GLint texIndex = 0;
+        for (; texIndex < tex2DCount; ++texIndex)
+        {
+            texData.assign(texData.size(), getPixel(texIndex));
+            glActiveTexture(GL_TEXTURE0 + texIndex);
+            glBindTexture(GL_TEXTURE_2D, mTextures[texIndex]);
+            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                         &texData[0]);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        }
+
+        ASSERT_GL_NO_ERROR();
+
+        for (; texIndex < texCubeCount; ++texIndex)
+        {
+            texData.assign(texData.size(), getPixel(texIndex));
+            glActiveTexture(GL_TEXTURE0 + texIndex);
+            glBindTexture(GL_TEXTURE_CUBE_MAP, mTextures[texIndex]);
+            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+                         GL_UNSIGNED_BYTE, &texData[0]);
+            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+                         GL_UNSIGNED_BYTE, &texData[0]);
+            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+                         GL_UNSIGNED_BYTE, &texData[0]);
+            glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+                         GL_UNSIGNED_BYTE, &texData[0]);
+            glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+                         GL_UNSIGNED_BYTE, &texData[0]);
+            glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
+                         GL_UNSIGNED_BYTE, &texData[0]);
+            glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+            glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+            glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+            glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        }
+
+        ASSERT_GL_NO_ERROR();
+    }
+
+    void testWithTextures(GLint vertexTextureCount,
+                          const std::string &vertexTexturePrefix,
+                          GLint fragmentTextureCount,
+                          const std::string &fragmentTexturePrefix)
+    {
+        // Generate textures
+        initTextures(vertexTextureCount + fragmentTextureCount, 0);
+
+        glUseProgram(mProgram);
+        RGBA8 expectedSum = {0};
+        for (GLint texIndex = 0; texIndex < vertexTextureCount; ++texIndex)
+        {
+            std::stringstream uniformNameStr;
+            uniformNameStr << vertexTexturePrefix << texIndex;
+            const std::string &uniformName = uniformNameStr.str();
+            GLint location = glGetUniformLocation(mProgram, uniformName.c_str());
+            ASSERT_NE(-1, location);
+
+            glUniform1i(location, texIndex);
+            RGBA8 contribution = getPixel(texIndex);
+            expectedSum.R += contribution.R;
+            expectedSum.G += contribution.G;
+        }
+
+        for (GLint texIndex = 0; texIndex < fragmentTextureCount; ++texIndex)
+        {
+            std::stringstream uniformNameStr;
+            uniformNameStr << fragmentTexturePrefix << texIndex;
+            const std::string &uniformName = uniformNameStr.str();
+            GLint location = glGetUniformLocation(mProgram, uniformName.c_str());
+            ASSERT_NE(-1, location);
+
+            glUniform1i(location, texIndex + vertexTextureCount);
+            RGBA8 contribution = getPixel(texIndex + vertexTextureCount);
+            expectedSum.R += contribution.R;
+            expectedSum.G += contribution.G;
+        }
+
+        ASSERT_GE(256u, expectedSum.G);
+
+        drawQuad(mProgram, "position", 0.5f);
+        ASSERT_GL_NO_ERROR();
+        EXPECT_PIXEL_EQ(0, 0, expectedSum.R, expectedSum.G, 0, 255);
+    }
+
+    GLuint mProgram;
+    std::vector<GLuint> mTextures;
+    GLint mMaxVertexTextures;
+    GLint mMaxFragmentTextures;
+    GLint mMaxCombinedTextures;
+};
+
+// Test rendering with the maximum vertex texture units.
+TEST_P(TextureLimitsTest, MaxVertexTextures)
+{
+    compileProgramWithTextureCounts("tex", mMaxVertexTextures, mMaxVertexTextures, "tex", 0, 0);
+    ASSERT_NE(0u, mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    testWithTextures(mMaxVertexTextures, "tex", 0, "tex");
+}
+
+// Test rendering with the maximum fragment texture units.
+TEST_P(TextureLimitsTest, MaxFragmentTextures)
+{
+    compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures, mMaxFragmentTextures);
+    ASSERT_NE(0u, mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    testWithTextures(mMaxFragmentTextures, "tex", 0, "tex");
+}
+
+// Test rendering with maximum combined texture units.
+TEST_P(TextureLimitsTest, MaxCombinedTextures)
+{
+    GLint vertexTextures = mMaxVertexTextures;
+
+    if (vertexTextures + mMaxFragmentTextures > mMaxCombinedTextures)
+    {
+        vertexTextures = mMaxCombinedTextures - mMaxFragmentTextures;
+    }
+
+    compileProgramWithTextureCounts("vtex", vertexTextures, vertexTextures, "ftex",
+                                    mMaxFragmentTextures, mMaxFragmentTextures);
+    ASSERT_NE(0u, mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    testWithTextures(vertexTextures, "vtex", mMaxFragmentTextures, "ftex");
+}
+
+// Negative test for exceeding the number of vertex textures
+TEST_P(TextureLimitsTest, ExcessiveVertexTextures)
+{
+    compileProgramWithTextureCounts("tex", mMaxVertexTextures + 1, mMaxVertexTextures + 1, "tex", 0,
+                                    0);
+    ASSERT_EQ(0u, mProgram);
+}
+
+// Negative test for exceeding the number of fragment textures
+TEST_P(TextureLimitsTest, ExcessiveFragmentTextures)
+{
+    compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures + 1,
+                                    mMaxFragmentTextures + 1);
+    ASSERT_EQ(0u, mProgram);
+}
+
+// Test active vertex textures under the limit, but excessive textures specified.
+TEST_P(TextureLimitsTest, MaxActiveVertexTextures)
+{
+    compileProgramWithTextureCounts("tex", mMaxVertexTextures + 4, mMaxVertexTextures, "tex", 0, 0);
+    ASSERT_NE(0u, mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    testWithTextures(mMaxVertexTextures, "tex", 0, "tex");
+}
+
+// Test active fragment textures under the limit, but excessive textures specified.
+TEST_P(TextureLimitsTest, MaxActiveFragmentTextures)
+{
+    compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures + 4,
+                                    mMaxFragmentTextures);
+    ASSERT_NE(0u, mProgram);
+    ASSERT_GL_NO_ERROR();
+
+    testWithTextures(0, "tex", mMaxFragmentTextures, "tex");
+}
+
+// Negative test for pointing two sampler uniforms of different types to the same texture.
+TEST_P(TextureLimitsTest, TextureTypeConflict)
+{
+    const std::string &vertexShader =
+        "attribute vec2 position;\n"
+        "varying float color;\n"
+        "uniform sampler2D tex2D;\n"
+        "uniform samplerCube texCube;\n"
+        "void main() {\n"
+        "  gl_Position = vec4(position, 0, 1);\n"
+        "  vec2 texCoord = (position * 0.5) + 0.5;\n"
+        "  color = texture2D(tex2D, texCoord).x;\n"
+        "  color += textureCube(texCube, vec3(texCoord, 0)).x;\n"
+        "}";
+    const std::string &fragmentShader =
+        "varying mediump float color;\n"
+        "void main() {\n"
+        "  gl_FragColor = vec4(color, 0, 0, 1);\n"
+        "}";
+
+    mProgram = CompileProgram(vertexShader, fragmentShader);
+    ASSERT_NE(0u, mProgram);
+
+    initTextures(1, 0);
+
+    glUseProgram(mProgram);
+    GLint tex2DLocation = glGetUniformLocation(mProgram, "tex2D");
+    ASSERT_NE(-1, tex2DLocation);
+    GLint texCubeLocation = glGetUniformLocation(mProgram, "texCube");
+    ASSERT_NE(-1, texCubeLocation);
+
+    glUniform1i(tex2DLocation, 0);
+    glUniform1i(texCubeLocation, 0);
+    ASSERT_GL_NO_ERROR();
+
+    drawQuad(mProgram, "position", 0.5f);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
+// Negative test for rendering with texture outside the valid range.
+// TODO(jmadill): Research if this is correct.
+TEST_P(TextureLimitsTest, DrawWithTexturePastMaximum)
+{
+    const std::string &vertexShader =
+        "attribute vec2 position;\n"
+        "varying float color;\n"
+        "uniform sampler2D tex2D;\n"
+        "void main() {\n"
+        "  gl_Position = vec4(position, 0, 1);\n"
+        "  vec2 texCoord = (position * 0.5) + 0.5;\n"
+        "  color = texture2D(tex2D, texCoord).x;\n"
+        "}";
+    const std::string &fragmentShader =
+        "varying mediump float color;\n"
+        "void main() {\n"
+        "  gl_FragColor = vec4(color, 0, 0, 1);\n"
+        "}";
+
+    mProgram = CompileProgram(vertexShader, fragmentShader);
+    ASSERT_NE(0u, mProgram);
+
+    glUseProgram(mProgram);
+    GLint tex2DLocation = glGetUniformLocation(mProgram, "tex2D");
+    ASSERT_NE(-1, tex2DLocation);
+
+    glUniform1i(tex2DLocation, mMaxCombinedTextures);
+    ASSERT_GL_NO_ERROR();
+
+    drawQuad(mProgram, "position", 0.5f);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
 ANGLE_INSTANTIATE_TEST(TextureTest, ES2_D3D9(), ES2_D3D11(), ES2_D3D11_FL9_3()); // TODO(geofflang): Figure out why this test fails on Intel OpenGL
 ANGLE_INSTANTIATE_TEST(TextureTestES3, ES3_D3D11(), ES3_OPENGL());
+ANGLE_INSTANTIATE_TEST(TextureLimitsTest, ES2_D3D11(), ES2_OPENGL());
 
 } // namespace
diff --git a/src/tests/gl_tests/UniformTest.cpp b/src/tests/gl_tests/UniformTest.cpp
index e1db43f..0cd3684 100644
--- a/src/tests/gl_tests/UniformTest.cpp
+++ b/src/tests/gl_tests/UniformTest.cpp
@@ -452,6 +452,46 @@
     }
 }
 
+// Check that sampler uniforms only show up one time in the list
+TEST_P(UniformTest, SamplerUniformsAppearOnce)
+{
+    const std::string &vertShader =
+        "attribute vec2 position;\n"
+        "uniform sampler2D tex2D;\n"
+        "varying vec4 color;\n"
+        "void main() {\n"
+        "  gl_Position = vec4(position, 0, 1);\n"
+        "  color = texture2D(tex2D, vec2(0));\n"
+        "}";
+
+    const std::string &fragShader =
+        "precision mediump float;\n"
+        "varying vec4 color;\n"
+        "uniform sampler2D tex2D;\n"
+        "void main() {\n"
+        "  gl_FragColor = texture2D(tex2D, vec2(0)) + color;\n"
+        "}";
+
+    GLuint program = CompileProgram(vertShader, fragShader);
+    ASSERT_NE(0u, program);
+
+    GLint activeUniformsCount = 0;
+    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniformsCount);
+    ASSERT_EQ(1, activeUniformsCount);
+
+    GLint size       = 0;
+    GLenum type      = GL_NONE;
+    GLchar name[120] = {0};
+    glGetActiveUniform(program, 0, 100, nullptr, &size, &type, name);
+    EXPECT_EQ(1, size);
+    EXPECT_EQ(GL_SAMPLER_2D, type);
+    EXPECT_STREQ("tex2D", name);
+
+    EXPECT_GL_NO_ERROR();
+
+    glDeleteProgram(program);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
 ANGLE_INSTANTIATE_TEST(UniformTest, ES2_D3D9(), ES2_D3D11(), ES2_OPENGL());
 ANGLE_INSTANTIATE_TEST(UniformTestES3, ES3_D3D11(), ES3_OPENGL());