@xterm/addon-webgl 0.20.0-beta.29 → 0.20.0-beta.290

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.
@@ -14,8 +14,8 @@ import { IdleTaskQueue } from 'common/TaskQueue';
14
14
  import { IColor } from 'common/Types';
15
15
  import { AttributeData } from 'common/buffer/AttributeData';
16
16
  import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
17
- import { IUnicodeService } from 'common/services/Services';
18
- import { Emitter } from 'vs/base/common/event';
17
+ import { ILogService, IUnicodeService } from 'common/services/Services';
18
+ import { Emitter } from 'common/Event';
19
19
 
20
20
  /**
21
21
  * A shared object which is used to draw nothing for a particular cell.
@@ -69,7 +69,7 @@ export class TextureAtlas implements ITextureAtlas {
69
69
  private _overflowSizePage: AtlasPage | undefined;
70
70
 
71
71
  private _tmpCanvas: HTMLCanvasElement;
72
- // A temporary context that glyphs are drawn to before being transfered to the atlas.
72
+ // A temporary context that glyphs are drawn to before being transferred to the atlas.
73
73
  private _tmpCtx: CanvasRenderingContext2D;
74
74
 
75
75
  private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 };
@@ -88,7 +88,8 @@ export class TextureAtlas implements ITextureAtlas {
88
88
  constructor(
89
89
  private readonly _document: Document,
90
90
  private readonly _config: ICharAtlasConfig,
91
- private readonly _unicodeService: IUnicodeService
91
+ private readonly _unicodeService: IUnicodeService,
92
+ private readonly _logService: ILogService
92
93
  ) {
93
94
  this._createNewPage();
94
95
  this._tmpCanvas = createCanvas(
@@ -108,6 +109,7 @@ export class TextureAtlas implements ITextureAtlas {
108
109
  page.canvas.remove();
109
110
  }
110
111
  this._onAddTextureAtlasCanvas.dispose();
112
+ this._onRemoveTextureAtlasCanvas.dispose();
111
113
  }
112
114
 
113
115
  public warmUp(): void {
@@ -119,7 +121,7 @@ export class TextureAtlas implements ITextureAtlas {
119
121
 
120
122
  private _doWarmUp(): void {
121
123
  // Pre-fill with ASCII 33-126, this is not urgent and done in idle callbacks
122
- const queue = new IdleTaskQueue();
124
+ const queue = new IdleTaskQueue(this._logService);
123
125
  for (let i = 33; i < 126; i++) {
124
126
  queue.enqueue(() => {
125
127
  if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) {
@@ -130,10 +132,8 @@ export class TextureAtlas implements ITextureAtlas {
130
132
  }
131
133
  }
132
134
 
133
- private _requestClearModel = false;
134
- public beginFrame(): boolean {
135
- return this._requestClearModel;
136
- }
135
+ private _pageLayoutVersion = 0;
136
+ public get pageLayoutVersion(): number { return this._pageLayoutVersion; }
137
137
 
138
138
  public clearTexture(): void {
139
139
  if (this._pages[0].currentRow.x === 0 && this._pages[0].currentRow.y === 0) {
@@ -148,7 +148,7 @@ export class TextureAtlas implements ITextureAtlas {
148
148
  }
149
149
 
150
150
  private _createNewPage(): AtlasPage {
151
- // Try merge the set of the 4 most used pages of the largest size. This is is deferred to a
151
+ // Try merge the set of the 4 most used pages of the largest size. This is deferred to a
152
152
  // microtask to ensure it does not interrupt textures that will be rendered in the current
153
153
  // animation frame which would result in blank rendered areas. This is actually not that
154
154
  // expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
@@ -176,12 +176,25 @@ export class TextureAtlas implements ITextureAtlas {
176
176
 
177
177
  // Gather details of the merge
178
178
  const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4);
179
- const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1);
179
+
180
+ // Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
181
+ // effectively reduce page count and merging would cause issues.
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();
185
+ const newPage = new AtlasPage(this._document, this._textureSize);
186
+ this._pages.push(newPage);
187
+ this._activePages.push(newPage);
188
+ this._onAddTextureAtlasCanvas.fire(newPage.canvas);
189
+ return newPage;
190
+ }
191
+
192
+ const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a - b);
180
193
  const mergedPageIndex = this.pages.length - mergingPages.length;
181
194
 
182
195
  // Merge into the new page
183
196
  const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
184
- mergedPage.version++;
197
+ mergedPage.version = ++AtlasPage.nextVersion;
185
198
 
186
199
  // Delete the pages, shifting glyph texture pages as needed
187
200
  for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
@@ -191,8 +204,8 @@ export class TextureAtlas implements ITextureAtlas {
191
204
  // Add the new merged page to the end
192
205
  this.pages.push(mergedPage);
193
206
 
194
- // Request the model to be cleared to refresh all texture pages.
195
- this._requestClearModel = true;
207
+ // Invalidate renderer models so all texture pages are refreshed.
208
+ this._pageLayoutVersion++;
196
209
  this._onAddTextureAtlasCanvas.fire(mergedPage.canvas);
197
210
  }
198
211
 
@@ -239,10 +252,27 @@ export class TextureAtlas implements ITextureAtlas {
239
252
  for (const g of adjustingPage.glyphs) {
240
253
  g.texturePage--;
241
254
  }
242
- adjustingPage.version++;
255
+ adjustingPage.version = ++AtlasPage.nextVersion;
243
256
  }
244
257
  }
245
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
+
246
276
  public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {
247
277
  return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight, domContainer);
248
278
  }
@@ -399,7 +429,7 @@ export class TextureAtlas implements ITextureAtlas {
399
429
  const cache = this._getContrastCache(dim);
400
430
  const adjustedColor = cache.getColor(bg, fg);
401
431
  if (adjustedColor !== undefined) {
402
- return adjustedColor || undefined;
432
+ return adjustedColor ?? undefined;
403
433
  }
404
434
 
405
435
  const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
@@ -514,7 +544,8 @@ export class TextureAtlas implements ITextureAtlas {
514
544
  // Draw custom characters if applicable
515
545
  let customGlyph = false;
516
546
  if (this._config.customGlyphs !== false) {
517
- customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css);
547
+ const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
548
+ customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, this._logService, backgroundColor.css, variantOffset);
518
549
  }
519
550
 
520
551
  // Whether to clear pixels based on a threshold difference between the glyph color and the
@@ -551,61 +582,67 @@ export class TextureAtlas implements ITextureAtlas {
551
582
  }
552
583
  this._tmpCtx.strokeStyle = this._getColorFromAnsiIndex(fg).css;
553
584
  }
585
+ this._tmpCtx.fillStyle = this._tmpCtx.strokeStyle;
554
586
 
555
587
  // Underline style/stroke
556
588
  this._tmpCtx.beginPath();
557
589
  const xLeft = padding;
558
- const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
559
- const yMid = yTop + lineWidth;
560
- const yBot = yTop + lineWidth * 2;
590
+ const yTopDefault = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
591
+ const yBotDefault = yTopDefault + lineWidth * 2;
561
592
  let nextOffset = this._workAttributeData.getUnderlineVariantOffset();
593
+ let yTop = 0;
594
+ let yBot = 0;
562
595
 
563
596
  for (let i = 0; i < chWidth; i++) {
597
+ let wasFilled = false;
564
598
  this._tmpCtx.save();
599
+ yTop = yTopDefault;
600
+ yBot = yBotDefault;
565
601
  const xChLeft = xLeft + i * this._config.deviceCellWidth;
566
602
  const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth;
567
- const xChMid = xChLeft + this._config.deviceCellWidth / 2;
568
603
  switch (this._workAttributeData.extended.underlineStyle) {
569
604
  case UnderlineStyle.DOUBLE:
570
- this._tmpCtx.moveTo(xChLeft, yTop);
571
- this._tmpCtx.lineTo(xChRight, yTop);
572
- this._tmpCtx.moveTo(xChLeft, yBot);
573
- this._tmpCtx.lineTo(xChRight, yBot);
605
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
606
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
607
+ this._tmpCtx.moveTo(xChLeft, yBotDefault);
608
+ this._tmpCtx.lineTo(xChRight, yBotDefault);
574
609
  break;
575
610
  case UnderlineStyle.CURLY:
576
- // Choose the bezier top and bottom based on the device pixel ratio, the curly line is
577
- // made taller when the line width is as otherwise it's not very clear otherwise.
578
- const yCurlyBot = lineWidth <= 1 ? yBot : Math.ceil(padding + this._config.deviceCharHeight - lineWidth / 2) - yOffset;
579
- const yCurlyTop = lineWidth <= 1 ? yTop : Math.ceil(padding + this._config.deviceCharHeight + lineWidth / 2) - yOffset;
580
- // Clip the left and right edges of the underline such that it can be drawn just outside
581
- // the edge of the cell to ensure a continuous stroke when there are multiple underlined
582
- // glyphs adjacent to one another.
611
+ yTop = this._config.deviceCharHeight + 1;
612
+ yBot = yTop + 3 * this._config.devicePixelRatio;
613
+
583
614
  const clipRegion = new Path2D();
584
615
  clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop);
585
616
  this._tmpCtx.clip(clipRegion);
586
- // Start 1/2 cell before and end 1/2 cells after to ensure a smooth curve with other
587
- // cells
588
- this._tmpCtx.moveTo(xChLeft - this._config.deviceCellWidth / 2, yMid);
589
- this._tmpCtx.bezierCurveTo(
590
- xChLeft - this._config.deviceCellWidth / 2, yCurlyTop,
591
- xChLeft, yCurlyTop,
592
- xChLeft, yMid
593
- );
594
- this._tmpCtx.bezierCurveTo(
595
- xChLeft, yCurlyBot,
596
- xChMid, yCurlyBot,
597
- xChMid, yMid
598
- );
599
- this._tmpCtx.bezierCurveTo(
600
- xChMid, yCurlyTop,
601
- xChRight, yCurlyTop,
602
- xChRight, yMid
603
- );
604
- this._tmpCtx.bezierCurveTo(
605
- xChRight, yCurlyBot,
606
- xChRight + this._config.deviceCellWidth / 2, yCurlyBot,
607
- xChRight + this._config.deviceCellWidth / 2, yMid
608
- );
617
+
618
+ // Draw a zigzag pattern, this is derived from the SVG used in monaco for the same
619
+ // style. The viewbox is 6x3 so scale it using that.
620
+ const cellW = this._config.deviceCellWidth;
621
+ const curlyH = (yBot - yTop);
622
+ const scaleX = cellW / 6;
623
+ const scaleY = curlyH / 3;
624
+
625
+ const polygons: number[][] = [
626
+ [0, 2, 1, 3, 2.4, 3, 0, 0.6],
627
+ [5.5, 0, 2.5, 3, 1.1, 3, 4.1, 0],
628
+ [4, 0, 6, 2, 6, 0.6, 5.4, 0],
629
+ ];
630
+
631
+ for (const polygon of polygons) {
632
+ this._tmpCtx.beginPath();
633
+ for (let i = 0; i < polygon.length; i += 2) {
634
+ const x = xChLeft + polygon[i] * scaleX;
635
+ const y = yBot - polygon[i + 1] * scaleY;
636
+ if (i === 0) {
637
+ this._tmpCtx.moveTo(x, y);
638
+ } else {
639
+ this._tmpCtx.lineTo(x, y);
640
+ }
641
+ }
642
+ this._tmpCtx.closePath();
643
+ this._tmpCtx.fill();
644
+ }
645
+ wasFilled = true;
609
646
  break;
610
647
  case UnderlineStyle.DOTTED:
611
648
  const offsetWidth = nextOffset === 0 ? 0 :
@@ -614,14 +651,14 @@ export class TextureAtlas implements ITextureAtlas {
614
651
  const isLineStart = nextOffset >= lineWidth ? false : true;
615
652
  if (isLineStart === false || offsetWidth === 0) {
616
653
  this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
617
- this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop);
618
- this._tmpCtx.lineTo(xChRight, yTop);
654
+ this._tmpCtx.moveTo(xChLeft + offsetWidth, yTopDefault);
655
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
619
656
  } else {
620
657
  this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
621
- this._tmpCtx.moveTo(xChLeft, yTop);
622
- this._tmpCtx.lineTo(xChLeft + offsetWidth, yTop);
623
- this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop);
624
- this._tmpCtx.lineTo(xChRight, yTop);
658
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
659
+ this._tmpCtx.lineTo(xChLeft + offsetWidth, yTopDefault);
660
+ this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTopDefault);
661
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
625
662
  }
626
663
  nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);
627
664
  break;
@@ -634,16 +671,18 @@ export class TextureAtlas implements ITextureAtlas {
634
671
  const gap = Math.floor(gapRatio * xChWidth);
635
672
  const end = xChWidth - line - gap;
636
673
  this._tmpCtx.setLineDash([line, gap, end]);
637
- this._tmpCtx.moveTo(xChLeft, yTop);
638
- this._tmpCtx.lineTo(xChRight, yTop);
674
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
675
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
639
676
  break;
640
677
  case UnderlineStyle.SINGLE:
641
678
  default:
642
- this._tmpCtx.moveTo(xChLeft, yTop);
643
- this._tmpCtx.lineTo(xChRight, yTop);
679
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
680
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
644
681
  break;
645
682
  }
646
- this._tmpCtx.stroke();
683
+ if (!wasFilled) {
684
+ this._tmpCtx.stroke();
685
+ }
647
686
  this._tmpCtx.restore();
648
687
  }
649
688
  this._tmpCtx.restore();
@@ -791,11 +830,15 @@ export class TextureAtlas implements ITextureAtlas {
791
830
  // Create a new page for oversized glyphs as they come up
792
831
  if (rasterizedGlyph.size.x > this._textureSize) {
793
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
+ }
794
837
  this._overflowSizePage = new AtlasPage(this._document, this._config.deviceMaxTextureSize);
795
838
  this.pages.push(this._overflowSizePage);
796
839
 
797
- // Request the model to be cleared to refresh all texture pages.
798
- this._requestClearModel = true;
840
+ // Invalidate renderer models so all texture pages are refreshed.
841
+ this._pageLayoutVersion++;
799
842
  this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas);
800
843
  }
801
844
  activePage = this._overflowSizePage;
@@ -916,7 +959,7 @@ export class TextureAtlas implements ITextureAtlas {
916
959
  rasterizedGlyph.size.y
917
960
  );
918
961
  activePage.addGlyph(rasterizedGlyph);
919
- activePage.version++;
962
+ activePage.version = ++AtlasPage.nextVersion;
920
963
 
921
964
  return rasterizedGlyph;
922
965
  }
@@ -1026,9 +1069,13 @@ class AtlasPage {
1026
1069
  }
1027
1070
 
1028
1071
  /**
1029
- * Used to check whether the canvas of the atlas page has changed.
1072
+ * Monotonically increasing across all atlas pages globally. Used to detect when the texture
1073
+ * unit at a given index needs to be re-uploaded — both for content changes within the same
1074
+ * page and for a page object swap at the same index (which happens after a page merge,
1075
+ * where a per-page counter could coincide with the previously-bound page's value).
1030
1076
  */
