@bigmistqke/view.gl 0.1.8 → 0.1.10

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.
Files changed (44) hide show
  1. package/.claude/settings.local.json +4 -8
  2. package/README.md +30 -0
  3. package/dev/src/examples/bezier.ts +3 -2
  4. package/dev/src/examples/game-of-life.ts +13 -30
  5. package/dev/src/examples/glsl-composition.ts +3 -3
  6. package/dev/src/examples/grid.ts +20 -14
  7. package/dev/src/examples/pix-sim.ts +29 -22
  8. package/dev/src/examples/ray-casting.ts +455 -0
  9. package/dev/src/examples/sdf.ts +98 -0
  10. package/dev/src/examples/symbol.ts +2 -2
  11. package/dev/src/index.ts +12 -11
  12. package/dev/src/utils.ts +18 -4
  13. package/dist/gl.d.mts +6 -0
  14. package/dist/gl.mjs +39 -0
  15. package/dist/gl.mjs.map +1 -0
  16. package/dist/index.d.mts +2 -2
  17. package/dist/index.mjs +27 -38
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/tag.d.mts +21 -4
  20. package/dist/tag.mjs +140 -110
  21. package/dist/tag.mjs.map +1 -1
  22. package/dist/{types-Bp9VAVe3.d.mts → types-DaV9McmR.d.mts} +18 -14
  23. package/package.json +10 -4
  24. package/src/gl.ts +45 -0
  25. package/src/index.ts +1 -1
  26. package/src/tag.ts +112 -44
  27. package/src/types.ts +36 -22
  28. package/src/utils/index.ts +0 -46
  29. package/test/tag.test.ts +462 -0
  30. package/tsup.config.ts +1 -1
  31. package/dev/dist/404.html +0 -19
  32. package/dev/dist/assets/bezier-DuLTb2d1.js +0 -294
  33. package/dev/dist/assets/game_of_life-D7qHdFcv.js +0 -202
  34. package/dev/dist/assets/grid-Ie22U_9K.js +0 -173
  35. package/dev/dist/assets/index-0qrXMlfv.js +0 -109
  36. package/dev/dist/assets/index-C2N3u8wj.css +0 -12
  37. package/dev/dist/assets/main-BZ1N6pp6.js +0 -32
  38. package/dev/dist/assets/overview-DAxtMIw5.js +0 -78
  39. package/dev/dist/assets/pix_sim-DIhzM_vu.js +0 -501
  40. package/dev/dist/assets/symbol-BouRL-Ni.js +0 -65
  41. package/dev/dist/assets/tag-C_bg62fD.js +0 -516
  42. package/dev/dist/assets/tetris-IMnkSi9p.js +0 -344
  43. package/dev/dist/assets/utils-CvCgsM08.js +0 -50
  44. package/dev/dist/index.html +0 -51
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigmistqke/view.gl",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "👀 Utilities for managing WebGL resources.",
5
5
  "exports": {
6
6
  ".": {
@@ -10,6 +10,10 @@
10
10
  "./tag": {
11
11
  "import": "./dist/tag.mjs",
12
12
  "types": "./dist/tag.d.mts"
13
+ },
14
+ "./gl": {
15
+ "import": "./dist/gl.mjs",
16
+ "types": "./dist/gl.d.mts"
13
17
  }
14
18
  },
15
19
  "keywords": [
@@ -26,7 +30,8 @@
26
30
  "typescript": "^5.8.3",
27
31
  "vite": "^7.0.2",
28
32
  "vite-tsconfig-paths": "^5.1.4",
29
- "vitest": "^3.2.4"
33
+ "vitest": "^3.2.4",
34
+ "bumpp": "^10.3.2"
30
35
  },
31
36
  "scripts": {
32
37
  "build": "tsup",
@@ -35,10 +40,11 @@
35
40
  "dev:watch": "concurrently \"pnpm dev:preview\" \"vite dev:build --watch\"",
36
41
  "dev": "vite serve dev",
37
42
  "format": "prettier -w \"src/**/*.{js,ts,json,css,tsx,jsx}\" \"dev/**/*.{js,ts,json,css,tsx,jsx}\"",
38
- "prepublish": "pnpm build",
43
+ "prepublish": "pnpm test && pnpm build",
39
44
  "test": "vitest --run",
40
45
  "test:coverage": "vitest run --coverage",
41
46
  "test:ui": "vitest --ui",
42
- "lint:types": "tsc --noEmit"
47
+ "lint:types": "tsc --noEmit",
48
+ "bump": "bumpp"
43
49
  }
44
50
  }
