From b9a72348218815c6daa98ec24ce22e67937fecb3 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 10 Jan 2023 16:04:23 -0800 Subject: [PATCH] [web] retain GL/Gr context on window resize (#38576) * [web] dont dispose of context on window resize * ++ * ++ * ++ * ++ * ++ * ++ * always re-create surface * ++ * ++ --- .../lib/src/engine/canvaskit/surface.dart | 66 ++++++++++--------- lib/web_ui/test/canvaskit/surface_test.dart | 11 ++-- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index ff9d829f7aea6..cd9b910ee694b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -19,8 +19,7 @@ import 'util.dart'; // Only supported in profile/release mode. Allows Flutter to use MSAA but // removes the ability for disabling AA on Paint objects. -const bool _kUsingMSAA = - bool.fromEnvironment('flutter.canvaskit.msaa'); +const bool _kUsingMSAA = bool.fromEnvironment('flutter.canvaskit.msaa'); typedef SubmitCallback = bool Function(SurfaceFrame, CkCanvas); @@ -146,36 +145,41 @@ class Surface { throw CanvasKitError('Cannot create surfaces of empty size.'); } - // Check if the window is the same size as before, and if so, don't allocate - // a new canvas as the previous canvas is big enough to fit everything. - final ui.Size? previousSurfaceSize = _currentSurfaceSize; - if (!_forceNewContext && - previousSurfaceSize != null && - size.width == previousSurfaceSize.width && - size.height == previousSurfaceSize.height) { - // The existing surface is still reusable. - if (window.devicePixelRatio != _currentDevicePixelRatio) { - _updateLogicalHtmlCanvasSize(); - _translateCanvas(); + if (!_forceNewContext) { + // Check if the window is the same size as before, and if so, don't allocate + // a new canvas as the previous canvas is big enough to fit everything. + final ui.Size? previousSurfaceSize = _currentSurfaceSize; + if (previousSurfaceSize != null && + size.width == previousSurfaceSize.width && + size.height == previousSurfaceSize.height) { + // The existing surface is still reusable. + if (window.devicePixelRatio != _currentDevicePixelRatio) { + _updateLogicalHtmlCanvasSize(); + _translateCanvas(); + } + return _surface!; } - return _surface!; - } - // If the current canvas size is smaller than the requested size then create - // a new, larger, canvas. Then update the GR context so we can create a new - // SkSurface. - final ui.Size? previousCanvasSize = _currentCanvasPhysicalSize; - if (_forceNewContext || - previousCanvasSize == null || - size.width > previousCanvasSize.width || - size.height > previousCanvasSize.height) { + final ui.Size? previousCanvasSize = _currentCanvasPhysicalSize; // Initialize a new, larger, canvas. If the size is growing, then make the // new canvas larger than required to avoid many canvas creations. - final ui.Size newSize = previousCanvasSize == null ? size : size * 1.4; + if (previousCanvasSize != null && + (size.width > previousCanvasSize.width || + size.height > previousCanvasSize.height)) { + final ui.Size newSize = size * 1.4; + _surface?.dispose(); + _surface = null; + htmlCanvas!.width = newSize.width; + htmlCanvas!.height = newSize.height; + _currentCanvasPhysicalSize = newSize; + _pixelWidth = newSize.width.ceil(); + _pixelHeight = newSize.height.ceil(); + _updateLogicalHtmlCanvasSize(); + } + } - // If we have a surface, send a dummy command to its canvas to make its context - // current or else disposing the context could fail below. - _surface?.getCanvas().clear(const ui.Color(0x00000000)); + // Either a new context is being forced or we've never had one. + if (_forceNewContext || _currentCanvasPhysicalSize == null) { _surface?.dispose(); _surface = null; _addedToScene = false; @@ -183,8 +187,8 @@ class Surface { _grContext?.delete(); _grContext = null; - _createNewCanvas(newSize); - _currentCanvasPhysicalSize = newSize; + _createNewCanvas(size); + _currentCanvasPhysicalSize = size; } else if (window.devicePixelRatio != _currentDevicePixelRatio) { _updateLogicalHtmlCanvasSize(); } @@ -192,7 +196,9 @@ class Surface { _currentDevicePixelRatio = window.devicePixelRatio; _currentSurfaceSize = size; _translateCanvas(); - return _surface = _createNewSurface(size); + _surface?.dispose(); + _surface = _createNewSurface(size); + return _surface!; } /// Sets the CSS size of the canvas so that canvas pixels are 1:1 with device diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index f3d7e26ddb044..f593c37e20a29 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -37,7 +37,8 @@ void testMain() { expect(originalSurface.width(), 9); expect(originalSurface.height(), 19); - // Shrinking reuses the existing canvas but translates it so Skia renders into the visible area. + // Shrinking reuses the existing canvas but translates it so + // Skia renders into the visible area. final CkSurface shrunkSurface = surface.acquireFrame(const ui.Size(5, 15)).skiaSurface; final DomCanvasElement shrunk = surface.htmlCanvas!; @@ -45,16 +46,16 @@ void testMain() { expect(shrunk.style.width, '9px'); expect(shrunk.style.height, '19px'); expect(shrunk.style.transform, _isTranslate(0, -4)); - expect(shrunkSurface, isNot(same(original))); + expect(shrunkSurface, isNot(same(originalSurface))); expect(shrunkSurface.width(), 5); expect(shrunkSurface.height(), 15); - // The first increase will allocate a new canvas, but will overallocate + // The first increase will allocate a new surface, but will overallocate // by 40% to accommodate future increases. final CkSurface firstIncreaseSurface = surface.acquireFrame(const ui.Size(10, 20)).skiaSurface; final DomCanvasElement firstIncrease = surface.htmlCanvas!; - expect(firstIncrease, isNot(same(original))); + expect(firstIncrease, same(original)); expect(firstIncreaseSurface, isNot(same(shrunkSurface))); // Expect overallocated dimensions @@ -79,7 +80,7 @@ void testMain() { // Increases beyond the 40% limit will cause a new allocation. final CkSurface hugeSurface = surface.acquireFrame(const ui.Size(20, 40)).skiaSurface; final DomCanvasElement huge = surface.htmlCanvas!; - expect(huge, isNot(same(secondIncrease))); + expect(huge, same(secondIncrease)); expect(hugeSurface, isNot(same(secondIncreaseSurface))); // Also over-allocated