mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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:
parent
75460b68be
commit
bbbbc30701
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -73,6 +73,7 @@ struct CanvasStackEntry {
|
||||
Matrix xformation;
|
||||
size_t stencil_depth = 0u;
|
||||
bool is_subpass = false;
|
||||
bool contains_clips = false;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user