@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/.claude/settings.local.json +8 -0
- package/.prettierrc +10 -0
- package/README.md +338 -0
- package/dev/bezier.ts +428 -0
- package/dev/game_of_life.ts +250 -0
- package/dev/grid.ts +223 -0
- package/dev/index.html +20 -0
- package/dev/index.ts +94 -0
- package/dev/pix_sim.ts +549 -0
- package/dev/tetris.ts +439 -0
- package/dev/tsconfig.json +4 -0
- package/dev/utils.ts +61 -0
- package/dev/vite.config.ts +42 -0
- package/package.json +29 -0
- package/src/features.ts +59 -0
- package/src/index.ts +313 -0
- package/src/tag.ts +193 -0
- package/src/types.ts +718 -0
- package/src/utils.ts +266 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +11 -0
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
|
+
}
|