丢失上下文与恢复
GPU 上下文不是进程寿命。内存压力、切后台、切显示器、驱动重置,都会把对象表清空。恢复是产品路径,不是 try/catch 边角。创建参数见 01 生命周期。
1. WebGL:必须 preventDefault
webglcontextlost 默认不可恢复。要让浏览器随后发 webglcontextrestored,必须在 lost 里 preventDefault()。
export type GlFactory = (gl: WebGL2RenderingContext) => void;
export class GlResourceTable {
readonly factories: GlFactory[] = [];
register(factory: GlFactory): void {
this.factories.push(factory);
}
rebuild(gl: WebGL2RenderingContext): void {
for (const factory of this.factories) {
factory(gl);
}
}
}
export function bindGlRestore(
canvas: HTMLCanvasElement,
gl: WebGL2RenderingContext,
table: GlResourceTable,
onReady: (gl: WebGL2RenderingContext) => void,
): { dispose: () => void; isAlive: () => boolean } {
let alive = true;
const onLost = (event: Event): void => {
event.preventDefault();
alive = false;
};
const onRestoredEvent = (): void => {
const next = canvas.getContext("webgl2");
if (!next) return;
table.rebuild(next);
alive = true;
onReady(next);
};
canvas.addEventListener("webglcontextlost", onLost);
canvas.addEventListener("webglcontextrestored", onRestoredEvent);
void gl;
return {
isAlive: () => alive,
dispose: () => {
canvas.removeEventListener("webglcontextlost", onLost);
canvas.removeEventListener("webglcontextrestored", onRestoredEvent);
},
};
}
丢失瞬间:所有 WebGLBuffer / WebGLTexture / WebGLProgram / WebGLVertexArrayObject / WebGLFramebuffer / WebGLUniformLocation 作废。数字句柄可能还在 JS 里,但 isBuffer / isProgram 为 false。rAF 必须看 alive:lost 之后、restored 之前禁止 useProgram。
资源表登记的是 工厂,不是句柄。创建顺序要稳定:先 compile program,再 VAO,再纹理,最后 FBO 附件。依赖未建完的纹理去 framebufferTexture2D,恢复后 FBO incomplete,画面黑。
适用:任何上线的 WebGL2 视图。
不适用:把「刷新页面」当恢复策略——iOS 切 App 回来会丢,用户没刷新。
2. 用 WEBGL_lose_context 测,不要等真机撞
export function loseForTest(gl: WebGL2RenderingContext): void {
const ext = gl.getExtension("WEBGL_lose_context");
if (!ext) throw new Error("WEBGL_lose_context missing");
ext.loseContext();
}
export function restoreForTest(gl: WebGL2RenderingContext): void {
const ext = gl.getExtension("WEBGL_lose_context");
ext?.restoreContext();
}
单测 / QA 按钮:lose → 断言 rAF 停、句柄 isProgram === false → restore → 断言第一帧画面与丢失前一致。没走这条路径的渲染器,等于没测过 iOS。
扩展在 restored 之后也要重新 getExtension。不要缓存 WEBGL_lose_context 以外的扩展对象当永活。
3. WebGPU:device.lost
GPUDevice.lost 是 Promise,resolve 一次。之后这个 device 上的 buffer / texture / pipeline / encoder 全部不可用。
export async function watchDeviceLost(
device: GPUDevice,
recreate: () => Promise<GPUDevice>,
): Promise<void> {
const info = await device.lost;
if (info.reason === "destroyed") return;
const next = await recreate();
void next;
}
reason === "destroyed" 是你自己 device.destroy(),不要再申请。unknown 才是驱动 / 系统回收。重建链:adapter 可能也没了 → requestAdapter → requestDevice → configure → 按资源表重建。旧 GPUBuffer 不能 queue.writeBuffer。
核对日期 2026-08:Chrome / Edge 桌面、Safari 26 / iOS 26、Firefox Windows 与 macOS Apple Silicon 有 WebGPU。Linux / Android Firefox 不完整。重建失败就降级 WebGL2,见 WebGPU 适配。
4. 和 failIfMajorPerformanceCaveat 的关系
丢失后 getContext("webgl2") 仍可能给到软件实现。恢复路径 沿用创建时的选项,保持 failIfMajorPerformanceCaveat: true。宁可变 Canvas 2D / 降质量,不要默默用 SwiftShader 冒充 60fps。2D 降级心智见 Canvas。
5. 失败形态
| 症状 | 原因 |
|---|---|
永远没有 webglcontextrestored | lost 时没 preventDefault |
restored 后第一帧黑 / INVALID_OPERATION | 仍在用旧 program / VAO / location |
| FBO incomplete | 重建顺序错,附件纹理还是丢失前的句柄 |
| iOS 切回白屏 | 没听 lost,rAF 继续 draw |
| WebGPU 重建后闪黑 | configure 清空交换链,没立刻重绘 |
| 恢复后 5fps | 软件光栅顶上,未坚持 caveat |
权威资料
- WebGL — The Context Lost Event
- MDN — WEBGL_lose_context
- MDN — WebGLContextEvent
- W3C — GPUDevice.lost
核对日期:2026-08-26