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 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, whether
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 three anchors must match arithmetic, not each other:
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) and `texture.sl` (a texture's orientation and sRGB decode).
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 eight lowercase hex characters
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
- - **`SL_IR_VERSION`** (`ir.ts`) is on every `Program` and in the hash, and is
318
- bumped whenever an opcode, a shape or what one computes changes. Because it
319
- is in the hash, a bump recompiles every cached shader. `toJSON` and
320
- `fromJSON` (`serial.ts`) are the IR as JSON for a host that stores programs;
321
- `fromJSON` checks everything an emitter relies on, refuses a newer version,
322
- and migrates an older one.
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
- There is none in the IR, deliberately. `sl.select`, `sl.step`, `sl.smoothstep` and
330
- `sl.mix` cover branching without branching, and `sl.repeat(n, body, seed)`
331
- unrolls at record time because `n` is a JavaScript number.
332
-
333
- `repeat` is honest about being a macro rather than a loop. It covers fbm,
334
- layered noise and small iterated distance fields, which is most of what 2D
335
- shaders loop for. A loop with a runtime count is `Specs/SL_NEXT.md` proposal
336
- 3b. Because it unrolls, the count multiplies the body's operation count.
337
- `compile` has no ceiling, so a long program costs what it costs, as any shader
338
- does; `corpus/long.sl` is over a thousand operations.
339
-
340
- Every loop that reaches a GPU is therefore bounded by a constant: `repeat` is
341
- unrolled, fbm's octaves are a constant 1 to 4, the helper loops in the noise
342
- and Voronoi functions have fixed trip counts. No program can hang a GPU today,
343
- so the compiled backends carry no loop cap. A data dependent loop (the
344
- raymarching tier) would need one emitted into every loop it prints, since a GPU
345
- reset takes the whole page's device, Unity's included.
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