[DisplayList] Disable group opacity when a RuntimeEffect is in use (#56936)

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

Impeller does not support group opacity for RuntimeEffects so we disable the optimization with a flag when it is detected.
diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc
index d7307d5..cfd1f6b 100644
--- a/display_list/display_list_unittests.cc
+++ b/display_list/display_list_unittests.cc
@@ -1367,7 +1367,7 @@
   DisplayListBuilder builder;
   // This empty draw rect will not actually be inserted into the stream,
   // but the Src blend mode will be synchronized as an attribute. The
-  // saveLayer following it should not use that attribute to base its
+  // SaveLayer following it should not use that attribute to base its
   // decisions about group opacity and the draw rect after that comes
   // with its own compatible blend mode.
   builder.DrawRect(SkRect{0, 0, 0, 0},
@@ -1416,7 +1416,7 @@
   DlPaint save_paint;
   builder.SaveLayer(nullptr, &save_paint);
   builder.DrawRect(SkRect{50, 50, 100, 100}, DlPaint());
-  // This image filter should be ignored since it was not set before saveLayer
+  // This image filter should be ignored since it was not set before SaveLayer
   // And the rect drawn with it will not contribute any more area to the bounds
   DlPaint draw_paint;
   draw_paint.setImageFilter(&kTestBlurImageFilter1);
@@ -2510,7 +2510,7 @@
   builder.DrawRect(SkRect{10, 10, 20, 20}, default_paint);
   builder.SaveLayer(nullptr, &filter_paint);
   // the following rectangle will be expanded to 50,50,60,60
-  // by the saveLayer filter during the restore operation
+  // by the SaveLayer filter during the restore operation
   builder.DrawRect(SkRect{53, 53, 57, 57}, default_paint);
   builder.Restore();
   auto display_list = builder.Build();
@@ -3272,7 +3272,7 @@
   builder.ClipRect(SkRect{50, 50, 60, 60}, ClipOp::kIntersect, false);
   builder.SaveLayer(nullptr, &filter_paint);
   // the following rectangle will be expanded to 23,23,87,87
-  // by the saveLayer filter during the restore operation
+  // by the SaveLayer filter during the restore operation
   // but it will then be clipped to 50,50,60,60
   builder.DrawRect(SkRect{53, 53, 57, 57}, default_paint);
   builder.Restore();
@@ -3862,7 +3862,7 @@
   builder.SaveLayer(nullptr, nullptr);
   builder.TransformReset();
   builder.Scale(20.0f, 20.0f);
-  // Net local transform for saveLayer is Scale(2, 2)
+  // Net local transform for SaveLayer is Scale(2, 2)
   {  //
     builder.DrawRect(rect, DlPaint());
   }
@@ -4451,7 +4451,7 @@
   builder.Restore();
 
   // Double check that kModulate is the max blend mode for the first
-  // saveLayer operations
+  // SaveLayer operations
   auto expect = std::max(DlBlendMode::kModulate, DlBlendMode::kSrc);
   ASSERT_EQ(expect, DlBlendMode::kModulate);
 
@@ -4487,8 +4487,8 @@
   auto dl = builder.Build();
 
   EXPECT_TRUE(dl->root_has_backdrop_filter());
-  // The saveLayer itself, though, does not have the contains backdrop
-  // flag set because its content does not contain a saveLayer with backdrop
+  // The SaveLayer itself, though, does not have the contains backdrop
+  // flag set because its content does not contain a SaveLayer with backdrop
   SAVE_LAYER_EXPECTOR(expector);
   expector.addExpectation(
       SaveLayerOptions::kNoAttributes.with_can_distribute_opacity());
@@ -5948,5 +5948,132 @@
   EXPECT_TRUE(!!builder.Build());
 }
 
+TEST_F(DisplayListTest, DisplayListDetectsRuntimeEffect) {
+  const auto runtime_effect = DlRuntimeEffect::MakeSkia(
+      SkRuntimeEffect::MakeForShader(
+          SkString("vec4 main(vec2 p) { return vec4(0); }"))
+          .effect);
+  auto color_source = DlColorSource::MakeRuntimeEffect(
+      runtime_effect, {}, std::make_shared<std::vector<uint8_t>>());
+  auto image_filter = DlImageFilter::MakeRuntimeEffect(
+      runtime_effect, {}, std::make_shared<std::vector<uint8_t>>());
+
+  {
+    // Default - no runtime effects, supports group opacity
+    DisplayListBuilder builder;
+    DlPaint paint;
+
+    builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint);
+    EXPECT_TRUE(builder.Build()->can_apply_group_opacity());
+  }
+
+  {
+    // Draw with RTE color source does not support group opacity
+    DisplayListBuilder builder;
+    DlPaint paint;
+
+    paint.setColorSource(color_source);
+    builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint);
+
+    EXPECT_FALSE(builder.Build()->can_apply_group_opacity());
+  }
+
+  {
+    // Draw with RTE image filter does not support group opacity
+    DisplayListBuilder builder;
+    DlPaint paint;
+
+    paint.setImageFilter(image_filter);
+    builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint);
+
+    EXPECT_FALSE(builder.Build()->can_apply_group_opacity());
+  }
+
+  {
+    // Draw with RTE color source inside SaveLayer does not support group
+    // opacity on the SaveLayer, but does support it on the DisplayList
+    DisplayListBuilder builder;
+    DlPaint paint;
+
+    builder.SaveLayer(nullptr, nullptr);
+    paint.setColorSource(color_source);
+    builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint);
+    builder.Restore();
+
+    auto display_list = builder.Build();
+    EXPECT_TRUE(display_list->can_apply_group_opacity());
+
+    SAVE_LAYER_EXPECTOR(expector);
+    expector.addExpectation([](const SaveLayerOptions& options) {
+      return !options.can_distribute_opacity();
+    });
+    display_list->Dispatch(expector);
+  }
+
+  {
+    // Draw with RTE image filter inside SaveLayer does not support group
+    // opacity on the SaveLayer, but does support it on the DisplayList
+    DisplayListBuilder builder;
+    DlPaint paint;
+
+    builder.SaveLayer(nullptr, nullptr);
+    paint.setImageFilter(image_filter);
+    builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint);
+    builder.Restore();
+
+    auto display_list = builder.Build();
+    EXPECT_TRUE(display_list->can_apply_group_opacity());
+
+    SAVE_LAYER_EXPECTOR(expector);
+    expector.addExpectation([](const SaveLayerOptions& options) {
+      return !options.can_distribute_opacity();
+    });
+    display_list->Dispatch(expector);
+  }
+
+  {
+    // Draw with RTE color source inside nested saveLayers does not support
+    // group opacity on the inner SaveLayer, but does support it on the
+    // outer SaveLayer and the DisplayList
+    DisplayListBuilder builder;
+    DlPaint paint;
+
+    builder.SaveLayer(nullptr, nullptr);
+
+    builder.SaveLayer(nullptr, nullptr);
+    paint.setColorSource(color_source);
+    builder.DrawRect(DlRect::MakeLTRB(0, 0, 50, 50), paint);
+    paint.setColorSource(nullptr);
+    builder.Restore();
+
+    builder.SaveLayer(nullptr, nullptr);
+    paint.setImageFilter(image_filter);
+    // Make sure these DrawRects are non-overlapping otherwise the outer
+    // SaveLayer and DisplayList will be incompatible due to overlaps
+    builder.DrawRect(DlRect::MakeLTRB(60, 60, 100, 100), paint);
+    paint.setImageFilter(nullptr);
+    builder.Restore();
+
+    builder.Restore();
+    auto display_list = builder.Build();
+    EXPECT_TRUE(display_list->can_apply_group_opacity());
+
+    SAVE_LAYER_EXPECTOR(expector);
+    expector.addExpectation([](const SaveLayerOptions& options) {
+      // outer SaveLayer supports group opacity
+      return options.can_distribute_opacity();
+    });
+    expector.addExpectation([](const SaveLayerOptions& options) {
+      // first inner SaveLayer does not support group opacity
+      return !options.can_distribute_opacity();
+    });
+    expector.addExpectation([](const SaveLayerOptions& options) {
+      // second inner SaveLayer does not support group opacity
+      return !options.can_distribute_opacity();
+    });
+    display_list->Dispatch(expector);
+  }
+}
+
 }  // namespace testing
 }  // namespace flutter
diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc
index 3e0e18d..682e882 100644
--- a/display_list/dl_builder.cc
+++ b/display_list/dl_builder.cc
@@ -244,6 +244,7 @@
       }
     }
   }
+  UpdateCurrentOpacityCompatibility();
 }
 void DisplayListBuilder::onSetImageFilter(const DlImageFilter* filter) {
   if (filter == nullptr) {
@@ -289,6 +290,7 @@
       }
     }
   }
+  UpdateCurrentOpacityCompatibility();
 }
 void DisplayListBuilder::onSetColorFilter(const DlColorFilter* filter) {
   if (filter == nullptr) {
diff --git a/display_list/dl_builder.h b/display_list/dl_builder.h
index fb7464a..92dc9b8 100644
--- a/display_list/dl_builder.h
+++ b/display_list/dl_builder.h
@@ -723,6 +723,7 @@
     current_opacity_compatibility_ =             //
         current_.getColorFilter() == nullptr &&  //
         !current_.isInvertColors() &&            //
+        !current_.usesRuntimeEffect() &&         //
         IsOpacityCompatible(current_.getBlendMode());
   }
 
diff --git a/display_list/dl_paint.h b/display_list/dl_paint.h
index ca5e69a..409bd80 100644
--- a/display_list/dl_paint.h
+++ b/display_list/dl_paint.h
@@ -178,6 +178,11 @@
 
   bool isDefault() const { return *this == kDefault; }
 
+  bool usesRuntimeEffect() const {
+    return ((color_source_ && color_source_->asRuntimeEffect()) ||
+            (image_filter_ && image_filter_->asRuntimeEffectFilter()));
+  }
+
   bool operator==(DlPaint const& other) const;
   bool operator!=(DlPaint const& other) const { return !(*this == other); }
 
diff --git a/display_list/dl_paint_unittests.cc b/display_list/dl_paint_unittests.cc
index e03f02e..1f76459 100644
--- a/display_list/dl_paint_unittests.cc
+++ b/display_list/dl_paint_unittests.cc
@@ -154,5 +154,49 @@
   EXPECT_NE(paint, DlPaint());
 }
 
