onejs-sl 0.1.8 → 0.1.10
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 +23 -0
- package/README.md +34 -2
- package/goldens/goldens.json +9 -1
- package/package.json +1 -1
- package/src/core.ts +2 -1
- package/src/ir.ts +132 -3
- package/src/lang/README.md +38 -5
- package/src/lang/ast.ts +17 -0
- package/src/lang/attributes.ts +116 -0
- package/src/lang/check.ts +17 -6
- package/src/lang/classify.ts +7 -3
- package/src/lang/glsl.ts +280 -0
- package/src/lang/lexer.ts +23 -1
- package/src/lang/lower.ts +32 -9
- package/src/lang/parser.ts +49 -4
- package/src/params.ts +21 -4
- package/src/serial.ts +22 -2
- package/src/sl.ts +39 -20
- package/src/tables.ts +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.10
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
- `[Range]`, `[Toggle]`, `[Enum]`, `[Header]`, `[Label]`, `[Color]` and `[Hide]` before a uniform
|
|
8
|
+
- `UniformDecl` carries `range`, `toggle`, `options`, `header`, `label` and `hide`, never hashed
|
|
9
|
+
- `sl.uniform.float` and the rest take the same control as a last argument
|
|
10
|
+
- `sl.uniform.colour` takes components as well as a hex string
|
|
11
|
+
- `controlProblem` and `CONTROL_FIELDS` are exported from the core
|
|
12
|
+
- `ATTRIBUTE_NAMES` and `ATTRIBUTE_DOCS` are exported from `onejs-sl/tables`
|
|
13
|
+
- `classify` marks an attribute's name and a string
|
|
14
|
+
|
|
15
|
+
## 0.1.9
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
- `texel` is one pixel in uv, and `centered` is uv with 0 at the centre and x scaled by aspect
|
|
20
|
+
- A local, parameter or uniform named `texel` or `centered` keeps working
|
|
21
|
+
- A local named after a builtin can be assigned to
|
|
22
|
+
- `fromGLSL` reads `u_time` and `u_resolution` as the inputs, and drops their declarations
|
|
23
|
+
- `fromGLSL` turns a mat2 rotation into `rotate()`, and points any other mat2 at it
|
|
24
|
+
- `INPUT_DOCS` say `uv` and `fragCoord` are the centre of the pixel
|
|
25
|
+
|
|
3
26
|
## 0.1.8
|
|
4
27
|
|
|
5
28
|
A pasted GLSL shader becomes a `.sl` file (`Specs/SL_NEXT.md` 5). Nothing a program compiles to changes.
|
package/README.md
CHANGED
|
@@ -345,8 +345,18 @@ reset takes the whole page's device, Unity's included.
|
|
|
345
345
|
## What a program is given
|
|
346
346
|
|
|
347
347
|
`uv`, `fragCoord`, `resolution`, `time` and `aspect`, and the last three are the
|
|
348
|
-
**target's**, not the window's.
|
|
349
|
-
|
|
348
|
+
**target's**, not the window's. `uv` and `fragCoord` are the centre of the pixel
|
|
349
|
+
being drawn, so the first column's `fragCoord.x` is 0.5 on both backends.
|
|
350
|
+
|
|
351
|
+
Two more are built from those: `texel`, one pixel in uv (`1 / resolution`), and
|
|
352
|
+
`centered`, uv with 0 at the centre and x scaled by `aspect`, so a circle drawn
|
|
353
|
+
in it stays round. They are recorded only when a program names them, so every
|
|
354
|
+
program that does not compiles to the bytes it always did, and a program with a
|
|
355
|
+
`texel` of its own keeps it: a local, a parameter or a uniform of that name
|
|
356
|
+
shadows the input.
|
|
357
|
+
|
|
358
|
+
Why `resolution` and `aspect` are the target's: a program is drawn with
|
|
359
|
+
`Graphics.Blit` into the element's own render texture, and Unity sets `_ScreenParams` per camera and
|
|
350
360
|
leaves it alone for a blit: reading it from a 64x256 target answers with the
|
|
351
361
|
game view's size. Both backends read the same wrong thing, so they agreed with
|
|
352
362
|
each other and the eject test, which compares them, saw nothing. What saw it was
|
|
@@ -357,6 +367,28 @@ sets `_Res` from the target and both backends read that.
|
|
|
357
367
|
`fx` had already learned this: `ShaderEffectElement` sets `_Aspect` from the
|
|
358
368
|
render texture with a comment saying why. The lesson did not travel.
|
|
359
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
|
+
|
|
360
392
|
## Colours
|
|
361
393
|
|
|
362
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.10",
|
|
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": [
|
|
@@ -108,6 +108,14 @@
|
|
|
108
108
|
"1.25": [255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255,255,128,64,255]
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
|
+
"inputs.sl": {
|
|
112
|
+
"hash": "07cd901a",
|
|
113
|
+
"source": "// texel and centered, the inputs built from the others (Specs/SL_NEXT.md 6).\n// A ring that stays round in any output shape, over cells 16 pixels wide.\nfloat4 main() {\n float ring = smoothstep(0.02, 0.0, abs(length(centered) - 0.3));\n float2 cell = frac(uv / (texel * 16.0));\n return float4(ring, cell, 1);\n}\n",
|
|
114
|
+
"samples": {
|
|
115
|
+
"0": [0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,173,171,86,255,139,212,86,255,0,244,86,255,0,110,86,255,207,171,86,255,24,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,253,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,193,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,202,255,0,171,202,255,0,212,202,255,173,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,158,255,0,171,158,255,0,212,158,255,139,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,253,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,188,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,207,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,202,255,0,171,202,255,0,212,202,255,207,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,226,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,158,255,0,171,158,255,0,212,158,255,24,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,193,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,253,212,237,255,188,244,237,255,207,110,237,255,226,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255],
|
|
116
|
+
"1.25": [0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,173,171,86,255,139,212,86,255,0,244,86,255,0,110,86,255,207,171,86,255,24,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,253,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,193,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,202,255,0,171,202,255,0,212,202,255,173,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,158,255,0,171,158,255,0,212,158,255,139,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,253,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,188,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,207,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,202,255,0,171,202,255,0,212,202,255,207,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,226,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,158,255,0,171,158,255,0,212,158,255,24,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,193,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,253,212,237,255,188,244,237,255,207,110,237,255,226,171,237,255,0,212,237,255,0,244,237,255,0,110,237,255,0,171,237,255,0,212,237,255,0,244,237,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,202,255,0,171,202,255,0,212,202,255,0,244,202,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,158,255,0,171,158,255,0,212,158,255,0,244,158,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255,0,110,86,255,0,171,86,255,0,212,86,255,0,244,86,255]
|
|
117
|
+
}
|
|
118
|
+
},
|
|
111
119
|
"orient.sl": {
|
|
112
120
|
"hash": "f3b299ff",
|
|
113
121
|
"source": "// An anchor: red is uv.x, green is uv.y, so a golden's orientation and its\n// linear-to-sRGB store can be checked against arithmetic, not against itself.\nfloat4 main() { return float4(uv.x, uv.y, 0, 1); }\n",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onejs-sl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
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
|
@@ -42,6 +42,27 @@ export const INPUTS = {
|
|
|
42
42
|
} as const
|
|
43
43
|
export type InputName = keyof typeof INPUTS
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Inputs a program can name that are built from the ones above rather than
|
|
47
|
+
* handed over by the host (`Specs/SL_NEXT.md` 6). Each read records the same
|
|
48
|
+
* few nodes a program would if it wrote the expression out, and the builder's
|
|
49
|
+
* hash consing makes every later read the same node. So there is no new
|
|
50
|
+
* instruction, no VM or host change, and a program that never names one holds
|
|
51
|
+
* no extra node, compiling to the bytes it always did.
|
|
52
|
+
*
|
|
53
|
+
* texel 1 / resolution: one pixel, in uv
|
|
54
|
+
* centered (uv - 0.5) * float2(aspect, 1): 0 at the centre, circles stay round
|
|
55
|
+
*/
|
|
56
|
+
export const DERIVED_INPUTS = {
|
|
57
|
+
texel: TYPE.VEC2,
|
|
58
|
+
centered: TYPE.VEC2,
|
|
59
|
+
} as const
|
|
60
|
+
export type DerivedInputName = keyof typeof DERIVED_INPUTS
|
|
61
|
+
|
|
62
|
+
/** Every input a program can name: the host's, then the derived ones. */
|
|
63
|
+
export type SourceInputName = InputName | DerivedInputName
|
|
64
|
+
export const SOURCE_INPUTS: Readonly<Record<SourceInputName, SLType>> = { ...INPUTS, ...DERIVED_INPUTS }
|
|
65
|
+
|
|
45
66
|
/** Index into `Program.nodes`. Always refers backwards. */
|
|
46
67
|
export type NodeRef = number
|
|
47
68
|
|
|
@@ -52,7 +73,86 @@ export type SLNode =
|
|
|
52
73
|
| { k: "swizzle"; type: SLType; src: NodeRef; chans: number[] }
|
|
53
74
|
| { k: "call"; type: SLType; op: SLOpCode; args: NodeRef[]; imm?: number[] }
|
|
54
75
|
|
|
55
|
-
|
|
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 {
|
|
56
156
|
name: string
|
|
57
157
|
type: SLType
|
|
58
158
|
/** Default, used when a caller does not supply the uniform. */
|
|
@@ -161,7 +261,7 @@ export class Builder {
|
|
|
161
261
|
return this.add({ k: "const", type: v.length as SLType, v: v.slice() })
|
|
162
262
|
}
|
|
163
263
|
|
|
164
|
-
uniform(name: string, type: SLType, value: number[], colour = false): NodeRef {
|
|
264
|
+
uniform(name: string, type: SLType, value: number[], colour = false, control: UniformControl = {}): NodeRef {
|
|
165
265
|
const existing = this.uniforms.findIndex((u) => u.name === name)
|
|
166
266
|
if (existing >= 0) {
|
|
167
267
|
const u = this.uniforms[existing]
|
|
@@ -171,10 +271,27 @@ export class Builder {
|
|
|
171
271
|
if ((u.colour === true) !== colour) {
|
|
172
272
|
throw new SLError(`uniform "${name}" is declared both as a colour and as plain numbers`)
|
|
173
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)
|
|
174
286
|
return this.add({ k: "uniform", type, slot: existing })
|
|
175
287
|
}
|
|
288
|
+
const problem = controlProblem(type, value, control)
|
|
289
|
+
if (problem !== null) throw new SLError(`uniform "${name}": ${problem.message}`)
|
|
176
290
|
const slot = this.uniforms.length
|
|
177
|
-
|
|
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)
|
|
178
295
|
return this.add({ k: "uniform", type, slot })
|
|
179
296
|
}
|
|
180
297
|
|
|
@@ -317,6 +434,18 @@ export function hashProgram(nodes: SLNode[], result: NodeRef, uniforms: UniformD
|
|
|
317
434
|
return fnv1a(parts.join("|"))
|
|
318
435
|
}
|
|
319
436
|
|
|
437
|
+
/** The control fields of `c` that are set, copied, in `CONTROL_FIELDS` order. */
|
|
438
|
+
export function controlOf(c: UniformControl): UniformControl {
|
|
439
|
+
const out: UniformControl = {}
|
|
440
|
+
if (c.range !== undefined) out.range = c.range.step === undefined ? { min: c.range.min, max: c.range.max } : { ...c.range }
|
|
441
|
+
if (c.toggle === true) out.toggle = true
|
|
442
|
+
if (c.options !== undefined) out.options = c.options.slice()
|
|
443
|
+
if (c.header !== undefined) out.header = c.header
|
|
444
|
+
if (c.label !== undefined) out.label = c.label
|
|
445
|
+
if (c.hide === true) out.hide = true
|
|
446
|
+
return out
|
|
447
|
+
}
|
|
448
|
+
|
|
320
449
|
/** Bumped when the hashing scheme changes, which invalidates generated shaders. */
|
|
321
450
|
export const SL_HASH_VERSION = 1
|
|
322
451
|
|
package/src/lang/README.md
CHANGED
|
@@ -121,7 +121,8 @@ the generated shader's Properties block shows, and every read of `tint` goes
|
|
|
121
121
|
through `toLinear`. Without that rule, `#ff8040` written as a literal and
|
|
122
122
|
`#ff8040` written as a default would be two different colours in one file.
|
|
123
123
|
A default built out of numbers (`float4(1, 0.5, 0.25, 1)`) is not a colour and
|
|
124
|
-
is read unconverted
|
|
124
|
+
is read unconverted, unless the uniform says `[Color]`, which makes it one
|
|
125
|
+
exactly as a hex default does.
|
|
125
126
|
|
|
126
127
|
**A uniform's default has to be written out.** Numbers, constructors of numbers
|
|
127
128
|
and colours, and nothing else. It is baked into the program before anything
|
|
@@ -166,6 +167,26 @@ browser and correct after an eject. A `.sl` file is held to the real number, 4,
|
|
|
166
167
|
reported at the declaration. The EDSL's ceiling is untouched here because
|
|
167
168
|
lowering it changes recorded behaviour rather than parser behaviour.
|
|
168
169
|
|
|
170
|
+
## Attributes
|
|
171
|
+
|
|
172
|
+
Unity's names, before a uniform, say what its control is: `[Range(0, 2)]`, with
|
|
173
|
+
an optional third number for the step, `[Toggle]`, `[Enum(Soft, Hard, Glow)]`,
|
|
174
|
+
`[Header("Shape")]`, `[Label("Glow colour")]`, `[Color]` and `[Hide]`. They go
|
|
175
|
+
on the uniform's line or the lines above it, in any order. A string exists only
|
|
176
|
+
here, which is the one reason the lexer reads one.
|
|
177
|
+
|
|
178
|
+
Three files share the work. The parser reads the shape (`[Name(args)]`, numbers,
|
|
179
|
+
words and strings); `attributes.ts` says what each one means and refuses what it
|
|
180
|
+
cannot mean, offering Unity's and English spellings as a fix (`Colour` to
|
|
181
|
+
`Color`, `HideInInspector` to `Hide`); `controlProblem` in `ir.ts` refuses what
|
|
182
|
+
does not fit the uniform's type and default, and is the same check the EDSL's
|
|
183
|
+
`sl.uniform.float(name, value, control)` runs. The last one needs the default,
|
|
184
|
+
so its errors come from lowering, marked on the attribute at fault.
|
|
185
|
+
|
|
186
|
+
What they produce is metadata on the uniform (`range`, `toggle`, `options`,
|
|
187
|
+
`header`, `label`, `hide`), which the hash never reads. `[Color]` is the
|
|
188
|
+
exception, because it changes the reads: it is the same as a hex default.
|
|
189
|
+
|
|
169
190
|
## Errors
|
|
170
191
|
|
|
171
192
|
Every one carries file, line, column, offset and length, and `text`, the
|
|
@@ -204,14 +225,15 @@ same place, over every program one token short of a real one.
|
|
|
204
225
|
## For an editor
|
|
205
226
|
|
|
206
227
|
`classify(source)` is every token with its class (keyword, type, builtin,
|
|
207
|
-
input, prelude, number, hex, comment, punct, member,
|
|
228
|
+
input, prelude, number, hex, string, comment, punct, member, attribute, ident,
|
|
229
|
+
or invalid), built
|
|
208
230
|
on the parser's own scanner and word lists, so the highlighting is the parser's.
|
|
209
231
|
It never throws, since it runs on every keystroke. It classifies by spelling,
|
|
210
232
|
not scope: a local named `circle` still reads as a builtin where it is used.
|
|
211
233
|
|
|
212
234
|
`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
|
|
235
|
+
name may be neither. `onejs-sl/tables` carries them, and `ATTRIBUTE_NAMES`, with
|
|
236
|
+
a one line description of every builtin, input, attribute and prelude function; a prelude function's is the
|
|
215
237
|
comment above it in `prelude-source.ts`.
|
|
216
238
|
|
|
217
239
|
## From GLSL
|
|
@@ -234,7 +256,18 @@ where they were, so the result reads as the shader it came from. What it does:
|
|
|
234
256
|
same. Shadertoy's `uv = fragCoord / iResolution.xy` line is dropped, since
|
|
235
257
|
that is the input `uv`.
|
|
236
258
|
- `iResolution`, `iMouse`, `iChannel0` to `3` and `uniform sampler2D` become
|
|
237
|
-
`resolution`, a `mouse` uniform and declared textures.
|
|
259
|
+
`resolution`, a `mouse` uniform and declared textures. The Book of Shaders'
|
|
260
|
+
`u_time` and `u_resolution`, glslsandbox's `time` and `resolution`, and
|
|
261
|
+
Shadertoy's own inputs where a tool declares them are the inputs: their
|
|
262
|
+
`uniform` declarations go, so the shader does not arrive frozen.
|
|
263
|
+
- A mat2 rotation becomes `rotate(p, angle)`: a function or macro returning
|
|
264
|
+
`mat2(c, -s, s, c)` or `mat2(c, s, -s, c)` of one angle, or the same written
|
|
265
|
+
in place, wherever a vector is multiplied by it (`p *= rot(a)`,
|
|
266
|
+
`rot(a) * p`, `p * rot(a)`). The sign of the angle comes from the matrix and
|
|
267
|
+
from which side the vector is on, since GLSL fills a matrix by columns. The
|
|
268
|
+
function or macro goes once every use is converted; otherwise it stays, and
|
|
269
|
+
the notes point at `rotate()`. A file with a `rotate` of its own is left
|
|
270
|
+
alone, since the calls would reach it. `#define NAME value`
|
|
238
271
|
becomes a const; any other directive is kept as a comment, with a note.
|
|
239
272
|
- A name the language already means something by gets a `_` suffix, and an
|
|
240
273
|
`int` becomes a float (a for-loop counter stays an int), each with a note.
|
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
|
@@ -28,15 +28,22 @@
|
|
|
28
28
|
* is refused with the reason.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { INPUTS } from "../ir"
|
|
31
|
+
import { DERIVED_INPUTS, INPUTS } from "../ir"
|
|
32
32
|
import { VM_TEXTURES, VM_UNIFORMS } from "../ops"
|
|
33
33
|
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))
|
|
41
|
+
/**
|
|
42
|
+
* Inputs built from the others (`texel`, `centered`). A declaration may take
|
|
43
|
+
* one's name, as it may a builtin's, because programs wrote their own `texel`
|
|
44
|
+
* long before the language had one, and that must keep compiling.
|
|
45
|
+
*/
|
|
46
|
+
const DERIVED_NAMES = new Set(Object.keys(DERIVED_INPUTS))
|
|
40
47
|
|
|
41
48
|
/**
|
|
42
49
|
* The GLSL spellings whose HLSL name means the same wherever it is written, so
|
|
@@ -133,13 +140,14 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
133
140
|
if (uniforms.has(n)) return "a uniform"
|
|
134
141
|
if (textures.has(n)) return "a texture"
|
|
135
142
|
if (consts.has(n)) return "a const"
|
|
143
|
+
if (DERIVED_NAMES.has(n)) return "a derived input"
|
|
136
144
|
return null
|
|
137
145
|
}
|
|
138
146
|
|
|
139
147
|
/** What a value may not be called: anything spoken for, except a builtin's or a prelude function's name. */
|
|
140
148
|
const valueClash = (n: string): string | null => {
|
|
141
149
|
const why = taken(n)
|
|
142
|
-
if (why !== null && why !== "a builtin") return why
|
|
150
|
+
if (why !== null && why !== "a builtin" && why !== "a derived input") return why
|
|
143
151
|
const fn = funcs.get(n)
|
|
144
152
|
return fn !== undefined && !fn.prelude ? "a function" : null
|
|
145
153
|
}
|
|
@@ -149,6 +157,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
149
157
|
const clash = valueClash(u.name)
|
|
150
158
|
if (clash !== null) fail(`"${u.name}" already names ${clash}`, u.pos, u.name.length)
|
|
151
159
|
})
|
|
160
|
+
for (const p of readAttributes(u).problems) attempt(() => fail(p.message, p.pos, p.length, p.fix))
|
|
152
161
|
if (!uniforms.has(u.name)) uniforms.set(u.name, u)
|
|
153
162
|
}
|
|
154
163
|
attempt(() => {
|
|
@@ -220,7 +229,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
220
229
|
if (main.params.length > 0) {
|
|
221
230
|
fail(
|
|
222
231
|
"main takes no parameters: what a program is given are the free identifiers uv, " +
|
|
223
|
-
"fragCoord, resolution, time and
|
|
232
|
+
"fragCoord, resolution, time, aspect, texel and centered",
|
|
224
233
|
main.params[0]!.pos,
|
|
225
234
|
)
|
|
226
235
|
}
|
|
@@ -365,7 +374,9 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
365
374
|
}
|
|
366
375
|
if (target.k !== "ident") fail("only a local can be assigned to", target.pos)
|
|
367
376
|
const n = target.name
|
|
368
|
-
|
|
377
|
+
// A local shadows what it is named after, a builtin or a
|
|
378
|
+
// derived input, so it is assignable like any other local.
|
|
379
|
+
const why = scope.has(n) ? null : taken(n)
|
|
369
380
|
if (why !== null) fail(`"${n}" is ${why} and cannot be assigned to`, target.pos, n.length)
|
|
370
381
|
if (counters.has(n)) {
|
|
371
382
|
fail(
|
|
@@ -423,7 +434,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
423
434
|
case "hex":
|
|
424
435
|
return
|
|
425
436
|
case "ident": {
|
|
426
|
-
if (scope.has(e.name) || INPUT_NAMES.has(e.name) || uniforms.has(e.name) || consts.has(e.name)) return
|
|
437
|
+
if (scope.has(e.name) || INPUT_NAMES.has(e.name) || DERIVED_NAMES.has(e.name) || uniforms.has(e.name) || consts.has(e.name)) return
|
|
427
438
|
if (textures.has(e.name)) {
|
|
428
439
|
fail(
|
|
429
440
|
`"${e.name}" is a texture, and a texture is only ever the first argument of ` +
|
|
@@ -581,7 +592,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
581
592
|
/** Refuses a name nothing declares, offering the nearest one spelled almost like it. */
|
|
582
593
|
function unknown(n: string, scope: Set<string>, pos: Pos): never {
|
|
583
594
|
const near = nearest(n, [
|
|
584
|
-
...scope, ...INPUT_NAMES, ...uniforms.keys(), ...textures.keys(), ...consts.keys(),
|
|
595
|
+
...scope, ...INPUT_NAMES, ...DERIVED_NAMES, ...uniforms.keys(), ...textures.keys(), ...consts.keys(),
|
|
585
596
|
...funcs.keys(), ...Object.keys(BUILTINS),
|
|
586
597
|
])
|
|
587
598
|
if (near === null) fail(`"${n}" is not declared`, pos, n.length)
|