From 235014f008b5cbd3eab17be3be680f7160441877 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 20 Aug 2024 11:03:28 -0700 Subject: [PATCH 01/14] Add comment for debugOverlayCanvas --- lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index a2d7ab4bd8ae9..b87874ab41f32 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -66,6 +66,9 @@ class HtmlViewEmbedder { /// Returns the most recent rendering. Only used in tests. Rendering get debugActiveRendering => _activeRendering; + /// If [debugOverlayOptimizationBounds] is true, this canvas will draw + /// semitransparent rectangles showing the computed bounds of the platform + /// views and pictures in the scene. DisplayCanvas? debugBoundsCanvas; /// The size of the frame, in physical pixels. From 1594d6de597c5de7c9148b2d537c60d21be619d0 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Fri, 23 Aug 2024 14:21:01 -0700 Subject: [PATCH 02/14] wip --- .../src/engine/canvaskit/embedded_views.dart | 33 ++++++++++--------- .../lib/src/engine/canvaskit/layer.dart | 7 ++++ .../lib/src/engine/canvaskit/layer_tree.dart | 2 +- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index b87874ab41f32..507b5f008cedb 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -78,27 +78,16 @@ class HtmlViewEmbedder { _frameSize = size; } - /// Returns a list of canvases which will be overlaid on top of the "base" - /// canvas after a platform view is composited into the scene. - /// - /// The engine asks for the overlay canvases immediately before the paint - /// phase, after the preroll phase. In the preroll phase we must be - /// conservative and assume that every platform view which is prerolled is - /// also composited, and therefore requires an overlay canvas. However, not - /// every platform view which is prerolled ends up being composited (it may be - /// clipped out and not actually drawn). This means that we may end up - /// overallocating canvases. This isn't a problem in practice, however, as - /// unused recording canvases are simply deleted at the end of the frame. - Iterable getOverlayCanvases() { + /// Returns a list of recording canvases which the pictures in the upcoming + /// paint step will be drawn into. These recording canvases are combined into + /// an N-way canvas for the rasterizer to record clip and transform operations + /// during the paint step. + Iterable getPictureCanvases() { return _context.pictureRecordersCreatedDuringPreroll .map((CkPictureRecorder r) => r.recordingCanvas!); } void prerollCompositeEmbeddedView(int viewId, EmbeddedViewParams params) { - final CkPictureRecorder pictureRecorder = CkPictureRecorder(); - pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); - _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); - // Do nothing if the params didn't change. if (_currentCompositionParams[viewId] == params) { // If the view was prerolled but not composited, then it needs to be @@ -112,6 +101,18 @@ class HtmlViewEmbedder { _viewsToRecomposite.add(viewId); } + void prerollPicture() { + final CkPictureRecorder pictureRecorder = CkPictureRecorder(); + pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); + _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); + } + + void prerollShaderMask() { + final CkPictureRecorder pictureRecorder = CkPictureRecorder(); + pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); + _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); + } + /// Prepares to composite [viewId]. /// /// If this returns a [CkCanvas], then that canvas should be the new leaf diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index 325322d799ab4..c13295a267a85 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -462,6 +462,12 @@ class ShaderMaskEngineLayer extends ContainerLayer final ui.BlendMode blendMode; final ui.FilterQuality filterQuality; + @override + void preroll(PrerollContext prerollContext, Matrix4 matrix) { + paintBounds = prerollChildren(prerollContext, matrix); + prerollContext.viewEmbedder?.prerollShaderMask(); + } + @override void paint(PaintContext paintContext) { assert(needsPainting); @@ -505,6 +511,7 @@ class PictureLayer extends Layer { @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { paintBounds = picture.cullRect.shift(offset); + prerollContext.viewEmbedder?.prerollPicture(); } @override diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart index 2aca00f35dd3c..b6b561c21de12 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart @@ -46,7 +46,7 @@ class LayerTree { final CkNWayCanvas internalNodesCanvas = CkNWayCanvas(); internalNodesCanvas.addCanvas(frame.canvas); final Iterable overlayCanvases = - frame.viewEmbedder!.getOverlayCanvases(); + frame.viewEmbedder!.getPictureCanvases(); overlayCanvases.forEach(internalNodesCanvas.addCanvas); final PaintContext context = PaintContext( internalNodesCanvas, From 10c348e7dbefa4dac627f498b016530f058f97c7 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 27 Aug 2024 12:40:50 -0700 Subject: [PATCH 03/14] wip --- .../src/engine/canvaskit/embedded_views.dart | 36 +++++++++---------- .../lib/src/engine/canvaskit/layer.dart | 8 ++--- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 507b5f008cedb..b4910906b0f33 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -5,7 +5,8 @@ import 'dart:math' as math; import 'package:ui/ui.dart' as ui; -import '../../engine.dart' show PlatformViewManager, configuration, longestIncreasingSubsequence; +import '../../engine.dart' + show PlatformViewManager, configuration, longestIncreasingSubsequence; import '../display.dart'; import '../dom.dart'; import '../html/path_to_svg_clip.dart'; @@ -107,6 +108,12 @@ class HtmlViewEmbedder { _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); } + CkCanvas drawPicture(CkPicture picture) { + final CkPictureRecorder pictureRecorder = CkPictureRecorder(); + pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); + _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); + } + void prerollShaderMask() { final CkPictureRecorder pictureRecorder = CkPictureRecorder(); pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); @@ -114,29 +121,17 @@ class HtmlViewEmbedder { } /// Prepares to composite [viewId]. - /// - /// If this returns a [CkCanvas], then that canvas should be the new leaf - /// node. Otherwise, keep the same leaf node. - CkCanvas? compositeEmbeddedView(int viewId) { + void compositeEmbeddedView(int viewId) { // Ensure platform view with `viewId` is injected into the `rasterizer.view`. rasterizer.view.dom.injectPlatformView(viewId); - final int overlayIndex = _context.viewCount; _compositionOrder.add(viewId); _context.viewCount++; - CkPictureRecorder? recorderToUseForRendering; - if (overlayIndex < _context.pictureRecordersCreatedDuringPreroll.length) { - recorderToUseForRendering = - _context.pictureRecordersCreatedDuringPreroll[overlayIndex]; - _context.pictureRecorders.add(recorderToUseForRendering); - } - if (_viewsToRecomposite.contains(viewId)) { _compositeWithParams(viewId, _currentCompositionParams[viewId]!); _viewsToRecomposite.remove(viewId); } - return recorderToUseForRendering?.recordingCanvas; } void _compositeWithParams(int platformViewId, EmbeddedViewParams params) { @@ -397,11 +392,11 @@ class HtmlViewEmbedder { debugBoundsCanvas ??= rasterizer.displayFactory.getCanvas(); final CkPictureRecorder boundsRecorder = CkPictureRecorder(); final CkCanvas boundsCanvas = boundsRecorder.beginRecording( - ui.Rect.fromLTWH( - 0, - 0, - _frameSize.width.toDouble(), - _frameSize.height.toDouble(), + ui.Rect.fromLTWH( + 0, + 0, + _frameSize.width.toDouble(), + _frameSize.height.toDouble(), ), ); final CkPaint platformViewBoundsPaint = CkPaint() @@ -925,4 +920,7 @@ class EmbedderFrameContext { /// The number of platform views in this frame. int viewCount = 0; + + /// Unoptimized rendering. + final Rendering unoptimizedRendering = Rendering(); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index c13295a267a85..655a2073ce5c9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -523,6 +523,8 @@ class PictureLayer extends Layer { paintContext.leafNodesCanvas!.drawPicture(picture); paintContext.leafNodesCanvas!.restore(); + CkCanvas nextCanvas = paintContext.viewEmbedder?.drawPicture(picture); + paintContext.leafNodesCanvas = nextCanvas; } } @@ -585,10 +587,6 @@ class PlatformViewLayer extends Layer { @override void paint(PaintContext paintContext) { - final CkCanvas? canvas = - paintContext.viewEmbedder?.compositeEmbeddedView(viewId); - if (canvas != null) { - paintContext.leafNodesCanvas = canvas; - } + paintContext.viewEmbedder?.compositeEmbeddedView(viewId); } } From 3fe35e56a4817375aff608d8477cde99c40a959b Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 29 Aug 2024 14:05:47 -0700 Subject: [PATCH 04/14] WIP on overlay optimization --- .../src/engine/canvaskit/embedded_views.dart | 65 +++++--- .../lib/src/engine/canvaskit/layer.dart | 13 +- .../lib/src/engine/canvaskit/layer_tree.dart | 12 +- .../canvaskit/overlay_scene_optimizer.dart | 151 +++++++++--------- .../lib/src/engine/canvaskit/rasterizer.dart | 7 +- .../test/canvaskit/embedded_views_test.dart | 6 +- 6 files changed, 132 insertions(+), 122 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index b4910906b0f33..7071df27a18ad 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -5,8 +5,7 @@ import 'dart:math' as math; import 'package:ui/ui.dart' as ui; -import '../../engine.dart' - show PlatformViewManager, configuration, longestIncreasingSubsequence; +import '../../engine.dart' show PlatformViewManager, configuration, longestIncreasingSubsequence; import '../display.dart'; import '../dom.dart'; import '../html/path_to_svg_clip.dart'; @@ -88,6 +87,15 @@ class HtmlViewEmbedder { .map((CkPictureRecorder r) => r.recordingCanvas!); } + /// Returns the first canvas to draw into. + /// + /// This returns `null` if preroll hasn't been done, or if there are no + /// pictures in the scene. + CkCanvas? getBaseCanvas() { + return _context + .pictureRecordersCreatedDuringPreroll.firstOrNull?.recordingCanvas; + } + void prerollCompositeEmbeddedView(int viewId, EmbeddedViewParams params) { // Do nothing if the params didn't change. if (_currentCompositionParams[viewId] == params) { @@ -102,22 +110,28 @@ class HtmlViewEmbedder { _viewsToRecomposite.add(viewId); } + /// Record that another picture recorder is needed. void prerollPicture() { final CkPictureRecorder pictureRecorder = CkPictureRecorder(); pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); } - CkCanvas drawPicture(CkPicture picture) { - final CkPictureRecorder pictureRecorder = CkPictureRecorder(); - pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); - _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); - } - - void prerollShaderMask() { - final CkPictureRecorder pictureRecorder = CkPictureRecorder(); - pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); - _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); + /// Finalize the given canvas and add it to the unoptimized scene. + /// + /// Returns the next canvas to draw pictures into. + CkCanvas? finalizePicture() { + final CkPictureRecorder currentRecorder = _context + .pictureRecordersCreatedDuringPreroll[_context.pictureRecorderIndex++]; + _context.sceneElements.add(currentRecorder); + // If this is the last picture, return null for the next canvas. + if (_context.pictureRecorderIndex >= + _context.pictureRecordersCreatedDuringPreroll.length) { + return null; + } + return _context + .pictureRecordersCreatedDuringPreroll[_context.pictureRecorderIndex] + .recordingCanvas!; } /// Prepares to composite [viewId]. @@ -126,7 +140,7 @@ class HtmlViewEmbedder { rasterizer.view.dom.injectPlatformView(viewId); _compositionOrder.add(viewId); - _context.viewCount++; + _context.sceneElements.add(viewId); if (_viewsToRecomposite.contains(viewId)) { _compositeWithParams(viewId, _currentCompositionParams[viewId]!); @@ -354,13 +368,17 @@ class HtmlViewEmbedder { sceneHost.append(_svgPathDefs!); } - Future submitFrame(CkPicture basePicture) async { - final List pictures = [basePicture]; - for (final CkPictureRecorder recorder in _context.pictureRecorders) { - pictures.add(recorder.endRecording()); - } + Future submitFrame() async { + final List unoptimizedRendering = + _context.sceneElements.map((Object element) { + if (element is CkPictureRecorder) { + return element.endRecording(); + } else { + return element; + } + }).toList(); Rendering rendering = createOptimizedRendering( - pictures, _compositionOrder, _currentCompositionParams); + unoptimizedRendering, _currentCompositionParams); rendering = _modifyRenderingForMaxCanvases(rendering); _updateDomForNewRendering(rendering); if (rendering.equalsForRendering(_activeRendering)) { @@ -918,9 +936,10 @@ class EmbedderFrameContext { /// This is a subset of [_pictureRecordersCreatedDuringPreroll]. final List pictureRecorders = []; - /// The number of platform views in this frame. - int viewCount = 0; + /// The index of the current picture recorder. + int pictureRecorderIndex = 0; - /// Unoptimized rendering. - final Rendering unoptimizedRendering = Rendering(); + /// List of picture recorders and platform view ids in the order they were + /// painted. + final List sceneElements = []; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index 655a2073ce5c9..a8e6cc3fb8c50 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -465,7 +465,7 @@ class ShaderMaskEngineLayer extends ContainerLayer @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { paintBounds = prerollChildren(prerollContext, matrix); - prerollContext.viewEmbedder?.prerollShaderMask(); + prerollContext.viewEmbedder?.prerollPicture(); } @override @@ -488,6 +488,11 @@ class ShaderMaskEngineLayer extends ContainerLayer paint.dispose(); paintContext.leafNodesCanvas!.restore(); + final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); + if (nextCanvas != null) { + paintContext.leafNodesCanvas = nextCanvas; + } + paintContext.internalNodesCanvas.restore(); } } @@ -523,8 +528,10 @@ class PictureLayer extends Layer { paintContext.leafNodesCanvas!.drawPicture(picture); paintContext.leafNodesCanvas!.restore(); - CkCanvas nextCanvas = paintContext.viewEmbedder?.drawPicture(picture); - paintContext.leafNodesCanvas = nextCanvas; + final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); + if (nextCanvas != null) { + paintContext.leafNodesCanvas = nextCanvas; + } } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart index b6b561c21de12..4b97e4a436499 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart @@ -44,13 +44,12 @@ class LayerTree { /// not be used. void paint(Frame frame, {bool ignoreRasterCache = false}) { final CkNWayCanvas internalNodesCanvas = CkNWayCanvas(); - internalNodesCanvas.addCanvas(frame.canvas); final Iterable overlayCanvases = frame.viewEmbedder!.getPictureCanvases(); overlayCanvases.forEach(internalNodesCanvas.addCanvas); final PaintContext context = PaintContext( internalNodesCanvas, - frame.canvas, + frame.viewEmbedder?.getBaseCanvas(), ignoreRasterCache ? null : frame.rasterCache, frame.viewEmbedder, ); @@ -81,10 +80,7 @@ class LayerTree { /// A single frame to be rendered. class Frame { - Frame(this.canvas, this.rasterCache, this.viewEmbedder); - - /// The canvas to render this frame to. - final CkCanvas canvas; + Frame(this.rasterCache, this.viewEmbedder); /// A cache of pre-rastered pictures. final RasterCache? rasterCache; @@ -110,7 +106,7 @@ class CompositorContext { RasterCache? rasterCache; /// Acquire a frame using this compositor's settings. - Frame acquireFrame(CkCanvas canvas, HtmlViewEmbedder? viewEmbedder) { - return Frame(canvas, rasterCache, viewEmbedder); + Frame acquireFrame(HtmlViewEmbedder? viewEmbedder) { + return Frame(rasterCache, viewEmbedder); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/overlay_scene_optimizer.dart b/lib/web_ui/lib/src/engine/canvaskit/overlay_scene_optimizer.dart index b98f969b1fbe3..e8d5220d3b2cc 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/overlay_scene_optimizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/overlay_scene_optimizer.dart @@ -156,108 +156,103 @@ ui.Rect computePlatformViewBounds(EmbeddedViewParams params) { /// [platformViews]. /// /// [paramsForViews] is required to compute the bounds of the platform views. -// TODO(harryterkelsen): Extend this to work for any sequence of platform views -// and pictures, https://github.com/flutter/flutter/issues/149863. Rendering createOptimizedRendering( - List pictures, - List platformViews, + List renderObjects, Map paramsForViews, ) { final Map cachedComputedRects = {}; - assert(pictures.length == platformViews.length + 1); final Rendering result = Rendering(); // The first picture is added to the rendering in a new render canvas. RenderingRenderCanvas tentativeCanvas = RenderingRenderCanvas(); - if (!pictures[0].cullRect.isEmpty) { - tentativeCanvas.add(pictures[0]); - } - for (int i = 0; i < platformViews.length; i++) { - final RenderingPlatformView platformView = - RenderingPlatformView(platformViews[i]); - if (PlatformViewManager.instance.isVisible(platformViews[i])) { - final ui.Rect platformViewBounds = cachedComputedRects[platformViews[i]] = - computePlatformViewBounds(paramsForViews[platformViews[i]]!); + for (final Object renderObject in renderObjects) { + if (renderObject is int) { + final RenderingPlatformView platformView = + RenderingPlatformView(renderObject); + if (PlatformViewManager.instance.isVisible(renderObject)) { + final ui.Rect platformViewBounds = cachedComputedRects[renderObject] = + computePlatformViewBounds(paramsForViews[renderObject]!); + + if (debugOverlayOptimizationBounds) { + platformView.debugComputedBounds = platformViewBounds; + } - if (debugOverlayOptimizationBounds) { - platformView.debugComputedBounds = platformViewBounds; + // If the platform view intersects with any pictures in the tentative canvas + // then add the tentative canvas to the rendering. + for (final CkPicture picture in tentativeCanvas.pictures) { + if (!picture.cullRect.intersect(platformViewBounds).isEmpty) { + result.add(tentativeCanvas); + tentativeCanvas = RenderingRenderCanvas(); + break; + } + } } + result.add(platformView); + } else if (renderObject is CkPicture) { + if (renderObject.cullRect.isEmpty) { + continue; + } + + // Find the first render canvas which comes after the last entity (picture + // or platform view) that the next picture intersects with, and add the + // picture to that render canvas, or create a new render canvas. - // If the platform view intersects with any pictures in the tentative canvas - // then add the tentative canvas to the rendering. + // First check if the picture intersects with any pictures in the + // tentative canvas, as this will be the last canvas in the rendering + // when it is eventually added. + bool addedToTentativeCanvas = false; for (final CkPicture picture in tentativeCanvas.pictures) { - if (!picture.cullRect.intersect(platformViewBounds).isEmpty) { - result.add(tentativeCanvas); - tentativeCanvas = RenderingRenderCanvas(); + if (!picture.cullRect.intersect(renderObject.cullRect).isEmpty) { + tentativeCanvas.add(renderObject); + addedToTentativeCanvas = true; break; } } - } - result.add(platformView); - - if (pictures[i + 1].cullRect.isEmpty) { - continue; - } - - // Find the first render canvas which comes after the last entity (picture - // or platform view) that the next picture intersects with, and add the - // picture to that render canvas, or create a new render canvas. - - // First check if the picture intersects with any pictures in the tentative - // canvas, as this will be the last canvas in the rendering when it is - // eventually added. - bool addedToTentativeCanvas = false; - for (final CkPicture picture in tentativeCanvas.pictures) { - if (!picture.cullRect.intersect(pictures[i + 1].cullRect).isEmpty) { - tentativeCanvas.add(pictures[i + 1]); - addedToTentativeCanvas = true; - break; + if (addedToTentativeCanvas) { + continue; } - } - if (addedToTentativeCanvas) { - continue; - } - RenderingRenderCanvas? lastCanvasSeen; - bool addedPictureToRendering = false; - for (final RenderingEntity entity in result.entities.reversed) { - if (entity is RenderingPlatformView) { - if (PlatformViewManager.instance.isVisible(entity.viewId)) { - final ui.Rect platformViewBounds = - cachedComputedRects[entity.viewId]!; - if (!platformViewBounds.intersect(pictures[i + 1].cullRect).isEmpty) { - // The next picture intersects with a platform view already in the - // result. Add this picture to the first render canvas which comes - // after this platform view or create one if none exists. - if (lastCanvasSeen != null) { - lastCanvasSeen.add(pictures[i + 1]); - } else { - tentativeCanvas.add(pictures[i + 1]); + RenderingRenderCanvas? lastCanvasSeen; + bool addedPictureToRendering = false; + for (final RenderingEntity entity in result.entities.reversed) { + if (entity is RenderingPlatformView) { + if (PlatformViewManager.instance.isVisible(entity.viewId)) { + final ui.Rect platformViewBounds = + cachedComputedRects[entity.viewId]!; + if (!platformViewBounds.intersect(renderObject.cullRect).isEmpty) { + // The next picture intersects with a platform view already in the + // result. Add this picture to the first render canvas which comes + // after this platform view or create one if none exists. + if (lastCanvasSeen != null) { + lastCanvasSeen.add(renderObject); + } else { + tentativeCanvas.add(renderObject); + } + addedPictureToRendering = true; + break; } - addedPictureToRendering = true; - break; } - } - } else if (entity is RenderingRenderCanvas) { - lastCanvasSeen = entity; - // Check if we intersect with any pictures in this render canvas. - for (final CkPicture picture in entity.pictures) { - if (!picture.cullRect.intersect(pictures[i + 1].cullRect).isEmpty) { - lastCanvasSeen.add(pictures[i + 1]); - addedPictureToRendering = true; - break; + } else if (entity is RenderingRenderCanvas) { + lastCanvasSeen = entity; + // Check if we intersect with any pictures in this render canvas. + for (final CkPicture picture in entity.pictures) { + if (!picture.cullRect.intersect(renderObject.cullRect).isEmpty) { + lastCanvasSeen.add(renderObject); + addedPictureToRendering = true; + break; + } } } } - } - if (!addedPictureToRendering) { - if (lastCanvasSeen != null) { - // Add it to the last canvas seen in the rendering, if any. - lastCanvasSeen.add(pictures[i + 1]); - } else { - tentativeCanvas.add(pictures[i + 1]); + if (!addedPictureToRendering) { + if (lastCanvasSeen != null) { + // Add it to the last canvas seen in the rendering, if any. + lastCanvasSeen.add(renderObject); + } else { + tentativeCanvas.add(renderObject); + } } } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index 640e09562ad7a..edd6de4bac9be 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -61,14 +61,11 @@ abstract class ViewRasterizer { currentFrameSize = bitmapSize; prepareToDraw(); viewEmbedder.frameSize = currentFrameSize; - final CkPictureRecorder pictureRecorder = CkPictureRecorder(); - pictureRecorder.beginRecording(ui.Offset.zero & currentFrameSize.toSize()); - final Frame compositorFrame = - context.acquireFrame(pictureRecorder.recordingCanvas!, viewEmbedder); + final Frame compositorFrame = context.acquireFrame(viewEmbedder); compositorFrame.raster(layerTree, ignoreRasterCache: true); - await viewEmbedder.submitFrame(pictureRecorder.endRecording()); + await viewEmbedder.submitFrame(); } /// Do some initialization to prepare to draw a frame. diff --git a/lib/web_ui/test/canvaskit/embedded_views_test.dart b/lib/web_ui/test/canvaskit/embedded_views_test.dart index 99b540884a892..cf71addabe271 100644 --- a/lib/web_ui/test/canvaskit/embedded_views_test.dart +++ b/lib/web_ui/test/canvaskit/embedded_views_test.dart @@ -1152,8 +1152,7 @@ void testMain() { // Scene 5: A combination of scene 1 and scene 4, where a subtitle is // painted over each platform view and a placeholder is painted under each - // one. Unfortunately, we need an overlay for each platform view in this - // case. + // one. final LayerSceneBuilder sb5 = LayerSceneBuilder(); sb5.pushOffset(0, 0); sb5.addPicture( @@ -1181,9 +1180,7 @@ void testMain() { _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, - _overlay, _platformView, - _overlay, _platformView, _overlay, ]); @@ -1314,7 +1311,6 @@ void testMain() { _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, - _overlay, _platformView, _overlay, ]); From 11a9b91be87437ca65067b51db83bf033225e03b Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 3 Sep 2024 15:39:01 -0700 Subject: [PATCH 05/14] Fix picture optimization for shader mask and backdrop filter --- .../lib/src/engine/canvaskit/layer.dart | 74 ++++++++++++++----- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index 15aad18087584..2b747bfedb0b1 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -57,6 +57,10 @@ class PrerollContext { /// A compositor for embedded HTML views. final HtmlViewEmbedder? viewEmbedder; + /// How many layers deep of filter masks we are. We cannot split pictures + /// that are affected by a shader mask or a backdrop filter. + int filterMaskLayers = 0; + final MutatorsStack mutatorsStack = MutatorsStack(); ui.Rect get cullRect { @@ -101,6 +105,10 @@ class PaintContext { /// A compositor for embedded HTML views. final HtmlViewEmbedder? viewEmbedder; + + /// How many layers deep of filter masks we are. We cannot split pictures + /// that are children of the same shader mask or backdrop filter. + int filterMaskLayers = 0; } /// A layer that contains child layers. @@ -173,8 +181,13 @@ class BackdropFilterEngineLayer extends ContainerLayer @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { + prerollContext.filterMaskLayers++; final ui.Rect childBounds = prerollChildren(prerollContext, matrix); paintBounds = childBounds.expandToInclude(prerollContext.cullRect); + prerollContext.filterMaskLayers--; + if (prerollContext.filterMaskLayers == 0) { + prerollContext.viewEmbedder?.prerollPicture(); + } } @override @@ -186,9 +199,17 @@ class BackdropFilterEngineLayer extends ContainerLayer // [internalNodesCanvas]), then later when we compose the canvases into a // single canvas, the backdrop filter will be applied multiple times. final CkCanvas currentCanvas = paintContext.leafNodesCanvas!; + paintContext.filterMaskLayers++; currentCanvas.saveLayerWithFilter(paintBounds, _filter, paint); paintChildren(paintContext); currentCanvas.restore(); + paintContext.filterMaskLayers--; + if (paintContext.filterMaskLayers == 0) { + final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); + if (nextCanvas != null) { + paintContext.leafNodesCanvas = nextCanvas; + } + } } // TODO(dnfield): dispose of the _filter @@ -417,17 +438,17 @@ class ImageFilterEngineLayer extends ContainerLayer } final ui.Rect childPaintBounds = prerollChildren(prerollContext, childMatrix); - if (_filter is ui.ColorFilter) { - // If the filter is a ColorFilter, the extended paint bounds will be the - // entire screen, which is not what we want. - paintBounds = childPaintBounds; - } else { - convertible.withSkImageFilter((skFilter) { - paintBounds = rectFromSkIRect( - skFilter.getOutputBounds(toSkRect(childPaintBounds)), - ); - }); - } + if (_filter is ui.ColorFilter) { + // If the filter is a ColorFilter, the extended paint bounds will be the + // entire screen, which is not what we want. + paintBounds = childPaintBounds; + } else { + convertible.withSkImageFilter((skFilter) { + paintBounds = rectFromSkIRect( + skFilter.getOutputBounds(toSkRect(childPaintBounds)), + ); + }); + } prerollContext.mutatorsStack.pop(); } @@ -462,14 +483,19 @@ class ShaderMaskEngineLayer extends ContainerLayer @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { + prerollContext.filterMaskLayers++; paintBounds = prerollChildren(prerollContext, matrix); - prerollContext.viewEmbedder?.prerollPicture(); + prerollContext.filterMaskLayers--; + if (prerollContext.filterMaskLayers == 0) { + prerollContext.viewEmbedder?.prerollPicture(); + } } @override void paint(PaintContext paintContext) { assert(needsPainting); + paintContext.filterMaskLayers++; paintContext.internalNodesCanvas.saveLayer(paintBounds, null); paintChildren(paintContext); @@ -484,10 +510,13 @@ class ShaderMaskEngineLayer extends ContainerLayer paintContext.leafNodesCanvas!.drawRect( ui.Rect.fromLTWH(0, 0, maskRect.width, maskRect.height), paint); paintContext.leafNodesCanvas!.restore(); + paintContext.filterMaskLayers--; - final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); - if (nextCanvas != null) { - paintContext.leafNodesCanvas = nextCanvas; + if (paintContext.filterMaskLayers == 0) { + final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); + if (nextCanvas != null) { + paintContext.leafNodesCanvas = nextCanvas; + } } paintContext.internalNodesCanvas.restore(); @@ -513,7 +542,9 @@ class PictureLayer extends Layer { @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { paintBounds = picture.cullRect.shift(offset); - prerollContext.viewEmbedder?.prerollPicture(); + if (prerollContext.filterMaskLayers == 0) { + prerollContext.viewEmbedder?.prerollPicture(); + } } @override @@ -525,9 +556,12 @@ class PictureLayer extends Layer { paintContext.leafNodesCanvas!.drawPicture(picture); paintContext.leafNodesCanvas!.restore(); - final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); - if (nextCanvas != null) { - paintContext.leafNodesCanvas = nextCanvas; + + if (paintContext.filterMaskLayers == 0) { + final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); + if (nextCanvas != null) { + paintContext.leafNodesCanvas = nextCanvas; + } } } } @@ -590,6 +624,8 @@ class PlatformViewLayer extends Layer { @override void paint(PaintContext paintContext) { + // TODO(harryterkelsen): Warn if we are a child of a backdrop filter or + // shader mask. paintContext.viewEmbedder?.compositeEmbeddedView(viewId); } } From 3dd0363acc3588b7e23728f7a78312ad4020d853 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Wed, 4 Sep 2024 10:56:29 -0700 Subject: [PATCH 06/14] Don't combine backdrop filter pictures --- lib/web_ui/lib/src/engine/canvaskit/layer.dart | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index 2b747bfedb0b1..6e541e20fcc31 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -181,13 +181,8 @@ class BackdropFilterEngineLayer extends ContainerLayer @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { - prerollContext.filterMaskLayers++; final ui.Rect childBounds = prerollChildren(prerollContext, matrix); paintBounds = childBounds.expandToInclude(prerollContext.cullRect); - prerollContext.filterMaskLayers--; - if (prerollContext.filterMaskLayers == 0) { - prerollContext.viewEmbedder?.prerollPicture(); - } } @override @@ -199,17 +194,9 @@ class BackdropFilterEngineLayer extends ContainerLayer // [internalNodesCanvas]), then later when we compose the canvases into a // single canvas, the backdrop filter will be applied multiple times. final CkCanvas currentCanvas = paintContext.leafNodesCanvas!; - paintContext.filterMaskLayers++; currentCanvas.saveLayerWithFilter(paintBounds, _filter, paint); paintChildren(paintContext); currentCanvas.restore(); - paintContext.filterMaskLayers--; - if (paintContext.filterMaskLayers == 0) { - final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); - if (nextCanvas != null) { - paintContext.leafNodesCanvas = nextCanvas; - } - } } // TODO(dnfield): dispose of the _filter From 42fb3d3f2b371252dac3d10bc4654c6db81d27fd Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 17 Sep 2024 11:03:45 -0700 Subject: [PATCH 07/14] wip --- .../src/engine/canvaskit/embedded_views.dart | 4 ++-- .../lib/src/engine/canvaskit/layer_tree.dart | 19 +++++++++++++++++++ .../lib/src/engine/canvaskit/rasterizer.dart | 2 +- lib/web_ui/test/ui/scene_builder_test.dart | 18 ++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 255a1b86d91b1..6405a68b729af 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -926,8 +926,8 @@ class EmbedderFrameContext { /// /// These picture recorders will be "claimed" in the paint phase by platform /// views being composited into the scene. - final List pictureRecordersCreatedDuringPreroll = - []; + final Map pictureRecordersCreatedDuringPreroll = + {}; /// Picture recorders which were actually used in the paint phase. /// diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart index 4b97e4a436499..81e16c21353aa 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart @@ -38,6 +38,25 @@ class LayerTree { rootLayer.preroll(context, Matrix4.identity()); } + /// Performs a paint pass with a recording canvas for each picture in the + /// tree. This paint pass is just used to measure the bounds for each picture + /// so we can optimize the total number of canvases required. + void measure(Frame frame, {bool ignoreRasterCache = false}) { + final CkNWayCanvas internalNodesCanvas = CkNWayCanvas(); + final Iterable overlayCanvases = + frame.viewEmbedder!.getPictureCanvases(); + overlayCanvases.forEach(internalNodesCanvas.addCanvas); + final PaintContext context = PaintContext( + internalNodesCanvas, + frame.viewEmbedder?.getBaseCanvas(), + ignoreRasterCache ? null : frame.rasterCache, + frame.viewEmbedder, + ); + if (rootLayer.needsPainting) { + rootLayer.paint(context); + } + } + /// Paints the layer tree into the given [frame]. /// /// If [ignoreRasterCache] is `true`, then the raster cache will diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart index edd6de4bac9be..2e38674cd95d7 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart @@ -53,7 +53,7 @@ abstract class ViewRasterizer { // The [frameSize] may be slightly imprecise if the `devicePixelRatio` isn't // an integer. For example, is you zoom to 110% in Chrome on a Macbook, the // `devicePixelRatio` is `2.200000047683716`, so when the physical size is - // computed by multiplying the logical size by the devie pixel ratio, the + // computed by multiplying the logical size by the device pixel ratio, the // result is slightly imprecise as well. Nevertheless, the number should // be close to an integer, so round the frame size to be more precice. final BitmapSize bitmapSize = BitmapSize.fromSize(frameSize); diff --git a/lib/web_ui/test/ui/scene_builder_test.dart b/lib/web_ui/test/ui/scene_builder_test.dart index aeaef07ad0c5a..8713314326517 100644 --- a/lib/web_ui/test/ui/scene_builder_test.dart +++ b/lib/web_ui/test/ui/scene_builder_test.dart @@ -308,6 +308,24 @@ Future testMain() async { await renderScene(sceneBuilder.build()); await matchGoldenFile('scene_builder_color_filter.png', region: region); }); + + test('overlapping pictures in opacity layer', () async { + final ui.SceneBuilder sceneBuilder = ui.SceneBuilder(); + sceneBuilder.pushOpacity(128); + sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) { + canvas.drawCircle(const ui.Offset(100, 150), 100, + ui.Paint()..color = const ui.Color(0xFFFF0000)); + })); + sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) { + canvas.drawCircle(const ui.Offset(200, 150), 100, + ui.Paint()..color = const ui.Color(0xFFFF0000)); + })); + sceneBuilder.pop(); + + await renderScene(sceneBuilder.build()); + await matchGoldenFile('scene_builder_overlapping_pictures_in_opacity.png', + region: region); + }); }); } From 38b3a5497ccd0f36029f44cadfd4755f7eb21c3b Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Fri, 20 Sep 2024 11:01:31 -0700 Subject: [PATCH 08/14] wip --- lib/web_ui/lib/src/engine.dart | 1 + .../src/engine/canvaskit/embedded_views.dart | 16 +- .../lib/src/engine/canvaskit/layer.dart | 484 +++-------------- .../lib/src/engine/canvaskit/layer_tree.dart | 22 +- .../src/engine/canvaskit/layer_visitor.dart | 487 ++++++++++++++++++ 5 files changed, 564 insertions(+), 446 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index cdf615a388617..f50b7cf78c73e 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -33,6 +33,7 @@ export 'engine/canvaskit/image_web_codecs.dart'; export 'engine/canvaskit/layer.dart'; export 'engine/canvaskit/layer_scene_builder.dart'; export 'engine/canvaskit/layer_tree.dart'; +export 'engine/canvaskit/layer_visitor.dart'; export 'engine/canvaskit/mask_filter.dart'; export 'engine/canvaskit/multi_surface_rasterizer.dart'; export 'engine/canvaskit/n_way_canvas.dart'; diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 6405a68b729af..58df27dd75881 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -84,6 +84,7 @@ class HtmlViewEmbedder { /// during the paint step. Iterable getPictureCanvases() { return _context.pictureRecordersCreatedDuringPreroll + .values .map((CkPictureRecorder r) => r.recordingCanvas!); } @@ -93,7 +94,8 @@ class HtmlViewEmbedder { /// pictures in the scene. CkCanvas? getBaseCanvas() { return _context - .pictureRecordersCreatedDuringPreroll.firstOrNull?.recordingCanvas; + .pictureRecordersCreatedDuringPreroll.values.firstOrNull + ?.recordingCanvas; } void prerollCompositeEmbeddedView(int viewId, EmbeddedViewParams params) { @@ -111,18 +113,18 @@ class HtmlViewEmbedder { } /// Record that another picture recorder is needed. - void prerollPicture() { + void prerollPicture(CkPicture picture) { final CkPictureRecorder pictureRecorder = CkPictureRecorder(); pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); - _context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder); + _context.pictureRecordersCreatedDuringPreroll[picture] = pictureRecorder; } /// Finalize the given canvas and add it to the unoptimized scene. /// /// Returns the next canvas to draw pictures into. - CkCanvas? finalizePicture() { + CkCanvas? finalizePicture(CkPicture picture) { final CkPictureRecorder currentRecorder = _context - .pictureRecordersCreatedDuringPreroll[_context.pictureRecorderIndex++]; + .pictureRecordersCreatedDuringPreroll[picture]!; _context.sceneElements.add(currentRecorder); // If this is the last picture, return null for the next canvas. if (_context.pictureRecorderIndex >= @@ -130,7 +132,7 @@ class HtmlViewEmbedder { return null; } return _context - .pictureRecordersCreatedDuringPreroll[_context.pictureRecorderIndex] + .pictureRecordersCreatedDuringPreroll[picture]! .recordingCanvas!; } @@ -398,7 +400,7 @@ class HtmlViewEmbedder { } for (final CkPictureRecorder recorder - in _context.pictureRecordersCreatedDuringPreroll) { + in _context.pictureRecordersCreatedDuringPreroll.values) { if (recorder.isRecording) { recorder.endRecording(); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index 591f1e26f9171..2ccbec7b7b65b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -4,18 +4,10 @@ import 'package:ui/ui.dart' as ui; -import '../color_filter.dart'; import '../vector_math.dart'; -import 'canvas.dart'; -import 'canvaskit_api.dart'; -import 'color_filter.dart'; -import 'embedded_views.dart'; -import 'image_filter.dart'; -import 'n_way_canvas.dart'; -import 'painting.dart'; +import 'layer_visitor.dart'; import 'path.dart'; import 'picture.dart'; -import 'raster_cache.dart'; /// A layer to be composed into a scene. /// @@ -31,15 +23,8 @@ abstract class Layer implements ui.EngineLayer { /// Whether or not this layer actually needs to be painted in the scene. bool get needsPainting => !paintBounds.isEmpty; - /// Pre-process this layer before painting. - /// - /// In this step, we compute the estimated [paintBounds] as well as - /// apply heuristics to prepare the render cache for pictures that - /// should be cached. - void preroll(PrerollContext prerollContext, Matrix4 matrix); - - /// Paint this layer into the scene. - void paint(PaintContext paintContext); + /// Implement layer visitor. + void accept(LayerVisitor visitor, T childData); // TODO(dnfield): Implement ui.EngineLayer.dispose for CanvasKit. // https://github.com/flutter/flutter/issues/82878 @@ -47,117 +32,19 @@ abstract class Layer implements ui.EngineLayer { void dispose() {} } -/// A context shared by all layers during the preroll pass. -class PrerollContext { - PrerollContext(this.rasterCache, this.viewEmbedder); - - /// A raster cache. Used to register candidates for caching. - final RasterCache? rasterCache; - - /// A compositor for embedded HTML views. - final HtmlViewEmbedder? viewEmbedder; - - /// How many layers deep of filter masks we are. We cannot split pictures - /// that are affected by a shader mask or a backdrop filter. - int filterMaskLayers = 0; - - final MutatorsStack mutatorsStack = MutatorsStack(); - - ui.Rect get cullRect { - ui.Rect cullRect = ui.Rect.largest; - for (final Mutator m in mutatorsStack) { - ui.Rect clipRect; - switch (m.type) { - case MutatorType.clipRect: - clipRect = m.rect!; - case MutatorType.clipRRect: - clipRect = m.rrect!.outerRect; - case MutatorType.clipPath: - clipRect = m.path!.getBounds(); - default: - continue; - } - cullRect = cullRect.intersect(clipRect); - } - return cullRect; - } -} - -/// A context shared by all layers during the paint pass. -class PaintContext { - PaintContext( - this.internalNodesCanvas, - this.leafNodesCanvas, - this.rasterCache, - this.viewEmbedder, - ); - - /// A multi-canvas that applies clips, transforms, and opacity - /// operations to all canvases (root canvas and overlay canvases for the - /// platform views). - CkNWayCanvas internalNodesCanvas; - - /// The canvas for leaf nodes to paint to. - CkCanvas? leafNodesCanvas; - - /// A raster cache potentially containing pre-rendered pictures. - final RasterCache? rasterCache; - - /// A compositor for embedded HTML views. - final HtmlViewEmbedder? viewEmbedder; - - /// How many layers deep of filter masks we are. We cannot split pictures - /// that are children of the same shader mask or backdrop filter. - int filterMaskLayers = 0; -} - /// A layer that contains child layers. abstract class ContainerLayer extends Layer { - final List _layers = []; + final List children = []; /// The list of child layers. /// /// Useful in tests. - List get debugLayers => _layers; + List get debugLayers => children; /// Register [child] as a child of this layer. void add(Layer child) { child.parent = this; - _layers.add(child); - } - - @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - paintBounds = prerollChildren(prerollContext, matrix); - } - - /// Run [preroll] on all of the child layers. - /// - /// Returns a [Rect] that covers the paint bounds of all of the child layers. - /// If all of the child layers have empty paint bounds, then the returned - /// [Rect] is empty. - ui.Rect prerollChildren(PrerollContext context, Matrix4 childMatrix) { - ui.Rect childPaintBounds = ui.Rect.zero; - for (final Layer layer in _layers) { - layer.preroll(context, childMatrix); - if (childPaintBounds.isEmpty) { - childPaintBounds = layer.paintBounds; - } else if (!layer.paintBounds.isEmpty) { - childPaintBounds = childPaintBounds.expandToInclude(layer.paintBounds); - } - } - return childPaintBounds; - } - - /// Calls [paint] on all child layers that need painting. - void paintChildren(PaintContext context) { - assert(needsPainting); - - for (final Layer layer in _layers) { - if (layer.needsPainting) { - layer.paint(context); - } - } + children.add(child); } } @@ -167,36 +54,21 @@ abstract class ContainerLayer extends Layer { /// to [LayerSceneBuilder] without requiring a [ContainerLayer]. class RootLayer extends ContainerLayer { @override - void paint(PaintContext paintContext) { - paintChildren(paintContext); + void accept(LayerVisitor visitor, T childData) { + visitor.visitRoot(this, childData); } } class BackdropFilterEngineLayer extends ContainerLayer implements ui.BackdropFilterEngineLayer { - BackdropFilterEngineLayer(this._filter, this._blendMode); + BackdropFilterEngineLayer(this.filter, this.blendMode); - final ui.ImageFilter _filter; - final ui.BlendMode _blendMode; - - @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - final ui.Rect childBounds = prerollChildren(prerollContext, matrix); - paintBounds = childBounds.expandToInclude(prerollContext.cullRect); - } + final ui.ImageFilter filter; + final ui.BlendMode blendMode; @override - void paint(PaintContext paintContext) { - final CkPaint paint = CkPaint()..blendMode = _blendMode; - - // Only apply the backdrop filter to the current canvas. If we apply the - // backdrop filter to every canvas (i.e. by applying it to the - // [internalNodesCanvas]), then later when we compose the canvases into a - // single canvas, the backdrop filter will be applied multiple times. - final CkCanvas currentCanvas = paintContext.leafNodesCanvas!; - currentCanvas.saveLayerWithFilter(paintBounds, _filter, paint); - paintChildren(paintContext); - currentCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitBackdropFilter(this, childData); } // TODO(dnfield): dispose of the _filter @@ -206,189 +78,76 @@ class BackdropFilterEngineLayer extends ContainerLayer /// A layer that clips its child layers by a given [Path]. class ClipPathEngineLayer extends ContainerLayer implements ui.ClipPathEngineLayer { - ClipPathEngineLayer(this._clipPath, this._clipBehavior) - : assert(_clipBehavior != ui.Clip.none); + ClipPathEngineLayer(this.clipPath, this.clipBehavior) + : assert(clipBehavior != ui.Clip.none); /// The path used to clip child layers. - final CkPath _clipPath; - final ui.Clip _clipBehavior; + final CkPath clipPath; + final ui.Clip clipBehavior; @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - prerollContext.mutatorsStack.pushClipPath(_clipPath); - final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); - final ui.Rect clipBounds = _clipPath.getBounds(); - if (childPaintBounds.overlaps(clipBounds)) { - paintBounds = childPaintBounds.intersect(clipBounds); - } - prerollContext.mutatorsStack.pop(); - } - - @override - void paint(PaintContext paintContext) { - assert(needsPainting); - - paintContext.internalNodesCanvas.save(); - paintContext.internalNodesCanvas - .clipPath(_clipPath, _clipBehavior != ui.Clip.hardEdge); - - if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - paintContext.internalNodesCanvas.saveLayer(paintBounds, null); - } - paintChildren(paintContext); - if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - paintContext.internalNodesCanvas.restore(); - } - paintContext.internalNodesCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitClipPath(this, childData); } } /// A layer that clips its child layers by a given [Rect]. class ClipRectEngineLayer extends ContainerLayer implements ui.ClipRectEngineLayer { - ClipRectEngineLayer(this._clipRect, this._clipBehavior) - : assert(_clipBehavior != ui.Clip.none); + ClipRectEngineLayer(this.clipRect, this.clipBehavior) + : assert(clipBehavior != ui.Clip.none); /// The rectangle used to clip child layers. - final ui.Rect _clipRect; - final ui.Clip _clipBehavior; - - @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - prerollContext.mutatorsStack.pushClipRect(_clipRect); - final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); - if (childPaintBounds.overlaps(_clipRect)) { - paintBounds = childPaintBounds.intersect(_clipRect); - } - prerollContext.mutatorsStack.pop(); - } + final ui.Rect clipRect; + final ui.Clip clipBehavior; @override - void paint(PaintContext paintContext) { - assert(needsPainting); - - paintContext.internalNodesCanvas.save(); - paintContext.internalNodesCanvas.clipRect( - _clipRect, - ui.ClipOp.intersect, - _clipBehavior != ui.Clip.hardEdge, - ); - if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - paintContext.internalNodesCanvas.saveLayer(_clipRect, null); - } - paintChildren(paintContext); - if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - paintContext.internalNodesCanvas.restore(); - } - paintContext.internalNodesCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitClipRect(this, childData); } } /// A layer that clips its child layers by a given [RRect]. class ClipRRectEngineLayer extends ContainerLayer implements ui.ClipRRectEngineLayer { - ClipRRectEngineLayer(this._clipRRect, this._clipBehavior) - : assert(_clipBehavior != ui.Clip.none); + ClipRRectEngineLayer(this.clipRRect, this.clipBehavior) + : assert(clipBehavior != ui.Clip.none); /// The rounded rectangle used to clip child layers. - final ui.RRect _clipRRect; - final ui.Clip? _clipBehavior; + final ui.RRect clipRRect; + final ui.Clip? clipBehavior; @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - prerollContext.mutatorsStack.pushClipRRect(_clipRRect); - final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); - if (childPaintBounds.overlaps(_clipRRect.outerRect)) { - paintBounds = childPaintBounds.intersect(_clipRRect.outerRect); - } - prerollContext.mutatorsStack.pop(); - } - - @override - void paint(PaintContext paintContext) { - assert(needsPainting); - - paintContext.internalNodesCanvas.save(); - paintContext.internalNodesCanvas - .clipRRect(_clipRRect, _clipBehavior != ui.Clip.hardEdge); - if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - paintContext.internalNodesCanvas.saveLayer(paintBounds, null); - } - paintChildren(paintContext); - if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - paintContext.internalNodesCanvas.restore(); - } - paintContext.internalNodesCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitClipRRect(this, childData); } } /// A layer that paints its children with the given opacity. class OpacityEngineLayer extends ContainerLayer implements ui.OpacityEngineLayer { - OpacityEngineLayer(this._alpha, this._offset); - - final int _alpha; - final ui.Offset _offset; + OpacityEngineLayer(this.alpha, this.offset); - @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - final Matrix4 childMatrix = Matrix4.copy(matrix); - childMatrix.translate(_offset.dx, _offset.dy); - prerollContext.mutatorsStack - .pushTransform(Matrix4.translationValues(_offset.dx, _offset.dy, 0.0)); - prerollContext.mutatorsStack.pushOpacity(_alpha); - super.preroll(prerollContext, childMatrix); - prerollContext.mutatorsStack.pop(); - prerollContext.mutatorsStack.pop(); - paintBounds = paintBounds.translate(_offset.dx, _offset.dy); - } + final int alpha; + final ui.Offset offset; @override - void paint(PaintContext paintContext) { - assert(needsPainting); - - final CkPaint paint = CkPaint(); - paint.color = ui.Color.fromARGB(_alpha, 0, 0, 0); - - paintContext.internalNodesCanvas.save(); - paintContext.internalNodesCanvas.translate(_offset.dx, _offset.dy); - - final ui.Rect saveLayerBounds = paintBounds.shift(-_offset); - - paintContext.internalNodesCanvas.saveLayer(saveLayerBounds, paint); - paintChildren(paintContext); - // Restore twice: once for the translate and once for the saveLayer. - paintContext.internalNodesCanvas.restore(); - paintContext.internalNodesCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitOpacity(this, childData); } } /// A layer that transforms its child layers by the given transform matrix. class TransformEngineLayer extends ContainerLayer implements ui.TransformEngineLayer { - TransformEngineLayer(this._transform); + TransformEngineLayer(this.transform); /// The matrix with which to transform the child layers. - final Matrix4 _transform; - - @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - final Matrix4 childMatrix = matrix.multiplied(_transform); - prerollContext.mutatorsStack.pushTransform(_transform); - final ui.Rect childPaintBounds = - prerollChildren(prerollContext, childMatrix); - paintBounds = _transform.transformRect(childPaintBounds); - prerollContext.mutatorsStack.pop(); - } + final Matrix4 transform; @override - void paint(PaintContext paintContext) { - assert(needsPainting); - - paintContext.internalNodesCanvas.save(); - paintContext.internalNodesCanvas.transform(_transform.storage); - paintChildren(paintContext); - paintContext.internalNodesCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitTransform(this, childData); } } @@ -401,58 +160,24 @@ class OffsetEngineLayer extends TransformEngineLayer implements ui.OffsetEngineLayer { OffsetEngineLayer(double dx, double dy) : super(Matrix4.translationValues(dx, dy, 0.0)); + + @override + void accept(LayerVisitor visitor, T childData) { + visitor.visitOffset(this, childData); + } } /// A layer that applies an [ui.ImageFilter] to its children. class ImageFilterEngineLayer extends ContainerLayer implements ui.ImageFilterEngineLayer { - ImageFilterEngineLayer(this._filter, this._offset); + ImageFilterEngineLayer(this.filter, this.offset); - final ui.Offset _offset; - final ui.ImageFilter _filter; - - @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - final Matrix4 childMatrix = Matrix4.copy(matrix); - childMatrix.translate(_offset.dx, _offset.dy); - prerollContext.mutatorsStack - .pushTransform(Matrix4.translationValues(_offset.dx, _offset.dy, 0.0)); - final CkManagedSkImageFilterConvertible convertible; - if (_filter is ui.ColorFilter) { - convertible = createCkColorFilter(_filter as EngineColorFilter)!; - } else { - convertible = _filter as CkManagedSkImageFilterConvertible; - } - ui.Rect childPaintBounds = prerollChildren(prerollContext, childMatrix); - childPaintBounds = childPaintBounds.translate(_offset.dx, _offset.dy); - if (_filter is ui.ColorFilter) { - // If the filter is a ColorFilter, the extended paint bounds will be the - // entire screen, which is not what we want. - paintBounds = childPaintBounds; - } else { - convertible.withSkImageFilter((skFilter) { - paintBounds = rectFromSkIRect( - skFilter.getOutputBounds(toSkRect(childPaintBounds)), - ); - }); - } - prerollContext.mutatorsStack.pop(); - } + final ui.Offset offset; + final ui.ImageFilter filter; @override - void paint(PaintContext paintContext) { - assert(needsPainting); - final ui.Rect offsetPaintBounds = paintBounds.shift(-_offset); - paintContext.internalNodesCanvas.save(); - paintContext.internalNodesCanvas.translate(_offset.dx, _offset.dy); - paintContext.internalNodesCanvas - .clipRect(offsetPaintBounds, ui.ClipOp.intersect, false); - final CkPaint paint = CkPaint(); - paint.imageFilter = _filter; - paintContext.internalNodesCanvas.saveLayer(offsetPaintBounds, paint); - paintChildren(paintContext); - paintContext.internalNodesCanvas.restore(); - paintContext.internalNodesCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitImageFilter(this, childData); } // TODO(dnfield): dispose of the _filter @@ -470,44 +195,8 @@ class ShaderMaskEngineLayer extends ContainerLayer final ui.FilterQuality filterQuality; @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - prerollContext.filterMaskLayers++; - paintBounds = prerollChildren(prerollContext, matrix); - prerollContext.filterMaskLayers--; - if (prerollContext.filterMaskLayers == 0) { - prerollContext.viewEmbedder?.prerollPicture(); - } - } - - @override - void paint(PaintContext paintContext) { - assert(needsPainting); - - paintContext.filterMaskLayers++; - paintContext.internalNodesCanvas.saveLayer(paintBounds, null); - paintChildren(paintContext); - - final CkPaint paint = CkPaint(); - paint.shader = shader; - paint.blendMode = blendMode; - paint.filterQuality = filterQuality; - - paintContext.leafNodesCanvas!.save(); - paintContext.leafNodesCanvas!.translate(maskRect.left, maskRect.top); - - paintContext.leafNodesCanvas!.drawRect( - ui.Rect.fromLTWH(0, 0, maskRect.width, maskRect.height), paint); - paintContext.leafNodesCanvas!.restore(); - paintContext.filterMaskLayers--; - - if (paintContext.filterMaskLayers == 0) { - final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); - if (nextCanvas != null) { - paintContext.leafNodesCanvas = nextCanvas; - } - } - - paintContext.internalNodesCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitShaderMask(this, childData); } } @@ -528,29 +217,8 @@ class PictureLayer extends Layer { final bool willChange; @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - paintBounds = picture.cullRect.shift(offset); - if (prerollContext.filterMaskLayers == 0) { - prerollContext.viewEmbedder?.prerollPicture(); - } - } - - @override - void paint(PaintContext paintContext) { - assert(needsPainting); - - paintContext.leafNodesCanvas!.save(); - paintContext.leafNodesCanvas!.translate(offset.dx, offset.dy); - - paintContext.leafNodesCanvas!.drawPicture(picture); - paintContext.leafNodesCanvas!.restore(); - - if (paintContext.filterMaskLayers == 0) { - final CkCanvas? nextCanvas = paintContext.viewEmbedder?.finalizePicture(); - if (nextCanvas != null) { - paintContext.leafNodesCanvas = nextCanvas; - } - } + void accept(LayerVisitor visitor, T childData) { + visitor.visitPicture(this, childData); } } @@ -562,26 +230,8 @@ class ColorFilterEngineLayer extends ContainerLayer final ui.ColorFilter filter; @override - void paint(PaintContext paintContext) { - assert(needsPainting); - - final CkPaint paint = CkPaint(); - paint.colorFilter = filter; - - // We need to clip because if the ColorFilter affects transparent black, - // then it will fill the entire `cullRect` of the picture, ignoring the - // `paintBounds` passed to `saveLayer`. See: - // https://github.com/flutter/flutter/issues/88866 - paintContext.internalNodesCanvas.save(); - - // TODO(hterkelsen): Only clip if the ColorFilter affects transparent black. - paintContext.internalNodesCanvas - .clipRect(paintBounds, ui.ClipOp.intersect, false); - - paintContext.internalNodesCanvas.saveLayer(paintBounds, paint); - paintChildren(paintContext); - paintContext.internalNodesCanvas.restore(); - paintContext.internalNodesCanvas.restore(); + void accept(LayerVisitor visitor, T childData) { + visitor.visitColorFilter(this, childData); } } @@ -595,25 +245,7 @@ class PlatformViewLayer extends Layer { final double height; @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - paintBounds = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height); - - /// ViewEmbedder is set to null when screenshotting. Therefore, skip - /// rendering - prerollContext.viewEmbedder?.prerollCompositeEmbeddedView( - viewId, - EmbeddedViewParams( - offset, - ui.Size(width, height), - prerollContext.mutatorsStack, - ), - ); - } - - @override - void paint(PaintContext paintContext) { - // TODO(harryterkelsen): Warn if we are a child of a backdrop filter or - // shader mask. - paintContext.viewEmbedder?.compositeEmbeddedView(viewId); + void accept(LayerVisitor visitor, T childData) { + visitor.visitPlatformView(this, childData); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart index 81e16c21353aa..37bb2d3861af9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart @@ -10,6 +10,7 @@ import '../vector_math.dart'; import 'canvas.dart'; import 'embedded_views.dart'; import 'layer.dart'; +import 'layer_visitor.dart'; import 'n_way_canvas.dart'; import 'picture_recorder.dart'; import 'raster_cache.dart'; @@ -31,11 +32,8 @@ class LayerTree { /// to raster. If [ignoreRasterCache] is `true`, then there will be no /// attempt to register pictures to cache. void preroll(Frame frame, {bool ignoreRasterCache = false}) { - final PrerollContext context = PrerollContext( - ignoreRasterCache ? null : frame.rasterCache, - frame.viewEmbedder, - ); - rootLayer.preroll(context, Matrix4.identity()); + final PrerollVisitor prerollVisitor = PrerollVisitor(frame.viewEmbedder); + rootLayer.accept(prerollVisitor, Matrix4.identity()); } /// Performs a paint pass with a recording canvas for each picture in the @@ -46,14 +44,13 @@ class LayerTree { final Iterable overlayCanvases = frame.viewEmbedder!.getPictureCanvases(); overlayCanvases.forEach(internalNodesCanvas.addCanvas); - final PaintContext context = PaintContext( + final PaintVisitor paintVisitor = PaintVisitor( internalNodesCanvas, frame.viewEmbedder?.getBaseCanvas(), - ignoreRasterCache ? null : frame.rasterCache, frame.viewEmbedder, ); if (rootLayer.needsPainting) { - rootLayer.paint(context); + rootLayer.accept(paintVisitor, null); } } @@ -66,14 +63,13 @@ class LayerTree { final Iterable overlayCanvases = frame.viewEmbedder!.getPictureCanvases(); overlayCanvases.forEach(internalNodesCanvas.addCanvas); - final PaintContext context = PaintContext( + final PaintVisitor paintVisitor = PaintVisitor( internalNodesCanvas, frame.viewEmbedder?.getBaseCanvas(), - ignoreRasterCache ? null : frame.rasterCache, frame.viewEmbedder, ); if (rootLayer.needsPainting) { - rootLayer.paint(context); + rootLayer.accept(paintVisitor, null); } } @@ -83,8 +79,8 @@ class LayerTree { ui.Picture flatten(ui.Size size) { final CkPictureRecorder recorder = CkPictureRecorder(); final CkCanvas canvas = recorder.beginRecording(ui.Offset.zero & size); - final PrerollContext prerollContext = PrerollContext(null, null); - rootLayer.preroll(prerollContext, Matrix4.identity()); + final PrerollVisitor prerollVisitor = PrerollVisitor(null); + rootLayer.accept(prerollVisitor, Matrix4.identity()); final CkNWayCanvas internalNodesCanvas = CkNWayCanvas(); internalNodesCanvas.addCanvas(canvas); diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart new file mode 100644 index 0000000000000..7eea89fd4786f --- /dev/null +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart @@ -0,0 +1,487 @@ +// 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 '../color_filter.dart'; +import '../vector_math.dart'; +import 'canvas.dart'; +import 'canvaskit_api.dart'; +import 'color_filter.dart'; +import 'embedded_views.dart'; +import 'image_filter.dart'; +import 'layer.dart'; +import 'n_way_canvas.dart'; +import 'painting.dart'; +import 'picture.dart'; + +abstract class LayerVisitor { + void visitRoot(RootLayer root, T childData); + void visitBackdropFilter( + BackdropFilterEngineLayer backdropFilter, T childData); + void visitClipPath(ClipPathEngineLayer clipPath, T childData); + void visitClipRect(ClipRectEngineLayer clipRect, T childData); + void visitClipRRect(ClipRRectEngineLayer clipRRect, T childData); + void visitOpacity(OpacityEngineLayer opacity, T childData); + void visitTransform(TransformEngineLayer transform, T childData); + void visitOffset(OffsetEngineLayer offset, T childData); + void visitImageFilter(ImageFilterEngineLayer imageFilter, T childData); + void visitShaderMask(ShaderMaskEngineLayer shaderMask, T childData); + void visitPicture(PictureLayer picture, T childData); + void visitColorFilter(ColorFilterEngineLayer colorFilter, T childData); + void visitPlatformView(PlatformViewLayer platformView, T childData); +} + +/// Pre-process the layer tree before painting. +/// +/// In this step, we compute the estimated [paintBounds] as well as +/// apply heuristics to prepare the render cache for pictures that +/// should be cached. +class PrerollVisitor extends LayerVisitor { + PrerollVisitor(this.viewEmbedder); + + final MutatorsStack mutatorsStack = MutatorsStack(); + + /// A compositor for embedded HTML views. + final HtmlViewEmbedder? viewEmbedder; + + /// How many layers deep of filter masks we are. We cannot split pictures + /// that are affected by a shader mask or a backdrop filter. + int filterMaskLayers = 0; + + CkPicture? lastPicture; + + ui.Rect get cullRect { + ui.Rect cullRect = ui.Rect.largest; + for (final Mutator m in mutatorsStack) { + ui.Rect clipRect; + switch (m.type) { + case MutatorType.clipRect: + clipRect = m.rect!; + case MutatorType.clipRRect: + clipRect = m.rrect!.outerRect; + case MutatorType.clipPath: + clipRect = m.path!.getBounds(); + default: + continue; + } + cullRect = cullRect.intersect(clipRect); + } + return cullRect; + } + + /// Run [preroll] on all of the child layers. + /// + /// Returns a [Rect] that covers the paint bounds of all of the child layers. + /// If all of the child layers have empty paint bounds, then the returned + /// [Rect] is empty. + ui.Rect prerollChildren(ContainerLayer layer, Matrix4 childMatrix) { + ui.Rect childPaintBounds = ui.Rect.zero; + for (final Layer layer in layer.children) { + layer.accept(this, childMatrix); + if (childPaintBounds.isEmpty) { + childPaintBounds = layer.paintBounds; + } else if (!layer.paintBounds.isEmpty) { + childPaintBounds = childPaintBounds.expandToInclude(layer.paintBounds); + } + } + return childPaintBounds; + } + + void prerollContainerLayer(ContainerLayer container, Matrix4 matrix) { + container.paintBounds = prerollChildren(container, matrix); + } + + @override + void visitRoot(RootLayer root, Matrix4 matrix) { + prerollContainerLayer(root, matrix); + } + + @override + void visitBackdropFilter( + BackdropFilterEngineLayer backdropFilter, Matrix4 matrix) { + final ui.Rect childBounds = prerollChildren(backdropFilter, matrix); + backdropFilter.paintBounds = childBounds.expandToInclude(cullRect); + } + + @override + void visitClipPath(ClipPathEngineLayer clipPath, Matrix4 matrix) { + mutatorsStack.pushClipPath(clipPath.clipPath); + final ui.Rect childPaintBounds = prerollChildren(clipPath, matrix); + final ui.Rect clipBounds = clipPath.clipPath.getBounds(); + if (childPaintBounds.overlaps(clipBounds)) { + clipPath.paintBounds = childPaintBounds.intersect(clipBounds); + } + mutatorsStack.pop(); + } + + @override + void visitClipRRect(ClipRRectEngineLayer clipRRect, Matrix4 matrix) { + mutatorsStack.pushClipRRect(clipRRect.clipRRect); + final ui.Rect childPaintBounds = prerollChildren(clipRRect, matrix); + if (childPaintBounds.overlaps(clipRRect.clipRRect.outerRect)) { + clipRRect.paintBounds = + childPaintBounds.intersect(clipRRect.clipRRect.outerRect); + } + mutatorsStack.pop(); + } + + @override + void visitClipRect(ClipRectEngineLayer clipRect, Matrix4 matrix) { + mutatorsStack.pushClipRect(clipRect.clipRect); + final ui.Rect childPaintBounds = prerollChildren(clipRect, matrix); + if (childPaintBounds.overlaps(clipRect.clipRect)) { + clipRect.paintBounds = childPaintBounds.intersect(clipRect.clipRect); + } + mutatorsStack.pop(); + } + + @override + void visitColorFilter(ColorFilterEngineLayer colorFilter, Matrix4 matrix) { + prerollContainerLayer(colorFilter, matrix); + } + + @override + void visitImageFilter(ImageFilterEngineLayer imageFilter, Matrix4 matrix) { + final Matrix4 childMatrix = Matrix4.copy(matrix); + childMatrix.translate(imageFilter.offset.dx, imageFilter.offset.dy); + mutatorsStack.pushTransform(Matrix4.translationValues( + imageFilter.offset.dx, imageFilter.offset.dy, 0.0)); + final CkManagedSkImageFilterConvertible convertible; + if (imageFilter.filter is ui.ColorFilter) { + convertible = + createCkColorFilter(imageFilter.filter as EngineColorFilter)!; + } else { + convertible = imageFilter.filter as CkManagedSkImageFilterConvertible; + } + ui.Rect childPaintBounds = prerollChildren(imageFilter, childMatrix); + childPaintBounds = childPaintBounds.translate( + imageFilter.offset.dx, imageFilter.offset.dy); + if (imageFilter.filter is ui.ColorFilter) { + // If the filter is a ColorFilter, the extended paint bounds will be the + // entire screen, which is not what we want. + imageFilter.paintBounds = childPaintBounds; + } else { + convertible.withSkImageFilter((SkImageFilter skFilter) { + imageFilter.paintBounds = rectFromSkIRect( + skFilter.getOutputBounds(toSkRect(childPaintBounds)), + ); + }); + } + mutatorsStack.pop(); + } + + @override + void visitOffset(OffsetEngineLayer offset, Matrix4 matrix) { + visitTransform(offset, matrix); + } + + @override + void visitOpacity(OpacityEngineLayer opacity, Matrix4 matrix) { + final Matrix4 childMatrix = Matrix4.copy(matrix); + childMatrix.translate(opacity.offset.dx, opacity.offset.dy); + mutatorsStack.pushTransform( + Matrix4.translationValues(opacity.offset.dx, opacity.offset.dy, 0.0)); + mutatorsStack.pushOpacity(opacity.alpha); + prerollContainerLayer(opacity, childMatrix); + mutatorsStack.pop(); + mutatorsStack.pop(); + opacity.paintBounds = + opacity.paintBounds.translate(opacity.offset.dx, opacity.offset.dy); + } + + @override + void visitPicture(PictureLayer picture, Matrix4 matrix) { + picture.paintBounds = picture.picture.cullRect.shift(picture.offset); + if (filterMaskLayers == 0) { + viewEmbedder?.prerollPicture(picture.picture); + } else { + lastPicture = picture.picture; + } + } + + @override + void visitPlatformView(PlatformViewLayer platformView, Matrix4 matrix) { + platformView.paintBounds = ui.Rect.fromLTWH( + platformView.offset.dx, + platformView.offset.dy, + platformView.width, + platformView.height, + ); + + /// ViewEmbedder is set to null when screenshotting. Therefore, skip + /// rendering + viewEmbedder?.prerollCompositeEmbeddedView( + platformView.viewId, + EmbeddedViewParams( + platformView.offset, + ui.Size(platformView.width, platformView.height), + mutatorsStack, + ), + ); + } + + @override + void visitShaderMask(ShaderMaskEngineLayer shaderMask, Matrix4 matrix) { + filterMaskLayers++; + shaderMask.paintBounds = prerollChildren(shaderMask, matrix); + filterMaskLayers--; + if (filterMaskLayers == 0) { + if (lastPicture != null) { + viewEmbedder?.prerollPicture(lastPicture!); + } + } + } + + @override + void visitTransform(TransformEngineLayer transform, Matrix4 matrix) { + final Matrix4 childMatrix = matrix.multiplied(transform.transform); + mutatorsStack.pushTransform(transform.transform); + final ui.Rect childPaintBounds = prerollChildren(transform, childMatrix); + transform.paintBounds = transform.transform.transformRect(childPaintBounds); + mutatorsStack.pop(); + } +} + +class PaintVisitor extends LayerVisitor { + PaintVisitor( + this.internalNodesCanvas, + this.leafNodesCanvas, + this.viewEmbedder, + ); + + /// A multi-canvas that applies clips, transforms, and opacity + /// operations to all canvases (root canvas and overlay canvases for the + /// platform views). + CkNWayCanvas internalNodesCanvas; + + /// The canvas for leaf nodes to paint to. + CkCanvas? leafNodesCanvas; + + /// A compositor for embedded HTML views. + final HtmlViewEmbedder? viewEmbedder; + + /// How many layers deep of filter masks we are. We cannot split pictures + /// that are children of the same shader mask or backdrop filter. + int filterMaskLayers = 0; + + /// Calls [paint] on all child layers that need painting. + void paintChildren(ContainerLayer container) { + assert(container.needsPainting); + + for (final Layer layer in container.children) { + if (layer.needsPainting) { + layer.accept(this, null); + } + } + } + + @override + void visitRoot(RootLayer root, _) { + paintChildren(root); + } + + @override + void visitBackdropFilter(BackdropFilterEngineLayer backdropFilter, _) { + final CkPaint paint = CkPaint()..blendMode = backdropFilter.blendMode; + + // Only apply the backdrop filter to the current canvas. If we apply the + // backdrop filter to every canvas (i.e. by applying it to the + // [internalNodesCanvas]), then later when we compose the canvases into a + // single canvas, the backdrop filter will be applied multiple times. + final CkCanvas currentCanvas = leafNodesCanvas!; + currentCanvas.saveLayerWithFilter( + backdropFilter.paintBounds, backdropFilter.filter, paint); + paintChildren(backdropFilter); + currentCanvas.restore(); + } + + @override + void visitClipPath(ClipPathEngineLayer clipPath, _) { + assert(clipPath.needsPainting); + + internalNodesCanvas.save(); + internalNodesCanvas.clipPath( + clipPath.clipPath, clipPath.clipBehavior != ui.Clip.hardEdge); + + if (clipPath.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + internalNodesCanvas.saveLayer(clipPath.paintBounds, null); + } + paintChildren(clipPath); + if (clipPath.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + internalNodesCanvas.restore(); + } + internalNodesCanvas.restore(); + } + + @override + void visitClipRect(ClipRectEngineLayer clipRect, _) { + assert(clipRect.needsPainting); + + internalNodesCanvas.save(); + internalNodesCanvas.clipRect( + clipRect.clipRect, + ui.ClipOp.intersect, + clipRect.clipBehavior != ui.Clip.hardEdge, + ); + if (clipRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + internalNodesCanvas.saveLayer(clipRect.clipRect, null); + } + paintChildren(clipRect); + if (clipRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + internalNodesCanvas.restore(); + } + internalNodesCanvas.restore(); + } + + @override + void visitClipRRect(ClipRRectEngineLayer clipRRect, _) { + assert(clipRRect.needsPainting); + + internalNodesCanvas.save(); + internalNodesCanvas.clipRRect( + clipRRect.clipRRect, clipRRect.clipBehavior != ui.Clip.hardEdge); + if (clipRRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + internalNodesCanvas.saveLayer(clipRRect.paintBounds, null); + } + paintChildren(clipRRect); + if (clipRRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + internalNodesCanvas.restore(); + } + internalNodesCanvas.restore(); + } + + @override + void visitOpacity(OpacityEngineLayer opacity, _) { + assert(opacity.needsPainting); + + final CkPaint paint = CkPaint(); + paint.color = ui.Color.fromARGB(opacity.alpha, 0, 0, 0); + + internalNodesCanvas.save(); + internalNodesCanvas.translate(opacity.offset.dx, opacity.offset.dy); + + final ui.Rect saveLayerBounds = opacity.paintBounds.shift(-opacity.offset); + + internalNodesCanvas.saveLayer(saveLayerBounds, paint); + paintChildren(opacity); + // Restore twice: once for the translate and once for the saveLayer. + internalNodesCanvas.restore(); + internalNodesCanvas.restore(); + } + + @override + void visitTransform(TransformEngineLayer transform, _) { + assert(transform.needsPainting); + + internalNodesCanvas.save(); + internalNodesCanvas.transform(transform.transform.storage); + paintChildren(transform); + internalNodesCanvas.restore(); + } + + @override + void visitOffset(OffsetEngineLayer offset, _) { + visitTransform(offset, null); + } + + @override + void visitImageFilter(ImageFilterEngineLayer imageFilter, _) { + assert(imageFilter.needsPainting); + final ui.Rect offsetPaintBounds = + imageFilter.paintBounds.shift(-imageFilter.offset); + internalNodesCanvas.save(); + internalNodesCanvas.translate(imageFilter.offset.dx, imageFilter.offset.dy); + internalNodesCanvas.clipRect(offsetPaintBounds, ui.ClipOp.intersect, false); + final CkPaint paint = CkPaint(); + paint.imageFilter = imageFilter.filter; + internalNodesCanvas.saveLayer(offsetPaintBounds, paint); + paintChildren(imageFilter); + internalNodesCanvas.restore(); + internalNodesCanvas.restore(); + } + + @override + void visitShaderMask(ShaderMaskEngineLayer shaderMask, _) { + assert(shaderMask.needsPainting); + + filterMaskLayers++; + internalNodesCanvas.saveLayer(shaderMask.paintBounds, null); + paintChildren(shaderMask); + + final CkPaint paint = CkPaint(); + paint.shader = shaderMask.shader; + paint.blendMode = shaderMask.blendMode; + paint.filterQuality = shaderMask.filterQuality; + + leafNodesCanvas!.save(); + leafNodesCanvas! + .translate(shaderMask.maskRect.left, shaderMask.maskRect.top); + + leafNodesCanvas!.drawRect( + ui.Rect.fromLTWH( + 0, 0, shaderMask.maskRect.width, shaderMask.maskRect.height), + paint); + leafNodesCanvas!.restore(); + filterMaskLayers--; + + if (filterMaskLayers == 0) { + final CkCanvas? nextCanvas = viewEmbedder?.finalizePicture(); + if (nextCanvas != null) { + leafNodesCanvas = nextCanvas; + } + } + + internalNodesCanvas.restore(); + } + + @override + void visitPicture(PictureLayer picture, _) { + assert(picture.needsPainting); + + leafNodesCanvas!.save(); + leafNodesCanvas!.translate(picture.offset.dx, picture.offset.dy); + + leafNodesCanvas!.drawPicture(picture.picture); + leafNodesCanvas!.restore(); + + if (filterMaskLayers == 0) { + final CkCanvas? nextCanvas = + viewEmbedder?.finalizePicture(picture.picture); + if (nextCanvas != null) { + leafNodesCanvas = nextCanvas; + } + } + } + + @override + void visitColorFilter(ColorFilterEngineLayer colorFilter, _) { + assert(colorFilter.needsPainting); + + final CkPaint paint = CkPaint(); + paint.colorFilter = colorFilter.filter; + + // We need to clip because if the ColorFilter affects transparent black, + // then it will fill the entire `cullRect` of the picture, ignoring the + // `paintBounds` passed to `saveLayer`. See: + // https://github.com/flutter/flutter/issues/88866 + internalNodesCanvas.save(); + + // TODO(hterkelsen): Only clip if the ColorFilter affects transparent black. + internalNodesCanvas.clipRect( + colorFilter.paintBounds, ui.ClipOp.intersect, false); + + internalNodesCanvas.saveLayer(colorFilter.paintBounds, paint); + paintChildren(colorFilter); + internalNodesCanvas.restore(); + internalNodesCanvas.restore(); + } + + @override + void visitPlatformView(PlatformViewLayer platformView, _) { + // TODO(harryterkelsen): Warn if we are a child of a backdrop filter or + // shader mask. + viewEmbedder?.compositeEmbeddedView(platformView.viewId); + } +} From 8ce933744c821882135ac9f4c98b21ece43442c4 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 23 Sep 2024 11:21:41 -0700 Subject: [PATCH 09/14] refactor to visitor pattern with 3 passes for rendering --- .../src/engine/canvaskit/embedded_views.dart | 117 +++-- .../lib/src/engine/canvaskit/layer_tree.dart | 28 +- .../src/engine/canvaskit/layer_visitor.dart | 412 +++++++++++++----- 3 files changed, 408 insertions(+), 149 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 58df27dd75881..0f88e1e42e8db 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -14,6 +14,7 @@ import '../svg.dart'; import '../util.dart'; import '../vector_math.dart'; import 'canvas.dart'; +import 'layer.dart'; import 'overlay_scene_optimizer.dart'; import 'painting.dart'; import 'path.dart'; @@ -81,21 +82,17 @@ class HtmlViewEmbedder { /// Returns a list of recording canvases which the pictures in the upcoming /// paint step will be drawn into. These recording canvases are combined into /// an N-way canvas for the rasterizer to record clip and transform operations - /// during the paint step. + /// during the measure step. Iterable getPictureCanvases() { - return _context.pictureRecordersCreatedDuringPreroll - .values + return _context.pictureRecordersCreatedDuringPreroll.values .map((CkPictureRecorder r) => r.recordingCanvas!); } - /// Returns the first canvas to draw into. - /// - /// This returns `null` if preroll hasn't been done, or if there are no - /// pictures in the scene. - CkCanvas? getBaseCanvas() { - return _context - .pictureRecordersCreatedDuringPreroll.values.firstOrNull - ?.recordingCanvas; + /// Returns a list of canvases for the optimized rendering. These are used in + /// the paint step. + Iterable getOptimizedCanvases() { + return _context.optimizedCanvasRecorders! + .map((CkPictureRecorder r) => r.recordingCanvas!); } void prerollCompositeEmbeddedView(int viewId, EmbeddedViewParams params) { @@ -112,28 +109,28 @@ class HtmlViewEmbedder { _viewsToRecomposite.add(viewId); } - /// Record that another picture recorder is needed. - void prerollPicture(CkPicture picture) { + /// Record that a picture recorder is needed for [picture] to be measured. + void prerollPicture(PictureLayer picture) { final CkPictureRecorder pictureRecorder = CkPictureRecorder(); pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); + if (_context.pictureRecordersCreatedDuringPreroll.containsKey(picture)) { + print('PREROLLED PICTURE ALREADY EXISTS IN SCENE!!!'); + } _context.pictureRecordersCreatedDuringPreroll[picture] = pictureRecorder; } - /// Finalize the given canvas and add it to the unoptimized scene. - /// - /// Returns the next canvas to draw pictures into. - CkCanvas? finalizePicture(CkPicture picture) { - final CkPictureRecorder currentRecorder = _context - .pictureRecordersCreatedDuringPreroll[picture]!; - _context.sceneElements.add(currentRecorder); - // If this is the last picture, return null for the next canvas. - if (_context.pictureRecorderIndex >= - _context.pictureRecordersCreatedDuringPreroll.length) { - return null; - } + /// Returns the picture recorder canvas that was created to measure [picture]. + CkCanvas getPictureRecorderFor(PictureLayer picture) { return _context - .pictureRecordersCreatedDuringPreroll[picture]! - .recordingCanvas!; + .pictureRecordersCreatedDuringPreroll[picture]!.recordingCanvas!; + } + + /// Adds the picture recorder associated with [picture] to the unoptimized + /// scene. + void addPictureToUnoptimizedScene(PictureLayer picture) { + final CkPictureRecorder currentRecorder = + _context.pictureRecordersCreatedDuringPreroll[picture]!; + _context.sceneElements.add(currentRecorder); } /// Prepares to composite [viewId]. @@ -370,11 +367,22 @@ class HtmlViewEmbedder { sceneHost.append(_svgPathDefs!); } - Future submitFrame() async { + void optimizeRendering() { + final Map scenePictureToRawPicture = + {}; + final Map reversePictureRecorderMap = + {}; + for (final MapEntry entry + in _context.pictureRecordersCreatedDuringPreroll.entries) { + reversePictureRecorderMap[entry.value] = entry.key; + } final List unoptimizedRendering = _context.sceneElements.map((Object element) { if (element is CkPictureRecorder) { - return element.endRecording(); + final CkPicture scenePicture = element.endRecording(); + final PictureLayer rawPicture = reversePictureRecorderMap[element]!; + scenePictureToRawPicture[scenePicture] = rawPicture; + return scenePicture; } else { return element; } @@ -382,6 +390,33 @@ class HtmlViewEmbedder { Rendering rendering = createOptimizedRendering( unoptimizedRendering, _currentCompositionParams); rendering = _modifyRenderingForMaxCanvases(rendering); + _context.optimizedRendering = rendering; + // Create new picture recorders for the optimized render canvases and record + // which pictures go in which canvas. + final List optimizedCanvasRecorders = + []; + final Map pictureToOptimizedCanvasMap = + {}; + for (final RenderingRenderCanvas renderCanvas in rendering.canvases) { + final CkPictureRecorder pictureRecorder = CkPictureRecorder(); + pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); + optimizedCanvasRecorders.add(pictureRecorder); + for (final CkPicture picture in renderCanvas.pictures) { + pictureToOptimizedCanvasMap[scenePictureToRawPicture[picture]!] = + pictureRecorder; + } + } + _context.optimizedCanvasRecorders = optimizedCanvasRecorders; + _context.pictureToOptimizedCanvasMap = pictureToOptimizedCanvasMap; + } + + CkCanvas getOptimizedCanvasFor(PictureLayer picture) { + assert(_context.optimizedRendering != null); + return _context.pictureToOptimizedCanvasMap![picture]!.recordingCanvas!; + } + + Future submitFrame() async { + final Rendering rendering = _context.optimizedRendering!; _updateDomForNewRendering(rendering); if (rendering.equalsForRendering(_activeRendering)) { // Copy the display canvases to the new rendering. @@ -394,9 +429,13 @@ class HtmlViewEmbedder { _activeRendering = rendering; final List renderCanvases = rendering.canvases; + int renderCanvasIndex = 0; for (final RenderingRenderCanvas renderCanvas in renderCanvases) { + final CkPicture renderPicture = _context + .optimizedCanvasRecorders![renderCanvasIndex++] + .endRecording(); await rasterizer.rasterizeToCanvas( - renderCanvas.displayCanvas!, renderCanvas.pictures); + renderCanvas.displayCanvas!, [renderPicture]); } for (final CkPictureRecorder recorder @@ -928,8 +967,9 @@ class EmbedderFrameContext { /// /// These picture recorders will be "claimed" in the paint phase by platform /// views being composited into the scene. - final Map pictureRecordersCreatedDuringPreroll = - {}; + final Map + pictureRecordersCreatedDuringPreroll = + {}; /// Picture recorders which were actually used in the paint phase. /// @@ -942,4 +982,17 @@ class EmbedderFrameContext { /// List of picture recorders and platform view ids in the order they were /// painted. final List sceneElements = []; + + /// The optimized rendering for this frame. This is set by calling + /// [optimizeRendering]. + Rendering? optimizedRendering; + + /// The picture recorders for the optimized rendering. This is set by calling + /// [optimizeRendering]. + List? optimizedCanvasRecorders; + + /// A map from the original PictureLayer to the picture recorder it should go + /// into in the optimized rendering. This is set by calling + /// [optimizedRendering]. + Map? pictureToOptimizedCanvasMap; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart index 37bb2d3861af9..faac9beb83359 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart @@ -40,17 +40,16 @@ class LayerTree { /// tree. This paint pass is just used to measure the bounds for each picture /// so we can optimize the total number of canvases required. void measure(Frame frame, {bool ignoreRasterCache = false}) { - final CkNWayCanvas internalNodesCanvas = CkNWayCanvas(); - final Iterable overlayCanvases = + final CkNWayCanvas nWayCanvas = CkNWayCanvas(); + final Iterable recordingCanvases = frame.viewEmbedder!.getPictureCanvases(); - overlayCanvases.forEach(internalNodesCanvas.addCanvas); - final PaintVisitor paintVisitor = PaintVisitor( - internalNodesCanvas, - frame.viewEmbedder?.getBaseCanvas(), - frame.viewEmbedder, + recordingCanvases.forEach(nWayCanvas.addCanvas); + final MeasureVisitor measureVisitor = MeasureVisitor( + nWayCanvas, + frame.viewEmbedder!, ); if (rootLayer.needsPainting) { - rootLayer.accept(paintVisitor, null); + rootLayer.accept(measureVisitor, null); } } @@ -61,12 +60,11 @@ class LayerTree { void paint(Frame frame, {bool ignoreRasterCache = false}) { final CkNWayCanvas internalNodesCanvas = CkNWayCanvas(); final Iterable overlayCanvases = - frame.viewEmbedder!.getPictureCanvases(); + frame.viewEmbedder!.getOptimizedCanvases(); overlayCanvases.forEach(internalNodesCanvas.addCanvas); final PaintVisitor paintVisitor = PaintVisitor( internalNodesCanvas, - frame.viewEmbedder?.getBaseCanvas(), - frame.viewEmbedder, + frame.viewEmbedder!, ); if (rootLayer.needsPainting) { rootLayer.accept(paintVisitor, null); @@ -84,10 +82,10 @@ class LayerTree { final CkNWayCanvas internalNodesCanvas = CkNWayCanvas(); internalNodesCanvas.addCanvas(canvas); - final PaintContext paintContext = - PaintContext(internalNodesCanvas, canvas, null, null); + final PaintVisitor paintVisitor = + PaintVisitor.forToImage(internalNodesCanvas, canvas); if (rootLayer.needsPainting) { - rootLayer.paint(paintContext); + rootLayer.accept(paintVisitor, null); } return recorder.endRecording(); } @@ -107,6 +105,8 @@ class Frame { bool raster(LayerTree layerTree, {bool ignoreRasterCache = false}) { timeAction(kProfilePrerollFrame, () { layerTree.preroll(this, ignoreRasterCache: ignoreRasterCache); + layerTree.measure(this, ignoreRasterCache: ignoreRasterCache); + viewEmbedder?.optimizeRendering(); }); timeAction(kProfileApplyFrame, () { layerTree.paint(this, ignoreRasterCache: ignoreRasterCache); diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart index 7eea89fd4786f..1a80a16ddb785 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart @@ -14,7 +14,6 @@ import 'image_filter.dart'; import 'layer.dart'; import 'n_way_canvas.dart'; import 'painting.dart'; -import 'picture.dart'; abstract class LayerVisitor { void visitRoot(RootLayer root, T childData); @@ -46,12 +45,6 @@ class PrerollVisitor extends LayerVisitor { /// A compositor for embedded HTML views. final HtmlViewEmbedder? viewEmbedder; - /// How many layers deep of filter masks we are. We cannot split pictures - /// that are affected by a shader mask or a backdrop filter. - int filterMaskLayers = 0; - - CkPicture? lastPicture; - ui.Rect get cullRect { ui.Rect cullRect = ui.Rect.largest; for (final Mutator m in mutatorsStack) { @@ -194,11 +187,7 @@ class PrerollVisitor extends LayerVisitor { @override void visitPicture(PictureLayer picture, Matrix4 matrix) { picture.paintBounds = picture.picture.cullRect.shift(picture.offset); - if (filterMaskLayers == 0) { - viewEmbedder?.prerollPicture(picture.picture); - } else { - lastPicture = picture.picture; - } + viewEmbedder?.prerollPicture(picture); } @override @@ -224,14 +213,7 @@ class PrerollVisitor extends LayerVisitor { @override void visitShaderMask(ShaderMaskEngineLayer shaderMask, Matrix4 matrix) { - filterMaskLayers++; shaderMask.paintBounds = prerollChildren(shaderMask, matrix); - filterMaskLayers--; - if (filterMaskLayers == 0) { - if (lastPicture != null) { - viewEmbedder?.prerollPicture(lastPicture!); - } - } } @override @@ -244,27 +226,248 @@ class PrerollVisitor extends LayerVisitor { } } -class PaintVisitor extends LayerVisitor { - PaintVisitor( - this.internalNodesCanvas, - this.leafNodesCanvas, +/// A layer visitor which measures the pictures that make up the scene and +/// prepares for them to be optimized into few canvases. +class MeasureVisitor extends LayerVisitor { + MeasureVisitor( + this.nWayCanvas, this.viewEmbedder, ); /// A multi-canvas that applies clips, transforms, and opacity /// operations to all canvases (root canvas and overlay canvases for the /// platform views). - CkNWayCanvas internalNodesCanvas; + CkNWayCanvas nWayCanvas; + + /// A compositor for embedded HTML views. + final HtmlViewEmbedder viewEmbedder; + + /// Measures all child layers that need painting. + void measureChildren(ContainerLayer container) { + assert(container.needsPainting); + + for (final Layer layer in container.children) { + if (layer.needsPainting) { + layer.accept(this, null); + } + } + } + + @override + void visitRoot(RootLayer root, _) { + measureChildren(root); + } + + @override + void visitBackdropFilter(BackdropFilterEngineLayer backdropFilter, _) { + measureChildren(backdropFilter); + } + + @override + void visitClipPath(ClipPathEngineLayer clipPath, _) { + assert(clipPath.needsPainting); + + nWayCanvas.save(); + nWayCanvas.clipPath( + clipPath.clipPath, clipPath.clipBehavior != ui.Clip.hardEdge); + + if (clipPath.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + nWayCanvas.saveLayer(clipPath.paintBounds, null); + } + measureChildren(clipPath); + if (clipPath.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + nWayCanvas.restore(); + } + nWayCanvas.restore(); + } + + @override + void visitClipRect(ClipRectEngineLayer clipRect, _) { + assert(clipRect.needsPainting); + + nWayCanvas.save(); + nWayCanvas.clipRect( + clipRect.clipRect, + ui.ClipOp.intersect, + clipRect.clipBehavior != ui.Clip.hardEdge, + ); + if (clipRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + nWayCanvas.saveLayer(clipRect.clipRect, null); + } + measureChildren(clipRect); + if (clipRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + nWayCanvas.restore(); + } + nWayCanvas.restore(); + } + + @override + void visitClipRRect(ClipRRectEngineLayer clipRRect, _) { + assert(clipRRect.needsPainting); + + nWayCanvas.save(); + nWayCanvas.clipRRect( + clipRRect.clipRRect, clipRRect.clipBehavior != ui.Clip.hardEdge); + if (clipRRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + nWayCanvas.saveLayer(clipRRect.paintBounds, null); + } + measureChildren(clipRRect); + if (clipRRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + nWayCanvas.restore(); + } + nWayCanvas.restore(); + } + + @override + void visitOpacity(OpacityEngineLayer opacity, _) { + assert(opacity.needsPainting); + + final CkPaint paint = CkPaint(); + paint.color = ui.Color.fromARGB(opacity.alpha, 0, 0, 0); + + nWayCanvas.save(); + nWayCanvas.translate(opacity.offset.dx, opacity.offset.dy); + + final ui.Rect saveLayerBounds = opacity.paintBounds.shift(-opacity.offset); + + nWayCanvas.saveLayer(saveLayerBounds, paint); + measureChildren(opacity); + // Restore twice: once for the translate and once for the saveLayer. + nWayCanvas.restore(); + nWayCanvas.restore(); + } + + @override + void visitTransform(TransformEngineLayer transform, _) { + assert(transform.needsPainting); + + nWayCanvas.save(); + nWayCanvas.transform(transform.transform.storage); + measureChildren(transform); + nWayCanvas.restore(); + } + + @override + void visitOffset(OffsetEngineLayer offset, _) { + visitTransform(offset, null); + } + + @override + void visitImageFilter(ImageFilterEngineLayer imageFilter, _) { + assert(imageFilter.needsPainting); + final ui.Rect offsetPaintBounds = + imageFilter.paintBounds.shift(-imageFilter.offset); + nWayCanvas.save(); + nWayCanvas.translate(imageFilter.offset.dx, imageFilter.offset.dy); + nWayCanvas.clipRect(offsetPaintBounds, ui.ClipOp.intersect, false); + final CkPaint paint = CkPaint(); + paint.imageFilter = imageFilter.filter; + nWayCanvas.saveLayer(offsetPaintBounds, paint); + measureChildren(imageFilter); + nWayCanvas.restore(); + nWayCanvas.restore(); + } + + @override + void visitShaderMask(ShaderMaskEngineLayer shaderMask, _) { + assert(shaderMask.needsPainting); + + nWayCanvas.saveLayer(shaderMask.paintBounds, null); + measureChildren(shaderMask); + + // final CkPaint paint = CkPaint(); + // paint.shader = shaderMask.shader; + // paint.blendMode = shaderMask.blendMode; + // paint.filterQuality = shaderMask.filterQuality; + + // pictureRecorderCanvas!.save(); + // pictureRecorderCanvas! + // .translate(shaderMask.maskRect.left, shaderMask.maskRect.top); + + // pictureRecorderCanvas!.drawRect( + // ui.Rect.fromLTWH( + // 0, 0, shaderMask.maskRect.width, shaderMask.maskRect.height), + // paint); + // pictureRecorderCanvas!.restore(); + + nWayCanvas.restore(); + } + + @override + void visitPicture(PictureLayer picture, _) { + assert(picture.needsPainting); + + final CkCanvas pictureRecorderCanvas = + viewEmbedder.getPictureRecorderFor(picture); + + pictureRecorderCanvas.save(); + pictureRecorderCanvas.translate(picture.offset.dx, picture.offset.dy); + + pictureRecorderCanvas.drawPicture(picture.picture); + pictureRecorderCanvas.restore(); + + viewEmbedder.addPictureToUnoptimizedScene(picture); + } + + @override + void visitColorFilter(ColorFilterEngineLayer colorFilter, _) { + assert(colorFilter.needsPainting); - /// The canvas for leaf nodes to paint to. - CkCanvas? leafNodesCanvas; + final CkPaint paint = CkPaint(); + paint.colorFilter = colorFilter.filter; + + // We need to clip because if the ColorFilter affects transparent black, + // then it will fill the entire `cullRect` of the picture, ignoring the + // `paintBounds` passed to `saveLayer`. See: + // https://github.com/flutter/flutter/issues/88866 + nWayCanvas.save(); + + // TODO(hterkelsen): Only clip if the ColorFilter affects transparent black. + nWayCanvas.clipRect(colorFilter.paintBounds, ui.ClipOp.intersect, false); + + nWayCanvas.saveLayer(colorFilter.paintBounds, paint); + measureChildren(colorFilter); + nWayCanvas.restore(); + nWayCanvas.restore(); + } + + @override + void visitPlatformView(PlatformViewLayer platformView, _) { + // TODO(harryterkelsen): Warn if we are a child of a backdrop filter or + // shader mask. + viewEmbedder.compositeEmbeddedView(platformView.viewId); + } +} + +/// A layer visitor which paints the layer tree into one or more canvases. +/// +/// The canvases are the optimized canvases that were created when the view +/// embedder optimized the canvases after the measure step. +class PaintVisitor extends LayerVisitor { + PaintVisitor( + this.nWayCanvas, + HtmlViewEmbedder this.viewEmbedder, + ) : toImageCanvas = null; + + PaintVisitor.forToImage( + this.nWayCanvas, + this.toImageCanvas, + ) : viewEmbedder = null; + + /// A multi-canvas that applies clips, transforms, and opacity + /// operations to all canvases (root canvas and overlay canvases for the + /// platform views). + CkNWayCanvas nWayCanvas; /// A compositor for embedded HTML views. final HtmlViewEmbedder? viewEmbedder; - /// How many layers deep of filter masks we are. We cannot split pictures - /// that are children of the same shader mask or backdrop filter. - int filterMaskLayers = 0; + final List shaderMaskStack = []; + + final Map> picturesUnderShaderMask = + >{}; + + final CkCanvas? toImageCanvas; /// Calls [paint] on all child layers that need painting. void paintChildren(ContainerLayer container) { @@ -286,70 +489,65 @@ class PaintVisitor extends LayerVisitor { void visitBackdropFilter(BackdropFilterEngineLayer backdropFilter, _) { final CkPaint paint = CkPaint()..blendMode = backdropFilter.blendMode; - // Only apply the backdrop filter to the current canvas. If we apply the - // backdrop filter to every canvas (i.e. by applying it to the - // [internalNodesCanvas]), then later when we compose the canvases into a - // single canvas, the backdrop filter will be applied multiple times. - final CkCanvas currentCanvas = leafNodesCanvas!; - currentCanvas.saveLayerWithFilter( + nWayCanvas.saveLayerWithFilter( backdropFilter.paintBounds, backdropFilter.filter, paint); paintChildren(backdropFilter); - currentCanvas.restore(); + nWayCanvas.restore(); } @override void visitClipPath(ClipPathEngineLayer clipPath, _) { assert(clipPath.needsPainting); - internalNodesCanvas.save(); - internalNodesCanvas.clipPath( + nWayCanvas.save(); + nWayCanvas.clipPath( clipPath.clipPath, clipPath.clipBehavior != ui.Clip.hardEdge); if (clipPath.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - internalNodesCanvas.saveLayer(clipPath.paintBounds, null); + nWayCanvas.saveLayer(clipPath.paintBounds, null); } paintChildren(clipPath); if (clipPath.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - internalNodesCanvas.restore(); + nWayCanvas.restore(); } - internalNodesCanvas.restore(); + nWayCanvas.restore(); } @override void visitClipRect(ClipRectEngineLayer clipRect, _) { assert(clipRect.needsPainting); - internalNodesCanvas.save(); - internalNodesCanvas.clipRect( + nWayCanvas.save(); + nWayCanvas.clipRect( clipRect.clipRect, ui.ClipOp.intersect, clipRect.clipBehavior != ui.Clip.hardEdge, ); if (clipRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - internalNodesCanvas.saveLayer(clipRect.clipRect, null); + nWayCanvas.saveLayer(clipRect.clipRect, null); } paintChildren(clipRect); if (clipRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - internalNodesCanvas.restore(); + nWayCanvas.restore(); } - internalNodesCanvas.restore(); + nWayCanvas.restore(); } @override void visitClipRRect(ClipRRectEngineLayer clipRRect, _) { assert(clipRRect.needsPainting); - internalNodesCanvas.save(); - internalNodesCanvas.clipRRect( + nWayCanvas.save(); + nWayCanvas.clipRRect( clipRRect.clipRRect, clipRRect.clipBehavior != ui.Clip.hardEdge); if (clipRRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - internalNodesCanvas.saveLayer(clipRRect.paintBounds, null); + nWayCanvas.saveLayer(clipRRect.paintBounds, null); } paintChildren(clipRRect); if (clipRRect.clipBehavior == ui.Clip.antiAliasWithSaveLayer) { - internalNodesCanvas.restore(); + nWayCanvas.restore(); } - internalNodesCanvas.restore(); + nWayCanvas.restore(); } @override @@ -359,26 +557,26 @@ class PaintVisitor extends LayerVisitor { final CkPaint paint = CkPaint(); paint.color = ui.Color.fromARGB(opacity.alpha, 0, 0, 0); - internalNodesCanvas.save(); - internalNodesCanvas.translate(opacity.offset.dx, opacity.offset.dy); + nWayCanvas.save(); + nWayCanvas.translate(opacity.offset.dx, opacity.offset.dy); final ui.Rect saveLayerBounds = opacity.paintBounds.shift(-opacity.offset); - internalNodesCanvas.saveLayer(saveLayerBounds, paint); + nWayCanvas.saveLayer(saveLayerBounds, paint); paintChildren(opacity); // Restore twice: once for the translate and once for the saveLayer. - internalNodesCanvas.restore(); - internalNodesCanvas.restore(); + nWayCanvas.restore(); + nWayCanvas.restore(); } @override void visitTransform(TransformEngineLayer transform, _) { assert(transform.needsPainting); - internalNodesCanvas.save(); - internalNodesCanvas.transform(transform.transform.storage); + nWayCanvas.save(); + nWayCanvas.transform(transform.transform.storage); paintChildren(transform); - internalNodesCanvas.restore(); + nWayCanvas.restore(); } @override @@ -391,23 +589,23 @@ class PaintVisitor extends LayerVisitor { assert(imageFilter.needsPainting); final ui.Rect offsetPaintBounds = imageFilter.paintBounds.shift(-imageFilter.offset); - internalNodesCanvas.save(); - internalNodesCanvas.translate(imageFilter.offset.dx, imageFilter.offset.dy); - internalNodesCanvas.clipRect(offsetPaintBounds, ui.ClipOp.intersect, false); + nWayCanvas.save(); + nWayCanvas.translate(imageFilter.offset.dx, imageFilter.offset.dy); + nWayCanvas.clipRect(offsetPaintBounds, ui.ClipOp.intersect, false); final CkPaint paint = CkPaint(); paint.imageFilter = imageFilter.filter; - internalNodesCanvas.saveLayer(offsetPaintBounds, paint); + nWayCanvas.saveLayer(offsetPaintBounds, paint); paintChildren(imageFilter); - internalNodesCanvas.restore(); - internalNodesCanvas.restore(); + nWayCanvas.restore(); + nWayCanvas.restore(); } @override void visitShaderMask(ShaderMaskEngineLayer shaderMask, _) { assert(shaderMask.needsPainting); - filterMaskLayers++; - internalNodesCanvas.saveLayer(shaderMask.paintBounds, null); + shaderMaskStack.add(shaderMask); + nWayCanvas.saveLayer(shaderMask.paintBounds, null); paintChildren(shaderMask); final CkPaint paint = CkPaint(); @@ -415,44 +613,54 @@ class PaintVisitor extends LayerVisitor { paint.blendMode = shaderMask.blendMode; paint.filterQuality = shaderMask.filterQuality; - leafNodesCanvas!.save(); - leafNodesCanvas! - .translate(shaderMask.maskRect.left, shaderMask.maskRect.top); - - leafNodesCanvas!.drawRect( - ui.Rect.fromLTWH( - 0, 0, shaderMask.maskRect.width, shaderMask.maskRect.height), - paint); - leafNodesCanvas!.restore(); - filterMaskLayers--; - - if (filterMaskLayers == 0) { - final CkCanvas? nextCanvas = viewEmbedder?.finalizePicture(); - if (nextCanvas != null) { - leafNodesCanvas = nextCanvas; + late List canvasesToApplyShaderMask; + if (viewEmbedder != null) { + final Set canvases = {}; + for (final PictureLayer picture in picturesUnderShaderMask[shaderMask]!) { + canvases.add(viewEmbedder!.getOptimizedCanvasFor(picture)); } + canvasesToApplyShaderMask = canvases.toList(); + } else { + canvasesToApplyShaderMask = [toImageCanvas!]; } - internalNodesCanvas.restore(); + for (final CkCanvas canvas in canvasesToApplyShaderMask) { + canvas.save(); + canvas.translate(shaderMask.maskRect.left, shaderMask.maskRect.top); + + canvas.drawRect( + ui.Rect.fromLTWH( + 0, 0, shaderMask.maskRect.width, shaderMask.maskRect.height), + paint); + canvas.restore(); + } + nWayCanvas.restore(); + shaderMaskStack.removeLast(); } @override void visitPicture(PictureLayer picture, _) { assert(picture.needsPainting); - leafNodesCanvas!.save(); - leafNodesCanvas!.translate(picture.offset.dx, picture.offset.dy); - - leafNodesCanvas!.drawPicture(picture.picture); - leafNodesCanvas!.restore(); + // For each shader mask this picture is a child of, record that it needs + // to have the shader mask applied to it. + for (final ShaderMaskEngineLayer shaderMask in shaderMaskStack) { + picturesUnderShaderMask.putIfAbsent(shaderMask, () => []); + picturesUnderShaderMask[shaderMask]!.add(picture); + } - if (filterMaskLayers == 0) { - final CkCanvas? nextCanvas = - viewEmbedder?.finalizePicture(picture.picture); - if (nextCanvas != null) { - leafNodesCanvas = nextCanvas; - } + late CkCanvas pictureRecorderCanvas; + if (viewEmbedder != null) { + pictureRecorderCanvas = viewEmbedder!.getOptimizedCanvasFor(picture); + } else { + pictureRecorderCanvas = toImageCanvas!; } + + pictureRecorderCanvas.save(); + pictureRecorderCanvas.translate(picture.offset.dx, picture.offset.dy); + + pictureRecorderCanvas.drawPicture(picture.picture); + pictureRecorderCanvas.restore(); } @override @@ -466,22 +674,20 @@ class PaintVisitor extends LayerVisitor { // then it will fill the entire `cullRect` of the picture, ignoring the // `paintBounds` passed to `saveLayer`. See: // https://github.com/flutter/flutter/issues/88866 - internalNodesCanvas.save(); + nWayCanvas.save(); // TODO(hterkelsen): Only clip if the ColorFilter affects transparent black. - internalNodesCanvas.clipRect( - colorFilter.paintBounds, ui.ClipOp.intersect, false); + nWayCanvas.clipRect(colorFilter.paintBounds, ui.ClipOp.intersect, false); - internalNodesCanvas.saveLayer(colorFilter.paintBounds, paint); + nWayCanvas.saveLayer(colorFilter.paintBounds, paint); paintChildren(colorFilter); - internalNodesCanvas.restore(); - internalNodesCanvas.restore(); + nWayCanvas.restore(); + nWayCanvas.restore(); } @override void visitPlatformView(PlatformViewLayer platformView, _) { - // TODO(harryterkelsen): Warn if we are a child of a backdrop filter or - // shader mask. - viewEmbedder?.compositeEmbeddedView(platformView.viewId); + // Do nothing. The platform view was already measured and placed in the + // optimized rendering in the measure step. } } From 2bd9f181baea1970af8f49913a3df99eef2bc72b Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 23 Sep 2024 11:31:59 -0700 Subject: [PATCH 10/14] remove debugging print --- lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 0f88e1e42e8db..44b11fea88efb 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -113,9 +113,6 @@ class HtmlViewEmbedder { void prerollPicture(PictureLayer picture) { final CkPictureRecorder pictureRecorder = CkPictureRecorder(); pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); - if (_context.pictureRecordersCreatedDuringPreroll.containsKey(picture)) { - print('PREROLLED PICTURE ALREADY EXISTS IN SCENE!!!'); - } _context.pictureRecordersCreatedDuringPreroll[picture] = pictureRecorder; } From 816a2f924f5225842808224da008720786f11cbf Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 23 Sep 2024 13:10:15 -0700 Subject: [PATCH 11/14] fix licenses --- ci/licenses_golden/licenses_flutter | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f6cdc665d4706..6dae3b2eb7593 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -43589,6 +43589,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.da ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart + ../../../flutter/LICENSE @@ -46469,6 +46470,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart From 76ef3d7b424d5e33b1dc83208e335d9a107e5942 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 23 Sep 2024 13:17:25 -0700 Subject: [PATCH 12/14] fix license and analysis hints --- .../src/engine/canvaskit/layer_visitor.dart | 117 ++++++++---------- 1 file changed, 52 insertions(+), 65 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart index 1a80a16ddb785..a51bd013d226f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart @@ -87,21 +87,21 @@ class PrerollVisitor extends LayerVisitor { } @override - void visitRoot(RootLayer root, Matrix4 matrix) { - prerollContainerLayer(root, matrix); + void visitRoot(RootLayer root, Matrix4 childData) { + prerollContainerLayer(root, childData); } @override void visitBackdropFilter( - BackdropFilterEngineLayer backdropFilter, Matrix4 matrix) { - final ui.Rect childBounds = prerollChildren(backdropFilter, matrix); + BackdropFilterEngineLayer backdropFilter, Matrix4 childData) { + final ui.Rect childBounds = prerollChildren(backdropFilter, childData); backdropFilter.paintBounds = childBounds.expandToInclude(cullRect); } @override - void visitClipPath(ClipPathEngineLayer clipPath, Matrix4 matrix) { + void visitClipPath(ClipPathEngineLayer clipPath, Matrix4 childData) { mutatorsStack.pushClipPath(clipPath.clipPath); - final ui.Rect childPaintBounds = prerollChildren(clipPath, matrix); + final ui.Rect childPaintBounds = prerollChildren(clipPath, childData); final ui.Rect clipBounds = clipPath.clipPath.getBounds(); if (childPaintBounds.overlaps(clipBounds)) { clipPath.paintBounds = childPaintBounds.intersect(clipBounds); @@ -110,9 +110,9 @@ class PrerollVisitor extends LayerVisitor { } @override - void visitClipRRect(ClipRRectEngineLayer clipRRect, Matrix4 matrix) { + void visitClipRRect(ClipRRectEngineLayer clipRRect, Matrix4 childData) { mutatorsStack.pushClipRRect(clipRRect.clipRRect); - final ui.Rect childPaintBounds = prerollChildren(clipRRect, matrix); + final ui.Rect childPaintBounds = prerollChildren(clipRRect, childData); if (childPaintBounds.overlaps(clipRRect.clipRRect.outerRect)) { clipRRect.paintBounds = childPaintBounds.intersect(clipRRect.clipRRect.outerRect); @@ -121,9 +121,9 @@ class PrerollVisitor extends LayerVisitor { } @override - void visitClipRect(ClipRectEngineLayer clipRect, Matrix4 matrix) { + void visitClipRect(ClipRectEngineLayer clipRect, Matrix4 childData) { mutatorsStack.pushClipRect(clipRect.clipRect); - final ui.Rect childPaintBounds = prerollChildren(clipRect, matrix); + final ui.Rect childPaintBounds = prerollChildren(clipRect, childData); if (childPaintBounds.overlaps(clipRect.clipRect)) { clipRect.paintBounds = childPaintBounds.intersect(clipRect.clipRect); } @@ -131,13 +131,13 @@ class PrerollVisitor extends LayerVisitor { } @override - void visitColorFilter(ColorFilterEngineLayer colorFilter, Matrix4 matrix) { - prerollContainerLayer(colorFilter, matrix); + void visitColorFilter(ColorFilterEngineLayer colorFilter, Matrix4 childData) { + prerollContainerLayer(colorFilter, childData); } @override - void visitImageFilter(ImageFilterEngineLayer imageFilter, Matrix4 matrix) { - final Matrix4 childMatrix = Matrix4.copy(matrix); + void visitImageFilter(ImageFilterEngineLayer imageFilter, Matrix4 childData) { + final Matrix4 childMatrix = Matrix4.copy(childData); childMatrix.translate(imageFilter.offset.dx, imageFilter.offset.dy); mutatorsStack.pushTransform(Matrix4.translationValues( imageFilter.offset.dx, imageFilter.offset.dy, 0.0)); @@ -166,13 +166,13 @@ class PrerollVisitor extends LayerVisitor { } @override - void visitOffset(OffsetEngineLayer offset, Matrix4 matrix) { - visitTransform(offset, matrix); + void visitOffset(OffsetEngineLayer offset, Matrix4 childData) { + visitTransform(offset, childData); } @override - void visitOpacity(OpacityEngineLayer opacity, Matrix4 matrix) { - final Matrix4 childMatrix = Matrix4.copy(matrix); + void visitOpacity(OpacityEngineLayer opacity, Matrix4 childData) { + final Matrix4 childMatrix = Matrix4.copy(childData); childMatrix.translate(opacity.offset.dx, opacity.offset.dy); mutatorsStack.pushTransform( Matrix4.translationValues(opacity.offset.dx, opacity.offset.dy, 0.0)); @@ -185,13 +185,13 @@ class PrerollVisitor extends LayerVisitor { } @override - void visitPicture(PictureLayer picture, Matrix4 matrix) { + void visitPicture(PictureLayer picture, Matrix4 childData) { picture.paintBounds = picture.picture.cullRect.shift(picture.offset); viewEmbedder?.prerollPicture(picture); } @override - void visitPlatformView(PlatformViewLayer platformView, Matrix4 matrix) { + void visitPlatformView(PlatformViewLayer platformView, Matrix4 childData) { platformView.paintBounds = ui.Rect.fromLTWH( platformView.offset.dx, platformView.offset.dy, @@ -212,13 +212,13 @@ class PrerollVisitor extends LayerVisitor { } @override - void visitShaderMask(ShaderMaskEngineLayer shaderMask, Matrix4 matrix) { - shaderMask.paintBounds = prerollChildren(shaderMask, matrix); + void visitShaderMask(ShaderMaskEngineLayer shaderMask, Matrix4 childData) { + shaderMask.paintBounds = prerollChildren(shaderMask, childData); } @override - void visitTransform(TransformEngineLayer transform, Matrix4 matrix) { - final Matrix4 childMatrix = matrix.multiplied(transform.transform); + void visitTransform(TransformEngineLayer transform, Matrix4 childData) { + final Matrix4 childMatrix = childData.multiplied(transform.transform); mutatorsStack.pushTransform(transform.transform); final ui.Rect childPaintBounds = prerollChildren(transform, childMatrix); transform.paintBounds = transform.transform.transformRect(childPaintBounds); @@ -254,17 +254,18 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitRoot(RootLayer root, _) { + void visitRoot(RootLayer root, void childData) { measureChildren(root); } @override - void visitBackdropFilter(BackdropFilterEngineLayer backdropFilter, _) { + void visitBackdropFilter( + BackdropFilterEngineLayer backdropFilter, void childData) { measureChildren(backdropFilter); } @override - void visitClipPath(ClipPathEngineLayer clipPath, _) { + void visitClipPath(ClipPathEngineLayer clipPath, void childData) { assert(clipPath.needsPainting); nWayCanvas.save(); @@ -282,7 +283,7 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitClipRect(ClipRectEngineLayer clipRect, _) { + void visitClipRect(ClipRectEngineLayer clipRect, void childData) { assert(clipRect.needsPainting); nWayCanvas.save(); @@ -302,7 +303,7 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitClipRRect(ClipRRectEngineLayer clipRRect, _) { + void visitClipRRect(ClipRRectEngineLayer clipRRect, void childData) { assert(clipRRect.needsPainting); nWayCanvas.save(); @@ -319,7 +320,7 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitOpacity(OpacityEngineLayer opacity, _) { + void visitOpacity(OpacityEngineLayer opacity, void childData) { assert(opacity.needsPainting); final CkPaint paint = CkPaint(); @@ -338,7 +339,7 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitTransform(TransformEngineLayer transform, _) { + void visitTransform(TransformEngineLayer transform, void childData) { assert(transform.needsPainting); nWayCanvas.save(); @@ -348,12 +349,12 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitOffset(OffsetEngineLayer offset, _) { + void visitOffset(OffsetEngineLayer offset, void childData) { visitTransform(offset, null); } @override - void visitImageFilter(ImageFilterEngineLayer imageFilter, _) { + void visitImageFilter(ImageFilterEngineLayer imageFilter, void childData) { assert(imageFilter.needsPainting); final ui.Rect offsetPaintBounds = imageFilter.paintBounds.shift(-imageFilter.offset); @@ -369,32 +370,17 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitShaderMask(ShaderMaskEngineLayer shaderMask, _) { + void visitShaderMask(ShaderMaskEngineLayer shaderMask, void childData) { assert(shaderMask.needsPainting); nWayCanvas.saveLayer(shaderMask.paintBounds, null); measureChildren(shaderMask); - // final CkPaint paint = CkPaint(); - // paint.shader = shaderMask.shader; - // paint.blendMode = shaderMask.blendMode; - // paint.filterQuality = shaderMask.filterQuality; - - // pictureRecorderCanvas!.save(); - // pictureRecorderCanvas! - // .translate(shaderMask.maskRect.left, shaderMask.maskRect.top); - - // pictureRecorderCanvas!.drawRect( - // ui.Rect.fromLTWH( - // 0, 0, shaderMask.maskRect.width, shaderMask.maskRect.height), - // paint); - // pictureRecorderCanvas!.restore(); - nWayCanvas.restore(); } @override - void visitPicture(PictureLayer picture, _) { + void visitPicture(PictureLayer picture, void childData) { assert(picture.needsPainting); final CkCanvas pictureRecorderCanvas = @@ -410,7 +396,7 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitColorFilter(ColorFilterEngineLayer colorFilter, _) { + void visitColorFilter(ColorFilterEngineLayer colorFilter, void childData) { assert(colorFilter.needsPainting); final CkPaint paint = CkPaint(); @@ -432,7 +418,7 @@ class MeasureVisitor extends LayerVisitor { } @override - void visitPlatformView(PlatformViewLayer platformView, _) { + void visitPlatformView(PlatformViewLayer platformView, void childData) { // TODO(harryterkelsen): Warn if we are a child of a backdrop filter or // shader mask. viewEmbedder.compositeEmbeddedView(platformView.viewId); @@ -481,12 +467,13 @@ class PaintVisitor extends LayerVisitor { } @override - void visitRoot(RootLayer root, _) { + void visitRoot(RootLayer root, void childData) { paintChildren(root); } @override - void visitBackdropFilter(BackdropFilterEngineLayer backdropFilter, _) { + void visitBackdropFilter( + BackdropFilterEngineLayer backdropFilter, void childData) { final CkPaint paint = CkPaint()..blendMode = backdropFilter.blendMode; nWayCanvas.saveLayerWithFilter( @@ -496,7 +483,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitClipPath(ClipPathEngineLayer clipPath, _) { + void visitClipPath(ClipPathEngineLayer clipPath, void childData) { assert(clipPath.needsPainting); nWayCanvas.save(); @@ -514,7 +501,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitClipRect(ClipRectEngineLayer clipRect, _) { + void visitClipRect(ClipRectEngineLayer clipRect, void childData) { assert(clipRect.needsPainting); nWayCanvas.save(); @@ -534,7 +521,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitClipRRect(ClipRRectEngineLayer clipRRect, _) { + void visitClipRRect(ClipRRectEngineLayer clipRRect, void childData) { assert(clipRRect.needsPainting); nWayCanvas.save(); @@ -551,7 +538,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitOpacity(OpacityEngineLayer opacity, _) { + void visitOpacity(OpacityEngineLayer opacity, void childData) { assert(opacity.needsPainting); final CkPaint paint = CkPaint(); @@ -570,7 +557,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitTransform(TransformEngineLayer transform, _) { + void visitTransform(TransformEngineLayer transform, void childData) { assert(transform.needsPainting); nWayCanvas.save(); @@ -580,12 +567,12 @@ class PaintVisitor extends LayerVisitor { } @override - void visitOffset(OffsetEngineLayer offset, _) { + void visitOffset(OffsetEngineLayer offset, void childData) { visitTransform(offset, null); } @override - void visitImageFilter(ImageFilterEngineLayer imageFilter, _) { + void visitImageFilter(ImageFilterEngineLayer imageFilter, void childData) { assert(imageFilter.needsPainting); final ui.Rect offsetPaintBounds = imageFilter.paintBounds.shift(-imageFilter.offset); @@ -601,7 +588,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitShaderMask(ShaderMaskEngineLayer shaderMask, _) { + void visitShaderMask(ShaderMaskEngineLayer shaderMask, void childData) { assert(shaderMask.needsPainting); shaderMaskStack.add(shaderMask); @@ -639,7 +626,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitPicture(PictureLayer picture, _) { + void visitPicture(PictureLayer picture, void childData) { assert(picture.needsPainting); // For each shader mask this picture is a child of, record that it needs @@ -664,7 +651,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitColorFilter(ColorFilterEngineLayer colorFilter, _) { + void visitColorFilter(ColorFilterEngineLayer colorFilter, void childData) { assert(colorFilter.needsPainting); final CkPaint paint = CkPaint(); @@ -686,7 +673,7 @@ class PaintVisitor extends LayerVisitor { } @override - void visitPlatformView(PlatformViewLayer platformView, _) { + void visitPlatformView(PlatformViewLayer platformView, void childData) { // Do nothing. The platform view was already measured and placed in the // optimized rendering in the measure step. } From 80127088f8c8896ee9f6af27028710d228862dd7 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 24 Sep 2024 11:15:35 -0700 Subject: [PATCH 13/14] respond to review comments --- .../src/engine/canvaskit/embedded_views.dart | 87 ++++++++++--------- .../src/engine/canvaskit/layer_visitor.dart | 2 +- .../canvaskit/overlay_scene_optimizer.dart | 39 +++++---- 3 files changed, 67 insertions(+), 61 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 44b11fea88efb..699aeedc97495 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -5,7 +5,8 @@ import 'dart:math' as math; import 'package:ui/ui.dart' as ui; -import '../../engine.dart' show PlatformViewManager, configuration, longestIncreasingSubsequence; +import '../../engine.dart' + show PlatformViewManager, configuration, longestIncreasingSubsequence; import '../display.dart'; import '../dom.dart'; import '../html/path_to_svg_clip.dart'; @@ -84,7 +85,7 @@ class HtmlViewEmbedder { /// an N-way canvas for the rasterizer to record clip and transform operations /// during the measure step. Iterable getPictureCanvases() { - return _context.pictureRecordersCreatedDuringPreroll.values + return _context.measuringPictureRecorders.values .map((CkPictureRecorder r) => r.recordingCanvas!); } @@ -113,21 +114,20 @@ class HtmlViewEmbedder { void prerollPicture(PictureLayer picture) { final CkPictureRecorder pictureRecorder = CkPictureRecorder(); pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize()); - _context.pictureRecordersCreatedDuringPreroll[picture] = pictureRecorder; + _context.measuringPictureRecorders[picture] = pictureRecorder; } - /// Returns the picture recorder canvas that was created to measure [picture]. - CkCanvas getPictureRecorderFor(PictureLayer picture) { - return _context - .pictureRecordersCreatedDuringPreroll[picture]!.recordingCanvas!; + /// Returns the canvas that was created to measure [picture]. + CkCanvas getMeasuringCanvasFor(PictureLayer picture) { + return _context.measuringPictureRecorders[picture]!.recordingCanvas!; } /// Adds the picture recorder associated with [picture] to the unoptimized /// scene. void addPictureToUnoptimizedScene(PictureLayer picture) { - final CkPictureRecorder currentRecorder = - _context.pictureRecordersCreatedDuringPreroll[picture]!; - _context.sceneElements.add(currentRecorder); + final CkPictureRecorder recorder = + _context.measuringPictureRecorders[picture]!; + _context.sceneElements.add(PictureSceneElement(picture, recorder)); } /// Prepares to composite [viewId]. @@ -136,7 +136,7 @@ class HtmlViewEmbedder { rasterizer.view.dom.injectPlatformView(viewId); _compositionOrder.add(viewId); - _context.sceneElements.add(viewId); + _context.sceneElements.add(PlatformViewSceneElement(viewId)); if (_viewsToRecomposite.contains(viewId)) { _compositeWithParams(viewId, _currentCompositionParams[viewId]!); @@ -364,26 +364,22 @@ class HtmlViewEmbedder { sceneHost.append(_svgPathDefs!); } + /// Optimizes the scene to use the fewest possible canvases. This sets up + /// the final paint pass to paint the pictures into the optimized canvases. void optimizeRendering() { final Map scenePictureToRawPicture = {}; - final Map reversePictureRecorderMap = - {}; - for (final MapEntry entry - in _context.pictureRecordersCreatedDuringPreroll.entries) { - reversePictureRecorderMap[entry.value] = entry.key; - } - final List unoptimizedRendering = - _context.sceneElements.map((Object element) { - if (element is CkPictureRecorder) { - final CkPicture scenePicture = element.endRecording(); - final PictureLayer rawPicture = reversePictureRecorderMap[element]!; - scenePictureToRawPicture[scenePicture] = rawPicture; - return scenePicture; + final Iterable unoptimizedRendering = + _context.sceneElements.map((SceneElement element) { + if (element is PictureSceneElement) { + final CkPicture scenePicture = element.pictureRecorder.endRecording(); + element.scenePicture = scenePicture; + scenePictureToRawPicture[scenePicture] = element.picture; + return element; } else { return element; } - }).toList(); + }); Rendering rendering = createOptimizedRendering( unoptimizedRendering, _currentCompositionParams); rendering = _modifyRenderingForMaxCanvases(rendering); @@ -407,6 +403,8 @@ class HtmlViewEmbedder { _context.pictureToOptimizedCanvasMap = pictureToOptimizedCanvasMap; } + /// Returns the canvas that this picture layer should draw into in the + /// optimized scene. CkCanvas getOptimizedCanvasFor(PictureLayer picture) { assert(_context.optimizedRendering != null); return _context.pictureToOptimizedCanvasMap![picture]!.recordingCanvas!; @@ -436,7 +434,7 @@ class HtmlViewEmbedder { } for (final CkPictureRecorder recorder - in _context.pictureRecordersCreatedDuringPreroll.values) { + in _context.measuringPictureRecorders.values) { if (recorder.isRecording) { recorder.endRecording(); } @@ -958,27 +956,34 @@ class MutatorsStack extends Iterable { Iterable get reversed => _mutators; } +sealed class SceneElement {} + +class PictureSceneElement extends SceneElement { + PictureSceneElement(this.picture, this.pictureRecorder); + + final PictureLayer picture; + final CkPictureRecorder pictureRecorder; + + /// The picture as it would be painted in the final scene, with clips and + /// transforms applied. This is set by [optimizeRendering]. + CkPicture? scenePicture; +} + +class PlatformViewSceneElement extends SceneElement { + PlatformViewSceneElement(this.viewId); + + final int viewId; +} + /// The state for the current frame. class EmbedderFrameContext { - /// Picture recorders which were created during the preroll phase. - /// - /// These picture recorders will be "claimed" in the paint phase by platform - /// views being composited into the scene. - final Map - pictureRecordersCreatedDuringPreroll = + /// Picture recorders which were created d the final bounds of the picture in the scene. + final Map measuringPictureRecorders = {}; - /// Picture recorders which were actually used in the paint phase. - /// - /// This is a subset of [_pictureRecordersCreatedDuringPreroll]. - final List pictureRecorders = []; - - /// The index of the current picture recorder. - int pictureRecorderIndex = 0; - /// List of picture recorders and platform view ids in the order they were /// painted. - final List sceneElements = []; + final List sceneElements = []; /// The optimized rendering for this frame. This is set by calling /// [optimizeRendering]. diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart index a51bd013d226f..78b2d1fcd577d 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart @@ -384,7 +384,7 @@ class MeasureVisitor extends LayerVisitor { assert(picture.needsPainting); final CkCanvas pictureRecorderCanvas = - viewEmbedder.getPictureRecorderFor(picture); + viewEmbedder.getMeasuringCanvasFor(picture); pictureRecorderCanvas.save(); pictureRecorderCanvas.translate(picture.offset.dx, picture.offset.dy); diff --git a/lib/web_ui/lib/src/engine/canvaskit/overlay_scene_optimizer.dart b/lib/web_ui/lib/src/engine/canvaskit/overlay_scene_optimizer.dart index e8d5220d3b2cc..2d70f2329e1e2 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/overlay_scene_optimizer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/overlay_scene_optimizer.dart @@ -157,7 +157,7 @@ ui.Rect computePlatformViewBounds(EmbeddedViewParams params) { /// /// [paramsForViews] is required to compute the bounds of the platform views. Rendering createOptimizedRendering( - List renderObjects, + Iterable renderObjects, Map paramsForViews, ) { final Map cachedComputedRects = {}; @@ -167,13 +167,13 @@ Rendering createOptimizedRendering( // The first picture is added to the rendering in a new render canvas. RenderingRenderCanvas tentativeCanvas = RenderingRenderCanvas(); - for (final Object renderObject in renderObjects) { - if (renderObject is int) { - final RenderingPlatformView platformView = - RenderingPlatformView(renderObject); - if (PlatformViewManager.instance.isVisible(renderObject)) { - final ui.Rect platformViewBounds = cachedComputedRects[renderObject] = - computePlatformViewBounds(paramsForViews[renderObject]!); + for (final SceneElement renderObject in renderObjects) { + if (renderObject is PlatformViewSceneElement) { + final int viewId = renderObject.viewId; + final RenderingPlatformView platformView = RenderingPlatformView(viewId); + if (PlatformViewManager.instance.isVisible(viewId)) { + final ui.Rect platformViewBounds = cachedComputedRects[viewId] = + computePlatformViewBounds(paramsForViews[viewId]!); if (debugOverlayOptimizationBounds) { platformView.debugComputedBounds = platformViewBounds; @@ -190,8 +190,9 @@ Rendering createOptimizedRendering( } } result.add(platformView); - } else if (renderObject is CkPicture) { - if (renderObject.cullRect.isEmpty) { + } else if (renderObject is PictureSceneElement) { + final CkPicture scenePicture = renderObject.scenePicture!; + if (scenePicture.cullRect.isEmpty) { continue; } @@ -204,8 +205,8 @@ Rendering createOptimizedRendering( // when it is eventually added. bool addedToTentativeCanvas = false; for (final CkPicture picture in tentativeCanvas.pictures) { - if (!picture.cullRect.intersect(renderObject.cullRect).isEmpty) { - tentativeCanvas.add(renderObject); + if (!picture.cullRect.intersect(scenePicture.cullRect).isEmpty) { + tentativeCanvas.add(scenePicture); addedToTentativeCanvas = true; break; } @@ -221,14 +222,14 @@ Rendering createOptimizedRendering( if (PlatformViewManager.instance.isVisible(entity.viewId)) { final ui.Rect platformViewBounds = cachedComputedRects[entity.viewId]!; - if (!platformViewBounds.intersect(renderObject.cullRect).isEmpty) { + if (!platformViewBounds.intersect(scenePicture.cullRect).isEmpty) { // The next picture intersects with a platform view already in the // result. Add this picture to the first render canvas which comes // after this platform view or create one if none exists. if (lastCanvasSeen != null) { - lastCanvasSeen.add(renderObject); + lastCanvasSeen.add(scenePicture); } else { - tentativeCanvas.add(renderObject); + tentativeCanvas.add(scenePicture); } addedPictureToRendering = true; break; @@ -238,8 +239,8 @@ Rendering createOptimizedRendering( lastCanvasSeen = entity; // Check if we intersect with any pictures in this render canvas. for (final CkPicture picture in entity.pictures) { - if (!picture.cullRect.intersect(renderObject.cullRect).isEmpty) { - lastCanvasSeen.add(renderObject); + if (!picture.cullRect.intersect(scenePicture.cullRect).isEmpty) { + lastCanvasSeen.add(scenePicture); addedPictureToRendering = true; break; } @@ -249,9 +250,9 @@ Rendering createOptimizedRendering( if (!addedPictureToRendering) { if (lastCanvasSeen != null) { // Add it to the last canvas seen in the rendering, if any. - lastCanvasSeen.add(renderObject); + lastCanvasSeen.add(scenePicture); } else { - tentativeCanvas.add(renderObject); + tentativeCanvas.add(scenePicture); } } } From 39c81d7e59767291732a82298096881531a41178 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 24 Sep 2024 11:27:55 -0700 Subject: [PATCH 14/14] fix errant import --- lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 699aeedc97495..936f07b349e65 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -5,8 +5,7 @@ import 'dart:math' as math; import 'package:ui/ui.dart' as ui; -import '../../engine.dart' - show PlatformViewManager, configuration, longestIncreasingSubsequence; +import '../../engine.dart' show PlatformViewManager, configuration, longestIncreasingSubsequence; import '../display.dart'; import '../dom.dart'; import '../html/path_to_svg_clip.dart';