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

Commit 218b416

Browse files
[Impeller] fix clip culling with exp canvas. (#54701)
Fixes performance problem where image filters break clip culling, and lack of clip culling stops the clear color optimization from firing. on the current canvas the cull rect computation is slightly incorrect, as we drop it as soon as we get a image filter. With the new canvas, we have the actual render target sizes, so we can correctly cull without it. After switching to experimental canvas, I will remove the cull rect field from the canvas stack entry - as the clip coverage stack already performs basically the same culling. This fixes the performance issue on the uncached zoom page transition where we lose the clear color optimization too early.
1 parent 053e136 commit 218b416

File tree

5 files changed

+155
-23
lines changed

5 files changed

+155
-23
lines changed

impeller/aiks/experimental_canvas.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "impeller/base/validation.h"
1212
#include "impeller/core/allocator.h"
1313
#include "impeller/core/formats.h"
14+
#include "impeller/entity/contents/clip_contents.h"
1415
#include "impeller/entity/contents/framebuffer_blend_contents.h"
1516
#include "impeller/entity/contents/text_contents.h"
1617
#include "impeller/entity/entity.h"
@@ -442,6 +443,12 @@ void ExperimentalCanvas::SaveLayer(
442443
entry.rendering_mode = Entity::RenderingMode::kSubpassAppendSnapshotTransform;
443444
transform_stack_.emplace_back(entry);
444445

446+
// The current clip aiks clip culling can not handle image filters.
447+
// Remove this once we've migrated to exp canvas and removed it.
448+
if (paint.image_filter) {
449+
transform_stack_.back().cull_rect = std::nullopt;
450+
}
451+
445452
// Start non-collapsed subpasses with a fresh clip coverage stack limited by
446453
// the subpass coverage. This is important because image filters applied to
447454
// save layers may transform the subpass texture after it's rendered,
@@ -764,6 +771,7 @@ void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) {
764771
auto transform = entity.GetTransform();
765772
entity.SetTransform(
766773
Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform);
774+
767775
// Ideally the clip depth would be greater than the current rendering
768776
// depth because any rendering calls that follow this clip operation will
769777
// pre-increment the depth and then be rendering above our clip depth,

impeller/entity/contents/clip_contents.cc

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,11 @@ Contents::ClipCoverage ClipContents::GetClipCoverage(
5353
case Entity::ClipOperation::kDifference:
5454
// This can be optimized further by considering cases when the bounds of
5555
// the current stencil will shrink.
56-
return {.type = ClipCoverage::Type::kAppend,
57-
.coverage = current_clip_coverage};
56+
return {
57+
.type = ClipCoverage::Type::kAppend, //
58+
.is_difference_or_non_square = true, //
59+
.coverage = current_clip_coverage //
60+
};
5861
case Entity::ClipOperation::kIntersect:
5962
if (!geometry_) {
6063
return {.type = ClipCoverage::Type::kAppend, .coverage = std::nullopt};
@@ -64,8 +67,9 @@ Contents::ClipCoverage ClipContents::GetClipCoverage(
6467
return {.type = ClipCoverage::Type::kAppend, .coverage = std::nullopt};
6568
}
6669
return {
67-
.type = ClipCoverage::Type::kAppend,
68-
.coverage = current_clip_coverage->Intersection(coverage.value()),
70+
.type = ClipCoverage::Type::kAppend, //
71+
.is_difference_or_non_square = !geometry_->IsAxisAlignedRect(), //
72+
.coverage = current_clip_coverage->Intersection(coverage.value()), //
6973
};
7074
}
7175
FML_UNREACHABLE();
@@ -91,19 +95,6 @@ bool ClipContents::Render(const ContentContext& renderer,
9195

9296
using VS = ClipPipeline::VertexShader;
9397

94-
if (clip_op_ == Entity::ClipOperation::kIntersect &&
95-
geometry_->IsAxisAlignedRect() &&
96-
entity.GetTransform().IsTranslationScaleOnly()) {
97-
std::optional<Rect> coverage =
98-
geometry_->GetCoverage(entity.GetTransform());
99-
if (coverage.has_value() &&
100-
coverage->Contains(Rect::MakeSize(pass.GetRenderTargetSize()))) {
101-
// Skip axis-aligned intersect clips that cover the whole render target
102-
// since they won't draw anything to the depth buffer.
103-
return true;
104-
}
105-
}
106-
10798
VS::FrameInfo info;
10899
info.depth = GetShaderClipDepth(entity);
109100

impeller/entity/contents/contents.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ class Contents {
3838
enum class Type { kNoChange, kAppend, kRestore };
3939

4040
Type type = Type::kNoChange;
41+
// TODO(jonahwilliams): this should probably use the Entity::ClipOperation
42+
// enum, but that has transitive import errors.
43+
bool is_difference_or_non_square = false;
44+
45+
/// @brief This coverage is the outer coverage of the clip.
46+
///
47+
/// For example, if the clip is a circular clip, this is the rectangle that
48+
/// contains the circle and not the rectangle that is contained within the
49+
/// circle. This means that we cannot use the coverage alone to determine if
50+
/// a clip can be culled, and instead also use the somewhat hacky
51+
/// "is_difference_or_non_square" field.
4152
std::optional<Rect> coverage = std::nullopt;
4253
};
4354

impeller/entity/entity_pass_clip_stack.cc

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ EntityPassClipStack::ClipStateResult EntityPassClipStack::ApplyClipState(
6464
case Contents::ClipCoverage::Type::kNoChange:
6565
break;
6666
case Contents::ClipCoverage::Type::kAppend: {
67-
auto op = CurrentClipCoverage();
67+
auto maybe_coverage = CurrentClipCoverage();
6868

6969
// Compute the previous clip height.
7070
size_t previous_clip_height = 0;
@@ -76,6 +76,24 @@ EntityPassClipStack::ClipStateResult EntityPassClipStack::ApplyClipState(
7676
previous_clip_height = clip_height_floor;
7777
}
7878

79+
if (!maybe_coverage.has_value()) {
80+
// Running this append op won't impact the clip buffer because the
81+
// whole screen is already being clipped, so skip it.
82+
return result;
83+
}
84+
auto op = maybe_coverage.value();
85+
86+
// If the new clip coverage is bigger than the existing coverage for
87+
// intersect clips, we do not need to change the clip region.
88+
if (!global_clip_coverage.is_difference_or_non_square &&
89+
global_clip_coverage.coverage.has_value() &&
90+
global_clip_coverage.coverage.value().Contains(op)) {
91+
subpass_state.clip_coverage.push_back(ClipCoverageLayer{
92+
.coverage = op, .clip_height = previous_clip_height + 1});
93+
94+
return result;
95+
}
96+
7997
subpass_state.clip_coverage.push_back(
8098
ClipCoverageLayer{.coverage = global_clip_coverage.coverage,
8199
.clip_height = previous_clip_height + 1});
@@ -85,11 +103,6 @@ EntityPassClipStack::ClipStateResult EntityPassClipStack::ApplyClipState(
85103
subpass_state.clip_coverage.front().clip_height +
86104
subpass_state.clip_coverage.size() - 1);
87105

88-
if (!op.has_value()) {
89-
// Running this append op won't impact the clip buffer because the
90-
// whole screen is already being clipped, so skip it.
91-
return result;
92-
}
93106
} break;
94107
case Contents::ClipCoverage::Type::kRestore: {
95108
ClipRestoreContents* restore_contents =

impeller/entity/entity_pass_unittests.cc

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,115 @@ TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverage) {
130130
EXPECT_EQ(recorder.GetReplayEntities().size(), 0u);
131131
}
132132

133+
// Append two clip coverages, the second is larger the first. This
134+
// should result in the second clip not requiring any update.
135+
TEST(EntityPassClipStackTest, AppendLargerClipCoverage) {
136+
EntityPassClipStack recorder =
137+
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));
138+
139+
ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u);
140+
141+
// Push a clip.
142+
Entity entity;
143+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
144+
Contents::ClipCoverage{
145+
.type = Contents::ClipCoverage::Type::kAppend,
146+
.coverage = Rect::MakeLTRB(50, 50, 55, 55),
147+
},
148+
entity, 0, Point(0, 0));
149+
EXPECT_TRUE(result.should_render);
150+
EXPECT_TRUE(result.clip_did_change);
151+
152+
// Push a clip with larger coverage than the previous state.
153+
result = recorder.ApplyClipState(
154+
Contents::ClipCoverage{
155+
.type = Contents::ClipCoverage::Type::kAppend,
156+
.coverage = Rect::MakeLTRB(0, 0, 100, 100),
157+
},
158+
entity, 0, Point(0, 0));
159+
160+
EXPECT_FALSE(result.should_render);
161+
EXPECT_FALSE(result.clip_did_change);
162+
}
163+
164+
// Since clip entities return the outer coverage we can only cull axis aligned
165+
// rectangles and intersect clips.
166+
TEST(EntityPassClipStackTest,
167+
AppendLargerClipCoverageWithDifferenceOrNonSquare) {
168+
EntityPassClipStack recorder =
169+
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));
170+
171+
ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u);
172+
173+
// Push a clip.
174+
Entity entity;
175+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
176+
Contents::ClipCoverage{
177+
.type = Contents::ClipCoverage::Type::kAppend,
178+
.coverage = Rect::MakeLTRB(50, 50, 55, 55),
179+
},
180+
entity, 0, Point(0, 0));
181+
EXPECT_TRUE(result.should_render);
182+
EXPECT_TRUE(result.clip_did_change);
183+
184+
// Push a clip with larger coverage than the previous state.
185+
result = recorder.ApplyClipState(
186+
Contents::ClipCoverage{
187+
.type = Contents::ClipCoverage::Type::kAppend,
188+
.is_difference_or_non_square = true,
189+
.coverage = Rect::MakeLTRB(0, 0, 100, 100),
190+
},
191+
entity, 0, Point(0, 0));
192+
193+
EXPECT_TRUE(result.should_render);
194+
EXPECT_TRUE(result.clip_did_change);
195+
}
196+
197+
TEST(EntityPassClipStackTest, AppendDecreasingSizeClipCoverage) {
198+
EntityPassClipStack recorder =
199+
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));
200+
201+
ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u);
202+
203+
// Push Clips that shrink in size. All should be applied.
204+
Entity entity;
205+
206+
for (auto i = 1; i < 20; i++) {
207+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
208+
Contents::ClipCoverage{
209+
.type = Contents::ClipCoverage::Type::kAppend,
210+
.coverage = Rect::MakeLTRB(i, i, 100 - i, 100 - i),
211+
},
212+
entity, 0, Point(0, 0));
213+
EXPECT_TRUE(result.should_render);
214+
EXPECT_TRUE(result.clip_did_change);
215+
EXPECT_EQ(recorder.CurrentClipCoverage(),
216+
Rect::MakeLTRB(i, i, 100 - i, 100 - i));
217+
}
218+
}
219+
220+
TEST(EntityPassClipStackTest, AppendIncreasingSizeClipCoverage) {
221+
EntityPassClipStack recorder =
222+
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));
223+
224+
ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u);
225+
226+
// Push Clips that grow in size. All should be skipped.
227+
Entity entity;
228+
229+
for (auto i = 1; i < 20; i++) {
230+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
231+
Contents::ClipCoverage{
232+
.type = Contents::ClipCoverage::Type::kAppend,
233+
.coverage = Rect::MakeLTRB(0 - i, 0 - i, 100 + i, 100 + i),
234+
},
235+
entity, 0, Point(0, 0));
236+
EXPECT_FALSE(result.should_render);
237+
EXPECT_FALSE(result.clip_did_change);
238+
EXPECT_EQ(recorder.CurrentClipCoverage(), Rect::MakeLTRB(0, 0, 100, 100));
239+
}
240+
}
241+
133242
TEST(EntityPassClipStackTest, UnbalancedRestore) {
134243
EntityPassClipStack recorder =
135244
EntityPassClipStack(Rect::MakeLTRB(0, 0, 100, 100));

0 commit comments

Comments
 (0)