onejs-sl 0.1.9 → 0.1.11
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 +21 -0
- package/README.md +22 -0
- package/goldens/goldens.json +5 -5
- package/lib/noise2d.hlsl +8 -1
- package/package.json +1 -1
- package/src/core.ts +2 -1
- package/src/ir.ts +128 -8
- package/src/lang/README.md +29 -5
- package/src/lang/ast.ts +17 -0
- package/src/lang/attributes.ts +116 -0
- package/src/lang/check.ts +2 -0
- package/src/lang/classify.ts +5 -1
- package/src/lang/lexer.ts +23 -1
- package/src/lang/lower.ts +21 -9
- package/src/lang/parser.ts +49 -4
- package/src/lib/glsl.ts +1 -1
- package/src/lib/hlsl.ts +1 -1
- package/src/lib/wgsl.ts +1 -1
- package/src/params.ts +15 -0
- package/src/serial.ts +22 -2
- package/src/sl.ts +29 -20
- package/src/tables.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.11
|
|
4
|
+
|
|
5
|
+
The value noise hash no longer rounds differently from one GPU to the next. Seeded value noise looks different, with the same character; program hashes do not change.
|
|
6
|
+
|
|
7
|
+
- `fbm` with 2 or more octaves now looks different
|
|
8
|
+
- Default `fx.noise` fields now look different, since they are seeded
|
|
9
|
+
- `noise`, one octave of `fbm`, simplex, turbulence and ridged are unchanged
|
|
10
|
+
- A helper library change that keeps the IR's shape does not bump `SL_IR_VERSION`
|
|
11
|
+
|
|
12
|
+
## 0.1.10
|
|
13
|
+
|
|
14
|
+
Attributes on uniforms, for a host's controls (`Specs/SL_NEXT.md` 1). A program that has none compiles to exactly what it did, and one that has them hashes as it would without.
|
|
15
|
+
|
|
16
|
+
- `[Range]`, `[Toggle]`, `[Enum]`, `[Header]`, `[Label]`, `[Color]` and `[Hide]` before a uniform
|
|
17
|
+
- `UniformDecl` carries `range`, `toggle`, `options`, `header`, `label` and `hide`, never hashed
|
|
18
|
+
- `sl.uniform.float` and the rest take the same control as a last argument
|
|
19
|
+
- `sl.uniform.colour` takes components as well as a hex string
|
|
20
|
+
- `controlProblem` and `CONTROL_FIELDS` are exported from the core
|
|
21
|
+
- `ATTRIBUTE_NAMES` and `ATTRIBUTE_DOCS` are exported from `onejs-sl/tables`
|
|
22
|
+
- `classify` marks an attribute's name and a string
|
|
23
|
+
|
|
3
24
|
## 0.1.9
|
|
4
25
|
|
|
5
26
|
Two inputs built from the others (`Specs/SL_NEXT.md` 6), and a converter that carries more of a pasted shader over. A program that names neither input compiles to exactly what it did.
|
package/README.md
CHANGED
|
@@ -367,6 +367,28 @@ sets `_Res` from the target and both backends read that.
|
|
|
367
367
|
`fx` had already learned this: `ShaderEffectElement` sets `_Aspect` from the
|
|
368
368
|
render texture with a comment saying why. The lesson did not travel.
|
|
369
369
|
|
|
370
|
+
## Controls
|
|
371
|
+
|
|
372
|
+
A uniform can say how a host should present it: `range` (a slider, with an
|
|
373
|
+
optional `step`), `toggle`, `options` (a dropdown whose value is the index),
|
|
374
|
+
`header`, `label` and `hide`, all on the `UniformDecl`. A `.sl` file writes them
|
|
375
|
+
as Unity's attributes (`[Range(0, 2)] uniform float warp = 1;`), the EDSL as the
|
|
376
|
+
last argument of `sl.uniform.float` and the rest. They are metadata: the hash
|
|
377
|
+
never reads them, so two programs that differ only in a control share one
|
|
378
|
+
compiled shader, and `toJSON` carries them for a host that stores a program.
|
|
379
|
+
|
|
380
|
+
The generated shader's Properties block does not carry them, and that was
|
|
381
|
+
measured rather than assumed (Unity 6000.5, 2026-09-27). Unity binds a material
|
|
382
|
+
property by the HLSL variable's type, not the Properties type: a `float4`
|
|
383
|
+
variable takes only `SetVector`, a `float` only `SetFloat`. Every uniform is a
|
|
384
|
+
`float4` set with `SetVector`, so a `Range(0, 2)` property over one draws a
|
|
385
|
+
slider whose default and every drag never reach the shader. A `Color` property
|
|
386
|
+
is worse, since `SetVector` on one converts sRGB to linear and the program
|
|
387
|
+
already converts a colour uniform. Carrying them means declaring a scalar
|
|
388
|
+
control as `float`, with a matching Properties line (`[ToggleUI]`, since
|
|
389
|
+
`[Toggle]` also defines a keyword; `[Enum]` as name, value pairs), and the bridge
|
|
390
|
+
setting it with `SetFloat`. `Specs/SL_NEXT.md` 1 keeps that as a later change.
|
|
391
|
+
|
|
370
392
|
## Colours
|
|
371
393
|
|
|
372
394
|
A hex colour is sRGB as written, the way CSS reads it, and the target holds
|
package/goldens/goldens.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"generatedBy": "onejs-sl goldens/run.mjs",
|
|
3
|
-
"package": "0.1.
|
|
3
|
+
"package": "0.1.11",
|
|
4
4
|
"ir": 2,
|
|
5
5
|
"target": "rgba8unorm-srgb: straight alpha, 8 bit, sRGB encoded, the stored bytes of a Linear OneJS game's element",
|
|
6
6
|
"size": [
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"hash": "8e7e61f6",
|
|
25
25
|
"source": "// A branch, a colour literal and a function that returns a vector.\nuniform float4 tint = #ff8040;\n\nfloat2 swirl(float2 p, float a) {\n float c = cos(a);\n float s = sin(a);\n return float2(p.x * c - p.y * s, p.x * s + p.y * c);\n}\n\nfloat4 main() {\n float2 q = swirl(uv - 0.5, time * 0.3);\n float v = 0.25;\n if (q.x > 0.1) { v = 0.75; } else { v = fbm(q * 3, 4); }\n return float4(tint.rgb * v, 1);\n}\n",
|
|
26
26
|
"samples": {
|
|
27
|
-
"0": [
|
|
28
|
-
"1.25": [214,
|
|
27
|
+
"0": [204,101,49,255,189,93,45,255,179,88,42,255,181,89,42,255,175,86,41,255,166,81,38,255,150,73,34,255,155,75,35,255,149,72,33,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,189,93,45,255,183,90,43,255,178,87,41,255,184,91,43,255,188,93,44,255,184,91,43,255,170,84,39,255,161,79,37,255,154,75,34,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,180,89,42,255,163,80,37,255,160,78,36,255,185,91,44,255,208,104,50,255,206,102,49,255,177,87,41,255,162,79,37,255,150,72,33,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,177,87,41,255,176,87,41,255,189,93,45,255,192,95,45,255,205,102,49,255,203,101,49,255,184,91,43,255,164,80,38,255,164,80,37,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,193,96,46,255,197,98,47,255,206,102,50,255,206,102,50,255,196,97,47,255,183,90,43,255,168,82,39,255,151,73,34,255,153,74,34,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,212,106,51,255,219,109,53,255,220,110,54,255,211,105,51,255,197,97,47,255,173,85,40,255,147,71,33,255,124,59,26,255,132,63,28,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,222,111,54,255,231,115,57,255,222,111,54,255,212,106,51,255,190,94,45,255,170,83,39,255,147,71,33,255,141,68,31,255,138,67,30,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,211,105,51,255,225,113,55,255,239,120,59,255,219,109,53,255,190,94,45,255,174,85,40,255,158,77,36,255,154,75,34,255,161,78,37,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,216,108,52,255,226,113,55,255,239,120,59,255,220,110,54,255,197,98,47,255,180,89,42,255,164,80,37,255,153,74,34,255,154,75,34,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,210,105,51,255,212,105,51,255,211,105,51,255,210,104,51,255,210,104,51,255,196,97,47,255,167,82,38,255,132,63,28,255,141,68,31,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,192,95,46,255,189,93,45,255,180,88,42,255,180,88,42,255,192,95,46,255,196,97,47,255,176,87,41,255,148,72,33,255,147,71,33,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,181,89,42,255,169,83,39,255,161,79,37,255,168,82,39,255,181,89,42,255,193,96,46,255,188,93,44,255,167,82,38,255,174,86,41,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,181,89,43,255,175,86,41,255,172,84,40,255,168,82,39,255,160,78,36,255,167,82,38,255,188,93,44,255,177,87,41,255,185,91,43,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,183,90,43,255,182,89,43,255,169,83,39,255,166,81,38,255,158,77,36,255,154,75,35,255,175,86,41,255,184,91,43,255,188,93,44,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,164,80,37,255,165,81,38,255,172,85,40,255,158,77,36,255,168,82,38,255,169,83,39,255,173,85,40,255,185,91,43,255,186,92,44,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,173,85,40,255,176,87,41,255,179,88,42,255,186,92,44,255,176,86,41,255,176,86,41,255,174,85,40,255,179,88,42,255,179,88,42,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255],
|
|
28
|
+
"1.25": [214,107,52,255,192,95,46,255,186,92,44,255,168,82,38,255,161,79,37,255,172,84,40,255,189,93,45,255,176,87,41,255,167,81,38,255,147,71,33,255,150,73,34,255,149,72,33,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,213,106,51,255,192,95,46,255,183,90,43,255,176,87,41,255,174,86,41,255,182,90,43,255,203,101,49,255,203,101,49,255,181,89,43,255,157,76,35,255,162,79,37,255,149,72,33,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,213,106,52,255,204,101,49,255,205,102,49,255,202,100,48,255,205,102,49,255,198,98,47,255,206,102,50,255,205,102,49,255,177,87,41,255,162,79,37,255,161,78,37,255,148,72,33,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,216,108,53,255,220,109,54,255,220,110,54,255,220,110,54,255,217,108,53,255,199,98,48,255,196,97,47,255,191,94,45,255,178,87,41,255,159,77,36,255,154,75,35,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,213,106,52,255,220,110,54,255,229,115,56,255,220,110,54,255,211,105,51,255,198,98,47,255,176,86,41,255,161,79,37,255,154,75,35,255,156,76,35,255,166,81,38,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,210,104,51,255,219,109,53,255,235,117,58,255,233,117,57,255,208,103,50,255,184,91,43,255,167,82,38,255,139,67,30,255,123,59,25,255,136,65,29,255,160,78,36,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,213,106,52,255,221,110,54,255,237,119,59,255,223,111,55,255,196,97,47,255,183,90,43,255,160,78,37,255,145,70,32,255,135,65,29,255,132,63,28,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,200,99,48,255,206,102,50,255,211,105,51,255,216,107,52,255,200,99,48,255,178,88,42,255,158,77,36,255,158,77,36,255,156,76,35,255,146,70,32,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,175,86,41,255,176,87,41,255,182,90,43,255,198,98,47,255,205,102,49,255,185,91,43,255,153,74,34,255,150,73,34,255,160,78,36,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,168,82,38,255,160,78,36,255,166,81,38,255,187,92,44,255,196,97,47,255,183,90,43,255,155,75,35,255,128,61,27,255,147,71,32,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,174,85,40,255,171,84,39,255,170,83,39,255,176,86,41,255,192,95,46,255,183,90,43,255,157,76,35,255,146,71,32,255,162,79,37,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,169,83,39,255,165,81,38,255,163,80,37,255,149,72,33,255,180,88,42,255,181,89,43,255,160,78,37,255,183,90,43,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,172,84,40,255,156,76,35,255,165,81,38,255,158,77,36,255,175,86,41,255,183,90,43,255,184,91,43,255,195,96,46,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,187,92,44,255,184,91,43,255,171,84,40,255,170,83,39,255,175,86,41,255,181,89,43,255,188,93,44,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,193,96,46,255,187,93,44,255,175,86,41,255,173,85,40,255,177,87,41,255,184,91,43,255,194,96,46,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,191,94,45,255,185,91,43,255,175,86,41,255,168,82,39,255,168,82,39,255,176,86,41,255,191,94,45,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255]
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"cov-arith.sl": {
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"hash": "4c59d609",
|
|
65
65
|
"source": "// Every noise: value, simplex, fbm, turbulence, ridged, voronoi.\nfloat4 main() {\n float2 p = uv * 5.0 + time * 0.1;\n float a = noise(p) * 0.5 + simplex(p * 1.3) * 0.5;\n float b = fbm(p, 3) * 0.5 + turbulence(p * 0.7, 2) * 0.5;\n float c = ridged(p * 0.6, 4) * 0.6 + voronoi(p * 1.5) * 0.4;\n return float4(a, b, c, 1);\n}\n",
|
|
66
66
|
"samples": {
|
|
67
|
-
"0": [174,
|
|
68
|
-
"1.25": [178,
|
|
67
|
+
"0": [174,158,126,255,210,189,205,255,158,187,175,255,225,193,173,255,147,149,159,255,135,169,158,255,173,156,188,255,178,161,183,255,141,180,181,255,132,192,161,255,199,176,94,255,212,191,150,255,240,219,200,255,231,197,184,255,226,193,172,255,217,203,188,255,161,175,160,255,178,177,187,255,192,159,236,255,180,184,203,255,211,168,201,255,195,193,171,255,186,189,164,255,146,163,176,255,210,170,158,255,191,156,155,255,207,158,174,255,174,174,183,255,202,184,201,255,231,196,212,255,178,204,157,255,220,205,189,255,195,183,156,255,215,155,197,255,195,202,201,255,195,214,130,255,179,191,119,255,189,167,164,255,196,174,118,255,156,166,150,255,162,180,122,255,165,183,113,255,179,183,162,255,212,178,179,255,221,200,173,255,214,186,160,255,180,193,170,255,178,191,189,255,190,209,165,255,219,173,163,255,209,158,181,255,206,181,158,255,156,173,180,255,182,189,189,255,190,180,155,255,188,197,192,255,196,193,169,255,166,184,165,255,180,194,176,255,200,152,185,255,145,199,174,255,178,175,188,255,188,187,181,255,187,158,174,255,168,189,208,255,164,168,179,255,141,189,174,255,213,205,201,255,143,180,157,255,213,210,149,255,162,171,141,255,224,204,192,255,216,199,215,255,208,184,182,255,203,195,192,255,190,192,148,255,182,144,148,255,218,160,208,255,223,180,192,255,204,191,174,255,186,161,222,255,177,182,180,255,139,154,177,255,173,141,131,255,181,176,163,255,223,153,151,255,186,170,167,255,216,191,192,255,181,175,178,255,204,197,137,255,222,186,165,255,203,177,188,255,183,143,196,255,195,175,184,255,193,188,156,255,186,202,161,255,165,125,179,255,188,180,157,255,180,151,196,255,142,126,152,255,180,183,152,255,211,197,199,255,190,175,197,255,201,166,91,255,174,176,185,255,179,192,165,255,210,210,165,255,196,178,121,255,199,183,155,255,167,177,165,255,179,166,180,255,165,198,141,255,122,125,130,255,165,145,152,255,134,131,170,255,131,185,164,255,142,157,198,255,166,160,175,255,215,202,181,255,201,172,148,255,213,204,164,255,195,199,143,255,218,180,135,255,181,202,139,255,160,157,180,255,200,187,216,255,204,157,210,255,182,163,208,255,144,164,130,255,187,153,193,255,176,161,113,255,174,165,175,255,163,190,168,255,181,175,136,255,118,188,183,255,206,180,171,255,172,196,186,255,159,179,193,255,173,186,176,255,193,183,187,255,169,152,174,255,178,134,176,255,121,136,160,255,160,152,198,255,135,186,182,255,94,96,190,255,90,168,135,255,117,181,164,255,175,183,161,255,174,149,160,255,127,142,205,255,179,137,155,255,195,160,161,255,203,162,135,255,153,159,146,255,154,175,175,255,179,156,189,255,192,141,125,255,153,133,166,255,167,174,147,255,181,146,177,255,153,143,176,255,175,178,181,255,138,181,186,255,150,156,168,255,117,129,182,255,148,163,178,255,182,184,129,255,213,201,176,255,207,205,179,255,213,162,162,255,193,190,154,255,123,140,222,255,175,166,213,255,154,168,160,255,105,138,87,255,186,150,153,255,122,125,177,255,143,144,221,255,92,143,173,255,192,164,151,255,192,183,184,255,193,171,169,255,165,151,189,255,216,187,189,255,204,187,172,255,182,178,157,255,197,144,177,255,160,166,179,255,184,191,163,255,180,161,169,255,154,134,153,255,200,163,157,255,120,161,167,255,104,151,182,255,137,193,194,255,189,186,149,255,164,180,161,255,152,148,212,255,215,203,187,255,188,226,135,255,218,207,155,255,178,181,122,255,162,178,162,255,137,145,168,255,145,167,130,255,112,179,165,255,167,160,187,255,175,139,184,255,168,179,150,255,179,148,107,255,177,183,185,255,173,179,132,255,185,162,149,255,202,183,174,255,223,196,184,255,220,202,115,255,244,185,153,255,200,178,198,255,187,186,187,255,179,182,193,255,149,136,175,255,110,181,166,255,170,171,196,255,124,169,161,255,178,146,212,255,187,137,177,255,129,151,194,255,149,192,163,255,193,199,101,255,170,177,165,255,219,207,188,255,218,196,160,255,198,214,174,255,215,199,206,255,167,151,176,255,158,182,184,255,194,198,166,255,140,158,163,255,175,155,126,255,163,173,150,255,172,150,210,255,189,177,194,255,164,154,194,255,162,157,198,255,208,197,202,255,181,182,178,255,184,199,195,255,197,207,178,255,194,194,164,255,199,180,140,255,173,149,171,255,182,180,186,255,199,197,207,255,185,152,162,255,171,136,142,255],
|
|
68
|
+
"1.25": [178,187,193,255,193,186,191,255,196,197,130,255,195,189,168,255,124,186,148,255,162,166,139,255,188,170,164,255,193,146,176,255,124,172,195,255,143,164,171,255,207,147,139,255,236,212,173,255,244,223,197,255,215,189,154,255,246,175,151,255,195,168,108,255,176,160,165,255,183,182,217,255,185,168,209,255,201,168,206,255,187,163,213,255,173,173,191,255,137,160,165,255,167,152,172,255,190,194,136,255,180,181,157,255,212,163,145,255,196,168,194,255,229,196,224,255,215,206,169,255,205,210,180,255,214,215,185,255,183,154,148,255,221,184,196,255,164,211,190,255,189,203,160,255,188,183,177,255,200,189,150,255,191,190,156,255,182,148,206,255,180,165,143,255,177,186,143,255,213,183,157,255,189,213,178,255,217,179,193,255,215,214,191,255,183,187,184,255,209,191,154,255,186,200,176,255,230,177,188,255,201,190,159,255,211,177,122,255,139,146,185,255,209,172,145,255,155,173,123,255,159,204,201,255,171,194,156,255,159,198,136,255,182,153,179,255,188,204,187,255,183,182,143,255,179,181,164,255,164,181,202,255,175,191,168,255,187,189,203,255,168,179,171,255,185,183,160,255,191,197,198,255,159,205,163,255,187,191,174,255,208,139,172,255,216,199,207,255,218,174,207,255,179,178,199,255,220,193,138,255,194,165,151,255,176,158,189,255,217,168,201,255,220,169,154,255,175,195,189,255,178,177,216,255,146,170,187,255,159,159,171,255,181,158,164,255,200,165,169,255,205,190,170,255,194,160,161,255,215,187,198,255,202,152,209,255,217,175,184,255,179,196,203,255,176,192,187,255,176,153,193,255,214,172,193,255,220,198,140,255,200,200,183,255,194,165,182,255,158,181,176,255,190,139,178,255,122,159,142,255,220,198,144,255,192,193,192,255,199,172,189,255,185,190,114,255,181,187,182,255,203,215,131,255,218,188,154,255,228,166,155,255,189,189,165,255,178,147,175,255,139,184,179,255,163,190,120,255,148,161,147,255,184,140,155,255,102,171,164,255,130,164,179,255,186,171,172,255,197,172,201,255,223,203,177,255,183,185,158,255,194,207,179,255,201,178,106,255,207,181,169,255,157,189,140,255,180,196,140,255,204,169,178,255,208,127,200,255,162,187,194,255,137,140,152,255,171,131,180,255,182,165,149,255,164,168,162,255,142,142,156,255,168,164,168,255,179,186,178,255,212,173,184,255,206,194,195,255,164,179,147,255,204,207,154,255,174,199,195,255,148,153,210,255,177,153,164,255,161,139,179,255,135,130,190,255,148,167,187,255,145,132,185,255,105,171,149,255,159,185,172,255,179,169,170,255,150,161,117,255,134,167,192,255,185,139,179,255,166,185,170,255,176,145,161,255,132,139,168,255,189,162,204,255,177,144,153,255,186,146,146,255,125,138,144,255,180,171,166,255,172,151,175,255,121,170,148,255,109,186,174,255,169,176,188,255,129,180,188,255,169,137,184,255,154,116,143,255,202,188,152,255,222,200,128,255,200,174,146,255,206,201,130,255,158,181,191,255,178,117,202,255,174,165,188,255,122,138,127,255,126,169,129,255,186,149,184,255,130,145,192,255,166,149,193,255,134,151,159,255,171,159,164,255,171,177,195,255,137,180,146,255,188,185,189,255,232,193,188,255,179,200,165,255,201,162,179,255,184,126,202,255,138,178,201,255,184,175,192,255,149,163,162,255,175,120,151,255,171,129,166,255,123,128,176,255,120,165,194,255,128,186,160,255,192,185,161,255,184,170,180,255,185,166,214,255,192,193,170,255,208,207,170,255,199,191,123,255,165,184,137,255,166,154,179,255,149,183,142,255,181,167,154,255,126,176,168,255,179,156,173,255,154,162,197,255,142,155,133,255,168,162,179,255,177,190,154,255,205,186,110,255,164,138,153,255,203,181,209,255,222,214,114,255,219,222,151,255,220,195,176,255,185,185,204,255,201,184,199,255,139,136,173,255,134,177,153,255,120,178,175,255,188,157,216,255,139,160,179,255,195,187,165,255,174,125,184,255,128,178,205,255,142,183,155,255,167,188,126,255,207,210,177,255,230,203,132,255,213,199,105,255,226,217,185,255,198,185,194,255,134,182,167,255,178,179,170,255,148,155,167,255,150,155,166,255,134,133,152,255,122,153,158,255,179,121,222,255,194,160,188,255,136,168,202,255,198,192,167,255,213,191,167,255,171,188,172,255,213,194,165,255,188,191,169,255,182,178,180,255,223,185,175,255,154,172,183,255,200,187,170,255,186,184,166,255,185,128,154,255,149,156,136,255]
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
"cov-step.sl": {
|
package/lib/noise2d.hlsl
CHANGED
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
// is the kind of thing nobody notices until two effects that should match do
|
|
10
10
|
// not.
|
|
11
11
|
|
|
12
|
+
// The product is reduced before the seed is added, and must stay that way.
|
|
13
|
+
// p * k reaches the tens of thousands, where one rounding step is a few
|
|
14
|
+
// thousandths, and `p * k + seed * c` is a multiply-add a compiler may fuse
|
|
15
|
+
// into one rounding. Dawn on Windows did and Metal and ANGLE did not, so the
|
|
16
|
+
// same program drew a different fbm on each. frac(frac(x) + frac(y)) is the
|
|
17
|
+
// same number, with nothing large left to fuse, and at seed 0 it is exactly
|
|
18
|
+
// what it was.
|
|
12
19
|
float onejsHash21(float2 p, float seed)
|
|
13
20
|
{
|
|
14
|
-
p = frac(p * float2(123.34, 456.21) + seed * 0.1731);
|
|
21
|
+
p = frac(frac(p * float2(123.34, 456.21)) + frac(seed * 0.1731));
|
|
15
22
|
p += dot(p, p + 45.32);
|
|
16
23
|
return frac(p.x * p.y);
|
|
17
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onejs-sl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "The OneJS shader language: a small typed language for per pixel programs, compiled to a portable IR and emitted as HLSL, WGSL, GLSL ES or a VM buffer",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
package/src/core.ts
CHANGED
|
@@ -11,9 +11,10 @@ export * as sl from "./sl"
|
|
|
11
11
|
export type { Float, Vec2, Vec3, Vec4, Num, ProgramInputs, Texture } from "./sl"
|
|
12
12
|
export {
|
|
13
13
|
TYPE, INPUTS, MAX_TEXTURES, MAX_NODES, SLError, hashProgram, widthName, SL_IR_VERSION, inputsUsed,
|
|
14
|
+
CONTROL_FIELDS, controlProblem,
|
|
14
15
|
} from "./ir"
|
|
15
16
|
export type {
|
|
16
|
-
SLType, InputName, NodeRef, SLNode, Program, UniformDecl, TextureDecl,
|
|
17
|
+
SLType, InputName, NodeRef, SLNode, Program, UniformDecl, UniformControl, TextureDecl,
|
|
17
18
|
} from "./ir"
|
|
18
19
|
export { SLOP, SL_ARITY, SL_NAME, INPUT_ID, isSampling } from "./ops"
|
|
19
20
|
export type { SLOpCode } from "./ops"
|
package/src/ir.ts
CHANGED
|
@@ -73,7 +73,86 @@ export type SLNode =
|
|
|
73
73
|
| { k: "swizzle"; type: SLType; src: NodeRef; chans: number[] }
|
|
74
74
|
| { k: "call"; type: SLType; op: SLOpCode; args: NodeRef[]; imm?: number[] }
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
/**
|
|
77
|
+
* How a host presents a uniform: a slider, a checkbox, a dropdown, a heading
|
|
78
|
+
* above it, the name it shows, or no control at all (`Specs/SL_NEXT.md` 1).
|
|
79
|
+
* Written in a `.sl` file as Unity's attributes, `[Range(0, 2)]` and the rest,
|
|
80
|
+
* and in the EDSL as the last argument of `sl.uniform.float`.
|
|
81
|
+
*
|
|
82
|
+
* Metadata for a host, like `colour`: it changes nothing a program computes, so
|
|
83
|
+
* it is never part of the hash, and two programs that differ only in it share
|
|
84
|
+
* one compiled shader.
|
|
85
|
+
*/
|
|
86
|
+
export interface UniformControl {
|
|
87
|
+
/** A slider from `min` to `max`, moving by `step` when there is one. A float only. */
|
|
88
|
+
range?: { min: number; max: number; step?: number }
|
|
89
|
+
/** A checkbox. The value is 0 or 1. A float only. */
|
|
90
|
+
toggle?: true
|
|
91
|
+
/** A dropdown of these names. The value is the index of the chosen one. A float only. */
|
|
92
|
+
options?: string[]
|
|
93
|
+
/** A heading shown above this uniform's control, starting a group. */
|
|
94
|
+
header?: string
|
|
95
|
+
/** The name the control shows, in place of the uniform's own. */
|
|
96
|
+
label?: string
|
|
97
|
+
/** No control: the host sets this one from code. */
|
|
98
|
+
hide?: true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The fields of `UniformControl`, in the order a host reads them. */
|
|
102
|
+
export const CONTROL_FIELDS = ["range", "toggle", "options", "header", "label", "hide"] as const
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* What is wrong with giving a uniform of `type` and default `value` this
|
|
106
|
+
* control, or null. Names the field at fault, so the text form can mark the
|
|
107
|
+
* attribute that wrote it. The one check, for `.sl` files and the EDSL alike.
|
|
108
|
+
*/
|
|
109
|
+
export function controlProblem(type: SLType, value: readonly number[], c: UniformControl): { field: keyof UniformControl; message: string } | null {
|
|
110
|
+
const kinds = (["range", "toggle", "options"] as const).filter((k) => c[k] !== undefined)
|
|
111
|
+
if (kinds.length > 1) {
|
|
112
|
+
return { field: kinds[1]!, message: `a uniform is one control, so it cannot have both ${ATTRIBUTE_OF[kinds[0]!]} and ${ATTRIBUTE_OF[kinds[1]!]}` }
|
|
113
|
+
}
|
|
114
|
+
const kind = kinds[0]
|
|
115
|
+
if (kind !== undefined && type !== TYPE.FLOAT) {
|
|
116
|
+
return { field: kind, message: `${ATTRIBUTE_OF[kind]} is for a float, and this is a ${widthName(type)}` }
|
|
117
|
+
}
|
|
118
|
+
const v = value[0] ?? 0
|
|
119
|
+
if (c.range !== undefined) {
|
|
120
|
+
const { min, max, step } = c.range
|
|
121
|
+
if (![min, max].every(Number.isFinite) || !(min < max)) {
|
|
122
|
+
return { field: "range", message: `a range runs from a smaller number to a larger one, and this is ${min} to ${max}` }
|
|
123
|
+
}
|
|
124
|
+
if (step !== undefined && !(Number.isFinite(step) && step > 0)) {
|
|
125
|
+
return { field: "range", message: `a range's step is a number above 0, and this is ${step}` }
|
|
126
|
+
}
|
|
127
|
+
if (v < min || v > max) {
|
|
128
|
+
return { field: "range", message: `the default ${v} is outside the range ${min} to ${max}` }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (c.toggle === true && v !== 0 && v !== 1) {
|
|
132
|
+
return { field: "toggle", message: `a toggle is 0 or 1, and the default is ${v}` }
|
|
133
|
+
}
|
|
134
|
+
if (c.options !== undefined) {
|
|
135
|
+
if (c.options.length < 2) return { field: "options", message: "an enum lists at least two options" }
|
|
136
|
+
const empty = c.options.findIndex((o) => o.trim() === "")
|
|
137
|
+
if (empty >= 0) return { field: "options", message: `option ${empty + 1} of the enum has no name` }
|
|
138
|
+
const twice = c.options.findIndex((o, i) => c.options!.indexOf(o) !== i)
|
|
139
|
+
if (twice >= 0) return { field: "options", message: `the enum lists "${c.options[twice]}" twice` }
|
|
140
|
+
if (!Number.isInteger(v) || v < 0 || v >= c.options.length) {
|
|
141
|
+
return { field: "options", message: `an enum's value is the index of an option, 0 to ${c.options.length - 1}, and the default is ${v}` }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const f of ["header", "label"] as const) {
|
|
145
|
+
if (c[f] !== undefined && c[f]!.trim() === "") return { field: f, message: `${ATTRIBUTE_OF[f]} needs some text` }
|
|
146
|
+
}
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** How each field is written in a `.sl` file, for the messages above. */
|
|
151
|
+
const ATTRIBUTE_OF: Record<keyof UniformControl, string> = {
|
|
152
|
+
range: "[Range]", toggle: "[Toggle]", options: "[Enum]", header: "[Header]", label: "[Label]", hide: "[Hide]",
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface UniformDecl extends UniformControl {
|
|
77
156
|
name: string
|
|
78
157
|
type: SLType
|
|
79
158
|
/** Default, used when a caller does not supply the uniform. */
|
|
@@ -182,7 +261,7 @@ export class Builder {
|
|
|
182
261
|
return this.add({ k: "const", type: v.length as SLType, v: v.slice() })
|
|
183
262
|
}
|
|
184
263
|
|
|
185
|
-
uniform(name: string, type: SLType, value: number[], colour = false): NodeRef {
|
|
264
|
+
uniform(name: string, type: SLType, value: number[], colour = false, control: UniformControl = {}): NodeRef {
|
|
186
265
|
const existing = this.uniforms.findIndex((u) => u.name === name)
|
|
187
266
|
if (existing >= 0) {
|
|
188
267
|
const u = this.uniforms[existing]
|
|
@@ -192,10 +271,27 @@ export class Builder {
|
|
|
192
271
|
if ((u.colour === true) !== colour) {
|
|
193
272
|
throw new SLError(`uniform "${name}" is declared both as a colour and as plain numbers`)
|
|
194
273
|
}
|
|
274
|
+
// A second read may say nothing about the control, or the same thing again.
|
|
275
|
+
const merged = { ...controlOf(u) }
|
|
276
|
+
for (const f of CONTROL_FIELDS) {
|
|
277
|
+
if (control[f] === undefined) continue
|
|
278
|
+
if (merged[f] !== undefined && JSON.stringify(merged[f]) !== JSON.stringify(control[f])) {
|
|
279
|
+
throw new SLError(`uniform "${name}" is given two different ${f === "options" ? "enums" : f + "s"}`)
|
|
280
|
+
}
|
|
281
|
+
Object.assign(merged, { [f]: control[f] })
|
|
282
|
+
}
|
|
283
|
+
const problem = controlProblem(type, u.value, merged)
|
|
284
|
+
if (problem !== null) throw new SLError(`uniform "${name}": ${problem.message}`)
|
|
285
|
+
Object.assign(u, merged)
|
|
195
286
|
return this.add({ k: "uniform", type, slot: existing })
|
|
196
287
|
}
|
|
288
|
+
const problem = controlProblem(type, value, control)
|
|
289
|
+
if (problem !== null) throw new SLError(`uniform "${name}": ${problem.message}`)
|
|
197
290
|
const slot = this.uniforms.length
|
|
198
|
-
|
|
291
|
+
const decl: UniformDecl = { name, type, value: value.slice() }
|
|
292
|
+
if (colour) decl.colour = true
|
|
293
|
+
Object.assign(decl, controlOf(control))
|
|
294
|
+
this.uniforms.push(decl)
|
|
199
295
|
return this.add({ k: "uniform", type, slot })
|
|
200
296
|
}
|
|
201
297
|
|
|
@@ -329,22 +425,46 @@ export function hashProgram(nodes: SLNode[], result: NodeRef, uniforms: UniformD
|
|
|
329
425
|
return d
|
|
330
426
|
}
|
|
331
427
|
|
|
332
|
-
// The IR version is in the hash, so a change to what an opcode
|
|
333
|
-
// changes every hash and every cache keyed by one
|
|
334
|
-
//
|
|
428
|
+
// The IR version is in the hash, so a change to what an opcode means in the
|
|
429
|
+
// IR changes every hash and every cache keyed by one recompiles rather than
|
|
430
|
+
// serving the old maths. A change to the helper library alone (a noise's
|
|
431
|
+
// arithmetic, say) is not one: every host includes the library's text when
|
|
432
|
+
// it compiles, and none caches a compiled program by hash across a
|
|
433
|
+
// library update, so the new text reaches every shader without a bump.
|
|
335
434
|
const parts: string[] = [`v${SL_HASH_VERSION}:${SL_IR_VERSION}`, of(result)]
|
|
336
435
|
for (const u of uniforms) parts.push(`U:${u.name}:${u.type}:${u.value.map(fixed).join(",")}`)
|
|
337
436
|
for (const t of textures) parts.push(`T:${t.name}:${t.slot}`)
|
|
338
437
|
return fnv1a(parts.join("|"))
|
|
339
438
|
}
|
|
340
439
|
|
|
440
|
+
/** The control fields of `c` that are set, copied, in `CONTROL_FIELDS` order. */
|
|
441
|
+
export function controlOf(c: UniformControl): UniformControl {
|
|
442
|
+
const out: UniformControl = {}
|
|
443
|
+
if (c.range !== undefined) out.range = c.range.step === undefined ? { min: c.range.min, max: c.range.max } : { ...c.range }
|
|
444
|
+
if (c.toggle === true) out.toggle = true
|
|
445
|
+
if (c.options !== undefined) out.options = c.options.slice()
|
|
446
|
+
if (c.header !== undefined) out.header = c.header
|
|
447
|
+
if (c.label !== undefined) out.label = c.label
|
|
448
|
+
if (c.hide === true) out.hide = true
|
|
449
|
+
return out
|
|
450
|
+
}
|
|
451
|
+
|
|
341
452
|
/** Bumped when the hashing scheme changes, which invalidates generated shaders. */
|
|
342
453
|
export const SL_HASH_VERSION = 1
|
|
343
454
|
|
|
344
455
|
/**
|
|
345
456
|
* Bumped whenever the IR changes in a way a reader has to know about: a new
|
|
346
|
-
* opcode, a new shape, a change to what an existing opcode
|
|
347
|
-
* change to the JSON shape (which also gets a migration in
|
|
457
|
+
* opcode, a new shape, a change to what an existing opcode's operands or
|
|
458
|
+
* result mean, or a change to the JSON shape (which also gets a migration in
|
|
459
|
+
* `fromJSON`).
|
|
460
|
+
*
|
|
461
|
+
* NOT bumped for a change to the helper library (`lib/*.hlsl`) that keeps the
|
|
462
|
+
* IR's shape, such as 0.1.11's value noise hash. Hosts include the library's
|
|
463
|
+
* text when they compile and do not cache its output by hash: OneJS's
|
|
464
|
+
* generated shaders include `SLCommon.cginc`, which recompiles, and Magerie
|
|
465
|
+
* keys its compiled pipelines by source text. A bump would change every hash,
|
|
466
|
+
* which strands every recorded program in a player built before the editor
|
|
467
|
+
* records it again, for no gain.
|
|
348
468
|
*
|
|
349
469
|
* A reader accepts every version up to its own and refuses a newer one with a
|
|
350
470
|
* message naming both, the rule the particle wire and fx follow. Part of the
|
package/src/lang/README.md
CHANGED
|
@@ -58,7 +58,9 @@ It writes two things beside the code:
|
|
|
58
58
|
|
|
59
59
|
- **`<name>.sl.d.ts`**, the way a USS module gets one, carrying the uniform
|
|
60
60
|
names in the type. `uniforms={{ wrap: 1 }}` is then a call site error rather
|
|
61
|
-
than a console warning on a frame nobody is looking at.
|
|
61
|
+
than a console warning on a frame nobody is looking at. Its doc comment lists
|
|
62
|
+
each uniform with its default and the control its attributes ask for, so
|
|
63
|
+
hovering the import says what to set (from onejs-unity after 0.5.19).
|
|
62
64
|
- **`app.sl.json`** beside the bundle, the manifest `SLShaderGenerator` already
|
|
63
65
|
watches for. This is the thing the file format makes possible and the EDSL
|
|
64
66
|
cannot: a program in a file is known statically, so **an ejected game is
|
|
@@ -121,7 +123,8 @@ the generated shader's Properties block shows, and every read of `tint` goes
|
|
|
121
123
|
through `toLinear`. Without that rule, `#ff8040` written as a literal and
|
|
122
124
|
`#ff8040` written as a default would be two different colours in one file.
|
|
123
125
|
A default built out of numbers (`float4(1, 0.5, 0.25, 1)`) is not a colour and
|
|
124
|
-
is read unconverted
|
|
126
|
+
is read unconverted, unless the uniform says `[Color]`, which makes it one
|
|
127
|
+
exactly as a hex default does.
|
|
125
128
|
|
|
126
129
|
**A uniform's default has to be written out.** Numbers, constructors of numbers
|
|
127
130
|
and colours, and nothing else. It is baked into the program before anything
|
|
@@ -166,6 +169,26 @@ browser and correct after an eject. A `.sl` file is held to the real number, 4,
|
|
|
166
169
|
reported at the declaration. The EDSL's ceiling is untouched here because
|
|
167
170
|
lowering it changes recorded behaviour rather than parser behaviour.
|
|
168
171
|
|
|
172
|
+
## Attributes
|
|
173
|
+
|
|
174
|
+
Unity's names, before a uniform, say what its control is: `[Range(0, 2)]`, with
|
|
175
|
+
an optional third number for the step, `[Toggle]`, `[Enum(Soft, Hard, Glow)]`,
|
|
176
|
+
`[Header("Shape")]`, `[Label("Glow colour")]`, `[Color]` and `[Hide]`. They go
|
|
177
|
+
on the uniform's line or the lines above it, in any order. A string exists only
|
|
178
|
+
here, which is the one reason the lexer reads one.
|
|
179
|
+
|
|
180
|
+
Three files share the work. The parser reads the shape (`[Name(args)]`, numbers,
|
|
181
|
+
words and strings); `attributes.ts` says what each one means and refuses what it
|
|
182
|
+
cannot mean, offering Unity's and English spellings as a fix (`Colour` to
|
|
183
|
+
`Color`, `HideInInspector` to `Hide`); `controlProblem` in `ir.ts` refuses what
|
|
184
|
+
does not fit the uniform's type and default, and is the same check the EDSL's
|
|
185
|
+
`sl.uniform.float(name, value, control)` runs. The last one needs the default,
|
|
186
|
+
so its errors come from lowering, marked on the attribute at fault.
|
|
187
|
+
|
|
188
|
+
What they produce is metadata on the uniform (`range`, `toggle`, `options`,
|
|
189
|
+
`header`, `label`, `hide`), which the hash never reads. `[Color]` is the
|
|
190
|
+
exception, because it changes the reads: it is the same as a hex default.
|
|
191
|
+
|
|
169
192
|
## Errors
|
|
170
193
|
|
|
171
194
|
Every one carries file, line, column, offset and length, and `text`, the
|
|
@@ -204,14 +227,15 @@ same place, over every program one token short of a real one.
|
|
|
204
227
|
## For an editor
|
|
205
228
|
|
|
206
229
|
`classify(source)` is every token with its class (keyword, type, builtin,
|
|
207
|
-
input, prelude, number, hex, comment, punct, member,
|
|
230
|
+
input, prelude, number, hex, string, comment, punct, member, attribute, ident,
|
|
231
|
+
or invalid), built
|
|
208
232
|
on the parser's own scanner and word lists, so the highlighting is the parser's.
|
|
209
233
|
It never throws, since it runs on every keystroke. It classifies by spelling,
|
|
210
234
|
not scope: a local named `circle` still reads as a builtin where it is used.
|
|
211
235
|
|
|
212
236
|
`SL_KEYWORDS` and `SL_TYPES` (`words.ts`) are the lists the parser reads, and a
|
|
213
|
-
name may be neither. `onejs-sl/tables` carries them
|
|
214
|
-
of every builtin, input and prelude function; a prelude function's is the
|
|
237
|
+
name may be neither. `onejs-sl/tables` carries them, and `ATTRIBUTE_NAMES`, with
|
|
238
|
+
a one line description of every builtin, input, attribute and prelude function; a prelude function's is the
|
|
215
239
|
comment above it in `prelude-source.ts`.
|
|
216
240
|
|
|
217
241
|
## From GLSL
|
package/src/lang/ast.ts
CHANGED
|
@@ -63,11 +63,28 @@ export interface FuncDecl {
|
|
|
63
63
|
prelude: boolean
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** An argument to an attribute: a number, a quoted string, or a bare word. */
|
|
67
|
+
export type AttributeArg =
|
|
68
|
+
| { k: "num"; value: number; pos: Pos; length: number }
|
|
69
|
+
| { k: "str"; text: string; pos: Pos; length: number }
|
|
70
|
+
| { k: "ident"; name: string; pos: Pos; length: number }
|
|
71
|
+
|
|
72
|
+
/** `[Range(0, 2)]` before a uniform: Unity's spelling of what its control is. */
|
|
73
|
+
export interface Attribute {
|
|
74
|
+
name: string
|
|
75
|
+
args: AttributeArg[]
|
|
76
|
+
/** Where the name starts, and how long it is, for a marker. */
|
|
77
|
+
pos: Pos
|
|
78
|
+
length: number
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
export interface UniformDecl {
|
|
67
82
|
name: string
|
|
68
83
|
type: TypeName
|
|
69
84
|
/** Null when the declaration gave no default; the slot then starts at zero. */
|
|
70
85
|
init: Expr | null
|
|
86
|
+
/** In the order written. What each means is decided by the checker, not the parser. */
|
|
87
|
+
attrs: Attribute[]
|
|
71
88
|
pos: Pos
|
|
72
89
|
}
|
|
73
90
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A uniform's attributes, read into what its control is (`Specs/SL_NEXT.md` 1).
|
|
3
|
+
*
|
|
4
|
+
* Unity's names, so a Unity developer already knows them: `[Range(0, 2)]`,
|
|
5
|
+
* `[Toggle]`, `[Enum(Soft, Hard, Glow)]`, `[Header("Shape")]`,
|
|
6
|
+
* `[Label("Glow colour")]`, `[Color]` and `[Hide]`. The parser reads their
|
|
7
|
+
* shape only; this says what each one means and what is wrong with it, for the
|
|
8
|
+
* checker to report and for lowering to build. What depends on the default
|
|
9
|
+
* (a default outside its range, say) is `controlProblem`'s, which the EDSL
|
|
10
|
+
* shares.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { UniformControl } from "../ir"
|
|
14
|
+
import { TYPE_WIDTH, type Attribute, type AttributeArg, type UniformDecl } from "./ast"
|
|
15
|
+
import type { Pos, SLFix } from "./lexer"
|
|
16
|
+
|
|
17
|
+
export interface AttributeProblem {
|
|
18
|
+
message: string
|
|
19
|
+
pos: Pos
|
|
20
|
+
length: number
|
|
21
|
+
fix?: SLFix
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ReadAttributes {
|
|
25
|
+
/** Every attribute that could be read. One that could not is left out, and is in `problems`. */
|
|
26
|
+
control: UniformControl
|
|
27
|
+
/** `[Color]`: the value is a colour as written, the same as a hex default. */
|
|
28
|
+
colour: boolean
|
|
29
|
+
/** The attribute that wrote each field, to mark when the default does not fit it. */
|
|
30
|
+
from: Partial<Record<keyof UniformControl, Attribute>>
|
|
31
|
+
problems: AttributeProblem[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Every attribute, in the order the docs list them. */
|
|
35
|
+
export const ATTRIBUTE_NAMES = ["Range", "Toggle", "Enum", "Header", "Label", "Color", "Hide"] as const
|
|
36
|
+
export type AttributeName = (typeof ATTRIBUTE_NAMES)[number]
|
|
37
|
+
|
|
38
|
+
/** Spellings from Unity, from other engines, or from English, that mean one of ours. */
|
|
39
|
+
const ALIASES: Record<string, AttributeName> = {
|
|
40
|
+
Colour: "Color", HideInInspector: "Hide", ToggleUI: "Toggle", KeywordEnum: "Enum", IntRange: "Range",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const LIST = "Range, Toggle, Enum, Header, Label, Color and Hide"
|
|
44
|
+
|
|
45
|
+
export function readAttributes(u: UniformDecl): ReadAttributes {
|
|
46
|
+
const out: ReadAttributes = { control: {}, colour: false, from: {}, problems: [] }
|
|
47
|
+
const seen = new Set<string>()
|
|
48
|
+
const problem = (at: Attribute | AttributeArg, message: string, fix?: SLFix) => {
|
|
49
|
+
out.problems.push({ message, pos: at.pos, length: at.length, ...(fix === undefined ? {} : { fix }) })
|
|
50
|
+
}
|
|
51
|
+
const text = (a: AttributeArg): string | null => a.k === "str" ? a.text : a.k === "ident" ? a.name : null
|
|
52
|
+
const none = (a: Attribute) => {
|
|
53
|
+
if (a.args.length === 0) return true
|
|
54
|
+
problem(a.args[0]!, `[${a.name}] takes no arguments`)
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
const one = (a: Attribute, what: string): string | null => {
|
|
58
|
+
const t = a.args.length === 1 ? text(a.args[0]!) : null
|
|
59
|
+
if (t === null) problem(a.args[0] ?? a, `[${a.name}] takes ${what} in quotes, as in [${a.name}("${a.name === "Header" ? "Shape" : "Glow colour"}")]`)
|
|
60
|
+
return t
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const a of u.attrs) {
|
|
64
|
+
const known = (ATTRIBUTE_NAMES as readonly string[]).includes(a.name)
|
|
65
|
+
if (!known) {
|
|
66
|
+
const to = ALIASES[a.name] ?? ATTRIBUTE_NAMES.find((n) => n.toLowerCase() === a.name.toLowerCase())
|
|
67
|
+
const hint = a.name === "IntRange" ? "; a whole number slider is [Range(min, max, 1)]" : ""
|
|
68
|
+
problem(a, `"${a.name}" is not an attribute; the attributes are ${LIST}${to === undefined ? "" : `. Did you mean ${to}?`}${hint}`,
|
|
69
|
+
to === undefined || a.name === "IntRange" ? undefined : { title: `Replace ${a.name} with ${to}`, replacement: to })
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if (seen.has(a.name)) { problem(a, `this uniform already has [${a.name}]`); continue }
|
|
73
|
+
seen.add(a.name)
|
|
74
|
+
switch (a.name) {
|
|
75
|
+
case "Range": {
|
|
76
|
+
const n = a.args.map((x) => x.k === "num" ? x.value : null)
|
|
77
|
+
if ((n.length !== 2 && n.length !== 3) || n.includes(null)) {
|
|
78
|
+
problem(a, "[Range] takes a minimum, a maximum and an optional step, all numbers: [Range(0, 1)] or [Range(1, 16, 1)]")
|
|
79
|
+
break
|
|
80
|
+
}
|
|
81
|
+
out.control.range = n.length === 3 ? { min: n[0]!, max: n[1]!, step: n[2]! } : { min: n[0]!, max: n[1]! }
|
|
82
|
+
out.from.range = a
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
case "Toggle":
|
|
86
|
+
if (none(a)) { out.control.toggle = true; out.from.toggle = a }
|
|
87
|
+
break
|
|
88
|
+
case "Enum": {
|
|
89
|
+
const num = a.args.find((x) => x.k === "num")
|
|
90
|
+
if (num !== undefined) {
|
|
91
|
+
problem(num, "[Enum] lists names only, as in [Enum(Soft, Hard, Glow)]; the value is the chosen name's index, from 0")
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
out.control.options = a.args.map((x) => text(x)!)
|
|
95
|
+
out.from.options = a
|
|
96
|
+
break
|
|
97
|
+
}
|
|
98
|
+
case "Header":
|
|
99
|
+
case "Label": {
|
|
100
|
+
const t = one(a, a.name === "Header" ? "a heading" : "a name")
|
|
101
|
+
const f = a.name === "Header" ? "header" : "label"
|
|
102
|
+
if (t !== null) { out.control[f] = t; out.from[f] = a }
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
case "Color":
|
|
106
|
+
if (!none(a)) break
|
|
107
|
+
if (TYPE_WIDTH[u.type] < 3) problem(a, `[Color] is for a float3 or a float4, and this is a ${u.type}`)
|
|
108
|
+
else out.colour = true
|
|
109
|
+
break
|
|
110
|
+
case "Hide":
|
|
111
|
+
if (none(a)) { out.control.hide = true; out.from.hide = a }
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return out
|
|
116
|
+
}
|
package/src/lang/check.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { SL_GLSL_HINT } from "../ops"
|
|
|
34
34
|
import { BUILTINS, NOT_YET } from "./builtins"
|
|
35
35
|
import { SL_SDF_SHAPES } from "../shapes"
|
|
36
36
|
import type { Expr, FuncDecl, Stmt, Unit } from "./ast"
|
|
37
|
+
import { readAttributes } from "./attributes"
|
|
37
38
|
import { SLParseError, type Pos, type SLFix } from "./lexer"
|
|
38
39
|
|
|
39
40
|
const INPUT_NAMES = new Set(Object.keys(INPUTS))
|
|
@@ -156,6 +157,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
156
157
|
const clash = valueClash(u.name)
|
|
157
158
|
if (clash !== null) fail(`"${u.name}" already names ${clash}`, u.pos, u.name.length)
|
|
158
159
|
})
|
|
160
|
+
for (const p of readAttributes(u).problems) attempt(() => fail(p.message, p.pos, p.length, p.fix))
|
|
159
161
|
if (!uniforms.has(u.name)) uniforms.set(u.name, u)
|
|
160
162
|
}
|
|
161
163
|
attempt(() => {
|
package/src/lang/classify.ts
CHANGED
|
@@ -21,7 +21,9 @@ import { KEYWORD_SET, TYPE_SET } from "./words"
|
|
|
21
21
|
|
|
22
22
|
export type SLTokenClass =
|
|
23
23
|
| "keyword" | "type" | "builtin" | "input" | "prelude"
|
|
24
|
-
| "number" | "hex" | "comment" | "punct"
|
|
24
|
+
| "number" | "hex" | "string" | "comment" | "punct"
|
|
25
|
+
/** An attribute's name, as in `[Range(0, 1)]`. */
|
|
26
|
+
| "attribute"
|
|
25
27
|
/** A component after a dot, as in `c.rgb`. */
|
|
26
28
|
| "member"
|
|
27
29
|
/** Any other name: a uniform, a local, a function the file declares. */
|
|
@@ -54,6 +56,8 @@ export function classify(source: string): SLClassifiedToken[] {
|
|
|
54
56
|
let kind: SLTokenClass
|
|
55
57
|
if (t.kind === "ident") {
|
|
56
58
|
if (before === ".") kind = beforeThat === "sdf" ? "builtin" : "member"
|
|
59
|
+
// There are no arrays, so a name straight after "[" is an attribute's.
|
|
60
|
+
else if (before === "[") kind = "attribute"
|
|
57
61
|
else if (KEYWORD_SET.has(t.text)) kind = "keyword"
|
|
58
62
|
else if (TYPE_SET.has(t.text)) kind = "type"
|
|
59
63
|
else if (INPUT_NAMES.has(t.text)) kind = "input"
|
package/src/lang/lexer.ts
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import { SLError } from "../ir"
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/** `string` only ever appears in an attribute: `[Label("Glow colour")]`. */
|
|
17
|
+
export type TokenKind = "ident" | "number" | "hex" | "string" | "punct" | "eof"
|
|
17
18
|
|
|
18
19
|
export interface Pos {
|
|
19
20
|
/** 1 based, the way every editor counts. */
|
|
@@ -30,6 +31,8 @@ export interface Token extends Pos {
|
|
|
30
31
|
text: string
|
|
31
32
|
/** Numbers only: the parsed value. */
|
|
32
33
|
value?: number
|
|
34
|
+
/** Strings only: the text between the quotes, with `\"` and `\\` read. */
|
|
35
|
+
str?: string
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -206,6 +209,25 @@ export function lex(source: string, file: string, keep: boolean): Token[] | Lexe
|
|
|
206
209
|
continue
|
|
207
210
|
}
|
|
208
211
|
|
|
212
|
+
if (c === "\"") {
|
|
213
|
+
const start = here()
|
|
214
|
+
let j = i + 1
|
|
215
|
+
let str = ""
|
|
216
|
+
while (j < source.length && source[j] !== "\"" && source[j] !== "\n") {
|
|
217
|
+
if (source[j] === "\\" && (source[j + 1] === "\"" || source[j + 1] === "\\")) j++
|
|
218
|
+
str += source[j]
|
|
219
|
+
j++
|
|
220
|
+
}
|
|
221
|
+
if (source[j] !== "\"") {
|
|
222
|
+
fail("this string is never closed; it ends at the end of its line", start, j - i)
|
|
223
|
+
} else {
|
|
224
|
+
push({ kind: "string", text: source.slice(i, j + 1), str, ...start }, j + 1)
|
|
225
|
+
j++
|
|
226
|
+
}
|
|
227
|
+
i = j
|
|
228
|
+
continue
|
|
229
|
+
}
|
|
230
|
+
|
|
209
231
|
if (isIdentStart(c)) {
|
|
210
232
|
const start = here()
|
|
211
233
|
let j = i
|
package/src/lang/lower.ts
CHANGED
|
@@ -33,11 +33,12 @@
|
|
|
33
33
|
* than an optimisation.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
import { DERIVED_INPUTS, INPUTS, type Program, type SLType } from "../ir"
|
|
36
|
+
import { controlProblem, DERIVED_INPUTS, INPUTS, type Program, type SLType, type UniformControl } from "../ir"
|
|
37
37
|
import type { SlSdfKind } from "../shapes"
|
|
38
38
|
import * as sl from "../sl"
|
|
39
39
|
import type { Num, Val } from "../sl"
|
|
40
40
|
import { TYPE_WIDTH, type Expr, type FuncDecl, type Stmt, type TypeName } from "./ast"
|
|
41
|
+
import { readAttributes } from "./attributes"
|
|
41
42
|
import { BUILTINS } from "./builtins"
|
|
42
43
|
import type { Checked } from "./check"
|
|
43
44
|
import { SLParseError, type Pos } from "./lexer"
|
|
@@ -188,9 +189,20 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
188
189
|
// that, `#ff8040` and a uniform defaulting to `#ff8040` would be two
|
|
189
190
|
// different colours in one file. `sl.uniform.colour` also marks
|
|
190
191
|
// the declaration, for a host's colour picker.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
// What the attributes say the control is, and whether the default
|
|
193
|
+
// fits it, marked on the attribute that does not fit.
|
|
194
|
+
const read = readAttributes(u)
|
|
195
|
+
const problem = recover(() => {
|
|
196
|
+
const p = controlProblem(width as SLType, components, read.control)
|
|
197
|
+
const attr = p === null ? undefined : read.from[p.field]
|
|
198
|
+
if (p !== null) fail(inFileWords(p.message), attr?.pos ?? u.pos, attr?.length ?? 1)
|
|
199
|
+
return false
|
|
200
|
+
}, () => true)
|
|
201
|
+
const control = problem ? {} : read.control
|
|
202
|
+
// `[Color]` says the same as a hex default, for a default written as numbers.
|
|
203
|
+
const value = (colour && u.init?.k === "hex") || read.colour
|
|
204
|
+
? at(u.pos, () => sl.uniform.colour(u.name, components, width as 3 | 4, control) as unknown as Val)
|
|
205
|
+
: at(u.pos, () => declareUniform(u.name, u.type, components, control))
|
|
194
206
|
global.declare(u.name, { width, value })
|
|
195
207
|
}
|
|
196
208
|
|
|
@@ -228,12 +240,12 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
228
240
|
|
|
229
241
|
// MARK: declarations
|
|
230
242
|
|
|
231
|
-
function declareUniform(name: string, type: TypeName, c: number[]): Val {
|
|
243
|
+
function declareUniform(name: string, type: TypeName, c: number[], control: UniformControl): Val {
|
|
232
244
|
switch (type) {
|
|
233
|
-
case "float": return sl.uniform.float(name, c[0]
|
|
234
|
-
case "float2": return sl.uniform.vec2(name, [c[0]!, c[1]!]) as unknown as Val
|
|
235
|
-
case "float3": return sl.uniform.vec3(name, [c[0]!, c[1]!, c[2]!]) as unknown as Val
|
|
236
|
-
case "float4": return sl.uniform.vec4(name, [c[0]!, c[1]!, c[2]!, c[3]!]) as unknown as Val
|
|
245
|
+
case "float": return sl.uniform.float(name, c[0]!, control) as unknown as Val
|
|
246
|
+
case "float2": return sl.uniform.vec2(name, [c[0]!, c[1]!], control) as unknown as Val
|
|
247
|
+
case "float3": return sl.uniform.vec3(name, [c[0]!, c[1]!, c[2]!], control) as unknown as Val
|
|
248
|
+
case "float4": return sl.uniform.vec4(name, [c[0]!, c[1]!, c[2]!, c[3]!], control) as unknown as Val
|
|
237
249
|
}
|
|
238
250
|
}
|
|
239
251
|
|
package/src/lang/parser.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
-
type AssignOp, type BinaryOp, type Expr, type FuncDecl, type Param, type Stmt,
|
|
15
|
+
type AssignOp, type Attribute, type AttributeArg, type BinaryOp, type Expr, type FuncDecl, type Param, type Stmt,
|
|
16
16
|
type TextureDecl, type TypeName, type UniformDecl, type Unit,
|
|
17
17
|
} from "./ast"
|
|
18
18
|
import { SLParseError, tokenize, type Pos, type SLFix, type Token } from "./lexer"
|
|
@@ -199,7 +199,18 @@ class Parser {
|
|
|
199
199
|
private parseDeclaration(unit: Unit): void {
|
|
200
200
|
const t = this.peek()
|
|
201
201
|
|
|
202
|
-
if (t.text === "
|
|
202
|
+
if (t.text === "[") {
|
|
203
|
+
const attrs = this.parseAttributes()
|
|
204
|
+
if (!this.at("uniform")) {
|
|
205
|
+
this.fail(
|
|
206
|
+
`an attribute belongs on a uniform, as in \`[Range(0, 1)] uniform float amount = 0.5;\`, ` +
|
|
207
|
+
`and this one is followed by ${describe(this.peek())}`, t,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
unit.uniforms.push(this.parseUniform(attrs))
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
if (t.text === "uniform") { unit.uniforms.push(this.parseUniform([])); return }
|
|
203
214
|
if (t.text === "texture2D") { unit.textures.push(this.parseTexture()); return }
|
|
204
215
|
if (t.text === "const") { unit.consts.push(this.parseConst()); return }
|
|
205
216
|
if (t.kind === "ident") {
|
|
@@ -214,14 +225,45 @@ class Parser {
|
|
|
214
225
|
)
|
|
215
226
|
}
|
|
216
227
|
|
|
217
|
-
private parseUniform(): UniformDecl {
|
|
228
|
+
private parseUniform(attrs: Attribute[]): UniformDecl {
|
|
218
229
|
const kw = this.next()
|
|
219
230
|
const type = this.expectType("a type after uniform")
|
|
220
231
|
const name = this.expectName("a uniform name")
|
|
221
232
|
let init: Expr | null = null
|
|
222
233
|
if (this.eat("=")) init = this.parseExpr()
|
|
223
234
|
this.expect(";", "after a uniform declaration")
|
|
224
|
-
return { name: name.text, type, init, pos: kw }
|
|
235
|
+
return { name: name.text, type, init, attrs, pos: kw }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** `[Name]` or `[Name(arg, ...)]`, one or more. Shape only: the checker says what each means. */
|
|
239
|
+
private parseAttributes(): Attribute[] {
|
|
240
|
+
const out: Attribute[] = []
|
|
241
|
+
while (this.eat("[")) {
|
|
242
|
+
const name = this.expectIdent("an attribute's name, such as Range")
|
|
243
|
+
const args: AttributeArg[] = []
|
|
244
|
+
if (this.eat("(")) {
|
|
245
|
+
if (!this.at(")")) {
|
|
246
|
+
do args.push(this.parseAttributeArg())
|
|
247
|
+
while (this.eat(","))
|
|
248
|
+
}
|
|
249
|
+
this.expect(")", "to close the attribute's arguments")
|
|
250
|
+
}
|
|
251
|
+
this.expect("]", "to close the attribute")
|
|
252
|
+
out.push({ name: name.text, args, pos: name, length: name.text.length })
|
|
253
|
+
}
|
|
254
|
+
return out
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private parseAttributeArg(): AttributeArg {
|
|
258
|
+
const t = this.next()
|
|
259
|
+
if (t.kind === "string") return { k: "str", text: t.str!, pos: t, length: t.text.length }
|
|
260
|
+
if (t.kind === "ident") return { k: "ident", name: t.text, pos: t, length: t.text.length }
|
|
261
|
+
if (t.kind === "number") return { k: "num", value: t.value!, pos: t, length: t.text.length }
|
|
262
|
+
if (t.text === "-" && this.peek().kind === "number") {
|
|
263
|
+
const n = this.next()
|
|
264
|
+
return { k: "num", value: -n.value!, pos: t, length: n.offset + n.text.length - t.offset }
|
|
265
|
+
}
|
|
266
|
+
this.fail(`expected a number, a word or a "string" as an attribute's argument, got ${describe(t)}`, t)
|
|
225
267
|
}
|
|
226
268
|
|
|
227
269
|
private parseTexture(): TextureDecl {
|
|
@@ -512,6 +554,9 @@ class Parser {
|
|
|
512
554
|
if (t.kind === "number") return { k: "num", value: t.value!, pos: t }
|
|
513
555
|
if (t.kind === "hex") return { k: "hex", hex: t.text, pos: t }
|
|
514
556
|
if (t.kind === "ident") return { k: "ident", name: t.text, pos: t }
|
|
557
|
+
if (t.kind === "string") {
|
|
558
|
+
this.fail(`a string is only an attribute's argument, as in [Label("Glow colour")]; a value is a number`, t)
|
|
559
|
+
}
|
|
515
560
|
if (t.text === "(") {
|
|
516
561
|
const inner = this.parseExpr()
|
|
517
562
|
this.expect(")", "to close a group")
|
package/src/lib/glsl.ts
CHANGED
|
@@ -548,7 +548,7 @@ float sdCircleWave(vec2 p, float tb, float ra) {
|
|
|
548
548
|
`,
|
|
549
549
|
`
|
|
550
550
|
float onejsHash21(vec2 p, float seed) {
|
|
551
|
-
p = fract(p * vec2(123.34, 456.21) + seed * 0.1731);
|
|
551
|
+
p = fract(fract(p * vec2(123.34, 456.21)) + fract(seed * 0.1731));
|
|
552
552
|
p += dot(p, p + 45.32);
|
|
553
553
|
return fract(p.x * p.y);
|
|
554
554
|
}
|
package/src/lib/hlsl.ts
CHANGED
|
@@ -548,7 +548,7 @@ float sdCircleWave(float2 p, float tb, float ra) {
|
|
|
548
548
|
`,
|
|
549
549
|
`
|
|
550
550
|
float onejsHash21(float2 p, float seed) {
|
|
551
|
-
p = frac(p * float2(123.34, 456.21) + seed * 0.1731);
|
|
551
|
+
p = frac(frac(p * float2(123.34, 456.21)) + frac(seed * 0.1731));
|
|
552
552
|
p += dot(p, p + 45.32);
|
|
553
553
|
return frac(p.x * p.y);
|
|
554
554
|
}
|
package/src/lib/wgsl.ts
CHANGED
|
@@ -589,7 +589,7 @@ fn sdCircleWave(pIn: vec2f, tbIn: f32, ra: f32) -> f32 {
|
|
|
589
589
|
`
|
|
590
590
|
fn onejsHash21(pIn: vec2f, seed: f32) -> f32 {
|
|
591
591
|
var p: vec2f = pIn;
|
|
592
|
-
p = fract(p * vec2f(123.34, 456.21) + seed * 0.1731);
|
|
592
|
+
p = fract(fract(p * vec2f(123.34, 456.21)) + fract(seed * 0.1731));
|
|
593
593
|
p += dot(p, p + 45.32);
|
|
594
594
|
return fract(p.x * p.y);
|
|
595
595
|
}
|
package/src/params.ts
CHANGED
|
@@ -21,6 +21,7 @@ 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
23
|
import type { SourceInputName } from "./ir"
|
|
24
|
+
import type { AttributeName } from "./lang/attributes"
|
|
24
25
|
|
|
25
26
|
/** One function of the helper library, as its source declares it. */
|
|
26
27
|
export interface LibSignature {
|
|
@@ -164,6 +165,20 @@ export const BUILTIN_DOCS: Readonly<Record<string, string>> = {
|
|
|
164
165
|
ramp: "t mapped through evenly spaced colour stops, from the first at 0 to the last at 1.",
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* One line on each attribute, for the same tooltips: what the control is, and
|
|
170
|
+
* how it is written.
|
|
171
|
+
*/
|
|
172
|
+
export const ATTRIBUTE_DOCS: Readonly<Record<AttributeName, string>> = {
|
|
173
|
+
Range: "A slider from the first number to the second: [Range(0, 2)]. A third number is its step. A float only.",
|
|
174
|
+
Toggle: "A checkbox. The value is 0 or 1. A float only.",
|
|
175
|
+
Enum: "A dropdown of names: [Enum(Soft, Hard, Glow)]. The value is the chosen name's index, from 0. A float only.",
|
|
176
|
+
Header: "A heading above this control, starting a group: [Header(\"Shape\")].",
|
|
177
|
+
Label: "The name the control shows, in place of the uniform's: [Label(\"Glow colour\")].",
|
|
178
|
+
Color: "A colour picker, the value a colour as written, the same as a hex default. A float3 or a float4.",
|
|
179
|
+
Hide: "No control: the host sets this uniform from code.",
|
|
180
|
+
}
|
|
181
|
+
|
|
167
182
|
/**
|
|
168
183
|
* One line on each input, for the same tooltips. In the words of no one host:
|
|
169
184
|
* the output is a UI element in OneJS and an image on a Magerie board, and the
|
package/src/serial.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* older one is migrated to this one and rehashed under it.
|
|
10
10
|
*/
|
|
11
11
|
import {
|
|
12
|
-
INPUTS, SLError, SL_IR_VERSION, TYPE, hashProgram,
|
|
12
|
+
INPUTS, SLError, SL_IR_VERSION, TYPE, controlOf, controlProblem, hashProgram,
|
|
13
13
|
type NodeRef, type Program, type SLNode, type SLType, type TextureDecl, type UniformDecl,
|
|
14
14
|
} from "./ir"
|
|
15
15
|
import { SL_ARITY, SL_NAME } from "./ops"
|
|
@@ -125,7 +125,27 @@ function uniform(u: unknown, i: number): UniformDecl {
|
|
|
125
125
|
}
|
|
126
126
|
const out: UniformDecl = { name: x.name!, type: x.type as SLType, value: x.value!.slice() }
|
|
127
127
|
if (x.colour === true) out.colour = true
|
|
128
|
-
|
|
128
|
+
// Its control: shapes checked here, sense checked by the one check the
|
|
129
|
+
// compiler uses, so a hand edited file cannot give a host a range it would
|
|
130
|
+
// choke on.
|
|
131
|
+
const r = x.range as { min?: unknown; max?: unknown; step?: unknown } | undefined
|
|
132
|
+
if (r !== undefined && (typeof r !== "object" || r === null || typeof r.min !== "number" || typeof r.max !== "number" ||
|
|
133
|
+
(r.step !== undefined && typeof r.step !== "number"))) {
|
|
134
|
+
fail(`uniform ${i}'s range needs a min and a max, and a step if any, as numbers`)
|
|
135
|
+
}
|
|
136
|
+
if (x.options !== undefined && (!Array.isArray(x.options) || !x.options.every((o) => typeof o === "string"))) {
|
|
137
|
+
fail(`uniform ${i}'s options must be a list of names`)
|
|
138
|
+
}
|
|
139
|
+
for (const f of ["header", "label"] as const) {
|
|
140
|
+
if (x[f] !== undefined && typeof x[f] !== "string") fail(`uniform ${i}'s ${f} must be text`)
|
|
141
|
+
}
|
|
142
|
+
for (const f of ["toggle", "hide"] as const) {
|
|
143
|
+
if (x[f] !== undefined && x[f] !== true) fail(`uniform ${i}'s ${f} can only be true`)
|
|
144
|
+
}
|
|
145
|
+
const control = controlOf(x)
|
|
146
|
+
const problem = controlProblem(out.type, out.value, control)
|
|
147
|
+
if (problem !== null) fail(`uniform ${i}: ${problem.message}`)
|
|
148
|
+
return Object.assign(out, control)
|
|
129
149
|
}
|
|
130
150
|
|
|
131
151
|
function texture(t: unknown, i: number): TextureDecl {
|
package/src/sl.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { parseColor as parseHex } from "./color"
|
|
26
26
|
import {
|
|
27
27
|
Builder, INPUTS, SLError, SL_IR_VERSION, TYPE, hashProgram, widthName,
|
|
28
|
-
type InputName, type NodeRef, type Program, type SLNode, type SLType,
|
|
28
|
+
type InputName, type NodeRef, type Program, type SLNode, type SLType, type UniformControl,
|
|
29
29
|
} from "./ir"
|
|
30
30
|
import { SLOP, type SLOpCode } from "./ops"
|
|
31
31
|
import { SL_SDF_PARAMS, SL_SDF_SHAPES, type SlSdfKind } from "./shapes"
|
|
@@ -665,40 +665,49 @@ export function voronoi(p: Vec2): Float {
|
|
|
665
665
|
return mk(p.owner, p.owner.call(SLOP.VORONOI, TYPE.FLOAT, [p.ref]), TYPE.FLOAT)
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
+
/**
|
|
669
|
+
* A uniform: a value the host sets by name, with the default the program
|
|
670
|
+
* starts from. The last argument says how a host presents it, a slider, a
|
|
671
|
+
* checkbox, a dropdown, a heading or a label, and is what `[Range(0, 2)]` and
|
|
672
|
+
* the other attributes write in a `.sl` file. It never changes the hash.
|
|
673
|
+
*
|
|
674
|
+
* sl.uniform.float("warp", 1, { range: { min: 0, max: 2 } })
|
|
675
|
+
*/
|
|
668
676
|
export const uniform = {
|
|
669
|
-
float(name: string, value = 0): Float {
|
|
677
|
+
float(name: string, value = 0, control: UniformControl = {}): Float {
|
|
670
678
|
const b = ctx()
|
|
671
|
-
return mk(b, b.uniform(name, TYPE.FLOAT, [value]), TYPE.FLOAT)
|
|
679
|
+
return mk(b, b.uniform(name, TYPE.FLOAT, [value], false, control), TYPE.FLOAT)
|
|
672
680
|
},
|
|
673
|
-
vec2(name: string, value: [number, number] = [0, 0]): Vec2 {
|
|
681
|
+
vec2(name: string, value: [number, number] = [0, 0], control: UniformControl = {}): Vec2 {
|
|
674
682
|
const b = ctx()
|
|
675
|
-
return mk(b, b.uniform(name, TYPE.VEC2, value), TYPE.VEC2)
|
|
683
|
+
return mk(b, b.uniform(name, TYPE.VEC2, value, false, control), TYPE.VEC2)
|
|
676
684
|
},
|
|
677
|
-
vec3(name: string, value: [number, number, number] = [0, 0, 0]): Vec3 {
|
|
685
|
+
vec3(name: string, value: [number, number, number] = [0, 0, 0], control: UniformControl = {}): Vec3 {
|
|
678
686
|
const b = ctx()
|
|
679
|
-
return mk(b, b.uniform(name, TYPE.VEC3, value), TYPE.VEC3)
|
|
687
|
+
return mk(b, b.uniform(name, TYPE.VEC3, value, false, control), TYPE.VEC3)
|
|
680
688
|
},
|
|
681
|
-
vec4(name: string, value: [number, number, number, number] = [0, 0, 0, 1]): Vec4 {
|
|
689
|
+
vec4(name: string, value: [number, number, number, number] = [0, 0, 0, 1], control: UniformControl = {}): Vec4 {
|
|
682
690
|
const b = ctx()
|
|
683
|
-
return mk(b, b.uniform(name, TYPE.VEC4, value), TYPE.VEC4)
|
|
691
|
+
return mk(b, b.uniform(name, TYPE.VEC4, value, false, control), TYPE.VEC4)
|
|
684
692
|
},
|
|
685
693
|
/**
|
|
686
|
-
* A colour, defaulting to `hex` as written (sRGB, the way CSS reads it)
|
|
687
|
-
* Returns it converted to linear, as a
|
|
688
|
-
* declaration `colour` so a host can offer a
|
|
689
|
-
* components when `width` is 3, dropping the
|
|
694
|
+
* A colour, defaulting to `hex` as written (sRGB, the way CSS reads it), or
|
|
695
|
+
* to components written the same way. Returns it converted to linear, as a
|
|
696
|
+
* hex literal is, and marks the declaration `colour` so a host can offer a
|
|
697
|
+
* colour picker for it. Three components when `width` is 3, dropping the
|
|
698
|
+
* alpha.
|
|
690
699
|
*/
|
|
691
|
-
colour: ((name: string, hex: string, width: 3 | 4 = 4): Vec3 | Vec4 => {
|
|
700
|
+
colour: ((name: string, hex: string | readonly number[], width: 3 | 4 = 4, control: UniformControl = {}): Vec3 | Vec4 => {
|
|
692
701
|
const b = ctx()
|
|
693
|
-
const c = parseHex(hex)
|
|
702
|
+
const c = typeof hex === "string" ? parseHex(hex) : [hex[0] ?? 0, hex[1] ?? 0, hex[2] ?? 0, hex[3] ?? 1]
|
|
694
703
|
const type = width === 3 ? TYPE.VEC3 : TYPE.VEC4
|
|
695
|
-
const raw = mk(b, b.uniform(name, type, c.slice(0, width), true), type)
|
|
704
|
+
const raw = mk(b, b.uniform(name, type, c.slice(0, width), true, control), type)
|
|
696
705
|
return toLinear(raw) as Vec3 | Vec4
|
|
697
706
|
}) as {
|
|
698
|
-
(name: string, hex: string): Vec4
|
|
699
|
-
(name: string, hex: string, width: 3): Vec3
|
|
700
|
-
(name: string, hex: string, width: 4): Vec4
|
|
701
|
-
(name: string, hex: string, width: 3 | 4): Vec3 | Vec4
|
|
707
|
+
(name: string, hex: string | readonly number[]): Vec4
|
|
708
|
+
(name: string, hex: string | readonly number[], width: 3, control?: UniformControl): Vec3
|
|
709
|
+
(name: string, hex: string | readonly number[], width: 4, control?: UniformControl): Vec4
|
|
710
|
+
(name: string, hex: string | readonly number[], width: 3 | 4, control?: UniformControl): Vec3 | Vec4
|
|
702
711
|
},
|
|
703
712
|
}
|
|
704
713
|
|
package/src/tables.ts
CHANGED
|
@@ -16,7 +16,9 @@ export type { SLSurface } from "./ops"
|
|
|
16
16
|
export { DERIVED_INPUTS, INPUTS, SOURCE_INPUTS } from "./ir"
|
|
17
17
|
export type { DerivedInputName, InputName, SourceInputName } from "./ir"
|
|
18
18
|
export { SL_SDF_SHAPES, SL_SDF_PARAMS } from "./shapes"
|
|
19
|
-
export { BUILTIN_DOCS, BUILTIN_PARAMS, INPUT_DOCS, LIB_SIGNATURES, SL_SDF_PARAM_NAMES } from "./params"
|
|
19
|
+
export { ATTRIBUTE_DOCS, BUILTIN_DOCS, BUILTIN_PARAMS, INPUT_DOCS, LIB_SIGNATURES, SL_SDF_PARAM_NAMES } from "./params"
|
|
20
|
+
export { ATTRIBUTE_NAMES } from "./lang/attributes"
|
|
21
|
+
export type { AttributeName } from "./lang/attributes"
|
|
20
22
|
export { PRELUDE_DOCS, PRELUDE_NAMES } from "./lang/prelude-source"
|
|
21
23
|
export { SL_KEYWORDS, SL_TYPES } from "./lang/words"
|
|
22
24
|
export { TYPE_WIDTH } from "./lang/ast"
|