[Impeller] Keep track of stencil coverage; don't render unused clips (#35966)
diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 3a3a013..a1dc577 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc
@@ -215,7 +215,6 @@ entity.SetTransformation(GetCurrentTransformation()); entity.SetContents(std::move(contents)); entity.SetStencilDepth(GetStencilDepth()); - entity.SetAddsToCoverage(false); GetCurrentPass().AddEntity(std::move(entity)); @@ -230,7 +229,6 @@ // takes up the full render target. entity.SetContents(std::make_shared<ClipRestoreContents>()); entity.SetStencilDepth(GetStencilDepth()); - entity.SetAddsToCoverage(false); GetCurrentPass().AddEntity(std::move(entity)); }
diff --git a/impeller/entity/contents/clip_contents.cc b/impeller/entity/contents/clip_contents.cc index c44d7b7..b677864 100644 --- a/impeller/entity/contents/clip_contents.cc +++ b/impeller/entity/contents/clip_contents.cc
@@ -3,16 +3,16 @@ // found in the LICENSE file. #include <optional> -#include "impeller/geometry/path_builder.h" -#include "impeller/renderer/formats.h" -#include "impeller/renderer/vertex_buffer_builder.h" -#include "linear_gradient_contents.h" +#include "fml/logging.h" #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/solid_color_contents.h" #include "impeller/entity/entity.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/renderer/formats.h" #include "impeller/renderer/render_pass.h" +#include "impeller/renderer/vertex_buffer_builder.h" namespace impeller { @@ -36,8 +36,32 @@ return std::nullopt; }; -bool ClipContents::ShouldRender(const Entity& entity, - const ISize& target_size) const { +Contents::StencilCoverage ClipContents::GetStencilCoverage( + const Entity& entity, + const std::optional<Rect>& current_stencil_coverage) const { + if (!current_stencil_coverage.has_value()) { + return {.type = StencilCoverage::Type::kAppend, .coverage = std::nullopt}; + } + switch (clip_op_) { + case Entity::ClipOperation::kDifference: + // This can be optimized further by considering cases when the bounds of + // the current stencil will shrink. + return {.type = StencilCoverage::Type::kAppend, + .coverage = current_stencil_coverage}; + case Entity::ClipOperation::kIntersect: + return { + .type = StencilCoverage::Type::kAppend, + .coverage = current_stencil_coverage->Intersection( + path_.GetTransformedBoundingBox(entity.GetTransformation()) + .value()), + }; + } + FML_UNREACHABLE(); +} + +bool ClipContents::ShouldRender( + const Entity& entity, + const std::optional<Rect>& stencil_coverage) const { return true; } @@ -119,8 +143,15 @@ return std::nullopt; }; -bool ClipRestoreContents::ShouldRender(const Entity& entity, - const ISize& target_size) const { +Contents::StencilCoverage ClipRestoreContents::GetStencilCoverage( + const Entity& entity, + const std::optional<Rect>& current_stencil_coverage) const { + return {.type = StencilCoverage::Type::kRestore, .coverage = std::nullopt}; +} + +bool ClipRestoreContents::ShouldRender( + const Entity& entity, + const std::optional<Rect>& stencil_coverage) const { return true; }
diff --git a/impeller/entity/contents/clip_contents.h b/impeller/entity/contents/clip_contents.h index 35c2a9a..9521aa0 100644 --- a/impeller/entity/contents/clip_contents.h +++ b/impeller/entity/contents/clip_contents.h
@@ -28,8 +28,13 @@ std::optional<Rect> GetCoverage(const Entity& entity) const override; // |Contents| + StencilCoverage GetStencilCoverage( + const Entity& entity, + const std::optional<Rect>& current_stencil_coverage) const override; + + // |Contents| bool ShouldRender(const Entity& entity, - const ISize& target_size) const override; + const std::optional<Rect>& stencil_coverage) const override; // |Contents| bool Render(const ContentContext& renderer, @@ -53,8 +58,13 @@ std::optional<Rect> GetCoverage(const Entity& entity) const override; // |Contents| + StencilCoverage GetStencilCoverage( + const Entity& entity, + const std::optional<Rect>& current_stencil_coverage) const override; + + // |Contents| bool ShouldRender(const Entity& entity, - const ISize& target_size) const override; + const std::optional<Rect>& stencil_coverage) const override; // |Contents| bool Render(const ContentContext& renderer,
diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc index a6c77db..877b8d1 100644 --- a/impeller/entity/contents/contents.cc +++ b/impeller/entity/contents/contents.cc
@@ -5,6 +5,7 @@ #include "impeller/entity/contents/contents.h" #include <optional> +#include "fml/logging.h" #include "impeller/entity/contents/content_context.h" #include "impeller/renderer/command_buffer.h" #include "impeller/renderer/render_pass.h" @@ -29,6 +30,13 @@ Contents::~Contents() = default; +Contents::StencilCoverage Contents::GetStencilCoverage( + const Entity& entity, + const std::optional<Rect>& current_stencil_coverage) const { + return {.type = StencilCoverage::Type::kNone, + .coverage = current_stencil_coverage}; +} + std::optional<Snapshot> Contents::RenderToSnapshot( const ContentContext& renderer, const Entity& entity) const { @@ -58,10 +66,19 @@ } bool Contents::ShouldRender(const Entity& entity, - const ISize& target_size) const { + const std::optional<Rect>& stencil_coverage) const { + if (!stencil_coverage.has_value()) { + return false; + } + if (Entity::BlendModeShouldCoverWholeScreen(entity.GetBlendMode())) { + return true; + } + auto coverage = GetCoverage(entity); - return coverage.has_value() && - Rect::MakeSize(target_size).IntersectsWithRect(coverage.value()); + if (!coverage.has_value()) { + return false; + } + return stencil_coverage->IntersectsWithRect(coverage.value()); } } // namespace impeller
diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h index f0c1668..6d7b6ed 100644 --- a/impeller/entity/contents/contents.h +++ b/impeller/entity/contents/contents.h
@@ -32,6 +32,13 @@ virtual ~Contents(); + struct StencilCoverage { + enum class Type { kNone, kAppend, kRestore }; + + Type type = Type::kNone; + std::optional<Rect> coverage = std::nullopt; + }; + virtual bool Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const = 0; @@ -39,6 +46,14 @@ /// @brief Get the screen space bounding rectangle that this contents affects. virtual std::optional<Rect> GetCoverage(const Entity& entity) const = 0; + /// @brief Given the current screen space bounding rectangle of the stencil, + /// return the expected stencil coverage after this draw call. This + /// should only be implemented for contents that may write to the + /// stencil buffer. + virtual StencilCoverage GetStencilCoverage( + const Entity& entity, + const std::optional<Rect>& current_stencil_coverage) const; + /// @brief Render this contents to a snapshot, respecting the entity's /// transform, path, stencil depth, and blend mode. /// The result texture size is always the size of @@ -48,7 +63,7 @@ const Entity& entity) const; virtual bool ShouldRender(const Entity& entity, - const ISize& target_size) const; + const std::optional<Rect>& stencil_coverage) const; protected:
diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc index c789551..78732be 100644 --- a/impeller/entity/contents/solid_color_contents.cc +++ b/impeller/entity/contents/solid_color_contents.cc
@@ -41,9 +41,13 @@ return path_.GetTransformedBoundingBox(entity.GetTransformation()); }; -bool SolidColorContents::ShouldRender(const Entity& entity, - const ISize& target_size) const { - return cover_ || Contents::ShouldRender(entity, target_size); +bool SolidColorContents::ShouldRender( + const Entity& entity, + const std::optional<Rect>& stencil_coverage) const { + if (!stencil_coverage.has_value()) { + return false; + } + return cover_ || Contents::ShouldRender(entity, stencil_coverage); } VertexBuffer SolidColorContents::CreateSolidFillVertices(const Path& path,
diff --git a/impeller/entity/contents/solid_color_contents.h b/impeller/entity/contents/solid_color_contents.h index b3f65a1..223ae13 100644 --- a/impeller/entity/contents/solid_color_contents.h +++ b/impeller/entity/contents/solid_color_contents.h
@@ -43,7 +43,7 @@ // |Contents| bool ShouldRender(const Entity& entity, - const ISize& target_size) const override; + const std::optional<Rect>& stencil_coverage) const override; // |Contents| bool Render(const ContentContext& renderer,
diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index 56f534a..6931228 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc
@@ -25,27 +25,24 @@ transformation_ = transformation; } -void Entity::SetAddsToCoverage(bool adds) { - adds_to_coverage_ = adds; -} - -bool Entity::AddsToCoverage() const { - return adds_to_coverage_; -} - std::optional<Rect> Entity::GetCoverage() const { - if (!adds_to_coverage_ || !contents_) { + if (!contents_) { return std::nullopt; } return contents_->GetCoverage(*this); } -bool Entity::ShouldRender(const ISize& target_size) const { - if (BlendModeShouldCoverWholeScreen(blend_mode_)) { - return true; +Contents::StencilCoverage Entity::GetStencilCoverage( + const std::optional<Rect>& current_stencil_coverage) const { + if (!contents_) { + return {}; } - return contents_->ShouldRender(*this, target_size); + return contents_->GetStencilCoverage(*this, current_stencil_coverage); +} + +bool Entity::ShouldRender(const std::optional<Rect>& stencil_coverage) const { + return contents_->ShouldRender(*this, stencil_coverage); } void Entity::SetContents(std::shared_ptr<Contents> contents) {
diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index 8c787e2..105c0a4 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h
@@ -97,13 +97,12 @@ void SetTransformation(const Matrix& transformation); - void SetAddsToCoverage(bool adds); - - bool AddsToCoverage() const; - std::optional<Rect> GetCoverage() const; - bool ShouldRender(const ISize& target_size) const; + Contents::StencilCoverage GetStencilCoverage( + const std::optional<Rect>& current_stencil_coverage) const; + + bool ShouldRender(const std::optional<Rect>& stencil_coverage) const; void SetContents(std::shared_ptr<Contents> contents); @@ -128,7 +127,6 @@ std::shared_ptr<Contents> contents_; BlendMode blend_mode_ = BlendMode::kSourceOver; uint32_t stencil_depth_ = 0u; - bool adds_to_coverage_ = true; }; } // namespace impeller
diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index 52a8ebd..b444ce5 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc
@@ -371,6 +371,11 @@ return EntityPass::EntityResult::Success(element_entity); } +struct StencilLayer { + std::optional<Rect> coverage; + size_t stencil_depth; +}; + bool EntityPass::OnRender( ContentContext& renderer, ISize root_pass_size, @@ -389,8 +394,12 @@ return false; } + std::vector<StencilLayer> stencil_stack = {StencilLayer{ + .coverage = Rect::MakeSize(render_target.GetRenderTargetSize()), + .stencil_depth = stencil_depth_floor}}; + auto render_element = [&stencil_depth_floor, &pass_context, &pass_depth, - &renderer](Entity& element_entity) { + &renderer, &stencil_stack](Entity& element_entity) { auto result = pass_context.GetRenderPass(pass_depth); if (!result.pass) { @@ -416,10 +425,46 @@ } } - if (!element_entity.ShouldRender(result.pass->GetRenderTargetSize())) { + if (!element_entity.ShouldRender(stencil_stack.back().coverage)) { return true; // Nothing to render. } + auto stencil_coverage = + element_entity.GetStencilCoverage(stencil_stack.back().coverage); + + switch (stencil_coverage.type) { + case Contents::StencilCoverage::Type::kNone: + break; + case Contents::StencilCoverage::Type::kAppend: { + auto op = stencil_stack.back().coverage; + stencil_stack.push_back(StencilLayer{ + .coverage = stencil_coverage.coverage, + .stencil_depth = element_entity.GetStencilDepth() + 1}); + + if (!op.has_value()) { + // Running this append op won't impact the stencil because the whole + // screen is already being clipped, so skip it. + return true; + } + } break; + case Contents::StencilCoverage::Type::kRestore: { + if (stencil_stack.back().stencil_depth <= + element_entity.GetStencilDepth()) { + // Drop stencil restores that will do nothing. + return true; + } + + FML_DCHECK(stencil_stack.size() > 1); + + stencil_stack.pop_back(); + + if (!stencil_stack.back().coverage.has_value()) { + // Running this restore op won't make anything renderable, so skip it. + return true; + } + } break; + } + element_entity.SetStencilDepth(element_entity.GetStencilDepth() - stencil_depth_floor); if (!element_entity.Render(renderer, *result.pass)) {
diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 90c44f7..34c00a5 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc
@@ -1433,7 +1433,9 @@ { auto fill = std::make_shared<SolidColorContents>(); fill->SetColor(Color::CornflowerBlue()); - ASSERT_FALSE(fill->ShouldRender(Entity{}, {100, 100})); + ASSERT_FALSE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); + ASSERT_FALSE( + fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); } // With path. @@ -1442,7 +1444,9 @@ fill->SetColor(Color::CornflowerBlue()); fill->SetPath( PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath()); - ASSERT_TRUE(fill->ShouldRender(Entity{}, {100, 100})); + ASSERT_TRUE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); + ASSERT_FALSE( + fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); } // With paint cover. @@ -1450,24 +1454,33 @@ auto fill = std::make_shared<SolidColorContents>(); fill->SetColor(Color::CornflowerBlue()); fill->SetCover(true); - ASSERT_TRUE(fill->ShouldRender(Entity{}, {100, 100})); + ASSERT_TRUE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); + ASSERT_TRUE( + fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); } } TEST_P(EntityTest, ClipContentsShouldRenderIsCorrect) { + // For clip ops, `ShouldRender` should always return true. + // Clip. { auto clip = std::make_shared<ClipContents>(); - ASSERT_TRUE(clip->ShouldRender(Entity{}, {100, 100})); + ASSERT_TRUE(clip->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); clip->SetPath( PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath()); - ASSERT_TRUE(clip->ShouldRender(Entity{}, {100, 100})); + ASSERT_TRUE(clip->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); + ASSERT_TRUE( + clip->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); } // Clip restore. { auto restore = std::make_shared<ClipRestoreContents>(); - ASSERT_TRUE(restore->ShouldRender(Entity{}, {100, 100})); + ASSERT_TRUE( + restore->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); + ASSERT_TRUE( + restore->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); } }