[Impeller] Add placeholder filter input. (#44290)

This is a revival of my patch to add the necessary mechanisms for:
https://github.com/flutter/flutter/issues/131632
... since it turns out we need it sooner than later for
https://github.com/flutter/flutter/issues/131182.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index dc1f98f..0fcc0e5 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -1201,6 +1201,8 @@
 ORIGIN: ../../../flutter/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/entity/contents/filters/inputs/filter_input.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/entity/contents/filters/inputs/filter_input.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/entity/contents/filters/inputs/placeholder_filter_input.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/entity/contents/filters/inputs/texture_filter_input.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/entity/contents/filters/inputs/texture_filter_input.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc + ../../../flutter/LICENSE
@@ -3903,6 +3905,8 @@
 FILE: ../../../flutter/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h
 FILE: ../../../flutter/impeller/entity/contents/filters/inputs/filter_input.cc
 FILE: ../../../flutter/impeller/entity/contents/filters/inputs/filter_input.h
+FILE: ../../../flutter/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc
+FILE: ../../../flutter/impeller/entity/contents/filters/inputs/placeholder_filter_input.h
 FILE: ../../../flutter/impeller/entity/contents/filters/inputs/texture_filter_input.cc
 FILE: ../../../flutter/impeller/entity/contents/filters/inputs/texture_filter_input.h
 FILE: ../../../flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc
diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn
index 8495a37..a09b7f9 100644
--- a/impeller/entity/BUILD.gn
+++ b/impeller/entity/BUILD.gn
@@ -175,6 +175,8 @@
     "contents/filters/inputs/filter_contents_filter_input.h",
     "contents/filters/inputs/filter_input.cc",
     "contents/filters/inputs/filter_input.h",
+    "contents/filters/inputs/placeholder_filter_input.cc",
+    "contents/filters/inputs/placeholder_filter_input.h",
     "contents/filters/inputs/texture_filter_input.cc",
     "contents/filters/inputs/texture_filter_input.h",
     "contents/filters/linear_to_srgb_filter_contents.cc",
diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc
index 36bb7bf..91ef1f0 100644
--- a/impeller/entity/contents/filters/filter_contents.cc
+++ b/impeller/entity/contents/filters/filter_contents.cc
@@ -277,4 +277,23 @@
   return parent_transform * GetLocalTransform(parent_transform);
 }
 
+bool FilterContents::IsLeaf() const {
+  for (auto& input : inputs_) {
+    if (!input->IsLeaf()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void FilterContents::SetLeafInputs(const FilterInput::Vector& inputs) {
+  if (IsLeaf()) {
+    inputs_ = inputs;
+    return;
+  }
+  for (auto& input : inputs_) {
+    input->SetLeafInputs(inputs);
+  }
+}
+
 }  // namespace impeller
diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h
index 270277d..739f653 100644
--- a/impeller/entity/contents/filters/filter_contents.h
+++ b/impeller/entity/contents/filters/filter_contents.h
@@ -133,6 +133,15 @@
 
   Matrix GetTransform(const Matrix& parent_transform) const;
 
+  /// @brief  Returns `true` if this filter does not have any `FilterInput`
+  ///         children.
+  bool IsLeaf() const;
+
+  /// @brief  Replaces the leaf of all leaf `FilterContents` with a new set
+  ///         of `inputs`.
+  /// @see    `FilterContents::IsLeaf`
+  void SetLeafInputs(const FilterInput::Vector& inputs);
+
  private:
   virtual std::optional<Rect> GetFilterCoverage(
       const FilterInput::Vector& inputs,
diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc
index c1960a1..5861247 100644
--- a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc
+++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc
@@ -58,4 +58,13 @@
   filter_->PopulateGlyphAtlas(lazy_glyph_atlas, scale);
 }
 
+bool FilterContentsFilterInput::IsLeaf() const {
+  return false;
+}
+
+void FilterContentsFilterInput::SetLeafInputs(
+    const FilterInput::Vector& inputs) {
+  filter_->SetLeafInputs(inputs);
+}
+
 }  // namespace impeller
diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h
index a4daf41..a10a5e5 100644
--- a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h
+++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h
@@ -36,8 +36,14 @@
       const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
       Scalar scale) override;
 
