最近,Figma 官方发布了一篇关于他们将渲染引擎迁移到 WebGPU 的文章 《Figma 渲染:由 WebGPU 驱动》 ,在社区里引起了不小的波澜。作为一个长期关注Web图形技术演进的人,我读完后可以说是心潮澎湃。
Figma此举,不仅仅是一次简单的技术升级。它像一声号角,宣告着一个新时代的到来:WebGPU 不再是实验室里的玩具,而是已经能为亿万用户提供服务的成熟技术。 这篇文章,我想借着Figma的案例,和大家深入聊聊 WebGPU 究竟是什么,它牛在哪里,以及它将如何颠覆我们对 Web 应用的想象。
从 WebGL 到 WebGPU:一次“非升不可”的进化
还记得2015年吗?那会儿React刚刚兴起,前端还在为组件化激动不已。Figma 的创始团队做出了一个在当时看来极为大胆的决定:在浏览器里,用 WebGL 构建一个专业级的设计工具。要知道,WebGL 本是为 3D 游戏设计的,拿它来做复杂的 2D 应用,坑多路少,但他们赌对了。正是这个决定,奠定了Figma后来丝般顺滑的无限画布和实时协作的基石。
然而,时代在变,技术在演进。WebGL 这位老将,也逐渐显露出它的疲态:
- API 设计过时:它的设计严重依赖全局状态机,写起代码来就像在走钢丝,一不小心忘记重置某个状态,就会导致难以追踪的渲染 bug。Figma 在博客中也提到,仅仅是为了适配 WebGPU 而重构接口,去掉这种全局状态依赖,就修复了他们 WebGL 渲染器中好几个历史遗留问题。
- 性能瓶颈:WebGL 本质上只是 OpenGL ES 2.0 的一层薄封装,无法充分利用现代 GPU(如 Vulkan, Metal, DirectX 12)的强大能力。驱动开销大,多线程支持弱,面对日益复杂的渲染需求,CPU 常常成为瓶颈。
- 通用计算能力缺失:WebGL 的核心是渲染管线,虽然可以通过一些“黑魔法”来做通用计算(GPGPU),但终究是“歪门邪道”,效率和便利性都极差。
于是,WebGPU 应运而生。它不是 WebGL 3.0,而是一次从底层开始的全新设计。它对标的是 Vulkan、Metal 和 DirectX 12 这些现代图形 API,目标就是将 GPU 的真正实力安全、高效地释放给 Web 开发者。
WebGPU 的“杀手锏”:不止是更快
Figma的迁移之路并非一帆风顺,他们重写了图形接口、设计了全新的着色器处理流程,并解决了棘手的 Uniform 缓冲区管理问题。那么,究竟是什么让 WebGPU 值得他们投入如此大的精力?
1. 架构的现代化:告别全局状态,拥抱面向对象
如果你写过原生图形API,你会对 WebGPU 的接口感到无比亲切。它抛弃了 WebGL 那套令人抓狂的 bind-to-edit 模式。
在 WebGL 中,你的代码可能是这样的:
JavaScript
// WebGL: 绑定并设置各种状态
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttributeLocation, ...);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindTexture(gl.TEXTURE_2D, myTexture);
gl.useProgram(myProgram);
gl.uniform1f(timeUniformLocation, time);
gl.drawArrays(gl.TRIANGLES, 0, 6); // 绘制!
所有资源都绑定到全局上下文,一环出错,满盘皆输。
而 WebGPU 采用了更现代、更健壮的面向对象 API。所有状态都被封装在明确的对象里,比如管线状态对象(Pipeline State Object)、绑定组(Bind Group)等。绘制调用变得极其“干净”:
JavaScript
// WebGPU: 记录指令
passEncoder.setPipeline(renderPipeline);
passEncoder.setVertexBuffer(0, vertexBuffer);
passEncoder.setBindGroup(0, uniformBindGroup);
passEncoder.draw(3, 1, 0, 0);
这种设计极大地降低了驱动的 CPU 开销,因为大部分状态验证都可以在管线创建时一次性完成。更重要的是,它让多线程渲染成为可能,你可以在多个 Worker 中并行地编码渲染指令,最后统一提交给主线程,将性能压榨到极致。
2. 计算着色器(Compute Shader):释放 GPU 的“蛮力”
这绝对是 WebGPU 相对于 WebGL 最具革命性的特性。
传统的渲染管线是高度专化的,就像一条固定的流水线,输入顶点,输出像素。而计算着色器则完全不同,它允许你在 GPU 上执行通用的、高度并行的计算任务,不受渲染流程的束缚。
这意味着什么?
Figma 在文章结尾畅想了未来:用计算着色器来优化高斯模糊的渲染。对于一个大半径的模糊效果,传统渲染方式需要多次 Pass,效率低下。而用计算着色器,可以一次性读取周边像素,在共享内存(Shared Memory)中高效完成计算,性能提升可能是数量级的。
这还只是冰山一角。物理模拟、机器学习模型推理、图像视频处理、复杂的粒子系统……所有这些计算密集型任务,现在都可以从“慢吞吞”的 CPU 转移到拥有成千上万个核心的 GPU 上。Web 应用的能力边界,由此被彻底打开。
3. 精细的资源管理与明确的错误处理
在 WebGPU 中,资源(Buffer、Texture)的创建和使用被严格分离开。你需要明确告诉 GPU 这个资源将被用作什么(GPUBufferUsage.UNIFORM,GPUBufferUsage.COPY_DST 等)。这使得驱动可以进行更深度的优化。
Figma 遇到的一个典型问题就是 Uniforms 的处理。在 WebGL 中,你可以随时随地通过 gl.uniform1f 这样的命令单独更新一个 uniform。但在现代 GPU 中,这种零散的更新效率极低。WebGPU 则强制你将 uniforms 组合到 Uniform Buffer 中,一次性批量上传。Figma 团队通过改造接口,将多次绘制的 uniform 数据打包(batch)到单个缓冲区,在 submit() 时一次性上传,从而避免了性能退化。
此外,WebGPU 提供了异步的错误报告机制。WebGL 的 getError() 是同步的,频繁调用会严重影响性能,所以大家通常只在调试时使用。而 WebGPU 的错误信息不仅详细,而且不会阻塞渲染主流程,这对于构建稳定、健壮的大型应用至关重要。
Figma 趟过的“坑”与我们的启示
当然,罗马不是一天建成的。Figma 的迁移之路也充满了挑战,其中最值得我们借鉴的,是他们处理设备兼容性的策略。
他们最初的计划是像 WebGL 那样,在加载时运行一套兼容性测试。但很快发现此路不通,因为 WebGPU 的数据回读是异步的,这会严重拖慢应用的启动速度。更糟的是,他们发现在 Windows 平台上,WebGPU 设备可能会在会话中途“丢失”。
最终,他们构建了一套动态回退系统:默认以 WebGPU 启动,一旦在运行时遇到无法恢复的错误,就无缝地、动态地切换回 WebGL 后端。同时,他们还通过数据监控,将那些回退率高的设备加入黑名单。
这套方案堪称工程典范,也给了我们所有打算拥抱 WebGPU 的开发者一个重要启示:在过渡期内,一个健壮的回退机制是必不可少的。
WebGPU 的未来已来,你准备好了吗?
Figma 的实践证明,WebGPU 已经准备好了。它的未来应用场景,远不止于设计工具:
- 次世代 Web 游戏:媲美原生画质的 3D 大作将直接在浏览器中运行。
- 交互式数据可视化:流畅处理数百万甚至上亿数据点,实时进行科学计算与渲染。
- 浏览器内的 AI/ML:利用 GPU 加速,直接在前端运行复杂的机器学习模型,保护用户隐私的同时提供更快的交互响应。
- 沉浸式 XR/元宇宙体验:为未来的 WebXR 提供强大的底层渲染能力。
Figma 已经为我们推开了这扇门。他们计划利用 RenderBundles 进一步降低渲染的 CPU 开销,利用 MSAA 提升抗锯齿质量,这些都是 WebGPU 独有的性能优化利器。
对于我们前端开发者而言,WebGPU 带来的不仅仅是一个新的图形 API,更是一次思维模式的转变。我们需要开始像游戏开发者一样思考,学习如何管理 GPU 资源,如何设计并行算法,如何将计算负载合理地分配给 CPU 和 GPU。
这无疑是一个陡峭的学习曲线,但回报也是巨大的。掌握了 WebGPU,你将解锁 Web 平台的终极性能,创造出今天看来还无法想象的交互体验。
路已铺就,未来可期。你,准备好迎接这个由 WebGPU 驱动的新时代了吗?