1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5part of engine; 6 7/// A layer to be composed into a scene. 8/// 9/// A layer is the lowest-level rendering primitive. It represents an atomic 10/// painting command. 11abstract class Layer implements ui.EngineLayer { 12 /// The layer that contains us as a child. 13 ContainerLayer parent; 14 15 /// An estimated rectangle that this layer will draw into. 16 ui.Rect paintBounds = ui.Rect.zero; 17 18 /// Whether or not this layer actually needs to be painted in the scene. 19 bool get needsPainting => !paintBounds.isEmpty; 20 21 /// Pre-process this layer before painting. 22 /// 23 /// In this step, we compute the estimated [paintBounds] as well as 24 /// apply heuristics to prepare the render cache for pictures that 25 /// should be cached. 26 void preroll(PrerollContext prerollContext, Matrix4 matrix); 27 28 /// Paint this layer into the scene. 29 void paint(PaintContext paintContext); 30} 31 32/// A context shared by all layers during the preroll pass. 33class PrerollContext { 34 /// A raster cache. Used to register candidates for caching. 35 final RasterCache rasterCache; 36 37 PrerollContext(this.rasterCache); 38} 39 40/// A context shared by all layers during the paint pass. 41class PaintContext { 42 /// The canvas to paint to. 43 final SkCanvas canvas; 44 45 /// A raster cache potentially containing pre-rendered pictures. 46 final RasterCache rasterCache; 47 48 PaintContext(this.canvas, this.rasterCache); 49} 50 51/// A layer that contains child layers. 52abstract class ContainerLayer extends Layer { 53 final List<Layer> _layers = <Layer>[]; 54 55 /// Register [child] as a child of this layer. 56 void add(Layer child) { 57 child.parent = this; 58 _layers.add(child); 59 } 60 61 @override 62 void preroll(PrerollContext prerollContext, Matrix4 matrix) { 63 paintBounds = prerollChildren(prerollContext, matrix); 64 } 65 66 /// Run [preroll] on all of the child layers. 67 /// 68 /// Returns a [Rect] that covers the paint bounds of all of the child layers. 69 /// If all of the child layers have empty paint bounds, then the returned 70 /// [Rect] is empty. 71 ui.Rect prerollChildren(PrerollContext context, Matrix4 childMatrix) { 72 ui.Rect childPaintBounds = ui.Rect.zero; 73 for (Layer layer in _layers) { 74 layer.preroll(context, childMatrix); 75 if (childPaintBounds.isEmpty) { 76 childPaintBounds = layer.paintBounds; 77 } else if (!layer.paintBounds.isEmpty) { 78 childPaintBounds = childPaintBounds.expandToInclude(layer.paintBounds); 79 } 80 } 81 return childPaintBounds; 82 } 83 84 /// Calls [paint] on all child layers that need painting. 85 void paintChildren(PaintContext context) { 86 assert(needsPainting); 87 88 for (Layer layer in _layers) { 89 if (layer.needsPainting) { 90 layer.paint(context); 91 } 92 } 93 } 94} 95 96/// A layer that clips its child layers by a given [Path]. 97class ClipPathLayer extends ContainerLayer { 98 /// The path used to clip child layers. 99 final ui.Path _clipPath; 100 101 ClipPathLayer(this._clipPath); 102 103 @override 104 void preroll(PrerollContext prerollContext, Matrix4 matrix) { 105 final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); 106 final ui.Rect clipBounds = _clipPath.getBounds(); 107 if (childPaintBounds.overlaps(clipBounds)) { 108 paintBounds = childPaintBounds.intersect(clipBounds); 109 } 110 } 111 112 @override 113 void paint(PaintContext paintContext) { 114 assert(needsPainting); 115 116 paintContext.canvas.save(); 117 paintContext.canvas.clipPath(_clipPath); 118 paintChildren(paintContext); 119 paintContext.canvas.restore(); 120 } 121} 122 123/// A layer that clips its child layers by a given [Rect]. 124class ClipRectLayer extends ContainerLayer { 125 /// The rectangle used to clip child layers. 126 final ui.Rect _clipRect; 127 128 ClipRectLayer(this._clipRect); 129 130 @override 131 void preroll(PrerollContext prerollContext, Matrix4 matrix) { 132 final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); 133 if (childPaintBounds.overlaps(_clipRect)) { 134 paintBounds = childPaintBounds.intersect(_clipRect); 135 } 136 } 137 138 @override 139 void paint(PaintContext paintContext) { 140 assert(needsPainting); 141 142 paintContext.canvas.save(); 143 paintContext.canvas.clipRect(_clipRect); 144 paintChildren(paintContext); 145 paintContext.canvas.restore(); 146 } 147} 148 149/// A layer that clips its child layers by a given [RRect]. 150class ClipRRectLayer extends ContainerLayer { 151 /// The rounded rectangle used to clip child layers. 152 final ui.RRect _clipRRect; 153 154 ClipRRectLayer(this._clipRRect); 155 156 @override 157 void preroll(PrerollContext prerollContext, Matrix4 matrix) { 158 final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); 159 if (childPaintBounds.overlaps(_clipRRect.outerRect)) { 160 paintBounds = childPaintBounds.intersect(_clipRRect.outerRect); 161 } 162 } 163 164 @override 165 void paint(PaintContext paintContext) { 166 assert(needsPainting); 167 168 paintContext.canvas.save(); 169 paintContext.canvas.clipRRect(_clipRRect); 170 paintChildren(paintContext); 171 paintContext.canvas.restore(); 172 } 173} 174 175/// A layer that transforms its child layers by the given transform matrix. 176class TransformLayer extends ContainerLayer 177 implements ui.OffsetEngineLayer, ui.TransformEngineLayer { 178 /// The matrix with which to transform the child layers. 179 final Matrix4 _transform; 180 181 TransformLayer(this._transform); 182 183 @override 184 void preroll(PrerollContext prerollContext, Matrix4 matrix) { 185 final Matrix4 childMatrix = matrix * _transform; 186 final ui.Rect childPaintBounds = 187 prerollChildren(prerollContext, childMatrix); 188 paintBounds = _transformRect(_transform, childPaintBounds); 189 } 190 191 /// Applies the given matrix as a perspective transform to the given point. 192 /// 193 /// This function assumes the given point has a z-coordinate of 0.0. The 194 /// z-coordinate of the result is ignored. 195 static ui.Offset _transformPoint(Matrix4 transform, ui.Offset point) { 196 final Vector3 position3 = Vector3(point.dx, point.dy, 0.0); 197 final Vector3 transformed3 = transform.perspectiveTransform(position3); 198 return ui.Offset(transformed3.x, transformed3.y); 199 } 200 201 /// Returns a rect that bounds the result of applying the given matrix as a 202 /// perspective transform to the given rect. 203 /// 204 /// This function assumes the given rect is in the plane with z equals 0.0. 205 /// The transformed rect is then projected back into the plane with z equals 206 /// 0.0 before computing its bounding rect. 207 static ui.Rect _transformRect(Matrix4 transform, ui.Rect rect) { 208 final ui.Offset point1 = _transformPoint(transform, rect.topLeft); 209 final ui.Offset point2 = _transformPoint(transform, rect.topRight); 210 final ui.Offset point3 = _transformPoint(transform, rect.bottomLeft); 211 final ui.Offset point4 = _transformPoint(transform, rect.bottomRight); 212 return ui.Rect.fromLTRB( 213 _min4(point1.dx, point2.dx, point3.dx, point4.dx), 214 _min4(point1.dy, point2.dy, point3.dy, point4.dy), 215 _max4(point1.dx, point2.dx, point3.dx, point4.dx), 216 _max4(point1.dy, point2.dy, point3.dy, point4.dy)); 217 } 218 219 static double _min4(double a, double b, double c, double d) { 220 return math.min(a, math.min(b, math.min(c, d))); 221 } 222 223 static double _max4(double a, double b, double c, double d) { 224 return math.max(a, math.max(b, math.max(c, d))); 225 } 226 227 @override 228 void paint(PaintContext paintContext) { 229 assert(needsPainting); 230 231 paintContext.canvas.save(); 232 paintContext.canvas.transform(_transform.storage); 233 paintChildren(paintContext); 234 paintContext.canvas.restore(); 235 } 236} 237 238/// A layer containing a [Picture]. 239class PictureLayer extends Layer { 240 /// The picture to paint into the canvas. 241 final ui.Picture picture; 242 243 /// The offset at which to paint the picture. 244 final ui.Offset offset; 245 246 /// A hint to the compositor about whether this picture is complex. 247 final bool isComplex; 248 249 /// A hint to the compositor that this picture is likely to change. 250 final bool willChange; 251 252 PictureLayer(this.picture, this.offset, this.isComplex, this.willChange); 253 254 @override 255 void preroll(PrerollContext prerollContext, Matrix4 matrix) { 256 final RasterCache cache = prerollContext.rasterCache; 257 if (cache != null) { 258 final Matrix4 translateMatrix = Matrix4.identity() 259 ..setTranslationRaw(offset.dx, offset.dy, 0); 260 final Matrix4 cacheMatrix = translateMatrix * matrix; 261 cache.prepare(picture, cacheMatrix, isComplex, willChange); 262 } 263 264 paintBounds = picture.cullRect.shift(offset); 265 } 266 267 @override 268 void paint(PaintContext paintContext) { 269 assert(picture != null); 270 assert(needsPainting); 271 272 paintContext.canvas.save(); 273 paintContext.canvas.translate(offset.dx, offset.dy); 274 275 if (paintContext.rasterCache != null) { 276 final Matrix4 cacheMatrix = paintContext.canvas.currentTransform; 277 final RasterCacheResult result = 278 paintContext.rasterCache.get(picture, cacheMatrix); 279 if (result.isValid) { 280 result.draw(paintContext.canvas); 281 return; 282 } 283 } 284 paintContext.canvas.drawPicture(picture); 285 paintContext.canvas.restore(); 286 } 287} 288 289/// A layer representing a physical shape. 290/// 291/// The shape clips its children to a given [Path], and casts a shadow based 292/// on the given elevation. 293class PhysicalShapeLayer extends ContainerLayer 294 implements ui.PhysicalShapeEngineLayer { 295 final double _elevation; 296 final ui.Color _color; 297 final ui.Color _shadowColor; 298 final ui.Path _path; 299 final ui.Clip _clipBehavior; 300 301 PhysicalShapeLayer( 302 this._elevation, 303 this._color, 304 this._shadowColor, 305 this._path, 306 this._clipBehavior, 307 ); 308 309 @override 310 void preroll(PrerollContext prerollContext, Matrix4 matrix) { 311 prerollChildren(prerollContext, matrix); 312 paintBounds = 313 ElevationShadow.computeShadowRect(_path.getBounds(), _elevation); 314 } 315 316 @override 317 void paint(PaintContext paintContext) { 318 assert(needsPainting); 319 320 if (_elevation != 0) { 321 drawShadow(paintContext.canvas, _path, _shadowColor, _elevation, 322 _color.alpha != 0xff); 323 } 324 325 final ui.Paint paint = ui.Paint()..color = _color; 326 if (_clipBehavior != ui.Clip.antiAliasWithSaveLayer) { 327 paintContext.canvas.drawPath(_path, paint); 328 } 329 330 final int saveCount = paintContext.canvas.save(); 331 switch (_clipBehavior) { 332 case ui.Clip.hardEdge: 333 paintContext.canvas.clipPath(_path); 334 break; 335 case ui.Clip.antiAlias: 336 // TODO(het): This is supposed to be different from Clip.hardEdge in 337 // that it anti-aliases the clip. The canvas clipPath() method 338 // should support this. 339 paintContext.canvas.clipPath(_path); 340 break; 341 case ui.Clip.antiAliasWithSaveLayer: 342 paintContext.canvas.clipPath(_path); 343 paintContext.canvas.saveLayer(paintBounds, null); 344 break; 345 case ui.Clip.none: 346 break; 347 } 348 349 if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { 350 // If we want to avoid the bleeding edge artifact 351 // (https://github.com/flutter/flutter/issues/18057#issue-328003931) 352 // using saveLayer, we have to call drawPaint instead of drawPath as 353 // anti-aliased drawPath will always have such artifacts. 354 paintContext.canvas.drawPaint(paint); 355 } 356 357 paintChildren(paintContext); 358 359 paintContext.canvas.restoreToCount(saveCount); 360 } 361 362 /// Draws a shadow on the given [canvas] for the given [path]. 363 /// 364 /// The blur of the shadow is decided by the [elevation], and the 365 /// shadow is painted with the given [color]. 366 static void drawShadow(SkCanvas canvas, ui.Path path, ui.Color color, 367 double elevation, bool transparentOccluder) { 368 canvas.drawShadow(path, color, elevation, transparentOccluder); 369 } 370} 371