Program: Add cache samplers and type masks.

This mask cleans up some of the iteration logic in
State::syncProgramTextures. It will make it easier to optimize this
function in the future. This will also make it easier to recompute
the sampler type validation.

Leads to a 5% improvement in State::syncProgramTextures.

Bug: angleproject:2747
Bug: angleproject:2763
Change-Id: Ic9a555df843ad23b4c562e6e4a2d425bee58a856
Reviewed-on: https://chromium-review.googlesource.com/1164306
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index 15cd8bc..0a2ebe1 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -794,9 +794,11 @@
       mGeometryShaderInputPrimitiveType(PrimitiveMode::Triangles),
       mGeometryShaderOutputPrimitiveType(PrimitiveMode::TriangleStrip),
       mGeometryShaderInvocations(1),
-      mGeometryShaderMaxVertices(0)
+      mGeometryShaderMaxVertices(0),
+      mActiveSamplerRefCounts{}
 {
     mComputeShaderLocalSize.fill(1);
+    mActiveSamplerTypes.fill(TextureType::InvalidEnum);
 }
 
 ProgramState::~ProgramState()
@@ -1226,8 +1228,6 @@
 
     initInterfaceBlockBindings();
 
-    setUniformValuesFromBindingQualifiers();
-
     // According to GLES 3.0/3.1 spec for LinkProgram and UseProgram,
     // Only successfully linked program can replace the executables.
     ASSERT(mLinked);
@@ -1236,6 +1236,11 @@
     // Mark implementation-specific unreferenced uniforms as ignored.
     mProgram->markUnusedUniformLocations(&mState.mUniformLocations, &mState.mSamplerBindings);
 
+    // Must be called after markUnusedUniformLocations.
+    mState.updateActiveSamplers();
+
+    setUniformValuesFromBindingQualifiers();
+
     // Save to the program cache.
     if (cache && (mState.mLinkedTransformFeedbackVaryings.empty() ||
                   !context->getWorkarounds().disableProgramCachingForTransformFeedback))
@@ -1287,6 +1292,22 @@
     }
 }
 
