Add support for fetching array uniforms by name (#180647)

Adds support for fetching objects representing array Uniforms of type
float, vec2, vec3, and vec4. Happy new year!

Before this change, setting an array of uniforms was verbose.
```glsl
uniform vec3[2] uColors;
```

```dart
shader.setFloat(0, 1.0);
shader.setFloat(1, 0.0);
shader.setFloat(2, 1.0);
shader.setFloat(3, 0.0);
shader.setFloat(4, 1.0);
shader.setFloat(5, 0.0);
```

After this change, we have:

```glsl
uniform vec3[2] uColors;
```

```dart
final colors = shader.getUniformVec3Array("uColors");
colors[0].set(1.0, 0.0, 1.0);
colors[1].set(0.0, 1.0, 0.0);
```

- Adds a class UniformArray to represent uniform arrays in Dart.
- Adds a bunch of methods that construct these arrays for various
datatypes
- Adds tests
- Also removes some of the awkwardness in the _getUniformFloatIndex
function by replacing that function entirely

## Pre-launch Checklist

- [x ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ x] I read and followed the [Flutter Style Guide], including
[Features we expect every widget to implement].
- [ x] I signed the [CLA].
- [ x] I listed at least one issue that this PR fixes in the description
above.
- [ x] I updated/added relevant documentation (doc comments with `///`).
- [ x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ x] All existing and new tests are passing.
diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/float_array_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/float_array_uniform.frag
new file mode 100644
index 0000000..68b67c1
--- /dev/null
+++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/float_array_uniform.frag
@@ -0,0 +1,19 @@
+#version 320 es
+
+// 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.
+
+// If updating this file, also update
+// engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart
+
+precision highp float;
+
+uniform float[4] color_array;
+
+out vec4 fragColor;
+
+void main() {
+  fragColor =
+      vec4(color_array[0], color_array[1], color_array[2], color_array[3]);
+}
diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms.frag
index 5774c38..dfb903c 100644
--- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms.frag
+++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms.frag
@@ -16,6 +16,10 @@
 layout(location = 2) uniform mat2 iMat2Uniform;
 layout(location = 3) uniform vec3 iVec3Uniform;
 layout(location = 4) uniform vec4 iVec4Uniform;
+layout(location = 5) uniform float[10] iFloatArrayUniform;
+layout(location = 15) uniform vec2[3] iVec2ArrayUniform;
+layout(location = 21) uniform vec3[3] iVec3ArrayUniform;
+layout(location = 30) uniform vec4[3] iVec4ArrayUniform;
 
 void main() {
   oColor = vec4(iFloatUniform, iVec2Uniform, iMat2Uniform[1][1]);
diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms_inserted.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms_inserted.frag
index 448fc01..4475c94 100644
--- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms_inserted.frag
+++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms_inserted.frag
@@ -16,6 +16,10 @@
 layout(location = 3) uniform mat2 iMat2Uniform;
 layout(location = 4) uniform vec3 iVec3Uniform;
 layout(location = 5) uniform vec4 iVec4Uniform;
+layout(location = 6) uniform float[10] iFloatArrayUniform;
+layout(location = 16) uniform vec2[3] iVec2ArrayUniform;
+layout(location = 22) uniform vec3[3] iVec3ArrayUniform;
+layout(location = 31) uniform vec4[3] iVec4ArrayUniform;
 
 void main() {
   oColor = vec4(iInserted * iFloatUniform, iVec2Uniform, iMat2Uniform[1][1]);
diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms_reordered.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms_reordered.frag
index 0744dbf..987a566 100644
--- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms_reordered.frag
+++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/uniforms_reordered.frag
@@ -15,6 +15,10 @@
 layout(location = 2) uniform vec2 iVec2Uniform;
 layout(location = 3) uniform vec3 iVec3Uniform;
 layout(location = 4) uniform vec4 iVec4Uniform;
+layout(location = 5) uniform float[10] iFloatArrayUniform;
+layout(location = 15) uniform vec2[3] iVec2ArrayUniform;
+layout(location = 21) uniform vec3[3] iVec3ArrayUniform;
+layout(location = 30) uniform vec4[3] iVec4ArrayUniform;
 
 void main() {
   oColor = vec4(iFloatUniform, iVec2Uniform, iMat2Uniform[1][1]);
diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec2_array_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec2_array_uniform.frag
new file mode 100644
index 0000000..e926d48
--- /dev/null
+++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec2_array_uniform.frag
@@ -0,0 +1,19 @@
+#version 320 es
+
+// 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.
+
+// If updating this file, also update
+// engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart
+
+precision highp float;
+
+uniform vec2[2] color_array;
+
+out vec4 fragColor;
+
+void main() {
+  fragColor = vec4(color_array[0].x, color_array[0].y, color_array[1].x,
+                   color_array[1].y);
+}
diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_array_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_array_uniform.frag
new file mode 100644
index 0000000..23acaba
--- /dev/null
+++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_array_uniform.frag
@@ -0,0 +1,18 @@
+#version 320 es
+
+// 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.
+
+// If updating this file, also update
+// engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart
+
+precision highp float;
+
+uniform vec3[2] color_array;
+
+out vec4 fragColor;
+
+void main() {
+  fragColor = vec4(mix(color_array[0], color_array[1], 0.5), 1);
+}
diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec4_array_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec4_array_uniform.frag
new file mode 100644
index 0000000..f8e98d4
--- /dev/null
+++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec4_array_uniform.frag
@@ -0,0 +1,18 @@
+#version 320 es
+
+// 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.
+
+// If updating this file, also update
+// engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart
+
+precision highp float;
+
+uniform vec4[2] color_array;
+
+out vec4 fragColor;
+
+void main() {
+  fragColor = mix(color_array[0], color_array[1], 0.5);
+}
diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart
index d25b09d..171de57 100644
--- a/engine/src/flutter/lib/ui/painting.dart
+++ b/engine/src/flutter/lib/ui/painting.dart
@@ -5324,6 +5324,8 @@
   external void _dispose();
 }
 
+typedef _UniformFloatInfo = ({int index, int size});
+
 /// An instance of [FragmentProgram] creates [Shader] objects (as used by
 /// [Paint.shader]).
 ///
@@ -5410,7 +5412,9 @@
           return true;
         }
 
-        slot._shaderIndex = program._getUniformFloatIndex(slot.name, slot.index);
+        final _UniformFloatInfo info = program._getUniformFloatInfo(slot.name);
+        slot._shaderIndex = info.index + slot.index;
+
         return false;
       });
 
