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]: new blur - adds mips for backdrop filters #49607

Merged
merged 28 commits into from
Jan 12, 2024

Conversation

gaaclarke
Copy link
Member

@gaaclarke gaaclarke commented Jan 8, 2024

If the new blur flag is on and a blur is used as a backdrop filter, the rendered texture will now have mipmaps which will make the downscaling step of the blur have more signal (and thus be less shimmery).

issue: flutter/flutter#140193
fixes: flutter/flutter#140949

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@bdero
Copy link
Member

bdero commented Jan 8, 2024

Although this won't generate a mipmap for the backdrop filter case yet, this should already be enough to fix the problem for ui.Images. If you wanna try it out in the playground, you can:

  • use the enable_mipmapping param when calling CreateTextureForFixture, and then
  • generate the mipmap using the GenerateMipmap() utility in aiks_unittests.cc.

@gaaclarke
Copy link
Member Author

Okay, after started invalidating mipmaps I've been able to test the sample code from flutter/flutter#140193

This greatly reduces the jitters but it doesn't 100% eliminate it.

I can't easily test against the old blur right now because there is a discrepancy between blur radii between the old and new blur. I wonder if the remainder of the shimmers is from the pixel alignment issue I was looking into friday.

before

before.mov

after

after.mov

@gaaclarke
Copy link
Member Author

I did performance testing with the backdrop blur test and gpu time is around 21ms. So I think we are good there.

@gaaclarke
Copy link
Member Author

I've rebased this PR onto the fix for the blur radius scaling and I got the same exact result. I'm guessing maybe that benchmark wasn't using an effect_transform? That or the downsampling is counterbalancing the scaled blur radius. Although suspiciously the result was the exact same number (maybe its a from a discrete set?)

const std::shared_ptr<Texture>& texture) {
if (texture->NeedsMipmapGeneration()) {
std::shared_ptr<CommandBuffer> mip_cmd_buffer =
context->CreateCommandBuffer();
Copy link
Member

Choose a reason for hiding this comment

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

Rather than creating a new command buffer, we could actually append this blit pass to the existing command buffer before submitting it in InlinePassContext::EndPass()

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you think we should do it? I can add a todo, I probably don't want to fiddle with what works right now.

@gaaclarke gaaclarke changed the title [Impeller]: new blur - adds mips [Impeller]: new blur - adds mips for backdrop filters Jan 12, 2024
@gaaclarke gaaclarke marked this pull request as ready for review January 12, 2024 17:25
@gaaclarke
Copy link
Member Author

@bdero I went through the draft comments and resolved them. Think everything was addressed or noted in cases where it was guidance. Feel free to reopen or raise similar points.

Right now the mip count is just hardcoded to 4 as we agreed upon and it is also only triggered for the new blur.

@flutter-dashboard
Copy link

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

Changes reported for pull request #49607 at sha 7cba038

Comment on lines +328 to 345
int32_t required_mip_count = 1;
IterateAllElements(
[&required_mip_count, lazy_glyph_atlas = renderer.GetLazyGlyphAtlas()](
const Element& element) {
if (auto entity = std::get_if<Entity>(&element)) {
if (const auto& contents = entity->GetContents()) {
contents->PopulateGlyphAtlas(lazy_glyph_atlas,
entity->DeriveTextScale());
}
}
if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
const EntityPass* entity_pass = subpass->get();
required_mip_count =
std::max(required_mip_count, entity_pass->GetRequiredMipCount());
}
return true;
});

Copy link
Member

Choose a reason for hiding this comment

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

I don't like that we're adding on to this callback - this is a fairly expensive traversal that we're doing in addition to the regular traversal. Can we compute this during normal traversal?

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

This lookahead traversal already exists, this just adds one extra branch for looking at mip counts. So the addition won't be expensive. The problem is that the resource is created before we actually render it, so there has to be some form of look ahead afaik. I didn't see an alternative, but maybe brandon has a suggestion.

Copy link
Member

Choose a reason for hiding this comment

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

Yes but I want to remove this lookahead, and this makes it harder :)

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 mean, you have to either look ahead, or be able to switch mid render. Since mip map counts have to be specified at creation I think that would mean assuming mip count 1, then blitting to a mipmap texture if we ever encounter something that needs mipmaps.

The other option is to unconditionally add mipmaps which we said we don't want.

Copy link
Member Author

Choose a reason for hiding this comment

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

So, if I have nested save layers the render target won't have mip maps if it can't be collapsed? Something like this?

Canvas canvas;
canvas.Save({.blend_mode = kMultiply});
canvas.DrawCircle();
canvas.Save({}, ImageFilter::MakeBlur());

