From 5fe6083d34ad6f560223482e6c26e4b90a93a2b5 Mon Sep 17 00:00:00 2001 From: Yegor Date: Tue, 15 Oct 2019 20:07:28 -0700 Subject: [PATCH] Move surface-based SceneBuilder implementation under surface/ (#13159) Move surface-based SceneBuilder implementation under surface/ --- ci/licenses_golden/licenses_flutter | 1 + lib/web_ui/lib/src/engine.dart | 1 + lib/web_ui/lib/src/engine/surface/scene.dart | 21 + .../lib/src/engine/surface/scene_builder.dart | 482 ++++++++++++++++++ .../lib/src/engine/surface/surface.dart | 20 +- lib/web_ui/lib/src/engine/window.dart | 17 +- lib/web_ui/lib/src/ui/compositing.dart | 278 +--------- lib/web_ui/lib/src/ui/window.dart | 16 +- lib/web_ui/test/compositing_test.dart | 2 +- .../test/engine/surface/surface_test.dart | 2 +- .../engine/compositing_golden_test.dart | 27 +- lib/web_ui/test/matchers.dart | 8 +- 12 files changed, 575 insertions(+), 300 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/surface/scene_builder.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 54317417f..fd655534c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -415,6 +415,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/opacity.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/picture.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/platform_view.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/scene.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/surface.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/transform.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/test_embedding.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 0fc6c7635..61938434b 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -82,6 +82,7 @@ part 'engine/surface/opacity.dart'; part 'engine/surface/picture.dart'; part 'engine/surface/platform_view.dart'; part 'engine/surface/scene.dart'; +part 'engine/surface/scene_builder.dart'; part 'engine/surface/surface.dart'; part 'engine/surface/transform.dart'; part 'engine/test_embedding.dart'; diff --git a/lib/web_ui/lib/src/engine/surface/scene.dart b/lib/web_ui/lib/src/engine/surface/scene.dart index b9c4599e5..f289c39d1 100644 --- a/lib/web_ui/lib/src/engine/surface/scene.dart +++ b/lib/web_ui/lib/src/engine/surface/scene.dart @@ -4,6 +4,27 @@ part of engine; +class SurfaceScene implements ui.Scene { + /// This class is created by the engine, and should not be instantiated + /// or extended directly. + /// + /// To create a Scene object, use a [SceneBuilder]. + SurfaceScene(this.webOnlyRootElement); + + final html.Element webOnlyRootElement; + + /// Creates a raster image representation of the current state of the scene. + /// This is a slow operation that is performed on a background thread. + Future toImage(int width, int height) { + throw UnsupportedError('toImage is not supported on the Web'); + } + + /// Releases the resources used by this scene. + /// + /// After calling this function, the scene is cannot be used further. + void dispose() {} +} + /// A surface that creates a DOM element for whole app. class PersistedScene extends PersistedContainerSurface { PersistedScene(PersistedScene oldLayer) : super(oldLayer) { diff --git a/lib/web_ui/lib/src/engine/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart new file mode 100644 index 000000000..b29953a30 --- /dev/null +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -0,0 +1,482 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of engine; + +class SurfaceSceneBuilder implements ui.SceneBuilder { + SurfaceSceneBuilder() { + _surfaceStack.add(PersistedScene(_lastFrameScene)); + } + + final List _surfaceStack = + []; + + /// The scene built by this scene builder. + /// + /// This getter should only be called after all surfaces are built. + PersistedScene get _persistedScene { + assert(() { + if (_surfaceStack.length != 1) { + final String surfacePrintout = _surfaceStack + .map((PersistedContainerSurface surface) => + surface.runtimeType) + .toList() + .join(', '); + throw Exception('Incorrect sequence of push/pop operations while ' + 'building scene surfaces. After building the scene the persisted ' + 'surface stack must contain a single element which corresponds ' + 'to the scene itself (_PersistedScene). All other surfaces ' + 'should have been popped off the stack. Found the following ' + 'surfaces in the stack:\n$surfacePrintout'); + } + return true; + }()); + return _surfaceStack.first; + } + + /// The surface currently being built. + PersistedContainerSurface get _currentSurface => _surfaceStack.last; + + ui.EngineLayer _pushSurface(PersistedContainerSurface surface) { + // Only attempt to update if the update is requested and the surface is in + // the live tree. + if (surface.oldLayer != null) { + assert(surface.oldLayer.runtimeType == surface.runtimeType); + assert(surface.oldLayer.isActive); + surface.oldLayer.state = PersistedSurfaceState.pendingUpdate; + } + _adoptSurface(surface); + _surfaceStack.add(surface); + return surface; + } + + void _addSurface(PersistedSurface surface) { + _adoptSurface(surface); + } + + void _adoptSurface(PersistedSurface surface) { + _currentSurface.appendChild(surface); + } + + /// Pushes an offset operation onto the operation stack. + /// + /// This is equivalent to [pushTransform] with a matrix with only translation. + /// + /// See [pop] for details about the operation stack. + @override + ui.OffsetEngineLayer pushOffset(double dx, double dy, + {ui.OffsetEngineLayer oldLayer}) { + return _pushSurface(PersistedOffset(oldLayer, dx, dy)); + } + + /// Pushes a transform operation onto the operation stack. + /// + /// The objects are transformed by the given matrix before rasterization. + /// + /// See [pop] for details about the operation stack. + @override + ui.TransformEngineLayer pushTransform(Float64List matrix4, + {ui.TransformEngineLayer oldLayer}) { + if (matrix4 == null) { + throw ArgumentError('"matrix4" argument cannot be null'); + } + if (matrix4.length != 16) { + throw ArgumentError('"matrix4" must have 16 entries.'); + } + return _pushSurface(PersistedTransform(oldLayer, matrix4)); + } + + /// Pushes a rectangular clip operation onto the operation stack. + /// + /// Rasterization outside the given rectangle is discarded. + /// + /// See [pop] for details about the operation stack, and [Clip] for different clip modes. + /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]). + @override + ui.ClipRectEngineLayer pushClipRect(ui.Rect rect, + {ui.Clip clipBehavior = ui.Clip.antiAlias, ui.ClipRectEngineLayer oldLayer}) { + assert(clipBehavior != null); + assert(clipBehavior != ui.Clip.none); + return _pushSurface(PersistedClipRect(oldLayer, rect)); + } + + /// Pushes a rounded-rectangular clip operation onto the operation stack. + /// + /// Rasterization outside the given rounded rectangle is discarded. + /// + /// See [pop] for details about the operation stack. + @override + ui.ClipRRectEngineLayer pushClipRRect(ui.RRect rrect, + {ui.Clip clipBehavior, ui.ClipRRectEngineLayer oldLayer}) { + return _pushSurface( + PersistedClipRRect(oldLayer, rrect, clipBehavior)); + } + + /// Pushes a path clip operation onto the operation stack. + /// + /// Rasterization outside the given path is discarded. + /// + /// See [pop] for details about the operation stack. + @override + ui.ClipPathEngineLayer pushClipPath(ui.Path path, + {ui.Clip clipBehavior = ui.Clip.antiAlias, ui.ClipPathEngineLayer oldLayer}) { + assert(clipBehavior != null); + assert(clipBehavior != ui.Clip.none); + return _pushSurface(PersistedClipPath(oldLayer, path, clipBehavior)); + } + + /// Pushes an opacity operation onto the operation stack. + /// + /// The given alpha value is blended into the alpha value of the objects' + /// rasterization. An alpha value of 0 makes the objects entirely invisible. + /// An alpha value of 255 has no effect (i.e., the objects retain the current + /// opacity). + /// + /// See [pop] for details about the operation stack. + @override + ui.OpacityEngineLayer pushOpacity(int alpha, + {ui.Offset offset = ui.Offset.zero, ui.OpacityEngineLayer oldLayer}) { + return _pushSurface(PersistedOpacity(oldLayer, alpha, offset)); + } + + /// Pushes a color filter operation onto the operation stack. + /// + /// The given color is applied to the objects' rasterization using the given + /// blend mode. + /// + /// {@macro dart.ui.sceneBuilder.oldLayer} + /// + /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} + /// + /// See [pop] for details about the operation stack. + @override + ui.ColorFilterEngineLayer pushColorFilter(ui.ColorFilter filter, + {ui.ColorFilterEngineLayer oldLayer}) { + assert(filter != null); + throw UnimplementedError(); + } + + /// Pushes a backdrop filter operation onto the operation stack. + /// + /// The given filter is applied to the current contents of the scene prior to + /// rasterizing the given objects. + /// + /// See [pop] for details about the operation stack. + @override + ui.BackdropFilterEngineLayer pushBackdropFilter(ui.ImageFilter filter, + {ui.BackdropFilterEngineLayer oldLayer}) { + return _pushSurface(PersistedBackdropFilter(oldLayer, filter)); + } + + /// Pushes a shader mask operation onto the operation stack. + /// + /// The given shader is applied to the object's rasterization in the given + /// rectangle using the given blend mode. + /// + /// See [pop] for details about the operation stack. + @override + ui.ShaderMaskEngineLayer pushShaderMask( + ui.Shader shader, ui.Rect maskRect, ui.BlendMode blendMode, + {ui.ShaderMaskEngineLayer oldLayer}) { + throw UnimplementedError(); + } + + /// Pushes a physical layer operation for an arbitrary shape onto the + /// operation stack. + /// + /// By default, the layer's content will not be clipped (clip = [Clip.none]). + /// If clip equals [Clip.hardEdge], [Clip.antiAlias], or [Clip.antiAliasWithSaveLayer], + /// then the content is clipped to the given shape defined by [path]. + /// + /// If [elevation] is greater than 0.0, then a shadow is drawn around the layer. + /// [shadowColor] defines the color of the shadow if present and [color] defines the + /// color of the layer background. + /// + /// See [pop] for details about the operation stack, and [Clip] for different clip modes. + @override + ui.PhysicalShapeEngineLayer pushPhysicalShape({ + ui.Path path, + double elevation, + ui.Color color, + ui.Color shadowColor, + ui.Clip clipBehavior = ui.Clip.none, + ui.PhysicalShapeEngineLayer oldLayer, + }) { + return _pushSurface(PersistedPhysicalShape( + oldLayer, + path, + elevation, + color.value, + shadowColor?.value ?? 0xFF000000, + clipBehavior, + )); + } + + /// Add a retained engine layer subtree from previous frames. + /// + /// All the engine layers that are in the subtree of the retained layer will + /// be automatically appended to the current engine layer tree. + /// + /// Therefore, when implementing a subclass of the [Layer] concept defined in + /// the rendering layer of Flutter's framework, once this is called, there's + /// no need to call [addToScene] for its children layers. + @override + void addRetained(ui.EngineLayer retainedLayer) { + final PersistedContainerSurface retainedSurface = retainedLayer; + assert(retainedSurface.isActive || retainedSurface.isReleased); + retainedSurface.tryRetain(); + _adoptSurface(retainedSurface); + } + + /// Ends the effect of the most recently pushed operation. + /// + /// Internally the scene builder maintains a stack of operations. Each of the + /// operations in the stack applies to each of the objects added to the scene. + /// Calling this function removes the most recently added operation from the + /// stack. + @override + void pop() { + assert(_surfaceStack.isNotEmpty); + _surfaceStack.removeLast(); + } + + /// Adds an object to the scene that displays performance statistics. + /// + /// Useful during development to assess the performance of the application. + /// The enabledOptions controls which statistics are displayed. The bounds + /// controls where the statistics are displayed. + /// + /// enabledOptions is a bit field with the following bits defined: + /// - 0x01: displayRasterizerStatistics - show GPU thread frame time + /// - 0x02: visualizeRasterizerStatistics - graph GPU thread frame times + /// - 0x04: displayEngineStatistics - show UI thread frame time + /// - 0x08: visualizeEngineStatistics - graph UI thread frame times + /// Set enabledOptions to 0x0F to enable all the currently defined features. + /// + /// The "UI thread" is the thread that includes all the execution of + /// the main Dart isolate (the isolate that can call + /// [Window.render]). The UI thread frame time is the total time + /// spent executing the [Window.onBeginFrame] callback. The "GPU + /// thread" is the thread (running on the CPU) that subsequently + /// processes the [Scene] provided by the Dart code to turn it into + /// GPU commands and send it to the GPU. + /// + /// See also the [PerformanceOverlayOption] enum in the rendering library. + /// for more details. + @override + void addPerformanceOverlay(int enabledOptions, ui.Rect bounds) { + _addPerformanceOverlay( + enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom); + } + + /// Whether we've already warned the user about the lack of the performance + /// overlay or not. + /// + /// We use this to avoid spamming the console with redundant warning messages. + static bool _webOnlyDidWarnAboutPerformanceOverlay = false; + + void _addPerformanceOverlay(int enabledOptions, double left, double right, + double top, double bottom) { + if (!_webOnlyDidWarnAboutPerformanceOverlay) { + _webOnlyDidWarnAboutPerformanceOverlay = true; + html.window.console + .warn('The performance overlay isn\'t supported on the web'); + } + } + + /// Adds a [Picture] to the scene. + /// + /// The picture is rasterized at the given offset. + @override + void addPicture( + ui.Offset offset, + ui.Picture picture, { + bool isComplexHint = false, + bool willChangeHint = false, + }) { + int hints = 0; + if (isComplexHint) { + hints |= 1; + } + if (willChangeHint) { + hints |= 2; + } + _addSurface( + persistedPictureFactory(offset.dx, offset.dy, picture, hints)); + } + + /// Adds a backend texture to the scene. + /// + /// The texture is scaled to the given size and rasterized at the given + /// offset. + @override + void addTexture(int textureId, + {ui.Offset offset = ui.Offset.zero, + double width = 0.0, + double height = 0.0, + bool freeze = false}) { + assert(offset != null, 'Offset argument was null'); + _addTexture(offset.dx, offset.dy, width, height, textureId); + } + + void _addTexture( + double dx, double dy, double width, double height, int textureId) { + // In test mode, allow this to be a no-op. + if (!ui.debugEmulateFlutterTesterEnvironment) { + throw UnimplementedError('Textures are not supported in Flutter Web'); + } + } + + /// Adds a platform view (e.g an iOS UIView) to the scene. + /// + /// Only supported on iOS, this is currently a no-op on other platforms. + /// + /// On iOS this layer splits the current output surface into two surfaces, one for the scene nodes + /// preceding the platform view, and one for the scene nodes following the platform view. + /// + /// ## Performance impact + /// + /// Adding an additional surface doubles the amount of graphics memory directly used by Flutter + /// for output buffers. Quartz might allocated extra buffers for compositing the Flutter surfaces + /// and the platform view. + /// + /// With a platform view in the scene, Quartz has to composite the two Flutter surfaces and the + /// embedded UIView. In addition to that, on iOS versions greater than 9, the Flutter frames are + /// synchronized with the UIView frames adding additional performance overhead. + @override + void addPlatformView( + int viewId, { + ui.Offset offset = ui.Offset.zero, + double width = 0.0, + double height = 0.0, + }) { + assert(offset != null, 'Offset argument was null'); + _addPlatformView(offset.dx, offset.dy, width, height, viewId); + } + + void _addPlatformView( + double dx, + double dy, + double width, + double height, + int viewId, + ) { + _addSurface(PersistedPlatformView(viewId, dx, dy, width, height)); + } + + /// (Fuchsia-only) Adds a scene rendered by another application to the scene + /// for this application. + @override + void addChildScene( + {ui.Offset offset = ui.Offset.zero, + double width = 0.0, + double height = 0.0, + ui.SceneHost sceneHost, + bool hitTestable = true}) { + _addChildScene(offset.dx, offset.dy, width, height, sceneHost, hitTestable); + } + + void _addChildScene(double dx, double dy, double width, double height, + ui.SceneHost sceneHost, bool hitTestable) { + throw UnimplementedError(); + } + + /// Sets a threshold after which additional debugging information should be + /// recorded. + /// + /// Currently this interface is difficult to use by end-developers. If you're + /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev). + /// We'll hopefully be able to figure out how to make this feature more useful + /// to you. + @override + void setRasterizerTracingThreshold(int frameInterval) {} + + /// Sets whether the raster cache should checkerboard cached entries. This is + /// only useful for debugging purposes. + /// + /// 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. + /// + /// Currently this interface is difficult to use by end-developers. If you're + /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev). + @override + void setCheckerboardRasterCacheImages(bool checkerboard) {} + + /// Sets whether the compositor should checkerboard layers that are rendered + /// to offscreen bitmaps. + /// + /// This is only useful for debugging purposes. + @override + void setCheckerboardOffscreenLayers(bool checkerboard) {} + + /// The scene recorded in the last frame. + /// + /// This is a surface tree that holds onto the DOM elements that can be reused + /// on the next frame. + static PersistedScene _lastFrameScene; + + /// Returns the computed persisted scene graph recorded in the last frame. + /// + /// This is only available in debug mode. It returns `null` in profile and + /// release modes. + static PersistedScene get debugLastFrameScene { + PersistedScene result; + assert(() { + result = _lastFrameScene; + return true; + }()); + return result; + } + + /// Discards information about previously rendered frames, including DOM + /// elements and cached canvases. + /// + /// After calling this function new canvases will be created for the + /// subsequent scene. This is useful when tests need predictable canvas + /// sizes. If the cache is not cleared, then canvases allocated in one test + /// may be reused in another test. + static void debugForgetFrameScene() { + _lastFrameScene?.rootElement?.remove(); + _lastFrameScene = null; + _clipIdCounter = 0; + _recycledCanvases.clear(); + } + + /// Finishes building the scene. + /// + /// Returns a [Scene] containing the objects that have been added to + /// this scene builder. The [Scene] can then be displayed on the + /// screen with [Window.render]. + /// + /// After calling this function, the scene builder object is invalid and + /// cannot be used further. + @override + SurfaceScene build() { + _persistedScene.preroll(); + if (_lastFrameScene == null) { + _persistedScene.build(); + } else { + _persistedScene.update(_lastFrameScene); + } + commitScene(_persistedScene); + _lastFrameScene = _persistedScene; + return SurfaceScene(_persistedScene.rootElement); + } + + /// Set properties on the linked scene. These properties include its bounds, + /// as well as whether it can be the target of focus events or not. + @override + void setProperties(double width, double height, double insetTop, + double insetRight, double insetBottom, double insetLeft, bool focusable) { + throw UnimplementedError(); + } +} diff --git a/lib/web_ui/lib/src/engine/surface/surface.dart b/lib/web_ui/lib/src/engine/surface/surface.dart index b97930c20..dc2df2d55 100644 --- a/lib/web_ui/lib/src/engine/surface/surface.dart +++ b/lib/web_ui/lib/src/engine/surface/surface.dart @@ -111,18 +111,6 @@ void commitScene(PersistedScene scene) { }()); } -/// Discards information about previously rendered frames, including DOM -/// elements and cached canvases. -/// -/// After calling this function new canvases will be created for the -/// subsequent scene. This is useful when tests need predictable canvas -/// sizes. If the cache is not cleared, then canvases allocated in one test -/// may be reused in another test. -void debugForgetFrameScene() { - _clipIdCounter = 0; - _recycledCanvases.clear(); -} - /// Surfaces that were retained this frame. /// /// Surfaces should be added to this list directly. Instead, if a surface needs @@ -639,6 +627,14 @@ abstract class PersistedSurface implements ui.EngineLayer { @protected @mustCallSuper void build() { + if (rootElement != null) { + try { + throw null; + } catch(_, stack) { + print('Attempted to build a $runtimeType, but it already has an HTML element ${rootElement.tagName}.'); + print(stack.toString().split('\n').take(20).join('\n')); + } + } assert(rootElement == null); assert(isCreated); rootElement = createElement(); diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index c37eae4e2..b74c1488e 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -248,12 +248,23 @@ class EngineWindow extends ui.Window { _brightnessMediaQueryListener = null; } - @override - void dispose() { - _removeBrightnessMediaQueryListener(); + void render(ui.Scene scene) { + if (experimentalUseSkia) { + final LayerScene layerScene = scene; + _rasterizer.draw(layerScene.layerTree); + } else { + final SurfaceScene surfaceScene = scene; + domRenderer.renderScene(surfaceScene.webOnlyRootElement); + } } + final Rasterizer _rasterizer = experimentalUseSkia + ? Rasterizer(Surface((SkCanvas canvas) { + domRenderer.renderScene(canvas.htmlCanvas); + canvas.skSurface.callMethod('flush'); + })) + : null; } /// The window singleton. diff --git a/lib/web_ui/lib/src/ui/compositing.dart b/lib/web_ui/lib/src/ui/compositing.dart index def31dbc7..d33ccc6c2 100644 --- a/lib/web_ui/lib/src/ui/compositing.dart +++ b/lib/web_ui/lib/src/ui/compositing.dart @@ -10,33 +10,15 @@ part of ui; /// /// Scene objects can be displayed on the screen using the /// [Window.render] method. -class Scene { - /// This class is created by the engine, and should not be instantiated - /// or extended directly. - /// - /// To create a Scene object, use a [SceneBuilder]. - Scene._(this.webOnlyRootElement); - - final html.Element webOnlyRootElement; - +abstract class Scene { /// Creates a raster image representation of the current state of the scene. /// This is a slow operation that is performed on a background thread. - Future toImage(int width, int height) { - if (width <= 0 || height <= 0) { - throw Exception('Invalid image dimensions.'); - } - throw UnsupportedError('toImage is not supported on the Web'); - // TODO(flutter_web): Implement [_toImage]. - // return futurize( - // (Callback callback) => _toImage(width, height, callback)); - } - - // String _toImage(int width, int height, Callback callback) => null; + Future toImage(int width, int height); /// Releases the resources used by this scene. /// /// After calling this function, the scene is cannot be used further. - void dispose() {} + void dispose(); } /// An opaque handle to a transform engine layer. @@ -120,81 +102,23 @@ abstract class PhysicalShapeEngineLayer implements EngineLayer {} /// To draw graphical operations onto a [Scene], first create a /// [Picture] using a [PictureRecorder] and a [Canvas], and then add /// it to the scene using [addPicture]. -class SceneBuilder { +abstract class SceneBuilder { /// Creates an empty [SceneBuilder] object. factory SceneBuilder() { if (engine.experimentalUseSkia) { return engine.LayerSceneBuilder(); } else { - return SceneBuilder._(); + return engine.SurfaceSceneBuilder(); } } - SceneBuilder._() { - _surfaceStack.add(engine.PersistedScene(_lastFrameScene)); - } - - factory SceneBuilder.layer() = engine.LayerSceneBuilder; - - final List _surfaceStack = - []; - - /// The scene built by this scene builder. - /// - /// This getter should only be called after all surfaces are built. - engine.PersistedScene get _persistedScene { - assert(() { - if (_surfaceStack.length != 1) { - final String surfacePrintout = _surfaceStack - .map((engine.PersistedContainerSurface surface) => - surface.runtimeType) - .toList() - .join(', '); - throw Exception('Incorrect sequence of push/pop operations while ' - 'building scene surfaces. After building the scene the persisted ' - 'surface stack must contain a single element which corresponds ' - 'to the scene itself (_PersistedScene). All other surfaces ' - 'should have been popped off the stack. Found the following ' - 'surfaces in the stack:\n$surfacePrintout'); - } - return true; - }()); - return _surfaceStack.first; - } - - /// The surface currently being built. - engine.PersistedContainerSurface get _currentSurface => _surfaceStack.last; - - EngineLayer _pushSurface(engine.PersistedContainerSurface surface) { - // Only attempt to update if the update is requested and the surface is in - // the live tree. - if (surface.oldLayer != null) { - assert(surface.oldLayer.runtimeType == surface.runtimeType); - assert(surface.oldLayer.isActive); - surface.oldLayer.state = engine.PersistedSurfaceState.pendingUpdate; - } - _adoptSurface(surface); - _surfaceStack.add(surface); - return surface; - } - - void _addSurface(engine.PersistedSurface surface) { - _adoptSurface(surface); - } - - void _adoptSurface(engine.PersistedSurface surface) { - _currentSurface.appendChild(surface); - } - /// Pushes an offset operation onto the operation stack. /// /// This is equivalent to [pushTransform] with a matrix with only translation. /// /// See [pop] for details about the operation stack. OffsetEngineLayer pushOffset(double dx, double dy, - {OffsetEngineLayer oldLayer}) { - return _pushSurface(engine.PersistedOffset(oldLayer, dx, dy)); - } + {OffsetEngineLayer oldLayer}); /// Pushes a transform operation onto the operation stack. /// @@ -202,15 +126,7 @@ class SceneBuilder { /// /// See [pop] for details about the operation stack. TransformEngineLayer pushTransform(Float64List matrix4, - {TransformEngineLayer oldLayer}) { - if (matrix4 == null) { - throw ArgumentError('"matrix4" argument cannot be null'); - } - if (matrix4.length != 16) { - throw ArgumentError('"matrix4" must have 16 entries.'); - } - return _pushSurface(engine.PersistedTransform(oldLayer, matrix4)); - } + {TransformEngineLayer oldLayer}); /// Pushes a rectangular clip operation onto the operation stack. /// @@ -219,11 +135,7 @@ class SceneBuilder { /// See [pop] for details about the operation stack, and [Clip] for different clip modes. /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]). ClipRectEngineLayer pushClipRect(Rect rect, - {Clip clipBehavior = Clip.antiAlias, ClipRectEngineLayer oldLayer}) { - assert(clipBehavior != null); - assert(clipBehavior != Clip.none); - return _pushSurface(engine.PersistedClipRect(oldLayer, rect)); - } + {Clip clipBehavior = Clip.antiAlias, ClipRectEngineLayer oldLayer}); /// Pushes a rounded-rectangular clip operation onto the operation stack. /// @@ -231,10 +143,7 @@ class SceneBuilder { /// /// See [pop] for details about the operation stack. ClipRRectEngineLayer pushClipRRect(RRect rrect, - {Clip clipBehavior, ClipRRectEngineLayer oldLayer}) { - return _pushSurface( - engine.PersistedClipRRect(oldLayer, rrect, clipBehavior)); - } + {Clip clipBehavior, ClipRRectEngineLayer oldLayer}); /// Pushes a path clip operation onto the operation stack. /// @@ -242,11 +151,7 @@ class SceneBuilder { /// /// See [pop] for details about the operation stack. ClipPathEngineLayer pushClipPath(Path path, - {Clip clipBehavior = Clip.antiAlias, ClipPathEngineLayer oldLayer}) { - assert(clipBehavior != null); - assert(clipBehavior != Clip.none); - return _pushSurface(engine.PersistedClipPath(oldLayer, path, clipBehavior)); - } + {Clip clipBehavior = Clip.antiAlias, ClipPathEngineLayer oldLayer}); /// Pushes an opacity operation onto the operation stack. /// @@ -257,9 +162,7 @@ class SceneBuilder { /// /// See [pop] for details about the operation stack. OpacityEngineLayer pushOpacity(int alpha, - {Offset offset = Offset.zero, OpacityEngineLayer oldLayer}) { - return _pushSurface(engine.PersistedOpacity(oldLayer, alpha, offset)); - } + {Offset offset = Offset.zero, OpacityEngineLayer oldLayer}); /// Pushes a color filter operation onto the operation stack. /// @@ -272,10 +175,7 @@ class SceneBuilder { /// /// See [pop] for details about the operation stack. ColorFilterEngineLayer pushColorFilter(ColorFilter filter, - {ColorFilterEngineLayer oldLayer}) { - assert(filter != null); - throw UnimplementedError(); - } + {ColorFilterEngineLayer oldLayer}); /// Pushes a backdrop filter operation onto the operation stack. /// @@ -284,9 +184,7 @@ class SceneBuilder { /// /// See [pop] for details about the operation stack. BackdropFilterEngineLayer pushBackdropFilter(ImageFilter filter, - {BackdropFilterEngineLayer oldLayer}) { - return _pushSurface(engine.PersistedBackdropFilter(oldLayer, filter)); - } + {BackdropFilterEngineLayer oldLayer}); /// Pushes a shader mask operation onto the operation stack. /// @@ -296,9 +194,7 @@ class SceneBuilder { /// See [pop] for details about the operation stack. ShaderMaskEngineLayer pushShaderMask( Shader shader, Rect maskRect, BlendMode blendMode, - {ShaderMaskEngineLayer oldLayer}) { - throw UnimplementedError(); - } + {ShaderMaskEngineLayer oldLayer}); /// Pushes a physical layer operation for an arbitrary shape onto the /// operation stack. @@ -319,16 +215,7 @@ class SceneBuilder { Color shadowColor, Clip clipBehavior = Clip.none, PhysicalShapeEngineLayer oldLayer, - }) { - return _pushSurface(engine.PersistedPhysicalShape( - oldLayer, - path, - elevation, - color.value, - shadowColor?.value ?? 0xFF000000, - clipBehavior, - )); - } + }); /// Add a retained engine layer subtree from previous frames. /// @@ -338,12 +225,7 @@ class SceneBuilder { /// Therefore, when implementing a subclass of the [Layer] concept defined in /// the rendering layer of Flutter's framework, once this is called, there's /// no need to call [addToScene] for its children layers. - void addRetained(EngineLayer retainedLayer) { - final engine.PersistedContainerSurface retainedSurface = retainedLayer; - assert(retainedSurface.isActive || retainedSurface.isReleased); - retainedSurface.tryRetain(); - _adoptSurface(retainedSurface); - } + void addRetained(EngineLayer retainedLayer); /// Ends the effect of the most recently pushed operation. /// @@ -351,10 +233,7 @@ class SceneBuilder { /// operations in the stack applies to each of the objects added to the scene. /// Calling this function removes the most recently added operation from the /// stack. - void pop() { - assert(_surfaceStack.isNotEmpty); - _surfaceStack.removeLast(); - } + void pop(); /// Adds an object to the scene that displays performance statistics. /// @@ -379,25 +258,7 @@ class SceneBuilder { /// /// See also the [PerformanceOverlayOption] enum in the rendering library. /// for more details. - void addPerformanceOverlay(int enabledOptions, Rect bounds) { - _addPerformanceOverlay( - enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom); - } - - /// Whether we've already warned the user about the lack of the performance - /// overlay or not. - /// - /// We use this to avoid spamming the console with redundant warning messages. - static bool _webOnlyDidWarnAboutPerformanceOverlay = false; - - void _addPerformanceOverlay(int enabledOptions, double left, double right, - double top, double bottom) { - if (!_webOnlyDidWarnAboutPerformanceOverlay) { - _webOnlyDidWarnAboutPerformanceOverlay = true; - html.window.console - .warn('The performance overlay isn\'t supported on the web'); - } - } + void addPerformanceOverlay(int enabledOptions, Rect bounds); /// Adds a [Picture] to the scene. /// @@ -407,17 +268,7 @@ class SceneBuilder { Picture picture, { bool isComplexHint = false, bool willChangeHint = false, - }) { - int hints = 0; - if (isComplexHint) { - hints |= 1; - } - if (willChangeHint) { - hints |= 2; - } - _addSurface( - engine.persistedPictureFactory(offset.dx, offset.dy, picture, hints)); - } + }); /// Adds a backend texture to the scene. /// @@ -427,18 +278,7 @@ class SceneBuilder { {Offset offset = Offset.zero, double width = 0.0, double height = 0.0, - bool freeze = false}) { - assert(offset != null, 'Offset argument was null'); - _addTexture(offset.dx, offset.dy, width, height, textureId); - } - - void _addTexture( - double dx, double dy, double width, double height, int textureId) { - // In test mode, allow this to be a no-op. - if (!debugEmulateFlutterTesterEnvironment) { - throw UnimplementedError('Textures are not supported in Flutter Web'); - } - } + bool freeze = false}); /// Adds a platform view (e.g an iOS UIView) to the scene. /// @@ -461,20 +301,7 @@ class SceneBuilder { Offset offset = Offset.zero, double width = 0.0, double height = 0.0, - }) { - assert(offset != null, 'Offset argument was null'); - _addPlatformView(offset.dx, offset.dy, width, height, viewId); - } - - void _addPlatformView( - double dx, - double dy, - double width, - double height, - int viewId, - ) { - _addSurface(engine.PersistedPlatformView(viewId, dx, dy, width, height)); - } + }); /// (Fuchsia-only) Adds a scene rendered by another application to the scene /// for this application. @@ -483,14 +310,7 @@ class SceneBuilder { double width = 0.0, double height = 0.0, SceneHost sceneHost, - bool hitTestable = true}) { - _addChildScene(offset.dx, offset.dy, width, height, sceneHost, hitTestable); - } - - void _addChildScene(double dx, double dy, double width, double height, - SceneHost sceneHost, bool hitTestable) { - throw UnimplementedError(); - } + bool hitTestable = true}); /// Sets a threshold after which additional debugging information should be /// recorded. @@ -499,7 +319,7 @@ class SceneBuilder { /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev). /// We'll hopefully be able to figure out how to make this feature more useful /// to you. - void setRasterizerTracingThreshold(int frameInterval) {} + void setRasterizerTracingThreshold(int frameInterval); /// Sets whether the raster cache should checkerboard cached entries. This is /// only useful for debugging purposes. @@ -517,45 +337,13 @@ class SceneBuilder { /// /// Currently this interface is difficult to use by end-developers. If you're /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev). - void setCheckerboardRasterCacheImages(bool checkerboard) {} + void setCheckerboardRasterCacheImages(bool checkerboard); /// Sets whether the compositor should checkerboard layers that are rendered /// to offscreen bitmaps. /// /// This is only useful for debugging purposes. - void setCheckerboardOffscreenLayers(bool checkerboard) {} - - /// The scene recorded in the last frame. - /// - /// This is a surface tree that holds onto the DOM elements that can be reused - /// on the next frame. - static engine.PersistedScene _lastFrameScene; - - /// Returns the computed persisted scene graph recorded in the last frame. - /// - /// This is only available in debug mode. It returns `null` in profile and - /// release modes. - static engine.PersistedScene get debugLastFrameScene { - engine.PersistedScene result; - assert(() { - result = _lastFrameScene; - return true; - }()); - return result; - } - - /// Discards information about previously rendered frames, including DOM - /// elements and cached canvases. - /// - /// After calling this function new canvases will be created for the - /// subsequent scene. This is useful when tests need predictable canvas - /// sizes. If the cache is not cleared, then canvases allocated in one test - /// may be reused in another test. - static void debugForgetFrameScene() { - _lastFrameScene?.rootElement?.remove(); - _lastFrameScene = null; - engine.debugForgetFrameScene(); - } + void setCheckerboardOffscreenLayers(bool checkerboard); /// Finishes building the scene. /// @@ -565,24 +353,12 @@ class SceneBuilder { /// /// After calling this function, the scene builder object is invalid and /// cannot be used further. - Scene build() { - _persistedScene.preroll(); - if (_lastFrameScene == null) { - _persistedScene.build(); - } else { - _persistedScene.update(_lastFrameScene); - } - engine.commitScene(_persistedScene); - _lastFrameScene = _persistedScene; - return Scene._(_persistedScene.rootElement); - } + Scene build(); /// Set properties on the linked scene. These properties include its bounds, /// as well as whether it can be the target of focus events or not. void setProperties(double width, double height, double insetTop, - double insetRight, double insetBottom, double insetLeft, bool focusable) { - throw UnimplementedError(); - } + double insetRight, double insetBottom, double insetLeft, bool focusable); } /// A handle for the framework to hold and retain an engine layer across frames. diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart index 47d27257b..58c82410d 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -974,21 +974,7 @@ abstract class Window { /// scheduling of frames. /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. - void render(Scene scene) { - if (engine.experimentalUseSkia) { - final engine.LayerScene layerScene = scene; - _rasterizer.draw(layerScene.layerTree); - } else { - engine.domRenderer.renderScene(scene.webOnlyRootElement); - } - } - - final engine.Rasterizer _rasterizer = engine.experimentalUseSkia - ? engine.Rasterizer(engine.Surface((engine.SkCanvas canvas) { - engine.domRenderer.renderScene(canvas.htmlCanvas); - canvas.skSurface.callMethod('flush'); - })) - : null; + void render(Scene scene); String get initialLifecycleState => _initialLifecycleState; diff --git a/lib/web_ui/test/compositing_test.dart b/lib/web_ui/test/compositing_test.dart index 005a7f086..a2be37525 100644 --- a/lib/web_ui/test/compositing_test.dart +++ b/lib/web_ui/test/compositing_test.dart @@ -185,7 +185,7 @@ void testLayerLifeCycle( TestLayerBuilder layerBuilder, ExpectedHtmlGetter expectedHtmlGetter) { // Force scene builder to start from scratch. This guarantees that the first // scene starts from the "build" phase. - SceneBuilder.debugForgetFrameScene(); + SurfaceSceneBuilder.debugForgetFrameScene(); // Build: builds a brand new layer. SceneBuilder sceneBuilder = SceneBuilder(); diff --git a/lib/web_ui/test/engine/surface/surface_test.dart b/lib/web_ui/test/engine/surface/surface_test.dart index d9a91cbc5..eb10cbdda 100644 --- a/lib/web_ui/test/engine/surface/surface_test.dart +++ b/lib/web_ui/test/engine/surface/surface_test.dart @@ -12,7 +12,7 @@ import 'package:test/test.dart'; void main() { group('Surface', () { setUp(() { - SceneBuilder.debugForgetFrameScene(); + SurfaceSceneBuilder.debugForgetFrameScene(); }); test('is created', () { diff --git a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart index caed73a7f..6334ea11f 100644 --- a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart @@ -18,14 +18,14 @@ void main() async { debugShowClipLayers = true; setUp(() { - SceneBuilder.debugForgetFrameScene(); + SurfaceSceneBuilder.debugForgetFrameScene(); for (html.Node scene in html.document.querySelectorAll('flt-scene')) { scene.remove(); } }); test('pushClipRect', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushClipRect( const Rect.fromLTRB(10, 10, 60, 60), ); @@ -38,7 +38,7 @@ void main() async { }, timeout: const Timeout(Duration(seconds: 10))); test('pushClipRect with offset and transform', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushOffset(0, 60); builder.pushTransform( @@ -58,7 +58,7 @@ void main() async { }, timeout: const Timeout(Duration(seconds: 10))); test('pushClipRRect', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushClipRRect( RRect.fromLTRBR(10, 10, 60, 60, const Radius.circular(5)), ); @@ -71,7 +71,7 @@ void main() async { }, timeout: const Timeout(Duration(seconds: 10))); test('pushPhysicalShape', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushPhysicalShape( path: Path()..addRect(const Rect.fromLTRB(10, 10, 60, 60)), clipBehavior: Clip.hardEdge, @@ -204,7 +204,7 @@ void _testCullRectComputation() { // Draw a picture inside a layer clip but fill all available space inside it. // Verify that the cull rect is equal to the layer clip. test('fills layer clip rect', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushClipRect( const Rect.fromLTWH(10, 10, 60, 60), ); @@ -232,7 +232,7 @@ void _testCullRectComputation() { // paint bounds overflow the layer clip. Verify that the cull rect is the // intersection between the layer clip and paint bounds. test('intersects layer clip rect and paint bounds', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushClipRect( const Rect.fromLTWH(10, 10, 60, 60), ); @@ -260,7 +260,7 @@ void _testCullRectComputation() { // an offset layer. Verify that the cull rect is the intersection between the // layer clip and the offset paint bounds. test('offsets picture inside layer clip rect', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushClipRect( const Rect.fromLTWH(10, 10, 60, 60), ); @@ -320,7 +320,7 @@ void _testCullRectComputation() { // Draw a picture inside a rotated clip. Verify that the cull rect is big // enough to fit the rotated clip. test('rotates clip and the picture', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushOffset(80, 50); builder.pushTransform( @@ -364,7 +364,7 @@ void _testCullRectComputation() { }, timeout: const Timeout(Duration(seconds: 10))); test('pushClipPath', () async { - final SceneBuilder builder = SceneBuilder(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); final Path path = Path(); path..addRect(const Rect.fromLTRB(10, 10, 60, 60)); builder.pushClipPath( @@ -381,9 +381,10 @@ void _testCullRectComputation() { // Draw a picture inside a rotated clip. Verify that the cull rect is big // enough to fit the rotated clip. test('clips correctly when using 3d transforms', () async { - final SceneBuilder builder = SceneBuilder(); - final double screenWidth = html.window.innerWidth.toDouble(); - final double screenHeight = html.window.innerHeight.toDouble(); + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + // TODO(yjbanov): see the TODO below. + // final double screenWidth = html.window.innerWidth.toDouble(); + // final double screenHeight = html.window.innerHeight.toDouble(); final Matrix4 scaleTransform = Matrix4.identity().scaled(0.5, 0.2); builder.pushTransform( diff --git a/lib/web_ui/test/matchers.dart b/lib/web_ui/test/matchers.dart index 7ae94348b..c8ff6ba4f 100644 --- a/lib/web_ui/test/matchers.dart +++ b/lib/web_ui/test/matchers.dart @@ -23,7 +23,7 @@ import 'package:ui/src/engine.dart'; /// /// Surfaces are returned in a depth-first order. Iterable enumerateSurfaces([PersistedSurface root]) { - root ??= SceneBuilder.debugLastFrameScene; + root ??= SurfaceSceneBuilder.debugLastFrameScene; final List surfaces = [root]; root.visitChildren((PersistedSurface surface) { @@ -37,7 +37,7 @@ Iterable enumerateSurfaces([PersistedSurface root]) { /// /// If [root] is `null` returns all pictures from the last rendered scene. Iterable enumeratePictures([PersistedSurface root]) { - root ??= SceneBuilder.debugLastFrameScene; + root ??= SurfaceSceneBuilder.debugLastFrameScene; return enumerateSurfaces(root).whereType(); } @@ -45,7 +45,7 @@ Iterable enumeratePictures([PersistedSurface root]) { /// /// If [root] is `null` returns all pictures from the last rendered scene. Iterable enumerateOffsets([PersistedSurface root]) { - root ??= SceneBuilder.debugLastFrameScene; + root ??= SurfaceSceneBuilder.debugLastFrameScene; return enumerateSurfaces(root).whereType(); } @@ -459,7 +459,7 @@ String get currentHtml { class SceneTester { SceneTester(this.scene); - final Scene scene; + final SurfaceScene scene; void expectSceneHtml(String expectedHtml) { expectHtml(scene.webOnlyRootElement, expectedHtml, -- GitLab