@bigmistqke/view.gl 0.1.0 → 0.1.2
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 +2 -1
- package/.github/ISSUE_TEMPLATE/bug-report.yml +31 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +27 -0
- package/.github/workflows/codeql.yml +46 -0
- package/.github/workflows/deploy-page.yml +38 -0
- package/.github/workflows/format.yml +40 -0
- package/README.md +92 -35
- package/dev/404.html +19 -0
- package/dev/dist/404.html +19 -0
- package/dev/dist/assets/bezier-DuLTb2d1.js +294 -0
- package/dev/dist/assets/game_of_life-D7qHdFcv.js +202 -0
- package/dev/dist/assets/grid-Ie22U_9K.js +173 -0
- package/dev/dist/assets/index-0qrXMlfv.js +109 -0
- package/dev/dist/assets/index-C2N3u8wj.css +12 -0
- package/dev/dist/assets/main-BZ1N6pp6.js +32 -0
- package/dev/dist/assets/overview-DAxtMIw5.js +78 -0
- package/dev/dist/assets/pix_sim-DIhzM_vu.js +501 -0
- package/dev/dist/assets/symbol-BouRL-Ni.js +65 -0
- package/dev/dist/assets/tag-C_bg62fD.js +516 -0
- package/dev/dist/assets/tetris-IMnkSi9p.js +344 -0
- package/dev/dist/assets/utils-CvCgsM08.js +50 -0
- package/dev/dist/index.html +51 -0
- package/dev/index.html +39 -10
- package/dev/{bezier.ts → src/examples/bezier.ts} +1 -1
- package/dev/{grid.ts → src/examples/grid.ts} +1 -1
- package/dev/{pix_sim.ts → src/examples/pix_sim.ts} +112 -97
- package/dev/src/examples/symbol.ts +79 -0
- package/dev/{tetris.ts → src/examples/tetris.ts} +1 -0
- package/dev/src/index.css +12 -0
- package/dev/src/index.ts +69 -0
- package/dev/{utils.ts → src/utils.ts} +8 -2
- package/dev/vite.config.ts +9 -1
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +324 -0
- package/dist/index.mjs.map +1 -0
- package/dist/tag.d.mts +97 -0
- package/dist/tag.mjs +162 -0
- package/dist/tag.mjs.map +1 -0
- package/dist/types-DoFAJoh-.d.mts +266 -0
- package/package.json +24 -10
- package/src/index.ts +39 -17
- package/src/tag.ts +47 -39
- package/src/types.ts +24 -24
- package/src/{utils.ts → utils/index.ts} +39 -14
- package/src/utils/object.ts +27 -0
- package/test/index.test.ts +1017 -0
- package/test/mocks/webgl.ts +248 -0
- package/test/setup.ts +13 -0
- package/test/tag.test.ts +790 -0
- package/vitest.config.ts +15 -0
- /package/dev/{game_of_life.ts → src/examples/game_of_life.ts} +0 -0
- /package/dev/{index.ts → src/examples/overview.ts} +0 -0
package/test/tag.test.ts
ADDED
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { attribute, compile, glsl, interleave, uniform } from '../src/tag'
|
|
3
|
+
import type { GL } from '../src/types'
|
|
4
|
+
import { createMockGL } from './mocks/webgl'
|
|
5
|
+
|
|
6
|
+
describe('uniform', () => {
|
|
7
|
+
it('should create uniform tags for basic types', () => {
|
|
8
|
+
expect(uniform.float('u_time')).toEqual({
|
|
9
|
+
type: 'uniform',
|
|
10
|
+
key: 'u_time',
|
|
11
|
+
kind: 'float',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
expect(uniform.vec2('u_resolution')).toEqual({
|
|
15
|
+
type: 'uniform',
|
|
16
|
+
key: 'u_resolution',
|
|
17
|
+
kind: 'vec2',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
expect(uniform.vec3('u_position')).toEqual({
|
|
21
|
+
type: 'uniform',
|
|
22
|
+
key: 'u_position',
|
|
23
|
+
kind: 'vec3',
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
expect(uniform.vec4('u_color')).toEqual({
|
|
27
|
+
type: 'uniform',
|
|
28
|
+
key: 'u_color',
|
|
29
|
+
kind: 'vec4',
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should create uniform tags for integer types', () => {
|
|
34
|
+
expect(uniform.int('u_count')).toEqual({
|
|
35
|
+
type: 'uniform',
|
|
36
|
+
key: 'u_count',
|
|
37
|
+
kind: 'int',
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expect(uniform.ivec2('u_indices')).toEqual({
|
|
41
|
+
type: 'uniform',
|
|
42
|
+
key: 'u_indices',
|
|
43
|
+
kind: 'ivec2',
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should create uniform tags for matrix types', () => {
|
|
48
|
+
expect(uniform.mat2('u_mat2')).toEqual({
|
|
49
|
+
type: 'uniform',
|
|
50
|
+
key: 'u_mat2',
|
|
51
|
+
kind: 'mat2',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(uniform.mat3('u_mat3')).toEqual({
|
|
55
|
+
type: 'uniform',
|
|
56
|
+
key: 'u_mat3',
|
|
57
|
+
kind: 'mat3',
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
expect(uniform.mat4('u_modelMatrix')).toEqual({
|
|
61
|
+
type: 'uniform',
|
|
62
|
+
key: 'u_modelMatrix',
|
|
63
|
+
kind: 'mat4',
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should create uniform tags for sampler types', () => {
|
|
68
|
+
expect(uniform.sampler2D('u_texture')).toEqual({
|
|
69
|
+
type: 'uniform',
|
|
70
|
+
key: 'u_texture',
|
|
71
|
+
kind: 'sampler2D',
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
expect(uniform.samplerCube('u_envMap')).toEqual({
|
|
75
|
+
type: 'uniform',
|
|
76
|
+
key: 'u_envMap',
|
|
77
|
+
kind: 'samplerCube',
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should create uniform tags with size option for arrays', () => {
|
|
82
|
+
expect(uniform.float('u_values', { size: 10 })).toEqual({
|
|
83
|
+
type: 'uniform',
|
|
84
|
+
key: 'u_values',
|
|
85
|
+
kind: 'float',
|
|
86
|
+
size: 10,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(uniform.vec3('u_palette', { size: 5 })).toEqual({
|
|
90
|
+
type: 'uniform',
|
|
91
|
+
key: 'u_palette',
|
|
92
|
+
kind: 'vec3',
|
|
93
|
+
size: 5,
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should handle symbol properties correctly', () => {
|
|
98
|
+
const symbolKey = Symbol('test')
|
|
99
|
+
// @ts-expect-error Testing symbol access
|
|
100
|
+
expect(uniform[symbolKey]).toBeUndefined()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('attribute', () => {
|
|
105
|
+
it('should create attribute tags for basic types', () => {
|
|
106
|
+
expect(attribute.float('a_t')).toEqual({
|
|
107
|
+
type: 'attribute',
|
|
108
|
+
key: 'a_t',
|
|
109
|
+
kind: 'float',
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
expect(attribute.vec2('a_position')).toEqual({
|
|
113
|
+
type: 'attribute',
|
|
114
|
+
key: 'a_position',
|
|
115
|
+
kind: 'vec2',
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
expect(attribute.vec3('a_normal')).toEqual({
|
|
119
|
+
type: 'attribute',
|
|
120
|
+
key: 'a_normal',
|
|
121
|
+
kind: 'vec3',
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
expect(attribute.vec4('a_color')).toEqual({
|
|
125
|
+
type: 'attribute',
|
|
126
|
+
key: 'a_color',
|
|
127
|
+
kind: 'vec4',
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('should create attribute tags for integer types', () => {
|
|
132
|
+
expect(attribute.int('a_index')).toEqual({
|
|
133
|
+
type: 'attribute',
|
|
134
|
+
key: 'a_index',
|
|
135
|
+
kind: 'int',
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
expect(attribute.ivec2('a_coords')).toEqual({
|
|
139
|
+
type: 'attribute',
|
|
140
|
+
key: 'a_coords',
|
|
141
|
+
kind: 'ivec2',
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('should create instanced attributes', () => {
|
|
146
|
+
expect(attribute.vec3('a_instancePos', { instanced: true })).toEqual({
|
|
147
|
+
type: 'attribute',
|
|
148
|
+
key: 'a_instancePos',
|
|
149
|
+
kind: 'vec3',
|
|
150
|
+
instanced: true,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
expect(attribute.float('a_instanceScale', { instanced: true })).toEqual({
|
|
154
|
+
type: 'attribute',
|
|
155
|
+
key: 'a_instanceScale',
|
|
156
|
+
kind: 'float',
|
|
157
|
+
instanced: true,
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should create attributes with custom buffer', () => {
|
|
162
|
+
const customBuffer = {} as WebGLBuffer
|
|
163
|
+
expect(attribute.vec2('a_position', { buffer: customBuffer })).toEqual({
|
|
164
|
+
type: 'attribute',
|
|
165
|
+
key: 'a_position',
|
|
166
|
+
kind: 'vec2',
|
|
167
|
+
buffer: customBuffer,
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should handle symbol properties correctly', () => {
|
|
172
|
+
const symbolKey = Symbol('test')
|
|
173
|
+
// @ts-expect-error Testing symbol access
|
|
174
|
+
expect(attribute[symbolKey]).toBeUndefined()
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('interleave', () => {
|
|
179
|
+
it('should create interleaved attribute tags', () => {
|
|
180
|
+
expect(
|
|
181
|
+
interleave('vertexData', [attribute.vec2('a_position'), attribute.vec4('a_color')]),
|
|
182
|
+
).toEqual({
|
|
183
|
+
type: 'interleavedAttribute',
|
|
184
|
+
key: 'vertexData',
|
|
185
|
+
layout: [
|
|
186
|
+
{ key: 'a_position', kind: 'vec2' },
|
|
187
|
+
{ key: 'a_color', kind: 'vec4' },
|
|
188
|
+
],
|
|
189
|
+
instanced: false,
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should create instanced interleaved attributes', () => {
|
|
194
|
+
expect(
|
|
195
|
+
interleave(
|
|
196
|
+
'instanceData',
|
|
197
|
+
[attribute.vec3('a_instancePos'), attribute.float('a_instanceScale')],
|
|
198
|
+
{ instanced: true },
|
|
199
|
+
),
|
|
200
|
+
).toEqual({
|
|
201
|
+
type: 'interleavedAttribute',
|
|
202
|
+
key: 'instanceData',
|
|
203
|
+
layout: [
|
|
204
|
+
{ key: 'a_instancePos', kind: 'vec3' },
|
|
205
|
+
{ key: 'a_instanceScale', kind: 'float' },
|
|
206
|
+
],
|
|
207
|
+
instanced: true,
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should handle custom buffer in options', () => {
|
|
212
|
+
const customBuffer = {} as WebGLBuffer
|
|
213
|
+
expect(interleave('uvData', [attribute.vec2('a_uv')], { buffer: customBuffer })).toEqual({
|
|
214
|
+
type: 'interleavedAttribute',
|
|
215
|
+
key: 'uvData',
|
|
216
|
+
layout: [{ key: 'a_uv', kind: 'vec2' }],
|
|
217
|
+
instanced: false,
|
|
218
|
+
buffer: customBuffer,
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should strip instanced property from layout attributes', () => {
|
|
223
|
+
expect(
|
|
224
|
+
interleave('data', [attribute.vec2('a_position'), attribute.vec4('a_color')]).layout,
|
|
225
|
+
).toEqual([
|
|
226
|
+
{ key: 'a_position', kind: 'vec2' },
|
|
227
|
+
{ key: 'a_color', kind: 'vec4' },
|
|
228
|
+
])
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('glsl', () => {
|
|
233
|
+
it('should create GLSL template with uniforms and attributes', () => {
|
|
234
|
+
const result = glsl`
|
|
235
|
+
${uniform.vec2('u_resolution')}
|
|
236
|
+
${attribute.vec2('a_position')}
|
|
237
|
+
|
|
238
|
+
void main() {
|
|
239
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
240
|
+
}
|
|
241
|
+
`
|
|
242
|
+
|
|
243
|
+
expect(result.template).toContain('uniform vec2 u_resolution;')
|
|
244
|
+
expect(result.template).toContain('attribute vec2 a_position;')
|
|
245
|
+
expect(result.template).toContain('void main()')
|
|
246
|
+
|
|
247
|
+
expect(result.uniforms).toEqual({
|
|
248
|
+
u_resolution: { kind: 'vec2' },
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
expect(result.attributes).toEqual({
|
|
252
|
+
a_position: { kind: 'vec2' },
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
expect(result.interleavedAttributes).toEqual({})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('should handle WebGL2 syntax with #version 300 es', () => {
|
|
259
|
+
const result = glsl`#version 300 es
|
|
260
|
+
${uniform.vec2('u_resolution')}
|
|
261
|
+
${attribute.vec2('a_position')}
|
|
262
|
+
|
|
263
|
+
void main() {
|
|
264
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
265
|
+
}
|
|
266
|
+
`
|
|
267
|
+
|
|
268
|
+
expect(result.template).toContain('uniform vec2 u_resolution;')
|
|
269
|
+
expect(result.template).toContain('in vec2 a_position;') // 'in' instead of 'attribute'
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should handle interleaved attributes', () => {
|
|
273
|
+
const result = glsl`
|
|
274
|
+
${interleave('vertexData', [attribute.vec2('a_position'), attribute.vec4('a_color')])}
|
|
275
|
+
|
|
276
|
+
void main() {
|
|
277
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
278
|
+
}
|
|
279
|
+
`
|
|
280
|
+
|
|
281
|
+
expect(result.template).toContain('attribute vec2 a_position;')
|
|
282
|
+
expect(result.template).toContain('attribute vec4 a_color;')
|
|
283
|
+
|
|
284
|
+
expect(result.interleavedAttributes).toEqual({
|
|
285
|
+
vertexData: {
|
|
286
|
+
layout: [
|
|
287
|
+
{ key: 'a_position', kind: 'vec2' },
|
|
288
|
+
{ key: 'a_color', kind: 'vec4' },
|
|
289
|
+
],
|
|
290
|
+
instanced: false,
|
|
291
|
+
},
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('should handle interleaved attributes in WebGL2', () => {
|
|
296
|
+
const result = glsl`#version 300 es
|
|
297
|
+
${interleave('vertexData', [attribute.vec2('a_position'), attribute.vec4('a_color')])}
|
|
298
|
+
|
|
299
|
+
void main() {
|
|
300
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
301
|
+
}
|
|
302
|
+
`
|
|
303
|
+
|
|
304
|
+
expect(result.template).toContain('in vec2 a_position;')
|
|
305
|
+
expect(result.template).toContain('in vec4 a_color;')
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('should handle uniform arrays', () => {
|
|
309
|
+
const result = glsl`
|
|
310
|
+
${uniform.vec3('u_palette', { size: 7 })}
|
|
311
|
+
|
|
312
|
+
void main() {
|
|
313
|
+
gl_FragColor = vec4(u_palette[0], 1.0);
|
|
314
|
+
}
|
|
315
|
+
`
|
|
316
|
+
|
|
317
|
+
expect(result.template).toContain('uniform vec3 u_palette[7];')
|
|
318
|
+
|
|
319
|
+
expect(result.uniforms).toEqual({
|
|
320
|
+
u_palette: { kind: 'vec3', size: 7 },
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('should handle string and number interpolations', () => {
|
|
325
|
+
const maxLights = 8
|
|
326
|
+
const shaderName = 'MyShader'
|
|
327
|
+
|
|
328
|
+
const result = glsl`
|
|
329
|
+
// ${shaderName}
|
|
330
|
+
#define MAX_LIGHTS ${maxLights}
|
|
331
|
+
${uniform.float('u_time')}
|
|
332
|
+
|
|
333
|
+
void main() {
|
|
334
|
+
gl_FragColor = vec4(1.0);
|
|
335
|
+
}
|
|
336
|
+
`
|
|
337
|
+
|
|
338
|
+
expect(result.template).toContain('// MyShader')
|
|
339
|
+
expect(result.template).toContain('#define MAX_LIGHTS 8')
|
|
340
|
+
expect(result.template).toContain('uniform float u_time;')
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it('should handle multiple attributes and uniforms', () => {
|
|
344
|
+
const result = glsl`
|
|
345
|
+
${uniform.mat4('u_mvpMatrix')}
|
|
346
|
+
${uniform.sampler2D('u_texture')}
|
|
347
|
+
${attribute.vec3('a_position')}
|
|
348
|
+
${attribute.vec2('a_uv')}
|
|
349
|
+
${attribute.vec4('a_color', { instanced: true })}
|
|
350
|
+
|
|
351
|
+
void main() {
|
|
352
|
+
gl_Position = u_mvpMatrix * vec4(a_position, 1.0);
|
|
353
|
+
}
|
|
354
|
+
`
|
|
355
|
+
|
|
356
|
+
expect(result.uniforms).toEqual({
|
|
357
|
+
u_mvpMatrix: { kind: 'mat4' },
|
|
358
|
+
u_texture: { kind: 'sampler2D' },
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
expect(result.attributes).toEqual({
|
|
362
|
+
a_position: { kind: 'vec3' },
|
|
363
|
+
a_uv: { kind: 'vec2' },
|
|
364
|
+
a_color: { kind: 'vec4', instanced: true },
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it('should handle empty template', () => {
|
|
369
|
+
const result = glsl``
|
|
370
|
+
|
|
371
|
+
expect(result.template).toBe('')
|
|
372
|
+
expect(result.uniforms).toEqual({})
|
|
373
|
+
expect(result.attributes).toEqual({})
|
|
374
|
+
expect(result.interleavedAttributes).toEqual({})
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('should skip string resources when collecting schema', () => {
|
|
378
|
+
const result = glsl`
|
|
379
|
+
${'// This is a comment'}
|
|
380
|
+
${42}
|
|
381
|
+
${uniform.float('u_time')}
|
|
382
|
+
${'precision highp float;'}
|
|
383
|
+
|
|
384
|
+
void main() {}
|
|
385
|
+
`
|
|
386
|
+
|
|
387
|
+
expect(result.uniforms).toEqual({
|
|
388
|
+
u_time: { kind: 'float' },
|
|
389
|
+
})
|
|
390
|
+
expect(Object.keys(result.uniforms).length).toBe(1)
|
|
391
|
+
})
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
describe('glsl with symbols', () => {
|
|
395
|
+
it('should support symbols in uniform tags', () => {
|
|
396
|
+
const u_time_symbol = Symbol('u_time')
|
|
397
|
+
const u_color_symbol = Symbol('u_color')
|
|
398
|
+
|
|
399
|
+
const shader = glsl`
|
|
400
|
+
precision mediump float;
|
|
401
|
+
|
|
402
|
+
${uniform.float(u_time_symbol)}
|
|
403
|
+
${uniform.vec3(u_color_symbol)}
|
|
404
|
+
|
|
405
|
+
void main() {
|
|
406
|
+
gl_FragColor = vec4(${u_color_symbol} * ${u_time_symbol}, 1.0);
|
|
407
|
+
}`
|
|
408
|
+
|
|
409
|
+
expect(shader.uniforms[u_time_symbol]).toEqual({ kind: 'float' })
|
|
410
|
+
expect(shader.uniforms[u_color_symbol]).toEqual({ kind: 'vec3' })
|
|
411
|
+
expect(shader.template).toContain('uniform float VIEW_GL_ALIAS_')
|
|
412
|
+
expect(shader.template).toContain('uniform vec3 VIEW_GL_ALIAS_')
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('should support symbols in attribute tags', () => {
|
|
416
|
+
const a_position_symbol = Symbol('a_position')
|
|
417
|
+
const a_color_symbol = Symbol('a_color')
|
|
418
|
+
|
|
419
|
+
const shader = glsl`
|
|
420
|
+
${attribute.vec2(a_position_symbol)}
|
|
421
|
+
${attribute.vec4(a_color_symbol, { instanced: true })}
|
|
422
|
+
|
|
423
|
+
void main() {
|
|
424
|
+
gl_Position = vec4(${a_position_symbol}, 0.0, 1.0);
|
|
425
|
+
}`
|
|
426
|
+
|
|
427
|
+
expect(shader.attributes[a_position_symbol]).toEqual({ kind: 'vec2' })
|
|
428
|
+
expect(shader.attributes[a_color_symbol]).toEqual({ kind: 'vec4', instanced: true })
|
|
429
|
+
expect(shader.template).toContain('attribute vec2 VIEW_GL_ALIAS_')
|
|
430
|
+
expect(shader.template).toContain('attribute vec4 VIEW_GL_ALIAS_')
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it('should support symbols in interleaved attribute tags', () => {
|
|
434
|
+
const data_symbol = Symbol('data')
|
|
435
|
+
const a_pos_symbol = Symbol('a_pos')
|
|
436
|
+
const a_col_symbol = Symbol('a_col')
|
|
437
|
+
|
|
438
|
+
const shader = glsl`
|
|
439
|
+
${interleave(data_symbol, [attribute.vec3(a_pos_symbol), attribute.vec4(a_col_symbol)])}
|
|
440
|
+
|
|
441
|
+
void main() {
|
|
442
|
+
gl_Position = vec4(${a_pos_symbol}, 1.0);
|
|
443
|
+
}`
|
|
444
|
+
|
|
445
|
+
expect(shader.interleavedAttributes[data_symbol]).toEqual({
|
|
446
|
+
layout: [
|
|
447
|
+
{ key: a_pos_symbol, kind: 'vec3' },
|
|
448
|
+
{ key: a_col_symbol, kind: 'vec4' },
|
|
449
|
+
],
|
|
450
|
+
instanced: false,
|
|
451
|
+
})
|
|
452
|
+
expect(shader.template).toContain('attribute vec3 VIEW_GL_ALIAS_')
|
|
453
|
+
expect(shader.template).toContain('attribute vec4 VIEW_GL_ALIAS_')
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it('should support mixed string and symbol keys', () => {
|
|
457
|
+
const u_color_symbol = Symbol('u_color')
|
|
458
|
+
const a_normal_symbol = Symbol('a_normal')
|
|
459
|
+
|
|
460
|
+
const shader = glsl`
|
|
461
|
+
${uniform.float('u_time')}
|
|
462
|
+
${uniform.vec3(u_color_symbol)}
|
|
463
|
+
${attribute.vec3('a_position')}
|
|
464
|
+
${attribute.vec3(a_normal_symbol)}
|
|
465
|
+
|
|
466
|
+
void main() {
|
|
467
|
+
gl_Position = vec4(a_position + ${a_normal_symbol}, u_time);
|
|
468
|
+
gl_FragColor = vec4(${u_color_symbol}, 1.0);
|
|
469
|
+
}`
|
|
470
|
+
|
|
471
|
+
expect(shader.uniforms.u_time).toEqual({ kind: 'float' })
|
|
472
|
+
expect(shader.uniforms[u_color_symbol]).toEqual({ kind: 'vec3' })
|
|
473
|
+
expect(shader.attributes.a_position).toEqual({ kind: 'vec3' })
|
|
474
|
+
expect(shader.attributes[a_normal_symbol]).toEqual({ kind: 'vec3' })
|
|
475
|
+
|
|
476
|
+
expect(shader.template).toContain('uniform float u_time')
|
|
477
|
+
expect(shader.template).toContain('uniform vec3 VIEW_GL_ALIAS_')
|
|
478
|
+
expect(shader.template).toContain('attribute vec3 a_position')
|
|
479
|
+
expect(shader.template).toContain('attribute vec3 VIEW_GL_ALIAS_')
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
it('should support symbols in array uniforms', () => {
|
|
483
|
+
const u_positions_symbol = Symbol('u_positions')
|
|
484
|
+
|
|
485
|
+
const shader = glsl`
|
|
486
|
+
${uniform.vec3(u_positions_symbol, { size: 10 })}
|
|
487
|
+
|
|
488
|
+
void main() {
|
|
489
|
+
gl_FragColor = vec4(${u_positions_symbol}[0], 1.0);
|
|
490
|
+
}`
|
|
491
|
+
|
|
492
|
+
expect(shader.uniforms[u_positions_symbol]).toEqual({ kind: 'vec3', size: 10 })
|
|
493
|
+
expect(shader.template).toContain('uniform vec3 VIEW_GL_ALIAS_')
|
|
494
|
+
expect(shader.template).toContain('[10];')
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
it('should preserve symbol identity across multiple uses', () => {
|
|
498
|
+
const u_shared_symbol = Symbol('u_shared')
|
|
499
|
+
|
|
500
|
+
const shader1 = glsl`${uniform.float(u_shared_symbol)}`
|
|
501
|
+
const shader2 = glsl`${uniform.float(u_shared_symbol)}`
|
|
502
|
+
|
|
503
|
+
// Both should generate the same alias
|
|
504
|
+
const alias1 = shader1.template.match(/VIEW_GL_ALIAS_\d+/)?.[0]
|
|
505
|
+
const alias2 = shader2.template.match(/VIEW_GL_ALIAS_\d+/)?.[0]
|
|
506
|
+
|
|
507
|
+
expect(alias1).toBe(alias2)
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
it('should support symbols in WebGL 2.0 shaders', () => {
|
|
511
|
+
const u_matrix_symbol = Symbol('u_matrix')
|
|
512
|
+
const a_vertex_symbol = Symbol('a_vertex')
|
|
513
|
+
|
|
514
|
+
const shader = glsl`#version 300 es
|
|
515
|
+
precision mediump float;
|
|
516
|
+
|
|
517
|
+
${uniform.mat4(u_matrix_symbol)}
|
|
518
|
+
${attribute.vec3(a_vertex_symbol)}
|
|
519
|
+
|
|
520
|
+
void main() {
|
|
521
|
+
gl_Position = ${u_matrix_symbol} * vec4(${a_vertex_symbol}, 1.0);
|
|
522
|
+
}`
|
|
523
|
+
|
|
524
|
+
expect(shader.template).toContain('uniform mat4 VIEW_GL_ALIAS_')
|
|
525
|
+
expect(shader.template).toContain('in vec3 VIEW_GL_ALIAS_') // WebGL2 uses 'in' instead of 'attribute'
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('should handle symbol interpolation in template', () => {
|
|
529
|
+
const color_symbol = Symbol('color')
|
|
530
|
+
|
|
531
|
+
const shader = glsl`
|
|
532
|
+
void main() {
|
|
533
|
+
vec3 ${color_symbol} = vec3(1.0, 0.0, 0.0);
|
|
534
|
+
gl_FragColor = vec4(${color_symbol}, 1.0);
|
|
535
|
+
}`
|
|
536
|
+
|
|
537
|
+
// Symbol used directly in template should be converted to alias
|
|
538
|
+
expect(shader.template).toMatch(/vec3 VIEW_GL_ALIAS_\d+ = vec3/)
|
|
539
|
+
expect(shader.template).toMatch(/vec4\(VIEW_GL_ALIAS_\d+, 1\.0\)/)
|
|
540
|
+
})
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
describe('compile with symbols', () => {
|
|
544
|
+
let gl: GL
|
|
545
|
+
let mockProgram: WebGLProgram
|
|
546
|
+
|
|
547
|
+
beforeEach(() => {
|
|
548
|
+
gl = createMockGL()
|
|
549
|
+
mockProgram = gl.createProgram()!
|
|
550
|
+
|
|
551
|
+
// Mock shader creation and compilation
|
|
552
|
+
gl.createShader = vi.fn(type => ({ type } as WebGLShader))
|
|
553
|
+
gl.shaderSource = vi.fn()
|
|
554
|
+
gl.compileShader = vi.fn()
|
|
555
|
+
gl.getShaderParameter = vi.fn(() => true)
|
|
556
|
+
gl.attachShader = vi.fn()
|
|
557
|
+
gl.linkProgram = vi.fn()
|
|
558
|
+
gl.getProgramParameter = vi.fn(() => true)
|
|
559
|
+
gl.createProgram = vi.fn(() => mockProgram)
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
it('should compile shaders with symbol keys', () => {
|
|
563
|
+
const u_time_symbol = Symbol('u_time')
|
|
564
|
+
const a_position_symbol = Symbol('a_position')
|
|
565
|
+
|
|
566
|
+
const vertex = glsl`
|
|
567
|
+
${attribute.vec2(a_position_symbol)}
|
|
568
|
+
${uniform.float(u_time_symbol)}
|
|
569
|
+
|
|
570
|
+
void main() {
|
|
571
|
+
gl_Position = vec4(${a_position_symbol}, 0.0, 1.0);
|
|
572
|
+
}`
|
|
573
|
+
|
|
574
|
+
const fragment = glsl`
|
|
575
|
+
precision mediump float;
|
|
576
|
+
|
|
577
|
+
${uniform.float(u_time_symbol)}
|
|
578
|
+
|
|
579
|
+
void main() {
|
|
580
|
+
gl_FragColor = vec4(${u_time_symbol}, 0.0, 0.0, 1.0);
|
|
581
|
+
}`
|
|
582
|
+
|
|
583
|
+
const { program, schema } = compile(gl, vertex, fragment)
|
|
584
|
+
|
|
585
|
+
expect(program).toBe(mockProgram)
|
|
586
|
+
expect(schema.uniforms[u_time_symbol]).toEqual({ kind: 'float' })
|
|
587
|
+
expect(schema.attributes[a_position_symbol]).toEqual({ kind: 'vec2' })
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
it('should merge symbols from vertex and fragment shaders', () => {
|
|
591
|
+
const u_shared_symbol = Symbol('u_shared')
|
|
592
|
+
const u_vertex_only = Symbol('u_vertex_only')
|
|
593
|
+
const u_fragment_only = Symbol('u_fragment_only')
|
|
594
|
+
|
|
595
|
+
const vertex = glsl`
|
|
596
|
+
${uniform.float(u_shared_symbol)}
|
|
597
|
+
${uniform.vec2(u_vertex_only)}
|
|
598
|
+
|
|
599
|
+
void main() {
|
|
600
|
+
gl_Position = vec4(${u_vertex_only}, ${u_shared_symbol}, 1.0);
|
|
601
|
+
}`
|
|
602
|
+
|
|
603
|
+
const fragment = glsl`
|
|
604
|
+
precision mediump float;
|
|
605
|
+
|
|
606
|
+
${uniform.float(u_shared_symbol)}
|
|
607
|
+
${uniform.vec3(u_fragment_only)}
|
|
608
|
+
|
|
609
|
+
void main() {
|
|
610
|
+
gl_FragColor = vec4(${u_fragment_only}, ${u_shared_symbol});
|
|
611
|
+
}`
|
|
612
|
+
|
|
613
|
+
const { schema } = compile(gl, vertex, fragment)
|
|
614
|
+
|
|
615
|
+
expect(schema.uniforms[u_shared_symbol]).toEqual({ kind: 'float' })
|
|
616
|
+
expect(schema.uniforms[u_vertex_only]).toEqual({ kind: 'vec2' })
|
|
617
|
+
expect(schema.uniforms[u_fragment_only]).toEqual({ kind: 'vec3' })
|
|
618
|
+
})
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
describe('compile', () => {
|
|
622
|
+
let gl: GL
|
|
623
|
+
let mockProgram: WebGLProgram
|
|
624
|
+
|
|
625
|
+
beforeEach(() => {
|
|
626
|
+
gl = createMockGL()
|
|
627
|
+
mockProgram = gl.createProgram()!
|
|
628
|
+
|
|
629
|
+
// Mock shader creation and compilation
|
|
630
|
+
gl.createShader = vi.fn(type => ({ type } as WebGLShader))
|
|
631
|
+
gl.shaderSource = vi.fn()
|
|
632
|
+
gl.compileShader = vi.fn()
|
|
633
|
+
gl.getShaderParameter = vi.fn(() => true)
|
|
634
|
+
gl.attachShader = vi.fn()
|
|
635
|
+
gl.linkProgram = vi.fn()
|
|
636
|
+
gl.getProgramParameter = vi.fn(() => true)
|
|
637
|
+
gl.createProgram = vi.fn(() => mockProgram)
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it('should compile shaders and return program with schema', () => {
|
|
641
|
+
const vertex = glsl`
|
|
642
|
+
${attribute.vec2('a_position')}
|
|
643
|
+
${uniform.mat4('u_mvpMatrix')}
|
|
644
|
+
|
|
645
|
+
void main() {
|
|
646
|
+
gl_Position = u_mvpMatrix * vec4(a_position, 0.0, 1.0);
|
|
647
|
+
}
|
|
648
|
+
`
|
|
649
|
+
|
|
650
|
+
const fragment = glsl`
|
|
651
|
+
${uniform.vec4('u_color')}
|
|
652
|
+
${uniform.sampler2D('u_texture')}
|
|
653
|
+
|
|
654
|
+
void main() {
|
|
655
|
+
gl_FragColor = u_color * texture2D(u_texture, vec2(0.0));
|
|
656
|
+
}
|
|
657
|
+
`
|
|
658
|
+
|
|
659
|
+
const result = compile(gl, vertex, fragment)
|
|
660
|
+
|
|
661
|
+
expect(result.program).toBe(mockProgram)
|
|
662
|
+
|
|
663
|
+
expect(result.schema.uniforms).toEqual({
|
|
664
|
+
u_mvpMatrix: { kind: 'mat4' },
|
|
665
|
+
u_color: { kind: 'vec4' },
|
|
666
|
+
u_texture: { kind: 'sampler2D' },
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
expect(result.schema.attributes).toEqual({
|
|
670
|
+
a_position: { kind: 'vec2' },
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
expect(result.schema.interleavedAttributes).toEqual({})
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
it('should merge uniforms from vertex and fragment shaders', () => {
|
|
677
|
+
const vertex = glsl`
|
|
678
|
+
${uniform.vec2('u_resolution')}
|
|
679
|
+
${uniform.float('u_time')}
|
|
680
|
+
|
|
681
|
+
void main() {}
|
|
682
|
+
`
|
|
683
|
+
|
|
684
|
+
const fragment = glsl`
|
|
685
|
+
${uniform.float('u_time')} // Same uniform in both shaders
|
|
686
|
+
${uniform.vec3('u_color')}
|
|
687
|
+
|
|
688
|
+
void main() {}
|
|
689
|
+
`
|
|
690
|
+
|
|
691
|
+
const result = compile(gl, vertex, fragment)
|
|
692
|
+
|
|
693
|
+
expect(result.schema.uniforms).toEqual({
|
|
694
|
+
u_resolution: { kind: 'vec2' },
|
|
695
|
+
u_time: { kind: 'float' },
|
|
696
|
+
u_color: { kind: 'vec3' },
|
|
697
|
+
})
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
it('should handle interleaved attributes in schema', () => {
|
|
701
|
+
const vertexData = interleave('vertexData', [
|
|
702
|
+
attribute.vec2('a_position'),
|
|
703
|
+
attribute.vec2('a_uv'),
|
|
704
|
+
])
|
|
705
|
+
|
|
706
|
+
const instanceData = interleave(
|
|
707
|
+
'instanceData',
|
|
708
|
+
[attribute.vec3('a_instancePos'), attribute.float('a_instanceScale')],
|
|
709
|
+
{ instanced: true },
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
const vertex = glsl`
|
|
713
|
+
${vertexData}
|
|
714
|
+
${instanceData}
|
|
715
|
+
|
|
716
|
+
void main() {
|
|
717
|
+
gl_Position = vec4(a_position + a_instancePos.xy, 0.0, 1.0);
|
|
718
|
+
}
|
|
719
|
+
`
|
|
720
|
+
|
|
721
|
+
const fragment = glsl`
|
|
722
|
+
void main() {
|
|
723
|
+
gl_FragColor = vec4(1.0);
|
|
724
|
+
}
|
|
725
|
+
`
|
|
726
|
+
|
|
727
|
+
const result = compile(gl, vertex, fragment)
|
|
728
|
+
|
|
729
|
+
expect(result.schema.interleavedAttributes).toEqual({
|
|
730
|
+
vertexData: {
|
|
731
|
+
layout: [
|
|
732
|
+
{ key: 'a_position', kind: 'vec2' },
|
|
733
|
+
{ key: 'a_uv', kind: 'vec2' },
|
|
734
|
+
],
|
|
735
|
+
instanced: false,
|
|
736
|
+
},
|
|
737
|
+
instanceData: {
|
|
738
|
+
layout: [
|
|
739
|
+
{ key: 'a_instancePos', kind: 'vec3' },
|
|
740
|
+
{ key: 'a_instanceScale', kind: 'float' },
|
|
741
|
+
],
|
|
742
|
+
instanced: true,
|
|
743
|
+
},
|
|
744
|
+
})
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
it('should handle shaders with only template strings', () => {
|
|
748
|
+
const vertex = glsl`
|
|
749
|
+
void main() {
|
|
750
|
+
gl_Position = vec4(0.0);
|
|
751
|
+
}
|
|
752
|
+
`
|
|
753
|
+
|
|
754
|
+
const fragment = glsl`
|
|
755
|
+
void main() {
|
|
756
|
+
gl_FragColor = vec4(1.0);
|
|
757
|
+
}
|
|
758
|
+
`
|
|
759
|
+
|
|
760
|
+
const result = compile(gl, vertex, fragment)
|
|
761
|
+
|
|
762
|
+
expect(result.program).toBe(mockProgram)
|
|
763
|
+
expect(result.schema.uniforms).toEqual({})
|
|
764
|
+
expect(result.schema.attributes).toEqual({})
|
|
765
|
+
expect(result.schema.interleavedAttributes).toEqual({})
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
it('should call WebGL functions in correct order', () => {
|
|
769
|
+
const vertex = glsl`void main() {}`
|
|
770
|
+
const fragment = glsl`void main() {}`
|
|
771
|
+
|
|
772
|
+
compile(gl, vertex, fragment)
|
|
773
|
+
|
|
774
|
+
// Check shader creation
|
|
775
|
+
expect(gl.createShader).toHaveBeenCalledTimes(2)
|
|
776
|
+
expect(gl.createShader).toHaveBeenCalledWith(gl.VERTEX_SHADER)
|
|
777
|
+
expect(gl.createShader).toHaveBeenCalledWith(gl.FRAGMENT_SHADER)
|
|
778
|
+
|
|
779
|
+
// Check shader compilation
|
|
780
|
+
expect(gl.shaderSource).toHaveBeenCalledTimes(2)
|
|
781
|
+
expect(gl.compileShader).toHaveBeenCalledTimes(2)
|
|
782
|
+
expect(gl.getShaderParameter).toHaveBeenCalledTimes(2)
|
|
783
|
+
|
|
784
|
+
// Check program creation and linking
|
|
785
|
+
expect(gl.createProgram).toHaveBeenCalled()
|
|
786
|
+
expect(gl.attachShader).toHaveBeenCalledTimes(2)
|
|
787
|
+
expect(gl.linkProgram).toHaveBeenCalled()
|
|
788
|
+
expect(gl.getProgramParameter).toHaveBeenCalled()
|
|
789
|
+
})
|
|
790
|
+
})
|