• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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