typegpu 0.11.7 → 0.11.9

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.
@@ -1,7 +1,7 @@
1
1
  import { $internal, $resolve } from "../../shared/symbols.js";
2
2
  import { Void } from "../../data/wgslTypes.js";
3
3
  import { snip } from "../../data/snippet.js";
4
- import { applyExternals, replaceExternalsInWgsl } from "../resolve/externals.js";
4
+ import { replaceExternalsInWgsl } from "../resolve/externals.js";
5
5
 
6
6
  //#region src/core/declare/tgpuDeclare.ts
7
7
  /**
@@ -16,19 +16,18 @@ function declare(declaration) {
16
16
  }
17
17
  var TgpuDeclareImpl = class {
18
18
  [$internal] = true;
19
- #externalsToApply = [];
19
+ #externals;
20
20
  #declaration;
21
21
  constructor(declaration) {
22
22
  this.#declaration = declaration;
23
23
  }
24
24
  $uses(dependencyMap) {
25
- this.#externalsToApply.push(dependencyMap);
25
+ if (this.#externals !== void 0) throw new Error("Cannot call '$uses' multiple times. If you wish to override dependencies, use slots or accessors instead.");
26
+ this.#externals = dependencyMap;
26
27
  return this;
27
28
  }
28
29
  [$resolve](ctx) {
29
- const externalMap = {};
30
- for (const externals of this.#externalsToApply) applyExternals(externalMap, externals);
31
- const replacedDeclaration = replaceExternalsInWgsl(ctx, externalMap, this.#declaration);
30
+ const replacedDeclaration = replaceExternalsInWgsl(ctx, this.#externals ?? {}, this.#declaration);
32
31
  ctx.addDeclaration(replacedDeclaration);
33
32
  return snip("", Void, "constant");
34
33
  }
@@ -0,0 +1,5 @@
1
+ import "../../data/snippet.js";
2
+ import "./fnTypes.js";
3
+ import "../../types.js";
4
+ import "../../data/wgslTypes.js";
5
+ import "../resolve/externals.js";
@@ -5,7 +5,7 @@ import { undecorate } from "../../data/dataTypes.js";
5
5
  import { snip } from "../../data/snippet.js";
6
6
  import { getAttributesString } from "../../data/attributes.js";
7
7
  import { validateIdentifier } from "../../nameUtils.js";
8
- import { applyExternals, replaceExternalsInWgsl } from "../resolve/externals.js";
8
+ import { mergeFunctionExternals, replaceExternalsInWgsl } from "../resolve/externals.js";
9
9
  import { extractArgs } from "./extractArgs.js";
10
10
 
11
11
  //#region src/core/function/fnCore.ts
@@ -16,19 +16,19 @@ function createFnCore(implementation, functionType, workgroupSize) {
16
16
  * initialized yet (like when accessing the Output struct of a vertex
17
17
  * entry fn).
18
18
  */
