Commit bb5a82a7 authored by Kris Giesing's avatar Kris Giesing

Allow independent rendering pipelines

Fixes #2723
parent e074af80
...@@ -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);
} }
......
...@@ -388,12 +388,7 @@ class BoxHitTestEntry extends HitTestEntry { ...@@ -388,12 +388,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';
...@@ -556,9 +551,9 @@ abstract class RenderBox extends RenderObject { ...@@ -556,9 +551,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);
......
...@@ -171,8 +171,8 @@ class RenderChildView extends RenderBox { ...@@ -171,8 +171,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);
} }
......
...@@ -1035,8 +1035,8 @@ class RenderDecoratedBox extends RenderProxyBox { ...@@ -1035,8 +1035,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);
} }
} }
......
...@@ -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