作为 WebGL 开发者,您可能会对开始使用 WebGPU 感到不安和兴奋,因为 WebGPU 是 WebGL 的继任产品,可为网络带来现代图形 API 的进步。
令人欣慰的是,WebGL 和 WebGPU 具有共同的许多核心概念。这两个 API 都允许您在 GPU 上运行称为着色器的小程序。WebGL 支持顶点和 fragment 着色器,而 WebGPU 还支持计算着色器。WebGL 使用 OpenGL 着色语言 (GLSL),而 WebGPU 使用 WebGPU 着色语言 (WGSL)。虽然这两种语言有所不同,但基本概念基本相同。
为此,本文重点介绍了 WebGL 和 WebGPU 之间的一些差异,以帮助您入门。
全局状态
WebGL 具有许多全局状态。某些设置适用于所有渲染操作,例如绑定哪些纹理和缓冲区。您可以通过调用各种 API 函数来设置此全局状态,并且在您对其进行更改之前,该状态始终有效。WebGL 中的全局状态是主要的错误来源,因为很容易忘记更改全局设置。此外,全局状态会导致代码共享变得困难,因为开发者需要小心,不要意外更改会影响代码其他部分的方式的全局状态。
WebGPU 是一种无状态 API,不保持全局状态。而是使用“管道”的概念来封装在 WebGL 中属于全局性的所有渲染状态。流水线包含要使用的混合、拓扑和属性等信息。流水线是不可变的。如果要更改某些设置,您需要再创建一个流水线。WebGPU 还使用命令编码器来批量处理命令,并按它们的记录顺序执行这些命令。这在阴影映射中很有用,例如,在对象上单次传递时,应用可以记录多个命令流,每个命令流对应一个光的阴影贴图。
总而言之,由于 WebGL 的全局状态模型使得创建强大的可组合库和应用变得困难和脆弱,因此 WebGPU 显著减少了开发者在向 GPU 发送命令时需要跟踪的状态数量。
不再同步
在 GPU 上,发送并同步等待命令通常效率低下,因为这可能会刷新流水线并导致产生气泡。在 WebGPU 和 WebGL 中尤其如此,它们使用多进程架构,其中 GPU 驱动程序在独立于 JavaScript 的进程中运行。
例如,在 WebGL 中,调用 gl.getError()
需要从 JavaScript 进程到 GPU 进程再到执行同步的 IPC。当两个进程进行通信时,这可能会导致 CPU 端出现气泡。
为避免出现这些气泡,WebGPU 采用了完全异步的设计。错误模型和所有其他操作都是异步发生的。例如,创建纹理时,即使纹理实际上有误,操作似乎也会立即成功。您只能异步发现错误。这种设计可保持跨进程通信无气泡,并可为应用提供可靠的性能。
计算着色器
计算着色器是在 GPU 上运行的程序,用于执行通用计算。它们仅适用于 WebGPU,而不适用于 WebGL。
与顶点和片段着色器不同,它们不限于图形处理,并且可用于各种任务,如机器学习、物理模拟和科学计算。计算着色器由数百甚至数千个线程并行执行,这使得它们在处理大型数据集时非常高效。如需了解 GPU 计算以及更多详细信息,请参阅这篇有关 WebGPU 的详尽文章。
视频帧处理
使用 JavaScript 和 WebAssembly 处理视频帧存在一些缺点:将数据从 GPU 内存复制到 CPU 内存的成本很高,并且可以通过工作器和 CPU 线程实现有限的并行处理。WebGPU 没有这些限制,由于与 WebCodecs API 紧密集成,因此它非常适合处理视频帧。
以下代码段展示了如何将 VideoFrame 作为外部纹理导入 WebGPU 并对其进行处理。您可以试用此演示。
// Init WebGPU device and pipeline...
// Configure canvas context...
// Feed camera stream to video...
(function render() {
const videoFrame = new VideoFrame(video);
applyFilter(videoFrame);
requestAnimationFrame(render);
})();
function applyFilter(videoFrame) {
const texture = device.importExternalTexture({ source: videoFrame });
const bindgroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: texture }],
});
// Finally, submit commands to GPU
}
默认的应用可移植性
WebGPU 会强制您请求 limits
。默认情况下,requestDevice()
会返回一个 GPUDevice,该 GPUDevice 可能与实体设备的硬件功能不匹配,而是返回所有 GPU 的合理且最低的共同标准。通过要求开发者请求设备限制,WebGPU 可确保应用在尽可能多的设备上运行。
画布处理
在您创建 WebGL 上下文并提供上下文属性(例如 alpha、抗锯齿、colorSpace、深度、retainDrawingBuffer 或模板)后,WebGL 会自动管理画布。
另一方面,WebGPU 要求您自行管理画布。例如,如需在 WebGPU 中实现抗锯齿,您可以创建多重采样纹理并渲染到它。然后,您需要将多重采样纹理解析为常规纹理,并将该纹理绘制到画布上。通过这种手动管理,您可以从单个 GPUDevice 对象输出到任意数量的画布。相比之下,WebGL 只能为每个画布创建一个上下文。
查看 WebGPU 多个画布演示。
另外,浏览器目前对每页的 WebGL 画布数量有限制。在撰写本文时,Chrome 和 Safari 最多只能同时使用 16 个 WebGL 画布;Firefox 最多可创建 200 张。另一方面,每页的 WebGPU 画布数量没有限制。
![屏幕截图:Safari、Chrome 和 Firefox 浏览器中的 WebGL 画布数量已达上限](https://developer.chrome.google.cn/static/blog/from-webgl-to-webgpu/image/screenshot-featuring-maxi-0808a2e4e5ccc.png?authuser=5&hl=sr)
有用的错误消息
WebGPU 为从 API 返回的每条消息提供了一个调用堆栈。这意味着您可以快速查看代码中发生错误的位置,这有助于调试和修正错误。
除了提供调用堆栈之外,WebGPU 错误消息也易于理解和操作。错误消息通常包含错误说明和错误修正建议。
借助 WebGPU,您还可以为每个 WebGPU 对象提供自定义 label
。然后,浏览器会在 GPUError 消息、控制台警告和浏览器开发者工具中使用此标签。
从名称到索引
在 WebGL 中,许多东西都是通过名称相连的。例如,您可以在 GLSL 中声明一个名为 myUniform
的统一变量,并使用 gl.getUniformLocation(program, 'myUniform')
获取其位置。这样,如果您输错了 uniform 变量的名称,就会收到错误消息。
另一方面,在 WebGPU 中,所有内容都完全通过字节偏移量或索引(通常称为“位置”)连接。您有责任使 WGSL 和 JavaScript 中的代码位置保持同步。
生成 mipmap
在 WebGL 中,您可以创建纹理的级别 0 mip,然后调用 gl.generateMipmap()
。WebGL 随后会为您生成所有其他 mip 级别。
在 WebGPU 中,您必须自行生成 mipmap。我们没有内置函数可执行此操作。如需详细了解相关决定,请参阅规范讨论。您可以使用 webgpu-utils 等方便的库生成 mipmap 或了解如何自行生成。
存储缓冲区和存储纹理
WebGL 和 WebGPU 均支持统一缓冲区,并允许您将大小有限的常量参数传递给着色器。存储缓冲区与统一缓冲区非常相似,仅由 WebGPU 支持,而且比统一缓冲区更强大、更灵活。
传递给着色器的存储缓冲区数据可能比统一缓冲区大得多。虽然该规范表明统一缓冲区绑定的大小不得超过 64KB(请参阅
maxUniformBufferBindingSize
),但在 WebGPU 中存储缓冲区绑定的大小上限至少为 128MB(请参阅maxStorageBufferBindingSize
)。存储缓冲区是可写的,支持一些原子操作,而统一缓冲区只是只读的。这样就可以实现新的算法类。
存储缓冲区绑定支持运行时大小的数组,以便实现更灵活的算法,同时必须在着色器中提供统一的缓冲区数组大小。
存储纹理仅在 WebGPU 中受支持,并且用于纹理化存储缓冲区与统一缓冲区之间的差别。它们比常规纹理更灵活,支持随机访问写入(以及未来读取)。
缓冲区和纹理变化
例如,在 WebGL 中,您可以创建缓冲区或纹理,然后随时分别使用 gl.bufferData()
和 gl.texImage2D()
更改其大小。
在 WebGPU 中,缓冲区和纹理是不可变的。也就是说,广告素材创建后,您便无法更改其大小、用途或格式。您只能更改其内容。
空间惯例差异
在 WebGL 中,Z 裁剪空间的范围为 -1 到 1。在 WebGPU 中,Z 裁剪空间的范围为 0 到 1。也就是说,z 值为 0 的对象距离镜头最近,z 值为 1 的对象距离最远。
WebGL 使用 OpenGL 惯例,其中 Y 轴向上,Z 轴朝向查看器。WebGPU 使用 Metal 惯例,其中 Y 轴在屏幕之外,Z 轴在屏幕范围外。请注意,在帧缓冲区坐标、视口坐标和 fragment/像素坐标中,Y 轴方向向下。在裁剪空间中,Y 轴方向仍像在 WebGL 中那样向上。
致谢
感谢 Corentin Wallez、Gregg Tavares、Stephen White、Ken Russell 和 Rachel Andrew 审阅本文。
此外,我还建议访问 WebGPUFundamentals.org 来深入了解 WebGPU 和 WebGL 之间的区别。