Commit cef24293 authored by krisgiesing's avatar krisgiesing

Merge pull request #2839 from krisgiesing/offscreen_layout

Allow independent rendering pipelines
parents 1e257925 bb5a82a7
...@@ -31,7 +31,7 @@ void main() { ...@@ -31,7 +31,7 @@ void main() {
for (int i = 0; i < _kNumberOfIterations || _kRunForever; ++i) { for (int i = 0; i < _kNumberOfIterations || _kRunForever; ++i) {
renderView.configuration = (i % 2 == 0) ? big : small; renderView.configuration = (i % 2 == 0) ? big : small;
RenderObject.flushLayout(); WidgetFlutterBinding.instance.pipelineOwner.flushLayout();
} }
watch.stop(); watch.stop();
......
...@@ -20,8 +20,8 @@ The last phase of a frame is the Semantics phase. This only occurs if ...@@ -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 a semantics server has been installed, for example if the user is
using an accessibility tool. using an accessibility tool.
Each frame, the semantics phase starts with a call to the static Each frame, the semantics phase starts with a call to the
`RenderObject.flushSemantics()` method from the `Renderer` binding's `PipelineOwner.flushSemantics()` method from the `Renderer` binding's
`beginFrame()` method. `beginFrame()` method.
Each node marked as needing semantics (which initially is just the Each node marked as needing semantics (which initially is just the
......
...@@ -48,6 +48,10 @@ abstract class Renderer extends Object with Scheduler, Services ...@@ -48,6 +48,10 @@ abstract class Renderer extends Object with Scheduler, Services
handleMetricsChanged(); // configures renderView's metrics 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. /// The render tree that's attached to the output surface.
RenderView get renderView => _renderView; RenderView get renderView => _renderView;
RenderView _renderView; RenderView _renderView;
...@@ -58,7 +62,7 @@ abstract class Renderer extends Object with Scheduler, Services ...@@ -58,7 +62,7 @@ abstract class Renderer extends Object with Scheduler, Services
if (_renderView != null) if (_renderView != null)
_renderView.detach(); _renderView.detach();
_renderView = value; _renderView = value;
_renderView.attach(); _renderView.attach(pipelineOwner);
} }
void handleMetricsChanged() { void handleMetricsChanged() {
...@@ -81,12 +85,12 @@ abstract class Renderer extends Object with Scheduler, Services ...@@ -81,12 +85,12 @@ abstract class Renderer extends Object with Scheduler, Services
/// Pump the rendering pipeline to generate a frame. /// Pump the rendering pipeline to generate a frame.
void beginFrame() { void beginFrame() {
assert(renderView != null); assert(renderView != null);
RenderObject.flushLayout(); pipelineOwner.flushLayout();
RenderObject.flushCompositingBits(); pipelineOwner.flushCompositingBits();
RenderObject.flushPaint(); pipelineOwner.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU renderView.compositeFrame(); // this sends the bits to the GPU
if (SemanticsNode.hasListeners) { if (SemanticsNode.hasListeners) {
RenderObject.flushSemantics(); pipelineOwner.flushSemantics();
SemanticsNode.sendSemanticsTree(); SemanticsNode.sendSemanticsTree();
} }
} }
......
...@@ -337,8 +337,8 @@ class RenderBlockViewport extends RenderBlockBase { ...@@ -337,8 +337,8 @@ class RenderBlockViewport extends RenderBlockBase {
} }
@override @override
void attach() { void attach(PipelineOwner owner) {
super.attach(); super.attach(owner);
_overlayPainter?.attach(this); _overlayPainter?.attach(this);
} }
......
...@@ -411,12 +411,7 @@ class BoxHitTestEntry extends HitTestEntry { ...@@ -411,12 +411,7 @@ class BoxHitTestEntry extends HitTestEntry {
/// Parent data used by [RenderBox] and its subclasses. /// Parent data used by [RenderBox] and its subclasses.
class BoxParentData extends ParentData { class BoxParentData extends ParentData {
/// The offset at which to paint the child in the parent's coordinate system /// The offset at which to paint the child in the parent's coordinate system
Offset get offset => _offset; Offset offset = Offset.zero;
Offset _offset = Offset.zero;
void set offset(Offset value) {
assert(RenderObject.debugDoingLayout);
_offset = value;
}
@override @override
String toString() => 'offset=$offset'; String toString() => 'offset=$offset';
...@@ -579,9 +574,9 @@ abstract class RenderBox extends RenderObject { ...@@ -579,9 +574,9 @@ abstract class RenderBox extends RenderObject {
assert(!_debugDoingBaseline); assert(!_debugDoingBaseline);
assert(() { assert(() {
final RenderObject parent = this.parent; final RenderObject parent = this.parent;
if (RenderObject.debugDoingLayout) if (owner.debugDoingLayout)
return (RenderObject.debugActiveLayout == parent) && parent.debugDoingThisLayout; return (RenderObject.debugActiveLayout == parent) && parent.debugDoingThisLayout;
if (RenderObject.debugDoingPaint) if (owner.debugDoingPaint)
return ((RenderObject.debugActivePaint == parent) && parent.debugDoingThisPaint) || return ((RenderObject.debugActivePaint == parent) && parent.debugDoingThisPaint) ||
((RenderObject.debugActivePaint == this) && debugDoingThisPaint); ((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
assert(parent == this.parent); assert(parent == this.parent);
......
...@@ -230,8 +230,8 @@ class RenderChildView extends RenderBox { ...@@ -230,8 +230,8 @@ class RenderChildView extends RenderBox {
} }
@override @override
void attach() { void attach(PipelineOwner owner) {
super.attach(); super.attach(owner);
_child?._attach(); _child?._attach();
} }
......
...@@ -51,7 +51,7 @@ class AbstractNode { ...@@ -51,7 +51,7 @@ class AbstractNode {
/// Call only from overrides of [redepthChildren] /// Call only from overrides of [redepthChildren]
void redepthChild(AbstractNode child) { void redepthChild(AbstractNode child) {
assert(child._attached == _attached); assert(child.owner == owner);
if (child._depth <= _depth) { if (child._depth <= _depth) {
child._depth = _depth + 1; child._depth = _depth + 1;
child.redepthChildren(); child.redepthChildren();
...@@ -62,16 +62,21 @@ class AbstractNode { ...@@ -62,16 +62,21 @@ class AbstractNode {
/// redepthChild(child) for each child. Do not call directly. /// redepthChild(child) for each child. Do not call directly.
void redepthChildren() { } void redepthChildren() { }
bool _attached = false; Object _owner;
/// The owner for this node (null if unattached).
Object get owner => _owner;
/// Whether this node is in a tree whose root is attached to something. /// Whether this node is in a tree whose root is attached to something.
bool get attached => _attached; bool get attached => _owner != null;
/// Mark this node as attached. /// Mark this node as attached to the given owner.
/// ///
/// Typically called only from the parent's attach(), and to mark the root of /// Typically called only from the parent's attach(), and to mark the root of
/// a tree attached. /// a tree attached.
void attach() { void attach(Object owner) {
_attached = true; assert(owner != null);
assert(_owner == null);
_owner = owner;
} }
/// Mark this node as detached. /// Mark this node as detached.
...@@ -79,7 +84,8 @@ class AbstractNode { ...@@ -79,7 +84,8 @@ class AbstractNode {
/// Typically called only from the parent's detach(), and to mark the root of /// Typically called only from the parent's detach(), and to mark the root of
/// a tree detached. /// a tree detached.
void detach() { void detach() {
_attached = false; assert(_owner != null);
_owner = null;
} }
AbstractNode _parent; AbstractNode _parent;
...@@ -99,7 +105,7 @@ class AbstractNode { ...@@ -99,7 +105,7 @@ class AbstractNode {
}); });
child._parent = this; child._parent = this;
if (attached) if (attached)
child.attach(); child.attach(_owner);
redepthChild(child); redepthChild(child);
} }
......
...@@ -565,7 +565,8 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment { ...@@ -565,7 +565,8 @@ class _RootSemanticsFragment extends _InterestingSemanticsFragment {
assert(currentSemantics == null); assert(currentSemantics == null);
assert(parentSemantics == null); assert(parentSemantics == null);
owner._semantics ??= new SemanticsNode.root( 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; SemanticsNode node = owner._semantics;
assert(MatrixUtils.matrixEquals(node.transform, new Matrix4.identity())); assert(MatrixUtils.matrixEquals(node.transform, new Matrix4.identity()));
...@@ -677,6 +678,105 @@ class _ForkingSemanticsFragment extends _SemanticsFragment { ...@@ -677,6 +678,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. /// An object in the render tree.
/// ///
/// The [RenderObject] class hierarchy is the core of the rendering /// The [RenderObject] class hierarchy is the core of the rendering
...@@ -817,8 +917,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -817,8 +917,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
} }
static bool _debugDoingLayout = false;
static bool get debugDoingLayout => _debugDoingLayout;
bool _debugDoingThisResize = false; bool _debugDoingThisResize = false;
bool get debugDoingThisResize => _debugDoingThisResize; bool get debugDoingThisResize => _debugDoingThisResize;
bool _debugDoingThisLayout = false; bool _debugDoingThisLayout = false;
...@@ -841,7 +939,9 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -841,7 +939,9 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
} }
static List<RenderObject> _nodesNeedingLayout = <RenderObject>[]; @override
PipelineOwner get owner => super.owner;
bool _needsLayout = true; bool _needsLayout = true;
/// Whether this render object's layout information is dirty. /// Whether this render object's layout information is dirty.
bool get needsLayout => _needsLayout; bool get needsLayout => _needsLayout;
...@@ -918,7 +1018,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -918,7 +1018,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
debugPrintStack(); debugPrintStack();
return true; return true;
}); });
_nodesNeedingLayout.add(this); if (owner != null)
owner._nodesNeedingLayout.add(this);
Scheduler.instance.ensureVisualUpdate(); Scheduler.instance.ensureVisualUpdate();
} }
} }
...@@ -942,41 +1043,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -942,41 +1043,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void scheduleInitialLayout() { void scheduleInitialLayout() {
assert(attached); assert(attached);
assert(parent is! RenderObject); assert(parent is! RenderObject);
assert(!_debugDoingLayout); assert(!owner._debugDoingLayout);
assert(_relayoutSubtreeRoot == null); assert(_relayoutSubtreeRoot == null);
_relayoutSubtreeRoot = this; _relayoutSubtreeRoot = this;
assert(() { assert(() {
_debugCanParentUseSize = false; _debugCanParentUseSize = false;
return true; 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() { void _layoutWithoutResize() {
assert(_relayoutSubtreeRoot == this); assert(_relayoutSubtreeRoot == this);
RenderObject debugPreviousActiveLayout; RenderObject debugPreviousActiveLayout;
...@@ -1186,16 +1262,11 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1186,16 +1262,11 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
// PAINTING // PAINTING
static bool _debugDoingPaint = false;
static bool get debugDoingPaint => _debugDoingPaint;
bool _debugDoingThisPaint = false; bool _debugDoingThisPaint = false;
bool get debugDoingThisPaint => _debugDoingThisPaint; bool get debugDoingThisPaint => _debugDoingThisPaint;
static RenderObject _debugActivePaint; static RenderObject _debugActivePaint;
static RenderObject get debugActivePaint => _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. /// Whether this render object repaints separately from its parent.
/// ///
/// Override this in subclasses to indicate that instances of your class ought /// Override this in subclasses to indicate that instances of your class ought
...@@ -1266,22 +1337,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1266,22 +1337,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
return true; return true;
}); });
// parent is fine (or there isn't one), but we are dirty // parent is fine (or there isn't one), but we are dirty
_nodesNeedingCompositingBitsUpdate.add(this); if (owner != null)
} owner._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();
} }
bool _needsCompositing; // initialised in the constructor bool _needsCompositing; // initialised in the constructor
...@@ -1325,7 +1382,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1325,7 +1382,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// This mechanism batches the painting work so that multiple sequential /// This mechanism batches the painting work so that multiple sequential
/// writes are coalesced, removing redundant computation. /// writes are coalesced, removing redundant computation.
void markNeedsPaint() { void markNeedsPaint() {
assert(!debugDoingPaint); assert(owner == null || !owner.debugDoingPaint);
if (!attached) if (!attached)
return; // Don't try painting things that aren't in the hierarchy return; // Don't try painting things that aren't in the hierarchy
if (_needsPaint) if (_needsPaint)
...@@ -1340,7 +1397,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1340,7 +1397,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
// If we always have our own layer, then we can just repaint // If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes. // ourselves without involving any other nodes.
assert(_layer != null); assert(_layer != null);
_nodesNeedingPaint.add(this); if (owner != null)
owner._nodesNeedingPaint.add(this);
Scheduler.instance.ensureVisualUpdate(); Scheduler.instance.ensureVisualUpdate();
} else if (parent is RenderObject) { } else if (parent is RenderObject) {
// We don't have our own layer; one of our ancestors will take // We don't have our own layer; one of our ancestors will take
...@@ -1359,32 +1417,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1359,32 +1417,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. /// Bootstrap the rendering pipeline by scheduling the very first paint.
/// ///
/// Requires that this render object is attached, is the root of the render /// Requires that this render object is attached, is the root of the render
...@@ -1394,12 +1426,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1394,12 +1426,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void scheduleInitialPaint(ContainerLayer rootLayer) { void scheduleInitialPaint(ContainerLayer rootLayer) {
assert(attached); assert(attached);
assert(parent is! RenderObject); assert(parent is! RenderObject);
assert(!_debugDoingPaint); assert(!owner._debugDoingPaint);
assert(isRepaintBoundary); assert(isRepaintBoundary);
assert(_layer == null); assert(_layer == null);
_layer = rootLayer; _layer = rootLayer;
assert(_needsPaint); assert(_needsPaint);
_nodesNeedingPaint.add(this); owner._nodesNeedingPaint.add(this);
} }
void _paintWithContext(PaintingContext context, Offset offset) { void _paintWithContext(PaintingContext context, Offset offset) {
assert(!_debugDoingThisPaint); assert(!_debugDoingThisPaint);
...@@ -1483,10 +1515,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1483,10 +1515,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
// SEMANTICS // SEMANTICS
static bool _semanticsEnabled = false;
static bool _debugDoingSemantics = false;
static List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
/// Bootstrap the semantics reporting mechanism by marking this node /// Bootstrap the semantics reporting mechanism by marking this node
/// as needing a semantics update. /// as needing a semantics update.
/// ///
...@@ -1497,32 +1525,15 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1497,32 +1525,15 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void scheduleInitialSemantics() { void scheduleInitialSemantics() {
assert(attached); assert(attached);
assert(parent is! RenderObject); assert(parent is! RenderObject);
assert(!_debugDoingSemantics); assert(!owner._debugDoingSemantics);
assert(_semantics == null); assert(_semantics == null);
assert(_needsSemanticsUpdate); assert(_needsSemanticsUpdate);
assert(_semanticsEnabled == false); assert(owner._semanticsEnabled == false);
_semanticsEnabled = true; owner._semanticsEnabled = true;
_nodesNeedingSemantics.add(this); owner._nodesNeedingSemantics.add(this);
Scheduler.instance.ensureVisualUpdate(); 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. /// Whether this RenderObject introduces a new box for accessibility purposes.
bool get hasSemantics => false; bool get hasSemantics => false;
...@@ -1567,8 +1578,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1567,8 +1578,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// 'noGeometry: true' when the geometry did change, the semantic /// 'noGeometry: true' when the geometry did change, the semantic
/// tree will be out of date. /// tree will be out of date.
void markNeedsSemanticsUpdate({ bool onlyChanges: false, bool noGeometry: false }) { void markNeedsSemanticsUpdate({ bool onlyChanges: false, bool noGeometry: false }) {
assert(!_debugDoingSemantics); assert(!attached || !owner._debugDoingSemantics);
if (!_semanticsEnabled || !attached || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry))) if (!attached || !owner._semanticsEnabled || (_needsSemanticsUpdate && onlyChanges && (_needsSemanticsGeometryUpdate || noGeometry)))
return; return;
if (!noGeometry && (_semantics == null || (_semantics.hasChildren && _semantics.wasAffectedByClip))) { if (!noGeometry && (_semantics == null || (_semantics.hasChildren && _semantics.wasAffectedByClip))) {
// Since the geometry might have changed, we need to make sure to reapply any clips. // Since the geometry might have changed, we need to make sure to reapply any clips.
...@@ -1588,7 +1599,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1588,7 +1599,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
if (!node._needsSemanticsUpdate) { if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true; node._needsSemanticsUpdate = true;
_nodesNeedingSemantics.add(node); owner._nodesNeedingSemantics.add(node);
} }
} else { } else {
// The shape of the semantics tree around us may have changed. // The shape of the semantics tree around us may have changed.
...@@ -1607,7 +1618,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1607,7 +1618,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
node._semantics?.reset(); node._semantics?.reset();
if (!node._needsSemanticsUpdate) { if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true; node._needsSemanticsUpdate = true;
_nodesNeedingSemantics.add(node); owner._nodesNeedingSemantics.add(node);
} }
} }
} }
...@@ -1820,10 +1831,10 @@ abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implem ...@@ -1820,10 +1831,10 @@ abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implem
} }
@override @override
void attach() { void attach(PipelineOwner owner) {
super.attach(); super.attach(owner);
if (_child != null) if (_child != null)
_child.attach(); _child.attach(owner);
} }
@override @override
...@@ -2040,11 +2051,11 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent ...@@ -2040,11 +2051,11 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
} }
@override @override
void attach() { void attach(PipelineOwner owner) {
super.attach(); super.attach(owner);
ChildType child = _firstChild; ChildType child = _firstChild;
while (child != null) { while (child != null) {
child.attach(); child.attach(owner);
final ParentDataType childParentData = child.parentData; final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling; child = childParentData.nextSibling;
} }
......
...@@ -912,8 +912,8 @@ class RenderDecoratedBox extends RenderProxyBox { ...@@ -912,8 +912,8 @@ class RenderDecoratedBox extends RenderProxyBox {
} }
@override @override
void attach() { void attach(PipelineOwner owner) {
super.attach(); super.attach(owner);
_addListenerIfNeeded(); _addListenerIfNeeded();
} }
......
...@@ -58,10 +58,11 @@ class SemanticsNode extends AbstractNode { ...@@ -58,10 +58,11 @@ class SemanticsNode extends AbstractNode {
_actionHandler = handler; _actionHandler = handler;
SemanticsNode.root({ SemanticsNode.root({
SemanticActionHandler handler SemanticActionHandler handler,
Object owner
}) : _id = 0, }) : _id = 0,
_actionHandler = handler { _actionHandler = handler {
attach(); attach(owner);
} }
static int _lastIdentifier = 0; static int _lastIdentifier = 0;
...@@ -265,8 +266,8 @@ class SemanticsNode extends AbstractNode { ...@@ -265,8 +266,8 @@ class SemanticsNode extends AbstractNode {
static Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>(); static Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>();
@override @override
void attach() { void attach(Object owner) {
super.attach(); super.attach(owner);
assert(!_nodes.containsKey(_id)); assert(!_nodes.containsKey(_id));
_nodes[_id] = this; _nodes[_id] = this;
_detachedNodes.remove(this); _detachedNodes.remove(this);
...@@ -274,7 +275,7 @@ class SemanticsNode extends AbstractNode { ...@@ -274,7 +275,7 @@ class SemanticsNode extends AbstractNode {
_inheritedMergeAllDescendantsIntoThisNode = parent._shouldMergeAllDescendantsIntoThisNode; _inheritedMergeAllDescendantsIntoThisNode = parent._shouldMergeAllDescendantsIntoThisNode;
if (_children != null) { if (_children != null) {
for (SemanticsNode child in _children) for (SemanticsNode child in _children)
child.attach(); child.attach(owner);
} }
} }
...@@ -404,7 +405,7 @@ class SemanticsNode extends AbstractNode { ...@@ -404,7 +405,7 @@ class SemanticsNode extends AbstractNode {
child._inheritedMergeAllDescendantsIntoThisNode = false; // this can add the node to the dirty list child._inheritedMergeAllDescendantsIntoThisNode = false; // this can add the node to the dirty list
} }
} }
} }
assert(_dirtyNodes[index] == node); // make sure nothing went in front of us in the list assert(_dirtyNodes[index] == node); // make sure nothing went in front of us in the list
} }
_dirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth); _dirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
......
...@@ -33,7 +33,7 @@ class ViewConfiguration { ...@@ -33,7 +33,7 @@ class ViewConfiguration {
/// The root of the render tree. /// The root of the render tree.
/// ///
/// The view represents the total output surface of the render tree and handles /// 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. /// [RenderBox], which is required to fill the entire output surface.
class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> { class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
RenderView({ RenderView({
......
...@@ -168,8 +168,8 @@ class RenderViewportBase extends RenderBox implements HasMainAxis { ...@@ -168,8 +168,8 @@ class RenderViewportBase extends RenderBox implements HasMainAxis {
} }
@override @override
void attach() { void attach(PipelineOwner owner) {
super.attach(); super.attach(owner);
_overlayPainter?.attach(this); _overlayPainter?.attach(this);
} }
......
...@@ -335,7 +335,11 @@ class RawGestureDetectorState extends State<RawGestureDetector> { ...@@ -335,7 +335,11 @@ class RawGestureDetectorState extends State<RawGestureDetector> {
/// the gesture detector should be enabled. /// the gesture detector should be enabled.
void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) { void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
assert(() { 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( throw new FlutterError(
'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n' 'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
'The replaceGestureRecognizers() method can only be called during the layout phase. ' 'The replaceGestureRecognizers() method can only be called during the layout phase. '
......
// 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)));
});
}
...@@ -41,16 +41,16 @@ class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services, ...@@ -41,16 +41,16 @@ class TestRenderingFlutterBinding extends BindingBase with Scheduler, Services,
@override @override
void beginFrame() { void beginFrame() {
RenderObject.flushLayout(); pipelineOwner.flushLayout();
if (phase == EnginePhase.layout) if (phase == EnginePhase.layout)
return; return;
RenderObject.flushCompositingBits(); pipelineOwner.flushCompositingBits();
if (phase == EnginePhase.compositingBits) if (phase == EnginePhase.compositingBits)
return; return;
RenderObject.flushPaint(); pipelineOwner.flushPaint();
if (phase == EnginePhase.paint) if (phase == EnginePhase.paint)
return; return;
renderer.renderView.compositeFrame(); renderView.compositeFrame();
} }
} }
......
...@@ -58,8 +58,8 @@ class SpriteBox extends RenderBox { ...@@ -58,8 +58,8 @@ class SpriteBox extends RenderBox {
} }
@override @override
void attach() { void attach(PipelineOwner owner) {
super.attach(); super.attach(owner);
_scheduleTick(); _scheduleTick();
} }
......
...@@ -46,20 +46,20 @@ class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding { ...@@ -46,20 +46,20 @@ class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
// Cloned from Renderer.beginFrame() but with early-exit semantics. // Cloned from Renderer.beginFrame() but with early-exit semantics.
void _beginFrame() { void _beginFrame() {
assert(renderView != null); assert(renderView != null);
RenderObject.flushLayout(); pipelineOwner.flushLayout();
if (phase == EnginePhase.layout) if (phase == EnginePhase.layout)
return; return;
RenderObject.flushCompositingBits(); pipelineOwner.flushCompositingBits();
if (phase == EnginePhase.compositingBits) if (phase == EnginePhase.compositingBits)
return; return;
RenderObject.flushPaint(); pipelineOwner.flushPaint();
if (phase == EnginePhase.paint) if (phase == EnginePhase.paint)
return; return;
renderView.compositeFrame(); // this sends the bits to the GPU renderView.compositeFrame(); // this sends the bits to the GPU
if (phase == EnginePhase.composite) if (phase == EnginePhase.composite)
return; return;
if (SemanticsNode.hasListeners) { if (SemanticsNode.hasListeners) {
RenderObject.flushSemantics(); pipelineOwner.flushSemantics();
if (phase == EnginePhase.flushSemantics) if (phase == EnginePhase.flushSemantics)
return; return;
SemanticsNode.sendSemanticsTree(); SemanticsNode.sendSemanticsTree();
......
...@@ -347,6 +347,7 @@ class AnalyzeCommand extends FlutterCommand { ...@@ -347,6 +347,7 @@ class AnalyzeCommand extends FlutterCommand {
new RegExp('^\\[(hint|error)\\] Unused import \\(${mainFile.path},'), new RegExp('^\\[(hint|error)\\] Unused import \\(${mainFile.path},'),
new RegExp(r'^\[.+\] .+ \(.+/\.pub-cache/.+'), 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 \'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'[0-9]+ (error|warning|hint|lint).+found\.'),
new RegExp(r'^$'), new RegExp(r'^$'),
]; ];
......
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