Fixes MatrixFilterContents rendering/coverage (#52880)

fixes: https://github.com/flutter/flutter/issues/147807
relands https://github.com/flutter/engine/pull/43943 (with fixes that hopefully avoid it being reverted again)

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files
index 37cc6de..62ff8bc 100644
--- a/ci/licenses_golden/excluded_files
+++ b/ci/licenses_golden/excluded_files
@@ -147,6 +147,7 @@
 ../../../flutter/impeller/entity/contents/clip_contents_unittests.cc
 ../../../flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc
 ../../../flutter/impeller/entity/contents/filters/inputs/filter_input_unittests.cc
+../../../flutter/impeller/entity/contents/filters/matrix_filter_contents_unittests.cc
 ../../../flutter/impeller/entity/contents/host_buffer_unittests.cc
 ../../../flutter/impeller/entity/contents/test
 ../../../flutter/impeller/entity/contents/tiled_texture_contents_unittests.cc
diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc
index 12c5414..a99bff8 100644
--- a/impeller/aiks/aiks_unittests.cc
+++ b/impeller/aiks/aiks_unittests.cc
@@ -2772,19 +2772,29 @@
 }
 
 TEST_P(AiksTest, MatrixImageFilterMagnify) {
-  Canvas canvas;
-  canvas.Scale(GetContentScale());
-  auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
-  canvas.Translate({600, -200});
-  canvas.SaveLayer({
-      .image_filter = std::make_shared<MatrixImageFilter>(
-          Matrix::MakeScale({2, 2, 2}), SamplerDescriptor{}),
-  });
-  canvas.DrawImage(image, {0, 0},
-                   Paint{.color = Color::White().WithAlpha(0.5)});
-  canvas.Restore();
+  Scalar scale = 2.0;
+  auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
+    if (AiksTest::ImGuiBegin("Controls", nullptr,
+                             ImGuiWindowFlags_AlwaysAutoResize)) {
+      ImGui::SliderFloat("Scale", &scale, 1, 2);
+      ImGui::End();
+    }
+    Canvas canvas;
+    canvas.Scale(GetContentScale());
+    auto image =
+        std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
+    canvas.Translate({600, -200});
+    canvas.SaveLayer({
+        .image_filter = std::make_shared<MatrixImageFilter>(
+            Matrix::MakeScale({scale, scale, 1}), SamplerDescriptor{}),
+    });
+    canvas.DrawImage(image, {0, 0},
+                     Paint{.color = Color::White().WithAlpha(0.5)});
+    canvas.Restore();
+    return canvas.EndRecordingAsPicture();
+  };
 
-  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
+  ASSERT_TRUE(OpenPlaygroundHere(callback));
 }
 
 // Render a white circle at the top left corner of the screen.
diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc
index 4692d11..ace3495 100644
--- a/impeller/aiks/canvas.cc
+++ b/impeller/aiks/canvas.cc
@@ -225,7 +225,7 @@
   entry.cull_rect = transform_stack_.back().cull_rect;
   entry.clip_height = transform_stack_.back().clip_height;
   if (create_subpass) {
-    entry.rendering_mode = Entity::RenderingMode::kSubpass;
+    entry.rendering_mode = Entity::RenderingMode::kBackdropSubpass;
     auto subpass = std::make_unique<EntityPass>();
     if (backdrop_filter) {
       EntityPass::BackdropFilterProc backdrop_filter_proc =
@@ -261,7 +261,9 @@
   current_pass_->PopClips(num_clips, current_depth_);
 
   if (transform_stack_.back().rendering_mode ==
-      Entity::RenderingMode::kSubpass) {
+          Entity::RenderingMode::kBackdropSubpass ||
+      transform_stack_.back().rendering_mode ==
+          Entity::RenderingMode::kImageFilterSubpass) {
     current_pass_->SetClipDepth(++current_depth_);
     current_pass_ = GetCurrentPass().GetSuperpass();
     FML_DCHECK(current_pass_);
diff --git a/impeller/aiks/experimental_canvas.cc b/impeller/aiks/experimental_canvas.cc
index 7837b0c..b8df394 100644
--- a/impeller/aiks/experimental_canvas.cc
+++ b/impeller/aiks/experimental_canvas.cc
@@ -230,7 +230,7 @@
       << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth
       << " after allocating " << total_content_depth;
   entry.clip_height = transform_stack_.back().clip_height;
-  entry.rendering_mode = Entity::RenderingMode::kSubpass;
+  entry.rendering_mode = Entity::RenderingMode::kBackdropSubpass;
   transform_stack_.emplace_back(entry);
 
   auto inline_pass = std::make_unique<InlinePassContext>(
@@ -272,7 +272,9 @@
   current_depth_ = transform_stack_.back().clip_depth;
 
   if (transform_stack_.back().rendering_mode ==
-      Entity::RenderingMode::kSubpass) {
+          Entity::RenderingMode::kBackdropSubpass ||
+      transform_stack_.back().rendering_mode ==
+          Entity::RenderingMode::kImageFilterSubpass) {
     auto inline_pass = std::move(inline_pass_contexts_.back());
 
     SaveLayerState save_layer_state = save_layer_state_.back();
diff --git a/impeller/aiks/paint.cc b/impeller/aiks/paint.cc
index 1efb587..32252b3 100644
--- a/impeller/aiks/paint.cc
+++ b/impeller/aiks/paint.cc
@@ -67,8 +67,8 @@
 std::shared_ptr<Contents> Paint::WithFiltersForSubpassTarget(
     std::shared_ptr<Contents> input,
     const Matrix& effect_transform) const {
-  auto image_filter =
-      WithImageFilter(input, effect_transform, Entity::RenderingMode::kSubpass);
+  auto image_filter = WithImageFilter(
+      input, effect_transform, Entity::RenderingMode::kImageFilterSubpass);
   if (image_filter) {
     input = image_filter;
   }
diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc
index 3461048..2879075 100644
--- a/impeller/aiks/paint_pass_delegate.cc
+++ b/impeller/aiks/paint_pass_delegate.cc
@@ -51,7 +51,7 @@
     const FilterInput::Variant& input,
     const Matrix& effect_transform) const {
   return paint_.WithImageFilter(input, effect_transform,
-                                Entity::RenderingMode::kSubpass);
+                                Entity::RenderingMode::kImageFilterSubpass);
 }
 
 /// OpacityPeepholePassDelegate
@@ -153,7 +153,7 @@
     const FilterInput::Variant& input,
     const Matrix& effect_transform) const {
   return paint_.WithImageFilter(input, effect_transform,
-                                Entity::RenderingMode::kSubpass);
+                                Entity::RenderingMode::kImageFilterSubpass);
 }
 
 }  // namespace impeller
diff --git a/impeller/display_list/dl_golden_unittests.cc b/impeller/display_list/dl_golden_unittests.cc
index 81f9e70..89f04aa 100644
--- a/impeller/display_list/dl_golden_unittests.cc
+++ b/impeller/display_list/dl_golden_unittests.cc
@@ -13,6 +13,7 @@
 
 using impeller::PlaygroundBackend;
 using impeller::PlaygroundTest;
+using impeller::Point;
 
 INSTANTIATE_PLAYGROUND_SUITE(DlGoldenTest);
 
@@ -48,5 +49,56 @@
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
+// Asserts that subpass rendering of MatrixImageFilters works.
+// https://github.com/flutter/flutter/issues/147807
+TEST_P(DlGoldenTest, Bug147807) {
+  Point content_scale = GetContentScale();
+  auto draw = [content_scale](DlCanvas* canvas,
+                              const std::vector<sk_sp<DlImage>>& images) {
+    canvas->Transform2DAffine(content_scale.x, 0, 0, 0, content_scale.y, 0);
+    DlPaint paint;
+    paint.setColor(DlColor(0xfffef7ff));
+    canvas->DrawRect(SkRect::MakeLTRB(0, 0, 375, 667), paint);
+    paint.setColor(DlColor(0xffff9800));
+    canvas->DrawRect(SkRect::MakeLTRB(0, 0, 187.5, 333.5), paint);
+    paint.setColor(DlColor(0xff9c27b0));
+    canvas->DrawRect(SkRect::MakeLTRB(187.5, 0, 375, 333.5), paint);
+    paint.setColor(DlColor(0xff4caf50));
+    canvas->DrawRect(SkRect::MakeLTRB(0, 333.5, 187.5, 667), paint);
+    paint.setColor(DlColor(0xfff44336));
+    canvas->DrawRect(SkRect::MakeLTRB(187.5, 333.5, 375, 667), paint);
+
+    canvas->Save();
+    {
+      canvas->ClipRRect(
+          SkRRect::MakeOval(SkRect::MakeLTRB(201.25, 10, 361.25, 170)),
+          DlCanvas::ClipOp::kIntersect, true);
+      SkRect save_layer_bounds = SkRect::MakeLTRB(201.25, 10, 361.25, 170);
+      DlMatrixImageFilter backdrop(SkMatrix::MakeAll(3, 0, -280,  //
+                                                     0, 3, -920,  //
+                                                     0, 0, 1),
+                                   DlImageSampling::kLinear);
+      canvas->SaveLayer(&save_layer_bounds, /*paint=*/nullptr, &backdrop);
+      {
+        canvas->Translate(201.25, 10);
+        auto paint = DlPaint()
+                         .setAntiAlias(true)
+                         .setColor(DlColor(0xff2196f3))
+                         .setStrokeWidth(5)
+                         .setDrawStyle(DlDrawStyle::kStroke);
+        canvas->DrawCircle(SkPoint::Make(80, 80), 80, paint);
+      }
+      canvas->Restore();
+    }
+    canvas->Restore();
+  };
+
+  DisplayListBuilder builder;
+  std::vector<sk_sp<DlImage>> images;
+  draw(&builder, images);
+
+  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
+}
+
 }  // namespace testing
 }  // namespace flutter
diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn
index c529d46..fbf419d 100644
--- a/impeller/entity/BUILD.gn
+++ b/impeller/entity/BUILD.gn
@@ -241,6 +241,7 @@
     "contents/clip_contents_unittests.cc",
     "contents/filters/gaussian_blur_filter_contents_unittests.cc",
     "contents/filters/inputs/filter_input_unittests.cc",
+    "contents/filters/matrix_filter_contents_unittests.cc",
     "contents/host_buffer_unittests.cc",
     "contents/tiled_texture_contents_unittests.cc",
     "entity_pass_target_unittests.cc",
diff --git a/impeller/entity/contents/filters/matrix_filter_contents.cc b/impeller/entity/contents/filters/matrix_filter_contents.cc
index 9681e8a..1c73cd3 100644
--- a/impeller/entity/contents/filters/matrix_filter_contents.cc
+++ b/impeller/entity/contents/filters/matrix_filter_contents.cc
@@ -28,6 +28,26 @@
   sampler_descriptor_ = std::move(desc);
 }
 
+namespace {
+Matrix CalculateSubpassTransform(const Matrix& snapshot_transform,
+                                 const Matrix& effect_transform,
+                                 const Matrix& matrix,
+                                 Entity::RenderingMode rendering_mode) {
+  if (rendering_mode == Entity::RenderingMode::kBackdropSubpass) {
+    return snapshot_transform *  //
+           effect_transform *    //
+           matrix *              //
+           effect_transform.Invert();
+  } else {
+    FML_DCHECK(rendering_mode == Entity::RenderingMode::kImageFilterSubpass);
+    return effect_transform *           //
+           matrix *                     //
+           effect_transform.Invert() *  //
+           snapshot_transform;
+  }
+}
+}  // namespace
+
 std::optional<Entity> MatrixFilterContents::RenderFilter(
     const FilterInput::Vector& inputs,
     const ContentContext& renderer,
@@ -40,29 +60,43 @@
     return std::nullopt;
   }
 
