跳到主要内容

GPU程序与三种表面

浏览器里能当「图」用的生产表面只有三类。选错不是风格问题,是把错误的 source of truth 写进热路径。

1. 三种真相

SVG / DOM 帧的真相 = 文档节点
Canvas 2D 帧的真相 = backing store 像素
WebGL/WebGPU 帧的真相 = GPU 程序 + GPU 资源

SVG 改 cx,下一帧节点还在。Canvas 2D 调 fill,像素立刻变,对象句柄没有了。GL 调 drawArrays,GPU 按当前 program / VAO / uniform 跑一遍;CPU 侧如果没有自己的场景图,你同样没有「那个立方体」可以拖。

生产后果:

  • 拖拽仍然是改 CPU 上的矩阵,再上传 uniform 或 instance buffer。
  • 撤销是场景命令栈,不是 readPixels 快照(全屏读回又慢又脏)。
  • 命中可以走 CPU 几何,也可以把 ID 烘到纹理再读 1 像素。后者是同步点,见 GPU 拾取

2. 最小提交循环

下面是 WebGL2 的最小骨架。它不依赖框架。

export function createGl(canvas: HTMLCanvasElement): WebGL2RenderingContext {
const gl = canvas.getContext("webgl2", {
alpha: true,
antialias: false,
depth: true,
preserveDrawingBuffer: false,
powerPreference: "high-performance",
failIfMajorPerformanceCaveat: true,
});
if (!gl) {
throw new Error("webgl2 unavailable");
}
return gl;
}

export function submitFrame(
gl: WebGL2RenderingContext,
draw: (gl: WebGL2RenderingContext) => void,
): void {
const w = gl.drawingBufferWidth;
const h = gl.drawingBufferHeight;
gl.viewport(0, 0, w, h);
gl.clearColor(0.07, 0.08, 0.1, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
draw(gl);
}

关键点:

  • 创建参数只认第一次。 之后 getContext("webgl2") 返回同一实例;getContext("2d") 得到 null
  • failIfMajorPerformanceCaveat 把软件实现挡在门外。需要软件回退时显式关,不要默默用 SwiftShader 冒充 60fps。
  • preserveDrawingBuffer: false 是默认,也是性能默认。不要为了「随时截图」打开它还以为没有代价。

WebGPU 的等价物是 adapter → device → configure,见 上下文生命周期

3. 什么时候可以不上 GPU

适用 Canvas 2D:对象 < 约 2k、效果是像素混合、要和 ImageBitmap / 视频帧同一套位图模型。
适用 SVG:要选中、对齐、读屏。
适用 GL:实例过万、每像素公式、与现有 GL 引擎同上下文。

不适用:为了「看起来 3D」给一个卡片开 WebGL。那是 CSS 或一张预烘焙图的活。

4. 失败形态

症状原因
getContext("webgl2") 为 null,2D 却还在这张 canvas 已经被 2d 占用
截图全黑默认不 preserve,读回发生在合成之后
移动端 5fps 还以为是算法问题拿到了软件光栅,没设 failIfMajorPerformanceCaveat
拖一下物体「没对象」只在 GPU 里有三角形,CPU 没有场景图

权威资料

核对日期:2026-08-26