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