From 6b37187b4d3c42e8a258439096fb85ee8971f46f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:08:33 -0700 Subject: [PATCH] Create AtlasPage --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 62 +++++++++---------- src/browser/renderer/shared/TextureAtlas.ts | 60 ++++++++++-------- src/browser/renderer/shared/Types.d.ts | 4 +- 3 files changed, 69 insertions(+), 57 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 58df22dd..2096c35a 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -77,6 +77,7 @@ void main() { const INDICES_PER_CELL = 11; const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; const CELL_POSITION_INDICES = 2; +const MAX_ATLAS_PAGES = 8; // Work variables to avoid garbage collection let $i = 0; @@ -90,8 +91,7 @@ export class GlyphRenderer extends Disposable { private readonly _projectionLocation: WebGLUniformLocation; private readonly _resolutionLocation: WebGLUniformLocation; private readonly _textureLocation: WebGLUniformLocation; - private readonly _atlasTexture: WebGLTexture; - private readonly _atlasTexture1: WebGLTexture; + private readonly _atlasTextures: WebGLTexture[]; private readonly _attributesBuffer: WebGLBuffer; private _atlas: ITextureAtlas | undefined; @@ -166,22 +166,18 @@ export class GlyphRenderer extends Disposable { gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); - // Setup empty texture atlas - this._atlasTexture = throwIfFalsy(gl.createTexture()); - this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture))); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); - - this._atlasTexture1 = throwIfFalsy(gl.createTexture()); - this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture1))); - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255])); + // Setup empty textures for all potential atlas pages + this._atlasTextures = []; + for (let i = 0; i < MAX_ATLAS_PAGES; i++) { + const texture = throwIfFalsy(gl.createTexture()); + this.register(toDisposable(() => gl.deleteTexture(texture))); + gl.activeTexture(gl.TEXTURE0 + i); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255])); + this._atlasTextures[i] = texture; + } // Allow drawing of transparent texture gl.enable(gl.BLEND); @@ -334,15 +330,17 @@ export class GlyphRenderer extends Disposable { const layerTextureUnits = new Int32Array([0, 1]); gl.uniform1iv(this._textureLocation, layerTextureUnits); gl.activeTexture(gl.TEXTURE0 + 0); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas); gl.generateMipmap(gl.TEXTURE_2D); - // TODO: Check if the particular texture page changed - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas1!); - gl.generateMipmap(gl.TEXTURE_2D); + if (this._atlas.pages.length > 1) { + // TODO: Check if the particular texture page changed + gl.activeTexture(gl.TEXTURE0 + 1); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[1]); + gl.generateMipmap(gl.TEXTURE_2D); + } } @@ -359,18 +357,20 @@ export class GlyphRenderer extends Disposable { this._atlas = atlas; gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas); gl.generateMipmap(gl.TEXTURE_2D); - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas1!); - gl.generateMipmap(gl.TEXTURE_2D); + if (atlas.pages.length > 1) { + gl.activeTexture(gl.TEXTURE0 + 1); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[1]); + gl.generateMipmap(gl.TEXTURE_2D); + } } public setDimensions(dimensions: IRenderDimensions): void { diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 25ecf3cb..0d291ff6 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -56,10 +56,10 @@ export class TextureAtlas implements ITextureAtlas { private _cacheMapCombined: FourKeyMap = new FourKeyMap(); // The texture that the atlas is drawn to - public cacheCanvas: HTMLCanvasElement; - public cacheCanvas1: HTMLCanvasElement | undefined; - private _cacheCtx: CanvasRenderingContext2D; - private _cacheCtx1: CanvasRenderingContext2D | undefined; + private _pages: AtlasPage[] = []; + public get pages(): HTMLCanvasElement[] { return this._pages.map(e => e.canvas); } + + public get cacheCanvas(): HTMLCanvasElement { return this._pages[0].canvas; } private _tmpCanvas: HTMLCanvasElement; // A temporary context that glyphs are drawn to before being transfered to the atlas. @@ -92,18 +92,9 @@ export class TextureAtlas implements ITextureAtlas { private readonly _config: ICharAtlasConfig, private readonly _unicodeService: IUnicodeService ) { - this.cacheCanvas = this._createCanvas(TEXTURE_WIDTH, TEXTURE_HEIGHT); - // The canvas needs alpha because we use clearColor to convert the background color to alpha. - // It might also contain some characters with transparent backgrounds if allowTransparency is - // set. - this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', { alpha: true })); - - this.cacheCanvas1 = this._createCanvas(TEXTURE_WIDTH, TEXTURE_HEIGHT); - this._cacheCtx1 = throwIfFalsy(this.cacheCanvas1.getContext('2d', { alpha: true })); - this._cacheCtx1.fillStyle = 'rgb(255, 255, 0)'; - this._cacheCtx1.fillRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); - - this._tmpCanvas = this._createCanvas( + this._pages.push(new AtlasPage(document)); + this._tmpCanvas = createCanvas( + document, this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2, this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 2 ); @@ -113,13 +104,6 @@ export class TextureAtlas implements ITextureAtlas { })); } - private _createCanvas(width: number, height: number): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; - } - public dispose(): void { if (this.cacheCanvas.parentElement) { this.cacheCanvas.parentElement.removeChild(this.cacheCanvas); @@ -159,7 +143,9 @@ export class TextureAtlas implements ITextureAtlas { if (this._currentRow.x === 0 && this._currentRow.y === 0) { return; } - this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + for (const page of this._pages) { + page.clear(); + } this._cacheMap.clear(); this._cacheMapCombined.clear(); this._currentRow.x = 0; @@ -689,7 +675,7 @@ export class TextureAtlas implements ITextureAtlas { activeRow.x += rasterizedGlyph.size.x; // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us - this._cacheCtx.putImageData( + this._pages[0].ctx.putImageData( imageData, rasterizedGlyph.texturePosition.x - this._workBoundingBox.left, rasterizedGlyph.texturePosition.y - this._workBoundingBox.top, @@ -792,6 +778,23 @@ export class TextureAtlas implements ITextureAtlas { } } +class AtlasPage { + public readonly canvas: HTMLCanvasElement; + public readonly ctx: CanvasRenderingContext2D; + + constructor(document: Document) { + this.canvas = createCanvas(document, TEXTURE_WIDTH, TEXTURE_HEIGHT); + // The canvas needs alpha because we use clearColor to convert the background color to alpha. + // It might also contain some characters with transparent backgrounds if allowTransparency is + // set. + this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true })); + } + + public clear(): void { + this.ctx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + } +} + /** * Makes a particular rgb color and colors that are nearly the same in an ImageData completely * transparent. @@ -846,3 +849,10 @@ function checkCompletelyTransparent(imageData: ImageData): boolean { } return true; } + +function createCanvas(document: Document, width: number, height: number): HTMLCanvasElement { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; +} diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index b5220e2c..f7ee968d 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -87,8 +87,10 @@ export interface IRenderer extends IDisposable { } export interface ITextureAtlas extends IDisposable { + /** @deprecated */ readonly cacheCanvas: HTMLCanvasElement; - readonly cacheCanvas1: HTMLCanvasElement | undefined; + + readonly pages: HTMLCanvasElement[]; hasCanvasChanged: boolean;