layer.dart 17 KB
Newer Older
1 2 3 4
// Copyright 2015 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.

5
import 'dart:ui' as ui show ImageFilter, Picture, SceneBuilder;
6
import 'dart:ui' show Offset;
7

8
import 'package:flutter/foundation.dart';
9
import 'package:flutter/painting.dart';
10
import 'package:vector_math/vector_math_64.dart';
11

12
import 'debug.dart';
13

14
/// A composited layer.
15 16 17 18
///
/// During painting, the render tree generates a tree of composited layers that
/// are uploaded into the engine and displayed by the compositor. This class is
/// the base class for all composited layers.
19
abstract class Layer {
20
  /// This layer's parent in the layer tree
21
  ContainerLayer get parent => _parent;
22
  ContainerLayer _parent;
23

24
  /// This layer's next sibling in the parent layer's child list
25
  Layer get nextSibling => _nextSibling;
26
  Layer _nextSibling;
27

28
  /// This layer's previous sibling in the parent layer's child list
29
  Layer get previousSibling => _previousSibling;
30
  Layer _previousSibling;
31

32
  /// Removes this layer from its parent layer's child list
33
  @mustCallSuper
34
  void detach() {
35
    _parent?._remove(this);
36
  }
37 38

  /// Replaces this layer with the given layer in the parent layer's child list
Hixie's avatar
Hixie committed
39 40 41 42 43 44 45 46 47 48 49
  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;
50 51 52 53 54 55 56
    assert(() {
      Layer node = this;
      while (node.parent != null)
        node = node.parent;
      assert(node != newLayer); // indicates we are about to create a cycle
      return true;
    });
Hixie's avatar
Hixie committed
57 58 59 60 61 62 63 64 65
    newLayer._parent = _parent;
    if (_parent._firstChild == this)
      _parent._firstChild = newLayer;
    if (_parent._lastChild == this)
      _parent._lastChild = newLayer;
    _nextSibling = null;
    _previousSibling = null;
    _parent = null;
  }
66

67
  /// Override this method to upload this layer to the engine
68 69 70
  ///
  /// The layerOffset is the accumulated offset of this layer's parent from the
  /// origin of the builder's coordinate system.
71
  void addToScene(ui.SceneBuilder builder, Offset layerOffset);
72

73
  @override
74 75
  String toString() => '$runtimeType';

76 77 78 79
  /// The object responsible for creating this layer.
  ///
  /// Defaults to the value of [RenderObject.debugCreator] for the render object
  /// that created this layer. Used in debug messages.
80
  dynamic debugCreator;
81

82
  /// Returns a string representation of this layer and its descendants.
83 84 85
  String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
    String result = '$prefixLineOne$this\n';
    final String childrenDescription = debugDescribeChildren(prefixOtherLines);
86 87 88 89
    final String descriptionPrefix = childrenDescription != '' ? '$prefixOtherLines \u2502 ' : '$prefixOtherLines   ';
    List<String> description = <String>[];
    debugFillDescription(description);
    result += description.map((String description) => "$descriptionPrefix$description\n").join();
90 91 92 93 94 95
    if (childrenDescription == '')
      result += '$prefixOtherLines\n';
    result += childrenDescription;
    return result;
  }

96
  /// Add additional information to the given description for use by [toStringDeep].
97 98
  @protected
  @mustCallSuper
99
  void debugFillDescription(List<String> description) {
100 101
    if (debugCreator != null)
      description.add('creator: $debugCreator');
102 103
  }

104
  /// Returns a description of this layer's children for use by [toStringDeep].
105
  @protected
106
  String debugDescribeChildren(String prefix) => '';
107 108
}

109
/// A composited layer containing a [Picture]
110
class PictureLayer extends Layer {
111 112 113
  /// The picture recorded for this layer
  ///
  /// The picture's coodinate system matches this layer's coodinate system
114
  ui.Picture picture;
115

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
  /// Hints that the painting in this layer is complex and would benefit from
  /// caching.
  ///
  /// If this hint is not set, the compositor will apply its own heuristics to
  /// decide whether the this layer is complex enough to benefit from caching.
  bool isComplexHint = false;

  /// Hints that the painting in this layer is likely to change next frame.
  ///
  /// This hint tells the compositor not to cache this layer because the cache
  /// will not be used in the future. If this hint is not set, the compositor
  /// will apply its own heuristics to decide whether this layer is likely to be
  /// reused in the future.
  bool willChangeHint = false;

131
  @override
132
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
133
    builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
134
  }
