From 53c71b3579a644bc33a3e2fa8c21512342a66366 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 31 May 2019 10:54:23 -0700 Subject: [PATCH] Remove experimentalCharAtlas, make dynamic default The NoneCharAtlas is still available to swap in if we need it. Fixes #1576 --- demo/client.ts | 1 - src/Terminal.ts | 2 - src/renderer/BaseRenderLayer.ts | 2 +- src/renderer/atlas/BaseCharAtlas.ts | 2 +- src/renderer/atlas/CharAtlasCache.ts | 14 +-- src/renderer/atlas/CharAtlasGenerator.ts | 129 ----------------------- src/renderer/atlas/CharAtlasUtils.ts | 4 +- src/renderer/atlas/DynamicCharAtlas.ts | 45 +++++++- src/renderer/atlas/LRUMap.test.ts | 2 +- src/renderer/atlas/LRUMap.ts | 2 +- src/renderer/atlas/NoneCharAtlas.ts | 24 ----- src/renderer/atlas/StaticCharAtlas.ts | 101 ------------------ src/renderer/atlas/Types.ts | 1 - typings/xterm.d.ts | 18 ---- 14 files changed, 49 insertions(+), 298 deletions(-) delete mode 100644 src/renderer/atlas/CharAtlasGenerator.ts delete mode 100644 src/renderer/atlas/NoneCharAtlas.ts delete mode 100644 src/renderer/atlas/StaticCharAtlas.ts diff --git a/demo/client.ts b/demo/client.ts index 9e83e8bc..10d27c86 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -215,7 +215,6 @@ function initOptions(term: TerminalType): void { bellSound: null, bellStyle: ['none', 'sound'], cursorStyle: ['block', 'underline', 'bar'], - experimentalCharAtlas: ['none', 'static', 'dynamic'], fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], diff --git a/src/Terminal.ts b/src/Terminal.ts index b77df953..5ee34f62 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -93,7 +93,6 @@ const DEFAULT_OPTIONS: ITerminalOptions = { bellStyle: 'none', drawBoldTextInBrightColors: true, enableBold: true, - experimentalCharAtlas: 'static', fontFamily: 'courier-new, courier, monospace', fontSize: 15, fontWeight: 'normal', @@ -496,7 +495,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } break; case 'drawBoldTextInBrightColors': - case 'experimentalCharAtlas': case 'enableBold': case 'letterSpacing': case 'lineHeight': diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index c59a4f24..333ce895 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -8,7 +8,7 @@ import { ITerminal } from '../Types'; import { ICellData } from 'core/Types'; import { DEFAULT_COLOR } from 'common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; -import BaseCharAtlas from './atlas/BaseCharAtlas'; +import { BaseCharAtlas } from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'core/buffer/BufferLine'; import { IColorSet } from 'ui/Types'; diff --git a/src/renderer/atlas/BaseCharAtlas.ts b/src/renderer/atlas/BaseCharAtlas.ts index ee69b381..470736f1 100644 --- a/src/renderer/atlas/BaseCharAtlas.ts +++ b/src/renderer/atlas/BaseCharAtlas.ts @@ -6,7 +6,7 @@ import { IGlyphIdentifier } from './Types'; import { IDisposable } from 'xterm'; -export default abstract class BaseCharAtlas implements IDisposable { +export abstract class BaseCharAtlas implements IDisposable { private _didWarmUp: boolean = false; public dispose(): void { } diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index 1ac568cf..80c05d6f 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -5,19 +5,11 @@ import { ITerminal } from '../../Types'; import { generateConfig, configEquals } from './CharAtlasUtils'; -import BaseCharAtlas from './BaseCharAtlas'; -import DynamicCharAtlas from './DynamicCharAtlas'; -import NoneCharAtlas from './NoneCharAtlas'; -import StaticCharAtlas from './StaticCharAtlas'; +import { BaseCharAtlas } from './BaseCharAtlas'; +import { DynamicCharAtlas } from './DynamicCharAtlas'; import { ICharAtlasConfig } from './Types'; import { IColorSet } from 'ui/Types'; -const charAtlasImplementations = { - 'none': NoneCharAtlas, - 'static': StaticCharAtlas, - 'dynamic': DynamicCharAtlas -}; - interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; config: ICharAtlasConfig; @@ -72,7 +64,7 @@ export function acquireCharAtlas( } const newEntry: ICharAtlasCacheEntry = { - atlas: new charAtlasImplementations[terminal.options.experimentalCharAtlas]( + atlas: new DynamicCharAtlas( document, newConfig ), diff --git a/src/renderer/atlas/CharAtlasGenerator.ts b/src/renderer/atlas/CharAtlasGenerator.ts deleted file mode 100644 index d6e3fb0c..00000000 --- a/src/renderer/atlas/CharAtlasGenerator.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { FontWeight } from 'xterm'; -import { isFirefox, isSafari } from 'common/Platform'; -import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; -import { IColor } from 'ui/Types'; - -/** - * Generates a char atlas. - * @param context The window or worker context. - * @param canvasFactory A function to generate a canvas with a width or height. - * @param config The config for the new char atlas. - */ -export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement, config: ICharAtlasConfig): HTMLCanvasElement | Promise { - const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; - const canvas = canvasFactory( - /*255 ascii chars*/255 * cellWidth, - (/*default+default bold*/2 + /*0-15*/16 + /*0-15 bold*/16) * cellHeight - ); - const ctx = canvas.getContext('2d', {alpha: config.allowTransparency}); - - ctx.fillStyle = config.colors.background.css; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - ctx.save(); - ctx.fillStyle = config.colors.foreground.css; - ctx.font = getFont(config.fontWeight, config); - ctx.textBaseline = 'middle'; - - // Default color - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); - ctx.clip(); - ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2); - ctx.restore(); - } - // Default color bold - ctx.save(); - ctx.font = getFont(config.fontWeightBold, config); - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); - ctx.clip(); - ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5); - ctx.restore(); - } - ctx.restore(); - - // Colors 0-15 - ctx.font = getFont(config.fontWeight, config); - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - const y = (colorIndex + 2) * cellHeight; - // Draw ascii characters - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, y, cellWidth, cellHeight); - ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex].css; - ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); - ctx.restore(); - } - } - - // Colors 0-15 bold - ctx.font = getFont(config.fontWeightBold, config); - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - const y = (colorIndex + 2 + 16) * cellHeight; - // Draw ascii characters - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, y, cellWidth, cellHeight); - ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex].css; - ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); - ctx.restore(); - } - } - ctx.restore(); - - // Support is patchy for createImageBitmap at the moment, pass a canvas back - // if support is lacking as drawImage works there too. Firefox is also - // included here as ImageBitmap appears both buggy and has horrible - // performance (tested on v55). - if (!('createImageBitmap' in context) || isFirefox || isSafari) { - // Don't attempt to clear background colors if createImageBitmap is not supported - return canvas; - } - - const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - - // Remove the background color from the image so characters may overlap - clearColor(charAtlasImageData, config.colors.background); - - return context.createImageBitmap(charAtlasImageData); -} - -/** - * Makes a partiicular rgb color in an ImageData completely transparent. - * @returns True if the result is "empty", meaning all pixels are fully transparent. - */ -export function clearColor(imageData: ImageData, color: IColor): boolean { - let isEmpty = true; - const r = color.rgba >>> 24; - const g = color.rgba >>> 16 & 0xFF; - const b = color.rgba >>> 8 & 0xFF; - for (let offset = 0; offset < imageData.data.length; offset += 4) { - if (imageData.data[offset] === r && - imageData.data[offset + 1] === g && - imageData.data[offset + 2] === b) { - imageData.data[offset + 3] = 0; - } else { - isEmpty = false; - } - } - return isEmpty; -} - -function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string { - return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`; -} diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index cc137b3c..ab0f3240 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -21,7 +21,6 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number ansi: colors.ansi.slice(0, 16) }; return { - type: terminal.options.experimentalCharAtlas, devicePixelRatio: window.devicePixelRatio, scaledCharWidth, scaledCharHeight, @@ -40,8 +39,7 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean return false; } } - return a.type === b.type && - a.devicePixelRatio === b.devicePixelRatio && + return a.devicePixelRatio === b.devicePixelRatio && a.fontFamily === b.fontFamily && a.fontSize === b.fontSize && a.fontWeight === b.fontWeight && diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index 3acf1b9a..695162c1 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -4,10 +4,9 @@ */ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; -import BaseCharAtlas from './BaseCharAtlas'; +import { BaseCharAtlas } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'ui/ColorManager'; -import { clearColor } from './CharAtlasGenerator'; -import LRUMap from './LRUMap'; +import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'ui/Types'; @@ -54,7 +53,7 @@ export function getGlyphCacheKey(glyph: IGlyphIdentifier): number { return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); } -export default class DynamicCharAtlas extends BaseCharAtlas { +export class DynamicCharAtlas extends BaseCharAtlas { // An ordered map that we're using to keep track of where each glyph is in the atlas texture. // It's ordered so that we can determine when to remove the old entries. private _cacheMap: LRUMap; @@ -328,3 +327,41 @@ export default class DynamicCharAtlas extends BaseCharAtlas { this._bitmapCommitTimeout = null; } } + +// This is used for debugging the renderer, just swap out `new DynamicCharAtlas` with +// `new NoneCharAtlas`. +export class NoneCharAtlas extends BaseCharAtlas { + constructor(document: Document, config: ICharAtlasConfig) { + super(); + } + + public draw( + ctx: CanvasRenderingContext2D, + glyph: IGlyphIdentifier, + x: number, + y: number + ): boolean { + return false; + } +} + +/** + * Makes a partiicular rgb color in an ImageData completely transparent. + * @returns True if the result is "empty", meaning all pixels are fully transparent. + */ +function clearColor(imageData: ImageData, color: IColor): boolean { + let isEmpty = true; + const r = color.rgba >>> 24; + const g = color.rgba >>> 16 & 0xFF; + const b = color.rgba >>> 8 & 0xFF; + for (let offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } else { + isEmpty = false; + } + } + return isEmpty; +} diff --git a/src/renderer/atlas/LRUMap.test.ts b/src/renderer/atlas/LRUMap.test.ts index 197d1159..b24ad2af 100644 --- a/src/renderer/atlas/LRUMap.test.ts +++ b/src/renderer/atlas/LRUMap.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import LRUMap from './LRUMap'; +import { LRUMap } from './LRUMap'; describe('LRUMap', () => { it('can be used to store and retrieve values', () => { diff --git a/src/renderer/atlas/LRUMap.ts b/src/renderer/atlas/LRUMap.ts index d7e01ec6..0db5621f 100644 --- a/src/renderer/atlas/LRUMap.ts +++ b/src/renderer/atlas/LRUMap.ts @@ -10,7 +10,7 @@ interface ILinkedListNode { value: T; } -export default class LRUMap { +export class LRUMap { private _map: { [key: number]: ILinkedListNode } = {}; private _head: ILinkedListNode = null; private _tail: ILinkedListNode = null; diff --git a/src/renderer/atlas/NoneCharAtlas.ts b/src/renderer/atlas/NoneCharAtlas.ts deleted file mode 100644 index 308e3075..00000000 --- a/src/renderer/atlas/NoneCharAtlas.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - * - * A dummy CharAtlas implementation that always fails to draw characters. - */ - -import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; -import BaseCharAtlas from './BaseCharAtlas'; - -export default class NoneCharAtlas extends BaseCharAtlas { - constructor(document: Document, config: ICharAtlasConfig) { - super(); - } - - public draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean { - return false; - } -} diff --git a/src/renderer/atlas/StaticCharAtlas.ts b/src/renderer/atlas/StaticCharAtlas.ts deleted file mode 100644 index a806abd8..00000000 --- a/src/renderer/atlas/StaticCharAtlas.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { DIM_OPACITY, IGlyphIdentifier, ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; -import { generateStaticCharAtlasTexture } from './CharAtlasGenerator'; -import BaseCharAtlas from './BaseCharAtlas'; -import { is256Color } from './CharAtlasUtils'; -import { DEFAULT_COLOR } from 'common/Types'; - -export default class StaticCharAtlas extends BaseCharAtlas { - private _texture: HTMLCanvasElement | ImageBitmap; - - constructor(private _document: Document, private _config: ICharAtlasConfig) { - super(); - } - - private _canvasFactory = (width: number, height: number) => { - const canvas = this._document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - - // This is useful for debugging - // document.body.appendChild(canvas); - - return canvas; - } - - protected _doWarmUp(): void { - const result = generateStaticCharAtlasTexture(window, this._canvasFactory, this._config); - if (result instanceof HTMLCanvasElement) { - this._texture = result; - } else { - result.then(texture => { - this._texture = texture; - }); - } - } - - private _isCached(glyph: IGlyphIdentifier, colorIndex: number): boolean { - const isAscii = glyph.code < 256; - // A color is basic if it is one of the 4 bit ANSI colors. - const isBasicColor = glyph.fg < 16; - const isDefaultColor = glyph.fg === DEFAULT_COLOR; - const isDefaultBackground = glyph.bg === DEFAULT_COLOR; - return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic; - } - - public draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean { - // we're not warmed up yet - if (this._texture === null || this._texture === undefined) { - return false; - } - - let colorIndex = 0; - if (is256Color(glyph.fg)) { - colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0); - } else if (glyph.fg === DEFAULT_COLOR) { - // If default color and bold - if (glyph.bold) { - colorIndex = 1; - } - } - if (!this._isCached(glyph, colorIndex)) { - return false; - } - - ctx.save(); - - // ImageBitmap's draw about twice as fast as from a canvas - const charAtlasCellWidth = this._config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const charAtlasCellHeight = this._config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; - - // Apply alpha to dim the character - if (glyph.dim) { - ctx.globalAlpha = DIM_OPACITY; - } - - ctx.drawImage( - this._texture, - glyph.code * charAtlasCellWidth, - colorIndex * charAtlasCellHeight, - charAtlasCellWidth, - this._config.scaledCharHeight, - x, - y, - charAtlasCellWidth, - this._config.scaledCharHeight - ); - - ctx.restore(); - - return true; - } -} diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts index f09a2b95..5bfd83bf 100644 --- a/src/renderer/atlas/Types.ts +++ b/src/renderer/atlas/Types.ts @@ -22,7 +22,6 @@ export interface IGlyphIdentifier { } export interface ICharAtlasConfig { - type: 'none' | 'static' | 'dynamic'; devicePixelRatio: number; fontSize: number; fontFamily: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ac6cfc61..7ca072d7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -83,24 +83,6 @@ declare module 'xterm' { */ enableBold?: boolean; - /** - * What character atlas implementation to use. The character atlas caches drawn characters, - * speeding up rendering significantly. However, it can introduce some minor rendering - * artifacts. - * - * - 'none': Don't use an atlas. - * - 'static': Generate an atlas when the terminal starts or is reconfigured. This atlas will - * only contain ASCII characters in 16 colors. - * - 'dynamic': Generate an atlas using a LRU cache as characters are requested. Limited to - * ASCII characters (for now), but supports 256 colors. For characters covered by the static - * cache, it's slightly slower in comparison, since there's more overhead involved in - * managing the cache. - * - * Currently defaults to 'static'. This option may be removed in the future. If it is, passed - * parameters will be ignored. - */ - experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; - /** * The font size used to render text. */