Skip to content

Commit

Permalink
Optimize static content scrolling (flutter#17621)
Browse files Browse the repository at this point in the history
* store paint command bounds
* do not apply commands outside the clip region
* better cull rect prediction
* enforce RecordingCanvas.endRecording
  • Loading branch information
yjbanov authored and goderbauer committed Apr 16, 2020
1 parent 3f3facd commit 218eaa7
Show file tree
Hide file tree
Showing 21 changed files with 533 additions and 177 deletions.
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/bitmap_canvas.dart
Expand Up @@ -545,7 +545,7 @@ class BitmapCanvas extends EngineCanvas {
/// Paints the [picture] into this canvas.
void drawPicture(ui.Picture picture) {
final EnginePicture enginePicture = picture;
enginePicture.recordingCanvas.apply(this);
enginePicture.recordingCanvas.apply(this, bounds);
}

/// Draws vertices on a gl context.
Expand Down
6 changes: 4 additions & 2 deletions lib/web_ui/lib/src/engine/picture.dart
Expand Up @@ -32,6 +32,7 @@ class EnginePictureRecorder implements ui.PictureRecorder {
return null;
}
_isRecording = false;
_canvas.endRecording();
return EnginePicture(_canvas, cullRect);
}
}
Expand All @@ -46,8 +47,9 @@ class EnginePicture implements ui.Picture {

@override
Future<ui.Image> toImage(int width, int height) async {
final BitmapCanvas canvas = BitmapCanvas(ui.Rect.fromLTRB(0, 0, width.toDouble(), height.toDouble()));
recordingCanvas.apply(canvas);
final ui.Rect imageRect = ui.Rect.fromLTRB(0, 0, width.toDouble(), height.toDouble());
final BitmapCanvas canvas = BitmapCanvas(imageRect);
recordingCanvas.apply(canvas, imageRect);
final String imageDataUrl = canvas.toDataUrl();
final html.ImageElement imageElement = html.ImageElement()
..src = imageDataUrl
Expand Down
59 changes: 36 additions & 23 deletions lib/web_ui/lib/src/engine/surface/picture.dart
Expand Up @@ -145,7 +145,7 @@ class PersistedHoudiniPicture extends PersistedPicture {
_canvas = canvas;
domRenderer.clearDom(rootElement);
rootElement.append(_canvas.rootElement);
picture.recordingCanvas.apply(_canvas);
picture.recordingCanvas.apply(_canvas, _optimalLocalCullRect);
canvas.commit();
}
}
Expand Down Expand Up @@ -231,7 +231,7 @@ class PersistedStandardPicture extends PersistedPicture {
_canvas = DomCanvas();
domRenderer.clearDom(rootElement);
rootElement.append(_canvas.rootElement);
picture.recordingCanvas.apply(_canvas);
picture.recordingCanvas.apply(_canvas, _optimalLocalCullRect);
}

void _applyBitmapPaint(EngineCanvas oldCanvas) {
Expand All @@ -244,7 +244,7 @@ class PersistedStandardPicture extends PersistedPicture {
oldCanvas.bounds = _optimalLocalCullRect;
_canvas = oldCanvas;
_canvas.clear();
picture.recordingCanvas.apply(_canvas);
picture.recordingCanvas.apply(_canvas, _optimalLocalCullRect);
} else {
// We can't use the old canvas because the size has changed, so we put
// it in a cache for later reuse.
Expand All @@ -265,7 +265,7 @@ class PersistedStandardPicture extends PersistedPicture {
domRenderer.clearDom(rootElement);
rootElement.append(_canvas.rootElement);
_canvas.clear();
picture.recordingCanvas.apply(_canvas);
picture.recordingCanvas.apply(_canvas, _optimalLocalCullRect);
},
));
}
Expand Down Expand Up @@ -352,7 +352,7 @@ class PersistedStandardPicture extends PersistedPicture {
/// to draw shapes and text.
abstract class PersistedPicture extends PersistedLeafSurface {
PersistedPicture(this.dx, this.dy, this.picture, this.hints)
: localPaintBounds = picture.recordingCanvas.computePaintBounds();
: localPaintBounds = picture.recordingCanvas.pictureBounds;

EngineCanvas _canvas;

Expand Down Expand Up @@ -491,7 +491,7 @@ abstract class PersistedPicture extends PersistedLeafSurface {

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

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

// Compute the new optimal rect to paint into.
final ui.Rect newLocalCullRect = ui.Rect.fromLTRB(
oldOptimalLocalCullRect.left - leftwardTrend,
oldOptimalLocalCullRect.top - upwardTrend,
oldOptimalLocalCullRect.right + rightwardTrend,
oldOptimalLocalCullRect.bottom + bottomwardTrend,
_exactLocalCullRect.left - _predictTrend(leftwardDelta, _exactLocalCullRect.width),
_exactLocalCullRect.top - _predictTrend(upwardDelta, _exactLocalCullRect.height),
_exactLocalCullRect.right + _predictTrend(rightwardDelta, _exactLocalCullRect.width),
_exactLocalCullRect.bottom + _predictTrend(bottomwardDelta, _exactLocalCullRect.height),
).intersect(localPaintBounds);

final bool localCullRectChanged = _optimalLocalCullRect != newLocalCullRect;
_optimalLocalCullRect = newLocalCullRect;
return localCullRectChanged;
}

/// Predicts the delta a particular side of a clip rect will move given the
/// [delta] it moved by last, and the respective [extent] (width or height)
/// of the clip.
static double _predictTrend(double delta, double extent) {
if (delta <= 0.0) {
// Shrinking. Give it 10% of the extent in case the trend is reversed.
return extent * 0.1;
} else {
// Growing. Predict 10 more frames of similar deltas. Give it at least
// 50% of the extent (protect from extremely slow growth trend such as
// slow scrolling). Give no more than the full extent (protects from
// fast scrolling that could lead to overallocation).
return math.min(
math.max(extent * 0.5, delta * 10.0),
extent,
);
}
}

/// Number of bitmap pixel painted by this picture.
///
/// If the implementation does not paint onto a bitmap canvas, it should
Expand Down

0 comments on commit 218eaa7

Please sign in to comment.