onejs-sl 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +70 -28
- package/goldens/goldens.json +147 -63
- package/package.json +1 -1
- package/src/body.ts +132 -26
- package/src/core.ts +1 -1
- package/src/hlsl.ts +7 -3
- package/src/ir.ts +293 -33
- package/src/lang/README.md +2 -2
- package/src/lang/ast.ts +37 -7
- package/src/lang/builtins.ts +5 -17
- package/src/lang/check.ts +179 -70
- package/src/lang/glsl.ts +6 -10
- package/src/lang/lexer.ts +30 -6
- package/src/lang/lower.ts +1034 -289
- package/src/lang/parser.ts +146 -68
- package/src/lang/words.ts +5 -6
- package/src/ops.ts +48 -4
- package/src/params.ts +2 -0
- package/src/serial.ts +100 -17
- package/src/sl.ts +343 -33
- package/src/structure.ts +285 -0
- package/src/web.ts +152 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
Real control flow: an `if` runs only the side it picks, a loop that cannot unroll runs as a loop, and `int`, `uint` and `bool` are types. A program with an `if` statement gets a new hash, which a build regenerates; every other program keeps its hash and its shader.
|
|
6
|
+
|
|
7
|
+
- `if` is a real branch, and `return` works anywhere
|
|
8
|
+
- `while`, `break`, `continue` and `switch`; a case never falls through
|
|
9
|
+
- A `for` unrolls when its turns are known, 64 or fewer, and nothing leaves early; any other loop is real, with a turn limit
|
|
10
|
+
- `int`, `uint` and `bool`; two ints divide toward zero; bit operators and `5u` literals
|
|
11
|
+
- Braces on their own are a block with its own scope
|
|
12
|
+
- A local declared without a value holds zero
|
|
13
|
+
- `sl.branch` and `sl.loop` record control flow from code
|
|
14
|
+
- IR 4: `if`, `loop`, `param` and `proj` nodes and int, uint and bool values; a program without them stays IR 3
|
|
15
|
+
- `fromGLSL` keeps `int`, `uint` and `bool`
|
|
16
|
+
|
|
17
|
+
## 0.4.0
|
|
18
|
+
|
|
19
|
+
Every hash changes once, so a program built in code is recorded again: run the app in the editor once, then build. A program from a `.sl` file needs only the app rebuilt. A host reading programs as JSON reads 0.3.0's files as before.
|
|
20
|
+
|
|
21
|
+
- Every hash changes once: `a + b` and `b + a` share one, as do `8 * uv` and `uv * 8` (`SL_HASH_VERSION` 2)
|
|
22
|
+
- A generated shader names its hash scheme on a line of its own (`// SL_HASH_VERSION 2`); `SL_HASH_VERSION` is exported from `onejs-sl/core`
|
|
23
|
+
- `tex2Dlod(t, uv, lod)` samples a mip level; `sampleLevel` on `sl.texture`
|
|
24
|
+
- `BodyTarget.sampleLevel` is required, for `tex2Dlod`
|
|
25
|
+
- `fromGLSL` turns `textureLod` into `tex2Dlod`
|
|
26
|
+
- A shape's parameters may be any value, a uniform included; a vector counts as its components
|
|
27
|
+
- `fbm`, `turbulence` and `ridged` take an octave count that is a value, rounded and held to 1 to 4
|
|
28
|
+
- IR 3: a shape's parameters and an octave count are always operands; `fromJSON` migrates an older file
|
|
29
|
+
- `toJSON` writes the hash scheme (`h`), so `fromJSON` rehashes a file an older scheme wrote instead of refusing it
|
|
30
|
+
- A ramp's stop may be a colour uniform or a const holding a hex, as well as a hex
|
|
31
|
+
|
|
3
32
|
## 0.3.0
|
|
4
33
|
|
|
5
34
|
The VM is gone: every program is compiled, so there is no instruction buffer, wire version or VM limit left to import. A host on `compile` changes nothing; one still importing `onejs-sl/vm` or `onejs-sl/limits` moves to `onejs-sl/compile` and `onejs-sl/core`.
|
package/README.md
CHANGED
|
@@ -51,7 +51,8 @@ only what it calls.
|
|
|
51
51
|
HLSL and Metal shared subset (HLSL spelling, `fmod`, no `mul`, no `static`, no
|
|
52
52
|
derivatives, no swizzle of a scalar, and every literal and scalar in an
|
|
53
53
|
intrinsic at its exact type, since Metal overloads where HLSL converts). The `BodyTarget` says what differs between hosts: an expression
|
|
54
|
-
for each input, the float4 holding a uniform slot, a texture sample
|
|
54
|
+
for each input, the float4 holding a uniform slot, a texture sample and a
|
|
55
|
+
sample at a mip level (`sampleLevel`, for `tex2Dlod`), whether
|
|
55
56
|
`toLinear` is real (`colour: "linear"`) or the identity (`"gamma"`), and
|
|
56
57
|
optionally a local to assign the result to. It returns the uniform and texture
|
|
57
58
|
slots the body uses and the library functions it calls. The sample's contract
|
|
@@ -131,9 +132,13 @@ what a WebGL player draws.
|
|
|
131
132
|
|
|
132
133
|
`npm run goldens` draws it on WebGPU and WebGL2 in a Chrome with its own
|
|
133
134
|
profile (set `CHROME` to choose one). The two backends must agree within 1/255
|
|
134
|
-
over every pixel, and
|
|
135
|
+
over every pixel, and seven anchors must match arithmetic, not each other:
|
|
135
136
|
`orient.sl` (orientation, and the linear to sRGB store), `hex.sl` (a hex colour
|
|
136
|
-
stores as written)
|
|
137
|
+
stores as written), `texture.sl` (a texture's orientation and sRGB decode) and
|
|
138
|
+
`lod.sl` (each mip level `tex2Dlod` reads, the texture's smaller levels being
|
|
139
|
+
solid colours). `sdf-values.sl`, `fbm-values.sl` and `ramp-values.sl` are black unless a
|
|
140
|
+
shape, a noise or a ramp draws differently with its parameters as values than
|
|
141
|
+
as constants.
|
|
137
142
|
The whole translated library must also compile on both backends, including
|
|
138
143
|
the functions no fixture reaches. The file describes the sampling grid, the
|
|
139
144
|
times and the texture every sampled slot gets. A host imports it as
|
|
@@ -305,7 +310,11 @@ So it is a Merkle hash over the graph reachable from the result, not a walk of
|
|
|
305
310
|
the node array. An earlier version hashed storage order, which meant hoisting a
|
|
306
311
|
shared subexpression into a `const` changed the hash without changing what the
|
|
307
312
|
program computed. Constants go through a fixed precision, so `0.1 + 0.2` and
|
|
308
|
-
`0.3` do not produce different shaders. It is
|
|
313
|
+
`0.3` do not produce different shaders. It is normalised, so two ways of
|
|
314
|
+
writing one computation share a hash: `a + b` and `b + a` (and `*`, `dot`,
|
|
315
|
+
`distance`), and `8 * uv`, whose 8 is a broadcast, and `uv * 8`, whose 8 is a
|
|
316
|
+
float2. `min` and `max` keep their order, since which of -0 and +0 they return
|
|
317
|
+
depends on it. It is eight lowercase hex characters
|
|
309
318
|
from FNV-1a, chosen so a C# implementation can produce the same string rather
|
|
310
319
|
than for any cryptographic reason.
|
|
311
320
|
|
|
@@ -314,35 +323,62 @@ than for any cryptographic reason.
|
|
|
314
323
|
Two numbers, with one rule: a reader accepts every version up to its own and
|
|
315
324
|
refuses a newer one with a message naming both.
|
|
316
325
|
|
|
317
|
-
- **`
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
326
|
+
- **`SL_HASH_VERSION`** (`ir.ts`) is bumped when the hashing scheme changes,
|
|
327
|
+
which changes every hash. 2 is the normalised hash above. `toJSON` writes it
|
|
328
|
+
beside the hash, and `fromJSON` checks a file's hash only when it was written
|
|
329
|
+
under the same scheme and IR version, and otherwise rehashes it.
|
|
330
|
+
|
|
331
|
+
- **`SL_IR_VERSION`** (`ir.ts`) is bumped whenever an opcode, a shape or what
|
|
332
|
+
one computes changes. A program carries the lowest version that has all its
|
|
333
|
+
nodes (`programVersion`), and that is what its hash carries, so a bump
|
|
334
|
+
rehashes only the programs using what it added: every other program keeps
|
|
335
|
+
the hash its shader was recorded under. `toJSON` and `fromJSON`
|
|
336
|
+
(`serial.ts`) are the IR as JSON for a host that stores programs; `fromJSON`
|
|
337
|
+
checks everything an emitter relies on, refuses a newer version, and
|
|
338
|
+
migrates an older one.
|
|
323
339
|
IR 2 came with #129. A shape takes as many parameters as it reads
|
|
324
340
|
(`SL_SDF_PARAMS`, six at most, and never fewer than four accepted), where it
|
|
325
|
-
used to take four and lose the rest.
|
|
341
|
+
used to take four and lose the rest. IR 3 added `SAMPLE_LOD`, and made an
|
|
342
|
+
SDF's shape parameters and a noise's octave count operands, where they were
|
|
343
|
+
immediates, so they can be any value; a constant one still prints as its
|
|
344
|
+
number. `fromJSON` moves an older file's immediates to operands. IR 4 added
|
|
345
|
+
control flow (the `if` and `loop` nodes, a loop's `param`s and each result's
|
|
346
|
+
`proj`) and int, uint and bool values (a node's `kind`). A program that uses
|
|
347
|
+
none of them is still IR 3 and keeps its hash; one with an `if` statement is
|
|
348
|
+
IR 4, since the statement is now a real branch rather than a select.
|
|
326
349
|
|
|
327
350
|
## Control flow
|
|
328
351
|
|
|
329
|
-
|
|
330
|
-
`
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
`
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
352
|
+
IR 4 has it: an `if` node runs one of its two regions and gives back their
|
|
353
|
+
results, and a `loop` node carries its values through turns while its
|
|
354
|
+
condition holds. `sl.branch(cond, whenTrue, whenFalse)` and
|
|
355
|
+
`sl.loop(init, cond, body, max)` record them, and the text form lowers `if`,
|
|
356
|
+
`for`, `while`, `break`, `continue`, `switch` and a `return` anywhere onto the
|
|
357
|
+
two (`src/lang/lower.ts` says how: a return or break that only some ways
|
|
358
|
+
through reach is a flag, and what follows runs under a branch on it). No
|
|
359
|
+
emitter ever prints a statement that returns or breaks from inside a region;
|
|
360
|
+
every one prints the same structured body.
|
|
361
|
+
|
|
362
|
+
`structure.ts` decides where each node is computed: in the innermost region
|
|
363
|
+
every use of it is inside. A value only a branch needs is computed only when
|
|
364
|
+
the branch runs, one only a loop's body reads is computed each turn, and one
|
|
365
|
+
needed on both sides is computed once, before either. It also marks the
|
|
366
|
+
regions whose flow differs between neighbouring pixels, where a texture
|
|
367
|
+
sample has no neighbours to take its mip level from; every emitter samples
|
|
368
|
+
level 0 there, so every backend draws the same picture.
|
|
369
|
+
|
|
370
|
+
A `for` in the text form still unrolls when its turns are known, are 64 or
|
|
371
|
+
fewer, and nothing in it leaves early, and `sl.repeat(n, body, seed)` still
|
|
372
|
+
unrolls at record time because `n` is a JavaScript number. Everything else is
|
|
373
|
+
a real loop.
|
|
374
|
+
|
|
375
|
+
Every real loop has a turn limit, `max`, emitted into the loop itself: the
|
|
376
|
+
count a constant bound gives, the `[Range]` maximum of a uniform bound, or
|
|
377
|
+
1024 when the bound says neither. A loop that reaches it leaves as if its
|
|
378
|
+
condition had failed. That is what keeps a data dependent loop, a ray march
|
|
379
|
+
say, from hanging a GPU, whose reset takes the whole page's device, Unity's
|
|
380
|
+
included. `Tools/sl-loop-timing` in the container times a loop that breaks
|
|
381
|
+
early against the same maths unrolled, on each backend.
|
|
346
382
|
|
|
347
383
|
## See also
|
|
348
384
|
|
|
@@ -408,6 +444,12 @@ own for a vec4 built from raw components. Both backends implement it gamma
|
|
|
408
444
|
aware (`sl_toLinear` in `lib/common.hlsl`), so a Gamma project gets the value
|
|
409
445
|
as written. Alpha is coverage and is never converted. Same rule as `fx`.
|
|
410
446
|
|
|
447
|
+
A ramp's stop can also be a colour value that was written as one: a colour
|
|
448
|
+
uniform, or a const holding a hex. The ramp takes the value from under the
|
|
449
|
+
conversion that reading it applied (`writtenColour` in `ir.ts`), so it blends
|
|
450
|
+
as written like a hex stop. A value computed in linear light has no written
|
|
451
|
+
form to take, so it is refused, with lerp as the way to blend it.
|
|
452
|
+
|
|
411
453
|
## Noise
|
|
412
454
|
|
|
413
455
|
`sl.noise`, `sl.simplex`, `sl.fbm(p, octaves, base)`, `sl.turbulence` and
|