Less tree walking for compositing bits updates.

Use the same technique for updating compositing bits as layout and
painting. This avoids walking the entire rendering tree when all you
need to update is a small subtree.
This commit is contained in:
Hixie 2015-12-17 13:53:34 -08:00
parent f107522fb3
commit ab01c7bf73
4 changed files with 75 additions and 34 deletions

View File

@ -69,7 +69,7 @@ abstract class Renderer extends Scheduler
void beginFrame() { void beginFrame() {
assert(renderView != null); assert(renderView != null);
RenderObject.flushLayout(); RenderObject.flushLayout();
renderView.updateCompositingBits(); RenderObject.flushCompositingBits();
RenderObject.flushPaint(); RenderObject.flushPaint();
renderView.compositeFrame(); renderView.compositeFrame();
} }

View File

@ -391,6 +391,10 @@ RenderingExceptionHandler debugRenderingExceptionHandler;
/// for their children. /// for their children.
abstract class RenderObject extends AbstractNode implements HitTestTarget { abstract class RenderObject extends AbstractNode implements HitTestTarget {
RenderObject() {
_needsCompositing = hasLayer;
}
// LAYOUT // LAYOUT
/// Data for use by the parent render object. /// Data for use by the parent render object.
@ -492,7 +496,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
} }
static List<RenderObject> _nodesNeedingLayout = new List<RenderObject>(); static List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
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;
@ -597,11 +601,11 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themeselves // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themeselves
while (_nodesNeedingLayout.isNotEmpty) { while (_nodesNeedingLayout.isNotEmpty) {
List<RenderObject> dirtyNodes = _nodesNeedingLayout; List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = new List<RenderObject>(); _nodesNeedingLayout = <RenderObject>[];
dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)..forEach((RenderObject node) { for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.attached) if (node._needsLayout && node.attached)
node._layoutWithoutResize(); node._layoutWithoutResize();
}); }
} }
} finally { } finally {
_debugDoingLayout = false; _debugDoingLayout = false;
@ -822,7 +826,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
static RenderObject _debugActivePaint = null; static RenderObject _debugActivePaint = null;
static RenderObject get debugActivePaint => _debugActivePaint; static RenderObject get debugActivePaint => _debugActivePaint;
static List<RenderObject> _nodesNeedingPaint = new List<RenderObject>(); static List<RenderObject> _nodesNeedingPaint = <RenderObject>[];
static List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
/// Whether this render object paints using a composited layer. /// Whether this render object paints using a composited layer.
/// ///
@ -843,49 +848,79 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
return _layer; return _layer;
} }
bool _needsCompositingBitsUpdate = true; bool _needsCompositingBitsUpdate = false; // set to true when a child is added
/// Mark the compositing state for this render object as dirty. /// Mark the compositing state for this render object as dirty.
/// ///
/// When the subtree is mutated, we need to recompute our [needsCompositing] /// When the subtree is mutated, we need to recompute our
/// bit, and our ancestors need to do the same (in case ours changed). /// [needsCompositing] bit, and some of our ancestors need to do the
/// Therefore, [adoptChild] and [dropChild] call /// same (in case ours changed in a way that will change theirs). To
/// [markNeedsCompositingBitsUpdate]. /// this end, [adoptChild] and [dropChild] call this method, and, as
/// necessary, this method calls the parent's, etc, walking up the
/// tree to mark all the nodes that need updating.
///
/// This method does not schedule a rendering frame, because since
/// it cannot be the case that _only_ the compositing bits changed,
/// something else will have scheduled a frame for us.
void _markNeedsCompositingBitsUpdate() { void _markNeedsCompositingBitsUpdate() {
if (_needsCompositingBitsUpdate) if (_needsCompositingBitsUpdate)
return; return;
_needsCompositingBitsUpdate = true; _needsCompositingBitsUpdate = true;
final AbstractNode parent = this.parent; if (parent is RenderObject) {
if (parent is RenderObject) final RenderObject parent = this.parent;
parent._markNeedsCompositingBitsUpdate(); if (parent._needsCompositingBitsUpdate)
assert(parent == this.parent); return;
} if (!hasLayer && !parent.hasLayer) {
parent._markNeedsCompositingBitsUpdate();
bool _needsCompositing = false; return;
/// Whether we or one of our descendants has a compositing layer. }
/// }
/// Only legal to call after [flushLayout] and [updateCompositingBits] have assert(() {
/// been called. final AbstractNode parent = this.parent;
bool get needsCompositing { if (parent is RenderObject)
assert(!_needsCompositingBitsUpdate); // make sure we don't use this bit when it is dirty return parent._needsCompositing;
return _needsCompositing; return true;
});
// parent is fine (or there isn't one), but we are dirty
_nodesNeedingCompositingBitsUpdate.add(this);
} }
/// Updates the [needsCompositing] bits. /// Updates the [needsCompositing] bits.
/// ///
/// Called as part of the rendering pipeline after [flushLayout] and before /// Called as part of the rendering pipeline after [flushLayout] and before
/// [flushPaint]. /// [flushPaint].
void updateCompositingBits() { 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
/// Whether we or one of our descendants has a compositing layer.
///
/// Only legal to call after [flushLayout] and [flushCompositingBits] have
/// been called.
bool get needsCompositing {
assert(!_needsCompositingBitsUpdate); // make sure we don't use this bit when it is dirty
return _needsCompositing;
}
void _updateCompositingBits() {
if (!_needsCompositingBitsUpdate) if (!_needsCompositingBitsUpdate)
return; return;
bool didHaveCompositedDescendant = _needsCompositing; bool oldNeedsCompositing = _needsCompositing;
visitChildren((RenderObject child) { visitChildren((RenderObject child) {
child.updateCompositingBits(); child._updateCompositingBits();
if (child.needsCompositing) if (child.needsCompositing)
_needsCompositing = true; _needsCompositing = true;
}); });
if (hasLayer) if (hasLayer)
_needsCompositing = true; _needsCompositing = true;
if (didHaveCompositedDescendant != _needsCompositing) if (oldNeedsCompositing != _needsCompositing)
markNeedsPaint(); markNeedsPaint();
_needsCompositingBitsUpdate = false; _needsCompositingBitsUpdate = false;
} }
@ -946,7 +981,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
_debugDoingPaint = true; _debugDoingPaint = true;
try { try {
List<RenderObject> dirtyNodes = _nodesNeedingPaint; List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = new List<RenderObject>(); _nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first). // Sort the dirty nodes in reverse order (deepest first).
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._needsPaint); assert(node._needsPaint);