135 136
}

137
/// A layer that indicates to the compositor that it should display
138 139
/// certain performance statistics within it.
class PerformanceOverlayLayer extends Layer {
140
  /// Creates a layer that displays a performance overlay.
141
  PerformanceOverlayLayer({
142 143 144 145
    @required this.overlayRect,
    @required this.optionsMask,
    @required this.rasterizerThreshold,
    @required this.checkerboardRasterCacheImages,
146
  });
147

148
  /// The rectangle in this layer's coordinate system that the overlay should occupy.
149
  Rect overlayRect;
150

151 152
  /// The mask is created by shifting 1 by the index of the specific
  /// [PerformanceOverlayOption] to enable.
153 154
  final int optionsMask;

155 156 157
  /// The rasterizer threshold is an integer specifying the number of frame
  /// intervals that the rasterizer must miss before it decides that the frame
  /// is suitable for capturing an SkPicture trace for further analysis.
158
  final int rasterizerThreshold;
159

160 161 162 163 164 165 166 167 168 169 170 171 172
  /// Whether the raster cache should checkerboard cached entries.
  ///
  /// The compositor can sometimes decide to cache certain portions of the
  /// widget hierarchy. Such portions typically don't change often from frame to
  /// frame and are expensive to render. This can speed up overall rendering. However,
  /// there is certain upfront cost to constructing these cache entries. And, if
  /// the cache entries are not used very often, this cost may not be worth the
  /// speedup in rendering of subsequent frames. If the developer wants to be certain
  /// that populating the raster cache is not causing stutters, this option can be
  /// set. Depending on the observations made, hints can be provided to the compositor
  /// that aid it in making better decisions about caching.
  final bool checkerboardRasterCacheImages;

173
  @override
174
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
175
    assert(optionsMask != null);
176
    builder.addPerformanceOverlay(optionsMask, overlayRect.shift(layerOffset));
177
    builder.setRasterizerTracingThreshold(rasterizerThreshold);
178
    builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
179 180 181
  }
}

182
/// A composited layer that has a list of children
183
class ContainerLayer extends Layer {
184
  /// The first composited layer in this layer's child list
185
  Layer get firstChild => _firstChild;
186
  Layer _firstChild;
187

188
  /// The last composited layer in this layer's child list
189
  Layer get lastChild => _lastChild;
190
  Layer _lastChild;
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

  bool _debugUltimatePreviousSiblingOf(Layer child, { Layer equals }) {
    while (child._previousSibling != null) {
      assert(child._previousSibling != child);
      child = child._previousSibling;
    }
    return child == equals;
  }

  bool _debugUltimateNextSiblingOf(Layer child, { Layer equals }) {
    while (child._nextSibling != null) {
      assert(child._nextSibling != child);
      child = child._nextSibling;
    }
    return child == equals;
  }

208 209
  /// Adds the given layer to the end of this layer's child list
  void append(Layer child) {
210 211 212 213 214 215
    assert(child != this);
    assert(child != _firstChild);
    assert(child != _lastChild);
    assert(child._parent == null);
    assert(child._nextSibling == null);
    assert(child._previousSibling == null);
216 217 218 219 220 221 222
    assert(() {
      Layer node = this;
      while (node.parent != null)
        node = node.parent;
      assert(node != child); // indicates we are about to create a cycle
      return true;
    });
223
    child._parent = this;
224 225 226 227 228 229
    child._previousSibling = _lastChild;
    if (_lastChild != null)
      _lastChild._nextSibling = child;
    _lastChild = child;
    if (_firstChild == null)
      _firstChild = child;
230 231
  }

232
  void _remove(Layer child) {
Hixie's avatar
Hixie committed
233
    assert(child._parent == this);
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
    assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
    assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
    if (child._previousSibling == null) {
      assert(_firstChild == child);
      _firstChild = child._nextSibling;
    } else {
      child._previousSibling._nextSibling = child._nextSibling;
    }
    if (child._nextSibling == null) {
      assert(_lastChild == child);
      _lastChild = child._previousSibling;
    } else {
      child._nextSibling._previousSibling = child._previousSibling;
    }
    child._previousSibling = null;
    child._nextSibling = null;
    child._parent = null;
  }
Hixie's avatar
Hixie committed
252

253
  /// Removes all of this layer's children from its child list
254 255 256 257 258 259 260 261 262 263 264 265 266
  void removeAllChildren() {
    Layer child = _firstChild;
    while (child != null) {
      Layer next = child.nextSibling;
      child._previousSibling = null;
      child._nextSibling = null;
      child._parent = null;
      child = next;
    }
    _firstChild = null;
    _lastChild = null;
  }

267
  @override
268
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
269
    addChildrenToScene(builder, layerOffset);
Hixie's avatar
Hixie committed
270 271
  }

