Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
move clip skipping logic into clip stack.
  • Loading branch information
jonahwilliams committed Aug 28, 2024
commit 9464c0035777c1254e8d94b876a96bdc0563441a
15 changes: 3 additions & 12 deletions impeller/aiks/experimental_canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,8 @@ void ExperimentalCanvas::SaveLayer(
entry.rendering_mode = Entity::RenderingMode::kSubpassAppendSnapshotTransform;
transform_stack_.emplace_back(entry);

// The DisplayList bounds/rtree doesn't account for filters applied to parent
// layers, and so sub-DisplayLists are getting culled as if no filters are
// applied.
// See also: https://github.com/flutter/flutter/issues/139294
// The current clip aiks clip culling can not handle image filters.
// Remove this once we've migrated to exp canvas and removed it.
if (paint.image_filter) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we switch to exp canvas we can delete the cull rect and just use the clip stack which is 99% the same data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the intention here is that if something outside of a DL applies an image filter then you need to cull the sub-dl by the "GetSourceCoverage" of that filter. No, it doesn't take that into account, the caller specifying the cull rect was supposed to...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wait, layers. We do expand the bounds of sub-layers by a filter applied on a parent layer...

void DisplayListBuilder::TransferLayerBounds(const SkRect& content_bounds) {

If rtree, then here:

if (AdjustRTreeRects(rtree_data_.value(), *filter, matrix, clip,

And if no rtree, then here:

if (!filter->map_device_bounds(global_ibounds, matrix, global_ibounds)) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't really accurate. I updated it to document that the cull rect logic gets deleted with new canvas.

transform_stack_.back().cull_rect = std::nullopt;
}
Expand Down Expand Up @@ -761,6 +759,7 @@ void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) {
auto transform = entity.GetTransform();
entity.SetTransform(
Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform);

// Ideally the clip depth would be greater than the current rendering
// depth because any rendering calls that follow this clip operation will
// pre-increment the depth and then be rendering above our clip depth,
Expand All @@ -783,14 +782,6 @@ void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) {
current_clip_coverage->Shift(-GetGlobalPassPosition());
}

// Skip rendering the clip if it is fully contained by the current render
// target.
if (entity.GetContents()->CanSkip(
render_passes_.back().inline_pass_context->GetTexture()->GetSize(),
entity.GetTransform())) {
return;
}

auto clip_coverage = entity.GetClipCoverage(current_clip_coverage);
if (clip_coverage.coverage.has_value()) {
clip_coverage.coverage =
Expand Down
18 changes: 0 additions & 18 deletions impeller/entity/contents/clip_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,6 @@ bool ClipContents::CanInheritOpacity(const Entity& entity) const {

void ClipContents::SetInheritedOpacity(Scalar opacity) {}

bool ClipContents::CanSkip(ISize render_pass_size, const Matrix& ctm) const {
if (clip_op_ == Entity::ClipOperation::kIntersect &&
geometry_->IsAxisAlignedRect() && ctm.IsTranslationScaleOnly()) {
std::optional<Rect> coverage = geometry_->GetCoverage(ctm);
if (coverage.has_value() &&
coverage->Contains(Rect::MakeSize(render_pass_size))) {
// Skip axis-aligned intersect clips that cover the whole render target
// since they won't draw anything to the depth buffer.
return true;
}
}
return false;
}

bool ClipContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
Expand All @@ -105,10 +91,6 @@ bool ClipContents::Render(const ContentContext& renderer,

using VS = ClipPipeline::VertexShader;

if (CanSkip(pass.GetRenderTargetSize(), entity.GetTransform())) {
return true;
}

VS::FrameInfo info;
info.depth = GetShaderClipDepth(entity);

Expand Down
2 changes: 0 additions & 2 deletions impeller/entity/contents/clip_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ class ClipContents final : public Contents {

void SetClipOperation(Entity::ClipOperation clip_op);

bool CanSkip(ISize render_pass_size, const Matrix& transform) const override;

// |Contents|
std::optional<Rect> GetCoverage(const Entity& entity) const override;

Expand Down
4 changes: 0 additions & 4 deletions impeller/entity/contents/contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,4 @@ void Contents::SetColorSourceSize(Size size) {
color_source_size_ = size;
}

bool Contents::CanSkip(ISize render_pass_size, const Matrix& transform) const {
return false;
}

} // namespace impeller
7 changes: 0 additions & 7 deletions impeller/entity/contents/contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,6 @@ class Contents {
///
void SetCoverageHint(std::optional<Rect> coverage_hint);

//----------------------------------------------------------------------------
/// @brief Whether this entity can be entirely skipped during rendering.
///
/// TODO(jonahwilliams): remove this method which was only added for clipping
/// once experimental canvas lands.
virtual bool CanSkip(ISize render_pass_size, const Matrix& transform) const;

const std::optional<Rect>& GetCoverageHint() const;

//----------------------------------------------------------------------------
Expand Down
24 changes: 18 additions & 6 deletions impeller/entity/entity_pass_clip_stack.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ EntityPassClipStack::ClipStateResult EntityPassClipStack::ApplyClipState(
case Contents::ClipCoverage::Type::kNoChange:
break;
case Contents::ClipCoverage::Type::kAppend: {
auto op = CurrentClipCoverage();
auto maybe_coverage = CurrentClipCoverage();

// Compute the previous clip height.
size_t previous_clip_height = 0;
Expand All @@ -72,6 +72,23 @@ EntityPassClipStack::ClipStateResult EntityPassClipStack::ApplyClipState(
previous_clip_height = clip_height_floor;
}

if (!maybe_coverage.has_value()) {
// Running this append op won't impact the clip buffer because the
// whole screen is already being clipped, so skip it.
return result;
}
auto op = maybe_coverage.value();

// If the new clip coverage is bigger than the existing coverage, we
// do not need to change the clip region.
if (global_clip_coverage.coverage.has_value() &&
global_clip_coverage.coverage.value().Contains(op)) {
subpass_state.clip_coverage.push_back(ClipCoverageLayer{
.coverage = op, .clip_height = previous_clip_height + 1});

return result;
}

subpass_state.clip_coverage.push_back(
ClipCoverageLayer{.coverage = global_clip_coverage.coverage,
.clip_height = previous_clip_height + 1});
Expand All @@ -81,11 +98,6 @@ EntityPassClipStack::ClipStateResult EntityPassClipStack::ApplyClipState(
subpass_state.clip_coverage.front().clip_height +
subpass_state.clip_coverage.size() - 1);

if (!op.has_value()) {
// Running this append op won't impact the clip buffer because the
// whole screen is already being clipped, so skip it.
return result;
}
} break;
case Contents::ClipCoverage::Type::kRestore: {
ClipRestoreContents* restore_contents =
Expand Down
76 changes: 76 additions & 0 deletions impeller/entity/entity_pass_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,82 @@ TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverage) {
EXPECT_EQ(recorder.GetReplayEntities().size(), 0u);
}

// Append two clip coverages, the second is larger the first. This
// should result in the second clip not requiring any update.
TEST(EntityPassClipStackTest, AppendLargerClipCoverage) {
EntityPassClipStack recorder =
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));

ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u);

// Push a clip.
Entity entity;
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
Contents::ClipCoverage{
.type = Contents::ClipCoverage::Type::kAppend,
.coverage = Rect::MakeLTRB(50, 50, 55, 55),
},
entity, 0, Point(0, 0));
EXPECT_TRUE(result.should_render);
EXPECT_TRUE(result.clip_did_change);

// Push a clip with larger coverage than the previous state.
result = recorder.ApplyClipState(
Contents::ClipCoverage{
.type = Contents::ClipCoverage::Type::kAppend,
.coverage = Rect::MakeLTRB(0, 0, 100, 100),
},
entity, 0, Point(0, 0));

EXPECT_FALSE(result.should_render);
EXPECT_FALSE(result.clip_did_change);
}

TEST(EntityPassClipStackTest, AppendDecreasingSizeClipCoverage) {
EntityPassClipStack recorder =
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));

ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u);

// Push Clips that shrink in size. All should be applied.
Entity entity;

for (auto i = 1; i < 20; i++) {
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
Contents::ClipCoverage{
.type = Contents::ClipCoverage::Type::kAppend,
.coverage = Rect::MakeLTRB(i, i, 100 - i, 100 - i),
},
entity, 0, Point(0, 0));
EXPECT_TRUE(result.should_render);
EXPECT_TRUE(result.clip_did_change);
EXPECT_EQ(recorder.CurrentClipCoverage(),
Rect::MakeLTRB(i, i, 100 - i, 100 - i));
}
}

