@bigmistqke/view.gl 0.1.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/src/index.ts ADDED
@@ -0,0 +1,313 @@
1
+ import { getInstancedArrays, getVertexArrayObject } from './features'
2
+ import type {
3
+ AttributeMethods,
4
+ AttributeSchema,
5
+ AttributeView,
6
+ BufferSchema,
7
+ BufferView,
8
+ GL,
9
+ UniformView as InferUniformView,
10
+ InterleavedAttributeMethods,
11
+ InterleavedAttributeSchema,
12
+ InterleavedAttributeView,
13
+ UniformSchema,
14
+ View,
15
+ ViewOptions,
16
+ ViewSchema,
17
+ } from './types'
18
+ import {
19
+ assertedNotNullish,
20
+ isMatKind,
21
+ isSamplerKind,
22
+ kindToSize,
23
+ kindToUniformFnName,
24
+ mapObject,
25
+ } from './utils'
26
+
27
+ /**********************************************************************************/
28
+ /* */
29
+ /* View */
30
+ /* */
31
+ /**********************************************************************************/
32
+
33
+ export function view<T extends ViewSchema>(
34
+ gl: GL,
35
+ program: WebGLProgram,
36
+ schema: T,
37
+ options?: ViewOptions,
38
+ ): View<T> {
39
+ return {
40
+ uniforms: !schema.uniforms ? undefined : uniformView(gl, program, schema.uniforms),
41
+ attributes: !schema.attributes
42
+ ? undefined
43
+ : attributeView(gl, program, schema.attributes, options),
44
+ interleavedAttributes: !schema.interleavedAttributes
45
+ ? undefined
46
+ : interleavedAttributeView(gl, program, schema.interleavedAttributes, options),
47
+ buffers: !schema.buffers ? undefined : bufferView(gl, schema.buffers),
48
+ } as View<T>
49
+ }
50
+
51
+ /**********************************************************************************/
52
+ /* */
53
+ /* Uniform View */
54
+ /* */
55
+ /**********************************************************************************/
56
+
57
+ export function uniformView<T extends UniformSchema>(
58
+ gl: GL,
59
+ program: WebGLProgram,
60
+ schema: T,
61
+ ): InferUniformView<T> {
62
+ return mapObject(schema, ({ kind, size }, name) => {
63
+ const location = gl.getUniformLocation(program, name)
64
+
65
+ if (isSamplerKind(kind)) {
66
+ return {
67
+ set(arg: number) {
68
+ gl.uniform1i(location, arg)
69
+ },
70
+ }
71
+ }
72
+
73
+ const fnName = `uniform${kindToUniformFnName(kind)}${size ? 'v' : ''}`
74
+
75
+ // @ts-ignore FIX WEBGL/WEBGL2 TYPES
76
+ const fn = gl[fnName].bind(gl)
77
+
78
+ if (isMatKind(kind)) {
79
+ return {
80
+ set(...args: any[]) {
81
+ fn(location, false, args[0])
82
+ },
83
+ }
84
+ }
85
+
86
+ return {
87
+ set(...args: any[]) {
88
+ fn(location, ...args)
89
+ },
90
+ }
91
+ })
92
+ }
93
+
94
+ /**********************************************************************************/
95
+ /* */
96
+ /* Attribute View */
97
+ /* */
98
+ /**********************************************************************************/
99
+
100
+ // Shared attribute helper functions
101
+ // between attributeView and interleavedAttributeView
102
+ function handleAttribute(
103
+ gl: GL,
104
+ location: number,
105
+ size: number,
106
+ stride: number,
107
+ offset: number,
108
+ type: 'FLOAT' | 'INT',
109
+ instanced?: boolean,
110
+ ) {
111
+ gl.enableVertexAttribArray(location)
112
+ gl.vertexAttribPointer(location, size, gl[type], false, stride, offset)
113
+
114
+ if (instanced) {
115
+ // Get instanced-arrays-feature: extension if webgl, gl if webgl2
116
+ assertedNotNullish(getInstancedArrays(gl)).vertexAttribDivisor(location, 1)
117
+ }
118
+ }
119
+
120
+ export function attributeView<T extends AttributeSchema>(
121
+ gl: GL,
122
+ program: WebGLProgram,
123
+ schema: T,
124
+ { signal }: ViewOptions = {},
125
+ ): AttributeView<T> {
126
+ const attributes = mapObject(
127
+ schema,
128
+ (
129
+ { kind, instanced, buffer = assertedNotNullish(gl.createBuffer()) },
130
+ name,
131
+ ): AttributeMethods => {
132
+ const location = gl.getAttribLocation(program, name)
133
+ if (location < 0) {
134
+ throw new Error(`Attribute '${name}' not found`)
135
+ }
136
+
137
+ const size = kindToSize(kind)
138
+ const type = kind.startsWith('i') ? 'INT' : 'FLOAT'
139
+
140
+ return {
141
+ buffer,
142
+ bind() {
143
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
144
+ handleAttribute(gl, location, size, 0, 0, type, instanced)
145
+ },
146
+ dispose() {
147
+ gl.deleteBuffer(buffer)
148
+ },
149
+ set(data, usage = 'STATIC_DRAW') {
150
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
151
+ gl.bufferData(gl.ARRAY_BUFFER, data, gl[usage])
152
+ return this
153
+ },
154
+ }
155
+ },
156
+ )
157
+
158
+ signal?.addEventListener('abort', function dispose() {
159
+ for (const name in attributes) {
160
+ attributes[name].dispose()
161
+ }
162
+ })
163
+
164
+ return attributes
165
+ }
166
+
167
+ /**********************************************************************************/
168
+ /* */
169
+ /* Interleaved Attribute View */
170
+ /* */
171
+ /**********************************************************************************/
172
+
173
+ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
174
+ gl: GL,
175
+ program: WebGLProgram,
176
+ schema: T,
177
+ { signal }: ViewOptions = {},
178
+ ): InterleavedAttributeView<T> {
179
+ // Initialize interleaved attributes
180
+ const interleavedAttributes = mapObject(schema, ({ layout, instanced }) => {
181
+ // Increment number to keep track of offset
182
+ let index = 0
183
+
184
+ // Calculate layout information
185
+ const handles = layout.map(layout => {
186
+ const location = gl.getAttribLocation(program, layout.name)
187
+
188
+ if (location < 0) {
189
+ throw new Error(`Attribute '${layout.name}' not found`)
190
+ }
191
+
192
+ const size = kindToSize(layout.kind)
193
+ const offset = index
194
+ const type = layout.kind.startsWith('i') ? 'INT' : 'FLOAT'
195
+ index += size * 4
196
+
197
+ return () => handleAttribute(gl, location, size, stride, offset, type, instanced)
198
+ })
199
+
200
+ // Set stride to final index
201
+ const stride = index
202
+
203
+ // Create a buffer
204
+ const buffer = assertedNotNullish(gl.createBuffer())
205
+
206
+ // Create VAO to cache attribute state
207
+ let vao: { unbind(): void; bind(): void; dispose(): void } | undefined = undefined
208
+
209
+ // Get VAO-feature: extension if webgl1, gl if webgl2
210
+ const feature = getVertexArrayObject(gl)
211
+ if (feature) {
212
+ const vertexArray = feature.createVertexArray()
213
+ feature.bindVertexArray(vertexArray)
214
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
215
+ for (const handle of handles) {
216
+ handle()
217
+ }
218
+ feature.bindVertexArray(null)
219
+ vao = {
220
+ unbind() {
221
+ feature.bindVertexArray(null)
222
+ },
223
+ bind() {
224
+ feature.bindVertexArray(vertexArray)
225
+ },
226
+ dispose() {
227
+ feature.deleteVertexArray(vertexArray)
228
+ },
229
+ }
230
+ }
231
+
232
+ return {
233
+ bind() {
234
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
235
+ if (vao) {
236
+ vao.bind()
237
+ } else {
238
+ // Fallback: manual attribute setup
239
+ for (const handle of handles) {
240
+ handle()
241
+ }
242
+ }
243
+ },
244
+ unbind() {
245
+ if (vao) {
246
+ vao.unbind()
247
+ }
248
+ },
249
+ dispose() {
250
+ gl.deleteBuffer(buffer)
251
+ if (vao) {
252
+ vao.dispose()
253
+ }
254
+ },
255
+ set(value, usage = 'STATIC_DRAW') {
256
+ if (vao) {
257
+ vao.bind()
258
+ } else {
259
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
260
+ }
261
+ gl.bufferData(gl.ARRAY_BUFFER, value, gl[usage])
262
+ if (vao) {
263
+ vao.unbind()
264
+ }
265
+ },
266
+ } satisfies InterleavedAttributeMethods
267
+ })
268
+
269
+ signal?.addEventListener('abort', function dispose() {
270
+ for (const name in interleavedAttributes) {
271
+ interleavedAttributes[name].dispose()
272
+ }
273
+ })
274
+
275
+ return interleavedAttributes
276
+ }
277
+
278
+ /**********************************************************************************/
279
+ /* */
280
+ /* Buffer View */
281
+ /* */
282
+ /**********************************************************************************/
283
+
284
+ export function bufferView<T extends BufferSchema>(
285
+ gl: GL,
286
+ schema: T,
287
+ { signal }: ViewOptions = {},
288
+ ): BufferView<T> {
289
+ // Initialize buffers
290
+ const buffers = mapObject(schema, ({ target = 'ARRAY_BUFFER', usage = 'STATIC_DRAW' }) => {
291
+ const buffer = assertedNotNullish(gl.createBuffer())
292
+ return {
293
+ bind() {
294
+ gl.bindBuffer(gl[target], buffer)
295
+ },
296
+ dispose() {
297
+ gl.deleteBuffer(buffer)
298
+ },
299
+ set(data: Float32Array) {
300
+ gl.bindBuffer(gl[target], buffer)
301
+ gl.bufferData(gl[target], data, gl[usage])
302
+ },
303
+ }
304
+ })
305
+
306
+ signal?.addEventListener('abort', function dispose() {
307
+ for (const name in buffers) {
308
+ buffers[name].dispose()
309
+ }
310
+ })
311
+
312
+ return buffers
313
+ }
package/src/tag.ts ADDED
@@ -0,0 +1,193 @@
1
+ import type {
2
+ AttributeKind,
3
+ AttributeOptions,
4
+ AttributeTag,
5
+ AttributeTagFn,
6
+ GLSL,
7
+ InterleaveTag,
8
+ Merge,
9
+ Prettify,
10
+ UniformKind,
11
+ UniformOptions,
12
+ UniformTag,
13
+ } from './types'
14
+ import { createProgram } from './utils'
15
+
16
+ /**********************************************************************************/
17
+ /* */
18
+ /* Tag Utilities */
19
+ /* */
20
+ /**********************************************************************************/
21
+
22
+ interface UniformTagFn<TKind extends UniformKind> {
23
+ <const TName extends string, const TOptions extends Omit<UniformOptions, 'kind'>>(
24
+ name: TName,
25
+ TOptions: TOptions,
26
+ ): Prettify<UniformTag<TName, TKind, TOptions>>
27
+ <const TName extends string>(name: TName): Prettify<UniformTag<TName, TKind>>
28
+ }
29
+
30
+ export const uniform = new Proxy(
31
+ {} as {
32
+ [TKey in UniformKind]: UniformTagFn<TKey>
33
+ },
34
+ {
35
+ get(target, property) {
36
+ // @ts-expect-error
37
+ if (typeof property === 'symbol') return target[property]
38
+ return (name: string, options?: Omit<UniformOptions, 'kind'>) => ({
39
+ type: 'uniform',
40
+ name,
41
+ kind: property,
42
+ ...options,
43
+ })
44
+ },
45
+ },
46
+ )
47
+
48
+ export const attribute = new Proxy(
49
+ {} as {
50
+ [TKey in AttributeKind]: AttributeTagFn<TKey>
51
+ },
52
+ {
53
+ get(target, property) {
54
+ // @ts-expect-error
55
+ if (typeof property === 'symbol') return target[property]
56
+ return (name: string, options?: Omit<AttributeOptions, 'kind'>) => ({
57
+ type: 'attribute',
58
+ name,
59
+ kind: property,
60
+ ...options,
61
+ })
62
+ },
63
+ },
64
+ )
65
+
66
+ export function interleave<
67
+ const TName extends string,
68
+ const TLayout extends Array<AttributeTag<string, AttributeKind, undefined>>,
69
+ const TOptions extends Omit<AttributeOptions, 'kind'>,
70
+ >(name: TName, layout: TLayout, instanced: TOptions): InterleaveTag<TName, TLayout, TOptions>
71
+ export function interleave<
72
+ const TName extends string,
73
+ const TLayout extends Array<AttributeTag<string, AttributeKind, undefined>>,
74
+ >(name: TName, layout: TLayout): InterleaveTag<TName, TLayout, { instanced: false }>
75
+ export function interleave<
76
+ const TName extends string,
77
+ const TLayout extends Array<AttributeTag<string, AttributeKind, undefined>>,
78
+ const TOptions extends Omit<AttributeOptions, 'kind'>,
79
+ >(name: TName, layout: TLayout, { instanced, buffer }?: TOptions = {}) {
80
+ return {
81
+ type: 'interleavedAttribute',
82
+ name,
83
+ // remove instanced- and type-property
84
+ layout: layout.map(({ name, kind }) => ({
85
+ name,
86
+ kind,
87
+ })),
88
+ instanced: !!instanced,
89
+ buffer,
90
+ } as InterleaveTag<TName, TLayout, TOptions>
91
+ }
92
+
93
+ /**********************************************************************************/
94
+ /* */
95
+ /* GLSL */
96
+ /* */
97
+ /**********************************************************************************/
98
+
99
+ export function glsl<
100
+ Elem extends string,
101
+ Template extends ReadonlyArray<Elem>,
102
+ Hole extends UniformTag | AttributeTag | InterleaveTag | string | number,
103
+ Holes extends Hole[],
104
+ >([initial, ...rest]: Template, ...holes: [...Holes]) {
105
+ const v300 = initial?.startsWith('#version 300 es')
106
+ const template = rest.reduce((out, templatePart, index) => {
107
+ const hole = holes[index]
108
+ const holePart =
109
+ typeof hole === 'string' || typeof hole === 'number'
110
+ ? hole
111
+ : hole.type === 'interleavedAttribute'
112
+ ? hole.layout.reduce(
113
+ (a, v) =>
114
+ v300 ? `${a}in ${v.kind} ${v.name};\n` : `${a}attribute ${v.kind} ${v.name};\n`,
115
+ '',
116
+ )
117
+ : typeof hole === 'object' && hole.type === 'uniform' && 'size' in hole
118
+ ? `${hole.type} ${hole.kind} ${hole.name}[${hole.size}];`
119
+ : `${hole.type === 'attribute' && v300 ? 'in' : hole.type} ${hole.kind} ${hole.name};`
120
+ return out + holePart + templatePart
121
+ }, initial || '')
122
+
123
+ return holes.reduce(
124
+ (resources, resource) => {
125
+ if (typeof resource === 'string' || typeof resource === 'number') {
126
+ return resources
127
+ }
128
+ // @ts-expect-error
129
+ resources[`${resource.type}s`][resource.name] = resource
130
+ return resources
131
+ },
132
+ {
133
+ template,
134
+ uniforms: {},
135
+ attributes: {},
136
+ interleavedAttributes: {},
137
+ } as {
138
+ template: string
139
+ uniforms: {
140
+ [K in Extract<Holes[number], UniformTag> as K['name']]: Prettify<Omit<K, 'name' | 'type'>>
141
+ }
142
+ attributes: {
143
+ [K in Extract<Holes[number], AttributeTag> as K['name']]: Prettify<Omit<K, 'name' | 'type'>>
144
+ }
145
+ interleavedAttributes: {
146
+ [K in Extract<Holes[number], InterleaveTag> as K['name']]: Prettify<
147
+ Omit<K, 'name' | 'type'>
148
+ >
149
+ }
150
+ },
151
+ )
152
+ }
153
+
154
+ /**********************************************************************************/
155
+ /* */
156
+ /* Compile */
157
+ /* */
158
+ /**********************************************************************************/
159
+
160
+ export function compile<TVertex extends GLSL, TFragment extends GLSL>(
161
+ gl: WebGLRenderingContext,
162
+ vertex: TVertex,
163
+ fragment: TFragment,
164
+ ) {
165
+ const program = createProgram(gl, vertex.template, fragment.template)
166
+
167
+ return {
168
+ program,
169
+ schema: {
170
+ uniforms: {
171
+ ...vertex.uniforms,
172
+ ...fragment.uniforms,
173
+ },
174
+ attributes: {
175
+ ...vertex.attributes,
176
+ ...fragment.attributes,
177
+ },
178
+ interleavedAttributes: {
179
+ ...vertex.interleavedAttributes,
180
+ ...fragment.interleavedAttributes,
181
+ },
182
+ },
183
+ } as {
184
+ program: WebGLProgram
185
+ schema: {
186
+ uniforms: Prettify<Merge<TVertex['uniforms'], TFragment['uniforms']>>
187
+ attributes: Prettify<Merge<TVertex['attributes'], TFragment['attributes']>>
188
+ interleavedAttributes: Prettify<
189
+ Merge<TVertex['interleavedAttributes'], TFragment['interleavedAttributes']>
190
+ >
191
+ }
192
+ }
193
+ }