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

Commit e57b440

Browse files
Reland "[canvaskit] Further improve overlay optimization by splitting pictures" (#55464)
This enhances the overlay optimization by delaying combining pictures to get tighter bounds for the pictures that make up the scene, enabling more sophisticated optimization since we can determine if they intersect with platform views on a per-picture basis. Fixes flutter/flutter#149863 On a Macbook in Chrome in an example app with an infinite scrolling grid of platform views, this brings the ratio of dropped frames from 93% to 55% (roughly 4 fps to 30 fps). This is a reland of #54878 with a fix for scenes with pictures and shader masks that are eventually entirely clipped out. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent bf09d34 commit e57b440

File tree

10 files changed

+1184
-656
lines changed

10 files changed

+1184
-656
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43578,6 +43578,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.da
4357843578
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart + ../../../flutter/LICENSE
4357943579
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart + ../../../flutter/LICENSE
4358043580
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart + ../../../flutter/LICENSE
43581+
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart + ../../../flutter/LICENSE
4358143582
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart + ../../../flutter/LICENSE
4358243583
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart + ../../../flutter/LICENSE
4358343584
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart + ../../../flutter/LICENSE
@@ -46447,6 +46448,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart
4644746448
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart
4644846449
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart
4644946450
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart
46451+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart
4645046452
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart
4645146453
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/multi_surface_rasterizer.dart
4645246454
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart

lib/web_ui/lib/src/engine.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export 'engine/canvaskit/image_web_codecs.dart';
3333
export 'engine/canvaskit/layer.dart';
3434
export 'engine/canvaskit/layer_scene_builder.dart';
3535
export 'engine/canvaskit/layer_tree.dart';
36+
export 'engine/canvaskit/layer_visitor.dart';
3637
export 'engine/canvaskit/mask_filter.dart';
3738
export 'engine/canvaskit/multi_surface_rasterizer.dart';
3839
export 'engine/canvaskit/n_way_canvas.dart';

lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart

Lines changed: 138 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import '../svg.dart';
1414
import '../util.dart';
1515
import '../vector_math.dart';
1616
import 'canvas.dart';
17+
import 'layer.dart';
1718
import 'overlay_scene_optimizer.dart';
1819
import 'painting.dart';
1920
import 'path.dart';
@@ -66,6 +67,9 @@ class HtmlViewEmbedder {
6667
/// Returns the most recent rendering. Only used in tests.
6768
Rendering get debugActiveRendering => _activeRendering;
6869

70+
/// If [debugOverlayOptimizationBounds] is true, this canvas will draw
71+
/// semitransparent rectangles showing the computed bounds of the platform
72+
/// views and pictures in the scene.
6973
DisplayCanvas? debugBoundsCanvas;
7074

7175
/// The size of the frame, in physical pixels.
@@ -75,27 +79,23 @@ class HtmlViewEmbedder {
7579
_frameSize = size;
7680
}
7781

78-
/// Returns a list of canvases which will be overlaid on top of the "base"
79-
/// canvas after a platform view is composited into the scene.
80-
///
81-
/// The engine asks for the overlay canvases immediately before the paint
82-
/// phase, after the preroll phase. In the preroll phase we must be
83-
/// conservative and assume that every platform view which is prerolled is
84-
/// also composited, and therefore requires an overlay canvas. However, not
85-
/// every platform view which is prerolled ends up being composited (it may be
86-
/// clipped out and not actually drawn). This means that we may end up
87-
/// overallocating canvases. This isn't a problem in practice, however, as
88-
/// unused recording canvases are simply deleted at the end of the frame.
89-
Iterable<CkCanvas> getOverlayCanvases() {
90-
return _context.pictureRecordersCreatedDuringPreroll
82+
/// Returns a list of recording canvases which the pictures in the upcoming
83+
/// paint step will be drawn into. These recording canvases are combined into
84+
/// an N-way canvas for the rasterizer to record clip and transform operations
85+
/// during the measure step.
86+
Iterable<CkCanvas> getPictureCanvases() {
87+
return _context.measuringPictureRecorders.values
9188
.map((CkPictureRecorder r) => r.recordingCanvas!);
9289
}
9390

94-
void prerollCompositeEmbeddedView(int viewId, EmbeddedViewParams params) {
95-
final CkPictureRecorder pictureRecorder = CkPictureRecorder();
96-
pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize());
97-
_context.pictureRecordersCreatedDuringPreroll.add(pictureRecorder);
91+
/// Returns a list of canvases for the optimized rendering. These are used in
92+
/// the paint step.
93+
Iterable<CkCanvas> getOptimizedCanvases() {
94+
return _context.optimizedCanvasRecorders!
95+
.map((CkPictureRecorder r) => r.recordingCanvas!);
96+
}
9897

98+
void prerollCompositeEmbeddedView(int viewId, EmbeddedViewParams params) {
9999
// Do nothing if the params didn't change.
100100
if (_currentCompositionParams[viewId] == params) {
101101
// If the view was prerolled but not composited, then it needs to be
@@ -109,30 +109,38 @@ class HtmlViewEmbedder {
109109
_viewsToRecomposite.add(viewId);
110110
}
111111

112+
/// Record that a picture recorder is needed for [picture] to be measured.
113+
void prerollPicture(PictureLayer picture) {
114+
final CkPictureRecorder pictureRecorder = CkPictureRecorder();
115+
pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize());
116+
_context.measuringPictureRecorders[picture] = pictureRecorder;
117+
}
118+
119+
/// Returns the canvas that was created to measure [picture].
120+
CkCanvas getMeasuringCanvasFor(PictureLayer picture) {
121+
return _context.measuringPictureRecorders[picture]!.recordingCanvas!;
122+
}
123+
124+
/// Adds the picture recorder associated with [picture] to the unoptimized
125+
/// scene.
126+
void addPictureToUnoptimizedScene(PictureLayer picture) {
127+
final CkPictureRecorder recorder =
128+
_context.measuringPictureRecorders[picture]!;
129+
_context.sceneElements.add(PictureSceneElement(picture, recorder));
130+
}
131+
112132
/// Prepares to composite [viewId].
113-
///
114-
/// If this returns a [CkCanvas], then that canvas should be the new leaf
115-
/// node. Otherwise, keep the same leaf node.
116-
CkCanvas? compositeEmbeddedView(int viewId) {
133+
void compositeEmbeddedView(int viewId) {
117134
// Ensure platform view with `viewId` is injected into the `rasterizer.view`.
118135
rasterizer.view.dom.injectPlatformView(viewId);
119136

120-
final int overlayIndex = _context.viewCount;
121137
_compositionOrder.add(viewId);
122-
_context.viewCount++;
123-
124-
CkPictureRecorder? recorderToUseForRendering;
125-
if (overlayIndex < _context.pictureRecordersCreatedDuringPreroll.length) {
126-
recorderToUseForRendering =
127-
_context.pictureRecordersCreatedDuringPreroll[overlayIndex];
128-
_context.pictureRecorders.add(recorderToUseForRendering);
129-
}
138+
_context.sceneElements.add(PlatformViewSceneElement(viewId));
130139

131140
if (_viewsToRecomposite.contains(viewId)) {
132141
_compositeWithParams(viewId, _currentCompositionParams[viewId]!);
133142
_viewsToRecomposite.remove(viewId);
134143
}
135-
return recorderToUseForRendering?.recordingCanvas;
136144
}
137145

138146
void _compositeWithParams(int platformViewId, EmbeddedViewParams params) {
@@ -355,14 +363,57 @@ class HtmlViewEmbedder {
355363
sceneHost.append(_svgPathDefs!);
356364
}
357365

358-
Future<void> submitFrame(CkPicture basePicture) async {
359-
final List<CkPicture> pictures = <CkPicture>[basePicture];
360-
for (final CkPictureRecorder recorder in _context.pictureRecorders) {
361-
pictures.add(recorder.endRecording());
362-
}
366+
/// Optimizes the scene to use the fewest possible canvases. This sets up
367+
/// the final paint pass to paint the pictures into the optimized canvases.
368+
void optimizeRendering() {
369+
final Map<CkPicture, PictureLayer> scenePictureToRawPicture =
370+
<CkPicture, PictureLayer>{};
371+
final Iterable<SceneElement> unoptimizedRendering =
372+
_context.sceneElements.map<SceneElement>((SceneElement element) {
373+
if (element is PictureSceneElement) {
374+
final CkPicture scenePicture = element.pictureRecorder.endRecording();
375+
if (scenePicture.cullRect.isEmpty) {
376+
element.picture.isCulled = true;
377+
}
378+
element.scenePicture = scenePicture;
379+
scenePictureToRawPicture[scenePicture] = element.picture;
380+
return element;
381+
} else {
382+
return element;
383+
}
384+
});
363385
Rendering rendering = createOptimizedRendering(
364-
pictures, _compositionOrder, _currentCompositionParams);
386+
unoptimizedRendering, _currentCompositionParams);
365387
rendering = _modifyRenderingForMaxCanvases(rendering);
388+
_context.optimizedRendering = rendering;
389+
// Create new picture recorders for the optimized render canvases and record
390+
// which pictures go in which canvas.
391+
final List<CkPictureRecorder> optimizedCanvasRecorders =
392+
<CkPictureRecorder>[];
393+
final Map<PictureLayer, CkPictureRecorder> pictureToOptimizedCanvasMap =
394+
<PictureLayer, CkPictureRecorder>{};
395+
for (final RenderingRenderCanvas renderCanvas in rendering.canvases) {
396+
final CkPictureRecorder pictureRecorder = CkPictureRecorder();
397+
pictureRecorder.beginRecording(ui.Offset.zero & _frameSize.toSize());
398+
optimizedCanvasRecorders.add(pictureRecorder);
399+
for (final CkPicture picture in renderCanvas.pictures) {
400+
pictureToOptimizedCanvasMap[scenePictureToRawPicture[picture]!] =
401+
pictureRecorder;
402+
}
403+
}
404+
_context.optimizedCanvasRecorders = optimizedCanvasRecorders;
405+
_context.pictureToOptimizedCanvasMap = pictureToOptimizedCanvasMap;
406+
}
407+
408+
/// Returns the canvas that this picture layer should draw into in the
409+
/// optimized scene.
410+
CkCanvas getOptimizedCanvasFor(PictureLayer picture) {
411+
assert(_context.optimizedRendering != null);
412+
return _context.pictureToOptimizedCanvasMap![picture]!.recordingCanvas!;
413+
}
414+
415+
Future<void> submitFrame() async {
416+
final Rendering rendering = _context.optimizedRendering!;
366417
_updateDomForNewRendering(rendering);
367418
if (rendering.equalsForRendering(_activeRendering)) {
368419
// Copy the display canvases to the new rendering.
@@ -375,13 +426,17 @@ class HtmlViewEmbedder {
375426
_activeRendering = rendering;
376427

377428
final List<RenderingRenderCanvas> renderCanvases = rendering.canvases;
429+
int renderCanvasIndex = 0;
378430
for (final RenderingRenderCanvas renderCanvas in renderCanvases) {
431+
final CkPicture renderPicture = _context
432+
.optimizedCanvasRecorders![renderCanvasIndex++]
433+
.endRecording();
379434
await rasterizer.rasterizeToCanvas(
380-
renderCanvas.displayCanvas!, renderCanvas.pictures);
435+
renderCanvas.displayCanvas!, <CkPicture>[renderPicture]);
381436
}
382437

383438
for (final CkPictureRecorder recorder
384-
in _context.pictureRecordersCreatedDuringPreroll) {
439+
in _context.measuringPictureRecorders.values) {
385440
if (recorder.isRecording) {
386441
recorder.endRecording();
387442
}
@@ -393,11 +448,11 @@ class HtmlViewEmbedder {
393448
debugBoundsCanvas ??= rasterizer.displayFactory.getCanvas();
394449
final CkPictureRecorder boundsRecorder = CkPictureRecorder();
395450
final CkCanvas boundsCanvas = boundsRecorder.beginRecording(
396-
ui.Rect.fromLTWH(
397-
0,
398-
0,
399-
_frameSize.width.toDouble(),
400-
_frameSize.height.toDouble(),
451+
ui.Rect.fromLTWH(
452+
0,
453+
0,
454+
_frameSize.width.toDouble(),
455+
_frameSize.height.toDouble(),
401456
),
402457
);
403458
final CkPaint platformViewBoundsPaint = CkPaint()
@@ -903,20 +958,45 @@ class MutatorsStack extends Iterable<Mutator> {
903958
Iterable<Mutator> get reversed => _mutators;
904959
}
905960

906-
/// The state for the current frame.
907-
class EmbedderFrameContext {
908-
/// Picture recorders which were created during the preroll phase.
909-
///
910-
/// These picture recorders will be "claimed" in the paint phase by platform
911-
/// views being composited into the scene.
912-
final List<CkPictureRecorder> pictureRecordersCreatedDuringPreroll =
913-
<CkPictureRecorder>[];
961+
sealed class SceneElement {}
914962

915-
/// Picture recorders which were actually used in the paint phase.
916-
///
917-
/// This is a subset of [_pictureRecordersCreatedDuringPreroll].
918-
final List<CkPictureRecorder> pictureRecorders = <CkPictureRecorder>[];
963+
class PictureSceneElement extends SceneElement {
964+
PictureSceneElement(this.picture, this.pictureRecorder);
965+
966+
final PictureLayer picture;
967+
final CkPictureRecorder pictureRecorder;
968+
969+
/// The picture as it would be painted in the final scene, with clips and
970+
/// transforms applied. This is set by [optimizeRendering].
971+
CkPicture? scenePicture;
972+
}
919973

920-
/// The number of platform views in this frame.
921-
int viewCount = 0;
974+
class PlatformViewSceneElement extends SceneElement {
975+
PlatformViewSceneElement(this.viewId);
976+
977+
final int viewId;
978+
}
979+
980+
/// The state for the current frame.
981+
class EmbedderFrameContext {
982+
/// Picture recorders which were created d the final bounds of the picture in the scene.
983+
final Map<PictureLayer, CkPictureRecorder> measuringPictureRecorders =
984+
<PictureLayer, CkPictureRecorder>{};
985+
986+
/// List of picture recorders and platform view ids in the order they were
987+
/// painted.
988+
final List<SceneElement> sceneElements = <SceneElement>[];
989+
990+
/// The optimized rendering for this frame. This is set by calling
991+
/// [optimizeRendering].
992+
Rendering? optimizedRendering;
993+
994+
/// The picture recorders for the optimized rendering. This is set by calling
995+
/// [optimizeRendering].
996+
List<CkPictureRecorder>? optimizedCanvasRecorders;
997+
998+
/// A map from the original PictureLayer to the picture recorder it should go
999+
/// into in the optimized rendering. This is set by calling
1000+
/// [optimizedRendering].
1001+
Map<PictureLayer, CkPictureRecorder>? pictureToOptimizedCanvasMap;
9221002
}

0 commit comments

Comments
 (0)