[Impeller] Support uniform struct arrays (#36194)

diff --git a/impeller/compiler/code_gen_template.h b/impeller/compiler/code_gen_template.h
index 56a14f7..becce0a 100644
--- a/impeller/compiler/code_gen_template.h
+++ b/impeller/compiler/code_gen_template.h
@@ -50,7 +50,7 @@
 {% for def in struct_definitions %}
   struct {{def.name}} {
 {% for member in def.members %}
-    {{member.type}} {{member.name}}; // (offset {{member.offset}}, size {{member.byte_length}})
+{% if member.element_padding > 0 %}Padded<{{member.type}}, {{member.element_padding}}>{% else %}{{member.type}}{% endif %} {{" " + member.name}}{% if member.array_elements > 1 %}[{{member.array_elements}}]{% endif %}; // (offset {{member.offset}}, size {{member.byte_length}})
 {% endfor %}
   }; // struct {{def.name}} (size {{def.byte_length}})
 {% endfor %}
@@ -205,10 +205,12 @@
   std::vector<ShaderStructMemberMetadata> {
     {% for member in buffer.type.members %}
       ShaderStructMemberMetadata {
-        {{ member.base_type }}, // type
-        "{{ member.name }}",    // name
-        {{ member.offset }},    // offset
-        {{ member.size }},      // size
+        {{ member.base_type }},      // type
+        "{{ member.name }}",         // name
+        {{ member.offset }},         // offset
+        {{ member.size }},           // size
+        {{ member.byte_length }},    // byte_length
+        {{ member.array_elements }}, // array_elements
       },
     {% endfor %}
   } // members
diff --git a/impeller/compiler/reflector.cc b/impeller/compiler/reflector.cc
index b6fc7b5..79a847a 100644
--- a/impeller/compiler/reflector.cc
+++ b/impeller/compiler/reflector.cc
@@ -474,7 +474,9 @@
       member["type"] = struct_member.type;
       member["base_type"] = struct_member.base_type;
       member["offset"] = struct_member.offset;
-      member["size"] = struct_member.byte_length;
+      member["size"] = struct_member.size;
+      member["byte_length"] = struct_member.byte_length;
+      member["array_elements"] = struct_member.array_elements;
       members.emplace_back(std::move(member));
     }
   }
@@ -571,6 +573,7 @@
     const auto& member = compiler_->get_type(struct_type.member_types[i]);
     const auto struct_member_offset =
         compiler_->type_struct_member_offset(struct_type, i);
+    auto array_elements = std::max(1u, GetArrayElements(member));
 
     if (struct_member_offset > current_byte_offset) {
       const auto alignment_pad = struct_member_offset - current_byte_offset;
@@ -580,7 +583,10 @@
           SPrintF("_PADDING_%s_",
                   GetMemberNameAtIndex(struct_type, i).c_str()),  // name
           current_byte_offset,                                    // offset
-          alignment_pad                                           // byte_length
+          alignment_pad,                                          // size
+          alignment_pad,                                          // byte_length
+          1,  // array_elements
+          0,  // element_padding
       });
       current_byte_offset += alignment_pad;
     }
@@ -598,14 +604,19 @@
         member.columns == 4 &&                                        //
         member.vecsize == 4                                           //
     ) {
+      uint32_t stride = GetArrayStride<sizeof(Matrix)>(struct_type, member, i);
+      uint32_t element_padding = stride - sizeof(Matrix);
       result.emplace_back(StructMember{
           "Matrix",                              // type
           BaseTypeToString(member.basetype),     // basetype
           GetMemberNameAtIndex(struct_type, i),  // name
           struct_member_offset,                  // offset
-          sizeof(Matrix)                         // byte_length
+          sizeof(Matrix),                        // size
+          stride * array_elements,               // byte_length
+          array_elements,                        // array_elements
+          element_padding,                       // element_padding
       });
-      current_byte_offset += sizeof(Matrix);
+      current_byte_offset += stride * array_elements;
       continue;
     }
 
