glre 0.36.0 → 0.38.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.
@@ -5,13 +5,16 @@ import {
5
5
  parseConstantHead,
6
6
  parseDeclare,
7
7
  parseDefine,
8
+ parseGather,
8
9
  parseIf,
10
+ parseScatter,
11
+ parseStorageHead,
9
12
  parseStruct,
10
13
  parseStructHead,
11
14
  parseSwitch,
12
15
  parseTexture,
13
- parseVaryingHead,
14
16
  parseUniformHead,
17
+ parseVaryingHead,
15
18
  } from './parse'
16
19
  import { getBluiltin, getOperator, getConversions, safeEventCall, getEventFun, initNodeContext } from './utils'
17
20
  import { is } from '../../utils/helpers'
@@ -42,6 +45,16 @@ export const code = <T extends Constants>(target: X<T>, c?: NodeContext | null):
42
45
  if (type === 'variable') return id
43
46
  if (type === 'member') return `${code(x, c)}.${code(y, c)}`
44
47
  if (type === 'element') return `${code(x, c)}[${code(y, c)}]`
48
+ if (type === 'gather')
49
+ return c.isWebGL //
50
+ ? parseGather(c, x, y, target)
51
+ : `${code(x, c)}[${code(y, c)}]`
52
+ if (type === 'scatter') {
53
+ const [storageNode, indexNode] = x.props.children ?? [] // x is gather node
54
+ return c.isWebGL
55
+ ? parseScatter(c, storageNode, y) // indexNode is not using
56
+ : `${code(storageNode, c)}[${code(indexNode, c)}] = ${code(y, c)};`
57
+ }
45
58
  if (type === 'ternary')
46
59
  return c.isWebGL
47
60
  ? `(${code(z, c)} ? ${code(x, c)} : ${code(y, c)})`
@@ -72,12 +85,12 @@ export const code = <T extends Constants>(target: X<T>, c?: NodeContext | null):
72
85
  if (type === 'switch') return parseSwitch(c, x, children)
73
86
  if (type === 'declare') return parseDeclare(c, x, y)
74
87
  if (type === 'define') {
75
- if (!c.code?.headers.has(id)) c.code?.headers.set(id, parseDefine(c, props, infer(target, c)))
88
+ if (!c.code?.headers.has(id)) c.code?.headers.set(id, parseDefine(c, props, target))
76
89
  return `${id}(${parseArray(children.slice(1), c)})`
77
90
  }
78
91
  if (type === 'struct') {
79
92
  if (!c.code?.headers.has(id)) c.code?.headers.set(id, parseStructHead(c, id, fields))
80
- return parseStruct(c, id, x.props.id, fields, initialValues)
93
+ return parseStruct(c, id, x.props.id, initialValues)
81
94
  }
