WebGPU 入门:在浏览器中释放 GPU 计算能力

Author Avatar
via
发表:2026-07-14 09:02:31
修改:2026-07-14 09:02:30

引言

WebGPU 是下一代浏览器图形 API,被视为 WebGL 的继任者。与 WebGL 不同,WebGPU 不仅仅用于渲染,它还暴露了 GPU 的通用计算能力(GPGPU),让开发者可以在浏览器中直接利用 GPU 进行并行计算。这意味着从机器学习推理到物理模拟,许多原本需要在服务器端运行的工作负载,现在可以在用户的设备上完成。

本文将带你了解 WebGPU 的核心概念、与 WebGL 的关键区别、基本用法,以及一个实际的 GPU 计算示例。

WebGPU vs WebGL:不只是升级

WebGL 基于 OpenGL ES,设计于 2011 年前后,其架构深深植根于固定管线和旧式 GPU 编程模型。WebGPU 则基于现代图形 API(Vulkan、Metal、Direct3D 12),更贴近当代 GPU 的实际工作方式。

主要区别

  • 显式管线模型:WebGPU 要求开发者预先定义渲染管线(Pipeline),包括着色器、混合状态、拓扑结构等,然后复用。WebGL 则是在每帧设置状态,开销更大。
  • 命令编码:WebGPU 使用命令缓冲区(Command Buffer),先录制命令再一次性提交,减少 CPU-GPU 通信开销。
  • 通用计算支持:WebGPU 原生支持计算着色器(Compute Shader),无需借助 WebGL 的 GPGPU hack。
  • 类型安全:WebGPU 的 API 设计更加类型安全,WGSL(WebGPU Shading Language)比 GLSL 更加现代化。

核心概念速览

1. Adapter 与 Device

navigator.gpu.requestAdapter() 返回一个 Adapter,代表一个 GPU 设备的抽象。adapter.requestDevice() 则返回 Device,这是你与 GPU 交互的主要接口。你可以把 Device 理解为 GPU 的"句柄"。

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) throw new Error('WebGPU 不支持');
const device = await adapter.requestDevice();

2. Buffer

Buffer 是 GPU 上的内存块。你需要指定用途(如 Uniform、Storage、Vertex)和可见性(Vertex、Fragment、Compute 着色器是否可访问)。

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

3. Pipeline

管线分为渲染管线(RenderPipeline)和计算管线(ComputePipeline)。创建管线时需要提供着色器代码和布局信息。

4. Bind Group

Bind Group 是将 Buffer、Texture 等资源绑定到着色器的机制。你需要先创建 Bind Group Layout,再创建 Bind Group。

5. Command Encoder

命令编码器用于录制 GPU 命令。录制完成后调用 finish() 生成 CommandBuffer,再提交给队列执行。

实战:用 Compute Shader 做矩阵乘法

下面演示如何用 WebGPU 的计算着色器实现矩阵乘法。这是 GPGPU 的经典用例,也是理解 WebGPU 计算能力的最佳入门。

WGSL 着色器

@group(0) @binding(0) var<storage, read> a: array<f32>;
@group(0) @binding(1) var<storage, read> b: array<f32>;
@group(0) @binding(2) var<storage, read_write> result: array<f32>;

let BLOCK_SIZE: u32 = 16u;

@compute @workgroup_size(16, 16)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
  let row = gid.x;
  let col = gid.y;
  
  // 假设 N x N 矩阵, N = 64
  let N: u32 = 64u;
  if (row >= N || col >= N) {
    return;
  }
  
  var sum: f32 = 0.0;
  for (var k: u32 = 0u; k < N; k = k + 1u) {
    sum = sum + a[row * N + k] * b[k * N + col];
  }
  result[row * N + col] = sum;
}

JavaScript 端

