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..aa478fa3f3a6a 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,42 +121,71 @@ 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 == 0) { + return _makeSoftwareCanvasSurface(htmlCanvas); + } + + _grContext = + canvasKit.callMethod('MakeGrContext', [glContext]); - final js.JsObject? skSurface = - canvasKit.callMethod('MakeOnScreenGLSurface', [ - _grContext, - size.width, - size.height, - canvasKit['SkColorSpace']['SRGB'], - ]); + if (_grContext == null) { + throw CanvasKitError('Failed to initialize CanvasKit. CanvasKit.MakeGrContext returned null.'); + } + + // Set the cache byte limit for this grContext, if not specified it will use + // CanvasKit's default. + _syncCacheBytes(); + + js.JsObject? skSurface = + canvasKit.callMethod('MakeOnScreenGLSurface', [ + _grContext, + size.width, + size.height, + canvasKit['SkColorSpace']['SRGB'], + ]); + + if (skSurface == null) { + return _makeSoftwareCanvasSurface(htmlCanvas); + } - if (skSurface == null) { - throw CanvasKitError('Could not create a surface.'); + return CkSurface(skSurface!, _grContext, glContext); } + } - htmlElement = htmlCanvas; - return CkSurface(skSurface, _grContext!, glContext); + static bool _didWarnAboutWebGlInitializationFailure = false; + + CkSurface _makeSoftwareCanvasSurface(html.CanvasElement htmlCanvas) { + 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, + ]), + null, + null, + ); } bool _presentSurface() { - canvasKit.callMethod('setCurrentContext', [_surface!.context]); - _surface!.getCanvas().flush(); + if (_surface!.context != null) { + canvasKit.callMethod('setCurrentContext', [_surface!.context]); + } + _surface!.flush(); return true; } } @@ -162,8 +193,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 +205,12 @@ class CkSurface { ); } - int get context => _glContext; + /// Flushes the graphics to be rendered on screen. + void flush() { + _surface.callMethod('flush'); + } + + int? get context => _glContext; int width() => _surface.callMethod('width'); int height() => _surface.callMethod('height'); @@ -184,10 +220,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; }