From 6a0b9bd209ada7d8355e577b0aa9fb19c7da168a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 30 Aug 2017 22:55:17 -0700 Subject: [PATCH] Only render fg cells if there are changes --- src/Types.ts | 1 + src/renderer/BackgroundRenderLayer.ts | 26 +++++++------------- src/renderer/ForegroundRenderLayer.ts | 34 +++++++++++++++++++-------- src/renderer/GridCache.ts | 20 ++++++++++++++++ 4 files changed, 53 insertions(+), 28 deletions(-) create mode 100644 src/renderer/GridCache.ts diff --git a/src/Types.ts b/src/Types.ts index 0753d80e..180dc5d6 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -16,5 +16,6 @@ export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type Charset = {[key: string]: string}; +// TODO: Add code here? export type CharData = [number, string, number]; export type LineData = CharData[]; diff --git a/src/renderer/BackgroundRenderLayer.ts b/src/renderer/BackgroundRenderLayer.ts index c1d9eac0..8577f58d 100644 --- a/src/renderer/BackgroundRenderLayer.ts +++ b/src/renderer/BackgroundRenderLayer.ts @@ -2,11 +2,12 @@ import { IRenderLayer } from './Interfaces'; import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; +import { GridCache } from './GridCache'; export class BackgroundRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; - private _currentState: number[][]; + private _state: GridCache; constructor(container: HTMLElement) { this._canvas = document.createElement('canvas'); @@ -14,8 +15,7 @@ export class BackgroundRenderLayer implements IRenderLayer { this._ctx = this._canvas.getContext('2d'); this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); - - this._currentState = []; + this._state = new GridCache(); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -23,18 +23,7 @@ export class BackgroundRenderLayer implements IRenderLayer { this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; - // Initialize current state grid - this._currentState = []; - for (let y = 0; y < terminal.rows; y++) { - if (this._currentState.length <= y) { - this._currentState.push([]); - } - for (let x = this._currentState[y].length; x < terminal.cols; x++) { - this._currentState[y].push(null); - } - this._currentState[y].length = terminal.cols; - } - this._currentState.length = terminal.rows; + this._state.resize(terminal.cols, terminal.rows); } public render(terminal: ITerminal, startRow: number, endRow: number): void { @@ -49,17 +38,18 @@ export class BackgroundRenderLayer implements IRenderLayer { const bg = data & 0x1ff; const flags = data >> 18; - const needsRefresh = (bg < 16 && this._currentState[y][x] !== bg) || this._currentState[y][x] !== null; + const cellState = this._state.cache[x][y]; + const needsRefresh = (bg < 16 && cellState !== bg) || cellState !== null; if (needsRefresh) { if (bg < 16) { this._ctx.save(); this._ctx.fillStyle = TANGO_COLORS[bg]; this._ctx.fillRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); this._ctx.restore(); - this._currentState[y][x] = bg; + this._state.cache[x][y] = bg; } else { this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); - this._currentState[y][x] = null; + this._state.cache[x][y] = null; } } } diff --git a/src/renderer/ForegroundRenderLayer.ts b/src/renderer/ForegroundRenderLayer.ts index 2ed0bd60..c6263247 100644 --- a/src/renderer/ForegroundRenderLayer.ts +++ b/src/renderer/ForegroundRenderLayer.ts @@ -3,11 +3,14 @@ import { IBuffer, ICharMeasure, ITerminal } from '../Interfaces'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer'; import { TANGO_COLORS } from './Color'; import { FLAGS } from './Types'; +import { GridCache } from './GridCache'; +import { CharData } from '../Types'; export class ForegroundRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D; private _charAtlas: ImageBitmap; + private _state: GridCache; private _charAtlasGenerator: CharAtlasGenerator; @@ -18,6 +21,7 @@ export class ForegroundRenderLayer implements IRenderLayer { this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio); container.appendChild(this._canvas); this._charAtlasGenerator = new CharAtlasGenerator(); + this._state = new GridCache(); } public resize(terminal: ITerminal, canvasWidth: number, canvasHeight: number, charSizeChanged: boolean): void { @@ -25,6 +29,7 @@ export class ForegroundRenderLayer implements IRenderLayer { this._canvas.height = canvasHeight * window.devicePixelRatio; this._canvas.style.width = `${canvasWidth}px`; this._canvas.style.height = `${canvasHeight}px`; + this._state.resize(terminal.cols, terminal.rows); if (charSizeChanged) { this._charAtlas = null; this._charAtlasGenerator.generate(terminal.charMeasure.width, terminal.charMeasure.height).then(bitmap => { @@ -54,21 +59,32 @@ export class ForegroundRenderLayer implements IRenderLayer { this._ctx.textBaseline = 'top'; this._ctx.font = `${16 * window.devicePixelRatio}px courier`; - // Clear out the old data - // TODO: This should be optimised, we don't want to rewrite every character - this._ctx.clearRect(0, startRow * scaledCharHeight, scaledCharWidth * terminal.cols, (endRow - startRow + 1) * scaledCharHeight); - for (let y = startRow; y <= endRow; y++) { let row = y + terminal.buffer.ydisp; let line = terminal.buffer.lines.get(row); for (let x = 0; x < terminal.cols; x++) { - const code: number = line[x][CHAR_DATA_CODE_INDEX]; + const charData = line[x]; + const code: number = charData[CHAR_DATA_CODE_INDEX]; + const char: string = charData[CHAR_DATA_CHAR_INDEX]; + const attr: number = charData[CHAR_DATA_ATTR_INDEX]; - if (!code) { + // Skip rendering if the character is identical + const state = this._state.cache[x][y]; + if (state && state[CHAR_DATA_CHAR_INDEX] === char && state[CHAR_DATA_ATTR_INDEX] === attr) { + // Skip render, contents are identical + this._state.cache[x][y] = charData; + continue; + } + this._state.cache[x][y] = charData; + + // Clear the old character + this._ctx.clearRect(x * scaledCharWidth, y * scaledCharHeight, scaledCharWidth, scaledCharHeight); + + // Skip rendering if the character is invisible + if (!code || code === 32/*' '*/) { continue; } - const attr: number = line[x][CHAR_DATA_ATTR_INDEX]; let fg = (attr >> 9) & 0x1ff; const flags = attr >> 18; @@ -90,8 +106,7 @@ export class ForegroundRenderLayer implements IRenderLayer { this._ctx.drawImage(this._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 char: string = line[x][CHAR_DATA_CHAR_INDEX]; - const width: number = line[x][CHAR_DATA_WIDTH_INDEX]; + const width: number = charData[CHAR_DATA_WIDTH_INDEX]; this._drawUnicodeChar(char, width, fg, x, y, scaledCharWidth, scaledCharHeight); } } @@ -103,7 +118,6 @@ export class ForegroundRenderLayer implements IRenderLayer { private _drawUnicodeChar(char: string, width: number, fg: number, x: number, y: number, scaledCharWidth: number, scaledCharHeight: number) { this._ctx.save(); - this._ctx.font = `${16 * window.devicePixelRatio}px courier`; this._ctx.textBaseline = 'top'; diff --git a/src/renderer/GridCache.ts b/src/renderer/GridCache.ts new file mode 100644 index 00000000..eba3dad6 --- /dev/null +++ b/src/renderer/GridCache.ts @@ -0,0 +1,20 @@ +export class GridCache { + public cache: T[][]; + + public constructor() { + this.cache = []; + } + + public resize(width: number, height: number) { + for (let x = 0; x < width; x++) { + if (this.cache.length <= x) { + this.cache.push([]); + } + for (let y = this.cache[x].length; y < height; y++) { + this.cache[x].push(null); + } + this.cache[x].length = height; + } + this.cache.length = width; + } +}