@thi.ng/shader-ast-js 0.7.68 → 1.0.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/pool.js ADDED
@@ -0,0 +1,68 @@
1
+ import { typedArray, } from "@thi.ng/api";
2
+ import { outOfBounds } from "@thi.ng/errors/out-of-bounds";
3
+ import { set } from "@thi.ng/vectors/set";
4
+ import { setC2, setC3, setC4 } from "@thi.ng/vectors/setc";
5
+ import { setN, setN2, setN3, setN4 } from "@thi.ng/vectors/setn";
6
+ /**
7
+ * Manager for re-using pre-allocated memory for various vector ops. If the
8
+ * compiled shader program includes a `main()` function, the pools will be reset
9
+ * automatically when that `main` function executes. Otherwise, users **MUST**
10
+ * call the {@link CompileResult.__reset} manually each time before invoking a
11
+ * compiled entry point function.
12
+ *
13
+ * @remarks
14
+ * Currently, the default capacity for each vector type is fixed at 2048 items.
15
+ * That means a shader program can create up to this number of temporary objects
16
+ * (per fragment/invocation). Should this number be insufficient, please submit
17
+ * an issue and explain your use case. Thanks!
18
+ */
19
+ export class Pool {
20
+ constructor(type, size, cap) {
21
+ this.size = size;
22
+ this.cap = cap;
23
+ this.index = 0;
24
+ this.mem = typedArray(type, cap * size);
25
+ this.items = new Array(cap);
26
+ for (let i = 0; i < cap; i++) {
27
+ this.items[i] = this.mem.subarray(i * size, i * size + size);
28
+ }
29
+ const next = () => {
30
+ if (this.index > this.items.length)
31
+ outOfBounds(this.index);
32
+ return this.items[this.index++];
33
+ };
34
+ let from;
35
+ let uniform;
36
+ switch (this.size) {
37
+ case 2:
38
+ from = (x, y) => setC2(next(), x, y);
39
+ uniform = (n) => setN2(next(), n);
40
+ case 3:
41
+ from = (x, y, z) => setC3(next(), x, y, z);
42
+ uniform = (n) => setN3(next(), n);
43
+ case 4:
44
+ from = (x, y, z, w) => setC4(next(), x, y, z, w);
45
+ uniform = (n) => setN4(next(), n);
46
+ default:
47
+ from = (...args) => set(next(), args);
48
+ uniform = (n) => setN(next(), n);
49
+ }
50
+ this.next = next;
51
+ this.from = from;
52
+ this.uniform = uniform;
53
+ }
54
+ reset() {
55
+ this.index = 0;
56
+ return this;
57
+ }
58
+ }
59
+ const CAP = 2048;
60
+ export const POOL_VEC2 = new Pool("f32", 2, CAP);
61
+ export const POOL_VEC3 = new Pool("f32", 3, CAP);
62
+ export const POOL_VEC4 = new Pool("f32", 4, CAP);
63
+ export const POOL_IVEC2 = new Pool("i32", 2, CAP);
64
+ export const POOL_IVEC3 = new Pool("i32", 3, CAP);
65
+ export const POOL_IVEC4 = new Pool("i32", 4, CAP);
66
+ export const POOL_UVEC2 = new Pool("u32", 2, CAP);
67
+ export const POOL_UVEC3 = new Pool("u32", 3, CAP);
68
+ export const POOL_UVEC4 = new Pool("u32", 4, CAP);
package/runtime.d.ts CHANGED
@@ -1,41 +1,54 @@
1
- import type { Fn } from "@thi.ng/api";
1
+ import type { Fn, UIntArray } from "@thi.ng/api";
2
2
  import { type IntBuffer } from "@thi.ng/pixel/int";
3
3
  import type { ReadonlyVec, Vec } from "@thi.ng/vectors";
