diff --git a/.ci.yaml b/.ci.yaml index dab1f17fb0..5bb50095d5 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -2114,6 +2114,16 @@ targets: ["devicelab", "android", "linux"] task_name: fullscreen_textfield_perf__e2e_summary + - name: Linux_android very_long_picture_scrolling_perf__e2e_summary + recipe: devicelab/devicelab_drone + bringup: true + presubmit: false + timeout: 120 + properties: + tags: > + ["devicelab", "android", "linux"] + task_name: very_long_picture_scrolling_perf__e2e_summary + - name: Linux_android hello_world__memory recipe: devicelab/devicelab_drone presubmit: false @@ -4196,6 +4206,16 @@ targets: ["devicelab", "ios", "mac"] task_name: fullscreen_textfield_perf_ios__e2e_summary + - name: Mac_ios very_long_picture_scrolling_perf_ios__e2e_summary + recipe: devicelab/devicelab_drone + bringup: true + presubmit: false + timeout: 120 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: very_long_picture_scrolling_perf_ios__e2e_summary + - name: Mac_ios tiles_scroll_perf_ios__timeline_summary recipe: devicelab/devicelab_drone presubmit: false diff --git a/TESTOWNERS b/TESTOWNERS index ed1c757708..7da41d661c 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -89,6 +89,7 @@ /dev/devicelab/bin/tasks/spell_check_test_ios.dart @camsim99 @flutter/android /dev/devicelab/bin/tasks/spell_check_test.dart @camsim99 @flutter/android /dev/devicelab/bin/tasks/textfield_perf__e2e_summary.dart @zanderso @flutter/engine +/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf__e2e_summary.dart @flar @flutter/engine /dev/devicelab/bin/tasks/web_size__compile_test.dart @yjbanov @flutter/web /dev/devicelab/bin/tasks/wide_gamut_ios.dart @gaaclarke @flutter/engine /dev/devicelab/bin/tasks/animated_advanced_blend_perf__timeline_summary.dart @gaaclarke @flutter/engine @@ -212,6 +213,7 @@ /dev/devicelab/bin/tasks/route_test_ios.dart @vashworth @flutter/tool /dev/devicelab/bin/tasks/simple_animation_perf_ios.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/tiles_scroll_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf_ios__e2e_summary.dart @flar @flutter/engine /dev/devicelab/bin/tasks/animated_blur_backdrop_filter_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/draw_points_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/draw_vertices_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index e347f1b906..0b4b380799 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -13,6 +13,7 @@ const String kLargeImageChangerRouteName = '/large_image_changer'; const String kLargeImagesRouteName = '/large_images'; const String kPathTessellationRouteName = '/path_tessellation'; const String kTextRouteName = '/text'; +const String kVeryLongPictureScrollingRouteName = '/very_long_picture_scrolling'; const String kFullscreenTextRouteName = '/fullscreen_text'; const String kAnimatedPlaceholderRouteName = '/animated_placeholder'; const String kClipperCacheRouteName = '/clipper_cache'; diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index 5cc99cda8e..23ec7817c4 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart @@ -42,6 +42,7 @@ import 'src/simple_scroll.dart'; import 'src/sliders.dart'; import 'src/stack_size.dart'; import 'src/text.dart'; +import 'src/very_long_picture_scrolling.dart'; const String kMacrobenchmarks = 'Macrobenchmarks'; @@ -97,6 +98,7 @@ class MacrobenchmarksApp extends StatelessWidget { kDrawVerticesPageRouteName: (BuildContext context) => const DrawVerticesPage(), kDrawAtlasPageRouteName: (BuildContext context) => const DrawAtlasPage(), kAnimatedAdvancedBlend: (BuildContext context) => const AnimatedAdvancedBlend(), + kVeryLongPictureScrollingRouteName: (BuildContext context) => const VeryLongPictureScrollingPerf(), }, ); } @@ -373,6 +375,13 @@ class HomePage extends StatelessWidget { Navigator.pushNamed(context, kAnimatedAdvancedBlend); }, ), + ElevatedButton( + key: const Key(kVeryLongPictureScrollingRouteName), + child: const Text('Very Long Picture Scrolling'), + onPressed: () { + Navigator.pushNamed(context, kVeryLongPictureScrollingRouteName); + }, + ), ], ), ); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/very_long_picture_scrolling.dart b/dev/benchmarks/macrobenchmarks/lib/src/very_long_picture_scrolling.dart new file mode 100644 index 0000000000..fb6a7cf20b --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/very_long_picture_scrolling.dart @@ -0,0 +1,240 @@ +// 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:typed_data'; +import 'dart:ui'; +import 'package:flutter/material.dart'; + +// Adapted from test case submitted in +// https://github.com/flutter/flutter/issues/92366 +// Converted to use fixed data rather than reading a waveform file +class VeryLongPictureScrollingPerf extends StatefulWidget { + const VeryLongPictureScrollingPerf({super.key}); + + @override + State createState() => VeryLongPictureScrollingPerfState(); +} + +class VeryLongPictureScrollingPerfState extends State { + bool consolidate = false; + bool useList = false; + Int16List waveData = loadGraph(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + actions: [ + Row( + children: [ + const Text('list:'), + Checkbox(value: useList, onChanged: (bool? value) => setState(() { + useList = value!; + }),), + ], + ), + Row( + children: [ + const Text('consolidate:'), + Checkbox(value: consolidate, onChanged: (bool? value) => setState(() { + consolidate = value!; + }),), + ], + ), + ], + ), + backgroundColor: Colors.transparent, + body: SizedBox( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: useList + ? ListView.builder( + key: const ValueKey('vlp_list_view_scrollable'), + scrollDirection: Axis.horizontal, + clipBehavior: Clip.none, + itemCount: (waveData.length / 200).ceil(), + itemExtent: 100, + itemBuilder: (BuildContext context, int index) => CustomPaint( + painter: PaintSomeTest( + waveData: waveData, + from: index * 200, + to: min((index + 1) * 200, waveData.length - 1), + ) + ), + ) + : SingleChildScrollView( + key: const ValueKey('vlp_single_child_scrollable'), + scrollDirection: Axis.horizontal, + child: SizedBox( + width: MediaQuery.of(context).size.width * 20, + height: MediaQuery.of(context).size.height, + child: RepaintBoundary( + child: CustomPaint( + isComplex: true, + painter: PaintTest( + consolidate: consolidate, + waveData: waveData, + ), + ), + ), + ), + ), + ), + ); + } +} + +class PaintTest extends CustomPainter { + const PaintTest({ + required this.consolidate, + required this.waveData, + }); + + final bool consolidate; + final Int16List waveData; + + @override + void paint(Canvas canvas, Size size) { + final double height = size.height; + double x = 0; + const double strokeSize = .5; + const double zoomFactor = .5; + + final Paint paintPos = Paint() + ..color = Colors.pink + ..strokeWidth = strokeSize + ..isAntiAlias = false + ..style = PaintingStyle.stroke; + + final Paint paintNeg = Paint() + ..color = Colors.pink + ..strokeWidth = strokeSize + ..isAntiAlias = false + ..style = PaintingStyle.stroke; + + final Paint paintZero = Paint() + ..color = Colors.green + ..strokeWidth = strokeSize + ..isAntiAlias = false + ..style = PaintingStyle.stroke; + + int index = 0; + Paint? listPaint; + final Float32List offsets = Float32List(consolidate ? waveData.length * 4 : 4); + int used = 0; + for (index = 0; index < waveData.length; index++) { + Paint curPaint; + Offset p1; + if (waveData[index].isNegative) { + curPaint = paintPos; + p1 = Offset(x, height * 1 / 2 - waveData[index] / 32768 * (height / 2)); + } else if (waveData[index] == 0) { + curPaint = paintZero; + p1 = Offset(x, height * 1 / 2 + 1); + } else { + curPaint = (waveData[index] == 0) ? paintZero : paintNeg; + p1 = Offset(x, height * 1 / 2 - waveData[index] / 32767 * (height / 2)); + } + final Offset p0 = Offset(x, height * 1 / 2); + if (consolidate) { + if (listPaint != null && listPaint != curPaint) { + canvas.drawRawPoints(PointMode.lines, offsets.sublist(0, used), listPaint); + used = 0; + } + listPaint = curPaint; + offsets[used++] = p0.dx; + offsets[used++] = p0.dy; + offsets[used++] = p1.dx; + offsets[used++] = p1.dy; + } else { + canvas.drawLine(p0, p1, curPaint); + } + x += zoomFactor; + } + if (consolidate && used > 0) { + canvas.drawRawPoints(PointMode.lines, offsets.sublist(0, used), listPaint!); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return oldDelegate is! PaintTest || + oldDelegate.consolidate != consolidate || + oldDelegate.waveData != waveData; + } +} + +class PaintSomeTest extends CustomPainter { + const PaintSomeTest({ + required this.waveData, + int? from, + int? to, + }) : from = from ?? 0, to = to?? waveData.length; + + final Int16List waveData; + final int from; + final int to; + + @override + void paint(Canvas canvas, Size size) { + final double height = size.height; + double x = 0; + const double strokeSize = .5; + const double zoomFactor = .5; + + final Paint paintPos = Paint() + ..color = Colors.pink + ..strokeWidth = strokeSize + ..isAntiAlias = false + ..style = PaintingStyle.stroke; + + final Paint paintNeg = Paint() + ..color = Colors.pink + ..strokeWidth = strokeSize + ..isAntiAlias = false + ..style = PaintingStyle.stroke; + + final Paint paintZero = Paint() + ..color = Colors.green + ..strokeWidth = strokeSize + ..isAntiAlias = false + ..style = PaintingStyle.stroke; + + for (int index = from; index <= to; index++) { + Paint curPaint; + Offset p1; + if (waveData[index].isNegative) { + curPaint = paintPos; + p1 = Offset(x, height * 1 / 2 - waveData[index] / 32768 * (height / 2)); + } else if (waveData[index] == 0) { + curPaint = paintZero; + p1 = Offset(x, height * 1 / 2 + 1); + } else { + curPaint = (waveData[index] == 0) ? paintZero : paintNeg; + p1 = Offset(x, height * 1 / 2 - waveData[index] / 32767 * (height / 2)); + } + final Offset p0 = Offset(x, height * 1 / 2); + canvas.drawLine(p0, p1, curPaint); + x += zoomFactor; + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return oldDelegate is! PaintSomeTest || + oldDelegate.waveData != waveData || + oldDelegate.from != from || + oldDelegate.to != to; + } +} + +Int16List loadGraph() { + final Int16List waveData = Int16List(350000); + final Random r = Random(0x42); + for (int i = 0; i < waveData.length; i++) { + waveData[i] = r.nextInt(32768) - 16384; + } + return waveData; +} diff --git a/dev/benchmarks/macrobenchmarks/test/very_long_picture_scrolling_perf_e2e.dart b/dev/benchmarks/macrobenchmarks/test/very_long_picture_scrolling_perf_e2e.dart new file mode 100644 index 0000000000..ff54e0cce6 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test/very_long_picture_scrolling_perf_e2e.dart @@ -0,0 +1,36 @@ +// 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/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:macrobenchmarks/common.dart'; + +import 'util.dart'; + +void main() { + macroPerfTestE2E( + 'very_long_picture_scrolling_perf', + kVeryLongPictureScrollingRouteName, + pageDelay: const Duration(seconds: 1), + duration: const Duration(seconds: 30), + body: (WidgetController controller) async { + final Finder nestedScroll = find.byKey(const ValueKey('vlp_single_child_scrollable')); + expect(nestedScroll, findsOneWidget); + Future scrollOnce(double offset) async { + await controller.timedDrag( + nestedScroll, + Offset(offset, 0.0), + const Duration(milliseconds: 3500), + ); + await Future.delayed(const Duration(milliseconds: 500)); + } + for (int i = 0; i < 2; i += 1) { + await scrollOnce(-3000.0); + await scrollOnce(-3000.0); + await scrollOnce(3000.0); + await scrollOnce(3000.0); + } + }, + ); +} diff --git a/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf__e2e_summary.dart b/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf__e2e_summary.dart new file mode 100644 index 0000000000..ef738f8510 --- /dev/null +++ b/dev/devicelab/bin/tasks/very_long_picture_scrolling_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(createVeryLongPictureScrollingPerfE2ETest(enableImpeller: false)); +} diff --git a/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf_ios__e2e_summary.dart b/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf_ios__e2e_summary.dart new file mode 100644 index 0000000000..99e61f877f --- /dev/null +++ b/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf_ios__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.ios; + await task(createVeryLongPictureScrollingPerfE2ETest(enableImpeller: false)); +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index 80706c6e2c..9018dfda43 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -308,6 +308,13 @@ TaskFunction createTextfieldPerfE2ETest() { ).run; } +TaskFunction createVeryLongPictureScrollingPerfE2ETest({required bool enableImpeller}) { + return PerfTest.e2e( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test/very_long_picture_scrolling_perf_e2e.dart', + enableImpeller: enableImpeller, + ).run; +} TaskFunction createSlidersPerfTest() { return PerfTest( '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',