Skip to content
This repository was archived by the owner on Apr 29, 2022. It is now read-only.

Conversation

@bdero
Copy link
Member

@bdero bdero commented Mar 9, 2022

Part of flutter/flutter#99521.

  • Add FilterContents class that take zero or more texture inputs and generates a result texture.
    • Any input may either be an impeller::Texture or another FilterContents which will get resolved at render time.
    • When the FilterContents is rendered with an entity, it draws the output texture to the parent pass via TextureContents and respects the entity's transform/stencil/path.
    • By default, the size of the first input texture is used as the size of the result texture, but filters may override this behavior depending on the use case. For example, a convolution filter (such as a Gaussian blur) may optionally increase the size of the result texture by a factor of the blur radius.
  • Add BlendFilterContents filter for simple additive blending/merging textures together.
    • Currently, input textures that don't match the size of the first texture stretch.
  • Add Entity::BlendMode::kPlus blend mode.

Right now the test just blends 3 fixtures together. Will make fancier tests once we have less trivial filters to work with.

image

Usage:

    auto bridge = CreateTextureForFixture("bay_bridge.jpg");
    auto boston = CreateTextureForFixture("boston.jpg");
    auto kalimba = CreateTextureForFixture("kalimba.jpg");

    // Draws kalimba and alpha blends boston over it.
    auto blend0 = FilterContents::MakeBlend(
        Entity::BlendMode::kSourceOver, {kalimba, boston});

    // Adds bridge*3 and the result of the first blend together.
    auto blend1 = FilterContents::MakeBlend(
        Entity::BlendMode::kPlus, {bridge, bridge, blend0, bridge});

    Entity entity;
    entity.SetPath(PathBuilder{}.AddRect({100, 100, 300, 300}).TakePath());
    entity.SetContents(blend1);
    entity.Render(context, pass);

@bdero bdero changed the title WIP: Chainable texture filters Chainable texture filters Mar 10, 2022
@bdero bdero requested review from chinmaygarde and dnfield and removed request for chinmaygarde March 10, 2022 00:46
@bdero
Copy link
Member Author

bdero commented Mar 10, 2022

This is ready for review

Copy link
Member

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

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

This is great. I left perhaps one too many nits but the mechanics look good.

///
/// The number of required or optional textures depends on the
/// particular filter's implementation.
void SetInputTextures(InputTextures& input_textures);
Copy link
Member

Choose a reason for hiding this comment

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

I think the pass by reference (because of the &) necessitates an unavoidable copy at the caller and you are now depending on the move ctor of the vector to not trash the container on the off chance the caller wants to reuse it. It doesn't but this pattern is somewhat dangerous nonetheless.

The unavoidable copy is because the caller can't move the vector in even if the wanted to because it has to be by reference. Ideally, you want to avoid vector copies both at the call-site and the implementation. The following signature is allows for that:

void SetInputTextures(InputTextures input_textures);

At the call-site, if the caller wants to move the container, they may do foo.SetInputTextures(std::move(texes)) and you are avoiding the copy in the implementation. OTOH, if the want to keep those textures around for further use, they may just not use the move and incur the copy themselves. They need the container, so they should have to copy it out.

I think in this case, the signature with the least bits and bobs in it is the most elegant.

Copy link
Member Author

Choose a reason for hiding this comment

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

Whoops! Thanks for the great explanation. Done.

return texture->GetSize();
}
}
return {0, 0};
Copy link
Member

Choose a reason for hiding this comment

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

Nit: The default ctor zero initializes. So just {} ought to be fine. This is good too.

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.

}

ISize FilterContents::GetOutputSize() const {
if (!input_textures_.empty()) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: A general note, prefer early returns over nesting. This is fine too for now but may get out of hand in other instances.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, done.


ISize FilterContents::GetOutputSize() const {
if (!input_textures_.empty()) {
if (std::holds_alternative<std::shared_ptr<FilterContents>>(
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Prefer std::get_if over std::holds_alternative and get. One, its shorter. But, more importantly, you are performing the second get without the check and this may be brittle if we add more variants.

Something like:

if (auto a = std::get_if<FooA>(var)) {
  return a->dosomething();
}

if (auto b = std::get_if<FooB>(var)) {
  return a->dosomethingelse();
}

FML_UNREACHABLE();

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice, done.

auto uniform_view = host_buffer.EmplaceUniform(frame_info);

for (const auto& texture : input_textures) {
Command cmd;
Copy link
Member

Choose a reason for hiding this comment

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

Drive by comment: Commands are copyable. So you can create commands that are similar except for some fields outside the for loop and update just the relevant bindings in the loop before adding the command to the pass (don't forget to remove the std::move though).

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice, moved most of this setup out.


FilterContents();

~FilterContents();
Copy link
Member

Choose a reason for hiding this comment

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

Nit:: Missing override.

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants