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

Commit 6f80649

Browse files
[web] use a render target instead of a new surface for Picture.toImage (#38573)
* [web] use a render target instead of a new surface for Picture.toImage * setup grcontext if missing * ++ * dispose * Add unit test for MakeRenderTarget * ++ * ++ * ++ * ++ * ++ * ++ * ++
1 parent 8efc718 commit 6f80649

File tree

6 files changed

+85
-8
lines changed

6 files changed

+85
-8
lines changed

lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ extension CanvasKitExtension on CanvasKit {
119119
int sampleCount,
120120
int stencil,
121121
);
122+
external SkSurface? MakeRenderTarget(
123+
SkGrContext grContext,
124+
int width,
125+
int height,
126+
);
122127
external SkSurface MakeSWCanvasSurface(DomCanvasElement canvas);
123128

124129
/// Creates an image from decoded pixels represented as a list of bytes.

lib/web_ui/lib/src/engine/canvaskit/picture.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,29 @@ class CkPicture extends ManagedSkiaObject<SkPicture> implements ui.Picture {
101101

102102
@override
103103
ui.Image toImageSync(int width, int height) {
104+
SurfaceFactory.instance.baseSurface.ensureSurface();
105+
if (SurfaceFactory.instance.baseSurface.usingSoftwareBackend) {
106+
return toImageSyncSoftware(width, height);
107+
}
108+
return toImageSyncGPU(width, height);
109+
}
110+
111+
ui.Image toImageSyncGPU(int width, int height) {
112+
assert(debugCheckNotDisposed('Cannot convert picture to image.'));
113+
114+
final CkSurface ckSurface = SurfaceFactory.instance.baseSurface
115+
.createRenderTargetSurface(ui.Size(width.toDouble(), height.toDouble()));
116+
final CkCanvas ckCanvas = ckSurface.getCanvas();
117+
ckCanvas.clear(const ui.Color(0x00000000));
118+
ckCanvas.drawPicture(this);
119+
final SkImage skImage = ckSurface.surface.makeImageSnapshot();
120+
ckSurface.dispose();
121+
return CkImage(skImage);
122+
}
123+
124+
ui.Image toImageSyncSoftware(int width, int height) {
104125
assert(debugCheckNotDisposed('Cannot convert picture to image.'));
126+
105127
final Surface surface = SurfaceFactory.instance.pictureToImageSurface;
106128
final CkSurface ckSurface =
107129
surface.createOrUpdateSurface(ui.Size(width.toDouble(), height.toDouble()));

lib/web_ui/lib/src/engine/canvaskit/surface.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,41 @@ class Surface {
141141
ui.Size? _currentSurfaceSize;
142142
double _currentDevicePixelRatio = -1;
143143

144+
/// This is only valid after the first frame or if [ensureSurface] has been
145+
/// called
146+
bool get usingSoftwareBackend => _glContext == null ||
147+
_grContext == null || webGLVersion == -1 || configuration.canvasKitForceCpuOnly;
148+
149+
/// Ensure that the initial surface exists and has a size of at least [size].
150+
///
151+
/// If not provided, [size] defaults to 1x1.
152+
///
153+
/// This also ensures that the gl/grcontext have been populated so
154+
/// that software rendering can be detected.
155+
void ensureSurface([ui.Size size = const ui.Size(1, 1)]) {
156+
// If the GrContext hasn't been setup yet then we need to force initialization
157+
// of the canvas and initial surface.
158+
if (_surface != null) {
159+
return;
160+
}
161+
// TODO(jonahwilliams): this is somewhat wasteful. We should probably
162+
// eagerly setup this surface instead of delaying until the first frame?
163+
// Or at least cache the estimated window size.
164+
createOrUpdateSurface(size);
165+
}
166+
167+
/// This method is not supported if software rendering is used.
168+
CkSurface createRenderTargetSurface(ui.Size size) {
169+
assert(!usingSoftwareBackend);
170+
171+
final SkSurface skSurface = canvasKit.MakeRenderTarget(
172+
_grContext!,
173+
size.width.ceil(),
174+
size.height.ceil(),
175+
)!;
176+
return CkSurface(skSurface, _glContext);
177+
}
178+
144179
/// Creates a <canvas> and SkSurface for the given [size].
145180
CkSurface createOrUpdateSurface(ui.Size size) {
146181
if (size.isEmpty) {

lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ class SurfaceFactory {
4646
/// all painting commands.
4747
final Surface baseSurface = Surface();
4848

49-
/// A surface used specifically for `Picture.toImage` calls, which can be
50-
/// reused in order to avoid creating too many WebGL contexts.
51-
late final Surface pictureToImageSurface = Surface();
52-
5349
/// The maximum number of surfaces which can be live at once.
5450
final int maximumSurfaces;
5551

52+
/// A surface used specifically for `Picture.toImage` when software rendering
53+
/// is supported.
54+
late final Surface pictureToImageSurface = Surface();
55+
5656
/// The maximum number of assignable overlays.
5757
///
5858
/// This is just `maximumSurfaces - 1` (the maximum number of surfaces minus

lib/web_ui/test/canvaskit/canvaskit_api_test.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,4 +1807,23 @@ void _paragraphTests() {
18071807

18081808
expect(skSurface, isNotNull);
18091809
}, skip: isFirefox); // Intended: Headless firefox has no webgl support https://github.com/flutter/flutter/issues/109265
1810+
1811+
test('MakeRenderTarget test', () {
1812+
final DomCanvasElement canvas = createDomCanvasElement(
1813+
width: 100,
1814+
height: 100,
1815+
);
1816+
1817+
final int glContext = canvasKit.GetWebGLContext(
1818+
canvas,
1819+
SkWebGLContextOptions(
1820+
antialias: 0,
1821+
majorVersion: webGLVersion.toDouble(),
1822+
),
1823+
).toInt();
1824+
final SkGrContext grContext = canvasKit.MakeGrContext(glContext.toDouble());
1825+
final SkSurface? surface = canvasKit.MakeRenderTarget(grContext, 1, 1);
1826+
1827+
expect(surface, isNotNull);
1828+
}, skip: isFirefox); // Intended: Headless firefox has no webgl support https://github.com/flutter/flutter/issues/109265
18101829
}

lib/web_ui/test/canvaskit/surface_factory_test.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ void testMain() {
2626
expect(SurfaceFactory(2).maximumSurfaces, 2);
2727
});
2828

29-
test('has a Surface dedicated to Picture.toImage', () {
30-
expect(SurfaceFactory(1).pictureToImageSurface, isNotNull);
31-
});
32-
3329
test('getSurface', () {
3430
final SurfaceFactory factory = SurfaceFactory(3);
3531
expect(factory.baseSurface, isNotNull);

0 commit comments

Comments
 (0)