[web:canvaskit] clean up the rest of skia_object_cache usages (flutter/engine#41259)
This should go after https://github.com/flutter/engine/pull/41230. Removes the remaining usages of `skia_object_cache.dart` and deletes it. Also fixes https://github.com/flutter/flutter/issues/86632
This commit is contained in:
@@ -1891,7 +1891,6 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/raster_cache.dart +
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/shader.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/surface.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/text.dart + ../../../flutter/LICENSE
|
||||
@@ -4487,7 +4486,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/raster_cache.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/shader.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/surface.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/text.dart
|
||||
|
||||
@@ -50,7 +50,6 @@ export 'engine/canvaskit/raster_cache.dart';
|
||||
export 'engine/canvaskit/rasterizer.dart';
|
||||
export 'engine/canvaskit/renderer.dart';
|
||||
export 'engine/canvaskit/shader.dart';
|
||||
export 'engine/canvaskit/skia_object_cache.dart';
|
||||
export 'engine/canvaskit/surface.dart';
|
||||
export 'engine/canvaskit/surface_factory.dart';
|
||||
export 'engine/canvaskit/text.dart';
|
||||
|
||||
@@ -333,855 +333,4 @@ class CkCanvas {
|
||||
}
|
||||
return matrix4;
|
||||
}
|
||||
|
||||
CkPictureSnapshot? get pictureSnapshot => null;
|
||||
}
|
||||
|
||||
class RecordingCkCanvas extends CkCanvas {
|
||||
RecordingCkCanvas(super.skCanvas, ui.Rect bounds)
|
||||
: pictureSnapshot = CkPictureSnapshot(bounds);
|
||||
|
||||
@override
|
||||
final CkPictureSnapshot pictureSnapshot;
|
||||
|
||||
void _addCommand(CkPaintCommand command) {
|
||||
pictureSnapshot._commands.add(command);
|
||||
}
|
||||
|
||||
@override
|
||||
void clear(ui.Color color) {
|
||||
super.clear(color);
|
||||
_addCommand(CkClearCommand(color));
|
||||
}
|
||||
|
||||
@override
|
||||
void clipPath(CkPath path, bool doAntiAlias) {
|
||||
super.clipPath(path, doAntiAlias);
|
||||
_addCommand(CkClipPathCommand(path, doAntiAlias));
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRRect(ui.RRect rrect, bool doAntiAlias) {
|
||||
super.clipRRect(rrect, doAntiAlias);
|
||||
_addCommand(CkClipRRectCommand(rrect, doAntiAlias));
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) {
|
||||
super.clipRect(rect, clipOp, doAntiAlias);
|
||||
_addCommand(CkClipRectCommand(rect, clipOp, doAntiAlias));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawArc(
|
||||
ui.Rect oval,
|
||||
double startAngle,
|
||||
double sweepAngle,
|
||||
bool useCenter,
|
||||
CkPaint paint,
|
||||
) {
|
||||
super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
|
||||
_addCommand(
|
||||
CkDrawArcCommand(oval, startAngle, sweepAngle, useCenter, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawAtlasRaw(
|
||||
CkPaint paint,
|
||||
CkImage atlas,
|
||||
Float32List rstTransforms,
|
||||
Float32List rects,
|
||||
Uint32List? colors,
|
||||
ui.BlendMode blendMode,
|
||||
) {
|
||||
super.drawAtlasRaw(paint, atlas, rstTransforms, rects, colors, blendMode);
|
||||
_addCommand(CkDrawAtlasCommand(
|
||||
paint, atlas, rstTransforms, rects, colors, blendMode));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawCircle(ui.Offset c, double radius, CkPaint paint) {
|
||||
super.drawCircle(c, radius, paint);
|
||||
_addCommand(CkDrawCircleCommand(c, radius, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawColor(ui.Color color, ui.BlendMode blendMode) {
|
||||
super.drawColor(color, blendMode);
|
||||
_addCommand(CkDrawColorCommand(color, blendMode));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawDRRect(ui.RRect outer, ui.RRect inner, CkPaint paint) {
|
||||
super.drawDRRect(outer, inner, paint);
|
||||
_addCommand(CkDrawDRRectCommand(outer, inner, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawImage(CkImage image, ui.Offset offset, CkPaint paint) {
|
||||
super.drawImage(image, offset, paint);
|
||||
_addCommand(CkDrawImageCommand(image, offset, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawImageRect(CkImage image, ui.Rect src, ui.Rect dst, CkPaint paint) {
|
||||
super.drawImageRect(image, src, dst, paint);
|
||||
_addCommand(CkDrawImageRectCommand(image, src, dst, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawImageNine(
|
||||
CkImage image, ui.Rect center, ui.Rect dst, CkPaint paint) {
|
||||
super.drawImageNine(image, center, dst, paint);
|
||||
_addCommand(CkDrawImageNineCommand(image, center, dst, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawLine(ui.Offset p1, ui.Offset p2, CkPaint paint) {
|
||||
super.drawLine(p1, p2, paint);
|
||||
_addCommand(CkDrawLineCommand(p1, p2, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawOval(ui.Rect rect, CkPaint paint) {
|
||||
super.drawOval(rect, paint);
|
||||
_addCommand(CkDrawOvalCommand(rect, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawPaint(CkPaint paint) {
|
||||
super.drawPaint(paint);
|
||||
_addCommand(CkDrawPaintCommand(paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawParagraph(CkParagraph paragraph, ui.Offset offset) {
|
||||
super.drawParagraph(paragraph, offset);
|
||||
_addCommand(CkDrawParagraphCommand(paragraph, offset));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawPath(CkPath path, CkPaint paint) {
|
||||
super.drawPath(path, paint);
|
||||
_addCommand(CkDrawPathCommand(path, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawPicture(CkPicture picture) {
|
||||
super.drawPicture(picture);
|
||||
_addCommand(CkDrawPictureCommand(picture));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) {
|
||||
super.drawPoints(paint, pointMode, points);
|
||||
_addCommand(CkDrawPointsCommand(pointMode, points, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawRRect(ui.RRect rrect, CkPaint paint) {
|
||||
super.drawRRect(rrect, paint);
|
||||
_addCommand(CkDrawRRectCommand(rrect, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawRect(ui.Rect rect, CkPaint paint) {
|
||||
super.drawRect(rect, paint);
|
||||
_addCommand(CkDrawRectCommand(rect, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawShadow(
|
||||
CkPath path, ui.Color color, double elevation, bool transparentOccluder) {
|
||||
super.drawShadow(path, color, elevation, transparentOccluder);
|
||||
_addCommand(
|
||||
CkDrawShadowCommand(path, color, elevation, transparentOccluder));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawVertices(
|
||||
CkVertices vertices, ui.BlendMode blendMode, CkPaint paint) {
|
||||
super.drawVertices(vertices, blendMode, paint);
|
||||
_addCommand(CkDrawVerticesCommand(vertices, blendMode, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void restore() {
|
||||
super.restore();
|
||||
_addCommand(const CkRestoreCommand());
|
||||
}
|
||||
|
||||
@override
|
||||
void restoreToCount(int count) {
|
||||
super.restoreToCount(count);
|
||||
_addCommand(CkRestoreToCountCommand(count));
|
||||
}
|
||||
|
||||
@override
|
||||
void rotate(double radians) {
|
||||
super.rotate(radians);
|
||||
_addCommand(CkRotateCommand(radians));
|
||||
}
|
||||
|
||||
@override
|
||||
int save() {
|
||||
_addCommand(const CkSaveCommand());
|
||||
return super.save();
|
||||
}
|
||||
|
||||
@override
|
||||
void saveLayer(ui.Rect bounds, CkPaint? paint) {
|
||||
super.saveLayer(bounds, paint);
|
||||
_addCommand(CkSaveLayerCommand(bounds, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void saveLayerWithoutBounds(CkPaint? paint) {
|
||||
super.saveLayerWithoutBounds(paint);
|
||||
_addCommand(CkSaveLayerWithoutBoundsCommand(paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter,
|
||||
[CkPaint? paint]) {
|
||||
super.saveLayerWithFilter(bounds, filter, paint);
|
||||
_addCommand(CkSaveLayerWithFilterCommand(bounds, filter, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void scale(double sx, double sy) {
|
||||
super.scale(sx, sy);
|
||||
_addCommand(CkScaleCommand(sx, sy));
|
||||
}
|
||||
|
||||
@override
|
||||
void skew(double sx, double sy) {
|
||||
super.skew(sx, sy);
|
||||
_addCommand(CkSkewCommand(sx, sy));
|
||||
}
|
||||
|
||||
@override
|
||||
void transform(Float32List matrix4) {
|
||||
super.transform(matrix4);
|
||||
_addCommand(CkTransformCommand(matrix4));
|
||||
}
|
||||
|
||||
@override
|
||||
void translate(double dx, double dy) {
|
||||
super.translate(dx, dy);
|
||||
_addCommand(CkTranslateCommand(dx, dy));
|
||||
}
|
||||
}
|
||||
|
||||
class CkPictureSnapshot {
|
||||
CkPictureSnapshot(this._bounds);
|
||||
|
||||
final ui.Rect _bounds;
|
||||
final List<CkPaintCommand> _commands = <CkPaintCommand>[];
|
||||
|
||||
SkPicture toPicture() {
|
||||
final SkPictureRecorder recorder = SkPictureRecorder();
|
||||
final Float32List skRect = toSkRect(_bounds);
|
||||
final SkCanvas skCanvas = recorder.beginRecording(skRect);
|
||||
for (final CkPaintCommand command in _commands) {
|
||||
command.apply(skCanvas);
|
||||
}
|
||||
final SkPicture skPicture = recorder.finishRecordingAsPicture();
|
||||
recorder.delete();
|
||||
return skPicture;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final CkPaintCommand command in _commands) {
|
||||
command.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A paint command recorded by [RecordingCkCanvas].
|
||||
///
|
||||
/// # Special rules when drawing images
|
||||
///
|
||||
/// A command painting an image must clone the original image to bump the ref
|
||||
/// count. Otherwise when the framework decides it doesn't need the image any
|
||||
/// more it will bump the ref count down and delete the underlying Skia object,
|
||||
/// leaving the picture that recorded this paint command with a dangling
|
||||
/// pointer. If we attempt to resurrect the picture we'll hit a use-after-free
|
||||
/// error. The command must call [CkImage.dispose] in its [dispose]
|
||||
/// implementation.
|
||||
abstract class CkPaintCommand {
|
||||
const CkPaintCommand();
|
||||
|
||||
/// Applies the command onto the [canvas].
|
||||
void apply(SkCanvas canvas);
|
||||
|
||||
/// Frees resources associated with the command.
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class CkClearCommand extends CkPaintCommand {
|
||||
const CkClearCommand(this.color);
|
||||
|
||||
final ui.Color color;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.clear(toSharedSkColor1(color));
|
||||
}
|
||||
}
|
||||
|
||||
class CkSaveCommand extends CkPaintCommand {
|
||||
const CkSaveCommand();
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.save();
|
||||
}
|
||||
}
|
||||
|
||||
class CkRestoreCommand extends CkPaintCommand {
|
||||
const CkRestoreCommand();
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
class CkRestoreToCountCommand extends CkPaintCommand {
|
||||
const CkRestoreToCountCommand(this.count);
|
||||
|
||||
final int count;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.restoreToCount(count.toDouble());
|
||||
}
|
||||
}
|
||||
|
||||
class CkTranslateCommand extends CkPaintCommand {
|
||||
CkTranslateCommand(this.dx, this.dy);
|
||||
|
||||
final double dx;
|
||||
final double dy;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.translate(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
class CkScaleCommand extends CkPaintCommand {
|
||||
CkScaleCommand(this.sx, this.sy);
|
||||
|
||||
final double sx;
|
||||
final double sy;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.scale(sx, sy);
|
||||
}
|
||||
}
|
||||
|
||||
class CkRotateCommand extends CkPaintCommand {
|
||||
CkRotateCommand(this.radians);
|
||||
|
||||
final double radians;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.rotate(radians * 180.0 / math.pi, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
class CkTransformCommand extends CkPaintCommand {
|
||||
CkTransformCommand(this.matrix4);
|
||||
|
||||
final Float32List matrix4;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.concat(toSkM44FromFloat32(matrix4));
|
||||
}
|
||||
}
|
||||
|
||||
class CkSkewCommand extends CkPaintCommand {
|
||||
CkSkewCommand(this.sx, this.sy);
|
||||
|
||||
final double sx;
|
||||
final double sy;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.skew(sx, sy);
|
||||
}
|
||||
}
|
||||
|
||||
class CkClipRectCommand extends CkPaintCommand {
|
||||
CkClipRectCommand(this.rect, this.clipOp, this.doAntiAlias);
|
||||
|
||||
final ui.Rect rect;
|
||||
final ui.ClipOp clipOp;
|
||||
final bool doAntiAlias;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.clipRect(
|
||||
toSkRect(rect),
|
||||
toSkClipOp(clipOp),
|
||||
doAntiAlias,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawArcCommand extends CkPaintCommand {
|
||||
CkDrawArcCommand(
|
||||
this.oval, this.startAngle, this.sweepAngle, this.useCenter, this.paint);
|
||||
|
||||
final ui.Rect oval;
|
||||
final double startAngle;
|
||||
final double sweepAngle;
|
||||
final bool useCenter;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
const double toDegrees = 180 / math.pi;
|
||||
canvas.drawArc(
|
||||
toSkRect(oval),
|
||||
startAngle * toDegrees,
|
||||
sweepAngle * toDegrees,
|
||||
useCenter,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawAtlasCommand extends CkPaintCommand {
|
||||
CkDrawAtlasCommand(this.paint, this.atlas, this.rstTransforms, this.rects,
|
||||
this.colors, this.blendMode);
|
||||
|
||||
final CkPaint paint;
|
||||
final CkImage atlas;
|
||||
final Float32List rstTransforms;
|
||||
final Float32List rects;
|
||||
final Uint32List? colors;
|
||||
final ui.BlendMode blendMode;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawAtlas(
|
||||
atlas.skImage,
|
||||
rects,
|
||||
rstTransforms,
|
||||
paint.skiaObject,
|
||||
toSkBlendMode(blendMode),
|
||||
colors,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkClipRRectCommand extends CkPaintCommand {
|
||||
CkClipRRectCommand(this.rrect, this.doAntiAlias);
|
||||
|
||||
final ui.RRect rrect;
|
||||
final bool doAntiAlias;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.clipRRect(
|
||||
toSkRRect(rrect),
|
||||
_clipOpIntersect,
|
||||
doAntiAlias,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkClipPathCommand extends CkPaintCommand {
|
||||
CkClipPathCommand(this.path, this.doAntiAlias);
|
||||
|
||||
final CkPath path;
|
||||
final bool doAntiAlias;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.clipPath(
|
||||
path.skiaObject,
|
||||
_clipOpIntersect,
|
||||
doAntiAlias,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawColorCommand extends CkPaintCommand {
|
||||
CkDrawColorCommand(this.color, this.blendMode);
|
||||
|
||||
final ui.Color color;
|
||||
final ui.BlendMode blendMode;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawColorInt(
|
||||
color.value.toDouble(),
|
||||
toSkBlendMode(blendMode),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawLineCommand extends CkPaintCommand {
|
||||
CkDrawLineCommand(this.p1, this.p2, this.paint);
|
||||
|
||||
final ui.Offset p1;
|
||||
final ui.Offset p2;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawLine(
|
||||
p1.dx,
|
||||
p1.dy,
|
||||
p2.dx,
|
||||
p2.dy,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawPaintCommand extends CkPaintCommand {
|
||||
CkDrawPaintCommand(this.paint);
|
||||
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawPaint(paint.skiaObject);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawVerticesCommand extends CkPaintCommand {
|
||||
CkDrawVerticesCommand(this.vertices, this.blendMode, this.paint);
|
||||
|
||||
final CkVertices vertices;
|
||||
final ui.BlendMode blendMode;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawVertices(
|
||||
vertices.skiaObject,
|
||||
toSkBlendMode(blendMode),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawPointsCommand extends CkPaintCommand {
|
||||
CkDrawPointsCommand(this.pointMode, this.points, this.paint);
|
||||
|
||||
final Float32List points;
|
||||
final ui.PointMode pointMode;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawPoints(
|
||||
toSkPointMode(pointMode),
|
||||
points,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawRectCommand extends CkPaintCommand {
|
||||
CkDrawRectCommand(this.rect, this.paint);
|
||||
|
||||
final ui.Rect rect;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawRect(toSkRect(rect), paint.skiaObject);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawRRectCommand extends CkPaintCommand {
|
||||
CkDrawRRectCommand(this.rrect, this.paint);
|
||||
|
||||
final ui.RRect rrect;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawRRect(
|
||||
toSkRRect(rrect),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawDRRectCommand extends CkPaintCommand {
|
||||
CkDrawDRRectCommand(this.outer, this.inner, this.paint);
|
||||
|
||||
final ui.RRect outer;
|
||||
final ui.RRect inner;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawDRRect(
|
||||
toSkRRect(outer),
|
||||
toSkRRect(inner),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawOvalCommand extends CkPaintCommand {
|
||||
CkDrawOvalCommand(this.rect, this.paint);
|
||||
|
||||
final ui.Rect rect;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawOval(
|
||||
toSkRect(rect),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawCircleCommand extends CkPaintCommand {
|
||||
CkDrawCircleCommand(this.c, this.radius, this.paint);
|
||||
|
||||
final ui.Offset c;
|
||||
final double radius;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawCircle(
|
||||
c.dx,
|
||||
c.dy,
|
||||
radius,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawPathCommand extends CkPaintCommand {
|
||||
CkDrawPathCommand(this.path, this.paint);
|
||||
|
||||
final CkPath path;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawPath(path.skiaObject, paint.skiaObject);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawShadowCommand extends CkPaintCommand {
|
||||
CkDrawShadowCommand(
|
||||
this.path, this.color, this.elevation, this.transparentOccluder);
|
||||
|
||||
final CkPath path;
|
||||
final ui.Color color;
|
||||
final double elevation;
|
||||
final bool transparentOccluder;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
drawSkShadow(canvas, path, color, elevation, transparentOccluder,
|
||||
ui.window.devicePixelRatio);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawImageCommand extends CkPaintCommand {
|
||||
CkDrawImageCommand(CkImage ckImage, this.offset, this.paint)
|
||||
: image = ckImage.clone();
|
||||
|
||||
final CkImage image;
|
||||
final ui.Offset offset;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
final ui.FilterQuality filterQuality = paint.filterQuality;
|
||||
if (filterQuality == ui.FilterQuality.high) {
|
||||
canvas.drawImageCubic(
|
||||
image.skImage,
|
||||
offset.dx,
|
||||
offset.dy,
|
||||
CkCanvas._kMitchellNetravali_B,
|
||||
CkCanvas._kMitchellNetravali_C,
|
||||
paint.skiaObject,
|
||||
);
|
||||
} else {
|
||||
canvas.drawImageOptions(
|
||||
image.skImage,
|
||||
offset.dx,
|
||||
offset.dy,
|
||||
toSkFilterMode(filterQuality),
|
||||
toSkMipmapMode(filterQuality),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
image.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawImageRectCommand extends CkPaintCommand {
|
||||
CkDrawImageRectCommand(CkImage ckImage, this.src, this.dst, this.paint)
|
||||
: image = ckImage.clone();
|
||||
|
||||
final CkImage image;
|
||||
final ui.Rect src;
|
||||
final ui.Rect dst;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
final ui.FilterQuality filterQuality = paint.filterQuality;
|
||||
if (filterQuality == ui.FilterQuality.high) {
|
||||
canvas.drawImageRectCubic(
|
||||
image.skImage,
|
||||
toSkRect(src),
|
||||
toSkRect(dst),
|
||||
CkCanvas._kMitchellNetravali_B,
|
||||
CkCanvas._kMitchellNetravali_C,
|
||||
paint.skiaObject,
|
||||
);
|
||||
} else {
|
||||
canvas.drawImageRectOptions(
|
||||
image.skImage,
|
||||
toSkRect(src),
|
||||
toSkRect(dst),
|
||||
toSkFilterMode(filterQuality),
|
||||
toSkMipmapMode(filterQuality),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
image.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawImageNineCommand extends CkPaintCommand {
|
||||
CkDrawImageNineCommand(CkImage ckImage, this.center, this.dst, this.paint)
|
||||
: image = ckImage.clone();
|
||||
|
||||
final CkImage image;
|
||||
final ui.Rect center;
|
||||
final ui.Rect dst;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawImageNine(
|
||||
image.skImage,
|
||||
toSkRect(center),
|
||||
toSkRect(dst),
|
||||
toSkFilterMode(paint.filterQuality),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
image.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawParagraphCommand extends CkPaintCommand {
|
||||
CkDrawParagraphCommand(this.paragraph, this.offset);
|
||||
|
||||
final CkParagraph paragraph;
|
||||
final ui.Offset offset;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawParagraph(
|
||||
paragraph.skiaObject,
|
||||
offset.dx,
|
||||
offset.dy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawPictureCommand extends CkPaintCommand {
|
||||
CkDrawPictureCommand(this.picture);
|
||||
|
||||
final CkPicture picture;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawPicture(picture.skiaObject);
|
||||
}
|
||||
}
|
||||
|
||||
class CkSaveLayerCommand extends CkPaintCommand {
|
||||
CkSaveLayerCommand(this.bounds, this.paint);
|
||||
|
||||
final ui.Rect bounds;
|
||||
final CkPaint? paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.saveLayer(
|
||||
paint?.skiaObject,
|
||||
toSkRect(bounds),
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkSaveLayerWithoutBoundsCommand extends CkPaintCommand {
|
||||
CkSaveLayerWithoutBoundsCommand(this.paint);
|
||||
|
||||
final CkPaint? paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.saveLayer(
|
||||
paint?.skiaObject,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkSaveLayerWithFilterCommand extends CkPaintCommand {
|
||||
CkSaveLayerWithFilterCommand(this.bounds, this.filter, this.paint);
|
||||
|
||||
final ui.Rect bounds;
|
||||
final ui.ImageFilter filter;
|
||||
final CkPaint? paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
final CkManagedSkImageFilterConvertible convertible;
|
||||
if (filter is ui.ColorFilter) {
|
||||
convertible = createCkColorFilter(filter as EngineColorFilter)!;
|
||||
} else {
|
||||
convertible = filter as CkManagedSkImageFilterConvertible;
|
||||
}
|
||||
convertible.imageFilter((SkImageFilter filter) {
|
||||
canvas.saveLayer(
|
||||
paint?.skiaObject,
|
||||
toSkRect(bounds),
|
||||
filter,
|
||||
0,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import 'package:ui/ui.dart' as ui;
|
||||
import '../browser_detection.dart';
|
||||
import '../configuration.dart';
|
||||
import '../dom.dart';
|
||||
import '../profiler.dart';
|
||||
import 'renderer.dart';
|
||||
|
||||
/// Entrypoint into the CanvasKit API.
|
||||
@@ -3350,172 +3349,6 @@ extension SkTypefaceFactoryExtension on SkTypefaceFactory {
|
||||
_MakeFreeTypeFaceFromData(fontData.toJS);
|
||||
}
|
||||
|
||||
/// Collects Skia objects that are no longer necessary.
|
||||
abstract class Collector {
|
||||
/// The production collector implementation.
|
||||
static final Collector _productionInstance = ProductionCollector();
|
||||
|
||||
/// The collector implementation currently in use.
|
||||
static Collector get instance => _instance;
|
||||
static Collector _instance = _productionInstance;
|
||||
|
||||
/// In tests overrides the collector implementation.
|
||||
static void debugOverrideCollector(Collector override) {
|
||||
_instance = override;
|
||||
}
|
||||
|
||||
/// In tests restores the collector to the production implementation.
|
||||
static void debugRestoreCollector() {
|
||||
_instance = _productionInstance;
|
||||
}
|
||||
|
||||
/// Registers a [deletable] for collection when the [wrapper] object is
|
||||
/// garbage collected.
|
||||
///
|
||||
/// The [debugLabel] is used to track the origin of the deletable.
|
||||
void register(Object wrapper, SkDeletable deletable);
|
||||
|
||||
/// Deletes the [deletable].
|
||||
///
|
||||
/// The exact timing of the deletion is implementation-specific. For example,
|
||||
/// a production implementation may want to batch deletables and schedule a
|
||||
/// timer to collect them instead of deleting right away.
|
||||
///
|
||||
/// A test implementation may want a collection strategy that's less efficient
|
||||
/// but more predictable.
|
||||
void collect(SkDeletable deletable);
|
||||
}
|
||||
|
||||
/// Uses the browser's real `FinalizationRegistry` to collect objects.
|
||||
///
|
||||
/// Uses timers to delete objects in batches and outside the animation frame.
|
||||
class ProductionCollector implements Collector {
|
||||
ProductionCollector() {
|
||||
_skObjectFinalizationRegistry =
|
||||
createSkObjectFinalizationRegistry((SkDeletable deletable) {
|
||||
// This is called when GC decides to collect the wrapper object and
|
||||
// notify us, which may happen after the object is already deleted
|
||||
// explicitly, e.g. when its ref count drops to zero. When that happens
|
||||
// skip collection of this object.
|
||||
if (!deletable.isDeleted()) {
|
||||
collect(deletable);
|
||||
}
|
||||
}.toJS);
|
||||
}
|
||||
|
||||
late final SkObjectFinalizationRegistry _skObjectFinalizationRegistry;
|
||||
List<SkDeletable> _skiaObjectCollectionQueue = <SkDeletable>[];
|
||||
Timer? _skiaObjectCollectionTimer;
|
||||
|
||||
@override
|
||||
void register(Object wrapper, SkDeletable deletable) {
|
||||
if (Instrumentation.enabled) {
|
||||
Instrumentation.instance.incrementCounter(
|
||||
'${deletable.constructor.name} registered',
|
||||
);
|
||||
}
|
||||
_skObjectFinalizationRegistry.register(wrapper, deletable);
|
||||
}
|
||||
|
||||
/// Schedules a Skia object for deletion in an asap timer.
|
||||
///
|
||||
/// A timer is used for the following reasons:
|
||||
///
|
||||
/// - Deleting the object immediately may lead to dangling pointer as the Skia
|
||||
/// object may still be used by a function in the current frame. For example,
|
||||
/// a `CkPaint` + `SkPaint` pair may be created by the framework, passed to
|
||||
/// the engine, and the `CkPaint` dropped immediately. Because GC can kick in
|
||||
/// any time, including in the middle of the event, we may delete `SkPaint`
|
||||
/// prematurely.
|
||||
/// - A microtask, while solves the problem above, would prevent the event from
|
||||
/// yielding to the graphics system to render the frame on the screen if there
|
||||
/// is a large number of objects to delete, causing jank.
|
||||
///
|
||||
/// Because scheduling a timer is expensive, the timer is shared by all objects
|
||||
/// deleted this frame. No timer is created if no objects were scheduled for
|
||||
/// deletion.
|
||||
@override
|
||||
void collect(SkDeletable deletable) {
|
||||
assert(
|
||||
!deletable.isDeleted(),
|
||||
'Attempted to delete an already deleted Skia object.',
|
||||
);
|
||||
_skiaObjectCollectionQueue.add(deletable);
|
||||
|
||||
_skiaObjectCollectionTimer ??= Timer(Duration.zero, () {
|
||||
// Null out the timer so we can schedule a new one next time objects are
|
||||
// scheduled for deletion.
|
||||
_skiaObjectCollectionTimer = null;
|
||||
collectSkiaObjectsNow();
|
||||
});
|
||||
}
|
||||
|
||||
/// Deletes all Skia objects pending deletion synchronously.
|
||||
///
|
||||
/// After calling this method [_skiaObjectCollectionQueue] is empty.
|
||||
///
|
||||
/// Throws a [SkiaObjectCollectionError] if CanvasKit fails to delete at least
|
||||
/// one object. The error is populated with information about the first failed
|
||||
/// object. Upon an error the collection continues and the collection queue is
|
||||
/// emptied out to prevent memory leaks. This may happen, for example, when the
|
||||
/// same object is deleted more than once.
|
||||
void collectSkiaObjectsNow() {
|
||||
domWindow.performance.mark('SkObject collection-start');
|
||||
final int length = _skiaObjectCollectionQueue.length;
|
||||
dynamic firstError;
|
||||
StackTrace? firstStackTrace;
|
||||
for (int i = 0; i < length; i++) {
|
||||
final SkDeletable deletable = _skiaObjectCollectionQueue[i];
|
||||
if (deletable.isDeleted()) {
|
||||
// Some Skia objects are ref counted and are deleted before GC and/or
|
||||
// the collection timer begins collecting them. So we have to check
|
||||
// again if the objects is worth collecting.
|
||||
continue;
|
||||
}
|
||||
if (Instrumentation.enabled) {
|
||||
Instrumentation.instance.incrementCounter(
|
||||
'${deletable.constructor.name} deleted',
|
||||
);
|
||||
}
|
||||
try {
|
||||
deletable.delete();
|
||||
} catch (error, stackTrace) {
|
||||
// Remember the error, but keep going. If for some reason CanvasKit fails
|
||||
// to delete an object we still want to delete other objects and empty
|
||||
// out the queue. Otherwise, the queue will never be flushed and keep
|
||||
// accumulating objects, a.k.a. memory leak.
|
||||
if (firstError == null) {
|
||||
firstError = error;
|
||||
firstStackTrace = stackTrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
_skiaObjectCollectionQueue = <SkDeletable>[];
|
||||
|
||||
domWindow.performance.mark('SkObject collection-end');
|
||||
domWindow.performance.measure('SkObject collection',
|
||||
'SkObject collection-start', 'SkObject collection-end');
|
||||
|
||||
// It's safe to throw the error here, now that we've processed the queue.
|
||||
if (firstError != null) {
|
||||
throw SkiaObjectCollectionError(firstError, firstStackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Thrown by [ProductionCollector] when Skia object collection fails.
|
||||
class SkiaObjectCollectionError implements Error {
|
||||
SkiaObjectCollectionError(this.error, this.stackTrace);
|
||||
|
||||
final dynamic error;
|
||||
|
||||
@override
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
@override
|
||||
String toString() => 'SkiaObjectCollectionError: $error\n$stackTrace';
|
||||
}
|
||||
|
||||
/// Any Skia object that has a `delete` method.
|
||||
@JS()
|
||||
@anonymous
|
||||
@@ -3591,12 +3424,6 @@ external JSAny? get _finalizationRegistryConstructor;
|
||||
bool browserSupportsFinalizationRegistry =
|
||||
_finalizationRegistryConstructor != null;
|
||||
|
||||
/// Sets the value of [browserSupportsFinalizationRegistry] to its true value.
|
||||
void debugResetBrowserSupportsFinalizationRegistry() {
|
||||
browserSupportsFinalizationRegistry =
|
||||
_finalizationRegistryConstructor != null;
|
||||
}
|
||||
|
||||
@JS()
|
||||
@staticInterop
|
||||
class SkData {}
|
||||
|
||||
@@ -17,29 +17,28 @@ import 'package:ui/ui.dart' as ui;
|
||||
import '../util.dart';
|
||||
import 'canvaskit_api.dart';
|
||||
import 'image.dart';
|
||||
import 'skia_object_cache.dart';
|
||||
import 'native_memory.dart';
|
||||
|
||||
/// The CanvasKit implementation of [ui.Codec].
|
||||
///
|
||||
/// Wraps `SkAnimatedImage`.
|
||||
class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
|
||||
implements ui.Codec {
|
||||
class CkAnimatedImage implements ui.Codec {
|
||||
/// Decodes an image from a list of encoded bytes.
|
||||
CkAnimatedImage.decodeFromBytes(this._bytes, this.src, {this.targetWidth, this.targetHeight});
|
||||
CkAnimatedImage.decodeFromBytes(this._bytes, this.src, {this.targetWidth, this.targetHeight}) {
|
||||
final SkAnimatedImage skAnimatedImage = createSkAnimatedImage();
|
||||
_ref = UniqueRef<SkAnimatedImage>(this, skAnimatedImage, 'Codec');
|
||||
}
|
||||
|
||||
late final UniqueRef<SkAnimatedImage> _ref;
|
||||
final String src;
|
||||
final Uint8List _bytes;
|
||||
int _frameCount = 0;
|
||||
int _repetitionCount = -1;
|
||||
|
||||
/// Current frame index.
|
||||
int _currentFrameIndex = 0;
|
||||
|
||||
final int? targetWidth;
|
||||
final int? targetHeight;
|
||||
|
||||
@override
|
||||
SkAnimatedImage createDefault() {
|
||||
SkAnimatedImage createSkAnimatedImage() {
|
||||
SkAnimatedImage? animatedImage =
|
||||
canvasKit.MakeAnimatedImageFromEncoded(_bytes);
|
||||
if (animatedImage == null) {
|
||||
@@ -66,16 +65,6 @@ class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
|
||||
_frameCount = animatedImage.getFrameCount().toInt();
|
||||
_repetitionCount = animatedImage.getRepetitionCount().toInt();
|
||||
|
||||
// Normally CanvasKit initializes `SkAnimatedImage` to point to the first
|
||||
// frame in the animation. However, if the Skia object has been deleted then
|
||||
// resurrected, the framework/app may already have advanced to one of the
|
||||
// subsequent frames. When that happens the value of _currentFrameIndex will
|
||||
// be something other than zero, and we need to tell the decoder to skip
|
||||
// over the previous frames to point to the current one.
|
||||
for (int i = 0; i < _currentFrameIndex; i++) {
|
||||
animatedImage.decodeNextFrame();
|
||||
}
|
||||
|
||||
return animatedImage;
|
||||
}
|
||||
|
||||
@@ -92,17 +81,6 @@ class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
|
||||
return resizedAnimatedImage;
|
||||
}
|
||||
|
||||
@override
|
||||
SkAnimatedImage resurrect() => createDefault();
|
||||
|
||||
@override
|
||||
bool get isResurrectionExpensive => true;
|
||||
|
||||
@override
|
||||
void delete() {
|
||||
rawSkiaObject?.delete();
|
||||
}
|
||||
|
||||
bool _disposed = false;
|
||||
bool get debugDisposed => _disposed;
|
||||
|
||||
@@ -118,7 +96,7 @@ class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
|
||||
'Cannot dispose a codec that has already been disposed.',
|
||||
);
|
||||
_disposed = true;
|
||||
delete();
|
||||
_ref.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -136,7 +114,7 @@ class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
|
||||
@override
|
||||
Future<ui.FrameInfo> getNextFrame() {
|
||||
assert(_debugCheckIsNotDisposed());
|
||||
final SkAnimatedImage animatedImage = skiaObject;
|
||||
final SkAnimatedImage animatedImage = _ref.nativeObject;
|
||||
|
||||
// SkAnimatedImage comes pre-initialized to point to the current frame (by
|
||||
// default the first frame, and, with some special resurrection logic in
|
||||
@@ -152,7 +130,6 @@ class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
|
||||
);
|
||||
|
||||
animatedImage.decodeNextFrame();
|
||||
_currentFrameIndex = (_currentFrameIndex + 1) % _frameCount;
|
||||
return Future<ui.FrameInfo>.value(currentFrame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import 'image_filter.dart';
|
||||
import 'mask_filter.dart';
|
||||
import 'native_memory.dart';
|
||||
import 'shader.dart';
|
||||
import 'skia_object_cache.dart';
|
||||
|
||||
/// The implementation of [ui.Paint] used by the CanvasKit backend.
|
||||
///
|
||||
|
||||
@@ -11,25 +11,20 @@ import '../util.dart';
|
||||
import 'canvas.dart';
|
||||
import 'canvaskit_api.dart';
|
||||
import 'image.dart';
|
||||
import 'skia_object_cache.dart';
|
||||
import 'native_memory.dart';
|
||||
import 'surface.dart';
|
||||
import 'surface_factory.dart';
|
||||
|
||||
/// Implements [ui.Picture] on top of [SkPicture].
|
||||
///
|
||||
/// Unlike most other [ManagedSkiaObject] implementations, instances of this
|
||||
/// class may have their Skia counterparts deleted before finalization registry
|
||||
/// or [SkiaObjectCache] decide to delete it.
|
||||
class CkPicture extends ManagedSkiaObject<SkPicture> implements ui.Picture {
|
||||
CkPicture(SkPicture super.picture, this.cullRect, this._snapshot) :
|
||||
assert(
|
||||
browserSupportsFinalizationRegistry && _snapshot == null ||
|
||||
_snapshot != null,
|
||||
'If the browser does not support FinalizationRegistry (WeakRef), then we must have a picture snapshot to be able to resurrect it.',
|
||||
);
|
||||
class CkPicture implements ui.Picture {
|
||||
CkPicture(SkPicture skPicture, this.cullRect) {
|
||||
_ref = UniqueRef<SkPicture>(this, skPicture, 'Picture');
|
||||
}
|
||||
|
||||
late final UniqueRef<SkPicture> _ref;
|
||||
final ui.Rect? cullRect;
|
||||
final CkPictureSnapshot? _snapshot;
|
||||
|
||||
SkPicture get skiaObject => _ref.nativeObject;
|
||||
|
||||
@override
|
||||
int get approximateBytesUsed => 0;
|
||||
@@ -86,11 +81,7 @@ class CkPicture extends ManagedSkiaObject<SkPicture> implements ui.Picture {
|
||||
Instrumentation.instance.incrementCounter('Picture disposed');
|
||||
}
|
||||
_isDisposed = true;
|
||||
_snapshot?.dispose();
|
||||
|
||||
// Emulate what SkiaObjectCache does.
|
||||
rawSkiaObject?.delete();
|
||||
rawSkiaObject = null;
|
||||
_ref.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -123,31 +114,4 @@ class CkPicture extends ManagedSkiaObject<SkPicture> implements ui.Picture {
|
||||
}
|
||||
return CkImage(rasterImage);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isResurrectionExpensive => true;
|
||||
|
||||
@override
|
||||
SkPicture createDefault() {
|
||||
// The default object is supplied in the constructor.
|
||||
throw StateError('Unreachable code');
|
||||
}
|
||||
|
||||
@override
|
||||
SkPicture resurrect() {
|
||||
// If a picture has been explicitly disposed of, it can no longer be
|
||||
// resurrected. An attempt to resurrect after the framework told the
|
||||
// engine to dispose of the picture likely indicates a bug in the engine.
|
||||
assert(debugCheckNotDisposed('Cannot resurrect picture.'));
|
||||
return _snapshot!.toPicture();
|
||||
}
|
||||
|
||||
@override
|
||||
void delete() {
|
||||
// This method may be called after [dispose], in which case there's nothing
|
||||
// left to do. The Skia object is deleted permanently.
|
||||
if (!_isDisposed) {
|
||||
rawSkiaObject?.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@ class CkPictureRecorder implements ui.PictureRecorder {
|
||||
final SkPictureRecorder recorder = _skRecorder = SkPictureRecorder();
|
||||
final Float32List skRect = toSkRect(bounds);
|
||||
final SkCanvas skCanvas = recorder.beginRecording(skRect);
|
||||
return _recordingCanvas = browserSupportsFinalizationRegistry
|
||||
? CkCanvas(skCanvas)
|
||||
: RecordingCkCanvas(skCanvas, bounds);
|
||||
return _recordingCanvas = CkCanvas(skCanvas);
|
||||
}
|
||||
|
||||
CkCanvas? get recordingCanvas => _recordingCanvas;
|
||||
@@ -38,8 +36,7 @@ class CkPictureRecorder implements ui.PictureRecorder {
|
||||
final SkPicture skPicture = recorder.finishRecordingAsPicture();
|
||||
recorder.delete();
|
||||
_skRecorder = null;
|
||||
final CkPicture result =
|
||||
CkPicture(skPicture, _cullRect, _recordingCanvas!.pictureSnapshot);
|
||||
final CkPicture result = CkPicture(skPicture, _cullRect);
|
||||
// We invoke the handler here, not in the picture constructor, because we want
|
||||
// [result.approximateBytesUsed] to be available for the handler.
|
||||
ui.Picture.onCreate?.call(result);
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../engine.dart' show Instrumentation;
|
||||
import 'canvaskit_api.dart';
|
||||
import 'renderer.dart';
|
||||
|
||||
/// A cache of Skia objects whose memory Flutter manages.
|
||||
///
|
||||
/// When using Skia, Flutter creates Skia objects which are allocated in
|
||||
/// WASM memory and which must be explicitly deleted. In the case of Flutter
|
||||
/// mobile, the Skia objects are wrapped by a C++ class which is destroyed
|
||||
/// when the associated Dart object is garbage collected.
|
||||
///
|
||||
/// On the web, we cannot tell when a Dart object is garbage collected, so
|
||||
/// we must use other strategies to know when to delete a Skia object. Some
|
||||
/// objects, like [ui.Paint], can safely delete their associated Skia object
|
||||
/// because they can always recreate the Skia object from data stored in the
|
||||
/// Dart object. Other objects, like [ui.Picture], can be serialized to a
|
||||
/// JS-managed data structure when they are deleted so that when the associated
|
||||
/// object is garbage collected, so is the serialized data.
|
||||
class SkiaObjectCache {
|
||||
SkiaObjectCache(this.maximumSize)
|
||||
: _itemQueue = DoubleLinkedQueue<SkiaObject<Object>>(),
|
||||
_itemMap = <SkiaObject<Object>, DoubleLinkedQueueEntry<SkiaObject<Object>>>{};
|
||||
|
||||
final int maximumSize;
|
||||
|
||||
/// A doubly linked list of the objects in the cache.
|
||||
///
|
||||
/// This makes it fast to move a recently used object to the front.
|
||||
final DoubleLinkedQueue<SkiaObject<Object>> _itemQueue;
|
||||
|
||||
/// A map of objects to their associated node in the [_itemQueue].
|
||||
///
|
||||
/// This makes it fast to find the node in the queue when we need to
|
||||
/// move the object to the front of the queue.
|
||||
final Map<SkiaObject<Object>, DoubleLinkedQueueEntry<SkiaObject<Object>>> _itemMap;
|
||||
|
||||
/// The number of objects in the cache.
|
||||
int get length => _itemQueue.length;
|
||||
|
||||
/// Whether or not [object] is in the cache.
|
||||
///
|
||||
/// This is only for testing.
|
||||
@visibleForTesting
|
||||
bool debugContains(SkiaObject<Object> object) {
|
||||
return _itemMap.containsKey(object);
|
||||
}
|
||||
|
||||
/// Adds [object] to the cache.
|
||||
///
|
||||
/// If adding [object] causes the total size of the cache to exceed
|
||||
/// [maximumSize], then the least recently used half of the cache
|
||||
/// will be deleted.
|
||||
void add(SkiaObject<Object> object) {
|
||||
_itemQueue.addFirst(object);
|
||||
_itemMap[object] = _itemQueue.firstEntry()!;
|
||||
|
||||
if (_itemQueue.length > maximumSize) {
|
||||
SkiaObjects.markCacheForResize(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// Records that [object] was used in the most recent frame.
|
||||
void markUsed(SkiaObject<Object> object) {
|
||||
final DoubleLinkedQueueEntry<SkiaObject<Object>> item = _itemMap[object]!;
|
||||
item.remove();
|
||||
_itemQueue.addFirst(object);
|
||||
_itemMap[object] = _itemQueue.firstEntry()!;
|
||||
}
|
||||
|
||||
/// Deletes the least recently used half of this cache.
|
||||
void resize() {
|
||||
final int itemsToDelete = maximumSize ~/ 2;
|
||||
for (int i = 0; i < itemsToDelete; i++) {
|
||||
final SkiaObject<Object> oldObject = _itemQueue.removeLast();
|
||||
_itemMap.remove(oldObject);
|
||||
oldObject.delete();
|
||||
oldObject.didDelete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An object backed by a JavaScript object mapped onto a Skia C++ object in the
|
||||
/// WebAssembly heap.
|
||||
///
|
||||
/// These objects are automatically deleted when no longer used.
|
||||
abstract class SkiaObject<T extends Object> {
|
||||
/// The JavaScript object that's mapped onto a Skia C++ object in the WebAssembly heap.
|
||||
T get skiaObject;
|
||||
|
||||
/// Deletes the associated C++ object from the WebAssembly heap.
|
||||
void delete();
|
||||
|
||||
/// Lifecycle method called immediately after calling [delete].
|
||||
///
|
||||
/// This method is used to
|
||||
void didDelete();
|
||||
}
|
||||
|
||||
/// A [SkiaObject] that manages the lifecycle of its C++ counterpart.
|
||||
///
|
||||
/// In browsers that support weak references we use feedback from the garbage
|
||||
/// collector to determine when it is safe to release the C++ object.
|
||||
///
|
||||
/// In browsers that do not support weak references we pessimistically delete
|
||||
/// the underlying C++ object before the Dart object is garbage-collected.
|
||||
///
|
||||
/// If [isResurrectionExpensive] is false the object is deleted at the end of
|
||||
/// the frame. If a deleted object is reused in a subsequent frame it is
|
||||
/// resurrected by calling [resurrect]. This allows reusing the C++ objects
|
||||
/// within the frame.
|
||||
///
|
||||
/// If [isResurrectionExpensive] is true the object is put in a LRU cache.
|
||||
/// Objects that are used least frequently are deleted from the cache when
|
||||
/// the cache limit is reached.
|
||||
///
|
||||
/// The lifecycle of a resurrectable C++ object is as follows:
|
||||
///
|
||||
/// - Create: a managed object is created using a default instance that's
|
||||
/// either supplied as a constructor argument, or obtained by calling
|
||||
/// [createDefault]. The data in the new object is expected to contain
|
||||
/// data matching Flutter's defaults (sometimes Skia defaults need to be
|
||||
/// adjusted).
|
||||
/// - Zero or more cycles of delete + resurrect: when a Dart object is reused
|
||||
/// after its C++ object is deleted we create a new C++ object populated with
|
||||
/// data from the current state of the Dart object. This is done using the
|
||||
/// [resurrect] method.
|
||||
/// - Final delete: if a Dart object is never reused, it is GC'd after its
|
||||
/// underlying C++ object is deleted. This is implemented by [SkiaObjects].
|
||||
abstract class ManagedSkiaObject<T extends Object> extends SkiaObject<T> {
|
||||
/// Creates a managed Skia object.
|
||||
///
|
||||
/// If `instance` is null calls [createDefault] to create a Skia object to
|
||||
/// manage. Otherwise, uses the provided instance.
|
||||
///
|
||||
/// The provided instance must not be managed by another [ManagedSkiaObject],
|
||||
/// as it may lead to undefined behavior.
|
||||
ManagedSkiaObject([T? instance]) {
|
||||
final T defaultObject = instance ?? createDefault();
|
||||
rawSkiaObject = defaultObject;
|
||||
if (browserSupportsFinalizationRegistry) {
|
||||
// If FinalizationRegistry is supported we will only ever need the
|
||||
// default object, as we know precisely when to delete it.
|
||||
Collector.instance.register(this, defaultObject as SkDeletable);
|
||||
} else {
|
||||
// If FinalizationRegistry is _not_ supported we may need to delete
|
||||
// and resurrect the object multiple times before deleting it forever.
|
||||
if (Instrumentation.enabled) {
|
||||
Instrumentation.instance.incrementCounter(
|
||||
'${(defaultObject as SkDeletable).constructor.name} created',
|
||||
);
|
||||
}
|
||||
if (isResurrectionExpensive) {
|
||||
SkiaObjects.manageExpensive(this);
|
||||
} else {
|
||||
SkiaObjects.manageResurrectable(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
T get skiaObject => rawSkiaObject ?? _doResurrect();
|
||||
|
||||
T _doResurrect() {
|
||||
assert(!browserSupportsFinalizationRegistry);
|
||||
final T skiaObject = resurrect();
|
||||
if (Instrumentation.enabled) {
|
||||
Instrumentation.instance.incrementCounter(
|
||||
'${(skiaObject as SkDeletable).constructor.name} resurrected',
|
||||
);
|
||||
}
|
||||
rawSkiaObject = skiaObject;
|
||||
if (isResurrectionExpensive) {
|
||||
SkiaObjects.manageExpensive(this);
|
||||
} else {
|
||||
SkiaObjects.manageResurrectable(this);
|
||||
}
|
||||
return skiaObject;
|
||||
}
|
||||
|
||||
@override
|
||||
void didDelete() {
|
||||
assert(!browserSupportsFinalizationRegistry);
|
||||
|
||||
// Null indicates that the object has been manually disposed of. This
|
||||
// happens for objects with manual lifecycles, such as Picture.
|
||||
if (rawSkiaObject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Instrumentation.enabled) {
|
||||
Instrumentation.instance.incrementCounter(
|
||||
'${(rawSkiaObject! as SkDeletable).constructor.name} deleted',
|
||||
);
|
||||
}
|
||||
rawSkiaObject = null;
|
||||
}
|
||||
|
||||
/// Returns the current skia object as is without attempting to
|
||||
/// resurrect it.
|
||||
///
|
||||
/// If the returned value is `null`, the corresponding C++ object has
|
||||
/// been deleted.
|
||||
///
|
||||
/// Use this field instead of the [skiaObject] getter when implementing
|
||||
/// the [delete] method.
|
||||
T? rawSkiaObject;
|
||||
|
||||
/// Instantiates a new Skia-backed JavaScript object containing default
|
||||
/// values.
|
||||
///
|
||||
/// The object is expected to represent Flutter's defaults. If Skia uses
|
||||
/// different defaults from those used by Flutter, this method is expected
|
||||
/// initialize the object to Flutter's defaults.
|
||||
T createDefault();
|
||||
|
||||
/// Creates a new Skia-backed JavaScript object containing data representing
|
||||
/// the current state of the Dart object.
|
||||
T resurrect();
|
||||
|
||||
/// Whether or not it is expensive to resurrect this object.
|
||||
///
|
||||
/// Defaults to false.
|
||||
bool get isResurrectionExpensive => false;
|
||||
}
|
||||
|
||||
/// A function that restores a Skia object that was temporarily deleted.
|
||||
typedef Resurrector<T> = T Function();
|
||||
|
||||
// ignore: avoid_classes_with_only_static_members
|
||||
/// Singleton that manages the lifecycles of [SkiaObject] instances.
|
||||
class SkiaObjects {
|
||||
@visibleForTesting
|
||||
static final List<ManagedSkiaObject<Object>> resurrectableObjects =
|
||||
<ManagedSkiaObject<Object>>[];
|
||||
|
||||
@visibleForTesting
|
||||
static int maximumCacheSize = 1024;
|
||||
|
||||
@visibleForTesting
|
||||
static final SkiaObjectCache expensiveCache =
|
||||
SkiaObjectCache(maximumCacheSize);
|
||||
|
||||
@visibleForTesting
|
||||
static final List<SkiaObjectCache> cachesToResize = <SkiaObjectCache>[];
|
||||
|
||||
static bool _addedCleanupCallback = false;
|
||||
|
||||
@visibleForTesting
|
||||
static void registerCleanupCallback() {
|
||||
if (_addedCleanupCallback) {
|
||||
return;
|
||||
}
|
||||
CanvasKitRenderer.instance.rasterizer.addPostFrameCallback(postFrameCleanUp);
|
||||
_addedCleanupCallback = true;
|
||||
}
|
||||
|
||||
/// Starts managing the lifecycle of resurrectable [object].
|
||||
///
|
||||
/// These can safely be deleted at any time.
|
||||
static void manageResurrectable(ManagedSkiaObject<Object> object) {
|
||||
registerCleanupCallback();
|
||||
resurrectableObjects.add(object);
|
||||
}
|
||||
|
||||
/// Starts managing the lifecycle of a resurrectable object that is expensive.
|
||||
///
|
||||
/// Since it's expensive to resurrect, we shouldn't just delete it after every
|
||||
/// frame. Instead, add it to a cache and only delete it when the cache fills.
|
||||
static void manageExpensive(SkiaObject<Object> object) {
|
||||
registerCleanupCallback();
|
||||
expensiveCache.add(object);
|
||||
}
|
||||
|
||||
/// Marks that [cache] has overflown its maximum size and show be resized.
|
||||
static void markCacheForResize(SkiaObjectCache cache) {
|
||||
registerCleanupCallback();
|
||||
if (cachesToResize.contains(cache)) {
|
||||
return;
|
||||
}
|
||||
cachesToResize.add(cache);
|
||||
}
|
||||
|
||||
/// Cleans up managed Skia memory.
|
||||
static void postFrameCleanUp() {
|
||||
if (resurrectableObjects.isEmpty && cachesToResize.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < resurrectableObjects.length; i++) {
|
||||
final SkiaObject<Object> object = resurrectableObjects[i];
|
||||
object.delete();
|
||||
object.didDelete();
|
||||
}
|
||||
resurrectableObjects.clear();
|
||||
|
||||
for (int i = 0; i < cachesToResize.length; i++) {
|
||||
final SkiaObjectCache cache = cachesToResize[i];
|
||||
cache.resize();
|
||||
}
|
||||
cachesToResize.clear();
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,10 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../util.dart';
|
||||
import 'canvaskit_api.dart';
|
||||
import 'skia_object_cache.dart';
|
||||
import 'native_memory.dart';
|
||||
|
||||
class CkVertices extends ManagedSkiaObject<SkVertices> implements ui.Vertices {
|
||||
class CkVertices implements ui.Vertices {
|
||||
factory CkVertices(
|
||||
ui.VertexMode mode,
|
||||
List<ui.Offset> positions, {
|
||||
@@ -82,48 +81,31 @@ class CkVertices extends ManagedSkiaObject<SkVertices> implements ui.Vertices {
|
||||
this._textureCoordinates,
|
||||
this._colors,
|
||||
this._indices,
|
||||
);
|
||||
|
||||
final SkVertexMode _mode;
|
||||
final Float32List _positions;
|
||||
final Float32List? _textureCoordinates;
|
||||
final Uint32List? _colors;
|
||||
final Uint16List? _indices;
|
||||
|
||||
@override
|
||||
SkVertices createDefault() {
|
||||
return canvasKit.MakeVertices(
|
||||
) {
|
||||
final SkVertices skVertices = canvasKit.MakeVertices(
|
||||
_mode,
|
||||
_positions,
|
||||
_textureCoordinates,
|
||||
_colors,
|
||||
_indices,
|
||||
);
|
||||
_ref = UniqueRef<SkVertices>(this, skVertices, 'Vertices');
|
||||
}
|
||||
|
||||
@override
|
||||
SkVertices resurrect() {
|
||||
return createDefault();
|
||||
}
|
||||
final SkVertexMode _mode;
|
||||
final Float32List _positions;
|
||||
final Float32List? _textureCoordinates;
|
||||
final Uint32List? _colors;
|
||||
final Uint16List? _indices;
|
||||
late final UniqueRef<SkVertices> _ref;
|
||||
|
||||
@override
|
||||
void delete() {
|
||||
rawSkiaObject?.delete();
|
||||
}
|
||||
|
||||
bool _disposed = false;
|
||||
SkVertices get skiaObject => _ref.nativeObject;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
delete();
|
||||
_disposed = true;
|
||||
_ref.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get debugDisposed {
|
||||
if (assertionsEnabled) {
|
||||
return _disposed;
|
||||
}
|
||||
throw StateError('Vertices.debugDisposed is only avialalbe when asserts are enabled.');
|
||||
}
|
||||
bool get debugDisposed => _ref.isDisposed;
|
||||
}
|
||||
|
||||
@@ -43,9 +43,6 @@ void testMain() {
|
||||
|
||||
test('renders using non-recording canvas if weak refs are supported',
|
||||
() async {
|
||||
expect(browserSupportsFinalizationRegistry, isTrue,
|
||||
reason: 'This test specifically tests non-recording canvas, which '
|
||||
'only works if FinalizationRegistry is available.');
|
||||
final CkPictureRecorder recorder = CkPictureRecorder();
|
||||
final CkCanvas canvas = recorder.beginRecording(kDefaultRegion);
|
||||
expect(canvas.runtimeType, CkCanvas);
|
||||
@@ -55,38 +52,6 @@ void testMain() {
|
||||
recorder.endRecording(),
|
||||
region: kDefaultRegion,
|
||||
);
|
||||
// Safari does not support weak refs (FinalizationRegistry).
|
||||
// This test should be revisited when Safari ships weak refs.
|
||||
// TODO(yjbanov): skip Firefox due to a crash: https://github.com/flutter/flutter/issues/86632
|
||||
}, skip: isSafari || isFirefox);
|
||||
|
||||
test('renders using a recording canvas if weak refs are not supported',
|
||||
() async {
|
||||
browserSupportsFinalizationRegistry = false;
|
||||
final CkPictureRecorder recorder = CkPictureRecorder();
|
||||
final CkCanvas canvas = recorder.beginRecording(kDefaultRegion);
|
||||
expect(canvas, isA<RecordingCkCanvas>());
|
||||
drawTestPicture(canvas);
|
||||
|
||||
final CkPicture originalPicture = recorder.endRecording();
|
||||
await matchPictureGolden('canvaskit_picture.png', originalPicture, region: kDefaultRegion);
|
||||
|
||||
final ByteData originalPixels =
|
||||
(await (await originalPicture.toImage(50, 50)).toByteData())!;
|
||||
|
||||
// Test that a picture restored from a snapshot looks the same.
|
||||
final CkPictureSnapshot? snapshot = canvas.pictureSnapshot;
|
||||
expect(snapshot, isNotNull);
|
||||
final SkPicture restoredSkPicture = snapshot!.toPicture();
|
||||
expect(restoredSkPicture, isNotNull);
|
||||
final CkPicture restoredPicture = CkPicture(
|
||||
restoredSkPicture, const ui.Rect.fromLTRB(0, 0, 50, 50), snapshot);
|
||||
final ByteData restoredPixels =
|
||||
(await (await restoredPicture.toImage(50, 50)).toByteData())!;
|
||||
|
||||
await matchPictureGolden('canvaskit_picture.png', restoredPicture, region: kDefaultRegion);
|
||||
expect(restoredPixels.buffer.asUint8List(),
|
||||
originalPixels.buffer.asUint8List());
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/51237
|
||||
@@ -885,9 +850,7 @@ void testMain() {
|
||||
|
||||
await matchGoldenFile('cross_overlay_resources.png', region: const ui.Rect.fromLTRB(0, 0, 100, 100));
|
||||
});
|
||||
|
||||
// TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520
|
||||
}, skip: isSafari || isFirefox);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> testSampleText(String language, String text,
|
||||
|
||||
@@ -1438,10 +1438,6 @@ void _canvasTests() {
|
||||
});
|
||||
|
||||
test('toImage.toByteData', () async {
|
||||
// Pretend that FinalizationRegistry is supported, so we can run this
|
||||
// test in older browsers (the test will use a TestCollector instead of
|
||||
// ProductionCollector)
|
||||
browserSupportsFinalizationRegistry = true;
|
||||
final SkPictureRecorder otherRecorder = SkPictureRecorder();
|
||||
final SkCanvas otherCanvas = otherRecorder
|
||||
.beginRecording(Float32List.fromList(<double>[0, 0, 1, 1]));
|
||||
@@ -1450,7 +1446,7 @@ void _canvasTests() {
|
||||
SkPaint()..setColorInt(0xAAFFFFFF),
|
||||
);
|
||||
final CkPicture picture =
|
||||
CkPicture(otherRecorder.finishRecordingAsPicture(), null, null);
|
||||
CkPicture(otherRecorder.finishRecordingAsPicture(), null);
|
||||
final CkImage image = await picture.toImage(1, 1) as CkImage;
|
||||
final ByteData rawData =
|
||||
await image.toByteData();
|
||||
|
||||
@@ -11,38 +11,20 @@ import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
/// Used in tests instead of [ProductionCollector] to control Skia object
|
||||
/// collection explicitly, and to prevent leaks across tests.
|
||||
///
|
||||
/// See [TestCollector] for usage.
|
||||
late TestCollector testCollector;
|
||||
|
||||
const MethodCodec codec = StandardMethodCodec();
|
||||
|
||||
/// Common test setup for all CanvasKit unit-tests.
|
||||
void setUpCanvasKitTest() {
|
||||
setUpAll(() async {
|
||||
expect(renderer, isA<CanvasKitRenderer>(), reason: 'This test must run in CanvasKit mode.');
|
||||
debugResetBrowserSupportsFinalizationRegistry();
|
||||
debugDisableFontFallbacks = false;
|
||||
await initializeEngine(assetManager: WebOnlyMockAssetManager());
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
testCollector = TestCollector();
|
||||
Collector.debugOverrideCollector(testCollector);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
testCollector.cleanUpAfterTest();
|
||||
debugResetBrowserSupportsFinalizationRegistry();
|
||||
HtmlViewEmbedder.instance.debugClear();
|
||||
SurfaceFactory.instance.debugClear();
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
debugResetBrowserSupportsFinalizationRegistry();
|
||||
});
|
||||
}
|
||||
|
||||
/// Utility function for CanvasKit tests to draw pictures without
|
||||
@@ -55,127 +37,6 @@ CkPicture paintPicture(
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
class _TestFinalizerRegistration {
|
||||
_TestFinalizerRegistration(this.wrapper, this.deletable, this.stackTrace);
|
||||
|
||||
final Object wrapper;
|
||||
final SkDeletable deletable;
|
||||
final StackTrace stackTrace;
|
||||
}
|
||||
|
||||
class _TestCollection {
|
||||
_TestCollection(this.deletable, this.stackTrace);
|
||||
|
||||
final SkDeletable deletable;
|
||||
final StackTrace stackTrace;
|
||||
}
|
||||
|
||||
/// Provides explicit synchronous API for collecting Skia objects in tests.
|
||||
///
|
||||
/// [ProductionCollector] relies on `FinalizationRegistry` and timers to
|
||||
/// delete Skia objects, which makes it more precise and efficient. However,
|
||||
/// it also makes it unpredictable. For example, an object created in one
|
||||
/// test may be collected while running another test because the timing is
|
||||
/// subject to browser-specific GC scheduling.
|
||||
///
|
||||
/// Tests should use [collectNow] and [collectAfterTest] to trigger collections.
|
||||
class TestCollector implements Collector {
|
||||
final List<_TestFinalizerRegistration> _activeRegistrations =
|
||||
<_TestFinalizerRegistration>[];
|
||||
final List<_TestFinalizerRegistration> _collectedRegistrations =
|
||||
<_TestFinalizerRegistration>[];
|
||||
|
||||
final List<_TestCollection> _pendingCollections = <_TestCollection>[];
|
||||
final List<_TestCollection> _completedCollections = <_TestCollection>[];
|
||||
|
||||
@override
|
||||
void register(Object wrapper, SkDeletable deletable) {
|
||||
_activeRegistrations.add(
|
||||
_TestFinalizerRegistration(wrapper, deletable, StackTrace.current),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void collect(SkDeletable deletable) {
|
||||
_pendingCollections.add(
|
||||
_TestCollection(deletable, StackTrace.current),
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all Skia objects scheduled for collection.
|
||||
void collectNow() {
|
||||
for (final _TestCollection collection in _pendingCollections) {
|
||||
late final _TestFinalizerRegistration? activeRegistration;
|
||||
for (final _TestFinalizerRegistration registration in _activeRegistrations) {
|
||||
if (identical(registration.deletable, collection.deletable)) {
|
||||
activeRegistration = registration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (activeRegistration == null) {
|
||||
late final _TestFinalizerRegistration? collectedRegistration;
|
||||
for (final _TestFinalizerRegistration registration
|
||||
in _collectedRegistrations) {
|
||||
if (identical(registration.deletable, collection.deletable)) {
|
||||
collectedRegistration = registration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (collectedRegistration == null) {
|
||||
fail(
|
||||
'Attempted to collect an object that was never registered for finalization.\n'
|
||||
'The collection was requested here:\n'
|
||||
'${collection.stackTrace}');
|
||||
} else {
|
||||
final _TestCollection firstCollection = _completedCollections
|
||||
.firstWhere((_TestCollection completedCollection) {
|
||||
return identical(
|
||||
completedCollection.deletable, collection.deletable);
|
||||
});
|
||||
fail(
|
||||
'Attempted to collect an object that was previously collected.\n'
|
||||
'The object was registered for finalization here:\n'
|
||||
'${collection.stackTrace}\n\n'
|
||||
'The first collection was requested here:\n'
|
||||
'${firstCollection.stackTrace}\n\n'
|
||||
'The second collection was requested here:\n'
|
||||
'${collection.stackTrace}',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_collectedRegistrations.add(activeRegistration);
|
||||
_activeRegistrations.remove(activeRegistration);
|
||||
_completedCollections.add(collection);
|
||||
if (!collection.deletable.isDeleted()) {
|
||||
collection.deletable.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
_pendingCollections.clear();
|
||||
}
|
||||
|
||||
/// Deletes all Skia objects with registered finalizers.
|
||||
///
|
||||
/// This also deletes active objects that have not been scheduled for
|
||||
/// collection, to prevent objects leaking across tests.
|
||||
void cleanUpAfterTest() {
|
||||
for (final _TestCollection collection in _pendingCollections) {
|
||||
if (!collection.deletable.isDeleted()) {
|
||||
collection.deletable.delete();
|
||||
}
|
||||
}
|
||||
for (final _TestFinalizerRegistration registration in _activeRegistrations) {
|
||||
if (!registration.deletable.isDeleted()) {
|
||||
registration.deletable.delete();
|
||||
}
|
||||
}
|
||||
_activeRegistrations.clear();
|
||||
_collectedRegistrations.clear();
|
||||
_pendingCollections.clear();
|
||||
_completedCollections.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> matchSceneGolden(String goldenFile, LayerScene scene, {
|
||||
required ui.Rect region,
|
||||
}) async {
|
||||
|
||||
@@ -95,12 +95,9 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
|
||||
// Disallow double-dispose.
|
||||
expect(() => image.dispose(), throwsAssertionError);
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('CkAnimatedImage remembers last animation position after resurrection', () async {
|
||||
browserSupportsFinalizationRegistry = false;
|
||||
|
||||
test('CkAnimatedImage iterates frames correctly', () async {
|
||||
final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test');
|
||||
expect(image.frameCount, 3);
|
||||
expect(image.repetitionCount, -1);
|
||||
@@ -109,16 +106,8 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
await expectFrameData(frame1, <int>[255, 0, 0, 255]);
|
||||
final ui.FrameInfo frame2 = await image.getNextFrame();
|
||||
await expectFrameData(frame2, <int>[0, 255, 0, 255]);
|
||||
|
||||
// Pretend that the image is temporarily deleted.
|
||||
image.delete();
|
||||
image.didDelete();
|
||||
|
||||
// Check that we got the 3rd frame after resurrection.
|
||||
final ui.FrameInfo frame3 = await image.getNextFrame();
|
||||
await expectFrameData(frame3, <int>[0, 0, 255, 255]);
|
||||
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('CkImage toString', () {
|
||||
@@ -128,7 +117,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
final CkImage image = CkImage(skImage);
|
||||
expect(image.toString(), '[1×1]');
|
||||
image.dispose();
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('CkImage can be explicitly disposed of', () {
|
||||
@@ -144,7 +132,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
|
||||
// Disallow double-dispose.
|
||||
expect(() => image.dispose(), throwsAssertionError);
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('CkImage can be explicitly disposed of when cloned', () async {
|
||||
@@ -163,22 +150,18 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
expect(image.isCloneOf(clone), isTrue);
|
||||
expect(box.isDisposed, isFalse);
|
||||
|
||||
testCollector.collectNow();
|
||||
expect(skImage.isDeleted(), isFalse);
|
||||
image.dispose();
|
||||
expect(box.refCount, 1);
|
||||
expect(box.isDisposed, isFalse);
|
||||
|
||||
testCollector.collectNow();
|
||||
expect(skImage.isDeleted(), isFalse);
|
||||
clone.dispose();
|
||||
expect(box.refCount, 0);
|
||||
expect(box.isDisposed, isTrue);
|
||||
|
||||
testCollector.collectNow();
|
||||
expect(skImage.isDeleted(), isTrue);
|
||||
expect(box.debugGetStackTraces().length, 0);
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('CkImage toByteData', () async {
|
||||
@@ -188,7 +171,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
final CkImage image = CkImage(skImage);
|
||||
expect((await image.toByteData()).lengthInBytes, greaterThan(0));
|
||||
expect((await image.toByteData(format: ui.ImageByteFormat.png)).lengthInBytes, greaterThan(0));
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('toByteData with decodeImageFromPixels on videoFrame formats', () async {
|
||||
@@ -262,7 +244,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
// The precise PNG encoding is browser-specific, but we can check the file
|
||||
// signature.
|
||||
expect(detectContentType(png.buffer.asUint8List()), 'image/png');
|
||||
testCollector.collectNow();
|
||||
// TODO(hterkelsen): Firefox and Safari do not currently support ImageDecoder.
|
||||
}, skip: isFirefox || isSafari);
|
||||
|
||||
@@ -282,7 +263,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
final ui.Image image = (await codec.getNextFrame()).image;
|
||||
expect(image.height, 1);
|
||||
expect(image.width, 1);
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('instantiateImageCodec respects target image size',
|
||||
@@ -312,8 +292,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
image.dispose();
|
||||
codec.dispose();
|
||||
}
|
||||
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('instantiateImageCodec with multi-frame image does not support targetWidth/targetHeight',
|
||||
@@ -339,8 +317,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
expect(image.height, 1);
|
||||
image.dispose();
|
||||
codec.dispose();
|
||||
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('skiaInstantiateWebImageCodec throws exception on request error',
|
||||
@@ -361,7 +337,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
'https://flutter.dev/docs/development/platform-integration/web-images',
|
||||
);
|
||||
}
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('skiaInstantiateWebImageCodec throws exception on HTTP error',
|
||||
@@ -377,7 +352,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
'Server response code: 404',
|
||||
);
|
||||
}
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('skiaInstantiateWebImageCodec includes URL in the error for malformed image',
|
||||
@@ -409,7 +383,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
|
||||
);
|
||||
}
|
||||
}
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('Reports error when failing to decode empty image data', () async {
|
||||
@@ -831,7 +804,6 @@ void _testCkAnimatedImage() {
|
||||
// The precise PNG encoding is browser-specific, but we can check the file
|
||||
// signature.
|
||||
expect(detectContentType(png!.buffer.asUint8List()), 'image/png');
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('CkAnimatedImage toByteData(RGBA)', () async {
|
||||
@@ -847,7 +819,6 @@ void _testCkAnimatedImage() {
|
||||
expect(rgba, isNotNull);
|
||||
expect(rgba!.buffer.asUint8List(), expectedColors[i]);
|
||||
}
|
||||
testCollector.collectNow();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -867,7 +838,6 @@ void _testCkBrowserImageDecoder() {
|
||||
// The precise PNG encoding is browser-specific, but we can check the file
|
||||
// signature.
|
||||
expect(detectContentType(png!.buffer.asUint8List()), 'image/png');
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('ImageDecoder toByteData(RGBA)', () async {
|
||||
@@ -886,7 +856,6 @@ void _testCkBrowserImageDecoder() {
|
||||
expect(rgba, isNotNull);
|
||||
expect(rgba!.buffer.asUint8List(), expectedColors[i]);
|
||||
}
|
||||
testCollector.collectNow();
|
||||
});
|
||||
|
||||
test('ImageDecoder expires after inactivity', () async {
|
||||
@@ -930,7 +899,6 @@ void _testCkBrowserImageDecoder() {
|
||||
final ui.FrameInfo frame3 = await image.getNextFrame();
|
||||
await expectFrameData(frame3, <int>[0, 0, 255, 255]);
|
||||
|
||||
testCollector.collectNow();
|
||||
debugRestoreWebDecoderExpireDuration();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ void testMain() {
|
||||
});
|
||||
|
||||
test('CkContourMeasure iteration', () {
|
||||
browserSupportsFinalizationRegistry = false;
|
||||
final ui.Path path = ui.Path();
|
||||
expect(path, isA<CkPath>());
|
||||
path.addRect(const ui.Rect.fromLTRB(0, 0, 10, 10));
|
||||
@@ -112,7 +111,6 @@ void testMain() {
|
||||
});
|
||||
|
||||
test('CkContourMeasure index', () {
|
||||
browserSupportsFinalizationRegistry = false;
|
||||
final ui.Path path = ui.Path();
|
||||
expect(path, isA<CkPath>());
|
||||
path.addRect(const ui.Rect.fromLTRB(0, 0, 10, 10));
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import '../common/matchers.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
@@ -20,19 +21,17 @@ void testMain() {
|
||||
group('CkPicture', () {
|
||||
setUpCanvasKitTest();
|
||||
|
||||
group('in browsers that do not support FinalizationRegistry', () {
|
||||
group('lifecycle', () {
|
||||
test('can be disposed of manually', () {
|
||||
browserSupportsFinalizationRegistry = false;
|
||||
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder);
|
||||
canvas.drawPaint(ui.Paint());
|
||||
final CkPicture picture = recorder.endRecording() as CkPicture;
|
||||
expect(picture.rawSkiaObject, isNotNull);
|
||||
expect(picture.skiaObject, isNotNull);
|
||||
expect(picture.debugDisposed, isFalse);
|
||||
picture.debugCheckNotDisposed('Test.'); // must not throw
|
||||
picture.dispose();
|
||||
expect(picture.rawSkiaObject, isNull);
|
||||
expect(() => picture.skiaObject, throwsA(isAssertionError));
|
||||
expect(picture.debugDisposed, isTrue);
|
||||
|
||||
StateError? actualError;
|
||||
@@ -50,36 +49,6 @@ void testMain() {
|
||||
'The picture has been disposed. '
|
||||
'When the picture was disposed the stack trace was:\n'
|
||||
));
|
||||
|
||||
// Emulate SkiaObjectCache deleting the picture
|
||||
picture.delete();
|
||||
picture.didDelete();
|
||||
expect(picture.rawSkiaObject, isNull);
|
||||
|
||||
// A Picture that's been disposed of can no longer be resurrected
|
||||
expect(() => picture.resurrect(), throwsStateError);
|
||||
expect(() => picture.toImage(10, 10), throwsStateError);
|
||||
expect(() => picture.dispose(), throwsStateError);
|
||||
});
|
||||
|
||||
test('can be deleted by SkiaObjectCache', () {
|
||||
browserSupportsFinalizationRegistry = false;
|
||||
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder);
|
||||
canvas.drawPaint(ui.Paint());
|
||||
final CkPicture picture = recorder.endRecording() as CkPicture;
|
||||
expect(picture.rawSkiaObject, isNotNull);
|
||||
|
||||
// Emulate SkiaObjectCache deleting the picture
|
||||
picture.delete();
|
||||
picture.didDelete();
|
||||
expect(picture.rawSkiaObject, isNull);
|
||||
|
||||
// Deletion is softer than disposal. An object may still be resurrected
|
||||
// if it was deleted prematurely.
|
||||
expect(picture.debugDisposed, isFalse);
|
||||
expect(picture.resurrect(), isNotNull);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:js/js.dart';
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('skia_objects_cache', () {
|
||||
_tests();
|
||||
});
|
||||
}
|
||||
|
||||
void _tests() {
|
||||
SkiaObjects.maximumCacheSize = 4;
|
||||
|
||||
setUpCanvasKitTest();
|
||||
|
||||
setUp(() async {
|
||||
// Pretend the browser does not support FinalizationRegistry so we can test the
|
||||
// resurrection logic.
|
||||
browserSupportsFinalizationRegistry = false;
|
||||
});
|
||||
|
||||
group(ManagedSkiaObject, () {
|
||||
test('implements create, cache, delete, resurrect, delete lifecycle', () {
|
||||
final FakeRasterizer fakeRasterizer = FakeRasterizer();
|
||||
CanvasKitRenderer.instance.rasterizer = fakeRasterizer;
|
||||
|
||||
// Trigger first create
|
||||
final TestSkiaObject testObject = TestSkiaObject();
|
||||
expect(SkiaObjects.resurrectableObjects.single, testObject);
|
||||
expect(testObject.createDefaultCount, 1);
|
||||
expect(testObject.resurrectCount, 0);
|
||||
expect(testObject.deleteCount, 0);
|
||||
|
||||
// Check that the getter does not have side-effects
|
||||
final SkPaint skiaObject1 = testObject.skiaObject;
|
||||
expect(skiaObject1, isNotNull);
|
||||
expect(SkiaObjects.resurrectableObjects.single, testObject);
|
||||
expect(testObject.createDefaultCount, 1);
|
||||
expect(testObject.resurrectCount, 0);
|
||||
expect(testObject.deleteCount, 0);
|
||||
|
||||
// Trigger first delete
|
||||
SkiaObjects.postFrameCleanUp();
|
||||
expect(SkiaObjects.resurrectableObjects, isEmpty);
|
||||
expect(fakeRasterizer.addPostFrameCallbackCount, 1);
|
||||
expect(testObject.createDefaultCount, 1);
|
||||
expect(testObject.resurrectCount, 0);
|
||||
expect(testObject.deleteCount, 1);
|
||||
|
||||
// Trigger resurrect
|
||||
final SkPaint skiaObject2 = testObject.skiaObject;
|
||||
expect(skiaObject2, isNotNull);
|
||||
expect(skiaObject2, isNot(same(skiaObject1)));
|
||||
expect(SkiaObjects.resurrectableObjects.single, testObject);
|
||||
expect(fakeRasterizer.addPostFrameCallbackCount, 1);
|
||||
expect(testObject.createDefaultCount, 1);
|
||||
expect(testObject.resurrectCount, 1);
|
||||
expect(testObject.deleteCount, 1);
|
||||
|
||||
// Trigger final delete
|
||||
SkiaObjects.postFrameCleanUp();
|
||||
expect(SkiaObjects.resurrectableObjects, isEmpty);
|
||||
expect(fakeRasterizer.addPostFrameCallbackCount, 1);
|
||||
expect(testObject.createDefaultCount, 1);
|
||||
expect(testObject.resurrectCount, 1);
|
||||
expect(testObject.deleteCount, 2);
|
||||
});
|
||||
|
||||
test('is added to SkiaObjects cache if expensive', () {
|
||||
final TestSkiaObject object1 = TestSkiaObject(isExpensive: true);
|
||||
expect(SkiaObjects.expensiveCache.length, 1);
|
||||
expect(SkiaObjects.expensiveCache.debugContains(object1), isTrue);
|
||||
|
||||
final TestSkiaObject object2 = TestSkiaObject(isExpensive: true);
|
||||
expect(SkiaObjects.expensiveCache.length, 2);
|
||||
expect(SkiaObjects.expensiveCache.debugContains(object2), isTrue);
|
||||
|
||||
SkiaObjects.postFrameCleanUp();
|
||||
expect(SkiaObjects.expensiveCache.length, 2);
|
||||
expect(SkiaObjects.expensiveCache.debugContains(object1), isTrue);
|
||||
expect(SkiaObjects.expensiveCache.debugContains(object2), isTrue);
|
||||
|
||||
/// Add 3 more objects to the cache to overflow it.
|
||||
TestSkiaObject(isExpensive: true);
|
||||
TestSkiaObject(isExpensive: true);
|
||||
TestSkiaObject(isExpensive: true);
|
||||
expect(SkiaObjects.expensiveCache.length, 5);
|
||||
expect(SkiaObjects.cachesToResize.length, 1);
|
||||
|
||||
SkiaObjects.postFrameCleanUp();
|
||||
expect(object1.deleteCount, 1);
|
||||
expect(object2.deleteCount, 1);
|
||||
expect(SkiaObjects.expensiveCache.length, 3);
|
||||
expect(SkiaObjects.expensiveCache.debugContains(object1), isFalse);
|
||||
expect(SkiaObjects.expensiveCache.debugContains(object2), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class TestSkDeletableMock {
|
||||
static int deleteCount = 0;
|
||||
|
||||
bool isDeleted() => _isDeleted;
|
||||
bool _isDeleted = false;
|
||||
|
||||
void delete() {
|
||||
expect(_isDeleted, isFalse,
|
||||
reason:
|
||||
'CanvasKit does not allow deleting the same object more than once.');
|
||||
_isDeleted = true;
|
||||
deleteCount++;
|
||||
}
|
||||
|
||||
JsConstructor get constructor => TestJsConstructor(name:
|
||||
'TestSkDeletable'.toJS);
|
||||
}
|
||||
|
||||
@JS()
|
||||
@anonymous
|
||||
@staticInterop
|
||||
class TestSkDeletable implements SkDeletable {
|
||||
factory TestSkDeletable() {
|
||||
final TestSkDeletableMock mock = TestSkDeletableMock();
|
||||
return TestSkDeletable._(
|
||||
isDeleted: () { return mock.isDeleted(); }.toJS,
|
||||
delete: () { return mock.delete(); }.toJS,
|
||||
constructor: mock.constructor);
|
||||
}
|
||||
|
||||
external factory TestSkDeletable._({
|
||||
JSFunction isDeleted,
|
||||
JSFunction delete,
|
||||
JsConstructor constructor});
|
||||
}
|
||||
|
||||
@JS()
|
||||
@anonymous
|
||||
@staticInterop
|
||||
class TestJsConstructor implements JsConstructor {
|
||||
external factory TestJsConstructor({JSString name});
|
||||
}
|
||||
|
||||
class TestSkiaObject extends ManagedSkiaObject<SkPaint> {
|
||||
TestSkiaObject({this.isExpensive = false});
|
||||
|
||||
int createDefaultCount = 0;
|
||||
int resurrectCount = 0;
|
||||
int deleteCount = 0;
|
||||
|
||||
final bool isExpensive;
|
||||
|
||||
@override
|
||||
SkPaint createDefault() {
|
||||
createDefaultCount++;
|
||||
return SkPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
SkPaint resurrect() {
|
||||
resurrectCount++;
|
||||
return SkPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void delete() {
|
||||
rawSkiaObject?.delete();
|
||||
deleteCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isResurrectionExpensive => isExpensive;
|
||||
}
|
||||
|
||||
class FakeRasterizer implements Rasterizer {
|
||||
int addPostFrameCallbackCount = 0;
|
||||
|
||||
@override
|
||||
void addPostFrameCallback(VoidCallback callback) {
|
||||
addPostFrameCallbackCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
CompositorContext get context => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void draw(LayerTree layerTree) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
void setSkiaResourceCacheMaxBytes(int bytes) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
void debugRunPostFrameCallbacks() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class TestSelfManagedObject extends SkiaObject<TestSkDeletable> {
|
||||
TestSkDeletable? _skiaObject = TestSkDeletable();
|
||||
|
||||
@override
|
||||
void delete() {
|
||||
_skiaObject!.delete();
|
||||
}
|
||||
|
||||
@override
|
||||
void didDelete() {
|
||||
_skiaObject = null;
|
||||
}
|
||||
|
||||
@override
|
||||
TestSkDeletable get skiaObject => throw UnimplementedError();
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../common/matchers.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
@@ -20,11 +21,11 @@ void testMain() {
|
||||
group('Vertices', () {
|
||||
setUpCanvasKitTest();
|
||||
|
||||
test('can be constructed, drawn, and deleted', () {
|
||||
test('can be constructed, drawn, and disposed of', () {
|
||||
final CkVertices vertices = _testVertices();
|
||||
expect(vertices, isA<CkVertices>());
|
||||
expect(vertices.createDefault(), isNotNull);
|
||||
expect(vertices.resurrect(), isNotNull);
|
||||
expect(vertices.skiaObject, isNotNull);
|
||||
expect(vertices.debugDisposed, isFalse);
|
||||
|
||||
final CkPictureRecorder recorder = CkPictureRecorder();
|
||||
final CkCanvas canvas =
|
||||
@@ -34,7 +35,9 @@ void testMain() {
|
||||
ui.BlendMode.srcOver,
|
||||
CkPaint(),
|
||||
);
|
||||
vertices.delete();
|
||||
vertices.dispose();
|
||||
expect(vertices.debugDisposed, isTrue);
|
||||
expect(() => vertices.skiaObject, throwsA(isAssertionError));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user