Prevent WebGL atlas page overflow

This commit is contained in:
Anthony Kim
2026-07-09 15:54:33 -07:00
parent 8237004d5c
commit 2b982bc39c
2 changed files with 45 additions and 26 deletions
+7 -1
View File
@@ -101,6 +101,7 @@ export class GlyphRenderer extends Disposable {
private _atlas: ITextureAtlas | undefined;
private _lastSeenPageLayoutVersion: number = -1;
private _pageOverflowWarned: boolean = false;
private _activeBuffer: number = 0;
private readonly _vertices: IVertices = {
count: 0,
@@ -375,7 +376,12 @@ export class GlyphRenderer extends Disposable {
// Bind the atlas page texture if they have changed. AtlasPage.version is globally
// monotonic, so a page object swap at the same index (which happens after a page merge)
// is detected by the same comparison.
for (let i = 0; i < this._atlas.pages.length; i++) {
const pageCount = Math.min(this._atlas.pages.length, this._atlasTextures.length);
if (this._atlas.pages.length > this._atlasTextures.length && !this._pageOverflowWarned) {
this._pageOverflowWarned = true;
this._logService.warn(`Atlas page count (${this._atlas.pages.length}) exceeds the renderer's texture capacity (${this._atlasTextures.length}), some glyphs will not render correctly`);
}
for (let i = 0; i < pageCount; i++) {
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
this._bindAtlasPageTexture(gl, this._atlas, i);
}
+38 -25
View File
@@ -152,7 +152,7 @@ export class TextureAtlas implements ITextureAtlas {
// microtask to ensure it does not interrupt textures that will be rendered in the current
// animation frame which would result in blank rendered areas. This is actually not that
// expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages)) {
if (TextureAtlas.maxAtlasPages && this._pages.length >= TextureAtlas.maxAtlasPages) {
// Find the set of the largest 4 images, below the maximum size, with the highest
// percentages used
const pagesBySize = this._pages.filter(e => {
@@ -180,31 +180,27 @@ export class TextureAtlas implements ITextureAtlas {
// Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
// effectively reduce page count and merging would cause issues.
if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {
const newPage = new AtlasPage(this._document, this._textureSize);
this._pages.push(newPage);
this._activePages.push(newPage);
this._onAddTextureAtlasCanvas.fire(newPage.canvas);
return newPage;
this._evictAllPages();
} else {
const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a - b);
const mergedPageIndex = this.pages.length - mergingPages.length;
// Merge into the new page
const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
mergedPage.version = ++AtlasPage.nextVersion;
// Delete the pages, shifting glyph texture pages as needed
for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
this._deletePage(sortedMergingPagesIndexes[i]);
}
// Add the new merged page to the end
this.pages.push(mergedPage);
// Request the model to be cleared to refresh all texture pages.
this._pageLayoutVersion++;
this._onAddTextureAtlasCanvas.fire(mergedPage.canvas);
}
const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a - b);
const mergedPageIndex = this.pages.length - mergingPages.length;
// Merge into the new page
const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
mergedPage.version = ++AtlasPage.nextVersion;
// Delete the pages, shifting glyph texture pages as needed
for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
this._deletePage(sortedMergingPagesIndexes[i]);
}
// Add the new merged page to the end
this.pages.push(mergedPage);
// Request the model to be cleared to refresh all texture pages.
this._pageLayoutVersion++;
this._onAddTextureAtlasCanvas.fire(mergedPage.canvas);
}
// All new atlas pages are created small as they are highly dynamic
@@ -254,6 +250,20 @@ export class TextureAtlas implements ITextureAtlas {
}
}
private _evictAllPages(): void {
for (const page of this._pages) {
this._onRemoveTextureAtlasCanvas.fire(page.canvas);
page.canvas.remove();
}
this._pages.length = 0;
this._activePages.length = 0;
this._overflowSizePage = undefined;
this._cacheMap.clear();
this._cacheMapCombined.clear();
this._didWarmUp = false;
this._pageLayoutVersion++;
}
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {
return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight, domContainer);
}
@@ -811,6 +821,9 @@ export class TextureAtlas implements ITextureAtlas {
// Create a new page for oversized glyphs as they come up
if (rasterizedGlyph.size.x > this._textureSize) {
if (!this._overflowSizePage) {
if (TextureAtlas.maxAtlasPages && this._pages.length >= TextureAtlas.maxAtlasPages) {
this._evictAllPages();
}
this._overflowSizePage = new AtlasPage(this._document, this._config.deviceMaxTextureSize);
this.pages.push(this._overflowSizePage);