Allow independent rendering pipelines

Fixes #2723
This commit is contained in:
Kris Giesing 2016-03-18 15:15:50 -07:00
parent e074af8099
commit bb5a82a726
18 changed files with 275 additions and 169 deletions

View file

@ -31,7 +31,7 @@ void main() {
for (int i = 0; i < _kNumberOfIterations || _kRunForever; ++i) {
renderView.configuration = (i % 2 == 0) ? big : small;
RenderObject.flushLayout();
WidgetFlutterBinding.instance.pipelineOwner.flushLayout();
}
watch.stop();

View file

@ -20,8 +20,8 @@ The last phase of a frame is the Semantics phase. This only occurs if
a semantics server has been installed, for example if the user is
using an accessibility tool.
Each frame, the semantics phase starts with a call to the static
`RenderObject.flushSemantics()` method from the `Renderer` binding's
Each frame, the semantics phase starts with a call to the
`PipelineOwner.flushSemantics()` method from the `Renderer` binding's
`beginFrame()` method.
Each node marked as needing semantics (which initially is just the

View file

@ -48,6 +48,10 @@ abstract class Renderer extends Object with Scheduler, Services
handleMetricsChanged(); // configures renderView's metrics
}
/// The render tree's owner, which maintains dirty state for layout,
/// composite, paint, and accessibility semantics
final PipelineOwner pipelineOwner = new PipelineOwner();
/// The render tree that's attached to the output surface.
RenderView get renderView => _renderView;
RenderView _renderView;
@ -58,7 +62,7 @@ abstract class Renderer extends Object with Scheduler, Services
if (_renderView != null)
_renderView.detach();
_renderView = value;
_renderView.attach();
_renderView.attach(pipelineOwner);
}
void handleMetricsChanged() {
@ -81,12 +85,12 @@ abstract class Renderer extends Object with Scheduler, Services
/// Pump the rendering pipeline to generate a frame.
void beginFrame() {
assert(renderView != null);
RenderObject.flushLayout();
RenderObject.flushCompositingBits();
RenderObject.flushPaint();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU
if (SemanticsNode.hasListeners) {
RenderObject.flushSemantics();
pipelineOwner.flushSemantics();
SemanticsNode.sendSemanticsTree();
}
}

View file

@ -337,8 +337,8 @@ class RenderBlockViewport extends RenderBlockBase {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_overlayPainter?.attach(this);
}

View file

@ -388,12 +388,7 @@ class BoxHitTestEntry extends HitTestEntry {
/// Parent data used by [RenderBox] and its subclasses.
class BoxParentData extends ParentData {
/// The offset at which to paint the child in the parent's coordinate system
Offset get offset => _offset;
Offset _offset = Offset.zero;
void set offset(Offset value) {
assert(RenderObject.debugDoingLayout);
_offset = value;
}
Offset offset = Offset.zero;
@override
String toString() => 'offset=$offset';
@ -556,9 +551,9 @@ abstract class RenderBox extends RenderObject {
assert(!_debugDoingBaseline);
assert(() {
final RenderObject parent = this.parent;
if (RenderObject.debugDoingLayout)
if (owner.debugDoingLayout)
return (RenderObject.debugActiveLayout == parent) && parent.debugDoingThisLayout;
if (RenderObject.debugDoingPaint)
if (owner.debugDoingPaint)
return ((RenderObject.debugActivePaint == parent) && parent.debugDoingThisPaint) ||
((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
assert(parent == this.parent);

View file

@ -171,8 +171,8 @@ class RenderChildView extends RenderBox {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_child?._attach();
}

View file

@ -51,7 +51,7 @@ class AbstractNode {
/// Call only from overrides of [redepthChildren]
void redepthChild(AbstractNode child) {
assert(child._attached == _attached);
assert(child.owner == owner);
if (child._depth <= _depth) {
child._depth = _depth + 1;
child.redepthChildren();
@ -62,16 +62,21 @@ class AbstractNode {
/// redepthChild(child) for each child. Do not call directly.
void redepthChildren() { }
bool _attached = false;
/// Whether this node is in a tree whose root is attached to something.
bool get attached => _attached;
Object _owner;
/// The owner for this node (null if unattached).
Object get owner => _owner;
/// Mark this node as attached.
/// Whether this node is in a tree whose root is attached to something.
bool get attached => _owner != null;
/// Mark this node as attached to the given owner.
///
/// Typically called only from the parent's attach(), and to mark the root of
/// a tree attached.
void attach() {
_attached = true;
void attach(Object owner) {
assert(owner != null);
assert(_owner == null);
_owner = owner;
}
/// Mark this node as detached.
@ -79,7 +84,8 @@ class AbstractNode {
/// Typically called only from the parent's detach(), and to mark the root of
/// a tree detached.
void detach() {
_attached = false;
assert(_owner != null);
_owner = null;
}
AbstractNode _parent;
@ -99,7 +105,7 @@ class AbstractNode {
});
child._parent = this;
if (attached)
child.attach();
child.attach(_owner);
redepthChild(child);
}

View file

@ -559,7 +559,8 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
assert(currentSemantics == null);
assert(parentSemantics == null);
owner._semantics ??= new SemanticsNode.root(
handler: owner is SemanticActionHandler ? owner as dynamic : null
handler: owner is SemanticActionHandler ? owner as dynamic : null,
owner: owner.owner
);
SemanticsNode node = owner._semantics;
assert(MatrixUtils.matrixEquals(node.transform, new Matrix4.identity()));
@ -671,6 +672,105 @@ class _ForkingSemanticsFragment extends _SemanticsFragment {
}
}
class PipelineOwner {
List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
bool _debugDoingLayout = false;
bool get debugDoingLayout => _debugDoingLayout;
/// Update the layout information for all dirty render objects.
///
/// This function is one of the core stages of the rendering pipeline. Layout
/// information is cleaned prior to painting so that render objects will
/// appear on screen in their up-to-date locations.
///
/// See [FlutterBinding] for an example of how this function is used.
void flushLayout() {
Timeline.startSync('Layout');
_debugDoingLayout = true;
try {
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themeselves
while (_nodesNeedingLayout.isNotEmpty) {
List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
} finally {
_debugDoingLayout = false;
Timeline.finishSync();
}
}
List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
/// Updates the [needsCompositing] bits.
///
/// Called as part of the rendering pipeline after [flushLayout] and before
/// [flushPaint].
void flushCompositingBits() {
Timeline.startSync('Compositing Bits');
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node.owner == this)
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();
Timeline.finishSync();
}
List<RenderObject> _nodesNeedingPaint = <RenderObject>[];
bool _debugDoingPaint = false;
bool get debugDoingPaint => _debugDoingPaint;
/// Update the display lists for all render objects.
///
/// This function is one of the core stages of the rendering pipeline.
/// Painting occurs after layout and before the scene is recomposited so that
/// scene is composited with up-to-date display lists for every render object.
///
/// See [FlutterBinding] for an example of how this function is used.
void flushPaint() {
Timeline.startSync('Paint');
_debugDoingPaint = true;
try {
List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._needsPaint);
if (node.owner == this)
PaintingContext.repaintCompositedChild(node);
};
assert(_nodesNeedingPaint.length == 0);
} finally {
_debugDoingPaint = false;
Timeline.finishSync();
}
}
bool _semanticsEnabled = false;
bool _debugDoingSemantics = false;
List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
void flushSemantics() {
Timeline.startSync('Semantics');
assert(_semanticsEnabled);
assert(() { _debugDoingSemantics = true; return true; });
try {
_nodesNeedingSemantics.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingSemantics) {
if (node._needsSemanticsUpdate && node.owner == this)
node._updateSemantics();
}
} finally {
_nodesNeedingSemantics.clear();
assert(() { _debugDoingSemantics = false; return true; });
Timeline.finishSync();
}
}
}
/// An object in the render tree.
///
/// The [RenderObject] class hierarchy is the core of the rendering
@ -811,8 +911,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
}
}
static bool _debugDoingLayout = false;
static bool get debugDoingLayout => _debugDoingLayout;
bool _debugDoingThisResize = false;
bool get debugDoingThisResize => _debugDoingThisResize;
bool _debugDoingThisLayout = false;
@ -835,7 +933,9 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
}
}
static List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
@override
PipelineOwner get owner => super.owner;
bool _needsLayout = true;
/// Whether this render object's layout information is dirty.
bool get needsLayout => _needsLayout;
@ -912,7 +1012,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
debugPrintStack();
return true;
});
_nodesNeedingLayout.add(this);
if (owner != null)
owner._nodesNeedingLayout.add(this);
Scheduler.instance.ensureVisualUpdate();
}
}
@ -936,41 +1037,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void scheduleInitialLayout() {
assert(attached);
assert(parent is! RenderObject);
assert(!_debugDoingLayout);
assert(!owner._debugDoingLayout);
assert(_relayoutSubtreeRoot == null);
_relayoutSubtreeRoot = this;
assert(() {
_debugCanParentUseSize = false;
return true;
});
_nodesNeedingLayout.add(this);
owner._nodesNeedingLayout.add(this);
}
/// Update the layout information for all dirty render objects.
///
/// This function is one of the core stages of the rendering pipeline. Layout
/// information is cleaned prior to painting so that render objects will
/// appear on screen in their up-to-date locations.
///
/// See [FlutterBinding] for an example of how this function is used.
static void flushLayout() {
Timeline.startSync('Layout');
_debugDoingLayout = true;
try {
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themeselves
while (_nodesNeedingLayout.isNotEmpty) {
List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.attached)
node._layoutWithoutResize();
}
}
} finally {
_debugDoingLayout = false;
Timeline.finishSync();
}
}
void _layoutWithoutResize() {
assert(_relayoutSubtreeRoot == this);
RenderObject debugPreviousActiveLayout;
@ -1180,16 +1256,11 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
// PAINTING
static bool _debugDoingPaint = false;
static bool get debugDoingPaint => _debugDoingPaint;
bool _debugDoingThisPaint = false;
bool get debugDoingThisPaint => _debugDoingThisPaint;
static RenderObject _debugActivePaint;
static RenderObject get debugActivePaint => _debugActivePaint;
static List<RenderObject> _nodesNeedingPaint = <RenderObject>[];
static List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
/// Whether this render object repaints separately from its parent.
///
/// Override this in subclasses to indicate that instances of your class ought
@ -1260,22 +1331,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
return true;
});
// parent is fine (or there isn't one), but we are dirty
_nodesNeedingCompositingBitsUpdate.add(this);
}
/// Updates the [needsCompositing] bits.
///
/// Called as part of the rendering pipeline after [flushLayout] and before
/// [flushPaint].
static void flushCompositingBits() {
Timeline.startSync('Compositing Bits');
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node.attached)
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();
Timeline.finishSync();
if (owner != null)
owner._nodesNeedingCompositingBitsUpdate.add(this);
}
bool _needsCompositing; // initialised in the constructor
@ -1319,7 +1376,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// This mechanism batches the painting work so that multiple sequential
/// writes are coalesced, removing redundant computation.
void markNeedsPaint() {
assert(!debugDoingPaint);
assert(owner == null || !owner.debugDoingPaint);
if (!attached)
return; // Don't try painting things that aren't in the hierarchy
if (_needsPaint)
@ -1334,7 +1391,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layer != null);
_nodesNeedingPaint.add(this);
if (owner != null)
owner._nodesNeedingPaint.add(this);
Scheduler.instance.ensureVisualUpdate();
} else if (parent is RenderObject) {
// We don't have our own layer; one of our ancestors will take
@ -1353,32 +1411,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
}
}
/// Update the display lists for all render objects.
///
/// This function is one of the core stages of the rendering pipeline.
/// Painting occurs after layout and before the scene is recomposited so that
/// scene is composited with up-to-date display lists for every render object.
///
/// See [FlutterBinding] for an example of how this function is used.
static void flushPaint() {
Timeline.startSync('Paint');
_debugDoingPaint = true;
try {
List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._needsPaint);
if (node.attached)
PaintingContext.repaintCompositedChild(node);
};
assert(_nodesNeedingPaint.length == 0);
} finally {
_debugDoingPaint = false;
Timeline.finishSync();
}
}
/// Bootstrap the rendering pipeline by scheduling the very first paint.
///
/// Requires that this render object is attached, is the root of the render
@ -1388,12 +1420,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void scheduleInitialPaint(ContainerLayer rootLayer) {
assert(attached);
assert(parent is! RenderObject);
assert(!_debugDoingPaint);
assert(!owner._debugDoingPaint);
assert(isRepaintBoundary);
assert(_layer == null);
_layer = rootLayer;
assert(_needsPaint);
_nodesNeedingPaint.add(this);
owner._nodesNeedingPaint.add(this);
}
void _paintWithContext(PaintingContext context, Offset offset) {
assert(!_debugDoingThisPaint);
@ -1477,10 +1509,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
// SEMANTICS
static bool _semanticsEnabled = false;
static bool _debugDoingSemantics = false;
static List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
/// Bootstrap the semantics reporting mechanism by marking this node
/// as needing a semantics update.
///
@ -1491,32 +1519,15 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void scheduleInitialSemantics() {
assert(attached);
assert(parent is! RenderObject);
assert(!_debugDoingSemantics);
assert(!owner._debugDoingSemantics);
assert(_semantics == null);
assert(_needsSemanticsUpdate);
assert(_semanticsEnabled == false);
_semanticsEnabled = true;
_nodesNeedingSemantics.add(this);
assert(owner._semanticsEnabled == false);
owner._semanticsEnabled = true;
owner._nodesNeedingSemantics.add(this);
Scheduler.instance.ensureVisualUpdate();
}
static void flushSemantics() {
Timeline.startSync('Semantics');
assert(_semanticsEnabled);
assert(() { _debugDoingSemantics = true; return true; });
try {
_nodesNeedingSemantics.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingSemantics) {
if (node._needsSemanticsUpdate)
node._updateSemantics();
}
} finally {
_nodesNeedingSemantics.clear();
assert(() { _debugDoingSemantics = false; return true; });
Timeline.finishSync();
}
}
/// Whether this RenderObject introduces a new box for accessibility purposes.
bool get hasSemantics => false;
@ -1561,8 +1572,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// 'noGeometry: true' when the geometry did change, the semantic
/// tree will be out of date.
void markNeedsSemanticsUpdate({ bool onlyChanges: false, bool noGeometry: false }) {
assert(!_debugDoingSemantics);
if (!_semanticsEnabled || !attached || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry)))
assert(!attached || !owner._debugDoingSemantics);
if (!attached || !owner._semanticsEnabled || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry)))
return;
if (!noGeometry && (_semantics == null || (_semantics.hasChildren && _semantics.wasAffectedByClip))) {
// Since the geometry might have changed, we need to make sure to reapply any clips.
@ -1582,7 +1593,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
}
if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true;
_nodesNeedingSemantics.add(node);
owner._nodesNeedingSemantics.add(node);
}
} else {
// The shape of the semantics tree around us may have changed.
@ -1601,7 +1612,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
node._semantics?.reset();
if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true;
_nodesNeedingSemantics.add(node);
owner._nodesNeedingSemantics.add(node);
}
}
}
@ -1814,10 +1825,10 @@ abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implem
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
if (_child != null)
_child.attach();
_child.attach(owner);
}
@override
@ -2034,11 +2045,11 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
ChildType child = _firstChild;
while (child != null) {
child.attach();
child.attach(owner);
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}

View file

@ -1035,8 +1035,8 @@ class RenderDecoratedBox extends RenderProxyBox {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_addListenerIfNeeded();
}

View file

@ -58,10 +58,11 @@ class SemanticsNode extends AbstractNode {
_actionHandler = handler;
SemanticsNode.root({
SemanticActionHandler handler
SemanticActionHandler handler,
Object owner
}) : _id = 0,
_actionHandler = handler {
attach();
attach(owner);
}
static int _lastIdentifier = 0;
@ -265,8 +266,8 @@ class SemanticsNode extends AbstractNode {
static Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>();
@override
void attach() {
super.attach();
void attach(Object owner) {
super.attach(owner);
assert(!_nodes.containsKey(_id));
_nodes[_id] = this;
_detachedNodes.remove(this);
@ -274,7 +275,7 @@ class SemanticsNode extends AbstractNode {
_inheritedMergeAllDescendantsIntoThisNode = parent._shouldMergeAllDescendantsIntoThisNode;
if (_children != null) {
for (SemanticsNode child in _children)
child.attach();
child.attach(owner);
}
}

View file

@ -33,7 +33,7 @@ class ViewConfiguration {
/// The root of the render tree.
///
/// The view represents the total output surface of the render tree and handles
/// bootstraping the rendering pipeline. The view has a unique child
/// bootstrapping the rendering pipeline. The view has a unique child
/// [RenderBox], which is required to fill the entire output surface.
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
RenderView({

View file

@ -168,8 +168,8 @@ class RenderViewportBase extends RenderBox implements HasMainAxis {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_overlayPainter?.attach(this);
}

View file

@ -335,7 +335,11 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
/// the gesture detector should be enabled.
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
assert(() {
if (!RenderObject.debugDoingLayout) {
// TODO kgiesing This assert will trigger if the owner of the current
// tree is different from the owner assigned to the renderer instance.
// Once elements have a notion of owners this assertion can be written
// more clearly.
if (!Renderer.instance.pipelineOwner.debugDoingLayout) {
throw new FlutterError(
'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
'The replaceGestureRecognizers() method can only be called during the layout phase. '

View file

@ -0,0 +1,84 @@
// Copyright 2016 The Chromium 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';
import 'rendering_tester.dart';
class TestLayout {
TestLayout() {
// viewport incoming constraints are tight 800x600
// viewport is vertical by default
root = new RenderViewport(
child: new RenderCustomPaint(
painter: new TestCallbackPainter(
onPaint: () { painted = true; }
),
child: child = new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tightFor(height: 10.0, width: 10.0)
)
)
);
}
RenderBox root;
RenderBox child;
bool painted = false;
}
void main() {
test('onscreen layout does not affect offscreen', () {
TestLayout onscreen = new TestLayout();
TestLayout offscreen = new TestLayout();
expect(onscreen.child.hasSize, isFalse);
expect(onscreen.painted, isFalse);
expect(offscreen.child.hasSize, isFalse);
expect(offscreen.painted, isFalse);
// Attach the offscreen to a custom render view and owner
RenderView renderView = new TestRenderView();
PipelineOwner pipelineOwner = new PipelineOwner();
renderView.attach(pipelineOwner);
renderView.child = offscreen.root;
renderView.scheduleInitialFrame();
// Lay out the onscreen in the default binding
layout(onscreen.root, phase: EnginePhase.layout);
expect(onscreen.child.hasSize, isTrue);
expect(onscreen.painted, isFalse);
expect(onscreen.child.size, equals(const Size(800.0, 10.0)));
// Make sure the offscreen didn't get laid out
expect(offscreen.child.hasSize, isFalse);
expect(offscreen.painted, isFalse);
// Now lay out the offscreen
pipelineOwner.flushLayout();
expect(offscreen.child.hasSize, isTrue);
expect(offscreen.painted, isFalse);
});
test('offscreen layout does not affect onscreen', () {
TestLayout onscreen = new TestLayout();
TestLayout offscreen = new TestLayout();
expect(onscreen.child.hasSize, isFalse);
expect(onscreen.painted, isFalse);
expect(offscreen.child.hasSize, isFalse);
expect(offscreen.painted, isFalse);
// Attach the offscreen to a custom render view and owner
RenderView renderView = new TestRenderView();
PipelineOwner pipelineOwner = new PipelineOwner();
renderView.attach(pipelineOwner);
renderView.child = offscreen.root;
renderView.scheduleInitialFrame();
// Lay out the offscreen
pipelineOwner.flushLayout();
expect(offscreen.child.hasSize, isTrue);
expect(offscreen.painted, isFalse);
// Make sure the onscreen didn't get laid out
expect(onscreen.child.hasSize, isFalse);
expect(onscreen.painted, isFalse);
// Now lay out the onscreen in the default binding
layout(onscreen.root, phase: EnginePhase.layout);
expect(onscreen.child.hasSize, isTrue);
expect(onscreen.painted, isFalse);
expect(onscreen.child.size, equals(const Size(800.0, 10.0)));
});
}

View file

@ -41,16 +41,16 @@ class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services,
@override
void beginFrame() {
RenderObject.flushLayout();
pipelineOwner.flushLayout();
if (phase == EnginePhase.layout)
return;
RenderObject.flushCompositingBits();
pipelineOwner.flushCompositingBits();
if (phase == EnginePhase.compositingBits)
return;
RenderObject.flushPaint();
pipelineOwner.flushPaint();
if (phase == EnginePhase.paint)
return;
renderer.renderView.compositeFrame();
renderView.compositeFrame();
}
}

View file

@ -58,8 +58,8 @@ class SpriteBox extends RenderBox {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_scheduleTick();
}

View file

@ -46,20 +46,20 @@ class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
// Cloned from Renderer.beginFrame() but with early-exit semantics.
void _beginFrame() {
assert(renderView != null);
RenderObject.flushLayout();
pipelineOwner.flushLayout();
if (phase == EnginePhase.layout)
return;
RenderObject.flushCompositingBits();
pipelineOwner.flushCompositingBits();
if (phase == EnginePhase.compositingBits)
return;
RenderObject.flushPaint();
pipelineOwner.flushPaint();
if (phase == EnginePhase.paint)
return;
renderView.compositeFrame(); // this sends the bits to the GPU
if (phase == EnginePhase.composite)
return;
if (SemanticsNode.hasListeners) {
RenderObject.flushSemantics();
pipelineOwner.flushSemantics();
if (phase == EnginePhase.flushSemantics)
return;
SemanticsNode.sendSemanticsTree();

View file

@ -347,6 +347,7 @@ class AnalyzeCommand extends FlutterCommand {
new RegExp('^\\[(hint|error)\\] Unused import \\(${mainFile.path},'),
new RegExp(r'^\[.+\] .+ \(.+/\.pub-cache/.+'),
new RegExp('\\[warning\\] Missing concrete implementation of \'RenderObject\\.applyPaintTransform\''), // https://github.com/dart-lang/sdk/issues/25232
new RegExp('\\[warning\\] Missing concrete implementation of \'AbstractNode\\.attach\''), // https://github.com/dart-lang/sdk/issues/25232
new RegExp(r'[0-9]+ (error|warning|hint|lint).+found\.'),
new RegExp(r'^$'),
];