onejs-sl 0.6.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 +16 -0
- package/README.md +44 -5
- 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 +70 -10
- 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 +12 -3
- package/src/lang/parser.ts +1 -1
- package/src/ops.ts +7 -3
- package/src/params.ts +5 -2
- package/src/serial.ts +8 -4
- package/src/sl.ts +37 -3
- package/src/structure.ts +2 -2
- package/src/tables.ts +2 -2
- package/src/web.ts +31 -7
package/src/core.ts
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
* game never executes.
|
|
9
9
|
*/
|
|
10
10
|
export * as sl from "./sl"
|
|
11
|
-
export type { Float, Vec2, Vec3, Vec4, Num, ProgramInputs, Texture } from "./sl"
|
|
11
|
+
export type { Float, Vec2, Vec3, Vec4, Num, ProgramInputs, PreviousFrame, Texture } from "./sl"
|
|
12
12
|
export {
|
|
13
|
-
TYPE, INPUTS, MAX_NODES, SLError, hashProgram, widthName, SL_IR_VERSION, SL_HASH_VERSION,
|
|
14
|
-
CONTROL_FIELDS, controlProblem,
|
|
13
|
+
TYPE, INPUTS, STEP_INPUTS, PREVIOUS, MAX_NODES, SLError, hashProgram, widthName, SL_IR_VERSION, SL_HASH_VERSION,
|
|
14
|
+
inputsUsed, readsOf, CONTROL_FIELDS, controlProblem,
|
|
15
15
|
} from "./ir"
|
|
16
16
|
export type {
|
|
17
|
-
SLType, InputName, NodeRef, SLNode, Program, UniformDecl, UniformControl, TextureDecl,
|
|
17
|
+
SLType, InputName, StepInputName, HostInputName, SLReads, NodeRef, SLNode, Program, UniformDecl, UniformControl, TextureDecl,
|
|
18
18
|
} from "./ir"
|
|
19
19
|
export { SLOP, SL_ARITY, SL_NAME, INPUT_ID, isSampling, UNIFORM_SLOTS, TEXTURE_SLOTS } from "./ops"
|
|
20
20
|
export type { SLOpCode } from "./ops"
|
package/src/hlsl.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* next time it is generated.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { SLError, SL_HASH_VERSION, type Program } from "./ir"
|
|
18
|
+
import { SLError, SL_HASH_VERSION, readsOf, type Program } from "./ir"
|
|
19
19
|
import { emitBody, lit, type BodyTarget } from "./body"
|
|
20
20
|
|
|
21
21
|
/** The property name a uniform gets. Prefixed so it cannot collide with ours. */
|
|
@@ -48,6 +48,7 @@ export function emitShader(p: Program, options: EmitOptions = {}): string {
|
|
|
48
48
|
const name = options.name ?? `Hidden/SLGenerated/${p.hash}`
|
|
49
49
|
const include = options.include ?? "SLCommon.cginc"
|
|
50
50
|
const body = emitFragmentBody(p)
|
|
51
|
+
const reads = readsOf(p)
|
|
51
52
|
|
|
52
53
|
const props: string[] = [
|
|
53
54
|
` _Secs ("Seconds", Float) = 0`,
|
|
@@ -69,6 +70,16 @@ export function emitShader(p: Program, options: EmitOptions = {}): string {
|
|
|
69
70
|
props.push(` _Tex${t.slot} ("${t.name}", 2D) = "white" {}`)
|
|
70
71
|
decls.push(` sampler2D _Tex${t.slot};`)
|
|
71
72
|
}
|
|
73
|
+
// Only for a program that samples it, so every other program's shader is
|
|
74
|
+
// the text it always was. Transparent black is the previous frame's clear
|
|
75
|
+
// value, so a host that binds nothing draws the first frame.
|
|
76
|
+
if (reads.previous) {
|
|
77
|
+
props.push(` _Prev ("Previous frame", 2D) = "black" {}`)
|
|
78
|
+
decls.push(` sampler2D _Prev;`)
|
|
79
|
+
}
|
|
80
|
+
// What the program returns is the next frame's previous, kept at 16 bits
|
|
81
|
+
// a channel, so it leaves at full precision rather than as a fixed4.
|
|
82
|
+
const out = reads.previous ? "float4" : "fixed4"
|
|
72
83
|
|
|
73
84
|
return `// GENERATED from a shader language program. Do not edit.
|
|
74
85
|
//
|
|
@@ -112,7 +123,7 @@ ${decls.join("\n")}
|
|
|
112
123
|
return o;
|
|
113
124
|
}
|
|
114
125
|
|
|
115
|
-
|
|
126
|
+
${out} frag(v2f i) : SV_Target
|
|
116
127
|
{
|
|
117
128
|
${body}
|
|
118
129
|
}
|
|
@@ -125,7 +136,9 @@ ${body}
|
|
|
125
136
|
|
|
126
137
|
/**
|
|
127
138
|
* The frame's side of the body: OneJS's names for the inputs, a uniform's
|
|
128
|
-
* material property, and `tex2D` and `tex2Dlod` on the slot's sampler.
|
|
139
|
+
* material property, and `tex2D` and `tex2Dlod` on the slot's sampler. The
|
|
140
|
+
* host puts the frame count and the step in `_Res`'s spare zw, and binds the
|
|
141
|
+
* previous frame as `_Prev`. `sl_toLinear` stays a
|
|
129
142
|
* call because `SLCommon.cginc` decides Gamma or Linear per project, at the
|
|
130
143
|
* shader compile, so this target always emits it.
|
|
131
144
|
*/
|
|
@@ -147,10 +160,15 @@ function unityTarget(p: Program): BodyTarget {
|
|
|
147
160
|
resolution: "_Res.xy",
|
|
148
161
|
time: "_Secs",
|
|
149
162
|
aspect: "(_Res.x / max(_Res.y, 1.0))",
|
|
163
|
+
frame: "_Res.z",
|
|
164
|
+
deltaTime: "_Res.w",
|
|
150
165
|
},
|
|
151
166
|
uniform: (slot) => uniformProperty(p.uniforms[slot]!.name),
|
|
152
167
|
sample: (slot, uv) => `tex2D(_Tex${slot}, ${uv})`,
|
|
153
168
|
sampleLevel: (slot, uv, lod) => `tex2Dlod(_Tex${slot}, float4(${uv}, 0.0, ${lod}))`,
|
|
169
|
+
// Flipped as this frame flipped the uv it wrote at, so a read at a
|
|
170
|
+
// pixel's own uv is that pixel whichever way the host's target runs.
|
|
171
|
+
previous: (uv) => `tex2Dlod(_Prev, float4(${uv} * float2(1.0, 1.0 - 2.0 * _FlipY) + float2(0.0, _FlipY), 0.0, 0.0))`,
|
|
154
172
|
colour: "linear",
|
|
155
173
|
indent: " ",
|
|
156
174
|
loopAttribute: "[loop]",
|
package/src/ir.ts
CHANGED
|
@@ -57,9 +57,35 @@ export const DERIVED_INPUTS = {
|
|
|
57
57
|
} as const
|
|
58
58
|
export type DerivedInputName = keyof typeof DERIVED_INPUTS
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Inputs the host hands over only to a program that reads one (IR 5,
|
|
62
|
+
* `Specs/SL_NEXT.md` 4). They are not recorded up front as `INPUTS` are, so a
|
|
63
|
+
* program that never names one holds no node for it and keeps its IR version,
|
|
64
|
+
* its hash and its bytes. `compile(p).reads` says which a program reads.
|
|
65
|
+
*
|
|
66
|
+
* frame frames drawn since the previous frame was last cleared, from 0.
|
|
67
|
+
* An int in the language; the host hands over a float holding it
|
|
68
|
+
* exactly, and the program converts
|
|
69
|
+
* deltaTime seconds from the frame before to this one; 0 on a frame that
|
|
70
|
+
* redraws the same time
|
|
71
|
+
*
|
|
72
|
+
* The previous frame itself is SLOP.SAMPLE_PREVIOUS, an op rather than an input.
|
|
73
|
+
*/
|
|
74
|
+
export const STEP_INPUTS = {
|
|
75
|
+
frame: TYPE.FLOAT,
|
|
76
|
+
deltaTime: TYPE.FLOAT,
|
|
77
|
+
} as const
|
|
78
|
+
export type StepInputName = keyof typeof STEP_INPUTS
|
|
79
|
+
|
|
80
|
+
/** The name `.sl` reads the previous frame by, as a texture: `tex2D(previous, uv)`. */
|
|
81
|
+
export const PREVIOUS = "previous"
|
|
82
|
+
|
|
83
|
+
/** Every input a host hands over: the ones every program has, then the ones a program asks for. */
|
|
84
|
+
export type HostInputName = InputName | StepInputName
|
|
85
|
+
|
|
60
86
|
/** Every input a program can name: the host's, then the derived ones. */
|
|
61
|
-
export type SourceInputName =
|
|
62
|
-
export const SOURCE_INPUTS: Readonly<Record<SourceInputName, SLType>> = { ...INPUTS, ...DERIVED_INPUTS }
|
|
87
|
+
export type SourceInputName = HostInputName | DerivedInputName
|
|
88
|
+
export const SOURCE_INPUTS: Readonly<Record<SourceInputName, SLType>> = { ...INPUTS, ...STEP_INPUTS, ...DERIVED_INPUTS }
|
|
63
89
|
|
|
64
90
|
/** Index into `Program.nodes`. Always refers backwards. */
|
|
65
91
|
export type NodeRef = number
|
|
@@ -77,7 +103,7 @@ export type SLKind = "int" | "uint" | "bool"
|
|
|
77
103
|
*/
|
|
78
104
|
export type ValueNode =
|
|
79
105
|
| { k: "const"; type: SLType; kind?: SLKind; v: number[] }
|
|
80
|
-
| { k: "input"; type: SLType; kind?: undefined; name:
|
|
106
|
+
| { k: "input"; type: SLType; kind?: undefined; name: HostInputName }
|
|
81
107
|
| { k: "uniform"; type: SLType; kind?: undefined; slot: number }
|
|
82
108
|
| { k: "swizzle"; type: SLType; kind?: SLKind; src: NodeRef; chans: number[] }
|
|
83
109
|
| { k: "call"; type: SLType; kind?: SLKind; op: SLOpCode; args: NodeRef[]; imm?: number[] }
|
|
@@ -454,17 +480,43 @@ export function reachable(nodes: SLNode[], result: NodeRef): NodeRef[] {
|
|
|
454
480
|
}
|
|
455
481
|
|
|
456
482
|
/**
|
|
457
|
-
* The inputs a program reads, in `INPUTS` order
|
|
458
|
-
* value computed and never used is not in the
|
|
459
|
-
* that with `time` is still not animated.
|
|
483
|
+
* The inputs a program reads, in `INPUTS` order and then `STEP_INPUTS` order.
|
|
484
|
+
* Dead nodes do not count: a value computed and never used is not in the
|
|
485
|
+
* picture, so a program that does that with `time` is still not animated.
|
|
460
486
|
*/
|
|
461
|
-
export function inputsUsed(p: Program):
|
|
462
|
-
const read = new Set<
|
|
487
|
+
export function inputsUsed(p: Program): HostInputName[] {
|
|
488
|
+
const read = new Set<HostInputName>()
|
|
463
489
|
for (const ref of reachable(p.nodes, p.result)) {
|
|
464
490
|
const n = p.nodes[ref]
|
|
465
491
|
if (n.k === "input") read.add(n.name)
|
|
466
492
|
}
|
|
467
|
-
return (Object.keys(INPUTS) as
|
|
493
|
+
return ([...Object.keys(INPUTS), ...Object.keys(STEP_INPUTS)] as HostInputName[]).filter((name) => read.has(name))
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* What a program needs its host to keep from one frame to the next
|
|
498
|
+
* (`Specs/SL_NEXT.md` 4). A host that draws a program reading any of these
|
|
499
|
+
* has to hand it over, or refuse the program: nothing here has a value that
|
|
500
|
+
* would pass for right.
|
|
501
|
+
*
|
|
502
|
+
* previous the host keeps what the program drew last and binds it
|
|
503
|
+
* frame frames drawn since `previous` was last cleared
|
|
504
|
+
* deltaTime seconds since the frame before
|
|
505
|
+
*/
|
|
506
|
+
export interface SLReads {
|
|
507
|
+
previous: boolean
|
|
508
|
+
frame: boolean
|
|
509
|
+
deltaTime: boolean
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export function readsOf(p: Program): SLReads {
|
|
513
|
+
const out: SLReads = { previous: false, frame: false, deltaTime: false }
|
|
514
|
+
for (const ref of reachable(p.nodes, p.result)) {
|
|
515
|
+
const n = p.nodes[ref]!
|
|
516
|
+
if (n.k === "call" && n.op === SLOP.SAMPLE_PREVIOUS) out.previous = true
|
|
517
|
+
else if (n.k === "input" && n.name in STEP_INPUTS) out[n.name as StepInputName] = true
|
|
518
|
+
}
|
|
519
|
+
return out
|
|
468
520
|
}
|
|
469
521
|
|
|
470
522
|
/** Structural key for hash consing. Order matters and is fixed by the node shape. */
|
|
@@ -681,10 +733,12 @@ const IR4_OPS = new Set<number>([
|
|
|
681
733
|
* holds, and `fromJSON` moves it to the form IR 3 has.
|
|
682
734
|
*/
|
|
683
735
|
function nodeVersion(n: SLNode): number {
|
|
736
|
+
if (n.k === "input" && n.name in STEP_INPUTS) return 5
|
|
684
737
|
if (n.k === "if" || n.k === "loop" || n.k === "param" || n.k === "proj" || n.kind !== undefined) return 4
|
|
685
738
|
if (n.k !== "call") return 2
|
|
686
739
|
if (IR4_OPS.has(n.op)) return 4
|
|
687
740
|
switch (n.op) {
|
|
741
|
+
case SLOP.SAMPLE_PREVIOUS: return 5
|
|
688
742
|
case SLOP.SAMPLE_LOD: return 3
|
|
689
743
|
case SLOP.SDF:
|
|
690
744
|
case SLOP.FBM:
|
|
@@ -705,6 +759,8 @@ function nodeVersion(n: SLNode): number {
|
|
|
705
759
|
* the shape id the one immediate
|
|
706
760
|
* FBM, TURBULENCE, RIDGED the point and the octave count, FBM's kind the
|
|
707
761
|
* one immediate
|
|
762
|
+
* SAMPLE_PREVIOUS a float4 at a float2, and no immediates: there is one
|
|
763
|
+
* previous frame, so there is no slot to name
|
|
708
764
|
*/
|
|
709
765
|
export function formProblem(n: Extract<SLNode, { k: "call" }>, nodes: readonly SLNode[]): string | null {
|
|
710
766
|
const widths = n.args.map((a) => { const v = nodes[a]!; return v.k === "if" || v.k === "loop" ? "control" : v.type }).join(",")
|
|
@@ -719,6 +775,9 @@ export function formProblem(n: Extract<SLNode, { k: "call" }>, nodes: readonly S
|
|
|
719
775
|
if (widths === "2,1" && (n.imm?.length ?? 0) === rest) return null
|
|
720
776
|
return `${SL_NAME[n.op]} takes a float2 and its octave count, a float`
|
|
721
777
|
}
|
|
778
|
+
case SLOP.SAMPLE_PREVIOUS:
|
|
779
|
+
if (widths === "2" && n.type === 4 && n.kind === undefined && (n.imm?.length ?? 0) === 0) return null
|
|
780
|
+
return "the previous frame is a float4 read at a float2"
|
|
722
781
|
default:
|
|
723
782
|
return null
|
|
724
783
|
}
|
|
@@ -759,8 +818,9 @@ export const SL_HASH_VERSION = 2
|
|
|
759
818
|
* 3 SAMPLE_LOD; an SDF's shape parameters and a noise's octave count are
|
|
760
819
|
* operands, where they were immediates
|
|
761
820
|
* 4 control flow (if, loop, their params and projs), ints, uints and bools
|
|
821
|
+
* 5 the previous frame (SAMPLE_PREVIOUS), and the frame and deltaTime inputs
|
|
762
822
|
*/
|
|
763
|
-
export const SL_IR_VERSION =
|
|
823
|
+
export const SL_IR_VERSION = 5
|
|
764
824
|
|
|
765
825
|
function fnv1a(s: string): string {
|
|
766
826
|
let h = 0x811c9dc5
|
package/src/lang/README.md
CHANGED
|
@@ -245,7 +245,8 @@ It rewrites tokens and keeps everything else: comments, layout and names stay
|
|
|
245
245
|
where they were, so the result reads as the shader it came from. What it does:
|
|
246
246
|
|
|
247
247
|
- The spellings that mean the same (`vec3`, `mix`, `fract`, `texture`,
|
|
248
|
-
`iTime`, `gl_FragCoord`) become the HLSL ones
|
|
248
|
+
`iTime`, `iFrame`, `iTimeDelta`, `gl_FragCoord`) become the HLSL ones and
|
|
249
|
+
the inputs. So do the calls that need an
|
|
249
250
|
expression: `mod` floors (`a - b * floor(a / b)`, never `%`, which
|
|
250
251
|
truncates), and `atan`, `inversesqrt`, `radians`, `degrees`, `exp2` and
|
|
251
252
|
`log2` become what they compute.
|
|
@@ -269,8 +270,8 @@ where they were, so the result reads as the shader it came from. What it does:
|
|
|
269
270
|
becomes a const; any other directive is kept as a comment, with a note.
|
|
270
271
|
- A name the language already means something by gets a `_` suffix, and an
|
|
271
272
|
`int` becomes a float (a for-loop counter stays an int), each with a note.
|
|
272
|
-
- What has no counterpart (`
|
|
273
|
-
nothing) is left in place with a note, for `errors` to point at.
|
|
273
|
+
- What has no counterpart (`iFrameRate`, `texelFetch`, `dFdx`, a function
|
|
274
|
+
returning nothing) is left in place with a note, for `errors` to point at.
|
|
274
275
|
|
|
275
276
|
It is not a GLSL parser, and is not meant to be: a shader it cannot follow comes
|
|
276
277
|
out with errors on the lines to look at, which is where a person pasting one
|
package/src/lang/check.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* is refused with the reason.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { DERIVED_INPUTS, INPUTS, tooManyTextures, tooManyUniforms } from "../ir"
|
|
31
|
+
import { DERIVED_INPUTS, INPUTS, PREVIOUS, STEP_INPUTS, tooManyTextures, tooManyUniforms } from "../ir"
|
|
32
32
|
import { TEXTURE_SLOTS, UNIFORM_SLOTS } from "../ops"
|
|
33
33
|
import { SL_GLSL_HINT } from "../ops"
|
|
34
34
|
import { BUILTINS, NOT_YET } from "./builtins"
|
|
@@ -44,6 +44,17 @@ const INPUT_NAMES = new Set(Object.keys(INPUTS))
|
|
|
44
44
|
* long before the language had one, and that must keep compiling.
|
|
45
45
|
*/
|
|
46
46
|
const DERIVED_NAMES = new Set(Object.keys(DERIVED_INPUTS))
|
|
47
|
+
/**
|
|
48
|
+
* `frame` and `deltaTime`, and `previous`, the texture that is the frame drawn
|
|
49
|
+
* before (IR 5). The language had none of them before 0.7.0, so a program may
|
|
50
|
+
* already use the name for its own value, texture or function, and keeps it.
|
|
51
|
+
* A function by the name is called by it; the bare name is still the input.
|
|
52
|
+
*/
|
|
53
|
+
const STEP_NAMES = new Set(Object.keys(STEP_INPUTS))
|
|
54
|
+
/** What a declaration may take the name of, with the words for it. */
|
|
55
|
+
const SHADOWABLE = new Set(["a builtin", "a derived input", "a built in input", "the previous frame"])
|
|
56
|
+
/** What a function may take the name of: only the names that came after files could have used them. */
|
|
57
|
+
const FUNCTION_MAY_TAKE = new Set(["a built in input", "the previous frame"])
|
|
47
58
|
|
|
48
59
|
/**
|
|
49
60
|
* The GLSL spellings whose HLSL name means the same wherever it is written, so
|
|
@@ -145,13 +156,15 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
145
156
|
if (textures.has(n)) return "a texture"
|
|
146
157
|
if (consts.has(n)) return "a const"
|
|
147
158
|
if (DERIVED_NAMES.has(n)) return "a derived input"
|
|
159
|
+
if (STEP_NAMES.has(n)) return "a built in input"
|
|
160
|
+
if (n === PREVIOUS) return "the previous frame"
|
|
148
161
|
return null
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
/** What a value may not be called: anything spoken for, except a builtin's or a prelude function's name. */
|
|
152
165
|
const valueClash = (n: string): string | null => {
|
|
153
166
|
const why = taken(n)
|
|
154
|
-
if (why !== null && why
|
|
167
|
+
if (why !== null && !SHADOWABLE.has(why)) return why
|
|
155
168
|
const fn = funcs.get(n)
|
|
156
169
|
return fn !== undefined && !fn.prelude ? "a function" : null
|
|
157
170
|
}
|
|
@@ -194,7 +207,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
194
207
|
for (const fn of unit.funcs) {
|
|
195
208
|
attempt(() => {
|
|
196
209
|
const clash = taken(fn.name)
|
|
197
|
-
if (clash !== null) fail(`"${fn.name}" already names ${clash}`, fn.pos, fn.name.length)
|
|
210
|
+
if (clash !== null && !FUNCTION_MAY_TAKE.has(clash)) fail(`"${fn.name}" already names ${clash}`, fn.pos, fn.name.length)
|
|
198
211
|
if (unit.funcs.filter((f) => f.name === fn.name).length > 1) {
|
|
199
212
|
fail(
|
|
200
213
|
`this file declares ${fn.name} more than once. There is no overloading: a function ` +
|
|
@@ -222,7 +235,8 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
222
235
|
if (main.params.length > 0) {
|
|
223
236
|
fail(
|
|
224
237
|
"main takes no parameters: what a program is given are the free identifiers uv, " +
|
|
225
|
-
"fragCoord, resolution, time, aspect, texel and
|
|
238
|
+
"fragCoord, resolution, time, aspect, texel, centered, frame and deltaTime, and the " +
|
|
239
|
+
"texture previous",
|
|
226
240
|
main.params[0]!.pos,
|
|
227
241
|
)
|
|
228
242
|
}
|
|
@@ -486,7 +500,15 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
486
500
|
case "bool":
|
|
487
501
|
return
|
|
488
502
|
case "ident": {
|
|
489
|
-
if (scope.has(e.name) || INPUT_NAMES.has(e.name) || DERIVED_NAMES.has(e.name) ||
|
|
503
|
+
if (scope.has(e.name) || INPUT_NAMES.has(e.name) || DERIVED_NAMES.has(e.name) || STEP_NAMES.has(e.name) ||
|
|
504
|
+
uniforms.has(e.name) || consts.has(e.name)) return
|
|
505
|
+
if (e.name === PREVIOUS && !textures.has(e.name)) {
|
|
506
|
+
fail(
|
|
507
|
+
"previous is the frame this program drew before, a texture, and a texture is only " +
|
|
508
|
+
"ever the first argument of tex2D. Write `tex2D(previous, uv)`",
|
|
509
|
+
e.pos, e.name.length,
|
|
510
|
+
)
|
|
511
|
+
}
|
|
490
512
|
if (textures.has(e.name)) {
|
|
491
513
|
fail(
|
|
492
514
|
`"${e.name}" is a texture, and a texture is only ever the first argument of ` +
|
|
@@ -604,7 +626,17 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
604
626
|
)
|
|
605
627
|
}
|
|
606
628
|
arity(e, n, builtin.min, builtin.max)
|
|
607
|
-
|
|
629
|
+
// The previous frame, unless something in this file took its name.
|
|
630
|
+
const previous = tex?.k === "ident" && tex.name === PREVIOUS && !textures.has(PREVIOUS) &&
|
|
631
|
+
!scope.has(PREVIOUS) && !uniforms.has(PREVIOUS) && !consts.has(PREVIOUS)
|
|
632
|
+
if (previous && n === "tex2Dlod") {
|
|
633
|
+
fail(
|
|
634
|
+
"previous has one level, the frame as drawn, so there is no mip level to pick. " +
|
|
635
|
+
"Read it with `tex2D(previous, uv)`",
|
|
636
|
+
e.pos,
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
if (!previous && (tex === undefined || tex.k !== "ident" || !textures.has(tex.name))) {
|
|
608
640
|
const lod = n === "tex2Dlod" ? ", 0" : ""
|
|
609
641
|
fail(
|
|
610
642
|
`${n} samples a texture declared in this file, as in \`texture2D grain;\` ` +
|
|
@@ -654,7 +686,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
654
686
|
/** Refuses a name nothing declares, offering the nearest one spelled almost like it. */
|
|
655
687
|
function unknown(n: string, scope: Set<string>, pos: Pos): never {
|
|
656
688
|
const near = nearest(n, [
|
|
657
|
-
...scope, ...INPUT_NAMES, ...DERIVED_NAMES, ...uniforms.keys(), ...textures.keys(), ...consts.keys(),
|
|
689
|
+
...scope, ...INPUT_NAMES, ...DERIVED_NAMES, ...STEP_NAMES, ...uniforms.keys(), ...textures.keys(), ...consts.keys(),
|
|
658
690
|
...funcs.keys(), ...Object.keys(BUILTINS),
|
|
659
691
|
])
|
|
660
692
|
if (near === null) fail(`"${n}" is not declared`, pos, n.length)
|
package/src/lang/classify.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* wants that should ask it.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { SOURCE_INPUTS } from "../ir"
|
|
16
|
+
import { PREVIOUS, SOURCE_INPUTS } from "../ir"
|
|
17
17
|
import { BUILTINS } from "./builtins"
|
|
18
18
|
import { lex } from "./lexer"
|
|
19
19
|
import { PRELUDE_NAMES } from "./prelude-source"
|
|
@@ -42,7 +42,7 @@ export interface SLClassifiedToken {
|
|
|
42
42
|
length: number
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const INPUT_NAMES: ReadonlySet<string> = new Set(Object.keys(SOURCE_INPUTS))
|
|
45
|
+
const INPUT_NAMES: ReadonlySet<string> = new Set([...Object.keys(SOURCE_INPUTS), PREVIOUS])
|
|
46
46
|
|
|
47
47
|
/** The tokens of `source`, comments included, in order, each with its class. */
|
|
48
48
|
export function classify(source: string): SLClassifiedToken[] {
|
package/src/lang/glsl.ts
CHANGED
|
@@ -76,6 +76,7 @@ const GLSL_TYPES = new Set(["float", "int", "uint", "bool", "vec2", "vec3", "vec
|
|
|
76
76
|
const RENAME: Record<string, string> = {
|
|
77
77
|
vec2: "float2", vec3: "float3", vec4: "float4", mix: "lerp", fract: "frac",
|
|
78
78
|
texture: "tex2D", texture2D: "tex2D", textureLod: "tex2Dlod", iTime: "time", iGlobalTime: "time", gl_FragCoord: "fragCoord",
|
|
79
|
+
iFrame: "frame", iTimeDelta: "deltaTime",
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
/**
|
|
@@ -94,8 +95,6 @@ const NARROW: Record<string, [string, string]> = {
|
|
|
94
95
|
|
|
95
96
|
/** Shadertoy's inputs with no counterpart yet, and why. */
|
|
96
97
|
const MISSING: Record<string, string> = {
|
|
97
|
-
iFrame: "there is no frame counter yet",
|
|
98
|
-
iTimeDelta: "there is no delta time yet",
|
|
99
98
|
iFrameRate: "there is no frame rate",
|
|
100
99
|
iDate: "there is no date",
|
|
101
100
|
iChannelTime: "a texture has no time of its own",
|
|
@@ -115,6 +114,7 @@ const MISSING: Record<string, string> = {
|
|
|
115
114
|
const HOST_UNIFORMS: Record<string, string> = {
|
|
116
115
|
u_time: "time", u_resolution: "resolution", time: "time", resolution: "resolution",
|
|
117
116
|
iTime: "time", iGlobalTime: "time", iResolution: "resolution", iMouse: "mouse",
|
|
117
|
+
iFrame: "frame", iTimeDelta: "deltaTime",
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
const QUALIFIERS = new Set(["highp", "mediump", "lowp"])
|
package/src/lang/lower.ts
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
*/
|
|
51
51
|
|
|
52
52
|
import {
|
|
53
|
-
controlProblem, DERIVED_INPUTS, INPUTS, RAMP_STOP_COMPUTED, truncateHeld, writtenColour,
|
|
53
|
+
controlProblem, DERIVED_INPUTS, INPUTS, PREVIOUS, RAMP_STOP_COMPUTED, STEP_INPUTS, truncateHeld, writtenColour,
|
|
54
54
|
type Program, type SLKind, type SLType, type UniformControl,
|
|
55
55
|
} from "../ir"
|
|
56
56
|
import { SLOP } from "../ops"
|
|
@@ -246,11 +246,11 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
246
246
|
// Read through, so a program that never names one records nothing
|
|
247
247
|
// for it. The setter only serves a refused assignment, which the
|
|
248
248
|
// checker has already reported and lowering carries on past.
|
|
249
|
-
for (const [name, width] of Object.entries(DERIVED_INPUTS)) {
|
|
249
|
+
for (const [name, width] of Object.entries({ ...DERIVED_INPUTS, ...STEP_INPUTS })) {
|
|
250
250
|
let value: LV | undefined
|
|
251
251
|
global.declare(name, {
|
|
252
252
|
width: width as SLType,
|
|
253
|
-
kind: undefined,
|
|
253
|
+
kind: name === "frame" ? "int" : undefined,
|
|
254
254
|
get value() { return value ??= (inputs as never)[name] },
|
|
255
255
|
set value(v: LV) { value = v },
|
|
256
256
|
})
|
|
@@ -289,6 +289,15 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
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
|
+
}
|
|
292
301
|
|
|
293
302
|
for (const c of unit.consts) {
|
|
294
303
|
const t = typeOf(c.type)
|
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, intUniformProblem,
|
|
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": {
|
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) {
|
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"
|