diff --git a/DEPS b/DEPS index f835d603ca363..767f9a6147cfd 100644 --- a/DEPS +++ b/DEPS @@ -627,7 +627,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': '2URwvdZiLGMKsnYKqGCtPo6Vw4fiUnRuEp4OayAc0PQC' + 'version': 'OOBEL73yb3wgbPPtZ_n599QLfJdr2snywov8_9Z39xUC' } ], 'condition': 'host_os == "linux" and not download_fuchsia_sdk', diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 02d16d077bcc6..f77e5485d201b 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -88,6 +88,9 @@ FILE: ../../../flutter/display_list/display_list_mask_filter.h FILE: ../../../flutter/display_list/display_list_mask_filter_unittests.cc FILE: ../../../flutter/display_list/display_list_ops.cc FILE: ../../../flutter/display_list/display_list_ops.h +FILE: ../../../flutter/display_list/display_list_paint.cc +FILE: ../../../flutter/display_list/display_list_paint.h +FILE: ../../../flutter/display_list/display_list_paint_unittests.cc FILE: ../../../flutter/display_list/display_list_test_utils.cc FILE: ../../../flutter/display_list/display_list_test_utils.h FILE: ../../../flutter/display_list/display_list_tile_mode.h diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index fc28d11e22ef6..616537a18ba4d 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: b2e279093e4def5ddc79c152c21376db +Signature: f1b9a633bc9d689703ad6d9b2b9635ab UNUSED LICENSES: diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index 7f00626b93239..678631c4dda15 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -41,6 +41,8 @@ source_set("display_list") { "display_list_mask_filter.h", "display_list_ops.cc", "display_list_ops.h", + "display_list_paint.cc", + "display_list_paint.h", "display_list_tile_mode.h", "display_list_utils.cc", "display_list_utils.h", @@ -69,6 +71,7 @@ source_set("unittests") { "display_list_enum_unittests.cc", "display_list_image_filter_unittests.cc", "display_list_mask_filter_unittests.cc", + "display_list_paint_unittests.cc", "display_list_test_utils.cc", "display_list_test_utils.h", "display_list_unittests.cc", diff --git a/display_list/display_list_attributes.h b/display_list/display_list_attributes.h index 76b2891c4458f..051e4efd65f7c 100644 --- a/display_list/display_list_attributes.h +++ b/display_list/display_list_attributes.h @@ -101,11 +101,11 @@ class DlAttribute { // Return an equivalent sk_sp version of this object. virtual sk_sp skia_object() const = 0; - // Perform a content aware |==| comparison of the ColorFilter. + // Perform a content aware |==| comparison of the Attribute. bool operator==(D const& other) const { return type() == other.type() && equals_(other); } - // Perform a content aware |!=| comparison of the ColorFilter. + // Perform a content aware |!=| comparison of the Attribute. bool operator!=(D const& other) const { return !(*this == other); } virtual ~DlAttribute() = default; diff --git a/display_list/display_list_blend_mode.h b/display_list/display_list_blend_mode.h index 32753daa09b5e..6a84e08879a8f 100644 --- a/display_list/display_list_blend_mode.h +++ b/display_list/display_list_blend_mode.h @@ -63,6 +63,7 @@ enum class DlBlendMode { kLastSeparableMode = kMultiply, //!< last blend mode operating separately on components kLastMode = kLuminosity, //!< last valid value + kDefaultMode = kSrcOver, }; inline DlBlendMode ToDl(SkBlendMode mode) { diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc index d84f26778ae09..fb95d205ac7fd 100644 --- a/display_list/display_list_builder.cc +++ b/display_list/display_list_builder.cc @@ -291,6 +291,49 @@ void DisplayListBuilder::onSetMaskFilter(const DlMaskFilter* filter) { } } +void DisplayListBuilder::setAttributesFromDlPaint( + const DlPaint& paint, + const DisplayListAttributeFlags flags) { + if (flags.applies_anti_alias()) { + setAntiAlias(paint.isAntiAlias()); + } + if (flags.applies_dither()) { + setDither(paint.isDither()); + } + if (flags.applies_alpha_or_color()) { + setColor(paint.getColor().argb); + } + if (flags.applies_blend()) { + setBlendMode(paint.getBlendMode()); + } + if (flags.applies_style()) { + setStyle(ToSk(paint.getDrawStyle())); + } + if (flags.is_stroked(ToSk(paint.getDrawStyle()))) { + setStrokeWidth(paint.getStrokeWidth()); + setStrokeMiter(paint.getStrokeMiter()); + setStrokeCap(ToSk(paint.getStrokeCap())); + setStrokeJoin(ToSk(paint.getStrokeJoin())); + } + if (flags.applies_shader()) { + setColorSource(paint.getColorSource().get()); + } + if (flags.applies_color_filter()) { + setInvertColors(paint.isInvertColors()); + setColorFilter(paint.getColorFilter().get()); + } + if (flags.applies_image_filter()) { + setImageFilter(paint.getImageFilter().get()); + } + // Waiting for https://github.com/flutter/engine/pull/32159 + // if (flags.applies_path_effect()) { + // setPathEffect(sk_ref_sp(paint.getPathEffect())); + // } + if (flags.applies_mask_filter()) { + setMaskFilter(paint.getMaskFilter().get()); + } +} + void DisplayListBuilder::setAttributesFromPaint( const SkPaint& paint, const DisplayListAttributeFlags flags) { @@ -386,6 +429,12 @@ void DisplayListBuilder::restore() { } } } +void DisplayListBuilder::restoreToCount(int restore_count) { + FML_DCHECK(restore_count <= getSaveCount()); + while (restore_count < getSaveCount()) { + restore(); + } +} void DisplayListBuilder::saveLayer(const SkRect* bounds, const SaveLayerOptions in_options) { SaveLayerOptions options = in_options.without_optimizations(); @@ -406,6 +455,15 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, } } } +void DisplayListBuilder::saveLayer(const SkRect* bounds, const DlPaint* paint) { + if (paint != nullptr) { + setAttributesFromDlPaint(*paint, + DisplayListOpFlags::kSaveLayerWithPaintFlags); + saveLayer(bounds, true); + } else { + saveLayer(bounds, false); + } +} void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) { if (SkScalarIsFinite(tx) && SkScalarIsFinite(ty) && @@ -536,6 +594,10 @@ void DisplayListBuilder::drawPaint() { Push(0, 1); CheckLayerOpacityCompatibility(); } +void DisplayListBuilder::drawPaint(const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawPaintFlags); + drawPaint(); +} void DisplayListBuilder::drawColor(SkColor color, DlBlendMode mode) { Push(0, 1, color, mode); CheckLayerOpacityCompatibility(mode); @@ -544,18 +606,38 @@ void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) { Push(0, 1, p0, p1); CheckLayerOpacityCompatibility(); } +void DisplayListBuilder::drawLine(const SkPoint& p0, + const SkPoint& p1, + const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawLineFlags); + drawLine(p0, p1); +} void DisplayListBuilder::drawRect(const SkRect& rect) { Push(0, 1, rect); CheckLayerOpacityCompatibility(); } +void DisplayListBuilder::drawRect(const SkRect& rect, const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawRectFlags); + drawRect(rect); +} void DisplayListBuilder::drawOval(const SkRect& bounds) { Push(0, 1, bounds); CheckLayerOpacityCompatibility(); } +void DisplayListBuilder::drawOval(const SkRect& bounds, const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawOvalFlags); + drawOval(bounds); +} void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius) { Push(0, 1, center, radius); CheckLayerOpacityCompatibility(); } +void DisplayListBuilder::drawCircle(const SkPoint& center, + SkScalar radius, + const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawCircleFlags); + drawCircle(center, radius); +} void DisplayListBuilder::drawRRect(const SkRRect& rrect) { if (rrect.isRect()) { drawRect(rrect.rect()); @@ -566,15 +648,29 @@ void DisplayListBuilder::drawRRect(const SkRRect& rrect) { CheckLayerOpacityCompatibility(); } } +void DisplayListBuilder::drawRRect(const SkRRect& rrect, const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawRRectFlags); + drawRRect(rrect); +} void DisplayListBuilder::drawDRRect(const SkRRect& outer, const SkRRect& inner) { Push(0, 1, outer, inner); CheckLayerOpacityCompatibility(); } +void DisplayListBuilder::drawDRRect(const SkRRect& outer, + const SkRRect& inner, + const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawDRRectFlags); + drawDRRect(outer, inner); +} void DisplayListBuilder::drawPath(const SkPath& path) { Push(0, 1, path); CheckLayerOpacityHairlineCompatibility(); } +void DisplayListBuilder::drawPath(const SkPath& path, const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawPathFlags); + drawPath(path); +} void DisplayListBuilder::drawArc(const SkRect& bounds, SkScalar start, @@ -587,6 +683,15 @@ void DisplayListBuilder::drawArc(const SkRect& bounds, CheckLayerOpacityCompatibility(); } } +void DisplayListBuilder::drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter, + const DlPaint& paint) { + setAttributesFromDlPaint( + paint, useCenter ? kDrawArcWithCenterFlags : kDrawArcNoCenterFlags); + drawArc(bounds, start, sweep, useCenter); +} void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode, uint32_t count, const SkPoint pts[]) { @@ -615,6 +720,28 @@ void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode, // See: https://fiddle.skia.org/c/228459001d2de8db117ce25ef5cedb0c UpdateLayerOpacityCompatibility(false); } +void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[], + const DlPaint& paint) { + const DisplayListAttributeFlags* flags; + switch (mode) { + case SkCanvas::PointMode::kPoints_PointMode: + flags = &DisplayListOpFlags::kDrawPointsAsPointsFlags; + break; + case SkCanvas::PointMode::kLines_PointMode: + flags = &DisplayListOpFlags::kDrawPointsAsLinesFlags; + break; + case SkCanvas::PointMode::kPolygon_PointMode: + flags = &DisplayListOpFlags::kDrawPointsAsPolygonFlags; + break; + default: + FML_DCHECK(false); + return; + } + setAttributesFromDlPaint(paint, *flags); + drawPoints(mode, count, pts); +} void DisplayListBuilder::drawSkVertices(const sk_sp vertices, SkBlendMode mode) { Push(0, 1, std::move(vertices), mode); @@ -634,6 +761,12 @@ void DisplayListBuilder::drawVertices(const DlVertices* vertices, // cases. UpdateLayerOpacityCompatibility(false); } +void DisplayListBuilder::drawVertices(const DlVertices* vertices, + DlBlendMode mode, + const DlPaint& paint) { + setAttributesFromDlPaint(paint, DisplayListOpFlags::kDrawVerticesFlags); + drawVertices(vertices, mode); +} void DisplayListBuilder::drawImage(const sk_sp image, const SkPoint point, @@ -644,6 +777,18 @@ void DisplayListBuilder::drawImage(const sk_sp image, : Push(0, 1, std::move(image), point, sampling); CheckLayerOpacityCompatibility(render_with_attributes); } +void DisplayListBuilder::drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling, + const DlPaint* paint) { + if (paint != nullptr) { + setAttributesFromDlPaint(*paint, + DisplayListOpFlags::kDrawImageWithPaintFlags); + drawImage(image, point, sampling, true); + } else { + drawImage(image, point, sampling, false); + } +} void DisplayListBuilder::drawImageRect(const sk_sp image, const SkRect& src, const SkRect& dst, @@ -654,6 +799,20 @@ void DisplayListBuilder::drawImageRect(const sk_sp image, render_with_attributes, constraint); CheckLayerOpacityCompatibility(render_with_attributes); } +void DisplayListBuilder::drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + const DlPaint* paint, + SkCanvas::SrcRectConstraint constraint) { + if (paint != nullptr) { + setAttributesFromDlPaint(*paint, + DisplayListOpFlags::kDrawImageRectWithPaintFlags); + drawImageRect(image, src, dst, sampling, true, constraint); + } else { + drawImageRect(image, src, dst, sampling, false, constraint); + } +} void DisplayListBuilder::drawImageNine(const sk_sp image, const SkIRect& center, const SkRect& dst, @@ -665,6 +824,19 @@ void DisplayListBuilder::drawImageNine(const sk_sp image, : Push(0, 1, std::move(image), center, dst, filter); CheckLayerOpacityCompatibility(render_with_attributes); } +void DisplayListBuilder::drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter, + const DlPaint* paint) { + if (paint != nullptr) { + setAttributesFromDlPaint(*paint, + DisplayListOpFlags::kDrawImageNineWithPaintFlags); + drawImageNine(image, center, dst, filter, true); + } else { + drawImageNine(image, center, dst, filter, false); + } +} void DisplayListBuilder::drawImageLattice(const sk_sp image, const SkCanvas::Lattice& lattice, const SkRect& dst, @@ -725,6 +897,25 @@ void DisplayListBuilder::drawAtlas(const sk_sp atlas, // of the transforms and texture rectangles. UpdateLayerOpacityCompatibility(false); } +void DisplayListBuilder::drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + DlBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cull_rect, + const DlPaint* paint) { + if (paint != nullptr) { + setAttributesFromDlPaint(*paint, + DisplayListOpFlags::kDrawAtlasWithPaintFlags); + drawAtlas(atlas, xform, tex, colors, count, mode, sampling, cull_rect, + true); + } else { + drawAtlas(atlas, xform, tex, colors, count, mode, sampling, cull_rect, + false); + } +} void DisplayListBuilder::drawPicture(const sk_sp picture, const SkMatrix* matrix, diff --git a/display_list/display_list_builder.h b/display_list/display_list_builder.h index e1437e9913473..15992c0e9a839 100644 --- a/display_list/display_list_builder.h +++ b/display_list/display_list_builder.h @@ -11,6 +11,7 @@ #include "flutter/display_list/display_list_dispatcher.h" #include "flutter/display_list/display_list_flags.h" #include "flutter/display_list/display_list_image.h" +#include "flutter/display_list/display_list_paint.h" #include "flutter/display_list/types.h" #include "flutter/fml/macros.h" @@ -158,8 +159,10 @@ class DisplayListBuilder final : public virtual Dispatcher, ? SaveLayerOptions::kWithAttributes : SaveLayerOptions::kNoAttributes); } + void saveLayer(const SkRect* bounds, const DlPaint* paint); void restore() override; int getSaveCount() { return layer_stack_.size(); } + void restoreToCount(int restore_count); void translate(SkScalar tx, SkScalar ty) override; void scale(SkScalar sx, SkScalar sy) override; @@ -192,21 +195,40 @@ class DisplayListBuilder final : public virtual Dispatcher, void clipPath(const SkPath& path, SkClipOp clip_op, bool is_aa) override; void drawPaint() override; + void drawPaint(const DlPaint& paint); void drawColor(SkColor color, DlBlendMode mode) override; void drawLine(const SkPoint& p0, const SkPoint& p1) override; + void drawLine(const SkPoint& p0, const SkPoint& p1, const DlPaint& paint); void drawRect(const SkRect& rect) override; + void drawRect(const SkRect& rect, const DlPaint& paint); void drawOval(const SkRect& bounds) override; + void drawOval(const SkRect& bounds, const DlPaint& paint); void drawCircle(const SkPoint& center, SkScalar radius) override; + void drawCircle(const SkPoint& center, SkScalar radius, const DlPaint& paint); void drawRRect(const SkRRect& rrect) override; + void drawRRect(const SkRRect& rrect, const DlPaint& paint); void drawDRRect(const SkRRect& outer, const SkRRect& inner) override; + void drawDRRect(const SkRRect& outer, + const SkRRect& inner, + const DlPaint& paint); void drawPath(const SkPath& path) override; + void drawPath(const SkPath& path, const DlPaint& paint); void drawArc(const SkRect& bounds, SkScalar start, SkScalar sweep, bool useCenter) override; + void drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter, + const DlPaint& paint); void drawPoints(SkCanvas::PointMode mode, uint32_t count, const SkPoint pts[]) override; + void drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[], + const DlPaint& paint); void drawSkVertices(const sk_sp vertices, SkBlendMode mode) override; void drawVertices(const DlVertices* vertices, DlBlendMode mode) override; @@ -214,10 +236,22 @@ class DisplayListBuilder final : public virtual Dispatcher, DlBlendMode mode) { drawVertices(vertices.get(), mode); } + void drawVertices(const DlVertices* vertices, + DlBlendMode mode, + const DlPaint& paint); + void drawVertices(const std::shared_ptr vertices, + DlBlendMode mode, + const DlPaint& paint) { + drawVertices(vertices.get(), mode, paint); + } void drawImage(const sk_sp image, const SkPoint point, const SkSamplingOptions& sampling, bool render_with_attributes) override; + void drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling, + const DlPaint* paint = nullptr); void drawImageRect( const sk_sp image, const SkRect& src, @@ -226,11 +260,23 @@ class DisplayListBuilder final : public virtual Dispatcher, bool render_with_attributes, SkCanvas::SrcRectConstraint constraint = SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint) override; + void drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + const DlPaint* paint = nullptr, + SkCanvas::SrcRectConstraint constraint = + SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint); void drawImageNine(const sk_sp image, const SkIRect& center, const SkRect& dst, SkFilterMode filter, bool render_with_attributes) override; + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter, + const DlPaint* paint = nullptr); void drawImageLattice(const sk_sp image, const SkCanvas::Lattice& lattice, const SkRect& dst, @@ -245,6 +291,15 @@ class DisplayListBuilder final : public virtual Dispatcher, const SkSamplingOptions& sampling, const SkRect* cullRect, bool render_with_attributes) override; + void drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + DlBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cullRect, + const DlPaint* paint = nullptr); void drawPicture(const sk_sp picture, const SkMatrix* matrix, bool render_with_attributes) override; @@ -277,6 +332,9 @@ class DisplayListBuilder final : public virtual Dispatcher, template void* Push(size_t extra, int op_inc, Args&&... args); + void setAttributesFromDlPaint(const DlPaint& paint, + const DisplayListAttributeFlags flags); + // kInvalidSigma is used to indicate that no MaskBlur is currently set. static constexpr SkScalar kInvalidSigma = 0.0; static bool mask_sigma_valid(SkScalar sigma) { diff --git a/display_list/display_list_enum_unittests.cc b/display_list/display_list_enum_unittests.cc index dc319e5a9c217..6a664fe6d3e84 100644 --- a/display_list/display_list_enum_unittests.cc +++ b/display_list/display_list_enum_unittests.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/display_list/display_list_blend_mode.h" +#include "flutter/display_list/display_list_paint.h" #include "flutter/display_list/display_list_tile_mode.h" #include "flutter/display_list/display_list_vertices.h" #include "flutter/display_list/types.h" @@ -25,6 +26,44 @@ TEST(DisplayListEnum, ToSkTileMode) { ASSERT_EQ(ToSk(DlTileMode::kDecal), SkTileMode::kDecal); } +TEST(DisplayListEnum, ToDlDrawStyle) { + ASSERT_EQ(ToDl(SkPaint::Style::kFill_Style), DlDrawStyle::kFill); + ASSERT_EQ(ToDl(SkPaint::Style::kStroke_Style), DlDrawStyle::kStroke); + ASSERT_EQ(ToDl(SkPaint::Style::kStrokeAndFill_Style), + DlDrawStyle::kStrokeAndFill); +} + +TEST(DisplayListEnum, ToSkDrawStyle) { + ASSERT_EQ(ToSk(DlDrawStyle::kFill), SkPaint::Style::kFill_Style); + ASSERT_EQ(ToSk(DlDrawStyle::kStroke), SkPaint::Style::kStroke_Style); + ASSERT_EQ(ToSk(DlDrawStyle::kStrokeAndFill), + SkPaint::Style::kStrokeAndFill_Style); +} + +TEST(DisplayListEnum, ToDlStrokeCap) { + ASSERT_EQ(ToDl(SkPaint::Cap::kButt_Cap), DlStrokeCap::kButt); + ASSERT_EQ(ToDl(SkPaint::Cap::kRound_Cap), DlStrokeCap::kRound); + ASSERT_EQ(ToDl(SkPaint::Cap::kSquare_Cap), DlStrokeCap::kSquare); +} + +TEST(DisplayListEnum, ToSkStrokeCap) { + ASSERT_EQ(ToSk(DlStrokeCap::kButt), SkPaint::Cap::kButt_Cap); + ASSERT_EQ(ToSk(DlStrokeCap::kRound), SkPaint::Cap::kRound_Cap); + ASSERT_EQ(ToSk(DlStrokeCap::kSquare), SkPaint::Cap::kSquare_Cap); +} + +TEST(DisplayListEnum, ToDlStrokeJoin) { + ASSERT_EQ(ToDl(SkPaint::Join::kMiter_Join), DlStrokeJoin::kMiter); + ASSERT_EQ(ToDl(SkPaint::Join::kRound_Join), DlStrokeJoin::kRound); + ASSERT_EQ(ToDl(SkPaint::Join::kBevel_Join), DlStrokeJoin::kBevel); +} + +TEST(DisplayListEnum, ToSkStrokeJoin) { + ASSERT_EQ(ToSk(DlStrokeJoin::kMiter), SkPaint::Join::kMiter_Join); + ASSERT_EQ(ToSk(DlStrokeJoin::kRound), SkPaint::Join::kRound_Join); + ASSERT_EQ(ToSk(DlStrokeJoin::kBevel), SkPaint::Join::kBevel_Join); +} + TEST(DisplayListEnum, ToDlVertexMode) { ASSERT_EQ(ToDl(SkVertices::VertexMode::kTriangles_VertexMode), DlVertexMode::kTriangles); diff --git a/display_list/display_list_paint.cc b/display_list/display_list_paint.cc new file mode 100644 index 0000000000000..8e532e0bbd70e --- /dev/null +++ b/display_list/display_list_paint.cc @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/display_list/display_list_paint.h" + +namespace flutter { + +DlPaint::DlPaint() + : blendMode_(static_cast(DlBlendMode::kDefaultMode)), + drawStyle_(static_cast(DlDrawStyle::kDefaultStyle)), + strokeCap_(static_cast(DlStrokeCap::kDefaultCap)), + strokeJoin_(static_cast(DlStrokeJoin::kDefaultJoin)), + isAntiAlias_(false), + isDither_(false), + isInvertColors_(false), + strokeWidth_(0.0), + strokeMiter_(4.0) {} + +bool DlPaint::operator==(DlPaint const& other) const { + return blendMode_ == other.blendMode_ && // + drawStyle_ == other.drawStyle_ && // + strokeCap_ == other.strokeCap_ && // + strokeJoin_ == other.strokeJoin_ && // + isAntiAlias_ == other.isAntiAlias_ && // + isDither_ == other.isDither_ && // + isInvertColors_ == other.isInvertColors_ && // + color_ == other.color_ && // + strokeWidth_ == other.strokeWidth_ && // + strokeMiter_ == other.strokeMiter_ && // + Equals(colorSource_, other.colorSource_) && // + Equals(colorFilter_, other.colorFilter_) && // + Equals(imageFilter_, other.imageFilter_) && // + Equals(maskFilter_, other.maskFilter_); +} + +} // namespace flutter diff --git a/display_list/display_list_paint.h b/display_list/display_list_paint.h new file mode 100644 index 0000000000000..c23d94a198fbb --- /dev/null +++ b/display_list/display_list_paint.h @@ -0,0 +1,218 @@ +// 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_DISPLAY_LIST_PAINT_H_ +#define FLUTTER_DISPLAY_LIST_DISPLAY_LIST_PAINT_H_ + +#include "flutter/display_list/display_list_blend_mode.h" +#include "flutter/display_list/display_list_color_filter.h" +#include "flutter/display_list/display_list_color_source.h" +#include "flutter/display_list/display_list_image_filter.h" +#include "flutter/display_list/display_list_mask_filter.h" + +namespace flutter { + +class DlColor { + public: + DlColor() : argb(0xFF000000) {} + DlColor(uint32_t argb) : argb(argb) {} + + uint32_t argb; + + bool operator==(DlColor const& other) const { return argb == other.argb; } + bool operator!=(DlColor const& other) const { return argb != other.argb; } +}; + +enum class DlDrawStyle { + kFill, //!< fills interior of shapes + kStroke, //!< strokes boundary of shapes + kStrokeAndFill, //!< both strokes and fills shapes + + kLastStyle = kStrokeAndFill, + kDefaultStyle = kFill, +}; + +inline DlDrawStyle ToDl(SkPaint::Style style) { + return static_cast(style); +} + +inline SkPaint::Style ToSk(DlDrawStyle style) { + return static_cast(style); +} + +enum class DlStrokeCap { + kButt, //!< no stroke extension + kRound, //!< adds circle + kSquare, //!< adds square + + kLastCap = kSquare, + kDefaultCap = kButt, +}; + +inline DlStrokeCap ToDl(SkPaint::Cap cap) { + return static_cast(cap); +} + +inline SkPaint::Cap ToSk(DlStrokeCap cap) { + return static_cast(cap); +} + +enum class DlStrokeJoin { + kMiter, //!< extends to miter limit + kRound, //!< adds circle + kBevel, //!< connects outside edges + + kLastJoin = kBevel, + kDefaultJoin = kMiter, +}; + +inline DlStrokeJoin ToDl(SkPaint::Join join) { + return static_cast(join); +} + +inline SkPaint::Join ToSk(DlStrokeJoin join) { + return static_cast(join); +} + +class DlPaint { + public: + DlPaint(); + + bool isAntiAlias() const { return isAntiAlias_; } + DlPaint& setAntiAlias(bool isAntiAlias) { + isAntiAlias_ = isAntiAlias; + return *this; + } + + bool isDither() const { return isDither_; } + DlPaint& setDither(bool isDither) { + isDither_ = isDither; + return *this; + } + + bool isInvertColors() const { return isInvertColors_; } + DlPaint& setInvertColors(bool isInvertColors) { + isInvertColors_ = isInvertColors; + return *this; + } + + DlColor getColor() const { return color_; } + DlPaint& setColor(DlColor color) { + color_.argb = color.argb; + return *this; + } + + DlBlendMode getBlendMode() const { + return static_cast(blendMode_); + } + DlPaint& setBlendMode(DlBlendMode mode) { + blendMode_ = static_cast(mode); + return *this; + } + + DlDrawStyle getDrawStyle() const { + return static_cast(drawStyle_); + } + DlPaint& setDrawStyle(DlDrawStyle style) { + drawStyle_ = static_cast(style); + return *this; + } + + DlStrokeCap getStrokeCap() const { + return static_cast(strokeCap_); + } + DlPaint& setStrokeCap(DlStrokeCap cap) { + strokeCap_ = static_cast(cap); + return *this; + } + + DlStrokeJoin getStrokeJoin() const { + return static_cast(strokeJoin_); + } + DlPaint& setStrokeJoin(DlStrokeJoin join) { + strokeJoin_ = static_cast(join); + return *this; + } + + float getStrokeWidth() const { return strokeWidth_; } + DlPaint& setStrokeWidth(float width) { + strokeWidth_ = width; + return *this; + } + + float getStrokeMiter() const { return strokeMiter_; } + DlPaint& setStrokeMiter(float miter) { + strokeMiter_ = miter; + return *this; + } + + std::shared_ptr getColorSource() const { return colorSource_; } + DlPaint& setColorSource(std::shared_ptr source) { + colorSource_ = source; + return *this; + } + + std::shared_ptr getColorFilter() const { return colorFilter_; } + DlPaint& setColorFilter(std::shared_ptr filter) { + colorFilter_ = filter; + return *this; + } + + std::shared_ptr getImageFilter() const { return imageFilter_; } + DlPaint& setImageFilter(std::shared_ptr filter) { + imageFilter_ = filter; + return *this; + } + + std::shared_ptr getMaskFilter() const { return maskFilter_; } + DlPaint& setMaskFilter(std::shared_ptr filter) { + maskFilter_ = filter; + return *this; + } + + bool operator==(DlPaint const& other) const; + bool operator!=(DlPaint const& other) const { return !(*this == other); } + + private: +#define ASSERT_ENUM_FITS(last_enum, num_bits) \ + static_assert(static_cast(last_enum) < (1 << num_bits) && \ + static_cast(last_enum) * 2 >= (1 << num_bits)) + + static constexpr int kBlendModeBits = 5; + static constexpr int kDrawStyleBits = 2; + static constexpr int kStrokeCapBits = 2; + static constexpr int kStrokeJoinBits = 2; + ASSERT_ENUM_FITS(DlBlendMode::kLastMode, kBlendModeBits); + ASSERT_ENUM_FITS(DlDrawStyle::kLastStyle, kDrawStyleBits); + ASSERT_ENUM_FITS(DlStrokeCap::kLastCap, kStrokeCapBits); + ASSERT_ENUM_FITS(DlStrokeJoin::kLastJoin, kStrokeJoinBits); + + union { + struct { + unsigned blendMode_ : kBlendModeBits; + unsigned drawStyle_ : kDrawStyleBits; + unsigned strokeCap_ : kStrokeCapBits; + unsigned strokeJoin_ : kStrokeJoinBits; + unsigned isAntiAlias_ : 1; + unsigned isDither_ : 1; + unsigned isInvertColors_ : 1; + }; + }; + + DlColor color_; + float strokeWidth_; + float strokeMiter_; + + std::shared_ptr colorSource_; + std::shared_ptr colorFilter_; + std::shared_ptr imageFilter_; + std::shared_ptr maskFilter_; + // missing (as compared to SkPaint): + // DlPathEffect - waiting for https://github.com/flutter/engine/pull/32159 + // DlBlender - not planning on using that object in a pure DisplayList world +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_DISPLAY_LIST_PAINT_H_ diff --git a/display_list/display_list_paint_unittests.cc b/display_list/display_list_paint_unittests.cc new file mode 100644 index 0000000000000..fcea32e455198 --- /dev/null +++ b/display_list/display_list_paint_unittests.cc @@ -0,0 +1,106 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/display_list/display_list_paint.h" + +#include "flutter/display_list/display_list_comparable.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(DisplayListPaint, ConstructorDefaults) { + DlPaint paint; + EXPECT_FALSE(paint.isAntiAlias()); + EXPECT_FALSE(paint.isDither()); + EXPECT_FALSE(paint.isInvertColors()); + EXPECT_EQ(paint.getColor(), DlColor{0xFF000000}); + EXPECT_EQ(paint.getBlendMode(), DlBlendMode::kDefaultMode); + EXPECT_EQ(paint.getDrawStyle(), DlDrawStyle::kDefaultStyle); + EXPECT_EQ(paint.getStrokeCap(), DlStrokeCap::kDefaultCap); + EXPECT_EQ(paint.getStrokeJoin(), DlStrokeJoin::kDefaultJoin); + EXPECT_EQ(paint.getStrokeWidth(), 0.0); + EXPECT_EQ(paint.getStrokeMiter(), 4.0); + EXPECT_EQ(paint.getColorSource(), nullptr); + EXPECT_EQ(paint.getColorFilter(), nullptr); + EXPECT_EQ(paint.getImageFilter(), nullptr); + EXPECT_EQ(paint.getMaskFilter(), nullptr); + + EXPECT_EQ(DlBlendMode::kDefaultMode, DlBlendMode::kSrcOver); + EXPECT_EQ(DlDrawStyle::kDefaultStyle, DlDrawStyle::kFill); + EXPECT_EQ(DlStrokeCap::kDefaultCap, DlStrokeCap::kButt); + EXPECT_EQ(DlStrokeJoin::kDefaultJoin, DlStrokeJoin::kMiter); + + EXPECT_EQ(paint, DlPaint()); + + EXPECT_NE(paint, DlPaint().setAntiAlias(true)); + EXPECT_NE(paint, DlPaint().setDither(true)); + EXPECT_NE(paint, DlPaint().setInvertColors(true)); + EXPECT_NE(paint, DlPaint().setColor(DlColor(0xFF00FF00))); + EXPECT_NE(paint, DlPaint().setBlendMode(DlBlendMode::kDstIn)); + EXPECT_NE(paint, DlPaint().setDrawStyle(DlDrawStyle::kStrokeAndFill)); + EXPECT_NE(paint, DlPaint().setStrokeCap(DlStrokeCap::kRound)); + EXPECT_NE(paint, DlPaint().setStrokeJoin(DlStrokeJoin::kRound)); + EXPECT_NE(paint, DlPaint().setStrokeWidth(6)); + EXPECT_NE(paint, DlPaint().setStrokeMiter(7)); + + DlColorColorSource colorSource(0xFFFF00FF); + EXPECT_NE(paint, DlPaint().setColorSource(colorSource.shared())); + + DlBlendColorFilter colorFilter(0xFFFFFF00, SkBlendMode::kDstIn); + EXPECT_NE(paint, DlPaint().setColorFilter(colorFilter.shared())); + + DlBlurImageFilter imageFilter(1.3, 4.7, DlTileMode::kClamp); + EXPECT_NE(paint, DlPaint().setImageFilter(imageFilter.shared())); + + DlBlurMaskFilter maskFilter(SkBlurStyle::kInner_SkBlurStyle, 3.14); + EXPECT_NE(paint, DlPaint().setMaskFilter(maskFilter.shared())); +} + +TEST(DisplayListPaint, ChainingConstructor) { + DlPaint paint = + DlPaint() // + .setAntiAlias(true) // + .setDither(true) // + .setInvertColors(true) // + .setColor({0xFF00FF00}) // + .setBlendMode(DlBlendMode::kLuminosity) // + .setDrawStyle(DlDrawStyle::kStrokeAndFill) // + .setStrokeCap(DlStrokeCap::kSquare) // + .setStrokeJoin(DlStrokeJoin::kBevel) // + .setStrokeWidth(42) // + .setStrokeMiter(1.5) // + .setColorSource(DlColorColorSource(0xFFFF00FF).shared()) // + .setColorFilter( + DlBlendColorFilter(0xFFFFFF00, SkBlendMode::kDstIn).shared()) + .setImageFilter( + DlBlurImageFilter(1.3, 4.7, DlTileMode::kClamp).shared()) + .setMaskFilter( + DlBlurMaskFilter(SkBlurStyle::kInner_SkBlurStyle, 3.14).shared()); + EXPECT_TRUE(paint.isAntiAlias()); + EXPECT_TRUE(paint.isDither()); + EXPECT_TRUE(paint.isInvertColors()); + EXPECT_EQ(paint.getColor(), DlColor{0xFF00FF00}); + EXPECT_EQ(paint.getBlendMode(), DlBlendMode::kLuminosity); + EXPECT_EQ(paint.getDrawStyle(), DlDrawStyle::kStrokeAndFill); + EXPECT_EQ(paint.getStrokeCap(), DlStrokeCap::kSquare); + EXPECT_EQ(paint.getStrokeJoin(), DlStrokeJoin::kBevel); + EXPECT_EQ(paint.getStrokeWidth(), 42); + EXPECT_EQ(paint.getStrokeMiter(), 1.5); + EXPECT_TRUE( + Equals(paint.getColorSource(), DlColorColorSource(0xFFFF00FF).shared())); + EXPECT_TRUE( + Equals(paint.getColorFilter(), + DlBlendColorFilter(0xFFFFFF00, SkBlendMode::kDstIn).shared())); + EXPECT_TRUE(Equals(paint.getImageFilter(), + DlBlurImageFilter(1.3, 4.7, DlTileMode::kClamp).shared())); + EXPECT_TRUE( + Equals(paint.getMaskFilter(), + DlBlurMaskFilter(SkBlurStyle::kInner_SkBlurStyle, 3.14).shared())); + + EXPECT_NE(paint, DlPaint()); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/clip_path_layer.cc b/flow/layers/clip_path_layer.cc index 5a4ad3044142e..b04f011ad7af5 100644 --- a/flow/layers/clip_path_layer.cc +++ b/flow/layers/clip_path_layer.cc @@ -10,7 +10,6 @@ namespace flutter { ClipPathLayer::ClipPathLayer(const SkPath& clip_path, Clip clip_behavior) : clip_path_(clip_path), clip_behavior_(clip_behavior) { FML_DCHECK(clip_behavior != Clip::none); - set_layer_can_inherit_opacity(true); } void ClipPathLayer::Diff(DiffContext* context, const Layer* old_layer) { @@ -41,12 +40,22 @@ void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipPath(clip_path_); + // Collect inheritance information on our children in Preroll so that + // we can pass it along by default. + context->subtree_can_inherit_opacity = true; + SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); if (child_paint_bounds.intersect(clip_path_bounds)) { set_paint_bounds(child_paint_bounds); } + // If we use a SaveLayer then we can accept opacity on behalf + // of our children and apply it in the saveLayer. + if (UsesSaveLayer()) { + context->subtree_can_inherit_opacity = true; + } + context->mutators_stack.Pop(); context->cull_rect = previous_cull_rect; } diff --git a/flow/layers/clip_path_layer_unittests.cc b/flow/layers/clip_path_layer_unittests.cc index 90ecbc3d698fa..b71403707a3ec 100644 --- a/flow/layers/clip_path_layer_unittests.cc +++ b/flow/layers/clip_path_layer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/clip_path_layer.h" +#include "flutter/flow/layers/opacity_layer.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -262,5 +263,236 @@ TEST_F(ClipPathLayerTest, Readback) { EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); } +TEST_F(ClipPathLayerTest, OpacityInheritance) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto layer_clip = SkPath() + .addRect(SkRect::MakeLTRB(5, 5, 25, 25)) + .addOval(SkRect::MakeLTRB(20, 20, 40, 50)); + auto clip_path_layer = + std::make_shared(layer_clip, Clip::hardEdge); + clip_path_layer->Add(mock1); + + // ClipRectLayer will pass through compatibility from a compatible child + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_path_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + clip_path_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + context->subtree_can_inherit_opacity = false; + clip_path_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path3 = SkPath().addRect({20, 20, 40, 40}); + auto mock3 = MockLayer::MakeOpacityCompatible(path3); + clip_path_layer->Add(mock3); + + // ClipRectLayer will not pass through compatibility from multiple + // overlapping children even if they are individually compatible + context->subtree_can_inherit_opacity = false; + clip_path_layer->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + { + // ClipRectLayer(aa with saveLayer) will always be compatible + auto clip_path_saveLayer = std::make_shared( + layer_clip, Clip::antiAliasWithSaveLayer); + clip_path_saveLayer->Add(mock1); + clip_path_saveLayer->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_path_saveLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + // Now add the overlapping child and test again, should still be compatible + clip_path_saveLayer->Add(mock3); + context->subtree_can_inherit_opacity = false; + clip_path_saveLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + } + + // An incompatible, but non-overlapping child for the following tests + auto path4 = SkPath().addRect({60, 60, 70, 70}); + auto mock4 = MockLayer::Make(path4); + + { + // ClipRectLayer with incompatible child will not be compatible + auto clip_path_bad_child = + std::make_shared(layer_clip, Clip::hardEdge); + clip_path_bad_child->Add(mock1); + clip_path_bad_child->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_path_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + clip_path_bad_child->Add(mock4); + + // The third child is non-overlapping, but not compatible so the + // TransformLayer should end up incompatible + context->subtree_can_inherit_opacity = false; + clip_path_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + } + + { + // ClipRectLayer(aa with saveLayer) will always be compatible + auto clip_path_saveLayer_bad_child = std::make_shared( + layer_clip, Clip::antiAliasWithSaveLayer); + clip_path_saveLayer_bad_child->Add(mock1); + clip_path_saveLayer_bad_child->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_path_saveLayer_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + // Now add the incompatible child and test again, should still be compatible + clip_path_saveLayer_bad_child->Add(mock4); + context->subtree_can_inherit_opacity = false; + clip_path_saveLayer_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + } +} + +TEST_F(ClipPathLayerTest, OpacityInheritancePainting) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + auto layer_clip = SkPath() + .addRect(SkRect::MakeLTRB(5, 5, 25, 25)) + .addOval(SkRect::MakeLTRB(20, 20, 40, 50)); + auto clip_path_layer = + std::make_shared(layer_clip, Clip::antiAlias); + clip_path_layer->Add(mock1); + clip_path_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_path_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(clip_path_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY); + DisplayListBuilder expected_builder; + /* OpacityLayer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* ClipRectLayer::Paint() */ { + expected_builder.save(); + expected_builder.clipPath(layer_clip, SkClipOp::kIntersect, true); + /* child layer1 paint */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&path1.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path1); + } + expected_builder.restore(); + } + /* child layer2 paint */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&path2.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path2); + } + expected_builder.restore(); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + +TEST_F(ClipPathLayerTest, OpacityInheritanceSaveLayerPainting) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto path2 = SkPath().addRect({20, 20, 40, 40}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + auto children_bounds = path1.getBounds(); + children_bounds.join(path2.getBounds()); + auto layer_clip = SkPath() + .addRect(SkRect::MakeLTRB(5, 5, 25, 25)) + .addOval(SkRect::MakeLTRB(20, 20, 40, 50)); + auto clip_path_layer = + std::make_shared(layer_clip, Clip::antiAliasWithSaveLayer); + clip_path_layer->Add(mock1); + clip_path_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_path_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(clip_path_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY); + DisplayListBuilder expected_builder; + /* OpacityLayer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* ClipRectLayer::Paint() */ { + expected_builder.save(); + expected_builder.clipPath(layer_clip, SkClipOp::kIntersect, true); + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&children_bounds, true); + /* child layer1 paint */ { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path1); + } + /* child layer2 paint */ { // + expected_builder.drawPath(path2); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/clip_rect_layer.cc b/flow/layers/clip_rect_layer.cc index cdb5531283bde..d7697680f8c6b 100644 --- a/flow/layers/clip_rect_layer.cc +++ b/flow/layers/clip_rect_layer.cc @@ -10,7 +10,6 @@ namespace flutter { ClipRectLayer::ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior) : clip_rect_(clip_rect), clip_behavior_(clip_behavior) { FML_DCHECK(clip_behavior != Clip::none); - set_layer_can_inherit_opacity(true); } void ClipRectLayer::Diff(DiffContext* context, const Layer* old_layer) { @@ -40,12 +39,22 @@ void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipRect(clip_rect_); + // Collect inheritance information on our children in Preroll so that + // we can pass it along by default. + context->subtree_can_inherit_opacity = true; + SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); if (child_paint_bounds.intersect(clip_rect_)) { set_paint_bounds(child_paint_bounds); } + // If we use a SaveLayer then we can accept opacity on behalf + // of our children and apply it in the saveLayer. + if (UsesSaveLayer()) { + context->subtree_can_inherit_opacity = true; + } + context->mutators_stack.Pop(); context->cull_rect = previous_cull_rect; } diff --git a/flow/layers/clip_rect_layer_unittests.cc b/flow/layers/clip_rect_layer_unittests.cc index 098322f04a282..bb8607a95b30e 100644 --- a/flow/layers/clip_rect_layer_unittests.cc +++ b/flow/layers/clip_rect_layer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/clip_rect_layer.h" +#include "flutter/flow/layers/opacity_layer.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -260,5 +261,228 @@ TEST_F(ClipRectLayerTest, Readback) { EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); } +TEST_F(ClipRectLayerTest, OpacityInheritance) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + SkRect clip_rect = SkRect::MakeWH(500, 500); + auto clip_rect_layer = + std::make_shared(clip_rect, Clip::hardEdge); + clip_rect_layer->Add(mock1); + + // ClipRectLayer will pass through compatibility from a compatible child + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + clip_rect_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + context->subtree_can_inherit_opacity = false; + clip_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path3 = SkPath().addRect({20, 20, 40, 40}); + auto mock3 = MockLayer::MakeOpacityCompatible(path3); + clip_rect_layer->Add(mock3); + + // ClipRectLayer will not pass through compatibility from multiple + // overlapping children even if they are individually compatible + context->subtree_can_inherit_opacity = false; + clip_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + { + // ClipRectLayer(aa with saveLayer) will always be compatible + auto clip_rect_saveLayer = std::make_shared( + clip_rect, Clip::antiAliasWithSaveLayer); + clip_rect_saveLayer->Add(mock1); + clip_rect_saveLayer->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_rect_saveLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + // Now add the overlapping child and test again, should still be compatible + clip_rect_saveLayer->Add(mock3); + context->subtree_can_inherit_opacity = false; + clip_rect_saveLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + } + + // An incompatible, but non-overlapping child for the following tests + auto path4 = SkPath().addRect({60, 60, 70, 70}); + auto mock4 = MockLayer::Make(path4); + + { + // ClipRectLayer with incompatible child will not be compatible + auto clip_rect_bad_child = + std::make_shared(clip_rect, Clip::hardEdge); + clip_rect_bad_child->Add(mock1); + clip_rect_bad_child->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_rect_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + clip_rect_bad_child->Add(mock4); + + // The third child is non-overlapping, but not compatible so the + // TransformLayer should end up incompatible + context->subtree_can_inherit_opacity = false; + clip_rect_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + } + + { + // ClipRectLayer(aa with saveLayer) will always be compatible + auto clip_rect_saveLayer_bad_child = std::make_shared( + clip_rect, Clip::antiAliasWithSaveLayer); + clip_rect_saveLayer_bad_child->Add(mock1); + clip_rect_saveLayer_bad_child->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_rect_saveLayer_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + // Now add the incompatible child and test again, should still be compatible + clip_rect_saveLayer_bad_child->Add(mock4); + context->subtree_can_inherit_opacity = false; + clip_rect_saveLayer_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + } +} + +TEST_F(ClipRectLayerTest, OpacityInheritancePainting) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + SkRect clip_rect = SkRect::MakeWH(500, 500); + auto clip_rect_layer = + std::make_shared(clip_rect, Clip::antiAlias); + clip_rect_layer->Add(mock1); + clip_rect_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(clip_rect_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY); + DisplayListBuilder expected_builder; + /* OpacityLayer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* ClipRectLayer::Paint() */ { + expected_builder.save(); + expected_builder.clipRect(clip_rect, SkClipOp::kIntersect, true); + /* child layer1 paint */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&path1.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path1); + } + expected_builder.restore(); + } + /* child layer2 paint */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&path2.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path2); + } + expected_builder.restore(); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + +TEST_F(ClipRectLayerTest, OpacityInheritanceSaveLayerPainting) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto path2 = SkPath().addRect({20, 20, 40, 40}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + SkRect clip_rect = SkRect::MakeWH(500, 500); + auto clip_rect_layer = + std::make_shared(clip_rect, Clip::antiAliasWithSaveLayer); + clip_rect_layer->Add(mock1); + clip_rect_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(clip_rect_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY); + DisplayListBuilder expected_builder; + /* OpacityLayer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* ClipRectLayer::Paint() */ { + expected_builder.save(); + expected_builder.clipRect(clip_rect, SkClipOp::kIntersect, true); + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&clip_rect, true); + /* child layer1 paint */ { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path1); + } + /* child layer2 paint */ { // + expected_builder.drawPath(path2); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/clip_rrect_layer.cc b/flow/layers/clip_rrect_layer.cc index 52377ae984507..792acd33f5b09 100644 --- a/flow/layers/clip_rrect_layer.cc +++ b/flow/layers/clip_rrect_layer.cc @@ -10,7 +10,6 @@ namespace flutter { ClipRRectLayer::ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior) : clip_rrect_(clip_rrect), clip_behavior_(clip_behavior) { FML_DCHECK(clip_behavior != Clip::none); - set_layer_can_inherit_opacity(true); } void ClipRRectLayer::Diff(DiffContext* context, const Layer* old_layer) { @@ -41,12 +40,22 @@ void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipRRect(clip_rrect_); + // Collect inheritance information on our children in Preroll so that + // we can pass it along by default. + context->subtree_can_inherit_opacity = true; + SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); if (child_paint_bounds.intersect(clip_rrect_bounds)) { set_paint_bounds(child_paint_bounds); } + // If we use a SaveLayer then we can accept opacity on behalf + // of our children and apply it in the saveLayer. + if (UsesSaveLayer()) { + context->subtree_can_inherit_opacity = true; + } + context->mutators_stack.Pop(); context->cull_rect = previous_cull_rect; } diff --git a/flow/layers/clip_rrect_layer_unittests.cc b/flow/layers/clip_rrect_layer_unittests.cc index 0391e0309b21e..69a7085174970 100644 --- a/flow/layers/clip_rrect_layer_unittests.cc +++ b/flow/layers/clip_rrect_layer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/clip_rrect_layer.h" +#include "flutter/flow/layers/opacity_layer.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -265,5 +266,233 @@ TEST_F(ClipRRectLayerTest, Readback) { EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); } +TEST_F(ClipRRectLayerTest, OpacityInheritance) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + SkRect clip_rect = SkRect::MakeWH(500, 500); + SkRRect clip_r_rect = SkRRect::MakeRectXY(clip_rect, 20, 20); + auto clip_r_rect_layer = + std::make_shared(clip_r_rect, Clip::hardEdge); + clip_r_rect_layer->Add(mock1); + + // ClipRectLayer will pass through compatibility from a compatible child + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_r_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + clip_r_rect_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + context->subtree_can_inherit_opacity = false; + clip_r_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path3 = SkPath().addRect({20, 20, 40, 40}); + auto mock3 = MockLayer::MakeOpacityCompatible(path3); + clip_r_rect_layer->Add(mock3); + + // ClipRectLayer will not pass through compatibility from multiple + // overlapping children even if they are individually compatible + context->subtree_can_inherit_opacity = false; + clip_r_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + { + // ClipRectLayer(aa with saveLayer) will always be compatible + auto clip_r_rect_saveLayer = std::make_shared( + clip_r_rect, Clip::antiAliasWithSaveLayer); + clip_r_rect_saveLayer->Add(mock1); + clip_r_rect_saveLayer->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_r_rect_saveLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + // Now add the overlapping child and test again, should still be compatible + clip_r_rect_saveLayer->Add(mock3); + context->subtree_can_inherit_opacity = false; + clip_r_rect_saveLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + } + + // An incompatible, but non-overlapping child for the following tests + auto path4 = SkPath().addRect({60, 60, 70, 70}); + auto mock4 = MockLayer::Make(path4); + + { + // ClipRectLayer with incompatible child will not be compatible + auto clip_r_rect_bad_child = + std::make_shared(clip_r_rect, Clip::hardEdge); + clip_r_rect_bad_child->Add(mock1); + clip_r_rect_bad_child->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_r_rect_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + clip_r_rect_bad_child->Add(mock4); + + // The third child is non-overlapping, but not compatible so the + // TransformLayer should end up incompatible + context->subtree_can_inherit_opacity = false; + clip_r_rect_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + } + + { + // ClipRectLayer(aa with saveLayer) will always be compatible + auto clip_r_rect_saveLayer_bad_child = std::make_shared( + clip_r_rect, Clip::antiAliasWithSaveLayer); + clip_r_rect_saveLayer_bad_child->Add(mock1); + clip_r_rect_saveLayer_bad_child->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + clip_r_rect_saveLayer_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + // Now add the incompatible child and test again, should still be compatible + clip_r_rect_saveLayer_bad_child->Add(mock4); + context->subtree_can_inherit_opacity = false; + clip_r_rect_saveLayer_bad_child->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + } +} + +TEST_F(ClipRRectLayerTest, OpacityInheritancePainting) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + SkRect clip_rect = SkRect::MakeWH(500, 500); + SkRRect clip_r_rect = SkRRect::MakeRectXY(clip_rect, 20, 20); + auto clip_rect_layer = + std::make_shared(clip_r_rect, Clip::antiAlias); + clip_rect_layer->Add(mock1); + clip_rect_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(clip_rect_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY); + DisplayListBuilder expected_builder; + /* OpacityLayer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* ClipRectLayer::Paint() */ { + expected_builder.save(); + expected_builder.clipRRect(clip_r_rect, SkClipOp::kIntersect, true); + /* child layer1 paint */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&path1.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path1); + } + expected_builder.restore(); + } + /* child layer2 paint */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&path2.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path2); + } + expected_builder.restore(); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + +TEST_F(ClipRRectLayerTest, OpacityInheritanceSaveLayerPainting) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto path2 = SkPath().addRect({20, 20, 40, 40}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + auto children_bounds = path1.getBounds(); + children_bounds.join(path2.getBounds()); + SkRect clip_rect = SkRect::MakeWH(500, 500); + SkRRect clip_r_rect = SkRRect::MakeRectXY(clip_rect, 20, 20); + auto clip_r_rect_layer = std::make_shared( + clip_r_rect, Clip::antiAliasWithSaveLayer); + clip_r_rect_layer->Add(mock1); + clip_r_rect_layer->Add(mock2); + + // ClipRectLayer will pass through compatibility from multiple + // non-overlapping compatible children + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + clip_r_rect_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(clip_r_rect_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY); + DisplayListBuilder expected_builder; + /* OpacityLayer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* ClipRectLayer::Paint() */ { + expected_builder.save(); + expected_builder.clipRRect(clip_r_rect, SkClipOp::kIntersect, true); + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&children_bounds, true); + /* child layer1 paint */ { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path1); + } + /* child layer2 paint */ { // + expected_builder.drawPath(path2); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/color_filter_layer.cc b/flow/layers/color_filter_layer.cc index 6f33b70981e27..17de94b4bd644 100644 --- a/flow/layers/color_filter_layer.cc +++ b/flow/layers/color_filter_layer.cc @@ -30,6 +30,10 @@ void ColorFilterLayer::Preroll(PrerollContext* context, Layer::AutoPrerollSaveLayerState::Create(context); ContainerLayer::Preroll(context, matrix); + // We always use a saveLayer (or a cached rendering), so we + // can always apply opacity in those cases. + context->subtree_can_inherit_opacity = true; + if (render_count_ >= kMinimumRendersBeforeCachingFilterLayer) { TryToPrepareRasterCache(context, this, matrix, RasterCacheLayerStrategy::kLayer); @@ -44,26 +48,27 @@ void ColorFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ColorFilterLayer::Paint"); FML_DCHECK(needs_painting(context)); + AutoCachePaint cache_paint(context); + if (context.raster_cache) { if (context.raster_cache->Draw(this, *context.leaf_nodes_canvas, - RasterCacheLayerStrategy::kLayer)) { + RasterCacheLayerStrategy::kLayer, + cache_paint.paint())) { return; } - SkPaint paint; - paint.setColorFilter(filter_); + cache_paint.setColorFilter(filter_); if (context.raster_cache->Draw(this, *context.leaf_nodes_canvas, RasterCacheLayerStrategy::kLayerChildren, - &paint)) { + cache_paint.paint())) { return; } } - SkPaint paint; - paint.setColorFilter(filter_); + cache_paint.setColorFilter(filter_); - Layer::AutoSaveLayer save = - Layer::AutoSaveLayer::Create(context, paint_bounds(), &paint); + Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( + context, paint_bounds(), cache_paint.paint()); PaintChildren(context); } diff --git a/flow/layers/color_filter_layer_unittests.cc b/flow/layers/color_filter_layer_unittests.cc index f721a5946a1d0..6cc9c8cfaca2e 100644 --- a/flow/layers/color_filter_layer_unittests.cc +++ b/flow/layers/color_filter_layer_unittests.cc @@ -4,6 +4,8 @@ #include "flutter/flow/layers/color_filter_layer.h" +#include "flutter/display_list/display_list_color_filter.h" +#include "flutter/flow/layers/opacity_layer.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -317,5 +319,67 @@ TEST_F(ColorFilterLayerTest, CacheChildren) { RasterCacheLayerStrategy::kLayerChildren)); } +TEST_F(ColorFilterLayerTest, OpacityInheritance) { + // clang-format off + float matrix[20] = { + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + }; + // clang-format on + auto layer_filter = DlMatrixColorFilter(matrix); + auto initial_transform = SkMatrix::Translate(50.0, 25.5); + const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); + auto mock_layer = std::make_shared(child_path); + auto color_filter_layer = + std::make_shared(layer_filter.skia_object()); + color_filter_layer->Add(mock_layer); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + color_filter_layer->Preroll(preroll_context(), initial_transform); + // ImageFilterLayers can always inherit opacity whether or not their + // children are compatible. + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(color_filter_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Translate(offset.fX, offset.fY)); + DisplayListBuilder expected_builder; + /* opacity_layer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* image_filter_layer::Paint() */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.setColorFilter(&layer_filter); + expected_builder.saveLayer(&child_path.getBounds(), true); + /* mock_layer::Paint() */ { + expected_builder.setColor(0xFF000000); + expected_builder.setColorFilter(nullptr); + expected_builder.drawPath(child_path); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index 36ea6ac883efa..7e9820e4aa1ac 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -136,16 +136,16 @@ void ContainerLayer::PrerollChildren(PrerollContext* context, FML_DCHECK(!context->has_platform_view); bool child_has_platform_view = false; bool child_has_texture_layer = false; - bool subtree_can_inherit_opacity = layer_can_inherit_opacity(); + bool subtree_can_inherit_opacity = context->subtree_can_inherit_opacity; for (auto& layer : layers_) { // Reset context->has_platform_view to false so that layers aren't treated // as if they have a platform view based on one being previously found in a // sibling tree. context->has_platform_view = false; - // Initialize the "inherit opacity" flag to the value recorded in the layer - // and allow it to override the answer during its |Preroll| - context->subtree_can_inherit_opacity = layer->layer_can_inherit_opacity(); + // Initialize the "inherit opacity" flag to false and allow the layer to + // override the answer during its |Preroll| + context->subtree_can_inherit_opacity = false; layer->Preroll(context, child_matrix); diff --git a/flow/layers/container_layer_unittests.cc b/flow/layers/container_layer_unittests.cc index 7fd4b32bb6cbe..8573c0ee957a9 100644 --- a/flow/layers/container_layer_unittests.cc +++ b/flow/layers/container_layer_unittests.cc @@ -192,6 +192,61 @@ TEST_F(ContainerLayerTest, NeedsSystemComposite) { child_path2, child_paint2}}})); } +TEST_F(ContainerLayerTest, OpacityInheritance) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto container1 = std::make_shared(); + container1->Add(mock1); + + // ContainerLayer will not pass through compatibility on its own + // Subclasses must explicitly enable this in their own Preroll + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + container1->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + container1->Add(mock2); + + // ContainerLayer will pass through compatibility from multiple + // non-overlapping compatible children if the caller enables it + context->subtree_can_inherit_opacity = true; + container1->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path3 = SkPath().addRect({20, 20, 40, 40}); + auto mock3 = MockLayer::MakeOpacityCompatible(path3); + container1->Add(mock3); + + // ContainerLayer will not pass through compatibility from multiple + // overlapping children even if they are individually compatible + // and the caller requests it + context->subtree_can_inherit_opacity = true; + container1->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + auto container2 = std::make_shared(); + container2->Add(mock1); + container2->Add(mock2); + + // Double check first two children are compatible and non-overlapping + // if the caller requests it + context->subtree_can_inherit_opacity = true; + container2->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path4 = SkPath().addRect({60, 60, 70, 70}); + auto mock4 = MockLayer::Make(path4); + container2->Add(mock4); + + // The third child is non-overlapping, but not compatible so the + // TransformLayer should end up incompatible + context->subtree_can_inherit_opacity = true; + container2->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); +} + using ContainerLayerDiffTest = DiffContextTest; // Insert PictureLayer amongst container layers diff --git a/flow/layers/display_list_layer.cc b/flow/layers/display_list_layer.cc index e5fe2554c8c34..e44d57b608deb 100644 --- a/flow/layers/display_list_layer.cc +++ b/flow/layers/display_list_layer.cc @@ -5,6 +5,7 @@ #include "flutter/flow/layers/display_list_layer.h" #include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list_flags.h" #include "flutter/flow/layers/offscreen_surface.h" namespace flutter { @@ -16,12 +17,7 @@ DisplayListLayer::DisplayListLayer(const SkPoint& offset, : offset_(offset), display_list_(std::move(display_list)), is_complex_(is_complex), - will_change_(will_change) { - if (display_list_.skia_object()) { - set_layer_can_inherit_opacity( - display_list_.skia_object()->can_apply_group_opacity()); - } -} + will_change_(will_change) {} bool DisplayListLayer::IsReplacing(DiffContext* context, const Layer* layer) const { @@ -97,6 +93,10 @@ void DisplayListLayer::Preroll(PrerollContext* context, SkRect bounds = disp_list->bounds().makeOffset(offset_.x(), offset_.y()); + if (disp_list->can_apply_group_opacity()) { + context->subtree_can_inherit_opacity = true; + } + if (auto* cache = context->raster_cache) { TRACE_EVENT0("flutter", "DisplayListLayer::RasterCache (Preroll)"); if (context->cull_rect.intersects(bounds)) { @@ -157,8 +157,16 @@ void DisplayListLayer::Paint(PaintContext& context) const { } if (context.leaf_nodes_builder) { + AutoCachePaint save_paint(context); + int restore_count = context.leaf_nodes_builder->getSaveCount(); + if (save_paint.paint() != nullptr) { + context.leaf_nodes_builder->setAttributesFromPaint( + *save_paint.paint(), DisplayListOpFlags::kSaveLayerWithPaintFlags); + context.leaf_nodes_builder->saveLayer(&paint_bounds(), true); + } display_list()->RenderTo(context.leaf_nodes_builder, context.inherited_opacity); + context.leaf_nodes_builder->restoreToCount(restore_count); } else { display_list()->RenderTo(context.leaf_nodes_canvas, context.inherited_opacity); diff --git a/flow/layers/display_list_layer_unittests.cc b/flow/layers/display_list_layer_unittests.cc index 02f6f02b64cb8..7796d586f6f28 100644 --- a/flow/layers/display_list_layer_unittests.cc +++ b/flow/layers/display_list_layer_unittests.cc @@ -107,6 +107,232 @@ TEST_F(DisplayListLayerTest, SimpleDisplayList) { EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); } +TEST_F(DisplayListLayerTest, SimpleDisplayListOpacityInheritance) { + const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); + const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + DisplayListBuilder builder; + builder.drawRect(picture_bounds); + auto display_list = builder.Build(); + auto display_list_layer = std::make_shared( + layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false); + EXPECT_TRUE(display_list->can_apply_group_opacity()); + + auto context = preroll_context(); + context->subtree_can_inherit_opacity = false; + display_list_layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint opacity_offset = SkPoint::Make(10, 10); + auto opacity_layer = + std::make_shared(opacity_alpha, opacity_offset); + opacity_layer->Add(display_list_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto save_layer_bounds = + picture_bounds.makeOffset(layer_offset.fX, layer_offset.fY); + auto opacity_integral_matrix = + RasterCache::GetIntegralTransCTM(SkMatrix::Translate(opacity_offset)); + SkMatrix layer_offset_matrix = opacity_integral_matrix; + layer_offset_matrix.postTranslate(layer_offset.fX, layer_offset.fY); + auto layer_offset_integral_matrix = + RasterCache::GetIntegralTransCTM(layer_offset_matrix); + DisplayListBuilder expected_builder; + /* opacity_layer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(opacity_offset.fX, opacity_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integral_matrix); +#endif + /* display_list_layer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(layer_offset.fX, layer_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(layer_offset_integral_matrix); +#endif + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&save_layer_bounds, true); + /* display_list contents */ { // + expected_builder.drawRect(picture_bounds); + } + expected_builder.restore(); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE( + DisplayListsEQ_Verbose(expected_builder.Build(), this->display_list())); +} + +TEST_F(DisplayListLayerTest, IncompatibleDisplayListOpacityInheritance) { + const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); + const SkRect picture1_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkRect picture2_bounds = SkRect::MakeLTRB(10.0f, 15.0f, 30.0f, 35.0f); + DisplayListBuilder builder; + builder.drawRect(picture1_bounds); + builder.drawRect(picture2_bounds); + auto display_list = builder.Build(); + auto display_list_layer = std::make_shared( + layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false); + EXPECT_FALSE(display_list->can_apply_group_opacity()); + + auto context = preroll_context(); + context->subtree_can_inherit_opacity = false; + display_list_layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint opacity_offset = SkPoint::Make(10, 10); + auto opacity_layer = + std::make_shared(opacity_alpha, opacity_offset); + opacity_layer->Add(display_list_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(opacity_layer->children_can_accept_opacity()); + + auto display_list_bounds = picture1_bounds; + display_list_bounds.join(picture2_bounds); + auto save_layer_bounds = + display_list_bounds.makeOffset(layer_offset.fX, layer_offset.fY); + save_layer_bounds.roundOut(&save_layer_bounds); + auto opacity_integral_matrix = + RasterCache::GetIntegralTransCTM(SkMatrix::Translate(opacity_offset)); + SkMatrix layer_offset_matrix = opacity_integral_matrix; + layer_offset_matrix.postTranslate(layer_offset.fX, layer_offset.fY); + auto layer_offset_integral_matrix = + RasterCache::GetIntegralTransCTM(layer_offset_matrix); + DisplayListBuilder expected_builder; + /* opacity_layer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(opacity_offset.fX, opacity_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integral_matrix); +#endif + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&save_layer_bounds, true); + { + /* display_list_layer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(layer_offset.fX, layer_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(layer_offset_integral_matrix); +#endif + expected_builder.drawRect(picture1_bounds); + expected_builder.drawRect(picture2_bounds); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE( + DisplayListsEQ_Verbose(expected_builder.Build(), this->display_list())); +} + +TEST_F(DisplayListLayerTest, CachedIncompatibleDisplayListOpacityInheritance) { + const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); + const SkRect picture1_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkRect picture2_bounds = SkRect::MakeLTRB(10.0f, 15.0f, 30.0f, 35.0f); + DisplayListBuilder builder; + builder.drawRect(picture1_bounds); + builder.drawRect(picture2_bounds); + auto display_list = builder.Build(); + auto display_list_layer = std::make_shared( + layer_offset, SkiaGPUObject(display_list, unref_queue()), true, false); + EXPECT_FALSE(display_list->can_apply_group_opacity()); + + use_skia_raster_cache(); + + auto context = preroll_context(); + context->subtree_can_inherit_opacity = false; + display_list_layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + // Pump the DisplayListLayer until it is ready to cache its DL + display_list_layer->Paint(paint_context()); + display_list_layer->Paint(paint_context()); + display_list_layer->Paint(paint_context()); + + int opacity_alpha = 0x7F; + SkPoint opacity_offset = SkPoint::Make(10, 10); + auto opacity_layer = + std::make_shared(opacity_alpha, opacity_offset); + opacity_layer->Add(display_list_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + // The following would be a great test of the painting of the above + // setup, but for the fact that the raster cache stores raw pointers + // to sk_sp and the canvas recorder then wraps each of those + // in a unique DlImage - which means the DisplayList objects will not + // compare with the Equals method since the addresses of the two + // DlImage objects will not be equal even if they point to the same + // SkImage on each frame. + // See https://github.com/flutter/flutter/issues/102331 + // auto display_list_bounds = picture1_bounds; + // display_list_bounds.join(picture2_bounds); + // auto save_layer_bounds = + // display_list_bounds.makeOffset(layer_offset.fX, layer_offset.fY); + // save_layer_bounds.roundOut(&save_layer_bounds); + // auto opacity_integral_matrix = + // RasterCache::GetIntegralTransCTM(SkMatrix::Translate(opacity_offset)); + // SkMatrix layer_offset_matrix = opacity_integral_matrix; + // layer_offset_matrix.postTranslate(layer_offset.fX, layer_offset.fY); + // auto layer_offset_integral_matrix = + // RasterCache::GetIntegralTransCTM(layer_offset_matrix); + // // Using a recorder instead of a DisplayListBuilder so we can hand it + // // off to the RasterCache::Draw() method + // DisplayListCanvasRecorder recorder(SkRect::MakeWH(1000, 1000)); + // /* opacity_layer::Paint() */ { + // recorder.save(); + // { + // recorder.translate(opacity_offset.fX, opacity_offset.fY); + // #ifndef SUPPORT_FRACTIONAL_TRANSLATION + // recorder.resetMatrix(); + // recorder.concat(opacity_integral_matrix); + // #endif + // /* display_list_layer::Paint() */ { + // recorder.save(); + // { + // recorder.translate(layer_offset.fX, layer_offset.fY); + // #ifndef SUPPORT_FRACTIONAL_TRANSLATION + // recorder.resetMatrix(); + // recorder.concat(layer_offset_integral_matrix); + // #endif + // SkPaint p; + // p.setAlpha(opacity_alpha); + // context->raster_cache->Draw(*display_list, recorder, &p); + // } + // recorder.restore(); + // } + // } + // recorder.restore(); + // } + + // opacity_layer->Paint(display_list_paint_context()); + // EXPECT_TRUE( + // DisplayListsEQ_Verbose(recorder.Build(), this->display_list())); +} + using DisplayListLayerDiffTest = DiffContextTest; TEST_F(DisplayListLayerDiffTest, SimpleDisplayList) { diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index fbeff92307c55..e5158b73b5928 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -44,6 +44,11 @@ void ImageFilterLayer::Preroll(PrerollContext* context, SkRect child_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_bounds); + context->subtree_can_inherit_opacity = true; + + // We always paint with a saveLayer (or a cached rendering), + // so we can always apply opacity in any of those cases. + context->subtree_can_inherit_opacity = true; if (!filter_) { set_paint_bounds(child_bounds); @@ -95,31 +100,32 @@ void ImageFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ImageFilterLayer::Paint"); FML_DCHECK(needs_painting(context)); + AutoCachePaint cache_paint(context); + if (context.raster_cache) { if (context.raster_cache->Draw(this, *context.leaf_nodes_canvas, - RasterCacheLayerStrategy::kLayer)) { + RasterCacheLayerStrategy::kLayer, + cache_paint.paint())) { return; } if (transformed_filter_) { - SkPaint paint; - paint.setImageFilter(transformed_filter_); + cache_paint.setImageFilter(transformed_filter_); if (context.raster_cache->Draw(this, *context.leaf_nodes_canvas, RasterCacheLayerStrategy::kLayerChildren, - &paint)) { + cache_paint.paint())) { return; } } } - SkPaint paint; - paint.setImageFilter(filter_); + cache_paint.setImageFilter(filter_); // Normally a save_layer is sized to the current layer bounds, but in this // case the bounds of the child may not be the same as the filtered version // so we use the bounds of the child container which do not include any // modifications that the filter might apply. - Layer::AutoSaveLayer save_layer = - Layer::AutoSaveLayer::Create(context, child_paint_bounds(), &paint); + Layer::AutoSaveLayer save_layer = Layer::AutoSaveLayer::Create( + context, child_paint_bounds(), cache_paint.paint()); PaintChildren(context); } diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc index 0b5898efbcc80..acf1c825d3306 100644 --- a/flow/layers/image_filter_layer_unittests.cc +++ b/flow/layers/image_filter_layer_unittests.cc @@ -373,6 +373,65 @@ TEST_F(ImageFilterLayerTest, CacheChildren) { RasterCacheLayerStrategy::kLayerChildren)); } +TEST_F(ImageFilterLayerTest, OpacityInheritance) { + const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto layer_filter = SkImageFilters::MatrixTransform( + SkMatrix(), + SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + // The mock_layer child will not be compatible with opacity + auto mock_layer = MockLayer::Make(child_path, child_paint); + auto image_filter_layer = std::make_shared(layer_filter); + image_filter_layer->Add(mock_layer); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + image_filter_layer->Preroll(preroll_context(), initial_transform); + // ImageFilterLayers can always inherit opacity whether or not their + // children are compatible. + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(image_filter_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Translate(offset.fX, offset.fY)); + auto dl_image_filter = DlImageFilter::From(layer_filter); + DisplayListBuilder expected_builder; + /* opacity_layer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* image_filter_layer::Paint() */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.setImageFilter(dl_image_filter.get()); + expected_builder.saveLayer(&child_path.getBounds(), true); + /* mock_layer::Paint() */ { + expected_builder.setColor(child_paint.getColor()); + expected_builder.setImageFilter(nullptr); + expected_builder.drawPath(child_path); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + using ImageFilterLayerDiffTest = DiffContextTest; TEST_F(ImageFilterLayerDiffTest, ImageFilterLayer) { diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index 621ae585701e5..8a34b775ef766 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -13,8 +13,7 @@ Layer::Layer() : paint_bounds_(SkRect::MakeEmpty()), unique_id_(NextUniqueID()), original_layer_id_(unique_id_), - subtree_has_platform_view_(false), - layer_can_inherit_opacity_(false) {} + subtree_has_platform_view_(false) {} Layer::~Layer() = default; diff --git a/flow/layers/layer.h b/flow/layers/layer.h index f0ca47ed0db9d..9a7316d4c887e 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -63,23 +63,41 @@ struct PrerollContext { // prescence of a texture layer during Preroll. bool has_texture_layer = false; - // This value indicates that the entire subtree below the layer can inherit - // an opacity value and modulate its own visibility accordingly. - // For Layers which cannot either apply such an inherited opacity nor pass - // it along to their children, they can ignore this value as its default - // behavior is "opt-in". - // For Layers that support this condition, it can be recorded in their - // constructor using the |set_layer_can_inherit_opacity| method and the - // value will be accumulated and recorded by the |PrerollChidren| method - // automatically. - // If the property is more dynamic then the Layer can dynamically set this - // flag before returning from the |Preroll| method. - // For ContainerLayers that need to know if their children can inherit - // the value, the |PrerollChildren| method will have set this value in - // the context before it returns. If the container can support it as long - // as the subtree can support it, no further work needs to be done other - // than to remember the value so that it can choose the right strategy - // for its |Paint| method. + // This field indicates whether the subtree rooted at this layer can + // inherit an opacity value and modulate its visibility accordingly. + // + // Any layer is free to ignore this flag. Its value will be false upon + // entry into its Preroll method, it will remain false if it calls + // PrerollChildren on any children it might have, and it will remain + // false upon exit from the Preroll method unless it takes specific + // action compute if it should be true. Thus, this property is "opt-in". + // + // If the value is false when the Preroll method exits, then the + // |PaintContext::inherited_opacity| value should always be set to + // 1.0 when its |Paint| method is called. + // + // Leaf layers need only be concerned with their own rendering and + // can set the value according to whether they can apply the opacity. + // + // For containers, there are 3 ways to interact with this field: + // + // 1. If you need to know whether your children are compatible, then + // set the field to true before you call PrerollChildren. That + // method will then reset the field to false if it detects any + // incompatible children. + // + // 2. If your decision on whether to inherit the opacity depends on + // the answer from the children, then remember the value of the + // field when PrerollChildren returns. (eg. OpacityLayer remembers + // this value to control whether to set the opacity value into the + // |PaintContext::inherited_opacity| field in |Paint| before + // recursing to its children in Paint) + // + // 3. If you want to indicate to your parents that you can accept + // inherited opacity regardless of whether your children were + // compatible then set this field to true before returning + // from your Preroll method. (eg. layers that always apply a + // saveLayer when rendering anyway can apply the opacity there) bool subtree_can_inherit_opacity = false; }; @@ -196,12 +214,28 @@ class Layer { ~AutoCachePaint() { context_.inherited_opacity = paint_.getAlphaf(); } + void setImageFilter(sk_sp filter) { + paint_.setImageFilter(filter); + update_needs_paint(); + } + + void setColorFilter(sk_sp filter) { + paint_.setColorFilter(filter); + update_needs_paint(); + } + const SkPaint* paint() { return needs_paint_ ? &paint_ : nullptr; } private: PaintContext& context_; SkPaint paint_; bool needs_paint_; + + void update_needs_paint() { + needs_paint_ = paint_.getImageFilter() != nullptr || + paint_.getColorFilter() != nullptr || + paint_.getAlphaf() < SK_Scalar1; + } }; // Calls SkCanvas::saveLayer and restores the layer upon destruction. Also @@ -271,18 +305,6 @@ class Layer { subtree_has_platform_view_ = value; } - // Returns true if the layer can render with an added opacity value inherited - // from an OpacityLayer ancestor and delivered to its |Paint| method through - // the |PaintContext.inherited_opacity| field. This flag can be set either - // in the Layer's constructor if it is a lifetime constant value, or during - // the |Preroll| method if it must determine the capability based on data - // only available when it is part of a tree. It must set this value before - // recursing to its children if it is a |ContainerLayer|. - bool layer_can_inherit_opacity() const { return layer_can_inherit_opacity_; } - void set_layer_can_inherit_opacity(bool value) { - layer_can_inherit_opacity_ = value; - } - // Returns the paint bounds in the layer's local coordinate system // as determined during Preroll(). The bounds should include any // transform, clip or distortions performed by the layer itself, @@ -353,7 +375,6 @@ class Layer { uint64_t unique_id_; uint64_t original_layer_id_; bool subtree_has_platform_view_; - bool layer_can_inherit_opacity_; static uint64_t NextUniqueID(); diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index 49fc890a632fa..b234428a800ce 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -10,12 +10,7 @@ namespace flutter { OpacityLayer::OpacityLayer(SkAlpha alpha, const SkPoint& offset) - : alpha_(alpha), offset_(offset), children_can_accept_opacity_(false) { - // We can always inhert opacity even if we cannot pass it along to - // our children as we can accumulate the inherited opacity into our - // own opacity value before we recurse. - set_layer_can_inherit_opacity(true); -} + : alpha_(alpha), offset_(offset), children_can_accept_opacity_(false) {} void OpacityLayer::Diff(DiffContext* context, const Layer* old_layer) { DiffContext::AutoSubtreeRestore subtree(context); @@ -51,12 +46,25 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { context->mutators_stack.PushOpacity(alpha_); Layer::AutoPrerollSaveLayerState save = Layer::AutoPrerollSaveLayerState::Create(context); + + // Collect inheritance information on our children in Preroll so that + // we can decide whether or not to use a saveLayer in Paint. + context->subtree_can_inherit_opacity = true; + + // ContainerLayer will turn the flag off if any children are + // incompatible or if they overlap ContainerLayer::Preroll(context, child_matrix); - context->mutators_stack.Pop(); - context->mutators_stack.Pop(); + // We store the inheritance ability of our children for |Paint| set_children_can_accept_opacity(context->subtree_can_inherit_opacity); + // Now we let our parent layers know that we, too, can inherit opacity + // regardless of what our children are capable of + context->subtree_can_inherit_opacity = true; + + context->mutators_stack.Pop(); + context->mutators_stack.Pop(); + set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY)); if (!children_can_accept_opacity()) { diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc index 2581dbff58997..03625756b4349 100644 --- a/flow/layers/opacity_layer_unittests.cc +++ b/flow/layers/opacity_layer_unittests.cc @@ -5,6 +5,8 @@ #include "flutter/flow/layers/opacity_layer.h" #include "flutter/flow/layers/clip_rect_layer.h" +#include "flutter/flow/layers/image_filter_layer.h" +#include "flutter/flow/layers/transform_layer.h" #include "flutter/flow/testing/diff_context_test.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" @@ -298,11 +300,11 @@ TEST_F(OpacityLayerTest, HalfTransparent) { EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_transform), Mutator(alpha_half)})); - SkPaint opacity_paint; - opacity_paint.setAlphaf(alpha_half * (1.0 / SK_AlphaOPAQUE)); SkRect opacity_bounds; expected_layer_bounds.makeOffset(-layer_offset.fX, -layer_offset.fY) .roundOut(&opacity_bounds); + DlPaint save_paint = DlPaint().setColor(DlColor(alpha_half << 24)); + DlPaint child_dl_paint = DlPaint().setColor(DlColor(SK_ColorGREEN)); auto expected_builder = DisplayListBuilder(); expected_builder.save(); @@ -311,10 +313,8 @@ TEST_F(OpacityLayerTest, HalfTransparent) { expected_builder.transformReset(); expected_builder.transform(SkM44(integral_layer_transform)); #endif - expected_builder.setColor(alpha_half << 24); - expected_builder.saveLayer(&opacity_bounds, true); - expected_builder.setColor(SkColors::kGreen.toSkColor()); - expected_builder.drawPath(child_path); + expected_builder.saveLayer(&opacity_bounds, &save_paint); + expected_builder.drawPath(child_path, child_dl_paint); expected_builder.restore(); expected_builder.restore(); sk_sp expected_display_list = expected_builder.Build(); @@ -467,6 +467,142 @@ TEST_F(OpacityLayerTest, CullRectIsTransformed) { EXPECT_EQ(mockLayer->parent_cull_rect().fTop, -20); } +TEST_F(OpacityLayerTest, OpacityInheritanceCompatibleChild) { + auto opacityLayer = + std::make_shared(128, SkPoint::Make(20, 20)); + auto mockLayer = MockLayer::MakeOpacityCompatible(SkPath()); + opacityLayer->Add(mockLayer); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + opacityLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + EXPECT_TRUE(opacityLayer->children_can_accept_opacity()); +} + +TEST_F(OpacityLayerTest, OpacityInheritanceIncompatibleChild) { + auto opacityLayer = + std::make_shared(128, SkPoint::Make(20, 20)); + auto mockLayer = MockLayer::Make(SkPath()); + opacityLayer->Add(mockLayer); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + opacityLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + EXPECT_FALSE(opacityLayer->children_can_accept_opacity()); +} + +TEST_F(OpacityLayerTest, OpacityInheritanceThroughContainer) { + auto opacityLayer = + std::make_shared(128, SkPoint::Make(20, 20)); + auto containerLayer = std::make_shared(); + auto mockLayer = MockLayer::MakeOpacityCompatible(SkPath()); + containerLayer->Add(mockLayer); + opacityLayer->Add(containerLayer); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + opacityLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + // By default a container layer will not pass opacity through to + // its children - specific subclasses will have to enable this + // pass through by setting the flag to true themselves before + // calling their super method ContainerLayer::Preroll(). + EXPECT_FALSE(opacityLayer->children_can_accept_opacity()); +} + +TEST_F(OpacityLayerTest, OpacityInheritanceThroughTransform) { + auto opacityLayer = + std::make_shared(128, SkPoint::Make(20, 20)); + auto transformLayer = std::make_shared(SkMatrix::Scale(2, 2)); + auto mockLayer = MockLayer::MakeOpacityCompatible(SkPath()); + transformLayer->Add(mockLayer); + opacityLayer->Add(transformLayer); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + opacityLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + EXPECT_TRUE(opacityLayer->children_can_accept_opacity()); +} + +TEST_F(OpacityLayerTest, OpacityInheritanceThroughImageFilter) { + auto opacityLayer = + std::make_shared(128, SkPoint::Make(20, 20)); + auto filterLayer = std::make_shared( + SkImageFilters::Blur(5.0, 5.0, nullptr)); + auto mockLayer = MockLayer::MakeOpacityCompatible(SkPath()); + filterLayer->Add(mockLayer); + opacityLayer->Add(filterLayer); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + opacityLayer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + EXPECT_TRUE(opacityLayer->children_can_accept_opacity()); +} + +TEST_F(OpacityLayerTest, OpacityInheritanceNested) { + SkPoint offset1 = SkPoint::Make(10, 20); + SkPoint offset2 = SkPoint::Make(20, 10); + SkPath mockPath = SkPath::Rect({10, 10, 20, 20}); + auto opacityLayer1 = std::make_shared(128, offset1); + auto opacityLayer2 = std::make_shared(64, offset2); + auto mockLayer = MockLayer::MakeOpacityCompatible(mockPath); + opacityLayer2->Add(mockLayer); + opacityLayer1->Add(opacityLayer2); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + opacityLayer1->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + EXPECT_TRUE(opacityLayer1->children_can_accept_opacity()); + EXPECT_TRUE(opacityLayer2->children_can_accept_opacity()); + + SkPaint saveLayerPaint; + SkScalar inheritedOpacity = 128 * 1.0 / SK_AlphaOPAQUE; + inheritedOpacity *= 64 * 1.0 / SK_AlphaOPAQUE; + saveLayerPaint.setAlphaf(inheritedOpacity); + + DisplayListBuilder expected_builder; + /* opacityLayer1::Paint */ { + expected_builder.save(); + { + expected_builder.translate(offset1.fX, offset1.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(SkM44::Translate(offset1.fX, offset1.fY)); +#endif + /* opacityLayer2::Paint */ { + expected_builder.save(); + { + expected_builder.translate(offset2.fX, offset2.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(SkM44::Translate(offset1.fX + offset2.fX, + offset1.fY + offset2.fY)); +#endif + /* mockLayer::Paint */ { + expected_builder.setColor(saveLayerPaint.getAlpha() << 24); + expected_builder.saveLayer(&mockPath.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(mockPath); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacityLayer1->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + using OpacityLayerDiffTest = DiffContextTest; TEST_F(OpacityLayerDiffTest, FractionalTranslation) { diff --git a/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc index aa1b15c6651ff..3e41506ea4fad 100644 --- a/flow/layers/physical_shape_layer_unittests.cc +++ b/flow/layers/physical_shape_layer_unittests.cc @@ -416,6 +416,20 @@ TEST_F(PhysicalShapeLayerTest, Readback) { EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); } +TEST_F(PhysicalShapeLayerTest, OpacityInheritance) { + SkPath layer_path; + layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto layer = + std::make_shared(SK_ColorGREEN, SK_ColorBLACK, + 0.0f, // elevation + layer_path, Clip::none); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + layer->Preroll(context, SkMatrix()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); +} + using PhysicalShapeLayerDiffTest = DiffContextTest; TEST_F(PhysicalShapeLayerDiffTest, NoClipPaintRegion) { diff --git a/flow/layers/picture_layer_unittests.cc b/flow/layers/picture_layer_unittests.cc index 1d619acb08feb..7838c84808451 100644 --- a/flow/layers/picture_layer_unittests.cc +++ b/flow/layers/picture_layer_unittests.cc @@ -97,6 +97,64 @@ TEST_F(PictureLayerTest, SimplePicture) { EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); } +TEST_F(PictureLayerTest, OpacityInheritanceCacheablePicture) { + use_mock_raster_cache(); + const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); + const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), true, false); + + // First try, no caching, cannot support opacity + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(raster_cache()->GetPictureCachedEntriesCount(), size_t(1)); + EXPECT_EQ(raster_cache()->EstimatePictureCacheByteSize(), size_t(0)); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + // Paint it enough times to reach its cache threshold + layer->Paint(paint_context()); + layer->Paint(paint_context()); + layer->Paint(paint_context()); + + // This time it will cache and can now support opacity + context->subtree_can_inherit_opacity = false; + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(raster_cache()->GetPictureCachedEntriesCount(), size_t(1)); + EXPECT_NE(raster_cache()->EstimatePictureCacheByteSize(), size_t(0)); + EXPECT_TRUE(context->subtree_can_inherit_opacity); +} + +TEST_F(PictureLayerTest, OpacityInheritanceUncacheablePicture) { + use_mock_raster_cache(); + const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); + const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, true); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(raster_cache()->GetPictureCachedEntriesCount(), size_t(0)); + EXPECT_EQ(raster_cache()->EstimatePictureCacheByteSize(), size_t(0)); + // First try, no caching, cannot support opacity + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + // Paint it enough times to reach its cache threshold + layer->Paint(paint_context()); + layer->Paint(paint_context()); + layer->Paint(paint_context()); + + context->subtree_can_inherit_opacity = false; + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(raster_cache()->GetPictureCachedEntriesCount(), size_t(0)); + EXPECT_EQ(raster_cache()->EstimatePictureCacheByteSize(), size_t(0)); + // It still is not compatible because it cannot cache + EXPECT_FALSE(context->subtree_can_inherit_opacity); +} + using PictureLayerDiffTest = DiffContextTest; TEST_F(PictureLayerDiffTest, SimplePicture) { diff --git a/flow/layers/platform_view_layer_unittests.cc b/flow/layers/platform_view_layer_unittests.cc index 58a791d7acca8..ecbbe3f39f036 100644 --- a/flow/layers/platform_view_layer_unittests.cc +++ b/flow/layers/platform_view_layer_unittests.cc @@ -83,5 +83,18 @@ TEST_F(PlatformViewLayerTest, ClippedPlatformViewPrerollsAndPaintsNothing) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +TEST_F(PlatformViewLayerTest, OpacityInheritance) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkSize layer_size = SkSize::Make(8.0f, 8.0f); + const int64_t view_id = 0; + auto layer = + std::make_shared(layer_offset, layer_size, view_id); + + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index e5b0fc84f3a4c..c428987383018 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -12,9 +12,7 @@ ShaderMaskLayer::ShaderMaskLayer(sk_sp shader, : shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode), - render_count_(1) { - set_layer_can_inherit_opacity(true); -} + render_count_(1) {} void ShaderMaskLayer::Diff(DiffContext* context, const Layer* old_layer) { DiffContext::AutoSubtreeRestore subtree(context); @@ -37,6 +35,10 @@ void ShaderMaskLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { Layer::AutoPrerollSaveLayerState::Create(context); ContainerLayer::Preroll(context, matrix); + // We always paint with a saveLayer (or a cached rendering), + // so we can always apply opacity in any of those cases. + context->subtree_can_inherit_opacity = true; + if (render_count_ >= kMinimumRendersBeforeCachingFilterLayer) { TryToPrepareRasterCache(context, this, matrix, RasterCacheLayerStrategy::kLayer); @@ -49,13 +51,15 @@ void ShaderMaskLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ShaderMaskLayer::Paint"); FML_DCHECK(needs_painting(context)); + AutoCachePaint cache_paint(context); + if (context.raster_cache && context.raster_cache->Draw(this, *context.leaf_nodes_canvas, - RasterCacheLayerStrategy::kLayer)) { + RasterCacheLayerStrategy::kLayer, + cache_paint.paint())) { return; } - AutoCachePaint cache_paint(context); Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( context, paint_bounds(), cache_paint.paint()); PaintChildren(context); diff --git a/flow/layers/shader_mask_layer_unittests.cc b/flow/layers/shader_mask_layer_unittests.cc index 76423b3b16b31..91219896ffe69 100644 --- a/flow/layers/shader_mask_layer_unittests.cc +++ b/flow/layers/shader_mask_layer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/shader_mask_layer.h" +#include "flutter/flow/layers/opacity_layer.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -319,5 +320,61 @@ TEST_F(ShaderMaskLayerTest, LayerCached) { RasterCacheLayerStrategy::kLayer)); } +TEST_F(ShaderMaskLayerTest, OpacityInheritance) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = MockLayer::Make(child_path); + const SkRect mask_rect = SkRect::MakeLTRB(10, 10, 20, 20); + auto shader_mask_layer = + std::make_shared(nullptr, mask_rect, SkBlendMode::kSrc); + shader_mask_layer->Add(mock_layer); + + // ShaderMaskLayers can always support opacity despite incompatible children + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + shader_mask_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(shader_mask_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY); + DisplayListBuilder expected_builder; + /* OpacityLayer::Paint() */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* ShaderMaskLayer::Paint() */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&child_path.getBounds(), true); + { + /* child layer paint */ { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(child_path); + } + expected_builder.translate(mask_rect.fLeft, mask_rect.fTop); + expected_builder.setBlendMode(DlBlendMode::kSrc); + expected_builder.drawRect( + SkRect::MakeWH(mask_rect.width(), mask_rect.height())); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/texture_layer.cc b/flow/layers/texture_layer.cc index 63194b0cd5cb2..6f5207da63bba 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -17,9 +17,7 @@ TextureLayer::TextureLayer(const SkPoint& offset, size_(size), texture_id_(texture_id), freeze_(freeze), - sampling_(sampling) { - set_layer_can_inherit_opacity(true); -} + sampling_(sampling) {} void TextureLayer::Diff(DiffContext* context, const Layer* old_layer) { DiffContext::AutoSubtreeRestore subtree(context); @@ -48,6 +46,7 @@ void TextureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(), size_.height())); context->has_texture_layer = true; + context->subtree_can_inherit_opacity = true; } void TextureLayer::Paint(PaintContext& context) const { diff --git a/flow/layers/texture_layer_unittests.cc b/flow/layers/texture_layer_unittests.cc index 4d251120efd9b..bd2c863c2dea5 100644 --- a/flow/layers/texture_layer_unittests.cc +++ b/flow/layers/texture_layer_unittests.cc @@ -116,5 +116,29 @@ TEST_F(TextureLayerDiffTest, TextureInRetainedLayer) { EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 100, 100)); } +TEST_F(TextureLayerTest, OpacityInheritance) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkSize layer_size = SkSize::Make(8.0f, 8.0f); + const int64_t texture_id = 0; + auto mock_texture = std::make_shared(texture_id); + auto layer = std::make_shared( + layer_offset, layer_size, texture_id, false, + SkSamplingOptions(SkFilterMode::kLinear)); + + // Ensure the texture is located by the Layer. + preroll_context()->texture_registry.RegisterTexture(mock_texture); + + // The texture layer always reports opacity compatibility. + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + context->texture_registry.RegisterTexture(mock_texture); + layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + // MockTexture has no actual textur to render into the + // PaintContext canvas so we have no way to verify its + // rendering. +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 68cf4e8e3192f..f34566a915484 100644 --- a/flow/layers/transform_layer.cc +++ b/flow/layers/transform_layer.cc @@ -24,7 +24,6 @@ TransformLayer::TransformLayer(const SkMatrix& transform) FML_LOG(ERROR) << "TransformLayer is constructed with an invalid matrix."; transform_.setIdentity(); } - set_layer_can_inherit_opacity(true); } void TransformLayer::Diff(DiffContext* context, const Layer* old_layer) { @@ -57,6 +56,10 @@ void TransformLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { context->cull_rect = kGiantRect; } + // Collect inheritance information on our children in Preroll so that + // we can pass it along by default. + context->subtree_can_inherit_opacity = true; + SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, child_matrix, &child_paint_bounds); diff --git a/flow/layers/transform_layer_unittests.cc b/flow/layers/transform_layer_unittests.cc index 174944c55a005..6947c13ea489f 100644 --- a/flow/layers/transform_layer_unittests.cc +++ b/flow/layers/transform_layer_unittests.cc @@ -234,6 +234,124 @@ TEST_F(TransformLayerTest, NestedSeparated) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +TEST_F(TransformLayerTest, OpacityInheritance) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto transform1 = std::make_shared(SkMatrix::Scale(2, 2)); + transform1->Add(mock1); + + // TransformLayer will pass through compatibility from a compatible child + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + transform1->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + transform1->Add(mock2); + + // TransformLayer will pass through compatibility from multiple + // non-overlapping compatible children + context->subtree_can_inherit_opacity = false; + transform1->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path3 = SkPath().addRect({20, 20, 40, 40}); + auto mock3 = MockLayer::MakeOpacityCompatible(path3); + transform1->Add(mock3); + + // TransformLayer will not pass through compatibility from multiple + // overlapping children even if they are individually compatible + context->subtree_can_inherit_opacity = false; + transform1->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + auto transform2 = std::make_shared(SkMatrix::Scale(2, 2)); + transform2->Add(mock1); + transform2->Add(mock2); + + // Double check first two children are compatible and non-overlapping + context->subtree_can_inherit_opacity = false; + transform2->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + auto path4 = SkPath().addRect({60, 60, 70, 70}); + auto mock4 = MockLayer::Make(path4); + transform2->Add(mock4); + + // The third child is non-overlapping, but not compatible so the + // TransformLayer should end up incompatible + context->subtree_can_inherit_opacity = false; + transform2->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); +} + +TEST_F(TransformLayerTest, OpacityInheritancePainting) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + auto mock1 = MockLayer::MakeOpacityCompatible(path1); + auto path2 = SkPath().addRect({40, 40, 50, 50}); + auto mock2 = MockLayer::MakeOpacityCompatible(path2); + auto transform = SkMatrix::Scale(2, 2); + auto transform_layer = std::make_shared(transform); + transform_layer->Add(mock1); + transform_layer->Add(mock2); + + // TransformLayer will pass through compatibility from multiple + // non-overlapping compatible children + PrerollContext* context = preroll_context(); + context->subtree_can_inherit_opacity = false; + transform_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); + + int opacity_alpha = 0x7F; + SkPoint offset = SkPoint::Make(10, 10); + auto opacity_layer = std::make_shared(opacity_alpha, offset); + opacity_layer->Add(transform_layer); + context->subtree_can_inherit_opacity = false; + opacity_layer->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); + + auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY); + DisplayListBuilder expected_builder; + /* opacity_layer paint */ { + expected_builder.save(); + { + expected_builder.translate(offset.fX, offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + expected_builder.transformReset(); + expected_builder.transform(opacity_integer_transform); +#endif + /* transform_layer paint */ { + expected_builder.save(); + expected_builder.transform(transform); + /* child layer1 paint */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&path1.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path1); + } + expected_builder.restore(); + } + /* child layer2 paint */ { + expected_builder.setColor(opacity_alpha << 24); + expected_builder.saveLayer(&path2.getBounds(), true); + { + expected_builder.setColor(0xFF000000); + expected_builder.drawPath(path2); + } + expected_builder.restore(); + } + expected_builder.restore(); + } + } + expected_builder.restore(); + } + + opacity_layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list())); +} + using TransformLayerLayerDiffTest = DiffContextTest; TEST_F(TransformLayerLayerDiffTest, Transform) { diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h index e234c529ab62b..650f50ca96649 100644 --- a/flow/testing/layer_test.h +++ b/flow/testing/layer_test.h @@ -39,6 +39,8 @@ template class LayerTestBase : public CanvasTestBase { using TestT = CanvasTestBase; + const SkRect kDlBounds = SkRect::MakeWH(500, 500); + public: LayerTestBase() : preroll_context_{ @@ -72,8 +74,8 @@ class LayerTestBase : public CanvasTestBase { .frame_device_pixel_ratio = 1.0f, // clang-format on }, - display_list_recorder_(kGiantRect), - internal_display_list_canvas_(kGiantRect.width(), kGiantRect.height()), + display_list_recorder_(kDlBounds), + internal_display_list_canvas_(kDlBounds.width(), kDlBounds.height()), display_list_paint_context_{ // clang-format off .internal_nodes_canvas = &internal_display_list_canvas_, @@ -193,6 +195,7 @@ class LayerTestBase : public CanvasTestBase { raster_cache_ = std::move(raster_cache); preroll_context_.raster_cache = raster_cache_.get(); paint_context_.raster_cache = raster_cache_.get(); + display_list_paint_context_.raster_cache = raster_cache_.get(); } FixedRefreshRateStopwatch raster_time_; diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc index dff041d36c95a..09b7c20ccde5f 100644 --- a/flow/testing/mock_layer.cc +++ b/flow/testing/mock_layer.cc @@ -10,11 +10,13 @@ namespace testing { MockLayer::MockLayer(SkPath path, SkPaint paint, bool fake_has_platform_view, - bool fake_reads_surface) + bool fake_reads_surface, + bool fake_opacity_compatible) : fake_paint_path_(path), fake_paint_(paint), fake_has_platform_view_(fake_has_platform_view), - fake_reads_surface_(fake_reads_surface) {} + fake_reads_surface_(fake_reads_surface), + fake_opacity_compatible_(fake_opacity_compatible) {} bool MockLayer::IsReplacing(DiffContext* context, const Layer* layer) const { // Similar to PictureLayer, only return true for identical mock layers; @@ -42,12 +44,23 @@ void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { if (fake_reads_surface_) { context->surface_needs_readback = true; } + if (fake_opacity_compatible_) { + context->subtree_can_inherit_opacity = true; + } } void MockLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); + if (context.inherited_opacity < SK_Scalar1) { + SkPaint p; + p.setAlphaf(context.inherited_opacity); + context.leaf_nodes_canvas->saveLayer(fake_paint_path_.getBounds(), &p); + } context.leaf_nodes_canvas->drawPath(fake_paint_path_, fake_paint_); + if (context.inherited_opacity < SK_Scalar1) { + context.leaf_nodes_canvas->restore(); + } } } // namespace testing diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h index 77a01854a0029..54a555c8dc87a 100644 --- a/flow/testing/mock_layer.h +++ b/flow/testing/mock_layer.h @@ -19,7 +19,17 @@ class MockLayer : public Layer { explicit MockLayer(SkPath path, SkPaint paint = SkPaint(), bool fake_has_platform_view = false, - bool fake_reads_surface = false); + bool fake_reads_surface = false, + bool fake_opacity_compatible_ = false); + + static std::shared_ptr Make(SkPath path, + SkPaint paint = SkPaint()) { + return std::make_shared(path, paint, false, false, false); + } + + static std::shared_ptr MakeOpacityCompatible(SkPath path) { + return std::make_shared(path, SkPaint(), false, false, true); + } void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; @@ -42,6 +52,7 @@ class MockLayer : public Layer { bool parent_has_platform_view_ = false; bool fake_has_platform_view_ = false; bool fake_reads_surface_ = false; + bool fake_opacity_compatible_ = false; FML_DISALLOW_COPY_AND_ASSIGN(MockLayer); }; diff --git a/flow/testing/mock_layer_unittests.cc b/flow/testing/mock_layer_unittests.cc index 41bf971fe0daa..3323f76d07618 100644 --- a/flow/testing/mock_layer_unittests.cc +++ b/flow/testing/mock_layer_unittests.cc @@ -78,5 +78,20 @@ TEST_F(MockLayerTest, SaveLayerOnLeafNodesCanvas) { EXPECT_EQ(preroll_context()->has_platform_view, true); } +TEST_F(MockLayerTest, OpacityInheritance) { + auto path1 = SkPath().addRect({10, 10, 30, 30}); + PrerollContext* context = preroll_context(); + + auto mock1 = std::make_shared(path1); + context->subtree_can_inherit_opacity = false; + mock1->Preroll(context, SkMatrix::I()); + EXPECT_FALSE(context->subtree_can_inherit_opacity); + + auto mock2 = MockLayer::MakeOpacityCompatible(path1); + context->subtree_can_inherit_opacity = false; + mock2->Preroll(context, SkMatrix::I()); + EXPECT_TRUE(context->subtree_can_inherit_opacity); +} + } // namespace testing } // namespace flutter diff --git a/flow/testing/mock_raster_cache.cc b/flow/testing/mock_raster_cache.cc index c1669f7e51adc..dafa8274618ce 100644 --- a/flow/testing/mock_raster_cache.cc +++ b/flow/testing/mock_raster_cache.cc @@ -25,6 +25,18 @@ std::unique_ptr MockRasterCache::RasterizePicture( return std::make_unique(cache_rect); } +std::unique_ptr MockRasterCache::RasterizeDisplayList( + DisplayList* display_list, + GrDirectContext* context, + const SkMatrix& ctm, + SkColorSpace* dst_color_space, + bool checkerboard) const { + SkRect logical_rect = display_list->bounds(); + SkIRect cache_rect = RasterCache::GetDeviceBounds(logical_rect, ctm); + + return std::make_unique(cache_rect); +} + std::unique_ptr MockRasterCache::RasterizeLayer( PrerollContext* context, Layer* layer, diff --git a/flow/testing/mock_raster_cache.h b/flow/testing/mock_raster_cache.h index 47e884c1c146b..1bd54ab62c463 100644 --- a/flow/testing/mock_raster_cache.h +++ b/flow/testing/mock_raster_cache.h @@ -63,6 +63,13 @@ class MockRasterCache : public RasterCache { SkColorSpace* dst_color_space, bool checkerboard) const override; + std::unique_ptr RasterizeDisplayList( + DisplayList* display_list, + GrDirectContext* context, + const SkMatrix& ctm, + SkColorSpace* dst_color_space, + bool checkerboard) const override; + std::unique_ptr RasterizeLayer( PrerollContext* context, Layer* layer, diff --git a/shell/platform/fuchsia/flutter/flatland_platform_view.cc b/shell/platform/fuchsia/flutter/flatland_platform_view.cc index 07fc7c750438b..07e15f8a028e3 100644 --- a/shell/platform/fuchsia/flutter/flatland_platform_view.cc +++ b/shell/platform/fuchsia/flutter/flatland_platform_view.cc @@ -32,7 +32,8 @@ FlatlandPlatformView::FlatlandPlatformView( AwaitVsyncCallback await_vsync_callback, AwaitVsyncForSecondaryCallbackCallback await_vsync_for_secondary_callback_callback) - : PlatformView(delegate, + : PlatformView(true /* is_flatland */, + delegate, std::move(task_runners), std::move(view_ref), std::move(external_view_embedder), diff --git a/shell/platform/fuchsia/flutter/gfx_platform_view.cc b/shell/platform/fuchsia/flutter/gfx_platform_view.cc index 4f1f05b1a224a..0b11daacf7dc2 100644 --- a/shell/platform/fuchsia/flutter/gfx_platform_view.cc +++ b/shell/platform/fuchsia/flutter/gfx_platform_view.cc @@ -33,7 +33,8 @@ GfxPlatformView::GfxPlatformView( AwaitVsyncCallback await_vsync_callback, AwaitVsyncForSecondaryCallbackCallback await_vsync_for_secondary_callback_callback) - : PlatformView(delegate, + : PlatformView(false /* is_flatland */, + delegate, std::move(task_runners), std::move(view_ref), std::move(external_view_embedder), diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index d0f24d1374cf9..1ff88686fc70b 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -51,6 +51,7 @@ void SetInterfaceErrorHandler(fidl::Binding& binding, std::string name) { } PlatformView::PlatformView( + bool is_flatland, flutter::PlatformView::Delegate& delegate, flutter::TaskRunners task_runners, fuchsia::ui::views::ViewRef view_ref, @@ -121,30 +122,33 @@ PlatformView::PlatformView( }); // Begin watching for pointer events. - pointer_delegate_->WatchLoop([weak = weak_factory_.GetWeakPtr()]( - std::vector events) { - if (!weak) { - FML_LOG(WARNING) << "PlatformView use-after-free attempted. Ignoring."; - return; - } + if (is_flatland) { // TODO(fxbug.dev/85125): make unconditional + pointer_delegate_->WatchLoop([weak = weak_factory_.GetWeakPtr()]( + std::vector events) { + if (!weak) { + FML_LOG(WARNING) << "PlatformView use-after-free attempted. Ignoring."; + return; + } - if (events.size() == 0) { - return; // No work, bounce out. - } + if (events.size() == 0) { + return; // No work, bounce out. + } - // If pixel ratio hasn't been set, use a default value of 1. - const float pixel_ratio = weak->view_pixel_ratio_.value_or(1.f); - auto packet = std::make_unique(events.size()); - for (size_t i = 0; i < events.size(); ++i) { - auto& event = events[i]; - // Translate logical to physical coordinates, as per flutter::PointerData - // contract. Done here because pixel ratio comes from the graphics API. - event.physical_x = event.physical_x * pixel_ratio; - event.physical_y = event.physical_y * pixel_ratio; - packet->SetPointerData(i, event); - } - weak->DispatchPointerDataPacket(std::move(packet)); - }); + // If pixel ratio hasn't been set, use a default value of 1. + const float pixel_ratio = weak->view_pixel_ratio_.value_or(1.f); + auto packet = std::make_unique(events.size()); + for (size_t i = 0; i < events.size(); ++i) { + auto& event = events[i]; + // Translate logical to physical coordinates, as per + // flutter::PointerData contract. Done here because pixel ratio comes + // from the graphics API. + event.physical_x = event.physical_x * pixel_ratio; + event.physical_y = event.physical_y * pixel_ratio; + packet->SetPointerData(i, event); + } + weak->DispatchPointerDataPacket(std::move(packet)); + }); + } // Finally! Register the native platform message handlers. RegisterPlatformMessageHandlers(); diff --git a/shell/platform/fuchsia/flutter/platform_view.h b/shell/platform/fuchsia/flutter/platform_view.h index cdf502291ed1a..25d8aedd6b7a8 100644 --- a/shell/platform/fuchsia/flutter/platform_view.h +++ b/shell/platform/fuchsia/flutter/platform_view.h @@ -64,6 +64,7 @@ class PlatformView : public flutter::PlatformView, private fuchsia::ui::input::InputMethodEditorClient { public: PlatformView( + bool is_flatland, flutter::PlatformView::Delegate& delegate, flutter::TaskRunners task_runners, fuchsia::ui::views::ViewRef view_ref, diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc index 2f5e8606ebec1..2b4e2f5938a22 100644 --- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -1385,7 +1385,8 @@ TEST_F(PlatformViewTests, OnShaderWarmup) { EXPECT_EQ(expected_result_string, response->result_string); } -TEST_F(PlatformViewTests, TouchSourceLogicalToPhysicalConversion) { +// TODO(fxbug.dev/85125): Enable when GFX converts to TouchSource. +TEST_F(PlatformViewTests, DISABLED_TouchSourceLogicalToPhysicalConversion) { constexpr std::array, 2> kRect = {{{0, 0}, {20, 20}}}; constexpr std::array kIdentity = {1, 0, 0, 0, 1, 0, 0, 0, 1}; constexpr fuchsia::ui::pointer::TouchInteractionId kIxnOne = { diff --git a/testing/display_list_testing.cc b/testing/display_list_testing.cc index 39c25075332fe..3f026339cd392 100644 --- a/testing/display_list_testing.cc +++ b/testing/display_list_testing.cc @@ -593,9 +593,9 @@ void DisplayListStreamDispatcher::saveLayer(const SkRect* bounds, indent(); } void DisplayListStreamDispatcher::restore() { - startl() << "restore();" << std::endl; outdent(); startl() << "}" << std::endl; + startl() << "restore();" << std::endl; } void DisplayListStreamDispatcher::translate(SkScalar tx, SkScalar ty) { @@ -651,7 +651,7 @@ void DisplayListStreamDispatcher::transformFullPerspective( startl() << ");" << std::endl; } void DisplayListStreamDispatcher::transformReset() { - startl() << "tranformReset();" << std::endl; + startl() << "transformReset();" << std::endl; } void DisplayListStreamDispatcher::clipRect(const SkRect& rect, SkClipOp clip_op, diff --git a/testing/mock_canvas.cc b/testing/mock_canvas.cc index 0132af7fe1a40..b53a01ca51caf 100644 --- a/testing/mock_canvas.cc +++ b/testing/mock_canvas.cc @@ -108,6 +108,21 @@ void MockCanvas::onDrawShadowRec(const SkPath& path, draw_calls_.emplace_back(DrawCall{current_layer_, DrawShadowData{path}}); } +void MockCanvas::onDrawImage2(const SkImage* image, + SkScalar x, + SkScalar y, + const SkSamplingOptions& options, + const SkPaint* paint) { + if (paint) { + draw_calls_.emplace_back( + DrawCall{current_layer_, + DrawImageData{sk_ref_sp(image), x, y, options, *paint}}); + } else { + draw_calls_.emplace_back(DrawCall{ + current_layer_, DrawImageDataNoPaint{sk_ref_sp(image), x, y, options}}); + } +} + void MockCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) { @@ -220,14 +235,6 @@ void MockCanvas::onDrawRRect(const SkRRect&, const SkPaint&) { FML_DCHECK(false); } -void MockCanvas::onDrawImage2(const SkImage*, - SkScalar, - SkScalar, - const SkSamplingOptions&, - const SkPaint*) { - FML_DCHECK(false); -} - void MockCanvas::onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, @@ -357,6 +364,30 @@ std::ostream& operator<<(std::ostream& os, return os << data.serialized_text << " " << data.paint << " " << data.offset; } +bool operator==(const MockCanvas::DrawImageData& a, + const MockCanvas::DrawImageData& b) { + return a.image == b.image && a.x == b.x && a.y == b.y && + a.options == b.options && a.paint == b.paint; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawImageData& data) { + return os << data.image << " " << data.x << " " << data.y << " " + << data.options << " " << data.paint; +} + +bool operator==(const MockCanvas::DrawImageDataNoPaint& a, + const MockCanvas::DrawImageDataNoPaint& b) { + return a.image == b.image && a.x == b.x && a.y == b.y && + a.options == b.options; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawImageDataNoPaint& data) { + return os << data.image << " " << data.x << " " << data.y << " " + << data.options; +} + bool operator==(const MockCanvas::DrawPictureData& a, const MockCanvas::DrawPictureData& b) { return a.serialized_picture->equals(b.serialized_picture.get()) && diff --git a/testing/mock_canvas.h b/testing/mock_canvas.h index f31d5e385ba97..c09acf5d9e0a3 100644 --- a/testing/mock_canvas.h +++ b/testing/mock_canvas.h @@ -15,6 +15,7 @@ #include "third_party/skia/include/core/SkCanvasVirtualEnforcer.h" #include "third_party/skia/include/core/SkClipOp.h" #include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkImageFilter.h" #include "third_party/skia/include/core/SkM44.h" #include "third_party/skia/include/core/SkPath.h" @@ -76,6 +77,21 @@ class MockCanvas : public SkCanvasVirtualEnforcer { SkPoint offset; }; + struct DrawImageDataNoPaint { + sk_sp image; + SkScalar x; + SkScalar y; + SkSamplingOptions options; + }; + + struct DrawImageData { + sk_sp image; + SkScalar x; + SkScalar y; + SkSamplingOptions options; + SkPaint paint; + }; + struct DrawPictureData { sk_sp serialized_picture; SkPaint paint; @@ -118,6 +134,8 @@ class MockCanvas : public SkCanvasVirtualEnforcer { DrawRectData, DrawPathData, DrawTextData, + DrawImageDataNoPaint, + DrawImageData, DrawPictureData, DrawShadowData, ClipRectData, @@ -268,6 +286,14 @@ extern bool operator==(const MockCanvas::DrawTextData& a, const MockCanvas::DrawTextData& b); extern std::ostream& operator<<(std::ostream& os, const MockCanvas::DrawTextData& data); +extern bool operator==(const MockCanvas::DrawImageData& a, + const MockCanvas::DrawImageData& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawImageData& data); +extern bool operator==(const MockCanvas::DrawImageDataNoPaint& a, + const MockCanvas::DrawImageDataNoPaint& b); +extern std::ostream& operator<<(std::ostream& os, + const MockCanvas::DrawImageDataNoPaint& data); extern bool operator==(const MockCanvas::DrawPictureData& a, const MockCanvas::DrawPictureData& b); extern std::ostream& operator<<(std::ostream& os,