diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 59730af6..f5ce46eb 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -122,6 +122,7 @@ export class GlyphRenderer extends Disposable { if (GlyphRenderer._maxAtlasPages === undefined) { GlyphRenderer._maxAtlasPages = throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number | null); } + this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(GlyphRenderer._maxAtlasPages))); this.register(toDisposable(() => gl.deleteProgram(this._program))); @@ -285,7 +286,8 @@ export class GlyphRenderer extends Disposable { } else { this._vertices.attributes.fill(0); } - for (let i = 0; i < this._vertices.attributesBuffers.length; i++) { + let i = 0; + for (; i < this._vertices.attributesBuffers.length; i++) { if (this._vertices.count !== newCount) { this._vertices.attributesBuffers[i] = new Float32Array(newCount); } else { @@ -293,7 +295,7 @@ export class GlyphRenderer extends Disposable { } } this._vertices.count = newCount; - let i = 0; + i = 0; for (let y = 0; y < terminal.rows; y++) { for (let x = 0; x < terminal.cols; x++) { this._vertices.attributes[i + 9] = x / terminal.cols; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 636be672..f1c9519f 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -7,7 +7,8 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; -import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; +import { createRenderDimensions, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ITerminal } from 'browser/Types'; diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 1d6802c4..a0ff276f 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -67,6 +67,19 @@ export class TextureAtlas implements ITextureAtlas { private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; private _workAttributeData: AttributeData = new AttributeData(); + private _textureSize: number = 512; + + /** + * Doubles the texture size of new atlas pages if allowed. + */ + private _increaseTextureSize(): void { + // 4096 is the minimum texture size in WebGL, but we still want the texture to be reasonably fast + // to upload. We could loosen this limit if it ever becomes a problem. + if (this._textureSize < 2048) { + this._textureSize *= 2; + } + } + private readonly _onAddTextureAtlasCanvas = new EventEmitter(); public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; @@ -75,7 +88,7 @@ export class TextureAtlas implements ITextureAtlas { private readonly _config: ICharAtlasConfig, private readonly _unicodeService: IUnicodeService ) { - const page = new AtlasPage(_document, _config.devicePixelRatio); + const page = new AtlasPage(_document, this._textureSize); this._pages.push(page); this._activePages.push(page); this._tmpCanvas = createCanvas( @@ -121,8 +134,11 @@ export class TextureAtlas implements ITextureAtlas { // TODO: Revisit when a new page gets added, this is only really needed now when there is no // room for a new row if (page.currentRow.y > Math.floor(page.canvas.height * 0.8)) { + if (this._pages.length === 4 || this._pages.length === 7) { + this._increaseTextureSize(); + } // TODO: Clear all pages and restart if the maximum page count is reached - const newPage = new AtlasPage(this._document, this._config.devicePixelRatio); + const newPage = new AtlasPage(this._document, this._textureSize); this._pages.push(newPage); this._activePages.push(newPage); this._onAddTextureAtlasCanvas.fire(newPage.canvas); @@ -597,7 +613,7 @@ export class TextureAtlas implements ITextureAtlas { } // TODO: Ideally this wouldn't pull dimensions off the first canvas - const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, restrictedPowerlineGlyph, customGlyph, padding, this._pages[0].canvas.width, this._pages[0].canvas.height); + const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, restrictedPowerlineGlyph, customGlyph, padding); // Find the best atlas row to use let activePage: AtlasPage; @@ -646,7 +662,10 @@ export class TextureAtlas implements ITextureAtlas { if (candidatePage === undefined) { // Creating a new page if there is no room // TODO: Share atlas page creation code - const newPage = new AtlasPage(this._document, this._config.devicePixelRatio); + if (this._pages.length === 4 || this._pages.length === 7) { + this._increaseTextureSize(); + } + const newPage = new AtlasPage(this._document, this._textureSize); this._pages.push(newPage); this._activePages.push(newPage); this._onAddTextureAtlasCanvas.fire(newPage.canvas); @@ -720,6 +739,10 @@ export class TextureAtlas implements ITextureAtlas { rasterizedGlyph.texturePositionClipSpace.x = activeRow.x / activePage.canvas.width; rasterizedGlyph.texturePositionClipSpace.y = activeRow.y / activePage.canvas.height; + // Fix the clipspace position as pages may be of differing size + rasterizedGlyph.sizeClipSpace.x /= activePage.canvas.width; + rasterizedGlyph.sizeClipSpace.y /= activePage.canvas.height; + // Update atlas current row, for fixed rows the glyph height will never be larger than the row // height activeRow.height = Math.max(activeRow.height, rasterizedGlyph.size.y); @@ -747,7 +770,7 @@ export class TextureAtlas implements ITextureAtlas { * @param imageData The image data to read. * @param boundingBox An IBoundingBox to put the clipped bounding box values. */ - private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number, pageWidth: number, pageHeight: number): IRasterizedGlyph { + private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number): IRasterizedGlyph { boundingBox.top = 0; const height = restrictedGlyph ? this._config.deviceCellHeight : this._tmpCanvas.height; const width = restrictedGlyph ? this._config.deviceCellWidth : allowedWidth; @@ -819,8 +842,8 @@ export class TextureAtlas implements ITextureAtlas { y: boundingBox.bottom - boundingBox.top + 1 }, sizeClipSpace: { - x: (boundingBox.right - boundingBox.left + 1) / pageWidth, - y: (boundingBox.bottom - boundingBox.top + 1) / pageHeight + x: (boundingBox.right - boundingBox.left + 1), + y: (boundingBox.bottom - boundingBox.top + 1) }, offset: { x: -boundingBox.left + padding + ((restrictedGlyph || customGlyph) ? Math.floor((this._config.deviceCellWidth - this._config.deviceCharWidth) / 2) : 0), @@ -859,10 +882,8 @@ class AtlasPage { constructor( document: Document, - dpr: number + size: number ) { - // TODO: Enforce a maximum texture size - const size = Math.pow(2, 8 + Math.max(1, dpr)); this.canvas = createCanvas(document, size, size); // 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 diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index 25999bcd..da455794 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -3,7 +3,7 @@ * @license MIT */ -export const DEFAULT_COLOR = 256; +export const DEFAULT_COLOR = 0; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); export const DEFAULT_EXT = 0;