Unlock GPU computing with WebGPU
App Services 进阶 2m

用 WebGPU 解锁 GPU 计算能力

Unlock GPU computing with WebGPU

2025年6月9日

在 Apple 官方观看视频

一句话判断

WebGPU 在 Apple 平台上几乎是一对一映射 Metal 的——如果你熟悉 Metal,上手 WebGPU 的障碍接近于零;如果你只熟悉 WebGL,那 compute shader 的能力会让你重新思考”浏览器里能做什么”。

这场 Session 讲了什么

WebGPU 不仅是 WebGL 的升级版,更是浏览器中唯一能做通用 GPU 计算的 API。Session 从三个方面展开:API 概览、着色器开发、性能优化。

API 层面,WebGPU 是一个扁平的接口体系,核心概念和 Metal 高度对齐:GPUDevice 对应 MTLDeviceGPUBindGroup 对应 Metal 的 argument buffer,render/compute pipeline 对应 MTLRenderPipelineState/MTLComputePipelineState。着色语言 WGSL 是从零设计的安全着色语言,支持 vertex、fragment 和 compute 三种程序类型。Session 用一个粒子动画示例展示了从顶点着色、片段着色到 compute shader 物理模拟的完整流程。

性能优化部分是亮点:使用 f16(half-precision float)减少内存带宽压力;用 render bundle 缓存绘制命令避免每帧重复验证;用 dynamic offset 合并 bind group 减少 Metal 对象数量;尽量合并 command buffer 和 pass 以减少同步开销。

值得深挖的点

Compute shader:WebGL 做不到的事

WebGL 不支持 compute shader,这意味着在浏览器中做通用 GPU 计算(机器学习推理、物理模拟、数据处理)以前是不可能的。WebGPU 的 compute shader 打开了这扇门。Session 中的粒子系统示例:100,000 个粒子的物理模拟(重力、速度、衰减)全部在 GPU 上完成,结果写入 buffer,JavaScript 通过 readBuffer 读取。

这和 Web Workers 做并行计算有本质区别:GPU 的并行粒度是 thousands of threads,远超 CPU 核心数。对于有大量相同计算但不同数据的场景(矩阵运算、图像处理、模拟),compute shader 可以做到 JavaScript 无法企及的性能。Three.js 等库已经内置了 WebGPU 后端,如果你的需求是 3D 渲染,用高层库就能直接受益。

Apple 平台上的性能细节

Session 特别强调了 Apple 平台(tile-based deferred renderer)上的最佳实践。f16 不只是”节省一半内存”那么简单——在 iOS 和 visionOS 上,使用 f16 或压缩格式可以显著降低内存压力,避免系统终止你的进程。即使只是用 f16 存储数据再立即解包到 f32,仍然能获得内存收益。

render bundle 的价值在于它把验证从每帧执行推迟到创建时执行一次。WebGPU 需要确保所有读写都在边界内,这通常意味着每帧大量验证。但 render bundle 创建时验证一次,之后重复执行。底层映射到 Metal 的 indirect command buffer,性能收益是实打实的。

dynamic offset 是另一个减少 Metal 对象的技巧。与其为每个实例创建一个 64 字节的 bind group(10 个实例 = 10 个 Metal buffer),不如创建一个 640 字节的大 buffer,用 dynamic offset 在 setBindGroup 时切换。这样只创建一个 Metal buffer,节省 9 个。

代码片段

初始化 WebGPU 设备和 canvas:

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice({
    requiredFeatures: ['shader-f16']  // 启用半精度浮点
});

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('webgpu');
ctx.configure({ device, format: 'bgra8unorm' });

创建 compute pipeline 和粒子 buffer:

const shaderModule = device.createShaderModule({
    code: `
        @group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
        @compute @workgroup_size(64)
        fn main(@builtin(global_invocation_id) id: vec3u) {
            var p = particles[id.x];
            p.velocity.y -= 0.001; // 重力
            p.position += p.velocity;
            particles[id.x] = p;
        }
    `
});

const pipeline = device.createComputePipeline({
    layout: 'auto',
    compute: { module: shaderModule, entryPoint: 'main' }
});

用 dynamic offset 合并 bind group:

// 一个大 buffer 替代多个小 buffer
const buffer = device.createBuffer({
    size: 640, // 10 × 64 字节
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});

// 绘制时通过 offset 切换实例数据
pass.setBindGroup(0, bindGroup, [instanceIndex * 64]);

最佳实践

建议新项目优先使用 WebGPU 而不是 WebGL。WebGPU 在所有 Apple 平台上都通过 Metal 实现,性能和功能都优于 WebGL。如果你的应用对性能敏感,请从一开始就考虑使用 f16 数据类型。

优先使用 render bundle 缓存重复的绘制命令。这在渲染静态或半静态内容(UI 元素、不频繁更新的 3D 场景)时收益最大。

避免频繁更新 index/indirect buffer。这类 buffer 的更新需要额外的边界验证,成本比普通 buffer 高。只在需要时更新,优先使用 read-only 访问模式。

还有什么值得关注

  • shader-f16 扩展在所有 Apple 设备上都可用,但属于可选特性,在其他平台上需要检查支持情况。
  • WebGPU 通过 Metal 实现,支持 Mac、iPhone、iPad 和 Vision Pro。
  • three.js 已经支持 WebGPU 后端,可以直接用来做 3D 渲染而不需要自己写底层代码。
应用服务 Safari与Web