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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "generatedBy": "onejs-sl goldens/run.mjs",
3
- "package": "0.1.9",
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": [192,95,46,255,199,98,48,255,196,97,47,255,198,98,47,255,193,95,46,255,187,92,44,255,180,89,42,255,187,92,44,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,188,93,44,255,195,97,46,255,199,98,48,255,205,102,49,255,203,101,49,255,202,100,49,255,196,97,47,255,200,99,48,255,189,93,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,188,93,44,255,202,100,48,255,217,108,53,255,209,104,50,255,206,102,49,255,198,98,47,255,191,94,45,255,198,98,47,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,195,97,46,255,205,102,49,255,216,107,52,255,206,102,50,255,204,101,49,255,197,98,47,255,194,96,46,255,190,94,45,255,187,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,206,102,49,255,207,103,50,255,212,106,51,255,210,105,51,255,198,98,47,255,186,92,44,255,174,85,40,255,161,78,37,255,156,76,35,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,215,107,52,255,217,108,53,255,224,112,55,255,214,107,52,255,202,100,48,255,180,89,42,255,153,74,34,255,131,62,28,255,125,59,26,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,221,110,54,255,228,114,56,255,237,119,59,255,214,107,52,255,189,94,45,255,171,84,40,255,147,71,33,255,140,68,31,255,138,66,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,218,108,53,255,233,117,58,255,240,120,59,255,230,115,57,255,199,99,48,255,175,86,41,255,158,77,36,255,153,74,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,220,110,54,255,230,115,57,255,237,119,59,255,227,113,56,255,203,101,49,255,181,89,42,255,164,80,37,255,153,74,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,210,104,51,255,208,103,50,255,210,104,51,255,209,104,50,255,210,105,51,255,197,98,47,255,168,82,38,255,132,63,28,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,194,96,46,255,188,93,44,255,181,89,42,255,182,89,43,255,196,97,47,255,200,99,48,255,179,88,42,255,151,73,34,255,151,73,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,175,86,41,255,167,82,38,255,176,87,41,255,183,90,43,255,188,93,44,255,184,91,43,255,187,92,44,255,189,93,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,175,86,41,255,169,83,39,255,167,82,38,255,165,80,38,255,157,77,36,255,165,81,38,255,192,95,46,255,209,104,50,255,214,107,52,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,85,40,255,165,80,38,255,156,76,35,255,152,74,34,255,156,76,35,255,166,81,38,255,188,93,44,255,215,107,52,255,221,110,54,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,162,79,37,255,172,84,40,255,178,88,42,255,167,82,38,255,181,89,42,255,185,91,43,255,193,95,46,255,214,107,52,255,206,103,50,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,176,87,41,255,189,94,45,255,191,94,45,255,187,92,44,255,186,92,44,255,198,98,47,255,190,94,45,255,181,89,43,255,177,87,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],
28
- "1.25": [214,106,52,255,197,98,47,255,190,94,45,255,196,97,47,255,209,104,50,255,206,102,49,255,205,102,49,255,200,99,48,255,192,95,46,255,180,88,42,255,180,89,42,255,175,86,41,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,218,109,53,255,208,104,50,255,203,100,49,255,205,102,49,255,214,107,52,255,211,105,51,255,205,102,49,255,201,100,48,255,197,98,47,255,193,96,46,255,197,98,47,255,175,86,41,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,220,110,54,255,212,105,51,255,208,103,50,255,210,105,51,255,216,107,52,255,206,102,50,255,203,101,49,255,198,98,47,255,191,94,45,255,198,98,47,255,197,97,47,255,168,82,39,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,219,109,53,255,221,110,54,255,217,108,53,255,222,111,54,255,219,109,53,255,202,100,48,255,195,97,46,255,191,94,45,255,193,96,46,255,192,95,46,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,215,107,52,255,221,110,54,255,229,114,56,255,235,118,58,255,214,106,52,255,202,100,48,255,184,91,43,255,167,82,38,255,163,80,37,255,171,84,40,255,176,87,41,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,225,112,55,255,213,106,51,255,231,116,57,255,238,119,59,255,239,120,59,255,210,104,51,255,185,91,43,255,170,83,39,255,143,69,31,255,130,62,28,255,129,62,27,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,212,106,51,255,218,109,53,255,236,118,58,255,231,115,57,255,211,105,51,255,184,91,43,255,161,78,37,255,144,70,32,255,135,65,29,255,125,60,26,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,199,99,48,255,204,101,49,255,210,104,51,255,215,107,52,255,203,101,49,255,179,88,42,255,158,77,36,255,158,77,36,255,156,76,35,255,146,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,178,87,41,255,177,87,41,255,183,90,43,255,199,99,48,255,207,103,50,255,186,92,44,255,153,74,34,255,150,73,34,255,163,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,171,84,40,255,166,81,38,255,173,85,40,255,190,94,45,255,200,99,48,255,185,91,44,255,156,76,35,255,130,62,28,255,173,85,40,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,157,77,36,255,163,80,37,255,172,84,40,255,171,84,40,255,186,92,44,255,181,89,42,255,162,79,37,255,150,73,33,255,172,84,40,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,165,81,38,255,153,74,34,255,150,73,34,255,157,77,36,255,178,88,42,255,193,96,46,255,192,95,46,255,188,93,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,181,89,43,255,167,82,38,255,174,86,41,255,170,83,39,255,186,92,44,255,212,105,51,255,217,108,53,255,206,102,50,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,95,46,255,184,91,43,255,191,94,45,255,188,93,44,255,199,99,48,255,215,107,52,255,219,109,53,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,190,94,45,255,191,94,45,255,198,98,47,255,191,94,45,255,189,93,45,255,203,101,49,255,200,99,48,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,190,94,45,255,197,98,47,255,190,94,45,255,170,83,39,255,166,81,38,255,169,83,39,255,190,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]
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,149,126,255,210,178,205,255,158,184,175,255,225,193,173,255,147,162,159,255,135,170,158,255,173,162,188,255,178,159,183,255,141,175,181,255,132,185,161,255,199,181,94,255,212,195,150,255,240,203,200,255,231,201,184,255,226,197,172,255,217,211,188,255,161,154,160,255,178,178,187,255,192,158,236,255,180,189,203,255,211,180,201,255,195,196,171,255,186,187,164,255,146,145,176,255,210,164,158,255,191,162,155,255,207,150,174,255,174,166,183,255,202,176,201,255,231,192,212,255,178,206,157,255,220,206,189,255,195,178,156,255,215,160,197,255,195,200,201,255,195,205,130,255,179,191,119,255,189,159,164,255,196,168,118,255,156,157,150,255,162,182,122,255,165,196,113,255,179,184,162,255,212,172,179,255,221,187,173,255,214,184,160,255,180,189,170,255,178,190,189,255,190,202,165,255,219,183,163,255,209,146,181,255,206,163,158,255,156,156,180,255,182,185,189,255,190,170,155,255,188,195,192,255,196,192,169,255,166,198,165,255,180,196,176,255,200,150,185,255,145,185,174,255,178,166,188,255,188,185,181,255,187,146,174,255,168,179,208,255,164,148,179,255,141,183,174,255,213,205,201,255,143,173,157,255,213,210,149,255,162,188,141,255,224,218,192,255,216,208,215,255,208,184,182,255,203,174,192,255,190,178,148,255,182,166,148,255,218,161,208,255,223,180,192,255,204,182,174,255,186,158,222,255,177,180,180,255,139,156,177,255,173,145,131,255,181,187,163,255,223,170,151,255,186,178,167,255,216,196,192,255,181,169,178,255,204,189,137,255,222,168,165,255,203,168,188,255,183,144,196,255,195,182,184,255,193,201,156,255,186,202,161,255,165,130,179,255,188,190,157,255,180,153,196,255,142,124,152,255,180,195,152,255,211,201,199,255,190,174,197,255,201,159,91,255,174,164,185,255,179,186,165,255,210,197,165,255,196,171,121,255,199,177,155,255,167,189,165,255,179,184,180,255,165,196,141,255,122,146,130,255,165,163,152,255,134,153,170,255,131,192,164,255,142,156,198,255,166,160,175,255,215,193,181,255,201,169,148,255,213,205,164,255,195,208,143,255,218,176,135,255,181,194,139,255,160,155,180,255,200,200,216,255,204,174,210,255,182,160,208,255,144,179,130,255,187,162,193,255,176,168,113,255,174,162,175,255,163,181,168,255,181,169,136,255,118,179,183,255,206,174,171,255,172,202,186,255,159,191,193,255,173,188,176,255,193,173,187,255,169,144,174,255,178,143,176,255,121,164,160,255,160,164,198,255,135,183,182,255,94,108,190,255,90,157,135,255,117,167,164,255,175,189,161,255,174,156,160,255,127,136,205,255,179,154,155,255,195,171,161,255,203,165,135,255,153,160,146,255,154,178,175,255,179,143,189,255,192,140,125,255,153,149,166,255,167,179,147,255,181,160,177,255,153,144,176,255,175,181,181,255,138,180,186,255,150,149,168,255,117,122,182,255,148,158,178,255,182,182,129,255,213,201,176,255,207,212,179,255,213,174,162,255,193,195,154,255,123,131,222,255,175,156,213,255,154,169,160,255,105,139,87,255,186,165,153,255,122,144,177,255,143,151,221,255,92,143,173,255,192,150,151,255,192,173,184,255,193,159,169,255,165,149,189,255,216,187,189,255,204,198,172,255,182,176,157,255,197,145,177,255,160,161,179,255,184,185,163,255,180,149,169,255,154,133,153,255,200,176,157,255,120,158,167,255,104,154,182,255,137,188,194,255,189,184,149,255,164,183,161,255,152,145,212,255,215,208,187,255,188,225,135,255,218,201,155,255,178,176,122,255,162,167,162,255,137,138,168,255,145,171,130,255,112,180,165,255,167,159,187,255,175,146,184,255,168,178,150,255,179,143,107,255,177,180,185,255,173,179,132,255,185,157,149,255,202,183,174,255,223,203,184,255,220,204,115,255,244,178,153,255,200,166,198,255,187,180,187,255,179,179,193,255,149,129,175,255,110,186,166,255,170,165,196,255,124,162,161,255,178,136,212,255,187,139,177,255,129,154,194,255,149,196,163,255,193,198,101,255,170,183,165,255,219,213,188,255,218,198,160,255,198,211,174,255,215,198,206,255,167,147,176,255,158,180,184,255,194,192,166,255,140,156,163,255,175,138,126,255,163,173,150,255,172,150,210,255,189,175,194,255,164,153,194,255,162,151,198,255,208,194,202,255,181,183,178,255,184,188,195,255,197,196,178,255,194,189,164,255,199,183,140,255,173,143,171,255,182,178,186,255,199,185,207,255,185,129,162,255,171,139,142,255],
68
- "1.25": [178,180,193,255,193,178,191,255,196,197,130,255,195,190,168,255,124,192,148,255,162,181,139,255,188,186,164,255,193,159,176,255,124,176,195,255,143,165,171,255,207,156,139,255,236,207,173,255,244,209,197,255,215,191,154,255,246,183,151,255,195,185,108,255,176,147,165,255,183,176,217,255,185,175,209,255,201,169,206,255,187,176,213,255,173,171,191,255,137,152,165,255,167,131,172,255,190,192,136,255,180,181,157,255,212,159,145,255,196,162,194,255,229,185,224,255,215,209,169,255,205,216,180,255,214,216,185,255,183,151,148,255,221,186,196,255,164,213,190,255,189,200,160,255,188,190,177,255,200,188,150,255,191,180,156,255,182,143,206,255,180,174,143,255,177,190,143,255,213,177,157,255,189,202,178,255,217,169,193,255,215,215,191,255,183,188,184,255,209,196,154,255,186,202,176,255,230,182,188,255,201,167,159,255,211,164,122,255,139,133,185,255,209,154,145,255,155,151,123,255,159,199,201,255,171,198,156,255,159,211,136,255,182,159,179,255,188,197,187,255,183,162,143,255,179,178,164,255,164,171,202,255,175,189,168,255,187,181,203,255,168,175,171,255,185,181,160,255,191,186,198,255,159,193,163,255,187,198,174,255,208,154,172,255,216,211,207,255,218,176,207,255,179,172,199,255,220,182,138,255,194,162,151,255,176,161,189,255,217,165,201,255,220,159,154,255,175,184,189,255,178,168,216,255,146,159,187,255,159,163,171,255,181,160,164,255,200,167,169,255,205,202,170,255,194,169,161,255,215,205,198,255,202,145,209,255,217,161,184,255,179,179,203,255,176,191,187,255,176,167,193,255,214,177,193,255,220,195,140,255,200,197,183,255,194,176,182,255,158,191,176,255,190,124,178,255,122,173,142,255,220,209,144,255,192,194,192,255,199,172,189,255,185,181,114,255,181,175,182,255,203,205,131,255,218,180,154,255,228,160,155,255,189,184,165,255,178,168,175,255,139,194,179,255,163,184,120,255,148,170,147,255,184,159,155,255,102,180,164,255,130,171,179,255,186,178,172,255,197,169,201,255,223,191,177,255,183,182,158,255,194,206,179,255,201,172,106,255,207,178,169,255,157,176,140,255,180,195,140,255,204,185,178,255,208,154,200,255,162,176,194,255,137,161,152,255,171,154,180,255,182,175,149,255,164,163,162,255,142,137,156,255,168,154,168,255,179,183,178,255,212,171,184,255,206,204,195,255,164,193,147,255,204,198,154,255,174,191,195,255,148,154,210,255,177,174,164,255,161,154,179,255,135,120,190,255,148,169,187,255,145,136,185,255,105,156,149,255,159,176,172,255,179,169,170,255,150,158,117,255,134,166,192,255,185,153,179,255,166,191,170,255,176,151,161,255,132,135,168,255,189,163,204,255,177,134,153,255,186,157,146,255,125,155,144,255,180,177,166,255,172,159,175,255,121,170,148,255,109,179,174,255,169,171,188,255,129,182,188,255,169,136,184,255,154,124,143,255,202,190,152,255,222,204,128,255,200,183,146,255,206,206,130,255,158,182,191,255,178,108,202,255,174,168,188,255,122,142,127,255,126,174,129,255,186,162,184,255,130,159,192,255,166,152,193,255,134,136,159,255,171,135,164,255,171,168,195,255,137,174,146,255,188,181,189,255,232,196,188,255,179,204,165,255,201,168,179,255,184,135,202,255,138,167,201,255,184,159,192,255,149,164,162,255,175,119,151,255,171,139,166,255,123,134,176,255,120,161,194,255,128,188,160,255,192,182,161,255,184,160,180,255,185,167,214,255,192,197,170,255,208,209,170,255,199,191,123,255,165,173,137,255,166,145,179,255,149,182,142,255,181,167,154,255,126,171,168,255,179,164,173,255,154,164,197,255,142,154,133,255,168,154,179,255,177,187,154,255,205,185,110,255,164,136,153,255,203,184,209,255,222,220,114,255,219,219,151,255,220,188,176,255,185,177,204,255,201,176,199,255,139,131,173,255,134,179,153,255,120,175,175,255,188,158,216,255,139,151,179,255,195,184,165,255,174,127,184,255,128,182,205,255,142,187,155,255,167,191,126,255,207,220,177,255,230,209,132,255,213,199,105,255,226,215,185,255,198,182,194,255,134,179,167,255,178,175,170,255,148,153,167,255,150,144,166,255,134,116,152,255,122,154,158,255,179,120,222,255,194,157,188,255,136,169,202,255,198,192,167,255,213,191,167,255,171,182,172,255,213,183,165,255,188,185,169,255,182,179,180,255,223,182,175,255,154,166,183,255,200,185,170,255,186,173,166,255,185,117,154,255,149,153,136,255]
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.9",
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
- export interface UniformDecl {
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
- this.uniforms.push(colour ? { name, type, value: value.slice(), colour: true } : { name, type, value: value.slice() })
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 computes
333
- // changes every hash and every cache keyed by one (an editor's generated
334
- // shaders, Magerie's pipelines) recompiles rather than serving the old maths.
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 computes, or a
347
- * change to the JSON shape (which also gets a migration in `fromJSON`).
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
@@ -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, ident, or invalid), built
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 with a one line description
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(() => {
@@ -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
- export type TokenKind = "ident" | "number" | "hex" | "punct" | "eof"
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
- const value = colour && u.init?.k === "hex"
192
- ? at(u.pos, () => sl.uniform.colour(u.name, (u.init as { hex: string }).hex, width as 3 | 4) as unknown as Val)
193
- : at(u.pos, () => declareUniform(u.name, u.type, components))
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]!) as unknown as Val
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
 
@@ -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 === "uniform") { unit.uniforms.push(this.parseUniform()); return }
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
- return out
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 hex literal is, and marks the
688
- * declaration `colour` so a host can offer a colour picker for it. Three
689
- * components when `width` is 3, dropping the alpha.
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"