typegpu 0.11.5 → 0.11.7

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 (68) hide show
  1. package/bin.mjs +57 -0
  2. package/builtin.d.ts +5 -1
  3. package/builtin.js +2 -0
  4. package/core/buffer/buffer.js +1 -1
  5. package/core/buffer/bufferUsage.js +18 -14
  6. package/core/constant/tgpuConstant.js +6 -6
  7. package/core/function/comptime.js +8 -3
  8. package/core/function/createCallableSchema.js +7 -5
  9. package/core/function/dualImpl.js +2 -2
  10. package/core/function/fnCore.js +7 -8
  11. package/core/function/tgpuComputeFn.js +1 -1
  12. package/core/function/tgpuFn.js +3 -2
  13. package/core/pipeline/computePipeline.js +3 -3
  14. package/core/pipeline/renderPipeline.js +3 -3
  15. package/core/rawCodeSnippet/tgpuRawCodeSnippet.d.ts +1 -1
  16. package/core/resolve/externals.js +1 -1
  17. package/core/resolve/tgpuResolve.d.ts +2 -2
  18. package/core/root/init.js +1 -1
  19. package/core/slot/accessor.js +1 -1
  20. package/core/texture/texture.js +8 -2
  21. package/core/variable/tgpuVariable.js +8 -6
  22. package/core/vertexLayout/vertexLayout.js +1 -1
  23. package/data/alignmentOf.js +1 -1
  24. package/data/attributes.d.ts +1 -1
  25. package/data/dataIO.js +1 -1
  26. package/data/dataTypes.d.ts +0 -1
  27. package/data/dataTypes.js +2 -12
  28. package/data/index.js +6 -23
  29. package/data/ref.js +20 -7
  30. package/data/snippet.d.ts +2 -1
  31. package/data/snippet.js +37 -10
  32. package/errors.d.ts +1 -4
  33. package/errors.js +1 -7
  34. package/index.d.ts +3 -3
  35. package/index.js +2 -2
  36. package/indexNamedExports.d.ts +2 -2
  37. package/package.js +1 -1
  38. package/package.json +5 -4
  39. package/resolutionCtx.js +10 -17
  40. package/shared/meta.d.ts +6 -15
  41. package/shared/meta.js +45 -38
  42. package/shared/normalizeMetadata.d.ts +32 -0
  43. package/shared/normalizeMetadata.js +40 -0
  44. package/std/atomic.js +1 -1
  45. package/std/boolean.js +36 -5
  46. package/std/environment.d.ts +48 -0
  47. package/std/environment.js +57 -0
  48. package/std/extensions.d.ts +2 -2
  49. package/std/extensions.js +2 -2
  50. package/std/index.d.ts +3 -2
  51. package/std/index.js +5 -2
  52. package/tgpuBindGroupLayout.js +1 -1
  53. package/tgsl/accessIndex.js +8 -14
  54. package/tgsl/accessProp.js +16 -29
  55. package/tgsl/consoleLog/serializers.js +1 -1
  56. package/tgsl/conversion.js +3 -3
  57. package/tgsl/forOfUtils.js +8 -5
  58. package/tgsl/generationHelpers.js +10 -11
  59. package/tgsl/infixDispatch.js +53 -0
  60. package/tgsl/shaderGenerator.d.ts +4 -1
  61. package/tgsl/shaderGenerator_members.d.ts +20 -2
  62. package/tgsl/shaderGenerator_members.js +5 -1
  63. package/tgsl/shellless.js +4 -4
  64. package/tgsl/wgslGenerator.d.ts +15 -11
  65. package/tgsl/wgslGenerator.js +174 -97
  66. package/types.d.ts +6 -4
  67. package/wgslExtensions.d.ts +3 -3
  68. package/wgslExtensions.js +3 -3
@@ -1,15 +1,33 @@
1
+ import "../shared/meta.js";
1
2
  import { Origin, Snippet } from "../data/snippet.js";
2
- import { FunctionArgument, ResolutionCtx, TgpuShaderStage } from "../types.js";
3
+ import { VariableScope } from "../core/variable/tgpuVariable.js";
4
+ import { BindableBufferUsage, FunctionArgument, ResolutionCtx, TgpuShaderStage } from "../types.js";
3
5
  import { BaseData } from "../data/wgslTypes.js";
4
6
  import { UnknownData } from "../data/dataTypes.js";
5
7
  import { Block } from "tinyest";
6
8
 
7
9
  //#region src/tgsl/shaderGenerator_members.d.ts
10
+
8
11
  interface FunctionDefinitionOptions {
9
12
  readonly functionType: 'normal' | TgpuShaderStage;
13
+ readonly name: string;
14
+ readonly workgroupSize?: readonly number[] | undefined;
10
15
  readonly args: readonly FunctionArgument[];
11
16
  readonly body: Block;
12
17
  determineReturnType(): BaseData;
13
18
  }
19
+ interface ConstantDefinitionOptions {
20
+ readonly id: string;
21
+ readonly dataType: BaseData;
22
+ readonly init: Snippet;
23
+ }
24
+ interface VariableDefinitionOptions {
25
+ readonly scope: VariableScope | BindableBufferUsage | 'handle';
26
+ readonly id: string;
27
+ readonly dataType: BaseData;
28
+ readonly init: Snippet | undefined;
29
+ readonly group?: string | undefined;
30
+ readonly binding?: number | undefined;
31
+ }
14
32
  //#endregion
