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 0873f835115..744a4ba8e0a 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 @@ -10,7 +10,6 @@ if (enable_unittests) { test_shaders = [ "blue_green_sampler.frag", "children_and_uniforms.frag", - "circle_sdf.frag", "double_sampler_swapped.frag", "double_sampler.frag", "filter_shader.frag", @@ -19,16 +18,15 @@ if (enable_unittests) { "missing_texture.frag", "no_builtin_redefinition.frag", "no_uniforms.frag", - "sdf.frag", "simple.frag", "texture.frag", "uniform_arrays.frag", "uniform_ordering.frag", "uniforms_inserted.frag", - "uniforms_renamed.frag", "uniforms_reordered.frag", "uniforms_sorted.frag", "uniforms.frag", + "uniforms_renamed.frag", "vec3_uniform.frag", ] @@ -40,6 +38,18 @@ if (enable_unittests) { impellerc("compile_general_shaders") { mnemonic = "IMPELLERC_SKSL" shaders = test_shaders + shader_target_flags = [ "--sksl" ] + intermediates_subdir = "iplr" + sl_file_extension = "iplr" + iplr = true + } + + impellerc("compile_sdf_shaders") { + mnemonic = "IMPELLERC_SDF" + shaders = [ + "circle_sdf.frag", + "sdf.frag", + ] shader_target_flags = [ "--sksl", "--runtime-stage-metal", @@ -52,8 +62,12 @@ if (enable_unittests) { } test_fixtures("fixtures") { - deps = [ ":compile_general_shaders" ] - fixtures = get_target_outputs(":compile_general_shaders") + deps = [ + ":compile_general_shaders", + ":compile_sdf_shaders", + ] + fixtures = get_target_outputs(":compile_general_shaders") + + get_target_outputs(":compile_sdf_shaders") dest = "$root_gen_dir/flutter/lib/ui" } } diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/blue_green_sampler.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/blue_green_sampler.frag index 224e9754886..8cff5d531da 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/blue_green_sampler.frag +++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/blue_green_sampler.frag @@ -6,9 +6,9 @@ precision highp float; -out vec4 oColor; +layout(location = 0) out vec4 oColor; -uniform sampler2D iChild; +layout(location = 0) uniform sampler2D iChild; void main() { // iChild1 is an image that is half blue, half green, diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/children_and_uniforms.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/children_and_uniforms.frag index 9dafb0cbc54..41bb1556535 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/children_and_uniforms.frag +++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/children_and_uniforms.frag @@ -8,10 +8,10 @@ precision highp float; layout(location = 0) out vec4 color; -uniform sampler2D child1; -uniform float a; -uniform sampler2D child2; -uniform float b; +layout(location = 0) uniform sampler2D child1; +layout(location = 1) uniform float a; +layout(location = 2) uniform sampler2D child2; +layout(location = 3) uniform float b; void main() { // child1 is a 10x10 image where the left half is blue and the right diff --git a/engine/src/flutter/testing/dart/fragment_shader_test.dart b/engine/src/flutter/testing/dart/fragment_shader_test.dart index 1bc8a7c7611..8d44ce8a465 100644 --- a/engine/src/flutter/testing/dart/fragment_shader_test.dart +++ b/engine/src/flutter/testing/dart/fragment_shader_test.dart @@ -17,6 +17,8 @@ import 'impeller_enabled.dart'; import 'shader_test_file_utils.dart'; void main() async { + final ImageComparer comparer = await ImageComparer.create(); + test('impellerc produces reasonable JSON encoded IPLR files', () async { final Directory directory = shaderDirectory('iplr-json'); final Object? rawData = convert.json.decode( @@ -41,6 +43,11 @@ void main() async { expect(uniformData['location'] is int, true); }); + if (impellerEnabled) { + // https://github.com/flutter/flutter/issues/122823 + return; + } + test('FragmentProgram objects are cached.', () async { final FragmentProgram programA = await FragmentProgram.fromAsset( 'blue_green_sampler.frag.iplr', @@ -52,6 +59,109 @@ void main() async { 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(); + }); + + test('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".')); + } + }); + + test('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`.')); + } + }); + + test('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`.')); + } + }); + + test('FragmentProgram getUniformVec2', () async { + final UniformVec2Slot slot = shader.getUniformVec2('iVec2Uniform'); + slot.set(6.0, 7.0); + }); + + test('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.')); + } + }); + + test('FragmentProgram getUniformVec3', () async { + final UniformVec3Slot slot = shader.getUniformVec3('iVec3Uniform'); + slot.set(0.8, 0.1, 0.3); + }); + + test('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.')); + } + }); + + test('FragmentProgram getUniformVec4', () async { + final UniformVec4Slot slot = shader.getUniformVec4('iVec4Uniform'); + slot.set(11.0, 22.0, 19.0, 96.0); + }); + + test('FragmentProgram getUniformVec4 wrong size', () async { + try { + shader.getUniformVec4('iVec3Uniform'); + fail('Unreachable'); + } catch (e) { + expect(e.toString(), contains('`iVec3Uniform` has size 3, not size 4.')); + } + }); + }); + test('FragmentProgram getImageSampler', () async { final FragmentProgram program = await FragmentProgram.fromAsset('uniform_ordering.frag.iplr'); final FragmentShader shader = program.fragmentShader(); @@ -72,6 +182,17 @@ void main() async { } }); + test('FragmentProgram getImageSampler wrong type', () async { + final FragmentProgram program = await FragmentProgram.fromAsset('uniform_ordering.frag.iplr'); + final FragmentShader shader = program.fragmentShader(); + try { + shader.getImageSampler('b'); + fail('Unreachable'); + } catch (e) { + expect(e.toString(), contains('Uniform "b" is not an image sampler.')); + } + }); + test('FragmentShader setSampler throws with out-of-bounds index', () async { final FragmentProgram program = await FragmentProgram.fromAsset('blue_green_sampler.frag.iplr'); final Image blueGreenImage = await _createBlueGreenImage(); @@ -210,6 +331,13 @@ void main() async { ); }); + test('FragmentShader simple shader renders correctly', () async { + final FragmentProgram program = await FragmentProgram.fromAsset('functions.frag.iplr'); + final FragmentShader shader = program.fragmentShader()..setFloat(0, 1.0); + await _expectShaderRendersGreen(shader); + shader.dispose(); + }); + test('Reused FragmentShader simple shader renders correctly', () async { final FragmentProgram program = await FragmentProgram.fromAsset('functions.frag.iplr'); final FragmentShader shader = program.fragmentShader()..setFloat(0, 1.0); @@ -239,6 +367,77 @@ void main() async { blueGreenImage.dispose(); }); + for (final (filterQuality, goldenFilename) in [ + (FilterQuality.none, 'fragment_shader_texture_with_quality_none.png'), + (FilterQuality.low, 'fragment_shader_texture_with_quality_low.png'), + (FilterQuality.medium, 'fragment_shader_texture_with_quality_medium.png'), + (FilterQuality.high, 'fragment_shader_texture_with_quality_high.png'), + ]) { + test('FragmentShader renders sampler with filter quality ${filterQuality.name}', () async { + final FragmentProgram program = await FragmentProgram.fromAsset('texture.frag.iplr'); + final Image image = _createOvalGradientImage(imageDimension: 16); + final FragmentShader shader = program.fragmentShader() + ..setImageSampler(0, image, filterQuality: filterQuality); + shader.getUniformFloat('u_size', 0).set(300); + shader.getUniformFloat('u_size', 1).set(300); + + final Image shaderImage = await _imageFromShader(shader: shader, imageDimension: 300); + + await comparer.addGoldenImage(shaderImage, goldenFilename); + shader.dispose(); + image.dispose(); + }); + } + + test('FragmentShader with uniforms renders correctly', () async { + final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr'); + + final FragmentShader shader = program.fragmentShader() + ..setFloat(0, 0.0) + ..setFloat(1, 0.25) + ..setFloat(2, 0.75) + ..setFloat(3, 0.0) + ..setFloat(4, 0.0) + ..setFloat(5, 0.0) + ..setFloat(6, 1.0); + + final ByteData renderedBytes = (await _imageByteDataFromShader(shader: shader))!; + + expect(toFloat(renderedBytes.getUint8(0)), closeTo(0.0, epsilon)); + expect(toFloat(renderedBytes.getUint8(1)), closeTo(0.25, epsilon)); + expect(toFloat(renderedBytes.getUint8(2)), closeTo(0.75, epsilon)); + expect(toFloat(renderedBytes.getUint8(3)), closeTo(1.0, epsilon)); + + 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 The ink_sparkle shader is accepted', () async { + if (impellerEnabled) { + print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823'); + return; + } + final FragmentProgram program = await FragmentProgram.fromAsset('ink_sparkle.frag.iplr'); + final FragmentShader shader = program.fragmentShader(); + + await _imageByteDataFromShader(shader: shader); + + // Testing that no exceptions are thrown. Tests that the ink_sparkle shader + // produces the correct pixels are in the framework. + shader.dispose(); + }); + test('FragmentShader Uniforms are sorted correctly', () async { final FragmentProgram program = await FragmentProgram.fromAsset('uniforms_sorted.frag.iplr'); @@ -293,6 +492,10 @@ void main() async { }); test('FragmentShader user defined functions do not redefine builtins', () async { + if (impellerEnabled) { + print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823'); + return; + } final FragmentProgram program = await FragmentProgram.fromAsset( 'no_builtin_redefinition.frag.iplr', ); @@ -302,198 +505,19 @@ void main() async { }); test('FragmentShader fromAsset accepts a shader with no uniforms', () async { + if (impellerEnabled) { + print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823'); + return; + } final FragmentProgram program = await FragmentProgram.fromAsset('no_uniforms.frag.iplr'); final FragmentShader shader = program.fragmentShader(); await _expectShaderRendersGreen(shader); 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)); - } - }); - - _runSkiaTest('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(); - try { - shader.getImageSampler('b'); - fail('Unreachable'); - } catch (e) { - expect(e.toString(), contains('Uniform "b" is not an image sampler.')); - } - }); - - final ImageComparer comparer = await ImageComparer.create(); - for (final (filterQuality, goldenFilename) in [ - (FilterQuality.none, 'fragment_shader_texture_with_quality_none.png'), - (FilterQuality.low, 'fragment_shader_texture_with_quality_low.png'), - (FilterQuality.medium, 'fragment_shader_texture_with_quality_medium.png'), - (FilterQuality.high, 'fragment_shader_texture_with_quality_high.png'), - ]) { - _runSkiaTest( - 'FragmentShader renders sampler with filter quality ${filterQuality.name}', - () async { - final FragmentProgram program = await FragmentProgram.fromAsset('texture.frag.iplr'); - final Image image = _createOvalGradientImage(imageDimension: 16); - final FragmentShader shader = program.fragmentShader() - ..setImageSampler(0, image, filterQuality: filterQuality); - shader.getUniformFloat('u_size', 0).set(300); - shader.getUniformFloat('u_size', 1).set(300); - - final Image shaderImage = await _imageFromShader(shader: shader, imageDimension: 300); - - await comparer.addGoldenImage(shaderImage, goldenFilename); - shader.dispose(); - image.dispose(); - }, - ); - } - - _runSkiaTest('FragmentShader simple shader renders correctly', () async { - final FragmentProgram program = await FragmentProgram.fromAsset('functions.frag.iplr'); - final FragmentShader shader = program.fragmentShader()..setFloat(0, 1.0); - await _expectShaderRendersGreen(shader); - shader.dispose(); - }); - - _runSkiaTest('FragmentShader with uniforms renders correctly', () async { - final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr'); - - final FragmentShader shader = program.fragmentShader() - ..setFloat(0, 0.0) - ..setFloat(1, 0.25) - ..setFloat(2, 0.75) - ..setFloat(3, 0.0) - ..setFloat(4, 0.0) - ..setFloat(5, 0.0) - ..setFloat(6, 1.0); - - final ByteData renderedBytes = (await _imageByteDataFromShader(shader: shader))!; - - expect(toFloat(renderedBytes.getUint8(0)), closeTo(0.0, epsilon)); - expect(toFloat(renderedBytes.getUint8(1)), closeTo(0.25, epsilon)); - expect(toFloat(renderedBytes.getUint8(2)), closeTo(0.75, epsilon)); - expect(toFloat(renderedBytes.getUint8(3)), closeTo(1.0, epsilon)); - - shader.dispose(); - }); - - _runSkiaTest('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('ImageFilter.shader errors if shader does not have correct uniform layout', () async { - if (impellerEnabled || !impellerEnabled) { - // TODO(gaaclarke): This test was disabled for a long time and atrophied - // we need to fix it or delete it. - print('This test atrophied and is disabled.'); + if (!impellerEnabled) { + print('Skipped for Skia'); return; } const shaders = [ @@ -524,7 +548,12 @@ void main() async { } }); - _runImpellerTest('Shader Compiler appropriately pads vec3 uniform arrays', () async { + test('Shader Compiler appropriately pads vec3 uniform arrays', () async { + if (!impellerEnabled) { + print('Skipped for Skia'); + return; + } + final FragmentProgram program = await FragmentProgram.fromAsset('vec3_uniform.frag.iplr'); final FragmentShader shader = program.fragmentShader(); @@ -537,7 +566,11 @@ void main() async { await _expectShaderRendersGreen(shader); }); - _runImpellerTest('ImageFilter.shader can be applied to canvas operations', () async { + test('ImageFilter.shader can be applied to canvas operations', () async { + if (!impellerEnabled) { + print('Skipped for Skia'); + return; + } final FragmentProgram program = await FragmentProgram.fromAsset('filter_shader.frag.iplr'); final FragmentShader shader = program.fragmentShader(); final recorder = PictureRecorder(); @@ -556,9 +589,8 @@ void main() async { // For an explaination of the problem see https://github.com/flutter/flutter/issues/163302 . test('ImageFilter.shader equality checks consider uniform values', () async { - if (impellerEnabled || !impellerEnabled) { - // TODO(gaaclarke): Delete or fix this test. - print("This test was skipped and now doesn't work."); + if (!impellerEnabled) { + print('Skipped for Skia'); return; } final FragmentProgram program = await FragmentProgram.fromAsset('filter_shader.frag.iplr'); @@ -583,56 +615,24 @@ void main() async { expect(identical(filter, filter_3), false); }); - _runSkiaTest('FragmentShader The ink_sparkle shader is accepted', () async { - final FragmentProgram program = await FragmentProgram.fromAsset('ink_sparkle.frag.iplr'); - final FragmentShader shader = program.fragmentShader(); - - await _imageByteDataFromShader(shader: shader); - - // Testing that no exceptions are thrown. Tests that the ink_sparkle shader - // produces the correct pixels are in the framework. - shader.dispose(); - }); - - if (!impellerEnabled) { - // Test all supported GLSL ops. See lib/spirv/lib/src/constants.dart - final Map iplrSupportedGLSLOpShaders = await _loadShaderAssets( - path.join('supported_glsl_op_shaders', 'iplr'), - '.iplr', - ); - _expectFragmentShadersRenderGreen(iplrSupportedGLSLOpShaders); - - // Test all supported instructions. See lib/spirv/lib/src/constants.dart - final Map iplrSupportedOpShaders = await _loadShaderAssets( - path.join('supported_op_shaders', 'iplr'), - '.iplr', - ); - _expectFragmentShadersRenderGreen(iplrSupportedOpShaders); + if (impellerEnabled) { + print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823'); + return; } -} -//////////////////////////////////////////////////////////////////////////////// -// Helper Functions //////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// + // Test all supported GLSL ops. See lib/spirv/lib/src/constants.dart + final Map iplrSupportedGLSLOpShaders = await _loadShaderAssets( + path.join('supported_glsl_op_shaders', 'iplr'), + '.iplr', + ); + _expectFragmentShadersRenderGreen(iplrSupportedGLSLOpShaders); -void _runSkiaTest(String name, void Function() callback) { - test(name, () { - if (impellerEnabled) { - print('skipped for Impeller - https://github.com/flutter/flutter/issues/122823'); - return; - } - callback(); - }); -} - -void _runImpellerTest(String name, void Function() callback) { - test(name, () { - if (!impellerEnabled) { - print('skipped for Skia'); - return; - } - callback(); - }); + // Test all supported instructions. See lib/spirv/lib/src/constants.dart + final Map iplrSupportedOpShaders = await _loadShaderAssets( + path.join('supported_op_shaders', 'iplr'), + '.iplr', + ); + _expectFragmentShadersRenderGreen(iplrSupportedOpShaders); } // Expect that all of the shaders in this folder render green.