+  // |FilterInput|
+  bool IsLeaf() const override;
+
+  // |FilterInput|
+  void SetLeafInputs(const FilterInput::Vector& inputs) override;
+
  private:
-  FilterContentsFilterInput(std::shared_ptr<FilterContents> filter);
+  explicit FilterContentsFilterInput(std::shared_ptr<FilterContents> filter);
 
   std::shared_ptr<FilterContents> filter_;
   mutable std::optional<Snapshot> snapshot_;
diff --git a/impeller/entity/contents/filters/inputs/filter_input.cc b/impeller/entity/contents/filters/inputs/filter_input.cc
index 8b9b10d..612a1f7 100644
--- a/impeller/entity/contents/filters/inputs/filter_input.cc
+++ b/impeller/entity/contents/filters/inputs/filter_input.cc
@@ -11,6 +11,7 @@
 #include "impeller/entity/contents/filters/filter_contents.h"
 #include "impeller/entity/contents/filters/inputs/contents_filter_input.h"
 #include "impeller/entity/contents/filters/inputs/filter_contents_filter_input.h"
+#include "impeller/entity/contents/filters/inputs/placeholder_filter_input.h"
 #include "impeller/entity/contents/filters/inputs/texture_filter_input.h"
 
 namespace impeller {
@@ -32,6 +33,11 @@
     return Make(*texture, Matrix());
   }
 
+  if (auto rect = std::get_if<Rect>(&input)) {
+    return std::shared_ptr<PlaceholderFilterInput>(
+        new PlaceholderFilterInput(*rect));
+  }
+
   FML_UNREACHABLE();
 }
 
@@ -70,4 +76,10 @@
 
 FilterInput::~FilterInput() = default;
 
+bool FilterInput::IsLeaf() const {
+  return true;
+}
+
+void FilterInput::SetLeafInputs(const FilterInput::Vector& inputs) {}
+
 }  // namespace impeller
diff --git a/impeller/entity/contents/filters/inputs/filter_input.h b/impeller/entity/contents/filters/inputs/filter_input.h
index 019c2ac..3e84f1e 100644
--- a/impeller/entity/contents/filters/inputs/filter_input.h
+++ b/impeller/entity/contents/filters/inputs/filter_input.h
@@ -32,7 +32,8 @@
   using Vector = std::vector<FilterInput::Ref>;
   using Variant = std::variant<std::shared_ptr<FilterContents>,
                                std::shared_ptr<Contents>,
-                               std::shared_ptr<Texture>>;
+                               std::shared_ptr<Texture>,
+                               Rect>;
 
   virtual ~FilterInput();
 
@@ -67,6 +68,15 @@
   virtual void PopulateGlyphAtlas(
       const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
       Scalar scale);
+
+  /// @brief  Returns `true` unless this input is a `FilterInput`, which may
+  ///         take other inputs.
+  virtual bool IsLeaf() const;
+
+  /// @brief  Replaces the inputs of all leaf `FilterContents` with a new set
+  ///         of `inputs`.
+  /// @see    `FilterInput::IsLeaf`
+  virtual void SetLeafInputs(const FilterInput::Vector& inputs);
 };
 
 }  // namespace impeller
diff --git a/impeller/entity/contents/filters/inputs/filter_input_unittests.cc b/impeller/entity/contents/filters/inputs/filter_input_unittests.cc
index 0471a1a..1f3d55f 100644
--- a/impeller/entity/contents/filters/inputs/filter_input_unittests.cc
+++ b/impeller/entity/contents/filters/inputs/filter_input_unittests.cc
@@ -5,8 +5,10 @@
 #include <memory>
 #include "flutter/testing/testing.h"
 #include "gtest/gtest.h"
+#include "impeller/entity/contents/filters/color_filter_contents.h"
 #include "impeller/entity/contents/filters/inputs/filter_input.h"
 #include "impeller/entity/entity.h"
+#include "impeller/geometry/color.h"
 #include "impeller/geometry/geometry_asserts.h"
 
 namespace impeller {
@@ -25,5 +27,41 @@
                      Matrix::MakeTranslation({1.0, 2.0, 0.0}));
 }
 
