Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c80d1e8

Browse files
authoredMar 13, 2020
Perf test for color filter with saveLayer (flutter#52063)
1 parent 3bc3609 commit c80d1e8

File tree

8 files changed

+243
-0
lines changed

8 files changed

+243
-0
lines changed
 

‎dev/benchmarks/macrobenchmarks/lib/common.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ const String kPictureCacheRouteName = '/picture_cache';
1111
const String kLargeImagesRouteName = '/large_images';
1212
const String kTextRouteName = '/text';
1313
const String kAnimatedPlaceholderRouteName = '/animated_placeholder';
14+
const String kColorFilterAndFadeRouteName = '/color_filter_and_fade';

‎dev/benchmarks/macrobenchmarks/lib/main.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'package:flutter/material.dart';
6+
import 'package:macrobenchmarks/src/color_filter_and_fade.dart';
67
import 'package:macrobenchmarks/src/large_images.dart';
78
import 'package:macrobenchmarks/src/picture_cache.dart';
89

@@ -38,6 +39,7 @@ class MacrobenchmarksApp extends StatelessWidget {
3839
kLargeImagesRouteName: (BuildContext context) => LargeImagesPage(),
3940
kTextRouteName: (BuildContext context) => TextPage(),
4041
kAnimatedPlaceholderRouteName: (BuildContext context) => AnimatedPlaceholderPage(),
42+
kColorFilterAndFadeRouteName: (BuildContext context) => ColorFilterAndFadePage(),
4143
},
4244
);
4345
}
@@ -115,6 +117,13 @@ class HomePage extends StatelessWidget {
115117
Navigator.pushNamed(context, kAnimatedPlaceholderRouteName);
116118
},
117119
),
120+
RaisedButton(
121+
key: const Key(kColorFilterAndFadeRouteName),
122+
child: const Text('Color Filter and Fade'),
123+
onPressed: () {
124+
Navigator.pushNamed(context, kColorFilterAndFadeRouteName);
125+
},
126+
),
118127
],
119128
),
120129
);
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:ui' as ui;
6+
import 'dart:ui';
7+
8+
import 'package:flutter/foundation.dart';
9+
import 'package:flutter/material.dart';
10+
import 'package:flutter/widgets.dart';
11+
import 'package:flutter/rendering.dart';
12+
13+
// This tests whether the Opacity layer raster cache works with color filters.
14+
// See https://github.com/flutter/flutter/issues/51975.
15+
class ColorFilterAndFadePage extends StatefulWidget {
16+
@override
17+
_ColorFilterAndFadePageState createState() => _ColorFilterAndFadePageState();
18+
}
19+
20+
class _ColorFilterAndFadePageState extends State<ColorFilterAndFadePage> with TickerProviderStateMixin {
21+
@override
22+
Widget build(BuildContext context) {
23+
final Widget shadowWidget = _ShadowWidget(
24+
width: 24,
25+
height: 24,
26+
useColorFilter: _useColorFilter,
27+
shadow: ui.Shadow(
28+
color: Colors.black45,
29+
offset: const Offset(0.0, 2.0),
30+
blurRadius: 4.0,
31+
),
32+
);
33+
34+
final Widget row = Row(
35+
mainAxisAlignment: MainAxisAlignment.center,
36+
children: <Widget>[
37+
shadowWidget,
38+
const SizedBox(width: 12),
39+
shadowWidget,
40+
const SizedBox(width: 12),
41+
shadowWidget,
42+
const SizedBox(width: 12),
43+
shadowWidget,
44+
const SizedBox(width: 12),
45+
shadowWidget,
46+
const SizedBox(width: 12),
47+
],
48+
);
49+
50+
final Widget column = Column(mainAxisAlignment: MainAxisAlignment.center,
51+
children: <Widget>[
52+
row,
53+
const SizedBox(height: 12),
54+
row,
55+
const SizedBox(height: 12),
56+
row,
57+
const SizedBox(height: 12),
58+
row,
59+
const SizedBox(height: 12),
60+
],
61+
);
62+
63+
final Widget fadeTransition = FadeTransition(
64+
opacity: _opacityAnimation,
65+
// This RepaintBoundary is necessary to not let the opacity change
66+
// invalidate the layer raster cache below. This is necessary with
67+
// or without the color filter.
68+
child: RepaintBoundary(
69+
child: column,
70+
),
71+
);
72+
73+
return Scaffold(
74+
backgroundColor: Colors.lightBlue,
75+
body: Center(
76+
child: Column(
77+
mainAxisSize: MainAxisSize.min,
78+
children: <Widget>[
79+
fadeTransition,
80+
Container(height: 20),
81+
const Text('Use Color Filter:'),
82+
Checkbox(
83+
value: _useColorFilter,
84+
onChanged: (bool value) {
85+
setState(() {
86+
_useColorFilter = value;
87+
});
88+
},
89+
),
90+
],
91+
),
92+
),
93+
);
94+
}
95+
96+
// Create a looping fade-in fade-out animation for opacity.
97+
void _initAnimation() {
98+
_controller = AnimationController(duration: const Duration(seconds: 3), vsync: this);
99+
_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
100+
_opacityAnimation.addStatusListener((AnimationStatus status) {
101+
if (status == AnimationStatus.completed) {
102+
_controller.reverse();
103+
} else if (status == AnimationStatus.dismissed) {
104+
_controller.forward();
105+
}
106+
});
107+
_controller.forward();
108+
}
109+
110+
@override
111+
void initState() {
112+
super.initState();
113+
_initAnimation();
114+
}
115+
116+
AnimationController _controller;
117+
Animation<double> _opacityAnimation;
118+
bool _useColorFilter = true;
119+
}
120+
121+
class _ShadowWidget extends StatelessWidget {
122+
const _ShadowWidget({
123+
@required this.width,
124+
@required this.height,
125+
@required this.useColorFilter,
126+
@required this.shadow,
127+
});
128+
129+
final double width;
130+
final double height;
131+
final bool useColorFilter;
132+
final Shadow shadow;
133+
134+
@override
135+
Widget build(BuildContext context) {
136+
return SizedBox(
137+
width: width,
138+
height: height,
139+
child: CustomPaint(
140+
painter: _ShadowPainter(
141+
useColorFilter: useColorFilter,
142+
shadow: shadow,
143+
),
144+
size: Size(width, height),
145+
isComplex: true,
146+
willChange: false,
147+
),
148+
);
149+
}
150+
}
151+
152+
class _ShadowPainter extends CustomPainter {
153+
const _ShadowPainter({this.useColorFilter, @required this.shadow});
154+
155+
final bool useColorFilter;
156+
final Shadow shadow;
157+
158+
@override
159+
void paint(Canvas canvas, Size size) {
160+
final Rect rect = Offset.zero & size;
161+
162+
final Paint paint = Paint();
163+
if (useColorFilter) {
164+
paint.colorFilter = ColorFilter.mode(shadow.color, BlendMode.srcIn);
165+
}
166+
167+
canvas.saveLayer(null, paint);
168+
canvas.translate(shadow.offset.dx, shadow.offset.dy);
169+
canvas.drawRect(rect, Paint());
170+
canvas.drawRect(rect, Paint()..maskFilter = MaskFilter.blur(BlurStyle.normal, shadow.blurSigma));
171+
canvas.restore();
172+
173+
canvas.drawRect(rect, Paint()..color = useColorFilter ? Colors.white : Colors.black);
174+
}
175+
176+
@override
177+
bool shouldRepaint(_ShadowPainter oldDelegate) => oldDelegate.useColorFilter != useColorFilter;
178+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_driver/driver_extension.dart';
6+
import 'package:macrobenchmarks/main.dart' as app;
7+
8+
void main() {
9+
enableFlutterDriverExtension();
10+
app.main();
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:macrobenchmarks/common.dart';
6+
7+
import 'util.dart';
8+
9+
void main() {
10+
macroPerfTest(
11+
'color_filter_and_fade_perf',
12+
kColorFilterAndFadeRouteName,
13+
pageDelay: const Duration(seconds: 1),
14+
duration: const Duration(seconds: 10),
15+
);
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:flutter_devicelab/tasks/perf_tests.dart';
8+
import 'package:flutter_devicelab/framework/adb.dart';
9+
import 'package:flutter_devicelab/framework/framework.dart';
10+
11+
Future<void> main() async {
12+
deviceOperatingSystem = DeviceOperatingSystem.android;
13+
await task(createColorFilterAndFadePerfTest());
14+
}

‎dev/devicelab/lib/tasks/perf_tests.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ TaskFunction createTextfieldPerfTest() {
173173
).run;
174174
}
175175

176+
TaskFunction createColorFilterAndFadePerfTest() {
177+
return PerfTest(
178+
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
179+
'test_driver/color_filter_and_fade_perf.dart',
180+
'color_filter_and_fade_perf',
181+
).run;
182+
}
183+
176184

177185
/// Measure application startup performance.
178186
class StartupTest {

‎dev/devicelab/manifest.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ tasks:
190190
stage: devicelab
191191
required_agent_capabilities: ["mac/android"]
192192

193+
color_filter_and_fade_perf__timeline_summary:
194+
description: >
195+
Measures the runtime performance of color filter with fade on Android.
196+
stage: devicelab
197+
required_agent_capabilities: ["mac/android"]
198+
193199
flavors_test:
194200
description: >
195201
Checks that flavored builds work on Android.

0 commit comments

Comments
 (0)
Please sign in to comment.