@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xterm/addon-webgl",
3
- "version": "0.20.0-beta.29",
3
+ "version": "0.20.0-beta.291",
4
4
  "author": {
5
5
  "name": "The xterm.js authors",
6
6
  "url": "https://xtermjs.org/"
@@ -17,14 +17,14 @@
17
17
  "xterm.js"
18
18
  ],
19
19
  "scripts": {
20
- "build": "../../node_modules/.bin/tsc -p .",
20
+ "build": "../../node_modules/.bin/tsgo -p .",
21
21
  "prepackage": "npm run build",
22
22
  "package": "../../node_modules/.bin/webpack",
23
23
  "prepublishOnly": "npm run package",
24
24
  "start": "node ../../demo/start"
25
25
  },
26
- "commit": "4406043e6b95494ae03bd781b9f64c832687a3c2",
26
+ "commit": "904ae935269eef5ec6a1415b64463c3d02eff1eb",
27
27
  "peerDependencies": {
28
- "@xterm/xterm": "^6.1.0-beta.30"
28
+ "@xterm/xterm": "^6.1.0-beta.292"
29
29
  }
30
30
  }
@@ -3,10 +3,11 @@ import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
3
3
  import { ReadonlyColorSet } from 'browser/Types';
4
4
  import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
5
5
  import { IDecorationService, IOptionsService } from 'common/services/Services';
6
- import { ICellData } from 'common/Types';
6
+ import { ICellData } from 'common/buffer/Types';
7
7
  import { Terminal } from '@xterm/xterm';
8
8
  import { rgba } from 'common/Color';
9
9
  import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
10
+ import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
10
11
 
11
12
  // Work variables to avoid garbage collection
12
13
  let $fg = 0;
@@ -42,7 +43,7 @@ export class CellColorResolver {
42
43
  * Resolves colors for the cell, putting the result into the shared {@link result}. This resolves
43
44
  * overrides, inverse and selection for the cell which can then be used to feed into the renderer.
44
45
  */
45
- public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number): void {
46
+ public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number, deviceCellHeight: number): void {
46
47
  this.result.bg = cell.bg;
47
48
  this.result.fg = cell.fg;
48
49
  this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0;
@@ -63,7 +64,9 @@ export class CellColorResolver {
63
64
  const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15));
64
65
  $variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2);
65
66
  }
66
-
67
+ if ($variantOffset === 0 && blockPatternCodepoints.has(code)) {
68
+ $variantOffset = ((x * deviceCellWidth) % 2) * 2 + ((y * deviceCellHeight) % 2);
69
+ }
67
70
  // Apply decorations on the bottom layer