@@ -5454,24 +5458,15 @@
     return index;
   }
 
-  int _getUniformFloatIndex(String name, int index, [int? expectedSize]) {
-    if (index < 0) {
-      throw ArgumentError('Index `$index` out of bounds for `$name`.');
-    }
-
+  _UniformFloatInfo _getUniformFloatInfo(String name) {
     var offset = 0;
+    var sizeInFloats = 0;
     var found = false;
     const sizeOfFloat = 4;
     for (final Object? entryDynamic in _uniformInfo) {
       final entry = entryDynamic! as Map<String, Object>;
-      final int sizeInFloats = (entry['size'] as int? ?? 0) ~/ sizeOfFloat;
+      sizeInFloats = (entry['size'] as int? ?? 0) ~/ sizeOfFloat;
       if (entry['name'] == name) {
-        if (index + 1 > sizeInFloats) {
-          throw ArgumentError('Index `$index` out of bounds for `$name`.');
-        }
-        if (expectedSize != null && sizeInFloats != expectedSize) {
-          throw ArgumentError('Uniform `$name` has size $sizeInFloats, not size $expectedSize.');
-        }
         found = true;
         break;
       }
@@ -5482,7 +5477,7 @@
       throw ArgumentError('No uniform named "$name".');
     }
 
-    return offset + index;
+    return (index: offset, size: sizeInFloats);
   }
 
   @pragma('vm:entry-point')
@@ -5509,6 +5504,10 @@
   }
 }
 
+/// A binding into a uniform defined in a shader. Used now to restrict the types
+/// of UniformArrays that can be created.
+sealed class UniformType {}
+
 /// A binding to a uniform of type float. Calling [set] on this object updates
 /// a float uniform's value.
 ///
@@ -5525,7 +5524,7 @@
 /// See also:
 ///   [FragmentShader.getUniformFloat] - How [UniformFloatSlot] instances are acquired.
 ///
