Hint that painting shadows are complex (#5000)

Previously, we considered them one operation in the compositor, which didn't
trigger caching. Now we have a way to explicitly hint that the compositor
should cache a layer.
diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart
index f1d66a5..73bf5e4 100644
--- a/packages/flutter/lib/src/painting/box_painter.dart
+++ b/packages/flutter/lib/src/painting/box_painter.dart
@@ -1200,6 +1200,9 @@
     );
   }
 
+  @override
+  bool get isComplex => boxShadow != null;
+
   /// Linearly interpolate between two box decorations.
   ///
   /// Interpolates each parameter of the box decoration separately.
diff --git a/packages/flutter/lib/src/painting/decoration.dart b/packages/flutter/lib/src/painting/decoration.dart
index 7cb3ab0..198a106 100644
--- a/packages/flutter/lib/src/painting/decoration.dart
+++ b/packages/flutter/lib/src/painting/decoration.dart
@@ -44,6 +44,9 @@
   /// to inset the children so as to not overlap the frame.
   EdgeInsets get padding => null;
 
+  /// Whether this decoration is complex enough to benefit from caching its painting.
+  bool get isComplex => false;
+
   /// Linearly interpolates from [a] to [this].
   Decoration lerpFrom(Decoration a, double t) => this;
 
diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart
index 0ba4c82..95583b8 100644
--- a/packages/flutter/lib/src/rendering/layer.dart
+++ b/packages/flutter/lib/src/rendering/layer.dart
@@ -114,9 +114,24 @@
   /// The picture's coodinate system matches this layer's coodinate system
   ui.Picture picture;
 
+  /// Hints that the painting in this layer is complex and would benefit from
+  /// caching.
+  ///
+  /// If this hint is not set, the compositor will apply its own heuristics to
+  /// decide whether the this layer is complex enough to benefit from caching.
+  bool isComplexHint = false;
+
+  /// Hints that the painting in this layer is likely to change next frame.
+  ///
+  /// This hint tells the compositor not to cache this layer because the cache
+  /// will not be used in the future. If this hint is not set, the compositor
+  /// will apply its own heuristics to decide whether this layer is likely to be
+  /// reused in the future.
+  bool willChangeHint = false;
+
   @override
   void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
-    builder.addPicture(layerOffset, picture);
+    builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
   }
 }
 
diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart
index 679640e..5dcd985 100644
--- a/packages/flutter/lib/src/rendering/object.dart
+++ b/packages/flutter/lib/src/rendering/object.dart
@@ -192,6 +192,26 @@
 
   static final Paint _disableAntialias = new Paint()..isAntiAlias = false;
 
+  /// Hints that the painting in the current layer is complex and would benefit
+  /// from caching.
+  ///
+  /// If this hint is not set, the compositor will apply its own heuristics to
+  /// decide whether the current layer is complex enough to benefit from
+  /// caching.
+  void setIsComplexHint() {
+    _currentLayer?.isComplexHint = true;
+  }
+
+  /// Hints that the painting in the current layer is likely to change next frame.
+  ///
+  /// This hint tells the compositor not to cache the current layer because the
+  /// cache will not be used in the future. If this hint is not set, the
+  /// compositor will apply its own heuristics to decide whether the current
+  /// layer is likely to be reused in the future.
+  void setWillChangeHint() {
+    _currentLayer?.willChangeHint = true;
+  }
+
   /// Adds a performance overlay to the scene.
   ///
   /// * `offset` is the offset from the origin of the canvas' coordinate system
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index 7ab769d..84702a8 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -1172,11 +1172,17 @@
     assert(size.height != null);
     _painter ??= _decoration.createBoxPainter(markNeedsPaint);
     final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
-    if (position == DecorationPosition.background)
+    if (position == DecorationPosition.background) {
       _painter.paint(context.canvas, offset, filledConfiguration);
+      if (decoration.isComplex)
+        context.setIsComplexHint();
+    }
     super.paint(context, offset);
-    if (position == DecorationPosition.foreground)
+    if (position == DecorationPosition.foreground) {
       _painter.paint(context.canvas, offset, filledConfiguration);
+      if (decoration.isComplex)
+        context.setIsComplexHint();
+    }
   }
 
   @override