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.
- package/dist/index.cjs +35 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +219 -1652
- package/dist/index.js +35 -30
- package/dist/index.js.map +1 -1
- package/dist/native.cjs +35 -30
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.ts +7 -6
- package/dist/native.js +35 -30
- package/dist/native.js.map +1 -1
- package/dist/react.cjs +35 -30
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +35 -30
- package/dist/react.js.map +1 -1
- package/dist/solid.cjs +35 -30
- package/dist/solid.cjs.map +1 -1
- package/dist/solid.d.ts +1 -1
- package/dist/solid.js +35 -30
- package/dist/solid.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/node/core.ts +25 -4
- package/src/node/index.ts +61 -64
- package/src/node/node.ts +9 -5
- package/src/node/scope.ts +30 -17
- package/src/node/types.ts +168 -119
- package/src/node/utils/const.ts +113 -120
- package/src/node/utils/index.ts +21 -7
- package/src/node/utils/infer.ts +13 -18
- package/src/node/utils/parse.ts +57 -16
- package/src/node/utils/utils.ts +16 -8
- package/src/types.ts +6 -4
- package/src/utils/program.ts +62 -18
- package/src/webgl.ts +79 -67
- package/src/webgpu.ts +50 -42
package/src/node/utils/index.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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.
|
|
98
|
-
|
|
99
|
-
|
|
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)
|
package/src/node/utils/infer.ts
CHANGED
|
@@ -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
|
-
|
|
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'
|
|
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
|
|
84
|
-
|
|
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
|
|
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
|
}
|
package/src/node/utils/parse.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
157
|
-
if (c.isWebGL)
|
|
158
|
-
|
|
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 `@
|
|
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) => {
|
package/src/node/utils/utils.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
86
|
-
if (
|
|
87
|
-
fun(
|
|
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 |
|
|
24
|
+
cs?: string | Void
|
|
24
25
|
fs?: string | Vec4
|
|
25
26
|
vert?: string | Vec4
|
|
26
|
-
comp?: string |
|
|
27
|
+
comp?: string | Void
|
|
27
28
|
frag?: string | Vec4
|
|
28
29
|
vertex?: string | Vec4
|
|
29
|
-
compute?: string |
|
|
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
|
}
|
package/src/utils/program.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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,
|
|
107
|
-
c.texImage2D(c.TEXTURE_2D, 0, c.RGBA32F, size, size, 0, c.RGBA, c.FLOAT,
|
|
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 {
|
|
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
|
-
|
|
15
|
-
|
|
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
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
94
|
-
c.
|
|
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:
|
|
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
|
}
|