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

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

相关推荐
twl4 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅7 分钟前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人15 分钟前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼19 分钟前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空22 分钟前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_27 分钟前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus34 分钟前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空38 分钟前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范
子兮曰43 分钟前
深入理解滑块验证码:那些你不知道的防破解机制
前端·javascript·canvas
会一丢丢蝶泳的咻狗1 小时前
Sass实现,蛇形流动布局
前端·css