Skip to content

Commit

Permalink
Add a per-frame limit on dynamic atlas writes
Browse files Browse the repository at this point in the history
This limits the amount of damage a pathological program can cause in
thrashing the LRU cache.

I ran the script described here:
#1327 (comment)

Without this patch, the entire output would get drawn in one frame that
took about 140 ms to draw. With this patch, it takes about 60 ms. It's
still nowhere as good as the static or none atlas implementations, but
it's not terrible, like it was.

This should have minimal impact on real-world applications: it'll just
take a little longer for the atlas to warm up.

The implementation is a little hacky, since it involves throwing some
code into TextRenderLayer that violates the separation of concerns, but
we can clean this up later.
  • Loading branch information
bgw committed Mar 27, 2018
1 parent 9e54b86 commit 83cf279
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/renderer/BaseRenderLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/TextRenderLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions src/renderer/atlas/BaseCharAtlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 16 additions & 1 deletion src/renderer/atlas/DynamicCharAtlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import LRUMap from './LRUMap';
const TEXTURE_WIDTH = 1024;
const TEXTURE_HEIGHT = 1024;

// 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;
Expand All @@ -39,6 +47,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');
Expand All @@ -64,6 +74,10 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
// document.body.appendChild(this._cacheCanvas);
}

public beginFrame(): void {
this._drawToCacheCount = 0;
}

public draw(
ctx: CanvasRenderingContext2D,
glyph: IGlyphIdentifier,
Expand All @@ -75,7 +89,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;
Expand Down Expand Up @@ -137,6 +151,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;
Expand Down

0 comments on commit 83cf279

Please sign in to comment.