onejs-sl 0.6.0 → 0.8.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,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0
4
+
5
+ A fractional `tex2Dlod` level now blends the two levels either side on every backend. No existing program's hash changes, but a host now gets two whole-level samples and a blend where it got one fractional sample, so a host that blended levels itself would blend twice, which makes this a minor.
6
+
7
+ - A fractional `tex2Dlod` level blends the two levels either side, whatever the texture's filter mode
8
+
9
+ ## 0.7.0
10
+
11
+ A program can read what it drew the frame before, and count frames (`Specs/SL_NEXT.md` 4). Every existing program keeps its hash and its shader; one that reads the new names is IR 5. A host has to forward the new `reads` to draw such a program, which makes this a minor.
12
+
13
+ - `previous`, the frame this program drew last, read as `tex2D(previous, uv)`; `previous.sample(uv)` in `sl.program`
14
+ - `frame`, an int counting frames since `previous` was last cleared, and `deltaTime`, the seconds since the frame before
15
+ - A file that already used one of the three names keeps its own
16
+ - `readsOf(program)` and `compile(p).reads` say which of the three a host has to keep between frames
17
+ - `BodyTarget` takes `inputs.frame`, `inputs.deltaTime` and `previous(uv)`, all optional; `emitBody` names the one a program needs and a target lacks
18
+ - The Unity shader reads the step from `_Res.zw` and the previous frame from `_Prev`; the web frame from `sl_Res.w`, `sl_Opt.z` and its own binding
19
+ - IR 5: `SAMPLE_PREVIOUS` and the two inputs
20
+ - `fromGLSL` turns `iFrame` and `iTimeDelta` into `frame` and `deltaTime`
21
+ - The goldens step the programs that read the three, and hold the history to the bit
22
+ - The web emitters refuse an input they have no mapping for
23
+ - The shipped source is typechecked against ES2020 with no Node types, which found the parser's `Array.prototype.at`
24
+
3
25
  ## 0.6.0
4
26
 
5
27
  An int uniform says it is one, and uint constants fold as int ones do. A `.sl` program whose uint constants now fold gets a new hash and shader, which a build regenerates; every other program keeps its hash and its shader, and a program built in code keeps both, since the fold is the `.sl` file's. That hash change and `sl.uniform.int` make this a minor.
package/README.md CHANGED
@@ -27,6 +27,11 @@ const plasma = sl.program(({ uv, time }) => {
27
27
  })
