使用 Web Worker + SharedArrayBuffer 并行实现视频实时滤镜效果

之前分别用 JSGolang WebAssemblyRust WebAssembly 实现过视频实时滤镜效果,证明了 WebAssembly 的性能确实要更好一些。但其实当处理图像这种较大的数据时,另一个比较常用的做法是分块并行处理,这就要用到 Web Worker 了。

思路也很简单,在主进程中我们把图像分成好几个块,然后传给各个 Web Worker 进行处理,然后接受处理后的结果拼接起来。不过在进程间传递大量数据显然是一个比较低效的方式,所以这里我们使用 SharedArrayBuffer,让主进程跟各 Web Worker 之间共享一块内存,这样就只需要传递 SharedArrayBuffer 的引用就可以了:

js 复制代码
// main.js
const worker = new Worker('...')
const sharedArrayBuffer = new SharedArrayBuffer(imageData.buffer.byteLength)
new Uint8ClampedArray(sharedArrayBuffer).set(imageData)

worker.postMessage({start, end, width, sharedArrayBuffer})

// webworker.js
onmessage = async (e: MessageEvent) => {
  const {
    data: {sharedArrayBuffer, start, end, width},
  } = e
  const uint8ClampedArray = new Uint8ClampedArray(sharedArrayBuffer)

  // update sharedArrayBuffer using uint8ClampedArray
  for (let i = start; i < end; i++) {
    for (let j = 0; j < width; j++) {
      uint8ClampedArray[i * width + j] = 100 // Red
      uint8ClampedArray[i * width + j + 1] = 100 // Green
      uint8ClampedArray[i * width + j + 2] = 100 // Blue
    }
  }
}

上述代码中的 startend 分别表示当前 Web Worker 需要处理的起始和结束行号,width 表示 Canvas 宽度。用图来表示就像这样:

把 Canvas 宽高设大一点后,对比之前的 JS 版本,效果还是很明显的:

到这,你肯定会想到,如果在 Web Worker 里面再用上 WebAssembly 那岂不是效果更炸裂?

我们来试试,只需要在 Web Worker 里面再把数据共享给 WASM 处理,然后再使用修改后的 memory.buffer 来更新 SharedArrayBuffer 对应的块就可以了:

js 复制代码
onmessage = async (e: MessageEvent) => {
  const {
    data: {sharedArrayBuffer, start, end, width},
  } = e
  const ptr = return_pointer()
  const uint8ClampedArrayForMemBuf = new Uint8ClampedArray(memory.buffer)
  const uint8ClampedArrayForSharedBuf = new Uint8ClampedArray(sharedArrayBuffer)
  // Sync the block data to WASM
  uint8ClampedArrayForMemBuf.set(
    uint8ClampedArrayForSharedBuf.slice(start * width * 4, end * width * 4)
  )
  // Image convolution
  filter_shared_mem(
    ptr,
    width,
    end - start,
    new Float32Array([].concat(...kernel))
  )
  // Update the block in SharedArrayBuffer using modified memory.buffer
  uint8ClampedArrayForSharedBuf.set(
    new Uint8ClampedArray(memory.buffer).slice(ptr, (end - start) * width * 4),
    start * width * 4
  )
}

这里需要注意的地方是从 SharedArrayBuffer 中同步数据到 memory.buffer 以及更新 SharedArrayBuffer 中的数据时,都只需要关注前 Web Worker 所属的块就可以了。

看一下效果,确实又提升了:

而且,通过分块的方式,还解决了另外一个内存相关的问题。为什么这么说呢,因为增大了 canvas 的宽高,相当于增加了需要处理的图像数据大小,此时如果切到图中的 Rust WebAssembly (Shared Memory) 会报 RangeError: offset is out of bounds 错误,原因在于图像大小已经超过了 WebAssembly 中初始的 Memory 的大小。虽然 MDN 上介绍了可以使用 memory.grow() 方法来扩大内存,但是还是会有别的错误,暂时未能解决。

欢迎关注公众号"前端游"

相关推荐
黑风风24 分钟前
Vue 项目中引入 Axios 详解
前端·javascript·vue.js
shiming887936 分钟前
Vue.js 的 Mixins
前端·javascript·vue.js
前端-卡布达37 分钟前
Vscode 中新手小白使用 Open With Live Server 的坑
前端·javascript·ide·vscode
深情废杨杨38 分钟前
前端vue-3种生命周期,只能在各自的领域使用
前端
寒山李白1 小时前
VuePress搭建文档网站/个人博客(详细配置)主题配置-侧边栏配置
前端·vue.js·vue·博客·vuepress·网站
二十雨辰2 小时前
[uni-app]小兔鲜-01项目起步
前端·javascript·vue.js·uni-app
kkkAloha2 小时前
面经 | JS
开发语言·前端·javascript
芥子沫2 小时前
Safari-常用快捷键(IPadOS版本)
前端·safari
wgc891783 小时前
Zabbix短信告警示例
前端·chrome·zabbix
逝缘~3 小时前
uni-icons自定义图标详细步骤及踩坑经历
前端·javascript·css·vue.js·uni-app·html