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
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
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