19
- const externalsToApply = [];
19
+ const externals = {};
20
20
  return {
21
21
  [$getNameForward]: typeof implementation === "function" ? implementation : void 0,
22
- applyExternals(newExternals) {
23
- externalsToApply.push(newExternals);
22
+ setExternals(key, newExternal) {
23
+ if (key === "userProvided" && "userProvided" in externals) throw new Error("Cannot call '$uses' multiple times. If you wish to override dependencies, use slots or accessors instead.");
24
+ externals[key] = newExternal;
25
+ if (externals.userProvided && externals.pluginProvided) throw new Error("Cannot call '$uses' on functions whose metadata was provided by unplugin-typegpu.");
24
26
  },
25
27
  resolve(ctx, argTypes, returnType, entryInput) {
26
- const externalMap = {};
27
28
  let attributes = "";
28
29
  if (functionType === "compute") attributes = `@compute @workgroup_size(${workgroupSize?.join(", ")}) `;
29
30
  else if (functionType === "vertex") attributes = `@vertex `;
30
31
  else if (functionType === "fragment") attributes = `@fragment `;
31
- for (const externals of externalsToApply) applyExternals(externalMap, externals);
32
32
  const id = ctx.makeUniqueIdentifier(getName(this), "global");
33
33
  if (typeof implementation === "string") {
34
34
  if (!returnType) throw new Error("Explicit return type is required for string implementation");
@@ -36,10 +36,11 @@ function createFnCore(implementation, functionType, workgroupSize) {
36
36
  for (const arg of entryInput.positionalArgs) {
37
37
  const result = /* @__PURE__ */ validateIdentifier(arg.schemaKey);
38
38
  if (!result.success) throw new Error(`Invalid argument name "${arg.schemaKey}"${result.error ? `: ${result.error}` : ""}`);
39
+ if (ctx.isIdentifierBanned(arg.schemaKey)) throw new Error(`Invalid argument name "${arg.schemaKey}", the identifier is a reserved keyword.`);
39
40
  }
40
- applyExternals(externalMap, { in: Object.fromEntries(entryInput.positionalArgs.map((a) => [a.schemaKey, a.schemaKey])) });
41
+ this.setExternals("args", { in: Object.fromEntries(entryInput.positionalArgs.map((a) => [a.schemaKey, a.schemaKey])) });
41
42
  }
42
- const replacedImpl = replaceExternalsInWgsl(ctx, externalMap, implementation);
43
+ const replacedImpl = replaceExternalsInWgsl(ctx, mergeFunctionExternals(externals), implementation);
43
44
  let header = "";
44
45
  let body = "";
45
46
  if (functionType !== "normal" && entryInput) {
@@ -64,12 +65,12 @@ function createFnCore(implementation, functionType, workgroupSize) {
64
65
  return snip(id, returnType, "runtime");
65
66
  }
66
67
  const pluginData = getFunctionMetadata(implementation);
67
- const pluginExternals = pluginData?.externals;
68
- if (pluginExternals) applyExternals(externalMap, Object.fromEntries(Object.entries(pluginExternals).filter(([name]) => !(name in externalMap))));
68
+ const pluginExternals = pluginData?.externals();
69
+ if (pluginExternals) this.setExternals("pluginProvided", pluginExternals);
69
70
  const ast = pluginData?.ast;
70
71
  if (!ast) throw new Error("Missing metadata for tgpu.fn function body (either missing 'use gpu' directive, or misconfigured `unplugin-typegpu`)");
71
72
  const maybeSecondArg = ast.params[1];
72
- if (maybeSecondArg && maybeSecondArg.type === "i" && functionType !== "normal") applyExternals(externalMap, { [maybeSecondArg.name]: undecorate(returnType) });
73
+ if (maybeSecondArg && maybeSecondArg.type === "i" && functionType !== "normal") this.setExternals("out", { [maybeSecondArg.name]: undecorate(returnType) });
73
74
  const { code, returnType: actualReturnType } = ctx.resolveFunction({
74
75
  functionType,
75
76
  name: id,
@@ -79,7 +80,7 @@ function createFnCore(implementation, functionType, workgroupSize) {
79
80
  params: ast.params,
80
81
  returnType,
81
82
  body: ast.body,
82
- externalMap
83
+ externalMap: mergeFunctionExternals(externals)
83
84
  });
84
85
  ctx.addDeclaration(code);
85
86
  return snip(id, actualReturnType, "runtime");
@@ -3,6 +3,9 @@ import { getName } from "../../shared/meta.js";
3
3
  import { createFnCore } from "./fnCore.js";
4
4
 
5
5
  //#region src/core/function/shelllessImpl.ts
6
+ function isShelllessImpl(value) {
7
+ return value?.resourceType === "shellless-impl";
8
+ }
6
9
  function createShelllessImpl(argTypes, implementation) {
7
10
  const core = createFnCore(implementation, "normal");
8
11
  return {
@@ -20,4 +23,4 @@ function createShelllessImpl(argTypes, implementation) {
20
23
  }
21
24
 
22
25
  //#endregion
23
- export { createShelllessImpl };
26
+ export { createShelllessImpl, isShelllessImpl };
@@ -35,7 +35,7 @@ function createComputeFn(shell, workgroupSize, implementation, entryInput) {
35
35
  return {
36
36
  shell,
37
37
  $uses(newExternals) {
38
- core.applyExternals(newExternals);
38
+ core.setExternals("userProvided", newExternals);
39
39
  return this;
40
40
  },
41
41
  [$internal]: true,
@@ -46,7 +46,7 @@ function createFn(shell, _implementation) {
46
46
  resourceType: "function",
47
47
  [$internal]: { implementation },
48
48
  $uses(newExternals) {
49
- core.applyExternals(newExternals);
49
+ core.setExternals("userProvided", newExternals);
50
50
  return this;
51
51
  },
52
52
  [$getNameForward]: core,
@@ -59,8 +59,8 @@ function createFn(shell, _implementation) {
59
59
  }),
60
60
  [$resolve](ctx) {
61
61
  if (typeof implementation === "string") {
62
- addArgTypesToExternals(implementation, shell.argTypes, core.applyExternals);
63
- addReturnTypeToExternals(implementation, shell.returnType, core.applyExternals);
62
+ addArgTypesToExternals(implementation, shell.argTypes, core);
63
+ addReturnTypeToExternals(implementation, shell.returnType, core);
64
64
  }
65
65
  return core.resolve(ctx, shell.argTypes, shell.returnType);
66
66
  }
@@ -34,12 +34,12 @@ function isTgpuFragmentFn(value) {
34
34
  function createFragmentFn(shell, implementation) {
35
35
  const core = createFnCore(implementation, "fragment");
36
36
  const outputType = shell.returnType;
37
- if (typeof implementation === "string") addReturnTypeToExternals(implementation, outputType, (externals) => core.applyExternals(externals));
37
+ if (typeof implementation === "string") addReturnTypeToExternals(implementation, outputType, core);
38
38
  return {
39
39
  shell,
40
40
  outputType,
41
41
  $uses(newExternals) {
42
- core.applyExternals(newExternals);
42
+ core.setExternals("userProvided", newExternals);
43
43
  return this;
44
44
  },
45
45
  [$internal]: true,
@@ -52,7 +52,7 @@ function createFragmentFn(shell, implementation) {
52
52
  [$resolve](ctx) {
53
53
  const entryInput = separateBuiltins(shell.in ?? {}, ctx.varyingLocations ?? {});
54
54
  if (entryInput.dataSchema && isNamable(entryInput.dataSchema)) entryInput.dataSchema.$name(`${getName(this) ?? ""}_Input`);
55
- core.applyExternals({ Out: outputType });
55
+ if (typeof implementation === "string") core.setExternals("out", { Out: outputType });
56
56
  return ctx.withSlots([[shaderStageSlot, "fragment"]], () => core.resolve(ctx, [], shell.returnType, entryInput));
57
57
  },
58
58
  toString() {
@@ -37,7 +37,7 @@ function createVertexFn(shell, implementation) {
37
37
  return {
38
38
  shell,
39
39
  $uses(newExternals) {
40
- core.applyExternals(newExternals);
40
+ core.setExternals("userProvided", newExternals);
41
41
  return this;
42
42
  },
43
43
  [$internal]: true,
@@ -48,7 +48,7 @@ function createVertexFn(shell, implementation) {
48
48
  },
49
49
  [$resolve](ctx) {
50
50
  const outputWithLocation = createIoSchema(shell.out, ctx.varyingLocations).$name(`${getName(this) ?? ""}_Output`);
51
- if (typeof implementation === "string") core.applyExternals({ Out: outputWithLocation });
51
+ if (typeof implementation === "string") core.setExternals("out", { Out: outputWithLocation });
52
52
  return ctx.withSlots([[shaderStageSlot, "vertex"]], () => core.resolve(ctx, [], outputWithLocation, entryInput));
53
53
  },
54
54
  toString() {
@@ -2,7 +2,7 @@ import { $gpuValueOf, $internal, $ownSnippet, $resolve } from "../../shared/symb
2
2
  import { snip } from "../../data/snippet.js";
3
3
  import { inCodegenMode } from "../../execMode.js";
4
4
  import { valueProxyHandler } from "../valueProxyUtils.js";
5
- import { applyExternals, replaceExternalsInWgsl } from "../resolve/externals.js";
5
+ import { replaceExternalsInWgsl } from "../resolve/externals.js";
6
6
 
7
7
  //#region src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts
8
8
  /**
@@ -51,22 +51,20 @@ var TgpuRawCodeSnippetImpl = class {
51
51
  dataType;
52
52
  origin;
53
53
  #expression;
54
- #externalsToApply;
54
+ #externals;
55
55
  constructor(expression, type, origin) {
56
56
  this[$internal] = true;
57
57
  this.dataType = type;
58
58
  this.origin = origin;
59
59
  this.#expression = expression;
60
- this.#externalsToApply = [];
61
60
  }
62
61
  $uses(dependencyMap) {
63
- this.#externalsToApply.push(dependencyMap);
62
+ if (this.#externals !== void 0) throw new Error("Cannot call '$uses' multiple times. If you wish to override dependencies, use slots or accessors instead.");
63
+ this.#externals = dependencyMap;
64
64
  return this;
65
65
  }
66
66
  [$resolve](ctx) {
67
- const externalMap = {};
68
- for (const externals of this.#externalsToApply) applyExternals(externalMap, externals);
69
- return snip(replaceExternalsInWgsl(ctx, externalMap, this.#expression), this.dataType, this.origin);
67
+ return snip(replaceExternalsInWgsl(ctx, this.#externals ?? {}, this.#expression), this.dataType, this.origin);
70
68
  }
71
69
  toString() {
72
70
  return `raw(${String(this.dataType)}): "${this.#expression}"`;
@@ -1,10 +1,11 @@
1
1
  import "../../types.js";
2
+ import "../function/fnCore.js";
2
3
 
3
4
  //#region src/core/resolve/externals.d.ts
4
5
 
5
6
  /**
6
7
  * A key-value mapping where keys represent identifiers within shader code,
7
- * and values can be any type that can be resolved to a code string.
8
+ * and values can either be another ExternalMap, or be any type that can be resolved to a code string.
8
9
  */
9
10
  type ExternalMap = Record<string, unknown>;
10
11
  //#endregion
@@ -1,32 +1,36 @@
1
1
  import { isWgslStruct } from "../../data/wgslTypes.js";
2
- import { getName, hasTinyestMetadata, setName } from "../../shared/meta.js";
2
+ import { getName, hasTinyestMetadata, isNamable, setName } from "../../shared/meta.js";
3
3
  import { isLooseData } from "../../data/dataTypes.js";
4
4
  import { isWgsl } from "../../types.js";
5
5
 
6
6
  //#region src/core/resolve/externals.ts
7
+ function isResolvable(value) {
8
+ return isWgsl(value) || isLooseData(value) || hasTinyestMetadata(value);
9
+ }
7
10
  /**
8
- * Merges two external maps into one. If a key is present in both maps, the value from the new map is used.
9
- * If the external value is a namable object, it is given a name if it does not already have one.
10
- * @param existing - The existing external map.
11
- * @param newExternals - The new external map.
11
+ * Merges function externals into one map.
12
12
  */
13
- function applyExternals(existing, newExternals) {
14
- for (const [key, value] of Object.entries(newExternals)) {
15
- existing[key] = value;
16
- if (value && (typeof value === "object" || typeof value === "function") && getName(value) === void 0) setName(value, key);
13
+ function mergeFunctionExternals(fnExternals) {
14
+ const base = fnExternals.pluginProvided ?? fnExternals.userProvided ?? {};
15
+ const result = Object.defineProperties({}, Object.getOwnPropertyDescriptors(base));
16
+ for (const flatExternal of [fnExternals.args, fnExternals.out].filter((e) => e !== void 0)) for (const [key, value] of Object.entries(flatExternal)) {
17
+ if (key in result && result[key] !== value) throw new Error(`Key '${key}' appears in externals despite already being used for argument/return type. Please rename this external.`);
18
+ result[key] = value;
17
19
  }
20
+ return result;
18
21
  }
19
- function addArgTypesToExternals(implementation, argTypes, applyExternals$1) {
20
- const argTypeNames = [...implementation.matchAll(/:\s*(?<arg>.*?)\s*[,)]/g)].map((found) => found ? found[1] : void 0);
21
- applyExternals$1(Object.fromEntries(argTypes.flatMap((argType, i) => {
22
- const argTypeName = argTypeNames ? argTypeNames[i] : void 0;
22
+ function addArgTypesToExternals(implementation, argTypes, core) {
23
+ const argTypeNames = [...implementation.matchAll(/:\s*(?<arg>.*?)\s*[,)]/g)].map((found) => found?.[1]);
24
+ const args = Object.fromEntries(argTypes.flatMap((argType, i) => {
25
+ const argTypeName = argTypeNames?.[i];
23
26
  return isWgslStruct(argType) && argTypeName !== void 0 ? [[argTypeName, argType]] : [];
24
- })));
27
+ }));
28
+ core.setExternals("args", args);
25
29
  }
26
- function addReturnTypeToExternals(implementation, returnType, applyExternals$1) {
30
+ function addReturnTypeToExternals(implementation, returnType, core) {
27
31
  const matched = implementation.match(/->\s(?<output>[\w\d_]+)\s{/);
28
32
  const outputName = matched ? matched[1]?.trim() : void 0;
29
- if (isWgslStruct(returnType) && outputName && !/\s/g.test(outputName)) applyExternals$1({ [outputName]: returnType });
33
+ if (isWgslStruct(returnType) && outputName && !/\s/g.test(outputName)) core.setExternals("out", { [outputName]: returnType });
30
34
  }
31
35
  function identifierRegex(name) {
32
36
  return new RegExp(`(?<![\\w\\$_.])${name.replaceAll(".", "\\.").replaceAll("$", "\\$")}(?![\\w\\$_])`, "g");
@@ -44,7 +48,10 @@ function replaceExternalsInWgsl(ctx, externalMap, wgsl) {
44
48
  return Object.entries(externalMap).reduce((acc, [externalName, external]) => {
45
49
  const externalRegex = identifierRegex(externalName);
46
50
  if (wgsl && externalName !== "Out" && externalName !== "in" && !externalRegex.test(wgsl)) console.warn(`The external '${externalName}' wasn't used in the resolved template.`);
47
- if (isWgsl(external) || isLooseData(external) || hasTinyestMetadata(external)) return acc.replaceAll(externalRegex, ctx.resolve(external).value);
51
+ if (isResolvable(external)) {
52
+ if (isNamable(external) && getName(external) === void 0) setName(external, externalName.split(".").at(-1));
53
+ return acc.replaceAll(externalRegex, ctx.resolve(external).value);
54
+ }
48
55
  if (external !== null && typeof external === "object") {
49
56
  const foundProperties = [...wgsl.matchAll(new RegExp(`${externalName.replaceAll(".", "\\.").replaceAll("$", "\\$")}\\.(?<prop>.*?)(?![\\w\\$_])`, "g"))].map((found) => found[1]);
50
57
  return [...new Set(foundProperties)].reduce((innerAcc, prop) => prop && prop in external ? replaceExternalsInWgsl(ctx, { [`${externalName}.${prop}`]: external[prop] }, innerAcc) : innerAcc, acc);
@@ -55,4 +62,4 @@ function replaceExternalsInWgsl(ctx, externalMap, wgsl) {
55
62
  }
56
63
 
57
64
  //#endregion
58
- export { addArgTypesToExternals, addReturnTypeToExternals, applyExternals, replaceExternalsInWgsl };
65
+ export { addArgTypesToExternals, addReturnTypeToExternals, mergeFunctionExternals, replaceExternalsInWgsl };
@@ -1,7 +1,7 @@
1
1
  import { $internal, $resolve } from "../../shared/symbols.js";
2
2
  import { Void } from "../../data/wgslTypes.js";
3
3
  import { snip } from "../../data/snippet.js";
4
- import { applyExternals, replaceExternalsInWgsl } from "./externals.js";
4
+ import { replaceExternalsInWgsl } from "./externals.js";
5
5
  import { isBindGroupLayout } from "../../tgpuBindGroupLayout.js";
6
6
  import { resolve } from "../../resolutionCtx.js";
7
7
  import { isPipeline } from "../pipeline/typeGuards.js";
@@ -19,12 +19,10 @@ function resolve$1(arg, options) {
19
19
  function resolveFromTemplate(options) {
20
20
  const { template, externals, unstable_shaderGenerator: shaderGenerator, names = "strict", config, enableExtensions } = options;
21
21
  if (!template) console.warn("Calling resolve with an empty template is deprecated and will soon return an empty string. Consider using the 'tgpu.resolve(resolvableArray, options)' API instead.");
22
- const dependencies = {};
23
- applyExternals(dependencies, externals ?? {});
24
22
  return resolve({
25
23
  [$internal]: true,
26
24
  [$resolve](ctx) {
27
- return snip(replaceExternalsInWgsl(ctx, dependencies, template ?? ""), Void, "runtime");
25
+ return snip(replaceExternalsInWgsl(ctx, externals, template ?? ""), Void, "runtime");
28
26
  },
29
27
  toString: () => "<root>"
30
28
  }, {
package/core/root/init.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { $getNameForward, $internal } from "../../shared/symbols.js";
2
- import { setName } from "../../shared/meta.js";
2
+ import { getName, setName } from "../../shared/meta.js";
3
3
  import { invariant } from "../../errors.js";
4
4
  import { isAccessor, isMutableAccessor } from "../slot/slotTypes.js";
5
5
  import { u32 } from "../../data/numeric.js";
@@ -116,6 +116,7 @@ var WithBindingImpl = class WithBindingImpl {
116
116
  u32,
117
117
  u32
118
118
  ])(callback);
119
+ if (getName(wrappedCallback) === void 0) wrappedCallback.$name("wrappedCallback");
119
120
  const sizeUniform = root.createUniform(vec3u);
120
121
  const mainCompute = computeFn({
121
122
  workgroupSize,
@@ -355,6 +355,15 @@ interface RenderBundleEncoderPass {
355
355
  */
356
356
  drawIndexedIndirect(indirectBuffer: GPUBuffer, indirectOffset: GPUSize64): void;
357
357
  }
358
+ type Replace<T$1, R> = Omit<T$1, keyof R> & R;
359
+ /**
360
+ * The same as {@link GPURenderPassDescriptor}, but accepting readonly tuples as the clearValue
361
+ */
362
+ type TgpuRenderPassDescriptor = Replace<GPURenderPassDescriptor, {
363
+ colorAttachments: (Replace<GPURenderPassColorAttachment, {
364
+ clearValue?: readonly [number, number, number, number] | GPUColor | undefined;
365
+ }> | null)[];
366
+ }>;
358
367
  interface RenderPass extends RenderBundleEncoderPass {
359
368
  /**
360
369
  * Sets the viewport used during the rasterization stage to linearly map from
@@ -611,7 +620,7 @@ interface ExperimentalTgpuRoot extends Omit<TgpuRoot, 'with'>, Withable_Deprecat
611
620
  readonly shaderGenerator?: ShaderGenerator | undefined;
612
621
  /** @deprecated Use `root.createTexture` instead. */
613
622
  createTexture<TWidth extends number, THeight extends number, TDepth extends number, TSize extends readonly [TWidth] | readonly [TWidth, THeight] | readonly [TWidth, THeight, TDepth], TFormat extends GPUTextureFormat, TMipLevelCount extends number, TSampleCount extends number, TViewFormats extends GPUTextureFormat[], TDimension extends GPUTextureDimension>(props: CreateTextureOptions<TSize, TFormat, TMipLevelCount, TSampleCount, TViewFormats, TDimension>): TgpuTexture<CreateTextureResult<TSize, TFormat, TMipLevelCount, TSampleCount, TViewFormats, TDimension>>;
614
- beginRenderPass(descriptor: GPURenderPassDescriptor, callback: (pass: RenderPass) => void): void;
623
+ beginRenderPass(descriptor: TgpuRenderPassDescriptor, callback: (pass: RenderPass) => void): void;
615
624
  /**
616
625
  * Creates a {@link GPURenderBundle} by recording draw commands into a
617
626
  * {@link GPURenderBundleEncoder}. The resulting bundle can be replayed in a
@@ -79,7 +79,7 @@ var TgpuAccessorImpl = class extends AccessorBase {
79
79
  }
80
80
  get $() {
81
81
  if (inCodegenMode()) return this[$gpuValueOf];
82
- throw new Error("`tgpu.accessor` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call");
82
+ throw new Error("`tgpu.accessor` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call. Use `tgpu.slot` for non-WGSL values instead.");
83
83
  }
84
84
  };
85
85
  var TgpuMutableAccessorImpl = class extends AccessorBase {
@@ -89,7 +89,7 @@ var TgpuMutableAccessorImpl = class extends AccessorBase {
89
89
  }
90
90
  get $() {
91
91
  if (inCodegenMode()) return this[$gpuValueOf];
92
- throw new Error("`tgpu.mutableAccessor` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call");
92
+ throw new Error("`tgpu.mutableAccessor` relies on GPU resources and cannot be accessed outside of a compute dispatch or draw call. Use `tgpu.slot` for non-WGSL values instead.");
93
93
  }
94
94
  };
95
95
 
@@ -139,6 +139,7 @@ function buildWriter(node, offsetExpr, valueExpr, depth = 0, partial = false) {
139
139
  return subSchema ? go(subSchema, `(${offsetExpr$1} + ${propOffset.offset})`, `${valueExpr$1}.${key}`, depth$1) : "";
140
140
  }).join("");
141
141
  if (isWgslArray(node$1) || isDisarray(node$1)) {
142
+ if (node$1.elementCount === 0) throw new Error("Cannot write using a runtime-sized schema.");
142
143
  const elementSize = roundUp(sizeOf(node$1.elementType), alignmentOf(node$1));
143
144
  const totalSize = node$1.elementCount * elementSize;
144
145
  const copyLen = partial ? `Math.min(${valueExpr$1}.byteLength, Math.max(0, endOffset - (${offsetExpr$1})))` : `Math.min(${valueExpr$1}.byteLength, ${totalSize})`;
package/data/numeric.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { $internal } from "../shared/symbols.js";
2
+ import { FiniteMathAssumptionError } from "../errors.js";
2
3
  import { callableSchema } from "../core/function/createCallableSchema.js";
3
4
 
4
5
  //#region src/data/numeric.ts
@@ -11,7 +12,7 @@ const boolCast = callableSchema({
11
12
  if (typeof v === "boolean") return v;
12
13
  return !!v;
13
14
  },
14
- codegenImpl: (ctx, args) => ctx.gen.typeInstantiation(bool, args)
15
+ codegenImpl: (ctx, [v]) => ctx.gen.typeInstantiation(bool, v ? [v] : [])
15
16
  });
16
17
  /**
17
18
  * A schema that represents a boolean value. (equivalent to `bool` in WGSL)
@@ -38,6 +39,7 @@ const u32Cast = callableSchema({
38
39
  normalImpl(v) {
39
40
  if (v === void 0) return 0;
40
41
  if (typeof v === "boolean") return v ? 1 : 0;
42
+ if (!Number.isFinite(v)) throw new FiniteMathAssumptionError(v, u32);
41
43
  if (!Number.isInteger(v)) {
42
44
  const truncated = Math.trunc(v);
43
45
  if (truncated < 0) return 0;
@@ -46,7 +48,7 @@ const u32Cast = callableSchema({
46
48
  }
47
49
  return (v & 4294967295) >>> 0;
48
50
  },
49
- codegenImpl: (ctx, args) => ctx.gen.typeInstantiation(u32, args)
51
+ codegenImpl: (ctx, [v]) => ctx.gen.typeInstantiation(u32, v ? [v] : [])
50
52
  });
51
53
  /**
52
54
  * A schema that represents an unsigned 32-bit integer value. (equivalent to `u32` in WGSL)
@@ -75,9 +77,10 @@ const i32Cast = callableSchema({
75
77
  normalImpl(v) {
76
78
  if (v === void 0) return 0;
77
79
  if (typeof v === "boolean") return v ? 1 : 0;
80
+ if (!Number.isFinite(v)) throw new FiniteMathAssumptionError(v, i32);
78
81
  return v | 0;
79
82
  },
80
- codegenImpl: (ctx, args) => ctx.gen.typeInstantiation(i32, args)
83
+ codegenImpl: (ctx, [v]) => ctx.gen.typeInstantiation(i32, v ? [v] : [])
81
84
  });
82
85
  const u16 = {
83
86
  [$internal]: {},
@@ -108,9 +111,10 @@ const f32Cast = callableSchema({
108
111
  normalImpl(v) {
109
112
  if (v === void 0) return 0;
110
113
  if (typeof v === "boolean") return v ? 1 : 0;
114
+ if (!Number.isFinite(v)) throw new FiniteMathAssumptionError(v, f32);
111
115
  return Math.fround(v);
112
116
  },
113
- codegenImpl: (ctx, args) => ctx.gen.typeInstantiation(f32, args)
117
+ codegenImpl: (ctx, [v]) => ctx.gen.typeInstantiation(f32, v ? [v] : [])
114
118
  });
115
119
  /**
116
120
  * A schema that represents a 32-bit float value. (equivalent to `f32` in WGSL)
@@ -182,9 +186,10 @@ const f16Cast = callableSchema({
182
186
  normalImpl(v) {
183
187
  if (v === void 0) return 0;
184
188
  if (typeof v === "boolean") return v ? 1 : 0;
189
+ if (!Number.isFinite(v)) throw new FiniteMathAssumptionError(v, f16);
185
190
  return roundToF16(v);
186
191
  },
187
- codegenImpl: (ctx, args) => ctx.gen.typeInstantiation(f16, args)
192
+ codegenImpl: (ctx, [v]) => ctx.gen.typeInstantiation(f16, v ? [v] : [])
188
193
  });
189
194
  /**
190
195
  * A schema that represents a 16-bit float value. (equivalent to `f16` in WGSL)
package/errors.js CHANGED
@@ -122,6 +122,12 @@ var SignatureNotSupportedError = class SignatureNotSupportedError extends Error
122
122
  Object.setPrototypeOf(this, SignatureNotSupportedError.prototype);
123
123
  }
124
124
  };
125
+ var FiniteMathAssumptionError = class FiniteMathAssumptionError extends Error {
126
+ constructor(value, type) {
127
+ super(`Cannot convert value '${value}' to type ${type.type} because of the Finite Math Assumption (see: https://www.w3.org/TR/WGSL/#finite-math-assumption)`);
128
+ Object.setPrototypeOf(this, FiniteMathAssumptionError.prototype);
129
+ }
130
+ };
125
131
 
126
132
  //#endregion
127
- export { ExecutionError, IllegalBufferAccessError, IllegalVarAccessError, MissingBindGroupsError, MissingSlotValueError, MissingVertexBuffersError, NotUniformError, ResolutionError, SignatureNotSupportedError, WgslTypeError, invariant };
133
+ export { ExecutionError, FiniteMathAssumptionError, IllegalBufferAccessError, IllegalVarAccessError, MissingBindGroupsError, MissingSlotValueError, MissingVertexBuffersError, NotUniformError, ResolutionError, SignatureNotSupportedError, WgslTypeError, invariant };
package/nameUtils.js CHANGED
@@ -342,28 +342,34 @@ const builtins = new Set([
342
342
  "quadSwapX",
343
343
  "quadSwapY"
344
344
  ]);
345
+ /**
346
+ * Sanitizes the primer so that it is compliant with WGSL guidelines.
347
+ * This primer is not necessarily a valid identifier yet, as it may still collide with other idents or reserved keywords.
348
+ */
345
349
  /* @__NO_SIDE_EFFECTS__ */
346
350
  function sanitizePrimer(primer) {
347
351
  if (primer) {
348
- const base = primer.replaceAll(/\s/g, "_").replaceAll(/[^\w\d]/g, "");
349
- if (base === "_" || base === "" || base.startsWith("__")) return "item";
352
+ const base = primer.replaceAll(/\s/g, "_").replaceAll(/[^\w]/g, "");
353
+ if (!(/* @__PURE__ */ validateIdentifier(base)).success) return "item";
350
354
  return base;
351
355
  }
352
356
  return "item";
353
357
  }
354
358
  /**
355
- * A function for checking whether an identifier needs renaming.
356
- * Throws if provided with an invalid identifier that cannot be easily renamed.
359
+ * A function for checking whether an identifier is valid.
360
+ * If `ident` passes the checks, it is not necessarily a valid identifier yet, as it may still collide with other idents or reserved keywords.
357
361
  * @example
358
362
  * validateIdentifier("ident"); // { success: true }
359
- * validateIdentifier("struct"); // { success: false, error: "Identifiers cannot start with reserved keywords." }
360
- * validateIdentifier("struct_1"); { success: false, error: "Identifiers cannot start with reserved keywords." }
361
- * validateIdentifier("_"); // { success: false }
363
+ * validateIdentifier("_"); // { success: false, error: `Identifiers cannot be equal to '' or '_'` }
362
364
  * validateIdentifier("my variable"); // { success: false, error: "Identifiers cannot contain whitespace." }
365
+ * validateIdentifier("0"); // { success: false, error: "Identifier is not compliant with the WGSL guideline." }
363
366
  */
364
367
  /* @__NO_SIDE_EFFECTS__ */
365
368
  function validateIdentifier(ident) {
366
- if (ident === "_") return { success: false };
369
+ if (ident === "_" || ident === "") return {
370
+ success: false,
371
+ error: `Identifiers cannot be equal to '' or '_'`
372
+ };
367
373
  if (/\s/.test(ident)) return {
368
374
  success: false,
369
375
  error: `Identifiers cannot contain whitespace.`
@@ -372,29 +378,20 @@ function validateIdentifier(ident) {
372
378
  success: false,
373
379
  error: `Identifiers cannot start with double underscores.`
374
380
  };
375
- const prefix = ident.split("_")[0];
376
- if (bannedTokens.has(prefix) || builtins.has(prefix)) return {
381
+ if (!/^(([_\p{XID_Start}][\p{XID_Continue}]+)|([\p{XID_Start}]))$/u.test(ident)) return {
377
382
  success: false,
378
- error: `Identifiers cannot start with reserved keywords.`
383
+ error: `Not compliant with WGSL guidelines.`
379
384
  };
380
385
  return { success: true };
381
386
  }
382
387
  /**
383
- * Same as `validateIdentifier`, except does not check for builtin clashes.
388
+ * Same as `validateIdentifier`, except also checks for bannedToken clashes.
384
389
  */
385
390
  /* @__NO_SIDE_EFFECTS__ */
386
391
  function validateProp(ident) {
387
- if (ident === "_") return { success: false };
388
- if (/\s/.test(ident)) return {
389
- success: false,
390
- error: `Identifiers cannot contain whitespace.`
391
- };
392
- if (ident.startsWith("__")) return {
393
- success: false,
394
- error: `Identifiers cannot start with double underscores.`
395
- };
396
- const prefix = ident.split("_")[0];
397
- if (bannedTokens.has(prefix)) return {
392
+ const identResult = /* @__PURE__ */ validateIdentifier(ident);
393
+ if (!identResult.success) return identResult;
394
+ if (bannedTokens.has(ident)) return {
398
395
  success: false,
399
396
  error: `Identifiers cannot start with reserved keywords.`
400
397
  };
package/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.11.7";
2
+ var version = "0.11.9";
3
3
 
4
4
  //#endregion
5
5
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegpu",
3
- "version": "0.11.7",
3
+ "version": "0.11.9",
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",
@@ -22,7 +22,6 @@
22
22
  "url": "git+https://github.com/software-mansion/TypeGPU.git",
23
23
  "directory": "packages/typegpu"
24
24
  },
25
- "bin": "./bin.mjs",
26
25
  "type": "module",
27
26
  "sideEffects": false,
28
27
  "exports": {
@@ -55,5 +54,8 @@
55
54
  "main": "./index.js",
56
55
  "types": "./index.d.ts",
57
56
  "private": false,
58
- "scripts": {}
57
+ "scripts": {},
58
+ "bin": {
59
+ "typegpu": "./bin.mjs"
60
+ }
59
61
  }
package/resolutionCtx.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { $internal, $providing, $resolve, isMarkedInternal } from "./shared/symbols.js";
2
2
  import { Void, isPtr, isWgslArray, isWgslStruct } from "./data/wgslTypes.js";
3
3
  import { safeStringify } from "./shared/stringify.js";
4
- import { getName, hasTinyestMetadata, setName } from "./shared/meta.js";
4
+ import { getName, hasTinyestMetadata, isNamable, setName } from "./shared/meta.js";
5
5
  import { UnknownData, isData } from "./data/dataTypes.js";
6
6
  import { snip } from "./data/snippet.js";
7
7
  import { MissingSlotValueError, ResolutionError, WgslTypeError, invariant } from "./errors.js";
@@ -11,7 +11,7 @@ import { provideCtx, topLevelState } from "./execMode.js";
11
11
  import { getBestConversion } from "./tgsl/conversion.js";
12
12
  import { bool } from "./data/numeric.js";
13
13
  import { coerceToSnippet, concretize, numericLiteralToSnippet } from "./tgsl/generationHelpers.js";
14
- import { sanitizePrimer, validateIdentifier } from "./nameUtils.js";
14
+ import { bannedTokens, sanitizePrimer, validateIdentifier } from "./nameUtils.js";
15
15
  import { createIoSchema } from "./core/function/ioSchema.js";
16
16
  import { AutoStruct } from "./data/autoStruct.js";
17
17
  import { EntryInputRouter } from "./core/function/entryInputRouter.js";
@@ -22,6 +22,7 @@ import { naturalsExcept } from "./shared/generators.js";
22
22
  import { TgpuBindGroupImpl, bindGroupLayout } from "./tgpuBindGroupLayout.js";
23
23
  import { LogGeneratorImpl, LogGeneratorNullImpl } from "./tgsl/consoleLog/logGenerator.js";
24
24
  import wgslGenerator_default from "./tgsl/wgslGenerator.js";
25
+ import { isShelllessImpl } from "./core/function/shelllessImpl.js";
25
26
  import { FuncParameterType } from "tinyest";
26
27
 
27
28
  //#region src/resolutionCtx.ts
@@ -112,6 +113,7 @@ var ItemStateStackImpl = class {
112
113
  const access = layer.argAccess[id];
113
114
  if (access) return access();
114
115
  const external = layer.externalMap[id];
116
+ if (isNamable(external) && getName(external) === void 0) setName(external, id);
115
117
  if (external !== void 0 && external !== null) return coerceToSnippet(external);
116
118
  return;
117
119
  }
@@ -257,6 +259,9 @@ var ResolutionCtxImpl = class {
257
259
  this.#logGenerator = opts.root ? new LogGeneratorImpl(opts.root) : new LogGeneratorNullImpl();
258
260
  this.#namespaceInternal = opts.namespace[$internal];
259
261
  }
262
+ isIdentifierBanned(name) {
263
+ return bannedTokens.has(name);
264
+ }
260
265
  isIdentifierTaken(name) {
261
266
  return this.#namespaceInternal.takenGlobalIdentifiers.has(name) || this._itemStateStack.isIdentifierTakenLocally(name);
262
267
  }
@@ -558,7 +563,7 @@ var ResolutionCtxImpl = class {
558
563
  }
559
564
  }
560
565
  resolve(item, schema) {
561
- if (isTgpuFn(item) || hasTinyestMetadata(item)) {
566
+ if (isTgpuFn(item) || isShelllessImpl(item)) {
562
567
  if (this.#currentlyResolvedItems.has(item) && !this.#namespaceInternal.memoizedResolves.has(item)) throw new Error(`Recursive function ${item} detected. Recursion is not allowed on the GPU.`);
563
568
  this.#currentlyResolvedItems.add(item);
564
569
  }
@@ -20,7 +20,8 @@ function normalizeExternalsV2(externals) {
20
20
  }
21
21
  function normalizeMetadata(meta) {
22
22
  if (meta.v === 1) {
23
- const externals = typeof meta?.externals === "function" ? meta.externals() : meta?.externals;
23
+ const rawExternals = meta.externals;
24
+ const externals = typeof rawExternals === "function" ? rawExternals : () => rawExternals;
24
25
  return {
25
26
  ...meta,
26
27
  externals
@@ -30,7 +31,7 @@ function normalizeMetadata(meta) {
30
31
  const externals = normalizeExternalsV2(meta?.externals);
31
32
  return {
32
33
  ...meta,
33
- externals
34
+ externals: () => externals
34
35
  };
35
36
  }
36
37
  throw new Error(`Unrecognized TypeGPU metadata format: ${safeStringify(meta)}`);
@@ -1,9 +1,11 @@
1
1
  import { isMat, isPtr, isVec, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
2
- import { MatrixColumnsAccess, UnknownData, isUnstruct, undecorate } from "../data/dataTypes.js";
2
+ import { getName, isNamable, setName } from "../shared/meta.js";
3
+ import { MatrixColumnsAccess, UnknownData, isUnstruct } from "../data/dataTypes.js";
3
4
  import { isSnippet, snip } from "../data/snippet.js";
4
5
  import { isKnownAtComptime } from "../types.js";
5
6
  import { stitch } from "../core/resolve/stitch.js";
6
7
  import { derefSnippet } from "../data/ref.js";
8
+ import { accessStructProp } from "./accessStructProp.js";
7
9
  import { bool, f16, f32, i32, u32 } from "../data/numeric.js";
8
10
  import { coerceToSnippet, numericLiteralToSnippet } from "./generationHelpers.js";
9
11
  import { vec2b, vec2f, vec2h, vec2i, vec2u, vec3b, vec3f, vec3h, vec3i, vec3u, vec4b, vec4f, vec4h, vec4i, vec4u } from "../data/vector.js";
@@ -71,12 +73,7 @@ function accessProp(target, propName) {
71
73
  return numericLiteralToSnippet(target.dataType.elementCount);
72
74
  }
73
75
  if (isMat(target.dataType) && propName === "columns") return snip(new MatrixColumnsAccess(target), UnknownData, target.origin, target.possibleSideEffects);
74
- if (isWgslStruct(target.dataType) || isUnstruct(target.dataType)) {
75
- let propType = target.dataType.propTypes[propName];
76
- if (!propType) return;
77
- propType = undecorate(propType);
78
- return snip(stitch`${target}.${propName}`, propType, target.origin, target.possibleSideEffects);
79
- }
76
+ if (isWgslStruct(target.dataType) || isUnstruct(target.dataType)) return accessStructProp(target, propName);
80
77
  if (target.dataType instanceof AutoStruct) {
81
78
  const result = target.dataType.accessProp(propName);
82
79
  if (!result) return;
@@ -102,7 +99,11 @@ function accessProp(target, propName) {
102
99
  if (!swizzleType) return;
103
100
  return snip(isKnownAtComptime(target) ? target.value[propName] : stitch`${target}.${propName}`, swizzleType, propLength === 1 ? target.origin : target.origin === "constant" || target.origin === "constant-immutable-def" ? "constant" : "runtime", target.possibleSideEffects);
104
101
  }
105
- if (isKnownAtComptime(target) || target.dataType === UnknownData) return coerceToSnippet(target.value[propName]);
102
+ if (isKnownAtComptime(target) || target.dataType === UnknownData) {
103
+ const prop = target.value[propName];
104
+ if (isNamable(prop) && getName(prop) === void 0) setName(prop, propName);
105
+ return coerceToSnippet(prop);
106
+ }
106
107
  }
107
108
 
108
109
  //#endregion
@@ -0,0 +1,20 @@
1
+ import { isWgslStruct } from "../data/wgslTypes.js";
2
+ import { isUnstruct, undecorate } from "../data/dataTypes.js";
3
+ import { snip } from "../data/snippet.js";
4
+ import { invariant } from "../errors.js";
5
+ import { stitch } from "../core/resolve/stitch.js";
6
+
7
+ //#region src/tgsl/accessStructProp.ts
8
+ /**
9
+ * This function is separate from `accessProp` to avoid a circular dependency.
10
+ */
11
+ function accessStructProp(target, propName) {
12
+ invariant(isWgslStruct(target.dataType) || isUnstruct(target.dataType), "Expected snippet type to be struct/unstruct.");
13
+ let propType = target.dataType.propTypes[propName];
14
+ if (!propType) return;
15
+ propType = undecorate(propType);
16
+ return snip(stitch`${target}.${propName}`, propType, target.origin, target.possibleSideEffects);
17
+ }
18
+
19
+ //#endregion
20
+ export { accessStructProp };
@@ -1,13 +1,15 @@
1
1
  import { DEV, TEST } from "../shared/env.js";
2
- import { isMat, isPtr, isVec } from "../data/wgslTypes.js";
2
+ import { isMat, isPtr, isVec, isWgslStruct } from "../data/wgslTypes.js";
3
3
  import { safeStringify } from "../shared/stringify.js";
4
+ import { getName } from "../shared/meta.js";
4
5
  import { UnknownData, undecorate } from "../data/dataTypes.js";
5
- import { snip, withDataType } from "../data/snippet.js";
6
+ import { isAlias, snip, withDataType } from "../data/snippet.js";
6
7
  import { WgslTypeError, invariant } from "../errors.js";
7
8
  import { stitch } from "../core/resolve/stitch.js";
8
9
  import { RefOperator, derefSnippet } from "../data/ref.js";
9
10
  import { schemaCallWrapperGPU } from "../data/schemaCallWrapper.js";
10
11
  import { assertExhaustive } from "../shared/utilityTypes.js";
12
+ import { accessStructProp } from "./accessStructProp.js";
11
13
 
12
14
  //#region src/tgsl/conversion.ts
13
15
  const INFINITE_RANK = {
@@ -17,10 +19,17 @@ const INFINITE_RANK = {
17
19
  function getAutoConversionRank(src, dest) {
18
20
  const trueSrc = undecorate(src);
19
21
  const trueDst = undecorate(dest);
20
- if (trueSrc.type === trueDst.type) return {
21
- rank: 0,
22
- action: "none"
23
- };
22
+ if (trueSrc.type === trueDst.type) {
23
+ if (trueSrc.type === "struct" && trueSrc !== trueDst) return {
24
+ rank: 1,
25
+ action: "cast",
26
+ targetType: dest
27
+ };
28
+ return {
29
+ rank: 0,
30
+ action: "none"
31
+ };
32
+ }
24
33
  if (trueSrc.type === "abstractFloat") {
25
34
  if (trueDst.type === "f32") return {
26
35
  rank: 1,
@@ -165,7 +174,22 @@ function applyActionToSnippet(ctx, snippet, action, targetType) {
165
174
  switch (action.action) {
166
175
  case "ref": return snip(new RefOperator(snippet, targetType), targetType, snippet.origin);
167
176
  case "deref": return derefSnippet(snippet);
168
- case "cast": return schemaCallWrapperGPU(ctx, targetType, snippet);
177
+ case "cast":
178
+ if (isWgslStruct(snippet.dataType) && isWgslStruct(targetType)) {
179
+ const typeName = getName(snippet.dataType) ?? "<unnamed>";
180
+ const targetName = getName(targetType) ?? "<unnamed>";
181
+ if (!isAlias(snippet)) throw new Error(`Cannot resolve struct cast from '${typeName}' to '${targetName}'. Store the value to a variable first, then cast it.`);
182
+ const propSnips = Object.entries(targetType.propTypes).map(([key, dataType]) => {
183
+ const accessedProp = accessStructProp(snippet, key);
184
+ if (!accessedProp) throw new Error(`Cannot auto-convert struct '${typeName}' to '${targetName}' because the property '${key}' is missing.`);
185
+ const converted = convertToCommonType(ctx, [accessedProp], [dataType]);
186
+ if (!converted || !converted[0]) throw new Error(`Cannot auto-convert struct '${typeName}' to '${targetName}' because type '${getName(accessedProp.dataType) ?? "<unnamed>"}' is not convertible to '${getName(undecorate(dataType)) ?? "<unnamed>"}'.`);
187
+ return converted[0];
188
+ });
189
+ const targetSnippet = ctx.resolve(targetType).value;
190
+ return snip(`${targetSnippet}(${propSnips.map((snip$1) => snip$1.value).join(", ")})`, targetType, "runtime", false);
191
+ }
192
+ return schemaCallWrapperGPU(ctx, targetType, snippet);
169
193
  default: assertExhaustive(action.action, "applyActionToSnippet");
170
194
  }
171
195
  }
@@ -232,7 +232,19 @@ ${this.ctx.pre}}`;
232
232
  if (rhsExpr.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot assign a ref to an existing variable '${stringifyNode(lhs)}', define a new variable instead.`);
233
233
  if (op === "==") throw new Error("Please use the === operator instead of ==");
234
234
  if (op === "!=") throw new Error("Please use the !== operator instead of !=");
235
- if (op === "===" && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) return snip(lhsExpr.value === rhsExpr.value, bool, "constant");
235
+ if (op === "===" && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) return snip(lhsExpr.value === rhsExpr.value, bool, "constant", false);
236
+ if (op === "!==" && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) return snip(lhsExpr.value !== rhsExpr.value, bool, "constant", false);
237
+ if ((op === "<" || op === "<=" || op === ">" || op === ">=") && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) {
238
+ const left = lhsExpr.value;
239
+ const right = rhsExpr.value;
240
+ if (typeof left !== "number" || typeof right !== "number") throw new WgslTypeError(`Inequality comparison '${op}' requires numeric operands, got '${typeof left}' and '${typeof right}'`);
241
+ switch (op) {
242
+ case "<": return snip(left < right, bool, "constant", false);
243
+ case "<=": return snip(left <= right, bool, "constant", false);
244
+ case ">": return snip(left > right, bool, "constant", false);
245
+ case ">=": return snip(left >= right, bool, "constant", false);
246
+ }
247
+ }
236
248
  if (lhsExpr.dataType === UnknownData) throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`);
237
249
  if (rhsExpr.dataType === UnknownData) throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
238
250
  const codegen = binaryOpCodeToCodegen[op];
@@ -475,6 +487,7 @@ ${this.ctx.pre}}`;
475
487
  return snip(stitch`${this.ctx.resolve(schema).value}(${args})`, schema, "runtime");
476
488
  }
477
489
  numericLiteral(value, schema) {
490
+ if (!Number.isFinite(value)) throw new Error(`Value '${value}' (${schema.type}) cannot be resolved due to WGSL's Finite Math Assumption (see: https://www.w3.org/TR/WGSL/#finite-math-assumption). This value might be a result of a comptime-evaluated operation.`);
478
491
  if (schema.type === "abstractInt") return snip(`${value}`, schema, "constant");
479
492
  if (schema.type === "u32") return snip(`${value}u`, schema, "constant");
480
493
  if (schema.type === "i32") return snip(`${value}i`, schema, "constant");
package/types.d.ts CHANGED
@@ -260,6 +260,7 @@ interface ResolutionCtx {
260
260
  * @returns an identifier that is unique within the given scope
261
261
  */
262
262
  makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string;
263
+ isIdentifierBanned(name: string): boolean;
263
264
  isIdentifierTaken(name: string): boolean;
264
265
  /**
265
266
  * Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier}