@xterm/addon-webgl 0.20.0-beta.26 → 0.20.0-beta.260

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.
@@ -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)) {
@@ -132,7 +134,9 @@ export class TextureAtlas implements ITextureAtlas {
132
134
 
133
135
  private _requestClearModel = false;
134
136
  public beginFrame(): boolean {
135
- return this._requestClearModel;
137
+ const result = this._requestClearModel;
138
+ this._requestClearModel = false;
139
+ return result;
136
140
  }
137
141
 
138
142
  public clearTexture(): void {
@@ -176,12 +180,23 @@ export class TextureAtlas implements ITextureAtlas {
176
180
 
177
181
  // Gather details of the merge
178
182
  const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4);
183
+
184
+ // Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
185
+ // effectively reduce page count and merging would cause issues.
186
+ if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {
187
+ const newPage = new AtlasPage(this._document, this._textureSize);
188
+ this._pages.push(newPage);
189
+ this._activePages.push(newPage);
190
+ this._onAddTextureAtlasCanvas.fire(newPage.canvas);
191
+ return newPage;
192
+ }
193
+
179
194
  const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1);
180
195
  const mergedPageIndex = this.pages.length - mergingPages.length;
181
196
 
182
197
  // Merge into the new page
183
198
  const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
184
- mergedPage.version++;
199
+ mergedPage.version = ++AtlasPage.nextVersion;
185
200
 
186
201
  // Delete the pages, shifting glyph texture pages as needed
187
202
  for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
@@ -239,7 +254,7 @@ export class TextureAtlas implements ITextureAtlas {
239
254
  for (const g of adjustingPage.glyphs) {
240
255
  g.texturePage--;
241
256
  }
242
- adjustingPage.version++;
257
+ adjustingPage.version = ++AtlasPage.nextVersion;
243
258
  }
244
259
  }
245
260
 
@@ -399,7 +414,7 @@ export class TextureAtlas implements ITextureAtlas {
399
414
  const cache = this._getContrastCache(dim);
400
415
  const adjustedColor = cache.getColor(bg, fg);
401
416
  if (adjustedColor !== undefined) {
402
- return adjustedColor || undefined;
417
+ return adjustedColor ?? undefined;
403
418
  }
404
419
 
405
420
  const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
@@ -514,7 +529,8 @@ export class TextureAtlas implements ITextureAtlas {
514
529
  // Draw custom characters if applicable
515
530
  let customGlyph = false;
516
531
  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);
532
+ const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
533
+ 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
534
  }
519
535
 
520
536
  // Whether to clear pixels based on a threshold difference between the glyph color and the
@@ -551,61 +567,67 @@ export class TextureAtlas implements ITextureAtlas {
551
567
  }
552
568
  this._tmpCtx.strokeStyle = this._getColorFromAnsiIndex(fg).css;
553
569
  }
570
+ this._tmpCtx.fillStyle = this._tmpCtx.strokeStyle;
554
571
 
555
572
  // Underline style/stroke
556
573
  this._tmpCtx.beginPath();
557
574
  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;
575
+ const yTopDefault = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
576
+ const yBotDefault = yTopDefault + lineWidth * 2;
561
577
  let nextOffset = this._workAttributeData.getUnderlineVariantOffset();
578
+ let yTop = 0;
579
+ let yBot = 0;
562
580
 
563
581
  for (let i = 0; i < chWidth; i++) {
582
+ let wasFilled = false;
564
583
  this._tmpCtx.save();
584
+ yTop = yTopDefault;
585
+ yBot = yBotDefault;
565
586
  const xChLeft = xLeft + i * this._config.deviceCellWidth;
566
587
  const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth;
567
- const xChMid = xChLeft + this._config.deviceCellWidth / 2;
568
588
  switch (this._workAttributeData.extended.underlineStyle) {
569
589
  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);
590
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
591
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
592
+ this._tmpCtx.moveTo(xChLeft, yBotDefault);
593
+ this._tmpCtx.lineTo(xChRight, yBotDefault);
574
594
  break;
575
595
  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.
596
+ yTop = this._config.deviceCharHeight + 1;
597
+ yBot = yTop + 3 * this._config.devicePixelRatio;
598
+
583
599
  const clipRegion = new Path2D();
584
600
  clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop);
585
601
  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