272
  /// Uploads all of this layer's children to the engine
273
  void addChildrenToScene(ui.SceneBuilder builder, Offset childOffset) {
274
    Layer child = _firstChild;
Hixie's avatar
Hixie committed
275
    while (child != null) {
276
      child.addToScene(builder, childOffset);
Hixie's avatar
Hixie committed
277 278 279
      child = child.nextSibling;
    }
  }
280

281
  @override
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
  String debugDescribeChildren(String prefix) {
    String result = '$prefix \u2502\n';
    if (_firstChild != null) {
      Layer child = _firstChild;
      int count = 1;
      while (child != _lastChild) {
        result += '${child.toStringDeep("$prefix \u251C\u2500child $count: ", "$prefix \u2502")}';
        count += 1;
        child = child._nextSibling;
      }
      if (child != null) {
        assert(child == _lastChild);
        result += '${child.toStringDeep("$prefix \u2514\u2500child $count: ", "$prefix  ")}';
      }
    }
    return result;
  }
Hixie's avatar
Hixie committed
299 300
}

301 302 303 304 305 306 307 308 309
/// A layer that is displayed at an offset from its parent layer.
///
/// Offset layers are key to efficient repainting because they are created by
/// repaint boundaries in the [RenderObject] tree (see
/// [RenderObject.isRepaintBoundary]). When a render object that is a repaint
/// boundary is asked to paint at given offset in a [PaintingContext], the
/// render object first checks whether it needs to repaint itself. If not, it
/// reuses its existing [OffsetLayer] (and its entire subtree) by mutating its
/// [offset] property, cutting off the paint walk.
310
class OffsetLayer extends ContainerLayer {
311 312 313
  /// Creates an offset layer.
  ///
  /// By default, [offset] is zero.
314 315 316 317 318
  OffsetLayer({ this.offset: Offset.zero });

  /// Offset from parent in the parent's coordinate system.
  Offset offset;

319
  @override
320 321 322 323
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    addChildrenToScene(builder, offset + layerOffset);
  }

324
  @override
325 326 327
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('offset: $offset');
328 329 330 331
  }
}


332
/// A composite layer that clips its children using a rectangle
Hixie's avatar
Hixie committed
333
class ClipRectLayer extends ContainerLayer {
334 335 336 337
  /// Creates a layer with a rectangular clip.
  ///
  /// The [clipRect] property must be non-null before the compositing phase of
  /// the pipeline.
338
  ClipRectLayer({ this.clipRect });
Hixie's avatar
Hixie committed
339

340
  /// The rectangle to clip in the parent's coordinate system
Hixie's avatar
Hixie committed
341 342
  Rect clipRect;

343
  @override
344
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
345 346
    builder.pushClipRect(clipRect.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
347
    builder.pop();
Hixie's avatar
Hixie committed
348 349
  }

350
  @override
351 352 353
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('clipRect: $clipRect');
354
  }
355
}
Hixie's avatar
Hixie committed
356

357
/// A composite layer that clips its children using a rounded rectangle
Hixie's avatar
Hixie committed
358
class ClipRRectLayer extends ContainerLayer {
359 360 361 362
  /// Creates a layer with a rounded-rectangular clip.
  ///
  /// The [clipRRect] property must be non-null before the compositing phase of
  /// the pipeline.
363
  ClipRRectLayer({ this.clipRRect });
364 365

  /// The rounded-rect to clip in the parent's coordinate system
366
  RRect clipRRect;
Hixie's avatar
Hixie committed
367

368
  @override
369
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
370 371
    builder.pushClipRRect(clipRRect.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
372
    builder.pop();
Hixie's avatar
Hixie committed
373
  }
374

375
  @override
376 377 378
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('clipRRect: $clipRRect');
379
  }
