登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

WebGPU 入门如何把计算结果回传页面:GPUBuffer、mapAsync 与设备丢失处理

来源:17golang原创

时间:2026-08-30 02:45:50 381浏览 收藏

页面里的 WebGPU 计算已经提交,JavaScript 却拿不到结果,问题通常不在 WGSL 公式,而在“GPU 可写的缓冲区”和“JavaScript 可读的缓冲区”没有分开。可靠的回传路径是:计算写入 output,命令编码器把它复制到 stagingBuffer,再等待 mapAsync 完成,读取 getMappedRange,最后 unmap

把 GPU 结果交给页面,关键不是直接读取 output,而是用 GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST 创建 stagingBuffer,并严格遵守映射、读取、解除映射的顺序。

要点速览

  • output 负责接收计算结果,stagingBuffer 负责被 JavaScript 读取。
  • copyBufferToBuffer 完成 GPU 内部搬运,mapAsync 完成异步可读准备。
  • 设备丢失后,旧设备创建的 buffer、pipeline 等资源都要跟着重建。

先分清 output 和 stagingBuffer 的职责

WebGPU 的 buffer 使用权限是在 device.createBuffer 时声明的。计算输出通常需要 STORAGE | COPY_SRC,因为计算着色器要写入它,之后还要作为复制源;给 JavaScript 读取的 stagingBuffer 则使用 MAP_READ | COPY_DSTMAP_READ 不能和任意用途混搭,先把职责分开,后面的命令才不会在验证阶段失败。

const output = device.createBuffer({
  size: BUFFER_SIZE,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});

const stagingBuffer = device.createBuffer({
  size: BUFFER_SIZE,
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});

这里的 output 不是 JavaScript 变量的普通数组,它是 GPU 设备管理的存储;stagingBuffer 也不是“自动同步的镜像”。两者之间必须明确编码一次复制命令。

WebGPU 中 output 写入计算结果后,copyBufferToBuffer 把结果送入 stagingBuffer 的真实数据路径示意图

copyBufferToBuffer 之后再请求 mapAsync

计算 pass 结束后,用 GPUCommandEncoder.copyBufferToBuffer 把 output 的结果复制到 stagingBuffer,再通过 device.queue.submit 提交命令。提交后才能对 stagingBuffer 调用 mapAsync;映射完成前,getMappedRange 不能使用。

const commandEncoder = device.createCommandEncoder();

commandEncoder.copyBufferToBuffer(
  output,
  0,
  stagingBuffer,
  0,
  BUFFER_SIZE,
);

device.queue.submit([commandEncoder.finish()]);

await stagingBuffer.mapAsync(
  GPUMapMode.READ,
  0,
  BUFFER_SIZE,
);
const mapped = stagingBuffer.getMappedRange(0, BUFFER_SIZE);
const data = mapped.slice(0);
stagingBuffer.unmap();
console.log(new Float32Array(data));

mapAsync 返回的 Promise 兑现,只说明这个范围已经可以被访问;读取完成后要先复制出自己的 ArrayBuffer,再调用 unmap。缓冲区保持 mapped 状态时,不能继续作为 GPU 命令的目标。

WebGPU 从 copyBufferToBuffer 到 queue.submit、mapAsync、getMappedRange、unmap 的回传控制流示意图

改动的重点是映射边界,不是把等待写得更长

如果映射范围从偏移量开始,offset 必须满足 API 的对齐要求,size 也必须落在 buffer 范围内。最稳妥的入门代码是从 0 映射完整的 BUFFER_SIZE,并让它保持 4 字节对齐;等数据结构稳定后,再为多个结果分段设计偏移量。

不要用一个同时承担 STORAGE、MAP_READ 等互相冲突职责的 buffer 代替两个 buffer,也不要在 mapAsync 尚未兑现时读取 mapped range。遇到 OperationError,优先检查 usage、offset、size 和当前 mapState,不要盲目增加延迟。

设备丢失后要重建整套 GPU 资源

GPUDevice.lost 是一个在设备生命周期内保持 pending、设备丢失时才兑现的 Promise。浏览器资源管理或驱动更新都可能触发设备丢失;如果原因不是主动调用 GPUDevice.destroy,应用可以重新申请 device,但旧 device 创建的 buffer、pipeline 和 bind group 不能继续复用。

device.lost.then((info) => {
  console.error(`WebGPU device was lost: ${info.message}`);
  if (info.reason !== "destroyed") {
    init();
  }
});

恢复函数应重新执行适配器申请、设备创建、buffer 创建和 pipeline 初始化。页面层只保留“正在恢复”的状态,避免用户连续点击时把旧设备上的命令再次提交。

常见问题

为什么不能直接对 output 调用 mapAsync?

output 通常使用 STORAGE | COPY_SRC,并没有 MAP_READ 权限。把结果复制到使用 MAP_READ | COPY_DST 创建的 stagingBuffer,才是常见的 GPU 到 JavaScript 回传路径。

mapAsync 完成后还要调用 unmap 吗?

要。读取 mapped range 并复制出数据后调用 unmap,缓冲区才重新对 GPU 命令可用;不要把 mapped 状态当成永久读取模式。

设备丢失后只重新 requestDevice 可以吗?

不够。旧设备拥有的 buffer、pipeline、bind group 等资源都需要重建,恢复流程应回到初始化阶段,并暂时阻止旧任务继续提交。

最小验收:看得到结果,也能处理失效设备

一个可交付的 WebGPU 回传实现,至少应能在控制台看到从 output 复制来的数值,确认 mapAsync 只在提交复制命令后调用,并在读取后执行 unmap。再人为触发设备失效或模拟恢复路径,确认 GPUDevice.lost 会重新创建整套资源。这样排查时,问题会落在清晰的 buffer 权限、命令顺序或设备生命周期上,而不是一段无法定位的“GPU 没返回结果”。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>