@xterm/addon-webgl 0.17.0-beta.1
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/LICENSE +19 -0
- package/README.md +41 -0
- package/lib/addon-webgl.js +2 -0
- package/lib/addon-webgl.js.map +1 -0
- package/package.json +28 -0
- package/src/GlyphRenderer.ts +381 -0
- package/src/RectangleRenderer.ts +382 -0
- package/src/RenderModel.ts +41 -0
- package/src/TypedArray.ts +32 -0
- package/src/Types.d.ts +33 -0
- package/src/WebglAddon.ts +93 -0
- package/src/WebglRenderer.ts +646 -0
- package/src/WebglUtils.ts +63 -0
- package/src/renderLayer/BaseRenderLayer.ts +220 -0
- package/src/renderLayer/LinkRenderLayer.ts +82 -0
- package/src/renderLayer/Types.ts +55 -0
- package/typings/addon-webgl.d.ts +48 -0
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xterm/addon-webgl",
|
|
3
|
+
"version": "0.17.0-beta.1",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "The xterm.js authors",
|
|
6
|
+
"url": "https://xtermjs.org/"
|
|
7
|
+
},
|
|
8
|
+
"main": "lib/addon-webgl.js",
|
|
9
|
+
"types": "typings/addon-webgl.d.ts",
|
|
10
|
+
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-webgl",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"terminal",
|
|
14
|
+
"webgl",
|
|
15
|
+
"xterm",
|
|
16
|
+
"xterm.js"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "../../node_modules/.bin/tsc -p .",
|
|
20
|
+
"prepackage": "npm run build",
|
|
21
|
+
"package": "../../node_modules/.bin/webpack",
|
|
22
|
+
"prepublishOnly": "npm run package",
|
|
23
|
+
"start-server-only": "node ../../demo/start-server-only"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"xterm": "^5.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
|
7
|
+
import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas';
|
|
8
|
+
import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types';
|
|
9
|
+
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
|
10
|
+
import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
11
|
+
import { Terminal } from '@xterm/xterm';
|
|
12
|
+
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
|
|
13
|
+
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
|
|
14
|
+
|
|
15
|
+
interface IVertices {
|
|
16
|
+
attributes: Float32Array;
|
|
17
|
+
/**
|
|
18
|
+
* These buffers are the ones used to bind to WebGL, the reason there are
|
|
19
|
+
* multiple is to allow double buffering to work as you cannot modify the
|
|
20
|
+
* buffer while it's being used by the GPU. Having multiple lets us start
|
|
21
|
+
* working on the next frame.
|
|
22
|
+
*/
|
|
23
|
+
attributesBuffers: Float32Array[];
|
|
24
|
+
count: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const enum VertexAttribLocations {
|
|
28
|
+
UNIT_QUAD = 0,
|
|
29
|
+
CELL_POSITION = 1,
|
|
30
|
+
OFFSET = 2,
|
|
31
|
+
SIZE = 3,
|
|
32
|
+
TEXPAGE = 4,
|
|
33
|
+
TEXCOORD = 5,
|
|
34
|
+
TEXSIZE = 6
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const vertexShaderSource = `#version 300 es
|
|
38
|
+
layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad;
|
|
39
|
+
layout (location = ${VertexAttribLocations.CELL_POSITION}) in vec2 a_cellpos;
|
|
40
|
+
layout (location = ${VertexAttribLocations.OFFSET}) in vec2 a_offset;
|
|
41
|
+
layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size;
|
|
42
|
+
layout (location = ${VertexAttribLocations.TEXPAGE}) in float a_texpage;
|
|
43
|
+
layout (location = ${VertexAttribLocations.TEXCOORD}) in vec2 a_texcoord;
|
|
44
|
+
layout (location = ${VertexAttribLocations.TEXSIZE}) in vec2 a_texsize;
|
|
45
|
+
|
|
46
|
+
uniform mat4 u_projection;
|
|
47
|
+
uniform vec2 u_resolution;
|
|
48
|
+
|
|
49
|
+
out vec2 v_texcoord;
|
|
50
|
+
flat out int v_texpage;
|
|
51
|
+
|
|
52
|
+
void main() {
|
|
53
|
+
vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size);
|
|
54
|
+
gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);
|
|
55
|
+
v_texpage = int(a_texpage);
|
|
56
|
+
v_texcoord = a_texcoord + a_unitquad * a_texsize;
|
|
57
|
+
}`;
|
|
58
|
+
|
|
59
|
+
function createFragmentShaderSource(maxFragmentShaderTextureUnits: number): string {
|
|
60
|
+
let textureConditionals = '';
|
|
61
|
+
for (let i = 1; i < maxFragmentShaderTextureUnits; i++) {
|
|
62
|
+
textureConditionals += ` else if (v_texpage == ${i}) { outColor = texture(u_texture[${i}], v_texcoord); }`;
|
|
63
|
+
}
|
|
64
|
+
return (`#version 300 es
|
|
65
|
+
precision lowp float;
|
|
66
|
+
|
|
67
|
+
in vec2 v_texcoord;
|
|
68
|
+
flat in int v_texpage;
|
|
69
|
+
|
|
70
|
+
uniform sampler2D u_texture[${maxFragmentShaderTextureUnits}];
|
|
71
|
+
|
|
72
|
+
out vec4 outColor;
|
|
73
|
+
|
|
74
|
+
void main() {
|
|
75
|
+
if (v_texpage == 0) {
|
|
76
|
+
outColor = texture(u_texture[0], v_texcoord);
|
|
77
|
+
} ${textureConditionals}
|
|
78
|
+
}`);
|
|
79
|
+
}
|
|
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;
|
|
84
|
+
|
|
85
|
+
// Work variables to avoid garbage collection
|
|
86
|
+
let $i = 0;
|
|
87
|
+
let $glyph: IRasterizedGlyph | undefined = undefined;
|
|
88
|
+
let $leftCellPadding = 0;
|
|
89
|
+
let $clippedPixels = 0;
|
|
90
|
+
|
|
91
|
+
export class GlyphRenderer extends Disposable {
|
|
92
|
+
private readonly _program: WebGLProgram;
|
|
93
|
+
private readonly _vertexArrayObject: IWebGLVertexArrayObject;
|
|
94
|
+
private readonly _projectionLocation: WebGLUniformLocation;
|
|
95
|
+
private readonly _resolutionLocation: WebGLUniformLocation;
|
|
96
|
+
private readonly _textureLocation: WebGLUniformLocation;
|
|
97
|
+
private readonly _atlasTextures: GLTexture[];
|
|
98
|
+
private readonly _attributesBuffer: WebGLBuffer;
|
|
99
|
+
|
|
100
|
+
private _atlas: ITextureAtlas | undefined;
|
|
101
|
+
private _activeBuffer: number = 0;
|
|
102
|
+
private readonly _vertices: IVertices = {
|
|
103
|
+
count: 0,
|
|
104
|
+
attributes: new Float32Array(0),
|
|
105
|
+
attributesBuffers: [
|
|
106
|
+
new Float32Array(0),
|
|
107
|
+
new Float32Array(0)
|
|
108
|
+
]
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
constructor(
|
|
112
|
+
private readonly _terminal: Terminal,
|
|
113
|
+
private readonly _gl: IWebGL2RenderingContext,
|
|
114
|
+
private _dimensions: IRenderDimensions
|
|
115
|
+
) {
|
|
116
|
+
super();
|
|
117
|
+
|
|
118
|
+
const gl = this._gl;
|
|
119
|
+
|
|
120
|
+
if (TextureAtlas.maxAtlasPages === undefined) {
|
|
121
|
+
// Typically 8 or 16
|
|
122
|
+
TextureAtlas.maxAtlasPages = Math.min(32, throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number | null));
|
|
123
|
+
// Almost all clients will support >= 4096
|
|
124
|
+
TextureAtlas.maxTextureSize = throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages)));
|
|
128
|
+
this.register(toDisposable(() => gl.deleteProgram(this._program)));
|
|
129
|
+
|
|
130
|
+
// Uniform locations
|
|
131
|
+
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
|
|
132
|
+
this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution'));
|
|
133
|
+
this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
|
|
134
|
+
|
|
135
|
+
// Create and set the vertex array object
|
|
136
|
+
this._vertexArrayObject = gl.createVertexArray();
|
|
137
|
+
gl.bindVertexArray(this._vertexArrayObject);
|
|
138
|
+
|
|
139
|
+
// Setup a_unitquad, this defines the 4 vertices of a rectangle
|
|
140
|
+
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
|
141
|
+
const unitQuadVerticesBuffer = gl.createBuffer();
|
|
142
|
+
this.register(toDisposable(() => gl.deleteBuffer(unitQuadVerticesBuffer)));
|
|
143
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer);
|
|
144
|
+
gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW);
|
|
145
|
+
gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);
|
|
146
|
+
gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);
|
|
147
|
+
|
|
148
|
+
// Setup the unit quad element array buffer, this points to indices in
|
|
149
|
+
// unitQuadVertices to allow is to draw 2 triangles from the vertices via a
|
|
150
|
+
// triangle strip
|
|
151
|
+
const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]);
|
|
152
|
+
const elementIndicesBuffer = gl.createBuffer();
|
|
153
|
+
this.register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer)));
|
|
154
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer);
|
|
155
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW);
|
|
156
|
+
|
|
157
|
+
// Setup attributes
|
|
158
|
+
this._attributesBuffer = throwIfFalsy(gl.createBuffer());
|
|
159
|
+
this.register(toDisposable(() => gl.deleteBuffer(this._attributesBuffer)));
|
|
160
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
|
161
|
+
gl.enableVertexAttribArray(VertexAttribLocations.OFFSET);
|
|
162
|
+
gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, BYTES_PER_CELL, 0);
|
|
163
|
+
gl.vertexAttribDivisor(VertexAttribLocations.OFFSET, 1);
|
|
164
|
+
gl.enableVertexAttribArray(VertexAttribLocations.SIZE);
|
|
165
|
+
gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT);
|
|
166
|
+
gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1);
|
|
167
|
+
gl.enableVertexAttribArray(VertexAttribLocations.TEXPAGE);
|
|
168
|
+
gl.vertexAttribPointer(VertexAttribLocations.TEXPAGE, 1, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT);
|
|
169
|
+
gl.vertexAttribDivisor(VertexAttribLocations.TEXPAGE, 1);
|
|
170
|
+
gl.enableVertexAttribArray(VertexAttribLocations.TEXCOORD);
|
|
171
|
+
gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 5 * Float32Array.BYTES_PER_ELEMENT);
|
|
172
|
+
gl.vertexAttribDivisor(VertexAttribLocations.TEXCOORD, 1);
|
|
173
|
+
gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE);
|
|
174
|
+
gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 7 * Float32Array.BYTES_PER_ELEMENT);
|
|
175
|
+
gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1);
|
|
176
|
+
gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION);
|
|
177
|
+
gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT);
|
|
178
|
+
gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1);
|
|
179
|
+
|
|
180
|
+
// Setup static uniforms
|
|
181
|
+
gl.useProgram(this._program);
|
|
182
|
+
const textureUnits = new Int32Array(TextureAtlas.maxAtlasPages);
|
|
183
|
+
for (let i = 0; i < TextureAtlas.maxAtlasPages; i++) {
|
|
184
|
+
textureUnits[i] = i;
|
|
185
|
+
}
|
|
186
|
+
gl.uniform1iv(this._textureLocation, textureUnits);
|
|
187
|
+
gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX);
|
|
188
|
+
|
|
189
|
+
// Setup 1x1 red pixel textures for all potential atlas pages, if one of these invalid textures
|
|
190
|
+
// is ever drawn it will show characters as red rectangles.
|
|
191
|
+
this._atlasTextures = [];
|
|
192
|
+
for (let i = 0; i < TextureAtlas.maxAtlasPages; i++) {
|
|
193
|
+
const glTexture = new GLTexture(throwIfFalsy(gl.createTexture()));
|
|
194
|
+
this.register(toDisposable(() => gl.deleteTexture(glTexture.texture)));
|
|
195
|
+
gl.activeTexture(gl.TEXTURE0 + i);
|
|
196
|
+
gl.bindTexture(gl.TEXTURE_2D, glTexture.texture);
|
|
197
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
198
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
199
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
|
|
200
|
+
this._atlasTextures[i] = glTexture;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Allow drawing of transparent texture
|
|
204
|
+
gl.enable(gl.BLEND);
|
|
205
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
206
|
+
|
|
207
|
+
// Set viewport
|
|
208
|
+
this.handleResize();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public beginFrame(): boolean {
|
|
212
|
+
return this._atlas ? this._atlas.beginFrame() : true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void {
|
|
216
|
+
// Since this function is called for every cell (`rows*cols`), it must be very optimized. It
|
|
217
|
+
// should not instantiate any variables unless a new glyph is drawn to the cache where the
|
|
218
|
+
// slight slowdown is acceptable for the developer ergonomics provided as it's a once of for
|
|
219
|
+
// each glyph.
|
|
220
|
+
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void {
|
|
224
|
+
$i = (y * this._terminal.cols + x) * INDICES_PER_CELL;
|
|
225
|
+
|
|
226
|
+
// Exit early if this is a null character, allow space character to continue as it may have
|
|
227
|
+
// underline/strikethrough styles
|
|
228
|
+
if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {
|
|
229
|
+
array.fill(0, $i, $i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!this._atlas) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Get the glyph
|
|
238
|
+
if (chars && chars.length > 1) {
|
|
239
|
+
$glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, false);
|
|
240
|
+
} else {
|
|
241
|
+
$glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, false);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
$leftCellPadding = Math.floor((this._dimensions.device.cell.width - this._dimensions.device.char.width) / 2);
|
|
245
|
+
if (bg !== lastBg && $glyph.offset.x > $leftCellPadding) {
|
|
246
|
+
$clippedPixels = $glyph.offset.x - $leftCellPadding;
|
|
247
|
+
// a_origin
|
|
248
|
+
array[$i ] = -($glyph.offset.x - $clippedPixels) + this._dimensions.device.char.left;
|
|
249
|
+
array[$i + 1] = -$glyph.offset.y + this._dimensions.device.char.top;
|
|
250
|
+
// a_size
|
|
251
|
+
array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.device.canvas.width;
|
|
252
|
+
array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height;
|
|
253
|
+
// a_texpage
|
|
254
|
+
array[$i + 4] = $glyph.texturePage;
|
|
255
|
+
// a_texcoord
|
|
256
|
+
array[$i + 5] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.pages[$glyph.texturePage].canvas.width;
|
|
257
|
+
array[$i + 6] = $glyph.texturePositionClipSpace.y;
|
|
258
|
+
// a_texsize
|
|
259
|
+
array[$i + 7] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.pages[$glyph.texturePage].canvas.width;
|
|
260
|
+
array[$i + 8] = $glyph.sizeClipSpace.y;
|
|
261
|
+
} else {
|
|
262
|
+
// a_origin
|
|
263
|
+
array[$i ] = -$glyph.offset.x + this._dimensions.device.char.left;
|
|
264
|
+
array[$i + 1] = -$glyph.offset.y + this._dimensions.device.char.top;
|
|
265
|
+
// a_size
|
|
266
|
+
array[$i + 2] = $glyph.size.x / this._dimensions.device.canvas.width;
|
|
267
|
+
array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height;
|
|
268
|
+
// a_texpage
|
|
269
|
+
array[$i + 4] = $glyph.texturePage;
|
|
270
|
+
// a_texcoord
|
|
271
|
+
array[$i + 5] = $glyph.texturePositionClipSpace.x;
|
|
272
|
+
array[$i + 6] = $glyph.texturePositionClipSpace.y;
|
|
273
|
+
// a_texsize
|
|
274
|
+
array[$i + 7] = $glyph.sizeClipSpace.x;
|
|
275
|
+
array[$i + 8] = $glyph.sizeClipSpace.y;
|
|
276
|
+
}
|
|
277
|
+
// a_cellpos only changes on resize
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public clear(): void {
|
|
281
|
+
const terminal = this._terminal;
|
|
282
|
+
const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;
|
|
283
|
+
|
|
284
|
+
// Clear vertices
|
|
285
|
+
if (this._vertices.count !== newCount) {
|
|
286
|
+
this._vertices.attributes = new Float32Array(newCount);
|
|
287
|
+
} else {
|
|
288
|
+
this._vertices.attributes.fill(0);
|
|
289
|
+
}
|
|
290
|
+
let i = 0;
|
|
291
|
+
for (; i < this._vertices.attributesBuffers.length; i++) {
|
|
292
|
+
if (this._vertices.count !== newCount) {
|
|
293
|
+
this._vertices.attributesBuffers[i] = new Float32Array(newCount);
|
|
294
|
+
} else {
|
|
295
|
+
this._vertices.attributesBuffers[i].fill(0);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
this._vertices.count = newCount;
|
|
299
|
+
i = 0;
|
|
300
|
+
for (let y = 0; y < terminal.rows; y++) {
|
|
301
|
+
for (let x = 0; x < terminal.cols; x++) {
|
|
302
|
+
this._vertices.attributes[i + 9] = x / terminal.cols;
|
|
303
|
+
this._vertices.attributes[i + 10] = y / terminal.rows;
|
|
304
|
+
i += INDICES_PER_CELL;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
public handleResize(): void {
|
|
310
|
+
const gl = this._gl;
|
|
311
|
+
gl.useProgram(this._program);
|
|
312
|
+
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
313
|
+
gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height);
|
|
314
|
+
this.clear();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public render(renderModel: IRenderModel): void {
|
|
318
|
+
if (!this._atlas) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const gl = this._gl;
|
|
323
|
+
|
|
324
|
+
gl.useProgram(this._program);
|
|
325
|
+
gl.bindVertexArray(this._vertexArrayObject);
|
|
326
|
+
|
|
327
|
+
// Alternate buffers each frame as the active buffer gets locked while it's in use by the GPU
|
|
328
|
+
this._activeBuffer = (this._activeBuffer + 1) % 2;
|
|
329
|
+
const activeBuffer = this._vertices.attributesBuffers[this._activeBuffer];
|
|
330
|
+
|
|
331
|
+
// Copy data for each cell of each line up to its line length (the last non-whitespace cell)
|
|
332
|
+
// from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU.
|
|
333
|
+
// The reasons for this are as follows:
|
|
334
|
+
// - So the active buffer can be alternated so we don't get blocked on rendering finishing
|
|
335
|
+
// - To copy either the normal attributes buffer or the selection attributes buffer when there
|
|
336
|
+
// is a selection
|
|
337
|
+
// - So we don't send vertices for all the line-ending whitespace to the GPU
|
|
338
|
+
let bufferLength = 0;
|
|
339
|
+
for (let y = 0; y < renderModel.lineLengths.length; y++) {
|
|
340
|
+
const si = y * this._terminal.cols * INDICES_PER_CELL;
|
|
341
|
+
const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL);
|
|
342
|
+
activeBuffer.set(sub, bufferLength);
|
|
343
|
+
bufferLength += sub.length;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Bind the attributes buffer
|
|
347
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
|
348
|
+
gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
|
|
349
|
+
|
|
350
|
+
// Bind the atlas page texture if they have changed
|
|
351
|
+
for (let i = 0; i < this._atlas.pages.length; i++) {
|
|
352
|
+
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
|
|
353
|
+
this._bindAtlasPageTexture(gl, this._atlas, i);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Draw the viewport
|
|
358
|
+
gl.drawElementsInstanced(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public setAtlas(atlas: ITextureAtlas): void {
|
|
362
|
+
this._atlas = atlas;
|
|
363
|
+
for (const glTexture of this._atlasTextures) {
|
|
364
|
+
glTexture.version = -1;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private _bindAtlasPageTexture(gl: IWebGL2RenderingContext, atlas: ITextureAtlas, i: number): void {
|
|
369
|
+
gl.activeTexture(gl.TEXTURE0 + i);
|
|
370
|
+
gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i].texture);
|
|
371
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
372
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
373
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[i].canvas);
|
|
374
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
375
|
+
this._atlasTextures[i].version = atlas.pages[i].version;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
public setDimensions(dimensions: IRenderDimensions): void {
|
|
379
|
+
this._dimensions = dimensions;
|
|
380
|
+
}
|
|
381
|
+
}
|