Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit b45a86d

Browse files
Reland "[skwasm] Clip pictures if they go beyond the bounds of the window." (#51077)
This fixes flutter/flutter#143800, where we are attempting to capture an image that is way too large. We only need to render the part of the image that will be visible in the window. This includes some additional fixes for regressions in the original fix.
1 parent dd3383d commit b45a86d

File tree

3 files changed

+116
-10
lines changed

3 files changed

+116
-10
lines changed

lib/web_ui/lib/src/engine/scene_view.dart

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ typedef RenderResult = ({
2020
// composite pictures into the canvases in the DOM tree it builds.
2121
abstract class PictureRenderer {
2222
FutureOr<RenderResult> renderPictures(List<ScenePicture> picture);
23+
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip);
2324
}
2425

2526
class _SceneRender {
@@ -43,15 +44,16 @@ class _SceneRender {
4344

4445
// This class builds a DOM tree that composites an `EngineScene`.
4546
class EngineSceneView {
46-
factory EngineSceneView(PictureRenderer pictureRenderer) {
47+
factory EngineSceneView(PictureRenderer pictureRenderer, ui.FlutterView flutterView) {
4748
final DomElement sceneElement = createDomElement('flt-scene');
48-
return EngineSceneView._(pictureRenderer, sceneElement);
49+
return EngineSceneView._(pictureRenderer, flutterView, sceneElement);
4950
}
5051

51-
EngineSceneView._(this.pictureRenderer, this.sceneElement);
52+
EngineSceneView._(this.pictureRenderer, this.flutterView, this.sceneElement);
5253

5354
final PictureRenderer pictureRenderer;
5455
final DomElement sceneElement;
56+
final ui.FlutterView flutterView;
5557

5658
List<SliceContainer> containers = <SliceContainer>[];
5759

@@ -87,19 +89,37 @@ class EngineSceneView {
8789
}
8890

8991
Future<void> _renderScene(EngineScene scene, FrameTimingRecorder? recorder) async {
92+
final ui.Rect screenBounds = ui.Rect.fromLTWH(
93+
0,
94+
0,
95+
flutterView.physicalSize.width,
96+
flutterView.physicalSize.height,
97+
);
9098
final List<LayerSlice> slices = scene.rootLayer.slices;
9199
final List<ScenePicture> picturesToRender = <ScenePicture>[];
100+
final List<ScenePicture> originalPicturesToRender = <ScenePicture>[];
92101
for (final LayerSlice slice in slices) {
93102
if (slice is PictureSlice) {
94-
picturesToRender.add(slice.picture);
103+
final ui.Rect clippedRect = slice.picture.cullRect.intersect(screenBounds);
104+
if (clippedRect.isEmpty) {
105+
// This picture is completely offscreen, so don't render it at all
106+
continue;
107+
} else if (clippedRect == slice.picture.cullRect) {
108+
// The picture doesn't need to be clipped, just render the original
109+
originalPicturesToRender.add(slice.picture);
110+
picturesToRender.add(slice.picture);
111+
} else {
112+
originalPicturesToRender.add(slice.picture);
113+
picturesToRender.add(pictureRenderer.clipPicture(slice.picture, clippedRect));
114+
}
95115
}
96116
}
97117
final Map<ScenePicture, DomImageBitmap> renderMap;
98118
if (picturesToRender.isNotEmpty) {
99119
final RenderResult renderResult = await pictureRenderer.renderPictures(picturesToRender);
100120
renderMap = <ScenePicture, DomImageBitmap>{
101121
for (int i = 0; i < picturesToRender.length; i++)
102-
picturesToRender[i]: renderResult.imageBitmaps[i],
122+
originalPicturesToRender[i]: renderResult.imageBitmaps[i],
103123
};
104124
recorder?.recordRasterStart(renderResult.rasterStartMicros);
105125
recorder?.recordRasterFinish(renderResult.rasterEndMicros);
@@ -115,6 +135,11 @@ class EngineSceneView {
115135
for (final LayerSlice slice in slices) {
116136
switch (slice) {
117137
case PictureSlice():
138+
final DomImageBitmap? bitmap = renderMap[slice.picture];
139+
if (bitmap == null) {
140+
// We didn't render this slice because no part of it is visible.
141+
continue;
142+
}
118143
PictureSliceContainer? container;
119144
for (int j = 0; j < reusableContainers.length; j++) {
120145
final SliceContainer? candidate = reusableContainers[j];
@@ -125,13 +150,14 @@ class EngineSceneView {
125150
}
126151
}
127152

153+
final ui.Rect clippedBounds = slice.picture.cullRect.intersect(screenBounds);
128154
if (container != null) {
129-
container.bounds = slice.picture.cullRect;
155+
container.bounds = clippedBounds;
130156
} else {
131-
container = PictureSliceContainer(slice.picture.cullRect);
157+
container = PictureSliceContainer(clippedBounds);
132158
}
133159
container.updateContents();
134-
container.renderBitmap(renderMap[slice.picture]!);
160+
container.renderBitmap(bitmap);
135161
newContainers.add(container);
136162

137163
case PlatformViewSlice():

lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ class SkwasmRenderer implements Renderer {
413413
EngineSceneView _getSceneViewForView(EngineFlutterView view) {
414414
// TODO(mdebbar): Support multi-view mode.
415415
if (_sceneView == null) {
416-
_sceneView = EngineSceneView(SkwasmPictureRenderer(surface));
416+
_sceneView = EngineSceneView(SkwasmPictureRenderer(surface), view);
417417
final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!;
418418
implicitView.dom.setScene(_sceneView!.sceneElement);
419419
}
@@ -482,4 +482,14 @@ class SkwasmPictureRenderer implements PictureRenderer {
482482
@override
483483
FutureOr<RenderResult> renderPictures(List<ScenePicture> pictures) =>
484484
surface.renderPictures(pictures.cast<SkwasmPicture>());
485+
486+
@override
487+
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip) {
488+
final ui.PictureRecorder recorder = ui.PictureRecorder();
489+
final ui.Canvas canvas = ui.Canvas(recorder, clip);
490+
canvas.clipRect(clip);
491+
canvas.drawPicture(picture);
492+
493+
return recorder.endRecording() as ScenePicture;
494+
}
485495
}

lib/web_ui/test/engine/scene_view_test.dart

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,60 @@ class StubPictureRenderer implements PictureRenderer {
4343
);
4444
}
4545

46+
@override
47+
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip) {
48+
clipRequests[picture] = clip;
49+
return picture;
50+
}
51+
4652
List<ScenePicture> renderedPictures = <ScenePicture>[];
53+
Map<ScenePicture, ui.Rect> clipRequests = <ScenePicture, ui.Rect>{};
54+
}
55+
56+
class StubFlutterView implements ui.FlutterView {
57+
@override
58+
double get devicePixelRatio => throw UnimplementedError();
59+
60+
@override
61+
ui.Display get display => throw UnimplementedError();
62+
63+
@override
64+
List<ui.DisplayFeature> get displayFeatures => throw UnimplementedError();
65+
66+
@override
67+
ui.GestureSettings get gestureSettings => throw UnimplementedError();
68+
69+
@override
70+
ui.ViewPadding get padding => throw UnimplementedError();
71+
72+
@override
73+
ui.ViewConstraints get physicalConstraints => throw UnimplementedError();
74+
75+
@override
76+
ui.Size get physicalSize => const ui.Size(1000, 1000);
77+
78+
@override
79+
ui.PlatformDispatcher get platformDispatcher => throw UnimplementedError();
80+
81+
@override
82+
void render(ui.Scene scene, {ui.Size? size}) {
83+
}
84+
85+
@override
86+
ui.ViewPadding get systemGestureInsets => throw UnimplementedError();
87+
88+
@override
89+
void updateSemantics(ui.SemanticsUpdate update) {
90+
}
91+
92+
@override
93+
int get viewId => throw UnimplementedError();
94+
95+
@override
96+
ui.ViewPadding get viewInsets => throw UnimplementedError();
97+
98+
@override
99+
ui.ViewPadding get viewPadding => throw UnimplementedError();
47100
}
48101

49102
void testMain() {
@@ -56,7 +109,7 @@ void testMain() {
56109

57110
setUp(() {
58111
stubPictureRenderer = StubPictureRenderer();
59-
sceneView = EngineSceneView(stubPictureRenderer);
112+
sceneView = EngineSceneView(stubPictureRenderer, StubFlutterView());
60113
});
61114

62115
test('SceneView places canvas according to device-pixel ratio', () async {
@@ -149,4 +202,21 @@ void testMain() {
149202
expect(stubPictureRenderer.renderedPictures.first, pictures.first);
150203
expect(stubPictureRenderer.renderedPictures.last, pictures.last);
151204
});
205+
206+
test('SceneView clips pictures that are outside the window screen', () async {
207+
final StubPicture picture = StubPicture(const ui.Rect.fromLTWH(
208+
-50,
209+
-50,
210+
100,
211+
120,
212+
));
213+
214+
final EngineRootLayer rootLayer = EngineRootLayer();
215+
rootLayer.slices.add(PictureSlice(picture));
216+
final EngineScene scene = EngineScene(rootLayer);
217+
await sceneView.renderScene(scene, null);
218+
219+
expect(stubPictureRenderer.renderedPictures.length, 1);
220+
expect(stubPictureRenderer.clipRequests.containsKey(picture), true);
221+
});
152222
}

0 commit comments

Comments
 (0)