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