跳到主要内容
3D Playground
GPU 与 WebGPU 计算第 2 / 11 节

你好 WebGPU:第一个三角形

入门WebGPU
此示例需要 WebGPU(Chrome / Edge 113+、Safari 26+)。

从检测到设备

WebGPU 的入口是 navigator.gpu。先检测它是否存在,再请求 adapter(一块物理或软件 GPU),最后从 adapter 拿到 device:你与 GPU 之间私有的逻辑连接,所有缓冲区、纹理、管线都从它创建。

js
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

即使 navigator.gpu 存在,adapter 也可能为 null,所以两步都要检查,并给用户友好的提示。

画布与格式

canvas.getContext("webgpu") 拿到上下文后要 configure:指定 device 和像素格式。getPreferredCanvasFormat() 返回当前平台最高效的格式(bgra8unorm 或 rgba8unorm)。

WGSL 与渲染管线

着色器用 WGSL 编写。这个例子没有顶点缓冲,位置和颜色直接按内置的 vertex_index 从常量数组取:

wgsl
@vertex
fn vs_main(@builtin(vertex_index) i : u32) -> VSOut {
  var P = array<vec2f, 3>(vec2f(0.0, 0.72), vec2f(-0.68, -0.56), vec2f(0.68, -0.56));
  ...
}

渲染管线把着色器和固定功能状态(图元拓扑、输出格式等)烘焙成一个不可变对象。想换拓扑就得建新管线:创建慢、切换快,这是刻意的设计。

命令编码与提交

GPU 不是一条条立即执行命令的。你先用 command encoder 录制命令,开一个 render pass(清屏、绘制),结束后 finish() 得到命令缓冲,再 queue.submit() 交给 GPU 队列。

限制在途帧数

requestAnimationFrame 每秒触发约 60 次,但 GPU 不一定跟得上。如果每帧都无条件提交,命令会在队列里越堆越多,延迟和内存一起上涨。所以渲染循环记录“在途帧数”,用 queue.onSubmittedWorkDone() 在 GPU 完成后减一,超过 2 帧就跳过本帧:

js
if (framesInFlight >= 2) return;
device.queue.submit([encoder.finish()]);
framesInFlight++;
device.queue.onSubmittedWorkDone().then(() => framesInFlight--);

后面所有 WebGPU 示例都沿用这个模式。

核心 API

navigator.gpu.requestAdapteradapter.requestDevicecontext.configurenavigator.gpu.getPreferredCanvasFormatdevice.createShaderModuledevice.createRenderPipelinedevice.createCommandEncoderencoder.beginRenderPassdevice.queue.submitdevice.queue.onSubmittedWorkDone

动手试试

  1. 1在 GUI 里把 topology 切到 line-strip 和 point-list,观察同样三个顶点的不同画法。
  2. 2给数组再加三个顶点,把 pass.draw(3) 改成 draw(6),画出第二个三角形。
  3. 3故意在 WGSL 里写错一个字,看控制台里 getCompilationInfo 报出的行号。
运行中