typegpu 0.12.7 → 0.12.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/function/autoIO.js +2 -2
- package/core/function/comptime.js +7 -5
- package/core/function/createCallableSchema.js +4 -3
- package/core/function/dualImpl.js +20 -19
- package/core/function/ioSchema.d.ts +10 -0
- package/core/function/ioSchema.js +37 -16
- package/core/function/tgpuFn.d.ts +11 -1
- package/core/function/tgpuFn.js +9 -2
- package/core/pipeline/renderPipeline.d.ts +4 -2
- package/core/pipeline/renderPipeline.js +12 -10
- package/core/pipeline/timeable.js +15 -2
- package/core/root/configurableImpl.js +1 -2
- package/core/texture/externalTexture.d.ts +1 -1
- package/core/texture/externalTexture.js +3 -1
- package/core/texture/texture.d.ts +1 -1
- package/core/texture/texture.js +4 -1
- package/core/unroll/tgpuUnroll.d.ts +4 -3
- package/core/unroll/tgpuUnroll.js +7 -1
- package/core/valueProxyUtils.d.ts +4 -2
- package/core/valueProxyUtils.js +4 -2
- package/data/compiledIO.js +12 -9
- package/data/dataIO.d.ts +10 -6
- package/data/dataIO.js +19 -4
- package/data/dataTypes.d.ts +0 -5
- package/data/dataTypes.js +0 -6
- package/data/matrixColumnsAccess.d.ts +8 -0
- package/data/matrixColumnsAccess.js +15 -0
- package/data/offsetUtils.js +181 -102
- package/data/ref.d.ts +12 -5
- package/data/ref.js +50 -23
- package/data/snippet.d.ts +7 -0
- package/data/snippet.js +9 -0
- package/data/struct.js +3 -0
- package/data/wgslTypes.d.ts +3 -3
- package/package.json +1 -1
- package/resolutionCtx.js +1 -1
- package/shared/meta.js +1 -1
- package/shared/symbols.d.ts +5 -0
- package/shared/symbols.js +6 -1
- package/std/numeric.js +1 -4
- package/std/packing.js +10 -10
- package/tgpuLogger.d.ts +1 -1
- package/tgpuLogger.js +0 -2
- package/tgsl/accessIndex.js +18 -15
- package/tgsl/accessProp.js +10 -7
- package/tgsl/conversion.js +3 -2
- package/tgsl/forOfUtils.js +2 -2
- package/tgsl/generationHelpers.d.ts +4 -3
- package/tgsl/generationHelpers.js +16 -3
- package/tgsl/infixDispatch.d.ts +4 -1
- package/tgsl/infixDispatch.js +13 -1
- package/tgsl/makeDereferenceable.d.ts +1 -1
- package/tgsl/makeDereferenceable.js +3 -1
- package/tgsl/shellless.js +5 -9
- package/tgsl/stringifySnippet.d.ts +9 -0
- package/tgsl/stringifySnippet.js +24 -0
- package/tgsl/wgslGenerator.d.ts +1 -0
- package/tgsl/wgslGenerator.js +98 -66
- package/types.d.ts +23 -2
- package/types.js +30 -5
package/tgsl/wgslGenerator.js
CHANGED
|
@@ -4,7 +4,7 @@ import { arrayOf } from "../data/array.js";
|
|
|
4
4
|
import { UnknownData, unptr } from "../data/dataTypes.js";
|
|
5
5
|
import { bool, i32, u32 } from "../data/numeric.js";
|
|
6
6
|
import { vec2u, vec3u, vec4u } from "../data/vector.js";
|
|
7
|
-
import { fallthroughCopyOrigin, isStoredInMemory, snip, } from "../data/snippet.js";
|
|
7
|
+
import { fallthroughCopyOrigin, isMutableOrigin, isStoredInMemory, snip, } from "../data/snippet.js";
|
|
8
8
|
import * as wgsl from "../data/wgslTypes.js";
|
|
9
9
|
import { invariant, ResolutionError, WgslForbiddenStatementError, WgslTypeError, } from "../errors.js";
|
|
10
10
|
import { getName } from "../shared/meta.js";
|
|
@@ -13,7 +13,7 @@ import { safeStringify } from "../shared/stringify.js";
|
|
|
13
13
|
import { pow } from "../std/numeric.js";
|
|
14
14
|
import { add, div, mul, neg, sub } from "../std/operators.js";
|
|
15
15
|
import { eq, ne, lt, le, gt, ge, not } from "../std/boolean.js";
|
|
16
|
-
import { isConstant, isGPUCallable,
|
|
16
|
+
import { asComptime, isConstant, isGPUCallable, } from "../types.js";
|
|
17
17
|
import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./conversion.js";
|
|
18
18
|
import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet, } from "./generationHelpers.js";
|
|
19
19
|
import { accessIndex } from "./accessIndex.js";
|
|
@@ -29,6 +29,7 @@ import { mathToStd, supportedLogOps } from "./jsPolyfills.js";
|
|
|
29
29
|
import * as forOfUtils from "./forOfUtils.js";
|
|
30
30
|
import { isTgpuRange } from "../std/range.js";
|
|
31
31
|
import { stringifyNode, stringifyObjectProperty } from "../shared/tseynit.js";
|
|
32
|
+
import { setSourceNode, stringifySnippet } from "./stringifySnippet.js";
|
|
32
33
|
import { getAttributesString } from "../data/attributes.js";
|
|
33
34
|
import { validSelectBranchTypes } from "../std/boolean.js";
|
|
34
35
|
import { isInfixDispatch } from "./infixDispatch.js";
|
|
@@ -127,12 +128,13 @@ const unaryOpCodeToCodegen = {
|
|
|
127
128
|
if (argExpr === undefined) {
|
|
128
129
|
throw new Error('The unary operator `!` expects 1 argument, but 0 were provided.');
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
const knownArg = asComptime(argExpr);
|
|
132
|
+
if (knownArg) {
|
|
133
|
+
return snip(!knownArg.value, bool, 'constant', false);
|
|
132
134
|
}
|
|
133
135
|
const argStr = ctx.resolveSnippet(argExpr).value;
|
|
134
136
|
if (!wgsl.isBool(argExpr.dataType)) {
|
|
135
|
-
throw new WgslTypeError(`Unary operator ! requires boolean operand. Got ${String(argExpr.dataType)}.${wgsl.isVecBool(argExpr.dataType)
|
|
137
|
+
throw new WgslTypeError(`Unary operator ! requires boolean operand. Got '${stringifySnippet(argExpr)}' of type ${String(argExpr.dataType)}.${wgsl.isVecBool(argExpr.dataType)
|
|
136
138
|
? ` For component-wise negation, use 'std.${not.toString()}'.`
|
|
137
139
|
: ''}`);
|
|
138
140
|
}
|
|
@@ -279,25 +281,41 @@ export class WgslGenerator {
|
|
|
279
281
|
return res;
|
|
280
282
|
}
|
|
281
283
|
_emitSwitchStatement(discriminantExpr, groupedCaseExprs) {
|
|
282
|
-
if (groupedCaseExprs.flatMap(([tests]) => tests).every((test) => test.value !== 'default')) {
|
|
283
|
-
// default clause is required in WGSL
|
|
284
|
-
groupedCaseExprs.push([[switchDefault], []]);
|
|
285
|
-
}
|
|
286
|
-
this.ctx.indent();
|
|
287
284
|
const cases = groupedCaseExprs.map(([tests, consequent]) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.join(', ');
|
|
291
|
-
// Purely cosmetic break pruning (it is legal, but there's no need to generate it).
|
|
285
|
+
// It is legal to generate a break at the end of a consequent, but there is no need to.
|
|
286
|
+
// Also, removing this lets us simplify more pruned switch statements to blocks.
|
|
292
287
|
const last = consequent.at(-1);
|
|
293
288
|
if (last && /^\s*break\s*;\s*$/.test(last.code) && last.endsWithControlFlow === 'break') {
|
|
294
289
|
consequent.pop();
|
|
295
290
|
}
|
|
296
291
|
const resolvedConsequent = consequent.map((s) => s.code).join('\n');
|
|
297
|
-
return
|
|
292
|
+
return { tests, resolvedConsequent };
|
|
293
|
+
});
|
|
294
|
+
if (cases.flatMap(({ tests }) => tests).every((test) => test !== switchDefault)) {
|
|
295
|
+
// default clause is required in WGSL
|
|
296
|
+
cases.push({ tests: [switchDefault], resolvedConsequent: '' });
|
|
297
|
+
}
|
|
298
|
+
// If the switch was pruned, and it has no breaks, we can simplify it to a block.
|
|
299
|
+
const onlyCase = cases.length === 1 ? cases.at(0) : undefined;
|
|
300
|
+
if (asComptime(discriminantExpr) !== undefined &&
|
|
301
|
+
onlyCase &&
|
|
302
|
+
onlyCase.tests.length === 1 &&
|
|
303
|
+
onlyCase.tests.at(0) === switchDefault) {
|
|
304
|
+
const { resolvedConsequent } = onlyCase;
|
|
305
|
+
if (!/\bbreak\b/.test(resolvedConsequent)) {
|
|
306
|
+
return `${this.ctx.pre}{\n${this.ctx.getDedented(resolvedConsequent)}\n${this.ctx.pre}}`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.ctx.indent();
|
|
310
|
+
const resolvedCases = cases.map(({ tests, resolvedConsequent }) => {
|
|
311
|
+
const resolvedTests = tests
|
|
312
|
+
.map((test) => (test.value === 'default' ? 'default' : this.ctx.resolveSnippet(test).value))
|
|
313
|
+
.join(', ');
|
|
314
|
+
return `${this.ctx.pre}case ${resolvedTests}: {\n${resolvedConsequent}\n${this.ctx.pre}}`;
|
|
298
315
|
});
|
|
299
316
|
this.ctx.dedent();
|
|
300
|
-
|
|
317
|
+
const body = resolvedCases.join('\n');
|
|
318
|
+
return stitch `${this.ctx.pre}switch ${discriminantExpr} {\n${body}\n${this.ctx.pre}}`;
|
|
301
319
|
}
|
|
302
320
|
_callShellless(callee, args) {
|
|
303
321
|
const isGeneric = isGenericFn(callee);
|
|
@@ -335,13 +353,17 @@ export class WgslGenerator {
|
|
|
335
353
|
// convert the result.
|
|
336
354
|
return result;
|
|
337
355
|
}
|
|
338
|
-
return tryConvertSnippet(this.ctx, result, expectedType);
|
|
356
|
+
return setSourceNode(tryConvertSnippet(this.ctx, result, expectedType), expression);
|
|
339
357
|
}
|
|
340
358
|
finally {
|
|
341
359
|
this.ctx.expectedType = prevExpectedType;
|
|
342
360
|
}
|
|
343
361
|
}
|
|
344
362
|
_expression(expression) {
|
|
363
|
+
// Remembering which JS expression produced the snippet, for more descriptive errors
|
|
364
|
+
return setSourceNode(this._expressionImpl(expression), expression);
|
|
365
|
+
}
|
|
366
|
+
_expressionImpl(expression) {
|
|
345
367
|
if (isId(expression)) {
|
|
346
368
|
return this._identifier(extractId(expression));
|
|
347
369
|
}
|
|
@@ -352,21 +374,23 @@ export class WgslGenerator {
|
|
|
352
374
|
const [_, lhs, op, rhs] = expression;
|
|
353
375
|
const lhsExpr = this._expression(lhs);
|
|
354
376
|
// Short Circuit Evaluation
|
|
355
|
-
|
|
377
|
+
const knownLhs = asComptime(lhsExpr);
|
|
378
|
+
if (knownLhs) {
|
|
356
379
|
const castToBool = wgsl.isBool(this.ctx.expectedType);
|
|
357
|
-
const evalRhs = op === '&&' ?
|
|
380
|
+
const evalRhs = op === '&&' ? knownLhs.value : !knownLhs.value;
|
|
358
381
|
if (!evalRhs) {
|
|
359
382
|
return castToBool
|
|
360
383
|
? snip(op === '||', bool, 'constant', false)
|
|
361
|
-
: coerceToSnippet(
|
|
384
|
+
: coerceToSnippet(knownLhs.value);
|
|
362
385
|
}
|
|
363
386
|
const rhsExpr = this._expression(rhs);
|
|
364
|
-
|
|
365
|
-
|
|
387
|
+
const knownRhs = asComptime(rhsExpr);
|
|
388
|
+
if (knownRhs) {
|
|
389
|
+
const rhsSnippet = coerceToSnippet(knownRhs.value);
|
|
366
390
|
return castToBool ? tryConvertSnippet(this.ctx, rhsSnippet, bool, false) : rhsSnippet;
|
|
367
391
|
}
|
|
368
392
|
if (rhsExpr.dataType === UnknownData) {
|
|
369
|
-
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type
|
|
393
|
+
throw new WgslTypeError(`Right-hand side '${stringifySnippet(rhsExpr)}' of '${op}' is of unknown type.`);
|
|
370
394
|
}
|
|
371
395
|
// we can skip lhs
|
|
372
396
|
return castToBool ? tryConvertSnippet(this.ctx, rhsExpr, bool, false) : rhsExpr;
|
|
@@ -374,13 +398,13 @@ export class WgslGenerator {
|
|
|
374
398
|
const rhsExpr = this._expression(rhs);
|
|
375
399
|
// they are not known at comptime
|
|
376
400
|
if (lhsExpr.dataType === UnknownData) {
|
|
377
|
-
throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type
|
|
401
|
+
throw new WgslTypeError(`Left-hand side '${stringifySnippet(lhsExpr)}' of '${op}' is of unknown type.`);
|
|
378
402
|
}
|
|
379
|
-
if (!
|
|
380
|
-
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type
|
|
403
|
+
if (!asComptime(rhsExpr) && rhsExpr.dataType === UnknownData) {
|
|
404
|
+
throw new WgslTypeError(`Right-hand side '${stringifySnippet(rhsExpr)}' of '${op}' is of unknown type.`);
|
|
381
405
|
}
|
|
382
406
|
if (!wgsl.isBool(lhsExpr.dataType) || !wgsl.isBool(rhsExpr.dataType)) {
|
|
383
|
-
throw new WgslTypeError(`Logical expression '${
|
|
407
|
+
throw new WgslTypeError(`Logical expression '${stringifyNode(expression)}' requires boolean operands. Got '${String(lhsExpr.dataType)}' and '${String(rhsExpr.dataType)}'.`);
|
|
384
408
|
}
|
|
385
409
|
const lhsStr = this.ctx.resolveSnippet(lhsExpr).value;
|
|
386
410
|
const rhsStr = this.ctx.resolveSnippet(rhsExpr).value;
|
|
@@ -393,7 +417,7 @@ export class WgslGenerator {
|
|
|
393
417
|
const lhsExpr = this._expression(lhs);
|
|
394
418
|
const rhsExpr = this._expression(rhs);
|
|
395
419
|
if (rhsExpr.value instanceof RefOperator) {
|
|
396
|
-
throw new WgslTypeError(
|
|
420
|
+
throw new WgslTypeError(`Cannot assign a ref to an existing variable '${stringifyNode(lhs)}', define a new variable instead.`);
|
|
397
421
|
}
|
|
398
422
|
if (op === '==') {
|
|
399
423
|
throw new Error('Please use the === operator instead of ==');
|
|
@@ -402,9 +426,11 @@ export class WgslGenerator {
|
|
|
402
426
|
throw new Error('Please use the !== operator instead of !=');
|
|
403
427
|
}
|
|
404
428
|
const stdBinaryRelationalOp = binaryRelationalOpToStdMap[op];
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
429
|
+
const knownLhs = stdBinaryRelationalOp && asComptime(lhsExpr);
|
|
430
|
+
const knownRhs = knownLhs && asComptime(rhsExpr);
|
|
431
|
+
if (stdBinaryRelationalOp && knownLhs && knownRhs) {
|
|
432
|
+
const left = knownLhs.value;
|
|
433
|
+
const right = knownRhs.value;
|
|
408
434
|
switch (op) {
|
|
409
435
|
case '===':
|
|
410
436
|
return snip(left === right, bool, 'constant', false);
|
|
@@ -413,7 +439,7 @@ export class WgslGenerator {
|
|
|
413
439
|
}
|
|
414
440
|
if (typeof left !== 'number' || typeof right !== 'number') {
|
|
415
441
|
const bothVectors = wgsl.isVec(lhsExpr.dataType) && wgsl.isVec(rhsExpr.dataType);
|
|
416
|
-
throw new WgslTypeError(`Comparison '${
|
|
442
|
+
throw new WgslTypeError(`Comparison '${stringifyNode(expression)}' requires numeric operands.${bothVectors
|
|
417
443
|
? ` For component-wise comparison, use 'std.${stdBinaryRelationalOp}'.`
|
|
418
444
|
: ''}`);
|
|
419
445
|
}
|
|
@@ -429,10 +455,10 @@ export class WgslGenerator {
|
|
|
429
455
|
}
|
|
430
456
|
}
|
|
431
457
|
if (lhsExpr.dataType === UnknownData) {
|
|
432
|
-
throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type
|
|
458
|
+
throw new WgslTypeError(`Left-hand side '${stringifySnippet(lhsExpr)}' of '${op}' is of unknown type.`);
|
|
433
459
|
}
|
|
434
460
|
if (rhsExpr.dataType === UnknownData) {
|
|
435
|
-
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type
|
|
461
|
+
throw new WgslTypeError(`Right-hand side '${stringifySnippet(rhsExpr)}' of '${op}' is of unknown type.`);
|
|
436
462
|
}
|
|
437
463
|
const codegen = binaryOpCodeToCodegen[op];
|
|
438
464
|
if (codegen) {
|
|
@@ -488,7 +514,7 @@ export class WgslGenerator {
|
|
|
488
514
|
(equalityCheck && wgsl.isBool(convLhs.dataType) && wgsl.isBool(convRhs.dataType));
|
|
489
515
|
if (!correctOperandTypes) {
|
|
490
516
|
const bothVectors = wgsl.isVec(convLhs.dataType) && wgsl.isVec(convRhs.dataType);
|
|
491
|
-
throw new WgslTypeError(`Comparison '${
|
|
517
|
+
throw new WgslTypeError(`Comparison '${stringifyNode(expression)}' requires numeric${equalityCheck ? ' or boolean' : ''} operands. Got '${String(convLhs.dataType)}' and '${String(convRhs.dataType)}'.${bothVectors
|
|
492
518
|
? ` For component-wise comparison, use 'std.${stdBinaryRelationalOp}'.`
|
|
493
519
|
: ''}`);
|
|
494
520
|
}
|
|
@@ -662,7 +688,7 @@ export class WgslGenerator {
|
|
|
662
688
|
if (maybeConsoleMethod) {
|
|
663
689
|
throw new Error(`Unsupported console functionality 'console.${maybeConsoleMethod}()'.`);
|
|
664
690
|
}
|
|
665
|
-
throw new Error(`Function '${getName(callee.value) ??
|
|
691
|
+
throw new Error(`Function '${getName(callee.value) ?? stringifySnippet(callee)}' is not marked with the 'use gpu' directive and cannot be used in a shader`);
|
|
666
692
|
}
|
|
667
693
|
if (expression[0] === NODE.objectExpr) {
|
|
668
694
|
// Normalize to `objectProperty[]`
|
|
@@ -694,7 +720,7 @@ export class WgslGenerator {
|
|
|
694
720
|
// Generating the expression and inferring the type instead
|
|
695
721
|
expr = this._expression(value);
|
|
696
722
|
if (expr.dataType === UnknownData) {
|
|
697
|
-
throw new WgslTypeError(
|
|
723
|
+
throw new WgslTypeError(`Value '${stringifySnippet(expr)}' of property '${key}' in object literal is of unknown type.`);
|
|
698
724
|
}
|
|
699
725
|
// Taking care of abstract numerics and implicit pointers
|
|
700
726
|
accessed = structType.provideProp(key, unptr(concretize(expr.dataType)));
|
|
@@ -789,8 +815,11 @@ The generated shader will evaluate them in declaration order.`);
|
|
|
789
815
|
// ternary operator
|
|
790
816
|
const [_, testNode, consequentNode, alternativeNode] = expression;
|
|
791
817
|
const test = this._expression(testNode);
|
|
792
|
-
|
|
793
|
-
|
|
818
|
+
const knownTest = asComptime(test);
|
|
819
|
+
if (knownTest) {
|
|
820
|
+
return knownTest.value
|
|
821
|
+
? this._expression(consequentNode)
|
|
822
|
+
: this._expression(alternativeNode);
|
|
794
823
|
}
|
|
795
824
|
else {
|
|
796
825
|
const convertedTest = tryConvertSnippet(this.ctx, test, bool, false);
|
|
@@ -1055,9 +1084,11 @@ Try 'return ${typeStr}(${str});' instead.
|
|
|
1055
1084
|
const eq = this._expression(eqNode);
|
|
1056
1085
|
if (eq.value instanceof RefOperator) {
|
|
1057
1086
|
// We're assigning a newly created `d.ref()`
|
|
1058
|
-
if (eq.
|
|
1059
|
-
|
|
1087
|
+
if (eq.value.addressable) {
|
|
1088
|
+
const refStr = stringifyNode(eqNode);
|
|
1089
|
+
throw new WgslTypeError(`'const ${rawId} = ${refStr}' is invalid, cannot store ${refStr} in a variable if it references another value. Copy the value passed into d.ref() instead, or inline ${refStr}.`);
|
|
1060
1090
|
}
|
|
1091
|
+
// Unwrapping the ref, and storing the value it was created from in a new variable
|
|
1061
1092
|
const refSnippet = eq.value.snippet;
|
|
1062
1093
|
const varName = this.refVariable(rawId, concretize(refSnippet.dataType));
|
|
1063
1094
|
return {
|
|
@@ -1287,7 +1318,8 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1287
1318
|
const originalLoopVarName = extractId(loopVar[1]);
|
|
1288
1319
|
const blockified = blockifySingleStatement(body);
|
|
1289
1320
|
if (shouldUnroll) {
|
|
1290
|
-
|
|
1321
|
+
const knownEnd = asComptime(range.end);
|
|
1322
|
+
if (!knownEnd) {
|
|
1291
1323
|
throw new Error('Cannot unroll loop. Length of iterable is unknown at comptime.');
|
|
1292
1324
|
}
|
|
1293
1325
|
const { value } = iterableSnippet;
|
|
@@ -1295,7 +1327,7 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1295
1327
|
? value.map((i) => coerceToSnippet(i))
|
|
1296
1328
|
: value instanceof ArrayExpression
|
|
1297
1329
|
? value.elements
|
|
1298
|
-
: Array.from({ length:
|
|
1330
|
+
: Array.from({ length: knownEnd.value }, (_, i) => forOfUtils.getElementSnippet(iterableSnippet, snip(i, u32, 'constant')));
|
|
1299
1331
|
if (elements.length === 0) {
|
|
1300
1332
|
return { code: '', definesInNearestScope: false };
|
|
1301
1333
|
}
|
|
@@ -1373,8 +1405,10 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1373
1405
|
});
|
|
1374
1406
|
// comptime folding
|
|
1375
1407
|
let matchedCaseWasNotLast = false;
|
|
1376
|
-
|
|
1377
|
-
|
|
1408
|
+
const knownDiscriminant = asComptime(discriminantExpr);
|
|
1409
|
+
const knownTests = knownDiscriminant && caseExprs.map(([test]) => asComptime(test));
|
|
1410
|
+
if (knownDiscriminant && knownTests?.every((test) => test !== undefined)) {
|
|
1411
|
+
let matchedCaseIndex = knownTests.findIndex((test) => test !== switchDefault && test?.value === knownDiscriminant.value);
|
|
1378
1412
|
if (matchedCaseIndex === -1) {
|
|
1379
1413
|
matchedCaseIndex = caseExprs.findIndex(([test]) => test === switchDefault);
|
|
1380
1414
|
}
|
|
@@ -1517,8 +1551,8 @@ ${stringifyNode(statement)}`);
|
|
|
1517
1551
|
if (!computed) {
|
|
1518
1552
|
return property[0];
|
|
1519
1553
|
}
|
|
1520
|
-
const key = this._expression(property[0]);
|
|
1521
|
-
if (!
|
|
1554
|
+
const key = asComptime(this._expression(property[0]));
|
|
1555
|
+
if (!key) {
|
|
1522
1556
|
throw new WgslTypeError(`Computed object property key '${stringifyObjectProperty(property)}' must be known at comptime.`);
|
|
1523
1557
|
}
|
|
1524
1558
|
if (typeof key.value !== 'string') {
|
|
@@ -1552,27 +1586,25 @@ ${stringifyNode(statement)}`);
|
|
|
1552
1586
|
}
|
|
1553
1587
|
}
|
|
1554
1588
|
}
|
|
1589
|
+
const immutabilityReasons = {
|
|
1590
|
+
constant: 'the left side is a constant',
|
|
1591
|
+
'constant-immutable-def': 'the left side is a constant',
|
|
1592
|
+
'runtime-immutable-def': 'the left side is immutable',
|
|
1593
|
+
uniform: 'uniform buffers cannot be mutated',
|
|
1594
|
+
readonly: 'readonly buffers cannot be mutated',
|
|
1595
|
+
immediate: 'immediate variables cannot be mutated',
|
|
1596
|
+
argument: 'non-pointer arguments cannot be mutated',
|
|
1597
|
+
handle: 'textures and samplers cannot be mutated',
|
|
1598
|
+
};
|
|
1555
1599
|
function validateSnippetMutation(mutated, expr) {
|
|
1556
|
-
if (mutated.origin
|
|
1557
|
-
|
|
1558
|
-
mutated.origin === 'runtime-immutable-def') {
|
|
1559
|
-
if (isKnownAtComptime(mutated)) {
|
|
1560
|
-
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.`);
|
|
1561
|
-
}
|
|
1562
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
|
|
1563
|
-
}
|
|
1564
|
-
if (mutated.origin === 'uniform') {
|
|
1565
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because uniform buffers cannot be mutated.`);
|
|
1566
|
-
}
|
|
1567
|
-
if (mutated.origin === 'readonly') {
|
|
1568
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because readonly buffers cannot be mutated.`);
|
|
1569
|
-
}
|
|
1570
|
-
if (mutated.origin === 'immediate') {
|
|
1571
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because immediate variables cannot be mutated.`);
|
|
1572
|
-
}
|
|
1573
|
-
if (mutated.origin === 'argument') {
|
|
1574
|
-
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
|
|
1600
|
+
if (isMutableOrigin(mutated.origin)) {
|
|
1601
|
+
return;
|
|
1575
1602
|
}
|
|
1603
|
+
const reason = asComptime(mutated)
|
|
1604
|
+
? 'the left side is immutable during shader execution. Try using tgpu.privateVar or buffers'
|
|
1605
|
+
: (immutabilityReasons[mutated.origin] ??
|
|
1606
|
+
'the left side is not a reference to an existing value');
|
|
1607
|
+
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because ${reason}.`);
|
|
1576
1608
|
}
|
|
1577
1609
|
function assertExhaustive(value) {
|
|
1578
1610
|
throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
|
package/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { TgpuVar } from './core/variable/tgpuVariable.ts';
|
|
|
18
18
|
import { type AnyData, UnknownData } from './data/dataTypes.ts';
|
|
19
19
|
import type { MapValueToSnippet, ResolvedSnippet, Snippet } from './data/snippet.ts';
|
|
20
20
|
import { type AnyMatInstance, type AnyVecInstance, type BaseData } from './data/wgslTypes.ts';
|
|
21
|
-
import { $cast, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve } from './shared/symbols.ts';
|
|
21
|
+
import { $cast, $comptimeValueOf, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve } from './shared/symbols.ts';
|
|
22
22
|
import type { TgpuBindGroupLayout, TgpuLayoutEntry } from './tgpuBindGroupLayout.ts';
|
|
23
23
|
import type { WgslEnableExtension } from './wgslExtensions.ts';
|
|
24
24
|
import type { Infer } from './shared/repr.ts';
|
|
@@ -342,6 +342,23 @@ export interface WithOwnSnippet {
|
|
|
342
342
|
readonly [$ownSnippet]: Snippet;
|
|
343
343
|
}
|
|
344
344
|
export declare function getOwnSnippet(value: unknown): Snippet | undefined;
|
|
345
|
+
export type ComptimeValue = {
|
|
346
|
+
comptime: false;
|
|
347
|
+
} | {
|
|
348
|
+
comptime: true;
|
|
349
|
+
value: unknown;
|
|
350
|
+
};
|
|
351
|
+
export interface WithComptimeValue {
|
|
352
|
+
/**
|
|
353
|
+
* @returns The value that this object represents at comptime,
|
|
354
|
+
* or `{ comptime: false }` if it is only known at runtime.
|
|
355
|
+
*/
|
|
356
|
+
[$comptimeValueOf](): ComptimeValue;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* A `[$comptimeValueOf]` implementation for resources that are only known at runtime
|
|
360
|
+
*/
|
|
361
|
+
export declare function runtimeOnly(): ComptimeValue;
|
|
345
362
|
export interface GPUCallable<TArgs extends unknown[] = unknown[]> {
|
|
346
363
|
[$gpuCallable]: {
|
|
347
364
|
strictSignature?: {
|
|
@@ -358,7 +375,11 @@ export type WithCast<T = BaseData> = GPUCallable<[v?: Infer<T>]> & {
|
|
|
358
375
|
export declare function hasCast(value: unknown): value is WithCast;
|
|
359
376
|
type AnyFn = (...args: never[]) => unknown;
|
|
360
377
|
export type DualFn<T extends AnyFn> = T & GPUCallable<Parameters<T>>;
|
|
361
|
-
|
|
378
|
+
/**
|
|
379
|
+
* @returns A snippet holding the JS value that `snippet` represents at comptime,
|
|
380
|
+
* or `undefined` if it is only known at runtime.
|
|
381
|
+
*/
|
|
382
|
+
export declare function asComptime(snippet: Snippet): Snippet | undefined;
|
|
362
383
|
export declare function isConstant(snippet: Snippet): boolean;
|
|
363
384
|
export declare function isWgsl(value: unknown): value is Wgsl;
|
|
364
385
|
export type BindableBufferUsage = 'uniform' | 'readonly' | 'mutable';
|
package/types.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isLazy, isProviding, isSlot, } from "./core/slot/slotTypes.js";
|
|
2
2
|
import { UnknownData } from "./data/dataTypes.js";
|
|
3
|
+
import { withValue } from "./data/snippet.js";
|
|
3
4
|
import { isWgslData, } from "./data/wgslTypes.js";
|
|
4
|
-
import { $cast, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve, } from "./shared/symbols.js";
|
|
5
|
+
import { $cast, $comptimeValueOf, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve, } from "./shared/symbols.js";
|
|
5
6
|
export class NormalState {
|
|
6
7
|
type = 'normal';
|
|
7
8
|
}
|
|
@@ -23,18 +24,42 @@ export function isSelfResolvable(value) {
|
|
|
23
24
|
export function getOwnSnippet(value) {
|
|
24
25
|
return value?.[$ownSnippet];
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* A `[$comptimeValueOf]` implementation for resources that are only known at runtime
|
|
29
|
+
*/
|
|
30
|
+
export function runtimeOnly() {
|
|
31
|
+
return { comptime: false };
|
|
32
|
+
}
|
|
26
33
|
export function isGPUCallable(value) {
|
|
27
34
|
return !!value?.[$gpuCallable];
|
|
28
35
|
}
|
|
29
36
|
export function hasCast(value) {
|
|
30
37
|
return !!value?.[$cast];
|
|
31
38
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
/**
|
|
40
|
+
* @returns A snippet holding the JS value that `snippet` represents at comptime,
|
|
41
|
+
* or `undefined` if it is only known at runtime.
|
|
42
|
+
*/
|
|
43
|
+
export function asComptime(snippet) {
|
|
44
|
+
const { value } = snippet;
|
|
45
|
+
if (typeof value === 'string' && snippet.dataType !== UnknownData) {
|
|
46
|
+
// Resolved WGSL code
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const getter = value?.[$comptimeValueOf];
|
|
50
|
+
if (!getter) {
|
|
51
|
+
// Objects that meet the criteria and didn't explicitly opt out of
|
|
52
|
+
// being known at comptime, are treated as known at comptime.
|
|
53
|
+
// That's mainly because there are far more objects that are eligible
|
|
54
|
+
// and we want to be treated as known at comptime than those that are
|
|
55
|
+
// special cases and need to opt out.
|
|
56
|
+
return snippet;
|
|
57
|
+
}
|
|
58
|
+
const result = getter.call(value);
|
|
59
|
+
return result.comptime ? withValue(result.value, snippet) : undefined;
|
|
35
60
|
}
|
|
36
61
|
export function isConstant(snippet) {
|
|
37
|
-
return (
|
|
62
|
+
return (asComptime(snippet) !== undefined ||
|
|
38
63
|
snippet.origin === 'constant' ||
|
|
39
64
|
snippet.origin === 'constant-immutable-def');
|
|
40
65
|
}
|