Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 756e78b

Browse files
committed
make cull_rect std::optional and add unit tests of Canvas clip bounds methods
1 parent a8b1db1 commit 756e78b

File tree

8 files changed

+457
-49
lines changed

8 files changed

+457
-49
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
../../../flutter/impeller/.gitignore
121121
../../../flutter/impeller/README.md
122122
../../../flutter/impeller/aiks/aiks_unittests.cc
123+
../../../flutter/impeller/aiks/canvas_unittests.cc
123124
../../../flutter/impeller/archivist/archivist_unittests.cc
124125
../../../flutter/impeller/base/README.md
125126
../../../flutter/impeller/base/base_unittests.cc

impeller/aiks/BUILD.gn

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ impeller_component("aiks_playground") {
4747

4848
impeller_component("aiks_unittests") {
4949
testonly = true
50-
sources = [ "aiks_unittests.cc" ]
50+
sources = [
51+
"aiks_unittests.cc",
52+
"canvas_unittests.cc",
53+
]
5154
deps = [
5255
":aiks",
5356
":aiks_playground",

impeller/aiks/canvas.cc

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
namespace impeller {
2424

2525
Canvas::Canvas() {
26-
Initialize(Rect::MakeLTRB(-1e9, -1e9, 1e9, 1e9));
26+
Initialize(std::nullopt);
2727
}
2828

2929
Canvas::Canvas(Rect cull_rect) {
@@ -37,11 +37,11 @@ Canvas::Canvas(IRect cull_rect) {
3737

3838
Canvas::~Canvas() = default;
3939

40-
void Canvas::Initialize(Rect cull_rect) {
40+
void Canvas::Initialize(std::optional<Rect> cull_rect) {
4141
initial_cull_rect_ = cull_rect;
4242
base_pass_ = std::make_unique<EntityPass>();
4343
current_pass_ = base_pass_.get();
44-
xformation_stack_.emplace_back(CanvasStackEntry{.clip_bounds = cull_rect});
44+
xformation_stack_.emplace_back(CanvasStackEntry{.cull_rect = cull_rect});
4545
lazy_glyph_atlas_ = std::make_shared<LazyGlyphAtlas>();
4646
FML_DCHECK(GetSaveCount() == 1u);
4747
FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u);
@@ -64,7 +64,7 @@ void Canvas::Save(
6464
std::optional<EntityPass::BackdropFilterProc> backdrop_filter) {
6565
auto entry = CanvasStackEntry{};
6666
entry.xformation = xformation_stack_.back().xformation;
67-
entry.clip_bounds = xformation_stack_.back().clip_bounds;
67+
entry.cull_rect = xformation_stack_.back().cull_rect;
6868
entry.stencil_depth = xformation_stack_.back().stencil_depth;
6969
if (create_subpass) {
7070
entry.is_subpass = true;
@@ -120,9 +120,13 @@ const Matrix& Canvas::GetCurrentTransformation() const {
120120
return xformation_stack_.back().xformation;
121121
}
122122

123-
const Rect Canvas::GetCurrentLocalClipBounds() const {
124-
Matrix inverse = xformation_stack_.back().xformation.Invert();
125-
return xformation_stack_.back().clip_bounds.TransformBounds(inverse);
123+
const std::optional<Rect> Canvas::GetCurrentLocalCullingBounds() const {
124+
auto cull_rect = xformation_stack_.back().cull_rect;
125+
if (cull_rect.has_value()) {
126+
Matrix inverse = xformation_stack_.back().xformation.Invert();
127+
cull_rect = cull_rect.value().TransformBounds(inverse);
128+
}
129+
return cull_rect;
126130
}
127131

128132
void Canvas::Translate(const Vector3& offset) {
@@ -273,22 +277,61 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
273277
}
274278

275279
void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) {
276-
ClipGeometry(Geometry::MakeFillPath(path), clip_op, path.GetBoundingBox());
280+
ClipGeometry(Geometry::MakeFillPath(path), clip_op);
281+
if (clip_op == Entity::ClipOperation::kIntersect) {
282+
auto bounds = path.GetBoundingBox();
283+
if (bounds.has_value()) {
284+
IntersectCulling(bounds.value());
285+
}
286+
}
277287
}
278288

279289
void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) {
280-
ClipGeometry(Geometry::MakeRect(rect), clip_op, rect);
290+
ClipGeometry(Geometry::MakeRect(rect), clip_op);
291+
switch (clip_op) {
292+
case Entity::ClipOperation::kIntersect:
293+
IntersectCulling(rect);
294+
break;
295+
case Entity::ClipOperation::kDifference:
296+
SubtractCulling(rect);
297+
break;
298+
}
281299
}
282300

283301
void Canvas::ClipRRect(const Rect& rect,
284302
Scalar corner_radius,
285303
Entity::ClipOperation clip_op) {
286-
ClipGeometry(Geometry::MakeRRect(rect, corner_radius), clip_op, rect);
304+
ClipGeometry(Geometry::MakeRRect(rect, corner_radius), clip_op);
305+
switch (clip_op) {
306+
case Entity::ClipOperation::kIntersect:
307+
IntersectCulling(rect);
308+
break;
309+
case Entity::ClipOperation::kDifference:
310+
if (corner_radius <= 0) {
311+
SubtractCulling(rect);
312+
} else {
313+
// We subtract the inner "tall" and "wide" rectangle pieces
314+
// that fit inside the corners which cover the greatest area
315+
// without involving the curved corners
316+
// Since this is a subtract operation, we can subtract each
317+
// rectangle piece individually without fear of interference.
318+
if (corner_radius * 2 < rect.size.width) {
319+
SubtractCulling(Rect::MakeLTRB(
320+
rect.GetLeft() + corner_radius, rect.GetTop(),
321+
rect.GetRight() - corner_radius, rect.GetBottom()));
322+
}
323+
if (corner_radius * 2 < rect.size.height) {
324+
SubtractCulling(Rect::MakeLTRB(
325+
rect.GetLeft(), rect.GetTop() + corner_radius, //
326+
rect.GetRight(), rect.GetBottom() - corner_radius));
327+
}
328+
}
329+
break;
330+
}
287331
}
288332

289333
void Canvas::ClipGeometry(std::unique_ptr<Geometry> geometry,
290-
Entity::ClipOperation clip_op,
291-
std::optional<Rect> geometry_bounds) {
334+
Entity::ClipOperation clip_op) {
292335
auto contents = std::make_shared<ClipContents>();
293336
contents->SetGeometry(std::move(geometry));
294337
contents->SetClipOperation(clip_op);
@@ -300,23 +343,35 @@ void Canvas::ClipGeometry(std::unique_ptr<Geometry> geometry,
300343

301344
GetCurrentPass().AddEntity(entity);
302345

303-
if (geometry_bounds.has_value()) {
304-
if (clip_op == Entity::ClipOperation::kIntersect) {
305-
Rect transformed =
306-
geometry_bounds.value().TransformBounds(GetCurrentTransformation());
307-
auto new_bounds =
308-
xformation_stack_.back().clip_bounds.Intersection(transformed);
309-
xformation_stack_.back().clip_bounds = new_bounds.value_or(Rect{});
310-
}
311-
// else if kDifference we could chop off a side of the clip_bounds if
312-
// the subtracted geometry was a rectangle and it spans the height or
313-
// width of the current bounds.
314-
}
315-
316346
++xformation_stack_.back().stencil_depth;
317347
xformation_stack_.back().contains_clips = true;
318348
}
319349

350+
void Canvas::IntersectCulling(Rect clip_rect) {
351+
clip_rect = clip_rect.TransformBounds(GetCurrentTransformation());
352+
std::optional<Rect>& cull_rect = xformation_stack_.back().cull_rect;
353+
if (cull_rect.has_value()) {
354+
cull_rect = cull_rect
355+
.value() //
356+
.Intersection(clip_rect) //
357+
.value_or(Rect{});
358+
} else {
359+
cull_rect = clip_rect;
360+
}
361+
}
362+
363+
void Canvas::SubtractCulling(Rect clip_rect) {
364+
std::optional<Rect>& cull_rect = xformation_stack_.back().cull_rect;
365+
if (cull_rect.has_value()) {
366+
clip_rect = clip_rect.TransformBounds(GetCurrentTransformation());
367+
cull_rect = cull_rect
368+
.value() //
369+
.Cutout(clip_rect) //
370+
.value_or(Rect{});
371+
}
372+
// else (no cull) diff (any clip) is non-rectangular
373+
}
374+
320375
void Canvas::RestoreClip() {
321376
Entity entity;
322377
entity.SetTransformation(GetCurrentTransformation());

impeller/aiks/canvas.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ namespace impeller {
2828

2929
class Entity;
3030

31+
struct CanvasStackEntry {
32+
Matrix xformation;
33+
// |cull_rect| is conservative screen-space bounds of the clipped output area
34+
std::optional<Rect> cull_rect;
35+
size_t stencil_depth = 0u;
36+
bool is_subpass = false;
37+
bool contains_clips = false;
38+
};
39+
3140
class Canvas {
3241
public:
3342
struct DebugOptions {
@@ -61,7 +70,7 @@ class Canvas {
6170

6271
const Matrix& GetCurrentTransformation() const;
6372

64-
const Rect GetCurrentLocalClipBounds() const;
73+
const std::optional<Rect> GetCurrentLocalCullingBounds() const;
6574

6675
void ResetTransform();
6776

@@ -141,9 +150,9 @@ class Canvas {
141150
EntityPass* current_pass_ = nullptr;
142151
std::deque<CanvasStackEntry> xformation_stack_;
143152
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;
144-
Rect initial_cull_rect_;
153+
std::optional<Rect> initial_cull_rect_;
145154

146-
void Initialize(Rect cull_rect);
155+
void Initialize(std::optional<Rect> cull_rect);
147156

148157
void Reset();
149158

@@ -152,8 +161,10 @@ class Canvas {
152161
size_t GetStencilDepth() const;
153162

154163
void ClipGeometry(std::unique_ptr<Geometry> geometry,
155-
Entity::ClipOperation clip_op,
156-
std::optional<Rect> geometry_bounds);
164+
Entity::ClipOperation clip_op);
165+
166+
void IntersectCulling(Rect clip_bounds);
167+
void SubtractCulling(Rect clip_bounds);
157168

158169
void Save(bool create_subpass,
159170
BlendMode = BlendMode::kSourceOver,

0 commit comments

Comments
 (0)