Skip to content

Commit

Permalink
Add difference clipping (flutter#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 0aaa84e commit eb7b7d1
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 63 deletions.
37 changes: 37 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,43 @@ TEST_F(AiksTest, CanRenderNestedClips) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_F(AiksTest, CanRenderDifferenceClips) {
Paint paint;
Canvas canvas;
canvas.Translate({400, 400});

// Limit drawing to face circle with a clip.
canvas.ClipPath(PathBuilder{}.AddCircle(Point(), 200).TakePath());
canvas.Save();

// Cut away eyes/mouth using difference clips.
canvas.ClipPath(PathBuilder{}.AddCircle({-100, -50}, 30).TakePath(),
Entity::ClipOperation::kDifference);
canvas.ClipPath(PathBuilder{}.AddCircle({100, -50}, 30).TakePath(),
Entity::ClipOperation::kDifference);
canvas.ClipPath(PathBuilder{}
.AddQuadraticCurve({-100, 50}, {0, 150}, {100, 50})
.TakePath(),
Entity::ClipOperation::kDifference);

// Draw a huge yellow rectangle to prove the clipping works.
paint.color = Color::Yellow();
canvas.DrawRect(Rect::MakeXYWH(-1000, -1000, 2000, 2000), paint);

// Remove the difference clips and draw hair that partially covers the eyes.
canvas.Restore();
paint.color = Color::Maroon();
canvas.DrawPath(PathBuilder{}
.MoveTo({200, -200})
.HorizontalLineTo(-200)
.VerticalLineTo(-40)
.CubicCurveTo({0, -40}, {0, -80}, {200, -80})
.TakePath(),
paint);

ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_F(AiksTest, ClipsUseCurrentTransform) {
std::array<Color, 5> colors = {Color::White(), Color::Black(),
Color::SkyBlue(), Color::Red(),
Expand Down
7 changes: 5 additions & 2 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,14 @@ void Canvas::SaveLayer(Paint paint, std::optional<Rect> bounds) {
}
}

void Canvas::ClipPath(Path path) {
void Canvas::ClipPath(Path path, Entity::ClipOperation clip_op) {
auto contents = std::make_shared<ClipContents>();
contents->SetClipOperation(clip_op);

Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetPath(std::move(path));
entity.SetContents(std::make_shared<ClipContents>());
entity.SetContents(std::move(contents));
entity.SetStencilDepth(GetStencilDepth());
entity.SetAddsToCoverage(false);

Expand Down
4 changes: 3 additions & 1 deletion impeller/aiks/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ class Canvas {
Rect dest,
Paint paint);

void ClipPath(Path path);
void ClipPath(
Path path,
Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect);

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

Expand Down
15 changes: 12 additions & 3 deletions impeller/display_list/display_list_dispatcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ void DisplayListDispatcher::setInvertColors(bool invert) {
UNIMPLEMENTED;
}

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

static Entity::ClipOperation ToClipOperation(SkClipOp clip_op) {
switch (clip_op) {
case SkClipOp::kDifference:
return Entity::ClipOperation::kDifference;
case SkClipOp::kIntersect:
return Entity::ClipOperation::kIntersect;
}
}

// |flutter::Dispatcher|
void DisplayListDispatcher::clipRect(const SkRect& rect,
SkClipOp clip_op,
bool is_aa) {
auto path = PathBuilder{}.AddRect(ToRect(rect)).TakePath();
canvas_.ClipPath(std::move(path));
canvas_.ClipPath(std::move(path), ToClipOperation(clip_op));
}

static PathBuilder::RoundingRadii ToRoundingRadii(const SkRRect& rrect) {
Expand Down Expand Up @@ -444,7 +453,7 @@ static Path ToPath(const SkRRect& rrect) {
void DisplayListDispatcher::clipRRect(const SkRRect& rrect,
SkClipOp clip_op,
bool is_aa) {
canvas_.ClipPath(ToPath(rrect));
canvas_.ClipPath(ToPath(rrect), ToClipOperation(clip_op));
}

// |flutter::Dispatcher|
Expand Down
64 changes: 54 additions & 10 deletions impeller/entity/contents/clip_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "impeller/geometry/path_builder.h"
#include "impeller/renderer/formats.h"
#include "impeller/renderer/vertex_buffer_builder.h"
#include "linear_gradient_contents.h"

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

ClipContents::~ClipContents() = default;

void ClipContents::SetClipOperation(Entity::ClipOperation clip_op) {
clip_op_ = clip_op;
}

bool ClipContents::Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const {
using VS = ClipPipeline::VertexShader;

VS::FrameInfo info;
// The color really doesn't matter.
info.color = Color::SkyBlue();

Command cmd;
cmd.label = "Clip";
cmd.pipeline =
renderer.GetClipPipeline(OptionsFromPassAndEntity(pass, entity));
auto options = OptionsFromPassAndEntity(pass, entity);
cmd.stencil_reference = entity.GetStencilDepth();
options.stencil_compare = CompareFunction::kEqual;
options.stencil_operation = StencilOperation::kIncrementClamp;

if (clip_op_ == Entity::ClipOperation::kDifference) {
{
cmd.label = "Difference Clip (Increment)";

cmd.primitive_type = PrimitiveType::kTriangleStrip;
auto points = Rect(Size(pass.GetRenderTargetSize())).GetPoints();
auto vertices =
VertexBufferBuilder<VS::PerVertexData>{}
.AddVertices({{points[0]}, {points[1]}, {points[2]}, {points[3]}})
.CreateVertexBuffer(pass.GetTransientsBuffer());
cmd.BindVertices(std::move(vertices));

info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize());
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(info));

cmd.pipeline = renderer.GetClipPipeline(options);
pass.AddCommand(cmd);
}

{
cmd.label = "Difference Clip (Punch)";

cmd.primitive_type = PrimitiveType::kTriangle;
cmd.stencil_reference = entity.GetStencilDepth() + 1;
options.stencil_compare = CompareFunction::kEqual;
options.stencil_operation = StencilOperation::kDecrementClamp;
}
} else {
cmd.label = "Intersect Clip";
options.stencil_compare = CompareFunction::kEqual;
options.stencil_operation = StencilOperation::kIncrementClamp;
}

cmd.pipeline = renderer.GetClipPipeline(options);
cmd.BindVertices(SolidColorContents::CreateSolidFillVertices(
entity.GetPath(), pass.GetTransientsBuffer()));

VS::FrameInfo info;
// The color really doesn't matter.
info.color = Color::SkyBlue();
info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
entity.GetTransformation();

VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(info));

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

Command cmd;
cmd.label = "Clip Restore";
cmd.pipeline =
renderer.GetClipRestorePipeline(OptionsFromPassAndEntity(pass, entity));
cmd.label = "Restore Clip";
auto options = OptionsFromPassAndEntity(pass, entity);
options.stencil_compare = CompareFunction::kLess;
options.stencil_operation = StencilOperation::kSetToReferenceValue;
cmd.pipeline = renderer.GetClipPipeline(options);
cmd.stencil_reference = entity.GetStencilDepth();

// Create a rect that covers the whole render target.
Expand Down
10 changes: 5 additions & 5 deletions impeller/entity/contents/clip_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,26 @@

#include "flutter/fml/macros.h"
#include "impeller/entity/contents/contents.h"
#include "impeller/typographer/glyph_atlas.h"
#include "impeller/entity/entity.h"

namespace impeller {

class GlyphAtlas;

class ClipContents final : public Contents {
public:
ClipContents();

~ClipContents();

void SetClipOperation(Entity::ClipOperation clip_op);

// |Contents|
bool Render(const ContentContext& renderer,
const Entity& entity,
RenderPass& pass) const override;

private:
Entity::ClipOperation clip_op_ = Entity::ClipOperation::kIntersect;

FML_DISALLOW_COPY_AND_ASSIGN(ClipContents);
};

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

~ClipRestoreContents();

void SetGlyphAtlas(std::shared_ptr<GlyphAtlas> atlas);

// |Contents|
bool Render(const ContentContext& renderer,
const Entity& entity,
Expand Down
45 changes: 12 additions & 33 deletions impeller/entity/contents/content_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,41 +33,20 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
// TODO(98684): Rework this API to allow fetching the descriptor without
// waiting for the pipeline to build.
if (auto solid_fill_pipeline = solid_fill_pipelines_[{}]->WaitAndGet()) {
// Clip pipeline.
{
auto clip_pipeline_descriptor = solid_fill_pipeline->GetDescriptor();
clip_pipeline_descriptor.SetLabel("Clip Pipeline");
// Write to the stencil buffer.
StencilAttachmentDescriptor stencil0;
stencil0.stencil_compare = CompareFunction::kEqual;
stencil0.depth_stencil_pass = StencilOperation::kIncrementClamp;
clip_pipeline_descriptor.SetStencilAttachmentDescriptors(stencil0);
// Disable write to all color attachments.
auto color_attachments =
clip_pipeline_descriptor.GetColorAttachmentDescriptors();
for (auto& color_attachment : color_attachments) {
color_attachment.second.write_mask =
static_cast<uint64_t>(ColorWriteMask::kNone);
}
clip_pipeline_descriptor.SetColorAttachmentDescriptors(
std::move(color_attachments));
clip_pipelines_[{}] =
std::make_unique<ClipPipeline>(*context_, clip_pipeline_descriptor);
auto clip_pipeline_descriptor = solid_fill_pipeline->GetDescriptor();
clip_pipeline_descriptor.SetLabel("Clip Pipeline");
// Disable write to all color attachments.
auto color_attachments =
clip_pipeline_descriptor.GetColorAttachmentDescriptors();
for (auto& color_attachment : color_attachments) {
color_attachment.second.write_mask =
static_cast<uint64_t>(ColorWriteMask::kNone);
}
clip_pipeline_descriptor.SetColorAttachmentDescriptors(
std::move(color_attachments));
clip_pipelines_[{}] =
std::make_unique<ClipPipeline>(*context_, clip_pipeline_descriptor);

// Clip restoration pipeline.
{
auto clip_pipeline_descriptor =
clip_pipelines_[{}]->WaitAndGet()->GetDescriptor();
clip_pipeline_descriptor.SetLabel("Clip Restoration Pipeline");
// Write to the stencil buffer.
StencilAttachmentDescriptor stencil0;
stencil0.stencil_compare = CompareFunction::kLess;
stencil0.depth_stencil_pass = StencilOperation::kSetToReferenceValue;
clip_pipeline_descriptor.SetStencilAttachmentDescriptors(stencil0);
clip_restoration_pipelines_[{}] = std::make_unique<ClipPipeline>(
*context_, std::move(clip_pipeline_descriptor));
}
} else {
return;
}
Expand Down
25 changes: 16 additions & 9 deletions impeller/entity/contents/content_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,30 @@ using SolidStrokePipeline =
PipelineT<SolidStrokeVertexShader, SolidStrokeFragmentShader>;
using GlyphAtlasPipeline =
PipelineT<GlyphAtlasVertexShader, GlyphAtlasFragmentShader>;
// Instead of requiring new shaders for clips, the solid fill stages are used
// Instead of requiring new shaders for clips, the solid fill stages are used
// to redirect writing to the stencil instead of color attachments.
using ClipPipeline = PipelineT<SolidFillVertexShader, SolidFillFragmentShader>;

struct ContentContextOptions {
SampleCount sample_count = SampleCount::kCount1;
Entity::BlendMode blend_mode = Entity::BlendMode::kSourceOver;
CompareFunction stencil_compare = CompareFunction::kEqual;
StencilOperation stencil_operation = StencilOperation::kKeep;

struct Hash {
constexpr std::size_t operator()(const ContentContextOptions& o) const {
return fml::HashCombine(o.sample_count, o.blend_mode);
return fml::HashCombine(o.sample_count, o.blend_mode, o.stencil_compare,
o.stencil_operation);
}
};

struct Equal {
constexpr bool operator()(const ContentContextOptions& lhs,
const ContentContextOptions& rhs) const {
return lhs.sample_count == rhs.sample_count &&
lhs.blend_mode == rhs.blend_mode;
lhs.blend_mode == rhs.blend_mode &&
lhs.stencil_compare == rhs.stencil_compare &&
lhs.stencil_operation == rhs.stencil_operation;
}
};
};
Expand Down Expand Up @@ -118,11 +123,6 @@ class ContentContext {
return GetPipeline(clip_pipelines_, opts);
}

std::shared_ptr<Pipeline> GetClipRestorePipeline(
ContentContextOptions opts) const {
return GetPipeline(clip_restoration_pipelines_, opts);
}

std::shared_ptr<Pipeline> GetGlyphAtlasPipeline(
ContentContextOptions opts) const {
return GetPipeline(glyph_atlas_pipelines_, opts);
Expand Down Expand Up @@ -150,7 +150,6 @@ class ContentContext {
mutable Variants<GaussianBlurPipeline> gaussian_blur_pipelines_;
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
mutable Variants<ClipPipeline> clip_pipelines_;
mutable Variants<ClipPipeline> clip_restoration_pipelines_;
mutable Variants<GlyphAtlasPipeline> glyph_atlas_pipelines_;

static void ApplyOptionsToDescriptor(PipelineDescriptor& desc,
Expand Down Expand Up @@ -262,6 +261,14 @@ class ContentContext {
FML_UNREACHABLE();
}
desc.SetColorAttachmentDescriptor(0u, std::move(color0));

if (desc.GetFrontStencilAttachmentDescriptor().has_value()) {
StencilAttachmentDescriptor stencil =
desc.GetFrontStencilAttachmentDescriptor().value();
stencil.stencil_compare = options.stencil_compare;
stencil.depth_stencil_pass = options.stencil_operation;
desc.SetStencilAttachmentDescriptors(stencil);
}
}

template <class TypedPipeline>
Expand Down
5 changes: 5 additions & 0 deletions impeller/entity/entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class Entity {
kLastAdvancedBlendMode = kScreen,
};

enum class ClipOperation {
kDifference,
kIntersect,
};

Entity();

~Entity();
Expand Down

0 comments on commit eb7b7d1

Please sign in to comment.