+TEST(FilterInputTest, IsLeaf) {
+  std::shared_ptr<FilterContents> leaf =
+      ColorFilterContents::MakeBlend(BlendMode::kSource, {});
+  ASSERT_TRUE(leaf->IsLeaf());
+
+  auto base = ColorFilterContents::MakeMatrixFilter(
+      FilterInput::Make(leaf), Matrix(), {}, Matrix(), false);
+
+  ASSERT_TRUE(leaf->IsLeaf());
+  ASSERT_FALSE(base->IsLeaf());
+}
+
+TEST(FilterInputTest, SetCoverageInputs) {
+  std::shared_ptr<FilterContents> leaf =
+      ColorFilterContents::MakeBlend(BlendMode::kSource, {});
+  ASSERT_TRUE(leaf->IsLeaf());
+
+  auto base = ColorFilterContents::MakeMatrixFilter(
+      FilterInput::Make(leaf), Matrix(), {}, Matrix(), false);
+
+  {
+    auto result = base->GetCoverage({});
+    ASSERT_FALSE(result.has_value());
+  }
+
+  auto coverage_rect = Rect::MakeLTRB(100, 100, 200, 200);
+  base->SetLeafInputs(FilterInput::Make({coverage_rect}));
+
+  {
+    auto result = base->GetCoverage({});
+    ASSERT_TRUE(result.has_value());
+    // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
+    ASSERT_RECT_NEAR(result.value(), coverage_rect);
+  }
+}
+
 }  // namespace testing
 }  // namespace impeller
diff --git a/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc b/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc
new file mode 100644
index 0000000..5cc22d8
--- /dev/null
+++ b/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc
@@ -0,0 +1,40 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "impeller/entity/contents/filters/inputs/placeholder_filter_input.h"
+
+#include <optional>
+#include <utility>
+
+#include "impeller/base/strings.h"
+
+namespace impeller {
+
+PlaceholderFilterInput::PlaceholderFilterInput(Rect coverage_rect)
+    : coverage_rect_(coverage_rect) {}
+
+PlaceholderFilterInput::~PlaceholderFilterInput() = default;
+
+FilterInput::Variant PlaceholderFilterInput::GetInput() const {
+  return coverage_rect_;
+}
+
+std::optional<Snapshot> PlaceholderFilterInput::GetSnapshot(
+    const std::string& label,
+    const ContentContext& renderer,
+    const Entity& entity,
+    std::optional<Rect> coverage_limit) const {
+  return std::nullopt;
+}
+
+std::optional<Rect> PlaceholderFilterInput::GetCoverage(
+    const Entity& entity) const {
+  return coverage_rect_;
+}
+
+void PlaceholderFilterInput::PopulateGlyphAtlas(
+    const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
+    Scalar scale) {}
+
+}  // namespace impeller
diff --git a/impeller/entity/contents/filters/inputs/placeholder_filter_input.h b/impeller/entity/contents/filters/inputs/placeholder_filter_input.h
new file mode 100644
index 0000000..09d5be6
--- /dev/null
+++ b/impeller/entity/contents/filters/inputs/placeholder_filter_input.h
@@ -0,0 +1,41 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include "impeller/entity/contents/filters/inputs/filter_input.h"
+
+namespace impeller {
+
+class PlaceholderFilterInput final : public FilterInput {
+ public:
+  explicit PlaceholderFilterInput(Rect coverage);
+
+  ~PlaceholderFilterInput() override;
+
+  // |FilterInput|
+  Variant GetInput() const override;
+
+  // |FilterInput|
+  std::optional<Snapshot> GetSnapshot(
+      const std::string& label,
+      const ContentContext& renderer,
+      const Entity& entity,
+      std::optional<Rect> coverage_limit) const override;
+
+  // |FilterInput|
+  std::optional<Rect> GetCoverage(const Entity& entity) const override;
+
+  // |FilterInput|
+  void PopulateGlyphAtlas(
+      const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
+      Scalar scale) override;
+
+ private:
+  Rect coverage_rect_;
+
+  friend FilterInput;
+};
+
+}  // namespace impeller