Support nested clips & clip state restoration (flutter/engine#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/flutter#98631.
This commit is contained in:
Brandon DeRosier 2022-02-17 11:32:35 -08:00 committed by Dan Field
parent 75460b68be
commit bbbbc30701
9 changed files with 141 additions and 27 deletions

View File

@ -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;

View File

@ -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;
}
@ -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));
@ -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) {}
@ -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;
}

View File

@ -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);
};

View File

@ -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;
}

View File

@ -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:
@ -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) {

View File

@ -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 {
@ -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()));
@ -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

View File

@ -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

View File

@ -73,6 +73,7 @@ struct CanvasStackEntry {
Matrix xformation;
size_t stencil_depth = 0u;
bool is_subpass = false;
bool contains_clips = false;
};
} // namespace impeller

View File

@ -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"
@ -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);
}