Skip to content

Commit 34c6926

Browse files
authored
Teach render objects to reuse engine layers (flutter#36402)
Teach Layer and its implementations, RenderObject and its implementations, and PaintingContext to reuse engine layers. The idea is that a concrete RenderObject creates a Layer and holds on to it as long as it needs it (i.e. when it is composited, and the layer type does not change). In return, each Layer object holds on to an EngineLayer and reports it to the engine via addRetained and oldLayer. This allows the Web engine to reuse DOM elements across frames. Without it, each frame drops all previously rendered HTML and regenerates it from scratch.
1 parent 1a5e4a5 commit 34c6926

22 files changed

+1213
-358
lines changed

packages/flutter/lib/src/rendering/binding.dart

+1-6
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
242242
// Creates a [MouseTracker] which manages state about currently connected
243243
// mice, for hover notification.
244244
MouseTracker _createMouseTracker() {
245-
return MouseTracker(pointerRouter, (Offset offset) {
246-
// Layer hit testing is done using device pixels, so we have to convert
247-
// the logical coordinates of the event location back to device pixels
248-
// here.
249-
return renderView.layer.findAll<MouseTrackerAnnotation>(offset * window.devicePixelRatio);
250-
});
245+
return MouseTracker(pointerRouter, renderView.hitTestMouseTrackers);
251246
}
252247

253248
void _handleSemanticsEnabledChanged() {

packages/flutter/lib/src/rendering/layer.dart

+261-137
Large diffs are not rendered by default.

packages/flutter/lib/src/rendering/object.dart

+129-47
Large diffs are not rendered by default.

packages/flutter/lib/src/rendering/proxy_box.dart

+116-54
Original file line numberDiff line numberDiff line change
@@ -789,14 +789,18 @@ class RenderOpacity extends RenderProxyBox {
789789
void paint(PaintingContext context, Offset offset) {
790790
if (child != null) {
791791
if (_alpha == 0) {
792+
// No need to keep the layer. We'll create a new one if necessary.
793+
layer = null;
792794
return;
793795
}
794796
if (_alpha == 255) {
797+
// No need to keep the layer. We'll create a new one if necessary.
798+
layer = null;
795799
context.paintChild(child, offset);
796800
return;
797801
}
798802
assert(needsCompositing);
799-
context.pushOpacity(offset, _alpha, super.paint);
803+
layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer);
800804
}
801805
}
802806

@@ -904,14 +908,19 @@ class RenderAnimatedOpacity extends RenderProxyBox {
904908
@override
905909
void paint(PaintingContext context, Offset offset) {
906910
if (child != null) {
907-
if (_alpha == 0)
911+
if (_alpha == 0) {
912+
// No need to keep the layer. We'll create a new one if necessary.
913+
layer = null;
908914
return;
915+
}
909916
if (_alpha == 255) {
917+
// No need to keep the layer. We'll create a new one if necessary.
918+
layer = null;
910919
context.paintChild(child, offset);
911920
return;
912921
}
913922
assert(needsCompositing);
914-
context.pushOpacity(offset, _alpha, super.paint);
923+
layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer);
915924
}
916925
}
917926

@@ -952,6 +961,9 @@ class RenderShaderMask extends RenderProxyBox {
952961
_blendMode = blendMode,
953962
super(child);
954963

964+
@override
965+
ShaderMaskLayer get layer => super.layer;
966+
955967
/// Called to creates the [Shader] that generates the mask.
956968
///
957969
/// The shader callback is called with the current size of the child so that
@@ -989,15 +1001,14 @@ class RenderShaderMask extends RenderProxyBox {
9891001
void paint(PaintingContext context, Offset offset) {
9901002
if (child != null) {
9911003
assert(needsCompositing);
992-
context.pushLayer(
993-
ShaderMaskLayer(
994-
shader: _shaderCallback(offset & size),
995-
maskRect: offset & size,
996-
blendMode: _blendMode,
997-
),
998-
super.paint,
999-
offset,
1000-
);
1004+
layer ??= ShaderMaskLayer();
1005+
layer
1006+
..shader = _shaderCallback(offset & size)
1007+
..maskRect = offset & size
1008+
..blendMode = _blendMode;
1009+
context.pushLayer(layer, super.paint, offset);
1010+
} else {
1011+
layer = null;
10011012
}
10021013
}
10031014
}
@@ -1015,6 +1026,9 @@ class RenderBackdropFilter extends RenderProxyBox {
10151026
_filter = filter,
10161027
super(child);
10171028

1029+
@override
1030+
BackdropFilterLayer get layer => super.layer;
1031+
10181032
/// The image filter to apply to the existing painted content before painting
10191033
/// the child.
10201034
///
@@ -1037,7 +1051,11 @@ class RenderBackdropFilter extends RenderProxyBox {
10371051
void paint(PaintingContext context, Offset offset) {
10381052
if (child != null) {
10391053
assert(needsCompositing);
1040-
context.pushLayer(BackdropFilterLayer(filter: _filter), super.paint, offset);
1054+
layer ??= BackdropFilterLayer();
1055+
layer.filter = _filter;
1056+
context.pushLayer(layer, super.paint, offset);
1057+
} else {
1058+
layer = null;
10411059
}
10421060
}
10431061
}
@@ -1292,7 +1310,9 @@ class RenderClipRect extends _RenderCustomClip<Rect> {
12921310
void paint(PaintingContext context, Offset offset) {
12931311
if (child != null) {
12941312
_updateClip();
1295-
context.pushClipRect(needsCompositing, offset, _clip, super.paint, clipBehavior: clipBehavior);
1313+
layer = context.pushClipRect(needsCompositing, offset, _clip, super.paint, clipBehavior: clipBehavior, oldLayer: layer);
1314+
} else {
1315+
layer = null;
12961316
}
12971317
}
12981318

@@ -1368,7 +1388,9 @@ class RenderClipRRect extends _RenderCustomClip<RRect> {
13681388
void paint(PaintingContext context, Offset offset) {
13691389
if (child != null) {
13701390
_updateClip();
1371-
context.pushClipRRect(needsCompositing, offset, _clip.outerRect, _clip, super.paint, clipBehavior: clipBehavior);
1391+
layer = context.pushClipRRect(needsCompositing, offset, _clip.outerRect, _clip, super.paint, clipBehavior: clipBehavior, oldLayer: layer);
1392+
} else {
1393+
layer = null;
13721394
}
13731395
}
13741396

@@ -1436,7 +1458,9 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
14361458
void paint(PaintingContext context, Offset offset) {
14371459
if (child != null) {
14381460
_updateClip();
1439-
context.pushClipPath(needsCompositing, offset, _clip, _getClipPath(_clip), super.paint, clipBehavior: clipBehavior);
1461+
layer = context.pushClipPath(needsCompositing, offset, _clip, _getClipPath(_clip), super.paint, clipBehavior: clipBehavior, oldLayer: layer);
1462+
} else {
1463+
layer = null;
14401464
}
14411465
}
14421466

@@ -1498,7 +1522,9 @@ class RenderClipPath extends _RenderCustomClip<Path> {
14981522
void paint(PaintingContext context, Offset offset) {
14991523
if (child != null) {
15001524
_updateClip();
1501-
context.pushClipPath(needsCompositing, offset, Offset.zero & size, _clip, super.paint, clipBehavior: clipBehavior);
1525+
layer = context.pushClipPath(needsCompositing, offset, Offset.zero & size, _clip, super.paint, clipBehavior: clipBehavior, oldLayer: layer);
1526+
} else {
1527+
layer = null;
15021528
}
15031529
}
15041530

@@ -1631,6 +1657,9 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
16311657
shadowColor: shadowColor
16321658
);
16331659

1660+
@override
1661+
PhysicalModelLayer get layer => super.layer;
1662+
16341663
/// The shape of the layer.
16351664
///
16361665
/// Defaults to [BoxShape.rectangle]. The [borderRadius] affects the corners
@@ -1710,18 +1739,20 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
17101739
}
17111740
return true;
17121741
}());
1713-
final PhysicalModelLayer physicalModel = PhysicalModelLayer(
1714-
clipPath: offsetRRectAsPath,
1715-
clipBehavior: clipBehavior,
1716-
elevation: paintShadows ? elevation : 0.0,
1717-
color: color,
1718-
shadowColor: shadowColor,
1719-
);
1742+
layer ??= PhysicalModelLayer();
1743+
layer
1744+
..clipPath = offsetRRectAsPath
1745+
..clipBehavior = clipBehavior
1746+
..elevation = paintShadows ? elevation : 0.0
1747+
..color = color
1748+
..shadowColor = shadowColor;
1749+
context.pushLayer(layer, super.paint, offset, childPaintBounds: offsetBounds);
17201750
assert(() {
1721-
physicalModel.debugCreator = debugCreator;
1751+
layer.debugCreator = debugCreator;
17221752
return true;
17231753
}());
1724-
context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds);
1754+
} else {
1755+
layer = null;
17251756
}
17261757
}
17271758