28
28
  ```
29
29
 
30
+ `npm install onejs-sl`. The package ships TypeScript source (its `exports`
31
+ point at `src/*.ts`): bundle it (esbuild, Vite) or run it with a TypeScript
32
+ loader (`npx tsx script.ts`). Plain `node` cannot load `.ts` from
33
+ `node_modules`.
34
+
30
35
  ## Entry points
31
36
 
32
37
  `"sideEffects": false`, and each backend is its own entry, so a host bundles
@@ -43,7 +48,9 @@ only what it calls.
43
48
  | `onejs-sl/emit/web` | `emitWGSL`, `emitGLSL`: OneJS's web frame |
44
49
  | `onejs-sl/goldens.json` | the goldens (below), as data |
45
50
 
46
- `src/entries.test.ts` pins every name, since removing one breaks a host.
51
+ [`src/entries.test.ts`](https://github.com/Singtaa/onejs-sl/blob/main/src/entries.test.ts)
52
+ (in the repository, not the npm package) pins every name, since removing one
53
+ breaks a host.
47
54
 
48
55
  ## A host's own frame: `emitBody`
49
56
 
@@ -54,13 +61,21 @@ intrinsic at its exact type, since Metal overloads where HLSL converts). The `Bo
54
61
  for each input, the float4 holding a uniform slot, a texture sample and a
55
62
  sample at a mip level (`sampleLevel`, for `tex2Dlod`), whether
56
63
  `toLinear` is real (`colour: "linear"`) or the identity (`"gamma"`), and
57
- optionally a local to assign the result to. It returns the uniform and texture
64
+ optionally a local to assign the result to. A host that steps a program frame
65
+ by frame adds `frame` and `deltaTime` to its inputs and a `previous` read (below,
66
+ "What a program is given"); a target without one cannot draw a program that
67
+ reads it, and `emitBody` says which is missing rather than print a shader that
68
+ stands still. It returns the uniform and texture
58
69
  slots the body uses and the library functions it calls. The sample's contract
59
70
  is on `BodyTarget.sample`: straight alpha in and out, rgb in the space `colour`
60
71
  names (an sRGB texture decoded before filtering when it is `linear`), and
61
72
  filtering and wrapping left to the host, which OneJS's hosts take from the
62
- bound texture's own settings. OneJS's Unity shader is
63
- one frame over it (`hlsl.ts`); Magerie's compute kernel is another, and its
73
+ bound texture's own settings. Between mip levels the body decides instead: a
74
+ fractional `tex2Dlod` level blends the two levels either side by the fraction,
75
+ whatever the texture's filter mode, since the body asks for both whole levels
76
+ and blends them itself. A level it can prove whole (a whole constant, a
77
+ `floor`, an int) costs one sample, and a level past the smallest reads the
78
+ smallest. OneJS's Unity shader is one frame over it (`hlsl.ts`); Magerie's compute kernel is another, and its
64
79
  target is in `src/body.test.ts` so an opcode cannot change without the text
65
80
  Magerie compiles changing in front of a test.
66
81
 
@@ -83,7 +98,8 @@ program reads it as one, so a host shows a whole number field; a float uniform
83
98
  has no `kind`. Neither mark is in the hash, since the reads' conversion already
84
99
  is. `inputsUsed`
85
100
  says which inputs the result depends on, dead nodes aside, so a host knows
86
- whether a program animates.
101
+ whether a program animates, and `readsOf` (also `compile(p).reads`) says which
102
+ of the previous frame, `frame` and `deltaTime` it needs kept between frames.
87
103
 
88
104
  ## The helper library: one source
89
105
 
@@ -137,13 +153,23 @@ what a WebGL player draws.
137
153
 
138
154
  `npm run goldens` draws it on WebGPU and WebGL2 in a Chrome with its own
139
155
  profile (set `CHROME` to choose one). The two backends must agree within 1/255
140
- over every pixel, and seven anchors must match arithmetic, not each other:
156
+ over every pixel, and eight anchors must match arithmetic, not each other:
141
157
  `orient.sl` (orientation, and the linear to sRGB store), `hex.sl` (a hex colour
142
- stores as written), `texture.sl` (a texture's orientation and sRGB decode) and
158
+ stores as written), `texture.sl` (a texture's orientation and sRGB decode),
143
159
  `lod.sl` (each mip level `tex2Dlod` reads, the texture's smaller levels being
144
- solid colours). `sdf-values.sl`, `fbm-values.sl` and `ramp-values.sl` are black unless a
160
+ solid colours) and `lod-frac.sl` (a fractional level blends the two levels
161
+ either side, on a texture sampled nearest between levels). `sdf-values.sl`, `fbm-values.sl` and `ramp-values.sl` are black unless a
145
162
  shape, a noise or a ramp draws differently with its parameters as values than
146
163
  as constants.
164
+ A fixture that reads the previous frame, `frame` or `deltaTime` is stepped
165
+ instead of drawn at times: eight frames at 1/30 of a second, into an
166
+ `rgba16float` history pair that starts clear, each frame then copied into the
167
+ target as an element shows it. goldens.json holds its samples at frames 0, 1
168
+ and 7, as shown and as the raw history. Three are anchors on the history, to
169
+ the bit: `counter.sl` (a step taken twice, a missed swap or 8 bit history),
170
+ `drift.sl` (the previous frame read upside down) and `frame-delta.sl` (`frame`
171
+ and `deltaTime` as a host hands them over); `trail.sl` is the fading trail the
172
+ docs show.
147
173
  The whole translated library must also compile on both backends, including
148
174
  the functions no fixture reaches. The file describes the sampling grid, the
149
175
  times and the texture every sampled slot gets. A host imports it as
@@ -156,7 +182,8 @@ bits per pixel, and `corpus/probe-hash.sl` does the same through a program;
156
182
  every pixel must match `goldens/reference.mjs`, which computes the hashes in
157
183
  JavaScript. goldens.json ships each probe under its name, as HLSL, WGSL and
158
184
  GLSL ES and with the bits it must draw, for a host to run on its own compiler,
159
- and marks the fixtures compared at 0/255 `exact`.
185
+ and marks the fixtures compared at 0/255 `exact`, and the stepped fixtures
186
+ whose history is compared to the bit `historyExact`.
160
187
 
161
188
  `npm run goldens -- --check` draws everything again and compares it with
162
189
  goldens.json instead of writing it: within 1/255, and exact fixtures and probes
@@ -170,9 +197,12 @@ hash fails there until the goldens are drawn again.
170
197
  The source reaches for no host API: no `fs`, `process`, `Buffer`,
171
198
  `TextEncoder`, `performance`, `structuredClone` or `Intl`. It runs in a
172
199
  browser (the Play editor's diagnostics), a Cloudflare worker (the Play build)
173
- and QuickJS (Magerie). Two checks hold it to that:
200
+ and QuickJS (Magerie). Three checks hold it to that:
174
201
 
175
202
  - lint refuses those globals and any Node module in `src/`;
203
+ - `npm run typecheck` also checks the shipped source alone against ES2020 with
204
+ no Node types (`tsconfig.lib.json`), since `@types/node` adds later methods
205
+ such as `Array.prototype.at`, which 0.5.0 used by mistake;
176
206
  - `npm run test:quickjs` bundles `quickjs/corpus.ts` as one ES2020 IIFE, the
177
207
  way Magerie bundles a script, runs it in QuickJS-ng and in a bare Node
178
208
  context, and fails unless the two results agree byte for byte. The corpus
@@ -225,8 +255,9 @@ drew what had no shader yet; OneJS 3.8 deleted it.) `hlsl.ts` feeds OneJS's
225
255
  draws a program asks the compiled program for its
226
256
  `hlsl` (a lazy getter, never read in Play), records it into
227
257
  `Assets/OneJS/Recorded.sl.json`, generates the shader and
228
- moves the live material onto it. `manifest()` is still there for an app that
229
- would rather write its programs out at build time.
258
+ moves the live material onto it. `manifest()`, in `onejs-unity/sl` and
259
+ `onejs-unity/sl/compiler` rather than this package, is still there for an app
260
+ that would rather write its programs out at build time.
230
261
 
231
262
  **A third and fourth backend, for the browser.** A player cannot compile a
232
263
  shader, but the page it runs in can. `web.ts` prints every program as WGSL and
@@ -350,7 +381,9 @@ number. `fromJSON` moves an older file's immediates to operands. IR 4 added
350
381
  control flow (the `if` and `loop` nodes, a loop's `param`s and each result's
351
382
  `proj`) and int, uint and bool values (a node's `kind`). A program that uses
352
383
  none of them is still IR 3 and keeps its hash; one with an `if` statement is
353
- IR 4, since the statement is now a real branch rather than a select.
384
+ IR 4, since the statement is now a real branch rather than a select. IR 5 added
385
+ the previous frame (`SAMPLE_PREVIOUS`) and the `frame` and `deltaTime` inputs;
386
+ a program that reads none of them keeps the version and the hash it had.
354
387
 
355
388
  ## Control flow
356
389
 
@@ -405,6 +438,25 @@ program that does not compiles to the bytes it always did, and a program with a
405
438
  `texel` of its own keeps it: a local, a parameter or a uniform of that name
406
439
  shadows the input.
407
440
 
441
+ Three are for a program drawn frame after frame, and a host hands them over
442
+ only to a program that reads them (`readsOf`):
443
+
444
+ - `previous`, a texture: what this program drew the frame before, read as
445
+ `tex2D(previous, uv)`. It is the program's result exactly, linear and
446
+ straight alpha, kept at 16 bits a channel, filtered bilinear and clamped at
447
+ the edges, with one level (so `tex2Dlod` refuses it). A read at a pixel's own
448
+ `uv` is that pixel.
449
+ - `frame`, an int: frames drawn since `previous` was last cleared, from 0.
450
+ - `deltaTime`: seconds since the frame before, 0 on the first.
451
+
452
+ A host advances a frame when it draws with `time` moved forward: the last
453
+ result becomes `previous`, `frame` goes up by one and `deltaTime` is the step.
454
+ A draw at the same time draws the same frame again. It clears (`previous`
455
+ transparent black, `frame` and `deltaTime` 0) on the first draw, a new size, a
456
+ new program, a seek and time going back, so `frame` is 0 exactly when
457
+ `previous` is clear. A file that already used one of the three names for its
458
+ own value, texture or function keeps it.
459
+
408
460
  Why `resolution` and `aspect` are the target's: a program is drawn with
409
461
  `Graphics.Blit` into the element's own render texture, and Unity sets `_ScreenParams` per camera and
410
462
  leaves it alone for a blit: reading it from a 64x256 target answers with the