typegpu 0.12.6 → 0.12.8

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.
Files changed (87) hide show
  1. package/common/writeSoA.js +16 -0
  2. package/core/buffer/buffer.js +47 -11
  3. package/core/commandEncoder/attachments.js +5 -2
  4. package/core/function/autoIO.js +5 -3
  5. package/core/function/comptime.js +7 -5
  6. package/core/function/createCallableSchema.js +4 -3
  7. package/core/function/dualImpl.js +20 -19
  8. package/core/function/ioSchema.d.ts +17 -2
  9. package/core/function/ioSchema.js +74 -27
  10. package/core/function/tgpuFn.d.ts +11 -1
  11. package/core/function/tgpuFn.js +9 -2
  12. package/core/function/tgpuFragmentFn.d.ts +1 -1
  13. package/core/function/tgpuFragmentFn.js +2 -1
  14. package/core/function/tgpuVertexFn.js +2 -1
  15. package/core/pipeline/connectAttachmentToShader.d.ts +2 -2
  16. package/core/pipeline/connectAttachmentToShader.js +16 -21
  17. package/core/pipeline/connectTargetsToShader.d.ts +5 -1
  18. package/core/pipeline/connectTargetsToShader.js +23 -18
  19. package/core/pipeline/renderPipeline.d.ts +9 -6
  20. package/core/pipeline/renderPipeline.js +22 -22
  21. package/core/pipeline/timeable.js +15 -2
  22. package/core/resolve/namespace.js +2 -2
  23. package/core/root/configurableImpl.js +1 -2
  24. package/core/root/rootTypes.d.ts +6 -2
  25. package/core/slot/accessor.d.ts +1 -1
  26. package/core/slot/accessor.js +72 -5
  27. package/core/texture/externalTexture.d.ts +1 -1
  28. package/core/texture/externalTexture.js +3 -1
  29. package/core/texture/texture.d.ts +1 -1
  30. package/core/texture/texture.js +12 -5
  31. package/core/unroll/tgpuUnroll.d.ts +4 -3
  32. package/core/unroll/tgpuUnroll.js +7 -1
  33. package/core/valueProxyUtils.d.ts +4 -2
  34. package/core/valueProxyUtils.js +4 -2
  35. package/data/autoStruct.d.ts +1 -1
  36. package/data/autoStruct.js +8 -4
  37. package/data/compiledIO.js +12 -9
  38. package/data/dataIO.d.ts +10 -6
  39. package/data/dataIO.js +19 -4
  40. package/data/dataTypes.d.ts +0 -5
  41. package/data/dataTypes.js +0 -6
  42. package/data/deepEqual.js +19 -9
  43. package/data/matrix.js +10 -3
  44. package/data/matrixColumnsAccess.d.ts +8 -0
  45. package/data/matrixColumnsAccess.js +15 -0
  46. package/data/offsetUtils.js +181 -102
  47. package/data/partialIO.d.ts +8 -1
  48. package/data/partialIO.js +36 -19
  49. package/data/ref.d.ts +12 -5
  50. package/data/ref.js +51 -24
  51. package/data/snippet.d.ts +13 -4
  52. package/data/snippet.js +17 -4
  53. package/data/struct.js +3 -0
  54. package/data/wgslTypes.d.ts +6 -3
  55. package/internal.d.ts +1 -0
  56. package/nameUtils.d.ts +7 -0
  57. package/nameUtils.js +60 -0
  58. package/package.json +1 -1
  59. package/resolutionCtx.js +1 -1
  60. package/shared/meta.js +1 -1
  61. package/shared/symbols.d.ts +5 -0
  62. package/shared/symbols.js +6 -1
  63. package/shared/tseynit.js +54 -50
  64. package/std/boolean.js +9 -10
  65. package/std/numeric.js +1 -4
  66. package/std/operators.js +9 -4
  67. package/std/packing.js +10 -10
  68. package/std/texture.d.ts +2 -2
  69. package/tgpuLogger.d.ts +1 -1
  70. package/tgpuLogger.js +0 -2
  71. package/tgsl/accessIndex.js +37 -38
  72. package/tgsl/accessProp.js +10 -7
  73. package/tgsl/conversion.js +3 -2
  74. package/tgsl/forOfUtils.js +8 -8
  75. package/tgsl/generationHelpers.d.ts +4 -3
  76. package/tgsl/generationHelpers.js +18 -5
  77. package/tgsl/infixDispatch.d.ts +4 -1
  78. package/tgsl/infixDispatch.js +13 -1
  79. package/tgsl/makeDereferenceable.d.ts +1 -1
  80. package/tgsl/makeDereferenceable.js +3 -1
  81. package/tgsl/shellless.js +5 -9
  82. package/tgsl/stringifySnippet.d.ts +9 -0
  83. package/tgsl/stringifySnippet.js +24 -0
  84. package/tgsl/wgslGenerator.d.ts +1 -0
  85. package/tgsl/wgslGenerator.js +180 -86
  86. package/types.d.ts +23 -2
  87. package/types.js +30 -5