4
+ export interface RenderPixelOpts {
5
+ x?: number;
6
+ y?: number;
7
+ w?: number;
8
+ h?: number;
9
+ bufW: number;
10
+ bufH: number;
11
+ offsetX?: number;
12
+ offsetY?: number;
13
+ imgH?: number;
14
+ /**
15
+ * Format conversion from float RGBA to packed integer.
16
+ */
17
+ fmt: Fn<ReadonlyVec, number>;
18
+ }
19
+ export declare const rgbaBgra8888: (rgba: ReadonlyVec) => number;
20
+ export declare const rgbaRgb565: (rgba: ReadonlyVec) => number;
4
21
  /**
5
22
  * Low-level function used by {@link canvasRenderer} and {@link renderBuffer}.
6
- * Applies shader function `fn` to each pixel in the given region of the `u32`
7
- * raw ABGR buffer (a `Uint32Array`). The region is defined by the top-left `x`,
8
- * `y` coords and `w`, `h` dimensions. The remaining parameters `bufW`, `bufH`,
9
- * `bufOffsetX`, `bufOffsetY` and `imgH` are used to define the actual location
10
- * of the given buffer in the full image to be computed and to support use cases
11
- * where the target array only defines a sub-region of the full image (e.g. when
12
- * splitting rendering over multiple workers, each with their own buffer).
23
+ * Applies shader function `fn` to each pixel in the given region of the
24
+ * `pixels` buffer (e.g. a `Uint32Array`). The render region is defined via
25
+ * options. The top-left `x`, `y` coords and `w`, `h` dimensions. The remaining
26
+ * parameters `bufW`, `bufH`, `bufOffsetX`, `bufOffsetY` and `imgH` are used to
27
+ * define the actual location of the given buffer in the full image to be
28
+ * computed and to support use cases where the target array only defines a
29
+ * sub-region of the full image (e.g. when splitting rendering over multiple
30
+ * workers, each with their own buffer).
13
31
  *
14
32
  * @param fn -
15
- * @param u32 -
16
- * @param bufW -
17
- * @param bufH -
18
- * @param x -
19
- * @param y -
20
- * @param w -
21
- * @param h -
22
- * @param bufOffsetX -
23
- * @param bufOffsetY -
24
- * @param imgH -
33
+ * @param pixels -
34
+ * @param opts
25
35
  */
26
- export declare const renderPixels: (fn: Fn<ReadonlyVec, Vec>, u32: Uint32Array, bufW: number, bufH: number, x: number, y: number, w?: number, h?: number, bufOffsetX?: number, bufOffsetY?: number, imgH?: number) => Uint32Array;
36
+ export declare const renderPixels: (fn: Fn<ReadonlyVec, Vec>, pixels: UIntArray, { x, y, w, h, bufW, bufH, offsetX, offsetY, imgH, fmt }: RenderPixelOpts) => UIntArray;
27
37
  /**
28
38
  * Takes a
29
39
  * [`IntBuffer`](https://docs.thi.ng/umbrella/pixel/classes/IntBuffer.html)
30
- * pixel buffer from thi.ng/pixel w/
31
- * [`ABGR8888`](https://docs.thi.ng/umbrella/pixel/variables/ABGR8888.html)
32
- * format, an optional buffer local region defined by `x`, `y`, `w`, `h` and
33
- * applies shader function `fn` to each pixel in that region (or full buffer by
34
- * default).
40
+ * pixel buffer from thi.ng/pixel, and options to define a buffer-local render
41
+ * region. Applies shader function `fn` to each pixel in that region (or the
42
+ * full buffer by default).
43
+ *
44
+ * @remarks
45
+ * In case the buffer only defines a sub-region of a larger image,
46
+ * {@link RenderPixelOpts.offsetX}, {@link RenderPixelOpts.offsetY} and
47
+ * {@link RenderPixelOpts.imgH} can be given to configure the location and full
48
+ * image height.
35
49
  *
36
- * In case the buffer only defines a sub-region of a larger image, `bufOffsetX`,
37
- * `bufOffsetY` and `imgH` can be given to configure the location and full image
38
- * height.
50
+ * The default target pixel format is `ABGR8888` using {@link rgbaBgra8888} as
51
+ * default {@link RenderPixelOpts.fmt} conversion.
39
52
  *
40
53
  * @param fn -
41
54
  * @param buf -
@@ -47,7 +60,7 @@ export declare const renderPixels: (fn: Fn<ReadonlyVec, Vec>, u32: Uint32Array,
47
60
  * @param bufOffsetY -
48
61
  * @param imgH -
49
62
  */