-  // The filter's matrix needs to be applied within the space defined by the
-  // scene's current transform matrix (CTM). For example: If the CTM is
-  // scaled up, then translations applied by the matrix should be magnified
-  // accordingly.
-  //
-  // To accomplish this, we sandwich the filter's matrix within the CTM in both
-  // cases. But notice that for the subpass backdrop filter case, we use the
-  // "effect transform" instead of the Entity's transform!
-  //
-  // That's because in the subpass backdrop filter case, the Entity's transform
-  // isn't actually the captured CTM of the scene like it usually is; instead,
-  // it's just a screen space translation that offsets the backdrop texture (as
-  // mentioned above). And so we sneak the subpass's captured CTM in through the
-  // effect transform.
-
-  auto transform = rendering_mode_ == Entity::RenderingMode::kSubpass
-                       ? effect_transform
-                       : entity.GetTransform();
-  snapshot->transform = transform *           //
-                        matrix_ *             //
-                        transform.Invert() *  //
-                        snapshot->transform;
-
+  if (rendering_mode_ == Entity::RenderingMode::kImageFilterSubpass ||
+      rendering_mode_ == Entity::RenderingMode::kBackdropSubpass) {
+    // There are two special quirks with how Matrix filters behave when used as
+    // subpass backdrop filters:
+    //
+    // 1. For subpass backdrop filters, the snapshot transform is always just a
+    //    translation that positions the parent pass texture correctly relative
+    //    to the subpass texture. However, this translation always needs to be
+    //    applied in screen space.
+    //
+    //    Since we know the snapshot transform will always have an identity
+    //    basis in this case, we safely reverse the order and apply the filter's
+    //    matrix within the snapshot transform space.
+    //
+    // 2. The filter's matrix needs to be applied within the space defined by
+    //    the scene's current transformation matrix (CTM). For example: If the
+    //    CTM is scaled up, then translations applied by the matrix should be
+    //    magnified accordingly.
+    //
+    //    To accomplish this, we sandwitch the filter's matrix within the CTM in
+    //    both cases. But notice that for the subpass backdrop filter case, we
+    //    use the "effect transform" instead of the Entity's transform!
+    //
+    //    That's because in the subpass backdrop filter case, the Entity's
+    //    transform isn't actually the captured CTM of the scene like it usually
+    //    is; instead, it's just a screen space translation that offsets the
+    //    backdrop texture (as mentioned above). And so we sneak the subpass's
+    //    captured CTM in through the effect transform.
+    //
+    snapshot->transform = CalculateSubpassTransform(
+        snapshot->transform, effect_transform, matrix_, rendering_mode_);
+  } else {
+    snapshot->transform = entity.GetTransform() *           //
+                          matrix_ *                         //
+                          entity.GetTransform().Invert() *  //
+                          snapshot->transform;
+  }
   snapshot->sampler_descriptor = sampler_descriptor_;
   if (!snapshot.has_value()) {
     return std::nullopt;
@@ -91,17 +125,24 @@
     return std::nullopt;
   }
 
-  auto coverage = inputs[0]->GetCoverage(entity);
+  std::optional<Rect> coverage = inputs[0]->GetCoverage(entity);
   if (!coverage.has_value()) {
     return std::nullopt;
   }
-  auto& m = rendering_mode_ == Entity::RenderingMode::kSubpass
-                ? effect_transform
-                : inputs[0]->GetTransform(entity);
-  auto transform = m *          //
-                   matrix_ *    //
-                   m.Invert();  //
-  return coverage->TransformBounds(transform);
+
+  Matrix input_transform = inputs[0]->GetTransform(entity);
+  if (rendering_mode_ == Entity::RenderingMode::kImageFilterSubpass ||
+      rendering_mode_ == Entity::RenderingMode::kBackdropSubpass) {
+    Rect coverage_bounds = coverage->TransformBounds(input_transform.Invert());
+    Matrix transform = CalculateSubpassTransform(
+        input_transform, effect_transform, matrix_, rendering_mode_);
+    return coverage_bounds.TransformBounds(transform);
+  } else {
+    Matrix transform = input_transform *          //
+                       matrix_ *                  //
+                       input_transform.Invert();  //
+    return coverage->TransformBounds(transform);
+  }
 }
 
 }  // namespace impeller
