From 5cc62e9dadeaee9da9c029b6a4a44ef77003b7e5 Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Wed, 9 Dec 2020 12:31:11 -0800 Subject: [PATCH] [canvaskit] reuse canvases when window resizes --- .../lib/src/engine/canvaskit/surface.dart | 18 +++++- lib/web_ui/test/canvaskit/surface_test.dart | 59 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 lib/web_ui/test/canvaskit/surface_test.dart diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index e537f4fcf0f40..f6eb47c7ed753 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -96,19 +96,31 @@ class Surface { throw CanvasKitError('Cannot create surfaces of empty size.'); } - if (size == _currentSize) { + // Check if the window is shrinking in size, and if so, don't allocate a + // new canvas as the previous canvas is big enough to fit everything. + final ui.Size? previousSize = _currentSize; + if (previousSize != null && + size.width <= previousSize.width && + size.height <= previousSize.height) { // The existing surface is still reusable. return; } - _currentSize = size; + _currentSize = _currentSize == null + // First frame. Allocate a canvas of the exact size as the window. The + // window is frequently never resized, particularly on mobile, so using + // the exact size is most optimal. + ? size + // The window is growing. Overallocate to prevent frequent reallocations. + : size * 1.4; + _surface?.dispose(); _surface = null; htmlElement?.remove(); htmlElement = null; _addedToScene = false; - _surface = _wrapHtmlCanvas(size); + _surface = _wrapHtmlCanvas(_currentSize!); } CkSurface _wrapHtmlCanvas(ui.Size physicalSize) { diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart new file mode 100644 index 0000000000000..6f61bced93530 --- /dev/null +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.12 +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +import 'common.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() { + group('CanvasKit', () { + setUpCanvasKitTest(); + + test('Surface allocates canvases efficiently', () { + final Surface surface = Surface(HtmlViewEmbedder()); + final CkSurface original = surface.acquireRenderSurface(ui.Size(9, 19)); + + // Expect exact requested dimensions. + expect(original.width(), 9); + expect(original.height(), 19); + + // Shrinking reuses the existing surface straight-up. + final CkSurface shrunk = surface.acquireRenderSurface(ui.Size(5, 15)); + expect(shrunk, same(original)); + + // The first increase will allocate a new surface, but will overallocate + // by 40% to accommodate future increases. + final CkSurface firstIncrease = surface.acquireRenderSurface(ui.Size(10, 20)); + expect(firstIncrease, isNot(same(original))); + + // Expect overallocated dimensions + expect(firstIncrease.width(), 14); + expect(firstIncrease.height(), 28); + + // Subsequent increases within 40% reuse the old surface. + final CkSurface secondIncrease = surface.acquireRenderSurface(ui.Size(11, 22)); + expect(secondIncrease, same(firstIncrease)); + + // Increases beyond the 40% limit will cause a new allocation. + final CkSurface huge = surface.acquireRenderSurface(ui.Size(20, 40)); + expect(huge, isNot(same(firstIncrease))); + + // Also over-allocated + expect(huge.width(), 28); + expect(huge.height(), 56); + + // Shrink again. Reuse the last allocated surface. + final CkSurface shrunk2 = surface.acquireRenderSurface(ui.Size(5, 15)); + expect(shrunk2, same(huge)); + }); + }, skip: isIosSafari); +}