@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.
Files changed (43) hide show
  1. package/README.md +94 -27
  2. package/dev/dist/404.html +19 -0
  3. package/dev/dist/assets/bezier-DrA4cU_H.js +292 -0
  4. package/dev/dist/assets/game-of-life-BYlzWhEL.js +186 -0
  5. package/dev/dist/assets/glsl-composition-D_BaFFij.js +97 -0
  6. package/dev/dist/assets/grid-DfXHNLBm.js +181 -0
  7. package/dev/dist/assets/index-C2N3u8wj.css +12 -0
  8. package/dev/dist/assets/index-D-N0FDYs.js +110 -0
  9. package/dev/dist/assets/main-CSE2fki3.js +32 -0
  10. package/dev/dist/assets/overview-IeUhUflE.js +80 -0
  11. package/dev/dist/assets/pix-sim-DMJJA_WO.js +500 -0
  12. package/dev/dist/assets/ray-casting-DTQetDPd.js +411 -0
  13. package/dev/dist/assets/ray-casting-modules-Dkfw4tdc.js +765 -0
  14. package/dev/dist/assets/sdf-CS0uBonx.js +66 -0
  15. package/dev/dist/assets/symbol-BL_7GJ0M.js +67 -0
  16. package/dev/dist/assets/tag-C0Vskpp8.js +833 -0
  17. package/dev/dist/assets/tetris-D8mokodr.js +344 -0
  18. package/dev/dist/assets/utils-2dzuv_bW.js +62 -0
  19. package/dev/dist/index.html +51 -0
  20. package/dist/chunk-BZDGEIOT.mjs +278 -0
  21. package/dist/chunk-BZDGEIOT.mjs.map +1 -0
  22. package/dist/{chunk-EYXOSM7B.mjs → chunk-H56LJYS5.mjs} +63 -133
  23. package/dist/chunk-H56LJYS5.mjs.map +1 -0
  24. package/dist/gl.d.mts +2 -2
  25. package/dist/gl.mjs +1 -1
  26. package/dist/index.d.mts +13 -4
  27. package/dist/index.mjs +7 -2
  28. package/dist/tag.d.mts +6 -1
  29. package/dist/tag.mjs +19 -6
  30. package/dist/tag.mjs.map +1 -1
  31. package/dist/{types-CyGyGi18.d.mts → types-k1Xz1iO3.d.mts} +44 -2
  32. package/package.json +1 -1
  33. package/src/gl.ts +13 -2
  34. package/src/index.ts +63 -39
  35. package/src/tag.ts +23 -2
  36. package/src/types.ts +43 -1
  37. package/src/utils.ts +45 -10
  38. package/test/index.test.ts +145 -29
  39. package/test/mocks/webgl.ts +14 -0
  40. package/test/tag.test.ts +41 -0
  41. package/dist/chunk-CJTT7WPV.mjs +0 -75
  42. package/dist/chunk-CJTT7WPV.mjs.map +0 -1
  43. 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 };