@@ -615,14 +626,19 @@
         member.columns == 1 &&                                        //
         member.vecsize == 2                                           //
     ) {
+      uint32_t stride = GetArrayStride<sizeof(Point)>(struct_type, member, i);
+      uint32_t element_padding = stride - sizeof(Point);
       result.emplace_back(StructMember{
           "Point",                               // type
           BaseTypeToString(member.basetype),     // basetype
           GetMemberNameAtIndex(struct_type, i),  // name
           struct_member_offset,                  // offset
-          sizeof(Point)                          // byte_length
+          sizeof(Point),                         // size
+          stride * array_elements,               // byte_length
+          array_elements,                        // array_elements
+          element_padding,                       // element_padding
       });
-      current_byte_offset += sizeof(Point);
+      current_byte_offset += stride * array_elements;
       continue;
     }
 
@@ -632,14 +648,19 @@
         member.columns == 1 &&                                        //
         member.vecsize == 3                                           //
     ) {
+      uint32_t stride = GetArrayStride<sizeof(Vector3)>(struct_type, member, i);
+      uint32_t element_padding = stride - sizeof(Vector3);
       result.emplace_back(StructMember{
           "Vector3",                             // type
           BaseTypeToString(member.basetype),     // basetype
           GetMemberNameAtIndex(struct_type, i),  // name
           struct_member_offset,                  // offset
-          sizeof(Vector3)                        // byte_length
+          sizeof(Vector3),                       // size
+          stride * array_elements,               // byte_length
+          array_elements,                        // array_elements
+          element_padding,                       // element_padding
       });
-      current_byte_offset += sizeof(Vector3);
+      current_byte_offset += stride * array_elements;
       continue;
     }
 
@@ -649,14 +670,19 @@
         member.columns == 1 &&                                        //
         member.vecsize == 4                                           //
     ) {
+      uint32_t stride = GetArrayStride<sizeof(Vector4)>(struct_type, member, i);
+      uint32_t element_padding = stride - sizeof(Vector4);
       result.emplace_back(StructMember{
           "Vector4",                             // type
           BaseTypeToString(member.basetype),     // basetype
           GetMemberNameAtIndex(struct_type, i),  // name
           struct_member_offset,                  // offset
-          sizeof(Vector4)                        // byte_length
+          sizeof(Vector4),                       // size
+          stride * array_elements,               // byte_length
+          array_elements,                        // array_elements
+          element_padding,                       // element_padding
       });
-      current_byte_offset += sizeof(Vector4);
+      current_byte_offset += stride * array_elements;
       continue;
     }
 
@@ -667,15 +693,23 @@
           member.columns == 1 &&           //
           member.vecsize == 1              //
       ) {
+        uint32_t stride = GetArrayStride<0>(struct_type, member, i);
+        if (stride == 0) {
+          stride = maybe_known_type.value().byte_size;
+        }
+        uint32_t element_padding = stride - maybe_known_type.value().byte_size;
         // Add the type directly.
         result.emplace_back(StructMember{
             maybe_known_type.value().name,         // type
             BaseTypeToString(member.basetype),     // basetype
             GetMemberNameAtIndex(struct_type, i),  // name
             struct_member_offset,                  // offset
-            maybe_known_type.value().byte_size     // byte_length
+            maybe_known_type.value().byte_size,    // size
+            stride * array_elements,               // byte_length
+            array_elements,                        // array_elements
+            element_padding,                       // element_padding
         });
-        current_byte_offset += maybe_known_type.value().byte_size;
+        current_byte_offset += stride * array_elements;
         continue;
       }
     }
