typegpu 0.12.6 → 0.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/writeSoA.js +16 -0
- package/core/buffer/buffer.js +47 -11
- package/core/commandEncoder/attachments.js +5 -2
- package/core/function/autoIO.js +4 -2
- package/core/function/ioSchema.d.ts +7 -2
- package/core/function/ioSchema.js +41 -15
- package/core/function/tgpuFragmentFn.d.ts +1 -1
- package/core/function/tgpuFragmentFn.js +2 -1
- package/core/function/tgpuVertexFn.js +2 -1
- package/core/pipeline/connectAttachmentToShader.d.ts +2 -2
- package/core/pipeline/connectAttachmentToShader.js +16 -21
- package/core/pipeline/connectTargetsToShader.d.ts +5 -1
- package/core/pipeline/connectTargetsToShader.js +23 -18
- package/core/pipeline/renderPipeline.d.ts +5 -4
- package/core/pipeline/renderPipeline.js +10 -12
- package/core/resolve/namespace.js +2 -2
- package/core/root/rootTypes.d.ts +6 -2
- package/core/slot/accessor.d.ts +1 -1
- package/core/slot/accessor.js +72 -5
- package/core/texture/texture.js +8 -4
- package/data/autoStruct.d.ts +1 -1
- package/data/autoStruct.js +8 -4
- package/data/deepEqual.js +19 -9
- package/data/matrix.js +10 -3
- package/data/partialIO.d.ts +8 -1
- package/data/partialIO.js +36 -19
- package/data/ref.js +2 -2
- package/data/snippet.d.ts +6 -4
- package/data/snippet.js +8 -4
- package/data/wgslTypes.d.ts +3 -0
- package/internal.d.ts +1 -0
- package/nameUtils.d.ts +7 -0
- package/nameUtils.js +60 -0
- package/package.json +1 -1
- package/shared/meta.js +1 -1
- package/shared/symbols.js +1 -1
- package/shared/tseynit.js +54 -50
- package/std/boolean.js +9 -10
- package/std/operators.js +9 -4
- package/std/texture.d.ts +2 -2
- package/tgsl/accessIndex.js +19 -23
- package/tgsl/forOfUtils.js +6 -6
- package/tgsl/generationHelpers.js +2 -2
- package/tgsl/wgslGenerator.js +85 -23
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,
|
|
7
|
+
import { fallthroughCopyOrigin, 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";
|
|
@@ -476,7 +476,9 @@ export class WgslGenerator {
|
|
|
476
476
|
validateSnippetMutation(convLhs, expression);
|
|
477
477
|
this.tryMarkModified(lhs);
|
|
478
478
|
// Compound assignment operators are okay, e.g. +=, -=, *=, /=, ...
|
|
479
|
-
if (op === '=' &&
|
|
479
|
+
if (op === '=' &&
|
|
480
|
+
isStoredInMemory(rhsExpr) &&
|
|
481
|
+
!wgsl.isNaturallyEphemeral(rhsExpr.dataType)) {
|
|
480
482
|
throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(unptr(rhsExpr.dataType)).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
|
|
481
483
|
}
|
|
482
484
|
}
|
|
@@ -707,23 +709,43 @@ export class WgslGenerator {
|
|
|
707
709
|
}
|
|
708
710
|
if (wgsl.isWgslStruct(structType)) {
|
|
709
711
|
const entries = {};
|
|
712
|
+
const keysWithSideEffectsInSourceOrder = [];
|
|
710
713
|
for (const prop of properties) {
|
|
711
714
|
const key = resolveUniqueKey(prop);
|
|
712
715
|
const value = prop[1];
|
|
713
716
|
const propType = structType.propTypes[key];
|
|
714
717
|
if (propType === undefined) {
|
|
715
718
|
// Evaluate every field even if it gets stripped by the struct schema
|
|
716
|
-
|
|
719
|
+
const expr = this._expression(value);
|
|
720
|
+
if (expr.possibleSideEffects) {
|
|
721
|
+
logger.warn('suspicious', `\
|
|
722
|
+
Object property '${stringifyObjectProperty(prop)}' in '${stringifyNode(expression)}' does not exist on type '${String(structType)}'.
|
|
723
|
+
The generated shader will omit it, so its runtime side effects will not occur.`);
|
|
724
|
+
}
|
|
717
725
|
continue;
|
|
718
726
|
}
|
|
719
727
|
const expr = this._typedExpression(value, propType);
|
|
720
728
|
entries[key] = expr;
|
|
729
|
+
if (expr.possibleSideEffects) {
|
|
730
|
+
keysWithSideEffectsInSourceOrder.push(key);
|
|
731
|
+
}
|
|
721
732
|
}
|
|
722
733
|
for (const key of Object.keys(structType.propTypes)) {
|
|
723
734
|
if (entries[key] === undefined) {
|
|
724
735
|
throw new WgslTypeError(`Missing property ${key} in object literal for struct ${structType}`);
|
|
725
736
|
}
|
|
726
737
|
}
|
|
738
|
+
const keysWithSideEffectsInSchemaOrder = Object.keys(structType.propTypes).filter((key) => entries[key].possibleSideEffects);
|
|
739
|
+
const changesSideEffectsOrder = keysWithSideEffectsInSourceOrder.some((key, index) => key !== keysWithSideEffectsInSchemaOrder[index]);
|
|
740
|
+
if (changesSideEffectsOrder) {
|
|
741
|
+
logger.warn('suspicious', `\
|
|
742
|
+
Properties with possible side effects in '${stringifyNode(expression)}' do not match '${String(structType)}' declaration order:
|
|
743
|
+
|
|
744
|
+
Source order: [${keysWithSideEffectsInSourceOrder.join(', ')}]
|
|
745
|
+
Declaration order: [${keysWithSideEffectsInSchemaOrder.join(', ')}]
|
|
746
|
+
|
|
747
|
+
The generated shader will evaluate them in declaration order.`);
|
|
748
|
+
}
|
|
727
749
|
const convertedSnippets = convertStructValues(this.ctx, structType, entries);
|
|
728
750
|
return snip(stitch `${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType,
|
|
729
751
|
/* origin */ 'runtime', convertedSnippets.some((s) => s.possibleSideEffects));
|
|
@@ -779,8 +801,8 @@ export class WgslGenerator {
|
|
|
779
801
|
!alt ||
|
|
780
802
|
consequent.possibleSideEffects ||
|
|
781
803
|
alternative.possibleSideEffects ||
|
|
782
|
-
(
|
|
783
|
-
(
|
|
804
|
+
(isStoredInMemory(consequent) && !wgsl.isNaturallyEphemeral(consequent.dataType)) ||
|
|
805
|
+
(isStoredInMemory(alternative) && !wgsl.isNaturallyEphemeral(alternative.dataType))) {
|
|
784
806
|
throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid. For more complex branching, please use 'std.select' or if/else statements.`);
|
|
785
807
|
}
|
|
786
808
|
return snip(stitch `select(${alt}, ${con}, ${convertedTest})`, con.dataType, 'runtime',
|
|
@@ -958,7 +980,7 @@ export class WgslGenerator {
|
|
|
958
980
|
// The existence of `expectedReturnType` implies a function shell, which in turn implies that the
|
|
959
981
|
// value will be copied on return anyway
|
|
960
982
|
!expectedReturnType &&
|
|
961
|
-
|
|
983
|
+
isStoredInMemory(returnSnippet) &&
|
|
962
984
|
!wgsl.isNaturallyEphemeral(returnSnippet.dataType) &&
|
|
963
985
|
returnSnippet.origin !== 'local-def') {
|
|
964
986
|
const str = stringifyNode(returnNode);
|
|
@@ -996,7 +1018,7 @@ Try 'return ${typeStr}(${str});' instead.
|
|
|
996
1018
|
const declaration = `let ${rawId}`;
|
|
997
1019
|
throw new WgslTypeError(`'${declaration} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'${schemaWrappingSuggestion(declaration, rhsStr, eq.value)}`);
|
|
998
1020
|
}
|
|
999
|
-
if (
|
|
1021
|
+
if (isStoredInMemory(eq) && !wgsl.isNaturallyEphemeral(eq.dataType)) {
|
|
1000
1022
|
// `let` declarations cannot store references
|
|
1001
1023
|
const rhsStr = stringifyNode(eqNode);
|
|
1002
1024
|
const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
|
|
@@ -1077,7 +1099,7 @@ Try 'return ${typeStr}(${str});' instead.
|
|
|
1077
1099
|
// function code has been processed, so at least currently, we lose that info.
|
|
1078
1100
|
varOrigin = 'local-def';
|
|
1079
1101
|
}
|
|
1080
|
-
else if (!
|
|
1102
|
+
else if (!isStoredInMemory(eq)) {
|
|
1081
1103
|
// Not a reference, but also not naturally ephemeral, so we cannot guarantee it won't be mutated.
|
|
1082
1104
|
// We defer the decision for now.
|
|
1083
1105
|
varType = '<deferred>';
|
|
@@ -1278,7 +1300,8 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1278
1300
|
return { code: '', definesInNearestScope: false };
|
|
1279
1301
|
}
|
|
1280
1302
|
const firstElement = elements[0];
|
|
1281
|
-
if (!
|
|
1303
|
+
if (!isStoredInMemory(firstElement) &&
|
|
1304
|
+
!wgsl.isNaturallyEphemeral(firstElement.dataType)) {
|
|
1282
1305
|
throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
|
|
1283
1306
|
}
|
|
1284
1307
|
let blocksCode = '';
|
|
@@ -1343,11 +1366,48 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1343
1366
|
if (statement[0] === NODE.switch) {
|
|
1344
1367
|
// Switch statement
|
|
1345
1368
|
const [_, discriminant, cases] = statement;
|
|
1346
|
-
|
|
1369
|
+
let discriminantExpr = this._expression(discriminant);
|
|
1370
|
+
let caseExprs = cases.map(([test, consequent]) => {
|
|
1371
|
+
const testExpr = test === null ? switchDefault : this._expression(test);
|
|
1372
|
+
return [testExpr, consequent];
|
|
1373
|
+
});
|
|
1374
|
+
// comptime folding
|
|
1375
|
+
let matchedCaseWasNotLast = false;
|
|
1376
|
+
if ([discriminantExpr, ...caseExprs.map(([test]) => test)].every(isKnownAtComptime)) {
|
|
1377
|
+
let matchedCaseIndex = caseExprs.findIndex(([test]) => test !== switchDefault && test.value === discriminantExpr.value);
|
|
1378
|
+
if (matchedCaseIndex === -1) {
|
|
1379
|
+
matchedCaseIndex = caseExprs.findIndex(([test]) => test === switchDefault);
|
|
1380
|
+
}
|
|
1381
|
+
if (matchedCaseIndex === -1) {
|
|
1382
|
+
return { code: '', definesInNearestScope: false };
|
|
1383
|
+
}
|
|
1384
|
+
const matchedConsequent = caseExprs
|
|
1385
|
+
.slice(matchedCaseIndex)
|
|
1386
|
+
.find(([, consequent]) => consequent.length !== 0)?.[1];
|
|
1387
|
+
if (matchedConsequent === undefined) {
|
|
1388
|
+
return { code: '', definesInNearestScope: false };
|
|
1389
|
+
}
|
|
1390
|
+
if (matchedConsequent !== caseExprs.at(-1)?.[1]) {
|
|
1391
|
+
// The JS behavior will be different if this case does not end with control flow.
|
|
1392
|
+
matchedCaseWasNotLast = true;
|
|
1393
|
+
}
|
|
1394
|
+
discriminantExpr = snip(0, u32, 'constant', false);
|
|
1395
|
+
caseExprs = [[switchDefault, matchedConsequent]];
|
|
1396
|
+
}
|
|
1397
|
+
// type concretization
|
|
1398
|
+
discriminantExpr = tryConvertSnippet(this.ctx, discriminantExpr, [i32, u32]);
|
|
1347
1399
|
const switchType = discriminantExpr.dataType;
|
|
1348
1400
|
invariant(switchType !== UnknownData);
|
|
1349
|
-
|
|
1350
|
-
|
|
1401
|
+
caseExprs = caseExprs.map(([test, consequent]) => {
|
|
1402
|
+
// TODO(#3148): Remove this throw.
|
|
1403
|
+
if (test !== switchDefault && test.dataType === UnknownData) {
|
|
1404
|
+
throw new Error(`Failed to convert one of the switch tests to '${switchType.type}'`);
|
|
1405
|
+
}
|
|
1406
|
+
const convertedTest = test === switchDefault ? test : tryConvertSnippet(this.ctx, test, [switchType]);
|
|
1407
|
+
return [convertedTest, consequent];
|
|
1408
|
+
});
|
|
1409
|
+
// consequent resolution
|
|
1410
|
+
const resolvedCaseExprs = caseExprs.map(([test, consequent]) => {
|
|
1351
1411
|
// In WGSL, each case is a different block. This block scope forbids scope leaking.
|
|
1352
1412
|
// TODO(#3001): Consider using NODE.block here
|
|
1353
1413
|
this.ctx.pushBlockScope();
|
|
@@ -1355,7 +1415,7 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1355
1415
|
this.ctx.indent();
|
|
1356
1416
|
try {
|
|
1357
1417
|
const consequentStmts = consequent.map((s) => this._statement(s));
|
|
1358
|
-
return [
|
|
1418
|
+
return [test, consequentStmts];
|
|
1359
1419
|
}
|
|
1360
1420
|
finally {
|
|
1361
1421
|
this.ctx.dedent();
|
|
@@ -1363,6 +1423,15 @@ ${this.ctx.pre}else ${alternate}`,
|
|
|
1363
1423
|
this.ctx.popBlockScope();
|
|
1364
1424
|
}
|
|
1365
1425
|
});
|
|
1426
|
+
const groupedCaseExprs = [];
|
|
1427
|
+
let currentGroup = [];
|
|
1428
|
+
for (const [index, [test, consequent]] of resolvedCaseExprs.entries()) {
|
|
1429
|
+
currentGroup.push(test);
|
|
1430
|
+
if (consequent.length > 0 || index === caseExprs.length - 1) {
|
|
1431
|
+
groupedCaseExprs.push([currentGroup, consequent]);
|
|
1432
|
+
currentGroup = [];
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1366
1435
|
// Validation
|
|
1367
1436
|
{
|
|
1368
1437
|
// Tests should be constant
|
|
@@ -1380,7 +1449,9 @@ ${stringifyNode(statement)}`);
|
|
|
1380
1449
|
// We skip this check, because WGSL errors are readable,
|
|
1381
1450
|
// and we cannot easily access non-comptime known constants.
|
|
1382
1451
|
// Tests should not have non-trivial fallthrough
|
|
1383
|
-
|
|
1452
|
+
resolvedCaseExprs
|
|
1453
|
+
.slice(0, matchedCaseWasNotLast ? undefined : -1)
|
|
1454
|
+
.forEach(([_, consequent]) => {
|
|
1384
1455
|
const last = consequent.at(-1);
|
|
1385
1456
|
if (last && !last.endsWithControlFlow) {
|
|
1386
1457
|
throw new Error(`Switch statement cannot have non-trivial fallthrough.
|
|
@@ -1389,15 +1460,6 @@ ${stringifyNode(statement)}`);
|
|
|
1389
1460
|
}
|
|
1390
1461
|
});
|
|
1391
1462
|
}
|
|
1392
|
-
const groupedCaseExprs = [];
|
|
1393
|
-
let currentGroup = [];
|
|
1394
|
-
for (const [index, [test, consequent]] of caseExprs.entries()) {
|
|
1395
|
-
currentGroup.push(test);
|
|
1396
|
-
if (consequent.length > 0 || index === caseExprs.length - 1) {
|
|
1397
|
-
groupedCaseExprs.push([currentGroup, consequent]);
|
|
1398
|
-
currentGroup = [];
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
1463
|
return {
|
|
1402
1464
|
code: this._emitSwitchStatement(discriminantExpr, groupedCaseExprs),
|
|
1403
1465
|
definesInNearestScope: false,
|