From f8cfcab996dcc96ac8a9f541fcfb84226bb5773d Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Thu, 16 Jul 2020 14:22:35 -0700 Subject: [PATCH 1/4] Add CPU-only rendering mode for CanvasKit as a fallback --- .../src/engine/compositor/initialization.dart | 4 + .../lib/src/engine/compositor/surface.dart | 99 ++++++++++++------- 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/lib/web_ui/lib/src/engine/compositor/initialization.dart b/lib/web_ui/lib/src/engine/compositor/initialization.dart index c7edbfa9e519b..4590b156b7978 100644 --- a/lib/web_ui/lib/src/engine/compositor/initialization.dart +++ b/lib/web_ui/lib/src/engine/compositor/initialization.dart @@ -9,6 +9,10 @@ part of engine; const bool experimentalUseSkia = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false); +// If set to true, forces CPU-only rendering (i.e. no WebGL). +const bool canvasKitForceCpuOnly = + bool.fromEnvironment('FLUTTER_WEB_CANVASKIT_FORCE_CPU_ONLY', defaultValue: false); + /// The URL to use when downloading the CanvasKit script and associated wasm. /// /// When CanvasKit pushes a new release to NPM, update this URL to reflect the diff --git a/lib/web_ui/lib/src/engine/compositor/surface.dart b/lib/web_ui/lib/src/engine/compositor/surface.dart index c0d8b217fa6e1..69600a0daa027 100644 --- a/lib/web_ui/lib/src/engine/compositor/surface.dart +++ b/lib/web_ui/lib/src/engine/compositor/surface.dart @@ -66,7 +66,9 @@ class Surface { SurfaceFrame acquireFrame(ui.Size size) { final CkSurface surface = acquireRenderSurface(size); - canvasKit.callMethod('setCurrentContext', [surface.context]); + if (surface.context != null) { + canvasKit.callMethod('setCurrentContext', [surface.context]); + } SubmitCallback submitCallback = (SurfaceFrame surfaceFrame, CkCanvas canvas) { return _presentSurface(); @@ -119,41 +121,64 @@ class Surface { ..position = 'absolute' ..width = '${logicalSize.width.ceil()}px' ..height = '${logicalSize.height.ceil()}px'; - final int glContext = canvasKit.callMethod('GetWebGLContext', [ - htmlCanvas, - // Default to no anti-aliasing. Paint commands can be explicitly - // anti-aliased by setting their `Paint` object's `antialias` property. - js.JsObject.jsify({'antialias': 0}), - ]); - _grContext = - canvasKit.callMethod('MakeGrContext', [glContext]); - - if (_grContext == null) { - throw CanvasKitError('Could not create a graphics context.'); - } - // Set the cache byte limit for this grContext, if not specified it will use - // CanvasKit's default. - _syncCacheBytes(); + htmlElement = htmlCanvas; + if (canvasKitForceCpuOnly) { + return _makeSoftwareCanvasSurface(htmlCanvas); + } else { + // Try WebGL first. + final int? glContext = canvasKit.callMethod('GetWebGLContext', [ + htmlCanvas, + // Default to no anti-aliasing. Paint commands can be explicitly + // anti-aliased by setting their `Paint` object's `antialias` property. + js.JsObject.jsify({'antialias': 0}), + ]); + + if (glContext == null) { + return _makeSoftwareCanvasSurface(htmlCanvas); + } + + _grContext = + canvasKit.callMethod('MakeGrContext', [glContext]); + + if (_grContext == null) { + return _makeSoftwareCanvasSurface(htmlCanvas); + } + + // Set the cache byte limit for this grContext, if not specified it will use + // CanvasKit's default. + _syncCacheBytes(); - final js.JsObject? skSurface = - canvasKit.callMethod('MakeOnScreenGLSurface', [ - _grContext, - size.width, - size.height, - canvasKit['SkColorSpace']['SRGB'], - ]); + js.JsObject? skSurface = + canvasKit.callMethod('MakeOnScreenGLSurface', [ + _grContext, + size.width, + size.height, + canvasKit['SkColorSpace']['SRGB'], + ]); - if (skSurface == null) { - throw CanvasKitError('Could not create a surface.'); + if (skSurface == null) { + return _makeSoftwareCanvasSurface(htmlCanvas); + } + + return CkSurface(skSurface!, _grContext, glContext); } + } - htmlElement = htmlCanvas; - return CkSurface(skSurface, _grContext!, glContext); + CkSurface _makeSoftwareCanvasSurface(html.CanvasElement htmlCanvas) { + return CkSurface( + canvasKit.callMethod('MakeSWCanvasSurface', [ + htmlCanvas, + ]), + null, + null, + ); } bool _presentSurface() { - canvasKit.callMethod('setCurrentContext', [_surface!.context]); + if (_surface!.context != null) { + canvasKit.callMethod('setCurrentContext', [_surface!.context]); + } _surface!.getCanvas().flush(); return true; } @@ -162,8 +187,8 @@ class Surface { /// A Dart wrapper around Skia's CkSurface. class CkSurface { final js.JsObject _surface; - final js.JsObject _grContext; - final int _glContext; + final js.JsObject? _grContext; + final int? _glContext; CkSurface(this._surface, this._grContext, this._glContext); @@ -174,7 +199,7 @@ class CkSurface { ); } - int get context => _glContext; + int? get context => _glContext; int width() => _surface.callMethod('width'); int height() => _surface.callMethod('height'); @@ -184,10 +209,16 @@ class CkSurface { return; } // Only resources from the current context can be disposed. - canvasKit.callMethod('setCurrentContext', [_glContext]); + if (_glContext != null) { + canvasKit.callMethod('setCurrentContext', [_glContext]); + } _surface.callMethod('dispose'); - _grContext.callMethod('releaseResourcesAndAbandonContext'); - _grContext.callMethod('delete'); + + // In CPU-only mode there's no graphics context. + if (_grContext != null) { + _grContext!.callMethod('releaseResourcesAndAbandonContext'); + _grContext!.callMethod('delete'); + } _isDisposed = true; } From c0413d9c85ff1d5232d39eb807f1273340723bd0 Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Thu, 16 Jul 2020 14:44:56 -0700 Subject: [PATCH 2/4] fix glContext init; warn when falling back to CPU --- lib/web_ui/lib/src/engine/compositor/surface.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/compositor/surface.dart b/lib/web_ui/lib/src/engine/compositor/surface.dart index 69600a0daa027..69bbaa01d05d4 100644 --- a/lib/web_ui/lib/src/engine/compositor/surface.dart +++ b/lib/web_ui/lib/src/engine/compositor/surface.dart @@ -127,14 +127,14 @@ class Surface { return _makeSoftwareCanvasSurface(htmlCanvas); } else { // Try WebGL first. - final int? glContext = canvasKit.callMethod('GetWebGLContext', [ + final int glContext = canvasKit.callMethod('GetWebGLContext', [ htmlCanvas, // Default to no anti-aliasing. Paint commands can be explicitly // anti-aliased by setting their `Paint` object's `antialias` property. js.JsObject.jsify({'antialias': 0}), ]); - if (glContext == null) { + if (glContext == 0) { return _makeSoftwareCanvasSurface(htmlCanvas); } @@ -166,6 +166,7 @@ class Surface { } CkSurface _makeSoftwareCanvasSurface(html.CanvasElement htmlCanvas) { + print('WARNING: failed to initialize WebGL. Falling back to CPU-only rendering.'); return CkSurface( canvasKit.callMethod('MakeSWCanvasSurface', [ htmlCanvas, From 2cc135b0632d95bc2e66a5af4cd91a99daac0e2c Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Thu, 16 Jul 2020 16:28:01 -0700 Subject: [PATCH 3/4] canvas.flush() => surface.flush() --- lib/web_ui/lib/src/engine/compositor/surface.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/compositor/surface.dart b/lib/web_ui/lib/src/engine/compositor/surface.dart index 69bbaa01d05d4..c123369baddde 100644 --- a/lib/web_ui/lib/src/engine/compositor/surface.dart +++ b/lib/web_ui/lib/src/engine/compositor/surface.dart @@ -180,7 +180,7 @@ class Surface { if (_surface!.context != null) { canvasKit.callMethod('setCurrentContext', [_surface!.context]); } - _surface!.getCanvas().flush(); + _surface!.flush(); return true; } } @@ -200,6 +200,11 @@ class CkSurface { ); } + /// Flushes the graphics to be rendered on screen. + void flush() { + _surface.callMethod('flush'); + } + int? get context => _glContext; int width() => _surface.callMethod('width'); From eb81968e2eb53cfe679b249f5cf02b472d220108 Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Thu, 16 Jul 2020 17:14:18 -0700 Subject: [PATCH 4/4] address comments --- lib/web_ui/lib/src/engine/compositor/surface.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/compositor/surface.dart b/lib/web_ui/lib/src/engine/compositor/surface.dart index c123369baddde..aa478fa3f3a6a 100644 --- a/lib/web_ui/lib/src/engine/compositor/surface.dart +++ b/lib/web_ui/lib/src/engine/compositor/surface.dart @@ -142,7 +142,7 @@ class Surface { canvasKit.callMethod('MakeGrContext', [glContext]); if (_grContext == null) { - return _makeSoftwareCanvasSurface(htmlCanvas); + throw CanvasKitError('Failed to initialize CanvasKit. CanvasKit.MakeGrContext returned null.'); } // Set the cache byte limit for this grContext, if not specified it will use @@ -165,8 +165,13 @@ class Surface { } } + static bool _didWarnAboutWebGlInitializationFailure = false; + CkSurface _makeSoftwareCanvasSurface(html.CanvasElement htmlCanvas) { - print('WARNING: failed to initialize WebGL. Falling back to CPU-only rendering.'); + if (!_didWarnAboutWebGlInitializationFailure) { + html.window.console.warn('WARNING: failed to initialize WebGL. Falling back to CPU-only rendering.'); + _didWarnAboutWebGlInitializationFailure = true; + } return CkSurface( canvasKit.callMethod('MakeSWCanvasSurface', [ htmlCanvas,