Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 86 additions & 32 deletions lib/web_ui/lib/src/engine/layers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class NoopOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'NoopOperation()';
}

class BackdropFilterLayer
Expand Down Expand Up @@ -76,6 +79,9 @@ class BackdropFilterOperation implements LayerOperation {
// no pictures, so we return true here.
@override
bool get shouldDrawIfEmpty => true;

@override
String toString() => 'BackdropFilterOperation(filter: $filter, mode: $mode)';
}

class ClipPathLayer
Expand Down Expand Up @@ -122,6 +128,9 @@ class ClipPathOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'ClipPathOperation(path: $path, clip: $clip)';
}

class ClipRectLayer
Expand Down Expand Up @@ -168,6 +177,9 @@ class ClipRectOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'ClipRectOperation(rect: $rect, clip: $clip)';
}

class ClipRRectLayer
Expand Down Expand Up @@ -214,6 +226,9 @@ class ClipRRectOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'ClipRRectOperation(rrect: $rrect, clip: $clip)';
}

class ColorFilterLayer
Expand Down Expand Up @@ -250,6 +265,9 @@ class ColorFilterOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'ColorFilterOperation(filter: $filter)';
}

class ImageFilterLayer
Expand Down Expand Up @@ -311,6 +329,9 @@ class ImageFilterOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'ImageFilterOperation(filter: $filter)';
}

class OffsetLayer
Expand Down Expand Up @@ -351,6 +372,9 @@ class OffsetOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'OffsetOperation(dx: $dx, dy: $dy)';
}

class OpacityLayer
Expand Down Expand Up @@ -401,6 +425,9 @@ class OpacityOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'OpacityOperation(offset: $offset, alpha: $alpha)';
}

class TransformLayer
Expand Down Expand Up @@ -443,6 +470,9 @@ class TransformOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'TransformOperation(matrix: $matrix)';
}

class ShaderMaskLayer
Expand Down Expand Up @@ -493,6 +523,9 @@ class ShaderMaskOperation implements LayerOperation {

@override
bool get shouldDrawIfEmpty => false;

@override
String toString() => 'ShaderMaskOperation(shader: $shader, maskRect: $maskRect, blendMode: $blendMode)';
}

class PlatformView {
Expand Down Expand Up @@ -543,6 +576,22 @@ mixin PictureEngineLayer implements ui.EngineLayer {
slice?.dispose();
}
}

@override
String toString() {
return 'PictureEngineLayer($operation)';
}

bool get isSimple {
if (slices.length > 1) {
return false;
}
final LayerSlice? singleSlice = slices.firstOrNull;
if (singleSlice == null || singleSlice.platformViews.isEmpty) {
return true;
}
return false;
}
}

abstract class LayerOperation {
Expand Down Expand Up @@ -697,7 +746,7 @@ class PlatformViewStyling {
final PlatformViewClip clip;

ui.Rect mapLocalToGlobal(ui.Rect rect) {
return position.mapLocalToGlobal(rect).intersect(clip.outerRect);
return position.mapLocalToGlobal(rect.intersect(clip.outerRect));
}

static PlatformViewStyling combine(PlatformViewStyling outer, PlatformViewStyling inner) {
Expand Down Expand Up @@ -924,23 +973,42 @@ class PlatformViewPathClip implements PlatformViewClip {
}

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) Function(ui.Rect)? debugRecorderFactory;

static (ui.PictureRecorder, SceneCanvas) defaultRecorderFactory() {
static (ui.PictureRecorder, SceneCanvas) defaultRecorderFactory(ui.Rect rect) {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final SceneCanvas canvas = ui.Canvas(recorder, ui.Rect.largest) as SceneCanvas;
final SceneCanvas canvas = ui.Canvas(recorder, rect) as SceneCanvas;
return (recorder, canvas);
}

final ui.PictureRecorder recorder;
final SceneCanvas canvas;
void addPicture(ui.Offset offset, ScenePicture picture) {
pictures.add((picture, offset));
final ui.Rect pictureRect = picture.cullRect.shift(offset);
cullRect = cullRect?.expandToInclude(pictureRect) ?? pictureRect;
}

LayerSlice buildWithOperation(LayerOperation operation) {
final ui.Rect recorderRect = operation.mapRect(cullRect ?? ui.Rect.zero);
final (recorder, canvas) = debugRecorderFactory != null ? debugRecorderFactory!(recorderRect) : defaultRecorderFactory(recorderRect);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe worth pulling the logic to create a recorder (using either the debug factory or the default factory) into its own method

operation.pre(canvas);
for (final (picture, offset) in pictures) {
if (offset != ui.Offset.zero) {
canvas.save();
canvas.translate(offset.dx, offset.dy);
canvas.drawPicture(picture);
canvas.restore();
} else {
canvas.drawPicture(picture);
}
}
operation.post(canvas);
final ui.Picture picture = recorder.endRecording();
return LayerSlice(picture as ScenePicture, platformViews);
}

final List<(ScenePicture, ui.Offset)> pictures = [];
ui.Rect? cullRect;
final List<PlatformView> platformViews = <PlatformView>[];
}

Expand Down Expand Up @@ -991,7 +1059,6 @@ class LayerBuilder {
return existingSliceBuilder;
}
final LayerSliceBuilder newSliceBuilder = LayerSliceBuilder();
layer.operation.pre(newSliceBuilder.canvas);
sliceBuilders[index] = newSliceBuilder;
return newSliceBuilder;
}
Expand All @@ -1002,16 +1069,8 @@ class LayerBuilder {
required int sliceIndex,
}) {
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);
}
drawCommands.add(PictureDrawCommand(offset, picture as ScenePicture, sliceIndex));
sliceBuilder.addPicture(offset, picture as ScenePicture);
drawCommands.add(PictureDrawCommand(offset, picture, sliceIndex));
}