82
95
  /**
83
96
  * headers
@@ -91,12 +104,12 @@ export const code = <T extends Constants>(target: X<T>, c?: NodeContext | null):
91
104
  return c.isWebGL ? `${id}` : `out.${id}`
92
105
  }
93
106
  if (type === 'builtin') {
94
- if (c.isWebGL) return getBluiltin(id)
107
+ if (c.isWebGL) return getBluiltin(c, id)
95
108
  if (id === 'position') return 'out.position'
96
109
  const field = `@builtin(${id}) ${id}: ${getConversions(infer(target, c), c)}`
97
- if (c.isFrag) {
98
- c.code?.fragInputs.set(id, field)
99
- } else c.code?.vertInputs.set(id, field)
110
+ if (c.label === 'compute') c.code?.computeInputs.set(id, field)
111
+ else if (c.label === 'frag') c.code?.fragInputs.set(id, field)
112
+ else if (c.label === 'vert') c.code?.vertInputs.set(id, field)
100
113
  return `in.${id}`
101
114
  }
102
115
  if (type === 'attribute') {
@@ -115,6 +128,7 @@ export const code = <T extends Constants>(target: X<T>, c?: NodeContext | null):
115
128
  target.listeners.add(fun)
116
129
  head = parseUniformHead(c, id, varType)
117
130
  }
131
+ if (type === 'storage') head = parseStorageHead(c, id, infer(target, c))
118
132
  if (type === 'constant') head = parseConstantHead(c, id, infer(target, c), code(x, c))
119
133
  if (head) {
120
134
  c.code?.headers.set(id, head)
@@ -31,14 +31,6 @@ const inferOperator = <T extends C>(L: T, R: T, op: string): T => {
31
31
  return L
32
32
  }
33
33
 
34
- // Unified logic with infer.ts InferArrayElement type
35
- const inferArrayElement = <T extends C>(arrayType: T): T => {
36
- if (arrayType === 'mat4') return 'vec4' as T
37
- if (arrayType === 'mat3') return 'vec3' as T
38
- if (arrayType === 'mat2') return 'vec2' as T
39
- return 'float' as T
40
- }
41
-
42
34
  export const inferPrimitiveType = <T extends C>(x: X) => {
43
35
  if (is.bol(x)) return 'bool' as T
44
36
  if (is.str(x)) return 'texture' as T
@@ -56,8 +48,8 @@ const inferFromArray = <T extends C>(arr: X<T>[], c: NodeContext) => {
56
48
  const [x] = arr
57
49
  if (is.str(x)) return x as T // for struct
58
50
  const ret = infer(x, c)
59
- for (const x of arr.slice(1))
60
- if (ret !== infer(x, c)) throw new Error(`glre node system error: defined scope return mismatch`)
51
+ // for (const x of arr.slice(1))
52
+ // if (ret !== infer(x, c)) throw new Error(`glre node system error: defined scope return mismatch`)
61
53
  return ret
62
54
  }
63
55
 
@@ -71,22 +63,25 @@ export const inferImpl = <T extends C>(target: NodeProxy<T>, c: NodeContext): T
71
63
  const [x, y, z] = children
72
64
  if (type === 'conversion') return x
73
65
  if (type === 'operator') return inferOperator(infer(y, c), infer(z, c), x)
74
- if (type === 'ternary') return inferOperator(infer(y, c), infer(z, c), 'add')
75
66
  if (type === 'builtin') return inferBuiltin(id)
76
67
  if (type === 'function') return inferFunction(x) || infer(y, c)
77
- if (type === 'define' && isConstants(layout?.type)) return layout?.type as T
68
+ if (type === 'define') {
69
+ if (isConstants(layout?.type)) return layout?.type as T
70
+ if (!inferFrom || inferFrom.length === 0) return 'void' as T
71
+ return inferFromArray(inferFrom, c)
72
+ }
78
73
  if (type === 'attribute' && is.arr(x) && c.gl?.count) return inferFromCount(x.length / c.gl.count)
79
- if (type === 'element') return inferArrayElement(infer(x, c) as T)
80
74
  if (type === 'member') {
81
75
  if (isSwizzle(y)) return inferFromCount(y.length)
82
76
  if (isNodeProxy(x)) {
83
- const field = (x as any).props.fields[y] // for variable node of struct member
84
- if (field) return infer(field, c)
77
+ const structType = infer(x, c)
78
+ const fields = c.code?.structFields?.get(structType)
79
+ if (fields && fields[y]) return infer(fields[y], c) as T
85
80
  }
86
- return 'float' as T // fallback @TODO FIX
81
+ return 'float' as T
87
82
  }
88
83
  if (inferFrom) return inferFromArray(inferFrom, c)
89
- return infer(x, c) // for uniform
84
+ return infer(x, c) // for uniform and storage gather and scatter
90
85
  }
91
86
 
92
87
  export const infer = <T extends C>(target: X<T>, c?: NodeContext | null): T => {
@@ -97,5 +92,5 @@ export const infer = <T extends C>(target: X<T>, c?: NodeContext | null): T => {
97
92
  if (c.infers.has(target)) return c.infers.get(target) as T
98
93
  const ret = inferImpl(target, c)
99
94
  c.infers.set(target, ret)
100
- return ret
95
+ return ret as T
101
96
  }
@@ -2,7 +2,7 @@ import { code } from '.'
2
2
  import { infer } from './infer'
3
3
  import { getConversions, addDependency } from './utils'
4
4
  import { is } from '../../utils/helpers'
5
- import type { Constants, NodeContext, NodeProps, NodeProxy, X } from '../types'
5
+ import type { Constants, NodeContext, NodeProps, NodeProxy, StructFields, X } from '../types'
6
6
 
7
7
  export const parseArray = (children: X[], c: NodeContext) => {
8
8
  return children
@@ -11,6 +11,35 @@ export const parseArray = (children: X[], c: NodeContext) => {
11
11
  .join(', ')
12
12
  }
13
13
 
14
+ // only for webgl
15
+ export const parseGather = (c: NodeContext, x: X, y: X, target: X) => {
16
+ const parseSwizzle = () => {
17
+ const valueType = infer(target, c)
18
+ if (valueType === 'float') return '.x'
19
+ if (valueType === 'vec2') return '.xy'
20
+ if (valueType === 'vec3') return '.xyz'
21
+ if (valueType === 'vec4') return ''
22
+ throw new Error(`Unsupported storage scatter type: ${valueType}`)
23
+ }
24
+ const indexVar = code(y, c)
25
+ const texSize = Math.floor(Math.sqrt(c.gl?.particles || 1024))
26
+ const coordX = `int(${indexVar}) % ${texSize}`
27
+ const coordY = `int(${indexVar}) / ${texSize}`
28
+ return `texelFetch(${code(x, c)}, ivec2(${coordX}, ${coordY}), 0)${parseSwizzle()}`
29
+ }
30
+
31
+ // only for webgl
32
+ export const parseScatter = (c: NodeContext, storageNode: X, valueNode: X) => {
33
+ const storageId = code(storageNode, c)
34
+ const valueCode = code(valueNode, c)
35
+ const valueType = infer(valueNode, c)
36
+ if (valueType === 'float') return `_${storageId} = vec4(${valueCode}, 0.0, 0.0, 1.0);`
37
+ if (valueType === 'vec2') return `_${storageId} = vec4(${valueCode}, 0.0, 1.0);`
38
+ if (valueType === 'vec3') return `_${storageId} = vec4(${valueCode}, 1.0);`
39
+ if (valueType === 'vec4') return `_${storageId} = ${valueCode};`
40
+ throw new Error(`Unsupported storage scatter type: ${valueType}`)
41
+ }
42
+
14
43
  export const parseTexture = (c: NodeContext, y: X, z: X, w: X) => {
15
44
  if (c.isWebGL) {
16
45
  const args = w ? [y, z, w] : [y, z]
@@ -58,7 +87,8 @@ export const parseDeclare = (c: NodeContext, x: X, y: X) => {
58
87
  return `var ${varName}: ${wgslType} = ${code(x, c)};`
59
88
  }
60
89
 
61
- export const parseStructHead = (c: NodeContext, id: string, fields: Record<string, NodeProxy> = {}) => {
90
+ export const parseStructHead = (c: NodeContext, id: string, fields: StructFields = {}) => {
91
+ c.code?.structFields?.set(id, fields)
62
92
  const lines: string[] = []
63
93
  for (const key in fields) {
64
94
  const fieldType = fields[key]
@@ -70,13 +100,8 @@ export const parseStructHead = (c: NodeContext, id: string, fields: Record<strin
70
100
  return `struct ${id} {\n ${ret}\n};`
71
101
  }
72
102
 
73
- export const parseStruct = (
74
- c: NodeContext,
75
- id: string,
76
- instanceId = '',
77
- fields?: Record<string, NodeProxy>,
78
- initialValues?: Record<string, NodeProxy>
79
- ) => {
103
+ export const parseStruct = (c: NodeContext, id: string, instanceId = '', initialValues?: StructFields) => {
104
+ const fields = c.code?.structFields?.get(id) || {}
80
105
  if (c.isWebGL) {
81
106
  if (initialValues) {
82
107
  const ordered = []
@@ -95,7 +120,7 @@ export const parseStruct = (
95
120
  /**
96
121
  * define
97
122
  */
