onejs-sl 0.1.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 +10 -0
- package/README.md +266 -0
- package/package.json +60 -0
- package/src/color.ts +16 -0
- package/src/core.ts +24 -0
- package/src/emit/unity.ts +3 -0
- package/src/emit/web.ts +3 -0
- package/src/encode.ts +462 -0
- package/src/hlsl.ts +275 -0
- package/src/index.ts +24 -0
- package/src/ir.ts +301 -0
- package/src/lang/README.md +167 -0
- package/src/lang/ast.ts +87 -0
- package/src/lang/builtins.ts +168 -0
- package/src/lang/check.ts +541 -0
- package/src/lang/index.ts +55 -0
- package/src/lang/lexer.ts +176 -0
- package/src/lang/lower.ts +628 -0
- package/src/lang/parser.ts +460 -0
- package/src/lang/prelude.ts +75 -0
- package/src/limits.ts +50 -0
- package/src/ops.ts +346 -0
- package/src/serial.ts +137 -0
- package/src/shapes.ts +49 -0
- package/src/sl.ts +721 -0
- package/src/tables.ts +15 -0
- package/src/vm.ts +11 -0
- package/src/web.ts +273 -0
- package/src/weblib.ts +1303 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
The shader language compiler as its own package, moved out of `onejs-unity` with its history and its tests. It behaves exactly as `onejs-unity` 0.5.15's `sl` did, at IR version 2 and VM wire version 2.
|
|
6
|
+
|
|
7
|
+
- Entry points `onejs-sl`, `/core`, `/tables`, `/limits`, `/vm`, `/emit/unity` and `/emit/web`
|
|
8
|
+
- `vmFit` says whether the VM runs a program, and why not, without encoding it
|
|
9
|
+
- `parseColor` is exported from `onejs-sl/core`
|
|
10
|
+
- The test suite also runs in QuickJS-ng and must match Node byte for byte
|
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# onejs-sl
|
|
2
|
+
|
|
3
|
+
The OneJS shader language: a small typed language for per pixel programs. A
|
|
4
|
+
program is written as a `.sl` file (HLSL text) or with the TypeScript form, and
|
|
5
|
+
recorded as one portable IR, a flat typed graph with a hash. The package emits
|
|
6
|
+
that IR as the buffer OneJS's VM evaluates, a Unity ShaderLab shader, WGSL and
|
|
7
|
+
GLSL ES.
|
|
8
|
+
|
|
9
|
+
It is the compiler [OneJS](https://onejs.com) uses, taken out of `onejs-unity`
|
|
10
|
+
so a host without Unity can run it too. `onejs-unity/sl` and
|
|
11
|
+
`onejs-unity/sl/compiler` re-export it, so nothing in a OneJS project imports
|
|
12
|
+
this package directly. User documentation is the
|
|
13
|
+
[shader language guide](https://onejs.com/docs/guides/shader-language); this
|
|
14
|
+
file is the design.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { parse, sl } from "onejs-sl"
|
|
18
|
+
|
|
19
|
+
const ripple = parse(`float4 main() {
|
|
20
|
+
float v = sin(length(uv - 0.5) * 40 - time * 4);
|
|
21
|
+
return float4(v * 0.5 + 0.5, 0, 0, 1);
|
|
22
|
+
}`, { file: "ripple.sl" })
|
|
23
|
+
|
|
24
|
+
const plasma = sl.program(({ uv, time }) => {
|
|
25
|
+
const p = uv.mul(8).add(time.mul(0.4))
|
|
26
|
+
const v = sl.sin(p.x).add(sl.sin(p.y))
|
|
27
|
+
return sl.vec4(v.mul(0.5).add(0.5), 0, 0, 1)
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Entry points
|
|
32
|
+
|
|
33
|
+
`"sideEffects": false`, and each backend is its own entry, so a host bundles
|
|
34
|
+
only what it calls.
|
|
35
|
+
|
|
36
|
+
| Entry | Contents |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `onejs-sl` | `parse` and `analyze`, the TypeScript form `sl`, the IR types, `SL_IR_VERSION`, `toJSON`/`fromJSON`, `SLParseError` (file, line, column, length) |
|
|
39
|
+
| `onejs-sl/core` | the same without the parser: what a game needs at run time |
|
|
40
|
+
| `onejs-sl/tables` | `BUILTINS`, `SL_HLSL`, `INPUTS`, `SL_SDF_SHAPES`, `SL_SDF_PARAMS`: what completion and highlighting read |
|
|
41
|
+
| `onejs-sl/limits` | `vmFit(program)`: whether the VM runs it, and why not, without encoding |
|
|
42
|
+
| `onejs-sl/vm` | `encode`, `SL_WIRE_VERSION` |
|
|
43
|
+
| `onejs-sl/emit/unity` | `emitShader`: the `.shader` a Unity editor generates |
|
|
44
|
+
| `onejs-sl/emit/web` | `emitWGSL`, `emitGLSL`: OneJS's web frame |
|
|
45
|
+
|
|
46
|
+
`src/entries.test.ts` pins every name, since removing one breaks a host.
|
|
47
|
+
|
|
48
|
+
## Runs anywhere ES2020 runs
|
|
49
|
+
|
|
50
|
+
The source reaches for no host API: no `fs`, `process`, `Buffer`,
|
|
51
|
+
`TextEncoder`, `performance`, `structuredClone` or `Intl`. It runs in a
|
|
52
|
+
browser (the Play editor's diagnostics), a Cloudflare worker (the Play build)
|
|
53
|
+
and QuickJS (Magerie). Two checks hold it to that:
|
|
54
|
+
|
|
55
|
+
- lint refuses those globals and any Node module in `src/`;
|
|
56
|
+
- `npm run test:quickjs` bundles `quickjs/corpus.ts` as one ES2020 IIFE, the
|
|
57
|
+
way Magerie bundles a script, runs it in QuickJS-ng and in a bare Node
|
|
58
|
+
context, and fails unless the two results agree byte for byte. The corpus
|
|
59
|
+
parses, encodes, fits and emits every program in `quickjs/corpus/`, every
|
|
60
|
+
shape at its full parameter count, and the error paths. `npm test` runs it
|
|
61
|
+
after vitest.
|
|
62
|
+
|
|
63
|
+
## Releasing
|
|
64
|
+
|
|
65
|
+
Push a tag `v<version>` matching `package.json`; `.github/workflows/publish.yml`
|
|
66
|
+
publishes through npm trusted publishing (OIDC), so no token exists anywhere.
|
|
67
|
+
Note the release in `CHANGELOG.md` first. The one exception is the first version: npm
|
|
68
|
+
attaches a trusted publisher to a package that already exists, so 0.1.0 was
|
|
69
|
+
published by hand, and the workflow checks its tag and publishes nothing. In the OneJS container this package
|
|
70
|
+
is checked out at `JSModules/onejs-sl`, and `onejs-unity` links it with
|
|
71
|
+
`file:../onejs-sl` as a dev dependency beside its `^` peer range.
|
|
72
|
+
|
|
73
|
+
## The one idea
|
|
74
|
+
|
|
75
|
+
**One authoring surface, one IR, two backends.** Unity cannot compile a shader
|
|
76
|
+
at runtime in a player build, on any graphics API, so in the browser a program
|
|
77
|
+
has to become data that a fixed shader evaluates. Ejecting to a Unity project
|
|
78
|
+
does not change what the author wrote, it changes what is possible, because an
|
|
79
|
+
editor compiles shaders at build time.
|
|
80
|
+
|
|
81
|
+
So the same source is interpreted by a VM on play.onejs.com and compiled from
|
|
82
|
+
generated HLSL after an eject, with no edit in between. Both backends exist:
|
|
83
|
+
`encode.ts` feeds OneJS's `Runtime/SL/SLProgramBridge.cs` and `FxProgram.shader`,
|
|
84
|
+
and `hlsl.ts` feeds its `Editor/SLShaderGenerator.cs`. Nobody writes a manifest for the
|
|
85
|
+
second: an editor that interprets a program asks the encoded program for its
|
|
86
|
+
`hlsl` (a lazy getter, never read in Play), records it into
|
|
87
|
+
`Assets/OneJS.Generated/Shaders/Recorded.sl.json`, generates the shader and
|
|
88
|
+
moves the live material onto it. `manifest()` is still there for an app that
|
|
89
|
+
would rather write its programs out at build time.
|
|
90
|
+
|
|
91
|
+
**A third and fourth backend, for the browser.** A player cannot compile a
|
|
92
|
+
shader, but the page it runs in can. `web.ts` prints every program as WGSL and
|
|
93
|
+
as GLSL ES 3.00 (`weblib.ts` holds the noise, colour and 42 distance functions
|
|
94
|
+
ported from `SLCommon.cginc`, `Noise2D.cginc` and `SDF2D.cginc`), and OneJS's
|
|
95
|
+
`Plugins/WebGL/OneJSSLWeb.jslib` compiles whichever one Unity's device speaks
|
|
96
|
+
and draws it into the element's target in place of the VM. A `.sl` import
|
|
97
|
+
carries both strings, printed at build time; an `encode()` result has them as
|
|
98
|
+
lazy getters, like `hlsl`. The VM draws until the compiled program is ready,
|
|
99
|
+
and for good if it fails to compile. The host contract (the frame block, the
|
|
100
|
+
16 uniform slots, one binding pair per sampled texture) is written out at the
|
|
101
|
+
top of `web.ts`.
|
|
102
|
+
|
|
103
|
+
The emitters match the HLSL emitter's semantics rather than each language's
|
|
104
|
+
own: `%` truncates like `fmod`, `pow` takes `abs` of its base, `asin` and
|
|
105
|
+
`acos` clamp, `log` and `sqrt` guard their argument, a select is the VM's
|
|
106
|
+
branchless `lerp`. `web.test.ts` checks the structure (every shape, every
|
|
107
|
+
opcode, the library order); whether the output matches the VM within 1/255 is
|
|
108
|
+
measured in a browser, through the real element, by `Tools/sl-web-parity` in
|
|
109
|
+
the container.
|
|
110
|
+
|
|
111
|
+
## Two ways to write one
|
|
112
|
+
|
|
113
|
+
A `.sl` file is HLSL text and `sl.program` is a TypeScript EDSL, and they record
|
|
114
|
+
the same graph: a file lowers THROUGH the EDSL, so `sin(x)` and `sl.sin(x)` are
|
|
115
|
+
one call. `lang/` is the parser and `lang/README.md` covers it; the rest of this
|
|
116
|
+
file is the IR, the hash and the EDSL, which both surfaces sit on.
|
|
117
|
+
|
|
118
|
+
```hlsl
|
|
119
|
+
float4 main() {
|
|
120
|
+
float2 p = uv * 8 + time * 0.4;
|
|
121
|
+
float v = sin(p.x) + sin(p.y);
|
|
122
|
+
return float4(v * 0.5 + 0.5, 0, 0, 1);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Docs lead with the file. The EDSL is the programmatic form: the IR builder, the
|
|
127
|
+
parser's target, and what a program built by code uses.
|
|
128
|
+
|
|
129
|
+
## Why an EDSL came first
|
|
130
|
+
|
|
131
|
+
A TypeScript EDSL inherits completion, type errors at the call site, jump to
|
|
132
|
+
definition, rename and the author's editor for free. Monaco in the Play editor
|
|
133
|
+
already has these types loaded.
|
|
134
|
+
|
|
135
|
+
It also gives common subexpression elimination for nothing, which is the most
|
|
136
|
+
valuable optimisation here, because **a `const` in the host language IS the
|
|
137
|
+
shared node**. `const p = uv.mul(8)` used three times is one node with three
|
|
138
|
+
references, and writing it out long hand three times costs exactly the same,
|
|
139
|
+
because nodes are interned as they are built.
|
|
140
|
+
|
|
141
|
+
That reasoning is why the parser, when it came, cost only a parser: it emits
|
|
142
|
+
this same IR, so it inherited the encoder, the emitter, the hash and every test
|
|
143
|
+
that runs on a program.
|
|
144
|
+
|
|
145
|
+
## What is checked, and when
|
|
146
|
+
|
|
147
|
+
Everything an author can get wrong is refused **when the program is written**,
|
|
148
|
+
at module load, not at draw time:
|
|
149
|
+
|
|
150
|
+
| Mistake | What happens |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `vec2` combined with a `vec3` | TypeScript error at the call site, and a runtime error behind it |
|
|
153
|
+
| `uv.z` on a two component value | "z is component 3 of a vec2, which has 2" |
|
|
154
|
+
| A program returning something other than a `vec4` | "a program must return a vec4. Wrap it: sl.vec4(value, 1)" |
|
|
155
|
+
| `vec4` given the wrong number of parts | "vec4 needs 4 components, got 3" |
|
|
156
|
+
| One uniform name at two widths | "declared as both a float and a vec4" |
|
|
157
|
+
| More than 15 textures | Names the limit and why it cannot be widened |
|
|
158
|
+
| A value borrowed from another program | "a value from another program cannot be used in this one" |
|
|
159
|
+
|
|
160
|
+
The texture ceiling is the fragment shader's sampler slots on the WebGL2
|
|
161
|
+
baseline, which is the one resource neither backend can widen.
|
|
162
|
+
|
|
163
|
+
## The hash is the fragile part
|
|
164
|
+
|
|
165
|
+
`Program.hash` is what will link a program to its compiled shader. If it differs
|
|
166
|
+
between the machine that generated the shader and the machine that runs it, the
|
|
167
|
+
runtime falls back to the VM and **nobody is told**: correct output, quietly
|
|
168
|
+
slow, no error. That is the worst failure this design can have.
|
|
169
|
+
|
|
170
|
+
So it is a Merkle hash over the graph reachable from the result, not a walk of
|
|
171
|
+
the node array. An earlier version hashed storage order, which meant hoisting a
|
|
172
|
+
shared subexpression into a `const` changed the hash without changing what the
|
|
173
|
+
program computed. Constants go through a fixed precision, so `0.1 + 0.2` and
|
|
174
|
+
`0.3` do not produce different shaders. It is eight lowercase hex characters
|
|
175
|
+
from FNV-1a, chosen so a C# implementation can produce the same string rather
|
|
176
|
+
than for any cryptographic reason.
|
|
177
|
+
|
|
178
|
+
## Versions
|
|
179
|
+
|
|
180
|
+
Two numbers, with one rule: a reader accepts every version up to its own and
|
|
181
|
+
refuses a newer one with a message naming both.
|
|
182
|
+
|
|
183
|
+
- **`SL_IR_VERSION`** (`ir.ts`) is on every `Program` and in the hash, and is
|
|
184
|
+
bumped whenever an opcode, a shape or what one computes changes. Because it
|
|
185
|
+
is in the hash, a bump recompiles every cached shader. `toJSON` and
|
|
186
|
+
`fromJSON` (`serial.ts`) are the IR as JSON for a host that stores programs;
|
|
187
|
+
`fromJSON` checks everything an emitter relies on, refuses a newer version,
|
|
188
|
+
and migrates an older one.
|
|
189
|
+
- **`SL_WIRE_VERSION`** (`ops.ts`) is the newest VM encoding, and
|
|
190
|
+
`SLProgramBridge.WireVersion` in OneJS must match it (a container test
|
|
191
|
+
compares the two). `Encoded.wire`, and the `wire` in a `.sl` import, is the
|
|
192
|
+
LOWEST version that can run that program, so a program using nothing new
|
|
193
|
+
stays 1 and still runs on an older Play container. The VM refuses a newer
|
|
194
|
+
one, only where the VM runs; a WebGL player draws compiled and never reads
|
|
195
|
+
the buffer.
|
|
196
|
+
|
|
197
|
+
IR 2 and wire 2 came with #129. A shape takes as many parameters as it reads
|
|
198
|
+
(`SL_SDF_PARAMS`, six at most, and never fewer than four accepted), where it
|
|
199
|
+
used to take four and lose the rest. One instruction holds a shape id and four
|
|
200
|
+
immediates, so the encoder's `forVm` turns a shape given a fifth or sixth into
|
|
201
|
+
`SDF_WIDE`, which reads the remaining four from a constant register. Only the
|
|
202
|
+
VM sees it, and only those programs are wire 2.
|
|
203
|
+
|
|
204
|
+
## Control flow
|
|
205
|
+
|
|
206
|
+
There is none in the IR, deliberately. `sl.select`, `sl.step`, `sl.smoothstep` and
|
|
207
|
+
`sl.mix` cover branching without branching, and `sl.repeat(n, body, seed)`
|
|
208
|
+
unrolls at record time because `n` is a JavaScript number.
|
|
209
|
+
|
|
210
|
+
`repeat` is honest about being a macro rather than a loop. It covers fbm,
|
|
211
|
+
layered noise and small iterated distance fields, which is most of what 2D
|
|
212
|
+
shaders loop for. A data dependent loop is out of scope: the VM would need a
|
|
213
|
+
nested bounded loop with a dynamic trip count while codegen would handle it
|
|
214
|
+
fine, and the two backends agreeing is the property the whole design protects.
|
|
215
|
+
Because it unrolls, the count multiplies the body's operation count toward the
|
|
216
|
+
VM's 256-instruction ceiling. The ceiling error names any `repeat` that fills a
|
|
217
|
+
quarter of the budget or more, so the fix reads as "lower this count" rather
|
|
218
|
+
than "fewer instructions".
|
|
219
|
+
|
|
220
|
+
Every loop that reaches a GPU is therefore bounded by a constant: `repeat` is
|
|
221
|
+
unrolled, fbm's octaves are a constant 1 to 4, the helper loops in the noise
|
|
222
|
+
and Voronoi functions have fixed trip counts. No program can hang a GPU today,
|
|
223
|
+
so the compiled backends carry no loop cap. A data dependent loop (the
|
|
224
|
+
raymarching tier) would need one emitted into every loop it prints, since a GPU
|
|
225
|
+
reset takes the whole page's device, Unity's included.
|
|
226
|
+
|
|
227
|
+
## See also
|
|
228
|
+
|
|
229
|
+
- `Specs/SHADER_LANG.md` in the OneJS container, sections 3 and 4, and section 5.4 for the Phase 0
|
|
230
|
+
measurements that decided the VM's shape
|
|
231
|
+
- `Tools/shader-vm-spike/`, the harness behind those numbers
|
|
232
|
+
- `onejs-unity`'s `fx`, the image pipeline this becomes a source and an operand for
|
|
233
|
+
|
|
234
|
+
## What a program is given
|
|
235
|
+
|
|
236
|
+
`uv`, `fragCoord`, `resolution`, `time` and `aspect`, and the last three are the
|
|
237
|
+
**target's**, not the window's. A program is drawn with `Graphics.Blit` into the
|
|
238
|
+
element's own render texture, and Unity sets `_ScreenParams` per camera and
|
|
239
|
+
leaves it alone for a blit: reading it from a 64x256 target answers with the
|
|
240
|
+
game view's size. Both backends read the same wrong thing, so they agreed with
|
|
241
|
+
each other and the eject test, which compares them, saw nothing. What saw it was
|
|
242
|
+
a picture, because aspect correction, the one thing `aspect` exists for,
|
|
243
|
+
stretched every circle by the shape of whatever window it was in. The host now
|
|
244
|
+
sets `_Res` from the target and both backends read that.
|
|
245
|
+
|
|
246
|
+
`fx` had already learned this: `ShaderEffectElement` sets `_Aspect` from the
|
|
247
|
+
render texture with a comment saying why. The lesson did not travel.
|
|
248
|
+
|
|
249
|
+
## Colours
|
|
250
|
+
|
|
251
|
+
A hex colour is sRGB as written, the way CSS reads it, and the target holds
|
|
252
|
+
linear light. `sl.ramp` mixes its stops in sRGB, which is what reads as an even
|
|
253
|
+
ramp, and converts the result once through `TO_LINEAR`; `sl.color("#hex")` is
|
|
254
|
+
`parseColor` plus that conversion, and `sl.toLinear` is the conversion on its
|
|
255
|
+
own for a vec4 built from raw components. Both backends implement it gamma
|
|
256
|
+
aware (`sl_toLinear` in `SLCommon.cginc`), so a Gamma project gets the value as
|
|
257
|
+
written. Alpha is coverage and is never converted. Same rule as `fx`.
|
|
258
|
+
|
|
259
|
+
## Noise
|
|
260
|
+
|
|
261
|
+
`sl.noise`, `sl.simplex`, `sl.fbm(p, octaves, base)`, `sl.turbulence` and
|
|
262
|
+
`sl.ridged` are the fields `fx.noise` draws, from the same `Noise2D.cginc`, so
|
|
263
|
+
a simplex here is the simplex there. Octaves are 1 to 4. A program has no seed;
|
|
264
|
+
offset the input for a different field. `sl.simplex` used to be value noise on
|
|
265
|
+
a rotated lattice, and `sl.fbm` had its own value noise; both changed on
|
|
266
|
+
2026-09-06 when the fields were unified.
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "onejs-sl",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./core": "./src/core.ts",
|
|
10
|
+
"./tables": "./src/tables.ts",
|
|
11
|
+
"./limits": "./src/limits.ts",
|
|
12
|
+
"./vm": "./src/vm.ts",
|
|
13
|
+
"./emit/unity": "./src/emit/unity.ts",
|
|
14
|
+
"./emit/web": "./src/emit/web.ts"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"!src/**/*.test.ts",
|
|
20
|
+
"!src/fixtures",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"onejs",
|
|
25
|
+
"shader",
|
|
26
|
+
"shading-language",
|
|
27
|
+
"hlsl",
|
|
28
|
+
"wgsl",
|
|
29
|
+
"glsl",
|
|
30
|
+
"compiler",
|
|
31
|
+
"sdf",
|
|
32
|
+
"noise",
|
|
33
|
+
"procedural"
|
|
34
|
+
],
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/Singtaa/onejs-sl.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://onejs.com/docs/guides/shader-language",
|
|
40
|
+
"author": "Singtaa",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"lint": "eslint .",
|
|
45
|
+
"test": "vitest run && node quickjs/run.mjs",
|
|
46
|
+
"test:quickjs": "node quickjs/run.mjs"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@eslint/js": "^10.0.1",
|
|
50
|
+
"@jitl/quickjs-ng-wasmfile-release-sync": "^0.32.0",
|
|
51
|
+
"@types/node": "^22.0.0",
|
|
52
|
+
"esbuild": "^0.25.0",
|
|
53
|
+
"eslint": "^10.9.1",
|
|
54
|
+
"globals": "^17.11.0",
|
|
55
|
+
"quickjs-emscripten-core": "^0.32.0",
|
|
56
|
+
"typescript": "^5.7.0",
|
|
57
|
+
"typescript-eslint": "^8.68.0",
|
|
58
|
+
"vitest": "^2.1.0"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/color.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "#rgb", "#rrggbb" or "#rrggbbaa" to 0..1 components.
|
|
3
|
+
*
|
|
4
|
+
* Shared by `sl` and `fx`, so a colour is written the same way in a program
|
|
5
|
+
* and in an image chain and neither has to import the other.
|
|
6
|
+
*/
|
|
7
|
+
export function parseColor(hex: string): [number, number, number, number] {
|
|
8
|
+
const m = /^#([0-9a-fA-F]{3,8})$/.exec(hex.trim())
|
|
9
|
+
if (m === null) throw new Error(`"${hex}" is not a colour; use #rgb, #rrggbb or #rrggbbaa`)
|
|
10
|
+
const h = m[1]!
|
|
11
|
+
const grab = (i: number, n: number) => parseInt(n === 1 ? h[i]! + h[i]! : h.slice(i * 2, i * 2 + 2), 16) / 255
|
|
12
|
+
if (h.length === 3) return [grab(0, 1), grab(1, 1), grab(2, 1), 1]
|
|
13
|
+
if (h.length === 6) return [grab(0, 2), grab(1, 2), grab(2, 2), 1]
|
|
14
|
+
if (h.length === 8) return [grab(0, 2), grab(1, 2), grab(2, 2), grab(3, 2)]
|
|
15
|
+
throw new Error(`"${hex}" is not a colour; use #rgb, #rrggbb or #rrggbbaa`)
|
|
16
|
+
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `onejs-sl/core`: a program and nothing that reads source text.
|
|
3
|
+
*
|
|
4
|
+
* The TypeScript form, the IR, its version and its JSON, the opcode and shape
|
|
5
|
+
* tables. What a game needs at run time, which is why OneJS's `onejs-unity/sl`
|
|
6
|
+
* is built on this entry and not on `onejs-sl`: the eject scaffold vendors
|
|
7
|
+
* every file an entry reaches, and the parser is two thousand lines a played
|
|
8
|
+
* game never executes.
|
|
9
|
+
*/
|
|
10
|
+
export * as sl from "./sl"
|
|
11
|
+
export type { Float, Vec2, Vec3, Vec4, Num, ProgramInputs, Texture } from "./sl"
|
|
12
|
+
export {
|
|
13
|
+
TYPE, INPUTS, MAX_TEXTURES, MAX_NODES, SLError, hashProgram, widthName, SL_IR_VERSION,
|
|
14
|
+
} from "./ir"
|
|
15
|
+
export type {
|
|
16
|
+
SLType, InputName, NodeRef, SLNode, Program, UniformDecl, TextureDecl,
|
|
17
|
+
} from "./ir"
|
|
18
|
+
export { SLOP, SL_ARITY, SL_NAME, INPUT_ID, isSampling } from "./ops"
|
|
19
|
+
export type { SLOpCode } from "./ops"
|
|
20
|
+
export { SL_SDF_SHAPES, SL_SDF_PARAMS } from "./shapes"
|
|
21
|
+
export type { SlSdfKind } from "./shapes"
|
|
22
|
+
export { toJSON, fromJSON } from "./serial"
|
|
23
|
+
export type { ProgramJSON } from "./serial"
|
|
24
|
+
export { parseColor } from "./color"
|
package/src/emit/web.ts
ADDED