- );
602
+
603
+ // Draw a zigzag pattern, this is derived from the SVG used in monaco for the same
604
+ // style. The viewbox is 6x3 so scale it using that.
605
+ const cellW = this._config.deviceCellWidth;
606
+ const curlyH = (yBot - yTop);
607
+ const scaleX = cellW / 6;
608
+ const scaleY = curlyH / 3;
609
+
610
+ const polygons: number[][] = [
611
+ [0, 2, 1, 3, 2.4, 3, 0, 0.6],
612
+ [5.5, 0, 2.5, 3, 1.1, 3, 4.1, 0],
613
+ [4, 0, 6, 2, 6, 0.6, 5.4, 0],
614
+ ];
615
+
616
+ for (const polygon of polygons) {
617
+ this._tmpCtx.beginPath();
618
+ for (let i = 0; i < polygon.length; i += 2) {
619
+ const x = xChLeft + polygon[i] * scaleX;
620
+ const y = yBot - polygon[i + 1] * scaleY;
621
+ if (i === 0) {
622
+ this._tmpCtx.moveTo(x, y);
623
+ } else {
624
+ this._tmpCtx.lineTo(x, y);
625
+ }
626
+ }
627
+ this._tmpCtx.closePath();
628
+ this._tmpCtx.fill();
629
+ }
630
+ wasFilled = true;
609
631
  break;
610
632
  case UnderlineStyle.DOTTED:
611
633
  const offsetWidth = nextOffset === 0 ? 0 :
@@ -614,14 +636,14 @@ export class TextureAtlas implements ITextureAtlas {
614
636
  const isLineStart = nextOffset >= lineWidth ? false : true;
615
637
  if (isLineStart === false || offsetWidth === 0) {
616
638
  this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
617
- this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop);
618
- this._tmpCtx.lineTo(xChRight, yTop);
639
+ this._tmpCtx.moveTo(xChLeft + offsetWidth, yTopDefault);
640
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
619
641
  } else {
620
642
  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);
643
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
644
+ this._tmpCtx.lineTo(xChLeft + offsetWidth, yTopDefault);
645
+ this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTopDefault);
646
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
625
647
  }
626
648
  nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);
627
649
  break;
@@ -634,16 +656,18 @@ export class TextureAtlas implements ITextureAtlas {
634
656
  const gap = Math.floor(gapRatio * xChWidth);
635
657
  const end = xChWidth - line - gap;
636
658
  this._tmpCtx.setLineDash([line, gap, end]);
637
- this._tmpCtx.moveTo(xChLeft, yTop);
638
- this._tmpCtx.lineTo(xChRight, yTop);
659
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
660
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
639
661
  break;
640
662
  case UnderlineStyle.SINGLE:
641
663
  default:
642
- this._tmpCtx.moveTo(xChLeft, yTop);
643
- this._tmpCtx.lineTo(xChRight, yTop);
664
+ this._tmpCtx.moveTo(xChLeft, yTopDefault);
665
+ this._tmpCtx.lineTo(xChRight, yTopDefault);
644
666
  break;
645
667
  }
646
- this._tmpCtx.stroke();
668
+ if (!wasFilled) {
669
+ this._tmpCtx.stroke();
670
+ }
647
671
  this._tmpCtx.restore();
648
672
  }
649
673
  this._tmpCtx.restore();
@@ -916,7 +940,7 @@ export class TextureAtlas implements ITextureAtlas {
916
940
  rasterizedGlyph.size.y
917
941
  );
918
942
  activePage.addGlyph(rasterizedGlyph);
919
- activePage.version++;
943
+ activePage.version = ++AtlasPage.nextVersion;
920
944
 
921
945
  return rasterizedGlyph;
922
946
  }
@@ -1026,9 +1050,13 @@ class AtlasPage {
1026
1050
  }
1027
1051
 
1028
1052
  /**
1029
- * Used to check whether the canvas of the atlas page has changed.
1053
+ * Monotonically increasing across all atlas pages globally. Used to detect when the texture
1054
+ * unit at a given index needs to be re-uploaded — both for content changes within the same
1055
+ * page and for a page object swap at the same index (which happens after a page merge,
1056
+ * where a per-page counter could coincide with the previously-bound page's value).
1030
1057
  */
1031
- public version = 0;
1058
+ public static nextVersion: number = 0;
1059
+ public version = ++AtlasPage.nextVersion;
1032
1060
 
1033
1061
  // Texture atlas current positioning data. The texture packing strategy used is to fill from
1034
1062
  // left-to-right and top-to-bottom. When the glyph being written is less than half of the current
@@ -1071,7 +1099,7 @@ class AtlasPage {
1071
1099
  this.currentRow.y = 0;
1072
1100
  this.currentRow.height = 0;
1073
1101
  this.fixedRows.length = 0;
1074
- this.version++;
1102
+ this.version = ++AtlasPage.nextVersion;
1075
1103
  }
1076
1104
  }
1077
1105
 
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.
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
 
@@ -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(() => {