From 247785410a2b431223dabdab64b32447f672f25b Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Wed, 7 Dec 2022 16:25:21 -0500 Subject: [PATCH 1/2] [web] Make Canvaskit's malloc more useful --- .../src/engine/canvaskit/canvaskit_api.dart | 72 ++++++++++++++----- .../engine/canvaskit/canvaskit_canvas.dart | 2 +- lib/web_ui/lib/src/engine/canvaskit/path.dart | 2 +- .../test/canvaskit/canvaskit_api_test.dart | 35 ++++++++- 4 files changed, 89 insertions(+), 22 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index 9f413f73ca87c..7116e0d27be9b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -1267,34 +1267,46 @@ Float32List toSkColorStops(List? 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) { - 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); + +@JS() +@staticInterop +abstract class _MallocObj {} /// Wraps a [Float32List] backed by WASM memory. /// @@ -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(); @@ -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 points) { final int len = points.length; final SkFloat32List skPoints = mallocFloat32List(len * 2); diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart index 444860bae5649..743fa1742ae89 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart @@ -326,7 +326,7 @@ class CanvasKitCanvas implements ui.Canvas { pointMode, skPoints.toTypedArray(), ); - freeFloat32List(skPoints); + freeList(skPoints); } @override diff --git a/lib/web_ui/lib/src/engine/canvaskit/path.dart b/lib/web_ui/lib/src/engine/canvaskit/path.dart index 3bbf4f90cb8b4..ccdeddb0d848a 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/path.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/path.dart @@ -89,7 +89,7 @@ class CkPath extends ManagedSkiaObject implements ui.Path { assert(points != null); final SkFloat32List encodedPoints = toMallocedSkPoints(points); skiaObject.addPoly(encodedPoints.toTypedArray(), close); - freeFloat32List(encodedPoints); + freeList(encodedPoints); } @override diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index 9a5784addac5c..3048c2906cb6d 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -533,11 +533,40 @@ void _imageFilterTests() { } void _mallocTests() { - test('SkFloat32List', () { + test('$SkFloat32List', () { + final List lists = []; + 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())); + } + }); + test('$SkUint32List', () { + final List lists = []; + + 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())); } }); } @@ -812,7 +841,7 @@ void _pathTests() { ui.Offset(10, 10), ]); path.addPoly(encodedPoints.toTypedArray(), true); - freeFloat32List(encodedPoints); + freeList(encodedPoints); }); test('addRRect', () { From 3d789f350cb773222238acfeced0138ec21f9446 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Wed, 14 Dec 2022 13:11:42 -0500 Subject: [PATCH 2/2] address review comments --- .../src/engine/canvaskit/canvaskit_api.dart | 38 +++++++++---------- .../engine/canvaskit/canvaskit_canvas.dart | 2 +- lib/web_ui/lib/src/engine/canvaskit/path.dart | 2 +- .../test/canvaskit/canvaskit_api_test.dart | 6 +-- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index 14bb44c264e82..fc226bfa7632f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -1269,31 +1269,31 @@ Float32List toSkColorStops(List? colorStops) { @JS() @staticInterop -abstract class _NativeArrayType {} +abstract class _NativeType {} @JS('Float32Array') -external _NativeArrayType get _nativeFloat32ArrayType; +external _NativeType get _nativeFloat32ArrayType; @JS('Uint32Array') -external _NativeArrayType get _nativeUint32ArrayType; +external _NativeType get _nativeUint32ArrayType; @JS('window.flutterCanvasKit.Malloc') -external Object _mallocList(_NativeArrayType nativeArrayType, int size); +external Object _malloc(_NativeType nativeType, int length); -/// Allocates a [Float32List] backed by WASM memory, managed by -/// a [SkFloat32List]. +/// Allocates a [Float32List] of [length] elements, backed by WASM memory, +/// managed by a [SkFloat32List]. /// -/// To free the allocated array use [freeList]. -SkFloat32List mallocFloat32List(int size) { - return _mallocList(_nativeFloat32ArrayType, size) as SkFloat32List; +/// To free the allocated array use [free]. +SkFloat32List mallocFloat32List(int length) { + return _malloc(_nativeFloat32ArrayType, length) as SkFloat32List; } -/// Allocates a [Uint32List] backed by WASM memory, managed by -/// a [SkUint32List]. +/// Allocates a [Uint32List] of [length] elements, 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; +/// 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] or [SkUint32List]. @@ -1302,11 +1302,11 @@ SkUint32List mallocUint32List(int size) { /// /// Use this function to free lists owned by the engine. @JS('window.flutterCanvasKit.Free') -external void freeList(_MallocObj list); +external void free(MallocObj list); @JS() @staticInterop -abstract class _MallocObj {} +abstract class MallocObj {} /// Wraps a [Float32List] backed by WASM memory. /// @@ -1315,7 +1315,7 @@ abstract class _MallocObj {} /// that's attached to the current WASM memory block. @JS() @staticInterop -class SkFloat32List extends _MallocObj {} +class SkFloat32List extends MallocObj {} extension SkFloat32ListExtension on SkFloat32List { /// The number of objects this pointer refers to. @@ -1338,7 +1338,7 @@ extension SkFloat32ListExtension on SkFloat32List { /// that's attached to the current WASM memory block. @JS() @staticInterop -class SkUint32List extends _MallocObj {} +class SkUint32List extends MallocObj {} extension SkUint32ListExtension on SkUint32List { /// The number of objects this pointer refers to. @@ -1623,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 -/// [freeList]. +/// [free]. SkFloat32List toMallocedSkPoints(List points) { final int len = points.length; final SkFloat32List skPoints = mallocFloat32List(len * 2); diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart index 743fa1742ae89..7dda561cded51 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart @@ -326,7 +326,7 @@ class CanvasKitCanvas implements ui.Canvas { pointMode, skPoints.toTypedArray(), ); - freeList(skPoints); + free(skPoints); } @override diff --git a/lib/web_ui/lib/src/engine/canvaskit/path.dart b/lib/web_ui/lib/src/engine/canvaskit/path.dart index ccdeddb0d848a..27479588f4b84 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/path.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/path.dart @@ -89,7 +89,7 @@ class CkPath extends ManagedSkiaObject implements ui.Path { assert(points != null); final SkFloat32List encodedPoints = toMallocedSkPoints(points); skiaObject.addPoly(encodedPoints.toTypedArray(), close); - freeList(encodedPoints); + free(encodedPoints); } @override diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index 3048c2906cb6d..cd76ddcbeb7af 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -546,7 +546,7 @@ void _mallocTests() { for (final SkFloat32List skList in lists) { // toTypedArray() still works. expect(() => skList.toTypedArray(), returnsNormally); - freeList(skList); + free(skList); // toTypedArray() throws after free. expect(() => skList.toTypedArray(), throwsA(isA())); } @@ -564,7 +564,7 @@ void _mallocTests() { for (final SkUint32List skList in lists) { // toTypedArray() still works. expect(() => skList.toTypedArray(), returnsNormally); - freeList(skList); + free(skList); // toTypedArray() throws after free. expect(() => skList.toTypedArray(), throwsA(isA())); } @@ -841,7 +841,7 @@ void _pathTests() { ui.Offset(10, 10), ]); path.addPoly(encodedPoints.toTypedArray(), true); - freeList(encodedPoints); + free(encodedPoints); }); test('addRRect', () {