98
- export const parseDefine = (c: NodeContext, props: NodeProps, returnType: Constants) => {
123
+ export const parseDefine = (c: NodeContext, props: NodeProps, target: NodeProxy) => {
99
124
  const { id, children = [], layout } = props
100
125
  const [x, ...args] = children
101
126
  const argParams: [name: string, type: string][] = []
@@ -108,6 +133,8 @@ export const parseDefine = (c: NodeContext, props: NodeProps, returnType: Consta
108
133
  for (let i = 0; i < args.length; i++) {
109
134
  argParams.push([`p${i}`, infer(args[i], c)])
110
135
  }
136
+ const scopeCode = code(x, c) // build struct headers before inferring returnType
137
+ const returnType = infer(target, c)
111
138
  const ret = []
112
139
  if (c?.isWebGL) {
113
140
  for (const [paramId, type] of argParams) {
@@ -118,9 +145,11 @@ export const parseDefine = (c: NodeContext, props: NodeProps, returnType: Consta
118
145
  ret.push(`${returnType} ${id}(${params}) {`)
119
146
  } else {
120
147
  for (const [paramId, type] of argParams) params.push(`${paramId}: ${getConversions(type, c)}`)
121
- ret.push(`fn ${id}(${params}) -> ${getConversions(returnType, c)} {`)
148
+ const isVoid = returnType === 'void'
149
+ if (isVoid) {
150
+ ret.push(`fn ${id}(${params}) {`)
151
+ } else ret.push(`fn ${id}(${params}) -> ${getConversions(returnType, c)} {`)
122
152
  }
123
- const scopeCode = code(x, c)
124
153
  if (scopeCode) ret.push(scopeCode)
125
154
  ret.push('}')
126
155
  return ret.join('\n')
@@ -135,6 +164,13 @@ export const parseVaryingHead = (c: NodeContext, id: string, type: string) => {
135
164
  : `@location(${c.code?.vertVaryings?.size || 0}) ${id}: ${getConversions(type, c)}`
136
165
  }
137
166
 
167
+ export const parseAttribHead = (c: NodeContext, id: string, type: Constants) => {
168
+ if (c.isWebGL) return `${type} ${id};`
169
+ const { location = 0 } = c.gl?.webgpu?.attribs.map.get(id) || {}
170
+ const wgslType = getConversions(type, c)
171
+ return `@location(${location}) ${id}: ${wgslType}`
172
+ }
173
+
138
174
  export const parseUniformHead = (c: NodeContext, id: string, type: Constants) => {
139
175
  const isTexture = type === 'sampler2D' || type === 'texture'
140
176
  if (c.isWebGL)
@@ -153,11 +189,16 @@ export const parseUniformHead = (c: NodeContext, id: string, type: Constants) =>
153
189
  return `@group(${group}) @binding(${binding}) var<uniform> ${id}: ${wgslType};`
154
190
  }
155
191
 
156
- export const parseAttribHead = (c: NodeContext, id: string, type: Constants) => {
157
- if (c.isWebGL) return `${type} ${id};`
158
- const { location = 0 } = c.gl?.webgpu?.attribs.map.get(id) || {}
192
+ export const parseStorageHead = (c: NodeContext, id: string, type: Constants) => {
193
+ if (c.isWebGL) {
194
+ const ret = `uniform sampler2D ${id};`
195
+ if (c.label !== 'compute') return ret
196
+ const location = c.units?.(id)
197
+ return `${ret}\nlayout(location = ${location}) out vec4 _${id};` // out texture buffer
198
+ }
199
+ const { group = 0, binding = 0 } = c.gl?.webgpu?.storages.map.get(id) || {}
159
200
  const wgslType = getConversions(type, c)
160
- return `@location(${location}) ${id}: ${wgslType}`
201
+ return `@group(${group}) @binding(${binding}) var<storage, read_write> ${id}: array<${wgslType}>;`
161
202
  }
162
203
 
163
204
  export const parseConstantHead = (c: NodeContext, id: string, type: Constants, value: string) => {
@@ -34,7 +34,7 @@ export const isNodeProxy = <T extends Constants>(x: unknown): x is NodeProxy<T>
34
34
 
35
35
  export const isConstants = (type?: unknown): type is Constants => {
36
36
  if (!is.str(type)) return false
37
- return CONSTANTS.includes(type)
37
+ return CONSTANTS.includes(type as any)
38
38
  }
39
39
 
40
40
  export const hex2rgb = (hex: number) => {
@@ -48,8 +48,14 @@ let count = 0
48
48
 
49
49
  export const getId = () => `x${count++}`
50
50
 
51
- export const getBluiltin = (id: string) => {
52
- return WGSL_TO_GLSL_BUILTIN[id as keyof typeof WGSL_TO_GLSL_BUILTIN]
51
+ export const getBluiltin = (c: NodeContext, id: string) => {
52
+ if (id === 'global_invocation_id') {
53
+ const size = Math.floor(Math.sqrt(c.gl?.particles || 1024))
54
+ return `uvec3(uint(gl_FragCoord.y) * uint(${size}) + uint(gl_FragCoord.x), 0u, 0u)`
55
+ }
56
+ const ret = WGSL_TO_GLSL_BUILTIN[id as keyof typeof WGSL_TO_GLSL_BUILTIN]
57
+ if (!ret) throw new Error(`Error: unknown builtin variable ${id}`)
58
+ return ret
53
59
  }
54
60
 
55
61
  export const getConversions = <T extends Constants>(x: X<T>, c?: NodeContext) => {
@@ -58,7 +64,7 @@ export const getConversions = <T extends Constants>(x: X<T>, c?: NodeContext) =>
58
64
  return TYPE_MAPPING[x as keyof typeof TYPE_MAPPING] || x // for struct type
59
65
  }
60
66
 
61
- export const getOperator = (op: X<string>) => {
67
+ export const getOperator = (op: X) => {
62
68
  return OPERATORS[op as keyof typeof OPERATORS] || op
63
69
  }
64
70
 
@@ -80,11 +86,11 @@ export const getEventFun = (c: NodeContext, id: string, isAttribute = false, isT
80
86
 
81
87
  export const safeEventCall = <T extends Constants>(x: X<T>, fun: (value: unknown) => void) => {
82
88
  if (is.und(x)) return
83
- if (!isNodeProxy(x)) return fun(x) // for uniform(1)
89
+ if (!isNodeProxy(x)) return fun(x) // for uniform(0) or uniform([0, 1])
84
90
  if (x.type !== 'conversion') return
85
- const value = x.props.children?.slice(1).filter(Boolean)
86
- if (!value?.length) return // for uniform(vec2())
87
- fun(value)
91
+ const args = x.props.children?.slice(1)
92
+ if (is.und(args?.[0])) return // ignore if uniform(vec2())
93
+ fun(args.map((x) => x ?? args[0])) // for uniform(vec2(1)) or uniform(vec2(1, 1))
88
94
  }
89
95
 
90
96
  export const initNodeContext = (c: NodeContext) => {
@@ -95,7 +101,9 @@ export const initNodeContext = (c: NodeContext) => {
95
101
  vertInputs: new Map(),
96
102
  vertOutputs: new Map(),
97
103
  vertVaryings: new Map(),
104
+ computeInputs: new Map(),
98
105
  dependencies: new Map(),
106
+ structFields: new Map(),
99
107
  }
100
108
  if (c.isWebGL) return c
101
109
  c.code.fragInputs.set('position', '@builtin(position) position: vec4f')
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { EventState, Nested } from 'reev'
2
2
  import type { Fun, Queue, Frame } from 'refr'
3
- import type { NodeProxy, Vec4 } from './node'
3
+ import type { NodeProxy, Vec4, Void } from './node'
4
4
  export type { Fun, Queue, Frame }
5
5
 
6
6
  export type GL = EventState<{
@@ -18,15 +18,16 @@ export type GL = EventState<{
18
18
  mouse: [number, number]
19
19
  count: number
20
20
  loading: number
21
+ particles: 64 | 256 | 576 | 1024 | 1600 | 2304 | 3136 | 4096 | 4096 | 5184 | 6400 // (8k)^2
21
22
  el: HTMLCanvasElement
22
23
  vs?: string | Vec4
23
- cs?: string | Vec4
24
+ cs?: string | Void
24
25
  fs?: string | Vec4
25
26
  vert?: string | Vec4
26
- comp?: string | Vec4
27
+ comp?: string | Void
27
28
  frag?: string | Vec4
28
29
  vertex?: string | Vec4
29
- compute?: string | Vec4
30
+ compute?: string | Void
30
31
  fragment?: string | Vec4
31
32
 
32
33
  /**
@@ -117,4 +118,5 @@ export interface WebGPUState {
117
118
  export interface WebGLState {
118
119
  context: WebGLRenderingContext
119
120
  program: WebGLProgram
121
+ storages: any
120
122
  }
@@ -1,6 +1,4 @@
1
- import { fragment, vertex } from '../node'
2
1
  import { is } from './helpers'
3
- import type { X } from '../node'
4
2
  import type { GL } from '../types'
5
3
 
6
4
  const createShader = (c: WebGLRenderingContext, source: string, type: number, onError = console.warn) => {
@@ -11,20 +9,16 @@ const createShader = (c: WebGLRenderingContext, source: string, type: number, on
11
9
  if (c.getShaderParameter(shader, c.COMPILE_STATUS)) return shader
12
10
  const error = c.getShaderInfoLog(shader)
13
11
  c.deleteShader(shader)
14
- onError(`Could not compile shader: ${error}`)
12
+ onError(`Could not compile shader: ${error}\n\n↓↓↓generated↓↓↓\n${source}`)
15
13
  }
16
14
 
17
- export const createProgram = (c: WebGLRenderingContext, vert: X, frag: X, gl: GL) => {
18
- if (!vert || !frag) return
19
- const config = { isWebGL: true, gl }
20
- frag = fragment(frag, config) // needs to be before vertex
21
- vert = vertex(vert, config)
15
+ export const createProgram = (c: WebGLRenderingContext, frag: string, vert: string, gl: GL) => {
22
16
  const pg = c.createProgram()
23
- const vs = createShader(c, vert, c.VERTEX_SHADER, gl.error)
24
17
  const fs = createShader(c, frag, c.FRAGMENT_SHADER, gl.error)
18
+ const vs = createShader(c, vert, c.VERTEX_SHADER, gl.error)
25
19
  if (!fs || !vs) return
26
- c.attachShader(pg, vs!)
27
20
  c.attachShader(pg, fs!)
21
+ c.attachShader(pg, vs!)
28
22
  c.linkProgram(pg)
29
23
  if (c.getProgramParameter(pg, c.LINK_STATUS)) return pg
30
24
  const error = c.getProgramInfoLog(pg)
@@ -93,20 +87,70 @@ export const createTexture = (c: WebGLRenderingContext, img: HTMLImageElement, l
93
87
  c.bindTexture(c.TEXTURE_2D, texture)
94
88
  }
95
89
 
96
- export const createStorage = (c: WebGL2RenderingContext, size: number, storage: any, array: any) => {
97
- const data = new Float32Array(size * size * 4)
98
- for (let i = 0; i < array.length; i++) data[i * 4] = array[i]
99
- c.activeTexture(c.TEXTURE0 + storage.unit)
100
- c.bindTexture(c.TEXTURE_2D, storage.a.texture)
101
- c.texImage2D(c.TEXTURE_2D, 0, c.RGBA32F, size, size, 0, c.RGBA, c.FLOAT, data)
90
+ /**
91
+ * for gpgpu
92
+ */
93
+ interface TextureBuffer {
94
+ texture: WebGLTexture
95
+ buffer: WebGLFramebuffer
96
+ }
97
+
98
+ export const createStorage = (
99
+ c: WebGL2RenderingContext,
100
+ value: number[],
101
+ size: number,
102
+ ping: TextureBuffer,
103
+ pong: TextureBuffer,
104
+ unit: number,
105
+ array: Float32Array
106
+ ) => {
107
+ const particles = size * size
108
+ const vectorSize = value.length / particles
109
+ for (let i = 0; i < particles; i++) {
110
+ for (let j = 0; j < Math.min(vectorSize, 4); j++) {
111
+ array[4 * i + j] = value[i * vectorSize + j] || 0
112
+ }
113
+ }
114
+ c.activeTexture(c.TEXTURE0 + unit)
115
+ c.bindTexture(c.TEXTURE_2D, ping.texture)
116
+ c.texImage2D(c.TEXTURE_2D, 0, c.RGBA32F, size, size, 0, c.RGBA, c.FLOAT, array)
102
117
  c.texParameteri(c.TEXTURE_2D, c.TEXTURE_MIN_FILTER, c.NEAREST)
103
118
  c.texParameteri(c.TEXTURE_2D, c.TEXTURE_MAG_FILTER, c.NEAREST)
104
119
  c.texParameteri(c.TEXTURE_2D, c.TEXTURE_WRAP_S, c.CLAMP_TO_EDGE)
105
120
  c.texParameteri(c.TEXTURE_2D, c.TEXTURE_WRAP_T, c.CLAMP_TO_EDGE)
106
- c.bindTexture(c.TEXTURE_2D, storage.b.texture)
107
- c.texImage2D(c.TEXTURE_2D, 0, c.RGBA32F, size, size, 0, c.RGBA, c.FLOAT, null)
121
+ c.bindTexture(c.TEXTURE_2D, pong.texture)
122
+ c.texImage2D(c.TEXTURE_2D, 0, c.RGBA32F, size, size, 0, c.RGBA, c.FLOAT, array)
108
123
  c.texParameteri(c.TEXTURE_2D, c.TEXTURE_MIN_FILTER, c.NEAREST)
109
124
  c.texParameteri(c.TEXTURE_2D, c.TEXTURE_MAG_FILTER, c.NEAREST)
110
125
  c.texParameteri(c.TEXTURE_2D, c.TEXTURE_WRAP_S, c.CLAMP_TO_EDGE)
111
126
  c.texParameteri(c.TEXTURE_2D, c.TEXTURE_WRAP_T, c.CLAMP_TO_EDGE)
112
127
  }
128
+
129
+ export const cleanStorage = (
130
+ c: WebGL2RenderingContext,
131
+ map: Iterable<{ ping: TextureBuffer; pong: TextureBuffer }>
132
+ ) => {
133
+ for (const { ping, pong } of map) {
134
+ c.deleteTexture(ping.texture)
135
+ c.deleteTexture(pong.texture)
136
+ c.deleteFramebuffer(ping.buffer)
137
+ c.deleteFramebuffer(pong.buffer)
138
+ }
139
+ }
140
+
141
+ export const createAttachment = (
142
+ c: WebGL2RenderingContext,
143
+ i: TextureBuffer,
144
+ o: TextureBuffer,
145
+ loc: WebGLUniformLocation,
146
+ unit: number,
147
+ index: number
148
+ ) => {
149
+ c.activeTexture(c.TEXTURE0 + unit)
150
+ c.bindTexture(c.TEXTURE_2D, i.texture)
151
+ c.uniform1i(loc, unit)
152
+ if (index === 0) c.bindFramebuffer(c.FRAMEBUFFER, o.buffer)
153
+ const attachment = c.COLOR_ATTACHMENT0 + index
154
+ c.framebufferTexture2D(c.FRAMEBUFFER, attachment, c.TEXTURE_2D, o.texture, 0)
155
+ return attachment
156
+ }
package/src/webgl.ts CHANGED
@@ -1,6 +1,15 @@
1
1
  import { nested as cached } from 'reev'
2
2
  import { loadingImage } from './utils/helpers'
3
- import { createAttrib, createProgram, createStorage, createTexture, createUniform } from './utils/program'
3
+ import {
4
+ cleanStorage,
5
+ createAttachment,
6
+ createAttrib,
7
+ createProgram,
8
+ createStorage,
9
+ createTexture,
10
+ createUniform,
11
+ } from './utils/program'
12
+ import { compute, fragment, vertex } from './node'
4
13
  import type { GL, WebGLState } from './types'
5
14
 
6
15
  const vert = /* cpp */ `
@@ -11,100 +20,103 @@ void main() {
11
20
  gl_Position = vec4(x, y, 0.0, 1.0);
12
21
  }`.trim()
13
22
 
14
- export const webgl = async (gl: GL) => {
15
- const c = gl.el!.getContext('webgl2')!
23
+ const computeProgram = (gl: GL, c: WebGL2RenderingContext) => {
24
+ if (!gl.cs) return null // ignore if no compute shader
16
25
  c.getExtension('EXT_color_buffer_float')
17
- const pg1 = createProgram(c, gl.vs, gl.fs, gl)!
18
- const pg2 = createProgram(c, vert, gl.cs, gl)!
19
- c.useProgram(pg1)
20
26
 
21
27
  let activeUnit = 0 // for texture units
22
28
  let currentNum = 0 // for storage buffers
23
29
 
24
- const attribs = cached((key) => c.getAttribLocation(pg1, key))
25
- const uniforms1 = cached((key) => c.getUniformLocation(pg1, key))
26
- const uniforms2 = cached((key) => c.getUniformLocation(pg2, key))
27
- const textures = cached(() => activeUnit++)
28
- const storages = cached(() => {
29
- const unit = activeUnit++
30
- const a = { texture: c.createTexture(), buffer: c.createFramebuffer() }
31
- const b = { texture: c.createTexture(), buffer: c.createFramebuffer() }
32
- return { a, b, unit, width: 0, height: 0 }
30
+ const units = cached(() => activeUnit++)
31
+ const config = { isWebGL: true, gl, units }
32
+
33
+ const pg = createProgram(c, compute(gl.cs, config), vert, gl)!
34
+ const size = Math.ceil(Math.sqrt(gl.particles))
35
+
36
+ const uniforms = cached((key) => c.getUniformLocation(pg, key)!)
37
+ const storages = cached((key) => {
38
+ const array = new Float32Array(size * size * 4) // RGBA texture data
39
+ const ping = { texture: c.createTexture(), buffer: c.createFramebuffer() }
40
+ const pong = { texture: c.createTexture(), buffer: c.createFramebuffer() }
41
+ return { ping, pong, array, loc: uniforms(key), unit: units(key) }
33
42
  })
34
43
 
44
+ const _uniform = (key: string, value: number | number[]) => {
45
+ c.useProgram(pg)
46
+ createUniform(c, uniforms(key), value)
47
+ }
48
+
49
+ const _storage = (key: string, value: number[]) => {
50
+ const { ping, pong, unit, array } = storages(key)
51
+ createStorage(c, value, size, ping, pong, unit, array)
52
+ }
53
+
54
+ const clean = () => {
55
+ c.deleteProgram(pg)
56
+ cleanStorage(c, storages.map.values())
57
+ }
58
+
59
+ const render = () => {
60
+ c.useProgram(pg)
61
+ const attachments = storages.map.values().map(({ ping, pong, loc, unit }, index) => {
62
+ const [i, o] = currentNum % 2 ? [ping, pong] : [pong, ping]
63
+ return createAttachment(c, i, o, loc, unit, index)
64
+ })
65
+ c.drawBuffers(attachments)
66
+ c.drawArrays(c.TRIANGLES, 0, 3)
67
+ c.bindFramebuffer(c.FRAMEBUFFER, null)
68
+ currentNum++
69
+ }
70
+
71
+ return { render, clean, _uniform, _storage, storages }
72
+ }
73
+
74
+ export const webgl = async (gl: GL) => {
75
+ const config = { isWebGL: true, gl }
76
+ const c = gl.el!.getContext('webgl2')!
77
+ const cp = computeProgram(gl, c)
78
+ const pg = createProgram(c, fragment(gl.fs, config), vertex(gl.vs, config), gl)!
79
+ c.useProgram(pg)
80
+
81
+ let activeUnit = 0 // for texture units
82
+
83
+ const units = cached(() => activeUnit++)
84
+ const attribs = cached((key) => c.getAttribLocation(pg, key))
85
+ const uniforms = cached((key) => c.getUniformLocation(pg, key))
86
+
35
87
  const _attribute = (key = '', value: number[], iboValue: number[]) => {
36
88
  const loc = attribs(key, true)
37
89
  createAttrib(c, loc, gl.count, value, iboValue)
38
90
  }
39
91
 
40
92
  const _uniform = (key: string, value: number | number[]) => {
41
- createUniform(c, uniforms1(key)!, value)
42
- if (!pg2) return
43
- c.useProgram(pg2)
44
- createUniform(c, uniforms2(key)!, value)
45
- c.useProgram(pg1)
93
+ c.useProgram(pg)
94
+ createUniform(c, uniforms(key)!, value)
95
+ cp?._uniform(key, value)
46
96
  }
47
97
 
48
98
  const _texture = (key: string, src: string) => {
99
+ c.useProgram(pg)
49
100
  loadingImage(gl, src, (source) => {
50
- const loc = uniforms1(key)
51
- const unit = textures(key)
52
- createTexture(c, source, loc, unit)
101
+ createTexture(c, source, uniforms(key), units(key))
53
102
  })
54
103
  }
55
104
 
56
- const _storage = (key: string, value: number[] | Float32Array) => {
57
- const array = value instanceof Float32Array ? value : new Float32Array(value)
58
- const storage = storages(key)
59
- const size = Math.ceil(Math.sqrt(array.length))
60
- storage.width = size
61
- storage.height = size
62
- createStorage(c, size, storage, array)
63
- c.uniform1i(uniforms1(key), storage.unit)
64
- }
65
-
66
105
  const clean = () => {
67
- c.deleteProgram(pg1)
68
- if (pg2) c.deleteProgram(pg2)
69
- for (const { a, b } of storages.map.values()) {
70
- c.deleteTexture(a.texture)
71
- c.deleteTexture(b.texture)
72
- c.deleteFramebuffer(a.buffer)
73
- c.deleteFramebuffer(b.buffer)
74
- }
106
+ cp?.clean()
107
+ c.deleteProgram(pg)
75
108
  c.getExtension('WEBGL_lose_context')?.loseContext()
76
109
  }
77
110
 
78
- const _compute = () => {
79
- c.useProgram(pg2)
80
- for (const [, storage] of storages.map) {
81
- const output = currentNum % 2 ? storage.b : storage.a
82
- c.bindFramebuffer(c.FRAMEBUFFER, output.buffer)
83
- c.framebufferTexture2D(c.FRAMEBUFFER, c.COLOR_ATTACHMENT0, c.TEXTURE_2D, output.texture, 0)
84
- c.viewport(0, 0, storage.width, storage.height)
85
- c.drawArrays(c.TRIANGLES, 0, 6)
86
- c.bindFramebuffer(c.FRAMEBUFFER, null)
87
- }
88
- currentNum++
89
- c.useProgram(pg1)
90
- }
91
-
92
111
  const render = () => {
93
- if (pg2) _compute()
94
- c.bindFramebuffer(c.FRAMEBUFFER, null)
95
- c.clear(c.COLOR_BUFFER_BIT)
112
+ cp?.render()
113
+ c.useProgram(pg)
96
114
  c.viewport(0, 0, ...gl.size)
97
- for (const [key, { unit, a, b }] of storages.map) {
98
- const loc = uniforms1(key)
99
- const output = currentNum % 2 ? a : b
100
- c.activeTexture(c.TEXTURE0 + unit)
101
- c.bindTexture(c.TEXTURE_2D, output.texture)
102
- c.uniform1i(loc, unit)
103
- }
104
115
  c.drawArrays(c.TRIANGLES, 0, gl.count)
116
+ c.bindFramebuffer(c.FRAMEBUFFER, null)
105
117
  }
106
118
 
107
- const webgl: WebGLState = { context: c, program: pg1 }
119
+ const webgl: WebGLState = { context: c, program: pg, storages: cp?.storages }
108
120
 
109
- return { webgl, render, clean, _attribute, _uniform, _texture, _storage }
121
+ return { webgl, render, clean, _attribute, _uniform, _texture, _storage: cp?._storage }
110
122
  }