Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@jonahwilliams
Copy link
Contributor

@jonahwilliams jonahwilliams commented Jun 20, 2024

Copy-pasta docs:

  /// Creates an image filter from a [FragmentShader].
  ///
  /// The fragment shader provided here has additional requirements to be used
  /// by the engine for filtering. The first uniform value must be a vec2, this
  /// will be set by the engine to the size of the bound texture. There must
  /// also be at least one sampler2D uniform, the first of which will be set by
  /// the engine to contain the filter input.
  ///
  /// For example, the following is a valid fragment shader that can be used
  /// with this API. Note that the uniform names are not required to have any
  /// particular value.
  ///
  /// ```glsl
  /// #include <flutter/runtime_effect.glsl>
  ///
  /// uniform vec2 u_size;
  /// uniform float u_time;
  ///
  /// uniform sampler2D u_texture_input;
  ///
  /// out vec4 frag_color;
  ///
  /// void main() {
  ///   frag_color = texture(u_texture_input, FlutterFragCoord().xy / u_size) * u_time;
  ///
  /// }
  ///
  /// ```
  ///
  /// This API is only supported when using the Impeller rendering engine. On
  /// other backends a [UnsupportedError] will be thrown. This error can be
  /// caught and used for feature detection.

Fixes jonahwilliams/flutter_shaders#34
Fixes jonahwilliams/flutter_shaders#26
Fixes flutter/flutter#132099

@github-actions github-actions bot added the platform-web Code specifically for the web engine label Jun 20, 2024
@jonahwilliams jonahwilliams changed the title [WIP] Add support for ImageFilter.shader [Impeller] Add support for ImageFilter.shader Jul 5, 2024
@jonahwilliams jonahwilliams marked this pull request as ready for review July 5, 2024 21:22
@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 #53490 at sha 77e290b

@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

Changes reported for pull request #53490 at sha 5bb8824

@jonahwilliams jonahwilliams marked this pull request as draft July 9, 2024 07:51
@flutter-dashboard
Copy link

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

@jonahwilliams
Copy link
Contributor Author

From offline discussion, lets add a Matrix option to provide some means of transforming the input pixels.

@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

Changes reported for pull request #53490 at sha 2edae62

@jonahwilliams
Copy link
Contributor Author

NVM we're not going to do the matrix approach. Adding @gaaclarke for the impeller side of things

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 Dart API and the DisplayList parts look good to me. I only looked through some of the changes to Impeller to implement it.

DlRuntimeEffectImageFilter filter_c(
nullptr, {nullptr}, std::make_shared<std::vector<uint8_t>>(1));

EXPECT_NE(filter_a, filter_c);
Copy link
Contributor

Choose a reason for hiding this comment

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

Also a version that is different based on samplers? I suppose it would be too hard to test equality if the shader was different - or do we have a RTE test fixture somewhere you could use for testing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

EXPECT_EQ(output_bounds, input_bounds);
}

TEST(DisplayListImageFilter, RuntimeEffectModifiesTransparentBlack) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not an issue with this PR, but this engaged a more global thought in me...

This reminds me that the name is a little off because the shader might modify transparent black and that's OK. What this method really is asking/answering is "does it modify transparent black pixels everywhere on the entire plane regardless of whether they were really "involved" in the original operation" which is a question apropos mostly for a CF, but IF needs to answer as well in case it is wrapping a CF. But the name itself doesn't really capture the question being asked. Perhaps ...WithPrejudice() or something...

FML_DCHECK(renderer.GetContext()->GetBackendType() ==
Context::BackendType::kVulkan);
ShaderUniformSlot uniform_slot;
uniform_slot.name = uniform.name.c_str();
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure why this is deleted???

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added back

Copy link
Member

@gaaclarke gaaclarke 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 looking good from my perspective. Code looks good, API looks good, documentation looks good. I just have a few style nits and a question about the user experience when errors happen.


class DlRuntimeEffectImageFilter final : public DlImageFilter {
public:
explicit DlRuntimeEffectImageFilter(
Copy link
Member

Choose a reason for hiding this comment

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

nit: I wish these were in a .cc file. Recompilation time was killing me the other day.

Copy link
Contributor

Choose a reason for hiding this comment

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

There are a few things I'd like to clean up here as well (and the other files in this directory). (There is a .cc file for each, we just don't use it much.)

