Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Prev Previous commit
Next Next commit
++
  • Loading branch information
jonahwilliams committed Jan 24, 2023
commit 96b96050ed1b951b94fe4f5cf42cab691a4c3cd4
38 changes: 38 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/picture.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data';

import 'package:ui/ui.dart' as ui;

import '../../engine.dart';
import '../configuration.dart';
import '../profiler.dart';
import '../util.dart';
import 'canvas.dart';
Expand Down Expand Up @@ -99,7 +103,16 @@ 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();
Expand All @@ -110,6 +123,31 @@ class CkPicture extends ManagedSkiaObject<SkPicture> implements ui.Picture {
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()));
final CkCanvas ckCanvas = ckSurface.getCanvas();
ckCanvas.clear(const ui.Color(0x00000000));
ckCanvas.drawPicture(this);
final SkImage skImage = ckSurface.surface.makeImageSnapshot();
final SkImageInfo imageInfo = SkImageInfo(
alphaType: canvasKit.AlphaType.Premul,
colorType: canvasKit.ColorType.RGBA_8888,
colorSpace: SkColorSpaceSRGB,
width: width.toDouble(),
height: height.toDouble(),
);
final Uint8List pixels = skImage.readPixels(0, 0, imageInfo);
final SkImage? rasterImage = canvasKit.MakeImage(imageInfo, pixels, (4 * width).toDouble());
if (rasterImage == null) {
throw StateError('Unable to convert image pixels into SkImage.');
}
return CkImage(rasterImage);
}

@override
bool get isResurrectionExpensive => true;

Expand Down
40 changes: 22 additions & 18 deletions lib/web_ui/lib/src/engine/canvaskit/surface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,30 +141,34 @@ class Surface {
ui.Size? _currentSurfaceSize;
double _currentDevicePixelRatio = -1;

CkSurface createRenderTargetSurface(ui.Size size) {
/// 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't invert configuration.canvasKitForceCpuOnly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, good catch. Fixed


/// Ensure that the initial surface, gl/grcontext have been populated so
/// that software rendering can be detected.
void ensureSurface() {
// If the GrContext hasn't been setup yet then we need to force initialization
// of the canvas and initial surface.
if (_surface == null) {
// 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(const ui.Size(1, 1));
}
// No context means software rendering.
if (_glContext == null ||_grContext == null ||
webGLVersion == -1 || configuration.canvasKitForceCpuOnly) {
return _makeSoftwareCanvasSurface(
htmlCanvas!, 'Failed to initialize WebGL surface');
if (_surface != null) {
return;
}
final SkSurface? skSurface = canvasKit.MakeRenderTarget(
// 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(const ui.Size(1, 1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe ensureSurface can take an optional Size?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

/// 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(),
);
if (skSurface == null) {
return _makeSoftwareCanvasSurface(
htmlCanvas!, 'Failed to initialize WebGL surface');
}
)!;
return CkSurface(skSurface, _glContext);
}

Expand Down
4 changes: 4 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class SurfaceFactory {
/// 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