TEST(EntityPassClipStackTest, AppendIncreasingSizeClipCoverage) {
EntityPassClipStack recorder =
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));

ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u);

// Push Clips that grow in size. All should be skipped.
Entity entity;

for (auto i = 1; i < 20; i++) {
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
Contents::ClipCoverage{
.type = Contents::ClipCoverage::Type::kAppend,
.coverage = Rect::MakeLTRB(0 - i, 0 - i, 100 + i, 100 + i),
},
entity, 0, Point(0, 0));
EXPECT_FALSE(result.should_render);
EXPECT_FALSE(result.clip_did_change);
EXPECT_EQ(recorder.CurrentClipCoverage(), Rect::MakeLTRB(0, 0, 100, 100));
}
}

TEST(EntityPassClipStackTest, UnbalancedRestore) {
EntityPassClipStack recorder =
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));
Expand Down
29 changes: 0 additions & 29 deletions impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1532,35 +1532,6 @@ TEST_P(EntityTest, ClipContentsShouldRenderIsCorrect) {
}
}

TEST_P(EntityTest, ClipContentsCanSkip) {
// Intersect Clips can be skipped if the render target coverage is fully
// inside of them.

auto clip = std::make_shared<ClipContents>();
clip->SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(0, 0, 100, 100)));
clip->SetClipOperation(Entity::ClipOperation::kIntersect);

// Fully Contained
EXPECT_TRUE(clip->CanSkip(ISize::MakeWH(50, 50), {}));

// Not Intersect
clip->SetClipOperation(Entity::ClipOperation::kDifference);
EXPECT_FALSE(clip->CanSkip(ISize::MakeWH(50, 50), {}));

// Not Contained
clip->SetClipOperation(Entity::ClipOperation::kIntersect);
EXPECT_FALSE(clip->CanSkip(ISize::MakeWH(200, 200), {}));

// Not Scale Translate
clip->SetClipOperation(Entity::ClipOperation::kIntersect);
EXPECT_FALSE(
clip->CanSkip(ISize::MakeWH(50, 50), Matrix::MakeRotationX(Radians(2))));

// Not Axis Aligned Rectangle.
clip->SetGeometry(Geometry::MakeOval(Rect::MakeLTRB(0, 0, 50, 50)));
EXPECT_FALSE(clip->CanSkip(ISize::MakeWH(50, 50), {}));
}

TEST_P(EntityTest, ClipContentsGetClipCoverageIsCorrect) {
// Intersection: No stencil coverage, no geometry.
{
Expand Down