Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Impeller] Add backdrop filter support; refactor EntityPass #33887

Merged
merged 13 commits into from
Jun 15, 2022

Conversation

bdero
Copy link
Member

@bdero bdero commented Jun 7, 2022

Adding backdrop filter support required adding a little bit of additional logic to the EntityPass, which was in dire need for some simplification, so I ended up doing some refactoring here.

The first 3 commits of this PR are mostly a refactor to wrangle the EntityPass logic in a more reasonable way:

  • Move the interleaving renderpass logic into an enclosed InlinePassContext with clear contracts. Passes can be ended at any time during EntityPass::OnRender. A new RenderPass with the right configuration will get created if necessary whenever the pass is fetched. This removes most of the state in the render method and makes reasoning about EntityPass::OnRender method way easier.
  • Per-Element Entity resolution during at render time is now in its own method: EntityPass::GetEntityForElement
    • Part of the motivation here is to make the possible outcomes from this resolution clearer (skip, fail, and success if there's actually something to render). This can be broken down further in the future

Conveniently, most of the behavior we actually needed to support the backdrop filter was already done thanks to the prior work on advanced blends:

  • A callback to generate the backdrop filter is stored in the EntityPass when SaveLayer is called.
  • When the backdrop filter proc is set, the parent pass will hand the child pass its texture at render time.
  • When said child pass is rendering, it runs the given texture through the backdrop filter proc as a TextureFilterInput, and then renders the resulting contents before any pass elements are rendered.
  • The running contains_advanced_blends_ flag is now reads_from_pass_texture_, and is set to true when the EntityPass contains any advanced blended elements or subpasses with backdrop filters. Behavior of this flag remains otherwise unchanged (it makes the root pass create an intermediate texture, and it makes subpasses create non-transient stencils).
Screen.Recording.2022-06-07.at.5.12.36.PM.mov

The demo started out more visually interesting, but it turns out that the presence of the backdrop filter is supposed to override the layer's coverage, whether or not entities or the SaveLayer bounds hint is present.

@bdero bdero requested a review from flar June 7, 2022 21:44
@bdero bdero changed the title [Impeller] Add backdrop filter support; some EntityPass refactoring [Impeller] Add backdrop filter support; refactor EntityPass Jun 7, 2022
Copy link
Contributor

@flar flar left a comment

Choose a reason for hiding this comment

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

I didn't look into all of the re-organization of the passes and entities, but I just did a cursory inspection of the logic to handle the backdrop filter - so I'm posting this as a comment review...

UNIMPLEMENTED;
canvas_.SaveLayer(paint, ToRect(bounds), ToImageFilterProc(backdrop));
} else {
canvas_.SaveLayer(paint, ToRect(bounds));
Copy link
Contributor

Choose a reason for hiding this comment

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

canvas_.SaveLayer takes an optional backdrop. Does having a special call omitting the argument save much?

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.

}

