| // 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:io'; |
| import 'dart:typed_data'; |
| import 'dart:ui'; |
| |
| import 'package:litetest/litetest.dart'; |
| import 'package:path/path.dart' as path; |
| |
| import 'shader_test_file_utils.dart'; |
| |
| void main() async { |
| test('simple shader renders correctly', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'functions.frag.iplr', |
| ); |
| final Shader shader = program.shader( |
| floatUniforms: Float32List.fromList(<double>[1]), |
| ); |
| _expectShaderRendersGreen(shader); |
| }); |
| |
| test('simple shader with space in key renders correctly', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'functions with_space.frag.iplr', |
| ); |
| final Shader shader = program.shader( |
| floatUniforms: Float32List.fromList(<double>[1]), |
| ); |
| _expectShaderRendersGreen(shader); |
| }); |
| |
| test('blue-green image renders green', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'blue_green_sampler.frag.iplr', |
| ); |
| final Image blueGreenImage = await _createBlueGreenImage(); |
| final ImageShader imageShader = ImageShader( |
| blueGreenImage, TileMode.clamp, TileMode.clamp, _identityMatrix); |
| final Shader shader = program.shader( |
| floatUniforms: Float32List.fromList(<double>[]), |
| samplerUniforms: <ImageShader>[imageShader], |
| ); |
| await _expectShaderRendersGreen(shader); |
| }); |
| |
| test('shader with uniforms renders correctly', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'uniforms.frag.iplr', |
| ); |
| |
| final Shader shader = program.shader( |
| floatUniforms: Float32List.fromList(<double>[ |
| 0.0, // iFloatUniform |
| 0.25, // iVec2Uniform.x |
| 0.75, // iVec2Uniform.y |
| 0.0, // iMat2Uniform[0][0] |
| 0.0, // iMat2Uniform[0][1] |
| 0.0, // iMat2Uniform[1][0] |
| 1.0, // iMat2Uniform[1][1] |
| ])); |
| |
| 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)); |
| }); |
| |
| test('shader with array uniforms renders correctly', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'uniform_arrays.frag.iplr', |
| ); |
| |
| final List<double> floatArray = List<double>.generate( |
| 24, (int i) => i.toDouble(), |
| ); |
| final Shader shader = program.shader( |
| floatUniforms: Float32List.fromList(<double>[ |
| ...floatArray, |
| ])); |
| |
| await _expectShaderRendersGreen(shader); |
| }); |
| |
| test('The ink_sparkle shader is accepted', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'ink_sparkle.frag.iplr', |
| ); |
| final Shader shader = program.shader( |
| floatUniforms: Float32List(32), |
| ); |
| |
| await _imageByteDataFromShader(shader: shader); |
| |
| // Testing that no exceptions are thrown. Tests that the ink_sparkle shader |
| // produces the correct pixels are in the framework. |
| }); |
| |
| test('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 Shader shader = program.shader( |
| floatUniforms: Float32List.fromList( |
| List<double>.generate(32, (int i) => i.toDouble()), |
| ), |
| ); |
| |
| await _expectShaderRendersGreen(shader); |
| }); |
| |
| test('fromAsset throws an exception on invalid assetKey', () async { |
| bool throws = false; |
| try { |
| await FragmentProgram.fromAsset( |
| '<invalid>', |
| ); |
| } catch (e) { |
| throws = true; |
| } |
| expect(throws, equals(true)); |
| }); |
| |
| test('fromAsset throws an exception on invalid data', () async { |
| bool throws = false; |
| try { |
| await FragmentProgram.fromAsset( |
| 'DashInNooglerHat.jpg', |
| ); |
| } catch (e) { |
| throws = true; |
| } |
| expect(throws, equals(true)); |
| }); |
| |
| test('user defined functions do not redefine builtins', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'no_builtin_redefinition.frag.iplr', |
| ); |
| final Shader shader = program.shader( |
| floatUniforms: Float32List.fromList(<double>[1.0]), |
| ); |
| await _expectShaderRendersGreen(shader); |
| }); |
| |
| test('fromAsset accepts a shader with no uniforms', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'no_uniforms.frag.iplr', |
| ); |
| final Shader shader = program.shader(); |
| await _expectShaderRendersGreen(shader); |
| }); |
| |
| // 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', |
| ); |
| expect(iplrSupportedGLSLOpShaders.isNotEmpty, true); |
| _expectIplrShadersRenderGreen(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', |
| ); |
| expect(iplrSupportedOpShaders.isNotEmpty, true); |
| _expectIplrShadersRenderGreen(iplrSupportedOpShaders); |
| |
| test('Equality depends on floatUniforms', () async { |
| final FragmentProgram program = await FragmentProgram.fromAsset( |
| 'simple.frag.iplr', |
| ); |
| final Float32List ones = Float32List.fromList(<double>[1]); |
| final Float32List zeroes = Float32List.fromList(<double>[0]); |
| |
| { |
| final Shader a = program.shader(floatUniforms: ones); |
| final Shader b = program.shader(floatUniforms: ones); |
| expect(a, b); |
| expect(a.hashCode, b.hashCode); |
| } |
| |
| { |
| final Shader a = program.shader(floatUniforms: ones); |
| final Shader b = program.shader(floatUniforms: zeroes); |
| expect(a, notEquals(b)); |
| expect(a.hashCode, notEquals(b.hashCode)); |
| } |
| }); |
| |
| test('Equality depends on data', () async { |
| final FragmentProgram programA = await FragmentProgram.fromAsset( |
| 'simple.frag.iplr', |
| ); |
| final FragmentProgram programB = await FragmentProgram.fromAsset( |
| 'uniforms.frag.iplr', |
| ); |
| final Shader a = programA.shader(); |
| final Shader b = programB.shader(); |
| |
| expect(a, notEquals(b)); |
| expect(a.hashCode, notEquals(b.hashCode)); |
| }); |
| } |
| |
| // 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 _expectIplrShadersRenderGreen(Map<String, FragmentProgram> shaders) { |
| for (final String key in shaders.keys) { |
| test('iplr $key renders green', () async { |
| final FragmentProgram program = shaders[key]!; |
| final Shader shader = program.shader( |
| floatUniforms: Float32List.fromList(<double>[1]), |
| ); |
| _expectShaderRendersGreen(shader); |
| }); |
| } |
| } |
| |
| // Expects that a spirv shader only outputs the color green. |
| Future<void> _expectShaderRendersGreen(Shader shader) async { |
| final ByteData renderedBytes = (await _imageByteDataFromShader( |
| shader: shader, |
| imageDimension: _shaderImageDimension, |
| ))!; |
| for (final int color in renderedBytes.buffer.asUint32List()) { |
| expect(toHexString(color), toHexString(_greenColor.value)); |
| } |
| } |
| |
| Future<ByteData?> _imageByteDataFromShader({ |
| required Shader shader, |
| int imageDimension = 100, |
| }) async { |
| final PictureRecorder recorder = PictureRecorder(); |
| final Canvas canvas = Canvas(recorder); |
| final Paint paint = Paint()..shader = shader; |
| canvas.drawPaint(paint); |
| final Picture picture = recorder.endRecording(); |
| final Image image = await picture.toImage( |
| imageDimension, |
| imageDimension, |
| ); |
| return image.toByteData(); |
| } |
| |
| // 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); |
| |
| // 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 int length = 10; |
| const int bytesPerPixel = 4; |
| final Uint8List pixels = Uint8List(length * length * bytesPerPixel); |
| int i = 0; |
| for (int y = 0; y < length; y++) { |
| for (int 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 ImageDescriptor 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(); |
| return frame.image; |
| } |
| |
| final Float64List _identityMatrix = Float64List.fromList(<double>[ |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0, |
| 0, 0, 0, 1, |
| ]); |