-
Notifications
You must be signed in to change notification settings - Fork 6k
[Impeller] Add support for ImageFilter.shader #53490
Conversation
|
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. |
|
Golden file changes are available for triage from new commit, Click here to view. |
|
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. |
|
From offline discussion, lets add a Matrix option to provide some means of transforming the input pixels. |
|
Golden file changes are available for triage from new commit, Click here to view. |
|
NVM we're not going to do the matrix approach. Adding @gaaclarke for the impeller side of things |
flar
left a comment
There was a problem hiding this 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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added back
gaaclarke
left a comment
There was a problem hiding this 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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| auto runtime_stage = | |
| std::shared_ptr<RuntimeStage> runtime_stage = |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| auto fragment_program_filter = filter->asRuntimeEffectFilter(); | |
| const DlRuntimeEffectImageFilter* fragment_program_filter = filter->asRuntimeEffectFilter(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (!sampler->asImage()) { | |
| if (!image) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| VALIDATION_LOG | ||
| << "Invalid fragment shader in RuntimeEffectFilterContents. " | ||
| << "Shader must have at least one sampler and a vec2 size uniform."; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gave it a shot
lib/ui/painting.dart
Outdated
| if (!_impellerEnabled) { | ||
| throw UnsupportedError('ImageFilter.shader only supported with Impeller rendering engine.'); | ||
| } | ||
| if (shader._floats.length < 2 || !shader._validateImageFilter()) { |
There was a problem hiding this comment.
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.
… into image_filter_shader
gaaclarke
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, thanks!
| 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'); | ||
| } |
There was a problem hiding this comment.
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.
|
Golden file changes are available for triage from new commit, Click here to view. |
…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
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
Copy-pasta docs:
Fixes jonahwilliams/flutter_shaders#34
Fixes jonahwilliams/flutter_shaders#26
Fixes flutter/flutter#132099