diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 6d493960..66b07fac 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -1,10 +1,16 @@ import { IRenderLayer } from './Interfaces'; import { ITerminal } from '../Interfaces'; +import { COLORS } from './Color'; export abstract class BaseRenderLayer implements IRenderLayer { protected _canvas: HTMLCanvasElement; protected _ctx: CanvasRenderingContext2D; + protected static _charAtlas: ImageBitmap; + private static _charAtlasCharWidth: number; + private static _charAtlasCharHeight: number; + private static _charAtlasGenerator: CharAtlasGenerator; + constructor(container: HTMLElement, id: string, zIndex: number) { this._canvas = document.createElement('canvas'); this._canvas.id = `xterm-${id}-layer`; @@ -12,6 +18,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); + + if (!BaseRenderLayer._charAtlasGenerator) { + BaseRenderLayer._charAtlasGenerator = new CharAtlasGenerator(); + } } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -19,5 +29,71 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; + // Only update the char atlas if the char size changed + if (charSizeChanged) { + // Only update the char atlas if an update for the right dimensions is not + // already in progress + if (BaseRenderLayer._charAtlasCharWidth !== terminal.charMeasure.width || + BaseRenderLayer._charAtlasCharHeight !== terminal.charMeasure.height) { + BaseRenderLayer._charAtlas = null; + BaseRenderLayer._charAtlasCharWidth = terminal.charMeasure.width; + BaseRenderLayer._charAtlasCharHeight = terminal.charMeasure.height; + BaseRenderLayer._charAtlasGenerator.generate(terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { + BaseRenderLayer._charAtlas = bitmap; + }); + } + } + } +} + +class CharAtlasGenerator { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + + constructor() { + this._canvas = document.createElement('canvas'); + this._ctx = this._canvas.getContext('2d'); + this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + } + + public generate(charWidth: number, charHeight: number): Promise { + const scaledCharWidth = Math.ceil(charWidth) * window.devicePixelRatio; + const scaledCharHeight = Math.ceil(charHeight) * window.devicePixelRatio; + + this._canvas.width = 255 * scaledCharWidth; + this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; + + this._ctx.save(); + this._ctx.fillStyle = '#ffffff'; + this._ctx.font = `${16 * window.devicePixelRatio}px courier`; + this._ctx.textBaseline = 'top'; + + // Default color + for (let i = 0; i < 256; i++) { + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); + } + + // Colors 0-15 + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + // colors 8-15 are bold + if (colorIndex === 8) { + this._ctx.font = `bold ${this._ctx.font}`; + } + const y = (colorIndex + 1) * scaledCharHeight; + // Clear rectangle as some fonts seem to draw over the bottom boundary + this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); + // Draw ascii characters + for (let i = 0; i < 256; i++) { + this._ctx.fillStyle = COLORS[colorIndex]; + this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); + } + } + this._ctx.restore(); + + const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); + const promise = window.createImageBitmap(charAtlasImageData); + // Clear the rect while the promise is in progress + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + return promise; } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index a073f69d..38700712 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -8,26 +8,16 @@ import { CharData } from '../Types'; import { BaseRenderLayer } from './BaseRenderLayer'; export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRenderLayer { - private _charAtlas: ImageBitmap; private _state: GridCache; - private _charAtlasGenerator: CharAtlasGenerator; - constructor(container: HTMLElement, zIndex: number) { super(container, 'fg', zIndex); - this._charAtlasGenerator = new CharAtlasGenerator(); this._state = new GridCache(); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { super.resize(terminal, canvasWidth, canvasHeight, charSizeChanged); this._state.resize(terminal.cols, terminal.rows); - if (charSizeChanged) { - this._charAtlas = null; - this._charAtlasGenerator.generate(terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { - this._charAtlas = bitmap; - }); - } } public render(terminal: ITerminal, startRow: number, endRow: number): void { @@ -36,7 +26,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende // TODO: Ensure that the render is eventually performed // Don't bother render until the atlas bitmap is ready - if (!this._charAtlas) { + if (!BaseRenderLayer._charAtlas) { return; } @@ -98,7 +88,7 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende if (code < 256 && (colorIndex > 0 || fg > 255)) { // ImageBitmap's draw about twice as fast as from a canvas - this._ctx.drawImage(this._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + this._ctx.drawImage(BaseRenderLayer._charAtlas, code * scaledCharWidth, colorIndex * scaledCharHeight, scaledCharWidth, scaledCharHeight, x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); } else { // TODO: Evaluate how long it takes to convert from a number const width: number = charData[CHAR_DATA_WIDTH_INDEX]; @@ -128,55 +118,3 @@ export class ForegroundRenderLayer extends BaseRenderLayer implements IDataRende this._ctx.restore(); } } - -class CharAtlasGenerator { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D; - - constructor() { - this._canvas = document.createElement('canvas'); - this._ctx = this._canvas.getContext('2d'); - this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - } - - public generate(charWidth: number, charHeight: number): Promise { - const scaledCharWidth = Math.ceil(charWidth) * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(charHeight) * window.devicePixelRatio; - - this._canvas.width = 255 * scaledCharWidth; - this._canvas.height = (/*default*/1 + /*0-15*/16) * scaledCharHeight; - - this._ctx.save(); - this._ctx.fillStyle = '#ffffff'; - this._ctx.font = `${16 * window.devicePixelRatio}px courier`; - this._ctx.textBaseline = 'top'; - - // Default color - for (let i = 0; i < 256; i++) { - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, 0); - } - - // Colors 0-15 - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - // colors 8-15 are bold - if (colorIndex === 8) { - this._ctx.font = `bold ${this._ctx.font}`; - } - const y = (colorIndex + 1) * scaledCharHeight; - // Clear rectangle as some fonts seem to draw over the bottom boundary - this._ctx.clearRect(0, y, this._canvas.width, scaledCharHeight); - // Draw ascii characters - for (let i = 0; i < 256; i++) { - this._ctx.fillStyle = COLORS[colorIndex]; - this._ctx.fillText(String.fromCharCode(i), i * scaledCharWidth, y); - } - } - this._ctx.restore(); - - const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); - const promise = window.createImageBitmap(charAtlasImageData); - // Clear the rect while the promise is in progress - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); - return promise; - } -}