[web] Fix canvas leak when dpi changes. Evict from BitmapCanvas cache under… (flutter/engine#16721)

* Fix canvas leak when dpi changes. Evict from BitmapCanvas cache under memory pressure.

* optimized _reduceCanvasMemoryUsage

* Addressed review comments
This commit is contained in:
Ferhat 2020-02-24 11:19:47 -08:00 committed by GitHub
parent b5f53219d9
commit a07014a9ca
2 changed files with 55 additions and 21 deletions

View File

@ -19,6 +19,15 @@ const int _kCanvasCacheSize = 30;
/// Canvases available for reuse, capped at [_kCanvasCacheSize].
final List<BitmapCanvas> _recycledCanvases = <BitmapCanvas>[];
/// Reduces recycled canvas list by 50% to reduce bitmap canvas memory use.
void _reduceCanvasMemoryUsage() {
final int canvasCount = _recycledCanvases.length;
for (int i = 0; i < canvasCount; i++) {
_recycledCanvases[i].dispose();
}
_recycledCanvases.clear();
}
/// A request to repaint a canvas.
///
/// Paint requests are prioritized such that the larger pictures go first. This
@ -42,22 +51,27 @@ class _PaintRequest {
List<_PaintRequest> _paintQueue = <_PaintRequest>[];
void _recycleCanvas(EngineCanvas canvas) {
if (canvas is BitmapCanvas && canvas.isReusable()) {
_recycledCanvases.add(canvas);
if (_recycledCanvases.length > _kCanvasCacheSize) {
final BitmapCanvas removedCanvas = _recycledCanvases.removeAt(0);
removedCanvas.dispose();
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.disposedCount++;
if (canvas is BitmapCanvas) {
if (canvas.isReusable()) {
_recycledCanvases.add(canvas);
if (_recycledCanvases.length > _kCanvasCacheSize) {
final BitmapCanvas removedCanvas = _recycledCanvases.removeAt(0);
removedCanvas.dispose();
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.disposedCount++;
}
}
}
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.inRecycleCount =
_recycledCanvases.length;
if (_debugShowCanvasReuseStats) {
DebugCanvasReuseOverlay.instance.inRecycleCount =
_recycledCanvases.length;
}
} else {
canvas.dispose();
}
}
}
/// Signature of a function that instantiates a [PersistedPicture].
typedef PersistedPictureFactory = PersistedPicture Function(
double dx,
@ -272,7 +286,6 @@ class PersistedStandardPicture extends PersistedPicture {
final ui.Size canvasSize = bounds.size;
BitmapCanvas bestRecycledCanvas;
double lastPixelCount = double.infinity;
for (int i = 0; i < _recycledCanvases.length; i++) {
final BitmapCanvas candidate = _recycledCanvases[i];
if (!candidate.isReusable()) {
@ -286,13 +299,21 @@ class PersistedStandardPicture extends PersistedPicture {
final bool fits = candidate.doesFitBounds(bounds);
final bool isSmaller = candidatePixelCount < lastPixelCount;
if (fits && isSmaller) {
bestRecycledCanvas = candidate;
lastPixelCount = candidatePixelCount;
final bool fitsExactly = candidateSize.width == canvasSize.width &&
candidateSize.height == canvasSize.height;
if (fitsExactly) {
// No need to keep looking any more.
break;
// [isTooSmall] is used to make sure that a small picture doesn't
// reuse and hold onto memory of a large canvas.
final double requestedPixelCount = bounds.width * bounds.height;
final bool isTooSmall = isSmaller &&
requestedPixelCount > 1 &&
(candidatePixelCount / requestedPixelCount) > 4;
if (!isTooSmall) {
bestRecycledCanvas = candidate;
lastPixelCount = candidatePixelCount;
final bool fitsExactly = candidateSize.width == canvasSize.width &&
candidateSize.height == canvasSize.height;
if (fitsExactly) {
// No need to keep looking any more.
break;
}
}
}
}

View File

@ -183,8 +183,6 @@ class RecordingCanvas {
}
void drawColor(ui.Color color, ui.BlendMode blendMode) {
_hasArbitraryPaint = true;
_didDraw = true;
_paintBounds.grow(_paintBounds.maxPaintBounds);
_commands.add(PaintDrawColor(color, blendMode));
}
@ -315,6 +313,21 @@ class RecordingCanvas {
}
void drawPath(ui.Path path, SurfacePaint paint) {
if (paint.shader == null) {
// For Rect/RoundedRect paths use drawRect/drawRRect code paths for
// DomCanvas optimization.
SurfacePath sPath = path;
final ui.Rect rect = sPath.webOnlyPathAsRect;
if (rect != null) {
drawRect(rect, paint);
return;
}
final ui.RRect rrect = sPath.webOnlyPathAsRoundedRect;
if (rrect != null) {
drawRRect(rrect, paint);
return;
}
}
_hasArbitraryPaint = true;
_didDraw = true;
ui.Rect pathBounds = path.getBounds();