50
- export declare const renderBuffer: (fn: Fn<ReadonlyVec, Vec>, buf: IntBuffer, x?: number, y?: number, w?: number, h?: number, bufOffsetX?: number, bufOffsetY?: number, imgH?: number) => IntBuffer;
63
+ export declare const renderBuffer: (fn: Fn<ReadonlyVec, Vec>, buf: IntBuffer, opts?: Partial<RenderPixelOpts>) => IntBuffer;
51
64
  /**
52
65
  * Higher order function accepting an `HTMLCanvasElement` and returning a render
53
66
  * function which accepts the following parameters:
package/runtime.js CHANGED
@@ -1,62 +1,62 @@
1
- import { assert } from "@thi.ng/errors/assert";
2
1
  import { clamp, clamp01 } from "@thi.ng/math/interval";
3
- import { ABGR8888 } from "@thi.ng/pixel/format/abgr8888";
4
2
  import { intBufferFromCanvas } from "@thi.ng/pixel/int";
5
- const rgba2bgra = (rgba) => ((clamp01(rgba[0]) * 255.5) << 0) |
3
+ export const rgbaBgra8888 = (rgba) => ((clamp01(rgba[0]) * 255.5) << 0) |
6
4
  ((clamp01(rgba[1]) * 255.5) << 8) |
7
5
  ((clamp01(rgba[2]) * 255.5) << 16) |
8
6
  ((clamp01(rgba[3]) * 255.5) << 24);
7
+ export const rgbaRgb565 = (rgba) => (((clamp01(rgba[0]) * 255.5) & 0xf8) << 8) |
8
+ (((clamp01(rgba[1]) * 255.5) & 0xfc) << 3) |
9
+ (((clamp01(rgba[2]) * 255.5) & 0xf8) >> 3);
9
10
  const clampCoord = (x, maxW, w) => w !== undefined ? Math.min(x + w, maxW) : maxW;
10
11
  /**
11
12
  * Low-level function used by {@link canvasRenderer} and {@link renderBuffer}.
12
- * Applies shader function `fn` to each pixel in the given region of the `u32`
13
- * raw ABGR buffer (a `Uint32Array`). The region is defined by the top-left `x`,
14
- * `y` coords and `w`, `h` dimensions. The remaining parameters `bufW`, `bufH`,
15
- * `bufOffsetX`, `bufOffsetY` and `imgH` are used to define the actual location
16
- * of the given buffer in the full image to be computed and to support use cases
17
- * where the target array only defines a sub-region of the full image (e.g. when
18
- * splitting rendering over multiple workers, each with their own buffer).
13
+ * Applies shader function `fn` to each pixel in the given region of the
14
+ * `pixels` buffer (e.g. a `Uint32Array`). The render region is defined via
15
+ * options. The top-left `x`, `y` coords and `w`, `h` dimensions. The remaining
16
+ * parameters `bufW`, `bufH`, `bufOffsetX`, `bufOffsetY` and `imgH` are used to
17
+ * define the actual location of the given buffer in the full image to be
18
+ * computed and to support use cases where the target array only defines a
19
+ * sub-region of the full image (e.g. when splitting rendering over multiple
20
+ * workers, each with their own buffer).
19
21
  *
20
22
  * @param fn -
21
- * @param u32 -
22
- * @param bufW -
23
- * @param bufH -
24
- * @param x -
25
- * @param y -
26
- * @param w -
27
- * @param h -
28
- * @param bufOffsetX -
29
- * @param bufOffsetY -
30
- * @param imgH -
23
+ * @param pixels -
24
+ * @param opts
31
25
  */
