使用 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() 方法来扩大内存,但是还是会有别的错误,暂时未能解决。

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

相关推荐
思想者001几秒前
Chrome访问https页面显示ERR_CERT_INVALID,且无法跳过继续访问
前端·chrome
x_chengqq3 小时前
前端批量下载文件
前端
捕鲸叉5 小时前
QT自定义工具条渐变背景颜色一例
开发语言·前端·c++·qt
傻小胖6 小时前
路由组件与一般组件的区别
前端·vue.js·react.js
Elena_Lucky_baby6 小时前
在Vue3项目中使用svg-sprite-loader
开发语言·前端·javascript
重生之搬砖忍者7 小时前
uniapp使用canvas生成订单小票图片
前端·javascript·canva可画
万水千山走遍TML7 小时前
console.log封装
前端·javascript·typescript·node·log·console·打印封装
阿雄不会写代码7 小时前
使用java springboot 使用 Redis 作为消息队列
前端·bootstrap·html
m0_748236587 小时前
【Nginx 】Nginx 部署前端 vue 项目
前端·vue.js·nginx
@C宝8 小时前
【前端面试题】前端中的两个外边距bug以及什么是BFC
前端·bug