没有 GPU 的 WebGL
一个 Chrome 标志如何使 3D 页面渲染速度提高 4 倍 2026 年 6 月 29 日(11小时前) WebGL 现在无处不在:3D 地图、座位图、产品配置器、着色器艺术着陆页。它也是你要求 Microlink 截屏时速度最慢的东西。一个 Chrome 标志解决了这个问题。一个 WebGL 页面(three.js)被捕获为动画截图,通过没有 GPU 的节点上的 Mesa llvmpipe 渲染。TL;DR 我们的服务器没有 GPU。WebGL 仍然必须在某个地方渲染。Chrome 的默认软件路径(SwiftShader)每个 3D 页面耗时约 24秒。将 ANGLE 指向 Mesa llvmpipe(--use-angle=gl)将其降低到约 6秒。这个单行标志是简单的部分。显示器、来自源代码的 Mesa,以及证明它保持在快速路径上的剩余部分则是其他故事。故意没有 GPU 我们的浏览器群运行在没有显卡和没有 /dev/dri 的商品 Linux 节点上。更便宜、更简单、更少的驱动需要管理。但是 WebGL 是一个 GPU API,因此没有 GPU 时,必须在 CPU 上模拟它。这个模拟的内容就是 24 秒截屏和 6 秒截屏之间的区别。Chrome 不渲染 WebGL。ANGLE 渲染。因此,Chrome 将 WebGL 交给 ANGLE,后者将其转换为平台所拥有的后端:Direct3D、Metal、本机 OpenGL 或 Vulkan,或者在没有 GPU 时使用软件渲染器。在没有 GPU 的情况下,这个软件渲染器就是全部游戏。Chrome 提供了两个:SwiftShader,它的默认捆绑,或者系统的 OpenGL 堆栈,而在我们的 Linux 节点上是 Mesa llvmpipe。CPU 上的相同像素,速度却截然不同。为什么差距是 4 倍?SwiftShader 保守地模拟了整个管道,优化为“在任何地方都正确绘制”。一个复杂的 3D 场景约需 24秒;旁边的 2D 页面则需要 2-3秒。而 llvmpipe 则是不同的构建方式:它将 JIT 转换为本机代码。LLVM 将实时着色器和 GL 状态编译成真实的 x86-64。没有解释器循环。它是分块的并且多线程。它的确使用了每一个核心。速度更快,输出相同。改变的地方:一行 - ' --use-angle=swiftshader ' , + ' --use-angle=gl ' 。你必须不要重新添加的标志:--disable-gpu 安静地强制 SwiftShader 再次启用。它是每个无头教程中复制次数最多的标志。--in-process-gpu 会杀死 ANGLE 需要的 GL 表面。问题是:你需要一个显示器 --use-angle=gl 必须绑定一个 GL 表面,这需要一个 X 显示器,即使是无头的。没有显示器,WebGL 会悄然降级为平面 2D 降级:截屏仍然成功,请求仍然返回 200,而输出是错误但可信的。因此,每个容器在 Chrome 启动之前启动一个虚拟显示器(Xvfb),并将 LIBGL_ALWAYS_SOFTWARE=1 锁定到 llvmpipe。为什么 Mesa 是从源代码构建的 Ubuntu jammy 的 Mesa 对此来说太旧了,之前用作回扫的 PPA 也没有了。因此基础镜像编译自己的:meson setup build \ - Dbuildtype=release - Dgallium-drivers=llvmpipe - Dvulkan-drivers=\ - Dllvm=enabled - Dshared-llvm=enabled 仅使用 llvmpipe,没有 Vulkan,共享 LLVM(这就是 JIT 速度所在)。构建工具链很庞大(LLVM、clang、Rust、~160 个 -dev 包),因此 Dockerfile 分为多个阶段:编译 Mesa,然后将仅工件复制到干净的镜像中。2.65GB,而不是 4.5GB。证明它:browserless.report() 你无法通过查看节点来判断使用哪个渲染器。apt list 说谎(我们在软件包上侧加载 Mesa),而真实答案在页面内部。因此 browserless.report() 直接询问实时 GL 上下文:const browserless = require('browserless') const report = await browserless.report() console.log(report) 在生产节点上运行的 browserless.report()。展开 gpu 和 cpu 以获取完整的画面。gpu 块就是整个故事:类型是软件 / llvmpipe 在这里。swiftshader 将意味着我们回退;硬件将意味着出现了 GPU。mesa 是从加载的 libgallium-<ver>.so 中读取的,而不是 dpkg,它报告在我们的侧加载下过时的软件包版本。simdWidth:256 表示 llvmpipe 使用 AVX2,这也是它快速的主要原因。report({ benchmark: true }) 添加一个确定性着色器基准(在 llvmpipe 上约 300 毫秒)以比较节点。该报告也是 CI 的门槛。平面 2D 降级是危险的,因为它看起来像成功。因此 CI 对 report() 进行断言:gpu.type 必须是软件,gpu.device 必须是 llvmpipe。任何偏差都将使构建失败,而不是发布平面 3D。相同的调用会在生产 pod 上运行。大部分工作是基准测试,而不是编码。差异是一行。证明这是正确的一行花了几周的测量,大部分是打击两个陷阱:开发机器会说谎。实际的 GPU 会渲染在生产上回黑色的页面。每个数字都必须来自生产形状的硬件。单次运行会说谎。冷 JIT,第一个绘画竞赛,共享核心。看似最快的结果有时是错误的:平面降级花费约 1 秒的时间。因此确定性基准的存在:固定着色器,强制帧,稳定的数字。通过它,比较不再是轶事。SwiftShader 的结果约为 24-31 秒;llvmpipe 约为 6 秒,温暖和正确。数字相同的 3D 图表,相同的无 GPU
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