onejs-sl 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/CHANGELOG.md +20 -0
- package/README.md +96 -7
- package/goldens/goldens.json +480 -0
- package/lib/common.hlsl +155 -0
- package/lib/noise2d.hlsl +167 -0
- package/lib/sdf2d.hlsl +510 -0
- package/package.json +9 -3
- package/src/body.ts +243 -0
- package/src/core.ts +1 -1
- package/src/emit/hlsl-body.ts +7 -0
- package/src/encode.ts +3 -24
- package/src/hlsl.ts +34 -143
- package/src/ir.ts +49 -2
- package/src/lang/lower.ts +6 -3
- package/src/lib/glsl.ts +899 -0
- package/src/lib/hlsl.ts +891 -0
- package/src/lib/index.ts +47 -0
- package/src/lib/table.ts +125 -0
- package/src/lib/types.ts +23 -0
- package/src/lib/wgsl.ts +994 -0
- package/src/serial.ts +3 -1
- package/src/shapes.ts +4 -4
- package/src/sl.ts +27 -1
- package/src/web.ts +53 -28
- package/src/weblib.ts +0 -1303
package/src/serial.ts
CHANGED
|
@@ -123,7 +123,9 @@ function uniform(u: unknown, i: number): UniformDecl {
|
|
|
123
123
|
if (typeof x.name !== "string" || ![1, 2, 3, 4].includes(x.type as number) || !Array.isArray(x.value)) {
|
|
124
124
|
fail(`uniform ${i} needs a name, a width and a value`)
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
const out: UniformDecl = { name: x.name!, type: x.type as SLType, value: x.value!.slice() }
|
|
127
|
+
if (x.colour === true) out.colour = true
|
|
128
|
+
return out
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
function texture(t: unknown, i: number): TextureDecl {
|
package/src/shapes.ts
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Duplicating and pinning is the trade this repository already makes for the
|
|
14
14
|
* painter and particle opcode tables, and for the SDF dispatcher that exists in
|
|
15
|
-
* both FxSources and SLCommon. The ids are a contract with
|
|
16
|
-
* way; what matters is that a test fails when they drift, not that there is
|
|
15
|
+
* both FxSources and SLCommon. The ids are a contract with `lib/sdf2d.hlsl`
|
|
16
|
+
* (SDF2D.cginc in OneJS) either way; what matters is that a test fails when they drift, not that there is
|
|
17
17
|
* only one copy.
|
|
18
18
|
*/
|
|
19
19
|
export const SL_SDF_SHAPES = {
|
|
@@ -30,8 +30,8 @@ export const SL_SDF_SHAPES = {
|
|
|
30
30
|
export type SlSdfKind = keyof typeof SL_SDF_SHAPES
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* How many parameters each shape reads, in the order
|
|
34
|
-
* (its `a` then `b`). Six shapes take more than four: roundedBox's last two
|
|
33
|
+
* How many parameters each shape reads, in the order `sl_sdfDistance` in
|
|
34
|
+
* `lib/common.hlsl` takes them (its `a` then `b`). Six shapes take more than four: roundedBox's last two
|
|
35
35
|
* corner radii, orientedBox's thickness, triangle's third vertex, horseshoe's
|
|
36
36
|
* second width, orientedVesica's width and bezier's end point. `sl.sdf` takes
|
|
37
37
|
* up to this many, and a test derives the same counts from the web emitters'
|
package/src/sl.ts
CHANGED
|
@@ -461,6 +461,14 @@ function difference(a: Num, b: Num): Num {
|
|
|
461
461
|
|
|
462
462
|
export function atan2(y: Num, x: Num): Float {
|
|
463
463
|
const b = ctx()
|
|
464
|
+
// An angle is one number. Given vectors, the node would still be a float
|
|
465
|
+
// and every backend would disagree about which lane it kept: HLSL takes
|
|
466
|
+
// .x silently, GLSL and WGSL refuse to compile.
|
|
467
|
+
for (const v of [y, x]) {
|
|
468
|
+
if (typeof v !== "number" && v.width !== 1) {
|
|
469
|
+
throw new SLError(`atan2 takes two floats, and this is a ${v.width === 2 ? "float2" : v.width === 3 ? "float3" : "float4"}: call it per component`)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
464
472
|
const yy = typeof y === "number" ? float(y) : y
|
|
465
473
|
const [a, c] = align2(yy as Val, x)
|
|
466
474
|
return mk(b, b.call(SLOP.ATAN2, TYPE.FLOAT, [a, c]), TYPE.FLOAT)
|
|
@@ -622,7 +630,7 @@ export function ramp(t: Num, stops: Array<string | [number, number, number, numb
|
|
|
622
630
|
* fifth and sixth.
|
|
623
631
|
*
|
|
624
632
|
* Which parameters a shape takes is the shape's own business; `circle` wants a
|
|
625
|
-
* radius, `roundedBox` wants half extents and a corner. See
|
|
633
|
+
* radius, `roundedBox` wants half extents and a corner. See `lib/sdf2d.hlsl`.
|
|
626
634
|
*/
|
|
627
635
|
export function sdf(kind: SlSdfKind, p: Vec2, params: number[] = []): Float {
|
|
628
636
|
const id = SL_SDF_SHAPES[kind]
|
|
@@ -664,6 +672,24 @@ export const uniform = {
|
|
|
664
672
|
const b = ctx()
|
|
665
673
|
return mk(b, b.uniform(name, TYPE.VEC4, value), TYPE.VEC4)
|
|
666
674
|
},
|
|
675
|
+
/**
|
|
676
|
+
* A colour, defaulting to `hex` as written (sRGB, the way CSS reads it).
|
|
677
|
+
* Returns it converted to linear, as a hex literal is, and marks the
|
|
678
|
+
* declaration `colour` so a host can offer a colour picker for it. Three
|
|
679
|
+
* components when `width` is 3, dropping the alpha.
|
|
680
|
+
*/
|
|
681
|
+
colour: ((name: string, hex: string, width: 3 | 4 = 4): Vec3 | Vec4 => {
|
|
682
|
+
const b = ctx()
|
|
683
|
+
const c = parseHex(hex)
|
|
684
|
+
const type = width === 3 ? TYPE.VEC3 : TYPE.VEC4
|
|
685
|
+
const raw = mk(b, b.uniform(name, type, c.slice(0, width), true), type)
|
|
686
|
+
return toLinear(raw) as Vec3 | Vec4
|
|
687
|
+
}) as {
|
|
688
|
+
(name: string, hex: string): Vec4
|
|
689
|
+
(name: string, hex: string, width: 3): Vec3
|
|
690
|
+
(name: string, hex: string, width: 4): Vec4
|
|
691
|
+
(name: string, hex: string, width: 3 | 4): Vec3 | Vec4
|
|
692
|
+
},
|
|
667
693
|
}
|
|
668
694
|
|
|
669
695
|
export interface Texture {
|
package/src/web.ts
CHANGED
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import { SLError, TYPE, type Program, type SLNode, type SLType } from "./ir"
|
|
29
|
+
import { libClosure, libIndex, LIB_FUNCTIONS, SDF_CALLS } from "./lib"
|
|
30
|
+
import { LIB_GLSL } from "./lib/glsl"
|
|
31
|
+
import { LIB_WGSL } from "./lib/wgsl"
|
|
29
32
|
import { INPUT_ID, SLOP, VM_UNIFORMS } from "./ops"
|
|
30
|
-
import { SDF_CALLS, WEB_LIB } from "./weblib"
|
|
31
33
|
|
|
32
34
|
export type WebLanguage = "glsl" | "wgsl"
|
|
33
35
|
|
|
@@ -44,7 +46,8 @@ export function emitWGSL(p: Program): string {
|
|
|
44
46
|
|
|
45
47
|
function emitWeb(p: Program, lang: WebLanguage): string {
|
|
46
48
|
const W = lang === "wgsl"
|
|
47
|
-
const need = new Set<
|
|
49
|
+
const need = new Set<number>()
|
|
50
|
+
const smoothsteps = new Set<SLType>()
|
|
48
51
|
const sampled = new Set<number>()
|
|
49
52
|
const lines: string[] = []
|
|
50
53
|
const emitted = new Set<number>()
|
|
@@ -100,7 +103,13 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
100
103
|
const s = n.args.map((r) => splat(r, t))
|
|
101
104
|
const w0 = n.args.length > 0 ? typeOf(n.args[0]) : 1
|
|
102
105
|
const imm = n.imm ?? []
|
|
103
|
-
|
|
106
|
+
/** A library function by name, and by HLSL parameter types where it is overloaded. */
|
|
107
|
+
const lib = (name: string, params?: string[]) => {
|
|
108
|
+
const i = libIndex(name, params)
|
|
109
|
+
need.add(i)
|
|
110
|
+
return W ? LIB_FUNCTIONS[i]!.wgsl : name
|
|
111
|
+
}
|
|
112
|
+
const hlslType = (w: SLType) => (w === 1 ? "float" : `float${w}`)
|
|
104
113
|
switch (n.op) {
|
|
105
114
|
case SLOP.COMPOSE: return t === 1 ? a[0] : `${T(t)}(${a.join(", ")})`
|
|
106
115
|
|
|
@@ -145,11 +154,11 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
145
154
|
? `(${a[0]} - 2.0 * ${a[1]} * ${a[0]} * ${a[1]})`
|
|
146
155
|
: `reflect(${a[0]}, ${a[1]})`
|
|
147
156
|
case SLOP.LUMINANCE: return `dot(${a[0]}.rgb, ${T(3)}(0.2126, 0.7152, 0.0722))`
|
|
148
|
-
case SLOP.TO_LINEAR: return `${lib(
|
|
157
|
+
case SLOP.TO_LINEAR: return `${lib("sl_toLinear", [hlslType(t)])}(${a[0]})`
|
|
149
158
|
|
|
150
159
|
case SLOP.MIX: return `mix(${s[0]}, ${s[1]}, ${s[2]})`
|
|
151
160
|
case SLOP.STEP: return `step(${s[0]}, ${s[1]})`
|
|
152
|
-
case SLOP.SMOOTHSTEP: return
|
|
161
|
+
case SLOP.SMOOTHSTEP: smoothsteps.add(t); return `sl_smoothstep${t}(${s[0]}, ${s[1]}, ${s[2]})`
|
|
153
162
|
// As the VM and the HLSL emitter do it: branchless, so every
|
|
154
163
|
// backend agrees on the edge value.
|
|
155
164
|
case SLOP.SELECT: {
|
|
@@ -158,8 +167,8 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
case SLOP.HSV2RGB: return `${lib("sl_hsv2rgb")}(${a[0]})`
|
|
161
|
-
case SLOP.NOISE: return `${lib("
|
|
162
|
-
case SLOP.SIMPLEX: return `${lib("
|
|
170
|
+
case SLOP.NOISE: return `${lib("sl_valueNoise")}(${a[0]})`
|
|
171
|
+
case SLOP.SIMPLEX: return `${lib("sl_simplex")}(${a[0]})`
|
|
163
172
|
case SLOP.FBM: return octaveCall(Math.round(imm[1] ?? 0), a[0], imm[0])
|
|
164
173
|
case SLOP.TURBULENCE: return octaveCall(2, a[0], imm[0])
|
|
165
174
|
case SLOP.RIDGED: return octaveCall(3, a[0], imm[0])
|
|
@@ -177,32 +186,36 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
177
186
|
)
|
|
178
187
|
}
|
|
179
188
|
|
|
180
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* The library's onejsFbmKind, resolved here since the kind is a
|
|
191
|
+
* constant: calling the dispatcher would print a WGSL `select` that
|
|
192
|
+
* runs two of the four fields to keep one.
|
|
193
|
+
*/
|
|
181
194
|
function octaveCall(kind: number, pt: string, octaves: number | undefined): string {
|
|
182
195
|
const o = Math.min(4, Math.max(1, Math.round(octaves ?? 3)))
|
|
183
|
-
const fn = kind === 2 ? "
|
|
184
|
-
return `${lib(fn)}(${pt}, 0.0, ${o})`
|
|
196
|
+
const fn = kind === 2 ? "onejsTurbulence" : kind === 3 ? "onejsRidged" : kind === 1 ? "onejsFbmSimplex" : "onejsFbm"
|
|
197
|
+
return `${lib(fn, ["float2", "float", "int", "float", "float"])}(${pt}, 0.0, ${o}, 2.0, 0.5)`
|
|
185
198
|
}
|
|
186
199
|
|
|
187
|
-
/** The shape is a constant, so this calls it directly instead of
|
|
200
|
+
/** The shape is a constant, so this calls it directly instead of sl_sdfDistance's switch. */
|
|
188
201
|
function sdfCall(id: number, pt: string, im: number[]): string {
|
|
189
202
|
const shape = SDF_CALLS[id]
|
|
190
|
-
//
|
|
203
|
+
// sl_sdfDistance returns 1e6 for an id it does not know.
|
|
191
204
|
if (shape === undefined) return lit(1e6)
|
|
192
|
-
lib(shape.fn)
|
|
205
|
+
const fn = lib(shape.fn)
|
|
193
206
|
const v = [im[1] ?? 0, im[2] ?? 0, im[3] ?? 0, im[4] ?? 0, im[5] ?? 0, im[6] ?? 0]
|
|
194
207
|
const args = shape.args.map((arg) => {
|
|
195
|
-
if (
|
|
208
|
+
if ("int" in arg) return String(Math.trunc(v[arg.int]))
|
|
196
209
|
const parts = arg.map((i) => lit(v[i]))
|
|
197
210
|
return parts.length === 1 ? parts[0] : `${T(parts.length as SLType)}(${parts.join(", ")})`
|
|
198
211
|
})
|
|
199
|
-
return `${
|
|
212
|
+
return `${fn}(${[pt, ...args].join(", ")})`
|
|
200
213
|
}
|
|
201
214
|
}
|
|
202
215
|
|
|
203
216
|
walk(p.result)
|
|
204
217
|
const body = lines.join("\n")
|
|
205
|
-
const library = librarySource(need, lang)
|
|
218
|
+
const library = [...[...smoothsteps].sort().map((w) => smoothstep(w, lang)), ...librarySource(need, lang)].join("\n")
|
|
206
219
|
const header = `// GENERATED from a shader language program (${p.hash}). Do not edit.`
|
|
207
220
|
// Only the textures the program samples. WebGPU's automatic layout leaves
|
|
208
221
|
// out a binding the shader never reads, and a bind group that supplies one
|
|
@@ -251,19 +264,31 @@ ${body}
|
|
|
251
264
|
`
|
|
252
265
|
}
|
|
253
266
|
|
|
254
|
-
/** The needed
|
|
255
|
-
function librarySource(need: Set<
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
267
|
+
/** The needed library functions and everything they call, in library order. */
|
|
268
|
+
function librarySource(need: Set<number>, lang: WebLanguage): string[] {
|
|
269
|
+
const texts = lang === "wgsl" ? LIB_WGSL : LIB_GLSL
|
|
270
|
+
return libClosure(need).map((i) => texts[i]!.trim())
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* HLSL's smoothstep is the formula whatever the edge order; GLSL leaves
|
|
275
|
+
* edge0 >= edge1 undefined, so the web writes the formula out. This belongs
|
|
276
|
+
* to the web frame rather than the library: the HLSL backends call the
|
|
277
|
+
* intrinsic, which already means this.
|
|
278
|
+
*/
|
|
279
|
+
function smoothstep(w: SLType, lang: WebLanguage): string {
|
|
280
|
+
if (lang === "glsl") {
|
|
281
|
+
const g = w === 1 ? "float" : `vec${w}`
|
|
282
|
+
return `${g} sl_smoothstep${w}(${g} a, ${g} b, ${g} x) {
|
|
283
|
+
${g} t = clamp((x - a) / (b - a), 0.0, 1.0);
|
|
284
|
+
return t * t * (3.0 - 2.0 * t);
|
|
285
|
+
}`
|
|
264
286
|
}
|
|
265
|
-
|
|
266
|
-
return
|
|
287
|
+
const t = w === 1 ? "f32" : `vec${w}f`
|
|
288
|
+
return `fn sl_smoothstep${w}(a: ${t}, b: ${t}, x: ${t}) -> ${t} {
|
|
289
|
+
let t = saturate((x - a) / (b - a));
|
|
290
|
+
return t * t * (3.0 - 2.0 * t);
|
|
291
|
+
}`
|
|
267
292
|
}
|
|
268
293
|
|
|
269
294
|
/** A literal that survives a float32 round trip and never reads as an int. */
|