onejs-sl 0.5.0 → 0.7.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 +27 -0
- package/README.md +50 -6
- package/goldens/goldens.json +69 -2
- package/package.json +2 -2
- package/src/body.ts +38 -5
- package/src/compile.ts +11 -1
- package/src/core.ts +4 -4
- package/src/hlsl.ts +21 -3
- package/src/ir.ts +98 -11
- package/src/lang/README.md +4 -3
- package/src/lang/check.ts +39 -7
- package/src/lang/classify.ts +2 -2
- package/src/lang/glsl.ts +2 -2
- package/src/lang/lower.ts +108 -41
- package/src/lang/parser.ts +1 -1
- package/src/ops.ts +7 -3
- package/src/params.ts +5 -2
- package/src/serial.ts +14 -4
- package/src/sl.ts +46 -3
- package/src/structure.ts +2 -2
- package/src/tables.ts +2 -2
- package/src/web.ts +31 -7
package/src/lang/lower.ts
CHANGED
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
* `uv.mul(8)`, not as `uv.mul(sl.float(8))`, because the first broadcasts to a
|
|
40
40
|
* float2 constant and the second builds a float plus a swizzle. Same picture,
|
|
41
41
|
* different graph, different hash, so the distinction is load bearing rather
|
|
42
|
-
* than an optimisation. An int constant is a bigint
|
|
43
|
-
* boolean for the same reason: folded until something
|
|
42
|
+
* than an optimisation. An int constant is a bigint, a uint constant a `Uint`
|
|
43
|
+
* and a bool constant a boolean for the same reason: folded until something
|
|
44
|
+
* needs a node.
|
|
44
45
|
*
|
|
45
46
|
* A COMPARISON WAITS to be used (`Test`). Where arithmetic or a `?:` reads it,
|
|
46
47
|
* it is the float that is 0 or 1 it always was, built the way it always was,
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
*/
|
|
50
51
|
|
|
51
52
|
import {
|
|
52
|
-
controlProblem, DERIVED_INPUTS, INPUTS, RAMP_STOP_COMPUTED, truncateHeld, writtenColour,
|
|
53
|
+
controlProblem, DERIVED_INPUTS, INPUTS, PREVIOUS, RAMP_STOP_COMPUTED, STEP_INPUTS, truncateHeld, writtenColour,
|
|
53
54
|
type Program, type SLKind, type SLType, type UniformControl,
|
|
54
55
|
} from "../ir"
|
|
55
56
|
import { SLOP } from "../ops"
|
|
@@ -80,17 +81,30 @@ class Test {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
/**
|
|
83
|
-
* A
|
|
84
|
-
*
|
|
85
|
-
* either of its forms.
|
|
84
|
+
* A uint constant, 0 to 2^32 - 1, not yet a node. A bigint is already an int,
|
|
85
|
+
* so a uint needs a form of its own to fold the way an int does.
|
|
86
86
|
*/
|
|
87
|
-
|
|
87
|
+
class Uint {
|
|
88
|
+
readonly v: bigint
|
|
89
|
+
/**
|
|
90
|
+
* Any whole number, wrapped to 32 bits as a uint wraps. By a mask, not
|
|
91
|
+
* `BigInt.asUintN`, which QuickJS-ng answers with a negative from 2^31 up.
|
|
92
|
+
*/
|
|
93
|
+
constructor(v: bigint) { this.v = v & 0xffffffffn }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A lowered value. A number is a float, a bigint an int, a Uint a uint and a
|
|
98
|
+
* boolean a bool, none of which has had to become a node yet; a Test is a
|
|
99
|
+
* comparison not yet either of its forms.
|
|
100
|
+
*/
|
|
101
|
+
type LV = Val | number | bigint | Uint | boolean | Test
|
|
88
102
|
|
|
89
103
|
/** A declared type: its width, and its kind when it is not floats. */
|
|
90
104
|
interface VType { width: SLType; kind: SLKind | undefined }
|
|
91
105
|
|
|
92
106
|
interface Binding extends VType {
|
|
93
|
-
/** Always of the declared type: a Test is resolved and an int is a bigint or an int node. */
|
|
107
|
+
/** Always of the declared type: a Test is resolved and an int is a bigint or an int node, a uint a Uint or a uint node. */
|
|
94
108
|
value: LV
|
|
95
109
|
}
|
|
96
110
|
|
|
@@ -205,7 +219,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
205
219
|
const blank = (t: VType, pos: Pos): LV => {
|
|
206
220
|
if (t.kind === "int") return 0n
|
|
207
221
|
if (t.kind === "bool") return false
|
|
208
|
-
if (t.kind === "uint") return
|
|
222
|
+
if (t.kind === "uint") return new Uint(0n)
|
|
209
223
|
return t.width === 1 ? 0 : at(pos, () => compose(t.width, new Array<LV>(t.width).fill(0)))
|
|
210
224
|
}
|
|
211
225
|
|
|
@@ -232,11 +246,11 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
232
246
|
// Read through, so a program that never names one records nothing
|
|
233
247
|
// for it. The setter only serves a refused assignment, which the
|
|
234
248
|
// checker has already reported and lowering carries on past.
|
|
235
|
-
for (const [name, width] of Object.entries(DERIVED_INPUTS)) {
|
|
249
|
+
for (const [name, width] of Object.entries({ ...DERIVED_INPUTS, ...STEP_INPUTS })) {
|
|
236
250
|
let value: LV | undefined
|
|
237
251
|
global.declare(name, {
|
|
238
252
|
width: width as SLType,
|
|
239
|
-
kind: undefined,
|
|
253
|
+
kind: name === "frame" ? "int" : undefined,
|
|
240
254
|
get value() { return value ??= (inputs as never)[name] },
|
|
241
255
|
set value(v: LV) { value = v },
|
|
242
256
|
})
|
|
@@ -275,6 +289,15 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
275
289
|
}
|
|
276
290
|
|
|
277
291
|
for (const t of unit.textures) samplers.set(t.name, at(t.pos, () => sl.texture(t.name)))
|
|
292
|
+
// Unless the file declared a texture of its own by the name. The
|
|
293
|
+
// checker refuses tex2Dlod on it, and anything else that took the
|
|
294
|
+
// name, before lowering could reach either.
|
|
295
|
+
if (!samplers.has(PREVIOUS)) {
|
|
296
|
+
samplers.set(PREVIOUS, {
|
|
297
|
+
sample: (uv) => inputs.previous.sample(uv),
|
|
298
|
+
sampleLevel: () => { throw new Error("tex2Dlod(previous) reached lowering; the checker refuses it") },
|
|
299
|
+
})
|
|
300
|
+
}
|
|
278
301
|
|
|
279
302
|
for (const c of unit.consts) {
|
|
280
303
|
const t = typeOf(c.type)
|
|
@@ -298,7 +321,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
298
321
|
case "float3": return sl.uniform.vec3(name, [c[0]!, c[1]!, c[2]!], control) as unknown as Val
|
|
299
322
|
case "float4": return sl.uniform.vec4(name, [c[0]!, c[1]!, c[2]!, c[3]!], control) as unknown as Val
|
|
300
323
|
// A float slot, as every host binds, read as the whole number it holds.
|
|
301
|
-
case "int": return sl.
|
|
324
|
+
case "int": return sl.uniform.int(name, c[0]!, control) as unknown as Val
|
|
302
325
|
case "uint":
|
|
303
326
|
case "bool": throw new Error(`the checker let a ${type} uniform through`)
|
|
304
327
|
}
|
|
@@ -585,7 +608,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
585
608
|
const got = widthOf(v)
|
|
586
609
|
if (got !== t.width) {
|
|
587
610
|
if (fn === main) {
|
|
588
|
-
const what =
|
|
611
|
+
const what = isConstant(v) ? "a single number" : `a ${widthType(got)}`
|
|
589
612
|
fail(`main returns a float4, a colour with alpha, and this returns ${what}. Wrap it: float4(value, 1)`, pos, 6)
|
|
590
613
|
}
|
|
591
614
|
fail(`${fn.name} is declared to return a ${fn.ret} and returns a ${widthType(got)}`, fn.pos)
|
|
@@ -755,8 +778,8 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
755
778
|
if (c.labels.includes(null)) { otherwise = c.body; continue }
|
|
756
779
|
let cond: Expr | null = null
|
|
757
780
|
for (const l of c.labels as Expr[]) {
|
|
758
|
-
const k = lowerExpr(l, scope, v
|
|
759
|
-
const key = typeof k === "bigint" ? k : k instanceof Val ? constValue(k) : null
|
|
781
|
+
const k = lowerExpr(l, scope, kindWant(v))
|
|
782
|
+
const key = typeof k === "bigint" ? k : k instanceof Uint ? k.v : k instanceof Val ? constValue(k) : null
|
|
760
783
|
if (key === null) fail("a case label is a whole number, or a const int", l.pos)
|
|
761
784
|
if (seen.has(key)) fail(`this switch already has a case ${key}`, l.pos)
|
|
762
785
|
seen.add(key)
|
|
@@ -795,13 +818,13 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
795
818
|
}
|
|
796
819
|
case "int":
|
|
797
820
|
if (isInt(v)) return v
|
|
798
|
-
if (v
|
|
821
|
+
if (isUint(v)) fail(`${name} is an int and this is a uint; convert it with int(...)`, pos)
|
|
799
822
|
if (isBoolish(v)) fail(`${name} is an int and this is a bool; convert it with int(...)`, pos)
|
|
800
823
|
fail(`${name} is an int and this is a ${widthType(widthOf(v))}; convert it with int(...), which truncates toward zero`, pos)
|
|
801
824
|
break
|
|
802
825
|
case "uint":
|
|
803
|
-
if (v
|
|
804
|
-
if (typeof v === "bigint" && v >= 0n) return
|
|
826
|
+
if (isUint(v)) return v
|
|
827
|
+
if (typeof v === "bigint" && v >= 0n) return new Uint(v)
|
|
805
828
|
fail(`${name} is a uint and this is ${describeLV(v)}; convert it with uint(...)`, pos)
|
|
806
829
|
break
|
|
807
830
|
case "bool": {
|
|
@@ -820,6 +843,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
820
843
|
function toFloat(v: LV, pos: Pos): number | Val {
|
|
821
844
|
if (typeof v === "number") return v
|
|
822
845
|
if (typeof v === "bigint") return Number(v)
|
|
846
|
+
if (v instanceof Uint) return Number(v.v)
|
|
823
847
|
if (typeof v === "boolean") return v ? 1 : 0
|
|
824
848
|
if (v instanceof Test) return v.float
|
|
825
849
|
return v.kind === undefined ? v : at(pos, () => sl.float(v) as unknown as Val)
|
|
@@ -839,6 +863,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
839
863
|
if (typeof v === "boolean") return v
|
|
840
864
|
if (typeof v === "number") return v >= 0.5
|
|
841
865
|
if (typeof v === "bigint") return v !== 0n
|
|
866
|
+
if (v instanceof Uint) return v.v !== 0n
|
|
842
867
|
if (v instanceof Test) return v.bool
|
|
843
868
|
if (v.kind === "bool") return v
|
|
844
869
|
return at(pos, () => (v.kind === undefined ? v.ge(0.5) : v.ne(0)) as unknown as Val)
|
|
@@ -898,9 +923,8 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
898
923
|
function lowerExpr(e: Expr, scope: Scope, want?: "int" | "uint"): LV {
|
|
899
924
|
switch (e.k) {
|
|
900
925
|
case "num":
|
|
901
|
-
if (e.unsigned === true
|
|
926
|
+
if (e.unsigned === true || (e.whole && want === "uint")) return new Uint(BigInt(e.value))
|
|
902
927
|
if (e.whole && want === "int") return BigInt.asIntN(32, BigInt(e.value))
|
|
903
|
-
if (e.whole && want === "uint") return at(e.pos, () => sl.uint(e.value) as unknown as Val)
|
|
904
928
|
return e.value
|
|
905
929
|
case "bool": return e.value
|
|
906
930
|
case "hex": return at(e.pos, () => sl.color(e.hex) as unknown as Val)
|
|
@@ -927,18 +951,22 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
927
951
|
case "-":
|
|
928
952
|
if (typeof v === "number") return -v
|
|
929
953
|
if (typeof v === "bigint") return BigInt.asIntN(32, -v)
|
|
954
|
+
// As the node would: a uint negated is taken as a float.
|
|
955
|
+
if (v instanceof Uint) return -Number(v.v)
|
|
930
956
|
if (v instanceof Val && v.kind !== "bool") return at(e.pos, () => v.neg())
|
|
931
957
|
return at(e.pos, () => asVal(toFloat(v, e.pos)).neg())
|
|
932
958
|
case "!":
|
|
933
959
|
if (typeof v === "boolean") return !v
|
|
934
960
|
if (typeof v === "number") return 1 - v
|
|
935
961
|
if (typeof v === "bigint") return v === 0n
|
|
962
|
+
if (v instanceof Uint) return v.v === 0n
|
|
936
963
|
if (v instanceof Test) return new Test(v.width, () => oneMinus(v.float, e.pos), () => notBool(v.bool))
|
|
937
964
|
if (v.kind === "bool") return at(e.pos, () => v.not())
|
|
938
965
|
if (v.kind === undefined) return oneMinus(v, e.pos)
|
|
939
966
|
return at(e.pos, () => v.eq(0))
|
|
940
967
|
case "~":
|
|
941
968
|
if (typeof v === "bigint") return BigInt.asIntN(32, ~v)
|
|
969
|
+
if (v instanceof Uint) return new Uint(~v.v)
|
|
942
970
|
if (v instanceof Val && (v.kind === "int" || v.kind === "uint")) return at(e.pos, () => v.bnot())
|
|
943
971
|
fail(`~ flips the bits of an int or a uint, and this is ${describeLV(v)}`, e.pos)
|
|
944
972
|
}
|
|
@@ -978,7 +1006,11 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
978
1006
|
}
|
|
979
1007
|
if (isUint(a) || isUint(b)) {
|
|
980
1008
|
if (!isIntLike(a) || !isIntLike(b)) fail(`a uint only meets another uint here; convert with uint(...) or float(...)`, pos)
|
|
981
|
-
|
|
1009
|
+
if (a instanceof Uint && b instanceof Uint) {
|
|
1010
|
+
if ((op === "/" || op === "%") && b.v === 0n) return new Uint(0n)
|
|
1011
|
+
return new Uint(INT_ARITH[op](a.v, b.v))
|
|
1012
|
+
}
|
|
1013
|
+
return at(pos, () => intVal(a)[METHOD[op]](other(b)))
|
|
982
1014
|
}
|
|
983
1015
|
return floatArithmetic(op, toFloat(a, pos), toFloat(b, pos), pos)
|
|
984
1016
|
}
|
|
@@ -1002,8 +1034,13 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1002
1034
|
if (!isIntLike(v)) fail(`${op} works on the bits of an int or a uint, and this is ${describeLV(v)}${isBoolish(v) ? "; use && or ||" : ""}`, pos)
|
|
1003
1035
|
}
|
|
1004
1036
|
if (typeof a === "bigint" && typeof b === "bigint") return BigInt.asIntN(32, INT_BITS[op](a, b))
|
|
1005
|
-
|
|
1006
|
-
const
|
|
1037
|
+
// A shift's count may be either kind, and the value shifted keeps its own.
|
|
1038
|
+
const shift = op === "<<" || op === ">>"
|
|
1039
|
+
const count = typeof b === "bigint" ? b : b instanceof Uint ? b.v : null
|
|
1040
|
+
if (a instanceof Uint && count !== null && (shift || b instanceof Uint)) return new Uint(INT_BITS[op](a.v, count))
|
|
1041
|
+
if (typeof a === "bigint" && shift && b instanceof Uint) return BigInt.asIntN(32, INT_BITS[op](a, b.v))
|
|
1042
|
+
const x = intVal(a as bigint | Uint | Val)
|
|
1043
|
+
const y = other(b as bigint | Uint | Val)
|
|
1007
1044
|
return at(pos, () => {
|
|
1008
1045
|
switch (op) {
|
|
1009
1046
|
case "&": return x.band(y)
|
|
@@ -1025,7 +1062,8 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1025
1062
|
return at(pos, () => intVal(a)[CMP_METHOD[op]!](typeof b === "bigint" ? Number(b) : b as Val) as unknown as Val)
|
|
1026
1063
|
}
|
|
1027
1064
|
if ((isUint(a) || isUint(b)) && isIntLike(a) && isIntLike(b)) {
|
|
1028
|
-
|
|
1065
|
+
if (a instanceof Uint && b instanceof Uint) return INT_CMP[op]!(a.v, b.v)
|
|
1066
|
+
return at(pos, () => intVal(a)[CMP_METHOD[op]!](other(b)) as unknown as Val)
|
|
1029
1067
|
}
|
|
1030
1068
|
const fa = toFloat(a, pos)
|
|
1031
1069
|
const fb = toFloat(b, pos)
|
|
@@ -1082,7 +1120,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1082
1120
|
/** `c ? a : b`: always a select, both sides computed. A float condition is the select it always was. */
|
|
1083
1121
|
function lowerChoice(e: Extract<Expr, { k: "cond" }>, scope: Scope, want?: "int" | "uint"): LV {
|
|
1084
1122
|
const cond = lowerExpr(e.cond, scope)
|
|
1085
|
-
if (typeof cond === "boolean" || typeof cond === "number" || typeof cond === "bigint") {
|
|
1123
|
+
if (typeof cond === "boolean" || typeof cond === "number" || typeof cond === "bigint" || cond instanceof Uint) {
|
|
1086
1124
|
return lowerExpr(boolOf(cond, e.pos) ? e.then : e.else, scope, want)
|
|
1087
1125
|
}
|
|
1088
1126
|
// Built before the sides, as it always was, so a program's nodes keep
|
|
@@ -1093,13 +1131,13 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1093
1131
|
let f = lowerExpr(e.else, scope, want)
|
|
1094
1132
|
if (isIntLike(t) && typeof f === "number" && wholeExpr(e.else)) f = lowerExpr(e.else, scope, kindWant(t))
|
|
1095
1133
|
if (isIntLike(f) && typeof t === "number" && wholeExpr(e.then)) t = lowerExpr(e.then, scope, kindWant(f))
|
|
1096
|
-
const kinded = [t, f].some((v) => typeof v === "bigint" || typeof v === "boolean" || (v instanceof Val && v.kind !== undefined))
|
|
1134
|
+
const kinded = [t, f].some((v) => typeof v === "bigint" || v instanceof Uint || typeof v === "boolean" || (v instanceof Val && v.kind !== undefined))
|
|
1097
1135
|
if (!kinded) {
|
|
1098
1136
|
const c = cond instanceof Test ? cond.float : cond
|
|
1099
1137
|
return at(e.pos, () => sl.select(c, toFloat(t, e.pos), toFloat(f, e.pos)))
|
|
1100
1138
|
}
|
|
1101
1139
|
const c = boolOf(cond, e.pos)
|
|
1102
|
-
const side = (v: LV): Num => (
|
|
1140
|
+
const side = (v: LV): Num => (isIntLike(v) ? intVal(v) : typeof v === "boolean" ? sl.bool(v) : v instanceof Test ? v.float : v) as Num
|
|
1103
1141
|
return at(e.pos, () => sl.select(c, side(t), side(f)))
|
|
1104
1142
|
}
|
|
1105
1143
|
|
|
@@ -1160,7 +1198,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1160
1198
|
// min, max, abs and clamp keep ints ints; everything else takes floats.
|
|
1161
1199
|
if (INT_BUILTINS.has(n) && args.some(isIntLike) && args.every((v, i) => isIntLike(v) || (typeof v === "number" && wholeExpr(e.args[i]!)))) {
|
|
1162
1200
|
const k = kindWant(args.find(isIntLike)!)
|
|
1163
|
-
args = e.args.map((a) => lowerExpr(a, scope, k)).map((v) => (
|
|
1201
|
+
args = e.args.map((a) => lowerExpr(a, scope, k)).map((v) => (isIntLike(v) ? intVal(v) : v) as LV)
|
|
1164
1202
|
return at(e.pos, () => builtin.lower!(args as Num[]))
|
|
1165
1203
|
}
|
|
1166
1204
|
const fs = args.map((v, i) => toFloat(v, e.args[i]!.pos))
|
|
@@ -1177,17 +1215,26 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1177
1215
|
const v = lowerExpr(e.args[0]!, scope, n === "bool" ? undefined : n)
|
|
1178
1216
|
if (widthOf(v) !== 1) fail(`${n}(x) takes one component, and this is a ${widthType(widthOf(v))}; convert each one`, e.pos)
|
|
1179
1217
|
const x = v instanceof Test ? v.bool : v
|
|
1218
|
+
// Between an int and a uint the bits are kept, as the GPU keeps them.
|
|
1180
1219
|
if (n === "int") {
|
|
1181
1220
|
if (typeof x === "bigint") return x
|
|
1221
|
+
if (x instanceof Uint) return BigInt.asIntN(32, x.v)
|
|
1182
1222
|
if (typeof x === "number") return BigInt(truncateHeld(x, "int"))
|
|
1183
1223
|
if (typeof x === "boolean") return x ? 1n : 0n
|
|
1184
1224
|
}
|
|
1225
|
+
if (n === "uint") {
|
|
1226
|
+
if (x instanceof Uint) return x
|
|
1227
|
+
if (typeof x === "bigint") return new Uint(x)
|
|
1228
|
+
if (typeof x === "number") return new Uint(BigInt(truncateHeld(x, "uint")))
|
|
1229
|
+
if (typeof x === "boolean") return new Uint(x ? 1n : 0n)
|
|
1230
|
+
}
|
|
1185
1231
|
if (n === "bool") {
|
|
1186
1232
|
if (typeof x === "boolean") return x
|
|
1187
1233
|
if (typeof x === "number") return x !== 0
|
|
1188
1234
|
if (typeof x === "bigint") return x !== 0n
|
|
1235
|
+
if (x instanceof Uint) return x.v !== 0n
|
|
1189
1236
|
}
|
|
1190
|
-
const c =
|
|
1237
|
+
const c = isIntLike(x) ? intVal(x) : x
|
|
1191
1238
|
return at(e.pos, () => (n === "int" ? sl.int(c) : n === "uint" ? sl.uint(c) : sl.bool(c)) as unknown as Val)
|
|
1192
1239
|
}
|
|
1193
1240
|
|
|
@@ -1238,7 +1285,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1238
1285
|
}
|
|
1239
1286
|
|
|
1240
1287
|
function compose(width: SLType, parts: LV[]): Val {
|
|
1241
|
-
const ps = parts.map((p) => (p instanceof Test ? p.float : typeof p === "bigint" ? Number(p) : typeof p === "boolean" ? (p ? 1 : 0) : p)) as Num[]
|
|
1288
|
+
const ps = parts.map((p) => (p instanceof Test ? p.float : typeof p === "bigint" ? Number(p) : p instanceof Uint ? Number(p.v) : typeof p === "boolean" ? (p ? 1 : 0) : p)) as Num[]
|
|
1242
1289
|
if (width === 2) return sl.vec2(...ps) as unknown as Val
|
|
1243
1290
|
if (width === 3) return sl.vec3(...ps) as unknown as Val
|
|
1244
1291
|
return sl.vec4(...ps) as unknown as Val
|
|
@@ -1284,6 +1331,12 @@ const INT_BITS: Record<BitOp, (a: bigint, b: bigint) => bigint> = {
|
|
|
1284
1331
|
">>": (a, b) => a >> (b & 31n),
|
|
1285
1332
|
}
|
|
1286
1333
|
|
|
1334
|
+
/** Comparisons of two uints, whose bigints compare as the values do. */
|
|
1335
|
+
const INT_CMP: Record<string, (a: bigint, b: bigint) => boolean> = {
|
|
1336
|
+
"<": (a, b) => a < b, "<=": (a, b) => a <= b, ">": (a, b) => a > b,
|
|
1337
|
+
">=": (a, b) => a >= b, "==": (a, b) => a === b, "!=": (a, b) => a !== b,
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1287
1340
|
const FOLD_CMP: Record<string, (a: number, b: number) => boolean> = {
|
|
1288
1341
|
"<": (a, b) => a < b, "<=": (a, b) => a <= b, ">": (a, b) => a > b,
|
|
1289
1342
|
">=": (a, b) => a >= b, "==": (a, b) => a === b, "!=": (a, b) => a !== b,
|
|
@@ -1313,11 +1366,11 @@ function isInt(v: LV): v is bigint | Val {
|
|
|
1313
1366
|
return typeof v === "bigint" || (v instanceof Val && v.kind === "int")
|
|
1314
1367
|
}
|
|
1315
1368
|
|
|
1316
|
-
function isUint(v: LV): v is Val {
|
|
1317
|
-
return v instanceof Val && v.kind === "uint"
|
|
1369
|
+
function isUint(v: LV): v is Uint | Val {
|
|
1370
|
+
return v instanceof Uint || (v instanceof Val && v.kind === "uint")
|
|
1318
1371
|
}
|
|
1319
1372
|
|
|
1320
|
-
function isIntLike(v: LV): v is bigint | Val {
|
|
1373
|
+
function isIntLike(v: LV): v is bigint | Uint | Val {
|
|
1321
1374
|
return isInt(v) || isUint(v)
|
|
1322
1375
|
}
|
|
1323
1376
|
|
|
@@ -1325,25 +1378,37 @@ function isBoolish(v: LV): boolean {
|
|
|
1325
1378
|
return typeof v === "boolean" || v instanceof Test || (v instanceof Val && v.kind === "bool")
|
|
1326
1379
|
}
|
|
1327
1380
|
|
|
1328
|
-
function isConstant(v: LV): v is number | bigint {
|
|
1329
|
-
return typeof v === "number" || typeof v === "bigint"
|
|
1381
|
+
function isConstant(v: LV): v is number | bigint | Uint {
|
|
1382
|
+
return typeof v === "number" || typeof v === "bigint" || v instanceof Uint
|
|
1330
1383
|
}
|
|
1331
1384
|
|
|
1332
|
-
function kindWant(v:
|
|
1333
|
-
return v
|
|
1385
|
+
function kindWant(v: LV): "int" | "uint" {
|
|
1386
|
+
return isUint(v) ? "uint" : "int"
|
|
1334
1387
|
}
|
|
1335
1388
|
|
|
1336
1389
|
function num(v: LV): number {
|
|
1337
|
-
return typeof v === "bigint" ? Number(v) : v as number
|
|
1390
|
+
return typeof v === "bigint" ? Number(v) : v instanceof Uint ? Number(v.v) : v as number
|
|
1338
1391
|
}
|
|
1339
1392
|
|
|
1340
|
-
/** An int operand as a node the EDSL's int methods take. */
|
|
1341
|
-
function intVal(v: bigint | Val): Val {
|
|
1342
|
-
|
|
1393
|
+
/** An int or uint operand as a node the EDSL's int methods take. */
|
|
1394
|
+
function intVal(v: bigint | Uint | Val): Val {
|
|
1395
|
+
if (typeof v === "bigint") return sl.int(Number(v)) as unknown as Val
|
|
1396
|
+
if (v instanceof Uint) return sl.uint(Number(v.v)) as unknown as Val
|
|
1397
|
+
return v
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* The second operand of an int method: an int constant as a number, which
|
|
1402
|
+
* takes the first operand's kind, and a uint one as a node, so an int meeting
|
|
1403
|
+
* it is refused rather than made a uint.
|
|
1404
|
+
*/
|
|
1405
|
+
function other(v: bigint | Uint | Val): number | Val {
|
|
1406
|
+
return typeof v === "bigint" ? Number(v) : intVal(v)
|
|
1343
1407
|
}
|
|
1344
1408
|
|
|
1345
1409
|
function asVal(v: LV): Val {
|
|
1346
1410
|
if (v instanceof Val) return v
|
|
1411
|
+
if (v instanceof Uint) return intVal(v)
|
|
1347
1412
|
return sl.float(typeof v === "number" ? v : num(v)) as unknown as Val
|
|
1348
1413
|
}
|
|
1349
1414
|
|
|
@@ -1366,6 +1431,7 @@ function article(t: VType): string {
|
|
|
1366
1431
|
|
|
1367
1432
|
function describeLV(v: LV): string {
|
|
1368
1433
|
if (typeof v === "bigint") return "an int"
|
|
1434
|
+
if (v instanceof Uint) return "a uint"
|
|
1369
1435
|
if (typeof v === "boolean" || v instanceof Test) return "a bool"
|
|
1370
1436
|
if (typeof v === "number") return "a float"
|
|
1371
1437
|
return v.kind === undefined ? `a ${widthType(v.width)}` : v.kind === "int" ? "an int" : `a ${v.kind}`
|
|
@@ -1380,6 +1446,7 @@ function constValue(v: Val): bigint | null {
|
|
|
1380
1446
|
/** Two lowered values that are certainly the same, so a join can leave them out. */
|
|
1381
1447
|
function same(a: LV, b: LV): boolean {
|
|
1382
1448
|
if (a === b) return true
|
|
1449
|
+
if (a instanceof Uint && b instanceof Uint) return a.v === b.v
|
|
1383
1450
|
return a instanceof Val && b instanceof Val && a.ref === b.ref && a.width === b.width && a.kind === b.kind
|
|
1384
1451
|
}
|
|
1385
1452
|
|
package/src/lang/parser.ts
CHANGED
|
@@ -636,7 +636,7 @@ function zeroOf(type: TypeName, at: Pos): Expr {
|
|
|
636
636
|
* inside braces that are the case's last statement, as in `case 0: { ...; break; }`.
|
|
637
637
|
*/
|
|
638
638
|
function dropClosingBreak(body: Stmt[]): boolean {
|
|
639
|
-
const last = body.
|
|
639
|
+
const last = body[body.length - 1]
|
|
640
640
|
if (last?.k === "break") {
|
|
641
641
|
body.pop()
|
|
642
642
|
return true
|
package/src/ops.ts
CHANGED
|
@@ -39,7 +39,7 @@ export const TEXTURE_SLOTS = 4
|
|
|
39
39
|
|
|
40
40
|
/** Input ids, which the web emitter switches on. */
|
|
41
41
|
export const INPUT_ID: Record<string, number> = {
|
|
42
|
-
uv: 0, fragCoord: 1, resolution: 2, time: 3, aspect: 4,
|
|
42
|
+
uv: 0, fragCoord: 1, resolution: 2, time: 3, aspect: 4, frame: 5, deltaTime: 6,
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export const SLOP = {
|
|
@@ -146,6 +146,9 @@ export const SLOP = {
|
|
|
146
146
|
// Sampling
|
|
147
147
|
SAMPLE: 144,
|
|
148
148
|
SAMPLE_LOD: 145,
|
|
149
|
+
// What this program drew the frame before, at a uv (IR 5). No slot: the
|
|
150
|
+
// host keeps it, and a program that never reads it has none.
|
|
151
|
+
SAMPLE_PREVIOUS: 146,
|
|
149
152
|
} as const
|
|
150
153
|
|
|
151
154
|
export type SLOpCode = (typeof SLOP)[keyof typeof SLOP]
|
|
@@ -187,7 +190,7 @@ export const SL_ARITY: Record<number, number> = {
|
|
|
187
190
|
[SLOP.NOISE]: 1, [SLOP.SIMPLEX]: 1, [SLOP.FBM]: -1, [SLOP.SDF]: -1,
|
|
188
191
|
[SLOP.VORONOI]: 1, [SLOP.TURBULENCE]: -1, [SLOP.RIDGED]: -1,
|
|
189
192
|
|
|
190
|
-
[SLOP.SAMPLE]: 1, [SLOP.SAMPLE_LOD]: 2,
|
|
193
|
+
[SLOP.SAMPLE]: 1, [SLOP.SAMPLE_LOD]: 2, [SLOP.SAMPLE_PREVIOUS]: 1,
|
|
191
194
|
}
|
|
192
195
|
|
|
193
196
|
/** Name per opcode, for error messages and for the HLSL emitter's comments. */
|
|
@@ -230,7 +233,7 @@ export interface SLSurface {
|
|
|
230
233
|
|
|
231
234
|
export const SL_HLSL: Record<number, SLSurface> = {
|
|
232
235
|
[SLOP.CONST]: { syntax: "a number literal" },
|
|
233
|
-
[SLOP.INPUT]: { syntax: "uv, fragCoord, resolution, time, aspect" },
|
|
236
|
+
[SLOP.INPUT]: { syntax: "uv, fragCoord, resolution, time, aspect, frame, deltaTime" },
|
|
234
237
|
[SLOP.UNIFORM]: { syntax: "uniform <type> name = <default>;" },
|
|
235
238
|
[SLOP.COMPOSE]: { syntax: "float2(), float3(), float4()" },
|
|
236
239
|
[SLOP.SWIZZLE]: { syntax: ".xyzw / .rgba" },
|
|
@@ -312,6 +315,7 @@ export const SL_HLSL: Record<number, SLSurface> = {
|
|
|
312
315
|
|
|
313
316
|
[SLOP.SAMPLE]: { call: "tex2D" },
|
|
314
317
|
[SLOP.SAMPLE_LOD]: { call: "tex2Dlod" },
|
|
318
|
+
[SLOP.SAMPLE_PREVIOUS]: { syntax: "tex2D(previous, uv)" },
|
|
315
319
|
}
|
|
316
320
|
|
|
317
321
|
/**
|
package/src/params.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { BUILTINS } from "./lang/builtins"
|
|
|
20
20
|
import { LIB_FUNCTIONS, SDF_CALLS } from "./lib/table"
|
|
21
21
|
import { LIB_PARAM_NAMES } from "./lib/names"
|
|
22
22
|
import { SL_SDF_PARAMS, SL_SDF_SHAPES, type SlSdfKind } from "./shapes"
|
|
23
|
-
import type { SourceInputName } from "./ir"
|
|
23
|
+
import type { PREVIOUS, SourceInputName } from "./ir"
|
|
24
24
|
import type { AttributeName } from "./lang/attributes"
|
|
25
25
|
|
|
26
26
|
/** One function of the helper library, as its source declares it. */
|
|
@@ -186,12 +186,15 @@ export const ATTRIBUTE_DOCS: Readonly<Record<AttributeName, string>> = {
|
|
|
186
186
|
* the output is a UI element in OneJS and an image on a Magerie board, and the
|
|
187
187
|
* clock is whichever one the host runs.
|
|
188
188
|
*/
|
|
189
|
-
export const INPUT_DOCS: Readonly<Record<SourceInputName, string>> = {
|
|
189
|
+
export const INPUT_DOCS: Readonly<Record<SourceInputName | typeof PREVIOUS, string>> = {
|
|
190
190
|
uv: "The centre of this pixel in the output, 0 to 1 on each axis, with y up.",
|
|
191
191
|
fragCoord: "The centre of this pixel in the output, in pixels, with y up: the first column is at 0.5.",
|
|
192
192
|
resolution: "The output's size in pixels.",
|
|
193
193
|
time: "The clock, in seconds.",
|
|
194
194
|
aspect: "The output's width divided by its height.",
|
|
195
|
+
frame: "Frames drawn since previous was last cleared, from 0: an int, 0 exactly when previous is clear.",
|
|
196
|
+
deltaTime: "Seconds since the frame before; 0 when a frame redraws the same time.",
|
|
197
|
+
previous: "What this program drew the frame before, as a texture: tex2D(previous, uv). Clear on the first.",
|
|
195
198
|
texel: "One pixel, in uv: 1 / resolution. Step by it to read a neighbouring pixel.",
|
|
196
199
|
centered: "uv with 0 at the centre and x scaled by aspect, so a circle drawn in it stays round.",
|
|
197
200
|
}
|
package/src/serial.ts
CHANGED
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
* older one is migrated to this one and rehashed under it.
|
|
10
10
|
*/
|
|
11
11
|
import {
|
|
12
|
-
INPUTS, SLError, SL_HASH_VERSION, SL_IR_VERSION, TYPE, checkCaps, controlOf, controlProblem, formProblem, hashProgram,
|
|
13
|
-
programVersion, type NodeRef, type Program, type SLNode, type SLType, type TextureDecl, type UniformDecl,
|
|
12
|
+
INPUTS, STEP_INPUTS, SLError, SL_HASH_VERSION, SL_IR_VERSION, TYPE, checkCaps, controlOf, controlProblem, formProblem, hashProgram, intUniformProblem,
|
|
13
|
+
programVersion, type HostInputName, type NodeRef, type Program, type SLNode, type SLType, type TextureDecl, type UniformDecl,
|
|
14
14
|
} from "./ir"
|
|
15
15
|
import { SLOP, SL_ARITY, SL_NAME } from "./ops"
|
|
16
16
|
|
|
17
|
+
const HOST_INPUTS: Readonly<Record<HostInputName, SLType>> = { ...INPUTS, ...STEP_INPUTS }
|
|
18
|
+
|
|
17
19
|
export interface ProgramJSON {
|
|
18
20
|
/** `SL_IR_VERSION` of the writer. Absent means 1. */
|
|
19
21
|
v: number
|
|
@@ -166,8 +168,10 @@ function node(n: unknown, i: number): SLNode {
|
|
|
166
168
|
return withKind({ k: "const", type: t, v })
|
|
167
169
|
}
|
|
168
170
|
case "input": {
|
|
169
|
-
const name = x.name as
|
|
170
|
-
if (!(name
|
|
171
|
+
const name = x.name as HostInputName
|
|
172
|
+
if (!Object.prototype.hasOwnProperty.call(HOST_INPUTS, name) || HOST_INPUTS[name] !== t) {
|
|
173
|
+
fail(`node ${i} reads an input "${String(x.name)}" that does not exist at that width`)
|
|
174
|
+
}
|
|
171
175
|
return { k: "input", type: t, name }
|
|
172
176
|
}
|
|
173
177
|
case "uniform": {
|
|
@@ -209,6 +213,12 @@ function uniform(u: unknown, i: number): UniformDecl {
|
|
|
209
213
|
}
|
|
210
214
|
const out: UniformDecl = { name: x.name!, type: x.type as SLType, value: x.value!.slice() }
|
|
211
215
|
if (x.colour === true) out.colour = true
|
|
216
|
+
if (x.kind !== undefined) {
|
|
217
|
+
if (x.kind !== "int") fail(`uniform ${i} holds a "${String(x.kind)}"; a uniform is a float or an int`)
|
|
218
|
+
const problem = intUniformProblem(out.type, out.value)
|
|
219
|
+
if (problem !== null) fail(`uniform ${i}: ${problem}`)
|
|
220
|
+
out.kind = "int"
|
|
221
|
+
}
|
|
212
222
|
// Its control: shapes checked here, sense checked by the one check the
|
|
213
223
|
// compiler uses, so a hand edited file cannot give a host a range it would
|
|
214
224
|
// choke on.
|
package/src/sl.ts
CHANGED
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
import { parseColor as parseHex } from "./color"
|
|
26
26
|
import {
|
|
27
|
-
Builder, INPUTS, RAMP_STOP_COMPUTED, SLError, TYPE, hashProgram, programVersion, truncateHeld, widthName, writtenColour,
|
|
28
|
-
type InputName, type NodeRef, type Program, type SLKind, type SLNode, type SLType, type UniformControl,
|
|
27
|
+
Builder, INPUTS, RAMP_STOP_COMPUTED, STEP_INPUTS, SLError, TYPE, hashProgram, programVersion, truncateHeld, widthName, writtenColour,
|
|
28
|
+
type HostInputName, type InputName, type NodeRef, type Program, type SLKind, type SLNode, type SLType, type UniformControl,
|
|
29
29
|
} from "./ir"
|
|
30
30
|
import { SLOP, SL_HLSL, SL_NAME, type SLOpCode } from "./ops"
|
|
31
31
|
import { SL_SDF_PARAMS, SL_SDF_SHAPES, type SlSdfKind } from "./shapes"
|
|
@@ -497,6 +497,26 @@ export interface ProgramInputs {
|
|
|
497
497
|
texel: Vec2
|
|
498
498
|
/** `(uv - 0.5) * vec2(aspect, 1)`: 0 at the centre, and a circle stays round. Recorded only if read. */
|
|
499
499
|
centered: Vec2
|
|
500
|
+
/**
|
|
501
|
+
* Frames drawn since `previous` was last cleared, from 0: so `frame` is 0
|
|
502
|
+
* exactly when `previous` is clear. Recorded only if read, and the host
|
|
503
|
+
* counts frames only for a program that reads it (`compile(p).reads`).
|
|
504
|
+
*/
|
|
505
|
+
frame: Int
|
|
506
|
+
/** Seconds from the frame before to this one; 0 when a frame redraws the same time. Recorded only if read. */
|
|
507
|
+
deltaTime: Float
|
|
508
|
+
/**
|
|
509
|
+
* What this program drew the frame before: transparent black on the first
|
|
510
|
+
* frame and after anything that clears it (a new size, a seek, time going
|
|
511
|
+
* back). Linear and straight alpha, as `main` returned it, bilinear and
|
|
512
|
+
* clamped at the edges. A host keeps it only for a program that samples it.
|
|
513
|
+
*/
|
|
514
|
+
previous: PreviousFrame
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** The frame a program drew before this one. It has one level, so there is no `sampleLevel`. */
|
|
518
|
+
export interface PreviousFrame {
|
|
519
|
+
sample(uv: Vec2): Vec4
|
|
500
520
|
}
|
|
501
521
|
|
|
502
522
|
/**
|
|
@@ -515,11 +535,25 @@ export function program(fn: (inputs: ProgramInputs) => Vec4): Program {
|
|
|
515
535
|
;(inputs as any)[name] = mk(b, ref, width as SLType)
|
|
516
536
|
}
|
|
517
537
|
// Getters, so a program that never reads one records nothing for it
|
|
518
|
-
// (DERIVED_INPUTS in ir.ts). A second read is hash
|
|
538
|
+
// (DERIVED_INPUTS and STEP_INPUTS in ir.ts). A second read is hash
|
|
539
|
+
// consed to the first.
|
|
540
|
+
const step = (name: keyof typeof STEP_INPUTS): Val =>
|
|
541
|
+
mk(b, b.add({ k: "input", type: STEP_INPUTS[name], name: name as HostInputName }), STEP_INPUTS[name])
|
|
519
542
|
Object.defineProperties(inputs, {
|
|
520
543
|
texel: { enumerable: true, get: () => vec2(1, 1).div(inputs.resolution) },
|
|
521
544
|
centered: { enumerable: true, get: () => inputs.uv.sub(0.5).mul(vec2(inputs.aspect, 1)) },
|
|
545
|
+
// The host hands the count over as a float, which holds it exactly
|
|
546
|
+
// for as long as anything will run.
|
|
547
|
+
frame: { enumerable: true, get: () => convert(step("frame"), "int") },
|
|
548
|
+
deltaTime: { enumerable: true, get: () => step("deltaTime") },
|
|
522
549
|
})
|
|
550
|
+
inputs.previous = {
|
|
551
|
+
sample(uv: Vec2): Vec4 {
|
|
552
|
+
if (uv.owner !== b) throw new SLError("a value from another program cannot be used in this one")
|
|
553
|
+
if (uv.width !== TYPE.VEC2 || uv.kind !== undefined) throw new SLError(`the previous frame is read at a float2, and this is ${uv.kind === undefined ? `a ${widthName(uv.width)}` : `an ${uv.kind}`}`)
|
|
554
|
+
return mk(b, b.call(SLOP.SAMPLE_PREVIOUS, TYPE.VEC4, [uv.ref]), TYPE.VEC4)
|
|
555
|
+
},
|
|
556
|
+
}
|
|
523
557
|
const out = fn(inputs)
|
|
524
558
|
if (!(out instanceof Val)) throw new SLError("a program must return an sl value, not " + typeof out)
|
|
525
559
|
if (out.width !== TYPE.VEC4) {
|
|
@@ -956,6 +990,15 @@ export const uniform = {
|
|
|
956
990
|
const b = ctx()
|
|
957
991
|
return mk(b, b.uniform(name, TYPE.FLOAT, [value], false, control), TYPE.FLOAT)
|
|
958
992
|
},
|
|
993
|
+
/**
|
|
994
|
+
* An int, read as the whole number it holds. Its slot is a float, as every
|
|
995
|
+
* host binds one; the declaration says `kind: "int"`, so a host shows a
|
|
996
|
+
* whole number field for it.
|
|
997
|
+
*/
|
|
998
|
+
int(name: string, value = 0, control: UniformControl = {}): Int {
|
|
999
|
+
const b = ctx()
|
|
1000
|
+
return int(mk(b, b.uniform(name, TYPE.FLOAT, [value], false, control, "int"), TYPE.FLOAT) as Float)
|
|
1001
|
+
},
|
|
959
1002
|
vec2(name: string, value: [number, number] = [0, 0], control: UniformControl = {}): Vec2 {
|
|
960
1003
|
const b = ctx()
|
|
961
1004
|
return mk(b, b.uniform(name, TYPE.VEC2, value, false, control), TYPE.VEC2)
|
package/src/structure.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { INPUTS, operands, reachable, type NodeRef, type Program, type SLNode } from "./ir"
|
|
19
|
-
import {
|
|
19
|
+
import { isSampling } from "./ops"
|
|
20
20
|
|
|
21
21
|
export interface Region {
|
|
22
22
|
id: number
|
|
@@ -171,7 +171,7 @@ function varyingValues(nodes: readonly SLNode[], reach: NodeRef[]): Set<NodeRef>
|
|
|
171
171
|
switch (n.k) {
|
|
172
172
|
case "input": if (PER_PIXEL.has(n.name)) mark(ref); break
|
|
173
173
|
case "call":
|
|
174
|
-
if (n.op
|
|
174
|
+
if (isSampling(n.op) || n.args.some((a) => out.has(a))) mark(ref)
|
|
175
175
|
break
|
|
176
176
|
case "swizzle": if (out.has(n.src)) mark(ref); break
|
|
177
177
|
case "proj": {
|
package/src/tables.ts
CHANGED
|
@@ -13,8 +13,8 @@ export {
|
|
|
13
13
|
SLOP, SL_ARITY, SL_NAME, SL_HLSL, SL_CALL_NAMES, SL_GLSL_HINT, SL_UNIMPLEMENTED,
|
|
14
14
|
} from "./ops"
|
|
15
15
|
export type { SLSurface } from "./ops"
|
|
16
|
-
export { DERIVED_INPUTS, INPUTS, SOURCE_INPUTS } from "./ir"
|
|
17
|
-
export type { DerivedInputName, InputName, SourceInputName } from "./ir"
|
|
16
|
+
export { DERIVED_INPUTS, INPUTS, PREVIOUS, SOURCE_INPUTS, STEP_INPUTS } from "./ir"
|
|
17
|
+
export type { DerivedInputName, HostInputName, InputName, SourceInputName, StepInputName } from "./ir"
|
|
18
18
|
export { SL_SDF_SHAPES, SL_SDF_PARAMS } from "./shapes"
|
|
19
19
|
export { ATTRIBUTE_DOCS, BUILTIN_DOCS, BUILTIN_PARAMS, INPUT_DOCS, LIB_SIGNATURES, SL_SDF_PARAM_NAMES } from "./params"
|
|
20
20
|
export { ATTRIBUTE_NAMES } from "./lang/attributes"
|