diff --git a/lib/web_ui/lib/src/engine/surface/picture.dart b/lib/web_ui/lib/src/engine/surface/picture.dart index 2138dd2653327..c95559691be61 100644 --- a/lib/web_ui/lib/src/engine/surface/picture.dart +++ b/lib/web_ui/lib/src/engine/surface/picture.dart @@ -19,6 +19,15 @@ const int _kCanvasCacheSize = 30; /// Canvases available for reuse, capped at [_kCanvasCacheSize]. final List _recycledCanvases = []; +/// 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 (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.disposedCount++; + 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; + } } } } diff --git a/lib/web_ui/lib/src/engine/surface/recording_canvas.dart b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart index 4209efe638e80..92ce62b93e303 100644 --- a/lib/web_ui/lib/src/engine/surface/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart @@ -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();