| // 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 'dart:async'; |
| import 'dart:collection'; |
| import 'dart:convert' as convert; |
| import 'dart:io'; |
| import 'dart:typed_data'; |
| import 'dart:ui'; |
| |
| import 'package:path/path.dart' as path; |
| import 'package:test/test.dart'; |
| |
| import 'goldens.dart'; |
| 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( |
| File(path.join(directory.path, 'ink_sparkle.frag.iplr')).readAsStringSync(), |
| ); |
| |
| expect(rawData is Map<String, Object?>, true); |
| |
| final data = rawData! as Map<String, Object?>; |
| expect(data.keys.toList(), <String>['format_version', 'sksl']); |
| expect(data['sksl'] is Map<String, Object?>, true); |
| |
| final skslData = data['sksl']! as Map<String, Object?>; |
| expect(skslData['uniforms'] is List<Object?>, true); |
| |
| final Object? rawUniformData = (skslData['uniforms']! as List<Object?>)[0]; |
| |
| expect(rawUniformData is Map<String, Object?>, true); |
| |
| final uniformData = rawUniformData! as Map<String, Object?>; |
| |
| 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', |
| ); |
| final FragmentProgram programB = await FragmentProgram.fromAsset( |
| 'blue_green_sampler.frag.iplr', |
| ); |
| |
| expect(identical(programA, programB), true); |
| }); |
| |
| test('FragmentProgram uniform info', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader(); |
| final List<UniformFloatSlot> slots = [ |
| shader.getUniformFloat('iFloatUniform'), |
| shader.getUniformFloat('iVec2Uniform', 0), |
| shader.getUniformFloat('iVec2Uniform', 1), |
| shader.getUniformFloat('iMat2Uniform', 0), |
| shader.getUniformFloat('iMat2Uniform', 1), |
| shader.getUniformFloat('iMat2Uniform', 2), |
| shader.getUniformFloat('iMat2Uniform', 3), |
| ]; |
| for (var i = 0; i < slots.length; ++i) { |
| expect(slots[i].shaderIndex, equals(i)); |
| } |
| }); |
| |
| test('FragmentProgram getUniformFloat unknown', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader(); |
| try { |
| shader.getUniformFloat('unknown'); |
| fail('Unreachable'); |
| } catch (e) { |
| expect(e.toString(), contains('No uniform named "unknown".')); |
| } |
| }); |
| |
| test('FragmentProgram getUniformFloat offset overflow', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader(); |
| 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 { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader(); |
| try { |
| shader.getUniformFloat('iVec2Uniform', -1); |
| fail('Unreachable'); |
| } catch (e) { |
| expect(e.toString(), contains('Index `-1` out of bounds for `iVec2Uniform`.')); |
| } |
| }); |
| |
| test('FragmentProgram getImageSampler', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniform_ordering.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader(); |
| final Image blueGreenImage = await _createBlueGreenImage(); |
| final ImageSamplerSlot slot = shader.getImageSampler('u_texture'); |
| slot.set(blueGreenImage); |
| expect(slot.shaderIndex, equals(0)); |
| }); |
| |
| test('FragmentProgram getImageSampler unknown', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniform_ordering.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader(); |
| try { |
| shader.getImageSampler('unknown'); |
| fail('Unreachable'); |
| } catch (e) { |
| expect(e.toString(), contains('No uniform named "unknown".')); |
| } |
| }); |
| |
| 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(); |
| final FragmentShader fragmentShader = program.fragmentShader(); |
| |
| try { |
| fragmentShader.setImageSampler(1, blueGreenImage); |
| fail('Unreachable'); |
| } catch (e) { |
| expect(e, contains('Sampler index out of bounds')); |
| } finally { |
| fragmentShader.dispose(); |
| blueGreenImage.dispose(); |
| } |
| }); |
| |
| test( |
| 'FragmentShader with sampler asserts if sampler is missing when assigned to paint', |
| () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'blue_green_sampler.frag.iplr', |
| ); |
| final FragmentShader fragmentShader = program.fragmentShader(); |
| |
| try { |
| Paint().shader = fragmentShader; |
| fail('Expected to throw'); |
| } catch (err) { |
| expect(err.toString(), contains('Invalid FragmentShader blue_green_sampler.frag.iplr')); |
| } finally { |
| fragmentShader.dispose(); |
| } |
| }, |
| ); |
| |
| test('FragmentShader setImageSampler asserts if image is disposed', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('blue_green_sampler.frag.iplr'); |
| final Image blueGreenImage = await _createBlueGreenImage(); |
| final FragmentShader fragmentShader = program.fragmentShader(); |
| |
| try { |
| blueGreenImage.dispose(); |
| expect( |
| () { |
| fragmentShader.setImageSampler(0, blueGreenImage); |
| }, |
| throwsA( |
| isA<AssertionError>().having( |
| (AssertionError e) => e.message, |
| 'message', |
| contains('Image has been disposed'), |
| ), |
| ), |
| ); |
| } finally { |
| fragmentShader.dispose(); |
| } |
| }); |
| |
| test('Disposed FragmentShader on Paint', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('blue_green_sampler.frag.iplr'); |
| final Image blueGreenImage = await _createBlueGreenImage(); |
| |
| final FragmentShader shader = program.fragmentShader()..setImageSampler(0, blueGreenImage); |
| shader.dispose(); |
| expect( |
| () { |
| Paint().shader = shader; |
| }, |
| throwsA( |
| isA<AssertionError>().having( |
| (AssertionError e) => e.message, |
| 'message', |
| contains('Attempted to set a disposed shader'), |
| ), |
| ), |
| ); |
| blueGreenImage.dispose(); |
| }); |
| |
| test('Disposed FragmentShader setFloat', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader()..setFloat(0, 0.0); |
| shader.dispose(); |
| |
| expect( |
| () { |
| shader.setFloat(0, 0.0); |
| }, |
| throwsA( |
| isA<AssertionError>().having( |
| (AssertionError e) => e.message, |
| 'message', |
| contains('Tried to accesss uniforms on a disposed Shader'), |
| ), |
| ), |
| ); |
| }); |
| |
| test('Disposed FragmentShader setImageSampler', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('blue_green_sampler.frag.iplr'); |
| final Image blueGreenImage = await _createBlueGreenImage(); |
| |
| final FragmentShader shader = program.fragmentShader()..setImageSampler(0, blueGreenImage); |
| shader.dispose(); |
| expect( |
| () { |
| shader.setImageSampler(0, blueGreenImage); |
| }, |
| throwsA( |
| isA<AssertionError>().having( |
| (AssertionError e) => e.message, |
| 'message', |
| contains('Tried to access uniforms on a disposed Shader'), |
| ), |
| ), |
| ); |
| blueGreenImage.dispose(); |
| }); |
| |
| test('Disposed FragmentShader dispose', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniforms.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader()..setFloat(0, 0.0); |
| shader.dispose(); |
| expect( |
| () { |
| shader.dispose(); |
| }, |
| throwsA( |
| isA<AssertionError>().having( |
| (AssertionError e) => e.message, |
| 'message', |
| contains('Shader cannot be disposed more than once'), |
| ), |
| ), |
| ); |
| }); |
| |
| 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); |
| await _expectShaderRendersGreen(shader); |
| |
| shader.setFloat(0, 0.0); |
| await _expectShaderRendersBlack(shader); |
| |
| shader.dispose(); |
| }); |
| |
| test('FragmentShader blue-green image renders green', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('blue_green_sampler.frag.iplr'); |
| final Image blueGreenImage = await _createBlueGreenImage(); |
| final FragmentShader shader = program.fragmentShader()..setImageSampler(0, blueGreenImage); |
| await _expectShaderRendersGreen(shader); |
| shader.dispose(); |
| blueGreenImage.dispose(); |
| }); |
| |
| test('FragmentShader blue-green image renders green - GPU image', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('blue_green_sampler.frag.iplr'); |
| final Image blueGreenImage = _createBlueGreenImageSync(); |
| final FragmentShader shader = program.fragmentShader()..setImageSampler(0, blueGreenImage); |
| await _expectShaderRendersGreen(shader); |
| shader.dispose(); |
| 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'); |
| |
| // The shader will not render green if the compiler doesn't keep the |
| // uniforms in the right order. |
| final FragmentShader shader = program.fragmentShader(); |
| for (var i = 0; i < 32; i++) { |
| shader.setFloat(i, i.toDouble()); |
| } |
| |
| await _expectShaderRendersGreen(shader); |
| |
| shader.dispose(); |
| }); |
| |
| test('FragmentShader Uniforms with interleaved textures are sorted ', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset('uniform_ordering.frag.iplr'); |
| |
| // The shader will not render green if the compiler doesn't keep the |
| // uniforms in the right order. |
| final FragmentShader shader = program.fragmentShader(); |
| shader.setFloat(0, 1); |
| shader.setFloat(1, 2); |
| shader.setFloat(2, 3); |
| |
| final Image blueGreenImage = _createBlueGreenImageSync(); |
| shader.setImageSampler(0, blueGreenImage); |
| |
| await _expectShaderRendersGreen(shader); |
| |
| shader.dispose(); |
| }); |
| |
| test('fromAsset throws an exception on invalid assetKey', () async { |
| var throws = false; |
| try { |
| await FragmentProgram.fromAsset('<invalid>'); |
| } catch (e) { |
| throws = true; |
| } |
| expect(throws, equals(true)); |
| }); |
| |
| test('fromAsset throws an exception on invalid data', () async { |
| var throws = false; |
| try { |
| await FragmentProgram.fromAsset('DashInNooglerHat.jpg'); |
| } catch (e) { |
| throws = true; |
| } |
| expect(throws, equals(true)); |
| }); |
| |
| 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', |
| ); |
| final FragmentShader shader = program.fragmentShader()..setFloat(0, 1.0); |
| await _expectShaderRendersGreen(shader); |
| shader.dispose(); |
| }); |
| |
| 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(); |
| }); |
| |
| test('ImageFilter.shader errors if shader does not have correct uniform layout', () async { |
| if (!impellerEnabled) { |
| print('Skipped for Skia'); |
| return; |
| } |
| const shaders = <String>[ |
| 'no_uniforms.frag.iplr', |
| 'missing_size.frag.iplr', |
| 'missing_texture.frag.iplr', |
| ]; |
| const errors = <(bool, bool)>[(true, true), (true, false), (false, false)]; |
| for (var i = 0; i < 3; i++) { |
| final String fileName = shaders[i]; |
| final FragmentProgram program = await FragmentProgram.fromAsset(fileName); |
| final FragmentShader shader = program.fragmentShader(); |
| |
| Object? error; |
| try { |
| ImageFilter.shader(shader); |
| } catch (err) { |
| error = err; |
| } |
| expect(error is StateError, true); |
| final (bool floatError, bool samplerError) = errors[i]; |
| if (floatError) { |
| expect(error.toString(), contains('shader has fewer than two float')); |
| } |
| if (samplerError) { |
| expect(error.toString(), contains('shader is missing a sampler uniform')); |
| } |
| } |
| }); |
| |
| 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(); |
| |
| // 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(12, 0); |
| shader.setFloat(13, 1.0); |
| shader.setFloat(14, 0); |
| |
| await _expectShaderRendersGreen(shader); |
| }); |
| |
| 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(); |
| final canvas = Canvas(recorder); |
| canvas.drawPaint( |
| Paint() |
| ..color = const Color(0xFFFF0000) |
| ..imageFilter = ImageFilter.shader(shader), |
| ); |
| final Image image = await recorder.endRecording().toImage(1, 1); |
| final ByteData data = (await image.toByteData())!; |
| final color = Color(data.buffer.asUint32List()[0]); |
| |
| expect(color, const Color(0xFF00FF00)); |
| }); |
| |
| // 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) { |
| print('Skipped for Skia'); |
| return; |
| } |
| final FragmentProgram program = await FragmentProgram.fromAsset('filter_shader.frag.iplr'); |
| final FragmentShader shader = program.fragmentShader(); |
| final filter = ImageFilter.shader(shader); |
| |
| // The same shader is equal to itself. |
| expect(filter, filter); |
| expect(identical(filter, filter), true); |
| |
| final filter_2 = ImageFilter.shader(shader); |
| |
| // The different shader is equal as long as uniforms are identical. |
| expect(filter, filter_2); |
| expect(identical(filter, filter_2), false); |
| |
| // Not equal if uniforms change. |
| shader.setFloat(0, 1); |
| final filter_3 = ImageFilter.shader(shader); |
| |
| expect(filter, isNot(filter_3)); |
| expect(identical(filter, filter_3), false); |
| }); |
| |
| if (impellerEnabled) { |
| print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823'); |
| return; |
| } |
| |
| // Test all supported GLSL ops. See lib/spirv/lib/src/constants.dart |
| final Map<String, FragmentProgram> 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<String, FragmentProgram> iplrSupportedOpShaders = await _loadShaderAssets( |
| path.join('supported_op_shaders', 'iplr'), |
| '.iplr', |
| ); |
| _expectFragmentShadersRenderGreen(iplrSupportedOpShaders); |
| } |
| |
| // Expect that all of the shaders in this folder render green. |
| // Keeping the outer loop of the test synchronous allows for easy printing |
| // of the file name within the test case. |
| void _expectFragmentShadersRenderGreen(Map<String, FragmentProgram> programs) { |
| if (programs.isEmpty) { |
| fail('No shaders found.'); |
| } |
| for (final String key in programs.keys) { |
| test('FragmentProgram $key renders green', () async { |
| final FragmentProgram program = programs[key]!; |
| final FragmentShader shader = program.fragmentShader()..setFloat(0, 1.0); |
| await _expectShaderRendersGreen(shader); |
| shader.dispose(); |
| }); |
| } |
| } |
| |
| Future<void> _expectShaderRendersColor(Shader shader, Color color) async { |
| final ByteData renderedBytes = (await _imageByteDataFromShader( |
| shader: shader, |
| imageDimension: _shaderImageDimension, |
| ))!; |
| for (final int c in renderedBytes.buffer.asUint32List()) { |
| expect(toHexString(c), toHexString(color.value)); |
| } |
| } |
| |
| // Expects that a shader only outputs the color green. |
| Future<void> _expectShaderRendersGreen(Shader shader) { |
| return _expectShaderRendersColor(shader, _greenColor); |
| } |
| |
| Future<void> _expectShaderRendersBlack(Shader shader) { |
| return _expectShaderRendersColor(shader, _blackColor); |
| } |
| |
| Future<ByteData?> _imageByteDataFromShader({ |
| required Shader shader, |
| int imageDimension = 100, |
| }) async { |
| final Image image = await _imageFromShader(shader: shader, imageDimension: imageDimension); |
| return image.toByteData(); |
| } |
| |
| Future<Image> _imageFromShader({required Shader shader, required int imageDimension}) { |
| final recorder = PictureRecorder(); |
| final canvas = Canvas(recorder); |
| final paint = Paint()..shader = shader; |
| canvas.drawPaint(paint); |
| final Picture picture = recorder.endRecording(); |
| return picture.toImage(imageDimension, imageDimension); |
| } |
| |
| // Loads the path and spirv content of the files at |
| // $FLUTTER_BUILD_DIRECTORY/gen/flutter/lib/spirv/test/$leafFolderName |
| // This is synchronous so that tests can be inside of a loop with |
| // the proper test name. |
| Future<Map<String, FragmentProgram>> _loadShaderAssets(String leafFolderName, String ext) async { |
| final Map<String, FragmentProgram> out = SplayTreeMap<String, FragmentProgram>(); |
| |
| final Directory directory = shaderDirectory(leafFolderName); |
| if (!directory.existsSync()) { |
| return out; |
| } |
| |
| await Future.forEach( |
| directory.listSync().where((FileSystemEntity entry) => path.extension(entry.path) == ext), |
| (FileSystemEntity entry) async { |
| final String key = path.basenameWithoutExtension(entry.path); |
| out[key] = await FragmentProgram.fromAsset(path.basename(entry.path)); |
| }, |
| ); |
| return out; |
| } |
| |
| // Arbitrary, but needs to be greater than 1 for frag coord tests. |
| const int _shaderImageDimension = 4; |
| |
| const Color _greenColor = Color(0xFF00FF00); |
| const Color _blackColor = Color(0xFF000000); |
| |
| // Precision for checking uniform values. |
| const double epsilon = 0.5 / 255.0; |
| |
| // Maps an int value from 0-255 to a double value of 0.0 to 1.0. |
| double toFloat(int v) => v.toDouble() / 255.0; |
| |
| String toHexString(int color) => '#${color.toRadixString(16)}'; |
| |
| // 10x10 image where the left half is blue and the right half is |
| // green. |
| Future<Image> _createBlueGreenImage() async { |
| const length = 10; |
| const bytesPerPixel = 4; |
| final pixels = Uint8List(length * length * bytesPerPixel); |
| var i = 0; |
| for (var y = 0; y < length; y++) { |
| for (var x = 0; x < length; x++) { |
| if (x < length / 2) { |
| pixels[i + 2] = 0xFF; // blue channel |
| } else { |
| pixels[i + 1] = 0xFF; // green channel |
| } |
| pixels[i + 3] = 0xFF; // alpha channel |
| i += bytesPerPixel; |
| } |
| } |
| final descriptor = ImageDescriptor.raw( |
| await ImmutableBuffer.fromUint8List(pixels), |
| width: length, |
| height: length, |
| pixelFormat: PixelFormat.rgba8888, |
| ); |
| final Codec codec = await descriptor.instantiateCodec(); |
| final FrameInfo frame = await codec.getNextFrame(); |
| codec.dispose(); |
| return frame.image; |
| } |
| |
| // A 10x10 image where the left half is blue and the right half is green. |
| Image _createBlueGreenImageSync() { |
| final recorder = PictureRecorder(); |
| final canvas = Canvas(recorder); |
| canvas.drawRect(const Rect.fromLTWH(0, 0, 5, 10), Paint()..color = const Color(0xFF0000FF)); |
| canvas.drawRect(const Rect.fromLTWH(5, 0, 5, 10), Paint()..color = const Color(0xFF00FF00)); |
| final Picture picture = recorder.endRecording(); |
| try { |
| return picture.toImageSync(10, 10); |
| } finally { |
| picture.dispose(); |
| } |
| } |
| |
| // Image of an oval painted with a linear gradient. |
| Image _createOvalGradientImage({required int imageDimension}) { |
| final recorder = PictureRecorder(); |
| final canvas = Canvas(recorder); |
| canvas.drawPaint(Paint()..color = const Color(0xFF000000)); |
| canvas.drawOval( |
| Rect.fromCenter( |
| center: Offset(imageDimension * 0.5, imageDimension * 0.5), |
| width: imageDimension * 0.6, |
| height: imageDimension * 0.9, |
| ), |
| Paint() |
| ..shader = Gradient.linear( |
| Offset.zero, |
| Offset(imageDimension.toDouble(), imageDimension.toDouble()), |
| [const Color(0xFFFF0000), const Color(0xFF00FF00)], |
| ), |
| ); |
| final Picture picture = recorder.endRecording(); |
| try { |
| return picture.toImageSync(imageDimension, imageDimension); |
| } finally { |
| picture.dispose(); |
| } |
| } |