1031
- public version = 0;
1077
+ public static nextVersion: number = 0;
1078
+ public version = ++AtlasPage.nextVersion;
1032
1079
 
1033
1080
  // Texture atlas current positioning data. The texture packing strategy used is to fill from
1034
1081
  // left-to-right and top-to-bottom. When the glyph being written is less than half of the current
@@ -1052,17 +1099,33 @@ class AtlasPage {
1052
1099
  size: number,
1053
1100
  sourcePages?: AtlasPage[]
1054
1101
  ) {
1055
- if (sourcePages) {
1056
- for (const p of sourcePages) {
1057
- this._glyphs.push(...p.glyphs);
1058
- this._usedPixels += p._usedPixels;
1059
- }
1060
- }
1061
1102
  this.canvas = createCanvas(document, size, size);
1062
1103
  // The canvas needs alpha because we use clearColor to convert the background color to alpha.
1063
1104
  // It might also contain some characters with transparent backgrounds if allowTransparency is
1064
1105
  // set.
1065
1106
  this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));
1107
+ if (sourcePages) {
1108
+ if (sourcePages.length === 4) {
1109
+ // optimized for quadmerge
1110
+ this._glyphs = this._glyphs.concat(
1111
+ sourcePages[0].glyphs,
1112
+ sourcePages[1].glyphs,
1113
+ sourcePages[2].glyphs,
1114
+ sourcePages[3].glyphs
1115
+ );
1116
+ this._usedPixels = sourcePages[0]._usedPixels +
1117
+ sourcePages[1]._usedPixels +
1118
+ sourcePages[2]._usedPixels +
1119
+ sourcePages[3]._usedPixels;
1120
+ } else {
1121
+ // fallback for non quadmerges (should never be used)
1122
+ for (let i = 0; i < sourcePages.length; ++i) {
1123
+ this._glyphs = this._glyphs.concat(sourcePages[i].glyphs);
1124
+ this._usedPixels += sourcePages[i]._usedPixels;
1125
+ }
1126
+
1127
+ }
1128
+ }
1066
1129
  }