async function matrixMultiply() {
  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();
  
  const N = 64;
  const matrixA = new Float32Array(N * N);
  const matrixB = new Float32Array(N * N);
  // 填充随机数据
  for (let i = 0; i < N * N; i++) {
    matrixA[i] = Math.random();
    matrixB[i] = Math.random();
  }
  
  // 创建 Buffer
  const bufA = device.createBuffer({
    size: matrixA.byteLength,
    usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
  });
  const bufB = device.createBuffer({
    size: matrixB.byteLength,
    usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
  });
  const bufResult = device.createBuffer({
    size: matrixA.byteLength,
    usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
  });
  const bufRead = device.createBuffer({
    size: matrixA.byteLength,
    usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
  });
  
  device.queue.writeBuffer(bufA, 0, matrixA);
  device.queue.writeBuffer(bufB, 0, matrixB);
  
  // 着色器代码
  const shaderModule = device.createShaderModule({
    code: shaderSource, // 上面的 WGSL 代码
  });
  
  // 计算管线
  const pipeline = device.createComputePipeline({
    layout: 'auto',
    compute: { module: shaderModule, entryPoint: 'main' },
  });
  
  const bindGroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [
      { binding: 0, resource: { buffer: bufA } },
      { binding: 1, resource: { buffer: bufB } },
      { binding: 2, resource: { buffer: bufResult } },
    ],
  });
  
  // 编码命令
  const encoder = device.createCommandEncoder();
  const pass = encoder.beginComputePass();
  pass.setPipeline(pipeline);
  pass.setBindGroup(0, bindGroup);
  pass.dispatchWorkgroups(Math.ceil(N / 16), Math.ceil(N / 16));
  pass.end();
  
  encoder.copyBufferToBuffer(bufResult, 0, bufRead, 0, matrixA.byteLength);
  device.queue.submit([encoder.finish()]);
  
  // 读取结果
  await bufRead.mapAsync(GPUMapMode.READ);
  const result = new Float32Array(bufRead.getMappedRange());
  console.log('Result:', result);
  bufRead.unmap();
}

性能注意事项

虽然 WebGPU 让浏览器具备了 GPU 计算能力,但并非所有场景都适合用它:

  • 数据传输开销:CPU 和 GPU 之间的数据传输有成本。如果计算量太小,花在传输上的时间可能比计算本身还长。
  • Workgroup 大小:合理设置 workgroup_size 对性能至关重要。通常选择 8×8 或 16×16,以匹配 GPU 的线程调度粒度。
  • 内存对齐:Storage Buffer 的访问模式对性能影响很大。连续访问(coalesced)比随机访问快得多。
  • 异步思维:所有 GPU 操作都是异步的。不要在性能敏感的循环中频繁创建 Buffer 或 Pipeline,应该在初始化时预创建。

浏览器兼容性

截至 2026 年,Chrome 和 Edge 已全面支持 WebGPU,Safari 也已在最新版本中落地支持,Firefox 仍在完善中。实际项目中建议使用特性检测:

if (!navigator.gpu) {
  console.warn('WebGPU 不可用,回退到 WebGL 或 CPU 方案');
}

应用场景展望

WebGPU 的计算能力为 Web 端打开了许多新大门:

  • 端侧 AI 推理:结合 TensorFlow.js 或 ONNX Runtime Web,可以在浏览器中运行小型模型推理,保护用户隐私且无需服务器算力。
  • 实时物理模拟:粒子系统、流体模拟等可以充分利用 GPU 并行性。
  • 图像/视频处理:滤镜、降噪、抠像等计算密集型任务可以在 GPU 上完成。
  • 数据可视化:大规模数据点的渲染和交互计算可以更流畅。

总结

WebGPU 不只是 WebGL 的升级版,它是一个从设计理念到 API 风格都焕然一新的图形与计算平台。显式管线模型、命令缓冲、原生 Compute Shader 支持让它成为 Web 端高性能计算的重要基础设施。

对于前端开发者来说,现在是学习 WebGPU 的好时机。虽然学习曲线比 WebGL 陡,但它的表达能力和性能潜力都远超前辈。从计算着色器入手,逐步过渡到渲染管线,是一条推荐的路线。

随着浏览器支持的完善和工具链的成熟,WebGPU 有望成为 Web 端图形和计算的统一底层,让浏览器从"文档查看器"进化为真正的"应用平台"。

评论