mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Use dart:_wasm constructs to avoid dependence on WebAssembly.Function (flutter/engine#46388)
Use the newly exposed functionality in `dart:_wasm` to fix up two different hacks we have: 1) When creating an image from an image source, use `wasm:import` instead of `@Native` and pass the image source directly as an externref. (Direct wasm binding instead of a JS interop shim, yay). 2) When binding the surface callback, previously we were wrapping the callback in a JS function, and then using `WebAssembly.Function` to create a wasm function wrapper around that. Now, we can create a `WasmFuncRef` that is a direct reference to a dart function and pass that over. Now there are no intermediary JavaScript layers when skwasm calls back to us, and we no longer are dependent on the type reflection flag in Chrome. This fixes https://github.com/flutter/flutter/issues/134556
This commit is contained in:
parent
1848abe6c0
commit
2c58573690
@ -64,7 +64,8 @@ window.onload = async function () {
|
||||
const skwasmInstance = await skwasm();
|
||||
window._flutter_skwasmInstance = skwasmInstance;
|
||||
resolve({
|
||||
"skwasm": skwasmInstance.asm ?? skwasmInstance.wasmExports,
|
||||
"skwasm": skwasmInstance.wasmExports,
|
||||
"skwasmWrapper": skwasmInstance,
|
||||
"ffi": {
|
||||
"memory": skwasmInstance.wasmMemory,
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
@DefaultAsset('skwasm')
|
||||
library skwasm_impl;
|
||||
|
||||
import 'dart:_wasm';
|
||||
import 'dart:ffi';
|
||||
import 'dart:js_interop';
|
||||
|
||||
@ -39,45 +40,27 @@ external ImageHandle imageCreateFromPixels(
|
||||
int rowByteCount,
|
||||
);
|
||||
|
||||
// We actually want this function to look something like this:
|
||||
//
|
||||
// @Native<ImageHandle Function(
|
||||
// WasmExternRef,
|
||||
// Int,
|
||||
// Int,
|
||||
// SurfaceHandle,
|
||||
// )>(symbol: 'image_createFromTextureSource', isLeaf: true)
|
||||
// external ImageHandle imageCreateFromTextureSource(
|
||||
// JSAny textureSource,
|
||||
// int width,
|
||||
// int height,
|
||||
// SurfaceHandle handle,
|
||||
// );
|
||||
//
|
||||
// However, this doesn't work because we cannot use extern refs as part of @Native
|
||||
// annotations currently. For now, we can use JS interop to expose this function
|
||||
// instead.
|
||||
extension SkwasmImageExtension on SkwasmInstance {
|
||||
@JS('wasmExports.image_createFromTextureSource')
|
||||
external JSNumber imageCreateFromTextureSource(
|
||||
JSAny textureSource,
|
||||
JSNumber width,
|
||||
JSNumber height,
|
||||
JSNumber surfaceHandle,
|
||||
);
|
||||
}
|
||||
// We use a wasm import directly here instead of @Native since this uses an externref
|
||||
// in the function signature.
|
||||
ImageHandle imageCreateFromTextureSource(
|
||||
JSAny frame,
|
||||
int width,
|
||||
int height,
|
||||
SurfaceHandle handle
|
||||
) => ImageHandle.fromAddress(
|
||||
skwasmInstance.imageCreateFromTextureSource(
|
||||
frame,
|
||||
width.toJS,
|
||||
height.toJS,
|
||||
handle.address.toJS,
|
||||
).toDartInt
|
||||
imageCreateFromTextureSourceImpl(
|
||||
externRefForJSAny(frame),
|
||||
width.toWasmI32(),
|
||||
height.toWasmI32(),
|
||||
handle.address.toWasmI32(),
|
||||
).toIntUnsigned()
|
||||
);
|
||||
@pragma('wasm:import', 'skwasm.image_createFromTextureSource')
|
||||
external WasmI32 imageCreateFromTextureSourceImpl(
|
||||
WasmExternRef? frame,
|
||||
WasmI32 width,
|
||||
WasmI32 height,
|
||||
WasmI32 surfaceHandle,
|
||||
);
|
||||
|
||||
@Native<Void Function(ImageHandle)>(symbol:'image_ref', isLeaf: true)
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:_wasm';
|
||||
import 'dart:js_interop';
|
||||
|
||||
@JS()
|
||||
@ -17,37 +18,11 @@ extension WebAssemblyMemoryExtension on WebAssemblyMemory {
|
||||
class SkwasmInstance {}
|
||||
|
||||
extension SkwasmInstanceExtension on SkwasmInstance {
|
||||
external JSNumber getEmptyTableSlot();
|
||||
|
||||
// The function here *must* be a directly exported wasm function, not a
|
||||
// JavaScript function. If you actually need to add a JavaScript function,
|
||||
// use `addFunction` instead.
|
||||
external void setWasmTableEntry(JSNumber index, JSAny function);
|
||||
|
||||
external JSNumber addFunction(WebAssemblyFunction function);
|
||||
external void removeFunction(JSNumber functionPointer);
|
||||
|
||||
external WebAssemblyMemory get wasmMemory;
|
||||
}
|
||||
|
||||
@JS('window._flutter_skwasmInstance')
|
||||
external SkwasmInstance get skwasmInstance;
|
||||
|
||||
@JS()
|
||||
@staticInterop
|
||||
@anonymous
|
||||
class WebAssemblyFunctionType {
|
||||
external factory WebAssemblyFunctionType({
|
||||
required JSArray parameters,
|
||||
required JSArray results,
|
||||
});
|
||||
}
|
||||
|
||||
@JS('WebAssembly.Function')
|
||||
@staticInterop
|
||||
class WebAssemblyFunction {
|
||||
external factory WebAssemblyFunction(
|
||||
WebAssemblyFunctionType functionType,
|
||||
JSFunction function
|
||||
);
|
||||
}
|
||||
@pragma('wasm:import', 'skwasmWrapper.addFunction')
|
||||
external WasmI32 addFunction(WasmFuncRef function);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:_wasm';
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:js_interop';
|
||||
@ -11,7 +12,52 @@ import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
@pragma('wasm:export')
|
||||
WasmVoid callbackHandler(WasmI32 callbackId, WasmI32 context, WasmExternRef? jsContext) {
|
||||
// Actually hide this call behind whether skwasm is enabled. Otherwise, the SkwasmCallbackHandler
|
||||
// won't actually be tree-shaken, and we end up with skwasm imports in non-skwasm builds.
|
||||
if (FlutterConfiguration.flutterWebUseSkwasm) {
|
||||
SkwasmCallbackHandler.instance.handleCallback(callbackId, context, jsContext);
|
||||
}
|
||||
return WasmVoid();
|
||||
}
|
||||
|
||||
// This class handles callbacks coming from Skwasm by keeping a map of callback IDs to Completers
|
||||
class SkwasmCallbackHandler {
|
||||
SkwasmCallbackHandler._withCallbackPointer(this.callbackPointer);
|
||||
|
||||
factory SkwasmCallbackHandler._() {
|
||||
final WasmFuncRef wasmFunction = WasmFunction<WasmVoid Function(WasmI32, WasmI32, WasmExternRef?)>.fromFunction(callbackHandler);
|
||||
final int functionIndex = addFunction(wasmFunction).toIntUnsigned();
|
||||
return SkwasmCallbackHandler._withCallbackPointer(
|
||||
OnRenderCallbackHandle.fromAddress(functionIndex)
|
||||
);
|
||||
}
|
||||
static SkwasmCallbackHandler instance = SkwasmCallbackHandler._();
|
||||
|
||||
final OnRenderCallbackHandle callbackPointer;
|
||||
final Map<CallbackId, Completer<JSAny>> _pendingCallbacks = <int, Completer<JSAny>>{};
|
||||
|
||||
// Returns a future that will resolve when Skwasm calls back with the given callbackID
|
||||
Future<JSAny> registerCallback(int callbackId) {
|
||||
final Completer<JSAny> completer = Completer<JSAny>();
|
||||
_pendingCallbacks[callbackId] = completer;
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void handleCallback(WasmI32 callbackId, WasmI32 context, WasmExternRef? jsContext) {
|
||||
// Skwasm can either callback with a JS object (an externref) or it can call back
|
||||
// with a simple integer, which usually refers to a pointer on its heap. In order
|
||||
// to coerce these into a single type, we just make the completers take a JSAny
|
||||
// that either contains the JS object or a JSNumber that contains the integer value.
|
||||
final Completer<JSAny> completer = _pendingCallbacks.remove(callbackId.toIntUnsigned())!;
|
||||
if (!jsContext.isNull) {
|
||||
completer.complete(jsContext!.toJS);
|
||||
} else {
|
||||
completer.complete(context.toIntUnsigned().toJS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SkwasmSurface {
|
||||
factory SkwasmSurface() {
|
||||
@ -25,32 +71,16 @@ class SkwasmSurface {
|
||||
|
||||
SkwasmSurface._fromHandle(this.handle) : threadId = surfaceGetThreadId(handle);
|
||||
final SurfaceHandle handle;
|
||||
OnRenderCallbackHandle _callbackHandle = nullptr;
|
||||
final Map<CallbackId, Completer<JSAny>> _pendingCallbacks = <int, Completer<JSAny>>{};
|
||||
|
||||
final int threadId;
|
||||
|
||||
void _initialize() {
|
||||
final WebAssemblyFunction wasmFunction = WebAssemblyFunction(
|
||||
WebAssemblyFunctionType(
|
||||
parameters: <JSString>[
|
||||
'i32'.toJS,
|
||||
'i32'.toJS,
|
||||
'externref'.toJS
|
||||
].toJS,
|
||||
results: <JSString>[].toJS
|
||||
),
|
||||
_callbackHandler.toJS,
|
||||
);
|
||||
_callbackHandle = OnRenderCallbackHandle.fromAddress(
|
||||
skwasmInstance.addFunction(wasmFunction).toDartInt,
|
||||
);
|
||||
surfaceSetCallbackHandler(handle, _callbackHandle);
|
||||
surfaceSetCallbackHandler(handle, SkwasmCallbackHandler.instance.callbackPointer);
|
||||
}
|
||||
|
||||
Future<DomImageBitmap> renderPicture(SkwasmPicture picture) async {
|
||||
final int callbackId = surfaceRenderPicture(handle, picture.handle);
|
||||
final DomImageBitmap bitmap = (await _registerCallback(callbackId)) as DomImageBitmap;
|
||||
final DomImageBitmap bitmap = (await SkwasmCallbackHandler.instance.registerCallback(callbackId)) as DomImageBitmap;
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@ -60,7 +90,7 @@ class SkwasmSurface {
|
||||
image.handle,
|
||||
format.index,
|
||||
);
|
||||
final int context = (await _registerCallback(callbackId) as JSNumber).toDartInt;
|
||||
final int context = (await SkwasmCallbackHandler.instance.registerCallback(callbackId) as JSNumber).toDartInt;
|
||||
final SkDataHandle dataHandle = SkDataHandle.fromAddress(context);
|
||||
final int byteCount = skDataGetSize(dataHandle);
|
||||
final Pointer<Uint8> dataPointer = skDataGetConstPointer(dataHandle).cast<Uint8>();
|
||||
@ -72,23 +102,7 @@ class SkwasmSurface {
|
||||
return ByteData.sublistView(output);
|
||||
}
|
||||
|
||||
Future<JSAny> _registerCallback(int callbackId) {
|
||||
final Completer<JSAny> completer = Completer<JSAny>();
|
||||
_pendingCallbacks[callbackId] = completer;
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void _callbackHandler(JSNumber callbackId, JSNumber context, JSAny? jsContext) {
|
||||
final Completer<JSAny> completer = _pendingCallbacks.remove(callbackId.toDartInt)!;
|
||||
if (jsContext.isUndefinedOrNull) {
|
||||
completer.complete(context);
|
||||
} else {
|
||||
completer.complete(jsContext);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
surfaceDestroy(handle);
|
||||
skwasmInstance.removeFunction(_callbackHandle.address.toJS);
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,6 +178,9 @@ List<String> getExtraImportsForLibrary(String libraryName) {
|
||||
extraImports.add(entry.value);
|
||||
}
|
||||
}
|
||||
if (libraryName == 'skwasm_impl') {
|
||||
extraImports.add("import 'dart:_wasm';");
|
||||
}
|
||||
return extraImports;
|
||||
}
|
||||
|
||||
|
||||
@ -184,6 +184,7 @@ void printSomething() {
|
||||
"import 'dart:_web_unicode';",
|
||||
"import 'dart:_web_test_fonts';",
|
||||
"import 'dart:_web_locale_keymap' as locale_keymap;",
|
||||
"import 'dart:_wasm';",
|
||||
]);
|
||||
|
||||
// Other libraries (should not have extra imports).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user