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

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) {
@@ -145,10 +145,15 @@ export class TextureAtlas implements ITextureAtlas {
145
145
  this._cacheMap.clear();
146
146
  this._cacheMapCombined.clear();
147
147
  this._didWarmUp = false;
148
+
149
+ // Invalidate renderer models so all texture pages are refreshed. The atlas may be shared, in
150
+ // which case the clearing renderer has cleared only its own model and every other owner still
151
+ // holds texture coords into the rows just wiped.
152
+ this._pageLayoutVersion++;
148
153
  }
149
154
 
150
155
  private _createNewPage(): AtlasPage {
151
- // Try merge the set of the 4 most used pages of the largest size. This is is deferred to a
156
+ // Try merge the set of the 4 most used pages of the largest size. This is deferred to a
152
157
  // microtask to ensure it does not interrupt textures that will be rendered in the current
153
158
  // animation frame which would result in blank rendered areas. This is actually not that
154
159
  // expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
@@ -176,12 +181,25 @@ export class TextureAtlas implements ITextureAtlas {
176
181
 
177
182
  // Gather details of the merge
178
183
  const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4);
179
- const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1);
184
+
185
+ // Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
186
+ // effectively reduce page count and merging would cause issues.
187
+ if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {
188
+ // Evict instead of adding a page beyond the renderer's texture capacity.
189
+ this._evictAllPages();
190
+ const newPage = new AtlasPage(this._document, this._textureSize);
191
+ this._pages.push(newPage);
192
+ this._activePages.push(newPage);
193
+ this._onAddTextureAtlasCanvas.fire(newPage.canvas);
194
+ return newPage;
195
+ }
196
+
197
+ const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a - b);
180
198
  const mergedPageIndex = this.pages.length - mergingPages.length;
181
199
 
182
200
  // Merge into the new page
183
201
  const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
184
- mergedPage.version++;
202
+ mergedPage.version = ++AtlasPage.nextVersion;
185
203
 
186
204
  // Delete the pages, shifting glyph texture pages as needed
187
205
  for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
@@ -191,8 +209,8 @@ export class TextureAtlas implements ITextureAtlas {
191
209
  // Add the new merged page to the end
192
210
  this.pages.push(mergedPage);
193
211
 
194
- // Request the model to be cleared to refresh all texture pages.
195
- this._requestClearModel = true;
212
+ // Invalidate renderer models so all texture pages are refreshed.
213
+ this._pageLayoutVersion++;
196
214
  this._onAddTextureAtlasCanvas.fire(mergedPage.canvas);
197
215
  }
198
216
 
@@ -239,8 +257,25 @@ export class TextureAtlas implements ITextureAtlas {
239
257
  for (const g of adjustingPage.glyphs) {
240
258
  g.texturePage--;
241
259
  }
242
- adjustingPage.version++;
260
+ adjustingPage.version = ++AtlasPage.nextVersion;
261
+ }
262
+ }
263
+
264
+ private _evictAllPages(): void {
265
+ const startTime = performance.now();
266
+ const pageCount = this._pages.length;
267
+ for (const page of this._pages) {
268
+ this._onRemoveTextureAtlasCanvas.fire(page.canvas);
269
+ page.canvas.remove();
243
270
  }
271
+ this._pages.length = 0;
272
+ this._activePages.length = 0;
273
+ this._overflowSizePage = undefined;
274
+ this._cacheMap.clear();
275
+ this._cacheMapCombined.clear();
276
+ this._didWarmUp = false;
277
+ this._pageLayoutVersion++;
278
+ this._logService.debug(`Evicted ${pageCount} WebGL atlas pages in ${(performance.now() - startTime).toFixed(2)}ms`);
244
279
  }
245
280
 
246
281
  public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {
@@ -399,7 +434,7 @@ export class TextureAtlas implements ITextureAtlas {
399
434
  const cache = this._getContrastCache(dim);
400
435
  const adjustedColor = cache.getColor(bg, fg);
401
436
  if (adjustedColor !== undefined) {
402
- return adjustedColor || undefined;
437
+ return adjustedColor ?? undefined;
403
438
  }
404
439
 
405
440
  const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
@@ -514,7 +549,8 @@ export class TextureAtlas implements ITextureAtlas {
514
549
  // Draw custom characters if applicable
515
550
  let customGlyph = false;
516
551
  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);
552
+ const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
553
+ 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
554
  }
519
555
 
520
556
  // Whether to clear pixels based on a threshold difference between the glyph color and the
@@ -551,61 +587,67 @@ export class TextureAtlas implements ITextureAtlas {
551
587
  }
552
588
  this._tmpCtx.strokeStyle = this._getColorFromAnsiIndex(fg).css;
553
589
  }
590
+ this._tmpCtx.fillStyle = this._tmpCtx.strokeStyle;
554
591
 
