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 ui; 6 7/// An opaque object representing a composited scene. 8/// 9/// To create a Scene object, use a [SceneBuilder]. 10/// 11/// Scene objects can be displayed on the screen using the 12/// [Window.render] method. 13class Scene { 14 /// This class is created by the engine, and should not be instantiated 15 /// or extended directly. 16 /// 17 /// To create a Scene object, use a [SceneBuilder]. 18 Scene._(this.webOnlyRootElement); 19 20 final html.Element webOnlyRootElement; 21 22 /// Creates a raster image representation of the current state of the scene. 23 /// This is a slow operation that is performed on a background thread. 24 Future<Image> toImage(int width, int height) { 25 if (width <= 0 || height <= 0) { 26 throw Exception('Invalid image dimensions.'); 27 } 28 throw UnsupportedError('toImage is not supported on the Web'); 29 // TODO(flutter_web): Implement [_toImage]. 30 // return futurize( 31 // (Callback<Image> callback) => _toImage(width, height, callback)); 32 } 33 34 // String _toImage(int width, int height, Callback<Image> callback) => null; 35 36 /// Releases the resources used by this scene. 37 /// 38 /// After calling this function, the scene is cannot be used further. 39 void dispose() {} 40} 41 42/// An opaque handle to a transform engine layer. 43/// 44/// Instances of this class are created by [SceneBuilder.pushTransform]. 45/// 46/// {@template dart.ui.sceneBuilder.oldLayerCompatibility} 47/// `oldLayer` parameter in [SceneBuilder] methods only accepts objects created 48/// by the engine. [SceneBuilder] will throw an [AssertionError] if you pass it 49/// a custom implementation of this class. 50/// {@endtemplate} 51abstract class TransformEngineLayer implements EngineLayer {} 52 53/// An opaque handle to an offset engine layer. 54/// 55/// Instances of this class are created by [SceneBuilder.pushOffset]. 56/// 57/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 58abstract class OffsetEngineLayer implements EngineLayer {} 59 60/// An opaque handle to a clip rect engine layer. 61/// 62/// Instances of this class are created by [SceneBuilder.pushClipRect]. 63/// 64/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 65abstract class ClipRectEngineLayer implements EngineLayer {} 66 67/// An opaque handle to a clip rounded rect engine layer. 68/// 69/// Instances of this class are created by [SceneBuilder.pushClipRRect]. 70/// 71/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 72abstract class ClipRRectEngineLayer implements EngineLayer {} 73 74/// An opaque handle to a clip path engine layer. 75/// 76/// Instances of this class are created by [SceneBuilder.pushClipPath]. 77/// 78/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 79abstract class ClipPathEngineLayer implements EngineLayer {} 80 81/// An opaque handle to an opacity engine layer. 82/// 83/// Instances of this class are created by [SceneBuilder.pushOpacity]. 84/// 85/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 86abstract class OpacityEngineLayer implements EngineLayer {} 87 88/// An opaque handle to a color filter engine layer. 89/// 90/// Instances of this class are created by [SceneBuilder.pushColorFilter]. 91/// 92/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 93abstract class ColorFilterEngineLayer implements EngineLayer {} 94 95/// An opaque handle to a backdrop filter engine layer. 96/// 97/// Instances of this class are created by [SceneBuilder.pushBackdropFilter]. 98/// 99/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 100abstract class BackdropFilterEngineLayer implements EngineLayer {} 101 102/// An opaque handle to a shader mask engine layer. 103/// 104/// Instances of this class are created by [SceneBuilder.pushShaderMask]. 105/// 106/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 107abstract class ShaderMaskEngineLayer implements EngineLayer {} 108 109/// An opaque handle to a physical shape engine layer. 110/// 111/// Instances of this class are created by [SceneBuilder.pushPhysicalShape]. 112/// 113/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} 114abstract class PhysicalShapeEngineLayer implements EngineLayer {} 115 116/// Builds a [Scene] containing the given visuals. 117/// 118/// A [Scene] can then be rendered using [Window.render]. 119/// 120/// To draw graphical operations onto a [Scene], first create a 121/// [Picture] using a [PictureRecorder] and a [Canvas], and then add 122/// it to the scene using [addPicture]. 123class SceneBuilder { 124 /// Creates an empty [SceneBuilder] object. 125 factory SceneBuilder() { 126 if (engine.experimentalUseSkia) { 127 return engine.LayerSceneBuilder(); 128 } else { 129 return SceneBuilder._(); 130 } 131 } 132 133 SceneBuilder._() { 134 _surfaceStack.add(engine.PersistedScene(_lastFrameScene)); 135 } 136 137 factory SceneBuilder.layer() = engine.LayerSceneBuilder; 138 139 final List<engine.PersistedContainerSurface> _surfaceStack = 140 <engine.PersistedContainerSurface>[]; 141 142 /// The scene built by this scene builder. 143 /// 144 /// This getter should only be called after all surfaces are built. 145 engine.PersistedScene get _persistedScene { 146 assert(() { 147 if (_surfaceStack.length != 1) { 148 final String surfacePrintout = _surfaceStack 149 .map<Type>((engine.PersistedContainerSurface surface) => 150 surface.runtimeType) 151 .toList() 152 .join(', '); 153 throw Exception('Incorrect sequence of push/pop operations while ' 154 'building scene surfaces. After building the scene the persisted ' 155 'surface stack must contain a single element which corresponds ' 156 'to the scene itself (_PersistedScene). All other surfaces ' 157 'should have been popped off the stack. Found the following ' 158 'surfaces in the stack:\n$surfacePrintout'); 159 } 160 return true; 161 }()); 162 return _surfaceStack.first; 163 } 164 165 /// The surface currently being built. 166 engine.PersistedContainerSurface get _currentSurface => _surfaceStack.last; 167 168 EngineLayer _pushSurface(engine.PersistedContainerSurface surface) { 169 // Only attempt to update if the update is requested and the surface is in 170 // the live tree. 171 if (surface.oldLayer != null) { 172 assert(surface.oldLayer.runtimeType == surface.runtimeType); 173 assert(surface.oldLayer.isActive); 174 surface.oldLayer.state = engine.PersistedSurfaceState.pendingUpdate; 175 } 176 _adoptSurface(surface); 177 _surfaceStack.add(surface); 178 return surface; 179 } 180 181 void _addSurface(engine.PersistedSurface surface) { 182 _adoptSurface(surface); 183 } 184 185 void _adoptSurface(engine.PersistedSurface surface) { 186 _currentSurface.appendChild(surface); 187 } 188 189 /// Pushes an offset operation onto the operation stack. 190 /// 191 /// This is equivalent to [pushTransform] with a matrix with only translation. 192 /// 193 /// See [pop] for details about the operation stack. 194 OffsetEngineLayer pushOffset(double dx, double dy, 195 {OffsetEngineLayer oldLayer}) { 196 return _pushSurface(engine.PersistedOffset(oldLayer, dx, dy)); 197 } 198 199 /// Pushes a transform operation onto the operation stack. 200 /// 201 /// The objects are transformed by the given matrix before rasterization. 202 /// 203 /// See [pop] for details about the operation stack. 204 TransformEngineLayer pushTransform(Float64List matrix4, 205 {TransformEngineLayer oldLayer}) { 206 if (matrix4 == null) { 207 throw ArgumentError('"matrix4" argument cannot be null'); 208 } 209 if (matrix4.length != 16) { 210 throw ArgumentError('"matrix4" must have 16 entries.'); 211 } 212 return _pushSurface(engine.PersistedTransform(oldLayer, matrix4)); 213 } 214 215 /// Pushes a rectangular clip operation onto the operation stack. 216 /// 217 /// Rasterization outside the given rectangle is discarded. 218 /// 219 /// See [pop] for details about the operation stack, and [Clip] for different clip modes. 220 /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]). 221 ClipRectEngineLayer pushClipRect(Rect rect, 222 {Clip clipBehavior = Clip.antiAlias, ClipRectEngineLayer oldLayer}) { 223 assert(clipBehavior != null); 224 assert(clipBehavior != Clip.none); 225 return _pushSurface(engine.PersistedClipRect(oldLayer, rect)); 226 } 227 228 /// Pushes a rounded-rectangular clip operation onto the operation stack. 229 /// 230 /// Rasterization outside the given rounded rectangle is discarded. 231 /// 232 /// See [pop] for details about the operation stack. 233 ClipRRectEngineLayer pushClipRRect(RRect rrect, 234 {Clip clipBehavior, ClipRRectEngineLayer oldLayer}) { 235 return _pushSurface( 236 engine.PersistedClipRRect(oldLayer, rrect, clipBehavior)); 237 } 238 239 /// Pushes a path clip operation onto the operation stack. 240 /// 241 /// Rasterization outside the given path is discarded. 242 /// 243 /// See [pop] for details about the operation stack. 244 ClipPathEngineLayer pushClipPath(Path path, 245 {Clip clipBehavior = Clip.antiAlias, ClipPathEngineLayer oldLayer}) { 246 assert(clipBehavior != null); 247 assert(clipBehavior != Clip.none); 248 return _pushSurface(engine.PersistedClipPath(oldLayer, path, clipBehavior)); 249 } 250 251 /// Pushes an opacity operation onto the operation stack. 252 /// 253 /// The given alpha value is blended into the alpha value of the objects' 254 /// rasterization. An alpha value of 0 makes the objects entirely invisible. 255 /// An alpha value of 255 has no effect (i.e., the objects retain the current 256 /// opacity). 257 /// 258 /// See [pop] for details about the operation stack. 259 OpacityEngineLayer pushOpacity(int alpha, 260 {Offset offset = Offset.zero, OpacityEngineLayer oldLayer}) { 261 return _pushSurface(engine.PersistedOpacity(oldLayer, alpha, offset)); 262 } 263 264 /// Pushes a color filter operation onto the operation stack. 265 /// 266 /// The given color is applied to the objects' rasterization using the given 267 /// blend mode. 268 /// 269 /// See [pop] for details about the operation stack. 270 ColorFilterEngineLayer pushColorFilter(ColorFilter filter, 271 {ColorFilterEngineLayer oldLayer}) { 272 throw UnimplementedError(); 273 } 274 275 /// Pushes a backdrop filter operation onto the operation stack. 276 /// 277 /// The given filter is applied to the current contents of the scene prior to 278 /// rasterizing the given objects. 279 /// 280 /// See [pop] for details about the operation stack. 281 BackdropFilterEngineLayer pushBackdropFilter(ImageFilter filter, 282 {BackdropFilterEngineLayer oldLayer}) { 283 return _pushSurface(engine.PersistedBackdropFilter(oldLayer, filter)); 284 } 285 286 /// Pushes a shader mask operation onto the operation stack. 287 /// 288 /// The given shader is applied to the object's rasterization in the given 289 /// rectangle using the given blend mode. 290 /// 291 /// See [pop] for details about the operation stack. 292 ShaderMaskEngineLayer pushShaderMask( 293 Shader shader, Rect maskRect, BlendMode blendMode, 294 {ShaderMaskEngineLayer oldLayer}) { 295 throw UnimplementedError(); 296 } 297 298 /// Pushes a physical layer operation for an arbitrary shape onto the 299 /// operation stack. 300 /// 301 /// By default, the layer's content will not be clipped (clip = [Clip.none]). 302 /// If clip equals [Clip.hardEdge], [Clip.antiAlias], or [Clip.antiAliasWithSaveLayer], 303 /// then the content is clipped to the given shape defined by [path]. 304 /// 305 /// If [elevation] is greater than 0.0, then a shadow is drawn around the layer. 306 /// [shadowColor] defines the color of the shadow if present and [color] defines the 307 /// color of the layer background. 308 /// 309 /// See [pop] for details about the operation stack, and [Clip] for different clip modes. 310 PhysicalShapeEngineLayer pushPhysicalShape({ 311 Path path, 312 double elevation, 313 Color color, 314 Color shadowColor, 315 Clip clipBehavior = Clip.none, 316 PhysicalShapeEngineLayer oldLayer, 317 }) { 318 return _pushSurface(engine.PersistedPhysicalShape( 319 oldLayer, 320 path, 321 elevation, 322 color.value, 323 shadowColor?.value ?? 0xFF000000, 324 clipBehavior, 325 )); 326 } 327 328 /// Add a retained engine layer subtree from previous frames. 329 /// 330 /// All the engine layers that are in the subtree of the retained layer will 331 /// be automatically appended to the current engine layer tree. 332 /// 333 /// Therefore, when implementing a subclass of the [Layer] concept defined in 334 /// the rendering layer of Flutter's framework, once this is called, there's 335 /// no need to call [addToScene] for its children layers. 336 void addRetained(EngineLayer retainedLayer) { 337 final engine.PersistedContainerSurface retainedSurface = retainedLayer; 338 assert(retainedSurface.isActive || retainedSurface.isReleased); 339 retainedSurface.tryRetain(); 340 _adoptSurface(retainedSurface); 341 } 342 343 /// Ends the effect of the most recently pushed operation. 344 /// 345 /// Internally the scene builder maintains a stack of operations. Each of the 346 /// operations in the stack applies to each of the objects added to the scene. 347 /// Calling this function removes the most recently added operation from the 348 /// stack. 349 void pop() { 350 assert(_surfaceStack.isNotEmpty); 351 _surfaceStack.removeLast(); 352 } 353 354 /// Adds an object to the scene that displays performance statistics. 355 /// 356 /// Useful during development to assess the performance of the application. 357 /// The enabledOptions controls which statistics are displayed. The bounds 358 /// controls where the statistics are displayed. 359 /// 360 /// enabledOptions is a bit field with the following bits defined: 361 /// - 0x01: displayRasterizerStatistics - show GPU thread frame time 362 /// - 0x02: visualizeRasterizerStatistics - graph GPU thread frame times 363 /// - 0x04: displayEngineStatistics - show UI thread frame time 364 /// - 0x08: visualizeEngineStatistics - graph UI thread frame times 365 /// Set enabledOptions to 0x0F to enable all the currently defined features. 366 /// 367 /// The "UI thread" is the thread that includes all the execution of 368 /// the main Dart isolate (the isolate that can call 369 /// [Window.render]). The UI thread frame time is the total time 370 /// spent executing the [Window.onBeginFrame] callback. The "GPU 371 /// thread" is the thread (running on the CPU) that subsequently 372 /// processes the [Scene] provided by the Dart code to turn it into 373 /// GPU commands and send it to the GPU. 374 /// 375 /// See also the [PerformanceOverlayOption] enum in the rendering library. 376 /// for more details. 377 void addPerformanceOverlay(int enabledOptions, Rect bounds) { 378 _addPerformanceOverlay( 379 enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom); 380 } 381 382 /// Whether we've already warned the user about the lack of the performance 383 /// overlay or not. 384 /// 385 /// We use this to avoid spamming the console with redundant warning messages. 386 static bool _webOnlyDidWarnAboutPerformanceOverlay = false; 387 388 void _addPerformanceOverlay(int enabledOptions, double left, double right, 389 double top, double bottom) { 390 if (!_webOnlyDidWarnAboutPerformanceOverlay) { 391 _webOnlyDidWarnAboutPerformanceOverlay = true; 392 html.window.console 393 .warn('The performance overlay isn\'t supported on the web'); 394 } 395 } 396 397 /// Adds a [Picture] to the scene. 398 /// 399 /// The picture is rasterized at the given offset. 400 void addPicture( 401 Offset offset, 402 Picture picture, { 403 bool isComplexHint = false, 404 bool willChangeHint = false, 405 }) { 406 int hints = 0; 407 if (isComplexHint) { 408 hints |= 1; 409 } 410 if (willChangeHint) { 411 hints |= 2; 412 } 413 _addSurface( 414 engine.persistedPictureFactory(offset.dx, offset.dy, picture, hints)); 415 } 416 417 /// Adds a backend texture to the scene. 418 /// 419 /// The texture is scaled to the given size and rasterized at the given 420 /// offset. 421 void addTexture(int textureId, 422 {Offset offset = Offset.zero, 423 double width = 0.0, 424 double height = 0.0, 425 bool freeze = false}) { 426 assert(offset != null, 'Offset argument was null'); 427 _addTexture(offset.dx, offset.dy, width, height, textureId); 428 } 429 430 void _addTexture( 431 double dx, double dy, double width, double height, int textureId) { 432 // In test mode, allow this to be a no-op. 433 if (!debugEmulateFlutterTesterEnvironment) { 434 throw UnimplementedError('Textures are not supported in Flutter Web'); 435 } 436 } 437 438 /// Adds a platform view (e.g an iOS UIView) to the scene. 439 /// 440 /// Only supported on iOS, this is currently a no-op on other platforms. 441 /// 442 /// On iOS this layer splits the current output surface into two surfaces, one for the scene nodes 443 /// preceding the platform view, and one for the scene nodes following the platform view. 444 /// 445 /// ## Performance impact 446 /// 447 /// Adding an additional surface doubles the amount of graphics memory directly used by Flutter 448 /// for output buffers. Quartz might allocated extra buffers for compositing the Flutter surfaces 449 /// and the platform view. 450 /// 451 /// With a platform view in the scene, Quartz has to composite the two Flutter surfaces and the 452 /// embedded UIView. In addition to that, on iOS versions greater than 9, the Flutter frames are 453 /// synchronized with the UIView frames adding additional performance overhead. 454 void addPlatformView( 455 int viewId, { 456 Offset offset = Offset.zero, 457 double width = 0.0, 458 double height = 0.0, 459 }) { 460 assert(offset != null, 'Offset argument was null'); 461 _addPlatformView(offset.dx, offset.dy, width, height, viewId); 462 } 463 464 void _addPlatformView( 465 double dx, 466 double dy, 467 double width, 468 double height, 469 int viewId, 470 ) { 471 _addSurface(engine.PersistedPlatformView(viewId, dx, dy, width, height)); 472 } 473 474 /// (Fuchsia-only) Adds a scene rendered by another application to the scene 475 /// for this application. 476 void addChildScene( 477 {Offset offset = Offset.zero, 478 double width = 0.0, 479 double height = 0.0, 480 SceneHost sceneHost, 481 bool hitTestable = true}) { 482 _addChildScene(offset.dx, offset.dy, width, height, sceneHost, hitTestable); 483 } 484 485 void _addChildScene(double dx, double dy, double width, double height, 486 SceneHost sceneHost, bool hitTestable) { 487 throw UnimplementedError(); 488 } 489 490 /// Sets a threshold after which additional debugging information should be 491 /// recorded. 492 /// 493 /// Currently this interface is difficult to use by end-developers. If you're 494 /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev). 495 /// We'll hopefully be able to figure out how to make this feature more useful 496 /// to you. 497 void setRasterizerTracingThreshold(int frameInterval) {} 498 499 /// Sets whether the raster cache should checkerboard cached entries. This is 500 /// only useful for debugging purposes. 501 /// 502 /// The compositor can sometimes decide to cache certain portions of the 503 /// widget hierarchy. Such portions typically don't change often from frame to 504 /// frame and are expensive to render. This can speed up overall rendering. 505 /// However, there is certain upfront cost to constructing these cache 506 /// entries. And, if the cache entries are not used very often, this cost may 507 /// not be worth the speedup in rendering of subsequent frames. If the 508 /// developer wants to be certain that populating the raster cache is not 509 /// causing stutters, this option can be set. Depending on the observations 510 /// made, hints can be provided to the compositor that aid it in making better 511 /// decisions about caching. 512 /// 513 /// Currently this interface is difficult to use by end-developers. If you're 514 /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev). 515 void setCheckerboardRasterCacheImages(bool checkerboard) {} 516 517 /// Sets whether the compositor should checkerboard layers that are rendered 518 /// to offscreen bitmaps. 519 /// 520 /// This is only useful for debugging purposes. 521 void setCheckerboardOffscreenLayers(bool checkerboard) {} 522 523 /// The scene recorded in the last frame. 524 /// 525 /// This is a surface tree that holds onto the DOM elements that can be reused 526 /// on the next frame. 527 static engine.PersistedScene _lastFrameScene; 528 529 /// Returns the computed persisted scene graph recorded in the last frame. 530 /// 531 /// This is only available in debug mode. It returns `null` in profile and 532 /// release modes. 533 static engine.PersistedScene get debugLastFrameScene { 534 engine.PersistedScene result; 535 assert(() { 536 result = _lastFrameScene; 537 return true; 538 }()); 539 return result; 540 } 541 542 /// Discards information about previously rendered frames, including DOM 543 /// elements and cached canvases. 544 /// 545 /// After calling this function new canvases will be created for the 546 /// subsequent scene. This is useful when tests need predictable canvas 547 /// sizes. If the cache is not cleared, then canvases allocated in one test 548 /// may be reused in another test. 549 static void debugForgetFrameScene() { 550 _lastFrameScene?.rootElement?.remove(); 551 _lastFrameScene = null; 552 engine.debugForgetFrameScene(); 553 } 554 555 /// Finishes building the scene. 556 /// 557 /// Returns a [Scene] containing the objects that have been added to 558 /// this scene builder. The [Scene] can then be displayed on the 559 /// screen with [Window.render]. 560 /// 561 /// After calling this function, the scene builder object is invalid and 562 /// cannot be used further. 563 Scene build() { 564 _persistedScene.preroll(); 565 if (_lastFrameScene == null) { 566 _persistedScene.build(); 567 } else { 568 _persistedScene.update(_lastFrameScene); 569 } 570 engine.commitScene(_persistedScene); 571 _lastFrameScene = _persistedScene; 572 return Scene._(_persistedScene.rootElement); 573 } 574 575 /// Set properties on the linked scene. These properties include its bounds, 576 /// as well as whether it can be the target of focus events or not. 577 void setProperties(double width, double height, double insetTop, 578 double insetRight, double insetBottom, double insetLeft, bool focusable) { 579 throw UnimplementedError(); 580 } 581} 582 583/// A handle for the framework to hold and retain an engine layer across frames. 584class EngineLayer {} 585 586//// (Fuchsia-only) Hosts content provided by another application. 587class SceneHost { 588 /// Creates a host for a child scene's content. 589 /// 590 /// The ViewHolder token is bound to a ViewHolder scene graph node which acts 591 /// as a container for the child's content. The creator of the SceneHost is 592 /// responsible for sending the corresponding ViewToken to the child. 593 /// 594 /// The ViewHolder token is a dart:zircon Handle, but that type isn't 595 /// available here. This is called by ChildViewConnection in 596 /// //topaz/public/dart/fuchsia_scenic_flutter/. 597 /// 598 /// The SceneHost takes ownership of the provided ViewHolder token. 599 SceneHost( 600 dynamic viewHolderToken, 601 void Function() viewConnectedCallback, 602 void Function() viewDisconnectedCallback, 603 void Function(bool) viewStateChangedCallback); 604 605 SceneHost.fromViewHolderToken( 606 dynamic viewHolderToken, 607 void Function() viewConnectedCallback, 608 void Function() viewDisconnectedCallback, 609 void Function(bool) viewStateChangedCallback); 610 611 /// Releases the resources associated with the SceneHost. 612 /// 613 /// After calling this function, the SceneHost cannot be used further. 614 void dispose() {} 615 616 /// Set properties on the linked scene. These properties include its bounds, 617 /// as well as whether it can be the target of focus events or not. 618 void setProperties(double width, double height, double insetTop, 619 double insetRight, double insetBottom, double insetLeft, bool focusable) { 620 throw UnimplementedError(); 621 } 622 623 /// Set the opacity of the linked scene. This opacity value is applied only 624 /// once, when the child scene is composited into our own. 625 void setOpacity(double opacity) { 626 throw UnimplementedError(); 627 } 628} 629