Skip to content

Commit dd87e3f

Browse files
authored
Optimize static content scrolling (flutter#17621)
* store paint command bounds * do not apply commands outside the clip region * better cull rect prediction * enforce RecordingCanvas.endRecording
1 parent 04cfe7c commit dd87e3f

21 files changed

+533
-177
lines changed

lib/web_ui/lib/src/engine/bitmap_canvas.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ class BitmapCanvas extends EngineCanvas {
545545
/// Paints the [picture] into this canvas.
546546
void drawPicture(ui.Picture picture) {
547547
final EnginePicture enginePicture = picture;
548-
enginePicture.recordingCanvas.apply(this);
548+
enginePicture.recordingCanvas.apply(this, bounds);
549549
}
550550

551551
/// Draws vertices on a gl context.

lib/web_ui/lib/src/engine/picture.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class EnginePictureRecorder implements ui.PictureRecorder {
3232
return null;
3333
}
3434
_isRecording = false;
35+
_canvas.endRecording();
3536
return EnginePicture(_canvas, cullRect);
3637
}
3738
}
@@ -46,8 +47,9 @@ class EnginePicture implements ui.Picture {
4647

4748
@override
4849
Future<ui.Image> toImage(int width, int height) async {
49-
final BitmapCanvas canvas = BitmapCanvas(ui.Rect.fromLTRB(0, 0, width.toDouble(), height.toDouble()));
50-
recordingCanvas.apply(canvas);
50+
final ui.Rect imageRect = ui.Rect.fromLTRB(0, 0, width.toDouble(), height.toDouble());
51+
final BitmapCanvas canvas = BitmapCanvas(imageRect);
52+
recordingCanvas.apply(canvas, imageRect);
5153
final String imageDataUrl = canvas.toDataUrl();
5254
final html.ImageElement imageElement = html.ImageElement()
5355
..src = imageDataUrl

lib/web_ui/lib/src/engine/surface/picture.dart

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class PersistedHoudiniPicture extends PersistedPicture {
145145
_canvas = canvas;
146146
domRenderer.clearDom(rootElement);
147147
rootElement.append(_canvas.rootElement);
148-
picture.recordingCanvas.apply(_canvas);
148+
picture.recordingCanvas.apply(_canvas, _optimalLocalCullRect);
149149
canvas.commit();
150150
}
151151
}
@@ -231,7 +231,7 @@ class PersistedStandardPicture extends PersistedPicture {
231231
_canvas = DomCanvas();
232232
domRenderer.clearDom(rootElement);
233233
rootElement.append(_canvas.rootElement);
234-
picture.recordingCanvas.apply(_canvas);
234+
picture.recordingCanvas.apply(_canvas, _optimalLocalCullRect);
235235
}
236236

237237
void _applyBitmapPaint(EngineCanvas oldCanvas) {
@@ -244,7 +244,7 @@ class PersistedStandardPicture extends PersistedPicture {
244244
oldCanvas.bounds = _optimalLocalCullRect;
245245
_canvas = oldCanvas;
246246
_canvas.clear();
247-
picture.recordingCanvas.apply(_canvas);
247+
picture.recordingCanvas.apply(_canvas, _optimalLocalCullRect);
248248
} else {
249249
// We can't use the old canvas because the size has changed, so we put
250250
// it in a cache for later reuse.
@@ -265,7 +265,7 @@ class PersistedStandardPicture extends PersistedPicture {
265265
domRenderer.clearDom(rootElement);
266266
rootElement.append(_canvas.rootElement);
267267
_canvas.clear();
268-
picture.recordingCanvas.apply(_canvas);
268+
picture.recordingCanvas.apply(_canvas, _optimalLocalCullRect);
269269
},
270270
));
271271
}
@@ -352,7 +352,7 @@ class PersistedStandardPicture extends PersistedPicture {
352352
/// to draw shapes and text.
353353
abstract class PersistedPicture extends PersistedLeafSurface {
354354
PersistedPicture(this.dx, this.dy, this.picture, this.hints)
355-
: localPaintBounds = picture.recordingCanvas.computePaintBounds();
355+
: localPaintBounds = picture.recordingCanvas.pictureBounds;
356356

357357
EngineCanvas _canvas;
358358

@@ -491,7 +491,7 @@ abstract class PersistedPicture extends PersistedLeafSurface {
491491

492492
// The new cull rect contains area not covered by a previous rect. Perhaps
493493
// the clip is growing, moving around the picture, or both. In this case
494-
// a part of the picture may not been painted. We will need to
494+
// a part of the picture may not have been painted. We will need to
495495
// request a new canvas and paint the picture on it. However, this is also
496496
// a strong signal that the clip will continue growing as typically
497497
// Flutter uses animated transitions. So instead of allocating the canvas
@@ -500,32 +500,45 @@ abstract class PersistedPicture extends PersistedLeafSurface {
500500
// will hit the above case where the new cull rect is fully contained
501501
// within the cull rect we compute now.
502502

503-
// If any of the borders moved.
504-
// TODO(yjbanov): consider switching to Mouad's snap-to-10px strategy. It
505-
// might be sufficient, if not more effective.
506-
const double kPredictedGrowthFactor = 3.0;
507-
final double leftwardTrend = kPredictedGrowthFactor *
508-
math.max(oldOptimalLocalCullRect.left - _exactLocalCullRect.left, 0);
509-
final double upwardTrend = kPredictedGrowthFactor *
510-
math.max(oldOptimalLocalCullRect.top - _exactLocalCullRect.top, 0);
511-
final double rightwardTrend = kPredictedGrowthFactor *
512-
math.max(_exactLocalCullRect.right - oldOptimalLocalCullRect.right, 0);
513-
final double bottomwardTrend = kPredictedGrowthFactor *
514-
math.max(
515-
_exactLocalCullRect.bottom - oldOptimalLocalCullRect.bottom, 0);
503+
// Compute the delta, by which each of the side of the clip rect has "moved"
504+
// since the last time we updated the cull rect.
505+
final double leftwardDelta = oldOptimalLocalCullRect.left - _exactLocalCullRect.left;
506+
final double upwardDelta = oldOptimalLocalCullRect.top - _exactLocalCullRect.top;
507+
final double rightwardDelta = _exactLocalCullRect.right - oldOptimalLocalCullRect.right;
508+
final double bottomwardDelta = _exactLocalCullRect.bottom - oldOptimalLocalCullRect.bottom;
516509

510+
// Compute the new optimal rect to paint into.
517511
final ui.Rect newLocalCullRect = ui.Rect.fromLTRB(
518-
oldOptimalLocalCullRect.left - leftwardTrend,
519-
oldOptimalLocalCullRect.top - upwardTrend,
520-
oldOptimalLocalCullRect.right + rightwardTrend,
521-
oldOptimalLocalCullRect.bottom + bottomwardTrend,
512+
_exactLocalCullRect.left - _predictTrend(leftwardDelta, _exactLocalCullRect.width),
513+
_exactLocalCullRect.top - _predictTrend(upwardDelta, _exactLocalCullRect.height),
514+
_exactLocalCullRect.right + _predictTrend(rightwardDelta, _exactLocalCullRect.width),
515+
_exactLocalCullRect.bottom + _predictTrend(bottomwardDelta, _exactLocalCullRect.height),
522516
).intersect(localPaintBounds);
523517

524518
final bool localCullRectChanged = _optimalLocalCullRect != newLocalCullRect;
525519
_optimalLocalCullRect = newLocalCullRect;
526520
return localCullRectChanged;
527521
}
528522

523+
/// Predicts the delta a particular side of a clip rect will move given the
524+
/// [delta] it moved by last, and the respective [extent] (width or height)
525+
/// of the clip.
526+
static double _predictTrend(double delta, double extent) {
527+
if (delta <= 0.0) {
528+
// Shrinking. Give it 10% of the extent in case the trend is reversed.
529+
return extent * 0.1;
530+
} else {
531+
// Growing. Predict 10 more frames of similar deltas. Give it at least
532+
// 50% of the extent (protect from extremely slow growth trend such as
533+
// slow scrolling). Give no more than the full extent (protects from
534+
// fast scrolling that could lead to overallocation).
535+
return math.min(
536+
math.max(extent * 0.5, delta * 10.0),
537+
extent,
538+
);
539+
}
540+
}
541+
529542
/// Number of bitmap pixel painted by this picture.
530543
///
531544
/// If the implementation does not paint onto a bitmap canvas, it should

0 commit comments

Comments
 (0)