555
592
  // Underline style/stroke
556
593
  this._tmpCtx.beginPath();
557
594
  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;
595
+ const yTopDefault = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
596
+ const yBotDefault = yTopDefault + lineWidth * 2;
561
597
  let nextOffset = this._workAttributeData.getUnderlineVariantOffset();
598
+ let yTop = 0;
599
+ let yBot = 0;
562
600
 
563
601
  for (let i = 0; i < chWidth; i++) {
602
+ let wasFilled = false;
564
603
  this._tmpCtx.save();
604
+ yTop = yTopDefault;
605
+ yBot = yBotDefault;
565
606
  const xChLeft = xLeft + i * this._config.deviceCellWidth;
566
607
  const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth;
567
- const xChMid = xChLeft + this._config.deviceCellWidth / 2;
568
608
  switch (this._workAttributeData.extended.underlineStyle) {
569
609
  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);
610
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
611
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
612
+ this._tmpCtx.moveTo(xChLeft, yBotDefault);
613
+ this._tmpCtx.lineTo(xChRight, yBotDefault);
574
614
  break;
575
615
  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.
616
+ yTop = this._config.deviceCharHeight + 1;
617
+ yBot = yTop + 3 * this._config.devicePixelRatio;
618
+
583
619
  const clipRegion = new Path2D();
584
620
  clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop);
585
621
  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
- );
622
+
623
+ // Draw a zigzag pattern, this is derived from the SVG used in monaco for the same
624
+ // style. The viewbox is 6x3 so scale it using that.
625
+ const cellW = this._config.deviceCellWidth;
626
+ const curlyH = (yBot - yTop);
627
+ const scaleX = cellW / 6;
628
+ const scaleY = curlyH / 3;
629
+
630
+ const polygons: number[][] = [
631
+ [0, 2, 1, 3, 2.4, 3, 0, 0.6],
632
+ [5.5, 0, 2.5, 3, 1.1, 3, 4.1, 0],
633
+ [4, 0, 6, 2, 6, 0.6, 5.4, 0],
634
+ ];
635
+
636
+ for (const polygon of polygons) {
637
+ this._tmpCtx.beginPath();
638
+ for (let i = 0; i < polygon.length; i += 2) {
639
+ const x = xChLeft + polygon[i] * scaleX;
640
+ const y = yBot - polygon[i + 1] * scaleY;
641
+ if (i === 0) {
642
+ this._tmpCtx.moveTo(x, y);
643
+ } else {
644
+ this._tmpCtx.lineTo(x, y);
645
+ }
646
+ }
647
+ this._tmpCtx.closePath();
648
+ this._tmpCtx.fill();
649
+ }
650
+ wasFilled = true;
609
651
  break;
610
652
  case UnderlineStyle.DOTTED:
611
653
  const offsetWidth = nextOffset === 0 ? 0 :
@@ -614,14 +656,14 @@ export class TextureAtlas implements ITextureAtlas {
614
656
  const isLineStart = nextOffset >= lineWidth ? false : true;
615
657
  if (isLineStart === false || offsetWidth === 0) {
616
658
  this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
617
- this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop);
618
- this._tmpCtx.lineTo(xChRight, yTop);
659
+ this._tmpCtx.moveTo(xChLeft + offsetWidth, yTopDefault);
660
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
619
661
  } else {
620
662
  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);
663
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
664
+ this._tmpCtx.lineTo(xChLeft + offsetWidth, yTopDefault);
665
+ this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTopDefault);
666
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
625
667
  }
626
668
  nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);
627
669
  break;
@@ -634,16 +676,18 @@ export class TextureAtlas implements ITextureAtlas {
634
676
  const gap = Math.floor(gapRatio * xChWidth);
635
677
  const end = xChWidth - line - gap;
636
678
  this._tmpCtx.setLineDash([line, gap, end]);
637
- this._tmpCtx.moveTo(xChLeft, yTop);
638
- this._tmpCtx.lineTo(xChRight, yTop);
679
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
680
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
639
681
  break;
640
682
  case UnderlineStyle.SINGLE:
641
683
  default:
642
- this._tmpCtx.moveTo(xChLeft, yTop);
643
- this._tmpCtx.lineTo(xChRight, yTop);
684
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
685
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
644
686
  break;
645
687
  }
646
- this._tmpCtx.stroke();
688
+ if (!wasFilled) {
689
+ this._tmpCtx.stroke();
690
+ }
647
691
  this._tmpCtx.restore();
648
692
  }
649
693
  this._tmpCtx.restore();
