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