Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3619,15 +3619,46 @@ TEST_P(AiksTest, MatrixImageFilterMagnify) {
canvas.Translate({600, -200});
canvas.SaveLayer({
.image_filter = std::make_shared<MatrixImageFilter>(
Matrix{
2, 0, 0, 0, //
0, 2, 0, 0, //
0, 0, 2, 0, //
0, 0, 0, 1 //
},
Matrix::MakeScale({2, 2, 2}), SamplerDescriptor{}),
});
canvas.DrawImage(image, {0, 0},
Paint{.color = Color::White().WithAlpha(0.5)});
canvas.Restore();

ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

// Render a white circle at the top left corner of the screen.
TEST_P(AiksTest, MatrixImageFilterDoesntCullWhenTranslatedFromOffscreen) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.Translate({100, 100});
// Draw a circle in a SaveLayer at -300, but move it back on-screen with a
// +300 translation applied by a SaveLayer image filter.
canvas.SaveLayer({
.image_filter = std::make_shared<MatrixImageFilter>(
Matrix::MakeTranslation({300, 0}), SamplerDescriptor{}),
});
canvas.DrawCircle({-300, 0}, 100, {.color = Color::Green()});
canvas.Restore();

ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

// Render a white circle at the top left corner of the screen.
TEST_P(AiksTest,
MatrixImageFilterDoesntCullWhenScaledAndTranslatedFromOffscreen) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.Translate({100, 100});
// Draw a circle in a SaveLayer at -300, but move it back on-screen with a
// +300 translation applied by a SaveLayer image filter.
canvas.SaveLayer({
.image_filter = std::make_shared<MatrixImageFilter>(
Matrix::MakeTranslation({300, 0}) * Matrix::MakeScale({2, 2, 2}),
SamplerDescriptor{}),
});
canvas.DrawImage(image, {0, 0}, Paint{.color = Color(1.0, 1.0, 1.0, 0.5)});
canvas.DrawCircle({-150, 0}, 50, {.color = Color::Green()});
canvas.Restore();

ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
Expand Down
2 changes: 1 addition & 1 deletion impeller/entity/contents/filters/matrix_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ std::optional<Entity> MatrixFilterContents::RenderFilter(
// 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
// 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!
//
Expand Down
20 changes: 13 additions & 7 deletions impeller/entity/entity_pass.cc
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,10 @@ std::optional<Rect> EntityPass::GetSubpassCoverage(
std::shared_ptr<FilterContents> image_filter =
subpass.delegate_->WithImageFilter(Rect(), subpass.xformation_);

// If the filter graph transforms the basis of the subpass, then its space
// has deviated too much from the parent pass to safely intersect with the
// pass coverage limit.
coverage_limit =
(image_filter && !image_filter->IsTranslationOnly() ? std::nullopt
: coverage_limit);
// If the subpass has an image filter, then its coverage space may deviate
// from the parent pass and make intersecting with the pass coverage limit
// unsafe.
coverage_limit = image_filter ? std::nullopt : coverage_limit;

auto entities_coverage = subpass.GetElementsCoverage(coverage_limit);
// The entities don't cover anything. There is nothing to do.
Expand Down Expand Up @@ -641,6 +639,14 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
auto subpass_capture = capture.CreateChild("EntityPass");
subpass_capture.AddRect("Coverage", *subpass_coverage, {.readonly = true});

// Start non-collapsed subpasses with a fresh clip coverage stack limited by
// the subpass coverage. This is important because image filters applied to
// save layers may transform the subpass texture after it's rendered,
// causing parent clip coverage to get misaligned with the actual area that
// the subpass will affect in the parent pass.
ClipCoverageStack subpass_clip_coverage_stack = {ClipCoverageLayer{
.coverage = subpass_coverage, .clip_depth = subpass->clip_depth_}};

// Stencil textures aren't shared between EntityPasses (as much of the
// time they are transient).
if (!subpass->OnRender(
Expand All @@ -652,7 +658,7 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
subpass_coverage->origin -
global_pass_position, // local_pass_position
++pass_depth, // pass_depth
clip_coverage_stack, // clip_coverage_stack
subpass_clip_coverage_stack, // clip_coverage_stack
subpass->clip_depth_, // clip_depth_floor
subpass_backdrop_filter_contents // backdrop_filter_contents
)) {
Expand Down
16 changes: 15 additions & 1 deletion impeller/entity/entity_pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,21 @@ class EntityPass {
void SetEnableOffscreenCheckerboard(bool enabled);

//----------------------------------------------------------------------------
/// @brief Get the coverage of an unfiltered subpass.
/// @brief Computes the coverage of a given subpass. This is used to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please include the coordinate space of the returned rect? I think it should be relative to it's parent subpass in pixels?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

I think it should be relative to it's parent subpass in pixels?

Actually, the output is in screen space. :)

After writing #46524 (comment), I also decided to do another iteration on the GetCoverage docstrings.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! 🙏

/// determine the texture size of a given subpass before it's rendered
/// to and passed through the subpass ImageFilter, if any.
///
/// @param[in] subpass The EntityPass for which to compute
/// pre-filteredcoverage.
/// @param[in] coverage_limit Confines coverage to a specified area. This
/// hint is used to trim coverage to the root
/// framebuffer area. `std::nullopt` means there
/// is no limit.
///
/// @return The screen space pixel area that the subpass contents will render
/// into, prior to being transformed by the subpass ImageFilter, if
/// any. `std::nullopt` means rendering the subpass will have no
/// effect on the color attachment.
///
std::optional<Rect> GetSubpassCoverage(
const EntityPass& subpass,
Expand Down