  • Move more code to the .cc file as @gaaclarke is suggesting.
  • Deskiafying the geometry and other simple objects, naturally.
  • Disallow stack allocation and make them all shared pointers. It's great for unit tests where we don't care about performance, but causes extra copies in the primary code. We can still inline them into the DL records with private constructors, but if any DlOpReceiver code wants to save them "for later", it then needs to deal with reconstructing a shared version. :( Still, the amount of annoyance to deal with stack allocation vs shared_ptr feels like it should be cleaned up. For example, if you ever put it into a DlPaint, we have to re-share it when we should just keep a (shared) copy of the original.
  • Having them be shared pointers means we never construct a NOP filter because it would notice that it was NOP and return a null instead of a constructed object that has an identity (am I useful?) crisis.
  • Have different files for the different types??? This would explode the source files in this directory, but isolate the implementations. There would still be a main "dl_image_filter.h" that included the others as it needs to support the type and asFoo() mechanisms, but we wouldn't have one giant regurgitation of code in the main file.
  • Use more vectors instead of count/ptr pairs.

case DlImageFilterType::kRuntimeEffect:
// UNSUPPORTED.
return nullptr;
break;
Copy link
Member

Choose a reason for hiding this comment

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

nit: break isn't needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

auto runtime_stages =
OpenAssetAsRuntimeStage("runtime_stage_filter_example.frag.iplr");

auto runtime_stage =
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
auto runtime_stage =
std::shared_ptr<RuntimeStage> runtime_stage =

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

FilterInput::Make(WrapInput(inner_dl_filter.get(), input)));
}
case flutter::DlImageFilterType::kRuntimeEffect: {
auto fragment_program_filter = filter->asRuntimeEffectFilter();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
auto fragment_program_filter = filter->asRuntimeEffectFilter();
const DlRuntimeEffectImageFilter* fragment_program_filter = filter->asRuntimeEffectFilter();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

auto fragment_program_filter = filter->asRuntimeEffectFilter();
FML_DCHECK(fragment_program_filter);
const flutter::DlRuntimeEffectImageFilter* runtime_filter =
fragment_program_filter->asRuntimeEffectFilter();
Copy link
Member

Choose a reason for hiding this comment

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

We already have this fragment_program_filter, right? I think auto is just making it hard to see. The autos here in general aren't helping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

return nullptr;
}
auto* image = sampler->asImage();
if (!sampler->asImage()) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (!sampler->asImage()) {
if (!image) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +59 to +61
VALIDATION_LOG
<< "Invalid fragment shader in RuntimeEffectFilterContents. "
<< "Shader must have at least one sampler and a vec2 size uniform.";
Copy link
Member

Choose a reason for hiding this comment

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

How does this manifest for users when they have an invalid shader? Are they getting dart exceptions, crashes, is this message visible to them? Is rendering after this aborted?

I think ideally this would not abort any rendering, the error would be visible to the filter being rendered in the UI, the user could hot reload to address the issue.

Rendering a magenta rect with a loud console message would be fine too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe we should always detect this in dart:ui and throw an exception when the filter is constructed:

https://github.com/flutter/engine/pull/53490/files#diff-1deac5f38d2c30929d40af54c24c990c604c6b7d7bb21a103d28ffd50c8dcb91R4102

However, we also need to check here since there may eventually be non dart users of this funciton. returning nullopt here causes the filter to not show up but anything that was not rendered as a child of the filter will still show uo.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, sounds good to me. Can we expand the dart test to test these 2 cases individually, uniform size being wrong and texture inputs being wrong? Right now it just tests them together.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

out vec4 frag_color;

void main() {
frag_color = texture(u_texture, FlutterFragCoord().xy / u_size);
Copy link
Member

Choose a reason for hiding this comment

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

Should this do something more noticeable like swizzle color channels for the golden test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah @flar suggested making this more interesting as well, working on that...

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 hoping for something location based which is one of the "features" of these new shaders. Just swizzling color channels in place or globally doesn't necessarily tell you that it is getting the right locations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gave it a shot

if (!_impellerEnabled) {
throw UnsupportedError('ImageFilter.shader only supported with Impeller rendering engine.');
}
if (shader._floats.length < 2 || !shader._validateImageFilter()) {
Copy link
Member

Choose a reason for hiding this comment

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

Can this error message be more apropos to what they did wrong. For example if they are missing just the sampler, can we just have the error talk about the sampler? This will save developers time as they respond to the problem.

Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

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

lgtm, thanks!

Comment on lines +4108 to +4113
if (invalidFloats) {
buffer.write('The shader has fewer than two float uniforms.\n');
}
if (invalidSampler) {
buffer.write('The shader is missing a sampler uniform.\n');
}
Copy link
Member

Choose a reason for hiding this comment

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

Oh nice, I thought this was a good idea too. I didn't want to pester you about it though, thanks.

@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

Changes reported for pull request #53490 at sha 10e9e59

@jonahwilliams jonahwilliams added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 8, 2024
@auto-submit auto-submit bot merged commit 44d788f into flutter:main Nov 8, 2024
35 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Nov 8, 2024
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Nov 8, 2024
…158362)

flutter/engine@8e19915...44d788f

2024-11-08 [email protected] [Impeller] Add support for ImageFilter.shader (flutter/engine#53490)
2024-11-07 [email protected] Roll Skia from 2ed9606702f1 to 7ae36ecfe93d (4 revisions) (flutter/engine#56442)
2024-11-07 [email protected] Roll Dart SDK from 6a3684b96121 to 50c620224f27 (1 revision) (flutter/engine#56438)

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 [email protected],[email protected] 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
nick9822 pushed a commit to nick9822/flutter that referenced this pull request Dec 18, 2024
Copy-pasta docs:

```
  /// Creates an image filter from a [FragmentShader].
  ///
  /// The fragment shader provided here has additional requirements to be used
  /// by the engine for filtering. The first uniform value must be a vec2, this
  /// will be set by the engine to the size of the bound texture. There must
  /// also be at least one sampler2D uniform, the first of which will be set by
  /// the engine to contain the filter input.
  ///
  /// For example, the following is a valid fragment shader that can be used
  /// with this API. Note that the uniform names are not required to have any
  /// particular value.
  ///
  /// ```glsl
  /// #include <flutter/runtime_effect.glsl>
  ///
  /// uniform vec2 u_size;
  /// uniform float u_time;
  ///
  /// uniform sampler2D u_texture_input;
  ///
  /// out vec4 frag_color;
  ///
  /// void main() {
  ///   frag_color = texture(u_texture_input, FlutterFragCoord().xy / u_size) * u_time;
  ///
  /// }
  ///
  /// ```
  ///
  /// This API is only supported when using the Impeller rendering engine. On
  /// other backends a [UnsupportedError] will be thrown. This error can be
  /// caught and used for feature detection.
  ```

Fixes jonahwilliams/flutter_shaders#34
Fixes jonahwilliams/flutter_shaders#26
Fixes flutter#132099
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

autosubmit Merge PR when tree becomes green via auto submit App e: impeller platform-web Code specifically for the web engine will affect goldens

Projects

None yet

4 participants