onejs-sl 0.1.13 → 0.2.1

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,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ A program built in code or read from JSON is refused past 16 uniforms or 4 textures, as a `.sl` file already was, instead of compiling and then drawing differently on different hosts. A host can read both caps.
6
+
7
+ ### Fixed
8
+
9
+ - 0.2.0 regression: `compile()` dropped `encode()`'s texture check, so a program built in code with a fifth texture compiled and then sampled nothing in the editor and a native player
10
+
11
+ ### Changed
12
+
13
+ - `sl.texture` and `sl.uniform` refuse past the caps where they are declared, in the parser's words
14
+ - `compile` and `fromJSON` refuse a program past either cap, however it was made
15
+ - `onejs-sl/core` exports `UNIFORM_SLOTS` (16) and `TEXTURE_SLOTS` (4), which the parser and the builder both read
16
+ - `MAX_TEXTURES` is a deprecated alias of `TEXTURE_SLOTS`, so 4 rather than 15
17
+ - `VM_UNIFORMS` and `VM_TEXTURES` are the same numbers, and go with the VM
18
+ - The parser's cap messages give OneJS's slots as the reason
19
+
20
+ ## 0.2.0
21
+
22
+ `compile()` replaces `encode()` for every host that draws compiled: it gives a program's hash, names, defaults and sources with no VM buffer and no budget, so a long program or one with many values at once compiles. Program hashes do not change.
23
+
24
+ - `onejs-sl/compile` exports `compile(program)`: hash, uniforms, defaults, textures, and lazy `hlsl`, `wgsl` and `glsl`
25
+ - `compile` never refuses a program for its length or for how many values it holds at once
26
+ - `encode` is `compile` plus the VM's buffer, and still applies the VM's limits
27
+ - `onejs-sl/vm` and `onejs-sl/limits` stay until the VM is deleted
28
+ - `corpus/long.sl`, over a thousand operations, is in the goldens
29
+
3
30
  ## 0.1.13
4
31
 
5
32
  Unity's Metal compile of OneJS's effects no longer warns of an uninitialized variable. Nothing draws differently.
package/README.md CHANGED
@@ -36,10 +36,11 @@ only what it calls.
36
36
  | Entry | Contents |
37
37
  |---|---|
38
38
  | `onejs-sl` | `parse` and `analyze`, `diagnose` (every error in a file, not just the first), `classify` (every token with its class, comments kept, never throws), `fromGLSL` (a pasted Shadertoy or WebGL shader as a `.sl` file, with notes on what did not carry over), the TypeScript form `sl`, the IR types, `SL_IR_VERSION`, `toJSON`/`fromJSON`, `SLParseError` (file, line, column, offset, length, the bare `text`, and a `fix` where one is certain) |
39
- | `onejs-sl/core` | the same without the parser: what a game needs at run time |
39
+ | `onejs-sl/core` | the same without the parser: what a game needs at run time. Also the caps on one program, `UNIFORM_SLOTS` (16) and `TEXTURE_SLOTS` (4), which the parser and the builder enforce and a host can read |
40
40
  | `onejs-sl/tables` | `BUILTINS`, `SL_HLSL`, `INPUTS`, `SL_SDF_SHAPES`, `SL_SDF_PARAMS`, `SL_KEYWORDS`, `SL_TYPES`, `TYPE_WIDTH`, `PRELUDE_NAMES`: what completion and highlighting read. `BUILTIN_PARAMS`, `SL_SDF_PARAM_NAMES` and `LIB_SIGNATURES` name every parameter, so an editor can show `lerp(x, y, s)`, and `BUILTIN_DOCS`, `INPUT_DOCS` and `PRELUDE_DOCS` give each a line for its tooltip |
41
- | `onejs-sl/limits` | `vmFit(program)`: whether the VM runs it, and why not, without encoding |
42
- | `onejs-sl/vm` | `encode`, `SL_WIRE_VERSION` |
41
+ | `onejs-sl/compile` | `compile(program)`: what a host draws a program with. Its hash, its uniform and texture names in slot order, their defaults, and `hlsl`, `wgsl` and `glsl` as lazy getters. No budget |
42
+ | `onejs-sl/limits` | `vmFit(program)`: whether the VM runs it, and why not. The VM's, until it is deleted |
43
+ | `onejs-sl/vm` | `encode`, `SL_WIRE_VERSION`: `compile` plus the VM's buffer. The VM's, until it is deleted |
43
44
  | `onejs-sl/emit/hlsl-body` | `emitBody`: a program as a function body for a host's own frame; `emitLibrary`: the library functions it calls |
44
45
  | `onejs-sl/emit/unity` | `emitShader`: the `.shader` a Unity editor generates, a frame over `emitBody` |
45
46
  | `onejs-sl/emit/web` | `emitWGSL`, `emitGLSL`: OneJS's web frame |
@@ -211,10 +212,11 @@ shaders, at build time, so the second prints HLSL for it.
211
212
 
212
213
  So the same source draws compiled from generated HLSL in the editor and in a
213
214
  native player, with no edit in between, and the VM stays behind a switch: OneJS
214
- leaves it out unless a project defines `ONEJS_SL_VM`. Both backends exist:
215
- `encode.ts` feeds OneJS's `Runtime/SL/SLProgramBridge.cs` and `FxProgram.shader`,
216
- and `hlsl.ts` feeds its `Editor/SLShaderGenerator.cs`. Nobody writes a manifest for the
217
- second: an editor that draws a program asks the encoded program for its
215
+ leaves it out unless a project defines `ONEJS_SL_VM`. `compile.ts` gives a host
216
+ everything it draws with and no VM buffer; `encode.ts` adds the buffer for
217
+ `FxProgram.shader`, and goes with the VM. `hlsl.ts` feeds OneJS's
218
+ `Editor/SLShaderGenerator.cs`. Nobody writes a manifest for it: an editor that
219
+ draws a program asks the compiled program for its
218
220
  `hlsl` (a lazy getter, never read in Play), records it into
219
221
  `Assets/OneJS/Recorded.sl.json`, generates the shader and
