blob: 0bcb16580d2f245e3f58acfa19d56a51593585dd [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 "impeller/aiks/canvas.h"
#include <optional>
#include <utility>
#include "flutter/fml/logging.h"
#include "flutter/fml/trace_event.h"
#include "impeller/aiks/image_filter.h"
#include "impeller/aiks/paint_pass_delegate.h"
#include "impeller/entity/contents/atlas_contents.h"
#include "impeller/entity/contents/clip_contents.h"
#include "impeller/entity/contents/solid_rrect_blur_contents.h"
#include "impeller/entity/contents/text_contents.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/entity/contents/vertices_contents.h"
#include "impeller/entity/geometry/geometry.h"
#include "impeller/geometry/path_builder.h"
namespace impeller {
Canvas::Canvas() {
Initialize(std::nullopt);
}
Canvas::Canvas(Rect cull_rect) {
Initialize(cull_rect);
}
Canvas::Canvas(IRect cull_rect) {
Initialize(Rect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(),
cull_rect.GetRight(), cull_rect.GetBottom()));
}
Canvas::~Canvas() = default;
void Canvas::Initialize(std::optional<Rect> cull_rect) {
initial_cull_rect_ = cull_rect;
base_pass_ = std::make_unique<EntityPass>();
current_pass_ = base_pass_.get();
xformation_stack_.emplace_back(CanvasStackEntry{.cull_rect = cull_rect});
FML_DCHECK(GetSaveCount() == 1u);
FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u);
}
void Canvas::Reset() {
base_pass_ = nullptr;
current_pass_ = nullptr;
xformation_stack_ = {};
}
void Canvas::Save() {
Save(false);
}
void Canvas::Save(bool create_subpass,
BlendMode blend_mode,
const std::shared_ptr<ImageFilter>& backdrop_filter) {
auto entry = CanvasStackEntry{};
entry.xformation = xformation_stack_.back().xformation;
entry.cull_rect = xformation_stack_.back().cull_rect;
entry.clip_depth = xformation_stack_.back().clip_depth;
if (create_subpass) {
entry.rendering_mode = Entity::RenderingMode::kSubpass;
auto subpass = std::make_unique<EntityPass>();
subpass->SetEnableOffscreenCheckerboard(
debug_options.offscreen_texture_checkerboard);
if (backdrop_filter) {
EntityPass::BackdropFilterProc backdrop_filter_proc =
[backdrop_filter = backdrop_filter->Clone()](
const FilterInput::Ref& input, const Matrix& effect_transform,
Entity::RenderingMode rendering_mode) {
auto filter = backdrop_filter->WrapInput(input);
filter->SetEffectTransform(effect_transform);
filter->SetRenderingMode(rendering_mode);
return filter;
};
subpass->SetBackdropFilter(backdrop_filter_proc);
}
subpass->SetBlendMode(blend_mode);
current_pass_ = GetCurrentPass().AddSubpass(std::move(subpass));
current_pass_->SetTransformation(xformation_stack_.back().xformation);
current_pass_->SetClipDepth(xformation_stack_.back().clip_depth);
}
xformation_stack_.emplace_back(entry);
}
bool Canvas::Restore() {
FML_DCHECK(xformation_stack_.size() > 0);
if (xformation_stack_.size() == 1) {
return false;
}
if (xformation_stack_.back().rendering_mode ==
Entity::RenderingMode::kSubpass) {
current_pass_ = GetCurrentPass().GetSuperpass();
FML_DCHECK(current_pass_);
}
bool contains_clips = xformation_stack_.back().contains_clips;
xformation_stack_.pop_back();
if (contains_clips) {
RestoreClip();
}
return true;
}
void Canvas::Concat(const Matrix& xformation) {
xformation_stack_.back().xformation = GetCurrentTransformation() * xformation;
}
void Canvas::PreConcat(const Matrix& xformation) {
xformation_stack_.back().xformation = xformation * GetCurrentTransformation();
}
void Canvas::ResetTransform() {
xformation_stack_.back().xformation = {};
}
void Canvas::Transform(const Matrix& xformation) {
Concat(xformation);
}
const Matrix& Canvas::GetCurrentTransformation() const {
return xformation_stack_.back().xformation;
}
const std::optional<Rect> Canvas::GetCurrentLocalCullingBounds() const {
auto cull_rect = xformation_stack_.back().cull_rect;
if (cull_rect.has_value()) {
Matrix inverse = xformation_stack_.back().xformation.Invert();
cull_rect = cull_rect.value().TransformBounds(inverse);
}
return cull_rect;
}
void Canvas::Translate(const Vector3& offset) {
Concat(Matrix::MakeTranslation(offset));
}
void Canvas::Scale(const Vector2& scale) {
Concat(Matrix::MakeScale(scale));
}
void Canvas::Scale(const Vector3& scale) {
Concat(Matrix::MakeScale(scale));
}
void Canvas::Skew(Scalar sx, Scalar sy) {
Concat(Matrix::MakeSkew(sx, sy));
}
void Canvas::Rotate(Radians radians) {
Concat(Matrix::MakeRotationZ(radians));
}
size_t Canvas::GetSaveCount() const {
return xformation_stack_.size();
}
void Canvas::RestoreToCount(size_t count) {
while (GetSaveCount() > count) {
if (!Restore()) {
return;
}
}
}
void Canvas::DrawPath(const Path& path, const Paint& paint) {
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(paint.CreateContentsForEntity(path)));
GetCurrentPass().AddEntity(entity);
}
void Canvas::DrawPaint(const Paint& paint) {
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.CreateContentsForEntity({}, true));
GetCurrentPass().AddEntity(entity);
}
bool Canvas::AttemptDrawBlurredRRect(const Rect& rect,
Scalar corner_radius,
const Paint& paint) {
Paint new_paint = paint;
if (new_paint.color_source.GetType() != ColorSource::Type::kColor ||
new_paint.style != Paint::Style::kFill) {
return false;
}
if (!new_paint.mask_blur_descriptor.has_value() ||
new_paint.mask_blur_descriptor->style !=
FilterContents::BlurStyle::kNormal) {
return false;
}
// For symmetrically mask blurred solid RRects, absorb the mask blur and use
// a faster SDF approximation.
auto contents = std::make_shared<SolidRRectBlurContents>();
contents->SetColor(new_paint.color);
contents->SetSigma(new_paint.mask_blur_descriptor->sigma);
contents->SetRRect(rect, corner_radius);
new_paint.mask_blur_descriptor = std::nullopt;
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(new_paint.blend_mode);
entity.SetContents(new_paint.WithFilters(std::move(contents)));
GetCurrentPass().AddEntity(entity);
return true;
}
void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) {
if (paint.stroke_cap == Cap::kRound) {
auto path = PathBuilder{}
.AddLine((p0), (p1))
.SetConvexity(Convexity::kConvex)
.TakePath();
Paint stroke_paint = paint;
stroke_paint.style = Paint::Style::kStroke;
DrawPath(path, stroke_paint);
return;
}
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(paint.CreateContentsForGeometry(
Geometry::MakeLine(p0, p1, paint.stroke_width, paint.stroke_cap))));
GetCurrentPass().AddEntity(entity);
}
void Canvas::DrawRect(Rect rect, const Paint& paint) {
if (paint.style == Paint::Style::kStroke) {
DrawPath(PathBuilder{}.AddRect(rect).TakePath(), paint);
return;
}
if (AttemptDrawBlurredRRect(rect, 0, paint)) {
return;
}
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(
paint.CreateContentsForGeometry(Geometry::MakeRect(rect))));
GetCurrentPass().AddEntity(entity);
}
void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) {
if (corner_radii.x == corner_radii.y &&
AttemptDrawBlurredRRect(rect, corner_radii.x, paint)) {
return;
}
auto path = PathBuilder{}
.SetConvexity(Convexity::kConvex)
.AddRoundedRect(rect, corner_radii)
.SetBounds(rect)
.TakePath();
if (paint.style == Paint::Style::kFill) {
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(
paint.CreateContentsForGeometry(Geometry::MakeFillPath(path))));
GetCurrentPass().AddEntity(entity);
return;
}
DrawPath(path, paint);
}
void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
Size half_size(radius, radius);
if (AttemptDrawBlurredRRect(
Rect::MakeOriginSize(center - half_size, half_size * 2), radius,
paint)) {
return;
}
auto circle_path =
PathBuilder{}
.AddCircle(center, radius)
.SetConvexity(Convexity::kConvex)
.SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius,
center.x + radius, center.y + radius))
.TakePath();
DrawPath(circle_path, paint);
}
void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) {
ClipGeometry(Geometry::MakeFillPath(path), clip_op);
if (clip_op == Entity::ClipOperation::kIntersect) {
auto bounds = path.GetBoundingBox();
if (bounds.has_value()) {
IntersectCulling(bounds.value());
}
}
}
void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) {
auto geometry = Geometry::MakeRect(rect);
auto& cull_rect = xformation_stack_.back().cull_rect;
if (clip_op == Entity::ClipOperation::kIntersect && //
cull_rect.has_value() && //
geometry->CoversArea(xformation_stack_.back().xformation, *cull_rect) //
) {
return; // This clip will do nothing, so skip it.
}
ClipGeometry(std::move(geometry), clip_op);
switch (clip_op) {
case Entity::ClipOperation::kIntersect:
IntersectCulling(rect);
break;
case Entity::ClipOperation::kDifference:
SubtractCulling(rect);
break;
}
}
void Canvas::ClipRRect(const Rect& rect,
Point corner_radii,
Entity::ClipOperation clip_op) {
auto path = PathBuilder{}
.SetConvexity(Convexity::kConvex)
.AddRoundedRect(rect, corner_radii)
.SetBounds(rect)
.TakePath();
auto size = rect.GetSize();
// Does the rounded rect have a flat part on the top/bottom or left/right?
bool flat_on_TB = corner_radii.x * 2 < size.width;
bool flat_on_LR = corner_radii.y * 2 < size.height;
std::optional<Rect> inner_rect = (flat_on_LR && flat_on_TB)
? rect.Expand(-corner_radii)
: std::make_optional<Rect>();
auto geometry = Geometry::MakeFillPath(path, inner_rect);
auto& cull_rect = xformation_stack_.back().cull_rect;
if (clip_op == Entity::ClipOperation::kIntersect && //
cull_rect.has_value() && //
geometry->CoversArea(xformation_stack_.back().xformation, *cull_rect) //
) {
return; // This clip will do nothing, so skip it.
}
ClipGeometry(std::move(geometry), clip_op);
switch (clip_op) {
case Entity::ClipOperation::kIntersect:
IntersectCulling(rect);
break;
case Entity::ClipOperation::kDifference:
if (corner_radii.x <= 0.0 || corner_radii.y <= 0) {
SubtractCulling(rect);
} else {
// We subtract the inner "tall" and "wide" rectangle pieces
// that fit inside the corners which cover the greatest area
// without involving the curved corners
// Since this is a subtract operation, we can subtract each
// rectangle piece individually without fear of interference.
if (flat_on_TB) {
SubtractCulling(rect.Expand({-corner_radii.x, 0.0}));
}
if (flat_on_LR) {
SubtractCulling(rect.Expand({0.0, -corner_radii.y}));
}
}
break;
}
}
void Canvas::ClipGeometry(std::unique_ptr<Geometry> geometry,
Entity::ClipOperation clip_op) {
auto contents = std::make_shared<ClipContents>();
contents->SetGeometry(std::move(geometry));
contents->SetClipOperation(clip_op);
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetContents(std::move(contents));
entity.SetClipDepth(GetClipDepth());
GetCurrentPass().AddEntity(entity);
++xformation_stack_.back().clip_depth;
xformation_stack_.back().contains_clips = true;
}
void Canvas::IntersectCulling(Rect clip_rect) {
clip_rect = clip_rect.TransformBounds(GetCurrentTransformation());
std::optional<Rect>& cull_rect = xformation_stack_.back().cull_rect;
if (cull_rect.has_value()) {
cull_rect = cull_rect
.value() //
.Intersection(clip_rect) //
.value_or(Rect{});
} else {
cull_rect = clip_rect;
}
}
void Canvas::SubtractCulling(Rect clip_rect) {
std::optional<Rect>& cull_rect = xformation_stack_.back().cull_rect;
if (cull_rect.has_value()) {
clip_rect = clip_rect.TransformBounds(GetCurrentTransformation());
cull_rect = cull_rect
.value() //
.Cutout(clip_rect) //
.value_or(Rect{});
}
// else (no cull) diff (any clip) is non-rectangular
}
void Canvas::RestoreClip() {
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
// This path is empty because ClipRestoreContents just generates a quad that
// takes up the full render target.
entity.SetContents(std::make_shared<ClipRestoreContents>());
entity.SetClipDepth(GetClipDepth());
GetCurrentPass().AddEntity(entity);
}
void Canvas::DrawPoints(std::vector<Point> points,
Scalar radius,
const Paint& paint,
PointStyle point_style) {
if (radius <= 0) {
return;
}
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(paint.CreateContentsForGeometry(
Geometry::MakePointField(std::move(points), radius,
/*round=*/point_style == PointStyle::kRound))));
GetCurrentPass().AddEntity(entity);
}
void Canvas::DrawPicture(const Picture& picture) {
if (!picture.pass) {
return;
}
// Clone the base pass and account for the CTM updates.
auto pass = picture.pass->Clone();
pass->IterateAllElements([&](auto& element) -> bool {
if (auto entity = std::get_if<Entity>(&element)) {
entity->IncrementStencilDepth(GetClipDepth());
entity->SetTransformation(GetCurrentTransformation() *
entity->GetTransformation());
return true;
}
if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
subpass->get()->SetClipDepth(subpass->get()->GetClipDepth() +
GetClipDepth());
return true;
}
FML_UNREACHABLE();
});
GetCurrentPass().AddSubpassInline(std::move(pass));
RestoreClip();
}
void Canvas::DrawImage(const std::shared_ptr<Image>& image,
Point offset,
const Paint& paint,
SamplerDescriptor sampler) {
if (!image) {
return;
}
const auto source = Rect::MakeSize(image->GetSize());
const auto dest = source.Shift(offset);
DrawImageRect(image, source, dest, paint, std::move(sampler));
}
void Canvas::DrawImageRect(const std::shared_ptr<Image>& image,
Rect source,
Rect dest,
const Paint& paint,
SamplerDescriptor sampler) {
if (!image || source.IsEmpty() || dest.IsEmpty()) {
return;
}
auto size = image->GetSize();
if (size.IsEmpty()) {
return;
}
auto contents = TextureContents::MakeRect(dest);
contents->SetTexture(image->GetTexture());
contents->SetSourceRect(source);
contents->SetSamplerDescriptor(std::move(sampler));
contents->SetOpacity(paint.color.alpha);
contents->SetDeferApplyingOpacity(paint.HasColorFilter());
Entity entity;
entity.SetBlendMode(paint.blend_mode);
entity.SetClipDepth(GetClipDepth());
entity.SetContents(paint.WithFilters(contents));
entity.SetTransformation(GetCurrentTransformation());
GetCurrentPass().AddEntity(entity);
}
Picture Canvas::EndRecordingAsPicture() {
Picture picture;
picture.pass = std::move(base_pass_);
Reset();
Initialize(initial_cull_rect_);
return picture;
}
EntityPass& Canvas::GetCurrentPass() {
FML_DCHECK(current_pass_ != nullptr);
return *current_pass_;
}
size_t Canvas::GetClipDepth() const {
return xformation_stack_.back().clip_depth;
}
void Canvas::SaveLayer(const Paint& paint,
std::optional<Rect> bounds,
const std::shared_ptr<ImageFilter>& backdrop_filter) {
TRACE_EVENT0("flutter", "Canvas::saveLayer");
Save(true, paint.blend_mode, backdrop_filter);
auto& new_layer_pass = GetCurrentPass();
new_layer_pass.SetBoundsLimit(bounds);
// Only apply opacity peephole on default blending.
if (paint.blend_mode == BlendMode::kSourceOver) {
new_layer_pass.SetDelegate(
std::make_shared<OpacityPeepholePassDelegate>(paint));
} else {
new_layer_pass.SetDelegate(std::make_shared<PaintPassDelegate>(paint));
}
}
void Canvas::DrawTextFrame(const std::shared_ptr<TextFrame>& text_frame,
Point position,
const Paint& paint) {
Entity entity;
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
auto text_contents = std::make_shared<TextContents>();
text_contents->SetTextFrame(text_frame);
text_contents->SetColor(paint.color);
entity.SetTransformation(GetCurrentTransformation() *
Matrix::MakeTranslation(position));
// TODO(bdero): This mask blur application is a hack. It will always wind up
// doing a gaussian blur that affects the color source itself
// instead of just the mask. The color filter text support
// needs to be reworked in order to interact correctly with
// mask filters.
// https://github.com/flutter/flutter/issues/133297
entity.SetContents(
paint.WithFilters(paint.WithMaskBlur(std::move(text_contents), true)));
GetCurrentPass().AddEntity(entity);
}
static bool UseColorSourceContents(
const std::shared_ptr<VerticesGeometry>& vertices,
const Paint& paint) {
// If there are no vertex color or texture coordinates. Or if there
// are vertex coordinates then only if the contents are an image or
// a solid color.
if (vertices->HasVertexColors()) {
return false;
}
if (vertices->HasTextureCoordinates() &&
(paint.color_source.GetType() == ColorSource::Type::kImage ||
paint.color_source.GetType() == ColorSource::Type::kColor)) {
return true;
}
return !vertices->HasTextureCoordinates();
}
void Canvas::DrawVertices(const std::shared_ptr<VerticesGeometry>& vertices,
BlendMode blend_mode,
const Paint& paint) {
// Override the blend mode with kDestination in order to match the behavior
// of Skia's SK_LEGACY_IGNORE_DRAW_VERTICES_BLEND_WITH_NO_SHADER flag, which
// is enabled when the Flutter engine builds Skia.
if (paint.color_source.GetType() == ColorSource::Type::kColor) {
blend_mode = BlendMode::kDestination;
}
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
// If there are no vertex color or texture coordinates. Or if there
// are vertex coordinates then only if the contents are an image.
if (UseColorSourceContents(vertices, paint)) {
auto contents = paint.CreateContentsForGeometry(vertices);
entity.SetContents(paint.WithFilters(std::move(contents)));
GetCurrentPass().AddEntity(entity);
return;
}
auto src_paint = paint;
src_paint.color = paint.color.WithAlpha(1.0);
std::shared_ptr<Contents> src_contents =
src_paint.CreateContentsForGeometry(vertices);
if (vertices->HasTextureCoordinates()) {
// If the color source has an intrinsic size, then we use that to
// create the src contents as a simplification. Otherwise we use
// the extent of the texture coordinates to determine how large
// the src contents should be. If neither has a value we fall back
// to using the geometry coverage data.
Rect src_coverage;
auto size = src_contents->GetColorSourceSize();
if (size.has_value()) {
src_coverage = Rect::MakeXYWH(0, 0, size->width, size->height);
} else {
auto cvg = vertices->GetCoverage(Matrix{});
FML_CHECK(cvg.has_value());
src_coverage =
// Covered by FML_CHECK.
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
vertices->GetTextureCoordinateCoverge().value_or(cvg.value());
}
src_contents =
src_paint.CreateContentsForGeometry(Geometry::MakeRect(src_coverage));
}
auto contents = std::make_shared<VerticesContents>();
contents->SetAlpha(paint.color.alpha);
contents->SetBlendMode(blend_mode);
contents->SetGeometry(vertices);
contents->SetSourceContents(std::move(src_contents));
entity.SetContents(paint.WithFilters(std::move(contents)));
GetCurrentPass().AddEntity(entity);
}
void Canvas::DrawAtlas(const std::shared_ptr<Image>& atlas,
std::vector<Matrix> transforms,
std::vector<Rect> texture_coordinates,
std::vector<Color> colors,
BlendMode blend_mode,
SamplerDescriptor sampler,
std::optional<Rect> cull_rect,
const Paint& paint) {
if (!atlas) {
return;
}
std::shared_ptr<AtlasContents> contents = std::make_shared<AtlasContents>();
contents->SetColors(std::move(colors));
contents->SetTransforms(std::move(transforms));
contents->SetTextureCoordinates(std::move(texture_coordinates));
contents->SetTexture(atlas->GetTexture());
contents->SetSamplerDescriptor(std::move(sampler));
contents->SetBlendMode(blend_mode);
contents->SetCullRect(cull_rect);
contents->SetAlpha(paint.color.alpha);
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetClipDepth(GetClipDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(contents));
GetCurrentPass().AddEntity(entity);
}
} // namespace impeller