跳到主要内容

丢失上下文与恢复

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 可能也没了requestAdapterrequestDeviceconfigure → 按资源表重建。旧 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. 失败形态

症状原因
永远没有 webglcontextrestoredlost 时没 preventDefault
restored 后第一帧黑 / INVALID_OPERATION仍在用旧 program / VAO / location
FBO incomplete重建顺序错,附件纹理还是丢失前的句柄
iOS 切回白屏没听 lost,rAF 继续 draw
WebGPU 重建后闪黑configure 清空交换链,没立刻重绘
恢复后 5fps软件光栅顶上,未坚持 caveat

权威资料

核对日期:2026-08-26