mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This reverts commit adc8e159a5
.
This should be safe to land once https://github.com/flutter/flutter/pull/28530 gets merged
Merge on yellow doc test because the doc test is actually green.
This commit is contained in:
parent
e50ebfc6fa
commit
19a6a6f431
@ -1 +1,6 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
const String kCullOpacityRouteName = '/cull_opacity';
|
||||
const String kCubicBezierRouteName = '/cubic_bezier';
|
||||
|
@ -1,6 +1,11 @@
|
||||
// Copyright 2015 The Chromium 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/material.dart';
|
||||
|
||||
import 'common.dart';
|
||||
import 'src/cubic_bezier.dart';
|
||||
import 'src/cull_opacity.dart';
|
||||
|
||||
const String kMacrobenchmarks ='Macrobenchmarks';
|
||||
@ -16,6 +21,7 @@ class MacrobenchmarksApp extends StatelessWidget {
|
||||
routes: <String, WidgetBuilder>{
|
||||
'/': (BuildContext context) => HomePage(),
|
||||
kCullOpacityRouteName: (BuildContext context) => CullOpacityPage(),
|
||||
kCubicBezierRouteName: (BuildContext context) => CubicBezierPage(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -34,6 +40,13 @@ class HomePage extends StatelessWidget {
|
||||
onPressed: (){
|
||||
Navigator.pushNamed(context, kCullOpacityRouteName);
|
||||
},
|
||||
),
|
||||
RaisedButton(
|
||||
key: const Key(kCubicBezierRouteName),
|
||||
child: const Text('Cubic Bezier'),
|
||||
onPressed: (){
|
||||
Navigator.pushNamed(context, kCubicBezierRouteName);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
|
380
dev/benchmarks/macrobenchmarks/lib/src/cubic_bezier.dart
Normal file
380
dev/benchmarks/macrobenchmarks/lib/src/cubic_bezier.dart
Normal file
@ -0,0 +1,380 @@
|
||||
// Copyright 2019 The Chromium 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:math';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Based on https://github.com/eseidelGoogle/bezier_perf/blob/master/lib/main.dart
|
||||
class CubicBezierPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const <Widget>[
|
||||
Bezier(Colors.amber, 1.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Bezier extends StatelessWidget {
|
||||
const Bezier(this.color, this.scale, {this.blur = 0.0, this.delay = 0.0});
|
||||
|
||||
final Color color;
|
||||
final double scale;
|
||||
final double blur;
|
||||
final double delay;
|
||||
|
||||
List<PathDetail> _getLogoPath() {
|
||||
final List<PathDetail> paths = <PathDetail>[];
|
||||
|
||||
final Path path = Path();
|
||||
path.moveTo(100.0, 97.0);
|
||||
path.cubicTo(100.0, 97.0, 142.0, 59.0, 169.91, 41.22);
|
||||
path.cubicTo(197.82, 23.44, 249.24, 5.52, 204.67, 85.84);
|
||||
|
||||
paths.add(PathDetail(path));
|
||||
|
||||
// Path 2
|
||||
final Path bezier2Path = Path();
|
||||
bezier2Path.moveTo(0.0, 70.55);
|
||||
bezier2Path.cubicTo(0.0, 70.55, 42.0, 31.55, 69.91, 14.77);
|
||||
bezier2Path.cubicTo(97.82, -2.01, 149.24, -20.93, 104.37, 59.39);
|
||||
|
||||
paths.add(PathDetail(bezier2Path,
|
||||
translate: <double>[29.45, 151.0], rotation: -1.5708));
|
||||
|
||||
// Path 3
|
||||
final Path bezier3Path = Path();
|
||||
bezier3Path.moveTo(0.0, 69.48);
|
||||
bezier3Path.cubicTo(0.0, 69.48, 44.82, 27.92, 69.91, 13.7);
|
||||
bezier3Path.cubicTo(95.0, -0.52, 149.24, -22.0, 104.37, 58.32);
|
||||
|
||||
paths.add(PathDetail(bezier3Path,
|
||||
translate: <double>[53.0, 200.48], rotation: -3.14159));
|
||||
|
||||
// Path 4
|
||||
final Path bezier4Path = Path();
|
||||
bezier4Path.moveTo(0.0, 69.48);
|
||||
bezier4Path.cubicTo(0.0, 69.48, 43.82, 27.92, 69.91, 13.7);
|
||||
bezier4Path.cubicTo(96.0, -0.52, 149.24, -22.0, 104.37, 58.32);
|
||||
|
||||
paths.add(PathDetail(bezier4Path,
|
||||
translate: <double>[122.48, 77.0], rotation: -4.71239));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(children: <Widget>[
|
||||
CustomPaint(
|
||||
foregroundPainter:
|
||||
BezierPainter(Colors.grey, 0.0, _getLogoPath(), false),
|
||||
size: const Size(100.0, 100.0),
|
||||
),
|
||||
AnimatedBezier(color, scale, blur: blur, delay: delay),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class PathDetail {
|
||||
PathDetail(this.path, {this.translate, this.rotation});
|
||||
|
||||
Path path;
|
||||
List<double> translate = <double>[];
|
||||
double rotation;
|
||||
}
|
||||
|
||||
class AnimatedBezier extends StatefulWidget {
|
||||
const AnimatedBezier(this.color, this.scale, {this.blur = 0.0, this.delay});
|
||||
|
||||
final Color color;
|
||||
final double scale;
|
||||
final double blur;
|
||||
final double delay;
|
||||
|
||||
@override
|
||||
State createState() => AnimatedBezierState();
|
||||
}
|
||||
|
||||
class Point {
|
||||
Point(this.x, this.y);
|
||||
|
||||
double x;
|
||||
double y;
|
||||
}
|
||||
|
||||
class AnimatedBezierState extends State<AnimatedBezier>
|
||||
with SingleTickerProviderStateMixin {
|
||||
double scale;
|
||||
AnimationController controller;
|
||||
CurvedAnimation curve;
|
||||
bool isPlaying = false;
|
||||
List<List<Point>> pointList = <List<Point>>[]
|
||||
..add(<Point>[])
|
||||
..add(<Point>[])
|
||||
..add(<Point>[])
|
||||
..add(<Point>[]);
|
||||
bool isReversed = false;
|
||||
|
||||
List<PathDetail> _playForward() {
|
||||
final List<PathDetail> paths = <PathDetail>[];
|
||||
final double t = curve.value;
|
||||
final double b = controller.upperBound;
|
||||
double pX;
|
||||
double pY;
|
||||
|
||||
final Path path = Path();
|
||||
|
||||
if (t < b / 2) {
|
||||
pX = _getCubicPoint(t * 2, 100.0, 100.0, 142.0, 169.91);
|
||||
pY = _getCubicPoint(t * 2, 97.0, 97.0, 59.0, 41.22);
|
||||
pointList[0].add(Point(pX, pY));
|
||||
} else {
|
||||
pX = _getCubicPoint(t * 2 - b, 169.91, 197.80, 249.24, 204.67);
|
||||
pY = _getCubicPoint(t * 2 - b, 41.22, 23.44, 5.52, 85.84);
|
||||
pointList[0].add(Point(pX, pY));
|
||||
}
|
||||
|
||||
path.moveTo(100.0, 97.0);
|
||||
|
||||
for (Point p in pointList[0]) {
|
||||
path.lineTo(p.x, p.y);
|
||||
}
|
||||
|
||||
paths.add(PathDetail(path));
|
||||
|
||||
// Path 2
|
||||
final Path bezier2Path = Path();
|
||||
|
||||
if (t <= b / 2) {
|
||||
final double pX = _getCubicPoint(t * 2, 0.0, 0.0, 42.0, 69.91);
|
||||
final double pY = _getCubicPoint(t * 2, 70.55, 70.55, 31.55, 14.77);
|
||||
pointList[1].add(Point(pX, pY));
|
||||
} else {
|
||||
final double pX = _getCubicPoint(t * 2 - b, 69.91, 97.82, 149.24, 104.37);
|
||||
final double pY = _getCubicPoint(t * 2 - b, 14.77, -2.01, -20.93, 59.39);
|
||||
pointList[1].add(Point(pX, pY));
|
||||
}
|
||||
|
||||
bezier2Path.moveTo(0.0, 70.55);
|
||||
|
||||
for (Point p in pointList[1]) {
|
||||
bezier2Path.lineTo(p.x, p.y);
|
||||
}
|
||||
|
||||
paths.add(PathDetail(bezier2Path,
|
||||
translate: <double>[29.45, 151.0], rotation: -1.5708));
|
||||
|
||||
// Path 3
|
||||
final Path bezier3Path = Path();
|
||||
if (t <= b / 2) {
|
||||
pX = _getCubicPoint(t * 2, 0.0, 0.0, 44.82, 69.91);
|
||||
pY = _getCubicPoint(t * 2, 69.48, 69.48, 27.92, 13.7);
|
||||
pointList[2].add(Point(pX, pY));
|
||||
} else {
|
||||
pX = _getCubicPoint(t * 2 - b, 69.91, 95.0, 149.24, 104.37);
|
||||
pY = _getCubicPoint(t * 2 - b, 13.7, -0.52, -22.0, 58.32);
|
||||
pointList[2].add(Point(pX, pY));
|
||||
}
|
||||
|
||||
bezier3Path.moveTo(0.0, 69.48);
|
||||
|
||||
for (Point p in pointList[2]) {
|
||||
bezier3Path.lineTo(p.x, p.y);
|
||||
}
|
||||
|
||||
paths.add(PathDetail(bezier3Path,
|
||||
translate: <double>[53.0, 200.48], rotation: -3.14159));
|
||||
|
||||
// Path 4
|
||||
final Path bezier4Path = Path();
|
||||
|
||||
if (t < b / 2) {
|
||||
final double pX = _getCubicPoint(t * 2, 0.0, 0.0, 43.82, 69.91);
|
||||
final double pY = _getCubicPoint(t * 2, 69.48, 69.48, 27.92, 13.7);
|
||||
pointList[3].add(Point(pX, pY));
|
||||
} else {
|
||||
final double pX = _getCubicPoint(t * 2 - b, 69.91, 96.0, 149.24, 104.37);
|
||||
final double pY = _getCubicPoint(t * 2 - b, 13.7, -0.52, -22.0, 58.32);
|
||||
pointList[3].add(Point(pX, pY));
|
||||
}
|
||||
|
||||
bezier4Path.moveTo(0.0, 69.48);
|
||||
|
||||
for (Point p in pointList[3]) {
|
||||
bezier4Path.lineTo(p.x, p.y);
|
||||
}
|
||||
|
||||
paths.add(PathDetail(bezier4Path,
|
||||
translate: <double>[122.48, 77.0], rotation: -4.71239));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
List<PathDetail> _playReversed() {
|
||||
for (List<Point> list in pointList) {
|
||||
if (list.isNotEmpty) {
|
||||
list.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
final List<Point> points = pointList[0];
|
||||
final Path path = Path();
|
||||
|
||||
path.moveTo(100.0, 97.0);
|
||||
|
||||
for (Point point in points) {
|
||||
path.lineTo(point.x, point.y);
|
||||
}
|
||||
|
||||
final Path bezier2Path = Path();
|
||||
|
||||
bezier2Path.moveTo(0.0, 70.55);
|
||||
|
||||
for (Point p in pointList[1]) {
|
||||
bezier2Path.lineTo(p.x, p.y);
|
||||
}
|
||||
|
||||
final Path bezier3Path = Path();
|
||||
bezier3Path.moveTo(0.0, 69.48);
|
||||
|
||||
for (Point p in pointList[2]) {
|
||||
bezier3Path.lineTo(p.x, p.y);
|
||||
}
|
||||
|
||||
final Path bezier4Path = Path();
|
||||
|
||||
bezier4Path.moveTo(0.0, 69.48);
|
||||
|
||||
for (Point p in pointList[3]) {
|
||||
bezier4Path.lineTo(p.x, p.y);
|
||||
}
|
||||
|
||||
return <PathDetail>[
|
||||
PathDetail(path),
|
||||
PathDetail(bezier2Path, translate: <double>[29.45, 151.0], rotation: -1.5708),
|
||||
PathDetail(bezier3Path,
|
||||
translate: <double>[53.0, 200.48], rotation: -3.14159),
|
||||
PathDetail(bezier4Path, translate: <double>[122.48, 77.0], rotation: -4.71239)
|
||||
];
|
||||
}
|
||||
|
||||
List<PathDetail> _getLogoPath() {
|
||||
if (!isReversed) {
|
||||
return _playForward();
|
||||
}
|
||||
|
||||
return _playReversed();
|
||||
}
|
||||
|
||||
//From http://wiki.roblox.com/index.php?title=File:Beziereq4.png
|
||||
double _getCubicPoint(double t, double p0, double p1, double p2, double p3) {
|
||||
return pow(1 - t, 3) * p0 +
|
||||
3 * pow(1 - t, 2) * t * p1 +
|
||||
3 * (1 - t) * pow(t, 2) * p2 +
|
||||
pow(t, 3) * p3;
|
||||
}
|
||||
|
||||
void playAnimation() {
|
||||
isPlaying = true;
|
||||
isReversed = false;
|
||||
for (List<Point> list in pointList) {
|
||||
list.clear();
|
||||
}
|
||||
controller.reset();
|
||||
controller.forward();
|
||||
}
|
||||
|
||||
void stopAnimation() {
|
||||
isPlaying = false;
|
||||
controller.stop();
|
||||
for (List<Point> list in pointList) {
|
||||
list.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void reverseAnimation() {
|
||||
isReversed = true;
|
||||
controller.reverse();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 1000));
|
||||
curve = CurvedAnimation(parent: controller, curve: Curves.linear)
|
||||
..addListener(() {
|
||||
setState(() {});
|
||||
})
|
||||
..addStatusListener((AnimationStatus state) {
|
||||
if (state == AnimationStatus.completed) {
|
||||
reverseAnimation();
|
||||
} else if (state == AnimationStatus.dismissed) {
|
||||
playAnimation();
|
||||
}
|
||||
});
|
||||
|
||||
playAnimation();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
foregroundPainter: BezierPainter(widget.color,
|
||||
curve.value * widget.blur, _getLogoPath(), isPlaying),
|
||||
size: const Size(100.0, 100.0));
|
||||
}
|
||||
}
|
||||
|
||||
class BezierPainter extends CustomPainter {
|
||||
BezierPainter(this.color, this.blur, this.path, this.isPlaying);
|
||||
|
||||
Color color;
|
||||
final double blur;
|
||||
List<PathDetail> path;
|
||||
bool isPlaying;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final Paint paint = Paint();
|
||||
paint.strokeWidth = 18.0;
|
||||
paint.style = PaintingStyle.stroke;
|
||||
paint.strokeCap = StrokeCap.round;
|
||||
paint.color = color;
|
||||
canvas.scale(0.5, 0.5);
|
||||
|
||||
for (int i = 0; i < path.length; i++) {
|
||||
if (path[i].translate != null) {
|
||||
canvas.translate(path[i].translate[0], path[i].translate[1]);
|
||||
}
|
||||
|
||||
if (path[i].rotation != null) {
|
||||
canvas.rotate(path[i].rotation);
|
||||
}
|
||||
|
||||
if (blur > 0) {
|
||||
final MaskFilter blur = MaskFilter.blur(BlurStyle.normal, this.blur);
|
||||
paint.maskFilter = blur;
|
||||
canvas.drawPath(path[i].path, paint);
|
||||
}
|
||||
|
||||
paint.maskFilter = null;
|
||||
canvas.drawPath(path[i].path, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(BezierPainter oldDelegate) => true;
|
||||
|
||||
@override
|
||||
bool shouldRebuildSemantics(BezierPainter oldDelegate) => false;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
// Copyright 2019 The Chromium 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';
|
||||
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
import 'package:flutter/painting.dart' show DefaultShaderWarmUp, PaintingBinding;
|
||||
import 'package:macrobenchmarks/main.dart' as app;
|
||||
|
||||
class CubicBezierShaderWarmUp extends DefaultShaderWarmUp {
|
||||
@override
|
||||
void warmUpOnCanvas(Canvas canvas) {
|
||||
super.warmUpOnCanvas(canvas);
|
||||
|
||||
// Warm up the cubic shaders used by CubicBezierPage.
|
||||
//
|
||||
// This tests that our custom shader warm up is working properly.
|
||||
// Without this custom shader warm up, the worst frame time is about 115ms.
|
||||
// With this, the worst frame time is about 70ms. (Data collected on a Moto
|
||||
// G4 based on Flutter version 704814c67a874077710524d30412337884bf0254.
|
||||
final Path path = Path();
|
||||
path.moveTo(20.0, 20.0);
|
||||
// This cubic path is based on
|
||||
// https://skia.org/user/api/SkPath_Reference#SkPath_cubicTo
|
||||
path.cubicTo(300.0, 80.0, -140.0, 90.0, 220.0, 10.0);
|
||||
final Paint paint = Paint();
|
||||
paint.isAntiAlias = true;
|
||||
paint.strokeWidth = 18.0;
|
||||
paint.style = PaintingStyle.stroke;
|
||||
paint.strokeCap = StrokeCap.round;
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
PaintingBinding.shaderWarmUp = CubicBezierShaderWarmUp();
|
||||
enableFlutterDriverExtension();
|
||||
app.main();
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
// Copyright 2019 The Chromium 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:macrobenchmarks/common.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
void main() {
|
||||
macroPerfTest('cubic_bezier_perf', kCubicBezierRouteName);
|
||||
}
|
@ -2,42 +2,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
|
||||
import 'package:macrobenchmarks/common.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
void main() {
|
||||
const String kName = 'cull_opacity_perf';
|
||||
|
||||
test(kName, () async {
|
||||
final FlutterDriver driver = await FlutterDriver.connect();
|
||||
|
||||
// The slight initial delay avoids starting the timing during a
|
||||
// period of increased load on the device. Without this delay, the
|
||||
// benchmark has greater noise.
|
||||
// See: https://github.com/flutter/flutter/issues/19434
|
||||
await Future<void>.delayed(const Duration(milliseconds: 250));
|
||||
|
||||
await driver.forceGC();
|
||||
|
||||
final SerializableFinder button = find.byValueKey(kCullOpacityRouteName);
|
||||
expect(button, isNotNull);
|
||||
await driver.tap(button);
|
||||
|
||||
// Wait for the page to load
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
final Timeline timeline = await driver.traceAction(() async {
|
||||
await Future<void>.delayed(const Duration(seconds: 10));
|
||||
});
|
||||
|
||||
final TimelineSummary summary = TimelineSummary.summarize(timeline);
|
||||
summary.writeSummaryToFile(kName, pretty: true);
|
||||
summary.writeTimelineToFile(kName, pretty: true);
|
||||
|
||||
driver.close();
|
||||
});
|
||||
macroPerfTest(
|
||||
'cull_opacity_perf',
|
||||
kCullOpacityRouteName,
|
||||
pageDelay: const Duration(seconds: 1),
|
||||
duration: const Duration(seconds: 10)
|
||||
);
|
||||
}
|
||||
|
44
dev/benchmarks/macrobenchmarks/test_driver/util.dart
Normal file
44
dev/benchmarks/macrobenchmarks/test_driver/util.dart
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2019 The Chromium 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 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
|
||||
void macroPerfTest(
|
||||
String testName,
|
||||
String routeName,
|
||||
{Duration pageDelay, Duration duration = const Duration(seconds: 3)}) {
|
||||
test(testName, () async {
|
||||
final FlutterDriver driver = await FlutterDriver.connect();
|
||||
|
||||
// The slight initial delay avoids starting the timing during a
|
||||
// period of increased load on the device. Without this delay, the
|
||||
// benchmark has greater noise.
|
||||
// See: https://github.com/flutter/flutter/issues/19434
|
||||
await Future<void>.delayed(const Duration(milliseconds: 250));
|
||||
|
||||
await driver.forceGC();
|
||||
|
||||
final SerializableFinder button = find.byValueKey(routeName);
|
||||
expect(button, isNotNull);
|
||||
await driver.tap(button);
|
||||
|
||||
if (pageDelay != null) {
|
||||
// Wait for the page to load
|
||||
await Future<void>.delayed(pageDelay);
|
||||
}
|
||||
|
||||
final Timeline timeline = await driver.traceAction(() async {
|
||||
await Future<void>.delayed(duration);
|
||||
});
|
||||
|
||||
final TimelineSummary summary = TimelineSummary.summarize(timeline);
|
||||
summary.writeSummaryToFile(testName, pretty: true);
|
||||
summary.writeTimelineToFile(testName, pretty: true);
|
||||
|
||||
driver.close();
|
||||
});
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
// Copyright 2019 The Chromium 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 'package:flutter_devicelab/tasks/perf_tests.dart';
|
||||
import 'package:flutter_devicelab/framework/adb.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
deviceOperatingSystem = DeviceOperatingSystem.android;
|
||||
await task(createCubicBezierPerfTest());
|
||||
}
|
@ -46,6 +46,14 @@ TaskFunction createCullOpacityPerfTest() {
|
||||
).run;
|
||||
}
|
||||
|
||||
TaskFunction createCubicBezierPerfTest() {
|
||||
return PerfTest(
|
||||
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
|
||||
'test_driver/cubic_bezier_perf.dart',
|
||||
'cubic_bezier_perf',
|
||||
).run;
|
||||
}
|
||||
|
||||
TaskFunction createFlutterGalleryStartupTest() {
|
||||
return StartupTest(
|
||||
'${flutterDirectory.path}/examples/flutter_gallery',
|
||||
|
@ -133,6 +133,13 @@ tasks:
|
||||
stage: devicelab
|
||||
required_agent_capabilities: ["mac/android"]
|
||||
|
||||
cubic_bezier_perf__timeline_summary:
|
||||
description: >
|
||||
Measures the runtime performance of cubic bezier animations on Android.
|
||||
stage: devicelab
|
||||
required_agent_capabilities: ["mac/android"]
|
||||
flaky: true
|
||||
|
||||
flavors_test:
|
||||
description: >
|
||||
Checks that flavored builds work on Android.
|
||||
|
33
examples/layers/raw/shader_warm_up.dart
Normal file
33
examples/layers/raw/shader_warm_up.dart
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This example shows the draw operations to warm up the GPU shaders by default.
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/painting.dart' show DefaultShaderWarmUp;
|
||||
|
||||
void beginFrame(Duration timeStamp) {
|
||||
// PAINT
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Rect paintBounds = ui.Rect.fromLTRB(0, 0, 1000, 1000);
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, paintBounds);
|
||||
final ui.Paint backgroundPaint = ui.Paint()..color = Colors.white;
|
||||
canvas.drawRect(paintBounds, backgroundPaint);
|
||||
const DefaultShaderWarmUp().warmUpOnCanvas(canvas);
|
||||
final ui.Picture picture = recorder.endRecording();
|
||||
|
||||
// COMPOSITE
|
||||
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder()
|
||||
..pushClipRect(paintBounds)
|
||||
..addPicture(ui.Offset.zero, picture)
|
||||
..pop();
|
||||
ui.window.render(sceneBuilder.build());
|
||||
}
|
||||
|
||||
void main() {
|
||||
ui.window.onBeginFrame = beginFrame;
|
||||
ui.window.scheduleFrame();
|
||||
}
|
13
examples/layers/test/smoketests/raw/shader_warm_up_test.dart
Normal file
13
examples/layers/test/smoketests/raw/shader_warm_up_test.dart
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2019 The Chromium 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:test_api/test_api.dart' hide TypeMatcher, isInstanceOf;
|
||||
|
||||
import '../../../raw/shader_warm_up.dart' as demo;
|
||||
|
||||
void main() {
|
||||
test('layers smoketest for raw/shader_warm_up.dart', () {
|
||||
demo.main();
|
||||
});
|
||||
}
|
@ -50,6 +50,7 @@ export 'src/painting/matrix_utils.dart';
|
||||
export 'src/painting/notched_shapes.dart';
|
||||
export 'src/painting/paint_utilities.dart';
|
||||
export 'src/painting/rounded_rectangle_border.dart';
|
||||
export 'src/painting/shader_warm_up.dart';
|
||||
export 'src/painting/shape_decoration.dart';
|
||||
export 'src/painting/stadium_border.dart';
|
||||
export 'src/painting/strut_style.dart';
|
||||
|
@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart' show ServicesBinding;
|
||||
|
||||
import 'image_cache.dart';
|
||||
import 'shader_warm_up.dart';
|
||||
|
||||
const double _kDefaultDecodedCacheRatioCap = 0.0;
|
||||
|
||||
@ -22,12 +23,34 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
|
||||
super.initInstances();
|
||||
_instance = this;
|
||||
_imageCache = createImageCache();
|
||||
if (shaderWarmUp != null) {
|
||||
shaderWarmUp.execute();
|
||||
}
|
||||
}
|
||||
|
||||
/// The current [PaintingBinding], if one has been created.
|
||||
static PaintingBinding get instance => _instance;
|
||||
static PaintingBinding _instance;
|
||||
|
||||
/// [ShaderWarmUp] to be executed during [initInstances].
|
||||
///
|
||||
/// If the application has scenes that require the compilation of complex
|
||||
/// shaders that are not covered by [DefaultShaderWarmUp], it may cause jank
|
||||
/// in the middle of an animation or interaction. In that case, set
|
||||
/// [shaderWarmUp] to a custom [ShaderWarmUp] before calling [initInstances]
|
||||
/// (usually before [runApp] for normal flutter apps, and before
|
||||
/// [enableFlutterDriverExtension] for flutter drive tests). Paint the scene
|
||||
/// in the custom [ShaderWarmUp] so Flutter can pre-compile and cache the
|
||||
/// shaders during startup. The warm up is only costly (100ms-200ms,
|
||||
/// depending on the shaders to compile) during the first run after the
|
||||
/// installation or a data wipe. The warm up does not block the main thread
|
||||
/// so there should be no "Application Not Responding" warning.
|
||||
///
|
||||
/// Currently the warm-up happens synchronously on the GPU thread which means
|
||||
/// the rendering of the first frame on the GPU thread will be postponed until
|
||||
/// the warm-up is finished.
|
||||
static ShaderWarmUp shaderWarmUp = const DefaultShaderWarmUp();
|
||||
|
||||
/// The singleton that implements the Flutter framework's image cache.
|
||||
///
|
||||
/// The cache is used internally by [ImageProvider] and should generally not
|
||||
|
148
packages/flutter/lib/src/painting/shader_warm_up.dart
Normal file
148
packages/flutter/lib/src/painting/shader_warm_up.dart
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2019 The Chromium 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:developer';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Interface for drawing an image to warm up Skia shader compilations.
|
||||
///
|
||||
/// When Skia first sees a certain type of draw operations on GPU, it needs to
|
||||
/// compile the corresponding shader. The compilation can be slow (20ms-200ms).
|
||||
/// Having that time as a startup latency is often better than having a jank in
|
||||
/// the middle of an animation.
|
||||
///
|
||||
/// Therefore, we use this during the [PaintingBinding.initInstances] call to
|
||||
/// move common shader compilations from animation time to startup time. By
|
||||
/// default, a [DefaultShaderWarmUp] is used. Create a custom [ShaderWarmUp]
|
||||
/// subclass to replace [PaintingBinding.shaderWarmUp] before
|
||||
/// [PaintingBinding.initInstances] is called. Usually, that can be done before
|
||||
/// calling [runApp].
|
||||
///
|
||||
/// This warm up needs to be run on each individual device because the shader
|
||||
/// compilation depends on the specific GPU hardware and driver a device has. It
|
||||
/// can't be pre-computed during the Flutter engine compilation as the engine is
|
||||
/// device agnostic.
|
||||
///
|
||||
/// If no warm up is desired (e.g., when the startup latency is crucial), set
|
||||
/// [PaintingBinding.shaderWarmUp] either to a custom ShaderWarmUp with an empty
|
||||
/// [warmUpOnCanvas] or null.
|
||||
abstract class ShaderWarmUp {
|
||||
/// Allow const constructors for subclasses.
|
||||
const ShaderWarmUp();
|
||||
|
||||
/// The size of the warm up image.
|
||||
///
|
||||
/// The exact size shouldn't matter much as long as it's not too far away from
|
||||
/// the target device's screen. 1024x1024 is a good choice as it is within an
|
||||
/// order of magnitude of most devices.
|
||||
///
|
||||
/// A custom shader warm up can override this based on targeted devices.
|
||||
ui.Size get size => const ui.Size(1024.0, 1024.0);
|
||||
|
||||
/// Trigger draw operations on a given canvas to warm up GPU shader
|
||||
/// compilation cache.
|
||||
///
|
||||
/// To decide which draw operations to be added to your custom warm up
|
||||
/// process, try capture an skp using `flutter screenshot --observatory-
|
||||
/// port=<port> --type=skia` and analyze it with https://debugger.skia.org.
|
||||
/// Alternatively, one may run the app with `flutter run --trace-skia` and
|
||||
/// then examine the GPU thread in the observatory timeline to see which
|
||||
/// Skia draw operations are commonly used, and which shader compilations
|
||||
/// are causing janks.
|
||||
@protected
|
||||
void warmUpOnCanvas(ui.Canvas canvas);
|
||||
|
||||
/// Construct an offscreen image of [size], and execute [warmUpOnCanvas] on a
|
||||
/// canvas associated with that image.
|
||||
void execute() {
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder);
|
||||
|
||||
warmUpOnCanvas(canvas);
|
||||
|
||||
final ui.Picture picture = recorder.endRecording();
|
||||
final TimelineTask shaderWarmUpTask = TimelineTask();
|
||||
shaderWarmUpTask.start('Warm-up shader');
|
||||
picture.toImage(size.width.ceil(), size.height.ceil()).then((ui.Image image) {
|
||||
shaderWarmUpTask.finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Default way of warming up Skia shader compilations.
|
||||
///
|
||||
/// The draw operations being warmed up here are decided according to Flutter
|
||||
/// engineers' observation and experience based on the apps and the performance
|
||||
/// issues seen so far.
|
||||
class DefaultShaderWarmUp extends ShaderWarmUp {
|
||||
/// Allow [DefaultShaderWarmUp] to be used as the default value of parameters.
|
||||
const DefaultShaderWarmUp();
|
||||
|
||||
/// Trigger common draw operations on a canvas to warm up GPU shader
|
||||
/// compilation cache.
|
||||
@override
|
||||
void warmUpOnCanvas(ui.Canvas canvas) {
|
||||
final ui.RRect rrect = ui.RRect.fromLTRBXY(20.0, 20.0, 60.0, 60.0, 10.0, 10.0);
|
||||
final ui.Path rrectPath = ui.Path()..addRRect(rrect);
|
||||
|
||||
final ui.Path circlePath = ui.Path()..addOval(
|
||||
ui.Rect.fromCircle(center: const ui.Offset(40.0, 40.0), radius: 20.0)
|
||||
);
|
||||
|
||||
// The following path is based on
|
||||
// https://skia.org/user/api/SkCanvas_Reference#SkCanvas_drawPath
|
||||
final ui.Path path = ui.Path();
|
||||
path.moveTo(20.0, 60.0);
|
||||
path.quadraticBezierTo(60.0, 20.0, 60.0, 60.0);
|
||||
path.close();
|
||||
path.moveTo(60.0, 20.0);
|
||||
path.quadraticBezierTo(60.0, 60.0, 20.0, 60.0);
|
||||
|
||||
final List<ui.Path> paths = <ui.Path>[rrectPath, circlePath, path];
|
||||
|
||||
final List<ui.Paint> paints = <ui.Paint>[
|
||||
ui.Paint()
|
||||
..isAntiAlias = true
|
||||
..style = ui.PaintingStyle.fill,
|
||||
ui.Paint()
|
||||
..isAntiAlias = true
|
||||
..style = ui.PaintingStyle.stroke
|
||||
..strokeWidth = 10,
|
||||
ui.Paint()
|
||||
..isAntiAlias = true
|
||||
..style = ui.PaintingStyle.stroke
|
||||
..strokeWidth = 0.1 // hairline
|
||||
];
|
||||
|
||||
// Warm up path stroke and fill shaders.
|
||||
for (int i = 0; i < paths.length; i += 1) {
|
||||
canvas.save();
|
||||
for (ui.Paint paint in paints) {
|
||||
canvas.drawPath(paths[i], paint);
|
||||
canvas.translate(80.0, 0.0);
|
||||
}
|
||||
canvas.restore();
|
||||
canvas.translate(0.0, 80.0);
|
||||
}
|
||||
|
||||
// Warm up shadow shaders.
|
||||
const ui.Color black = ui.Color(0xFF000000);
|
||||
canvas.save();
|
||||
canvas.drawShadow(rrectPath, black, 10.0, true);
|
||||
canvas.translate(80.0, 0.0);
|
||||
canvas.drawShadow(rrectPath, black, 10.0, false);
|
||||
canvas.restore();
|
||||
|
||||
// Warm up text shaders.
|
||||
canvas.translate(0.0, 80.0);
|
||||
final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
|
||||
ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
|
||||
)..pushStyle(ui.TextStyle(color: black))..addText('_');
|
||||
final ui.Paragraph paragraph = paragraphBuilder.build()
|
||||
..layout(const ui.ParagraphConstraints(width: 60.0));
|
||||
canvas.drawParagraph(paragraph, const ui.Offset(20.0, 20.0));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user