flutter / mirrors / engine / 0ca622adee4ea338378385e0ea63f5c55d70c3ba / . / display_list / benchmarking / dl_complexity_gl.cc

// 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/benchmarking/dl_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::BatchedComplexity() { | |

// Calculate the impact of saveLayer. | |

unsigned int save_layer_complexity; | |

if (save_layer_count_ == 0) { | |

save_layer_complexity = 0; | |

} else { | |

// m = 1/5 | |

// c = 10 | |

save_layer_complexity = (save_layer_count_ + 50) * 40000; | |

} | |

unsigned int draw_text_blob_complexity; | |

if (draw_text_blob_count_ == 0) { | |

draw_text_blob_complexity = 0; | |

} else { | |

// m = 1/240 | |

// c = 0.25 | |

draw_text_blob_complexity = (draw_text_blob_count_ + 60) * 2500 / 3; | |

} | |

return save_layer_complexity + draw_text_blob_complexity; | |

} | |

void DisplayListGLComplexityCalculator::GLHelper::saveLayer( | |

const SkRect& bounds, | |

const SaveLayerOptions options, | |

const DlImageFilter* backdrop) { | |

if (IsComplex()) { | |

return; | |

} | |

if (backdrop) { | |

// Flutter does not offer this operation so this value can only ever be | |

// non-null for a frame-wide builder which is not currently evaluated for | |

// complexity. | |

AccumulateComplexity(Ceiling()); | |

} | |

save_layer_count_++; | |

} | |

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::drawDashedLine( | |

const DlPoint& p0, | |

const DlPoint& p1, | |

DlScalar on_length, | |

DlScalar off_length) { | |

// Dashing is slightly more complex than a regular drawLine, but this | |

// op is so rare it is not worth measuring the difference. | |

drawLine(ToSkPoint(p0), ToSkPoint(p1)); | |

} | |

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 (DrawStyle() == DlDrawStyle::kFill) { | |

// 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 (DrawStyle() == DlDrawStyle::kFill) { | |

// 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 (DrawStyle() == DlDrawStyle::kFill) { | |

// 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 (DrawStyle() == DlDrawStyle::kFill || | |

((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 (DrawStyle() == DlDrawStyle::kFill) { | |

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 (DrawStyle() == DlDrawStyle::kStroke) { | |

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( | |

DlCanvas::PointMode mode, | |

uint32_t count, | |

const SkPoint points[]) { | |

if (IsComplex()) { | |

return; | |

} | |

unsigned int complexity; | |

if (IsAntiAliased()) { | |

if (mode == DlCanvas::PointMode::kPoints) { | |

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 == DlCanvas::PointMode::kLines) { | |

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 == DlCanvas::PointMode::kPoints) { | |

// 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 == DlCanvas::PointMode::kLines) { | |

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 DlVertices* vertices, | |

DlBlendMode 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. | |

// 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 = (vertices->vertex_count() + 1600) * 250 / 2; | |

AccumulateComplexity(complexity); | |

} | |

void DisplayListGLComplexityCalculator::GLHelper::drawImage( | |

const sk_sp<DlImage> image, | |

const SkPoint point, | |

DlImageSampling 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, | |

bool enforce_src_edges) { | |

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 && | |

enforce_src_edges && 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<DlImage> image, | |

const SkIRect& center, | |

const SkRect& dst, | |

DlFilterMode 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<DisplayList> display_list, | |

SkScalar opacity) { | |

if (IsComplex()) { | |

return; | |

} | |

GLHelper helper(Ceiling() - CurrentComplexityScore()); | |

if (opacity < SK_Scalar1 && !display_list->can_apply_group_opacity()) { | |

auto bounds = display_list->bounds(); | |

helper.saveLayer(bounds, SaveLayerOptions::kWithAttributes, nullptr); | |

} | |

display_list->Dispatch(helper); | |

AccumulateComplexity(helper.ComplexityScore()); | |

} | |

void DisplayListGLComplexityCalculator::GLHelper::drawTextBlob( | |

const sk_sp<SkTextBlob> blob, | |

SkScalar x, | |

SkScalar y) { | |

if (IsComplex()) { | |

return; | |

} | |

// DrawTextBlob has a high fixed cost, but if we call it multiple times | |

// per frame, that fixed cost is greatly reduced per subsequent call. This | |

// is likely because there is batching being done in SkCanvas. | |

// Increment draw_text_blob_count_ and calculate the cost at the end. | |

draw_text_blob_count_++; | |

} | |

void DisplayListGLComplexityCalculator::GLHelper::drawTextFrame( | |

const std::shared_ptr<impeller::TextFrame>& text_frame, | |

SkScalar x, | |

SkScalar y) {} | |

void DisplayListGLComplexityCalculator::GLHelper::drawShadow( | |

const SkPath& path, | |

const DlColor 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 |