| // 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_DISPLAY_LIST_BENCHMARKING_DL_COMPLEXITY_HELPER_H_ |
| #define FLUTTER_DISPLAY_LIST_BENCHMARKING_DL_COMPLEXITY_HELPER_H_ |
| |
| #include "flutter/display_list/benchmarking/dl_complexity.h" |
| #include "flutter/display_list/dl_blend_mode.h" |
| #include "flutter/display_list/dl_op_receiver.h" |
| #include "flutter/display_list/utils/dl_receiver_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 DlOpReceiver, |
| public virtual IgnoreClipDispatchHelper, |
| public virtual IgnoreTransformDispatchHelper { |
| public: |
| explicit ComplexityCalculatorHelper(unsigned int ceiling) |
| : ceiling_(ceiling) {} |
| |
| virtual ~ComplexityCalculatorHelper() = default; |
| |
| void setInvertColors(bool invert) override {} |
| void setStrokeCap(DlStrokeCap cap) override {} |
| void setStrokeJoin(DlStrokeJoin join) override {} |
| void setStrokeMiter(SkScalar limit) override {} |
| void setColor(DlColor color) override {} |
| void setBlendMode(DlBlendMode mode) override {} |
| void setColorSource(const DlColorSource* source) override {} |
| void setImageFilter(const DlImageFilter* filter) override {} |
| void setColorFilter(const DlColorFilter* filter) override {} |
| void setPathEffect(const DlPathEffect* effect) override {} |
| void setMaskFilter(const DlMaskFilter* filter) 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 setDrawStyle(DlDrawStyle style) override { |
| current_paint_.setDrawStyle(style); |
| } |
| |
| void setStrokeWidth(SkScalar width) override { |
| current_paint_.setStrokeWidth(width); |
| } |
| |
| void drawColor(DlColor color, DlBlendMode 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<DlImage> image, |
| const SkRect& src, |
| const SkRect& dst, |
| DlImageSampling sampling, |
| bool render_with_attributes, |
| SrcRectConstraint constraint = SrcRectConstraint::kFast) override { |
| if (IsComplex()) { |
| return; |
| } |
| ImageRect(image->dimensions(), image->isTextureBacked(), |
| render_with_attributes, constraint == SrcRectConstraint::kStrict); |
| } |
| |
| void drawAtlas(const sk_sp<DlImage> atlas, |
| const SkRSXform xform[], |
| const SkRect tex[], |
| const DlColor colors[], |
| int count, |
| DlBlendMode mode, |
| DlImageSampling 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, true); |
| } |
| } |
| |
| // 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 any draw ops where the complexity is dependent |
| // on the number of calls made. |
| unsigned int batched_complexity = BatchedComplexity(); |
| |
| // Check for overflow |
| if (Ceiling() - complexity_score_ < batched_complexity) { |
| return Ceiling(); |
| } |
| |
| return complexity_score_ + batched_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 DlDrawStyle DrawStyle() { return current_paint_.getDrawStyle(); } |
| inline bool IsComplex() { return is_complex_; } |
| inline unsigned int Ceiling() { return ceiling_; } |
| 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(); |
| std::vector<uint8_t> verbs(verb_count); |
| path.getVerbs(verbs.data(), verbs.size()); |
| |
| 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, |
| bool enforce_src_edges) = 0; |
| |
| // This calculates and returns the cost of draw calls which are batched and |
| // thus have a time cost proportional to the number of draw calls made, such |
| // as saveLayer and drawTextBlob. |
| virtual unsigned int BatchedComplexity() = 0; |
| |
| private: |
| DlPaint 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_ = false; |
| unsigned int ceiling_; |
| |
| unsigned int complexity_score_ = 0; |
| }; |
| |
| } // namespace flutter |
| |
| #endif // FLUTTER_DISPLAY_LIST_BENCHMARKING_DL_COMPLEXITY_HELPER_H_ |