Maintain dirtiness and use retained engine layers (#23434)
For #21756
diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart
index c018819..f4533ea 100644
--- a/packages/flutter/lib/src/rendering/layer.dart
+++ b/packages/flutter/lib/src/rendering/layer.dart
@@ -4,7 +4,7 @@
import 'dart:async';
import 'dart:collection';
-import 'dart:ui' as ui show Image, ImageFilter, Picture, Scene, SceneBuilder;
+import 'dart:ui' as ui show EngineLayer, Image, ImageFilter, Picture, Scene, SceneBuilder;
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
@@ -41,6 +41,59 @@
@override
ContainerLayer get parent => super.parent;
+ // Whether this layer has any changes since its last call to [addToScene].
+ //
+ // Initialized to true as a new layer has never called [addToScene].
+ bool _needsAddToScene = true;
+
+ /// Mark that this layer has changed and [addToScene] needs to be called.
+ @protected
+ void markNeedsAddToScene() {
+ _needsAddToScene = true;
+ }
+
+ /// Mark that this layer is in sync with engine.
+ ///
+ /// This is only for debug and test purpose only.
+ @visibleForTesting
+ void debugMarkClean() {
+ assert((){
+ _needsAddToScene = false;
+ return true;
+ }());
+ }
+
+ /// Subclasses may override this to true to disable retained rendering.
+ @protected
+ bool get alwaysNeedsAddToScene => false;
+
+ bool _subtreeNeedsAddToScene;
+
+ /// Whether any layer in the subtree needs [addToScene].
+ ///
+ /// This is for debug and test purpose only. It only becomes valid after
+ /// calling [updateSubtreeNeedsAddToScene].
+ @visibleForTesting
+ bool get debugSubtreeNeedsAddToScene {
+ bool result;
+ assert((){
+ result = _subtreeNeedsAddToScene;
+ return true;
+ }());
+ return result;
+ }
+
+ ui.EngineLayer _engineLayer;
+
+ /// Traverse the layer tree and compute if any subtree needs [addToScene].
+ ///
+ /// A subtree needs [addToScene] if any of its layer needs [addToScene].
+ /// The [ContainerLayer] will override this to respect its children.
+ @protected
+ void updateSubtreeNeedsAddToScene() {
+ _subtreeNeedsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
+ }
+
/// This layer's next sibling in the parent layer's child list.
Layer get nextSibling => _nextSibling;
Layer _nextSibling;
@@ -49,6 +102,18 @@
Layer get previousSibling => _previousSibling;
Layer _previousSibling;
+ @override
+ void dropChild(AbstractNode child) {
+ markNeedsAddToScene();
+ super.dropChild(child);
+ }
+
+ @override
+ void adoptChild(AbstractNode child) {
+ markNeedsAddToScene();
+ super.adoptChild(child);
+ }
+
/// Removes this layer from its parent layer's child list.
///
/// This has no effect if the layer's parent is already null.
@@ -104,7 +169,29 @@
S find<S>(Offset regionOffset);
/// Override this method to upload this layer to the engine.
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]);
+ ///
+ /// Return the engine layer for retained rendering. When there's no
+ /// corresponding engine layer, null is returned.
+ @protected
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]);
+
+ void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
+ // There can't be a loop by adding a retained layer subtree whose
+ // _subtreeNeedsAddToScene is false.
+ //
+ // Proof by contradiction:
+ //
+ // If we introduce a loop, this retained layer must be appended to one of
+ // its descendent layers, say A. That means the child structure of A has
+ // changed so A's _needsAddToScene is true. This contradicts
+ // _subtreeNeedsAddToScene being false.
+ if (!_subtreeNeedsAddToScene && _engineLayer != null) {
+ builder.addRetained(_engineLayer);
+ return;
+ }
+ _engineLayer = addToScene(builder);
+ _needsAddToScene = false;
+ }
/// The object responsible for creating this layer.
///
@@ -144,7 +231,12 @@
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- ui.Picture picture;
+ ui.Picture get picture => _picture;
+ ui.Picture _picture;
+ set picture(ui.Picture picture) {
+ _needsAddToScene = true;
+ _picture = picture;
+ }
/// Hints that the painting in this layer is complex and would benefit from
/// caching.
@@ -154,7 +246,14 @@
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- bool isComplexHint = false;
+ bool get isComplexHint => _isComplexHint;
+ bool _isComplexHint = false;
+ set isComplexHint(bool value) {
+ if (value != _isComplexHint) {
+ _isComplexHint = value;
+ markNeedsAddToScene();
+ }
+ }
/// Hints that the painting in this layer is likely to change next frame.
///
@@ -165,11 +264,19 @@
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- bool willChangeHint = false;
+ bool get willChangeHint => _willChangeHint;
+ bool _willChangeHint = false;
+ set willChangeHint(bool value) {
+ if (value != _willChangeHint) {
+ _willChangeHint = value;
+ markNeedsAddToScene();
+ }
+ }
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
+ return null; // this does not return an engine layer yet.
}
@override
@@ -234,7 +341,7 @@
final bool freeze;
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
final Rect shiftedRect = rect.shift(layerOffset);
builder.addTexture(
textureId,
@@ -243,6 +350,7 @@
height: shiftedRect.height,
freeze: freeze,
);
+ return null; // this does not return an engine layer yet.
}
@override
@@ -256,18 +364,25 @@
class PerformanceOverlayLayer extends Layer {
/// Creates a layer that displays a performance overlay.
PerformanceOverlayLayer({
- @required this.overlayRect,
+ @required Rect overlayRect,
@required this.optionsMask,
@required this.rasterizerThreshold,
@required this.checkerboardRasterCacheImages,
@required this.checkerboardOffscreenLayers,
- });
+ }) : _overlayRect = overlayRect;
/// The rectangle in this layer's coordinate system that the overlay should occupy.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- Rect overlayRect;
+ Rect get overlayRect => _overlayRect;
+ Rect _overlayRect;
+ set overlayRect(Rect value) {
+ if (value != _overlayRect) {
+ _overlayRect = value;
+ markNeedsAddToScene();
+ }
+ }
/// The mask is created by shifting 1 by the index of the specific
/// [PerformanceOverlayOption] to enable.
@@ -302,12 +417,13 @@
final bool checkerboardOffscreenLayers;
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
assert(optionsMask != null);
builder.addPerformanceOverlay(optionsMask, overlayRect.shift(layerOffset));
builder.setRasterizerTracingThreshold(rasterizerThreshold);
builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers);
+ return null; // this does not return an engine layer yet.
}
@override
@@ -349,6 +465,17 @@
}
@override
+ void updateSubtreeNeedsAddToScene() {
+ super.updateSubtreeNeedsAddToScene();
+ Layer child = firstChild;
+ while (child != null) {
+ child.updateSubtreeNeedsAddToScene();
+ _subtreeNeedsAddToScene = _subtreeNeedsAddToScene || child._subtreeNeedsAddToScene;
+ child = child.nextSibling;
+ }
+ }
+
+ @override
S find<S>(Offset regionOffset) {
Layer current = lastChild;
while (current != null) {
@@ -451,8 +578,9 @@
}
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
addChildrenToScene(builder, layerOffset);
+ return null; // ContainerLayer does not have a corresponding engine layer
}
/// Uploads all of this layer's children to the engine.
@@ -465,7 +593,11 @@
void addChildrenToScene(ui.SceneBuilder builder, [Offset childOffset = Offset.zero]) {
Layer child = firstChild;
while (child != null) {
- child.addToScene(builder, childOffset);
+ if (childOffset == Offset.zero) {
+ child._addToSceneWithRetainedRendering(builder);
+ } else {
+ child.addToScene(builder, childOffset);
+ }
child = child.nextSibling;
}
}
@@ -540,7 +672,7 @@
///
/// By default, [offset] is zero. It must be non-null before the compositing
/// phase of the pipeline.
- OffsetLayer({ this.offset = Offset.zero });
+ OffsetLayer({ Offset offset = Offset.zero }) : _offset = offset;
/// Offset from parent in the parent's coordinate system.
///
@@ -549,7 +681,14 @@
///
/// The [offset] property must be non-null before the compositing phase of the
/// pipeline.
- Offset offset;
+ Offset get offset => _offset;
+ Offset _offset;
+ set offset(Offset value) {
+ if (value != _offset) {
+ markNeedsAddToScene();
+ }
+ _offset = value;
+ }
@override
S find<S>(Offset regionOffset) {
@@ -563,16 +702,25 @@
transform.multiply(Matrix4.translationValues(offset.dx, offset.dy, 0.0));
}
+ /// Consider this layer as the root and build a scene (a tree of layers)
+ /// in the engine.
+ ui.Scene buildScene(ui.SceneBuilder builder) {
+ updateSubtreeNeedsAddToScene();
+ addToScene(builder);
+ return builder.build();
+ }
+
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
// Skia has a fast path for concatenating scale/translation only matrices.
// Hence pushing a translation-only transform layer should be fast. For
// retained rendering, we don't want to push the offset down to each leaf
// node. Otherwise, changing an offset layer on the very high level could
// cascade the change to too many leaves.
- builder.pushOffset(layerOffset.dx + offset.dx, layerOffset.dy + offset.dy);
+ final ui.EngineLayer engineLayer = builder.pushOffset(layerOffset.dx + offset.dx, layerOffset.dy + offset.dy);
addChildrenToScene(builder);
builder.pop();
+ return engineLayer;
}
@override
@@ -608,8 +756,7 @@
);
transform.scale(pixelRatio, pixelRatio);
builder.pushTransform(transform.storage);
- addToScene(builder);
- final ui.Scene scene = builder.build();
+ final ui.Scene scene = buildScene(builder);
try {
// Size is rounded up to the next pixel to make sure we don't clip off
// anything.
@@ -633,14 +780,22 @@
///
/// The [clipRect] property must be non-null before the compositing phase of
/// the pipeline.
- ClipRectLayer({ this.clipRect, Clip clipBehavior = Clip.hardEdge }) :
- _clipBehavior = clipBehavior, assert(clipBehavior != null), assert(clipBehavior != Clip.none);
+ ClipRectLayer({ @required Rect clipRect, Clip clipBehavior = Clip.hardEdge }) :
+ _clipRect = clipRect, _clipBehavior = clipBehavior,
+ assert(clipBehavior != null), assert(clipBehavior != Clip.none);
/// The rectangle to clip in the parent's coordinate system.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- Rect clipRect;
+ Rect get clipRect => _clipRect;
+ Rect _clipRect;
+ set clipRect(Rect value) {
+ if (value != _clipRect) {
+ _clipRect = value;
+ markNeedsAddToScene();
+ }
+ }
/// {@template flutter.clipper.clipBehavior}
/// Controls how to clip (default to [Clip.antiAlias]).
@@ -652,7 +807,10 @@
set clipBehavior(Clip value) {
assert(value != null);
assert(value != Clip.none);
- _clipBehavior = value;
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsAddToScene();
+ }
}
@override
@@ -663,7 +821,7 @@
}
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
bool enabled = true;
assert(() {
enabled = !debugDisableClipLayers;
@@ -674,6 +832,7 @@
addChildrenToScene(builder, layerOffset);
if (enabled)
builder.pop();
+ return null; // this does not return an engine layer yet.
}
@override
@@ -693,14 +852,22 @@
///
/// The [clipRRect] property must be non-null before the compositing phase of
/// the pipeline.
- ClipRRectLayer({ this.clipRRect, Clip clipBehavior = Clip.antiAlias }) :
- _clipBehavior = clipBehavior, assert(clipBehavior != null), assert(clipBehavior != Clip.none);
+ ClipRRectLayer({ @required RRect clipRRect, Clip clipBehavior = Clip.antiAlias }) :
+ _clipRRect = clipRRect, _clipBehavior = clipBehavior,
+ assert(clipBehavior != null), assert(clipBehavior != Clip.none);
/// The rounded-rect to clip in the parent's coordinate system.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- RRect clipRRect;
+ RRect get clipRRect => _clipRRect;
+ RRect _clipRRect;
+ set clipRRect(RRect value) {
+ if (value != _clipRRect) {
+ _clipRRect = value;
+ markNeedsAddToScene();
+ }
+ }
/// {@macro flutter.clipper.clipBehavior}
Clip get clipBehavior => _clipBehavior;
@@ -708,7 +875,10 @@
set clipBehavior(Clip value) {
assert(value != null);
assert(value != Clip.none);
- _clipBehavior = value;
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsAddToScene();
+ }
}
@override
@@ -719,7 +889,7 @@
}
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
bool enabled = true;
assert(() {
enabled = !debugDisableClipLayers;
@@ -730,6 +900,7 @@
addChildrenToScene(builder, layerOffset);
if (enabled)
builder.pop();
+ return null; // this does not return an engine layer yet.
}
@override
@@ -749,14 +920,22 @@
///
/// The [clipPath] property must be non-null before the compositing phase of
/// the pipeline.
- ClipPathLayer({ this.clipPath, Clip clipBehavior = Clip.antiAlias }) :
- _clipBehavior = clipBehavior, assert(clipBehavior != null), assert(clipBehavior != Clip.none);
+ ClipPathLayer({ @required Path clipPath, Clip clipBehavior = Clip.antiAlias }) :
+ _clipPath = clipPath, _clipBehavior = clipBehavior,
+ assert(clipBehavior != null), assert(clipBehavior != Clip.none);
/// The path to clip in the parent's coordinate system.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- Path clipPath;
+ Path get clipPath => _clipPath;
+ Path _clipPath;
+ set clipPath(Path value) {
+ if (value != _clipPath) {
+ _clipPath = value;
+ markNeedsAddToScene();
+ }
+ }
/// {@macro flutter.clipper.clipBehavior}
Clip get clipBehavior => _clipBehavior;
@@ -764,7 +943,10 @@
set clipBehavior(Clip value) {
assert(value != null);
assert(value != Clip.none);
- _clipBehavior = value;
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsAddToScene();
+ }
}
@override
@@ -775,7 +957,7 @@
}
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
bool enabled = true;
assert(() {
enabled = !debugDisableClipLayers;
@@ -786,6 +968,7 @@
addChildrenToScene(builder, layerOffset);
if (enabled)
builder.pop();
+ return null; // this does not return an engine layer yet.
}
}
@@ -826,7 +1009,7 @@
bool _inverseDirty = true;
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
_lastEffectiveTransform = transform;
final Offset totalOffset = offset + layerOffset;
if (totalOffset != Offset.zero) {
@@ -836,6 +1019,7 @@
builder.pushTransform(_lastEffectiveTransform.storage);
addChildrenToScene(builder);
builder.pop();
+ return null; // this does not return an engine layer yet.
}
@override
@@ -875,7 +1059,8 @@
///
/// The [alpha] property must be non-null before the compositing phase of
/// the pipeline.
- OpacityLayer({ this.alpha });
+ OpacityLayer({ @required int alpha, Offset offset = Offset.zero })
+ : _alpha = alpha, _offset = offset;
/// The amount to multiply into the alpha channel.
///
@@ -884,20 +1069,38 @@
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- int alpha;
+ int get alpha => _alpha;
+ int _alpha;
+ set alpha(int value) {
+ if (value != _alpha) {
+ _alpha = value;
+ markNeedsAddToScene();
+ }
+ }
+
+ /// Offset from parent in the parent's coordinate system.
+ Offset get offset => _offset;
+ Offset _offset;
+ set offset(Offset value) {
+ if (value != _offset) {
+ _offset = value;
+ markNeedsAddToScene();
+ }
+ }
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
bool enabled = true;
assert(() {
enabled = !debugDisableOpacityLayers;
return true;
}());
if (enabled)
- builder.pushOpacity(alpha);
- addChildrenToScene(builder, layerOffset);
+ builder.pushOpacity(alpha, offset: offset + layerOffset);
+ addChildrenToScene(builder);
if (enabled)
builder.pop();
+ return null; // this does not return an engine layer yet.
}
@override
@@ -913,31 +1116,54 @@
///
/// The [shader], [maskRect], and [blendMode] properties must be non-null
/// before the compositing phase of the pipeline.
- ShaderMaskLayer({ this.shader, this.maskRect, this.blendMode });
+ ShaderMaskLayer({ @required Shader shader, @required Rect maskRect, @required BlendMode blendMode })
+ : _shader = shader, _maskRect = maskRect, _blendMode = blendMode;
/// The shader to apply to the children.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- Shader shader;
+ Shader get shader => _shader;
+ Shader _shader;
+ set shader(Shader value) {
+ if (value != _shader) {
+ _shader = value;
+ markNeedsAddToScene();
+ }
+ }
/// The size of the shader.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- Rect maskRect;
+ Rect get maskRect => _maskRect;
+ Rect _maskRect;
+ set maskRect(Rect value) {
+ if (value != _maskRect) {
+ _maskRect = value;
+ markNeedsAddToScene();
+ }
+ }
/// The blend mode to apply when blending the shader with the children.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- BlendMode blendMode;
+ BlendMode get blendMode => _blendMode;
+ BlendMode _blendMode;
+ set blendMode(BlendMode value) {
+ if (value != _blendMode) {
+ _blendMode = value;
+ markNeedsAddToScene();
+ }
+ }
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
builder.pushShaderMask(shader, maskRect.shift(layerOffset), blendMode);
addChildrenToScene(builder, layerOffset);
builder.pop();
+ return null; // this does not return an engine layer yet.
}
@override
@@ -955,19 +1181,27 @@
///
/// The [filter] property must be non-null before the compositing phase of the
/// pipeline.
- BackdropFilterLayer({ this.filter });
+ BackdropFilterLayer({ @required ui.ImageFilter filter }) : _filter = filter;
/// The filter to apply to the existing contents of the scene.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- ui.ImageFilter filter;
+ ui.ImageFilter get filter => _filter;
+ ui.ImageFilter _filter;
+ set filter(ui.ImageFilter value) {
+ if (value != _filter) {
+ _filter = value;
+ markNeedsAddToScene();
+ }
+ }
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
builder.pushBackdropFilter(filter);
addChildrenToScene(builder, layerOffset);
builder.pop();
+ return null; // this does not return an engine layer yet.
}
}
@@ -986,25 +1220,44 @@
///
/// The [clipPath], [elevation], and [color] arguments must not be null.
PhysicalModelLayer({
- @required this.clipPath,
- this.clipBehavior = Clip.none,
- @required this.elevation,
- @required this.color,
- @required this.shadowColor,
+ @required Path clipPath,
+ Clip clipBehavior = Clip.none,
+ @required double elevation,
+ @required Color color,
+ @required Color shadowColor,
}) : assert(clipPath != null),
assert(clipBehavior != null),
assert(elevation != null),
assert(color != null),
- assert(shadowColor != null);
+ assert(shadowColor != null),
+ _clipPath = clipPath,
+ _clipBehavior = clipBehavior,
+ _elevation = elevation,
+ _color = color,
+ _shadowColor = shadowColor;
/// The path to clip in the parent's coordinate system.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- Path clipPath;
+ Path get clipPath => _clipPath;
+ Path _clipPath;
+ set clipPath(Path value) {
+ if (value != _clipPath) {
+ _clipPath = value;
+ markNeedsAddToScene();
+ }
+ }
/// {@macro flutter.widgets.Clip}
- Clip clipBehavior;
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior;
+ set clipBehavior(Clip value) {
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsAddToScene();
+ }
+ }
/// The z-coordinate at which to place this physical object.
///
@@ -1016,16 +1269,37 @@
/// flag is set. For this reason, this property will often be set to zero in
/// tests even if the layer should be raised. To verify the actual value,
/// consider setting [debugDisableShadows] to false in your test.
- double elevation;
+ double get elevation => _elevation;
+ double _elevation;
+ set elevation(double value) {
+ if (value != _elevation) {
+ _elevation = value;
+ markNeedsAddToScene();
+ }
+ }
/// The background color.
///
/// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]).
- Color color;
+ Color get color => _color;
+ Color _color;
+ set color(Color value) {
+ if (value != _color) {
+ _color = value;
+ markNeedsAddToScene();
+ }
+ }
/// The shadow color.
- Color shadowColor;
+ Color get shadowColor => _shadowColor;
+ Color _shadowColor;
+ set shadowColor(Color value) {
+ if (value != _shadowColor) {
+ _shadowColor = value;
+ markNeedsAddToScene();
+ }
+ }
@override
S find<S>(Offset regionOffset) {
@@ -1035,14 +1309,15 @@
}
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer engineLayer;
bool enabled = true;
assert(() {
enabled = !debugDisablePhysicalShapeLayers;
return true;
}());
if (enabled) {
- builder.pushPhysicalShape(
+ engineLayer = builder.pushPhysicalShape(
path: clipPath.shift(layerOffset),
elevation: elevation,
color: color,
@@ -1053,6 +1328,7 @@
addChildrenToScene(builder, layerOffset);
if (enabled)
builder.pop();
+ return engineLayer;
}
@override
@@ -1115,6 +1391,10 @@
/// pipeline.
Offset offset;
+ /// {@macro flutter.leaderFollower.alwaysNeedsAddToScene}
+ @override
+ bool get alwaysNeedsAddToScene => true;
+
@override
void attach(Object owner) {
super.attach(owner);
@@ -1144,7 +1424,7 @@
}
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
assert(offset != null);
_lastOffset = offset + layerOffset;
if (_lastOffset != Offset.zero)
@@ -1152,6 +1432,7 @@
addChildrenToScene(builder);
if (_lastOffset != Offset.zero)
builder.pop();
+ return null; // this does not have an engine layer.
}
/// Applies the transform that would be applied when compositing the given
@@ -1347,15 +1628,28 @@
_inverseDirty = true;
}
+ /// {@template flutter.leaderFollower.alwaysNeedsAddToScene}
+ /// This disables retained rendering for Leader/FollowerLayer.
+ ///
+ /// A FollowerLayer copies changes from a LeaderLayer that could be anywhere
+ /// in the Layer tree, and that LeaderLayer could change without notifying the
+ /// FollowerLayer. Therefore we have to always call a FollowerLayer's
+ /// [addToScene]. In order to call FollowerLayer's [addToScene], LeaderLayer's
+ /// [addToScene] must be called first so LeaderLayer must also be considered
+ /// as [alwaysNeedsAddToScene].
+ /// {@endtemplate}
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ bool get alwaysNeedsAddToScene => true;
+
+ @override
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
assert(link != null);
assert(showWhenUnlinked != null);
if (link.leader == null && !showWhenUnlinked) {
_lastTransform = null;
_lastOffset = null;
_inverseDirty = true;
- return;
+ return null; // this does not have an engine layer.
}
_establishTransform();
if (_lastTransform != null) {
@@ -1371,6 +1665,7 @@
builder.pop();
}
_inverseDirty = true;
+ return null; // this does not have an engine layer.
}
@override
diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart
index 8a92f06..3b53308 100644
--- a/packages/flutter/lib/src/rendering/object.dart
+++ b/packages/flutter/lib/src/rendering/object.dart
@@ -198,6 +198,7 @@
return true;
}());
}
+ assert(child._layer != null);
child._layer.offset = offset;
appendLayer(child._layer);
}
@@ -488,7 +489,7 @@
/// ancestor render objects that this render object will include a composited
/// layer, which, for example, causes them to use composited clips.
void pushOpacity(Offset offset, int alpha, PaintingContextCallback painter) {
- pushLayer(OpacityLayer(alpha: alpha), painter, offset);
+ pushLayer(OpacityLayer(alpha: alpha, offset: offset), painter, Offset.zero);
}
@override
diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart
index 44e11b2..3719760 100644
--- a/packages/flutter/lib/src/rendering/view.dart
+++ b/packages/flutter/lib/src/rendering/view.dart
@@ -192,8 +192,7 @@
Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
- layer.addToScene(builder);
- final ui.Scene scene = builder.build();
+ final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
ui.window.render(scene);
diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart
index 19dec90..673616c 100644
--- a/packages/flutter/lib/src/widgets/widget_inspector.dart
+++ b/packages/flutter/lib/src/widgets/widget_inspector.dart
@@ -12,6 +12,7 @@
show
window,
ClipOp,
+ EngineLayer,
Image,
ImageByteFormat,
Paragraph,
@@ -54,8 +55,8 @@
final Layer _layer;
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
- _layer.addToScene(builder, layerOffset);
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ return _layer.addToScene(builder, layerOffset);
}
@override
@@ -312,8 +313,9 @@
/// screenshots render to the scene in the local coordinate system of the layer.
class _ScreenshotContainerLayer extends OffsetLayer {
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
addChildrenToScene(builder, layerOffset);
+ return null; // this does not have an engine layer.
}
}
@@ -588,7 +590,7 @@
// We must build the regular scene before we can build the screenshot
// scene as building the screenshot scene assumes addToScene has already
// been called successfully for all layers in the regular scene.
- repaintBoundary.layer.addToScene(ui.SceneBuilder());
+ repaintBoundary.layer.buildScene(ui.SceneBuilder());
return data.containerLayer.toImage(renderBounds, pixelRatio: pixelRatio);
}
@@ -2226,9 +2228,9 @@
double _textPainterMaxWidth;
@override
- void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
+ ui.EngineLayer addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
if (!selection.active)
- return;
+ return null;
final RenderObject selected = selection.current;
final List<_TransformedRect> candidates = <_TransformedRect>[];
@@ -2251,6 +2253,7 @@
_picture = _buildPicture(state);
}
builder.addPicture(layerOffset, _picture);
+ return null; // this does not have an engine layer.
}
ui.Picture _buildPicture(_InspectorOverlayRenderState state) {
diff --git a/packages/flutter/test/rendering/layers_test.dart b/packages/flutter/test/rendering/layers_test.dart
index 727c655..0ce8f33 100644
--- a/packages/flutter/test/rendering/layers_test.dart
+++ b/packages/flutter/test/rendering/layers_test.dart
@@ -40,4 +40,73 @@
expect(boundary.layer, isNotNull);
expect(boundary.layer.attached, isTrue); // this time it did again!
});
+
+ test('layer subtree dirtiness is correctly computed', () {
+ final ContainerLayer a = ContainerLayer();
+ final ContainerLayer b = ContainerLayer();
+ final ContainerLayer c = ContainerLayer();
+ final ContainerLayer d = ContainerLayer();
+ final ContainerLayer e = ContainerLayer();
+ final ContainerLayer f = ContainerLayer();
+ final ContainerLayer g = ContainerLayer();
+
+ final PictureLayer h = PictureLayer(Rect.zero);
+ final PictureLayer i = PictureLayer(Rect.zero);
+ final PictureLayer j = PictureLayer(Rect.zero);
+
+ // The tree is like the following where b and j are dirty:
+ // a____
+ // / \
+ // (x)b___ c
+ // / \ \ |
+ // d e f g
+ // / \ |
+ // h i j(x)
+ a.append(b);
+ a.append(c);
+ b.append(d);
+ b.append(e);
+ b.append(f);
+ d.append(h);
+ d.append(i);
+ c.append(g);
+ g.append(j);
+
+ a.debugMarkClean();
+ b.markNeedsAddToScene(); // ignore: invalid_use_of_protected_member
+ c.debugMarkClean();
+ d.debugMarkClean();
+ e.debugMarkClean();
+ f.debugMarkClean();
+ g.debugMarkClean();
+ h.debugMarkClean();
+ i.debugMarkClean();
+ j.markNeedsAddToScene(); // ignore: invalid_use_of_protected_member
+
+ a.updateSubtreeNeedsAddToScene();
+
+ expect(a.debugSubtreeNeedsAddToScene, true);
+ expect(b.debugSubtreeNeedsAddToScene, true);
+ expect(c.debugSubtreeNeedsAddToScene, true);
+ expect(g.debugSubtreeNeedsAddToScene, true);
+ expect(j.debugSubtreeNeedsAddToScene, true);
+
+ expect(d.debugSubtreeNeedsAddToScene, false);
+ expect(e.debugSubtreeNeedsAddToScene, false);
+ expect(f.debugSubtreeNeedsAddToScene, false);
+ expect(h.debugSubtreeNeedsAddToScene, false);
+ expect(i.debugSubtreeNeedsAddToScene, false);
+ });
+
+ test('leader and follower layers are always dirty', () {
+ final LayerLink link = LayerLink();
+ final LeaderLayer leaderLayer = LeaderLayer(link: link);
+ final FollowerLayer followerLayer = FollowerLayer(link: link);
+ leaderLayer.debugMarkClean();
+ followerLayer.debugMarkClean();
+ leaderLayer.updateSubtreeNeedsAddToScene();
+ followerLayer.updateSubtreeNeedsAddToScene();
+ expect(leaderLayer.debugSubtreeNeedsAddToScene, true);
+ expect(followerLayer.debugSubtreeNeedsAddToScene, true);
+ });
}