blob: 387455327e85fb5cf19fb20a12064c4ca9704a6d [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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(&current_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(&current_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