diff --git a/impeller/entity/contents/filters/matrix_filter_contents_unittests.cc b/impeller/entity/contents/filters/matrix_filter_contents_unittests.cc
new file mode 100644
index 0000000..3c2a3af
--- /dev/null
+++ b/impeller/entity/contents/filters/matrix_filter_contents_unittests.cc
@@ -0,0 +1,218 @@
+// 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 "flutter/testing/testing.h"
+#include "gmock/gmock.h"
+#include "impeller/entity/contents/filters/matrix_filter_contents.h"
+#include "impeller/entity/entity_playground.h"
+#include "impeller/geometry/geometry_asserts.h"
+
+namespace impeller {
+namespace testing {
+
+class MatrixFilterContentsTest : public EntityPlayground {
+ public:
+  /// Create a texture that has been cleared to transparent black.
+  std::shared_ptr<Texture> MakeTexture(ISize size) {
+    std::shared_ptr<CommandBuffer> command_buffer =
+        GetContentContext()->GetContext()->CreateCommandBuffer();
+    if (!command_buffer) {
+      return nullptr;
+    }
+
+    auto render_target = GetContentContext()->MakeSubpass(
+        "Clear Subpass", size, command_buffer,
+        [](const ContentContext&, RenderPass&) { return true; });
+
+    if (!GetContentContext()
+             ->GetContext()
+             ->GetCommandQueue()
+             ->Submit(/*buffers=*/{command_buffer})
+             .ok()) {
+      return nullptr;
+    }
+
+    if (render_target.ok()) {
+      return render_target.value().GetRenderTargetTexture();
+    }
+    return nullptr;
+  }
+};
+
+INSTANTIATE_PLAYGROUND_SUITE(MatrixFilterContentsTest);
+
+TEST(MatrixFilterContentsTest, Create) {
+  MatrixFilterContents contents;
+  EXPECT_TRUE(contents.IsTranslationOnly());
+}
+
+TEST(MatrixFilterContentsTest, CoverageEmpty) {
+  MatrixFilterContents contents;
+  FilterInput::Vector inputs = {};
+  Entity entity;
+  std::optional<Rect> coverage =
+      contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
+  ASSERT_FALSE(coverage.has_value());
+}
+
+TEST(MatrixFilterContentsTest, CoverageSimple) {
+  MatrixFilterContents contents;
+  FilterInput::Vector inputs = {
+      FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))};
+  Entity entity;
+  std::optional<Rect> coverage =
+      contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
+
+  ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110));
+}
+
+TEST(MatrixFilterContentsTest, Coverage2x) {
+  MatrixFilterContents contents;
+  contents.SetMatrix(Matrix::MakeScale({2.0, 2.0, 1.0}));
+  FilterInput::Vector inputs = {
+      FilterInput::Make(Rect::MakeXYWH(10, 10, 100, 100))};
+  Entity entity;
+  std::optional<Rect> coverage =
+      contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
+
+  ASSERT_EQ(coverage, Rect::MakeXYWH(20, 20, 200, 200));
+}
+
+TEST(MatrixFilterContentsTest, Coverage2xEffect) {
+  MatrixFilterContents contents;
+  FilterInput::Vector inputs = {
+      FilterInput::Make(Rect::MakeXYWH(10, 10, 100, 100))};
+  Entity entity;
+  std::optional<Rect> coverage = contents.GetFilterCoverage(
+      inputs, entity, /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}));
+
+  ASSERT_EQ(coverage, Rect::MakeXYWH(10, 10, 100, 100));
+}
+
+namespace {
+void expectRenderCoverageEqual(const std::optional<Entity>& result,
+                               const std::optional<Rect> contents_coverage,
+                               const Rect& expected) {
+  EXPECT_TRUE(result.has_value());
+  if (result.has_value()) {
+    EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
+    std::optional<Rect> result_coverage = result.value().GetCoverage();
+    EXPECT_TRUE(result_coverage.has_value());
+    EXPECT_TRUE(contents_coverage.has_value());
+    if (result_coverage.has_value() && contents_coverage.has_value()) {
+      EXPECT_TRUE(RectNear(contents_coverage.value(), expected));
+      EXPECT_TRUE(RectNear(result_coverage.value(), expected));
+    }
+  }
+}
+}  // namespace
+
+TEST_P(MatrixFilterContentsTest, RenderCoverageMatchesGetCoverageIdentity) {
+  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
+  MatrixFilterContents contents;
+  contents.SetInputs({FilterInput::Make(texture)});
+
+  Entity entity;
+  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
+
+  std::shared_ptr<ContentContext> renderer = GetContentContext();
+  std::optional<Entity> result =
+      contents.GetEntity(*renderer, entity, /*coverage_hint=*/{});
+  expectRenderCoverageEqual(result, contents.GetCoverage(entity),
+                            Rect::MakeXYWH(100, 200, 100, 100));
+}
+
+TEST_P(MatrixFilterContentsTest, RenderCoverageMatchesGetCoverageTranslate) {
+  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
+  MatrixFilterContents contents;
+  contents.SetInputs({FilterInput::Make(texture)});
+  contents.SetMatrix(Matrix::MakeTranslation({50, 100, 0}));
+  contents.SetEffectTransform(Matrix::MakeScale({2, 2, 1}));
+
+  Entity entity;
+  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
+
+  std::shared_ptr<ContentContext> renderer = GetContentContext();
+  std::optional<Entity> result =
+      contents.GetEntity(*renderer, entity, /*coverage_hint=*/{});
+  expectRenderCoverageEqual(result, contents.GetCoverage(entity),
+                            Rect::MakeXYWH(150, 300, 100, 100));
+}
+
+TEST_P(MatrixFilterContentsTest,
+       RenderCoverageMatchesGetCoverageBackdropSubpassTranslate) {
+  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
+  MatrixFilterContents contents;
+  contents.SetInputs({FilterInput::Make(texture)});
+  contents.SetMatrix(Matrix::MakeTranslation({50, 100, 0}));
+  contents.SetEffectTransform(Matrix::MakeScale({2, 2, 1}));
+  contents.SetRenderingMode(Entity::RenderingMode::kBackdropSubpass);
+
+  Entity entity;
+  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
+
+  std::shared_ptr<ContentContext> renderer = GetContentContext();
+  std::optional<Entity> result =
+      contents.GetEntity(*renderer, entity, /*coverage_hint=*/{});
+  expectRenderCoverageEqual(result, contents.GetCoverage(entity),
+                            Rect::MakeXYWH(200, 400, 100, 100));
+}
+
+TEST_P(MatrixFilterContentsTest, RenderCoverageMatchesGetCoverageScale) {
+  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
+  MatrixFilterContents contents;
+  contents.SetInputs({FilterInput::Make(texture)});
+  contents.SetMatrix(Matrix::MakeScale({3, 3, 1}));
+  contents.SetEffectTransform(Matrix::MakeScale({2, 2, 1}));
+
+  Entity entity;
+  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
+
+  std::shared_ptr<ContentContext> renderer = GetContentContext();
+  std::optional<Entity> result =
+      contents.GetEntity(*renderer, entity, /*coverage_hint=*/{});
+  expectRenderCoverageEqual(result, contents.GetCoverage(entity),
+                            Rect::MakeXYWH(100, 200, 300, 300));
+}
+
+TEST_P(MatrixFilterContentsTest,
+       RenderCoverageMatchesGetCoverageBackdropSubpassScale) {
+  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
+  MatrixFilterContents contents;
+  contents.SetInputs({FilterInput::Make(texture)});
+  contents.SetMatrix(Matrix::MakeScale({3, 3, 1}));
+  contents.SetEffectTransform(Matrix::MakeScale({2, 2, 1}));
+  contents.SetRenderingMode(Entity::RenderingMode::kBackdropSubpass);
+
+  Entity entity;
+  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
+
+  std::shared_ptr<ContentContext> renderer = GetContentContext();
+  std::optional<Entity> result =
+      contents.GetEntity(*renderer, entity, /*coverage_hint=*/{});
+  expectRenderCoverageEqual(result, contents.GetCoverage(entity),
+                            Rect::MakeXYWH(100, 200, 300, 300));
+}
+
+TEST_P(MatrixFilterContentsTest,
+       RenderCoverageMatchesGetCoverageImageFilterSubpassScale) {
+  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
+  MatrixFilterContents contents;
+  contents.SetInputs({FilterInput::Make(texture)});
+  contents.SetMatrix(Matrix::MakeScale({3, 3, 1}));
+  contents.SetEffectTransform(Matrix::MakeScale({2, 2, 1}));
+  contents.SetRenderingMode(Entity::RenderingMode::kImageFilterSubpass);
+
+  Entity entity;
+  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
+
+  std::shared_ptr<ContentContext> renderer = GetContentContext();
+  std::optional<Entity> result =
+      contents.GetEntity(*renderer, entity, /*coverage_hint=*/{});
+  expectRenderCoverageEqual(result, contents.GetCoverage(entity),
+                            Rect::MakeXYWH(300, 600, 300, 300));
+}
+
+}  // namespace testing
+}  // namespace impeller
diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h
index d2d4eb9..293a178 100644
--- a/impeller/entity/entity.h
+++ b/impeller/entity/entity.h
@@ -33,7 +33,8 @@
     /// rather than local space, and so some filters (namely,
     /// MatrixFilterContents) need to interpret the given EffectTransform as the
     /// current transform matrix.
