typegpu 0.11.3 → 0.11.5
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.
- package/README.md +4 -2
- package/core/buffer/buffer.js +17 -53
- package/core/buffer/bufferUsage.js +2 -2
- package/core/constant/tgpuConstant.js +1 -1
- package/core/function/fnCore.js +12 -6
- package/core/function/tgpuFragmentFn.d.ts +1 -1
- package/core/pipeline/computePipeline.d.ts +1 -1
- package/core/pipeline/computePipeline.js +1 -1
- package/core/pipeline/renderPipeline.js +1 -1
- package/core/resolve/namespace.d.ts +2 -11
- package/core/resolve/namespace.js +7 -24
- package/core/resolve/resolveData.js +3 -2
- package/core/resolve/tgpuResolve.js +1 -1
- package/core/root/init.d.ts +1 -0
- package/core/root/init.js +6 -0
- package/core/root/rootTypes.d.ts +21 -15
- package/core/sampler/sampler.js +2 -2
- package/core/simulate/tgpuSimulate.js +1 -1
- package/core/slot/accessor.js +1 -1
- package/core/texture/externalTexture.js +1 -1
- package/core/texture/texture.js +2 -2
- package/core/variable/tgpuVariable.js +1 -1
- package/data/autoStruct.js +3 -2
- package/data/dataIO.d.ts +11 -0
- package/data/dataIO.js +45 -1
- package/data/dataTypes.d.ts +1 -1
- package/data/dataTypes.js +2 -11
- package/data/partialIO.d.ts +8 -0
- package/data/partialIO.js +6 -1
- package/data/struct.js +3 -2
- package/data/wgslTypes.d.ts +16 -16
- package/index.d.ts +3 -1
- package/index.js +3 -1
- package/indexNamedExports.d.ts +2 -0
- package/{nameRegistry.js → nameUtils.js} +46 -90
- package/package.js +1 -1
- package/package.json +1 -1
- package/resolutionCtx.js +64 -30
- package/shared/stringify.js +1 -0
- package/shared/tseynit.js +90 -0
- package/std/copy.d.ts +7 -0
- package/std/copy.js +27 -0
- package/std/index.d.ts +3 -2
- package/std/index.js +3 -1
- package/tgpuUnstable.js +1 -1
- package/tgsl/accessIndex.js +2 -1
- package/tgsl/consoleLog/deserializers.js +1 -1
- package/tgsl/consoleLog/logGenerator.js +1 -6
- package/tgsl/consoleLog/types.d.ts +4 -5
- package/tgsl/conversion.js +4 -1
- package/tgsl/generationHelpers.d.ts +2 -1
- package/tgsl/jsPolyfills.d.ts +25 -0
- package/tgsl/jsPolyfills.js +44 -0
- package/tgsl/wgslGenerator.d.ts +20 -2
- package/tgsl/wgslGenerator.js +114 -57
- package/types.d.ts +29 -2
- package/nameRegistry.d.ts +0 -30
- package/tgsl/consoleLog/types.js +0 -12
- package/tgsl/math.js +0 -45
package/tgsl/wgslGenerator.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { $gpuCallable, $internal, $providing, isMarkedInternal } from "../shared/symbols.js";
|
|
2
2
|
import { getName } from "../shared/meta.js";
|
|
3
3
|
import { Void, isBool, isNaturallyEphemeral, isNumericSchema, isPtr, isVec, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
|
|
4
|
-
import {
|
|
4
|
+
import { InfixDispatch, UnknownData, unptr } from "../data/dataTypes.js";
|
|
5
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";
|
|
9
9
|
import { createPtrFromOrigin, implicitFrom, ptrFn } from "../data/ptr.js";
|
|
10
|
-
import { RefOperator } from "../data/ref.js";
|
|
10
|
+
import { RefOperator, _ref } 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";
|
|
@@ -23,10 +23,11 @@ import { isGenericFn } from "../core/function/tgpuFn.js";
|
|
|
23
23
|
import { arrayOf } from "../data/array.js";
|
|
24
24
|
import { pow } from "../std/numeric.js";
|
|
25
25
|
import { resolveData } from "../core/resolve/resolveData.js";
|
|
26
|
-
import { UnrollableIterable } from "../core/unroll/tgpuUnroll.js";
|
|
27
|
-
import { mathToStd } from "./
|
|
26
|
+
import { UnrollableIterable, unroll } from "../core/unroll/tgpuUnroll.js";
|
|
27
|
+
import { mathToStd, supportedLogOps } from "./jsPolyfills.js";
|
|
28
28
|
import { isTgpuRange } from "../std/range.js";
|
|
29
29
|
import { getElementSnippet, getElementType, getLoopVarKind, getRangeSnippets } from "./forOfUtils.js";
|
|
30
|
+
import { stringifyNode } from "../shared/tseynit.js";
|
|
30
31
|
import * as tinyest from "tinyest";
|
|
31
32
|
|
|
32
33
|
//#region src/tgsl/wgslGenerator.ts
|
|
@@ -164,8 +165,11 @@ ${this.ctx.pre}}`;
|
|
|
164
165
|
this.ctx.popBlockScope();
|
|
165
166
|
}
|
|
166
167
|
}
|
|
168
|
+
_blockStatement(block, externalMap) {
|
|
169
|
+
return `${this.ctx.pre}${this._block(block, externalMap)}`;
|
|
170
|
+
}
|
|
167
171
|
refVariable(id, dataType) {
|
|
168
|
-
const varName = this.ctx.
|
|
172
|
+
const varName = this.ctx.makeUniqueIdentifier(id, "block");
|
|
169
173
|
const ptrType = ptrFn(dataType);
|
|
170
174
|
const snippet = snip(new RefOperator(snip(varName, dataType, "function"), ptrType), ptrType, "function");
|
|
171
175
|
this.ctx.defineVariable(id, snippet);
|
|
@@ -180,10 +184,14 @@ ${this.ctx.pre}}`;
|
|
|
180
184
|
else if (!naturallyEphemeral) varOrigin = isEphemeralOrigin(origin) ? "this-function" : origin;
|
|
181
185
|
else if (origin === "constant" && varType === "const") varOrigin = "constant";
|
|
182
186
|
else varOrigin = "runtime";
|
|
183
|
-
const snippet = snip(this.ctx.
|
|
187
|
+
const snippet = snip(this.ctx.makeUniqueIdentifier(id, "block"), dataType, varOrigin);
|
|
184
188
|
this.ctx.defineVariable(id, snippet);
|
|
185
189
|
return snippet;
|
|
186
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Creates a variable declaration string.
|
|
193
|
+
* `keyword` may be a placeholder filled in later.
|
|
194
|
+
*/
|
|
187
195
|
emitVarDecl(pre, keyword, name, _dataType, rhsStr) {
|
|
188
196
|
return `${pre}${keyword} ${name} = ${rhsStr};`;
|
|
189
197
|
}
|
|
@@ -225,8 +233,9 @@ ${this.ctx.pre}}`;
|
|
|
225
233
|
return snip(rhsStr$1, bool, "runtime");
|
|
226
234
|
}
|
|
227
235
|
const rhsExpr = this._expression(rhs);
|
|
228
|
-
if (rhsExpr.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot assign a ref to an existing variable '${
|
|
236
|
+
if (rhsExpr.value instanceof RefOperator) throw new WgslTypeError(stitch`Cannot assign a ref to an existing variable '${stringifyNode(lhs)}', define a new variable instead.`);
|
|
229
237
|
if (op === "==") throw new Error("Please use the === operator instead of ==");
|
|
238
|
+
if (op === "!=") throw new Error("Please use the !== operator instead of !=");
|
|
230
239
|
if (op === "===" && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) return snip(lhsExpr.value === rhsExpr.value, bool, "constant");
|
|
231
240
|
if (lhsExpr.dataType === UnknownData) throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`);
|
|
232
241
|
if (rhsExpr.dataType === UnknownData) throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
|
|
@@ -250,19 +259,14 @@ ${this.ctx.pre}}`;
|
|
|
250
259
|
const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value;
|
|
251
260
|
const type = operatorToType(convLhs.dataType, op, convRhs.dataType);
|
|
252
261
|
if (exprType === NODE.assignmentExpr) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (op === "=" && rhsExpr.origin === "argument" && !isNaturallyEphemeral(rhsExpr.dataType)) throw new WgslTypeError(`'${
|
|
256
|
-
if (op === "=" && !isEphemeralSnippet(rhsExpr)) throw new WgslTypeError(`'${
|
|
262
|
+
validateSnippetMutation(convLhs, expression);
|
|
263
|
+
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-----`);
|
|
257
266
|
}
|
|
258
267
|
return snip(parenthesizedOps.includes(op) ? `(${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr})` : `${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr}`, type, "runtime");
|
|
259
268
|
}
|
|
260
|
-
if (expression[0] === NODE.postUpdate) {
|
|
261
|
-
const [_, op, arg] = expression;
|
|
262
|
-
const argExpr = this._expression(arg);
|
|
263
|
-
const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value;
|
|
264
|
-
return snip(`${argStr}${op}`, argExpr.dataType, "runtime");
|
|
265
|
-
}
|
|
269
|
+
if (expression[0] === NODE.postUpdate) throw new Error(`'${stringifyNode(expression)}' is invalid because update is only allowed as a statement.`);
|
|
266
270
|
if (expression[0] === NODE.unaryExpr) {
|
|
267
271
|
const [_, op, arg] = expression;
|
|
268
272
|
const argExpr = this._expression(arg);
|
|
@@ -274,37 +278,28 @@ ${this.ctx.pre}}`;
|
|
|
274
278
|
}
|
|
275
279
|
if (expression[0] === NODE.memberAccess) {
|
|
276
280
|
const [_, targetNode, property] = expression;
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
if (target.value === Math) {
|
|
280
|
-
if (property in mathToStd && mathToStd[property]) return snip(mathToStd[property], UnknownData, "runtime");
|
|
281
|
-
if (typeof Math[property] === "function") throw new Error(`Unsupported functionality 'Math.${property}'. Use an std alternative, or implement the function manually.`);
|
|
282
|
-
}
|
|
283
|
-
const accessed = accessProp(target, property);
|
|
284
|
-
if (!accessed) throw new Error(stitch`Property '${property}' not found on value '${target}' of type ${this.ctx.resolve(target.dataType)}`);
|
|
281
|
+
const accessed = accessProp(this._expression(targetNode), property);
|
|
282
|
+
if (!accessed) throw new Error(`Property '${property}' not found on '${stringifyNode(targetNode)}'`);
|
|
285
283
|
return accessed;
|
|
286
284
|
}
|
|
287
285
|
if (expression[0] === NODE.indexAccess) {
|
|
288
286
|
const [_, targetNode, propertyNode] = expression;
|
|
289
287
|
const target = this._expression(targetNode);
|
|
290
288
|
const inProperty = this._expression(propertyNode);
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
if (!accessed) {
|
|
294
|
-
const targetStr = this.ctx.resolve(target.value, target.dataType).value;
|
|
295
|
-
const propertyStr = this.ctx.resolve(property.value, property.dataType).value;
|
|
296
|
-
throw new Error(`Index access '${targetStr}[${propertyStr}]' is invalid. If the value is an array, to address this, consider one of the following approaches: (1) declare the array using 'tgpu.const', (2) store the array in a buffer, or (3) define the array within the GPU function scope.`);
|
|
297
|
-
}
|
|
289
|
+
const accessed = accessIndex(target, convertToCommonType(this.ctx, [inProperty], [u32, i32], false)?.[0] ?? inProperty);
|
|
290
|
+
if (!accessed) throw new Error(`Index access '${stringifyNode(expression)}' is invalid. If the value is an array, to address this, consider one of the following approaches: (1) declare the array using 'tgpu.const', (2) store the array in a buffer, or (3) define the array within the GPU function scope.`);
|
|
298
291
|
return accessed;
|
|
299
292
|
}
|
|
300
293
|
if (expression[0] === NODE.numericLiteral) {
|
|
301
294
|
const type = typeof expression[1] === "string" ? numericLiteralToSnippet(parseNumericString(expression[1])) : numericLiteralToSnippet(expression[1]);
|
|
302
|
-
|
|
295
|
+
invariant(type, `Expected ${stringifyNode(expression)} to be valid numeric literal`);
|
|
303
296
|
return type;
|
|
304
297
|
}
|
|
305
298
|
if (expression[0] === NODE.call) {
|
|
306
299
|
const [_, calleeNode, argNodes] = expression;
|
|
307
|
-
const
|
|
300
|
+
const _callee = this._expression(calleeNode);
|
|
301
|
+
const callee = mathToStd.has(_callee.value) ? snip(mathToStd.get(_callee.value), UnknownData, "runtime") : _callee;
|
|
302
|
+
if (supportedLogOps().includes(callee.value)) return this.ctx.generateLog(callee.value, argNodes.map((arg) => this._expression(arg)));
|
|
308
303
|
if (isWgslStruct(callee.value)) {
|
|
309
304
|
if (argNodes.length > 1) throw new WgslTypeError("Struct schemas should always be called with at most 1 argument");
|
|
310
305
|
if (!argNodes[0]) return snip(`${this.ctx.resolve(callee.value).value}()`, callee.value, "runtime");
|
|
@@ -324,14 +319,14 @@ ${this.ctx.pre}}`;
|
|
|
324
319
|
const rhs = this._expression(argNodes[0]);
|
|
325
320
|
return callee.value.operator(this.ctx, [callee.value.lhs, rhs]);
|
|
326
321
|
}
|
|
327
|
-
if (callee.value
|
|
322
|
+
if ((callee.value === _ref || callee.value === unroll) && argNodes[0]) this.tryMarkModified(argNodes[0]);
|
|
328
323
|
if (isGPUCallable(callee.value)) {
|
|
329
324
|
const callable = callee.value[$gpuCallable];
|
|
330
325
|
const strictSignature = callable.strictSignature;
|
|
331
326
|
let convertedArguments;
|
|
332
327
|
if (strictSignature) convertedArguments = argNodes.map((arg, i) => {
|
|
333
328
|
const argType = strictSignature.argTypes[i];
|
|
334
|
-
if (!argType) throw new WgslTypeError(`
|
|
329
|
+
if (!argType) throw new WgslTypeError(`Call '${stringifyNode(expression)}' is invalid since the function expected fewer arguments`);
|
|
335
330
|
return this._typedExpression(arg, argType);
|
|
336
331
|
});
|
|
337
332
|
else convertedArguments = argNodes.map((arg) => this._expression(arg));
|
|
@@ -361,6 +356,10 @@ ${this.ctx.pre}}`;
|
|
|
361
356
|
}));
|
|
362
357
|
if (shelllessCall) return shelllessCall;
|
|
363
358
|
}
|
|
359
|
+
const maybeMathMethod = Object.getOwnPropertyNames(Math).find((prop) => Math[prop] === callee.value);
|
|
360
|
+
if (maybeMathMethod) throw new Error(`Unsupported Math functionality 'Math.${maybeMathMethod}()'. Use an std alternative, or implement the function manually.`);
|
|
361
|
+
const maybeConsoleMethod = Object.getOwnPropertyNames(console).find((prop) => console[prop] === callee.value);
|
|
362
|
+
if (maybeConsoleMethod) throw new Error(`Unsupported console functionality 'console.${maybeConsoleMethod}()'.`);
|
|
364
363
|
throw new Error(`Function '${getName(callee.value) ?? String(callee.value)}' is not marked with the 'use gpu' directive and cannot be used in a shader`);
|
|
365
364
|
}
|
|
366
365
|
if (expression[0] === NODE.objectExpr) {
|
|
@@ -391,7 +390,7 @@ ${this.ctx.pre}}`;
|
|
|
391
390
|
const convertedSnippets = convertStructValues(this.ctx, structType, entries);
|
|
392
391
|
return snip(stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType, "runtime");
|
|
393
392
|
}
|
|
394
|
-
throw new WgslTypeError(`No target type could be inferred for object
|
|
393
|
+
throw new WgslTypeError(`No target type could be inferred for object '${stringifyNode(expression)}', please wrap the object in the corresponding schema.`);
|
|
395
394
|
}
|
|
396
395
|
if (expression[0] === NODE.arrayExpr) {
|
|
397
396
|
const [_, valueNodes] = expression;
|
|
@@ -406,7 +405,7 @@ ${this.ctx.pre}}`;
|
|
|
406
405
|
const valuesSnippets = valueNodes.map((value) => this._expression(value));
|
|
407
406
|
if (valuesSnippets.length === 0) throw new WgslTypeError("Cannot infer the type of an empty array literal.");
|
|
408
407
|
const converted = convertToCommonType(this.ctx, valuesSnippets);
|
|
409
|
-
if (!converted) throw new WgslTypeError(
|
|
408
|
+
if (!converted) throw new WgslTypeError(`Values '${stringifyNode(expression)}' cannot be automatically converted to a common type. Consider wrapping the array in an appropriate schema`);
|
|
410
409
|
values = converted;
|
|
411
410
|
elemType = concretize(values[0]?.dataType);
|
|
412
411
|
}
|
|
@@ -417,14 +416,21 @@ ${this.ctx.pre}}`;
|
|
|
417
416
|
const [_, test, consequent, alternative] = expression;
|
|
418
417
|
const testExpression = this._expression(test);
|
|
419
418
|
if (isKnownAtComptime(testExpression)) return testExpression.value ? this._expression(consequent) : this._expression(alternative);
|
|
420
|
-
else throw new Error(`Ternary operator is
|
|
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.`);
|
|
421
420
|
}
|
|
422
421
|
if (expression[0] === NODE.stringLiteral) return snip(expression[1], UnknownData, "constant");
|
|
423
422
|
if (expression[0] === NODE.preUpdate) throw new Error("Cannot use pre-updates in TypeGPU functions.");
|
|
424
423
|
assertExhaustive(expression);
|
|
425
424
|
}
|
|
426
425
|
functionDefinition(options) {
|
|
427
|
-
|
|
426
|
+
let body = this._block(options.body);
|
|
427
|
+
const scope = this.ctx.topFunctionScope;
|
|
428
|
+
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"]));
|
|
430
|
+
if (Object.keys(replacements).length > 0) {
|
|
431
|
+
const regex = new RegExp(Object.keys(replacements).join("|"), "gi");
|
|
432
|
+
body = body.replace(regex, (match) => replacements[match] ?? "#ERR");
|
|
433
|
+
}
|
|
428
434
|
const returnType = options.determineReturnType();
|
|
429
435
|
const argList = options.args.filter((arg) => arg.used || options.functionType === "normal").map((arg) => {
|
|
430
436
|
return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`;
|
|
@@ -448,10 +454,10 @@ ${this.ctx.pre}}`;
|
|
|
448
454
|
if (returnNode !== void 0) {
|
|
449
455
|
const expectedReturnType = this.ctx.topFunctionReturnType;
|
|
450
456
|
let returnSnippet = expectedReturnType ? this._typedExpression(returnNode, expectedReturnType) : this._expression(returnNode);
|
|
451
|
-
if (returnSnippet.value instanceof RefOperator) throw new WgslTypeError(
|
|
452
|
-
if (returnSnippet.origin === "argument" && !isNaturallyEphemeral(returnSnippet.dataType) && this.ctx.topFunctionScope?.functionType === "normal") throw new WgslTypeError(
|
|
457
|
+
if (returnSnippet.value instanceof RefOperator) throw new WgslTypeError(`Cannot return '${stringifyNode(returnNode)}' because it is a d.ref`);
|
|
458
|
+
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.`);
|
|
453
459
|
if (!expectedReturnType && !isEphemeralSnippet(returnSnippet) && returnSnippet.origin !== "this-function") {
|
|
454
|
-
const str =
|
|
460
|
+
const str = stringifyNode(returnNode);
|
|
455
461
|
const typeStr = this.ctx.resolve(unptr(returnSnippet.dataType)).value;
|
|
456
462
|
throw new WgslTypeError(`'return ${str};' is invalid, cannot return references.
|
|
457
463
|
-----
|
|
@@ -482,7 +488,7 @@ Try 'return ${typeStr}(${str});' instead.
|
|
|
482
488
|
if (!Array.isArray(node)) node = blockifySingleStatement(node);
|
|
483
489
|
if (node[0] === NODE.block && node[1].length === 1 && node[1][0][0] === NODE.if) return this._statement(node[1][0]);
|
|
484
490
|
if (node[0] === NODE.if) return this._statement(node);
|
|
485
|
-
return
|
|
491
|
+
return this._blockStatement(blockifySingleStatement(node));
|
|
486
492
|
}
|
|
487
493
|
const consequent = this._block(blockifySingleStatement(consNode));
|
|
488
494
|
const alternate = !altNode ? void 0 : this._block(blockifySingleStatement(altNode));
|
|
@@ -492,14 +498,13 @@ ${this.ctx.pre}if (${condition}) ${consequent}
|
|
|
492
498
|
${this.ctx.pre}else ${alternate}`;
|
|
493
499
|
}
|
|
494
500
|
if (statement[0] === NODE.let || statement[0] === NODE.const) {
|
|
495
|
-
let varType = "
|
|
501
|
+
let varType = "<deferred>";
|
|
496
502
|
const [stmtType, rawId, rawValue] = statement;
|
|
497
503
|
const eq = rawValue !== void 0 ? this._expression(rawValue) : void 0;
|
|
498
|
-
if (!eq) throw new Error(`
|
|
504
|
+
if (!eq) throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
|
|
499
505
|
const ephemeral = isEphemeralSnippet(eq);
|
|
500
506
|
let dataType = eq.dataType;
|
|
501
507
|
const naturallyEphemeral = isNaturallyEphemeral(dataType);
|
|
502
|
-
if (isLooseData(eq.dataType)) throw new Error(`Cannot create variable '${rawId}' with loose data type.`);
|
|
503
508
|
if (eq.value instanceof RefOperator) {
|
|
504
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.`);
|
|
505
510
|
const refSnippet = eq.value.snippet;
|
|
@@ -508,7 +513,7 @@ ${this.ctx.pre}else ${alternate}`;
|
|
|
508
513
|
}
|
|
509
514
|
if (!ephemeral) {
|
|
510
515
|
if (stmtType === NODE.let) {
|
|
511
|
-
const rhsStr$1 =
|
|
516
|
+
const rhsStr$1 = stringifyNode(rawValue ?? "");
|
|
512
517
|
const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
|
|
513
518
|
throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, because references cannot be assigned to 'let' variable declarations.
|
|
514
519
|
-----
|
|
@@ -525,14 +530,17 @@ ${this.ctx.pre}else ${alternate}`;
|
|
|
525
530
|
invariant(ptrType !== void 0, `Creating pointer type from origin ${eq.origin}`);
|
|
526
531
|
dataType = ptrType;
|
|
527
532
|
}
|
|
528
|
-
if (!(eq.value instanceof RefOperator))
|
|
533
|
+
if (!(eq.value instanceof RefOperator)) {
|
|
534
|
+
dataType = implicitFrom(dataType);
|
|
535
|
+
this.tryMarkModified(rawValue);
|
|
536
|
+
}
|
|
529
537
|
}
|
|
530
538
|
} else if (stmtType === NODE.const) {
|
|
531
539
|
if (eq.origin === "argument") varType = "let";
|
|
532
540
|
else if (naturallyEphemeral) varType = eq.origin === "constant" ? "const" : "let";
|
|
533
541
|
} else if (eq.origin === "argument") {
|
|
534
542
|
if (!naturallyEphemeral) {
|
|
535
|
-
const rhsStr$1 =
|
|
543
|
+
const rhsStr$1 = stringifyNode(rawValue ?? "");
|
|
536
544
|
const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
|
|
537
545
|
throw new WgslTypeError(`'let ${rawId} = ${rhsStr$1}' is invalid, because references to arguments cannot be assigned to 'let' variable declarations.
|
|
538
546
|
-----
|
|
@@ -544,9 +552,16 @@ ${this.ctx.pre}else ${alternate}`;
|
|
|
544
552
|
const snippet = this.blockVariable(varType, rawId, concretize(dataType), eq.origin);
|
|
545
553
|
const rhsSnippet = tryConvertSnippet(this.ctx, eq, dataType, false);
|
|
546
554
|
const rhsStr = this.ctx.resolve(rhsSnippet.value, rhsSnippet.dataType).value;
|
|
547
|
-
|
|
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);
|
|
548
563
|
}
|
|
549
|
-
if (statement[0] === NODE.block) return
|
|
564
|
+
if (statement[0] === NODE.block) return this._blockStatement(statement);
|
|
550
565
|
if (statement[0] === NODE.for) {
|
|
551
566
|
const [_, init, condition, update, body] = statement;
|
|
552
567
|
const prevUnrollingFlag = this.#unrolling;
|
|
@@ -583,6 +598,7 @@ ${this.ctx.pre}else ${alternate}`;
|
|
|
583
598
|
if (statement[0] === NODE.forOf) {
|
|
584
599
|
const [_, loopVar, iterable, body] = statement;
|
|
585
600
|
if (loopVar[0] !== NODE.const) throw new WgslTypeError("Only `for (const ... of ... )` loops are supported");
|
|
601
|
+
this.tryMarkModified(iterable);
|
|
586
602
|
let ctxIndent = false;
|
|
587
603
|
const prevUnrollingFlag = this.#unrolling;
|
|
588
604
|
try {
|
|
@@ -600,22 +616,22 @@ ${this.ctx.pre}else ${alternate}`;
|
|
|
600
616
|
if (length === 0) return "";
|
|
601
617
|
const { value } = iterableSnippet;
|
|
602
618
|
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")));
|
|
603
|
-
if (isEphemeralSnippet(elements[0]) && !isNaturallyEphemeral(elements[0]?.dataType)) throw new WgslTypeError(
|
|
604
|
-
return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this.
|
|
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.`);
|
|
620
|
+
return elements.map((e, i) => `${this.ctx.pre}// unrolled iteration #${i}\n${this._blockStatement(blockified, { [originalLoopVarName]: e })}`).join("\n");
|
|
605
621
|
}
|
|
606
622
|
this.#unrolling = false;
|
|
607
|
-
const index = this.ctx.
|
|
623
|
+
const index = this.ctx.makeUniqueIdentifier("i", "block");
|
|
608
624
|
const forHeaderStr = stitch`${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`;
|
|
609
625
|
let bodyStr = "";
|
|
610
626
|
if (isTgpuRange(iterableSnippet.value)) bodyStr = this._block(blockified, { [originalLoopVarName]: snip(index, range.start.dataType, "runtime") });
|
|
611
627
|
else {
|
|
612
628
|
this.ctx.indent();
|
|
613
629
|
ctxIndent = true;
|
|
614
|
-
const loopVarName = this.ctx.
|
|
630
|
+
const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, "block");
|
|
615
631
|
const elementSnippet = getElementSnippet(iterableSnippet, snip(index, u32, "runtime"));
|
|
616
632
|
const loopVarKind = getLoopVarKind(elementSnippet);
|
|
617
633
|
const elementType = getElementType(elementSnippet, iterableSnippet);
|
|
618
|
-
bodyStr = `{\n${stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`}\n${this.
|
|
634
|
+
bodyStr = `{\n${stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`}\n${this._blockStatement(blockified, { [originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin) })}\n`;
|
|
619
635
|
this.ctx.dedent();
|
|
620
636
|
bodyStr += `${this.ctx.pre}}`;
|
|
621
637
|
ctxIndent = false;
|
|
@@ -627,6 +643,14 @@ ${this.ctx.pre}else ${alternate}`;
|
|
|
627
643
|
this.ctx.popBlockScope();
|
|
628
644
|
}
|
|
629
645
|
}
|
|
646
|
+
if (statement[0] === NODE.postUpdate) {
|
|
647
|
+
const [_, op, arg] = statement;
|
|
648
|
+
const argExpr = this._expression(arg);
|
|
649
|
+
const argStr = this.ctx.resolve(argExpr.value, argExpr.dataType).value;
|
|
650
|
+
validateSnippetMutation(argExpr, statement);
|
|
651
|
+
this.tryMarkModified(arg);
|
|
652
|
+
return `${this.ctx.pre}${argStr}${op};`;
|
|
653
|
+
}
|
|
630
654
|
if (statement[0] === NODE.continue) {
|
|
631
655
|
if (this.#unrolling) throw new WgslTypeError("Cannot unroll loop containing `continue`");
|
|
632
656
|
return `${this.ctx.pre}continue;`;
|
|
@@ -639,7 +663,35 @@ ${this.ctx.pre}else ${alternate}`;
|
|
|
639
663
|
const resolved = expr.value && this.ctx.resolve(expr.value).value;
|
|
640
664
|
return resolved ? `${this.ctx.pre}${resolved};` : "";
|
|
641
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Attempts a member access lookup to mark a variable as modified.
|
|
668
|
+
* @example
|
|
669
|
+
* // given `let a; a = 1;`
|
|
670
|
+
* tryMarkModified('a') // `a` is marked in the function scope
|
|
671
|
+
*
|
|
672
|
+
* // given `const obj; obj.prop = 1;`
|
|
673
|
+
* tryMarkModified('obj.prop') // `obj` is marked in the function scope
|
|
674
|
+
*
|
|
675
|
+
* // given `this.buffer.$;`
|
|
676
|
+
* tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it
|
|
677
|
+
*/
|
|
678
|
+
tryMarkModified(expr) {
|
|
679
|
+
if (!expr) return;
|
|
680
|
+
const maybeObject = extractObject(expr);
|
|
681
|
+
if (maybeObject !== void 0) {
|
|
682
|
+
const snippet = this.ctx.getById(maybeObject);
|
|
683
|
+
const scope = this.ctx.topFunctionScope;
|
|
684
|
+
if (snippet && scope && scope.placeholderForVariable.has(snippet)) scope.modifiedVariables.add(snippet);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
642
687
|
};
|
|
688
|
+
function validateSnippetMutation(mutated, expr) {
|
|
689
|
+
if (mutated.origin === "constant" || mutated.origin === "constant-tgpu-const-ref" || mutated.origin === "runtime-tgpu-const-ref") {
|
|
690
|
+
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
|
+
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
|
|
692
|
+
}
|
|
693
|
+
if (mutated.origin === "argument") throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
|
|
694
|
+
}
|
|
643
695
|
function assertExhaustive(value) {
|
|
644
696
|
throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
|
|
645
697
|
}
|
|
@@ -651,6 +703,11 @@ function parseNumericString(str) {
|
|
|
651
703
|
function blockifySingleStatement(statement) {
|
|
652
704
|
return typeof statement !== "object" || statement[0] !== NODE.block ? [NODE.block, [statement]] : statement;
|
|
653
705
|
}
|
|
706
|
+
function extractObject(expr) {
|
|
707
|
+
let object = expr;
|
|
708
|
+
while (Array.isArray(object) && (object[0] === NODE.memberAccess || object[0] === NODE.indexAccess)) object = object[1];
|
|
709
|
+
if (typeof object === "string") return object;
|
|
710
|
+
}
|
|
654
711
|
const wgslGenerator = new WgslGenerator();
|
|
655
712
|
var wgslGenerator_default = wgslGenerator;
|
|
656
713
|
|
package/types.d.ts
CHANGED
|
@@ -70,6 +70,14 @@ type FunctionScopeLayer = {
|
|
|
70
70
|
* All types used in `return` statements.
|
|
71
71
|
*/
|
|
72
72
|
reportedReturnTypes: Set<BaseData>;
|
|
73
|
+
/**
|
|
74
|
+
* Maps variables to their modifier placeholders
|
|
75
|
+
*/
|
|
76
|
+
placeholderForVariable: Map<Snippet, string>;
|
|
77
|
+
/**
|
|
78
|
+
* Local variables that need `var` modifier.
|
|
79
|
+
*/
|
|
80
|
+
modifiedVariables: Set<Snippet>;
|
|
73
81
|
};
|
|
74
82
|
type SlotBindingLayer = {
|
|
75
83
|
type: 'slotBinding';
|
|
@@ -77,6 +85,7 @@ type SlotBindingLayer = {
|
|
|
77
85
|
};
|
|
78
86
|
type BlockScopeLayer = {
|
|
79
87
|
type: 'blockScope';
|
|
88
|
+
takenLocalIdentifiers: Set<string>;
|
|
80
89
|
declarations: Map<string, Snippet>;
|
|
81
90
|
externals: Map<string, Snippet>;
|
|
82
91
|
};
|
|
@@ -84,6 +93,7 @@ type StackLayer = ItemLayer | SlotBindingLayer | FunctionScopeLayer | BlockScope
|
|
|
84
93
|
interface ItemStateStack {
|
|
85
94
|
readonly itemDepth: number;
|
|
86
95
|
readonly topItem: ItemLayer;
|
|
96
|
+
readonly topBlockScope: BlockScopeLayer | undefined;
|
|
87
97
|
readonly topFunctionScope: FunctionScopeLayer | undefined;
|
|
88
98
|
pushItem(): void;
|
|
89
99
|
pushSlotBindings(pairs: SlotValuePair[]): void;
|
|
@@ -237,8 +247,25 @@ interface ResolutionCtx {
|
|
|
237
247
|
* @param name the temporary name to assign to the item (if missing, just returns `callback()`)
|
|
238
248
|
*/
|
|
239
249
|
withRenamed<T>(item: object, name: string | undefined, callback: () => T): T;
|
|
240
|
-
|
|
241
|
-
|
|
250
|
+
/**
|
|
251
|
+
* @param primer The basis for the unique identifier. Depending on the strategy, or
|
|
252
|
+
* the names already taken, this may be modified to ensure uniqueness.
|
|
253
|
+
* @param scope The scope in which to generate the identifier. 'global' means
|
|
254
|
+
* the identifier is meant to be unique across the entire program, while
|
|
255
|
+
* 'block' means it cannot shadow any existing identifiers visible from
|
|
256
|
+
* within the current block. After the block is popped, any identifiers
|
|
257
|
+
* defined within it are no longer visible.
|
|
258
|
+
* @returns an identifier that is unique within the given scope
|
|
259
|
+
*/
|
|
260
|
+
makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string;
|
|
261
|
+
isIdentifierTaken(name: string): boolean;
|
|
262
|
+
/**
|
|
263
|
+
* Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier}
|
|
264
|
+
* within the given scope.
|
|
265
|
+
* @param name The name to reserve
|
|
266
|
+
* @param scope See {@link makeUniqueIdentifier} for a description of the scope parameter.
|
|
267
|
+
*/
|
|
268
|
+
reserveIdentifier(name: string, scope: 'global' | 'block'): void;
|
|
242
269
|
}
|
|
243
270
|
/**
|
|
244
271
|
* Houses a method on the symbol '$resolve` that returns a
|
package/nameRegistry.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
//#region src/nameRegistry.d.ts
|
|
2
|
-
interface NameRegistry {
|
|
3
|
-
/**
|
|
4
|
-
* Creates a valid WGSL identifier, each guaranteed to be unique
|
|
5
|
-
* in the lifetime of a single resolution process
|
|
6
|
-
* (excluding non-global identifiers from popped scopes).
|
|
7
|
-
* Should append "_" to primer, followed by some id.
|
|
8
|
-
* @param primer Used in the generation process, makes the identifier more recognizable.
|
|
9
|
-
* @param global Whether the name should be registered in the global scope (true), or in the current function scope (false)
|
|
10
|
-
*/
|
|
11
|
-
makeUnique(primer: string | undefined, global: boolean): string;
|
|
12
|
-
/**
|
|
13
|
-
* Creates a valid WGSL identifier.
|
|
14
|
-
* Renames identifiers that are WGSL reserved words.
|
|
15
|
-
* @param primer Used in the generation process.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* makeValid("notAKeyword"); // "notAKeyword"
|
|
19
|
-
* makeValid("struct"); // makeUnique("struct")
|
|
20
|
-
* makeValid("struct_1"); // makeUnique("struct_1") (to avoid potential name collisions)
|
|
21
|
-
* makeValid("_"); // ERROR (too difficult to make valid to care)
|
|
22
|
-
*/
|
|
23
|
-
makeValid(primer: string): string;
|
|
24
|
-
pushFunctionScope(): void;
|
|
25
|
-
popFunctionScope(): void;
|
|
26
|
-
pushBlockScope(): void;
|
|
27
|
-
popBlockScope(): void;
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
export { NameRegistry };
|
package/tgsl/consoleLog/types.js
DELETED
package/tgsl/math.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { f32 } from "../data/numeric.js";
|
|
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
|
-
//#region src/tgsl/math.ts
|
|
5
|
-
const mathToStd = {
|
|
6
|
-
abs,
|
|
7
|
-
acos,
|
|
8
|
-
acosh,
|
|
9
|
-
asin,
|
|
10
|
-
asinh,
|
|
11
|
-
atan,
|
|
12
|
-
atan2,
|
|
13
|
-
atanh,
|
|
14
|
-
ceil,
|
|
15
|
-
cos,
|
|
16
|
-
cosh,
|
|
17
|
-
exp,
|
|
18
|
-
floor,
|
|
19
|
-
fround: f32,
|
|
20
|
-
clz32: countLeadingZeros,
|
|
21
|
-
trunc,
|
|
22
|
-
log,
|
|
23
|
-
log2,
|
|
24
|
-
pow,
|
|
25
|
-
sign,
|
|
26
|
-
sin,
|
|
27
|
-
sinh,
|
|
28
|
-
sqrt,
|
|
29
|
-
tan,
|
|
30
|
-
tanh,
|
|
31
|
-
max,
|
|
32
|
-
min,
|
|
33
|
-
cbrt: void 0,
|
|
34
|
-
log10: void 0,
|
|
35
|
-
log1p: void 0,
|
|
36
|
-
f16round: void 0,
|
|
37
|
-
hypot: void 0,
|
|
38
|
-
expm1: void 0,
|
|
39
|
-
random: void 0,
|
|
40
|
-
imul: void 0,
|
|
41
|
-
round: void 0
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
//#endregion
|
|
45
|
-
export { mathToStd };
|