typegpu 0.10.2 → 0.11.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.
Files changed (211) hide show
  1. package/_virtual/_rolldown/runtime.js +6 -11
  2. package/builtin.js +1 -3
  3. package/common/fullScreenTriangle.d.ts +1 -5
  4. package/common/fullScreenTriangle.js +1 -3
  5. package/common/index.d.ts +3 -2
  6. package/common/index.js +6 -4
  7. package/common/writeSoA.d.ts +16 -0
  8. package/common/writeSoA.js +90 -0
  9. package/core/buffer/buffer.d.ts +12 -7
  10. package/core/buffer/buffer.js +102 -53
  11. package/core/buffer/bufferShorthand.d.ts +5 -5
  12. package/core/buffer/bufferShorthand.js +9 -5
  13. package/core/buffer/bufferUsage.d.ts +0 -2
  14. package/core/buffer/bufferUsage.js +6 -4
  15. package/core/constant/tgpuConstant.d.ts +2 -1
  16. package/core/constant/tgpuConstant.js +8 -7
  17. package/core/declare/tgpuDeclare.js +8 -9
  18. package/core/function/autoIO.d.ts +7 -6
  19. package/core/function/autoIO.js +1 -3
  20. package/core/function/comptime.js +1 -3
  21. package/core/function/createCallableSchema.js +4 -6
  22. package/core/function/dualImpl.js +1 -3
  23. package/core/function/entryInputRouter.js +39 -0
  24. package/core/function/extractArgs.js +2 -2
  25. package/core/function/fnCore.js +19 -8
  26. package/core/function/fnTypes.d.ts +14 -8
  27. package/core/function/ioSchema.js +24 -3
  28. package/core/function/shelllessImpl.js +1 -3
  29. package/core/function/templateUtils.js +1 -2
  30. package/core/function/tgpuComputeFn.d.ts +2 -3
  31. package/core/function/tgpuComputeFn.js +9 -16
  32. package/core/function/tgpuFn.d.ts +2 -2
  33. package/core/function/tgpuFn.js +1 -3
  34. package/core/function/tgpuFragmentFn.d.ts +5 -1
  35. package/core/function/tgpuFragmentFn.js +5 -10
  36. package/core/function/tgpuVertexFn.d.ts +4 -0
  37. package/core/function/tgpuVertexFn.js +6 -12
  38. package/core/pipeline/applyPipelineState.js +1 -3
  39. package/core/pipeline/computePipeline.d.ts +2 -6
  40. package/core/pipeline/computePipeline.js +64 -63
  41. package/core/pipeline/connectAttachmentToShader.js +1 -3
  42. package/core/pipeline/connectTargetsToShader.js +1 -3
  43. package/core/pipeline/limitsOverflow.js +1 -2
  44. package/core/pipeline/pipelineUtils.js +29 -0
  45. package/core/pipeline/renderPipeline.d.ts +23 -5
  46. package/core/pipeline/renderPipeline.js +32 -14
  47. package/core/pipeline/timeable.d.ts +0 -3
  48. package/core/pipeline/timeable.js +3 -9
  49. package/core/pipeline/typeGuards.js +1 -3
  50. package/core/querySet/querySet.d.ts +0 -2
  51. package/core/querySet/querySet.js +37 -36
  52. package/core/rawCodeSnippet/tgpuRawCodeSnippet.js +1 -3
  53. package/core/resolve/externals.d.ts +0 -2
  54. package/core/resolve/externals.js +2 -4
  55. package/core/resolve/namespace.js +1 -3
  56. package/core/resolve/resolveData.js +1 -3
  57. package/core/resolve/stitch.js +1 -3
  58. package/core/resolve/tgpuResolve.d.ts +3 -1
  59. package/core/resolve/tgpuResolve.js +3 -5
  60. package/core/root/configurableImpl.js +2 -3
  61. package/core/root/init.d.ts +0 -5
  62. package/core/root/init.js +35 -28
  63. package/core/root/rootTypes.d.ts +25 -5
  64. package/core/sampler/sampler.d.ts +0 -4
  65. package/core/sampler/sampler.js +3 -3
  66. package/core/simulate/tgpuSimulate.js +1 -3
  67. package/core/slot/accessor.d.ts +0 -4
  68. package/core/slot/accessor.js +1 -3
  69. package/core/slot/internalSlots.js +1 -3
  70. package/core/slot/lazy.js +1 -3
  71. package/core/slot/slot.js +2 -3
  72. package/core/slot/slotTypes.js +1 -3
  73. package/core/texture/externalTexture.d.ts +0 -6
  74. package/core/texture/externalTexture.js +2 -3
  75. package/core/texture/texture.d.ts +0 -4
  76. package/core/texture/texture.js +5 -3
  77. package/core/texture/textureFormats.js +1 -3
  78. package/core/texture/textureUtils.js +1 -3
  79. package/core/texture/usageExtension.js +1 -3
  80. package/core/unroll/tgpuUnroll.d.ts +58 -3
  81. package/core/unroll/tgpuUnroll.js +63 -5
  82. package/core/valueProxyUtils.js +1 -3
  83. package/core/variable/tgpuVariable.js +1 -3
  84. package/core/vertexLayout/connectAttributesToShader.js +1 -3
  85. package/core/vertexLayout/vertexLayout.js +9 -9
  86. package/data/alignIO.js +1 -2
  87. package/data/alignmentOf.d.ts +0 -1
  88. package/data/alignmentOf.js +1 -3
  89. package/data/array.d.ts +1 -3
  90. package/data/array.js +2 -4
  91. package/data/atomic.js +2 -3
  92. package/data/attributes.js +3 -3
  93. package/data/autoStruct.d.ts +1 -3
  94. package/data/autoStruct.js +1 -3
  95. package/data/compiledIO.js +83 -86
  96. package/data/dataIO.js +46 -39
  97. package/data/dataTypes.d.ts +7 -7
  98. package/data/dataTypes.js +6 -3
  99. package/data/deepEqual.js +1 -3
  100. package/data/disarray.d.ts +1 -3
  101. package/data/disarray.js +1 -3
  102. package/data/getLongestContiguousPrefix.d.ts +0 -1
  103. package/data/getLongestContiguousPrefix.js +1 -3
  104. package/data/index.d.ts +3 -3
  105. package/data/index.js +10 -3
  106. package/data/isContiguous.d.ts +0 -1
  107. package/data/isContiguous.js +1 -3
  108. package/data/matrix.d.ts +8 -10
  109. package/data/matrix.js +32 -18
  110. package/data/numberOps.js +1 -2
  111. package/data/numeric.js +16 -29
  112. package/data/offsetUtils.d.ts +2 -2
  113. package/data/offsetUtils.js +3 -5
  114. package/data/offsets.js +1 -3
  115. package/data/partialIO.js +84 -39
  116. package/data/ptr.d.ts +0 -1
  117. package/data/ptr.js +1 -3
  118. package/data/ref.d.ts +0 -3
  119. package/data/ref.js +1 -3
  120. package/data/sampler.js +1 -3
  121. package/data/schemaCallWrapper.js +1 -3
  122. package/data/schemaMemoryLayout.js +1 -3
  123. package/data/sizeOf.d.ts +0 -1
  124. package/data/sizeOf.js +1 -3
  125. package/data/snippet.js +12 -3
  126. package/data/struct.js +1 -3
  127. package/data/texture.js +1 -3
  128. package/data/unstruct.js +1 -3
  129. package/data/vector.js +4 -12
  130. package/data/vectorImpl.js +27 -28
  131. package/data/vectorOps.js +20 -3
  132. package/data/vertexFormatData.js +2 -3
  133. package/data/wgslTypes.d.ts +39 -11
  134. package/data/wgslTypes.js +10 -3
  135. package/errors.js +6 -3
  136. package/execMode.js +1 -3
  137. package/extension.js +1 -3
  138. package/getGPUValue.js +1 -3
  139. package/index.d.ts +4 -2
  140. package/index.js +3 -3
  141. package/indexNamedExports.d.ts +3 -1
  142. package/mathUtils.js +1 -2
  143. package/memo.js +8 -8
  144. package/nameRegistry.js +1 -3
  145. package/package.js +2 -3
  146. package/package.json +7 -7
  147. package/resolutionCtx.d.ts +0 -10
  148. package/resolutionCtx.js +84 -18
  149. package/shared/env.js +1 -2
  150. package/shared/generators.js +1 -2
  151. package/shared/meta.js +1 -3
  152. package/shared/repr.d.ts +32 -2
  153. package/shared/stringify.js +1 -3
  154. package/shared/symbols.d.ts +10 -1
  155. package/shared/symbols.js +10 -33
  156. package/shared/utilityTypes.d.ts +6 -2
  157. package/shared/utilityTypes.js +1 -2
  158. package/shared/vertexFormat.js +1 -2
  159. package/std/array.d.ts +1 -1
  160. package/std/array.js +1 -3
  161. package/std/atomic.d.ts +12 -12
  162. package/std/atomic.js +1 -3
  163. package/std/bitcast.d.ts +2 -2
  164. package/std/bitcast.js +1 -3
  165. package/std/boolean.d.ts +30 -16
  166. package/std/boolean.js +37 -12
  167. package/std/derivative.d.ts +9 -9
  168. package/std/derivative.js +1 -3
  169. package/std/discard.d.ts +1 -1
  170. package/std/discard.js +1 -3
  171. package/std/extensions.d.ts +1 -3
  172. package/std/extensions.js +1 -3
  173. package/std/index.d.ts +5 -4
  174. package/std/index.js +8 -5
  175. package/std/matrix.d.ts +5 -5
  176. package/std/matrix.js +1 -3
  177. package/std/numeric.d.ts +78 -132
  178. package/std/numeric.js +1 -3
  179. package/std/operators.d.ts +16 -8
  180. package/std/operators.js +80 -6
  181. package/std/packing.d.ts +4 -4
  182. package/std/packing.js +1 -3
  183. package/std/range.d.ts +24 -0
  184. package/std/range.js +38 -0
  185. package/std/subgroup.d.ts +21 -21
  186. package/std/subgroup.js +1 -3
  187. package/std/texture.d.ts +20 -11
  188. package/std/texture.js +13 -3
  189. package/tgpu.js +1 -3
  190. package/tgpuBindGroupLayout.js +9 -8
  191. package/tgpuUnstable.js +1 -3
  192. package/tgsl/accessIndex.js +2 -4
  193. package/tgsl/accessProp.js +8 -6
  194. package/tgsl/consoleLog/deserializers.js +1 -3
  195. package/tgsl/consoleLog/logGenerator.js +2 -4
  196. package/tgsl/consoleLog/serializers.js +24 -26
  197. package/tgsl/consoleLog/types.d.ts +0 -2
  198. package/tgsl/consoleLog/types.js +1 -2
  199. package/tgsl/conversion.js +1 -3
  200. package/tgsl/forOfUtils.js +35 -9
  201. package/tgsl/generationHelpers.js +3 -3
  202. package/tgsl/math.js +1 -3
  203. package/tgsl/shaderGenerator.d.ts +10 -8
  204. package/tgsl/shaderGenerator_members.d.ts +2 -0
  205. package/tgsl/shaderGenerator_members.js +6 -0
  206. package/tgsl/shellless.js +1 -8
  207. package/tgsl/wgslGenerator.d.ts +36 -0
  208. package/tgsl/wgslGenerator.js +144 -81
  209. package/types.d.ts +14 -4
  210. package/types.js +3 -3
  211. package/wgslExtensions.js +1 -2