@@ -1768,6 +1799,9 @@ class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
17681799
clipBehavior: clipBehavior
17691800
);
17701801

1802+
@override
1803+
PhysicalModelLayer get layer => super.layer;
1804+
17711805
@override
17721806
Path get _defaultClip => Path()..addRect(Offset.zero & size);
17731807

@@ -1804,18 +1838,20 @@ class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
18041838
}
18051839
return true;
18061840
}());
1807-
final PhysicalModelLayer physicalModel = PhysicalModelLayer(
1808-
clipPath: offsetPath,
1809-
clipBehavior: clipBehavior,
1810-
elevation: paintShadows ? elevation : 0.0,
1811-
color: color,
1812-
shadowColor: shadowColor,
1813-
);
1841+
layer ??= PhysicalModelLayer();
1842+
layer
1843+
..clipPath = offsetPath
1844+
..clipBehavior = clipBehavior
1845+
..elevation = paintShadows ? elevation : 0.0
1846+
..color = color
1847+
..shadowColor = shadowColor;
1848+
context.pushLayer(layer, super.paint, offset, childPaintBounds: offsetBounds);
18141849
assert(() {
1815-
physicalModel.debugCreator = debugCreator;
1850+
layer.debugCreator = debugCreator;
18161851
return true;
18171852
}());
1818-
context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds);
1853+
} else {
1854+
layer = null;
18191855
}
18201856
}
18211857

