diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index 4ceb777104114..4da9ed1a5b8d1 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -120,6 +120,11 @@ extension CanvasKitExtension on CanvasKit { int sampleCount, int stencil, ); + external SkSurface? MakeRenderTarget( + SkGrContext grContext, + int width, + int height, + ); external SkSurface MakeSWCanvasSurface(DomCanvasElement canvas); /// Creates an image from decoded pixels represented as a list of bytes. diff --git a/lib/web_ui/lib/src/engine/canvaskit/picture.dart b/lib/web_ui/lib/src/engine/canvaskit/picture.dart index d877fd1124bb9..bd147b59ec7f0 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/picture.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/picture.dart @@ -101,7 +101,29 @@ class CkPicture extends ManagedSkiaObject implements ui.Picture { @override ui.Image toImageSync(int width, int height) { + SurfaceFactory.instance.baseSurface.ensureSurface(); + if (SurfaceFactory.instance.baseSurface.usingSoftwareBackend) { + return toImageSyncSoftware(width, height); + } + return toImageSyncGPU(width, height); + } + + ui.Image toImageSyncGPU(int width, int height) { + assert(debugCheckNotDisposed('Cannot convert picture to image.')); + + final CkSurface ckSurface = SurfaceFactory.instance.baseSurface + .createRenderTargetSurface(ui.Size(width.toDouble(), height.toDouble())); + final CkCanvas ckCanvas = ckSurface.getCanvas(); + ckCanvas.clear(const ui.Color(0x00000000)); + ckCanvas.drawPicture(this); + final SkImage skImage = ckSurface.surface.makeImageSnapshot(); + ckSurface.dispose(); + return CkImage(skImage); + } + + ui.Image toImageSyncSoftware(int width, int height) { assert(debugCheckNotDisposed('Cannot convert picture to image.')); + final Surface surface = SurfaceFactory.instance.pictureToImageSurface; final CkSurface ckSurface = surface.createOrUpdateSurface(ui.Size(width.toDouble(), height.toDouble())); diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 37d02e8dac7dd..1d24687d118ef 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -141,6 +141,41 @@ class Surface { ui.Size? _currentSurfaceSize; double _currentDevicePixelRatio = -1; + /// This is only valid after the first frame or if [ensureSurface] has been + /// called + bool get usingSoftwareBackend => _glContext == null || + _grContext == null || webGLVersion == -1 || configuration.canvasKitForceCpuOnly; + + /// Ensure that the initial surface exists and has a size of at least [size]. + /// + /// If not provided, [size] defaults to 1x1. + /// + /// This also ensures that the gl/grcontext have been populated so + /// that software rendering can be detected. + void ensureSurface([ui.Size size = const ui.Size(1, 1)]) { + // If the GrContext hasn't been setup yet then we need to force initialization + // of the canvas and initial surface. + if (_surface != null) { + return; + } + // TODO(jonahwilliams): this is somewhat wasteful. We should probably + // eagerly setup this surface instead of delaying until the first frame? + // Or at least cache the estimated window size. + createOrUpdateSurface(size); + } + + /// This method is not supported if software rendering is used. + CkSurface createRenderTargetSurface(ui.Size size) { + assert(!usingSoftwareBackend); + + final SkSurface skSurface = canvasKit.MakeRenderTarget( + _grContext!, + size.width.ceil(), + size.height.ceil(), + )!; + return CkSurface(skSurface, _glContext); + } + /// Creates a and SkSurface for the given [size]. CkSurface createOrUpdateSurface(ui.Size size) { if (size.isEmpty) { diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart b/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart index c0a1b4f3c5119..08e863074360a 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart @@ -46,13 +46,13 @@ class SurfaceFactory { /// all painting commands. final Surface baseSurface = Surface(); - /// A surface used specifically for `Picture.toImage` calls, which can be - /// reused in order to avoid creating too many WebGL contexts. - late final Surface pictureToImageSurface = Surface(); - /// The maximum number of surfaces which can be live at once. final int maximumSurfaces; + /// A surface used specifically for `Picture.toImage` when software rendering + /// is supported. + late final Surface pictureToImageSurface = Surface(); + /// The maximum number of assignable overlays. /// /// This is just `maximumSurfaces - 1` (the maximum number of surfaces minus diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index a0c00347a51a5..22ba3ca6d2c5e 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -1807,4 +1807,23 @@ void _paragraphTests() { expect(skSurface, isNotNull); }, skip: isFirefox); // Intended: Headless firefox has no webgl support https://github.com/flutter/flutter/issues/109265 + + test('MakeRenderTarget test', () { + final DomCanvasElement canvas = createDomCanvasElement( + width: 100, + height: 100, + ); + + final int glContext = canvasKit.GetWebGLContext( + canvas, + SkWebGLContextOptions( + antialias: 0, + majorVersion: webGLVersion.toDouble(), + ), + ).toInt(); + final SkGrContext grContext = canvasKit.MakeGrContext(glContext.toDouble()); + final SkSurface? surface = canvasKit.MakeRenderTarget(grContext, 1, 1); + + expect(surface, isNotNull); + }, skip: isFirefox); // Intended: Headless firefox has no webgl support https://github.com/flutter/flutter/issues/109265 } diff --git a/lib/web_ui/test/canvaskit/surface_factory_test.dart b/lib/web_ui/test/canvaskit/surface_factory_test.dart index d9da96d693ffe..05db21472386c 100644 --- a/lib/web_ui/test/canvaskit/surface_factory_test.dart +++ b/lib/web_ui/test/canvaskit/surface_factory_test.dart @@ -26,10 +26,6 @@ void testMain() { expect(SurfaceFactory(2).maximumSurfaces, 2); }); - test('has a Surface dedicated to Picture.toImage', () { - expect(SurfaceFactory(1).pictureToImageSurface, isNotNull); - }); - test('getSurface', () { final SurfaceFactory factory = SurfaceFactory(3); expect(factory.baseSurface, isNotNull);