onejs-sl 0.4.0 → 0.5.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/CHANGELOG.md +14 -0
- package/README.md +35 -18
- package/goldens/goldens.json +52 -3
- package/package.json +1 -1
- package/src/body.ts +103 -20
- package/src/hlsl.ts +1 -0
- package/src/ir.ts +170 -26
- package/src/lang/ast.ts +37 -7
- package/src/lang/check.ts +155 -59
- package/src/lang/glsl.ts +5 -8
- package/src/lang/lexer.ts +30 -6
- package/src/lang/lower.ts +1015 -266
- package/src/lang/parser.ts +146 -68
- package/src/lang/words.ts +5 -6
- package/src/ops.ts +44 -1
- package/src/serial.ts +39 -8
- package/src/sl.ts +255 -11
- package/src/structure.ts +285 -0
- package/src/web.ts +108 -22
package/src/web.ts
CHANGED
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
* vertex index) so one module is one pipeline; the GLSL host supplies its own.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import { INT_LIMITS } from "./body"
|
|
28
|
+
import { SLError, TYPE, valueAt, type NodeRef, type Program, type SLKind, type SLType, type ValueNode } from "./ir"
|
|
29
|
+
import { inVaryingFlow, local, printBody, structure, type Syntax } from "./structure"
|
|
28
30
|
import { libClosure, libIndex, LIB_FUNCTIONS, SDF_CALLS } from "./lib"
|
|
29
31
|
import { LIB_GLSL } from "./lib/glsl"
|
|
30
32
|
import { LIB_WGSL } from "./lib/wgsl"
|
|
@@ -48,10 +50,22 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
48
50
|
const need = new Set<number>()
|
|
49
51
|
const smoothsteps = new Set<SLType>()
|
|
50
52
|
const sampled = new Set<number>()
|
|
51
|
-
const
|
|
52
|
-
const emitted = new Set<number>()
|
|
53
|
+
const shape = structure(p)
|
|
53
54
|
const T = (t: SLType) => (W ? (t === 1 ? "f32" : `vec${t}f`) : t === 1 ? "float" : `vec${t}`)
|
|
54
|
-
|
|
55
|
+
/** A value's type in this language, its kind included. */
|
|
56
|
+
const typeName = (n: { type: SLType; kind?: SLKind }): string => {
|
|
57
|
+
if (n.kind === undefined) return T(n.type)
|
|
58
|
+
const scalar = W ? { int: "i32", uint: "u32", bool: "bool" }[n.kind] : n.kind
|
|
59
|
+
if (n.type === 1) return scalar
|
|
60
|
+
return W ? `vec${n.type}<${scalar}>` : `${{ int: "i", uint: "u", bool: "b" }[n.kind]}vec${n.type}`
|
|
61
|
+
}
|
|
62
|
+
const kindLit = (kind: SLKind, v: number): string => {
|
|
63
|
+
if (kind === "bool") return v !== 0 ? "true" : "false"
|
|
64
|
+
if (kind === "uint") return `${v >>> 0}u`
|
|
65
|
+
const i = W ? `${v}i` : String(v)
|
|
66
|
+
return v < 0 ? `(${i})` : i
|
|
67
|
+
}
|
|
68
|
+
const typeOf = (ref: number) => valueAt(p.nodes, ref).type
|
|
55
69
|
/** A constant node's value, or null for anything computed. */
|
|
56
70
|
const constantOf = (ref: number): number[] | null => {
|
|
57
71
|
const c = p.nodes[ref]!
|
|
@@ -63,19 +77,12 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
63
77
|
/** A literal at width `t`. */
|
|
64
78
|
const k = (v: number, t: SLType) => (t > 1 ? `${T(t)}(${lit(v)})` : lit(v))
|
|
65
79
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
const n = p.nodes[ref]
|
|
69
|
-
const deps = n.k === "swizzle" ? [n.src] : n.k === "call" ? n.args : []
|
|
70
|
-
for (const d of deps) walk(d)
|
|
71
|
-
emitted.add(ref)
|
|
72
|
-
const e = expr(n)
|
|
73
|
-
lines.push(W ? ` let n${ref}: ${T(n.type)} = ${e};` : ` ${T(n.type)} n${ref} = ${e};`)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const expr = (n: SLNode): string => {
|
|
80
|
+
const expr = (ref: NodeRef): string => {
|
|
81
|
+
const n = valueAt(p.nodes, ref)
|
|
77
82
|
switch (n.k) {
|
|
78
|
-
case "const":
|
|
83
|
+
case "const":
|
|
84
|
+
if (n.kind !== undefined) return n.type === 1 ? kindLit(n.kind, n.v[0]!) : `${typeName(n)}(${n.v.map((v) => kindLit(n.kind!, v)).join(", ")})`
|
|
85
|
+
return n.type === 1 ? lit(n.v[0]) : `${T(n.type)}(${n.v.map(lit).join(", ")})`
|
|
79
86
|
case "input": {
|
|
80
87
|
const res = W ? "sl.res" : "sl_Res"
|
|
81
88
|
switch (INPUT_ID[n.name]) {
|
|
@@ -92,14 +99,15 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
92
99
|
}
|
|
93
100
|
case "swizzle": {
|
|
94
101
|
// A scalar has one component and no swizzle in either language.
|
|
95
|
-
if (typeOf(n.src) === 1) return n.type === 1 ? `n${n.src}` : `${
|
|
102
|
+
if (typeOf(n.src) === 1) return n.type === 1 ? `n${n.src}` : `${typeName(n)}(n${n.src})`
|
|
96
103
|
return `n${n.src}.${n.chans.map((c) => "xyzw"[c]).join("")}`
|
|
97
104
|
}
|
|
98
|
-
case "call": return call(n)
|
|
105
|
+
case "call": return call(n, ref)
|
|
106
|
+
default: throw new SLError(`the ${lang.toUpperCase()} emitter reached a ${n.k} as an expression`)
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
|
|
102
|
-
const call = (n: Extract<
|
|
110
|
+
const call = (n: Extract<ValueNode, { k: "call" }>, ref: NodeRef): string => {
|
|
103
111
|
const t = n.type
|
|
104
112
|
const a = n.args.map((r) => `n${r}`)
|
|
105
113
|
// Every argument at the result's width, for the element wise ops. WGSL
|
|
@@ -114,8 +122,55 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
114
122
|
return W ? LIB_FUNCTIONS[i]!.wgsl : name
|
|
115
123
|
}
|
|
116
124
|
const hlslType = (w: SLType) => (w === 1 ? "float" : `float${w}`)
|
|
125
|
+
const argKind = n.args.length > 0 ? valueAt(p.nodes, n.args[0]!).kind : undefined
|
|
126
|
+
// IR 4: the ops that take ints, uints and bools, by the kinds involved.
|
|
127
|
+
if (n.kind !== undefined || argKind !== undefined) {
|
|
128
|
+
const zero = kindLit(n.kind ?? "int", 0)
|
|
129
|
+
/** `c ? x : y`, which WGSL spells select(y, x, c). */
|
|
130
|
+
const pick = (c: string, x: string, y: string) => (W ? `select(${y}, ${x}, ${c})` : `(${c} ? ${x} : ${y})`)
|
|
131
|
+
switch (n.op) {
|
|
132
|
+
case SLOP.ADD: return `(${a[0]} + ${a[1]})`
|
|
133
|
+
case SLOP.SUB: return `(${a[0]} - ${a[1]})`
|
|
134
|
+
case SLOP.MUL: return `(${a[0]} * ${a[1]})`
|
|
135
|
+
// Truncating, and 0 for a zero divisor. GLSL ES leaves % of a
|
|
136
|
+
// negative undefined, so it is written out from the division.
|
|
137
|
+
case SLOP.DIV: return pick(`${a[1]} == ${zero}`, zero, `${a[0]} / ${a[1]}`)
|
|
138
|
+
case SLOP.MOD: return pick(`${a[1]} == ${zero}`, zero, W ? `${a[0]} % ${a[1]}` : `${a[0]} - ${a[1]} * (${a[0]} / ${a[1]})`)
|
|
139
|
+
case SLOP.NEG: return `(-${a[0]})`
|
|
140
|
+
case SLOP.MIN: return `min(${a[0]}, ${a[1]})`
|
|
141
|
+
case SLOP.MAX: return `max(${a[0]}, ${a[1]})`
|
|
142
|
+
case SLOP.ABS: return `abs(${a[0]})`
|
|
143
|
+
case SLOP.CLAMP: return `clamp(${a[0]}, ${a[1]}, ${a[2]})`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
117
146
|
switch (n.op) {
|
|
118
|
-
case SLOP.COMPOSE: return t === 1 ? a[0] : `${
|
|
147
|
+
case SLOP.COMPOSE: return t === 1 ? a[0] : `${typeName(n)}(${a.join(", ")})`
|
|
148
|
+
case SLOP.CAST: {
|
|
149
|
+
const from = valueAt(p.nodes, n.args[0]!).kind
|
|
150
|
+
if (n.kind === "bool") return `(${a[0]} != ${from === undefined ? "0.0" : kindLit(from, 0)})`
|
|
151
|
+
if (n.kind !== undefined && from === undefined) {
|
|
152
|
+
const [lo, hi] = INT_LIMITS[n.kind]
|
|
153
|
+
return `${typeName(n)}(clamp(${a[0]}, ${lo}, ${hi}))`
|
|
154
|
+
}
|
|
155
|
+
return `${typeName(n)}(${a[0]})`
|
|
156
|
+
}
|
|
157
|
+
case SLOP.LT: return `(${a[0]} < ${a[1]})`
|
|
158
|
+
case SLOP.LE: return `(${a[0]} <= ${a[1]})`
|
|
159
|
+
case SLOP.GT: return `(${a[0]} > ${a[1]})`
|
|
160
|
+
case SLOP.GE: return `(${a[0]} >= ${a[1]})`
|
|
161
|
+
case SLOP.EQ: return `(${a[0]} == ${a[1]})`
|
|
162
|
+
case SLOP.NE: return `(${a[0]} != ${a[1]})`
|
|
163
|
+
case SLOP.AND: return `(${a[0]} && ${a[1]})`
|
|
164
|
+
case SLOP.OR: return `(${a[0]} || ${a[1]})`
|
|
165
|
+
case SLOP.NOT: return `(!${a[0]})`
|
|
166
|
+
case SLOP.BIT_AND: return `(${a[0]} & ${a[1]})`
|
|
167
|
+
case SLOP.BIT_OR: return `(${a[0]} | ${a[1]})`
|
|
168
|
+
case SLOP.BIT_XOR: return `(${a[0]} ^ ${a[1]})`
|
|
169
|
+
case SLOP.BIT_NOT: return `(~${a[0]})`
|
|
170
|
+
// The count modulo 32, which GLSL leaves undefined past 31; WGSL shifts by a u32.
|
|
171
|
+
case SLOP.SHL: return W ? `(${a[0]} << (u32(${a[1]}) & 31u))` : `(${a[0]} << (${a[1]} & ${kindLit(valueAt(p.nodes, n.args[1]!).kind ?? "int", 31)}))`
|
|
172
|
+
case SLOP.SHR: return W ? `(${a[0]} >> (u32(${a[1]}) & 31u))` : `(${a[0]} >> (${a[1]} & ${kindLit(valueAt(p.nodes, n.args[1]!).kind ?? "int", 31)}))`
|
|
173
|
+
case SLOP.CHOOSE: return W ? `select(${a[2]}, ${a[1]}, ${a[0]})` : `(${a[0]} ? ${a[1]} : ${a[2]})`
|
|
119
174
|
|
|
120
175
|
case SLOP.ADD: return `(${s[0]} + ${s[1]})`
|
|
121
176
|
case SLOP.SUB: return `(${s[0]} - ${s[1]})`
|
|
@@ -181,6 +236,12 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
181
236
|
case SLOP.SAMPLE: {
|
|
182
237
|
const slot = Math.round(imm[0] ?? 0)
|
|
183
238
|
sampled.add(slot)
|
|
239
|
+
// Where pixels part ways there are no neighbours to take a mip
|
|
240
|
+
// level from, and WGSL refuses the implicit sample outright, so
|
|
241
|
+
// every backend samples level 0 there (`structure.ts`).
|
|
242
|
+
if (inVaryingFlow(shape, ref)) {
|
|
243
|
+
return W ? `textureSampleLevel(sl_tex${slot}, sl_samp${slot}, ${a[0]}, 0.0)` : `textureLod(sl_Tex${slot}, ${a[0]}, 0.0)`
|
|
244
|
+
}
|
|
184
245
|
return W ? `textureSample(sl_tex${slot}, sl_samp${slot}, ${a[0]})` : `texture(sl_Tex${slot}, ${a[0]})`
|
|
185
246
|
}
|
|
186
247
|
case SLOP.SAMPLE_LOD: {
|
|
@@ -245,8 +306,33 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
245
306
|
}
|
|
246
307
|
}
|
|
247
308
|
|
|
248
|
-
|
|
249
|
-
const
|
|
309
|
+
const vtype = (ref: NodeRef) => typeName(valueAt(p.nodes, ref))
|
|
310
|
+
const syntax: Syntax = W
|
|
311
|
+
? {
|
|
312
|
+
value: (ref, e) => `let ${local(ref)}: ${vtype(ref)} = ${e};`,
|
|
313
|
+
mutable: (n, like, init) => `var ${n}: ${vtype(like)}${init === undefined ? "" : ` = ${init}`};`,
|
|
314
|
+
counter: (n) => `var ${n}: i32 = 0;`,
|
|
315
|
+
assign: (n, v) => `${n} = ${v};`,
|
|
316
|
+
increment: (n) => `${n} = ${n} + 1;`,
|
|
317
|
+
ifOpen: (c) => `if (${c}) {`,
|
|
318
|
+
elseOpen: "} else {",
|
|
319
|
+
close: "}",
|
|
320
|
+
loopOpen: "loop {",
|
|
321
|
+
breakUnless: (c, turns, max) => `if (!(${c}) || ${turns} >= ${max}) { break; }`,
|
|
322
|
+
}
|
|
323
|
+
: {
|
|
324
|
+
value: (ref, e) => `${vtype(ref)} ${local(ref)} = ${e};`,
|
|
325
|
+
mutable: (n, like, init) => `${vtype(like)} ${n}${init === undefined ? "" : ` = ${init}`};`,
|
|
326
|
+
counter: (n) => `int ${n} = 0;`,
|
|
327
|
+
assign: (n, v) => `${n} = ${v};`,
|
|
328
|
+
increment: (n) => `${n}++;`,
|
|
329
|
+
ifOpen: (c) => `if (${c}) {`,
|
|
330
|
+
elseOpen: "} else {",
|
|
331
|
+
close: "}",
|
|
332
|
+
loopOpen: "for (;;) {",
|
|
333
|
+
breakUnless: (c, turns, max) => `if (!(${c}) || ${turns} >= ${max}) break;`,
|
|
334
|
+
}
|
|
335
|
+
const body = printBody(p, shape, syntax, expr, " ").join("\n")
|
|
250
336
|
const library = [...[...smoothsteps].sort().map((w) => smoothstep(w, lang)), ...librarySource(need, lang)].join("\n")
|
|
251
337
|
const header = `// GENERATED from a shader language program (${p.hash}). Do not edit.`
|
|
252
338
|
// Only the textures the program samples. WebGPU's automatic layout leaves
|