@@ -683,16 +717,23 @@
     // Catch all for unknown types. Just add the necessary padding to the struct
     // and move on.
     {
-      const size_t byte_length =
-          (member.width * member.columns * member.vecsize) / 8u;
+      const size_t size = (member.width * member.columns * member.vecsize) / 8u;
+      uint32_t stride = GetArrayStride<0>(struct_type, member, i);
+      if (stride == 0) {
+        stride = size;
+      }
+      auto element_padding = stride - size;
       result.emplace_back(StructMember{
-          TypeNameWithPaddingOfSize(byte_length),  // type
-          BaseTypeToString(member.basetype),       // basetype
-          GetMemberNameAtIndex(struct_type, i),    // name
-          struct_member_offset,                    // offset
-          byte_length                              // byte_length
+          TypeNameWithPaddingOfSize(size),       // type
+          BaseTypeToString(member.basetype),     // basetype
+          GetMemberNameAtIndex(struct_type, i),  // name
+          struct_member_offset,                  // offset
+          size,                                  // size
+          stride * array_elements,               // byte_length
+          array_elements,                        // array_elements
+          0,                                     // element_padding
       });
-      current_byte_offset += byte_length;
+      current_byte_offset += stride * array_elements;
       continue;
     }
   }
@@ -709,7 +750,10 @@
                 spirv_cross::SPIRType::BaseType::Void),  // basetype
             "_PADDING_",                                 // name
             current_byte_offset,                         // offset
-            padding                                      // byte_length
+            padding,                                     // size
+            padding,                                     // byte_length
+            1,                                           // array_elements
+            0,                                           // element_padding
         });
       }
     }
@@ -746,13 +790,15 @@
   result["name"] = struc->name;
   result["byte_length"] = struc->byte_length;
   auto& members = result["members"] = nlohmann::json::array_t{};
-  for (const auto& struc_member : struc->members) {
+  for (const auto& struct_member : struc->members) {
     auto& member = members.emplace_back(nlohmann::json::object_t{});
-    member["name"] = struc_member.name;
-    member["type"] = struc_member.type;
-    member["base_type"] = struc_member.base_type;
-    member["offset"] = struc_member.offset;
-    member["byte_length"] = struc_member.byte_length;
+    member["name"] = struct_member.name;
+    member["type"] = struct_member.type;
+    member["base_type"] = struct_member.base_type;
+    member["offset"] = struct_member.offset;
+    member["byte_length"] = struct_member.byte_length;
+    member["array_elements"] = struct_member.array_elements;
+    member["element_padding"] = struct_member.element_padding;
   }
   return result;
 }
@@ -863,7 +909,10 @@
         vertex_type.base_type_name,  // base type
         vertex_type.variable_name,   // name
         struc.byte_length,           // offset
-        vertex_type.byte_length      // byte_length
+        vertex_type.byte_length,     // size
+        vertex_type.byte_length,     // byte_length
+        1,                           // array_elements
+        0,                           // element_padding
     };
     struc.byte_length += vertex_type.byte_length;
     struc.members.emplace_back(std::move(member));
diff --git a/impeller/compiler/reflector.h b/impeller/compiler/reflector.h
index 2c0de13..a58d8d1 100644
--- a/impeller/compiler/reflector.h
+++ b/impeller/compiler/reflector.h
@@ -23,18 +23,27 @@
   std::string base_type;
   std::string name;
   size_t offset = 0u;
+  size_t size = 0u;
   size_t byte_length = 0u;
+  size_t array_elements = 1u;
+  size_t element_padding = 0u;
 
   StructMember(std::string p_type,
                std::string p_base_type,
                std::string p_name,
                size_t p_offset,
-               size_t p_byte_length)
+               size_t p_size,
+               size_t p_byte_length,
+               size_t p_array_elements,
+               size_t p_element_padding)
       : type(std::move(p_type)),
         base_type(std::move(p_base_type)),
         name(std::move(p_name)),
         offset(p_offset),