package/data/snippet.d.ts CHANGED
@@ -7,25 +7,26 @@ export type Origin = 'uniform' | 'readonly' | 'mutable' | 'workgroup' | 'private
7
7
  */
8
8
  export declare function fallthroughCopyOrigin(origin: Origin): Origin;
9
9
  /**
10
- * Whether a snippet aliases a value that lives outside the current expression.
10
+ * Whether a snippet refers to a value already stored in memory.
11
11
  *
12
12
  * @example
13
13
  * ```ts
14
14
  * function foo(a: number) {
15
+ * 'use gpu';
15
16
  * const color = d.vec3f(1, 2, 3);
16
17
  * return color * a;
17
18
  * }
18
19
  *
19
- * // References:
20
+ * // Stored in memory:
20
21
  * // - color
21
22
  * // - a
22
23
  * //
23
- * // Not references:
24
+ * // Ephemeral values:
24
25
  * // - d.vec3f(1, 2, 3)
25
26
  * // - color * a
26
27
  * ```
27
28
  */
28
- export declare function isAlias(snippet: Snippet): boolean;
29
+ export declare function isStoredInMemory(snippet: Snippet): boolean;
29
30
  export declare const originToPtrParams: {
30
31
  readonly uniform: {
31
32
  readonly space: "uniform";
@@ -61,6 +62,14 @@ export declare const originToPtrParams: {
61
62
  };
62
63
  };
63
64
  export type OriginToPtrParams = typeof originToPtrParams;
65
+ /**
66
+ * Whether values of this origin can have their address taken.
67
+ */
68
+ export declare function isAddressableOrigin(origin: Origin): origin is keyof OriginToPtrParams;
69
+ /**
70
+ * Whether values of this origin can be mutated.
71
+ */
72
+ export declare function isMutableOrigin(origin: Origin): boolean;
64
73
  export interface Snippet {
65
74
  readonly value: unknown;
66
75
  /**
package/data/snippet.js CHANGED
@@ -16,25 +16,26 @@ export function fallthroughCopyOrigin(origin) {
16
16
  return 'runtime';
17
17
  }
18
18
  /**
19
- * Whether a snippet aliases a value that lives outside the current expression.
19
+ * Whether a snippet refers to a value already stored in memory.
20
20
  *
21
21
  * @example
22
22
  * ```ts
23
23
  * function foo(a: number) {
24
+ * 'use gpu';
24
25
  * const color = d.vec3f(1, 2, 3);
25
26
  * return color * a;
26
27
  * }
27
28
  *
28
- * // References:
29
+ * // Stored in memory:
29
30
  * // - color
30
31
  * // - a
31
32
  * //
32
- * // Not references:
33
+ * // Ephemeral values:
33
34
  * // - d.vec3f(1, 2, 3)
34
35
  * // - color * a
35
36
  * ```
36
37
  */
37
- export function isAlias(snippet) {
38
+ export function isStoredInMemory(snippet) {
38
39
  return !(snippet.origin === 'runtime' || snippet.origin === 'constant');
39
40
  }
40
41
  export const originToPtrParams = {
@@ -48,6 +49,18 @@ export const originToPtrParams = {
48
49
  // Local declarations are also in the `function` address space
49
50
  'local-def': { space: 'function', access: 'read-write' },
50
51
  };
52
+ /**
53
+ * Whether values of this origin can have their address taken.
54
+ */
55
+ export function isAddressableOrigin(origin) {
56
+ return origin in originToPtrParams;
57
+ }
58
+ /**
59
+ * Whether values of this origin can be mutated.
60
+ */
61
+ export function isMutableOrigin(origin) {
62
+ return isAddressableOrigin(origin) && originToPtrParams[origin].access === 'read-write';
63
+ }
51
64
  class SnippetImpl {
52
65
  value;
53
66
  dataType;
package/data/struct.js CHANGED
@@ -17,6 +17,9 @@ import { schemaCallWrapper } from "./schemaCallWrapper.js";
17
17
  * each entry describing one struct member.
18
18
  */
19
19
  export function struct(props) {
20
+ if (Object.keys(props).length === 0) {
21
+ throw new Error('Cannot create struct schema with no properties.');
22
+ }
20
23
  return INTERNAL_createStruct(props, false);
21
24
  }
22
25
  export function abstruct(props) {
@@ -704,11 +704,12 @@ export interface Mat2x2f extends BaseData {
704
704
  readonly type: 'mat2x2f';
705
705
  readonly primitive: F32;
706
706
  readonly [$repr]: m2x2f;
707
- readonly [$inRepr]: m2x2f | readonly number[] | Float32Array;
707
+ readonly [$inRepr]: m2x2f | readonly [number, number, number, number] | Float32Array;
708
708
  readonly [$validStorageSchema]: true;
709
709
  readonly [$validUniformSchema]: true;
710
710
  (...elements: [number, number, number, number]): m2x2f;
711
711
  (...columns: [v2f, v2f]): m2x2f;
712
+ (other: m2x2f): m2x2f;
712
713
  (): m2x2f;
713
714
  identity(): m2x2f;
714
715
  }
@@ -719,11 +720,12 @@ export interface Mat3x3f extends BaseData {
719
720
  readonly type: 'mat3x3f';
720
721
  readonly primitive: F32;
721
722
  readonly [$repr]: m3x3f;
722
- readonly [$inRepr]: m3x3f | readonly number[] | Float32Array;
723
+ readonly [$inRepr]: m3x3f | readonly [number, number, number, number, number, number, number, number, number] | Float32Array;
723
724
  readonly [$validStorageSchema]: true;
724
725
  readonly [$validUniformSchema]: true;
725
726
  (...elements: [number, number, number, number, number, number, number, number, number]): m3x3f;
726
727
  (...columns: [v3f, v3f, v3f]): m3x3f;
728
+ (other: m3x3f): m3x3f;
727
729
  (): m3x3f;
728
730
  identity(): m3x3f;
729
731
  }
@@ -734,11 +736,12 @@ export interface Mat4x4f extends BaseData {
734
736
  readonly type: 'mat4x4f';
735
737
  readonly primitive: F32;
736
738
  readonly [$repr]: m4x4f;
737
- readonly [$inRepr]: m4x4f | readonly number[] | Float32Array;
739
+ readonly [$inRepr]: m4x4f | readonly [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number] | Float32Array;
738
740
  readonly [$validStorageSchema]: true;
739
741
  readonly [$validUniformSchema]: true;
740
742
  (...elements: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]): m4x4f;
741
743
  (...columns: [v4f, v4f, v4f, v4f]): m4x4f;
744
+ (other: m4x4f): m4x4f;
742
745
  (): m4x4f;
743
746
  identity(): m4x4f;
744
747
  translation(vec: v3f): m4x4f;
package/internal.d.ts CHANGED
@@ -13,4 +13,5 @@ export type { RestoreContext } from './serial/types.ts';
13
13
  export { deserializeDataSchema, serializeDataSchema, type SerializedDataSchema, } from './serial/schema.ts';
14
14
  export type { ResolutionCtx, FunctionArgument } from './types.ts';
15
15
  export type { Snippet, ResolvedSnippet, Origin } from './data/snippet.ts';
16
+ export type { AutoStruct } from './data/autoStruct.ts';
16
17
  export type { ShaderGenerator, ResolvedStatement, BinaryOperator, ShaderGeneratorClass, FunctionDefinitionOptions, ConstantDefinitionOptions, VariableDefinitionOptions, } from './tgsl/shaderGenerator.ts';
package/nameUtils.d.ts CHANGED
@@ -1,4 +1,11 @@
1
1
  export declare const bannedTokens: Set<string>;
2
+ /**
3
+ * Predeclared enumerants that appear as template parameters, e.g. `var<storage, read_write>` or
4
+ * `texture_storage_2d<rgba8unorm, write>`. A module-scope or local declaration with one of these names
5
+ * would shadow the enumerant, so a template using it would refer to the declaration instead.
6
+ * Unlike `bannedTokens`, these remain valid as struct member names, which never shadow anything.
7
+ */
8
+ export declare const templateEnumerants: Set<string>;
2
9
  export declare const builtins: Set<string>;
3
10
  /**
4
11
  * Sanitizes the primer so that it is compliant with WGSL guidelines.
package/nameUtils.js CHANGED
@@ -178,6 +178,66 @@ export const bannedTokens = new Set([
178
178
  'uniform',
179
179
  'storage',
180
180
  ]);
181
+ /**
182
+ * Predeclared enumerants that appear as template parameters, e.g. `var<storage, read_write>` or
183
+ * `texture_storage_2d<rgba8unorm, write>`. A module-scope or local declaration with one of these names
184
+ * would shadow the enumerant, so a template using it would refer to the declaration instead.
185
+ * Unlike `bannedTokens`, these remain valid as struct member names, which never shadow anything.
186
+ */
187
+ export const templateEnumerants = new Set([
188
+ // address spaces
189
+ 'function',
190
+ 'private',
191
+ 'workgroup',
192
+ 'uniform',
193
+ 'storage',
194
+ 'immediate',
195
+ // access modes
196
+ 'read',
197
+ 'write',
198
+ 'read_write',
199
+ // texel formats
200
+ 'rgba8unorm',
201
+ 'rgba8snorm',
202
+ 'rgba8uint',
203
+ 'rgba8sint',
204
+ 'rgba16unorm',
205
+ 'rgba16snorm',
206
+ 'rgba16uint',
207
+ 'rgba16sint',
208
+ 'rgba16float',
209
+ 'rg8unorm',
210
+ 'rg8snorm',
211
+ 'rg8uint',
212
+ 'rg8sint',
213
+ 'rg16unorm',
214
+ 'rg16snorm',
215
+ 'rg16uint',
216
+ 'rg16sint',
217
+ 'rg16float',
218
+ 'r8unorm',
219
+ 'r8snorm',
220
+ 'r8uint',
221
+ 'r8sint',
222
+ 'r16unorm',
223
+ 'r16snorm',
224
+ 'r16uint',
225
+ 'r16sint',
226
+ 'r16float',
227
+ 'r32uint',
228
+ 'r32sint',
229
+ 'r32float',
230
+ 'rg32uint',
231
+ 'rg32sint',
232
+ 'rg32float',
233
+ 'rgba32uint',
234
+ 'rgba32sint',
235
+ 'rgba32float',
236
+ 'bgra8unorm',
237
+ 'rgb10a2unorm',
238
+ 'rgb10a2uint',
239
+ 'rg11b10ufloat',
240
+ ]);
181
241
  export const builtins = new Set([
182
242
  // constructors
183
243
  'array',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegpu",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "description": "A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.",
5
5
  "keywords": [
6
6
  "compute",
package/resolutionCtx.js CHANGED
@@ -417,7 +417,7 @@ export class ResolutionCtxImpl {
417
417
  return this._indentController.dedent();
418
418
  }
419
419
  getDedented(code) {
420
- return code.replaceAll(`\n${INDENT[1]}`, '\n');
420
+ return code.replaceAll(`\n${INDENT[1]}`, '\n').replaceAll(new RegExp(`^${INDENT[1]}`, 'g'), '');
421
421
  }
422
422
  withResetIndentLevel(callback) {
423
423
  return this._indentController.withResetLevel(callback);
package/shared/meta.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.12.6";
1
+ const version = "0.12.8";
2
2
  import { DEV, TEST } from "./env.js";
3
3
  import { $getNameForward, $soul, isMarkedInternal } from "./symbols.js";
4
4
  import { normalizeMetadata } from "./normalizeMetadata.js";
@@ -18,6 +18,11 @@ export declare const $providing: unique symbol;
18
18
  * Objects can provide the snippet that represents them.
19
19
  */
20
20
  export declare const $ownSnippet: unique symbol;
21
+ /**
22
+ * Objects can provide the value that they represent at comptime (e.g. when passed
23
+ * into comptime functions), or signal that they are not known at comptime.
24
+ */
25
+ export declare const $comptimeValueOf: unique symbol;
21
26
  export declare const $resolve: unique symbol;
22
27
  /**
23
28
  * A way for a schema to provide casting behavior, without the need to be explicitly
package/shared/symbols.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.12.6";
1
+ const version = "0.12.8";
2
2
  export const $internal = Symbol(`typegpu:${version}:$internal`);
3
3
  /** A plain record of all definitional state of a resource, surviving transfer between runtimes */
4
4
  export const $soul = Symbol(`typegpu:${version}:$soul`);
@@ -19,6 +19,11 @@ export const $providing = Symbol(`typegpu:${version}:$providing`);
19
19
  * Objects can provide the snippet that represents them.
20
20
  */
21
21
  export const $ownSnippet = Symbol(`typegpu:${version}:$ownSnippet`);
22
+ /**
23
+ * Objects can provide the value that they represent at comptime (e.g. when passed
24
+ * into comptime functions), or signal that they are not known at comptime.
25
+ */
26
+ export const $comptimeValueOf = Symbol(`typegpu:${version}:$comptimeValueOf`);
22
27
  export const $resolve = Symbol(`typegpu:${version}:$resolve`);
23
28
  /**
24
29
  * A way for a schema to provide casting behavior, without the need to be explicitly
package/shared/tseynit.js CHANGED
@@ -2,90 +2,94 @@ import * as tinyest from 'tinyest';
2
2
  const { NodeTypeCatalog: NODE } = tinyest;
3
3
  export function stringifyNode(node) {
4
4
  if (isExpression(node)) {
5
- return stringifyExpression(node, '');
5
+ return stringifyExpression(node);
6
6
  }
7
7
  return stringifyStatement(node, '');
8
8
  }
9
- function stringifyStatement(node, ident) {
9
+ function stringifyStatement(node, indent) {
10
10
  if (isExpression(node)) {
11
- return `${ident}${stringifyExpression(node, ident)};`;
11
+ return `${indent}${stringifyExpression(node)};`;
12
12
  }
13
13
  if (node[0] === NODE.block) {
14
- const statements = node[1].map((n) => stringifyStatement(n, ident + ' '));
15
- return `{\n${statements.join('\n')}\n${ident}}`;
14
+ const statements = node[1].map((n) => stringifyStatement(n, indent + ' '));
15
+ return `{\n${statements.join('\n')}\n${indent}}`;
16
16
  }
17
17
  if (node[0] === NODE.return) {
18
- const expr = node[1] === undefined ? '' : ` ${stringifyExpression(node[1], '')}`;
19
- return `${ident}return${expr};`;
18
+ const expr = node[1] === undefined ? '' : ` ${stringifyExpression(node[1])}`;
19
+ return `${indent}return${expr};`;
20
20
  }
21
21
  if (node[0] === NODE.if) {
22
- const cond = stringifyExpression(node[1], ident);
23
- const then = stringifyStatement(node[2], ident);
24
- const base = `${ident}if (${cond}) ${then}`;
22
+ const cond = stringifyExpression(node[1]);
23
+ const then = stringifyStatement(node[2], indent);
24
+ const base = `${indent}if (${cond}) ${then}`;
25
25
  if (node[3] !== undefined) {
26
- return `${base} else ${stringifyStatement(node[3], ident)}`;
26
+ return `${base} else ${stringifyStatement(node[3], indent)}`;
27
27
  }
28
28
  return base;
29
29
  }
30
30
  if (node[0] === NODE.let) {
31
- const decl = `${ident}let ${stringifyExpression(node[1], ident)}`;
31
+ const decl = `${indent}let ${stringifyExpression(node[1])}`;
32
32
  if (node[2] !== undefined) {
33
- return `${decl} = ${stringifyExpression(node[2], ident)};`;
33
+ return `${decl} = ${stringifyExpression(node[2])};`;
34
34
  }
35
35
  return `${decl};`;
36
36
  }
37
37
  if (node[0] === NODE.const) {
38
- const decl = `${ident}const ${stringifyExpression(node[1], ident)}`;
38
+ const decl = `${indent}const ${stringifyExpression(node[1])}`;
39
39
  if (node[2] !== undefined) {
40
- return `${decl} = ${stringifyExpression(node[2], ident)};`;
40
+ return `${decl} = ${stringifyExpression(node[2])};`;
41
41
  }
42
42
  return `${decl};`;
43
43
  }
44
44
  if (node[0] === NODE.for) {
45
45
  const init = node[1] ? stringifyStatement(node[1], '') : ';';
46
- const cond = node[2] ? stringifyExpression(node[2], ident) : '';
46
+ const cond = node[2] ? stringifyExpression(node[2]) : '';
47
47
  const update = node[3] ? stringifyStatement(node[3], '') : '';
48
- const body = stringifyStatement(node[4], ident);
49
- return `${ident}for (${init} ${cond}; ${update.slice(0, -1) /* trim the ';' */}) ${body}`;
48
+ const body = stringifyStatement(node[4], indent);
49
+ return `${indent}for (${init} ${cond}; ${update.slice(0, -1) /* trim the ';' */}) ${body}`;
50
50
  }
51
51
  if (node[0] === NODE.while) {
52
- const cond = stringifyExpression(node[1], ident);
53
- const body = stringifyStatement(node[2], ident);
54
- return `${ident}while (${cond}) ${body}`;
52
+ const cond = stringifyExpression(node[1]);
53
+ const body = stringifyStatement(node[2], indent);
54
+ return `${indent}while (${cond}) ${body}`;
55
55
  }
56
56
  if (node[0] === NODE.continue) {
57
- return `${ident}continue;`;
57
+ return `${indent}continue;`;
58
58
  }
59
59
  if (node[0] === NODE.break) {
60
- return `${ident}break;`;
60
+ return `${indent}break;`;
61
61
  }
62
62
  if (node[0] === NODE.forOf) {
63
63
  const leftKind = node[1][0] === NODE.const ? 'const' : 'let';
64
- const leftName = stringifyExpression(node[1][1], ident);
65
- const right = stringifyExpression(node[2], ident);
66
- const body = stringifyStatement(node[3], ident);
67
- return `${ident}for (${leftKind} ${leftName} of ${right}) ${body}`;
64
+ const leftName = stringifyExpression(node[1][1]);
65
+ const right = stringifyExpression(node[2]);
66
+ const body = stringifyStatement(node[3], indent);
67
+ return `${indent}for (${leftKind} ${leftName} of ${right}) ${body}`;
68
68
  }
69
69
  if (node[0] === NODE.switch) {
70
- const discriminant = stringifyExpression(node[1], ident);
70
+ const discriminant = stringifyExpression(node[1]);
71
71
  const cases = node[2].map((c) => {
72
- const test = c[0] === null ? 'default' : `case ${stringifyExpression(c[0], ident)}`;
72
+ const test = c[0] === null ? 'default' : `case ${stringifyExpression(c[0])}`;
73
73
  if (c[1].length === 0) {
74
74
  return `${test}:`;
75
75
  }
76
- const consequents = c[1].map((s) => stringifyStatement(s, ident + ' '));
76
+ const consequents = c[1].map((s) => stringifyStatement(s, indent + ' '));
77
77
  return `${test}:\n${consequents.join('\n')}`;
78
78
  });
79
- return `${ident}switch (${discriminant}) {\n${cases.map((c) => `${ident + ' '}${c}`).join('\n')}\n${ident}}`;
79
+ return `${indent}switch (${discriminant}) {\n${cases.map((c) => `${indent + ' '}${c}`).join('\n')}\n${indent}}`;
80
80
  }
81
81
  assertExhaustive(node);
82
82
  }
83
+ const JS_IDENTIFIER = /^[$_\p{ID_Start}][$_\u{200c}\u{200d}\p{ID_Continue}]*$/u;
84
+ function stringifyObjectPropertyKey(key) {
85
+ return JS_IDENTIFIER.test(key) ? key : JSON.stringify(key);
86
+ }
83
87
  export function stringifyObjectProperty([key, value, computed]) {
84
- const keyStr = computed ? `[${stringifyExpression(key, '')}]` : stringifyExpression(key, '');
85
- const valueStr = stringifyExpression(value, '');
88
+ const keyStr = computed ? `[${stringifyExpression(key)}]` : stringifyObjectPropertyKey(key);
89
+ const valueStr = stringifyExpression(value);
86
90
  return `${keyStr}: ${valueStr}`;
87
91
  }
88
- function stringifyExpression(node, ident) {
92
+ function stringifyExpression(node) {
89
93
  if (typeof node === 'string') {
90
94
  return node;
91
95
  }
@@ -105,52 +109,52 @@ function stringifyExpression(node, ident) {
105
109
  return JSON.stringify(node[1]);
106
110
  }
107
111
  if (node[0] === NODE.arrayExpr) {
108
- const elements = node[1].map((n) => stringifyExpression(n, ident));
112
+ const elements = node[1].map((n) => stringifyExpression(n));
109
113
  return `[${elements.join(', ')}]`;
110
114
  }
111
115
  if (node[0] === NODE.binaryExpr) {
112
- return `${wrapIfComplex(node[1], ident)} ${node[2]} ${wrapIfComplex(node[3], ident)}`;
116
+ return `${wrapIfComplex(node[1])} ${node[2]} ${wrapIfComplex(node[3])}`;
113
117
  }
114
118
  if (node[0] === NODE.assignmentExpr) {
115
- return `${stringifyExpression(node[1], ident)} ${node[2]} ${stringifyExpression(node[3], ident)}`;
119
+ return `${stringifyExpression(node[1])} ${node[2]} ${stringifyExpression(node[3])}`;
116
120
  }
117
121
  if (node[0] === NODE.logicalExpr) {
118
- return `${wrapIfComplex(node[1], ident)} ${node[2]} ${wrapIfComplex(node[3], ident)}`;
122
+ return `${wrapIfComplex(node[1])} ${node[2]} ${wrapIfComplex(node[3])}`;
119
123
  }
120
124
  if (node[0] === NODE.unaryExpr) {
121
125
  // Unary word operators like void, instanceof and delete require a space
122
126
  const sep = node[1].length > 1 ? ' ' : '';
123
- return `${node[1]}${sep}${wrapIfComplex(node[2], ident)}`;
127
+ return `${node[1]}${sep}${wrapIfComplex(node[2])}`;
124
128
  }
125
129
  if (node[0] === NODE.call) {
126
- const callee = wrapIfComplex(node[1], ident);
127
- const args = node[2].map((n) => stringifyExpression(n, ident)).join(', ');
130
+ const callee = wrapIfComplex(node[1]);
131
+ const args = node[2].map((n) => stringifyExpression(n)).join(', ');
128
132
  return `${callee}(${args})`;
129
133
  }
130
134
  if (node[0] === NODE.memberAccess) {
131
135
  if (Array.isArray(node[1]) && node[1][0] === NODE.numericLiteral) {
132
- return `(${stringifyExpression(node[1], ident)}).${stringifyExpression(node[2], ident)}`;
136
+ return `(${stringifyExpression(node[1])}).${stringifyExpression(node[2])}`;
133
137
  }
134
- return `${wrapIfComplex(node[1], ident)}.${stringifyExpression(node[2], ident)}`;
138
+ return `${wrapIfComplex(node[1])}.${stringifyExpression(node[2])}`;
135
139
  }
136
140
  if (node[0] === NODE.indexAccess) {
137
- return `${wrapIfComplex(node[1], ident)}[${stringifyExpression(node[2], ident)}]`;
141
+ return `${wrapIfComplex(node[1])}[${stringifyExpression(node[2])}]`;
138
142
  }
139
143
  if (node[0] === NODE.preUpdate) {
140
- return `${node[1]}${wrapIfComplex(node[2], ident)}`;
144
+ return `${node[1]}${wrapIfComplex(node[2])}`;
141
145
  }
142
146
  if (node[0] === NODE.postUpdate) {
143
- return `${wrapIfComplex(node[2], ident)}${node[1]}`;
147
+ return `${wrapIfComplex(node[2])}${node[1]}`;
144
148
  }
145
149
  if (node[0] === NODE.objectExpr) {
146
150
  if (Array.isArray(node[1])) {
147
151
  return `{ ${node[1].map(stringifyObjectProperty).join(', ')} }`;
148
152
  }
149
- const entries = Object.entries(node[1]).map(([key, val]) => `${key}: ${stringifyExpression(val, ident)}`);
153
+ const entries = Object.entries(node[1]).map(([key, val]) => `${stringifyObjectPropertyKey(key)}: ${stringifyExpression(val)}`);
150
154
  return `{ ${entries.join(', ')} }`;
151
155
  }
152
156
  if (node[0] === NODE.conditionalExpr) {
153
- return `${wrapIfComplex(node[1], ident)} ? ${wrapIfComplex(node[2], ident)} : ${wrapIfComplex(node[3], ident)}`;
157
+ return `${wrapIfComplex(node[1])} ? ${wrapIfComplex(node[2])} : ${wrapIfComplex(node[3])}`;
154
158
  }
155
159
  if (node[0] === NODE.nullLiteral) {
156
160
  return 'null';
@@ -199,8 +203,8 @@ const SIMPLE_NODES = [
199
203
  /**
200
204
  * Stringifies expression, and wraps it in parentheses if they cannot be trivially omitted
201
205
  */
202
- function wrapIfComplex(node, ident) {
203
- const s = stringifyExpression(node, ident);
206
+ function wrapIfComplex(node) {
207
+ const s = stringifyExpression(node);
204
208
  if (typeof node === 'string' || typeof node === 'boolean') {
205
209
  return s;
206
210
  }
package/std/boolean.js CHANGED
@@ -9,6 +9,7 @@ import { isBool, isVecBool, isVecInstance, } from "../data/wgslTypes.js";
9
9
  import { SignatureNotSupportedError, WgslTypeError } from "../errors.js";
10
10
  import { unify } from "../tgsl/conversion.js";
11
11
  import { cpuCopy } from "./copy.js";
12
+ import { coerceToSnippet } from "../tgsl/generationHelpers.js";
12
13
  function correspondingBooleanVectorSchema(dataType) {
13
14
  if (dataType.type.includes('2')) {
14
15
  return vec2b;
@@ -30,7 +31,7 @@ export const allEq = dualImpl({
30
31
  name: 'allEq',
31
32
  signature: (...argTypes) => ({ argTypes, returnType: bool }),
32
33
  normalImpl: (lhs, rhs) => cpuAll(cpuEq(lhs, rhs)),
33
- codegenImpl: (_ctx, [lhs, rhs]) => stitch `all(${lhs} == ${rhs})`,
34
+ codegenImpl: (ctx, [lhs, rhs]) => stitch `all(${ctx.gen.emitBinaryOp(lhs, '==', rhs)})`,
34
35
  sideEffects: false,
35
36
  });
36
37
  const cpuEq = (lhs, rhs) => {
@@ -54,7 +55,7 @@ export const eq = dualImpl({
54
55
  returnType: correspondingBooleanVectorSchema(argTypes[0]),
55
56
  }),
56
57
  normalImpl: cpuEq,
57
- codegenImpl: (_ctx, [lhs, rhs]) => stitch `(${lhs} == ${rhs})`,
58
+ codegenImpl: (ctx, [lhs, rhs]) => ctx.gen.emitBinaryOp(lhs, '==', rhs),
58
59
  sideEffects: false,
59
60
  });
60
61
  /**
@@ -72,7 +73,7 @@ export const ne = dualImpl({
72
73
  returnType: correspondingBooleanVectorSchema(argTypes[0]),
73
74
  }),
74
75
  normalImpl: (lhs, rhs) => cpuNot(cpuEq(lhs, rhs)),
75
- codegenImpl: (_ctx, [lhs, rhs]) => stitch `(${lhs} != ${rhs})`,
76
+ codegenImpl: (ctx, [lhs, rhs]) => ctx.gen.emitBinaryOp(lhs, '!=', rhs),
76
77
  sideEffects: false,
77
78
  });
78
79
  const cpuLt = (lhs, rhs) => {
@@ -95,7 +96,7 @@ export const lt = dualImpl({
95
96
  returnType: correspondingBooleanVectorSchema(argTypes[0]),
96
97
  }),
97
98
  normalImpl: cpuLt,
98
- codegenImpl: (_ctx, [lhs, rhs]) => stitch `(${lhs} < ${rhs})`,
99
+ codegenImpl: (ctx, [lhs, rhs]) => ctx.gen.emitBinaryOp(lhs, '<', rhs),
99
100
  sideEffects: false,
100
101
  });
101
102
  /**
@@ -113,7 +114,7 @@ export const le = dualImpl({
113
114
  returnType: correspondingBooleanVectorSchema(argTypes[0]),
114
115
  }),
115
116
  normalImpl: (lhs, rhs) => cpuOr(cpuLt(lhs, rhs), cpuEq(lhs, rhs)),
116
- codegenImpl: (_ctx, [lhs, rhs]) => stitch `(${lhs} <= ${rhs})`,
117
+ codegenImpl: (ctx, [lhs, rhs]) => ctx.gen.emitBinaryOp(lhs, '<=', rhs),
117
118
  sideEffects: false,
118
119
  });
119
120
  /**
@@ -131,7 +132,7 @@ export const gt = dualImpl({
131
132
  returnType: correspondingBooleanVectorSchema(argTypes[0]),
132
133
  }),
133
134
  normalImpl: (lhs, rhs) => cpuAnd(cpuNot(cpuLt(lhs, rhs)), cpuNot(cpuEq(lhs, rhs))),
134
- codegenImpl: (_ctx, [lhs, rhs]) => stitch `(${lhs} > ${rhs})`,
135
+ codegenImpl: (ctx, [lhs, rhs]) => ctx.gen.emitBinaryOp(lhs, '>', rhs),
135
136
  sideEffects: false,
136
137
  });
137
138
  /**
@@ -149,7 +150,7 @@ export const ge = dualImpl({
149
150
  returnType: correspondingBooleanVectorSchema(argTypes[0]),
150
151
  }),
151
152
  normalImpl: (lhs, rhs) => cpuNot(cpuLt(lhs, rhs)),
152
- codegenImpl: (_ctx, [lhs, rhs]) => stitch `(${lhs} >= ${rhs})`,
153
+ codegenImpl: (ctx, [lhs, rhs]) => ctx.gen.emitBinaryOp(lhs, '>=', rhs),
153
154
  sideEffects: false,
154
155
  });
155
156
  function cpuNot(value) {
@@ -274,9 +275,7 @@ export const isCloseTo = dualImpl({
274
275
  return stitch `(abs(f32(${lhs}) - f32(${rhs})) <= ${precision})`;
275
276
  }
276
277
  if (!isSnippetNumeric(lhs) && !isSnippetNumeric(rhs)) {
277
- // https://www.w3.org/TR/WGSL/#vector-multi-component:~:text=Binary%20arithmetic%20expressions%20with%20mixed%20scalar%20and%20vector%20operands
278
- // (a-a)+prec creates a vector of a.length elements, all equal to prec
279
- return stitch `all(abs(${lhs} - ${rhs}) <= (${lhs} - ${lhs}) + ${precision})`;
278
+ return stitch `all(abs(${lhs} - ${rhs}) <= ${_ctx.gen.typeInstantiation(lhs.dataType, [coerceToSnippet(precision)])})`;
280
279
  }
281
280
  return 'false';
282
281
  },
package/std/numeric.js CHANGED
@@ -747,10 +747,7 @@ export const round = dualImpl({
747
747
  });
748
748
  function cpuSaturate(value) {
749
749
  assertKind(value, floatKind);
750
- if (typeof value === 'number') {
751
- return Math.max(0, Math.min(1, value));
752
- }
753
- throw new MissingCpuImplError('CPU implementation for saturate on vectors not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues');
750
+ return generalizeFn((value) => clampScalar(value, 0, 1), [value]);
754
751
  }
755
752
  export const saturate = dualImpl({
756
753
  name: 'saturate',
package/std/operators.js CHANGED
@@ -160,10 +160,15 @@ function cpuNeg(value) {
160
160
  }
161
161
  export const neg = dualImpl({
162
162
  name: 'neg',
163
- signature: (arg) => ({
164
- argTypes: [arg],
165
- returnType: arg,
166
- }),
163
+ signature: (arg) => {
164
+ if (getPrimitive(arg) === u32) {
165
+ throw new WgslTypeError(`Unsupported signature. Unary operator - cannot be applied to argument of kind '${arg.type}'.`);
166
+ }
167
+ return {
168
+ argTypes: [arg],
169
+ returnType: arg,
170
+ };
171
+ },
167
172
  normalImpl: cpuNeg,
168
173
  codegenImpl: (_ctx, [arg]) => stitch `-(${arg})`,
169
174
  sideEffects: false,