| // 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_accumulation_rect.h" |
| #include "fml/logging.h" |
| #include "third_party/skia/include/core/SkScalar.h" |
| |
| namespace flutter { |
| |
| #define DL_BUILDER_PAGE 4096 |
| |
| // CopyV(dst, src,n, src,n, ...) copies any number of typed srcs into dst. |
| static void CopyV(void* dst) {} |
| |
| template <typename S, typename... Rest> |
| static void CopyV(void* dst, const S* src, int n, Rest&&... rest) { |
| FML_DCHECK(((uintptr_t)dst & (alignof(S) - 1)) == 0) |
| << "Expected " << dst << " to be aligned for at least " << alignof(S) |
| << " bytes."; |
| // If n is 0, there is nothing to copy into dst from src. |
| if (n > 0) { |
| memcpy(dst, src, n * sizeof(S)); |
| dst = reinterpret_cast<void*>(reinterpret_cast<uint8_t*>(dst) + |
| n * sizeof(S)); |
| } |
| // Repeat for the next items, if any |
| CopyV(dst, std::forward<Rest>(rest)...); |
| } |
| |
| static constexpr inline bool is_power_of_two(int value) { |
| return (value & (value - 1)) == 0; |
| } |
| |
| template <typename T, typename... Args> |
| void* DisplayListBuilder::Push(size_t pod, Args&&... args) { |
| size_t size = SkAlignPtr(sizeof(T) + pod); |
| FML_CHECK(size < (1 << 24)); |
| if (used_ + size > allocated_) { |
| static_assert(is_power_of_two(DL_BUILDER_PAGE), |
| "This math needs updating for non-pow2."); |
| // Next greater multiple of DL_BUILDER_PAGE. |
| allocated_ = (used_ + size + DL_BUILDER_PAGE) & ~(DL_BUILDER_PAGE - 1); |
| storage_.realloc(allocated_); |
| FML_CHECK(storage_.get()); |
| memset(storage_.get() + used_, 0, allocated_ - used_); |
| } |
| FML_CHECK(used_ + size <= allocated_); |
| auto op = reinterpret_cast<T*>(storage_.get() + used_); |
| used_ += size; |
| new (op) T{std::forward<Args>(args)...}; |
| op->type = T::kType; |
| op->size = size; |
| render_op_count_ += T::kRenderOpInc; |
| depth_ += T::kDepthInc * render_op_depth_cost_; |
| op_index_++; |
| return op + 1; |
| } |
| |
| sk_sp<DisplayList> DisplayListBuilder::Build() { |
| while (save_stack_.size() > 1) { |
| restore(); |
| } |
| |
| size_t bytes = used_; |
| int count = render_op_count_; |
| size_t nested_bytes = nested_bytes_; |
| int nested_count = nested_op_count_; |
| uint32_t total_depth = depth_; |
| bool opacity_compatible = current_layer().is_group_opacity_compatible(); |
| bool is_safe = is_ui_thread_safe_; |
| bool affects_transparency = current_layer().affects_transparent_layer; |
| bool root_has_backdrop_filter = current_layer().contains_backdrop_filter; |
| DlBlendMode max_root_blend_mode = current_layer().max_blend_mode; |
| |
| sk_sp<DlRTree> rtree; |
| SkRect bounds; |
| if (rtree_data_.has_value()) { |
| auto& rects = rtree_data_->rects; |
| auto& indices = rtree_data_->indices; |
| rtree = sk_make_sp<DlRTree>(rects.data(), rects.size(), indices.data(), |
| [](int id) { return id >= 0; }); |
| // RTree bounds may be tighter due to applying filter bounds |
| // adjustments to each op as we restore layers rather than to |
| // the entire layer bounds. |
| bounds = rtree->bounds(); |
| rtree_data_.reset(); |
| } else { |
| bounds = current_layer().global_space_accumulator.bounds(); |
| } |
| |
| used_ = allocated_ = render_op_count_ = op_index_ = 0; |
| nested_bytes_ = nested_op_count_ = 0; |
| depth_ = 0; |
| is_ui_thread_safe_ = true; |
| current_opacity_compatibility_ = true; |
| render_op_depth_cost_ = 1u; |
| current_ = DlPaint(); |
| |
| save_stack_.pop_back(); |
| Init(rtree != nullptr); |
| |
| storage_.realloc(bytes); |
| return sk_sp<DisplayList>(new DisplayList( |
| std::move(storage_), bytes, count, nested_bytes, nested_count, |
| total_depth, bounds, opacity_compatible, is_safe, affects_transparency, |
| max_root_blend_mode, root_has_backdrop_filter, std::move(rtree))); |
| } |
| |
| static constexpr DlRect kEmpty = DlRect(); |
| |
| static const DlRect& ProtectEmpty(const SkRect& rect) { |
| // isEmpty protects us against NaN while we normalize any empty cull rects |
| return rect.isEmpty() ? kEmpty : ToDlRect(rect); |
| } |
| |
| DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect, |
| bool prepare_rtree) |
| : original_cull_rect_(ProtectEmpty(cull_rect)) { |
| Init(prepare_rtree); |
| } |
| |
| void DisplayListBuilder::Init(bool prepare_rtree) { |
| FML_DCHECK(save_stack_.empty()); |
| FML_DCHECK(!rtree_data_.has_value()); |
| |
| save_stack_.emplace_back(original_cull_rect_); |
| current_info().is_nop = original_cull_rect_.IsEmpty(); |
| if (prepare_rtree) { |
| rtree_data_.emplace(); |
| } |
| } |
| |
| DisplayListBuilder::~DisplayListBuilder() { |
| uint8_t* ptr = storage_.get(); |
| if (ptr) { |
| DisplayList::DisposeOps(ptr, ptr + used_); |
| } |
| } |
| |
| SkISize DisplayListBuilder::GetBaseLayerSize() const { |
| return ToSkISize(DlIRect::RoundOut(original_cull_rect_).GetSize()); |
| } |
| |
| SkImageInfo DisplayListBuilder::GetImageInfo() const { |
| SkISize size = GetBaseLayerSize(); |
| return SkImageInfo::MakeUnknown(size.width(), size.height()); |
| } |
| |
| void DisplayListBuilder::onSetAntiAlias(bool aa) { |
| current_.setAntiAlias(aa); |
| Push<SetAntiAliasOp>(0, aa); |
| } |
| void DisplayListBuilder::onSetInvertColors(bool invert) { |
| current_.setInvertColors(invert); |
| Push<SetInvertColorsOp>(0, invert); |
| UpdateCurrentOpacityCompatibility(); |
| } |
| void DisplayListBuilder::onSetStrokeCap(DlStrokeCap cap) { |
| current_.setStrokeCap(cap); |
| Push<SetStrokeCapOp>(0, cap); |
| } |
| void DisplayListBuilder::onSetStrokeJoin(DlStrokeJoin join) { |
| current_.setStrokeJoin(join); |
| Push<SetStrokeJoinOp>(0, join); |
| } |
| void DisplayListBuilder::onSetDrawStyle(DlDrawStyle style) { |
| current_.setDrawStyle(style); |
| Push<SetStyleOp>(0, style); |
| } |
| void DisplayListBuilder::onSetStrokeWidth(float width) { |
| current_.setStrokeWidth(width); |
| Push<SetStrokeWidthOp>(0, width); |
| } |
| void DisplayListBuilder::onSetStrokeMiter(float limit) { |
| current_.setStrokeMiter(limit); |
| Push<SetStrokeMiterOp>(0, limit); |
| } |
| void DisplayListBuilder::onSetColor(DlColor color) { |
| current_.setColor(color); |
| Push<SetColorOp>(0, color); |
| } |
| void DisplayListBuilder::onSetBlendMode(DlBlendMode mode) { |
| current_.setBlendMode(mode); |
| Push<SetBlendModeOp>(0, mode); |
| UpdateCurrentOpacityCompatibility(); |
| } |
| |
| void DisplayListBuilder::onSetColorSource(const DlColorSource* source) { |
| if (source == nullptr) { |
| current_.setColorSource(nullptr); |
| Push<ClearColorSourceOp>(0); |
| } 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: { |
| const DlImageColorSource* image_source = source->asImage(); |
| FML_DCHECK(image_source); |
| Push<SetImageColorSourceOp>(0, image_source); |
| break; |
| } |
| case DlColorSourceType::kLinearGradient: { |
| const DlLinearGradientColorSource* linear = source->asLinearGradient(); |
| FML_DCHECK(linear); |
| void* pod = Push<SetPodColorSourceOp>(linear->size()); |
| new (pod) DlLinearGradientColorSource(linear); |
| break; |
| } |
| case DlColorSourceType::kRadialGradient: { |
| const DlRadialGradientColorSource* radial = source->asRadialGradient(); |
| FML_DCHECK(radial); |
| void* pod = Push<SetPodColorSourceOp>(radial->size()); |
| new (pod) DlRadialGradientColorSource(radial); |
| break; |
| } |
| case DlColorSourceType::kConicalGradient: { |
| const DlConicalGradientColorSource* conical = |
| source->asConicalGradient(); |
| FML_DCHECK(conical); |
| void* pod = Push<SetPodColorSourceOp>(conical->size()); |
| new (pod) DlConicalGradientColorSource(conical); |
| break; |
| } |
| case DlColorSourceType::kSweepGradient: { |
| const DlSweepGradientColorSource* sweep = source->asSweepGradient(); |
| FML_DCHECK(sweep); |
| void* pod = Push<SetPodColorSourceOp>(sweep->size()); |
| new (pod) DlSweepGradientColorSource(sweep); |
| break; |
| } |
| case DlColorSourceType::kRuntimeEffect: { |
| const DlRuntimeEffectColorSource* effect = source->asRuntimeEffect(); |
| FML_DCHECK(effect); |
| Push<SetRuntimeEffectColorSourceOp>(0, effect); |
| break; |
| } |
| #ifdef IMPELLER_ENABLE_3D |
| case DlColorSourceType::kScene: { |
| const DlSceneColorSource* scene = source->asScene(); |
| FML_DCHECK(scene); |
| Push<SetSceneColorSourceOp>(0, scene); |
| break; |
| } |
| #endif // IMPELLER_ENABLE_3D |
| } |
| } |
| } |
| void DisplayListBuilder::onSetImageFilter(const DlImageFilter* filter) { |
| if (filter == nullptr) { |
| current_.setImageFilter(nullptr); |
| Push<ClearImageFilterOp>(0); |
| } else { |
| current_.setImageFilter(filter->shared()); |
| switch (filter->type()) { |
| case DlImageFilterType::kBlur: { |
| const DlBlurImageFilter* blur_filter = filter->asBlur(); |
| FML_DCHECK(blur_filter); |
| void* pod = Push<SetPodImageFilterOp>(blur_filter->size()); |
| new (pod) DlBlurImageFilter(blur_filter); |
| break; |
| } |
| case DlImageFilterType::kDilate: { |
| const DlDilateImageFilter* dilate_filter = filter->asDilate(); |
| FML_DCHECK(dilate_filter); |
| void* pod = Push<SetPodImageFilterOp>(dilate_filter->size()); |
| new (pod) DlDilateImageFilter(dilate_filter); |
| break; |
| } |
| case DlImageFilterType::kErode: { |
| const DlErodeImageFilter* erode_filter = filter->asErode(); |
| FML_DCHECK(erode_filter); |
| void* pod = Push<SetPodImageFilterOp>(erode_filter->size()); |
| new (pod) DlErodeImageFilter(erode_filter); |
| break; |
| } |
| case DlImageFilterType::kMatrix: { |
| const DlMatrixImageFilter* matrix_filter = filter->asMatrix(); |
| FML_DCHECK(matrix_filter); |
| void* pod = Push<SetPodImageFilterOp>(matrix_filter->size()); |
| new (pod) DlMatrixImageFilter(matrix_filter); |
| break; |
| } |
| case DlImageFilterType::kCompose: |
| case DlImageFilterType::kLocalMatrix: |
| case DlImageFilterType::kColorFilter: { |
| Push<SetSharedImageFilterOp>(0, filter); |
| break; |
| } |
| } |
| } |
| } |
| void DisplayListBuilder::onSetColorFilter(const DlColorFilter* filter) { |
| if (filter == nullptr) { |
| current_.setColorFilter(nullptr); |
| Push<ClearColorFilterOp>(0); |
| } else { |
| current_.setColorFilter(filter->shared()); |
| switch (filter->type()) { |
| case DlColorFilterType::kBlend: { |
| const DlBlendColorFilter* blend_filter = filter->asBlend(); |
| FML_DCHECK(blend_filter); |
| void* pod = Push<SetPodColorFilterOp>(blend_filter->size()); |
| new (pod) DlBlendColorFilter(blend_filter); |
| break; |
| } |
| case DlColorFilterType::kMatrix: { |
| const DlMatrixColorFilter* matrix_filter = filter->asMatrix(); |
| FML_DCHECK(matrix_filter); |
| void* pod = Push<SetPodColorFilterOp>(matrix_filter->size()); |
| new (pod) DlMatrixColorFilter(matrix_filter); |
| break; |
| } |
| case DlColorFilterType::kSrgbToLinearGamma: { |
| void* pod = Push<SetPodColorFilterOp>(filter->size()); |
| new (pod) DlSrgbToLinearGammaColorFilter(); |
| break; |
| } |
| case DlColorFilterType::kLinearToSrgbGamma: { |
| void* pod = Push<SetPodColorFilterOp>(filter->size()); |
| new (pod) DlLinearToSrgbGammaColorFilter(); |
| break; |
| } |
| } |
| } |
| UpdateCurrentOpacityCompatibility(); |
| } |
| void DisplayListBuilder::onSetMaskFilter(const DlMaskFilter* filter) { |
| if (filter == nullptr) { |
| current_.setMaskFilter(nullptr); |
| render_op_depth_cost_ = 1u; |
| Push<ClearMaskFilterOp>(0); |
| } else { |
| current_.setMaskFilter(filter->shared()); |
| render_op_depth_cost_ = 2u; |
| switch (filter->type()) { |
| case DlMaskFilterType::kBlur: { |
| const DlBlurMaskFilter* blur_filter = filter->asBlur(); |
| FML_DCHECK(blur_filter); |
| void* pod = Push<SetPodMaskFilterOp>(blur_filter->size()); |
| new (pod) DlBlurMaskFilter(blur_filter); |
| break; |
| } |
| } |
| } |
| } |
| |
| void DisplayListBuilder::SetAttributesFromPaint( |
| const DlPaint& paint, |
| const DisplayListAttributeFlags flags) { |
| if (flags.applies_anti_alias()) { |
| setAntiAlias(paint.isAntiAlias()); |
| } |
| if (flags.applies_alpha_or_color()) { |
| setColor(paint.getColor()); |
| } |
| if (flags.applies_blend()) { |
| setBlendMode(paint.getBlendMode()); |
| } |
| if (flags.applies_style()) { |
| setDrawStyle(paint.getDrawStyle()); |
| } |
| if (flags.is_stroked(paint.getDrawStyle())) { |
| setStrokeWidth(paint.getStrokeWidth()); |
| setStrokeMiter(paint.getStrokeMiter()); |
| setStrokeCap(paint.getStrokeCap()); |
| setStrokeJoin(paint.getStrokeJoin()); |
| } |
| if (flags.applies_shader()) { |
| setColorSource(paint.getColorSource().get()); |
| } |
| if (flags.applies_color_filter()) { |
| setInvertColors(paint.isInvertColors()); |
| setColorFilter(paint.getColorFilter().get()); |
| } |
| if (flags.applies_image_filter()) { |
| setImageFilter(paint.getImageFilter().get()); |
| } |
| if (flags.applies_mask_filter()) { |
| setMaskFilter(paint.getMaskFilter().get()); |
| } |
| } |
| |
| void DisplayListBuilder::checkForDeferredSave() { |
| if (current_info().has_deferred_save_op) { |
| size_t save_offset = used_; |
| Push<SaveOp>(0); |
| current_info().save_offset = save_offset; |
| current_info().save_depth = depth_; |
| current_info().has_deferred_save_op = false; |
| } |
| } |
| |
| void DisplayListBuilder::Save() { |
| bool was_nop = current_info().is_nop; |
| save_stack_.emplace_back(¤t_info()); |
| current_info().is_nop = was_nop; |
| |
| FML_DCHECK(save_stack_.size() >= 2u); |
| FML_DCHECK(current_info().has_deferred_save_op); |
| } |
| |
| void DisplayListBuilder::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) { |
| // If we can't render, whether because we were already in a no-render |
| // state from the parent or because our own attributes make us a nop, |
| // we can just simplify this whole layer to a regular save that has |
| // nop state. We need to have a SaveInfo for the eventual restore(), |
| // but no rendering ops should be accepted between now and then so |
| // it doesn't need any of the data associated with a layer SaveInfo. |
| Save(); |
| current_info().is_nop = true; |
| return; |
| } |
| |
| if (backdrop != nullptr) { |
| current_layer().contains_backdrop_filter = true; |
| } |
| |
| // Snapshot these values before we do any work as we need the values |
| // from before the method was called, but some of the operations below |
| // might update them. |
| size_t save_offset = used_; |
| uint32_t save_depth = depth_; |
| |
| // A backdrop will affect up to the entire surface, bounded by the clip |
| bool will_be_unbounded = (backdrop != nullptr); |
| std::shared_ptr<const DlImageFilter> filter; |
| |
| if (options.renders_with_attributes()) { |
| if (!paint_nops_on_transparency()) { |
| // We will fill the clip of the outer layer when we restore. |
| will_be_unbounded = true; |
| } |
| filter = current_.getImageFilter(); |
| CheckLayerOpacityCompatibility(true); |
| UpdateLayerResult(result, true); |
| } else { |
| CheckLayerOpacityCompatibility(false); |
| UpdateLayerResult(result, false); |
| } |
| |
| // 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 (will_be_unbounded) { |
| // 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); |
| } |
| |
| // Accumulate information for the SaveInfo we are about to push onto the |
| // stack. |
| { |
| size_t rtree_index = |
| rtree_data_.has_value() ? rtree_data_->rects.size() : 0u; |
| |
| save_stack_.emplace_back(¤t_info(), filter, rtree_index); |
| FML_DCHECK(current_info().is_save_layer); |
| FML_DCHECK(!current_info().is_nop); |
| FML_DCHECK(!current_info().has_deferred_save_op); |
| current_info().save_offset = save_offset; |
| current_info().save_depth = save_depth; |
| |
| // If we inherit some culling bounds and we have a filter then we need |
| // to adjust them so that we cull for the correct input space for the |
| // output of the filter. |
| if (filter) { |
| SkRect outer_cull_rect = current_info().global_state.device_cull_rect(); |
| SkMatrix matrix = current_info().global_state.matrix_3x3(); |
| |
| SkIRect output_bounds = outer_cull_rect.roundOut(); |
| SkIRect input_bounds; |
| if (filter->get_input_device_bounds(output_bounds, matrix, |
| input_bounds)) { |
| current_info().global_state.resetDeviceCullRect( |
| SkRect::Make(input_bounds)); |
| } else { |
| // Filter could not make any promises about the bounds it needs to |
| // fill the output space, so we use a maximal rect to accumulate |
| // the layer bounds. |
| current_info().global_state.resetDeviceCullRect(kMaxCullRect); |
| } |
| } |
| |
| // We always want to cull based on user provided bounds, though, as |
| // that is legacy behavior even if it doesn't always work precisely |
| // in a rotated or skewed coordinate system (but it will work |
| // conservatively). |
| if (in_options.bounds_from_caller()) { |
| current_info().global_state.clipRect(bounds, ClipOp::kIntersect, false); |
| } |
| } |
| |
| // Accumulate options to store in the SaveLayer op record. |
| { |
| SkRect record_bounds; |
| if (in_options.bounds_from_caller()) { |
| options = options.with_bounds_from_caller(); |
| record_bounds = bounds; |
| } else { |
| FML_DCHECK(record_bounds.isEmpty()); |
| } |
| |
| if (backdrop) { |
| Push<SaveLayerBackdropOp>(0, options, record_bounds, backdrop); |
| } else { |
| Push<SaveLayerOp>(0, options, record_bounds); |
| } |
| } |
| |
| 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_ || filter) { |
| UpdateLayerOpacityCompatibility(false); |
| } |
| } |
| } |
| void DisplayListBuilder::SaveLayer(const SkRect* bounds, |
| const DlPaint* paint, |
| const DlImageFilter* backdrop) { |
| SaveLayerOptions options; |
| SkRect temp_bounds; |
| if (bounds) { |
| options = options.with_bounds_from_caller(); |
| temp_bounds = *bounds; |
| } else { |
| temp_bounds.setEmpty(); |
| } |
| if (paint != nullptr) { |
| options = options.with_renders_with_attributes(); |
| SetAttributesFromPaint(*paint, |
| DisplayListOpFlags::kSaveLayerWithPaintFlags); |
| } |
| saveLayer(temp_bounds, options, backdrop); |
| } |
| |
| void DisplayListBuilder::Restore() { |
| if (save_stack_.size() <= 1) { |
| return; |
| } |
| |
| if (!current_info().has_deferred_save_op) { |
| SaveOpBase* op = reinterpret_cast<SaveOpBase*>(storage_.get() + |
| current_info().save_offset); |
| FML_CHECK(op->type == DisplayListOpType::kSave || |
| op->type == DisplayListOpType::kSaveLayer || |
| op->type == DisplayListOpType::kSaveLayerBackdrop); |
| |
| op->restore_index = op_index_; |
| op->total_content_depth = depth_ - current_info().save_depth; |
| |
| if (current_info().is_save_layer) { |
| RestoreLayer(); |
| } |
| |
| // Wait until all outgoing bounds information for the saveLayer is |
| // recorded before pushing the record to the buffer so that any rtree |
| // bounds will be attributed to the op_index of the restore op. |
| Push<RestoreOp>(0); |
| } else { |
| FML_DCHECK(!current_info().is_save_layer); |
| } |
| |
| save_stack_.pop_back(); |
| } |
| |
| void DisplayListBuilder::RestoreLayer() { |
| FML_DCHECK(save_stack_.size() > 1); |
| FML_DCHECK(current_info().is_save_layer); |
| FML_DCHECK(!current_info().has_deferred_save_op); |
| |
| // A saveLayer will usually do a final copy to the main buffer in |
| // addition to its content, but that is accounted for outside of |
| // the total content depth computed above in Restore. |
| depth_ += render_op_depth_cost_; |
| |
| SkRect content_bounds = current_layer().layer_local_accumulator.bounds(); |
| |
| SaveLayerOpBase* layer_op = reinterpret_cast<SaveLayerOpBase*>( |
| storage_.get() + current_info().save_offset); |
| FML_CHECK(layer_op->type == DisplayListOpType::kSaveLayer || |
| layer_op->type == DisplayListOpType::kSaveLayerBackdrop); |
| |
| if (layer_op->options.bounds_from_caller()) { |
| if (!content_bounds.isEmpty() && !layer_op->rect.contains(content_bounds)) { |
| layer_op->options = layer_op->options.with_content_is_clipped(); |
| content_bounds.intersect(layer_op->rect); |
| } |
| } |
| layer_op->rect = content_bounds; |
| layer_op->max_blend_mode = current_layer().max_blend_mode; |
| |
| if (current_layer().contains_backdrop_filter) { |
| layer_op->options = layer_op->options.with_contains_backdrop_filter(); |
| } |
| |
| if (current_layer().is_group_opacity_compatible()) { |
| layer_op->options = layer_op->options.with_can_distribute_opacity(); |
| } |
| |
| // Ensure that the bounds transferred in the following call will be |
| // attributed to the index of the restore op. |
| FML_DCHECK(layer_op->restore_index == op_index_); |
| TransferLayerBounds(content_bounds); |
| } |
| |
| // There are a few different conditions and corresponding operations to |
| // consider when transferring bounds from one layer to another. The current |
| // layer will have accumulated its bounds into 2 potential places: |
| // |
| // - Its own private layer local bounds, which were potentially clipped by |
| // the supplied bounds and passed here as the content_bounds. |
| // |
| // - Either the rtree rect list, or the global space accumulator, one or |
| // the other. |
| // |
| // If there is no filter then the private layer bounds are complete and |
| // they simply need to be passed along to the parent into its layer local |
| // accumulator. Also, if there was no filter then the existing bounds |
| // recorded in either the rtree rects or the layer's global space accumulator |
| // (shared with its parent) need no updating so no global space transfer |
| // has to occur. |
| // |
| // If there is a filter then the global content bounds will need to be |
| // adjusted in one of two ways (rtree vs non-rtree): |
| // |
| // - If we are accumulating rtree rects then each of the rects accumulated |
| // during this current layer will need to be updated by the filter in the |
| // global coordinate space in which they were accumulated. In this mode |
| // we should never have a global space accumulator on the layer. |
| // |
| // - Otherwise we were accumulating global bounds into our own private |
| // global space accumulator which need to be adjusted in the global space |
| // coordinate system by the filter. |
| // |
| // Finally, we will have to adjust the layer's content bounds by the filter |
| // and accumulate those into the parent layer's local bounds. |
| void DisplayListBuilder::TransferLayerBounds(const SkRect& content_bounds) { |
| auto& filter = current_layer().filter; |
| |
| if (!filter) { |
| // We either accumulate global bounds into the rtree_data if there |
| // is one, or into the global_space_accumulator, but not both. |
| FML_DCHECK(!rtree_data_.has_value() || |
| current_layer().global_space_accumulator.is_empty()); |
| |
| parent_info().AccumulateBoundsLocal(content_bounds); |
| parent_layer().global_space_accumulator.accumulate( |
| current_layer().global_space_accumulator); |
| return; |
| } |
| |
| bool parent_is_flooded = false; |
| SkRect bounds_for_parent = content_bounds; |
| |
| // First, let's adjust or transfer the global/rtree bounds by the filter. |
| |
| // Matrix and Clip for the filter adjustment are the global values from |
| // just before our saveLayer and should still be the current values |
| // present in the parent layer. |
| const SkRect clip = parent_info().global_state.device_cull_rect(); |
| const SkMatrix matrix = parent_info().global_state.matrix_3x3(); |
| |
| if (rtree_data_.has_value()) { |
| // Neither current or parent layer should have any global bounds in |
| // their accumulator |
| FML_DCHECK(current_layer().global_space_accumulator.is_empty()); |
| FML_DCHECK(parent_layer().global_space_accumulator.is_empty()); |
| |
| // The rtree rects were accumulated without the bounds modification of |
| // the filter applied to the layer so they may fail to trigger on a |
| // culled dispatch if their filter "fringes" are in the dispatch scope |
| // but their base rendering bounds are not. (Also, they will not |
| // contribute fully when we compute the overall bounds of this DL.) |
| // |
| // To make sure they are rendered in the culled dispatch situation, we |
| // revisit all of the RTree rects accumulated during the current layer |
| // (indicated by rtree_rects_start_index) and expand them by the filter. |
| |
| if (AdjustRTreeRects(rtree_data_.value(), *filter, matrix, clip, |
| current_layer().rtree_rects_start_index)) { |
| parent_is_flooded = true; |
| } |
| } else { |
| SkRect global_bounds = current_layer().global_space_accumulator.bounds(); |
| if (!global_bounds.isEmpty()) { |
| SkIRect global_ibounds = global_bounds.roundOut(); |
| if (!filter->map_device_bounds(global_ibounds, matrix, global_ibounds)) { |
| parent_is_flooded = true; |
| } else { |
| global_bounds.set(global_ibounds); |
| if (global_bounds.intersect(clip)) { |
| parent_layer().global_space_accumulator.accumulate(global_bounds); |
| } |
| } |
| } |
| } |
| |
| // Now we visit the layer bounds which are in the layer's local coordinate |
| // system must be accumulated into the parent layer's bounds while |
| // adjusting them by the layer's local coordinate system (handled by the |
| // Accumulate() methods). |
| |
| // A filter will happily adjust empty bounds to be non-empty, so we |
| // specifically avoid that case here. Also, if we are already planning |
| // to flood the parent due to any of the cases above, we don't need to |
| // run the filter on the content bounds only to discover the same |
| // condition. |
| if (!parent_is_flooded && !bounds_for_parent.isEmpty()) { |
| if (!filter->map_local_bounds(bounds_for_parent, bounds_for_parent)) { |
| parent_is_flooded = true; |
| } |
| } |
| |
| if (parent_is_flooded) { |
| // All of the above computations deferred the flooded parent status |
| // to here. We need to mark the parent as flooded in both its layer |
| // and global accumulators. Note that even though the rtree rects |
| // were expanded to the size of the clip above, this method will still |
| // add one more rect to the rtree with the op index of the restore |
| // command to prevent the saveLayer itself from being elided in the |
| // rare case that there are no rendering ops in it, or somehow none |
| // of them were chosen by the rtree search (unlikely). The saveLayer |
| // must be processed for the parent flood to happen. |
| AccumulateUnbounded(parent_info()); |
| } else { |
| parent_info().AccumulateBoundsLocal(bounds_for_parent); |
| } |
| } |
| |
| bool DisplayListBuilder::AdjustRTreeRects(RTreeData& data, |
| const DlImageFilter& filter, |
| const SkMatrix& matrix, |
| const SkRect& clip, |
| size_t rect_start_index) { |
| auto& rects = data.rects; |
| auto& indices = data.indices; |
| FML_DCHECK(rects.size() == indices.size()); |
| int ret = false; |
| auto rect_keep = rect_start_index; |
| for (size_t i = rect_start_index; i < rects.size(); i++) { |
| SkRect bounds = rects[i]; |
| SkIRect ibounds; |
| if (filter.map_device_bounds(bounds.roundOut(), matrix, ibounds)) { |
| bounds.set(ibounds); |
| } else { |
| bounds = clip; |
| ret = true; |
| } |
| if (bounds.intersect(clip)) { |
| indices[rect_keep] = indices[i]; |
| rects[rect_keep] = bounds; |
| rect_keep++; |
| } |
| } |
| indices.resize(rect_keep); |
| rects.resize(rect_keep); |
| return ret; |
| } |
| |
| void DisplayListBuilder::RestoreToCount(int restore_count) { |
| FML_DCHECK(restore_count <= GetSaveCount()); |
| while (restore_count < GetSaveCount() && GetSaveCount() > 1) { |
| restore(); |
| } |
| } |
| |
| void DisplayListBuilder::Translate(SkScalar tx, SkScalar ty) { |
| if (std::isfinite(tx) && std::isfinite(ty) && (tx != 0.0 || ty != 0.0)) { |
| checkForDeferredSave(); |
| Push<TranslateOp>(0, tx, ty); |
| global_state().translate(tx, ty); |
| layer_local_state().translate(tx, ty); |
| } |
| } |
| void DisplayListBuilder::Scale(SkScalar sx, SkScalar sy) { |
| if (std::isfinite(sx) && std::isfinite(sy) && (sx != 1.0 || sy != 1.0)) { |
| checkForDeferredSave(); |
| Push<ScaleOp>(0, sx, sy); |
| global_state().scale(sx, sy); |
| layer_local_state().scale(sx, sy); |
| } |
| } |
| void DisplayListBuilder::Rotate(SkScalar degrees) { |
| if (SkScalarMod(degrees, 360.0) != 0.0) { |
| checkForDeferredSave(); |
| Push<RotateOp>(0, degrees); |
| global_state().rotate(degrees); |
| layer_local_state().rotate(degrees); |
| } |
| } |
| void DisplayListBuilder::Skew(SkScalar sx, SkScalar sy) { |
| if (std::isfinite(sx) && std::isfinite(sy) && (sx != 0.0 || sy != 0.0)) { |
| checkForDeferredSave(); |
| Push<SkewOp>(0, sx, sy); |
| global_state().skew(sx, sy); |
| layer_local_state().skew(sx, sy); |
| } |
| } |
| |
| // clang-format off |
| |
| // 2x3 2D affine subset of a 4x4 transform in row major order |
| void DisplayListBuilder::Transform2DAffine( |
| SkScalar mxx, SkScalar mxy, SkScalar mxt, |
| SkScalar myx, SkScalar myy, SkScalar myt) { |
| if (std::isfinite(mxx) && std::isfinite(myx) && |
| std::isfinite(mxy) && std::isfinite(myy) && |
| std::isfinite(mxt) && std::isfinite(myt)) { |
| if (mxx == 1 && mxy == 0 && |
| myx == 0 && myy == 1) { |
| Translate(mxt, myt); |
| } else { |
| checkForDeferredSave(); |
| Push<Transform2DAffineOp>(0, |
| mxx, mxy, mxt, |
| myx, myy, myt); |
| global_state().transform2DAffine(mxx, mxy, mxt, |
| myx, myy, myt); |
| layer_local_state().transform2DAffine(mxx, mxy, mxt, |
| myx, myy, myt); |
| } |
| } |
| } |
| // full 4x4 transform in row major order |
| void DisplayListBuilder::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 (std::isfinite(mxx) && std::isfinite(mxy) && |
| std::isfinite(mxz) && std::isfinite(mxt) && |
| std::isfinite(myx) && std::isfinite(myy) && |
| std::isfinite(myz) && std::isfinite(myt) && |
| std::isfinite(mzx) && std::isfinite(mzy) && |
| std::isfinite(mzz) && std::isfinite(mzt) && |
| std::isfinite(mwx) && std::isfinite(mwy) && |
| std::isfinite(mwz) && std::isfinite(mwt)) { |
| checkForDeferredSave(); |
| Push<TransformFullPerspectiveOp>(0, |
| mxx, mxy, mxz, mxt, |
| myx, myy, myz, myt, |
| mzx, mzy, mzz, mzt, |
| mwx, mwy, mwz, mwt); |
| global_state().transformFullPerspective(mxx, mxy, mxz, mxt, |
| myx, myy, myz, myt, |
| mzx, mzy, mzz, mzt, |
| mwx, mwy, mwz, mwt); |
| layer_local_state().transformFullPerspective(mxx, mxy, mxz, mxt, |
| myx, myy, myz, myt, |
| mzx, mzy, mzz, mzt, |
| mwx, mwy, mwz, mwt); |
| } |
| } |
| // clang-format on |
| void DisplayListBuilder::TransformReset() { |
| checkForDeferredSave(); |
| Push<TransformResetOp>(0); |
| |
| // The matrices in layer_tracker_ and tracker_ are similar, but |
| // start at a different base transform. The tracker_ potentially |
| // has some number of transform operations on it that prefix the |
| // operations accumulated in layer_tracker_. So we can't set them both |
| // to identity in parallel as they would no longer maintain their |
| // relationship to each other. |
| // Instead we reinterpret this operation as transforming by the |
| // inverse of the current transform. Doing so to tracker_ sets it |
| // to identity so we can avoid the math there, but we must do the |
| // math the long way for layer_tracker_. This becomes: |
| // layer_tracker_.transform(tracker_.inverse()); |
| if (!layer_local_state().inverseTransform(global_state())) { |
| // If the inverse operation failed then that means that either |
| // the matrix above the current layer was singular, or the matrix |
| // became singular while we were accumulating the current layer. |
| // In either case, we should no longer be accumulating any |
| // contents so we set the layer tracking transform to a singular one. |
| layer_local_state().setTransform(SkMatrix::Scale(0.0f, 0.0f)); |
| } |
| |
| global_state().setIdentity(); |
| } |
| void DisplayListBuilder::Transform(const SkMatrix* matrix) { |
| if (matrix != nullptr) { |
| Transform(SkM44(*matrix)); |
| } |
| } |
| void DisplayListBuilder::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 DisplayListBuilder::ClipRect(const SkRect& rect, |
| ClipOp clip_op, |
| bool is_aa) { |
| if (!rect.isFinite()) { |
| return; |
| } |
| if (current_info().is_nop) { |
| return; |
| } |
| if (current_info().has_valid_clip && |
| clip_op == DlCanvas::ClipOp::kIntersect && |
| layer_local_state().rect_covers_cull(rect)) { |
| return; |
| } |
| global_state().clipRect(rect, clip_op, is_aa); |
| layer_local_state().clipRect(rect, clip_op, is_aa); |
| if (global_state().is_cull_rect_empty() || |
| layer_local_state().is_cull_rect_empty()) { |
| current_info().is_nop = true; |
| return; |
| } |
| current_info().has_valid_clip = true; |
| checkForDeferredSave(); |
| switch (clip_op) { |
| case ClipOp::kIntersect: |
| Push<ClipIntersectRectOp>(0, rect, is_aa); |
| break; |
| case ClipOp::kDifference: |
| Push<ClipDifferenceRectOp>(0, rect, is_aa); |
| break; |
| } |
| } |
| void DisplayListBuilder::ClipOval(const SkRect& bounds, |
| ClipOp clip_op, |
| bool is_aa) { |
| if (!bounds.isFinite()) { |
| return; |
| } |
| if (current_info().is_nop) { |
| return; |
| } |
| if (current_info().has_valid_clip && |
| clip_op == DlCanvas::ClipOp::kIntersect && |
| layer_local_state().oval_covers_cull(bounds)) { |
| return; |
| } |
| global_state().clipOval(bounds, clip_op, is_aa); |
| layer_local_state().clipOval(bounds, clip_op, is_aa); |
| if (global_state().is_cull_rect_empty() || |
| layer_local_state().is_cull_rect_empty()) { |
| current_info().is_nop = true; |
| return; |
| } |
| current_info().has_valid_clip = true; |
| checkForDeferredSave(); |
| switch (clip_op) { |
| case ClipOp::kIntersect: |
| Push<ClipIntersectOvalOp>(0, bounds, is_aa); |
| break; |
| case ClipOp::kDifference: |
| Push<ClipDifferenceOvalOp>(0, bounds, is_aa); |
| break; |
| } |
| } |
| void DisplayListBuilder::ClipRRect(const SkRRect& rrect, |
| ClipOp clip_op, |
| bool is_aa) { |
| if (current_info().is_nop) { |
| return; |
| } |
| if (rrect.isRect()) { |
| clipRect(rrect.rect(), clip_op, is_aa); |
| return; |
| } |
| if (rrect.isOval()) { |
| clipOval(rrect.rect(), clip_op, is_aa); |
| return; |
| } |
| if (current_info().has_valid_clip && |
| clip_op == DlCanvas::ClipOp::kIntersect && |
| layer_local_state().rrect_covers_cull(rrect)) { |
| return; |
| } |
| global_state().clipRRect(rrect, clip_op, is_aa); |
| layer_local_state().clipRRect(rrect, clip_op, is_aa); |
| if (global_state().is_cull_rect_empty() || |
| layer_local_state().is_cull_rect_empty()) { |
| current_info().is_nop = true; |
| return; |
| } |
| current_info().has_valid_clip = true; |
| checkForDeferredSave(); |
| switch (clip_op) { |
| case ClipOp::kIntersect: |
| Push<ClipIntersectRRectOp>(0, rrect, is_aa); |
| break; |
| case ClipOp::kDifference: |
| Push<ClipDifferenceRRectOp>(0, rrect, is_aa); |
| break; |
| } |
| } |
| void DisplayListBuilder::ClipPath(const SkPath& path, |
| ClipOp clip_op, |
| bool is_aa) { |
| if (current_info().is_nop) { |
| return; |
| } |
| if (!path.isInverseFillType()) { |
| SkRect rect; |
| if (path.isRect(&rect)) { |
| this->clipRect(rect, clip_op, is_aa); |
| return; |
| } |
| if (path.isOval(&rect)) { |
| this->clipOval(rect, clip_op, is_aa); |
| return; |
| } |
| SkRRect rrect; |
| if (path.isRRect(&rrect)) { |
| this->clipRRect(rrect, clip_op, is_aa); |
| return; |
| } |
| } |
| global_state().clipPath(path, clip_op, is_aa); |
| layer_local_state().clipPath(path, clip_op, is_aa); |
| if (global_state().is_cull_rect_empty() || |
| layer_local_state().is_cull_rect_empty()) { |
| current_info().is_nop = true; |
| return; |
| } |
| current_info().has_valid_clip = true; |
| checkForDeferredSave(); |
| switch (clip_op) { |
| case ClipOp::kIntersect: |
| Push<ClipIntersectPathOp>(0, path, is_aa); |
| break; |
| case ClipOp::kDifference: |
| Push<ClipDifferencePathOp>(0, path, is_aa); |
| break; |
| } |
| } |
| |
| bool DisplayListBuilder::QuickReject(const SkRect& bounds) const { |
| return global_state().content_culled(bounds); |
| } |
| |
| void DisplayListBuilder::drawPaint() { |
| OpResult result = PaintResult(current_, kDrawPaintFlags); |
| if (result != OpResult::kNoEffect && AccumulateUnbounded()) { |
| Push<DrawPaintOp>(0); |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| void DisplayListBuilder::DrawPaint(const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawPaintFlags); |
| drawPaint(); |
| } |
| void DisplayListBuilder::DrawColor(DlColor color, DlBlendMode mode) { |
| OpResult result = PaintResult(DlPaint(color).setBlendMode(mode)); |
| if (result != OpResult::kNoEffect && AccumulateUnbounded()) { |
| Push<DrawColorOp>(0, color, mode); |
| CheckLayerOpacityCompatibility(mode); |
| UpdateLayerResult(result, mode); |
| } |
| } |
| void DisplayListBuilder::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<DrawLineOp>(0, p0, p1); |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| void DisplayListBuilder::DrawLine(const SkPoint& p0, |
| const SkPoint& p1, |
| const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawLineFlags); |
| drawLine(p0, p1); |
| } |
| void DisplayListBuilder::drawDashedLine(const DlPoint& p0, |
| const DlPoint& p1, |
| DlScalar on_length, |
| DlScalar off_length) { |
| SkRect bounds = SkRect::MakeLTRB(p0.x, p0.y, p1.x, p1.y).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<DrawDashedLineOp>(0, p0, p1, on_length, off_length); |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| void DisplayListBuilder::DrawDashedLine(const DlPoint& p0, |
| const DlPoint& p1, |
| DlScalar on_length, |
| DlScalar off_length, |
| const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawLineFlags); |
| drawDashedLine(p0, p1, on_length, off_length); |
| } |
| void DisplayListBuilder::drawRect(const SkRect& rect) { |
| DisplayListAttributeFlags flags = kDrawRectFlags; |
| OpResult result = PaintResult(current_, flags); |
| if (result != OpResult::kNoEffect && |
| AccumulateOpBounds(rect.makeSorted(), flags)) { |
| Push<DrawRectOp>(0, rect); |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| void DisplayListBuilder::DrawRect(const SkRect& rect, const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRectFlags); |
| drawRect(rect); |
| } |
| void DisplayListBuilder::drawOval(const SkRect& bounds) { |
| DisplayListAttributeFlags flags = kDrawOvalFlags; |
| OpResult result = PaintResult(current_, flags); |
| if (result != OpResult::kNoEffect && |
| AccumulateOpBounds(bounds.makeSorted(), flags)) { |
| Push<DrawOvalOp>(0, bounds); |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| void DisplayListBuilder::DrawOval(const SkRect& bounds, const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawOvalFlags); |
| drawOval(bounds); |
| } |
| void DisplayListBuilder::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<DrawCircleOp>(0, center, radius); |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| } |
| void DisplayListBuilder::DrawCircle(const SkPoint& center, |
| SkScalar radius, |
| const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawCircleFlags); |
| drawCircle(center, radius); |
| } |
| void DisplayListBuilder::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<DrawRRectOp>(0, rrect); |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| } |
| void DisplayListBuilder::DrawRRect(const SkRRect& rrect, const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRRectFlags); |
| drawRRect(rrect); |
| } |
| void DisplayListBuilder::drawDRRect(const SkRRect& outer, |
| const SkRRect& inner) { |
| DisplayListAttributeFlags flags = kDrawDRRectFlags; |
| OpResult result = PaintResult(current_, flags); |
| if (result != OpResult::kNoEffect && |
| AccumulateOpBounds(outer.getBounds(), flags)) { |
| Push<DrawDRRectOp>(0, outer, inner); |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| void DisplayListBuilder::DrawDRRect(const SkRRect& outer, |
| const SkRRect& inner, |
| const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawDRRectFlags); |
| drawDRRect(outer, inner); |
| } |
| void DisplayListBuilder::drawPath(const SkPath& path) { |
| if (!path.isInverseFillType()) { |
| SkRect rect; |
| if (path.isRect(&rect)) { |
| drawRect(rect); |
| return; |
| } |
| if (path.isOval(&rect)) { |
| drawOval(rect); |
| return; |
| } |
| SkRRect rrect; |
| if (path.isRRect(&rrect)) { |
| drawRRect(rrect); |
| return; |
| } |
| } |
| 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<DrawPathOp>(0, path); |
| CheckLayerOpacityHairlineCompatibility(); |
| UpdateLayerResult(result); |
| } |
| } |
| } |
| void DisplayListBuilder::DrawPath(const SkPath& path, const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawPathFlags); |
| drawPath(path); |
| } |
| |
| void DisplayListBuilder::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<DrawArcOp>(0, bounds, start, sweep, useCenter); |
| if (useCenter) { |
| CheckLayerOpacityHairlineCompatibility(); |
| } else { |
| CheckLayerOpacityCompatibility(); |
| } |
| UpdateLayerResult(result); |
| } |
| } |
| void DisplayListBuilder::DrawArc(const SkRect& bounds, |
| SkScalar start, |
| SkScalar sweep, |
| bool useCenter, |
| const DlPaint& paint) { |
| SetAttributesFromPaint( |
| paint, useCenter ? kDrawArcWithCenterFlags : kDrawArcNoCenterFlags); |
| drawArc(bounds, start, sweep, useCenter); |
| } |
| |
| DisplayListAttributeFlags DisplayListBuilder::FlagsForPointMode( |
| PointMode mode) { |
| switch (mode) { |
| case DlCanvas::PointMode::kPoints: |
| return kDrawPointsAsPointsFlags; |
| case PointMode::kLines: |
| return kDrawPointsAsLinesFlags; |
| case PointMode::kPolygon: |
| return kDrawPointsAsPolygonFlags; |
| } |
| FML_UNREACHABLE(); |
| } |
| void DisplayListBuilder::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); |
| AccumulationRect accumulator; |
| for (size_t i = 0; i < count; i++) { |
| accumulator.accumulate(pts[i]); |
| } |
| SkRect point_bounds = accumulator.bounds(); |
| if (!AccumulateOpBounds(point_bounds, flags)) { |
| return; |
| } |
| |
| void* data_ptr; |
| switch (mode) { |
| case PointMode::kPoints: |
| data_ptr = Push<DrawPointsOp>(bytes, count); |
| break; |
| case PointMode::kLines: |
| data_ptr = Push<DrawLinesOp>(bytes, count); |
| break; |
| case PointMode::kPolygon: |
| data_ptr = Push<DrawPolygonOp>(bytes, 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 |
| current_layer().layer_local_accumulator.record_overlapping_bounds(); |
| // Even though we've eliminated the possibility of opacity peephole |
| // optimizations above, we still set the appropriate flags based on |
| // the rendering attributes in case we solve the overlapping points |
| // problem above. |
| CheckLayerOpacityCompatibility(); |
| UpdateLayerResult(result); |
| } |
| void DisplayListBuilder::DrawPoints(PointMode mode, |
| uint32_t count, |
| const SkPoint pts[], |
| const DlPaint& paint) { |
| SetAttributesFromPaint(paint, FlagsForPointMode(mode)); |
| drawPoints(mode, count, pts); |
| } |
| void DisplayListBuilder::drawVertices( |
| const std::shared_ptr<DlVertices>& vertices, |
| DlBlendMode mode) { |
| DisplayListAttributeFlags flags = kDrawVerticesFlags; |
| OpResult result = PaintResult(current_, flags); |
| if (result != OpResult::kNoEffect && |
| AccumulateOpBounds(vertices->bounds(), flags)) { |
| Push<DrawVerticesOp>(0, vertices, mode); |
| // 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); |
| // Even though we already eliminated opacity peephole optimization |
| // due to the color issues identified above, drawVertices also fails |
| // based on the fact that the vertices are rendered independently |
| // so we cannot guarantee the non-overlapping condition. We record |
| // both conditions in case a solution is found to applying the |
| // colors above - both conditions must be analyzed sufficiently |
| // and implemented accordingly before drawVertices is compatible with |
| // opacity peephole optimizations. |
| current_layer().layer_local_accumulator.record_overlapping_bounds(); |
| } |
| } |
| void DisplayListBuilder::DrawVertices( |
| const std::shared_ptr<DlVertices>& vertices, |
| DlBlendMode mode, |
| const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawVerticesFlags); |
| drawVertices(vertices, mode); |
| } |
| |
| void DisplayListBuilder::drawImage(const sk_sp<DlImage> 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<DrawImageWithAttrOp>(0, image, point, sampling) |
| : Push<DrawImageOp>(0, image, point, sampling); |
| CheckLayerOpacityCompatibility(render_with_attributes); |
| UpdateLayerResult(result, render_with_attributes); |
| is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); |
| } |
| } |
| void DisplayListBuilder::DrawImage(const sk_sp<DlImage>& 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 DisplayListBuilder::drawImageRect(const sk_sp<DlImage> 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<DrawImageRectOp>(0, image, src, dst, sampling, render_with_attributes, |
| constraint); |
| CheckLayerOpacityCompatibility(render_with_attributes); |
| UpdateLayerResult(result, render_with_attributes); |
| is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); |
| } |
| } |
| void DisplayListBuilder::DrawImageRect(const sk_sp<DlImage>& 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 DisplayListBuilder::drawImageNine(const sk_sp<DlImage> 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<DrawImageNineWithAttrOp>(0, image, center, dst, filter) |
| : Push<DrawImageNineOp>(0, image, center, dst, filter); |
| CheckLayerOpacityCompatibility(render_with_attributes); |
| UpdateLayerResult(result, render_with_attributes); |
| is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe(); |
| } |
| } |
| void DisplayListBuilder::DrawImageNine(const sk_sp<DlImage>& 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 DisplayListBuilder::drawAtlas(const sk_sp<DlImage> atlas, |
| const SkRSXform xform[], |
| const SkRect tex[], |
| const DlColor colors[], |
| int count, |
| DlBlendMode mode, |
| DlImageSampling sampling, |
| const SkRect* cull_rect, |
| bool render_with_attributes) { |
| DisplayListAttributeFlags flags = render_with_attributes // |
| ? kDrawAtlasWithPaintFlags |
| : kDrawAtlasFlags; |
| OpResult result = PaintResult(current_, flags); |
| if (result == OpResult::kNoEffect) { |
| return; |
| } |
| SkPoint quad[4]; |
| AccumulationRect accumulator; |
| 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++) { |
| accumulator.accumulate(quad[j]); |
| } |
| } |
| if (accumulator.is_empty() || |
| !AccumulateOpBounds(accumulator.bounds(), flags)) { |
| return; |
| } |
| // Accumulating the bounds might not trip the overlap condition if the |
| // whole atlas operation is separated from other rendering calls, but |
| // since each atlas op is treated as an independent operation, we have |
| // to pass along our locally computed overlap condition for the individual |
| // atlas operations to the layer accumulator. |
| // Note that the above accumulation may falsely trigger the overlapping |
| // state as it is done quad corner by quad corner and an entire quad may |
| // be non-overlapping with the layer bounds, but as we add each point |
| // independently it might expand the bounds on one corner and then flag |
| // the condition when the next corner is added. |
| if (accumulator.overlap_detected()) { |
| current_layer().layer_local_accumulator.record_overlapping_bounds(); |
| } |
| |
| int bytes = count * (sizeof(SkRSXform) + sizeof(SkRect)); |
| void* data_ptr; |
| if (colors != nullptr) { |
| bytes += count * sizeof(DlColor); |
| if (cull_rect != nullptr) { |
| data_ptr = |
| Push<DrawAtlasCulledOp>(bytes, atlas, count, mode, sampling, true, |
| *cull_rect, render_with_attributes); |
| } else { |
| data_ptr = Push<DrawAtlasOp>(bytes, 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<DrawAtlasCulledOp>(bytes, atlas, count, mode, sampling, false, |
| *cull_rect, render_with_attributes); |
| } else { |
| data_ptr = Push<DrawAtlasOp>(bytes, 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, render_with_attributes); |
| is_ui_thread_safe_ = is_ui_thread_safe_ && atlas->isUIThreadSafe(); |
| } |
| void DisplayListBuilder::DrawAtlas(const sk_sp<DlImage>& atlas, |
| const SkRSXform xform[], |
| const SkRect tex[], |
| const DlColor colors[], |
| int count, |
| DlBlendMode mode, |
| DlImageSampling sampling, |
| const SkRect* cull_rect, |
| 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 DisplayListBuilder::DrawDisplayList(const sk_sp<DisplayList> display_list, |
| SkScalar opacity) { |
| if (!std::isfinite(opacity) || opacity <= SK_ScalarNearlyZero || |
| display_list->op_count() == 0 || display_list->bounds().isEmpty() || |
| current_info().is_nop) { |
| return; |
| } |
| const SkRect bounds = display_list->bounds(); |
| bool accumulated; |
| sk_sp<const DlRTree> rtree; |
| if (!rtree_data_.has_value() || !(rtree = display_list->rtree())) { |
| accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); |
| } else { |
| std::list<SkRect> rects = |
| rtree->searchAndConsolidateRects(GetLocalClipBounds(), 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; |
| } |
| } |
| } |
| if (!accumulated) { |
| return; |
| } |
| |
| DlPaint current_paint = current_; |
| Push<DrawDisplayListOp>(0, display_list, |
| opacity < SK_Scalar1 ? opacity : SK_Scalar1); |
| |
| // This depth increment accounts for every draw call in the child |
| // DisplayList and is in addition to the implicit depth increment |
| // that was performed when we pushed the DrawDisplayListOp. The |
| // eventual dispatcher can use or ignore the implicit depth increment |
| // as it sees fit depending on whether it needs to do rendering |
| // before or after the drawDisplayList op, but it must be accounted |
| // for if the depth value accounting is to remain consistent between |
| // the recording and dispatching process. |
| depth_ += display_list->total_depth(); |
| |
| 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, |
| display_list->max_root_blend_mode()); |
| if (display_list->root_has_backdrop_filter()) { |
| current_layer().contains_backdrop_filter = true; |
| } |
| } |
| void DisplayListBuilder::drawTextBlob(const sk_sp<SkTextBlob> 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<DrawTextBlobOp>(0, 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 DisplayListBuilder::DrawTextBlob(const sk_sp<SkTextBlob>& blob, |
| SkScalar x, |
| SkScalar y, |
| const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawTextBlobFlags); |
| drawTextBlob(blob, x, y); |
| } |
| |
| void DisplayListBuilder::drawTextFrame( |
| const std::shared_ptr<impeller::TextFrame>& text_frame, |
| SkScalar x, |
| SkScalar y) { |
| DisplayListAttributeFlags flags = kDrawTextBlobFlags; |
| OpResult result = PaintResult(current_, flags); |
| if (result == OpResult::kNoEffect) { |
| return; |
| } |
| impeller::Rect bounds = text_frame->GetBounds(); |
| SkRect sk_bounds = SkRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(), |
| bounds.GetRight(), bounds.GetBottom()); |
| bool unclipped = AccumulateOpBounds(sk_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<DrawTextFrameOp>(0, text_frame, 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 DisplayListBuilder::DrawTextFrame( |
| const std::shared_ptr<impeller::TextFrame>& text_frame, |
| SkScalar x, |
| SkScalar y, |
| const DlPaint& paint) { |
| SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawTextBlobFlags); |
| drawTextFrame(text_frame, x, y); |
| } |
| |
| void DisplayListBuilder::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<DrawShadowTransparentOccluderOp>(0, path, color, elevation, |
| dpr) |
| : Push<DrawShadowOp>(0, path, color, elevation, dpr); |
| UpdateLayerOpacityCompatibility(false); |
| UpdateLayerResult(result, DlBlendMode::kSrcOver); |
| } |
| } |
| } |
| |
| bool DisplayListBuilder::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.GeometryFlags(is_stroked); |
| |
| 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); |
| } |
| } |
| } |
| } |
| |
| // Color filter does not modify bounds even if it affects transparent |
| // black because it is clipped by the "mask" of the primitive. That |
| // property only comes into play when it is applied to something like |
| // a layer. |
| |
| if (flags.applies_image_filter()) { |
| auto filter = current_.getImageFilterPtr(); |
| if (filter && !filter->map_local_bounds(bounds, bounds)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool DisplayListBuilder::AccumulateUnbounded(const SaveInfo& save) { |
| SkRect global_clip = save.global_state.device_cull_rect(); |
| SkRect layer_clip = save.global_state.local_cull_rect(); |
| if (global_clip.isEmpty() || !save.layer_state.mapAndClipRect(&layer_clip)) { |
| return false; |
| } |
| if (rtree_data_.has_value()) { |
| FML_DCHECK(save.layer_info->global_space_accumulator.is_empty()); |
| rtree_data_->rects.push_back(global_clip); |
| rtree_data_->indices.push_back(op_index_); |
| } else { |
| save.layer_info->global_space_accumulator.accumulate(global_clip); |
| } |
| save.layer_info->layer_local_accumulator.accumulate(layer_clip); |
| return true; |
| } |
| |
| bool DisplayListBuilder::AccumulateOpBounds(SkRect& bounds, |
| DisplayListAttributeFlags flags) { |
| if (AdjustBoundsForPaint(bounds, flags)) { |
| return AccumulateBounds(bounds); |
| } else { |
| return AccumulateUnbounded(); |
| } |
| } |
| |
| bool DisplayListBuilder::AccumulateBounds(const SkRect& bounds, |
| SaveInfo& layer, |
| int id) { |
| if (bounds.isEmpty()) { |
| return false; |
| } |
| SkRect global_bounds; |
| SkRect layer_bounds; |
| if (!layer.global_state.mapAndClipRect(bounds, &global_bounds) || |
| !layer.layer_state.mapAndClipRect(bounds, &layer_bounds)) { |
| return false; |
| } |
| if (rtree_data_.has_value()) { |
| FML_DCHECK(layer.layer_info->global_space_accumulator.is_empty()); |
| if (id >= 0) { |
| rtree_data_->rects.push_back(global_bounds); |
| rtree_data_->indices.push_back(id); |
| } |
| } else { |
| layer.layer_info->global_space_accumulator.accumulate(global_bounds); |
| } |
| layer.layer_info->layer_local_accumulator.accumulate(layer_bounds); |
| return true; |
| } |
| |
| bool DisplayListBuilder::SaveInfo::AccumulateBoundsLocal(const SkRect& bounds) { |
| if (bounds.isEmpty()) { |
| return false; |
| } |
| SkRect local_bounds; |
| if (!layer_state.mapAndClipRect(bounds, &local_bounds)) { |
| return false; |
| } |
| layer_info->layer_local_accumulator.accumulate(local_bounds); |
| return true; |
| } |
| |
| bool DisplayListBuilder::paint_nops_on_transparency() { |
| // SkImageFilter::canComputeFastBounds tests for transparency behavior |
| // This test assumes that the blend mode checked down below will |
| // NOP on transparent black. |
| if (current_.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 DisplayListBuilder::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; |
| } |
| |
| DisplayListBuilder::OpResult DisplayListBuilder::PaintResult( |
| const DlPaint& paint, |
| DisplayListAttributeFlags flags) { |
| if (current_info().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 |