@@ -2145,10 +2181,12 @@ class RenderTransform extends RenderProxyBox {
21452181
if (child != null) {
21462182
final Matrix4 transform = _effectiveTransform;
21472183
final Offset childOffset = MatrixUtils.getAsTranslation(transform);
2148-
if (childOffset == null)
2149-
context.pushTransform(needsCompositing, offset, transform, super.paint);
2150-
else
2184+
if (childOffset == null) {
2185+
layer = context.pushTransform(needsCompositing, offset, transform, super.paint, oldLayer: layer);
2186+
} else {
21512187
super.paint(context, offset + childOffset);
2188+
layer = null;
2189+
}
21522190
}
21532191
}
21542192

@@ -2288,12 +2326,14 @@ class RenderFittedBox extends RenderProxyBox {
22882326
}
22892327
}
22902328

2291-
void _paintChildWithTransform(PaintingContext context, Offset offset) {
2329+
TransformLayer _paintChildWithTransform(PaintingContext context, Offset offset) {
22922330
final Offset childOffset = MatrixUtils.getAsTranslation(_transform);
22932331
if (childOffset == null)
2294-
context.pushTransform(needsCompositing, offset, _transform, super.paint);
2332+
return context.pushTransform(needsCompositing, offset, _transform, super.paint,
2333+
oldLayer: layer is TransformLayer ? layer : null);
22952334
else
22962335
super.paint(context, offset + childOffset);
2336+
return null;
22972337
}
22982338

22992339
@override
@@ -2303,9 +2343,10 @@ class RenderFittedBox extends RenderProxyBox {
23032343
_updatePaintData();
23042344
if (child != null) {
23052345
if (_hasVisualOverflow)
2306-
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform);
2346+
layer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform,
2347+
oldLayer: layer is ClipRectLayer ? layer : null);
23072348
else
2308-
_paintChildWithTransform(context, offset);
2349+
layer = _paintChildWithTransform(context, offset);
23092350
}
23102351
}
23112352

