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();