220
222
  moves the live material onto it. `manifest()` is still there for an app that
@@ -226,7 +228,7 @@ as GLSL ES 3.00 (carrying the library functions it calls, translated from
226
228
  `lib/*.hlsl`), and OneJS's
227
229
  `Plugins/WebGL/OneJSSLWeb.jslib` compiles whichever one Unity's device speaks
228
230
  and draws it into the element's target. A `.sl` import carries both strings,
229
- printed at build time; an `encode()` result has them as lazy getters, like
231
+ printed at build time; a `compile()` result has them as lazy getters, like
230
232
  `hlsl`. A WebGL player has no VM: the element draws nothing until the compiled
231
233
  program is ready, and nothing after a compile error, which the page reports.
232
234
  The host contract (the frame block, the
@@ -347,10 +349,10 @@ layered noise and small iterated distance fields, which is most of what 2D
347
349
  shaders loop for. A data dependent loop is out of scope: the VM would need a
348
350
  nested bounded loop with a dynamic trip count while codegen would handle it
349
351
  fine, and the two backends agreeing is the property the whole design protects.
350
- Because it unrolls, the count multiplies the body's operation count toward the
351
- VM's 256-instruction ceiling. The ceiling error names any `repeat` that fills a
352
- quarter of the budget or more, so the fix reads as "lower this count" rather
353
- than "fewer instructions".
352
+ Because it unrolls, the count multiplies the body's operation count. `compile`
353
+ has no ceiling, so a long program costs what it costs, as any shader does;
354
+ `corpus/long.sl` is over a thousand operations. `encode` still refuses past the
355
+ VM's 256, and names any `repeat` that fills a quarter of that.
354
356
 
355
357
  Every loop that reaches a GPU is therefore bounded by a constant: `repeat` is
356
358
  unrolled, fbm's octaves are a constant 1 to 4, the helper loops in the noise
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "generatedBy": "onejs-sl goldens/run.mjs",
3
- "package": "0.1.13",
3
+ "package": "0.2.1",
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": [
@@ -95,7 +95,7 @@
95
95
  },