15
- export { FunctionDefinitionOptions };
33
+ export { ConstantDefinitionOptions, FunctionDefinitionOptions, VariableDefinitionOptions };
@@ -1,8 +1,12 @@
1
1
  import { __export } from "../_virtual/rolldown_runtime.js";
2
+ import { getName } from "../shared/meta.js";
2
3
  import { UnknownData } from "../data/dataTypes.js";
3
4
 
4
5
  //#region src/tgsl/shaderGenerator_members.ts
5
- var shaderGenerator_members_exports = /* @__PURE__ */ __export({ UnknownData: () => UnknownData });
6
+ var shaderGenerator_members_exports = /* @__PURE__ */ __export({
7
+ UnknownData: () => UnknownData,
8
+ getName: () => getName
9
+ });
6
10
 
7
11
  //#endregion
8
12
  export { shaderGenerator_members_exports };
package/tgsl/shellless.js CHANGED
@@ -1,5 +1,5 @@
1
- import { getMetaData, getName } from "../shared/meta.js";
2
1
  import { isPtr, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
2
+ import { getFunctionMetadata, getName } from "../shared/meta.js";
3
3
  import { UnknownData } from "../data/dataTypes.js";
4
4
  import { WgslTypeError } from "../errors.js";
5
5
  import { RefOperator } from "../data/ref.js";
@@ -17,15 +17,15 @@ function shallowEqualSchemas(a, b) {
17
17
  var ShelllessRepository = class {
18
18
  cache = /* @__PURE__ */ new Map();
19
19
  get(fn, argSnippets) {
20
- const meta = getMetaData(fn);
21
- if (!meta?.ast) return void 0;
20
+ const meta = getFunctionMetadata(fn);
21
+ if (!meta) return;
22
22
  if (!argSnippets && meta.ast.params.length > 0) throw new Error(`Cannot resolve '${getName(fn)}' directly, because it expects arguments. Either call it from another function, or wrap it in a shell`);
23
23
  const argTypes = (argSnippets ?? []).map((s, index) => {
24
24
  if (s.value instanceof RefOperator) {
25
25
  if (s.dataType === UnknownData) throw new WgslTypeError(`d.ref() created with primitive types must be stored in a variable before use`);
26
26
  return s.dataType;
27
27
  }
28
- 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.`);
28
+ if (s.dataType === UnknownData) throw new Error(`Passed illegal value ${s.value} as the #${index} argument to ${getName(fn) ?? "<unnamed>"}(...)\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.`);
29
29
  let type = concretize(s.dataType);
30
30
  if (isPtr(type) && type.implicit) type = type.inner;
31
31
  return type;
@@ -1,6 +1,6 @@
1
- import { Origin, ResolvedSnippet, Snippet } from "../data/snippet.js";
1
+ import { ResolvedSnippet, Snippet } from "../data/snippet.js";
2
2
  import { GenerationCtx } from "./generationHelpers.js";
3
- import { FunctionDefinitionOptions } from "./shaderGenerator_members.js";
3
+ import { ConstantDefinitionOptions, FunctionDefinitionOptions, VariableDefinitionOptions } from "./shaderGenerator_members.js";
4
4
  import { ShaderGenerator } from "./shaderGenerator.js";
5
5
  import { BaseData, StorableData } from "../data/wgslTypes.js";
6
6
  import { UnknownData } from "../data/dataTypes.js";
@@ -12,22 +12,23 @@ declare class WgslGenerator implements ShaderGenerator {
12
12
  #private;
13
13
  initGenerator(ctx: GenerationCtx): void;
14
14
  protected get ctx(): GenerationCtx;
15
- _block([_, statements]: tinyest.Block, externalMap?: ExternalMap): string;
16
- _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string;
15
+ protected _block([_, statements]: tinyest.Block, externalMap?: ExternalMap): string;
16
+ protected _blockStatement(block: tinyest.Block, externalMap?: ExternalMap): string;
17
17
  refVariable(id: string, dataType: StorableData): string;
18
- blockVariable(varType: 'var' | 'let' | 'const' | '<deferred>', id: string, dataType: BaseData | UnknownData, origin: Origin): Snippet;
19
18
  /**
20
19
  * Creates a variable declaration string.
21
20
  * `keyword` may be a placeholder filled in later.
22
21
  */
23
- protected emitVarDecl(pre: string, keyword: 'var' | 'let' | 'const' | `#VAR_${number}#`, name: string, _dataType: BaseData | UnknownData, rhsStr: string): string;
24
- _identifier(id: string): Snippet;
22
+ protected _emitVarDecl(keyword: 'var' | 'let' | 'const' | `#VAR_${number}#`, name: string, _dataType: BaseData | UnknownData, rhsStr: string): string;
23
+ protected _identifier(id: string): Snippet;
25
24
  /**
26
25
  * A wrapper for `generateExpression` that updates `ctx.expectedType`
27
26
  * and tries to convert the result when it does not match the expected type.
28
27
  */
29
- _typedExpression(expression: tinyest.Expression, expectedType: BaseData | BaseData[]): Snippet;
30
- _expression(expression: tinyest.Expression): Snippet;
28
+ protected _typedExpression(expression: tinyest.Expression, expectedType: BaseData | BaseData[]): Snippet;
29
+ protected _expression(expression: tinyest.Expression): Snippet;
30
+ declareGlobalConst(options: ConstantDefinitionOptions): ResolvedSnippet;
31
+ declareGlobalVar(options: VariableDefinitionOptions): ResolvedSnippet;
31
32
  functionDefinition(options: FunctionDefinitionOptions): string;
32
33
  /**
33
34
  * Generates a WGSL type string for the given data type, and adds necessary
@@ -36,8 +37,11 @@ declare class WgslGenerator implements ShaderGenerator {
36
37
  */
37
38
  typeAnnotation(data: BaseData): string;
38
39
  typeInstantiation(schema: BaseData, args: readonly Snippet[]): ResolvedSnippet;
39
- _return(statement: tinyest.Return): string;
40
- _statement(statement: tinyest.Statement): string;
40
+ numericLiteral(value: number, schema: BaseData): ResolvedSnippet;
41
+ protected _return(statement: tinyest.Return): string;
42
+ protected _letStatement(statement: tinyest.Let): string;
43
+ protected _constStatement(statement: tinyest.Const): string;
44
+ protected _statement(statement: tinyest.Statement): string;
41
45
  /**
42
46
  * Attempts a member access lookup to mark a variable as modified.
43
47
  * @example
@@ -1,14 +1,14 @@
1
1
  import { $gpuCallable, $internal, $providing, isMarkedInternal } from "../shared/symbols.js";
2
- import { getName } from "../shared/meta.js";
3
2
  import { Void, isBool, isNaturallyEphemeral, isNumericSchema, isPtr, isVec, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
4
- import { InfixDispatch, UnknownData, unptr } from "../data/dataTypes.js";
5
- import { fallthroughCopyOrigin, isEphemeralOrigin, isEphemeralSnippet, snip } from "../data/snippet.js";
3
+ import { safeStringify } from "../shared/stringify.js";
4
+ import { getName } from "../shared/meta.js";
5
+ import { UnknownData, unptr } from "../data/dataTypes.js";
6
+ import { fallthroughCopyOrigin, isAlias, snip } from "../data/snippet.js";
6
7
  import { ResolutionError, WgslTypeError, invariant } from "../errors.js";
7
8
  import { isGPUCallable, isKnownAtComptime } from "../types.js";
8
9
  import { stitch } from "../core/resolve/stitch.js";
9
10
  import { createPtrFromOrigin, implicitFrom, ptrFn } from "../data/ptr.js";
10
11
  import { RefOperator, _ref } from "../data/ref.js";
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
14
  import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet } from "./generationHelpers.js";
@@ -16,6 +16,7 @@ import { vec2u, vec3u, vec4u } from "../data/vector.js";
16
16
  import { getAttributesString } from "../data/attributes.js";
17
17
  import { AutoStruct } from "../data/autoStruct.js";
18
18
  import { add, div, mul, neg, sub } from "../std/operators.js";
19
+ import { isInfixDispatch } from "./infixDispatch.js";
19
20
  import { accessProp } from "./accessProp.js";
20
21
  import { accessIndex } from "./accessIndex.js";
21
22
  import { constant } from "../core/constant/tgpuConstant.js";
@@ -28,6 +29,7 @@ import { mathToStd, supportedLogOps } from "./jsPolyfills.js";
28
29
  import { isTgpuRange } from "../std/range.js";
29
30
  import { getElementSnippet, getElementType, getLoopVarKind, getRangeSnippets } from "./forOfUtils.js";
30
31
  import { stringifyNode } from "../shared/tseynit.js";
32
+ import { validSelectBranchTypes } from "../std/boolean.js";
31
33
  import * as tinyest from "tinyest";
32
34
 
33
35
  //#region src/tgsl/wgslGenerator.ts
@@ -138,6 +140,13 @@ const binaryOpCodeToCodegen = {
138
140
  "/": div[$gpuCallable].call.bind(div),
139
141
  "**": pow[$gpuCallable].call.bind(pow)
140
142
  };
143
+ const usageToVarTemplateMap = {
144
+ private: "private",
145
+ workgroup: "workgroup",
146
+ uniform: "uniform",
147
+ mutable: "storage, read_write",
148
+ readonly: "storage, read"
149
+ };
141
150
  var WgslGenerator = class {
142
151
  #ctx = void 0;
143
152
  #unrolling = false;
@@ -175,25 +184,12 @@ ${this.ctx.pre}}`;
175
184
  this.ctx.defineVariable(id, snippet);
176
185
  return varName;
177
186
  }
178
- blockVariable(varType, id, dataType, origin) {
179
- const naturallyEphemeral = isNaturallyEphemeral(dataType);
180
- let varOrigin;
181
- if (origin === "constant-tgpu-const-ref" || origin === "runtime-tgpu-const-ref") varOrigin = origin;
182
- else if (origin === "argument") if (naturallyEphemeral) varOrigin = "runtime";
183
- else varOrigin = "argument";
184
- else if (!naturallyEphemeral) varOrigin = isEphemeralOrigin(origin) ? "this-function" : origin;
185
- else if (origin === "constant" && varType === "const") varOrigin = "constant";
186
- else varOrigin = "runtime";
187
- const snippet = snip(this.ctx.makeUniqueIdentifier(id, "block"), dataType, varOrigin);
188
- this.ctx.defineVariable(id, snippet);
189
- return snippet;
190
- }
191
187
  /**
192
188
  * Creates a variable declaration string.
193
189
  * `keyword` may be a placeholder filled in later.
194
190
  */
195
- emitVarDecl(pre, keyword, name, _dataType, rhsStr) {
196
- return `${pre}${keyword} ${name} = ${rhsStr};`;
191
+ _emitVarDecl(keyword, name, _dataType, rhsStr) {
192
+ return `${this.ctx.pre}${keyword} ${name} = ${rhsStr};`;
197
193
  }
198
194
  _identifier(id) {
199
195
  if (!id) throw new Error("Cannot resolve an empty identifier");
@@ -261,8 +257,7 @@ ${this.ctx.pre}}`;
261
257
  if (exprType === NODE.assignmentExpr) {
262
258
  validateSnippetMutation(convLhs, expression);
263
259
  this.tryMarkModified(lhs);
264
- if (op === "=" && rhsExpr.origin === "argument" && !isNaturallyEphemeral(rhsExpr.dataType)) throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because argument references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(rhsExpr.dataType).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
265
- if (op === "=" && !isEphemeralSnippet(rhsExpr)) throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(rhsExpr.dataType).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
260
+ if (op === "=" && isAlias(rhsExpr) && !isNaturallyEphemeral(rhsExpr.dataType)) throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(rhsExpr.dataType).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
266
261
  }
267
262
  return snip(parenthesizedOps.includes(op) ? `(${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr})` : `${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr}`, type, "runtime");
268
263
  }
@@ -314,10 +309,11 @@ ${this.ctx.pre}}`;
314
309
  return snip(this.ctx.resolve(arg.value, callee.value).value, callee.value, "runtime");
315
310
  }
316
311
  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.");
317
- if (callee.value instanceof InfixDispatch) {
318
- if (!argNodes[0]) throw new WgslTypeError(`An infix operator '${callee.value.name}' was called without any arguments`);
312
+ if (isInfixDispatch(callee.value)) {
313
+ if (!argNodes[0]) throw new WgslTypeError(`An infix operator '${getName(callee.value.operator)}' was called without any arguments`);
314
+ const lhs = coerceToSnippet(callee.value.lhs);
319
315
  const rhs = this._expression(argNodes[0]);
320
- return callee.value.operator(this.ctx, [callee.value.lhs, rhs]);
316
+ return callee.value.operator[$gpuCallable].call(this.ctx, [lhs, rhs]);
321
317
  }
322
318
  if ((callee.value === _ref || callee.value === unroll) && argNodes[0]) this.tryMarkModified(argNodes[0]);
323
319
  if (isGPUCallable(callee.value)) {
@@ -413,20 +409,42 @@ ${this.ctx.pre}}`;
413
409
  return snip(new ArrayExpression(arrayType, values), arrayType, "runtime");
414
410
  }
415
411
  if (expression[0] === NODE.conditionalExpr) {
416
- const [_, test, consequent, alternative] = expression;
417
- const testExpression = this._expression(test);
418
- if (isKnownAtComptime(testExpression)) return testExpression.value ? this._expression(consequent) : this._expression(alternative);
419
- else throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid, because only comptime-known checks are supported. For runtime checks, please use 'std.select' or if/else statements.`);
412
+ const [_, testNode, consequentNode, alternativeNode] = expression;
413
+ const test = this._expression(testNode);
414
+ if (isKnownAtComptime(test)) return test.value ? this._expression(consequentNode) : this._expression(alternativeNode);
415
+ else {
416
+ const consequent = this._expression(consequentNode);
417
+ const alternative = this._expression(alternativeNode);
418
+ const [con, alt] = convertToCommonType(this.ctx, [consequent, alternative], validSelectBranchTypes) ?? [];
419
+ if (!con || !alt || consequent.possibleSideEffects || alternative.possibleSideEffects) throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid. For more complex branching, please use 'std.select' or if/else statements.`);
420
+ return snip(stitch`select(${alt}, ${con}, ${test})`, con.dataType, "runtime", test.possibleSideEffects);
421
+ }
420
422
  }
421
423
  if (expression[0] === NODE.stringLiteral) return snip(expression[1], UnknownData, "constant");
422
424
  if (expression[0] === NODE.preUpdate) throw new Error("Cannot use pre-updates in TypeGPU functions.");
423
425
  assertExhaustive(expression);
424
426
  }
427
+ declareGlobalConst(options) {
428
+ const resolvedDataType = this.ctx.resolve(options.dataType).value;
429
+ const resolvedValue = this.ctx.resolveSnippet(options.init).value;
430
+ this.ctx.addDeclaration(`const ${options.id}: ${resolvedDataType} = ${resolvedValue};`);
431
+ return snip(options.id, options.dataType, "constant-immutable-def");
432
+ }
433
+ declareGlobalVar(options) {
434
+ let pre = "";
435
+ if (options.group !== void 0) pre += `@group(${options.group}) `;
436
+ if (options.binding !== void 0) pre += `@binding(${options.binding}) `;
437
+ if (options.scope in usageToVarTemplateMap) pre += `var<${usageToVarTemplateMap[options.scope]}> `;
438
+ else pre += `var `;
439
+ pre += `${options.id}: ${this.ctx.resolve(options.dataType).value}`;
440
+ this.ctx.addDeclaration(options.init ? `${pre} = ${this.ctx.resolveSnippet(options.init).value};` : `${pre};`);
441
+ return snip(options.id, options.dataType, options.scope);
442
+ }
425
443
  functionDefinition(options) {
426
444
  let body = this._block(options.body);
427
445
  const scope = this.ctx.topFunctionScope;
428
446
  invariant(scope, "Expected function scope to be present");
429
- const replacements = Object.fromEntries(scope.placeholderForVariable.entries().map(([variable, placeholder]) => [placeholder, scope.modifiedVariables.has(variable) ? "var" : "let"]));
447
+ const replacements = Object.fromEntries([...scope.placeholderForVariable.entries()].map(([variable, placeholder]) => [placeholder, scope.modifiedVariables.has(variable) ? "var" : "let"]));
430
448
  if (Object.keys(replacements).length > 0) {
431
449
  const regex = new RegExp(Object.keys(replacements).join("|"), "gi");
432
450
  body = body.replace(regex, (match) => replacements[match] ?? "#ERR");
@@ -435,7 +453,14 @@ ${this.ctx.pre}}`;
435
453
  const argList = options.args.filter((arg) => arg.used || options.functionType === "normal").map((arg) => {
436
454
  return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`;
437
455
  }).join(", ");
438
- return `${returnType.type !== "void" ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} ` : `(${argList}) `}${body}`;
456
+ const head = returnType.type !== "void" ? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} ` : `(${argList}) `;
457
+ let attributes = "";
458
+ if (options.functionType === "compute") {
459
+ if (!options.workgroupSize) throw new Error("Compute shaders must have a workgroup size");
460
+ attributes = `@compute @workgroup_size(${options.workgroupSize.join(", ")}) `;
461
+ } else if (options.functionType === "vertex") attributes = `@vertex `;
462
+ else if (options.functionType === "fragment") attributes = `@fragment `;
463
+ return `${attributes}fn ${options.name}${head}${body}`;
439
464
  }
440
465
  /**
441
466
  * Generates a WGSL type string for the given data type, and adds necessary
@@ -449,6 +474,17 @@ ${this.ctx.pre}}`;
449
474
  if (args.length === 1 && args[0]?.dataType === schema) return snip(stitch`${args[0]}`, schema, fallthroughCopyOrigin(args[0].origin));
450
475
  return snip(stitch`${this.ctx.resolve(schema).value}(${args})`, schema, "runtime");
451
476
  }
477
+ numericLiteral(value, schema) {
478
+ if (schema.type === "abstractInt") return snip(`${value}`, schema, "constant");
479
+ if (schema.type === "u32") return snip(`${value}u`, schema, "constant");
480
+ if (schema.type === "i32") return snip(`${value}i`, schema, "constant");
481
+ const exp = value.toExponential();
482
+ const decimal = schema.type === "abstractFloat" && Number.isInteger(value) ? `${value}.` : `${value}`;
483
+ const base = exp.length < decimal.length ? exp : decimal;
484
+ if (schema.type === "f32") return snip(`${base}f`, schema, "constant");
485
+ if (schema.type === "f16") return snip(`${base}h`, schema, "constant");
486
+ return snip(base, schema, "constant");
487
+ }
452
488
  _return(statement) {
453
489
  const returnNode = statement[1];
454
490
  if (returnNode !== void 0) {
@@ -456,7 +492,7 @@ ${this.ctx.pre}}`;
456
492
  let returnSnippet = expectedReturnType ? this._typedExpression(returnNode, expectedReturnType) : this._expression(returnNode);
457
493
  if (returnSnippet.value instanceof RefOperator) throw new WgslTypeError(`Cannot return '${stringifyNode(returnNode)}' because it is a d.ref`);
458
494
  if (returnSnippet.origin === "argument" && !isNaturallyEphemeral(returnSnippet.dataType) && this.ctx.topFunctionScope?.functionType === "normal") throw new WgslTypeError(`'${stringifyNode(statement)}' is invalid, cannot return references to arguments. Copy the argument before returning it.`);
459
- if (!expectedReturnType && !isEphemeralSnippet(returnSnippet) && returnSnippet.origin !== "this-function") {
495
+ if (!expectedReturnType && isAlias(returnSnippet) && !isNaturallyEphemeral(returnSnippet.dataType) && returnSnippet.origin !== "local-def") {
460
496
  const str = stringifyNode(returnNode);
461
497
  const typeStr = this.ctx.resolve(unptr(returnSnippet.dataType)).value;
462
498
  throw new WgslTypeError(`'return ${str};' is invalid, cannot return references.
@@ -471,6 +507,106 @@ Try 'return ${typeStr}(${str});' instead.
471
507
  }
472
508
  return `${this.ctx.pre}return;`;
473
509
  }
510
+ _letStatement(statement) {
511
+ const [_, rawId, eqNode] = statement;
512
+ if (eqNode === void 0) throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
513
+ const eq = this._expression(eqNode);
514
+ if (eq.value instanceof RefOperator) {
515
+ const rhsStr$1 = stringifyNode(eqNode);
516
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, cannot initialize 'let' variables with d.ref()
517
+ -----
518
+ - Try 'const ${rawId} = ${rhsStr$1}'.
519
+ -----`);
520
+ }
521
+ const definitionDataType = eq.dataType;
522
+ if (definitionDataType === UnknownData) {
523
+ const rhsStr$1 = stringifyNode(eqNode);
524
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, cannot determine WGSL type of '${rhsStr$1}'
525
+ -----
526
+ - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'let ${rawId} = Schema(${rhsStr$1})'
527
+ -----`);
528
+ }
529
+ if (isAlias(eq) && !isNaturallyEphemeral(eq.dataType)) {
530
+ const rhsStr$1 = stringifyNode(eqNode);
531
+ const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
532
+ throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, because references cannot be assigned to 'let' variable declarations.
533
+ -----
534
+ - Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr$1})' if you need to reassign '${rawId}' later
535
+ - Try 'const ${rawId} = ${rhsStr$1}' if you won't reassign '${rawId}' later.
536
+ -----`);
537
+ }
538
+ const concreteType = concretize(definitionDataType);
539
+ const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, "block"), concreteType, "local-def", false);
540
+ this.ctx.defineVariable(rawId, snippet);
541
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
542
+ const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value;
543
+ const scope = this.ctx.topFunctionScope;
544
+ invariant(scope, `Expected function scope to be present for ${rawId}`);
545
+ const emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
546
+ scope.placeholderForVariable.set(snippet, emittedVarType);
547
+ return this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr);
548
+ }
549
+ _constStatement(statement) {
550
+ const [_, rawId, eqNode] = statement;
551
+ if (eqNode === void 0) throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
552
+ const eq = this._expression(eqNode);
553
+ if (eq.value instanceof RefOperator) {
554
+ if (eq.dataType !== UnknownData) throw new WgslTypeError(`Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`);
555
+ const refSnippet = eq.value.snippet;
556
+ const varName = this.refVariable(rawId, concretize(refSnippet.dataType));
557
+ return stitch`${this.ctx.pre}var ${varName} = ${tryConvertSnippet(this.ctx, refSnippet, refSnippet.dataType, false)};`;
558
+ }
559
+ const rhsNaturallyEphemeral = isNaturallyEphemeral(eq.dataType);
560
+ let varOrigin = "local-def";
561
+ let varType = "<deferred>";
562
+ let definitionDataType = eq.dataType;
563
+ if (definitionDataType === UnknownData) {
564
+ const rhsStr$1 = stringifyNode(eqNode);
565
+ throw new WgslTypeError(`'const ${rawId} = ${rhsStr$1}' is invalid, cannot determine WGSL type of '${rhsStr$1}'
566
+ -----
567
+ - Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'const ${rawId} = Schema(${rhsStr$1})'
568
+ -----`);
569
+ }
570
+ if (eq.origin === "argument") {
571
+ varType = "let";
572
+ varOrigin = rhsNaturallyEphemeral ? "local-def" : "argument";
573
+ } else if (eq.origin === "constant-immutable-def") {
574
+ varType = "const";
575
+ varOrigin = "constant-immutable-def";
576
+ } else if (eq.origin === "runtime-immutable-def") {
577
+ varType = "let";
578
+ varOrigin = "runtime-immutable-def";
579
+ } else if (rhsNaturallyEphemeral) {
580
+ varType = eq.origin === "constant" ? "const" : "let";
581
+ varOrigin = "local-def";
582
+ } else if (!isAlias(eq)) {
583
+ varType = "<deferred>";
584
+ varOrigin = "local-def";
585
+ } else {
586
+ varType = "let";
587
+ varOrigin = eq.origin;
588
+ if (!isPtr(eq.dataType)) {
589
+ const ptrType = createPtrFromOrigin(eq.origin, concretize(eq.dataType));
590
+ invariant(ptrType !== void 0, `Creating pointer type from origin ${eq.origin}`);
591
+ definitionDataType = ptrType;
592
+ }
593
+ definitionDataType = implicitFrom(definitionDataType);
594
+ this.tryMarkModified(eqNode);
595
+ }
596
+ const concreteType = concretize(definitionDataType);
597
+ const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, "block"), concreteType, varOrigin, false);
598
+ this.ctx.defineVariable(rawId, snippet);
599
+ const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
600
+ const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value;
601
+ let emittedVarType;
602
+ if (varType === "<deferred>") {
603
+ const scope = this.ctx.topFunctionScope;
604
+ invariant(scope, `Expected function scope to be present for ${rawId}`);
605
+ emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
606
+ scope.placeholderForVariable.set(snippet, emittedVarType);
607
+ } else emittedVarType = varType;
608
+ return this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr);
609
+ }
474
610
  _statement(statement) {
475
611
  if (typeof statement === "string") {
476
612
  const id = this._identifier(statement);
@@ -497,70 +633,8 @@ Try 'return ${typeStr}(${str});' instead.
497
633
  ${this.ctx.pre}if (${condition}) ${consequent}
498
634
  ${this.ctx.pre}else ${alternate}`;
499
635
  }
500
- if (statement[0] === NODE.let || statement[0] === NODE.const) {
501
- let varType = "<deferred>";
502
- const [stmtType, rawId, rawValue] = statement;
503
- const eq = rawValue !== void 0 ? this._expression(rawValue) : void 0;
504
- if (!eq) throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
505
- const ephemeral = isEphemeralSnippet(eq);
506
- let dataType = eq.dataType;
507
- const naturallyEphemeral = isNaturallyEphemeral(dataType);
508
- if (eq.value instanceof RefOperator) {
509
- if (eq.dataType !== UnknownData) throw new WgslTypeError(`Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`);
510
- const refSnippet = eq.value.snippet;
511
- const varName = this.refVariable(rawId, concretize(refSnippet.dataType));
512
- return stitch`${this.ctx.pre}var ${varName} = ${tryConvertSnippet(this.ctx, refSnippet, refSnippet.dataType, false)};`;
513
- }
514
- if (!ephemeral) {
515
- if (stmtType === NODE.let) {
516
- const rhsStr$1 = stringifyNode(rawValue ?? "");
517
- const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
518
- throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, because references cannot be assigned to 'let' variable declarations.
519
- -----
520
- - Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr$1})' if you need to reassign '${rawId}' later
521
- - Try 'const ${rawId} = ${rhsStr$1}' if you won't reassign '${rawId}' later.
522
- -----`);
523
- }
524
- if (eq.origin === "constant-tgpu-const-ref") varType = "const";
525
- else if (eq.origin === "runtime-tgpu-const-ref") varType = "let";
526
- else {
527
- varType = "let";
528
- if (!isPtr(dataType)) {
529
- const ptrType = createPtrFromOrigin(eq.origin, concretize(dataType));
530
- invariant(ptrType !== void 0, `Creating pointer type from origin ${eq.origin}`);
531
- dataType = ptrType;
532
- }
533
- if (!(eq.value instanceof RefOperator)) {
534
- dataType = implicitFrom(dataType);
535
- this.tryMarkModified(rawValue);
536
- }
537
- }
538
- } else if (stmtType === NODE.const) {
539
- if (eq.origin === "argument") varType = "let";
540
- else if (naturallyEphemeral) varType = eq.origin === "constant" ? "const" : "let";
541
- } else if (eq.origin === "argument") {
542
- if (!naturallyEphemeral) {
543
- const rhsStr$1 = stringifyNode(rawValue ?? "");
544
- const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
545
- throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, because references to arguments cannot be assigned to 'let' variable declarations.
546
- -----
547
- - Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr$1})' if you need to reassign '${rawId}' later
548
- - Try 'const ${rawId} = ${rhsStr$1}' if you won't reassign '${rawId}' later.
549
- -----`);
550
- }
551
- }
552
- const snippet = this.blockVariable(varType, rawId, concretize(dataType), eq.origin);
553
- const rhsSnippet = tryConvertSnippet(this.ctx, eq, dataType, false);
554
- const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value;
555
- let emittedVarType;
556
- if (varType === "<deferred>") {
557
- const scope = this.ctx.topFunctionScope;
558
- invariant(scope, `Expected function scope to be present for ${rawId}`);
559
- emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
560
- scope.placeholderForVariable.set(snippet, emittedVarType);
561
- } else emittedVarType = varType;
562
- return this.emitVarDecl(this.ctx.pre, emittedVarType, snippet.value, concretize(dataType), rhsStr);
563
- }
636
+ if (statement[0] === NODE.let) return this._letStatement(statement);
637
+ if (statement[0] === NODE.const) return this._constStatement(statement);
564
638
  if (statement[0] === NODE.block) return this._blockStatement(statement);
565
639
  if (statement[0] === NODE.for) {
566
640
  const [_, init, condition, update, body] = statement;
@@ -616,7 +690,8 @@ ${this.ctx.pre}else ${alternate}`;
616
690
  if (length === 0) return "";
617
691
  const { value } = iterableSnippet;
618
692
  const elements = isTgpuRange(value) ? value.map((i) => coerceToSnippet(i)) : value instanceof ArrayExpression ? value.elements : Array.from({ length }, (_$1, i) => getElementSnippet(iterableSnippet, snip(i, u32, "constant")));
619
- if (isEphemeralSnippet(elements[0]) && !isNaturallyEphemeral(elements[0]?.dataType)) throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
693
+ const firstElement = elements[0];
694
+ if (!isAlias(firstElement) && !isNaturallyEphemeral(firstElement.dataType)) throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
620
695
  return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { [originalLoopVarName]: e })}`).join("\n");
621
696
  }
622
697
  this.#unrolling = false;
@@ -686,10 +761,12 @@ ${this.ctx.pre}else ${alternate}`;
686
761
  }
687
762
  };
688
763
  function validateSnippetMutation(mutated, expr) {
689
- if (mutated.origin === "constant" || mutated.origin === "constant-tgpu-const-ref" || mutated.origin === "runtime-tgpu-const-ref") {
764
+ if (mutated.origin === "constant" || mutated.origin === "constant-immutable-def" || mutated.origin === "runtime-immutable-def") {
690
765
  if (isKnownAtComptime(mutated)) throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is defined outside of the shader, and therefore is immutable during its execution. Try using tgpu.privateVar or buffers.`);
