Reland Fixes MatrixFilterContents rendering/coverage #52880 (flutter/engine#52939)

fixes https://github.com/flutter/flutter/issues/147807
relands https://github.com/flutter/engine/pull/52880
relands https://github.com/flutter/engine/pull/43943

This was previously reverted because of the golden test failure `widgets.widgets.magnifier.styled`.  This fixes that problem by instead of focusing on ImageFilter and BackdropFilter subpasses, instead focuses on wether a subpass is clipped or not and by extension how the math should be handled.

`widgets.widgets.magnifier.styled` after diff:

![widgets widgets magnifier styled](https://github.com/flutter/engine/assets/30870216/d4611586-90f7-4d3e-90d8-018dd678d028)

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
gaaclarke 2024-05-21 12:09:25 -07:00 committed by GitHub
parent 7ae310f931
commit 6b11db0352
15 changed files with 415 additions and 57 deletions

View File

@ -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

View File

@ -2739,19 +2739,29 @@ TEST_P(AiksTest, VerticesGeometryColorUVPositionDataAdvancedBlend) {
}
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.

View File

@ -225,7 +225,8 @@ void Canvas::Save(bool create_subpass,
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::kSubpassAppendSnapshotTransform;
auto subpass = std::make_unique<EntityPass>();
if (backdrop_filter) {
EntityPass::BackdropFilterProc backdrop_filter_proc =
@ -261,7 +262,9 @@ bool Canvas::Restore() {
current_pass_->PopClips(num_clips, current_depth_);
if (transform_stack_.back().rendering_mode ==
Entity::RenderingMode::kSubpass) {
Entity::RenderingMode::kSubpassAppendSnapshotTransform ||
transform_stack_.back().rendering_mode ==
Entity::RenderingMode::kSubpassPrependSnapshotTransform) {
current_pass_->SetClipDepth(++current_depth_);
current_pass_ = GetCurrentPass().GetSuperpass();
FML_DCHECK(current_pass_);

View File

@ -230,7 +230,7 @@ void ExperimentalCanvas::SaveLayer(
<< 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::kSubpassAppendSnapshotTransform;
transform_stack_.emplace_back(entry);
auto inline_pass = std::make_unique<InlinePassContext>(
@ -272,7 +272,9 @@ bool ExperimentalCanvas::Restore() {
current_depth_ = transform_stack_.back().clip_depth;
if (transform_stack_.back().rendering_mode ==
Entity::RenderingMode::kSubpass) {
Entity::RenderingMode::kSubpassAppendSnapshotTransform ||
transform_stack_.back().rendering_mode ==
Entity::RenderingMode::kSubpassPrependSnapshotTransform) {
auto inline_pass = std::move(inline_pass_contexts_.back());
SaveLayerState save_layer_state = save_layer_state_.back();

View File

@ -68,7 +68,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);
WithImageFilter(input, effect_transform,
Entity::RenderingMode::kSubpassPrependSnapshotTransform);
if (image_filter) {
input = image_filter;
}

View File

@ -50,8 +50,9 @@ std::shared_ptr<Contents> PaintPassDelegate::CreateContentsForSubpassTarget(
std::shared_ptr<FilterContents> PaintPassDelegate::WithImageFilter(
const FilterInput::Variant& input,
const Matrix& effect_transform) const {
return paint_.WithImageFilter(input, effect_transform,
Entity::RenderingMode::kSubpass);
return paint_.WithImageFilter(
input, effect_transform,
Entity::RenderingMode::kSubpassPrependSnapshotTransform);
}
/// OpacityPeepholePassDelegate
@ -152,8 +153,9 @@ OpacityPeepholePassDelegate::CreateContentsForSubpassTarget(
std::shared_ptr<FilterContents> OpacityPeepholePassDelegate::WithImageFilter(
const FilterInput::Variant& input,
const Matrix& effect_transform) const {
return paint_.WithImageFilter(input, effect_transform,
Entity::RenderingMode::kSubpass);
return paint_.WithImageFilter(
input, effect_transform,
Entity::RenderingMode::kSubpassPrependSnapshotTransform);
}
} // namespace impeller

View File

@ -13,6 +13,7 @@ namespace testing {
using impeller::PlaygroundBackend;
using impeller::PlaygroundTest;
using impeller::Point;
INSTANTIATE_PLAYGROUND_SUITE(DlGoldenTest);
@ -48,5 +49,56 @@ TEST_P(DlGoldenTest, CanRenderImage) {
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

View File

@ -241,6 +241,7 @@ impeller_component("entity_unittests") {
"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",

View File

@ -28,6 +28,28 @@ void MatrixFilterContents::SetSamplerDescriptor(SamplerDescriptor desc) {
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::kSubpassAppendSnapshotTransform) {
return snapshot_transform * //
effect_transform * //
matrix * //
effect_transform.Invert();
} else {
FML_DCHECK(rendering_mode ==
Entity::RenderingMode::kSubpassPrependSnapshotTransform);
return effect_transform * //
matrix * //
effect_transform.Invert() * //
snapshot_transform;
}
}
} // namespace
std::optional<Entity> MatrixFilterContents::RenderFilter(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
@ -40,29 +62,45 @@ std::optional<Entity> MatrixFilterContents::RenderFilter(
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::kSubpassPrependSnapshotTransform ||
rendering_mode_ ==
Entity::RenderingMode::kSubpassAppendSnapshotTransform) {
// 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 +129,26 @@ std::optional<Rect> MatrixFilterContents::GetFilterCoverage(
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::kSubpassPrependSnapshotTransform ||
rendering_mode_ ==
Entity::RenderingMode::kSubpassAppendSnapshotTransform) {
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

View File

@ -0,0 +1,220 @@
// 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,
RenderCoverageMatchesGetCoverageClippedSubpassTranslate) {
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::kSubpassAppendSnapshotTransform);
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,
RenderCoverageMatchesGetCoverageClippedSubpassScale) {
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::kSubpassAppendSnapshotTransform);
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, RenderCoverageMatchesGetCoverageSubpassScale) {
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::kSubpassPrependSnapshotTransform);
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

View File

@ -32,7 +32,8 @@ class Entity {
/// rather than local space, and so some filters (namely,
/// MatrixFilterContents) need to interpret the given EffectTransform as the
/// current transform matrix.
kSubpass,
kSubpassAppendSnapshotTransform,
kSubpassPrependSnapshotTransform,
};
/// An enum to define how to repeat, fold, or omit colors outside of the

View File

@ -183,7 +183,8 @@ std::optional<Rect> EntityPass::GetElementsCoverage(
std::shared_ptr<FilterContents> backdrop_filter =
subpass.backdrop_filter_proc_(
FilterInput::Make(accumulated_coverage.value()),
subpass.transform_, Entity::RenderingMode::kSubpass);
subpass.transform_,
Entity::RenderingMode::kSubpassAppendSnapshotTransform);
if (backdrop_filter) {
auto backdrop_coverage = backdrop_filter->GetCoverage({});
unfiltered_coverage =
@ -570,9 +571,14 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
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(),
// When the subpass has a translation that means the math with
// the snapshot has to be different.
subpass->transform_.HasTranslation()
? Entity::RenderingMode::kSubpassPrependSnapshotTransform
: Entity::RenderingMode::kSubpassAppendSnapshotTransform);
// 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

View File

@ -331,6 +331,8 @@ struct Matrix {
return m[3] != 0 || m[7] != 0 || m[11] != 0 || m[15] != 1;
}
constexpr bool HasTranslation() const { return m[12] != 0 || m[13] != 0; }
constexpr bool IsAligned2D(Scalar tolerance = 0) const {
if (HasPerspective2D()) {
return false;

View File

@ -60,6 +60,13 @@ TEST(MatrixTest, HasPerspective) {
// clang-format on
}
TEST(MatrixTest, HasTranslation) {
EXPECT_TRUE(Matrix::MakeTranslation({100, 100, 0}).HasTranslation());
EXPECT_TRUE(Matrix::MakeTranslation({0, 100, 0}).HasTranslation());
EXPECT_TRUE(Matrix::MakeTranslation({100, 0, 0}).HasTranslation());
EXPECT_FALSE(Matrix().HasTranslation());
}
TEST(MatrixTest, IsAligned2D) {
EXPECT_TRUE(Matrix().IsAligned2D());
EXPECT_TRUE(Matrix::MakeScale({1.0f, 1.0f, 2.0f}).IsAligned2D());

View File

@ -774,6 +774,9 @@ impeller_Play_AiksTest_VerticesGeometryUVPositionDataWithTranslate_Vulkan.png
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