@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/README.md +1 -1
- package/lib/addon-webgl.js +1 -1
- package/lib/addon-webgl.js.map +1 -1
- package/lib/addon-webgl.mjs +5 -31
- package/lib/addon-webgl.mjs.map +4 -4
- package/package.json +4 -4
- package/src/CellColorResolver.ts +6 -3
- package/src/CharAtlasCache.ts +3 -1
- package/src/CharAtlasUtils.ts +3 -0
- package/src/CursorBlinkStateManager.ts +67 -8
- package/src/DevicePixelObserver.ts +1 -1
- package/src/GlyphRenderer.ts +60 -29
- package/src/RectangleRenderer.ts +15 -11
- package/src/RenderModel.ts +7 -5
- package/src/TextureAtlas.ts +147 -79
- package/src/Types.ts +9 -6
- package/src/WebglAddon.ts +9 -13
- package/src/WebglRenderer.ts +123 -36
- package/src/WebglUtils.ts +7 -6
- package/src/customGlyphs/CustomGlyphDefinitions.ts +76 -31
- package/src/customGlyphs/CustomGlyphRasterizer.ts +45 -20
- package/src/renderLayer/BaseRenderLayer.ts +1 -1
- package/typings/addon-webgl.d.ts +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xterm/addon-webgl",
|
|
3
|
-
"version": "0.20.0-beta.
|
|
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/
|
|
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": "
|
|
26
|
+
"commit": "904ae935269eef5ec6a1415b64463c3d02eff1eb",
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@xterm/xterm": "^6.1.0-beta.
|
|
28
|
+
"@xterm/xterm": "^6.1.0-beta.292"
|
|
29
29
|
}
|
|
30
30
|
}
|
package/src/CellColorResolver.ts
CHANGED
|
@@ -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) {
|
package/src/CharAtlasCache.ts
CHANGED
|
@@ -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
|
};
|
package/src/CharAtlasUtils.ts
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 '
|
|
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
|
package/src/GlyphRenderer.ts
CHANGED
|
@@ -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 '
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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,
|
|
322
|
-
gl.uniform2f(this._resolutionLocation,
|
|
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
|
-
|
|
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
|
|
package/src/RectangleRenderer.ts
CHANGED
|
@@ -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 '
|
|
10
|
+
import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
11
11
|
import { IColor } from 'common/Types';
|
|
12
12
|
import { Terminal } from '@xterm/xterm';
|
|
13
|
-
import {
|
|
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.
|
|
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) *
|
|
221
|
-
bg = model.cells[modelIndex +
|
|
222
|
-
fg = model.cells[modelIndex +
|
|
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 +
|
|
340
|
-
|
|
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;
|
package/src/RenderModel.ts
CHANGED
|
@@ -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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 *
|
|
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);
|