Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[skwasm] Clip pictures if they go beyond the bounds of the window. #50887

Merged
merged 3 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
41 changes: 36 additions & 5 deletions lib/web_ui/lib/src/engine/scene_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ typedef RenderResult = ({
// composite pictures into the canvases in the DOM tree it builds.
abstract class PictureRenderer {
FutureOr<RenderResult> renderPictures(List<ScenePicture> picture);
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip);
}

class _SceneRender {
Expand Down Expand Up @@ -86,20 +87,49 @@ class EngineSceneView {
}
}

ScenePicture _clipPictureIfNeeded(ScenePicture picture, ui.Rect clip) {
final ui.Rect pictureRect = picture.cullRect;
if (pictureRect.left >= clip.left &&
pictureRect.top >= clip.top &&
pictureRect.right <= clip.right &&
pictureRect.bottom <= clip.bottom) {
// The picture is already within the clip bounds.
return picture;
}

return pictureRenderer.clipPicture(picture, clip);
}

ui.Rect? _getScreenBounds() {
final DomScreen? screen = domWindow.screen;
if (screen == null) {
return null;
}
return ui.Rect.fromLTWH(0, 0, screen.width, screen.height);
}

Future<void> _renderScene(EngineScene scene, FrameTimingRecorder? recorder) async {
final ui.Rect? screenBounds = _getScreenBounds();
if (screenBounds == null) {
// The browser isn't displaying the document. Skip rendering.
return;
}
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 is PictureSlice) {
picturesToRender.add(slice.picture);
if (slice is PictureSlice && !slice.picture.cullRect.isEmpty) {
originalPicturesToRender.add(slice.picture);
final ScenePicture clippedPicture = _clipPictureIfNeeded(slice.picture, screenBounds);
picturesToRender.add(clippedPicture);
}
}
final Map<ScenePicture, DomImageBitmap> renderMap;
if (picturesToRender.isNotEmpty) {
final RenderResult renderResult = await pictureRenderer.renderPictures(picturesToRender);
renderMap = <ScenePicture, DomImageBitmap>{
for (int i = 0; i < picturesToRender.length; i++)
picturesToRender[i]: renderResult.imageBitmaps[i],
originalPicturesToRender[i]: renderResult.imageBitmaps[i],
};
recorder?.recordRasterStart(renderResult.rasterStartMicros);
recorder?.recordRasterFinish(renderResult.rasterEndMicros);
Expand All @@ -125,10 +155,11 @@ class EngineSceneView {
}
}

final ui.Rect clippedBounds = slice.picture.cullRect.intersect(screenBounds);
if (container != null) {
container.bounds = slice.picture.cullRect;
container.bounds = clippedBounds;
} else {
container = PictureSliceContainer(slice.picture.cullRect);
container = PictureSliceContainer(clippedBounds);
}
container.updateContents();
container.renderBitmap(renderMap[slice.picture]!);
Expand Down
10 changes: 10 additions & 0 deletions lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -482,4 +482,14 @@ class SkwasmPictureRenderer implements PictureRenderer {
@override
FutureOr<RenderResult> renderPictures(List<ScenePicture> pictures) =>
surface.renderPictures(pictures.cast<SkwasmPicture>());

@override
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip) {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder, clip);
canvas.clipRect(clip);
canvas.drawPicture(picture);

return recorder.endRecording() as ScenePicture;
}
}
24 changes: 24 additions & 0 deletions lib/web_ui/test/engine/scene_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ class StubPictureRenderer implements PictureRenderer {
);
}

@override
ScenePicture clipPicture(ScenePicture picture, ui.Rect clip) {
clipRequests[picture] = clip;
return picture;
}

List<ScenePicture> renderedPictures = <ScenePicture>[];
Map<ScenePicture, ui.Rect> clipRequests = <ScenePicture, ui.Rect>{};
}

void testMain() {
Expand Down Expand Up @@ -149,4 +156,21 @@ void testMain() {
expect(stubPictureRenderer.renderedPictures.first, pictures.first);
expect(stubPictureRenderer.renderedPictures.last, pictures.last);
});

test('SceneView clips pictures that are outside the window screen', () async {
final StubPicture picture = StubPicture(const ui.Rect.fromLTWH(
-50,
-50,
100,
120,
));

final EngineRootLayer rootLayer = EngineRootLayer();
rootLayer.slices.add(PictureSlice(picture));
final EngineScene scene = EngineScene(rootLayer);
await sceneView.renderScene(scene, null);

expect(stubPictureRenderer.renderedPictures.length, 1);
expect(stubPictureRenderer.clipRequests.containsKey(picture), true);
});
}