mirror of
https://github.com/flutter/flutter.git
synced 2026-01-09 07:51:35 +08:00
Moved high bitrate texture tests to engine (#179906)
This adds a few goldens where skia supports the file format. This is a bit more scalable and direct than managing integration tests. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
052435ce7b
commit
5632701286
10
.ci.yaml
10
.ci.yaml
@ -5401,16 +5401,6 @@ targets:
|
||||
["devicelab", "ios", "mac"]
|
||||
task_name: wide_gamut_ios
|
||||
|
||||
- name: Mac_ios high_bitrate_images
|
||||
recipe: devicelab/devicelab_drone
|
||||
presubmit: true
|
||||
bringup: true
|
||||
timeout: 60
|
||||
properties:
|
||||
tags: >
|
||||
["devicelab", "ios", "mac"]
|
||||
task_name: high_bitrate_images_ios
|
||||
|
||||
- name: Mac_x64_ios hot_mode_dev_cycle_ios__benchmark
|
||||
recipe: devicelab/devicelab_drone
|
||||
presubmit: false
|
||||
|
||||
@ -79,7 +79,6 @@
|
||||
/dev/devicelab/bin/tasks/gradient_dynamic_perf__e2e_summary.dart @flar @flutter/engine
|
||||
/dev/devicelab/bin/tasks/gradient_static_perf__e2e_summary.dart @flar @flutter/engine
|
||||
/dev/devicelab/bin/tasks/gradle_java8_compile_test.dart @jesswrd @flutter/android
|
||||
/dev/devicelab/bin/tasks/high_bitrate_images_ios.dart @gaaclarke @flutter/engine
|
||||
/dev/devicelab/bin/tasks/hot_mode_dev_cycle_linux__benchmark.dart @bkonyi @flutter/tool
|
||||
/dev/devicelab/bin/tasks/hybrid_android_views_integration_test.dart @gmackall @flutter/android
|
||||
/dev/devicelab/bin/tasks/image_list_jit_reported_duration.dart @jtmcdole @flutter/engine
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
// Copyright 2014 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 'package:flutter_devicelab/framework/devices.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
import 'package:flutter_devicelab/tasks/integration_tests.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
deviceOperatingSystem = DeviceOperatingSystem.ios;
|
||||
await task(createHighBitrateImagesTest());
|
||||
}
|
||||
@ -247,14 +247,6 @@ TaskFunction createWideGamutTest() {
|
||||
).call;
|
||||
}
|
||||
|
||||
TaskFunction createHighBitrateImagesTest() {
|
||||
return IntegrationTest(
|
||||
'${flutterDirectory.path}/dev/integration_tests/high_bitrate_images',
|
||||
'integration_test/app_test.dart',
|
||||
createPlatforms: <String>['ios'],
|
||||
).call;
|
||||
}
|
||||
|
||||
class DriverTest {
|
||||
DriverTest(
|
||||
this.testDirectory,
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
# high_bitrate_images
|
||||
|
||||
An integration test used for testing high bitrate image support in the engine.
|
||||
|
||||
## Local run
|
||||
|
||||
```sh
|
||||
flutter create --platforms="ios" --no-overwrite .
|
||||
flutter test integration_test/app_test.dart
|
||||
```
|
||||
@ -1,107 +0,0 @@
|
||||
// Copyright 2014 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' show Completer;
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:high_bitrate_images/main.dart' as app;
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
// See: https://developer.apple.com/documentation/metal/mtlpixelformat/mtlpixelformatbgr10_xr.
|
||||
double _decodeBGR10(int x) {
|
||||
const max = 1.25098;
|
||||
const min = -0.752941;
|
||||
const intercept = min;
|
||||
const double slope = (max - min) / 1024.0;
|
||||
return (x * slope) + intercept;
|
||||
}
|
||||
|
||||
Uint8List _convertBGRA10XRToBGRA8888(Uint8List bgra10xr) {
|
||||
final inputByteData = ByteData.sublistView(bgra10xr);
|
||||
final bgra8888 = Uint8List(bgra10xr.lengthInBytes ~/ 2); // 8 bytes per pixel -> 4 bytes per pixel
|
||||
final outputByteData = ByteData.view(bgra8888.buffer);
|
||||
|
||||
for (var i = 0, j = 0; i < bgra10xr.lengthInBytes; i += 8, j += 4) {
|
||||
final int pixel = inputByteData.getUint64(i, Endian.host);
|
||||
|
||||
final double blue10 = _decodeBGR10((pixel >> 6) & 0x3ff);
|
||||
final double green10 = _decodeBGR10((pixel >> 22) & 0x3ff);
|
||||
final double red10 = _decodeBGR10((pixel >> 38) & 0x3ff);
|
||||
|
||||
final int blue8 = (blue10.clamp(0.0, 1.0) * 255).round();
|
||||
final int green8 = (green10.clamp(0.0, 1.0) * 255).round();
|
||||
final int red8 = (red10.clamp(0.0, 1.0) * 255).round();
|
||||
const alpha8 = 255; // Assuming opaque for BGRA8888
|
||||
|
||||
final int bgra8888Pixel = (alpha8 << 24) | (red8 << 16) | (green8 << 8) | blue8;
|
||||
outputByteData.setUint32(j, bgra8888Pixel, Endian.host);
|
||||
}
|
||||
return bgra8888;
|
||||
}
|
||||
|
||||
Future<ui.Image> _getScreenshot() async {
|
||||
const channel = MethodChannel('flutter/screenshot');
|
||||
final result = await channel.invokeMethod('test') as List<Object?>;
|
||||
|
||||
expect(result, isNotNull);
|
||||
expect(result.length, 4);
|
||||
final [int width, int height, String format, Uint8List bytes] = result as List<dynamic>;
|
||||
|
||||
expect(format, equals('MTLPixelFormatBGRA10_XR'));
|
||||
|
||||
final completer = Completer<ui.Image>();
|
||||
final Uint8List pixels = _convertBGRA10XRToBGRA8888(bytes);
|
||||
ui.decodeImageFromPixels(
|
||||
pixels,
|
||||
width,
|
||||
height,
|
||||
ui.PixelFormat.bgra8888,
|
||||
(ui.Image image) => completer.complete(image),
|
||||
);
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('end-to-end test', () {
|
||||
testWidgets('renders cpu sdfs with rgba32f', (WidgetTester tester) async {
|
||||
app.testToRun = app.TestType.cpuRgba32fSdf;
|
||||
app.main();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
await _getScreenshot();
|
||||
// TODO(gaaclarke): Turn this into a golden test. This turned out to be
|
||||
// quite involved so it's deferred.
|
||||
// expect(
|
||||
// screenshot,
|
||||
// matchesGoldenFile('high_bitrate_images.rbga32f'),
|
||||
// );
|
||||
});
|
||||
|
||||
testWidgets('renders cpu sdfs with r32f', (WidgetTester tester) async {
|
||||
app.testToRun = app.TestType.cpuR32fSdf;
|
||||
app.main();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
await _getScreenshot();
|
||||
});
|
||||
|
||||
testWidgets('renders gpu sdfs with rgba32f', (WidgetTester tester) async {
|
||||
app.testToRun = app.TestType.gpuRgba32fSdf;
|
||||
app.main();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
await _getScreenshot();
|
||||
});
|
||||
|
||||
testWidgets('renders gpu sdfs with r32f', (WidgetTester tester) async {
|
||||
app.testToRun = app.TestType.gpuR32fSdf;
|
||||
app.main();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
await _getScreenshot();
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,154 +0,0 @@
|
||||
// Copyright 2014 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:math' show sqrt;
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CpuSdfCanvas extends StatefulWidget {
|
||||
const CpuSdfCanvas({super.key, required this.targetFormat});
|
||||
|
||||
final ui.TargetPixelFormat targetFormat;
|
||||
|
||||
@override
|
||||
State<CpuSdfCanvas> createState() => _CpuSdfCanvasState();
|
||||
}
|
||||
|
||||
class _CpuSdfCanvasState extends State<CpuSdfCanvas> {
|
||||
ui.FragmentShader? _shader;
|
||||
ui.Image? _sdfImage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadShader().then((ui.FragmentShader shader) {
|
||||
setState(() {
|
||||
_shader = shader;
|
||||
});
|
||||
});
|
||||
switch (widget.targetFormat) {
|
||||
case ui.TargetPixelFormat.rgbaFloat32:
|
||||
_loadRGBA32FloatSdfImage().then((ui.Image image) {
|
||||
setState(() {
|
||||
_sdfImage = image;
|
||||
});
|
||||
});
|
||||
case ui.TargetPixelFormat.rFloat32:
|
||||
_loadR32FloatSdfImage().then((ui.Image image) {
|
||||
setState(() {
|
||||
_sdfImage = image;
|
||||
});
|
||||
});
|
||||
case ui.TargetPixelFormat.dontCare:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<ui.FragmentShader> _loadShader() async {
|
||||
final ui.FragmentProgram program = await ui.FragmentProgram.fromAsset('shaders/sdf.frag');
|
||||
return program.fragmentShader();
|
||||
}
|
||||
|
||||
Future<ui.Image> _loadRGBA32FloatSdfImage() async {
|
||||
const width = 1024;
|
||||
const height = 1024;
|
||||
const double radius = width / 4.0;
|
||||
final floats = List<double>.filled(width * height * 4, 0.0);
|
||||
for (var i = 0; i < height; ++i) {
|
||||
for (var j = 0; j < width; ++j) {
|
||||
double x = j.toDouble();
|
||||
double y = i.toDouble();
|
||||
x -= width / 2.0;
|
||||
y -= height / 2.0;
|
||||
final double length = sqrt(x * x + y * y);
|
||||
final int idx = i * width * 4 + j * 4;
|
||||
floats[idx + 0] = length - radius;
|
||||
floats[idx + 1] = 0.0;
|
||||
floats[idx + 2] = 0.0;
|
||||
floats[idx + 3] = 1.0;
|
||||
}
|
||||
}
|
||||
final floatList = Float32List.fromList(floats);
|
||||
final intList = Uint8List.view(floatList.buffer);
|
||||
final completer = Completer<ui.Image>();
|
||||
ui.decodeImageFromPixels(
|
||||
intList,
|
||||
width,
|
||||
height,
|
||||
ui.PixelFormat.rgbaFloat32,
|
||||
targetFormat: widget.targetFormat,
|
||||
(ui.Image image) {
|
||||
completer.complete(image);
|
||||
},
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<ui.Image> _loadR32FloatSdfImage() async {
|
||||
const width = 1024;
|
||||
const height = 1024;
|
||||
const double radius = width / 4.0;
|
||||
final floats = List<double>.filled(width * height, 0.0);
|
||||
for (var i = 0; i < height; ++i) {
|
||||
for (var j = 0; j < width; ++j) {
|
||||
double x = j.toDouble();
|
||||
double y = i.toDouble();
|
||||
x -= width / 2.0;
|
||||
y -= height / 2.0;
|
||||
final double length = sqrt(x * x + y * y);
|
||||
final int idx = i * width + j;
|
||||
floats[idx] = length - radius;
|
||||
}
|
||||
}
|
||||
final floatList = Float32List.fromList(floats);
|
||||
final intList = Uint8List.view(floatList.buffer);
|
||||
final completer = Completer<ui.Image>();
|
||||
ui.decodeImageFromPixels(
|
||||
intList,
|
||||
width,
|
||||
height,
|
||||
ui.PixelFormat.rFloat32,
|
||||
targetFormat: ui.TargetPixelFormat.rFloat32,
|
||||
(ui.Image image) {
|
||||
completer.complete(image);
|
||||
},
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_shader == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return SizedBox.expand(
|
||||
child: (_shader != null && _sdfImage != null)
|
||||
? CustomPaint(painter: CpuSdfPainter(_shader!, _sdfImage!))
|
||||
: Container(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CpuSdfPainter extends CustomPainter {
|
||||
CpuSdfPainter(this.shader, this.image);
|
||||
|
||||
final ui.FragmentShader shader;
|
||||
final ui.Image image;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
shader.setFloat(0, size.width);
|
||||
shader.setFloat(1, size.height);
|
||||
shader.setImageSampler(0, image);
|
||||
final paint = Paint()..shader = shader;
|
||||
canvas.drawRect(Offset.zero & size, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
// Copyright 2014 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:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GpuSdfCanvas extends StatefulWidget {
|
||||
const GpuSdfCanvas({super.key, required this.targetFormat});
|
||||
|
||||
final ui.TargetPixelFormat targetFormat;
|
||||
|
||||
@override
|
||||
State<GpuSdfCanvas> createState() => _GpuSdfCanvasState();
|
||||
}
|
||||
|
||||
class _GpuSdfCanvasState extends State<GpuSdfCanvas> {
|
||||
ui.Image? _image;
|
||||
ui.FragmentShader? _circle;
|
||||
ui.FragmentShader? _sdfShader;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadCircleShader().then((ui.FragmentShader shader) {
|
||||
setState(() {
|
||||
_circle = shader;
|
||||
});
|
||||
});
|
||||
_loadSdfShader().then((ui.FragmentShader shader) {
|
||||
setState(() {
|
||||
_sdfShader = shader;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<ui.FragmentShader> _loadCircleShader() async {
|
||||
final ui.FragmentProgram program = await ui.FragmentProgram.fromAsset(
|
||||
'shaders/circle_sdf.frag',
|
||||
);
|
||||
return program.fragmentShader();
|
||||
}
|
||||
|
||||
Future<ui.FragmentShader> _loadSdfShader() async {
|
||||
final ui.FragmentProgram program = await ui.FragmentProgram.fromAsset('shaders/sdf.frag');
|
||||
return program.fragmentShader();
|
||||
}
|
||||
|
||||
ui.Image _loadImage(ui.FragmentShader shader) {
|
||||
const size = Size(512, 512);
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
shader.setFloat(0, size.width);
|
||||
shader.setFloat(1, size.height);
|
||||
final paint = Paint()..shader = _circle;
|
||||
canvas.drawRect(Offset.zero & size, paint);
|
||||
final ui.Picture picture = recorder.endRecording();
|
||||
return picture.toImageSync(
|
||||
size.width.toInt(),
|
||||
size.height.toInt(),
|
||||
targetFormat: widget.targetFormat,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_circle == null || _sdfShader == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
_image ??= _loadImage(_circle!);
|
||||
return SizedBox.expand(child: CustomPaint(painter: GpuSdfPainter(_image!, _sdfShader!)));
|
||||
}
|
||||
}
|
||||
|
||||
class GpuSdfPainter extends CustomPainter {
|
||||
GpuSdfPainter(this.image, this.shader);
|
||||
|
||||
final ui.Image image;
|
||||
final ui.FragmentShader shader;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
shader.setFloat(0, size.width);
|
||||
shader.setFloat(1, size.height);
|
||||
shader.setImageSampler(0, image);
|
||||
final paint = Paint()..shader = shader;
|
||||
canvas.drawRect(Offset.zero & size, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
// Copyright 2014 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:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'cpu_sdf_canvas.dart';
|
||||
import 'gpu_sdf_canvas.dart';
|
||||
|
||||
enum TestType { cpuR32fSdf, cpuRgba32fSdf, gpuR32fSdf, gpuRgba32fSdf }
|
||||
|
||||
TestType testToRun = TestType.cpuR32fSdf;
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(title: 'SDF Demo', theme: ThemeData.dark(), home: const MyHomePage());
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key});
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget child;
|
||||
switch (testToRun) {
|
||||
case TestType.cpuR32fSdf:
|
||||
child = const CpuSdfCanvas(targetFormat: ui.TargetPixelFormat.rFloat32);
|
||||
case TestType.cpuRgba32fSdf:
|
||||
child = const CpuSdfCanvas(targetFormat: ui.TargetPixelFormat.rgbaFloat32);
|
||||
case TestType.gpuR32fSdf:
|
||||
child = const GpuSdfCanvas(targetFormat: ui.TargetPixelFormat.rFloat32);
|
||||
case TestType.gpuRgba32fSdf:
|
||||
child = const GpuSdfCanvas(targetFormat: ui.TargetPixelFormat.rgbaFloat32);
|
||||
}
|
||||
return Scaffold(body: child);
|
||||
}
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
name: high_bitrate_images
|
||||
description: "A new Flutter project."
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.0-88.0.dev
|
||||
|
||||
resolution: workspace
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
shaders:
|
||||
- shaders/circle_sdf.frag
|
||||
- shaders/sdf.frag
|
||||
|
||||
# PUBSPEC CHECKSUM: vrimb3
|
||||
@ -1,18 +0,0 @@
|
||||
#version 320 es
|
||||
|
||||
// Copyright 2014 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 <flutter/runtime_effect.glsl>
|
||||
|
||||
out vec4 fragColor;
|
||||
uniform vec2 uSize;
|
||||
|
||||
void main() {
|
||||
vec2 p = FlutterFragCoord().xy / uSize;
|
||||
vec2 center = vec2(0.5, 0.5);
|
||||
float radius = 0.25;
|
||||
float d = length(p - center) - radius;
|
||||
fragColor = vec4(d, 0.0, 0.0, 1.0);
|
||||
}
|
||||
@ -43,9 +43,30 @@ if (enable_unittests) {
|
||||
iplr = true
|
||||
}
|
||||
|
||||
impellerc("compile_sdf_shaders") {
|
||||
mnemonic = "IMPELLERC_SDF"
|
||||
shaders = [
|
||||
"circle_sdf.frag",
|
||||
"sdf.frag",
|
||||
]
|
||||
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_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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,13 +7,12 @@
|
||||
#include <flutter/runtime_effect.glsl>
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform vec2 uSize;
|
||||
uniform sampler2D uTex;
|
||||
|
||||
void main() {
|
||||
vec2 p = FlutterFragCoord().xy / uSize;
|
||||
float d = texture(uTex, p).r;
|
||||
vec3 col = d > 0.0 ? vec3(0.0) : vec3(1.0);
|
||||
fragColor = vec4(col, 1.0);
|
||||
vec2 p = FlutterFragCoord().xy / uSize;
|
||||
vec2 center = vec2(0.5, 0.5);
|
||||
float radius = 0.25;
|
||||
float d = length(p - center) - radius;
|
||||
fragColor = vec4(d, 0.0, 0.0, 1.0);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#version 320 es
|
||||
|
||||
// Copyright 2014 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 <flutter/runtime_effect.glsl>
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform vec2 uSize;
|
||||
uniform sampler2D uTex;
|
||||
|
||||
void main() {
|
||||
vec2 p = FlutterFragCoord().xy / uSize;
|
||||
float d = texture(uTex, p).r;
|
||||
// d > 0 means outside the shape (positive distance), so black.
|
||||
// d <= 0 means inside the shape (negative distance), so white.
|
||||
vec3 col = d > 0.0 ? vec3(0.0) : vec3(1.0);
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
@ -20,6 +20,7 @@ tests = [
|
||||
"gesture_settings_test.dart",
|
||||
"gpu_test.dart",
|
||||
"gradient_test.dart",
|
||||
"high_bitrate_texture_test.dart",
|
||||
"http_allow_http_connections_test.dart",
|
||||
"http_disallow_http_connections_test.dart",
|
||||
"image_descriptor_test.dart",
|
||||
|
||||
239
engine/src/flutter/testing/dart/high_bitrate_texture_test.dart
Normal file
239
engine/src/flutter/testing/dart/high_bitrate_texture_test.dart
Normal file
@ -0,0 +1,239 @@
|
||||
// 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:math' as math;
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'goldens.dart';
|
||||
import 'impeller_enabled.dart';
|
||||
|
||||
void main() async {
|
||||
final ImageComparer comparer = await ImageComparer.create();
|
||||
|
||||
test('decodeImageFromPixels with RGBA Float32', () async {
|
||||
const dimension = 1024;
|
||||
final Image image = await _createRGBA32FloatImage(dimension, dimension);
|
||||
final Image shaderImage = await _drawIntoImage(image);
|
||||
|
||||
final ByteData data = (await shaderImage.toByteData())!;
|
||||
|
||||
// Check top left is Red
|
||||
var offset = 0;
|
||||
expect(data.getUint8(offset), 255, reason: 'Top left Red');
|
||||
expect(data.getUint8(offset + 1), 0, reason: 'Top left Green');
|
||||
expect(data.getUint8(offset + 2), 0, reason: 'Top left Blue');
|
||||
expect(data.getUint8(offset + 3), 255, reason: 'Top left Alpha');
|
||||
|
||||
// Check center is Black
|
||||
offset = ((dimension ~/ 2) * dimension + (dimension ~/ 2)) * 4;
|
||||
expect(data.getUint8(offset), 0, reason: 'Center Red');
|
||||
expect(data.getUint8(offset + 1), 0, reason: 'Center Green');
|
||||
expect(data.getUint8(offset + 2), 0, reason: 'Center Blue');
|
||||
expect(data.getUint8(offset + 3), 255, reason: 'Center Alpha');
|
||||
|
||||
await comparer.addGoldenImage(shaderImage, 'decode_image_from_pixels_rgba_float32.png');
|
||||
image.dispose();
|
||||
});
|
||||
|
||||
test('decodeImageFromPixels with R Float32', () async {
|
||||
if (!impellerEnabled) {
|
||||
print('Skipped for Skia');
|
||||
return;
|
||||
}
|
||||
const dimension = 1024;
|
||||
final Image image = await _createR32FloatImage(dimension, dimension);
|
||||
final Image shaderImage = await _drawIntoImage(image);
|
||||
|
||||
final ByteData data = (await shaderImage.toByteData())!;
|
||||
|
||||
// Check top left is Red
|
||||
var offset = 0;
|
||||
expect(data.getUint8(offset), 255, reason: 'Top left Red');
|
||||
expect(data.getUint8(offset + 1), 0, reason: 'Top left Green');
|
||||
expect(data.getUint8(offset + 2), 0, reason: 'Top left Blue');
|
||||
expect(data.getUint8(offset + 3), 255, reason: 'Top left Alpha');
|
||||
|
||||
// Check center is Black
|
||||
offset = ((dimension ~/ 2) * dimension + (dimension ~/ 2)) * 4;
|
||||
expect(data.getUint8(offset), 0, reason: 'Center Red');
|
||||
expect(data.getUint8(offset + 1), 0, reason: 'Center Green');
|
||||
expect(data.getUint8(offset + 2), 0, reason: 'Center Blue');
|
||||
expect(data.getUint8(offset + 3), 255, reason: 'Center Alpha');
|
||||
|
||||
image.dispose();
|
||||
});
|
||||
|
||||
test('Picture.toImageSync with rgbaFloat32', () async {
|
||||
const dimension = 1024;
|
||||
final Image image = await _drawWithCircleShader(
|
||||
dimension,
|
||||
dimension,
|
||||
TargetPixelFormat.rgbaFloat32,
|
||||
);
|
||||
final Image shaderImage = await _drawWithShader(image);
|
||||
|
||||
final ByteData data = (await shaderImage.toByteData())!;
|
||||
|
||||
// Check top left is Black (outside circle, d > 0 -> vec3(0.0))
|
||||
var offset = 0;
|
||||
expect(data.getUint8(offset), 0, reason: 'Top left Red');
|
||||
expect(data.getUint8(offset + 1), 0, reason: 'Top left Green');
|
||||
expect(data.getUint8(offset + 2), 0, reason: 'Top left Blue');
|
||||
expect(data.getUint8(offset + 3), 255, reason: 'Top left Alpha');
|
||||
|
||||
// Check center is White (inside circle, d <= 0 -> vec3(1.0))
|
||||
offset = ((dimension ~/ 2) * dimension + (dimension ~/ 2)) * 4;
|
||||
expect(data.getUint8(offset), 255, reason: 'Center Red');
|
||||
expect(data.getUint8(offset + 1), 255, reason: 'Center Green');
|
||||
expect(data.getUint8(offset + 2), 255, reason: 'Center Blue');
|
||||
expect(data.getUint8(offset + 3), 255, reason: 'Center Alpha');
|
||||
|
||||
await comparer.addGoldenImage(shaderImage, 'picture_to_image_rgba_float32.png');
|
||||
image.dispose();
|
||||
});
|
||||
|
||||
test('Picture.toImageSync with rFloat32', () async {
|
||||
if (!impellerEnabled) {
|
||||
print('Skipped for Skia');
|
||||
return;
|
||||
}
|
||||
const dimension = 1024;
|
||||
final Image image = await _drawWithCircleShader(
|
||||
dimension,
|
||||
dimension,
|
||||
TargetPixelFormat.rFloat32,
|
||||
);
|
||||
final Image shaderImage = await _drawWithShader(image);
|
||||
|
||||
final ByteData data = (await shaderImage.toByteData())!;
|
||||
|
||||
// Check top left is Black
|
||||
var offset = 0;
|
||||
expect(data.getUint8(offset), 0, reason: 'Top left Red');
|
||||
expect(data.getUint8(offset + 1), 0, reason: 'Top left Green');
|
||||
expect(data.getUint8(offset + 2), 0, reason: 'Top left Blue');
|
||||
expect(data.getUint8(offset + 3), 255, reason: 'Top left Alpha');
|
||||
|
||||
// Check center is White
|
||||
offset = ((dimension ~/ 2) * dimension + (dimension ~/ 2)) * 4;
|
||||
expect(data.getUint8(offset), 255, reason: 'Center Red');
|
||||
expect(data.getUint8(offset + 1), 255, reason: 'Center Green');
|
||||
expect(data.getUint8(offset + 2), 255, reason: 'Center Blue');
|
||||
expect(data.getUint8(offset + 3), 255, reason: 'Center Alpha');
|
||||
|
||||
image.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
Future<Image> _drawWithShader(Image image) async {
|
||||
final FragmentProgram program = await FragmentProgram.fromAsset('sdf.frag.iplr');
|
||||
final FragmentShader shader = program.fragmentShader();
|
||||
shader.setFloat(0, image.width.toDouble());
|
||||
shader.setFloat(1, image.height.toDouble());
|
||||
shader.setImageSampler(0, image);
|
||||
|
||||
final recorder = PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
|
||||
Paint()..shader = shader,
|
||||
);
|
||||
final Picture picture = recorder.endRecording();
|
||||
return picture.toImageSync(image.width, image.height);
|
||||
}
|
||||
|
||||
Future<Image> _drawWithCircleShader(int width, int height, TargetPixelFormat format) async {
|
||||
final FragmentProgram program = await FragmentProgram.fromAsset('circle_sdf.frag.iplr');
|
||||
final FragmentShader shader = program.fragmentShader();
|
||||
shader.setFloat(0, width.toDouble());
|
||||
shader.setFloat(1, height.toDouble());
|
||||
|
||||
final recorder = PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()),
|
||||
Paint()..shader = shader,
|
||||
);
|
||||
final Picture picture = recorder.endRecording();
|
||||
return picture.toImageSync(width, height, targetFormat: format);
|
||||
}
|
||||
|
||||
Future<Image> _drawIntoImage(Image image) {
|
||||
final recorder = PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
canvas.drawImage(image, Offset.zero, Paint());
|
||||
final Picture picture = recorder.endRecording();
|
||||
return picture.toImage(image.width, image.height);
|
||||
}
|
||||
|
||||
/// Draws an ellipsis with radii half the dimensions in the rgbaFloat32 pixel
|
||||
/// format.
|
||||
Future<Image> _createRGBA32FloatImage(int width, int height) async {
|
||||
final double radius = width / 4.0;
|
||||
final floats = List<double>.filled(width * height * 4, 0.0);
|
||||
for (var i = 0; i < height; ++i) {
|
||||
for (var j = 0; j < width; ++j) {
|
||||
double x = j.toDouble();
|
||||
double y = i.toDouble();
|
||||
x -= width / 2.0;
|
||||
y -= height / 2.0;
|
||||
final double length = math.sqrt(x * x + y * y);
|
||||
final int idx = i * width * 4 + j * 4;
|
||||
floats[idx + 0] = length - radius;
|
||||
floats[idx + 1] = 0.0;
|
||||
floats[idx + 2] = 0.0;
|
||||
floats[idx + 3] = 1.0;
|
||||
}
|
||||
}
|
||||
final floatList = Float32List.fromList(floats);
|
||||
final intList = Uint8List.view(floatList.buffer);
|
||||
final completer = Completer<Image>();
|
||||
decodeImageFromPixels(
|
||||
intList,
|
||||
width,
|
||||
height,
|
||||
PixelFormat.rgbaFloat32,
|
||||
targetFormat: TargetPixelFormat.rgbaFloat32,
|
||||
(Image image) {
|
||||
completer.complete(image);
|
||||
},
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Draws an ellipsis with radii half the dimensions in the rFloat32 pixel
|
||||
/// format.
|
||||
Future<Image> _createR32FloatImage(int width, int height) async {
|
||||
final double radius = width / 4.0;
|
||||
final floats = List<double>.filled(width * height, 0.0);
|
||||
for (var i = 0; i < height; ++i) {
|
||||
for (var j = 0; j < width; ++j) {
|
||||
double x = j.toDouble();
|
||||
double y = i.toDouble();
|
||||
x -= width / 2.0;
|
||||
y -= height / 2.0;
|
||||
final double length = math.sqrt(x * x + y * y);
|
||||
final int idx = i * width + j;
|
||||
floats[idx] = length - radius;
|
||||
}
|
||||
}
|
||||
final floatList = Float32List.fromList(floats);
|
||||
final intList = Uint8List.view(floatList.buffer);
|
||||
final completer = Completer<Image>();
|
||||
decodeImageFromPixels(
|
||||
intList,
|
||||
width,
|
||||
height,
|
||||
PixelFormat.rFloat32,
|
||||
targetFormat: TargetPixelFormat.rFloat32,
|
||||
(Image image) {
|
||||
completer.complete(image);
|
||||
},
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
@ -28,7 +28,6 @@ workspace:
|
||||
- dev/integration_tests/external_textures
|
||||
- dev/integration_tests/flavors
|
||||
- dev/integration_tests/flutter_gallery
|
||||
- dev/integration_tests/high_bitrate_images
|
||||
- dev/integration_tests/hook_user_defines
|
||||
- dev/integration_tests/ios_add2app_life_cycle/flutterapp
|
||||
- dev/integration_tests/ios_app_with_extensions
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user