diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/BUILD.gn b/engine/src/flutter/lib/ui/fixtures/shaders/BUILD.gn index a410e9cf75e..47dfbd7e468 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/BUILD.gn +++ b/engine/src/flutter/lib/ui/fixtures/shaders/BUILD.gn @@ -13,6 +13,7 @@ if (enable_unittests) { "//flutter/lib/ui/fixtures/shaders/general_shaders", "//flutter/lib/ui/fixtures/shaders/supported_glsl_op_shaders", "//flutter/lib/ui/fixtures/shaders/supported_op_shaders", + "//flutter/lib/ui/fixtures/shaders/uniforms", ] } diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/BUILD.gn b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/BUILD.gn index 37e3d04c6fa..5c17d13c62c 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/BUILD.gn +++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/BUILD.gn @@ -30,7 +30,6 @@ if (enable_unittests) { "uniforms_reordered.frag", "uniforms_sorted.frag", "uniforms.frag", - "vec3_uniform.frag", ] group("general_shaders") { diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/BUILD.gn b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/BUILD.gn new file mode 100644 index 00000000000..d75abef171e --- /dev/null +++ b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/BUILD.gn @@ -0,0 +1,46 @@ +# 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. + +import("//build/compiled_action.gni") +import("//flutter/impeller/tools/impeller.gni") +import("//flutter/testing/testing.gni") + +if (enable_unittests) { + test_shaders = [ + "all_uniforms.frag", + "float_array_uniform.frag", + "float_uniform.frag", + "vec2_uniform.frag", + "vec2_array_uniform.frag", + "vec3_uniform.frag", + "vec3_array_uniform.frag", + "vec4_uniform.frag", + "vec4_array_uniform.frag", + ] + + group("uniforms") { + testonly = true + deps = [ ":fixtures" ] + } + + impellerc("compile_uniforms") { + mnemonic = "IMPELLERC_SKSL" + shaders = test_shaders + shader_target_flags = [ + "--sksl", + "--runtime-stage-metal", + "--runtime-stage-gles", + "--runtime-stage-vulkan", + ] + intermediates_subdir = "iplr" + sl_file_extension = "iplr" + iplr = true + } + + test_fixtures("fixtures") { + deps = [ ":compile_uniforms" ] + fixtures = get_target_outputs(":compile_uniforms") + dest = "$root_gen_dir/flutter/lib/ui" + } +} diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/all_uniforms.frag b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/all_uniforms.frag new file mode 100644 index 00000000000..2df139cbbdc --- /dev/null +++ b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/all_uniforms.frag @@ -0,0 +1,84 @@ +// 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. + +#include + +out vec4 fragColor; + +// One for each base type, 40 for the arrays +float N_COLOR_VALUES = 44; + +uniform float uFloat; + +uniform vec2 uVec2; +uniform vec3 uVec3; +uniform vec4 uVec4; + +const int ARRAY_SIZE = 10; + +uniform float[ARRAY_SIZE] uFloatArray; +uniform vec2[ARRAY_SIZE] uVec2Array; +uniform vec3[ARRAY_SIZE] uVec3Array; +uniform vec4[ARRAY_SIZE] uVec4Array; + +void main() { + float u = FlutterFragCoord().x / N_COLOR_VALUES; + + float increment = 1.0 / N_COLOR_VALUES; + + float offset = increment; + + if (u < offset) { + fragColor = vec4(uFloat, 0, 0, 1); + return; + } + offset += increment; + if (u < offset) { + fragColor = vec4(uVec2, 0, 1); + return; + } + offset += increment; + if (u < offset) { + fragColor = vec4(uVec3, 1); + return; + } + offset += increment; + if (u < offset) { + fragColor = uVec4; + return; + } + offset += increment; + + for (int i = 0; i < ARRAY_SIZE; ++i) { + if (u < offset) { + fragColor = vec4(uFloatArray[i], 0, 0, 1); + return; + } + offset += increment; + } + + for (int i = 0; i < ARRAY_SIZE; ++i) { + if (u < offset) { + fragColor = vec4(uVec2Array[i], 0, 1); + return; + } + offset += increment; + } + + for (int i = 0; i < ARRAY_SIZE; ++i) { + if (u < offset) { + fragColor = vec4(uVec3Array[i], 1); + return; + } + offset += increment; + } + + for (int i = 0; i < ARRAY_SIZE; ++i) { + if (u < offset) { + fragColor = uVec4Array[i]; + return; + } + offset += increment; + } +} \ No newline at end of file diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/float_array_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/float_array_uniform.frag similarity index 100% rename from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/float_array_uniform.frag rename to engine/src/flutter/lib/ui/fixtures/shaders/uniforms/float_array_uniform.frag diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/float_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/float_uniform.frag new file mode 100644 index 00000000000..b782c4b5270 --- /dev/null +++ b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/float_uniform.frag @@ -0,0 +1,15 @@ +#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. + +precision highp float; + +uniform float color_r; + +out vec4 fragColor; + +void main() { + fragColor = vec4(color_r, 0, 0, 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/uniforms/vec2_array_uniform.frag similarity index 100% rename from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec2_array_uniform.frag rename to engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec2_array_uniform.frag diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec2_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec2_uniform.frag new file mode 100644 index 00000000000..aa54b494e05 --- /dev/null +++ b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec2_uniform.frag @@ -0,0 +1,15 @@ +#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. + +precision highp float; + +uniform vec2 color_rg; + +out vec4 fragColor; + +void main() { + fragColor = vec4(color_rg, 0, 1); +} diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_array_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec3_array_uniform.frag similarity index 63% rename from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_array_uniform.frag rename to engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec3_array_uniform.frag index 23acabaf5f6..3f63e13643a 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_array_uniform.frag +++ b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec3_array_uniform.frag @@ -7,6 +7,10 @@ // If updating this file, also update // engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart +#include + +uniform vec2 u_size; + precision highp float; uniform vec3[2] color_array; @@ -14,5 +18,11 @@ uniform vec3[2] color_array; out vec4 fragColor; void main() { - fragColor = vec4(mix(color_array[0], color_array[1], 0.5), 1); + vec2 uv = FlutterFragCoord().xy / u_size; + + if (uv.x < 0.5) { + fragColor = vec4(color_array[0], 1); + } else { + fragColor = vec4(color_array[1], 1); + } } diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec3_uniform.frag similarity index 77% rename from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_uniform.frag rename to engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec3_uniform.frag index 2b1cadd8fb6..db1539de687 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec3_uniform.frag +++ b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec3_uniform.frag @@ -6,10 +6,10 @@ precision highp float; -uniform vec3[4] color_array; +uniform vec3 color_rgb; out vec4 fragColor; void main() { - fragColor = vec4(color_array[3].xyz, 1); + fragColor = vec4(color_rgb, 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/uniforms/vec4_array_uniform.frag similarity index 64% rename from engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec4_array_uniform.frag rename to engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec4_array_uniform.frag index f8e98d45757..d86687ef043 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/vec4_array_uniform.frag +++ b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec4_array_uniform.frag @@ -7,6 +7,10 @@ // If updating this file, also update // engine/src/flutter/lib/web_ui/test/ui/fragment_shader_test.dart +#include + +uniform vec2 u_size; + precision highp float; uniform vec4[2] color_array; @@ -14,5 +18,11 @@ uniform vec4[2] color_array; out vec4 fragColor; void main() { - fragColor = mix(color_array[0], color_array[1], 0.5); + vec2 uv = FlutterFragCoord().xy / u_size; + + if (uv.x < 0.5) { + fragColor = color_array[0]; + } else { + fragColor = color_array[1]; + } } diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec4_uniform.frag b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec4_uniform.frag new file mode 100644 index 00000000000..4525a8b361b --- /dev/null +++ b/engine/src/flutter/lib/ui/fixtures/shaders/uniforms/vec4_uniform.frag @@ -0,0 +1,15 @@ +#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. + +precision highp float; + +uniform vec4 color_rgba; + +out vec4 fragColor; + +void main() { + fragColor = color_rgba; +} diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart index d3ca916fe41..a3cac1c7f03 100644 --- a/engine/src/flutter/lib/ui/painting.dart +++ b/engine/src/flutter/lib/ui/painting.dart @@ -5809,7 +5809,7 @@ base class FragmentShader extends Shader { final slots = List.generate( size, - (i) => UniformFloatSlot._(this, name, info.index, i), + (i) => UniformFloatSlot._(this, name, i, info.index + i), ); _slots.removeWhere((WeakReference ref) => ref.target == null); _slots.addAll(slots.map((slot) => WeakReference(slot))); diff --git a/engine/src/flutter/testing/dart/fragment_shader_test.dart b/engine/src/flutter/testing/dart/fragment_shader_test.dart index 5c70b6f46a4..ccb4a9182a7 100644 --- a/engine/src/flutter/testing/dart/fragment_shader_test.dart +++ b/engine/src/flutter/testing/dart/fragment_shader_test.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert' as convert; import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; import 'dart:ui'; @@ -52,7 +53,7 @@ void main() async { expect(identical(programA, programB), true); }); - group('FragmentProgram getUniform*', () { + group('getUniformFloat slots', () { late FragmentShader shader; setUpAll(() async { @@ -74,199 +75,459 @@ void main() async { expect(slots[i].shaderIndex, equals(i)); } }); + }); - test('FragmentProgram getUniformFloat unknown', () async { - expect( - () { - shader.getUniformFloat('unknown'); - }, - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('No uniform named "unknown"'), + group('FragmentShader uniforms', () { + late Map shaderMap; + + setUpAll(() async { + shaderMap = { + UniformFloatSlot: (await FragmentProgram.fromAsset( + 'float_uniform.frag.iplr', + )).fragmentShader(), + UniformVec2Slot: (await FragmentProgram.fromAsset( + 'vec2_uniform.frag.iplr', + )).fragmentShader(), + UniformVec3Slot: (await FragmentProgram.fromAsset( + 'vec3_uniform.frag.iplr', + )).fragmentShader(), + UniformVec4Slot: (await FragmentProgram.fromAsset( + 'vec4_uniform.frag.iplr', + )).fragmentShader(), + UniformArray: (await FragmentProgram.fromAsset( + 'float_array_uniform.frag.iplr', + )).fragmentShader(), + UniformArray: (await FragmentProgram.fromAsset( + 'vec2_array_uniform.frag.iplr', + )).fragmentShader(), + UniformArray: (await FragmentProgram.fromAsset( + 'vec3_array_uniform.frag.iplr', + )).fragmentShader(), + UniformArray: (await FragmentProgram.fromAsset( + 'vec4_array_uniform.frag.iplr', + )).fragmentShader(), + }; + }); + + group('float', () { + test('set using setUniformFloat', () async { + final FragmentShader shader = shaderMap[UniformFloatSlot]!; + const color = Color.fromARGB(255, 255, 0, 0); + shader.setFloat(0, color.r); + _expectShaderRendersColor(shader, color); + }); + + test('set using getUniformFloat', () async { + final FragmentShader shader = shaderMap[UniformFloatSlot]!; + const color = Color.fromARGB(255, 50, 0, 0); + shader.getUniformFloat('color_r').set(color.r); + _expectShaderRendersColor(shader, color); + }); + + test('getUniformFloat offset overflow', () async { + final FragmentShader shader = shaderMap[UniformFloatSlot]!; + expect( + () => shader.getUniformFloat('color_r', 2), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('Index `2` out of bounds for `color_r`.'), + ), ), - ), - ); - }); + ); + }); - test('FragmentProgram getUniformFloat offset overflow', () async { - expect( - () => shader.getUniformFloat('iVec2Uniform', 2), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('Index `2` out of bounds for `iVec2Uniform`.'), + test('getUniformFloat offset underflow', () async { + final FragmentShader shader = shaderMap[UniformFloatSlot]!; + expect( + () => shader.getUniformFloat('color_r', -1), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('Index `-1` out of bounds for `color_r`.'), + ), ), - ), - ); + ); + }); }); + group('vec2', () { + test('set using setFloat', () async { + final FragmentShader shader = shaderMap[UniformVec2Slot]!; + const color = Color.fromARGB(255, 255, 255, 0); + shader.setFloat(0, color.r); + shader.setFloat(1, color.g); + _expectShaderRendersColor(shader, color); + }); - test('FragmentProgram getUniformFloat offset underflow', () async { - expect( - () => shader.getUniformFloat('iVec2Uniform', -1), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('Index `-1` out of bounds for `iVec2Uniform`.'), + test('set using getUniformVec2', () async { + final FragmentShader shader = shaderMap[UniformVec2Slot]!; + const color = Color.fromARGB(255, 50, 50, 0); + shader.getUniformVec2('color_rg').set(color.r, color.g); + _expectShaderRendersColor(shader, color); + }); + + test('wrong datatype', () async { + final FragmentShader shader = shaderMap[UniformVec3Slot]!; + expect( + () => shader.getUniformVec2('color_rgb'), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('`color_rgb` has size 3, not size 2.'), + ), ), - ), - ); + ); + }); }); + group('vec3', () { + test('set using setFloat', () async { + final FragmentShader shader = shaderMap[UniformVec3Slot]!; + const color = 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); + _expectShaderRendersColor(shader, color); + }); - test('FragmentProgram getUniformVec2', () async { - final UniformVec2Slot slot = shader.getUniformVec2('iVec2Uniform'); - slot.set(6.0, 7.0); - }); + test('set using getUniformVec3', () async { + final FragmentShader shader = shaderMap[UniformVec3Slot]!; + const color = Color.fromARGB(255, 42, 67, 12); + shader.getUniformVec3('color_rgb').set(color.r, color.g, color.b); + _expectShaderRendersColor(shader, color); + }); - test('FragmentProgram getUniformVec2 wrong size', () async { - expect( - () => shader.getUniformVec2('iVec3Uniform'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('`iVec3Uniform` has size 3, not size 2.'), + test('wrong datatype', () async { + final FragmentShader shader = shaderMap[UniformVec2Slot]!; + expect( + () => shader.getUniformVec3('color_rg'), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('`color_rg` has size 2, not size 3.'), + ), ), - ), - ); - expect( - () => shader.getUniformVec2('iFloatUniform'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('`iFloatUniform` has size 1, not size 2.'), + ); + }); + }); + + group('vec4', () { + test('set using setFloat', () async { + const color = Color.fromARGB(255, 67, 42, 12); + final FragmentShader shader = shaderMap[UniformVec4Slot]!; + shader.setFloat(0, color.r); + shader.setFloat(1, color.g); + shader.setFloat(2, color.b); + shader.setFloat(3, color.a); + _expectShaderRendersColor(shader, color); + }); + + test('set using getUniformFloat', () async { + const color = Color.fromARGB(255, 12, 37, 27); + final FragmentShader shader = shaderMap[UniformVec4Slot]!; + shader.getUniformVec4('color_rgba').set(color.r, color.g, color.b, color.a); + _expectShaderRendersColor(shader, color); + }); + + test('wrong datatype', () async { + final FragmentShader shader = shaderMap[UniformVec3Slot]!; + expect( + () => shader.getUniformVec4('color_rgb'), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('`color_rgb` has size 3, not size 4.'), + ), ), - ), - ); + ); + }); }); - test('FragmentProgram getUniformVec3', () async { - final UniformVec3Slot slot = shader.getUniformVec3('iVec3Uniform'); - slot.set(0.8, 0.1, 0.3); + group('float array', () { + test('set using setFloat', () { + const color = Color.fromARGB(255, 11, 22, 96); + final FragmentShader shader = shaderMap[UniformArray]!; + shader.setFloat(0, color.r); + shader.setFloat(1, color.g); + shader.setFloat(2, color.b); + shader.setFloat(3, color.a); + _expectShaderRendersColor(shader, color); + }); + + test('set using getUniformFloatArray', () async { + const color = Color.fromARGB(255, 96, 11, 22); + final FragmentShader shader = shaderMap[UniformArray]!; + final UniformArray 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); + _expectShaderRendersColor(shader, color); + }); }); - test('FragmentProgram getUniformVec3 wrong size', () async { - expect( - () => shader.getUniformVec3('iVec2Uniform'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('`iVec2Uniform` has size 2, not size 3.'), + group('vec2 array', () { + test('set using setFloat', () async { + const color = Color.fromARGB(255, 67, 42, 12); + final FragmentShader shader = shaderMap[UniformArray]!; + shader.setFloat(0, color.r); + shader.setFloat(1, color.g); + shader.setFloat(2, color.b); + shader.setFloat(3, color.a); + _expectShaderRendersColor(shader, color); + }); + + test('set using getUniformVec2Array', () async { + const color = Color.fromARGB(255, 1, 73, 26); + final FragmentShader shader = shaderMap[UniformArray]!; + final UniformArray colorRgba = shader.getUniformVec2Array('color_array'); + colorRgba[0].set(color.r, color.g); + colorRgba[1].set(color.b, color.a); + _expectShaderRendersColor(shader, color); + }); + + test('wrong datatype', () async { + final FragmentShader shader = shaderMap[UniformVec3Slot]!; + expect( + () => shader.getUniformVec2Array('color_rgb'), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('Uniform size (3) for "color_rgb" is not a multiple of 2.'), + ), ), - ), - ); - expect( - () => shader.getUniformVec3('iVec4Uniform'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('`iVec4Uniform` has size 4, not size 3.'), + ); + }); + }); + + group('vec3 array', () { + test('set using setFloat', () async { + const cpuColors = [Color.fromARGB(255, 67, 42, 12), Color.fromARGB(255, 11, 22, 96)]; + final FragmentShader shader = shaderMap[UniformArray]!; + 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); + _expectShaderRendersBarcode(shader, cpuColors); + }); + + test('set using getUniformVec3Array', () async { + const cpuColors = [Color.fromARGB(255, 11, 22, 96), Color.fromARGB(255, 67, 42, 12)]; + final FragmentShader shader = shaderMap[UniformArray]!; + shader.getUniformVec2('u_size').set(2, 2); + final UniformArray 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); + _expectShaderRendersBarcode(shader, cpuColors); + }); + + test('wrong datatype', () async { + final FragmentShader shader = shaderMap[UniformVec4Slot]!; + expect( + () => shader.getUniformVec3Array('color_rgba'), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('Uniform size (4) for "color_rgba" is not a multiple of 3.'), + ), ), - ), - ); + ); + }); }); - test('FragmentProgram getUniformVec4', () async { - final UniformVec4Slot slot = shader.getUniformVec4('iVec4Uniform'); - slot.set(11.0, 22.0, 19.0, 96.0); - }); + group('vec4 array', () { + test('set using setFloat', () async { + const cpuColors = [Color.fromARGB(77, 67, 42, 12), Color.fromARGB(51, 11, 22, 96)]; + final FragmentShader shader = shaderMap[UniformArray]!; + // '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); + _expectShaderRendersBarcode(shader, cpuColors); + }); - test('FragmentProgram getUniformVec4 wrong size', () async { - expect( - () => shader.getUniformVec4('iVec3Uniform'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('`iVec3Uniform` has size 3, not size 4.'), + test('set using getUniformVec4Array', () async { + const cpuColors = [Color.fromARGB(51, 11, 22, 96), Color.fromARGB(77, 67, 42, 12)]; + final FragmentShader shader = shaderMap[UniformArray]!; + shader.getUniformVec2('u_size').set(2, 2); + final UniformArray 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); + _expectShaderRendersBarcode(shader, cpuColors); + }); + + test('wrong datatype', () async { + final FragmentShader shader = shaderMap[UniformVec3Slot]!; + expect( + () => shader.getUniformVec4Array('color_rgb'), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('Uniform size (3) for "color_rgb" is not a multiple of 4.'), + ), ), - ), - ); + ); + }); }); - test('FragmentProgram getUniformArray float', () async { - final UniformArray slots = shader.getUniformFloatArray( - 'iFloatArrayUniform', - ); - expect(slots.length, 10); - slots[0].set(1.0); - slots[1].set(1.0); - }); + group('all uniforms', () { + late FragmentProgram program; + late List cpuColors; + final random = Random(1337); + setUpAll(() async { + program = await FragmentProgram.fromAsset('all_uniforms.frag.iplr'); + }); - test('FragmentProgram getUniformArray not found', () async { - expect( - () => shader.getUniformFloatArray('unknown'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('No uniform named "unknown".'), + setUp(() async { + cpuColors = List.empty(growable: true); + // uFloat + cpuColors.add(Color.fromARGB(255, random.nextInt(255), 0, 0)); + // uVec2 + cpuColors.add(Color.fromARGB(255, random.nextInt(255), random.nextInt(255), 0)); + // uVec3 + cpuColors.add( + Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)), + ); + // uVec4 + cpuColors.add( + Color.fromARGB( + random.nextInt(255), + random.nextInt(255), + random.nextInt(255), + random.nextInt(255), ), - ), - ); - }); + ); + for (var i = 0; i < 10; ++i) { + cpuColors.add(Color.fromARGB(255, random.nextInt(255), 0, 0)); + } - test('FragmentProgram getUniformArrayVec2', () async { - final UniformArray slots = shader.getUniformVec2Array('iVec2ArrayUniform'); - expect(slots.length, 3); - slots[0].set(1.0, 1.0); - }); + for (var i = 0; i < 10; ++i) { + cpuColors.add(Color.fromARGB(255, random.nextInt(255), random.nextInt(255), 0)); + } - test('FragmentProgram getUniformArrayVec2 wrong type', () async { - expect( - () => shader.getUniformVec2Array('iVec3ArrayUniform'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('Uniform size (9) for "iVec3ArrayUniform" is not a multiple of 2.'), - ), - ), - ); - }); + for (var i = 0; i < 10; ++i) { + cpuColors.add( + Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)), + ); + } + for (var i = 0; i < 10; ++i) { + cpuColors.add( + Color.fromARGB( + random.nextInt(255), + random.nextInt(255), + random.nextInt(255), + random.nextInt(255), + ), + ); + } + }); - test('FragmentProgram getUniformArrayVec3', () async { - final UniformArray slots = shader.getUniformVec3Array('iVec3ArrayUniform'); - expect(slots.length, 3); - slots[0].set(1.0, 1.0, 1.0); - }); + test('set using setFloat', () async { + final 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); - test('FragmentProgram getUniformArrayVec3 wrong type', () async { - expect( - () => shader.getUniformVec3Array('iFloatArrayUniform'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('Uniform size (10) for "iFloatArrayUniform" is not a multiple of 3.'), - ), - ), - ); - }); + var shaderOffset = 10; + var colorOffset = 4; - test('FragmentProgram getUniformArrayVec4', () async { - final UniformArray slots = shader.getUniformVec4Array('iVec4ArrayUniform'); - expect(slots.length, 3); - slots[0].set(1.0, 1.0, 1.0, 1.0); - }); + 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); + } + _expectShaderRendersBarcode(shader, cpuColors); + }); - test('FragmentProgram getUniformArrayVec4 wrong type', () async { - expect( - () => shader.getUniformVec4Array('iFloatArrayUniform'), - throwsA( - isA().having( - (e) => e.message, - 'message', - contains('Uniform size (10) for "iFloatArrayUniform" is not a multiple of 4.'), - ), - ), - ); + test('set using getUniform*', () async { + final 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 UniformArray floatArray = shader.getUniformFloatArray( + 'uFloatArray', + ); + final UniformArray vec2Array = shader.getUniformVec2Array('uVec2Array'); + final UniformArray vec3Array = shader.getUniformVec3Array('uVec3Array'); + final UniformArray 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; + } + _expectShaderRendersBarcode(shader, cpuColors); + }); }); }); @@ -526,109 +787,6 @@ void main() async { shader.dispose(); }); - 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 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 { - try { - shader.getUniformFloat('unknown'); - fail('Unreachable'); - } catch (e) { - expect(e.toString(), contains('No uniform named "unknown".')); - } - }); - - _runSkiaTest('FragmentProgram getUniformFloat offset overflow', () async { - try { - shader.getUniformFloat('iVec2Uniform', 2); - fail('Unreachable'); - } catch (e) { - expect(e.toString(), contains('Index `2` out of bounds for `iVec2Uniform`.')); - } - }); - - _runSkiaTest('FragmentProgram getUniformFloat offset underflow', () async { - try { - shader.getUniformFloat('iVec2Uniform', -1); - fail('Unreachable'); - } catch (e) { - expect(e.toString(), 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 { - try { - shader.getUniformVec2('iVec3Uniform'); - fail('Unreachable'); - } catch (e) { - expect(e.toString(), contains('`iVec3Uniform` has size 3, not size 2.')); - } - try { - shader.getUniformVec2('iFloatUniform'); - } catch (e) { - expect(e.toString(), 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 { - try { - shader.getUniformVec3('iVec2Uniform'); - fail('Unreachable'); - } catch (e) { - expect(e.toString(), contains('`iVec2Uniform` has size 2, not size 3.')); - } - try { - shader.getUniformVec3('iVec4Uniform'); - } catch (e) { - expect(e.toString(), 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 { - try { - shader.getUniformVec4('iVec3Uniform'); - fail('Unreachable'); - } catch (e) { - expect(e.toString(), contains('`iVec3Uniform` has size 3, not size 4.')); - } - }); - }); - _runSkiaTest('FragmentProgram getImageSampler wrong type', () async { final FragmentProgram program = await FragmentProgram.fromAsset('uniform_ordering.frag.iplr'); final FragmentShader shader = program.fragmentShader(); @@ -695,18 +853,6 @@ void main() async { shader.dispose(); }); - test('FragmentShader shader with array uniforms renders correctly', () async { - final FragmentProgram program = await FragmentProgram.fromAsset('uniform_arrays.frag.iplr'); - - final FragmentShader shader = program.fragmentShader(); - for (var i = 0; i < 20; i++) { - shader.setFloat(i, i.toDouble()); - } - - await _expectShaderRendersGreen(shader); - shader.dispose(); - }); - test('FragmentShader shader with mat2 uniform renders correctly', () async { final FragmentProgram program = await FragmentProgram.fromAsset('uniform_mat2.frag.iplr'); @@ -752,23 +898,6 @@ void main() async { }, ); - _runImpellerTest( - 'Shader Compiler appropriately pads vec3 uniform arrays', - () async { - final FragmentProgram program = await FragmentProgram.fromAsset('vec3_uniform.frag.iplr'); - final FragmentShader shader = program.fragmentShader(); - - // Set the last vec3 in the uniform array to green. The shader will read this - // value, and if the uniforms were padded correctly will render green. - shader.setFloat(9, 0); // color_array[3].x - shader.setFloat(10, 1.0); // color_array[3].y - shader.setFloat(11, 0); // color_array[3].z - - await _expectShaderRendersGreen(shader); - }, - skip: Platform.executableArguments.contains('--impeller-backend=metal'), - ); - _runImpellerTest('ImageFilter.shader can be applied to canvas operations', () async { final FragmentProgram program = await FragmentProgram.fromAsset('filter_shader.frag.iplr'); final FragmentShader shader = program.fragmentShader(); @@ -887,6 +1016,34 @@ void _expectFragmentShadersRenderGreen(Map programs) { } } +Future _expectShaderRendersBarcode(Shader shader, List barcodeColors) async { + final ByteData renderedBytes = (await _imageByteDataFromShader( + shader: shader, + imageDimension: barcodeColors.length, + ))!; + + expect(renderedBytes.lengthInBytes % 4, 0); + final List renderedColors = List.generate(barcodeColors.length, (int xCoord) { + return 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 Color renderedColor = renderedColors[i]; + final 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); + } +} + Future _expectShaderRendersColor(Shader shader, Color color) async { final ByteData renderedBytes = (await _imageByteDataFromShader( shader: shader,