@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,720 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render Worker entry point.
|
|
3
|
+
*
|
|
4
|
+
* Receives an OffscreenCanvas (transferred from the main thread) and a
|
|
5
|
+
* SharedArrayBuffer reference for the CellGrid. Runs its own WebGL2
|
|
6
|
+
* render loop at display refresh rate via requestAnimationFrame.
|
|
7
|
+
*
|
|
8
|
+
* The worker owns the glyph atlas and all GL resources. The main thread
|
|
9
|
+
* only sends lightweight messages for cursor, selection, theme, font,
|
|
10
|
+
* and resize events.
|
|
11
|
+
*/
|
|
12
|
+
import { CellGrid, DEFAULT_THEME } from "@next_term/core";
|
|
13
|
+
import { build256Palette } from "./renderer.js";
|
|
14
|
+
import { BG_INSTANCE_FLOATS, GLYPH_INSTANCE_FLOATS, GlyphAtlas, hexToFloat4, packBgInstance, packGlyphInstance, } from "./webgl-renderer.js";
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Attribute bit positions
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const ATTR_BOLD = 0x01;
|
|
19
|
+
const ATTR_ITALIC = 0x02;
|
|
20
|
+
const ATTR_INVERSE = 0x40;
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Shader sources (duplicated from webgl-renderer.ts for worker isolation)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
const BG_VERTEX_SHADER = `#version 300 es
|
|
25
|
+
in vec2 a_position;
|
|
26
|
+
in vec2 a_cellPos;
|
|
27
|
+
in vec4 a_color;
|
|
28
|
+
uniform vec2 u_resolution;
|
|
29
|
+
uniform vec2 u_cellSize;
|
|
30
|
+
out vec4 v_color;
|
|
31
|
+
void main() {
|
|
32
|
+
vec2 cellPixelPos = a_cellPos * u_cellSize;
|
|
33
|
+
vec2 pos = cellPixelPos + a_position * u_cellSize;
|
|
34
|
+
vec2 clipPos = (pos / u_resolution) * 2.0 - 1.0;
|
|
35
|
+
clipPos.y = -clipPos.y;
|
|
36
|
+
gl_Position = vec4(clipPos, 0.0, 1.0);
|
|
37
|
+
v_color = a_color;
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
const BG_FRAGMENT_SHADER = `#version 300 es
|
|
41
|
+
precision mediump float;
|
|
42
|
+
in vec4 v_color;
|
|
43
|
+
out vec4 fragColor;
|
|
44
|
+
void main() {
|
|
45
|
+
fragColor = v_color;
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
const GLYPH_VERTEX_SHADER = `#version 300 es
|
|
49
|
+
in vec2 a_position;
|
|
50
|
+
in vec2 a_cellPos;
|
|
51
|
+
in vec4 a_color;
|
|
52
|
+
in vec4 a_texCoord;
|
|
53
|
+
in vec2 a_glyphSize;
|
|
54
|
+
uniform vec2 u_resolution;
|
|
55
|
+
uniform vec2 u_cellSize;
|
|
56
|
+
out vec4 v_color;
|
|
57
|
+
out vec2 v_texCoord;
|
|
58
|
+
void main() {
|
|
59
|
+
vec2 cellPixelPos = a_cellPos * u_cellSize;
|
|
60
|
+
vec2 size = (a_glyphSize.x > 0.0) ? a_glyphSize : u_cellSize;
|
|
61
|
+
vec2 pos = cellPixelPos + a_position * size;
|
|
62
|
+
vec2 clipPos = (pos / u_resolution) * 2.0 - 1.0;
|
|
63
|
+
clipPos.y = -clipPos.y;
|
|
64
|
+
gl_Position = vec4(clipPos, 0.0, 1.0);
|
|
65
|
+
v_color = a_color;
|
|
66
|
+
v_texCoord = a_texCoord.xy + a_position * a_texCoord.zw;
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
const GLYPH_FRAGMENT_SHADER = `#version 300 es
|
|
70
|
+
precision mediump float;
|
|
71
|
+
in vec4 v_color;
|
|
72
|
+
in vec2 v_texCoord;
|
|
73
|
+
uniform sampler2D u_atlas;
|
|
74
|
+
out vec4 fragColor;
|
|
75
|
+
void main() {
|
|
76
|
+
float alpha = texture(u_atlas, v_texCoord).a;
|
|
77
|
+
fragColor = vec4(v_color.rgb, v_color.a * alpha);
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Shader helpers
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
function compileShader(gl, type, source) {
|
|
84
|
+
const shader = gl.createShader(type);
|
|
85
|
+
if (!shader)
|
|
86
|
+
throw new Error("Failed to create shader");
|
|
87
|
+
gl.shaderSource(shader, source);
|
|
88
|
+
gl.compileShader(shader);
|
|
89
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
90
|
+
const info = gl.getShaderInfoLog(shader);
|
|
91
|
+
gl.deleteShader(shader);
|
|
92
|
+
throw new Error(`Shader compile error: ${info}`);
|
|
93
|
+
}
|
|
94
|
+
return shader;
|
|
95
|
+
}
|
|
96
|
+
function createProgram(gl, vertSrc, fragSrc) {
|
|
97
|
+
const vert = compileShader(gl, gl.VERTEX_SHADER, vertSrc);
|
|
98
|
+
const frag = compileShader(gl, gl.FRAGMENT_SHADER, fragSrc);
|
|
99
|
+
const program = gl.createProgram();
|
|
100
|
+
if (!program)
|
|
101
|
+
throw new Error("Failed to create program");
|
|
102
|
+
gl.attachShader(program, vert);
|
|
103
|
+
gl.attachShader(program, frag);
|
|
104
|
+
gl.linkProgram(program);
|
|
105
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
106
|
+
const info = gl.getProgramInfoLog(program);
|
|
107
|
+
gl.deleteProgram(program);
|
|
108
|
+
throw new Error(`Program link error: ${info}`);
|
|
109
|
+
}
|
|
110
|
+
gl.detachShader(program, vert);
|
|
111
|
+
gl.detachShader(program, frag);
|
|
112
|
+
gl.deleteShader(vert);
|
|
113
|
+
gl.deleteShader(frag);
|
|
114
|
+
return program;
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Worker state
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
let canvas = null;
|
|
120
|
+
let gl = null;
|
|
121
|
+
let grid = null;
|
|
122
|
+
let atlas = null;
|
|
123
|
+
let cols = 0;
|
|
124
|
+
let rows = 0;
|
|
125
|
+
let dpr = 1;
|
|
126
|
+
let fontSize = 14;
|
|
127
|
+
let fontFamily = "monospace";
|
|
128
|
+
let theme = DEFAULT_THEME;
|
|
129
|
+
let palette = [];
|
|
130
|
+
let paletteFloat = [];
|
|
131
|
+
let themeFgFloat = [0, 0, 0, 1];
|
|
132
|
+
let themeBgFloat = [0, 0, 0, 1];
|
|
133
|
+
let themeCursorFloat = [0, 0, 0, 1];
|
|
134
|
+
let cellWidth = 0;
|
|
135
|
+
let cellHeight = 0;
|
|
136
|
+
// Cursor & selection (updated via postMessage or SAB)
|
|
137
|
+
let cursorRow = 0;
|
|
138
|
+
let cursorCol = 0;
|
|
139
|
+
let cursorVisible = true;
|
|
140
|
+
let prevCursorRow = -1;
|
|
141
|
+
let prevCursorCol = -1;
|
|
142
|
+
let cursorStyle = "block";
|
|
143
|
+
let selection = null;
|
|
144
|
+
// GL resources
|
|
145
|
+
let bgProgram = null;
|
|
146
|
+
let glyphProgram = null;
|
|
147
|
+
let quadVBO = null;
|
|
148
|
+
let quadEBO = null;
|
|
149
|
+
let bgInstanceVBO = null;
|
|
150
|
+
let glyphInstanceVBO = null;
|
|
151
|
+
let bgVAO = null;
|
|
152
|
+
let glyphVAO = null;
|
|
153
|
+
let bgInstances = new Float32Array(0);
|
|
154
|
+
let glyphInstances = new Float32Array(0);
|
|
155
|
+
let rafId = null;
|
|
156
|
+
let disposed = false;
|
|
157
|
+
let contextLost = false;
|
|
158
|
+
// FPS tracking
|
|
159
|
+
let frameCount = 0;
|
|
160
|
+
let lastFpsTime = 0;
|
|
161
|
+
let currentFps = 0;
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Helpers
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
function buildPaletteFloat() {
|
|
166
|
+
palette = build256Palette(theme);
|
|
167
|
+
paletteFloat = palette.map((c) => hexToFloat4(c));
|
|
168
|
+
themeFgFloat = hexToFloat4(theme.foreground);
|
|
169
|
+
themeBgFloat = hexToFloat4(theme.background);
|
|
170
|
+
themeCursorFloat = hexToFloat4(theme.cursor);
|
|
171
|
+
}
|
|
172
|
+
function measureCellSize() {
|
|
173
|
+
const measureCanvas = new OffscreenCanvas(100, 100);
|
|
174
|
+
const ctx = measureCanvas.getContext("2d");
|
|
175
|
+
if (!ctx) {
|
|
176
|
+
cellWidth = Math.ceil(fontSize * 0.6);
|
|
177
|
+
cellHeight = Math.ceil(fontSize * 1.2);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
181
|
+
const metrics = ctx.measureText("M");
|
|
182
|
+
cellWidth = Math.ceil(metrics.width);
|
|
183
|
+
if (typeof metrics.fontBoundingBoxAscent === "number" &&
|
|
184
|
+
typeof metrics.fontBoundingBoxDescent === "number") {
|
|
185
|
+
cellHeight = Math.ceil(metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
cellHeight = Math.ceil(fontSize * 1.2);
|
|
189
|
+
}
|
|
190
|
+
if (cellWidth <= 0)
|
|
191
|
+
cellWidth = Math.ceil(fontSize * 0.6);
|
|
192
|
+
if (cellHeight <= 0)
|
|
193
|
+
cellHeight = Math.ceil(fontSize * 1.2);
|
|
194
|
+
}
|
|
195
|
+
function resolveColorFloat(colorIdx, isRGB, g, col, isForeground) {
|
|
196
|
+
if (isRGB) {
|
|
197
|
+
const offset = isForeground ? col : 256 + col;
|
|
198
|
+
const rgb = g.rgbColors[offset];
|
|
199
|
+
const r = ((rgb >> 16) & 0xff) / 255;
|
|
200
|
+
const green = ((rgb >> 8) & 0xff) / 255;
|
|
201
|
+
const b = (rgb & 0xff) / 255;
|
|
202
|
+
return [r, green, b, 1.0];
|
|
203
|
+
}
|
|
204
|
+
if (isForeground && colorIdx === 7)
|
|
205
|
+
return themeFgFloat;
|
|
206
|
+
if (!isForeground && colorIdx === 0)
|
|
207
|
+
return themeBgFloat;
|
|
208
|
+
if (colorIdx >= 0 && colorIdx < 256) {
|
|
209
|
+
return paletteFloat[colorIdx];
|
|
210
|
+
}
|
|
211
|
+
return isForeground ? themeFgFloat : themeBgFloat;
|
|
212
|
+
}
|
|
213
|
+
function createGridFromSAB(buffer, c, r) {
|
|
214
|
+
// Create a CellGrid view over the shared buffer.
|
|
215
|
+
// We construct a CellGrid with matching dims then overlay the buffer.
|
|
216
|
+
// Since CellGrid constructor allocates its own buffer, we need to construct
|
|
217
|
+
// a lightweight wrapper that uses the shared buffer directly.
|
|
218
|
+
const g = Object.create(CellGrid.prototype);
|
|
219
|
+
const CELL_SIZE = 2;
|
|
220
|
+
const cellBytes = c * r * CELL_SIZE * 4;
|
|
221
|
+
const dirtyBytes = r * 4;
|
|
222
|
+
const rgbBytes = 512 * 4;
|
|
223
|
+
const cursorBytes = 4 * 4;
|
|
224
|
+
// Use Object.defineProperty to set readonly properties
|
|
225
|
+
Object.defineProperty(g, "cols", { value: c, writable: false });
|
|
226
|
+
Object.defineProperty(g, "rows", { value: r, writable: false });
|
|
227
|
+
Object.defineProperty(g, "isShared", { value: true, writable: false });
|
|
228
|
+
Object.defineProperty(g, "data", {
|
|
229
|
+
value: new Uint32Array(buffer, 0, c * r * CELL_SIZE),
|
|
230
|
+
writable: false,
|
|
231
|
+
});
|
|
232
|
+
Object.defineProperty(g, "dirtyRows", {
|
|
233
|
+
value: new Int32Array(buffer, cellBytes, r),
|
|
234
|
+
writable: false,
|
|
235
|
+
});
|
|
236
|
+
Object.defineProperty(g, "rgbColors", {
|
|
237
|
+
value: new Uint32Array(buffer, cellBytes + dirtyBytes, 512),
|
|
238
|
+
writable: false,
|
|
239
|
+
});
|
|
240
|
+
Object.defineProperty(g, "cursorData", {
|
|
241
|
+
value: new Int32Array(buffer, cellBytes + dirtyBytes + rgbBytes, 4),
|
|
242
|
+
writable: false,
|
|
243
|
+
});
|
|
244
|
+
Object.defineProperty(g, "rowOffsetData", {
|
|
245
|
+
value: new Int32Array(buffer, cellBytes + dirtyBytes + rgbBytes + cursorBytes, 1),
|
|
246
|
+
writable: false,
|
|
247
|
+
});
|
|
248
|
+
// Set private buffer field for getBuffer()
|
|
249
|
+
Object.defineProperty(g, "buffer", { value: buffer, writable: false });
|
|
250
|
+
return g;
|
|
251
|
+
}
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// GL resource management
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
function initGLResources() {
|
|
256
|
+
if (!gl)
|
|
257
|
+
return;
|
|
258
|
+
bgProgram = createProgram(gl, BG_VERTEX_SHADER, BG_FRAGMENT_SHADER);
|
|
259
|
+
glyphProgram = createProgram(gl, GLYPH_VERTEX_SHADER, GLYPH_FRAGMENT_SHADER);
|
|
260
|
+
const quadVerts = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
|
261
|
+
const quadIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
|
|
262
|
+
quadVBO = gl.createBuffer();
|
|
263
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, quadVBO);
|
|
264
|
+
gl.bufferData(gl.ARRAY_BUFFER, quadVerts, gl.STATIC_DRAW);
|
|
265
|
+
quadEBO = gl.createBuffer();
|
|
266
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, quadEBO);
|
|
267
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, quadIndices, gl.STATIC_DRAW);
|
|
268
|
+
bgInstanceVBO = gl.createBuffer();
|
|
269
|
+
glyphInstanceVBO = gl.createBuffer();
|
|
270
|
+
bgVAO = gl.createVertexArray();
|
|
271
|
+
gl.bindVertexArray(bgVAO);
|
|
272
|
+
setupBgVAO(gl);
|
|
273
|
+
gl.bindVertexArray(null);
|
|
274
|
+
glyphVAO = gl.createVertexArray();
|
|
275
|
+
gl.bindVertexArray(glyphVAO);
|
|
276
|
+
setupGlyphVAO(gl);
|
|
277
|
+
gl.bindVertexArray(null);
|
|
278
|
+
if (atlas)
|
|
279
|
+
atlas.recreateTexture();
|
|
280
|
+
}
|
|
281
|
+
function setupBgVAO(g) {
|
|
282
|
+
const FLOAT = 4;
|
|
283
|
+
if (!bgProgram)
|
|
284
|
+
return;
|
|
285
|
+
const program = bgProgram;
|
|
286
|
+
const aPos = g.getAttribLocation(program, "a_position");
|
|
287
|
+
g.bindBuffer(g.ARRAY_BUFFER, quadVBO);
|
|
288
|
+
g.enableVertexAttribArray(aPos);
|
|
289
|
+
g.vertexAttribPointer(aPos, 2, g.FLOAT, false, 0, 0);
|
|
290
|
+
g.bindBuffer(g.ELEMENT_ARRAY_BUFFER, quadEBO);
|
|
291
|
+
g.bindBuffer(g.ARRAY_BUFFER, bgInstanceVBO);
|
|
292
|
+
const stride = BG_INSTANCE_FLOATS * FLOAT;
|
|
293
|
+
const aCellPos = g.getAttribLocation(program, "a_cellPos");
|
|
294
|
+
g.enableVertexAttribArray(aCellPos);
|
|
295
|
+
g.vertexAttribPointer(aCellPos, 2, g.FLOAT, false, stride, 0);
|
|
296
|
+
g.vertexAttribDivisor(aCellPos, 1);
|
|
297
|
+
const aColor = g.getAttribLocation(program, "a_color");
|
|
298
|
+
g.enableVertexAttribArray(aColor);
|
|
299
|
+
g.vertexAttribPointer(aColor, 4, g.FLOAT, false, stride, 2 * FLOAT);
|
|
300
|
+
g.vertexAttribDivisor(aColor, 1);
|
|
301
|
+
}
|
|
302
|
+
function setupGlyphVAO(g) {
|
|
303
|
+
const FLOAT = 4;
|
|
304
|
+
if (!glyphProgram)
|
|
305
|
+
return;
|
|
306
|
+
const program = glyphProgram;
|
|
307
|
+
const aPos = g.getAttribLocation(program, "a_position");
|
|
308
|
+
g.bindBuffer(g.ARRAY_BUFFER, quadVBO);
|
|
309
|
+
g.enableVertexAttribArray(aPos);
|
|
310
|
+
g.vertexAttribPointer(aPos, 2, g.FLOAT, false, 0, 0);
|
|
311
|
+
g.bindBuffer(g.ELEMENT_ARRAY_BUFFER, quadEBO);
|
|
312
|
+
g.bindBuffer(g.ARRAY_BUFFER, glyphInstanceVBO);
|
|
313
|
+
const stride = GLYPH_INSTANCE_FLOATS * FLOAT;
|
|
314
|
+
const aCellPos = g.getAttribLocation(program, "a_cellPos");
|
|
315
|
+
g.enableVertexAttribArray(aCellPos);
|
|
316
|
+
g.vertexAttribPointer(aCellPos, 2, g.FLOAT, false, stride, 0);
|
|
317
|
+
g.vertexAttribDivisor(aCellPos, 1);
|
|
318
|
+
const aColor = g.getAttribLocation(program, "a_color");
|
|
319
|
+
g.enableVertexAttribArray(aColor);
|
|
320
|
+
g.vertexAttribPointer(aColor, 4, g.FLOAT, false, stride, 2 * FLOAT);
|
|
321
|
+
g.vertexAttribDivisor(aColor, 1);
|
|
322
|
+
const aTexCoord = g.getAttribLocation(program, "a_texCoord");
|
|
323
|
+
g.enableVertexAttribArray(aTexCoord);
|
|
324
|
+
g.vertexAttribPointer(aTexCoord, 4, g.FLOAT, false, stride, 6 * FLOAT);
|
|
325
|
+
g.vertexAttribDivisor(aTexCoord, 1);
|
|
326
|
+
const aGlyphSize = g.getAttribLocation(program, "a_glyphSize");
|
|
327
|
+
g.enableVertexAttribArray(aGlyphSize);
|
|
328
|
+
g.vertexAttribPointer(aGlyphSize, 2, g.FLOAT, false, stride, 10 * FLOAT);
|
|
329
|
+
g.vertexAttribDivisor(aGlyphSize, 1);
|
|
330
|
+
}
|
|
331
|
+
function ensureInstanceBuffers() {
|
|
332
|
+
const totalCells = cols * rows;
|
|
333
|
+
const neededBg = totalCells * BG_INSTANCE_FLOATS;
|
|
334
|
+
const neededGlyph = totalCells * GLYPH_INSTANCE_FLOATS;
|
|
335
|
+
if (bgInstances.length < neededBg) {
|
|
336
|
+
bgInstances = new Float32Array(neededBg);
|
|
337
|
+
}
|
|
338
|
+
if (glyphInstances.length < neededGlyph) {
|
|
339
|
+
glyphInstances = new Float32Array(neededGlyph);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function syncCanvasSize() {
|
|
343
|
+
if (!canvas)
|
|
344
|
+
return;
|
|
345
|
+
const width = cols * cellWidth;
|
|
346
|
+
const height = rows * cellHeight;
|
|
347
|
+
canvas.width = Math.round(width * dpr);
|
|
348
|
+
canvas.height = Math.round(height * dpr);
|
|
349
|
+
}
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// Render
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
function render() {
|
|
354
|
+
if (disposed || contextLost || !gl || !grid)
|
|
355
|
+
return;
|
|
356
|
+
// Read cursor from SAB
|
|
357
|
+
const cursor = grid.getCursor();
|
|
358
|
+
cursorRow = cursor.row;
|
|
359
|
+
cursorCol = cursor.col;
|
|
360
|
+
cursorVisible = cursor.visible;
|
|
361
|
+
cursorStyle = cursor.style;
|
|
362
|
+
// If cursor moved, mark old and new rows dirty to erase ghost and draw fresh
|
|
363
|
+
if (prevCursorRow >= 0 &&
|
|
364
|
+
prevCursorRow < rows &&
|
|
365
|
+
(prevCursorRow !== cursorRow || prevCursorCol !== cursorCol)) {
|
|
366
|
+
grid.markDirty(prevCursorRow);
|
|
367
|
+
}
|
|
368
|
+
if (cursorRow >= 0 && cursorRow < rows) {
|
|
369
|
+
grid.markDirty(cursorRow);
|
|
370
|
+
}
|
|
371
|
+
prevCursorRow = cursorRow;
|
|
372
|
+
prevCursorCol = cursorCol;
|
|
373
|
+
// Check dirty rows
|
|
374
|
+
let anyDirty = false;
|
|
375
|
+
for (let r = 0; r < rows; r++) {
|
|
376
|
+
if (grid.isDirty(r)) {
|
|
377
|
+
anyDirty = true;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (!anyDirty)
|
|
382
|
+
return;
|
|
383
|
+
let bgCount = 0;
|
|
384
|
+
let glyphCount = 0;
|
|
385
|
+
for (let row = 0; row < rows; row++) {
|
|
386
|
+
for (let col = 0; col < cols; col++) {
|
|
387
|
+
const codepoint = grid.getCodepoint(row, col);
|
|
388
|
+
const fgIdx = grid.getFgIndex(row, col);
|
|
389
|
+
const bgIdx = grid.getBgIndex(row, col);
|
|
390
|
+
const attrs = grid.getAttrs(row, col);
|
|
391
|
+
const fgIsRGB = grid.isFgRGB(row, col);
|
|
392
|
+
const bgIsRGB = grid.isBgRGB(row, col);
|
|
393
|
+
let fg = resolveColorFloat(fgIdx, fgIsRGB, grid, col, true);
|
|
394
|
+
let bg = resolveColorFloat(bgIdx, bgIsRGB, grid, col, false);
|
|
395
|
+
if (attrs & ATTR_INVERSE) {
|
|
396
|
+
const tmp = fg;
|
|
397
|
+
fg = bg;
|
|
398
|
+
bg = tmp;
|
|
399
|
+
}
|
|
400
|
+
packBgInstance(bgInstances, bgCount * BG_INSTANCE_FLOATS, col, row, bg[0], bg[1], bg[2], bg[3]);
|
|
401
|
+
bgCount++;
|
|
402
|
+
if (codepoint > 0x20) {
|
|
403
|
+
const bold = !!(attrs & ATTR_BOLD);
|
|
404
|
+
const italic = !!(attrs & ATTR_ITALIC);
|
|
405
|
+
const glyph = atlas?.getGlyph(codepoint, bold, italic);
|
|
406
|
+
if (glyph) {
|
|
407
|
+
packGlyphInstance(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);
|
|
408
|
+
glyphCount++;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
grid.clearDirty(row);
|
|
413
|
+
}
|
|
414
|
+
// Upload atlas
|
|
415
|
+
atlas?.upload(gl);
|
|
416
|
+
const canvasWidth = canvas?.width ?? 0;
|
|
417
|
+
const canvasHeight = canvas?.height ?? 0;
|
|
418
|
+
gl.viewport(0, 0, canvasWidth, canvasHeight);
|
|
419
|
+
gl.clearColor(themeBgFloat[0], themeBgFloat[1], themeBgFloat[2], 1.0);
|
|
420
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
421
|
+
const cellW = cellWidth * dpr;
|
|
422
|
+
const cellH = cellHeight * dpr;
|
|
423
|
+
// Background pass
|
|
424
|
+
if (bgCount > 0 && bgProgram && bgVAO && bgInstanceVBO) {
|
|
425
|
+
gl.useProgram(bgProgram);
|
|
426
|
+
gl.uniform2f(gl.getUniformLocation(bgProgram, "u_resolution"), canvasWidth, canvasHeight);
|
|
427
|
+
gl.uniform2f(gl.getUniformLocation(bgProgram, "u_cellSize"), cellW, cellH);
|
|
428
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, bgInstanceVBO);
|
|
429
|
+
gl.bufferData(gl.ARRAY_BUFFER, bgInstances.subarray(0, bgCount * BG_INSTANCE_FLOATS), gl.DYNAMIC_DRAW);
|
|
430
|
+
gl.bindVertexArray(bgVAO);
|
|
431
|
+
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, bgCount);
|
|
432
|
+
}
|
|
433
|
+
// Glyph pass
|
|
434
|
+
if (glyphCount > 0 && glyphProgram && glyphVAO && glyphInstanceVBO) {
|
|
435
|
+
gl.enable(gl.BLEND);
|
|
436
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
437
|
+
gl.useProgram(glyphProgram);
|
|
438
|
+
gl.uniform2f(gl.getUniformLocation(glyphProgram, "u_resolution"), canvasWidth, canvasHeight);
|
|
439
|
+
gl.uniform2f(gl.getUniformLocation(glyphProgram, "u_cellSize"), cellW, cellH);
|
|
440
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
441
|
+
gl.bindTexture(gl.TEXTURE_2D, atlas?.getTexture() ?? null);
|
|
442
|
+
gl.uniform1i(gl.getUniformLocation(glyphProgram, "u_atlas"), 0);
|
|
443
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, glyphInstanceVBO);
|
|
444
|
+
gl.bufferData(gl.ARRAY_BUFFER, glyphInstances.subarray(0, glyphCount * GLYPH_INSTANCE_FLOATS), gl.DYNAMIC_DRAW);
|
|
445
|
+
gl.bindVertexArray(glyphVAO);
|
|
446
|
+
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, glyphCount);
|
|
447
|
+
gl.disable(gl.BLEND);
|
|
448
|
+
}
|
|
449
|
+
// Selection overlay
|
|
450
|
+
drawSelection();
|
|
451
|
+
// Cursor
|
|
452
|
+
drawCursor();
|
|
453
|
+
gl.bindVertexArray(null);
|
|
454
|
+
}
|
|
455
|
+
function drawSelection() {
|
|
456
|
+
if (!gl || !grid || !selection)
|
|
457
|
+
return;
|
|
458
|
+
if (!bgProgram || !bgVAO || !bgInstanceVBO)
|
|
459
|
+
return;
|
|
460
|
+
const sr = Math.max(0, selection.startRow);
|
|
461
|
+
const er = Math.min(rows - 1, selection.endRow);
|
|
462
|
+
if (sr === er && selection.startCol === selection.endCol)
|
|
463
|
+
return;
|
|
464
|
+
const selColor = hexToFloat4(theme.selectionBackground);
|
|
465
|
+
const selInstances = [];
|
|
466
|
+
for (let row = sr; row <= er; row++) {
|
|
467
|
+
let colStart;
|
|
468
|
+
let colEnd;
|
|
469
|
+
if (sr === er) {
|
|
470
|
+
colStart = selection.startCol;
|
|
471
|
+
colEnd = selection.endCol;
|
|
472
|
+
}
|
|
473
|
+
else if (row === sr) {
|
|
474
|
+
colStart = selection.startCol;
|
|
475
|
+
colEnd = cols - 1;
|
|
476
|
+
}
|
|
477
|
+
else if (row === er) {
|
|
478
|
+
colStart = 0;
|
|
479
|
+
colEnd = selection.endCol;
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
colStart = 0;
|
|
483
|
+
colEnd = cols - 1;
|
|
484
|
+
}
|
|
485
|
+
for (let col = colStart; col <= colEnd; col++) {
|
|
486
|
+
selInstances.push(col, row, selColor[0], selColor[1], selColor[2], 0.5);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (selInstances.length === 0)
|
|
490
|
+
return;
|
|
491
|
+
const selData = new Float32Array(selInstances);
|
|
492
|
+
const selCount = selInstances.length / BG_INSTANCE_FLOATS;
|
|
493
|
+
gl.enable(gl.BLEND);
|
|
494
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
495
|
+
gl.useProgram(bgProgram);
|
|
496
|
+
gl.uniform2f(gl.getUniformLocation(bgProgram, "u_resolution"), canvas?.width ?? 0, canvas?.height ?? 0);
|
|
497
|
+
gl.uniform2f(gl.getUniformLocation(bgProgram, "u_cellSize"), cellWidth * dpr, cellHeight * dpr);
|
|
498
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, bgInstanceVBO);
|
|
499
|
+
gl.bufferData(gl.ARRAY_BUFFER, selData, gl.DYNAMIC_DRAW);
|
|
500
|
+
gl.bindVertexArray(bgVAO);
|
|
501
|
+
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, selCount);
|
|
502
|
+
gl.disable(gl.BLEND);
|
|
503
|
+
}
|
|
504
|
+
function drawCursor() {
|
|
505
|
+
if (!gl || !cursorVisible)
|
|
506
|
+
return;
|
|
507
|
+
if (!bgProgram || !bgVAO || !bgInstanceVBO)
|
|
508
|
+
return;
|
|
509
|
+
const cc = themeCursorFloat;
|
|
510
|
+
gl.enable(gl.BLEND);
|
|
511
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
512
|
+
gl.useProgram(bgProgram);
|
|
513
|
+
gl.uniform2f(gl.getUniformLocation(bgProgram, "u_resolution"), canvas?.width ?? 0, canvas?.height ?? 0);
|
|
514
|
+
gl.uniform2f(gl.getUniformLocation(bgProgram, "u_cellSize"), cellWidth * dpr, cellHeight * dpr);
|
|
515
|
+
let cursorData;
|
|
516
|
+
switch (cursorStyle) {
|
|
517
|
+
case "block":
|
|
518
|
+
cursorData = new Float32Array([cursorCol, cursorRow, cc[0], cc[1], cc[2], 0.5]);
|
|
519
|
+
break;
|
|
520
|
+
case "underline": {
|
|
521
|
+
const lineH = Math.max(2 * dpr, 1);
|
|
522
|
+
const fractionalRow = cursorRow + (cellHeight * dpr - lineH) / (cellHeight * dpr);
|
|
523
|
+
cursorData = new Float32Array([cursorCol, fractionalRow, cc[0], cc[1], cc[2], cc[3]]);
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case "bar":
|
|
527
|
+
cursorData = new Float32Array([cursorCol, cursorRow, cc[0], cc[1], cc[2], cc[3]]);
|
|
528
|
+
break;
|
|
529
|
+
default:
|
|
530
|
+
cursorData = new Float32Array([cursorCol, cursorRow, cc[0], cc[1], cc[2], 0.5]);
|
|
531
|
+
}
|
|
532
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, bgInstanceVBO);
|
|
533
|
+
gl.bufferData(gl.ARRAY_BUFFER, cursorData, gl.DYNAMIC_DRAW);
|
|
534
|
+
gl.bindVertexArray(bgVAO);
|
|
535
|
+
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, 1);
|
|
536
|
+
gl.disable(gl.BLEND);
|
|
537
|
+
}
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// Render loop
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
function startRenderLoop() {
|
|
542
|
+
if (disposed)
|
|
543
|
+
return;
|
|
544
|
+
lastFpsTime = performance.now();
|
|
545
|
+
frameCount = 0;
|
|
546
|
+
const loop = () => {
|
|
547
|
+
if (disposed)
|
|
548
|
+
return;
|
|
549
|
+
render();
|
|
550
|
+
// FPS tracking
|
|
551
|
+
frameCount++;
|
|
552
|
+
const now = performance.now();
|
|
553
|
+
const elapsed = now - lastFpsTime;
|
|
554
|
+
if (elapsed >= 1000) {
|
|
555
|
+
currentFps = Math.round((frameCount * 1000) / elapsed);
|
|
556
|
+
frameCount = 0;
|
|
557
|
+
lastFpsTime = now;
|
|
558
|
+
// Report FPS back to main thread
|
|
559
|
+
const msg = { type: "frame", fps: currentFps };
|
|
560
|
+
self.postMessage(msg);
|
|
561
|
+
}
|
|
562
|
+
rafId = requestAnimationFrame(loop);
|
|
563
|
+
};
|
|
564
|
+
rafId = requestAnimationFrame(loop);
|
|
565
|
+
}
|
|
566
|
+
function stopRenderLoop() {
|
|
567
|
+
if (rafId !== null) {
|
|
568
|
+
cancelAnimationFrame(rafId);
|
|
569
|
+
rafId = null;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
// Dispose
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
function disposeResources() {
|
|
576
|
+
disposed = true;
|
|
577
|
+
stopRenderLoop();
|
|
578
|
+
if (gl) {
|
|
579
|
+
if (atlas)
|
|
580
|
+
atlas.dispose(gl);
|
|
581
|
+
if (bgProgram)
|
|
582
|
+
gl.deleteProgram(bgProgram);
|
|
583
|
+
if (glyphProgram)
|
|
584
|
+
gl.deleteProgram(glyphProgram);
|
|
585
|
+
if (quadVBO)
|
|
586
|
+
gl.deleteBuffer(quadVBO);
|
|
587
|
+
if (quadEBO)
|
|
588
|
+
gl.deleteBuffer(quadEBO);
|
|
589
|
+
if (bgInstanceVBO)
|
|
590
|
+
gl.deleteBuffer(bgInstanceVBO);
|
|
591
|
+
if (glyphInstanceVBO)
|
|
592
|
+
gl.deleteBuffer(glyphInstanceVBO);
|
|
593
|
+
if (bgVAO)
|
|
594
|
+
gl.deleteVertexArray(bgVAO);
|
|
595
|
+
if (glyphVAO)
|
|
596
|
+
gl.deleteVertexArray(glyphVAO);
|
|
597
|
+
}
|
|
598
|
+
canvas = null;
|
|
599
|
+
gl = null;
|
|
600
|
+
grid = null;
|
|
601
|
+
atlas = null;
|
|
602
|
+
}
|
|
603
|
+
// ---------------------------------------------------------------------------
|
|
604
|
+
// Message handler
|
|
605
|
+
// ---------------------------------------------------------------------------
|
|
606
|
+
function handleMessage(msg) {
|
|
607
|
+
switch (msg.type) {
|
|
608
|
+
case "init": {
|
|
609
|
+
canvas = msg.canvas;
|
|
610
|
+
cols = msg.cols;
|
|
611
|
+
rows = msg.rows;
|
|
612
|
+
theme = msg.theme;
|
|
613
|
+
fontSize = msg.fontSize;
|
|
614
|
+
fontFamily = msg.fontFamily;
|
|
615
|
+
dpr = msg.devicePixelRatio;
|
|
616
|
+
buildPaletteFloat();
|
|
617
|
+
measureCellSize();
|
|
618
|
+
grid = createGridFromSAB(msg.sharedBuffer, cols, rows);
|
|
619
|
+
atlas = new GlyphAtlas(Math.round(fontSize * dpr), fontFamily);
|
|
620
|
+
gl = canvas.getContext("webgl2", {
|
|
621
|
+
alpha: false,
|
|
622
|
+
antialias: false,
|
|
623
|
+
premultipliedAlpha: false,
|
|
624
|
+
preserveDrawingBuffer: false,
|
|
625
|
+
});
|
|
626
|
+
if (!gl) {
|
|
627
|
+
const err = { type: "error", message: "WebGL2 not available in worker" };
|
|
628
|
+
self.postMessage(err);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
// Handle context loss on the OffscreenCanvas
|
|
632
|
+
canvas.addEventListener("webglcontextlost", (e) => {
|
|
633
|
+
e.preventDefault();
|
|
634
|
+
contextLost = true;
|
|
635
|
+
stopRenderLoop();
|
|
636
|
+
});
|
|
637
|
+
canvas.addEventListener("webglcontextrestored", () => {
|
|
638
|
+
contextLost = false;
|
|
639
|
+
initGLResources();
|
|
640
|
+
if (grid)
|
|
641
|
+
grid.markAllDirty();
|
|
642
|
+
startRenderLoop();
|
|
643
|
+
});
|
|
644
|
+
syncCanvasSize();
|
|
645
|
+
initGLResources();
|
|
646
|
+
ensureInstanceBuffers();
|
|
647
|
+
grid.markAllDirty();
|
|
648
|
+
startRenderLoop();
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
case "update": {
|
|
652
|
+
cursorRow = msg.cursor.row;
|
|
653
|
+
cursorCol = msg.cursor.col;
|
|
654
|
+
cursorVisible = msg.cursor.visible;
|
|
655
|
+
cursorStyle = msg.cursor.style;
|
|
656
|
+
selection = msg.selection;
|
|
657
|
+
// Also write cursor to SAB for consistency
|
|
658
|
+
if (grid) {
|
|
659
|
+
grid.setCursor(cursorRow, cursorCol, cursorVisible, cursorStyle);
|
|
660
|
+
}
|
|
661
|
+
// Mark all dirty so the next frame picks up changes
|
|
662
|
+
if (grid)
|
|
663
|
+
grid.markAllDirty();
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
case "resize": {
|
|
667
|
+
cols = msg.cols;
|
|
668
|
+
rows = msg.rows;
|
|
669
|
+
grid = createGridFromSAB(msg.sharedBuffer, cols, rows);
|
|
670
|
+
syncCanvasSize();
|
|
671
|
+
ensureInstanceBuffers();
|
|
672
|
+
grid.markAllDirty();
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
case "theme": {
|
|
676
|
+
theme = msg.theme;
|
|
677
|
+
buildPaletteFloat();
|
|
678
|
+
if (grid)
|
|
679
|
+
grid.markAllDirty();
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
case "font": {
|
|
683
|
+
fontSize = msg.fontSize;
|
|
684
|
+
fontFamily = msg.fontFamily;
|
|
685
|
+
measureCellSize();
|
|
686
|
+
if (gl && atlas) {
|
|
687
|
+
atlas.dispose(gl);
|
|
688
|
+
}
|
|
689
|
+
atlas = new GlyphAtlas(Math.round(fontSize * dpr), fontFamily);
|
|
690
|
+
if (gl) {
|
|
691
|
+
initGLResources();
|
|
692
|
+
}
|
|
693
|
+
syncCanvasSize();
|
|
694
|
+
ensureInstanceBuffers();
|
|
695
|
+
if (grid)
|
|
696
|
+
grid.markAllDirty();
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
case "dispose": {
|
|
700
|
+
disposeResources();
|
|
701
|
+
self.close();
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// ---------------------------------------------------------------------------
|
|
707
|
+
// Bootstrap
|
|
708
|
+
// ---------------------------------------------------------------------------
|
|
709
|
+
self.addEventListener("message", (event) => {
|
|
710
|
+
try {
|
|
711
|
+
handleMessage(event.data);
|
|
712
|
+
}
|
|
713
|
+
catch (e) {
|
|
714
|
+
self.postMessage({
|
|
715
|
+
type: "error",
|
|
716
|
+
message: e instanceof Error ? e.message : "Internal render error",
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
//# sourceMappingURL=render-worker.js.map
|