diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine.dart b/engine/src/flutter/lib/web_ui/lib/src/engine.dart index dc774028779..25314ad53a8 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine.dart @@ -74,7 +74,6 @@ export 'engine/layer/layer_scene_builder.dart'; export 'engine/layer/layer_tree.dart'; export 'engine/layer/layer_visitor.dart'; export 'engine/layer/n_way_canvas.dart'; -export 'engine/layers.dart'; export 'engine/lazy_path.dart'; export 'engine/mouse/context_menu.dart'; export 'engine/mouse/cursor.dart'; @@ -82,11 +81,11 @@ export 'engine/mouse/prevent_default.dart'; export 'engine/navigation/history.dart'; export 'engine/noto_font.dart'; export 'engine/noto_font_encoding.dart'; +export 'engine/occlusion_map.dart'; export 'engine/onscreen_logging.dart'; export 'engine/platform_dispatcher.dart'; export 'engine/platform_dispatcher/app_lifecycle_state.dart'; export 'engine/platform_dispatcher/view_focus_binding.dart'; -export 'engine/platform_views.dart'; export 'engine/platform_views/content_manager.dart'; export 'engine/platform_views/embedder.dart'; export 'engine/platform_views/message_handler.dart'; @@ -99,9 +98,6 @@ export 'engine/profiler.dart'; export 'engine/raw_keyboard.dart'; export 'engine/renderer.dart'; export 'engine/safe_browser_api.dart'; -export 'engine/scene_builder.dart'; -export 'engine/scene_painting.dart'; -export 'engine/scene_view.dart'; export 'engine/semantics/accessibility.dart'; export 'engine/semantics/alert.dart'; export 'engine/semantics/checkable.dart'; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/image.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/image.dart index 987271f188b..b1acf49d29f 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -312,17 +312,6 @@ CkImage scaleImage(SkImage image, int? targetWidth, int? targetHeight) { return ckImage; } -/// Thrown when the web engine fails to decode an image, either due to a -/// network issue, corrupted image contents, or missing codec. -class ImageCodecException implements Exception { - ImageCodecException(this._message); - - final String _message; - - @override - String toString() => 'ImageCodecException: $_message'; -} - const String _kNetworkImageMessage = 'Failed to load network image.'; /// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia after diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart index f750deda057..ee9ef98d301 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart @@ -7,7 +7,7 @@ import 'dart:typed_data'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -class CkPictureRecorder implements LayerPictureRecorder, ScenePictureRecorder { +class CkPictureRecorder implements LayerPictureRecorder { SkPictureRecorder? _skRecorder; CkCanvas? _recordingCanvas; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/raster_cache.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/raster_cache.dart deleted file mode 100644 index 192dd42c03d..00000000000 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/raster_cache.dart +++ /dev/null @@ -1,52 +0,0 @@ -// 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. - -import 'package:ui/ui.dart' as ui; - -import '../vector_math.dart'; -import 'canvas.dart'; - -/// A cache of [Picture]s that have already been rasterized. -/// -/// In the case of a [Picture] with a lot of complex drawing commands, it can -/// be faster to rasterize that [Picture] into it's own canvas and composite -/// that canvas into the scene rather than replay the drawing commands for that -/// picture into the overall scene. -/// -/// This class is responsible for deciding if a [Picture] should be cached and -/// for creating the cached [Picture]s that can be drawn directly into the -/// canvas. -class RasterCache { - /// Make a decision on whether to cache [picture] under transform [matrix]. - /// - /// This is based on heuristics such as how many times [picture] has been - /// drawn before and the complexity of the drawing commands in [picture]. - /// - /// We also take into account the current transform [matrix], because, for - /// example, a picture may be rasterized with the identity transform, but - /// when it is used, the transform is a 3x scale. In this case, compositing - /// the rendered picture would result in pixelation. So, we must use both - /// the picture and the transform as a cache key. - /// - /// The flag [isComplex] is a hint to the raster cache that this picture - /// contains complex drawing commands, and as such should be more strongly - /// considered for caching. - /// - /// The flag [willChange] is a hint to the raster cache that this picture - /// will change, and so should be less likely to be cached. - void prepare(ui.Picture picture, Matrix4 matrix, bool isComplex, bool willChange) {} - - /// Gets a raster cache result for the [picture] at transform [matrix]. - RasterCacheResult get(ui.Picture picture, Matrix4 matrix) => RasterCacheResult(); -} - -/// A cache entry for a given picture and matrix. -class RasterCacheResult { - /// Whether or not this result represents a rasterized picture that can be - /// drawn into the scene. - bool get isValid => false; - - /// Draws the rasterized picture into the [canvas]. - void draw(CkCanvas canvas) {} -} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index 9d07f2efe11..2e259330505 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -428,71 +428,6 @@ class CanvasKitRenderer extends Renderer { ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => isExperimentalWebParagraph ? WebParagraphBuilder(style) : CkParagraphBuilder(style); - // TODO(harryterkelsen): Merge this logic with the async logic in - // [EngineScene], https://github.com/flutter/flutter/issues/142072. - @override - Future renderScene(ui.Scene scene, EngineFlutterView view) async { - assert( - rasterizers.containsKey(view.viewId), - "Unable to render to a view which hasn't been registered", - ); - final ViewRasterizer rasterizer = rasterizers[view.viewId]!; - final RenderQueue renderQueue = rasterizer.queue; - final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled - ? FrameTimingRecorder() - : null; - if (renderQueue.current != null) { - // If a scene is already queued up, drop it and queue this one up instead - // so that the scene view always displays the most recently requested scene. - renderQueue.next?.completer.complete(); - final Completer completer = Completer(); - renderQueue.next = (scene: scene, completer: completer, recorder: recorder); - return completer.future; - } - final Completer completer = Completer(); - renderQueue.current = (scene: scene, completer: completer, recorder: recorder); - unawaited(_kickRenderLoop(rasterizer)); - return completer.future; - } - - Future _kickRenderLoop(ViewRasterizer rasterizer) async { - final RenderQueue renderQueue = rasterizer.queue; - final RenderRequest current = renderQueue.current!; - try { - await _renderScene(current.scene, rasterizer, current.recorder); - current.completer.complete(); - } catch (error, stackTrace) { - current.completer.completeError(error, stackTrace); - } - renderQueue.current = renderQueue.next; - renderQueue.next = null; - if (renderQueue.current == null) { - return; - } else { - return _kickRenderLoop(rasterizer); - } - } - - Future _renderScene( - ui.Scene scene, - ViewRasterizer rasterizer, - FrameTimingRecorder? recorder, - ) async { - // "Build finish" and "raster start" happen back-to-back because we - // render on the same thread, so there's no overhead from hopping to - // another thread. - // - // CanvasKit works differently from the HTML renderer in that in HTML - // we update the DOM in SceneBuilder.build, which is these function calls - // here are CanvasKit-only. - recorder?.recordBuildFinish(); - recorder?.recordRasterStart(); - - await rasterizer.draw((scene as LayerScene).layerTree, null); - recorder?.recordRasterFinish(); - recorder?.submitTimings(); - } - @override void clearFragmentProgramCache() { _programs.clear(); @@ -535,5 +470,14 @@ class CanvasKitRenderer extends Renderer { ); @override - void dumpDebugInfo() {} + void dumpDebugInfo() { + int i = 0; + for (final viewRasterizer in rasterizers.values) { + final Map? debugJson = viewRasterizer.dumpDebugInfo(); + if (debugJson != null) { + downloadDebugInfo('flutter-scene$i', debugJson); + i++; + } + } + } } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/frame_service.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/frame_service.dart index 613e3a6b362..9e3639d6125 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/frame_service.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/frame_service.dart @@ -162,9 +162,8 @@ class FrameService { // In Flutter terminology "building a frame" consists of "beginning // frame" and "drawing frame". // - // We do not call `recordBuildFinish` from here because - // part of the rasterization process, particularly in the HTML - // renderer, takes place in the `SceneBuilder.build()`. + // We do not call `recordBuildFinish` from here because part of the + // rasterization process takes place in `SceneBuilder.build()`. FrameTimingRecorder.recordCurrentFrameBuildStart(); // We have to convert high-resolution time to `int` so we can construct diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/image_decoder.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/image_decoder.dart index e62646e2314..f67065b0092 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/image_decoder.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/image_decoder.dart @@ -267,3 +267,14 @@ ui.Image scaleImageIfNeeded( image.dispose(); return finalImage; } + +/// Thrown when the web engine fails to decode an image, either due to a +/// network issue, corrupted image contents, or missing codec. +class ImageCodecException implements Exception { + ImageCodecException(this._message); + + final String _message; + + @override + String toString() => 'ImageCodecException: $_message'; +} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/layers.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/layers.dart deleted file mode 100644 index e00924640b8..00000000000 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/layers.dart +++ /dev/null @@ -1,1387 +0,0 @@ -// 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. - -import 'dart:math' as math; -import 'dart:typed_data'; - -import 'package:meta/meta.dart'; -import 'package:ui/src/engine.dart'; -import 'package:ui/ui.dart' as ui; - -class EngineRootLayer with PictureEngineLayer { - @override - final NoopOperation operation = const NoopOperation(); - - @override - EngineRootLayer emptyClone() => EngineRootLayer(); -} - -class NoopOperation implements LayerOperation { - const NoopOperation(); - - @override - PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect; - - @override - void pre(SceneCanvas canvas) { - canvas.save(); - } - - @override - void post(SceneCanvas canvas) { - canvas.restore(); - } - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'NoopOperation()'; - - @override - Map get debugJsonDescription { - return {'type': 'noop'}; - } -} - -class BackdropFilterLayer with PictureEngineLayer implements ui.BackdropFilterEngineLayer { - BackdropFilterLayer(this.operation); - - @override - final LayerOperation operation; - - @override - BackdropFilterLayer emptyClone() => BackdropFilterLayer(operation); -} - -class BackdropFilterOperation implements LayerOperation { - BackdropFilterOperation(this.filter, this.mode); - - final ui.ImageFilter filter; - final ui.BlendMode mode; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect; - - @override - void pre(SceneCanvas canvas) { - canvas.saveLayerWithFilter(ui.Rect.largest, ui.Paint()..blendMode = mode, filter); - } - - @override - void post(SceneCanvas canvas) { - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); - - @override - bool get affectsBackdrop => true; - - @override - String toString() => 'BackdropFilterOperation(filter: $filter, mode: $mode)'; - - @override - Map get debugJsonDescription { - return { - 'type': 'backdropFilter', - 'filter': filter.toString(), - 'mode': mode.toString(), - }; - } -} - -class ClipPathLayer with PictureEngineLayer implements ui.ClipPathEngineLayer { - ClipPathLayer(this.operation); - - @override - final ClipPathOperation operation; - - @override - ClipPathLayer emptyClone() => ClipPathLayer(operation); -} - -class ClipPathOperation implements LayerOperation { - ClipPathOperation(this.path, this.clip); - - final ScenePath path; - final ui.Clip clip; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(path.getBounds()); - - @override - void pre(SceneCanvas canvas) { - canvas.save(); - canvas.clipPath(path, doAntiAlias: clip != ui.Clip.hardEdge); - if (clip == ui.Clip.antiAliasWithSaveLayer) { - canvas.saveLayer(path.getBounds(), ui.Paint()); - } - } - - @override - void post(SceneCanvas canvas) { - if (clip == ui.Clip.antiAliasWithSaveLayer) { - canvas.restore(); - } - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() { - return PlatformViewStyling(clip: PlatformViewPathClip(path)); - } - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'ClipPathOperation(path: $path, clip: $clip)'; - - @override - Map get debugJsonDescription { - final ui.Rect bounds = path.getBounds(); - return { - 'type': 'clipPath', - 'pathBounds': { - 'left': bounds.left, - 'top': bounds.top, - 'right': bounds.right, - 'bottom': bounds.bottom, - }, - 'clip': clip.name, - }; - } -} - -class ClipRectLayer with PictureEngineLayer implements ui.ClipRectEngineLayer { - ClipRectLayer(this.operation); - - @override - final ClipRectOperation operation; - - @override - ClipRectLayer emptyClone() => ClipRectLayer(operation); -} - -class ClipRectOperation implements LayerOperation { - const ClipRectOperation(this.rect, this.clip); - - final ui.Rect rect; - final ui.Clip clip; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rect); - - @override - void pre(SceneCanvas canvas) { - canvas.save(); - canvas.clipRect(rect, doAntiAlias: clip != ui.Clip.hardEdge); - if (clip == ui.Clip.antiAliasWithSaveLayer) { - canvas.saveLayer(rect, ui.Paint()); - } - } - - @override - void post(SceneCanvas canvas) { - if (clip == ui.Clip.antiAliasWithSaveLayer) { - canvas.restore(); - } - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() { - return PlatformViewStyling(clip: PlatformViewRectClip(rect)); - } - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'ClipRectOperation(rect: $rect, clip: $clip)'; - - @override - Map get debugJsonDescription { - return { - 'type': 'clipRect', - 'rect': {'left': rect.left, 'top': rect.top, 'right': rect.right, 'bottom': rect.bottom}, - 'clip': clip.name, - }; - } -} - -class ClipRRectLayer with PictureEngineLayer implements ui.ClipRRectEngineLayer { - ClipRRectLayer(this.operation); - - @override - final ClipRRectOperation operation; - - @override - ClipRRectLayer emptyClone() => ClipRRectLayer(operation); -} - -class ClipRRectOperation implements LayerOperation { - const ClipRRectOperation(this.rrect, this.clip); - - final ui.RRect rrect; - final ui.Clip clip; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rrect.outerRect); - - @override - void pre(SceneCanvas canvas) { - canvas.save(); - canvas.clipRRect(rrect, doAntiAlias: clip != ui.Clip.hardEdge); - if (clip == ui.Clip.antiAliasWithSaveLayer) { - canvas.saveLayer(rrect.outerRect, ui.Paint()); - } - } - - @override - void post(SceneCanvas canvas) { - if (clip == ui.Clip.antiAliasWithSaveLayer) { - canvas.restore(); - } - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() { - return PlatformViewStyling(clip: PlatformViewRRectClip(rrect)); - } - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'ClipRRectOperation(rrect: $rrect, clip: $clip)'; - - @override - Map get debugJsonDescription { - return { - 'type': 'clipRRect', - 'rrect': { - 'left': rrect.left, - 'top': rrect.top, - 'right': rrect.right, - 'bottom': rrect.bottom, - 'tlRadiusX': rrect.tlRadiusX, - 'tlRadiusY': rrect.tlRadiusY, - 'trRadiusX': rrect.trRadiusX, - 'trRadiusY': rrect.trRadiusY, - 'brRadiusX': rrect.brRadiusX, - 'brRadiusY': rrect.brRadiusY, - 'blRadiusX': rrect.blRadiusX, - 'blRadiusY': rrect.blRadiusY, - }, - 'clip': clip.name, - }; - } -} - -class ClipRSuperellipseLayer with PictureEngineLayer implements ui.ClipRSuperellipseEngineLayer { - ClipRSuperellipseLayer(this.operation); - - @override - final ClipRSuperellipseOperation operation; - - @override - ClipRSuperellipseLayer emptyClone() => ClipRSuperellipseLayer(operation); -} - -class ClipRSuperellipseOperation implements LayerOperation { - const ClipRSuperellipseOperation(this.rsuperellipse, this.clip); - - final ui.RSuperellipse rsuperellipse; - final ui.Clip clip; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rsuperellipse.outerRect); - - @override - void pre(SceneCanvas canvas) { - canvas.save(); - canvas.clipRSuperellipse(rsuperellipse, doAntiAlias: clip != ui.Clip.hardEdge); - if (clip == ui.Clip.antiAliasWithSaveLayer) { - canvas.saveLayer(rsuperellipse.outerRect, ui.Paint()); - } - } - - @override - void post(SceneCanvas canvas) { - if (clip == ui.Clip.antiAliasWithSaveLayer) { - canvas.restore(); - } - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() { - // RSuperellipse ops in PlatformView are approximated by RRect because they - // are expensive. - return PlatformViewStyling(clip: PlatformViewRRectClip(rsuperellipse.toApproximateRRect())); - } - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'ClipRSuperellipseOperation(rsuperellipse: $rsuperellipse, clip: $clip)'; - - @override - Map get debugJsonDescription { - return { - 'type': 'clipRSuperellipse', - 'rsuperellipse': { - 'left': rsuperellipse.left, - 'top': rsuperellipse.top, - 'right': rsuperellipse.right, - 'bottom': rsuperellipse.bottom, - 'tlRadiusX': rsuperellipse.tlRadiusX, - 'tlRadiusY': rsuperellipse.tlRadiusY, - 'trRadiusX': rsuperellipse.trRadiusX, - 'trRadiusY': rsuperellipse.trRadiusY, - 'brRadiusX': rsuperellipse.brRadiusX, - 'brRadiusY': rsuperellipse.brRadiusY, - 'blRadiusX': rsuperellipse.blRadiusX, - 'blRadiusY': rsuperellipse.blRadiusY, - }, - 'clip': clip.name, - }; - } -} - -class ColorFilterLayer with PictureEngineLayer implements ui.ColorFilterEngineLayer { - ColorFilterLayer(this.operation); - - @override - final ColorFilterOperation operation; - - @override - ColorFilterLayer emptyClone() => ColorFilterLayer(operation); -} - -class ColorFilterOperation implements LayerOperation { - ColorFilterOperation(this.filter); - - final ui.ColorFilter filter; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect; - - @override - void pre(SceneCanvas canvas) { - canvas.saveLayer(ui.Rect.largest, ui.Paint()..colorFilter = filter); - } - - @override - void post(SceneCanvas canvas) { - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'ColorFilterOperation(filter: $filter)'; - - @override - Map get debugJsonDescription { - return {'type': 'colorFilter', 'filter': filter.toString()}; - } -} - -class ImageFilterLayer with PictureEngineLayer implements ui.ImageFilterEngineLayer { - ImageFilterLayer(this.operation); - - @override - final ImageFilterOperation operation; - - @override - ImageFilterLayer emptyClone() => ImageFilterLayer(operation); -} - -class ImageFilterOperation implements LayerOperation { - ImageFilterOperation(this.filter, this.offset); - - final SceneImageFilter filter; - final ui.Offset offset; - - @override - ui.Rect mapRect(ui.Rect contentRect) => filter.filterBounds(contentRect).shift(offset); - - @override - void pre(SceneCanvas canvas) { - if (offset != ui.Offset.zero) { - canvas.save(); - canvas.translate(offset.dx, offset.dy); - } - canvas.saveLayer(ui.Rect.largest, ui.Paint()..imageFilter = filter); - } - - @override - void post(SceneCanvas canvas) { - if (offset != ui.Offset.zero) { - canvas.restore(); - } - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() { - PlatformViewStyling styling = const PlatformViewStyling(); - if (offset != ui.Offset.zero) { - styling = PlatformViewStyling(position: PlatformViewPosition.offset(offset)); - } - final Matrix4? transform = filter.transform; - if (transform != null) { - styling = PlatformViewStyling.combine( - styling, - PlatformViewStyling(position: PlatformViewPosition.transform(transform)), - ); - } - return const PlatformViewStyling(); - } - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'ImageFilterOperation(filter: $filter)'; - - @override - Map get debugJsonDescription { - return { - 'type': 'imageFilter', - 'filter': filter.toString(), - 'offset': {'x': offset.dx, 'y': offset.dy}, - }; - } -} - -class OffsetLayer with PictureEngineLayer implements ui.OffsetEngineLayer { - OffsetLayer(this.operation); - - @override - final OffsetOperation operation; - - @override - OffsetLayer emptyClone() => OffsetLayer(operation); -} - -class OffsetOperation implements LayerOperation { - OffsetOperation(this.dx, this.dy); - - final double dx; - final double dy; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect.shift(ui.Offset(dx, dy)); - - @override - void pre(SceneCanvas canvas) { - canvas.save(); - canvas.translate(dx, dy); - } - - @override - void post(SceneCanvas canvas) { - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() => - PlatformViewStyling(position: PlatformViewPosition.offset(ui.Offset(dx, dy))); - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'OffsetOperation(dx: $dx, dy: $dy)'; - - @override - Map get debugJsonDescription { - return { - 'type': 'offset', - 'offset': {'x': dx, 'y': dy}, - }; - } -} - -class OpacityLayer with PictureEngineLayer implements ui.OpacityEngineLayer { - OpacityLayer(this.operation); - - @override - final OpacityOperation operation; - - @override - OpacityLayer emptyClone() => OpacityLayer(operation); -} - -class OpacityOperation implements LayerOperation { - OpacityOperation(this.alpha, this.offset); - - final int alpha; - final ui.Offset offset; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect.shift(offset); - - @override - void pre(SceneCanvas canvas) { - if (offset != ui.Offset.zero) { - canvas.save(); - canvas.translate(offset.dx, offset.dy); - } - canvas.saveLayer(ui.Rect.largest, ui.Paint()..color = ui.Color.fromARGB(alpha, 0, 0, 0)); - } - - @override - void post(SceneCanvas canvas) { - canvas.restore(); - if (offset != ui.Offset.zero) { - canvas.restore(); - } - } - - @override - PlatformViewStyling createPlatformViewStyling() => PlatformViewStyling( - position: offset != ui.Offset.zero - ? PlatformViewPosition.offset(offset) - : const PlatformViewPosition.zero(), - opacity: alpha.toDouble() / 255.0, - ); - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'OpacityOperation(offset: $offset, alpha: $alpha)'; - - @override - Map get debugJsonDescription { - return { - 'type': 'opacity', - 'alpha': alpha, - 'offset': {'x': offset.dx, 'y': offset.dy}, - }; - } -} - -class TransformLayer with PictureEngineLayer implements ui.TransformEngineLayer { - TransformLayer(this.operation); - - @override - final TransformOperation operation; - - @override - TransformLayer emptyClone() => TransformLayer(operation); -} - -class TransformOperation implements LayerOperation { - TransformOperation(this.transform); - - final Float64List transform; - - Matrix4? _memoizedMatrix; - Matrix4 get matrix => - _memoizedMatrix ?? (_memoizedMatrix = Matrix4.fromFloat32List(toMatrix32(transform))); - - @override - ui.Rect mapRect(ui.Rect contentRect) => matrix.transformRect(contentRect); - - @override - void pre(SceneCanvas canvas) { - canvas.save(); - canvas.transform(transform); - } - - @override - void post(SceneCanvas canvas) { - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() => - PlatformViewStyling(position: PlatformViewPosition.transform(matrix)); - - @override - bool get affectsBackdrop => false; - - @override - String toString() => 'TransformOperation(matrix: $matrix)'; - - @override - Map get debugJsonDescription { - return {'type': 'transform', 'matrix': transform.toList()}; - } -} - -class ShaderMaskLayer with PictureEngineLayer implements ui.ShaderMaskEngineLayer { - ShaderMaskLayer(this.operation); - - @override - final ShaderMaskOperation operation; - - @override - ShaderMaskLayer emptyClone() => ShaderMaskLayer(operation); -} - -class ShaderMaskOperation implements LayerOperation { - ShaderMaskOperation(this.shader, this.maskRect, this.blendMode); - - final ui.Shader shader; - final ui.Rect maskRect; - final ui.BlendMode blendMode; - - @override - ui.Rect mapRect(ui.Rect contentRect) => contentRect; - - @override - void pre(SceneCanvas canvas) { - canvas.saveLayer(ui.Rect.largest, ui.Paint()); - } - - @override - void post(SceneCanvas canvas) { - canvas.save(); - canvas.translate(maskRect.left, maskRect.top); - canvas.drawRect( - ui.Rect.fromLTWH(0, 0, maskRect.width, maskRect.height), - ui.Paint() - ..blendMode = blendMode - ..shader = shader, - ); - canvas.restore(); - canvas.restore(); - } - - @override - PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling(); - - @override - bool get affectsBackdrop => false; - - @override - String toString() => - 'ShaderMaskOperation(shader: $shader, maskRect: $maskRect, blendMode: $blendMode)'; - - @override - Map get debugJsonDescription { - return { - 'type': 'shaderMask', - 'shader': shader.toString(), - 'maskRect': { - 'left': maskRect.left, - 'top': maskRect.top, - 'right': maskRect.right, - 'bottom': maskRect.bottom, - }, - }; - } -} - -class PlatformView { - PlatformView(this.viewId, this.bounds, this.styling); - - final int viewId; - - // The bounds of this platform view, in the layer's local coordinate space. - final ui.Rect bounds; - - final PlatformViewStyling styling; - - @override - String toString() { - return 'PlatformView(viewId: $viewId, bounds: $bounds, styling: $styling)'; - } -} - -class LayerSlice { - LayerSlice(this.picture, this.platformViews); - - // The picture of native flutter content to be rendered - ScenePicture picture; - - // Platform views to be placed on top of the flutter content. - final List platformViews; - - void dispose() { - picture.dispose(); - } -} - -mixin PictureEngineLayer implements ui.EngineLayer { - // Each layer is represented as a series of "slices" which contain flutter content - // with platform views on top. This is ordered from bottommost to topmost. - List slices = []; - - List drawCommands = []; - PlatformViewStyling platformViewStyling = const PlatformViewStyling(); - - LayerOperation get operation; - - PictureEngineLayer emptyClone(); - - @override - void dispose() { - for (final LayerSlice? slice in slices) { - slice?.dispose(); - } - } - - @override - String toString() { - return 'PictureEngineLayer($operation)'; - } - - Map get debugJsonDescription { - return { - 'operation': operation.debugJsonDescription, - 'commands': drawCommands.map((c) => c.debugJsonDescription).toList(), - }; - } - - bool get isSimple { - if (slices.length > 1) { - return false; - } - final LayerSlice? singleSlice = slices.firstOrNull; - if (singleSlice == null || singleSlice.platformViews.isEmpty) { - return true; - } - return false; - } -} - -abstract class LayerOperation { - const LayerOperation(); - - // Given an input content rectangle, this returns a conservative estimate of - // the covering rectangle of the content after it has been processed by the - // layer operation. - ui.Rect mapRect(ui.Rect contentRect); - - void pre(SceneCanvas canvas); - void post(SceneCanvas canvas); - - PlatformViewStyling createPlatformViewStyling(); - - /// Indicates whether this operation's `pre` and `post` methods should be - /// invoked even if it contains no pictures. (Most operations don't need to - /// actually be performed at all if they don't contain any pictures.) - bool get affectsBackdrop; - - Map get debugJsonDescription; -} - -sealed class LayerDrawCommand { - Map get debugJsonDescription; -} - -class PictureDrawCommand extends LayerDrawCommand { - PictureDrawCommand(this.offset, this.picture, this.sliceIndex); - - final int sliceIndex; - final ui.Offset offset; - final ScenePicture picture; - - @override - Map get debugJsonDescription { - final bounds = picture.cullRect; - return { - 'type': 'picture', - 'sliceIndex': sliceIndex, - 'offset': {'x': offset.dx, 'y': offset.dy}, - 'localBounds': { - 'left': bounds.left, - 'top': bounds.top, - 'right': bounds.right, - 'bottom': bounds.bottom, - }, - }; - } -} - -class PlatformViewDrawCommand extends LayerDrawCommand { - PlatformViewDrawCommand(this.viewId, this.bounds, this.sliceIndex); - - final int sliceIndex; - final int viewId; - final ui.Rect bounds; - - @override - Map get debugJsonDescription { - return { - 'type': 'platformView', - 'sliceIndex': sliceIndex, - 'viewId': viewId, - 'localBounds': { - 'left': bounds.left, - 'top': bounds.top, - 'right': bounds.right, - 'bottom': bounds.bottom, - }, - }; - } -} - -class RetainedLayerDrawCommand extends LayerDrawCommand { - RetainedLayerDrawCommand(this.layer); - - final PictureEngineLayer layer; - - @override - Map get debugJsonDescription { - return {'type': 'layer', 'layer': layer.debugJsonDescription}; - } -} - -// Represents how a platform view should be positioned in the scene. -// This object is immutable, so it can be reused across different platform -// views that have the same positioning. -class PlatformViewPosition { - // No transformation at all. We leave both fields null. - const PlatformViewPosition.zero() : offset = null, transform = null; - - // A simple offset is the most common scenario. In those cases, we only - // store the offset and leave the transform as null - const PlatformViewPosition.offset(this.offset) : transform = null; - - // In more complex cases, we store the transform. In those cases, the offset - // is left as null. - const PlatformViewPosition.transform(this.transform) : offset = null; - - bool get isZero => (offset == null) && (transform == null); - - ui.Rect mapLocalToGlobal(ui.Rect rect) { - if (offset != null) { - return rect.shift(offset!); - } - if (transform != null) { - return transform!.transformRect(rect); - } - return rect; - } - - // Note that by construction only one of these can be set at any given time, not both. - final ui.Offset? offset; - final Matrix4? transform; - - static PlatformViewPosition combine(PlatformViewPosition outer, PlatformViewPosition inner) { - // We try to reuse existing objects if possible, if they are immutable. - if (outer.isZero) { - return inner; - } - if (inner.isZero) { - return outer; - } - final ui.Offset? outerOffset = outer.offset; - final ui.Offset? innerOffset = inner.offset; - if (outerOffset != null && innerOffset != null) { - // Both positions are simple offsets, so they can be combined cheaply - // into another offset. - return PlatformViewPosition.offset(outerOffset + innerOffset); - } - - // Otherwise, at least one of the positions involves a matrix transform. - final Matrix4 innerTransform; - final Matrix4 outerTransform; - if (innerOffset != null) { - innerTransform = Matrix4.translationValues(innerOffset.dx, innerOffset.dy, 0); - } else { - innerTransform = inner.transform!; - } - if (outerOffset != null) { - outerTransform = Matrix4.translationValues(outerOffset.dx, outerOffset.dy, 0); - } else { - outerTransform = outer.transform!; - } - return PlatformViewPosition.transform(outerTransform.multiplied(innerTransform)); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other is! PlatformViewPosition) { - return false; - } - return (offset == other.offset) && (transform == other.transform); - } - - @override - int get hashCode { - return Object.hash(offset, transform); - } - - @override - String toString() { - if (offset != null) { - return 'PlatformViewPosition(offset: $offset)'; - } - if (transform != null) { - return 'PlatformViewPosition(transform: $transform)'; - } - return 'PlatformViewPosition(zero)'; - } -} - -// Represents the styling to be performed on a platform view when it is -// composited. This object is immutable so that it can be reused with different -// platform views that have the same styling. -class PlatformViewStyling { - const PlatformViewStyling({ - this.position = const PlatformViewPosition.zero(), - this.clip = const PlatformViewNoClip(), - this.opacity = 1.0, - }); - - bool get isDefault => position.isZero && (opacity == 1.0) && clip is PlatformViewNoClip; - - final PlatformViewPosition position; - final double opacity; - final PlatformViewClip clip; - - ui.Rect mapLocalToGlobal(ui.Rect rect) { - return position.mapLocalToGlobal(rect).intersect(clip.outerRect); - } - - static PlatformViewStyling combine(PlatformViewStyling outer, PlatformViewStyling inner) { - // Attempt to reuse one of the existing immutable objects. - if (outer.isDefault) { - return inner; - } - if (inner.isDefault) { - return outer; - } - return PlatformViewStyling( - position: PlatformViewPosition.combine(outer.position, inner.position), - clip: PlatformViewClip.combine(outer.clip, inner.clip.positioned(outer.position)), - opacity: outer.opacity * inner.opacity, - ); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other is! PlatformViewStyling) { - return false; - } - return (position == other.position) && (opacity == other.opacity) && (clip == other.clip); - } - - @override - int get hashCode { - return Object.hash(position, opacity, clip); - } - - @override - String toString() { - return 'PlatformViewStyling(position: $position, clip: $clip, opacity: $opacity)'; - } -} - -sealed class PlatformViewClip { - PlatformViewClip positioned(PlatformViewPosition position); - - /// The largest rectangle that is entirely inside the clip region. All - /// inside of this region is unclipped. - ui.Rect get innerRect; - - /// The bounding rectangle of the clip region. All content outside of this - /// region is clipped. - ui.Rect get outerRect; - - ScenePath get toPath; - - static bool rectCovers(ui.Rect covering, ui.Rect covered) { - return covering.left <= covered.left && - covering.right >= covered.right && - covering.top <= covered.top && - covering.bottom >= covered.bottom; - } - - static PlatformViewClip combine(PlatformViewClip outer, PlatformViewClip inner) { - if (outer is PlatformViewNoClip) { - return inner; - } - if (inner is PlatformViewNoClip) { - return outer; - } - - if (rectCovers(outer.innerRect, inner.outerRect)) { - return inner; - } - - if (rectCovers(inner.innerRect, outer.outerRect)) { - return outer; - } - - return PlatformViewPathClip( - ui.Path.combine(ui.PathOperation.intersect, outer.toPath, inner.toPath) as ScenePath, - ); - } -} - -class PlatformViewNoClip implements PlatformViewClip { - const PlatformViewNoClip(); - - @override - PlatformViewClip positioned(PlatformViewPosition positioned) { - return this; - } - - @override - ScenePath get toPath => ui.Path() as ScenePath; - - @override - bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == PlatformViewNoClip); - } - - @override - int get hashCode => runtimeType.hashCode; - - @override - ui.Rect get innerRect => ui.Rect.zero; - - @override - ui.Rect get outerRect => ui.Rect.largest; - - @override - String toString() { - return 'PlatformViewClip(none)'; - } -} - -class PlatformViewRectClip implements PlatformViewClip { - const PlatformViewRectClip(this.rect); - - final ui.Rect rect; - - @override - PlatformViewClip positioned(PlatformViewPosition position) { - if (position.isZero) { - return this; - } - final ui.Offset? offset = position.offset; - if (offset != null) { - return PlatformViewRectClip(rect.shift(offset)); - } else { - return PlatformViewPathClip(toPath.transform(position.transform!.toFloat64()) as ScenePath); - } - } - - @override - ScenePath get toPath => (ui.Path() as ScenePath)..addRect(rect); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is PlatformViewRectClip && rect == other.rect; - } - - @override - int get hashCode => Object.hash(runtimeType, rect); - - @override - ui.Rect get innerRect => rect; - - @override - ui.Rect get outerRect => rect; - - @override - String toString() { - return 'PlatformViewRectClip($rect)'; - } -} - -class PlatformViewRRectClip implements PlatformViewClip { - const PlatformViewRRectClip(this.rrect); - - final ui.RRect rrect; - - @override - PlatformViewClip positioned(PlatformViewPosition position) { - if (position.isZero) { - return this; - } - final ui.Offset? offset = position.offset; - if (offset != null) { - return PlatformViewRRectClip(rrect.shift(offset)); - } else { - return PlatformViewPathClip(toPath.transform(position.transform!.toFloat64()) as ScenePath); - } - } - - @override - ScenePath get toPath => (ui.Path() as ScenePath)..addRRect(rrect); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is PlatformViewRRectClip && rrect == other.rrect; - } - - @override - int get hashCode => Object.hash(runtimeType, rrect); - - @override - ui.Rect get innerRect => rrect.safeInnerRect; - - @override - ui.Rect get outerRect => rrect.outerRect; - - @override - String toString() { - return 'PlatformViewRRectClip($rrect)'; - } -} - -class PlatformViewPathClip implements PlatformViewClip { - PlatformViewPathClip(this.path); - - final ScenePath path; - - @override - PlatformViewClip positioned(PlatformViewPosition position) { - if (position.isZero) { - return this; - } - - final ui.Offset? offset = position.offset; - if (offset != null) { - return PlatformViewPathClip(path.shift(offset) as ScenePath); - } else { - return PlatformViewPathClip(path.transform(position.transform!.toFloat64()) as ScenePath); - } - } - - @override - ScenePath get toPath => path; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is PlatformViewPathClip && path == other.path; - } - - @override - int get hashCode => Object.hash(runtimeType, path); - - @override - ui.Rect get innerRect => ui.Rect.zero; - - @override - ui.Rect get outerRect => path.getBounds(); - - @override - String toString() { - return 'PlatformViewPathClip($path)'; - } -} - -class LayerSliceBuilder { - @visibleForTesting - static (ui.PictureRecorder, SceneCanvas) Function(ui.Rect)? debugRecorderFactory; - - static (ui.PictureRecorder, SceneCanvas) defaultRecorderFactory(ui.Rect rect) { - final ui.PictureRecorder recorder = ui.PictureRecorder(); - final SceneCanvas canvas = ui.Canvas(recorder, rect) as SceneCanvas; - return (recorder, canvas); - } - - void addPicture(ui.Offset offset, ScenePicture picture) { - pictures.add((picture, offset)); - final ui.Rect pictureRect = picture.cullRect.shift(offset); - cullRect = cullRect?.expandToInclude(pictureRect) ?? pictureRect; - } - - (ui.PictureRecorder, SceneCanvas) createRecorder(ui.Rect rect) => - debugRecorderFactory != null ? debugRecorderFactory!(rect) : defaultRecorderFactory(rect); - - LayerSlice buildWithOperation(LayerOperation operation, ui.Rect? backdropRect) { - final ui.Rect effectiveRect; - if (backdropRect != null && cullRect != null) { - effectiveRect = cullRect!.expandToInclude(backdropRect); - } else { - effectiveRect = backdropRect ?? cullRect ?? ui.Rect.zero; - } - final (recorder, canvas) = createRecorder(operation.mapRect(effectiveRect)); - operation.pre(canvas); - for (final (picture, offset) in pictures) { - if (offset != ui.Offset.zero) { - canvas.save(); - canvas.translate(offset.dx, offset.dy); - canvas.drawPicture(picture); - canvas.restore(); - } else { - canvas.drawPicture(picture); - } - } - operation.post(canvas); - final ui.Picture picture = recorder.endRecording(); - return LayerSlice(picture as ScenePicture, platformViews); - } - - final List<(ScenePicture, ui.Offset)> pictures = []; - ui.Rect? cullRect; - final List platformViews = []; -} - -class LayerBuilder { - factory LayerBuilder.rootLayer() { - return LayerBuilder._(null, EngineRootLayer()); - } - - factory LayerBuilder.childLayer({ - required LayerBuilder parent, - required PictureEngineLayer layer, - }) { - return LayerBuilder._(parent, layer); - } - - LayerBuilder._(this.parent, this.layer); - - final LayerBuilder? parent; - final PictureEngineLayer layer; - - final List sliceBuilders = []; - final List drawCommands = []; - - ui.Rect? getCurrentBackdropRectAtSliceIndex(int index) { - final parentRect = parent?.getCurrentBackdropRectAtSliceIndex(index); - final sliceBuilder = index < sliceBuilders.length ? sliceBuilders[index] : null; - final sliceRect = sliceBuilder?.cullRect; - final ui.Rect? combinedRect; - if (sliceRect != null && parentRect != null) { - combinedRect = parentRect.expandToInclude(sliceRect); - } else { - combinedRect = parentRect ?? sliceRect; - } - return combinedRect == null ? null : layer.operation.mapRect(combinedRect); - } - - int getCurrentSliceCount() { - final parentSliceCount = parent?.getCurrentSliceCount(); - if (parentSliceCount != null) { - return math.max(parentSliceCount, sliceBuilders.length); - } else { - return sliceBuilders.length; - } - } - - PlatformViewStyling? _memoizedPlatformViewStyling; - PlatformViewStyling get platformViewStyling { - return _memoizedPlatformViewStyling ??= layer.operation.createPlatformViewStyling(); - } - - PlatformViewStyling? _memoizedGlobalPlatformViewStyling; - PlatformViewStyling get globalPlatformViewStyling { - if (_memoizedGlobalPlatformViewStyling != null) { - return _memoizedGlobalPlatformViewStyling!; - } - if (parent != null) { - return _memoizedGlobalPlatformViewStyling ??= PlatformViewStyling.combine( - parent!.globalPlatformViewStyling, - platformViewStyling, - ); - } - return _memoizedGlobalPlatformViewStyling ??= platformViewStyling; - } - - LayerSliceBuilder getOrCreateSliceBuilderAtIndex(int index) { - while (sliceBuilders.length <= index) { - sliceBuilders.add(null); - } - final LayerSliceBuilder? existingSliceBuilder = sliceBuilders[index]; - if (existingSliceBuilder != null) { - return existingSliceBuilder; - } - final LayerSliceBuilder newSliceBuilder = LayerSliceBuilder(); - sliceBuilders[index] = newSliceBuilder; - return newSliceBuilder; - } - - void addPicture(ui.Offset offset, ui.Picture picture, {required int sliceIndex}) { - final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(sliceIndex); - sliceBuilder.addPicture(offset, picture as ScenePicture); - drawCommands.add(PictureDrawCommand(offset, picture, sliceIndex)); - } - - void addPlatformView(int viewId, {required ui.Rect bounds, required int sliceIndex}) { - final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(sliceIndex); - sliceBuilder.platformViews.add(PlatformView(viewId, bounds, platformViewStyling)); - drawCommands.add(PlatformViewDrawCommand(viewId, bounds, sliceIndex)); - } - - void mergeLayer(PictureEngineLayer layer) { - for (int i = 0; i < layer.slices.length; i++) { - final LayerSlice? slice = layer.slices[i]; - if (slice != null) { - final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(i); - sliceBuilder.addPicture(ui.Offset.zero, slice.picture); - sliceBuilder.platformViews.addAll( - slice.platformViews.map((PlatformView view) { - return PlatformView( - view.viewId, - view.bounds, - PlatformViewStyling.combine(platformViewStyling, view.styling), - ); - }), - ); - } - } - drawCommands.add(RetainedLayerDrawCommand(layer)); - } - - PictureEngineLayer sliceUp() { - final int sliceCount = layer.operation.affectsBackdrop - ? getCurrentSliceCount() - : sliceBuilders.length; - final slices = []; - for (int i = 0; i < sliceCount; i++) { - final ui.Rect? backdropRect; - if (layer.operation.affectsBackdrop) { - backdropRect = getCurrentBackdropRectAtSliceIndex(i); - } else { - backdropRect = null; - } - final LayerSliceBuilder? builder; - if (backdropRect != null) { - builder = getOrCreateSliceBuilderAtIndex(i); - } else { - builder = i < sliceBuilders.length ? sliceBuilders[i] : null; - } - slices.add(builder?.buildWithOperation(layer.operation, backdropRect)); - } - layer.slices = slices; - return layer; - } - - PictureEngineLayer build() { - layer.drawCommands = drawCommands; - layer.platformViewStyling = platformViewStyling; - return sliceUp(); - } -} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/lazy_path.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/lazy_path.dart index b662f147bac..140f9654e3b 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/lazy_path.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/lazy_path.dart @@ -8,7 +8,7 @@ import 'dart:typed_data'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -abstract class DisposablePath implements ScenePath, LayerPath { +abstract class DisposablePath implements LayerPath { @override DisposablePathMetrics computeMetrics({bool forceClosed = false}); @@ -355,7 +355,7 @@ abstract class DisposablePathConstructors { ); } -class LazyPath implements ScenePath, LayerPath, Collectable { +class LazyPath implements LayerPath, Collectable { factory LazyPath(DisposablePathConstructors constructors) => LazyPath._(constructors, ui.PathFillType.nonZero, () => constructors.createNew()); LazyPath._(this.constructors, this._fillType, this.initializer) : _commands = []; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/occlusion_map.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/occlusion_map.dart new file mode 100644 index 00000000000..49b4b08ca46 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/occlusion_map.dart @@ -0,0 +1,89 @@ +// 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. + +import 'dart:math' as math; + +import 'package:ui/ui.dart' as ui; + +sealed class OcclusionMapNode { + bool overlaps(ui.Rect rect); + OcclusionMapNode insert(ui.Rect rect); + ui.Rect get boundingBox; +} + +class OcclusionMapEmpty implements OcclusionMapNode { + @override + ui.Rect get boundingBox => ui.Rect.zero; + + @override + OcclusionMapNode insert(ui.Rect rect) => OcclusionMapLeaf(rect); + + @override + bool overlaps(ui.Rect rect) => false; +} + +class OcclusionMapLeaf implements OcclusionMapNode { + OcclusionMapLeaf(this.rect); + + final ui.Rect rect; + + @override + ui.Rect get boundingBox => rect; + + @override + OcclusionMapNode insert(ui.Rect other) => OcclusionMapBranch(this, OcclusionMapLeaf(other)); + + @override + bool overlaps(ui.Rect other) => rect.overlaps(other); +} + +class OcclusionMapBranch implements OcclusionMapNode { + OcclusionMapBranch(this.left, this.right) + : boundingBox = left.boundingBox.expandToInclude(right.boundingBox); + + final OcclusionMapNode left; + final OcclusionMapNode right; + + @override + final ui.Rect boundingBox; + + double _areaOfUnion(ui.Rect first, ui.Rect second) { + return (math.max(first.right, second.right) - math.min(first.left, second.left)) * + (math.max(first.bottom, second.bottom) - math.min(first.top, second.top)); + } + + @override + OcclusionMapNode insert(ui.Rect other) { + // Try to create nodes with the smallest possible area + final double leftOtherArea = _areaOfUnion(left.boundingBox, other); + final double rightOtherArea = _areaOfUnion(right.boundingBox, other); + final double leftRightArea = boundingBox.width * boundingBox.height; + if (leftOtherArea < rightOtherArea) { + if (leftOtherArea < leftRightArea) { + return OcclusionMapBranch(left.insert(other), right); + } + } else { + if (rightOtherArea < leftRightArea) { + return OcclusionMapBranch(left, right.insert(other)); + } + } + return OcclusionMapBranch(this, OcclusionMapLeaf(other)); + } + + @override + bool overlaps(ui.Rect rect) { + if (!boundingBox.overlaps(rect)) { + return false; + } + return left.overlaps(rect) || right.overlaps(rect); + } +} + +class OcclusionMap { + OcclusionMapNode root = OcclusionMapEmpty(); + + void addRect(ui.Rect rect) => root = root.insert(rect); + + bool overlaps(ui.Rect rect) => root.overlaps(rect); +} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views.dart deleted file mode 100644 index 956e19d8e04..00000000000 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views.dart +++ /dev/null @@ -1,8 +0,0 @@ -// 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. - -// TODO(yjbanov): The code in this file was temporarily moved to lib/web_ui/lib/ui.dart -// during the NNBD migration so that `dart:ui` does not have to export -// `dart:_engine`. NNBD does not allow exported non-migrated libraries -// from migrated libraries. diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart index dca456f3571..3916e8ad28a 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/renderer.dart @@ -275,7 +275,53 @@ abstract class Renderer { /// Map from view id to the associated [ViewRasterizer] for that view. final Map rasterizers = {}; - Future renderScene(ui.Scene scene, EngineFlutterView view); + Future renderScene(ui.Scene scene, EngineFlutterView view) async { + assert( + rasterizers.containsKey(view.viewId), + "Unable to render to a view which hasn't been registered", + ); + final ViewRasterizer rasterizer = rasterizers[view.viewId]!; + final RenderQueue renderQueue = rasterizer.queue; + final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled + ? FrameTimingRecorder() + : null; + if (renderQueue.current != null) { + // If a scene is already queued up, drop it and queue this one up instead + // so that the scene view always displays the most recently requested scene. + renderQueue.next?.completer.complete(); + final Completer completer = Completer(); + renderQueue.next = (scene: scene, completer: completer, recorder: recorder); + return completer.future; + } + final Completer completer = Completer(); + renderQueue.current = (scene: scene, completer: completer, recorder: recorder); + unawaited(_kickRenderLoop(rasterizer)); + return completer.future; + } + + Future _kickRenderLoop(ViewRasterizer rasterizer) async { + final RenderQueue renderQueue = rasterizer.queue; + while (renderQueue.current != null) { + final RenderRequest current = renderQueue.current!; + try { + await _renderScene(current.scene, rasterizer, current.recorder); + current.completer.complete(); + } catch (error, stackTrace) { + current.completer.completeError(error, stackTrace); + } + renderQueue.current = renderQueue.next; + renderQueue.next = null; + } + } + + Future _renderScene( + ui.Scene scene, + ViewRasterizer rasterizer, + FrameTimingRecorder? recorder, + ) async { + await rasterizer.draw((scene as LayerScene).layerTree, recorder); + recorder?.submitTimings(); + } void dumpDebugInfo(); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/scene_builder.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/scene_builder.dart deleted file mode 100644 index 1031dc9c51c..00000000000 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/scene_builder.dart +++ /dev/null @@ -1,489 +0,0 @@ -// 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. - -import 'dart:math' as math; -import 'dart:typed_data'; - -import 'package:ui/src/engine.dart'; -import 'package:ui/ui.dart' as ui; - -// This file implements a SceneBuilder and Scene that works with any renderer -// implementation that provides: -// * A `ui.Canvas` that conforms to `SceneCanvas` -// * A `ui.Picture` that conforms to `ScenePicture` -// * A `ui.ImageFilter` that conforms to `SceneImageFilter` -// -// These contain a few augmentations to the normal `dart:ui` API that provide -// additional sizing information that the scene builder uses to determine how -// these object might occlude one another. - -class EngineScene implements ui.Scene { - EngineScene(this.rootLayer); - - final EngineRootLayer rootLayer; - - // We keep a refcount here because this can be asynchronously rendered, so we - // don't necessarily want to dispose immediately when the user calls dispose. - // Instead, we need to stay alive until we're done rendering. - int _refCount = 1; - - void beginRender() { - assert(_refCount > 0); - _refCount++; - } - - void endRender() { - _refCount--; - _disposeIfNeeded(); - } - - @override - void dispose() { - _refCount--; - _disposeIfNeeded(); - } - - void _disposeIfNeeded() { - assert(_refCount >= 0); - if (_refCount == 0) { - rootLayer.dispose(); - } - } - - @override - Future toImage(int width, int height) async { - return toImageSync(width, height); - } - - @override - ui.Image toImageSync(int width, int height) { - final ui.PictureRecorder recorder = ui.PictureRecorder(); - final ui.Rect canvasRect = ui.Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); - final ui.Canvas canvas = ui.Canvas(recorder, canvasRect); - - // Only rasterizes the pictures. - for (final LayerSlice? slice in rootLayer.slices) { - if (slice != null) { - canvas.drawPicture(slice.picture); - } - } - return recorder.endRecording().toImageSync(width, height); - } - - Map get debugJsonDescription { - return {'rootLayer': rootLayer.debugJsonDescription}; - } -} - -sealed class OcclusionMapNode { - bool overlaps(ui.Rect rect); - OcclusionMapNode insert(ui.Rect rect); - ui.Rect get boundingBox; -} - -class OcclusionMapEmpty implements OcclusionMapNode { - @override - ui.Rect get boundingBox => ui.Rect.zero; - - @override - OcclusionMapNode insert(ui.Rect rect) => OcclusionMapLeaf(rect); - - @override - bool overlaps(ui.Rect rect) => false; -} - -class OcclusionMapLeaf implements OcclusionMapNode { - OcclusionMapLeaf(this.rect); - - final ui.Rect rect; - - @override - ui.Rect get boundingBox => rect; - - @override - OcclusionMapNode insert(ui.Rect other) => OcclusionMapBranch(this, OcclusionMapLeaf(other)); - - @override - bool overlaps(ui.Rect other) => rect.overlaps(other); -} - -class OcclusionMapBranch implements OcclusionMapNode { - OcclusionMapBranch(this.left, this.right) - : boundingBox = left.boundingBox.expandToInclude(right.boundingBox); - - final OcclusionMapNode left; - final OcclusionMapNode right; - - @override - final ui.Rect boundingBox; - - double _areaOfUnion(ui.Rect first, ui.Rect second) { - return (math.max(first.right, second.right) - math.min(first.left, second.left)) * - (math.max(first.bottom, second.bottom) - math.max(first.top, second.top)); - } - - @override - OcclusionMapNode insert(ui.Rect other) { - // Try to create nodes with the smallest possible area - final double leftOtherArea = _areaOfUnion(left.boundingBox, other); - final double rightOtherArea = _areaOfUnion(right.boundingBox, other); - final double leftRightArea = boundingBox.width * boundingBox.height; - if (leftOtherArea < rightOtherArea) { - if (leftOtherArea < leftRightArea) { - return OcclusionMapBranch(left.insert(other), right); - } - } else { - if (rightOtherArea < leftRightArea) { - return OcclusionMapBranch(left, right.insert(other)); - } - } - return OcclusionMapBranch(this, OcclusionMapLeaf(other)); - } - - @override - bool overlaps(ui.Rect rect) { - if (!boundingBox.overlaps(rect)) { - return false; - } - return left.overlaps(rect) || right.overlaps(rect); - } -} - -class OcclusionMap { - OcclusionMapNode root = OcclusionMapEmpty(); - - void addRect(ui.Rect rect) => root = root.insert(rect); - - bool overlaps(ui.Rect rect) => root.overlaps(rect); -} - -class SceneSlice { - final OcclusionMap pictureOcclusionMap = OcclusionMap(); - final OcclusionMap platformViewOcclusionMap = OcclusionMap(); -} - -class EngineSceneBuilder implements ui.SceneBuilder { - LayerBuilder currentBuilder = LayerBuilder.rootLayer(); - - final List sceneSlices = [SceneSlice()]; - - // This represents the simplest case with no platform views, which is a fast path - // that allows us to avoid work tracking the pictures themselves. - bool _isSimple = true; - - @override - void addPerformanceOverlay(int enabledOptions, ui.Rect bounds) { - // We don't plan to implement this on the web. - throw UnimplementedError(); - } - - @override - void addPicture( - ui.Offset offset, - ui.Picture picture, { - bool isComplexHint = false, - bool willChangeHint = false, - }) { - final int sliceIndex = _placePicture( - offset, - picture as ScenePicture, - currentBuilder.globalPlatformViewStyling, - ); - currentBuilder.addPicture(offset, picture, sliceIndex: sliceIndex); - } - - // This function determines the lowest scene slice that this picture can be placed - // into and adds it to that slice's occlusion map. - // - // The picture is placed in the last slice where it either intersects with a picture - // in the slice or it intersects with a platform view in the preceding slice. If the - // picture intersects with a platform view in the last slice, a new slice is added at - // the end and the picture goes in there. - int _placePicture(ui.Offset offset, ScenePicture picture, PlatformViewStyling styling) { - if (_isSimple) { - // This is the fast path where there are no platform views. The picture should - // just be placed on the bottom (and only) slice. - return 0; - } - final ui.Rect cullRect = picture.cullRect.shift(offset); - final ui.Rect mappedCullRect = styling.mapLocalToGlobal(cullRect); - int sliceIndex = sceneSlices.length; - while (sliceIndex > 0) { - final SceneSlice sliceBelow = sceneSlices[sliceIndex - 1]; - if (sliceBelow.platformViewOcclusionMap.overlaps(mappedCullRect)) { - break; - } - sliceIndex--; - if (sliceBelow.pictureOcclusionMap.overlaps(mappedCullRect)) { - break; - } - } - if (sliceIndex == 0) { - // Don't bother to populate the lowest occlusion map with pictures, since - // we never hit test against pictures in the bottom slice. - return sliceIndex; - } - if (sliceIndex == sceneSlices.length) { - // Insert a new slice. - sceneSlices.add(SceneSlice()); - } - final SceneSlice slice = sceneSlices[sliceIndex]; - slice.pictureOcclusionMap.addRect(mappedCullRect); - return sliceIndex; - } - - @override - void addPlatformView( - int viewId, { - ui.Offset offset = ui.Offset.zero, - double width = 0.0, - double height = 0.0, - }) { - final ui.Rect platformViewRect = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height); - final int sliceIndex = _placePlatformView( - viewId, - platformViewRect, - currentBuilder.globalPlatformViewStyling, - ); - currentBuilder.addPlatformView(viewId, bounds: platformViewRect, sliceIndex: sliceIndex); - } - - // This function determines the lowest scene slice this platform view can be placed - // into and adds it to that slice's occlusion map. - // - // The platform view is placed into the last slice where it intersects with a picture - // or a platform view. - int _placePlatformView(int viewId, ui.Rect rect, PlatformViewStyling styling) { - // Once we add a platform view, we actually have to do proper occlusion tracking. - _isSimple = false; - - final ui.Rect globalPlatformViewRect = styling.mapLocalToGlobal(rect); - int sliceIndex = sceneSlices.length - 1; - while (sliceIndex > 0) { - final SceneSlice slice = sceneSlices[sliceIndex]; - if (slice.platformViewOcclusionMap.overlaps(globalPlatformViewRect) || - slice.pictureOcclusionMap.overlaps(globalPlatformViewRect)) { - break; - } - sliceIndex--; - } - final SceneSlice slice = sceneSlices[sliceIndex]; - slice.platformViewOcclusionMap.addRect(globalPlatformViewRect); - return sliceIndex; - } - - @override - void addRetained(ui.EngineLayer retainedLayer) { - final PictureEngineLayer placedEngineLayer = _placeRetainedLayer( - retainedLayer as PictureEngineLayer, - currentBuilder.globalPlatformViewStyling, - ); - currentBuilder.mergeLayer(placedEngineLayer); - } - - PictureEngineLayer _placeRetainedLayer( - PictureEngineLayer retainedLayer, - PlatformViewStyling styling, - ) { - if (_isSimple && retainedLayer.isSimple) { - // There are no platform views, so we don't need to do any occlusion tracking - // and can simply merge the layer. - return retainedLayer; - } - bool needsRebuild = false; - final List revisedDrawCommands = []; - final PlatformViewStyling combinedStyling = PlatformViewStyling.combine( - styling, - retainedLayer.platformViewStyling, - ); - for (final LayerDrawCommand command in retainedLayer.drawCommands) { - switch (command) { - case PictureDrawCommand( - offset: final ui.Offset offset, - picture: final ScenePicture picture, - ): - final int sliceIndex = _placePicture(offset, picture, combinedStyling); - if (command.sliceIndex != sliceIndex) { - needsRebuild = true; - } - revisedDrawCommands.add(PictureDrawCommand(offset, picture, sliceIndex)); - case PlatformViewDrawCommand(viewId: final int viewId, bounds: final ui.Rect bounds): - final int sliceIndex = _placePlatformView(viewId, bounds, combinedStyling); - if (command.sliceIndex != sliceIndex) { - needsRebuild = true; - } - revisedDrawCommands.add(PlatformViewDrawCommand(viewId, bounds, sliceIndex)); - case RetainedLayerDrawCommand(layer: final PictureEngineLayer sublayer): - final PictureEngineLayer revisedSublayer = _placeRetainedLayer(sublayer, combinedStyling); - if (sublayer != revisedSublayer) { - needsRebuild = true; - } - revisedDrawCommands.add(RetainedLayerDrawCommand(revisedSublayer)); - } - } - - if (!needsRebuild) { - // No elements changed which slice position they are in, so we can simply - // merge the existing layer down and don't have to redraw individual elements. - return retainedLayer; - } - - // Otherwise, we replace the commands of the layer to create a new one. - currentBuilder = LayerBuilder.childLayer( - parent: currentBuilder, - layer: retainedLayer.emptyClone(), - ); - for (final LayerDrawCommand command in revisedDrawCommands) { - switch (command) { - case PictureDrawCommand( - offset: final ui.Offset offset, - picture: final ScenePicture picture, - ): - currentBuilder.addPicture(offset, picture, sliceIndex: command.sliceIndex); - case PlatformViewDrawCommand(viewId: final int viewId, bounds: final ui.Rect bounds): - currentBuilder.addPlatformView(viewId, bounds: bounds, sliceIndex: command.sliceIndex); - case RetainedLayerDrawCommand(layer: final PictureEngineLayer layer): - currentBuilder.mergeLayer(layer); - } - } - final PictureEngineLayer newLayer = currentBuilder.build(); - currentBuilder = currentBuilder.parent!; - return newLayer; - } - - @override - void addTexture( - int textureId, { - ui.Offset offset = ui.Offset.zero, - double width = 0.0, - double height = 0.0, - bool freeze = false, - ui.FilterQuality filterQuality = ui.FilterQuality.low, - }) { - // addTexture is not implemented on web. - } - - @override - ui.BackdropFilterEngineLayer pushBackdropFilter( - ui.ImageFilter filter, { - ui.BlendMode blendMode = ui.BlendMode.srcOver, - ui.BackdropFilterEngineLayer? oldLayer, - int? backdropId, - }) => pushLayer( - BackdropFilterLayer(BackdropFilterOperation(filter, blendMode)), - ); - - @override - ui.ClipPathEngineLayer pushClipPath( - ui.Path path, { - ui.Clip clipBehavior = ui.Clip.antiAlias, - ui.ClipPathEngineLayer? oldLayer, - }) => pushLayer(ClipPathLayer(ClipPathOperation(path as ScenePath, clipBehavior))); - - @override - ui.ClipRRectEngineLayer pushClipRRect( - ui.RRect rrect, { - required ui.Clip clipBehavior, - ui.ClipRRectEngineLayer? oldLayer, - }) => pushLayer(ClipRRectLayer(ClipRRectOperation(rrect, clipBehavior))); - - @override - ui.ClipRSuperellipseEngineLayer pushClipRSuperellipse( - ui.RSuperellipse rsuperellipse, { - required ui.Clip clipBehavior, - ui.ClipRSuperellipseEngineLayer? oldLayer, - }) { - return pushLayer( - ClipRSuperellipseLayer(ClipRSuperellipseOperation(rsuperellipse, clipBehavior)), - ); - } - - @override - ui.ClipRectEngineLayer pushClipRect( - ui.Rect rect, { - ui.Clip clipBehavior = ui.Clip.antiAlias, - ui.ClipRectEngineLayer? oldLayer, - }) { - return pushLayer(ClipRectLayer(ClipRectOperation(rect, clipBehavior))); - } - - @override - ui.ColorFilterEngineLayer pushColorFilter( - ui.ColorFilter filter, { - ui.ColorFilterEngineLayer? oldLayer, - }) => pushLayer(ColorFilterLayer(ColorFilterOperation(filter))); - - @override - ui.ImageFilterEngineLayer pushImageFilter( - ui.ImageFilter filter, { - ui.Offset offset = ui.Offset.zero, - ui.ImageFilterEngineLayer? oldLayer, - }) => pushLayer( - ImageFilterLayer(ImageFilterOperation(filter as SceneImageFilter, offset)), - ); - - @override - ui.OffsetEngineLayer pushOffset(double dx, double dy, {ui.OffsetEngineLayer? oldLayer}) => - pushLayer(OffsetLayer(OffsetOperation(dx, dy))); - - @override - ui.OpacityEngineLayer pushOpacity( - int alpha, { - ui.Offset offset = ui.Offset.zero, - ui.OpacityEngineLayer? oldLayer, - }) => pushLayer(OpacityLayer(OpacityOperation(alpha, offset))); - - @override - ui.ShaderMaskEngineLayer pushShaderMask( - ui.Shader shader, - ui.Rect maskRect, - ui.BlendMode blendMode, { - ui.ShaderMaskEngineLayer? oldLayer, - ui.FilterQuality filterQuality = ui.FilterQuality.low, - }) => - pushLayer(ShaderMaskLayer(ShaderMaskOperation(shader, maskRect, blendMode))); - - @override - ui.TransformEngineLayer pushTransform(Float64List matrix4, {ui.TransformEngineLayer? oldLayer}) => - pushLayer(TransformLayer(TransformOperation(matrix4))); - - @override - void setProperties( - double width, - double height, - double insetTop, - double insetRight, - double insetBottom, - double insetLeft, - bool focusable, - ) { - // Not implemented on web - } - - @override - ui.Scene build() { - while (currentBuilder.parent != null) { - pop(); - } - final PictureEngineLayer rootLayer = currentBuilder.build(); - return EngineScene(rootLayer as EngineRootLayer); - } - - @override - void pop() { - final LayerBuilder? parentBuilder = currentBuilder.parent; - if (parentBuilder == null) { - // Root layer. Nothing to pop. - return; - } - final PictureEngineLayer layer = currentBuilder.build(); - currentBuilder = parentBuilder; - currentBuilder.mergeLayer(layer); - } - - T pushLayer(T layer) { - currentBuilder = LayerBuilder.childLayer(parent: currentBuilder, layer: layer); - return layer; - } -} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/scene_painting.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/scene_painting.dart deleted file mode 100644 index ae6cc8c8692..00000000000 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/scene_painting.dart +++ /dev/null @@ -1,45 +0,0 @@ -// 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. - -import 'package:ui/ui.dart' as ui; - -import 'vector_math.dart'; - -// These are additional APIs that are not part of the `dart:ui` interface that -// are needed internally to properly implement a `SceneBuilder` on top of the -// generic Canvas/Picture api. -abstract class SceneCanvas implements ui.Canvas { - // This is the same as a normal `saveLayer` call, but we can pass a backdrop image filter. - void saveLayerWithFilter(ui.Rect? bounds, ui.Paint paint, ui.ImageFilter backdropFilter); -} - -abstract class ScenePicture implements ui.Picture { - // This is a conservative bounding box of all the drawing primitives in this picture. - ui.Rect get cullRect; -} - -abstract class ScenePictureRecorder implements ui.PictureRecorder { - /// Whether this reference to the underlying picture recorder is [dispose]d. - /// - /// This only returns a valid value if asserts are enabled, and must not be - /// used otherwise. - bool get debugDisposed; -} - -abstract class SceneImageFilter implements ui.ImageFilter { - // Since some image filters affect the actual drawing bounds of a given picture, this - // gives the maximum draw boundary for a picture with the given input bounds after it - // has been processed by the filter. - ui.Rect filterBounds(ui.Rect inputBounds); - - // The matrix image filter changes the position of the content, so when positioning - // platform views and calculating occlusion we need to take its transform into account. - Matrix4? get transform; -} - -abstract class ScenePath implements ui.Path { - // In order to properly clip platform views with paths, we need to be able to get a - // string representation of them. - String toSvgString(); -} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/scene_view.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/scene_view.dart deleted file mode 100644 index 6a3cd089065..00000000000 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/scene_view.dart +++ /dev/null @@ -1,405 +0,0 @@ -// 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. - -import 'dart:async'; - -import 'package:ui/src/engine.dart'; -import 'package:ui/ui.dart' as ui; - -const String kCanvasContainerTag = 'flt-canvas-container'; - -typedef RenderResult = ({ - List imageBitmaps, - int rasterStartMicros, - int rasterEndMicros, -}); - -// This is an interface that renders a `ScenePicture` as a `DomImageBitmap`. -// It is optionally asynchronous. It is required for the `EngineSceneView` to -// composite pictures into the canvases in the DOM tree it builds. -abstract class PictureRenderer { - FutureOr renderPictures(List picture, int width, int height); -} - -class _SceneRender { - _SceneRender(this.scene, this._completer, {this.recorder}) { - scene.beginRender(); - } - - final EngineScene scene; - final Completer _completer; - final FrameTimingRecorder? recorder; - - void done() { - scene.endRender(); - _completer.complete(); - } -} - -// This class builds a DOM tree that composites an `EngineScene`. -class EngineSceneView { - factory EngineSceneView(PictureRenderer pictureRenderer, EngineFlutterView flutterView) { - final DomElement sceneElement = createDomElement('flt-scene'); - return EngineSceneView._(pictureRenderer, flutterView, sceneElement); - } - - EngineSceneView._(this.pictureRenderer, this.flutterView, this.sceneElement); - - final PictureRenderer pictureRenderer; - final DomElement sceneElement; - final EngineFlutterView flutterView; - - List containers = []; - - _SceneRender? _currentRender; - _SceneRender? _nextRender; - - // Only populated in debug mode. - _SceneRender? _previousRender; - - Future renderScene(EngineScene scene, FrameTimingRecorder? recorder) { - if (_currentRender != null) { - // If a scene is already queued up, drop it and queue this one up instead - // so that the scene view always displays the most recently requested scene. - _nextRender?.done(); - final Completer completer = Completer(); - _nextRender = _SceneRender(scene, completer, recorder: recorder); - return completer.future; - } - final Completer completer = Completer(); - _currentRender = _SceneRender(scene, completer, recorder: recorder); - _kickRenderLoop(); - return completer.future; - } - - Future _kickRenderLoop() async { - final _SceneRender current = _currentRender!; - await _renderScene(current.scene, current.recorder); - _renderComplete(current); - _currentRender = _nextRender; - _nextRender = null; - if (_currentRender == null) { - return; - } else { - return _kickRenderLoop(); - } - } - - Future _renderScene(EngineScene scene, FrameTimingRecorder? recorder) async { - final ui.Rect screenBounds = ui.Rect.fromLTWH( - 0, - 0, - flutterView.physicalSize.width, - flutterView.physicalSize.height, - ); - final List slices = scene.rootLayer.slices; - final List picturesToRender = []; - for (final LayerSlice? slice in slices) { - if (slice == null) { - continue; - } - final ui.Rect clippedRect = slice.picture.cullRect.intersect(screenBounds); - if (clippedRect.isEmpty) { - // This picture is completely offscreen, so don't render it at all - continue; - } else { - picturesToRender.add(slice.picture); - } - } - final Map renderMap; - if (picturesToRender.isNotEmpty) { - final RenderResult renderResult = await pictureRenderer.renderPictures( - picturesToRender, - screenBounds.width.ceil(), - screenBounds.height.ceil(), - ); - renderMap = { - for (int i = 0; i < picturesToRender.length; i++) - picturesToRender[i]: renderResult.imageBitmaps[i], - }; - recorder?.recordRasterStart(renderResult.rasterStartMicros); - recorder?.recordRasterFinish(renderResult.rasterEndMicros); - } else { - renderMap = {}; - recorder?.recordRasterStart(); - recorder?.recordRasterFinish(); - } - recorder?.submitTimings(); - - final List reusableContainers = List.from(containers); - final List newContainers = []; - for (final LayerSlice? slice in slices) { - if (slice == null) { - continue; - } - final DomImageBitmap? bitmap = renderMap[slice.picture]; - if (bitmap != null) { - PictureSliceContainer? container; - for (int j = 0; j < reusableContainers.length; j++) { - final SliceContainer? candidate = reusableContainers[j]; - if (candidate is PictureSliceContainer) { - container = candidate; - reusableContainers[j] = null; - break; - } - } - - if (container != null) { - container.bounds = screenBounds; - } else { - container = PictureSliceContainer(screenBounds); - } - container.updateContents(); - container.renderBitmap(bitmap); - newContainers.add(container); - } - - for (final PlatformView view in slice.platformViews) { - // Ensure the contents of the platform view are injected into the DOM. - flutterView.dom.injectPlatformView(view.viewId); - - // Attempt to reuse a container for the existing view - PlatformViewContainer? container; - for (int j = 0; j < reusableContainers.length; j++) { - final SliceContainer? candidate = reusableContainers[j]; - if (candidate is PlatformViewContainer && candidate.viewId == view.viewId) { - container = candidate; - reusableContainers[j] = null; - break; - } - } - container ??= PlatformViewContainer(view.viewId); - container.bounds = view.bounds; - container.styling = view.styling; - container.updateContents(); - newContainers.add(container); - } - } - - for (final SliceContainer? container in reusableContainers) { - if (container != null) { - sceneElement.removeChild(container.container); - } - } - - containers = newContainers; - - DomElement? currentElement = sceneElement.firstElementChild; - for (final SliceContainer container in containers) { - if (currentElement == null) { - sceneElement.appendChild(container.container); - } else if (currentElement == container.container) { - currentElement = currentElement.nextElementSibling; - } else { - sceneElement.insertBefore(container.container, currentElement); - } - } - } - - void _renderComplete(_SceneRender render) { - render.done(); - if (kDebugMode) { - _previousRender = render; - } - } - - Map? dumpDebugInfo() { - if (kDebugMode && _previousRender != null) { - return _previousRender!.scene.debugJsonDescription; - } - return null; - } -} - -sealed class SliceContainer { - DomElement get container; - - void updateContents(); -} - -final class PictureSliceContainer extends SliceContainer { - factory PictureSliceContainer(ui.Rect bounds) { - final DomElement container = domDocument.createElement(kCanvasContainerTag); - final DomHTMLCanvasElement canvas = createDomCanvasElement( - width: bounds.width.toInt(), - height: bounds.height.toInt(), - ); - container.appendChild(canvas); - return PictureSliceContainer._(bounds, container, canvas); - } - - PictureSliceContainer._(this._bounds, this.container, this.canvas); - - ui.Rect _bounds; - bool _dirty = true; - - ui.Rect get bounds => _bounds; - set bounds(ui.Rect bounds) { - if (_bounds != bounds) { - _bounds = bounds; - _dirty = true; - } - } - - @override - void updateContents() { - if (_dirty) { - _dirty = false; - - final ui.Rect roundedOutBounds = ui.Rect.fromLTRB( - bounds.left.floorToDouble(), - bounds.top.floorToDouble(), - bounds.right.ceilToDouble(), - bounds.bottom.ceilToDouble(), - ); - final DomCSSStyleDeclaration style = canvas.style; - final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio; - final double logicalWidth = roundedOutBounds.width / devicePixelRatio; - final double logicalHeight = roundedOutBounds.height / devicePixelRatio; - final double logicalLeft = roundedOutBounds.left / devicePixelRatio; - final double logicalTop = roundedOutBounds.top / devicePixelRatio; - style.width = '${logicalWidth}px'; - style.height = '${logicalHeight}px'; - style.position = 'absolute'; - style.left = '${logicalLeft}px'; - style.top = '${logicalTop}px'; - canvas.width = roundedOutBounds.width.ceilToDouble(); - canvas.height = roundedOutBounds.height.ceilToDouble(); - } - } - - void renderBitmap(DomImageBitmap bitmap) { - final DomImageBitmapRenderingContext ctx = canvas.contextBitmapRenderer; - ctx.transferFromImageBitmap(bitmap); - } - - @override - final DomElement container; - final DomHTMLCanvasElement canvas; -} - -final class PlatformViewContainer extends SliceContainer { - PlatformViewContainer(this.viewId) - : container = createDomElement('flt-clip'), - slot = createPlatformViewSlot(viewId) { - container.appendChild(slot); - } - - final int viewId; - PlatformViewStyling? _styling; - ui.Rect? _bounds; - bool _dirty = false; - - ui.Path? _clipPath; - String? _clipPathString; - - @override - final DomElement container; - - final DomElement slot; - - set styling(PlatformViewStyling styling) { - if (_styling != styling) { - _styling = styling; - _dirty = true; - } - } - - set bounds(ui.Rect bounds) { - if (_bounds != bounds) { - _bounds = bounds; - _dirty = true; - } - } - - set clipPath(ScenePath? path) { - if (_clipPath == path) { - return; - } - - _clipPath = path; - _clipPathString = path?.toSvgString(); - } - - String cssStringForClip(PlatformViewClip clip, double devicePixelRatio) { - switch (clip) { - case PlatformViewNoClip(): - clipPath = null; - return ''; - case PlatformViewRectClip(): - clipPath = null; - final double top = clip.rect.top / devicePixelRatio; - final double right = clip.rect.right / devicePixelRatio; - final double bottom = clip.rect.bottom / devicePixelRatio; - final double left = clip.rect.left / devicePixelRatio; - return 'rect(${top}px ${right}px ${bottom}px ${left}px)'; - case PlatformViewRRectClip(): - clipPath = null; - final double top = clip.rrect.top / devicePixelRatio; - final double right = clip.rrect.right / devicePixelRatio; - final double bottom = clip.rrect.bottom / devicePixelRatio; - final double left = clip.rrect.left / devicePixelRatio; - final double tlRadiusX = clip.rrect.tlRadiusX / devicePixelRatio; - final double tlRadiusY = clip.rrect.tlRadiusY / devicePixelRatio; - final double trRadiusX = clip.rrect.trRadiusX / devicePixelRatio; - final double trRadiusY = clip.rrect.trRadiusY / devicePixelRatio; - final double brRadiusX = clip.rrect.brRadiusX / devicePixelRatio; - final double brRadiusY = clip.rrect.brRadiusY / devicePixelRatio; - final double blRadiusX = clip.rrect.blRadiusX / devicePixelRatio; - final double blRadiusY = clip.rrect.blRadiusY / devicePixelRatio; - return 'rect(${top}px ${right}px ${bottom}px ${left}px round ${tlRadiusX}px ${trRadiusX}px ${brRadiusX}px ${blRadiusX}px / ${tlRadiusY}px ${trRadiusY}px ${brRadiusY}px ${blRadiusY}px)'; - case PlatformViewPathClip(): - ScenePath path = clip.path; - if (devicePixelRatio != 1.0) { - final dprTransform = Matrix4.identity()..scale(1 / devicePixelRatio); - path = path.transform(dprTransform.toFloat64()) as ScenePath; - } - clipPath = path; - return "path('$_clipPathString')"; - } - } - - @override - void updateContents() { - assert(_styling != null); - assert(_bounds != null); - if (_dirty) { - final DomCSSStyleDeclaration style = slot.style; - style.position = 'absolute'; - style.width = '${_bounds!.width}px'; - style.height = '${_bounds!.height}px'; - - final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio; - final PlatformViewPosition position = _styling!.position; - - final Matrix4 transform; - if (position.transform != null) { - transform = position.transform!.clone()..translate(_bounds!.left, _bounds!.top); - } else { - final ui.Offset offset = position.offset ?? ui.Offset.zero; - transform = Matrix4.translationValues( - _bounds!.left + offset.dx, - _bounds!.top + offset.dy, - 0, - ); - } - final double inverseScale = 1.0 / devicePixelRatio; - final Matrix4 scaleMatrix = Matrix4.diagonal3Values(inverseScale, inverseScale, 1); - scaleMatrix.multiply(transform); - style.transform = float64ListToCssTransform(scaleMatrix.storage); - style.transformOrigin = '0 0 0'; - style.opacity = _styling!.opacity != 1.0 ? '${_styling!.opacity}' : ''; - - final DomCSSStyleDeclaration containerStyle = container.style; - containerStyle.position = 'absolute'; - containerStyle.width = '100%'; - containerStyle.height = '100%'; - - final String clipPathString = cssStringForClip(_styling!.clip, devicePixelRatio); - containerStyle.clipPath = clipPathString; - - _dirty = false; - } - } -} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index b3667e9eb3d..2587ee04216 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -14,6 +14,7 @@ export 'skwasm_impl/filters.dart'; export 'skwasm_impl/font_collection.dart'; export 'skwasm_impl/image.dart'; export 'skwasm_impl/memory.dart'; +export 'skwasm_impl/offscreen_canvas_rasterizer.dart'; export 'skwasm_impl/paint.dart'; export 'skwasm_impl/paragraph.dart'; export 'skwasm_impl/path.dart'; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart index 88b48cfcf55..2a2fc5a025d 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart @@ -9,7 +9,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; -class SkwasmCanvas implements SceneCanvas { +class SkwasmCanvas implements LayerCanvas { factory SkwasmCanvas(SkwasmPictureRecorder recorder, ui.Rect cullRect) => SkwasmCanvas.fromHandle( withStackScope( (StackScope s) => @@ -412,4 +412,16 @@ class SkwasmCanvas implements SceneCanvas { return scope.convertMatrix44FromNative(outMatrix); }); } + + @override + void clear(ui.Color color) { + canvasClear(_handle, color.value); + } + + @override + bool quickReject(ui.Rect rect) { + return withStackScope((StackScope s) { + return canvasQuickReject(_handle, s.convertRectToNative(rect)); + }); + } } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart index c89d05c76d2..92766c29c17 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart @@ -11,7 +11,7 @@ import 'package:ui/ui.dart' as ui; typedef ImageFilterHandleBorrow = T Function(ImageFilterHandle handle); -abstract class SkwasmImageFilter implements SceneImageFilter { +abstract class SkwasmImageFilter implements LayerImageFilter { const SkwasmImageFilter(); factory SkwasmImageFilter.blur({ diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/offscreen_canvas_rasterizer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/offscreen_canvas_rasterizer.dart new file mode 100644 index 00000000000..6c9ad205d3e --- /dev/null +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/offscreen_canvas_rasterizer.dart @@ -0,0 +1,83 @@ +// 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. + +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +import '../skwasm_impl.dart'; + +/// A [Rasterizer] that uses a single GL context in an OffscreenCanvas to do +/// all the rendering. It transfers bitmaps created in the OffscreenCanvas to +/// one or many on-screen elements to actually display the scene. +class SkwasmOffscreenCanvasRasterizer extends Rasterizer { + SkwasmOffscreenCanvasRasterizer(this.offscreenSurface); + + /// This is an SkSurface backed by an OffScreenCanvas. This single Surface is + /// used to render to many RenderCanvases to produce the rendered scene. + final SkwasmSurface offscreenSurface; + + @override + SkwasmOffscreenCanvasViewRasterizer createViewRasterizer(EngineFlutterView view) { + return _viewRasterizers.putIfAbsent( + view, + () => SkwasmOffscreenCanvasViewRasterizer(view, this), + ); + } + + final Map _viewRasterizers = + {}; + + @override + void setResourceCacheMaxBytes(int bytes) { + offscreenSurface.setSkiaResourceCacheMaxBytes(bytes); + } + + @override + void dispose() { + offscreenSurface.dispose(); + for (final SkwasmOffscreenCanvasViewRasterizer viewRasterizer in _viewRasterizers.values) { + viewRasterizer.dispose(); + } + } +} + +class SkwasmOffscreenCanvasViewRasterizer extends ViewRasterizer { + SkwasmOffscreenCanvasViewRasterizer(super.view, this.rasterizer); + + final SkwasmOffscreenCanvasRasterizer rasterizer; + + @override + final DisplayCanvasFactory displayFactory = DisplayCanvasFactory( + createCanvas: () => RenderCanvas(), + ); + + @override + void prepareToDraw() { + // No need to do anything here. Skwasm sizes the surface in the `rasterize` + // call below. + } + + @override + Future rasterize( + List displayCanvases, + List pictures, + FrameTimingRecorder? recorder, + ) async { + if (displayCanvases.length != pictures.length) { + throw ArgumentError('Called rasterize() with a different number of canvases and pictures.'); + } + final RenderResult renderResult = await rasterizer.offscreenSurface.renderPictures( + pictures.cast(), + currentFrameSize.width, + currentFrameSize.height, + ); + recorder?.recordRasterStart(renderResult.rasterStartMicros); + recorder?.recordRasterFinish(renderResult.rasterEndMicros); + for (int i = 0; i < displayCanvases.length; i++) { + final RenderCanvas renderCanvas = displayCanvases[i] as RenderCanvas; + final DomImageBitmap bitmap = renderResult.imageBitmaps[i]; + renderCanvas.render(bitmap); + } + } +} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart index 0486f450ed8..2e0a1050f50 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart @@ -14,7 +14,7 @@ enum PathDirection { clockwise, counterClockwise } enum PathArcSize { small, large } -class SkwasmPath extends SkwasmObjectWrapper implements ScenePath, DisposablePath { +class SkwasmPath extends SkwasmObjectWrapper implements LayerPath, DisposablePath { factory SkwasmPath() { return SkwasmPath.fromHandle(pathCreate()); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart index 76b18a25be7..fd0cf72cb09 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart @@ -6,7 +6,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; -class SkwasmPicture extends SkwasmObjectWrapper implements ScenePicture { +class SkwasmPicture extends SkwasmObjectWrapper implements LayerPicture { SkwasmPicture.fromHandle(PictureHandle handle) : super(handle, _registry); static final SkwasmFinalizationRegistry _registry = @@ -39,7 +39,7 @@ class SkwasmPicture extends SkwasmObjectWrapper implements ScenePict } class SkwasmPictureRecorder extends SkwasmObjectWrapper - implements ScenePictureRecorder { + implements LayerPictureRecorder { SkwasmPictureRecorder() : super(pictureRecorderCreate(), _registry); static final SkwasmFinalizationRegistry _registry = diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart index 9dd5e49592d..f3026681621 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart @@ -63,6 +63,9 @@ external void canvasClipPath(CanvasHandle canvas, PathHandle path, bool antialia @Native(symbol: 'canvas_drawColor', isLeaf: true) external void canvasDrawColor(CanvasHandle canvas, int color, int blendMode); +@Native(symbol: 'canvas_clear', isLeaf: true) +external void canvasClear(CanvasHandle canvas, int color); + @Native( symbol: 'canvas_drawLine', isLeaf: true, @@ -255,3 +258,6 @@ external void canvasGetLocalClipBounds(CanvasHandle canvas, RawRect outRect); @Native(symbol: 'canvas_getDeviceClipBounds', isLeaf: true) external void canvasGetDeviceClipBounds(CanvasHandle canvas, RawIRect outRect); + +@Native(symbol: 'canvas_quickReject', isLeaf: true) +external bool canvasQuickReject(CanvasHandle canvas, RawRect rect); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart index 07ff8407c32..f5f60964eaf 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart @@ -33,6 +33,12 @@ external void surfaceSetCallbackHandler(SurfaceHandle surface, OnRenderCallbackH @Native(symbol: 'surface_destroy', isLeaf: true) external void surfaceDestroy(SurfaceHandle surface); +@Native( + symbol: 'surface_setResourceCacheLimitBytes', + isLeaf: true, +) +external void surfaceSetResourceCacheLimitBytes(SurfaceHandle surface, int bytes); + @Native, Int, Int, Int)>( symbol: 'surface_renderPictures', isLeaf: true, diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index 930691002a0..2b68e2893d7 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'dart:ffi'; import 'dart:js_interop'; import 'dart:math' as math; @@ -16,8 +15,6 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web; class SkwasmRenderer extends Renderer { late SkwasmSurface surface; - final Map _sceneViews = - {}; bool get isMultiThreaded => skwasmIsMultiThreaded(); @@ -178,7 +175,7 @@ class SkwasmRenderer extends Renderer { ); @override - ui.SceneBuilder createSceneBuilder() => EngineSceneBuilder(); + ui.SceneBuilder createSceneBuilder() => LayerSceneBuilder(); @override ui.StrutStyle createStrutStyle({ @@ -324,7 +321,7 @@ class SkwasmRenderer extends Renderer { @override FutureOr initialize() { surface = SkwasmSurface(); - rasterizer = NoopRasterizer(); + rasterizer = SkwasmOffscreenCanvasRasterizer(surface); return super.initialize(); } @@ -399,25 +396,6 @@ class SkwasmRenderer extends Renderer { } } - @override - Future renderScene(ui.Scene scene, EngineFlutterView view) { - final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled - ? FrameTimingRecorder() - : null; - recorder?.recordBuildFinish(); - - final EngineSceneView sceneView = _getSceneViewForView(view); - return sceneView.renderScene(scene as EngineScene, recorder); - } - - EngineSceneView _getSceneViewForView(EngineFlutterView view) { - return _sceneViews.putIfAbsent(view, () { - final EngineSceneView sceneView = EngineSceneView(SkwasmPictureRenderer(surface), view); - view.dom.setScene(sceneView.sceneElement); - return sceneView; - }); - } - @override String get rendererTag => 'skwasm'; @@ -494,27 +472,6 @@ class SkwasmRenderer extends Renderer { ); } - String _generateDebugFilename(String filePrefix) { - final now = DateTime.now(); - final String y = now.year.toString().padLeft(4, '0'); - final String mo = now.month.toString().padLeft(2, '0'); - final String d = now.day.toString().padLeft(2, '0'); - final String h = now.hour.toString().padLeft(2, '0'); - final String mi = now.minute.toString().padLeft(2, '0'); - final String s = now.second.toString().padLeft(2, '0'); - return '$filePrefix-$y-$mo-$d-$h-$mi-$s.json'; - } - - void _dumpDebugInfo(String filePrefix, Map json) { - final String jsonString = const JsonEncoder.withIndent(' ').convert(json); - final blob = createDomBlob([jsonString], {'type': 'application/json'}); - final url = domWindow.URL.createObjectURL(blob); - final element = domDocument.createElement('a'); - element.setAttribute('href', url); - element.setAttribute('download', _generateDebugFilename(filePrefix)); - element.click(); - } - @override void dumpDebugInfo() { if (kDebugMode) { @@ -551,14 +508,14 @@ class SkwasmRenderer extends Renderer { 'surfaceCount': counts[26], 'verticesCount': counts[27], }; - _dumpDebugInfo('live_object_counts', countsJson); + downloadDebugInfo('live_object_counts', countsJson); }); int i = 0; - for (final view in _sceneViews.values) { - final Map? debugJson = view.dumpDebugInfo(); + for (final viewRasterizer in rasterizers.values) { + final Map? debugJson = viewRasterizer.dumpDebugInfo(); if (debugJson != null) { - _dumpDebugInfo('flutter-scene$i', debugJson); + downloadDebugInfo('flutter-scene$i', debugJson); i++; } } @@ -567,106 +524,6 @@ class SkwasmRenderer extends Renderer { @override void debugResetRasterizer() { - rasterizer = NoopRasterizer(); + rasterizer = SkwasmOffscreenCanvasRasterizer(surface); } } - -// A [Rasterizer] that does nothing. A placeholder that will be fleshed out -// when Skwasm and CanvasKit renderers are unified. See: https://github.com/flutter/flutter/issues/172311 -class NoopRasterizer implements Rasterizer { - @override - ViewRasterizer createViewRasterizer(EngineFlutterView view) { - return NoopViewRasterizer(); - } - - @override - void dispose() { - // Do nothing - } - - @override - void setResourceCacheMaxBytes(int bytes) { - // Do nothing - } -} - -// A [ViewRasterizer] that does nothing. A placeholder that will be fleshed out -// when Skwasm and CanvasKit renderers are unified. See: https://github.com/flutter/flutter/issues/172311 -class NoopViewRasterizer implements ViewRasterizer { - @override - BitmapSize currentFrameSize = BitmapSize.zero; - - @override - CompositorContext get context => throw UnimplementedError(); - - @override - void debugClear() { - // Do nothing - } - - @override - DisplayCanvasFactory get displayFactory => throw UnimplementedError(); - - @override - void dispose() { - // Do nothing - } - - @override - DisplayCanvas getOverlay() { - throw UnimplementedError(); - } - - @override - void prepareToDraw() {} - - @override - RenderQueue get queue => throw UnimplementedError(); - - @override - void releaseOverlay(DisplayCanvas overlay) {} - - @override - void releaseOverlays() {} - - @override - void removeOverlaysFromDom() {} - - @override - DomElement get sceneElement => throw UnimplementedError(); - - @override - EngineFlutterView get view => throw UnimplementedError(); - - @override - PlatformViewEmbedder get viewEmbedder => throw UnimplementedError(); - - @override - Map? dumpDebugInfo() { - throw UnimplementedError(); - } - - @override - Future rasterize( - List displayCanvases, - List pictures, - FrameTimingRecorder? recorder, - ) { - throw UnimplementedError(); - } - - @override - Future draw(LayerTree layerTree, FrameTimingRecorder? recorder) { - throw UnimplementedError(); - } -} - -class SkwasmPictureRenderer implements PictureRenderer { - SkwasmPictureRenderer(this.surface); - - SkwasmSurface surface; - - @override - FutureOr renderPictures(List pictures, int width, int height) => - surface.renderPictures(pictures.cast(), width, height); -} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart index c109d1dfccc..2a185763758 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart @@ -68,6 +68,12 @@ class SkwasmCallbackHandler { } } +typedef RenderResult = ({ + List imageBitmaps, + int rasterStartMicros, + int rasterEndMicros, +}); + class SkwasmSurface { factory SkwasmSurface() { final SurfaceHandle surfaceHandle = withStackScope((StackScope scope) { @@ -127,6 +133,10 @@ class SkwasmSurface { return ByteData.sublistView(output); } + void setSkiaResourceCacheMaxBytes(int bytes) { + surfaceSetResourceCacheLimitBytes(handle, bytes); + } + void dispose() { surfaceDestroy(handle); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/util.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/util.dart index ad68bcd1d2e..bf417f9ecb8 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/util.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/util.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:collection'; +import 'dart:convert'; import 'dart:math' as math; import 'dart:typed_data'; @@ -798,3 +799,24 @@ class BitmapSize { static const BitmapSize zero = BitmapSize(0, 0); } + +String _generateDebugFilename(String filePrefix) { + final now = DateTime.now(); + final String y = now.year.toString().padLeft(4, '0'); + final String mo = now.month.toString().padLeft(2, '0'); + final String d = now.day.toString().padLeft(2, '0'); + final String h = now.hour.toString().padLeft(2, '0'); + final String mi = now.minute.toString().padLeft(2, '0'); + final String s = now.second.toString().padLeft(2, '0'); + return '$filePrefix-$y-$mo-$d-$h-$mi-$s.json'; +} + +void downloadDebugInfo(String filePrefix, Map json) { + final String jsonString = const JsonEncoder.withIndent(' ').convert(json); + final blob = createDomBlob([jsonString], {'type': 'application/json'}); + final url = domWindow.URL.createObjectURL(blob); + final element = domDocument.createElement('a'); + element.setAttribute('href', url); + element.setAttribute('download', _generateDebugFilename(filePrefix)); + element.click(); +} diff --git a/engine/src/flutter/lib/web_ui/skwasm/canvas.cpp b/engine/src/flutter/lib/web_ui/skwasm/canvas.cpp index e6653eddbff..973257f6722 100644 --- a/engine/src/flutter/lib/web_ui/skwasm/canvas.cpp +++ b/engine/src/flutter/lib/web_ui/skwasm/canvas.cpp @@ -172,6 +172,10 @@ SKWASM_EXPORT void canvas_transform(DisplayListBuilder* canvas, canvas->Transform(*matrix44); } +SKWASM_EXPORT void canvas_clear(DisplayListBuilder* canvas, uint32_t color) { + canvas->DrawColor(DlColor(color), DlBlendMode::kSrc); +} + SKWASM_EXPORT void canvas_clipRect(DisplayListBuilder* canvas, const DlRect* rect, DlClipOp op, @@ -365,3 +369,8 @@ SKWASM_EXPORT void canvas_getDeviceClipBounds(DisplayListBuilder* canvas, DlIRect* outRect) { *outRect = DlIRect::RoundOut(canvas->GetDestinationClipCoverage()); } + +SKWASM_EXPORT bool canvas_quickReject(DisplayListBuilder* canvas, + DlRect* rect) { + return canvas->QuickReject(*rect); +} diff --git a/engine/src/flutter/lib/web_ui/skwasm/surface.cpp b/engine/src/flutter/lib/web_ui/skwasm/surface.cpp index f430af73a01..5ee63d54d46 100644 --- a/engine/src/flutter/lib/web_ui/skwasm/surface.cpp +++ b/engine/src/flutter/lib/web_ui/skwasm/surface.cpp @@ -42,6 +42,11 @@ void Surface::dispose() { delete this; } +// Main thread only +void Surface::setResourceCacheLimit(int bytes) { + _grContext->setResourceCacheLimit(bytes); +} + // Main thread only uint32_t Surface::renderPictures(DisplayList** pictures, int width, @@ -268,6 +273,11 @@ SKWASM_EXPORT void surface_dispose(Surface* surface) { surface->dispose(); } +SKWASM_EXPORT void surface_setResourceCacheLimitBytes(Surface* surface, + int bytes) { + surface->setResourceCacheLimit(bytes); +} + SKWASM_EXPORT uint32_t surface_renderPictures(Surface* surface, DisplayList** pictures, int width, diff --git a/engine/src/flutter/lib/web_ui/skwasm/surface.h b/engine/src/flutter/lib/web_ui/skwasm/surface.h index 867a09e6801..909c32c1564 100644 --- a/engine/src/flutter/lib/web_ui/skwasm/surface.h +++ b/engine/src/flutter/lib/web_ui/skwasm/surface.h @@ -58,6 +58,7 @@ class Surface { // Main thread only void dispose(); + void setResourceCacheLimit(int bytes); uint32_t renderPictures(flutter::DisplayList** picture, int width, int height, diff --git a/engine/src/flutter/lib/web_ui/test/engine/scene_builder_test.dart b/engine/src/flutter/lib/web_ui/test/engine/scene_builder_test.dart deleted file mode 100644 index 27c4f7abac5..00000000000 --- a/engine/src/flutter/lib/web_ui/test/engine/scene_builder_test.dart +++ /dev/null @@ -1,380 +0,0 @@ -// 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. - -import 'package:test/bootstrap/browser.dart'; -import 'package:test/test.dart'; -import 'package:ui/src/engine.dart'; - -import 'package:ui/ui.dart' as ui; - -import 'scene_builder_utils.dart'; - -void main() { - internalBootstrapBrowserTest(() => testMain); -} - -void testMain() { - setUpAll(() { - LayerSliceBuilder.debugRecorderFactory = (ui.Rect rect) { - final StubSceneCanvas canvas = StubSceneCanvas(); - final StubPictureRecorder recorder = StubPictureRecorder(canvas); - return (recorder, canvas); - }; - }); - - tearDownAll(() { - LayerSliceBuilder.debugRecorderFactory = null; - }); - - group('EngineSceneBuilder', () { - test('single picture', () { - final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); - - const ui.Rect pictureRect = ui.Rect.fromLTRB(100, 100, 200, 200); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect)); - - final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 1); - expect(slices[0], layerSlice(withPictureRect: pictureRect)); - }); - - test('two pictures', () { - final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); - - const ui.Rect pictureRect1 = ui.Rect.fromLTRB(100, 100, 200, 200); - const ui.Rect pictureRect2 = ui.Rect.fromLTRB(300, 300, 400, 400); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect1)); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2)); - - final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 1); - expect(slices[0], layerSlice(withPictureRect: const ui.Rect.fromLTRB(100, 100, 400, 400))); - }); - - test('picture + platform view (overlapping)', () { - final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); - - const ui.Rect pictureRect = ui.Rect.fromLTRB(100, 100, 200, 200); - const ui.Rect platformViewRect = ui.Rect.fromLTRB(150, 150, 250, 250); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect)); - sceneBuilder.addPlatformView( - 1, - offset: platformViewRect.topLeft, - width: platformViewRect.width, - height: platformViewRect.height, - ); - - final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 1); - expect( - slices[0], - layerSlice( - withPictureRect: pictureRect, - withPlatformViews: [ - PlatformView(1, platformViewRect, const PlatformViewStyling()), - ], - ), - ); - }); - - test('platform view + picture (overlapping)', () { - final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); - - const ui.Rect pictureRect = ui.Rect.fromLTRB(100, 100, 200, 200); - const ui.Rect platformViewRect = ui.Rect.fromLTRB(150, 150, 250, 250); - sceneBuilder.addPlatformView( - 1, - offset: platformViewRect.topLeft, - width: platformViewRect.width, - height: platformViewRect.height, - ); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect)); - - final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 2); - expect( - slices[0], - layerSlice( - withPlatformViews: [ - PlatformView(1, platformViewRect, const PlatformViewStyling()), - ], - ), - ); - expect(slices[1], layerSlice(withPictureRect: pictureRect)); - }); - - test('platform view sandwich (overlapping)', () { - final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); - - const ui.Rect pictureRect1 = ui.Rect.fromLTRB(100, 100, 200, 200); - const ui.Rect platformViewRect = ui.Rect.fromLTRB(150, 150, 250, 250); - const ui.Rect pictureRect2 = ui.Rect.fromLTRB(200, 200, 300, 300); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect1)); - sceneBuilder.addPlatformView( - 1, - offset: platformViewRect.topLeft, - width: platformViewRect.width, - height: platformViewRect.height, - ); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2)); - - final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 2); - expect( - slices[0], - layerSlice( - withPictureRect: pictureRect1, - withPlatformViews: [ - PlatformView(1, platformViewRect, const PlatformViewStyling()), - ], - ), - ); - expect(slices[1], layerSlice(withPictureRect: pictureRect2)); - }); - - test('platform view sandwich (non-overlapping)', () { - final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); - - const ui.Rect pictureRect1 = ui.Rect.fromLTRB(100, 100, 200, 200); - const ui.Rect platformViewRect = ui.Rect.fromLTRB(150, 150, 250, 250); - const ui.Rect pictureRect2 = ui.Rect.fromLTRB(50, 50, 100, 100); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect1)); - sceneBuilder.addPlatformView( - 1, - offset: platformViewRect.topLeft, - width: platformViewRect.width, - height: platformViewRect.height, - ); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2)); - - final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - - // The top picture does not overlap with the platform view, so it should - // be grouped into the slice below it to reduce the number of canvases we - // need. - expect(slices.length, 1); - expect( - slices[0], - layerSlice( - withPictureRect: const ui.Rect.fromLTRB(50, 50, 200, 200), - withPlatformViews: [ - PlatformView(1, platformViewRect, const PlatformViewStyling()), - ], - ), - ); - }); - - test('platform view sandwich (overlapping) with offset layers', () { - final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); - - const ui.Rect pictureRect1 = ui.Rect.fromLTRB(100, 100, 200, 200); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect1)); - - sceneBuilder.pushOffset(150, 150); - const ui.Rect platformViewRect = ui.Rect.fromLTRB(0, 0, 100, 100); - sceneBuilder.addPlatformView( - 1, - offset: platformViewRect.topLeft, - width: platformViewRect.width, - height: platformViewRect.height, - ); - sceneBuilder.pushOffset(50, 50); - sceneBuilder.addPicture(ui.Offset.zero, StubPicture(const ui.Rect.fromLTRB(0, 0, 100, 100))); - - final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 2); - expect( - slices[0], - layerSlice( - withPictureRect: pictureRect1, - withPlatformViews: [ - PlatformView( - 1, - platformViewRect, - const PlatformViewStyling(position: PlatformViewPosition.offset(ui.Offset(150, 150))), - ), - ], - ), - ); - expect(slices[1], layerSlice(withPictureRect: const ui.Rect.fromLTRB(200, 200, 300, 300))); - }); - - test('nested offset and clip', () { - final EngineSceneBuilder builder = EngineSceneBuilder(); - { - builder.pushOffset(16, 43); - { - builder.pushClipRect( - const ui.Rect.fromLTRB(0, 0, 1007, 1122), - clipBehavior: ui.Clip.hardEdge, - ); - { - builder.pushOffset(1, 214); - builder.addPlatformView(1, width: 1005, height: 907); - builder.pop(); - } - builder.pop(); - } - builder.pop(); - } - - const ui.Rect pictureRect = ui.Rect.fromLTRB(16, 198, 310, 280); - builder.addPicture(ui.Offset.zero, StubPicture(pictureRect)); - - final EngineScene scene = builder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - expect(slices.length, 2); - expect( - slices[0], - layerSlice( - withPlatformViews: [ - PlatformView( - 1, - const ui.Rect.fromLTRB(0, 0, 1005, 907), - const PlatformViewStyling( - position: PlatformViewPosition.offset(ui.Offset(17, 257)), - clip: PlatformViewRectClip(ui.Rect.fromLTRB(16.0, 43.0, 1023.0, 1165.0)), - ), - ), - ], - ), - ); - expect(slices[1], layerSlice(withPictureRect: pictureRect)); - }); - - test('grid view test', () { - // This test case covers a grid of elements, where each element is a platform - // view that has flutter content underneath it and on top of it. - // See a detailed explanation of this use-case in the following flutter issue: - // https://github.com/flutter/flutter/issues/149863 - final EngineSceneBuilder sceneBuilder = EngineSceneBuilder(); - - const double padding = 10; - const double tileSize = 50; - final List expectedPlatformViews = []; - for (int x = 0; x < 10; x++) { - for (int y = 0; y < 10; y++) { - final ui.Offset offset = ui.Offset( - padding + (tileSize + padding) * x, - padding + (tileSize + padding) * y, - ); - sceneBuilder.pushOffset(offset.dx, offset.dy); - sceneBuilder.addPicture( - ui.Offset.zero, - StubPicture(const ui.Rect.fromLTWH(0, 0, tileSize, tileSize)), - ); - sceneBuilder.addPlatformView( - 1, - offset: const ui.Offset(5, 5), - width: tileSize - 10, - height: tileSize - 10, - ); - sceneBuilder.addPicture( - const ui.Offset(10, 10), - StubPicture(const ui.Rect.fromLTWH(0, 0, tileSize - 20, tileSize - 20)), - ); - sceneBuilder.pop(); - expectedPlatformViews.add( - PlatformView( - 1, - const ui.Rect.fromLTRB(5.0, 5.0, tileSize - 5.0, tileSize - 5.0), - PlatformViewStyling(position: PlatformViewPosition.offset(offset)), - ), - ); - } - } - - final EngineScene scene = sceneBuilder.build() as EngineScene; - final List slices = scene.rootLayer.slices; - - // It is important that the optimizations of the scene builder result in - // there only being two scene slices. - expect(slices.length, 2); - expect( - slices[0], - layerSlice( - withPictureRect: const ui.Rect.fromLTRB( - padding, - padding, - 10 * (padding + tileSize), - 10 * (padding + tileSize), - ), - withPlatformViews: expectedPlatformViews, - ), - ); - expect( - slices[1], - layerSlice( - withPictureRect: const ui.Rect.fromLTRB( - padding + 10, - padding + 10, - 10 * (padding + tileSize) - 10, - 10 * (padding + tileSize) - 10, - ), - ), - ); - }); - }); -} - -LayerSliceMatcher layerSlice({ - ui.Rect withPictureRect = ui.Rect.zero, - List withPlatformViews = const [], -}) => LayerSliceMatcher(withPictureRect, withPlatformViews); - -class LayerSliceMatcher extends Matcher { - LayerSliceMatcher(this.expectedPictureRect, this.expectedPlatformViews); - - final ui.Rect expectedPictureRect; - final List expectedPlatformViews; - - @override - Description describe(Description description) { - return description.add( - '', - ); - } - - @override - bool matches(dynamic item, Map matchState) { - if (item is! LayerSlice) { - return false; - } - final ScenePicture picture = item.picture; - if (picture is! StubPicture) { - return false; - } - - if (picture.cullRect != expectedPictureRect) { - return false; - } - - if (item.platformViews.length != expectedPlatformViews.length) { - return false; - } - - for (int i = 0; i < item.platformViews.length; i++) { - final PlatformView expectedView = expectedPlatformViews[i]; - final PlatformView actualView = item.platformViews[i]; - if (expectedView.viewId != actualView.viewId) { - return false; - } - if (expectedView.bounds != actualView.bounds) { - return false; - } - if (expectedView.styling != actualView.styling) { - return false; - } - } - - return true; - } -} diff --git a/engine/src/flutter/lib/web_ui/test/engine/scene_builder_utils.dart b/engine/src/flutter/lib/web_ui/test/engine/scene_builder_utils.dart deleted file mode 100644 index 9d78ea7e12c..00000000000 --- a/engine/src/flutter/lib/web_ui/test/engine/scene_builder_utils.dart +++ /dev/null @@ -1,243 +0,0 @@ -// 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. - -import 'dart:typed_data'; - -import 'package:ui/src/engine.dart'; -import 'package:ui/ui.dart' as ui; - -class StubPicture implements ScenePicture { - StubPicture(this.cullRect); - - bool _isDisposed = false; - - @override - final ui.Rect cullRect; - - @override - int get approximateBytesUsed => throw UnimplementedError(); - - @override - bool get debugDisposed => _isDisposed; - - @override - void dispose() { - assert(!_isDisposed); - _isDisposed = true; - } - - @override - Future toImage(int width, int height) { - throw UnimplementedError(); - } - - @override - ui.Image toImageSync(int width, int height) { - throw UnimplementedError(); - } -} - -class StubCompositePicture extends StubPicture { - StubCompositePicture(this.children) - : super( - children.fold(null, (ui.Rect? previousValue, StubPicture child) { - final ui.Rect childRect = child.cullRect; - if (childRect.isEmpty) { - return previousValue; - } - return previousValue?.expandToInclude(child.cullRect) ?? child.cullRect; - }) ?? - ui.Rect.zero, - ); - - final List children; -} - -class StubPictureRecorder implements ui.PictureRecorder { - StubPictureRecorder(this.canvas); - - final StubSceneCanvas canvas; - - @override - ui.Picture endRecording() { - return StubCompositePicture(canvas.pictures); - } - - @override - bool get isRecording => throw UnimplementedError(); -} - -class StubSceneCanvas implements SceneCanvas { - List pictures = []; - - // We actually use offsets in some of the tests, so we need to track the - // translate calls as they are made. - List offsetStack = [ui.Offset.zero]; - - ui.Offset get currentOffset { - return offsetStack.last; - } - - set currentOffset(ui.Offset offset) { - offsetStack[offsetStack.length - 1] = offset; - } - - @override - void drawPicture(ui.Picture picture) { - pictures.add(StubPicture((picture as StubPicture).cullRect.shift(currentOffset))); - } - - @override - void clipPath(ui.Path path, {bool doAntiAlias = true}) {} - - @override - void clipRRect(ui.RRect rrect, {bool doAntiAlias = true}) {} - - @override - void clipRSuperellipse(ui.RSuperellipse rsuperellipse, {bool doAntiAlias = true}) {} - - @override - void clipRect(ui.Rect rect, {ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true}) {} - - @override - void drawArc( - ui.Rect rect, - double startAngle, - double sweepAngle, - bool useCenter, - ui.Paint paint, - ) {} - - @override - void drawAtlas( - ui.Image atlas, - List transforms, - List rects, - List? colors, - ui.BlendMode? blendMode, - ui.Rect? cullRect, - ui.Paint paint, - ) {} - - @override - void drawCircle(ui.Offset c, double radius, ui.Paint paint) {} - - @override - void drawColor(ui.Color color, ui.BlendMode blendMode) {} - - @override - void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) {} - - @override - void drawImage(ui.Image image, ui.Offset offset, ui.Paint paint) {} - - @override - void drawImageNine(ui.Image image, ui.Rect center, ui.Rect dst, ui.Paint paint) {} - - @override - void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, ui.Paint paint) {} - - @override - void drawLine(ui.Offset p1, ui.Offset p2, ui.Paint paint) {} - - @override - void drawOval(ui.Rect rect, ui.Paint paint) {} - - @override - void drawPaint(ui.Paint paint) {} - - @override - void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) {} - - @override - void drawPath(ui.Path path, ui.Paint paint) {} - - @override - void drawPoints(ui.PointMode pointMode, List points, ui.Paint paint) {} - - @override - void drawRRect(ui.RRect rrect, ui.Paint paint) {} - - @override - void drawRSuperellipse(ui.RSuperellipse rsuperellipse, ui.Paint paint) {} - - @override - void drawRawAtlas( - ui.Image atlas, - Float32List rstTransforms, - Float32List rects, - Int32List? colors, - ui.BlendMode? blendMode, - ui.Rect? cullRect, - ui.Paint paint, - ) {} - - @override - void drawRawPoints(ui.PointMode pointMode, Float32List points, ui.Paint paint) {} - - @override - void drawRect(ui.Rect rect, ui.Paint paint) {} - - @override - void drawShadow(ui.Path path, ui.Color color, double elevation, bool transparentOccluder) {} - - @override - void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) {} - - @override - ui.Rect getDestinationClipBounds() { - throw UnimplementedError(); - } - - @override - ui.Rect getLocalClipBounds() { - throw UnimplementedError(); - } - - @override - int getSaveCount() { - throw UnimplementedError(); - } - - @override - Float64List getTransform() { - throw UnimplementedError(); - } - - @override - void restore() { - offsetStack.removeLast(); - } - - @override - void restoreToCount(int count) {} - - @override - void rotate(double radians) {} - - @override - void save() { - offsetStack.add(currentOffset); - } - - @override - void saveLayer(ui.Rect? bounds, ui.Paint paint) {} - - @override - void saveLayerWithFilter(ui.Rect? bounds, ui.Paint paint, ui.ImageFilter backdropFilter) {} - - @override - void scale(double sx, [double? sy]) {} - - @override - void skew(double sx, double sy) {} - - @override - void transform(Float64List matrix4) {} - - @override - void translate(double dx, double dy) { - currentOffset += ui.Offset(dx, dy); - } -} diff --git a/engine/src/flutter/lib/web_ui/test/engine/scene_view_test.dart b/engine/src/flutter/lib/web_ui/test/engine/scene_view_test.dart deleted file mode 100644 index 23b8cd5fd34..00000000000 --- a/engine/src/flutter/lib/web_ui/test/engine/scene_view_test.dart +++ /dev/null @@ -1,413 +0,0 @@ -// 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. - -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:test/bootstrap/browser.dart'; -import 'package:test/test.dart'; -import 'package:ui/src/engine.dart'; - -import 'package:ui/ui.dart' as ui; -import 'package:ui/ui_web/src/ui_web.dart'; - -import '../common/test_initialization.dart'; -import 'scene_builder_utils.dart'; - -void main() { - internalBootstrapBrowserTest(() => testMain); -} - -class StubPictureRenderer implements PictureRenderer { - final DomHTMLCanvasElement scratchCanvasElement = createDomCanvasElement(width: 500, height: 500); - - @override - Future renderPictures(List pictures, int width, int height) async { - renderedPictures.addAll(pictures); - final List bitmaps = await Future.wait( - pictures.map((ScenePicture picture) { - final ui.Rect cullRect = picture.cullRect; - final Future bitmap = createImageBitmap(scratchCanvasElement, ( - x: 0, - y: 0, - width: cullRect.width.toInt(), - height: cullRect.height.toInt(), - )); - return bitmap; - }), - ); - return (imageBitmaps: bitmaps, rasterStartMicros: 0, rasterEndMicros: 0); - } - - List renderedPictures = []; - List clippedPictures = []; -} - -class StubFlutterView implements EngineFlutterView { - // Overridden in some tests - @override - DomManager dom = StubDomManager(); - - @override - double get devicePixelRatio => throw UnimplementedError(); - - @override - EngineFlutterDisplay get display => throw UnimplementedError(); - - @override - List get displayFeatures => throw UnimplementedError(); - - @override - ui.GestureSettings get gestureSettings => throw UnimplementedError(); - - @override - ViewPadding get padding => throw UnimplementedError(); - - @override - ViewConstraints get physicalConstraints => throw UnimplementedError(); - - @override - ui.Size get physicalSize => const ui.Size(1000, 1000); - - @override - EnginePlatformDispatcher get platformDispatcher => throw UnimplementedError(); - - @override - void render(ui.Scene scene, {ui.Size? size}) {} - - @override - ViewPadding get systemGestureInsets => throw UnimplementedError(); - - @override - void updateSemantics(ui.SemanticsUpdate update) {} - - @override - int get viewId => throw UnimplementedError(); - - @override - ViewPadding get viewInsets => throw UnimplementedError(); - - @override - ViewPadding get viewPadding => throw UnimplementedError(); - - @override - ui.Size? debugPhysicalSizeOverride; - - @override - bool isDisposed = false; - - @override - PointerBinding get pointerBinding => throw UnimplementedError(); - - @override - set pointerBinding(_) { - throw UnimplementedError(); - } - - @override - ContextMenu get contextMenu => throw UnimplementedError(); - - @override - void debugForceResize() { - throw UnimplementedError(); - } - - @override - DimensionsProvider get dimensionsProvider => throw UnimplementedError(); - - @override - void dispose() { - throw UnimplementedError(); - } - - @override - EmbeddingStrategy get embeddingStrategy => throw UnimplementedError(); - - @override - MouseCursor get mouseCursor => throw UnimplementedError(); - - @override - Stream get onResize => throw UnimplementedError(); - - @override - void resize(ui.Size newPhysicalSize) { - throw UnimplementedError(); - } - - @override - EngineSemanticsOwner get semantics => throw UnimplementedError(); -} - -class StubPath implements ScenePath { - StubPath(this.pathString, this.transformedPathString); - - final String pathString; - final String transformedPathString; - Float64List? appliedTransform; - - @override - ui.PathFillType get fillType => throw UnimplementedError(); - - @override - set fillType(ui.PathFillType value) => throw UnimplementedError(); - - @override - void moveTo(double x, double y) => throw UnimplementedError(); - - @override - void relativeMoveTo(double dx, double dy) => throw UnimplementedError(); - - @override - void lineTo(double x, double y) => throw UnimplementedError(); - - @override - void relativeLineTo(double dx, double dy) => throw UnimplementedError(); - - @override - void quadraticBezierTo(double x1, double y1, double x2, double y2) => throw UnimplementedError(); - - @override - void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) => - throw UnimplementedError(); - - @override - void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) => - throw UnimplementedError(); - - @override - void relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3) => - throw UnimplementedError(); - - @override - void conicTo(double x1, double y1, double x2, double y2, double w) => throw UnimplementedError(); - - @override - void relativeConicTo(double x1, double y1, double x2, double y2, double w) => - throw UnimplementedError(); - - @override - void arcTo(ui.Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) => - throw UnimplementedError(); - - @override - void arcToPoint( - ui.Offset arcEnd, { - ui.Radius radius = ui.Radius.zero, - double rotation = 0.0, - bool largeArc = false, - bool clockwise = true, - }) => throw UnimplementedError(); - - @override - void relativeArcToPoint( - ui.Offset arcEndDelta, { - ui.Radius radius = ui.Radius.zero, - double rotation = 0.0, - bool largeArc = false, - bool clockwise = true, - }) => throw UnimplementedError(); - - @override - void addRect(ui.Rect rect) => throw UnimplementedError(); - - @override - void addOval(ui.Rect oval) => throw UnimplementedError(); - - @override - void addArc(ui.Rect oval, double startAngle, double sweepAngle) => throw UnimplementedError(); - - @override - void addPolygon(List points, bool close) => throw UnimplementedError(); - - @override - void addRRect(ui.RRect rrect) => throw UnimplementedError(); - - @override - void addRSuperellipse(ui.RSuperellipse rsuperellipse) => throw UnimplementedError(); - - @override - void addPath(ui.Path path, ui.Offset offset, {Float64List? matrix4}) => - throw UnimplementedError(); - - @override - void extendWithPath(ui.Path path, ui.Offset offset, {Float64List? matrix4}) => - throw UnimplementedError(); - - @override - void close() => throw UnimplementedError(); - - @override - void reset() => throw UnimplementedError(); - - @override - bool contains(ui.Offset point) => throw UnimplementedError(); - - @override - ui.Path shift(ui.Offset offset) => throw UnimplementedError(); - - @override - StubPath transform(Float64List matrix4) { - appliedTransform = matrix4; - return StubPath(transformedPathString, ''); - } - - @override - ui.Rect getBounds() => throw UnimplementedError(); - - @override - ui.PathMetrics computeMetrics({bool forceClosed = false}) => throw UnimplementedError(); - - @override - String toSvgString() => pathString; -} - -void testMain() { - late EngineSceneView sceneView; - late StubPictureRenderer stubPictureRenderer; - - setUpImplicitView(); - - setUp(() { - stubPictureRenderer = StubPictureRenderer(); - sceneView = EngineSceneView(stubPictureRenderer, StubFlutterView()); - }); - - tearDown(() { - expect( - stubPictureRenderer.clippedPictures.every((StubPicture picture) => picture.debugDisposed), - true, - ); - }); - - test('SceneView places platform view according to device-pixel ratio', () async { - debugOverrideDevicePixelRatio(2.0); - - final StubPath clipPath = StubPath('M 2 2', 'M 1 1'); - - final PlatformView platformView = PlatformView( - 1, - const ui.Rect.fromLTWH(50, 80, 100, 120), - PlatformViewStyling(clip: PlatformViewPathClip(clipPath)), - ); - final EngineRootLayer rootLayer = EngineRootLayer(); - rootLayer.slices.add(LayerSlice(StubPicture(ui.Rect.zero), [platformView])); - final EngineScene scene = EngineScene(rootLayer); - await sceneView.renderScene(scene, null); - - final DomElement sceneElement = sceneView.sceneElement; - final List children = sceneElement.children.toList(); - - expect(children.length, 1); - final DomElement clipElement = children.first; - expect(clipElement.tagName, equalsIgnoringCase('flt-clip')); - - expect(clipElement.style.clipPath, 'path("M 1 1")'); - - // We expect the path to be scaled down by 1 / DPR - final Matrix4 expectedTransform = Matrix4.identity()..scale(0.5); - expect(clipPath.appliedTransform, expectedTransform.toFloat64()); - - final List clipChildren = clipElement.children.toList(); - expect(clipChildren.length, 1); - - final DomElement containerElement = clipChildren.first; - final DomCSSStyleDeclaration style = containerElement.style; - expect(style.left, ''); - expect(style.top, ''); - expect(style.width, '100px'); - expect(style.height, '120px'); - - // The heavy lifting of offsetting and sizing is done by the transform - expect(style.transform, 'matrix(0.5, 0, 0, 0.5, 25, 40)'); - - debugOverrideDevicePixelRatio(null); - }); - - test('SceneView always renders most recent picture and skips intermediate pictures', () async { - final List pictures = []; - final List> renderFutures = >[]; - for (int i = 1; i < 20; i++) { - final StubPicture picture = StubPicture(const ui.Rect.fromLTWH(50, 80, 100, 120)); - pictures.add(picture); - final EngineRootLayer rootLayer = EngineRootLayer(); - rootLayer.slices.add(LayerSlice(picture, [])); - final EngineScene scene = EngineScene(rootLayer); - renderFutures.add(sceneView.renderScene(scene, null)); - } - await Future.wait(renderFutures); - - // Should just render the first and last pictures and skip the one inbetween. - expect(stubPictureRenderer.renderedPictures.length, 2); - expect(stubPictureRenderer.renderedPictures.first, pictures.first); - expect(stubPictureRenderer.renderedPictures.last, pictures.last); - }); - - test('SceneView clips pictures that are outside the window screen', () async { - final StubPicture picture = StubPicture(const ui.Rect.fromLTWH(-50, -50, 100, 120)); - - final EngineRootLayer rootLayer = EngineRootLayer(); - rootLayer.slices.add(LayerSlice(picture, [])); - final EngineScene scene = EngineScene(rootLayer); - await sceneView.renderScene(scene, null); - - expect(stubPictureRenderer.renderedPictures.length, 1); - }); - - test('SceneView places platform view contents in the DOM', () async { - const int expectedPlatformViewId = 1234; - - int? injectedViewId; - final DomManager stubDomManager = StubDomManager() - ..injectPlatformViewOverride = (int viewId) { - injectedViewId = viewId; - }; - sceneView = EngineSceneView(stubPictureRenderer, StubFlutterView()..dom = stubDomManager); - - final PlatformView platformView = PlatformView( - expectedPlatformViewId, - const ui.Rect.fromLTWH(50, 80, 100, 120), - const PlatformViewStyling(), - ); - - final EngineRootLayer rootLayer = EngineRootLayer(); - rootLayer.slices.add(LayerSlice(StubPicture(ui.Rect.zero), [platformView])); - final EngineScene scene = EngineScene(rootLayer); - await sceneView.renderScene(scene, null); - - expect( - injectedViewId, - expectedPlatformViewId, - reason: 'SceneView should call injectPlatformView on its flutterView.dom', - ); - }); -} - -class StubDomManager implements DomManager { - void Function(int platformViewId) injectPlatformViewOverride = (int id) {}; - @override - void injectPlatformView(int platformViewId) { - injectPlatformViewOverride(platformViewId); - } - - @override - DomElement get platformViewsHost => throw UnimplementedError(); - - @override - DomShadowRoot get renderingHost => throw UnimplementedError(); - - @override - DomElement get rootElement => throw UnimplementedError(); - - @override - DomElement get sceneHost => throw UnimplementedError(); - - @override - DomElement get semanticsHost => throw UnimplementedError(); - - @override - void setScene(DomElement sceneElement) {} - - @override - DomElement get textEditingHost => throw UnimplementedError(); -} diff --git a/engine/src/flutter/lib/web_ui/test/ui/canvas_golden_test.dart b/engine/src/flutter/lib/web_ui/test/ui/canvas_golden_test.dart index 7ecf5f5156c..9a72ff059a2 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/canvas_golden_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/canvas_golden_test.dart @@ -38,9 +38,7 @@ void testMain() { await drawTestPicture(canvas as LayerCanvas); await drawPictureUsingCurrentRenderer(recorder.endRecording()); await matchGoldenFile('ui_weakref_picture.png', region: kDefaultRegion); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('text style - foreground/background/color do not leak across paragraphs', () async { const double testWidth = 440; diff --git a/engine/src/flutter/lib/web_ui/test/ui/color_filter_golden_test.dart b/engine/src/flutter/lib/web_ui/test/ui/color_filter_golden_test.dart index 396b4b21dd9..d167b03da7f 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/color_filter_golden_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/color_filter_golden_test.dart @@ -10,7 +10,6 @@ import 'package:web_engine_tester/golden_tester.dart'; import '../common/rendering.dart'; import '../common/test_initialization.dart'; -import 'utils.dart'; void main() { internalBootstrapBrowserTest(() => testMain); @@ -292,9 +291,7 @@ void testMain() { await renderScene(builder.build()); await matchGoldenFile('ui_colorfilter_as_imagefilter.png', region: region); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm || isSafari || isFirefox); + }); // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); } diff --git a/engine/src/flutter/lib/web_ui/test/ui/filters_test.dart b/engine/src/flutter/lib/web_ui/test/ui/filters_test.dart index 236bda9d241..80c6e1f0120 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/filters_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/filters_test.dart @@ -486,8 +486,8 @@ Future testMain() async { await renderScene(builder.build()); await matchGoldenFile('ui_zero_sigma_blur.png', region: region); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 + // Skwasm crashes with a zero-sigma blur. See: + // https://github.com/flutter/flutter/issues/174583 }, skip: isSkwasm); test('== operator', () { diff --git a/engine/src/flutter/lib/web_ui/test/ui/layer_test.dart b/engine/src/flutter/lib/web_ui/test/ui/layer_test.dart index c2eedf31f34..5764d5abfd1 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/layer_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/layer_test.dart @@ -135,7 +135,5 @@ void testMain() { region: const ui.Rect.fromLTRB(0, 0, 200, 200), ); }); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); } diff --git a/engine/src/flutter/lib/web_ui/test/ui/multi_view_test.dart b/engine/src/flutter/lib/web_ui/test/ui/multi_view_test.dart index eb1dd2751ff..861bffe475b 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/multi_view_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/multi_view_test.dart @@ -54,9 +54,7 @@ void testMain() { createDomElement('unregistered-view'), ); expect(() => renderer.renderScene(scene, unregisteredView), throwsAssertionError); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('will dispose the Rasterizer for a disposed view', () async { final EngineFlutterView view = EngineFlutterView( diff --git a/engine/src/flutter/lib/web_ui/test/ui/picture_test.dart b/engine/src/flutter/lib/web_ui/test/ui/picture_test.dart index 3acf358c39d..ce7ebf5df6c 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/picture_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/picture_test.dart @@ -8,7 +8,6 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import '../common/test_initialization.dart'; -import 'utils.dart'; void main() { internalBootstrapBrowserTest(() => testMain); @@ -33,9 +32,7 @@ Future testMain() async { picture.dispose(); expect(picture.debugDisposed, true); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('Picture construction invokes onCreate once', () async { int onCreateInvokedCount = 0; diff --git a/engine/src/flutter/lib/web_ui/test/ui/platform_view_test.dart b/engine/src/flutter/lib/web_ui/test/ui/platform_view_test.dart index ecd6ddcfec2..d5a4609d73e 100644 --- a/engine/src/flutter/lib/web_ui/test/ui/platform_view_test.dart +++ b/engine/src/flutter/lib/web_ui/test/ui/platform_view_test.dart @@ -483,9 +483,7 @@ Future testMain() async { 'matrix(1, 0, 0, 1, 6, 6)', 'matrix(1, 0, 0, 1, 3, 3)', ]); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('converts device pixels to logical pixels (no clips)', () async { EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(4); @@ -503,9 +501,7 @@ Future testMain() async { expect(getTransformChain(slotHost), ['matrix(0.25, 0, 0, 0.25, 1.5, 1.5)']); EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(null); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('converts device pixels to logical pixels (with clips)', () async { EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(4); @@ -529,9 +525,7 @@ Future testMain() async { 'matrix(0.25, 0, 0, 0.25, 0.75, 0.75)', ]); EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(null); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('renders overlays on top of platform views', () async { debugOverrideJsConfiguration( @@ -677,9 +671,7 @@ Future testMain() async { await renderTestScene(viewCount: 0); _expectSceneMatches(<_EmbeddedViewMarker>[]); debugOverrideJsConfiguration(null); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('correctly reuses overlays', () async { final ui.PictureRecorder testRecorder = ui.PictureRecorder(); @@ -803,9 +795,7 @@ Future testMain() async { _overlay, _platformView, ]); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('embeds and disposes of a platform view', () async { await createPlatformView(1, platformViewType); @@ -899,10 +889,8 @@ Future testMain() async { implicitView.debugForceResize(); // ImageDecoder is not supported in Safari or Firefox. - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 }, - skip: isSafari || isFirefox || isSkwasm, + skip: isSafari || isFirefox, ); test('does not crash when a prerolled platform view is not composited', () async { @@ -916,9 +904,7 @@ Future testMain() async { // The below line should not throw an error. await renderScene(sb.build()); _expectSceneMatches(<_EmbeddedViewMarker>[]); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('does not create overlays for invisible platform views', () async { final ui.PictureRecorder testRecorder = ui.PictureRecorder(); @@ -1150,9 +1136,7 @@ Future testMain() async { _platformView, _overlay, ]); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('can dispose without crashing', () async { ui_web.platformViewRegistry.registerViewFactory( @@ -1181,9 +1165,7 @@ Future testMain() async { // The following line used to cause a "Concurrent modification during iteration" embedder.dispose(); }, returnsNormally); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('optimizes overlays when pictures and platform views do not overlap', () async { ui.Picture rectPicture(ui.Rect rect) { @@ -1422,10 +1404,7 @@ Future testMain() async { .map((CompositionCanvas canvas) => canvas.pictures.length) .toList(); expect(picturesPerCanvas, [1, 2]); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); - + }); test('can customize amount of overlays', () async { final ui.PictureRecorder testRecorder = ui.PictureRecorder(); final ui.Canvas testCanvas = ui.Canvas(testRecorder); @@ -1493,9 +1472,7 @@ Future testMain() async { ]); debugOverrideJsConfiguration(null); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); + }); test('correctly rearranges pictures to second-to-last canvas ' 'when hitting canvas limit', () async { @@ -1605,10 +1582,7 @@ Future testMain() async { .toList(); expect(picturesPerCanvasInSecondRendering, [19]); debugOverrideJsConfiguration(null); - // Unskip when Skwasm and CanvasKit are unified: - // https://github.com/flutter/flutter/issues/172311 - }, skip: isSkwasm); - + }); test('disposes render pictures', () async { Instrumentation.enabled = true; Instrumentation.instance.debugCounters.clear();