Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[web] retain GL/Gr context on window resize #38576

Merged
merged 11 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 36 additions & 30 deletions lib/web_ui/lib/src/engine/canvaskit/surface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -146,53 +145,60 @@ 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;
_grContext?.releaseResourcesAndAbandonContext();
_grContext?.delete();
_grContext = null;

_createNewCanvas(newSize);
_currentCanvasPhysicalSize = newSize;
_createNewCanvas(size);
_currentCanvasPhysicalSize = size;
} else if (window.devicePixelRatio != _currentDevicePixelRatio) {
_updateLogicalHtmlCanvasSize();
}

_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
Expand Down
11 changes: 6 additions & 5 deletions lib/web_ui/test/canvaskit/surface_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,25 @@ 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!;
expect(shrunk, same(original));
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
Expand All @@ -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
Expand Down