typegpu 0.12.7 → 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 (60) hide show
  1. package/core/function/autoIO.js +2 -2
  2. package/core/function/comptime.js +7 -5
  3. package/core/function/createCallableSchema.js +4 -3
  4. package/core/function/dualImpl.js +20 -19
  5. package/core/function/ioSchema.d.ts +10 -0
  6. package/core/function/ioSchema.js +37 -16
  7. package/core/function/tgpuFn.d.ts +11 -1
  8. package/core/function/tgpuFn.js +9 -2
  9. package/core/pipeline/renderPipeline.d.ts +4 -2
  10. package/core/pipeline/renderPipeline.js +12 -10
  11. package/core/pipeline/timeable.js +15 -2
  12. package/core/root/configurableImpl.js +1 -2
  13. package/core/texture/externalTexture.d.ts +1 -1
  14. package/core/texture/externalTexture.js +3 -1
  15. package/core/texture/texture.d.ts +1 -1
  16. package/core/texture/texture.js +4 -1
  17. package/core/unroll/tgpuUnroll.d.ts +4 -3
  18. package/core/unroll/tgpuUnroll.js +7 -1
  19. package/core/valueProxyUtils.d.ts +4 -2
  20. package/core/valueProxyUtils.js +4 -2
  21. package/data/compiledIO.js +12 -9
  22. package/data/dataIO.d.ts +10 -6
  23. package/data/dataIO.js +19 -4
  24. package/data/dataTypes.d.ts +0 -5
  25. package/data/dataTypes.js +0 -6
  26. package/data/matrixColumnsAccess.d.ts +8 -0
  27. package/data/matrixColumnsAccess.js +15 -0
  28. package/data/offsetUtils.js +181 -102
  29. package/data/ref.d.ts +12 -5
  30. package/data/ref.js +50 -23
  31. package/data/snippet.d.ts +7 -0
  32. package/data/snippet.js +9 -0
  33. package/data/struct.js +3 -0
  34. package/data/wgslTypes.d.ts +3 -3
  35. package/package.json +1 -1
  36. package/resolutionCtx.js +1 -1
  37. package/shared/meta.js +1 -1
  38. package/shared/symbols.d.ts +5 -0
  39. package/shared/symbols.js +6 -1
  40. package/std/numeric.js +1 -4
  41. package/std/packing.js +10 -10
  42. package/tgpuLogger.d.ts +1 -1
  43. package/tgpuLogger.js +0 -2
  44. package/tgsl/accessIndex.js +18 -15
  45. package/tgsl/accessProp.js +10 -7
  46. package/tgsl/conversion.js +3 -2
  47. package/tgsl/forOfUtils.js +2 -2
  48. package/tgsl/generationHelpers.d.ts +4 -3
  49. package/tgsl/generationHelpers.js +16 -3
  50. package/tgsl/infixDispatch.d.ts +4 -1
  51. package/tgsl/infixDispatch.js +13 -1
  52. package/tgsl/makeDereferenceable.d.ts +1 -1
  53. package/tgsl/makeDereferenceable.js +3 -1
  54. package/tgsl/shellless.js +5 -9
  55. package/tgsl/stringifySnippet.d.ts +9 -0
  56. package/tgsl/stringifySnippet.js +24 -0
  57. package/tgsl/wgslGenerator.d.ts +1 -0
  58. package/tgsl/wgslGenerator.js +98 -66
  59. package/types.d.ts +23 -2
  60. package/types.js +30 -5
package/data/snippet.js CHANGED
@@ -49,9 +49,18 @@ export const originToPtrParams = {
49
49
  // Local declarations are also in the `function` address space
50
50
  'local-def': { space: 'function', access: 'read-write' },
51
51
  };
