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

[web] Make Canvaskit's malloc more useful #38130

Merged
merged 3 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 58 additions & 20 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1267,34 +1267,46 @@ Float32List toSkColorStops(List<double>? colorStops) {
return skColorStops;
}

@JS('Float32Array')
external _NativeFloat32ArrayType get _nativeFloat32ArrayType;

@JS()
@staticInterop
class _NativeFloat32ArrayType {}
abstract class _NativeType {}

@JS('Float32Array')
external _NativeType get _nativeFloat32ArrayType;

@JS('Uint32Array')
external _NativeType get _nativeUint32ArrayType;

@JS('window.flutterCanvasKit.Malloc')
external SkFloat32List _mallocFloat32List(
_NativeFloat32ArrayType float32ListType,
int size,
);
external Object _malloc(_NativeType nativeType, int length);

/// Allocates a [Float32List] of [length] elements, backed by WASM memory,
/// managed by a [SkFloat32List].
///
/// To free the allocated array use [free].
SkFloat32List mallocFloat32List(int length) {
return _malloc(_nativeFloat32ArrayType, length) as SkFloat32List;
}

/// Allocates a [Float32List] backed by WASM memory, managed by
/// a [SkFloat32List].
/// Allocates a [Uint32List] of [length] elements, backed by WASM memory,
/// managed by a [SkUint32List].
///
/// To free the allocated array use [freeFloat32List].
SkFloat32List mallocFloat32List(int size) {
return _mallocFloat32List(_nativeFloat32ArrayType, size);
/// To free the allocated array use [free].
SkUint32List mallocUint32List(int length) {
return _malloc(_nativeUint32ArrayType, length) as SkUint32List;
}

/// Frees the WASM memory occupied by a [SkFloat32List].
/// Frees the WASM memory occupied by a [SkFloat32List] or [SkUint32List].
///
/// The [list] is no longer usable after calling this function.
///
/// Use this function to free lists owned by the engine.
@JS('window.flutterCanvasKit.Free')
external void freeFloat32List(SkFloat32List list);
external void free(MallocObj list);

@JS()
@staticInterop
abstract class MallocObj {}

/// Wraps a [Float32List] backed by WASM memory.
///
Expand All @@ -1303,19 +1315,45 @@ external void freeFloat32List(SkFloat32List list);
/// that's attached to the current WASM memory block.
@JS()
@staticInterop
class SkFloat32List {}
class SkFloat32List extends MallocObj {}

extension SkFloat32ListExtension on SkFloat32List {
/// The number of objects this pointer refers to.
external int length;

/// Returns the [Float32List] object backed by WASM memory.
///
/// Do not reuse the returned list across multiple WASM function/method
/// Do not reuse the returned array across multiple WASM function/method
/// invocations that may lead to WASM memory to grow. When WASM memory
/// grows the [Float32List] object becomes "detached" and is no longer
/// usable. Instead, call this method every time you need to read from
/// grows, the returned [Float32List] object becomes "detached" and is no
/// longer usable. Instead, call this method every time you need to read from
/// or write to the list.
external Float32List toTypedArray();
}

/// Wraps a [Uint32List] backed by WASM memory.
///
/// This wrapper is necessary because the raw [Uint32List] will get detached
/// when WASM grows its memory. Call [toTypedArray] to get a new instance
/// that's attached to the current WASM memory block.
@JS()
@staticInterop
class SkUint32List extends MallocObj {}

extension SkUint32ListExtension on SkUint32List {
/// The number of objects this pointer refers to.
external int length;

/// Returns the [Uint32List] object backed by WASM memory.
///
/// Do not reuse the returned array across multiple WASM function/method
/// invocations that may lead to WASM memory to grow. When WASM memory
/// grows, the returned [Uint32List] object becomes "detached" and is no
/// longer usable. Instead, call this method every time you need to read from
/// or write to the list.
external Uint32List toTypedArray();
}

/// Writes [color] information into the given [skColor] buffer.
Float32List _populateSkColor(SkFloat32List skColor, ui.Color color) {
final Float32List array = skColor.toTypedArray();
Expand Down Expand Up @@ -1585,7 +1623,7 @@ Float32List toOuterSkRect(ui.RRect rrect) {
/// Uses `CanvasKit.Malloc` to allocate storage for the points in the WASM
/// memory to avoid unnecessary copying. Unless CanvasKit takes ownership of
/// the list the returned list must be explicitly freed using
/// [freeMallocedFloat32List].
/// [free].
SkFloat32List toMallocedSkPoints(List<ui.Offset> points) {
final int len = points.length;
final SkFloat32List skPoints = mallocFloat32List(len * 2);
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ class CanvasKitCanvas implements ui.Canvas {
pointMode,
skPoints.toTypedArray(),
);
freeFloat32List(skPoints);
free(skPoints);
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/canvaskit/path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class CkPath extends ManagedSkiaObject<SkPath> implements ui.Path {
assert(points != null);
final SkFloat32List encodedPoints = toMallocedSkPoints(points);
skiaObject.addPoly(encodedPoints.toTypedArray(), close);
freeFloat32List(encodedPoints);
free(encodedPoints);
}

@override
Expand Down
35 changes: 32 additions & 3 deletions lib/web_ui/test/canvaskit/canvaskit_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -533,11 +533,40 @@ void _imageFilterTests() {
}

void _mallocTests() {
test('SkFloat32List', () {
test('$SkFloat32List', () {
final List<SkFloat32List> lists = <SkFloat32List>[];

for (int size = 0; size < 1000; size++) {
final SkFloat32List skList = mallocFloat32List(4);
expect(skList, isNotNull);
expect(skList.toTypedArray().length, 4);
expect(skList.toTypedArray(), hasLength(4));
lists.add(skList);
}

for (final SkFloat32List skList in lists) {
// toTypedArray() still works.
expect(() => skList.toTypedArray(), returnsNormally);
free(skList);
// toTypedArray() throws after free.
expect(() => skList.toTypedArray(), throwsA(isA<Error>()));
}
});
test('$SkUint32List', () {
final List<SkUint32List> lists = <SkUint32List>[];

for (int size = 0; size < 1000; size++) {
final SkUint32List skList = mallocUint32List(4);
expect(skList, isNotNull);
expect(skList.toTypedArray(), hasLength(4));
lists.add(skList);
}

for (final SkUint32List skList in lists) {
// toTypedArray() still works.
expect(() => skList.toTypedArray(), returnsNormally);
free(skList);
// toTypedArray() throws after free.
expect(() => skList.toTypedArray(), throwsA(isA<Error>()));
}
});
}
Expand Down Expand Up @@ -812,7 +841,7 @@ void _pathTests() {
ui.Offset(10, 10),
]);
path.addPoly(encodedPoints.toTypedArray(), true);
freeFloat32List(encodedPoints);
free(encodedPoints);
});

test('addRRect', () {
Expand Down