Revert "Reland [skwasm] Scene builder optimizations for platform view placement (#55468)" (#55715)
This reverts commit def85c709fa1239df33147189769c67ae8185be0 (https://github.com/flutter/engine/pull/55468)
Reason for revert, devtools has been having rendering issues since this commit. See https://github.com/flutter/devtools/issues/8401
diff --git a/lib/web_ui/lib/src/engine/color_filter.dart b/lib/web_ui/lib/src/engine/color_filter.dart
index a74145c..83a7ca2 100644
--- a/lib/web_ui/lib/src/engine/color_filter.dart
+++ b/lib/web_ui/lib/src/engine/color_filter.dart
@@ -135,7 +135,4 @@
return 'ColorFilter.srgbToLinearGamma()';
}
}
-
- @override
- Matrix4? get transform => null;
}
diff --git a/lib/web_ui/lib/src/engine/layers.dart b/lib/web_ui/lib/src/engine/layers.dart
index 538b6d5..f3658dd 100644
--- a/lib/web_ui/lib/src/engine/layers.dart
+++ b/lib/web_ui/lib/src/engine/layers.dart
@@ -5,51 +5,15 @@
import 'dart:typed_data';
import 'package:meta/meta.dart';
-import 'package:ui/src/engine.dart';
+import 'package:ui/src/engine/scene_painting.dart';
+import 'package:ui/src/engine/vector_math.dart';
import 'package:ui/ui.dart' as ui;
-class EngineRootLayer with PictureEngineLayer {
- @override
- final NoopOperation operation = const NoopOperation();
-
- @override
- EngineRootLayer emptyClone() => EngineRootLayer();
-}
-
-class NoopOperation implements LayerOperation {
- const NoopOperation();
-
- @override
- PlatformViewStyling createPlatformViewStyling() => const PlatformViewStyling();
-
- @override
- ui.Rect mapRect(ui.Rect contentRect) => contentRect;
-
- @override
- void pre(SceneCanvas canvas) {
- canvas.save();
- }
-
- @override
- void post(SceneCanvas canvas) {
- canvas.restore();
- }
-
- @override
- bool get shouldDrawIfEmpty => false;
-}
+class EngineRootLayer with PictureEngineLayer {}
class BackdropFilterLayer
with PictureEngineLayer
- implements ui.BackdropFilterEngineLayer {
- BackdropFilterLayer(this.operation);
-
- @override
- final LayerOperation operation;
-
- @override
- BackdropFilterLayer emptyClone() => BackdropFilterLayer(operation);
-}
+ implements ui.BackdropFilterEngineLayer {}
class BackdropFilterOperation implements LayerOperation {
BackdropFilterOperation(this.filter, this.mode);
@@ -60,12 +24,12 @@
ui.Rect mapRect(ui.Rect contentRect) => contentRect;
@override
- void pre(SceneCanvas canvas) {
- canvas.saveLayerWithFilter(ui.Rect.largest, ui.Paint()..blendMode = mode, filter);
+ void pre(SceneCanvas canvas, ui.Rect contentRect) {
+ canvas.saveLayerWithFilter(contentRect, ui.Paint()..blendMode = mode, filter);
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
canvas.restore();
}
@@ -80,15 +44,7 @@
class ClipPathLayer
with PictureEngineLayer
- implements ui.ClipPathEngineLayer {
- ClipPathLayer(this.operation);
-
- @override
- final ClipPathOperation operation;
-
- @override
- ClipPathLayer emptyClone() => ClipPathLayer(operation);
-}
+ implements ui.ClipPathEngineLayer {}
class ClipPathOperation implements LayerOperation {
ClipPathOperation(this.path, this.clip);
@@ -99,7 +55,7 @@
ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(path.getBounds());
@override
- void pre(SceneCanvas canvas) {
+ void pre(SceneCanvas canvas, ui.Rect contentRect) {
canvas.save();
canvas.clipPath(path, doAntiAlias: clip != ui.Clip.hardEdge);
if (clip == ui.Clip.antiAliasWithSaveLayer) {
@@ -108,7 +64,7 @@
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
if (clip == ui.Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
@@ -126,15 +82,7 @@
class ClipRectLayer
with PictureEngineLayer
- implements ui.ClipRectEngineLayer {
- ClipRectLayer(this.operation);
-
- @override
- final ClipRectOperation operation;
-
- @override
- ClipRectLayer emptyClone() => ClipRectLayer(operation);
-}
+ implements ui.ClipRectEngineLayer {}
class ClipRectOperation implements LayerOperation {
const ClipRectOperation(this.rect, this.clip);
@@ -145,7 +93,7 @@
ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rect);
@override
- void pre(SceneCanvas canvas) {
+ void pre(SceneCanvas canvas, ui.Rect contentRect) {
canvas.save();
canvas.clipRect(rect, doAntiAlias: clip != ui.Clip.hardEdge);
if (clip == ui.Clip.antiAliasWithSaveLayer) {
@@ -154,7 +102,7 @@
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
if (clip == ui.Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
@@ -172,15 +120,7 @@
class ClipRRectLayer
with PictureEngineLayer
- implements ui.ClipRRectEngineLayer {
- ClipRRectLayer(this.operation);
-
- @override
- final ClipRRectOperation operation;
-
- @override
- ClipRRectLayer emptyClone() => ClipRRectLayer(operation);
-}
+ implements ui.ClipRRectEngineLayer {}
class ClipRRectOperation implements LayerOperation {
const ClipRRectOperation(this.rrect, this.clip);
@@ -191,7 +131,7 @@
ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rrect.outerRect);
@override
- void pre(SceneCanvas canvas) {
+ void pre(SceneCanvas canvas, ui.Rect contentRect) {
canvas.save();
canvas.clipRRect(rrect, doAntiAlias: clip != ui.Clip.hardEdge);
if (clip == ui.Clip.antiAliasWithSaveLayer) {
@@ -200,7 +140,7 @@
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
if (clip == ui.Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
@@ -218,15 +158,7 @@
class ColorFilterLayer
with PictureEngineLayer
- implements ui.ColorFilterEngineLayer {
- ColorFilterLayer(this.operation);
-
- @override
- final ColorFilterOperation operation;
-
- @override
- ColorFilterLayer emptyClone() => ColorFilterLayer(operation);
-}
+ implements ui.ColorFilterEngineLayer {}
class ColorFilterOperation implements LayerOperation {
ColorFilterOperation(this.filter);
@@ -236,12 +168,12 @@
ui.Rect mapRect(ui.Rect contentRect) => contentRect;
@override
- void pre(SceneCanvas canvas) {
- canvas.saveLayer(ui.Rect.largest, ui.Paint()..colorFilter = filter);
+ void pre(SceneCanvas canvas, ui.Rect contentRect) {
+ canvas.saveLayer(contentRect, ui.Paint()..colorFilter = filter);
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
canvas.restore();
}
@@ -254,15 +186,7 @@
class ImageFilterLayer
with PictureEngineLayer
- implements ui.ImageFilterEngineLayer {
- ImageFilterLayer(this.operation);
-
- @override
- final ImageFilterOperation operation;
-
- @override
- ImageFilterLayer emptyClone() => ImageFilterLayer(operation);
-}
+ implements ui.ImageFilterEngineLayer {}
class ImageFilterOperation implements LayerOperation {
ImageFilterOperation(this.filter, this.offset);
@@ -273,16 +197,17 @@
ui.Rect mapRect(ui.Rect contentRect) => filter.filterBounds(contentRect);
@override
- void pre(SceneCanvas canvas) {
+ void pre(SceneCanvas canvas, ui.Rect contentRect) {
if (offset != ui.Offset.zero) {
canvas.save();
canvas.translate(offset.dx, offset.dy);
}
- canvas.saveLayer(ui.Rect.largest, ui.Paint()..imageFilter = filter);
+ final ui.Rect adjustedContentRect = filter.filterBounds(contentRect);
+ canvas.saveLayer(adjustedContentRect, ui.Paint()..imageFilter = filter);
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
if (offset != ui.Offset.zero) {
canvas.restore();
}
@@ -291,22 +216,13 @@
@override
PlatformViewStyling createPlatformViewStyling() {
- PlatformViewStyling styling = const PlatformViewStyling();
if (offset != ui.Offset.zero) {
- styling = PlatformViewStyling(
+ return PlatformViewStyling(
position: PlatformViewPosition.offset(offset)
);
+ } else {
+ return const PlatformViewStyling();
}
- final Matrix4? transform = filter.transform;
- if (transform != null) {
- styling = PlatformViewStyling.combine(
- styling,
- PlatformViewStyling(
- position: PlatformViewPosition.transform(transform),
- ),
- );
- }
- return const PlatformViewStyling();
}
@override
@@ -315,15 +231,7 @@
class OffsetLayer
with PictureEngineLayer
- implements ui.OffsetEngineLayer {
- OffsetLayer(this.operation);
-
- @override
- final OffsetOperation operation;
-
- @override
- OffsetLayer emptyClone() => OffsetLayer(operation);
-}
+ implements ui.OffsetEngineLayer {}
class OffsetOperation implements LayerOperation {
OffsetOperation(this.dx, this.dy);
@@ -334,13 +242,13 @@
ui.Rect mapRect(ui.Rect contentRect) => contentRect.shift(ui.Offset(dx, dy));
@override
- void pre(SceneCanvas canvas) {
+ void pre(SceneCanvas canvas, ui.Rect cullRect) {
canvas.save();
canvas.translate(dx, dy);
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
canvas.restore();
}
@@ -355,15 +263,7 @@
class OpacityLayer
with PictureEngineLayer
- implements ui.OpacityEngineLayer {
- OpacityLayer(this.operation);
-
- @override
- final OpacityOperation operation;
-
- @override
- OpacityLayer emptyClone() => OpacityLayer(operation);
-}
+ implements ui.OpacityEngineLayer {}
class OpacityOperation implements LayerOperation {
OpacityOperation(this.alpha, this.offset);
@@ -374,19 +274,20 @@
ui.Rect mapRect(ui.Rect contentRect) => contentRect.shift(offset);
@override
- void pre(SceneCanvas canvas) {
+ void pre(SceneCanvas canvas, ui.Rect cullRect) {
if (offset != ui.Offset.zero) {
canvas.save();
canvas.translate(offset.dx, offset.dy);
+ cullRect = cullRect.shift(-offset);
}
canvas.saveLayer(
- ui.Rect.largest,
+ cullRect,
ui.Paint()..color = ui.Color.fromARGB(alpha, 0, 0, 0)
);
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
canvas.restore();
if (offset != ui.Offset.zero) {
canvas.restore();
@@ -405,15 +306,7 @@
class TransformLayer
with PictureEngineLayer
- implements ui.TransformEngineLayer {
- TransformLayer(this.operation);
-
- @override
- final TransformOperation operation;
-
- @override
- TransformLayer emptyClone() => TransformLayer(operation);
-}
+ implements ui.TransformEngineLayer {}
class TransformOperation implements LayerOperation {
TransformOperation(this.transform);
@@ -426,13 +319,13 @@
ui.Rect mapRect(ui.Rect contentRect) => matrix.transformRect(contentRect);
@override
- void pre(SceneCanvas canvas) {
+ void pre(SceneCanvas canvas, ui.Rect cullRect) {
canvas.save();
canvas.transform(transform);
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
canvas.restore();
}
@@ -447,15 +340,7 @@
class ShaderMaskLayer
with PictureEngineLayer
- implements ui.ShaderMaskEngineLayer {
- ShaderMaskLayer(this.operation);
-
- @override
- final ShaderMaskOperation operation;
-
- @override
- ShaderMaskLayer emptyClone() => ShaderMaskLayer(operation);
-}
+ implements ui.ShaderMaskEngineLayer {}
class ShaderMaskOperation implements LayerOperation {
ShaderMaskOperation(this.shader, this.maskRect, this.blendMode);
@@ -467,15 +352,15 @@
ui.Rect mapRect(ui.Rect contentRect) => contentRect;
@override
- void pre(SceneCanvas canvas) {
+ void pre(SceneCanvas canvas, ui.Rect contentRect) {
canvas.saveLayer(
- ui.Rect.largest,
+ contentRect,
ui.Paint(),
);
}
@override
- void post(SceneCanvas canvas) {
+ void post(SceneCanvas canvas, ui.Rect contentRect) {
canvas.save();
canvas.translate(maskRect.left, maskRect.top);
canvas.drawRect(
@@ -504,43 +389,47 @@
final ui.Rect bounds;
final PlatformViewStyling styling;
-
- @override
- String toString() {
- return 'PlatformView(viewId: $viewId, bounds: $bounds, styling: $styling)';
- }
}
-class LayerSlice {
- LayerSlice(this.picture, this.platformViews);
+sealed class LayerSlice {
+ void dispose();
+}
- // The picture of native flutter content to be rendered
+// A slice that contains one or more platform views to be rendered.
+class PlatformViewSlice implements LayerSlice {
+ PlatformViewSlice(this.views, this.occlusionRect);
+
+ List<PlatformView> views;
+
+ // A conservative estimate of what area platform views in this slice may cover.
+ // This is expressed in the coordinate space of the parent.
+ ui.Rect? occlusionRect;
+
+ @override
+ void dispose() {}
+}
+
+// A slice that contains flutter content to be rendered int he form of a single
+// ScenePicture.
+class PictureSlice implements LayerSlice {
+ PictureSlice(this.picture);
+
ScenePicture picture;
- // Platform views to be placed on top of the flutter content.
- final List<PlatformView> platformViews;
-
- void dispose() {
- picture.dispose();
- }
+ @override
+ void dispose() => picture.dispose();
}
mixin PictureEngineLayer implements ui.EngineLayer {
- // Each layer is represented as a series of "slices" which contain flutter content
- // with platform views on top. This is ordered from bottommost to topmost.
- List<LayerSlice?> slices = [];
-
- List<LayerDrawCommand> drawCommands = [];
- PlatformViewStyling platformViewStyling = const PlatformViewStyling();
-
- LayerOperation get operation;
-
- PictureEngineLayer emptyClone();
+ // Each layer is represented as a series of "slices" which contain either
+ // flutter content or platform views. Slices in this list are ordered from
+ // bottom to top.
+ List<LayerSlice> slices = <LayerSlice>[];
@override
void dispose() {
- for (final LayerSlice? slice in slices) {
- slice?.dispose();
+ for (final LayerSlice slice in slices) {
+ slice.dispose();
}
}
}
@@ -553,8 +442,8 @@
// layer operation.
ui.Rect mapRect(ui.Rect contentRect);
- void pre(SceneCanvas canvas);
- void post(SceneCanvas canvas);
+ void pre(SceneCanvas canvas, ui.Rect contentRect);
+ void post(SceneCanvas canvas, ui.Rect contentRect);
PlatformViewStyling createPlatformViewStyling();
@@ -564,29 +453,11 @@
bool get shouldDrawIfEmpty;
}
-sealed class LayerDrawCommand {
-}
+class PictureDrawCommand {
+ PictureDrawCommand(this.offset, this.picture);
-class PictureDrawCommand extends LayerDrawCommand {
- PictureDrawCommand(this.offset, this.picture, this.sliceIndex);
-
- final int sliceIndex;
- final ui.Offset offset;
- final ScenePicture picture;
-}
-
-class PlatformViewDrawCommand extends LayerDrawCommand {
- PlatformViewDrawCommand(this.viewId, this.bounds, this.sliceIndex);
-
- final int sliceIndex;
- final int viewId;
- final ui.Rect bounds;
-}
-
-class RetainedLayerDrawCommand extends LayerDrawCommand {
- RetainedLayerDrawCommand(this.layer);
-
- final PictureEngineLayer layer;
+ ui.Offset offset;
+ ui.Picture picture;
}
// Represents how a platform view should be positioned in the scene.
@@ -606,17 +477,6 @@
bool get isZero => (offset == null) && (transform == null);
- ui.Rect mapLocalToGlobal(ui.Rect rect) {
- if (offset != null) {
- return rect.shift(offset!);
- }
- if (transform != null) {
- return transform!.transformRect(rect);
- }
- return rect;
- }
-
- // Note that by construction only one of these can be set at any given time, not both.
final ui.Offset? offset;
final Matrix4? transform;
@@ -667,17 +527,6 @@
int get hashCode {
return Object.hash(offset, transform);
}
-
- @override
- String toString() {
- if (offset != null) {
- return 'PlatformViewPosition(offset: $offset)';
- }
- if (transform != null) {
- return 'PlatformViewPosition(transform: $transform)';
- }
- return 'PlatformViewPosition(zero)';
- }
}
// Represents the styling to be performed on a platform view when it is
@@ -696,10 +545,6 @@
final double opacity;
final PlatformViewClip clip;
- ui.Rect mapLocalToGlobal(ui.Rect rect) {
- return position.mapLocalToGlobal(rect).intersect(clip.outerRect);
- }
-
static PlatformViewStyling combine(PlatformViewStyling outer, PlatformViewStyling inner) {
// Attempt to reuse one of the existing immutable objects.
if (outer.isDefault) {
@@ -730,11 +575,6 @@
int get hashCode {
return Object.hash(position, opacity, clip);
}
-
- @override
- String toString() {
- return 'PlatformViewStyling(position: $position, clip: $clip, opacity: $opacity)';
- }
}
sealed class PlatformViewClip {
@@ -802,7 +642,7 @@
ui.Rect get innerRect => ui.Rect.zero;
@override
- ui.Rect get outerRect => ui.Rect.largest;
+ ui.Rect get outerRect => ui.Rect.zero;
}
class PlatformViewRectClip implements PlatformViewClip {
@@ -923,137 +763,164 @@
ui.Rect get outerRect => path.getBounds();
}
-class LayerSliceBuilder {
- factory LayerSliceBuilder() {
- final (recorder, canvas) = debugRecorderFactory != null ? debugRecorderFactory!() : defaultRecorderFactory();
- return LayerSliceBuilder._(recorder, canvas);
- }
- LayerSliceBuilder._(this.recorder, this.canvas);
-
- @visibleForTesting
- static (ui.PictureRecorder, SceneCanvas) Function()? debugRecorderFactory;
-
- static (ui.PictureRecorder, SceneCanvas) defaultRecorderFactory() {
- final ui.PictureRecorder recorder = ui.PictureRecorder();
- final SceneCanvas canvas = ui.Canvas(recorder, ui.Rect.largest) as SceneCanvas;
- return (recorder, canvas);
- }
-
- final ui.PictureRecorder recorder;
- final SceneCanvas canvas;
- final List<PlatformView> platformViews = <PlatformView>[];
-}
-
class LayerBuilder {
factory LayerBuilder.rootLayer() {
- return LayerBuilder._(null, EngineRootLayer());
+ return LayerBuilder._(null, EngineRootLayer(), null);
}
factory LayerBuilder.childLayer({
required LayerBuilder parent,
required PictureEngineLayer layer,
+ required LayerOperation operation
}) {
- return LayerBuilder._(parent, layer);
+ return LayerBuilder._(parent, layer, operation);
}
LayerBuilder._(
this.parent,
- this.layer);
+ this.layer,
+ this.operation);
+
+ @visibleForTesting
+ static (ui.PictureRecorder, SceneCanvas) Function(ui.Rect)? debugRecorderFactory;
final LayerBuilder? parent;
final PictureEngineLayer layer;
-
- final List<LayerSliceBuilder?> sliceBuilders = <LayerSliceBuilder?>[];
- final List<LayerDrawCommand> drawCommands = <LayerDrawCommand>[];
+ final LayerOperation? operation;
+ final List<PictureDrawCommand> pendingPictures = <PictureDrawCommand>[];
+ List<PlatformView> pendingPlatformViews = <PlatformView>[];
+ ui.Rect? picturesRect;
+ ui.Rect? platformViewRect;
PlatformViewStyling? _memoizedPlatformViewStyling;
+
PlatformViewStyling get platformViewStyling {
- return _memoizedPlatformViewStyling ??= layer.operation.createPlatformViewStyling();
+ return _memoizedPlatformViewStyling ??= operation?.createPlatformViewStyling() ?? const PlatformViewStyling();
}
- PlatformViewStyling? _memoizedGlobalPlatformViewStyling;
- PlatformViewStyling get globalPlatformViewStyling {
- if (_memoizedGlobalPlatformViewStyling != null) {
- return _memoizedGlobalPlatformViewStyling!;
+ (ui.PictureRecorder, SceneCanvas) _createRecorder(ui.Rect rect) {
+ if (debugRecorderFactory != null) {
+ return debugRecorderFactory!(rect);
}
- if (parent != null) {
- return _memoizedGlobalPlatformViewStyling ??= PlatformViewStyling.combine(parent!.globalPlatformViewStyling, platformViewStyling);
- }
- return _memoizedGlobalPlatformViewStyling ??= platformViewStyling;
+ final ui.PictureRecorder recorder = ui.PictureRecorder();
+ final SceneCanvas canvas = ui.Canvas(recorder, rect) as SceneCanvas;
+ return (recorder, canvas);
}
- LayerSliceBuilder getOrCreateSliceBuilderAtIndex(int index) {
- while (sliceBuilders.length <= index) {
- sliceBuilders.add(null);
+ void flushSlices() {
+ if (pendingPictures.isNotEmpty || (operation?.shouldDrawIfEmpty ?? false)) {
+ // Merge the existing draw commands into a single picture and add a slice
+ // with that picture to the slice list.
+ final ui.Rect drawnRect = picturesRect ?? ui.Rect.zero;
+ final ui.Rect rect = operation?.mapRect(drawnRect) ?? drawnRect;
+ final (ui.PictureRecorder recorder, SceneCanvas canvas) = _createRecorder(rect);
+
+ operation?.pre(canvas, rect);
+ for (final PictureDrawCommand command in pendingPictures) {
+ if (command.offset != ui.Offset.zero) {
+ canvas.save();
+ canvas.translate(command.offset.dx, command.offset.dy);
+ canvas.drawPicture(command.picture);
+ canvas.restore();
+ } else {
+ canvas.drawPicture(command.picture);
+ }
+ }
+ operation?.post(canvas, rect);
+ final ui.Picture picture = recorder.endRecording();
+ layer.slices.add(PictureSlice(picture as ScenePicture));
}
- final LayerSliceBuilder? existingSliceBuilder = sliceBuilders[index];
- if (existingSliceBuilder != null) {
- return existingSliceBuilder;
+
+ if (pendingPlatformViews.isNotEmpty) {
+ // Take any pending platform views and lower them into a platform view
+ // slice.
+ ui.Rect? occlusionRect = platformViewRect;
+ if (occlusionRect != null && operation != null) {
+ occlusionRect = operation!.mapRect(occlusionRect);
+ }
+ layer.slices.add(PlatformViewSlice(pendingPlatformViews, occlusionRect));
}
- final LayerSliceBuilder newSliceBuilder = LayerSliceBuilder();
- layer.operation.pre(newSliceBuilder.canvas);
- sliceBuilders[index] = newSliceBuilder;
- return newSliceBuilder;
+
+ pendingPictures.clear();
+ pendingPlatformViews = <PlatformView>[];
+
+ // All the pictures and platform views have been lowered into slices. Clear
+ // our occlusion rectangles.
+ picturesRect = null;
+ platformViewRect = null;
}
void addPicture(
ui.Offset offset,
ui.Picture picture, {
- required int sliceIndex,
+ bool isComplexHint = false,
+ bool willChangeHint = false
}) {
- final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(sliceIndex);
- final SceneCanvas canvas = sliceBuilder.canvas;
- if (offset != ui.Offset.zero) {
- canvas.save();
- canvas.translate(offset.dx, offset.dy);
- canvas.drawPicture(picture);
- canvas.restore();
- } else {
- canvas.drawPicture(picture);
+ final ui.Rect cullRect = (picture as ScenePicture).cullRect;
+ final ui.Rect shiftedRect = cullRect.shift(offset);
+
+ final ui.Rect? currentPlatformViewRect = platformViewRect;
+ if (currentPlatformViewRect != null) {
+ // Whenever we add a picture to our layer, we try to see if the picture
+ // will overlap with any platform views that are currently on top of our
+ // drawing surface. If they don't overlap with the platform views, they
+ // can be grouped with the existing pending pictures.
+ if (pendingPictures.isEmpty || currentPlatformViewRect.overlaps(shiftedRect)) {
+ // If they do overlap with the platform views, however, we should flush
+ // all the current content into slices and start anew with a fresh
+ // group of pictures and platform views that will be rendered on top of
+ // the previous content. Note that we also flush if we have no pending
+ // pictures to group with. This is the case when platform views are
+ // the first thing in our stack of objects to composite, and it doesn't
+ // make sense to try to put a picture slice below the first platform
+ // view slice, even if the picture doesn't overlap.
+ flushSlices();
+ }
}
- drawCommands.add(PictureDrawCommand(offset, picture as ScenePicture, sliceIndex));
+ pendingPictures.add(PictureDrawCommand(offset, picture));
+ picturesRect = picturesRect?.expandToInclude(shiftedRect) ?? shiftedRect;
}
void addPlatformView(
int viewId, {
- required ui.Rect bounds,
- required int sliceIndex,
+ ui.Offset offset = ui.Offset.zero,
+ double width = 0.0,
+ double height = 0.0
}) {
- final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(sliceIndex);
- sliceBuilder.platformViews.add(PlatformView(viewId, bounds, platformViewStyling));
- drawCommands.add(PlatformViewDrawCommand(viewId, bounds, sliceIndex));
+ final ui.Rect bounds = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height);
+ platformViewRect = platformViewRect?.expandToInclude(bounds) ?? bounds;
+ pendingPlatformViews.add(PlatformView(viewId, bounds, platformViewStyling));
}
void mergeLayer(PictureEngineLayer layer) {
- for (int i = 0; i < layer.slices.length; i++) {
- final LayerSlice? slice = layer.slices[i];
- if (slice != null) {
- final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(i);
- sliceBuilder.canvas.drawPicture(slice.picture);
- sliceBuilder.platformViews.addAll(slice.platformViews.map((PlatformView view) {
- return PlatformView(view.viewId, view.bounds, PlatformViewStyling.combine(platformViewStyling, view.styling));
- }));
+ // When we merge layers, we attempt to merge slices as much as possible as
+ // well, based on ordering of pictures and platform views and reusing the
+ // occlusion logic for determining where we can lower each picture.
+ for (final LayerSlice slice in layer.slices) {
+ switch (slice) {
+ case PictureSlice():
+ addPicture(ui.Offset.zero, slice.picture);
+ case PlatformViewSlice():
+ final ui.Rect? occlusionRect = slice.occlusionRect;
+ if (occlusionRect != null) {
+ platformViewRect = platformViewRect?.expandToInclude(occlusionRect) ?? occlusionRect;
+ }
+ for (final PlatformView view in slice.views) {
+ // Merge the platform view styling of this layer with the nested
+ // platform views.
+ final PlatformViewStyling styling = PlatformViewStyling.combine(
+ platformViewStyling,
+ view.styling,
+ );
+ pendingPlatformViews.add(PlatformView(view.viewId, view.bounds, styling));
+ }
}
}
- drawCommands.add(RetainedLayerDrawCommand(layer));
- }
-
- PictureEngineLayer sliceUp() {
- final List<LayerSlice?> slices = sliceBuilders.map((LayerSliceBuilder? builder) {
- if (builder == null) {
- return null;
- }
- layer.operation.post(builder.canvas);
- final ScenePicture picture = builder.recorder.endRecording() as ScenePicture;
- return LayerSlice(picture, builder.platformViews);
- }).toList();
- layer.slices = slices;
- return layer;
}
PictureEngineLayer build() {
- layer.drawCommands = drawCommands;
- layer.platformViewStyling = platformViewStyling;
- return sliceUp();
+ // Lower any pending pictures or platform views to their respective slices.
+ flushSlices();
+ return layer;
}
}
diff --git a/lib/web_ui/lib/src/engine/scene_builder.dart b/lib/web_ui/lib/src/engine/scene_builder.dart
index 6d6db06..af84cb0 100644
--- a/lib/web_ui/lib/src/engine/scene_builder.dart
+++ b/lib/web_ui/lib/src/engine/scene_builder.dart
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:math' as math;
import 'dart:typed_data';
import 'package:ui/src/engine.dart';
@@ -63,115 +62,17 @@
final ui.Rect canvasRect = ui.Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble());
final ui.Canvas canvas = ui.Canvas(recorder, canvasRect);
- // Only rasterizes the pictures.
- for (final LayerSlice? slice in rootLayer.slices) {
- if (slice != null) {
- canvas.drawPicture(slice.picture);
- }
+ // Only rasterizes the picture slices.
+ for (final PictureSlice slice in rootLayer.slices.whereType<PictureSlice>()) {
+ canvas.drawPicture(slice.picture);
}
return recorder.endRecording().toImageSync(width, height);
}
}
-sealed class OcclusionMapNode {
- bool overlaps(ui.Rect rect);
- OcclusionMapNode insert(ui.Rect rect);
- ui.Rect get boundingBox;
-}
-
-class OcclusionMapEmpty implements OcclusionMapNode {
- @override
- ui.Rect get boundingBox => ui.Rect.zero;
-
- @override
- OcclusionMapNode insert(ui.Rect rect) => OcclusionMapLeaf(rect);
-
- @override
- bool overlaps(ui.Rect rect) => false;
-
-}
-
-class OcclusionMapLeaf implements OcclusionMapNode {
- OcclusionMapLeaf(this.rect);
-
- final ui.Rect rect;
-
- @override
- ui.Rect get boundingBox => rect;
-
- @override
- OcclusionMapNode insert(ui.Rect other) => OcclusionMapBranch(this, OcclusionMapLeaf(other));
-
- @override
- bool overlaps(ui.Rect other) => rect.overlaps(other);
-}
-
-class OcclusionMapBranch implements OcclusionMapNode {
- OcclusionMapBranch(this.left, this.right)
- : boundingBox = left.boundingBox.expandToInclude(right.boundingBox);
-
- final OcclusionMapNode left;
- final OcclusionMapNode right;
-
- @override
- final ui.Rect boundingBox;
-
- double _areaOfUnion(ui.Rect first, ui.Rect second) {
- return (math.max(first.right, second.right) - math.min(first.left, second.left))
- * (math.max(first.bottom, second.bottom) - math.max(first.top, second.top));
- }
-
- @override
- OcclusionMapNode insert(ui.Rect other) {
- // Try to create nodes with the smallest possible area
- final double leftOtherArea = _areaOfUnion(left.boundingBox, other);
- final double rightOtherArea = _areaOfUnion(right.boundingBox, other);
- final double leftRightArea = boundingBox.width * boundingBox.height;
- if (leftOtherArea < rightOtherArea) {
- if (leftOtherArea < leftRightArea) {
- return OcclusionMapBranch(
- left.insert(other),
- right,
- );
- }
- } else {
- if (rightOtherArea < leftRightArea) {
- return OcclusionMapBranch(
- left,
- right.insert(other),
- );
- }
- }
- return OcclusionMapBranch(this, OcclusionMapLeaf(other));
- }
-
- @override
- bool overlaps(ui.Rect rect) {
- if (!boundingBox.overlaps(rect)) {
- return false;
- }
- return left.overlaps(rect) || right.overlaps(rect);
- }
-}
-
-class OcclusionMap {
- OcclusionMapNode root = OcclusionMapEmpty();
-
- void addRect(ui.Rect rect) => root = root.insert(rect);
-
- bool overlaps(ui.Rect rect) => root.overlaps(rect);
-}
-
-class SceneSlice {
- final OcclusionMap pictureOcclusionMap = OcclusionMap();
- final OcclusionMap platformViewOcclusionMap = OcclusionMap();
-}
-
class EngineSceneBuilder implements ui.SceneBuilder {
LayerBuilder currentBuilder = LayerBuilder.rootLayer();
- final List<SceneSlice> sceneSlices = <SceneSlice>[SceneSlice()];
-
@override
void addPerformanceOverlay(int enabledOptions, ui.Rect bounds) {
// We don't plan to implement this on the web.
@@ -185,44 +86,15 @@
bool isComplexHint = false,
bool willChangeHint = false
}) {
- final int sliceIndex = _placePicture(offset, picture as ScenePicture);
currentBuilder.addPicture(
offset,
picture,
- sliceIndex: sliceIndex,
+ isComplexHint:
+ isComplexHint,
+ willChangeHint: willChangeHint
);
}
- // This function determines the lowest scene slice that this picture can be placed
- // into and adds it to that slice's occlusion map.
- //
- // The picture is placed in the last slice where it either intersects with a picture
- // in the slice or it intersects with a platform view in the preceding slice. If the
- // picture intersects with a platform view in the last slice, a new slice is added at
- // the end and the picture goes in there.
- int _placePicture(ui.Offset offset, ScenePicture picture) {
- final ui.Rect cullRect = picture.cullRect.shift(offset);
- final ui.Rect mappedCullRect = currentBuilder.globalPlatformViewStyling.mapLocalToGlobal(cullRect);
- int sliceIndex = sceneSlices.length;
- while (sliceIndex > 0) {
- final SceneSlice sliceBelow = sceneSlices[sliceIndex - 1];
- if (sliceBelow.platformViewOcclusionMap.overlaps(mappedCullRect)) {
- break;
- }
- sliceIndex--;
- if (sliceBelow.pictureOcclusionMap.overlaps(mappedCullRect)) {
- break;
- }
- }
- if (sliceIndex == sceneSlices.length) {
- // Insert a new slice.
- sceneSlices.add(SceneSlice());
- }
- final SceneSlice slice = sceneSlices[sliceIndex];
- slice.pictureOcclusionMap.addRect(mappedCullRect);
- return sliceIndex;
- }
-
@override
void addPlatformView(
int viewId, {
@@ -230,94 +102,17 @@
double width = 0.0,
double height = 0.0
}) {
- final ui.Rect platformViewRect = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height);
- final int sliceIndex = _placePlatformView(viewId, platformViewRect);
currentBuilder.addPlatformView(
viewId,
- bounds: platformViewRect,
- sliceIndex: sliceIndex,
+ offset: offset,
+ width: width,
+ height: height
);
}
- // This function determines the lowest scene slice this platform view can be placed
- // into and adds it to that slice's occlusion map.
- //
- // The platform view is placed into the last slice where it intersects with a picture
- // or a platform view.
- int _placePlatformView(
- int viewId,
- ui.Rect rect, {
- PlatformViewStyling styling = const PlatformViewStyling(),
- }) {
- final PlatformViewStyling combinedStyling = PlatformViewStyling.combine(currentBuilder.globalPlatformViewStyling, styling);
- final ui.Rect globalPlatformViewRect = combinedStyling.mapLocalToGlobal(rect);
- int sliceIndex = sceneSlices.length - 1;
- while (sliceIndex > 0) {
- final SceneSlice slice = sceneSlices[sliceIndex];
- if (slice.platformViewOcclusionMap.overlaps(globalPlatformViewRect) ||
- slice.pictureOcclusionMap.overlaps(globalPlatformViewRect)) {
- break;
- }
- sliceIndex--;
- }
- final SceneSlice slice = sceneSlices[sliceIndex];
- slice.platformViewOcclusionMap.addRect(globalPlatformViewRect);
- return sliceIndex;
- }
-
@override
void addRetained(ui.EngineLayer retainedLayer) {
- final PictureEngineLayer placedEngineLayer = _placeRetainedLayer(retainedLayer as PictureEngineLayer);
- currentBuilder.mergeLayer(placedEngineLayer);
- }
-
- PictureEngineLayer _placeRetainedLayer(PictureEngineLayer retainedLayer) {
- bool needsRebuild = false;
- final List<LayerDrawCommand> revisedDrawCommands = [];
- for (final LayerDrawCommand command in retainedLayer.drawCommands) {
- switch (command) {
- case PictureDrawCommand(offset: final ui.Offset offset, picture: final ScenePicture picture):
- final int sliceIndex = _placePicture(offset, picture);
- if (command.sliceIndex != sliceIndex) {
- needsRebuild = true;
- }
- revisedDrawCommands.add(PictureDrawCommand(offset, picture, sliceIndex));
- case PlatformViewDrawCommand(viewId: final int viewId, bounds: final ui.Rect bounds):
- final int sliceIndex = _placePlatformView(viewId, bounds);
- if (command.sliceIndex != sliceIndex) {
- needsRebuild = true;
- }
- revisedDrawCommands.add(PlatformViewDrawCommand(viewId, bounds, sliceIndex));
- case RetainedLayerDrawCommand(layer: final PictureEngineLayer sublayer):
- final PictureEngineLayer revisedSublayer = _placeRetainedLayer(sublayer);
- if (sublayer != revisedSublayer) {
- needsRebuild = true;
- }
- revisedDrawCommands.add(RetainedLayerDrawCommand(revisedSublayer));
- }
- }
-
- if (!needsRebuild) {
- // No elements changed which slice position they are in, so we can simply
- // merge the existing layer down and don't have to redraw individual elements.
- return retainedLayer;
- }
-
- // Otherwise, we replace the commands of the layer to create a new one.
- currentBuilder = LayerBuilder.childLayer(parent: currentBuilder, layer: retainedLayer.emptyClone());
- for (final LayerDrawCommand command in revisedDrawCommands) {
- switch (command) {
- case PictureDrawCommand(offset: final ui.Offset offset, picture: final ScenePicture picture):
- currentBuilder.addPicture(offset, picture, sliceIndex: command.sliceIndex);
- case PlatformViewDrawCommand(viewId: final int viewId, bounds: final ui.Rect bounds):
- currentBuilder.addPlatformView(viewId, bounds: bounds, sliceIndex: command.sliceIndex);
- case RetainedLayerDrawCommand(layer: final PictureEngineLayer layer):
- currentBuilder.mergeLayer(layer);
- }
- }
- final PictureEngineLayer newLayer = currentBuilder.build();
- currentBuilder = currentBuilder.parent!;
- return newLayer;
+ currentBuilder.mergeLayer(retainedLayer as PictureEngineLayer);
}
@override
@@ -337,21 +132,30 @@
ui.ImageFilter filter, {
ui.BlendMode blendMode = ui.BlendMode.srcOver,
ui.BackdropFilterEngineLayer? oldLayer
- }) => pushLayer<BackdropFilterLayer>(BackdropFilterLayer(BackdropFilterOperation(filter, blendMode)));
+ }) => pushLayer<BackdropFilterLayer>(
+ BackdropFilterLayer(),
+ BackdropFilterOperation(filter, blendMode),
+ );
@override
ui.ClipPathEngineLayer pushClipPath(
ui.Path path, {
ui.Clip clipBehavior = ui.Clip.antiAlias,
ui.ClipPathEngineLayer? oldLayer
- }) => pushLayer<ClipPathLayer>(ClipPathLayer(ClipPathOperation(path as ScenePath, clipBehavior)));
+ }) => pushLayer<ClipPathLayer>(
+ ClipPathLayer(),
+ ClipPathOperation(path as ScenePath, clipBehavior),
+ );
@override
ui.ClipRRectEngineLayer pushClipRRect(
ui.RRect rrect, {
required ui.Clip clipBehavior,
ui.ClipRRectEngineLayer? oldLayer
- }) => pushLayer<ClipRRectLayer>(ClipRRectLayer(ClipRRectOperation(rrect, clipBehavior)));
+ }) => pushLayer<ClipRRectLayer>(
+ ClipRRectLayer(),
+ ClipRRectOperation(rrect, clipBehavior)
+ );
@override
ui.ClipRectEngineLayer pushClipRect(
@@ -359,14 +163,20 @@
ui.Clip clipBehavior = ui.Clip.antiAlias,
ui.ClipRectEngineLayer? oldLayer
}) {
- return pushLayer<ClipRectLayer>(ClipRectLayer(ClipRectOperation(rect, clipBehavior)));
+ return pushLayer<ClipRectLayer>(
+ ClipRectLayer(),
+ ClipRectOperation(rect, clipBehavior)
+ );
}
@override
ui.ColorFilterEngineLayer pushColorFilter(
ui.ColorFilter filter, {
ui.ColorFilterEngineLayer? oldLayer
- }) => pushLayer<ColorFilterLayer>(ColorFilterLayer(ColorFilterOperation(filter)));
+ }) => pushLayer<ColorFilterLayer>(
+ ColorFilterLayer(),
+ ColorFilterOperation(filter),
+ );
@override
ui.ImageFilterEngineLayer pushImageFilter(
@@ -374,7 +184,8 @@
ui.Offset offset = ui.Offset.zero,
ui.ImageFilterEngineLayer? oldLayer
}) => pushLayer<ImageFilterLayer>(
- ImageFilterLayer(ImageFilterOperation(filter as SceneImageFilter, offset)),
+ ImageFilterLayer(),
+ ImageFilterOperation(filter as SceneImageFilter, offset),
);
@override
@@ -382,14 +193,19 @@
double dx,
double dy, {
ui.OffsetEngineLayer? oldLayer
- }) => pushLayer<OffsetLayer>(OffsetLayer(OffsetOperation(dx, dy)));
+ }) => pushLayer<OffsetLayer>(
+ OffsetLayer(),
+ OffsetOperation(dx, dy)
+ );
@override
ui.OpacityEngineLayer pushOpacity(int alpha, {
ui.Offset offset = ui.Offset.zero,
ui.OpacityEngineLayer? oldLayer
- }) => pushLayer<OpacityLayer>(OpacityLayer(OpacityOperation(alpha, offset)));
-
+ }) => pushLayer<OpacityLayer>(
+ OpacityLayer(),
+ OpacityOperation(alpha, offset),
+ );
@override
ui.ShaderMaskEngineLayer pushShaderMask(
ui.Shader shader,
@@ -398,14 +214,18 @@
ui.ShaderMaskEngineLayer? oldLayer,
ui.FilterQuality filterQuality = ui.FilterQuality.low
}) => pushLayer<ShaderMaskLayer>(
- ShaderMaskLayer(ShaderMaskOperation(shader, maskRect, blendMode)),
+ ShaderMaskLayer(),
+ ShaderMaskOperation(shader, maskRect, blendMode)
);
@override
ui.TransformEngineLayer pushTransform(
Float64List matrix4, {
ui.TransformEngineLayer? oldLayer
- }) => pushLayer<TransformLayer>(TransformLayer(TransformOperation(matrix4)));
+ }) => pushLayer<TransformLayer>(
+ TransformLayer(),
+ TransformOperation(matrix4),
+ );
@override
void setProperties(
@@ -440,10 +260,11 @@
currentBuilder.mergeLayer(layer);
}
- T pushLayer<T extends PictureEngineLayer>(T layer) {
+ T pushLayer<T extends PictureEngineLayer>(T layer, LayerOperation operation) {
currentBuilder = LayerBuilder.childLayer(
parent: currentBuilder,
layer: layer,
+ operation: operation
);
return layer;
}
diff --git a/lib/web_ui/lib/src/engine/scene_painting.dart b/lib/web_ui/lib/src/engine/scene_painting.dart
index 4f70633..1ef39d2 100644
--- a/lib/web_ui/lib/src/engine/scene_painting.dart
+++ b/lib/web_ui/lib/src/engine/scene_painting.dart
@@ -4,8 +4,6 @@
import 'package:ui/ui.dart' as ui;
-import 'vector_math.dart';
-
// These are additional APIs that are not part of the `dart:ui` interface that
// are needed internally to properly implement a `SceneBuilder` on top of the
// generic Canvas/Picture api.
@@ -24,10 +22,6 @@
// gives the maximum draw boundary for a picture with the given input bounds after it
// has been processed by the filter.
ui.Rect filterBounds(ui.Rect inputBounds);
-
- // The matrix image filter changes the position of the content, so when positioning
- // platform views and calculating occlusion we need to take its transform into account.
- Matrix4? get transform;
}
abstract class ScenePath implements ui.Path {
diff --git a/lib/web_ui/lib/src/engine/scene_view.dart b/lib/web_ui/lib/src/engine/scene_view.dart
index 909c386..5350420 100644
--- a/lib/web_ui/lib/src/engine/scene_view.dart
+++ b/lib/web_ui/lib/src/engine/scene_view.dart
@@ -95,24 +95,23 @@
flutterView.physicalSize.width,
flutterView.physicalSize.height,
);
- final List<LayerSlice?> slices = scene.rootLayer.slices;
+ final List<LayerSlice> slices = scene.rootLayer.slices;
final List<ScenePicture> picturesToRender = <ScenePicture>[];
final List<ScenePicture> originalPicturesToRender = <ScenePicture>[];
- for (final LayerSlice? slice in slices) {
- if (slice == null) {
- continue;
- }
- final ui.Rect clippedRect = slice.picture.cullRect.intersect(screenBounds);
- if (clippedRect.isEmpty) {
- // This picture is completely offscreen, so don't render it at all
- continue;
- } else if (clippedRect == slice.picture.cullRect) {
- // The picture doesn't need to be clipped, just render the original
- originalPicturesToRender.add(slice.picture);
- picturesToRender.add(slice.picture);
- } else {
- originalPicturesToRender.add(slice.picture);
- picturesToRender.add(pictureRenderer.clipPicture(slice.picture, clippedRect));
+ for (final LayerSlice slice in slices) {
+ if (slice is PictureSlice) {
+ final ui.Rect clippedRect = slice.picture.cullRect.intersect(screenBounds);
+ if (clippedRect.isEmpty) {
+ // This picture is completely offscreen, so don't render it at all
+ continue;
+ } else if (clippedRect == slice.picture.cullRect) {
+ // The picture doesn't need to be clipped, just render the original
+ originalPicturesToRender.add(slice.picture);
+ picturesToRender.add(slice.picture);
+ } else {
+ originalPicturesToRender.add(slice.picture);
+ picturesToRender.add(pictureRenderer.clipPicture(slice.picture, clippedRect));
+ }
}
}
final Map<ScenePicture, DomImageBitmap> renderMap;
@@ -133,55 +132,58 @@
final List<SliceContainer?> reusableContainers = List<SliceContainer?>.from(containers);
final List<SliceContainer> newContainers = <SliceContainer>[];
- for (final LayerSlice? slice in slices) {
- if (slice == null) {
- continue;
- }
- final DomImageBitmap? bitmap = renderMap[slice.picture];
- if (bitmap != null) {
- PictureSliceContainer? container;
- for (int j = 0; j < reusableContainers.length; j++) {
- final SliceContainer? candidate = reusableContainers[j];
- if (candidate is PictureSliceContainer) {
- container = candidate;
- reusableContainers[j] = null;
- break;
+ for (final LayerSlice slice in slices) {
+ switch (slice) {
+ case PictureSlice():
+ final DomImageBitmap? bitmap = renderMap[slice.picture];
+ if (bitmap == null) {
+ // We didn't render this slice because no part of it is visible.
+ continue;
}
- }
-
- final ui.Rect clippedBounds = slice.picture.cullRect.intersect(screenBounds);
- if (container != null) {
- container.bounds = clippedBounds;
- } else {
- container = PictureSliceContainer(clippedBounds);
- }
- container.updateContents();
- container.renderBitmap(bitmap);
- newContainers.add(container);
- }
-
- for (final PlatformView view in slice.platformViews) {
- // TODO(harryterkelsen): Inject the FlutterView instance from `renderScene`,
- // instead of using `EnginePlatformDispatcher...implicitView` directly,
- // or make the FlutterView "register" like in canvaskit.
- // Ensure the platform view contents are injected in the DOM.
- EnginePlatformDispatcher.instance.implicitView?.dom.injectPlatformView(view.viewId);
-
- // Attempt to reuse a container for the existing view
- PlatformViewContainer? container;
- for (int j = 0; j < reusableContainers.length; j++) {
- final SliceContainer? candidate = reusableContainers[j];
- if (candidate is PlatformViewContainer && candidate.viewId == view.viewId) {
- container = candidate;
- reusableContainers[j] = null;
- break;
+ PictureSliceContainer? container;
+ for (int j = 0; j < reusableContainers.length; j++) {
+ final SliceContainer? candidate = reusableContainers[j];
+ if (candidate is PictureSliceContainer) {
+ container = candidate;
+ reusableContainers[j] = null;
+ break;
+ }
}
- }
- container ??= PlatformViewContainer(view.viewId);
- container.bounds = view.bounds;
- container.styling = view.styling;
- container.updateContents();
- newContainers.add(container);
+
+ final ui.Rect clippedBounds = slice.picture.cullRect.intersect(screenBounds);
+ if (container != null) {
+ container.bounds = clippedBounds;
+ } else {
+ container = PictureSliceContainer(clippedBounds);
+ }
+ container.updateContents();
+ container.renderBitmap(bitmap);
+ newContainers.add(container);
+
+ case PlatformViewSlice():
+ for (final PlatformView view in slice.views) {
+ // TODO(harryterkelsen): Inject the FlutterView instance from `renderScene`,
+ // instead of using `EnginePlatformDispatcher...implicitView` directly,
+ // or make the FlutterView "register" like in canvaskit.
+ // Ensure the platform view contents are injected in the DOM.
+ EnginePlatformDispatcher.instance.implicitView?.dom.injectPlatformView(view.viewId);
+
+ // Attempt to reuse a container for the existing view
+ PlatformViewContainer? container;
+ for (int j = 0; j < reusableContainers.length; j++) {
+ final SliceContainer? candidate = reusableContainers[j];
+ if (candidate is PlatformViewContainer && candidate.viewId == view.viewId) {
+ container = candidate;
+ reusableContainers[j] = null;
+ break;
+ }
+ }
+ container ??= PlatformViewContainer(view.viewId);
+ container.bounds = view.bounds;
+ container.styling = view.styling;
+ container.updateContents();
+ newContainers.add(container);
+ }
}
}
diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart
index 06b8c47..7c611d1 100644
--- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart
+++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart
@@ -88,9 +88,6 @@
@override
String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, ${tileModeString(tileMode)})';
-
- @override
- Matrix4? get transform => null;
}
class SkwasmDilateFilter extends SkwasmImageFilter {
@@ -108,9 +105,6 @@
@override
String toString() => 'ImageFilter.dilate($radiusX, $radiusY)';
-
- @override
- Matrix4? get transform => null;
}
class SkwasmErodeFilter extends SkwasmImageFilter {
@@ -128,9 +122,6 @@
@override
String toString() => 'ImageFilter.erode($radiusX, $radiusY)';
-
- @override
- Matrix4? get transform => null;
}
class SkwasmMatrixFilter extends SkwasmImageFilter {
@@ -153,9 +144,6 @@
@override
String toString() => 'ImageFilter.matrix($matrix4, $filterQuality)';
-
- @override
- Matrix4? get transform => Matrix4.fromFloat32List(toMatrix32(matrix4));
}
class SkwasmColorImageFilter extends SkwasmImageFilter {
@@ -174,9 +162,6 @@
@override
String toString() => filter.toString();
-
- @override
- Matrix4? get transform => null;
}
class SkwasmComposedImageFilter extends SkwasmImageFilter {
@@ -198,16 +183,6 @@
@override
String toString() => 'ImageFilter.compose($outer, $inner)';
-
- @override
- Matrix4? get transform {
- final outerTransform = outer.transform;
- final innerTransform = inner.transform;
- if (outerTransform != null && innerTransform != null) {
- return outerTransform.multiplied(innerTransform);
- }
- return outerTransform ?? innerTransform;
- }
}
typedef ColorFilterHandleBorrow = void Function(ColorFilterHandle handle);
diff --git a/lib/web_ui/test/engine/scene_builder_test.dart b/lib/web_ui/test/engine/scene_builder_test.dart
index a94a651..d8826e8 100644
--- a/lib/web_ui/test/engine/scene_builder_test.dart
+++ b/lib/web_ui/test/engine/scene_builder_test.dart
@@ -16,7 +16,7 @@
void testMain() {
setUpAll(() {
- LayerSliceBuilder.debugRecorderFactory = () {
+ LayerBuilder.debugRecorderFactory = (ui.Rect rect) {
final StubSceneCanvas canvas = StubSceneCanvas();
final StubPictureRecorder recorder = StubPictureRecorder(canvas);
return (recorder, canvas);
@@ -24,7 +24,7 @@
});
tearDownAll(() {
- LayerSliceBuilder.debugRecorderFactory = null;
+ LayerBuilder.debugRecorderFactory = null;
});
group('EngineSceneBuilder', () {
@@ -35,23 +35,23 @@
sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect));
final EngineScene scene = sceneBuilder.build() as EngineScene;
- final List<LayerSlice?> slices = scene.rootLayer.slices;
+ final List<LayerSlice> slices = scene.rootLayer.slices;
expect(slices.length, 1);
- expect(slices[0], layerSlice(withPictureRect: pictureRect));
+ expect(slices[0], pictureSliceWithRect(pictureRect));
});
test('two pictures', () {
final EngineSceneBuilder sceneBuilder = EngineSceneBuilder();
const ui.Rect pictureRect1 = ui.Rect.fromLTRB(100, 100, 200, 200);
- const ui.Rect pictureRect2 = ui.Rect.fromLTRB(300, 300, 400, 400);
+ const ui.Rect pictureRect2 = ui.Rect.fromLTRB(300, 400, 400, 400);
sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect1));
sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2));
final EngineScene scene = sceneBuilder.build() as EngineScene;
- final List<LayerSlice?> slices = scene.rootLayer.slices;
+ final List<LayerSlice> slices = scene.rootLayer.slices;
expect(slices.length, 1);
- expect(slices[0], layerSlice(withPictureRect: const ui.Rect.fromLTRB(100, 100, 400, 400)));
+ expect(slices[0], pictureSliceWithRect(const ui.Rect.fromLTRB(100, 100, 400, 400)));
});
test('picture + platform view (overlapping)', () {
@@ -68,11 +68,10 @@
);
final EngineScene scene = sceneBuilder.build() as EngineScene;
- final List<LayerSlice?> slices = scene.rootLayer.slices;
- expect(slices.length, 1);
- expect(slices[0], layerSlice(
- withPictureRect: pictureRect,
- withPlatformViews: <PlatformView>[
+ final List<LayerSlice> slices = scene.rootLayer.slices;
+ expect(slices.length, 2);
+ expect(slices[0], pictureSliceWithRect(pictureRect));
+ expect(slices[1], platformViewSliceWithViews(<PlatformView>[
PlatformView(1, platformViewRect, const PlatformViewStyling())
]));
});
@@ -91,12 +90,12 @@
sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect));
final EngineScene scene = sceneBuilder.build() as EngineScene;
- final List<LayerSlice?> slices = scene.rootLayer.slices;
+ final List<LayerSlice> slices = scene.rootLayer.slices;
expect(slices.length, 2);
- expect(slices[0], layerSlice(withPlatformViews: <PlatformView>[
+ expect(slices[0], platformViewSliceWithViews(<PlatformView>[
PlatformView(1, platformViewRect, const PlatformViewStyling())
]));
- expect(slices[1], layerSlice(withPictureRect: pictureRect));
+ expect(slices[1], pictureSliceWithRect(pictureRect));
});
test('platform view sandwich (overlapping)', () {
@@ -115,14 +114,13 @@
sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2));
final EngineScene scene = sceneBuilder.build() as EngineScene;
- final List<LayerSlice?> slices = scene.rootLayer.slices;
- expect(slices.length, 2);
- expect(slices[0], layerSlice(
- withPictureRect: pictureRect1,
- withPlatformViews: <PlatformView>[
+ final List<LayerSlice> slices = scene.rootLayer.slices;
+ expect(slices.length, 3);
+ expect(slices[0], pictureSliceWithRect(pictureRect1));
+ expect(slices[1], platformViewSliceWithViews(<PlatformView>[
PlatformView(1, platformViewRect, const PlatformViewStyling())
]));
- expect(slices[1], layerSlice(withPictureRect: pictureRect2));
+ expect(slices[2], pictureSliceWithRect(pictureRect2));
});
test('platform view sandwich (non-overlapping)', () {
@@ -141,15 +139,14 @@
sceneBuilder.addPicture(ui.Offset.zero, StubPicture(pictureRect2));
final EngineScene scene = sceneBuilder.build() as EngineScene;
- final List<LayerSlice?> slices = scene.rootLayer.slices;
+ final List<LayerSlice> slices = scene.rootLayer.slices;
// The top picture does not overlap with the platform view, so it should
// be grouped into the slice below it to reduce the number of canvases we
// need.
- expect(slices.length, 1);
- expect(slices[0], layerSlice(
- withPictureRect: const ui.Rect.fromLTRB(50, 50, 200, 200),
- withPlatformViews: <PlatformView>[
+ expect(slices.length, 2);
+ expect(slices[0], pictureSliceWithRect(const ui.Rect.fromLTRB(50, 50, 200, 200)));
+ expect(slices[1], platformViewSliceWithViews(<PlatformView>[
PlatformView(1, platformViewRect, const PlatformViewStyling())
]));
});
@@ -172,99 +169,34 @@
sceneBuilder.addPicture(ui.Offset.zero, StubPicture(const ui.Rect.fromLTRB(0, 0, 100, 100)));
final EngineScene scene = sceneBuilder.build() as EngineScene;
- final List<LayerSlice?> slices = scene.rootLayer.slices;
- expect(slices.length, 2);
- expect(slices[0], layerSlice(
- withPictureRect: pictureRect1,
- withPlatformViews: <PlatformView>[
+ final List<LayerSlice> slices = scene.rootLayer.slices;
+ expect(slices.length, 3);
+ expect(slices[0], pictureSliceWithRect(pictureRect1));
+ expect(slices[1], platformViewSliceWithViews(<PlatformView>[
PlatformView(1, platformViewRect, const PlatformViewStyling(position: PlatformViewPosition.offset(ui.Offset(150, 150))))
]));
- expect(slices[1], layerSlice(withPictureRect: const ui.Rect.fromLTRB(200, 200, 300, 300)));
- });
-
- test('grid view test', () {
- // This test case covers a grid of elements, where each element is a platform
- // view that has flutter content underneath it and on top of it.
- // See a detailed explanation of this use-case in the following flutter issue:
- // https://github.com/flutter/flutter/issues/149863
- final EngineSceneBuilder sceneBuilder = EngineSceneBuilder();
-
- const double padding = 10;
- const double tileSize = 50;
- final List<PlatformView> expectedPlatformViews = <PlatformView>[];
- for (int x = 0; x < 10; x++) {
- for (int y = 0; y < 10; y++) {
- final ui.Offset offset = ui.Offset(
- padding + (tileSize + padding) * x,
- padding + (tileSize + padding) * y,
- );
- sceneBuilder.pushOffset(offset.dx, offset.dy);
- sceneBuilder.addPicture(
- ui.Offset.zero,
- StubPicture(const ui.Rect.fromLTWH(0, 0, tileSize, tileSize))
- );
- sceneBuilder.addPlatformView(
- 1,
- offset: const ui.Offset(5, 5),
- width: tileSize - 10,
- height: tileSize - 10,
- );
- sceneBuilder.addPicture(
- const ui.Offset(10, 10),
- StubPicture(const ui.Rect.fromLTWH(0, 0, tileSize - 20, tileSize - 20)),
- );
- sceneBuilder.pop();
- expectedPlatformViews.add(PlatformView(
- 1,
- const ui.Rect.fromLTRB(5.0, 5.0, tileSize - 5.0, tileSize - 5.0),
- PlatformViewStyling(position: PlatformViewPosition.offset(offset))
- ));
- }
- }
-
- final EngineScene scene = sceneBuilder.build() as EngineScene;
- final List<LayerSlice?> slices = scene.rootLayer.slices;
-
- // It is important that the optimizations of the scene builder result in
- // there only being two scene slices.
- expect(slices.length, 2);
- expect(slices[0], layerSlice(
- withPictureRect: const ui.Rect.fromLTRB(
- padding,
- padding,
- 10 * (padding + tileSize),
- 10 * (padding + tileSize)
- ),
- withPlatformViews: expectedPlatformViews,
- ));
- expect(slices[1], layerSlice(withPictureRect: const ui.Rect.fromLTRB(
- padding + 10,
- padding + 10,
- 10 * (padding + tileSize) - 10,
- 10 * (padding + tileSize) - 10,
- )));
+ expect(slices[2], pictureSliceWithRect(const ui.Rect.fromLTRB(200, 200, 300, 300)));
});
});
}
-LayerSliceMatcher layerSlice({
- ui.Rect withPictureRect = ui.Rect.zero,
- List<PlatformView> withPlatformViews = const <PlatformView>[],
-}) => LayerSliceMatcher(withPictureRect, withPlatformViews);
-class LayerSliceMatcher extends Matcher {
- LayerSliceMatcher(this.expectedPictureRect, this.expectedPlatformViews);
+PictureSliceMatcher pictureSliceWithRect(ui.Rect rect) => PictureSliceMatcher(rect);
+PlatformViewSliceMatcher platformViewSliceWithViews(List<PlatformView> views)
+ => PlatformViewSliceMatcher(views);
- final ui.Rect expectedPictureRect;
- final List<PlatformView> expectedPlatformViews;
+class PictureSliceMatcher extends Matcher {
+ PictureSliceMatcher(this.expectedRect);
+
+ final ui.Rect expectedRect;
@override
Description describe(Description description) {
- return description.add('<picture slice with cullRect: $expectedPictureRect and platform views: $expectedPlatformViews>');
+ return description.add('<picture slice with cullRect: $expectedRect>');
}
@override
bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
- if (item is! LayerSlice) {
+ if (item is! PictureSlice) {
return false;
}
final ScenePicture picture = item.picture;
@@ -272,28 +204,50 @@
return false;
}
- if (picture.cullRect != expectedPictureRect) {
+ if (picture.cullRect != expectedRect) {
return false;
}
- if (item.platformViews.length != expectedPlatformViews.length) {
+ return true;
+ }
+}
+
+class PlatformViewSliceMatcher extends Matcher {
+ PlatformViewSliceMatcher(this.expectedPlatformViews);
+
+ final List<PlatformView> expectedPlatformViews;
+
+ @override
+ Description describe(Description description) {
+ return description.add('<platform view slice with platform views: $expectedPlatformViews>');
+ }
+
+ @override
+ bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
+ if (item is! PlatformViewSlice) {
return false;
}
- for (int i = 0; i < item.platformViews.length; i++) {
+ if (item.views.length != expectedPlatformViews.length) {
+ return false;
+ }
+
+ for (int i = 0; i < item.views.length; i++) {
final PlatformView expectedView = expectedPlatformViews[i];
- final PlatformView actualView = item.platformViews[i];
+ final PlatformView actualView = item.views[i];
if (expectedView.viewId != actualView.viewId) {
+ print('viewID mismatch');
return false;
}
if (expectedView.bounds != actualView.bounds) {
+ print('bounds mismatch');
return false;
}
if (expectedView.styling != actualView.styling) {
+ print('styling mismatch');
return false;
}
}
-
return true;
}
}
diff --git a/lib/web_ui/test/engine/scene_builder_utils.dart b/lib/web_ui/test/engine/scene_builder_utils.dart
index ec014e7..033177c 100644
--- a/lib/web_ui/test/engine/scene_builder_utils.dart
+++ b/lib/web_ui/test/engine/scene_builder_utils.dart
@@ -36,12 +36,8 @@
class StubCompositePicture extends StubPicture {
StubCompositePicture(this.children) : super(
children.fold(null, (ui.Rect? previousValue, StubPicture child) {
- final ui.Rect childRect = child.cullRect;
- if (childRect.isEmpty) {
- return previousValue;
- }
return previousValue?.expandToInclude(child.cullRect) ?? child.cullRect;
- }) ?? ui.Rect.zero
+ })!
);
final List<StubPicture> children;
diff --git a/lib/web_ui/test/engine/scene_view_test.dart b/lib/web_ui/test/engine/scene_view_test.dart
index 58ff09d..80093bb 100644
--- a/lib/web_ui/test/engine/scene_view_test.dart
+++ b/lib/web_ui/test/engine/scene_view_test.dart
@@ -172,7 +172,7 @@
120,
));
final EngineRootLayer rootLayer = EngineRootLayer();
- rootLayer.slices.add(LayerSlice(picture, <PlatformView>[]));
+ rootLayer.slices.add(PictureSlice(picture));
final EngineScene scene = EngineScene(rootLayer);
await sceneView.renderScene(scene, null);
@@ -205,7 +205,7 @@
const ui.Rect.fromLTWH(50, 80, 100, 120),
const PlatformViewStyling());
final EngineRootLayer rootLayer = EngineRootLayer();
- rootLayer.slices.add(LayerSlice(StubPicture(ui.Rect.zero), <PlatformView>[platformView]));
+ rootLayer.slices.add(PlatformViewSlice(<PlatformView>[platformView], null));
final EngineScene scene = EngineScene(rootLayer);
await sceneView.renderScene(scene, null);
@@ -246,7 +246,7 @@
));
pictures.add(picture);
final EngineRootLayer rootLayer = EngineRootLayer();
- rootLayer.slices.add(LayerSlice(picture, <PlatformView>[]));
+ rootLayer.slices.add(PictureSlice(picture));
final EngineScene scene = EngineScene(rootLayer);
renderFutures.add(sceneView.renderScene(scene, null));
}
@@ -267,7 +267,7 @@
));
final EngineRootLayer rootLayer = EngineRootLayer();
- rootLayer.slices.add(LayerSlice(picture, <PlatformView>[]));
+ rootLayer.slices.add(PictureSlice(picture));
final EngineScene scene = EngineScene(rootLayer);
await sceneView.renderScene(scene, null);
diff --git a/lib/web_ui/test/ui/scene_builder_test.dart b/lib/web_ui/test/ui/scene_builder_test.dart
index 428437d..8199b1f 100644
--- a/lib/web_ui/test/ui/scene_builder_test.dart
+++ b/lib/web_ui/test/ui/scene_builder_test.dart
@@ -223,7 +223,7 @@
region: region);
});
- test('blur image filter layer', () async {
+ test('image filter layer', () async {
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
sceneBuilder.pushImageFilter(ui.ImageFilter.blur(
sigmaX: 5.0,
@@ -239,23 +239,6 @@
await matchGoldenFile('scene_builder_image_filter.png', region: region);
});
- test('matrix image filter layer', () async {
- final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
- sceneBuilder.pushOffset(50.0, 50.0);
-
- final Matrix4 matrix = Matrix4.rotationZ(math.pi / 18);
- final ui.ImageFilter matrixFilter = ui.ImageFilter.matrix(toMatrix64(matrix.storage));
- sceneBuilder.pushImageFilter(matrixFilter);
- sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) {
- canvas.drawRect(
- region,
- ui.Paint()..color = const ui.Color(0xFF00FF00)
- );
- }));
- await renderScene(sceneBuilder.build());
- await matchGoldenFile('scene_builder_matrix_image_filter.png', region: region);
- });
-
// Regression test for https://github.com/flutter/flutter/issues/154303
test('image filter layer with offset', () async {
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();