96
96
  "ember.sl": {
97
97
  "hash": "44ba7cc3",
98
- "source": "// A fire, in one file: one texture, one loop, one function.\n//\n// Everything a Unity shader makes you write that is not the effect is absent.\n// No Properties block, no SubShader, no CGPROGRAM, no appdata or v2f, no\n// _Time.y. `uniform` declares the slot AND the name React binds against, and\n// `texture2D` does the same for a sampler.\n//\n// This program sits at exactly 8 of the VM's 8 registers, which is the real\n// ceiling and not a soft one: adding another live value here is refused when\n// the game is built rather than rendered wrong. That is the trade for a\n// program that runs interpreted in a browser and compiled after an eject from\n// the same file, with no edit in between.\ntexture2D smoke;\n\nuniform float heat = 0.55;\nuniform float2 source = float2(0.5, 0.08);\n\n// Layered noise: one texture, three scales, drifting.\n//\n// The loop UNROLLS at build time. `i` is a number the parser substitutes, so\n// both backends see straight line code and neither needs a branch, which is\n// also why the bound has to be a constant. The function inlines, so calling it\n// costs exactly what writing the three lines out would.\nfloat plume(float2 p, float t) {\n float s = 0;\n for (int i = 0; i < 3; i++) {\n s = s + tex2D(smoke, p * (1 + i * 1.9) - t).r * (0.6 / (1 + i));\n }\n return s;\n}\n\nfloat4 main() {\n float2 p = uv - source;\n // A cone: narrow across, tall up, brightest at the source.\n float cone = saturate(1 - length(p * float2(3, 1.1)));\n float fuel = saturate(cone * (0.5 + heat) * (plume(p, time * 0.4) + 0.35));\n\n // Colours as written. They are mixed in sRGB and converted once, so the\n // ramp reads as evenly as its swatches do.\n return ramp(fuel, #00000000, #b53205cc, #ff8a20, #ffe7b0);\n}\n",
98
+ "source": "// A fire, in one file: one texture, one loop, one function.\n//\n// Everything a Unity shader makes you write that is not the effect is absent.\n// No Properties block, no SubShader, no CGPROGRAM, no appdata or v2f, no\n// _Time.y. `uniform` declares the slot AND the name React binds against, and\n// `texture2D` does the same for a sampler.\n//\n// This program sits at exactly 8 of the VM's 8 registers, which is why it is\n// in the corpus: the VM refused a ninth live value. Compiled, there is no such\n// ceiling, and the same file draws the same in the editor, a player and a\n// browser, with no edit in between.\ntexture2D smoke;\n\nuniform float heat = 0.55;\nuniform float2 source = float2(0.5, 0.08);\n\n// Layered noise: one texture, three scales, drifting.\n//\n// The loop UNROLLS at build time. `i` is a number the parser substitutes, so\n// both backends see straight line code and neither needs a branch, which is\n// also why the bound has to be a constant. The function inlines, so calling it\n// costs exactly what writing the three lines out would.\nfloat plume(float2 p, float t) {\n float s = 0;\n for (int i = 0; i < 3; i++) {\n s = s + tex2D(smoke, p * (1 + i * 1.9) - t).r * (0.6 / (1 + i));\n }\n return s;\n}\n\nfloat4 main() {\n float2 p = uv - source;\n // A cone: narrow across, tall up, brightest at the source.\n float cone = saturate(1 - length(p * float2(3, 1.1)));\n float fuel = saturate(cone * (0.5 + heat) * (plume(p, time * 0.4) + 0.35));\n\n // Colours as written. They are mixed in sRGB and converted once, so the\n // ramp reads as evenly as its swatches do.\n return ramp(fuel, #00000000, #b53205cc, #ff8a20, #ffe7b0);\n}\n",
99
99
  "samples": {
100
100
  "0": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,6,2,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,1,0,15,16,2,0,22,15,2,0,22,7,1,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,1,0,13,23,3,0,30,32,5,0,38,31,5,0,37,25,3,0,31,6,1,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,21,2,0,27,38,6,0,45,46,8,0,53,46,8,0,53,42,7,0,48,26,3,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,1,0,14,34,5,0,40,52,10,0,59,61,13,0,69,61,13,0,69,58,12,0,66,45,8,0,52,6,1,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,2,0,25,46,8,0,53,66,14,1,74,75,17,1,85,75,18,1,85,73,16,1,82,63,13,0,71,27,3,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,30,4,0,36,58,12,0,66,78,18,1,88,89,22,1,100,90,22,1,101,88,21,1,99,80,19,1,90,46,8,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,0,10,40,6,0,46,69,15,1,78,91,22,1,102,103,26,1,116,104,26,1,116,102,25,1,115,95,23,1,107,62,13,0,70,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,2,0,18,48,9,0,55,79,19,1,89,103,26,1,116,117,30,1,131,117,30,1,132,116,30,1,130,110,28,1,123,77,18,1,87,12,2,0,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,2,0,25,56,11,0,63,88,21,1,99,115,30,1,129,131,34,1,147,131,34,1,147,129,34,1,145,123,32,1,138,90,22,1,101,26,3,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,3,0,30,62,13,0,70,96,24,1,108,126,33,1,141,144,38,1,162,144,38,1,162,141,37,1,158,134,35,1,151,101,25,1,114,37,6,0,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,4,0,35,67,15,1,76,103,26,1,116,135,36,1,152,157,42,2,177,157,42,2,177,151,41,2,170,144,38,1,162,110,28,1,124,46,8,0,53,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,5,0,38,71,16,1,80,108,27,1,122,143,38,1,161,170,46,2,192,169,46,2,190,159,43,2,179,151,40,2,170,117,30,1,132,52,10,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,5,0,40,74,16,1,83,111,29,1,125,148,40,2,167,181,50,2,204,178,48,2,201,165,44,2,186,155,42,2,175,121,31,1,136,57,11,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35,5,0,41,74,17,1,84,113,29,1,127,151,40,2,170,184,53,2,206,182,50,2,205,167,45,2,188,157,42,2,177,123,32,1,138,58,12,0,66,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,5,0,41,74,17,1,83,112,29,1,126,149,40,2,168,182,51,2,205,180,49,2,203,165,45,2,187,156,42,2,176,122,32,1,137,57,11,0,65,0,0,0,0,0,0,0,0,0,0,0,0],
101
101
  "1.25": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,6,2,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,1,0,15,16,2,0,22,15,2,0,21,5,1,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,1,0,13,23,3,0,30,32,5,0,38,30,4,0,37,20,2,0,26,3,1,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,21,2,0,27,38,6,0,45,46,8,0,53,45,8,0,52,34,5,0,41,16,2,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,1,0,14,34,5,0,40,52,10,0,59,61,13,0,69,60,13,0,68,48,9,0,55,29,4,0,35,2,1,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,2,0,25,46,8,0,53,66,14,1,74,75,17,1,85,74,17,1,83,61,13,0,70,42,7,0,48,15,2,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,30,4,0,36,58,12,0,66,78,18,1,88,89,22,1,100,88,21,1,99,74,17,1,84,53,10,0,61,27,3,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,1,0,10,40,6,0,46,69,15,1,78,91,22,1,102,103,26,1,116,101,25,1,114,86,21,1,97,64,14,0,73,38,6,0,44,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,2,0,18,48,9,0,55,79,19,1,89,103,26,1,116,117,30,1,131,115,30,1,129,98,24,1,110,74,17,1,84,48,9,0,55,6,1,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,2,0,25,56,11,0,63,88,21,1,99,115,30,1,129,131,34,1,147,128,34,1,144,109,28,1,122,83,20,1,94,56,11,0,64,16,2,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,3,0,30,62,13,0,70,96,24,1,108,126,33,1,141,144,38,1,162,141,37,1,159,119,31,1,134,91,22,1,103,63,13,0,72,24,3,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,4,0,35,67,15,1,76,103,26,1,116,135,36,1,152,157,42,2,177,154,41,2,173,128,34,1,144,98,24,1,110,69,15,1,78,31,4,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,5,0,38,71,16,1,80,108,27,1,122,143,38,1,161,170,46,2,192,165,45,2,186,135,36,1,152,103,26,1,115,74,16,1,83,35,6,0,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,5,0,40,74,16,1,83,111,29,1,125,148,40,2,167,181,50,2,204,174,47,2,197,139,37,1,157,106,27,1,119,76,18,1,86,38,6,0,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35,5,0,41,74,17,1,84,113,29,1,127,151,40,2,170,184,53,2,206,179,49,2,202,141,37,1,159,107,27,1,120,77,18,1,87,39,6,0,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,5,0,41,74,17,1,83,112,29,1,126,149,40,2,168,182,51,2,205,176,48,2,198,140,37,1,157,106,27,1,119,77,18,1,86,39,6,0,45,0,0,0,0,0,0,0,0,0,0,0,0]
@@ -118,6 +118,14 @@
118
118
  "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]
119
119
  }
120
120
  },
