Skip to content

Commit

Permalink
Support nested clips & clip state restoration (#14)
Browse files Browse the repository at this point in the history
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/flutter#98631.
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 4220ef3 commit 9b2cbc1
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 27 deletions.
13 changes: 13 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ TEST_F(AiksTest, CanRenderClips) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_F(AiksTest, CanRenderNestedClips) {
Canvas canvas;
Paint paint;
paint.color = Color::Fuchsia();
canvas.Save();
canvas.ClipPath(PathBuilder{}.AddCircle({200, 400}, 300).TakePath());
canvas.Restore();
canvas.ClipPath(PathBuilder{}.AddCircle({600, 400}, 300).TakePath());
canvas.ClipPath(PathBuilder{}.AddCircle({400, 600}, 300).TakePath());
canvas.DrawRect(Rect::MakeXYWH(200, 200, 400, 400), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_F(AiksTest, CanSaveLayerStandalone) {
Canvas canvas;

Expand Down
29 changes: 23 additions & 6 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ bool Canvas::Restore() {
current_pass_ = GetCurrentPass().GetSuperpass();
FML_DCHECK(current_pass_);
}

bool contains_clips = xformation_stack_.back().contains_clips;
xformation_stack_.pop_back();

if (contains_clips) {
RestoreClip();
}

return true;
}

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

void Canvas::ClipPath(Path path) {
IncrementStencilDepth();

Entity entity;
entity.SetTransformation(GetCurrentTransformation());
entity.SetPath(std::move(path));
Expand All @@ -139,6 +144,22 @@ void Canvas::ClipPath(Path path) {
entity.SetAddsToCoverage(false);

GetCurrentPass().AddEntity(std::move(entity));

++xformation_stack_.back().stencil_depth;
xformation_stack_.back().contains_clips = true;
}

void Canvas::RestoreClip() {
Entity entity;
entity.SetTransformation(GetCurrentTransformation());
// This path is empty because ClipRestoreContents just generates a quad that
// takes up the full render target.
entity.SetPath({});
entity.SetContents(std::make_shared<ClipRestoreContents>());
entity.SetStencilDepth(GetStencilDepth());
entity.SetAddsToCoverage(false);

GetCurrentPass().AddEntity(std::move(entity));
}

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

void Canvas::IncrementStencilDepth() {
++xformation_stack_.back().stencil_depth;
}

size_t Canvas::GetStencilDepth() const {
return xformation_stack_.back().stencil_depth;
}
Expand Down
4 changes: 2 additions & 2 deletions impeller/aiks/canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ class Canvas {

EntityPass& GetCurrentPass();

void IncrementStencilDepth();

size_t GetStencilDepth() const;

void Save(bool create_subpass);

void RestoreClip();

FML_DISALLOW_COPY_AND_ASSIGN(Canvas);
};

Expand Down
53 changes: 36 additions & 17 deletions impeller/entity/content_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,44 @@ ContentContext::ContentContext(std::shared_ptr<Context> context)
std::make_unique<SolidStrokePipeline>(*context_);

// Pipelines that are variants of the base pipelines with custom descriptors.
// 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()) {
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::kGreaterEqual;
stencil0.depth_stencil_pass = StencilOperation::kSetToReferenceValue;
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.
{
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);
}

// 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));
}
clip_pipeline_descriptor.SetColorAttachmentDescriptors(
std::move(color_attachments));
clip_pipelines_[{}] = std::make_unique<ClipPipeline>(
*context_, std::move(clip_pipeline_descriptor));
} else {
return;
}
Expand Down
5 changes: 5 additions & 0 deletions impeller/entity/content_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ class ContentContext {
return GetPipeline(clip_pipelines_, opts);
}

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

std::shared_ptr<Context> GetContext() const;

private:
Expand All @@ -94,6 +98,7 @@ class ContentContext {
mutable Variants<TexturePipeline> texture_pipelines_;
mutable Variants<SolidStrokePipeline> solid_stroke_pipelines_;
mutable Variants<ClipPipeline> clip_pipelines_;
mutable Variants<ClipPipeline> clip_restoration_pipelines_;

static void ApplyOptionsToDescriptor(PipelineDescriptor& desc,
const Options& options) {
Expand Down
45 changes: 44 additions & 1 deletion impeller/entity/contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "impeller/renderer/sampler_library.h"
#include "impeller/renderer/surface.h"
#include "impeller/renderer/tessellator.h"
#include "impeller/renderer/vertex_buffer.h"
#include "impeller/renderer/vertex_buffer_builder.h"

namespace impeller {
Expand Down Expand Up @@ -385,7 +386,7 @@ bool ClipContents::Render(const ContentContext& renderer,
Command cmd;
cmd.label = "Clip";
cmd.pipeline = renderer.GetClipPipeline(OptionsFromPass(pass));
cmd.stencil_reference = entity.GetStencilDepth() + 1u;
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(
CreateSolidFillVertices(entity.GetPath(), pass.GetTransientsBuffer()));

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

/*******************************************************************************
******* ClipRestoreContents
******************************************************************************/

ClipRestoreContents::ClipRestoreContents() = default;

ClipRestoreContents::~ClipRestoreContents() = default;

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

Command cmd;
cmd.label = "Clip Restore";
cmd.pipeline = renderer.GetClipRestorePipeline(OptionsFromPass(pass));
cmd.stencil_reference = entity.GetStencilDepth();

// Create a rect that covers the whole render target.
auto size = pass.GetRenderTargetSize();
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
vtx_builder.AddVertices({
{Point(0.0, 0.0)},
{Point(size.width, 0.0)},
{Point(size.width, size.height)},
{Point(0.0, 0.0)},
{Point(size.width, size.height)},
{Point(0.0, size.height)},
});
cmd.BindVertices(vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer()));

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

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

pass.AddCommand(std::move(cmd));
return true;
}

} // namespace impeller
15 changes: 15 additions & 0 deletions impeller/entity/contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,19 @@ class ClipContents final : public Contents {
FML_DISALLOW_COPY_AND_ASSIGN(ClipContents);
};

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

~ClipRestoreContents();

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

private:
FML_DISALLOW_COPY_AND_ASSIGN(ClipRestoreContents);
};

} // namespace impeller
1 change: 1 addition & 0 deletions impeller/entity/entity_pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ struct CanvasStackEntry {
Matrix xformation;
size_t stencil_depth = 0u;
bool is_subpass = false;
bool contains_clips = false;
};

} // namespace impeller
3 changes: 2 additions & 1 deletion impeller/renderer/pipeline_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "flutter/fml/macros.h"
#include "impeller/base/base.h"
#include "impeller/renderer/context.h"
#include "impeller/renderer/formats.h"
#include "impeller/renderer/pipeline_descriptor.h"
#include "impeller/renderer/shader_library.h"
#include "impeller/renderer/vertex_descriptor.h"
Expand Down Expand Up @@ -107,7 +108,7 @@ struct PipelineBuilder {
// Setup default stencil buffer descriptions.
{
StencilAttachmentDescriptor stencil0;
stencil0.stencil_compare = CompareFunction::kLessEqual;
stencil0.stencil_compare = CompareFunction::kEqual;
desc.SetStencilAttachmentDescriptors(stencil0);
desc.SetStencilPixelFormat(PixelFormat::kDefaultStencil);
}
Expand Down

0 comments on commit 9b2cbc1

Please sign in to comment.