void addPlatformView(
Expand All @@ -1029,7 +1088,7 @@ class LayerBuilder {
final LayerSlice? slice = layer.slices[i];
if (slice != null) {
final LayerSliceBuilder sliceBuilder = getOrCreateSliceBuilderAtIndex(i);
sliceBuilder.canvas.drawPicture(slice.picture);
sliceBuilder.addPicture(ui.Offset.zero, slice.picture);
sliceBuilder.platformViews.addAll(slice.platformViews.map((PlatformView view) {
return PlatformView(view.viewId, view.bounds, PlatformViewStyling.combine(platformViewStyling, view.styling));
}));
Expand All @@ -1039,14 +1098,9 @@ class LayerBuilder {
}

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();
final List<LayerSlice?> slices = sliceBuilders.map(
(LayerSliceBuilder? builder) => builder?.buildWithOperation(layer.operation)
).toList();
layer.slices = slices;
return layer;
}
Expand Down
54 changes: 39 additions & 15 deletions lib/web_ui/lib/src/engine/scene_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ class EngineSceneBuilder implements ui.SceneBuilder {

final List<SceneSlice> sceneSlices = <SceneSlice>[SceneSlice()];

// This represents the simplest case with no platform views, which is a fast path
// that allows us to avoid work tracking the pictures themselves.
bool _isSimple = true;

@override
void addPerformanceOverlay(int enabledOptions, ui.Rect bounds) {
// We don't plan to implement this on the web.
Expand All @@ -185,7 +189,7 @@ class EngineSceneBuilder implements ui.SceneBuilder {
bool isComplexHint = false,
bool willChangeHint = false
}) {
final int sliceIndex = _placePicture(offset, picture as ScenePicture);
final int sliceIndex = _placePicture(offset, picture as ScenePicture, currentBuilder.globalPlatformViewStyling);
currentBuilder.addPicture(
offset,
picture,
Expand All @@ -200,9 +204,14 @@ class EngineSceneBuilder implements ui.SceneBuilder {
// 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) {
int _placePicture(ui.Offset offset, ScenePicture picture, PlatformViewStyling styling) {
if (_isSimple) {
// This is the fast path where there are no platform views. The picture should
// just be placed on the bottom (and only) slice.
return 0;
}
final ui.Rect cullRect = picture.cullRect.shift(offset);
final ui.Rect mappedCullRect = currentBuilder.globalPlatformViewStyling.mapLocalToGlobal(cullRect);
final ui.Rect mappedCullRect = styling.mapLocalToGlobal(cullRect);
int sliceIndex = sceneSlices.length;
while (sliceIndex > 0) {
final SceneSlice sliceBelow = sceneSlices[sliceIndex - 1];
Expand All @@ -214,6 +223,11 @@ class EngineSceneBuilder implements ui.SceneBuilder {
break;
}
}
if (sliceIndex == 0) {
// Don't bother to populate the lowest occlusion map with pictures, since
// we never hit test against pictures in the bottom slice.
return sliceIndex;
}
if (sliceIndex == sceneSlices.length) {
// Insert a new slice.
sceneSlices.add(SceneSlice());
Expand All @@ -231,7 +245,7 @@ class EngineSceneBuilder implements ui.SceneBuilder {
double height = 0.0
}) {
final ui.Rect platformViewRect = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height);
final int sliceIndex = _placePlatformView(viewId, platformViewRect);
final int sliceIndex = _placePlatformView(viewId, platformViewRect, currentBuilder.globalPlatformViewStyling);
currentBuilder.addPlatformView(
viewId,
bounds: platformViewRect,
Expand All @@ -246,11 +260,13 @@ class EngineSceneBuilder implements ui.SceneBuilder {
// 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);
ui.Rect rect,
PlatformViewStyling styling,
) {
// Once we add a platform view, we actually have to do proper occlusion tracking.
_isSimple = false;

final ui.Rect globalPlatformViewRect = styling.mapLocalToGlobal(rect);
int sliceIndex = sceneSlices.length - 1;
while (sliceIndex > 0) {
final SceneSlice slice = sceneSlices[sliceIndex];
Expand All @@ -260,36 +276,44 @@ class EngineSceneBuilder implements ui.SceneBuilder {
}
sliceIndex--;
}
sliceIndex = 0;
final SceneSlice slice = sceneSlices[sliceIndex];
slice.platformViewOcclusionMap.addRect(globalPlatformViewRect);
print('placed platform view. localRect: $rect globalRect: $globalPlatformViewRect sliceIndex: $sliceIndex');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete

return sliceIndex;
}

@override
void addRetained(ui.EngineLayer retainedLayer) {
final PictureEngineLayer placedEngineLayer = _placeRetainedLayer(retainedLayer as PictureEngineLayer);
final PictureEngineLayer placedEngineLayer = _placeRetainedLayer(retainedLayer as PictureEngineLayer, currentBuilder.globalPlatformViewStyling);
currentBuilder.mergeLayer(placedEngineLayer);
}

PictureEngineLayer _placeRetainedLayer(PictureEngineLayer retainedLayer) {
PictureEngineLayer _placeRetainedLayer(PictureEngineLayer retainedLayer, PlatformViewStyling styling) {
if (_isSimple && retainedLayer.isSimple) {
// There are no platform views, so we don't need to do any occlusion tracking
// and can simply merge the layer.
return retainedLayer;
}
bool needsRebuild = false;
final List<LayerDrawCommand> revisedDrawCommands = [];
final PlatformViewStyling combinedStyling = PlatformViewStyling.combine(styling, retainedLayer.platformViewStyling);
for (final LayerDrawCommand command in retainedLayer.drawCommands) {
switch (command) {
case PictureDrawCommand(offset: final ui.Offset offset, picture: final ScenePicture picture):
final int sliceIndex = _placePicture(offset, picture);
final int sliceIndex = _placePicture(offset, picture, combinedStyling);
if (command.sliceIndex != sliceIndex) {
needsRebuild = true;
}
revisedDrawCommands.add(PictureDrawCommand(offset, picture, sliceIndex));
case PlatformViewDrawCommand(viewId: final int viewId, bounds: final ui.Rect bounds):
final int sliceIndex = _placePlatformView(viewId, bounds);
final int sliceIndex = _placePlatformView(viewId, bounds, combinedStyling);
if (command.sliceIndex != sliceIndex) {
needsRebuild = true;
}
revisedDrawCommands.add(PlatformViewDrawCommand(viewId, bounds, sliceIndex));
case RetainedLayerDrawCommand(layer: final PictureEngineLayer sublayer):
final PictureEngineLayer revisedSublayer = _placeRetainedLayer(sublayer);
final PictureEngineLayer revisedSublayer = _placeRetainedLayer(sublayer, combinedStyling);
if (sublayer != revisedSublayer) {
needsRebuild = true;
}
Expand Down Expand Up @@ -336,7 +360,7 @@ class EngineSceneBuilder implements ui.SceneBuilder {
ui.BackdropFilterEngineLayer pushBackdropFilter(
ui.ImageFilter filter, {
ui.BlendMode blendMode = ui.BlendMode.srcOver,
ui.BackdropFilterEngineLayer? oldLayer
ui.BackdropFilterEngineLayer? oldLayer,
int? backdropId,
}) => pushLayer<BackdropFilterLayer>(BackdropFilterLayer(BackdropFilterOperation(filter, blendMode)));

Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/test/engine/scene_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ void main() {

void testMain() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a regression test for the bug which caused you to have to revert the original PR?

setUpAll(() {
LayerSliceBuilder.debugRecorderFactory = () {
LayerSliceBuilder.debugRecorderFactory = (ui.Rect rect) {
final StubSceneCanvas canvas = StubSceneCanvas();
final StubPictureRecorder recorder = StubPictureRecorder(canvas);
return (recorder, canvas);
Expand Down