onejs-sl 0.7.0 → 0.9.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 +33 -0
- package/README.md +61 -15
- package/goldens/goldens.json +56 -5
- package/package.json +1 -1
- package/src/body.ts +87 -8
- package/src/core.ts +3 -2
- package/src/hlsl.ts +59 -4
- package/src/index.ts +2 -2
- package/src/ir.ts +129 -12
- package/src/lang/README.md +41 -12
- package/src/lang/ast.ts +6 -1
- package/src/lang/attributes.ts +75 -2
- package/src/lang/builtins.ts +2 -0
- package/src/lang/check.ts +14 -6
- package/src/lang/classify.ts +48 -4
- package/src/lang/glsl.ts +13 -4
- package/src/lang/index.ts +2 -2
- package/src/lang/lower.ts +17 -2
- package/src/lang/parser.ts +6 -5
- package/src/ops.ts +23 -9
- package/src/params.ts +12 -2
- package/src/serial.ts +16 -5
- package/src/sl.ts +41 -7
- package/src/tables.ts +5 -3
- package/src/web.ts +68 -15
package/src/web.ts
CHANGED
|
@@ -14,11 +14,23 @@
|
|
|
14
14
|
*
|
|
15
15
|
* sl_Res (target width, target height, seconds, frame)
|
|
16
16
|
* sl_Opt (1 in a Linear colour space project else 0, 1 to flip uv.y, deltaTime, 0)
|
|
17
|
-
* sl_U
|
|
17
|
+
* sl_U uniform slots, each a vec4, in the program's slot order: 16 of
|
|
18
|
+
* them, or WEB_UNIFORM_SLOTS for a program with more than 16
|
|
19
|
+
* uniforms. A host fills WEB_UNIFORM_SLOTS whatever the program
|
|
20
|
+
* declares, which both languages allow.
|
|
18
21
|
* textures by slot: GLSL `sl_Tex<slot>`; WGSL `sl_tex<slot>` at binding
|
|
19
22
|
* 2 + 2 * slot with its own sampler `sl_samp<slot>` at 1 + 2 * slot,
|
|
20
23
|
* all in group 0 beside the frame block at binding 0. Only the
|
|
21
|
-
* slots the program
|
|
24
|
+
* slots the program reads are declared, and in WGSL a slot's
|
|
25
|
+
* sampler only when the program samples it, rather than only
|
|
26
|
+
* measuring it (`textureSize`) or fetching from it (`tex2Dfetch`).
|
|
27
|
+
* a texture's sampler, when its program sets one (`[Wrap(Repeat)]`,
|
|
28
|
+
* `[Filter(Point)]`, IR 6): its declaration line ends
|
|
29
|
+
* `// sampler <wrap> <filter>`, the sampler's in WGSL and the
|
|
30
|
+
* sampler2D's in GLSL, wrap one of repeat, clamp, mirror and filter
|
|
31
|
+
* point or linear. The host samples that slot so, whatever the
|
|
32
|
+
* bound texture's own settings; with no such comment, as those
|
|
33
|
+
* settings say. `samplingOf` in ir.ts says what each word means.
|
|
22
34
|
* the previous frame, only for a program that samples it: GLSL `sl_Prev`;
|
|
23
35
|
* WGSL `sl_prev` with its sampler `sl_prevSamp`, at the two
|
|
24
36
|
* bindings after the last texture slot's, whatever the program
|
|
@@ -34,7 +46,7 @@
|
|
|
34
46
|
*/
|
|
35
47
|
|
|
36
48
|
import { INT_LIMITS } from "./body"
|
|
37
|
-
import { SLError, TYPE, valueAt, type NodeRef, type Program, type SLKind, type SLType, type ValueNode } from "./ir"
|
|
49
|
+
import { SLError, TYPE, isWhole, samplingOf, valueAt, type NodeRef, type Program, type TextureDecl, type SLKind, type SLType, type ValueNode } from "./ir"
|
|
38
50
|
import { inVaryingFlow, local, printBody, structure, type Syntax } from "./structure"
|
|
39
51
|
import { libClosure, libIndex, LIB_FUNCTIONS, SDF_CALLS } from "./lib"
|
|
40
52
|
import { LIB_GLSL } from "./lib/glsl"
|
|
@@ -43,9 +55,21 @@ import { INPUT_ID, SLOP, TEXTURE_SLOTS, UNIFORM_SLOTS } from "./ops"
|
|
|
43
55
|
|
|
44
56
|
export type WebLanguage = "glsl" | "wgsl"
|
|
45
57
|
|
|
46
|
-
/** Uniform slots
|
|
58
|
+
/** Uniform slots a host provides, so one buffer fits every program. */
|
|
47
59
|
export const WEB_UNIFORM_SLOTS = UNIFORM_SLOTS
|
|
48
60
|
|
|
61
|
+
/**
|
|
62
|
+
* The block a program with 16 uniforms or fewer declares: the size it had
|
|
63
|
+
* before onejs-sl 0.9 raised the cap. A WebGPU host before then (OneJS 3.9.8)
|
|
64
|
+
* binds a buffer of exactly this many slots, which a larger block fails to
|
|
65
|
+
* validate against, so a program that fits keeps drawing there.
|
|
66
|
+
*/
|
|
67
|
+
const BASE_UNIFORM_SLOTS = 16
|
|
68
|
+
|
|
69
|
+
function uniformBlock(p: Program): number {
|
|
70
|
+
return p.uniforms.length > BASE_UNIFORM_SLOTS ? WEB_UNIFORM_SLOTS : BASE_UNIFORM_SLOTS
|
|
71
|
+
}
|
|
72
|
+
|
|
49
73
|
export function emitGLSL(p: Program): string {
|
|
50
74
|
return emitWeb(p, "glsl")
|
|
51
75
|
}
|
|
@@ -59,9 +83,12 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
59
83
|
const need = new Set<number>()
|
|
60
84
|
const smoothsteps = new Set<SLType>()
|
|
61
85
|
const sampled = new Set<number>()
|
|
86
|
+
/** Slots read without a sampler: measured or fetched from. */
|
|
87
|
+
const loaded = new Set<number>()
|
|
62
88
|
/** Whether the body samples the previous frame, which declares its binding. */
|
|
63
89
|
let previous = false
|
|
64
90
|
const shape = structure(p)
|
|
91
|
+
const size = (slot: number) => W ? `vec2f(textureDimensions(sl_tex${slot}))` : `vec2(textureSize(sl_Tex${slot}, 0))`
|
|
65
92
|
const T = (t: SLType) => (W ? (t === 1 ? "f32" : `vec${t}f`) : t === 1 ? "float" : `vec${t}`)
|
|
66
93
|
/** A value's type in this language, its kind included. */
|
|
67
94
|
const typeName = (n: { type: SLType; kind?: SLKind }): string => {
|
|
@@ -261,9 +288,30 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
261
288
|
case SLOP.SAMPLE_LOD: {
|
|
262
289
|
const slot = Math.round(imm[0] ?? 0)
|
|
263
290
|
sampled.add(slot)
|
|
291
|
+
const at = (level: string) => W
|
|
292
|
+
? `textureSampleLevel(sl_tex${slot}, sl_samp${slot}, ${a[0]}, ${level})`
|
|
293
|
+
: `textureLod(sl_Tex${slot}, ${a[0]}, ${level})`
|
|
294
|
+
// A fraction blends the level below and the level above,
|
|
295
|
+
// whatever the texture's filter between levels, as the HLSL
|
|
296
|
+
// emitter does (`isWhole`). Past the smallest level both read
|
|
297
|
+
// the smallest, as a whole level past it does.
|
|
298
|
+
if (isWhole(p.nodes, n.args[1]!)) return at(a[1]!)
|
|
299
|
+
const below = `floor(${a[1]})`
|
|
300
|
+
return `mix(${at(below)}, ${at(`${below} + 1.0`)}, fract(${a[1]}))`
|
|
301
|
+
}
|
|
302
|
+
case SLOP.TEXTURE_SIZE: {
|
|
303
|
+
const slot = Math.round(imm[0] ?? 0)
|
|
304
|
+
loaded.add(slot)
|
|
305
|
+
return size(slot)
|
|
306
|
+
}
|
|
307
|
+
case SLOP.SAMPLE_FETCH: {
|
|
308
|
+
const slot = Math.round(imm[0] ?? 0)
|
|
309
|
+
loaded.add(slot)
|
|
310
|
+
// Floored and held inside, as the HLSL body does: a load out of
|
|
311
|
+
// range is undefined in GLSL and implementation defined in WGSL.
|
|
264
312
|
return W
|
|
265
|
-
? `
|
|
266
|
-
: `
|
|
313
|
+
? `textureLoad(sl_tex${slot}, vec2i(clamp(floor(${a[0]}), vec2f(0.0), ${size(slot)} - 1.0)), 0)`
|
|
314
|
+
: `texelFetch(sl_Tex${slot}, ivec2(clamp(floor(${a[0]}), vec2(0.0), ${size(slot)} - 1.0)), 0)`
|
|
267
315
|
}
|
|
268
316
|
case SLOP.SAMPLE_PREVIOUS:
|
|
269
317
|
previous = true
|
|
@@ -356,21 +404,26 @@ function emitWeb(p: Program, lang: WebLanguage): string {
|
|
|
356
404
|
const body = printBody(p, shape, syntax, expr, " ").join("\n")
|
|
357
405
|
const library = [...[...smoothsteps].sort().map((w) => smoothstep(w, lang)), ...librarySource(need, lang)].join("\n")
|
|
358
406
|
const header = `// GENERATED from a shader language program (${p.hash}). Do not edit.`
|
|
359
|
-
// Only the textures the program
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
const textures = p.textures.filter((t) => sampled.has(t.slot))
|
|
407
|
+
// Only the textures the program reads, and in WGSL only the samplers it
|
|
408
|
+
// samples with. WebGPU's automatic layout leaves out a binding the shader
|
|
409
|
+
// never reads, and a bind group that supplies one anyway does not
|
|
410
|
+
// validate; the host reads these declarations back to know what to bind.
|
|
411
|
+
const textures = p.textures.filter((t) => sampled.has(t.slot) || loaded.has(t.slot))
|
|
412
|
+
/** The comment that says a texture's sampler is the program's own, for a slot it samples. */
|
|
413
|
+
const samplerNote = (t: TextureDecl) => {
|
|
414
|
+
const s = sampled.has(t.slot) ? samplingOf(t) : null
|
|
415
|
+
return s === null ? "" : ` // sampler ${s.wrap} ${s.filter}`
|
|
416
|
+
}
|
|
364
417
|
|
|
365
418
|
if (W) {
|
|
366
419
|
const bindings = [...textures.map((t) =>
|
|
367
|
-
`@group(0) @binding(${1 + 2 * t.slot}) var sl_samp${t.slot}: sampler
|
|
420
|
+
(sampled.has(t.slot) ? `@group(0) @binding(${1 + 2 * t.slot}) var sl_samp${t.slot}: sampler;${samplerNote(t)}\n` : "") +
|
|
368
421
|
`@group(0) @binding(${2 + 2 * t.slot}) var sl_tex${t.slot}: texture_2d<f32>;`),
|
|
369
422
|
...previous ? [
|
|
370
423
|
`@group(0) @binding(${1 + 2 * TEXTURE_SLOTS}) var sl_prevSamp: sampler;\n` +
|
|
371
424
|
`@group(0) @binding(${2 + 2 * TEXTURE_SLOTS}) var sl_prev: texture_2d<f32>;`] : []].join("\n")
|
|
372
425
|
return `${header}
|
|
373
|
-
struct SLFrame { res: vec4f, opt: vec4f, u: array<vec4f, ${
|
|
426
|
+
struct SLFrame { res: vec4f, opt: vec4f, u: array<vec4f, ${uniformBlock(p)}> }
|
|
374
427
|
@group(0) @binding(0) var<uniform> sl: SLFrame;
|
|
375
428
|
${bindings}
|
|
376
429
|
${library}
|
|
@@ -386,14 +439,14 @@ ${body}
|
|
|
386
439
|
}
|
|
387
440
|
`
|
|
388
441
|
}
|
|
389
|
-
const samplers = [...textures.map((t) => `uniform sampler2D sl_Tex${t.slot}
|
|
442
|
+
const samplers = [...textures.map((t) => `uniform sampler2D sl_Tex${t.slot};${samplerNote(t)}`), ...previous ? ["uniform sampler2D sl_Prev;"] : []].join("\n")
|
|
390
443
|
return `#version 300 es
|
|
391
444
|
${header}
|
|
392
445
|
precision highp float;
|
|
393
446
|
precision highp int;
|
|
394
447
|
uniform vec4 sl_Res;
|
|
395
448
|
uniform vec4 sl_Opt;
|
|
396
|
-
uniform vec4 sl_U[${
|
|
449
|
+
uniform vec4 sl_U[${uniformBlock(p)}];
|
|
397
450
|
${samplers}
|
|
398
451
|
out vec4 sl_Out;
|
|
399
452
|
${library}
|