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

Commit e0f86b2

Browse files
authored
[Impeller] Use the scissor to limit all draws by clip coverage. (#51698)
Attempts to improve flutter/flutter#145274. Our new clipping technique paints walls on the depth buffer "in front" of the Entities that will be affected by said clips. So when an intersect clip is drawn (the common case), the clip will cover the whole framebuffer. Depth is divvied up such that deeper clips get drawn _behind_ shallower clips, and so many clips actually don't end up drawing depth across the whole framebuffer. However, if the app does a lot of transitioning from a huge clips to a small clips, a lot of unnecessary depth churn occurs (very common in Flutter -- this happens with both the app bar and floating action button in the counter template, for example). Since progressively deeper layers in the clip coverage stack always subset their parent layers, we can reduce waste for small intersect clips by setting the scissor to the clip coverage rect instead of drawing the clip to the whole screen. Note that this change _does not_ help much with huge/fullscreen clips. Also, we could potentially improve this further by computing much stricter bounds. Rather than just using clip coverage for the scissor, we could intersect it with the union of all draws affected by the clip at the cost of a bit more CPU churn per draw. I don't think that's enough juice for the squeeze though. Before (`Play/AiksTest.CanRenderNestedClips/Metal`): https://github.com/flutter/engine/assets/919017/7858400f-793a-4f7b-a0e4-fa3581198beb After (`Play/AiksTest.CanRenderNestedClips/Metal`): https://github.com/flutter/engine/assets/919017/b2f7c96d-a820-454d-91df-f5fae4976e91
1 parent 6bb4374 commit e0f86b2

File tree

4 files changed

+134
-52
lines changed

4 files changed

+134
-52
lines changed

impeller/entity/entity_pass.cc

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "impeller/entity/inline_pass_context.h"
2626
#include "impeller/geometry/color.h"
2727
#include "impeller/geometry/rect.h"
28+
#include "impeller/geometry/size.h"
2829
#include "impeller/renderer/command_buffer.h"
2930

3031
#ifdef IMPELLER_DEBUG
@@ -752,6 +753,25 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
752753
FML_UNREACHABLE();
753754
}
754755