-base class UniformFloatSlot {
+base class UniformFloatSlot extends UniformType {
   UniformFloatSlot._(this._shader, this.name, this.index, this._shaderIndex);
 
   /// Set the float value of the bound uniform.
@@ -5563,7 +5562,7 @@
 /// See also:
 ///   [FragmentShader.getUniformVec2] - How [UniformVec2Slot] instances are acquired.
 ///
-base class UniformVec2Slot {
+base class UniformVec2Slot extends UniformType {
   UniformVec2Slot._(this._xSlot, this._ySlot);
 
   /// Set the float value of the bound uniform.
@@ -5589,7 +5588,7 @@
 /// See also:
 ///   [FragmentShader.getUniformVec3] - How [UniformVec3Slot] instances are acquired.
 ///
-base class UniformVec3Slot {
+base class UniformVec3Slot extends UniformType {
   UniformVec3Slot._(this._xSlot, this._ySlot, this._zSlot);
 
   /// Set the float value of the bound uniform.
@@ -5616,7 +5615,7 @@
 /// See also:
 ///   [FragmentShader.getUniformVec4] - How [UniformVec4Slot] instances are acquired.
 ///
-base class UniformVec4Slot {
+base class UniformVec4Slot extends UniformType {
   UniformVec4Slot._(this._xSlot, this._ySlot, this._zSlot, this._wSlot);
 
   /// Set the float value of the bound uniform.
@@ -5630,6 +5629,34 @@
   final UniformFloatSlot _xSlot, _ySlot, _zSlot, _wSlot;
 }
 
+/// An array of bindings to uniforms of the same type T. Access elements via [] and
+/// set them individually.
+/// Example:
+///
+/// ```dart
+/// void updateShader(ui.FragmentShader shader) {
+///   final ui.UniformArray<ui.UniformVec4Slot> colors = shader.getUniformVec4Array('uColorArray');
+///   colors[0].set(1.0, 0.0, 1.0, 0.3);
+/// }
+/// ```
+///
+/// See also:
+///   [FragmentShader.getUniformFloatArray] - How [UniformArray<Float>] instances are acquired.
+///
+class UniformArray<T extends UniformType> {
+  UniformArray._(this._elements);
+
+  /// Access an element of the UniformArray.
+  T operator [](int index) {
+    return _elements[index];
+  }
+
+  /// The number of Uniforms in the UniformArray.
+  int get length => _elements.length;
+
+  final List<T> _elements;
+}
+
 /// A binding to a shader's image sampler. Calling [set] on this object updates
 /// a sampler's bound image.
 base class ImageSamplerSlot {
@@ -5683,10 +5710,15 @@
   }
 
   List<UniformFloatSlot> _getSlotsForUniform(String name, int size) {
-    final int baseShaderIndex = _program._getUniformFloatIndex(name, 0, size);
+    final _UniformFloatInfo info = _program._getUniformFloatInfo(name);
+
+    if (info.size != size) {
+      throw ArgumentError('Uniform `$name` has size ${info.size}, not size $size.');
+    }
+
     final slots = List<UniformFloatSlot>.generate(
       size,
-      (i) => UniformFloatSlot._(this, name, baseShaderIndex, i),
+      (i) => UniformFloatSlot._(this, name, info.index, i),
     );
     _slots.removeWhere((WeakReference<UniformFloatSlot> ref) => ref.target == null);
     _slots.addAll(slots.map((slot) => WeakReference<UniformFloatSlot>(slot)));
@@ -5766,8 +5798,12 @@
   /// ```
   UniformFloatSlot getUniformFloat(String name, [int? index]) {
     index ??= 0;
-    final int shaderIndex = _program._getUniformFloatIndex(name, index);
-    final result = UniformFloatSlot._(this, name, index, shaderIndex);
+
+    final _UniformFloatInfo info = _program._getUniformFloatInfo(name);
+
+    IndexError.check(index, info.size, message: 'Index `$index` out of bounds for `$name`.');
+
+    final result = UniformFloatSlot._(this, name, index, info.index + index);
     _slots.removeWhere((WeakReference<UniformFloatSlot> ref) => ref.target == null);
     _slots.add(WeakReference<UniformFloatSlot>(result));
     return result;
@@ -5833,6 +5869,126 @@
     return UniformVec4Slot._(slots[0], slots[1], slots[2], slots[3]);
   }
 
+  UniformArray<T> _getUniformArray<T extends UniformType>(
+    String name,
+    int elementSize,
+    T Function(List<UniformFloatSlot> slots) elementFactory,
+  ) {
+    final _UniformFloatInfo info = _program._getUniformFloatInfo(name);
+
+    if (info.size % elementSize != 0) {
+      throw ArgumentError(
+        'Uniform size (${info.size}) for "$name" is not a multiple of $elementSize.',
+      );
+    }
+    final int numElements = info.size ~/ elementSize;
+
+    final elements = List<T>.generate(numElements, (i) {
+      final slots = List<UniformFloatSlot>.generate(
+        info.size,
+        (j) => UniformFloatSlot._(this, name, j, info.index + i * elementSize + j),
+      );
+      _slots.addAll(slots.map((slot) => WeakReference<UniformFloatSlot>(slot)));
+      return elementFactory(slots);
+    });
+
+    _slots.removeWhere((WeakReference<UniformFloatSlot> ref) => ref.target == null);
+
+    return UniformArray<T>._(elements);
+  }
+
+  /// Access the binding for a float[] uniform named [name].
+  ///
+  /// Example:
+  ///
+  /// ```glsl
+  /// uniform float[10] uValues;
+  /// ```
+  ///
+  /// ```dart
+  /// void updateShader(ui.FragmentShader shader) {
+  ///   final ui.UniformArray<ui.UniformFloatSlot> values = shader.getUniformFloatArray('uValues');
+  ///   values[2].set(1.0);
+  /// }
+  /// ```
+  UniformArray<UniformFloatSlot> getUniformFloatArray(String name) {
+    return _getUniformArray(name, 1, (components) => components.first);
+  }
+
+  /// Access the binding for a vec2[] uniform named [name].
+  ///
+  /// Example:
+  ///
+  /// ```glsl
+  /// uniform vec2[10] uPositions;
+  /// ```
+  ///
+  /// ```dart
+  /// void updateShader(ui.FragmentShader shader) {
+  ///   final ui.UniformArray<ui.UniformVec2Slot> positions = shader.getUniformVec2Array('uPositions');
+  ///   positions[2].set(6.0, 7.0);
+  /// }
+  /// ```
+  UniformArray<UniformVec2Slot> getUniformVec2Array(String name) {
+    return _getUniformArray<UniformVec2Slot>(
+      name,
+      2, // 2 floats per element
+      (components) => UniformVec2Slot._(
+        components[0],
+        components[1],
+      ), // Create Vec2 from two UniformFloat components
+    );
+  }
+
+  /// Access the binding for a vec3[] uniform named [name].
+  ///
+  /// Example:
+  ///
+  /// ```glsl
+  /// uniform vec3[10] uColors;
+  /// ```
+  ///
+  /// ```dart
+  /// void updateShader(ui.FragmentShader shader) {
+  ///   final ui.UniformArray<ui.UniformVec3Slot> colors = shader.getUniformVec3Array('uColors');
+  ///   colors[0].set(1.0, 0.0, 1.0);
+  /// }
+  /// ```
+  UniformArray<UniformVec3Slot> getUniformVec3Array(String name) {
+    return _getUniformArray<UniformVec3Slot>(
+      name,
+      3, // 3 floats per element
+      (components) => UniformVec3Slot._(components[0], components[1], components[2]), // Create Vec3
+    );
+  }
+
+  /// Access the binding for a vec4[] uniform named [name].
+  ///
+  /// Example:
+  ///
+  /// ```glsl
+  /// uniform vec4[10] uColors;
+  /// ```
+  ///
+  /// ```dart
+  /// void updateShader(ui.FragmentShader shader) {
+  ///   final ui.UniformArray<ui.UniformVec4Slot> colors = shader.getUniformVec4Array('uColors');
+  ///   colors[0].set(1.0, 0.0, 1.0, 0.5);
+  /// }
+  /// ```
+  UniformArray<UniformVec4Slot> getUniformVec4Array(String name) {
+    return _getUniformArray<UniformVec4Slot>(
+      name,
+      4, // 4 floats per element
+      (components) => UniformVec4Slot._(
+        components[0],
+        components[1],
+        components[2],
+        components[3],
+      ), // Create Vec4
+    );
+  }
+
   /// Access the [ImageSamplerSlot] binding associated with the sampler named
   /// [name].
   ///
diff --git a/engine/src/flutter/lib/web_ui/lib/painting.dart b/engine/src/flutter/lib/web_ui/lib/painting.dart
index 88ff3ae..6726052 100644
--- a/engine/src/flutter/lib/web_ui/lib/painting.dart
+++ b/engine/src/flutter/lib/web_ui/lib/painting.dart
@@ -1015,7 +1015,9 @@
   FragmentShader fragmentShader();
 }
 
-abstract class UniformFloatSlot {
+sealed class UniformType {}
+
+abstract class UniformFloatSlot extends UniformType {
   UniformFloatSlot(this.name, this.index);
 
   void set(double val);
@@ -1027,18 +1029,23 @@
   final int index;
 }
 
-abstract class UniformVec2Slot {
+abstract class UniformVec2Slot extends UniformType {
   void set(double x, double y);
 }
 
-abstract class UniformVec3Slot {
+abstract class UniformVec3Slot extends UniformType {
   void set(double x, double y, double z);
 }
 
-abstract class UniformVec4Slot {
+abstract class UniformVec4Slot extends UniformType {
   void set(double x, double y, double z, double w);
 }
 
+abstract class UniformArray<T extends UniformType> {
+  T operator [](int index);
+  int get length;
+}
+
 abstract class ImageSamplerSlot {
   void set(Image val);
   int get shaderIndex;
@@ -1064,5 +1071,13 @@
 
   UniformVec4Slot getUniformVec4(String name);
 
+  UniformArray<UniformFloatSlot> getUniformFloatArray(String name);
+
+  UniformArray<UniformVec2Slot> getUniformVec2Array(String name);
+
+  UniformArray<UniformVec3Slot> getUniformVec3Array(String name);
+
+  UniformArray<UniformVec4Slot> getUniformVec4Array(String name);
+
   ImageSamplerSlot getImageSampler(String name);
 }
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart
index ec4cbd3..f93f996 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart
@@ -326,24 +326,13 @@
     return CkFragmentShader(name, effect, this);
   }
 
-  int _getShaderIndex(String name, int index, [int? expectedSize]) {
-    var result = 0;
+  UniformData _getUniformFloatInfo(String name) {
     for (final UniformData uniform in uniforms) {
       if (uniform.name == name) {
-        if (index < 0 || index >= uniform.floatCount) {
-          throw IndexError.withLength(index, uniform.floatCount);
-        }
-        if (expectedSize != null && uniform.floatCount != expectedSize) {
-          throw ArgumentError(
-            'Uniform `$name` has size ${uniform.floatCount}, not size $expectedSize.',
-          );
-        }
-        result += index;
-        break;
+        return uniform;
       }
-      result += uniform.floatCount;
     }
-    return result;
+    throw ArgumentError('No uniform named "$name".');
   }
 }
 
@@ -430,8 +419,11 @@
   @override
   ui.UniformFloatSlot getUniformFloat(String name, [int? index]) {
     index ??= 0;
-    final int shaderIndex = _program._getShaderIndex(name, index);
-    return CkUniformFloatSlot._(this, index, name, shaderIndex);
+    final UniformData info = _program._getUniformFloatInfo(name);
+
+    IndexError.check(index, info.floatCount, message: 'Index `$index` out of bounds for `$name`.');
+
+    return CkUniformFloatSlot._(this, index, name, info.location + index);
   }
 
   @override
@@ -452,16 +444,87 @@
     return _CkUniformVec4Slot._(slots[0], slots[1], slots[2], slots[3]);
   }
 
+  ui.UniformArray<T> _getUniformArray<T extends ui.UniformType>(
+    String name,
+    int elementSize,
+    T Function(List<CkUniformFloatSlot> slots) elementFactory,
+  ) {
+    final UniformData info = _program._getUniformFloatInfo(name);
+
+    if (info.floatCount % elementSize != 0) {
+      throw ArgumentError(
+        'Uniform size (${info.floatCount}) for "$name" is not a multiple of $elementSize.',
+      );
+    }
+    final int numElements = info.floatCount ~/ elementSize;
+
+    final elements = List<T>.generate(numElements, (i) {
+      final slots = List<CkUniformFloatSlot>.generate(
+        info.floatCount,
+        (j) => CkUniformFloatSlot._(this, j, name, info.location + i * elementSize + j),
+      );
+      return elementFactory(slots);
+    });
+
+    return _CkUniformFloatArray<T>._(elements);
+  }
+
+  @override
+  ui.UniformArray<ui.UniformFloatSlot> getUniformFloatArray(String name) {
+    return _getUniformArray(name, 1, (components) => components.first);
+  }
+
+  @override
+  ui.UniformArray<ui.UniformVec2Slot> getUniformVec2Array(String name) {
+    return _getUniformArray<_CkUniformVec2Slot>(
+      name,
+      2, // 2 floats per element
+      (components) => _CkUniformVec2Slot._(
+        components[0],
+        components[1],
+      ), // Create Vec2 from two UniformFloat components
+    );
+  }
+
+  @override
+  ui.UniformArray<ui.UniformVec3Slot> getUniformVec3Array(String name) {
+    return _getUniformArray<_CkUniformVec3Slot>(
+      name,
+      3, // 3 floats per element
+      (components) =>
+          _CkUniformVec3Slot._(components[0], components[1], components[2]), // Create Vec3
+    );
+  }
+
+  @override
+  ui.UniformArray<ui.UniformVec4Slot> getUniformVec4Array(String name) {
+    return _getUniformArray<_CkUniformVec4Slot>(
+      name,
+      4, // 4 floats per element
+      (components) => _CkUniformVec4Slot._(
+        components[0],
+        components[1],
+        components[2],
+        components[3],
+      ), // Create Vec4
+    );
+  }
+
   @override
   ui.ImageSamplerSlot getImageSampler(String name) {
     throw UnsupportedError('getImageSampler is not supported on the web.');
   }
 
   List<CkUniformFloatSlot> _getUniformFloatSlots(String name, int size) {
-    final int baseShaderIndex = _program._getShaderIndex(name, 0, size);
+    final UniformData info = _program._getUniformFloatInfo(name);
+
+    if (info.floatCount != size) {
+      throw ArgumentError('Uniform `$name` has size ${info.floatCount}, not size $size.');
+    }
+
     return List<CkUniformFloatSlot>.generate(
       size,
-      (i) => CkUniformFloatSlot._(this, i, name, baseShaderIndex),
+      (i) => CkUniformFloatSlot._(this, i, name, info.location + i),
     );
   }
 }
@@ -524,3 +587,17 @@
 
   final CkUniformFloatSlot _xSlot, _ySlot, _zSlot, _wSlot;
 }
+
+class _CkUniformFloatArray<T extends ui.UniformType> implements ui.UniformArray<T> {
+  _CkUniformFloatArray._(this._elements);
+
+  @override
+  T operator [](int index) {
+    return _elements[index];
+  }
+
+  @override
+  int get length => _elements.length;
+
+  final List<T> _elements;
+}
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart
index 0c73efc..1efb091 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart
@@ -250,24 +250,13 @@
 
   int get uniformSize => runtimeEffectGetUniformSize(handle);
 
-  int _getShaderIndex(String name, int index, [int? expectedSize]) {
-    var result = 0;
+  UniformData _getUniformFloatInfo(String name) {
     for (final UniformData uniform in _shaderData.uniforms) {
       if (uniform.name == name) {
-        if (index < 0 || index >= uniform.floatCount) {
-          throw IndexError.withLength(index, uniform.floatCount);
-        }
-        if (expectedSize != null && uniform.floatCount != expectedSize) {
-          throw ArgumentError(
-            'Uniform `$name` has size ${uniform.floatCount}, not size $expectedSize.',
-          );
-        }
-        result += index;
-        break;
+        return uniform;
       }
-      result += uniform.floatCount;
     }
-    return result;
+    throw ArgumentError('No uniform named "$name".');
   }
 }
 
@@ -383,8 +372,11 @@
   @override
   ui.UniformFloatSlot getUniformFloat(String name, [int? index]) {
     index ??= 0;
-    final int shaderIndex = _program._getShaderIndex(name, index);
-    return SkwasmUniformFloatSlot._(this, index, name, shaderIndex);
+    final UniformData info = _program._getUniformFloatInfo(name);
+
+    IndexError.check(index, info.floatCount, message: 'Index `$index` out of bounds for `$name`.');
+
+    return SkwasmUniformFloatSlot._(this, index, name, info.location + index);
   }
 
   @override
@@ -411,10 +403,81 @@
   }
 
   List<SkwasmUniformFloatSlot> _getUniformFloatSlots(String name, int size) {
-    final int baseShaderIndex = _program._getShaderIndex(name, 0, size);
+    final UniformData info = _program._getUniformFloatInfo(name);
+
+    if (info.floatCount != size) {
+      throw ArgumentError('Uniform `$name` has size ${info.floatCount}, not size $size.');
+    }
+
     return List<SkwasmUniformFloatSlot>.generate(
       size,
-      (i) => SkwasmUniformFloatSlot._(this, i, name, baseShaderIndex),
+      (i) => SkwasmUniformFloatSlot._(this, i, name, info.location + i),
+    );
+  }
+
+  ui.UniformArray<T> _getUniformArray<T extends ui.UniformType>(
+    String name,
+    int elementSize,
+    T Function(List<SkwasmUniformFloatSlot> slots) elementFactory,
+  ) {
+    final UniformData info = _program._getUniformFloatInfo(name);
+
+    if (info.floatCount % elementSize != 0) {
+      throw ArgumentError(
+        'Uniform size (${info.floatCount}) for "$name" is not a multiple of $elementSize.',
+      );
+    }
+    final int numElements = info.floatCount ~/ elementSize;
+
+    final elements = List<T>.generate(numElements, (i) {
+      final slots = List<SkwasmUniformFloatSlot>.generate(
+        info.floatCount,
+        (j) => SkwasmUniformFloatSlot._(this, j, name, info.location + i * elementSize + j),
+      );
+      return elementFactory(slots);
+    });
+
+    return _SkwasmUniformArray<T>._(elements);
+  }
+
+  @override
+  ui.UniformArray<ui.UniformFloatSlot> getUniformFloatArray(String name) {
+    return _getUniformArray(name, 1, (components) => components.first);
+  }
+
+  @override
+  ui.UniformArray<ui.UniformVec2Slot> getUniformVec2Array(String name) {
+    return _getUniformArray<_SkwasmUniformVec2Slot>(
+      name,
+      2, // 2 floats per element
+      (components) => _SkwasmUniformVec2Slot._(
+        components[0],
+        components[1],
+      ), // Create Vec2 from two UniformFloat components
+    );
+  }
+
+  @override
+  ui.UniformArray<ui.UniformVec3Slot> getUniformVec3Array(String name) {
+    return _getUniformArray<_SkwasmUniformVec3Slot>(
+      name,
+      3, // 3 floats per element
+      (components) =>
+          _SkwasmUniformVec3Slot._(components[0], components[1], components[2]), // Create Vec3
+    );
+  }
+
+  @override
+  ui.UniformArray<ui.UniformVec4Slot> getUniformVec4Array(String name) {
+    return _getUniformArray<_SkwasmUniformVec4Slot>(
+      name,
+      4, // 4 floats per element
+      (components) => _SkwasmUniformVec4Slot._(
+        components[0],
+        components[1],
+        components[2],
+        components[3],
+      ), // Create Vec4
     );
   }
 }
@@ -477,3 +540,17 @@
 
   final SkwasmUniformFloatSlot _xSlot, _ySlot, _zSlot, _wSlot;
 }
+
+class _SkwasmUniformArray<T extends ui.UniformType> implements ui.UniformArray<T> {
+  _SkwasmUniformArray._(this._elements);
+
+  @override
+  T operator [](int index) {
+    return _elements[index];
+  }
+
+  @override
+  int get length => _elements.length;
+
+  final List<T> _elements;
+}
diff --git a/engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart b/engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart
index ce3d1f8..0947730 100644
--- a/engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart
+++ b/engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart
@@ -325,6 +325,106 @@
 }
 ''';
 
+// Simple shader with a float[] uniform
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/float_array_uniform.frag
+// For changes, update that file and regenerate using impellerc.
+const String kFloatArraySkSl = r'''
+{
+  "format_version": 1,
+  "sksl": {
+    "entrypoint": "float_array_uniform_fragment_main",
+    "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform float color_array[4];\n\nvec4 fragColor;\n\nvoid FLT_main()\n{\n    fragColor = vec4(color_array[0], color_array[1], color_array[2], color_array[3]);\n}\n\nhalf4 main(float2 iFragCoord)\n{\n      flutter_FragCoord = float4(iFragCoord, 0, 0);\n      FLT_main();\n      return fragColor;\n}\n",
+    "stage": 1,
+    "uniforms": [
+      {
+        "array_elements": 4,
+        "bit_width": 32,
+        "columns": 1,
+        "location": 0,
+        "name": "color_array",
+        "rows": 1,
+        "type": 10
+      }
+    ]
+  }
+}
+''';
+
+// Simple shader with a vec2[] uniform
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec2_array_uniform.frag
+// For changes, update that file and regenerate using impellerc.
+const String kVec2ArraySkSl = r'''
+{
+  "format_version": 1,
+  "sksl": {
+    "entrypoint": "vec2_array_uniform_fragment_main",
+    "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec2 color_array[2];\n\nvec4 fragColor;\n\nvoid FLT_main()\n{\n    fragColor = vec4(color_array[0].x, color_array[0].y, color_array[1].x, color_array[1].y);\n}\n\nhalf4 main(float2 iFragCoord)\n{\n      flutter_FragCoord = float4(iFragCoord, 0, 0);\n      FLT_main();\n      return fragColor;\n}\n",
+    "stage": 1,
+    "uniforms": [
+      {
+        "array_elements": 2,
+        "bit_width": 32,
+        "columns": 1,
+        "location": 0,
+        "name": "color_array",
+        "rows": 2,
+        "type": 10
+      }
+    ]
+  }
+}
+''';
+
+// Simple shader with a vec3[] uniform
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_array_uniform.frag
+// For changes, update that file and regenerate using impellerc.
+const String kVec3ArraySkSl = r'''
+{
+  "format_version": 1,
+  "sksl": {
+    "entrypoint": "vec3_array_uniform_fragment_main",
+    "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec3 color_array[2];\n\nvec4 fragColor;\n\nvoid FLT_main()\n{\n    fragColor = vec4(mix(color_array[0], color_array[1], vec3(0.5)), 1.0);\n}\n\nhalf4 main(float2 iFragCoord)\n{\n      flutter_FragCoord = float4(iFragCoord, 0, 0);\n      FLT_main();\n      return fragColor;\n}\n",
+    "stage": 1,
+    "uniforms": [
+      {
+        "array_elements": 2,
+        "bit_width": 32,
+        "columns": 1,
+        "location": 0,
+        "name": "color_array",
+        "rows": 3,
+        "type": 10
+      }
+    ]
+  }
+}
+''';
+
+// Simple shader with a vec4[] uniform
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec4_array_uniform.frag
+// For changes, update that file and regenerate using impellerc.
+const String kVec4ArraySkSl = r'''
+{
+  "format_version": 1,
+  "sksl": {
+    "entrypoint": "vec4_array_uniform_fragment_main",
+    "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec4 color_array[2];\n\nvec4 fragColor;\n\nvoid FLT_main()\n{\n    fragColor = mix(color_array[0], color_array[1], vec4(0.5));\n}\n\nhalf4 main(float2 iFragCoord)\n{\n      flutter_FragCoord = float4(iFragCoord, 0, 0);\n      FLT_main();\n      return fragColor;\n}\n",
+    "stage": 1,
+    "uniforms": [
+      {
+        "array_elements": 2,
+        "bit_width": 32,
+        "columns": 1,
+        "location": 0,
+        "name": "color_array",
+        "rows": 4,
+        "type": 10
+      }
+    ]
+  }
+}
+''';
+
 Future<void> testMain() async {
   setUpUnitTests(withImplicitView: true, setUpTestViewDimensions: false);
 
@@ -337,6 +437,10 @@
     assetScope.setAsset('texture_shader', ByteData.sublistView(utf8.encode(kTextureShaderSksl)));
     assetScope.setAsset('many_arrays', ByteData.sublistView(utf8.encode(kManyArraysSksl)));
     assetScope.setAsset('ink_sparkle', ByteData.sublistView(utf8.encode(kInkSparkleSksl)));
+    assetScope.setAsset('float_array', ByteData.sublistView(utf8.encode(kFloatArraySkSl)));
+    assetScope.setAsset('vec2_array', ByteData.sublistView(utf8.encode(kVec2ArraySkSl)));
+    assetScope.setAsset('vec3_array', ByteData.sublistView(utf8.encode(kVec3ArraySkSl)));
+    assetScope.setAsset('vec4_array', ByteData.sublistView(utf8.encode(kVec4ArraySkSl)));
   });
 
   tearDown(() {
@@ -427,7 +531,7 @@
     final ui.FragmentProgram program = await renderer.createFragmentProgram('ink_sparkle');
     final ui.FragmentShader shader = program.fragmentShader();
     final ui.UniformFloatSlot slot = shader.getUniformFloat('u_rotation1', 1);
-    expect(slot.shaderIndex, equals(27));
+    expect(slot.shaderIndex, equals(15));
   });
 
   test('getUniformVec2 works with correct datatype', () async {
@@ -447,6 +551,92 @@
     shader.getUniformVec4('u_color').set(0.8, 0.1, 0.3, 1.0);
   });
 
+  test('getUniformFloatArray works with correct datatype', () async {
+    final ui.FragmentProgram program = await renderer.createFragmentProgram('float_array');
+    final ui.FragmentShader shader = program.fragmentShader();
+    final ui.UniformArray<ui.UniformFloatSlot> colorArray = shader.getUniformFloatArray(
+      'color_array',
+    );
+    colorArray[0].set(0.6);
+    colorArray[1].set(0.7);
+    colorArray[2].set(0.8);
+    colorArray[3].set(0.9);
+
+    final recorder = ui.PictureRecorder();
+    final canvas = ui.Canvas(recorder, region);
+    canvas.drawRect(
+      ui.Rect.fromLTRB(0, 0, region.width, region.height),
+      ui.Paint()..shader = shader,
+    );
+
+    await drawPictureUsingCurrentRenderer(recorder.endRecording());
+
+    await matchGoldenFile('uniform_float_array.png', region: region);
+  }, skip: isWimp);
+
+  test('getUniformVec2Array works with correct datatype', () async {
+    final ui.FragmentProgram program = await renderer.createFragmentProgram('vec2_array');
+    final ui.FragmentShader shader = program.fragmentShader();
+    final ui.UniformArray<ui.UniformVec2Slot> colorArray = shader.getUniformVec2Array(
+      'color_array',
+    );
+    colorArray[0].set(0.6, 0.7);
+    colorArray[1].set(0.8, 0.9);
+
+    final recorder = ui.PictureRecorder();
+    final canvas = ui.Canvas(recorder, region);
+    canvas.drawRect(
+      ui.Rect.fromLTRB(0, 0, region.width, region.height),
+      ui.Paint()..shader = shader,
+    );
+
+    await drawPictureUsingCurrentRenderer(recorder.endRecording());
+
+    await matchGoldenFile('uniform_vec2_array.png', region: region);
+  }, skip: isWimp);
+
+  test('getUniformVec3Array works with correct datatype', () async {
+    final ui.FragmentProgram program = await renderer.createFragmentProgram('vec3_array');
+    final ui.FragmentShader shader = program.fragmentShader();
+    final ui.UniformArray<ui.UniformVec3Slot> colorArray = shader.getUniformVec3Array(
+      'color_array',
+    );
+    colorArray[0].set(0.6, 0.7, 0.8);
+    colorArray[1].set(0.9, 1.0, 0.0);
+
+    final recorder = ui.PictureRecorder();
+    final canvas = ui.Canvas(recorder, region);
+    canvas.drawRect(
+      ui.Rect.fromLTRB(0, 0, region.width, region.height),
+      ui.Paint()..shader = shader,
+    );
+
+    await drawPictureUsingCurrentRenderer(recorder.endRecording());
+
+    await matchGoldenFile('uniform_vec3_array.png', region: region);
+  }, skip: isWimp);
+
+  test('getUniformVec4Array works with correct datatype', () async {
+    final ui.FragmentProgram program = await renderer.createFragmentProgram('vec4_array');
+    final ui.FragmentShader shader = program.fragmentShader();
+    final ui.UniformArray<ui.UniformVec4Slot> colorArray = shader.getUniformVec4Array(
+      'color_array',
+    );
+    colorArray[0].set(0.6, 0.7, 0.8, 0.9);
+    colorArray[1].set(0.9, 0.8, 0.7, 0.6);
+
+    final recorder = ui.PictureRecorder();
+    final canvas = ui.Canvas(recorder, region);
+    canvas.drawRect(
+      ui.Rect.fromLTRB(0, 0, region.width, region.height),
+      ui.Paint()..shader = shader,
+    );
+
+    await drawPictureUsingCurrentRenderer(recorder.endRecording());
+
+    await matchGoldenFile('uniform_vec4_array.png', region: region);
+  }, skip: isWimp);
+
   group('Uniform by-name accessors throw errors with incorrect datatypes.', () {
     late ui.FragmentProgram program;
     late ui.FragmentShader shader;
diff --git a/engine/src/flutter/testing/dart/fragment_shader_test.dart b/engine/src/flutter/testing/dart/fragment_shader_test.dart
index e0d7d6c..da690ae 100644
--- a/engine/src/flutter/testing/dart/fragment_shader_test.dart
+++ b/engine/src/flutter/testing/dart/fragment_shader_test.dart
@@ -52,6 +52,224 @@
     expect(identical(programA, programB), true);
   });
 
+  group('FragmentProgram getUniform*', () {
+    late FragmentShader shader;
+
+    setUpAll(() async {
+      final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr');
+      shader = program.fragmentShader();
+    });
+
+    _runSkiaTest('FragmentProgram uniform info', () async {
+      final List<UniformFloatSlot> slots = [
+        shader.getUniformFloat('iFloatUniform'),
+        shader.getUniformFloat('iVec2Uniform', 0),
+        shader.getUniformFloat('iVec2Uniform', 1),
+        shader.getUniformFloat('iMat2Uniform', 0),
+        shader.getUniformFloat('iMat2Uniform', 1),
+        shader.getUniformFloat('iMat2Uniform', 2),
+        shader.getUniformFloat('iMat2Uniform', 3),
+      ];
+      for (var i = 0; i < slots.length; ++i) {
+        expect(slots[i].shaderIndex, equals(i));
+      }
+    });
+
+    test('FragmentProgram getUniformFloat unknown', () async {
+      expect(
+        () {
+          shader.getUniformFloat('unknown');
+        },
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('No uniform named "unknown"'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformFloat offset overflow', () async {
+      expect(
+        () => shader.getUniformFloat('iVec2Uniform', 2),
+        throwsA(
+          isA<IndexError>().having(
+            (e) => e.message,
+            'message',
+            contains('Index `2` out of bounds for `iVec2Uniform`.'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformFloat offset underflow', () async {
+      expect(
+        () => shader.getUniformFloat('iVec2Uniform', -1),
+        throwsA(
+          isA<IndexError>().having(
+            (e) => e.message,
+            'message',
+            contains('Index `-1` out of bounds for `iVec2Uniform`.'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformVec2', () async {
+      final UniformVec2Slot slot = shader.getUniformVec2('iVec2Uniform');
+      slot.set(6.0, 7.0);
+    });
+
+    _runSkiaTest('FragmentProgram getUniformVec2 wrong size', () async {
+      expect(
+        () => shader.getUniformVec2('iVec3Uniform'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('`iVec3Uniform` has size 3, not size 2.'),
+          ),
+        ),
+      );
+      expect(
+        () => shader.getUniformVec2('iFloatUniform'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('`iFloatUniform` has size 1, not size 2.'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformVec3', () async {
+      final UniformVec3Slot slot = shader.getUniformVec3('iVec3Uniform');
+      slot.set(0.8, 0.1, 0.3);
+    });
+
+    _runSkiaTest('FragmentProgram getUniformVec3 wrong size', () async {
+      expect(
+        () => shader.getUniformVec3('iVec2Uniform'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('`iVec2Uniform` has size 2, not size 3.'),
+          ),
+        ),
+      );
+      expect(
+        () => shader.getUniformVec3('iVec4Uniform'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('`iVec4Uniform` has size 4, not size 3.'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformVec4', () async {
+      final UniformVec4Slot slot = shader.getUniformVec4('iVec4Uniform');
+      slot.set(11.0, 22.0, 19.0, 96.0);
+    });
+
+    _runSkiaTest('FragmentProgram getUniformVec4 wrong size', () async {
+      expect(
+        () => shader.getUniformVec4('iVec3Uniform'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('`iVec3Uniform` has size 3, not size 4.'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformArray float', () async {
+      final UniformArray<UniformFloatSlot> slots = shader.getUniformFloatArray(
+        'iFloatArrayUniform',
+      );
+      expect(slots.length, 10);
+      slots[0].set(1.0);
+      slots[1].set(1.0);
+    });
+
+    _runSkiaTest('FragmentProgram getUniformArray not found', () async {
+      expect(
+        () => shader.getUniformFloatArray('unknown'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('No uniform named "unknown".'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformArrayVec2', () async {
+      final UniformArray<UniformVec2Slot> slots = shader.getUniformVec2Array('iVec2ArrayUniform');
+      expect(slots.length, 3);
+      slots[0].set(1.0, 1.0);
+    });
+
+    _runSkiaTest('FragmentProgram getUniformArrayVec2 wrong type', () async {
+      expect(
+        () => shader.getUniformVec2Array('iVec3ArrayUniform'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('Uniform size (9) for "iVec3ArrayUniform" is not a multiple of 2.'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformArrayVec3', () async {
+      final UniformArray<UniformVec3Slot> slots = shader.getUniformVec3Array('iVec3ArrayUniform');
+      expect(slots.length, 3);
+      slots[0].set(1.0, 1.0, 1.0);
+    });
+
+    _runSkiaTest('FragmentProgram getUniformArrayVec3 wrong type', () async {
+      expect(
+        () => shader.getUniformVec3Array('iFloatArrayUniform'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('Uniform size (10) for "iFloatArrayUniform" is not a multiple of 3.'),
+          ),
+        ),
+      );
+    });
+
+    _runSkiaTest('FragmentProgram getUniformArrayVec4', () async {
+      final UniformArray<UniformVec4Slot> slots = shader.getUniformVec4Array('iVec4ArrayUniform');
+      expect(slots.length, 3);
+      slots[0].set(1.0, 1.0, 1.0, 1.0);
+    });
+
+    _runSkiaTest('FragmentProgram getUniformArrayVec4 wrong type', () async {
+      expect(
+        () => shader.getUniformVec4Array('iFloatArrayUniform'),
+        throwsA(
+          isA<ArgumentError>().having(
+            (e) => e.message,
+            'message',
+            contains('Uniform size (10) for "iFloatArrayUniform" is not a multiple of 4.'),
+          ),
+        ),
+      );
+    });
+  });
+
   test('FragmentProgram getImageSampler', () async {
     final FragmentProgram program = await FragmentProgram.fromAsset('uniform_ordering.frag.iplr');
     final FragmentShader shader = program.fragmentShader();