Hixie's avatar
Hixie committed
380 381
}

382
/// A composite layer that clips its children using a path
Hixie's avatar
Hixie committed
383
class ClipPathLayer extends ContainerLayer {
384 385 386 387
  /// Creates a layer with a path-based clip.
  ///
  /// The [clipPath] property must be non-null before the compositing phase of
  /// the pipeline.
388
  ClipPathLayer({ this.clipPath });
389 390

  /// The path to clip in the parent's coordinate system
Hixie's avatar
Hixie committed
391 392
  Path clipPath;

393
  @override
394
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
395 396
    builder.pushClipPath(clipPath.shift(layerOffset));
    addChildrenToScene(builder, layerOffset);
397
    builder.pop();
Hixie's avatar
Hixie committed
398
  }
399

400
  @override
401 402 403
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('clipPath: $clipPath');
404
  }
405 406
}

407
/// A composited layer that applies a transformation matrix to its children
408
class TransformLayer extends OffsetLayer {
409 410 411 412 413 414
  /// Creates a transform layer.
  ///
  /// The [transform] property must be non-null before the compositing phase of
  /// the pipeline.
  TransformLayer({
    this.transform
415
  });
416

417
  /// The matrix to apply
418
  Matrix4 transform;
419

420
  @override
421
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
422 423 424 425 426 427
    assert(offset == Offset.zero);
    Matrix4 effectiveTransform = transform;
    if (layerOffset != Offset.zero) {
      effectiveTransform = new Matrix4.translationValues(layerOffset.dx, layerOffset.dy, 0.0)
        ..multiply(transform);
    }
428
    builder.pushTransform(effectiveTransform.storage);
429 430
    addChildrenToScene(builder, Offset.zero);
    builder.pop();
431
  }
432

433
  @override
434 435 436 437
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('transform:');
    description.addAll(debugDescribeTransform(transform));
438
  }
439 440
}

441
/// A composited layer that makes its children partially transparent
442
class OpacityLayer extends ContainerLayer {
443 444 445 446
  /// Creates an opacity layer.
  ///
  /// The [alpha] property must be non-null before the compositing phase of
  /// the pipeline.
447
  OpacityLayer({ this.alpha });
448 449 450 451 452

  /// The amount to multiply into the alpha channel
  ///
  /// The opacity is expressed as an integer from 0 to 255, where 0 is fully
  /// transparent and 255 is fully opaque.
453 454
  int alpha;

455
  @override
456
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
Adam Barth's avatar
Adam Barth committed
457
    builder.pushOpacity(alpha);
458
    addChildrenToScene(builder, layerOffset);
459
    builder.pop();
460
  }
461

462
  @override
463 464 465
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('alpha: $alpha');
466
  }
467
}
468 469 470

/// A composited layer that applies a shader to hits children.
class ShaderMaskLayer extends ContainerLayer {
471 472
  /// Creates a shader mask layer.
  ///
473
  /// The [shader], [maskRect], and [blendMode] properties must be non-null
474
  /// before the compositing phase of the pipeline.
475
  ShaderMaskLayer({ this.shader, this.maskRect, this.blendMode });
476 477

  /// The shader to apply to the children.
478
  Shader shader;
479 480 481 482

  /// The size of the shader.
  Rect maskRect;

483 484
  /// The blend mode to apply when blending the shader with the children.
  BlendMode blendMode;
485

486
  @override
487
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
488
    builder.pushShaderMask(shader, maskRect.shift(layerOffset), blendMode);
489
    addChildrenToScene(builder, layerOffset);
490 491 492
    builder.pop();
  }

493
  @override
494 495 496 497
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('shader: $shader');
    description.add('maskRect: $maskRect');
498
    description.add('blendMode: $blendMode');
499 500
  }
}
501 502 503

/// A composited layer that applies a filter to the existing contents of the scene.
class BackdropFilterLayer extends ContainerLayer {
504 505 506 507
  /// Creates a backdrop filter layer.
  ///
  /// The [filter] property must be non-null before the compositing phase of the
  /// pipeline.
508 509 510 511 512 513 514 515 516 517 518 519
  BackdropFilterLayer({ this.filter });

  /// The filter to apply to the existing contents of the scene.
  ui.ImageFilter filter;

  @override
  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.pushBackdropFilter(filter);
    addChildrenToScene(builder, layerOffset);
    builder.pop();
  }
}