package/src/gl.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { GL } from "./types"
2
+
3
+ export function createShader(gl: GL, type: number, source: string): WebGLShader {
4
+ const shader = gl.createShader(type)
5
+ if (!shader) throw new Error('Failed to create shader')
6
+
7
+ gl.shaderSource(shader, source)
8
+ gl.compileShader(shader)
9
+
10
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
11
+ const info = gl.getShaderInfoLog(shader)
12
+ gl.deleteShader(shader)
13
+ throw new Error(
14
+ `Failed to compile ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader: ${info}`,
15
+ )
16
+ }
17
+
18
+ return shader
19
+ }
20
+
21
+ export function createProgram(gl: GL, vertexSource: string, fragmentSource: string): WebGLProgram {
22
+ const program = gl.createProgram()
23
+ if (!program) throw new Error('Failed to create WebGL program')
24
+
25
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource)
26
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
27
+
28
+ gl.attachShader(program, vertexShader)
29
+ gl.attachShader(program, fragmentShader)
30
+ gl.linkProgram(program)
31
+
32
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
33
+ const info = gl.getProgramInfoLog(program)
34
+ gl.deleteProgram(program)
35
+ gl.deleteShader(vertexShader)
36
+ gl.deleteShader(fragmentShader)
37
+ throw new Error(`Failed to link program: ${info}`)
38
+ }
39
+
40
+ // Clean up shaders after linking
41
+ gl.deleteShader(vertexShader)
42
+ gl.deleteShader(fragmentShader)
43
+
44
+ return program
45
+ }
package/src/index.ts CHANGED
@@ -22,9 +22,9 @@ import {
22
22
  isSamplerKind,
23
23
  kindToSize,
24
24
  kindToUniformFnName,
25
- ObjectUtils,
26
25
  } from './utils'
27
26
  export * from './types'
27
+ import * as ObjectUtils from "./utils/object"
28
28
 
29
29
  /**********************************************************************************/
30
30
  /* */
package/src/tag.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { toID, view } from '.'
2
+ import { createProgram } from './gl'
2
3
  import type { Prettify } from './type-utils'
