Flutter Engine
The Flutter Engine
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,
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::drawDashedLine(
115 const DlPoint& p0,
116 const DlPoint& p1,
117 DlScalar on_length,
118 DlScalar off_length) {
119 // Dashing is slightly more complex than a regular drawLine, but this
120 // op is so rare it is not worth measuring the difference.
121 drawLine(ToSkPoint(p0), ToSkPoint(p1));
122}
123
124void DisplayListMetalComplexityCalculator::MetalHelper::drawRect(
125 const SkRect& rect) {
126 if (IsComplex()) {
127 return;
128 }
129
130 unsigned int complexity;
131
132 // If stroked, cost scales linearly with the rectangle width/height.
133 // If filled, it scales with the area.
134 //
135 // Hairline stroke vs non hairline has no real penalty at smaller lengths,
136 // but it increases at larger lengths. There isn't enough data to get a good
137 // idea of the penalty at lengths > 1000px.
138 //
139 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
140 // currently use it anywhere in Flutter.
141 if (DrawStyle() == DlDrawStyle::kFill) {
142 // No real difference for AA with filled styles.
143 unsigned int area = rect.width() * rect.height();
144
145 // m = 1/9000
146 // c = 0
147 complexity = area / 225;
148 } else {
149 // Take the average of the width and height.
150 unsigned int length = (rect.width() + rect.height()) / 2;
151
152 // There is a penalty for AA being *disabled*.
153 if (IsAntiAliased()) {
154 // m = 1/65
155 // c = 0
156 complexity = length * 8 / 13;
157 } else {
158 // m = 1/35
159 // c = 0
160 complexity = length * 8 / 7;
161 }
162 }
163
164 AccumulateComplexity(complexity);
165}
166
167void DisplayListMetalComplexityCalculator::MetalHelper::drawOval(
168 const SkRect& bounds) {
169 if (IsComplex()) {
170 return;
171 }
172 // DrawOval scales very roughly linearly with the bounding box width/height
173 // (not area) for stroked styles without AA.
174 //
175 // Filled styles and stroked styles with AA scale linearly with the bounding
176 // box area.
177 unsigned int area = bounds.width() * bounds.height();
178
179 unsigned int complexity;
180
181 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
182 // currently use it anywhere in Flutter.
183 if (DrawStyle() == DlDrawStyle::kFill) {
184 // With filled styles, there is no significant AA penalty.
185 // m = 1/16000
186 // c = 0
187 complexity = area / 80;
188 } else {
189 if (IsAntiAliased()) {
190 // m = 1/7500
191 // c = 0
192 complexity = area * 2 / 75;
193 } else {
194 // Take the average of the width and height.
195 unsigned int length = (bounds.width() + bounds.height()) / 2;
196
197 // m = 1/80
198 // c = 0
199 complexity = length * 5 / 2;
200 }
201 }
202
203 AccumulateComplexity(complexity);
204}
205
206void DisplayListMetalComplexityCalculator::MetalHelper::drawCircle(
207 const SkPoint& center,
208 SkScalar radius) {
209 if (IsComplex()) {
210 return;
211 }
212
213 unsigned int complexity;
214
215 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
216 // currently use it anywhere in Flutter.
217 if (DrawStyle() == DlDrawStyle::kFill) {
218 // We can ignore pi here.
219 unsigned int area = radius * radius;
220 // m = 1/1300
221 // c = 5
222 complexity = (area + 6500) * 2 / 65;
223
224 // Penalty of around 5% when AA is disabled.
225 if (!IsAntiAliased()) {
226 complexity *= 1.05f;
227 }
228 } else {
229 // Hairline vs non-hairline has no significant performance difference.
230 if (IsAntiAliased()) {
231 // m = 1/7
232 // c = 7
233 complexity = (radius + 49) * 40 / 7;
234 } else {
235 // m = 1/16
236 // c = 8
237 complexity = (radius + 128) * 5 / 2;
238 }
239 }
240
241 AccumulateComplexity(complexity);
242}
243
244void DisplayListMetalComplexityCalculator::MetalHelper::drawRRect(
245 const SkRRect& rrect) {
246 if (IsComplex()) {
247 return;
248 }
249 // RRects scale linearly with the area of the bounding rect.
250 unsigned int area = rrect.width() * rrect.height();
251
252 // Drawing RRects is split into two performance tiers; an expensive
253 // one and a less expensive one. Both scale linearly with area.
254 //
255 // Expensive: All filled style, symmetric w/AA.
256 bool expensive =
257 (DrawStyle() == DlDrawStyle::kFill) ||
258 ((rrect.getType() == SkRRect::Type::kSimple_Type) && IsAntiAliased());
259
260 unsigned int complexity;
261
262 // These values were worked out by creating a straight line graph (y=mx+c)
263 // approximately matching the measured data, normalising the data so that
264 // 0.0005ms resulted in a score of 100 then simplifying down the formula.
265 if (expensive) {
266 // m = 1/25000
267 // c = 2
268 // An area of 7000px^2 ~= baseline timing of 0.0005ms.
269 complexity = (area + 10500) / 175;
270 } else {
271 // m = 1/7000
272 // c = 1.5
273 // An area of 16000px^2 ~= baseline timing of 0.0005ms.
274 complexity = (area + 50000) / 625;
275 }
276
277 AccumulateComplexity(complexity);
278}
279
280void DisplayListMetalComplexityCalculator::MetalHelper::drawDRRect(
281 const SkRRect& outer,
282 const SkRRect& inner) {
283 if (IsComplex()) {
284 return;
285 }
286 // There are roughly four classes here:
287 // a) Filled style with AA enabled.
288 // b) Filled style with AA disabled.
289 // c) Complex RRect type with AA enabled and filled style.
290 // d) Everything else.
291 //
292 // a) and c) scale linearly with the area, b) and d) scale linearly with
293 // a single dimension (length). In all cases, the dimensions refer to
294 // the outer RRect.
295 unsigned int length = (outer.width() + outer.height()) / 2;
296
297 unsigned int complexity;
298
299 // These values were worked out by creating a straight line graph (y=mx+c)
300 // approximately matching the measured data, normalising the data so that
301 // 0.0005ms resulted in a score of 100 then simplifying down the formula.
302 //
303 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
304 // currently use it anywhere in Flutter.
305 if (DrawStyle() == DlDrawStyle::kFill) {
306 unsigned int area = outer.width() * outer.height();
307 if (outer.getType() == SkRRect::Type::kComplex_Type) {
308 // m = 1/1000
309 // c = 1
310 complexity = (area + 1000) / 10;
311 } else {
312 if (IsAntiAliased()) {
313 // m = 1/3500
314 // c = 1.5
315 complexity = (area + 5250) / 35;
316 } else {
317 // m = 1/30
318 // c = 1
319 complexity = (300 + (10 * length)) / 3;
320 }
321 }
322 } else {
323 // m = 1/60
324 // c = 1.75
325 complexity = ((10 * length) + 1050) / 6;
326 }
327
328 AccumulateComplexity(complexity);
329}
330
332 const SkPath& path) {
333 if (IsComplex()) {
334 return;
335 }
336 // There is negligible effect on the performance for hairline vs. non-hairline
337 // stroke widths.
338 //
339 // The data for filled styles is currently suspicious, so for now we are going
340 // to assign scores based on stroked styles.
341
342 unsigned int line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost;
343
344 if (IsAntiAliased()) {
345 line_verb_cost = 75;
346 quad_verb_cost = 100;
347 conic_verb_cost = 160;
348 cubic_verb_cost = 210;
349 } else {
350 line_verb_cost = 67;
351 quad_verb_cost = 80;
352 conic_verb_cost = 140;
353 cubic_verb_cost = 210;
354 }
355
356 // There seems to be a fixed cost of around 1ms for calling drawPath.
357 unsigned int complexity =
358 200000 + CalculatePathComplexity(path, line_verb_cost, quad_verb_cost,
359 conic_verb_cost, cubic_verb_cost);
360
361 AccumulateComplexity(complexity);
362}
363
364void DisplayListMetalComplexityCalculator::MetalHelper::drawArc(
365 const SkRect& oval_bounds,
366 SkScalar start_degrees,
367 SkScalar sweep_degrees,
368 bool use_center) {
369 if (IsComplex()) {
370 return;
371 }
372 // Hairline vs non-hairline makes no difference to the performance.
373 // Stroked styles without AA scale linearly with the diameter.
374 // Stroked styles with AA scale linearly with the area except for small
375 // values. Filled styles scale linearly with the area.
376 unsigned int diameter = (oval_bounds.width() + oval_bounds.height()) / 2;
377 unsigned int area = oval_bounds.width() * oval_bounds.height();
378
379 unsigned int complexity;
380
381 // These values were worked out by creating a straight line graph (y=mx+c)
382 // approximately matching the measured data, normalising the data so that
383 // 0.0005ms resulted in a score of 100 then simplifying down the formula.
384 //
385 // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
386 // currently use it anywhere in Flutter.
387 if (DrawStyle() == DlDrawStyle::kStroke) {
388 if (IsAntiAliased()) {
389 // m = 1/8500
390 // c = 16
391 complexity = (area + 136000) * 2 / 765;
392 } else {
393 // m = 1/60
394 // c = 3
395 complexity = (diameter + 180) * 10 / 27;
396 }
397 } else {
398 if (IsAntiAliased()) {
399 // m = 1/20000
400 // c = 20
401 complexity = (area + 400000) / 900;
402 } else {
403 // m = 1/2100
404 // c = 8
405 complexity = (area + 16800) * 2 / 189;
406 }
407 }
408
409 AccumulateComplexity(complexity);
410}
411
412void DisplayListMetalComplexityCalculator::MetalHelper::drawPoints(
414 uint32_t count,
415 const SkPoint points[]) {
416 if (IsComplex()) {
417 return;
418 }
419 unsigned int complexity;
420
421 // If AA is off then they all behave similarly, and scale
422 // linearly with the point count.
423 if (!IsAntiAliased()) {
424 // m = 1/16000
425 // c = 0.75
426 complexity = (count + 12000) * 25 / 2;
427 } else {
429 // m = 1/1250
430 // c = 1
431 complexity = (count + 1250) * 160;
432 } else {
433 if (IsHairline() && mode == DlCanvas::PointMode::kPoints) {
434 // This is a special case, it triggers an extremely fast path.
435 // m = 1/14500
436 // c = 0
437 complexity = count * 400 / 29;
438 } else {
439 // m = 1/2200
440 // c = 0.75
441 complexity = (count + 1650) * 1000 / 11;
442 }
443 }
444 }
445 AccumulateComplexity(complexity);
446}
447
448void DisplayListMetalComplexityCalculator::MetalHelper::drawVertices(
449 const DlVertices* vertices,
451 // There is currently no way for us to get the VertexMode from the SkVertices
452 // object, but for future reference:
453 //
454 // TriangleStrip is roughly 25% more expensive than TriangleFan.
455 // TriangleFan is roughly 5% more expensive than Triangles.
456
457 // For the baseline, it's hard to identify the trend. It might be O(n^1/2).
458 // For now, treat it as linear as an approximation.
459 //
460 // m = 1/4000
461 // c = 1
462 unsigned int complexity = (vertices->vertex_count() + 4000) * 50;
463
464 AccumulateComplexity(complexity);
465}
466
467void DisplayListMetalComplexityCalculator::MetalHelper::drawImage(
468 const sk_sp<DlImage> image,
469 const SkPoint point,
471 bool render_with_attributes) {
472 if (IsComplex()) {
473 return;
474 }
475 // AA vs non-AA has a cost but it's dwarfed by the overall cost of the
476 // drawImage call.
477 //
478 // The main difference is if the image is backed by a texture already or not
479 // If we don't need to upload, then the cost scales linearly with the
480 // area of the image. If it needs uploading, the cost scales linearly
481 // with the square of the area (!!!).
482 SkISize dimensions = image->dimensions();
483 unsigned int area = dimensions.width() * dimensions.height();
484
485 // m = 1/17000
486 // c = 3
487 unsigned int complexity = (area + 51000) * 4 / 170;
488
489 if (!image->isTextureBacked()) {
490 // We can't square the area here as we'll overflow, so let's approximate
491 // by taking the calculated complexity score and applying a multiplier to
492 // it.
493 //
494 // (complexity * area / 35000) + 1200 gives a reasonable approximation.
495 float multiplier = area / 35000.0f;
496 complexity = complexity * multiplier + 1200;
497 }
498
499 AccumulateComplexity(complexity);
500}
501
502void DisplayListMetalComplexityCalculator::MetalHelper::ImageRect(
503 const SkISize& size,
504 bool texture_backed,
505 bool render_with_attributes,
506 bool enforce_src_edges) {
507 if (IsComplex()) {
508 return;
509 }
510 // Two main groups here - texture-backed and non-texture-backed images.
511 //
512 // Within each group, they all perform within a few % of each other *except*
513 // when we have a strict constraint and anti-aliasing enabled.
514 unsigned int area = size.width() * size.height();
515
516 // These values were worked out by creating a straight line graph (y=mx+c)
517 // approximately matching the measured data, normalising the data so that
518 // 0.0005ms resulted in a score of 100 then simplifying down the formula.
519 unsigned int complexity;
520 if (texture_backed) {
521 // Baseline for texture-backed SkImages.
522 // m = 1/23000
523 // c = 2.3
524 complexity = (area + 52900) * 2 / 115;
525 if (render_with_attributes && enforce_src_edges && IsAntiAliased()) {
526 // There's about a 30% performance penalty from the baseline.
527 complexity *= 1.3f;
528 }
529 } else {
530 if (render_with_attributes && enforce_src_edges && IsAntiAliased()) {
531 // m = 1/12200
532 // c = 2.75
533 complexity = (area + 33550) * 2 / 61;
534 } else {
535 // m = 1/14500
536 // c = 2.5
537 complexity = (area + 36250) * 4 / 145;
538 }
539 }
540
541 AccumulateComplexity(complexity);
542}
543
544void DisplayListMetalComplexityCalculator::MetalHelper::drawImageNine(
545 const sk_sp<DlImage> image,
546 const SkIRect& center,
547 const SkRect& dst,
548 DlFilterMode filter,
549 bool render_with_attributes) {
550 if (IsComplex()) {
551 return;
552 }
553 // Whether uploading or not, the performance is comparable across all
554 // variations.
555 SkISize dimensions = image->dimensions();
556 unsigned int area = dimensions.width() * dimensions.height();
557
558 // m = 1/8000
559 // c = 3
560 unsigned int complexity = (area + 24000) / 20;
561 AccumulateComplexity(complexity);
562}
563
564void DisplayListMetalComplexityCalculator::MetalHelper::drawDisplayList(
565 const sk_sp<DisplayList> display_list,
566 SkScalar opacity) {
567 if (IsComplex()) {
568 return;
569 }
570 MetalHelper helper(Ceiling() - CurrentComplexityScore());
571 if (opacity < SK_Scalar1 && !display_list->can_apply_group_opacity()) {
572 auto bounds = display_list->bounds();
573 helper.saveLayer(bounds, SaveLayerOptions::kWithAttributes, nullptr);
574 }
575 display_list->Dispatch(helper);
576 AccumulateComplexity(helper.ComplexityScore());
577}
578
580 const sk_sp<SkTextBlob> blob,
581 SkScalar x,
582 SkScalar y) {
583 if (IsComplex()) {
584 return;
585 }
586
587 // DrawTextBlob has a high fixed cost, but if we call it multiple times
588 // per frame, that fixed cost is greatly reduced per subsequent call. This
589 // is likely because there is batching being done in SkCanvas.
590
591 // Increment draw_text_blob_count_ and calculate the cost at the end.
592 draw_text_blob_count_++;
593}
594
595void DisplayListMetalComplexityCalculator::MetalHelper::drawTextFrame(
596 const std::shared_ptr<impeller::TextFrame>& text_frame,
597 SkScalar x,
598 SkScalar y) {}
599
600void DisplayListMetalComplexityCalculator::MetalHelper::drawShadow(
601 const SkPath& path,
602 const DlColor color,
603 const SkScalar elevation,
604 bool transparent_occluder,
605 SkScalar dpr) {
606 if (IsComplex()) {
607 return;
608 }
609
610 // Elevation has no significant effect on the timings. Whether the shadow
611 // is cast by a transparent occluder or not has a small impact of around 5%.
612 //
613 // The path verbs do have an effect but only if the verb type is cubic; line,
614 // quad and conic all perform similarly.
615 float occluder_penalty = 1.0f;
616 if (transparent_occluder) {
617 occluder_penalty = 1.05f;
618 }
619
620 // The benchmark uses a test path of around 10 path elements. This is likely
621 // to be similar to what we see in real world usage, but we should benchmark
622 // different path lengths to see how much impact there is from varying the
623 // path length.
624 //
625 // For now, we will assume that there is no fixed overhead and that the time
626 // spent rendering the shadow for a path is split evenly amongst all the verbs
627 // enumerated.
628 unsigned int line_verb_cost = 20000; // 0.1ms
629 unsigned int quad_verb_cost = 20000; // 0.1ms
630 unsigned int conic_verb_cost = 20000; // 0.1ms
631 unsigned int cubic_verb_cost = 80000; // 0.4ms
632
633 unsigned int complexity =
634 0 + CalculatePathComplexity(path, line_verb_cost, quad_verb_cost,
635 conic_verb_cost, cubic_verb_cost);
636
637 AccumulateComplexity(complexity * occluder_penalty);
638}
639
640} // namespace flutter
const char * options
int count
Definition: FontMgrTest.cpp:50
static const int points[]
SkISize dimensions() const
Definition: SkImage.h:297
virtual bool isTextureBacked() const =0
Definition: SkPath.h:59
Type getType() const
Definition: SkRRect.h:76
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
Definition: display_list.h:154
DlColor color
float SkScalar
Definition: extension.cpp:12
size_t length
static void drawPath(SkPath &path, SkCanvas *canvas, SkColor color, const SkRect &clip, SkPaint::Cap cap, SkPaint::Join join, SkPaint::Style style, SkPathFillType fill, SkScalar strokeWidth)
Definition: linepaths.cpp:22
double y
double x
drawTextBlob(r.blob.get(), r.x, r.y, r.paint)) DRAW(DrawSlug
Optional< SkRect > bounds
Definition: SkRecords.h:189
sk_sp< const SkImage > image
Definition: SkRecords.h:269
sk_sp< const SkImageFilter > backdrop
Definition: SkRecords.h:191
SkRRect rrect
Definition: SkRecords.h:232
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
SkSamplingOptions sampling
Definition: SkRecords.h:337
impeller::Scalar DlScalar
const SkPoint & ToSkPoint(const DlPoint &point)
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
impeller::Point DlPoint
dst
Definition: cp.py:12
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition: SkVx.h:707
flutter::DlColor DlColor
flutter::SaveLayerOptions SaveLayerOptions
Definition: SkRect.h:32
Definition: SkSize.h:16
constexpr int32_t width() const
Definition: SkSize.h:36
constexpr int32_t height() const
Definition: SkSize.h:37
constexpr float y() const
Definition: SkPoint_impl.h:187
constexpr float x() const
Definition: SkPoint_impl.h:181
constexpr float height() const
Definition: SkRect.h:769
constexpr float width() const
Definition: SkRect.h:762