blob: 13253affa2e0a9a7eda97de7ac857fe08a95de91 [file] [log] [blame]
// 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_