Commit bb5a82a7 authored by Kris Giesing's avatar Kris Giesing

Allow independent rendering pipelines

Fixes #2723
parent e074af80
......@@ -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();
......
......@@ -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
......
......@@ -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();
}
}
......
......@@ -337,8 +337,8 @@ class RenderBlockViewport extends RenderBlockBase {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_overlayPainter?.attach(this);
}
......
......@@ -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);
......
......@@ -171,8 +171,8 @@ class RenderChildView extends RenderBox {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_child?._attach();
}
......
......@@ -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;
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.
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
/// 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);
}
......
......@@ -1035,8 +1035,8 @@ class RenderDecoratedBox extends RenderProxyBox {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_addListenerIfNeeded();
}
......
......@@ -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);
}
}
......@@ -404,7 +405,7 @@ class SemanticsNode extends AbstractNode {
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
}
_dirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
......
......@@ -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({
......
......@@ -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);
}
......
......@@ -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. '
......
// 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,
@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();
}
}
......
......@@ -58,8 +58,8 @@ class SpriteBox extends RenderBox {
}
@override
void attach() {
super.attach();
void attach(PipelineOwner owner) {
super.attach(owner);
_scheduleTick();
}
......
......@@ -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();
......
......@@ -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'^$'),
];
......
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