From 938b09fbeb13e0e701144ea1cadd782684019664 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Mon, 24 Jul 2023 14:26:57 -0700 Subject: [PATCH 1/6] Embed a license readme in every zip archive --- BUILD.gn | 18 +++++++++--------- build/archives/BUILD.gn | 12 ------------ build/zip_bundle.gni | 28 ++++++++++++++++++++++++++-- impeller/compiler/BUILD.gn | 25 +------------------------ tools/font-subset/BUILD.gn | 22 ---------------------- tools/path_ops/BUILD.gn | 28 +--------------------------- 6 files changed, 37 insertions(+), 96 deletions(-) diff --git a/BUILD.gn b/BUILD.gn index 46a1e76c8fcea..b05a9367d1ed1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -125,15 +125,15 @@ group("flutter") { ] } - if ((flutter_runtime_mode == "debug" || flutter_runtime_mode == "profile") && - (is_ios || is_android)) { - public_deps += [ "//flutter/testing/scenario_app" ] - } - - if (is_android && flutter_runtime_mode == "profile" && - current_cpu == "arm64") { - public_deps += [ "//flutter/testing/android_background_image" ] - } + # if ((flutter_runtime_mode == "debug" || flutter_runtime_mode == "profile") && + # (is_ios || is_android)) { + # public_deps += [ "//flutter/testing/scenario_app" ] + # } + + # if (is_android && flutter_runtime_mode == "profile" && + # current_cpu == "arm64") { + # public_deps += [ "//flutter/testing/android_background_image" ] + # } } group("unittests") { diff --git a/build/archives/BUILD.gn b/build/archives/BUILD.gn index 5530c63784689..002bbeabada90 100644 --- a/build/archives/BUILD.gn +++ b/build/archives/BUILD.gn @@ -26,12 +26,10 @@ if (build_engine_artifacts && !is_android) { deps = [ "//flutter/flutter_frontend_server:frontend_server", "//flutter/impeller/compiler:impellerc", - "//flutter/impeller/compiler:impellerc_license", "//flutter/impeller/tessellator:tessellator_shared", "//flutter/lib/snapshot:generate_snapshot_bin", "//flutter/shell/testing:testing", "//flutter/tools/path_ops:path_ops", - "//flutter/tools/path_ops:path_ops_license", "//third_party/dart/runtime/bin:gen_snapshot", ] if (is_mac) { @@ -81,16 +79,6 @@ if (build_engine_artifacts && !is_android) { source = "$root_out_dir/flutter_tester$exe" destination = "flutter_tester$exe" }, - { - source = - "$root_out_dir/gen/flutter/impeller/compiler/LICENSE.impellerc.md" - destination = "LICENSE.impellerc.md" - }, - { - source = - "$root_out_dir/gen/flutter/tools/path_ops/LICENSE.path_ops.md" - destination = "LICENSE.path_ops.md" - }, { source = "$root_out_dir/impellerc$exe" destination = "impellerc$exe" diff --git a/build/zip_bundle.gni b/build/zip_bundle.gni index 034aaba76da45..85e127fed20ec 100644 --- a/build/zip_bundle.gni +++ b/build/zip_bundle.gni @@ -3,6 +3,7 @@ # found in the LICENSE file. import("//flutter/common/config.gni") +import("//flutter/shell/version/version.gni") if (flutter_runtime_mode == "jit_release") { android_zip_archive_dir = "android-$target_cpu-jit-release" @@ -38,16 +39,39 @@ if (flutter_runtime_mode == "jit_release") { template("zip_bundle") { assert(defined(invoker.output), "output must be defined") assert(defined(invoker.files), "files must be defined as a list of scopes") + + license_readme = "$target_gen_dir/LICENSE.$target_name.md" + license_target = "_${target_name}_license_md" + + generated_file(license_target) { + source_path = rebase_path(".", "//flutter") + license_path = + rebase_path("//flutter/sky/packages/sky_engine/LICENSE", "//flutter") + git_url = "https://github.com/flutter/engine/tree/$engine_version" + outputs = [ license_readme ] + contents = [ + "# $target_name", + "", + "This bundle is part of the the Flutter SDK.", + "", + "The source code is hosted at [flutter/engine/$source_path]($git_url/$source_path).", + "The for this bundle is hosted at [flutter/engine/sky/packages/sky_engine/LICENSE]($git_url/$license_path).", + ] + } + action(target_name) { script = "//flutter/build/zip.py" outputs = [ "$root_build_dir/zip_archives/${invoker.output}" ] - sources = [] + sources = [ license_readme ] forward_variables_from(invoker, [ "visibility" ]) - deps = invoker.deps + deps = [ ":$license_target" ] + invoker.deps args = [ "-o", rebase_path(outputs[0]), + "-i", + rebase_path(license_readme), + "LICENSE.md", ] foreach(input, invoker.files) { args += [ diff --git a/impeller/compiler/BUILD.gn b/impeller/compiler/BUILD.gn index 788248f5b1b05..60b624133e74d 100644 --- a/impeller/compiler/BUILD.gn +++ b/impeller/compiler/BUILD.gn @@ -72,26 +72,6 @@ impeller_component("compiler_lib") { ] } -generated_file("impellerc_license") { - source_path = rebase_path(".", "//flutter") - git_url = "https://github.com/flutter/engine/tree/$engine_version" - outputs = [ "$target_gen_dir/LICENSE.impellerc.md" ] - contents = [ - "# impellerc", - "", - "This tool is used by the Flutter SDK to compile shaders.", - "", - "Source code for this tool: [flutter/engine/$source_path]($git_url/$source_path).", - "", - "## Licenses", - "", - "### impellerc", - "", - read_file("//flutter/sky/packages/sky_engine/LICENSE", "string"), - "", - ] -} - impeller_component("impellerc") { target_type = "executable" @@ -108,10 +88,7 @@ impeller_component("impellerc") { } group("compiler") { - deps = [ - ":impellerc", - ":impellerc_license", - ] + deps = [ ":impellerc" ] } impeller_component("compiler_unittests") { diff --git a/tools/font-subset/BUILD.gn b/tools/font-subset/BUILD.gn index 880769d4d75fc..db8fd7f434fc5 100644 --- a/tools/font-subset/BUILD.gn +++ b/tools/font-subset/BUILD.gn @@ -3,7 +3,6 @@ # found in the LICENSE file. import("//flutter/build/zip_bundle.gni") -import("//flutter/shell/version/version.gni") executable("_font-subset") { output_name = "font-subset" @@ -28,22 +27,6 @@ executable("_font-subset") { } } -generated_file("_font-subset-license") { - source_path = rebase_path(".", "//flutter") - license_path = - rebase_path("//flutter/sky/packages/sky_engine/LICENSE", "//flutter") - git_url = "https://github.com/flutter/engine/tree/$engine_version" - outputs = [ "$target_gen_dir/LICENSE.font-subset.md" ] - contents = [ - "# font-subset", - "", - "This tool is used by the Flutter SDK to tree shake icon fonts.", - "", - "Source code for this tool: [flutter/engine/$source_path]($git_url/$source_path).", - "License for this tool: [flutter/engine/sky/packages/sky_engine/LICENSE]($git_url/$license_path).", - ] -} - generated_file("font_entitlement_config") { outputs = [ "$target_gen_dir/font_subset_without_entitlements.txt" ] @@ -75,15 +58,10 @@ zip_bundle("font-subset") { source = "$root_gen_dir/const_finder.dart.snapshot" destination = "const_finder.dart.snapshot" }, - { - source = "$target_gen_dir/LICENSE.font-subset.md" - destination = "LICENSE.font-subset.md" - }, ] deps = [ ":_font-subset", - ":_font-subset-license", "//flutter/tools/const_finder", ] if (is_mac) { diff --git a/tools/path_ops/BUILD.gn b/tools/path_ops/BUILD.gn index 8871392db967b..cb711533dfbe4 100644 --- a/tools/path_ops/BUILD.gn +++ b/tools/path_ops/BUILD.gn @@ -4,38 +4,12 @@ import("//flutter/shell/version/version.gni") -generated_file("path_ops_license") { - source_path = rebase_path(".", "//flutter") - git_url = "https://github.com/flutter/engine/tree/$engine_version" - outputs = [ "$target_gen_dir/LICENSE.path_ops.md" ] - contents = [ - "# path_ops", - "", - "This tool is used by the vector_graphics library to calculate path intersections.", - "", - "Source code for this tool: [flutter/engine/$source_path]($git_url/$source_path).", - "", - "## Licenses", - "", - read_file("//flutter/sky/packages/sky_engine/LICENSE", "string"), - "", - "## Additional open source licenses", - "", - "### Skia", - "", - read_file("//third_party/skia/LICENSE", "string"), - ] -} - shared_library("path_ops") { sources = [ "path_ops.cc", "path_ops.h", ] - deps = [ - ":path_ops_license", - "//third_party/skia", - ] + deps = [ "//third_party/skia" ] metadata = { entitlement_file_path = [ "libpath_ops.dylib" ] From f31335231b5b1d53aa1857ea6ef10d2812917cd2 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Mon, 24 Jul 2023 14:36:10 -0700 Subject: [PATCH 2/6] Update BUILD.gn --- BUILD.gn | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/BUILD.gn b/BUILD.gn index b05a9367d1ed1..46a1e76c8fcea 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -125,15 +125,15 @@ group("flutter") { ] } - # if ((flutter_runtime_mode == "debug" || flutter_runtime_mode == "profile") && - # (is_ios || is_android)) { - # public_deps += [ "//flutter/testing/scenario_app" ] - # } - - # if (is_android && flutter_runtime_mode == "profile" && - # current_cpu == "arm64") { - # public_deps += [ "//flutter/testing/android_background_image" ] - # } + if ((flutter_runtime_mode == "debug" || flutter_runtime_mode == "profile") && + (is_ios || is_android)) { + public_deps += [ "//flutter/testing/scenario_app" ] + } + + if (is_android && flutter_runtime_mode == "profile" && + current_cpu == "arm64") { + public_deps += [ "//flutter/testing/android_background_image" ] + } } group("unittests") { From 2d0334697d62dee05a98326f57532dd760ed907b Mon Sep 17 00:00:00 2001 From: Dan Field Date: Mon, 24 Jul 2023 20:44:53 -0700 Subject: [PATCH 3/6] Update build/zip_bundle.gni Co-authored-by: Zachary Anderson --- build/zip_bundle.gni | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/zip_bundle.gni b/build/zip_bundle.gni index 85e127fed20ec..c8a024391a3b5 100644 --- a/build/zip_bundle.gni +++ b/build/zip_bundle.gni @@ -55,7 +55,7 @@ template("zip_bundle") { "This bundle is part of the the Flutter SDK.", "", "The source code is hosted at [flutter/engine/$source_path]($git_url/$source_path).", - "The for this bundle is hosted at [flutter/engine/sky/packages/sky_engine/LICENSE]($git_url/$license_path).", + "The license for this bundle is hosted at [flutter/engine/sky/packages/sky_engine/LICENSE]($git_url/$license_path).", ] } From 4cf3037553a82bc0ea1ff798324cc031a052c526 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 25 Jul 2023 09:22:45 -0700 Subject: [PATCH 4/6] Mention sky_engine.zip as well --- build/zip_bundle.gni | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/zip_bundle.gni b/build/zip_bundle.gni index c8a024391a3b5..5b4a731d42d6e 100644 --- a/build/zip_bundle.gni +++ b/build/zip_bundle.gni @@ -48,6 +48,7 @@ template("zip_bundle") { license_path = rebase_path("//flutter/sky/packages/sky_engine/LICENSE", "//flutter") git_url = "https://github.com/flutter/engine/tree/$engine_version" + sky_engine_url = "https://storage.googleapis.com/flutter_infra_release/flutter/$engine_version/sky_engine.zip" outputs = [ license_readme ] contents = [ "# $target_name", @@ -55,7 +56,8 @@ template("zip_bundle") { "This bundle is part of the the Flutter SDK.", "", "The source code is hosted at [flutter/engine/$source_path]($git_url/$source_path).", - "The license for this bundle is hosted at [flutter/engine/sky/packages/sky_engine/LICENSE]($git_url/$license_path).", + "The license for this bundle is hosted at [flutter/engine/sky/packages/sky_engine/LICENSE]($git_url/$license_path) ", + "and [sky_engine.zip]($sky_engine_url).", ] } From 91a182a730ba0ba007d48c9ece904c222cfaf8e9 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 25 Jul 2023 09:23:44 -0700 Subject: [PATCH 5/6] Make sure not to overwrite license.md --- build/zip_bundle.gni | 2 +- display_list/dl_optimizing_canvas.cc | 1339 ++++++++++++++++++++++++++ display_list/dl_optimizing_canvas.h | 295 ++++++ 3 files changed, 1635 insertions(+), 1 deletion(-) create mode 100644 display_list/dl_optimizing_canvas.cc create mode 100644 display_list/dl_optimizing_canvas.h diff --git a/build/zip_bundle.gni b/build/zip_bundle.gni index 5b4a731d42d6e..cdc9b2d812902 100644 --- a/build/zip_bundle.gni +++ b/build/zip_bundle.gni @@ -73,7 +73,7 @@ template("zip_bundle") { rebase_path(outputs[0]), "-i", rebase_path(license_readme), - "LICENSE.md", + "LICENSE.$target_name.md", ] foreach(input, invoker.files) { args += [ diff --git a/display_list/dl_optimizing_canvas.cc b/display_list/dl_optimizing_canvas.cc new file mode 100644 index 0000000000000..480adc3efc74b --- /dev/null +++ b/display_list/dl_optimizing_canvas.cc @@ -0,0 +1,1339 @@ +// 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/dl_builder.h" + +#include "flutter/display_list/display_list.h" +#include "flutter/display_list/dl_blend_mode.h" +#include "flutter/display_list/dl_op_flags.h" +#include "flutter/display_list/dl_op_records.h" +#include "flutter/display_list/effects/dl_color_source.h" +#include "flutter/display_list/utils/dl_bounds_accumulator.h" +#include "fml/logging.h" +#include "third_party/skia/include/core/SkScalar.h" + +namespace flutter { + +static constexpr inline bool is_power_of_two(int value) { + return (value & (value - 1)) == 0; +} + +DlOptimizingCanvas::DlOptimizingCanvas(const SkRect& cull_rect, + bool prepare_rtree) + : tracker_(cull_rect, SkMatrix::I()) { + if (prepare_rtree) { + accumulator_ = std::make_unique(); + } else { + accumulator_ = std::make_unique(); + } + + layer_stack_.emplace_back(); + current_layer_ = &layer_stack_.back(); +} + +SkISize DlOptimizingCanvas::GetBaseLayerSize() const { + return tracker_.base_device_cull_rect().roundOut().size(); +} + +SkImageInfo DlOptimizingCanvas::GetImageInfo() const { + SkISize size = GetBaseLayerSize(); + return SkImageInfo::MakeUnknown(size.width(), size.height()); +} + +void DlOptimizingCanvas::onSetAntiAlias(bool aa) { + current_.setAntiAlias(aa); +} +void DlOptimizingCanvas::onSetDither(bool dither) { + current_.setDither(dither); +} +void DlOptimizingCanvas::onSetInvertColors(bool invert) { + current_.setInvertColors(invert); + UpdateCurrentOpacityCompatibility(); +} +void DlOptimizingCanvas::onSetStrokeCap(DlStrokeCap cap) { + current_.setStrokeCap(cap); +} +void DlOptimizingCanvas::onSetStrokeJoin(DlStrokeJoin join) { + current_.setStrokeJoin(join); +} +void DlOptimizingCanvas::onSetDrawStyle(DlDrawStyle style) { + current_.setDrawStyle(style); +} +void DlOptimizingCanvas::onSetStrokeWidth(float width) { + current_.setStrokeWidth(width); +} +void DlOptimizingCanvas::onSetStrokeMiter(float limit) { + current_.setStrokeMiter(limit); +} +void DlOptimizingCanvas::onSetColor(DlColor color) { + current_.setColor(color); +} +void DlOptimizingCanvas::onSetBlendMode(DlBlendMode mode) { + current_.setBlendMode(mode); + + UpdateCurrentOpacityCompatibility(); +} + +void DlOptimizingCanvas::onSetColorSource(const DlColorSource* source) { + if (source == nullptr) { + current_.setColorSource(nullptr); + + } else { + current_.setColorSource(source->shared()); + is_ui_thread_safe_ = is_ui_thread_safe_ && source->isUIThreadSafe(); + switch (source->type()) { + case DlColorSourceType::kColor: { + const DlColorColorSource* color_source = source->asColor(); + current_.setColorSource(nullptr); + setColor(color_source->color()); + break; + } + case DlColorSourceType::kImage: + case DlColorSourceType::kLinearGradient: + case DlColorSourceType::kRadialGradient: + case DlColorSourceType::kConicalGradient: + case DlColorSourceType::kSweepGradient: + case DlColorSourceType::kRuntimeEffect: +#ifdef IMPELLER_ENABLE_3D + case DlColorSourceType::kScene: +#endif // IMPELLER_ENABLE_3D + break; + } + } +} +void DlOptimizingCanvas::onSetImageFilter(const DlImageFilter* filter) { + if (filter == nullptr) { + current_.setImageFilter(nullptr); + } else { + current_.setImageFilter(filter->shared()); + } +} +void DlOptimizingCanvas::onSetColorFilter(const DlColorFilter* filter) { + if (filter == nullptr) { + current_.setColorFilter(nullptr); + } else { + current_.setColorFilter(filter->shared()); + } + UpdateCurrentOpacityCompatibility(); +} +void DlOptimizingCanvas::onSetPathEffect(const DlPathEffect* effect) { + if (effect == nullptr) { + current_.setPathEffect(nullptr); + } else { + current_.setPathEffect(effect->shared()); + } +} +void DlOptimizingCanvas::onSetMaskFilter(const DlMaskFilter* filter) { + if (filter == nullptr) { + current_.setMaskFilter(nullptr); + + } else { + current_.setMaskFilter(filter->shared()); + } +} + +void DlOptimizingCanvas::checkForDeferredSave() { + if (current_layer_->has_deferred_save_op_) { + size_t save_offset_ = used_; + Push(0, 1); + current_layer_->save_offset_ = save_offset_; + current_layer_->has_deferred_save_op_ = false; + } +} + +void DlOptimizingCanvas::Save() { + bool is_nop = current_layer_->is_nop_; + layer_stack_.emplace_back(); + current_layer_ = &layer_stack_.back(); + current_layer_->has_deferred_save_op_ = true; + current_layer_->is_nop_ = is_nop; + tracker_.save(); + accumulator()->save(); +} + +void DlOptimizingCanvas::Restore() { + if (layer_stack_.size() > 1) { + // Grab the current layer info before we push the restore + // on the stack. + LayerInfo layer_info = layer_stack_.back(); + + tracker_.restore(); + layer_stack_.pop_back(); + current_layer_ = &layer_stack_.back(); + bool is_unbounded = layer_info.is_unbounded(); + + // Before we pop_back we will get the current layer bounds from the + // current accumulator and adjust it as required based on the filter. + std::shared_ptr filter = layer_info.filter(); + if (filter) { + const SkRect clip = tracker_.device_cull_rect(); + if (!accumulator()->restore( + [filter = filter, matrix = GetTransform()](const SkRect& input, + SkRect& output) { + SkIRect output_bounds; + bool ret = filter->map_device_bounds(input.roundOut(), matrix, + output_bounds); + output.set(output_bounds); + return ret; + }, + &clip)) { + is_unbounded = true; + } + } else { + accumulator()->restore(); + } + + if (is_unbounded) { + AccumulateUnbounded(); + } + + if (!layer_info.has_layer()) { + // For regular save() ops there was no protecting layer so we have to + // accumulate the values into the enclosing layer. + if (layer_info.cannot_inherit_opacity()) { + current_layer_->mark_incompatible(); + } else if (layer_info.has_compatible_op()) { + current_layer_->add_compatible_op(); + } + } + } +} + +void DlOptimizingCanvas::saveLayer(const SkRect* bounds, + const SaveLayerOptions in_options, + const DlImageFilter* backdrop) { + SaveLayerOptions options = in_options.without_optimizations(); + DisplayListAttributeFlags flags = options.renders_with_attributes() + ? kSaveLayerWithPaintFlags + : kSaveLayerFlags; + OpResult result = PaintResult(current_, flags); + if (result == OpResult::kNoEffect) { + save(); + current_layer_->is_nop_ = true; + return; + } + size_t save_layer_offset = used_; + if (options.renders_with_attributes()) { + // The actual flood of the outer layer clip will occur after the + // (eventual) corresponding restore is called, but rather than + // remember this information in the LayerInfo until the restore + // method is processed, we just mark the unbounded state up front. + // Another reason to accumulate the clip here rather than in + // restore is so that this savelayer will be tagged in the rtree + // with its full bounds and the right op_index so that it doesn't + // get culled during rendering. + if (!paint_nops_on_transparency()) { + // We will fill the clip of the outer layer when we restore. + // Accumulate should always return true here because if the + // clip was empty then that would have been caught up above + // when we tested the PaintResult. + [[maybe_unused]] bool unclipped = AccumulateUnbounded(); + FML_DCHECK(unclipped); + } + CheckLayerOpacityCompatibility(true); + layer_stack_.emplace_back(save_layer_offset, true, + current_.getImageFilter()); + } else { + CheckLayerOpacityCompatibility(false); + layer_stack_.emplace_back(save_layer_offset, true, nullptr); + } + current_layer_ = &layer_stack_.back(); + + tracker_.save(); + accumulator()->save(); + + if (backdrop) { + // A backdrop will affect up to the entire surface, bounded by the clip + // Accumulate should always return true here because if the + // clip was empty then that would have been caught up above + // when we tested the PaintResult. + [[maybe_unused]] bool unclipped = AccumulateUnbounded(); + FML_DCHECK(unclipped); + bounds // + ? Push(0, 1, options, *bounds, backdrop) + : Push(0, 1, options, backdrop); + } else { + bounds // + ? Push(0, 1, options, *bounds) + : Push(0, 1, options); + } + + if (options.renders_with_attributes()) { + // |current_opacity_compatibility_| does not take an ImageFilter into + // account because an individual primitive with an ImageFilter can apply + // opacity on top of it. But, if the layer is applying the ImageFilter + // then it cannot pass the opacity on. + if (!current_opacity_compatibility_ || + current_.getImageFilter() != nullptr) { + UpdateLayerOpacityCompatibility(false); + } + } + UpdateLayerResult(result); + + if (options.renders_with_attributes() && current_.getImageFilter()) { + // We use |resetCullRect| here because we will be accumulating bounds of + // primitives before applying the filter to those bounds. We might + // encounter a primitive whose bounds are clipped, but whose filtered + // bounds will not be clipped. If the individual rendering ops bounds + // are clipped, it will not contribute to the overall bounds which + // could lead to inaccurate (subset) bounds of the DisplayList. + // We need to reset the cull rect here to avoid this premature clipping. + // The filtered bounds will be clipped to the existing clip rect when + // this layer is restored. + // If bounds is null then the original cull_rect will be used. + tracker_.resetCullRect(bounds); + } else if (bounds) { + // Even though Skia claims that the bounds are only a hint, they actually + // use them as the temporary layer bounds during rendering the layer, so + // we set them as if a clip operation were performed. + tracker_.clipRect(*bounds, ClipOp::kIntersect, false); + } +} +void DlOptimizingCanvas::SaveLayer(const SkRect* bounds, + const DlPaint* paint, + const DlImageFilter* backdrop) { + if (paint != nullptr) { + SetAttributesFromPaint(*paint, + DisplayListOpFlags::kSaveLayerWithPaintFlags); + saveLayer(bounds, SaveLayerOptions::kWithAttributes, backdrop); + } else { + saveLayer(bounds, SaveLayerOptions::kNoAttributes, backdrop); + } +} + +void DlOptimizingCanvas::Translate(SkScalar tx, SkScalar ty) { + if (SkScalarIsFinite(tx) && SkScalarIsFinite(ty) && + (tx != 0.0 || ty != 0.0)) { + checkForDeferredSave(); + Push(0, 1, tx, ty); + tracker_.translate(tx, ty); + } +} +void DlOptimizingCanvas::Scale(SkScalar sx, SkScalar sy) { + if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && + (sx != 1.0 || sy != 1.0)) { + checkForDeferredSave(); + Push(0, 1, sx, sy); + tracker_.scale(sx, sy); + } +} +void DlOptimizingCanvas::Rotate(SkScalar degrees) { + if (SkScalarMod(degrees, 360.0) != 0.0) { + checkForDeferredSave(); + Push(0, 1, degrees); + tracker_.rotate(degrees); + } +} +void DlOptimizingCanvas::Skew(SkScalar sx, SkScalar sy) { + if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && + (sx != 0.0 || sy != 0.0)) { + checkForDeferredSave(); + Push(0, 1, sx, sy); + tracker_.skew(sx, sy); + } +} + +// clang-format off + +// 2x3 2D affine subset of a 4x4 transform in row major order +void DlOptimizingCanvas::Transform2DAffine( + SkScalar mxx, SkScalar mxy, SkScalar mxt, + SkScalar myx, SkScalar myy, SkScalar myt) { + if (SkScalarsAreFinite(mxx, myx) && + SkScalarsAreFinite(mxy, myy) && + SkScalarsAreFinite(mxt, myt)) { + if (mxx == 1 && mxy == 0 && + myx == 0 && myy == 1) { + Translate(mxt, myt); + } else { + checkForDeferredSave(); + Push(0, 1, + mxx, mxy, mxt, + myx, myy, myt); + tracker_.transform2DAffine(mxx, mxy, mxt, + myx, myy, myt); + } + } +} +// full 4x4 transform in row major order +void DlOptimizingCanvas::TransformFullPerspective( + SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, + SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, + SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, + SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) { + if ( mxz == 0 && + myz == 0 && + mzx == 0 && mzy == 0 && mzz == 1 && mzt == 0 && + mwx == 0 && mwy == 0 && mwz == 0 && mwt == 1) { + Transform2DAffine(mxx, mxy, mxt, + myx, myy, myt); + } else if (SkScalarsAreFinite(mxx, mxy) && SkScalarsAreFinite(mxz, mxt) && + SkScalarsAreFinite(myx, myy) && SkScalarsAreFinite(myz, myt) && + SkScalarsAreFinite(mzx, mzy) && SkScalarsAreFinite(mzz, mzt) && + SkScalarsAreFinite(mwx, mwy) && SkScalarsAreFinite(mwz, mwt)) { + checkForDeferredSave(); + Push(0, 1, + mxx, mxy, mxz, mxt, + myx, myy, myz, myt, + mzx, mzy, mzz, mzt, + mwx, mwy, mwz, mwt); + tracker_.transformFullPerspective(mxx, mxy, mxz, mxt, + myx, myy, myz, myt, + mzx, mzy, mzz, mzt, + mwx, mwy, mwz, mwt); + } +} +// clang-format on +void DlOptimizingCanvas::TransformReset() { + checkForDeferredSave(); + Push(0, 0); + tracker_.setIdentity(); +} +void DlOptimizingCanvas::Transform(const SkMatrix* matrix) { + if (matrix != nullptr) { + Transform(SkM44(*matrix)); + } +} +void DlOptimizingCanvas::Transform(const SkM44* m44) { + if (m44 != nullptr) { + transformFullPerspective( + m44->rc(0, 0), m44->rc(0, 1), m44->rc(0, 2), m44->rc(0, 3), + m44->rc(1, 0), m44->rc(1, 1), m44->rc(1, 2), m44->rc(1, 3), + m44->rc(2, 0), m44->rc(2, 1), m44->rc(2, 2), m44->rc(2, 3), + m44->rc(3, 0), m44->rc(3, 1), m44->rc(3, 2), m44->rc(3, 3)); + } +} + +void DlOptimizingCanvas::ClipRect(const SkRect& rect, + ClipOp clip_op, + bool is_aa) { + if (!rect.isFinite()) { + return; + } + tracker_.clipRect(rect, clip_op, is_aa); + if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { + current_layer_->is_nop_ = true; + return; + } + checkForDeferredSave(); + switch (clip_op) { + case ClipOp::kIntersect: + Push(0, 1, rect, is_aa); + break; + case ClipOp::kDifference: + Push(0, 1, rect, is_aa); + break; + } +} +void DlOptimizingCanvas::ClipRRect(const SkRRect& rrect, + ClipOp clip_op, + bool is_aa) { + if (rrect.isRect()) { + clipRect(rrect.rect(), clip_op, is_aa); + } else { + tracker_.clipRRect(rrect, clip_op, is_aa); + if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { + current_layer_->is_nop_ = true; + return; + } + checkForDeferredSave(); + switch (clip_op) { + case ClipOp::kIntersect: + Push(0, 1, rrect, is_aa); + break; + case ClipOp::kDifference: + Push(0, 1, rrect, is_aa); + break; + } + } +} +void DlOptimizingCanvas::ClipPath(const SkPath& path, + ClipOp clip_op, + bool is_aa) { + if (!path.isInverseFillType()) { + SkRect rect; + if (path.isRect(&rect)) { + this->clipRect(rect, clip_op, is_aa); + return; + } + SkRRect rrect; + if (path.isOval(&rect)) { + rrect.setOval(rect); + this->clipRRect(rrect, clip_op, is_aa); + return; + } + if (path.isRRect(&rrect)) { + this->clipRRect(rrect, clip_op, is_aa); + return; + } + } + tracker_.clipPath(path, clip_op, is_aa); + if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { + current_layer_->is_nop_ = true; + return; + } + checkForDeferredSave(); + switch (clip_op) { + case ClipOp::kIntersect: + Push(0, 1, path, is_aa); + break; + case ClipOp::kDifference: + Push(0, 1, path, is_aa); + break; + } +} + +bool DlOptimizingCanvas::QuickReject(const SkRect& bounds) const { + return tracker_.content_culled(bounds); +} + +void DlOptimizingCanvas::drawPaint() { + OpResult result = PaintResult(current_, kDrawPaintFlags); + if (result != OpResult::kNoEffect && AccumulateUnbounded()) { + Push(0, 1); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::DrawPaint(const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawPaintFlags); + drawPaint(); +} +void DlOptimizingCanvas::DrawColor(DlColor color, DlBlendMode mode) { + OpResult result = PaintResult(DlPaint(color).setBlendMode(mode)); + if (result != OpResult::kNoEffect && AccumulateUnbounded()) { + Push(0, 1, color, mode); + CheckLayerOpacityCompatibility(mode); + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::drawLine(const SkPoint& p0, const SkPoint& p1) { + SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); + DisplayListAttributeFlags flags = + (bounds.width() > 0.0f && bounds.height() > 0.0f) ? kDrawLineFlags + : kDrawHVLineFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds, flags)) { + Push(0, 1, p0, p1); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::DrawLine(const SkPoint& p0, + const SkPoint& p1, + const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawLineFlags); + drawLine(p0, p1); +} +void DlOptimizingCanvas::drawRect(const SkRect& rect) { + DisplayListAttributeFlags flags = kDrawRectFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && + AccumulateOpBounds(rect.makeSorted(), flags)) { + Push(0, 1, rect); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::DrawRect(const SkRect& rect, const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRectFlags); + drawRect(rect); +} +void DlOptimizingCanvas::drawOval(const SkRect& bounds) { + DisplayListAttributeFlags flags = kDrawOvalFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && + AccumulateOpBounds(bounds.makeSorted(), flags)) { + Push(0, 1, bounds); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::DrawOval(const SkRect& bounds, const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawOvalFlags); + drawOval(bounds); +} +void DlOptimizingCanvas::drawCircle(const SkPoint& center, SkScalar radius) { + DisplayListAttributeFlags flags = kDrawCircleFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect) { + SkRect bounds = SkRect::MakeLTRB(center.fX - radius, center.fY - radius, + center.fX + radius, center.fY + radius); + if (AccumulateOpBounds(bounds, flags)) { + Push(0, 1, center, radius); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } + } +} +void DlOptimizingCanvas::DrawCircle(const SkPoint& center, + SkScalar radius, + const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawCircleFlags); + drawCircle(center, radius); +} +void DlOptimizingCanvas::drawRRect(const SkRRect& rrect) { + if (rrect.isRect()) { + drawRect(rrect.rect()); + } else if (rrect.isOval()) { + drawOval(rrect.rect()); + } else { + DisplayListAttributeFlags flags = kDrawRRectFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && + AccumulateOpBounds(rrect.getBounds(), flags)) { + Push(0, 1, rrect); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } + } +} +void DlOptimizingCanvas::DrawRRect(const SkRRect& rrect, const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRRectFlags); + drawRRect(rrect); +} +void DlOptimizingCanvas::drawDRRect(const SkRRect& outer, + const SkRRect& inner) { + DisplayListAttributeFlags flags = kDrawDRRectFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && + AccumulateOpBounds(outer.getBounds(), flags)) { + Push(0, 1, outer, inner); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::DrawDRRect(const SkRRect& outer, + const SkRRect& inner, + const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawDRRectFlags); + drawDRRect(outer, inner); +} +void DlOptimizingCanvas::drawPath(const SkPath& path) { + DisplayListAttributeFlags flags = kDrawPathFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect) { + bool is_visible = path.isInverseFillType() + ? AccumulateUnbounded() + : AccumulateOpBounds(path.getBounds(), flags); + if (is_visible) { + Push(0, 1, path); + CheckLayerOpacityHairlineCompatibility(); + UpdateLayerResult(result); + } + } +} +void DlOptimizingCanvas::DrawPath(const SkPath& path, const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawPathFlags); + drawPath(path); +} + +void DlOptimizingCanvas::drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter) { + DisplayListAttributeFlags flags = // + useCenter // + ? kDrawArcWithCenterFlags + : kDrawArcNoCenterFlags; + OpResult result = PaintResult(current_, flags); + // This could be tighter if we compute where the start and end + // angles are and then also consider the quadrants swept and + // the center if specified. + if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds, flags)) { + Push(0, 1, bounds, start, sweep, useCenter); + if (useCenter) { + CheckLayerOpacityHairlineCompatibility(); + } else { + CheckLayerOpacityCompatibility(); + } + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::DrawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter, + const DlPaint& paint) { + SetAttributesFromPaint( + paint, useCenter ? kDrawArcWithCenterFlags : kDrawArcNoCenterFlags); + drawArc(bounds, start, sweep, useCenter); +} + +DisplayListAttributeFlags DlOptimizingCanvas::FlagsForPointMode( + PointMode mode) { + switch (mode) { + case DlCanvas::PointMode::kPoints: + return kDrawPointsAsPointsFlags; + case PointMode::kLines: + return kDrawPointsAsLinesFlags; + case PointMode::kPolygon: + return kDrawPointsAsPolygonFlags; + } + FML_UNREACHABLE(); +} +void DlOptimizingCanvas::drawPoints(PointMode mode, + uint32_t count, + const SkPoint pts[]) { + if (count == 0) { + return; + } + DisplayListAttributeFlags flags = FlagsForPointMode(mode); + OpResult result = PaintResult(current_, flags); + if (result == OpResult::kNoEffect) { + return; + } + + FML_DCHECK(count < DlOpReceiver::kMaxDrawPointsCount); + int bytes = count * sizeof(SkPoint); + RectBoundsAccumulator ptBounds; + for (size_t i = 0; i < count; i++) { + ptBounds.accumulate(pts[i]); + } + SkRect point_bounds = ptBounds.bounds(); + if (!AccumulateOpBounds(point_bounds, flags)) { + return; + } + + void* data_ptr; + switch (mode) { + case PointMode::kPoints: + data_ptr = Push(bytes, 1, count); + break; + case PointMode::kLines: + data_ptr = Push(bytes, 1, count); + break; + case PointMode::kPolygon: + data_ptr = Push(bytes, 1, count); + break; + default: + FML_UNREACHABLE(); + return; + } + CopyV(data_ptr, pts, count); + // drawPoints treats every point or line (or segment of a polygon) + // as a completely separate operation meaning we cannot ensure + // distribution of group opacity without analyzing the mode and the + // bounds of every sub-primitive. + // See: https://fiddle.skia.org/c/228459001d2de8db117ce25ef5cedb0c + UpdateLayerOpacityCompatibility(false); + UpdateLayerResult(result); +} +void DlOptimizingCanvas::DrawPoints(PointMode mode, + uint32_t count, + const SkPoint pts[], + const DlPaint& paint) { + SetAttributesFromPaint(paint, FlagsForPointMode(mode)); + drawPoints(mode, count, pts); +} +void DlOptimizingCanvas::drawVertices(const DlVertices* vertices, + DlBlendMode mode) { + DisplayListAttributeFlags flags = kDrawVerticesFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && + AccumulateOpBounds(vertices->bounds(), flags)) { + void* pod = Push(vertices->size(), 1, mode); + new (pod) DlVertices(vertices); + // DrawVertices applies its colors to the paint so we have no way + // of controlling opacity using the current paint attributes. + // Although, examination of the |mode| might find some predictable + // cases. + UpdateLayerOpacityCompatibility(false); + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::DrawVertices(const DlVertices* vertices, + DlBlendMode mode, + const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawVerticesFlags); + drawVertices(vertices, mode); +} + +void DlOptimizingCanvas::drawImage(const sk_sp image, + const SkPoint point, + DlImageSampling sampling, + bool render_with_attributes) { + DisplayListAttributeFlags flags = render_with_attributes // + ? kDrawImageWithPaintFlags + : kDrawImageFlags; + OpResult result = PaintResult(current_, flags); + if (result == OpResult::kNoEffect) { + return; + } + SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, // + image->width(), image->height()); + if (AccumulateOpBounds(bounds, flags)) { + render_with_attributes + ? Push(0, 1, image, point, sampling) + : Push(0, 1, image, point, sampling); + CheckLayerOpacityCompatibility(render_with_attributes); + UpdateLayerResult(result); + is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); + } +} +void DlOptimizingCanvas::DrawImage(const sk_sp& image, + const SkPoint point, + DlImageSampling sampling, + const DlPaint* paint) { + if (paint != nullptr) { + SetAttributesFromPaint(*paint, + DisplayListOpFlags::kDrawImageWithPaintFlags); + drawImage(image, point, sampling, true); + } else { + drawImage(image, point, sampling, false); + } +} +void DlOptimizingCanvas::drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + DlImageSampling sampling, + bool render_with_attributes, + SrcRectConstraint constraint) { + DisplayListAttributeFlags flags = render_with_attributes + ? kDrawImageRectWithPaintFlags + : kDrawImageRectFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && AccumulateOpBounds(dst, flags)) { + Push(0, 1, image, src, dst, sampling, + render_with_attributes, constraint); + CheckLayerOpacityCompatibility(render_with_attributes); + UpdateLayerResult(result); + is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); + } +} +void DlOptimizingCanvas::DrawImageRect(const sk_sp& image, + const SkRect& src, + const SkRect& dst, + DlImageSampling sampling, + const DlPaint* paint, + SrcRectConstraint constraint) { + if (paint != nullptr) { + SetAttributesFromPaint(*paint, + DisplayListOpFlags::kDrawImageRectWithPaintFlags); + drawImageRect(image, src, dst, sampling, true, constraint); + } else { + drawImageRect(image, src, dst, sampling, false, constraint); + } +} +void DlOptimizingCanvas::drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + DlFilterMode filter, + bool render_with_attributes) { + DisplayListAttributeFlags flags = render_with_attributes + ? kDrawImageNineWithPaintFlags + : kDrawImageNineFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && AccumulateOpBounds(dst, flags)) { + render_with_attributes + ? Push(0, 1, image, center, dst, filter) + : Push(0, 1, image, center, dst, filter); + CheckLayerOpacityCompatibility(render_with_attributes); + UpdateLayerResult(result); + is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); + } +} +void DlOptimizingCanvas::DrawImageNine(const sk_sp& image, + const SkIRect& center, + const SkRect& dst, + DlFilterMode filter, + const DlPaint* paint) { + if (paint != nullptr) { + SetAttributesFromPaint(*paint, + DisplayListOpFlags::kDrawImageNineWithPaintFlags); + drawImageNine(image, center, dst, filter, true); + } else { + drawImageNine(image, center, dst, filter, false); + } +} +void DlOptimizingCanvas::drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const DlColor colors[], + int count, + DlBlendMode mode, + DlImageSampling sampling, + const SkRect* cull_rect, + bool render_with_attributes) { + DisplayListAttributeFlags flags = render_with_attributes // + ? kDrawAtlasWithPaintFlags + : kDrawAtlasFlags; + OpResult result = PaintResult(current_, flags); + if (result == OpResult::kNoEffect) { + return; + } + SkPoint quad[4]; + RectBoundsAccumulator atlasBounds; + for (int i = 0; i < count; i++) { + const SkRect& src = tex[i]; + xform[i].toQuad(src.width(), src.height(), quad); + for (int j = 0; j < 4; j++) { + atlasBounds.accumulate(quad[j]); + } + } + if (atlasBounds.is_empty() || + !AccumulateOpBounds(atlasBounds.bounds(), flags)) { + return; + } + + int bytes = count * (sizeof(SkRSXform) + sizeof(SkRect)); + void* data_ptr; + if (colors != nullptr) { + bytes += count * sizeof(DlColor); + if (cull_rect != nullptr) { + data_ptr = + Push(bytes, 1, atlas, count, mode, sampling, true, + *cull_rect, render_with_attributes); + } else { + data_ptr = Push(bytes, 1, atlas, count, mode, sampling, true, + render_with_attributes); + } + CopyV(data_ptr, xform, count, tex, count, colors, count); + } else { + if (cull_rect != nullptr) { + data_ptr = + Push(bytes, 1, atlas, count, mode, sampling, false, + *cull_rect, render_with_attributes); + } else { + data_ptr = Push(bytes, 1, atlas, count, mode, sampling, + false, render_with_attributes); + } + CopyV(data_ptr, xform, count, tex, count); + } + // drawAtlas treats each image as a separate operation so we cannot rely + // on it to distribute the opacity without overlap without checking all + // of the transforms and texture rectangles. + UpdateLayerOpacityCompatibility(false); + UpdateLayerResult(result); + is_ui_thread_safe_ = is_ui_thread_safe_ && atlas->isUIThreadSafe(); +} +void DlOptimizingCanvas::DrawAtlas(const sk_sp& atlas, + const SkRSXform xform[], + const SkRect tex[], + const DlColor colors[], + int count, + DlBlendMode mode, + DlImageSampling sampling, + const SkRect* cull_rect, + const DlPaint* paint) { + if (paint != nullptr) { + SetAttributesFromPaint(*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 DlOptimizingCanvas::DrawDisplayList(const sk_sp display_list, + SkScalar opacity) { + if (!SkScalarIsFinite(opacity) || opacity <= SK_ScalarNearlyZero || + display_list->op_count() == 0 || display_list->bounds().isEmpty() || + current_layer_->is_nop_) { + return; + } + const SkRect bounds = display_list->bounds(); + bool accumulated; + switch (accumulator()->type()) { + case BoundsAccumulatorType::kRect: + accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); + break; + case BoundsAccumulatorType::kRTree: + auto rtree = display_list->rtree(); + if (rtree) { + std::list rects = + rtree->searchAndConsolidateRects(bounds, false); + accumulated = false; + for (const SkRect& rect : rects) { + // TODO (https://github.com/flutter/flutter/issues/114919): Attributes + // are not necessarily `kDrawDisplayListFlags`. + if (AccumulateOpBounds(rect, kDrawDisplayListFlags)) { + accumulated = true; + } + } + } else { + accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); + } + break; + } + if (!accumulated) { + return; + } + + DlPaint current_paint = current_; + Push(0, 1, display_list, + opacity < SK_Scalar1 ? opacity : SK_Scalar1); + is_ui_thread_safe_ = is_ui_thread_safe_ && display_list->isUIThreadSafe(); + // Not really necessary if the developer is interacting with us via + // our attribute-state-less DlCanvas methods, but this avoids surprises + // for those who may have been using the stateful Dispatcher methods. + SetAttributesFromPaint(current_paint, + DisplayListOpFlags::kSaveLayerWithPaintFlags); + + // The non-nested op count accumulated in the |Push| method will include + // this call to |drawDisplayList| for non-nested op count metrics. + // But, for nested op count metrics we want the |drawDisplayList| call itself + // to be transparent. So we subtract 1 from our accumulated nested count to + // balance out against the 1 that was accumulated into the regular count. + // This behavior is identical to the way SkPicture computed nested op counts. + nested_op_count_ += display_list->op_count(true) - 1; + nested_bytes_ += display_list->bytes(true); + UpdateLayerOpacityCompatibility(display_list->can_apply_group_opacity()); + // Nop DisplayLists are eliminated above so we either affect transparent + // pixels or we do not. We should not have [kNoEffect]. + UpdateLayerResult(display_list->modifies_transparent_black() + ? OpResult::kAffectsAll + : OpResult::kPreservesTransparency); +} +void DlOptimizingCanvas::drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) { + DisplayListAttributeFlags flags = kDrawTextBlobFlags; + OpResult result = PaintResult(current_, flags); + if (result == OpResult::kNoEffect) { + return; + } + bool unclipped = AccumulateOpBounds(blob->bounds().makeOffset(x, y), flags); + // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the + // unit tests can use Fuchsia's font manager instead of the empty default. + // Until then we might encounter empty bounds for otherwise valid text and + // thus we ignore the results from AccumulateOpBounds. +#if defined(OS_FUCHSIA) + unclipped = true; +#endif // OS_FUCHSIA + if (unclipped) { + Push(0, 1, blob, x, y); + // There is no way to query if the glyphs of a text blob overlap and + // there are no current guarantees from either Skia or Impeller that + // they will protect overlapping glyphs from the effects of overdraw + // so we must make the conservative assessment that this DL layer is + // not compatible with group opacity inheritance. + UpdateLayerOpacityCompatibility(false); + UpdateLayerResult(result); + } +} +void DlOptimizingCanvas::DrawTextBlob(const sk_sp& blob, + SkScalar x, + SkScalar y, + const DlPaint& paint) { + SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawTextBlobFlags); + drawTextBlob(blob, x, y); +} +void DlOptimizingCanvas::DrawShadow(const SkPath& path, + const DlColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) { + OpResult result = PaintResult(DlPaint(color)); + if (result != OpResult::kNoEffect) { + SkRect shadow_bounds = + DlCanvas::ComputeShadowBounds(path, elevation, dpr, GetTransform()); + if (AccumulateOpBounds(shadow_bounds, kDrawShadowFlags)) { + transparent_occluder // + ? Push(0, 1, path, color, elevation, + dpr) + : Push(0, 1, path, color, elevation, dpr); + UpdateLayerOpacityCompatibility(false); + UpdateLayerResult(result); + } + } +} + +bool DlOptimizingCanvas::ComputeFilteredBounds(SkRect& bounds, + const DlImageFilter* filter) { + if (filter) { + if (!filter->map_local_bounds(bounds, bounds)) { + return false; + } + } + return true; +} + +bool DlOptimizingCanvas::AdjustBoundsForPaint(SkRect& bounds, + DisplayListAttributeFlags flags) { + if (flags.ignores_paint()) { + return true; + } + + if (flags.is_geometric()) { + bool is_stroked = flags.is_stroked(current_.getDrawStyle()); + + // Path effect occurs before stroking... + DisplayListSpecialGeometryFlags special_flags = + flags.WithPathEffect(current_.getPathEffectPtr(), is_stroked); + if (current_.getPathEffect()) { + auto effect_bounds = current_.getPathEffect()->effect_bounds(bounds); + if (!effect_bounds.has_value()) { + return false; + } + bounds = effect_bounds.value(); + } + + if (is_stroked) { + // Determine the max multiplier to the stroke width first. + SkScalar pad = 1.0f; + if (current_.getStrokeJoin() == DlStrokeJoin::kMiter && + special_flags.may_have_acute_joins()) { + pad = std::max(pad, current_.getStrokeMiter()); + } + if (current_.getStrokeCap() == DlStrokeCap::kSquare && + special_flags.may_have_diagonal_caps()) { + pad = std::max(pad, SK_ScalarSqrt2); + } + SkScalar min_stroke_width = 0.01; + pad *= std::max(current_.getStrokeWidth() * 0.5f, min_stroke_width); + bounds.outset(pad, pad); + } + } + + if (flags.applies_mask_filter()) { + auto filter = current_.getMaskFilter(); + if (filter) { + switch (filter->type()) { + case DlMaskFilterType::kBlur: { + FML_DCHECK(filter->asBlur()); + SkScalar mask_sigma_pad = filter->asBlur()->sigma() * 3.0; + bounds.outset(mask_sigma_pad, mask_sigma_pad); + } + } + } + } + + if (flags.applies_image_filter()) { + return ComputeFilteredBounds(bounds, current_.getImageFilter().get()); + } + + return true; +} + +bool DlOptimizingCanvas::AccumulateUnbounded() { + SkRect clip = tracker_.device_cull_rect(); + if (clip.isEmpty()) { + return false; + } + accumulator()->accumulate(clip, op_index_); + return true; +} + +bool DlOptimizingCanvas::AccumulateOpBounds(SkRect& bounds, + DisplayListAttributeFlags flags) { + if (AdjustBoundsForPaint(bounds, flags)) { + return AccumulateBounds(bounds); + } else { + return AccumulateUnbounded(); + } +} +bool DlOptimizingCanvas::AccumulateBounds(SkRect& bounds) { + if (!bounds.isEmpty()) { + tracker_.mapRect(&bounds); + if (bounds.intersect(tracker_.device_cull_rect())) { + accumulator()->accumulate(bounds, op_index_); + return true; + } + } + return false; +} + +bool DlOptimizingCanvas::paint_nops_on_transparency() { + // SkImageFilter::canComputeFastBounds tests for transparency behavior + // This test assumes that the blend mode checked down below will + // NOP on transparent black. + if (current_.getImageFilterPtr() && + current_.getImageFilterPtr()->modifies_transparent_black()) { + return false; + } + + // We filter the transparent black that is used for the background of a + // saveLayer and make sure it returns transparent black. If it does, then + // the color filter will leave all area surrounding the contents of the + // save layer untouched out to the edge of the output surface. + // This test assumes that the blend mode checked down below will + // NOP on transparent black. + if (current_.getColorFilterPtr() && + current_.getColorFilterPtr()->modifies_transparent_black()) { + return false; + } + + // Unusual blendmodes require us to process a saved layer + // even with operations outside the clip. + // For example, DstIn is used by masking layers. + // https://code.google.com/p/skia/issues/detail?id=1291 + // https://crbug.com/401593 + switch (current_.getBlendMode()) { + // For each of the following transfer modes, if the source + // alpha is zero (our transparent black), the resulting + // blended pixel is not necessarily equal to the original + // destination pixel. + // Mathematically, any time in the following equations where + // the result is not d assuming source is 0 + case DlBlendMode::kClear: // r = 0 + case DlBlendMode::kSrc: // r = s + case DlBlendMode::kSrcIn: // r = s * da + case DlBlendMode::kDstIn: // r = d * sa + case DlBlendMode::kSrcOut: // r = s * (1-da) + case DlBlendMode::kDstATop: // r = d*sa + s*(1-da) + case DlBlendMode::kModulate: // r = s*d + return false; + break; + + // And in these equations, the result must be d if the + // source is 0 + case DlBlendMode::kDst: // r = d + case DlBlendMode::kSrcOver: // r = s + (1-sa)*d + case DlBlendMode::kDstOver: // r = d + (1-da)*s + case DlBlendMode::kDstOut: // r = d * (1-sa) + case DlBlendMode::kSrcATop: // r = s*da + d*(1-sa) + case DlBlendMode::kXor: // r = s*(1-da) + d*(1-sa) + case DlBlendMode::kPlus: // r = min(s + d, 1) + case DlBlendMode::kScreen: // r = s + d - s*d + case DlBlendMode::kOverlay: // multiply or screen, depending on dest + case DlBlendMode::kDarken: // rc = s + d - max(s*da, d*sa), + // ra = kSrcOver + case DlBlendMode::kLighten: // rc = s + d - min(s*da, d*sa), + // ra = kSrcOver + case DlBlendMode::kColorDodge: // brighten destination to reflect source + case DlBlendMode::kColorBurn: // darken destination to reflect source + case DlBlendMode::kHardLight: // multiply or screen, depending on source + case DlBlendMode::kSoftLight: // lighten or darken, depending on source + case DlBlendMode::kDifference: // rc = s + d - 2*(min(s*da, d*sa)), + // ra = kSrcOver + case DlBlendMode::kExclusion: // rc = s + d - two(s*d), ra = kSrcOver + case DlBlendMode::kMultiply: // r = s*(1-da) + d*(1-sa) + s*d + case DlBlendMode::kHue: // ra = kSrcOver + case DlBlendMode::kSaturation: // ra = kSrcOver + case DlBlendMode::kColor: // ra = kSrcOver + case DlBlendMode::kLuminosity: // ra = kSrcOver + return true; + break; + } +} + +DlColor DlOptimizingCanvas::GetEffectiveColor(const DlPaint& paint, + DisplayListAttributeFlags flags) { + DlColor color; + if (flags.applies_color()) { + const DlColorSource* source = paint.getColorSourcePtr(); + if (source) { + if (source->asColor()) { + color = source->asColor()->color(); + } else { + color = source->is_opaque() ? DlColor::kBlack() : kAnyColor; + } + } else { + color = paint.getColor(); + } + } else if (flags.applies_alpha()) { + // If the operation applies alpha, but not color, then the only impact + // of the alpha is to modulate the output towards transparency. + // We can not guarantee an opaque source even if the alpha is opaque + // since that would require knowing something about the colors that + // the alpha is modulating, but we can guarantee a transparent source + // if the alpha is 0. + color = (paint.getAlpha() == 0) ? DlColor::kTransparent() : kAnyColor; + } else { + color = kAnyColor; + } + if (flags.applies_image_filter()) { + auto filter = paint.getImageFilterPtr(); + if (filter) { + if (!color.isTransparent() || filter->modifies_transparent_black()) { + color = kAnyColor; + } + } + } + if (flags.applies_color_filter()) { + auto filter = paint.getColorFilterPtr(); + if (filter) { + if (!color.isTransparent() || filter->modifies_transparent_black()) { + color = kAnyColor; + } + } + } + return color; +} + +DlOptimizingCanvas::OpResult DlOptimizingCanvas::PaintResult( + const DlPaint& paint, + DisplayListAttributeFlags flags) { + if (current_layer_->is_nop_) { + return OpResult::kNoEffect; + } + if (flags.applies_blend()) { + switch (paint.getBlendMode()) { + // Nop blend mode (singular, there is only one) + case DlBlendMode::kDst: + return OpResult::kNoEffect; + + // Always clears pixels blend mode (singular, there is only one) + case DlBlendMode::kClear: + return OpResult::kPreservesTransparency; + + case DlBlendMode::kHue: + case DlBlendMode::kSaturation: + case DlBlendMode::kColor: + case DlBlendMode::kLuminosity: + case DlBlendMode::kColorBurn: + return GetEffectiveColor(paint, flags).isTransparent() + ? OpResult::kNoEffect + : OpResult::kAffectsAll; + + // kSrcIn modifies pixels towards transparency + case DlBlendMode::kSrcIn: + return OpResult::kPreservesTransparency; + + // These blend modes preserve destination alpha + case DlBlendMode::kSrcATop: + case DlBlendMode::kDstOut: + return GetEffectiveColor(paint, flags).isTransparent() + ? OpResult::kNoEffect + : OpResult::kPreservesTransparency; + + // Always destructive blend modes, potentially not affecting transparency + case DlBlendMode::kSrc: + case DlBlendMode::kSrcOut: + case DlBlendMode::kDstATop: + return GetEffectiveColor(paint, flags).isTransparent() + ? OpResult::kPreservesTransparency + : OpResult::kAffectsAll; + + // The kDstIn blend mode modifies the destination unless the + // source color is opaque. + case DlBlendMode::kDstIn: + return GetEffectiveColor(paint, flags).isOpaque() + ? OpResult::kNoEffect + : OpResult::kPreservesTransparency; + + // The next group of blend modes modifies the destination unless the + // source color is transparent. + case DlBlendMode::kSrcOver: + case DlBlendMode::kDstOver: + case DlBlendMode::kXor: + case DlBlendMode::kPlus: + case DlBlendMode::kScreen: + case DlBlendMode::kMultiply: + case DlBlendMode::kOverlay: + case DlBlendMode::kDarken: + case DlBlendMode::kLighten: + case DlBlendMode::kColorDodge: + case DlBlendMode::kHardLight: + case DlBlendMode::kSoftLight: + case DlBlendMode::kDifference: + case DlBlendMode::kExclusion: + return GetEffectiveColor(paint, flags).isTransparent() + ? OpResult::kNoEffect + : OpResult::kAffectsAll; + + // Modulate only leaves the pixel alone when the source is white. + case DlBlendMode::kModulate: + return GetEffectiveColor(paint, flags) == DlColor::kWhite() + ? OpResult::kNoEffect + : OpResult::kPreservesTransparency; + } + } + return OpResult::kAffectsAll; +} + +} // namespace flutter diff --git a/display_list/dl_optimizing_canvas.h b/display_list/dl_optimizing_canvas.h new file mode 100644 index 0000000000000..04fbdfa05f2af --- /dev/null +++ b/display_list/dl_optimizing_canvas.h @@ -0,0 +1,295 @@ +// 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. + +#pragma once + +#include "flutter/display_list/dl_canvas.h" + +namespace flutter { + +class DlOptimizingCanvas : public virtual DlCanvas { + public: + DlOptimizingCanvas(const SkRect& cull_rect, bool prepare_rtree = false); + virtual ~DlOptimizingCanvas() = default; + + virtual SkISize GetBaseLayerSize() const override; + + virtual void Save() override; + virtual void SaveLayer(const SkRect* bounds, + const DlPaint* paint = nullptr, + const DlImageFilter* backdrop = nullptr) override; + virtual void Restore() override; + virtual void RestoreToCount(int restore_count) override; + + virtual void Translate(SkScalar tx, SkScalar ty) override; + virtual void Scale(SkScalar sx, SkScalar sy) override; + virtual void Rotate(SkScalar degrees) override; + virtual void Skew(SkScalar sx, SkScalar sy) override; + + // clang-format off + + // 2x3 2D affine subset of a 4x4 transform in row major order + virtual void Transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt, + SkScalar myx, SkScalar myy, SkScalar myt) override; + // full 4x4 transform in row major order + virtual void TransformFullPerspective( + SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, + SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, + SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, + SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override; + // clang-format on + virtual void TransformReset() override; + virtual void Transform(const SkMatrix* matrix) override; + virtual void Transform(const SkM44* matrix44) override; + virtual void Transform(const SkMatrix& matrix) { Transform(&matrix); } + virtual void Transform(const SkM44& matrix44) { Transform(&matrix44); } + virtual void SetTransform(const SkMatrix* matrix) override; + virtual void SetTransform(const SkM44* matrix44) override; + virtual void SetTransform(const SkMatrix& matrix) { SetTransform(&matrix); } + virtual void SetTransform(const SkM44& matrix44) { SetTransform(&matrix44); } + + /// Returns the 4x4 full perspective transform representing all transform + /// operations executed so far in this DisplayList within the enclosing + /// save stack. + virtual SkM44 GetTransformFullPerspective() const override; + /// Returns the 3x3 partial perspective transform representing all transform + /// operations executed so far in this DisplayList within the enclosing + /// save stack. + virtual SkMatrix GetTransform() const override; + + virtual void ClipRect(const SkRect& rect, + ClipOp clip_op = ClipOp::kIntersect, + bool is_aa = false) override; + virtual void ClipRRect(const SkRRect& rrect, + ClipOp clip_op = ClipOp::kIntersect, + bool is_aa = false) override; + virtual void ClipPath(const SkPath& path, + ClipOp clip_op = ClipOp::kIntersect, + bool is_aa = false) override; + + /// Conservative estimate of the bounds of all outstanding clip operations + /// measured in the coordinate space within which this DisplayList will + /// be rendered. + virtual SkRect GetDestinationClipBounds() const override; + /// Conservative estimate of the bounds of all outstanding clip operations + /// transformed into the local coordinate space in which currently + /// recorded rendering operations are interpreted. + virtual SkRect GetLocalClipBounds() const override; + + /// Return true iff the supplied bounds are easily shown to be outside + /// of the current clip bounds. This method may conservatively return + /// false if it cannot make the determination. + virtual bool QuickReject(const SkRect& bounds) const override; + + virtual void DrawPaint(const DlPaint& paint) override; + virtual void DrawColor(DlColor color, + DlBlendMode mode = DlBlendMode::kSrcOver) override; + + virtual void DrawLine(const SkPoint& p0, + const SkPoint& p1, + const DlPaint& paint) override; + virtual void DrawRect(const SkRect& rect, const DlPaint& paint) override; + virtual void DrawOval(const SkRect& bounds, const DlPaint& paint) override; + virtual void DrawCircle(const SkPoint& center, + SkScalar radius, + const DlPaint& paint); + virtual void DrawRRect(const SkRRect& rrect, const DlPaint& paint) override; + virtual void DrawDRRect(const SkRRect& outer, + const SkRRect& inner, + const DlPaint& paint) override; + virtual void DrawPath(const SkPath& path, const DlPaint& paint) override; + virtual void DrawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter, + const DlPaint& paint) override; + virtual void DrawPoints(PointMode mode, + uint32_t count, + const SkPoint pts[], + const DlPaint& paint) override; + virtual void DrawVertices(const DlVertices* vertices, + DlBlendMode mode, + const DlPaint& paint) override; + virtual void DrawImage(const sk_sp& image, + const SkPoint point, + DlImageSampling sampling, + const DlPaint* paint = nullptr) override; + virtual void DrawImageRect( + const sk_sp& image, + const SkRect& src, + const SkRect& dst, + DlImageSampling sampling, + const DlPaint* paint = nullptr, + SrcRectConstraint constraint = SrcRectConstraint::kFast) override; + virtual void DrawImageNine(const sk_sp& image, + const SkIRect& center, + const SkRect& dst, + DlFilterMode filter, + const DlPaint* paint = nullptr) override; + virtual void DrawAtlas(const sk_sp& atlas, + const SkRSXform xform[], + const SkRect tex[], + const DlColor colors[], + int count, + DlBlendMode mode, + DlImageSampling sampling, + const SkRect* cullRect, + const DlPaint* paint = nullptr) override; + virtual void DrawDisplayList(const sk_sp display_list, + SkScalar opacity = SK_Scalar1) override; + virtual void DrawImpellerPicture( + const std::shared_ptr& picture, + SkScalar opacity = SK_Scalar1) override; + virtual void DrawTextBlob(const sk_sp& blob, + SkScalar x, + SkScalar y, + const DlPaint& paint) override; + virtual void DrawShadow(const SkPath& path, + const DlColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) override; + + private: + std::vector layer_stack_; + LayerInfo* current_layer_; + DisplayListMatrixClipTracker tracker_; + std::unique_ptr accumulator_; + BoundsAccumulator* accumulator() { return accumulator_.get(); } + + // This flag indicates whether or not the current rendering attributes + // are compatible with rendering ops applying an inherited opacity. + bool current_opacity_compatibility_ = true; + + // Returns the compatibility of a given blend mode for applying an + // inherited opacity value to modulate the visibility of the op. + // For now we only accept SrcOver blend modes but this could be expanded + // in the future to include other (rarely used) modes that also modulate + // the opacity of a rendering operation at the cost of a switch statement + // or lookup table. + static bool IsOpacityCompatible(DlBlendMode mode) { + return (mode == DlBlendMode::kSrcOver); + } + + void UpdateCurrentOpacityCompatibility() { + current_opacity_compatibility_ = // + current_.getColorFilter() == nullptr && // + !current_.isInvertColors() && // + IsOpacityCompatible(current_.getBlendMode()); + } + + // Update the opacity compatibility flags of the current layer for an op + // that has determined its compatibility as indicated by |compatible|. + void UpdateLayerOpacityCompatibility(bool compatible) { + if (compatible) { + current_layer_->add_compatible_op(); + } else { + current_layer_->mark_incompatible(); + } + } + + // Check for opacity compatibility for an op that may or may not use the + // current rendering attributes as indicated by |uses_blend_attribute|. + // If the flag is false then the rendering op will be able to substitute + // a default Paint object with the opacity applied using the default SrcOver + // blend mode which is always compatible with applying an inherited opacity. + void CheckLayerOpacityCompatibility(bool uses_blend_attribute = true) { + UpdateLayerOpacityCompatibility(!uses_blend_attribute || + current_opacity_compatibility_); + } + + void CheckLayerOpacityHairlineCompatibility() { + UpdateLayerOpacityCompatibility( + current_opacity_compatibility_ && + (current_.getDrawStyle() == DlDrawStyle::kFill || + current_.getStrokeWidth() > 0)); + } + + // Check for opacity compatibility for an op that ignores the current + // attributes and uses the indicated blend |mode| to render to the layer. + // This is only used by |drawColor| currently. + void CheckLayerOpacityCompatibility(DlBlendMode mode) { + UpdateLayerOpacityCompatibility(IsOpacityCompatible(mode)); + } + + // The DisplayList had an unbounded call with no cull rect or clip + // to contain it. Should only be called after the stream is fully + // built. + // Unbounded operations are calls like |drawColor| which are defined + // to flood the entire surface, or calls that relied on a rendering + // attribute which is unable to compute bounds (should be rare). + // In those cases the bounds will represent only the accumulation + // of the bounded calls and this flag will be set to indicate that + // condition. + bool is_unbounded() const { + FML_DCHECK(layer_stack_.size() == 1); + return layer_stack_.front().is_unbounded(); + } + + SkRect bounds() const { + FML_DCHECK(layer_stack_.size() == 1); + if (is_unbounded()) { + FML_LOG(INFO) << "returning partial bounds for unbounded DisplayList"; + } + + return accumulator_->bounds(); + } + + sk_sp rtree() { + FML_DCHECK(layer_stack_.size() == 1); + if (is_unbounded()) { + FML_LOG(INFO) << "returning partial rtree for unbounded DisplayList"; + } + + return accumulator_->rtree(); + } + + enum class OpResult { + kNoEffect, + kPreservesTransparency, + kAffectsAll, + }; + + bool paint_nops_on_transparency(); + OpResult PaintResult(const DlPaint& paint, + DisplayListAttributeFlags flags = kDrawPaintFlags); + + void UpdateLayerResult(OpResult result) { + switch (result) { + case OpResult::kNoEffect: + case OpResult::kPreservesTransparency: + break; + case OpResult::kAffectsAll: + current_layer_->add_visible_op(); + break; + } + } + + // Adjusts the indicated bounds for the given flags and returns true if + // the calculation was possible, or false if it could not be estimated. + bool AdjustBoundsForPaint(SkRect& bounds, DisplayListAttributeFlags flags); + + // Records the fact that we encountered an op that either could not + // estimate its bounds or that fills all of the destination space. + bool AccumulateUnbounded(); + + // Records the bounds for an op after modifying them according to the + // supplied attribute flags and transforming by the current matrix. + bool AccumulateOpBounds(const SkRect& bounds, + DisplayListAttributeFlags flags) { + SkRect safe_bounds = bounds; + return AccumulateOpBounds(safe_bounds, flags); + } + + // Records the bounds for an op after modifying them according to the + // supplied attribute flags and transforming by the current matrix + // and clipping against the current clip. + bool AccumulateOpBounds(SkRect& bounds, DisplayListAttributeFlags flags); + + // Records the given bounds after transforming by the current matrix + // and clipping against the current clip. + bool AccumulateBounds(SkRect& bounds); +}; + +} // namespace flutter From 827eda900f8e946cb3a4cff32f6f9eac4e93d219 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 25 Jul 2023 09:50:03 -0700 Subject: [PATCH 6/6] stray files --- display_list/dl_optimizing_canvas.cc | 1339 -------------------------- display_list/dl_optimizing_canvas.h | 295 ------ 2 files changed, 1634 deletions(-) delete mode 100644 display_list/dl_optimizing_canvas.cc delete mode 100644 display_list/dl_optimizing_canvas.h diff --git a/display_list/dl_optimizing_canvas.cc b/display_list/dl_optimizing_canvas.cc deleted file mode 100644 index 480adc3efc74b..0000000000000 --- a/display_list/dl_optimizing_canvas.cc +++ /dev/null @@ -1,1339 +0,0 @@ -// 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/dl_builder.h" - -#include "flutter/display_list/display_list.h" -#include "flutter/display_list/dl_blend_mode.h" -#include "flutter/display_list/dl_op_flags.h" -#include "flutter/display_list/dl_op_records.h" -#include "flutter/display_list/effects/dl_color_source.h" -#include "flutter/display_list/utils/dl_bounds_accumulator.h" -#include "fml/logging.h" -#include "third_party/skia/include/core/SkScalar.h" - -namespace flutter { - -static constexpr inline bool is_power_of_two(int value) { - return (value & (value - 1)) == 0; -} - -DlOptimizingCanvas::DlOptimizingCanvas(const SkRect& cull_rect, - bool prepare_rtree) - : tracker_(cull_rect, SkMatrix::I()) { - if (prepare_rtree) { - accumulator_ = std::make_unique(); - } else { - accumulator_ = std::make_unique(); - } - - layer_stack_.emplace_back(); - current_layer_ = &layer_stack_.back(); -} - -SkISize DlOptimizingCanvas::GetBaseLayerSize() const { - return tracker_.base_device_cull_rect().roundOut().size(); -} - -SkImageInfo DlOptimizingCanvas::GetImageInfo() const { - SkISize size = GetBaseLayerSize(); - return SkImageInfo::MakeUnknown(size.width(), size.height()); -} - -void DlOptimizingCanvas::onSetAntiAlias(bool aa) { - current_.setAntiAlias(aa); -} -void DlOptimizingCanvas::onSetDither(bool dither) { - current_.setDither(dither); -} -void DlOptimizingCanvas::onSetInvertColors(bool invert) { - current_.setInvertColors(invert); - UpdateCurrentOpacityCompatibility(); -} -void DlOptimizingCanvas::onSetStrokeCap(DlStrokeCap cap) { - current_.setStrokeCap(cap); -} -void DlOptimizingCanvas::onSetStrokeJoin(DlStrokeJoin join) { - current_.setStrokeJoin(join); -} -void DlOptimizingCanvas::onSetDrawStyle(DlDrawStyle style) { - current_.setDrawStyle(style); -} -void DlOptimizingCanvas::onSetStrokeWidth(float width) { - current_.setStrokeWidth(width); -} -void DlOptimizingCanvas::onSetStrokeMiter(float limit) { - current_.setStrokeMiter(limit); -} -void DlOptimizingCanvas::onSetColor(DlColor color) { - current_.setColor(color); -} -void DlOptimizingCanvas::onSetBlendMode(DlBlendMode mode) { - current_.setBlendMode(mode); - - UpdateCurrentOpacityCompatibility(); -} - -void DlOptimizingCanvas::onSetColorSource(const DlColorSource* source) { - if (source == nullptr) { - current_.setColorSource(nullptr); - - } else { - current_.setColorSource(source->shared()); - is_ui_thread_safe_ = is_ui_thread_safe_ && source->isUIThreadSafe(); - switch (source->type()) { - case DlColorSourceType::kColor: { - const DlColorColorSource* color_source = source->asColor(); - current_.setColorSource(nullptr); - setColor(color_source->color()); - break; - } - case DlColorSourceType::kImage: - case DlColorSourceType::kLinearGradient: - case DlColorSourceType::kRadialGradient: - case DlColorSourceType::kConicalGradient: - case DlColorSourceType::kSweepGradient: - case DlColorSourceType::kRuntimeEffect: -#ifdef IMPELLER_ENABLE_3D - case DlColorSourceType::kScene: -#endif // IMPELLER_ENABLE_3D - break; - } - } -} -void DlOptimizingCanvas::onSetImageFilter(const DlImageFilter* filter) { - if (filter == nullptr) { - current_.setImageFilter(nullptr); - } else { - current_.setImageFilter(filter->shared()); - } -} -void DlOptimizingCanvas::onSetColorFilter(const DlColorFilter* filter) { - if (filter == nullptr) { - current_.setColorFilter(nullptr); - } else { - current_.setColorFilter(filter->shared()); - } - UpdateCurrentOpacityCompatibility(); -} -void DlOptimizingCanvas::onSetPathEffect(const DlPathEffect* effect) { - if (effect == nullptr) { - current_.setPathEffect(nullptr); - } else { - current_.setPathEffect(effect->shared()); - } -} -void DlOptimizingCanvas::onSetMaskFilter(const DlMaskFilter* filter) { - if (filter == nullptr) { - current_.setMaskFilter(nullptr); - - } else { - current_.setMaskFilter(filter->shared()); - } -} - -void DlOptimizingCanvas::checkForDeferredSave() { - if (current_layer_->has_deferred_save_op_) { - size_t save_offset_ = used_; - Push(0, 1); - current_layer_->save_offset_ = save_offset_; - current_layer_->has_deferred_save_op_ = false; - } -} - -void DlOptimizingCanvas::Save() { - bool is_nop = current_layer_->is_nop_; - layer_stack_.emplace_back(); - current_layer_ = &layer_stack_.back(); - current_layer_->has_deferred_save_op_ = true; - current_layer_->is_nop_ = is_nop; - tracker_.save(); - accumulator()->save(); -} - -void DlOptimizingCanvas::Restore() { - if (layer_stack_.size() > 1) { - // Grab the current layer info before we push the restore - // on the stack. - LayerInfo layer_info = layer_stack_.back(); - - tracker_.restore(); - layer_stack_.pop_back(); - current_layer_ = &layer_stack_.back(); - bool is_unbounded = layer_info.is_unbounded(); - - // Before we pop_back we will get the current layer bounds from the - // current accumulator and adjust it as required based on the filter. - std::shared_ptr filter = layer_info.filter(); - if (filter) { - const SkRect clip = tracker_.device_cull_rect(); - if (!accumulator()->restore( - [filter = filter, matrix = GetTransform()](const SkRect& input, - SkRect& output) { - SkIRect output_bounds; - bool ret = filter->map_device_bounds(input.roundOut(), matrix, - output_bounds); - output.set(output_bounds); - return ret; - }, - &clip)) { - is_unbounded = true; - } - } else { - accumulator()->restore(); - } - - if (is_unbounded) { - AccumulateUnbounded(); - } - - if (!layer_info.has_layer()) { - // For regular save() ops there was no protecting layer so we have to - // accumulate the values into the enclosing layer. - if (layer_info.cannot_inherit_opacity()) { - current_layer_->mark_incompatible(); - } else if (layer_info.has_compatible_op()) { - current_layer_->add_compatible_op(); - } - } - } -} - -void DlOptimizingCanvas::saveLayer(const SkRect* bounds, - const SaveLayerOptions in_options, - const DlImageFilter* backdrop) { - SaveLayerOptions options = in_options.without_optimizations(); - DisplayListAttributeFlags flags = options.renders_with_attributes() - ? kSaveLayerWithPaintFlags - : kSaveLayerFlags; - OpResult result = PaintResult(current_, flags); - if (result == OpResult::kNoEffect) { - save(); - current_layer_->is_nop_ = true; - return; - } - size_t save_layer_offset = used_; - if (options.renders_with_attributes()) { - // The actual flood of the outer layer clip will occur after the - // (eventual) corresponding restore is called, but rather than - // remember this information in the LayerInfo until the restore - // method is processed, we just mark the unbounded state up front. - // Another reason to accumulate the clip here rather than in - // restore is so that this savelayer will be tagged in the rtree - // with its full bounds and the right op_index so that it doesn't - // get culled during rendering. - if (!paint_nops_on_transparency()) { - // We will fill the clip of the outer layer when we restore. - // Accumulate should always return true here because if the - // clip was empty then that would have been caught up above - // when we tested the PaintResult. - [[maybe_unused]] bool unclipped = AccumulateUnbounded(); - FML_DCHECK(unclipped); - } - CheckLayerOpacityCompatibility(true); - layer_stack_.emplace_back(save_layer_offset, true, - current_.getImageFilter()); - } else { - CheckLayerOpacityCompatibility(false); - layer_stack_.emplace_back(save_layer_offset, true, nullptr); - } - current_layer_ = &layer_stack_.back(); - - tracker_.save(); - accumulator()->save(); - - if (backdrop) { - // A backdrop will affect up to the entire surface, bounded by the clip - // Accumulate should always return true here because if the - // clip was empty then that would have been caught up above - // when we tested the PaintResult. - [[maybe_unused]] bool unclipped = AccumulateUnbounded(); - FML_DCHECK(unclipped); - bounds // - ? Push(0, 1, options, *bounds, backdrop) - : Push(0, 1, options, backdrop); - } else { - bounds // - ? Push(0, 1, options, *bounds) - : Push(0, 1, options); - } - - if (options.renders_with_attributes()) { - // |current_opacity_compatibility_| does not take an ImageFilter into - // account because an individual primitive with an ImageFilter can apply - // opacity on top of it. But, if the layer is applying the ImageFilter - // then it cannot pass the opacity on. - if (!current_opacity_compatibility_ || - current_.getImageFilter() != nullptr) { - UpdateLayerOpacityCompatibility(false); - } - } - UpdateLayerResult(result); - - if (options.renders_with_attributes() && current_.getImageFilter()) { - // We use |resetCullRect| here because we will be accumulating bounds of - // primitives before applying the filter to those bounds. We might - // encounter a primitive whose bounds are clipped, but whose filtered - // bounds will not be clipped. If the individual rendering ops bounds - // are clipped, it will not contribute to the overall bounds which - // could lead to inaccurate (subset) bounds of the DisplayList. - // We need to reset the cull rect here to avoid this premature clipping. - // The filtered bounds will be clipped to the existing clip rect when - // this layer is restored. - // If bounds is null then the original cull_rect will be used. - tracker_.resetCullRect(bounds); - } else if (bounds) { - // Even though Skia claims that the bounds are only a hint, they actually - // use them as the temporary layer bounds during rendering the layer, so - // we set them as if a clip operation were performed. - tracker_.clipRect(*bounds, ClipOp::kIntersect, false); - } -} -void DlOptimizingCanvas::SaveLayer(const SkRect* bounds, - const DlPaint* paint, - const DlImageFilter* backdrop) { - if (paint != nullptr) { - SetAttributesFromPaint(*paint, - DisplayListOpFlags::kSaveLayerWithPaintFlags); - saveLayer(bounds, SaveLayerOptions::kWithAttributes, backdrop); - } else { - saveLayer(bounds, SaveLayerOptions::kNoAttributes, backdrop); - } -} - -void DlOptimizingCanvas::Translate(SkScalar tx, SkScalar ty) { - if (SkScalarIsFinite(tx) && SkScalarIsFinite(ty) && - (tx != 0.0 || ty != 0.0)) { - checkForDeferredSave(); - Push(0, 1, tx, ty); - tracker_.translate(tx, ty); - } -} -void DlOptimizingCanvas::Scale(SkScalar sx, SkScalar sy) { - if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && - (sx != 1.0 || sy != 1.0)) { - checkForDeferredSave(); - Push(0, 1, sx, sy); - tracker_.scale(sx, sy); - } -} -void DlOptimizingCanvas::Rotate(SkScalar degrees) { - if (SkScalarMod(degrees, 360.0) != 0.0) { - checkForDeferredSave(); - Push(0, 1, degrees); - tracker_.rotate(degrees); - } -} -void DlOptimizingCanvas::Skew(SkScalar sx, SkScalar sy) { - if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && - (sx != 0.0 || sy != 0.0)) { - checkForDeferredSave(); - Push(0, 1, sx, sy); - tracker_.skew(sx, sy); - } -} - -// clang-format off - -// 2x3 2D affine subset of a 4x4 transform in row major order -void DlOptimizingCanvas::Transform2DAffine( - SkScalar mxx, SkScalar mxy, SkScalar mxt, - SkScalar myx, SkScalar myy, SkScalar myt) { - if (SkScalarsAreFinite(mxx, myx) && - SkScalarsAreFinite(mxy, myy) && - SkScalarsAreFinite(mxt, myt)) { - if (mxx == 1 && mxy == 0 && - myx == 0 && myy == 1) { - Translate(mxt, myt); - } else { - checkForDeferredSave(); - Push(0, 1, - mxx, mxy, mxt, - myx, myy, myt); - tracker_.transform2DAffine(mxx, mxy, mxt, - myx, myy, myt); - } - } -} -// full 4x4 transform in row major order -void DlOptimizingCanvas::TransformFullPerspective( - SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, - SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, - SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, - SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) { - if ( mxz == 0 && - myz == 0 && - mzx == 0 && mzy == 0 && mzz == 1 && mzt == 0 && - mwx == 0 && mwy == 0 && mwz == 0 && mwt == 1) { - Transform2DAffine(mxx, mxy, mxt, - myx, myy, myt); - } else if (SkScalarsAreFinite(mxx, mxy) && SkScalarsAreFinite(mxz, mxt) && - SkScalarsAreFinite(myx, myy) && SkScalarsAreFinite(myz, myt) && - SkScalarsAreFinite(mzx, mzy) && SkScalarsAreFinite(mzz, mzt) && - SkScalarsAreFinite(mwx, mwy) && SkScalarsAreFinite(mwz, mwt)) { - checkForDeferredSave(); - Push(0, 1, - mxx, mxy, mxz, mxt, - myx, myy, myz, myt, - mzx, mzy, mzz, mzt, - mwx, mwy, mwz, mwt); - tracker_.transformFullPerspective(mxx, mxy, mxz, mxt, - myx, myy, myz, myt, - mzx, mzy, mzz, mzt, - mwx, mwy, mwz, mwt); - } -} -// clang-format on -void DlOptimizingCanvas::TransformReset() { - checkForDeferredSave(); - Push(0, 0); - tracker_.setIdentity(); -} -void DlOptimizingCanvas::Transform(const SkMatrix* matrix) { - if (matrix != nullptr) { - Transform(SkM44(*matrix)); - } -} -void DlOptimizingCanvas::Transform(const SkM44* m44) { - if (m44 != nullptr) { - transformFullPerspective( - m44->rc(0, 0), m44->rc(0, 1), m44->rc(0, 2), m44->rc(0, 3), - m44->rc(1, 0), m44->rc(1, 1), m44->rc(1, 2), m44->rc(1, 3), - m44->rc(2, 0), m44->rc(2, 1), m44->rc(2, 2), m44->rc(2, 3), - m44->rc(3, 0), m44->rc(3, 1), m44->rc(3, 2), m44->rc(3, 3)); - } -} - -void DlOptimizingCanvas::ClipRect(const SkRect& rect, - ClipOp clip_op, - bool is_aa) { - if (!rect.isFinite()) { - return; - } - tracker_.clipRect(rect, clip_op, is_aa); - if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { - current_layer_->is_nop_ = true; - return; - } - checkForDeferredSave(); - switch (clip_op) { - case ClipOp::kIntersect: - Push(0, 1, rect, is_aa); - break; - case ClipOp::kDifference: - Push(0, 1, rect, is_aa); - break; - } -} -void DlOptimizingCanvas::ClipRRect(const SkRRect& rrect, - ClipOp clip_op, - bool is_aa) { - if (rrect.isRect()) { - clipRect(rrect.rect(), clip_op, is_aa); - } else { - tracker_.clipRRect(rrect, clip_op, is_aa); - if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { - current_layer_->is_nop_ = true; - return; - } - checkForDeferredSave(); - switch (clip_op) { - case ClipOp::kIntersect: - Push(0, 1, rrect, is_aa); - break; - case ClipOp::kDifference: - Push(0, 1, rrect, is_aa); - break; - } - } -} -void DlOptimizingCanvas::ClipPath(const SkPath& path, - ClipOp clip_op, - bool is_aa) { - if (!path.isInverseFillType()) { - SkRect rect; - if (path.isRect(&rect)) { - this->clipRect(rect, clip_op, is_aa); - return; - } - SkRRect rrect; - if (path.isOval(&rect)) { - rrect.setOval(rect); - this->clipRRect(rrect, clip_op, is_aa); - return; - } - if (path.isRRect(&rrect)) { - this->clipRRect(rrect, clip_op, is_aa); - return; - } - } - tracker_.clipPath(path, clip_op, is_aa); - if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { - current_layer_->is_nop_ = true; - return; - } - checkForDeferredSave(); - switch (clip_op) { - case ClipOp::kIntersect: - Push(0, 1, path, is_aa); - break; - case ClipOp::kDifference: - Push(0, 1, path, is_aa); - break; - } -} - -bool DlOptimizingCanvas::QuickReject(const SkRect& bounds) const { - return tracker_.content_culled(bounds); -} - -void DlOptimizingCanvas::drawPaint() { - OpResult result = PaintResult(current_, kDrawPaintFlags); - if (result != OpResult::kNoEffect && AccumulateUnbounded()) { - Push(0, 1); - CheckLayerOpacityCompatibility(); - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::DrawPaint(const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawPaintFlags); - drawPaint(); -} -void DlOptimizingCanvas::DrawColor(DlColor color, DlBlendMode mode) { - OpResult result = PaintResult(DlPaint(color).setBlendMode(mode)); - if (result != OpResult::kNoEffect && AccumulateUnbounded()) { - Push(0, 1, color, mode); - CheckLayerOpacityCompatibility(mode); - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::drawLine(const SkPoint& p0, const SkPoint& p1) { - SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); - DisplayListAttributeFlags flags = - (bounds.width() > 0.0f && bounds.height() > 0.0f) ? kDrawLineFlags - : kDrawHVLineFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds, flags)) { - Push(0, 1, p0, p1); - CheckLayerOpacityCompatibility(); - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::DrawLine(const SkPoint& p0, - const SkPoint& p1, - const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawLineFlags); - drawLine(p0, p1); -} -void DlOptimizingCanvas::drawRect(const SkRect& rect) { - DisplayListAttributeFlags flags = kDrawRectFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect && - AccumulateOpBounds(rect.makeSorted(), flags)) { - Push(0, 1, rect); - CheckLayerOpacityCompatibility(); - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::DrawRect(const SkRect& rect, const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRectFlags); - drawRect(rect); -} -void DlOptimizingCanvas::drawOval(const SkRect& bounds) { - DisplayListAttributeFlags flags = kDrawOvalFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect && - AccumulateOpBounds(bounds.makeSorted(), flags)) { - Push(0, 1, bounds); - CheckLayerOpacityCompatibility(); - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::DrawOval(const SkRect& bounds, const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawOvalFlags); - drawOval(bounds); -} -void DlOptimizingCanvas::drawCircle(const SkPoint& center, SkScalar radius) { - DisplayListAttributeFlags flags = kDrawCircleFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect) { - SkRect bounds = SkRect::MakeLTRB(center.fX - radius, center.fY - radius, - center.fX + radius, center.fY + radius); - if (AccumulateOpBounds(bounds, flags)) { - Push(0, 1, center, radius); - CheckLayerOpacityCompatibility(); - UpdateLayerResult(result); - } - } -} -void DlOptimizingCanvas::DrawCircle(const SkPoint& center, - SkScalar radius, - const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawCircleFlags); - drawCircle(center, radius); -} -void DlOptimizingCanvas::drawRRect(const SkRRect& rrect) { - if (rrect.isRect()) { - drawRect(rrect.rect()); - } else if (rrect.isOval()) { - drawOval(rrect.rect()); - } else { - DisplayListAttributeFlags flags = kDrawRRectFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect && - AccumulateOpBounds(rrect.getBounds(), flags)) { - Push(0, 1, rrect); - CheckLayerOpacityCompatibility(); - UpdateLayerResult(result); - } - } -} -void DlOptimizingCanvas::DrawRRect(const SkRRect& rrect, const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRRectFlags); - drawRRect(rrect); -} -void DlOptimizingCanvas::drawDRRect(const SkRRect& outer, - const SkRRect& inner) { - DisplayListAttributeFlags flags = kDrawDRRectFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect && - AccumulateOpBounds(outer.getBounds(), flags)) { - Push(0, 1, outer, inner); - CheckLayerOpacityCompatibility(); - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::DrawDRRect(const SkRRect& outer, - const SkRRect& inner, - const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawDRRectFlags); - drawDRRect(outer, inner); -} -void DlOptimizingCanvas::drawPath(const SkPath& path) { - DisplayListAttributeFlags flags = kDrawPathFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect) { - bool is_visible = path.isInverseFillType() - ? AccumulateUnbounded() - : AccumulateOpBounds(path.getBounds(), flags); - if (is_visible) { - Push(0, 1, path); - CheckLayerOpacityHairlineCompatibility(); - UpdateLayerResult(result); - } - } -} -void DlOptimizingCanvas::DrawPath(const SkPath& path, const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawPathFlags); - drawPath(path); -} - -void DlOptimizingCanvas::drawArc(const SkRect& bounds, - SkScalar start, - SkScalar sweep, - bool useCenter) { - DisplayListAttributeFlags flags = // - useCenter // - ? kDrawArcWithCenterFlags - : kDrawArcNoCenterFlags; - OpResult result = PaintResult(current_, flags); - // This could be tighter if we compute where the start and end - // angles are and then also consider the quadrants swept and - // the center if specified. - if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds, flags)) { - Push(0, 1, bounds, start, sweep, useCenter); - if (useCenter) { - CheckLayerOpacityHairlineCompatibility(); - } else { - CheckLayerOpacityCompatibility(); - } - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::DrawArc(const SkRect& bounds, - SkScalar start, - SkScalar sweep, - bool useCenter, - const DlPaint& paint) { - SetAttributesFromPaint( - paint, useCenter ? kDrawArcWithCenterFlags : kDrawArcNoCenterFlags); - drawArc(bounds, start, sweep, useCenter); -} - -DisplayListAttributeFlags DlOptimizingCanvas::FlagsForPointMode( - PointMode mode) { - switch (mode) { - case DlCanvas::PointMode::kPoints: - return kDrawPointsAsPointsFlags; - case PointMode::kLines: - return kDrawPointsAsLinesFlags; - case PointMode::kPolygon: - return kDrawPointsAsPolygonFlags; - } - FML_UNREACHABLE(); -} -void DlOptimizingCanvas::drawPoints(PointMode mode, - uint32_t count, - const SkPoint pts[]) { - if (count == 0) { - return; - } - DisplayListAttributeFlags flags = FlagsForPointMode(mode); - OpResult result = PaintResult(current_, flags); - if (result == OpResult::kNoEffect) { - return; - } - - FML_DCHECK(count < DlOpReceiver::kMaxDrawPointsCount); - int bytes = count * sizeof(SkPoint); - RectBoundsAccumulator ptBounds; - for (size_t i = 0; i < count; i++) { - ptBounds.accumulate(pts[i]); - } - SkRect point_bounds = ptBounds.bounds(); - if (!AccumulateOpBounds(point_bounds, flags)) { - return; - } - - void* data_ptr; - switch (mode) { - case PointMode::kPoints: - data_ptr = Push(bytes, 1, count); - break; - case PointMode::kLines: - data_ptr = Push(bytes, 1, count); - break; - case PointMode::kPolygon: - data_ptr = Push(bytes, 1, count); - break; - default: - FML_UNREACHABLE(); - return; - } - CopyV(data_ptr, pts, count); - // drawPoints treats every point or line (or segment of a polygon) - // as a completely separate operation meaning we cannot ensure - // distribution of group opacity without analyzing the mode and the - // bounds of every sub-primitive. - // See: https://fiddle.skia.org/c/228459001d2de8db117ce25ef5cedb0c - UpdateLayerOpacityCompatibility(false); - UpdateLayerResult(result); -} -void DlOptimizingCanvas::DrawPoints(PointMode mode, - uint32_t count, - const SkPoint pts[], - const DlPaint& paint) { - SetAttributesFromPaint(paint, FlagsForPointMode(mode)); - drawPoints(mode, count, pts); -} -void DlOptimizingCanvas::drawVertices(const DlVertices* vertices, - DlBlendMode mode) { - DisplayListAttributeFlags flags = kDrawVerticesFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect && - AccumulateOpBounds(vertices->bounds(), flags)) { - void* pod = Push(vertices->size(), 1, mode); - new (pod) DlVertices(vertices); - // DrawVertices applies its colors to the paint so we have no way - // of controlling opacity using the current paint attributes. - // Although, examination of the |mode| might find some predictable - // cases. - UpdateLayerOpacityCompatibility(false); - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::DrawVertices(const DlVertices* vertices, - DlBlendMode mode, - const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawVerticesFlags); - drawVertices(vertices, mode); -} - -void DlOptimizingCanvas::drawImage(const sk_sp image, - const SkPoint point, - DlImageSampling sampling, - bool render_with_attributes) { - DisplayListAttributeFlags flags = render_with_attributes // - ? kDrawImageWithPaintFlags - : kDrawImageFlags; - OpResult result = PaintResult(current_, flags); - if (result == OpResult::kNoEffect) { - return; - } - SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, // - image->width(), image->height()); - if (AccumulateOpBounds(bounds, flags)) { - render_with_attributes - ? Push(0, 1, image, point, sampling) - : Push(0, 1, image, point, sampling); - CheckLayerOpacityCompatibility(render_with_attributes); - UpdateLayerResult(result); - is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); - } -} -void DlOptimizingCanvas::DrawImage(const sk_sp& image, - const SkPoint point, - DlImageSampling sampling, - const DlPaint* paint) { - if (paint != nullptr) { - SetAttributesFromPaint(*paint, - DisplayListOpFlags::kDrawImageWithPaintFlags); - drawImage(image, point, sampling, true); - } else { - drawImage(image, point, sampling, false); - } -} -void DlOptimizingCanvas::drawImageRect(const sk_sp image, - const SkRect& src, - const SkRect& dst, - DlImageSampling sampling, - bool render_with_attributes, - SrcRectConstraint constraint) { - DisplayListAttributeFlags flags = render_with_attributes - ? kDrawImageRectWithPaintFlags - : kDrawImageRectFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect && AccumulateOpBounds(dst, flags)) { - Push(0, 1, image, src, dst, sampling, - render_with_attributes, constraint); - CheckLayerOpacityCompatibility(render_with_attributes); - UpdateLayerResult(result); - is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); - } -} -void DlOptimizingCanvas::DrawImageRect(const sk_sp& image, - const SkRect& src, - const SkRect& dst, - DlImageSampling sampling, - const DlPaint* paint, - SrcRectConstraint constraint) { - if (paint != nullptr) { - SetAttributesFromPaint(*paint, - DisplayListOpFlags::kDrawImageRectWithPaintFlags); - drawImageRect(image, src, dst, sampling, true, constraint); - } else { - drawImageRect(image, src, dst, sampling, false, constraint); - } -} -void DlOptimizingCanvas::drawImageNine(const sk_sp image, - const SkIRect& center, - const SkRect& dst, - DlFilterMode filter, - bool render_with_attributes) { - DisplayListAttributeFlags flags = render_with_attributes - ? kDrawImageNineWithPaintFlags - : kDrawImageNineFlags; - OpResult result = PaintResult(current_, flags); - if (result != OpResult::kNoEffect && AccumulateOpBounds(dst, flags)) { - render_with_attributes - ? Push(0, 1, image, center, dst, filter) - : Push(0, 1, image, center, dst, filter); - CheckLayerOpacityCompatibility(render_with_attributes); - UpdateLayerResult(result); - is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); - } -} -void DlOptimizingCanvas::DrawImageNine(const sk_sp& image, - const SkIRect& center, - const SkRect& dst, - DlFilterMode filter, - const DlPaint* paint) { - if (paint != nullptr) { - SetAttributesFromPaint(*paint, - DisplayListOpFlags::kDrawImageNineWithPaintFlags); - drawImageNine(image, center, dst, filter, true); - } else { - drawImageNine(image, center, dst, filter, false); - } -} -void DlOptimizingCanvas::drawAtlas(const sk_sp atlas, - const SkRSXform xform[], - const SkRect tex[], - const DlColor colors[], - int count, - DlBlendMode mode, - DlImageSampling sampling, - const SkRect* cull_rect, - bool render_with_attributes) { - DisplayListAttributeFlags flags = render_with_attributes // - ? kDrawAtlasWithPaintFlags - : kDrawAtlasFlags; - OpResult result = PaintResult(current_, flags); - if (result == OpResult::kNoEffect) { - return; - } - SkPoint quad[4]; - RectBoundsAccumulator atlasBounds; - for (int i = 0; i < count; i++) { - const SkRect& src = tex[i]; - xform[i].toQuad(src.width(), src.height(), quad); - for (int j = 0; j < 4; j++) { - atlasBounds.accumulate(quad[j]); - } - } - if (atlasBounds.is_empty() || - !AccumulateOpBounds(atlasBounds.bounds(), flags)) { - return; - } - - int bytes = count * (sizeof(SkRSXform) + sizeof(SkRect)); - void* data_ptr; - if (colors != nullptr) { - bytes += count * sizeof(DlColor); - if (cull_rect != nullptr) { - data_ptr = - Push(bytes, 1, atlas, count, mode, sampling, true, - *cull_rect, render_with_attributes); - } else { - data_ptr = Push(bytes, 1, atlas, count, mode, sampling, true, - render_with_attributes); - } - CopyV(data_ptr, xform, count, tex, count, colors, count); - } else { - if (cull_rect != nullptr) { - data_ptr = - Push(bytes, 1, atlas, count, mode, sampling, false, - *cull_rect, render_with_attributes); - } else { - data_ptr = Push(bytes, 1, atlas, count, mode, sampling, - false, render_with_attributes); - } - CopyV(data_ptr, xform, count, tex, count); - } - // drawAtlas treats each image as a separate operation so we cannot rely - // on it to distribute the opacity without overlap without checking all - // of the transforms and texture rectangles. - UpdateLayerOpacityCompatibility(false); - UpdateLayerResult(result); - is_ui_thread_safe_ = is_ui_thread_safe_ && atlas->isUIThreadSafe(); -} -void DlOptimizingCanvas::DrawAtlas(const sk_sp& atlas, - const SkRSXform xform[], - const SkRect tex[], - const DlColor colors[], - int count, - DlBlendMode mode, - DlImageSampling sampling, - const SkRect* cull_rect, - const DlPaint* paint) { - if (paint != nullptr) { - SetAttributesFromPaint(*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 DlOptimizingCanvas::DrawDisplayList(const sk_sp display_list, - SkScalar opacity) { - if (!SkScalarIsFinite(opacity) || opacity <= SK_ScalarNearlyZero || - display_list->op_count() == 0 || display_list->bounds().isEmpty() || - current_layer_->is_nop_) { - return; - } - const SkRect bounds = display_list->bounds(); - bool accumulated; - switch (accumulator()->type()) { - case BoundsAccumulatorType::kRect: - accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); - break; - case BoundsAccumulatorType::kRTree: - auto rtree = display_list->rtree(); - if (rtree) { - std::list rects = - rtree->searchAndConsolidateRects(bounds, false); - accumulated = false; - for (const SkRect& rect : rects) { - // TODO (https://github.com/flutter/flutter/issues/114919): Attributes - // are not necessarily `kDrawDisplayListFlags`. - if (AccumulateOpBounds(rect, kDrawDisplayListFlags)) { - accumulated = true; - } - } - } else { - accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); - } - break; - } - if (!accumulated) { - return; - } - - DlPaint current_paint = current_; - Push(0, 1, display_list, - opacity < SK_Scalar1 ? opacity : SK_Scalar1); - is_ui_thread_safe_ = is_ui_thread_safe_ && display_list->isUIThreadSafe(); - // Not really necessary if the developer is interacting with us via - // our attribute-state-less DlCanvas methods, but this avoids surprises - // for those who may have been using the stateful Dispatcher methods. - SetAttributesFromPaint(current_paint, - DisplayListOpFlags::kSaveLayerWithPaintFlags); - - // The non-nested op count accumulated in the |Push| method will include - // this call to |drawDisplayList| for non-nested op count metrics. - // But, for nested op count metrics we want the |drawDisplayList| call itself - // to be transparent. So we subtract 1 from our accumulated nested count to - // balance out against the 1 that was accumulated into the regular count. - // This behavior is identical to the way SkPicture computed nested op counts. - nested_op_count_ += display_list->op_count(true) - 1; - nested_bytes_ += display_list->bytes(true); - UpdateLayerOpacityCompatibility(display_list->can_apply_group_opacity()); - // Nop DisplayLists are eliminated above so we either affect transparent - // pixels or we do not. We should not have [kNoEffect]. - UpdateLayerResult(display_list->modifies_transparent_black() - ? OpResult::kAffectsAll - : OpResult::kPreservesTransparency); -} -void DlOptimizingCanvas::drawTextBlob(const sk_sp blob, - SkScalar x, - SkScalar y) { - DisplayListAttributeFlags flags = kDrawTextBlobFlags; - OpResult result = PaintResult(current_, flags); - if (result == OpResult::kNoEffect) { - return; - } - bool unclipped = AccumulateOpBounds(blob->bounds().makeOffset(x, y), flags); - // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the - // unit tests can use Fuchsia's font manager instead of the empty default. - // Until then we might encounter empty bounds for otherwise valid text and - // thus we ignore the results from AccumulateOpBounds. -#if defined(OS_FUCHSIA) - unclipped = true; -#endif // OS_FUCHSIA - if (unclipped) { - Push(0, 1, blob, x, y); - // There is no way to query if the glyphs of a text blob overlap and - // there are no current guarantees from either Skia or Impeller that - // they will protect overlapping glyphs from the effects of overdraw - // so we must make the conservative assessment that this DL layer is - // not compatible with group opacity inheritance. - UpdateLayerOpacityCompatibility(false); - UpdateLayerResult(result); - } -} -void DlOptimizingCanvas::DrawTextBlob(const sk_sp& blob, - SkScalar x, - SkScalar y, - const DlPaint& paint) { - SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawTextBlobFlags); - drawTextBlob(blob, x, y); -} -void DlOptimizingCanvas::DrawShadow(const SkPath& path, - const DlColor color, - const SkScalar elevation, - bool transparent_occluder, - SkScalar dpr) { - OpResult result = PaintResult(DlPaint(color)); - if (result != OpResult::kNoEffect) { - SkRect shadow_bounds = - DlCanvas::ComputeShadowBounds(path, elevation, dpr, GetTransform()); - if (AccumulateOpBounds(shadow_bounds, kDrawShadowFlags)) { - transparent_occluder // - ? Push(0, 1, path, color, elevation, - dpr) - : Push(0, 1, path, color, elevation, dpr); - UpdateLayerOpacityCompatibility(false); - UpdateLayerResult(result); - } - } -} - -bool DlOptimizingCanvas::ComputeFilteredBounds(SkRect& bounds, - const DlImageFilter* filter) { - if (filter) { - if (!filter->map_local_bounds(bounds, bounds)) { - return false; - } - } - return true; -} - -bool DlOptimizingCanvas::AdjustBoundsForPaint(SkRect& bounds, - DisplayListAttributeFlags flags) { - if (flags.ignores_paint()) { - return true; - } - - if (flags.is_geometric()) { - bool is_stroked = flags.is_stroked(current_.getDrawStyle()); - - // Path effect occurs before stroking... - DisplayListSpecialGeometryFlags special_flags = - flags.WithPathEffect(current_.getPathEffectPtr(), is_stroked); - if (current_.getPathEffect()) { - auto effect_bounds = current_.getPathEffect()->effect_bounds(bounds); - if (!effect_bounds.has_value()) { - return false; - } - bounds = effect_bounds.value(); - } - - if (is_stroked) { - // Determine the max multiplier to the stroke width first. - SkScalar pad = 1.0f; - if (current_.getStrokeJoin() == DlStrokeJoin::kMiter && - special_flags.may_have_acute_joins()) { - pad = std::max(pad, current_.getStrokeMiter()); - } - if (current_.getStrokeCap() == DlStrokeCap::kSquare && - special_flags.may_have_diagonal_caps()) { - pad = std::max(pad, SK_ScalarSqrt2); - } - SkScalar min_stroke_width = 0.01; - pad *= std::max(current_.getStrokeWidth() * 0.5f, min_stroke_width); - bounds.outset(pad, pad); - } - } - - if (flags.applies_mask_filter()) { - auto filter = current_.getMaskFilter(); - if (filter) { - switch (filter->type()) { - case DlMaskFilterType::kBlur: { - FML_DCHECK(filter->asBlur()); - SkScalar mask_sigma_pad = filter->asBlur()->sigma() * 3.0; - bounds.outset(mask_sigma_pad, mask_sigma_pad); - } - } - } - } - - if (flags.applies_image_filter()) { - return ComputeFilteredBounds(bounds, current_.getImageFilter().get()); - } - - return true; -} - -bool DlOptimizingCanvas::AccumulateUnbounded() { - SkRect clip = tracker_.device_cull_rect(); - if (clip.isEmpty()) { - return false; - } - accumulator()->accumulate(clip, op_index_); - return true; -} - -bool DlOptimizingCanvas::AccumulateOpBounds(SkRect& bounds, - DisplayListAttributeFlags flags) { - if (AdjustBoundsForPaint(bounds, flags)) { - return AccumulateBounds(bounds); - } else { - return AccumulateUnbounded(); - } -} -bool DlOptimizingCanvas::AccumulateBounds(SkRect& bounds) { - if (!bounds.isEmpty()) { - tracker_.mapRect(&bounds); - if (bounds.intersect(tracker_.device_cull_rect())) { - accumulator()->accumulate(bounds, op_index_); - return true; - } - } - return false; -} - -bool DlOptimizingCanvas::paint_nops_on_transparency() { - // SkImageFilter::canComputeFastBounds tests for transparency behavior - // This test assumes that the blend mode checked down below will - // NOP on transparent black. - if (current_.getImageFilterPtr() && - current_.getImageFilterPtr()->modifies_transparent_black()) { - return false; - } - - // We filter the transparent black that is used for the background of a - // saveLayer and make sure it returns transparent black. If it does, then - // the color filter will leave all area surrounding the contents of the - // save layer untouched out to the edge of the output surface. - // This test assumes that the blend mode checked down below will - // NOP on transparent black. - if (current_.getColorFilterPtr() && - current_.getColorFilterPtr()->modifies_transparent_black()) { - return false; - } - - // Unusual blendmodes require us to process a saved layer - // even with operations outside the clip. - // For example, DstIn is used by masking layers. - // https://code.google.com/p/skia/issues/detail?id=1291 - // https://crbug.com/401593 - switch (current_.getBlendMode()) { - // For each of the following transfer modes, if the source - // alpha is zero (our transparent black), the resulting - // blended pixel is not necessarily equal to the original - // destination pixel. - // Mathematically, any time in the following equations where - // the result is not d assuming source is 0 - case DlBlendMode::kClear: // r = 0 - case DlBlendMode::kSrc: // r = s - case DlBlendMode::kSrcIn: // r = s * da - case DlBlendMode::kDstIn: // r = d * sa - case DlBlendMode::kSrcOut: // r = s * (1-da) - case DlBlendMode::kDstATop: // r = d*sa + s*(1-da) - case DlBlendMode::kModulate: // r = s*d - return false; - break; - - // And in these equations, the result must be d if the - // source is 0 - case DlBlendMode::kDst: // r = d - case DlBlendMode::kSrcOver: // r = s + (1-sa)*d - case DlBlendMode::kDstOver: // r = d + (1-da)*s - case DlBlendMode::kDstOut: // r = d * (1-sa) - case DlBlendMode::kSrcATop: // r = s*da + d*(1-sa) - case DlBlendMode::kXor: // r = s*(1-da) + d*(1-sa) - case DlBlendMode::kPlus: // r = min(s + d, 1) - case DlBlendMode::kScreen: // r = s + d - s*d - case DlBlendMode::kOverlay: // multiply or screen, depending on dest - case DlBlendMode::kDarken: // rc = s + d - max(s*da, d*sa), - // ra = kSrcOver - case DlBlendMode::kLighten: // rc = s + d - min(s*da, d*sa), - // ra = kSrcOver - case DlBlendMode::kColorDodge: // brighten destination to reflect source - case DlBlendMode::kColorBurn: // darken destination to reflect source - case DlBlendMode::kHardLight: // multiply or screen, depending on source - case DlBlendMode::kSoftLight: // lighten or darken, depending on source - case DlBlendMode::kDifference: // rc = s + d - 2*(min(s*da, d*sa)), - // ra = kSrcOver - case DlBlendMode::kExclusion: // rc = s + d - two(s*d), ra = kSrcOver - case DlBlendMode::kMultiply: // r = s*(1-da) + d*(1-sa) + s*d - case DlBlendMode::kHue: // ra = kSrcOver - case DlBlendMode::kSaturation: // ra = kSrcOver - case DlBlendMode::kColor: // ra = kSrcOver - case DlBlendMode::kLuminosity: // ra = kSrcOver - return true; - break; - } -} - -DlColor DlOptimizingCanvas::GetEffectiveColor(const DlPaint& paint, - DisplayListAttributeFlags flags) { - DlColor color; - if (flags.applies_color()) { - const DlColorSource* source = paint.getColorSourcePtr(); - if (source) { - if (source->asColor()) { - color = source->asColor()->color(); - } else { - color = source->is_opaque() ? DlColor::kBlack() : kAnyColor; - } - } else { - color = paint.getColor(); - } - } else if (flags.applies_alpha()) { - // If the operation applies alpha, but not color, then the only impact - // of the alpha is to modulate the output towards transparency. - // We can not guarantee an opaque source even if the alpha is opaque - // since that would require knowing something about the colors that - // the alpha is modulating, but we can guarantee a transparent source - // if the alpha is 0. - color = (paint.getAlpha() == 0) ? DlColor::kTransparent() : kAnyColor; - } else { - color = kAnyColor; - } - if (flags.applies_image_filter()) { - auto filter = paint.getImageFilterPtr(); - if (filter) { - if (!color.isTransparent() || filter->modifies_transparent_black()) { - color = kAnyColor; - } - } - } - if (flags.applies_color_filter()) { - auto filter = paint.getColorFilterPtr(); - if (filter) { - if (!color.isTransparent() || filter->modifies_transparent_black()) { - color = kAnyColor; - } - } - } - return color; -} - -DlOptimizingCanvas::OpResult DlOptimizingCanvas::PaintResult( - const DlPaint& paint, - DisplayListAttributeFlags flags) { - if (current_layer_->is_nop_) { - return OpResult::kNoEffect; - } - if (flags.applies_blend()) { - switch (paint.getBlendMode()) { - // Nop blend mode (singular, there is only one) - case DlBlendMode::kDst: - return OpResult::kNoEffect; - - // Always clears pixels blend mode (singular, there is only one) - case DlBlendMode::kClear: - return OpResult::kPreservesTransparency; - - case DlBlendMode::kHue: - case DlBlendMode::kSaturation: - case DlBlendMode::kColor: - case DlBlendMode::kLuminosity: - case DlBlendMode::kColorBurn: - return GetEffectiveColor(paint, flags).isTransparent() - ? OpResult::kNoEffect - : OpResult::kAffectsAll; - - // kSrcIn modifies pixels towards transparency - case DlBlendMode::kSrcIn: - return OpResult::kPreservesTransparency; - - // These blend modes preserve destination alpha - case DlBlendMode::kSrcATop: - case DlBlendMode::kDstOut: - return GetEffectiveColor(paint, flags).isTransparent() - ? OpResult::kNoEffect - : OpResult::kPreservesTransparency; - - // Always destructive blend modes, potentially not affecting transparency - case DlBlendMode::kSrc: - case DlBlendMode::kSrcOut: - case DlBlendMode::kDstATop: - return GetEffectiveColor(paint, flags).isTransparent() - ? OpResult::kPreservesTransparency - : OpResult::kAffectsAll; - - // The kDstIn blend mode modifies the destination unless the - // source color is opaque. - case DlBlendMode::kDstIn: - return GetEffectiveColor(paint, flags).isOpaque() - ? OpResult::kNoEffect - : OpResult::kPreservesTransparency; - - // The next group of blend modes modifies the destination unless the - // source color is transparent. - case DlBlendMode::kSrcOver: - case DlBlendMode::kDstOver: - case DlBlendMode::kXor: - case DlBlendMode::kPlus: - case DlBlendMode::kScreen: - case DlBlendMode::kMultiply: - case DlBlendMode::kOverlay: - case DlBlendMode::kDarken: - case DlBlendMode::kLighten: - case DlBlendMode::kColorDodge: - case DlBlendMode::kHardLight: - case DlBlendMode::kSoftLight: - case DlBlendMode::kDifference: - case DlBlendMode::kExclusion: - return GetEffectiveColor(paint, flags).isTransparent() - ? OpResult::kNoEffect - : OpResult::kAffectsAll; - - // Modulate only leaves the pixel alone when the source is white. - case DlBlendMode::kModulate: - return GetEffectiveColor(paint, flags) == DlColor::kWhite() - ? OpResult::kNoEffect - : OpResult::kPreservesTransparency; - } - } - return OpResult::kAffectsAll; -} - -} // namespace flutter diff --git a/display_list/dl_optimizing_canvas.h b/display_list/dl_optimizing_canvas.h deleted file mode 100644 index 04fbdfa05f2af..0000000000000 --- a/display_list/dl_optimizing_canvas.h +++ /dev/null @@ -1,295 +0,0 @@ -// 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. - -#pragma once - -#include "flutter/display_list/dl_canvas.h" - -namespace flutter { - -class DlOptimizingCanvas : public virtual DlCanvas { - public: - DlOptimizingCanvas(const SkRect& cull_rect, bool prepare_rtree = false); - virtual ~DlOptimizingCanvas() = default; - - virtual SkISize GetBaseLayerSize() const override; - - virtual void Save() override; - virtual void SaveLayer(const SkRect* bounds, - const DlPaint* paint = nullptr, - const DlImageFilter* backdrop = nullptr) override; - virtual void Restore() override; - virtual void RestoreToCount(int restore_count) override; - - virtual void Translate(SkScalar tx, SkScalar ty) override; - virtual void Scale(SkScalar sx, SkScalar sy) override; - virtual void Rotate(SkScalar degrees) override; - virtual void Skew(SkScalar sx, SkScalar sy) override; - - // clang-format off - - // 2x3 2D affine subset of a 4x4 transform in row major order - virtual void Transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt, - SkScalar myx, SkScalar myy, SkScalar myt) override; - // full 4x4 transform in row major order - virtual void TransformFullPerspective( - SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, - SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, - SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, - SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override; - // clang-format on - virtual void TransformReset() override; - virtual void Transform(const SkMatrix* matrix) override; - virtual void Transform(const SkM44* matrix44) override; - virtual void Transform(const SkMatrix& matrix) { Transform(&matrix); } - virtual void Transform(const SkM44& matrix44) { Transform(&matrix44); } - virtual void SetTransform(const SkMatrix* matrix) override; - virtual void SetTransform(const SkM44* matrix44) override; - virtual void SetTransform(const SkMatrix& matrix) { SetTransform(&matrix); } - virtual void SetTransform(const SkM44& matrix44) { SetTransform(&matrix44); } - - /// Returns the 4x4 full perspective transform representing all transform - /// operations executed so far in this DisplayList within the enclosing - /// save stack. - virtual SkM44 GetTransformFullPerspective() const override; - /// Returns the 3x3 partial perspective transform representing all transform - /// operations executed so far in this DisplayList within the enclosing - /// save stack. - virtual SkMatrix GetTransform() const override; - - virtual void ClipRect(const SkRect& rect, - ClipOp clip_op = ClipOp::kIntersect, - bool is_aa = false) override; - virtual void ClipRRect(const SkRRect& rrect, - ClipOp clip_op = ClipOp::kIntersect, - bool is_aa = false) override; - virtual void ClipPath(const SkPath& path, - ClipOp clip_op = ClipOp::kIntersect, - bool is_aa = false) override; - - /// Conservative estimate of the bounds of all outstanding clip operations - /// measured in the coordinate space within which this DisplayList will - /// be rendered. - virtual SkRect GetDestinationClipBounds() const override; - /// Conservative estimate of the bounds of all outstanding clip operations - /// transformed into the local coordinate space in which currently - /// recorded rendering operations are interpreted. - virtual SkRect GetLocalClipBounds() const override; - - /// Return true iff the supplied bounds are easily shown to be outside - /// of the current clip bounds. This method may conservatively return - /// false if it cannot make the determination. - virtual bool QuickReject(const SkRect& bounds) const override; - - virtual void DrawPaint(const DlPaint& paint) override; - virtual void DrawColor(DlColor color, - DlBlendMode mode = DlBlendMode::kSrcOver) override; - - virtual void DrawLine(const SkPoint& p0, - const SkPoint& p1, - const DlPaint& paint) override; - virtual void DrawRect(const SkRect& rect, const DlPaint& paint) override; - virtual void DrawOval(const SkRect& bounds, const DlPaint& paint) override; - virtual void DrawCircle(const SkPoint& center, - SkScalar radius, - const DlPaint& paint); - virtual void DrawRRect(const SkRRect& rrect, const DlPaint& paint) override; - virtual void DrawDRRect(const SkRRect& outer, - const SkRRect& inner, - const DlPaint& paint) override; - virtual void DrawPath(const SkPath& path, const DlPaint& paint) override; - virtual void DrawArc(const SkRect& bounds, - SkScalar start, - SkScalar sweep, - bool useCenter, - const DlPaint& paint) override; - virtual void DrawPoints(PointMode mode, - uint32_t count, - const SkPoint pts[], - const DlPaint& paint) override; - virtual void DrawVertices(const DlVertices* vertices, - DlBlendMode mode, - const DlPaint& paint) override; - virtual void DrawImage(const sk_sp& image, - const SkPoint point, - DlImageSampling sampling, - const DlPaint* paint = nullptr) override; - virtual void DrawImageRect( - const sk_sp& image, - const SkRect& src, - const SkRect& dst, - DlImageSampling sampling, - const DlPaint* paint = nullptr, - SrcRectConstraint constraint = SrcRectConstraint::kFast) override; - virtual void DrawImageNine(const sk_sp& image, - const SkIRect& center, - const SkRect& dst, - DlFilterMode filter, - const DlPaint* paint = nullptr) override; - virtual void DrawAtlas(const sk_sp& atlas, - const SkRSXform xform[], - const SkRect tex[], - const DlColor colors[], - int count, - DlBlendMode mode, - DlImageSampling sampling, - const SkRect* cullRect, - const DlPaint* paint = nullptr) override; - virtual void DrawDisplayList(const sk_sp display_list, - SkScalar opacity = SK_Scalar1) override; - virtual void DrawImpellerPicture( - const std::shared_ptr& picture, - SkScalar opacity = SK_Scalar1) override; - virtual void DrawTextBlob(const sk_sp& blob, - SkScalar x, - SkScalar y, - const DlPaint& paint) override; - virtual void DrawShadow(const SkPath& path, - const DlColor color, - const SkScalar elevation, - bool transparent_occluder, - SkScalar dpr) override; - - private: - std::vector layer_stack_; - LayerInfo* current_layer_; - DisplayListMatrixClipTracker tracker_; - std::unique_ptr accumulator_; - BoundsAccumulator* accumulator() { return accumulator_.get(); } - - // This flag indicates whether or not the current rendering attributes - // are compatible with rendering ops applying an inherited opacity. - bool current_opacity_compatibility_ = true; - - // Returns the compatibility of a given blend mode for applying an - // inherited opacity value to modulate the visibility of the op. - // For now we only accept SrcOver blend modes but this could be expanded - // in the future to include other (rarely used) modes that also modulate - // the opacity of a rendering operation at the cost of a switch statement - // or lookup table. - static bool IsOpacityCompatible(DlBlendMode mode) { - return (mode == DlBlendMode::kSrcOver); - } - - void UpdateCurrentOpacityCompatibility() { - current_opacity_compatibility_ = // - current_.getColorFilter() == nullptr && // - !current_.isInvertColors() && // - IsOpacityCompatible(current_.getBlendMode()); - } - - // Update the opacity compatibility flags of the current layer for an op - // that has determined its compatibility as indicated by |compatible|. - void UpdateLayerOpacityCompatibility(bool compatible) { - if (compatible) { - current_layer_->add_compatible_op(); - } else { - current_layer_->mark_incompatible(); - } - } - - // Check for opacity compatibility for an op that may or may not use the - // current rendering attributes as indicated by |uses_blend_attribute|. - // If the flag is false then the rendering op will be able to substitute - // a default Paint object with the opacity applied using the default SrcOver - // blend mode which is always compatible with applying an inherited opacity. - void CheckLayerOpacityCompatibility(bool uses_blend_attribute = true) { - UpdateLayerOpacityCompatibility(!uses_blend_attribute || - current_opacity_compatibility_); - } - - void CheckLayerOpacityHairlineCompatibility() { - UpdateLayerOpacityCompatibility( - current_opacity_compatibility_ && - (current_.getDrawStyle() == DlDrawStyle::kFill || - current_.getStrokeWidth() > 0)); - } - - // Check for opacity compatibility for an op that ignores the current - // attributes and uses the indicated blend |mode| to render to the layer. - // This is only used by |drawColor| currently. - void CheckLayerOpacityCompatibility(DlBlendMode mode) { - UpdateLayerOpacityCompatibility(IsOpacityCompatible(mode)); - } - - // The DisplayList had an unbounded call with no cull rect or clip - // to contain it. Should only be called after the stream is fully - // built. - // Unbounded operations are calls like |drawColor| which are defined - // to flood the entire surface, or calls that relied on a rendering - // attribute which is unable to compute bounds (should be rare). - // In those cases the bounds will represent only the accumulation - // of the bounded calls and this flag will be set to indicate that - // condition. - bool is_unbounded() const { - FML_DCHECK(layer_stack_.size() == 1); - return layer_stack_.front().is_unbounded(); - } - - SkRect bounds() const { - FML_DCHECK(layer_stack_.size() == 1); - if (is_unbounded()) { - FML_LOG(INFO) << "returning partial bounds for unbounded DisplayList"; - } - - return accumulator_->bounds(); - } - - sk_sp rtree() { - FML_DCHECK(layer_stack_.size() == 1); - if (is_unbounded()) { - FML_LOG(INFO) << "returning partial rtree for unbounded DisplayList"; - } - - return accumulator_->rtree(); - } - - enum class OpResult { - kNoEffect, - kPreservesTransparency, - kAffectsAll, - }; - - bool paint_nops_on_transparency(); - OpResult PaintResult(const DlPaint& paint, - DisplayListAttributeFlags flags = kDrawPaintFlags); - - void UpdateLayerResult(OpResult result) { - switch (result) { - case OpResult::kNoEffect: - case OpResult::kPreservesTransparency: - break; - case OpResult::kAffectsAll: - current_layer_->add_visible_op(); - break; - } - } - - // Adjusts the indicated bounds for the given flags and returns true if - // the calculation was possible, or false if it could not be estimated. - bool AdjustBoundsForPaint(SkRect& bounds, DisplayListAttributeFlags flags); - - // Records the fact that we encountered an op that either could not - // estimate its bounds or that fills all of the destination space. - bool AccumulateUnbounded(); - - // Records the bounds for an op after modifying them according to the - // supplied attribute flags and transforming by the current matrix. - bool AccumulateOpBounds(const SkRect& bounds, - DisplayListAttributeFlags flags) { - SkRect safe_bounds = bounds; - return AccumulateOpBounds(safe_bounds, flags); - } - - // Records the bounds for an op after modifying them according to the - // supplied attribute flags and transforming by the current matrix - // and clipping against the current clip. - bool AccumulateOpBounds(SkRect& bounds, DisplayListAttributeFlags flags); - - // Records the given bounds after transforming by the current matrix - // and clipping against the current clip. - bool AccumulateBounds(SkRect& bounds); -}; - -} // namespace flutter