-        byte_length(p_byte_length) {}
+        size(p_size),
+        byte_length(p_byte_length),
+        array_elements(p_array_elements),
+        element_padding(p_element_padding) {}
 };
 
 class Reflector {
@@ -142,6 +151,17 @@
 
   uint32_t GetArrayElements(const spirv_cross::SPIRType& type) const;
 
+  template <uint32_t Size>
+  uint32_t GetArrayStride(const spirv_cross::SPIRType& struct_type,
+                          const spirv_cross::SPIRType& member_type,
+                          uint32_t index) const {
+    auto element_count = GetArrayElements(member_type);
+    if (element_count <= 1) {
+      return Size;
+    }
+    return compiler_->type_struct_member_array_stride(struct_type, index);
+  };
+
   FML_DISALLOW_COPY_AND_ASSIGN(Reflector);
 };
 
diff --git a/impeller/docs/ubo_gles2.md b/impeller/docs/ubo_gles2.md
index ef6c93a..4fb4831 100644
--- a/impeller/docs/ubo_gles2.md
+++ b/impeller/docs/ubo_gles2.md
@@ -31,6 +31,7 @@
   std::string name; // the uniform member name "frame_info.mvp"
   size_t offset;
   size_t size;
+  size_t array_elements;
 };
 ```
 
diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn
index a31b604..c125990 100644
--- a/impeller/fixtures/BUILD.gn
+++ b/impeller/fixtures/BUILD.gn
@@ -8,6 +8,8 @@
 impeller_shaders("shader_fixtures") {
   name = "fixtures"
   shaders = [
+    "array.frag",
+    "array.vert",
     "box_fade.frag",
     "box_fade.vert",
     "colors.vert",
diff --git a/impeller/fixtures/array.frag b/impeller/fixtures/array.frag
new file mode 100644
index 0000000..def284a
--- /dev/null
+++ b/impeller/fixtures/array.frag
@@ -0,0 +1,27 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+uniform FragInfo {
+  vec2 circle_positions[4];
+  vec4 colors[4];
+}
+frag_info;
+
+in vec2 v_position;
+
+out vec4 frag_color;
+
+float SphereDistance(vec2 position, float radius) {
+  return length(v_position - position) - radius;
+}
+
+void main() {
+  for (int i = 0; i < 4; i++) {
+    if (SphereDistance(frag_info.circle_positions[i].xy, 20) <= 0) {
+      frag_color = frag_info.colors[i];
+      return;
+    }
+  }
+  frag_color = vec4(0);
+}
diff --git a/impeller/fixtures/array.vert b/impeller/fixtures/array.vert
new file mode 100644
index 0000000..2babce3
--- /dev/null
+++ b/impeller/fixtures/array.vert
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+uniform VertInfo {
+  mat4 mvp;
+}
+vert_info;
+
+in vec2 position;
+
+out vec2 v_position;
+
+void main() {
+  gl_Position = vert_info.mvp * vec4(position, 0.0, 1.0);
+  v_position = position;
+}
diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles.cc b/impeller/renderer/backend/gles/buffer_bindings_gles.cc
index 5119b5c..ceb13db 100644
--- a/impeller/renderer/backend/gles/buffer_bindings_gles.cc
+++ b/impeller/renderer/backend/gles/buffer_bindings_gles.cc
@@ -5,7 +5,9 @@
 #include "impeller/renderer/backend/gles/buffer_bindings_gles.h"
 
 #include <algorithm>
+#include <cstring>
 #include <sstream>
+#include <vector>
 
 #include "impeller/base/config.h"
 #include "impeller/base/validation.h"
@@ -70,9 +72,13 @@
 }
 
 static std::string CreateUnifiormMemberKey(const std::string& struct_name,
-                                           const std::string& member) {
+                                           const std::string& member,
+                                           bool is_array) {
   std::stringstream stream;
   stream << struct_name << "." << member;
+  if (is_array) {
+    stream << "[0]";
+  }
   return NormalizeUniformKey(stream.str());
 }
 
@@ -114,10 +120,6 @@
       VALIDATION_LOG << "Uniform name could not be read for active uniform.";
       return false;
     }
-    if (uniform_var_size != 1) {
-      VALIDATION_LOG << "Array uniform types are not supported.";
-      return false;
-    }
     uniform_locations_[NormalizeUniformKey(std::string{
         name.data(), static_cast<size_t>(written_count)})] = location;
   }
@@ -208,51 +210,69 @@
       // mappings for these. Keep going.
       continue;
     }
-    const auto member_key =
-        CreateUnifiormMemberKey(metadata->name, member.name);
+
+    const auto member_key = CreateUnifiormMemberKey(metadata->name, member.name,
+                                                    member.array_elements > 1);
     const auto location = uniform_locations_.find(member_key);
     if (location == uniform_locations_.end()) {
       VALIDATION_LOG << "Location for uniform member not known: " << member_key;
       return false;
     }
 
+    size_t element_stride = member.byte_length / member.array_elements;
+
+    auto* buffer_data =
+        reinterpret_cast<const GLfloat*>(buffer_ptr + member.offset);
+
+    std::vector<uint8_t> array_element_buffer;
+    if (member.array_elements > 1) {
+      // When binding uniform arrays, the elements must be contiguous. Copy the
+      // uniforms to a temp buffer to eliminate any padding needed by the other
+      // backends.
+      array_element_buffer.resize(member.size * member.array_elements);
+      for (size_t element_i = 0; element_i < member.array_elements;
+           element_i++) {
+        std::memcpy(array_element_buffer.data() + element_i * member.size,
+                    reinterpret_cast<const char*>(buffer_data) +
+                        element_i * element_stride,
+                    member.size);
+      }
+      buffer_data =
+          reinterpret_cast<const GLfloat*>(array_element_buffer.data());
+    }
+
     switch (member.type) {
       case ShaderType::kFloat:
         switch (member.size) {
           case sizeof(Matrix):
-            gl.UniformMatrix4fv(location->second,  // location
-                                1u,                // count
-                                GL_FALSE,          // normalize
-                                reinterpret_cast<const GLfloat*>(
-                                    buffer_ptr + member.offset)  // data
+            gl.UniformMatrix4fv(location->second,       // location
+                                member.array_elements,  // count
+                                GL_FALSE,               // normalize
+                                buffer_data             // data
             );
             continue;
           case sizeof(Vector4):
-            gl.Uniform4fv(location->second,  // location
-                          1u,                // count
-                          reinterpret_cast<const GLfloat*>(
-                              buffer_ptr + member.offset)  // data
+            gl.Uniform4fv(location->second,       // location
+                          member.array_elements,  // count
+                          buffer_data             // data
             );
             continue;
           case sizeof(Vector3):
-            gl.Uniform3fv(location->second,  // location
-                          1u,                // count
-                          reinterpret_cast<const GLfloat*>(
-                              buffer_ptr + member.offset)  // data
+            gl.Uniform3fv(location->second,       // location
+                          member.array_elements,  // count
+                          buffer_data             // data
             );
             continue;
           case sizeof(Vector2):
-            gl.Uniform2fv(location->second,  // location
-                          1u,                // count
-                          reinterpret_cast<const GLfloat*>(
-                              buffer_ptr + member.offset)  // data
+            gl.Uniform2fv(location->second,       // location
+                          member.array_elements,  // count
+                          buffer_data             // data
             );
             continue;
           case sizeof(Scalar):
-            gl.Uniform1fv(location->second,  // location
-                          1u,                // count
-                          reinterpret_cast<const GLfloat*>(
-                              buffer_ptr + member.offset)  // data
+            gl.Uniform1fv(location->second,       // location
+                          member.array_elements,  // count
+                          buffer_data             // data
             );
             continue;
         }
diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc
index af01a5d..315ec54 100644
--- a/impeller/renderer/renderer_unittests.cc
+++ b/impeller/renderer/renderer_unittests.cc
@@ -5,6 +5,8 @@
 #include "flutter/fml/time/time_point.h"
 #include "flutter/testing/testing.h"
 #include "impeller/base/strings.h"
+#include "impeller/fixtures/array.frag.h"
+#include "impeller/fixtures/array.vert.h"
 #include "impeller/fixtures/box_fade.frag.h"
 #include "impeller/fixtures/box_fade.vert.h"
 #include "impeller/fixtures/colors.frag.h"
@@ -736,5 +738,61 @@
   OpenPlaygroundHere(callback);
 }
 
+TEST_P(RendererTest, ArrayUniforms) {
+  using VS = ArrayVertexShader;
+  using FS = ArrayFragmentShader;
+
+  auto context = GetContext();
+  auto pipeline_descriptor =
+      PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context);
+  ASSERT_TRUE(pipeline_descriptor.has_value());
+  pipeline_descriptor->SetSampleCount(SampleCount::kCount4);
+  auto pipeline =
+      context->GetPipelineLibrary()->GetPipeline(pipeline_descriptor).get();
+  ASSERT_TRUE(pipeline && pipeline->IsValid());
+
+  SinglePassCallback callback = [&](RenderPass& pass) {
+    auto size = pass.GetRenderTargetSize();
+
+    Command cmd;
+    cmd.pipeline = pipeline;
+    cmd.label = "Google Dots";
+    VertexBufferBuilder<VS::PerVertexData> builder;
+    builder.AddVertices({{Point()},
+                         {Point(0, size.height)},
+                         {Point(size.width, 0)},
+                         {Point(size.width, 0)},
+                         {Point(0, size.height)},
+                         {Point(size.width, size.height)}});
+    cmd.BindVertices(builder.CreateVertexBuffer(pass.GetTransientsBuffer()));
+
+    VS::VertInfo vs_uniform;
+    vs_uniform.mvp =
+        Matrix::MakeOrthographic(size) * Matrix::MakeScale(GetContentScale());
+    VS::BindVertInfo(cmd,
+                     pass.GetTransientsBuffer().EmplaceUniform(vs_uniform));
+
+    auto time = fml::TimePoint::Now().ToEpochDelta().ToSecondsF();
+    auto y_pos = [&time](float x) {
+      return 400 + 10 * std::cos(time * 5 + x / 6);
+    };
+
+    FS::FragInfo fs_uniform = {
+        .circle_positions = {Point(430, y_pos(0)), Point(480, y_pos(1)),
+                             Point(530, y_pos(2)), Point(580, y_pos(3))},
+        .colors = {Color::MakeRGBA8(66, 133, 244, 255),
+                   Color::MakeRGBA8(219, 68, 55, 255),
+                   Color::MakeRGBA8(244, 180, 0, 255),
+                   Color::MakeRGBA8(15, 157, 88, 255)},
+    };
+    FS::BindFragInfo(cmd,
+                     pass.GetTransientsBuffer().EmplaceUniform(fs_uniform));
+
+    pass.AddCommand(cmd);
+    return true;
+  };
+  OpenPlaygroundHere(callback);
+}
+
 }  // namespace testing
 }  // namespace impeller
diff --git a/impeller/renderer/shader_types.h b/impeller/renderer/shader_types.h
index fe4af81..cf2e9c9 100644
--- a/impeller/renderer/shader_types.h
+++ b/impeller/renderer/shader_types.h
@@ -67,6 +67,8 @@
   std::string name;
   size_t offset;
   size_t size;
+  size_t byte_length;
+  size_t array_elements;
 };
 
 struct ShaderMetadata {
@@ -122,6 +124,17 @@
   uint8_t pad_[Size];
 };
 
+/// @brief Struct used for padding uniform buffer array elements.
+template <typename T,
+          size_t Size,
+          class = std::enable_if_t<std::is_standard_layout_v<T>>>
+struct Padded {
+  T value;
+  Padding<Size> _PADDING_;
+
+  Padded(T p_value) : value(p_value){};  // NOLINT(google-explicit-constructor)
+};
+
 inline constexpr Vector4 ToVector(Color color) {
   return {color.red, color.green, color.blue, color.alpha};
 }