From 00af63b271f9f29031e0494c0216bc5c6d0c5ee0 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Sep 2018 10:10:50 -0700 Subject: [PATCH] Expand glyph when needed --- src/renderer/atlas/DynamicCharAtlas.ts | 58 +++++++++++++++++++++----- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index 4516524f..e4f4dfb9 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -30,9 +30,21 @@ const TRANSPARENT_COLOR = { // cache. const FRAME_CACHE_DRAW_LIMIT = 100; +/** + * The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch + * the operation as window.createImageBitmap is asynchronous. + */ const GLYPH_BITMAP_COMMIT_DELAY = 100; -const GLYPH_BITMAP_COMMIT_LIMIT = 100; +/** + * The initial size of the queue used to track glyphs waiting on bitmap generation. + */ +const GLYPHS_WAITING_ON_BITMAP_QUEUE_INITIAL_SIZE = 100; + +/** + * When the limit of the bitmap queue is reached, the queue increases by this factor. + */ +const GLYPHS_WAITING_ON_BITMAP_QUEUE_INCREMENT_FACTOR = 2; interface IGlyphCacheValue { index: number; @@ -71,9 +83,16 @@ export default class DynamicCharAtlas extends BaseCharAtlas { private _drawToCacheCount: number = 0; - private _glyphsWaitingOnBitmap: Uint32Array = new Uint32Array(GLYPH_BITMAP_COMMIT_LIMIT); + // An array of glyph keys that are waiting on the bitmap to be generated. + private _glyphsWaitingOnBitmapQueue: Uint32Array = new Uint32Array(GLYPHS_WAITING_ON_BITMAP_QUEUE_INITIAL_SIZE); + + // The number of glyphs keys waiting on the bitmap to be generated. private _glyphsWaitingOnBitmapCount: number = 0; + + // The timeout that is used to batch bitmap generation so it's not requested for every new glyph. private _bitmapCommitTimeout: number | null = null; + + // The bitmap to draw from, this is much faster on other browsers than others. private _bitmap: ImageBitmap | null = null; constructor(document: Document, private _config: ICharAtlasConfig) { @@ -276,8 +295,8 @@ export default class DynamicCharAtlas extends BaseCharAtlas { // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us this._cacheCtx.putImageData(imageData, x, y); - this._glyphsWaitingOnBitmap[this._glyphsWaitingOnBitmapCount++] = getGlyphCacheKey(code, fg, bg, bold, dim, italic); - this._queueGenerateBitmap(); + // Add the glyph and queue it to the bitmap (if the browser supports it) + this._addGlyphToBitmap(code, fg, bg, bold, dim, italic); return { index, @@ -286,7 +305,14 @@ export default class DynamicCharAtlas extends BaseCharAtlas { }; } - private _queueGenerateBitmap(): void { + private _addGlyphToBitmap( + code: number, + bg: number, + fg: number, + bold: boolean, + dim: boolean, + italic: boolean + ): void { // Support is patchy for createImageBitmap at the moment, pass a canvas back // if support is lacking as drawImage works there too. Firefox is also // included here as ImageBitmap appears both buggy and has horrible @@ -295,7 +321,13 @@ export default class DynamicCharAtlas extends BaseCharAtlas { return; } - // Check if it's already queued + // Add the glyph to the queue, increasing the size of it if needed + if (this._glyphsWaitingOnBitmapCount >= this._glyphsWaitingOnBitmapQueue.length) { + this._expandGlyphWaitingOnBitmapQueue(); + } + this._glyphsWaitingOnBitmapQueue[this._glyphsWaitingOnBitmapCount++] = getGlyphCacheKey(code, fg, bg, bold, dim, italic); + + // Check if bitmap generation timeout already exists if (this._bitmapCommitTimeout !== null) { return; } @@ -303,6 +335,12 @@ export default class DynamicCharAtlas extends BaseCharAtlas { this._bitmapCommitTimeout = window.setTimeout(() => this._generateBitmap(), GLYPH_BITMAP_COMMIT_DELAY); } + private _expandGlyphWaitingOnBitmapQueue(): void { + const newQueue = new Uint32Array(this._glyphsWaitingOnBitmapQueue.length * GLYPHS_WAITING_ON_BITMAP_QUEUE_INCREMENT_FACTOR); + newQueue.set(this._glyphsWaitingOnBitmapQueue, 0); + this._glyphsWaitingOnBitmapQueue = newQueue; + } + private _generateBitmap(): void { const countAtGeneration = this._glyphsWaitingOnBitmapCount; window.createImageBitmap(this._cacheCanvas).then(bitmap => { @@ -311,16 +349,14 @@ export default class DynamicCharAtlas extends BaseCharAtlas { // Mark all new glyphs as in bitmap for (let i = 0; i < countAtGeneration; i++) { - const key = this._glyphsWaitingOnBitmap[i]; + const key = this._glyphsWaitingOnBitmapQueue[i]; this._cacheMap.get(key).inBitmap = true; - this._glyphsWaitingOnBitmap[i] = 0; + this._glyphsWaitingOnBitmapQueue[i] = 0; } // Fix up any glyphs that were added since image bitmap was created if (countAtGeneration > this._glyphsWaitingOnBitmapCount) { - // TODO: Verify this - // TODO: Use 2 arrays to speed up set? - this._glyphsWaitingOnBitmap.set(this._glyphsWaitingOnBitmap.subarray(countAtGeneration, this._glyphsWaitingOnBitmapCount - countAtGeneration), 0); + this._glyphsWaitingOnBitmapQueue.set(this._glyphsWaitingOnBitmapQueue.subarray(countAtGeneration, this._glyphsWaitingOnBitmapCount - countAtGeneration), 0); } this._glyphsWaitingOnBitmapCount -= countAtGeneration; });