Skip to content

Commit eb7b7d1

Browse files
bderodnfield
authored andcommitted
Add difference clipping (flutter#104)
1 parent 0aaa84e commit eb7b7d1

File tree

9 files changed

+149
-63
lines changed

9 files changed

+149
-63
lines changed

impeller/aiks/aiks_unittests.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,43 @@ TEST_F(AiksTest, CanRenderNestedClips) {
123123
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
124124
}
125125

126+
TEST_F(AiksTest, CanRenderDifferenceClips) {
127+
Paint paint;
128+
Canvas canvas;
129+
canvas.Translate({400, 400});
130+
131+
// Limit drawing to face circle with a clip.
132+
canvas.ClipPath(PathBuilder{}.AddCircle(Point(), 200).TakePath());
133+
canvas.Save();
134+
135+
// Cut away eyes/mouth using difference clips.
136+
canvas.ClipPath(PathBuilder{}.AddCircle({-100, -50}, 30).TakePath(),
137+
Entity::ClipOperation::kDifference);
138+
canvas.ClipPath(PathBuilder{}.AddCircle({100, -50}, 30).TakePath(),
139+
Entity::ClipOperation::kDifference);
140+
canvas.ClipPath(PathBuilder{}
141+
.AddQuadraticCurve({-100, 50}, {0, 150}, {100, 50})
142+
.TakePath(),
143+
Entity::ClipOperation::kDifference);
144+
145+
// Draw a huge yellow rectangle to prove the clipping works.
146+
paint.color = Color::Yellow();
147+
canvas.DrawRect(Rect::MakeXYWH(-1000, -1000, 2000, 2000), paint);
148+
149+
// Remove the difference clips and draw hair that partially covers the eyes.
150+
canvas.Restore();
151+
paint.color = Color::Maroon();
152+
canvas.DrawPath(PathBuilder{}
153+
.MoveTo({200, -200})
154+
.HorizontalLineTo(-200)
155+
.VerticalLineTo(-40)
156+
.CubicCurveTo({0, -40}, {0, -80}, {200, -80})
157+
.TakePath(),
158+
paint);
159+
160+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
161+
}
162+
126163
TEST_F(AiksTest, ClipsUseCurrentTransform) {
127164
std::array<Color, 5> colors = {Color::White(), Color::Black(),
128165
Color::SkyBlue(), Color::Red(),

impeller/aiks/canvas.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,14 @@ void Canvas::SaveLayer(Paint paint, std::optional<Rect> bounds) {
151151
}
152152
}
153153

154-
void Canvas::ClipPath(Path path) {
154+
void Canvas::ClipPath(Path path, Entity::ClipOperation clip_op) {
155+
auto contents = std::make_shared<ClipContents>();
156+
contents->SetClipOperation(clip_op);
157+
155158
Entity entity;
156159
entity.SetTransformation(GetCurrentTransformation());
157160
entity.SetPath(std::move(path));
158-
entity.SetContents(std::make_shared<ClipContents>());
161+
entity.SetContents(std::move(contents));
159162
entity.SetStencilDepth(GetStencilDepth());
160163
entity.SetAddsToCoverage(false);
161164

impeller/aiks/canvas.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ class Canvas {
7272
Rect dest,
7373
Paint paint);
7474

75-
void ClipPath(Path path);
75+
void ClipPath(
76+
Path path,
77+
Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect);
7678

7779
void DrawShadow(Path path, Color color, Scalar elevation);
7880

impeller/display_list/display_list_dispatcher.cc

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ void DisplayListDispatcher::setInvertColors(bool invert) {
159159
UNIMPLEMENTED;
160160
}
161161

162-
std::optional<Entity::BlendMode> ToBlendMode(flutter::DlBlendMode mode) {
162+
static std::optional<Entity::BlendMode> ToBlendMode(flutter::DlBlendMode mode) {
163163
switch (mode) {
164164
case flutter::DlBlendMode::kClear:
165165
return Entity::BlendMode::kClear;
@@ -351,12 +351,21 @@ static Rect ToRect(const SkRect& rect) {
351351
return Rect::MakeLTRB(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
352352
}
353353

354+
static Entity::ClipOperation ToClipOperation(SkClipOp clip_op) {
355+
switch (clip_op) {
356+
case SkClipOp::kDifference:
357+
return Entity::ClipOperation::kDifference;
358+
case SkClipOp::kIntersect:
359+
return Entity::ClipOperation::kIntersect;
360+
}
361+
}
362+
354363
// |flutter::Dispatcher|
355364
void DisplayListDispatcher::clipRect(const SkRect& rect,
356365
SkClipOp clip_op,
357366
bool is_aa) {
358367
auto path = PathBuilder{}.AddRect(ToRect(rect)).TakePath();
359-
canvas_.ClipPath(std::move(path));
368+
canvas_.ClipPath(std::move(path), ToClipOperation(clip_op));
360369
}
361370

362371
static PathBuilder::RoundingRadii ToRoundingRadii(const SkRRect& rrect) {
@@ -444,7 +453,7 @@ static Path ToPath(const SkRRect& rrect) {
444453
void DisplayListDispatcher::clipRRect(const SkRRect& rrect,
445454
SkClipOp clip_op,
446455
bool is_aa) {
447-
canvas_.ClipPath(ToPath(rrect));
456+
canvas_.ClipPath(ToPath(rrect), ToClipOperation(clip_op));
448457
}
449458

450459
// |flutter::Dispatcher|

impeller/entity/contents/clip_contents.cc

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include "impeller/geometry/path_builder.h"
6+
#include "impeller/renderer/formats.h"
7+
#include "impeller/renderer/vertex_buffer_builder.h"
58
#include "linear_gradient_contents.h"
69

710
#include "impeller/entity/contents/clip_contents.h"
@@ -20,25 +23,64 @@ ClipContents::ClipContents() = default;
2023

2124
ClipContents::~ClipContents() = default;
2225

26+
void ClipContents::SetClipOperation(Entity::ClipOperation clip_op) {
27+
clip_op_ = clip_op;
28+
}
29+
2330
bool ClipContents::Render(const ContentContext& renderer,
2431
const Entity& entity,
2532
RenderPass& pass) const {
2633
using VS = ClipPipeline::VertexShader;
2734

35+
VS::FrameInfo info;
36+
// The color really doesn't matter.
37+
info.color = Color::SkyBlue();
38+
2839
Command cmd;
29-
cmd.label = "Clip";
30-
cmd.pipeline =
31-
renderer.GetClipPipeline(OptionsFromPassAndEntity(pass, entity));
40+
auto options = OptionsFromPassAndEntity(pass, entity);
3241
cmd.stencil_reference = entity.GetStencilDepth();
42+
options.stencil_compare = CompareFunction::kEqual;
43+
options.stencil_operation = StencilOperation::kIncrementClamp;
44+
45+
if (clip_op_ == Entity::ClipOperation::kDifference) {
46+
{
47+
cmd.label = "Difference Clip (Increment)";
48+
49+
cmd.primitive_type = PrimitiveType::kTriangleStrip;
50+
auto points = Rect(Size(pass.GetRenderTargetSize())).GetPoints();
51+
auto vertices =
52+
VertexBufferBuilder<VS::PerVertexData>{}
53+
.AddVertices({{points[0]}, {points[1]}, {points[2]}, {points[3]}})
54+
.CreateVertexBuffer(pass.GetTransientsBuffer());
55+
cmd.BindVertices(std::move(vertices));
56+
57+
info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize());
58+
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(info));
59+
60+
cmd.pipeline = renderer.GetClipPipeline(options);
61+
pass.AddCommand(cmd);
62+
}
63+
64+
{
65+
cmd.label = "Difference Clip (Punch)";
66+
67+
cmd.primitive_type = PrimitiveType::kTriangle;
68+
cmd.stencil_reference = entity.GetStencilDepth() + 1;
69+
options.stencil_compare = CompareFunction::kEqual;
70+
options.stencil_operation = StencilOperation::kDecrementClamp;
71+
}
72+
} else {
73+
cmd.label = "Intersect Clip";
74+
options.stencil_compare = CompareFunction::kEqual;
75+
options.stencil_operation = StencilOperation::kIncrementClamp;
76+
}
77+
78+
cmd.pipeline = renderer.GetClipPipeline(options);
3379
cmd.BindVertices(SolidColorContents::CreateSolidFillVertices(
3480
entity.GetPath(), pass.GetTransientsBuffer()));
3581

36-
VS::FrameInfo info;
37-
// The color really doesn't matter.
38-
info.color = Color::SkyBlue();
3982
info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
4083
entity.GetTransformation();
41-
4284
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(info));
4385

4486
pass.AddCommand(std::move(cmd));
@@ -59,9 +101,11 @@ bool ClipRestoreContents::Render(const ContentContext& renderer,
59101
using VS = ClipPipeline::VertexShader;
60102

61103
Command cmd;
62-
cmd.label = "Clip Restore";
63-
cmd.pipeline =
64-
renderer.GetClipRestorePipeline(OptionsFromPassAndEntity(pass, entity));
104+
cmd.label = "Restore Clip";
105+
auto options = OptionsFromPassAndEntity(pass, entity);
106+
options.stencil_compare = CompareFunction::kLess;
107+
options.stencil_operation = StencilOperation::kSetToReferenceValue;
108+
cmd.pipeline = renderer.GetClipPipeline(options);
65109
cmd.stencil_reference = entity.GetStencilDepth();
66110

67111
// Create a rect that covers the whole render target.

impeller/entity/contents/clip_contents.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,26 @@
1010

1111
#include "flutter/fml/macros.h"
1212
#include "impeller/entity/contents/contents.h"
13-
#include "impeller/typographer/glyph_atlas.h"
13+
#include "impeller/entity/entity.h"
1414

1515
namespace impeller {
1616

17-
class GlyphAtlas;
18-
1917
class ClipContents final : public Contents {
2018
public:
2119
ClipContents();
2220

2321
~ClipContents();
2422

23+
void SetClipOperation(Entity::ClipOperation clip_op);
24+
2525
// |Contents|
2626
bool Render(const ContentContext& renderer,
2727
const Entity& entity,
2828
RenderPass& pass) const override;
2929

3030
private:
31+
Entity::ClipOperation clip_op_ = Entity::ClipOperation::kIntersect;
32+
3133
FML_DISALLOW_COPY_AND_ASSIGN(ClipContents);
3234
};
3335

@@ -37,8 +39,6 @@ class ClipRestoreContents final : public Contents {
3739

3840
~ClipRestoreContents();
3941

40-
void SetGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas);
41-
4242
// |Contents|
4343
bool Render(const ContentContext& renderer,
4444
const Entity& entity,

impeller/entity/contents/content_context.cc

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,41 +33,20 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
3333
// TODO(98684): Rework this API to allow fetching the descriptor without
3434
// waiting for the pipeline to build.
3535
if (auto solid_fill_pipeline = solid_fill_pipelines_[{}]->WaitAndGet()) {
36-
// Clip pipeline.
37-
{
38-
auto clip_pipeline_descriptor = solid_fill_pipeline->GetDescriptor();
39-
clip_pipeline_descriptor.SetLabel("Clip Pipeline");
40-
// Write to the stencil buffer.
41-
StencilAttachmentDescriptor stencil0;
42-
stencil0.stencil_compare = CompareFunction::kEqual;
43-
stencil0.depth_stencil_pass = StencilOperation::kIncrementClamp;
44-
clip_pipeline_descriptor.SetStencilAttachmentDescriptors(stencil0);
45-
// Disable write to all color attachments.
46-
auto color_attachments =
47-
clip_pipeline_descriptor.GetColorAttachmentDescriptors();
48-
for (auto& color_attachment : color_attachments) {
49-
color_attachment.second.write_mask =
50-
static_cast<uint64_t>(ColorWriteMask::kNone);
51-
}
52-
clip_pipeline_descriptor.SetColorAttachmentDescriptors(
53-
std::move(color_attachments));
54-
clip_pipelines_[{}] =
55-
std::make_unique<ClipPipeline>(*context_, clip_pipeline_descriptor);
36+
auto clip_pipeline_descriptor = solid_fill_pipeline->GetDescriptor();
37+
clip_pipeline_descriptor.SetLabel("Clip Pipeline");
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);
5644
}
45+
clip_pipeline_descriptor.SetColorAttachmentDescriptors(
46+
std::move(color_attachments));
47+
clip_pipelines_[{}] =
48+
std::make_unique<ClipPipeline>(*context_, clip_pipeline_descriptor);
5749

58-
// Clip restoration pipeline.
59-
{
60-
auto clip_pipeline_descriptor =
61-
clip_pipelines_[{}]->WaitAndGet()->GetDescriptor();
62-
clip_pipeline_descriptor.SetLabel("Clip Restoration Pipeline");
63-
// Write to the stencil buffer.
64-
StencilAttachmentDescriptor stencil0;
65-
stencil0.stencil_compare = CompareFunction::kLess;
66-
stencil0.depth_stencil_pass = StencilOperation::kSetToReferenceValue;
67-
clip_pipeline_descriptor.SetStencilAttachmentDescriptors(stencil0);
68-
clip_restoration_pipelines_[{}] = std::make_unique<ClipPipeline>(
69-
*context_, std::move(clip_pipeline_descriptor));
70-
}
7150
} else {
7251
return;
7352
}

impeller/entity/contents/content_context.h

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,30 @@ using SolidStrokePipeline =
4848
PipelineT<SolidStrokeVertexShader, SolidStrokeFragmentShader>;
4949
using GlyphAtlasPipeline =
5050
PipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
51-
// Instead of requiring new shaders for clips, the solid fill stages are used
51+
// Instead of requiring new shaders for clips, the solid fill stages are used
5252
// to redirect writing to the stencil instead of color attachments.
5353
using ClipPipeline = PipelineT<SolidFillVertexShader, SolidFillFragmentShader>;
5454

5555
struct ContentContextOptions {
5656
SampleCount sample_count = SampleCount::kCount1;
5757
Entity::BlendMode blend_mode = Entity::BlendMode::kSourceOver;
58+
CompareFunction stencil_compare = CompareFunction::kEqual;
59+
StencilOperation stencil_operation = StencilOperation::kKeep;
5860

5961
struct Hash {
6062
constexpr std::size_t operator()(const ContentContextOptions& o) const {
61-
return fml::HashCombine(o.sample_count, o.blend_mode);
63+
return fml::HashCombine(o.sample_count, o.blend_mode, o.stencil_compare,
64+
o.stencil_operation);
6265
}
6366
};
6467

6568
struct Equal {
6669
constexpr bool operator()(const ContentContextOptions& lhs,
6770
const ContentContextOptions& rhs) const {
6871
return lhs.sample_count == rhs.sample_count &&
69-
lhs.blend_mode == rhs.blend_mode;
72+
lhs.blend_mode == rhs.blend_mode &&
73+
lhs.stencil_compare == rhs.stencil_compare &&
74+
lhs.stencil_operation == rhs.stencil_operation;
7075
}
7176
};
7277
};
@@ -118,11 +123,6 @@ class ContentContext {
118123
return GetPipeline(clip_pipelines_, opts);
119124
}
120125

121-
std::shared_ptr<Pipeline> GetClipRestorePipeline(
122-
ContentContextOptions opts) const {
123-
return GetPipeline(clip_restoration_pipelines_, opts);
124-
}
125-
126126
std::shared_ptr<Pipeline> GetGlyphAtlasPipeline(
127127
ContentContextOptions opts) const {
128128
return GetPipeline(glyph_atlas_pipelines_, opts);
@@ -150,7 +150,6 @@ class ContentContext {
150150
mutable Variants<GaussianBlurPipeline> gaussian_blur_pipelines_;
151151
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
152152
mutable Variants<ClipPipeline> clip_pipelines_;
153-
mutable Variants<ClipPipeline> clip_restoration_pipelines_;
154153
mutable Variants<GlyphAtlasPipeline> glyph_atlas_pipelines_;
155154

156155
static void ApplyOptionsToDescriptor(PipelineDescriptor& desc,
@@ -262,6 +261,14 @@ class ContentContext {
262261
FML_UNREACHABLE();
263262
}
264263
desc.SetColorAttachmentDescriptor(0u, std::move(color0));
264+
265+
if (desc.GetFrontStencilAttachmentDescriptor().has_value()) {
266+
StencilAttachmentDescriptor stencil =
267+
desc.GetFrontStencilAttachmentDescriptor().value();
268+
stencil.stencil_compare = options.stencil_compare;
269+
stencil.depth_stencil_pass = options.stencil_operation;
270+
desc.SetStencilAttachmentDescriptors(stencil);
271+
}
265272
}
266273

267274
template <class TypedPipeline>

impeller/entity/entity.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ class Entity {
4848
kLastAdvancedBlendMode = kScreen,
4949
};
5050

51+
enum class ClipOperation {
52+
kDifference,
53+
kIntersect,
54+
};
55+
5156
Entity();
5257

5358
~Entity();

0 commit comments

Comments
 (0)