From 1e2583eff25200b10f01d1fc2d13e7936fba57d2 Mon Sep 17 00:00:00 2001
From: Jonah Williams <jonahwilliams@google.com>
Date: Mon, 24 Feb 2025 11:01:55 -0800
Subject: [PATCH] [ui] Fix ImageFilter.shader equality to consider uniform
 values. (#163348)

Fixes https://github.com/flutter/flutter/issues/163302

Framework widgets check for ImageFIlter.== to determine whether to mark
themselves dirty. The filter obejct needs to delegate its equality to
the underlying native filter so that uniform values are considered.
---
 engine/src/flutter/lib/ui/dart_ui.cc          |  1 +
 engine/src/flutter/lib/ui/painting.dart       |  7 ++++-
 .../flutter/lib/ui/painting/image_filter.cc   |  4 +++
 .../flutter/lib/ui/painting/image_filter.h    |  1 +
 .../testing/dart/fragment_shader_test.dart    | 28 +++++++++++++++++++
 5 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/engine/src/flutter/lib/ui/dart_ui.cc b/engine/src/flutter/lib/ui/dart_ui.cc
index a47bf094c42..7bb1aef0ede 100644
--- a/engine/src/flutter/lib/ui/dart_ui.cc
+++ b/engine/src/flutter/lib/ui/dart_ui.cc
@@ -207,6 +207,7 @@ typedef CanvasPath Path;
   V(ImageFilter, initComposeFilter)              \
   V(ImageFilter, initShader)                     \
   V(ImageFilter, initMatrix)                     \
+  V(ImageFilter, equals)                         \
   V(ImageShader, dispose)                        \
   V(ImageShader, initWithImage)                  \
   V(ImmutableBuffer, dispose)                    \
diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart
index dea0c7885fa..ddda85d2752 100644
--- a/engine/src/flutter/lib/ui/painting.dart
+++ b/engine/src/flutter/lib/ui/painting.dart
@@ -4474,9 +4474,14 @@ class _FragmentShaderImageFilter implements ImageFilter {
     if (other.runtimeType != runtimeType) {
       return false;
     }
-    return other is _FragmentShaderImageFilter && other.shader == shader;
+    return other is _FragmentShaderImageFilter &&
+        other.shader == shader &&
+        _equals(nativeFilter, other.nativeFilter);
   }
 
+  @Native<Bool Function(Handle, Handle)>(symbol: 'ImageFilter::equal')
+  external static bool _equals(_ImageFilter a, _ImageFilter b);
+
   @override
   int get hashCode => shader.hashCode;
 }
diff --git a/engine/src/flutter/lib/ui/painting/image_filter.cc b/engine/src/flutter/lib/ui/painting/image_filter.cc
index 2319008bc4f..8d21d638847 100644
--- a/engine/src/flutter/lib/ui/painting/image_filter.cc
+++ b/engine/src/flutter/lib/ui/painting/image_filter.cc
@@ -125,4 +125,8 @@ void ImageFilter::initShader(ReusableFragmentShader* shader) {
   filter_ = shader->as_image_filter();
 }
 
+bool ImageFilter::equals(ImageFilter* a, ImageFilter* b) {
+  return a->filter_ == b->filter_;
+}
+
 }  // namespace flutter
diff --git a/engine/src/flutter/lib/ui/painting/image_filter.h b/engine/src/flutter/lib/ui/painting/image_filter.h
index c7bb6369bdb..c251d9653f6 100644
--- a/engine/src/flutter/lib/ui/painting/image_filter.h
+++ b/engine/src/flutter/lib/ui/painting/image_filter.h
@@ -36,6 +36,7 @@ class ImageFilter : public RefCountedDartWrappable<ImageFilter> {
   void initColorFilter(ColorFilter* colorFilter);
   void initComposeFilter(ImageFilter* outer, ImageFilter* inner);
   void initShader(ReusableFragmentShader* shader);
+  bool equals(ImageFilter* a, ImageFilter* b);
 
   const std::shared_ptr<DlImageFilter> filter(DlTileMode mode) const;
 
diff --git a/engine/src/flutter/testing/dart/fragment_shader_test.dart b/engine/src/flutter/testing/dart/fragment_shader_test.dart
index 0bfffbf515d..54366f7af2a 100644
--- a/engine/src/flutter/testing/dart/fragment_shader_test.dart
+++ b/engine/src/flutter/testing/dart/fragment_shader_test.dart
@@ -415,6 +415,34 @@ void main() async {
     expect(color, const Color(0xFF00FF00));
   });
 
+  // For an explaination of the problem see https://github.com/flutter/flutter/issues/163302 .
+  test('ImageFilter.shader equality checks consider uniform values', () async {
+    if (!impellerEnabled) {
+      print('Skipped for Skia');
+      return;
+    }
+    final FragmentProgram program = await FragmentProgram.fromAsset('filter_shader.frag.iplr');
+    final FragmentShader shader = program.fragmentShader();
+    final ImageFilter filter = ImageFilter.shader(shader);
+
+    // The same shader is equal to itself.
+    expect(filter, filter);
+    expect(identical(filter, filter), true);
+
+    final ImageFilter filter_2 = ImageFilter.shader(shader);
+
+    // The different shader is equal as long as uniforms are identical.
+    expect(filter, filter_2);
+    expect(identical(filter, filter_2), false);
+
+    // Not equal if uniforms change.
+    shader.setFloat(0, 1);
+    final ImageFilter filter_3 = ImageFilter.shader(shader);
+
+    expect(filter, isNot(filter_3));
+    expect(identical(filter, filter_3), false);
+  });
+
   if (impellerEnabled) {
     print('Skipped for Impeller - https://github.com/flutter/flutter/issues/122823');
     return;
-- 
GitLab