onejs-sl 0.9.1 → 0.9.3
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 +17 -0
- package/README.md +8 -2
- package/goldens/goldens.json +10 -2
- package/lib/noise2d.hlsl +3 -3
- package/package.json +1 -1
- package/src/body.ts +24 -5
- package/src/lang/README.md +8 -0
- package/src/lang/check.ts +2 -2
- package/src/lang/lower.ts +41 -11
- package/src/lang/parser.ts +5 -5
- package/src/lang/prelude-source.ts +8 -0
- package/src/sl.ts +4 -4
- package/src/structure.ts +13 -3
- package/src/web.ts +20 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.3
|
|
4
|
+
|
|
5
|
+
A hex colour is a float3 wherever a float3 is wanted, and `lookRay(eye, target, lens)` gives the ray through this pixel from a camera. Every program that compiled before keeps its hash.
|
|
6
|
+
|
|
7
|
+
- `lookRay(eye, target, lens)` returns the ray through this pixel from a y up camera at `eye` looking at `target`, as Unity's camera casts it; `lens` is `0.5 / tan(fov / 2)`
|
|
8
|
+
- A hex colour is a float3 wherever a float3 is wanted: returned, passed, in `float3(...)`, or beside a float3 in an op, a choice or a builtin
|
|
9
|
+
- A program that compiled before keeps its hash
|
|
10
|
+
|
|
11
|
+
## 0.9.2
|
|
12
|
+
|
|
13
|
+
A fractional `tex2Dlod` level now weighs each level's colour by its alpha, so a transparent level no longer darkens the other. Every program keeps its hash; a host's generated shaders take the new text on their next build.
|
|
14
|
+
|
|
15
|
+
- A fractional `tex2Dlod` level blends the two levels premultiplied, so a transparent level fades the other out instead of pulling it toward black
|
|
16
|
+
- Opaque textures draw as they did, and so does colour under zero alpha
|
|
17
|
+
- Every program keeps its hash; one with a fractional `tex2Dlod` compiles to new shader text, which a host's generated shaders pick up on their next build
|
|
18
|
+
- The goldens anchor `lod-alpha.sl` to arithmetic, through a texture with alpha bound to a slot declared `edge`
|
|
19
|
+
|
|
3
20
|
## 0.9.1
|
|
4
21
|
|
|
5
22
|
`?:` now takes one side instead of blending the two, so a huge, infinite or NaN value on the side not taken no longer spoils the result. Every program keeps its hash; a host's generated shaders take the new text on their next build.
|
package/README.md
CHANGED
|
@@ -73,7 +73,10 @@ filtering and wrapping left to the host, which OneJS's hosts take from the
|
|
|
73
73
|
bound texture's own settings. Between mip levels the body decides instead: a
|
|
74
74
|
fractional `tex2Dlod` level blends the two levels either side by the fraction,
|
|
75
75
|
whatever the texture's filter mode, since the body asks for both whole levels
|
|
76
|
-
and blends them itself.
|
|
76
|
+
and blends them itself. The blend weighs each level's colour by its alpha, as
|
|
77
|
+
trilinear filtering of premultiplied texels does, so a level that reads
|
|
78
|
+
transparent fades the other out instead of darkening it; where both levels are
|
|
79
|
+
opaque, or both transparent, it is the plain blend. A level it can prove whole (a whole constant, a
|
|
77
80
|
`floor`, an int) costs one sample, and a level past the smallest reads the
|
|
78
81
|
smallest. A texture whose program sets its own sampler (`[Wrap(Repeat)]`,
|
|
79
82
|
`[Filter(Point)]`) goes to `sampleWith` instead, with both halves filled in,
|
|
@@ -162,7 +165,10 @@ over every pixel, and the anchors must match arithmetic, not each other:
|
|
|
162
165
|
stores as written), `texture.sl` (a texture's orientation and sRGB decode),
|
|
163
166
|
`lod.sl` (each mip level `tex2Dlod` reads, the texture's smaller levels being
|
|
164
167
|
solid colours), `lod-frac.sl` (a fractional level blends the two levels
|
|
165
|
-
either side, on a texture sampled nearest between levels), `
|
|
168
|
+
either side, on a texture sampled nearest between levels), `lod-alpha.sl` (the
|
|
169
|
+
same blend weighs each level's colour by its alpha, through a second texture,
|
|
170
|
+
bound to a slot declared `edge`, whose levels are transparent, opaque and half
|
|
171
|
+
transparent), `wrap-repeat.sl`
|
|
166
172
|
and `wrap-mirror.sl` (a program's own wrap over a texture the host binds
|
|
167
173
|
clamped, the filter's footprint wrapping with it), `filter-point.sl` (its own
|
|
168
174
|
point filter, to the byte, through `tex2D` and `tex2Dlod`) and `fetch.sl`
|
package/goldens/goldens.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"generatedBy": "onejs-sl goldens/run.mjs",
|
|
3
|
-
"package": "0.9.
|
|
3
|
+
"package": "0.9.3",
|
|
4
4
|
"ir": 6,
|
|
5
5
|
"target": "rgba8unorm-srgb: straight alpha, 8 bit, sRGB encoded, the stored bytes of a Linear OneJS game's element",
|
|
6
6
|
"size": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"historyExact": "a stepped fixture marked historyExact has to hold its history samples to the bit",
|
|
21
21
|
"samples": "a 16 x 16 grid at x = 2 + 4i, y = 2 + 4j, rows from the top, i fastest, RGBA each",
|
|
22
|
-
"texture": "every sampled slot: 8 x 8, rgba8 sRGB, linear filter, clamp to edge; texel (x, y) with y from the top = (32x + 16, 32y + 16, (x + y) even ? 200 : 40, 255); uv (0, 0) is the image's bottom left. Mip levels 1 to 3 (4 x 4, 2 x 2, 1 x 1) are each one colour, (224, 64, 32, 255), (32, 224, 64, 255), (64, 32, 224, 255), sampled nearest between levels",
|
|
22
|
+
"texture": "every sampled slot: 8 x 8, rgba8 sRGB, linear filter, clamp to edge; texel (x, y) with y from the top = (32x + 16, 32y + 16, (x + y) even ? 200 : 40, 255); uv (0, 0) is the image's bottom left. Mip levels 1 to 3 (4 x 4, 2 x 2, 1 x 1) are each one colour, (224, 64, 32, 255), (32, 224, 64, 255), (64, 32, 224, 255), sampled nearest between levels. A slot declared edge gets instead an 8 x 8 sampled the same way whose levels 0 to 3 are each one colour with straight alpha, (0, 0, 0, 0), (224, 64, 32, 255), (32, 224, 64, 128), (255, 255, 255, 0)",
|
|
23
23
|
"uniforms": "each program's declared defaults; a colour uniform's default is sRGB as written",
|
|
24
24
|
"drawnOn": {
|
|
25
25
|
"webgpu": "apple metal-3",
|
|
@@ -215,6 +215,14 @@
|
|
|
215
215
|
"1.25": [0,255,0,255,0,255,0,255,255,0,0,255,255,0,0,255,0,0,0,255,0,0,0,255,255,255,0,255,255,255,0,255,0,0,0,255,0,0,0,255,255,255,0,255,255,255,0,255,0,0,0,255,0,0,0,255,255,0,0,255,255,0,0,255,0,255,0,255,0,255,0,255,255,0,0,255,255,0,0,255,0,0,0,255,0,0,0,255,255,255,0,255,255,255,0,255,0,0,0,255,0,0,0,255,255,255,0,255,255,255,0,255,0,0,0,255,0,0,0,255,255,0,0,255,255,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,255,255,0,255,255,255,0,255,255,0,0,255,255,0,0,255,0,255,0,255,0,255,0,255,255,255,0,255,255,255,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,255,255,0,255,255,255,0,255,255,0,0,255,255,0,0,255,0,255,0,255,0,255,0,255,255,255,0,255,255,255,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,0,255,0,255,0,255,0,255,255,255,0,255,255,255,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,255,0,255,0,255,0,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,0,255,0,255,0,255,0,255,255,255,0,255,255,255,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,255,0,255,0,255,0,255,255,255,0,255,255,255,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,255,0,0,255,255,0,0,255,255,0,255,255,255,0,255,255,255,255,0,255,255,255,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,0,0,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,255,0,0,255,255,0,0,255,255,0,255,255,255,0,255,255,255,255,0,255,255,255,0,255,255,0,0,255,255,0,0,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,255,255,0,0,255,255,255,0,255,255,255,0,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,0,0,255,255,0,0,255,255,255,255,255,255,255,255,255,255,0,0,255,255,0,0,255,255,255,0,255,255,255,0,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,0,0,255,255,0,0,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,255,255,255,0,255,255,255,0,255,0,255,0,255,0,255,255,255,255,255,255,255,255,255,0,255,0,255,0,255,0,255,255,0,0,255,255,0,0,255,0,0,255,255,0,0,255,255,255,255,0,255,255,255,0,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255,0,255,0,255,0,255,0,255,255,255,255,255,255,255,255,255,0,255,0,255,0,255,0,255,255,0,0,255,255,0,0,255,0,0,255,255,0,0,255,255,255,255,0,255,255,255,0,255,0,255,255,255,0,255,255,255,255,255,255,255,255,255,255,255]
|
|
216
216
|
}
|
|
217
217
|
},
|
|
218
|
+
"lod-alpha.sl": {
|
|
219
|
+
"hash": "aaf5be77",
|
|
220
|
+
"source": "// An anchor for a fractional tex2Dlod level on a texture with alpha: the level\n// runs from 0 at the left edge to 3.5 at the right, through the goldens' edge\n// texture, whose levels are transparent black, opaque, half transparent and\n// transparent white. A fraction weighs each level's colour by its alpha, so a\n// transparent level fades the other out instead of pulling it toward the\n// colour it stores.\ntexture2D edge;\nfloat4 main() { return tex2Dlod(edge, uv, uv.x * 3.5); }\n",
|
|
221
|
+
"samples": {
|
|
222
|
+
"0": [224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0],
|
|
223
|
+
"1.25": [224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0,224,64,32,35,224,64,32,91,224,64,32,146,224,64,32,202,223,67,32,254,211,105,38,226,193,138,44,198,165,170,51,170,112,204,59,142,32,224,64,115,32,224,64,87,32,224,64,59,32,224,64,31,32,224,64,3,255,255,255,0,255,255,255,0]
|
|
224
|
+
}
|
|
225
|
+
},
|
|
218
226
|
"lod-frac.sl": {
|
|
219
227
|
"hash": "fd3f2852",
|
|
220
228
|
"source": "// An anchor for a fractional tex2Dlod level: the level runs from 0 at the left\n// edge to 4.5 at the right. A fraction blends the level below and the level\n// above by that fraction, whatever the texture's own filter between levels,\n// and past level 3, the smallest, both are level 3.\ntexture2D t;\nfloat4 main() { return tex2Dlod(t, uv, uv.x * 4.5); }\n",
|
package/lib/noise2d.hlsl
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// is the kind of thing nobody notices until two effects that should match do
|
|
10
10
|
// not.
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// hash
|
|
13
13
|
//
|
|
14
14
|
// The lattice hash is integer arithmetic, and has to stay integer arithmetic.
|
|
15
15
|
// A float hash, frac of a large product as most shader noise is written, is
|
|
@@ -108,7 +108,7 @@ float onejsFbm(float2 p, float seed, int octaves)
|
|
|
108
108
|
return onejsFbm(p, seed, octaves, 2.0, 0.5);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
//
|
|
111
|
+
// simplex
|
|
112
112
|
//
|
|
113
113
|
// Ashima/McEwan 2D simplex. Worth carrying next to the value noise because the
|
|
114
114
|
// two fail differently: value noise interpolates a square grid, so at high
|
|
@@ -167,7 +167,7 @@ float onejsFbmSimplex(float2 p, float seed, int octaves, float lacunarity, float
|
|
|
167
167
|
return sum / max(norm, 1e-4);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// turbulence and ridged
|
|
171
171
|
//
|
|
172
172
|
// fBm above sums signed octaves, so octaves cancel as often as they add and the
|
|
173
173
|
// field reads as cloud. Turbulence sums the ABSOLUTE value of each octave: the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onejs-sl",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
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 or GLSL ES",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
package/src/body.ts
CHANGED
|
@@ -48,7 +48,8 @@ export interface BodyTarget {
|
|
|
48
48
|
* do with the GPU's sampler:
|
|
49
49
|
*
|
|
50
50
|
* - **Straight alpha**, not premultiplied, in and out: the body never
|
|
51
|
-
* multiplies or divides by alpha,
|
|
51
|
+
* multiplies or divides by alpha, except to blend two mip levels
|
|
52
|
+
* (`sampleLevel`), and its result is straight too.
|
|
52
53
|
* - **In the space `colour` names.** With `linear`, rgb is linear light, so
|
|
53
54
|
* an sRGB texture is decoded before it is filtered, as an sRGB sampler
|
|
54
55
|
* does; with `gamma`, rgb is as stored.
|
|
@@ -68,7 +69,11 @@ export interface BodyTarget {
|
|
|
68
69
|
* asks for whole levels, or for a level it cannot prove whole, floored and
|
|
69
70
|
* floored plus one, which it blends by the fraction itself; so a fraction
|
|
70
71
|
* draws the same whatever the texture's filter between levels, and a host
|
|
71
|
-
* has no blending to do.
|
|
72
|
+
* has no blending to do. The blend weighs each level's colour by its alpha,
|
|
73
|
+
* as trilinear filtering of premultiplied texels does, and divides alpha
|
|
74
|
+
* back out, so a level that reads transparent adds no colour; where both
|
|
75
|
+
* read transparent it is the plain blend. A level past the smallest reads
|
|
76
|
+
* the smallest.
|
|
72
77
|
* Otherwise the same contract as `sample`. `tex2Dlod` in a program; HLSL's
|
|
73
78
|
* `tex2Dlod(s, float4(uv, 0, lod))` and Metal's `sample(s, uv, level(lod))`.
|
|
74
79
|
*/
|
|
@@ -198,6 +203,8 @@ export function emitBody(p: Program, target: BodyTarget): Body {
|
|
|
198
203
|
const textures = new Set<number>()
|
|
199
204
|
const helpers = new Set<string>()
|
|
200
205
|
const helper = (fn: string) => { helpers.add(fn); return fn }
|
|
206
|
+
/** The statements a node prints ahead of its own line (`printBody`'s `before`). */
|
|
207
|
+
const ahead = new Map<NodeRef, string[]>()
|
|
201
208
|
/** A node at width `w`: a scalar repeated into a constructor, since HLSL refuses float3(x) and Metal x.xxx. */
|
|
202
209
|
/** A constant node's value, or null for anything computed. */
|
|
203
210
|
const constantOf = (ref: number): number[] | null => {
|
|
@@ -404,10 +411,22 @@ export function emitBody(p: Program, target: BodyTarget): Body {
|
|
|
404
411
|
// A fraction blends the level below and the level above,
|
|
405
412
|
// whatever the texture's filter between levels, as the web
|
|
406
413
|
// emitters do (`isWhole`). Past the smallest level both read the
|
|
407
|
-
// smallest, as a whole level past it does.
|
|
414
|
+
// smallest, as a whole level past it does. The blend weighs
|
|
415
|
+
// each level's colour by its alpha, as trilinear filtering of
|
|
416
|
+
// premultiplied texels does: a level that reads transparent
|
|
417
|
+
// adds no colour, rather than pulling the other toward black.
|
|
418
|
+
// Where both are opaque it is the plain lerp, and where both
|
|
419
|
+
// are transparent too, so colour under zero alpha survives.
|
|
408
420
|
if (isWhole(p.nodes, n.args[1]!)) return level(slot, a[0]!, a[1]!)
|
|
409
421
|
const below = `floor(${a[1]})`
|
|
410
|
-
|
|
422
|
+
const [lo, hi, mixed] = ["lo", "hi", "pm"].map((s) => `${name(ref)}_${s}`)
|
|
423
|
+
const pm = (v: string) => `float4(${v}.rgb * ${v}.a, ${v}.a)`
|
|
424
|
+
ahead.set(ref, [
|
|
425
|
+
`float4 ${lo} = ${level(slot, a[0]!, below)};`,
|
|
426
|
+
`float4 ${hi} = ${level(slot, a[0]!, `${below} + 1.0`)};`,
|
|
427
|
+
`float4 ${mixed} = lerp(${pm(lo)}, ${pm(hi)}, frac(${a[1]}));`,
|
|
428
|
+
])
|
|
429
|
+
return `float4(${mixed}.a > 0.0 ? ${mixed}.rgb / ${mixed}.a : lerp(${lo}.rgb, ${hi}.rgb, frac(${a[1]})), ${mixed}.a)`
|
|
411
430
|
}
|
|
412
431
|
case SLOP.TEXTURE_SIZE: {
|
|
413
432
|
const slot = Math.round(imm[0] ?? 0)
|
|
@@ -449,7 +468,7 @@ export function emitBody(p: Program, target: BodyTarget): Body {
|
|
|
449
468
|
loopOpen: `${target.loopAttribute === undefined ? "" : target.loopAttribute + " "}for (;;) {`,
|
|
450
469
|
breakUnless: (c, turns, max) => `if (!(${c}) || ${turns} >= ${max}) break;`,
|
|
451
470
|
}
|
|
452
|
-
const lines = printBody(p, shape, syntax, expr, indent)
|
|
471
|
+
const lines = printBody(p, shape, syntax, expr, indent, (ref) => ahead.get(ref) ?? [])
|
|
453
472
|
lines.push(target.result === undefined
|
|
454
473
|
? `${indent}return ${name(p.result)};`
|
|
455
474
|
: `${indent}${target.result} = ${name(p.result)};`)
|
package/src/lang/README.md
CHANGED
|
@@ -125,6 +125,14 @@ constant 7 before the IR sees it. `sin(0.5)` is two instructions, because
|
|
|
125
125
|
folding it would mean a second implementation of `sin` in JavaScript, and a
|
|
126
126
|
second implementation is somewhere for the two backends to disagree.
|
|
127
127
|
|
|
128
|
+
**A hex colour takes the width its place wants.** Alone, `#ff8040` is a
|
|
129
|
+
float4. Where a float3 is wanted (a float3's declaration, a float3 function's
|
|
130
|
+
return, a float3 parameter, `float3(#ff8040)`, the other side of an op or a
|
|
131
|
+
choice, or a builtin's other arguments) it is that float3, its alpha dropped.
|
|
132
|
+
A choice between two hexes counts as one. A computed float4 does not fit a
|
|
133
|
+
float3 this way: that is still an error, since its alpha may be meant
|
|
134
|
+
(`writtenHex` and `asWidth` in `lower.ts`; `hex-width.test.ts`).
|
|
135
|
+
|
|
128
136
|
**A hex default makes a uniform a colour.** `uniform float4 tint = #ff8040;`
|
|
129
137
|
stores the sRGB components as its default, which is what the host sets and what
|
|
130
138
|
the generated shader's Properties block shows, and every read of `tint` goes
|
package/src/lang/check.ts
CHANGED
|
@@ -243,7 +243,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
243
243
|
}
|
|
244
244
|
})
|
|
245
245
|
|
|
246
|
-
//
|
|
246
|
+
// bodies
|
|
247
247
|
|
|
248
248
|
// The prelude's own bodies are checked once, where it is built, against a
|
|
249
249
|
// unit that has no uniforms of its own. Re-checking them here would let a
|
|
@@ -492,7 +492,7 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
|
|
|
492
492
|
return `this can never run: every way through the ${above} above ends in a return, break or continue`
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
-
//
|
|
495
|
+
// expressions
|
|
496
496
|
|
|
497
497
|
function checkExpr(fn: FuncDecl, e: Expr, scope: Set<string>): void {
|
|
498
498
|
switch (e.k) {
|
package/src/lang/lower.ts
CHANGED
|
@@ -317,7 +317,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
317
317
|
})
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
-
//
|
|
320
|
+
// declarations
|
|
321
321
|
|
|
322
322
|
function declareUniform(name: string, type: TypeName, c: number[], control: UniformControl): Val {
|
|
323
323
|
switch (type) {
|
|
@@ -429,7 +429,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
//
|
|
432
|
+
// functions
|
|
433
433
|
|
|
434
434
|
/** A function's body run to its return, in `scope`, which holds its parameters. */
|
|
435
435
|
function callBody(fn: FuncDecl, scope: Scope): LV {
|
|
@@ -442,7 +442,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
442
442
|
return frame.retval
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
//
|
|
445
|
+
// statements
|
|
446
446
|
|
|
447
447
|
/**
|
|
448
448
|
* Runs a body. What it does to the locals is in their bindings and what it
|
|
@@ -570,7 +570,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
570
570
|
case "switch": exec([switchAsIf(s, scope)], scope, frame); break
|
|
571
571
|
case "block": exec(s.body, new Scope(scope), frame); break
|
|
572
572
|
case "return": {
|
|
573
|
-
const v = lowerExpr(s.value, scope, want(frame.ret))
|
|
573
|
+
const v = asWidth(lowerExpr(s.value, scope, want(frame.ret)), s.value, frame.ret.width, s.pos)
|
|
574
574
|
frame.retval = returned(v, frame, s.pos)
|
|
575
575
|
frame.returned = true
|
|
576
576
|
break
|
|
@@ -606,6 +606,25 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
606
606
|
b.value = fit(v, b, name, s.pos)
|
|
607
607
|
}
|
|
608
608
|
|
|
609
|
+
/**
|
|
610
|
+
* A colour written as a hex, `#ff8000`, or a choice between two. It has
|
|
611
|
+
* no width of its own: alone it is a float4, and where a float3 is wanted
|
|
612
|
+
* (a float3's declaration, return or argument, the other side of an op or
|
|
613
|
+
* a choice, a builtin's other arguments) it is that float3, its alpha
|
|
614
|
+
* dropped. Before, only a declaration dropped it, and returning one from a
|
|
615
|
+
* float3 function, passing one, or mixing one with a float3 was an error
|
|
616
|
+
* (learn Mac, 6 Nov 2026, lessons 7 and 8).
|
|
617
|
+
*/
|
|
618
|
+
function writtenHex(e: Expr): boolean {
|
|
619
|
+
return e.k === "hex" || (e.k === "cond" && writtenHex(e.then) && writtenHex(e.else))
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** `v`, lowered from `e`, as the float3 a place wants when `e` is a written hex; otherwise as it is. */
|
|
623
|
+
function asWidth(v: LV, e: Expr, width: number, pos: Pos): LV {
|
|
624
|
+
if (width !== 3 || widthOf(v) !== 4 || !writtenHex(e)) return v
|
|
625
|
+
return at(pos, () => asVal(v).swz("xyz") as unknown as Val)
|
|
626
|
+
}
|
|
627
|
+
|
|
609
628
|
/** A return's value as the function's type. Only the kind converts; a width has to match. */
|
|
610
629
|
function returned(v: LV, frame: Frame, pos: Pos): LV {
|
|
611
630
|
const fn = frame.fn
|
|
@@ -621,7 +640,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
621
640
|
return fit(v, t, `${fn.name}'s result`, pos)
|
|
622
641
|
}
|
|
623
642
|
|
|
624
|
-
//
|
|
643
|
+
// loops
|
|
625
644
|
|
|
626
645
|
function runLoop(s: Extract<Stmt, { k: "for" | "while" }>, scope: Scope, frame: Frame): void {
|
|
627
646
|
const loopScope = new Scope(scope)
|
|
@@ -765,7 +784,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
765
784
|
return r === undefined ? null : { min: r.min, max: r.max }
|
|
766
785
|
}
|
|
767
786
|
|
|
768
|
-
//
|
|
787
|
+
// switch
|
|
769
788
|
|
|
770
789
|
/**
|
|
771
790
|
* A switch as the if chain it means: `x == 1 || x == 2` per case, in order,
|
|
@@ -801,7 +820,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
801
820
|
return out[0] ?? { k: "if", cond: { k: "bool", value: true, pos: s.pos }, then: [], else: [], pos: s.pos }
|
|
802
821
|
}
|
|
803
822
|
|
|
804
|
-
//
|
|
823
|
+
// conversions
|
|
805
824
|
|
|
806
825
|
/**
|
|
807
826
|
* A value as a declared type, as a declaration, an assignment and a
|
|
@@ -919,7 +938,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
919
938
|
return b.width === 1 ? parts[0]! : at(pos, () => compose(b.width, parts))
|
|
920
939
|
}
|
|
921
940
|
|
|
922
|
-
//
|
|
941
|
+
// expressions
|
|
923
942
|
|
|
924
943
|
/**
|
|
925
944
|
* `want` is the kind the value is going into, an int or a uint, which makes
|
|
@@ -993,6 +1012,8 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
993
1012
|
|
|
994
1013
|
/** A binary op on operands already lowered from `ea` and `eb`. */
|
|
995
1014
|
function combine(op: BinaryOp, a: LV, b: LV, ea: Expr, eb: Expr, scope: Scope, pos: Pos): LV {
|
|
1015
|
+
a = asWidth(a, ea, widthOf(b), pos)
|
|
1016
|
+
b = asWidth(b, eb, widthOf(a), pos)
|
|
996
1017
|
// A whole number beside an int is an int: `i < 10`, `i % 3`.
|
|
997
1018
|
if (isIntLike(a) && typeof b === "number" && wholeExpr(eb)) b = lowerExpr(eb, scope, kindWant(a))
|
|
998
1019
|
if (isIntLike(b) && typeof a === "number" && wholeExpr(ea)) a = lowerExpr(ea, scope, kindWant(b))
|
|
@@ -1134,6 +1155,8 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1134
1155
|
if (cond instanceof Test) void cond.float
|
|
1135
1156
|
let t = lowerExpr(e.then, scope, want)
|
|
1136
1157
|
let f = lowerExpr(e.else, scope, want)
|
|
1158
|
+
t = asWidth(t, e.then, widthOf(f), e.pos)
|
|
1159
|
+
f = asWidth(f, e.else, widthOf(t), e.pos)
|
|
1137
1160
|
if (isIntLike(t) && typeof f === "number" && wholeExpr(e.else)) f = lowerExpr(e.else, scope, kindWant(t))
|
|
1138
1161
|
if (isIntLike(f) && typeof t === "number" && wholeExpr(e.then)) t = lowerExpr(e.then, scope, kindWant(f))
|
|
1139
1162
|
const kinded = [t, f].some((v) => typeof v === "bigint" || v instanceof Uint || typeof v === "boolean" || (v instanceof Val && v.kind !== undefined))
|
|
@@ -1163,7 +1186,11 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1163
1186
|
const n = (callee as Extract<Expr, { k: "ident" }>).name
|
|
1164
1187
|
|
|
1165
1188
|
if (n === "int" || n === "uint" || n === "bool") return convert(n, e, scope)
|
|
1166
|
-
if (n in TYPE_WIDTH)
|
|
1189
|
+
if (n in TYPE_WIDTH) {
|
|
1190
|
+
// float3(#ff8000) is the colour's rgb, as a float3 declared from it is.
|
|
1191
|
+
const one = e.args.length === 1 ? asWidth(toFloat(lowerExpr(e.args[0]!, scope), e.args[0]!.pos), e.args[0]!, TYPE_WIDTH[n as TypeName], e.pos) : null
|
|
1192
|
+
return construct(n as TypeName, one !== null ? [one as number | Val] : floats(e.args), e.pos)
|
|
1193
|
+
}
|
|
1167
1194
|
|
|
1168
1195
|
if (n === "textureSize" || n === "tex2Dfetch") {
|
|
1169
1196
|
const tex = samplers.get((e.args[0] as Extract<Expr, { k: "ident" }>).name)!
|
|
@@ -1210,6 +1237,9 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1210
1237
|
const builtin = BUILTINS[n]
|
|
1211
1238
|
if (builtin !== undefined) {
|
|
1212
1239
|
let args = e.args.map((a) => lowerExpr(a, scope))
|
|
1240
|
+
// A hex among float3s is a float3: lerp(#ff8000, sky, t).
|
|
1241
|
+
const others = Math.max(0, ...args.filter((_, i) => !writtenHex(e.args[i]!)).map(widthOf))
|
|
1242
|
+
args = args.map((v, i) => asWidth(v, e.args[i]!, others, e.args[i]!.pos))
|
|
1213
1243
|
// min, max, abs and clamp keep ints ints; everything else takes floats.
|
|
1214
1244
|
if (INT_BUILTINS.has(n) && args.some(isIntLike) && args.every((v, i) => isIntLike(v) || (typeof v === "number" && wholeExpr(e.args[i]!)))) {
|
|
1215
1245
|
const k = kindWant(args.find(isIntLike)!)
|
|
@@ -1264,7 +1294,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1264
1294
|
const arg = e.args[i]!
|
|
1265
1295
|
// In the caller's scope, where the argument is written; as the
|
|
1266
1296
|
// parameter's kind, so a whole number passed to an int is one.
|
|
1267
|
-
const v = lowerExpr(arg, caller, want(t))
|
|
1297
|
+
const v = asWidth(lowerExpr(arg, caller, want(t)), arg, t.width, arg.pos)
|
|
1268
1298
|
const got = widthOf(v)
|
|
1269
1299
|
if (got !== t.width) {
|
|
1270
1300
|
fail(
|
|
@@ -1307,7 +1337,7 @@ export function lower(checked: Checked, errors?: SLParseError[]): Program {
|
|
|
1307
1337
|
}
|
|
1308
1338
|
}
|
|
1309
1339
|
|
|
1310
|
-
//
|
|
1340
|
+
// tables and small helpers
|
|
1311
1341
|
|
|
1312
1342
|
type ArithOp = "+" | "-" | "*" | "/" | "%"
|
|
1313
1343
|
type BitOp = "&" | "|" | "^" | "<<" | ">>"
|
package/src/lang/parser.ts
CHANGED
|
@@ -82,7 +82,7 @@ class Parser {
|
|
|
82
82
|
private readonly errors?: SLParseError[],
|
|
83
83
|
) {}
|
|
84
84
|
|
|
85
|
-
//
|
|
85
|
+
// token plumbing
|
|
86
86
|
|
|
87
87
|
private peek(n = 0): Token { return this.tokens[Math.min(this.i + n, this.tokens.length - 1)]! }
|
|
88
88
|
private next(): Token { return this.tokens[this.i++]! }
|
|
@@ -107,7 +107,7 @@ class Parser {
|
|
|
107
107
|
this.errors.push(e)
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
//
|
|
110
|
+
// recovery
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
113
|
* Runs one statement's or one declaration's parse. Collecting, an error is
|
|
@@ -184,7 +184,7 @@ class Parser {
|
|
|
184
184
|
this.fail(`"${t.text}" is not a type; the types are float, float2, float3, float4, int, uint and bool`, t)
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
//
|
|
187
|
+
// the file
|
|
188
188
|
|
|
189
189
|
parseUnit(): Unit {
|
|
190
190
|
const unit: Unit = {
|
|
@@ -328,7 +328,7 @@ class Parser {
|
|
|
328
328
|
return { name: name.text, ret, params, body, pos: start, prelude: false }
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
//
|
|
331
|
+
// statements
|
|
332
332
|
|
|
333
333
|
private parseBlock(): Stmt[] {
|
|
334
334
|
this.expect("{", "to open a body")
|
|
@@ -538,7 +538,7 @@ class Parser {
|
|
|
538
538
|
return { k: "switch", value, cases, pos: kw }
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
-
//
|
|
541
|
+
// expressions
|
|
542
542
|
|
|
543
543
|
parseExpr(): Expr { return this.parseBinary(0) }
|
|
544
544
|
|
|
@@ -40,6 +40,14 @@ float2 tile(float2 p, float n) {
|
|
|
40
40
|
float3 palette(float t, float3 a, float3 b, float3 c, float3 d) {
|
|
41
41
|
return a + b * cos(6.28318530718 * (c * t + d));
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
// This pixel's ray from eye toward target, as Unity's camera casts it. lens is 0.5 / tan(fov / 2).
|
|
45
|
+
float3 lookRay(float3 eye, float3 target, float lens) {
|
|
46
|
+
float3 forward = normalize(target - eye);
|
|
47
|
+
float3 right = normalize(cross(float3(0, 1, 0), forward));
|
|
48
|
+
float3 up = cross(forward, right);
|
|
49
|
+
return normalize(forward * lens + right * centered.x + up * centered.y);
|
|
50
|
+
}
|
|
43
51
|
`
|
|
44
52
|
|
|
45
53
|
/** Each prelude function's name, in the order the source declares them. */
|
package/src/sl.ts
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
import { SLOP, SL_HLSL, SL_NAME, type SLOpCode } from "./ops"
|
|
32
32
|
import { SL_SDF_PARAMS, SL_SDF_SHAPES, type SlSdfKind } from "./shapes"
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// values
|
|
35
35
|
|
|
36
36
|
type Swz = "x" | "y" | "z" | "w" | "r" | "g" | "b" | "a"
|
|
37
37
|
const CHAN: Record<string, number> = { x: 0, y: 1, z: 2, w: 3, r: 0, g: 1, b: 2, a: 3 }
|
|
@@ -202,7 +202,7 @@ function withKind<T extends SLNode>(n: T, kind: SLKind | undefined): T {
|
|
|
202
202
|
return kind === undefined ? n : { ...n, kind }
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
//
|
|
205
|
+
// recording context
|
|
206
206
|
|
|
207
207
|
let current: Builder | null = null
|
|
208
208
|
|
|
@@ -288,7 +288,7 @@ function un(op: SLOpCode, a: Val): Val {
|
|
|
288
288
|
return mk(a.owner, a.owner.call(op, a.width, [a.ref]), a.width)
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
//
|
|
291
|
+
// kinds (IR 4)
|
|
292
292
|
|
|
293
293
|
/** The ops an int or a uint keeps its kind through. Anything else takes it as a float. */
|
|
294
294
|
const INT_OPS = new Set<number>([SLOP.ADD, SLOP.SUB, SLOP.MUL, SLOP.DIV, SLOP.MOD, SLOP.MIN, SLOP.MAX, SLOP.CLAMP])
|
|
@@ -486,7 +486,7 @@ function typed(b: Builder, v: Num, t: ValType): NodeRef {
|
|
|
486
486
|
return v.ref
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
//
|
|
489
|
+
// the public surface
|
|
490
490
|
|
|
491
491
|
export interface ProgramInputs {
|
|
492
492
|
uv: Vec2
|
package/src/structure.ts
CHANGED
|
@@ -224,8 +224,15 @@ export const local = (ref: NodeRef): string => `n${ref}`
|
|
|
224
224
|
* Prints the body: every placed node in its region, each `if` and `loop` with
|
|
225
225
|
* its regions inside it. `expr` prints a value node's expression; the result is
|
|
226
226
|
* `local(p.result)` for the caller to return.
|
|
227
|
+
*
|
|
228
|
+
* `before` gives the statements a value node needs ahead of its own line,
|
|
229
|
+
* asked after `expr` has printed that node: locals of its own, named after
|
|
230
|
+
* `local(ref)`, for an expression that would otherwise repeat a texture read.
|
|
227
231
|
*/
|
|
228
|
-
export function printBody(
|
|
232
|
+
export function printBody(
|
|
233
|
+
p: Program, s: Structure, syntax: Syntax, expr: (ref: NodeRef) => string, indent: string,
|
|
234
|
+
before: (ref: NodeRef) => readonly string[] = () => [],
|
|
235
|
+
): string[] {
|
|
229
236
|
const lines: string[] = []
|
|
230
237
|
const loopRef = new Map<number, NodeRef>()
|
|
231
238
|
for (const [ref, n] of p.nodes.entries()) if (n.k === "loop") loopRef.set(n.id, ref)
|
|
@@ -270,8 +277,11 @@ export function printBody(p: Program, s: Structure, syntax: Syntax, expr: (ref:
|
|
|
270
277
|
lines.push(ind + syntax.value(ref, src.k === "loop" ? carried(n.src, n.index) : result(n.src, n.index)))
|
|
271
278
|
break
|
|
272
279
|
}
|
|
273
|
-
default:
|
|
274
|
-
|
|
280
|
+
default: {
|
|
281
|
+
const e = expr(ref)
|
|
282
|
+
for (const line of before(ref)) lines.push(ind + line)
|
|
283
|
+
lines.push(ind + syntax.value(ref, e))
|
|
284
|
+
}
|
|
275
285
|
}
|
|
276
286
|
}
|
|
277
287
|
}
|
package/src/web.ts
CHANGED
|
@@ -114,6 +114,8 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
114
114
|
const splat = (ref: number, t: SLType) => (typeOf(ref) === 1 && t > 1 ? `${T(t)}(n${ref})` : `n${ref}`)
|
|
115
115
|
/** A literal at width `t`. */
|
|
116
116
|
const k = (v: number, t: SLType) => (t > 1 ? `${T(t)}(${lit(v)})` : lit(v))
|
|
117
|
+
/** The statements a node prints ahead of its own line (`printBody`'s `before`). */
|
|
118
|
+
const ahead = new Map<NodeRef, string[]>()
|
|
117
119
|
|
|
118
120
|
const expr = (ref: NodeRef): string => {
|
|
119
121
|
const n = valueAt(p.nodes, ref)
|
|
@@ -297,11 +299,25 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
297
299
|
: `textureLod(sl_Tex${slot}, ${a[0]}, ${level})`
|
|
298
300
|
// A fraction blends the level below and the level above,
|
|
299
301
|
// whatever the texture's filter between levels, as the HLSL
|
|
300
|
-
// emitter does (`isWhole`)
|
|
301
|
-
// the
|
|
302
|
+
// emitter does (`isWhole`), each level's colour weighed by its
|
|
303
|
+
// alpha, the plain blend where both are transparent. Past the
|
|
304
|
+
// smallest level both read the smallest, as a whole level past
|
|
305
|
+
// it does.
|
|
302
306
|
if (isWhole(p.nodes, n.args[1]!)) return at(a[1]!)
|
|
303
307
|
const below = `floor(${a[1]})`
|
|
304
|
-
|
|
308
|
+
const [lo, hi, mixed] = ["lo", "hi", "pm"].map((s) => `${local(ref)}_${s}`)
|
|
309
|
+
const pm = (v: string) => `${T(4)}(${v}.rgb * ${v}.a, ${v}.a)`
|
|
310
|
+
const decl = (v: string, e: string) => (W ? `let ${v}: ${T(4)} = ${e};` : `${T(4)} ${v} = ${e};`)
|
|
311
|
+
ahead.set(ref, [
|
|
312
|
+
decl(lo, at(below)),
|
|
313
|
+
decl(hi, at(`${below} + 1.0`)),
|
|
314
|
+
decl(mixed, `mix(${pm(lo)}, ${pm(hi)}, fract(${a[1]}))`),
|
|
315
|
+
])
|
|
316
|
+
const plain = `mix(${lo}.rgb, ${hi}.rgb, fract(${a[1]}))`
|
|
317
|
+
const rgb = W
|
|
318
|
+
? `select(${plain}, ${mixed}.rgb / ${mixed}.a, ${mixed}.a > 0.0)`
|
|
319
|
+
: `(${mixed}.a > 0.0 ? ${mixed}.rgb / ${mixed}.a : ${plain})`
|
|
320
|
+
return `${T(4)}(${rgb}, ${mixed}.a)`
|
|
305
321
|
}
|
|
306
322
|
case SLOP.TEXTURE_SIZE: {
|
|
307
323
|
const slot = Math.round(imm[0] ?? 0)
|
|
@@ -405,7 +421,7 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
405
421
|
loopOpen: "for (;;) {",
|
|
406
422
|
breakUnless: (c, turns, max) => `if (!(${c}) || ${turns} >= ${max}) break;`,
|
|
407
423
|
}
|
|
408
|
-
const body = printBody(p, shape, syntax, expr, " ").join("\n")
|
|
424
|
+
const body = printBody(p, shape, syntax, expr, " ", (ref) => ahead.get(ref) ?? []).join("\n")
|
|
409
425
|
const library = [...[...smoothsteps].sort().map((w) => smoothstep(w, lang)), ...librarySource(need, lang)].join("\n")
|
|
410
426
|
const header = `// GENERATED from a shader language program (${p.hash}). Do not edit.`
|
|
411
427
|
// Only the textures the program reads, and in WGSL only the samplers it
|