691
766
  throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
692
767
  }
768
+ if (mutated.origin === "uniform") throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because uniform buffers cannot be mutated.`);
769
+ if (mutated.origin === "readonly") throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because readonly buffers cannot be mutated.`);
693
770
  if (mutated.origin === "argument") throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
694
771
  }
695
772
  function assertExhaustive(value) {
package/types.d.ts CHANGED
@@ -16,7 +16,7 @@ import { ShaderGenerator } from "./tgsl/shaderGenerator.js";
16
16
  import { TgpuBuffer } from "./core/buffer/buffer.js";
17
17
  import { TgpuDeclare } from "./core/declare/tgpuDeclare.js";
18
18
  import { TgpuFn } from "./core/function/tgpuFn.js";
19
- import { WgslExtension } from "./wgslExtensions.js";
19
+ import { WgslEnableExtension } from "./wgslExtensions.js";
20
20
  import { AnyMatInstance, AnyVecInstance, BaseData } from "./data/wgslTypes.js";
21
21
  import { TgpuTexture, TgpuTextureView } from "./core/texture/texture.js";
22
22
  import { Infer } from "./shared/repr.js";
@@ -28,8 +28,10 @@ import { Block, FuncParameter } from "tinyest";
28
28
  type ResolvableObject = SelfResolvable | TgpuBufferUsage | TgpuConst | TgpuDeclare | TgpuBindGroupLayout | TgpuFn | TgpuComputeFn | TgpuFragmentFn | TgpuComputePipeline | TgpuRenderPipeline | TgpuVertexFn | TgpuSampler | TgpuAccessor | TgpuExternalTexture | TgpuTexture | TgpuTextureView | TgpuVar | AnyVecInstance | AnyMatInstance | AnyData | ((...args: never[]) => unknown);
29
29
  type Wgsl = Eventual<string | number | boolean | ResolvableObject>;
30
30
  type TgpuShaderStage = 'compute' | 'vertex' | 'fragment';
31
- interface FnToWgslOptions {
31
+ interface ResolveFunctionOptions {
32
32
  functionType: 'normal' | TgpuShaderStage;
33
+ workgroupSize?: readonly number[] | undefined;
34
+ name: string;
33
35
  argTypes: BaseData[];
34
36
  /**
35
37
  * The return type of the function. If undefined, the type should be inferred
@@ -196,7 +198,7 @@ interface ResolutionCtx {
196
198
  itemStateStack: ItemStateStack;
197
199
  };
198
200
  readonly mode: ExecState;
199
- readonly enableExtensions: WgslExtension[] | undefined;
201
+ readonly enableExtensions: WgslEnableExtension[] | undefined;
200
202
  readonly gen: ShaderGenerator;
201
203
  addDeclaration(declaration: string): void;
202
204
  withResetIndentLevel<T>(callback: () => T): T;
@@ -232,7 +234,7 @@ interface ResolutionCtx {
232
234
  * Equivalent to `snip(ctx.resolve(snippet.value, snippet.dataType).value, snippet.dataType, snippet.origin)`.
233
235
  */
234
236
  resolveSnippet(snippet: Snippet): ResolvedSnippet;
235
- fnToWgsl(options: FnToWgslOptions): {
237
+ resolveFunction(options: ResolveFunctionOptions): {
236
238
  code: string;
237
239
  returnType: BaseData;
238
240
  };
@@ -1,5 +1,5 @@
1
1
  //#region src/wgslExtensions.d.ts
2
- declare const wgslExtensions: readonly ["f16", "clip_distances", "dual_source_blending", "subgroups", "primitive_index"];
3
- type WgslExtension = (typeof wgslExtensions)[number];
2
+ declare const wgslEnableExtensions: readonly ["f16", "clip_distances", "dual_source_blending", "subgroups", "primitive_index"];
3
+ type WgslEnableExtension = (typeof wgslEnableExtensions)[number];
4
4
  //#endregion
5
- export { WgslExtension };
5
+ export { WgslEnableExtension };
package/wgslExtensions.js CHANGED
@@ -1,12 +1,12 @@
1
1
  //#region src/wgslExtensions.ts
2
- const wgslExtensions = [
2
+ const wgslEnableExtensions = [
3
3
  "f16",
4
4
  "clip_distances",
5
5
  "dual_source_blending",
6
6
  "subgroups",
7
7
  "primitive_index"
8
8
  ];
9
- const wgslExtensionToFeatureName = {
9
+ const wgslEnableExtensionToFeatureName = {
10
10
  f16: "shader-f16",
11
11
  clip_distances: "clip-distances",
12
12
  dual_source_blending: "dual-source-blending",
@@ -15,4 +15,4 @@ const wgslExtensionToFeatureName = {
15
15
  };
16
16
 
17
17
  //#endregion
18
- export { wgslExtensionToFeatureName, wgslExtensions };
18
+ export { wgslEnableExtensionToFeatureName, wgslEnableExtensions };