Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/bitmap_canvas.dart
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not keep extent neutral?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assumption is that the larger the scrollable area the faster the absolute scroll speed. For example, consider scrolling in portrait mode vs landscape mode, or scrolling a full screen vs a 100px-high drop-down list. My method for choosing this strategy was not very scientific though, so I'm open to other ideas.

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
Loading