756+
static void SetClipScissor(std::optional<Rect> clip_coverage,
757+
RenderPass& pass,
758+
Point global_pass_position) {
759+
if constexpr (!ContentContext::kEnableStencilThenCover) {
760+
return;
761+
}
762+
// Set the scissor to the clip coverage area. We do this prior to rendering
763+
// the clip itself and all its contents.
764+
IRect scissor;
765+
if (clip_coverage.has_value()) {
766+
clip_coverage = clip_coverage->Shift(-global_pass_position);
767+
scissor = IRect::RoundOut(clip_coverage.value());
768+
// The scissor rect must not exceed the size of the render target.
769+
scissor = scissor.Intersection(IRect::MakeSize(pass.GetRenderTargetSize()))
770+
.value_or(IRect());
771+
}
772+
pass.SetScissor(scissor);
773+
}
774+
755775
bool EntityPass::RenderElement(Entity& element_entity,
756776
size_t clip_depth_floor,
757777
InlinePassContext& pass_context,
@@ -771,8 +791,10 @@ bool EntityPass::RenderElement(Entity& element_entity,
771791
// Restore any clips that were recorded before the backdrop filter was
772792
// applied.
773793
auto& replay_entities = clip_coverage_stack.GetReplayEntities();
774-
for (const auto& entity : replay_entities) {
775-
if (!entity.Render(renderer, *result.pass)) {
794+
for (const auto& replay : replay_entities) {
795+
SetClipScissor(clip_coverage_stack.CurrentClipCoverage(), *result.pass,
796+
global_pass_position);
797+
if (!replay.entity.Render(renderer, *result.pass)) {
776798
VALIDATION_LOG << "Failed to render entity for clip restore.";
777799
}
778800
}
@@ -826,11 +848,18 @@ bool EntityPass::RenderElement(Entity& element_entity,
826848
element_entity.GetContents()->SetCoverageHint(
827849
Rect::Intersection(element_coverage_hint, current_clip_coverage));
828850

829-
if (!clip_coverage_stack.AppendClipCoverage(clip_coverage, element_entity,
830-
clip_depth_floor,
831-
global_pass_position)) {
832-
// If the entity's coverage change did not change the clip coverage, we
833-
// don't need to render it.
851+
EntityPassClipStack::ClipStateResult clip_state_result =
852+
clip_coverage_stack.ApplyClipState(clip_coverage, element_entity,
853+
clip_depth_floor,
854+
global_pass_position);
855+
856+
if (clip_state_result.clip_did_change) {
857+
// We only need to update the pass scissor if the clip state has changed.
858+
SetClipScissor(clip_coverage_stack.CurrentClipCoverage(), *result.pass,
859+
global_pass_position);
860+
}
861+
862+
if (!clip_state_result.should_render) {
834863
return true;
835864
}
836865

impeller/entity/entity_pass_clip_stack.cc

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,23 @@ EntityPassClipStack::GetClipCoverageLayers() const {
4949
return subpass_state_.back().clip_coverage;
5050
}
5151

52-
bool EntityPassClipStack::AppendClipCoverage(
53-
Contents::ClipCoverage clip_coverage,
52+
EntityPassClipStack::ClipStateResult EntityPassClipStack::ApplyClipState(
53+
Contents::ClipCoverage global_clip_coverage,
5454
Entity& entity,
5555
size_t clip_depth_floor,
5656
Point global_pass_position) {
57+
ClipStateResult result = {.should_render = false, .clip_did_change = false};
58+
5759
auto& subpass_state = GetCurrentSubpassState();
58-
switch (clip_coverage.type) {
60+
switch (global_clip_coverage.type) {
5961
case Contents::ClipCoverage::Type::kNoChange:
6062
break;
6163
case Contents::ClipCoverage::Type::kAppend: {
6264
auto op = CurrentClipCoverage();
6365
subpass_state.clip_coverage.push_back(
64-
ClipCoverageLayer{.coverage = clip_coverage.coverage,
66+
ClipCoverageLayer{.coverage = global_clip_coverage.coverage,
6567
.clip_depth = entity.GetClipDepth() + 1});
68+
result.clip_did_change = true;
6669

6770
FML_DCHECK(subpass_state.clip_coverage.back().clip_depth ==
6871
subpass_state.clip_coverage.front().clip_depth +
@@ -71,14 +74,14 @@ bool EntityPassClipStack::AppendClipCoverage(
7174
if (!op.has_value()) {
7275
// Running this append op won't impact the clip buffer because the
7376
// whole screen is already being clipped, so skip it.
74-
return false;
77+
return result;
7578
}
7679
} break;
7780
case Contents::ClipCoverage::Type::kRestore: {
7881
if (subpass_state.clip_coverage.back().clip_depth <=
7982
entity.GetClipDepth()) {
8083
// Drop clip restores that will do nothing.
81-
return false;
84+
return result;
8285
}
8386

8487
auto restoration_index = entity.GetClipDepth() -
@@ -96,18 +99,19 @@ bool EntityPassClipStack::AppendClipCoverage(
9699
restore_coverage = restore_coverage->Shift(-global_pass_position);
97100
}
98101
subpass_state.clip_coverage.resize(restoration_index + 1);
102+
result.clip_did_change = true;
99103

100104
if constexpr (ContentContext::kEnableStencilThenCover) {
101105
// Skip all clip restores when stencil-then-cover is enabled.
102106
if (subpass_state.clip_coverage.back().coverage.has_value()) {
103-
RecordEntity(entity, clip_coverage.type);
107+
RecordEntity(entity, global_clip_coverage.type, Rect());
104108
}
105-
return false;
109+
return result;
106110
}
107111

108112
if (!subpass_state.clip_coverage.back().coverage.has_value()) {
109113
// Running this restore op won't make anything renderable, so skip it.
110-
return false;
114+
return result;
111115
}
112116

113117
auto restore_contents =
@@ -130,19 +134,23 @@ bool EntityPassClipStack::AppendClipCoverage(
130134
#endif
131135

132136
entity.SetClipDepth(entity.GetClipDepth() - clip_depth_floor);
133-
RecordEntity(entity, clip_coverage.type);
137+
RecordEntity(entity, global_clip_coverage.type,
138+
subpass_state.clip_coverage.back().coverage);
134139

135-
return true;
140+
result.should_render = true;
141+
return result;
136142
}
137143

138144
void EntityPassClipStack::RecordEntity(const Entity& entity,
139-
Contents::ClipCoverage::Type type) {
145+
Contents::ClipCoverage::Type type,
146+
std::optional<Rect> clip_coverage) {
140147
auto& subpass_state = GetCurrentSubpassState();
141148
switch (type) {
142149
case Contents::ClipCoverage::Type::kNoChange:
143150
return;
144151
case Contents::ClipCoverage::Type::kAppend:
145-
subpass_state.rendered_clip_entities.push_back(entity.Clone());
152+
subpass_state.rendered_clip_entities.push_back(
153+
{.entity = entity.Clone(), .clip_coverage = clip_coverage});
146154
break;
147155
case Contents::ClipCoverage::Type::kRestore:
148156
if (!subpass_state.rendered_clip_entities.empty()) {
@@ -157,7 +165,8 @@ EntityPassClipStack::GetCurrentSubpassState() {
157165
return subpass_state_.back();
158166
}
159167

160-
const std::vector<Entity>& EntityPassClipStack::GetReplayEntities() const {
168+
const std::vector<EntityPassClipStack::ReplayResult>&
169+
EntityPassClipStack::GetReplayEntities() const {
161170
return subpass_state_.back().rendered_clip_entities;
162171
}
163172

impeller/entity/entity_pass_clip_stack.h

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#define FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_CLIP_STACK_H_
77

88
#include "impeller/entity/contents/contents.h"
9+
#include "impeller/entity/entity.h"
10+
#include "impeller/geometry/rect.h"
911

1012
namespace impeller {
1113

@@ -21,6 +23,20 @@ struct ClipCoverageLayer {
2123
/// stencil buffer is left in an identical state.
2224
class EntityPassClipStack {
2325
public:
26+
struct ReplayResult {
27+
Entity entity;
28+
std::optional<Rect> clip_coverage;
29+
};
30+
31+
struct ClipStateResult {
32+
/// Whether or not the Entity should be rendered. If false, the Entity may
33+
/// be safely skipped.
34+
bool should_render = false;
35+
/// Whether or not the current clip coverage changed during the call to
36+
/// `ApplyClipState`.
37+
bool clip_did_change = false;
38+
};
39+
2440
/// Create a new [EntityPassClipStack] with an initialized coverage rect.
2541
explicit EntityPassClipStack(const Rect& initial_coverage_rect);
2642

@@ -34,24 +50,27 @@ class EntityPassClipStack {
3450

3551
bool HasCoverage() const;
3652

37-
/// Returns true if entity should be rendered.
38-
bool AppendClipCoverage(Contents::ClipCoverage clip_coverage,
39-
Entity& entity,
40-
size_t clip_depth_floor,
41-
Point global_pass_position);
53+
/// @brief Applies the current clip state to an Entity. If the given Entity
54+
/// is a clip operation, then the clip state is updated accordingly.
55+
ClipStateResult ApplyClipState(Contents::ClipCoverage global_clip_coverage,
56+
Entity& entity,
57+
size_t clip_depth_floor,
58+
Point global_pass_position);
4259

4360
// Visible for testing.
44-
void RecordEntity(const Entity& entity, Contents::ClipCoverage::Type type);
61+
void RecordEntity(const Entity& entity,
62+
Contents::ClipCoverage::Type type,
63+
std::optional<Rect> clip_coverage);
4564

4665
// Visible for testing.
47-
const std::vector<Entity>& GetReplayEntities() const;
66+
const std::vector<ReplayResult>& GetReplayEntities() const;
4867

4968
// Visible for testing.
5069
const std::vector<ClipCoverageLayer> GetClipCoverageLayers() const;
5170

5271
private:
5372
struct SubpassState {
54-
std::vector<Entity> rendered_clip_entities;
73+
std::vector<ReplayResult> rendered_clip_entities;
5574
std::vector<ClipCoverageLayer> clip_coverage;
5675
};
5776

impeller/entity/entity_pass_unittests.cc

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,26 @@ TEST(EntityPassClipStackTest, CanPushAndPopEntities) {
1717
EXPECT_TRUE(recorder.GetReplayEntities().empty());
1818

1919
Entity entity;
20-
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kAppend);
20+
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kAppend,
21+
Rect::MakeLTRB(0, 0, 100, 100));
2122
EXPECT_EQ(recorder.GetReplayEntities().size(), 1u);
2223

23-
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kAppend);
24+
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kAppend,
25+
Rect::MakeLTRB(0, 0, 50, 50));
2426
EXPECT_EQ(recorder.GetReplayEntities().size(), 2u);
25-
26-
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore);
27+
ASSERT_TRUE(recorder.GetReplayEntities()[0].clip_coverage.has_value());
28+
ASSERT_TRUE(recorder.GetReplayEntities()[1].clip_coverage.has_value());
29+
// NOLINTBEGIN(bugprone-unchecked-optional-access)
30+
EXPECT_EQ(recorder.GetReplayEntities()[0].clip_coverage.value(),
31+
Rect::MakeLTRB(0, 0, 100, 100));
32+
EXPECT_EQ(recorder.GetReplayEntities()[1].clip_coverage.value(),
33+
Rect::MakeLTRB(0, 0, 50, 50));
34+
// NOLINTEND(bugprone-unchecked-optional-access)
35+
36+
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore, Rect());
2737
EXPECT_EQ(recorder.GetReplayEntities().size(), 1u);
2838

29-
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore);
39+
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore, Rect());
3040
EXPECT_TRUE(recorder.GetReplayEntities().empty());
3141
}
3242

@@ -37,7 +47,7 @@ TEST(EntityPassClipStackTest, CanPopEntitiesSafely) {
3747
EXPECT_TRUE(recorder.GetReplayEntities().empty());
3848

3949
Entity entity;
40-
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore);
50+
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kRestore, Rect());
4151
EXPECT_TRUE(recorder.GetReplayEntities().empty());
4252
}
4353

@@ -48,7 +58,8 @@ TEST(EntityPassClipStackTest, CanAppendNoChange) {
4858
EXPECT_TRUE(recorder.GetReplayEntities().empty());
4959

5060
Entity entity;
51-
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kNoChange);
61+
recorder.RecordEntity(entity, Contents::ClipCoverage::Type::kNoChange,
62+
Rect());
5263
EXPECT_TRUE(recorder.GetReplayEntities().empty());
5364
}
5465

@@ -61,12 +72,14 @@ TEST(EntityPassClipStackTest, AppendCoverageNoChange) {
6172
EXPECT_EQ(recorder.GetClipCoverageLayers()[0].clip_depth, 0u);
6273

6374
Entity entity;
64-
recorder.AppendClipCoverage(
75+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
6576
Contents::ClipCoverage{
6677
.type = Contents::ClipCoverage::Type::kNoChange,
6778
.coverage = std::nullopt,
6879
},
6980
entity, 0, Point(0, 0));
81+
EXPECT_TRUE(result.should_render);
82+
EXPECT_FALSE(result.clip_did_change);
7083

7184
EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage,
7285
Rect::MakeSize(Size::MakeWH(100, 100)));
@@ -82,12 +95,14 @@ TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverage) {
8295
// Push a clip.
8396
Entity entity;
8497
entity.SetClipDepth(0);
85-
recorder.AppendClipCoverage(
98+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
8699
Contents::ClipCoverage{
87100
.type = Contents::ClipCoverage::Type::kAppend,
88101
.coverage = Rect::MakeLTRB(50, 50, 55, 55),
89102
},
90103
entity, 0, Point(0, 0));
104+
EXPECT_TRUE(result.should_render);
105+
EXPECT_TRUE(result.clip_did_change);
91106

92107
ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 2u);
93108
EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage,
@@ -97,7 +112,7 @@ TEST(EntityPassClipStackTest, AppendAndRestoreClipCoverage) {
97112

98113
// Restore the clip.
99114
entity.SetClipDepth(0);
100-
recorder.AppendClipCoverage(
115+
recorder.ApplyClipState(
101116
Contents::ClipCoverage{
102117
.type = Contents::ClipCoverage::Type::kRestore,
103118
.coverage = Rect::MakeLTRB(50, 50, 55, 55),
@@ -120,12 +135,14 @@ TEST(EntityPassClipStackTest, UnbalancedRestore) {
120135
// Restore the clip.
121136
Entity entity;
122137
entity.SetClipDepth(0);
123-
recorder.AppendClipCoverage(
138+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
124139
Contents::ClipCoverage{
125140
.type = Contents::ClipCoverage::Type::kRestore,
126141
.coverage = Rect::MakeLTRB(50, 50, 55, 55),
127142
},
128143
entity, 0, Point(0, 0));
144+
EXPECT_FALSE(result.should_render);
145+
EXPECT_FALSE(result.clip_did_change);
129146

130147
ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 1u);
131148
EXPECT_EQ(recorder.GetClipCoverageLayers()[0].coverage,
@@ -143,12 +160,16 @@ TEST(EntityPassClipStackTest, ClipAndRestoreWithSubpasses) {
143160
// Push a clip.
144161
Entity entity;
145162
entity.SetClipDepth(0u);
146-
recorder.AppendClipCoverage(
147-
Contents::ClipCoverage{
148-
.type = Contents::ClipCoverage::Type::kAppend,
149-
.coverage = Rect::MakeLTRB(50, 50, 55, 55),
150-
},
151-
entity, 0, Point(0, 0));
163+
{
164+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
165+
Contents::ClipCoverage{
166+
.type = Contents::ClipCoverage::Type::kAppend,
167+
.coverage = Rect::MakeLTRB(50, 50, 55, 55),
168+
},
169+
entity, 0, Point(0, 0));
170+
EXPECT_TRUE(result.should_render);
171+
EXPECT_TRUE(result.clip_did_change);
172+
}
152173

153174
ASSERT_EQ(recorder.GetClipCoverageLayers().size(), 2u);
154175
EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage,
@@ -163,12 +184,16 @@ TEST(EntityPassClipStackTest, ClipAndRestoreWithSubpasses) {
163184
Rect::MakeLTRB(50, 50, 55, 55));
164185

165186
entity.SetClipDepth(1);
166-
recorder.AppendClipCoverage(
167-
Contents::ClipCoverage{
168-
.type = Contents::ClipCoverage::Type::kAppend,
169-
.coverage = Rect::MakeLTRB(54, 54, 55, 55),
170-
},
171-
entity, 0, Point(0, 0));
187+
{
188+
EntityPassClipStack::ClipStateResult result = recorder.ApplyClipState(
189+
Contents::ClipCoverage{
190+
.type = Contents::ClipCoverage::Type::kAppend,
191+
.coverage = Rect::MakeLTRB(54, 54, 55, 55),
192+
},
193+
entity, 0, Point(0, 0));
194+
EXPECT_TRUE(result.should_render);
195+
EXPECT_TRUE(result.clip_did_change);
196+
}
172197

173198
EXPECT_EQ(recorder.GetClipCoverageLayers()[1].coverage,
174199
Rect::MakeLTRB(54, 54, 55, 55));

0 commit comments

Comments
 (0)