@xterm/addon-webgl 0.20.0-beta.287 → 0.20.0-beta.289

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xterm/addon-webgl",
3
- "version": "0.20.0-beta.287",
3
+ "version": "0.20.0-beta.289",
4
4
  "author": {
5
5
  "name": "The xterm.js authors",
6
6
  "url": "https://xtermjs.org/"
@@ -23,8 +23,8 @@
23
23
  "prepublishOnly": "npm run package",
24
24
  "start": "node ../../demo/start"
25
25
  },
26
- "commit": "8aab310366549d8d865bd8fc4bd509051f2bb2a1",
26
+ "commit": "ce2169485677951c7701129516cbb68e01330d86",
27
27
  "peerDependencies": {
28
- "@xterm/xterm": "^6.1.0-beta.288"
28
+ "@xterm/xterm": "^6.1.0-beta.290"
29
29
  }
30
30
  }
@@ -100,6 +100,8 @@ export class GlyphRenderer extends Disposable {
100
100
  private readonly _attributesBuffer: WebGLBuffer;
101
101
 
102
102
  private _atlas: ITextureAtlas | undefined;
103
+ private _lastSeenPageLayoutVersion: number = -1;
104
+ private _pageOverflowWarned: boolean = false;
103
105
  private _activeBuffer: number = 0;
104
106
  private readonly _vertices: IVertices = {
105
107
  count: 0,
@@ -213,8 +215,19 @@ export class GlyphRenderer extends Disposable {
213
215
  this.handleResize();
214
216
  }
215
217
 
218
+ /**
219
+ * Call when a frame is being drawn. Returns whether the full model must be rebuilt before
220
+ * rendering this frame because the atlas page layout changed since this renderer last drew.
221
+ */
216
222
  public beginFrame(): boolean {
217
- return this._atlas ? this._atlas.beginFrame() : true;
223
+ if (!this._atlas) {
224
+ return true;
225
+ }
226
+ if (this._atlas.pageLayoutVersion !== this._lastSeenPageLayoutVersion) {
227
+ this._lastSeenPageLayoutVersion = this._atlas.pageLayoutVersion;
228
+ return true;
229
+ }
230
+ return false;
218
231
  }
219
232
 
220
233
  public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
@@ -363,7 +376,13 @@ export class GlyphRenderer extends Disposable {
363
376
  // Bind the atlas page texture if they have changed. AtlasPage.version is globally
364
377
  // monotonic, so a page object swap at the same index (which happens after a page merge)
365
378
  // is detected by the same comparison.
366
- for (let i = 0; i < this._atlas.pages.length; i++) {
379
+ // Clamp defensively in case the atlas unexpectedly exceeds texture capacity.
380
+ const pageCount = Math.min(this._atlas.pages.length, this._atlasTextures.length);
381
+ if (this._atlas.pages.length > this._atlasTextures.length && !this._pageOverflowWarned) {
382
+ this._pageOverflowWarned = true;
383
+ 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`);
384
+ }
385
+ for (let i = 0; i < pageCount; i++) {
367
386
  if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
368
387
  this._bindAtlasPageTexture(gl, this._atlas, i);
369
388
  }
@@ -375,6 +394,7 @@ export class GlyphRenderer extends Disposable {
375
394
 
376
395
  public setAtlas(atlas: ITextureAtlas): void {
377
396
  this._atlas = atlas;
397
+ this._lastSeenPageLayoutVersion = -1;
378
398
  this.invalidateAtlasTextures();
379
399
  }
380
400
 
@@ -132,12 +132,8 @@ export class TextureAtlas implements ITextureAtlas {
132
132
  }
133
133
  }
134
134
 
135
- private _requestClearModel = false;
136
- public beginFrame(): boolean {
137
- const result = this._requestClearModel;
138
- this._requestClearModel = false;
139
- return result;
140
- }
135
+ private _pageLayoutVersion = 0;
136
+ public get pageLayoutVersion(): number { return this._pageLayoutVersion; }
141
137
 
142
138
  public clearTexture(): void {
143
139
  if (this._pages[0].currentRow.x === 0 && this._pages[0].currentRow.y === 0) {
@@ -184,6 +180,8 @@ export class TextureAtlas implements ITextureAtlas {
184
180
  // Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
185
181
  // effectively reduce page count and merging would cause issues.
186
182
  if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {
183
+ // Evict instead of adding a page beyond the renderer's texture capacity.
184
+ this._evictAllPages();
187
185
  const newPage = new AtlasPage(this._document, this._textureSize);
188
186
  this._pages.push(newPage);
189
187
  this._activePages.push(newPage);
@@ -206,8 +204,8 @@ export class TextureAtlas implements ITextureAtlas {
206
204
  // Add the new merged page to the end
207
205
  this.pages.push(mergedPage);
208
206
 
209
- // Request the model to be cleared to refresh all texture pages.
210
- this._requestClearModel = true;
207
+ // Invalidate renderer models so all texture pages are refreshed.
208
+ this._pageLayoutVersion++;
211
209
  this._onAddTextureAtlasCanvas.fire(mergedPage.canvas);
212
210
  }
213
211
 
@@ -258,6 +256,23 @@ export class TextureAtlas implements ITextureAtlas {
258
256
  }
259
257
  }
260
258
 
259
+ private _evictAllPages(): void {
260
+ const startTime = performance.now();
261
+ const pageCount = this._pages.length;
262
+ for (const page of this._pages) {
263
+ this._onRemoveTextureAtlasCanvas.fire(page.canvas);
264
+ page.canvas.remove();
265
+ }
266
+ this._pages.length = 0;
267
+ this._activePages.length = 0;
268
+ this._overflowSizePage = undefined;
269
+ this._cacheMap.clear();
270
+ this._cacheMapCombined.clear();
271
+ this._didWarmUp = false;
272
+ this._pageLayoutVersion++;
273
+ this._logService.debug(`Evicted ${pageCount} WebGL atlas pages in ${(performance.now() - startTime).toFixed(2)}ms`);
274
+ }
275
+
261
276
  public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {
262
277
  return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight, domContainer);
263
278
  }
@@ -815,11 +830,15 @@ export class TextureAtlas implements ITextureAtlas {
815
830
  // Create a new page for oversized glyphs as they come up
816
831
  if (rasterizedGlyph.size.x > this._textureSize) {
817
832
  if (!this._overflowSizePage) {
833
+ // Make room for the oversized page without exceeding texture capacity.
834
+ if (TextureAtlas.maxAtlasPages && this._pages.length >= TextureAtlas.maxAtlasPages) {
835
+ this._evictAllPages();
836
+ }
818
837
  this._overflowSizePage = new AtlasPage(this._document, this._config.deviceMaxTextureSize);
819
838
  this.pages.push(this._overflowSizePage);
820
839
 
821
- // Request the model to be cleared to refresh all texture pages.
822
- this._requestClearModel = true;
840
+ // Invalidate renderer models so all texture pages are refreshed.
841
+ this._pageLayoutVersion++;
823
842
  this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas);
824
843
  }
825
844
  activePage = this._overflowSizePage;
package/src/Types.ts CHANGED
@@ -68,10 +68,12 @@ export interface ITextureAtlas extends IDisposable {
68
68
  warmUp(): void;
69
69
 
70
70
  /**
71
- * Call when a frame is being drawn, this will return true if the atlas was cleared to make room
72
- * for a new set of glyphs.
71
+ * Incremented whenever cached glyph texture page mappings may be stale, such as after atlas page
72
+ * merges or overflow page creation. Renderers compare this against their own last-seen value and
73
+ * rebuild their model when it changes; a shared atlas can have many renderers, so this must not
74
+ * be a consume-once flag.
73
75
  */
74
- beginFrame(): boolean;
76
+ readonly pageLayoutVersion: number;
75
77
 
76
78
  /**
77
79
  * Clear all glyphs from the texture atlas.