@@ -7,7 +7,6 @@ import { getOwnSnippet } from "../types.js";
7
7
  import { stitch } from "../core/resolve/stitch.js";
8
8
  import { isRef } from "../data/ref.js";
9
9
  import { abstractFloat, abstractInt, bool, f32, i32 } from "../data/numeric.js";
10
-
11
10
  //#region src/tgsl/generationHelpers.ts
12
11
  function numericLiteralToSnippet(value) {
13
12
  if (value >= 2 ** 63 || value < -(2 ** 63)) return snip(value, abstractFloat, "constant");
@@ -46,6 +45,8 @@ function coerceToSnippet(value) {
46
45
  */
47
46
  var ArrayExpression = class {
48
47
  [$internal] = true;
48
+ type;
49
+ elements;
49
50
  constructor(type, elements) {
50
51
  this.type = type;
51
52
  this.elements = elements;
@@ -62,6 +63,5 @@ var ArrayExpression = class {
62
63
  return snip(stitch`${`array<${ctx.resolve(this.type.elementType).value}, ${this.elements.length}>`}(${this.elements})`, this.type, "runtime");
63
64
  }
64
65
  };
65
-
66
66
  //#endregion
67
- export { ArrayExpression, coerceToSnippet, concretize, concretizeSnippets, numericLiteralToSnippet };
67
+ export { ArrayExpression, coerceToSnippet, concretize, concretizeSnippets, numericLiteralToSnippet };
package/tgsl/math.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { f32 } from "../data/numeric.js";
2
2
  import { abs, acos, acosh, asin, asinh, atan, atan2, atanh, ceil, cos, cosh, countLeadingZeros, exp, floor, log, log2, max, min, pow, sign, sin, sinh, sqrt, tan, tanh, trunc } from "../std/numeric.js";
3
-
4
3
  //#region src/tgsl/math.ts
5
4
  const mathToStd = {
6
5
  abs,
@@ -40,6 +39,5 @@ const mathToStd = {
40
39
  imul: void 0,
41
40
  round: void 0
42
41
  };
43
-
44
42
  //#endregion
45
- export { mathToStd };
43
+ export { mathToStd };
@@ -1,18 +1,20 @@
1
- import { Snippet } from "../data/snippet.js";
1
+ import { ResolvedSnippet, Snippet } from "../data/snippet.js";
2
2
  import { GenerationCtx } from "./generationHelpers.js";
3
- import { ExternalMap } from "../core/resolve/externals.js";
4
3
  import { BaseData } from "../data/wgslTypes.js";
5
- import { Block, Expression, Statement } from "tinyest";
4
+ import { Block } from "tinyest";
6
5
 
7
6
  //#region src/tgsl/shaderGenerator.d.ts
7
+ /**
8
+ * **NOTE: This is an unstable API and may change in the future.**
9
+ *
10
+ * An interface meant to be used by other systems to generate snippets of
11
+ * shader code in the target language (WGSL, GLSL, etc.).
12
+ */
8
13
  interface ShaderGenerator {
9
14
  initGenerator(ctx: GenerationCtx): void;
10
- block(body: Block, externalMap?: ExternalMap): string;
11
- identifier(id: string): Snippet;
12
- typedExpression(expression: Expression, expectedType: BaseData): Snippet;
13
- expression(expression: Expression): Snippet;
14
- statement(statement: Statement): string;
15
15
  functionDefinition(body: Block): string;
16
+ typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet;
17
+ typeAnnotation(schema: BaseData): string;
16
18
  }
17
19
  //#endregion
18
20
  export { ShaderGenerator };
@@ -0,0 +1,2 @@
1
+ import { ResolutionCtx } from "../types.js";
2
+ import { UnknownData } from "../data/dataTypes.js";
@@ -0,0 +1,6 @@
1
+ import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
+ import { UnknownData } from "../data/dataTypes.js";
3
+ //#region src/tgsl/shaderGenerator_members.ts
4
+ var shaderGenerator_members_exports = /* @__PURE__ */ __exportAll({ UnknownData: () => UnknownData });
5
+ //#endregion
6
+ export { shaderGenerator_members_exports };
package/tgsl/shellless.js CHANGED
@@ -2,11 +2,9 @@ import { getMetaData, getName } from "../shared/meta.js";
2
2
  import { isPtr, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
3
3
  import { UnknownData } from "../data/dataTypes.js";
4
4
  import { WgslTypeError } from "../errors.js";
5
- import { getResolutionCtx } from "../execMode.js";
6
5
  import { RefOperator } from "../data/ref.js";
7
6
  import { concretize } from "./generationHelpers.js";
8
7
  import { createShelllessImpl } from "../core/function/shelllessImpl.js";
9
-
10
8
  //#region src/tgsl/shellless.ts
11
9
  function shallowEqualSchemas(a, b) {
12
10
  if (a.type !== b.type) return false;
@@ -28,10 +26,6 @@ var ShelllessRepository = class {
28
26
  }
29
27
  if (s.dataType === UnknownData) throw new Error(`Passed illegal value ${s.value} as the #${index} argument to ${meta.name}(...)\nShellless functions can only accept arguments representing WGSL resources: constructible WGSL types, d.refs, samplers or texture views.\nRemember, that arguments such as samplers, texture views, accessors, slots etc. should be dereferenced via '.$' first.`);
30
28
  let type = concretize(s.dataType);
31
- if (s.origin === "constant-tgpu-const-ref" || s.origin === "runtime-tgpu-const-ref") {
32
- const ctx = getResolutionCtx();
33
- throw new Error(`Cannot pass constant references as function arguments. Explicitly copy them by wrapping them in a schema: '${ctx.resolve(type).value}(...)'`);
34
- }
35
29
  if (isPtr(type) && type.implicit) type = type.inner;
36
30
  return type;
37
31
  });
@@ -48,6 +42,5 @@ var ShelllessRepository = class {
48
42
  return shellless;
49
43
  }
50
44
  };
51
-
52
45
  //#endregion
53
- export { ShelllessRepository };
46
+ export { ShelllessRepository };
@@ -0,0 +1,36 @@
1
+ import { Origin, ResolvedSnippet, Snippet } from "../data/snippet.js";
2
+ import { GenerationCtx } from "./generationHelpers.js";
3
+ import { ShaderGenerator } from "./shaderGenerator.js";
4
+ import { BaseData, StorableData } from "../data/wgslTypes.js";
5
+ import { UnknownData } from "../data/dataTypes.js";
6
+ import { ExternalMap } from "../core/resolve/externals.js";
7
+ import * as tinyest from "tinyest";
8
+
9
+ //#region src/tgsl/wgslGenerator.d.ts
10
+ declare class WgslGenerator implements ShaderGenerator {
11
+ #private;
12
+ initGenerator(ctx: GenerationCtx): void;
13
+ protected get ctx(): GenerationCtx;
14
+ _block([_, statements]: tinyest.Block, externalMap?: ExternalMap): string;
15
+ refVariable(id: string, dataType: StorableData): string;
16
+ blockVariable(varType: 'var' | 'let' | 'const', id: string, dataType: BaseData | UnknownData, origin: Origin): Snippet;
17
+ protected emitVarDecl(pre: string, keyword: 'var' | 'let' | 'const', name: string, _dataType: BaseData | UnknownData, rhsStr: string): string;
18
+ _identifier(id: string): Snippet;
19
+ /**
20
+ * A wrapper for `generateExpression` that updates `ctx.expectedType`
21
+ * and tries to convert the result when it does not match the expected type.
22
+ */
23
+ _typedExpression(expression: tinyest.Expression, expectedType: BaseData | BaseData[]): Snippet;
24
+ _expression(expression: tinyest.Expression): Snippet;
25
+ functionDefinition(body: tinyest.Block): string;
26
+ /**
27
+ * Generates a WGSL type string for the given data type, and adds necessary
28
+ * definitions to the shader preamble. This shouldn't be called directly, only
29
+ * through `ctx.resolve` to properly cache the result.
30
+ */
31
+ typeAnnotation(data: BaseData): string;
32
+ typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet;
33
+ _statement(statement: tinyest.Statement): string;
34
+ }
35
+ //#endregion
36
+ export { WgslGenerator };
@@ -1,8 +1,8 @@
1
1
  import { $gpuCallable, $internal, $providing, isMarkedInternal } from "../shared/symbols.js";
2
2
  import { getName } from "../shared/meta.js";
3
- import { Void, isNaturallyEphemeral, isPtr, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
3
+ import { Void, isBool, isNaturallyEphemeral, isNumericSchema, isPtr, isVec, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
4
4
  import { ConsoleLog, InfixDispatch, UnknownData, isLooseData, unptr } from "../data/dataTypes.js";
5
- import { isEphemeralOrigin, isEphemeralSnippet, snip } from "../data/snippet.js";
5
+ import { fallthroughCopyOrigin, isEphemeralOrigin, isEphemeralSnippet, snip } from "../data/snippet.js";
6
6
  import { ResolutionError, WgslTypeError, invariant } from "../errors.js";
7
7
  import { isGPUCallable, isKnownAtComptime } from "../types.js";
8
8
  import { stitch } from "../core/resolve/stitch.js";
@@ -11,8 +11,9 @@ import { RefOperator } from "../data/ref.js";
11
11
  import { safeStringify } from "../shared/stringify.js";
12
12
  import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./conversion.js";
13
13
  import { bool, i32, u32 } from "../data/numeric.js";
14
- import { AutoStruct } from "../data/autoStruct.js";
15
14
  import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet } from "./generationHelpers.js";
15
+ import { vec2u, vec3u, vec4u } from "../data/vector.js";
16
+ import { AutoStruct } from "../data/autoStruct.js";
16
17
  import { add, div, mul, neg, sub } from "../std/operators.js";
17
18
  import { accessProp } from "./accessProp.js";
18
19
  import { accessIndex } from "./accessIndex.js";
@@ -20,11 +21,12 @@ import { constant } from "../core/constant/tgpuConstant.js";
20
21
  import { isGenericFn } from "../core/function/tgpuFn.js";
21
22
  import { arrayOf } from "../data/array.js";
22
23
  import { pow } from "../std/numeric.js";
24
+ import { resolveData } from "../core/resolve/resolveData.js";
23
25
  import { UnrollableIterable } from "../core/unroll/tgpuUnroll.js";
24
26
  import { mathToStd } from "./math.js";
25
- import { getElementCountSnippet, getElementSnippet, getElementType, getLoopVarKind } from "./forOfUtils.js";
27
+ import { isTgpuRange } from "../std/range.js";
28
+ import { getElementSnippet, getElementType, getLoopVarKind, getRangeSnippets } from "./forOfUtils.js";
26
29
  import * as tinyest from "tinyest";
27
-
28
30
  //#region src/tgsl/wgslGenerator.ts
29
31
  const { NodeTypeCatalog: NODE } = tinyest;
30
32
  const parenthesizedOps = [
@@ -61,6 +63,12 @@ const binaryLogicalOps = [
61
63
  ">",
62
64
  ">="
63
65
  ];
66
+ const bitShiftOps = [
67
+ "<<",
68
+ ">>",
69
+ "<<=",
70
+ ">>="
71
+ ];
64
72
  const OP_MAP = {
65
73
  "===": "==",
66
74
  "!==": "!=",
@@ -106,7 +114,19 @@ function operatorToType(lhs, op, rhs) {
106
114
  }
107
115
  const unaryOpCodeToCodegen = {
108
116
  "-": neg[$gpuCallable].call.bind(neg),
109
- void: () => snip(void 0, Void, "constant")
117
+ void: () => snip(void 0, Void, "constant"),
118
+ "!": (ctx, [argExpr]) => {
119
+ if (argExpr === void 0) throw new Error("The unary operator `!` expects 1 argument, but 0 were provided.");
120
+ if (isKnownAtComptime(argExpr)) return snip(!argExpr.value, bool, "constant");
121
+ const { value, dataType } = argExpr;
122
+ const argStr = ctx.resolve(value, dataType).value;
123
+ if (isBool(dataType)) return snip(`!${argStr}`, bool, "runtime");
124
+ if (isNumericSchema(dataType)) {
125
+ const resultStr = `!bool(${argStr})`;
126
+ return snip(dataType.type === "f32" ? `(((bitcast<u32>(${argStr}) & 0x7fffffff) > 0x7f800000) || ${resultStr})` : dataType.type === "f16" ? `(((bitcast<u32>(${argStr}) & 0x7fff) > 0x7c00) || ${resultStr})` : resultStr, bool, "runtime");
127
+ }
128
+ return snip(false, bool, "constant");
129
+ }
110
130
  };
111
131
  const binaryOpCodeToCodegen = {
112
132
  "+": add[$gpuCallable].call.bind(add),
@@ -125,7 +145,7 @@ var WgslGenerator = class {
125
145
  if (!this.#ctx) throw new Error("WGSL Generator has not yet been initialized. Please call initialize(ctx) before using the generator.");
126
146
  return this.#ctx;
127
147
  }
128
- block([_, statements], externalMap) {
148
+ _block([_, statements], externalMap) {
129
149
  this.ctx.pushBlockScope();
130
150
  if (externalMap) {
131
151
  const externals = Object.fromEntries(Object.entries(externalMap).map(([id, value]) => [id, coerceToSnippet(value)]));
@@ -133,7 +153,7 @@ var WgslGenerator = class {
133
153
  }
134
154
  try {
135
155
  this.ctx.indent();
136
- const body = statements.map((statement) => this.statement(statement)).filter((statement) => statement.length > 0).join("\n");
156
+ const body = statements.map((statement) => this._statement(statement)).filter((statement) => statement.length > 0).join("\n");
137
157
  this.ctx.dedent();
138
158
  return `{
139
159
  ${body}
@@ -162,7 +182,10 @@ ${this.ctx.pre}}`;
162
182
  this.ctx.defineVariable(id, snippet);
163
183
  return snippet;
164
184
  }
165
- identifier(id) {
185
+ emitVarDecl(pre, keyword, name, _dataType, rhsStr) {
186
+ return `${pre}${keyword} ${name} = ${rhsStr};`;
187
+ }
188
+ _identifier(id) {
166
189
  if (!id) throw new Error("Cannot resolve an empty identifier");
167
190
  if (id === "undefined") return snip(void 0, Void, "constant");
168
191
  const res = this.ctx.getById(id);
@@ -173,24 +196,33 @@ ${this.ctx.pre}}`;
173
196
  * A wrapper for `generateExpression` that updates `ctx.expectedType`
174
197
  * and tries to convert the result when it does not match the expected type.
175
198
  */
176
- typedExpression(expression, expectedType) {
199
+ _typedExpression(expression, expectedType) {
177
200
  const prevExpectedType = this.ctx.expectedType;
178
201
  this.ctx.expectedType = expectedType;
179
202
  try {
180
- const result = this.expression(expression);
203
+ const result = this._expression(expression);
181
204
  if (expectedType instanceof AutoStruct) return result;
182
205
  return tryConvertSnippet(this.ctx, result, expectedType);
183
206
  } finally {
184
207
  this.ctx.expectedType = prevExpectedType;
185
208
  }
186
209
  }
187
- expression(expression) {
188
- if (typeof expression === "string") return this.identifier(expression);
210
+ _expression(expression) {
211
+ if (typeof expression === "string") return this._identifier(expression);
189
212
  if (typeof expression === "boolean") return snip(expression, bool, "constant");
190
213
  if (expression[0] === NODE.logicalExpr || expression[0] === NODE.binaryExpr || expression[0] === NODE.assignmentExpr) {
191
214
  const [exprType, lhs, op, rhs] = expression;
192
- const lhsExpr = this.expression(lhs);
193
- const rhsExpr = this.expression(rhs);
215
+ const lhsExpr = this._expression(lhs);
216
+ if ((op === "||" || op === "&&") && isKnownAtComptime(lhsExpr)) {
217
+ if (!(op === "&&" ? lhsExpr.value : !lhsExpr.value)) return snip(op === "||", bool, "constant");
218
+ const rhsExpr = this._expression(rhs);
219
+ if (rhsExpr.dataType === UnknownData) throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
220
+ if (isKnownAtComptime(rhsExpr)) return snip(!!rhsExpr.value, bool, "constant");
221
+ const convRhs = tryConvertSnippet(this.ctx, rhsExpr, bool, false);
222
+ const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value;
223
+ return snip(rhsStr, bool, "runtime");
224
+ }
225
+ const rhsExpr = this._expression(rhs);
194
226
  if (rhsExpr.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot assign a ref to an existing variable '${lhsExpr}', define a new variable instead.`);
195
227
  if (op === "==") throw new Error("Please use the === operator instead of ==");
196
228
  if (op === "===" && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) return snip(lhsExpr.value === rhsExpr.value, bool, "constant");
@@ -198,8 +230,20 @@ ${this.ctx.pre}}`;
198
230
  if (rhsExpr.dataType === UnknownData) throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
199
231
  const codegen = binaryOpCodeToCodegen[op];
200
232
  if (codegen) return codegen(this.ctx, [lhsExpr, rhsExpr]);
201
- const forcedType = exprType === NODE.assignmentExpr ? [lhsExpr.dataType] : void 0;
202
- const [convLhs, convRhs] = convertToCommonType(this.ctx, [lhsExpr, rhsExpr], forcedType) ?? [lhsExpr, rhsExpr];
233
+ let convLhs;
234
+ let convRhs;
235
+ if (bitShiftOps.includes(op)) {
236
+ let rhsTarget;
237
+ if (isVec(lhsExpr.dataType)) {
238
+ const cc = lhsExpr.dataType.componentCount;
239
+ rhsTarget = cc === 2 ? vec2u : cc === 3 ? vec3u : vec4u;
240
+ } else rhsTarget = u32;
241
+ convRhs = tryConvertSnippet(this.ctx, rhsExpr, rhsTarget, false);
242
+ convLhs = lhsExpr;
243
+ } else {
244
+ const forcedType = exprType === NODE.assignmentExpr ? [lhsExpr.dataType] : void 0;
245
+ [convLhs, convRhs] = convertToCommonType(this.ctx, [lhsExpr, rhsExpr], forcedType) ?? [lhsExpr, rhsExpr];
246
+ }
203
247
  const lhsStr = this.ctx.resolve(convLhs.value, convLhs.dataType).value;
204
248
  const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value;
205
249
  const type = operatorToType(convLhs.dataType, op, convRhs.dataType);
@@ -213,13 +257,13 @@ ${this.ctx.pre}}`;
213
257
  }
214
258
  if (expression[0] === NODE.postUpdate) {
215
259
  const [_, op, arg] = expression;
216
- const argExpr = this.expression(arg);
260
+ const argExpr = this._expression(arg);
217
261
  const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value;
218
262
  return snip(`${argStr}${op}`, argExpr.dataType, "runtime");
219
263
  }
220
264
  if (expression[0] === NODE.unaryExpr) {
221
265
  const [_, op, arg] = expression;
222
- const argExpr = this.expression(arg);
266
+ const argExpr = this._expression(arg);
223
267
  const codegen = unaryOpCodeToCodegen[op];
224
268
  if (codegen) return codegen(this.ctx, [argExpr]);
225
269
  const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value;
@@ -228,7 +272,7 @@ ${this.ctx.pre}}`;
228
272
  }
229
273
  if (expression[0] === NODE.memberAccess) {
230
274
  const [_, targetNode, property] = expression;
231
- const target = this.expression(targetNode);
275
+ const target = this._expression(targetNode);
232
276
  if (target.value === console) return snip(new ConsoleLog(property), UnknownData, "runtime");
233
277
  if (target.value === Math) {
234
278
  if (property in mathToStd && mathToStd[property]) return snip(mathToStd[property], UnknownData, "runtime");
@@ -240,8 +284,8 @@ ${this.ctx.pre}}`;
240
284
  }
241
285
  if (expression[0] === NODE.indexAccess) {
242
286
  const [_, targetNode, propertyNode] = expression;
243
- const target = this.expression(targetNode);
244
- const inProperty = this.expression(propertyNode);
287
+ const target = this._expression(targetNode);
288
+ const inProperty = this._expression(propertyNode);
245
289
  const property = convertToCommonType(this.ctx, [inProperty], [u32, i32], false)?.[0] ?? inProperty;
246
290
  const accessed = accessIndex(target, property);
247
291
  if (!accessed) {
@@ -258,27 +302,27 @@ ${this.ctx.pre}}`;
258
302
  }
259
303
  if (expression[0] === NODE.call) {
260
304
  const [_, calleeNode, argNodes] = expression;
261
- const callee = this.expression(calleeNode);
305
+ const callee = this._expression(calleeNode);
262
306
  if (isWgslStruct(callee.value)) {
263
307
  if (argNodes.length > 1) throw new WgslTypeError("Struct schemas should always be called with at most 1 argument");
264
308
  if (!argNodes[0]) return snip(`${this.ctx.resolve(callee.value).value}()`, callee.value, "runtime");
265
- const arg = this.typedExpression(argNodes[0], callee.value);
309
+ const arg = this._typedExpression(argNodes[0], callee.value);
266
310
  return snip(this.ctx.resolve(arg.value, callee.value).value, callee.value, "runtime");
267
311
  }
268
312
  if (isWgslArray(callee.value)) {
269
313
  if (argNodes.length > 1) throw new WgslTypeError("Array schemas should always be called with at most 1 argument");
270
314
  if (!argNodes[0]) return snip(`${this.ctx.resolve(callee.value).value}()`, callee.value, "runtime");
271
- const arg = this.typedExpression(argNodes[0], callee.value);
315
+ const arg = this._typedExpression(argNodes[0], callee.value);
272
316
  if (arg.value instanceof ArrayExpression) return snip(stitch`${this.ctx.resolve(callee.value).value}(${arg.value.elements})`, arg.dataType, "runtime");
273
317
  return snip(this.ctx.resolve(arg.value, callee.value).value, callee.value, "runtime");
274
318
  }
275
319
  if (callee.value === constant) throw new Error("Constants cannot be defined within TypeGPU function scope. To address this, move the constant definition outside the function scope.");
276
320
  if (callee.value instanceof InfixDispatch) {
277
321
  if (!argNodes[0]) throw new WgslTypeError(`An infix operator '${callee.value.name}' was called without any arguments`);
278
- const rhs = this.expression(argNodes[0]);
322
+ const rhs = this._expression(argNodes[0]);
279
323
  return callee.value.operator(this.ctx, [callee.value.lhs, rhs]);
280
324
  }
281
- if (callee.value instanceof ConsoleLog) return this.ctx.generateLog(callee.value.op, argNodes.map((arg) => this.expression(arg)));
325
+ if (callee.value instanceof ConsoleLog) return this.ctx.generateLog(callee.value.op, argNodes.map((arg) => this._expression(arg)));
282
326
  if (isGPUCallable(callee.value)) {
283
327
  const callable = callee.value[$gpuCallable];
284
328
  const strictSignature = callable.strictSignature;
@@ -286,9 +330,9 @@ ${this.ctx.pre}}`;
286
330
  if (strictSignature) convertedArguments = argNodes.map((arg, i) => {
287
331
  const argType = strictSignature.argTypes[i];
288
332
  if (!argType) throw new WgslTypeError(`Function '${getName(callee.value)}' was called with too many arguments`);
289
- return this.typedExpression(arg, argType);
333
+ return this._typedExpression(arg, argType);
290
334
  });
291
- else convertedArguments = argNodes.map((arg) => this.expression(arg));
335
+ else convertedArguments = argNodes.map((arg) => this._expression(arg));
292
336
  try {
293
337
  return callable.call(this.ctx, convertedArguments);
294
338
  } catch (err) {
@@ -301,7 +345,7 @@ ${this.ctx.pre}}`;
301
345
  const slotPairs = isGeneric ? callee.value[$providing]?.pairs ?? [] : [];
302
346
  const callback = isGeneric ? callee.value[$internal].inner : callee.value;
303
347
  const shelllessCall = this.ctx.withRenamed(callback, getName(callee.value), () => this.ctx.withSlots(slotPairs, () => {
304
- const args = argNodes.map((arg) => this.expression(arg));
348
+ const args = argNodes.map((arg) => this._expression(arg));
305
349
  const shellless = this.ctx.shelllessRepo.get(callback, args);
306
350
  if (!shellless) return;
307
351
  const converted = args.map((s, idx) => {
@@ -324,11 +368,11 @@ ${this.ctx.pre}}`;
324
368
  const entries = Object.fromEntries(Object.entries(obj).map(([key, value]) => {
325
369
  let accessed = structType.accessProp(key);
326
370
  let expr;
327
- if (accessed) expr = this.typedExpression(value, accessed.type);
371
+ if (accessed) expr = this._typedExpression(value, accessed.type);
328
372
  else {
329
- expr = this.expression(value);
373
+ expr = this._expression(value);
330
374
  if (expr.dataType === UnknownData) throw new WgslTypeError(stitch`Property ${key} in object literal has a value of unknown type: '${expr}'`);
331
- accessed = structType.provideProp(key, concretize(expr.dataType));
375
+ accessed = structType.provideProp(key, unptr(concretize(expr.dataType)));
332
376
  }
333
377
  return [accessed.prop, expr];
334
378
  }));
@@ -340,7 +384,7 @@ ${this.ctx.pre}}`;
340
384
  const entries = Object.fromEntries(Object.entries(structType.propTypes).map(([key, value]) => {
341
385
  const val = obj[key];
342
386
  if (val === void 0) throw new WgslTypeError(`Missing property ${key} in object literal for struct ${structType}`);
343
- return [key, this.typedExpression(val, value)];
387
+ return [key, this._typedExpression(val, value)];
344
388
  }));
345
389
  const convertedSnippets = convertStructValues(this.ctx, structType, entries);
346
390
  return snip(stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType, "runtime");
@@ -354,10 +398,10 @@ ${this.ctx.pre}}`;
354
398
  let values;
355
399
  if (isWgslArray(arrType)) {
356
400
  elemType = arrType.elementType;
357
- values = valueNodes.map((value) => this.typedExpression(value, elemType));
401
+ values = valueNodes.map((value) => this._typedExpression(value, elemType));
358
402
  if (values.length !== arrType.elementCount) throw new WgslTypeError(`Cannot create value of type '${arrType}' from an array of length: ${values.length}`);
359
403
  } else {
360
- const valuesSnippets = valueNodes.map((value) => this.expression(value));
404
+ const valuesSnippets = valueNodes.map((value) => this._expression(value));
361
405
  if (valuesSnippets.length === 0) throw new WgslTypeError("Cannot infer the type of an empty array literal.");
362
406
  const converted = convertToCommonType(this.ctx, valuesSnippets);
363
407
  if (!converted) throw new WgslTypeError("The given values cannot be automatically converted to a common type. Consider wrapping the array in an appropriate schema");
@@ -369,8 +413,8 @@ ${this.ctx.pre}}`;
369
413
  }
370
414
  if (expression[0] === NODE.conditionalExpr) {
371
415
  const [_, test, consequent, alternative] = expression;
372
- const testExpression = this.expression(test);
373
- if (isKnownAtComptime(testExpression)) return testExpression.value ? this.expression(consequent) : this.expression(alternative);
416
+ const testExpression = this._expression(test);
417
+ if (isKnownAtComptime(testExpression)) return testExpression.value ? this._expression(consequent) : this._expression(alternative);
374
418
  else throw new Error(`Ternary operator is only supported for comptime-known checks (used with '${testExpression.value}'). For runtime checks, please use 'std.select' or if/else statements.`);
375
419
  }
376
420
  if (expression[0] === NODE.stringLiteral) return snip(expression[1], UnknownData, "constant");
@@ -378,11 +422,23 @@ ${this.ctx.pre}}`;
378
422
  assertExhaustive(expression);
379
423
  }
380
424
  functionDefinition(body) {
381
- return this.block(body);
425
+ return this._block(body);
426
+ }
427
+ /**
428
+ * Generates a WGSL type string for the given data type, and adds necessary
429
+ * definitions to the shader preamble. This shouldn't be called directly, only
430
+ * through `ctx.resolve` to properly cache the result.
431
+ */
432
+ typeAnnotation(data) {
433
+ return resolveData(this.ctx, data);
434
+ }
435
+ typeInstantiation(schema, args) {
436
+ if (args.length === 1 && args[0]?.dataType === schema) return snip(stitch`${args[0]}`, schema, fallthroughCopyOrigin(args[0].origin));
437
+ return snip(stitch`${this.ctx.resolve(schema).value}(${args})`, schema, "runtime");
382
438
  }
383
- statement(statement) {
439
+ _statement(statement) {
384
440
  if (typeof statement === "string") {
385
- const id = this.identifier(statement);
441
+ const id = this._identifier(statement);
386
442
  const resolved = id.value && this.ctx.resolve(id.value).value;
387
443
  return resolved ? `${this.ctx.pre}${resolved};` : "";
388
444
  }
@@ -391,7 +447,7 @@ ${this.ctx.pre}}`;
391
447
  const returnNode = statement[1];
392
448
  if (returnNode !== void 0) {
393
449
  const expectedReturnType = this.ctx.topFunctionReturnType;
394
- let returnSnippet = expectedReturnType ? this.typedExpression(returnNode, expectedReturnType) : this.expression(returnNode);
450
+ let returnSnippet = expectedReturnType ? this._typedExpression(returnNode, expectedReturnType) : this._expression(returnNode);
395
451
  if (returnSnippet.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot return references, returning '${returnSnippet.value.snippet}'`);
396
452
  if (returnSnippet.origin === "argument" && !isNaturallyEphemeral(returnSnippet.dataType) && this.ctx.topFunctionScope?.functionType === "normal") throw new WgslTypeError(stitch`Cannot return references to arguments, returning '${returnSnippet}'. Copy the argument before returning it.`);
397
453
  if (!expectedReturnType && !isEphemeralSnippet(returnSnippet) && returnSnippet.origin !== "this-function") {
@@ -411,11 +467,17 @@ Try 'return ${typeStr}(${str});' instead.
411
467
  }
412
468
  if (statement[0] === NODE.if) {
413
469
  const [_, condNode, consNode, altNode] = statement;
414
- const condition = this.typedExpression(condNode, bool);
415
- const consequent = condition.value === false ? void 0 : this.block(blockifySingleStatement(consNode));
416
- const alternate = condition.value === true || !altNode ? void 0 : this.block(blockifySingleStatement(altNode));
417
- if (condition.value === true) return `${this.ctx.pre}${consequent}`;
418
- if (condition.value === false) return alternate ? `${this.ctx.pre}${alternate}` : "";
470
+ const condition = this._typedExpression(condNode, bool);
471
+ if (typeof condition.value === "boolean") {
472
+ let node = condition.value ? consNode : altNode;
473
+ if (node === void 0) return "";
474
+ if (!Array.isArray(node)) node = blockifySingleStatement(node);
475
+ if (node[0] === NODE.block && node[1].length === 1 && node[1][0][0] === NODE.if) return this._statement(node[1][0]);
476
+ if (node[0] === NODE.if) return this._statement(node);
477
+ return `${this.ctx.pre}${this._block(blockifySingleStatement(node))}`;
478
+ }
479
+ const consequent = this._block(blockifySingleStatement(consNode));
480
+ const alternate = !altNode ? void 0 : this._block(blockifySingleStatement(altNode));
419
481
  if (!alternate) return stitch`${this.ctx.pre}if (${condition}) ${consequent}`;
420
482
  return stitch`\
421
483
  ${this.ctx.pre}if (${condition}) ${consequent}
@@ -424,7 +486,7 @@ ${this.ctx.pre}else ${alternate}`;
424
486
  if (statement[0] === NODE.let || statement[0] === NODE.const) {
425
487
  let varType = "var";
426
488
  const [stmtType, rawId, rawValue] = statement;
427
- const eq = rawValue !== void 0 ? this.expression(rawValue) : void 0;
489
+ const eq = rawValue !== void 0 ? this._expression(rawValue) : void 0;
428
490
  if (!eq) throw new Error(`Cannot create variable '${rawId}' without an initial value.`);
429
491
  const ephemeral = isEphemeralSnippet(eq);
430
492
  let dataType = eq.dataType;
@@ -472,9 +534,11 @@ ${this.ctx.pre}else ${alternate}`;
472
534
  }
473
535
  }
474
536
  const snippet = this.blockVariable(varType, rawId, concretize(dataType), eq.origin);
475
- return stitch`${this.ctx.pre}${varType} ${snippet.value} = ${tryConvertSnippet(this.ctx, eq, dataType, false)};`;
537
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, dataType, false);
538
+ const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value;
539
+ return this.emitVarDecl(this.ctx.pre, varType, snippet.value, concretize(dataType), rhsStr);
476
540
  }
477
- if (statement[0] === NODE.block) return `${this.ctx.pre}${this.block(statement)}`;
541
+ if (statement[0] === NODE.block) return `${this.ctx.pre}${this._block(statement)}`;
478
542
  if (statement[0] === NODE.for) {
479
543
  const [_, init, condition, update, body] = statement;
480
544
  const prevUnrollingFlag = this.#unrolling;
@@ -482,13 +546,13 @@ ${this.ctx.pre}else ${alternate}`;
482
546
  try {
483
547
  this.ctx.pushBlockScope();
484
548
  const [initStatement, conditionExpr, updateStatement] = this.ctx.withResetIndentLevel(() => [
485
- init ? this.statement(init) : void 0,
486
- condition ? this.typedExpression(condition, bool) : void 0,
487
- update ? this.statement(update) : void 0
549
+ init ? this._statement(init) : void 0,
550
+ condition ? this._typedExpression(condition, bool) : void 0,
551
+ update ? this._statement(update) : void 0
488
552
  ]);
489
553
  const initStr = initStatement ? initStatement.slice(0, -1) : "";
490
554
  const updateStr = updateStatement ? updateStatement.slice(0, -1) : "";
491
- const bodyStr = this.block(blockifySingleStatement(body));
555
+ const bodyStr = this._block(blockifySingleStatement(body));
492
556
  return stitch`${this.ctx.pre}for (${initStr}; ${conditionExpr}; ${updateStr}) ${bodyStr}`;
493
557
  } finally {
494
558
  this.#unrolling = prevUnrollingFlag;
@@ -500,9 +564,9 @@ ${this.ctx.pre}else ${alternate}`;
500
564
  this.#unrolling = false;
501
565
  try {
502
566
  const [_, condition, body] = statement;
503
- const condSnippet = this.typedExpression(condition, bool);
567
+ const condSnippet = this._typedExpression(condition, bool);
504
568
  const conditionStr = this.ctx.resolve(condSnippet.value).value;
505
- const bodyStr = this.block(blockifySingleStatement(body));
569
+ const bodyStr = this._block(blockifySingleStatement(body));
506
570
  return `${this.ctx.pre}while (${conditionStr}) ${bodyStr}`;
507
571
  } finally {
508
572
  this.#unrolling = prevUnrollingFlag;
@@ -515,40 +579,40 @@ ${this.ctx.pre}else ${alternate}`;
515
579
  const prevUnrollingFlag = this.#unrolling;
516
580
  try {
517
581
  this.ctx.pushBlockScope();
518
- const iterableExpr = this.expression(iterable);
582
+ const iterableExpr = this._expression(iterable);
519
583
  const shouldUnroll = iterableExpr.value instanceof UnrollableIterable;
520
584
  const iterableSnippet = shouldUnroll ? iterableExpr.value.snippet : iterableExpr;
521
- const elementCountSnippet = getElementCountSnippet(this.ctx, iterableSnippet, shouldUnroll);
585
+ const range = getRangeSnippets(this.ctx, iterableSnippet, shouldUnroll);
522
586
  const originalLoopVarName = loopVar[1];
523
587
  const blockified = blockifySingleStatement(body);
524
588
  if (shouldUnroll) {
525
- if (!isKnownAtComptime(elementCountSnippet)) throw new Error("Cannot unroll loop. Length of iterable is unknown at comptime.");
589
+ if (!isKnownAtComptime(range.end)) throw new Error("Cannot unroll loop. Length of iterable is unknown at comptime.");
526
590
  this.#unrolling = true;
527
- const length = elementCountSnippet.value;
591
+ const length = range.end.value;
528
592
  if (length === 0) return "";
529
593
  const { value } = iterableSnippet;
530
- const elements = value instanceof ArrayExpression ? value.elements : Array.from({ length }, (_, i) => getElementSnippet(iterableSnippet, snip(i, u32, "constant")));
594
+ const elements = isTgpuRange(value) ? value.map((i) => coerceToSnippet(i)) : value instanceof ArrayExpression ? value.elements : Array.from({ length }, (_, i) => getElementSnippet(iterableSnippet, snip(i, u32, "constant")));
531
595
  if (isEphemeralSnippet(elements[0]) && !isNaturallyEphemeral(elements[0]?.dataType)) throw new WgslTypeError("Cannot unroll loop. The elements of iterable are emphemeral but not naturally ephemeral.");
532
- return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this.block(blockified, { [originalLoopVarName]: e })}`).join("\n");
596
+ return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this.ctx.pre}${this._block(blockified, { [originalLoopVarName]: e })}`).join("\n");
533
597
  }
534
- if (isEphemeralSnippet(iterableSnippet)) throw new Error(`\`for ... of ...\` loops only support iterables stored in variables.
535
- -----
536
- You can wrap iterable with \`tgpu.unroll(...)\`. If iterable is known at comptime, the loop will be unrolled.
537
- -----`);
538
598
  this.#unrolling = false;
539
599
  const index = this.ctx.makeNameValid("i");
540
- const elementSnippet = getElementSnippet(iterableSnippet, snip(index, u32, "runtime"));
541
- const loopVarName = this.ctx.makeNameValid(originalLoopVarName);
542
- const loopVarKind = getLoopVarKind(elementSnippet);
543
- const elementType = getElementType(elementSnippet, iterableSnippet);
544
- const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = 0u; ${index} < ${elementCountSnippet}; ${index}++) {`;
545
- this.ctx.indent();
546
- ctxIndent = true;
547
- const loopVarDeclStr = stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`;
548
- const bodyStr = `${this.ctx.pre}${this.block(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin) })}`;
549
- this.ctx.dedent();
550
- ctxIndent = false;
551
- return stitch`${forHeaderStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`;
600
+ const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`;
601
+ let bodyStr = "";
602
+ if (isTgpuRange(iterableSnippet.value)) bodyStr = this._block(blockified, { [originalLoopVarName]: snip(index, u32, "runtime") });
603
+ else {
604
+ this.ctx.indent();
605
+ ctxIndent = true;
606
+ const loopVarName = this.ctx.makeNameValid(originalLoopVarName);
607
+ const elementSnippet = getElementSnippet(iterableSnippet, snip(index, u32, "runtime"));
608
+ const loopVarKind = getLoopVarKind(elementSnippet);
609
+ const elementType = getElementType(elementSnippet, iterableSnippet);
610
+ bodyStr = `{\n${stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`}\n${this.ctx.pre}${this._block(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin) })}\n`;
611
+ this.ctx.dedent();
612
+ bodyStr += `${this.ctx.pre}}`;
613
+ ctxIndent = false;
614
+ }
615
+ return stitch`${forHeaderStr} ${bodyStr.trim()}`;
552
616
  } finally {
553
617
  if (ctxIndent) this.ctx.dedent();
554
618
  this.#unrolling = prevUnrollingFlag;
@@ -563,7 +627,7 @@ ${this.ctx.pre}else ${alternate}`;
563
627
  if (this.#unrolling) throw new WgslTypeError("Cannot unroll loop containing `break`");
564
628
  return `${this.ctx.pre}break;`;
565
629
  }
566
- const expr = this.expression(statement);
630
+ const expr = this._expression(statement);
567
631
  const resolved = expr.value && this.ctx.resolve(expr.value).value;
568
632
  return resolved ? `${this.ctx.pre}${resolved};` : "";
569
633
  }
@@ -580,6 +644,5 @@ function blockifySingleStatement(statement) {
580
644
  return typeof statement !== "object" || statement[0] !== NODE.block ? [NODE.block, [statement]] : statement;
581
645
  }
582
646
  const wgslGenerator = new WgslGenerator();
583
-
584
647
  //#endregion
585
- export { wgslGenerator as default };
648
+ export { WgslGenerator, wgslGenerator as default };