ES31: Add array element transformfeedback

This enhances the PackedVarying to be either one element of array, or
whole array. Correspondingly the VaryingPacking is expanded to pack it
properly.

BUG=angleproject:1950

Change-Id: I0529d7ac4367d42b2b433410fbf08351412aada8
Reviewed-on: https://chromium-review.googlesource.com/459115
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index ebedc4b..037283b 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -129,7 +129,33 @@
 // true if varying x has a higher priority in packing than y
 bool ComparePackedVarying(const PackedVarying &x, const PackedVarying &y)
 {
-    return gl::CompareShaderVar(*x.varying, *y.varying);
+    // If the PackedVarying 'x' or 'y' to be compared is an array element, this clones an equivalent
+    // non-array shader variable 'vx' or 'vy' for actual comparison instead.
+    sh::ShaderVariable vx, vy;
+    const sh::ShaderVariable *px, *py;
+    if (x.isArrayElement())
+    {
+        vx           = *x.varying;
+        vx.arraySize = 0;
+        px           = &vx;
+    }
+    else
+    {
+        px = x.varying;
+    }
+
+    if (y.isArrayElement())
+    {
+        vy           = *y.varying;
+        vy.arraySize = 0;
+        py           = &vy;
+    }
+    else
+    {
+        py = y.varying;
+    }
+
+    return gl::CompareShaderVar(*px, *py);
 }
 
 template <typename VarT>
@@ -171,6 +197,23 @@
     }
 }
 
+bool IncludeSameArrayElement(const std::set<std::string> &nameSet, const std::string &name)
+{
+    size_t subscript     = GL_INVALID_INDEX;
+    std::string baseName = ParseResourceName(name, &subscript);
+    for (auto it = nameSet.begin(); it != nameSet.end(); ++it)
+    {
+        size_t arrayIndex     = GL_INVALID_INDEX;
+        std::string arrayName = ParseResourceName(*it, &arrayIndex);
+        if (baseName == arrayName && (subscript == GL_INVALID_INDEX ||
+                                      arrayIndex == GL_INVALID_INDEX || subscript == arrayIndex))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
 }  // anonymous namespace
 
 const char *const g_fakepath = "C:\\fakepath";
@@ -692,7 +735,7 @@
 
         const auto &mergedVaryings = getMergedVaryings();
 
-        if (!linkValidateTransformFeedback(mInfoLog, mergedVaryings, caps))
+        if (!linkValidateTransformFeedback(context, mInfoLog, mergedVaryings, caps))
         {
             return NoError();
         }
@@ -735,7 +778,7 @@
 {
     mState.mAttributes.clear();
     mState.mActiveAttribLocationsMask.reset();
-    mState.mTransformFeedbackVaryingVars.clear();
+    mState.mLinkedTransformFeedbackVaryings.clear();
     mState.mUniforms.clear();
     mState.mUniformLocations.clear();
     mState.mUniformBlocks.clear();
@@ -871,7 +914,7 @@
     }
 
     unsigned int transformFeedbackVaryingCount = stream.readInt<unsigned int>();
-    ASSERT(mState.mTransformFeedbackVaryingVars.empty());
+    ASSERT(mState.mLinkedTransformFeedbackVaryings.empty());
     for (unsigned int transformFeedbackVaryingIndex = 0;
         transformFeedbackVaryingIndex < transformFeedbackVaryingCount;
         ++transformFeedbackVaryingIndex)
@@ -881,7 +924,9 @@
         stream.readInt(&varying.type);
         stream.readString(&varying.name);
 
-        mState.mTransformFeedbackVaryingVars.push_back(varying);
+        GLuint arrayIndex = stream.readInt<GLuint>();
+
+        mState.mLinkedTransformFeedbackVaryings.emplace_back(varying, arrayIndex);
     }
 
     stream.readInt(&mState.mTransformFeedbackBufferMode);
