Revert "Reland [skwasm] Scene builder optimizations for platform view placement (#55468)" (flutter/engine#55715)

This reverts commit b99e758ee1025d5bab2ecff1d62ff75a17b68996 (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
This commit is contained in:
Jackson Gardner 2024-10-07 17:01:15 -07:00 committed by GitHub
parent 4d14f8631b
commit db449a1603
10 changed files with 391 additions and 802 deletions

View File

@ -135,7 +135,4 @@ class EngineColorFilter implements SceneImageFilter, ui.ColorFilter {
return 'ColorFilter.srgbToLinearGamma()';
}
}
@override
Matrix4? get transform => null;
}

View File

@ -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 @@ class BackdropFilterOperation implements LayerOperation {
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 BackdropFilterOperation implements LayerOperation {
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 @@ class ClipPathOperation implements LayerOperation {
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 @@ class ClipPathOperation implements LayerOperation {
}
@override
void post(SceneCanvas canvas) {
void post(SceneCanvas canvas, ui.Rect contentRect) {
if (clip == ui.Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
@ -126,15 +82,7 @@ class ClipPathOperation implements LayerOperation {
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 @@ class ClipRectOperation implements LayerOperation {
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 @@ class ClipRectOperation implements LayerOperation {
}
@override
void post(SceneCanvas canvas) {
void post(SceneCanvas canvas, ui.Rect contentRect) {
if (clip == ui.Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
@ -172,15 +120,7 @@ class ClipRectOperation implements LayerOperation {
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 @@ class ClipRRectOperation implements LayerOperation {
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 @@ class ClipRRectOperation implements LayerOperation {
}
@override
void post(SceneCanvas canvas) {
void post(SceneCanvas canvas, ui.Rect contentRect) {
if (clip == ui.Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
@ -218,15 +158,7 @@ class ClipRRectOperation implements LayerOperation {
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 @@ class ColorFilterOperation implements LayerOperation {
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 ColorFilterOperation implements LayerOperation {
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 @@ class ImageFilterOperation implements LayerOperation {
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 @@ class ImageFilterOperation implements LayerOperation {
@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 ImageFilterOperation implements LayerOperation {
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 @@ class OffsetOperation implements LayerOperation {
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 OffsetOperation implements LayerOperation {
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 @@ class OpacityOperation implements LayerOperation {
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 OpacityOperation implements LayerOperation {
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 @@ class TransformOperation implements LayerOperation {
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 TransformOperation implements LayerOperation {
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 @@ class ShaderMaskOperation implements LayerOperation {
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 @@ class PlatformView {
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();
}
// 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);
// The picture of native flutter content to be rendered
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 @@ abstract class LayerOperation {
// 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 @@ abstract class LayerOperation {
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 @@ class PlatformViewPosition {
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 @@ class PlatformViewPosition {
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 @@ class PlatformViewStyling {
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 @@ class PlatformViewStyling {
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 @@ class PlatformViewNoClip implements PlatformViewClip {
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 @@ class PlatformViewPathClip implements PlatformViewClip {
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;
}
}

View File

@ -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 @@ class EngineScene implements ui.Scene {
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 @@ class EngineSceneBuilder implements ui.SceneBuilder {
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 @@ class EngineSceneBuilder implements ui.SceneBuilder {
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 @@ class EngineSceneBuilder implements ui.SceneBuilder {
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 @@ class EngineSceneBuilder implements ui.SceneBuilder {
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 @@ class EngineSceneBuilder implements ui.SceneBuilder {
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 @@ class EngineSceneBuilder implements ui.SceneBuilder {
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 @@ class EngineSceneBuilder implements ui.SceneBuilder {
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 @@ class EngineSceneBuilder implements ui.SceneBuilder {
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;
}

View File

@ -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 @@ abstract class SceneImageFilter implements ui.ImageFilter {
// 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 {

View File

@ -95,24 +95,23 @@ class EngineSceneView {
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 @@ class EngineSceneView {
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;
}
}
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);
}
}
container ??= PlatformViewContainer(view.viewId);
container.bounds = view.bounds;
container.styling = view.styling;
container.updateContents();
newContainers.add(container);
}
}

View File

@ -88,9 +88,6 @@ class SkwasmBlurFilter extends SkwasmImageFilter {
@override
String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, ${tileModeString(tileMode)})';
@override
Matrix4? get transform => null;
}
class SkwasmDilateFilter extends SkwasmImageFilter {
@ -108,9 +105,6 @@ class SkwasmDilateFilter extends SkwasmImageFilter {
@override
String toString() => 'ImageFilter.dilate($radiusX, $radiusY)';
@override
Matrix4? get transform => null;
}
class SkwasmErodeFilter extends SkwasmImageFilter {
@ -128,9 +122,6 @@ class SkwasmErodeFilter extends SkwasmImageFilter {
@override
String toString() => 'ImageFilter.erode($radiusX, $radiusY)';
@override
Matrix4? get transform => null;
}
class SkwasmMatrixFilter extends SkwasmImageFilter {
@ -153,9 +144,6 @@ class SkwasmMatrixFilter extends SkwasmImageFilter {
@override
String toString() => 'ImageFilter.matrix($matrix4, $filterQuality)';
@override
Matrix4? get transform => Matrix4.fromFloat32List(toMatrix32(matrix4));
}
class SkwasmColorImageFilter extends SkwasmImageFilter {
@ -174,9 +162,6 @@ class SkwasmColorImageFilter extends SkwasmImageFilter {
@override
String toString() => filter.toString();
@override
Matrix4? get transform => null;
}
class SkwasmComposedImageFilter extends SkwasmImageFilter {
@ -198,16 +183,6 @@ class SkwasmComposedImageFilter extends SkwasmImageFilter {
@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);

View File

@ -16,7 +16,7 @@ void main() {
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 @@ void testMain() {
});
tearDownAll(() {
LayerSliceBuilder.debugRecorderFactory = null;
LayerBuilder.debugRecorderFactory = null;
});
group('EngineSceneBuilder', () {
@ -35,23 +35,23 @@ void testMain() {
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 @@ void testMain() {
);
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 @@ void testMain() {
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 @@ void testMain() {
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 @@ void testMain() {
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 @@ void testMain() {
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 @@ class LayerSliceMatcher extends Matcher {
return false;
}
if (picture.cullRect != expectedPictureRect) {
if (picture.cullRect != expectedRect) {
return false;
}
if (item.platformViews.length != expectedPlatformViews.length) {
return false;
}
for (int i = 0; i < item.platformViews.length; i++) {
final PlatformView expectedView = expectedPlatformViews[i];
final PlatformView actualView = item.platformViews[i];
if (expectedView.viewId != actualView.viewId) {
return false;
}
if (expectedView.bounds != actualView.bounds) {
return false;
}
if (expectedView.styling != actualView.styling) {
return false;
}
}
return true;
}
}
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;
}
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.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;
}
}

View File

@ -36,12 +36,8 @@ class StubPicture implements ScenePicture {
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;

View File

@ -172,7 +172,7 @@ void testMain() {
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 @@ void testMain() {
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 @@ void testMain() {
));
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 @@ void testMain() {
));
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);

View File

@ -223,7 +223,7 @@ Future<void> testMain() async {
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 @@ Future<void> testMain() async {
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();