@bigmistqke/view.gl 0.1.19 → 0.2.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/dist/{chunk-ZP4HP5H7.mjs → chunk-EYXOSM7B.mjs} +168 -67
- package/dist/chunk-EYXOSM7B.mjs.map +1 -0
- package/dist/gl.d.mts +1 -1
- package/dist/index.d.mts +13 -5
- package/dist/index.mjs +3 -1
- package/dist/tag.d.mts +1 -1
- package/dist/tag.mjs +1 -1
- package/dist/{types-DTSPQclW.d.mts → types-CyGyGi18.d.mts} +88 -5
- package/package.json +1 -1
- package/src/index.ts +243 -87
- package/src/types.ts +94 -4
- package/test/index.test.ts +228 -55
- package/test/mocks/webgl.ts +6 -0
- package/dist/chunk-ZP4HP5H7.mjs.map +0 -1
package/test/index.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
bufferView,
|
|
5
5
|
interleavedAttributeView,
|
|
6
6
|
uniformView,
|
|
7
|
+
vaoView,
|
|
7
8
|
view,
|
|
8
9
|
} from '../src/index'
|
|
9
10
|
import type {
|
|
@@ -83,10 +84,11 @@ describe('view', () => {
|
|
|
83
84
|
|
|
84
85
|
expect(result.interleavedAttributes).toBeDefined()
|
|
85
86
|
expect(result.interleavedAttributes!.data).toBeDefined()
|
|
86
|
-
expect(result.interleavedAttributes!.data.bind).toBeInstanceOf(Function)
|
|
87
|
-
expect(result.interleavedAttributes!.data.
|
|
87
|
+
expect(result.interleavedAttributes!.data.buffer.bind).toBeInstanceOf(Function)
|
|
88
|
+
expect(result.interleavedAttributes!.data.buffer.perInstance.bind).toBeInstanceOf(Function)
|
|
89
|
+
expect(result.interleavedAttributes!.data.constant.bind).toBeInstanceOf(Function)
|
|
88
90
|
expect(result.interleavedAttributes!.data.dispose).toBeInstanceOf(Function)
|
|
89
|
-
expect(result.interleavedAttributes!.data.set).toBeInstanceOf(Function)
|
|
91
|
+
expect(result.interleavedAttributes!.data.buffer.set).toBeInstanceOf(Function)
|
|
90
92
|
})
|
|
91
93
|
|
|
92
94
|
it('should create a view with buffers', () => {
|
|
@@ -166,7 +168,7 @@ describe('view', () => {
|
|
|
166
168
|
expect(result.attributes![a_position_symbol].bind).toBeInstanceOf(Function)
|
|
167
169
|
|
|
168
170
|
expect(result.interleavedAttributes![data_symbol]).toBeDefined()
|
|
169
|
-
expect(result.interleavedAttributes![data_symbol].bind).toBeInstanceOf(Function)
|
|
171
|
+
expect(result.interleavedAttributes![data_symbol].buffer.bind).toBeInstanceOf(Function)
|
|
170
172
|
|
|
171
173
|
expect(result.buffers![vertices_symbol]).toBeDefined()
|
|
172
174
|
expect(result.buffers![vertices_symbol].bind).toBeInstanceOf(Function)
|
|
@@ -778,10 +780,10 @@ describe('interleavedAttributeView', () => {
|
|
|
778
780
|
const interleavedAttributes = interleavedAttributeView(gl, program, schema)
|
|
779
781
|
|
|
780
782
|
expect(interleavedAttributes.vertexData).toBeDefined()
|
|
781
|
-
expect(interleavedAttributes.vertexData.bind).toBeInstanceOf(Function)
|
|
782
|
-
expect(interleavedAttributes.vertexData.
|
|
783
|
+
expect(interleavedAttributes.vertexData.buffer.bind).toBeInstanceOf(Function)
|
|
784
|
+
expect(interleavedAttributes.vertexData.buffer.perInstance.bind).toBeInstanceOf(Function)
|
|
783
785
|
expect(interleavedAttributes.vertexData.dispose).toBeInstanceOf(Function)
|
|
784
|
-
expect(interleavedAttributes.vertexData.set).toBeInstanceOf(Function)
|
|
786
|
+
expect(interleavedAttributes.vertexData.buffer.set).toBeInstanceOf(Function)
|
|
785
787
|
})
|
|
786
788
|
|
|
787
789
|
it('should return a disposer from bind that restores the default vertex array', () => {
|
|
@@ -804,7 +806,7 @@ describe('interleavedAttributeView', () => {
|
|
|
804
806
|
const previous = { id: 'previously-bound-vao' }
|
|
805
807
|
;(gl.getParameter as any).mockReturnValue(previous)
|
|
806
808
|
|
|
807
|
-
const unbind = interleavedAttributes.vertexData.bind()
|
|
809
|
+
const unbind = interleavedAttributes.vertexData.buffer.bind()
|
|
808
810
|
expect(unbind).toBeInstanceOf(Function)
|
|
809
811
|
|
|
810
812
|
;(gl.bindVertexArray as any).mockClear()
|
|
@@ -826,7 +828,7 @@ describe('interleavedAttributeView', () => {
|
|
|
826
828
|
|
|
827
829
|
const interleavedAttributes = interleavedAttributeView(gl, program, schema)
|
|
828
830
|
|
|
829
|
-
interleavedAttributes.vertexData.bind()
|
|
831
|
+
interleavedAttributes.vertexData.buffer.bind()
|
|
830
832
|
|
|
831
833
|
// Check vertexAttribPointer calls
|
|
832
834
|
const calls = (gl.vertexAttribPointer as any).mock.calls
|
|
@@ -850,23 +852,28 @@ describe('interleavedAttributeView', () => {
|
|
|
850
852
|
expect(calls[2][5]).toBe(24) // offset
|
|
851
853
|
})
|
|
852
854
|
|
|
853
|
-
it('
|
|
855
|
+
it('steps per instance or per vertex from the same buffer', () => {
|
|
856
|
+
// A divisor is a property of the draw, not of the data, so it is chosen
|
|
857
|
+
// here rather than declared in the schema.
|
|
854
858
|
const schema = {
|
|
855
859
|
instanceData: {
|
|
856
860
|
layout: [
|
|
857
861
|
{ key: 'a_instancePos', kind: 'vec3' },
|
|
858
862
|
{ key: 'a_instanceScale', kind: 'float' },
|
|
859
863
|
],
|
|
860
|
-
instanced: true,
|
|
861
864
|
},
|
|
862
865
|
} satisfies InterleavedAttributeSchema
|
|
863
866
|
|
|
864
867
|
const interleavedAttributes = interleavedAttributeView(gl, program, schema)
|
|
865
868
|
|
|
866
|
-
interleavedAttributes.instanceData.bind()
|
|
867
|
-
|
|
869
|
+
interleavedAttributes.instanceData.buffer.perInstance.bind()
|
|
868
870
|
expect(gl.vertexAttribDivisor).toHaveBeenCalledTimes(2)
|
|
869
871
|
expect(gl.vertexAttribDivisor).toHaveBeenCalledWith(expect.any(Number), 1)
|
|
872
|
+
|
|
873
|
+
vi.mocked(gl.vertexAttribDivisor).mockClear()
|
|
874
|
+
|
|
875
|
+
interleavedAttributes.instanceData.buffer.bind()
|
|
876
|
+
expect(gl.vertexAttribDivisor).toHaveBeenCalledWith(expect.any(Number), 0)
|
|
870
877
|
})
|
|
871
878
|
|
|
872
879
|
it('should handle integer attributes in layout', () => {
|
|
@@ -883,7 +890,7 @@ describe('interleavedAttributeView', () => {
|
|
|
883
890
|
|
|
884
891
|
const interleavedAttributes = interleavedAttributeView(gl, program, schema)
|
|
885
892
|
|
|
886
|
-
interleavedAttributes.data.bind()
|
|
893
|
+
interleavedAttributes.data.buffer.bind()
|
|
887
894
|
|
|
888
895
|
const floatCalls = (gl.vertexAttribPointer as any).mock.calls
|
|
889
896
|
const intCalls = (gl.vertexAttribIPointer as any).mock.calls
|
|
@@ -908,7 +915,7 @@ describe('interleavedAttributeView', () => {
|
|
|
908
915
|
} satisfies InterleavedAttributeSchema
|
|
909
916
|
|
|
910
917
|
const interleavedAttributes = interleavedAttributeView(gl, program, schema)
|
|
911
|
-
interleavedAttributes.data.bind()
|
|
918
|
+
interleavedAttributes.data.buffer.bind()
|
|
912
919
|
|
|
913
920
|
const calls = (gl.vertexAttribPointer as any).mock.calls
|
|
914
921
|
const stride = 16 // 8 + 4 + 4
|
|
@@ -961,25 +968,13 @@ describe('interleavedAttributeView', () => {
|
|
|
961
968
|
1, // color
|
|
962
969
|
])
|
|
963
970
|
|
|
964
|
-
interleavedAttributes.vertexData.set(data)
|
|
971
|
+
interleavedAttributes.vertexData.buffer.set(data)
|
|
965
972
|
expect(gl.bufferData).toHaveBeenCalledWith(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
|
|
966
973
|
})
|
|
967
974
|
|
|
968
|
-
it('
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
createVertexArrayOES: vi.fn(() => vertexArray),
|
|
972
|
-
bindVertexArrayOES: vi.fn(),
|
|
973
|
-
deleteVertexArrayOES: vi.fn(),
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Override the getExtension method for this test
|
|
977
|
-
const originalGetExtension = gl.getExtension
|
|
978
|
-
gl.getExtension = vi.fn((name: string) => {
|
|
979
|
-
if (name === 'OES_vertex_array_object') return vaoExt as any
|
|
980
|
-
return originalGetExtension.call(gl, name)
|
|
981
|
-
}) as any
|
|
982
|
-
|
|
975
|
+
it('does not create a vertex array of its own', () => {
|
|
976
|
+
// A layout used to make one here, which is why the other participants in a
|
|
977
|
+
// draw had to be bound after it to be included. Arrays are vao()'s job.
|
|
983
978
|
const schema = {
|
|
984
979
|
vertexData: {
|
|
985
980
|
layout: [{ key: 'a_position', kind: 'vec2' }],
|
|
@@ -987,26 +982,9 @@ describe('interleavedAttributeView', () => {
|
|
|
987
982
|
},
|
|
988
983
|
} satisfies InterleavedAttributeSchema
|
|
989
984
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
expect(vaoExt.createVertexArrayOES).toHaveBeenCalled()
|
|
993
|
-
expect(vaoExt.bindVertexArrayOES).toHaveBeenCalledWith(vertexArray)
|
|
994
|
-
expect(vaoExt.bindVertexArrayOES).toHaveBeenCalledWith(null) // unbind after setup
|
|
995
|
-
|
|
996
|
-
// Test bind
|
|
997
|
-
interleavedAttributes.vertexData.bind()
|
|
998
|
-
expect(vaoExt.bindVertexArrayOES).toHaveBeenCalledWith(vertexArray)
|
|
999
|
-
|
|
1000
|
-
// Test unbind
|
|
1001
|
-
interleavedAttributes.vertexData.unbind()
|
|
1002
|
-
expect(vaoExt.bindVertexArrayOES).toHaveBeenCalledWith(null)
|
|
1003
|
-
|
|
1004
|
-
// Test dispose
|
|
1005
|
-
interleavedAttributes.vertexData.dispose()
|
|
1006
|
-
expect(vaoExt.deleteVertexArrayOES).toHaveBeenCalledWith(vertexArray)
|
|
985
|
+
interleavedAttributeView(gl as any, program, schema)
|
|
1007
986
|
|
|
1008
|
-
|
|
1009
|
-
gl.getExtension = originalGetExtension
|
|
987
|
+
expect(gl.createVertexArray).not.toHaveBeenCalled()
|
|
1010
988
|
})
|
|
1011
989
|
|
|
1012
990
|
it('should dispose buffers', () => {
|
|
@@ -1085,13 +1063,13 @@ describe('interleavedAttributeView', () => {
|
|
|
1085
1063
|
const interleavedAttributes = interleavedAttributeView(gl, program, schema)
|
|
1086
1064
|
|
|
1087
1065
|
expect(interleavedAttributes[data_symbol]).toBeDefined()
|
|
1088
|
-
expect(interleavedAttributes[data_symbol].bind).toBeInstanceOf(Function)
|
|
1089
|
-
expect(interleavedAttributes[data_symbol].
|
|
1066
|
+
expect(interleavedAttributes[data_symbol].buffer.bind).toBeInstanceOf(Function)
|
|
1067
|
+
expect(interleavedAttributes[data_symbol].buffer.perInstance.bind).toBeInstanceOf(Function)
|
|
1090
1068
|
expect(interleavedAttributes[data_symbol].dispose).toBeInstanceOf(Function)
|
|
1091
|
-
expect(interleavedAttributes[data_symbol].set).toBeInstanceOf(Function)
|
|
1069
|
+
expect(interleavedAttributes[data_symbol].buffer.set).toBeInstanceOf(Function)
|
|
1092
1070
|
|
|
1093
1071
|
// Test binding
|
|
1094
|
-
interleavedAttributes[data_symbol].bind()
|
|
1072
|
+
interleavedAttributes[data_symbol].buffer.bind()
|
|
1095
1073
|
|
|
1096
1074
|
// Should bind buffer and set up attributes
|
|
1097
1075
|
expect(gl.bindBuffer).toHaveBeenCalled()
|
|
@@ -1119,7 +1097,7 @@ describe('interleavedAttributeView', () => {
|
|
|
1119
1097
|
expect(interleavedAttributes[vertexData]).toBeDefined()
|
|
1120
1098
|
|
|
1121
1099
|
// Test that it correctly calculates stride
|
|
1122
|
-
interleavedAttributes[vertexData].bind()
|
|
1100
|
+
interleavedAttributes[vertexData].buffer.bind()
|
|
1123
1101
|
|
|
1124
1102
|
const calls = (gl.vertexAttribPointer as any).mock.calls
|
|
1125
1103
|
// All calls should have the same stride (3+3+2)*4 = 32 bytes
|
|
@@ -1272,3 +1250,198 @@ describe('bufferView', () => {
|
|
|
1272
1250
|
expect(gl.bufferData).toHaveBeenCalledWith(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW)
|
|
1273
1251
|
})
|
|
1274
1252
|
})
|
|
1253
|
+
|
|
1254
|
+
describe('vaoView', () => {
|
|
1255
|
+
let gl: WebGL2RenderingContext
|
|
1256
|
+
let program: WebGLProgram
|
|
1257
|
+
beforeEach(() => {
|
|
1258
|
+
const mock = createMockCanvas()
|
|
1259
|
+
gl = mock.gl
|
|
1260
|
+
program = gl.createProgram()!
|
|
1261
|
+
})
|
|
1262
|
+
|
|
1263
|
+
function participants() {
|
|
1264
|
+
const attributes = attributeView(gl as any, program, {
|
|
1265
|
+
a_corner: { kind: 'vec2' },
|
|
1266
|
+
} satisfies AttributeSchema)
|
|
1267
|
+
const interleaved = interleavedAttributeView(gl as any, program, {
|
|
1268
|
+
a_instance: {
|
|
1269
|
+
layout: [{ key: 'a_position', kind: 'vec2' }],
|
|
1270
|
+
},
|
|
1271
|
+
} satisfies InterleavedAttributeSchema)
|
|
1272
|
+
const buffers = bufferView(gl as any, {
|
|
1273
|
+
b_index: { target: 'ELEMENT_ARRAY_BUFFER' },
|
|
1274
|
+
} satisfies BufferSchema)
|
|
1275
|
+
return { attributes, interleaved, buffers }
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
it('writes every participant into one array, at construction', () => {
|
|
1279
|
+
const { attributes, interleaved, buffers } = participants()
|
|
1280
|
+
const vertexArray = { id: 'vao' } as unknown as WebGLVertexArrayObject
|
|
1281
|
+
gl.createVertexArray = vi.fn(() => vertexArray) as any
|
|
1282
|
+
|
|
1283
|
+
const calls: string[] = []
|
|
1284
|
+
gl.bindVertexArray = vi.fn(v => calls.push(v === vertexArray ? 'bind' : 'unbind')) as any
|
|
1285
|
+
gl.vertexAttribPointer = vi.fn(() => calls.push('pointer')) as any
|
|
1286
|
+
gl.bindBuffer = vi.fn((target: number) =>
|
|
1287
|
+
calls.push(target === gl.ELEMENT_ARRAY_BUFFER ? 'indices' : 'array'),
|
|
1288
|
+
) as any
|
|
1289
|
+
|
|
1290
|
+
vaoView(gl as any, [
|
|
1291
|
+
attributes.a_corner,
|
|
1292
|
+
interleaved.a_instance.buffer.perInstance,
|
|
1293
|
+
buffers.b_index,
|
|
1294
|
+
])
|
|
1295
|
+
|
|
1296
|
+
// Every participant's state is written while the new array is bound —
|
|
1297
|
+
// that is the whole contract, and what call order used to decide.
|
|
1298
|
+
const bound = calls.indexOf('bind')
|
|
1299
|
+
const unbound = calls.lastIndexOf('unbind')
|
|
1300
|
+
expect(bound).toBeGreaterThanOrEqual(0)
|
|
1301
|
+
expect(calls.indexOf('pointer')).toBeGreaterThan(bound)
|
|
1302
|
+
expect(calls.indexOf('indices')).toBeGreaterThan(bound)
|
|
1303
|
+
expect(unbound).toBeGreaterThan(calls.lastIndexOf('indices'))
|
|
1304
|
+
})
|
|
1305
|
+
|
|
1306
|
+
it('binds and restores, and disposes the array', () => {
|
|
1307
|
+
const { attributes } = participants()
|
|
1308
|
+
const vertexArray = { id: 'vao' } as unknown as WebGLVertexArrayObject
|
|
1309
|
+
const foreign = { id: 'foreign' } as unknown as WebGLVertexArrayObject
|
|
1310
|
+
// Swapped before the first getVertexArrayObject call: on a context without
|
|
1311
|
+
// native vertex arrays the extension wrapper binds these once, up front.
|
|
1312
|
+
gl.createVertexArray = vi.fn(() => vertexArray) as any
|
|
1313
|
+
const bindVertexArray = vi.fn()
|
|
1314
|
+
gl.bindVertexArray = bindVertexArray as any
|
|
1315
|
+
gl.getParameter = vi.fn(() => foreign) as any
|
|
1316
|
+
|
|
1317
|
+
const vao = vaoView(gl as any, [attributes.a_corner])
|
|
1318
|
+
bindVertexArray.mockClear()
|
|
1319
|
+
|
|
1320
|
+
const restore = vao.bind()
|
|
1321
|
+
expect(bindVertexArray).toHaveBeenLastCalledWith(vertexArray)
|
|
1322
|
+
restore()
|
|
1323
|
+
expect(bindVertexArray).toHaveBeenLastCalledWith(foreign)
|
|
1324
|
+
|
|
1325
|
+
vao.dispose()
|
|
1326
|
+
expect(gl.deleteVertexArray).toHaveBeenCalledWith(vertexArray)
|
|
1327
|
+
})
|
|
1328
|
+
|
|
1329
|
+
it('re-applies each participant per bind when the context has no vertex arrays', () => {
|
|
1330
|
+
// WebGL1 without OES_vertex_array_object: nowhere to keep the state, so
|
|
1331
|
+
// the array is emulated by doing the work again on every bind.
|
|
1332
|
+
const mock = createMockGL()
|
|
1333
|
+
const gl1 = mock as any
|
|
1334
|
+
gl1.createVertexArray = undefined
|
|
1335
|
+
gl1.getExtension = vi.fn(() => null)
|
|
1336
|
+
const program1 = gl1.createProgram()
|
|
1337
|
+
|
|
1338
|
+
const attributes = attributeView(gl1, program1, {
|
|
1339
|
+
a_corner: { kind: 'vec2' },
|
|
1340
|
+
} satisfies AttributeSchema)
|
|
1341
|
+
|
|
1342
|
+
const vao = vaoView(gl1, [attributes.a_corner])
|
|
1343
|
+
expect(gl1.vertexAttribPointer).not.toHaveBeenCalled()
|
|
1344
|
+
|
|
1345
|
+
vao.bind()
|
|
1346
|
+
expect(gl1.vertexAttribPointer).toHaveBeenCalledTimes(1)
|
|
1347
|
+
vao.bind()
|
|
1348
|
+
expect(gl1.vertexAttribPointer).toHaveBeenCalledTimes(2)
|
|
1349
|
+
})
|
|
1350
|
+
})
|
|
1351
|
+
|
|
1352
|
+
describe('binding a participant on its own', () => {
|
|
1353
|
+
let gl: WebGL2RenderingContext
|
|
1354
|
+
let program: WebGLProgram
|
|
1355
|
+
beforeEach(() => {
|
|
1356
|
+
const mock = createMockCanvas()
|
|
1357
|
+
gl = mock.gl
|
|
1358
|
+
program = gl.createProgram()!
|
|
1359
|
+
})
|
|
1360
|
+
|
|
1361
|
+
it('targets the default vertex array, not whichever one is current', () => {
|
|
1362
|
+
// The pointer calls name no destination. Without binding first, this would
|
|
1363
|
+
// edit a caller's array — and an array keeps what it is given, so the
|
|
1364
|
+
// damage would outlive the call.
|
|
1365
|
+
const foreign = { id: 'foreign' } as unknown as WebGLVertexArrayObject
|
|
1366
|
+
gl.getParameter = vi.fn(() => foreign) as any
|
|
1367
|
+
const bindVertexArray = vi.fn()
|
|
1368
|
+
gl.bindVertexArray = bindVertexArray as any
|
|
1369
|
+
|
|
1370
|
+
const attributes = attributeView(gl as any, program, {
|
|
1371
|
+
a_corner: { kind: 'vec2' },
|
|
1372
|
+
} satisfies AttributeSchema)
|
|
1373
|
+
|
|
1374
|
+
const restore = attributes.a_corner.bind()
|
|
1375
|
+
expect(bindVertexArray).toHaveBeenCalledWith(null)
|
|
1376
|
+
restore()
|
|
1377
|
+
expect(bindVertexArray).toHaveBeenLastCalledWith(foreign)
|
|
1378
|
+
})
|
|
1379
|
+
})
|
|
1380
|
+
|
|
1381
|
+
describe('the constant source', () => {
|
|
1382
|
+
let gl: WebGL2RenderingContext
|
|
1383
|
+
let program: WebGLProgram
|
|
1384
|
+
beforeEach(() => {
|
|
1385
|
+
const mock = createMockCanvas()
|
|
1386
|
+
gl = mock.gl
|
|
1387
|
+
program = gl.createProgram()!
|
|
1388
|
+
})
|
|
1389
|
+
|
|
1390
|
+
const schema = {
|
|
1391
|
+
a_instance: {
|
|
1392
|
+
layout: [
|
|
1393
|
+
{ key: 'a_pos', kind: 'vec2' },
|
|
1394
|
+
{ key: 'a_size', kind: 'float' },
|
|
1395
|
+
],
|
|
1396
|
+
},
|
|
1397
|
+
} satisfies InterleavedAttributeSchema
|
|
1398
|
+
|
|
1399
|
+
it('gives the layout per-key setters, like a uniform', () => {
|
|
1400
|
+
const { a_instance } = interleavedAttributeView(gl as any, program, schema)
|
|
1401
|
+
|
|
1402
|
+
expect(a_instance.constant.a_pos.set).toBeInstanceOf(Function)
|
|
1403
|
+
expect(a_instance.constant.a_size.set).toBeInstanceOf(Function)
|
|
1404
|
+
})
|
|
1405
|
+
|
|
1406
|
+
it('disables the arrays, so the attributes read the constants', () => {
|
|
1407
|
+
const { a_instance } = interleavedAttributeView(gl as any, program, schema)
|
|
1408
|
+
|
|
1409
|
+
a_instance.constant.applyToVertexArray()
|
|
1410
|
+
|
|
1411
|
+
expect(gl.disableVertexAttribArray).toHaveBeenCalledTimes(2)
|
|
1412
|
+
})
|
|
1413
|
+
|
|
1414
|
+
it('writes the values on every bind, not once into the array', () => {
|
|
1415
|
+
// The enable flag is vertex-array state; the values are context state, and
|
|
1416
|
+
// no array can keep them. Applying them once at construction would leave a
|
|
1417
|
+
// later draw showing whatever the previous one set.
|
|
1418
|
+
const { a_instance } = interleavedAttributeView(gl as any, program, schema)
|
|
1419
|
+
a_instance.constant.a_pos.set(1, 2)
|
|
1420
|
+
a_instance.constant.a_size.set(4)
|
|
1421
|
+
|
|
1422
|
+
const vao = vaoView(gl as any, [a_instance.constant])
|
|
1423
|
+
expect(gl.vertexAttrib2f).not.toHaveBeenCalled()
|
|
1424
|
+
|
|
1425
|
+
vao.bind()
|
|
1426
|
+
expect(gl.vertexAttrib2f).toHaveBeenCalledWith(expect.any(Number), 1, 2)
|
|
1427
|
+
expect(gl.vertexAttrib1f).toHaveBeenCalledWith(expect.any(Number), 4)
|
|
1428
|
+
|
|
1429
|
+
a_instance.constant.a_pos.set(7, 8)
|
|
1430
|
+
vao.bind()
|
|
1431
|
+
expect(gl.vertexAttrib2f).toHaveBeenLastCalledWith(expect.any(Number), 7, 8)
|
|
1432
|
+
})
|
|
1433
|
+
|
|
1434
|
+
it('refuses a kind vertexAttrib* cannot express, when asked for one', () => {
|
|
1435
|
+
// A hard limit of the API, not a choice: vertexAttrib* takes at most four
|
|
1436
|
+
// floats and has no matrix form. The buffer source handles it fine, so the
|
|
1437
|
+
// complaint waits until someone reaches for a constant.
|
|
1438
|
+
const matrix = {
|
|
1439
|
+
a_instance: { layout: [{ key: 'a_model', kind: 'mat4' }] },
|
|
1440
|
+
} satisfies InterleavedAttributeSchema
|
|
1441
|
+
|
|
1442
|
+
const { a_instance } = interleavedAttributeView(gl as any, program, matrix)
|
|
1443
|
+
|
|
1444
|
+
expect(() => a_instance.buffer.bind()).not.toThrow()
|
|
1445
|
+
expect(() => a_instance.constant).toThrow(/cannot be a constant attribute/)
|
|
1446
|
+
})
|
|
1447
|
+
})
|
package/test/mocks/webgl.ts
CHANGED
|
@@ -134,6 +134,12 @@ export function createMockGL() {
|
|
|
134
134
|
// which tests override where the previous value is what is under test.
|
|
135
135
|
getParameter: vi.fn(() => null),
|
|
136
136
|
getVertexAttrib: vi.fn(() => 0),
|
|
137
|
+
disableVertexAttribArray: vi.fn(),
|
|
138
|
+
vertexAttrib1f: vi.fn(),
|
|
139
|
+
vertexAttrib2f: vi.fn(),
|
|
140
|
+
vertexAttrib3f: vi.fn(),
|
|
141
|
+
vertexAttrib4f: vi.fn(),
|
|
142
|
+
VERTEX_ATTRIB_ARRAY_ENABLED: 0x8622,
|
|
137
143
|
|
|
138
144
|
// Uniform methods
|
|
139
145
|
getUniformLocation: vi.fn((program: MockWebGLProgram, name: string) => {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["import type {\n AttributeKind,\n FramebufferDefinition,\n GL,\n KIND_TO_SIZE_MAP,\n KIND_TO_UNIFORM_FN_NAME_MAP,\n TextureDefinition,\n UniformKind,\n} from './types'\n\nexport function assertedNotNullish<T>(value: T, message?: string): NonNullable<T> {\n if (value === undefined || value === null) throw new Error(message)\n return value\n}\n\n/**********************************************************************************/\n/* */\n/* Kind To Uniform Name */\n/* */\n/**********************************************************************************/\n\nexport const kindToUniformFnName = <T extends UniformKind>(\n kind: T,\n): KIND_TO_UNIFORM_FN_NAME_MAP[T] => {\n switch (kind[0]) {\n // mat\n case 'm':\n return ('Matrix' + kind.slice(3) + 'fv') as KIND_TO_UNIFORM_FN_NAME_MAP[T]\n // sampler/booleans/integer\n case 's':\n case 'b':\n case 'i':\n return ((kind.match(/\\d/)?.[0] ?? '1') + 'i') as KIND_TO_UNIFORM_FN_NAME_MAP[T]\n // unsigned integers\n case 'u':\n return ((kind[4] || '1') + 'ui') as KIND_TO_UNIFORM_FN_NAME_MAP[T]\n // vec\n case 'v':\n return (kind[3] + 'f') as KIND_TO_UNIFORM_FN_NAME_MAP[T]\n default:\n switch (kind) {\n case 'float':\n return '1f' as KIND_TO_UNIFORM_FN_NAME_MAP[T]\n case 'uint':\n return '1ui' as KIND_TO_UNIFORM_FN_NAME_MAP[T]\n default:\n return '1i' as KIND_TO_UNIFORM_FN_NAME_MAP[T]\n }\n }\n}\n\n/**********************************************************************************/\n/* */\n/* Kind To Size */\n/* */\n/**********************************************************************************/\n\nexport const kindToSize = <T extends UniformKind>(kind: T): KIND_TO_SIZE_MAP[T] => {\n switch (kind[0]) {\n case 'm':\n const [a, b] = kind.match(/\\d+/g)!.map(Number) as [number, number?]\n return (a * (b ?? a)) as KIND_TO_SIZE_MAP[T]\n case 'v':\n case 'i':\n case 'b':\n case 'u':\n const match = kind.match(/\\d/) as [string] | undefined\n return (match ? +match[0] : 1) as KIND_TO_SIZE_MAP[T]\n default:\n return 1 as KIND_TO_SIZE_MAP[T]\n }\n}\n\n/**********************************************************************************/\n/* */\n/* Guards */\n/* */\n/**********************************************************************************/\n\nexport const isMatKind = <\n T extends UniformKind | AttributeKind,\n TPrefix extends string,\n TPostfix extends string,\n>(\n kind: T,\n): kind is T & `${TPrefix}mat${TPostfix}` => kind.includes('mat')\n\nexport const isSamplerKind = <TPrefix extends string, TPostfix extends string>(\n kind: UniformKind,\n): kind is UniformKind & `${TPrefix}sampler${TPostfix}` => kind.includes('sampler')\n\nexport const isVecKind = <TPrefix extends string, TPostfix extends string>(\n kind: UniformKind,\n): kind is UniformKind & `${TPrefix}vec${TPostfix}` => kind.includes('vec')\n\n/**********************************************************************************/\n/* */\n/* Create Texture */\n/* */\n/**********************************************************************************/\n\nexport function createTexture(\n gl: GL,\n {\n target = 'TEXTURE_2D',\n internalFormat = 'RGBA',\n format = 'RGBA',\n type = 'UNSIGNED_BYTE',\n minFilter = 'NEAREST',\n magFilter = 'NEAREST',\n wrapS = 'CLAMP_TO_EDGE',\n wrapT = 'CLAMP_TO_EDGE',\n width,\n height,\n }: TextureDefinition,\n data?: ArrayBufferView | null,\n): WebGLTexture {\n const texture = gl.createTexture()\n\n function getTextureConstant(name: string) {\n if (!(name in gl)) {\n throw new Error(`Attempted to create webgl2-only texture (${name}) in webgl1`)\n }\n return gl[name as keyof typeof gl] as any\n }\n\n gl.bindTexture(gl[target], texture)\n gl.texImage2D(\n gl[target],\n 0,\n getTextureConstant(internalFormat),\n width,\n height,\n 0,\n getTextureConstant(format),\n getTextureConstant(type),\n data ?? null,\n )\n\n console.log(minFilter)\n // Set texture parameters\n gl.texParameteri(gl[target], gl.TEXTURE_MIN_FILTER, gl[minFilter])\n gl.texParameteri(gl[target], gl.TEXTURE_MAG_FILTER, gl[magFilter])\n gl.texParameteri(gl[target], gl.TEXTURE_WRAP_S, gl[wrapS])\n gl.texParameteri(gl[target], gl.TEXTURE_WRAP_T, gl[wrapT])\n\n return texture\n}\n\n/**********************************************************************************/\n/* */\n/* Create Framebuffer */\n/* */\n/**********************************************************************************/\n\nconst FRAMEBUFFER_ATTACHMENT_MAP = {\n color: 'COLOR_ATTACHMENT0',\n depth: 'DEPTH_ATTACHMENT',\n stencil: 'STENCIL_ATTACHMENT',\n depthStencil: 'DEPTH_STENCIL_ATTACHMENT',\n} as const\n\nclass FramebufferError extends Error {\n constructor(gl: GL, status: number) {\n let errorMessage = `Framebuffer '${name}' not complete. Status: `\n switch (status) {\n case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:\n errorMessage += 'FRAMEBUFFER_INCOMPLETE_ATTACHMENT'\n break\n case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:\n errorMessage += 'FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT'\n break\n case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:\n errorMessage += 'FRAMEBUFFER_INCOMPLETE_DIMENSIONS'\n break\n case gl.FRAMEBUFFER_UNSUPPORTED:\n errorMessage += 'FRAMEBUFFER_UNSUPPORTED'\n break\n default:\n errorMessage += `Unknown (${status})`\n }\n super(errorMessage)\n }\n}\n\nexport function createFramebuffer(\n gl: GL,\n { attachment, texture, ...options }: FramebufferDefinition,\n) {\n // Create texture for the framebuffer\n texture ??= createTexture(gl, options)\n\n // Create framebuffer\n const framebuffer = assertedNotNullish(\n gl.createFramebuffer(),\n `Failed to create framebuffer: ${name}`,\n )\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)\n\n gl.framebufferTexture2D(\n gl.FRAMEBUFFER,\n // Determine attachment point based on attachment kind\n gl[FRAMEBUFFER_ATTACHMENT_MAP[attachment]],\n gl.TEXTURE_2D,\n texture,\n 0,\n )\n\n // Check framebuffer completeness\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER)\n if (status !== gl.FRAMEBUFFER_COMPLETE) {\n throw new FramebufferError(gl, status)\n }\n\n return {\n texture,\n framebuffer,\n }\n}\n\n/**********************************************************************************/\n/* */\n/* Create Upsert */\n/* */\n/**********************************************************************************/\n\ntype UpsertMapKind<T, U> = Map<T, U> | (T extends WeakKey ? WeakMap<T, U> : never)\n\nexport function createUpsertMap<T extends UpsertMapKind<any, any> = Map<any, any>>(\n constructor?: new () => T,\n) {\n const map = new (constructor ?? Map)() as T\n return Object.assign(map, {\n getOrInsert(\n key: T extends UpsertMapKind<infer U, any> ? U : never,\n value: T extends UpsertMapKind<any, infer U> ? () => U : never,\n ) {\n let result = map.get(key)\n if (result) {\n return result\n }\n result = value()\n map.set(key, result)\n return result\n },\n })\n}\n\nexport function forEachObject<T extends Record<string, any>>(\n value: T,\n callback: (value: T[keyof T], key: keyof T, index: number) => void,\n) {\n let index = 0\n for (const key in value) {\n callback(value[key], key, index)\n index++\n }\n for (const key of Object.getOwnPropertySymbols(value)) {\n callback(value[key as keyof T], key as keyof T, index)\n index++\n }\n}\n\nexport function mapObject<T extends Record<string, any>, TReturn>(\n value: T,\n callback: (value: T[keyof T], key: keyof T, index: number) => TReturn,\n): { [TKey in keyof T]: TReturn } {\n const result = {} as { [TKey in keyof T]: TReturn }\n\n forEachObject(value, (value, key, index) => {\n result[key] = callback(value, key, index)\n })\n\n return result\n}\n","import { getInstancedArrays, getVertexArrayObject } from './gl'\nimport type {\n AttributeFormat,\n AttributeKind,\n AttributeMethods,\n AttributeSchema,\n AttributeView,\n BufferSchema,\n BufferView,\n GL,\n UniformView as InferUniformView,\n InterleavedAttributeMethods,\n InterleavedAttributeSchema,\n InterleavedAttributeView,\n UniformSchema,\n View,\n ViewOptions,\n ViewSchema,\n} from './types'\nimport {\n isSamplerKind,\n kindToUniformFnName,\n isMatKind,\n assertedNotNullish,\n kindToSize,\n forEachObject,\n} from './utils'\nimport { createUpsertMap, mapObject } from './utils'\nexport * from './types'\n\n/**********************************************************************************/\n/* */\n/* To ID */\n/* */\n/**********************************************************************************/\n\nlet index = 0\nconst PREFIX = 'VIEW_GL_ALIAS'\n// Firefox has currently a bug where it can not handle symbol as WeakRef / key for WeakMap\n// see https://bugzilla.mozilla.org/show_bug.cgi?id=1710433\nconst HAS_WEAK_SYMBOL = (() => {\n try {\n const symbol = Symbol()\n new WeakMap().set(symbol, '')\n return true\n } catch {\n return false\n }\n})()\n// NOTE: fallback to Map will currently cause a minor memory leak.\nconst SYMBOL_MAP = createUpsertMap(HAS_WEAK_SYMBOL ? WeakMap<symbol, string> : Map<symbol, string>)\n\nexport function toID(key: string | number | symbol) {\n if (typeof key === 'string') {\n return key\n }\n if (typeof key === 'symbol') {\n return SYMBOL_MAP.getOrInsert(key, () => `${PREFIX}_${index++}`)\n }\n return key.toString()\n}\n\n/**********************************************************************************/\n/* */\n/* View */\n/* */\n/**********************************************************************************/\n\nexport function view<TSchema extends ViewSchema>(\n gl: GL,\n program: WebGLProgram,\n schema: TSchema,\n options?: ViewOptions,\n): View<TSchema> {\n return {\n uniforms: !schema.uniforms ? undefined : uniformView(gl, program, schema.uniforms),\n attributes: !schema.attributes\n ? undefined\n : attributeView(gl, program, schema.attributes, options),\n interleavedAttributes: !schema.interleavedAttributes\n ? undefined\n : interleavedAttributeView(gl, program, schema.interleavedAttributes, options),\n buffers: !schema.buffers ? undefined : bufferView(gl, schema.buffers),\n } as View<TSchema>\n}\n\n/**********************************************************************************/\n/* */\n/* Uniform View */\n/* */\n/**********************************************************************************/\n\nexport function uniformView<T extends UniformSchema>(\n gl: GL,\n program: WebGLProgram,\n schema: T,\n): InferUniformView<T> {\n // @ts-expect-error - conditional return type based on size is hard to infer\n return mapObject(schema, ({ kind, size }, key) => {\n const name = toID(key)\n\n if (isSamplerKind(kind)) {\n const location = gl.getUniformLocation(program, name)\n return {\n set(arg: number) {\n gl.uniform1i(location, arg)\n },\n }\n }\n\n // Array uniform - support both bulk set and indexed access\n if (size) {\n const bulkFnName = `uniform${kindToUniformFnName(kind)}v`\n // @ts-ignore FIX WEBGL/WEBGL2 TYPES\n const bulkFn = gl[bulkFnName].bind(gl)\n const bulkLocation = gl.getUniformLocation(program, name)\n\n const elementFnName = `uniform${kindToUniformFnName(kind)}`\n // @ts-ignore FIX WEBGL/WEBGL2 TYPES\n const elementFn = gl[elementFnName].bind(gl)\n\n // Build array of element setters\n const elements: Array<{ set(...args: any[]): void }> = []\n for (let i = 0; i < size; i++) {\n const location = gl.getUniformLocation(program, `${name}[${i}]`)\n elements[i] = {\n set(...args: any[]) {\n elementFn(location, ...args)\n },\n }\n }\n\n // Return array-like object with bulk set\n return Object.assign(elements, {\n set(arg: Float32Array) {\n bulkFn(bulkLocation, arg)\n },\n })\n }\n\n // Non-array uniform\n const location = gl.getUniformLocation(program, name)\n const fnName = `uniform${kindToUniformFnName(kind)}`\n\n // @ts-ignore FIX WEBGL/WEBGL2 TYPES\n const fn = gl[fnName].bind(gl)\n\n if (isMatKind(kind)) {\n return {\n set(...args: any[]) {\n fn(location, false, args[0])\n },\n }\n }\n\n return {\n set(...args: any[]) {\n fn(location, ...args)\n },\n }\n })\n}\n\n/**********************************************************************************/\n/* */\n/* Attribute View */\n/* */\n/**********************************************************************************/\n\ntype GLType =\n | 'FLOAT'\n | 'INT'\n | 'UNSIGNED_INT'\n | 'SHORT'\n | 'UNSIGNED_SHORT'\n | 'BYTE'\n | 'UNSIGNED_BYTE'\n\nconst FORMAT_TO_GL_TYPE: Record<AttributeFormat, GLType> = {\n float32: 'FLOAT',\n int32: 'INT',\n uint32: 'UNSIGNED_INT',\n int16: 'SHORT',\n uint16: 'UNSIGNED_SHORT',\n int8: 'BYTE',\n uint8: 'UNSIGNED_BYTE',\n}\n\nconst FORMAT_BYTE_SIZE: Record<AttributeFormat, number> = {\n float32: 4,\n int32: 4,\n uint32: 4,\n int16: 2,\n uint16: 2,\n int8: 1,\n uint8: 1,\n}\n\nfunction defaultFormat(kind: AttributeKind): AttributeFormat {\n if (kind.startsWith('u')) return 'uint32'\n if (kind.startsWith('i')) return 'int32'\n return 'float32'\n}\n\n// Shared attribute helper functions\n// between attributeView and interleavedAttributeView\nfunction handleAttribute(\n gl: GL,\n location: number,\n size: number,\n stride: number,\n offset: number,\n glType: GLType,\n isIntegerKind: boolean,\n normalized: boolean,\n instanced?: boolean,\n) {\n gl.enableVertexAttribArray(location)\n if (isIntegerKind) {\n ;(gl as WebGL2RenderingContext).vertexAttribIPointer(location, size, gl[glType], stride, offset)\n } else {\n gl.vertexAttribPointer(location, size, gl[glType], normalized, stride, offset)\n }\n\n // Always stated, never inherited: the divisor belongs to the location rather\n // than to the program, so a location left at 1 by an earlier instanced\n // attribute would feed one value to every vertex here.\n // Get instanced-arrays-feature: extension if webgl, gl if webgl2\n const instancedArrays = getInstancedArrays(gl)\n if (instanced) {\n assertedNotNullish(instancedArrays).vertexAttribDivisor(location, 1)\n } else if (instancedArrays) {\n instancedArrays.vertexAttribDivisor(location, 0)\n }\n}\n\n// Same enum in webgl2 and in the webgl1 extensions, which expose no constants\nconst VERTEX_ATTRIB_ARRAY_DIVISOR = 0x88fe\nconst VERTEX_ARRAY_BINDING = 0x85b5\n\n// Reads the divisor currently attached to a location, 0 where instancing is\n// unavailable and nothing can have set one\nfunction readDivisor(gl: GL, location: number): number {\n return getInstancedArrays(gl)\n ? (gl.getVertexAttrib(location, VERTEX_ATTRIB_ARRAY_DIVISOR) as number)\n : 0\n}\n\n// Wraps a restore so it runs once: a disposer is per bind() and undoing twice\n// would put back state a later bind() is relying on\nfunction once(restore: () => void): () => void {\n let done = false\n return () => {\n if (done) return\n done = true\n restore()\n }\n}\n\nexport function attributeView<T extends AttributeSchema>(\n gl: GL,\n program: WebGLProgram,\n schema: T,\n { signal }: ViewOptions = {},\n): AttributeView<T> {\n const attributes = mapObject(\n schema,\n (\n {\n kind,\n format,\n normalized = false,\n instanced,\n buffer = assertedNotNullish(gl.createBuffer()),\n },\n key,\n ): AttributeMethods => {\n const name = toID(key)\n\n const location = gl.getAttribLocation(program, name)\n if (location < 0) {\n throw new Error(`Attribute '${name}' not found`)\n }\n\n const size = kindToSize(kind)\n const resolvedFormat = format ?? defaultFormat(kind)\n const glType = FORMAT_TO_GL_TYPE[resolvedFormat]\n const isIntKind = kind.startsWith('i') || kind.startsWith('u')\n\n return {\n buffer,\n bind() {\n // Snapshot before the change, restore in the disposer\n const previousDivisor = readDivisor(gl, location)\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n handleAttribute(gl, location, size, 0, 0, glType, isIntKind, normalized, instanced)\n return once(() => {\n getInstancedArrays(gl)?.vertexAttribDivisor(location, previousDivisor)\n })\n },\n dispose() {\n gl.deleteBuffer(buffer)\n },\n set(data, usage = 'STATIC_DRAW') {\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(gl.ARRAY_BUFFER, data, gl[usage])\n return this\n },\n }\n },\n )\n\n signal?.addEventListener('abort', function dispose() {\n forEachObject(attributes, value => value.dispose())\n })\n\n return attributes\n}\n\n/**********************************************************************************/\n/* */\n/* Interleaved Attribute View */\n/* */\n/**********************************************************************************/\n\nexport function interleavedAttributeView<T extends InterleavedAttributeSchema>(\n gl: GL,\n program: WebGLProgram,\n schema: T,\n { signal }: ViewOptions = {},\n): InterleavedAttributeView<T> {\n // Initialize interleaved attributes\n const interleavedAttributes = mapObject(schema, ({ layout, instanced }) => {\n // Increment number to keep track of offset\n let index = 0\n\n // Calculate layout information\n const locations: number[] = []\n const handles = layout.map(layout => {\n const name = toID(layout.key)\n\n const location = gl.getAttribLocation(program, name)\n\n if (location < 0) {\n throw new Error(`Attribute '${name}' not found`)\n }\n\n locations.push(location)\n\n const size = kindToSize(layout.kind)\n const offset = index\n const resolvedFormat = layout.format ?? defaultFormat(layout.kind)\n const glType = FORMAT_TO_GL_TYPE[resolvedFormat]\n const isIntKind = layout.kind.startsWith('i') || layout.kind.startsWith('u')\n const normalized = layout.normalized ?? false\n index += size * FORMAT_BYTE_SIZE[resolvedFormat]\n\n return () =>\n handleAttribute(\n gl,\n location,\n size,\n stride,\n offset,\n glType,\n isIntKind,\n normalized,\n instanced,\n )\n })\n\n // Set stride to final index\n const stride = index\n\n // Create a buffer\n const buffer = assertedNotNullish(gl.createBuffer())\n\n // Create VAO to cache attribute state\n let vao: { unbind(): void; bind(): void; dispose(): void } | undefined = undefined\n\n // Get VAO-feature: extension if webgl1, gl if webgl2\n const feature = getVertexArrayObject(gl)\n if (feature) {\n const vertexArray = feature.createVertexArray()\n feature.bindVertexArray(vertexArray)\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n for (const handle of handles) {\n handle()\n }\n feature.bindVertexArray(null)\n vao = {\n unbind() {\n feature.bindVertexArray(null)\n },\n bind() {\n feature.bindVertexArray(vertexArray)\n },\n dispose() {\n feature.deleteVertexArray(vertexArray)\n },\n }\n }\n\n // A selected vertex array stays selected for every later draw, which would\n // then write its own attributes into this one\n const unbind = () => {\n if (vao) {\n vao.unbind()\n }\n }\n\n return {\n bind() {\n // Snapshot before the change, restore in the disposer\n const previousVertexArray = feature\n ? (gl.getParameter(VERTEX_ARRAY_BINDING) as WebGLVertexArrayObject | null)\n : null\n const previousDivisors = vao\n ? []\n : locations.map(location => [location, readDivisor(gl, location)] as const)\n\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n if (vao) {\n vao.bind()\n } else {\n // Fallback: manual attribute setup\n for (const handle of handles) {\n handle()\n }\n }\n\n return once(() => {\n if (feature) {\n feature.bindVertexArray(previousVertexArray)\n }\n // Without a vertex array the manual setup wrote divisors straight to\n // the locations, so they are ours to put back\n if (!vao) {\n const instancedArrays = getInstancedArrays(gl)\n previousDivisors.forEach(([location, divisor]) => {\n instancedArrays?.vertexAttribDivisor(location, divisor)\n })\n }\n })\n },\n unbind,\n dispose() {\n gl.deleteBuffer(buffer)\n if (vao) {\n vao.dispose()\n }\n },\n set(value, usage = 'STATIC_DRAW') {\n if (vao) {\n vao.bind()\n }\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(gl.ARRAY_BUFFER, value, gl[usage])\n if (vao) {\n vao.unbind()\n }\n },\n } satisfies InterleavedAttributeMethods\n })\n\n signal?.addEventListener('abort', function dispose() {\n forEachObject(interleavedAttributes, value => value.dispose())\n })\n\n return interleavedAttributes\n}\n\n/**********************************************************************************/\n/* */\n/* Buffer View */\n/* */\n/**********************************************************************************/\n\nexport function bufferView<T extends BufferSchema>(\n gl: GL,\n schema: T,\n { signal }: ViewOptions = {},\n): BufferView<T> {\n // Initialize buffers\n const buffers = mapObject(schema, ({ target = 'ARRAY_BUFFER', usage = 'STATIC_DRAW' }) => {\n const buffer = assertedNotNullish(gl.createBuffer())\n\n return {\n bind() {\n // Snapshot before the change, restore in the disposer. Restoring the\n // previous buffer rather than null matters for ELEMENT_ARRAY_BUFFER,\n // whose binding is recorded in the bound vertex array: clearing it\n // would strip the index buffer from an array the caller may not own.\n const previousBuffer = gl.getParameter(gl[`${target}_BINDING`]) as WebGLBuffer | null\n gl.bindBuffer(gl[target], buffer)\n return once(() => {\n gl.bindBuffer(gl[target], previousBuffer)\n })\n },\n dispose() {\n gl.deleteBuffer(buffer)\n },\n set(data: Float32Array) {\n gl.bindBuffer(gl[target], buffer)\n gl.bufferData(gl[target], data, gl[usage])\n },\n }\n })\n\n signal?.addEventListener('abort', function dispose() {\n forEachObject(buffers, value => value.dispose())\n })\n\n return buffers\n}\n"],"mappings":";;;;;;AAUO,SAAS,mBAAsB,OAAU,SAAkC;AAChF,MAAI,UAAU,UAAa,UAAU,KAAM,OAAM,IAAI,MAAM,OAAO;AAClE,SAAO;AACT;AAQO,IAAM,sBAAsB,CACjC,SACmC;AACnC,UAAQ,KAAK,CAAC,GAAG;AAAA;AAAA,IAEf,KAAK;AACH,aAAQ,WAAW,KAAK,MAAM,CAAC,IAAI;AAAA;AAAA,IAErC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,cAAS,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,OAAO;AAAA;AAAA,IAE3C,KAAK;AACH,cAAS,KAAK,CAAC,KAAK,OAAO;AAAA;AAAA,IAE7B,KAAK;AACH,aAAQ,KAAK,CAAC,IAAI;AAAA,IACpB;AACE,cAAQ,MAAM;AAAA,QACZ,KAAK;AACH,iBAAO;AAAA,QACT,KAAK;AACH,iBAAO;AAAA,QACT;AACE,iBAAO;AAAA,MACX;AAAA,EACJ;AACF;AAQO,IAAM,aAAa,CAAwB,SAAiC;AACjF,UAAQ,KAAK,CAAC,GAAG;AAAA,IACf,KAAK;AACH,YAAM,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,MAAM,EAAG,IAAI,MAAM;AAC7C,aAAQ,KAAK,KAAK;AAAA,IACpB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,YAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,aAAQ,QAAQ,CAAC,MAAM,CAAC,IAAI;AAAA,IAC9B;AACE,aAAO;AAAA,EACX;AACF;AAQO,IAAM,YAAY,CAKvB,SAC2C,KAAK,SAAS,KAAK;AAEzD,IAAM,gBAAgB,CAC3B,SACyD,KAAK,SAAS,SAAS;AA4I3E,SAAS,gBACd,aACA;AACA,QAAM,MAAM,KAAK,eAAe,KAAK;AACrC,SAAO,OAAO,OAAO,KAAK;AAAA,IACxB,YACE,KACA,OACA;AACA,UAAI,SAAS,IAAI,IAAI,GAAG;AACxB,UAAI,QAAQ;AACV,eAAO;AAAA,MACT;AACA,eAAS,MAAM;AACf,UAAI,IAAI,KAAK,MAAM;AACnB,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;AAEO,SAAS,cACd,OACA,UACA;AACA,MAAIA,SAAQ;AACZ,aAAW,OAAO,OAAO;AACvB,aAAS,MAAM,GAAG,GAAG,KAAKA,MAAK;AAC/B,IAAAA;AAAA,EACF;AACA,aAAW,OAAO,OAAO,sBAAsB,KAAK,GAAG;AACrD,aAAS,MAAM,GAAc,GAAG,KAAgBA,MAAK;AACrD,IAAAA;AAAA,EACF;AACF;AAEO,SAAS,UACd,OACA,UACgC;AAChC,QAAM,SAAS,CAAC;AAEhB,gBAAc,OAAO,CAACC,QAAO,KAAKD,WAAU;AAC1C,WAAO,GAAG,IAAI,SAASC,QAAO,KAAKD,MAAK;AAAA,EAC1C,CAAC;AAED,SAAO;AACT;;;AC/OA,IAAI,QAAQ;AACZ,IAAM,SAAS;AAGf,IAAM,mBAAmB,MAAM;AAC7B,MAAI;AACF,UAAM,SAAS,OAAO;AACtB,yBAAI,QAAQ,GAAE,IAAI,QAAQ,EAAE;AAC5B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF,GAAG;AAEH,IAAM,aAAa,gBAAgB,kBAAkB,UAA0B,GAAmB;AAE3F,SAAS,KAAK,KAA+B;AAClD,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO,WAAW,YAAY,KAAK,MAAM,GAAG,MAAM,IAAI,OAAO,EAAE;AAAA,EACjE;AACA,SAAO,IAAI,SAAS;AACtB;AAQO,SAAS,KACd,IACA,SACA,QACA,SACe;AACf,SAAO;AAAA,IACL,UAAU,CAAC,OAAO,WAAW,SAAY,YAAY,IAAI,SAAS,OAAO,QAAQ;AAAA,IACjF,YAAY,CAAC,OAAO,aAChB,SACA,cAAc,IAAI,SAAS,OAAO,YAAY,OAAO;AAAA,IACzD,uBAAuB,CAAC,OAAO,wBAC3B,SACA,yBAAyB,IAAI,SAAS,OAAO,uBAAuB,OAAO;AAAA,IAC/E,SAAS,CAAC,OAAO,UAAU,SAAY,WAAW,IAAI,OAAO,OAAO;AAAA,EACtE;AACF;AAQO,SAAS,YACd,IACA,SACA,QACqB;AAErB,SAAO,UAAU,QAAQ,CAAC,EAAE,MAAM,KAAK,GAAG,QAAQ;AAChD,UAAME,QAAO,KAAK,GAAG;AAErB,QAAI,cAAc,IAAI,GAAG;AACvB,YAAMC,YAAW,GAAG,mBAAmB,SAASD,KAAI;AACpD,aAAO;AAAA,QACL,IAAI,KAAa;AACf,aAAG,UAAUC,WAAU,GAAG;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAGA,QAAI,MAAM;AACR,YAAM,aAAa,UAAU,oBAAoB,IAAI,CAAC;AAEtD,YAAM,SAAS,GAAG,UAAU,EAAE,KAAK,EAAE;AACrC,YAAM,eAAe,GAAG,mBAAmB,SAASD,KAAI;AAExD,YAAM,gBAAgB,UAAU,oBAAoB,IAAI,CAAC;AAEzD,YAAM,YAAY,GAAG,aAAa,EAAE,KAAK,EAAE;AAG3C,YAAM,WAAiD,CAAC;AACxD,eAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,cAAMC,YAAW,GAAG,mBAAmB,SAAS,GAAGD,KAAI,IAAI,CAAC,GAAG;AAC/D,iBAAS,CAAC,IAAI;AAAA,UACZ,OAAO,MAAa;AAClB,sBAAUC,WAAU,GAAG,IAAI;AAAA,UAC7B;AAAA,QACF;AAAA,MACF;AAGA,aAAO,OAAO,OAAO,UAAU;AAAA,QAC7B,IAAI,KAAmB;AACrB,iBAAO,cAAc,GAAG;AAAA,QAC1B;AAAA,MACF,CAAC;AAAA,IACH;AAGA,UAAM,WAAW,GAAG,mBAAmB,SAASD,KAAI;AACpD,UAAM,SAAS,UAAU,oBAAoB,IAAI,CAAC;AAGlD,UAAM,KAAK,GAAG,MAAM,EAAE,KAAK,EAAE;AAE7B,QAAI,UAAU,IAAI,GAAG;AACnB,aAAO;AAAA,QACL,OAAO,MAAa;AAClB,aAAG,UAAU,OAAO,KAAK,CAAC,CAAC;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO,MAAa;AAClB,WAAG,UAAU,GAAG,IAAI;AAAA,MACtB;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAiBA,IAAM,oBAAqD;AAAA,EACzD,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AACT;AAEA,IAAM,mBAAoD;AAAA,EACxD,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AACT;AAEA,SAAS,cAAc,MAAsC;AAC3D,MAAI,KAAK,WAAW,GAAG,EAAG,QAAO;AACjC,MAAI,KAAK,WAAW,GAAG,EAAG,QAAO;AACjC,SAAO;AACT;AAIA,SAAS,gBACP,IACA,UACA,MACA,QACA,QACA,QACA,eACA,YACA,WACA;AACA,KAAG,wBAAwB,QAAQ;AACnC,MAAI,eAAe;AACjB;AAAC,IAAC,GAA8B,qBAAqB,UAAU,MAAM,GAAG,MAAM,GAAG,QAAQ,MAAM;AAAA,EACjG,OAAO;AACL,OAAG,oBAAoB,UAAU,MAAM,GAAG,MAAM,GAAG,YAAY,QAAQ,MAAM;AAAA,EAC/E;AAMA,QAAM,kBAAkB,mBAAmB,EAAE;AAC7C,MAAI,WAAW;AACb,uBAAmB,eAAe,EAAE,oBAAoB,UAAU,CAAC;AAAA,EACrE,WAAW,iBAAiB;AAC1B,oBAAgB,oBAAoB,UAAU,CAAC;AAAA,EACjD;AACF;AAGA,IAAM,8BAA8B;AACpC,IAAM,uBAAuB;AAI7B,SAAS,YAAY,IAAQ,UAA0B;AACrD,SAAO,mBAAmB,EAAE,IACvB,GAAG,gBAAgB,UAAU,2BAA2B,IACzD;AACN;AAIA,SAAS,KAAK,SAAiC;AAC7C,MAAI,OAAO;AACX,SAAO,MAAM;AACX,QAAI,KAAM;AACV,WAAO;AACP,YAAQ;AAAA,EACV;AACF;AAEO,SAAS,cACd,IACA,SACA,QACA,EAAE,OAAO,IAAiB,CAAC,GACT;AAClB,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,CACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb;AAAA,MACA,SAAS,mBAAmB,GAAG,aAAa,CAAC;AAAA,IAC/C,GACA,QACqB;AACrB,YAAMA,QAAO,KAAK,GAAG;AAErB,YAAM,WAAW,GAAG,kBAAkB,SAASA,KAAI;AACnD,UAAI,WAAW,GAAG;AAChB,cAAM,IAAI,MAAM,cAAcA,KAAI,aAAa;AAAA,MACjD;AAEA,YAAM,OAAO,WAAW,IAAI;AAC5B,YAAM,iBAAiB,UAAU,cAAc,IAAI;AACnD,YAAM,SAAS,kBAAkB,cAAc;AAC/C,YAAM,YAAY,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG;AAE7D,aAAO;AAAA,QACL;AAAA,QACA,OAAO;AAEL,gBAAM,kBAAkB,YAAY,IAAI,QAAQ;AAChD,aAAG,WAAW,GAAG,cAAc,MAAM;AACrC,0BAAgB,IAAI,UAAU,MAAM,GAAG,GAAG,QAAQ,WAAW,YAAY,SAAS;AAClF,iBAAO,KAAK,MAAM;AAChB,+BAAmB,EAAE,GAAG,oBAAoB,UAAU,eAAe;AAAA,UACvE,CAAC;AAAA,QACH;AAAA,QACA,UAAU;AACR,aAAG,aAAa,MAAM;AAAA,QACxB;AAAA,QACA,IAAI,MAAM,QAAQ,eAAe;AAC/B,aAAG,WAAW,GAAG,cAAc,MAAM;AACrC,aAAG,WAAW,GAAG,cAAc,MAAM,GAAG,KAAK,CAAC;AAC9C,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,iBAAiB,SAAS,SAAS,UAAU;AACnD,kBAAc,YAAY,WAAS,MAAM,QAAQ,CAAC;AAAA,EACpD,CAAC;AAED,SAAO;AACT;AAQO,SAAS,yBACd,IACA,SACA,QACA,EAAE,OAAO,IAAiB,CAAC,GACE;AAE7B,QAAM,wBAAwB,UAAU,QAAQ,CAAC,EAAE,QAAQ,UAAU,MAAM;AAEzE,QAAIE,SAAQ;AAGZ,UAAM,YAAsB,CAAC;AAC7B,UAAM,UAAU,OAAO,IAAI,CAAAC,YAAU;AACnC,YAAMH,QAAO,KAAKG,QAAO,GAAG;AAE5B,YAAM,WAAW,GAAG,kBAAkB,SAASH,KAAI;AAEnD,UAAI,WAAW,GAAG;AAChB,cAAM,IAAI,MAAM,cAAcA,KAAI,aAAa;AAAA,MACjD;AAEA,gBAAU,KAAK,QAAQ;AAEvB,YAAM,OAAO,WAAWG,QAAO,IAAI;AACnC,YAAM,SAASD;AACf,YAAM,iBAAiBC,QAAO,UAAU,cAAcA,QAAO,IAAI;AACjE,YAAM,SAAS,kBAAkB,cAAc;AAC/C,YAAM,YAAYA,QAAO,KAAK,WAAW,GAAG,KAAKA,QAAO,KAAK,WAAW,GAAG;AAC3E,YAAM,aAAaA,QAAO,cAAc;AACxC,MAAAD,UAAS,OAAO,iBAAiB,cAAc;AAE/C,aAAO,MACL;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACJ,CAAC;AAGD,UAAM,SAASA;AAGf,UAAM,SAAS,mBAAmB,GAAG,aAAa,CAAC;AAGnD,QAAI,MAAqE;AAGzE,UAAM,UAAU,qBAAqB,EAAE;AACvC,QAAI,SAAS;AACX,YAAM,cAAc,QAAQ,kBAAkB;AAC9C,cAAQ,gBAAgB,WAAW;AACnC,SAAG,WAAW,GAAG,cAAc,MAAM;AACrC,iBAAW,UAAU,SAAS;AAC5B,eAAO;AAAA,MACT;AACA,cAAQ,gBAAgB,IAAI;AAC5B,YAAM;AAAA,QACJ,SAAS;AACP,kBAAQ,gBAAgB,IAAI;AAAA,QAC9B;AAAA,QACA,OAAO;AACL,kBAAQ,gBAAgB,WAAW;AAAA,QACrC;AAAA,QACA,UAAU;AACR,kBAAQ,kBAAkB,WAAW;AAAA,QACvC;AAAA,MACF;AAAA,IACF;AAIA,UAAM,SAAS,MAAM;AACnB,UAAI,KAAK;AACP,YAAI,OAAO;AAAA,MACb;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO;AAEL,cAAM,sBAAsB,UACvB,GAAG,aAAa,oBAAoB,IACrC;AACJ,cAAM,mBAAmB,MACrB,CAAC,IACD,UAAU,IAAI,cAAY,CAAC,UAAU,YAAY,IAAI,QAAQ,CAAC,CAAU;AAE5E,WAAG,WAAW,GAAG,cAAc,MAAM;AACrC,YAAI,KAAK;AACP,cAAI,KAAK;AAAA,QACX,OAAO;AAEL,qBAAW,UAAU,SAAS;AAC5B,mBAAO;AAAA,UACT;AAAA,QACF;AAEA,eAAO,KAAK,MAAM;AAChB,cAAI,SAAS;AACX,oBAAQ,gBAAgB,mBAAmB;AAAA,UAC7C;AAGA,cAAI,CAAC,KAAK;AACR,kBAAM,kBAAkB,mBAAmB,EAAE;AAC7C,6BAAiB,QAAQ,CAAC,CAAC,UAAU,OAAO,MAAM;AAChD,+BAAiB,oBAAoB,UAAU,OAAO;AAAA,YACxD,CAAC;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MACA;AAAA,MACA,UAAU;AACR,WAAG,aAAa,MAAM;AACtB,YAAI,KAAK;AACP,cAAI,QAAQ;AAAA,QACd;AAAA,MACF;AAAA,MACA,IAAI,OAAO,QAAQ,eAAe;AAChC,YAAI,KAAK;AACP,cAAI,KAAK;AAAA,QACX;AACA,WAAG,WAAW,GAAG,cAAc,MAAM;AACrC,WAAG,WAAW,GAAG,cAAc,OAAO,GAAG,KAAK,CAAC;AAC/C,YAAI,KAAK;AACP,cAAI,OAAO;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,UAAQ,iBAAiB,SAAS,SAAS,UAAU;AACnD,kBAAc,uBAAuB,WAAS,MAAM,QAAQ,CAAC;AAAA,EAC/D,CAAC;AAED,SAAO;AACT;AAQO,SAAS,WACd,IACA,QACA,EAAE,OAAO,IAAiB,CAAC,GACZ;AAEf,QAAM,UAAU,UAAU,QAAQ,CAAC,EAAE,SAAS,gBAAgB,QAAQ,cAAc,MAAM;AACxF,UAAM,SAAS,mBAAmB,GAAG,aAAa,CAAC;AAEnD,WAAO;AAAA,MACL,OAAO;AAKL,cAAM,iBAAiB,GAAG,aAAa,GAAG,GAAG,MAAM,UAAU,CAAC;AAC9D,WAAG,WAAW,GAAG,MAAM,GAAG,MAAM;AAChC,eAAO,KAAK,MAAM;AAChB,aAAG,WAAW,GAAG,MAAM,GAAG,cAAc;AAAA,QAC1C,CAAC;AAAA,MACH;AAAA,MACA,UAAU;AACR,WAAG,aAAa,MAAM;AAAA,MACxB;AAAA,MACA,IAAI,MAAoB;AACtB,WAAG,WAAW,GAAG,MAAM,GAAG,MAAM;AAChC,WAAG,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF,CAAC;AAED,UAAQ,iBAAiB,SAAS,SAAS,UAAU;AACnD,kBAAc,SAAS,WAAS,MAAM,QAAQ,CAAC;AAAA,EACjD,CAAC;AAED,SAAO;AACT;","names":["index","value","name","location","index","layout"]}
|