mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Reland "[skwasm] Clip pictures if they go beyond the bounds of the window." (flutter/engine#51077)
This fixes https://github.com/flutter/flutter/issues/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.
This commit is contained in:
parent
0d3941630d
commit
0424f1f86e
@ -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 {
|
||||
@ -43,15 +44,16 @@ class _SceneRender {
|
||||
|
||||
// This class builds a DOM tree that composites an `EngineScene`.
|
||||
class EngineSceneView {
|
||||
factory EngineSceneView(PictureRenderer pictureRenderer) {
|
||||
factory EngineSceneView(PictureRenderer pictureRenderer, ui.FlutterView flutterView) {
|
||||
final DomElement sceneElement = createDomElement('flt-scene');
|
||||
return EngineSceneView._(pictureRenderer, sceneElement);
|
||||
return EngineSceneView._(pictureRenderer, flutterView, sceneElement);
|
||||
}
|
||||
|
||||
EngineSceneView._(this.pictureRenderer, this.sceneElement);
|
||||
EngineSceneView._(this.pictureRenderer, this.flutterView, this.sceneElement);
|
||||
|
||||
final PictureRenderer pictureRenderer;
|
||||
final DomElement sceneElement;
|
||||
final ui.FlutterView flutterView;
|
||||
|
||||
List<SliceContainer> containers = <SliceContainer>[];
|
||||
|
||||
@ -87,11 +89,29 @@ class EngineSceneView {
|
||||
}
|
||||
|
||||
Future<void> _renderScene(EngineScene scene, FrameTimingRecorder? recorder) async {
|
||||
final ui.Rect screenBounds = ui.Rect.fromLTWH(
|
||||
0,
|
||||
0,
|
||||
flutterView.physicalSize.width,
|
||||
flutterView.physicalSize.height,
|
||||
);
|
||||
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);
|
||||
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;
|
||||
@ -99,7 +119,7 @@ class EngineSceneView {
|
||||
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);
|
||||
@ -115,6 +135,11 @@ class EngineSceneView {
|
||||
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;
|
||||
}
|
||||
PictureSliceContainer? container;
|
||||
for (int j = 0; j < reusableContainers.length; j++) {
|
||||
final SliceContainer? candidate = reusableContainers[j];
|
||||
@ -125,13 +150,14 @@ 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]!);
|
||||
container.renderBitmap(bitmap);
|
||||
newContainers.add(container);
|
||||
|
||||
case PlatformViewSlice():
|
||||
|
||||
@ -413,7 +413,7 @@ class SkwasmRenderer implements Renderer {
|
||||
EngineSceneView _getSceneViewForView(EngineFlutterView view) {
|
||||
// TODO(mdebbar): Support multi-view mode.
|
||||
if (_sceneView == null) {
|
||||
_sceneView = EngineSceneView(SkwasmPictureRenderer(surface));
|
||||
_sceneView = EngineSceneView(SkwasmPictureRenderer(surface), view);
|
||||
final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!;
|
||||
implicitView.dom.setScene(_sceneView!.sceneElement);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,60 @@ 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>{};
|
||||
}
|
||||
|
||||
class StubFlutterView implements ui.FlutterView {
|
||||
@override
|
||||
double get devicePixelRatio => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.Display get display => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
List<ui.DisplayFeature> get displayFeatures => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.GestureSettings get gestureSettings => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.ViewPadding get padding => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.ViewConstraints get physicalConstraints => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.Size get physicalSize => const ui.Size(1000, 1000);
|
||||
|
||||
@override
|
||||
ui.PlatformDispatcher get platformDispatcher => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void render(ui.Scene scene, {ui.Size? size}) {
|
||||
}
|
||||
|
||||
@override
|
||||
ui.ViewPadding get systemGestureInsets => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void updateSemantics(ui.SemanticsUpdate update) {
|
||||
}
|
||||
|
||||
@override
|
||||
int get viewId => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.ViewPadding get viewInsets => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.ViewPadding get viewPadding => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
@ -56,7 +109,7 @@ void testMain() {
|
||||
|
||||
setUp(() {
|
||||
stubPictureRenderer = StubPictureRenderer();
|
||||
sceneView = EngineSceneView(stubPictureRenderer);
|
||||
sceneView = EngineSceneView(stubPictureRenderer, StubFlutterView());
|
||||
});
|
||||
|
||||
test('SceneView places canvas according to device-pixel ratio', () async {
|
||||
@ -149,4 +202,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);
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user