68
71
  this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => {
69
72
  if (d.backgroundColorRGB) {
@@ -8,6 +8,7 @@ import { ITerminalOptions, Terminal } from '@xterm/xterm';
8
8
  import { ITerminal, ReadonlyColorSet } from 'browser/Types';
9
9
  import { ICharAtlasConfig, ITextureAtlas } from './Types';
10
10
  import { generateConfig, configEquals } from './CharAtlasUtils';
11
+ import type { ILogService } from 'common/services/Services';
11
12
 
12
13
  interface ITextureAtlasCacheEntry {
13
14
  atlas: ITextureAtlas;
@@ -67,8 +68,9 @@ export function acquireTextureAtlas(
67
68
  }
68
69
 
69
70
  const core: ITerminal = (terminal as any)._core;
71
+ const logService = (core as any)._logService as ILogService;
70
72
  const newEntry: ITextureAtlasCacheEntry = {
71
- atlas: new TextureAtlas(document, newConfig, core.unicodeService),
73
+ atlas: new TextureAtlas(document, newConfig, core.unicodeService, logService),
72
74
  config: newConfig,
73
75
  ownedBy: [terminal]
74
76
  };
@@ -59,6 +59,7 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
59
59
  }
60
60
  }
61
61
  return a.devicePixelRatio === b.devicePixelRatio &&
62
+ a.deviceMaxTextureSize === b.deviceMaxTextureSize &&
62
63
  a.customGlyphs === b.customGlyphs &&
63
64
  a.lineHeight === b.lineHeight &&
64
65
  a.letterSpacing === b.letterSpacing &&
@@ -67,6 +68,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
67
68
  a.fontWeight === b.fontWeight &&
68
69
  a.fontWeightBold === b.fontWeightBold &&
69
70
  a.allowTransparency === b.allowTransparency &&
71
+ a.deviceCellWidth === b.deviceCellWidth &&
72
+ a.deviceCellHeight === b.deviceCellHeight &&
70
73
  a.deviceCharWidth === b.deviceCharWidth &&
71
74
  a.deviceCharHeight === b.deviceCharHeight &&
72
75
  a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
@@ -3,12 +3,15 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
+ import { RendererConstants } from 'browser/renderer/shared/Constants';
6
7
  import { ICoreBrowserService } from 'browser/services/Services';
7
8
 
8
- /**
9
- * The time between cursor blinks.
10
- */
11
- const BLINK_INTERVAL = 600;
9
+ const enum Constants {
10
+ /**
11
+ * The time between cursor blinks.
12
+ */
13
+ BLINK_INTERVAL = 600,
14
+ }
12
15
 
13
16
  export class CursorBlinkStateManager {
14
17
  public isCursorVisible: boolean;
@@ -16,6 +19,8 @@ export class CursorBlinkStateManager {
16
19
  private _animationFrame: number | undefined;
17
20
  private _blinkStartTimeout: number | undefined;
18
21
  private _blinkInterval: number | undefined;
22
+ private _idleTimeout: number | undefined;
23
+ private _isIdlePaused: boolean = false;
19
24
 
20
25
  /**
21
26
  * The time at which the animation frame was restarted, this is used on the
@@ -31,6 +36,7 @@ export class CursorBlinkStateManager {
31
36
  this.isCursorVisible = true;
32
37
  if (this._coreBrowserService.isFocused) {
33
38
  this._restartInterval();
39
+ this._resetIdleTimer();
34
40
  }
35
41
  }
36
42
 
@@ -49,9 +55,16 @@ export class CursorBlinkStateManager {
49
55
  this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
50
56
  this._animationFrame = undefined;
51
57
  }
58
+ if (this._idleTimeout) {
59
+ this._coreBrowserService.window.clearTimeout(this._idleTimeout);
60
+ this._idleTimeout = undefined;
61
+ }
52
62
  }
53
63
 
54
64
  public restartBlinkAnimation(): void {
65
+ if (this._isIdlePaused) {
66
+ this._resetIdleTimer();
67
+ }
55
68
  if (this.isPaused) {
56
69
  return;
57
70
  }
@@ -67,7 +80,7 @@ export class CursorBlinkStateManager {
67
80
  }
68
81
  }
69
82
 
70
- private _restartInterval(timeToStart: number = BLINK_INTERVAL): void {
83
+ private _restartInterval(timeToStart: number = Constants.BLINK_INTERVAL): void {
71
84
  // Clear any existing interval
72
85
  if (this._blinkInterval) {
73
86
  this._coreBrowserService.window.clearInterval(this._blinkInterval);
@@ -82,7 +95,7 @@ export class CursorBlinkStateManager {
82
95
  // Check if another animation restart was requested while this was being
83
96
  // started
84
97
  if (this._animationTimeRestarted) {
85
- const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
98
+ const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
86
99
  this._animationTimeRestarted = undefined;
87
100
  if (time > 0) {
88
101
  this._restartInterval(time);
@@ -103,7 +116,7 @@ export class CursorBlinkStateManager {
103
116
  if (this._animationTimeRestarted) {
104
117
  // calc time diff
105
118
  // Make restart interval do a setTimeout initially?
106
- const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
119
+ const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
107
120
  this._animationTimeRestarted = undefined;
108
121
  this._restartInterval(time);
109
122
  return;
@@ -115,12 +128,13 @@ export class CursorBlinkStateManager {
115
128
  this._renderCallback();
116
129
  this._animationFrame = undefined;
117
130
  });
118
- }, BLINK_INTERVAL);
131
+ }, Constants.BLINK_INTERVAL);
119
132
  }, timeToStart);
120
133
  }
121
134
 
122
135
  public pause(): void {
123
136
  this.isCursorVisible = true;
137
+ this._isIdlePaused = false;
124
138
  if (this._blinkInterval) {
125
139
  this._coreBrowserService.window.clearInterval(this._blinkInterval);
126
140
  this._blinkInterval = undefined;
@@ -133,6 +147,10 @@ export class CursorBlinkStateManager {
133
147
  this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
134
148
  this._animationFrame = undefined;
135
149
  }
150
+ if (this._idleTimeout) {
151
+ this._coreBrowserService.window.clearTimeout(this._idleTimeout);
152
+ this._idleTimeout = undefined;
153
+ }
136
154
  }
137
155
 
138
156
  public resume(): void {
@@ -141,6 +159,47 @@ export class CursorBlinkStateManager {
141
159
 
142
160
  this._animationTimeRestarted = undefined;
143
161
  this._restartInterval();
162
+ this._resetIdleTimer();
144
163
  this.restartBlinkAnimation();
145
164
  }
165
+
166
+ /**
167
+ * Resets the idle timer. If the terminal is idle for the idle timeout period,
168
+ * the cursor blinking will stop.
169
+ */
170
+ private _resetIdleTimer(): void {
171
+ this._isIdlePaused = false;
172
+ if (this._idleTimeout) {
173
+ this._coreBrowserService.window.clearTimeout(this._idleTimeout);
174
+ }
175
+ this._idleTimeout = this._coreBrowserService.window.setTimeout(() => {
176
+ this._stopBlinkingDueToIdle();
177
+ }, RendererConstants.CURSOR_BLINK_IDLE_TIMEOUT);
178
+ }
179
+
180
+ /**
181
+ * Stops cursor blinking due to idle timeout.
182
+ */
183
+ private _stopBlinkingDueToIdle(): void {
184
+ // Make cursor visible and stop blinking
185
+ this.isCursorVisible = true;
186
+ this._isIdlePaused = true;
187
+ if (this._blinkInterval) {
188
+ this._coreBrowserService.window.clearInterval(this._blinkInterval);
189
+ this._blinkInterval = undefined;
190
+ }
191
+ if (this._blinkStartTimeout) {
192
+ this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);
193
+ this._blinkStartTimeout = undefined;
194
+ }
195
+ if (this._animationFrame) {
196
+ this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
197
+ this._animationFrame = undefined;
198
+ }
199
+ // Clear the idle timeout as we've already acted on it
200
+ this._coreBrowserService.window.clearTimeout(this._idleTimeout);
201
+ this._idleTimeout = undefined;
202
+ // Trigger a render to show the cursor in its final visible state
203
+ this._renderCallback();
204
+ }
146
205
  }
@@ -3,7 +3,7 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
- import { toDisposable, IDisposable } from 'vs/base/common/lifecycle';
6
+ import { toDisposable, IDisposable } from 'common/Lifecycle';
7
7
 
8
8
  export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
9
9
  // Observe any resizes to the element and extract the actual pixel size of the element if the
@@ -5,11 +5,11 @@
5
5
  import { TextureAtlas } from './TextureAtlas';
6
6
  import { IRenderDimensions } from 'browser/renderer/shared/Types';
7
7
  import { NULL_CELL_CODE } from 'common/buffer/Constants';
8
- import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
8
+ import { Disposable, toDisposable } from 'common/Lifecycle';
9
9
  import { Terminal } from '@xterm/xterm';
10
10
  import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';
11
11
  import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
12
- import type { IOptionsService } from 'common/services/Services';
12
+ import type { ILogService, IOptionsService } from 'common/services/Services';
13
13
  import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
14
14
 
15
15
  interface IVertices {
@@ -78,9 +78,11 @@ void main() {
78
78
  }`);
79
79
  }
80
80
 
81
- const INDICES_PER_CELL = 11;
82
- const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT;
83
- const CELL_POSITION_INDICES = 2;
81
+ const enum Constants {
82
+ INDICES_PER_CELL = 11,
83
+ BYTES_PER_CELL = INDICES_PER_CELL * 4/* Float32Array.BYTES_PER_ELEMENT */,
84
+ CELL_POSITION_INDICES = 2
85
+ }
84
86
 
85
87
  // Work variables to avoid garbage collection
86
88
  let $i = 0;
@@ -98,6 +100,8 @@ export class GlyphRenderer extends Disposable {
98
100
  private readonly _attributesBuffer: WebGLBuffer;
99
101
 
100
102
  private _atlas: ITextureAtlas | undefined;
103
+ private _lastSeenPageLayoutVersion: number = -1;
104
+ private _pageOverflowWarned: boolean = false;
101
105
  private _activeBuffer: number = 0;
102
106
  private readonly _vertices: IVertices = {
103
107
  count: 0,
@@ -112,7 +116,8 @@ export class GlyphRenderer extends Disposable {
112
116
  private readonly _terminal: Terminal,
113
117
  private readonly _gl: IWebGL2RenderingContext,
114
118
  private _dimensions: IRenderDimensions,
115
- private readonly _optionsService: IOptionsService
119
+ private readonly _optionsService: IOptionsService,
120
+ private readonly _logService: ILogService
116
121
  ) {
117
122
  super();
118
123
 
@@ -125,7 +130,7 @@ export class GlyphRenderer extends Disposable {
125
130
  TextureAtlas.maxTextureSize = throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null);
126
131
  }
127
132
 
128
- this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages)));
133
+ this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages), this._logService));
129
134
  this._register(toDisposable(() => gl.deleteProgram(this._program)));
130
135
 
131
136
  // Uniform locations
@@ -134,8 +139,9 @@ export class GlyphRenderer extends Disposable {
134
139
  this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
135
140
 
136
141
  // Create and set the vertex array object
137
- this._vertexArrayObject = gl.createVertexArray();
138
- gl.bindVertexArray(this._vertexArrayObject);
142
+ const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
143
+ this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
144
+ gl.bindVertexArray(vertexArrayObject);
139
145
 
140
146
  // Setup a_unitquad, this defines the 4 vertices of a rectangle
141
147
  const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
@@ -160,22 +166,22 @@ export class GlyphRenderer extends Disposable {
160
166
  this._register(toDisposable(() => gl.deleteBuffer(this._attributesBuffer)));
161
167
  gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
162
168
  gl.enableVertexAttribArray(VertexAttribLocations.OFFSET);
163
- gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, BYTES_PER_CELL, 0);
169
+ gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 0);
164
170
  gl.vertexAttribDivisor(VertexAttribLocations.OFFSET, 1);
165
171
  gl.enableVertexAttribArray(VertexAttribLocations.SIZE);
166
- gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT);
172
+ gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT);
167
173
  gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1);
168
174
  gl.enableVertexAttribArray(VertexAttribLocations.TEXPAGE);
169
- gl.vertexAttribPointer(VertexAttribLocations.TEXPAGE, 1, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT);
175
+ gl.vertexAttribPointer(VertexAttribLocations.TEXPAGE, 1, gl.FLOAT, false, Constants.BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT);
170
176
  gl.vertexAttribDivisor(VertexAttribLocations.TEXPAGE, 1);
171
177
  gl.enableVertexAttribArray(VertexAttribLocations.TEXCOORD);
172
- gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 5 * Float32Array.BYTES_PER_ELEMENT);
178
+ gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 5 * Float32Array.BYTES_PER_ELEMENT);
173
179
  gl.vertexAttribDivisor(VertexAttribLocations.TEXCOORD, 1);
174
180
  gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE);
175
- gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 7 * Float32Array.BYTES_PER_ELEMENT);
181
+ gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 7 * Float32Array.BYTES_PER_ELEMENT);
176
182
  gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1);
177
183
  gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION);
178
- gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT);
184
+ gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, Constants.BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT);
179
185
  gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1);
180
186
 
181
187
  // Setup static uniforms
@@ -209,25 +215,36 @@ export class GlyphRenderer extends Disposable {
209
215
  this.handleResize();
210
216
  }
211
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
+ */
212
222
  public beginFrame(): boolean {
213
- 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;
214
231
  }
215
232
 
216
233
  public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
217
234
  // Since this function is called for every cell (`rows*cols`), it must be very optimized. It
218
235
  // should not instantiate any variables unless a new glyph is drawn to the cache where the
219
- // slight slowdown is acceptable for the developer ergonomics provided as it's a once of for
236
+ // slight slowdown is acceptable for the developer ergonomics provided as it's a one-off for
220
237
  // each glyph.
221
238
  this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, width, lastBg);
222
239
  }
223
240
 
224
241
  private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
225
- $i = (y * this._terminal.cols + x) * INDICES_PER_CELL;
242
+ $i = (y * this._terminal.cols + x) * Constants.INDICES_PER_CELL;
226
243
 
227
244
  // Exit early if this is a null character, allow space character to continue as it may have
228
245
  // underline/strikethrough styles
229
246
  if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {
230
- array.fill(0, $i, $i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES);
247
+ array.fill(0, $i, $i + Constants.INDICES_PER_CELL - 1 - Constants.CELL_POSITION_INDICES);
231
248
  return;
232
249
  }
233
250
 
@@ -288,7 +305,7 @@ export class GlyphRenderer extends Disposable {
288
305
 
289
306
  public clear(): void {
290
307
  const terminal = this._terminal;
291
- const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;
308
+ const newCount = terminal.cols * terminal.rows * Constants.INDICES_PER_CELL;
292
309
 
293
310
  // Clear vertices
294
311
  if (this._vertices.count !== newCount) {
@@ -310,7 +327,7 @@ export class GlyphRenderer extends Disposable {
310
327
  for (let x = 0; x < terminal.cols; x++) {
311
328
  this._vertices.attributes[i + 9] = x / terminal.cols;
312
329
  this._vertices.attributes[i + 10] = y / terminal.rows;
313
- i += INDICES_PER_CELL;
330
+ i += Constants.INDICES_PER_CELL;
314
331
  }
315
332
  }
316
333
  }
@@ -318,8 +335,8 @@ export class GlyphRenderer extends Disposable {
318
335
  public handleResize(): void {
319
336
  const gl = this._gl;
320
337
  gl.useProgram(this._program);
321
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
322
- gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height);
338
+ gl.viewport(0, 0, this._dimensions.device.canvas.width, this._dimensions.device.canvas.height);
339
+ gl.uniform2f(this._resolutionLocation, this._dimensions.device.canvas.width, this._dimensions.device.canvas.height);
323
340
  this.clear();
324
341
  }
325
342
 
@@ -346,8 +363,8 @@ export class GlyphRenderer extends Disposable {
346
363
  // - So we don't send vertices for all the line-ending whitespace to the GPU
347
364
  let bufferLength = 0;
348
365
  for (let y = 0; y < renderModel.lineLengths.length; y++) {
349
- const si = y * this._terminal.cols * INDICES_PER_CELL;
350
- const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL);
366
+ const si = y * this._terminal.cols * Constants.INDICES_PER_CELL;
367
+ const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * Constants.INDICES_PER_CELL);
351
368
  activeBuffer.set(sub, bufferLength);
352
369
  bufferLength += sub.length;
353
370
  }
@@ -356,19 +373,32 @@ export class GlyphRenderer extends Disposable {
356
373
  gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
357
374
  gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
358
375
 
359
- // Bind the atlas page texture if they have changed
360
- for (let i = 0; i < this._atlas.pages.length; i++) {
376
+ // Bind the atlas page texture if they have changed. AtlasPage.version is globally
377
+ // monotonic, so a page object swap at the same index (which happens after a page merge)
378
+ // is detected by the same comparison.
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++) {
361
386
  if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
362
387
  this._bindAtlasPageTexture(gl, this._atlas, i);
363
388
  }
364
389
  }
365
390
 
366
391
  // Draw the viewport
367
- gl.drawElementsInstanced(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL);
392
+ gl.drawElementsInstanced(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, bufferLength / Constants.INDICES_PER_CELL);
368
393
  }
369
394
 
370
395
  public setAtlas(atlas: ITextureAtlas): void {
371
396
  this._atlas = atlas;
397
+ this._lastSeenPageLayoutVersion = -1;
398
+ this.invalidateAtlasTextures();
399
+ }
400
+
401
+ public invalidateAtlasTextures(): void {
372
402
  for (const glTexture of this._atlasTextures) {
373
403
  glTexture.version = -1;
374
404
  }
@@ -379,8 +409,9 @@ export class GlyphRenderer extends Disposable {
379
409
  gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i].texture);
380
410
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
381
411
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
412
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
413
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
382
414
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[i].canvas);
383
- gl.generateMipmap(gl.TEXTURE_2D);
384
415
  this._atlasTextures[i].version = atlas.pages[i].version;
385
416
  }
386
417
 
@@ -7,13 +7,14 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types';
7
7
  import { IThemeService } from 'browser/services/Services';
8
8
  import { ReadonlyColorSet } from 'browser/Types';
9
9
  import { Attributes, FgFlags } from 'common/buffer/Constants';
10
- import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
10
+ import { Disposable, toDisposable } from 'common/Lifecycle';
11
11
  import { IColor } from 'common/Types';
12
12
  import { Terminal } from '@xterm/xterm';
13
- import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
13
+ import { RenderModelConstants } from './RenderModel';
14
14
  import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
15
15
  import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';
16
16
  import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
17
+ import type { ILogService } from 'common/services/Services';
17
18
 
18
19
  const enum VertexAttribLocations {
19
20
  POSITION = 0,
@@ -89,21 +90,23 @@ export class RectangleRenderer extends Disposable {
89
90
  private _terminal: Terminal,
90
91
  private _gl: IWebGL2RenderingContext,
91
92
  private _dimensions: IRenderDimensions,
92
- private readonly _themeService: IThemeService
93
+ private readonly _themeService: IThemeService,
94
+ private readonly _logService: ILogService
93
95
  ) {
94
96
  super();
95
97
 
96
98
  const gl = this._gl;
97
99
 
98
- this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
100
+ this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource, this._logService));
99
101
  this._register(toDisposable(() => gl.deleteProgram(this._program)));
100
102
 
101
103
  // Uniform locations
102
104
  this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
103
105
 
104
106
  // Create and set the vertex array object
105
- this._vertexArrayObject = gl.createVertexArray();
106
- gl.bindVertexArray(this._vertexArrayObject);
107
+ const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
108
+ this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
109
+ gl.bindVertexArray(vertexArrayObject);
107
110
 
108
111
  // Setup a_unitquad, this defines the 4 vertices of a rectangle
109
112
  const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
@@ -217,9 +220,9 @@ export class RectangleRenderer extends Disposable {
217
220
  currentFg = 0;
218
221
  currentInverse = false;
219
222
  for (x = 0; x < terminal.cols; x++) {
220
- modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
221
- bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET];
222
- fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET];
223
+ modelIndex = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
224
+ bg = model.cells[modelIndex + RenderModelConstants.BG_OFFSET];
225
+ fg = model.cells[modelIndex + RenderModelConstants.FG_OFFSET];
223
226
  inverse = !!(fg & FgFlags.INVERSE);
224
227
  if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) {
225
228
  // A rectangle needs to be drawn if going from non-default to another color
@@ -336,8 +339,9 @@ export class RectangleRenderer extends Disposable {
336
339
  }
337
340
  }
338
341
 
339
- if (vertices.attributes.length < offset + 4) {
340
- vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);
342
+ if (vertices.attributes.length < offset + INDICES_PER_RECTANGLE) {
343
+ // +1 for the viewport-clear rectangle at offset 0.
344
+ vertices.attributes = expandFloat32Array(vertices.attributes, (this._terminal.rows * this._terminal.cols + 1) * INDICES_PER_RECTANGLE);
341
345
  }
342
346
  $x1 = startX * this._dimensions.device.cell.width;
343
347
  $y1 = y * this._dimensions.device.cell.height;
@@ -7,10 +7,12 @@ import { ICursorRenderModel, IRenderModel } from './Types';
7
7
  import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
8
8
  import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
9
9
 
10
- export const RENDER_MODEL_INDICIES_PER_CELL = 4;
11
- export const RENDER_MODEL_BG_OFFSET = 1;
12
- export const RENDER_MODEL_FG_OFFSET = 2;
13
- export const RENDER_MODEL_EXT_OFFSET = 3;
10
+ export const enum RenderModelConstants {
11
+ INDICIES_PER_CELL = 4,
12
+ BG_OFFSET = 1,
13
+ FG_OFFSET = 2,
14
+ EXT_OFFSET = 3
15
+ }
14
16
 
15
17
  export const COMBINED_CHAR_BIT_MASK = 0x80000000;
16
18
 
@@ -27,7 +29,7 @@ export class RenderModel implements IRenderModel {
27
29
  }
28
30
 
29
31
  public resize(cols: number, rows: number): void {
30
- const indexCount = cols * rows * RENDER_MODEL_INDICIES_PER_CELL;
32
+ const indexCount = cols * rows * RenderModelConstants.INDICIES_PER_CELL;
31
33
  if (indexCount !== this.cells.length) {
32
34
  this.cells = new Uint32Array(indexCount);
33
35
  this.lineLengths = new Uint32Array(rows);