diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 1747c428..a55b090d 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -20,7 +20,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - private _charAtlas: BaseCharAtlas; + protected _charAtlas: BaseCharAtlas; constructor( private _container: HTMLElement, diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 2487a294..edef316b 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -54,6 +54,8 @@ export class TextRenderLayer extends BaseRenderLayer { return; } + this._charAtlas.beginFrame(); + for (let y = startRow; y <= endRow; y++) { const row = y + terminal.buffer.ydisp; const line = terminal.buffer.lines.get(row); diff --git a/src/renderer/atlas/BaseCharAtlas.ts b/src/renderer/atlas/BaseCharAtlas.ts index 8656f212..2680bd10 100644 --- a/src/renderer/atlas/BaseCharAtlas.ts +++ b/src/renderer/atlas/BaseCharAtlas.ts @@ -25,6 +25,15 @@ export default abstract class BaseCharAtlas { */ protected _doWarmUp(): void { } + /** + * Called when we start drawing a new frame. + * + * TODO: We rely on this getting called by TextRenderLayer. This should really be called by + * Renderer instead, but we need to make Renderer the source-of-truth for the char atlas, instead + * of BaseRenderLayer. + */ + public beginFrame(): void { } + /** * May be called before warmUp finishes, however it is okay for the implementation to * do nothing and return false in that case. diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index 63da8807..7a89dac9 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -19,6 +19,14 @@ const TRANSPARENT_COLOR = { rgba: 0, } +// Drawing to the cache is expensive: If we have to draw more than this number of glyphs to the +// cache in a single frame, give up on trying to cache anything else, and try to finish the current +// frame ASAP. +// +// This helps to limit the amount of damage a program can do when it would otherwise thrash the +// cache. +const DRAW_TO_CACHE_LIMIT = 100; + interface IGlyphCacheValue { index: number; isEmpty: boolean; @@ -44,6 +52,8 @@ export default class DynamicCharAtlas extends BaseCharAtlas { private _width: number; private _height: number; + private _drawToCacheCount: number = 0; + constructor(document: Document, private _config: ICharAtlasConfig) { super(); this._cacheCanvas = document.createElement('canvas'); @@ -69,6 +79,10 @@ export default class DynamicCharAtlas extends BaseCharAtlas { // document.body.appendChild(this._cacheCanvas); } + public beginFrame(): void { + this._drawToCacheCount = 0; + } + public draw( ctx: CanvasRenderingContext2D, glyph: IGlyphIdentifier, @@ -80,7 +94,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas { if (cacheValue != null) { this._drawFromCache(ctx, cacheValue, x, y); return true; - } else if (this._canCache(glyph)) { + } else if (this._canCache(glyph) && this._drawToCacheCount < DRAW_TO_CACHE_LIMIT) { let index; if (this._cacheMap.size < this._cacheMap.capacity) { index = this._cacheMap.size; @@ -142,6 +156,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas { // TODO: We do this (or something similar) in multiple places. We should split this off // into a shared function. private _drawToCache(glyph: IGlyphIdentifier, index: number): IGlyphCacheValue { + this._drawToCacheCount++; // draw the background let backgroundColor;