diff --git a/lib/web_ui/lib/src/engine/scene_view.dart b/lib/web_ui/lib/src/engine/scene_view.dart index b137b70f4f909..72a2f36fb44cf 100644 --- a/lib/web_ui/lib/src/engine/scene_view.dart +++ b/lib/web_ui/lib/src/engine/scene_view.dart @@ -20,6 +20,7 @@ typedef RenderResult = ({ // composite pictures into the canvases in the DOM tree it builds. abstract class PictureRenderer { FutureOr renderPictures(List picture); + ScenePicture clipPicture(ScenePicture picture, ui.Rect clip); } class _SceneRender { @@ -86,12 +87,41 @@ 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 _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 slices = scene.rootLayer.slices; final List picturesToRender = []; + final List originalPicturesToRender = []; 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 renderMap; @@ -99,7 +129,7 @@ class EngineSceneView { final RenderResult renderResult = await pictureRenderer.renderPictures(picturesToRender); renderMap = { 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); @@ -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]!); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index a0bfd4780da08..7eb3ef1d5e6ca 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -482,4 +482,14 @@ class SkwasmPictureRenderer implements PictureRenderer { @override FutureOr renderPictures(List pictures) => surface.renderPictures(pictures.cast()); + + @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; + } } diff --git a/lib/web_ui/test/engine/scene_view_test.dart b/lib/web_ui/test/engine/scene_view_test.dart index 93d54b09b226f..f9461162864ca 100644 --- a/lib/web_ui/test/engine/scene_view_test.dart +++ b/lib/web_ui/test/engine/scene_view_test.dart @@ -43,7 +43,14 @@ class StubPictureRenderer implements PictureRenderer { ); } + @override + ScenePicture clipPicture(ScenePicture picture, ui.Rect clip) { + clipRequests[picture] = clip; + return picture; + } + List renderedPictures = []; + Map clipRequests = {}; } void testMain() { @@ -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); + }); }