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

[web] use a render target instead of a new surface for Picture.toImage #38573

Merged
merged 16 commits into from
Jan 25, 2023
Merged
5 changes: 5 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/picture.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,29 @@ class CkPicture extends ManagedSkiaObject<SkPicture> 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()));
Expand Down
35 changes: 35 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/surface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 <canvas> and SkSurface for the given [size].
CkSurface createOrUpdateSurface(ui.Size size) {
if (size.isEmpty) {
Expand Down
8 changes: 4 additions & 4 deletions lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions lib/web_ui/test/canvaskit/canvaskit_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 0 additions & 4 deletions lib/web_ui/test/canvaskit/surface_factory_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down