52
+ /**
53
+ * Whether values of this origin can have their address taken.
54
+ */
52
55
  export function isAddressableOrigin(origin) {
53
56
  return origin in originToPtrParams;
54
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
+ }
55
64
  class SnippetImpl {
56
65
  value;
57
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,7 +704,7 @@ 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;
@@ -720,7 +720,7 @@ export interface Mat3x3f extends BaseData {
720
720
  readonly type: 'mat3x3f';
721
721
  readonly primitive: F32;
722
722
  readonly [$repr]: m3x3f;
723
- readonly [$inRepr]: m3x3f | readonly number[] | Float32Array;
723
+ readonly [$inRepr]: m3x3f | readonly [number, number, number, number, number, number, number, number, number] | Float32Array;
724
724
  readonly [$validStorageSchema]: true;
725
725
  readonly [$validUniformSchema]: true;
726
726
  (...elements: [number, number, number, number, number, number, number, number, number]): m3x3f;
@@ -736,7 +736,7 @@ export interface Mat4x4f extends BaseData {
736
736
  readonly type: 'mat4x4f';
737
737
  readonly primitive: F32;
738
738
  readonly [$repr]: m4x4f;
739
- 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;
740
740
  readonly [$validStorageSchema]: true;
741
741
  readonly [$validUniformSchema]: true;
742
742
  (...elements: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]): m4x4f;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegpu",
3
- "version": "0.12.7",
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.7";
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.7";
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/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/packing.js CHANGED
@@ -96,7 +96,7 @@ export const unpack4x8unorm = dualImpl({
96
96
  return vec4f(reader.readUint8() / 255, reader.readUint8() / 255, reader.readUint8() / 255, reader.readUint8() / 255);
97
97
  },
98
98
  signature: { argTypes: [u32], returnType: vec4f },
99
- codegenImpl: (_ctx, [e]) => stitch `unpack4x8unorm(${e})`,
99
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('unpack4x8unorm', [], [e]),
100
100
  sideEffects: false,
101
101
  });
102
102
  /**
@@ -110,7 +110,7 @@ export const unpack4x8snorm = dualImpl({
110
110
  return vec4f(Math.max(reader.readInt8() / 127, -1), Math.max(reader.readInt8() / 127, -1), Math.max(reader.readInt8() / 127, -1), Math.max(reader.readInt8() / 127, -1));
111
111
  },
112
112
  signature: { argTypes: [u32], returnType: vec4f },
113
- codegenImpl: (_ctx, [e]) => stitch `unpack4x8snorm(${e})`,
113
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('unpack4x8snorm', [], [e]),
114
114
  sideEffects: false,
115
115
  });
116
116
  /**
@@ -124,7 +124,7 @@ export const unpack4xI8 = dualImpl({
124
124
  return vec4i(reader.readInt8(), reader.readInt8(), reader.readInt8(), reader.readInt8());
125
125
  },
126
126
  signature: { argTypes: [u32], returnType: vec4i },
127
- codegenImpl: (_ctx, [e]) => stitch `unpack4xI8(${e})`,
127
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('unpack4xI8', [], [e]),
128
128
  sideEffects: false,
129
129
  });
130
130
  /**
@@ -138,7 +138,7 @@ export const unpack4xU8 = dualImpl({
138
138
  return vec4u(reader.readUint8(), reader.readUint8(), reader.readUint8(), reader.readUint8());
139
139
  },
140
140
  signature: { argTypes: [u32], returnType: vec4u },
141
- codegenImpl: (_ctx, [e]) => stitch `unpack4xU8(${e})`,
141
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('unpack4xU8', [], [e]),
142
142
  sideEffects: false,
143
143
  });
144
144
  /**
@@ -182,7 +182,7 @@ export const pack4x8unorm = dualImpl({
182
182
  writer.writeUint8(Math.floor(0.5 + 255 * clampScalar(e.w, 0, 1)));
183
183
  }),
184
184
  signature: { argTypes: [vec4f], returnType: u32 },
185
- codegenImpl: (_ctx, [e]) => stitch `pack4x8unorm(${e})`,
185
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('pack4x8unorm', [], [e]),
186
186
  sideEffects: false,
187
187
  });
188
188
  /**
@@ -198,7 +198,7 @@ export const pack4x8snorm = dualImpl({
198
198
  writer.writeInt8(Math.floor(0.5 + 127 * clampScalar(e.w, -1, 1)));
199
199
  }),
200
200
  signature: { argTypes: [vec4f], returnType: u32 },
201
- codegenImpl: (_ctx, [e]) => stitch `pack4x8snorm(${e})`,
201
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('pack4x8snorm', [], [e]),
202
202
  sideEffects: false,
203
203
  });
204
204
  /**
@@ -214,7 +214,7 @@ export const pack4xI8 = dualImpl({
214
214
  writer.writeInt8(e.w);
215
215
  }),
216
216
  signature: { argTypes: [vec4i], returnType: u32 },
217
- codegenImpl: (_ctx, [e]) => stitch `pack4xI8(${e})`,
217
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('pack4xI8', [], [e]),
218
218
  sideEffects: false,
219
219
  });
220
220
  /**
@@ -230,7 +230,7 @@ export const pack4xU8 = dualImpl({
230
230
  writer.writeUint8(e.w);
231
231
  }),
232
232
  signature: { argTypes: [vec4u], returnType: u32 },
233
- codegenImpl: (_ctx, [e]) => stitch `pack4xU8(${e})`,
233
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('pack4xU8', [], [e]),
234
234
  sideEffects: false,
235
235
  });
236
236
  /**
@@ -246,7 +246,7 @@ export const pack4xI8Clamp = dualImpl({
246
246
  writer.writeInt8(clampScalar(e.w, -128, 127));
247
247
  }),
248
248
  signature: { argTypes: [vec4i], returnType: u32 },
249
- codegenImpl: (_ctx, [e]) => stitch `pack4xI8Clamp(${e})`,
249
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('pack4xI8Clamp', [], [e]),
250
250
  sideEffects: false,
251
251
  });
252
252
  /**
@@ -262,6 +262,6 @@ export const pack4xU8Clamp = dualImpl({
262
262
  writer.writeUint8(clampScalar(e.w, 0, 255));
263
263
  }),
264
264
  signature: { argTypes: [vec4u], returnType: u32 },
265
- codegenImpl: (_ctx, [e]) => stitch `pack4xU8Clamp(${e})`,
265
+ codegenImpl: (ctx, [e]) => ctx.gen.emitCall('pack4xU8Clamp', [], [e]),
266
266
  sideEffects: false,
267
267
  });
package/tgpuLogger.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare const warningTypes: readonly ["deprecated", "suspicious", "fallback", "precision-loss", "implicit-conversion", "webgpu-feature-missing", "webgpu-limits-exceeded", "locations-mismatched", "log-limit-exceeded", "external-omitted", "uniform-schema-misaligned"];
1
+ declare const warningTypes: readonly ["deprecated", "suspicious", "fallback", "precision-loss", "implicit-conversion", "webgpu-feature-missing", "webgpu-limits-exceeded", "log-limit-exceeded", "external-omitted", "uniform-schema-misaligned"];
2
2
  type WarningType = (typeof warningTypes)[number];
3
3
  interface Logger {
4
4
  warn(type: WarningType, ...args: unknown[]): void;
package/tgpuLogger.js CHANGED
@@ -7,7 +7,6 @@ const warningTypes = [
7
7
  'implicit-conversion',
8
8
  'webgpu-feature-missing',
9
9
  'webgpu-limits-exceeded',
10
- 'locations-mismatched',
11
10
  'log-limit-exceeded',
12
11
  'external-omitted',
13
12
  'uniform-schema-misaligned',
@@ -21,7 +20,6 @@ export class TgpuLogger {
21
20
  this.#initialEnabledWarnings = [
22
21
  'webgpu-feature-missing',
23
22
  'webgpu-limits-exceeded',
24
- 'locations-mismatched',
25
23
  'log-limit-exceeded',
26
24
  'external-omitted',
27
25
  ];
@@ -1,10 +1,11 @@
1
1
  import { stitch } from "../core/resolve/stitch.js";
2
- import { isDisarray, MatrixColumnsAccess } from "../data/dataTypes.js";
2
+ import { isDisarray } from "../data/dataTypes.js";
3
+ import { MatrixColumnsAccess } from "../data/matrixColumnsAccess.js";
3
4
  import { derefSnippet } from "../data/ref.js";
4
5
  import { snip } from "../data/snippet.js";
5
6
  import { vec2f, vec3f, vec4f } from "../data/vector.js";
6
- import { isPtr, isVec, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
7
- import { isKnownAtComptime } from "../types.js";
7
+ import { isPtr, isVec, isWgslArray } from "../data/wgslTypes.js";
8
+ import { asComptime } from "../types.js";
8
9
  import { accessProp } from "./accessProp.js";
9
10
  import { ArrayExpression, coerceToSnippet } from "./generationHelpers.js";
10
11
  const indexableTypeToResult = {
@@ -30,24 +31,27 @@ function deriveIndexAccessOrigin(targetOrigin, indexOrigin) {
30
31
  }
31
32
  export function accessIndex(target, indexArg) {
32
33
  const index = typeof indexArg === 'number' ? coerceToSnippet(indexArg) : indexArg;
34
+ const knownIndex = asComptime(index);
33
35
  // array
34
36
  if (isWgslArray(target.dataType) || isDisarray(target.dataType)) {
35
37
  const elementType = target.dataType.elementType;
36
- if (target.value instanceof ArrayExpression && isKnownAtComptime(index)) {
37
- return target.value.elements[index.value];
38
+ if (target.value instanceof ArrayExpression && knownIndex) {
39
+ return target.value.elements[knownIndex.value];
38
40
  }
39
41
  const origin = deriveIndexAccessOrigin(target.origin, index.origin);
40
- return snip(isKnownAtComptime(target) && isKnownAtComptime(index)
42
+ const knownTarget = knownIndex && asComptime(target);
43
+ return snip(knownTarget && knownIndex
41
44
  ? // oxlint-disable-next-line typescript/no-explicit-any -- it's fine, it's there
42
- target.value[index.value]
45
+ knownTarget.value[knownIndex.value]
43
46
  : stitch `${target}[${index}]`, elementType,
44
47
  /* origin */ origin, target.possibleSideEffects || index.possibleSideEffects);