+TEST(DisplayListPaint, PaintDetectsRuntimeEffects) {
+  const auto runtime_effect = DlRuntimeEffect::MakeSkia(
+      SkRuntimeEffect::MakeForShader(
+          SkString("vec4 main(vec2 p) { return vec4(0); }"))
+          .effect);
+  auto color_source = DlColorSource::MakeRuntimeEffect(
+      runtime_effect, {}, std::make_shared<std::vector<uint8_t>>());
+  auto image_filter = DlImageFilter::MakeRuntimeEffect(
+      runtime_effect, {}, std::make_shared<std::vector<uint8_t>>());
+  DlPaint paint;
+
+  EXPECT_FALSE(paint.usesRuntimeEffect());
+  paint.setColorSource(color_source);
+  EXPECT_TRUE(paint.usesRuntimeEffect());
+  paint.setColorSource(nullptr);
+  EXPECT_FALSE(paint.usesRuntimeEffect());
+
+  EXPECT_FALSE(paint.usesRuntimeEffect());
+  paint.setImageFilter(image_filter);
+  EXPECT_TRUE(paint.usesRuntimeEffect());
+  paint.setImageFilter(nullptr);
+  EXPECT_FALSE(paint.usesRuntimeEffect());
+
+  EXPECT_FALSE(paint.usesRuntimeEffect());
+  paint.setColorSource(color_source);
+  EXPECT_TRUE(paint.usesRuntimeEffect());
+  paint.setImageFilter(image_filter);
+  EXPECT_TRUE(paint.usesRuntimeEffect());
+  paint.setImageFilter(nullptr);
+  EXPECT_TRUE(paint.usesRuntimeEffect());
+  paint.setColorSource(nullptr);
+  EXPECT_FALSE(paint.usesRuntimeEffect());
+
+  EXPECT_FALSE(paint.usesRuntimeEffect());
+  paint.setColorSource(color_source);
+  EXPECT_TRUE(paint.usesRuntimeEffect());
+  paint.setImageFilter(image_filter);
+  EXPECT_TRUE(paint.usesRuntimeEffect());
+  paint.setColorSource(nullptr);
+  EXPECT_TRUE(paint.usesRuntimeEffect());
+  paint.setImageFilter(nullptr);
+  EXPECT_FALSE(paint.usesRuntimeEffect());
+}
+
 }  // namespace testing
 }  // namespace flutter