121
+ "long.sl": {
122
+ "hash": "c563091c",
123
+ "source": "// Longer than the VM ever ran: the loop unrolls to several hundred\n// instructions, past the 256 the VM had room for, so only a compiled backend\n// can draw it. Forty eight soft rings on a circle, each tinted by its place.\n// Smooth everywhere, so every backend agrees on it to within a level.\nfloat4 main() {\n float2 p = (uv - 0.5) * 2.0;\n float3 c = 0.0;\n for (int i = 0; i < 48; i++) {\n float a = i * 0.1309;\n float2 q = p - float2(0.6 * cos(a), 0.6 * sin(a));\n float ring = smoothstep(0.06, 0.0, abs(length(q) - 0.2));\n c = c + ring * float3(0.5 + 0.5 * cos(i * 0.4), 0.5 + 0.5 * sin(i * 0.3), 0.6) * 0.12;\n }\n return float4(saturate(c), 1.0);\n}\n",
124
+ "samples": {
125
+ "0": [0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,18,0,12,255,109,37,94,255,119,74,117,255,94,95,115,255,47,79,82,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,151,20,122,255,127,35,106,255,116,48,103,255,93,63,93,255,73,86,96,255,67,92,97,255,43,125,112,255,15,121,101,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,139,58,123,255,118,45,103,255,99,41,88,255,111,48,99,255,96,74,101,255,74,82,91,255,71,93,96,255,49,94,84,255,51,115,97,255,65,150,120,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,109,92,122,255,101,71,103,255,85,51,83,255,115,50,103,255,142,48,121,255,153,86,145,255,106,129,143,255,68,127,117,255,64,109,95,255,72,113,95,255,82,125,102,255,76,109,87,255,0,0,0,255,0,0,0,255,0,0,0,255,5,10,12,255,82,94,106,255,74,76,88,255,95,79,103,255,163,74,146,255,3,0,2,255,0,0,0,255,0,0,0,255,3,61,50,255,90,164,134,255,88,123,104,255,90,117,100,255,123,141,119,255,0,0,0,255,0,0,0,255,0,0,0,255,29,101,94,255,64,103,103,255,77,92,99,255,88,113,121,255,2,1,2,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,77,100,81,255,113,123,109,255,91,103,90,255,117,112,104,255,33,29,26,255,0,0,0,255,0,0,0,255,21,138,117,255,57,103,93,255,63,114,101,255,55,163,145,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,145,147,122,255,118,125,101,255,108,111,91,255,86,66,68,255,0,0,0,255,0,0,0,255,42,142,115,255,61,116,96,255,67,107,91,255,83,175,143,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,143,132,120,255,97,96,85,255,123,115,102,255,79,72,61,255,0,0,0,255,0,0,0,255,48,104,82,255,68,118,97,255,74,115,96,255,107,139,117,255,54,60,50,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,116,128,116,255,111,118,106,255,115,103,104,255,123,118,106,255,13,13,9,255,0,0,0,255,0,0,0,255,0,0,0,255,104,137,112,255,81,98,84,255,99,103,95,255,157,138,134,255,101,64,81,255,0,0,0,255,0,0,0,255,39,80,116,255,89,107,116,255,92,101,94,255,88,98,90,255,141,149,127,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,108,120,101,255,105,108,97,255,105,94,95,255,117,96,104,255,118,77,109,255,115,56,122,255,81,49,120,255,63,68,106,255,63,71,94,255,63,75,81,255,92,108,107,255,50,57,51,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,146,127,120,255,121,96,102,255,112,85,100,255,97,65,90,255,88,50,101,255,65,44,85,255,67,58,104,255,49,64,90,255,66,89,107,255,63,86,92,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,110,75,87,255,141,85,119,255,108,58,104,255,81,39,91,255,72,38,102,255,53,47,106,255,34,69,127,255,10,34,51,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,28,6,26,255,59,10,68,255,39,2,61,255,1,1,9,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255],
126
+ "1.25": [0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,18,0,12,255,109,37,94,255,119,74,117,255,94,95,115,255,47,79,82,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,151,20,122,255,127,35,106,255,116,48,103,255,93,63,93,255,73,86,96,255,67,92,97,255,43,125,112,255,15,121,101,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,139,58,123,255,118,45,103,255,99,41,88,255,111,48,99,255,96,74,101,255,74,82,91,255,71,93,96,255,49,94,84,255,51,115,97,255,65,150,120,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,109,92,122,255,101,71,103,255,85,51,83,255,115,50,103,255,142,48,121,255,153,86,145,255,106,129,143,255,68,127,117,255,64,109,95,255,72,113,95,255,82,125,102,255,76,109,87,255,0,0,0,255,0,0,0,255,0,0,0,255,5,10,12,255,82,94,106,255,74,76,88,255,95,79,103,255,163,74,146,255,3,0,2,255,0,0,0,255,0,0,0,255,3,61,50,255,90,164,134,255,88,123,104,255,90,117,100,255,123,141,119,255,0,0,0,255,0,0,0,255,0,0,0,255,29,101,94,255,64,103,103,255,77,92,99,255,88,113,121,255,2,1,2,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,77,100,81,255,113,123,109,255,91,103,90,255,117,112,104,255,33,29,26,255,0,0,0,255,0,0,0,255,21,138,117,255,57,103,93,255,63,114,101,255,55,163,145,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,145,147,122,255,118,125,101,255,108,111,91,255,86,66,68,255,0,0,0,255,0,0,0,255,42,142,115,255,61,116,96,255,67,107,91,255,83,175,143,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,143,132,120,255,97,96,85,255,123,115,102,255,79,72,61,255,0,0,0,255,0,0,0,255,48,104,82,255,68,118,97,255,74,115,96,255,107,139,117,255,54,60,50,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,116,128,116,255,111,118,106,255,115,103,104,255,123,118,106,255,13,13,9,255,0,0,0,255,0,0,0,255,0,0,0,255,104,137,112,255,81,98,84,255,99,103,95,255,157,138,134,255,101,64,81,255,0,0,0,255,0,0,0,255,39,80,116,255,89,107,116,255,92,101,94,255,88,98,90,255,141,149,127,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,108,120,101,255,105,108,97,255,105,94,95,255,117,96,104,255,118,77,109,255,115,56,122,255,81,49,120,255,63,68,106,255,63,71,94,255,63,75,81,255,92,108,107,255,50,57,51,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,146,127,120,255,121,96,102,255,112,85,100,255,97,65,90,255,88,50,101,255,65,44,85,255,67,58,104,255,49,64,90,255,66,89,107,255,63,86,92,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,110,75,87,255,141,85,119,255,108,58,104,255,81,39,91,255,72,38,102,255,53,47,106,255,34,69,127,255,10,34,51,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,28,6,26,255,59,10,68,255,39,2,61,255,1,1,9,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255]
127
+ }
128
+ },
121
129
  "orient.sl": {
122
130
  "hash": "f3b299ff",
123
131
  "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,12 +1,13 @@
1
1
  {
2
2
  "name": "onejs-sl",
3
- "version": "0.1.13",
3
+ "version": "0.2.1",
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",
7
7
  "exports": {
8
8
  ".": "./src/index.ts",
9
9
  "./core": "./src/core.ts",
10
+ "./compile": "./src/compile.ts",
10
11
  "./tables": "./src/tables.ts",
11
12
  "./limits": "./src/limits.ts",
12
13
  "./vm": "./src/vm.ts",
package/src/compile.ts ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * `onejs-sl/compile`: a program as what a host needs to draw it.
3
+ *
4
+ * Its hash, its uniform and texture names in slot order, its declared defaults,
5
+ * and the program printed for each compiler that draws it: HLSL for a Unity
6
+ * editor to generate a shader from, WGSL and GLSL ES 3.00 for a browser to
7
+ * compile on Unity's own device.
8
+ *
9
+ * There is no budget. `encode` refuses a program past the VM's 256
10
+ * instructions or 8 registers, because the VM cannot run it; every backend
11
+ * here is a real compiler, so a long program costs what it costs, as any
12
+ * shader does. `Specs/SL_NEXT.md` section 0.5, step 3.
13
+ */
14
+
15
+ import { checkCaps, type Program } from "./ir"
16
+ import { emitShader } from "./hlsl"
17
+ import { emitGLSL, emitWGSL } from "./web"
18
+ import { uniformDefaults } from "./sl"
19
+
20
+ export interface Compiled {
21
+ /** The program's own hash: what a host finds its compiled shader by. */
22
+ hash: string
23
+ /**
24
+ * Uniform names in SLOT ORDER, so a host can set one by name.
25
+ *
26
+ * Every backend addresses a uniform by slot. Without the names the host had
27
+ * a name from the game and no way to turn it into a slot, so it set a
28
+ * material property instead and every uniform stayed at zero.
29
+ */
30
+ uniforms: string[]
31
+ /**
32
+ * Declared uniform defaults, four floats per slot in slot order.
33
+ *
34
+ * The generated shader writes these into its Properties block, so a
35
+ * compiled material starts at them, while the web host's uniform array
36
+ * starts at zero. Without carrying them across, an unset uniform was its
37
+ * default in the editor and 0 in the browser: one program, two pictures,
38
+ * nothing to see in either. The host seeds these right after binding and
39
+ * lets the caller's own `uniforms` write over them.
40
+ */
41
+ defaults: number[]
42
+ /**
43
+ * Texture names in SLOT order, so a host can bind one by name.
44
+ *
45
+ * The generated shader declares `_Tex0` and the web host binds by slot, so
46
+ * a host handed the name an author wrote had no way to reach either.
47
+ * Setting a material property called `grain` bound nothing, silently.
48
+ */
49
+ textures: string[]
50
+ /**
51
+ * The program as HLSL, for a host that can compile it.
52
+ *
53
+ * Lazy, and absent from enumeration: in Play nothing ever reads it, so the
54
+ * emitter never runs there. In an editor the host asks for it once per
55
+ * program it has no compiled shader for, records it, and generates the
56
+ * shader, which is how a game ends up compiled without anybody writing a
57
+ * manifest.
58
+ */
59
+ readonly hlsl: string
60
+ /**
61
+ * The program as WGSL and as GLSL ES 3.00, for a browser to compile on
62
+ * Unity's own device (WebGPU and WebGL2 respectively). Lazy and absent
63
+ * from enumeration like `hlsl`: a `.sl` import carries them as plain
64
+ * strings from the build instead, and a host reads only the one its
65
+ * backend needs.
66
+ */
67
+ readonly wgsl: string
68
+ readonly glsl: string
69
+ }
70
+
71
+ export function compile(program: Program): Compiled {
72
+ checkCaps(program)
73
+ const compiled = {
74
+ hash: program.hash,
75
+ // Slot order, which is declaration order: Builder.uniform pushes and
76
+ // uses the resulting index as the slot.
77
+ uniforms: program.uniforms.map((u) => u.name),
78
+ defaults: uniformDefaults(program),
79
+ textures: program.textures.map((t) => t.name),
80
+ } as Compiled
81
+ let hlsl: string | undefined
82
+ let wgsl: string | undefined
83
+ let glsl: string | undefined
84
+ Object.defineProperty(compiled, "hlsl", {
85
+ enumerable: false,
86
+ get: () => (hlsl ??= emitShader(program)),
87
+ })
88
+ Object.defineProperty(compiled, "wgsl", {
89
+ enumerable: false,
90
+ get: () => (wgsl ??= emitWGSL(program)),
91
+ })
92
+ Object.defineProperty(compiled, "glsl", {
93
+ enumerable: false,
94
+ get: () => (glsl ??= emitGLSL(program)),
95
+ })
96
+ return compiled
97
+ }
package/src/core.ts CHANGED
@@ -16,7 +16,7 @@ export {
16
16
  export type {
17
17
  SLType, InputName, NodeRef, SLNode, Program, UniformDecl, UniformControl, TextureDecl,
18
18
  } from "./ir"
19
- export { SLOP, SL_ARITY, SL_NAME, INPUT_ID, isSampling } from "./ops"
19
+ export { SLOP, SL_ARITY, SL_NAME, INPUT_ID, isSampling, UNIFORM_SLOTS, TEXTURE_SLOTS } from "./ops"
20
20
  export type { SLOpCode } from "./ops"
21
21
  export { SL_SDF_SHAPES, SL_SDF_PARAMS } from "./shapes"
22
22
  export type { SlSdfKind } from "./shapes"
package/src/encode.ts CHANGED
@@ -24,12 +24,10 @@
24
24
  */
25
25
 
26
26
  import {
27
- MAX_TEXTURES, SLError, TYPE, reachable, type NodeRef, type Program, type SLNode, type SLType,
27
+ SLError, TYPE, reachable, type NodeRef, type Program, type SLNode, type SLType,
28
28
  } from "./ir"
29
- import { INPUT_ID, SLOP } from "./ops"
30
- import { emitShader } from "./hlsl"
31
- import { emitGLSL, emitWGSL } from "./web"
32
- import { uniformDefaults } from "./sl"
29
+ import { INPUT_ID, SLOP, TEXTURE_SLOTS } from "./ops"
30
+ import { compile, type Compiled } from "./compile"
33
31
 
34
32
  // Lives in ops.ts with the other wire constants; re-exported so nothing that
35
33
  // reached it through the encoder has to move.
@@ -73,7 +71,12 @@ export const MAX_INSTRUCTIONS = 256
73
71
  */
74
72
  export const TEXELS_PER_INSTRUCTION = 2
75
73
 
76
- export interface Encoded {
74
+ /**
75
+ * A program as the VM's buffer, plus everything `compile` gives a host.
76
+ *
77
+ * The metadata and the lazy sources are `compile`'s, so the two cannot drift.
78
+ */
79
+ export interface Encoded extends Compiled {
77
80
  /** Flat RGBA float data, `TEXELS_PER_INSTRUCTION * 4` numbers per instruction. */
78
81
  data: Float32Array
79
82
  instructions: number
@@ -81,60 +84,11 @@ export interface Encoded {
81
84
  resultRegister: number
82
85
  /** Registers actually used, for the shader to size nothing and for reporting. */
83
86
  registersUsed: number
84
- /**
85
- * Uniform names in SLOT ORDER, so a host can set one by name.
86
- *
87
- * The VM addresses uniforms by slot and knows nothing about names, and the
88
- * encoded instructions carry the slot only. Without this the host had a
89
- * name from the game and no way to turn it into a slot, so it set a
90
- * material property instead and every uniform stayed at zero.
91
- */
92
- uniforms: string[]
93
- /**
94
- * Declared uniform defaults, four floats per slot in slot order.
95
- *
96
- * The generated shader writes these into its Properties block, so a
97
- * compiled material starts at them, while the VM's uniform array starts at
98
- * zero. Without carrying them across, an unset uniform was its default
99
- * after an eject and 0 in the browser: one program, two pictures, nothing
100
- * to see in either. The host seeds these right after uploading and lets
101
- * the caller's own `uniforms` write over them.
102
- */
103
- defaults: number[]
104
- /**
105
- * Texture names in SLOT order, so a host can bind one by name.
106
- *
107
- * The same reason the uniform names are here: the VM addresses a texture by
108
- * slot and the generated shader declares `_Tex0`, so a host handed the name
109
- * an author wrote had no way to reach either. Setting a material property
110
- * called `grain` bound nothing, in both backends, silently.
111
- */
112
- textures: string[]
113
- hash: string
114
87
  /**
115
88
  * The lowest VM encoding that can run this buffer. A VM older than this
116
89
  * refuses the program; see `SL_WIRE_VERSION`.
117
90
  */
118
91
  wire: number
119
- /**
120
- * The program as HLSL, for a host that can compile it.
121
- *
122
- * Lazy, and absent from enumeration: in Play nothing ever reads it, so the
123
- * emitter never runs there. In an editor the host asks for it once per
124
- * program it has no compiled shader for, records it, and generates the
125
- * shader, which is how an ejected game ends up compiled without anybody
126
- * writing a manifest.
127
- */
128
- readonly hlsl: string
129
- /**
130
- * The program as WGSL and as GLSL ES 3.00, for a browser to compile on
131
- * Unity's own device (WebGPU and WebGL2 respectively). Lazy and absent
132
- * from enumeration like `hlsl`: a `.sl` import carries them as plain
133
- * strings from the build instead, and a host reads only the one its
134
- * backend needs.
135
- */
136
- readonly wgsl: string
137
- readonly glsl: string
138
92
  }
139
93
 
140
94
  interface Instr {
@@ -234,7 +188,7 @@ export function checkBudget(program: Program, order: NodeRef[]): void {
234
188
  )
235
189
  }
236
190
  for (const t of program.textures) {
237
- if (t.slot >= MAX_TEXTURES) throw new SLError(`texture "${t.name}" is past the sampler budget`)
191
+ if (t.slot >= TEXTURE_SLOTS) throw new SLError(`texture "${t.name}" is past the sampler budget`)
238
192
  }
239
193
  }
240
194
 
@@ -328,34 +282,15 @@ export function encode(source: Program): Encoded {
328
282
  data[o + 6] = ins.imm[2]; data[o + 7] = ins.imm[3]
329
283
  })
330
284
 
331
- const encoded = {
332
- data,
333
- instructions: out.length,
334
- resultRegister: reg.get(program.result)!,
335
- registersUsed: peak,
336
- // Slot order, which is declaration order: Builder.uniform pushes and
337
- // uses the resulting index as the slot.
338
- uniforms: program.uniforms.map((u) => u.name),
339
- defaults: uniformDefaults(program),
340
- textures: program.textures.map((t) => t.name),
341
- hash: program.hash,
342
- wire: wireOf(source, program),
343
- } as Encoded
344
- let hlsl: string | undefined
345
- let wgsl: string | undefined
346
- let glsl: string | undefined
347
- Object.defineProperty(encoded, "hlsl", {
348
- enumerable: false,
349
- get: () => (hlsl ??= emitShader(source)),
350
- })
351
- Object.defineProperty(encoded, "wgsl", {
352
- enumerable: false,
353
- get: () => (wgsl ??= emitWGSL(source)),
354
- })
355
- Object.defineProperty(encoded, "glsl", {
356
- enumerable: false,
357
- get: () => (glsl ??= emitGLSL(source)),
358
- })
285
+ // The hash, names, defaults and sources are compile's, from the SOURCE
286
+ // program: every compiled backend reads the IR's own SDF, and forVm keeps
287
+ // the hash, since nothing about what the program computes changed.
288
+ const encoded = compile(source) as Encoded
289
+ encoded.data = data
290
+ encoded.instructions = out.length
291
+ encoded.resultRegister = reg.get(program.result)!
292
+ encoded.registersUsed = peak
293
+ encoded.wire = wireOf(source, program)
359
294
  return encoded
360
295
  }
361
296
 
package/src/index.ts CHANGED
@@ -9,8 +9,9 @@
9
9
  * onejs-sl this: parse, diagnose, classify, fromGLSL, the TypeScript form, the IR, its JSON
10
10
  * onejs-sl/core the same without the parser, for a game at run time
11
11
  * onejs-sl/tables builtins, opcodes, inputs, shapes: what an editor reads
12
- * onejs-sl/limits whether a program fits the VM, without encoding it
13
- * onejs-sl/vm the VM encoder
12
+ * onejs-sl/compile what a host draws a program with, and no budget
13
+ * onejs-sl/limits whether a program fits the VM, until the VM goes
14
+ * onejs-sl/vm the VM encoder, until the VM goes
14
15
  * onejs-sl/emit/unity the ShaderLab shader a Unity editor generates
15
16
  * onejs-sl/emit/web WGSL and GLSL ES with OneJS's web frame
16
17
  *
package/src/ir.ts CHANGED
@@ -26,7 +26,7 @@
26
26
  * them, and it is fully testable with no GPU.
27
27
  */
28
28
 
29
- import { SLOP, SL_ARITY, SL_NAME, type SLOpCode } from "./ops"
29
+ import { SLOP, SL_ARITY, SL_NAME, TEXTURE_SLOTS, UNIFORM_SLOTS, type SLOpCode } from "./ops"
30
30
 
31
31
  /** Component count. The only notion of type the IR has. */
32
32
  export const TYPE = { FLOAT: 1, VEC2: 2, VEC3: 3, VEC4: 4 } as const
@@ -199,11 +199,38 @@ export interface Program {
199
199
  }
200
200
 
201
201
  /**
202
- * Sampler slots in a fragment shader on the WebGL2 baseline, minus one for the
203
- * program texture itself. Exceeding it is refused when the program is written
204
- * rather than when it is drawn, with a message naming the limit.
202
+ * @deprecated Use `TEXTURE_SLOTS`. Was 15, the WebGL2 sampler count, which let a
203
+ * program declare textures no native host binds.
205
204
  */
206
- export const MAX_TEXTURES = 15
205
+ export const MAX_TEXTURES = TEXTURE_SLOTS
206
+
207
+ /**
208
+ * The refusals for a program past its caps, worded once for the parser (a
209
+ * `file`) and the builder (a `program`) so the two cannot drift apart.
210
+ */
211
+ export function tooManyUniforms(declared: number, source: "file" | "program"): string {
212
+ return `this ${source} declares ${declared} uniforms and a program may hold ${UNIFORM_SLOTS}. ` +
213
+ `OneJS keeps a program's uniforms in ${UNIFORM_SLOTS} slots, so this one would have no ` +
214
+ `slot of its own; it is refused here rather than drawn wrong. Pack related values into ` +
215
+ `a ${source === "file" ? "float4" : "vec4"}.`
216
+ }
217
+
218
+ export function tooManyTextures(declared: number, source: "file" | "program"): string {
219
+ return `this ${source} declares ${declared} textures and a program may sample ${TEXTURE_SLOTS}. ` +
220
+ `OneJS binds ${TEXTURE_SLOTS} textures to a program in the editor and a native player, so ` +
221
+ `this one would sample nothing there while a browser drew it: two pictures from one ${source}.`
222
+ }
223
+
224
+ /**
225
+ * Refuses a finished program past either cap. The parser and the builder
226
+ * refuse at the declaration, where the message can point at it; this is for
227
+ * a program that arrived some other way, stored as JSON or put together by
228
+ * hand, and `compile` runs it so nothing reaches a host around it.
229
+ */
230
+ export function checkCaps(p: Pick<Program, "uniforms" | "textures">): void {
231
+ if (p.uniforms.length > UNIFORM_SLOTS) throw new SLError(tooManyUniforms(p.uniforms.length, "program"))
232
+ if (p.textures.length > TEXTURE_SLOTS) throw new SLError(tooManyTextures(p.textures.length, "program"))
233
+ }
207
234
 
208
235
  /** Instructions a single program may hold. Generous; the ceiling that matters is registers. */
209
236
  export const MAX_NODES = 4096
@@ -288,6 +315,7 @@ export class Builder {
288
315
  const problem = controlProblem(type, value, control)
289
316
  if (problem !== null) throw new SLError(`uniform "${name}": ${problem.message}`)
290
317
  const slot = this.uniforms.length
318
+ if (slot >= UNIFORM_SLOTS) throw new SLError(tooManyUniforms(slot + 1, "program"))
291
319
  const decl: UniformDecl = { name, type, value: value.slice() }
292
320
  if (colour) decl.colour = true
293
321
  Object.assign(decl, controlOf(control))
@@ -298,12 +326,8 @@ export class Builder {
298
326
  texture(name: string): number {
299
327
  const existing = this.textures.findIndex((t) => t.name === name)
300
328
  if (existing >= 0) return this.textures[existing].slot
301
- if (this.textures.length >= MAX_TEXTURES) {
302
- throw new SLError(
303
- `a program may sample at most ${MAX_TEXTURES} textures, and this one asks for ` +
304
- `${this.textures.length + 1}. That ceiling is the fragment shader's sampler slots ` +
305
- `on the WebGL2 baseline, so it cannot be widened.`,
306
- )
329
+ if (this.textures.length >= TEXTURE_SLOTS) {
330
+ throw new SLError(tooManyTextures(this.textures.length + 1, "program"))
307
331
  }
308
332
  const slot = this.textures.length
309
333
  this.textures.push({ name, slot })
package/src/lang/check.ts CHANGED
@@ -28,8 +28,8 @@
28
28
  * is refused with the reason.
29
29
  */
30
30
 
31
- import { DERIVED_INPUTS, INPUTS } from "../ir"
32
- import { VM_TEXTURES, VM_UNIFORMS } from "../ops"
31
+ import { DERIVED_INPUTS, INPUTS, tooManyTextures, tooManyUniforms } from "../ir"
32
+ import { TEXTURE_SLOTS, UNIFORM_SLOTS } 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"
@@ -161,15 +161,9 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
161
161
  if (!uniforms.has(u.name)) uniforms.set(u.name, u)
162
162
  }
163
163
  attempt(() => {
164
- if (uniforms.size <= VM_UNIFORMS) return
165
- const over = unit.uniforms[VM_UNIFORMS]!
166
- fail(
167
- `this file declares ${uniforms.size} uniforms and a program may hold ${VM_UNIFORMS}. ` +
168
- `That is the size of the VM's uniform array; past it a slot reads the last one instead ` +
169
- `of its own, so it is refused here rather than rendered wrong. Pack related values into ` +
170
- `a float4.`,
171
- over.pos, over.name.length,
172
- )
164
+ if (uniforms.size <= UNIFORM_SLOTS) return
165
+ const over = unit.uniforms[UNIFORM_SLOTS]!
166
+ fail(tooManyUniforms(uniforms.size, "file"), over.pos, over.name.length)
173
167
  })
174
168
 
175
169
  for (const t of unit.textures) {
@@ -180,14 +174,9 @@ export function check(unit: Unit, prelude: FuncDecl[], options: CheckOptions = {
180
174
  if (!textures.has(t.name)) textures.set(t.name, t)
181
175
  }
182
176
  attempt(() => {
183
- if (textures.size <= VM_TEXTURES) return
184
- const over = unit.textures[VM_TEXTURES]!
185
- fail(
186
- `this file declares ${textures.size} textures and a program may sample ${VM_TEXTURES}. ` +
187
- `The VM shader declares four samplers; a fifth would sample the fourth in the browser ` +
188
- `and its own after an eject, which is two different pictures from one file.`,
189
- over.pos, over.name.length,
190
- )
177
+ if (textures.size <= TEXTURE_SLOTS) return
178
+ const over = unit.textures[TEXTURE_SLOTS]!
179
+ fail(tooManyTextures(textures.size, "file"), over.pos, over.name.length)
191
180
  })
192
181
 
193
182
  for (const c of unit.consts) {
package/src/ops.ts CHANGED
@@ -35,17 +35,24 @@
35
35
  export const SL_WIRE_VERSION = 2
36
36
 
37
37
  /**
38
- * What the VM shader actually declares, and therefore what a program may use.
38
+ * How many uniforms and textures one program may declare: the language's caps,
39
+ * for a host to read.
39
40
  *
40
- * `FxProgram.shader` has `float4 _Uniforms[16]` and four `sampler2D`s, and
41
- * `SLProgramBridge.cs` fills exactly those. Past either ceiling the VM does not
42
- * fail, it clamps: uniform 17 reads slot 15 and texture slot 4 samples `_Tex3`,
43
- * while the generated HLSL would declare both correctly. That is a silent
44
- * disagreement between the backends, so the text form reports it at the
45
- * declaration with a file and a line. See `Specs/SL_TEXT.md` 3.7.
41
+ * They are what OneJS draws a program with. It keeps a program's uniforms in
42
+ * 16 slots in the editor, a native player and a browser alike
43
+ * (`SLProgramBridge.MaxUniforms`, the web frame's `sl_U`), and binds four
44
+ * textures in the editor and a native player (`_Tex0` to `_Tex3`). A program
45
+ * past either has something with no slot of its own, which one host would drop
46
+ * and another would draw, so the parser and the builder both refuse it at the
47
+ * declaration, the parser with a file and a line. `Specs/SL_NEXT.md` section 8
48
+ * proposes raising both.
46
49
  */
47
- export const VM_UNIFORMS = 16
48
- export const VM_TEXTURES = 4
50
+ export const UNIFORM_SLOTS = 16
51
+ export const TEXTURE_SLOTS = 4
52
+
53
+ /** The VM's names for the same two caps, which go when the VM does. */
54
+ export const VM_UNIFORMS = UNIFORM_SLOTS
55
+ export const VM_TEXTURES = TEXTURE_SLOTS
49
56
 
50
57
  /** Input ids, fixed here because the shader switches on them. */
51
58
  export const INPUT_ID: Record<string, number> = {
package/src/serial.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * older one is migrated to this one and rehashed under it.
10
10
  */
11
11
  import {
12
- INPUTS, SLError, SL_IR_VERSION, TYPE, controlOf, controlProblem, hashProgram,
12
+ INPUTS, SLError, SL_IR_VERSION, TYPE, checkCaps, controlOf, controlProblem, hashProgram,
13
13
  type NodeRef, type Program, type SLNode, type SLType, type TextureDecl, type UniformDecl,
14
14
  } from "./ir"
15
15
  import { SL_ARITY, SL_NAME } from "./ops"
@@ -44,6 +44,7 @@ export function fromJSON(json: unknown): Program {
44
44
  if (nodes[result!]!.type !== TYPE.VEC4) fail("its result is not a float4")
45
45
  const uniforms = (j.uniforms ?? []).map(uniform)
46
46
  const textures = (j.textures ?? []).map(texture)
47
+ checkCaps({ uniforms, textures })
47
48
  for (const n of nodes) {
48
49
  if (n.k === "uniform" && n.slot >= uniforms.length) fail(`a node reads uniform slot ${n.slot}, which is not declared`)
49
50
  }
package/src/web.ts CHANGED
@@ -29,12 +29,12 @@ import { SLError, TYPE, type Program, type SLNode, type SLType } from "./ir"
29
29
  import { libClosure, libIndex, LIB_FUNCTIONS, SDF_CALLS } from "./lib"
30
30
  import { LIB_GLSL } from "./lib/glsl"
31
31
  import { LIB_WGSL } from "./lib/wgsl"
32
- import { INPUT_ID, SLOP, VM_UNIFORMS } from "./ops"
32
+ import { INPUT_ID, SLOP, UNIFORM_SLOTS } from "./ops"
33
33
 
34
34
  export type WebLanguage = "glsl" | "wgsl"
35
35
 
36
36
  /** Uniform slots the host always provides, so every program shares one layout. */
37
- export const WEB_UNIFORM_SLOTS = VM_UNIFORMS
37
+ export const WEB_UNIFORM_SLOTS = UNIFORM_SLOTS
38
38
 
39
39
  export function emitGLSL(p: Program): string {
40
40
  return emitWeb(p, "glsl")