Skip to content

Commit 5b53be0

Browse files
yjbanovbrandon-bethke-timu
authored andcommitted
[web:canvaskit] switch to temporary SkPaint objects (flutter#54818)
Do not eagerly create an `SkPaint` object that's strongly referenced by `CkPaint`. Instead, when a `Canvas.draw*` is called, create a temporary `SkPaint` object, pass it to Skia, then immediately delete it. This way there are no persistent `SkPaint` handles lurking in the system that transitively hold onto expensive native resources. Addresses the `Paint` issue in flutter/flutter#153678 in CanvasKit Spot checking some benchmarks. Here's the effect of this PR on `draw_rect_variable_paint`. It's a bit of a stress test as it creates 300K distinct `Paint` objects to render 600 pictures (a typical ratio does not normally exceed ten paints to one picture). Even so, the effect of creating an extra `SkPaint` on every `draw*` command does not look significant. However, removing a dependency on the GC for 300K objects looks like a good trade-off. Allocation stats: ``` Paint Created: 300000 Paint Deleted: 300000 Paint Leaked: 300000 Picture Created: 600 Picture Deleted: 599 Picture Leaked: 599 ``` Performance stats: ``` windowRenderDuration: (samples: 98 clean/2 outliers/100 measured/300 total) | average: 4679.551020408163 μs | outlier average: 5100 μs | outlier/clean ratio: 1.0898481452084188x | noise: 3.11% sceneBuildDuration: (samples: 98 clean/2 outliers/100 measured/300 total) | average: 4689.765306122449 μs | outlier average: 5100 μs | outlier/clean ratio: 1.087474461321549x | noise: 3.19% drawFrameDuration: (samples: 97 clean/3 outliers/100 measured/300 total) | average: 8447.474226804125 μs | outlier average: 9332.666666666666 μs | outlier/clean ratio: 1.1047878236850721x | noise: 3.52% ``` Allocation stats: ``` Picture Created: 600 Picture Deleted: 599 Picture Leaked: 599 ``` Performance stats: ``` windowRenderDuration: (samples: 97 clean/3 outliers/100 measured/300 total) | average: 4780.40206185567 μs | outlier average: 5133.666666666667 μs | outlier/clean ratio: 1.0738985131877936x | noise: 2.70% sceneBuildDuration: (samples: 97 clean/3 outliers/100 measured/300 total) | average: 4787.6082474226805 μs | outlier average: 5133.666666666667 μs | outlier/clean ratio: 1.0722821085936345x | noise: 2.72% drawFrameDuration: (samples: 97 clean/3 outliers/100 measured/300 total) | average: 8243.309278350516 μs | outlier average: 9033.333333333334 μs | outlier/clean ratio: 1.0958382159768851x | noise: 2.60% ```
1 parent c47d93a commit 5b53be0

File tree

13 files changed

+325
-377
lines changed

13 files changed

+325
-377
lines changed

lib/web_ui/lib/painting.dart

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,14 +219,6 @@ abstract class Paint {
219219
factory Paint() => engine.renderer.createPaint();
220220

221221
factory Paint.from(Paint other) {
222-
// This is less efficient than copying the underlying buffer or object but
223-
// it's a reasonable default, as if a user wanted to implement a copy of a
224-
// paint object themselves they are unable to do much better than this.
225-
//
226-
// TODO(matanlurey): Web team, if important to optimize, could:
227-
// 1. Add a `engine.renderer.copyPaint` method.
228-
// 2. Use the below code as the default implementation.
229-
// 3. Have renderer-specific implementations override with optimized code.
230222
final Paint paint = Paint();
231223
paint
232224
..blendMode = other.blendMode

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

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,16 @@ class CkCanvas {
8080
CkPaint paint,
8181
) {
8282
const double toDegrees = 180 / math.pi;
83+
84+
final skPaint = paint.toSkPaint();
8385
skCanvas.drawArc(
8486
toSkRect(oval),
8587
startAngle * toDegrees,
8688
sweepAngle * toDegrees,
8789
useCenter,
88-
paint.skiaObject,
90+
skPaint,
8991
);
92+
skPaint.delete();
9093
}
9194

9295
// TODO(flar): CanvasKit does not expose sampling options available on SkCanvas.drawAtlas
@@ -98,23 +101,27 @@ class CkCanvas {
98101
Uint32List? colors,
99102
ui.BlendMode blendMode,
100103
) {
104+
final skPaint = paint.toSkPaint();
101105
skCanvas.drawAtlas(
102106
atlas.skImage,
103107
rects,
104108
rstTransforms,
105-
paint.skiaObject,
109+
skPaint,
106110
toSkBlendMode(blendMode),
107111
colors,
108112
);
113+
skPaint.delete();
109114
}
110115

111116
void drawCircle(ui.Offset c, double radius, CkPaint paint) {
117+
final skPaint = paint.toSkPaint();
112118
skCanvas.drawCircle(
113119
c.dx,
114120
c.dy,
115121
radius,
116-
paint.skiaObject,
122+
skPaint,
117123
);
124+
skPaint.delete();
118125
}
119126

120127
void drawColor(ui.Color color, ui.BlendMode blendMode) {
@@ -125,23 +132,26 @@ class CkCanvas {
125132
}
126133

127134
void drawDRRect(ui.RRect outer, ui.RRect inner, CkPaint paint) {
135+
final skPaint = paint.toSkPaint();
128136
skCanvas.drawDRRect(
129137
toSkRRect(outer),
130138
toSkRRect(inner),
131-
paint.skiaObject,
139+
skPaint,
132140
);
141+
skPaint.delete();
133142
}
134143

135144
void drawImage(CkImage image, ui.Offset offset, CkPaint paint) {
136145
final ui.FilterQuality filterQuality = paint.filterQuality;
146+
final skPaint = paint.toSkPaint();
137147
if (filterQuality == ui.FilterQuality.high) {
138148
skCanvas.drawImageCubic(
139149
image.skImage,
140150
offset.dx,
141151
offset.dy,
142152
_kMitchellNetravali_B,
143153
_kMitchellNetravali_C,
144-
paint.skiaObject,
154+
skPaint,
145155
);
146156
} else {
147157
skCanvas.drawImageOptions(
@@ -150,21 +160,23 @@ class CkCanvas {
150160
offset.dy,
151161
toSkFilterMode(filterQuality),
152162
toSkMipmapMode(filterQuality),
153-
paint.skiaObject,
163+
skPaint,
154164
);
155165
}
166+
skPaint.delete();
156167
}
157168

158169
void drawImageRect(CkImage image, ui.Rect src, ui.Rect dst, CkPaint paint) {
159170
final ui.FilterQuality filterQuality = paint.filterQuality;
171+
final skPaint = paint.toSkPaint();
160172
if (filterQuality == ui.FilterQuality.high) {
161173
skCanvas.drawImageRectCubic(
162174
image.skImage,
163175
toSkRect(src),
164176
toSkRect(dst),
165177
_kMitchellNetravali_B,
166178
_kMitchellNetravali_C,
167-
paint.skiaObject,
179+
skPaint,
168180
);
169181
} else {
170182
skCanvas.drawImageRectOptions(
@@ -173,41 +185,50 @@ class CkCanvas {
173185
toSkRect(dst),
174186
toSkFilterMode(filterQuality),
175187
toSkMipmapMode(filterQuality),
176-
paint.skiaObject,
188+
skPaint,
177189
);
178190
}
191+
skPaint.delete();
179192
}
180193

181194
void drawImageNine(
182195
CkImage image, ui.Rect center, ui.Rect dst, CkPaint paint) {
196+
final skPaint = paint.toSkPaint();
183197
skCanvas.drawImageNine(
184198
image.skImage,
185199
toSkRect(center),
186200
toSkRect(dst),
187201
toSkFilterMode(paint.filterQuality),
188-
paint.skiaObject,
202+
skPaint,
189203
);
204+
skPaint.delete();
190205
}
191206

192207
void drawLine(ui.Offset p1, ui.Offset p2, CkPaint paint) {
208+
final skPaint = paint.toSkPaint();
193209
skCanvas.drawLine(
194210
p1.dx,
195211
p1.dy,
196212
p2.dx,
197213
p2.dy,
198-
paint.skiaObject,
214+
skPaint,
199215
);
216+
skPaint.delete();
200217
}
201218

202219
void drawOval(ui.Rect rect, CkPaint paint) {
220+
final skPaint = paint.toSkPaint();
203221
skCanvas.drawOval(
204222
toSkRect(rect),
205-
paint.skiaObject,
223+
skPaint,
206224
);
225+
skPaint.delete();
207226
}
208227

209228
void drawPaint(CkPaint paint) {
210-
skCanvas.drawPaint(paint.skiaObject);
229+
final skPaint = paint.toSkPaint();
230+
skCanvas.drawPaint(skPaint);
231+
skPaint.delete();
211232
}
212233

213234
void drawParagraph(CkParagraph paragraph, ui.Offset offset) {
@@ -219,7 +240,9 @@ class CkCanvas {
219240
}
220241

221242
void drawPath(CkPath path, CkPaint paint) {
222-
skCanvas.drawPath(path.skiaObject, paint.skiaObject);
243+
final skPaint = paint.toSkPaint();
244+
skCanvas.drawPath(path.skiaObject, skPaint);
245+
skPaint.delete();
223246
}
224247

225248
void drawPicture(CkPicture picture) {
@@ -228,22 +251,28 @@ class CkCanvas {
228251
}
229252

230253
void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) {
254+
final skPaint = paint.toSkPaint();
231255
skCanvas.drawPoints(
232256
toSkPointMode(pointMode),
233257
points,
234-
paint.skiaObject,
258+
skPaint,
235259
);
260+
skPaint.delete();
236261
}
237262

238263
void drawRRect(ui.RRect rrect, CkPaint paint) {
264+
final skPaint = paint.toSkPaint();
239265
skCanvas.drawRRect(
240266
toSkRRect(rrect),
241-
paint.skiaObject,
267+
skPaint,
242268
);
269+
skPaint.delete();
243270
}
244271

245272
void drawRect(ui.Rect rect, CkPaint paint) {
246-
skCanvas.drawRect(toSkRect(rect), paint.skiaObject);
273+
final skPaint = paint.toSkPaint();
274+
skCanvas.drawRect(toSkRect(rect), skPaint);
275+
skPaint.delete();
247276
}
248277

249278
void drawShadow(
@@ -254,11 +283,13 @@ class CkCanvas {
254283

255284
void drawVertices(
256285
CkVertices vertices, ui.BlendMode blendMode, CkPaint paint) {
286+
final skPaint = paint.toSkPaint();
257287
skCanvas.drawVertices(
258288
vertices.skiaObject,
259289
toSkBlendMode(blendMode),
260-
paint.skiaObject,
290+
skPaint,
261291
);
292+
skPaint.delete();
262293
}
263294

264295
void restore() {
@@ -278,16 +309,20 @@ class CkCanvas {
278309
}
279310

280311
void saveLayer(ui.Rect bounds, CkPaint? paint) {
312+
final skPaint = paint?.toSkPaint();
281313
skCanvas.saveLayer(
282-
paint?.skiaObject,
314+
skPaint,
283315
toSkRect(bounds),
284316
null,
285317
null,
286318
);
319+
skPaint?.delete();
287320
}
288321

289322
void saveLayerWithoutBounds(CkPaint? paint) {
290-
skCanvas.saveLayer(paint?.skiaObject, null, null, null);
323+
final skPaint = paint?.toSkPaint();
324+
skCanvas.saveLayer(skPaint, null, null, null);
325+
skPaint?.delete();
291326
}
292327

293328
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter,
@@ -298,13 +333,15 @@ class CkCanvas {
298333
} else {
299334
convertible = filter as CkManagedSkImageFilterConvertible;
300335
}
301-
convertible.imageFilter((SkImageFilter filter) {
336+
convertible.withSkImageFilter((SkImageFilter filter) {
337+
final skPaint = paint?.toSkPaint();
302338
skCanvas.saveLayer(
303-
paint?.skiaObject,
339+
skPaint,
304340
toSkRect(bounds),
305341
filter,
306342
0,
307343
);
344+
skPaint?.delete();
308345
});
309346
}
310347

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,9 @@ class SkImageFilter {}
15161516
extension SkImageFilterExtension on SkImageFilter {
15171517
external JSVoid delete();
15181518

1519+
@JS('isDeleted')
1520+
external JSBoolean _isDeleted();
1521+
bool isDeleted() => _isDeleted().toDart;
15191522

15201523
@JS('getOutputBounds')
15211524
external JSInt32Array _getOutputBounds(JSFloat32Array bounds);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ abstract class CkColorFilter implements CkManagedSkImageFilterConvertible {
7171
SkColorFilter _initRawColorFilter();
7272

7373
@override
74-
void imageFilter(SkImageFilterBorrow borrow) {
74+
void withSkImageFilter(SkImageFilterBorrow borrow) {
7575
// Since ColorFilter has a const constructor it cannot store dynamically
7676
// created Skia objects. Therefore a new SkImageFilter is created every time
7777
// it's used. However, once used it's no longer needed, so it's deleted

0 commit comments

Comments
 (0)