Skip to content

Commit 9b2cbc1

Browse files
bderodnfield
authored andcommitted
Support nested clips & clip state restoration (#14)
Clip restoration works with a single draw call: - When clip paths are added, they increase the stencil height only if the stencil matches the current depth. So higher depths are always a subset of lower depths. - When popping the canvas stack, an entity is appended to run a draw call which max bounds the stencil to the previous depth. Fixes flutter#98631.
1 parent 4220ef3 commit 9b2cbc1

File tree

9 files changed

+141
-27
lines changed

9 files changed

+141
-27
lines changed

impeller/aiks/aiks_unittests.cc

+13
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ TEST_F(AiksTest, CanRenderClips) {
106106
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
107107
}
108108

109+
TEST_F(AiksTest, CanRenderNestedClips) {
110+
Canvas canvas;
111+
Paint paint;
112+
paint.color = Color::Fuchsia();
113+
canvas.Save();
114+
canvas.ClipPath(PathBuilder{}.AddCircle({200, 400}, 300).TakePath());
115+
canvas.Restore();
116+
canvas.ClipPath(PathBuilder{}.AddCircle({600, 400}, 300).TakePath());
117+
canvas.ClipPath(PathBuilder{}.AddCircle({400, 600}, 300).TakePath());
118+
canvas.DrawRect(Rect::MakeXYWH(200, 200, 400, 400), paint);
119+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
120+
}
121+
109122
TEST_F(AiksTest, CanSaveLayerStandalone) {
110123
Canvas canvas;
111124

impeller/aiks/canvas.cc

+23-6
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ bool Canvas::Restore() {
4545
current_pass_ = GetCurrentPass().GetSuperpass();
4646
FML_DCHECK(current_pass_);
4747
}
48+
49+
bool contains_clips = xformation_stack_.back().contains_clips;
4850
xformation_stack_.pop_back();
51+
52+
if (contains_clips) {
53+
RestoreClip();
54+
}
55+
4956
return true;
5057
}
5158

@@ -129,8 +136,6 @@ void Canvas::SaveLayer(Paint paint, std::optional<Rect> bounds) {
129136
}
130137

131138
void Canvas::ClipPath(Path path) {
132-
IncrementStencilDepth();
133-
134139
Entity entity;
135140
entity.SetTransformation(GetCurrentTransformation());
136141
entity.SetPath(std::move(path));
@@ -139,6 +144,22 @@ void Canvas::ClipPath(Path path) {
139144
entity.SetAddsToCoverage(false);
140145

141146
GetCurrentPass().AddEntity(std::move(entity));
147+
148+
++xformation_stack_.back().stencil_depth;
149+
xformation_stack_.back().contains_clips = true;
150+
}
151+
152+
void Canvas::RestoreClip() {
153+
Entity entity;
154+
entity.SetTransformation(GetCurrentTransformation());
155+
// This path is empty because ClipRestoreContents just generates a quad that
156+
// takes up the full render target.
157+
entity.SetPath({});
158+
entity.SetContents(std::make_shared<ClipRestoreContents>());
159+
entity.SetStencilDepth(GetStencilDepth());
160+
entity.SetAddsToCoverage(false);
161+
162+
GetCurrentPass().AddEntity(std::move(entity));
142163
}
143164

144165
void Canvas::DrawShadow(Path path, Color color, Scalar elevation) {}
@@ -213,10 +234,6 @@ EntityPass& Canvas::GetCurrentPass() {
213234
return *current_pass_;
214235
}
215236

216-
void Canvas::IncrementStencilDepth() {
217-
++xformation_stack_.back().stencil_depth;
218-
}
219-
220237
size_t Canvas::GetStencilDepth() const {
221238
return xformation_stack_.back().stencil_depth;
222239
}

impeller/aiks/canvas.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ class Canvas {
8787

8888
EntityPass& GetCurrentPass();
8989

90-
void IncrementStencilDepth();
91-
9290
size_t GetStencilDepth() const;
9391

9492
void Save(bool create_subpass);
9593

94+
void RestoreClip();
95+
9696
FML_DISALLOW_COPY_AND_ASSIGN(Canvas);
9797
};
9898

impeller/entity/content_context.cc

+36-17
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,44 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
2323
std::make_unique<SolidStrokePipeline>(*context_);
2424

2525
// Pipelines that are variants of the base pipelines with custom descriptors.
26+
// TODO(98684): Rework this API to allow fetching the descriptor without
27+
// waiting for the pipeline to build.
2628
if (auto solid_fill_pipeline = solid_fill_pipelines_[{}]->WaitAndGet()) {
27-
auto clip_pipeline_descriptor = solid_fill_pipeline->GetDescriptor();
28-
clip_pipeline_descriptor.SetLabel("Clip Pipeline");
29-
// Write to the stencil buffer.
30-
StencilAttachmentDescriptor stencil0;
31-
stencil0.stencil_compare = CompareFunction::kGreaterEqual;
32-
stencil0.depth_stencil_pass = StencilOperation::kSetToReferenceValue;
33-
clip_pipeline_descriptor.SetStencilAttachmentDescriptors(stencil0);
34-
// Disable write to all color attachments.
35-
auto color_attachments =
36-
clip_pipeline_descriptor.GetColorAttachmentDescriptors();
37-
for (auto& color_attachment : color_attachments) {
38-
color_attachment.second.write_mask =
39-
static_cast<uint64_t>(ColorWriteMask::kNone);
29+
// Clip pipeline.
30+
{
31+
auto clip_pipeline_descriptor = solid_fill_pipeline->GetDescriptor();
32+
clip_pipeline_descriptor.SetLabel("Clip Pipeline");
33+
// Write to the stencil buffer.
34+
StencilAttachmentDescriptor stencil0;
35+
stencil0.stencil_compare = CompareFunction::kEqual;
36+
stencil0.depth_stencil_pass = StencilOperation::kIncrementClamp;
37+
clip_pipeline_descriptor.SetStencilAttachmentDescriptors(stencil0);
38+
// Disable write to all color attachments.
39+
auto color_attachments =
40+
clip_pipeline_descriptor.GetColorAttachmentDescriptors();
41+
for (auto& color_attachment : color_attachments) {
42+
color_attachment.second.write_mask =
43+
static_cast<uint64_t>(ColorWriteMask::kNone);
44+
}
45+
clip_pipeline_descriptor.SetColorAttachmentDescriptors(
46+
std::move(color_attachments));
47+
clip_pipelines_[{}] =
48+
std::make_unique<ClipPipeline>(*context_, clip_pipeline_descriptor);
49+
}
50+
51+
// Clip restoration pipeline.
52+
{
53+
auto clip_pipeline_descriptor =
54+
clip_pipelines_[{}]->WaitAndGet()->GetDescriptor();
55+
clip_pipeline_descriptor.SetLabel("Clip Restoration Pipeline");
56+
// Write to the stencil buffer.
57+
StencilAttachmentDescriptor stencil0;
58+
stencil0.stencil_compare = CompareFunction::kLess;
59+
stencil0.depth_stencil_pass = StencilOperation::kSetToReferenceValue;
60+
clip_pipeline_descriptor.SetStencilAttachmentDescriptors(stencil0);
61+
clip_restoration_pipelines_[{}] = std::make_unique<ClipPipeline>(
62+
*context_, std::move(clip_pipeline_descriptor));
4063
}
41-
clip_pipeline_descriptor.SetColorAttachmentDescriptors(
42-
std::move(color_attachments));
43-
clip_pipelines_[{}] = std::make_unique<ClipPipeline>(
44-
*context_, std::move(clip_pipeline_descriptor));
4564
} else {
4665
return;
4766
}

impeller/entity/content_context.h

+5
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ class ContentContext {
7777
return GetPipeline(clip_pipelines_, opts);
7878
}
7979

80+
std::shared_ptr<Pipeline> GetClipRestorePipeline(Options opts) const {
81+
return GetPipeline(clip_restoration_pipelines_, opts);
82+
}
83+
8084
std::shared_ptr<Context> GetContext() const;
8185

8286
private:
@@ -94,6 +98,7 @@ class ContentContext {
9498
mutable Variants<TexturePipeline> texture_pipelines_;
9599
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
96100
mutable Variants<ClipPipeline> clip_pipelines_;
101+
mutable Variants<ClipPipeline> clip_restoration_pipelines_;
97102

98103
static void ApplyOptionsToDescriptor(PipelineDescriptor& desc,
99104
const Options& options) {

impeller/entity/contents.cc

+44-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "impeller/renderer/sampler_library.h"
1616
#include "impeller/renderer/surface.h"
1717
#include "impeller/renderer/tessellator.h"
18+
#include "impeller/renderer/vertex_buffer.h"
1819
#include "impeller/renderer/vertex_buffer_builder.h"
1920

2021
namespace impeller {
@@ -385,7 +386,7 @@ bool ClipContents::Render(const ContentContext& renderer,
385386
Command cmd;
386387
cmd.label = "Clip";
387388
cmd.pipeline = renderer.GetClipPipeline(OptionsFromPass(pass));
388-
cmd.stencil_reference = entity.GetStencilDepth() + 1u;
389+
cmd.stencil_reference = entity.GetStencilDepth();
389390
cmd.BindVertices(
390391
CreateSolidFillVertices(entity.GetPath(), pass.GetTransientsBuffer()));
391392

@@ -400,4 +401,46 @@ bool ClipContents::Render(const ContentContext& renderer,
400401
return true;
401402
}
402403

404+
/*******************************************************************************
405+
******* ClipRestoreContents
406+
******************************************************************************/
407+
408+
ClipRestoreContents::ClipRestoreContents() = default;
409+
410+
ClipRestoreContents::~ClipRestoreContents() = default;
411+
412+
bool ClipRestoreContents::Render(const ContentContext& renderer,
413+
const Entity& entity,
414+
RenderPass& pass) const {
415+
using VS = ClipPipeline::VertexShader;
416+
417+
Command cmd;
418+
cmd.label = "Clip Restore";
419+
cmd.pipeline = renderer.GetClipRestorePipeline(OptionsFromPass(pass));
420+
cmd.stencil_reference = entity.GetStencilDepth();
421+
422+
// Create a rect that covers the whole render target.
423+
auto size = pass.GetRenderTargetSize();
424+
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
425+
vtx_builder.AddVertices({
426+
{Point(0.0, 0.0)},
427+
{Point(size.width, 0.0)},
428+
{Point(size.width, size.height)},
429+
{Point(0.0, 0.0)},
430+
{Point(size.width, size.height)},
431+
{Point(0.0, size.height)},
432+
});
433+
cmd.BindVertices(vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer()));
434+
435+
VS::FrameInfo info;
436+
// The color really doesn't matter.
437+
info.color = Color::SkyBlue();
438+
info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize());
439+
440+
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(info));
441+
442+
pass.AddCommand(std::move(cmd));
443+
return true;
444+
}
445+
403446
} // namespace impeller

impeller/entity/contents.h

+15
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,19 @@ class ClipContents final : public Contents {
152152
FML_DISALLOW_COPY_AND_ASSIGN(ClipContents);
153153
};
154154

155+
class ClipRestoreContents final : public Contents {
156+
public:
157+
ClipRestoreContents();
158+
159+
~ClipRestoreContents();
160+
161+
// |Contents|
162+
bool Render(const ContentContext& renderer,
163+
const Entity& entity,
164+
RenderPass& pass) const override;
165+
166+
private:
167+
FML_DISALLOW_COPY_AND_ASSIGN(ClipRestoreContents);
168+
};
169+
155170
} // namespace impeller

impeller/entity/entity_pass.h

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ struct CanvasStackEntry {
7373
Matrix xformation;
7474
size_t stencil_depth = 0u;
7575
bool is_subpass = false;
76+
bool contains_clips = false;
7677
};
7778

7879
} // namespace impeller

impeller/renderer/pipeline_builder.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "flutter/fml/macros.h"
99
#include "impeller/base/base.h"
1010
#include "impeller/renderer/context.h"
11+
#include "impeller/renderer/formats.h"
1112
#include "impeller/renderer/pipeline_descriptor.h"
1213
#include "impeller/renderer/shader_library.h"
1314
#include "impeller/renderer/vertex_descriptor.h"
@@ -107,7 +108,7 @@ struct PipelineBuilder {
107108
// Setup default stencil buffer descriptions.
108109
{
109110
StencilAttachmentDescriptor stencil0;
110-
stencil0.stencil_compare = CompareFunction::kLessEqual;
111+
stencil0.stencil_compare = CompareFunction::kEqual;
111112
desc.SetStencilAttachmentDescriptors(stencil0);
112113
desc.SetStencilPixelFormat(PixelFormat::kDefaultStencil);
113114
}

0 commit comments

Comments
 (0)