mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[canvaskit] Improve how overlays are optimized (flutter/engine#54547)
Enhances the overlay optimization by pushing new pictures to the earliest RenderCanvas they can go into. This improvement is made especially clear in the new test case I added to `embedded_views_test.dart`, with the previous algorithm we would have used an overlay for each platform view even though all of the pictures could go into the base canvas with no issue. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
parent
9c6ec923c9
commit
e0529ddd81
@ -156,55 +156,115 @@ ui.Rect computePlatformViewBounds(EmbeddedViewParams params) {
|
||||
/// [platformViews].
|
||||
///
|
||||
/// [paramsForViews] is required to compute the bounds of the platform views.
|
||||
// TODO(harryterkelsen): Extend this to work for any sequence of platform views
|
||||
// and pictures, https://github.com/flutter/flutter/issues/149863.
|
||||
Rendering createOptimizedRendering(
|
||||
List<CkPicture> pictures,
|
||||
List<int> platformViews,
|
||||
Map<int, EmbeddedViewParams> paramsForViews,
|
||||
) {
|
||||
final Map<int, ui.Rect> cachedComputedRects = <int, ui.Rect>{};
|
||||
assert(pictures.length == platformViews.length + 1);
|
||||
|
||||
final Rendering result = Rendering();
|
||||
|
||||
// The first render canvas is required due to the pseudo-platform view "V_0"
|
||||
// which is defined as a platform view that comes before all Flutter drawing
|
||||
// commands and intersects with everything.
|
||||
RenderingRenderCanvas currentRenderCanvas = RenderingRenderCanvas();
|
||||
|
||||
// This line essentially unwinds the first iteration of the following loop.
|
||||
// Since "V_0" intersects with all subsequent pictures, then the first picture
|
||||
// it intersects with is "P_0", so we create a new render canvas and add "P_0"
|
||||
// to it.
|
||||
// The first picture is added to the rendering in a new render canvas.
|
||||
RenderingRenderCanvas tentativeCanvas = RenderingRenderCanvas();
|
||||
if (!pictures[0].cullRect.isEmpty) {
|
||||
currentRenderCanvas.add(pictures[0]);
|
||||
tentativeCanvas.add(pictures[0]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < platformViews.length; i++) {
|
||||
final RenderingPlatformView platformView =
|
||||
RenderingPlatformView(platformViews[i]);
|
||||
if (PlatformViewManager.instance.isVisible(platformViews[i])) {
|
||||
final ui.Rect platformViewBounds =
|
||||
final ui.Rect platformViewBounds = cachedComputedRects[platformViews[i]] =
|
||||
computePlatformViewBounds(paramsForViews[platformViews[i]]!);
|
||||
|
||||
if (debugOverlayOptimizationBounds) {
|
||||
platformView.debugComputedBounds = platformViewBounds;
|
||||
}
|
||||
bool intersectsWithCurrentPictures = false;
|
||||
for (final CkPicture picture in currentRenderCanvas.pictures) {
|
||||
if (picture.cullRect.overlaps(platformViewBounds)) {
|
||||
intersectsWithCurrentPictures = true;
|
||||
|
||||
// If the platform view intersects with any pictures in the tentative canvas
|
||||
// then add the tentative canvas to the rendering.
|
||||
for (final CkPicture picture in tentativeCanvas.pictures) {
|
||||
if (!picture.cullRect.intersect(platformViewBounds).isEmpty) {
|
||||
result.add(tentativeCanvas);
|
||||
tentativeCanvas = RenderingRenderCanvas();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (intersectsWithCurrentPictures) {
|
||||
result.add(currentRenderCanvas);
|
||||
currentRenderCanvas = RenderingRenderCanvas();
|
||||
}
|
||||
}
|
||||
result.add(platformView);
|
||||
if (!pictures[i + 1].cullRect.isEmpty) {
|
||||
currentRenderCanvas.add(pictures[i + 1]);
|
||||
|
||||
if (pictures[i + 1].cullRect.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the first render canvas which comes after the last entity (picture
|
||||
// or platform view) that the next picture intersects with, and add the
|
||||
// picture to that render canvas, or create a new render canvas.
|
||||
|
||||
// First check if the picture intersects with any pictures in the tentative
|
||||
// canvas, as this will be the last canvas in the rendering when it is
|
||||
// eventually added.
|
||||
bool addedToTentativeCanvas = false;
|
||||
for (final CkPicture picture in tentativeCanvas.pictures) {
|
||||
if (!picture.cullRect.intersect(pictures[i + 1].cullRect).isEmpty) {
|
||||
tentativeCanvas.add(pictures[i + 1]);
|
||||
addedToTentativeCanvas = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (addedToTentativeCanvas) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RenderingRenderCanvas? lastCanvasSeen;
|
||||
bool addedPictureToRendering = false;
|
||||
for (final RenderingEntity entity in result.entities.reversed) {
|
||||
if (entity is RenderingPlatformView) {
|
||||
if (PlatformViewManager.instance.isVisible(entity.viewId)) {
|
||||
final ui.Rect platformViewBounds =
|
||||
cachedComputedRects[entity.viewId]!;
|
||||
if (!platformViewBounds.intersect(pictures[i + 1].cullRect).isEmpty) {
|
||||
// The next picture intersects with a platform view already in the
|
||||
// result. Add this picture to the first render canvas which comes
|
||||
// after this platform view or create one if none exists.
|
||||
if (lastCanvasSeen != null) {
|
||||
lastCanvasSeen.add(pictures[i + 1]);
|
||||
} else {
|
||||
tentativeCanvas.add(pictures[i + 1]);
|
||||
}
|
||||
addedPictureToRendering = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (entity is RenderingRenderCanvas) {
|
||||
lastCanvasSeen = entity;
|
||||
// Check if we intersect with any pictures in this render canvas.
|
||||
for (final CkPicture picture in entity.pictures) {
|
||||
if (!picture.cullRect.intersect(pictures[i + 1].cullRect).isEmpty) {
|
||||
lastCanvasSeen.add(pictures[i + 1]);
|
||||
addedPictureToRendering = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!addedPictureToRendering) {
|
||||
if (lastCanvasSeen != null) {
|
||||
// Add it to the last canvas seen in the rendering, if any.
|
||||
lastCanvasSeen.add(pictures[i + 1]);
|
||||
} else {
|
||||
tentativeCanvas.add(pictures[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentRenderCanvas.pictures.isNotEmpty) {
|
||||
result.add(currentRenderCanvas);
|
||||
|
||||
if (tentativeCanvas.pictures.isNotEmpty) {
|
||||
result.add(tentativeCanvas);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1120,9 +1120,135 @@ void testMain() {
|
||||
_platformView,
|
||||
_overlay,
|
||||
]);
|
||||
|
||||
// Scene 4: Same as scene 1 but with a placeholder rectangle painted
|
||||
// under each platform view. This is closer to how the real Flutter
|
||||
// framework would render a grid of platform views. Interestingly, in this
|
||||
// case every drawing can go in a base canvas.
|
||||
final LayerSceneBuilder sb4 = LayerSceneBuilder();
|
||||
sb4.pushOffset(0, 0);
|
||||
sb4.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(0, 0, 300, 300)));
|
||||
sb4.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(10, 10, 50, 50)));
|
||||
sb4.addPlatformView(0,
|
||||
offset: const ui.Offset(10, 10), width: 50, height: 50);
|
||||
sb4.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(70, 10, 50, 50)));
|
||||
sb4.addPlatformView(1,
|
||||
offset: const ui.Offset(70, 10), width: 50, height: 50);
|
||||
sb4.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(130, 10, 50, 50)));
|
||||
sb4.addPlatformView(2,
|
||||
offset: const ui.Offset(130, 10), width: 50, height: 50);
|
||||
final LayerScene scene4 = sb4.build();
|
||||
await renderScene(scene4);
|
||||
_expectSceneMatches(<_EmbeddedViewMarker>[
|
||||
_overlay,
|
||||
_platformView,
|
||||
_platformView,
|
||||
_platformView,
|
||||
]);
|
||||
|
||||
// Scene 5: A combination of scene 1 and scene 4, where a subtitle is
|
||||
// painted over each platform view and a placeholder is painted under each
|
||||
// one. Unfortunately, we need an overlay for each platform view in this
|
||||
// case.
|
||||
final LayerSceneBuilder sb5 = LayerSceneBuilder();
|
||||
sb5.pushOffset(0, 0);
|
||||
sb5.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(0, 0, 300, 300)));
|
||||
sb5.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(10, 10, 50, 50)));
|
||||
sb5.addPlatformView(0,
|
||||
offset: const ui.Offset(10, 10), width: 50, height: 50);
|
||||
sb5.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(12, 12, 10, 10)));
|
||||
sb5.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(70, 10, 50, 50)));
|
||||
sb5.addPlatformView(1,
|
||||
offset: const ui.Offset(70, 10), width: 50, height: 50);
|
||||
sb5.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(72, 12, 10, 10)));
|
||||
sb5.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(130, 10, 50, 50)));
|
||||
sb5.addPlatformView(2,
|
||||
offset: const ui.Offset(130, 10), width: 50, height: 50);
|
||||
sb5.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(132, 12, 10, 10)));
|
||||
final LayerScene scene5 = sb5.build();
|
||||
await renderScene(scene5);
|
||||
_expectSceneMatches(<_EmbeddedViewMarker>[
|
||||
_overlay,
|
||||
_platformView,
|
||||
_overlay,
|
||||
_platformView,
|
||||
_overlay,
|
||||
_platformView,
|
||||
_overlay,
|
||||
]);
|
||||
});
|
||||
|
||||
test('sinks platform view under the canvas if it does not overlap with the picture',
|
||||
test(
|
||||
'correctly places pictures in case where next '
|
||||
'picture intersects multiple elements', () async {
|
||||
ui_web.platformViewRegistry.registerViewFactory(
|
||||
'test-view',
|
||||
(int viewId) => createDomHTMLDivElement()..className = 'platform-view',
|
||||
);
|
||||
ui_web.platformViewRegistry.registerViewFactory(
|
||||
'invisible-view',
|
||||
(int viewId) =>
|
||||
createDomHTMLDivElement()..className = 'invisible-platform-view',
|
||||
isVisible: false,
|
||||
);
|
||||
|
||||
CkPicture rectPicture(ui.Rect rect) {
|
||||
return paintPicture(rect, (CkCanvas canvas) {
|
||||
canvas.drawRect(
|
||||
rect, CkPaint()..color = const ui.Color.fromARGB(255, 255, 0, 0));
|
||||
});
|
||||
}
|
||||
|
||||
await createPlatformView(0, 'test-view');
|
||||
await createPlatformView(1, 'invisible-view');
|
||||
|
||||
expect(PlatformViewManager.instance.isVisible(0), isTrue);
|
||||
expect(PlatformViewManager.instance.isVisible(1), isFalse);
|
||||
|
||||
final LayerSceneBuilder sb = LayerSceneBuilder();
|
||||
sb.pushOffset(0, 0);
|
||||
sb.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(0, 0, 100, 100)));
|
||||
sb.addPlatformView(0,
|
||||
offset: const ui.Offset(10, 10), width: 50, height: 50);
|
||||
sb.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(0, 0, 100, 100)));
|
||||
sb.addPlatformView(1,
|
||||
offset: const ui.Offset(10, 10), width: 50, height: 50);
|
||||
sb.addPicture(
|
||||
ui.Offset.zero, rectPicture(const ui.Rect.fromLTWH(0, 0, 5, 5)));
|
||||
final LayerScene scene = sb.build();
|
||||
await renderScene(scene);
|
||||
_expectSceneMatches(<_EmbeddedViewMarker>[
|
||||
_overlay,
|
||||
_platformView,
|
||||
_platformView,
|
||||
_overlay,
|
||||
]);
|
||||
|
||||
final Rendering rendering = CanvasKitRenderer.instance
|
||||
.debugGetRasterizerForView(implicitView)!
|
||||
.viewEmbedder
|
||||
.debugActiveRendering;
|
||||
final List<int> picturesPerCanvas = rendering.canvases
|
||||
.map((RenderingRenderCanvas canvas) => canvas.pictures.length)
|
||||
.toList();
|
||||
expect(picturesPerCanvas, <int>[1, 2]);
|
||||
});
|
||||
|
||||
test(
|
||||
'sinks platform view under the canvas if it does not overlap with the picture',
|
||||
() async {
|
||||
ui_web.platformViewRegistry.registerViewFactory(
|
||||
'test-view',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user