@hex1b/web-terminal 0.1.0 → 0.166.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/README.md +314 -11
- package/dist/backend-selection.d.ts +7 -0
- package/dist/backend-selection.d.ts.map +1 -0
- package/dist/backend-selection.js +23 -0
- package/dist/backend-selection.js.map +1 -0
- package/dist/hyperlinks.d.ts +10 -0
- package/dist/hyperlinks.d.ts.map +1 -0
- package/dist/hyperlinks.js +28 -0
- package/dist/hyperlinks.js.map +1 -0
- package/dist/mouse-input.d.ts +3 -0
- package/dist/mouse-input.d.ts.map +1 -1
- package/dist/mouse-input.js +69 -2
- package/dist/mouse-input.js.map +1 -1
- package/dist/protocol.d.ts +1 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +40 -0
- package/dist/protocol.js.map +1 -1
- package/dist/render-backend.d.ts +31 -0
- package/dist/render-backend.d.ts.map +1 -0
- package/dist/render-backend.js +5 -0
- package/dist/render-backend.js.map +1 -0
- package/dist/renderer-options.d.ts +3 -0
- package/dist/renderer-options.d.ts.map +1 -0
- package/dist/renderer-options.js +6 -0
- package/dist/renderer-options.js.map +1 -0
- package/dist/renderer.d.ts +12 -24
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +35 -173
- package/dist/renderer.js.map +1 -1
- package/dist/terminal-worker.js +18 -10
- package/dist/terminal-worker.js.map +1 -1
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/web-terminal.d.ts +8 -1
- package/dist/web-terminal.d.ts.map +1 -1
- package/dist/web-terminal.js +130 -17
- package/dist/web-terminal.js.map +1 -1
- package/dist/webgl2-backend.d.ts +38 -0
- package/dist/webgl2-backend.d.ts.map +1 -0
- package/dist/webgl2-backend.js +456 -0
- package/dist/webgl2-backend.js.map +1 -0
- package/dist/webgpu-backend.d.ts +24 -0
- package/dist/webgpu-backend.d.ts.map +1 -0
- package/dist/webgpu-backend.js +234 -0
- package/dist/webgpu-backend.js.map +1 -0
- package/dist/wire-types.d.ts +20 -2
- package/dist/wire-types.d.ts.map +1 -1
- package/dist/wire-types.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { QUAD_STRIDE, RendererUnavailableError } from "./render-backend.js";
|
|
2
|
+
const vertexShader = /* glsl */ `#version 300 es
|
|
3
|
+
precision highp float;
|
|
4
|
+
precision highp int;
|
|
5
|
+
|
|
6
|
+
layout(location = 0) in vec4 rect;
|
|
7
|
+
layout(location = 1) in vec4 uvRect;
|
|
8
|
+
layout(location = 2) in vec4 color;
|
|
9
|
+
layout(location = 3) in float mode;
|
|
10
|
+
uniform vec2 viewport;
|
|
11
|
+
out vec2 fragmentUv;
|
|
12
|
+
out vec4 fragmentTint;
|
|
13
|
+
flat out float fragmentMode;
|
|
14
|
+
|
|
15
|
+
void main() {
|
|
16
|
+
const vec2 corners[6] = vec2[6](
|
|
17
|
+
vec2(0, 0), vec2(1, 0), vec2(0, 1),
|
|
18
|
+
vec2(0, 1), vec2(1, 0), vec2(1, 1)
|
|
19
|
+
);
|
|
20
|
+
vec2 corner = corners[gl_VertexID];
|
|
21
|
+
vec2 position = rect.xy + corner * rect.zw;
|
|
22
|
+
gl_Position = vec4(position / viewport * vec2(2, -2) + vec2(-1, 1), 0, 1);
|
|
23
|
+
fragmentUv = mix(uvRect.xy, uvRect.zw, corner);
|
|
24
|
+
fragmentTint = color;
|
|
25
|
+
fragmentMode = mode;
|
|
26
|
+
}`;
|
|
27
|
+
const fragmentShader = /* glsl */ `#version 300 es
|
|
28
|
+
precision highp float;
|
|
29
|
+
precision highp int;
|
|
30
|
+
|
|
31
|
+
uniform highp sampler2D image;
|
|
32
|
+
in vec2 fragmentUv;
|
|
33
|
+
in vec4 fragmentTint;
|
|
34
|
+
flat in float fragmentMode;
|
|
35
|
+
out vec4 fragmentColor;
|
|
36
|
+
|
|
37
|
+
void main() {
|
|
38
|
+
vec4 texel = textureLod(image, fragmentUv, 0.0);
|
|
39
|
+
if (fragmentMode < 0.5) {
|
|
40
|
+
fragmentColor = fragmentTint;
|
|
41
|
+
} else if (fragmentMode < 1.5) {
|
|
42
|
+
fragmentColor = vec4(fragmentTint.rgb, fragmentTint.a * texel.a);
|
|
43
|
+
} else {
|
|
44
|
+
fragmentColor = texel * fragmentTint;
|
|
45
|
+
}
|
|
46
|
+
}`;
|
|
47
|
+
function errorFrom(value) {
|
|
48
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
49
|
+
}
|
|
50
|
+
function positiveInteger(value) {
|
|
51
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
52
|
+
}
|
|
53
|
+
function prepareUpload(gl) {
|
|
54
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
55
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
56
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
57
|
+
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
|
|
58
|
+
gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0);
|
|
59
|
+
gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, 0);
|
|
60
|
+
gl.pixelStorei(gl.UNPACK_SKIP_ROWS, 0);
|
|
61
|
+
}
|
|
62
|
+
class WebGl2Texture {
|
|
63
|
+
owner;
|
|
64
|
+
gl;
|
|
65
|
+
texture;
|
|
66
|
+
width;
|
|
67
|
+
height;
|
|
68
|
+
assertActive;
|
|
69
|
+
release;
|
|
70
|
+
destroyed = false;
|
|
71
|
+
constructor(owner, gl, texture, width, height, assertActive, release) {
|
|
72
|
+
this.owner = owner;
|
|
73
|
+
this.gl = gl;
|
|
74
|
+
this.texture = texture;
|
|
75
|
+
this.width = width;
|
|
76
|
+
this.height = height;
|
|
77
|
+
this.assertActive = assertActive;
|
|
78
|
+
this.release = release;
|
|
79
|
+
}
|
|
80
|
+
bind() {
|
|
81
|
+
this.assertActive();
|
|
82
|
+
if (this.destroyed)
|
|
83
|
+
throw new Error("WebGL2 texture is destroyed");
|
|
84
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
|
|
85
|
+
prepareUpload(this.gl);
|
|
86
|
+
}
|
|
87
|
+
writePixels(pixels, width, height, x = 0, y = 0) {
|
|
88
|
+
if (!positiveInteger(width) || !positiveInteger(height) ||
|
|
89
|
+
!Number.isSafeInteger(x) || !Number.isSafeInteger(y) || x < 0 || y < 0 ||
|
|
90
|
+
x + width > this.width || y + height > this.height || pixels.byteLength < width * height * 4) {
|
|
91
|
+
throw new Error("Invalid WebGL2 texture upload dimensions or pixel data");
|
|
92
|
+
}
|
|
93
|
+
this.bind();
|
|
94
|
+
this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, x, y, width, height, this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixels);
|
|
95
|
+
}
|
|
96
|
+
writeBitmap(bitmap) {
|
|
97
|
+
if (bitmap.width !== this.width || bitmap.height !== this.height) {
|
|
98
|
+
throw new Error("WebGL2 bitmap dimensions do not match the texture");
|
|
99
|
+
}
|
|
100
|
+
this.bind();
|
|
101
|
+
// ImageBitmap ignores unpack conversion flags; the shared decoder supplies straight-alpha,
|
|
102
|
+
// unconverted, top-down bitmaps. Raw RGBA uploads use the explicit unpack state above.
|
|
103
|
+
this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, 0, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, bitmap);
|
|
104
|
+
}
|
|
105
|
+
destroy() {
|
|
106
|
+
if (this.destroyed)
|
|
107
|
+
return;
|
|
108
|
+
this.destroyed = true;
|
|
109
|
+
this.release(this);
|
|
110
|
+
this.gl.deleteTexture(this.texture);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export class WebGl2Backend {
|
|
114
|
+
canvas;
|
|
115
|
+
gl;
|
|
116
|
+
onFatal;
|
|
117
|
+
kind = "webgl2";
|
|
118
|
+
textureLimit = 0;
|
|
119
|
+
canvasLimit = 0;
|
|
120
|
+
bufferBytes = 0;
|
|
121
|
+
disposed = false;
|
|
122
|
+
lostError;
|
|
123
|
+
program;
|
|
124
|
+
instanceBuffer;
|
|
125
|
+
vertexArray;
|
|
126
|
+
viewport;
|
|
127
|
+
shaders = new Set();
|
|
128
|
+
textures = new Set();
|
|
129
|
+
fences = new Set();
|
|
130
|
+
onContextLost = (event) => {
|
|
131
|
+
const message = event.statusMessage;
|
|
132
|
+
this.contextLost(new Error(`WebGL2 context lost${message ? `: ${message}` : ""}`));
|
|
133
|
+
};
|
|
134
|
+
static async create(canvas, onFatal) {
|
|
135
|
+
const gl = canvas.getContext("webgl2", {
|
|
136
|
+
alpha: false,
|
|
137
|
+
antialias: false,
|
|
138
|
+
depth: false,
|
|
139
|
+
stencil: false,
|
|
140
|
+
premultipliedAlpha: false,
|
|
141
|
+
preserveDrawingBuffer: false,
|
|
142
|
+
});
|
|
143
|
+
if (!gl)
|
|
144
|
+
throw new RendererUnavailableError("Could not create an OffscreenCanvas WebGL2 context");
|
|
145
|
+
const backend = new WebGl2Backend(canvas, gl, onFatal);
|
|
146
|
+
try {
|
|
147
|
+
backend.initialize();
|
|
148
|
+
return backend;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
backend.dispose();
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
constructor(canvas, gl, onFatal) {
|
|
156
|
+
this.canvas = canvas;
|
|
157
|
+
this.gl = gl;
|
|
158
|
+
this.onFatal = onFatal;
|
|
159
|
+
}
|
|
160
|
+
get maxTextureDimension2D() { return this.textureLimit; }
|
|
161
|
+
get maxCanvasDimension2D() { return this.canvasLimit; }
|
|
162
|
+
get instanceBufferBytes() { return this.bufferBytes; }
|
|
163
|
+
initialize() {
|
|
164
|
+
const gl = this.gl;
|
|
165
|
+
this.canvas.addEventListener("webglcontextlost", this.onContextLost);
|
|
166
|
+
this.checkErrors("initialization");
|
|
167
|
+
this.textureLimit = gl.getParameter(gl.MAX_TEXTURE_SIZE);
|
|
168
|
+
const viewportLimit = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
|
|
169
|
+
this.canvasLimit = Math.min(this.textureLimit, gl.getParameter(gl.MAX_RENDERBUFFER_SIZE), viewportLimit[0], viewportLimit[1]);
|
|
170
|
+
if (!positiveInteger(this.textureLimit) || !positiveInteger(this.canvasLimit)) {
|
|
171
|
+
throw new Error("Invalid WebGL2 texture or viewport limits");
|
|
172
|
+
}
|
|
173
|
+
const vertex = this.compile(gl.VERTEX_SHADER, vertexShader);
|
|
174
|
+
const fragment = this.compile(gl.FRAGMENT_SHADER, fragmentShader);
|
|
175
|
+
const program = gl.createProgram();
|
|
176
|
+
if (!program)
|
|
177
|
+
throw new Error("Could not allocate a WebGL2 shader program");
|
|
178
|
+
this.program = program;
|
|
179
|
+
gl.attachShader(program, vertex);
|
|
180
|
+
gl.attachShader(program, fragment);
|
|
181
|
+
gl.linkProgram(program);
|
|
182
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
183
|
+
throw new Error(`WebGL2 shader link failed: ${gl.getProgramInfoLog(program) || "No diagnostic available"}`);
|
|
184
|
+
}
|
|
185
|
+
for (const shader of this.shaders) {
|
|
186
|
+
gl.detachShader(program, shader);
|
|
187
|
+
gl.deleteShader(shader);
|
|
188
|
+
}
|
|
189
|
+
this.shaders.clear();
|
|
190
|
+
const viewport = gl.getUniformLocation(program, "viewport");
|
|
191
|
+
const image = gl.getUniformLocation(program, "image");
|
|
192
|
+
if (viewport === null || image === null)
|
|
193
|
+
throw new Error("WebGL2 shader uniforms are unavailable");
|
|
194
|
+
this.viewport = viewport;
|
|
195
|
+
const buffer = gl.createBuffer();
|
|
196
|
+
if (!buffer)
|
|
197
|
+
throw new Error("Could not allocate a WebGL2 instance buffer");
|
|
198
|
+
this.instanceBuffer = buffer;
|
|
199
|
+
const vertexArray = gl.createVertexArray();
|
|
200
|
+
if (!vertexArray)
|
|
201
|
+
throw new Error("Could not allocate a WebGL2 vertex array");
|
|
202
|
+
this.vertexArray = vertexArray;
|
|
203
|
+
gl.useProgram(program);
|
|
204
|
+
gl.uniform1i(image, 0);
|
|
205
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
206
|
+
gl.bindVertexArray(vertexArray);
|
|
207
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
208
|
+
for (let attribute = 0; attribute < 4; attribute++) {
|
|
209
|
+
gl.enableVertexAttribArray(attribute);
|
|
210
|
+
gl.vertexAttribDivisor(attribute, 1);
|
|
211
|
+
}
|
|
212
|
+
gl.enable(gl.BLEND);
|
|
213
|
+
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
|
|
214
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
215
|
+
gl.disable(gl.DEPTH_TEST);
|
|
216
|
+
gl.disable(gl.STENCIL_TEST);
|
|
217
|
+
gl.disable(gl.CULL_FACE);
|
|
218
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
219
|
+
gl.disable(gl.DITHER);
|
|
220
|
+
this.resize(this.canvas.width, this.canvas.height);
|
|
221
|
+
}
|
|
222
|
+
compile(type, source) {
|
|
223
|
+
const gl = this.gl;
|
|
224
|
+
const shader = gl.createShader(type);
|
|
225
|
+
if (!shader)
|
|
226
|
+
throw new Error("Could not allocate a WebGL2 shader");
|
|
227
|
+
this.shaders.add(shader);
|
|
228
|
+
gl.shaderSource(shader, source);
|
|
229
|
+
gl.compileShader(shader);
|
|
230
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
231
|
+
throw new Error(`WebGL2 shader compilation failed: ${gl.getShaderInfoLog(shader) || "No diagnostic available"}`);
|
|
232
|
+
}
|
|
233
|
+
return shader;
|
|
234
|
+
}
|
|
235
|
+
assertActive() {
|
|
236
|
+
if (this.disposed)
|
|
237
|
+
throw new Error("WebGL2 backend is disposed");
|
|
238
|
+
if (this.lostError)
|
|
239
|
+
throw this.lostError;
|
|
240
|
+
}
|
|
241
|
+
contextLost(error) {
|
|
242
|
+
if (this.disposed || this.lostError)
|
|
243
|
+
return;
|
|
244
|
+
this.lostError = error;
|
|
245
|
+
for (const fence of this.fences)
|
|
246
|
+
this.settleFence(fence, error);
|
|
247
|
+
this.onFatal(error);
|
|
248
|
+
}
|
|
249
|
+
checkErrors(operation) {
|
|
250
|
+
this.assertActive();
|
|
251
|
+
const gl = this.gl;
|
|
252
|
+
const code = gl.getError();
|
|
253
|
+
if (code === gl.CONTEXT_LOST_WEBGL || gl.isContextLost()) {
|
|
254
|
+
const error = new Error("WebGL2 context lost");
|
|
255
|
+
this.contextLost(error);
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
if (code !== gl.NO_ERROR) {
|
|
259
|
+
const name = code === gl.OUT_OF_MEMORY ? "OUT_OF_MEMORY" :
|
|
260
|
+
code === gl.INVALID_ENUM ? "INVALID_ENUM" :
|
|
261
|
+
code === gl.INVALID_VALUE ? "INVALID_VALUE" :
|
|
262
|
+
code === gl.INVALID_OPERATION ? "INVALID_OPERATION" :
|
|
263
|
+
code === gl.INVALID_FRAMEBUFFER_OPERATION ? "INVALID_FRAMEBUFFER_OPERATION" : `0x${code.toString(16)}`;
|
|
264
|
+
throw new Error(`WebGL2 ${operation} failed: ${name}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
createTexture(width, height, label) {
|
|
268
|
+
this.assertActive();
|
|
269
|
+
if (!positiveInteger(width) || !positiveInteger(height) ||
|
|
270
|
+
width > this.textureLimit || height > this.textureLimit) {
|
|
271
|
+
throw new Error(`${label} has invalid dimensions or exceeds the WebGL2 texture dimension limit`);
|
|
272
|
+
}
|
|
273
|
+
const gl = this.gl;
|
|
274
|
+
const texture = gl.createTexture();
|
|
275
|
+
if (!texture) {
|
|
276
|
+
this.checkErrors("texture allocation");
|
|
277
|
+
throw new Error(`Could not allocate WebGL2 texture: ${label}`);
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
281
|
+
prepareUpload(gl);
|
|
282
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
283
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
284
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
285
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
286
|
+
// WebGL guarantees zero initialization for null data, including untouched atlas padding.
|
|
287
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
288
|
+
this.checkErrors(`texture allocation (${label})`);
|
|
289
|
+
const resource = new WebGl2Texture(this, gl, texture, width, height, () => this.assertActive(), resource => this.textures.delete(resource));
|
|
290
|
+
this.textures.add(resource);
|
|
291
|
+
return resource;
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
gl.deleteTexture(texture);
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
resize(width, height) {
|
|
299
|
+
this.assertActive();
|
|
300
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
301
|
+
throw new Error("Invalid WebGL2 logical viewport dimensions");
|
|
302
|
+
}
|
|
303
|
+
if (!positiveInteger(this.canvas.width) || !positiveInteger(this.canvas.height) ||
|
|
304
|
+
this.canvas.width > this.canvasLimit || this.canvas.height > this.canvasLimit) {
|
|
305
|
+
throw new Error("Canvas exceeds the WebGL2 drawing buffer dimension limit");
|
|
306
|
+
}
|
|
307
|
+
const gl = this.gl;
|
|
308
|
+
if (gl.drawingBufferWidth !== this.canvas.width || gl.drawingBufferHeight !== this.canvas.height) {
|
|
309
|
+
this.checkErrors("drawing buffer resize");
|
|
310
|
+
throw new Error("WebGL2 could not allocate the requested canvas drawing buffer");
|
|
311
|
+
}
|
|
312
|
+
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
313
|
+
gl.useProgram(this.program);
|
|
314
|
+
gl.uniform2f(this.viewport, width, height);
|
|
315
|
+
this.checkErrors("resize");
|
|
316
|
+
}
|
|
317
|
+
submit(instances, quadCount, batches, background) {
|
|
318
|
+
this.assertActive();
|
|
319
|
+
if (!Number.isSafeInteger(quadCount) || quadCount < 0 || quadCount * QUAD_STRIDE > instances.length) {
|
|
320
|
+
throw new Error("Invalid WebGL2 instance data");
|
|
321
|
+
}
|
|
322
|
+
for (const batch of batches) {
|
|
323
|
+
if (!(batch.resource instanceof WebGl2Texture) || batch.resource.owner !== this) {
|
|
324
|
+
throw new Error("Texture belongs to a different rendering backend");
|
|
325
|
+
}
|
|
326
|
+
if (batch.resource.destroyed)
|
|
327
|
+
throw new Error("WebGL2 texture is destroyed");
|
|
328
|
+
if (!Number.isSafeInteger(batch.start) || !Number.isSafeInteger(batch.count) ||
|
|
329
|
+
batch.start < 0 || batch.count < 0 || batch.start + batch.count > quadCount) {
|
|
330
|
+
throw new Error("Invalid WebGL2 batch instance range");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const gl = this.gl;
|
|
334
|
+
const strideBytes = QUAD_STRIDE * Float32Array.BYTES_PER_ELEMENT;
|
|
335
|
+
const usedBytes = quadCount * strideBytes;
|
|
336
|
+
gl.bindVertexArray(this.vertexArray);
|
|
337
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuffer);
|
|
338
|
+
if (!this.bufferBytes || usedBytes > this.bufferBytes) {
|
|
339
|
+
const bytes = Math.max(256, instances.byteLength);
|
|
340
|
+
gl.bufferData(gl.ARRAY_BUFFER, bytes, gl.DYNAMIC_DRAW);
|
|
341
|
+
this.checkErrors("instance buffer allocation");
|
|
342
|
+
this.bufferBytes = bytes;
|
|
343
|
+
}
|
|
344
|
+
if (usedBytes)
|
|
345
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, instances, 0, quadCount * QUAD_STRIDE);
|
|
346
|
+
gl.useProgram(this.program);
|
|
347
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
348
|
+
gl.clearColor(background[0], background[1], background[2], 1);
|
|
349
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
350
|
+
for (const batch of batches) {
|
|
351
|
+
if (!batch.count)
|
|
352
|
+
continue;
|
|
353
|
+
const resource = batch.resource;
|
|
354
|
+
gl.bindTexture(gl.TEXTURE_2D, resource.texture);
|
|
355
|
+
// WebGL2 has no baseInstance; rebase all per-instance attributes for each ordered batch.
|
|
356
|
+
const offset = batch.start * strideBytes;
|
|
357
|
+
gl.vertexAttribPointer(0, 4, gl.FLOAT, false, strideBytes, offset);
|
|
358
|
+
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, strideBytes, offset + 16);
|
|
359
|
+
gl.vertexAttribPointer(2, 4, gl.FLOAT, false, strideBytes, offset + 32);
|
|
360
|
+
gl.vertexAttribPointer(3, 1, gl.FLOAT, false, strideBytes, offset + 48);
|
|
361
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, batch.count);
|
|
362
|
+
}
|
|
363
|
+
// Upload errors are checked once per submission, not once per glyph.
|
|
364
|
+
this.checkErrors("frame submission");
|
|
365
|
+
}
|
|
366
|
+
async idle() {
|
|
367
|
+
this.checkErrors("GPU completion");
|
|
368
|
+
const gl = this.gl;
|
|
369
|
+
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
370
|
+
if (!sync) {
|
|
371
|
+
this.checkErrors("GPU fence allocation");
|
|
372
|
+
throw new Error("Could not allocate a WebGL2 GPU fence");
|
|
373
|
+
}
|
|
374
|
+
return new Promise((resolve, reject) => {
|
|
375
|
+
const fence = { sync, resolve, reject };
|
|
376
|
+
this.fences.add(fence);
|
|
377
|
+
try {
|
|
378
|
+
gl.flush();
|
|
379
|
+
this.checkErrors("GPU fence submission");
|
|
380
|
+
this.pollFence(fence);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
this.settleFence(fence, errorFrom(error));
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
pollFence(fence) {
|
|
388
|
+
if (!this.fences.has(fence))
|
|
389
|
+
return;
|
|
390
|
+
try {
|
|
391
|
+
this.assertActive();
|
|
392
|
+
const gl = this.gl;
|
|
393
|
+
if (gl.isContextLost()) {
|
|
394
|
+
const error = new Error("WebGL2 context lost");
|
|
395
|
+
this.contextLost(error);
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
const status = gl.clientWaitSync(fence.sync, 0, 0);
|
|
399
|
+
if (status === gl.ALREADY_SIGNALED || status === gl.CONDITION_SATISFIED) {
|
|
400
|
+
this.checkErrors("GPU completion");
|
|
401
|
+
this.settleFence(fence);
|
|
402
|
+
}
|
|
403
|
+
else if (status === gl.TIMEOUT_EXPIRED) {
|
|
404
|
+
fence.timer = setTimeout(() => {
|
|
405
|
+
fence.timer = undefined;
|
|
406
|
+
this.pollFence(fence);
|
|
407
|
+
}, 0);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
this.checkErrors("GPU fence wait");
|
|
411
|
+
throw new Error("WebGL2 GPU fence wait failed");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
this.settleFence(fence, errorFrom(error));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
settleFence(fence, error) {
|
|
419
|
+
if (!this.fences.delete(fence))
|
|
420
|
+
return;
|
|
421
|
+
if (fence.timer !== undefined)
|
|
422
|
+
clearTimeout(fence.timer);
|
|
423
|
+
this.gl.deleteSync(fence.sync);
|
|
424
|
+
if (error)
|
|
425
|
+
fence.reject(error);
|
|
426
|
+
else
|
|
427
|
+
fence.resolve();
|
|
428
|
+
}
|
|
429
|
+
dispose() {
|
|
430
|
+
if (this.disposed)
|
|
431
|
+
return;
|
|
432
|
+
this.disposed = true;
|
|
433
|
+
this.canvas.removeEventListener("webglcontextlost", this.onContextLost);
|
|
434
|
+
for (const fence of this.fences)
|
|
435
|
+
this.settleFence(fence, new Error("WebGL2 backend is disposed"));
|
|
436
|
+
for (const texture of this.textures)
|
|
437
|
+
texture.destroy();
|
|
438
|
+
const gl = this.gl;
|
|
439
|
+
if (this.instanceBuffer)
|
|
440
|
+
gl.deleteBuffer(this.instanceBuffer);
|
|
441
|
+
if (this.vertexArray)
|
|
442
|
+
gl.deleteVertexArray(this.vertexArray);
|
|
443
|
+
if (this.program)
|
|
444
|
+
gl.deleteProgram(this.program);
|
|
445
|
+
for (const shader of this.shaders)
|
|
446
|
+
gl.deleteShader(shader);
|
|
447
|
+
this.shaders.clear();
|
|
448
|
+
this.instanceBuffer = undefined;
|
|
449
|
+
this.vertexArray = undefined;
|
|
450
|
+
this.program = undefined;
|
|
451
|
+
this.bufferBytes = 0;
|
|
452
|
+
if (!gl.isContextLost())
|
|
453
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
//# sourceMappingURL=webgl2-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webgl2-backend.js","sourceRoot":"","sources":["../src/webgl2-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { QUAD_STRIDE, RendererUnavailableError } from \"./render-backend.js\";\nimport type { RenderBackend, RenderBatch, RenderColor, RenderPixels, RenderTexture } from \"./render-backend.js\";\n\nconst vertexShader = /* glsl */ `#version 300 es\nprecision highp float;\nprecision highp int;\n\nlayout(location = 0) in vec4 rect;\nlayout(location = 1) in vec4 uvRect;\nlayout(location = 2) in vec4 color;\nlayout(location = 3) in float mode;\nuniform vec2 viewport;\nout vec2 fragmentUv;\nout vec4 fragmentTint;\nflat out float fragmentMode;\n\nvoid main() {\n const vec2 corners[6] = vec2[6](\n vec2(0, 0), vec2(1, 0), vec2(0, 1),\n vec2(0, 1), vec2(1, 0), vec2(1, 1)\n );\n vec2 corner = corners[gl_VertexID];\n vec2 position = rect.xy + corner * rect.zw;\n gl_Position = vec4(position / viewport * vec2(2, -2) + vec2(-1, 1), 0, 1);\n fragmentUv = mix(uvRect.xy, uvRect.zw, corner);\n fragmentTint = color;\n fragmentMode = mode;\n}`;\n\nconst fragmentShader = /* glsl */ `#version 300 es\nprecision highp float;\nprecision highp int;\n\nuniform highp sampler2D image;\nin vec2 fragmentUv;\nin vec4 fragmentTint;\nflat in float fragmentMode;\nout vec4 fragmentColor;\n\nvoid main() {\n vec4 texel = textureLod(image, fragmentUv, 0.0);\n if (fragmentMode < 0.5) {\n fragmentColor = fragmentTint;\n } else if (fragmentMode < 1.5) {\n fragmentColor = vec4(fragmentTint.rgb, fragmentTint.a * texel.a);\n } else {\n fragmentColor = texel * fragmentTint;\n }\n}`;\n\nfunction errorFrom(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction positiveInteger(value: number): boolean {\n return Number.isSafeInteger(value) && value > 0;\n}\n\nfunction prepareUpload(gl: WebGL2RenderingContext): void {\n gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);\n gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0);\n gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, 0);\n gl.pixelStorei(gl.UNPACK_SKIP_ROWS, 0);\n}\n\nclass WebGl2Texture implements RenderTexture {\n destroyed = false;\n\n constructor(readonly owner: WebGl2Backend, private readonly gl: WebGL2RenderingContext,\n readonly texture: WebGLTexture, readonly width: number, readonly height: number,\n private readonly assertActive: () => void, private readonly release: (texture: WebGl2Texture) => void) {}\n\n private bind(): void {\n this.assertActive();\n if (this.destroyed) throw new Error(\"WebGL2 texture is destroyed\");\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n prepareUpload(this.gl);\n }\n\n writePixels(pixels: RenderPixels, width: number, height: number, x = 0, y = 0): void {\n if (!positiveInteger(width) || !positiveInteger(height) ||\n !Number.isSafeInteger(x) || !Number.isSafeInteger(y) || x < 0 || y < 0 ||\n x + width > this.width || y + height > this.height || pixels.byteLength < width * height * 4) {\n throw new Error(\"Invalid WebGL2 texture upload dimensions or pixel data\");\n }\n this.bind();\n this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, x, y, width, height,\n this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixels);\n }\n\n writeBitmap(bitmap: ImageBitmap): void {\n if (bitmap.width !== this.width || bitmap.height !== this.height) {\n throw new Error(\"WebGL2 bitmap dimensions do not match the texture\");\n }\n this.bind();\n // ImageBitmap ignores unpack conversion flags; the shared decoder supplies straight-alpha,\n // unconverted, top-down bitmaps. Raw RGBA uploads use the explicit unpack state above.\n this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, 0, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, bitmap);\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.release(this);\n this.gl.deleteTexture(this.texture);\n }\n}\n\ninterface PendingFence {\n sync: WebGLSync;\n timer?: ReturnType<typeof setTimeout>;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\nexport class WebGl2Backend implements RenderBackend {\n readonly kind = \"webgl2\";\n private textureLimit = 0;\n private canvasLimit = 0;\n private bufferBytes = 0;\n private disposed = false;\n private lostError?: Error;\n private program?: WebGLProgram;\n private instanceBuffer?: WebGLBuffer;\n private vertexArray?: WebGLVertexArrayObject;\n private viewport?: WebGLUniformLocation;\n private readonly shaders = new Set<WebGLShader>();\n private readonly textures = new Set<WebGl2Texture>();\n private readonly fences = new Set<PendingFence>();\n private readonly onContextLost = (event: Event): void => {\n const message = (event as WebGLContextEvent).statusMessage;\n this.contextLost(new Error(`WebGL2 context lost${message ? `: ${message}` : \"\"}`));\n };\n\n static async create(canvas: OffscreenCanvas, onFatal: (error: Error) => void): Promise<WebGl2Backend> {\n const gl = canvas.getContext(\"webgl2\", {\n alpha: false,\n antialias: false,\n depth: false,\n stencil: false,\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n });\n if (!gl) throw new RendererUnavailableError(\"Could not create an OffscreenCanvas WebGL2 context\");\n const backend = new WebGl2Backend(canvas, gl, onFatal);\n try {\n backend.initialize();\n return backend;\n } catch (error) {\n backend.dispose();\n throw error;\n }\n }\n\n private constructor(private readonly canvas: OffscreenCanvas, readonly gl: WebGL2RenderingContext,\n private readonly onFatal: (error: Error) => void) {}\n\n get maxTextureDimension2D(): number { return this.textureLimit; }\n get maxCanvasDimension2D(): number { return this.canvasLimit; }\n get instanceBufferBytes(): number { return this.bufferBytes; }\n\n private initialize(): void {\n const gl = this.gl;\n this.canvas.addEventListener(\"webglcontextlost\", this.onContextLost);\n this.checkErrors(\"initialization\");\n this.textureLimit = gl.getParameter(gl.MAX_TEXTURE_SIZE);\n const viewportLimit = gl.getParameter(gl.MAX_VIEWPORT_DIMS) as Int32Array;\n this.canvasLimit = Math.min(this.textureLimit, gl.getParameter(gl.MAX_RENDERBUFFER_SIZE),\n viewportLimit[0], viewportLimit[1]);\n if (!positiveInteger(this.textureLimit) || !positiveInteger(this.canvasLimit)) {\n throw new Error(\"Invalid WebGL2 texture or viewport limits\");\n }\n\n const vertex = this.compile(gl.VERTEX_SHADER, vertexShader);\n const fragment = this.compile(gl.FRAGMENT_SHADER, fragmentShader);\n const program = gl.createProgram();\n if (!program) throw new Error(\"Could not allocate a WebGL2 shader program\");\n this.program = program;\n gl.attachShader(program, vertex);\n gl.attachShader(program, fragment);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(`WebGL2 shader link failed: ${gl.getProgramInfoLog(program) || \"No diagnostic available\"}`);\n }\n for (const shader of this.shaders) {\n gl.detachShader(program, shader);\n gl.deleteShader(shader);\n }\n this.shaders.clear();\n const viewport = gl.getUniformLocation(program, \"viewport\");\n const image = gl.getUniformLocation(program, \"image\");\n if (viewport === null || image === null) throw new Error(\"WebGL2 shader uniforms are unavailable\");\n this.viewport = viewport;\n const buffer = gl.createBuffer();\n if (!buffer) throw new Error(\"Could not allocate a WebGL2 instance buffer\");\n this.instanceBuffer = buffer;\n const vertexArray = gl.createVertexArray();\n if (!vertexArray) throw new Error(\"Could not allocate a WebGL2 vertex array\");\n this.vertexArray = vertexArray;\n\n gl.useProgram(program);\n gl.uniform1i(image, 0);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindVertexArray(vertexArray);\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n for (let attribute = 0; attribute < 4; attribute++) {\n gl.enableVertexAttribArray(attribute);\n gl.vertexAttribDivisor(attribute, 1);\n }\n gl.enable(gl.BLEND);\n gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);\n gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.disable(gl.DEPTH_TEST);\n gl.disable(gl.STENCIL_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.SCISSOR_TEST);\n gl.disable(gl.DITHER);\n this.resize(this.canvas.width, this.canvas.height);\n }\n\n private compile(type: number, source: string): WebGLShader {\n const gl = this.gl;\n const shader = gl.createShader(type);\n if (!shader) throw new Error(\"Could not allocate a WebGL2 shader\");\n this.shaders.add(shader);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n throw new Error(`WebGL2 shader compilation failed: ${gl.getShaderInfoLog(shader) || \"No diagnostic available\"}`);\n }\n return shader;\n }\n\n private assertActive(): void {\n if (this.disposed) throw new Error(\"WebGL2 backend is disposed\");\n if (this.lostError) throw this.lostError;\n }\n\n private contextLost(error: Error): void {\n if (this.disposed || this.lostError) return;\n this.lostError = error;\n for (const fence of this.fences) this.settleFence(fence, error);\n this.onFatal(error);\n }\n\n private checkErrors(operation: string): void {\n this.assertActive();\n const gl = this.gl;\n const code = gl.getError();\n if (code === gl.CONTEXT_LOST_WEBGL || gl.isContextLost()) {\n const error = new Error(\"WebGL2 context lost\");\n this.contextLost(error);\n throw error;\n }\n if (code !== gl.NO_ERROR) {\n const name = code === gl.OUT_OF_MEMORY ? \"OUT_OF_MEMORY\" :\n code === gl.INVALID_ENUM ? \"INVALID_ENUM\" :\n code === gl.INVALID_VALUE ? \"INVALID_VALUE\" :\n code === gl.INVALID_OPERATION ? \"INVALID_OPERATION\" :\n code === gl.INVALID_FRAMEBUFFER_OPERATION ? \"INVALID_FRAMEBUFFER_OPERATION\" : `0x${code.toString(16)}`;\n throw new Error(`WebGL2 ${operation} failed: ${name}`);\n }\n }\n\n createTexture(width: number, height: number, label: string): RenderTexture {\n this.assertActive();\n if (!positiveInteger(width) || !positiveInteger(height) ||\n width > this.textureLimit || height > this.textureLimit) {\n throw new Error(`${label} has invalid dimensions or exceeds the WebGL2 texture dimension limit`);\n }\n const gl = this.gl;\n const texture = gl.createTexture();\n if (!texture) {\n this.checkErrors(\"texture allocation\");\n throw new Error(`Could not allocate WebGL2 texture: ${label}`);\n }\n try {\n gl.bindTexture(gl.TEXTURE_2D, texture);\n prepareUpload(gl);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n // WebGL guarantees zero initialization for null data, including untouched atlas padding.\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n this.checkErrors(`texture allocation (${label})`);\n const resource = new WebGl2Texture(this, gl, texture, width, height,\n () => this.assertActive(), resource => this.textures.delete(resource));\n this.textures.add(resource);\n return resource;\n } catch (error) {\n gl.deleteTexture(texture);\n throw error;\n }\n }\n\n resize(width: number, height: number): void {\n this.assertActive();\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {\n throw new Error(\"Invalid WebGL2 logical viewport dimensions\");\n }\n if (!positiveInteger(this.canvas.width) || !positiveInteger(this.canvas.height) ||\n this.canvas.width > this.canvasLimit || this.canvas.height > this.canvasLimit) {\n throw new Error(\"Canvas exceeds the WebGL2 drawing buffer dimension limit\");\n }\n const gl = this.gl;\n if (gl.drawingBufferWidth !== this.canvas.width || gl.drawingBufferHeight !== this.canvas.height) {\n this.checkErrors(\"drawing buffer resize\");\n throw new Error(\"WebGL2 could not allocate the requested canvas drawing buffer\");\n }\n gl.viewport(0, 0, this.canvas.width, this.canvas.height);\n gl.useProgram(this.program!);\n gl.uniform2f(this.viewport!, width, height);\n this.checkErrors(\"resize\");\n }\n\n submit(instances: Float32Array<ArrayBuffer>, quadCount: number, batches: readonly RenderBatch[],\n background: RenderColor): void {\n this.assertActive();\n if (!Number.isSafeInteger(quadCount) || quadCount < 0 || quadCount * QUAD_STRIDE > instances.length) {\n throw new Error(\"Invalid WebGL2 instance data\");\n }\n for (const batch of batches) {\n if (!(batch.resource instanceof WebGl2Texture) || batch.resource.owner !== this) {\n throw new Error(\"Texture belongs to a different rendering backend\");\n }\n if (batch.resource.destroyed) throw new Error(\"WebGL2 texture is destroyed\");\n if (!Number.isSafeInteger(batch.start) || !Number.isSafeInteger(batch.count) ||\n batch.start < 0 || batch.count < 0 || batch.start + batch.count > quadCount) {\n throw new Error(\"Invalid WebGL2 batch instance range\");\n }\n }\n const gl = this.gl;\n const strideBytes = QUAD_STRIDE * Float32Array.BYTES_PER_ELEMENT;\n const usedBytes = quadCount * strideBytes;\n gl.bindVertexArray(this.vertexArray!);\n gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuffer!);\n if (!this.bufferBytes || usedBytes > this.bufferBytes) {\n const bytes = Math.max(256, instances.byteLength);\n gl.bufferData(gl.ARRAY_BUFFER, bytes, gl.DYNAMIC_DRAW);\n this.checkErrors(\"instance buffer allocation\");\n this.bufferBytes = bytes;\n }\n if (usedBytes) gl.bufferSubData(gl.ARRAY_BUFFER, 0, instances, 0, quadCount * QUAD_STRIDE);\n gl.useProgram(this.program!);\n gl.activeTexture(gl.TEXTURE0);\n gl.clearColor(background[0], background[1], background[2], 1);\n gl.clear(gl.COLOR_BUFFER_BIT);\n for (const batch of batches) {\n if (!batch.count) continue;\n const resource = batch.resource as WebGl2Texture;\n gl.bindTexture(gl.TEXTURE_2D, resource.texture);\n // WebGL2 has no baseInstance; rebase all per-instance attributes for each ordered batch.\n const offset = batch.start * strideBytes;\n gl.vertexAttribPointer(0, 4, gl.FLOAT, false, strideBytes, offset);\n gl.vertexAttribPointer(1, 4, gl.FLOAT, false, strideBytes, offset + 16);\n gl.vertexAttribPointer(2, 4, gl.FLOAT, false, strideBytes, offset + 32);\n gl.vertexAttribPointer(3, 1, gl.FLOAT, false, strideBytes, offset + 48);\n gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, batch.count);\n }\n // Upload errors are checked once per submission, not once per glyph.\n this.checkErrors(\"frame submission\");\n }\n\n async idle(): Promise<void> {\n this.checkErrors(\"GPU completion\");\n const gl = this.gl;\n const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);\n if (!sync) {\n this.checkErrors(\"GPU fence allocation\");\n throw new Error(\"Could not allocate a WebGL2 GPU fence\");\n }\n return new Promise<void>((resolve, reject) => {\n const fence: PendingFence = { sync, resolve, reject };\n this.fences.add(fence);\n try {\n gl.flush();\n this.checkErrors(\"GPU fence submission\");\n this.pollFence(fence);\n } catch (error) {\n this.settleFence(fence, errorFrom(error));\n }\n });\n }\n\n private pollFence(fence: PendingFence): void {\n if (!this.fences.has(fence)) return;\n try {\n this.assertActive();\n const gl = this.gl;\n if (gl.isContextLost()) {\n const error = new Error(\"WebGL2 context lost\");\n this.contextLost(error);\n throw error;\n }\n const status = gl.clientWaitSync(fence.sync, 0, 0);\n if (status === gl.ALREADY_SIGNALED || status === gl.CONDITION_SATISFIED) {\n this.checkErrors(\"GPU completion\");\n this.settleFence(fence);\n } else if (status === gl.TIMEOUT_EXPIRED) {\n fence.timer = setTimeout(() => {\n fence.timer = undefined;\n this.pollFence(fence);\n }, 0);\n } else {\n this.checkErrors(\"GPU fence wait\");\n throw new Error(\"WebGL2 GPU fence wait failed\");\n }\n } catch (error) {\n this.settleFence(fence, errorFrom(error));\n }\n }\n\n private settleFence(fence: PendingFence, error?: Error): void {\n if (!this.fences.delete(fence)) return;\n if (fence.timer !== undefined) clearTimeout(fence.timer);\n this.gl.deleteSync(fence.sync);\n if (error) fence.reject(error);\n else fence.resolve();\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.canvas.removeEventListener(\"webglcontextlost\", this.onContextLost);\n for (const fence of this.fences) this.settleFence(fence, new Error(\"WebGL2 backend is disposed\"));\n for (const texture of this.textures) texture.destroy();\n const gl = this.gl;\n if (this.instanceBuffer) gl.deleteBuffer(this.instanceBuffer);\n if (this.vertexArray) gl.deleteVertexArray(this.vertexArray);\n if (this.program) gl.deleteProgram(this.program);\n for (const shader of this.shaders) gl.deleteShader(shader);\n this.shaders.clear();\n this.instanceBuffer = undefined;\n this.vertexArray = undefined;\n this.program = undefined;\n this.bufferBytes = 0;\n if (!gl.isContextLost()) gl.getExtension(\"WEBGL_lose_context\")?.loseContext();\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RenderBackend, RenderBatch, RenderColor, RenderTexture } from "./render-backend.js";
|
|
2
|
+
export declare class WebGpuBackend implements RenderBackend {
|
|
3
|
+
readonly device: GPUDevice;
|
|
4
|
+
readonly kind = "webgpu";
|
|
5
|
+
instanceBufferBytes: number;
|
|
6
|
+
instanceBuffer: GPUBuffer | undefined;
|
|
7
|
+
disposed: boolean;
|
|
8
|
+
context?: GPUCanvasContext;
|
|
9
|
+
format: GPUTextureFormat;
|
|
10
|
+
uniform: GPUBuffer;
|
|
11
|
+
sampler: GPUSampler;
|
|
12
|
+
pipeline?: GPURenderPipeline;
|
|
13
|
+
static create(canvas: OffscreenCanvas, onFatal: (error: Error) => void): Promise<WebGpuBackend>;
|
|
14
|
+
constructor(device: GPUDevice);
|
|
15
|
+
get maxTextureDimension2D(): number;
|
|
16
|
+
get maxCanvasDimension2D(): number;
|
|
17
|
+
initialize(canvas: OffscreenCanvas): Promise<void>;
|
|
18
|
+
createTexture(width: number, height: number, label: string): RenderTexture;
|
|
19
|
+
resize(width: number, height: number): void;
|
|
20
|
+
submit(instances: Float32Array<ArrayBuffer>, quadCount: number, batches: readonly RenderBatch[], background: RenderColor): void;
|
|
21
|
+
idle(): Promise<void>;
|
|
22
|
+
dispose(): void;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=webgpu-backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webgpu-backend.d.ts","sourceRoot":"","sources":["../src/webgpu-backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAgB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA6DhH,qBAAa,aAAc,YAAW,aAAa;IA4CrC,QAAQ,CAAC,MAAM,EAAE,SAAS;IA3CtC,QAAQ,CAAC,IAAI,YAAY;IACzB,mBAAmB,SAAK;IACxB,cAAc,EAAE,SAAS,GAAG,SAAS,CAAC;IACtC,QAAQ,UAAS;IACjB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;WAEhB,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;gBAiChF,MAAM,EAAE,SAAS;IAMtC,IAAI,qBAAqB,IAAI,MAAM,CAAqD;IACxF,IAAI,oBAAoB,IAAI,MAAM,CAAuC;IAEnE,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCxD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa;IAyB1E,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAI3C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,EAC7F,UAAU,EAAE,WAAW,GAAG,IAAI;IA8B1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAE3B,OAAO,IAAI,IAAI;CAQhB"}
|