Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
dl_complexity_metal.cc
Go to the documentation of this file.
1// Copyright 2013 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#include "flutter/display_list/benchmarking/dl_complexity_metal.h"
6
7// The numbers and weightings used in this file stem from taking the
8// data from the DisplayListBenchmarks suite run on an iPhone 12 and
9// applying very rough analysis on them to identify the approximate
10// trends.
11//
12// See the comments in display_list_complexity_helper.h for details on the
13// process and rationale behind coming up with these numbers.
14
15namespace flutter {
16
17DisplayListMetalComplexityCalculator*
18 DisplayListMetalComplexityCalculator::instance_ = nullptr;
19
20DisplayListMetalComplexityCalculator*
22 if (instance_ == nullptr) {
24 }
25 return instance_;
26}
27
28unsigned int
29DisplayListMetalComplexityCalculator::MetalHelper::BatchedComplexity() {
30 // Calculate the impact of saveLayer.
31 unsigned int save_layer_complexity;
32 if (save_layer_count_ == 0) {
33 save_layer_complexity = 0;
34 } else {
35 // saveLayer seems to have two trends; if the count is < 200,
36 // then the individual cost of a saveLayer is higher than if
37 // the count is > 200.
38 //
39 // However, the trend is strange and we should gather more data to
40 // get a better idea of how to represent the trend. That being said, it's
41 // very unlikely we'll ever hit a DisplayList with 200+ saveLayer calls
42 // in it, so we will calculate based on the more reasonably anticipated
43 // range of less than 200, with the trend line more weighted towards the
44 // lower end of that range (as the data itself doesn't present as a straight
45 // line). Further, we will easily hit our cache thresholds with such a
46 // large number of saveLayer calls.
47 //
48 // m = 1/2
49 // c = 1
50 save_layer_complexity = (save_layer_count_ + 2) * 100000;
51 }
52
53 unsigned int draw_text_blob_complexity;
54 if (draw_text_blob_count_ == 0) {
55 draw_text_blob_complexity = 0;
56 } else {
57 // m = 1/240
58 // c = 0.75
59 draw_text_blob_complexity = (draw_text_blob_count_ + 180) * 2500 / 3;
60 }
61
62 return save_layer_complexity + draw_text_blob_complexity;
63}
64
65void DisplayListMetalComplexityCalculator::MetalHelper::saveLayer(
66 const SkRect& bounds,
67 const SaveLayerOptions options,
68 const DlImageFilter* backdrop) {
69 if (IsComplex()) {
70 return;
71 }
72 if (backdrop) {
73 // Flutter does not offer this operation so this value can only ever be
74 // non-null for a frame-wide builder which is not currently evaluated for
75 // complexity.
76 AccumulateComplexity(Ceiling());
77 }
78 save_layer_count_++;
79}
80
81void DisplayListMetalComplexityCalculator::MetalHelper::drawLine(
82 const SkPoint& p0,
83 const SkPoint& p1) {
84 if (IsComplex()) {
85 return;
86 }
87 // The curve here may be log-linear, although it doesn't really match up that
88 // well. To avoid costly computations, try and do a best fit of the data onto
89 // a linear graph as a very rough first order approximation.
90
91 float non_hairline_penalty = 1.0f;
92 float aa_penalty = 1.0f;
93
94 if (!IsHairline()) {
95 non_hairline_penalty = 1.15f;
96 }
97 if (IsAntiAliased()) {
98 aa_penalty = 1.4f;
99 }
100
101 // Use an approximation for the distance to avoid floating point or
102 // sqrt() calls.
103 SkScalar distance = abs(p0.x() - p1.x()) + abs(p0.y() - p1.y());
104
105 // The baseline complexity is for a hairline stroke with no AA.
106 // m = 1/45
107 // c = 5
108 unsigned int complexity =
109 ((distance + 225) * 4 / 9) * non_hairline_penalty * aa_penalty;
110
111 AccumulateComplexity(complexity);
112}
113
114void DisplayListMetalComplexityCalculator::MetalHelper::drawRect(
115 const SkRect& rect) {
116 if (IsComplex()) {
117 return;
118 }
119
120 unsigned int complexity;
121
122 // If stroked, cost scales linearly with the rectangle width/height.
123 // If filled, it scales with the area.
124 //
125 // Hairline stroke vs non hairline has no real penalty at smaller lengths,
126 // but it increases at larger lengths. There isn't enough data to get a good
127 // idea of the penalty at lengths > 1000px.
128 //
129 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
130 // currently use it anywhere in Flutter.
131 if (DrawStyle() == DlDrawStyle::kFill) {
132 // No real difference for AA with filled styles.
133 unsigned int area = rect.width() * rect.height();
134
135 // m = 1/9000
136 // c = 0
137 complexity = area / 225;
138 } else {
139 // Take the average of the width and height.
140 unsigned int length = (rect.width() + rect.height()) / 2;
141
142 // There is a penalty for AA being *disabled*.
143 if (IsAntiAliased()) {
144 // m = 1/65
145 // c = 0
146 complexity = length * 8 / 13;
147 } else {
148 // m = 1/35
149 // c = 0
150 complexity = length * 8 / 7;
151 }
152 }
153
154 AccumulateComplexity(complexity);
155}
156
157void DisplayListMetalComplexityCalculator::MetalHelper::drawOval(
158 const SkRect& bounds) {
159 if (IsComplex()) {
160 return;
161 }
162 // DrawOval scales very roughly linearly with the bounding box width/height
163 // (not area) for stroked styles without AA.
164 //
165 // Filled styles and stroked styles with AA scale linearly with the bounding
166 // box area.
167 unsigned int area = bounds.width() * bounds.height();
168
169 unsigned int complexity;
170
171 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
172 // currently use it anywhere in Flutter.
173 if (DrawStyle() == DlDrawStyle::kFill) {
174 // With filled styles, there is no significant AA penalty.
175 // m = 1/16000
176 // c = 0
177 complexity = area / 80;
178 } else {
179 if (IsAntiAliased()) {
180 // m = 1/7500
181 // c = 0
182 complexity = area * 2 / 75;
183 } else {
184 // Take the average of the width and height.
185 unsigned int length = (bounds.width() + bounds.height()) / 2;
186
187 // m = 1/80
188 // c = 0
189 complexity = length * 5 / 2;
190 }
191 }
192
193 AccumulateComplexity(complexity);
194}
195
196void DisplayListMetalComplexityCalculator::MetalHelper::drawCircle(
197 const SkPoint& center,
198 SkScalar radius) {
199 if (IsComplex()) {
200 return;
201 }
202
203 unsigned int complexity;
204
205 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
206 // currently use it anywhere in Flutter.
207 if (DrawStyle() == DlDrawStyle::kFill) {
208 // We can ignore pi here.
209 unsigned int area = radius * radius;
210 // m = 1/1300
211 // c = 5
212 complexity = (area + 6500) * 2 / 65;
213
214 // Penalty of around 5% when AA is disabled.
215 if (!IsAntiAliased()) {
216 complexity *= 1.05f;
217 }
218 } else {
219 // Hairline vs non-hairline has no significant performance difference.
220 if (IsAntiAliased()) {
221 // m = 1/7
222 // c = 7
223 complexity = (radius + 49) * 40 / 7;
224 } else {
225 // m = 1/16
226 // c = 8
227 complexity = (radius + 128) * 5 / 2;
228 }
229 }
230
231 AccumulateComplexity(complexity);
232}
233
234void DisplayListMetalComplexityCalculator::MetalHelper::drawRRect(
235 const SkRRect& rrect) {
236 if (IsComplex()) {
237 return;
238 }
239 // RRects scale linearly with the area of the bounding rect.
240 unsigned int area = rrect.width() * rrect.height();
241
242 // Drawing RRects is split into two performance tiers; an expensive
243 // one and a less expensive one. Both scale linearly with area.
244 //
245 // Expensive: All filled style, symmetric w/AA.
246 bool expensive =
247 (DrawStyle() == DlDrawStyle::kFill) ||
248 ((rrect.getType() == SkRRect::Type::kSimple_Type) && IsAntiAliased());
249
250 unsigned int complexity;
251
252 // These values were worked out by creating a straight line graph (y=mx+c)
253 // approximately matching the measured data, normalising the data so that
254 // 0.0005ms resulted in a score of 100 then simplifying down the formula.
255 if (expensive) {
256 // m = 1/25000
257 // c = 2
258 // An area of 7000px^2 ~= baseline timing of 0.0005ms.
259 complexity = (area + 10500) / 175;
260 } else {
261 // m = 1/7000
262 // c = 1.5
263 // An area of 16000px^2 ~= baseline timing of 0.0005ms.
264 complexity = (area + 50000) / 625;
265 }
266
267 AccumulateComplexity(complexity);
268}
269
270void DisplayListMetalComplexityCalculator::MetalHelper::drawDRRect(
271 const SkRRect& outer,
272 const SkRRect& inner) {
273 if (IsComplex()) {
274 return;
275 }
276 // There are roughly four classes here:
277 // a) Filled style with AA enabled.
278 // b) Filled style with AA disabled.
279 // c) Complex RRect type with AA enabled and filled style.
280 // d) Everything else.
281 //
282 // a) and c) scale linearly with the area, b) and d) scale linearly with
283 // a single dimension (length). In all cases, the dimensions refer to
284 // the outer RRect.
285 unsigned int length = (outer.width() + outer.height()) / 2;
286
287 unsigned int complexity;
288
289 // These values were worked out by creating a straight line graph (y=mx+c)
290 // approximately matching the measured data, normalising the data so that
291 // 0.0005ms resulted in a score of 100 then simplifying down the formula.
292 //
293 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
294 // currently use it anywhere in Flutter.
295 if (DrawStyle() == DlDrawStyle::kFill) {
296 unsigned int area = outer.width() * outer.height();
297 if (outer.getType() == SkRRect::Type::kComplex_Type) {
298 // m = 1/1000
299 // c = 1
300 complexity = (area + 1000) / 10;
301 } else {
302 if (IsAntiAliased()) {
303 // m = 1/3500
304 // c = 1.5
305 complexity = (area + 5250) / 35;
306 } else {
307 // m = 1/30
308 // c = 1
309 complexity = (300 + (10 * length)) / 3;
310 }
311 }
312 } else {
313 // m = 1/60
314 // c = 1.75
315 complexity = ((10 * length) + 1050) / 6;
316 }
317
318 AccumulateComplexity(complexity);
319}
320
321void DisplayListMetalComplexityCalculator::MetalHelper::drawPath(
322 const SkPath& path) {
323 if (IsComplex()) {
324 return;
325 }
326 // There is negligible effect on the performance for hairline vs. non-hairline
327 // stroke widths.
328 //
329 // The data for filled styles is currently suspicious, so for now we are going
330 // to assign scores based on stroked styles.
331
332 unsigned int line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost;
333
334 if (IsAntiAliased()) {
335 line_verb_cost = 75;
336 quad_verb_cost = 100;
337 conic_verb_cost = 160;
338 cubic_verb_cost = 210;
339 } else {
340 line_verb_cost = 67;
341 quad_verb_cost = 80;
342 conic_verb_cost = 140;
343 cubic_verb_cost = 210;
344 }
345
346 // There seems to be a fixed cost of around 1ms for calling drawPath.
347 unsigned int complexity =
348 200000 + CalculatePathComplexity(path, line_verb_cost, quad_verb_cost,
349 conic_verb_cost, cubic_verb_cost);
350
351 AccumulateComplexity(complexity);
352}
353
354void DisplayListMetalComplexityCalculator::MetalHelper::drawArc(
355 const SkRect& oval_bounds,
356 SkScalar start_degrees,
357 SkScalar sweep_degrees,
358 bool use_center) {
359 if (IsComplex()) {
360 return;
361 }
362 // Hairline vs non-hairline makes no difference to the performance.
363 // Stroked styles without AA scale linearly with the diameter.
364 // Stroked styles with AA scale linearly with the area except for small
365 // values. Filled styles scale linearly with the area.
366 unsigned int diameter = (oval_bounds.width() + oval_bounds.height()) / 2;
367 unsigned int area = oval_bounds.width() * oval_bounds.height();
368
369 unsigned int complexity;
370
371 // These values were worked out by creating a straight line graph (y=mx+c)
372 // approximately matching the measured data, normalising the data so that
373 // 0.0005ms resulted in a score of 100 then simplifying down the formula.
374 //
375 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
376 // currently use it anywhere in Flutter.
377 if (DrawStyle() == DlDrawStyle::kStroke) {
378 if (IsAntiAliased()) {
379 // m = 1/8500
380 // c = 16
381 complexity = (area + 136000) * 2 / 765;
382 } else {
383 // m = 1/60
384 // c = 3
385 complexity = (diameter + 180) * 10 / 27;
386 }
387 } else {
388 if (IsAntiAliased()) {
389 // m = 1/20000
390 // c = 20
391 complexity = (area + 400000) / 900;
392 } else {
393 // m = 1/2100
394 // c = 8
395 complexity = (area + 16800) * 2 / 189;
396 }
397 }
398
399 AccumulateComplexity(complexity);
400}
401
402void DisplayListMetalComplexityCalculator::MetalHelper::drawPoints(
404 uint32_t count,
405 const SkPoint points[]) {
406 if (IsComplex()) {
407 return;
408 }
409 unsigned int complexity;
410
411 // If AA is off then they all behave similarly, and scale
412 // linearly with the point count.
413 if (!IsAntiAliased()) {
414 // m = 1/16000
415 // c = 0.75
416 complexity = (count + 12000) * 25 / 2;
417 } else {
419 // m = 1/1250
420 // c = 1
421 complexity = (count + 1250) * 160;
422 } else {
423 if (IsHairline() && mode == DlCanvas::PointMode::kPoints) {
424 // This is a special case, it triggers an extremely fast path.
425 // m = 1/14500
426 // c = 0
427 complexity = count * 400 / 29;
428 } else {
429 // m = 1/2200
430 // c = 0.75
431 complexity = (count + 1650) * 1000 / 11;
432 }
433 }
434 }
435 AccumulateComplexity(complexity);
436}
437
438void DisplayListMetalComplexityCalculator::MetalHelper::drawVertices(
439 const DlVertices* vertices,
441 // There is currently no way for us to get the VertexMode from the SkVertices
442 // object, but for future reference:
443 //
444 // TriangleStrip is roughly 25% more expensive than TriangleFan.
445 // TriangleFan is roughly 5% more expensive than Triangles.
446
447 // For the baseline, it's hard to identify the trend. It might be O(n^1/2).
448 // For now, treat it as linear as an approximation.
449 //
450 // m = 1/4000
451 // c = 1
452 unsigned int complexity = (vertices->vertex_count() + 4000) * 50;
453
454 AccumulateComplexity(complexity);
455}
456
457void DisplayListMetalComplexityCalculator::MetalHelper::drawImage(
458 const sk_sp<DlImage> image,
459 const SkPoint point,
460 DlImageSampling sampling,
461 bool render_with_attributes) {
462 if (IsComplex()) {
463 return;
464 }
465 // AA vs non-AA has a cost but it's dwarfed by the overall cost of the
466 // drawImage call.
467 //
468 // The main difference is if the image is backed by a texture already or not
469 // If we don't need to upload, then the cost scales linearly with the
470 // area of the image. If it needs uploading, the cost scales linearly
471 // with the square of the area (!!!).
472 SkISize dimensions = image->dimensions();
473 unsigned int area = dimensions.width() * dimensions.height();
474
475 // m = 1/17000
476 // c = 3
477 unsigned int complexity = (area + 51000) * 4 / 170;
478
479 if (!image->isTextureBacked()) {
480 // We can't square the area here as we'll overflow, so let's approximate
481 // by taking the calculated complexity score and applying a multiplier to
482 // it.
483 //
484 // (complexity * area / 35000) + 1200 gives a reasonable approximation.
485 float multiplier = area / 35000.0f;
486 complexity = complexity * multiplier + 1200;
487 }
488
489 AccumulateComplexity(complexity);
490}
491
492void DisplayListMetalComplexityCalculator::MetalHelper::ImageRect(
493 const SkISize& size,
494 bool texture_backed,
495 bool render_with_attributes,
496 bool enforce_src_edges) {
497 if (IsComplex()) {
498 return;
499 }
500 // Two main groups here - texture-backed and non-texture-backed images.
501 //
502 // Within each group, they all perform within a few % of each other *except*
503 // when we have a strict constraint and anti-aliasing enabled.
504 unsigned int area = size.width() * size.height();
505
506 // These values were worked out by creating a straight line graph (y=mx+c)
507 // approximately matching the measured data, normalising the data so that
508 // 0.0005ms resulted in a score of 100 then simplifying down the formula.
509 unsigned int complexity;
510 if (texture_backed) {
511 // Baseline for texture-backed SkImages.
512 // m = 1/23000
513 // c = 2.3
514 complexity = (area + 52900) * 2 / 115;
515 if (render_with_attributes && enforce_src_edges && IsAntiAliased()) {
516 // There's about a 30% performance penalty from the baseline.
517 complexity *= 1.3f;
518 }
519 } else {
520 if (render_with_attributes && enforce_src_edges && IsAntiAliased()) {
521 // m = 1/12200
522 // c = 2.75
523 complexity = (area + 33550) * 2 / 61;
524 } else {
525 // m = 1/14500
526 // c = 2.5
527 complexity = (area + 36250) * 4 / 145;
528 }
529 }
530
531 AccumulateComplexity(complexity);
532}
533
534void DisplayListMetalComplexityCalculator::MetalHelper::drawImageNine(
535 const sk_sp<DlImage> image,
536 const SkIRect& center,
537 const SkRect& dst,
538 DlFilterMode filter,
539 bool render_with_attributes) {
540 if (IsComplex()) {
541 return;
542 }
543 // Whether uploading or not, the performance is comparable across all
544 // variations.
545 SkISize dimensions = image->dimensions();
546 unsigned int area = dimensions.width() * dimensions.height();
547
548 // m = 1/8000
549 // c = 3
550 unsigned int complexity = (area + 24000) / 20;
551 AccumulateComplexity(complexity);
552}
553
554void DisplayListMetalComplexityCalculator::MetalHelper::drawDisplayList(
555 const sk_sp<DisplayList> display_list,
556 SkScalar opacity) {
557 if (IsComplex()) {
558 return;
559 }
560 MetalHelper helper(Ceiling() - CurrentComplexityScore());
561 if (opacity < SK_Scalar1 && !display_list->can_apply_group_opacity()) {
562 auto bounds = display_list->bounds();
563 helper.saveLayer(bounds, SaveLayerOptions::kWithAttributes, nullptr);
564 }
565 display_list->Dispatch(helper);
566 AccumulateComplexity(helper.ComplexityScore());
567}
568
569void DisplayListMetalComplexityCalculator::MetalHelper::drawTextBlob(
570 const sk_sp<SkTextBlob> blob,
571 SkScalar x,
572 SkScalar y) {
573 if (IsComplex()) {
574 return;
575 }
576
577 // DrawTextBlob has a high fixed cost, but if we call it multiple times
578 // per frame, that fixed cost is greatly reduced per subsequent call. This
579 // is likely because there is batching being done in SkCanvas.
580
581 // Increment draw_text_blob_count_ and calculate the cost at the end.
582 draw_text_blob_count_++;
583}
584
585void DisplayListMetalComplexityCalculator::MetalHelper::drawTextFrame(
586 const std::shared_ptr<impeller::TextFrame>& text_frame,
587 SkScalar x,
588 SkScalar y) {}
589
590void DisplayListMetalComplexityCalculator::MetalHelper::drawShadow(
591 const SkPath& path,
592 const DlColor color,
593 const SkScalar elevation,
594 bool transparent_occluder,
595 SkScalar dpr) {
596 if (IsComplex()) {
597 return;
598 }
599
600 // Elevation has no significant effect on the timings. Whether the shadow
601 // is cast by a transparent occluder or not has a small impact of around 5%.
602 //
603 // The path verbs do have an effect but only if the verb type is cubic; line,
604 // quad and conic all perform similarly.
605 float occluder_penalty = 1.0f;
606 if (transparent_occluder) {
607 occluder_penalty = 1.05f;
608 }
609
610 // The benchmark uses a test path of around 10 path elements. This is likely
611 // to be similar to what we see in real world usage, but we should benchmark
612 // different path lengths to see how much impact there is from varying the
613 // path length.
614 //
615 // For now, we will assume that there is no fixed overhead and that the time
616 // spent rendering the shadow for a path is split evenly amongst all the verbs
617 // enumerated.
618 unsigned int line_verb_cost = 20000; // 0.1ms
619 unsigned int quad_verb_cost = 20000; // 0.1ms
620 unsigned int conic_verb_cost = 20000; // 0.1ms
621 unsigned int cubic_verb_cost = 80000; // 0.4ms
622
623 unsigned int complexity =
624 0 + CalculatePathComplexity(path, line_verb_cost, quad_verb_cost,
625 conic_verb_cost, cubic_verb_cost);
626
627 AccumulateComplexity(complexity * occluder_penalty);
628}
629
630} // namespace flutter
const char * options
int count
static const int points[]
SkColor4f color
static SkScalar center(float pos0, float pos1)
SkISize dimensions() const
Definition SkImage.h:297
virtual bool isTextureBacked() const =0
Type getType() const
Definition SkRRect.h:76
@ kSimple_Type
non-zero width and height with equal radii
Definition SkRRect.h:70
@ kComplex_Type
non-zero width and height with arbitrary radii
Definition SkRRect.h:72
SkScalar width() const
Definition SkRRect.h:95
SkScalar height() const
Definition SkRRect.h:102
static DisplayListMetalComplexityCalculator * GetInstance()
@ kPolygon
draw each pair of overlapping points as a line segment
@ kPoints
draw each point separately
static const SaveLayerOptions kWithAttributes
sk_sp< SkImage > image
Definition examples.cpp:29
float SkScalar
Definition extension.cpp:12
size_t length
double y
double x
Optional< SkRect > bounds
Definition SkRecords.h:189
SkRRect rrect
Definition SkRecords.h:232
sk_sp< SkBlender > blender SkRect rect
Definition SkRecords.h:350
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
@ kStroke
strokes boundary of shapes
@ kFill
fills interior of shapes
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition switches.h:228
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition switches.h:259
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition SkVx.h:707
constexpr int32_t width() const
Definition SkSize.h:36
constexpr int32_t height() const
Definition SkSize.h:37
constexpr float y() const
constexpr float x() const
constexpr float height() const
Definition SkRect.h:769
constexpr float width() const
Definition SkRect.h:762