@@ -2706,6 +2747,7 @@ class RenderMouseRegion extends RenderProxyBox {
27062747
@override
27072748
void paint(PaintingContext context, Offset offset) {
27082749
if (_annotationIsActive) {
2750+
// Annotated region layers are not retained because they do not create engine layers.
27092751
final AnnotatedRegionLayer<MouseTrackerAnnotation> layer = AnnotatedRegionLayer<MouseTrackerAnnotation>(
27102752
_hoverAnnotation,
27112753
size: size,
@@ -2832,7 +2874,8 @@ class RenderRepaintBoundary extends RenderProxyBox {
28322874
/// * [dart:ui.Scene.toImage] for more information about the image returned.
28332875
Future<ui.Image> toImage({ double pixelRatio = 1.0 }) {
28342876
assert(!debugNeedsPaint);
2835-
return layer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
2877+
final OffsetLayer offsetLayer = layer;
2878+
return offsetLayer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
28362879
}
28372880

28382881

@@ -4657,7 +4700,16 @@ class RenderLeaderLayer extends RenderProxyBox {
46574700

46584701
@override
46594702
void paint(PaintingContext context, Offset offset) {
4660-
context.pushLayer(LeaderLayer(link: link, offset: offset), super.paint, Offset.zero);
4703+
if (layer == null) {
4704+
layer = LeaderLayer(link: link, offset: offset);
4705+
} else {
4706+
final LeaderLayer leaderLayer = layer;
4707+
leaderLayer
4708+
..link = link
4709+
..offset = offset;
4710+
}
4711+
context.pushLayer(layer, super.paint, Offset.zero);
4712+
assert(layer != null);
46614713
}
46624714

46634715
@override
@@ -4743,15 +4795,16 @@ class RenderFollowerLayer extends RenderProxyBox {
47434795

47444796
@override
47454797
void detach() {
4746-
_layer = null;
4798+
layer = null;
47474799
super.detach();
47484800
}
47494801

47504802
@override
47514803
bool get alwaysNeedsCompositing => true;
47524804

47534805
/// The layer we created when we were last painted.
4754-
FollowerLayer _layer;
4806+
@override
4807+
FollowerLayer get layer => super.layer;
47554808

47564809
/// Return the transform that was used in the last composition phase, if any.
47574810
///
@@ -4760,7 +4813,7 @@ class RenderFollowerLayer extends RenderProxyBox {
47604813
/// [FollowerLayer.getLastTransform]), this returns the identity matrix (see
47614814
/// [new Matrix4.identity].
47624815
Matrix4 getCurrentTransform() {
4763-
return _layer?.getLastTransform() ?? Matrix4.identity();
4816+
return layer?.getLastTransform() ?? Matrix4.identity();
47644817
}
47654818

47664819
@override
@@ -4786,14 +4839,22 @@ class RenderFollowerLayer extends RenderProxyBox {
47864839
@override
47874840
void paint(PaintingContext context, Offset offset) {
47884841
assert(showWhenUnlinked != null);
4789-
_layer = FollowerLayer(
4790-
link: link,
4791-
showWhenUnlinked: showWhenUnlinked,
4792-
linkedOffset: this.offset,
4793-
unlinkedOffset: offset,
4794-
);
4842+
if (layer == null) {
4843+
layer = FollowerLayer(
4844+
link: link,
4845+
showWhenUnlinked: showWhenUnlinked,
4846+
linkedOffset: this.offset,
4847+
unlinkedOffset: offset,
4848+
);
4849+
} else {
4850+
layer
4851+
..link = link
4852+
..showWhenUnlinked = showWhenUnlinked
4853+
..linkedOffset = this.offset
4854+
..unlinkedOffset = offset;
4855+
}
47954856
context.pushLayer(
4796-
_layer,
4857+
layer,
47974858
super.paint,
47984859
Offset.zero,
47994860
childPaintBounds: const Rect.fromLTRB(
@@ -4871,6 +4932,7 @@ class RenderAnnotatedRegion<T> extends RenderProxyBox {
48714932

48724933
@override
48734934
void paint(PaintingContext context, Offset offset) {
4935+
// Annotated region layers are not retained because they do not create engine layers.
48744936
final AnnotatedRegionLayer<T> layer = AnnotatedRegionLayer<T>(
48754937
value,
48764938
size: sized ? size : null,

0 commit comments

Comments
 (0)