1067
1130
 
1068
1131
  public clear(): void {
@@ -1071,7 +1134,7 @@ class AtlasPage {
1071
1134
  this.currentRow.y = 0;
1072
1135
  this.currentRow.height = 0;
1073
1136
  this.fixedRows.length = 0;
1074
- this.version++;
1137
+ this.version = ++AtlasPage.nextVersion;
1075
1138
  }
1076
1139
  }
1077
1140
 
@@ -1097,7 +1160,7 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThreshol
1097
1160
  // were covered (fg=#8ae234, bg=#c4a000).
1098
1161
  const threshold = Math.floor((Math.abs(r - fgR) + Math.abs(g - fgG) + Math.abs(b - fgB)) / 12);
1099
1162
 
1100
- // Set alpha channel of relevent pixels to 0
1163
+ // Set alpha channel of relevant pixels to 0
1101
1164
  let isEmpty = true;
1102
1165
  for (let offset = 0; offset < imageData.data.length; offset += 4) {
1103
1166
  // Check exact match
package/src/Types.ts CHANGED
@@ -7,7 +7,7 @@ import { FontWeight } from '@xterm/xterm';
7
7
  import { IColorSet } from 'browser/Types';
8
8
  import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
9
9
  import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';
10
- import type { Event } from 'vs/base/common/event';
10
+ import type { IEvent } from 'common/Event';
11
11
 
12
12
  export interface IRenderModel {
13
13
  cells: Uint32Array;
@@ -28,6 +28,7 @@ export interface ICursorRenderModel {
28
28
  export interface IWebGL2RenderingContext extends WebGLRenderingContext {
29
29
  vertexAttribDivisor(index: number, divisor: number): void;
30
30
  createVertexArray(): IWebGLVertexArrayObject;
31
+ deleteVertexArray(vao: IWebGLVertexArrayObject): void;
31
32
  bindVertexArray(vao: IWebGLVertexArrayObject): void;
32
33
  drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void;
33
34
  }
@@ -58,8 +59,8 @@ export interface ICharAtlasConfig {
58
59
  export interface ITextureAtlas extends IDisposable {
59
60
  readonly pages: { canvas: HTMLCanvasElement, version: number }[];
60
61
 
61
- onAddTextureAtlasCanvas: Event<HTMLCanvasElement>;
62
- onRemoveTextureAtlasCanvas: Event<HTMLCanvasElement>;
62
+ onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
63
+ onRemoveTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
63
64
 
64
65
  /**
65
66
  * Warm up the texture atlas, adding common glyphs to avoid slowing early frame.
@@ -67,10 +68,12 @@ export interface ITextureAtlas extends IDisposable {
67
68
  warmUp(): void;
68
69
 
69
70
  /**
70
- * Call when a frame is being drawn, this will return true if the atlas was cleared to make room
71
- * 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.
72
75
  */
73
- beginFrame(): boolean;
76
+ readonly pageLayoutVersion: number;
74
77
 
75
78
  /**
76
79
  * Clear all glyphs from the texture atlas.
package/src/WebglAddon.ts CHANGED
@@ -7,15 +7,14 @@ import type { ITerminalAddon, Terminal } from '@xterm/xterm';
7
7
  import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
8
8
  import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
9
9
  import { ITerminal } from 'browser/Types';
10
- import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
10
+ import { Disposable, toDisposable } from 'common/Lifecycle';
11
11
  import { getSafariVersion, isSafari } from 'common/Platform';
12
12
  import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
13
13
  import { IWebGL2RenderingContext } from './Types';
14
14
  import { WebglRenderer } from './WebglRenderer';
15
- import { setTraceLogger } from 'common/services/LogService';
16
- import { Emitter, Event } from 'vs/base/common/event';
15
+ import { Emitter, EventUtils } from 'common/Event';
17
16
 
18
- export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi {
17
+ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi {
19
18
  private _terminal?: Terminal;
20
19
  private _renderer?: WebglRenderer;
21
20
 
@@ -41,7 +40,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
41
40
  };
42
41
  const gl = document.createElement('canvas').getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;
43
42
  if (!gl) {
44
- throw new Error('Webgl2 is only supported on Safari 16 and above');
43
+ throw new Error('WebGL2 is only supported on Safari 16 and above');
45
44
  }
46
45
  }
47
46
  super();
@@ -69,10 +68,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
69
68
  const logService: ILogService = unsafeCore._logService;
70
69
  const themeService: IThemeService = unsafeCore._themeService;
71
70
 
72
- // Set trace logger just in case it hasn't been yet which could happen when the addon is
73
- // bundled separately to the core module
74
- setTraceLogger(logService);
75
-
76
71
  this._renderer = this._register(new WebglRenderer(
77
72
  terminal,
78
73
  characterJoinerService,
@@ -80,15 +75,16 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
80
75
  coreBrowserService,
81
76
  coreService,
82
77
  decorationService,
78
+ logService,
83
79
  optionsService,
84
80
  themeService,
85
81
  this._customGlyphs,
86
82
  this._preserveDrawingBuffer
87
83
  ));
88
- this._register(Event.forward(this._renderer.onContextLoss, this._onContextLoss));
89
- this._register(Event.forward(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
90
- this._register(Event.forward(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas));
91
- this._register(Event.forward(this._renderer.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas));
84
+ this._register(EventUtils.forward(this._renderer.onContextLoss, this._onContextLoss));
85
+ this._register(EventUtils.forward(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
86
+ this._register(EventUtils.forward(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas));
87
+ this._register(EventUtils.forward(this._renderer.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas));
92
88
  renderService.setRenderer(this._renderer);
93
89
 
94
90
  this._register(toDisposable(() => {