+void ProgramState::updateActiveSamplers()
+{
+    for (SamplerBinding &samplerBinding : mSamplerBindings)
+    {
+        if (samplerBinding.unreferenced)
+            continue;
+
+        for (GLint textureUnit : samplerBinding.boundTextureUnits)
+        {
+            mActiveSamplerRefCounts[textureUnit]++;
+            mActiveSamplerTypes[textureUnit] = getSamplerUniformTextureType(textureUnit);
+            mActiveSamplersMask.set(textureUnit);
+        }
+    }
+}
+
 // Returns the program object to an unlinked state, before re-linking, or at destruction
 void Program::unlink()
 {
@@ -2046,60 +2067,22 @@
 
 bool Program::validateSamplersImpl(InfoLog *infoLog, const Caps &caps)
 {
-    if (mTextureUnitTypesCache.empty())
-    {
-        mTextureUnitTypesCache.resize(caps.maxCombinedTextureImageUnits, TextureType::InvalidEnum);
-    }
-    else
-    {
-        std::fill(mTextureUnitTypesCache.begin(), mTextureUnitTypesCache.end(),
-                  TextureType::InvalidEnum);
-    }
-
     // 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 (const auto &samplerBinding : mState.mSamplerBindings)
+    for (size_t textureUnit : mState.mActiveSamplersMask)
     {
-        if (samplerBinding.unreferenced)
-            continue;
-
-        TextureType textureType = samplerBinding.textureType;
-
-        for (GLuint textureUnit : samplerBinding.boundTextureUnits)
+        if (mState.mActiveSamplerTypes[textureUnit] == TextureType::InvalidEnum)
         {
-            if (textureUnit >= caps.maxCombinedTextureImageUnits)
+            if (infoLog)
             {
-                if (infoLog)
-                {
-                    (*infoLog) << "Sampler uniform (" << textureUnit
-                               << ") exceeds GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS ("
-                               << caps.maxCombinedTextureImageUnits << ")";
-                }
-
-                mCachedValidateSamplersResult = false;
-                return false;
+                (*infoLog) << "Samplers of conflicting types refer to the same texture "
+                              "image unit ("
+                           << textureUnit << ").";
             }
 
-            if (mTextureUnitTypesCache[textureUnit] != TextureType::InvalidEnum)
-            {
-                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 = false;
+            return false;
         }
     }
 
@@ -2651,8 +2634,8 @@
     {
         const auto &samplerUniform = mState.mUniforms[samplerIndex];
         TextureType textureType    = SamplerTypeToTextureType(samplerUniform.type);
-        mState.mSamplerBindings.emplace_back(
-            SamplerBinding(textureType, samplerUniform.getBasicTypeElementCount(), false));
+        unsigned int elementCount  = samplerUniform.getBasicTypeElementCount();
+        mState.mSamplerBindings.emplace_back(textureType, elementCount, false);
     }
 }
 
@@ -3469,15 +3452,93 @@
 {
     ASSERT(mState.isSamplerUniformIndex(locationInfo.index));
     GLuint samplerIndex = mState.getSamplerIndexFromUniformIndex(locationInfo.index);
-    std::vector<GLuint> *boundTextureUnits =
-        &mState.mSamplerBindings[samplerIndex].boundTextureUnits;
+    SamplerBinding &samplerBinding = mState.mSamplerBindings[samplerIndex];
+    std::vector<GLuint> &boundTextureUnits = samplerBinding.boundTextureUnits;
 
-    std::copy(v, v + clampedCount, boundTextureUnits->begin() + locationInfo.arrayIndex);
+    if (samplerBinding.unreferenced)
+        return;
+
+    // Update the sampler uniforms.
+    for (GLsizei arrayIndex = 0; arrayIndex < clampedCount; ++arrayIndex)
+    {
+        GLint oldSamplerIndex = boundTextureUnits[arrayIndex + locationInfo.arrayIndex];
+        GLint newSamplerIndex = v[arrayIndex];
+
+        if (oldSamplerIndex == newSamplerIndex)
+            continue;
+
+        boundTextureUnits[arrayIndex + locationInfo.arrayIndex] = newSamplerIndex;
+
+        // Update the reference counts.
+        uint32_t &oldRefCount = mState.mActiveSamplerRefCounts[oldSamplerIndex];
+        uint32_t &newRefCount = mState.mActiveSamplerRefCounts[newSamplerIndex];
+        ASSERT(oldRefCount > 0);
+        ASSERT(newRefCount < std::numeric_limits<uint32_t>::max());
+        oldRefCount--;
+        newRefCount++;
+
+        // Check for binding type change.
+        TextureType &newSamplerType = mState.mActiveSamplerTypes[newSamplerIndex];
+        TextureType &oldSamplerType = mState.mActiveSamplerTypes[oldSamplerIndex];
+
+        if (newRefCount == 1)
+        {
+            newSamplerType = samplerBinding.textureType;
+            mState.mActiveSamplersMask.set(newSamplerIndex);
+        }
+        else if (newSamplerType != samplerBinding.textureType)
+        {
+            // Conflict detected. Ensure we reset it properly.
+            newSamplerType = TextureType::InvalidEnum;
+        }
+
+        // Unset previously active sampler.
+        if (oldRefCount == 0)
+        {
+            oldSamplerType = TextureType::InvalidEnum;
+            mState.mActiveSamplersMask.reset(oldSamplerIndex);
+        }
+        else if (oldSamplerType == TextureType::InvalidEnum)
+        {
+            // Previous conflict. Check if this new change fixed the conflict.
+            oldSamplerType = mState.getSamplerUniformTextureType(oldSamplerIndex);
+        }
+    }
 
     // Invalidate the validation cache.
     mCachedValidateSamplersResult.reset();
 }
 
+TextureType ProgramState::getSamplerUniformTextureType(size_t textureUnitIndex) const
+{
+    TextureType foundType = TextureType::InvalidEnum;
+
+    for (const SamplerBinding &binding : mSamplerBindings)
+    {
+        if (binding.unreferenced)
+            continue;
+
+        // A conflict exists if samplers of different types are sourced by the same texture unit.
+        // We need to check all bound textures to detect this error case.
+        for (GLuint textureUnit : binding.boundTextureUnits)
+        {
+            if (textureUnit == textureUnitIndex)
+            {
+                if (foundType == TextureType::InvalidEnum)
+                {
+                    foundType = binding.textureType;
+                }
+                else if (foundType != binding.textureType)
+                {
+                    return TextureType::InvalidEnum;
+                }
+            }
+        }
+    }
+
+    return foundType;
+}
+
 template <typename T>
 GLsizei Program::clampUniformCount(const VariableLocation &locationInfo,
                                    GLsizei count,