Commit 654fc734 authored by Hixie's avatar Hixie

Improvements for Layers logic

- Introduce some new Layer classes.
- Introduce paintChildWith* methods.
- Convert paint() methods to use paintChildWith* where appropriate.
- Fix paintBounds logic in Layer world.
- Introduce Layer.replaceWith(), so that it's clearer what's going on.
- Make RenderObjects have a ContainerLayer, not a PictureLayer.
- Introduce a PaintingContext.replacingLayer() constructor to highlight
  where we are creating a layer just to replace an older one.
- Rename some layer-related methods and fields for clarity:
   requiresCompositing -> hasLayer
   hasCompositedDescendant -> needsCompositing
   updateCompositing -> updateCompositingBits
   _needsCompositingUpdate -> _needsCompositingBitsUpdate
   markNeedsCompositingUpdate -> markNeedsCompositingBitsUpdate
- After updating compositing bits, if we find that the bit changed, we
  now call markNeedsPaint().
- Reorder markNeedsPaint() logic for clarity.
- Make flushPaint() start at the deepest node.
- Make _compositeChild() avoid repainting children with hasLayer that
  aren't dirty, instead it just reuses their existing layer.
- Made RenderView reuse the RenderObject layer instead of having its own.
- Made RenderView have hasLayer set to true.
- Add various asserts and comments.
parent d662f7e6
...@@ -26,15 +26,40 @@ abstract class Layer { ...@@ -26,15 +26,40 @@ abstract class Layer {
if (_parent != null) if (_parent != null)
_parent.remove(this); _parent.remove(this);
} }
void replaceWith(Layer newLayer) {
assert(_parent != null);
assert(newLayer._parent == null);
assert(newLayer._nextSibling == null);
assert(newLayer._previousSibling == null);
newLayer._nextSibling = _nextSibling;
if (_nextSibling != null)
newLayer._nextSibling._previousSibling = newLayer;
newLayer._previousSibling = _previousSibling;
if (_previousSibling != null)
newLayer._previousSibling._nextSibling = newLayer;
newLayer._parent = _parent;
if (_parent._firstChild == this)
_parent._firstChild = newLayer;
if (_parent._lastChild == this)
_parent._lastChild = newLayer;
_nextSibling = null;
_previousSibling = null;
_parent = null;
}
// The paint() methods are temporary. Eventually, Layers won't have
// a paint() method, the entire Layer hierarchy will be handed over
// to the C++ side for processing. Until we implement that, though,
// we instead have the layers paint themselves into a canvas at
// paint time.
void paint(sky.Canvas canvas); void paint(sky.Canvas canvas);
} }
class PictureLayer extends Layer { class PictureLayer extends Layer {
PictureLayer({ Offset offset: Offset.zero, this.size }) PictureLayer({ Offset offset: Offset.zero, this.paintBounds })
: super(offset: offset); : super(offset: offset);
Size size; Rect paintBounds;
sky.Picture picture; sky.Picture picture;
bool _debugPaintLayerBorder(sky.Canvas canvas) { bool _debugPaintLayerBorder(sky.Canvas canvas) {
...@@ -43,12 +68,13 @@ class PictureLayer extends Layer { ...@@ -43,12 +68,13 @@ class PictureLayer extends Layer {
..color = debugPaintLayerBordersColor ..color = debugPaintLayerBordersColor
..strokeWidth = 2.0 ..strokeWidth = 2.0
..setStyle(sky.PaintingStyle.stroke); ..setStyle(sky.PaintingStyle.stroke);
canvas.drawRect(Point.origin & size, border); canvas.drawRect(paintBounds, border);
} }
return true; return true;
} }
void paint(sky.Canvas canvas) { void paint(sky.Canvas canvas) {
assert(picture != null);
canvas.translate(offset.dx, offset.dy); canvas.translate(offset.dx, offset.dy);
canvas.drawPicture(picture); canvas.drawPicture(picture);
assert(_debugPaintLayerBorder(canvas)); assert(_debugPaintLayerBorder(canvas));
...@@ -59,17 +85,11 @@ class PictureLayer extends Layer { ...@@ -59,17 +85,11 @@ class PictureLayer extends Layer {
class ContainerLayer extends Layer { class ContainerLayer extends Layer {
ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset); ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset);
void paint(sky.Canvas canvas) { // TODO(ianh): hide firstChild since nobody uses it
Layer child = firstChild;
while (child != null) {
child.paint(canvas);
child = child.nextSibling;
}
}
Layer _firstChild; Layer _firstChild;
Layer get firstChild => _firstChild; Layer get firstChild => _firstChild;
// TODO(ianh): remove _lastChild since nobody uses it
Layer _lastChild; Layer _lastChild;
Layer get lastChild => _lastChild; Layer get lastChild => _lastChild;
...@@ -89,6 +109,7 @@ class ContainerLayer extends Layer { ...@@ -89,6 +109,7 @@ class ContainerLayer extends Layer {
return child == equals; return child == equals;
} }
// TODO(ianh): Remove 'before' and rename the function to 'append' since nobody uses 'before'
void add(Layer child, { Layer before }) { void add(Layer child, { Layer before }) {
assert(child != this); assert(child != this);
assert(before != this); assert(before != this);
...@@ -126,10 +147,11 @@ class ContainerLayer extends Layer { ...@@ -126,10 +147,11 @@ class ContainerLayer extends Layer {
} }
} }
// TODO(ianh): Hide this function since only detach() uses it
void remove(Layer child) { void remove(Layer child) {
assert(child._parent == this);
assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
assert(child._parent == this);
if (child._previousSibling == null) { if (child._previousSibling == null) {
assert(_firstChild == child); assert(_firstChild == child);
_firstChild = child._nextSibling; _firstChild = child._nextSibling;
...@@ -146,6 +168,69 @@ class ContainerLayer extends Layer { ...@@ -146,6 +168,69 @@ class ContainerLayer extends Layer {
child._nextSibling = null; child._nextSibling = null;
child._parent = null; child._parent = null;
} }
void paint(sky.Canvas canvas) {
canvas.translate(offset.dx, offset.dy);
paintChildren(canvas);
canvas.translate(-offset.dx, -offset.dy);
}
void paintChildren(sky.Canvas canvas) {
Layer child = firstChild;
while (child != null) {
child.paint(canvas);
child = child.nextSibling;
}
}
}
class ClipRectLayer extends ContainerLayer {
ClipRectLayer({ Offset offset: Offset.zero, this.clipRect }) : super(offset: offset);
// clipRect is _not_ affected by given offset
Rect clipRect;
void paint(sky.Canvas canvas) {
canvas.save();
canvas.clipRect(clipRect);
canvas.translate(offset.dx, offset.dy);
paintChildren(canvas);
canvas.restore();
}
}
final Paint _disableAntialias = new Paint()..isAntiAlias = false;
class ClipRRectLayer extends ContainerLayer {
ClipRRectLayer({ Offset offset: Offset.zero, this.bounds, this.clipRRect }) : super(offset: offset);
// bounds and clipRRect are _not_ affected by given offset
Rect bounds;
sky.RRect clipRRect;
void paint(sky.Canvas canvas) {
canvas.saveLayer(bounds, _disableAntialias);
canvas.clipRRect(clipRRect);
canvas.translate(offset.dx, offset.dy);
paintChildren(canvas);
canvas.restore();
}
}
class ClipPathLayer extends ContainerLayer {
ClipPathLayer({ Offset offset: Offset.zero, this.bounds, this.clipPath }) : super(offset: offset);
// bounds and clipPath are _not_ affected by given offset
Rect bounds;
Path clipPath;
void paint(sky.Canvas canvas) {
canvas.saveLayer(bounds, _disableAntialias);
canvas.clipPath(clipPath);
canvas.translate(offset.dx, offset.dy);
paintChildren(canvas);
canvas.restore();
}
} }
class TransformLayer extends ContainerLayer { class TransformLayer extends ContainerLayer {
...@@ -157,21 +242,22 @@ class TransformLayer extends ContainerLayer { ...@@ -157,21 +242,22 @@ class TransformLayer extends ContainerLayer {
canvas.save(); canvas.save();
canvas.translate(offset.dx, offset.dy); canvas.translate(offset.dx, offset.dy);
canvas.concat(transform.storage); canvas.concat(transform.storage);
super.paint(canvas); paintChildren(canvas);
canvas.restore(); canvas.restore();
} }
} }
class ClipLayer extends ContainerLayer { class PaintLayer extends ContainerLayer {
ClipLayer({ Offset offset: Offset.zero, this.clipRect }) : super(offset: offset); PaintLayer({ Offset offset: Offset.zero, this.bounds, this.paintSettings }) : super(offset: offset);
Rect clipRect; // bounds is _not_ affected by given offset
Rect bounds;
Paint paintSettings; // TODO(ianh): rename this to 'paint' once paint() is gone
void paint(sky.Canvas canvas) { void paint(sky.Canvas canvas) {
canvas.save(); canvas.saveLayer(bounds, paintSettings);
canvas.translate(offset.dx, offset.dy); canvas.translate(offset.dx, offset.dy);
canvas.clipRect(clipRect); paintChildren(canvas);
super.paint(canvas);
canvas.restore(); canvas.restore();
} }
} }
...@@ -192,10 +278,9 @@ class ColorFilterLayer extends ContainerLayer { ...@@ -192,10 +278,9 @@ class ColorFilterLayer extends ContainerLayer {
Paint paint = new Paint() Paint paint = new Paint()
..color = color ..color = color
..setTransferMode(transferMode); ..setTransferMode(transferMode);
canvas.saveLayer(offset & size, paint); canvas.saveLayer(offset & size, paint);
canvas.translate(offset.dx, offset.dy); canvas.translate(offset.dx, offset.dy);
super.paint(canvas); paintChildren(canvas);
canvas.restore(); canvas.restore();
} }
} }
...@@ -41,94 +41,211 @@ class PaintingContext { ...@@ -41,94 +41,211 @@ class PaintingContext {
// Don't keep a reference to the PaintingContext.canvas, since it // Don't keep a reference to the PaintingContext.canvas, since it
// can change dynamically after any call to this object's methods. // can change dynamically after any call to this object's methods.
PaintingContext(Offset offest, Size size) { PaintingContext.withOffset(Offset offset, Rect paintBounds) {
_startRecording(offest, size); _containerLayer = new ContainerLayer(offset: offset);
_startRecording(paintBounds);
} }
PaintingCanvas _canvas; PaintingContext.withLayer(ContainerLayer containerLayer, Rect paintBounds) {
PaintingCanvas get canvas => _canvas; _containerLayer = containerLayer;
_startRecording(paintBounds);
PictureLayer _layer; }
PictureLayer get layer => _layer;
sky.PictureRecorder _recorder; factory PaintingContext.replacingLayer(ContainerLayer oldLayer, Rect paintBounds) {
PaintingContext newContext = new PaintingContext.withOffset(oldLayer.offset, paintBounds);
if (oldLayer.parent != null)
oldLayer.replaceWith(newContext._containerLayer);
return newContext;
}
PaintingContext.forTesting(this._canvas); PaintingContext.forTesting(this._canvas);
void _startRecording(Offset offset, Size size) { ContainerLayer _containerLayer;
assert(_layer == null); ContainerLayer get containerLayer => _containerLayer;
PictureLayer _currentLayer;
sky.PictureRecorder _recorder;
PaintingCanvas _canvas;
PaintingCanvas get canvas => _canvas; // Paint on this.
void _startRecording(Rect paintBounds) {
assert(_currentLayer == null);
assert(_recorder == null); assert(_recorder == null);
assert(_canvas == null); assert(_canvas == null);
_layer = new PictureLayer(offset: offset, size: size); _currentLayer = new PictureLayer(paintBounds: paintBounds);
_recorder = new sky.PictureRecorder(); _recorder = new sky.PictureRecorder();
_canvas = new PaintingCanvas(_recorder, Point.origin & size); _canvas = new PaintingCanvas(_recorder, paintBounds);
_containerLayer.add(_currentLayer);
} }
void endRecording() { void endRecording() {
assert(_layer != null); assert(_currentLayer != null);
assert(_recorder != null); assert(_recorder != null);
assert(_canvas != null); assert(_canvas != null);
_layer.picture = _recorder.endRecording(); _currentLayer.picture = _recorder.endRecording();
_layer = null; _currentLayer = null;
_recorder = null; _recorder = null;
_canvas = null; _canvas = null;
} }
bool debugCanPaintChild() { bool debugCanPaintChild(RenderObject child) {
// You need to use layers if you are applying transforms, clips, // You need to use layers if you are applying transforms, clips,
// or similar, to a child. To do so, use the paintChildWith*() // or similar, to a child. To do so, use the paintChildWith*()
// methods below. // methods below.
// (commented out for now because we haven't ported everything yet) // (commented out for now because we haven't ported everything yet)
// assert(canvas.getSaveCount() == 1); assert(canvas.getSaveCount() == 1 || !child.needsCompositing);
return true; return true;
} }
void paintChild(RenderObject child, Point point) { void paintChild(RenderObject child, Point childPosition) {
assert(debugCanPaintChild()); assert(debugCanPaintChild(child));
final Offset offset = point.toOffset(); final Offset childOffset = childPosition.toOffset();
if (!child.requiresCompositing) { if (!child.hasLayer) {
child._paintWithContext(this, offset); insertChild(child, childOffset);
} else {
compositeChild(child, childOffset: childOffset, parentLayer: _containerLayer);
}
}
// Below we have various variants of the paintChild() method, which
// do additional work, such as clipping or transforming, at the same
// time as painting the children.
// If none of the descendants require compositing, then these don't
// need to use a new layer, because at no point will any of the
// children introduce a new layer of their own. In that case, we
// just use regular canvas commands to do the work.
// If at least one of the descendants requires compositing, though,
// we introduce a new layer to do the work, so that when the
// children are split into a new layer, the work (e.g. clip) is not
// lost, as it would if we didn't introduce a new layer.
static final Paint _disableAntialias = new Paint()..isAntiAlias = false;
void paintChildWithClipRect(RenderObject child, Point childPosition, Rect clipRect) {
// clipRect is in the parent's coordinate space
assert(debugCanPaintChild(child));
final Offset childOffset = childPosition.toOffset();
if (!child.needsCompositing) {
canvas.save();
canvas.clipRect(clipRect);
insertChild(child, childOffset);
canvas.restore();
} else {
ClipRectLayer clipLayer = new ClipRectLayer(offset: childOffset, clipRect: clipRect);
_containerLayer.add(clipLayer);
compositeChild(child, parentLayer: clipLayer);
}
}
void paintChildWithClipRRect(RenderObject child, Point childPosition, Rect bounds, sky.RRect clipRRect) {
// clipRRect is in the parent's coordinate space
assert(debugCanPaintChild(child));
final Offset childOffset = childPosition.toOffset();
if (!child.needsCompositing) {
canvas.saveLayer(bounds, _disableAntialias);
canvas.clipRRect(clipRRect);
insertChild(child, childOffset);
canvas.restore();
} else {
ClipRRectLayer clipLayer = new ClipRRectLayer(offset: childOffset, bounds: bounds, clipRRect: clipRRect);
_containerLayer.add(clipLayer);
compositeChild(child, parentLayer: clipLayer);
}
}
void paintChildWithClipPath(RenderObject child, Point childPosition, Rect bounds, Path clipPath) {
// bounds and clipPath are in the parent's coordinate space
assert(debugCanPaintChild(child));
final Offset childOffset = childPosition.toOffset();
if (!child.needsCompositing) {
canvas.saveLayer(bounds, _disableAntialias);
canvas.clipPath(clipPath);
canvas.translate(childOffset.dx, childOffset.dy);
insertChild(child, Offset.zero);
canvas.restore();
} else { } else {
_compositeChild(child, offset, layer.parent, layer.nextSibling); ClipPathLayer clipLayer = new ClipPathLayer(offset: childOffset, bounds: bounds, clipPath: clipPath);
_containerLayer.add(clipLayer);
compositeChild(child, parentLayer: clipLayer);
} }
} }
void paintChildWithClip(RenderObject child, Point point, Rect clipRect) { void paintChildWithTransform(RenderObject child, Point childPosition, Matrix4 transform) {
assert(debugCanPaintChild()); assert(debugCanPaintChild(child));
final Offset offset = point.toOffset(); final Offset childOffset = childPosition.toOffset();
if (!child.hasCompositedDescendant) { if (!child.needsCompositing) {
// If none of the descendants require compositing, then we don't
// need to use a new layer here, because at no point will any of
// the children introduce a new layer of their own.
canvas.save(); canvas.save();
canvas.clipRect(clipRect.shift(offset)); canvas.translate(childOffset.dx, childOffset.dy);
child._paintWithContext(this, offset); canvas.concat(transform.storage);
insertChild(child, Offset.zero);
canvas.restore(); canvas.restore();
} else { } else {
// At least one of the descendants requires compositing. We TransformLayer transformLayer = new TransformLayer(offset: childOffset, transform: transform);
// therefore introduce a new layer to do the clipping, so that _containerLayer.add(transformLayer);
// when the children are split into a new layer, the clip is not compositeChild(child, parentLayer: transformLayer);
// lost, as it would if we didn't introduce a new layer.
ClipLayer clip = new ClipLayer(offset: offset, clipRect: clipRect);
layer.parent.add(clip, before: layer.nextSibling);
_compositeChild(child, Offset.zero, clip, null);
} }
} }
void _compositeChild(RenderObject child, Offset offset, ContainerLayer parentLayer, Layer nextSibling) { void paintChildWithPaint(RenderObject child, Point childPosition, Rect bounds, Paint paint) {
final PictureLayer originalLayer = _layer; assert(debugCanPaintChild(child));
final Offset childOffset = childPosition.toOffset();
if (!child.needsCompositing) {
canvas.saveLayer(bounds, paint);
canvas.translate(childOffset.dx, childOffset.dy);
insertChild(child, Offset.zero);
canvas.restore();
} else {
PaintLayer paintLayer = new PaintLayer(offset: childOffset, bounds: bounds, paintSettings: paint);
_containerLayer.add(paintLayer);
compositeChild(child, parentLayer: paintLayer);
}
}
// do not call directly
void insertChild(RenderObject child, Offset offset) {
child._paintWithContext(this, offset);
}
// do not call directly
void compositeChild(RenderObject child, { Offset childOffset: Offset.zero, ContainerLayer parentLayer }) {
// This ends the current layer and starts a new layer for the
// remainder of our rendering. It also creates a new layer for the
// child, and inserts that layer into the given parentLayer, which
// must either be our current layer's parent layer, or at least
// must have our current layer's parent layer as an ancestor.
final PictureLayer originalLayer = _currentLayer;
assert(() {
assert(parentLayer != null);
assert(originalLayer != null);
assert(originalLayer.parent != null);
ContainerLayer ancestor = parentLayer;
while (ancestor != null && ancestor != originalLayer.parent)
ancestor = ancestor.parent;
assert(ancestor == originalLayer.parent);
assert(originalLayer.parent == _containerLayer);
return true;
});
// End our current layer.
endRecording(); endRecording();
Rect childBounds = child.paintBounds; // Create a layer for our child, and paint the child into it.
Offset childOffset = childBounds.topLeft.toOffset(); if (child.needsPaint || !child.hasLayer) {
PaintingContext context = new PaintingContext(offset + childOffset, childBounds.size); PaintingContext newContext = new PaintingContext.withOffset(childOffset, child.paintBounds);
parentLayer.add(context.layer, before: nextSibling); child._layer = newContext.containerLayer;
child._layer = context.layer; child._paintWithContext(newContext, Offset.zero);
child._paintWithContext(context, -childOffset); newContext.endRecording();
} else {
assert(child._layer != null);
child._layer.detach();
child._layer.offset = childOffset;
}
parentLayer.add(child._layer);
_startRecording(originalLayer.offset, originalLayer.size); // Start a new layer for anything that remains of our own paint.
originalLayer.parent.add(layer, before: context.layer.nextSibling); _startRecording(originalLayer.paintBounds);
context.endRecording();
} }
} }
...@@ -163,7 +280,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -163,7 +280,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
setupParentData(child); setupParentData(child);
super.adoptChild(child); super.adoptChild(child);
markNeedsLayout(); markNeedsLayout();
markNeedsCompositingUpdate(); markNeedsCompositingBitsUpdate();
} }
void dropChild(RenderObject child) { // only for use by subclasses void dropChild(RenderObject child) { // only for use by subclasses
assert(debugCanPerformMutations); assert(debugCanPerformMutations);
...@@ -173,7 +290,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -173,7 +290,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
child.parentData.detach(); child.parentData.detach();
super.dropChild(child); super.dropChild(child);
markNeedsLayout(); markNeedsLayout();
markNeedsCompositingUpdate(); markNeedsCompositingBitsUpdate();
} }
// Override in subclasses with children and call the visitor for each child. // Override in subclasses with children and call the visitor for each child.
...@@ -254,7 +371,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -254,7 +371,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
void scheduleInitialLayout() { void scheduleInitialLayout() {
assert(attached); assert(attached);
assert(parent == null); assert(parent is! RenderObject);
assert(_relayoutSubtreeRoot == null); assert(_relayoutSubtreeRoot == null);
_relayoutSubtreeRoot = this; _relayoutSubtreeRoot = this;
assert(() { assert(() {
...@@ -412,71 +529,90 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -412,71 +529,90 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
static List<RenderObject> _nodesNeedingPaint = new List<RenderObject>(); static List<RenderObject> _nodesNeedingPaint = new List<RenderObject>();
PictureLayer _layer; // Override this in subclasses to indicate that instances of your
PictureLayer get layer { // class need to have their own Layer. For example, videos.
assert(requiresCompositing); bool get hasLayer => false;
return _layer;
}
bool get requiresCompositing => false;
bool _hasCompositedDescendant = false; ContainerLayer _layer;
bool get hasCompositedDescendant { ContainerLayer get layer {
assert(!_needsCompositingUpdate); assert(hasLayer);
return _hasCompositedDescendant; assert(!_needsPaint);
return _layer;
} }
bool _needsCompositingUpdate = false; // When the subtree is mutated, we need to recompute our
void markNeedsCompositingUpdate() { // "needsCompositing" bit, and our ancestors need to do the
if (_needsCompositingUpdate) // same (in case ours changed). adoptChild() and dropChild() thus
// call markNeedsCompositingBitsUpdate().
bool _needsCompositingBitsUpdate = true;
void markNeedsCompositingBitsUpdate() {
if (_needsCompositingBitsUpdate)
return; return;
_needsCompositingUpdate = true; _needsCompositingBitsUpdate = true;
final AbstractNode parent = this.parent; final AbstractNode parent = this.parent; // TODO(ianh): remove the once the analyzer is cleverer
if (parent is RenderObject) if (parent is RenderObject)
parent.markNeedsCompositingUpdate(); parent.markNeedsCompositingBitsUpdate();
} }
bool _needsCompositing = false;
void updateCompositing() { bool get needsCompositing {
if (!_needsCompositingUpdate) // needsCompositing is true if either we have a layer or one of our descendants has a layer
assert(!_needsCompositingBitsUpdate); // make sure we don't use this bit when it is dirty
return _needsCompositing;
}
void updateCompositingBits() {
if (!_needsCompositingBitsUpdate)
return; return;
bool didHaveCompositedDescendant = _needsCompositing;
visitChildren((RenderObject child) { visitChildren((RenderObject child) {
child.updateCompositing(); child.updateCompositingBits();
if (child.hasCompositedDescendant) if (child.needsCompositing)
_hasCompositedDescendant = true; _needsCompositing = true;
}); });
if (requiresCompositing) if (hasLayer)
_hasCompositedDescendant = true; _needsCompositing = true;
_needsCompositingUpdate = false; if (didHaveCompositedDescendant != _needsCompositing)
markNeedsPaint();
_needsCompositingBitsUpdate = false;
} }
bool _needsPaint = true; bool _needsPaint = true;
bool get needsPaint => _needsPaint; bool get needsPaint => _needsPaint;
void markNeedsPaint() { void markNeedsPaint() {
assert(!debugDoingPaint); assert(!debugDoingPaint);
if (!attached) return; // Don't try painting things that aren't in the hierarchy if (!attached) return; // Don't try painting things that aren't in the hierarchy
if (_needsPaint) return; if (_needsPaint) return;
if (requiresCompositing) { if (hasLayer) {
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layer != null);
_needsPaint = true; _needsPaint = true;
_nodesNeedingPaint.add(this); _nodesNeedingPaint.add(this);
scheduler.ensureVisualUpdate(); scheduler.ensureVisualUpdate();
} else if (parent is! RenderObject) { } else if (parent is RenderObject) {
// we're the root of the render tree (probably a RenderView) // We don't have our own layer; one of our ancestors will take
// care of updating the layer we're in and when they do that
// we'll get our paint() method called.
assert(_layer == null);
(parent as RenderObject).markNeedsPaint(); // TODO(ianh): remove the cast once the analyzer is cleverer
} else {
// If we're the root of the render tree (probably a RenderView),
// then we have to paint ourselves, since nobody else can paint
// us. We don't add ourselves to _nodesNeedingPaint in this
// case, because the root is always told to paint regardless.
_needsPaint = true; _needsPaint = true;
scheduler.ensureVisualUpdate(); scheduler.ensureVisualUpdate();
} else {
(parent as RenderObject).markNeedsPaint(); // TODO(ianh): remove the cast once the analyzer is cleverer
} }
} }
static void flushPaint() { static void flushPaint() {
sky.tracing.begin('RenderObject.flushPaint'); sky.tracing.begin('RenderObject.flushPaint');
_debugDoingPaint = true; _debugDoingPaint = true;
try { try {
List<RenderObject> dirtyNodes = _nodesNeedingPaint; List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = new List<RenderObject>(); _nodesNeedingPaint = new List<RenderObject>();
for (RenderObject node in dirtyNodes..sort((a, b) => a.depth - b.depth)) { // Sort the dirty nodes in reverse order (deepest first).
if (node._needsPaint && node.attached) for (RenderObject node in dirtyNodes..sort((a, b) => b.depth - a.depth)) {
assert(node._needsPaint);
if (node.attached)
node._repaint(); node._repaint();
}; };
assert(_nodesNeedingPaint.length == 0); assert(_nodesNeedingPaint.length == 0);
...@@ -485,33 +621,36 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -485,33 +621,36 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
sky.tracing.end('RenderObject.flushPaint'); sky.tracing.end('RenderObject.flushPaint');
} }
} }
void initialPaint(ContainerLayer rootLayer, Size size) {
assert(attached);
assert(parent is! RenderObject);
assert(!_debugDoingPaint);
assert(hasLayer);
PaintingContext newContext = new PaintingContext.withLayer(rootLayer, Point.origin & size);
_paintLayer(newContext);
}
void _repaint() { void _repaint() {
assert(!_needsLayout); assert(hasLayer);
assert(requiresCompositing);
assert(_layer != null); assert(_layer != null);
// TODO(abarth): Using _layer.offset isn't correct if the topLeft of our PaintingContext newContext = new PaintingContext.replacingLayer(_layer, paintBounds);
// paint bounds has changed since our last repaint. _paintLayer(newContext);
PaintingContext context = new PaintingContext(_layer.offset, paintBounds.size); }
_layer.parent.add(context.layer, before: _layer); void _paintLayer(PaintingContext context) {
_layer.detach(); _layer = context._containerLayer;
_layer = context._layer;
_needsPaint = false;
try { try {
_paintWithContext(context, Offset.zero); _paintWithContext(context, Offset.zero);
context.endRecording();
} catch (e) { } catch (e) {
print('Exception raised during _repaint:\n${e}\nContext:\n${this}'); print('Exception raised during _paintLayer:\n${e}\nContext:\n${this}');
if (inDebugBuild) if (inDebugBuild)
rethrow; rethrow;
return; return;
} }
assert(!_needsLayout); // check that the paint() method didn't mark us dirty again
assert(!_needsPaint); // check that the paint() method didn't mark us dirty again
} }
void _paintWithContext(PaintingContext context, Offset offset) { void _paintWithContext(PaintingContext context, Offset offset) {
_needsPaint = false;
assert(!_debugDoingThisPaint); assert(!_debugDoingThisPaint);
assert(!_needsLayout);
assert(!_needsCompositingBitsUpdate);
RenderObject debugLastActivePaint; RenderObject debugLastActivePaint;
assert(() { assert(() {
_debugDoingThisPaint = true; _debugDoingThisPaint = true;
...@@ -522,9 +661,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -522,9 +661,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
context.canvas.save(); context.canvas.save();
context.canvas.clipRect(paintBounds.shift(offset)); context.canvas.clipRect(paintBounds.shift(offset));
} }
assert(!hasLayer || _layer != null);
return true; return true;
}); });
_needsPaint = false;
paint(context, offset); paint(context, offset);
assert(!_needsLayout); // check that the paint() method didn't mark us dirty again
assert(!_needsPaint); // check that the paint() method didn't mark us dirty again
assert(() { assert(() {
if (debugPaintBoundsEnabled) if (debugPaintBoundsEnabled)
context.canvas.restore(); context.canvas.restore();
...@@ -532,7 +675,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -532,7 +675,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
_debugDoingThisPaint = false; _debugDoingThisPaint = false;
return true; return true;
}); });
assert(!_needsPaint);
} }
Rect get paintBounds; Rect get paintBounds;
......
...@@ -295,18 +295,12 @@ class RenderOpacity extends RenderProxyBox { ...@@ -295,18 +295,12 @@ class RenderOpacity extends RenderProxyBox {
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null) { if (child != null) {
int a = _alpha; int a = _alpha;
if (a == 0) if (a == 0)
return; return;
if (a == 255)
if (a == 255) {
context.paintChild(child, offset.toPoint()); context.paintChild(child, offset.toPoint());
return; else
} context.paintChildWithPaint(child, offset.toPoint(), null, _paint);
context.canvas.saveLayer(null, _paint); // TODO(abarth): layerize
context.paintChild(child, offset.toPoint());
context.canvas.restore();
} }
} }
} }
...@@ -349,11 +343,8 @@ class RenderColorFilter extends RenderProxyBox { ...@@ -349,11 +343,8 @@ class RenderColorFilter extends RenderProxyBox {
} }
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null) { if (child != null)
context.canvas.saveLayer(offset & size, _paint); // TODO(abarth): layerize context.paintChildWithPaint(child, offset.toPoint(), offset & size, _paint);
context.paintChild(child, offset.toPoint());
context.canvas.restore();
}
} }
} }
...@@ -362,7 +353,7 @@ class RenderClipRect extends RenderProxyBox { ...@@ -362,7 +353,7 @@ class RenderClipRect extends RenderProxyBox {
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null) if (child != null)
context.paintChildWithClip(child, offset.toPoint(), Offset.zero & size); context.paintChildWithClipRect(child, offset.toPoint(), offset & size);
} }
} }
...@@ -393,16 +384,11 @@ class RenderClipRRect extends RenderProxyBox { ...@@ -393,16 +384,11 @@ class RenderClipRRect extends RenderProxyBox {
markNeedsPaint(); markNeedsPaint();
} }
final Paint _paint = new Paint()..isAntiAlias = false;
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null) { if (child != null) {
Rect rect = offset & size; Rect rect = offset & size;
context.canvas.saveLayer(rect, _paint); // TODO(abarth): layerize
sky.RRect rrect = new sky.RRect()..setRectXY(rect, xRadius, yRadius); sky.RRect rrect = new sky.RRect()..setRectXY(rect, xRadius, yRadius);
context.canvas.clipRRect(rrect); context.paintChildWithClipRRect(child, offset.toPoint(), rect, rrect);
context.paintChild(child, offset.toPoint());
context.canvas.restore();
} }
} }
} }
...@@ -410,8 +396,6 @@ class RenderClipRRect extends RenderProxyBox { ...@@ -410,8 +396,6 @@ class RenderClipRRect extends RenderProxyBox {
class RenderClipOval extends RenderProxyBox { class RenderClipOval extends RenderProxyBox {
RenderClipOval({ RenderBox child }) : super(child); RenderClipOval({ RenderBox child }) : super(child);
final Paint _paint = new Paint()..isAntiAlias = false;
Rect _cachedRect; Rect _cachedRect;
Path _cachedPath; Path _cachedPath;
...@@ -426,10 +410,7 @@ class RenderClipOval extends RenderProxyBox { ...@@ -426,10 +410,7 @@ class RenderClipOval extends RenderProxyBox {
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null) { if (child != null) {
Rect rect = offset & size; Rect rect = offset & size;
context.canvas.saveLayer(rect, _paint); // TODO(abarth): layerize context.paintChildWithClipPath(child, offset.toPoint(), rect, _getPath(rect));
context.canvas.clipPath(_getPath(rect));
context.paintChild(child, offset.toPoint());
context.canvas.restore();
} }
} }
} }
...@@ -561,11 +542,8 @@ class RenderTransform extends RenderProxyBox { ...@@ -561,11 +542,8 @@ class RenderTransform extends RenderProxyBox {
} }
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
context.canvas.save(); if (child != null)
context.canvas.translate(offset.dx, offset.dy); context.paintChildWithTransform(child, offset.toPoint(), _transform);
context.canvas.concat(_transform.storage);
super.paint(context, Offset.zero);
context.canvas.restore();
} }
void applyPaintTransform(Matrix4 transform) { void applyPaintTransform(Matrix4 transform) {
......
...@@ -72,7 +72,7 @@ class SkyBinding { ...@@ -72,7 +72,7 @@ class SkyBinding {
} }
void beginFrame(double timeStamp) { void beginFrame(double timeStamp) {
RenderObject.flushLayout(); RenderObject.flushLayout();
_renderView.updateCompositing(); _renderView.updateCompositingBits();
RenderObject.flushPaint(); RenderObject.flushPaint();
_renderView.paintFrame(); _renderView.paintFrame();
_renderView.compositeFrame(); _renderView.compositeFrame();
......
...@@ -42,8 +42,6 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -42,8 +42,6 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
markNeedsLayout(); markNeedsLayout();
} }
ContainerLayer _rootLayer;
// We never call layout() on this class, so this should never get // We never call layout() on this class, so this should never get
// checked. (This class is laid out using scheduleInitialLayout().) // checked. (This class is laid out using scheduleInitialLayout().)
bool debugDoesMeetConstraints() { assert(false); return false; } bool debugDoesMeetConstraints() { assert(false); return false; }
...@@ -79,6 +77,8 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -79,6 +77,8 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
return true; return true;
} }
bool get hasLayer => true;
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null) if (child != null)
context.paintChild(child, offset.toPoint()); context.paintChild(child, offset.toPoint());
...@@ -88,12 +88,9 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -88,12 +88,9 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
sky.tracing.begin('RenderView.paintFrame'); sky.tracing.begin('RenderView.paintFrame');
try { try {
final double devicePixelRatio = sky.view.devicePixelRatio; final double devicePixelRatio = sky.view.devicePixelRatio;
Matrix4 transform = new Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0); Matrix4 logicalToDeviceZoom = new Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0);
_rootLayer = new TransformLayer(transform: transform); ContainerLayer rootLayer = new TransformLayer(transform: logicalToDeviceZoom);
PaintingContext context = new PaintingContext(Offset.zero, size); initialPaint(rootLayer, size);
_rootLayer.add(context.layer);
context.paintChild(child, Point.origin);
context.endRecording();
} finally { } finally {
sky.tracing.end('RenderView.paintFrame'); sky.tracing.end('RenderView.paintFrame');
} }
...@@ -102,9 +99,12 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -102,9 +99,12 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
void compositeFrame() { void compositeFrame() {
sky.tracing.begin('RenderView.compositeFrame'); sky.tracing.begin('RenderView.compositeFrame');
try { try {
// Eventually we will want to pass the entire layer tree to the C++ side.
// For now, however, we take the layer tree and paint it into a Canvas,
// which we then hand to the C++ side.
sky.PictureRecorder recorder = new sky.PictureRecorder(); sky.PictureRecorder recorder = new sky.PictureRecorder();
sky.Canvas canvas = new sky.Canvas(recorder, Point.origin & (size * sky.view.devicePixelRatio)); sky.Canvas canvas = new sky.Canvas(recorder, Point.origin & (size * sky.view.devicePixelRatio));
_rootLayer.paint(canvas); layer.paint(canvas);
sky.view.picture = recorder.endRecording(); sky.view.picture = recorder.endRecording();
} finally { } finally {
sky.tracing.end('RenderView.compositeFrame'); sky.tracing.end('RenderView.compositeFrame');
......
...@@ -122,13 +122,10 @@ class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox ...@@ -122,13 +122,10 @@ class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox
Offset roundedScrollOffset = _scrollOffsetRoundedToIntegerDevicePixels; Offset roundedScrollOffset = _scrollOffsetRoundedToIntegerDevicePixels;
bool _needsClip = offset < Offset.zero || bool _needsClip = offset < Offset.zero ||
!(offset & size).contains(((offset - roundedScrollOffset) & child.size).bottomRight); !(offset & size).contains(((offset - roundedScrollOffset) & child.size).bottomRight);
if (_needsClip) {
context.canvas.save();
context.canvas.clipRect(offset & size);
}
context.paintChild(child, (offset - roundedScrollOffset).toPoint());
if (_needsClip) if (_needsClip)
context.canvas.restore(); context.paintChildWithClipRect(child, (offset - roundedScrollOffset).toPoint(), offset & size);
else
context.paintChild(child, (offset - roundedScrollOffset).toPoint());
} }
} }
......
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