diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 43de1c98..23c763a2 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -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(); public get onContextLoss(): IEvent { 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. diff --git a/demo/client.ts b/demo/client.ts index 3593004a..2557fb44 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -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() {