@bigmistqke/view.gl 0.1.19 → 0.3.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 (44) hide show
  1. package/README.md +82 -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-X4M3LV4R.mjs +433 -0
  23. package/dist/chunk-X4M3LV4R.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 +23 -6
  27. package/dist/index.mjs +9 -2
  28. package/dist/tag.d.mts +6 -1
  29. package/dist/tag.mjs +13 -5
  30. package/dist/tag.mjs.map +1 -1
  31. package/dist/{types-DTSPQclW.d.mts → types-CNdqh7tB.d.mts} +121 -8
  32. package/package.json +1 -1
  33. package/src/gl.ts +13 -2
  34. package/src/index.ts +294 -122
  35. package/src/tag.ts +18 -2
  36. package/src/types.ts +127 -7
  37. package/src/utils.ts +45 -10
  38. package/test/index.test.ts +339 -84
  39. package/test/mocks/webgl.ts +20 -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-ZP4HP5H7.mjs +0 -405
  44. package/dist/chunk-ZP4HP5H7.mjs.map +0 -1
package/src/types.ts CHANGED
@@ -322,11 +322,58 @@ type FormatToArray = {
322
322
  uint8: Uint8Array
323
323
  }
324
324
 
325
- export interface AttributeMethods<T extends AttributeDefinition = AttributeDefinition> {
325
+ /**
326
+ * What a participant contributes to a vertex array.
327
+ *
328
+ * `enableVertexAttribArray`, `vertexAttribPointer`, `vertexAttribDivisor` and
329
+ * the `ELEMENT_ARRAY_BUFFER` binding all write into whichever vertex array is
330
+ * bound when they run — they name no destination of their own. That is what
331
+ * makes them a participant in something rather than a property of themselves,
332
+ * and why `vao()` exists: it binds the array first, then asks each participant
333
+ * to write itself in.
334
+ *
335
+ * The returned function undoes the write. It is only used where there is no
336
+ * vertex array to hold the state — a WebGL1 context without
337
+ * `OES_vertex_array_object` — since a real vertex array is thrown away whole.
338
+ */
339
+ export interface VertexArrayParticipant {
340
+ applyToVertexArray(): () => void
341
+ /**
342
+ * State the CONTEXT holds rather than the vertex array, re-applied on every
343
+ * bind because no array can keep it.
344
+ *
345
+ * There is exactly one such thing: the current generic vertex attribute
346
+ * values, which an attribute reads when its array is disabled. The enable
347
+ * flag saying to read them is vertex-array state; the values themselves are
348
+ * not. So a constant source splits across the two hooks — and the tempting
349
+ * optimisation of skipping this when nothing changed would let one draw's
350
+ * constants leak into the next.
351
+ */
352
+ applyToContext?(): void
353
+ }
354
+
355
+ export interface AttributeMethods<T extends AttributeDefinition = AttributeDefinition>
356
+ extends VertexArrayParticipant {
326
357
  buffer: WebGLBuffer
327
- /** Binds the attribute, returning a disposer that restores the divisor */
358
+ /**
359
+ * Apply this attribute to the DEFAULT vertex array, returning a disposer.
360
+ *
361
+ * Explicitly the default one: these calls have no destination of their own,
362
+ * so without binding first this would edit whichever array happened to be
363
+ * bound — and a vertex array keeps what it is given, so the damage would
364
+ * outlive the call. Use `vao()` to build an array of your own.
365
+ */
328
366
  bind(): () => void
329
367
  set(data: FormatToArray[ResolvedFormat<T>], usage?: GLUsage): { bind(): () => void }
368
+ /**
369
+ * Delete the buffer, if this attribute is the one that made it.
370
+ *
371
+ * A buffer named by the schema came from somewhere else and is deleted by
372
+ * whoever made it — a shared buffer is the point of passing one in, and one
373
+ * view cannot know whether the others are done. So this is safe to call
374
+ * twice and safe to call on a borrowed buffer, and it is not a way to free
375
+ * one: dispose the view that created it.
376
+ */
330
377
  dispose(): void
331
378
  }
332
379
 
@@ -354,13 +401,55 @@ export interface InterleavedAttributeDefinition {
354
401
 
355
402
  export type InterleavedAttributeSchema = Record<string | symbol, InterleavedAttributeDefinition>
356
403
 
404
+ /**
405
+ * A source is where one draw's values for a layout come from. Both kinds are
406
+ * participants, so either can go into a `vao()`.
407
+ */
408
+ export interface Source extends VertexArrayParticipant {
409
+ /**
410
+ * Apply this source to the DEFAULT vertex array, returning a disposer. See
411
+ * `AttributeMethods.bind` — and prefer `vao()`, which is the only way to get
412
+ * a source and its fellow participants into an array of their own.
413
+ */
414
+ bind(): () => void
415
+ }
416
+
417
+ export interface BufferSource extends Source {
418
+ /**
419
+ * The same buffer, stepped once per instance rather than once per vertex.
420
+ *
421
+ * A divisor is a property of the draw, not of the data — the same markers
422
+ * are an instanced fleet in one draw and a single mesh in the next — so it
423
+ * is chosen here rather than declared in the schema.
424
+ */
425
+ perInstance: Source
426
+ set(data: Float32Array, usage?: GLUsage): void
427
+ }
428
+
429
+ export interface ConstantAttributeMethods {
430
+ set(...values: number[]): void
431
+ }
432
+
433
+ /**
434
+ * Per-key setters over the same layout the buffer source uses — a constant
435
+ * attribute is a uniform in all but name, so it is written like one.
436
+ */
437
+ export type ConstantSource<T extends InterleavedAttributeLayout[]> = Source & {
438
+ [K in T[number] as K['key'] extends string ? K['key'] : never]: ConstantAttributeMethods
439
+ }
440
+
357
441
  export interface InterleavedAttributeMethods<
358
442
  T extends InterleavedAttributeLayout[] = InterleavedAttributeLayout[],
359
443
  > {
444
+ buffer: BufferSource
445
+ constant: ConstantSource<T>
446
+ dispose(): void
447
+ }
448
+
449
+ export interface VertexArrayMethods {
360
450
  /** Binds the vertex array, returning a disposer that restores the previous one */
361
451
  bind(): () => void
362
452
  unbind(): void
363
- set(data: Float32Array, usage?: GLUsage): void
364
453
  dispose(): void
365
454
  }
366
455
 
@@ -381,7 +470,7 @@ export interface BufferDefinition {
381
470
 
382
471
  export type BufferSchema = Record<string | symbol, BufferDefinition>
383
472
 
384
- export interface BufferMethods {
473
+ export interface BufferMethods extends VertexArrayParticipant {
385
474
  set(data: Float32Array | Uint16Array | Uint32Array): void
386
475
  /** Binds the buffer, returning a disposer that restores the previous binding */
387
476
  bind(): () => void
@@ -422,6 +511,12 @@ export interface TextureDefinition {
422
511
  export interface Framebuffer {
423
512
  texture: WebGLTexture
424
513
  framebuffer: WebGLFramebuffer
514
+ /**
515
+ * Delete the framebuffer, and the texture only if `createFramebuffer` made
516
+ * it. A texture handed in belongs to whoever made it. Idempotent, and exactly
517
+ * what aborting the signal does.
518
+ */
519
+ dispose(): void
425
520
  }
426
521
 
427
522
  /**********************************************************************************/
@@ -537,11 +632,16 @@ export interface ViewOptions {
537
632
  signal?: AbortSignal
538
633
  }
539
634
 
635
+ /**
636
+ * What a program has names for. Buffers are absent on purpose: a buffer has no
637
+ * name in the source, so it cannot be resolved against a program the way a
638
+ * uniform or an attribute is, and its lifetime is rarely a program's. Build one
639
+ * with `bufferView` and pass it to `vao()` alongside the attributes.
640
+ */
540
641
  export interface ViewSchema {
541
642
  uniforms?: UniformSchema
542
643
  attributes?: AttributeSchema
543
644
  interleavedAttributes?: InterleavedAttributeSchema
544
- buffers?: BufferSchema
545
645
  }
546
646
 
547
647
  type PartialSchema<T> =
@@ -551,7 +651,6 @@ export interface ViewSchemaPartial {
551
651
  uniforms?: PartialSchema<UniformSchema>
552
652
  attributes?: PartialSchema<AttributeSchema>
553
653
  interleavedAttributes?: PartialSchema<InterleavedAttributeSchema>
554
- buffers?: PartialSchema<BufferSchema>
555
654
  }
556
655
 
557
656
  export type View<T extends ViewSchema = ViewSchema> = {
@@ -561,8 +660,24 @@ export type View<T extends ViewSchema = ViewSchema> = {
561
660
  interleavedAttributes: T['interleavedAttributes'] extends InterleavedAttributeSchema
562
661
  ? Prettify<InterleavedAttributeView<T['interleavedAttributes']>>
563
662
  : {}
564
- buffers: T['buffers'] extends BufferSchema ? Prettify<BufferView<T['buffers']>> : {}
565
663
  uniforms: T['uniforms'] extends UniformSchema ? Prettify<UniformView<T['uniforms']>> : {}
664
+ /**
665
+ * Build a vertex array from the participants one draw needs — its attributes,
666
+ * its interleaved layouts, its element buffer.
667
+ *
668
+ * This is the one member the schema cannot declare, because its input is the
669
+ * schema's own output. It is also the only place a vertex array is created:
670
+ * the participants no longer make one each, so which array they end up in is
671
+ * something you say rather than something you inherit from call order.
672
+ */
673
+ vao(participants: VertexArrayParticipant[]): VertexArrayMethods
674
+ /**
675
+ * Delete everything the view made: the buffers behind its attributes and
676
+ * layouts, and any vertex array still held from `vao()`. Buffers named by the
677
+ * schema are left alone — those belong to whoever made them. Idempotent, and
678
+ * exactly what aborting the signal does.
679
+ */
680
+ dispose(): void
566
681
  }
567
682
 
568
683
  /**********************************************************************************/
@@ -791,5 +906,10 @@ export type CompileResult<
791
906
  view: Prettify<View<TSchema>>
792
907
  vertex: string
793
908
  fragment: string
909
+ /**
910
+ * Delete the program and everything its view made. Idempotent, and
911
+ * exactly what aborting the signal does.
912
+ */
913
+ dispose(): void
794
914
  }
795
915
  : never
package/src/utils.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  KIND_TO_UNIFORM_FN_NAME_MAP,
7
7
  TextureDefinition,
8
8
  UniformKind,
9
+ ViewOptions,
9
10
  } from './types'
10
11
 
11
12
  export function assertedNotNullish<T>(value: T, message?: string): NonNullable<T> {
@@ -13,6 +14,24 @@ export function assertedNotNullish<T>(value: T, message?: string): NonNullable<T
13
14
  return value
14
15
  }
15
16
 
17
+ /**
18
+ * Wraps a teardown so it runs once.
19
+ *
20
+ * Two reasons, and both bite. A disposer is per `bind()`, and undoing twice
21
+ * would put back state a later `bind()` is relying on. And a `dispose()` that
22
+ * deletes twice invalidates a handle someone else may still be holding — the
23
+ * object survives while it is bound, so the fault surfaces later, somewhere
24
+ * else. Idempotent is the only safe shape for either.
25
+ */
26
+ export function once(teardown: () => void): () => void {
27
+ let done = false
28
+ return () => {
29
+ if (done) return
30
+ done = true
31
+ teardown()
32
+ }
33
+ }
34
+
16
35
  /**********************************************************************************/
17
36
  /* */
18
37
  /* Kind To Uniform Name */
@@ -114,8 +133,9 @@ export function createTexture(
114
133
  height,
115
134
  }: TextureDefinition,
116
135
  data?: ArrayBufferView | null,
136
+ { signal }: ViewOptions = {},
117
137
  ): WebGLTexture {
118
- const texture = gl.createTexture()
138
+ const texture = assertedNotNullish(gl.createTexture(), 'Failed to create texture')
119
139
 
120
140
  function getTextureConstant(name: string) {
121
141
  if (!(name in gl)) {
@@ -137,13 +157,17 @@ export function createTexture(
137
157
  data ?? null,
138
158
  )
139
159
 
140
- console.log(minFilter)
141
160
  // Set texture parameters
142
161
  gl.texParameteri(gl[target], gl.TEXTURE_MIN_FILTER, gl[minFilter])
143
162
  gl.texParameteri(gl[target], gl.TEXTURE_MAG_FILTER, gl[magFilter])
144
163
  gl.texParameteri(gl[target], gl.TEXTURE_WRAP_S, gl[wrapS])
145
164
  gl.texParameteri(gl[target], gl.TEXTURE_WRAP_T, gl[wrapT])
146
165
 
166
+ signal?.addEventListener(
167
+ 'abort',
168
+ once(() => gl.deleteTexture(texture)),
169
+ )
170
+
147
171
  return texture
148
172
  }
149
173
 
@@ -162,7 +186,7 @@ const FRAMEBUFFER_ATTACHMENT_MAP = {
162
186
 
163
187
  class FramebufferError extends Error {
164
188
  constructor(gl: GL, status: number) {
165
- let errorMessage = `Framebuffer '${name}' not complete. Status: `
189
+ let errorMessage = 'Framebuffer not complete. Status: '
166
190
  switch (status) {
167
191
  case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
168
192
  errorMessage += 'FRAMEBUFFER_INCOMPLETE_ATTACHMENT'
@@ -185,16 +209,15 @@ class FramebufferError extends Error {
185
209
 
186
210
  export function createFramebuffer(
187
211
  gl: GL,
188
- { attachment, texture, ...options }: FramebufferDefinition,
212
+ { attachment, texture: providedTexture, ...definition }: FramebufferDefinition,
213
+ { signal }: ViewOptions = {},
189
214
  ) {
190
- // Create texture for the framebuffer
191
- texture ??= createTexture(gl, options)
215
+ // Create texture for the framebuffer, unless one was handed in. Which one it
216
+ // is decides who deletes it below — the same rule the attribute buffers follow.
217
+ const texture = providedTexture ?? createTexture(gl, definition)
192
218
 
193
219
  // Create framebuffer
194
- const framebuffer = assertedNotNullish(
195
- gl.createFramebuffer(),
196
- `Failed to create framebuffer: ${name}`,
197
- )
220
+ const framebuffer = assertedNotNullish(gl.createFramebuffer(), 'Failed to create framebuffer')
198
221
 
199
222
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
200
223
 
@@ -213,9 +236,21 @@ export function createFramebuffer(
213
236
  throw new FramebufferError(gl, status)
214
237
  }
215
238
 
239
+ const dispose = once(() => {
240
+ gl.deleteFramebuffer(framebuffer)
241
+ // A texture handed in belongs to whoever made it — it commonly outlives the
242
+ // framebuffer, ping-ponged between two of them.
243
+ if (!providedTexture) {
244
+ gl.deleteTexture(texture)
245
+ }
246
+ })
247
+
248
+ signal?.addEventListener('abort', dispose)
249
+
216
250
  return {
217
251
  texture,
218
252
  framebuffer,
253
+ dispose,
219
254
  }
220
255
  }
221
256