blob: c1cdac1fe9b2e5b0dc2e72ede1821575d2b8c027 [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_canvas_to_receiver.h"
#include "flutter/display_list/display_list.h"
#include "flutter/display_list/dl_blend_mode.h"
#include "flutter/display_list/dl_op_flags.h"
#include "flutter/display_list/dl_op_records.h"
#include "flutter/display_list/effects/dl_color_source.h"
#include "flutter/display_list/utils/dl_bounds_accumulator.h"
#include "fml/logging.h"
#include "third_party/skia/include/core/SkScalar.h"
namespace flutter {
DlCanvasToReceiver::DlCanvasToReceiver(
std::shared_ptr<DlCanvasReceiver> receiver)
: receiver_(std::move(receiver)) {
layer_stack_.emplace_back();
current_layer_ = &layer_stack_.back();
}
SkISize DlCanvasToReceiver::GetBaseLayerSize() const {
CheckAlive();
return receiver_->base_device_cull_rect().roundOut().size();
}
SkImageInfo DlCanvasToReceiver::GetImageInfo() const {
CheckAlive();
SkISize size = GetBaseLayerSize();
return SkImageInfo::MakeUnknown(size.width(), size.height());
}
bool DlCanvasToReceiver::SetAttributesFromPaint(
const DlPaint* paint,
const DisplayListAttributeFlags flags) {
if (paint == nullptr) {
return true;
}
bool can_inherit_opacity = true;
if (flags.applies_anti_alias()) {
bool aa = paint->isAntiAlias();
if (current_.isAntiAlias() != aa) {
current_.setAntiAlias(aa);
receiver_->setAntiAlias(aa);
}
}
if (flags.applies_dither()) {
bool dither = paint->isDither();
if (current_.isDither() != dither) {
current_.setDither(dither);
receiver_->setDither(dither);
}
}
if (flags.applies_alpha_or_color()) {
DlColor color = paint->getColor();
if (current_.getColor() != color) {
current_.setColor(color);
receiver_->setColor(color);
}
}
if (flags.applies_blend()) {
DlBlendMode mode = paint->getBlendMode();
if (current_.getBlendMode() != mode) {
current_.setBlendMode(mode);
receiver_->setBlendMode(mode);
}
if (!IsOpacityCompatible(mode)) {
can_inherit_opacity = false;
}
}
if (flags.applies_style()) {
DlDrawStyle style = paint->getDrawStyle();
if (current_.getDrawStyle() != style) {
current_.setDrawStyle(style);
receiver_->setDrawStyle(style);
}
}
if (flags.is_stroked(paint->getDrawStyle())) {
SkScalar width = paint->getStrokeWidth();
if (current_.getStrokeWidth() != width) {
current_.setStrokeWidth(width);
receiver_->setStrokeWidth(width);
}
if (flags.may_have_trouble_with_hairlines() && width <= 0) {
can_inherit_opacity = false;
}
SkScalar miter = paint->getStrokeMiter();
if (current_.getStrokeMiter() != miter) {
current_.setStrokeMiter(miter);
receiver_->setStrokeMiter(miter);
}
DlStrokeCap cap = paint->getStrokeCap();
if (current_.getStrokeCap() != cap) {
current_.setStrokeCap(cap);
receiver_->setStrokeCap(cap);
}
DlStrokeJoin join = paint->getStrokeJoin();
if (current_.getStrokeJoin() != join) {
current_.setStrokeJoin(join);
receiver_->setStrokeJoin(join);
}
}
if (flags.applies_shader()) {
auto source = paint->getColorSourcePtr();
if (NotEquals(current_.getColorSourcePtr(), source)) {
current_.setColorSource(paint->getColorSource());
receiver_->setColorSource(source);
}
}
if (flags.applies_color_filter()) {
bool invert = paint->isInvertColors();
if (current_.isInvertColors() != invert) {
current_.setInvertColors(invert);
receiver_->setInvertColors(invert);
}
auto filter = paint->getColorFilterPtr();
if (NotEquals(current_.getColorFilterPtr(), filter)) {
current_.setColorFilter(paint->getColorFilter());
receiver_->setColorFilter(filter);
}
if (invert || filter) {
can_inherit_opacity = false;
}
}
if (flags.applies_image_filter()) {
auto filter = paint->getImageFilterPtr();
if (NotEquals(current_.getImageFilterPtr(), filter)) {
current_.setImageFilter(paint->getImageFilter());
receiver_->setImageFilter(filter);
}
}
if (flags.applies_path_effect()) {
auto effect = paint->getPathEffectPtr();
if (NotEquals(current_.getPathEffectPtr(), effect)) {
current_.setPathEffect(paint->getPathEffect());
receiver_->setPathEffect(effect);
}
}
if (flags.applies_mask_filter()) {
auto filter = paint->getMaskFilterPtr();
if (NotEquals(current_.getMaskFilterPtr(), filter)) {
current_.setMaskFilter(paint->getMaskFilter());
receiver_->setMaskFilter(filter);
}
}
return can_inherit_opacity;
}
void DlCanvasToReceiver::Save() {
CheckAlive();
bool is_nop = current_layer_->state_is_nop_;
layer_stack_.emplace_back();
current_layer_ = &layer_stack_.back();
current_layer_->state_is_nop_ = is_nop;
receiver_->save();
}
void DlCanvasToReceiver::SaveLayer(const SkRect* bounds,
const DlPaint* paint,
const DlImageFilter* backdrop) {
CheckAlive();
SaveLayerOptions options = paint ? SaveLayerOptions::kWithAttributes //
: SaveLayerOptions::kNoAttributes;
DisplayListAttributeFlags flags = paint ? kSaveLayerWithPaintFlags //
: kSaveLayerFlags;
OpResult result = PaintResult(paint, flags);
if (result == OpResult::kNoEffect) {
Save();
current_layer_->state_is_nop_ = true;
return;
}
bool unbounded = false;
if (backdrop) {
// A backdrop will pre-fill up to the entire surface, bounded by
// the clip.
unbounded = true;
} else if (!paint_nops_on_transparency(paint)) {
// The actual flood of the outer layer clip based on the paint
// 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.
unbounded = true;
}
if (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);
}
auto image_filter = paint ? paint->getImageFilter() : nullptr;
bool layer_can_inherit_opacity = SetAttributesFromPaint(paint, flags);
receiver_->saveLayer(bounds, options, backdrop);
current_layer_->Update(result, layer_can_inherit_opacity);
layer_stack_.emplace_back(true, image_filter);
current_layer_ = &layer_stack_.back();
if (image_filter != nullptr || !layer_can_inherit_opacity) {
// The compatibility computed by SetAttributes 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
// to its children even if they are compatible.
// Also, if a layer cannot itself inherit opacity then it cannot
// pass it along to its children. Arguably this information is
// not interesting because the saveLayer will have informed its
// enclosing layers not to send it an opacity to apply in the
// first place. But some tests check to see if the layer has
// noticed this situation by testing the inheritance property on
// the layer itself. Better tests wouldn't expect this property
// to be recorded there, but we will mark our children incompatible
// to be explicit about this situation.
current_layer_->mark_incompatible();
}
if (image_filter != nullptr) {
// We use |resetCullRect| here because we will be accumulating bounds of
// primitives before applying the filter to those bounds. We might
// encounter a primitive whose bounds are clipped, but whose filtered
// bounds will not be clipped. If the individual rendering ops bounds
// are clipped, it will not contribute to the overall bounds which
// could lead to inaccurate (subset) bounds of the DisplayList.
// We need to reset the cull rect here to avoid this premature clipping.
// The filtered bounds will be clipped to the existing clip rect when
// this layer is restored.
// If bounds is null then the original cull_rect will be used.
receiver_->resetCullRect(bounds);
} else if (bounds != nullptr) {
// Even though Skia claims that the bounds are only a hint, they actually
// use them as the temporary layer bounds during rendering the layer, so
// we set them as if a clip operation were performed.
receiver_->intersectCullRect(*bounds);
}
}
void DlCanvasToReceiver::Restore() {
CheckAlive();
if (layer_stack_.size() > 1) {
// Grab the current layer info before we push the restore
// on the stack.
LayerInfo layer_info = layer_stack_.back();
layer_stack_.pop_back();
current_layer_ = &layer_stack_.back();
if (layer_info.has_layer()) {
bool can_distribute_opacity =
layer_info.has_layer() && layer_info.is_group_opacity_compatible();
receiver_->restoreLayer(layer_info.filter().get(),
layer_info.is_unbounded(),
can_distribute_opacity);
} else {
receiver_->restore();
// For regular save() ops there was no protecting layer so we have to
// accumulate the opacity compatibility values into the enclosing layer.
if (layer_info.cannot_inherit_opacity()) {
current_layer_->mark_incompatible();
} else if (layer_info.has_compatible_op()) {
current_layer_->add_compatible_op();
}
}
}
}
void DlCanvasToReceiver::RestoreToCount(int restore_count) {
CheckAlive();
FML_DCHECK(restore_count <= GetSaveCount());
while (restore_count < GetSaveCount() && GetSaveCount() > 1) {
Restore();
}
}
void DlCanvasToReceiver::Translate(SkScalar tx, SkScalar ty) {
CheckAlive();
if (SkScalarIsFinite(tx) && SkScalarIsFinite(ty) &&
(tx != 0.0 || ty != 0.0)) {
receiver_->translate(tx, ty);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
}
void DlCanvasToReceiver::Scale(SkScalar sx, SkScalar sy) {
CheckAlive();
if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) &&
(sx != 1.0 || sy != 1.0)) {
receiver_->scale(sx, sy);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
}
void DlCanvasToReceiver::Rotate(SkScalar degrees) {
CheckAlive();
if (SkScalarMod(degrees, 360.0) != 0.0) {
receiver_->rotate(degrees);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
}
void DlCanvasToReceiver::Skew(SkScalar sx, SkScalar sy) {
CheckAlive();
if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) &&
(sx != 0.0 || sy != 0.0)) {
receiver_->skew(sx, sy);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
}
// clang-format off
// 2x3 2D affine subset of a 4x4 transform in row major order
void DlCanvasToReceiver::Transform2DAffine(
SkScalar mxx, SkScalar mxy, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myt) {
CheckAlive();
if (SkScalarsAreFinite(mxx, myx) &&
SkScalarsAreFinite(mxy, myy) &&
SkScalarsAreFinite(mxt, myt)) {
if (mxx == 1 && mxy == 0 &&
myx == 0 && myy == 1) {
Translate(mxt, myt);
} else {
receiver_->transform2DAffine(mxx, mxy, mxt,
myx, myy, myt);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
}
}
// full 4x4 transform in row major order
void DlCanvasToReceiver::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) {
CheckAlive();
if ( mxz == 0 &&
myz == 0 &&
mzx == 0 && mzy == 0 && mzz == 1 && mzt == 0 &&
mwx == 0 && mwy == 0 && mwz == 0 && mwt == 1) {
Transform2DAffine(mxx, mxy, mxt,
myx, myy, myt);
} else if (SkScalarsAreFinite(mxx, mxy) && SkScalarsAreFinite(mxz, mxt) &&
SkScalarsAreFinite(myx, myy) && SkScalarsAreFinite(myz, myt) &&
SkScalarsAreFinite(mzx, mzy) && SkScalarsAreFinite(mzz, mzt) &&
SkScalarsAreFinite(mwx, mwy) && SkScalarsAreFinite(mwz, mwt)) {
receiver_->transformFullPerspective(mxx, mxy, mxz, mxt,
myx, myy, myz, myt,
mzx, mzy, mzz, mzt,
mwx, mwy, mwz, mwt);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
}
void DlCanvasToReceiver::TransformReset() {
CheckAlive();
receiver_->transformReset();
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
void DlCanvasToReceiver::Transform(const SkMatrix* matrix) {
CheckAlive();
if (matrix != nullptr) {
if (matrix->hasPerspective()) {
Transform2DAffine(
matrix->getScaleX(), matrix->getSkewX(), matrix->getTranslateX(),
matrix->getSkewY(), matrix->getScaleY(), matrix->getTranslateY());
} else {
TransformFullPerspective(
matrix->rc(0, 0), matrix->rc(0, 1), 0.0f, matrix->rc(0, 2),
matrix->rc(1, 0), matrix->rc(1, 1), 0.0f, matrix->rc(1, 2),
0.0f, 0.0f, 1.0f, 0.0f,
matrix->rc(2, 0), matrix->rc(2, 1), 0.0f, matrix->rc(2, 2));
}
}
}
void DlCanvasToReceiver::Transform(const SkM44* m44) {
CheckAlive();
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));
}
}
// clang-format on
void DlCanvasToReceiver::ClipRect(const SkRect& rect,
ClipOp clip_op,
bool is_aa) {
CheckAlive();
if (!rect.isFinite()) {
return;
}
receiver_->clipRect(rect, clip_op, is_aa);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
void DlCanvasToReceiver::ClipRRect(const SkRRect& rrect,
ClipOp clip_op,
bool is_aa) {
CheckAlive();
if (rrect.isRect()) {
ClipRect(rrect.rect(), clip_op, is_aa);
} else {
receiver_->clipRRect(rrect, clip_op, is_aa);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
}
void DlCanvasToReceiver::ClipPath(const SkPath& path,
ClipOp clip_op,
bool is_aa) {
CheckAlive();
if (!path.isInverseFillType()) {
SkRect rect;
if (path.isRect(&rect)) {
this->ClipRect(rect, clip_op, is_aa);
return;
}
SkRRect rrect;
if (path.isOval(&rect)) {
rrect.setOval(rect);
this->ClipRRect(rrect, clip_op, is_aa);
return;
}
if (path.isRRect(&rrect)) {
this->ClipRRect(rrect, clip_op, is_aa);
return;
}
}
receiver_->clipPath(path, clip_op, is_aa);
if (receiver_->is_nop()) {
current_layer_->state_is_nop_ = true;
}
}
bool DlCanvasToReceiver::QuickReject(const SkRect& bounds) const {
CheckAlive();
return receiver_->content_culled(bounds);
}
void DlCanvasToReceiver::DrawPaint(const DlPaint& paint) {
CheckAlive();
OpResult result = PaintResult(paint, kDrawPaintFlags);
if (result != OpResult::kNoEffect && AccumulateUnbounded()) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, kDrawPaintFlags);
receiver_->drawPaint();
current_layer_->Update(result, can_inherit_opacity);
}
}
void DlCanvasToReceiver::DrawColor(DlColor color, DlBlendMode mode) {
CheckAlive();
// We use DrawPaint flags here because the DrawColor flags indicate
// that they will ignore the values in the paint that we are creating.
// But we need the PaintResult to actually look at this paint object
// so DrawPaint flags tell it to check all the required values.
// Alternately, we could just call |DrawPaint(DlPaint(...));| which
// is the equivalent call and the same thing would happen.
OpResult result =
PaintResult(DlPaint(color).setBlendMode(mode), kDrawPaintFlags);
if (result != OpResult::kNoEffect && AccumulateUnbounded()) {
receiver_->drawColor(color, mode);
current_layer_->Update(result, IsOpacityCompatible(mode));
}
}
void DlCanvasToReceiver::DrawLine(const SkPoint& p0,
const SkPoint& p1,
const DlPaint& paint) {
CheckAlive();
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(paint, flags);
if (result != OpResult::kNoEffect &&
AccumulateOpBounds(bounds, &paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, flags);
receiver_->drawLine(p0, p1);
current_layer_->Update(result, can_inherit_opacity);
}
}
void DlCanvasToReceiver::DrawRect(const SkRect& rect, const DlPaint& paint) {
CheckAlive();
DisplayListAttributeFlags flags = kDrawRectFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect &&
AccumulateOpBounds(rect.makeSorted(), &paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, flags);
receiver_->drawRect(rect);
current_layer_->Update(result, can_inherit_opacity);
}
}
void DlCanvasToReceiver::DrawOval(const SkRect& bounds, const DlPaint& paint) {
CheckAlive();
DisplayListAttributeFlags flags = kDrawOvalFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect &&
AccumulateOpBounds(bounds.makeSorted(), &paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, flags);
receiver_->drawOval(bounds);
current_layer_->Update(result, can_inherit_opacity);
}
}
void DlCanvasToReceiver::DrawCircle(const SkPoint& center,
SkScalar radius,
const DlPaint& paint) {
CheckAlive();
DisplayListAttributeFlags flags = kDrawCircleFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect) {
SkRect bounds = SkRect::MakeLTRB(center.fX - radius, center.fY - radius,
center.fX + radius, center.fY + radius);
if (AccumulateOpBounds(bounds, &paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, flags);
receiver_->drawCircle(center, radius);
current_layer_->Update(result, can_inherit_opacity);
}
}
}
void DlCanvasToReceiver::DrawRRect(const SkRRect& rrect, const DlPaint& paint) {
CheckAlive();
if (rrect.isRect()) {
DrawRect(rrect.rect(), paint);
} else if (rrect.isOval()) {
DrawOval(rrect.rect(), paint);
} else {
DisplayListAttributeFlags flags = kDrawRRectFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect &&
AccumulateOpBounds(rrect.getBounds(), &paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, flags);
receiver_->drawRRect(rrect);
current_layer_->Update(result, can_inherit_opacity);
}
}
}
void DlCanvasToReceiver::DrawDRRect(const SkRRect& outer,
const SkRRect& inner,
const DlPaint& paint) {
CheckAlive();
DisplayListAttributeFlags flags = kDrawDRRectFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect &&
AccumulateOpBounds(outer.getBounds(), &paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, flags);
receiver_->drawDRRect(outer, inner);
current_layer_->Update(result, can_inherit_opacity);
}
}
void DlCanvasToReceiver::DrawPath(const SkPath& path, const DlPaint& paint) {
CheckAlive();
DisplayListAttributeFlags flags = kDrawPathFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect) {
bool is_visible = path.isInverseFillType()
? AccumulateUnbounded()
: AccumulateOpBounds(path.getBounds(), &paint, flags);
if (is_visible) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, flags);
receiver_->drawPath(path);
current_layer_->Update(result, can_inherit_opacity);
}
}
}
void DlCanvasToReceiver::DrawArc(const SkRect& bounds,
SkScalar start,
SkScalar sweep,
bool useCenter,
const DlPaint& paint) {
CheckAlive();
DisplayListAttributeFlags flags = //
useCenter //
? kDrawArcWithCenterFlags
: kDrawArcNoCenterFlags;
OpResult result = PaintResult(paint, 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, &paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(&paint, flags);
receiver_->drawArc(bounds, start, sweep, useCenter);
current_layer_->Update(result, can_inherit_opacity);
}
}
DisplayListAttributeFlags DlCanvasToReceiver::FlagsForPointMode(
PointMode mode) {
switch (mode) {
case DlCanvas::PointMode::kPoints:
return kDrawPointsAsPointsFlags;
case PointMode::kLines:
return kDrawPointsAsLinesFlags;
case PointMode::kPolygon:
return kDrawPointsAsPolygonFlags;
}
FML_UNREACHABLE();
}
void DlCanvasToReceiver::DrawPoints(PointMode mode,
uint32_t count,
const SkPoint pts[],
const DlPaint& paint) {
CheckAlive();
if (count == 0) {
return;
}
DisplayListAttributeFlags flags = FlagsForPointMode(mode);
OpResult result = PaintResult(paint, flags);
if (result == OpResult::kNoEffect) {
return;
}
FML_DCHECK(count < DlOpReceiver::kMaxDrawPointsCount);
RectBoundsAccumulator ptBounds;
for (size_t i = 0; i < count; i++) {
ptBounds.accumulate(pts[i]);
}
SkRect point_bounds = ptBounds.bounds();
if (!AccumulateOpBounds(point_bounds, &paint, flags)) {
return;
}
(void)SetAttributesFromPaint(&paint, flags);
receiver_->drawPoints(mode, count, pts);
// 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_->Update(result, false);
}
void DlCanvasToReceiver::DrawVertices(const DlVertices* vertices,
DlBlendMode mode,
const DlPaint& paint) {
CheckAlive();
DisplayListAttributeFlags flags = kDrawVerticesFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect &&
AccumulateOpBounds(vertices->bounds(), &paint, flags)) {
(void)SetAttributesFromPaint(&paint, flags);
receiver_->drawVertices(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.
current_layer_->Update(result, false);
}
}
void DlCanvasToReceiver::DrawImage(const sk_sp<DlImage>& image,
const SkPoint point,
DlImageSampling sampling,
const DlPaint* paint) {
CheckAlive();
DisplayListAttributeFlags flags = paint ? kDrawImageWithPaintFlags //
: kDrawImageFlags;
OpResult result = PaintResult(paint, flags);
if (result == OpResult::kNoEffect) {
return;
}
SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, //
image->width(), image->height());
if (AccumulateOpBounds(bounds, paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(paint, flags);
receiver_->drawImage(image, point, sampling, paint != nullptr);
current_layer_->Update(result, can_inherit_opacity);
}
}
void DlCanvasToReceiver::DrawImageRect(const sk_sp<DlImage>& image,
const SkRect& src,
const SkRect& dst,
DlImageSampling sampling,
const DlPaint* paint,
SrcRectConstraint constraint) {
CheckAlive();
DisplayListAttributeFlags flags = paint ? kDrawImageRectWithPaintFlags //
: kDrawImageRectFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect && AccumulateOpBounds(dst, paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(paint, flags);
receiver_->drawImageRect(image, src, dst, sampling, paint != nullptr,
constraint);
current_layer_->Update(result, can_inherit_opacity);
}
}
void DlCanvasToReceiver::DrawImageNine(const sk_sp<DlImage>& image,
const SkIRect& center,
const SkRect& dst,
DlFilterMode filter,
const DlPaint* paint) {
CheckAlive();
DisplayListAttributeFlags flags = paint ? kDrawImageNineWithPaintFlags //
: kDrawImageNineFlags;
OpResult result = PaintResult(paint, flags);
if (result != OpResult::kNoEffect && AccumulateOpBounds(dst, paint, flags)) {
bool can_inherit_opacity = SetAttributesFromPaint(paint, flags);
receiver_->drawImageNine(image, center, dst, filter, paint != nullptr);
current_layer_->Update(result, can_inherit_opacity);
}
}
void DlCanvasToReceiver::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) {
CheckAlive();
DisplayListAttributeFlags flags = paint ? kDrawAtlasWithPaintFlags //
: kDrawAtlasFlags;
OpResult result = PaintResult(paint, flags);
if (result == OpResult::kNoEffect) {
return;
}
SkPoint quad[4];
RectBoundsAccumulator atlasBounds;
for (int i = 0; i < count; i++) {
const SkRect& src = tex[i];
xform[i].toQuad(src.width(), src.height(), quad);
for (int j = 0; j < 4; j++) {
atlasBounds.accumulate(quad[j]);
}
}
if (atlasBounds.is_empty() ||
!AccumulateOpBounds(atlasBounds.bounds(), paint, flags)) {
return;
}
(void)SetAttributesFromPaint(paint, flags);
receiver_->drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
cull_rect, paint != nullptr);
// 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.
current_layer_->Update(result, false);
}
void DlCanvasToReceiver::DrawDisplayList(const sk_sp<DisplayList> display_list,
SkScalar opacity) {
CheckAlive();
if (!SkScalarIsFinite(opacity) || opacity <= SK_ScalarNearlyZero ||
display_list->op_count() == 0 || display_list->bounds().isEmpty() ||
current_layer_->state_is_nop_) {
return;
}
const SkRect bounds = display_list->bounds();
bool accumulated;
if (receiver_->wants_granular_bounds()) {
auto rtree = display_list->rtree();
if (rtree) {
std::list<SkRect> rects = rtree->searchAndConsolidateRects(bounds, false);
accumulated = false;
for (const SkRect& rect : rects) {
// TODO (https://github.com/flutter/flutter/issues/114919): Attributes
// are not necessarily `kDrawDisplayListFlags`.
if (AccumulateOpBounds(rect, nullptr, kDrawDisplayListFlags)) {
accumulated = true;
}
}
} else {
accumulated = AccumulateOpBounds(bounds, nullptr, kDrawDisplayListFlags);
}
} else {
accumulated = AccumulateOpBounds(bounds, nullptr, kDrawDisplayListFlags);
}
if (!accumulated) {
return;
}
receiver_->drawDisplayList(display_list,
opacity < SK_Scalar1 ? opacity : SK_Scalar1);
// 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);
current_layer_->Update(
// Nop DisplayLists are eliminated above so we either affect transparent
// pixels or we do not. We should not have [kNoEffect].
display_list->modifies_transparent_black()
? OpResult::kAffectsAll
: OpResult::kPreservesTransparency,
display_list->can_apply_group_opacity());
}
void DlCanvasToReceiver::DrawTextBlob(const sk_sp<SkTextBlob>& blob,
SkScalar x,
SkScalar y,
const DlPaint& paint) {
CheckAlive();
DisplayListAttributeFlags flags = kDrawTextBlobFlags;
OpResult result = PaintResult(paint, flags);
if (result == OpResult::kNoEffect) {
return;
}
bool unclipped =
AccumulateOpBounds(blob->bounds().makeOffset(x, y), &paint, 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) {
(void)SetAttributesFromPaint(&paint, flags);
receiver_->drawTextBlob(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.
current_layer_->Update(result, false);
}
}
void DlCanvasToReceiver::DrawShadow(const SkPath& path,
const DlColor color,
const SkScalar elevation,
bool transparent_occluder,
SkScalar dpr) {
CheckAlive();
OpResult result = PaintResult(DlPaint(color));
if (result != OpResult::kNoEffect) {
SkRect shadow_bounds =
DlCanvas::ComputeShadowBounds(path, elevation, dpr, GetTransform());
if (AccumulateOpBounds(shadow_bounds, nullptr, kDrawShadowFlags)) {
receiver_->drawShadow(path, color, elevation, transparent_occluder, dpr);
current_layer_->Update(result, false);
}
}
}
bool DlCanvasToReceiver::ComputeFilteredBounds(SkRect& bounds,
const DlImageFilter* filter) {
if (filter) {
if (!filter->map_local_bounds(bounds, bounds)) {
return false;
}
}
return true;
}
bool DlCanvasToReceiver::AdjustBoundsForPaint(SkRect& bounds,
const DlPaint* in_paint,
DisplayListAttributeFlags flags) {
if (flags.ignores_paint()) {
return true;
}
const DlPaint& paint = in_paint ? *in_paint : kDefaultPaint_;
if (flags.is_geometric()) {
bool is_stroked = flags.is_stroked(paint.getDrawStyle());
// Path effect occurs before stroking...
DisplayListSpecialGeometryFlags special_flags =
flags.WithPathEffect(paint.getPathEffectPtr(), is_stroked);
if (paint.getPathEffect()) {
auto effect_bounds = paint.getPathEffect()->effect_bounds(bounds);
if (!effect_bounds.has_value()) {
return false;
}
bounds = effect_bounds.value();
}
if (is_stroked) {
// Determine the max multiplier to the stroke width first.
SkScalar pad = 1.0f;
if (paint.getStrokeJoin() == DlStrokeJoin::kMiter &&
special_flags.may_have_acute_joins()) {
pad = std::max(pad, paint.getStrokeMiter());
}
if (paint.getStrokeCap() == DlStrokeCap::kSquare &&
special_flags.may_have_diagonal_caps()) {
pad = std::max(pad, SK_ScalarSqrt2);
}
SkScalar min_stroke_width = 0.01;
pad *= std::max(paint.getStrokeWidth() * 0.5f, min_stroke_width);
bounds.outset(pad, pad);
}
}
if (flags.applies_mask_filter()) {
auto filter = paint.getMaskFilter();
if (filter) {
switch (filter->type()) {
case DlMaskFilterType::kBlur: {
FML_DCHECK(filter->asBlur());
SkScalar mask_sigma_pad = filter->asBlur()->sigma() * 3.0;
bounds.outset(mask_sigma_pad, mask_sigma_pad);
}
}
}
}
if (flags.applies_image_filter()) {
return ComputeFilteredBounds(bounds, paint.getImageFilter().get());
}
return true;
}
bool DlCanvasToReceiver::AccumulateUnbounded() {
return receiver_->accumulateUnboundedForNextOp();
}
bool DlCanvasToReceiver::AccumulateOpBounds(SkRect& bounds,
const DlPaint* paint,
DisplayListAttributeFlags flags) {
if (AdjustBoundsForPaint(bounds, paint, flags)) {
return AccumulateBounds(bounds);
} else {
return AccumulateUnbounded();
}
}
bool DlCanvasToReceiver::AccumulateBounds(SkRect& bounds) {
return receiver_->accumulateLocalBoundsForNextOp(bounds);
}
bool DlCanvasToReceiver::paint_nops_on_transparency(const DlPaint* paint) {
if (paint == nullptr) {
return true;
}
// SkImageFilter::canComputeFastBounds tests for transparency behavior
// This test assumes that the blend mode checked down below will
// NOP on transparent black.
if (paint->getImageFilterPtr() &&
paint->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 (paint->getColorFilterPtr() &&
paint->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 (paint->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 DlCanvasToReceiver::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;
}
DlCanvasToReceiver::OpResult DlCanvasToReceiver::PaintResult(
const DlPaint& paint,
DisplayListAttributeFlags flags) {
if (current_layer_->state_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;
}
DlPaint DlCanvasToReceiver::kDefaultPaint_;
} // namespace flutter