Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
72 changes: 55 additions & 17 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 _NativeArrayType {}

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

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

@JS('window.flutterCanvasKit.Malloc')
external SkFloat32List _mallocFloat32List(
_NativeFloat32ArrayType float32ListType,
int size,
);
external Object _mallocList(_NativeArrayType nativeArrayType, int size);

/// Allocates a [Float32List] backed by WASM memory, managed by
/// a [SkFloat32List].
///
/// To free the allocated array use [freeFloat32List].
/// To free the allocated array use [freeList].
SkFloat32List mallocFloat32List(int size) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's document if size is in bytes or element count. Also, if it's the latter, then perhaps length is a more idiomatic name for the parameter (and I think sizeInBytes is more common for the other one).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll check what CanvasKit calls the parameter.

return _mallocFloat32List(_nativeFloat32ArrayType, size);
return _mallocList(_nativeFloat32ArrayType, size) as SkFloat32List;
}

/// Allocates a [Uint32List] backed by WASM memory, managed by
/// a [SkUint32List].
///
/// To free the allocated array use [freeList].
SkUint32List mallocUint32List(int size) {
return _mallocList(_nativeUint32ArrayType, size) 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 freeList(_MallocObj list);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's strange for a public function to take a private type. Can _MallocObj be public?

Also, nit: avoid abbreviations - MallocObject.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: do we need List in the name? In the future, we could extend the API to include other malloc-ed types, including non-List kinds, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We don't need List in the name. I'll remove it.


@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 @@ -1584,7 +1622,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].
/// [freeList].
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);
freeList(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);
freeList(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);
freeList(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);
freeList(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);
freeList(encodedPoints);
});

test('addRRect', () {
Expand Down