@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.
- package/README.md +82 -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-X4M3LV4R.mjs +433 -0
- package/dist/chunk-X4M3LV4R.mjs.map +1 -0
- package/dist/gl.d.mts +2 -2
- package/dist/gl.mjs +1 -1
- package/dist/index.d.mts +23 -6
- package/dist/index.mjs +9 -2
- package/dist/tag.d.mts +6 -1
- package/dist/tag.mjs +13 -5
- package/dist/tag.mjs.map +1 -1
- package/dist/{types-DTSPQclW.d.mts → types-CNdqh7tB.d.mts} +121 -8
- package/package.json +1 -1
- package/src/gl.ts +13 -2
- package/src/index.ts +294 -122
- package/src/tag.ts +18 -2
- package/src/types.ts +127 -7
- package/src/utils.ts +45 -10
- package/test/index.test.ts +339 -84
- package/test/mocks/webgl.ts +20 -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-ZP4HP5H7.mjs +0 -405
- package/dist/chunk-ZP4HP5H7.mjs.map +0 -1
package/src/index.ts
CHANGED
|
@@ -9,10 +9,14 @@ import type {
|
|
|
9
9
|
BufferView,
|
|
10
10
|
GL,
|
|
11
11
|
UniformView as InferUniformView,
|
|
12
|
+
GLUsage,
|
|
13
|
+
InterleavedAttributeLayout,
|
|
12
14
|
InterleavedAttributeMethods,
|
|
13
15
|
InterleavedAttributeSchema,
|
|
14
16
|
InterleavedAttributeView,
|
|
15
17
|
UniformSchema,
|
|
18
|
+
VertexArrayMethods,
|
|
19
|
+
VertexArrayParticipant,
|
|
16
20
|
View,
|
|
17
21
|
ViewOptions,
|
|
18
22
|
ViewSchema,
|
|
@@ -24,9 +28,13 @@ import {
|
|
|
24
28
|
assertedNotNullish,
|
|
25
29
|
kindToSize,
|
|
26
30
|
forEachObject,
|
|
31
|
+
once,
|
|
27
32
|
} from './utils'
|
|
28
33
|
import { createUpsertMap, mapObject } from './utils'
|
|
29
34
|
export * from './types'
|
|
35
|
+
// Documented under Utils but reachable from nowhere until now: utils is not a
|
|
36
|
+
// build entry, and named rather than star so the internal helpers stay internal.
|
|
37
|
+
export { createFramebuffer, createTexture } from './utils'
|
|
30
38
|
|
|
31
39
|
/**********************************************************************************/
|
|
32
40
|
/* */
|
|
@@ -70,20 +78,132 @@ export function view<TSchema extends ViewSchema>(
|
|
|
70
78
|
gl: GL,
|
|
71
79
|
program: WebGLProgram,
|
|
72
80
|
schema: TSchema,
|
|
73
|
-
|
|
81
|
+
{ signal }: ViewOptions = {},
|
|
74
82
|
): View<TSchema> {
|
|
83
|
+
// The sub-views are built without the signal and the view wires disposal
|
|
84
|
+
// once, so that `dispose()` and aborting do the same thing rather than each
|
|
85
|
+
// doing half of it.
|
|
86
|
+
const attributes = !schema.attributes ? undefined : attributeView(gl, program, schema.attributes)
|
|
87
|
+
const interleavedAttributes = !schema.interleavedAttributes
|
|
88
|
+
? undefined
|
|
89
|
+
: interleavedAttributeView(gl, program, schema.interleavedAttributes)
|
|
90
|
+
|
|
91
|
+
// Vertex arrays are made on demand rather than declared, so they are the one
|
|
92
|
+
// thing the view cannot dispose from the schema alone. Held until disposed —
|
|
93
|
+
// by the view or by the caller — and dropped either way, so a view that hands
|
|
94
|
+
// out an array per frame does not accumulate them.
|
|
95
|
+
const vertexArrays = new Set<VertexArrayMethods>()
|
|
96
|
+
|
|
97
|
+
const dispose = once(() => {
|
|
98
|
+
if (attributes) forEachObject(attributes, value => value.dispose())
|
|
99
|
+
if (interleavedAttributes) forEachObject(interleavedAttributes, value => value.dispose())
|
|
100
|
+
vertexArrays.forEach(vertexArray => vertexArray.dispose())
|
|
101
|
+
vertexArrays.clear()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
signal?.addEventListener('abort', dispose)
|
|
105
|
+
|
|
75
106
|
return {
|
|
76
107
|
uniforms: !schema.uniforms ? undefined : uniformView(gl, program, schema.uniforms),
|
|
77
|
-
attributes
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
attributes,
|
|
109
|
+
interleavedAttributes,
|
|
110
|
+
vao(participants: VertexArrayParticipant[]) {
|
|
111
|
+
const vertexArray = vaoView(gl, participants)
|
|
112
|
+
vertexArrays.add(vertexArray)
|
|
113
|
+
return {
|
|
114
|
+
...vertexArray,
|
|
115
|
+
dispose: once(() => {
|
|
116
|
+
vertexArrays.delete(vertexArray)
|
|
117
|
+
vertexArray.dispose()
|
|
118
|
+
}),
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
dispose,
|
|
84
122
|
} as View<TSchema>
|
|
85
123
|
}
|
|
86
124
|
|
|
125
|
+
/**********************************************************************************/
|
|
126
|
+
/* */
|
|
127
|
+
/* Vertex Array */
|
|
128
|
+
/* */
|
|
129
|
+
/**********************************************************************************/
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Bind the default vertex array, returning a disposer that puts the previous
|
|
133
|
+
* one back. The pointer calls name no destination, so anything applied without
|
|
134
|
+
* a vertex array of its own has to say which one it means — and a vertex array
|
|
135
|
+
* keeps what it is given, so writing into a caller's array by accident would
|
|
136
|
+
* outlive the call that did it.
|
|
137
|
+
*/
|
|
138
|
+
function bindDefaultVertexArray(gl: GL): () => void {
|
|
139
|
+
const feature = getVertexArrayObject(gl)
|
|
140
|
+
if (!feature) {
|
|
141
|
+
return () => {}
|
|
142
|
+
}
|
|
143
|
+
const previous = gl.getParameter(VERTEX_ARRAY_BINDING) as WebGLVertexArrayObject | null
|
|
144
|
+
feature.bindVertexArray(null)
|
|
145
|
+
return () => feature.bindVertexArray(previous)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A vertex array over the participants one draw needs.
|
|
150
|
+
*
|
|
151
|
+
* Where the context has vertex arrays, each participant writes itself in once,
|
|
152
|
+
* here, and `bind()` is a single `bindVertexArray`. Where it does not — WebGL1
|
|
153
|
+
* without `OES_vertex_array_object` — there is nowhere to keep the state, so
|
|
154
|
+
* `bind()` re-applies every participant and the disposer puts back what they
|
|
155
|
+
* displaced. Same contract, and the emulation is why the name still fits.
|
|
156
|
+
*/
|
|
157
|
+
export function vaoView(
|
|
158
|
+
gl: GL,
|
|
159
|
+
participants: VertexArrayParticipant[],
|
|
160
|
+
{ signal }: ViewOptions = {},
|
|
161
|
+
): VertexArrayMethods {
|
|
162
|
+
const feature = getVertexArrayObject(gl)
|
|
163
|
+
|
|
164
|
+
const methods: VertexArrayMethods = !feature
|
|
165
|
+
? {
|
|
166
|
+
bind() {
|
|
167
|
+
const restores = participants.map(participant => participant.applyToVertexArray())
|
|
168
|
+
participants.forEach(participant => participant.applyToContext?.())
|
|
169
|
+
return once(() => restores.forEach(restore => restore()))
|
|
170
|
+
},
|
|
171
|
+
unbind() {},
|
|
172
|
+
dispose() {},
|
|
173
|
+
}
|
|
174
|
+
: (() => {
|
|
175
|
+
const vertexArray = assertedNotNullish(feature.createVertexArray())
|
|
176
|
+
const previous = gl.getParameter(VERTEX_ARRAY_BINDING) as WebGLVertexArrayObject | null
|
|
177
|
+
feature.bindVertexArray(vertexArray)
|
|
178
|
+
// Discarding the restorers is the point: the state lives in this array
|
|
179
|
+
// now, and the array is thrown away whole.
|
|
180
|
+
for (const participant of participants) {
|
|
181
|
+
participant.applyToVertexArray()
|
|
182
|
+
}
|
|
183
|
+
feature.bindVertexArray(previous)
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
bind() {
|
|
187
|
+
const previousVertexArray = gl.getParameter(
|
|
188
|
+
VERTEX_ARRAY_BINDING,
|
|
189
|
+
) as WebGLVertexArrayObject | null
|
|
190
|
+
feature.bindVertexArray(vertexArray)
|
|
191
|
+
// Context state the array cannot hold — see applyToContext.
|
|
192
|
+
participants.forEach(participant => participant.applyToContext?.())
|
|
193
|
+
return once(() => feature.bindVertexArray(previousVertexArray))
|
|
194
|
+
},
|
|
195
|
+
unbind() {
|
|
196
|
+
feature.bindVertexArray(null)
|
|
197
|
+
},
|
|
198
|
+
dispose: once(() => feature.deleteVertexArray(vertexArray)),
|
|
199
|
+
}
|
|
200
|
+
})()
|
|
201
|
+
|
|
202
|
+
signal?.addEventListener('abort', () => methods.dispose())
|
|
203
|
+
|
|
204
|
+
return methods
|
|
205
|
+
}
|
|
206
|
+
|
|
87
207
|
/**********************************************************************************/
|
|
88
208
|
/* */
|
|
89
209
|
/* Uniform View */
|
|
@@ -246,17 +366,6 @@ function readDivisor(gl: GL, location: number): number {
|
|
|
246
366
|
: 0
|
|
247
367
|
}
|
|
248
368
|
|
|
249
|
-
// Wraps a restore so it runs once: a disposer is per bind() and undoing twice
|
|
250
|
-
// would put back state a later bind() is relying on
|
|
251
|
-
function once(restore: () => void): () => void {
|
|
252
|
-
let done = false
|
|
253
|
-
return () => {
|
|
254
|
-
if (done) return
|
|
255
|
-
done = true
|
|
256
|
-
restore()
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
369
|
export function attributeView<T extends AttributeSchema>(
|
|
261
370
|
gl: GL,
|
|
262
371
|
program: WebGLProgram,
|
|
@@ -266,17 +375,17 @@ export function attributeView<T extends AttributeSchema>(
|
|
|
266
375
|
const attributes = mapObject(
|
|
267
376
|
schema,
|
|
268
377
|
(
|
|
269
|
-
{
|
|
270
|
-
kind,
|
|
271
|
-
format,
|
|
272
|
-
normalized = false,
|
|
273
|
-
instanced,
|
|
274
|
-
buffer = assertedNotNullish(gl.createBuffer()),
|
|
275
|
-
},
|
|
378
|
+
{ kind, format, normalized = false, instanced, buffer: providedBuffer },
|
|
276
379
|
key,
|
|
277
380
|
): AttributeMethods => {
|
|
278
381
|
const name = toID(key)
|
|
279
382
|
|
|
383
|
+
// Whoever made the buffer deletes it. A schema can name a buffer that
|
|
384
|
+
// belongs to another view — the quad `compile.toQuad` shares across every
|
|
385
|
+
// program on a context, an attribute two programs step over — and
|
|
386
|
+
// deleting one of those would take it out from under its owner.
|
|
387
|
+
const buffer = providedBuffer ?? assertedNotNullish(gl.createBuffer())
|
|
388
|
+
|
|
280
389
|
const location = gl.getAttribLocation(program, name)
|
|
281
390
|
if (location < 0) {
|
|
282
391
|
throw new Error(`Attribute '${name}' not found`)
|
|
@@ -287,20 +396,35 @@ export function attributeView<T extends AttributeSchema>(
|
|
|
287
396
|
const glType = FORMAT_TO_GL_TYPE[resolvedFormat]
|
|
288
397
|
const isIntKind = kind.startsWith('i') || kind.startsWith('u')
|
|
289
398
|
|
|
399
|
+
function applyToVertexArray() {
|
|
400
|
+
// Snapshot before the change, restore in the disposer
|
|
401
|
+
const previousDivisor = readDivisor(gl, location)
|
|
402
|
+
// vertexAttribPointer captures whatever is bound to ARRAY_BUFFER when
|
|
403
|
+
// it runs, so this binding is the setup for the pointer, not a lasting
|
|
404
|
+
// part of the vertex array's state.
|
|
405
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
406
|
+
handleAttribute(gl, location, size, 0, 0, glType, isIntKind, normalized, instanced)
|
|
407
|
+
return once(() => {
|
|
408
|
+
getInstancedArrays(gl)?.vertexAttribDivisor(location, previousDivisor)
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
|
|
290
412
|
return {
|
|
291
413
|
buffer,
|
|
414
|
+
applyToVertexArray,
|
|
292
415
|
bind() {
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
296
|
-
handleAttribute(gl, location, size, 0, 0, glType, isIntKind, normalized, instanced)
|
|
416
|
+
const restoreVertexArray = bindDefaultVertexArray(gl)
|
|
417
|
+
const restore = applyToVertexArray()
|
|
297
418
|
return once(() => {
|
|
298
|
-
|
|
419
|
+
restore()
|
|
420
|
+
restoreVertexArray()
|
|
299
421
|
})
|
|
300
422
|
},
|
|
301
|
-
dispose() {
|
|
302
|
-
|
|
303
|
-
|
|
423
|
+
dispose: once(() => {
|
|
424
|
+
if (!providedBuffer) {
|
|
425
|
+
gl.deleteBuffer(buffer)
|
|
426
|
+
}
|
|
427
|
+
}),
|
|
304
428
|
set(data, usage = 'STATIC_DRAW') {
|
|
305
429
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
306
430
|
gl.bufferData(gl.ARRAY_BUFFER, data, gl[usage])
|
|
@@ -323,6 +447,73 @@ export function attributeView<T extends AttributeSchema>(
|
|
|
323
447
|
/* */
|
|
324
448
|
/**********************************************************************************/
|
|
325
449
|
|
|
450
|
+
/**
|
|
451
|
+
* The other place an attribute's value can come from: not a buffer, but the
|
|
452
|
+
* context's current generic vertex attribute values, which an attribute reads
|
|
453
|
+
* for every vertex when its array is disabled.
|
|
454
|
+
*
|
|
455
|
+
* A constant is a uniform in all but name — one value for the whole draw, set
|
|
456
|
+
* by a function chosen from the kind — so it gets the setters a uniform gets,
|
|
457
|
+
* off the same layout the buffer source uses.
|
|
458
|
+
*
|
|
459
|
+
* `vertexAttrib*` tops out at four floats and has no matrix form, so a layout
|
|
460
|
+
* key that is fine in a buffer can be impossible as a constant. That is a hard
|
|
461
|
+
* limit of the API rather than a choice, and it is worth saying so out loud at
|
|
462
|
+
* the point someone reaches for it.
|
|
463
|
+
*/
|
|
464
|
+
function constantSource(gl: GL, layout: InterleavedAttributeLayout[], locations: number[]) {
|
|
465
|
+
const setters = layout.map((entry, index) => {
|
|
466
|
+
const size = kindToSize(entry.kind)
|
|
467
|
+
const name = toID(entry.key)
|
|
468
|
+
if (isMatKind(entry.kind) || size > 4) {
|
|
469
|
+
throw new Error(
|
|
470
|
+
`'${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.`,
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
// The values are context state, not vertex-array state, so they are held
|
|
474
|
+
// here and written again on every bind — see applyToContext.
|
|
475
|
+
let values: number[] = []
|
|
476
|
+
const fn = gl[`vertexAttrib${size as 1 | 2 | 3 | 4}f`].bind(gl)
|
|
477
|
+
return {
|
|
478
|
+
name,
|
|
479
|
+
location: locations[index]!,
|
|
480
|
+
write: () => {
|
|
481
|
+
if (values.length) {
|
|
482
|
+
;(fn as (location: number, ...args: number[]) => void)(locations[index]!, ...values)
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
methods: {
|
|
486
|
+
set(...args: number[]) {
|
|
487
|
+
values = args
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
return Object.assign(Object.fromEntries(setters.map(setter => [setter.name, setter.methods])), {
|
|
494
|
+
applyToVertexArray() {
|
|
495
|
+
const restores = setters.map(({ location }) => {
|
|
496
|
+
const wasEnabled = gl.getVertexAttrib(location, gl.VERTEX_ATTRIB_ARRAY_ENABLED) as boolean
|
|
497
|
+
gl.disableVertexAttribArray(location)
|
|
498
|
+
return () => (wasEnabled ? gl.enableVertexAttribArray(location) : undefined)
|
|
499
|
+
})
|
|
500
|
+
return once(() => restores.forEach(restore => restore()))
|
|
501
|
+
},
|
|
502
|
+
applyToContext() {
|
|
503
|
+
setters.forEach(setter => setter.write())
|
|
504
|
+
},
|
|
505
|
+
bind() {
|
|
506
|
+
const restoreVertexArray = bindDefaultVertexArray(gl)
|
|
507
|
+
const restore = this.applyToVertexArray()
|
|
508
|
+
this.applyToContext()
|
|
509
|
+
return once(() => {
|
|
510
|
+
restore()
|
|
511
|
+
restoreVertexArray()
|
|
512
|
+
})
|
|
513
|
+
},
|
|
514
|
+
})
|
|
515
|
+
}
|
|
516
|
+
|
|
326
517
|
export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
|
|
327
518
|
gl: GL,
|
|
328
519
|
program: WebGLProgram,
|
|
@@ -330,7 +521,7 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
|
|
|
330
521
|
{ signal }: ViewOptions = {},
|
|
331
522
|
): InterleavedAttributeView<T> {
|
|
332
523
|
// Initialize interleaved attributes
|
|
333
|
-
const interleavedAttributes = mapObject(schema, ({ layout
|
|
524
|
+
const interleavedAttributes = mapObject(schema, ({ layout }) => {
|
|
334
525
|
// Increment number to keep track of offset
|
|
335
526
|
let index = 0
|
|
336
527
|
|
|
@@ -355,7 +546,7 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
|
|
|
355
546
|
const normalized = layout.normalized ?? false
|
|
356
547
|
index += size * FORMAT_BYTE_SIZE[resolvedFormat]
|
|
357
548
|
|
|
358
|
-
return () =>
|
|
549
|
+
return (divisor: 0 | 1) =>
|
|
359
550
|
handleAttribute(
|
|
360
551
|
gl,
|
|
361
552
|
location,
|
|
@@ -365,7 +556,7 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
|
|
|
365
556
|
glType,
|
|
366
557
|
isIntKind,
|
|
367
558
|
normalized,
|
|
368
|
-
|
|
559
|
+
divisor === 1,
|
|
369
560
|
)
|
|
370
561
|
})
|
|
371
562
|
|
|
@@ -375,91 +566,61 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
|
|
|
375
566
|
// Create a buffer
|
|
376
567
|
const buffer = assertedNotNullish(gl.createBuffer())
|
|
377
568
|
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
569
|
+
// This layout used to create a vertex array of its own, here, which is why
|
|
570
|
+
// the other participants in a draw had to be bound after it to be included
|
|
571
|
+
// — the array they landed in was whichever one happened to be current.
|
|
572
|
+
// Arrays are `vao()`'s job now; a layout is a layout and a buffer.
|
|
573
|
+
function applyToVertexArray(divisor: 0 | 1) {
|
|
574
|
+
const previousDivisors = locations.map(
|
|
575
|
+
location => [location, readDivisor(gl, location)] as const,
|
|
576
|
+
)
|
|
386
577
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
387
578
|
for (const handle of handles) {
|
|
388
|
-
handle()
|
|
389
|
-
}
|
|
390
|
-
feature.bindVertexArray(null)
|
|
391
|
-
vao = {
|
|
392
|
-
unbind() {
|
|
393
|
-
feature.bindVertexArray(null)
|
|
394
|
-
},
|
|
395
|
-
bind() {
|
|
396
|
-
feature.bindVertexArray(vertexArray)
|
|
397
|
-
},
|
|
398
|
-
dispose() {
|
|
399
|
-
feature.deleteVertexArray(vertexArray)
|
|
400
|
-
},
|
|
579
|
+
handle(divisor)
|
|
401
580
|
}
|
|
581
|
+
return once(() => {
|
|
582
|
+
const instancedArrays = getInstancedArrays(gl)
|
|
583
|
+
previousDivisors.forEach(([location, previous]) => {
|
|
584
|
+
instancedArrays?.vertexAttribDivisor(location, previous)
|
|
585
|
+
})
|
|
586
|
+
})
|
|
402
587
|
}
|
|
403
588
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
589
|
+
let constant: ReturnType<typeof constantSource> | undefined
|
|
590
|
+
|
|
591
|
+
/** One layout, one buffer, but a divisor per draw rather than per schema. */
|
|
592
|
+
function bufferSource(divisor: 0 | 1) {
|
|
593
|
+
const apply = () => applyToVertexArray(divisor)
|
|
594
|
+
return {
|
|
595
|
+
applyToVertexArray: apply,
|
|
596
|
+
bind() {
|
|
597
|
+
const restoreVertexArray = bindDefaultVertexArray(gl)
|
|
598
|
+
const restore = apply()
|
|
599
|
+
return once(() => {
|
|
600
|
+
restore()
|
|
601
|
+
restoreVertexArray()
|
|
602
|
+
})
|
|
603
|
+
},
|
|
409
604
|
}
|
|
410
605
|
}
|
|
411
606
|
|
|
412
607
|
return {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
:
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
for (const handle of handles) {
|
|
428
|
-
handle()
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return once(() => {
|
|
433
|
-
if (feature) {
|
|
434
|
-
feature.bindVertexArray(previousVertexArray)
|
|
435
|
-
}
|
|
436
|
-
// Without a vertex array the manual setup wrote divisors straight to
|
|
437
|
-
// the locations, so they are ours to put back
|
|
438
|
-
if (!vao) {
|
|
439
|
-
const instancedArrays = getInstancedArrays(gl)
|
|
440
|
-
previousDivisors.forEach(([location, divisor]) => {
|
|
441
|
-
instancedArrays?.vertexAttribDivisor(location, divisor)
|
|
442
|
-
})
|
|
443
|
-
}
|
|
444
|
-
})
|
|
445
|
-
},
|
|
446
|
-
unbind,
|
|
447
|
-
dispose() {
|
|
448
|
-
gl.deleteBuffer(buffer)
|
|
449
|
-
if (vao) {
|
|
450
|
-
vao.dispose()
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
set(value, usage = 'STATIC_DRAW') {
|
|
454
|
-
if (vao) {
|
|
455
|
-
vao.bind()
|
|
456
|
-
}
|
|
457
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
458
|
-
gl.bufferData(gl.ARRAY_BUFFER, value, gl[usage])
|
|
459
|
-
if (vao) {
|
|
460
|
-
vao.unbind()
|
|
461
|
-
}
|
|
608
|
+
buffer: Object.assign(bufferSource(0), {
|
|
609
|
+
/** The same buffer, stepped once per instance instead of once per vertex. */
|
|
610
|
+
perInstance: bufferSource(1),
|
|
611
|
+
set(value: Float32Array, usage: GLUsage = 'STATIC_DRAW') {
|
|
612
|
+
// Uploading is storage, not binding: ARRAY_BUFFER is not vertex-array
|
|
613
|
+
// state, so this touches no array, whichever one is bound.
|
|
614
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
615
|
+
gl.bufferData(gl.ARRAY_BUFFER, value, gl[usage])
|
|
616
|
+
},
|
|
617
|
+
}),
|
|
618
|
+
// Lazy: a layout key can be perfectly good in a buffer and impossible as
|
|
619
|
+
// a constant, and finding that out should wait until someone asks for one.
|
|
620
|
+
get constant() {
|
|
621
|
+
return (constant ??= constantSource(gl, layout, locations))
|
|
462
622
|
},
|
|
623
|
+
dispose: once(() => gl.deleteBuffer(buffer)),
|
|
463
624
|
} satisfies InterleavedAttributeMethods
|
|
464
625
|
})
|
|
465
626
|
|
|
@@ -485,21 +646,32 @@ export function bufferView<T extends BufferSchema>(
|
|
|
485
646
|
const buffers = mapObject(schema, ({ target = 'ARRAY_BUFFER', usage = 'STATIC_DRAW' }) => {
|
|
486
647
|
const buffer = assertedNotNullish(gl.createBuffer())
|
|
487
648
|
|
|
649
|
+
function applyToVertexArray() {
|
|
650
|
+
// Snapshot before the change, restore in the disposer. Restoring the
|
|
651
|
+
// previous buffer rather than null matters for ELEMENT_ARRAY_BUFFER,
|
|
652
|
+
// whose binding is recorded in the bound vertex array: clearing it
|
|
653
|
+
// would strip the index buffer from an array the caller may not own.
|
|
654
|
+
const previousBuffer = gl.getParameter(gl[`${target}_BINDING`]) as WebGLBuffer | null
|
|
655
|
+
gl.bindBuffer(gl[target], buffer)
|
|
656
|
+
return once(() => {
|
|
657
|
+
gl.bindBuffer(gl[target], previousBuffer)
|
|
658
|
+
})
|
|
659
|
+
}
|
|
660
|
+
|
|
488
661
|
return {
|
|
662
|
+
applyToVertexArray,
|
|
489
663
|
bind() {
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
gl.bindBuffer(gl[target], buffer)
|
|
664
|
+
// ELEMENT_ARRAY_BUFFER's binding belongs to the bound vertex array, so
|
|
665
|
+
// like the attributes this has to say which array it means. ARRAY_BUFFER
|
|
666
|
+
// is context state and unaffected either way.
|
|
667
|
+
const restoreVertexArray = bindDefaultVertexArray(gl)
|
|
668
|
+
const restore = applyToVertexArray()
|
|
496
669
|
return once(() => {
|
|
497
|
-
|
|
670
|
+
restore()
|
|
671
|
+
restoreVertexArray()
|
|
498
672
|
})
|
|
499
673
|
},
|
|
500
|
-
dispose()
|
|
501
|
-
gl.deleteBuffer(buffer)
|
|
502
|
-
},
|
|
674
|
+
dispose: once(() => gl.deleteBuffer(buffer)),
|
|
503
675
|
set(data: Float32Array) {
|
|
504
676
|
gl.bindBuffer(gl[target], buffer)
|
|
505
677
|
gl.bufferData(gl[target], data, gl[usage])
|
package/src/tag.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
ViewSchema,
|
|
19
19
|
ViewSchemaPartial,
|
|
20
20
|
} from './types'
|
|
21
|
-
import { createUpsertMap } from './utils'
|
|
21
|
+
import { createUpsertMap, once } from './utils'
|
|
22
22
|
|
|
23
23
|
export function glsl<TSlot extends GLSLSlot, const TSlots extends TSlot[]>(
|
|
24
24
|
template: TemplateStringsArray,
|
|
@@ -99,6 +99,11 @@ export function interleave<
|
|
|
99
99
|
export interface CompileOptions<TSchema extends ViewSchemaPartial> {
|
|
100
100
|
schema?: TSchema
|
|
101
101
|
webgl2?: boolean
|
|
102
|
+
/**
|
|
103
|
+
* Disposes what `compile` made: the program, and the buffers the view created
|
|
104
|
+
* for itself. Buffers named by the schema belong to whoever made them.
|
|
105
|
+
*/
|
|
106
|
+
signal?: AbortSignal
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
export function compile<
|
|
@@ -145,10 +150,21 @@ export function compile<
|
|
|
145
150
|
try {
|
|
146
151
|
const program = createProgram(gl, _vertex.template, _fragment.template)
|
|
147
152
|
|
|
153
|
+
// The same rule the views follow: `compile` made this program, so `compile`
|
|
154
|
+
// is what deletes it. A program handed in from outside would not be ours to
|
|
155
|
+
// delete, and there is no way to hand one in.
|
|
156
|
+
const resolvedView = view(gl, program, schema)
|
|
157
|
+
const dispose = once(() => {
|
|
158
|
+
resolvedView.dispose()
|
|
159
|
+
gl.deleteProgram(program)
|
|
160
|
+
})
|
|
161
|
+
options?.signal?.addEventListener('abort', dispose)
|
|
162
|
+
|
|
148
163
|
return {
|
|
149
164
|
program,
|
|
150
165
|
schema,
|
|
151
|
-
view:
|
|
166
|
+
view: resolvedView,
|
|
167
|
+
dispose,
|
|
152
168
|
vertex: _vertex.template,
|
|
153
169
|
fragment: _fragment.template,
|
|
154
170
|
} as unknown as Prettify<CompileResult<TVertex, TFragment, TSchema>>
|