blob: f2e6fce7a55c1e7e26072d171de87e9575ea2a8b [file] [log] [blame]
// 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() {
test('throws exception for invalid shader', () async {
final ByteBuffer invalidBytes = Uint8List.fromList(<int>[1, 2, 3, 4, 5]).buffer;
try {
await FragmentProgram.compile(spirv: invalidBytes);
fail('expected compile to throw an exception');
} catch (_) {
}
});
test('simple shader renders correctly', () async {
final Uint8List shaderBytes = await spvFile('general_shaders', 'functions.spv').readAsBytes();
final FragmentProgram program = await FragmentProgram.compile(
spirv: shaderBytes.buffer,
);
final Shader shader = program.shader(
floatUniforms: Float32List.fromList(<double>[1]),
);
_expectShaderRendersGreen(shader);
});
test('shader with functions renders green', () async {
final ByteBuffer spirv = spvFile('general_shaders', 'functions.spv').readAsBytesSync().buffer;
final FragmentProgram program = await FragmentProgram.compile(
spirv: spirv,
);
final Shader shader = program.shader(
floatUniforms: Float32List.fromList(<double>[1]),
);
_expectShaderRendersGreen(shader);
});
test('blue-green image renders green', () async {
final ByteBuffer spirv = spvFile('general_shaders', 'blue_green_sampler.spv').readAsBytesSync().buffer;
final FragmentProgram program = await FragmentProgram.compile(
debugPrint: true,
spirv: spirv,
);
final Image blueGreenImage = await _createBlueGreenImage();
final ImageShader imageShader = ImageShader(
blueGreenImage, TileMode.clamp, TileMode.clamp, _identityMatrix);
final Shader shader = program.shader(
samplerUniforms: <ImageShader>[imageShader],
);
await _expectShaderRendersGreen(shader);
});
test('shader with uniforms renders correctly', () async {
final Uint8List shaderBytes = await spvFile('general_shaders', 'uniforms.spv').readAsBytes();
final FragmentProgram program = await FragmentProgram.compile(spirv: shaderBytes.buffer);
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 all supported GLSL ops. See lib/spirv/lib/src/constants.dart
final Map<String, ByteBuffer> supportedGLSLOpShaders =
_loadSpv('supported_glsl_op_shaders');
expect(supportedGLSLOpShaders.isNotEmpty, true);
_expectShadersRenderGreen(supportedGLSLOpShaders);
_expectShadersHaveOp(supportedGLSLOpShaders, true /* glsl ops */);
// Test all supported instructions. See lib/spirv/lib/src/constants.dart
final Map<String, ByteBuffer> supportedOpShaders =
_loadSpv('supported_op_shaders');
expect(supportedOpShaders.isNotEmpty, true);
_expectShadersRenderGreen(supportedOpShaders);
_expectShadersHaveOp(supportedOpShaders, false /* glsl ops */);
test('equality depends on floatUniforms', () async {
final ByteBuffer spirv = spvFile('general_shaders', 'simple.spv')
.readAsBytesSync().buffer;
final FragmentProgram program = await FragmentProgram.compile(spirv: spirv);
final Float32List ones = Float32List.fromList(<double>[1]);
final Float32List zeroes = Float32List.fromList(<double>[0]);
{
final a = program.shader(floatUniforms: ones);
final b = program.shader(floatUniforms: ones);
expect(a, b);
expect(a.hashCode, b.hashCode);
}
{
final a = program.shader(floatUniforms: ones);
final b = program.shader(floatUniforms: zeroes);
expect(a, notEquals(b));
expect(a.hashCode, notEquals(b.hashCode));
}
});
test('equality depends on spirv', () async {
final ByteBuffer spirvA = spvFile('general_shaders', 'simple.spv')
.readAsBytesSync().buffer;
final ByteBuffer spirvB = spvFile('general_shaders', 'uniforms.spv')
.readAsBytesSync().buffer;
final FragmentProgram programA = await FragmentProgram.compile(spirv: spirvA);
final FragmentProgram programB = await FragmentProgram.compile(spirv: spirvB);
final a = programA.shader();
final b = programB.shader();
expect(a, notEquals(b));
expect(a.hashCode, notEquals(b.hashCode));
});
}
// Expect that all of the spirv 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 _expectShadersRenderGreen(Map<String, ByteBuffer> shaders) {
for (final String key in shaders.keys) {
test('$key renders green', () async {
final FragmentProgram program = await FragmentProgram.compile(
spirv: shaders[key]!,
);
final Shader shader = program.shader(
floatUniforms: Float32List.fromList(<double>[1]),
);
_expectShaderRendersGreen(shader);
});
}
}
void _expectShadersHaveOp(Map<String, ByteBuffer> shaders, bool glsl) {
for (final String key in shaders.keys) {
test('$key contains opcode', () {
_expectShaderHasOp(shaders[key]!, key, glsl);
});
}
}
const int _opExtInst = 12;
// Expects that a spirv shader has the op code identified by its file name.
void _expectShaderHasOp(ByteBuffer spirv, String filename, bool glsl) {
final Uint32List words = spirv.asUint32List();
final List<String> sections = filename.split('_');
expect(sections.length, greaterThan(1));
final int op = int.parse(sections.first);
// skip the header
int position = 5;
bool found = false;
while (position < words.length) {
final int word = words[position];
final int currentOpCode = word & 0xFFFF;
if (glsl) {
if (currentOpCode == _opExtInst && words[position + 4] == op) {
found = true;
break;
}
} else {
if (currentOpCode == op) {
found = true;
break;
}
}
final int advance = word >> 16;
if (advance <= 0) {
break;
}
position += advance;
}
expect(found, true);
}
// 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.
Map<String, ByteBuffer> _loadSpv(String leafFolderName) {
final Map<String, ByteBuffer> out = SplayTreeMap<String, ByteBuffer>();
final Directory directory = spvDirectory(leafFolderName);
if (!directory.existsSync()) {
return out;
}
directory
.listSync()
.where((FileSystemEntity entry) => path.extension(entry.path) == '.spv')
.forEach((FileSystemEntity entry) {
final String key = path.basenameWithoutExtension(entry.path);
out[key] = (entry as File).readAsBytesSync().buffer;
});
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 _blueColor = Color(0xFF0000FF);
// 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 {
final int length = 10;
final 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;
}
// A single uniform with value 1.
final Float32List _singleUniform = Float32List.fromList(<double>[1]);
final Float64List _identityMatrix = Float64List.fromList(<double>[
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]);