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:
parent
4d14f8631b
commit
db449a1603
@ -135,7 +135,4 @@ class EngineColorFilter implements SceneImageFilter, ui.ColorFilter {
|
||||
return 'ColorFilter.srgbToLinearGamma()';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Matrix4? get transform => null;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user