Unverified Commit 025397ae authored by Dan Field's avatar Dan Field Committed by GitHub

Release retained resources from layers/dispose pictures (#84740)

parent 1977ee75
......@@ -524,12 +524,18 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
thumbCenterY + CupertinoThumbPainter.radius,
);
_clipRRectLayer = context.pushClipRRect(needsCompositing, Offset.zero, thumbBounds, trackRRect, (PaintingContext innerContext, Offset offset) {
_clipRRectLayer.layer = context.pushClipRRect(needsCompositing, Offset.zero, thumbBounds, trackRRect, (PaintingContext innerContext, Offset offset) {
const CupertinoThumbPainter.switchThumb().paint(innerContext.canvas, thumbBounds);
}, oldLayer: _clipRRectLayer);
}, oldLayer: _clipRRectLayer.layer);
}
ClipRRectLayer? _clipRRectLayer;
final LayerHandle<ClipRRectLayer> _clipRRectLayer = LayerHandle<ClipRRectLayer>();
@override
void dispose() {
_clipRRectLayer.layer = null;
super.dispose();
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
......
......@@ -289,19 +289,25 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox {
}
final BoxParentData childParentData = child!.parentData! as BoxParentData;
_clipPathLayer = context.pushClipPath(
_clipPathLayer.layer = context.pushClipPath(
needsCompositing,
offset + childParentData.offset,
Offset.zero & child!.size,
_clipPath(),
(PaintingContext innerContext, Offset innerOffset) => innerContext.paintChild(child!, innerOffset),
oldLayer: _clipPathLayer,
oldLayer: _clipPathLayer.layer,
);
}
ClipPathLayer? _clipPathLayer;
final LayerHandle<ClipPathLayer> _clipPathLayer = LayerHandle<ClipPathLayer>();
Paint? _debugPaint;
@override
void dispose() {
_clipPathLayer.layer = null;
super.dispose();
}
@override
void debugPaintSize(PaintingContext context, Offset offset) {
assert(() {
......
......@@ -1502,15 +1502,15 @@ class _RenderDecoration extends RenderBox {
_labelTransform = Matrix4.identity()
..translate(dx, labelOffset.dy + dy)
..scale(scale);
_transformLayer = context.pushTransform(
layer = context.pushTransform(
needsCompositing,
offset,
_labelTransform!,
_paintLabel,
oldLayer: _transformLayer,
oldLayer: layer as TransformLayer?,
);
} else {
_transformLayer = null;
layer = null;
}
doPaint(icon);
......@@ -1524,8 +1524,6 @@ class _RenderDecoration extends RenderBox {
doPaint(counter);
}
TransformLayer? _transformLayer;
@override
bool hitTestSelf(Offset position) => true;
......
......@@ -327,19 +327,25 @@ class RenderAnimatedSize extends RenderAligningShiftedBox {
void paint(PaintingContext context, Offset offset) {
if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) {
final Rect rect = Offset.zero & size;
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
rect,
super.paint,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer = null;
_clipRectLayer.layer = null;
super.paint(context, offset);
}
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
}
......@@ -301,6 +301,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
_foregroundRenderObject = null;
_backgroundRenderObject?.dispose();
_backgroundRenderObject = null;
_clipRectLayer.layer = null;
_cachedBuiltInForegroundPainters?.dispose();
_cachedBuiltInPainters?.dispose();
super.dispose();
......@@ -4007,22 +4008,22 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
void paint(PaintingContext context, Offset offset) {
_computeTextMetricsIfNeeded();
if (_hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
_paintContents,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer = null;
_clipRectLayer.layer = null;
_paintContents(context, offset);
}
_paintHandleLayers(context, getEndpointsForSelection(selection!));
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
......
......@@ -1084,17 +1084,17 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
return;
if (clipBehavior == Clip.none) {
_clipRectLayer = null;
_clipRectLayer.layer = null;
defaultPaint(context, offset);
} else {
// We have overflow and the clipBehavior isn't none. Clip it.
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
defaultPaint,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
}
......@@ -1141,7 +1141,13 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}());
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override
Rect? describeApproximatePaintClip(RenderObject child) => _hasOverflow ? Offset.zero & size : null;
......
......@@ -389,21 +389,27 @@ class RenderFlow extends RenderBox
@override
void paint(PaintingContext context, Offset offset) {
if (clipBehavior == Clip.none) {
_clipRectLayer = null;
_clipRectLayer.layer = null;
_paintWithDelegate(context, offset);
} else {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
_paintWithDelegate,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
}
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
......
......@@ -785,22 +785,28 @@ class RenderListWheelViewport
void paint(PaintingContext context, Offset offset) {
if (childCount > 0) {
if (_shouldClipAtCurrentOffset() && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
_paintVisibleChildren,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer = null;
_clipRectLayer.layer = null;
_paintVisibleChildren(context, offset);
}
}
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
/// Paints all children visible in the current viewport.
void _paintVisibleChildren(PaintingContext context, Offset offset) {
......
......@@ -117,30 +117,32 @@ class PaintingContext extends ClipContext {
);
return true;
}());
OffsetLayer? childLayer = child._layer as OffsetLayer?;
OffsetLayer? childLayer = child._layerHandle.layer as OffsetLayer?;
if (childLayer == null) {
assert(debugAlsoPaintedParent);
assert(child._layerHandle.layer == null);
// Not using the `layer` setter because the setter asserts that we not
// replace the layer for repaint boundaries. That assertion does not
// apply here because this is exactly the place designed to create a
// layer for repaint boundaries.
child._layer = childLayer = OffsetLayer();
final OffsetLayer layer = OffsetLayer();
child._layerHandle.layer = childLayer = layer;
} else {
assert(debugAlsoPaintedParent || childLayer.attached);
childLayer.removeAllChildren();
}
assert(identical(childLayer, child._layer));
assert(child._layer is OffsetLayer);
assert(identical(childLayer, child._layerHandle.layer));
assert(child._layerHandle.layer is OffsetLayer);
assert(() {
child._layer!.debugCreator = child.debugCreator ?? child.runtimeType;
childLayer!.debugCreator = child.debugCreator ?? child.runtimeType;
return true;
}());
childContext ??= PaintingContext(child._layer!, child.paintBounds);
childContext ??= PaintingContext(childLayer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero);
// Double-check that the paint method did not replace the layer (the first
// check is done in the [layer] setter itself).
assert(identical(childLayer, child._layer));
assert(identical(childLayer, child._layerHandle.layer));
childContext.stopRecordingIfNeeded();
}
......@@ -209,14 +211,14 @@ class PaintingContext extends ClipContext {
includedParent: true,
includedChild: false,
);
child._layer!.debugCreator = child.debugCreator ?? child;
child._layerHandle.layer!.debugCreator = child.debugCreator ?? child;
return true;
}());
}
assert(child._layer is OffsetLayer);
final OffsetLayer childOffsetLayer = child._layer! as OffsetLayer;
assert(child._layerHandle.layer is OffsetLayer);
final OffsetLayer childOffsetLayer = child._layerHandle.layer! as OffsetLayer;
childOffsetLayer.offset = offset;
appendLayer(child._layer!);
appendLayer(childOffsetLayer);
}
/// Adds a layer to the recording requiring that the recording is already
......@@ -266,6 +268,7 @@ class PaintingContext extends ClipContext {
Canvas get canvas {
if (_canvas == null)
_startRecording();
assert(_currentLayer != null);
return _canvas!;
}
......@@ -391,6 +394,7 @@ class PaintingContext extends ClipContext {
stopRecordingIfNeeded();
appendLayer(childLayer);
final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
painter(childContext, offset);
childContext.stopRecordingIfNeeded();
}
......@@ -969,9 +973,9 @@ class PipelineOwner {
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layer != null);
assert(node._layerHandle.layer != null);
if (node._needsPaint && node.owner == this) {
if (node._layer!.attached) {
if (node._layerHandle.layer!.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
......@@ -1274,7 +1278,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
@mustCallSuper
void dispose() {
assert(!_debugDisposed);
_layer = null;
_layerHandle.layer = null;
assert(() {
// TODO(dnfield): Enable this assert once clients have had a chance to
// migrate.
......@@ -1478,7 +1482,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
_needsCompositingBitsUpdate = false;
markNeedsCompositingBitsUpdate();
}
if (_needsPaint && _layer != null) {
if (_needsPaint && _layerHandle.layer != null) {
// Don't enter this block if we've never painted at all;
// scheduleInitialPaint() will handle it
_needsPaint = false;
......@@ -2050,14 +2054,22 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
/// never populating this field, or by setting it to null when the value of
/// [needsCompositing] changes from true to false.
///
/// If a new layer is created and stored in some other field on the render
/// object, the render object must use a [LayerHandle] to store it. A layer
/// handle will prevent the layer from being disposed before the render
/// object is finished with it, and it will also make sure that the layer
/// gets appropriately disposed when the render object creates a replacement
/// or nulls it out. The render object must null out the [LayerHandle.layer]
/// in its [dispose] method.
///
/// If this render object is a repaint boundary, the framework automatically
/// creates an [OffsetLayer] and populates this field prior to calling the
/// [paint] method. The [paint] method must not replace the value of this
/// field.
@protected
ContainerLayer? get layer {
assert(!isRepaintBoundary || (_layer == null || _layer is OffsetLayer));
return _layer;
assert(!isRepaintBoundary || _layerHandle.layer == null || _layerHandle.layer is OffsetLayer);
return _layerHandle.layer;
}
@protected
......@@ -2068,9 +2080,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
'The framework creates and assigns an OffsetLayer to a repaint '
'boundary automatically.',
);
_layer = newLayer;
_layerHandle.layer = newLayer;
}
ContainerLayer? _layer;
final LayerHandle<ContainerLayer> _layerHandle = LayerHandle<ContainerLayer>();
/// In debug mode, the compositing layer that this render object uses to repaint.
///
......@@ -2082,7 +2095,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
ContainerLayer? get debugLayer {
ContainerLayer? result;
assert(() {
result = _layer;
result = _layerHandle.layer;
return true;
}());
return result;
......@@ -2218,7 +2231,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
}());
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layer is OffsetLayer);
assert(_layerHandle.layer is OffsetLayer);
if (owner != null) {
owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate();
......@@ -2251,14 +2264,14 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
assert(attached);
assert(isRepaintBoundary);
assert(_needsPaint);
assert(_layer != null);
assert(!_layer!.attached);
assert(_layerHandle.layer != null);
assert(!_layerHandle.layer!.attached);
AbstractNode? node = parent;
while (node is RenderObject) {
if (node.isRepaintBoundary) {
if (node._layer == null)
if (node._layerHandle.layer == null)
break; // looks like the subtree here has never been painted. let it handle itself.
if (node._layer!.attached)
if (node._layerHandle.layer!.attached)
break; // it's the one that detached us, so it's the one that will decide to repaint us.
node._needsPaint = true;
}
......@@ -2278,8 +2291,8 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
assert(parent is! RenderObject);
assert(!owner!._debugDoingPaint);
assert(isRepaintBoundary);
assert(_layer == null);
_layer = rootLayer;
assert(_layerHandle.layer == null);
_layerHandle.layer = rootLayer;
assert(_needsPaint);
owner!._nodesNeedingPaint.add(this);
}
......@@ -2296,9 +2309,9 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
assert(parent is! RenderObject);
assert(!owner!._debugDoingPaint);
assert(isRepaintBoundary);
assert(_layer != null); // use scheduleInitialPaint the first time
_layer!.detach();
_layer = rootLayer;
assert(_layerHandle.layer != null); // use scheduleInitialPaint the first time
_layerHandle.layer!.detach();
_layerHandle.layer = rootLayer;
markNeedsPaint();
}
......@@ -2388,7 +2401,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
_debugDoingThisPaint = true;
debugLastActivePaint = _debugActivePaint;
_debugActivePaint = this;
assert(!isRepaintBoundary || _layer != null);
assert(!isRepaintBoundary || _layerHandle.layer != null);
return true;
}());
_needsPaint = false;
......@@ -2983,7 +2996,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
properties.add(DiagnosticsProperty<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? 'can use size' : null, missingIfNull: true));
properties.add(DiagnosticsProperty<Constraints>('constraints', _constraints, missingIfNull: true));
// don't access it via the "layer" getter since that's only valid when we don't need paint
properties.add(DiagnosticsProperty<ContainerLayer>('layer', _layer, defaultValue: null));
properties.add(DiagnosticsProperty<ContainerLayer>('layer', _layerHandle.layer, defaultValue: null));
properties.add(DiagnosticsProperty<SemanticsNode>('semantics node', _semantics, defaultValue: null));
properties.add(FlagProperty(
'isBlockingSemanticsOfPreviouslyPaintedNodes',
......
......@@ -209,21 +209,27 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
// Clip the texture if it's going to paint out of the bounds of the renter box
// (see comment in _paintTexture for an explanation of when this happens).
if ((size.width < _currentAndroidViewSize.width || size.height < _currentAndroidViewSize.height) && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
true,
offset,
offset & size,
_paintTexture,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
return;
}
_clipRectLayer = null;
_clipRectLayer.layer = null;
_paintTexture(context, offset);
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
void _paintTexture(PaintingContext context, Offset offset) {
// As resizing the Android view happens asynchronously we don't know exactly when is a
......
......@@ -117,19 +117,25 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderB
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
_transformLayer = context.pushTransform(
_transformLayer.layer = context.pushTransform(
needsCompositing,
offset,
_paintTransform!,
_paintChild,
oldLayer: _transformLayer,
oldLayer: _transformLayer.layer,
);
} else {
_transformLayer = null;
_transformLayer.layer = null;
}
}
TransformLayer? _transformLayer;
final LayerHandle<TransformLayer> _transformLayer = LayerHandle<TransformLayer>();
@override
void dispose() {
_transformLayer.layer = null;
super.dispose();
}
@override
void applyPaintTransform(RenderBox child, Matrix4 transform) {
......
......@@ -801,17 +801,17 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO
}
if (clipBehavior == Clip.none) {
_clipRectLayer = null;
_clipRectLayer.layer = null;
super.paint(context, offset);
} else {
// We have overflow and the clipBehavior isn't none. Clip it.
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
super.paint,
clipBehavior: clipBehavior,
oldLayer:_clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
}
......@@ -822,7 +822,13 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO
}());
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override
Rect? describeApproximatePaintClip(RenderObject child) {
......
......@@ -632,21 +632,27 @@ class RenderStack extends RenderBox
@override
void paint(PaintingContext context, Offset offset) {
if (clipBehavior != Clip.none && _hasVisualOverflow) {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
paintStack,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer = null;
_clipRectLayer.layer = null;
paintStack(context, offset);
}
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override
Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
......
......@@ -632,21 +632,27 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
if (firstChild == null)
return;
if (hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
_paintContents,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer = null;
_clipRectLayer.layer = null;
_paintContents(context, offset);
}
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
void _paintContents(PaintingContext context, Offset offset) {
for (final RenderSliver child in childrenInPaintOrder) {
......
......@@ -761,21 +761,27 @@ class RenderWrap extends RenderBox
// TODO(ianh): move the debug flex overflow paint logic somewhere common so
// it can be reused here
if (_hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
defaultPaint,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer = null;
_clipRectLayer.layer = null;
defaultPaint(context, offset);
}
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
......
......@@ -787,21 +787,27 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox
@override
void paint(PaintingContext context, Offset offset) {
if (_hasVisualOverflow && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
paintStack,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer = null;
_clipRectLayer.layer = null;
paintStack(context, offset);
}
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
......
......@@ -620,22 +620,28 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
}
if (_shouldClipAtPaintOffset(paintOffset) && clipBehavior != Clip.none) {
_clipRectLayer = context.pushClipRect(
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
paintContents,
clipBehavior: clipBehavior,
oldLayer: _clipRectLayer,
oldLayer: _clipRectLayer.layer,
);
} else {
_clipRectLayer = null;
_clipRectLayer.layer = null;
paintContents(context, offset);
}
}
}
ClipRectLayer? _clipRectLayer;
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
@override
void dispose() {
_clipRectLayer.layer = null;
super.dispose();
}
@override
void applyPaintTransform(RenderBox child, Matrix4 transform) {
......
......@@ -262,6 +262,7 @@ void main() {
r' owner: RenderView#[0-9a-f]{5}\n'
r' creator: RenderView\n'
r' engine layer: (TransformEngineLayer|PersistedTransform)#[0-9a-f]{5}\n'
r' handles: 1\n'
r' offset: Offset\(0\.0, 0\.0\)\n'
r' transform:\n'
r' \[0] 3\.0,0\.0,0\.0,0\.0\n'
......
......@@ -648,7 +648,7 @@ void main() {
child: box200x200,
);
layout(defaultBox, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
defaultBox.paint(context, Offset.zero);
context.paintChild(defaultBox, Offset.zero);
expect(context.clipBehavior, equals(Clip.none));
for (final Clip clip in Clip.values) {
......@@ -659,7 +659,7 @@ void main() {
clipBehavior: clip,
);
layout(box, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
box.paint(context, Offset.zero);
context.paintChild(box, Offset.zero);
expect(context.clipBehavior, equals(clip));
}
});
......
......@@ -163,9 +163,10 @@ void main() {
);
layout(root);
dynamic error;
final PaintingContext context = PaintingContext(ContainerLayer(), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
try {
s.debugPaint(
PaintingContext(ContainerLayer(), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)),
context,
const Offset(0.0, 500),
);
} catch (e) {
......@@ -195,9 +196,10 @@ void main() {
);
layout(root);
dynamic error;
final PaintingContext context = PaintingContext(ContainerLayer(), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0));
try {
s.debugPaint(
PaintingContext(ContainerLayer(), const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)),
context,
const Offset(0.0, 500),
);
} catch (e) {
......
......@@ -47,7 +47,7 @@ void main() {
selection: const TextSelection(baseOffset: 0, extentOffset: 0),
);
layout(defaultEditable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
defaultEditable.paint(context, Offset.zero);
context.paintChild(defaultEditable, Offset.zero);
expect(context.clipBehavior, equals(Clip.hardEdge));
context.clipBehavior = Clip.none; // Reset as Clip.none won't write into clipBehavior.
......@@ -63,7 +63,7 @@ void main() {
clipBehavior: clip,
);
layout(editable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
editable.paint(context, Offset.zero);
context.paintChild(editable, Offset.zero);
expect(context.clipBehavior, equals(clip));
}
});
......
......@@ -62,13 +62,13 @@ void main() {
// By default, clipBehavior should be Clip.none
final RenderFlex defaultFlex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200]);
layout(defaultFlex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
defaultFlex.paint(context, Offset.zero);
context.paintChild(defaultFlex, Offset.zero);
expect(context.clipBehavior, equals(Clip.none));
for (final Clip clip in Clip.values) {
final RenderFlex flex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200], clipBehavior: clip);
layout(flex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
flex.paint(context, Offset.zero);
context.paintChild(flex, Offset.zero);
expect(context.clipBehavior, equals(clip));
}
});
......
......@@ -256,7 +256,7 @@ void main() {
);
});
test('PictureLayer prints picture, engine layer, and raster cache hints in debug info', () {
test('PictureLayer prints picture, raster cache hints in debug info', () {
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.drawPaint(Paint());
......@@ -267,10 +267,20 @@ void main() {
layer.willChangeHint = false;
final List<String> info = _getDebugInfo(layer);
expect(info, contains('picture: ${describeIdentity(picture)}'));
expect(info, contains('engine layer: ${describeIdentity(null)}'));
expect(info, isNot(contains('engine layer: ${describeIdentity(null)}')));
expect(info, contains('raster cache hints: isComplex = true, willChange = false'));
});
test('Layer prints engineLayer if it is not null in debug info', () {
final ConcreteLayer layer = ConcreteLayer();
List<String> info = _getDebugInfo(layer);
expect(info, isNot(contains('engine layer: ${describeIdentity(null)}')));
layer.engineLayer = FakeEngineLayer();
info = _getDebugInfo(layer);
expect(info, contains('engine layer: ${describeIdentity(layer.engineLayer)}'));
});
test('mutating PictureLayer fields triggers needsAddToScene', () {
final PictureLayer pictureLayer = PictureLayer(Rect.zero);
checkNeedsAddToScene(pictureLayer, () {
......@@ -591,6 +601,135 @@ void main() {
// layer.
parent.buildScene(SceneBuilder());
}, skip: isBrowser); // TODO(yjbanov): `toImage` doesn't work on the Web: https://github.com/flutter/flutter/issues/42767
// TODO(dnfield): remove this when https://github.com/flutter/flutter/issues/85066 is resolved.
const bool bug85066 = true;
test('PictureLayer does not let you call dispose unless refcount is 0', () {
PictureLayer layer = PictureLayer(Rect.zero);
expect(layer.debugHandleCount, 0);
layer.dispose();
expect(layer.debugDisposed, true, skip: bug85066);
layer = PictureLayer(Rect.zero);
final LayerHandle<PictureLayer> handle = LayerHandle<PictureLayer>(layer);
expect(layer.debugHandleCount, 1);
expect(() => layer.dispose(), throwsAssertionError);
handle.layer = null;
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, true, skip: bug85066);
expect(() => layer.dispose(), throwsAssertionError, skip: bug85066); // already disposed.
});
test('Layer append/remove increases/decreases handle count', () {
final PictureLayer layer = PictureLayer(Rect.zero);
final ContainerLayer parent = ContainerLayer();
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, false);
parent.append(layer);
expect(layer.debugHandleCount, 1);
expect(layer.debugDisposed, false);
layer.remove();
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, true, skip: bug85066);
});
test('Layer.dispose disposes the engineLayer', () {
final Layer layer = ConcreteLayer();
final FakeEngineLayer engineLayer = FakeEngineLayer();
layer.engineLayer = engineLayer;
expect(engineLayer.disposed, false);
layer.dispose();
expect(engineLayer.disposed, true);
expect(layer.engineLayer, null);
});
test('Layer.engineLayer (set) disposes the engineLayer', () {
final Layer layer = ConcreteLayer();
final FakeEngineLayer engineLayer = FakeEngineLayer();
layer.engineLayer = engineLayer;
expect(engineLayer.disposed, false);
layer.engineLayer = null;
expect(engineLayer.disposed, true);
});
test('PictureLayer.picture (set) disposes the picture', () {
final PictureLayer layer = PictureLayer(Rect.zero);
final FakePicture picture = FakePicture();
layer.picture = picture;
expect(picture.disposed, false);
layer.picture = null;
expect(picture.disposed, true);
});
test('PictureLayer disposes the picture', () {
final PictureLayer layer = PictureLayer(Rect.zero);
final FakePicture picture = FakePicture();
layer.picture = picture;
expect(picture.disposed, false);
layer.dispose();
expect(picture.disposed, true);
});
test('LayerHandle disposes the layer', () {
final ConcreteLayer layer = ConcreteLayer();
final ConcreteLayer layer2 = ConcreteLayer();
expect(layer.debugHandleCount, 0);
expect(layer2.debugHandleCount, 0);
final LayerHandle<ConcreteLayer> holder = LayerHandle<ConcreteLayer>(layer);
expect(layer.debugHandleCount, 1);
expect(layer.debugDisposed, false);
expect(layer2.debugHandleCount, 0);
expect(layer2.debugDisposed, false);
holder.layer = layer;
expect(layer.debugHandleCount, 1);
expect(layer.debugDisposed, false);
expect(layer2.debugHandleCount, 0);
expect(layer2.debugDisposed, false);
holder.layer = layer2;
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, true, skip: bug85066);
expect(layer2.debugHandleCount, 1);
expect(layer2.debugDisposed, false);
holder.layer = null;
expect(layer.debugHandleCount, 0);
expect(layer.debugDisposed, true, skip: bug85066);
expect(layer2.debugHandleCount, 0);
expect(layer2.debugDisposed, true, skip: bug85066);
expect(() => holder.layer = layer, throwsAssertionError, skip: bug85066);
});
}
class FakeEngineLayer extends Fake implements EngineLayer {
bool disposed = false;
@override
void dispose() {
assert(!disposed);
disposed = true;
}
}
class FakePicture extends Fake implements Picture {
bool disposed = false;
@override
void dispose() {
assert(!disposed);
disposed = true;
}
}
class ConcreteLayer extends Layer {
@override
void addToScene(SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {}
}
class _TestAlwaysNeedsAddToSceneLayer extends ContainerLayer {
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
// TODO(dnfield): remove when https://github.com/flutter/flutter/issues/85066 is resolved.
const bool bug85066 = true;
testWidgets('Tracks picture layers accurately when painting is interleaved with a pushLayer', (WidgetTester tester) async {
// Creates a RenderObject that will paint into multiple picture layers.
// Asserts that both layers get a handle, and that all layers get correctly
// released.
final GlobalKey key = GlobalKey();
await tester.pumpWidget(RepaintBoundary(
child: CustomPaint(
key: key,
painter: SimplePainter(),
child: const RepaintBoundary(child: Placeholder()),
foregroundPainter: SimplePainter(),
),
));
final List<Layer> layers = tester.binding.renderView.debugLayer!.depthFirstIterateChildren();
final RenderObject renderObject = key.currentContext!.findRenderObject()!;
for (final Layer layer in layers) {
expect(layer.debugDisposed, false);
}
await tester.pumpWidget(const SizedBox());
for (final Layer layer in layers) {
expect(layer.debugDisposed, true, skip: bug85066);
}
expect(renderObject.debugDisposed, true);
});
}
class SimplePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
canvas.drawPaint(Paint());
}
@override
bool shouldRepaint(SimplePainter oldDelegate) => true;
}
......@@ -305,7 +305,6 @@ class TestClipPaintingContext extends PaintingContext {
ClipRectLayer? oldLayer,
}) {
this.clipBehavior = clipBehavior;
return null;
}
Clip clipBehavior = Clip.none;
......
......@@ -83,7 +83,7 @@ void main() {
parentData.left = parentData.right = 0;
}
layout(stack, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
stack.paint(context, Offset.zero);
context.paintChild(stack, Offset.zero);
expect(context.clipBehavior, equals(clip));
}
});
......
......@@ -209,13 +209,13 @@ void main() {
// By default, clipBehavior should be Clip.none
final RenderWrap defaultWrap = RenderWrap(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200]);
layout(defaultWrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
defaultWrap.paint(context, Offset.zero);
context.paintChild(defaultWrap, Offset.zero);
expect(context.clipBehavior, equals(Clip.none));
for (final Clip clip in Clip.values) {
final RenderWrap wrap = RenderWrap(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200], clipBehavior: clip);
layout(wrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
wrap.paint(context, Offset.zero);
context.paintChild(wrap, Offset.zero);
expect(context.clipBehavior, equals(clip));
}
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment