diff --git a/display_list/display_list.cc b/display_list/display_list.cc index 202333459656f..d808f20c1ea82 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.cc @@ -5,9 +5,9 @@ #include #include "flutter/display_list/display_list.h" +#include "flutter/display_list/display_list_builder.h" #include "flutter/display_list/display_list_canvas_dispatcher.h" #include "flutter/display_list/display_list_ops.h" -#include "flutter/display_list/display_list_utils.h" #include "flutter/fml/trace_event.h" namespace flutter { @@ -23,7 +23,6 @@ DisplayList::DisplayList() nested_op_count_(0), unique_id_(0), bounds_({0, 0, 0, 0}), - bounds_cull_({0, 0, 0, 0}), can_apply_group_opacity_(true) {} DisplayList::DisplayList(uint8_t* ptr, @@ -31,16 +30,17 @@ DisplayList::DisplayList(uint8_t* ptr, unsigned int op_count, size_t nested_byte_count, unsigned int nested_op_count, - const SkRect& cull_rect, - bool can_apply_group_opacity) + const SkRect& bounds, + bool can_apply_group_opacity, + sk_sp rtree) : storage_(ptr), byte_count_(byte_count), op_count_(op_count), nested_byte_count_(nested_byte_count), nested_op_count_(nested_op_count), - bounds_({0, 0, -1, -1}), - bounds_cull_(cull_rect), - can_apply_group_opacity_(can_apply_group_opacity) { + bounds_(bounds), + can_apply_group_opacity_(can_apply_group_opacity), + rtree_(std::move(rtree)) { static std::atomic next_id{1}; do { unique_id_ = next_id.fetch_add(+1, std::memory_order_relaxed); @@ -52,26 +52,6 @@ DisplayList::~DisplayList() { DisposeOps(ptr, ptr + byte_count_); } -void DisplayList::ComputeBounds() { - RectBoundsAccumulator accumulator; - DisplayListBoundsCalculator calculator(accumulator, &bounds_cull_); - Dispatch(calculator); - if (calculator.is_unbounded()) { - FML_LOG(INFO) << "returning partial bounds for unbounded DisplayList"; - } - bounds_ = accumulator.bounds(); -} - -void DisplayList::ComputeRTree() { - RTreeBoundsAccumulator accumulator; - DisplayListBoundsCalculator calculator(accumulator, &bounds_cull_); - Dispatch(calculator); - if (calculator.is_unbounded()) { - FML_LOG(INFO) << "returning partial rtree for unbounded DisplayList"; - } - rtree_ = accumulator.rtree(); -} - void DisplayList::Dispatch(Dispatcher& dispatcher, uint8_t* ptr, uint8_t* end) const { diff --git a/display_list/display_list.h b/display_list/display_list.h index e30968a1f64a5..87c67d9c9f3d2 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -248,21 +248,9 @@ class DisplayList : public SkRefCnt { uint32_t unique_id() const { return unique_id_; } - const SkRect& bounds() { - if (bounds_.width() < 0.0) { - // ComputeBounds() will leave the variable with a - // non-negative width and height - ComputeBounds(); - } - return bounds_; - } + const SkRect& bounds() { return bounds_; } - sk_sp rtree() { - if (!rtree_) { - ComputeRTree(); - } - return rtree_; - } + sk_sp rtree() { return rtree_; } bool Equals(const DisplayList* other) const; bool Equals(const DisplayList& other) const { return Equals(&other); } @@ -280,8 +268,9 @@ class DisplayList : public SkRefCnt { unsigned int op_count, size_t nested_byte_count, unsigned int nested_op_count, - const SkRect& cull_rect, - bool can_apply_group_opacity); + const SkRect& bounds, + bool can_apply_group_opacity, + sk_sp rtree); struct SkFreeDeleter { void operator()(uint8_t* p) { sk_free(p); } @@ -295,15 +284,10 @@ class DisplayList : public SkRefCnt { uint32_t unique_id_; SkRect bounds_; - sk_sp rtree_; - - // Only used for drawPaint() and drawColor() - SkRect bounds_cull_; bool can_apply_group_opacity_; + sk_sp rtree_; - void ComputeBounds(); - void ComputeRTree(); void Dispatch(Dispatcher& ctx, uint8_t* ptr, uint8_t* end) const; friend class DisplayListBuilder; diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc index 7aa654730e245..b1cdb843a5434 100644 --- a/display_list/display_list_builder.cc +++ b/display_list/display_list_builder.cc @@ -6,6 +6,7 @@ #include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_blend_mode.h" +#include "flutter/display_list/display_list_canvas_dispatcher.h" #include "flutter/display_list/display_list_color_source.h" #include "flutter/display_list/display_list_ops.h" @@ -62,12 +63,18 @@ sk_sp DisplayListBuilder::Build() { bool compatible = layer_stack_.back().is_group_opacity_compatible(); return sk_sp(new DisplayList(storage_.release(), bytes, count, nested_bytes, nested_count, - cull_rect_, compatible)); + bounds(), compatible, rtree())); } -DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect) - : cull_rect_(cull_rect) { - layer_stack_.emplace_back(SkM44(), cull_rect); +DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect, + bool prepare_rtree) { + if (prepare_rtree) { + accumulator_ = std::make_unique(); + } else { + accumulator_ = std::make_unique(); + } + + layer_stack_.emplace_back(SkM44(), SkMatrix::I(), cull_rect); current_layer_ = &layer_stack_.back(); } @@ -121,6 +128,7 @@ void DisplayListBuilder::onSetBlendMode(DlBlendMode mode) { Push(0, 0, mode); UpdateCurrentOpacityCompatibility(); } + void DisplayListBuilder::onSetBlender(sk_sp blender) { // setBlender(nullptr) should be redirected to setBlendMode(SrcOver) // by the set method, if not then the following is inefficient but works @@ -137,6 +145,7 @@ void DisplayListBuilder::onSetBlender(sk_sp blender) { UpdateCurrentOpacityCompatibility(); } } + void DisplayListBuilder::onSetColorSource(const DlColorSource* source) { if (source == nullptr) { current_.setColorSource(nullptr); @@ -432,6 +441,7 @@ void DisplayListBuilder::save() { layer_stack_.emplace_back(current_layer_); current_layer_ = &layer_stack_.back(); current_layer_->has_deferred_save_op_ = true; + accumulator()->save(); } void DisplayListBuilder::restore() { @@ -442,9 +452,37 @@ void DisplayListBuilder::restore() { // Grab the current layer info before we push the restore // on the stack. LayerInfo layer_info = layer_stack_.back(); + layer_stack_.pop_back(); current_layer_ = &layer_stack_.back(); - if (layer_info.has_layer) { + bool is_unbounded = layer_info.is_unbounded(); + + // Before we pop_back we will get the current layer bounds from the + // current accumulator and adjust it as required based on the filter. + std::shared_ptr filter = layer_info.filter(); + if (filter) { + const SkRect* clip = ¤t_layer_->clip_bounds(); + if (!accumulator()->restore( + [filter = filter, matrix = getTransform()](const SkRect& input, + SkRect& output) { + SkIRect output_bounds; + bool ret = filter->map_device_bounds(input.roundOut(), matrix, + output_bounds); + output.set(output_bounds); + return ret; + }, + clip)) { + is_unbounded = true; + } + } else { + accumulator()->restore(); + } + + if (is_unbounded) { + AccumulateUnbounded(); + } + + if (layer_info.has_layer()) { if (layer_info.is_group_opacity_compatible()) { // We are now going to go back and modify the matching saveLayer // call to add the option indicating it can distribute an opacity @@ -459,15 +497,15 @@ void DisplayListBuilder::restore() { // Once built, the DisplayList records must remain read only to // ensure consistency of rendering and |Equals()| behavior. SaveLayerOp* op = reinterpret_cast( - storage_.get() + layer_info.save_layer_offset); + storage_.get() + layer_info.save_layer_offset()); op->options = op->options.with_can_distribute_opacity(); } } else { // For regular save() ops there was no protecting layer so we have to // accumulate the values into the enclosing layer. - if (layer_info.cannot_inherit_opacity) { + if (layer_info.cannot_inherit_opacity()) { current_layer_->mark_incompatible(); - } else if (layer_info.has_compatible_op) { + } else if (layer_info.has_compatible_op()) { current_layer_->add_compatible_op(); } } @@ -494,7 +532,22 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, : Push(0, 1, options); } CheckLayerOpacityCompatibility(options.renders_with_attributes()); - layer_stack_.emplace_back(current_layer_, save_layer_offset, true); + + if (options.renders_with_attributes()) { + // The actual flood of the outer layer clip will occur after the + // (eventual) corresponding restore is called, but rather than + // remember this information in the LayerInfo until the restore + // method is processed, we just mark the unbounded state up front. + if (!paint_nops_on_transparency()) { + // We will fill the clip of the outer layer when we restore + AccumulateUnbounded(); + } + layer_stack_.emplace_back(current_layer_, save_layer_offset, true, + current_.getImageFilter()); + } else { + layer_stack_.emplace_back(current_layer_, save_layer_offset, true, nullptr); + } + accumulator()->save(); current_layer_ = &layer_stack_.back(); if (options.renders_with_attributes()) { // |current_opacity_compatibility_| does not take an ImageFilter into @@ -506,6 +559,17 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, UpdateLayerOpacityCompatibility(false); } } + + // Even though Skia claims that the bounds are only a hint, they actually + // use them as the temporary layer bounds during rendering the layer, so + // we set them as if a clip operation were performed. + if (bounds) { + intersect(*bounds); + } + if (backdrop) { + // A backdrop will affect up to the entire surface, bounded by the clip + AccumulateUnbounded(); + } } void DisplayListBuilder::saveLayer(const SkRect* bounds, const DlPaint* paint, @@ -524,7 +588,8 @@ void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) { (tx != 0.0 || ty != 0.0)) { checkForDeferredSave(); Push(0, 1, tx, ty); - current_layer_->matrix.preTranslate(tx, ty); + current_layer_->matrix().preTranslate(tx, ty); + current_layer_->update_matrix33(); } } void DisplayListBuilder::scale(SkScalar sx, SkScalar sy) { @@ -532,14 +597,16 @@ void DisplayListBuilder::scale(SkScalar sx, SkScalar sy) { (sx != 1.0 || sy != 1.0)) { checkForDeferredSave(); Push(0, 1, sx, sy); - current_layer_->matrix.preScale(sx, sy); + current_layer_->matrix().preScale(sx, sy); + current_layer_->update_matrix33(); } } void DisplayListBuilder::rotate(SkScalar degrees) { if (SkScalarMod(degrees, 360.0) != 0.0) { checkForDeferredSave(); Push(0, 1, degrees); - current_layer_->matrix.preConcat(SkMatrix::RotateDeg(degrees)); + current_layer_->matrix().preConcat(SkMatrix::RotateDeg(degrees)); + current_layer_->update_matrix33(); } } void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) { @@ -547,7 +614,8 @@ void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) { (sx != 0.0 || sy != 0.0)) { checkForDeferredSave(); Push(0, 1, sx, sy); - current_layer_->matrix.preConcat(SkMatrix::Skew(sx, sy)); + current_layer_->matrix().preConcat(SkMatrix::Skew(sx, sy)); + current_layer_->update_matrix33(); } } @@ -566,10 +634,11 @@ void DisplayListBuilder::transform2DAffine( Push(0, 1, mxx, mxy, mxt, myx, myy, myt); - current_layer_->matrix.preConcat(SkM44(mxx, mxy, 0, mxt, - myx, myy, 0, myt, - 0, 0, 1, 0, - 0, 0, 0, 1)); + current_layer_->matrix().preConcat(SkM44(mxx, mxy, 0, mxt, + myx, myy, 0, myt, + 0, 0, 1, 0, + 0, 0, 0, 1)); + current_layer_->update_matrix33(); } } // full 4x4 transform in row major order @@ -594,17 +663,19 @@ void DisplayListBuilder::transformFullPerspective( myx, myy, myz, myt, mzx, mzy, mzz, mzt, mwx, mwy, mwz, mwt); - current_layer_->matrix.preConcat(SkM44(mxx, mxy, mxz, mxt, - myx, myy, myz, myt, - mzx, mzy, mzz, mzt, - mwx, mwy, mwz, mwt)); + current_layer_->matrix().preConcat(SkM44(mxx, mxy, mxz, mxt, + myx, myy, myz, myt, + mzx, mzy, mzz, mzt, + mwx, mwy, mwz, mwt)); + current_layer_->update_matrix33(); } } // clang-format on void DisplayListBuilder::transformReset() { checkForDeferredSave(); Push(0, 0); - current_layer_->matrix.setIdentity(); + current_layer_->matrix().setIdentity(); + current_layer_->update_matrix33(); } void DisplayListBuilder::transform(const SkMatrix* matrix) { if (matrix != nullptr) { @@ -695,15 +766,15 @@ void DisplayListBuilder::clipPath(const SkPath& path, } void DisplayListBuilder::intersect(const SkRect& rect) { SkRect dev_clip_bounds = getTransform().mapRect(rect); - if (!current_layer_->clip_bounds.intersect(dev_clip_bounds)) { - current_layer_->clip_bounds.setEmpty(); + if (!current_layer_->clip_bounds().intersect(dev_clip_bounds)) { + current_layer_->clip_bounds().setEmpty(); } } SkRect DisplayListBuilder::getLocalClipBounds() { SkM44 inverse; - if (current_layer_->matrix.invert(&inverse)) { + if (current_layer_->matrix().invert(&inverse)) { SkRect dev_bounds; - current_layer_->clip_bounds.roundOut(&dev_bounds); + current_layer_->clip_bounds().roundOut(&dev_bounds); return inverse.asM33().mapRect(dev_bounds); } return kMaxCullRect; @@ -721,12 +792,13 @@ bool DisplayListBuilder::quickReject(const SkRect& bounds) const { } SkRect dev_bounds; matrix.mapRect(bounds).roundOut(&dev_bounds); - return !current_layer_->clip_bounds.intersects(dev_bounds); + return !current_layer_->clip_bounds().intersects(dev_bounds); } void DisplayListBuilder::drawPaint() { Push(0, 1); CheckLayerOpacityCompatibility(); + AccumulateUnbounded(); } void DisplayListBuilder::drawPaint(const DlPaint& paint) { setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawPaintFlags); @@ -735,10 +807,16 @@ void DisplayListBuilder::drawPaint(const DlPaint& paint) { void DisplayListBuilder::drawColor(DlColor color, DlBlendMode mode) { Push(0, 1, color, mode); CheckLayerOpacityCompatibility(mode); + AccumulateUnbounded(); } void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) { Push(0, 1, p0, p1); CheckLayerOpacityCompatibility(); + SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); + DisplayListAttributeFlags flags = + (bounds.width() > 0.0f && bounds.height() > 0.0f) ? kDrawLineFlags + : kDrawHVLineFlags; + AccumulateOpBounds(bounds, flags); } void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1, @@ -749,6 +827,7 @@ void DisplayListBuilder::drawLine(const SkPoint& p0, void DisplayListBuilder::drawRect(const SkRect& rect) { Push(0, 1, rect); CheckLayerOpacityCompatibility(); + AccumulateOpBounds(rect, kDrawRectFlags); } void DisplayListBuilder::drawRect(const SkRect& rect, const DlPaint& paint) { setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawRectFlags); @@ -757,6 +836,7 @@ void DisplayListBuilder::drawRect(const SkRect& rect, const DlPaint& paint) { void DisplayListBuilder::drawOval(const SkRect& bounds) { Push(0, 1, bounds); CheckLayerOpacityCompatibility(); + AccumulateOpBounds(bounds, kDrawOvalFlags); } void DisplayListBuilder::drawOval(const SkRect& bounds, const DlPaint& paint) { setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawOvalFlags); @@ -765,6 +845,9 @@ void DisplayListBuilder::drawOval(const SkRect& bounds, const DlPaint& paint) { void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius) { Push(0, 1, center, radius); CheckLayerOpacityCompatibility(); + AccumulateOpBounds(SkRect::MakeLTRB(center.fX - radius, center.fY - radius, + center.fX + radius, center.fY + radius), + kDrawCircleFlags); } void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius, @@ -780,6 +863,7 @@ void DisplayListBuilder::drawRRect(const SkRRect& rrect) { } else { Push(0, 1, rrect); CheckLayerOpacityCompatibility(); + AccumulateOpBounds(rrect.getBounds(), kDrawRRectFlags); } } void DisplayListBuilder::drawRRect(const SkRRect& rrect, const DlPaint& paint) { @@ -790,6 +874,7 @@ void DisplayListBuilder::drawDRRect(const SkRRect& outer, const SkRRect& inner) { Push(0, 1, outer, inner); CheckLayerOpacityCompatibility(); + AccumulateOpBounds(outer.getBounds(), kDrawDRRectFlags); } void DisplayListBuilder::drawDRRect(const SkRRect& outer, const SkRRect& inner, @@ -800,6 +885,11 @@ void DisplayListBuilder::drawDRRect(const SkRRect& outer, void DisplayListBuilder::drawPath(const SkPath& path) { Push(0, 1, path); CheckLayerOpacityHairlineCompatibility(); + if (path.isInverseFillType()) { + AccumulateUnbounded(); + } else { + AccumulateOpBounds(path.getBounds(), kDrawPathFlags); + } } void DisplayListBuilder::drawPath(const SkPath& path, const DlPaint& paint) { setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawPathFlags); @@ -816,6 +906,13 @@ void DisplayListBuilder::drawArc(const SkRect& bounds, } else { CheckLayerOpacityCompatibility(); } + // This could be tighter if we compute where the start and end + // angles are and then also consider the quadrants swept and + // the center if specified. + AccumulateOpBounds(bounds, + useCenter // + ? kDrawArcWithCenterFlags + : kDrawArcNoCenterFlags); } void DisplayListBuilder::drawArc(const SkRect& bounds, SkScalar start, @@ -829,18 +926,30 @@ void DisplayListBuilder::drawArc(const SkRect& bounds, void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode, uint32_t count, const SkPoint pts[]) { + if (count == 0) { + return; + } + void* data_ptr; FML_DCHECK(count < kMaxDrawPointsCount); int bytes = count * sizeof(SkPoint); + RectBoundsAccumulator ptBounds; + for (size_t i = 0; i < count; i++) { + ptBounds.accumulate(pts[i]); + } + SkRect point_bounds = ptBounds.bounds(); switch (mode) { case SkCanvas::PointMode::kPoints_PointMode: data_ptr = Push(bytes, 1, count); + AccumulateOpBounds(point_bounds, kDrawPointsAsPointsFlags); break; case SkCanvas::PointMode::kLines_PointMode: data_ptr = Push(bytes, 1, count); + AccumulateOpBounds(point_bounds, kDrawPointsAsLinesFlags); break; case SkCanvas::PointMode::kPolygon_PointMode: data_ptr = Push(bytes, 1, count); + AccumulateOpBounds(point_bounds, kDrawPointsAsPolygonFlags); break; default: FML_DCHECK(false); @@ -884,6 +993,7 @@ void DisplayListBuilder::drawSkVertices(const sk_sp vertices, // Although, examination of the |mode| might find some predictable // cases. UpdateLayerOpacityCompatibility(false); + AccumulateOpBounds(vertices->bounds(), kDrawVerticesFlags); } void DisplayListBuilder::drawVertices(const DlVertices* vertices, DlBlendMode mode) { @@ -894,6 +1004,7 @@ void DisplayListBuilder::drawVertices(const DlVertices* vertices, // Although, examination of the |mode| might find some predictable // cases. UpdateLayerOpacityCompatibility(false); + AccumulateOpBounds(vertices->bounds(), kDrawVerticesFlags); } void DisplayListBuilder::drawVertices(const DlVertices* vertices, DlBlendMode mode, @@ -910,6 +1021,12 @@ void DisplayListBuilder::drawImage(const sk_sp image, ? Push(0, 1, image, point, sampling) : Push(0, 1, image, point, sampling); CheckLayerOpacityCompatibility(render_with_attributes); + SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, // + image->width(), image->height()); + DisplayListAttributeFlags flags = render_with_attributes // + ? kDrawImageWithPaintFlags + : kDrawImageFlags; + AccumulateOpBounds(bounds, flags); } void DisplayListBuilder::drawImage(const sk_sp& image, const SkPoint point, @@ -932,6 +1049,10 @@ void DisplayListBuilder::drawImageRect(const sk_sp image, Push(0, 1, image, src, dst, sampling, render_with_attributes, constraint); CheckLayerOpacityCompatibility(render_with_attributes); + DisplayListAttributeFlags flags = render_with_attributes + ? kDrawImageRectWithPaintFlags + : kDrawImageRectFlags; + AccumulateOpBounds(dst, flags); } void DisplayListBuilder::drawImageRect(const sk_sp& image, const SkRect& src, @@ -956,6 +1077,10 @@ void DisplayListBuilder::drawImageNine(const sk_sp image, ? Push(0, 1, image, center, dst, filter) : Push(0, 1, image, center, dst, filter); CheckLayerOpacityCompatibility(render_with_attributes); + DisplayListAttributeFlags flags = render_with_attributes + ? kDrawImageNineWithPaintFlags + : kDrawImageNineFlags; + AccumulateOpBounds(dst, flags); } void DisplayListBuilder::drawImageNine(const sk_sp& image, const SkIRect& center, @@ -991,6 +1116,10 @@ void DisplayListBuilder::drawImageLattice(const sk_sp image, CopyV(pod, lattice.fXDivs, x_div_count, lattice.fYDivs, y_div_count, lattice.fColors, cell_count, lattice.fRectTypes, cell_count); CheckLayerOpacityCompatibility(render_with_attributes); + DisplayListAttributeFlags flags = render_with_attributes + ? kDrawImageLatticeWithPaintFlags + : kDrawImageLatticeFlags; + AccumulateOpBounds(dst, flags); } void DisplayListBuilder::drawAtlas(const sk_sp atlas, const SkRSXform xform[], @@ -1029,6 +1158,22 @@ void DisplayListBuilder::drawAtlas(const sk_sp atlas, // on it to distribute the opacity without overlap without checking all // of the transforms and texture rectangles. UpdateLayerOpacityCompatibility(false); + + SkPoint quad[4]; + RectBoundsAccumulator atlasBounds; + for (int i = 0; i < count; i++) { + const SkRect& src = tex[i]; + xform[i].toQuad(src.width(), src.height(), quad); + for (int j = 0; j < 4; j++) { + atlasBounds.accumulate(quad[j]); + } + } + if (atlasBounds.is_not_empty()) { + DisplayListAttributeFlags flags = render_with_attributes // + ? kDrawAtlasWithPaintFlags + : kDrawAtlasFlags; + AccumulateOpBounds(atlasBounds.bounds(), flags); + } } void DisplayListBuilder::drawAtlas(const sk_sp& atlas, const SkRSXform xform[], @@ -1053,6 +1198,18 @@ void DisplayListBuilder::drawAtlas(const sk_sp& atlas, void DisplayListBuilder::drawPicture(const sk_sp picture, const SkMatrix* matrix, bool render_with_attributes) { + // TODO(flar) cull rect really cannot be trusted in general, but it will + // work for SkPictures generated from our own PictureRecorder or any + // picture captured with an SkRTreeFactory or accurate bounds estimate. + SkRect bounds = picture->cullRect(); + if (matrix) { + matrix->mapRect(&bounds); + } + DisplayListAttributeFlags flags = render_with_attributes // + ? kDrawPictureWithPaintFlags + : kDrawPictureFlags; + AccumulateOpBounds(bounds, flags); + matrix // ? Push(0, 1, picture, *matrix, render_with_attributes) @@ -1069,6 +1226,25 @@ void DisplayListBuilder::drawPicture(const sk_sp picture, } void DisplayListBuilder::drawDisplayList( const sk_sp display_list) { + const SkRect bounds = display_list->bounds(); + switch (accumulator()->type()) { + case BoundsAccumulatorType::kRect: + AccumulateOpBounds(bounds, kDrawDisplayListFlags); + break; + case BoundsAccumulatorType::kRTree: + auto rtree = display_list->rtree(); + if (rtree) { + std::list rects = rtree->searchNonOverlappingDrawnRects(bounds); + for (const SkRect& rect : rects) { + // TODO (https://github.com/flutter/flutter/issues/114919): Attributes + // are not necessarily `kDrawDisplayListFlags`. + AccumulateOpBounds(rect, kDrawDisplayListFlags); + } + } else { + AccumulateOpBounds(bounds, kDrawDisplayListFlags); + } + break; + } Push(0, 1, display_list); // The non-nested op count accumulated in the |Push| method will include // this call to |drawDisplayList| for non-nested op count metrics. @@ -1083,6 +1259,7 @@ void DisplayListBuilder::drawDisplayList( void DisplayListBuilder::drawTextBlob(const sk_sp blob, SkScalar x, SkScalar y) { + AccumulateOpBounds(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags); Push(0, 1, blob, x, y); CheckLayerOpacityCompatibility(); } @@ -1098,10 +1275,178 @@ void DisplayListBuilder::drawShadow(const SkPath& path, const SkScalar elevation, bool transparent_occluder, SkScalar dpr) { + SkRect shadow_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds( + path, elevation, dpr, getTransform()); + AccumulateOpBounds(shadow_bounds, kDrawShadowFlags); + transparent_occluder // ? Push(0, 1, path, color, elevation, dpr) : Push(0, 1, path, color, elevation, dpr); UpdateLayerOpacityCompatibility(false); } +bool DisplayListBuilder::ComputeFilteredBounds(SkRect& bounds, + const DlImageFilter* filter) { + if (filter) { + if (!filter->map_local_bounds(bounds, bounds)) { + return false; + } + } + return true; +} + +bool DisplayListBuilder::AdjustBoundsForPaint(SkRect& bounds, + DisplayListAttributeFlags flags) { + if (flags.ignores_paint()) { + return true; + } + + if (flags.is_geometric()) { + // Path effect occurs before stroking... + DisplayListSpecialGeometryFlags special_flags = + flags.WithPathEffect(current_.getPathEffectPtr()); + if (current_.getPathEffect()) { + auto effect_bounds = current_.getPathEffect()->effect_bounds(bounds); + if (!effect_bounds.has_value()) { + return false; + } + bounds = effect_bounds.value(); + } + + if (flags.is_stroked(current_.getDrawStyle())) { + // Determine the max multiplier to the stroke width first. + SkScalar pad = 1.0f; + if (current_.getStrokeJoin() == DlStrokeJoin::kMiter && + special_flags.may_have_acute_joins()) { + pad = std::max(pad, current_.getStrokeMiter()); + } + if (current_.getStrokeCap() == DlStrokeCap::kSquare && + special_flags.may_have_diagonal_caps()) { + pad = std::max(pad, SK_ScalarSqrt2); + } + SkScalar min_stroke_width = 0.01; + pad *= std::max(getStrokeWidth() * 0.5f, min_stroke_width); + bounds.outset(pad, pad); + } + } + + if (flags.applies_mask_filter()) { + if (current_.getMaskFilter()) { + const DlBlurMaskFilter* blur_filter = current_.getMaskFilter()->asBlur(); + if (blur_filter) { + SkScalar mask_sigma_pad = blur_filter->sigma() * 3.0; + bounds.outset(mask_sigma_pad, mask_sigma_pad); + } else { + SkPaint p; + p.setMaskFilter(current_.getMaskFilter()->skia_object()); + if (!p.canComputeFastBounds()) { + return false; + } + bounds = p.computeFastBounds(bounds, &bounds); + } + } + } + + if (flags.applies_image_filter()) { + return ComputeFilteredBounds(bounds, current_.getImageFilter().get()); + } + + return true; +} + +void DisplayListBuilder::AccumulateUnbounded() { + accumulator()->accumulate(current_layer_->clip_bounds()); +} + +void DisplayListBuilder::AccumulateOpBounds(SkRect& bounds, + DisplayListAttributeFlags flags) { + if (AdjustBoundsForPaint(bounds, flags)) { + AccumulateBounds(bounds); + } else { + AccumulateUnbounded(); + } +} +void DisplayListBuilder::AccumulateBounds(SkRect& bounds) { + getTransform().mapRect(&bounds); + if (bounds.intersect(current_layer_->clip_bounds())) { + accumulator()->accumulate(bounds); + } +} + +bool DisplayListBuilder::paint_nops_on_transparency() { + // SkImageFilter::canComputeFastBounds tests for transparency behavior + // This test assumes that the blend mode checked down below will + // NOP on transparent black. + if (current_.getImageFilter() && + current_.getImageFilter()->modifies_transparent_black()) { + return false; + } + + // We filter the transparent black that is used for the background of a + // saveLayer and make sure it returns transparent black. If it does, then + // the color filter will leave all area surrounding the contents of the + // save layer untouched out to the edge of the output surface. + // This test assumes that the blend mode checked down below will + // NOP on transparent black. + if (current_.getColorFilter() && + current_.getColorFilter()->modifies_transparent_black()) { + return false; + } + + if (!getBlendMode()) { + return false; // can we query other blenders for this? + } + // Unusual blendmodes require us to process a saved layer + // even with operations outisde the clip. + // For example, DstIn is used by masking layers. + // https://code.google.com/p/skia/issues/detail?id=1291 + // https://crbug.com/401593 + switch (getBlendMode().value()) { + // For each of the following transfer modes, if the source + // alpha is zero (our transparent black), the resulting + // blended pixel is not necessarily equal to the original + // destination pixel. + // Mathematically, any time in the following equations where + // the result is not d assuming source is 0 + case DlBlendMode::kClear: // r = 0 + case DlBlendMode::kSrc: // r = s + case DlBlendMode::kSrcIn: // r = s * da + case DlBlendMode::kDstIn: // r = d * sa + case DlBlendMode::kSrcOut: // r = s * (1-da) + case DlBlendMode::kDstATop: // r = d*sa + s*(1-da) + case DlBlendMode::kModulate: // r = s*d + return false; + break; + + // And in these equations, the result must be d if the + // source is 0 + case DlBlendMode::kDst: // r = d + case DlBlendMode::kSrcOver: // r = s + (1-sa)*d + case DlBlendMode::kDstOver: // r = d + (1-da)*s + case DlBlendMode::kDstOut: // r = d * (1-sa) + case DlBlendMode::kSrcATop: // r = s*da + d*(1-sa) + case DlBlendMode::kXor: // r = s*(1-da) + d*(1-sa) + case DlBlendMode::kPlus: // r = min(s + d, 1) + case DlBlendMode::kScreen: // r = s + d - s*d + case DlBlendMode::kOverlay: // multiply or screen, depending on dest + case DlBlendMode::kDarken: // rc = s + d - max(s*da, d*sa), + // ra = kSrcOver + case DlBlendMode::kLighten: // rc = s + d - min(s*da, d*sa), + // ra = kSrcOver + case DlBlendMode::kColorDodge: // brighten destination to reflect source + case DlBlendMode::kColorBurn: // darken destination to reflect source + case DlBlendMode::kHardLight: // multiply or screen, depending on source + case DlBlendMode::kSoftLight: // lighten or darken, depending on source + case DlBlendMode::kDifference: // rc = s + d - 2*(min(s*da, d*sa)), + // ra = kSrcOver + case DlBlendMode::kExclusion: // rc = s + d - two(s*d), ra = kSrcOver + case DlBlendMode::kMultiply: // r = s*(1-da) + d*(1-sa) + s*d + case DlBlendMode::kHue: // ra = kSrcOver + case DlBlendMode::kSaturation: // ra = kSrcOver + case DlBlendMode::kColor: // ra = kSrcOver + case DlBlendMode::kLuminosity: // ra = kSrcOver + return true; + break; + } +} } // namespace flutter diff --git a/display_list/display_list_builder.h b/display_list/display_list_builder.h index 2f2cd863bd96c..b16e8b37ea012 100644 --- a/display_list/display_list_builder.h +++ b/display_list/display_list_builder.h @@ -14,6 +14,7 @@ #include "flutter/display_list/display_list_paint.h" #include "flutter/display_list/display_list_path_effect.h" #include "flutter/display_list/display_list_sampling_options.h" +#include "flutter/display_list/display_list_utils.h" #include "flutter/display_list/types.h" #include "flutter/fml/macros.h" @@ -31,7 +32,11 @@ class DisplayListBuilder final : public virtual Dispatcher, static constexpr SkRect kMaxCullRect = SkRect::MakeLTRB(-1E9F, -1E9F, 1E9F, 1E9F); - explicit DisplayListBuilder(const SkRect& cull_rect = kMaxCullRect); + explicit DisplayListBuilder(bool prepare_rtree) + : DisplayListBuilder(kMaxCullRect, prepare_rtree) {} + + explicit DisplayListBuilder(const SkRect& cull_rect = kMaxCullRect, + bool prepare_rtree = false); ~DisplayListBuilder(); @@ -205,11 +210,11 @@ class DisplayListBuilder final : public virtual Dispatcher, /// Returns the 4x4 full perspective transform representing all transform /// operations executed so far in this DisplayList within the enclosing /// save stack. - SkM44 getTransformFullPerspective() { return current_layer_->matrix; } + SkM44 getTransformFullPerspective() const { return current_layer_->matrix(); } /// Returns the 3x3 partial perspective transform representing all transform /// operations executed so far in this DisplayList within the enclosing /// save stack. - SkMatrix getTransform() const { return current_layer_->matrix.asM33(); } + SkMatrix getTransform() const { return current_layer_->matrix33(); } void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) override; void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool is_aa) override; @@ -218,7 +223,7 @@ class DisplayListBuilder final : public virtual Dispatcher, /// Conservative estimate of the bounds of all outstanding clip operations /// measured in the coordinate space within which this DisplayList will /// be rendered. - SkRect getDestinationClipBounds() { return current_layer_->clip_bounds; } + SkRect getDestinationClipBounds() { return current_layer_->clip_bounds(); } /// Conservative estimate of the bounds of all outstanding clip operations /// transformed into the local coordinate space in which currently /// recorded rendering operations are interpreted. @@ -366,8 +371,6 @@ class DisplayListBuilder final : public virtual Dispatcher, size_t nested_bytes_ = 0; int nested_op_count_ = 0; - SkRect cull_rect_; - template void* Push(size_t extra, int op_inc, Args&&... args); @@ -381,25 +384,34 @@ class DisplayListBuilder final : public virtual Dispatcher, return SkScalarIsFinite(sigma) && sigma > 0.0; } - struct LayerInfo { - LayerInfo(const SkM44& matrix, - const SkRect& clip_bounds, - size_t save_layer_offset = 0, - bool has_layer = false) - : save_layer_offset(save_layer_offset), - has_layer(has_layer), - cannot_inherit_opacity(false), - has_compatible_op(false), - matrix(matrix), - clip_bounds(clip_bounds) {} - - LayerInfo(const LayerInfo* current_layer, - size_t save_layer_offset = 0, - bool has_layer = false) - : LayerInfo(current_layer->matrix, - current_layer->clip_bounds, + class LayerInfo { + public: + explicit LayerInfo(const SkM44& matrix, + const SkMatrix& matrix33, + const SkRect& clip_bounds, + size_t save_layer_offset = 0, + bool has_layer = false, + std::shared_ptr filter = nullptr) + : save_layer_offset_(save_layer_offset), + has_layer_(has_layer), + cannot_inherit_opacity_(false), + has_compatible_op_(false), + matrix_(matrix), + matrix33_(matrix33), + clip_bounds_(clip_bounds), + filter_(filter), + is_unbounded_(false) {} + + explicit LayerInfo(const LayerInfo* current_layer, + size_t save_layer_offset = 0, + bool has_layer = false, + std::shared_ptr filter = nullptr) + : LayerInfo(current_layer->matrix_, + current_layer->matrix33_, + current_layer->clip_bounds_, save_layer_offset, - has_layer) {} + has_layer, + filter) {} // The offset into the memory buffer where the saveLayer DLOp record // for this saveLayer() call is placed. This may be needed if the @@ -407,20 +419,22 @@ class DisplayListBuilder final : public virtual Dispatcher, // the records inside the saveLayer that may impact how the saveLayer // is handled (e.g., |cannot_inherit_opacity| == false). // This offset is only valid if |has_layer| is true. - size_t save_layer_offset; - - bool has_deferred_save_op_ = false; + size_t save_layer_offset() const { return save_layer_offset_; } - bool has_layer; - bool cannot_inherit_opacity; - bool has_compatible_op; + bool has_layer() const { return has_layer_; } + bool cannot_inherit_opacity() const { return cannot_inherit_opacity_; } + bool has_compatible_op() const { return cannot_inherit_opacity_; } + SkM44& matrix() { return matrix_; } + SkMatrix& matrix33() { return matrix33_; } + SkRect& clip_bounds() { return clip_bounds_; } - SkM44 matrix; - SkRect clip_bounds; + void update_matrix33() { matrix33_ = matrix_.asM33(); } - bool is_group_opacity_compatible() const { return !cannot_inherit_opacity; } + bool is_group_opacity_compatible() const { + return !cannot_inherit_opacity_; + } - void mark_incompatible() { cannot_inherit_opacity = true; } + void mark_incompatible() { cannot_inherit_opacity_ = true; } // For now this only allows a single compatible op to mark the // layer as being compatible with group opacity. If we start @@ -428,18 +442,64 @@ class DisplayListBuilder final : public virtual Dispatcher, // can upgrade this to checking for overlapping ops. // See https://github.com/flutter/flutter/issues/93899 void add_compatible_op() { - if (!cannot_inherit_opacity) { - if (has_compatible_op) { - cannot_inherit_opacity = true; + if (!cannot_inherit_opacity_) { + if (has_compatible_op_) { + cannot_inherit_opacity_ = true; } else { - has_compatible_op = true; + has_compatible_op_ = true; } } } + + // The filter to apply to the layer bounds when it is restored + std::shared_ptr filter() { return filter_; } + + // is_unbounded should be set to true if we ever encounter an operation + // on a layer that either is unrestricted (|drawColor| or |drawPaint|) + // or cannot compute its bounds (some effects and filters) and there + // was no outstanding clip op at the time. + // When the layer is restored, the outer layer may then process this + // unbounded state by accumulating its own clip or transferring the + // unbounded state to its own outer layer. + // Typically the DisplayList will have been constructed with a cull + // rect which will act as a default clip for the outermost layer and + // the unbounded state of all sub layers will eventually be caught by + // that cull rect so that the overall unbounded state of the entire + // DisplayList will never be true. + // + // SkPicture treats these same conditions as a Nop (they accumulate + // the SkPicture cull rect, but if it was not specified then it is an + // empty Rect and so has no effect on the bounds). + // + // Flutter is unlikely to ever run into this as the Dart mechanisms + // all supply a non-null cull rect for all Dart Picture objects, + // even if that cull rect is kGiantRect. + void set_unbounded() { is_unbounded_ = true; } + + // |is_unbounded| should be called after |getLayerBounds| in case + // a problem was found during the computation of those bounds, + // the layer will have one last chance to flag an unbounded state. + bool is_unbounded() const { return is_unbounded_; } + + private: + size_t save_layer_offset_; + bool has_layer_; + bool cannot_inherit_opacity_; + bool has_compatible_op_; + SkM44 matrix_; + SkMatrix matrix33_; + SkRect clip_bounds_; + std::shared_ptr filter_; + bool is_unbounded_; + bool has_deferred_save_op_ = false; + + friend class DisplayListBuilder; }; std::vector layer_stack_; LayerInfo* current_layer_; + std::unique_ptr accumulator_; + BoundsAccumulator* accumulator() { return accumulator_.get(); } // This flag indicates whether or not the current rendering attributes // are compatible with rendering ops applying an inherited opacity. @@ -514,6 +574,69 @@ class DisplayListBuilder final : public virtual Dispatcher, void onSetPathEffect(const DlPathEffect* effect); void onSetMaskFilter(const DlMaskFilter* filter); + // The DisplayList had an unbounded call with no cull rect or clip + // to contain it. Should only be called after the stream is fully + // built. + // Unbounded operations are calls like |drawColor| which are defined + // to flood the entire surface, or calls that relied on a rendering + // attribute which is unable to compute bounds (should be rare). + // In those cases the bounds will represent only the accumulation + // of the bounded calls and this flag will be set to indicate that + // condition. + bool is_unbounded() const { + FML_DCHECK(layer_stack_.size() == 1); + return layer_stack_.front().is_unbounded(); + } + + SkRect bounds() const { + FML_DCHECK(layer_stack_.size() == 1); + if (is_unbounded()) { + FML_LOG(INFO) << "returning partial bounds for unbounded DisplayList"; + } + + return accumulator_->bounds(); + } + + sk_sp rtree() { + FML_DCHECK(layer_stack_.size() == 1); + if (is_unbounded()) { + FML_LOG(INFO) << "returning partial rtree for unbounded DisplayList"; + } + + return accumulator_->rtree(); + } + + bool paint_nops_on_transparency(); + + // Computes the bounds of an operation adjusted for a given ImageFilter + static bool ComputeFilteredBounds(SkRect& bounds, + const DlImageFilter* filter); + + // Adjusts the indicated bounds for the given flags and returns true if + // the calculation was possible, or false if it could not be estimated. + bool AdjustBoundsForPaint(SkRect& bounds, DisplayListAttributeFlags flags); + + // Records the fact that we encountered an op that either could not + // estimate its bounds or that fills all of the destination space. + void AccumulateUnbounded(); + + // Records the bounds for an op after modifying them according to the + // supplied attribute flags and transforming by the current matrix. + void AccumulateOpBounds(const SkRect& bounds, + DisplayListAttributeFlags flags) { + SkRect safe_bounds = bounds; + AccumulateOpBounds(safe_bounds, flags); + } + + // Records the bounds for an op after modifying them according to the + // supplied attribute flags and transforming by the current matrix + // and clipping against the current clip. + void AccumulateOpBounds(SkRect& bounds, DisplayListAttributeFlags flags); + + // Records the given bounds after transforming by the current matrix + // and clipping against the current clip. + void AccumulateBounds(SkRect& bounds); + DlPaint current_; // If |current_blender_| is set then ignore |current_.getBlendMode()| sk_sp current_blender_; diff --git a/display_list/display_list_builder_benchmarks.cc b/display_list/display_list_builder_benchmarks.cc index 129eac3abd497..57270578ca90f 100644 --- a/display_list/display_list_builder_benchmarks.cc +++ b/display_list/display_list_builder_benchmarks.cc @@ -46,12 +46,18 @@ static void Complete(DisplayListBuilder& builder, } } +bool NeedPrepareRTree(DisplayListBuilderBenchmarkType type) { + return type == DisplayListBuilderBenchmarkType::kRtree || + type == DisplayListBuilderBenchmarkType::kBoundsAndRtree; +} + } // namespace static void BM_DisplayListBuilderDefault(benchmark::State& state, DisplayListBuilderBenchmarkType type) { + bool prepare_rtree = NeedPrepareRTree(type); while (state.KeepRunning()) { - DisplayListBuilder builder; + DisplayListBuilder builder(prepare_rtree); InvokeAllRenderingOps(builder); Complete(builder, type); } @@ -60,8 +66,9 @@ static void BM_DisplayListBuilderDefault(benchmark::State& state, static void BM_DisplayListBuilderWithScaleAndTranslate( benchmark::State& state, DisplayListBuilderBenchmarkType type) { + bool prepare_rtree = NeedPrepareRTree(type); while (state.KeepRunning()) { - DisplayListBuilder builder; + DisplayListBuilder builder(prepare_rtree); builder.scale(3.5, 3.5); builder.translate(10.3, 6.9); InvokeAllRenderingOps(builder); @@ -72,8 +79,9 @@ static void BM_DisplayListBuilderWithScaleAndTranslate( static void BM_DisplayListBuilderWithPerspective( benchmark::State& state, DisplayListBuilderBenchmarkType type) { + bool prepare_rtree = NeedPrepareRTree(type); while (state.KeepRunning()) { - DisplayListBuilder builder; + DisplayListBuilder builder(prepare_rtree); builder.transformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29, 0, 0, 0, 12); InvokeAllRenderingOps(builder); @@ -85,8 +93,9 @@ static void BM_DisplayListBuilderWithClipRect( benchmark::State& state, DisplayListBuilderBenchmarkType type) { SkRect clip_bounds = SkRect::MakeLTRB(6.5, 7.3, 90.2, 85.7); + bool prepare_rtree = NeedPrepareRTree(type); while (state.KeepRunning()) { - DisplayListBuilder builder; + DisplayListBuilder builder(prepare_rtree); builder.clipRect(clip_bounds, SkClipOp::kIntersect, true); InvokeAllRenderingOps(builder); Complete(builder, type); @@ -96,8 +105,9 @@ static void BM_DisplayListBuilderWithClipRect( static void BM_DisplayListBuilderWithSaveLayer( benchmark::State& state, DisplayListBuilderBenchmarkType type) { + bool prepare_rtree = NeedPrepareRTree(type); while (state.KeepRunning()) { - DisplayListBuilder builder; + DisplayListBuilder builder(prepare_rtree); for (auto& group : allRenderingOps) { for (size_t i = 0; i < group.variants.size(); i++) { auto& invocation = group.variants[i]; @@ -116,8 +126,9 @@ static void BM_DisplayListBuilderWithSaveLayerAndImageFilter( DlPaint layer_paint; layer_paint.setImageFilter(&testing::kTestBlurImageFilter1); SkRect layer_bounds = SkRect::MakeLTRB(6.5, 7.3, 35.2, 42.7); + bool prepare_rtree = NeedPrepareRTree(type); while (state.KeepRunning()) { - DisplayListBuilder builder; + DisplayListBuilder builder(prepare_rtree); for (auto& group : allRenderingOps) { for (size_t i = 0; i < group.variants.size(); i++) { auto& invocation = group.variants[i]; diff --git a/display_list/display_list_canvas_recorder.cc b/display_list/display_list_canvas_recorder.cc index 406ee96015bc9..e905c26071417 100644 --- a/display_list/display_list_canvas_recorder.cc +++ b/display_list/display_list_canvas_recorder.cc @@ -19,9 +19,10 @@ namespace flutter { } \ } while (0) -DisplayListCanvasRecorder::DisplayListCanvasRecorder(const SkRect& bounds) +DisplayListCanvasRecorder::DisplayListCanvasRecorder(const SkRect& bounds, + bool prepare_rtree) : SkCanvasVirtualEnforcer(bounds.width(), bounds.height()), - builder_(sk_make_sp(bounds)) {} + builder_(sk_make_sp(bounds, prepare_rtree)) {} sk_sp DisplayListCanvasRecorder::Build() { CHECK_DISPOSE(nullptr); diff --git a/display_list/display_list_canvas_recorder.h b/display_list/display_list_canvas_recorder.h index b5d079bdb3b4e..a2977693078c3 100644 --- a/display_list/display_list_canvas_recorder.h +++ b/display_list/display_list_canvas_recorder.h @@ -26,7 +26,8 @@ class DisplayListCanvasRecorder public SkRefCnt, DisplayListOpFlags { public: - explicit DisplayListCanvasRecorder(const SkRect& bounds); + explicit DisplayListCanvasRecorder(const SkRect& bounds, + bool prepare_rtree = false); const sk_sp builder() { return builder_; } diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 2a9e5d7cf7e27..2eecf7baee585 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -164,6 +164,30 @@ TEST(DisplayList, SingleOpDisplayListsCompareToEachOther) { } } +TEST(DisplayList, SingleOpDisplayListsAreEqualWhetherOrNotToPrepareRtree) { + for (auto& group : allGroups) { + for (size_t i = 0; i < group.variants.size(); i++) { + DisplayListBuilder buider1(/*prepare_rtree=*/false); + DisplayListBuilder buider2(/*prepare_rtree=*/true); + group.variants[i].invoker(buider1); + group.variants[i].invoker(buider2); + sk_sp dl1 = buider1.Build(); + sk_sp dl2 = buider2.Build(); + + auto desc = group.op_name + "(variant " + std::to_string(i + 1) + " )"; + ASSERT_EQ(dl1->op_count(false), dl2->op_count(false)) << desc; + ASSERT_EQ(dl1->bytes(false), dl2->bytes(false)) << desc; + ASSERT_EQ(dl1->op_count(true), dl2->op_count(true)) << desc; + ASSERT_EQ(dl1->bytes(true), dl2->bytes(true)) << desc; + ASSERT_EQ(dl1->bounds(), dl2->bounds()) << desc; + ASSERT_TRUE(dl1->Equals(*dl2)) << desc; + ASSERT_TRUE(dl2->Equals(*dl1)) << desc; + ASSERT_EQ(dl1->rtree().get(), nullptr) << desc; + ASSERT_NE(dl2->rtree().get(), nullptr) << desc; + } + } +} + TEST(DisplayList, FullRotationsAreNop) { DisplayListBuilder builder; builder.rotate(0); @@ -1560,7 +1584,7 @@ static void test_rtree(const sk_sp& rtree, } TEST(DisplayList, RTreeOfSimpleScene) { - DisplayListBuilder builder; + DisplayListBuilder builder(/*prepare_rtree=*/true); builder.drawRect({10, 10, 20, 20}); builder.drawRect({50, 50, 60, 60}); auto display_list = builder.Build(); @@ -1587,7 +1611,7 @@ TEST(DisplayList, RTreeOfSimpleScene) { } TEST(DisplayList, RTreeOfSaveRestoreScene) { - DisplayListBuilder builder; + DisplayListBuilder builder(/*prepare_rtree=*/true); builder.drawRect({10, 10, 20, 20}); builder.save(); builder.drawRect({50, 50, 60, 60}); @@ -1616,7 +1640,7 @@ TEST(DisplayList, RTreeOfSaveRestoreScene) { } TEST(DisplayList, RTreeOfSaveLayerFilterScene) { - DisplayListBuilder builder; + DisplayListBuilder builder(/*prepare_rtree=*/true); // blur filter with sigma=1 expands by 3 on all sides auto filter = DlBlurImageFilter(1.0, 1.0, DlTileMode::kClamp); DlPaint default_paint = DlPaint(); @@ -1651,12 +1675,12 @@ TEST(DisplayList, RTreeOfSaveLayerFilterScene) { } TEST(DisplayList, NestedDisplayListRTreesAreSparse) { - DisplayListBuilder nested_dl_builder; + DisplayListBuilder nested_dl_builder(/**prepare_rtree=*/true); nested_dl_builder.drawRect({10, 10, 20, 20}); nested_dl_builder.drawRect({50, 50, 60, 60}); auto nested_display_list = nested_dl_builder.Build(); - DisplayListBuilder builder; + DisplayListBuilder builder(/**prepare_rtree=*/true); builder.drawDisplayList(nested_display_list); auto display_list = builder.Build(); @@ -2215,7 +2239,7 @@ TEST(DisplayList, NOPClipDoesNotTriggerDeferredSave) { } TEST(DisplayList, RTreeOfClippedSaveLayerFilterScene) { - DisplayListBuilder builder; + DisplayListBuilder builder(/*prepare_rtree=*/true); // blur filter with sigma=1 expands by 30 on all sides auto filter = DlBlurImageFilter(10.0, 10.0, DlTileMode::kClamp); DlPaint default_paint = DlPaint(); diff --git a/display_list/display_list_utils.cc b/display_list/display_list_utils.cc index ff8ddd534dca5..ab8e3c28eff94 100644 --- a/display_list/display_list_utils.cc +++ b/display_list/display_list_utils.cc @@ -107,157 +107,6 @@ sk_sp SkPaintDispatchHelper::makeColorFilter() const { return invert_filter; } -void SkMatrixDispatchHelper::translate(SkScalar tx, SkScalar ty) { - matrix_.preTranslate(tx, ty); - matrix33_ = matrix_.asM33(); -} -void SkMatrixDispatchHelper::scale(SkScalar sx, SkScalar sy) { - matrix_.preScale(sx, sy); - matrix33_ = matrix_.asM33(); -} -void SkMatrixDispatchHelper::rotate(SkScalar degrees) { - matrix33_.setRotate(degrees); - matrix_.preConcat(matrix33_); - matrix33_ = matrix_.asM33(); -} -void SkMatrixDispatchHelper::skew(SkScalar sx, SkScalar sy) { - matrix33_.setSkew(sx, sy); - matrix_.preConcat(matrix33_); - matrix33_ = matrix_.asM33(); -} - -// clang-format off - -// 2x3 2D affine subset of a 4x4 transform in row major order -void SkMatrixDispatchHelper::transform2DAffine( - SkScalar mxx, SkScalar mxy, SkScalar mxt, - SkScalar myx, SkScalar myy, SkScalar myt) { - matrix_.preConcat({ - mxx, mxy, 0 , mxt, - myx, myy, 0 , myt, - 0 , 0 , 1 , 0 , - 0 , 0 , 0 , 1 , - }); - matrix33_ = matrix_.asM33(); -} -// full 4x4 transform in row major order -void SkMatrixDispatchHelper::transformFullPerspective( - SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, - SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, - SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, - SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) { - matrix_.preConcat({ - mxx, mxy, mxz, mxt, - myx, myy, myz, myt, - mzx, mzy, mzz, mzt, - mwx, mwy, mwz, mwt, - }); - matrix33_ = matrix_.asM33(); -} - -// clang-format on - -void SkMatrixDispatchHelper::transformReset() { - matrix_ = {}; - matrix33_ = {}; -} - -void SkMatrixDispatchHelper::save() { - saved_.push_back(matrix_); -} -void SkMatrixDispatchHelper::restore() { - if (saved_.empty()) { - return; - } - matrix_ = saved_.back(); - matrix33_ = matrix_.asM33(); - saved_.pop_back(); -} -void SkMatrixDispatchHelper::reset() { - matrix_.setIdentity(); - matrix33_ = matrix_.asM33(); -} - -void ClipBoundsDispatchHelper::clipRect(const SkRect& rect, - SkClipOp clip_op, - bool is_aa) { - switch (clip_op) { - case SkClipOp::kIntersect: - intersect(rect, is_aa); - break; - case SkClipOp::kDifference: - break; - } -} -void ClipBoundsDispatchHelper::clipRRect(const SkRRect& rrect, - SkClipOp clip_op, - bool is_aa) { - switch (clip_op) { - case SkClipOp::kIntersect: - intersect(rrect.getBounds(), is_aa); - break; - case SkClipOp::kDifference: - break; - } -} -void ClipBoundsDispatchHelper::clipPath(const SkPath& path, - SkClipOp clip_op, - bool is_aa) { - switch (clip_op) { - case SkClipOp::kIntersect: - intersect(path.getBounds(), is_aa); - break; - case SkClipOp::kDifference: - break; - } -} -void ClipBoundsDispatchHelper::intersect(const SkRect& rect, bool is_aa) { - SkRect dev_clip_bounds = matrix().mapRect(rect); - if (is_aa) { - dev_clip_bounds.roundOut(&dev_clip_bounds); - } - if (has_clip_) { - if (!bounds_.intersect(dev_clip_bounds)) { - bounds_.setEmpty(); - } - } else { - has_clip_ = true; - if (dev_clip_bounds.isEmpty()) { - bounds_.setEmpty(); - } else { - bounds_ = dev_clip_bounds; - } - } -} -void ClipBoundsDispatchHelper::save() { - if (!has_clip_) { - saved_.push_back(SkRect::MakeLTRB(0, 0, -1, -1)); - } else if (bounds_.isEmpty()) { - saved_.push_back(SkRect::MakeEmpty()); - } else { - saved_.push_back(bounds_); - } -} -void ClipBoundsDispatchHelper::restore() { - if (saved_.empty()) { - return; - } - bounds_ = saved_.back(); - saved_.pop_back(); - has_clip_ = (bounds_.fLeft <= bounds_.fRight && // - bounds_.fTop <= bounds_.fBottom); - if (!has_clip_) { - bounds_.setEmpty(); - } -} -void ClipBoundsDispatchHelper::reset(const SkRect* cull_rect) { - if ((has_clip_ = cull_rect != nullptr) && !cull_rect->isEmpty()) { - bounds_ = *cull_rect; - } else { - bounds_.setEmpty(); - } -} - void RectBoundsAccumulator::accumulate(const SkRect& r) { if (r.fLeft < r.fRight && r.fTop < r.fBottom) { rect_.accumulate(r.fLeft, r.fTop); @@ -330,12 +179,6 @@ void RTreeBoundsAccumulator::accumulate(const SkRect& r) { rects_.push_back(r); } } -bool RTreeBoundsAccumulator::is_empty() const { - return rects_.empty(); -} -bool RTreeBoundsAccumulator::is_not_empty() const { - return !rects_.empty(); -} void RTreeBoundsAccumulator::save() { saved_offsets_.push_back(rects_.size()); } @@ -369,6 +212,16 @@ bool RTreeBoundsAccumulator::restore( rects_.resize(previous_size); return success; } + +SkRect RTreeBoundsAccumulator::bounds() const { + FML_DCHECK(saved_offsets_.empty()); + RectBoundsAccumulator accumulator; + for (auto& rect : rects_) { + accumulator.accumulate(rect); + } + return accumulator.bounds(); +} + sk_sp RTreeBoundsAccumulator::rtree() const { FML_DCHECK(saved_offsets_.empty()); DlRTreeFactory factory; @@ -377,497 +230,4 @@ sk_sp RTreeBoundsAccumulator::rtree() const { return rtree; } -DisplayListBoundsCalculator::DisplayListBoundsCalculator( - BoundsAccumulator& accumulator, - const SkRect* cull_rect) - : ClipBoundsDispatchHelper(cull_rect), accumulator_(accumulator) { - layer_infos_.emplace_back(std::make_unique(nullptr)); -} -void DisplayListBoundsCalculator::setStrokeCap(DlStrokeCap cap) { - cap_is_square_ = (cap == DlStrokeCap::kSquare); -} -void DisplayListBoundsCalculator::setStrokeJoin(DlStrokeJoin join) { - join_is_miter_ = (join == DlStrokeJoin::kMiter); -} -void DisplayListBoundsCalculator::setStyle(DlDrawStyle style) { - style_ = style; -} -void DisplayListBoundsCalculator::setStrokeWidth(SkScalar width) { - half_stroke_width_ = std::max(width * 0.5f, kMinStrokeWidth); -} -void DisplayListBoundsCalculator::setStrokeMiter(SkScalar limit) { - miter_limit_ = std::max(limit, 1.0f); -} -void DisplayListBoundsCalculator::setBlendMode(DlBlendMode mode) { - blend_mode_ = mode; -} -void DisplayListBoundsCalculator::setBlender(sk_sp blender) { - SkPaint paint; - paint.setBlender(std::move(blender)); - auto blend_mode = paint.asBlendMode(); - if (blend_mode.has_value()) { - blend_mode_ = ToDl(blend_mode.value()); - } else { - blend_mode_ = std::nullopt; - } -} -void DisplayListBoundsCalculator::setImageFilter(const DlImageFilter* filter) { - image_filter_ = filter ? filter->shared() : nullptr; -} -void DisplayListBoundsCalculator::setColorFilter(const DlColorFilter* filter) { - color_filter_ = filter ? filter->shared() : nullptr; -} -void DisplayListBoundsCalculator::setPathEffect(const DlPathEffect* effect) { - path_effect_ = effect ? effect->shared() : nullptr; -} -void DisplayListBoundsCalculator::setMaskFilter(const DlMaskFilter* filter) { - mask_filter_ = filter ? filter->shared() : nullptr; -} -void DisplayListBoundsCalculator::save() { - SkMatrixDispatchHelper::save(); - ClipBoundsDispatchHelper::save(); - layer_infos_.emplace_back(std::make_unique(nullptr)); - accumulator_.save(); -} -void DisplayListBoundsCalculator::saveLayer(const SkRect* bounds, - const SaveLayerOptions options, - const DlImageFilter* backdrop) { - SkMatrixDispatchHelper::save(); - ClipBoundsDispatchHelper::save(); - if (options.renders_with_attributes()) { - // The actual flood of the outer layer clip will occur after the - // (eventual) corresponding restore is called, but rather than - // remember this information in the LayerInfo until the restore - // method is processed, we just mark the unbounded state up front. - if (!paint_nops_on_transparency()) { - // We will fill the clip of the outer layer when we restore - AccumulateUnbounded(); - } - - layer_infos_.emplace_back(std::make_unique(image_filter_)); - } else { - layer_infos_.emplace_back(std::make_unique(nullptr)); - } - - accumulator_.save(); - - // Even though Skia claims that the bounds are only a hint, they actually - // use them as the temporary layer bounds during rendering the layer, so - // we set them as if a clip operation were performed. - if (bounds) { - clipRect(*bounds, SkClipOp::kIntersect, false); - } - if (backdrop) { - // A backdrop will affect up to the entire surface, bounded by the clip - AccumulateUnbounded(); - } -} -void DisplayListBoundsCalculator::restore() { - if (layer_infos_.size() > 1) { - SkMatrixDispatchHelper::restore(); - ClipBoundsDispatchHelper::restore(); - - // Remember a few pieces of information from the current layer info - // for later processing. - LayerData* layer_info = layer_infos_.back().get(); - bool is_unbounded = layer_info->is_unbounded(); - - // Before we pop_back we will get the current layer bounds from the - // current accumulator and adjust ot as required based on the filter. - std::shared_ptr filter = layer_info->filter(); - const SkRect* clip = has_clip() ? &clip_bounds() : nullptr; - if (filter) { - if (!accumulator_.restore( - [filter = filter, matrix = matrix()](const SkRect& input, - SkRect& output) { - SkIRect output_bounds; - bool ret = filter->map_device_bounds(input.roundOut(), matrix, - output_bounds); - output.set(output_bounds); - return ret; - }, - clip)) { - is_unbounded = true; - } - } else { - accumulator_.restore(); - } - - // Restore the accumulator before popping the LayerInfo so that - // it nevers points to an out of scope instance. - layer_infos_.pop_back(); - - if (is_unbounded) { - AccumulateUnbounded(); - } - } -} - -void DisplayListBoundsCalculator::drawPaint() { - AccumulateUnbounded(); -} -void DisplayListBoundsCalculator::drawColor(DlColor color, DlBlendMode mode) { - AccumulateUnbounded(); -} -void DisplayListBoundsCalculator::drawLine(const SkPoint& p0, - const SkPoint& p1) { - SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); - DisplayListAttributeFlags flags = - (bounds.width() > 0.0f && bounds.height() > 0.0f) ? kDrawLineFlags - : kDrawHVLineFlags; - AccumulateOpBounds(bounds, flags); -} -void DisplayListBoundsCalculator::drawRect(const SkRect& rect) { - AccumulateOpBounds(rect, kDrawRectFlags); -} -void DisplayListBoundsCalculator::drawOval(const SkRect& bounds) { - AccumulateOpBounds(bounds, kDrawOvalFlags); -} -void DisplayListBoundsCalculator::drawCircle(const SkPoint& center, - SkScalar radius) { - AccumulateOpBounds(SkRect::MakeLTRB(center.fX - radius, center.fY - radius, - center.fX + radius, center.fY + radius), - kDrawCircleFlags); -} -void DisplayListBoundsCalculator::drawRRect(const SkRRect& rrect) { - AccumulateOpBounds(rrect.getBounds(), kDrawRRectFlags); -} -void DisplayListBoundsCalculator::drawDRRect(const SkRRect& outer, - const SkRRect& inner) { - AccumulateOpBounds(outer.getBounds(), kDrawDRRectFlags); -} -void DisplayListBoundsCalculator::drawPath(const SkPath& path) { - if (path.isInverseFillType()) { - AccumulateUnbounded(); - } else { - AccumulateOpBounds(path.getBounds(), kDrawPathFlags); - } -} -void DisplayListBoundsCalculator::drawArc(const SkRect& bounds, - SkScalar start, - SkScalar sweep, - bool useCenter) { - // This could be tighter if we compute where the start and end - // angles are and then also consider the quadrants swept and - // the center if specified. - AccumulateOpBounds(bounds, - useCenter // - ? kDrawArcWithCenterFlags - : kDrawArcNoCenterFlags); -} -void DisplayListBoundsCalculator::drawPoints(SkCanvas::PointMode mode, - uint32_t count, - const SkPoint pts[]) { - if (count > 0) { - RectBoundsAccumulator pt_bounds; - for (size_t i = 0; i < count; i++) { - pt_bounds.accumulate(pts[i]); - } - SkRect point_bounds = pt_bounds.bounds(); - switch (mode) { - case SkCanvas::kPoints_PointMode: - AccumulateOpBounds(point_bounds, kDrawPointsAsPointsFlags); - break; - case SkCanvas::kLines_PointMode: - AccumulateOpBounds(point_bounds, kDrawPointsAsLinesFlags); - break; - case SkCanvas::kPolygon_PointMode: - AccumulateOpBounds(point_bounds, kDrawPointsAsPolygonFlags); - break; - } - } -} -void DisplayListBoundsCalculator::drawSkVertices( - const sk_sp vertices, - SkBlendMode mode) { - AccumulateOpBounds(vertices->bounds(), kDrawVerticesFlags); -} -void DisplayListBoundsCalculator::drawVertices(const DlVertices* vertices, - DlBlendMode mode) { - AccumulateOpBounds(vertices->bounds(), kDrawVerticesFlags); -} -void DisplayListBoundsCalculator::drawImage(const sk_sp image, - const SkPoint point, - DlImageSampling sampling, - bool render_with_attributes) { - SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, // - image->width(), image->height()); - DisplayListAttributeFlags flags = render_with_attributes // - ? kDrawImageWithPaintFlags - : kDrawImageFlags; - AccumulateOpBounds(bounds, flags); -} -void DisplayListBoundsCalculator::drawImageRect( - const sk_sp image, - const SkRect& src, - const SkRect& dst, - DlImageSampling sampling, - bool render_with_attributes, - SkCanvas::SrcRectConstraint constraint) { - DisplayListAttributeFlags flags = render_with_attributes - ? kDrawImageRectWithPaintFlags - : kDrawImageRectFlags; - AccumulateOpBounds(dst, flags); -} -void DisplayListBoundsCalculator::drawImageNine(const sk_sp image, - const SkIRect& center, - const SkRect& dst, - DlFilterMode filter, - bool render_with_attributes) { - DisplayListAttributeFlags flags = render_with_attributes - ? kDrawImageNineWithPaintFlags - : kDrawImageNineFlags; - AccumulateOpBounds(dst, flags); -} -void DisplayListBoundsCalculator::drawImageLattice( - const sk_sp image, - const SkCanvas::Lattice& lattice, - const SkRect& dst, - DlFilterMode filter, - bool render_with_attributes) { - DisplayListAttributeFlags flags = render_with_attributes - ? kDrawImageLatticeWithPaintFlags - : kDrawImageLatticeFlags; - AccumulateOpBounds(dst, flags); -} -void DisplayListBoundsCalculator::drawAtlas(const sk_sp atlas, - const SkRSXform xform[], - const SkRect tex[], - const DlColor colors[], - int count, - DlBlendMode mode, - DlImageSampling sampling, - const SkRect* cullRect, - bool render_with_attributes) { - SkPoint quad[4]; - RectBoundsAccumulator atlas_bounds; - for (int i = 0; i < count; i++) { - const SkRect& src = tex[i]; - xform[i].toQuad(src.width(), src.height(), quad); - for (int j = 0; j < 4; j++) { - atlas_bounds.accumulate(quad[j]); - } - } - if (atlas_bounds.is_not_empty()) { - DisplayListAttributeFlags flags = render_with_attributes // - ? kDrawAtlasWithPaintFlags - : kDrawAtlasFlags; - AccumulateOpBounds(atlas_bounds.bounds(), flags); - } -} -void DisplayListBoundsCalculator::drawPicture(const sk_sp picture, - const SkMatrix* pic_matrix, - bool render_with_attributes) { - // TODO(flar) cull rect really cannot be trusted in general, but it will - // work for SkPictures generated from our own PictureRecorder or any - // picture captured with an SkRTreeFactory or accurate bounds estimate. - SkRect bounds = picture->cullRect(); - if (pic_matrix) { - pic_matrix->mapRect(&bounds); - } - DisplayListAttributeFlags flags = render_with_attributes // - ? kDrawPictureWithPaintFlags - : kDrawPictureFlags; - AccumulateOpBounds(bounds, flags); -} -void DisplayListBoundsCalculator::drawDisplayList( - const sk_sp display_list) { - const SkRect bounds = display_list->bounds(); - switch (accumulator_.type()) { - case BoundsAccumulatorType::kRect: - AccumulateOpBounds(bounds, kDrawDisplayListFlags); - return; - case BoundsAccumulatorType::kRTree: - std::list rects = - display_list->rtree()->searchNonOverlappingDrawnRects(bounds); - for (const SkRect& rect : rects) { - // TODO (https://github.com/flutter/flutter/issues/114919): Attributes - // are not necessarily `kDrawDisplayListFlags`. - AccumulateOpBounds(rect, kDrawDisplayListFlags); - } - return; - } - - FML_UNREACHABLE(); -} -void DisplayListBoundsCalculator::drawTextBlob(const sk_sp blob, - SkScalar x, - SkScalar y) { - AccumulateOpBounds(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags); -} -void DisplayListBoundsCalculator::drawShadow(const SkPath& path, - const DlColor color, - const SkScalar elevation, - bool transparent_occluder, - SkScalar dpr) { - SkRect shadow_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds( - path, elevation, dpr, matrix()); - AccumulateOpBounds(shadow_bounds, kDrawShadowFlags); -} - -bool DisplayListBoundsCalculator::ComputeFilteredBounds(SkRect& bounds, - DlImageFilter* filter) { - if (filter) { - if (!filter->map_local_bounds(bounds, bounds)) { - return false; - } - } - return true; -} - -bool DisplayListBoundsCalculator::AdjustBoundsForPaint( - SkRect& bounds, - DisplayListAttributeFlags flags) { - if (flags.ignores_paint()) { - return true; - } - - if (flags.is_geometric()) { - // Path effect occurs before stroking... - DisplayListSpecialGeometryFlags special_flags = - flags.WithPathEffect(path_effect_.get()); - if (path_effect_) { - auto effect_bounds = path_effect_->effect_bounds(bounds); - if (!effect_bounds.has_value()) { - return false; - } - bounds = effect_bounds.value(); - } - - if (flags.is_stroked(style_)) { - // Determine the max multiplier to the stroke width first. - SkScalar pad = 1.0f; - if (join_is_miter_ && special_flags.may_have_acute_joins()) { - pad = std::max(pad, miter_limit_); - } - if (cap_is_square_ && special_flags.may_have_diagonal_caps()) { - pad = std::max(pad, SK_ScalarSqrt2); - } - pad *= half_stroke_width_; - bounds.outset(pad, pad); - } - } - - if (flags.applies_mask_filter()) { - if (mask_filter_) { - const DlBlurMaskFilter* blur_filter = mask_filter_->asBlur(); - if (blur_filter) { - SkScalar mask_sigma_pad = blur_filter->sigma() * 3.0; - bounds.outset(mask_sigma_pad, mask_sigma_pad); - } else { - SkPaint p; - p.setMaskFilter(mask_filter_->skia_object()); - if (!p.canComputeFastBounds()) { - return false; - } - bounds = p.computeFastBounds(bounds, &bounds); - } - } - } - - if (flags.applies_image_filter()) { - return ComputeFilteredBounds(bounds, image_filter_.get()); - } - - return true; -} - -void DisplayListBoundsCalculator::AccumulateUnbounded() { - if (has_clip()) { - accumulator_.accumulate(clip_bounds()); - } else { - layer_infos_.back()->set_unbounded(); - } -} -void DisplayListBoundsCalculator::AccumulateOpBounds( - SkRect& bounds, - DisplayListAttributeFlags flags) { - if (AdjustBoundsForPaint(bounds, flags)) { - AccumulateBounds(bounds); - } else { - AccumulateUnbounded(); - } -} -void DisplayListBoundsCalculator::AccumulateBounds(SkRect& bounds) { - matrix().mapRect(&bounds); - if (!has_clip() || bounds.intersect(clip_bounds())) { - accumulator_.accumulate(bounds); - } -} - -bool DisplayListBoundsCalculator::paint_nops_on_transparency() { - // SkImageFilter::canComputeFastBounds tests for transparency behavior - // This test assumes that the blend mode checked down below will - // NOP on transparent black. - if (image_filter_ && image_filter_->modifies_transparent_black()) { - return false; - } - - // We filter the transparent black that is used for the background of a - // saveLayer and make sure it returns transparent black. If it does, then - // the color filter will leave all area surrounding the contents of the - // save layer untouched out to the edge of the output surface. - // This test assumes that the blend mode checked down below will - // NOP on transparent black. - if (color_filter_ && color_filter_->modifies_transparent_black()) { - return false; - } - - if (!blend_mode_) { - return false; // can we query other blenders for this? - } - // Unusual blendmodes require us to process a saved layer - // even with operations outisde the clip. - // For example, DstIn is used by masking layers. - // https://code.google.com/p/skia/issues/detail?id=1291 - // https://crbug.com/401593 - switch (blend_mode_.value()) { - // For each of the following transfer modes, if the source - // alpha is zero (our transparent black), the resulting - // blended pixel is not necessarily equal to the original - // destination pixel. - // Mathematically, any time in the following equations where - // the result is not d assuming source is 0 - case DlBlendMode::kClear: // r = 0 - case DlBlendMode::kSrc: // r = s - case DlBlendMode::kSrcIn: // r = s * da - case DlBlendMode::kDstIn: // r = d * sa - case DlBlendMode::kSrcOut: // r = s * (1-da) - case DlBlendMode::kDstATop: // r = d*sa + s*(1-da) - case DlBlendMode::kModulate: // r = s*d - return false; - break; - - // And in these equations, the result must be d if the - // source is 0 - case DlBlendMode::kDst: // r = d - case DlBlendMode::kSrcOver: // r = s + (1-sa)*d - case DlBlendMode::kDstOver: // r = d + (1-da)*s - case DlBlendMode::kDstOut: // r = d * (1-sa) - case DlBlendMode::kSrcATop: // r = s*da + d*(1-sa) - case DlBlendMode::kXor: // r = s*(1-da) + d*(1-sa) - case DlBlendMode::kPlus: // r = min(s + d, 1) - case DlBlendMode::kScreen: // r = s + d - s*d - case DlBlendMode::kOverlay: // multiply or screen, depending on dest - case DlBlendMode::kDarken: // rc = s + d - max(s*da, d*sa), - // ra = kSrcOver - case DlBlendMode::kLighten: // rc = s + d - min(s*da, d*sa), - // ra = kSrcOver - case DlBlendMode::kColorDodge: // brighten destination to reflect source - case DlBlendMode::kColorBurn: // darken destination to reflect source - case DlBlendMode::kHardLight: // multiply or screen, depending on source - case DlBlendMode::kSoftLight: // lighten or darken, depending on source - case DlBlendMode::kDifference: // rc = s + d - 2*(min(s*da, d*sa)), - // ra = kSrcOver - case DlBlendMode::kExclusion: // rc = s + d - two(s*d), ra = kSrcOver - case DlBlendMode::kMultiply: // r = s*(1-da) + d*(1-sa) + s*d - case DlBlendMode::kHue: // ra = kSrcOver - case DlBlendMode::kSaturation: // ra = kSrcOver - case DlBlendMode::kColor: // ra = kSrcOver - case DlBlendMode::kLuminosity: // ra = kSrcOver - return true; - break; - } -} - } // namespace flutter diff --git a/display_list/display_list_utils.h b/display_list/display_list_utils.h index 406e852fbcae9..992d464ff8d38 100644 --- a/display_list/display_list_utils.h +++ b/display_list/display_list_utils.h @@ -9,7 +9,8 @@ #include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_blend_mode.h" -#include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list_dispatcher.h" +#include "flutter/display_list/display_list_flags.h" #include "flutter/display_list/display_list_rtree.h" #include "flutter/fml/logging.h" #include "flutter/fml/macros.h" @@ -27,17 +28,6 @@ // SkPaintAttributeDispatchHelper: // Tracks the attribute methods and maintains their state in an // SkPaint object. -// SkMatrixTransformDispatchHelper: -// Tracks the transform methods and maintains their state in a -// (save/restore stack of) SkMatrix object. -// ClipBoundsDispatchHelper: -// Tracks the clip methods and maintains a culling box in a -// (save/restore stack of) SkRect culling rectangle. -// -// DisplayListBoundsCalculator: -// A class that can traverse an entire display list and compute -// a conservative estimate of the bounds of all of the rendering -// operations. namespace flutter { @@ -234,109 +224,6 @@ class SkPaintDispatchHelper : public virtual Dispatcher { SkScalar opacity_; }; -class SkMatrixSource { - public: - // The current full 4x4 transform matrix. Not generally needed - // for 2D operations. See |matrix|. - virtual const SkM44& m44() const = 0; - - // The current matrix expressed as an SkMatrix. The data held - // in an SkMatrix is enough to perform point and rect transforms - // assuming input coordinates have only an X and Y and an assumed - // Z of 0 and an assumed W of 1. - // See the block comment on the transform methods in |Dispatcher| - // for a detailed explanation. - virtual const SkMatrix& matrix() const = 0; -}; - -// A utility class that will monitor the Dispatcher methods relating -// to the transform and accumulate them into an SkMatrix which can -// be accessed at any time via matrix(). -// -// This class also implements an appropriate stack of transforms via -// its save() and restore() methods so those methods will need to be -// forwarded if overridden in more than one super class. -class SkMatrixDispatchHelper : public virtual Dispatcher, - public virtual SkMatrixSource { - public: - void translate(SkScalar tx, SkScalar ty) override; - void scale(SkScalar sx, SkScalar sy) override; - void rotate(SkScalar degrees) override; - void skew(SkScalar sx, SkScalar sy) override; - - // clang-format off - - // 2x3 2D affine subset of a 4x4 transform in row major order - void transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt, - SkScalar myx, SkScalar myy, SkScalar myt) override; - // full 4x4 transform in row major order - void transformFullPerspective( - SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, - SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, - SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, - SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override; - - // clang-format on - - void transformReset() override; - - void save() override; - void restore() override; - - const SkM44& m44() const override { return matrix_; } - const SkMatrix& matrix() const override { return matrix33_; } - - protected: - void reset(); - - private: - SkM44 matrix_; - SkMatrix matrix33_; - std::vector saved_; -}; - -// A utility class that will monitor the Dispatcher methods relating -// to the clip and accumulate a conservative bounds into an SkRect -// which can be accessed at any time via getCullingBounds(). -// -// The subclass must implement a single virtual method matrix() -// which will happen automatically if the subclass also inherits -// from SkMatrixTransformDispatchHelper. -// -// This class also implements an appropriate stack of transforms via -// its save() and restore() methods so those methods will need to be -// forwarded if overridden in more than one super class. -class ClipBoundsDispatchHelper : public virtual Dispatcher, - private virtual SkMatrixSource { - public: - ClipBoundsDispatchHelper() : ClipBoundsDispatchHelper(nullptr) {} - - explicit ClipBoundsDispatchHelper(const SkRect* cull_rect) - : has_clip_(cull_rect), - bounds_(cull_rect && !cull_rect->isEmpty() ? *cull_rect - : SkRect::MakeEmpty()) {} - - void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) override; - void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool is_aa) override; - void clipPath(const SkPath& path, SkClipOp clip_op, bool is_aa) override; - - void save() override; - void restore() override; - - bool has_clip() const { return has_clip_; } - const SkRect& clip_bounds() const { return bounds_; } - - protected: - void reset(const SkRect* cull_rect); - - private: - bool has_clip_; - SkRect bounds_; - std::vector saved_; - - void intersect(const SkRect& clipBounds, bool is_aa); -}; - enum class BoundsAccumulatorType { kRect, kRTree, @@ -362,10 +249,9 @@ class BoundsAccumulator { /// be trusted. typedef bool BoundsModifier(const SkRect& original, SkRect* dest); - virtual void accumulate(const SkRect& r) = 0; + virtual ~BoundsAccumulator() = default; - virtual bool is_empty() const = 0; - virtual bool is_not_empty() const = 0; + virtual void accumulate(const SkRect& r) = 0; /// Save aside the rects/bounds currently being accumulated and start /// accumulating a new set of rects/bounds. When restore is called, @@ -399,6 +285,10 @@ class BoundsAccumulator { std::function map, const SkRect* clip = nullptr) = 0; + virtual SkRect bounds() const = 0; + + virtual sk_sp rtree() const = 0; + virtual BoundsAccumulatorType type() const = 0; }; @@ -408,15 +298,15 @@ class RectBoundsAccumulator final : public virtual BoundsAccumulator { void accumulate(const SkPoint& p) { rect_.accumulate(p.fX, p.fY); } void accumulate(const SkRect& r) override; - bool is_empty() const override { return rect_.is_empty(); } - bool is_not_empty() const override { return rect_.is_not_empty(); } + bool is_empty() const { return rect_.is_empty(); } + bool is_not_empty() const { return rect_.is_not_empty(); } void save() override; void restore() override; bool restore(std::function mapper, const SkRect* clip) override; - SkRect bounds() const { + SkRect bounds() const override { FML_DCHECK(saved_rects_.empty()); return rect_.bounds(); } @@ -425,6 +315,8 @@ class RectBoundsAccumulator final : public virtual BoundsAccumulator { return BoundsAccumulatorType::kRect; } + sk_sp rtree() const override { return nullptr; } + private: class AccumulationRect { public: @@ -453,10 +345,6 @@ class RectBoundsAccumulator final : public virtual BoundsAccumulator { class RTreeBoundsAccumulator final : public virtual BoundsAccumulator { public: void accumulate(const SkRect& r) override; - - bool is_empty() const override; - bool is_not_empty() const override; - void save() override; void restore() override; @@ -464,7 +352,9 @@ class RTreeBoundsAccumulator final : public virtual BoundsAccumulator { std::function map, const SkRect* clip = nullptr) override; - sk_sp rtree() const; + SkRect bounds() const override; + + sk_sp rtree() const override; BoundsAccumulatorType type() const override { return BoundsAccumulatorType::kRTree; @@ -475,231 +365,6 @@ class RTreeBoundsAccumulator final : public virtual BoundsAccumulator { std::vector saved_offsets_; }; -// This class implements all rendering methods and computes a liberal -// bounds of the rendering operations. -class DisplayListBoundsCalculator final - : public virtual Dispatcher, - public virtual IgnoreAttributeDispatchHelper, - public virtual SkMatrixDispatchHelper, - public virtual ClipBoundsDispatchHelper, - DisplayListOpFlags { - public: - // Construct a Calculator to determine the bounds of a list of - // DisplayList dispatcher method calls. Since 2 of the method calls - // have no intrinsic size because they flood the entire clip/surface, - // the |cull_rect| provides a bounds for them to include. If cull_rect - // is not specified or is null, then the unbounded calls will not - // affect the resulting bounds, but will set a flag that can be - // queried using |isUnbounded| if an alternate plan is available - // for such cases. - // The flag should never be set if a cull_rect is provided. - explicit DisplayListBoundsCalculator(BoundsAccumulator& accumulator, - const SkRect* cull_rect = nullptr); - - void setStrokeCap(DlStrokeCap cap) override; - void setStrokeJoin(DlStrokeJoin join) override; - void setStyle(DlDrawStyle style) override; - void setStrokeWidth(SkScalar width) override; - void setStrokeMiter(SkScalar limit) override; - void setBlendMode(DlBlendMode mode) override; - void setBlender(sk_sp blender) 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; - void saveLayer(const SkRect* bounds, - const SaveLayerOptions options, - const DlImageFilter* backdrop) override; - void restore() override; - - void drawPaint() override; - void drawColor(DlColor color, DlBlendMode mode) override; - 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& bounds, - SkScalar start, - SkScalar sweep, - bool useCenter) override; - void drawPoints(SkCanvas::PointMode mode, - uint32_t count, - const SkPoint pts[]) override; - void drawSkVertices(const sk_sp vertices, - SkBlendMode mode) override; - void drawVertices(const DlVertices* vertices, DlBlendMode mode) override; - void drawImage(const sk_sp image, - const SkPoint point, - DlImageSampling sampling, - bool render_with_attributes) override; - void drawImageRect(const sk_sp image, - const SkRect& src, - const SkRect& dst, - DlImageSampling sampling, - bool render_with_attributes, - SkCanvas::SrcRectConstraint constraint) override; - void drawImageNine(const sk_sp image, - const SkIRect& center, - const SkRect& dst, - DlFilterMode filter, - bool render_with_attributes) override; - void drawImageLattice(const sk_sp image, - const SkCanvas::Lattice& lattice, - const SkRect& dst, - DlFilterMode filter, - bool render_with_attributes) override; - void drawAtlas(const sk_sp atlas, - const SkRSXform xform[], - const SkRect tex[], - const DlColor colors[], - int count, - DlBlendMode mode, - DlImageSampling sampling, - const SkRect* cullRect, - bool render_with_attributes) override; - void drawPicture(const sk_sp picture, - const SkMatrix* matrix, - bool with_save_layer) 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 DlColor color, - const SkScalar elevation, - bool transparent_occluder, - SkScalar dpr) override; - - // The DisplayList had an unbounded call with no cull rect or clip - // to contain it. Should only be called after the stream is fully - // dispatched. - // Unbounded operations are calls like |drawColor| which are defined - // to flood the entire surface, or calls that relied on a rendering - // attribute which is unable to compute bounds (should be rare). - // In those cases the bounds will represent only the accumulation - // of the bounded calls and this flag will be set to indicate that - // condition. - bool is_unbounded() const { - FML_DCHECK(layer_infos_.size() == 1); - return layer_infos_.front()->is_unbounded(); - } - - private: - BoundsAccumulator& accumulator_; - - // A class that remembers the information kept for a single - // |save| or |saveLayer|. - // Each save or saveLayer will maintain its own bounds accumulator - // and then accumulate that back into the surrounding accumulator - // during restore. - class LayerData { - public: - // Construct a LayerData to push on the save stack for a |save| - // or |saveLayer| call. - // Some saveLayer calls will process their bounds by a - // |DlImageFilter| when they are restored, but for most - // saveLayer (and all save) calls the filter will be null. - explicit LayerData(std::shared_ptr filter = nullptr) - : filter_(filter), is_unbounded_(false) {} - ~LayerData() = default; - - // The filter to apply to the layer bounds when it is restored - std::shared_ptr filter() { return filter_; } - - // is_unbounded should be set to true if we ever encounter an operation - // on a layer that either is unrestricted (|drawColor| or |drawPaint|) - // or cannot compute its bounds (some effects and filters) and there - // was no outstanding clip op at the time. - // When the layer is restored, the outer layer may then process this - // unbounded state by accumulating its own clip or transferring the - // unbounded state to its own outer layer. - // Typically the DisplayList will have been constructed with a cull - // rect which will act as a default clip for the outermost layer and - // the unbounded state of all sub layers will eventually be caught by - // that cull rect so that the overall unbounded state of the entire - // DisplayList will never be true. - // - // SkPicture treats these same conditions as a Nop (they accumulate - // the SkPicture cull rect, but if it was not specified then it is an - // empty Rect and so has no effect on the bounds). - // If the Calculator object accumulates this flag into the root layer, - // then at least we can make the caller aware of that exceptional - // condition via the |DisplayListBoundsCalculator::isUnbounded| call. - // - // Flutter is unlikely to ever run into this as the Dart mechanisms - // all supply a non-null cull rect for all Dart Picture objects, - // even if that cull rect is kGiantRect. - void set_unbounded() { is_unbounded_ = true; } - - // |is_unbounded| should be called after |getLayerBounds| in case - // a problem was found during the computation of those bounds, - // the layer will have one last chance to flag an unbounded state. - bool is_unbounded() const { return is_unbounded_; } - - bool map_bounds(const SkRect& input, SkRect* output) { - *output = input; - return true; - } - - private: - std::shared_ptr filter_; - bool is_unbounded_; - - FML_DISALLOW_COPY_AND_ASSIGN(LayerData); - }; - - std::vector> layer_infos_; - - static constexpr SkScalar kMinStrokeWidth = 0.01; - - std::optional blend_mode_ = DlBlendMode::kSrcOver; - std::shared_ptr color_filter_; - - SkScalar half_stroke_width_ = kMinStrokeWidth; - SkScalar miter_limit_ = 4.0; - DlDrawStyle style_ = DlDrawStyle::kFill; - bool join_is_miter_ = true; - bool cap_is_square_ = false; - std::shared_ptr image_filter_; - std::shared_ptr path_effect_; - std::shared_ptr mask_filter_; - - bool paint_nops_on_transparency(); - - // Computes the bounds of an operation adjusted for a given ImageFilter - static bool ComputeFilteredBounds(SkRect& bounds, DlImageFilter* filter); - - // Adjusts the indicated bounds for the given flags and returns true if - // the calculation was possible, or false if it could not be estimated. - bool AdjustBoundsForPaint(SkRect& bounds, DisplayListAttributeFlags flags); - - // Records the fact that we encountered an op that either could not - // estimate its bounds or that fills all of the destination space. - void AccumulateUnbounded(); - - // Records the bounds for an op after modifying them according to the - // supplied attribute flags and transforming by the current matrix. - void AccumulateOpBounds(const SkRect& bounds, - DisplayListAttributeFlags flags) { - SkRect safe_bounds = bounds; - AccumulateOpBounds(safe_bounds, flags); - } - - // Records the bounds for an op after modifying them according to the - // supplied attribute flags and transforming by the current matrix - // and clipping against the current clip. - void AccumulateOpBounds(SkRect& bounds, DisplayListAttributeFlags flags); - - // Records the given bounds after transforming by the current matrix - // and clipping against the current clip. - void AccumulateBounds(SkRect& bounds); -}; - } // namespace flutter #endif // FLUTTER_DISPLAY_LIST_DISPLAY_LIST_UTILS_H_ diff --git a/display_list/display_list_utils_unittests.cc b/display_list/display_list_utils_unittests.cc index 3141ffd0ebbf6..9f77c949b861e 100644 --- a/display_list/display_list_utils_unittests.cc +++ b/display_list/display_list_utils_unittests.cc @@ -9,22 +9,14 @@ namespace flutter { namespace testing { class MockDispatchHelper final : public virtual Dispatcher, - public virtual SkPaintDispatchHelper, - public virtual SkMatrixDispatchHelper, - public virtual ClipBoundsDispatchHelper, - public virtual IgnoreDrawDispatchHelper { + public SkPaintDispatchHelper, + public IgnoreClipDispatchHelper, + public IgnoreTransformDispatchHelper, + public IgnoreDrawDispatchHelper { public: - void save() override { - SkPaintDispatchHelper::save_opacity(0.5f); - SkMatrixDispatchHelper::save(); - ClipBoundsDispatchHelper::save(); - } + void save() override { SkPaintDispatchHelper::save_opacity(0.5f); } - void restore() override { - SkPaintDispatchHelper::restore_opacity(); - SkMatrixDispatchHelper::restore(); - ClipBoundsDispatchHelper::restore(); - } + void restore() override { SkPaintDispatchHelper::restore_opacity(); } }; // Regression test for https://github.com/flutter/flutter/issues/100176. diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index f3388bab4641b..68f845fb85c3f 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -40,7 +40,9 @@ void SkPictureEmbedderViewSlice::render_into(DisplayListBuilder* builder) { } DisplayListEmbedderViewSlice::DisplayListEmbedderViewSlice(SkRect view_bounds) { - recorder_ = std::make_unique(view_bounds); + recorder_ = std::make_unique( + /*bounds=*/view_bounds, + /*prepare_rtree=*/true); } SkCanvas* DisplayListEmbedderViewSlice::canvas() { diff --git a/lib/ui/painting/picture_recorder.cc b/lib/ui/painting/picture_recorder.cc index 8a625603d3a1c..fb382546a8246 100644 --- a/lib/ui/painting/picture_recorder.cc +++ b/lib/ui/painting/picture_recorder.cc @@ -27,7 +27,8 @@ PictureRecorder::PictureRecorder() {} PictureRecorder::~PictureRecorder() {} SkCanvas* PictureRecorder::BeginRecording(SkRect bounds) { - display_list_recorder_ = sk_make_sp(bounds); + display_list_recorder_ = + sk_make_sp(bounds, /*prepare_rtree=*/true); return display_list_recorder_.get(); }