32
- export const renderPixels = (fn, u32, bufW, bufH, x, y, w, h, bufOffsetX = 0, bufOffsetY = 0, imgH = bufH) => {
33
- const frag = [];
34
- x = clamp(x, 0, bufW);
35
- y = clamp(y, 0, bufH);
26
+ export const renderPixels = (fn, pixels, { x, y, w, h, bufW, bufH, offsetX, offsetY, imgH, fmt }) => {
27
+ offsetX = offsetX || 0;
28
+ offsetY = offsetY || 0;
29
+ imgH = (imgH || bufH) - 1 - offsetY;
30
+ x = clamp(x || 0, 0, bufW);
31
+ y = clamp(y || 0, 0, bufH);
36
32
  const x2 = clampCoord(x, bufW, w);
37
33
  const y2 = clampCoord(y, bufH, h);
34
+ const fragCoord = [];
38
35
  for (let yy = y; yy < y2; yy++) {
39
- frag[1] = imgH - 1 - yy - bufOffsetY;
36
+ fragCoord[1] = imgH - yy;
40
37
  let i = yy * bufW + x;
41
38
  for (let xx = x; xx < x2; xx++) {
42
- frag[0] = xx + bufOffsetX;
43
- u32[i++] = rgba2bgra(fn(frag));
39
+ fragCoord[0] = xx + offsetX;
40
+ pixels[i++] = fmt(fn(fragCoord));
44
41
  }
45
42
  }
46
- return u32;
43
+ return pixels;
47
44
  };
48
45
  /**
49
46
  * Takes a
50
47
  * [`IntBuffer`](https://docs.thi.ng/umbrella/pixel/classes/IntBuffer.html)
51
- * pixel buffer from thi.ng/pixel w/
52
- * [`ABGR8888`](https://docs.thi.ng/umbrella/pixel/variables/ABGR8888.html)
53
- * format, an optional buffer local region defined by `x`, `y`, `w`, `h` and
54
- * applies shader function `fn` to each pixel in that region (or full buffer by
55
- * default).
48
+ * pixel buffer from thi.ng/pixel, and options to define a buffer-local render
49
+ * region. Applies shader function `fn` to each pixel in that region (or the
50
+ * full buffer by default).
51
+ *
52
+ * @remarks
53
+ * In case the buffer only defines a sub-region of a larger image,
54
+ * {@link RenderPixelOpts.offsetX}, {@link RenderPixelOpts.offsetY} and
55
+ * {@link RenderPixelOpts.imgH} can be given to configure the location and full
56
+ * image height.
56
57
  *
57
- * In case the buffer only defines a sub-region of a larger image, `bufOffsetX`,
58
- * `bufOffsetY` and `imgH` can be given to configure the location and full image
59
- * height.
58
+ * The default target pixel format is `ABGR8888` using {@link rgbaBgra8888} as
59
+ * default {@link RenderPixelOpts.fmt} conversion.
60
60
  *
61
61
  * @param fn -
62
62
  * @param buf -
@@ -68,9 +68,15 @@ export const renderPixels = (fn, u32, bufW, bufH, x, y, w, h, bufOffsetX = 0, bu
68
68
  * @param bufOffsetY -
69
69
  * @param imgH -
70
70
  */
71
- export const renderBuffer = (fn, buf, x = 0, y = 0, w, h, bufOffsetX = 0, bufOffsetY = 0, imgH = buf.height) => {
72
- assert(buf.format === ABGR8888, `invalid buffer pixel format`);
73
- renderPixels(fn, buf.data, buf.width, buf.height, x, y, w, h, bufOffsetX, bufOffsetY, imgH);
71
+ export const renderBuffer = (fn, buf, opts) => {
72
+ renderPixels(fn, buf.data, {
73
+ fmt: rgbaBgra8888,
74
+ bufW: buf.width,
75
+ bufH: buf.height,
76
+ x: 0,
77
+ y: 0,
78
+ ...opts,
79
+ });
74
80
  return buf;
75
81
  };
76
82
  /**
@@ -87,7 +93,7 @@ export const canvasRenderer = (canvas) => {
87
93
  const buf = intBufferFromCanvas(canvas);
88
94
  const data = new ImageData(canvas.width, canvas.height);
89
95
  return (fn, x = 0, y = 0, w = canvas.width, h = canvas.height) => {
90
- renderBuffer(fn, buf, x, y, w, h);
96
+ renderBuffer(fn, buf, { x, y, w, h });
91
97
  buf.blitCanvas(canvas, { data });
92
98
  };
93
99
  };
package/target.js CHANGED
@@ -31,22 +31,25 @@ const OP_IDS = {
31
31
  "<<": "lshift",
32
32
  ">>": "rshift",
33
33
  };
34
- const PRELUDE = [
35
- "float",
36
- "int",
37
- "uint",
34
+ const VEC_TYPES = [
38
35
  "vec2",
39
36
  "vec3",
40
37
  "vec4",
41
- "bvec2",
42
- "bvec3",
43
- "bvec4",
44
38
  "ivec2",
45
39
  "ivec3",
46
40
  "ivec4",
47
41
  "uvec2",
48
42
  "uvec3",
49
43
  "uvec4",
44
+ ];
45
+ const PRELUDE = [
46
+ "float",
47
+ "int",
48
+ "uint",
49
+ ...VEC_TYPES,
50
+ "bvec2",
51
+ "bvec3",
52
+ "bvec4",
50
53
  "mat2",
51
54
  "mat3",
52
55
  "mat4",
@@ -58,8 +61,10 @@ const PRELUDE = [
58
61
  ]
59
62
  .map((x) => `const ${x} = env.${x};`)
60
63
  .join("\n");
64
+ const POOL_PRELUDE = VEC_TYPES.map((x) => `const $${x} = env.pools.${x}.from;`).join("\n");
61
65
  const COMPS = { x: 0, y: 1, z: 2, w: 3 };
62
66
  const RE_SEMI = /[};]$/;
67
+ const RESET = `for(let t in env.pools) env.pools[t].reset();`;
63
68
  const isIntOrBool = (l) => isInt(l) || isUint(l) || isBool(l);
64
69
  const isVecOrMat = (l) => isVec(l) || isMat(l);
65
70
  const swizzle = (id) => [...id].map((x) => COMPS[x]).join(", ");
@@ -79,6 +84,7 @@ export const targetJS = (opts) => {
79
84
  : String;
80
85
  const $list = (body, sep = ", ") => body.map(emit).join(sep);
81
86
  const $fn = (name, args) => `${name}(${$list(args)})`;
87
+ const $vecFromPool = ({ val, info, type }) => !info ? `$${type}(${$list(val)})` : `env.${type}${info}(${$list(val)})`;
82
88
  const $vec = ({ val, info, type }) => !info ? `[${$list(val)}]` : `env.${type}${info}(${$list(val)})`;
83
89
  const $num = (v, f) => isNumber(v) ? String(v) : f(v);
84
90
  const $float = (v, f) => (isNumber(v) ? ff(v) : f(v));
@@ -110,7 +116,19 @@ export const targetJS = (opts) => {
110
116
  : undefined;
111
117
  return res.join(" ");
112
118
  },
113
- fn: (t) => `${buildComments(t)}\nfunction ${t.id}(${$list(t.args)}) ${emit(t.scope)}`,
119
+ fn: (t) => {
120
+ let body;
121
+ if (t.id === "main") {
122
+ body = `{\n${RESET}\n${emit({
123
+ ...t.scope,
124
+ global: true,
125
+ })}}`;
126
+ }
127
+ else {
128
+ body = emit(t.scope);
129
+ }
130
+ return `${buildComments(t)}\nfunction ${t.id}(${$list(t.args)}) ${body}`;
131
+ },
114
132
  for: (t) => `for(${t.init ? emit(t.init) : ""}; ${emit(t.test)}; ${t.iter ? emit(t.iter) : ""}) ${emit(t.scope)}`,
115
133
  idx: (t) => `${emit(t.val)}[${emit(t.id)}]`,
116
134
  idxm: (t) => `${t.val.type}.idx(${emit(t.val)},${emit(t.id)})`,
@@ -132,15 +150,16 @@ export const targetJS = (opts) => {
132
150
  case "vec2":
133
151
  case "vec3":
134
152
  case "vec4":
135
- case "bvec2":
136
- case "bvec3":
137
- case "bvec4":
138
153
  case "ivec2":
139
154
  case "ivec3":
140
155
  case "ivec4":
141
156
  case "uvec2":
142
157
  case "uvec3":
143
158
  case "uvec4":
159
+ return $vecFromPool(t);
160
+ case "bvec2":
161
+ case "bvec3":
162
+ case "bvec4":
144
163
  case "mat2":
145
164
  case "mat3":
146
165
  case "mat4":
@@ -185,7 +204,7 @@ export const targetJS = (opts) => {
185
204
  scope: (t) => {
186
205
  let res = $list(t.body, ";\n");
187
206
  res += t.body.length && !RE_SEMI.test(res) ? ";" : "";
188
- return !t.global ? `{\n${res}\n}` : res;
207
+ return t.global ? res : `{\n${res}\n}`;
189
208
  },
190
209
  swizzle: (t) => t.id.length > 1
191
210
  ? `env.swizzle${t.id.length}(${emit(t.val)}, ${swizzle(t.id)})`
@@ -197,7 +216,16 @@ export const targetJS = (opts) => {
197
216
  Object.assign(emit, {
198
217
  compile: (tree, env = JS_DEFAULT_ENV) => {
199
218
  const exports = buildExports(tree);
200
- return new Function("env", [PRELUDE, emit(tree), "return {", exports, "};"].join("\n"))(env);
219
+ return new Function("env", [
220
+ PRELUDE,
221
+ POOL_PRELUDE,
222
+ emit(tree),
223
+ "return {",
224
+ `__reset: () => {${RESET}},`,
225
+ `__stats: () => Object.entries(env.pools).reduce((acc, [k, v]) => (acc[k] = v.index, acc), {}),`,
226
+ exports,
227
+ "};",
228
+ ].join("\n"))(env);
201
229
  },
202
230
  });
203
231
  return emit;