Handle WebGL context loss/restore events in WebGL renderer.

If the context is restored within a brief period of time (3 seconds),
recreate members dependent on WebGL state, and fire onRequestRedraw to
the renderer's listeners.

Handle changes to the char atlas better in the demo client.

Tested in Chrome Canary on macOS by running the demo in one window,
and visiting the internal URL about:gpucrash in another window. The
terminal now recovers and redraws properly.
This commit is contained in:
Kenneth Russell
2022-08-30 19:42:22 -07:00
parent ef759a6d7b
commit 9459553665
2 changed files with 45 additions and 9 deletions
+44 -8
View File
@@ -46,8 +46,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _canvas: HTMLCanvasElement;
private _gl: IWebGL2RenderingContext;
private _rectangleRenderer: RectangleRenderer;
private _glyphRenderer: GlyphRenderer;
private _rectangleRenderer!: RectangleRenderer;
private _glyphRenderer!: GlyphRenderer;
public dimensions: IRenderDimensions;
@@ -62,6 +62,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _onContextLoss = new EventEmitter<void>();
public get onContextLoss(): IEvent<void> { return this._onContextLoss.event; }
private _contextRestorationTimeout: number | undefined;
constructor(
private _terminal: Terminal,
private _colors: IColorSet,
@@ -108,16 +110,35 @@ export class WebglRenderer extends Disposable implements IRenderer {
throw new Error('WebGL2 not supported ' + this._gl);
}
this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); }));
this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => {
console.log('webglcontextlost event received');
// Prevent the default behavior in order to enable WebGL context restoration.
e.preventDefault();
// Wait a few seconds to see if the 'webglcontextrestored' event is fired.
// If not, dispatch the onContextLoss notification to observers.
this._contextRestorationTimeout = setTimeout(() => {
if (this._contextRestorationTimeout !== 0) {
console.log('webgl context not restored; firing onContextLoss');
this._onContextLoss.fire(e);
}
}, 3000 /* ms */);
}));
this.register(addDisposableDomListener(this._canvas, 'webglcontextrestored', (e) => {
console.log('webglcontextrestored event received');
clearTimeout(this._contextRestorationTimeout);
this._contextRestorationTimeout = 0;
// The texture atlas and glyph renderer must be fully reinitialized
// because their contents have been lost.
removeTerminalFromCache(this._terminal);
this._initializeWebGLState();
this._requestRedrawViewport();
}));
this.register(observeDevicePixelDimensions(this._canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h)));
this._core.screenElement!.appendChild(this._canvas);
this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions));
this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions));
// Update dimensions and acquire char atlas
this.onCharSizeChanged();
this._initializeWebGLState();
this._isAttached = document.body.contains(this._core.screenElement!);
}
@@ -235,6 +256,21 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._refreshCharAtlas();
}
/**
* Initializes members dependent on WebGL context state.
*/
private _initializeWebGLState(): void {
// Dispose any previous rectangle and glyph renderers before creating new ones.
this._rectangleRenderer?.dispose();
this._glyphRenderer?.dispose();
this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions);
this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions);
// Update dimensions and acquire char atlas
this.onCharSizeChanged();
}
/**
* Refreshes the char atlas, aquiring a new one if necessary.
* @param terminal The terminal.
+1 -1
View File
@@ -606,7 +606,7 @@ function htmlSerializeButtonHandler(): void {
}
function addTextureAtlas(e: HTMLCanvasElement) {
document.querySelector('#texture-atlas').appendChild(e);
document.querySelector('#texture-atlas').replaceChildren(e);
}
function writeCustomGlyphHandler() {