diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 12acb82b16796..cdc62d2a3708d 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -57,12 +57,20 @@ FILE: ../../../flutter/display_list/display_list_color_filter.h FILE: ../../../flutter/display_list/display_list_color_filter_unittests.cc FILE: ../../../flutter/display_list/display_list_complexity.cc FILE: ../../../flutter/display_list/display_list_complexity.h +FILE: ../../../flutter/display_list/display_list_complexity_gl.cc +FILE: ../../../flutter/display_list/display_list_complexity_gl.h +FILE: ../../../flutter/display_list/display_list_complexity_helper.h +FILE: ../../../flutter/display_list/display_list_complexity_metal.cc +FILE: ../../../flutter/display_list/display_list_complexity_metal.h +FILE: ../../../flutter/display_list/display_list_complexity_unittests.cc FILE: ../../../flutter/display_list/display_list_dispatcher.cc FILE: ../../../flutter/display_list/display_list_dispatcher.h FILE: ../../../flutter/display_list/display_list_flags.cc FILE: ../../../flutter/display_list/display_list_flags.h FILE: ../../../flutter/display_list/display_list_ops.cc FILE: ../../../flutter/display_list/display_list_ops.h +FILE: ../../../flutter/display_list/display_list_test_utils.cc +FILE: ../../../flutter/display_list/display_list_test_utils.h FILE: ../../../flutter/display_list/display_list_unittests.cc FILE: ../../../flutter/display_list/display_list_utils.cc FILE: ../../../flutter/display_list/display_list_utils.h diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index 76dc9b591fcce..5f166c9ffd9c5 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -18,6 +18,10 @@ source_set("display_list") { "display_list_color_filter.h", "display_list_complexity.cc", "display_list_complexity.h", + "display_list_complexity_gl.cc", + "display_list_complexity_gl.h", + "display_list_complexity_metal.cc", + "display_list_complexity_metal.h", "display_list_dispatcher.cc", "display_list_dispatcher.h", "display_list_flags.cc", @@ -43,6 +47,9 @@ source_set("unittests") { sources = [ "display_list_canvas_unittests.cc", "display_list_color_filter_unittests.cc", + "display_list_complexity_unittests.cc", + "display_list_test_utils.cc", + "display_list_test_utils.h", "display_list_unittests.cc", ] diff --git a/display_list/display_list_complexity.cc b/display_list/display_list_complexity.cc index 53cef3cea29a9..1823088816b1c 100644 --- a/display_list/display_list_complexity.cc +++ b/display_list/display_list_complexity.cc @@ -4,6 +4,8 @@ #include "flutter/display_list/display_list_complexity.h" #include "flutter/display_list/display_list.h" +#include "flutter/display_list/display_list_complexity_gl.h" +#include "flutter/display_list/display_list_complexity_metal.h" namespace flutter { @@ -21,6 +23,10 @@ DisplayListNaiveComplexityCalculator::GetInstance() { DisplayListComplexityCalculator* DisplayListComplexityCalculator::GetForBackend( GrBackendApi backend) { switch (backend) { + case GrBackendApi::kMetal: + return DisplayListMetalComplexityCalculator::GetInstance(); + case GrBackendApi::kOpenGL: + return DisplayListGLComplexityCalculator::GetInstance(); default: return DisplayListNaiveComplexityCalculator::GetInstance(); } diff --git a/display_list/display_list_complexity.h b/display_list/display_list_complexity.h index ebb1832a84a0d..f6a838865ee4c 100644 --- a/display_list/display_list_complexity.h +++ b/display_list/display_list_complexity.h @@ -19,11 +19,18 @@ class DisplayListComplexityCalculator { virtual ~DisplayListComplexityCalculator() = default; // Returns a calculated complexity score for a given DisplayList object - virtual unsigned int compute(DisplayList* display_list) = 0; + virtual unsigned int Compute(DisplayList* display_list) = 0; // Returns whether a given complexity score meets the threshold for // cacheability for this particular ComplexityCalculator - virtual bool should_be_cached(unsigned int complexity_score) = 0; + virtual bool ShouldBeCached(unsigned int complexity_score) = 0; + + // Sets a ceiling for the complexity score being calculated. By default + // this is the largest number representable by an unsigned int. + // + // This setting has no effect on non-accumulator based scorers such as + // the Naive calculator. + virtual void SetComplexityCeiling(unsigned int ceiling) = 0; }; class DisplayListNaiveComplexityCalculator @@ -31,14 +38,16 @@ class DisplayListNaiveComplexityCalculator public: static DisplayListComplexityCalculator* GetInstance(); - unsigned int compute(DisplayList* display_list) override { + unsigned int Compute(DisplayList* display_list) override { return display_list->op_count(true); } - bool should_be_cached(unsigned int complexity_score) override { + bool ShouldBeCached(unsigned int complexity_score) override { return complexity_score > 5u; } + void SetComplexityCeiling(unsigned int ceiling) override {} + private: DisplayListNaiveComplexityCalculator() {} static DisplayListNaiveComplexityCalculator* instance_; diff --git a/display_list/display_list_complexity_gl.cc b/display_list/display_list_complexity_gl.cc new file mode 100644 index 0000000000000..c08a038f1cc52 --- /dev/null +++ b/display_list/display_list_complexity_gl.cc @@ -0,0 +1,682 @@ +// Copyright 2013 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. + +#include "flutter/display_list/display_list_complexity_gl.h" + +// The numbers and weightings used in this file stem from taking the +// data from the DisplayListBenchmarks suite run on an Pixel 4 and +// applying very rough analysis on them to identify the approximate +// trends. +// +// See the comments in display_list_complexity_helper.h for details on the +// process and rationale behind coming up with these numbers. + +namespace flutter { + +DisplayListGLComplexityCalculator* + DisplayListGLComplexityCalculator::instance_ = nullptr; + +DisplayListGLComplexityCalculator* +DisplayListGLComplexityCalculator::GetInstance() { + if (instance_ == nullptr) { + instance_ = new DisplayListGLComplexityCalculator(); + } + return instance_; +} + +unsigned int +DisplayListGLComplexityCalculator::GLHelper::SaveLayerComplexity() { + // Calculate the impact of saveLayer. + unsigned int save_layer_complexity; + if (SaveLayerCount() == 0) { + save_layer_complexity = 0; + } else { + // m = 1/5 + // c = 10 + save_layer_complexity = (SaveLayerCount() + 50) * 40000; + } + + return save_layer_complexity; +} + +void DisplayListGLComplexityCalculator::GLHelper::drawLine(const SkPoint& p0, + const SkPoint& p1) { + if (IsComplex()) { + return; + } + + // There is a relatively high fixed overhead cost for drawLine on OpenGL. + // Further, there is a strange bump where the cost of drawing a line of + // length ~500px is actually more costly than drawing a line of length + // ~1000px. The calculations here will be for a linear graph that + // approximate the overall trend. + + float non_hairline_penalty = 1.0f; + unsigned int aa_penalty = 1; + + // The non-hairline penalty is insignificant when AA is on. + if (!IsHairline() && !IsAntiAliased()) { + non_hairline_penalty = 1.15f; + } + if (IsAntiAliased()) { + aa_penalty = 2; + } + + // Use an approximation for the distance to avoid floating point or + // sqrt() calls. + SkScalar distance = abs(p0.x() - p1.x()) + abs(p0.y() - p1.y()); + + // The baseline complexity is for a hairline stroke with no AA. + // m = 1/40 + // c = 13 + unsigned int complexity = + ((distance + 520) / 2) * non_hairline_penalty * aa_penalty; + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawRect(const SkRect& rect) { + if (IsComplex()) { + return; + } + + unsigned int complexity; + + // If stroked, cost scales linearly with the rectangle width/height. + // If filled, it scales with the area. + // + // Hairline stroke vs non hairline has no significant penalty. + // + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kFill_Style) { + // No real difference for AA with filled styles + unsigned int area = rect.width() * rect.height(); + + // m = 1/3500 + // c = 0 + complexity = area * 2 / 175; + } else { + // Take the average of the width and height. + unsigned int length = (rect.width() + rect.height()) / 2; + + if (IsAntiAliased()) { + // m = 1/30 + // c = 0 + complexity = length * 4 / 3; + } else { + // If AA is disabled, the data shows that at larger sizes the overall + // cost comes down, peaking at around 1000px. As we don't anticipate + // rasterising rects with AA disabled to be all that frequent, just treat + // it as a straight line that peaks at 1000px, beyond which it stays + // constant. The rationale here is that it makes more sense to + // overestimate than to start decreasing the cost as the length goes up. + // + // This should be a reasonable approximation as it doesn't decrease by + // much from 1000px to 2000px. + // + // m = 1/20 + // c = 0 + complexity = std::min(length, 1000u) * 2; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawOval( + const SkRect& bounds) { + if (IsComplex()) { + return; + } + // DrawOval scales very roughly linearly with the bounding box width/height + // (not area) for stroked styles without AA. + // + // Filled styles and stroked styles with AA scale linearly with the bounding + // box area. + unsigned int area = bounds.width() * bounds.height(); + + unsigned int complexity; + + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kFill_Style) { + // With filled styles, there is no significant AA penalty. + // m = 1/6000 + // c = 0 + complexity = area / 30; + } else { + if (IsAntiAliased()) { + // m = 1/4000 + // c = 0 + complexity = area / 20; + } else { + // Take the average of the width and height. + unsigned int length = (bounds.width() + bounds.height()) / 2; + + // m = 1/75 + // c = 0 + complexity = length * 8 / 3; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawCircle( + const SkPoint& center, + SkScalar radius) { + if (IsComplex()) { + return; + } + + unsigned int complexity; + + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kFill_Style) { + // We can ignore pi here + unsigned int area = radius * radius; + // m = 1/525 + // c = 50 + complexity = (area + 26250) * 8 / 105; + + // Penalty of around 8% when AA is disabled. + if (!IsAntiAliased()) { + complexity *= 1.08f; + } + } else { + // Hairline vs non-hairline has no significant performance difference. + if (IsAntiAliased()) { + // m = 1/3 + // c = 10 + complexity = (radius + 30) * 40 / 3; + } else { + // m = 1/10 + // c = 20 + complexity = (radius + 200) * 4; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawRRect( + const SkRRect& rrect) { + if (IsComplex()) { + return; + } + + // Drawing RRects is split into three performance tiers: + // + // 1) All stroked styles without AA *except* simple/symmetric RRects. + // 2) All filled styles and symmetric stroked styles w/AA. + // 3) Remaining stroked styles with AA. + // + // 1) and 3) scale linearly with length, 2) scales with area. + + unsigned int complexity; + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + if (Style() == SkPaint::Style::kFill_Style || + ((rrect.getType() == SkRRect::Type::kSimple_Type) && IsAntiAliased())) { + unsigned int area = rrect.width() * rrect.height(); + // m = 1/3200 + // c = 0.5 + complexity = (area + 1600) / 80; + } else { + // Take the average of the width and height. + unsigned int length = (rrect.width() + rrect.height()) / 2; + + // There is some difference between hairline and non-hairline performance + // but the spread is relatively inconsistent and it's pretty much a wash. + if (IsAntiAliased()) { + // m = 1/25 + // c = 1 + complexity = (length + 25) * 8 / 5; + } else { + // m = 1/50 + // c = 0.75 + complexity = ((length * 2) + 75) * 2 / 5; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawDRRect( + const SkRRect& outer, + const SkRRect& inner) { + if (IsComplex()) { + return; + } + // There are roughly four classes here: + // a) Filled style. + // b) Complex RRect type with AA enabled and filled style. + // c) Stroked style with AA enabled. + // d) Stroked style with AA disabled. + // + // a) and b) scale linearly with the area, c) and d) scale linearly with + // a single dimension (length). In all cases, the dimensions refer to + // the outer RRect. + + unsigned int complexity; + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + // + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kFill_Style) { + unsigned int area = outer.width() * outer.height(); + if (outer.getType() == SkRRect::Type::kComplex_Type) { + // m = 1/500 + // c = 0.5 + complexity = (area + 250) / 5; + } else { + // m = 1/1600 + // c = 2 + complexity = (area + 3200) / 16; + } + } else { + unsigned int length = (outer.width() + outer.height()) / 2; + if (IsAntiAliased()) { + // m = 1/15 + // c = 1 + complexity = (length + 15) * 20 / 3; + } else { + // m = 1/27 + // c = 0.5 + complexity = ((length * 2) + 27) * 50 / 27; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawPath(const SkPath& path) { + if (IsComplex()) { + return; + } + // There is negligible effect on the performance for hairline vs. non-hairline + // stroke widths. + // + // The data for filled styles is currently suspicious, so for now we are going + // to assign scores based on stroked styles. + + unsigned int line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost; + unsigned int complexity; + + if (IsAntiAliased()) { + // There seems to be a fixed cost of around 1ms for calling drawPath with + // AA. + complexity = 200000; + + line_verb_cost = 235; + quad_verb_cost = 365; + conic_verb_cost = 365; + cubic_verb_cost = 725; + } else { + // There seems to be a fixed cost of around 0.25ms for calling drawPath. + // without AA + complexity = 50000; + + line_verb_cost = 135; + quad_verb_cost = 150; + conic_verb_cost = 200; + cubic_verb_cost = 235; + } + + complexity += CalculatePathComplexity(path, line_verb_cost, quad_verb_cost, + conic_verb_cost, cubic_verb_cost); + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawArc( + const SkRect& oval_bounds, + SkScalar start_degrees, + SkScalar sweep_degrees, + bool use_center) { + if (IsComplex()) { + return; + } + // Hairline vs non-hairline makes no difference to the performance. + // Stroked styles without AA scale linearly with the log of the diameter. + // Stroked styles with AA scale linearly with the area. + // Filled styles scale lienarly with the area. + unsigned int area = oval_bounds.width() * oval_bounds.height(); + unsigned int complexity; + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + // + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kStroke_Style) { + if (IsAntiAliased()) { + // m = 1/3800 + // c = 12 + complexity = (area + 45600) / 171; + } else { + unsigned int diameter = (oval_bounds.width() + oval_bounds.height()) / 2; + // m = 15 + // c = -100 + // This should never go negative though, so use std::max to ensure + // c is never larger than 15*log_diameter. + // + // Pre-multiply by 15 here so we get a little bit more precision. + unsigned int log_diameter = 15 * log(diameter); + complexity = (log_diameter - std::max(log_diameter, 100u)) * 200 / 9; + } + } else { + if (IsAntiAliased()) { + // m = 1/1000 + // c = 10 + complexity = (area + 10000) / 45; + } else { + // m = 1/6500 + // c = 12 + complexity = (area + 52000) * 2 / 585; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawPoints( + SkCanvas::PointMode mode, + uint32_t count, + const SkPoint points[]) { + if (IsComplex()) { + return; + } + unsigned int complexity; + + if (IsAntiAliased()) { + if (mode == SkCanvas::kPoints_PointMode) { + if (IsHairline()) { + // This is a special case, it triggers an extremely fast path. + // m = 1/4500 + // c = 0 + complexity = count * 400 / 9; + } else { + // m = 1/500 + // c = 0 + complexity = count * 400; + } + } else if (mode == SkCanvas::kLines_PointMode) { + if (IsHairline()) { + // m = 1/750 + // c = 0 + complexity = count * 800 / 3; + } else { + // m = 1/500 + // c = 0 + complexity = count * 400; + } + } else { + if (IsHairline()) { + // m = 1/350 + // c = 0 + complexity = count * 4000 / 7; + } else { + // m = 1/250 + // c = 0 + complexity = count * 800; + } + } + } else { + if (mode == SkCanvas::kPoints_PointMode) { + // Hairline vs non hairline makes no difference for points without AA. + // m = 1/18000 + // c = 0.25 + complexity = (count + 4500) * 100 / 9; + } else if (mode == SkCanvas::kLines_PointMode) { + if (IsHairline()) { + // m = 1/8500 + // c = 0.25 + complexity = (count + 2125) * 400 / 17; + } else { + // m = 1/9000 + // c = 0.25 + complexity = (count + 2250) * 200 / 9; + } + } else { + // Polygon only really diverges for hairline vs non hairline at large + // point counts, and only by a few %. + // m = 1/7500 + // c = 0.25 + complexity = (count + 1875) * 80 / 3; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawVertices( + const sk_sp vertices, + SkBlendMode mode) { + // There is currently no way for us to get the VertexMode from the SkVertices + // object, but for future reference: + // + // TriangleStrip is roughly 25% more expensive than TriangleFan. + // TriangleFan is roughly 5% more expensive than Triangles. + + // There is currently no way for us to get the vertex count from an SkVertices + // object, so we have to estimate it from the approximate size. + // + // Approximate size returns the sum of the sizes of the positions (SkPoint), + // texs (SkPoint), colors (SkColor) and indices (uint16_t) arrays multiplied + // by sizeof(type). As a very, very rough estimate, divide that by 20 to get + // an idea of the vertex count. + unsigned int approximate_vertex_count = vertices->approximateSize() / 20; + + // For the baseline, it's hard to identify the trend. It might be O(n^1/2) + // For now, treat it as linear as an approximation. + // + // m = 1/1600 + // c = 1 + unsigned int complexity = (approximate_vertex_count + 1600) * 250 / 2; + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawImage( + const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling, + bool render_with_attributes) { + if (IsComplex()) { + return; + } + // AA vs non-AA has a cost but it's dwarfed by the overall cost of the + // drawImage call. + // + // The main difference is if the image is backed by a texture already or not + // If we don't need to upload, then the cost scales linearly with the + // length of the image. If it needs uploading, the cost scales linearly + // with the square of the area (!!!). + SkISize dimensions = image->dimensions(); + unsigned int length = (dimensions.width() + dimensions.height()) / 2; + unsigned int area = dimensions.width() * dimensions.height(); + + // m = 1/13 + // c = 0 + unsigned int complexity = length * 400 / 13; + + if (!image->isTextureBacked()) { + // We can't square the area here as we'll overflow, so let's approximate + // by taking the calculated complexity score and applying a multiplier to + // it. + // + // (complexity * area / 60000) + 4000 gives a reasonable approximation with + // AA (complexity * area / 19000) gives a reasonable approximation without + // AA. + float multiplier; + if (IsAntiAliased()) { + multiplier = area / 60000.0f; + complexity = complexity * multiplier + 4000; + } else { + multiplier = area / 19000.0f; + complexity = complexity * multiplier; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::ImageRect( + const SkISize& size, + bool texture_backed, + bool render_with_attributes, + SkCanvas::SrcRectConstraint constraint) { + if (IsComplex()) { + return; + } + // Two main groups here - texture-backed and non-texture-backed images. + // + // Within each group, they all perform within a few % of each other *except* + // when we have a strict constraint and anti-aliasing enabled. + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + unsigned int complexity; + if (!texture_backed || + (texture_backed && render_with_attributes && + constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint && + IsAntiAliased())) { + unsigned int area = size.width() * size.height(); + // m = 1/4000 + // c = 5 + complexity = (area + 20000) / 10; + } else { + unsigned int length = (size.width() + size.height()) / 2; + // There's a little bit of spread here but the numbers are pretty large + // anyway. + // + // m = 1/22 + // c = 0 + complexity = length * 200 / 11; + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawImageNine( + const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) { + if (IsComplex()) { + return; + } + + SkISize dimensions = image->dimensions(); + unsigned int area = dimensions.width() * dimensions.height(); + + // m = 1/3600 + // c = 3 + unsigned int complexity = (area + 10800) / 9; + + // Uploading incurs about a 40% performance penalty. + if (!image->isTextureBacked()) { + complexity *= 1.4f; + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawDisplayList( + const sk_sp display_list) { + if (IsComplex()) { + return; + } + GLHelper helper(Ceiling() - CurrentComplexityScore()); + display_list->Dispatch(helper); + AccumulateComplexity(helper.ComplexityScore()); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawTextBlob( + const sk_sp blob, + SkScalar x, + SkScalar y) { + if (IsComplex()) { + return; + } + // There are two classes here, hairline vs non-hairline. + // Hairline scales loglinearly with the number of glyphs. + // Non-hairline scales linearly. + + // Unfortunately there is currently no way for us to figure out the glyph + // count from an SkTextBlob. We will need to figure out a better solution + // here, but for now just use a placeholder value of 100 glyphs. + unsigned int glyph_count = 100; + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + unsigned int complexity; + if (IsHairline() && Style() == SkPaint::Style::kStroke_Style) { + // If hairlines are on, we hit a degenerative case within Skia that causes + // our time to skyrocket. + // + // m = 1/65 + // c = 1 + complexity = (glyph_count + 65) * 40000 / 13; + } else { + // m = 1/3500 + // c = 0.5 + complexity = (glyph_count + 1750) * 40 / 7; + } + + AccumulateComplexity(complexity); +} + +void DisplayListGLComplexityCalculator::GLHelper::drawShadow( + const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) { + if (IsComplex()) { + return; + } + + // Elevation has no significant effect on the timings. Whether the shadow + // is cast by a transparent occluder or not has a small impact of around 5%. + // + // The path verbs do have an effect but only if the verb type is cubic; line, + // quad and conic all perform similarly. + float occluder_penalty = 1.0f; + if (transparent_occluder) { + occluder_penalty = 1.20f; + } + + // The benchmark uses a test path of around 10 path elements. This is likely + // to be similar to what we see in real world usage, but we should benchmark + // different path lengths to see how much impact there is from varying the + // path length. + // + // For now, we will assume that there is no fixed overhead and that the time + // spent rendering the shadow for a path is split evenly amongst all the verbs + // enumerated. + unsigned int line_verb_cost = 17000; // 0.085ms + unsigned int quad_verb_cost = 20000; // 0.1ms + unsigned int conic_verb_cost = 20000; // 0.1ms + unsigned int cubic_verb_cost = 120000; // 0.6ms + + unsigned int complexity = CalculatePathComplexity( + path, line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost); + + AccumulateComplexity(complexity * occluder_penalty); +} + +} // namespace flutter diff --git a/display_list/display_list_complexity_gl.h b/display_list/display_list_complexity_gl.h new file mode 100644 index 0000000000000..ae26a05de8259 --- /dev/null +++ b/display_list/display_list_complexity_gl.h @@ -0,0 +1,90 @@ +// Copyright 2013 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. + +#ifndef FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_GL_H_ +#define FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_GL_H_ + +#include "flutter/display_list/display_list_complexity_helper.h" + +namespace flutter { + +class DisplayListGLComplexityCalculator + : public DisplayListComplexityCalculator { + public: + static DisplayListGLComplexityCalculator* GetInstance(); + + unsigned int Compute(DisplayList* display_list) override { + GLHelper helper(ceiling_); + display_list->Dispatch(helper); + return helper.ComplexityScore(); + } + + bool ShouldBeCached(unsigned int complexity_score) override { + // Set cache threshold at 1ms + return complexity_score > 200000u; + } + + void SetComplexityCeiling(unsigned int ceiling) override { + ceiling_ = ceiling; + } + + private: + class GLHelper : public ComplexityCalculatorHelper { + public: + GLHelper(unsigned int ceiling) : ComplexityCalculatorHelper(ceiling) {} + + void drawLine(const SkPoint& p0, const SkPoint& p1) override; + void drawRect(const SkRect& rect) override; + void drawOval(const SkRect& bounds) override; + void drawCircle(const SkPoint& center, SkScalar radius) override; + void drawRRect(const SkRRect& rrect) override; + void drawDRRect(const SkRRect& outer, const SkRRect& inner) override; + void drawPath(const SkPath& path) override; + void drawArc(const SkRect& oval_bounds, + SkScalar start_degrees, + SkScalar sweep_degrees, + bool use_center) override; + void drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint points[]) override; + void drawVertices(const sk_sp vertices, + SkBlendMode mode) override; + void drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling, + bool render_with_attributes) override; + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) override; + void drawDisplayList(const sk_sp display_list) override; + void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) override; + void drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) override; + + protected: + void ImageRect(const SkISize& size, + bool texture_backed, + bool render_with_attributes, + SkCanvas::SrcRectConstraint constraint) override; + + unsigned int SaveLayerComplexity() override; + }; + + DisplayListGLComplexityCalculator() + : ceiling_(std::numeric_limits::max()) {} + static DisplayListGLComplexityCalculator* instance_; + + unsigned int ceiling_; +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_GL_H_ diff --git a/display_list/display_list_complexity_helper.h b/display_list/display_list_complexity_helper.h new file mode 100644 index 0000000000000..f70f1af61a2f8 --- /dev/null +++ b/display_list/display_list_complexity_helper.h @@ -0,0 +1,306 @@ +// Copyright 2013 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. + +#ifndef FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_HELPER_H_ +#define FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_HELPER_H_ + +#include "flutter/display_list/display_list_complexity.h" +#include "flutter/display_list/display_list_dispatcher.h" +#include "flutter/display_list/display_list_utils.h" + +namespace flutter { + +// The Metal and OpenGL complexity calculators use benchmark data gathered +// using the display_list_benchmarks test suite on real devices. +// +// Constants of proportionality and trends were chosen to better match +// larger numbers rather than smaller ones. This may turn out to be the +// wrong decision, but the rationale here is that with the smaller numbers, +// we have: +// +// a) More noise. +// b) Less absolute difference. If the constant we've chosen is out by 50% +// on a measurement that is 0.001ms, that's less of an issue than if +// the measurement is 15ms. +// c) Smaller numbers affect the caching decision negligibly; the caching +// decision is likely to be driven by slower ops rather than faster ones. +// +// In some cases, a cost penalty is used to figure out the cost of an +// attribute such as anti-aliasing or fill style. In some of these, the +// penalty is proportional to some other value such as the radius of +// a circle. In these cases, we ensure that the penalty is never smaller +// than 1.0f. +// +// The error bars in measurement are likely quite large and will +// vary from device to device, so the goal here is not to provide a +// perfectly accurate representation of a given DisplayList's +// complexity but rather a very rough estimate that improves upon our +// previous cache admission policy (op_count > 5). +// +// There will likely be future work that will improve upon the figures +// in here. Notably, we do not take matrices or clips into account yet. +// +// The scoring is based around a baseline score of 100 being roughly +// equivalent to 0.0005ms of time. With a 32-bit unsigned integer, this +// would set the maximum time estimate associated with the complexity score +// at about 21 seconds, which far exceeds what we would ever expect a +// DisplayList to take rasterising. +// +// Finally, care has been taken to keep the computations as cheap as possible. +// We need to be able to calculate the complexity as quickly as possible +// so that we don't end up wasting too much time figuring out if something +// should be cached or not and eat into the time we could have just spent +// rasterising the DisplayList. +// +// In order to keep the computations cheap, the following tradeoffs were made: +// +// a) Limit all computations to simple division, multiplication, addition +// and subtraction. +// b) Try and use integer arithmetic as much as possible. +// c) If a specific draw op is logarithmic in complexity, do a best fit +// onto a linear equation within the range we expect to see the variables +// fall within. +// +// As an example of the above, let's say we have some data that looks like the +// complexity is something like O(n^1/3). We would like to avoid anything too +// expensive to calculate, so taking the cube root of the value to try and +// calculate the time cost should be avoided. +// +// In this case, the approach would be to take a straight line approximation +// that maps closely in the range of n where we feel it is most likely to occur. +// For example, if this were drawLine, and n were the line length, and we +// expected the line lengths to typically be between 50 and 100, then we would +// figure out the equation of the straight line graph that approximates the +// n^1/3 curve, and if possible try and choose an approximation that is more +// representative in the range of [50, 100] for n. +// +// Once that's done, we can develop the formula as follows: +// +// Take y = mx + c (straight line graph chosen as per guidelines above). +// Divide by however many ops the benchmark ran for a single pass. +// Multiply by 200,000 to normalise 0.0005ms = 100. +// Simplify down the formula. +// +// So if we had m = 1/5 and c = 0, and the drawLines benchmark ran 10,000 +// drawLine calls per iteration: +// +// y (time taken) = x (line length) / 5 +// y = x / 5 * 200,000 / 10,000 +// y = x / 5 * 20 +// y = 4x + +class ComplexityCalculatorHelper + : public virtual Dispatcher, + public virtual IgnoreClipDispatchHelper, + public virtual IgnoreTransformDispatchHelper { + public: + ComplexityCalculatorHelper(unsigned int ceiling) + : is_complex_(false), + ceiling_(ceiling), + save_layer_count_(0), + complexity_score_(0) {} + + virtual ~ComplexityCalculatorHelper() = default; + + void setDither(bool dither) override {} + void setInvertColors(bool invert) override {} + void setStrokeCap(SkPaint::Cap cap) override {} + void setStrokeJoin(SkPaint::Join join) override {} + void setStrokeMiter(SkScalar limit) override {} + void setColor(SkColor color) override {} + void setBlendMode(SkBlendMode mode) override {} + void setBlender(sk_sp blender) override {} + void setShader(sk_sp shader) override {} + void setImageFilter(sk_sp filter) override {} + void setColorFilter(const DlColorFilter* filter) override {} + void setPathEffect(sk_sp effect) override {} + void setMaskFilter(sk_sp filter) override {} + void setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) override {} + + void save() override {} + // We accumulate the cost of restoring a saveLayer() in saveLayer() + void restore() override {} + + void setAntiAlias(bool aa) override { current_paint_.setAntiAlias(aa); } + + void setStyle(SkPaint::Style style) override { + current_paint_.setStyle(style); + } + + void setStrokeWidth(SkScalar width) override { + current_paint_.setStrokeWidth(width); + } + + void saveLayer(const SkRect* bounds, + const SaveLayerOptions options) override { + if (IsComplex()) { + return; + } + save_layer_count_++; + } + + void drawColor(SkColor color, SkBlendMode mode) override { + if (IsComplex()) { + return; + } + // Placeholder value here. This is a relatively cheap operation. + AccumulateComplexity(50); + } + + void drawPaint() override { + if (IsComplex()) { + return; + } + // Placeholder value here. This can be cheap (e.g. effectively a drawColor), + // or expensive (e.g. a bitmap shader with an image filter) + AccumulateComplexity(50); + } + + void drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + bool render_with_attributes, + SkCanvas::SrcRectConstraint constraint) override { + if (IsComplex()) { + return; + } + ImageRect(image->dimensions(), image->isTextureBacked(), + render_with_attributes, constraint); + } + + void drawImageLattice(const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) override { + if (IsComplex()) { + return; + } + // This is not currently called from Flutter code, and this API is likely + // to be removed in the future. For now, just return what drawImageNine + // would + ImageRect(image->dimensions(), image->isTextureBacked(), + render_with_attributes, + SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint); + } + + void drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cull_rect, + bool render_with_attributes) override { + if (IsComplex()) { + return; + } + // This API just does a series of drawImage calls from the atlas + // This is equivalent to calling drawImageRect lots of times + for (int i = 0; i < count; i++) { + ImageRect(SkISize::Make(tex[i].width(), tex[i].height()), true, + render_with_attributes, + SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint); + } + } + + void drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool render_with_attributes) override { + // This API shouldn't be used, but for now just take the + // approximateOpCount() and multiply by 50 as a placeholder. + AccumulateComplexity(picture->approximateOpCount() * 50); + } + + // This method finalizes the complexity score calculation and returns it + unsigned int ComplexityScore() { + // We hit our ceiling, so return that + if (IsComplex()) { + return Ceiling(); + } + + // Calculate the impact of saveLayer. + unsigned int save_layer_complexity = SaveLayerComplexity(); + + // Check for overflow + if (Ceiling() - complexity_score_ < save_layer_complexity) { + return Ceiling(); + } + + return complexity_score_ + save_layer_complexity; + } + + protected: + void AccumulateComplexity(unsigned int complexity) { + // Check to see if we will overflow by accumulating this complexity score + if (ceiling_ - complexity_score_ < complexity) { + is_complex_ = true; + return; + } + + complexity_score_ += complexity; + } + + inline bool IsAntiAliased() { return current_paint_.isAntiAlias(); } + inline bool IsHairline() { return current_paint_.getStrokeWidth() == 0.0f; } + inline SkPaint::Style Style() { return current_paint_.getStyle(); } + inline bool IsComplex() { return is_complex_; } + inline unsigned int Ceiling() { return ceiling_; } + inline unsigned int SaveLayerCount() { return save_layer_count_; } + inline unsigned int CurrentComplexityScore() { return complexity_score_; } + + unsigned int CalculatePathComplexity(const SkPath& path, + unsigned int line_verb_cost, + unsigned int quad_verb_cost, + unsigned int conic_verb_cost, + unsigned int cubic_verb_cost) { + int verb_count = path.countVerbs(); + uint8_t verbs[verb_count]; + path.getVerbs(verbs, verb_count); + + unsigned int complexity = 0; + for (int i = 0; i < verb_count; i++) { + switch (verbs[i]) { + case SkPath::Verb::kLine_Verb: + complexity += line_verb_cost; + break; + case SkPath::Verb::kQuad_Verb: + complexity += quad_verb_cost; + break; + case SkPath::Verb::kConic_Verb: + complexity += conic_verb_cost; + break; + case SkPath::Verb::kCubic_Verb: + complexity += cubic_verb_cost; + break; + } + } + return complexity; + } + + virtual void ImageRect(const SkISize& size, + bool texture_backed, + bool render_with_attributes, + SkCanvas::SrcRectConstraint constraint) = 0; + + virtual unsigned int SaveLayerComplexity() = 0; + + private: + SkPaint current_paint_; + + // If we exceed the ceiling (defaults to the largest number representable + // by unsigned int), then set the is_complex_ bool and we no longer + // accumulate. + bool is_complex_; + unsigned int ceiling_; + + unsigned int save_layer_count_; + unsigned int complexity_score_; +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_HELPER_H_ diff --git a/display_list/display_list_complexity_metal.cc b/display_list/display_list_complexity_metal.cc new file mode 100644 index 0000000000000..a8520fa44383b --- /dev/null +++ b/display_list/display_list_complexity_metal.cc @@ -0,0 +1,627 @@ +// Copyright 2013 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. + +#include "flutter/display_list/display_list_complexity_metal.h" + +// The numbers and weightings used in this file stem from taking the +// data from the DisplayListBenchmarks suite run on an iPhone 12 and +// applying very rough analysis on them to identify the approximate +// trends. +// +// See the comments in display_list_complexity_helper.h for details on the +// process and rationale behind coming up with these numbers. + +namespace flutter { + +DisplayListMetalComplexityCalculator* + DisplayListMetalComplexityCalculator::instance_ = nullptr; + +DisplayListMetalComplexityCalculator* +DisplayListMetalComplexityCalculator::GetInstance() { + if (instance_ == nullptr) { + instance_ = new DisplayListMetalComplexityCalculator(); + } + return instance_; +} + +unsigned int +DisplayListMetalComplexityCalculator::MetalHelper::SaveLayerComplexity() { + // Calculate the impact of saveLayer. + unsigned int save_layer_complexity; + if (SaveLayerCount() == 0) { + save_layer_complexity = 0; + } else { + // saveLayer seems to have two trends; if the count is < 200, + // then the individual cost of a saveLayer is higher than if + // the count is > 200. + if (SaveLayerCount() > 200) { + // m = 1/5 + // c = 1 + save_layer_complexity = (SaveLayerCount() + 5) * 40000; + } else { + // m = 1/2 + // c = 1 + save_layer_complexity = (SaveLayerCount() + 2) * 100000; + } + } + + return save_layer_complexity; +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawLine( + const SkPoint& p0, + const SkPoint& p1) { + if (IsComplex()) { + return; + } + // The curve here may be log-linear, although it doesn't really match up that + // well. To avoid costly computations, try and do a best fit of the data onto + // a linear graph as a very rough first order approximation. + + float non_hairline_penalty = 1.0f; + float aa_penalty = 1.0f; + + if (!IsHairline()) { + non_hairline_penalty = 1.15f; + } + if (IsAntiAliased()) { + aa_penalty = 1.4f; + } + + // Use an approximation for the distance to avoid floating point or + // sqrt() calls. + SkScalar distance = abs(p0.x() - p1.x()) + abs(p0.y() - p1.y()); + + // The baseline complexity is for a hairline stroke with no AA. + // m = 1/45 + // c = 5 + unsigned int complexity = + ((distance + 225) * 4 / 9) * non_hairline_penalty * aa_penalty; + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawRect( + const SkRect& rect) { + if (IsComplex()) { + return; + } + + unsigned int complexity; + + // If stroked, cost scales linearly with the rectangle width/height. + // If filled, it scales with the area. + // + // Hairline stroke vs non hairline has no real penalty at smaller lengths, + // but it increases at larger lengths. There isn't enough data to get a good + // idea of the penalty at lengths > 1000px. + // + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kFill_Style) { + // No real difference for AA with filled styles. + unsigned int area = rect.width() * rect.height(); + + // m = 1/9000 + // c = 0 + complexity = area / 225; + } else { + // Take the average of the width and height. + unsigned int length = (rect.width() + rect.height()) / 2; + + // There is a penalty for AA being *disabled*. + if (IsAntiAliased()) { + // m = 1/65 + // c = 0 + complexity = length * 8 / 13; + } else { + // m = 1/35 + // c = 0 + complexity = length * 8 / 7; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawOval( + const SkRect& bounds) { + if (IsComplex()) { + return; + } + // DrawOval scales very roughly linearly with the bounding box width/height + // (not area) for stroked styles without AA. + // + // Filled styles and stroked styles with AA scale linearly with the bounding + // box area. + unsigned int area = bounds.width() * bounds.height(); + + unsigned int complexity; + + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kFill_Style) { + // With filled styles, there is no significant AA penalty. + // m = 1/16000 + // c = 0 + complexity = area / 80; + } else { + if (IsAntiAliased()) { + // m = 1/7500 + // c = 0 + complexity = area * 2 / 75; + } else { + // Take the average of the width and height. + unsigned int length = (bounds.width() + bounds.height()) / 2; + + // m = 1/80 + // c = 0 + complexity = length * 5 / 2; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawCircle( + const SkPoint& center, + SkScalar radius) { + if (IsComplex()) { + return; + } + + unsigned int complexity; + + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kFill_Style) { + // We can ignore pi here. + unsigned int area = radius * radius; + // m = 1/1300 + // c = 5 + complexity = (area + 6500) * 2 / 65; + + // Penalty of around 5% when AA is disabled. + if (!IsAntiAliased()) { + complexity *= 1.05f; + } + } else { + // Hairline vs non-hairline has no significant performance difference. + if (IsAntiAliased()) { + // m = 1/7 + // c = 7 + complexity = (radius + 49) * 40 / 7; + } else { + // m = 1/16 + // c = 8 + complexity = (radius + 128) * 5 / 2; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawRRect( + const SkRRect& rrect) { + if (IsComplex()) { + return; + } + // RRects scale linearly with the area of the bounding rect. + unsigned int area = rrect.width() * rrect.height(); + + // Drawing RRects is split into two performance tiers; an expensive + // one and a less expensive one. Both scale linearly with area. + // + // Expensive: All filled style, symmetric w/AA. + bool expensive = + (Style() == SkPaint::Style::kFill_Style) || + ((rrect.getType() == SkRRect::Type::kSimple_Type) && IsAntiAliased()); + + unsigned int complexity; + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + if (expensive) { + // m = 1/25000 + // c = 2 + // An area of 7000px^2 ~= baseline timing of 0.0005ms. + complexity = (area + 10500) / 175; + } else { + // m = 1/7000 + // c = 1.5 + // An area of 16000px^2 ~= baseline timing of 0.0005ms. + complexity = (area + 50000) / 625; + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawDRRect( + const SkRRect& outer, + const SkRRect& inner) { + if (IsComplex()) { + return; + } + // There are roughly four classes here: + // a) Filled style with AA enabled. + // b) Filled style with AA disabled. + // c) Complex RRect type with AA enabled and filled style. + // d) Everything else. + // + // a) and c) scale linearly with the area, b) and d) scale linearly with + // a single dimension (length). In all cases, the dimensions refer to + // the outer RRect. + unsigned int length = (outer.width() + outer.height()) / 2; + + unsigned int complexity; + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + // + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kFill_Style) { + unsigned int area = outer.width() * outer.height(); + if (outer.getType() == SkRRect::Type::kComplex_Type) { + // m = 1/1000 + // c = 1 + complexity = (area + 1000) / 10; + } else { + if (IsAntiAliased()) { + // m = 1/3500 + // c = 1.5 + complexity = (area + 5250) / 35; + } else { + // m = 1/30 + // c = 1 + complexity = (300 + (10 * length)) / 3; + } + } + } else { + // m = 1/60 + // c = 1.75 + complexity = ((10 * length) + 1050) / 6; + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawPath( + const SkPath& path) { + if (IsComplex()) { + return; + } + // There is negligible effect on the performance for hairline vs. non-hairline + // stroke widths. + // + // The data for filled styles is currently suspicious, so for now we are going + // to assign scores based on stroked styles. + + unsigned int line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost; + + if (IsAntiAliased()) { + line_verb_cost = 75; + quad_verb_cost = 100; + conic_verb_cost = 160; + cubic_verb_cost = 210; + } else { + line_verb_cost = 67; + quad_verb_cost = 80; + conic_verb_cost = 140; + cubic_verb_cost = 210; + } + + // There seems to be a fixed cost of around 1ms for calling drawPath. + unsigned int complexity = + 200000 + CalculatePathComplexity(path, line_verb_cost, quad_verb_cost, + conic_verb_cost, cubic_verb_cost); + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawArc( + const SkRect& oval_bounds, + SkScalar start_degrees, + SkScalar sweep_degrees, + bool use_center) { + if (IsComplex()) { + return; + } + // Hairline vs non-hairline makes no difference to the performance. + // Stroked styles without AA scale linearly with the diameter. + // Stroked styles with AA scale linearly with the area except for small + // values. Filled styles scale linearly with the area. + unsigned int diameter = (oval_bounds.width() + oval_bounds.height()) / 2; + unsigned int area = oval_bounds.width() * oval_bounds.height(); + + unsigned int complexity; + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + // + // There is also a kStrokeAndFill_Style that Skia exposes, but we do not + // currently use it anywhere in Flutter. + if (Style() == SkPaint::Style::kStroke_Style) { + if (IsAntiAliased()) { + // m = 1/8500 + // c = 16 + complexity = (area + 136000) * 2 / 765; + } else { + // m = 1/60 + // c = 3 + complexity = (diameter + 180) * 10 / 27; + } + } else { + if (IsAntiAliased()) { + // m = 1/20000 + // c = 20 + complexity = (area + 400000) / 900; + } else { + // m = 1/2100 + // c = 8 + complexity = (area + 16800) * 2 / 189; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawPoints( + SkCanvas::PointMode mode, + uint32_t count, + const SkPoint points[]) { + if (IsComplex()) { + return; + } + unsigned int complexity; + + // If AA is off then they all behave similarly, and scale + // linearly with the point count. + if (!IsAntiAliased()) { + // m = 1/16000 + // c = 0.75 + complexity = (count + 12000) * 25 / 2; + } else { + if (mode == SkCanvas::kPolygon_PointMode) { + // m = 1/1250 + // c = 1 + complexity = (count + 1250) * 160; + } else { + if (IsHairline() && mode == SkCanvas::kPoints_PointMode) { + // This is a special case, it triggers an extremely fast path. + // m = 1/14500 + // c = 0 + complexity = count * 400 / 29; + } else { + // m = 1/2200 + // c = 0.75 + complexity = (count + 1650) * 1000 / 11; + } + } + } + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawVertices( + const sk_sp vertices, + SkBlendMode mode) { + // There is currently no way for us to get the VertexMode from the SkVertices + // object, but for future reference: + // + // TriangleStrip is roughly 25% more expensive than TriangleFan. + // TriangleFan is roughly 5% more expensive than Triangles. + + // There is currently no way for us to get the vertex count from an SkVertices + // object, so we have to estimate it from the approximate size. + // + // Approximate size returns the sum of the sizes of the positions (SkPoint), + // texs (SkPoint), colors (SkColor) and indices (uint16_t) arrays multiplied + // by sizeof(type). As a very, very rough estimate, divide that by 20 to get + // an idea of the vertex count. + unsigned int approximate_vertex_count = vertices->approximateSize() / 20; + + // For the baseline, it's hard to identify the trend. It might be O(n^1/2). + // For now, treat it as linear as an approximation. + // + // m = 1/4000 + // c = 1 + unsigned int complexity = (approximate_vertex_count + 4000) * 50; + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawImage( + const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling, + bool render_with_attributes) { + if (IsComplex()) { + return; + } + // AA vs non-AA has a cost but it's dwarfed by the overall cost of the + // drawImage call. + // + // The main difference is if the image is backed by a texture already or not + // If we don't need to upload, then the cost scales linearly with the + // area of the image. If it needs uploading, the cost scales linearly + // with the square of the area (!!!). + SkISize dimensions = image->dimensions(); + unsigned int area = dimensions.width() * dimensions.height(); + + // m = 1/17000 + // c = 3 + unsigned int complexity = (area + 51000) * 4 / 170; + + if (!image->isTextureBacked()) { + // We can't square the area here as we'll overflow, so let's approximate + // by taking the calculated complexity score and applying a multiplier to + // it. + // + // (complexity * area / 35000) + 1200 gives a reasonable approximation. + float multiplier = area / 35000.0f; + complexity = complexity * multiplier + 1200; + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::ImageRect( + const SkISize& size, + bool texture_backed, + bool render_with_attributes, + SkCanvas::SrcRectConstraint constraint) { + if (IsComplex()) { + return; + } + // Two main groups here - texture-backed and non-texture-backed images. + // + // Within each group, they all perform within a few % of each other *except* + // when we have a strict constraint and anti-aliasing enabled. + unsigned int area = size.width() * size.height(); + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + unsigned int complexity; + if (texture_backed) { + // Baseline for texture-backed SkImages. + // m = 1/23000 + // c = 2.3 + complexity = (area + 52900) * 2 / 115; + if (render_with_attributes && + constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint && + IsAntiAliased()) { + // There's about a 30% performance penalty from the baseline. + complexity *= 1.3f; + } + } else { + if (render_with_attributes && + constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint && + IsAntiAliased()) { + // m = 1/12200 + // c = 2.75 + complexity = (area + 33550) * 2 / 61; + } else { + // m = 1/14500 + // c = 2.5 + complexity = (area + 36250) * 4 / 145; + } + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawImageNine( + const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) { + if (IsComplex()) { + return; + } + // Whether uploading or not, the performance is comparable across all + // variations. + SkISize dimensions = image->dimensions(); + unsigned int area = dimensions.width() * dimensions.height(); + + // m = 1/8000 + // c = 3 + unsigned int complexity = (area + 24000) / 20; + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawDisplayList( + const sk_sp display_list) { + if (IsComplex()) { + return; + } + MetalHelper helper(Ceiling() - CurrentComplexityScore()); + display_list->Dispatch(helper); + AccumulateComplexity(helper.ComplexityScore()); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawTextBlob( + const sk_sp blob, + SkScalar x, + SkScalar y) { + if (IsComplex()) { + return; + } + // There are two classes here, hairline vs non-hairline. + // + // Hairline scales loglinearly with the number of glyphs. + // Non-hairline scales linearly. + + // Unfortunately there is currently no way for us to figure out the glyph + // count from an SkTextBlob. We will need to figure out a better solution + // here, but for now just use a placeholder value of 100 glyphs. + unsigned int glyph_count = 100; + + // These values were worked out by creating a straight line graph (y=mx+c) + // approximately matching the measured data, normalising the data so that + // 0.0005ms resulted in a score of 100 then simplifying down the formula. + unsigned int complexity; + if (IsHairline() && Style() == SkPaint::Style::kStroke_Style) { + // If hairlines are on, we hit a degenerative case within Skia that causes + // our time to skyrocket. + // + // m = 1/3000 + // c = 1.75 + // x = glyph_count * log2(glyph_count) + unsigned int x = glyph_count * log(glyph_count); + complexity = (x + 5250) * 200 / 3; + } else { + // m = 1/5000 + // c = 0.75 + complexity = 40 * (glyph_count + 3750); + } + + AccumulateComplexity(complexity); +} + +void DisplayListMetalComplexityCalculator::MetalHelper::drawShadow( + const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) { + if (IsComplex()) { + return; + } + + // Elevation has no significant effect on the timings. Whether the shadow + // is cast by a transparent occluder or not has a small impact of around 5%. + // + // The path verbs do have an effect but only if the verb type is cubic; line, + // quad and conic all perform similarly. + float occluder_penalty = 1.0f; + if (transparent_occluder) { + occluder_penalty = 1.05f; + } + + // The benchmark uses a test path of around 10 path elements. This is likely + // to be similar to what we see in real world usage, but we should benchmark + // different path lengths to see how much impact there is from varying the + // path length. + // + // For now, we will assume that there is no fixed overhead and that the time + // spent rendering the shadow for a path is split evenly amongst all the verbs + // enumerated. + unsigned int line_verb_cost = 20000; // 0.1ms + unsigned int quad_verb_cost = 20000; // 0.1ms + unsigned int conic_verb_cost = 20000; // 0.1ms + unsigned int cubic_verb_cost = 80000; // 0.4ms + + unsigned int complexity = + 0 + CalculatePathComplexity(path, line_verb_cost, quad_verb_cost, + conic_verb_cost, cubic_verb_cost); + + AccumulateComplexity(complexity * occluder_penalty); +} + +} // namespace flutter diff --git a/display_list/display_list_complexity_metal.h b/display_list/display_list_complexity_metal.h new file mode 100644 index 0000000000000..aae8685cbcea3 --- /dev/null +++ b/display_list/display_list_complexity_metal.h @@ -0,0 +1,90 @@ +// Copyright 2013 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. + +#ifndef FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_METAL_H_ +#define FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_METAL_H_ + +#include "flutter/display_list/display_list_complexity_helper.h" + +namespace flutter { + +class DisplayListMetalComplexityCalculator + : public DisplayListComplexityCalculator { + public: + static DisplayListMetalComplexityCalculator* GetInstance(); + + unsigned int Compute(DisplayList* display_list) override { + MetalHelper helper(ceiling_); + display_list->Dispatch(helper); + return helper.ComplexityScore(); + } + + bool ShouldBeCached(unsigned int complexity_score) override { + // Set cache threshold at 1ms + return complexity_score > 200000u; + } + + void SetComplexityCeiling(unsigned int ceiling) override { + ceiling_ = ceiling; + } + + private: + class MetalHelper : public ComplexityCalculatorHelper { + public: + MetalHelper(unsigned int ceiling) : ComplexityCalculatorHelper(ceiling) {} + + void drawLine(const SkPoint& p0, const SkPoint& p1) override; + void drawRect(const SkRect& rect) override; + void drawOval(const SkRect& bounds) override; + void drawCircle(const SkPoint& center, SkScalar radius) override; + void drawRRect(const SkRRect& rrect) override; + void drawDRRect(const SkRRect& outer, const SkRRect& inner) override; + void drawPath(const SkPath& path) override; + void drawArc(const SkRect& oval_bounds, + SkScalar start_degrees, + SkScalar sweep_degrees, + bool use_center) override; + void drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint points[]) override; + void drawVertices(const sk_sp vertices, + SkBlendMode mode) override; + void drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling, + bool render_with_attributes) override; + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter, + bool render_with_attributes) override; + void drawDisplayList(const sk_sp display_list) override; + void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) override; + void drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) override; + + protected: + void ImageRect(const SkISize& size, + bool texture_backed, + bool render_with_attributes, + SkCanvas::SrcRectConstraint constraint) override; + + unsigned int SaveLayerComplexity() override; + }; + + DisplayListMetalComplexityCalculator() + : ceiling_(std::numeric_limits::max()) {} + static DisplayListMetalComplexityCalculator* instance_; + + unsigned int ceiling_; +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_METAL_H_ diff --git a/display_list/display_list_complexity_unittests.cc b/display_list/display_list_complexity_unittests.cc new file mode 100644 index 0000000000000..d75c6f47e2d9c --- /dev/null +++ b/display_list/display_list_complexity_unittests.cc @@ -0,0 +1,425 @@ +// Copyright 2013 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. + +#include "flutter/display_list/display_list.h" +#include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list_complexity.h" +#include "flutter/display_list/display_list_complexity_gl.h" +#include "flutter/display_list/display_list_complexity_metal.h" +#include "flutter/display_list/display_list_test_utils.h" +#include "flutter/testing/testing.h" +#include "third_party/skia/include/core/SkColor.h" + +namespace flutter { +namespace testing { + +namespace { + +std::vector Calculators() { + return {DisplayListMetalComplexityCalculator::GetInstance(), + DisplayListGLComplexityCalculator::GetInstance(), + DisplayListNaiveComplexityCalculator::GetInstance()}; +} + +std::vector AccumulatorCalculators() { + return {DisplayListMetalComplexityCalculator::GetInstance(), + DisplayListGLComplexityCalculator::GetInstance()}; +} + +std::vector GetTestPoints() { + std::vector points; + points.push_back(SkPoint::Make(0, 0)); + points.push_back(SkPoint::Make(10, 0)); + points.push_back(SkPoint::Make(10, 10)); + points.push_back(SkPoint::Make(20, 10)); + points.push_back(SkPoint::Make(20, 20)); + + return points; +} + +} // namespace + +TEST(DisplayListComplexity, EmptyDisplayList) { + auto display_list = GetSampleDisplayList(0); + + auto calculators = Calculators(); + for (auto calculator : calculators) { + ASSERT_EQ(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DisplayListCeiling) { + auto display_list = GetSampleDisplayList(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + calculator->SetComplexityCeiling(10u); + ASSERT_EQ(calculator->Compute(display_list.get()), 10u); + calculator->SetComplexityCeiling(std::numeric_limits::max()); + } +} + +TEST(DisplayListComplexity, NestedDisplayList) { + auto display_list = GetSampleNestedDisplayList(); + + auto calculators = Calculators(); + for (auto calculator : calculators) { + // There's only one draw call in the "outer" DisplayList, which calls + // drawDisplayList with the "inner" DisplayList. To ensure we are + // recursing correctly into the inner DisplayList, check that we aren't + // returning 0 (if the function is a no-op) or 1 (as the op_count is 1) + ASSERT_GT(calculator->Compute(display_list.get()), 1u); + } +} + +TEST(DisplayListComplexity, AntiAliasing) { + DisplayListBuilder builder_no_aa; + builder_no_aa.drawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100)); + auto display_list_no_aa = builder_no_aa.Build(); + + DisplayListBuilder builder_aa; + builder_aa.setAntiAlias(true); + builder_aa.drawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100)); + auto display_list_aa = builder_aa.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list_no_aa.get()), + calculator->Compute(display_list_aa.get())); + } +} + +TEST(DisplayListComplexity, StrokeWidth) { + DisplayListBuilder builder_stroke_0; + builder_stroke_0.setStrokeWidth(0.0f); + builder_stroke_0.drawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100)); + auto display_list_stroke_0 = builder_stroke_0.Build(); + + DisplayListBuilder builder_stroke_1; + builder_stroke_1.setStrokeWidth(1.0f); + builder_stroke_1.drawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100)); + auto display_list_stroke_1 = builder_stroke_1.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list_stroke_0.get()), + calculator->Compute(display_list_stroke_1.get())); + } +} + +TEST(DisplayListComplexity, Style) { + DisplayListBuilder builder_filled; + builder_filled.setStyle(SkPaint::Style::kFill_Style); + builder_filled.drawRect(SkRect::MakeXYWH(10, 10, 80, 80)); + auto display_list_filled = builder_filled.Build(); + + DisplayListBuilder builder_stroked; + builder_stroked.setStyle(SkPaint::Style::kStroke_Style); + builder_stroked.drawRect(SkRect::MakeXYWH(10, 10, 80, 80)); + auto display_list_stroked = builder_stroked.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list_filled.get()), + calculator->Compute(display_list_stroked.get())); + } +} + +TEST(DisplayListComplexity, SaveLayers) { + DisplayListBuilder builder; + builder.saveLayer(nullptr, true); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawPath) { + DisplayListBuilder builder_line; + SkPath linePath; + linePath.moveTo(SkPoint::Make(0, 0)); + linePath.lineTo(SkPoint::Make(10, 10)); + linePath.close(); + builder_line.drawPath(linePath); + auto display_list_line = builder_line.Build(); + + DisplayListBuilder builder_quad; + SkPath quadPath; + quadPath.moveTo(SkPoint::Make(0, 0)); + quadPath.quadTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20)); + quadPath.close(); + builder_quad.drawPath(quadPath); + auto display_list_quad = builder_quad.Build(); + + DisplayListBuilder builder_conic; + SkPath conicPath; + conicPath.moveTo(SkPoint::Make(0, 0)); + conicPath.conicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20), 1.5f); + conicPath.close(); + builder_conic.drawPath(conicPath); + auto display_list_conic = builder_conic.Build(); + + DisplayListBuilder builder_cubic; + SkPath cubicPath; + cubicPath.moveTo(SkPoint::Make(0, 0)); + cubicPath.cubicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20), + SkPoint::Make(20, 20)); + builder_cubic.drawPath(cubicPath); + auto display_list_cubic = builder_cubic.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list_line.get()), 0u); + ASSERT_NE(calculator->Compute(display_list_quad.get()), 0u); + ASSERT_NE(calculator->Compute(display_list_conic.get()), 0u); + ASSERT_NE(calculator->Compute(display_list_cubic.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawShadow) { + DisplayListBuilder builder_line; + SkPath linePath; + linePath.moveTo(SkPoint::Make(0, 0)); + linePath.lineTo(SkPoint::Make(10, 10)); + linePath.close(); + builder_line.drawShadow(linePath, SK_ColorRED, 10.0f, false, 1.0f); + auto display_list_line = builder_line.Build(); + + DisplayListBuilder builder_quad; + SkPath quadPath; + quadPath.moveTo(SkPoint::Make(0, 0)); + quadPath.quadTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20)); + quadPath.close(); + builder_quad.drawShadow(quadPath, SK_ColorRED, 10.0f, false, 1.0f); + auto display_list_quad = builder_quad.Build(); + + DisplayListBuilder builder_conic; + SkPath conicPath; + conicPath.moveTo(SkPoint::Make(0, 0)); + conicPath.conicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20), 1.5f); + conicPath.close(); + builder_conic.drawShadow(conicPath, SK_ColorRED, 10.0f, false, 1.0f); + auto display_list_conic = builder_conic.Build(); + + DisplayListBuilder builder_cubic; + SkPath cubicPath; + cubicPath.moveTo(SkPoint::Make(0, 0)); + cubicPath.cubicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20), + SkPoint::Make(20, 20)); + builder_cubic.drawShadow(cubicPath, SK_ColorRED, 10.0f, false, 1.0f); + auto display_list_cubic = builder_cubic.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list_line.get()), 0u); + ASSERT_NE(calculator->Compute(display_list_quad.get()), 0u); + ASSERT_NE(calculator->Compute(display_list_conic.get()), 0u); + ASSERT_NE(calculator->Compute(display_list_cubic.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawOval) { + DisplayListBuilder builder; + builder.drawOval(SkRect::MakeXYWH(10, 10, 100, 80)); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawCircle) { + DisplayListBuilder builder; + builder.drawCircle(SkPoint::Make(50, 50), 10.0f); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawRRect) { + DisplayListBuilder builder; + builder.drawRRect( + SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 80), 2.0f, 3.0f)); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawDRRect) { + DisplayListBuilder builder; + SkRRect outer = + SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 80), 2.0f, 3.0f); + SkRRect inner = + SkRRect::MakeRectXY(SkRect::MakeXYWH(15, 15, 70, 70), 1.5f, 1.5f); + builder.drawDRRect(outer, inner); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawArc) { + DisplayListBuilder builder; + builder.drawArc(SkRect::MakeXYWH(10, 10, 100, 80), 0.0f, 10.0f, true); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawVertices) { + auto points = GetTestPoints(); + auto vertices = + SkVertices::MakeCopy(SkVertices::VertexMode::kTriangles_VertexMode, + points.size(), points.data(), nullptr, nullptr); + DisplayListBuilder builder; + builder.drawVertices(vertices, SkBlendMode::kSrc); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawTextBlob) { + auto text_blob = SkTextBlob::MakeFromString( + "The quick brown fox jumps over the lazy dog.", SkFont()); + + DisplayListBuilder builder; + builder.drawTextBlob(text_blob, 0.0f, 0.0f); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawPoints) { + auto points = GetTestPoints(); + DisplayListBuilder builder_lines; + builder_lines.drawPoints(SkCanvas::kLines_PointMode, points.size(), + points.data()); + auto display_list_lines = builder_lines.Build(); + + DisplayListBuilder builder_points; + builder_points.drawPoints(SkCanvas::kPoints_PointMode, points.size(), + points.data()); + auto display_list_points = builder_points.Build(); + + DisplayListBuilder builder_polygon; + builder_polygon.drawPoints(SkCanvas::kPolygon_PointMode, points.size(), + points.data()); + auto display_list_polygon = builder_polygon.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list_lines.get()), 0u); + ASSERT_NE(calculator->Compute(display_list_points.get()), 0u); + ASSERT_NE(calculator->Compute(display_list_polygon.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawImage) { + SkImageInfo info = + SkImageInfo::Make(50, 50, SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType); + SkBitmap bitmap; + bitmap.allocPixels(info, 0); + auto image = SkImage::MakeFromBitmap(bitmap); + + DisplayListBuilder builder; + builder.drawImage(image, SkPoint::Make(0, 0), SkSamplingOptions(), false); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawImageNine) { + SkImageInfo info = + SkImageInfo::Make(50, 50, SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType); + SkBitmap bitmap; + bitmap.allocPixels(info, 0); + auto image = SkImage::MakeFromBitmap(bitmap); + + SkIRect center = SkIRect::MakeXYWH(5, 5, 20, 20); + SkRect dest = SkRect::MakeXYWH(0, 0, 50, 50); + + DisplayListBuilder builder; + builder.drawImageNine(image, center, dest, SkFilterMode::kNearest, true); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawImageRect) { + SkImageInfo info = + SkImageInfo::Make(50, 50, SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType); + SkBitmap bitmap; + bitmap.allocPixels(info, 0); + auto image = SkImage::MakeFromBitmap(bitmap); + + SkRect src = SkRect::MakeXYWH(0, 0, 50, 50); + SkRect dest = SkRect::MakeXYWH(0, 0, 50, 50); + + DisplayListBuilder builder; + builder.drawImageRect(image, src, dest, SkSamplingOptions(), true); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +TEST(DisplayListComplexity, DrawAtlas) { + SkImageInfo info = + SkImageInfo::Make(50, 50, SkColorType::kRGBA_8888_SkColorType, + SkAlphaType::kPremul_SkAlphaType); + SkBitmap bitmap; + bitmap.allocPixels(info, 0); + auto image = SkImage::MakeFromBitmap(bitmap); + + std::vector rects; + std::vector xforms; + for (int i = 0; i < 10; i++) { + rects.push_back(SkRect::MakeXYWH(0, 0, 10, 10)); + xforms.push_back(SkRSXform::Make(0, 0, 0, 0)); + } + + DisplayListBuilder builder; + builder.drawAtlas(image, xforms.data(), rects.data(), nullptr, 10, + SkBlendMode::kSrc, SkSamplingOptions(), nullptr, true); + auto display_list = builder.Build(); + + auto calculators = AccumulatorCalculators(); + for (auto calculator : calculators) { + ASSERT_NE(calculator->Compute(display_list.get()), 0u); + } +} + +} // namespace testing +} // namespace flutter diff --git a/display_list/display_list_test_utils.cc b/display_list/display_list_test_utils.cc new file mode 100644 index 0000000000000..bdf455cb34e0d --- /dev/null +++ b/display_list/display_list_test_utils.cc @@ -0,0 +1,71 @@ +// Copyright 2013 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. + +#include "flutter/display_list/display_list_test_utils.h" +#include "flutter/display_list/display_list_builder.h" + +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" + +namespace flutter { +namespace testing { + +sk_sp GetSamplePicture() { + SkPictureRecorder recorder; + recorder.beginRecording(SkRect::MakeWH(150, 100)); + SkPaint paint; + paint.setColor(SK_ColorRED); + recorder.getRecordingCanvas()->drawRect(SkRect::MakeXYWH(10, 10, 80, 80), + paint); + return recorder.finishRecordingAsPicture(); +} + +sk_sp GetSampleDisplayList() { + DisplayListBuilder builder(SkRect::MakeWH(150, 100)); + builder.setColor(SK_ColorRED); + builder.drawRect(SkRect::MakeXYWH(10, 10, 80, 80)); + return builder.Build(); +} + +sk_sp GetSampleNestedPicture() { + SkPictureRecorder recorder; + recorder.beginRecording(SkRect::MakeWH(150, 100)); + SkCanvas* canvas = recorder.getRecordingCanvas(); + SkPaint paint; + for (int y = 10; y <= 60; y += 10) { + for (int x = 10; x <= 60; x += 10) { + paint.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE); + canvas->drawRect(SkRect::MakeXYWH(x, y, 80, 80), paint); + } + } + SkPictureRecorder outer_recorder; + outer_recorder.beginRecording(SkRect::MakeWH(150, 100)); + canvas = outer_recorder.getRecordingCanvas(); + canvas->drawPicture(recorder.finishRecordingAsPicture()); + return outer_recorder.finishRecordingAsPicture(); +} + +sk_sp GetSampleNestedDisplayList() { + DisplayListBuilder builder(SkRect::MakeWH(150, 100)); + for (int y = 10; y <= 60; y += 10) { + for (int x = 10; x <= 60; x += 10) { + builder.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE); + builder.drawRect(SkRect::MakeXYWH(x, y, 80, 80)); + } + } + DisplayListBuilder outer_builder(SkRect::MakeWH(150, 100)); + outer_builder.drawDisplayList(builder.Build()); + return outer_builder.Build(); +} + +sk_sp GetSampleDisplayList(int ops) { + DisplayListBuilder builder(SkRect::MakeWH(150, 100)); + for (int i = 0; i < ops; i++) { + builder.drawColor(SK_ColorRED, SkBlendMode::kSrc); + } + return builder.Build(); +} + +} // namespace testing +} // namespace flutter diff --git a/display_list/display_list_test_utils.h b/display_list/display_list_test_utils.h new file mode 100644 index 0000000000000..c29800d05fc28 --- /dev/null +++ b/display_list/display_list_test_utils.h @@ -0,0 +1,25 @@ +// Copyright 2013 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. + +#ifndef FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_UNITTESTS_H_ +#define FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_UNITTESTS_H_ + +#include "flutter/display_list/display_list.h" +#include "flutter/display_list/display_list_builder.h" + +#include "third_party/skia/include/core/SkPicture.h" + +namespace flutter { +namespace testing { + +sk_sp GetSamplePicture(); +sk_sp GetSampleDisplayList(); +sk_sp GetSampleNestedPicture(); +sk_sp GetSampleNestedDisplayList(); +sk_sp GetSampleDisplayList(int ops); + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_FLOW_DISPLAY_LIST_COMPLEXITY_UNITTESTS_H_ diff --git a/flow/BUILD.gn b/flow/BUILD.gn index 212d3960778fe..def9e48d62c76 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -169,6 +169,7 @@ if (enable_unittests) { ":flow_fixtures", ":flow_testing", "//flutter/common/graphics", + "//flutter/display_list:unittests", "//flutter/fml", "//flutter/testing:skia", "//flutter/testing:testing_lib", diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index f268b1275df7a..647567b74dc72 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -109,10 +109,8 @@ static bool IsDisplayListWorthRasterizing( return true; } - // TODO(abarth): We should find a better heuristic here that lets us avoid - // wasting memory on trivial layers that are easy to re-rasterize every frame. - unsigned int complexity_score = complexity_calculator->compute(display_list); - return complexity_calculator->should_be_cached(complexity_score); + unsigned int complexity_score = complexity_calculator->Compute(display_list); + return complexity_calculator->ShouldBeCached(complexity_score); } /// @note Procedure doesn't copy all closures. diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc index ed2be3feb4a8a..b62cfe5d08cd3 100644 --- a/flow/raster_cache_unittests.cc +++ b/flow/raster_cache_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list_test_utils.h" #include "flutter/flow/raster_cache.h" #include "flutter/flow/testing/mock_raster_cache.h" #include "gtest/gtest.h" @@ -14,65 +15,6 @@ namespace flutter { namespace testing { -namespace { - -sk_sp GetSamplePicture() { - SkPictureRecorder recorder; - recorder.beginRecording(SkRect::MakeWH(150, 100)); - SkPaint paint; - paint.setColor(SK_ColorRED); - recorder.getRecordingCanvas()->drawRect(SkRect::MakeXYWH(10, 10, 80, 80), - paint); - return recorder.finishRecordingAsPicture(); -} - -sk_sp GetSampleDisplayList() { - DisplayListBuilder builder(SkRect::MakeWH(150, 100)); - builder.setColor(SK_ColorRED); - builder.drawRect(SkRect::MakeXYWH(10, 10, 80, 80)); - return builder.Build(); -} - -sk_sp GetSampleNestedPicture() { - SkPictureRecorder recorder; - recorder.beginRecording(SkRect::MakeWH(150, 100)); - SkCanvas* canvas = recorder.getRecordingCanvas(); - SkPaint paint; - for (int y = 10; y <= 60; y += 10) { - for (int x = 10; x <= 60; x += 10) { - paint.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE); - canvas->drawRect(SkRect::MakeXYWH(x, y, 80, 80), paint); - } - } - SkPictureRecorder outer_recorder; - outer_recorder.beginRecording(SkRect::MakeWH(150, 100)); - canvas = outer_recorder.getRecordingCanvas(); - canvas->drawPicture(recorder.finishRecordingAsPicture()); - return outer_recorder.finishRecordingAsPicture(); -} - -sk_sp GetSampleNestedDisplayList() { - DisplayListBuilder builder(SkRect::MakeWH(150, 100)); - for (int y = 10; y <= 60; y += 10) { - for (int x = 10; x <= 60; x += 10) { - builder.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE); - builder.drawRect(SkRect::MakeXYWH(x, y, 80, 80)); - } - } - DisplayListBuilder outer_builder(SkRect::MakeWH(150, 100)); - outer_builder.drawDisplayList(builder.Build()); - return outer_builder.Build(); -} - -sk_sp GetSampleDisplayList(int ops) { - DisplayListBuilder builder(SkRect::MakeWH(150, 100)); - for (int i = 0; i < ops; i++) { - builder.drawColor(SK_ColorRED, SkBlendMode::kSrc); - } - return builder.Build(); -} - -} // namespace TEST(RasterCache, SimpleInitialization) { flutter::RasterCache cache; @@ -492,11 +434,11 @@ TEST(RasterCache, NaiveComplexityScoringDisplayList) { // Five raster ops will not be cached auto display_list = GetSampleDisplayList(5); - unsigned int complexity_score = calculator->compute(display_list.get()); + unsigned int complexity_score = calculator->Compute(display_list.get()); ASSERT_EQ(complexity_score, 5u); ASSERT_EQ(display_list->op_count(), 5u); - ASSERT_FALSE(calculator->should_be_cached(complexity_score)); + ASSERT_FALSE(calculator->ShouldBeCached(complexity_score)); SkCanvas dummy_canvas; @@ -517,11 +459,11 @@ TEST(RasterCache, NaiveComplexityScoringDisplayList) { // Six raster ops should be cached display_list = GetSampleDisplayList(6); - complexity_score = calculator->compute(display_list.get()); + complexity_score = calculator->Compute(display_list.get()); ASSERT_EQ(complexity_score, 6u); ASSERT_EQ(display_list->op_count(), 6u); - ASSERT_TRUE(calculator->should_be_cached(complexity_score)); + ASSERT_TRUE(calculator->ShouldBeCached(complexity_score)); cache.PrepareNewFrame(); @@ -681,5 +623,4 @@ TEST(RasterCache, RasterCacheKeySameType) { } } // namespace testing - } // namespace flutter