diff --git a/.ci.yaml b/.ci.yaml index 331004f4628..319f9ccc58a 100755 --- a/.ci.yaml +++ b/.ci.yaml @@ -2374,6 +2374,39 @@ targets: task_name: opacity_peephole_col_of_alpha_savelayer_rows_perf__e2e_summary scheduler: luci + - name: Linux_android gradient_dynamic_perf__e2e_summary + bringup: true + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux"] + task_name: gradient_dynamic_perf__e2e_summary + scheduler: luci + + - name: Linux_android gradient_consistent_perf__e2e_summary + bringup: true + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux"] + task_name: gradient_consistent_perf__e2e_summary + scheduler: luci + + - name: Linux_android gradient_static_perf__e2e_summary + bringup: true + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux"] + task_name: gradient_static_perf__e2e_summary + scheduler: luci + - name: Linux_android android_choreographer_do_frame_test recipe: devicelab/devicelab_drone presubmit: false diff --git a/TESTOWNERS b/TESTOWNERS index 287b317631f..769b384f277 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -75,6 +75,9 @@ /dev/devicelab/bin/tasks/opacity_peephole_fade_transition_text_perf__e2e_summary.dart @flar @flutter/engine /dev/devicelab/bin/tasks/opacity_peephole_col_of_alpha_savelayer_rows_perf__e2e_summary.dart @flar @flutter/engine /dev/devicelab/bin/tasks/opacity_peephole_grid_of_alpha_savelayers_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/gradient_consistent_perf__e2e_summary.dart @flar @flutter/engine +/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 ## Windows Android DeviceLab tests /dev/devicelab/bin/tasks/basic_material_app_win__compile.dart @zanderso @flutter/tool diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index 582a5d12f98..423146508d8 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -26,6 +26,7 @@ const String kStackSizeRouteName = '/stack_size'; const String kAnimationWithMicrotasksRouteName = '/animation_with_microtasks'; const String kAnimatedImageRouteName = '/animated_image'; const String kOpacityPeepholeRouteName = '/opacity_peephole'; +const String kGradientPerfRouteName = '/gradient_perf'; const String kOpacityPeepholeOneRectRouteName = '$kOpacityPeepholeRouteName/one_big_rect'; const String kOpacityPeepholeColumnOfOpacityRouteName = '$kOpacityPeepholeRouteName/column_of_opacity'; @@ -39,7 +40,12 @@ const String kOpacityPeepholeGridOfRectsWithAlphaRouteName = '$kOpacityPeepholeR const String kOpacityPeepholeGridOfAlphaSaveLayerRectsRouteName = '$kOpacityPeepholeRouteName/grid_of_alpha_savelayer_rects'; const String kOpacityPeepholeColumnOfAlphaSaveLayerRowsOfRectsRouteName = '$kOpacityPeepholeRouteName/column_of_alpha_save_layer_rows_of_rects'; +const String kGradientPerfRecreateDynamicRouteName = '$kGradientPerfRouteName/recreate_dynamic'; +const String kGradientPerfRecreateConsistentRouteName = '$kGradientPerfRouteName/recreate_consistent'; +const String kGradientPerfStaticConsistentRouteName = '$kGradientPerfRouteName/static_consistent'; + const String kScrollableName = '/macrobenchmark_listview'; const String kOpacityScrollableName = '$kOpacityPeepholeRouteName/listview'; +const String kGradientPerfScrollableName = '$kGradientPerfRouteName/listview'; const String kStackSizeKey = 'stack_size'; diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index da57c9087f4..9bdfd5eb8bd 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart @@ -16,6 +16,7 @@ import 'src/cubic_bezier.dart'; import 'src/cull_opacity.dart'; import 'src/filtered_child_animation.dart'; import 'src/fullscreen_textfield.dart'; +import 'src/gradient_perf.dart'; import 'src/heavy_grid_view.dart'; import 'src/large_image_changer.dart'; import 'src/large_images.dart'; @@ -69,6 +70,8 @@ class MacrobenchmarksApp extends StatelessWidget { kAnimatedImageRouteName: (BuildContext context) => const AnimatedImagePage(), kOpacityPeepholeRouteName: (BuildContext context) => const OpacityPeepholePage(), ...opacityPeepholeRoutes, + kGradientPerfRouteName: (BuildContext context) => const GradientPerfHomePage(), + ...gradientPerfRoutes, }, ); } @@ -247,6 +250,13 @@ class HomePage extends StatelessWidget { Navigator.pushNamed(context, kOpacityPeepholeRouteName); }, ), + ElevatedButton( + key: const Key(kGradientPerfRouteName), + child: const Text('Gradient performance tests'), + onPressed: () { + Navigator.pushNamed(context, kGradientPerfRouteName); + }, + ), ], ), ); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/gradient_perf.dart b/dev/benchmarks/macrobenchmarks/lib/src/gradient_perf.dart new file mode 100644 index 00000000000..f6b02362e7f --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/gradient_perf.dart @@ -0,0 +1,265 @@ +// 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:math'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +import '../common.dart'; + +Map gradientPerfRoutes = { + kGradientPerfRecreateDynamicRouteName: (BuildContext _) => const RecreateDynamicPainterPage(), + kGradientPerfRecreateConsistentRouteName: (BuildContext _) => const RecreateConsistentPainterPage(), + kGradientPerfStaticConsistentRouteName: (BuildContext _) => const StaticConsistentPainterPage(), +}; + +typedef CustomPaintFactory = CustomPainter Function(double hue); + +class GradientPerfHomePage extends StatelessWidget { + const GradientPerfHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Gradient Perf')), + body: ListView( + key: const Key(kGradientPerfScrollableName), + children: [ + ElevatedButton( + key: const Key(kGradientPerfRecreateDynamicRouteName), + child: const Text('Recreate Dynamic Gradients'), + onPressed: () { + Navigator.pushNamed(context, kGradientPerfRecreateDynamicRouteName); + }, + ), + ElevatedButton( + key: const Key(kGradientPerfRecreateConsistentRouteName), + child: const Text('Recreate Same Gradients'), + onPressed: () { + Navigator.pushNamed(context, kGradientPerfRecreateConsistentRouteName); + }, + ), + ElevatedButton( + key: const Key(kGradientPerfStaticConsistentRouteName), + child: const Text('Static Gradients'), + onPressed: () { + Navigator.pushNamed(context, kGradientPerfStaticConsistentRouteName); + }, + ), + ], + ), + ); + } +} + +class _PainterPage extends StatefulWidget { + const _PainterPage({Key? key, required this.title, required this.factory}) : super(key: key); + + final String title; + final CustomPaintFactory factory; + + @override + State<_PainterPage> createState() => _PainterPageState(); +} + +class RecreateDynamicPainterPage extends _PainterPage { + const RecreateDynamicPainterPage({Key? key}) + : super(key: key, title: 'Recreate Dynamic Gradients', factory: makePainter); + + static CustomPainter makePainter(double f) { + return RecreatedDynamicGradients(baseFactor: f); + } +} + +class RecreateConsistentPainterPage extends _PainterPage { + const RecreateConsistentPainterPage({Key? key}) + : super(key: key, title: 'Recreate Same Gradients', factory: makePainter); + + static CustomPainter makePainter(double f) { + return RecreatedConsistentGradients(baseFactor: f); + } +} + +class StaticConsistentPainterPage extends _PainterPage { + const StaticConsistentPainterPage({Key? key}) + : super(key: key, title: 'Reuse Same Gradients', factory: makePainter); + + static CustomPainter makePainter(double f) { + return StaticConsistentGradients(baseFactor: f); + } +} + +class _PainterPageState extends State<_PainterPage> with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this); + _controller.repeat(period: const Duration(seconds: 2)); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: AnimatedBuilder( + animation: _controller, + builder: (BuildContext context, Widget? child) { + return CustomPaint( + size: const Size(paintW, paintH), + painter: widget.factory(_controller.value), + willChange: true, + ); + }, + ), + ), + ); + } +} + +Color color(double factor) { + int v = ((factor * 255 * 3) % (255 * 3)).round(); + if (v < 0) { + v += 255 * 3; + } + int r = 0; + int g = 0; + int b = 0; + if (v < 255) { + r = 255 - v; + g = v; + } else { + v -= 255; + if (v < 255) { + g = 255 - v; + b = v; + } else { + v -= 255; + b = 255 - v; + r = v; + } + } + return Color.fromARGB(255, r, g, b); +} + +Shader rotatingGradient(double factor, double x, double y, double h) { + final double s = sin(factor * 2 * pi) * h/8; + final double c = cos(factor * 2 * pi) * h/8; + final double cx = x; + final double cy = y + h/2; + final Offset p0 = Offset(cx + s, cy + c); + final Offset p1 = Offset(cx - s, cy - c); + return ui.Gradient.linear(p0, p1, [ + color(factor), + color(factor + 0.5), + ]); +} + +const int nAcross = 12; +const int nDown = 16; +const double cellW = 20; +const double cellH = 20; +const double hGap = 5; +const double vGap = 5; +const double paintW = hGap + (cellW + hGap) * nAcross; +const double paintH = vGap + (cellH + vGap) * nDown; + +double x(int i, int j) { + return hGap + i * (cellW + hGap); +} + +double y(int i, int j) { + return vGap + j * (cellH + vGap); +} + +Shader gradient(double baseFactor, int i, int j) { + final double lineFactor = baseFactor + 1/3 + 0.5 * (j + 1) / (nDown + 1); + final double cellFactor = lineFactor + 1/3 * (i + 1) / (nAcross + 1); + return rotatingGradient(cellFactor, x(i, j) + cellW / 2, y(i, j), cellH); +} + +class RecreatedDynamicGradients extends CustomPainter { + RecreatedDynamicGradients({required this.baseFactor}); + + final double baseFactor; + + @override + void paint(Canvas canvas, Size size) { + final Paint p = Paint(); + p.color = color(baseFactor); + canvas.drawRect(Offset.zero & size, p); + for (int j = 0; j < nDown; j++) { + for (int i = 0; i < nAcross; i++) { + p.shader = gradient(baseFactor, i, j); + canvas.drawRect(Rect.fromLTWH(x(i, j), y(i, j), cellW, cellH), p); + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} + +class RecreatedConsistentGradients extends CustomPainter { + RecreatedConsistentGradients({required this.baseFactor}); + + final double baseFactor; + + @override + void paint(Canvas canvas, Size size) { + final Paint p = Paint(); + p.color = color(baseFactor); + canvas.drawRect(Offset.zero & size, p); + for (int j = 0; j < nDown; j++) { + for (int i = 0; i < nAcross; i++) { + p.shader = gradient(0, i, j); + canvas.drawRect(Rect.fromLTWH(x(i, j), y(i, j), cellW, cellH), p); + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} + +class StaticConsistentGradients extends CustomPainter { + StaticConsistentGradients({required this.baseFactor}); + + final double baseFactor; + + static List> gradients = >[ + for (int j = 0; j < nDown; j++) + [ + for (int i = 0; i < nAcross; i++) + gradient(0, i, j), + ], + ]; + + @override + void paint(Canvas canvas, Size size) { + final Paint p = Paint(); + p.color = color(baseFactor); + canvas.drawRect(Offset.zero & size, p); + for (int j = 0; j < nDown; j++) { + for (int i = 0; i < nAcross; i++) { + p.shader = gradients[j][i]; + canvas.drawRect(Rect.fromLTWH(x(i, j), y(i, j), cellW, cellH), p); + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/dev/benchmarks/macrobenchmarks/test/gradient_consistent_perf_e2e.dart b/dev/benchmarks/macrobenchmarks/test/gradient_consistent_perf_e2e.dart new file mode 100644 index 00000000000..1cbfd91c287 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test/gradient_consistent_perf_e2e.dart @@ -0,0 +1,19 @@ +// 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:macrobenchmarks/common.dart'; + +import 'util.dart'; + +void main() { + macroPerfTestMultiPageE2E( + 'gradient_consistent_perf', + [ + ScrollableButtonRoute(kScrollableName, kGradientPerfRouteName), + ScrollableButtonRoute(kGradientPerfScrollableName, kGradientPerfRecreateConsistentRouteName), + ], + pageDelay: const Duration(seconds: 1), + duration: const Duration(seconds: 10), + ); +} diff --git a/dev/benchmarks/macrobenchmarks/test/gradient_dynamic_perf_e2e.dart b/dev/benchmarks/macrobenchmarks/test/gradient_dynamic_perf_e2e.dart new file mode 100644 index 00000000000..fbff6e3e603 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test/gradient_dynamic_perf_e2e.dart @@ -0,0 +1,19 @@ +// 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:macrobenchmarks/common.dart'; + +import 'util.dart'; + +void main() { + macroPerfTestMultiPageE2E( + 'gradient_dynamic_perf', + [ + ScrollableButtonRoute(kScrollableName, kGradientPerfRouteName), + ScrollableButtonRoute(kGradientPerfScrollableName, kGradientPerfRecreateDynamicRouteName), + ], + pageDelay: const Duration(seconds: 1), + duration: const Duration(seconds: 10), + ); +} diff --git a/dev/benchmarks/macrobenchmarks/test/gradient_static_perf_e2e.dart b/dev/benchmarks/macrobenchmarks/test/gradient_static_perf_e2e.dart new file mode 100644 index 00000000000..dbeb5a1cb5f --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test/gradient_static_perf_e2e.dart @@ -0,0 +1,19 @@ +// 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:macrobenchmarks/common.dart'; + +import 'util.dart'; + +void main() { + macroPerfTestMultiPageE2E( + 'gradient_static_perf', + [ + ScrollableButtonRoute(kScrollableName, kGradientPerfRouteName), + ScrollableButtonRoute(kGradientPerfScrollableName, kGradientPerfStaticConsistentRouteName), + ], + pageDelay: const Duration(seconds: 1), + duration: const Duration(seconds: 10), + ); +} diff --git a/dev/devicelab/bin/tasks/gradient_consistent_perf__e2e_summary.dart b/dev/devicelab/bin/tasks/gradient_consistent_perf__e2e_summary.dart new file mode 100644 index 00000000000..416eb07cbdb --- /dev/null +++ b/dev/devicelab/bin/tasks/gradient_consistent_perf__e2e_summary.dart @@ -0,0 +1,14 @@ +// 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 'package:flutter_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/perf_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createGradientConsistentPerfE2ETest()); +} diff --git a/dev/devicelab/bin/tasks/gradient_dynamic_perf__e2e_summary.dart b/dev/devicelab/bin/tasks/gradient_dynamic_perf__e2e_summary.dart new file mode 100644 index 00000000000..31fad947d41 --- /dev/null +++ b/dev/devicelab/bin/tasks/gradient_dynamic_perf__e2e_summary.dart @@ -0,0 +1,14 @@ +// 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 'package:flutter_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/perf_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createGradientDynamicPerfE2ETest()); +} diff --git a/dev/devicelab/bin/tasks/gradient_static_perf__e2e_summary.dart b/dev/devicelab/bin/tasks/gradient_static_perf__e2e_summary.dart new file mode 100644 index 00000000000..9c86acc1fda --- /dev/null +++ b/dev/devicelab/bin/tasks/gradient_static_perf__e2e_summary.dart @@ -0,0 +1,14 @@ +// 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 'package:flutter_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/perf_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createGradientStaticPerfE2ETest()); +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index 72668cf9be0..a004d8580d8 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -556,6 +556,27 @@ TaskFunction createOpacityPeepholeColOfAlphaSaveLayerRowsPerfE2ETest() { ).run; } +TaskFunction createGradientDynamicPerfE2ETest() { + return PerfTest.e2e( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test/gradient_dynamic_perf_e2e.dart', + ).run; +} + +TaskFunction createGradientConsistentPerfE2ETest() { + return PerfTest.e2e( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test/gradient_consistent_perf_e2e.dart', + ).run; +} + +TaskFunction createGradientStaticPerfE2ETest() { + return PerfTest.e2e( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test/gradient_static_perf_e2e.dart', + ).run; +} + Map _average(List> results, int iterations) { final Map tally = {}; for (final Map item in results) {