Update web ui fragment shader tests (#181877)
Adds a bunch of tests for uniform setting functionality for custom
fragment shaders on the web.
Deletes redundant tests.
Fixes a discovered issue in the uniform offset calculation. We were
previously using the `location`, which is the integer offset of the
uniform, not the offset in floats.
diff --git a/engine/src/flutter/impeller/compiler/README.md b/engine/src/flutter/impeller/compiler/README.md
index cf44365..746f036 100644
--- a/engine/src/flutter/impeller/compiler/README.md
+++ b/engine/src/flutter/impeller/compiler/README.md
@@ -5,3 +5,10 @@
shaders and generates libraries suitable for consumption by an Impeller backend.
Along with said libraries, the reflector generates code and meta-data to
construct rendering and compute pipelines at runtime.
+
+# Invocation
+
+To invoke `impellerc` by itself, [compile the engine](https://github.com/flutter/flutter/blob/master/docs/engine/contributing/Compiling-the-engine.md) and run the binary via
+```
+`find engine/src/out/host_debug_unopt_arm64 -name impellerc` --input=path/to/shader.frag --input-type=frag --entry-point=main`
+```
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 f0a5726..7b28bb5 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
@@ -423,7 +423,7 @@
IndexError.check(index, info.floatCount, message: 'Index `$index` out of bounds for `$name`.');
- return CkUniformFloatSlot._(this, index, name, info.location + index);
+ return CkUniformFloatSlot._(this, index, name, info.floatOffset + index);
}
@override
@@ -461,7 +461,7 @@
final elements = List<T>.generate(numElements, (i) {
final slots = List<CkUniformFloatSlot>.generate(
info.floatCount,
- (j) => CkUniformFloatSlot._(this, j, name, info.location + i * elementSize + j),
+ (j) => CkUniformFloatSlot._(this, j, name, info.floatOffset + i * elementSize + j),
);
return elementFactory(slots);
});
@@ -554,7 +554,7 @@
return List<CkUniformFloatSlot>.generate(
size,
- (i) => CkUniformFloatSlot._(this, i, name, info.location + i),
+ (i) => CkUniformFloatSlot._(this, i, name, info.floatOffset + i),
);
}
}
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/shader_data.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/shader_data.dart
index 371bb6b..9b645f6 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/shader_data.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/shader_data.dart
@@ -34,6 +34,7 @@
var textureCount = 0;
var floatCount = 0;
+ var floatOffset = 0;
for (var i = 0; i < rawUniforms.length; i += 1) {
final Object? rawUniformData = rawUniforms[i];
if (rawUniformData is! Map<String, Object?>) {
@@ -78,7 +79,9 @@
location: location,
type: type,
floatCount: uniformFloatCount,
+ floatOffset: floatOffset,
);
+ floatOffset += uniformFloatCount;
}
return ShaderData(
source: source,
@@ -100,18 +103,21 @@
required this.location,
required this.type,
required this.floatCount,
+ required this.floatOffset,
});
final String name;
final UniformType type;
final int location;
final int floatCount;
+ final int floatOffset;
static const UniformData empty = UniformData(
name: '',
location: -1,
type: UniformType.Float,
floatCount: -1,
+ floatOffset: -1,
);
}
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 81e5cd54..5c39427 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
@@ -376,7 +376,7 @@
IndexError.check(index, info.floatCount, message: 'Index `$index` out of bounds for `$name`.');
- return SkwasmUniformFloatSlot._(this, index, name, info.location + index);
+ return SkwasmUniformFloatSlot._(this, index, name, info.floatOffset + index);
}
@override
@@ -441,7 +441,7 @@
return List<SkwasmUniformFloatSlot>.generate(
size,
- (i) => SkwasmUniformFloatSlot._(this, i, name, info.location + i),
+ (i) => SkwasmUniformFloatSlot._(this, i, name, info.floatOffset + i),
);
}
@@ -462,7 +462,7 @@
final elements = List<T>.generate(numElements, (i) {
final slots = List<SkwasmUniformFloatSlot>.generate(
info.floatCount,
- (j) => SkwasmUniformFloatSlot._(this, j, name, info.location + i * elementSize + j),
+ (j) => SkwasmUniformFloatSlot._(this, j, name, info.floatOffset + i * elementSize + j),
);
return elementFactory(slots);
});
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 0947730..852ef7c 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
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:convert';
+import 'dart:math';
import 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
@@ -325,12 +326,112 @@
}
''';
+// Simple shader with a float uniform
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/uniforms/float_uniform.frag
+// For changes, update that file and regenerate using impellerc.
+const String kFloatSkSl = r'''
+{
+ "format_version": 2,
+ "sksl": {
+ "entrypoint": "float_uniform_fragment_main",
+ "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform float color_r;\n\nvec4 fragColor;\n\nvoid FLT_main()\n{\n fragColor = vec4(color_r, 0.0, 0.0, 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": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 0,
+ "name": "color_r",
+ "rows": 1,
+ "type": 10
+ }
+ ]
+ }
+}
+''';
+
+// Simple shader with a vec2 uniform
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec2_uniform.frag
+// For changes, update that file and regenerate using impellerc.
+const String kVec2SkSl = r'''
+{
+ "format_version": 2,
+ "sksl": {
+ "entrypoint": "vec2_uniform_fragment_main",
+ "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec2 color_rg;\n\nvec4 fragColor;\n\nvoid FLT_main()\n{\n fragColor = vec4(color_rg, 0.0, 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": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 0,
+ "name": "color_rg",
+ "rows": 2,
+ "type": 10
+ }
+ ]
+ }
+}
+''';
+
+// Simple shader with a vec3 uniform
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec3_uniform.frag
+// For changes, update that file and regenerate using impellerc.
+const String kVec3SkSl = r'''
+{
+ "format_version": 2,
+ "sksl": {
+ "entrypoint": "vec3_uniform_fragment_main",
+ "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec3 color_rgb;\n\nvec4 fragColor;\n\nvoid FLT_main()\n{\n fragColor = vec4(color_rgb, 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": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 0,
+ "name": "color_rgb",
+ "rows": 3,
+ "type": 10
+ }
+ ]
+ }
+}
+''';
+
+// Simple shader with a vec4 uniform
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec4_uniform.frag
+// For changes, update that file and regenerate using impellerc.
+const String kVec4SkSl = r'''
+{
+ "format_version": 2,
+ "sksl": {
+ "entrypoint": "vec4_uniform_fragment_main",
+ "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec4 color_rgba;\n\nvec4 fragColor;\n\nvoid FLT_main()\n{\n fragColor = color_rgba;\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": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 0,
+ "name": "color_rgba",
+ "rows": 4,
+ "type": 10
+ }
+ ]
+ }
+}
+''';
+
// Simple shader with a float[] uniform
-// Generated from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/float_array_uniform.frag
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/uniforms/float_array_uniform.frag
// For changes, update that file and regenerate using impellerc.
const String kFloatArraySkSl = r'''
{
- "format_version": 1,
+ "format_version": 2,
"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",
@@ -351,11 +452,11 @@
''';
// Simple shader with a vec2[] uniform
-// Generated from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec2_array_uniform.frag
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec2_array_uniform.frag
// For changes, update that file and regenerate using impellerc.
const String kVec2ArraySkSl = r'''
{
- "format_version": 1,
+ "format_version": 2,
"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",
@@ -376,21 +477,30 @@
''';
// Simple shader with a vec3[] uniform
-// Generated from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_array_uniform.frag
+// Generated from engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec3_array_uniform.frag
// For changes, update that file and regenerate using impellerc.
const String kVec3ArraySkSl = r'''
{
- "format_version": 1,
+ "format_version": 2,
"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",
+ "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec2 u_size;\nuniform vec3 color_array[2];\n\nvec4 fragColor;\n\nvec2 FLT_flutter_local_FlutterFragCoord()\n{\n return flutter_FragCoord.xy;\n}\n\nvoid FLT_main()\n{\n vec2 uv = FLT_flutter_local_FlutterFragCoord() / u_size;\n if (uv.x < 0.5)\n {\n fragColor = vec4(color_array[0], 1.0);\n }\n else\n {\n fragColor = vec4(color_array[1], 1.0);\n }\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": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 0,
+ "name": "u_size",
+ "rows": 2,
+ "type": 10
+ },
+ {
"array_elements": 2,
"bit_width": 32,
"columns": 1,
- "location": 0,
+ "location": 1,
"name": "color_array",
"rows": 3,
"type": 10
@@ -405,17 +515,26 @@
// For changes, update that file and regenerate using impellerc.
const String kVec4ArraySkSl = r'''
{
- "format_version": 1,
+ "format_version": 2,
"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",
+ "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec2 u_size;\nuniform vec4 color_array[2];\n\nvec4 fragColor;\n\nvec2 FLT_flutter_local_FlutterFragCoord()\n{\n return flutter_FragCoord.xy;\n}\n\nvoid FLT_main()\n{\n vec2 uv = FLT_flutter_local_FlutterFragCoord() / u_size;\n if (uv.x < 0.5)\n {\n fragColor = color_array[0];\n }\n else\n {\n fragColor = color_array[1];\n }\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": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 0,
+ "name": "u_size",
+ "rows": 2,
+ "type": 10
+ },
+ {
"array_elements": 2,
"bit_width": 32,
"columns": 1,
- "location": 0,
+ "location": 1,
"name": "color_array",
"rows": 4,
"type": 10
@@ -425,25 +544,115 @@
}
''';
+const String kAllUniformsSkSl = r'''
+{
+ "format_version": 2,
+ "sksl": {
+ "entrypoint": "all_uniforms_fragment_main",
+ "shader": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform float uFloat;\nuniform vec2 uVec2;\nuniform vec3 uVec3;\nuniform vec4 uVec4;\nuniform float uFloatArray[10];\nuniform vec2 uVec2Array[10];\nuniform vec3 uVec3Array[10];\nuniform vec4 uVec4Array[10];\n\nvec4 fragColor;\n\nfloat N_COLOR_VALUES;\n\nvec2 FLT_flutter_local_FlutterFragCoord()\n{\n return flutter_FragCoord.xy;\n}\n\nvoid FLT_main()\n{\n N_COLOR_VALUES = 44.0;\n float u = FLT_flutter_local_FlutterFragCoord().x / N_COLOR_VALUES;\n float increment = 1.0 / N_COLOR_VALUES;\n float offset = increment;\n if (u < offset)\n {\n fragColor = vec4(uFloat, 0.0, 0.0, 1.0);\n return;\n }\n offset += increment;\n if (u < offset)\n {\n fragColor = vec4(uVec2, 0.0, 1.0);\n return;\n }\n offset += increment;\n if (u < offset)\n {\n fragColor = vec4(uVec3, 1.0);\n return;\n }\n offset += increment;\n if (u < offset)\n {\n fragColor = uVec4;\n return;\n }\n offset += increment;\n for (int i = 0; i < 10; i++)\n {\n if (u < offset)\n {\n fragColor = vec4(uFloatArray[i], 0.0, 0.0, 1.0);\n return;\n }\n offset += increment;\n }\n for (int i_1 = 0; i_1 < 10; i_1++)\n {\n if (u < offset)\n {\n fragColor = vec4(uVec2Array[i_1], 0.0, 1.0);\n return;\n }\n offset += increment;\n }\n for (int i_2 = 0; i_2 < 10; i_2++)\n {\n if (u < offset)\n {\n fragColor = vec4(uVec3Array[i_2], 1.0);\n return;\n }\n offset += increment;\n }\n for (int i_3 = 0; i_3 < 10; i_3++)\n {\n if (u < offset)\n {\n fragColor = uVec4Array[i_3];\n return;\n }\n offset += increment;\n }\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": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 0,
+ "name": "uFloat",
+ "rows": 1,
+ "type": 10
+ },
+ {
+ "array_elements": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 1,
+ "name": "uVec2",
+ "rows": 2,
+ "type": 10
+ },
+ {
+ "array_elements": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 2,
+ "name": "uVec3",
+ "rows": 3,
+ "type": 10
+ },
+ {
+ "array_elements": 0,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 3,
+ "name": "uVec4",
+ "rows": 4,
+ "type": 10
+ },
+ {
+ "array_elements": 10,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 4,
+ "name": "uFloatArray",
+ "rows": 1,
+ "type": 10
+ },
+ {
+ "array_elements": 10,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 14,
+ "name": "uVec2Array",
+ "rows": 2,
+ "type": 10
+ },
+ {
+ "array_elements": 10,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 24,
+ "name": "uVec3Array",
+ "rows": 3,
+ "type": 10
+ },
+ {
+ "array_elements": 10,
+ "bit_width": 32,
+ "columns": 1,
+ "location": 34,
+ "name": "uVec4Array",
+ "rows": 4,
+ "type": 10
+ }
+ ]
+ }
+}
+''';
+
Future<void> testMain() async {
setUpUnitTests(withImplicitView: true, setUpTestViewDimensions: false);
const region = ui.Rect.fromLTWH(0, 0, 300, 300);
late FakeAssetScope assetScope;
- setUp(() {
+ setUpAll(() {
assetScope = fakeAssetManager.pushAssetScope();
assetScope.setAsset('voronoi_shader', ByteData.sublistView(utf8.encode(kVoronoiShaderSksl)));
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)));
+ assetScope.setAsset('float_uniform', ByteData.sublistView(utf8.encode(kFloatSkSl)));
+ assetScope.setAsset('vec2_uniform', ByteData.sublistView(utf8.encode(kVec2SkSl)));
+ assetScope.setAsset('vec3_uniform', ByteData.sublistView(utf8.encode(kVec3SkSl)));
+ assetScope.setAsset('vec4_uniform', ByteData.sublistView(utf8.encode(kVec4SkSl)));
+ assetScope.setAsset('float_array_uniform', ByteData.sublistView(utf8.encode(kFloatArraySkSl)));
+ assetScope.setAsset('vec2_array_uniform', ByteData.sublistView(utf8.encode(kVec2ArraySkSl)));
+ assetScope.setAsset('vec3_array_uniform', ByteData.sublistView(utf8.encode(kVec3ArraySkSl)));
+ assetScope.setAsset('vec4_array_uniform', ByteData.sublistView(utf8.encode(kVec4ArraySkSl)));
+ assetScope.setAsset('all_uniforms', ByteData.sublistView(utf8.encode(kAllUniformsSkSl)));
});
- tearDown(() {
+ tearDownAll(() {
fakeAssetManager.popAssetScope(assetScope);
});
@@ -527,160 +736,470 @@
await expectLater(ui.FragmentProgram.fromAsset('voronoi shader'), completes);
}, skip: isWimp); // https://github.com/flutter/flutter/issues/175431
- test('getUniformFloat works with correct datatype', () async {
- 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(15));
- });
-
- test('getUniformVec2 works with correct datatype', () async {
- final ui.FragmentProgram program = await renderer.createFragmentProgram('many_arrays');
- final ui.FragmentShader shader = program.fragmentShader();
- shader.getUniformVec2('uSize').set(6.0, 7.0);
- });
- test('getUniformVec3 works with correct datatype', () async {
- final ui.FragmentProgram program = await renderer.createFragmentProgram('many_arrays');
- final ui.FragmentShader shader = program.fragmentShader();
- shader.getUniformVec3('uLoneVector').set(11.0, 22.0, 19.96);
- });
-
- test('getUniformVec4 works with correct datatype', () async {
- final ui.FragmentProgram program = await renderer.createFragmentProgram('ink_sparkle');
- final ui.FragmentShader shader = program.fragmentShader();
- 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;
- setUp(() async {
- program = await renderer.createFragmentProgram('many_arrays');
- shader = program.fragmentShader();
+ group('fragmentShader uniforms', () {
+ late Map<Type, ui.FragmentShader> shaderMap;
+ setUpAll(() async {
+ shaderMap = {
+ ui.UniformFloatSlot: (await renderer.createFragmentProgram(
+ 'float_uniform',
+ )).fragmentShader(),
+ ui.UniformVec2Slot: (await renderer.createFragmentProgram('vec2_uniform')).fragmentShader(),
+ ui.UniformVec3Slot: (await renderer.createFragmentProgram('vec3_uniform')).fragmentShader(),
+ ui.UniformVec4Slot: (await renderer.createFragmentProgram('vec4_uniform')).fragmentShader(),
+ ui.UniformArray<ui.UniformFloatSlot>: (await renderer.createFragmentProgram(
+ 'float_array_uniform',
+ )).fragmentShader(),
+ ui.UniformArray<ui.UniformVec2Slot>: (await renderer.createFragmentProgram(
+ 'vec2_array_uniform',
+ )).fragmentShader(),
+ ui.UniformArray<ui.UniformVec3Slot>: (await renderer.createFragmentProgram(
+ 'vec3_array_uniform',
+ )).fragmentShader(),
+ ui.UniformArray<ui.UniformVec4Slot>: (await renderer.createFragmentProgram(
+ 'vec4_array_uniform',
+ )).fragmentShader(),
+ };
});
- test('getUniformVec2', () {
- expect(
- () => shader.getUniformVec2('uLoneMatrix'),
- throwsA(
- isA<ArgumentError>().having(
- (e) => e.message,
- 'message',
- 'Uniform `uLoneMatrix` has size 16, not size 2.',
- ),
- ),
- );
+
+ group('float', () {
+ group('float', () {
+ test('set using setUniformFloat', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformFloatSlot]!;
+ const color = ui.Color.fromARGB(255, 255, 0, 0);
+ shader.setFloat(0, color.r);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('set using getUniformFloat', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformFloatSlot]!;
+ const color = ui.Color.fromARGB(255, 50, 0, 0);
+ shader.getUniformFloat('color_r').set(color.r);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('getUniformFloat offset overflow', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformFloatSlot]!;
+ expect(
+ () => shader.getUniformFloat('color_r', 2),
+ throwsA(
+ isA<IndexError>().having(
+ (e) => e.message,
+ 'message',
+ contains('Index `2` out of bounds for `color_r`.'),
+ ),
+ ),
+ );
+ });
+
+ test('getUniformFloat offset underflow', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformFloatSlot]!;
+ expect(
+ () => shader.getUniformFloat('color_r', -1),
+ throwsA(
+ isA<IndexError>().having(
+ (e) => e.message,
+ 'message',
+ contains('Index `-1` out of bounds for `color_r`.'),
+ ),
+ ),
+ );
+ });
+ });
+ group('vec2', () {
+ test('set using setFloat', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec2Slot]!;
+ const color = ui.Color.fromARGB(255, 255, 255, 0);
+ shader.setFloat(0, color.r);
+ shader.setFloat(1, color.g);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('set using getUniformVec2', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec2Slot]!;
+ const color = ui.Color.fromARGB(255, 50, 50, 0);
+ shader.getUniformVec2('color_rg').set(color.r, color.g);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('wrong datatype', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec3Slot]!;
+ expect(
+ () => shader.getUniformVec2('color_rgb'),
+ throwsA(
+ isA<ArgumentError>().having(
+ (e) => e.message,
+ 'message',
+ contains('`color_rgb` has size 3, not size 2.'),
+ ),
+ ),
+ );
+ });
+ });
+ group('vec3', () {
+ test('set using setFloat', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec3Slot]!;
+ const color = ui.Color.fromARGB(255, 67, 42, 12);
+ shader.setFloat(0, color.r);
+ shader.setFloat(1, color.g);
+ shader.setFloat(2, color.b);
+ // Note: The original test also called getUniformVec3 after setFloat.
+ // Assuming this was intentional to test idempotency or a specific interaction.
+ shader.getUniformVec3('color_rgb').set(color.r, color.g, color.b);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('set using getUniformVec3', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec3Slot]!;
+ const color = ui.Color.fromARGB(255, 42, 67, 12);
+ shader.getUniformVec3('color_rgb').set(color.r, color.g, color.b);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('wrong datatype', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec2Slot]!;
+ expect(
+ () => shader.getUniformVec3('color_rg'),
+ throwsA(
+ isA<ArgumentError>().having(
+ (e) => e.message,
+ 'message',
+ contains('`color_rg` has size 2, not size 3.'),
+ ),
+ ),
+ );
+ });
+ });
+
+ group('vec4', () {
+ test('set using setFloat', () async {
+ const color = ui.Color.fromARGB(255, 67, 42, 12);
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec4Slot]!;
+ shader.setFloat(0, color.r);
+ shader.setFloat(1, color.g);
+ shader.setFloat(2, color.b);
+ shader.setFloat(3, color.a);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('set using getUniformVec4', () async {
+ const color = ui.Color.fromARGB(255, 12, 37, 27);
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec4Slot]!;
+ shader.getUniformVec4('color_rgba').set(color.r, color.g, color.b, color.a);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('wrong datatype', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec3Slot]!;
+ expect(
+ () => shader.getUniformVec4('color_rgb'),
+ throwsA(
+ isA<ArgumentError>().having(
+ (e) => e.message,
+ 'message',
+ contains('`color_rgb` has size 3, not size 4.'),
+ ),
+ ),
+ );
+ });
+ });
+
+ group('float array', () {
+ test('set using setFloat', () async {
+ const color = ui.Color.fromARGB(255, 11, 22, 96);
+ final ui.FragmentShader shader = shaderMap[ui.UniformArray<ui.UniformFloatSlot>]!;
+ shader.setFloat(0, color.r);
+ shader.setFloat(1, color.g);
+ shader.setFloat(2, color.b);
+ shader.setFloat(3, color.a);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('set using getUniformFloatArray', () async {
+ const color = ui.Color.fromARGB(255, 96, 11, 22);
+ final ui.FragmentShader shader = shaderMap[ui.UniformArray<ui.UniformFloatSlot>]!;
+ final ui.UniformArray<ui.UniformFloatSlot> colorRgba = shader.getUniformFloatArray(
+ 'color_array',
+ );
+ colorRgba[0].set(color.r);
+ colorRgba[1].set(color.g);
+ colorRgba[2].set(color.b);
+ colorRgba[3].set(color.a);
+ await _expectShaderRendersColor(shader, color);
+ });
+ });
+
+ group('vec2 array', () {
+ test('set using setFloat', () async {
+ const color = ui.Color.fromARGB(255, 67, 42, 12);
+ final ui.FragmentShader shader = shaderMap[ui.UniformArray<ui.UniformVec2Slot>]!;
+ shader.setFloat(0, color.r);
+ shader.setFloat(1, color.g);
+ shader.setFloat(2, color.b);
+ shader.setFloat(3, color.a);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('set using getUniformVec2Array', () async {
+ const color = ui.Color.fromARGB(255, 1, 73, 26);
+ final ui.FragmentShader shader = shaderMap[ui.UniformArray<ui.UniformVec2Slot>]!;
+ final ui.UniformArray<ui.UniformVec2Slot> colorRgba = shader.getUniformVec2Array(
+ 'color_array',
+ );
+ colorRgba[0].set(color.r, color.g);
+ colorRgba[1].set(color.b, color.a);
+ await _expectShaderRendersColor(shader, color);
+ });
+
+ test('wrong datatype', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec3Slot]!;
+ expect(
+ () => shader.getUniformVec2Array('color_rgb'),
+ throwsA(
+ isA<ArgumentError>().having(
+ (e) => e.message,
+ 'message',
+ contains('Uniform size (3) for "color_rgb" is not a multiple of 2.'),
+ ),
+ ),
+ );
+ });
+ });
+
+ group('vec3 array', () {
+ test('set using setFloat', () async {
+ const cpuColors = [
+ ui.Color.fromARGB(255, 67, 42, 12),
+ ui.Color.fromARGB(255, 11, 22, 96),
+ ];
+ final ui.FragmentShader shader = shaderMap[ui.UniformArray<ui.UniformVec3Slot>]!;
+ shader.setFloat(0, 2);
+ shader.setFloat(1, 2);
+ shader.setFloat(2, cpuColors[0].r);
+ shader.setFloat(3, cpuColors[0].g);
+ shader.setFloat(4, cpuColors[0].b);
+ shader.setFloat(5, cpuColors[1].r);
+ shader.setFloat(6, cpuColors[1].g);
+ shader.setFloat(7, cpuColors[1].b);
+ await _expectShaderRendersBarcode(shader, cpuColors);
+ });
+
+ test('set using getUniformVec3Array', () async {
+ const cpuColors = [
+ ui.Color.fromARGB(255, 11, 22, 96),
+ ui.Color.fromARGB(255, 67, 42, 12),
+ ];
+ final ui.FragmentShader shader = shaderMap[ui.UniformArray<ui.UniformVec3Slot>]!;
+ shader.getUniformVec2('u_size').set(2, 2);
+ final ui.UniformArray<ui.UniformVec3Slot> gpuColors = shader.getUniformVec3Array(
+ 'color_array',
+ );
+ gpuColors[0].set(cpuColors[0].r, cpuColors[0].g, cpuColors[0].b);
+ gpuColors[1].set(cpuColors[1].r, cpuColors[1].g, cpuColors[1].b);
+ await _expectShaderRendersBarcode(shader, cpuColors);
+ });
+
+ test('wrong datatype', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec4Slot]!;
+ expect(
+ () => shader.getUniformVec3Array('color_rgba'),
+ throwsA(
+ isA<ArgumentError>().having(
+ (e) => e.message,
+ 'message',
+ contains('Uniform size (4) for "color_rgba" is not a multiple of 3.'),
+ ),
+ ),
+ );
+ });
+ });
+
+ group('vec4 array', () {
+ test('set using setFloat', () async {
+ const cpuColors = [
+ ui.Color.fromARGB(255, 67, 42, 12),
+ ui.Color.fromARGB(255, 11, 22, 96),
+ ];
+ final ui.FragmentShader shader = shaderMap[ui.UniformArray<ui.UniformVec4Slot>]!;
+ // 'u_size'
+ shader.setFloat(0, 2);
+ shader.setFloat(1, 2);
+ shader.setFloat(2, cpuColors[0].r);
+ shader.setFloat(3, cpuColors[0].g);
+ shader.setFloat(4, cpuColors[0].b);
+ shader.setFloat(5, cpuColors[0].a);
+ shader.setFloat(6, cpuColors[1].r);
+ shader.setFloat(7, cpuColors[1].g);
+ shader.setFloat(8, cpuColors[1].b);
+ shader.setFloat(9, cpuColors[1].a);
+ await _expectShaderRendersBarcode(shader, cpuColors);
+ });
+
+ test('set using getUniformVec4Array', () async {
+ const cpuColors = [
+ ui.Color.fromARGB(255, 11, 22, 96),
+ ui.Color.fromARGB(255, 67, 42, 12),
+ ];
+ final ui.FragmentShader shader = shaderMap[ui.UniformArray<ui.UniformVec4Slot>]!;
+ shader.getUniformVec2('u_size').set(2, 2);
+ final ui.UniformArray<ui.UniformVec4Slot> colors = shader.getUniformVec4Array(
+ 'color_array',
+ );
+ colors[0].set(cpuColors[0].r, cpuColors[0].g, cpuColors[0].b, cpuColors[0].a);
+ colors[1].set(cpuColors[1].r, cpuColors[1].g, cpuColors[1].b, cpuColors[1].a);
+ await _expectShaderRendersBarcode(shader, cpuColors);
+ });
+
+ test('wrong datatype', () async {
+ final ui.FragmentShader shader = shaderMap[ui.UniformVec3Slot]!;
+ expect(
+ () => shader.getUniformVec4Array('color_rgb'),
+ throwsA(
+ isA<ArgumentError>().having(
+ (e) => e.message,
+ 'message',
+ contains('Uniform size (3) for "color_rgb" is not a multiple of 4.'),
+ ),
+ ),
+ );
+ });
+ });
+
+ group('all uniforms', () {
+ late ui.FragmentProgram program;
+ late List<ui.Color> cpuColors;
+ final random = Random(1337);
+ setUpAll(() async {
+ program = await ui.FragmentProgram.fromAsset('all_uniforms');
+ });
+
+ setUp(() async {
+ cpuColors = List<ui.Color>.empty(growable: true);
+ // uFloat
+ cpuColors.add(ui.Color.fromARGB(255, random.nextInt(255), 0, 0));
+ // uVec2
+ cpuColors.add(ui.Color.fromARGB(255, random.nextInt(255), random.nextInt(255), 0));
+ // uVec3
+ cpuColors.add(
+ ui.Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)),
+ );
+ // uVec4
+ cpuColors.add(
+ ui.Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)),
+ );
+ for (var i = 0; i < 10; ++i) {
+ cpuColors.add(ui.Color.fromARGB(255, random.nextInt(255), 0, 0));
+ }
+
+ for (var i = 0; i < 10; ++i) {
+ cpuColors.add(ui.Color.fromARGB(255, random.nextInt(255), random.nextInt(255), 0));
+ }
+
+ for (var i = 0; i < 10; ++i) {
+ cpuColors.add(
+ ui.Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)),
+ );
+ }
+ for (var i = 0; i < 10; ++i) {
+ cpuColors.add(
+ ui.Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)),
+ );
+ }
+ });
+
+ test('set using setFloat', () async {
+ final ui.FragmentShader shader = program.fragmentShader();
+ // uFloat
+ shader.setFloat(0, cpuColors[0].r);
+ //uVec2
+ shader.setFloat(1, cpuColors[1].r);
+ shader.setFloat(2, cpuColors[1].g);
+ //uVec3
+ shader.setFloat(3, cpuColors[2].r);
+ shader.setFloat(4, cpuColors[2].g);
+ shader.setFloat(5, cpuColors[2].b);
+ //uVec4
+ shader.setFloat(6, cpuColors[3].r);
+ shader.setFloat(7, cpuColors[3].g);
+ shader.setFloat(8, cpuColors[3].b);
+ shader.setFloat(9, cpuColors[3].a);
+
+ var shaderOffset = 10;
+ var colorOffset = 4;
+
+ for (var i = 0; i < 10; ++i) {
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset++].r);
+ }
+ for (var i = 0; i < 10; ++i) {
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset].r);
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset++].g);
+ }
+ for (var i = 0; i < 10; ++i) {
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset].r);
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset].g);
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset++].b);
+ }
+ for (var i = 0; i < 10; ++i) {
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset].r);
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset].g);
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset].b);
+ shader.setFloat(shaderOffset++, cpuColors[colorOffset++].a);
+ }
+ await _expectShaderRendersBarcode(shader, cpuColors);
+ });
+
+ test('set using getUniform*', () async {
+ final ui.FragmentShader shader = program.fragmentShader();
+ shader.getUniformFloat('uFloat').set(cpuColors[0].r);
+ shader.getUniformVec2('uVec2').set(cpuColors[1].r, cpuColors[1].g);
+ shader.getUniformVec3('uVec3').set(cpuColors[2].r, cpuColors[2].g, cpuColors[2].b);
+ shader
+ .getUniformVec4('uVec4')
+ .set(cpuColors[3].r, cpuColors[3].g, cpuColors[3].b, cpuColors[3].a);
+
+ final ui.UniformArray<ui.UniformFloatSlot> floatArray = shader.getUniformFloatArray(
+ 'uFloatArray',
+ );
+ final ui.UniformArray<ui.UniformVec2Slot> vec2Array = shader.getUniformVec2Array(
+ 'uVec2Array',
+ );
+ final ui.UniformArray<ui.UniformVec3Slot> vec3Array = shader.getUniformVec3Array(
+ 'uVec3Array',
+ );
+ final ui.UniformArray<ui.UniformVec4Slot> vec4Array = shader.getUniformVec4Array(
+ 'uVec4Array',
+ );
+
+ var colorOffset = 4;
+
+ for (var i = 0; i < 10; ++i) {
+ floatArray[i].set(cpuColors[colorOffset++].r);
+ }
+ for (var i = 0; i < 10; ++i) {
+ vec2Array[i].set(cpuColors[colorOffset].r, cpuColors[colorOffset].g);
+ ++colorOffset;
+ }
+ for (var i = 0; i < 10; ++i) {
+ vec3Array[i].set(
+ cpuColors[colorOffset].r,
+ cpuColors[colorOffset].g,
+ cpuColors[colorOffset].b,
+ );
+ ++colorOffset;
+ }
+ for (var i = 0; i < 10; ++i) {
+ vec4Array[i].set(
+ cpuColors[colorOffset].r,
+ cpuColors[colorOffset].g,
+ cpuColors[colorOffset].b,
+ cpuColors[colorOffset].a,
+ );
+ ++colorOffset;
+ }
+ await _expectShaderRendersBarcode(shader, cpuColors);
+ });
+ });
});
- test('getUniformVec3', () {
- expect(
- () => shader.getUniformVec3('uSize'),
- throwsA(
- isA<ArgumentError>().having(
- (e) => e.message,
- 'message',
- 'Uniform `uSize` has size 2, not size 3.',
- ),
- ),
- );
- });
- test('getUniformVec4', () {
- expect(
- () => shader.getUniformVec4('uLoneMatrix'),
- throwsA(
- isA<ArgumentError>().having(
- (e) => e.message,
- 'message',
- 'Uniform `uLoneMatrix` has size 16, not size 4.',
- ),
- ),
- );
- });
- });
+ }, skip: isWimp);
}
// Image of an oval painted with a linear gradient.
@@ -708,3 +1227,64 @@
picture.dispose();
}
}
+
+ui.Image _imageFromShader({required ui.Shader shader, required int imageDimension}) {
+ final recorder = ui.PictureRecorder();
+ final canvas = ui.Canvas(recorder);
+ final paint = ui.Paint()..shader = shader;
+ canvas.drawPaint(paint);
+ final ui.Picture picture = recorder.endRecording();
+ return picture.toImageSync(imageDimension, imageDimension);
+}
+
+Future<ByteData?> _imageByteDataFromShader({
+ required ui.Shader shader,
+ int imageDimension = 100,
+}) async {
+ final ui.Image image = _imageFromShader(shader: shader, imageDimension: imageDimension);
+ return image.toByteData();
+}
+
+Future<void> _expectShaderRendersColor(ui.Shader shader, ui.Color color) async {
+ final ByteData renderedBytes = (await _imageByteDataFromShader(shader: shader))!;
+
+ expect(renderedBytes.lengthInBytes % 4, 0);
+ for (var byteOffset = 0; byteOffset < renderedBytes.lengthInBytes; byteOffset += 4) {
+ final pixelColor = ui.Color.fromARGB(
+ renderedBytes.getUint8(byteOffset + 3),
+ renderedBytes.getUint8(byteOffset),
+ renderedBytes.getUint8(byteOffset + 1),
+ renderedBytes.getUint8(byteOffset + 2),
+ );
+
+ expect(pixelColor, color);
+ }
+}
+
+Future<void> _expectShaderRendersBarcode(ui.Shader shader, List<ui.Color> barcodeColors) async {
+ final ByteData renderedBytes = (await _imageByteDataFromShader(
+ shader: shader,
+ imageDimension: barcodeColors.length,
+ ))!;
+
+ expect(renderedBytes.lengthInBytes % 4, 0);
+ final List<ui.Color> renderedColors = List.generate(barcodeColors.length, (int xCoord) {
+ return ui.Color.fromARGB(
+ renderedBytes.getUint8(xCoord * 4 + 3),
+ renderedBytes.getUint8(xCoord * 4),
+ renderedBytes.getUint8(xCoord * 4 + 1),
+ renderedBytes.getUint8(xCoord * 4 + 2),
+ );
+ });
+
+ for (var i = 0; i < barcodeColors.length; ++i) {
+ final ui.Color renderedColor = renderedColors[i];
+ final ui.Color expectedColor = barcodeColors[i];
+ final reasonString =
+ 'Comparison failed on color $i. \nExpected: $expectedColor.\nActual: $renderedColor.';
+ expect(renderedColor.r.clamp(-1, 1), closeTo(expectedColor.r, 0.06), reason: reasonString);
+ expect(renderedColor.g.clamp(-1, 1), closeTo(expectedColor.g, 0.06), reason: reasonString);
+ expect(renderedColor.b.clamp(-1, 1), closeTo(expectedColor.b, 0.06), reason: reasonString);
+ expect(renderedColor.a.clamp(-1, 1), closeTo(expectedColor.a, 0.06), reason: reasonString);
+ }
+}