@bigmistqke/view.gl 0.2.0 → 0.4.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 +94 -27
- package/dev/dist/404.html +19 -0
- package/dev/dist/assets/bezier-DrA4cU_H.js +292 -0
- package/dev/dist/assets/game-of-life-BYlzWhEL.js +186 -0
- package/dev/dist/assets/glsl-composition-D_BaFFij.js +97 -0
- package/dev/dist/assets/grid-DfXHNLBm.js +181 -0
- package/dev/dist/assets/index-C2N3u8wj.css +12 -0
- package/dev/dist/assets/index-D-N0FDYs.js +110 -0
- package/dev/dist/assets/main-CSE2fki3.js +32 -0
- package/dev/dist/assets/overview-IeUhUflE.js +80 -0
- package/dev/dist/assets/pix-sim-DMJJA_WO.js +500 -0
- package/dev/dist/assets/ray-casting-DTQetDPd.js +411 -0
- package/dev/dist/assets/ray-casting-modules-Dkfw4tdc.js +765 -0
- package/dev/dist/assets/sdf-CS0uBonx.js +66 -0
- package/dev/dist/assets/symbol-BL_7GJ0M.js +67 -0
- package/dev/dist/assets/tag-C0Vskpp8.js +833 -0
- package/dev/dist/assets/tetris-D8mokodr.js +344 -0
- package/dev/dist/assets/utils-2dzuv_bW.js +62 -0
- package/dev/dist/index.html +51 -0
- package/dist/chunk-BZDGEIOT.mjs +278 -0
- package/dist/chunk-BZDGEIOT.mjs.map +1 -0
- package/dist/{chunk-EYXOSM7B.mjs → chunk-H56LJYS5.mjs} +63 -133
- package/dist/chunk-H56LJYS5.mjs.map +1 -0
- package/dist/gl.d.mts +2 -2
- package/dist/gl.mjs +1 -1
- package/dist/index.d.mts +13 -4
- package/dist/index.mjs +7 -2
- package/dist/tag.d.mts +6 -1
- package/dist/tag.mjs +19 -6
- package/dist/tag.mjs.map +1 -1
- package/dist/{types-CyGyGi18.d.mts → types-k1Xz1iO3.d.mts} +44 -2
- package/package.json +1 -1
- package/src/gl.ts +13 -2
- package/src/index.ts +63 -39
- package/src/tag.ts +23 -2
- package/src/types.ts +43 -1
- package/src/utils.ts +45 -10
- package/test/index.test.ts +145 -29
- package/test/mocks/webgl.ts +14 -0
- package/test/tag.test.ts +41 -0
- package/dist/chunk-CJTT7WPV.mjs +0 -75
- package/dist/chunk-CJTT7WPV.mjs.map +0 -1
- package/dist/chunk-EYXOSM7B.mjs.map +0 -1
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
function assertedNotNullish(value, message) {
|
|
2
|
+
if (value === void 0 || value === null) throw new Error(message);
|
|
3
|
+
return value;
|
|
4
|
+
}
|
|
5
|
+
function once(teardown) {
|
|
6
|
+
let done = false;
|
|
7
|
+
return () => {
|
|
8
|
+
if (done) return;
|
|
9
|
+
done = true;
|
|
10
|
+
teardown();
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const kindToUniformFnName = (kind) => {
|
|
14
|
+
switch (kind[0]) {
|
|
15
|
+
// mat
|
|
16
|
+
case "m":
|
|
17
|
+
return "Matrix" + kind.slice(3) + "fv";
|
|
18
|
+
// sampler/booleans/integer
|
|
19
|
+
case "s":
|
|
20
|
+
case "b":
|
|
21
|
+
case "i":
|
|
22
|
+
return (kind.match(/\d/)?.[0] ?? "1") + "i";
|
|
23
|
+
// unsigned integers
|
|
24
|
+
case "u":
|
|
25
|
+
return (kind[4] || "1") + "ui";
|
|
26
|
+
// vec
|
|
27
|
+
case "v":
|
|
28
|
+
return kind[3] + "f";
|
|
29
|
+
default:
|
|
30
|
+
switch (kind) {
|
|
31
|
+
case "float":
|
|
32
|
+
return "1f";
|
|
33
|
+
case "uint":
|
|
34
|
+
return "1ui";
|
|
35
|
+
default:
|
|
36
|
+
return "1i";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const kindToSize = (kind) => {
|
|
41
|
+
switch (kind[0]) {
|
|
42
|
+
case "m":
|
|
43
|
+
const [a, b] = kind.match(/\d+/g).map(Number);
|
|
44
|
+
return a * (b ?? a);
|
|
45
|
+
case "v":
|
|
46
|
+
case "i":
|
|
47
|
+
case "b":
|
|
48
|
+
case "u":
|
|
49
|
+
const match = kind.match(/\d/);
|
|
50
|
+
return match ? +match[0] : 1;
|
|
51
|
+
default:
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const isMatKind = (kind) => kind.includes("mat");
|
|
56
|
+
const isSamplerKind = (kind) => kind.includes("sampler");
|
|
57
|
+
function createTexture(gl, {
|
|
58
|
+
target = "TEXTURE_2D",
|
|
59
|
+
internalFormat = "RGBA",
|
|
60
|
+
format = "RGBA",
|
|
61
|
+
type = "UNSIGNED_BYTE",
|
|
62
|
+
minFilter = "NEAREST",
|
|
63
|
+
magFilter = "NEAREST",
|
|
64
|
+
wrapS = "CLAMP_TO_EDGE",
|
|
65
|
+
wrapT = "CLAMP_TO_EDGE",
|
|
66
|
+
width,
|
|
67
|
+
height
|
|
68
|
+
}, data, { signal } = {}) {
|
|
69
|
+
const texture = assertedNotNullish(gl.createTexture(), "Failed to create texture");
|
|
70
|
+
function getTextureConstant(name) {
|
|
71
|
+
if (!(name in gl)) {
|
|
72
|
+
throw new Error(`Attempted to create webgl2-only texture (${name}) in webgl1`);
|
|
73
|
+
}
|
|
74
|
+
return gl[name];
|
|
75
|
+
}
|
|
76
|
+
gl.bindTexture(gl[target], texture);
|
|
77
|
+
gl.texImage2D(
|
|
78
|
+
gl[target],
|
|
79
|
+
0,
|
|
80
|
+
getTextureConstant(internalFormat),
|
|
81
|
+
width,
|
|
82
|
+
height,
|
|
83
|
+
0,
|
|
84
|
+
getTextureConstant(format),
|
|
85
|
+
getTextureConstant(type),
|
|
86
|
+
null
|
|
87
|
+
);
|
|
88
|
+
gl.texParameteri(gl[target], gl.TEXTURE_MIN_FILTER, gl[minFilter]);
|
|
89
|
+
gl.texParameteri(gl[target], gl.TEXTURE_MAG_FILTER, gl[magFilter]);
|
|
90
|
+
gl.texParameteri(gl[target], gl.TEXTURE_WRAP_S, gl[wrapS]);
|
|
91
|
+
gl.texParameteri(gl[target], gl.TEXTURE_WRAP_T, gl[wrapT]);
|
|
92
|
+
signal?.addEventListener(
|
|
93
|
+
"abort",
|
|
94
|
+
once(() => gl.deleteTexture(texture))
|
|
95
|
+
);
|
|
96
|
+
return texture;
|
|
97
|
+
}
|
|
98
|
+
const FRAMEBUFFER_ATTACHMENT_MAP = {
|
|
99
|
+
color: "COLOR_ATTACHMENT0",
|
|
100
|
+
depth: "DEPTH_ATTACHMENT",
|
|
101
|
+
stencil: "STENCIL_ATTACHMENT",
|
|
102
|
+
depthStencil: "DEPTH_STENCIL_ATTACHMENT"
|
|
103
|
+
};
|
|
104
|
+
class FramebufferError extends Error {
|
|
105
|
+
constructor(gl, status) {
|
|
106
|
+
let errorMessage = "Framebuffer not complete. Status: ";
|
|
107
|
+
switch (status) {
|
|
108
|
+
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
|
109
|
+
errorMessage += "FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
|
|
110
|
+
break;
|
|
111
|
+
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
|
112
|
+
errorMessage += "FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
|
|
113
|
+
break;
|
|
114
|
+
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
|
|
115
|
+
errorMessage += "FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
|
|
116
|
+
break;
|
|
117
|
+
case gl.FRAMEBUFFER_UNSUPPORTED:
|
|
118
|
+
errorMessage += "FRAMEBUFFER_UNSUPPORTED";
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
errorMessage += `Unknown (${status})`;
|
|
122
|
+
}
|
|
123
|
+
super(errorMessage);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function createFramebuffer(gl, { attachment, texture: providedTexture, ...definition }, { signal } = {}) {
|
|
127
|
+
const texture = providedTexture ?? createTexture(gl, definition);
|
|
128
|
+
const framebuffer = assertedNotNullish(gl.createFramebuffer(), "Failed to create framebuffer");
|
|
129
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
130
|
+
gl.framebufferTexture2D(
|
|
131
|
+
gl.FRAMEBUFFER,
|
|
132
|
+
// Determine attachment point based on attachment kind
|
|
133
|
+
gl[FRAMEBUFFER_ATTACHMENT_MAP[attachment]],
|
|
134
|
+
gl.TEXTURE_2D,
|
|
135
|
+
texture,
|
|
136
|
+
0
|
|
137
|
+
);
|
|
138
|
+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
139
|
+
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
140
|
+
throw new FramebufferError(gl, status);
|
|
141
|
+
}
|
|
142
|
+
const dispose = once(() => {
|
|
143
|
+
gl.deleteFramebuffer(framebuffer);
|
|
144
|
+
if (!providedTexture) {
|
|
145
|
+
gl.deleteTexture(texture);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
signal?.addEventListener("abort", dispose);
|
|
149
|
+
return {
|
|
150
|
+
texture,
|
|
151
|
+
framebuffer,
|
|
152
|
+
dispose
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function createUpsertMap(constructor) {
|
|
156
|
+
const map = new (constructor ?? Map)();
|
|
157
|
+
return Object.assign(map, {
|
|
158
|
+
getOrInsert(key, value) {
|
|
159
|
+
let result = map.get(key);
|
|
160
|
+
if (result) {
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
result = value();
|
|
164
|
+
map.set(key, result);
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function forEachObject(value, callback) {
|
|
170
|
+
let index = 0;
|
|
171
|
+
for (const key in value) {
|
|
172
|
+
callback(value[key], key, index);
|
|
173
|
+
index++;
|
|
174
|
+
}
|
|
175
|
+
for (const key of Object.getOwnPropertySymbols(value)) {
|
|
176
|
+
callback(value[key], key, index);
|
|
177
|
+
index++;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function mapObject(value, callback) {
|
|
181
|
+
const result = {};
|
|
182
|
+
forEachObject(value, (value2, key, index) => {
|
|
183
|
+
result[key] = callback(value2, key, index);
|
|
184
|
+
});
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isWebGL2RenderingContext(gl) {
|
|
189
|
+
return typeof WebGL2RenderingContext !== "undefined" && gl instanceof WebGL2RenderingContext;
|
|
190
|
+
}
|
|
191
|
+
function createShader(gl, type, source) {
|
|
192
|
+
const shader = gl.createShader(type);
|
|
193
|
+
if (!shader) throw new Error("Failed to create shader");
|
|
194
|
+
gl.shaderSource(shader, source);
|
|
195
|
+
gl.compileShader(shader);
|
|
196
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
197
|
+
const info = gl.getShaderInfoLog(shader);
|
|
198
|
+
gl.deleteShader(shader);
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Failed to compile ${type === gl.VERTEX_SHADER ? "vertex" : "fragment"} shader: ${info}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return shader;
|
|
204
|
+
}
|
|
205
|
+
function createProgram(gl, vertexSource, fragmentSource, { signal } = {}) {
|
|
206
|
+
const program = gl.createProgram();
|
|
207
|
+
if (!program) throw new Error("Failed to create WebGL program");
|
|
208
|
+
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
|
|
209
|
+
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
210
|
+
gl.attachShader(program, vertexShader);
|
|
211
|
+
gl.attachShader(program, fragmentShader);
|
|
212
|
+
gl.linkProgram(program);
|
|
213
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
214
|
+
const info = gl.getProgramInfoLog(program);
|
|
215
|
+
gl.deleteProgram(program);
|
|
216
|
+
gl.deleteShader(vertexShader);
|
|
217
|
+
gl.deleteShader(fragmentShader);
|
|
218
|
+
throw new Error(`Failed to link program: ${info}`);
|
|
219
|
+
}
|
|
220
|
+
gl.deleteShader(vertexShader);
|
|
221
|
+
gl.deleteShader(fragmentShader);
|
|
222
|
+
signal?.addEventListener(
|
|
223
|
+
"abort",
|
|
224
|
+
once(() => gl.deleteProgram(program))
|
|
225
|
+
);
|
|
226
|
+
return program;
|
|
227
|
+
}
|
|
228
|
+
const INSTANCED_ARRAYS_WRAPPER_MAP = /* @__PURE__ */ new WeakMap();
|
|
229
|
+
function getInstancedArrays(gl) {
|
|
230
|
+
if (isWebGL2RenderingContext(gl)) return gl;
|
|
231
|
+
const cached = INSTANCED_ARRAYS_WRAPPER_MAP.get(gl);
|
|
232
|
+
if (cached) return cached;
|
|
233
|
+
const ext = gl.getExtension("ANGLE_instanced_arrays");
|
|
234
|
+
if (!ext) return void 0;
|
|
235
|
+
const wrapper = {
|
|
236
|
+
drawArraysInstanced: ext.drawArraysInstancedANGLE.bind(ext),
|
|
237
|
+
drawElementsInstanced: ext.drawElementsInstancedANGLE.bind(ext),
|
|
238
|
+
vertexAttribDivisor: ext.vertexAttribDivisorANGLE.bind(ext)
|
|
239
|
+
};
|
|
240
|
+
INSTANCED_ARRAYS_WRAPPER_MAP.set(gl, wrapper);
|
|
241
|
+
return wrapper;
|
|
242
|
+
}
|
|
243
|
+
const VERTEX_ARRAY_OBJECT_WRAPPER_MAP = /* @__PURE__ */ new WeakMap();
|
|
244
|
+
function getVertexArrayObject(gl) {
|
|
245
|
+
if (isWebGL2RenderingContext(gl)) return gl;
|
|
246
|
+
const cached = VERTEX_ARRAY_OBJECT_WRAPPER_MAP.get(gl);
|
|
247
|
+
if (cached) return cached;
|
|
248
|
+
const ext = gl.getExtension("OES_vertex_array_object");
|
|
249
|
+
if (!ext) return null;
|
|
250
|
+
const wrapper = {
|
|
251
|
+
bindVertexArray: ext.bindVertexArrayOES.bind(ext),
|
|
252
|
+
createVertexArray: ext.createVertexArrayOES.bind(ext),
|
|
253
|
+
deleteVertexArray: ext.deleteVertexArrayOES.bind(ext)
|
|
254
|
+
};
|
|
255
|
+
VERTEX_ARRAY_OBJECT_WRAPPER_MAP.set(gl, wrapper);
|
|
256
|
+
return wrapper;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let index = 0;
|
|
260
|
+
const PREFIX = "VIEW_GL_ALIAS";
|
|
261
|
+
const HAS_WEAK_SYMBOL = (() => {
|
|
262
|
+
try {
|
|
263
|
+
const symbol = Symbol();
|
|
264
|
+
(/* @__PURE__ */ new WeakMap()).set(symbol, "");
|
|
265
|
+
return true;
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
})();
|
|
270
|
+
const SYMBOL_MAP = createUpsertMap(HAS_WEAK_SYMBOL ? WeakMap : Map);
|
|
271
|
+
function toID(key) {
|
|
272
|
+
if (typeof key === "string") {
|
|
273
|
+
return key;
|
|
274
|
+
}
|
|
275
|
+
if (typeof key === "symbol") {
|
|
276
|
+
return SYMBOL_MAP.getOrInsert(key, () => `${PREFIX}_${index++}`);
|
|
277
|
+
}
|
|
278
|
+
return key.toString();
|
|
279
|
+
}
|
|
280
|
+
function view(gl, program, schema, { signal } = {}) {
|
|
281
|
+
const attributes = !schema.attributes ? void 0 : attributeView(gl, program, schema.attributes);
|
|
282
|
+
const interleavedAttributes = !schema.interleavedAttributes ? void 0 : interleavedAttributeView(gl, program, schema.interleavedAttributes);
|
|
283
|
+
const vertexArrays = /* @__PURE__ */ new Set();
|
|
284
|
+
const dispose = once(() => {
|
|
285
|
+
if (attributes) forEachObject(attributes, (value) => value.dispose());
|
|
286
|
+
if (interleavedAttributes) forEachObject(interleavedAttributes, (value) => value.dispose());
|
|
287
|
+
vertexArrays.forEach((vertexArray) => vertexArray.dispose());
|
|
288
|
+
vertexArrays.clear();
|
|
289
|
+
});
|
|
290
|
+
signal?.addEventListener("abort", dispose);
|
|
291
|
+
return {
|
|
292
|
+
uniforms: !schema.uniforms ? void 0 : uniformView(gl, program, schema.uniforms),
|
|
293
|
+
attributes,
|
|
294
|
+
interleavedAttributes,
|
|
295
|
+
vao(participants) {
|
|
296
|
+
const vertexArray = vaoView(gl, participants);
|
|
297
|
+
vertexArrays.add(vertexArray);
|
|
298
|
+
return {
|
|
299
|
+
...vertexArray,
|
|
300
|
+
dispose: once(() => {
|
|
301
|
+
vertexArrays.delete(vertexArray);
|
|
302
|
+
vertexArray.dispose();
|
|
303
|
+
})
|
|
304
|
+
};
|
|
305
|
+
},
|
|
306
|
+
dispose
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function bindDefaultVertexArray(gl) {
|
|
310
|
+
const feature = getVertexArrayObject(gl);
|
|
311
|
+
if (!feature) {
|
|
312
|
+
return () => {
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const previous = gl.getParameter(VERTEX_ARRAY_BINDING);
|
|
316
|
+
feature.bindVertexArray(null);
|
|
317
|
+
return () => feature.bindVertexArray(previous);
|
|
318
|
+
}
|
|
319
|
+
function vaoView(gl, participants, { signal } = {}) {
|
|
320
|
+
const feature = getVertexArrayObject(gl);
|
|
321
|
+
const methods = !feature ? {
|
|
322
|
+
bind() {
|
|
323
|
+
const restores = participants.map((participant) => participant.applyToVertexArray());
|
|
324
|
+
participants.forEach((participant) => participant.applyToContext?.());
|
|
325
|
+
return once(() => restores.forEach((restore) => restore()));
|
|
326
|
+
},
|
|
327
|
+
unbind() {
|
|
328
|
+
},
|
|
329
|
+
dispose() {
|
|
330
|
+
}
|
|
331
|
+
} : (() => {
|
|
332
|
+
const vertexArray = assertedNotNullish(feature.createVertexArray());
|
|
333
|
+
const previous = gl.getParameter(VERTEX_ARRAY_BINDING);
|
|
334
|
+
feature.bindVertexArray(vertexArray);
|
|
335
|
+
for (const participant of participants) {
|
|
336
|
+
participant.applyToVertexArray();
|
|
337
|
+
}
|
|
338
|
+
feature.bindVertexArray(previous);
|
|
339
|
+
return {
|
|
340
|
+
bind() {
|
|
341
|
+
const previousVertexArray = gl.getParameter(
|
|
342
|
+
VERTEX_ARRAY_BINDING
|
|
343
|
+
);
|
|
344
|
+
feature.bindVertexArray(vertexArray);
|
|
345
|
+
participants.forEach((participant) => participant.applyToContext?.());
|
|
346
|
+
return once(() => feature.bindVertexArray(previousVertexArray));
|
|
347
|
+
},
|
|
348
|
+
unbind() {
|
|
349
|
+
feature.bindVertexArray(null);
|
|
350
|
+
},
|
|
351
|
+
dispose: once(() => feature.deleteVertexArray(vertexArray))
|
|
352
|
+
};
|
|
353
|
+
})();
|
|
354
|
+
signal?.addEventListener("abort", () => methods.dispose());
|
|
355
|
+
return methods;
|
|
356
|
+
}
|
|
357
|
+
function uniformView(gl, program, schema) {
|
|
358
|
+
return mapObject(schema, ({ kind, size }, key) => {
|
|
359
|
+
const name = toID(key);
|
|
360
|
+
if (isSamplerKind(kind)) {
|
|
361
|
+
const location2 = gl.getUniformLocation(program, name);
|
|
362
|
+
return {
|
|
363
|
+
set(arg) {
|
|
364
|
+
gl.uniform1i(location2, arg);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (size) {
|
|
369
|
+
const bulkFnName = `uniform${kindToUniformFnName(kind)}v`;
|
|
370
|
+
const bulkFn = gl[bulkFnName].bind(gl);
|
|
371
|
+
const bulkLocation = gl.getUniformLocation(program, name);
|
|
372
|
+
const elementFnName = `uniform${kindToUniformFnName(kind)}`;
|
|
373
|
+
const elementFn = gl[elementFnName].bind(gl);
|
|
374
|
+
const elements = [];
|
|
375
|
+
for (let i = 0; i < size; i++) {
|
|
376
|
+
const location2 = gl.getUniformLocation(program, `${name}[${i}]`);
|
|
377
|
+
elements[i] = {
|
|
378
|
+
set(...args) {
|
|
379
|
+
elementFn(location2, ...args);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return Object.assign(elements, {
|
|
384
|
+
set(arg) {
|
|
385
|
+
bulkFn(bulkLocation, arg);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
const location = gl.getUniformLocation(program, name);
|
|
390
|
+
const fnName = `uniform${kindToUniformFnName(kind)}`;
|
|
391
|
+
const fn = gl[fnName].bind(gl);
|
|
392
|
+
if (isMatKind(kind)) {
|
|
393
|
+
return {
|
|
394
|
+
set(...args) {
|
|
395
|
+
fn(location, false, args[0]);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
set(...args) {
|
|
401
|
+
fn(location, ...args);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const FORMAT_TO_GL_TYPE = {
|
|
407
|
+
float32: "FLOAT",
|
|
408
|
+
int32: "INT",
|
|
409
|
+
uint32: "UNSIGNED_INT",
|
|
410
|
+
int16: "SHORT",
|
|
411
|
+
uint16: "UNSIGNED_SHORT",
|
|
412
|
+
int8: "BYTE",
|
|
413
|
+
uint8: "UNSIGNED_BYTE"
|
|
414
|
+
};
|
|
415
|
+
const FORMAT_BYTE_SIZE = {
|
|
416
|
+
float32: 4,
|
|
417
|
+
int32: 4,
|
|
418
|
+
uint32: 4,
|
|
419
|
+
int16: 2,
|
|
420
|
+
uint16: 2,
|
|
421
|
+
int8: 1,
|
|
422
|
+
uint8: 1
|
|
423
|
+
};
|
|
424
|
+
function defaultFormat(kind) {
|
|
425
|
+
if (kind.startsWith("u")) return "uint32";
|
|
426
|
+
if (kind.startsWith("i")) return "int32";
|
|
427
|
+
return "float32";
|
|
428
|
+
}
|
|
429
|
+
function handleAttribute(gl, location, size, stride, offset, glType, isIntegerKind, normalized, instanced) {
|
|
430
|
+
gl.enableVertexAttribArray(location);
|
|
431
|
+
if (isIntegerKind) {
|
|
432
|
+
gl.vertexAttribIPointer(location, size, gl[glType], stride, offset);
|
|
433
|
+
} else {
|
|
434
|
+
gl.vertexAttribPointer(location, size, gl[glType], normalized, stride, offset);
|
|
435
|
+
}
|
|
436
|
+
const instancedArrays = getInstancedArrays(gl);
|
|
437
|
+
if (instanced) {
|
|
438
|
+
assertedNotNullish(instancedArrays).vertexAttribDivisor(location, 1);
|
|
439
|
+
} else if (instancedArrays) {
|
|
440
|
+
instancedArrays.vertexAttribDivisor(location, 0);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const VERTEX_ATTRIB_ARRAY_DIVISOR = 35070;
|
|
444
|
+
const VERTEX_ARRAY_BINDING = 34229;
|
|
445
|
+
function readDivisor(gl, location) {
|
|
446
|
+
return getInstancedArrays(gl) ? gl.getVertexAttrib(location, VERTEX_ATTRIB_ARRAY_DIVISOR) : 0;
|
|
447
|
+
}
|
|
448
|
+
function attributeView(gl, program, schema, { signal } = {}) {
|
|
449
|
+
const attributes = mapObject(
|
|
450
|
+
schema,
|
|
451
|
+
({ kind, format, normalized = false, instanced, buffer: providedBuffer }, key) => {
|
|
452
|
+
const name = toID(key);
|
|
453
|
+
const buffer = providedBuffer ?? assertedNotNullish(gl.createBuffer());
|
|
454
|
+
const location = gl.getAttribLocation(program, name);
|
|
455
|
+
if (location < 0) {
|
|
456
|
+
throw new Error(`Attribute '${name}' not found`);
|
|
457
|
+
}
|
|
458
|
+
const size = kindToSize(kind);
|
|
459
|
+
const resolvedFormat = format ?? defaultFormat(kind);
|
|
460
|
+
const glType = FORMAT_TO_GL_TYPE[resolvedFormat];
|
|
461
|
+
const isIntKind = kind.startsWith("i") || kind.startsWith("u");
|
|
462
|
+
function applyToVertexArray() {
|
|
463
|
+
const previousDivisor = readDivisor(gl, location);
|
|
464
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
465
|
+
handleAttribute(gl, location, size, 0, 0, glType, isIntKind, normalized, instanced);
|
|
466
|
+
return once(() => {
|
|
467
|
+
getInstancedArrays(gl)?.vertexAttribDivisor(location, previousDivisor);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
buffer,
|
|
472
|
+
applyToVertexArray,
|
|
473
|
+
bind() {
|
|
474
|
+
const restoreVertexArray = bindDefaultVertexArray(gl);
|
|
475
|
+
const restore = applyToVertexArray();
|
|
476
|
+
return once(() => {
|
|
477
|
+
restore();
|
|
478
|
+
restoreVertexArray();
|
|
479
|
+
});
|
|
480
|
+
},
|
|
481
|
+
dispose: once(() => {
|
|
482
|
+
if (!providedBuffer) {
|
|
483
|
+
gl.deleteBuffer(buffer);
|
|
484
|
+
}
|
|
485
|
+
}),
|
|
486
|
+
set(data, usage = "STATIC_DRAW") {
|
|
487
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
488
|
+
gl.bufferData(gl.ARRAY_BUFFER, data, gl[usage]);
|
|
489
|
+
return this;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
signal?.addEventListener("abort", function dispose() {
|
|
495
|
+
forEachObject(attributes, (value) => value.dispose());
|
|
496
|
+
});
|
|
497
|
+
return attributes;
|
|
498
|
+
}
|
|
499
|
+
function constantSource(gl, layout, locations) {
|
|
500
|
+
const setters = layout.map((entry, index2) => {
|
|
501
|
+
const size = kindToSize(entry.kind);
|
|
502
|
+
const name = toID(entry.key);
|
|
503
|
+
if (isMatKind(entry.kind) || size > 4) {
|
|
504
|
+
throw new Error(
|
|
505
|
+
`'${name}' is a ${entry.kind}, which cannot be a constant attribute: vertexAttrib* takes at most 4 floats and has no matrix form. Use the buffer source for it.`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
let values = [];
|
|
509
|
+
const fn = gl[`vertexAttrib${size}f`].bind(gl);
|
|
510
|
+
return {
|
|
511
|
+
name,
|
|
512
|
+
location: locations[index2],
|
|
513
|
+
write: () => {
|
|
514
|
+
if (values.length) {
|
|
515
|
+
fn(locations[index2], ...values);
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
methods: {
|
|
519
|
+
set(...args) {
|
|
520
|
+
values = args;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
});
|
|
525
|
+
return Object.assign(Object.fromEntries(setters.map((setter) => [setter.name, setter.methods])), {
|
|
526
|
+
applyToVertexArray() {
|
|
527
|
+
const restores = setters.map(({ location }) => {
|
|
528
|
+
const wasEnabled = gl.getVertexAttrib(location, gl.VERTEX_ATTRIB_ARRAY_ENABLED);
|
|
529
|
+
gl.disableVertexAttribArray(location);
|
|
530
|
+
return () => wasEnabled ? gl.enableVertexAttribArray(location) : void 0;
|
|
531
|
+
});
|
|
532
|
+
return once(() => restores.forEach((restore) => restore()));
|
|
533
|
+
},
|
|
534
|
+
applyToContext() {
|
|
535
|
+
setters.forEach((setter) => setter.write());
|
|
536
|
+
},
|
|
537
|
+
bind() {
|
|
538
|
+
const restoreVertexArray = bindDefaultVertexArray(gl);
|
|
539
|
+
const restore = this.applyToVertexArray();
|
|
540
|
+
this.applyToContext();
|
|
541
|
+
return once(() => {
|
|
542
|
+
restore();
|
|
543
|
+
restoreVertexArray();
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
function interleavedAttributeView(gl, program, schema, { signal } = {}) {
|
|
549
|
+
const interleavedAttributes = mapObject(schema, ({ layout }) => {
|
|
550
|
+
let index2 = 0;
|
|
551
|
+
const locations = [];
|
|
552
|
+
const handles = layout.map((layout2) => {
|
|
553
|
+
const name = toID(layout2.key);
|
|
554
|
+
const location = gl.getAttribLocation(program, name);
|
|
555
|
+
if (location < 0) {
|
|
556
|
+
throw new Error(`Attribute '${name}' not found`);
|
|
557
|
+
}
|
|
558
|
+
locations.push(location);
|
|
559
|
+
const size = kindToSize(layout2.kind);
|
|
560
|
+
const offset = index2;
|
|
561
|
+
const resolvedFormat = layout2.format ?? defaultFormat(layout2.kind);
|
|
562
|
+
const glType = FORMAT_TO_GL_TYPE[resolvedFormat];
|
|
563
|
+
const isIntKind = layout2.kind.startsWith("i") || layout2.kind.startsWith("u");
|
|
564
|
+
const normalized = layout2.normalized ?? false;
|
|
565
|
+
index2 += size * FORMAT_BYTE_SIZE[resolvedFormat];
|
|
566
|
+
return (divisor) => handleAttribute(
|
|
567
|
+
gl,
|
|
568
|
+
location,
|
|
569
|
+
size,
|
|
570
|
+
stride,
|
|
571
|
+
offset,
|
|
572
|
+
glType,
|
|
573
|
+
isIntKind,
|
|
574
|
+
normalized,
|
|
575
|
+
divisor === 1
|
|
576
|
+
);
|
|
577
|
+
});
|
|
578
|
+
const stride = index2;
|
|
579
|
+
const buffer = assertedNotNullish(gl.createBuffer());
|
|
580
|
+
function applyToVertexArray(divisor) {
|
|
581
|
+
const previousDivisors = locations.map(
|
|
582
|
+
(location) => [location, readDivisor(gl, location)]
|
|
583
|
+
);
|
|
584
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
585
|
+
for (const handle of handles) {
|
|
586
|
+
handle(divisor);
|
|
587
|
+
}
|
|
588
|
+
return once(() => {
|
|
589
|
+
const instancedArrays = getInstancedArrays(gl);
|
|
590
|
+
previousDivisors.forEach(([location, previous]) => {
|
|
591
|
+
instancedArrays?.vertexAttribDivisor(location, previous);
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
let constant;
|
|
596
|
+
function bufferSource(divisor) {
|
|
597
|
+
const apply = () => applyToVertexArray(divisor);
|
|
598
|
+
return {
|
|
599
|
+
applyToVertexArray: apply,
|
|
600
|
+
bind() {
|
|
601
|
+
const restoreVertexArray = bindDefaultVertexArray(gl);
|
|
602
|
+
const restore = apply();
|
|
603
|
+
return once(() => {
|
|
604
|
+
restore();
|
|
605
|
+
restoreVertexArray();
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
buffer: Object.assign(bufferSource(0), {
|
|
612
|
+
/** The same buffer, stepped once per instance instead of once per vertex. */
|
|
613
|
+
perInstance: bufferSource(1),
|
|
614
|
+
set(value, usage = "STATIC_DRAW") {
|
|
615
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
616
|
+
gl.bufferData(gl.ARRAY_BUFFER, value, gl[usage]);
|
|
617
|
+
}
|
|
618
|
+
}),
|
|
619
|
+
// Lazy: a layout key can be perfectly good in a buffer and impossible as
|
|
620
|
+
// a constant, and finding that out should wait until someone asks for one.
|
|
621
|
+
get constant() {
|
|
622
|
+
return constant ??= constantSource(gl, layout, locations);
|
|
623
|
+
},
|
|
624
|
+
dispose: once(() => gl.deleteBuffer(buffer))
|
|
625
|
+
};
|
|
626
|
+
});
|
|
627
|
+
signal?.addEventListener("abort", function dispose() {
|
|
628
|
+
forEachObject(interleavedAttributes, (value) => value.dispose());
|
|
629
|
+
});
|
|
630
|
+
return interleavedAttributes;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function glsl(template, ...slots) {
|
|
634
|
+
return { template, slots, type: "glsl" };
|
|
635
|
+
}
|
|
636
|
+
const uniform = new Proxy(
|
|
637
|
+
{},
|
|
638
|
+
{
|
|
639
|
+
get(target, property) {
|
|
640
|
+
if (typeof property === "symbol") return target[property];
|
|
641
|
+
return (key, options) => ({
|
|
642
|
+
type: "uniform",
|
|
643
|
+
key,
|
|
644
|
+
kind: property,
|
|
645
|
+
...options
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
);
|
|
650
|
+
const attribute = new Proxy(
|
|
651
|
+
{},
|
|
652
|
+
{
|
|
653
|
+
get(target, property) {
|
|
654
|
+
if (typeof property === "symbol") return target[property];
|
|
655
|
+
return (key, options) => ({
|
|
656
|
+
type: "attribute",
|
|
657
|
+
key,
|
|
658
|
+
kind: property,
|
|
659
|
+
...options
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
function interleave(key, layout, { instanced, buffer } = {}) {
|
|
665
|
+
return {
|
|
666
|
+
type: "interleavedAttribute",
|
|
667
|
+
key,
|
|
668
|
+
// remove instanced- and type-property
|
|
669
|
+
layout: layout.map(({ key: key2, kind }) => ({
|
|
670
|
+
key: key2,
|
|
671
|
+
kind
|
|
672
|
+
})),
|
|
673
|
+
instanced: !!instanced,
|
|
674
|
+
buffer
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function compile(gl, vertex, fragment, options) {
|
|
678
|
+
const _vertex = resolveGLSLTag(vertex, options);
|
|
679
|
+
const _fragment = resolveGLSLTag(fragment, options);
|
|
680
|
+
const schema = {
|
|
681
|
+
uniforms: {
|
|
682
|
+
..._vertex.schema.uniforms,
|
|
683
|
+
..._fragment.schema.uniforms
|
|
684
|
+
},
|
|
685
|
+
attributes: {
|
|
686
|
+
..._vertex.schema.attributes,
|
|
687
|
+
..._fragment.schema.attributes
|
|
688
|
+
},
|
|
689
|
+
interleavedAttributes: {
|
|
690
|
+
..._vertex.schema.interleavedAttributes,
|
|
691
|
+
..._fragment.schema.interleavedAttributes
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
for (const kind in options?.schema) {
|
|
695
|
+
const schemaKind = schema[kind];
|
|
696
|
+
const configSchemaKind = options?.schema[kind];
|
|
697
|
+
for (const key in configSchemaKind) {
|
|
698
|
+
schemaKind[key] = {
|
|
699
|
+
...schemaKind[key],
|
|
700
|
+
...configSchemaKind[key]
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
try {
|
|
705
|
+
const program = createProgram(gl, _vertex.template, _fragment.template);
|
|
706
|
+
const resolvedView = view(gl, program, schema);
|
|
707
|
+
const dispose = once(() => {
|
|
708
|
+
resolvedView.dispose();
|
|
709
|
+
gl.deleteProgram(program);
|
|
710
|
+
});
|
|
711
|
+
options?.signal?.addEventListener("abort", dispose);
|
|
712
|
+
return {
|
|
713
|
+
program,
|
|
714
|
+
schema,
|
|
715
|
+
view: resolvedView,
|
|
716
|
+
dispose,
|
|
717
|
+
vertex: _vertex.template,
|
|
718
|
+
fragment: _fragment.template
|
|
719
|
+
};
|
|
720
|
+
} catch (error) {
|
|
721
|
+
console.error("Error while creating WebGLProgram - vertex\n\n", _vertex.template);
|
|
722
|
+
console.error("Error while creating WebGLProgram - fragment\n\n", _fragment.template);
|
|
723
|
+
throw error;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function resolveGLSLTag(tag, options) {
|
|
727
|
+
return {
|
|
728
|
+
template: compile.toString(tag, options),
|
|
729
|
+
schema: compile.toSchema(tag)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
compile.toSchema = function(tag) {
|
|
733
|
+
const result = {
|
|
734
|
+
uniforms: {},
|
|
735
|
+
attributes: {},
|
|
736
|
+
interleavedAttributes: {}
|
|
737
|
+
};
|
|
738
|
+
tag.slots.forEach(function handleSlot(slot) {
|
|
739
|
+
if (typeof slot !== "object") {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (Array.isArray(slot)) {
|
|
743
|
+
slot.forEach(handleSlot);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (slot.type === "glsl") {
|
|
747
|
+
const { uniforms, attributes, interleavedAttributes } = compile.toSchema(slot);
|
|
748
|
+
result.uniforms = {
|
|
749
|
+
...result.uniforms,
|
|
750
|
+
...uniforms
|
|
751
|
+
};
|
|
752
|
+
result.attributes = {
|
|
753
|
+
...result.attributes,
|
|
754
|
+
...attributes
|
|
755
|
+
};
|
|
756
|
+
result.interleavedAttributes = {
|
|
757
|
+
...result.interleavedAttributes,
|
|
758
|
+
...interleavedAttributes
|
|
759
|
+
};
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const { key: name, type, ...rest } = slot;
|
|
763
|
+
result[`${type}s`][name] = rest;
|
|
764
|
+
});
|
|
765
|
+
return result;
|
|
766
|
+
};
|
|
767
|
+
compile.toString = function({ template: [initial, ...rest], slots }, config) {
|
|
768
|
+
const v300 = config?.webgl2 ?? !!initial?.startsWith("#version 300 es");
|
|
769
|
+
let template = initial ?? "";
|
|
770
|
+
for (let i = 0; i < rest.length; i++) {
|
|
771
|
+
template += `${resolveGlslSlotToString(slots[i], v300)}${rest[i]}`;
|
|
772
|
+
}
|
|
773
|
+
return template;
|
|
774
|
+
};
|
|
775
|
+
function resolveGlslSlotToString(slot, v300) {
|
|
776
|
+
if (typeof slot !== "object") {
|
|
777
|
+
return toID(slot);
|
|
778
|
+
}
|
|
779
|
+
if (Array.isArray(slot)) {
|
|
780
|
+
return slot.map((slot2) => resolveGlslSlotToString(slot2, v300)).join("");
|
|
781
|
+
}
|
|
782
|
+
switch (slot.type) {
|
|
783
|
+
case "glsl":
|
|
784
|
+
return compile.toString(slot);
|
|
785
|
+
case "interleavedAttribute":
|
|
786
|
+
return slot.layout.reduce(
|
|
787
|
+
(a, v) => v300 ? `${a}in ${v.kind} ${toID(v.key)};
|
|
788
|
+
` : `${a}attribute ${v.kind} ${toID(v.key)};
|
|
789
|
+
`,
|
|
790
|
+
""
|
|
791
|
+
);
|
|
792
|
+
case "uniform":
|
|
793
|
+
if ("size" in slot) {
|
|
794
|
+
return `${slot.type} ${slot.kind} ${toID(slot.key)}[${slot.size}];`;
|
|
795
|
+
}
|
|
796
|
+
return `${slot.type} ${slot.kind} ${toID(slot.key)};`;
|
|
797
|
+
}
|
|
798
|
+
if (slot.type === "attribute") {
|
|
799
|
+
return `${v300 ? "in" : slot.type} ${slot.kind} ${toID(slot.key)};`;
|
|
800
|
+
}
|
|
801
|
+
throw new Error(`Unexpected slot: ${JSON.stringify(slot)}`);
|
|
802
|
+
}
|
|
803
|
+
const QUAD_FLOAT_ARRAY = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]);
|
|
804
|
+
const QUAD_BUFFER_MAP = createUpsertMap(WeakMap);
|
|
805
|
+
compile.toQuad = function(gl, fragment, options) {
|
|
806
|
+
const buffer = QUAD_BUFFER_MAP.getOrInsert(gl, gl.createBuffer.bind(gl));
|
|
807
|
+
const webgl2 = options?.webgl2 ?? fragment.template[0]?.startsWith("#version 300 es");
|
|
808
|
+
const vertex = webgl2 ? glsl`#version 300 es
|
|
809
|
+
precision mediump float;
|
|
810
|
+
|
|
811
|
+
${attribute.vec2("a_quad", { buffer })}
|
|
812
|
+
|
|
813
|
+
out vec2 v_uv;
|
|
814
|
+
|
|
815
|
+
void main() {
|
|
816
|
+
v_uv = a_quad;
|
|
817
|
+
gl_Position = vec4(v_uv, 0.0, 1.0);
|
|
818
|
+
}` : glsl`precision mediump float;
|
|
819
|
+
|
|
820
|
+
${attribute.vec2("a_quad", { buffer })}
|
|
821
|
+
|
|
822
|
+
varying vec2 v_uv;
|
|
823
|
+
|
|
824
|
+
void main() {
|
|
825
|
+
v_uv = a_quad;
|
|
826
|
+
gl_Position = vec4(v_uv, 0.0, 1.0);
|
|
827
|
+
}`;
|
|
828
|
+
const result = compile(gl, vertex, fragment, options);
|
|
829
|
+
result.view.attributes.a_quad.set(QUAD_FLOAT_ARRAY);
|
|
830
|
+
return result;
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
export { attribute as a, createFramebuffer as b, compile as c, uniformView as d, attributeView as e, glsl as g, interleave as i, uniform as u };
|