-    kSubpass,
+    kBackdropSubpass,
+    kImageFilterSubpass,
   };
 
   /// An enum to define how to repeat, fold, or omit colors outside of the
diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc
index 596f441..ee28a0d 100644
--- a/impeller/entity/entity_pass.cc
+++ b/impeller/entity/entity_pass.cc
@@ -185,7 +185,7 @@
         std::shared_ptr<FilterContents> backdrop_filter =
             subpass.backdrop_filter_proc_(
                 FilterInput::Make(accumulated_coverage.value()),
-                subpass.transform_, Entity::RenderingMode::kSubpass);
+                subpass.transform_, Entity::RenderingMode::kBackdropSubpass);
         if (backdrop_filter) {
           auto backdrop_coverage = backdrop_filter->GetCoverage({});
           unfiltered_coverage =
@@ -585,9 +585,9 @@
       auto texture = pass_context.GetTexture();
       // Render the backdrop texture before any of the pass elements.
       const auto& proc = subpass->backdrop_filter_proc_;
-      subpass_backdrop_filter_contents =
-          proc(FilterInput::Make(std::move(texture)),
-               subpass->transform_.Basis(), Entity::RenderingMode::kSubpass);
+      subpass_backdrop_filter_contents = proc(
+          FilterInput::Make(std::move(texture)), subpass->transform_.Basis(),
+          Entity::RenderingMode::kBackdropSubpass);
 
       // If the very first thing we render in this EntityPass is a subpass that
       // happens to have a backdrop filter, than that backdrop filter will end
diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt
index 1ec4f81..c291136 100644
--- a/testing/impeller_golden_tests_output.txt
+++ b/testing/impeller_golden_tests_output.txt
@@ -777,6 +777,9 @@
 impeller_Play_AiksTest_VerticesGeometryUVPositionData_Metal.png
 impeller_Play_AiksTest_VerticesGeometryUVPositionData_OpenGLES.png
 impeller_Play_AiksTest_VerticesGeometryUVPositionData_Vulkan.png
+impeller_Play_DlGoldenTest_Bug147807_Metal.png
+impeller_Play_DlGoldenTest_Bug147807_OpenGLES.png
+impeller_Play_DlGoldenTest_Bug147807_Vulkan.png
 impeller_Play_DlGoldenTest_CanDrawPaint_Metal.png
 impeller_Play_DlGoldenTest_CanDrawPaint_OpenGLES.png
 impeller_Play_DlGoldenTest_CanDrawPaint_Vulkan.png