From bc3bdacdaa3b220e400c9a597e5f59303e902196 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 1 Sep 2017 16:43:45 -0700 Subject: [PATCH] Add a char atlas cache to allow different styles+reuse across terminals --- src/renderer/BaseRenderLayer.ts | 8 +--- src/utils/CharAtlas.ts | 85 +++++++++++++++++++++++++++++++-- src/utils/TestUtils.test.ts | 3 ++ 3 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index d131bee0..e996a2a8 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -32,9 +32,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onThemeChanged(terminal: ITerminal, colorSet: IColorSet): void { this._charAtlas = null; - acquireCharAtlas(terminal, this.colors).then(bitmap => { - this._charAtlas = bitmap; - }); + acquireCharAtlas(terminal, this.colors).then(bitmap => this._charAtlas = bitmap); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -46,9 +44,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.style.height = `${canvasHeight}px`; if (charSizeChanged) { - acquireCharAtlas(terminal, this.colors).then(bitmap => { - this._charAtlas = bitmap; - }); + acquireCharAtlas(terminal, this.colors).then(bitmap => this._charAtlas = bitmap); } } diff --git a/src/utils/CharAtlas.ts b/src/utils/CharAtlas.ts index a375d468..18398efc 100644 --- a/src/utils/CharAtlas.ts +++ b/src/utils/CharAtlas.ts @@ -1,17 +1,92 @@ -import { ITerminal } from '../Interfaces'; +import { ITerminal, ITheme } from '../Interfaces'; import { IColorSet } from '../renderer/Interfaces'; +interface ICharAtlasConfig { + fontSize: number; + fontFamily: string; + scaledCharWidth: number; + scaledCharHeight: number; + colors: IColorSet; +} + +interface ICharAtlasCacheEntry { + bitmap: Promise; + config: ICharAtlasConfig; + ownedBy: ITerminal[]; +} + +let charAtlasCache: ICharAtlasCacheEntry[] = []; + export function acquireCharAtlas(terminal: ITerminal, colors: IColorSet): Promise { const scaledCharWidth = terminal.charMeasure.width * window.devicePixelRatio; const scaledCharHeight = terminal.charMeasure.height * window.devicePixelRatio; + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); - // TODO: Check to see if the atlas already exists in a cache + // Check to see if the terminal already owns this config + for (let i = 0; i < charAtlasCache.length; i++) { + const entry = charAtlasCache[i]; + const ownedByIndex = entry.ownedBy.indexOf(terminal); + if (ownedByIndex >= 0) { + if (configEquals(entry.config, newConfig)) { + return entry.bitmap; + } else { + // The configs differ, release the terminal from the entry + if (entry.ownedBy.length === 1) { + charAtlasCache.splice(i, 1); + } else { + entry.ownedBy.splice(ownedByIndex, 1); + } + break; + } + } + } - return generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.foreground, colors.ansi); + // Try match a char atlas from the cache + for (let i = 0; i < charAtlasCache.length; i++) { + const entry = charAtlasCache[i]; + if (configEquals(entry.config, newConfig)) { + // Add the terminal to the cache entry and return + entry.ownedBy.push(terminal); + return entry.bitmap; + } + } + + const newEntry: ICharAtlasCacheEntry = { + bitmap: generator.generate(scaledCharWidth, scaledCharHeight, terminal.options.fontSize, terminal.options.fontFamily, colors.foreground, colors.ansi), + config: newConfig, + ownedBy: [terminal] + }; + charAtlasCache.push(newEntry); + return newEntry.bitmap; } -export function releaseCharAtlas(terminal: ITerminal): void { - // TODO: Release the char atlas if it's no longer needed +function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { + const clonedColors = { + foreground: colors.foreground, + background: colors.background, + ansi: colors.ansi.slice(0, 16) + }; + return { + scaledCharWidth, + scaledCharHeight, + fontFamily: terminal.options.fontFamily, + fontSize: terminal.options.fontSize, + colors: clonedColors + }; +} + +function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { + for (let i = 0; i < a.colors.ansi.length; i++) { + if (a.colors.ansi[i] !== b.colors.ansi[i]) { + return false; + } + } + return a.fontFamily === b.fontFamily && + a.fontSize === b.fontSize && + a.scaledCharWidth === b.scaledCharWidth && + a.scaledCharHeight === b.scaledCharHeight && + a.colors.foreground === b.colors.foreground && + a.colors.background === b.colors.background; } class CharAtlasGenerator { diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index ae83aed4..3a66d49d 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -52,6 +52,9 @@ export class MockTerminal implements ITerminal { showCursor(): void { throw new Error('Method not implemented.'); } + refresh(start: number, end: number): void { + throw new Error('Method not implemented.'); + } blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData { const line: LineData = []; cols = cols || this.cols;