45
48
  }
46
49
  // vector
47
50
  if (isVec(target.dataType)) {
48
- return snip(isKnownAtComptime(target) && isKnownAtComptime(index)
51
+ const knownTarget = knownIndex && asComptime(target);
52
+ return snip(knownTarget && knownIndex
49
53
  ? // oxlint-disable-next-line typescript/no-explicit-any -- it's fine, it's there
50
- target.value[index.value]
54
+ knownTarget.value[knownIndex.value]
51
55
  : stitch `${target}[${index}]`, target.dataType.primitive, deriveIndexAccessOrigin(target.origin, index.origin), target.possibleSideEffects || index.possibleSideEffects);
52
56
  }
53
57
  if (isPtr(target.dataType)) {
@@ -64,16 +68,15 @@ export function accessIndex(target, indexArg) {
64
68
  if (target.dataType.type in indexableTypeToResult) {
65
69
  throw new Error("The only way of accessing matrix elements in TypeGPU functions is through the 'columns' property.");
66
70
  }
67
- if (isKnownAtComptime(target) && isKnownAtComptime(index)) {
71
+ const knownTarget = knownIndex && asComptime(target);
72
+ if (knownTarget && knownIndex) {
68
73
  // No idea what the type is, so we act on the snippet's value and try to guess
69
74
  return coerceToSnippet(
70
75
  // oxlint-disable-next-line typescript/no-explicit-any -- we're inspecting the value, and it could be any value
71
- target.value[index.value]);
76
+ knownTarget.value[knownIndex.value]);
72
77
  }
73
- if (isWgslStruct(target.dataType) &&
74
- isKnownAtComptime(index) &&
75
- typeof index.value === 'string') {
76
- return accessProp(target, index.value);
78
+ if (typeof knownIndex?.value === 'string') {
79
+ return accessProp(target, knownIndex.value);
77
80
  }
78
81
  return undefined;
79
82
  }
@@ -1,13 +1,14 @@
1
1
  import { stitch } from "../core/resolve/stitch.js";
2
2
  import { AutoStruct } from "../data/autoStruct.js";
3
3
  import { EntryInputRouter } from "../core/function/entryInputRouter.js";
4
- import { isUnstruct, MatrixColumnsAccess, UnknownData } from "../data/dataTypes.js";
4
+ import { isUnstruct, UnknownData } from "../data/dataTypes.js";
5
+ import { MatrixColumnsAccess } from "../data/matrixColumnsAccess.js";
5
6
  import { bool, f16, f32, i32, u32 } from "../data/numeric.js";
6
7
  import { derefSnippet } from "../data/ref.js";
7
8
  import { isSnippet, snip } from "../data/snippet.js";
8
9
  import { vec2b, vec2f, vec2h, vec2i, vec2u, vec3b, vec3f, vec3h, vec3i, vec3u, vec4b, vec4f, vec4h, vec4i, vec4u, } from "../data/vector.js";
9
10
  import { isMat, isPtr, isVec, isWgslArray, isWgslStruct, } from "../data/wgslTypes.js";
10
- import { isKnownAtComptime } from "../types.js";
11
+ import { asComptime } from "../types.js";
11
12
  import { coerceToSnippet, numericLiteralToSnippet } from "./generationHelpers.js";
12
13
  import { InfixDispatch, infixOperators } from "./infixDispatch.js";
13
14
  import { accessStructProp } from "./accessStructProp.js";
@@ -128,9 +129,10 @@ export function accessProp(target, propName) {
128
129
  if (!swizzleType) {
129
130
  return undefined;
130
131
  }
131
- return snip(isKnownAtComptime(target)
132
+ const knownTarget = asComptime(target);
133
+ return snip(knownTarget
132
134
  ? // oxlint-disable-next-line typescript/no-explicit-any -- it's fine, the prop is there
133
- target.value[propName]
135
+ knownTarget.value[propName]
134
136
  : stitch `${target}.${propName}`, swizzleType,
135
137
  // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL)
136
138
  /* origin */ propLength === 1
@@ -139,9 +141,10 @@ export function accessProp(target, propName) {
139
141
  ? 'constant'
140
142
  : 'runtime', target.possibleSideEffects);
141
143
  }
142
- if (isKnownAtComptime(target) || target.dataType === UnknownData) {
143
- // oxlint-disable-next-line typescript/no-explicit-any -- we either know exactly what it is, or have no idea at all
144
- const prop = target.value[propName];
144
+ const knownTarget = asComptime(target);
145
+ if (knownTarget) {
146
+ // oxlint-disable-next-line typescript/no-explicit-any
147
+ const prop = knownTarget.value[propName];
145
148
  if (isNamable(prop) && getName(prop) === undefined) {
146
149
  setName(prop, propName);
147
150
  }
@@ -10,6 +10,7 @@ import { safeStringify } from "../shared/stringify.js";
10
10
  import { assertExhaustive } from "../shared/utilityTypes.js";
11
11
  import { logger } from "../tgpuLogger.js";
12
12
  import { accessStructProp } from "./accessStructProp.js";
13
+ import { stringifySnippet } from "./stringifySnippet.js";
13
14
  const INFINITE_RANK = {
14
15
  rank: Number.POSITIVE_INFINITY,
15
16
  action: 'none',
@@ -254,7 +255,7 @@ export function convertToCommonType(ctx, values, restrictTo, verbose = true) {
254
255
  }
255
256
  if (verbose && conversion.hasImplicitConversions) {
256
257
  logger.warn('implicit-conversion', `Implicit conversions from [\n${values
257
- .map((v) => ` ${ctx.resolveSnippet(v).value}: ${safeStringify(v.dataType)}`)
258
+ .map((v) => ` ${stringifySnippet(v)}: ${safeStringify(v.dataType)}`)
258
259
  .join(',\n')}\n] to ${conversion.targetType.type} are supported, but not recommended.
259
260
  Consider using explicit conversions instead.`);
260
261
  }
@@ -281,7 +282,7 @@ export function tryConvertSnippet(ctx, snippet, targetDataTypes, verbose = true)
281
282
  if (converted) {
282
283
  return converted[0];
283
284
  }
284
- throw new WgslTypeError(`Cannot convert value of type '${String(dataType)}' to any of the target types: [${targets.map((t) => t.type).join(', ')}]`);
285
+ throw new WgslTypeError(`Cannot convert '${stringifySnippet(snippet)}' of type '${String(dataType)}' to any of the target types: [${targets.map((t) => t.type).join(', ')}]`);
285
286
  }
286
287
  export function convertStructValues(ctx, structType, values) {
287
288
  return Object.entries(structType.propTypes).map(([key, targetType]) => {
@@ -1,6 +1,5 @@
1
1
  import { UnknownData } from "../data/dataTypes.js";
2
2
  import { isAddressableOrigin, isStoredInMemory, snip } from "../data/snippet.js";
3
- import { stitch } from "../core/resolve/stitch.js";
4
3
  import * as wgsl from "../data/wgslTypes.js";
5
4
  import { i32, u32 } from "../data/numeric.js";
6
5
  import { invariant, WgslTypeError } from "../errors.js";
@@ -9,6 +8,7 @@ import { accessIndex } from "./accessIndex.js";
9
8
  import { createPtrFromOrigin, implicitFrom } from "../data/ptr.js";
10
9
  import { $gpuCallable } from "../shared/symbols.js";
11
10
  import { ArrayExpression, concretize } from "./generationHelpers.js";
11
+ import { stringifySnippet } from "./stringifySnippet.js";
12
12
  import { isTgpuRange } from "../std/range.js";
13
13
  export function getLoopVarKind(elementSnippet) {
14
14
  // If it's ephemeral, it's a value that cannot change. If it's a reference, we take
@@ -28,7 +28,7 @@ export function getElementSnippet(iterableSnippet, index) {
28
28
  export function getElementType(elementSnippet, iterableSnippet) {
29
29
  let elementType = elementSnippet.dataType;
30
30
  if (elementType === UnknownData) {
31
- throw new WgslTypeError(stitch `The elements in iterable ${iterableSnippet} are of unknown type`);
31
+ throw new WgslTypeError(`The elements in iterable '${stringifySnippet(iterableSnippet)}' are of unknown type`);
32
32
  }
33
33
  if (wgsl.isNaturallyEphemeral(elementSnippet.dataType) ||
34
34
  !isAddressableOrigin(elementSnippet.origin)) {
@@ -1,7 +1,7 @@
1
1
  import type { ResolvedSnippet, Snippet } from '../data/snippet.ts';
2
2
  import { type AnyWgslData, type BaseData, type F32, type I32, type WgslArray } from '../data/wgslTypes.ts';
3
- import { type ResolutionCtx, type SelfResolvable } from '../types.ts';
4
- import { $internal, $resolve } from '../shared/symbols.ts';
3
+ import { type ComptimeValue, type ResolutionCtx, type SelfResolvable, type WithComptimeValue } from '../types.ts';
4
+ import { $comptimeValueOf, $internal, $resolve } from '../shared/symbols.ts';
5
5
  export declare function numericLiteralToSnippet(value: number): Snippet;
6
6
  export declare function concretize<T extends BaseData>(type: T): T | F32 | I32;
7
7
  export declare function concretizeSnippet(snippet: Snippet): Snippet;
@@ -12,11 +12,12 @@ export declare function coerceToSnippet(value: unknown): Snippet;
12
12
  * Defers resolution. Stores array elements as snippets so the
13
13
  * generator can access them when needed.
14
14
  */
15
- export declare class ArrayExpression implements SelfResolvable {
15
+ export declare class ArrayExpression implements SelfResolvable, WithComptimeValue {
16
16
  readonly [$internal] = true;
17
17
  readonly type: WgslArray<AnyWgslData>;
18
18
  readonly elements: Snippet[];
19
19
  constructor(type: WgslArray<AnyWgslData>, elements: Snippet[]);
20
20
  toString(): string;
21
+ [$comptimeValueOf](): ComptimeValue;
21
22
  [$resolve](ctx: ResolutionCtx): ResolvedSnippet;
22
23
  }
@@ -3,10 +3,11 @@ import { abstractFloat, abstractInt, bool, f32, i32 } from "../data/numeric.js";
3
3
  import { isRef } from "../data/ref.js";
4
4
  import { isStoredInMemory, isSnippet, snip, withDataType } from "../data/snippet.js";
5
5
  import { isMatInstance, isNaturallyEphemeral, isVecInstance, WORKAROUND_getSchema, } from "../data/wgslTypes.js";
6
- import { getOwnSnippet } from "../types.js";
6
+ import { asComptime, getOwnSnippet, } from "../types.js";
7
7
  import { WgslTypeError } from "../errors.js";
8
- import { $internal, $resolve } from "../shared/symbols.js";
8
+ import { $comptimeValueOf, $internal, $resolve } from "../shared/symbols.js";
9
9
  import { logger } from "../tgpuLogger.js";
10
+ import { stringifySnippet } from "./stringifySnippet.js";
10
11
  export function numericLiteralToSnippet(value) {
11
12
  if (value >= 2 ** 63 || value < -(2 ** 63)) {
12
13
  return snip(value, abstractFloat, /* origin */ 'constant', /* possibleSideEffects */ false);
@@ -78,11 +79,23 @@ export class ArrayExpression {
78
79
  toString() {
79
80
  return 'ArrayExpression';
80
81
  }
82
+ [$comptimeValueOf]() {
83
+ // The array is known at comptime only if every element is known at comptime
84
+ const values = [];
85
+ for (const elem of this.elements) {
86
+ const known = asComptime(elem);
87
+ if (!known) {
88
+ return { comptime: false };
89
+ }
90
+ values.push(known.value);
91
+ }
92
+ return { comptime: true, value: values };
93
+ }
81
94
  [$resolve](ctx) {
82
95
  for (const elem of this.elements) {
83
96
  // We check if there are no references among the elements
84
97
  if (isStoredInMemory(elem) && !isNaturallyEphemeral(elem.dataType)) {
85
- const snippetStr = ctx.resolveSnippet(elem).value;
98
+ const snippetStr = stringifySnippet(elem);
86
99
  const snippetType = ctx.resolve(concretize(elem.dataType)).value;
87
100
  throw new WgslTypeError(`'${snippetStr}' reference cannot be used in an array constructor.\n-----\nTry '${snippetType}(${snippetStr})' or 'arrayOf(${snippetType}, count)([...])' to copy the value instead.\n-----`);
88
101
  }
@@ -2,6 +2,8 @@ import type { MatBase } from '../data/matrix.ts';
2
2
  import { type Snippet } from '../data/snippet.ts';
3
3
  import type { VecBase } from '../data/vectorImpl.ts';
4
4
  import type { AnyMatInstance, AnyNumericVecInstance } from '../data/wgslTypes.ts';
5
+ import { $comptimeValueOf } from '../shared/symbols.ts';
6
+ import { type ComptimeValue, type WithComptimeValue } from '../types.ts';
5
7
  export declare const infixOperators: {
6
8
  readonly add: import("../types.ts").DualFn<{
7
9
  (lhs: number, rhs: number): number;
@@ -56,10 +58,11 @@ export type InfixOperator = (typeof infixOperators)[InfixOperatorName];
56
58
  * d.vec2u(1).mul(2) // lhs is a snippet
57
59
  * }
58
60
  */
59
- export declare class InfixDispatch {
61
+ export declare class InfixDispatch implements WithComptimeValue {
60
62
  readonly lhs: Snippet | Numeric;
61
63
  readonly operator: InfixOperator;
62
64
  constructor(lhs: Snippet | Numeric, operator: InfixOperator);
65
+ [$comptimeValueOf](): ComptimeValue;
63
66
  }
64
67
  export declare function isInfixDispatch(o: unknown): o is InfixDispatch;
65
68
  /**
@@ -1,5 +1,7 @@
1
- import {} from "../data/snippet.js";
1
+ import { isSnippet } from "../data/snippet.js";
2
2
  import { inCodegenMode } from "../execMode.js";
3
+ import { $comptimeValueOf } from "../shared/symbols.js";
4
+ import { asComptime } from "../types.js";
3
5
  import { add, bitShiftLeft, bitShiftRight, div, mod, mul, sub } from "../std/operators.js";
4
6
  export const infixOperators = {
5
7
  add,
@@ -28,6 +30,16 @@ export class InfixDispatch {
28
30
  this.lhs = lhs;
29
31
  this.operator = operator;
30
32
  }
33
+ [$comptimeValueOf]() {
34
+ const known = isSnippet(this.lhs) ? asComptime(this.lhs) : { value: this.lhs };
35
+ if (!known) {
36
+ return { comptime: false };
37
+ }
38
+ // Same as accessing e.g. `.mul` on a vector in JS, but bound to `lhs`
39
+ const lhs = known.value;
40
+ const operator = this.operator;
41
+ return { comptime: true, value: (rhs) => operator(lhs, rhs) };
42
+ }
31
43
  }
32
44
  export function isInfixDispatch(o) {
33
45
  return o instanceof InfixDispatch;
@@ -1,5 +1,5 @@
1
1
  import type { Snippet } from '../data/snippet.ts';
2
- import type { SimulationState } from '../types.ts';
2
+ import { type SimulationState } from '../types.ts';
3
3
  import type { makeResolvable } from './makeResolvable.ts';
4
4
  /**
5
5
  * WARNING: This is an API that touches a lot of internals, and is not stable
@@ -1,5 +1,6 @@
1
- import { $gpuValueOf, $internal, $ownSnippet, $resolve } from "../shared/symbols.js";
1
+ import { $comptimeValueOf, $gpuValueOf, $internal, $ownSnippet, $resolve, } from "../shared/symbols.js";
2
2
  import { valueProxyHandler } from "../core/valueProxyUtils.js";
3
+ import { runtimeOnly } from "../types.js";
3
4
  import { getExecMode } from "../execMode.js";
4
5
  import { assertExhaustive } from "../shared/utilityTypes.js";
5
6
  /**
@@ -30,6 +31,7 @@ export function makeDereferenceable(value, options) {
30
31
  }
31
32
  return options.codegenMode.getBaseSnippet.apply(resource, [this]);
32
33
  },
34
+ [$comptimeValueOf]: runtimeOnly,
33
35
  [$resolve]: (ctx) => ctx.resolve(resource),
34
36
  toString: () => `${resource.toString()}.$`,
35
37
  }, valueProxyHandler);
package/tgsl/shellless.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import { createShelllessImpl } from "../core/function/shelllessImpl.js";
2
2
  import { UnknownData } from "../data/dataTypes.js";
3
- import { RefOperator } from "../data/ref.js";
4
3
  import { isPtr, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
5
- import { WgslTypeError } from "../errors.js";
6
4
  import { getFunctionMetadata, getName } from "../shared/meta.js";
7
5
  import { concretize } from "./generationHelpers.js";
6
+ import { stringifySnippet } from "./stringifySnippet.js";
8
7
  function shallowEqualSchemas(a, b) {
9
8
  if (a.type !== b.type)
10
9
  return false;
@@ -34,14 +33,11 @@ export class ShelllessRepository {
34
33
  throw new Error(`Cannot resolve '${getName(fn)}' directly, because it expects arguments. Either call it from another function, or wrap it in a shell`);
35
34
  }
36
35
  const argTypes = (argSnippets ?? []).map((s, index) => {
37
- if (s.value instanceof RefOperator) {
38
- if (s.dataType === UnknownData) {
39
- throw new WgslTypeError(`d.ref() created with primitive types must be stored in a variable before use`);
40
- }
41
- return s.dataType;
42
- }
43
36
  if (s.dataType === UnknownData) {
44
- throw new Error(`Passed illegal value ${s.value} as the #${index} argument to ${getName(fn) ?? '<unnamed>'}(...)\n` +
37
+ const snippetStr = stringifySnippet(s);
38
+ const valueStr = String(s.value);
39
+ const description = snippetStr === valueStr ? `'${snippetStr}'` : `'${snippetStr}' (${valueStr})`;
40
+ throw new Error(`Passed illegal value ${description} as the #${index} argument to ${getName(fn) ?? '<unnamed>'}(...)\n` +
45
41
  `Shellless functions can only accept arguments representing WGSL resources: constructible WGSL types, d.refs, samplers or texture views.\n` +
46
42
  `Remember, that arguments such as samplers, texture views, accessors, slots etc. should be dereferenced via '.$' first.`);
47
43
  }
@@ -0,0 +1,9 @@
1
+ import type * as tinyest from 'tinyest';
2
+ import type { Snippet } from '../data/snippet.ts';
3
+ export declare function setSourceNode(snippet: Snippet, node: tinyest.Expression): Snippet;
4
+ export declare function getSourceNode(snippet: Snippet): tinyest.Expression | undefined;
5
+ /**
6
+ * Describes a snippet in a human-readable way, meant to be used in error messages.
7
+ * Prefers the JS expression that produced the snippet, falling back to its value.
8
+ */
9
+ export declare function stringifySnippet(snippet: Snippet): string;
@@ -0,0 +1,24 @@
1
+ import { stringifyNode } from "../shared/tseynit.js";
2
+ /**
3
+ * The JS expression that first produced a given snippet.
4
+ * Snippets can be shared (e.g. every reference to a variable returns the same snippet),
5
+ * so only the first, most specific node is remembered.
6
+ */
7
+ const sourceNodes = new WeakMap();
8
+ export function setSourceNode(snippet, node) {
9
+ if (!sourceNodes.has(snippet)) {
10
+ sourceNodes.set(snippet, node);
11
+ }
12
+ return snippet;
13
+ }
14
+ export function getSourceNode(snippet) {
15
+ return sourceNodes.get(snippet);
16
+ }
17
+ /**
18
+ * Describes a snippet in a human-readable way, meant to be used in error messages.
19
+ * Prefers the JS expression that produced the snippet, falling back to its value.
20
+ */
21
+ export function stringifySnippet(snippet) {
22
+ const node = getSourceNode(snippet);
23
+ return node !== undefined ? stringifyNode(node) : String(snippet.value);
24
+ }
@@ -27,6 +27,7 @@ export declare class WgslGenerator implements ShaderGenerator {
27
27
  */
28
28
  protected _typedExpression(expression: tinyest.Expression, expectedType: wgsl.BaseData | wgsl.BaseData[]): Snippet;
29
29
  protected _expression(expression: tinyest.Expression): Snippet;
30
+ private _expressionImpl;
30
31
  declareGlobalConst(options: ConstantDefinitionOptions): ResolvedSnippet;
31
32
  declareGlobalVar(options: VariableDefinitionOptions): ResolvedSnippet;
32
33
  functionDefinition(options: FunctionDefinitionOptions): string;