@@ -791,11 +835,15 @@ export class TextureAtlas implements ITextureAtlas {
791
835
  // Create a new page for oversized glyphs as they come up
792
836
  if (rasterizedGlyph.size.x > this._textureSize) {
793
837
  if (!this._overflowSizePage) {
838
+ // Make room for the oversized page without exceeding texture capacity.
839
+ if (TextureAtlas.maxAtlasPages && this._pages.length >= TextureAtlas.maxAtlasPages) {
840
+ this._evictAllPages();
841
+ }
794
842
  this._overflowSizePage = new AtlasPage(this._document, this._config.deviceMaxTextureSize);
795
843
  this.pages.push(this._overflowSizePage);
796
844
 
797
- // Request the model to be cleared to refresh all texture pages.
798
- this._requestClearModel = true;
845
+ // Invalidate renderer models so all texture pages are refreshed.
846
+ this._pageLayoutVersion++;
799
847
  this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas);
800
848
  }
801
849
  activePage = this._overflowSizePage;
@@ -916,7 +964,7 @@ export class TextureAtlas implements ITextureAtlas {
916
964
  rasterizedGlyph.size.y
917
965
  );
918
966
  activePage.addGlyph(rasterizedGlyph);
919
- activePage.version++;
967
+ activePage.version = ++AtlasPage.nextVersion;
920
968
 
921
969
  return rasterizedGlyph;
922
970
  }
@@ -1026,9 +1074,13 @@ class AtlasPage {
1026
1074
  }
1027
1075
 
1028
1076
  /**
1029
- * Used to check whether the canvas of the atlas page has changed.
1077
+ * Monotonically increasing across all atlas pages globally. Used to detect when the texture
1078
+ * unit at a given index needs to be re-uploaded — both for content changes within the same
1079
+ * page and for a page object swap at the same index (which happens after a page merge,
1080
+ * where a per-page counter could coincide with the previously-bound page's value).
1030
1081
  */
1031
- public version = 0;
1082
+ public static nextVersion: number = 0;
1083
+ public version = ++AtlasPage.nextVersion;
1032
1084
 
1033
1085
  // Texture atlas current positioning data. The texture packing strategy used is to fill from
1034
1086
  // left-to-right and top-to-bottom. When the glyph being written is less than half of the current
@@ -1052,17 +1104,33 @@ class AtlasPage {
1052
1104
  size: number,
1053
1105
  sourcePages?: AtlasPage[]
1054
1106
  ) {
1055
- if (sourcePages) {
1056
- for (const p of sourcePages) {
1057
- this._glyphs.push(...p.glyphs);
1058
- this._usedPixels += p._usedPixels;
1059
- }
1060
- }
1061
1107
  this.canvas = createCanvas(document, size, size);
1062
1108
  // The canvas needs alpha because we use clearColor to convert the background color to alpha.
1063
1109
  // It might also contain some characters with transparent backgrounds if allowTransparency is
1064
1110
  // set.
1065
1111
  this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));
1112
+ if (sourcePages) {
1113
+ if (sourcePages.length === 4) {
1114
+ // optimized for quadmerge
1115
+ this._glyphs = this._glyphs.concat(
1116
+ sourcePages[0].glyphs,
1117
+ sourcePages[1].glyphs,
1118
+ sourcePages[2].glyphs,
1119
+ sourcePages[3].glyphs
1120
+ );
1121
+ this._usedPixels = sourcePages[0]._usedPixels +
1122
+ sourcePages[1]._usedPixels +
1123
+ sourcePages[2]._usedPixels +
1124
+ sourcePages[3]._usedPixels;
1125
+ } else {
1126
+ // fallback for non quadmerges (should never be used)
1127
+ for (let i = 0; i < sourcePages.length; ++i) {
1128
+ this._glyphs = this._glyphs.concat(sourcePages[i].glyphs);
1129
+ this._usedPixels += sourcePages[i]._usedPixels;
1130
+ }
1131
+
1132
+ }
1133
+ }
1066
1134
  }
1067
1135
 
1068
1136
  public clear(): void {
@@ -1071,7 +1139,7 @@ class AtlasPage {
1071
1139
  this.currentRow.y = 0;
1072
1140
  this.currentRow.height = 0;
1073
1141
  this.fixedRows.length = 0;
1074
- this.version++;
1142
+ this.version = ++AtlasPage.nextVersion;
1075
1143
  }
1076
1144
  }
1077
1145
 
@@ -1097,7 +1165,7 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThreshol
1097
1165
  // were covered (fg=#8ae234, bg=#c4a000).
1098
1166
  const threshold = Math.floor((Math.abs(r - fgR) + Math.abs(g - fgG) + Math.abs(b - fgB)) / 12);
1099
1167
 
1100
- // Set alpha channel of relevent pixels to 0
1168
+ // Set alpha channel of relevant pixels to 0
1101
1169
  let isEmpty = true;
1102
1170
  for (let offset = 0; offset < imageData.data.length; offset += 4) {
1103
1171
  // 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(() => {