3
4
  import type {
4
5
  AttributeDefinition,
@@ -6,6 +7,7 @@ import type {
6
7
  AttributeTokenFn as AttributeTagMethod,
7
8
  AttributeToken,
8
9
  CompileResult,
10
+ GL,
9
11
  GLSL,
10
12
  GLSLSlot,
11
13
  GLSLToSchema,
@@ -16,7 +18,7 @@ import type {
16
18
  ViewSchema,
17
19
  ViewSchemaPartial,
18
20
  } from './types'
19
- import { createProgram } from './utils'
21
+ import { createUpsertMap } from './utils'
20
22
 
21
23
  export function glsl<TSlot extends GLSLSlot, TSlots extends TSlot[]>(
22
24
  template: TemplateStringsArray,
@@ -94,13 +96,23 @@ export function interleave<
94
96
  /* */
95
97
  /**********************************************************************************/
96
98
 
99
+ export interface CompileOptions<TSchema extends ViewSchemaPartial> {
100
+ schema?: TSchema
101
+ webgl2?: boolean
102
+ }
103
+
97
104
  export function compile<
98
105
  TVertex extends GLSL,
99
106
  TFragment extends GLSL,
100
- TOverride extends ViewSchemaPartial,
101
- >(gl: WebGLRenderingContext, vertex: TVertex, fragment: TFragment, overrideSchema?: TOverride) {
102
- const _vertex = resolveGLSLTag(vertex) as { template: string; schema: ViewSchema }
103
- const _fragment = resolveGLSLTag(fragment) as { template: string; schema: ViewSchema }
107
+ TSchema extends ViewSchemaPartial,
108
+ >(
109
+ gl: WebGLRenderingContext,
110
+ vertex: TVertex,
111
+ fragment: TFragment,
112
+ options?: CompileOptions<TSchema> | undefined,
113
+ ) {
114
+ const _vertex = resolveGLSLTag(vertex, options) as { template: string; schema: ViewSchema }
115
+ const _fragment = resolveGLSLTag(fragment, options) as { template: string; schema: ViewSchema }
104
116
 
105
117
  const schema = {
106
118
  uniforms: {
@@ -117,32 +129,40 @@ export function compile<
117
129
  },
118
130
  }
119
131
 
120
- for (const kind in overrideSchema) {
132
+ // Deep merge options.schema with generated schema
133
+ for (const kind in options?.schema) {
121
134
  const schemaKind = schema[kind as keyof typeof schema]
122
- const overrideSchemaKind = overrideSchema[kind]
135
+ const configSchemaKind = options?.schema[kind]
123
136
 
124
- for (const key in overrideSchemaKind) {
137
+ for (const key in configSchemaKind) {
125
138
  schemaKind[key] = {
126
139
  ...schemaKind[key],
127
- ...overrideSchemaKind[key],
140
+ ...configSchemaKind[key],
128
141
  }
129
142
  }
130
143
  }
131
144
 
132
- const program = createProgram(gl, _vertex.template, _fragment.template)
133
-
134
- return {
135
- program,
136
- schema,
137
- view: view(gl, program, schema),
138
- vertex: _vertex.template,
139
- fragment: _fragment.template,
140
- } as unknown as Prettify<CompileResult<TVertex, TFragment, TOverride>>
145
+ try {
146
+ const program = createProgram(gl, _vertex.template, _fragment.template)
147
+
148
+ return {
149
+ program,
150
+ schema,
151
+ view: view(gl, program, schema),
152
+ vertex: _vertex.template,
153
+ fragment: _fragment.template,
154
+ } as unknown as Prettify<CompileResult<TVertex, TFragment, TSchema>>
155
+ } catch (error) {
156
+ console.error('Error while creating WebGLProgram - vertex\n\n', _vertex.template)
157
+ console.error('Error while creating WebGLProgram - fragment\n\n', _fragment.template)
158
+
159
+ throw error
160
+ }
141
161
  }
142
162
 
143
- function resolveGLSLTag<TTag extends GLSL>(tag: TTag) {
163
+ function resolveGLSLTag<TTag extends GLSL>(tag: TTag, options?: { webgl2?: boolean }) {
144
164
  return {
145
- template: compile.toString(tag),
165
+ template: compile.toString(tag, options),
146
166
  schema: compile.toSchema(tag),
147
167
  }
148
168
  }
@@ -154,13 +174,13 @@ compile.toSchema = function <TTag extends GLSL>(tag: TTag) {
154
174
  interleavedAttributes: {},
155
175
  }
156
176
 
157
- function handleSlot(slot: GLSLSlot) {
158
- if (Array.isArray(slot)) {
159
- slot.forEach(handleSlot)
177
+ tag.slots.forEach(function handleSlot(slot: GLSLSlot) {
178
+ if (typeof slot !== 'object') {
160
179
  return
161
180
  }
162
181
 
163
- if (typeof slot !== 'object') {
182
+ if (Array.isArray(slot)) {
183
+ slot.forEach(handleSlot)
164
184
  return
165
185
  }
166
186
 
@@ -187,49 +207,97 @@ compile.toSchema = function <TTag extends GLSL>(tag: TTag) {
187
207
 
188
208
  // @ts-expect-error
189
209
  result[`${type}s`][name] = rest
190
- }
191
-
192
- tag.slots.forEach(handleSlot)
210
+ })
193
211
 
194
212
  return result as unknown as Prettify<GLSLToSchema<TTag>>
195
213
  }
196
214
 
197
- compile.toString = function <TTag extends GLSL>({ template: [initial, ...rest], slots }: TTag) {
198
- const v300 = !!initial?.startsWith('#version 300 es')
215
+ compile.toString = function <TTag extends GLSL>(
216
+ { template: [initial, ...rest], slots }: TTag,
217
+ config?: { webgl2?: boolean },
218
+ ) {
219
+ const v300 = config?.webgl2 ?? !!initial?.startsWith('#version 300 es')
199
220
 
200
221
  let template = initial ?? ''
201
222
 
202
223
  for (let i = 0; i < rest.length; i++) {
203
- template += `${glslSlotToString(slots[i]!, v300)}${rest[i]}`
224
+ template += `${resolveGlslSlotToString(slots[i]!, v300)}${rest[i]}`
204
225
  }
205
226
 
206
227
  return template
207
228
  }
208
229
 
209
- function glslSlotToString(slot: GLSLSlot, v300: boolean): string {
230
+ function resolveGlslSlotToString(slot: GLSLSlot, v300: boolean): string {
210
231
  if (typeof slot !== 'object') {
211
232
  return toID(slot)
212
233
  }
213
234
 
214
235
  if (Array.isArray(slot)) {
215
- return slot.map(slot => glslSlotToString(slot, v300)).join('')
236
+ return slot.map(slot => resolveGlslSlotToString(slot, v300)).join('')
216
237
  }
217
238
 
218
- if (slot.type === 'glsl') {
219
- return compile.toString(slot)
239
+ switch (slot.type) {
240
+ case 'glsl':
241
+ return compile.toString(slot)
242
+ case 'interleavedAttribute':
243
+ return slot.layout.reduce(
244
+ (a, v) =>
245
+ v300 ? `${a}in ${v.kind} ${toID(v.key)};\n` : `${a}attribute ${v.kind} ${toID(v.key)};\n`,
246
+ '',
247
+ )
248
+ case 'uniform':
249
+ if ('size' in slot) {
250
+ return `${slot.type} ${slot.kind} ${toID(slot.key)}[${slot.size}];`
251
+ }
252
+ return `${slot.type} ${slot.kind} ${toID(slot.key)};`
220
253
  }
221
254
 
222
- if (slot.type === 'interleavedAttribute') {
223
- return slot.layout.reduce(
224
- (a, v) =>
225
- v300 ? `${a}in ${v.kind} ${toID(v.key)};\n` : `${a}attribute ${v.kind} ${toID(v.key)};\n`,
226
- '',
227
- )
255
+ if (slot.type === 'attribute') {
256
+ return `${v300 ? 'in' : slot.type} ${slot.kind} ${toID(slot.key)};`
228
257
  }
229
258
 
230
- if (slot.type === 'uniform' && 'size' in slot) {
231
- return `${slot.type} ${slot.kind} ${toID(slot.key)}[${slot.size}];`
232
- }
259
+ throw new Error(`Unexpected slot: ${JSON.stringify(slot)}`)
260
+ }
261
+
262
+ const QUAD_FLOAT_ARRAY = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1])
263
+ const QUAD_BUFFER_MAP = createUpsertMap(WeakMap<GL, WebGLBuffer>)
264
+
265
+ compile.toQuad = function <TFragment extends GLSL, TSchema extends ViewSchemaPartial>(
266
+ gl: GL,
267
+ fragment: TFragment,
268
+ options?: CompileOptions<TSchema>,
269
+ ) {
270
+ const buffer = QUAD_BUFFER_MAP.getOrInsert(gl, gl.createBuffer.bind(gl))
271
+
272
+ const webgl2 = options?.webgl2 ?? fragment.template[0]?.startsWith('#version 300 es')
273
+
274
+ const vertex = webgl2
275
+ ? glsl`#version 300 es
276
+ precision mediump float;
277
+
278
+ ${attribute.vec2('a_quad', { buffer })}
279
+
280
+ out vec2 v_uv;
281
+
282
+ void main() {
283
+ v_uv = a_quad;
284
+ gl_Position = vec4(v_uv, 0.0, 1.0);
285
+ }`
286
+ : glsl`precision mediump float;
287
+
288
+ ${attribute.vec2('a_quad', { buffer })}
289
+
290
+ varying vec2 v_uv;
291
+
292
+ void main() {
293
+ v_uv = a_quad;
294
+ gl_Position = vec4(v_uv, 0.0, 1.0);
295
+ }`
296
+
297
+ const result = compile(gl, vertex, fragment, options)
298
+
299
+ // @ts-expect-error
300
+ result.view.attributes.a_quad.set(QUAD_FLOAT_ARRAY)
233
301
 
234
- return `${slot.type === 'attribute' && v300 ? 'in' : slot.type} ${slot.kind} ${toID(slot.key)};`
302
+ return result
235
303
  }
package/src/types.ts CHANGED
@@ -416,7 +416,7 @@ export interface AttributeMethods<T = AttributeKind> {
416
416
  }
417
417
 
418
418
  export type AttributeView<T extends AttributeSchema> = {
419
- [K in keyof T]: AttributeMethods<T[K]['kind']>
419
+ [K in keyof T]: Prettify<AttributeMethods<T[K]['kind']>>
420
420
  }
421
421
 
422
422
  /**********************************************************************************/
@@ -635,12 +635,12 @@ export interface ViewSchemaPartial {
635
635
  export type View<T extends ViewSchema = ViewSchema> = {
636
636
  attributes: T['attributes'] extends AttributeSchema
637
637
  ? Prettify<AttributeView<T['attributes']>>
638
- : never
638
+ : {}
639
639
  interleavedAttributes: T['interleavedAttributes'] extends InterleavedAttributeSchema
640
640
  ? Prettify<InterleavedAttributeView<T['interleavedAttributes']>>
641
- : never
642
- buffers: T['buffers'] extends BufferSchema ? Prettify<BufferView<T['buffers']>> : never
643
- uniforms: T['uniforms'] extends UniformSchema ? Prettify<UniformView<T['uniforms']>> : never
641
+ : {}
642
+ buffers: T['buffers'] extends BufferSchema ? Prettify<BufferView<T['buffers']>> : {}
643
+ uniforms: T['uniforms'] extends UniformSchema ? Prettify<UniformView<T['uniforms']>> : {}
644
644
  }
645
645
 
646
646
  /**********************************************************************************/
@@ -703,7 +703,7 @@ export interface AttributeToken<
703
703
  > {
704
704
  type: 'attribute'
705
705
  kind: TKind
706
- instanced: TInstanced
706
+ instanced: TInstanced extends boolean ? boolean : false
707
707
  key: TKey
708
708
  }
709
709
 
@@ -768,26 +768,40 @@ type _FlattenSlots<T extends Array<GLSLSlot>> = T extends [infer First, ...infer
768
768
  export type MergeGLSLSchema<
769
769
  TVertex extends ViewSchema,
770
770
  TFragment extends ViewSchema,
771
- TOverrides extends ViewSchemaPartial = {},
772
- > = {
773
- uniforms: DeepMerge<
774
- [ShallowMerge<[TVertex['uniforms'], TFragment['uniforms']]>, TOverrides['uniforms']]
775
- >
776
- attributes: DeepMerge<
777
- [ShallowMerge<[TVertex['attributes'], TFragment['attributes']]>, TOverrides['attributes']]
778
- >
779
- interleavedAttributes: DeepMerge<
780
- [
781
- ShallowMerge<[TVertex['interleavedAttributes'], TFragment['interleavedAttributes']]>,
782
- TOverrides['interleavedAttributes'],
783
- ]
784
- >
785
- }
771
+ TOverrides extends ViewSchemaPartial | undefined = {},
772
+ > = TOverrides extends ViewSchemaPartial
773
+ ? {
774
+ uniforms: Prettify<
775
+ DeepMerge<
776
+ [ShallowMerge<[TVertex['uniforms'], TFragment['uniforms']]>, TOverrides['uniforms']]
777
+ >
778
+ >
779
+ attributes: Prettify<
780
+ DeepMerge<
781
+ [ShallowMerge<[TVertex['attributes'], TFragment['attributes']]>, TOverrides['attributes']]
782
+ >
783
+ >
784
+ interleavedAttributes: Prettify<
785
+ DeepMerge<
786
+ [
787
+ ShallowMerge<[TVertex['interleavedAttributes'], TFragment['interleavedAttributes']]>,
788
+ TOverrides['interleavedAttributes'],
789
+ ]
790
+ >
791
+ >
792
+ }
793
+ : {
794
+ uniforms: Prettify<ShallowMerge<[TVertex['uniforms'], TFragment['uniforms']]>>
795
+ attributes: Prettify<ShallowMerge<[TVertex['attributes'], TFragment['attributes']]>>
796
+ interleavedAttributes: Prettify<
797
+ ShallowMerge<[TVertex['interleavedAttributes'], TFragment['interleavedAttributes']]>
798
+ >
799
+ }
786
800
 
787
801
  export type CompileResult<
788
802
  TVertex extends GLSL,
789
803
  TFragment extends GLSL,
790
- TOverrideSchema extends ViewSchemaPartial,
804
+ TOverrideSchema extends ViewSchemaPartial | undefined,
791
805
  > =
792
806
  MergeGLSLSchema<
793
807
  GLSLToSchema<TVertex>,
@@ -8,57 +8,11 @@ import type {
8
8
  UniformKind,
9
9
  } from '../types'
10
10
 
11
- export * as ObjectUtils from './object'
12
-
13
11
  export function assertedNotNullish<T>(value: T, message?: string): NonNullable<T> {
14
12
  if (value === undefined || value === null) throw new Error(message)
15
13
  return value
16
14
  }
17
15
 
18
- function createGLShader(gl: GL, type: number, source: string): WebGLShader {
19
- const shader = gl.createShader(type)
20
- if (!shader) throw new Error('Failed to create shader')
21
-
22
- gl.shaderSource(shader, source)
23
- gl.compileShader(shader)
24
-
25
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
26
- const info = gl.getShaderInfoLog(shader)
27
- gl.deleteShader(shader)
28
- throw new Error(
29
- `Failed to compile ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader: ${info}`,
30
- )
31
- }
32
-
33
- return shader
34
- }
35
-
36
- export function createProgram(gl: GL, vertexSource: string, fragmentSource: string): WebGLProgram {
37
- const program = gl.createProgram()
38
- if (!program) throw new Error('Failed to create WebGL program')
39
-
40
- const vertexShader = createGLShader(gl, gl.VERTEX_SHADER, vertexSource)
41
- const fragmentShader = createGLShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
42
-
43
- gl.attachShader(program, vertexShader)
44
- gl.attachShader(program, fragmentShader)
45
- gl.linkProgram(program)
46
-
47
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
48
- const info = gl.getProgramInfoLog(program)
49
- gl.deleteProgram(program)
50
- gl.deleteShader(vertexShader)
51
- gl.deleteShader(fragmentShader)
52
- throw new Error(`Failed to link program: ${info}`)
53
- }
54
-
55
- // Clean up shaders after linking
56
- gl.deleteShader(vertexShader)
57
- gl.deleteShader(fragmentShader)
58
-
59
- return program
60
- }
61
-
62
16
  /**********************************************************************************/
63
17
  /* */
64
18
  /* Kind To Uniform Name */