Copy link
Member

Choose a reason for hiding this comment

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

Yeah. Inverting this to remove the nested SaveLayer would also have a similarly mip-less result:

Canvas canvas;
canvas.SaveLayer({.image_filter = ImageFilter::MakeBlur()});
canvas.DrawCircle();

Copy link
Member Author

@gaaclarke gaaclarke Jan 12, 2024

Choose a reason for hiding this comment

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

The following works with the proper mipmaps, is this what you had in mind?

TEST_P(AiksTest, GaussianBlurFoo) {
  Canvas canvas;
  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
  canvas.SaveLayer({}, std::nullopt,
                   ImageFilter::MakeBlur(Sigma(30), Sigma(30),
                                         FilterContents::BlurStyle::kNormal,
                                         Entity::TileMode::kClamp));
  canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});

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

Copy link
Member Author

Choose a reason for hiding this comment

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

This gets us in a place without mipmaps:

TEST_P(AiksTest, GaussianBlurFoo) {
  Canvas canvas;
  canvas.DrawPaint({.color = Color::Wheat()});
  canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
  canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
  canvas.SaveLayer({}, std::nullopt,
                   ImageFilter::MakeBlur(Sigma(30), Sigma(30),
                                         FilterContents::BlurStyle::kNormal,
                                         Entity::TileMode::kClamp));
  canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});

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

Copy link
Member

Choose a reason for hiding this comment

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

is this what you had in mind?

The example I gave sets an image filter on the paint state handed to the SaveLayer -- no backdrop filter involved. So something like:

TEST_P(AiksTest, GaussianBlurFoo) {
  Canvas canvas;
  canvas.SaveLayer({.image_filter = ImageFilter::MakeBlur(...)});
  canvas.DrawCircle({200, 200}, 50, {.color = Color::Chartreuse()});

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

@@ -156,6 +187,9 @@ void Canvas::Save(bool create_subpass,
return filter;
};
subpass->SetBackdropFilter(backdrop_filter_proc);
MipCountVisitor mip_count_visitor;
backdrop_filter->Visit(mip_count_visitor);
subpass->SetRequiredMipCount(mip_count_visitor.GetRequiredMipCount());
Copy link
Member

Choose a reason for hiding this comment

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

We can work out the required mip level for the EntityPass texture in Canvas::Save without look-aheads later on.

  • When creating a new subpass:
    • If the new subpass has backdrop filter, assign the current pass (not the child pass being created) a new max mip level: GetCurrentPass().SetRequiredMipCount(std::max(GetCurrentPass().GetRequiredMipCount(), backdrop_filter_mip_count))
    • If the Canvas paint state has an image filter, then initialize the max mip level of the subpass with the image filter's max mip level: subpass->SetRequiredMipCount(paint_image_filter_mip_count)

Copy link
Member

Choose a reason for hiding this comment

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

Then, we just use subpass->required_mip_count_ in the two places where we provision the child pass texture in EntityPass.

Copy link
Member

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

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

LGTM

@gaaclarke gaaclarke merged commit 205ed68 into flutter:main Jan 12, 2024
29 checks passed
auto offscreen_target = CreateRenderTarget(
renderer, root_render_target.GetRenderTargetSize(),
EntityPassTarget offscreen_target = CreateRenderTarget(
renderer, root_render_target.GetRenderTargetSize(), required_mip_count,
Copy link
Member

Choose a reason for hiding this comment

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

I thought we were going to protect this behind a flag for the new blur to keep the benchmark comparison sane?

Copy link
Member Author

@gaaclarke gaaclarke Jan 12, 2024

Choose a reason for hiding this comment

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

It is behind the new blur flag. Notice how the tests make sure the mip count is 1. When you turn the flag off on those bump up to 4.

Copy link
Member Author

Choose a reason for hiding this comment

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

So it's doing all the new calculations have test coverage, but the effect on performance should be negligible.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I missed the FilterContents::kBlurFilterRequiredMipCount. LGTM

engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jan 12, 2024
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Jan 13, 2024
…141492)

flutter/engine@b8e5d47...205ed68

2024-01-12 30870216+gaaclarke@users.noreply.github.com [Impeller]: new blur - adds mips for backdrop filters (flutter/engine#49607)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC bdero@google.com,rmistry@google.com,zra@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
auto-submit bot pushed a commit that referenced this pull request Jan 16, 2024
This resolves Jonah's feedback on #49607 and Brandons feedback on #49607 and #49778.

This avoids the need to do a lookahead by storing the proper mip count value while building the drawing on the canvas.

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Status: ✅ Done
3 participants