View File

@ -21,6 +21,7 @@ class TestRenderView extends RenderView {
enum EnginePhase { enum EnginePhase {
layout, layout,
compositingBits,
paint, paint,
composite composite
} }
@ -39,7 +40,9 @@ class TestRenderingFlutterBinding extends BindingBase with Scheduler, Renderer,
RenderObject.flushLayout(); RenderObject.flushLayout();
if (phase == EnginePhase.layout) if (phase == EnginePhase.layout)
return; return;
renderer.renderView.updateCompositingBits(); RenderObject.flushCompositingBits();
if (phase == EnginePhase.compositingBits)
return;
RenderObject.flushPaint(); RenderObject.flushPaint();
if (phase == EnginePhase.paint) if (phase == EnginePhase.paint)
return; return;

View File

@ -10,18 +10,21 @@ import 'rendering_tester.dart';
void main() { void main() {
test('Stack can layout with top, right, bottom, left 0.0', () { test('Stack can layout with top, right, bottom, left 0.0', () {
RenderBox size = new RenderConstrainedBox( RenderBox size = new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tight(const Size(100.0, 100.0))); additionalConstraints: new BoxConstraints.tight(const Size(100.0, 100.0))
);
RenderBox red = new RenderDecoratedBox( RenderBox red = new RenderDecoratedBox(
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: const Color(0xFFFF0000) backgroundColor: const Color(0xFFFF0000)
), ),
child: size); child: size
);
RenderBox green = new RenderDecoratedBox( RenderBox green = new RenderDecoratedBox(
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: const Color(0xFFFF0000) backgroundColor: const Color(0xFFFF0000)
)); )
);
RenderBox stack = new RenderStack(children: <RenderBox>[red, green]); RenderBox stack = new RenderStack(children: <RenderBox>[red, green]);
(green.parentData as StackParentData) (green.parentData as StackParentData)