微信小程序中已经支持了 WebAssembly ,但他的文档语焉不详,也无法直接使用 opencv 编译出来的 js 胶水文件。我们需要一些简单的改动,让他可以工作。

具体流程

使用 WXWebAssembly 代替 WebAssembly

在微信小程序中,需要把所有的 WebAssembly 都替换成 WXWebAssembly。我们可以直接 opencv 编译出的 js 胶水文件中的所有内容

替换完成后,要调整 WXWebAssembly 中没有的 API ,如 function abort() 中,有对 WXWebAssembly.RuntimeError 的引用,我们可以直接改写

- var e = new WXWebAssembly.RuntimeError(what);
- throw e
+ throw what

调整 wasm 文件的加载方式

由于微信只支持使用本地的 wasm 文件加载方式,所以我们可以调整 js 文件中的异步加载的过程,简化代码,可以解决大部分问题

在 instantiateArrayBuffer 方法中,原:

  function instantiateArrayBuffer(receiver) {
    return getBinaryPromise().then(function (binary) {
      return WebAssembly.instantiate(binary, info)
    }).then(function (instance) {
      return instance
    }).then(receiver, function (reason) {
      err("failed to asynchronously prepare wasm: " + reason);
      abort(reason)
    })
  }

修改后:

  function instantiateArrayBuffer(receiver) {
    return WXWebAssembly.instantiate("your-lib.wasm", info)
      .then(function(wasm) { 
        receiver(wasm);
       })
  }

他的调用者 instantiateAsync 也可以简化成

  function instantiateAsync() {
    return instantiateArrayBuffer(receiveInstantiationResult) 
  }

删除一些微信小程序不支持的方法

微信中有许多不支持的 BOM 会被 WebAssembly 的胶水代码引用,我们都要改成替代方法,如

  DB_NAME: () => {
    return "EM_FS_" + window.location.pathname
  },

改成

  DB_NAME: () => {
    return "EM_FS_WORKER"
  },

还有

  if (ENVIRONMENT_IS_WORKER) {
    scriptDirectory = self.location.href
  } else if (typeof document != "undefined" && document.currentScript) {
    scriptDirectory = document.currentScript.src
  }
  if (scriptDirectory.indexOf("blob:") !== 0) {
    scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1)
  } else {
    scriptDirectory = ""
  }

检查以后,这里完全可以简化成

  scriptDirectory = ""

另外在真机上,不支持 new Function 这样的写法,所以 createNamedFunction 就要简化成

function createNamedFunction(name, body) {
  return ()=>{}
}

同理,真机没有 performance 对象,我们要替换其为 Date 方法

- } else _emscripten_get_now = (() => performance.now());
+ } else _emscripten_get_now = (() => new Date().getTime());

输出模块,更好调用

删除文件底部的 run 函数,并 export default Module

 - run();
 + export default Module;

这样在调用处,我们可以这样

const wasm = require('./lib').default

wasm.onRuntimeInitialized = ()=> {
    // your callback
};

wasm.run(); // start loading

使用时的事项

微信上 onCameraFrame 的方法返回的 frame.data 是一个 ArrayBuffer ,是一个 RGBA 的数据,我们在实际使用中经常会通过 Worker.postMessage 传递给 Worker 运算来提升效率。

WebAssembly 中无法直接访问到 js 空间中的内存,我们需要手动申请内存,并把 ArrayBuffer 的值传进去,这里需要一个 Uint8Array 的转化,我们才能在 WebAssembly 空间中使用

const { width, height, data } = payload;

let buffer = wasm._malloc(4 * width * height); 
const newData = new Uint8Array(data);
wasm.HEAPU8.set(newData, buffer);

wasm._analyse(buffer);

// free
wasm._free(buffer);

另外,在实际使用时,由于真机的运行内存比 PC 少太多,所以我们要打开内存增涨 flag 以防止 OOM

-sALLOW_MEMORY_GROWTH=1