@@ -1011,12 +1056,14 @@
         stream.writeInt(binding);
     }
 
-    stream.writeInt(mState.mTransformFeedbackVaryingVars.size());
-    for (const sh::Varying &varying : mState.mTransformFeedbackVaryingVars)
+    stream.writeInt(mState.mLinkedTransformFeedbackVaryings.size());
+    for (const auto &var : mState.mLinkedTransformFeedbackVaryings)
     {
-        stream.writeInt(varying.arraySize);
-        stream.writeInt(varying.type);
-        stream.writeString(varying.name);
+        stream.writeInt(var.arraySize);
+        stream.writeInt(var.type);
+        stream.writeString(var.name);
+
+        stream.writeIntOrNegOne(var.arrayIndex);
     }
 
     stream.writeInt(mState.mTransformFeedbackBufferMode);
@@ -1840,24 +1887,25 @@
 {
     if (mLinked)
     {
-        ASSERT(index < mState.mTransformFeedbackVaryingVars.size());
-        const sh::Varying &varying = mState.mTransformFeedbackVaryingVars[index];
-        GLsizei lastNameIdx = std::min(bufSize - 1, static_cast<GLsizei>(varying.name.length()));
+        ASSERT(index < mState.mLinkedTransformFeedbackVaryings.size());
+        const auto &var     = mState.mLinkedTransformFeedbackVaryings[index];
+        std::string varName = var.nameWithArrayIndex();
+        GLsizei lastNameIdx = std::min(bufSize - 1, static_cast<GLsizei>(varName.length()));
         if (length)
         {
             *length = lastNameIdx;
         }
         if (size)
         {
-            *size = varying.elementCount();
+            *size = var.size();
         }
         if (type)
         {
-            *type = varying.type;
+            *type = var.type;
         }
         if (name)
         {
-            memcpy(name, varying.name.c_str(), lastNameIdx);
+            memcpy(name, varName.c_str(), lastNameIdx);
             name[lastNameIdx] = '\0';
         }
     }
@@ -1867,7 +1915,7 @@
 {
     if (mLinked)
     {
-        return static_cast<GLsizei>(mState.mTransformFeedbackVaryingVars.size());
+        return static_cast<GLsizei>(mState.mLinkedTransformFeedbackVaryings.size());
     }
     else
     {
@@ -1880,9 +1928,10 @@
     if (mLinked)
     {
         GLsizei maxSize = 0;
-        for (const sh::Varying &varying : mState.mTransformFeedbackVaryingVars)
+        for (const auto &var : mState.mLinkedTransformFeedbackVaryings)
         {
-            maxSize = std::max(maxSize, static_cast<GLsizei>(varying.name.length() + 1));
+            maxSize =
+                std::max(maxSize, static_cast<GLsizei>(var.nameWithArrayIndex().length() + 1));
         }
 
         return maxSize;
@@ -2427,7 +2476,8 @@
     return true;
 }
 
-bool Program::linkValidateTransformFeedback(InfoLog &infoLog,
+bool Program::linkValidateTransformFeedback(const gl::Context *context,
+                                            InfoLog &infoLog,
                                             const Program::MergedVaryings &varyings,
                                             const Caps &caps) const
 {
@@ -2438,11 +2488,14 @@
     for (const std::string &tfVaryingName : mState.mTransformFeedbackVaryingNames)
     {
         bool found = false;
+        size_t subscript     = GL_INVALID_INDEX;
+        std::string baseName = ParseResourceName(tfVaryingName, &subscript);
+
         for (const auto &ref : varyings)
         {
             const sh::Varying *varying = ref.second.get();
 
-            if (tfVaryingName == varying->name)
+            if (baseName == varying->name)
             {
                 if (uniqueNames.count(tfVaryingName) > 0)
                 {
@@ -2450,16 +2503,29 @@
                             << tfVaryingName << ").";
                     return false;
                 }
-                uniqueNames.insert(tfVaryingName);
-
-                if (varying->isArray())
+                if (context->getClientVersion() >= Version(3, 1))
+                {
+                    if (IncludeSameArrayElement(uniqueNames, tfVaryingName))
+                    {
+                        infoLog
+                            << "Two transform feedback varyings include the same array element ("
+                            << tfVaryingName << ").";
+                        return false;
+                    }
+                }
+                else if (varying->isArray())
                 {
                     infoLog << "Capture of arrays is undefined and not supported.";
                     return false;
                 }
 
+                uniqueNames.insert(tfVaryingName);
+
                 // TODO(jmadill): Investigate implementation limits on D3D11
-                size_t componentCount = VariableComponentCount(varying->type);
+                size_t elementCount =
+                    ((varying->isArray() && subscript == GL_INVALID_INDEX) ? varying->elementCount()
+                                                                           : 1);
+                size_t componentCount = VariableComponentCount(varying->type) * elementCount;
                 if (mState.mTransformFeedbackBufferMode == GL_SEPARATE_ATTRIBS &&
                     componentCount > caps.maxTransformFeedbackSeparateComponents)
                 {
@@ -2474,13 +2540,12 @@
                 break;
             }
         }
-
-        if (tfVaryingName.find('[') != std::string::npos)
+        if (context->getClientVersion() < Version(3, 1) &&
+            tfVaryingName.find('[') != std::string::npos)
         {
             infoLog << "Capture of array elements is undefined and not supported.";
             return false;
         }
-
         // All transform feedback varyings are expected to exist since packVaryings checks for them.
         ASSERT(found);
     }
@@ -2500,15 +2565,18 @@
 void Program::gatherTransformFeedbackVaryings(const Program::MergedVaryings &varyings)
 {
     // Gather the linked varyings that are used for transform feedback, they should all exist.
-    mState.mTransformFeedbackVaryingVars.clear();
+    mState.mLinkedTransformFeedbackVaryings.clear();
     for (const std::string &tfVaryingName : mState.mTransformFeedbackVaryingNames)
     {
+        size_t subscript     = GL_INVALID_INDEX;
+        std::string baseName = ParseResourceName(tfVaryingName, &subscript);
         for (const auto &ref : varyings)
         {
             const sh::Varying *varying = ref.second.get();
-            if (tfVaryingName == varying->name)
+            if (baseName == varying->name)
             {
-                mState.mTransformFeedbackVaryingVars.push_back(*varying);
+                mState.mLinkedTransformFeedbackVaryings.emplace_back(
+                    *varying, static_cast<GLuint>(subscript));
                 break;
             }
         }
@@ -2537,6 +2605,7 @@
 {
     const std::vector<std::string> &tfVaryings = mState.getTransformFeedbackVaryingNames();
     std::vector<PackedVarying> packedVaryings;
+    std::set<std::string> uniqueFullNames;
 
     for (const auto &ref : mergedVaryings)
     {
@@ -2574,7 +2643,13 @@
 
         for (const std::string &tfVarying : tfVaryings)
         {
-            if (tfVarying == input->name)
+            size_t subscript     = GL_INVALID_INDEX;
+            std::string baseName = ParseResourceName(tfVarying, &subscript);
+            if (uniqueFullNames.count(tfVarying) > 0)
+            {
+                continue;
+            }
+            if (baseName == input->name)
             {
                 // Transform feedback for varying structs is underspecified.
                 // See Khronos bug 9856.
@@ -2583,8 +2658,13 @@
                 {
                     packedVaryings.push_back(PackedVarying(*input, input->interpolation));
                     packedVaryings.back().vertexOnly = true;
+                    packedVaryings.back().arrayIndex = static_cast<GLuint>(subscript);
+                    uniqueFullNames.insert(tfVarying);
                 }
-                break;
+                if (subscript == GL_INVALID_INDEX)
+                {
+                    break;
+                }
             }
         }
     }