Merge pull request #2157 from Tyriar/1576_remove_static_atlas

Remove experimentalCharAtlas, make dynamic default
This commit is contained in:
Daniel Imms
2019-05-31 16:08:15 -07:00
committed by GitHub
14 changed files with 49 additions and 298 deletions
-1
View File
@@ -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'],
-2
View File
@@ -92,7 +92,6 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
bellSound: DEFAULT_BELL_SOUND,
bellStyle: 'none',
drawBoldTextInBrightColors: true,
experimentalCharAtlas: 'static',
fontFamily: 'courier-new, courier, monospace',
fontSize: 15,
fontWeight: 'normal',
@@ -495,7 +494,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
}
break;
case 'drawBoldTextInBrightColors':
case 'experimentalCharAtlas':
case 'letterSpacing':
case 'lineHeight':
case 'fontWeight':
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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 { }
+3 -11
View File
@@ -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
),
-129
View File
@@ -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<ImageBitmap> {
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}`;
}
+1 -3
View File
@@ -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 &&
+41 -4
View File
@@ -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<IGlyphCacheValue>;
@@ -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;
}
+1 -1
View File
@@ -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', () => {
+1 -1
View File
@@ -10,7 +10,7 @@ interface ILinkedListNode<T> {
value: T;
}
export default class LRUMap<T> {
export class LRUMap<T> {
private _map: { [key: number]: ILinkedListNode<T> } = {};
private _head: ILinkedListNode<T> = null;
private _tail: ILinkedListNode<T> = null;
-24
View File
@@ -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;
}
}
-101
View File
@@ -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;
}
}
-1
View File
@@ -22,7 +22,6 @@ export interface IGlyphIdentifier {
}
export interface ICharAtlasConfig {
type: 'none' | 'static' | 'dynamic';
devicePixelRatio: number;
fontSize: number;
fontFamily: string;
-18
View File
@@ -76,24 +76,6 @@ declare module 'xterm' {
*/
drawBoldTextInBrightColors?: 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.
*/