@next_term/web 0.1.0-next.0
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/dist/accessibility.d.ts +46 -0
- package/dist/accessibility.d.ts.map +1 -0
- package/dist/accessibility.js +196 -0
- package/dist/accessibility.js.map +1 -0
- package/dist/addon.d.ts.map +1 -0
- package/dist/addon.js +2 -0
- package/dist/addon.js.map +1 -0
- package/dist/addons/fit.d.ts.map +1 -0
- package/dist/addons/fit.js +40 -0
- package/dist/addons/fit.js.map +1 -0
- package/dist/addons/search.d.ts +56 -0
- package/dist/addons/search.d.ts.map +1 -0
- package/dist/addons/search.js +178 -0
- package/dist/addons/search.js.map +1 -0
- package/dist/addons/web-links.d.ts +30 -0
- package/dist/addons/web-links.d.ts.map +1 -0
- package/dist/addons/web-links.js +170 -0
- package/dist/addons/web-links.js.map +1 -0
- package/dist/fit.d.ts.map +1 -0
- package/dist/fit.js +14 -0
- package/dist/fit.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/input-handler.d.ts +185 -0
- package/dist/input-handler.d.ts.map +1 -0
- package/dist/input-handler.js +1197 -0
- package/dist/input-handler.js.map +1 -0
- package/dist/parser-worker.d.ts.map +1 -0
- package/dist/parser-worker.js +128 -0
- package/dist/parser-worker.js.map +1 -0
- package/dist/render-bridge.d.ts +56 -0
- package/dist/render-bridge.d.ts.map +1 -0
- package/dist/render-bridge.js +158 -0
- package/dist/render-bridge.js.map +1 -0
- package/dist/render-worker.d.ts +62 -0
- package/dist/render-worker.d.ts.map +1 -0
- package/dist/render-worker.js +720 -0
- package/dist/render-worker.js.map +1 -0
- package/dist/renderer.d.ts +86 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +454 -0
- package/dist/renderer.js.map +1 -0
- package/dist/shared-context.d.ts +93 -0
- package/dist/shared-context.d.ts.map +1 -0
- package/dist/shared-context.js +561 -0
- package/dist/shared-context.js.map +1 -0
- package/dist/web-terminal.d.ts +152 -0
- package/dist/web-terminal.d.ts.map +1 -0
- package/dist/web-terminal.js +684 -0
- package/dist/web-terminal.js.map +1 -0
- package/dist/webgl-renderer.d.ts +146 -0
- package/dist/webgl-renderer.d.ts.map +1 -0
- package/dist/webgl-renderer.js +1047 -0
- package/dist/webgl-renderer.js.map +1 -0
- package/dist/worker-bridge.d.ts +51 -0
- package/dist/worker-bridge.d.ts.map +1 -0
- package/dist/worker-bridge.js +185 -0
- package/dist/worker-bridge.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWebGLContext — manages a single WebGL2 context shared across
|
|
3
|
+
* multiple terminal panes.
|
|
4
|
+
*
|
|
5
|
+
* Chrome limits WebGL contexts to 16 per page. This class allows any number
|
|
6
|
+
* of terminal panes to render through one context by using `gl.viewport` and
|
|
7
|
+
* `gl.scissor` to partition the canvas into regions.
|
|
8
|
+
*
|
|
9
|
+
* The shared canvas is positioned as an overlay by the consumer (typically
|
|
10
|
+
* TerminalPane). Each registered terminal provides its CellGrid, CursorState,
|
|
11
|
+
* and a viewport rectangle (in CSS pixels relative to the canvas).
|
|
12
|
+
*/
|
|
13
|
+
import type { CellGrid, CursorState, SelectionRange, Theme } from "@next_term/core";
|
|
14
|
+
export interface TerminalEntry {
|
|
15
|
+
grid: CellGrid;
|
|
16
|
+
cursor: CursorState;
|
|
17
|
+
selection: SelectionRange | null;
|
|
18
|
+
viewport: {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare class SharedWebGLContext {
|
|
26
|
+
private gl;
|
|
27
|
+
private canvas;
|
|
28
|
+
private terminals;
|
|
29
|
+
private disposed;
|
|
30
|
+
private rafId;
|
|
31
|
+
private bgProgram;
|
|
32
|
+
private glyphProgram;
|
|
33
|
+
private quadVBO;
|
|
34
|
+
private quadEBO;
|
|
35
|
+
private bgInstanceVBO;
|
|
36
|
+
private glyphInstanceVBO;
|
|
37
|
+
private bgVAO;
|
|
38
|
+
private glyphVAO;
|
|
39
|
+
private bgInstances;
|
|
40
|
+
private glyphInstances;
|
|
41
|
+
private atlas;
|
|
42
|
+
private theme;
|
|
43
|
+
private palette;
|
|
44
|
+
private paletteFloat;
|
|
45
|
+
private themeFgFloat;
|
|
46
|
+
private themeBgFloat;
|
|
47
|
+
private themeCursorFloat;
|
|
48
|
+
private fontSize;
|
|
49
|
+
private fontFamily;
|
|
50
|
+
private dpr;
|
|
51
|
+
private cellWidth;
|
|
52
|
+
private cellHeight;
|
|
53
|
+
constructor(options?: {
|
|
54
|
+
fontSize?: number;
|
|
55
|
+
fontFamily?: string;
|
|
56
|
+
theme?: Partial<Theme>;
|
|
57
|
+
devicePixelRatio?: number;
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Initialize the WebGL context. Must be called after the canvas is in the
|
|
61
|
+
* DOM (or at least after construction if you plan to append it yourself).
|
|
62
|
+
*/
|
|
63
|
+
init(): void;
|
|
64
|
+
addTerminal(id: string, grid: CellGrid, cursor: CursorState): void;
|
|
65
|
+
setViewport(id: string, x: number, y: number, width: number, height: number): void;
|
|
66
|
+
updateTerminal(id: string, grid: CellGrid, cursor: CursorState): void;
|
|
67
|
+
removeTerminal(id: string): void;
|
|
68
|
+
getTerminalIds(): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Render all terminals in one frame.
|
|
71
|
+
*/
|
|
72
|
+
render(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Update the shared canvas size to match a container element.
|
|
75
|
+
*/
|
|
76
|
+
syncCanvasSize(width: number, height: number): void;
|
|
77
|
+
getCanvas(): HTMLCanvasElement;
|
|
78
|
+
getCellSize(): {
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
};
|
|
82
|
+
startRenderLoop(): void;
|
|
83
|
+
stopRenderLoop(): void;
|
|
84
|
+
dispose(): void;
|
|
85
|
+
private renderTerminal;
|
|
86
|
+
private initGLResources;
|
|
87
|
+
private setupBgVAO;
|
|
88
|
+
private setupGlyphVAO;
|
|
89
|
+
private buildPaletteFloat;
|
|
90
|
+
private resolveColorFloat;
|
|
91
|
+
private measureCellSize;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=shared-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-context.d.ts","sourceRoot":"","sources":["../src/shared-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedWebGLContext — manages a single WebGL2 context shared across
|
|
3
|
+
* multiple terminal panes.
|
|
4
|
+
*
|
|
5
|
+
* Chrome limits WebGL contexts to 16 per page. This class allows any number
|
|
6
|
+
* of terminal panes to render through one context by using `gl.viewport` and
|
|
7
|
+
* `gl.scissor` to partition the canvas into regions.
|
|
8
|
+
*
|
|
9
|
+
* The shared canvas is positioned as an overlay by the consumer (typically
|
|
10
|
+
* TerminalPane). Each registered terminal provides its CellGrid, CursorState,
|
|
11
|
+
* and a viewport rectangle (in CSS pixels relative to the canvas).
|
|
12
|
+
*/
|
|
13
|
+
import { DEFAULT_THEME } from "@next_term/core";
|
|
14
|
+
import { build256Palette } from "./renderer.js";
|
|
15
|
+
import { BG_INSTANCE_FLOATS, GLYPH_INSTANCE_FLOATS, GlyphAtlas, hexToFloat4, packBgInstance, packGlyphInstance, } from "./webgl-renderer.js";
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Attribute bit positions
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const ATTR_BOLD = 0x01;
|
|
20
|
+
const ATTR_ITALIC = 0x02;
|
|
21
|
+
const ATTR_INVERSE = 0x40;
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Shader sources (same as webgl-renderer.ts)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
const BG_VERTEX_SHADER = `#version 300 es
|
|
26
|
+
in vec2 a_position;
|
|
27
|
+
in vec2 a_cellPos;
|
|
28
|
+
in vec4 a_color;
|
|
29
|
+
|
|
30
|
+
uniform vec2 u_resolution;
|
|
31
|
+
uniform vec2 u_cellSize;
|
|
32
|
+
|
|
33
|
+
out vec4 v_color;
|
|
34
|
+
|
|
35
|
+
void main() {
|
|
36
|
+
vec2 cellPixelPos = a_cellPos * u_cellSize;
|
|
37
|
+
vec2 pos = cellPixelPos + a_position * u_cellSize;
|
|
38
|
+
vec2 clipPos = (pos / u_resolution) * 2.0 - 1.0;
|
|
39
|
+
clipPos.y = -clipPos.y;
|
|
40
|
+
gl_Position = vec4(clipPos, 0.0, 1.0);
|
|
41
|
+
v_color = a_color;
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
const BG_FRAGMENT_SHADER = `#version 300 es
|
|
45
|
+
precision mediump float;
|
|
46
|
+
in vec4 v_color;
|
|
47
|
+
out vec4 fragColor;
|
|
48
|
+
void main() {
|
|
49
|
+
fragColor = v_color;
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
const GLYPH_VERTEX_SHADER = `#version 300 es
|
|
53
|
+
in vec2 a_position;
|
|
54
|
+
in vec2 a_cellPos;
|
|
55
|
+
in vec4 a_color;
|
|
56
|
+
in vec4 a_texCoord;
|
|
57
|
+
in vec2 a_glyphSize;
|
|
58
|
+
|
|
59
|
+
uniform vec2 u_resolution;
|
|
60
|
+
uniform vec2 u_cellSize;
|
|
61
|
+
|
|
62
|
+
out vec4 v_color;
|
|
63
|
+
out vec2 v_texCoord;
|
|
64
|
+
|
|
65
|
+
void main() {
|
|
66
|
+
vec2 cellPixelPos = a_cellPos * u_cellSize;
|
|
67
|
+
vec2 size = (a_glyphSize.x > 0.0) ? a_glyphSize : u_cellSize;
|
|
68
|
+
vec2 pos = cellPixelPos + a_position * size;
|
|
69
|
+
vec2 clipPos = (pos / u_resolution) * 2.0 - 1.0;
|
|
70
|
+
clipPos.y = -clipPos.y;
|
|
71
|
+
gl_Position = vec4(clipPos, 0.0, 1.0);
|
|
72
|
+
v_color = a_color;
|
|
73
|
+
v_texCoord = a_texCoord.xy + a_position * a_texCoord.zw;
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
const GLYPH_FRAGMENT_SHADER = `#version 300 es
|
|
77
|
+
precision mediump float;
|
|
78
|
+
in vec4 v_color;
|
|
79
|
+
in vec2 v_texCoord;
|
|
80
|
+
uniform sampler2D u_atlas;
|
|
81
|
+
out vec4 fragColor;
|
|
82
|
+
void main() {
|
|
83
|
+
float alpha = texture(u_atlas, v_texCoord).a;
|
|
84
|
+
fragColor = vec4(v_color.rgb, v_color.a * alpha);
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Shader compilation helpers
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
function compileShader(gl, type, source) {
|
|
91
|
+
const shader = gl.createShader(type);
|
|
92
|
+
if (!shader)
|
|
93
|
+
throw new Error("Failed to create shader");
|
|
94
|
+
gl.shaderSource(shader, source);
|
|
95
|
+
gl.compileShader(shader);
|
|
96
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
97
|
+
const info = gl.getShaderInfoLog(shader);
|
|
98
|
+
gl.deleteShader(shader);
|
|
99
|
+
throw new Error(`Shader compile error: ${info}`);
|
|
100
|
+
}
|
|
101
|
+
return shader;
|
|
102
|
+
}
|
|
103
|
+
function createProgram(gl, vertSrc, fragSrc) {
|
|
104
|
+
const vert = compileShader(gl, gl.VERTEX_SHADER, vertSrc);
|
|
105
|
+
const frag = compileShader(gl, gl.FRAGMENT_SHADER, fragSrc);
|
|
106
|
+
const program = gl.createProgram();
|
|
107
|
+
if (!program)
|
|
108
|
+
throw new Error("Failed to create program");
|
|
109
|
+
gl.attachShader(program, vert);
|
|
110
|
+
gl.attachShader(program, frag);
|
|
111
|
+
gl.linkProgram(program);
|
|
112
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
113
|
+
const info = gl.getProgramInfoLog(program);
|
|
114
|
+
gl.deleteProgram(program);
|
|
115
|
+
throw new Error(`Program link error: ${info}`);
|
|
116
|
+
}
|
|
117
|
+
gl.detachShader(program, vert);
|
|
118
|
+
gl.detachShader(program, frag);
|
|
119
|
+
gl.deleteShader(vert);
|
|
120
|
+
gl.deleteShader(frag);
|
|
121
|
+
return program;
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// SharedWebGLContext
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
export class SharedWebGLContext {
|
|
127
|
+
gl = null;
|
|
128
|
+
canvas;
|
|
129
|
+
terminals = new Map();
|
|
130
|
+
disposed = false;
|
|
131
|
+
rafId = null;
|
|
132
|
+
// GL resources
|
|
133
|
+
bgProgram = null;
|
|
134
|
+
glyphProgram = null;
|
|
135
|
+
quadVBO = null;
|
|
136
|
+
quadEBO = null;
|
|
137
|
+
bgInstanceVBO = null;
|
|
138
|
+
glyphInstanceVBO = null;
|
|
139
|
+
bgVAO = null;
|
|
140
|
+
glyphVAO = null;
|
|
141
|
+
// Instance data (CPU side)
|
|
142
|
+
bgInstances;
|
|
143
|
+
glyphInstances;
|
|
144
|
+
// Glyph atlas
|
|
145
|
+
atlas;
|
|
146
|
+
// Theme / palette
|
|
147
|
+
theme;
|
|
148
|
+
palette;
|
|
149
|
+
paletteFloat = [];
|
|
150
|
+
themeFgFloat = [0, 0, 0, 1];
|
|
151
|
+
themeBgFloat = [0, 0, 0, 1];
|
|
152
|
+
themeCursorFloat = [0, 0, 0, 1];
|
|
153
|
+
fontSize;
|
|
154
|
+
fontFamily;
|
|
155
|
+
dpr;
|
|
156
|
+
cellWidth = 0;
|
|
157
|
+
cellHeight = 0;
|
|
158
|
+
constructor(options) {
|
|
159
|
+
this.fontSize = options?.fontSize ?? 14;
|
|
160
|
+
this.fontFamily = options?.fontFamily ?? "'Menlo', 'DejaVu Sans Mono', 'Consolas', monospace";
|
|
161
|
+
this.theme = { ...DEFAULT_THEME, ...options?.theme };
|
|
162
|
+
this.dpr =
|
|
163
|
+
options?.devicePixelRatio ?? (typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1);
|
|
164
|
+
this.palette = build256Palette(this.theme);
|
|
165
|
+
this.buildPaletteFloat();
|
|
166
|
+
this.measureCellSize();
|
|
167
|
+
this.atlas = new GlyphAtlas(Math.round(this.fontSize * this.dpr), this.fontFamily);
|
|
168
|
+
// Pre-allocate instance buffers
|
|
169
|
+
const maxCells = 80 * 24;
|
|
170
|
+
this.bgInstances = new Float32Array(maxCells * BG_INSTANCE_FLOATS);
|
|
171
|
+
this.glyphInstances = new Float32Array(maxCells * GLYPH_INSTANCE_FLOATS);
|
|
172
|
+
// Create the shared canvas
|
|
173
|
+
this.canvas = document.createElement("canvas");
|
|
174
|
+
this.canvas.style.display = "block";
|
|
175
|
+
this.canvas.style.position = "absolute";
|
|
176
|
+
this.canvas.style.top = "0";
|
|
177
|
+
this.canvas.style.left = "0";
|
|
178
|
+
this.canvas.style.pointerEvents = "none";
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Initialize the WebGL context. Must be called after the canvas is in the
|
|
182
|
+
* DOM (or at least after construction if you plan to append it yourself).
|
|
183
|
+
*/
|
|
184
|
+
init() {
|
|
185
|
+
this.gl = this.canvas.getContext("webgl2", {
|
|
186
|
+
alpha: true,
|
|
187
|
+
antialias: false,
|
|
188
|
+
premultipliedAlpha: false,
|
|
189
|
+
preserveDrawingBuffer: false,
|
|
190
|
+
});
|
|
191
|
+
if (!this.gl) {
|
|
192
|
+
throw new Error("WebGL2 is not available");
|
|
193
|
+
}
|
|
194
|
+
this.initGLResources();
|
|
195
|
+
}
|
|
196
|
+
// -----------------------------------------------------------------------
|
|
197
|
+
// Public API
|
|
198
|
+
// -----------------------------------------------------------------------
|
|
199
|
+
addTerminal(id, grid, cursor) {
|
|
200
|
+
this.terminals.set(id, {
|
|
201
|
+
grid,
|
|
202
|
+
cursor,
|
|
203
|
+
selection: null,
|
|
204
|
+
viewport: { x: 0, y: 0, width: 0, height: 0 },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
setViewport(id, x, y, width, height) {
|
|
208
|
+
const entry = this.terminals.get(id);
|
|
209
|
+
if (entry) {
|
|
210
|
+
entry.viewport = { x, y, width, height };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
updateTerminal(id, grid, cursor) {
|
|
214
|
+
const entry = this.terminals.get(id);
|
|
215
|
+
if (entry) {
|
|
216
|
+
entry.grid = grid;
|
|
217
|
+
entry.cursor = cursor;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
removeTerminal(id) {
|
|
221
|
+
this.terminals.delete(id);
|
|
222
|
+
}
|
|
223
|
+
getTerminalIds() {
|
|
224
|
+
return Array.from(this.terminals.keys());
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Render all terminals in one frame.
|
|
228
|
+
*/
|
|
229
|
+
render() {
|
|
230
|
+
if (this.disposed || !this.gl)
|
|
231
|
+
return;
|
|
232
|
+
const gl = this.gl;
|
|
233
|
+
const canvasWidth = this.canvas.width;
|
|
234
|
+
const canvasHeight = this.canvas.height;
|
|
235
|
+
// Full-canvas clear
|
|
236
|
+
gl.viewport(0, 0, canvasWidth, canvasHeight);
|
|
237
|
+
gl.clearColor(0, 0, 0, 0);
|
|
238
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
239
|
+
// Render each terminal in its viewport
|
|
240
|
+
for (const [_id, entry] of this.terminals) {
|
|
241
|
+
this.renderTerminal(entry);
|
|
242
|
+
}
|
|
243
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Update the shared canvas size to match a container element.
|
|
247
|
+
*/
|
|
248
|
+
syncCanvasSize(width, height) {
|
|
249
|
+
this.canvas.width = Math.round(width * this.dpr);
|
|
250
|
+
this.canvas.height = Math.round(height * this.dpr);
|
|
251
|
+
this.canvas.style.width = `${width}px`;
|
|
252
|
+
this.canvas.style.height = `${height}px`;
|
|
253
|
+
}
|
|
254
|
+
getCanvas() {
|
|
255
|
+
return this.canvas;
|
|
256
|
+
}
|
|
257
|
+
getCellSize() {
|
|
258
|
+
return { width: this.cellWidth, height: this.cellHeight };
|
|
259
|
+
}
|
|
260
|
+
startRenderLoop() {
|
|
261
|
+
if (this.disposed)
|
|
262
|
+
return;
|
|
263
|
+
const loop = () => {
|
|
264
|
+
if (this.disposed)
|
|
265
|
+
return;
|
|
266
|
+
this.render();
|
|
267
|
+
this.rafId = requestAnimationFrame(loop);
|
|
268
|
+
};
|
|
269
|
+
this.rafId = requestAnimationFrame(loop);
|
|
270
|
+
}
|
|
271
|
+
stopRenderLoop() {
|
|
272
|
+
if (this.rafId !== null) {
|
|
273
|
+
cancelAnimationFrame(this.rafId);
|
|
274
|
+
this.rafId = null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
dispose() {
|
|
278
|
+
if (this.disposed)
|
|
279
|
+
return;
|
|
280
|
+
this.disposed = true;
|
|
281
|
+
this.stopRenderLoop();
|
|
282
|
+
const gl = this.gl;
|
|
283
|
+
if (gl) {
|
|
284
|
+
this.atlas.dispose(gl);
|
|
285
|
+
if (this.bgProgram)
|
|
286
|
+
gl.deleteProgram(this.bgProgram);
|
|
287
|
+
if (this.glyphProgram)
|
|
288
|
+
gl.deleteProgram(this.glyphProgram);
|
|
289
|
+
if (this.quadVBO)
|
|
290
|
+
gl.deleteBuffer(this.quadVBO);
|
|
291
|
+
if (this.quadEBO)
|
|
292
|
+
gl.deleteBuffer(this.quadEBO);
|
|
293
|
+
if (this.bgInstanceVBO)
|
|
294
|
+
gl.deleteBuffer(this.bgInstanceVBO);
|
|
295
|
+
if (this.glyphInstanceVBO)
|
|
296
|
+
gl.deleteBuffer(this.glyphInstanceVBO);
|
|
297
|
+
if (this.bgVAO)
|
|
298
|
+
gl.deleteVertexArray(this.bgVAO);
|
|
299
|
+
if (this.glyphVAO)
|
|
300
|
+
gl.deleteVertexArray(this.glyphVAO);
|
|
301
|
+
}
|
|
302
|
+
this.terminals.clear();
|
|
303
|
+
if (this.canvas.parentElement) {
|
|
304
|
+
this.canvas.parentElement.removeChild(this.canvas);
|
|
305
|
+
}
|
|
306
|
+
this.gl = null;
|
|
307
|
+
}
|
|
308
|
+
// -----------------------------------------------------------------------
|
|
309
|
+
// Per-terminal rendering
|
|
310
|
+
// -----------------------------------------------------------------------
|
|
311
|
+
renderTerminal(entry) {
|
|
312
|
+
if (!this.gl)
|
|
313
|
+
return;
|
|
314
|
+
const gl = this.gl;
|
|
315
|
+
const { grid, cursor, viewport } = entry;
|
|
316
|
+
const cols = grid.cols;
|
|
317
|
+
const rows = grid.rows;
|
|
318
|
+
// Convert CSS viewport to device pixels
|
|
319
|
+
const vpX = Math.round(viewport.x * this.dpr);
|
|
320
|
+
const vpY = Math.round(viewport.y * this.dpr);
|
|
321
|
+
const vpW = Math.round(viewport.width * this.dpr);
|
|
322
|
+
const vpH = Math.round(viewport.height * this.dpr);
|
|
323
|
+
// WebGL viewport Y is from bottom; canvas Y is from top
|
|
324
|
+
const canvasHeight = this.canvas.height;
|
|
325
|
+
const glY = canvasHeight - vpY - vpH;
|
|
326
|
+
gl.viewport(vpX, glY, vpW, vpH);
|
|
327
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
328
|
+
gl.scissor(vpX, glY, vpW, vpH);
|
|
329
|
+
// Clear this region with background color
|
|
330
|
+
gl.clearColor(this.themeBgFloat[0], this.themeBgFloat[1], this.themeBgFloat[2], 1.0);
|
|
331
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
332
|
+
// Ensure instance buffers are large enough
|
|
333
|
+
const totalCells = cols * rows;
|
|
334
|
+
if (this.bgInstances.length < totalCells * BG_INSTANCE_FLOATS) {
|
|
335
|
+
this.bgInstances = new Float32Array(totalCells * BG_INSTANCE_FLOATS);
|
|
336
|
+
}
|
|
337
|
+
if (this.glyphInstances.length < totalCells * GLYPH_INSTANCE_FLOATS) {
|
|
338
|
+
this.glyphInstances = new Float32Array(totalCells * GLYPH_INSTANCE_FLOATS);
|
|
339
|
+
}
|
|
340
|
+
// Build instance data
|
|
341
|
+
let bgCount = 0;
|
|
342
|
+
let glyphCount = 0;
|
|
343
|
+
for (let row = 0; row < rows; row++) {
|
|
344
|
+
for (let col = 0; col < cols; col++) {
|
|
345
|
+
const codepoint = grid.getCodepoint(row, col);
|
|
346
|
+
const fgIdx = grid.getFgIndex(row, col);
|
|
347
|
+
const bgIdx = grid.getBgIndex(row, col);
|
|
348
|
+
const attrs = grid.getAttrs(row, col);
|
|
349
|
+
const fgIsRGB = grid.isFgRGB(row, col);
|
|
350
|
+
const bgIsRGB = grid.isBgRGB(row, col);
|
|
351
|
+
let fg = this.resolveColorFloat(fgIdx, fgIsRGB, grid, col, true);
|
|
352
|
+
let bg = this.resolveColorFloat(bgIdx, bgIsRGB, grid, col, false);
|
|
353
|
+
if (attrs & ATTR_INVERSE) {
|
|
354
|
+
const tmp = fg;
|
|
355
|
+
fg = bg;
|
|
356
|
+
bg = tmp;
|
|
357
|
+
}
|
|
358
|
+
packBgInstance(this.bgInstances, bgCount * BG_INSTANCE_FLOATS, col, row, bg[0], bg[1], bg[2], bg[3]);
|
|
359
|
+
bgCount++;
|
|
360
|
+
if (codepoint > 0x20) {
|
|
361
|
+
const bold = !!(attrs & ATTR_BOLD);
|
|
362
|
+
const italic = !!(attrs & ATTR_ITALIC);
|
|
363
|
+
const glyph = this.atlas.getGlyph(codepoint, bold, italic);
|
|
364
|
+
if (glyph) {
|
|
365
|
+
packGlyphInstance(this.glyphInstances, glyphCount * GLYPH_INSTANCE_FLOATS, col, row, fg[0], fg[1], fg[2], fg[3], glyph.u, glyph.v, glyph.w, glyph.h, glyph.pw, glyph.ph);
|
|
366
|
+
glyphCount++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
grid.clearDirty(row);
|
|
371
|
+
}
|
|
372
|
+
// Upload atlas if dirty
|
|
373
|
+
this.atlas.upload(gl);
|
|
374
|
+
const cellW = this.cellWidth * this.dpr;
|
|
375
|
+
const cellH = this.cellHeight * this.dpr;
|
|
376
|
+
// --- Background pass ---
|
|
377
|
+
if (bgCount > 0 && this.bgProgram && this.bgVAO && this.bgInstanceVBO) {
|
|
378
|
+
gl.useProgram(this.bgProgram);
|
|
379
|
+
gl.uniform2f(gl.getUniformLocation(this.bgProgram, "u_resolution"), vpW, vpH);
|
|
380
|
+
gl.uniform2f(gl.getUniformLocation(this.bgProgram, "u_cellSize"), cellW, cellH);
|
|
381
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.bgInstanceVBO);
|
|
382
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.bgInstances.subarray(0, bgCount * BG_INSTANCE_FLOATS), gl.DYNAMIC_DRAW);
|
|
383
|
+
gl.bindVertexArray(this.bgVAO);
|
|
384
|
+
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, bgCount);
|
|
385
|
+
}
|
|
386
|
+
// --- Glyph pass ---
|
|
387
|
+
if (glyphCount > 0 && this.glyphProgram && this.glyphVAO && this.glyphInstanceVBO) {
|
|
388
|
+
gl.enable(gl.BLEND);
|
|
389
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
390
|
+
gl.useProgram(this.glyphProgram);
|
|
391
|
+
gl.uniform2f(gl.getUniformLocation(this.glyphProgram, "u_resolution"), vpW, vpH);
|
|
392
|
+
gl.uniform2f(gl.getUniformLocation(this.glyphProgram, "u_cellSize"), cellW, cellH);
|
|
393
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
394
|
+
gl.bindTexture(gl.TEXTURE_2D, this.atlas.getTexture());
|
|
395
|
+
gl.uniform1i(gl.getUniformLocation(this.glyphProgram, "u_atlas"), 0);
|
|
396
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphInstanceVBO);
|
|
397
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.glyphInstances.subarray(0, glyphCount * GLYPH_INSTANCE_FLOATS), gl.DYNAMIC_DRAW);
|
|
398
|
+
gl.bindVertexArray(this.glyphVAO);
|
|
399
|
+
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, glyphCount);
|
|
400
|
+
gl.disable(gl.BLEND);
|
|
401
|
+
}
|
|
402
|
+
// --- Cursor ---
|
|
403
|
+
if (cursor.visible && this.bgProgram && this.bgVAO && this.bgInstanceVBO) {
|
|
404
|
+
const cc = this.themeCursorFloat;
|
|
405
|
+
gl.enable(gl.BLEND);
|
|
406
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
407
|
+
gl.useProgram(this.bgProgram);
|
|
408
|
+
gl.uniform2f(gl.getUniformLocation(this.bgProgram, "u_resolution"), vpW, vpH);
|
|
409
|
+
gl.uniform2f(gl.getUniformLocation(this.bgProgram, "u_cellSize"), cellW, cellH);
|
|
410
|
+
const cursorData = new Float32Array([cursor.col, cursor.row, cc[0], cc[1], cc[2], 0.5]);
|
|
411
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.bgInstanceVBO);
|
|
412
|
+
gl.bufferData(gl.ARRAY_BUFFER, cursorData, gl.DYNAMIC_DRAW);
|
|
413
|
+
gl.bindVertexArray(this.bgVAO);
|
|
414
|
+
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, 1);
|
|
415
|
+
gl.disable(gl.BLEND);
|
|
416
|
+
}
|
|
417
|
+
gl.bindVertexArray(null);
|
|
418
|
+
}
|
|
419
|
+
// -----------------------------------------------------------------------
|
|
420
|
+
// GL resource initialization
|
|
421
|
+
// -----------------------------------------------------------------------
|
|
422
|
+
initGLResources() {
|
|
423
|
+
const gl = this.gl;
|
|
424
|
+
if (!gl)
|
|
425
|
+
return;
|
|
426
|
+
this.bgProgram = createProgram(gl, BG_VERTEX_SHADER, BG_FRAGMENT_SHADER);
|
|
427
|
+
this.glyphProgram = createProgram(gl, GLYPH_VERTEX_SHADER, GLYPH_FRAGMENT_SHADER);
|
|
428
|
+
const quadVerts = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
|
429
|
+
const quadIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
|
|
430
|
+
this.quadVBO = gl.createBuffer();
|
|
431
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVBO);
|
|
432
|
+
gl.bufferData(gl.ARRAY_BUFFER, quadVerts, gl.STATIC_DRAW);
|
|
433
|
+
this.quadEBO = gl.createBuffer();
|
|
434
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.quadEBO);
|
|
435
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, quadIndices, gl.STATIC_DRAW);
|
|
436
|
+
this.bgInstanceVBO = gl.createBuffer();
|
|
437
|
+
this.glyphInstanceVBO = gl.createBuffer();
|
|
438
|
+
this.bgVAO = gl.createVertexArray();
|
|
439
|
+
gl.bindVertexArray(this.bgVAO);
|
|
440
|
+
this.setupBgVAO(gl);
|
|
441
|
+
gl.bindVertexArray(null);
|
|
442
|
+
this.glyphVAO = gl.createVertexArray();
|
|
443
|
+
gl.bindVertexArray(this.glyphVAO);
|
|
444
|
+
this.setupGlyphVAO(gl);
|
|
445
|
+
gl.bindVertexArray(null);
|
|
446
|
+
}
|
|
447
|
+
setupBgVAO(gl) {
|
|
448
|
+
const FLOAT = 4;
|
|
449
|
+
if (!this.bgProgram)
|
|
450
|
+
return;
|
|
451
|
+
const program = this.bgProgram;
|
|
452
|
+
const aPos = gl.getAttribLocation(program, "a_position");
|
|
453
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVBO);
|
|
454
|
+
gl.enableVertexAttribArray(aPos);
|
|
455
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
|
|
456
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.quadEBO);
|
|
457
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.bgInstanceVBO);
|
|
458
|
+
const stride = BG_INSTANCE_FLOATS * FLOAT;
|
|
459
|
+
const aCellPos = gl.getAttribLocation(program, "a_cellPos");
|
|
460
|
+
gl.enableVertexAttribArray(aCellPos);
|
|
461
|
+
gl.vertexAttribPointer(aCellPos, 2, gl.FLOAT, false, stride, 0);
|
|
462
|
+
gl.vertexAttribDivisor(aCellPos, 1);
|
|
463
|
+
const aColor = gl.getAttribLocation(program, "a_color");
|
|
464
|
+
gl.enableVertexAttribArray(aColor);
|
|
465
|
+
gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, stride, 2 * FLOAT);
|
|
466
|
+
gl.vertexAttribDivisor(aColor, 1);
|
|
467
|
+
}
|
|
468
|
+
setupGlyphVAO(gl) {
|
|
469
|
+
const FLOAT = 4;
|
|
470
|
+
if (!this.glyphProgram)
|
|
471
|
+
return;
|
|
472
|
+
const program = this.glyphProgram;
|
|
473
|
+
const aPos = gl.getAttribLocation(program, "a_position");
|
|
474
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVBO);
|
|
475
|
+
gl.enableVertexAttribArray(aPos);
|
|
476
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
|
|
477
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.quadEBO);
|
|
478
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphInstanceVBO);
|
|
479
|
+
const stride = GLYPH_INSTANCE_FLOATS * FLOAT;
|
|
480
|
+
const aCellPos = gl.getAttribLocation(program, "a_cellPos");
|
|
481
|
+
gl.enableVertexAttribArray(aCellPos);
|
|
482
|
+
gl.vertexAttribPointer(aCellPos, 2, gl.FLOAT, false, stride, 0);
|
|
483
|
+
gl.vertexAttribDivisor(aCellPos, 1);
|
|
484
|
+
const aColor = gl.getAttribLocation(program, "a_color");
|
|
485
|
+
gl.enableVertexAttribArray(aColor);
|
|
486
|
+
gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, stride, 2 * FLOAT);
|
|
487
|
+
gl.vertexAttribDivisor(aColor, 1);
|
|
488
|
+
const aTexCoord = gl.getAttribLocation(program, "a_texCoord");
|
|
489
|
+
gl.enableVertexAttribArray(aTexCoord);
|
|
490
|
+
gl.vertexAttribPointer(aTexCoord, 4, gl.FLOAT, false, stride, 6 * FLOAT);
|
|
491
|
+
gl.vertexAttribDivisor(aTexCoord, 1);
|
|
492
|
+
const aGlyphSize = gl.getAttribLocation(program, "a_glyphSize");
|
|
493
|
+
gl.enableVertexAttribArray(aGlyphSize);
|
|
494
|
+
gl.vertexAttribPointer(aGlyphSize, 2, gl.FLOAT, false, stride, 10 * FLOAT);
|
|
495
|
+
gl.vertexAttribDivisor(aGlyphSize, 1);
|
|
496
|
+
}
|
|
497
|
+
// -----------------------------------------------------------------------
|
|
498
|
+
// Color resolution
|
|
499
|
+
// -----------------------------------------------------------------------
|
|
500
|
+
buildPaletteFloat() {
|
|
501
|
+
this.paletteFloat = this.palette.map((c) => hexToFloat4(c));
|
|
502
|
+
this.themeFgFloat = hexToFloat4(this.theme.foreground);
|
|
503
|
+
this.themeBgFloat = hexToFloat4(this.theme.background);
|
|
504
|
+
this.themeCursorFloat = hexToFloat4(this.theme.cursor);
|
|
505
|
+
}
|
|
506
|
+
resolveColorFloat(colorIdx, isRGB, grid, col, isForeground) {
|
|
507
|
+
if (isRGB) {
|
|
508
|
+
const offset = isForeground ? col : 256 + col;
|
|
509
|
+
const rgb = grid.rgbColors[offset];
|
|
510
|
+
const r = ((rgb >> 16) & 0xff) / 255;
|
|
511
|
+
const g = ((rgb >> 8) & 0xff) / 255;
|
|
512
|
+
const b = (rgb & 0xff) / 255;
|
|
513
|
+
return [r, g, b, 1.0];
|
|
514
|
+
}
|
|
515
|
+
if (isForeground && colorIdx === 7)
|
|
516
|
+
return this.themeFgFloat;
|
|
517
|
+
if (!isForeground && colorIdx === 0)
|
|
518
|
+
return this.themeBgFloat;
|
|
519
|
+
if (colorIdx >= 0 && colorIdx < 256) {
|
|
520
|
+
return this.paletteFloat[colorIdx];
|
|
521
|
+
}
|
|
522
|
+
return isForeground ? this.themeFgFloat : this.themeBgFloat;
|
|
523
|
+
}
|
|
524
|
+
// -----------------------------------------------------------------------
|
|
525
|
+
// Cell measurement
|
|
526
|
+
// -----------------------------------------------------------------------
|
|
527
|
+
measureCellSize() {
|
|
528
|
+
const offscreen = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(100, 100) : null;
|
|
529
|
+
let measureCtx = null;
|
|
530
|
+
if (offscreen) {
|
|
531
|
+
measureCtx = offscreen.getContext("2d");
|
|
532
|
+
}
|
|
533
|
+
else if (typeof document !== "undefined") {
|
|
534
|
+
const tmpCanvas = document.createElement("canvas");
|
|
535
|
+
tmpCanvas.width = 100;
|
|
536
|
+
tmpCanvas.height = 100;
|
|
537
|
+
measureCtx = tmpCanvas.getContext("2d");
|
|
538
|
+
}
|
|
539
|
+
if (!measureCtx) {
|
|
540
|
+
this.cellWidth = Math.ceil(this.fontSize * 0.6);
|
|
541
|
+
this.cellHeight = Math.ceil(this.fontSize * 1.2);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const font = `${this.fontSize}px ${this.fontFamily}`;
|
|
545
|
+
measureCtx.font = font;
|
|
546
|
+
const metrics = measureCtx.measureText("M");
|
|
547
|
+
this.cellWidth = Math.ceil(metrics.width);
|
|
548
|
+
if (typeof metrics.fontBoundingBoxAscent === "number" &&
|
|
549
|
+
typeof metrics.fontBoundingBoxDescent === "number") {
|
|
550
|
+
this.cellHeight = Math.ceil(metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
this.cellHeight = Math.ceil(this.fontSize * 1.2);
|
|
554
|
+
}
|
|
555
|
+
if (this.cellWidth <= 0)
|
|
556
|
+
this.cellWidth = Math.ceil(this.fontSize * 0.6);
|
|
557
|
+
if (this.cellHeight <= 0)
|
|
558
|
+
this.cellHeight = Math.ceil(this.fontSize * 1.2);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
//# sourceMappingURL=shared-context.js.map
|