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