From d82da195f305e0e0f82a9de8db34d0a726ae20a1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 2 Nov 2022 06:02:53 -0700 Subject: [PATCH] Remove atlas pages from demo when they're merged --- addons/xterm-addon-webgl/src/WebglAddon.ts | 3 ++ addons/xterm-addon-webgl/src/WebglRenderer.ts | 5 ++- demo/client.ts | 4 +++ src/browser/renderer/shared/TextureAtlas.ts | 32 ++++++++----------- src/browser/renderer/shared/Types.d.ts | 1 + 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 5262da8e..9ae6df5a 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -21,6 +21,8 @@ export class WebglAddon extends Disposable implements ITerminalAddon { public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter()); public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; + private readonly _onRemoveTextureAtlasCanvas = this.register(new EventEmitter()); + public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; private readonly _onContextLoss = this.register(new EventEmitter()); public readonly onContextLoss = this._onContextLoss.event; @@ -67,6 +69,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon { this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); + this.register(forwardEvent(this._renderer.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)); renderService.setRenderer(this._renderer); this.register(toDisposable(() => { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 7570566f..7941b2a5 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -53,6 +53,8 @@ export class WebglRenderer extends Disposable implements IRenderer { public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter()); public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; + private readonly _onRemoveTextureAtlasCanvas = this.register(new EventEmitter()); + public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; private readonly _onRequestRedraw = this.register(new EventEmitter()); public readonly onRequestRedraw = this._onRequestRedraw.event; private readonly _onContextLoss = this.register(new EventEmitter()); @@ -270,7 +272,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._onChangeTextureAtlas.fire(atlas.pages[0].canvas); this._charAtlasDisposable = getDisposeArrayDisposable([ atlas.onRequestRedrawViewport(() => this._requestRedrawViewport()), - forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas) + forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas), + forwardEvent(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas) ]); } this._charAtlas = atlas; diff --git a/demo/client.ts b/demo/client.ts index 67ad0284..3a438c00 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -279,6 +279,7 @@ function createTerminal(): void { setTextureAtlas(addons.webgl.instance.textureAtlas); addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e)); + addons.webgl.instance.onRemoveTextureAtlasCanvas(e => removeTextureAtlas(e)); } }, 0); @@ -662,6 +663,9 @@ function appendTextureAtlas(e: HTMLCanvasElement): void { styleAtlasPage(e); document.querySelector('#texture-atlas').appendChild(e); } +function removeTextureAtlas(e: HTMLCanvasElement): void { + e.remove(); +} function styleAtlasPage(e: HTMLCanvasElement): void { e.style.width = `${e.width / window.devicePixelRatio}px`; e.style.height = `${e.height / window.devicePixelRatio}px`; diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index ffa3cb40..62f36ad3 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -38,7 +38,8 @@ const enum Constants { ROW_PIXEL_THRESHOLD = 2, /** * The maximum texture size regardless of what the actual hardware maximum turns out to be. This - * is enforced to ensure uploading the texture still finishes in a reasonable amount of time. + * is enforced to ensure uploading the texture still finishes in a reasonable amount of time. A + * 4096 squared image takes up 16MB of GPU memory. */ FORCED_MAX_TEXTURE_SIZE = 4096 } @@ -81,6 +82,8 @@ export class TextureAtlas implements ITextureAtlas { public readonly onRequestRedrawViewport = this._onRequestRedrawViewport.event; private readonly _onAddTextureAtlasCanvas = new EventEmitter(); public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; + private readonly _onRemoveTextureAtlasCanvas = new EventEmitter(); + public readonly onRemoveTextureAtlasCanvas = this._onRemoveTextureAtlasCanvas.event; constructor( private readonly _document: Document, @@ -145,17 +148,14 @@ export class TextureAtlas implements ITextureAtlas { } private _createNewPage(): AtlasPage { - // Try merge the set of the 4 most used pages of the largest size. This is is deferred to a // microtask to ensure it does not interrupt textures that will be rendered in the current // animation frame which would result in blank rendered areas. This is actually not that // expensive relative to drawing the glyphs, so there is no need to wait for an idle callback. if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages / 2)) { - // TODO: If the viewport has enough glyphs to fill the new page, there will be a webgl error queueMicrotask(() => { - // Migrate over 1 page at a time due to the time it takes to iterate over glyphs - - // Find the set of the largest 4 images below the maximum size with the highest percentages used the 4 most used pages + // Find the set of the largest 4 images, below the maximum size, with the highest + // percentages used const pagesBySize = this._pages.filter(e => { return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE); }).sort((a, b) => { @@ -175,30 +175,25 @@ export class TextureAtlas implements ITextureAtlas { } } - // TODO: This is slow, need to sort after slice so _pages doesn't get sorted - - const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4); // .sort((a, b) => a.percentageUsed < b.percentageUsed ? 1 : -1); + // Gather details of the merge + const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4); const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1); - // TODO: Pull texture page index in a nicer way const mergedPageIndex = sortedMergingPagesIndexes[0]; + // Merge into the new page const mergedPage = this._mergePages(mergingPages, mergedPageIndex); - - // (console as any).image(mergedPage.canvas); - mergedPage.hasCanvasChanged = true; - // this._pages[0] = mergedPage; - // Replace an old merging page with the merged + + // Replace the first _merging_ page with the _merged_ page this._pages[mergedPageIndex] = mergedPage; - // TODO: Splice other 3 pages, shifting all other texture page props + // Delete the other 3 pages, shifting glyph texture pages as needed for (let i = sortedMergingPagesIndexes.length - 1; i >= 1; i--) { this._deletePage(sortedMergingPagesIndexes[i]); } // Request the model to be cleared to refresh all texture pages. this._requestClearModel = true; - this._onAddTextureAtlasCanvas.fire(mergedPage.canvas); }); } @@ -228,8 +223,7 @@ export class TextureAtlas implements ITextureAtlas { g.texturePositionClipSpace.y = g.texturePosition.y / mergedSize; } - p.ctx.fillStyle = 'green'; - p.ctx.fillRect(0, 0, p.canvas.width, p.canvas.height); + this._onRemoveTextureAtlasCanvas.fire(p.canvas); // Remove the merging page from active pages if it was there const index = this._activePages.indexOf(p); diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 5f4a53ff..cbebcad4 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -91,6 +91,7 @@ export interface ITextureAtlas extends IDisposable { onRequestRedrawViewport: IEvent; onAddTextureAtlasCanvas: IEvent; + onRemoveTextureAtlasCanvas: IEvent; /** * Warm up the texture atlas, adding common glyphs to avoid slowing early frame.