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