mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[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:
parent
b5f53219d9
commit
a07014a9ca
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user