if (subpass_coverage->size.IsEmpty()) {
// It is not an error to have an empty subpass. But subpasses that can't
// create their intermediates must trip errors.
continue;
return EntityPass::EntityResult::Skip();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible for the subpass to be non-"empty", but produce an empty coverage? If so, then we don't want to skip here if there is a backdrop filter.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another way to look at this is that the subpass has some coverage, and if there is an optional backdrop filter, then it also has some coverage, and then you combine the two (and clip the combined coverage). Now, if that combined clipped coverage is null, then it's OK to skip.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

const auto subpass_coverage = GetSubpassCoverage(*subpass);
if (subpass->elements_.empty() && backdrop_filter_proc_.has_value()) {
// An empty subpass with a backdrop filter should render the backdrop over
// the entire coverage of the current pass.
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, even if the subpass is not empty, the presence of a backdrop filter indicates that the entire background will be filtered (output restricted by any existing clips).

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, I see... what about when SaveLayer bounds are given? That should still clip the filtered background, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this is fixed now. Added a video of the test demonstrating the clipping behavior in the PR description.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually no, the saveLayer bounds do not clip the filtered background. I don't even know if they clip the children (subpass) either in this case, Skia fiddle would show the expected behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

I find this behavior a bit odd, but confirmed in Skia fiddle: https://fiddle.skia.org/c/51c183699c942925d0b13d967f932010

Fixed and updated the demo video in the description.

ToRect(bounds));
auto paint = options.renders_with_attributes() ? paint_ : Paint{};
canvas_.SaveLayer(paint, ToRect(bounds),
backdrop ? ToImageFilterProc(backdrop) : std::nullopt);
Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking more along the lines of just:

canvas_.SaveLayer(paint, ToRect(bounds), ToImageFilterProc(backdrop));

since ToIFP handles null already...

Copy link
Member Author

Choose a reason for hiding this comment

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

Whoopsies, went too fast here. Fixed.

@bdero bdero requested a review from flar June 8, 2022 00:15
}
if (subpass->backdrop_filter_proc_.has_value()) {
subpass_coverage = Rect(
position, Size(pass_context.GetRenderTarget().GetRenderTargetSize()));
Copy link
Contributor

Choose a reason for hiding this comment

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

It won't really happen with a blur, but it is possible for the filtered output to be smaller than the saveLayer rect. Consider the erode filter, for instance. With a large erode radius it could drop the total size of the filtered backdrop down to a very small size.

Although you don't seem to be computing the filtered size here, just the size of the backdrop...?

Copy link
Member Author

@bdero bdero Jun 8, 2022

Choose a reason for hiding this comment

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

I did some more comprehensive testing in Skia fiddle and confirmed that coverage of the filter is important. Right now there are 2 problems with my coverage approach here:

  1. As you mentioned, the post-filter coverage is not getting incorporated. This means that if you use a blur filter on a small layer, the expanded sides of the blur will get cut off (which is wrong).
  2. Only the backdrop texture is getting incorporated in coverage when a filter is present, which means entities added to the layer with the filter may get cut off, which is also wrong.

Copy link
Contributor

@flar flar Jun 8, 2022

Choose a reason for hiding this comment

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

In # 1, by "small layer", do you mean the saveLayer bounds? Presumably if the parent layer is small, the blur would be clipped to it anyway.

For # 2, I'm not sure what you mean by "entities added to the layer"? Do you mean things you draw in the sub-pass, or the content of the saveLayer? I think those would normally (in Skia?) be cut off if the saveLayer bounds were provided without a backdrop filter, but not if the backdrop filter requires a larger layer.

My impression is that the bounds are:

Union(backdrop(parent layer).output_bounds, saveLayer bounds).IntersectWith(clip and parent layer bounds).

Copy link
Member Author

Choose a reason for hiding this comment

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

Presumably if the parent layer is small, the blur would be clipped to it anyway

This is what I thought too, but the blur seems to be getting properly incorporated into the layer's coverage (which in turn impacts the parent layer's size): https://fiddle.skia.org/c/7cb23795f3cfc78a10e50c3503379d38
In the above Skia fiddle, I expected to see the blur to get cut off on the bottom and right side of the middle circle (#2), But to my surprise, the parent layer's coverage seems to expand appropriately and nothing is getting cut off.

There's a dependency loop. Assuming SaveLayer B is a child of SaveLayer A, and SaveLayer B has a backdrop filter:

  1. The SaveLayer A's coverage depends on the coverage of SaveLayer B.
  2. The SaveLayer B's coverage depends on the backdrop filter coverage.
  3. The backdrop filter coverage depends on the parent pass texture size.
  4. The parent pass texture size is the SaveLayer A's coverage.

This can be solved by extending the filter coverage API to allow the caller to supply a list of prospective coverage rectangles for the inputs (as opposed to resolving the FilterInput coverage) via the Contents::GetCoverage implementation. Then, EntityPass coverage routine can incorporate subpass filter coverage without having to allocate textures. Another alternative would be to add mock/coverage-only filter inputs.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you specify bounds for A's saveLayer then those get used as a clip, but yeah. B's saveLayer is a rendering operation within A so its bounds affect the default bounds for A. Also, if you applied a blur filter to B (not a backdrop, but a blur of the layer), those blur bounds would also be accumulated into A's default bounds.

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright, I changed the coverage computation to union with the backdrop filter's coverage before creating the child pass texture.

However, this union currently happens outside the actual coverage routine, which means the backdrop filter coverage still doesn't properly propagate up to parent passes. Fixing this is a bit more involved, so I wrote an issue with a plan for how to address it in a future patch: flutter/flutter#105810

@bdero bdero removed their assignment Jun 13, 2022
Copy link
Contributor

@flar flar left a comment

Choose a reason for hiding this comment

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

The bounds logic looks good to me. I can't really vouch for the pipeline rework you've done here, but I'll approve based on the parts that I did follow.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2022
houhuayong pushed a commit to houhuayong/engine that referenced this pull request Jun 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
affects: engine e: impeller waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land.
Projects
No open projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants