wesl 0.6.0-pre10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/index.js +4468 -0
- package/dist/index.js.map +1 -0
- package/dist/minified.js +3426 -0
- package/dist/minified.js.map +1 -0
- package/dist/tools/packages/wesl/src/AbstractElems.d.ts +322 -0
- package/dist/tools/packages/wesl/src/Assertions.d.ts +27 -0
- package/dist/tools/packages/wesl/src/BindIdents.d.ts +70 -0
- package/dist/tools/packages/wesl/src/Conditions.d.ts +6 -0
- package/dist/tools/packages/wesl/src/FlattenTreeImport.d.ts +11 -0
- package/dist/tools/packages/wesl/src/LinkedWesl.d.ts +50 -0
- package/dist/tools/packages/wesl/src/Linker.d.ts +87 -0
- package/dist/tools/packages/wesl/src/LinkerUtil.d.ts +3 -0
- package/dist/tools/packages/wesl/src/LiveDeclarations.d.ts +12 -0
- package/dist/tools/packages/wesl/src/LowerAndEmit.d.ts +31 -0
- package/dist/tools/packages/wesl/src/Mangler.d.ts +39 -0
- package/dist/tools/packages/wesl/src/ParseWESL.d.ts +60 -0
- package/dist/tools/packages/wesl/src/ParsedRegistry.d.ts +29 -0
- package/dist/tools/packages/wesl/src/PathUtil.d.ts +6 -0
- package/dist/tools/packages/wesl/src/RawEmit.d.ts +6 -0
- package/dist/tools/packages/wesl/src/Reflection.d.ts +45 -0
- package/dist/tools/packages/wesl/src/Scope.d.ts +81 -0
- package/dist/tools/packages/wesl/src/StandardTypes.d.ts +13 -0
- package/dist/tools/packages/wesl/src/TransformBindingStructs.d.ts +52 -0
- package/dist/tools/packages/wesl/src/Util.d.ts +43 -0
- package/dist/tools/packages/wesl/src/WESLCollect.d.ts +94 -0
- package/dist/tools/packages/wesl/src/WeslBundle.d.ts +13 -0
- package/dist/tools/packages/wesl/src/WeslDevice.d.ts +25 -0
- package/dist/tools/packages/wesl/src/debug/ASTtoString.d.ts +5 -0
- package/dist/tools/packages/wesl/src/debug/ImportToString.d.ts +2 -0
- package/dist/tools/packages/wesl/src/debug/LineWrapper.d.ts +21 -0
- package/dist/tools/packages/wesl/src/debug/ScopeToString.d.ts +6 -0
- package/dist/tools/packages/wesl/src/index.d.ts +11 -0
- package/dist/tools/packages/wesl/src/parse/ImportGrammar.d.ts +5 -0
- package/dist/tools/packages/wesl/src/parse/Keywords.d.ts +4 -0
- package/dist/tools/packages/wesl/src/parse/WeslBaseGrammar.d.ts +5 -0
- package/dist/tools/packages/wesl/src/parse/WeslExpression.d.ts +13 -0
- package/dist/tools/packages/wesl/src/parse/WeslGrammar.d.ts +80 -0
- package/dist/tools/packages/wesl/src/parse/WeslStream.d.ts +44 -0
- package/dist/tools/packages/wesl/src/test/BindWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ConditionLinking.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ConditionalTranslationCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ErrorLogging.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Expression.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/FlattenTreeImport.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ImportCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ImportSyntaxCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/LinkGlob.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/LinkPackage.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Linker.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Mangling.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseComments.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseConditions.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseError.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/PathUtil.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/PrettyGrammar.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Reflection.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ScopeWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TestLink.d.ts +21 -0
- package/dist/tools/packages/wesl/src/test/TestSetup.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TestUtil.d.ts +40 -0
- package/dist/tools/packages/wesl/src/test/Tokenizer.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TransformBindingStructs.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Util.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/VirtualModules.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/WeslDevice.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/WgslTests.d.ts +0 -0
- package/dist/tools/packages/wesl/src/vlq/vlq.d.ts +11 -0
- package/package.json +46 -0
- package/src/AbstractElems.ts +446 -0
- package/src/Assertions.ts +51 -0
- package/src/BindIdents.ts +523 -0
- package/src/Conditions.ts +74 -0
- package/src/FlattenTreeImport.ts +55 -0
- package/src/LinkedWesl.ts +184 -0
- package/src/Linker.ts +284 -0
- package/src/LinkerUtil.ts +29 -0
- package/src/LiveDeclarations.ts +31 -0
- package/src/LowerAndEmit.ts +413 -0
- package/src/Mangler.ts +94 -0
- package/src/ParseWESL.ts +157 -0
- package/src/ParsedRegistry.ts +120 -0
- package/src/PathUtil.ts +31 -0
- package/src/RawEmit.ts +102 -0
- package/src/Reflection.ts +334 -0
- package/src/Scope.ts +162 -0
- package/src/StandardTypes.ts +97 -0
- package/src/TransformBindingStructs.ts +319 -0
- package/src/Util.ts +194 -0
- package/src/WESLCollect.ts +614 -0
- package/src/WeslBundle.ts +16 -0
- package/src/WeslDevice.ts +209 -0
- package/src/debug/ASTtoString.ts +290 -0
- package/src/debug/ImportToString.ts +29 -0
- package/src/debug/LineWrapper.ts +70 -0
- package/src/debug/ScopeToString.ts +79 -0
- package/src/index.ts +11 -0
- package/src/parse/ImportGrammar.ts +157 -0
- package/src/parse/Keywords.ts +26 -0
- package/src/parse/WeslBaseGrammar.ts +8 -0
- package/src/parse/WeslExpression.ts +207 -0
- package/src/parse/WeslGrammar.ts +856 -0
- package/src/parse/WeslStream.ts +279 -0
- package/src/test/BindWESL.test.ts +57 -0
- package/src/test/ConditionLinking.test.ts +91 -0
- package/src/test/ConditionalTranslationCases.test.ts +56 -0
- package/src/test/ErrorLogging.test.ts +30 -0
- package/src/test/Expression.test.ts +22 -0
- package/src/test/FlattenTreeImport.test.ts +74 -0
- package/src/test/ImportCases.test.ts +56 -0
- package/src/test/ImportSyntaxCases.test.ts +24 -0
- package/src/test/LinkGlob.test.ts +25 -0
- package/src/test/LinkPackage.test.ts +26 -0
- package/src/test/Linker.test.ts +125 -0
- package/src/test/Mangling.test.ts +45 -0
- package/src/test/ParseComments.test.ts +36 -0
- package/src/test/ParseConditions.test.ts +183 -0
- package/src/test/ParseError.test.ts +36 -0
- package/src/test/ParseWESL.test.ts +1572 -0
- package/src/test/PathUtil.test.ts +34 -0
- package/src/test/PrettyGrammar.test.ts +20 -0
- package/src/test/Reflection.test.ts +172 -0
- package/src/test/ScopeWESL.test.ts +462 -0
- package/src/test/TestLink.ts +82 -0
- package/src/test/TestSetup.ts +4 -0
- package/src/test/TestUtil.ts +126 -0
- package/src/test/Tokenizer.test.ts +135 -0
- package/src/test/TransformBindingStructs.test.ts +230 -0
- package/src/test/Util.test.ts +22 -0
- package/src/test/VirtualModules.test.ts +37 -0
- package/src/test/WeslDevice.test.ts +265 -0
- package/src/test/WgslTests.ts +0 -0
- package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
- package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
- package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
- package/src/test/wgsl_1/main.wgsl +3 -0
- package/src/test/wgsl_1/util.wgsl +1 -0
- package/src/test/wgsl_2/main2.wgsl +3 -0
- package/src/test/wgsl_2/util2.wgsl +1 -0
- package/src/vlq/vlq.ts +94 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { WeslBundle } from "wesl";
|
|
2
|
+
import { parseSrcModule, parseWESL, WeslAST } from "./ParseWESL.ts";
|
|
3
|
+
import { normalize, noSuffix } from "./PathUtil.ts";
|
|
4
|
+
import { resetScopeIds, SrcModule } from "./Scope.ts";
|
|
5
|
+
|
|
6
|
+
export interface ParsedRegistry {
|
|
7
|
+
modules: Record<string, WeslAST>; // key is module path, e.g. "rand_pkg::foo::bar"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parsedRegistry(): ParsedRegistry {
|
|
11
|
+
resetScopeIds(); // for debug
|
|
12
|
+
return { modules: {} };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** for debug */
|
|
16
|
+
export function registryToString(registry: ParsedRegistry): string {
|
|
17
|
+
return `modules: ${[...Object.keys(registry.modules)]}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse WESL each src module (file) into AST elements and a Scope tree.
|
|
22
|
+
* @param src keys are module paths, values are wesl src strings
|
|
23
|
+
*/
|
|
24
|
+
export function parseWeslSrc(src: Record<string, string>): ParsedRegistry {
|
|
25
|
+
const parsedEntries = Object.entries(src).map(([path, src]) => {
|
|
26
|
+
const weslAST = parseWESL(src);
|
|
27
|
+
return [path, weslAST];
|
|
28
|
+
});
|
|
29
|
+
return { modules: Object.fromEntries(parsedEntries) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Look up a module with a flexible selector.
|
|
33
|
+
* :: separated module path, package::util
|
|
34
|
+
* / separated file path ./util.wesl (or ./util)
|
|
35
|
+
* - note: a file path should not include a weslRoot prefix, e.g. not ./shaders/util.wesl
|
|
36
|
+
* simpleName util
|
|
37
|
+
*/
|
|
38
|
+
export function selectModule(
|
|
39
|
+
parsed: ParsedRegistry,
|
|
40
|
+
selectPath: string,
|
|
41
|
+
packageName = "package",
|
|
42
|
+
): WeslAST | undefined {
|
|
43
|
+
// dlog({reg: [...Object.keys(parsed.modules)]});
|
|
44
|
+
let modulePath: string;
|
|
45
|
+
if (selectPath.includes("::")) {
|
|
46
|
+
modulePath = selectPath;
|
|
47
|
+
} else if (
|
|
48
|
+
selectPath.includes("/") ||
|
|
49
|
+
selectPath.endsWith(".wesl") ||
|
|
50
|
+
selectPath.endsWith(".wgsl")
|
|
51
|
+
) {
|
|
52
|
+
modulePath = fileToModulePath(selectPath, packageName);
|
|
53
|
+
} else {
|
|
54
|
+
modulePath = packageName + "::" + selectPath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parsed.modules[modulePath];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param srcFiles map of source strings by file path
|
|
62
|
+
* key is '/' separated relative path (relative to srcRoot, not absolute file path )
|
|
63
|
+
* value is wesl source string
|
|
64
|
+
* @param registry add parsed modules to this registry
|
|
65
|
+
* @param packageName name of package
|
|
66
|
+
*/
|
|
67
|
+
export function parseIntoRegistry(
|
|
68
|
+
srcFiles: Record<string, string>,
|
|
69
|
+
registry: ParsedRegistry,
|
|
70
|
+
packageName: string = "package",
|
|
71
|
+
debugWeslRoot?: string,
|
|
72
|
+
): void {
|
|
73
|
+
if (debugWeslRoot === undefined) {
|
|
74
|
+
debugWeslRoot = "";
|
|
75
|
+
} else if (!debugWeslRoot.endsWith("/")) {
|
|
76
|
+
debugWeslRoot += "/";
|
|
77
|
+
}
|
|
78
|
+
const srcModules: SrcModule[] = Object.entries(srcFiles).map(
|
|
79
|
+
([filePath, src]) => {
|
|
80
|
+
const modulePath = fileToModulePath(filePath, packageName);
|
|
81
|
+
return { modulePath, debugFilePath: debugWeslRoot + filePath, src };
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
srcModules.forEach(mod => {
|
|
85
|
+
const parsed = parseSrcModule(mod, undefined);
|
|
86
|
+
if (registry.modules[mod.modulePath]) {
|
|
87
|
+
throw new Error(`duplicate module path: '${mod.modulePath}'`);
|
|
88
|
+
}
|
|
89
|
+
registry.modules[mod.modulePath] = parsed;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function parseLibsIntoRegistry(
|
|
94
|
+
libs: WeslBundle[],
|
|
95
|
+
registry: ParsedRegistry,
|
|
96
|
+
): void {
|
|
97
|
+
libs.forEach(({ modules, name }) =>
|
|
98
|
+
parseIntoRegistry(modules, registry, name),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const libRegex = /^lib\.w[eg]sl$/i;
|
|
103
|
+
|
|
104
|
+
/** convert a file path (./foo/bar.wesl)
|
|
105
|
+
* to a module path (package::foo::bar) */
|
|
106
|
+
function fileToModulePath(filePath: string, packageName: string): string {
|
|
107
|
+
if (filePath.includes("::")) {
|
|
108
|
+
// already a module path
|
|
109
|
+
return filePath;
|
|
110
|
+
}
|
|
111
|
+
if (packageName !== "package" && libRegex.test(filePath)) {
|
|
112
|
+
// special case for lib.wesl files in external packages
|
|
113
|
+
return packageName;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const strippedPath = noSuffix(normalize(filePath));
|
|
117
|
+
const moduleSuffix = strippedPath.replaceAll("/", "::");
|
|
118
|
+
const modulePath = packageName + "::" + moduleSuffix;
|
|
119
|
+
return modulePath;
|
|
120
|
+
}
|
package/src/PathUtil.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** simplistic path manipulation utilities */
|
|
2
|
+
|
|
3
|
+
/** return path with ./ and foo/.. elements removed */
|
|
4
|
+
export function normalize(path: string): string {
|
|
5
|
+
const segments = path.split("/");
|
|
6
|
+
const noDots = segments.filter(s => s !== ".");
|
|
7
|
+
const noDbl: string[] = [];
|
|
8
|
+
|
|
9
|
+
noDots.forEach(s => {
|
|
10
|
+
if (s !== "") {
|
|
11
|
+
if (s === ".." && noDbl.length && noDbl[noDbl.length - 1] !== "..") {
|
|
12
|
+
noDbl.pop();
|
|
13
|
+
} else {
|
|
14
|
+
noDbl.push(s);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return noDbl.join("/");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** return path w/o a suffix.
|
|
23
|
+
* e.g. /foo/bar.wgsl => /foo/bar */
|
|
24
|
+
export function noSuffix(path: string): string {
|
|
25
|
+
const lastSlash = path.lastIndexOf("/");
|
|
26
|
+
const lastStart = lastSlash === -1 ? 0 : lastSlash + 1;
|
|
27
|
+
|
|
28
|
+
const suffix = path.indexOf(".", lastStart);
|
|
29
|
+
const suffixStart = suffix === -1 ? path.length : suffix;
|
|
30
|
+
return path.slice(0, suffixStart);
|
|
31
|
+
}
|
package/src/RawEmit.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AttributeElem,
|
|
3
|
+
NameElem,
|
|
4
|
+
StuffElem,
|
|
5
|
+
TranslateTimeExpressionElem,
|
|
6
|
+
TypeRefElem,
|
|
7
|
+
TypeTemplateParameter,
|
|
8
|
+
UnknownExpressionElem,
|
|
9
|
+
} from "./AbstractElems.ts";
|
|
10
|
+
import { assertUnreachable } from "./Assertions.ts";
|
|
11
|
+
import {
|
|
12
|
+
diagnosticControlToString,
|
|
13
|
+
expressionToString,
|
|
14
|
+
findDecl,
|
|
15
|
+
} from "./LowerAndEmit.ts";
|
|
16
|
+
import { RefIdent } from "./Scope.ts";
|
|
17
|
+
|
|
18
|
+
// LATER DRY emitting elements like this with LowerAndEmit?
|
|
19
|
+
|
|
20
|
+
export function attributeToString(e: AttributeElem): string {
|
|
21
|
+
const { kind } = e.attribute;
|
|
22
|
+
// LATER emit more precise source map info by making use of all the spans
|
|
23
|
+
// Like the first case does
|
|
24
|
+
if (kind === "@attribute") {
|
|
25
|
+
const { params } = e.attribute;
|
|
26
|
+
if (params === undefined || params.length === 0) {
|
|
27
|
+
return "@" + e.attribute.name;
|
|
28
|
+
} else {
|
|
29
|
+
return `@${e.attribute.name}(${params
|
|
30
|
+
.map(param => contentsToString(param))
|
|
31
|
+
.join(", ")})`;
|
|
32
|
+
}
|
|
33
|
+
} else if (kind === "@builtin") {
|
|
34
|
+
return "@builtin(" + e.attribute.param.name + ")";
|
|
35
|
+
} else if (kind === "@diagnostic") {
|
|
36
|
+
return (
|
|
37
|
+
"@diagnostic" +
|
|
38
|
+
diagnosticControlToString(e.attribute.severity, e.attribute.rule)
|
|
39
|
+
);
|
|
40
|
+
} else if (kind === "@if") {
|
|
41
|
+
return `@if(${expressionToString(e.attribute.param.expression)})`;
|
|
42
|
+
} else if (kind === "@interpolate") {
|
|
43
|
+
return `@interpolate(${e.attribute.params.map(v => v.name).join(", ")})`;
|
|
44
|
+
} else {
|
|
45
|
+
assertUnreachable(kind);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function typeListToString(params: TypeTemplateParameter[]): string {
|
|
50
|
+
return `<${params.map(typeParamToString).join(", ")}>`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function typeParamToString(param?: TypeTemplateParameter): string {
|
|
54
|
+
if (param === undefined) return "?";
|
|
55
|
+
if (typeof param === "string") return param;
|
|
56
|
+
|
|
57
|
+
if (param.kind === "expression") return contentsToString(param);
|
|
58
|
+
if (param.kind === "type") return typeRefToString(param);
|
|
59
|
+
assertUnreachable(param);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function typeRefToString(t?: TypeRefElem): string {
|
|
63
|
+
if (!t) return "?";
|
|
64
|
+
const { name, templateParams } = t;
|
|
65
|
+
const params = templateParams ? typeListToString(templateParams) : "";
|
|
66
|
+
return `${refToString(name)}${params}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function refToString(ref: RefIdent | string): string {
|
|
70
|
+
if (typeof ref === "string") return ref;
|
|
71
|
+
if (ref.std) return ref.originalName;
|
|
72
|
+
const decl = findDecl(ref);
|
|
73
|
+
return decl.mangledName || decl.originalName;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function contentsToString(
|
|
77
|
+
elem:
|
|
78
|
+
| TranslateTimeExpressionElem
|
|
79
|
+
| UnknownExpressionElem
|
|
80
|
+
| NameElem
|
|
81
|
+
| StuffElem,
|
|
82
|
+
): string {
|
|
83
|
+
if (elem.kind === "translate-time-expression") {
|
|
84
|
+
throw new Error("Not supported");
|
|
85
|
+
} else if (elem.kind === "expression" || elem.kind === "stuff") {
|
|
86
|
+
const parts = elem.contents.map(c => {
|
|
87
|
+
const { kind } = c;
|
|
88
|
+
if (kind === "text") {
|
|
89
|
+
return c.srcModule.src.slice(c.start, c.end);
|
|
90
|
+
} else if (kind === "ref") {
|
|
91
|
+
return refToString(c.ident);
|
|
92
|
+
} else {
|
|
93
|
+
return `?${c.kind}?`;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return parts.join(" ");
|
|
97
|
+
} else if (elem.kind === "name") {
|
|
98
|
+
return elem.name;
|
|
99
|
+
} else {
|
|
100
|
+
assertUnreachable(elem);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { matchOneOf } from "mini-parse";
|
|
2
|
+
import {
|
|
3
|
+
BindingStructElem,
|
|
4
|
+
NameElem,
|
|
5
|
+
StructMemberElem,
|
|
6
|
+
TextElem,
|
|
7
|
+
TranslateTimeExpressionElem,
|
|
8
|
+
TypeRefElem,
|
|
9
|
+
UnknownExpressionElem,
|
|
10
|
+
} from "./AbstractElems.ts";
|
|
11
|
+
import { assertThat } from "./Assertions.ts";
|
|
12
|
+
import { TransformedAST, WeslJsPlugin } from "./Linker.ts";
|
|
13
|
+
import { identElemLog } from "./LinkerUtil.ts";
|
|
14
|
+
import { RefIdent } from "./Scope.ts";
|
|
15
|
+
import {
|
|
16
|
+
multisampledTextureTypes,
|
|
17
|
+
sampledTextureTypes,
|
|
18
|
+
textureStorageTypes,
|
|
19
|
+
} from "./StandardTypes.ts";
|
|
20
|
+
import { findMap } from "./Util.ts";
|
|
21
|
+
|
|
22
|
+
export type BindingStructReportFn = (structs: BindingStructElem[]) => void;
|
|
23
|
+
export const textureStorage = matchOneOf(textureStorageTypes);
|
|
24
|
+
|
|
25
|
+
export function reportBindingStructsPlugin(
|
|
26
|
+
fn: BindingStructReportFn,
|
|
27
|
+
): WeslJsPlugin {
|
|
28
|
+
return {
|
|
29
|
+
transform: reportBindingStructs(fn),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Linker plugin that generates TypeScript strings for GPUBindingGroupLayouts
|
|
34
|
+
* based on the binding structs in the WESL source
|
|
35
|
+
*
|
|
36
|
+
* requires the enableBindingStructs() transform to be enabled
|
|
37
|
+
*
|
|
38
|
+
* @param fn a function that will be called with the binding structs
|
|
39
|
+
* (Normally the caller will pass a function that uses bindingGroupLayoutTs()
|
|
40
|
+
* to generate the TypeScript)
|
|
41
|
+
*
|
|
42
|
+
* The generated TypeScript looks looks roughly like this
|
|
43
|
+
|
|
44
|
+
export function MyBindingLayout(device: GPUDevice): GPUBindGroupLayout {
|
|
45
|
+
return device.createBindGroupLayout({
|
|
46
|
+
entries: [
|
|
47
|
+
{
|
|
48
|
+
binding: 0,
|
|
49
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
50
|
+
buffer: {
|
|
51
|
+
type: "storage",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
*/
|
|
58
|
+
export function reportBindingStructs(
|
|
59
|
+
fn: BindingStructReportFn,
|
|
60
|
+
): (ast: TransformedAST) => TransformedAST {
|
|
61
|
+
return (ast: TransformedAST) => {
|
|
62
|
+
const structs = ast.notableElems.bindingStructs as BindingStructElem[];
|
|
63
|
+
fn(structs);
|
|
64
|
+
return ast;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function firstLetterLower(s: string): string {
|
|
69
|
+
return s[0].toLowerCase() + s.slice(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @return a string containing a generated TypeScript function that creates
|
|
74
|
+
* a GPUBindingGroupLayout instance to align with the binding structures
|
|
75
|
+
* in wesl source.
|
|
76
|
+
*/
|
|
77
|
+
export function bindingGroupLayoutTs(
|
|
78
|
+
struct: BindingStructElem,
|
|
79
|
+
typeScript = true,
|
|
80
|
+
): string {
|
|
81
|
+
if (!struct) {
|
|
82
|
+
console.log("no struct!???");
|
|
83
|
+
return "";
|
|
84
|
+
}
|
|
85
|
+
const structName = firstLetterLower(struct.name.ident.mangledName!);
|
|
86
|
+
const visibility = shaderVisiblity(struct);
|
|
87
|
+
const entries = struct.members
|
|
88
|
+
.map(m => memberToLayoutEntry(m, visibility))
|
|
89
|
+
.join(",");
|
|
90
|
+
|
|
91
|
+
const fnName = `${structName}Layout`;
|
|
92
|
+
const entriesName = `${structName}Entries`;
|
|
93
|
+
|
|
94
|
+
const fnParams =
|
|
95
|
+
typeScript ? `(device: GPUDevice): GPUBindGroupLayout` : `(device)`;
|
|
96
|
+
|
|
97
|
+
const src = `
|
|
98
|
+
const ${entriesName} = [ ${entries} ];
|
|
99
|
+
function ${fnName}${fnParams} {
|
|
100
|
+
return device.createBindGroupLayout({
|
|
101
|
+
entries: ${entriesName}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const layoutFunctions = { ${fnName} };
|
|
106
|
+
export const layoutEntries = { ${entriesName} };
|
|
107
|
+
`;
|
|
108
|
+
return src;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** return the shader stage visibility for a binding struct, based on
|
|
112
|
+
* the shader entry function that has the binding struct as a parameter.
|
|
113
|
+
*
|
|
114
|
+
* The shader entry function is attached to the binding struct
|
|
115
|
+
* by the enableBindingStructs() transform.
|
|
116
|
+
*/
|
|
117
|
+
function shaderVisiblity(struct: BindingStructElem): string {
|
|
118
|
+
const { entryFn } = struct;
|
|
119
|
+
if (!entryFn) {
|
|
120
|
+
identElemLog(struct.name, "missing entry function for binding struct");
|
|
121
|
+
} else {
|
|
122
|
+
const { attributes = [] } = entryFn;
|
|
123
|
+
if (
|
|
124
|
+
attributes.find(
|
|
125
|
+
({ attribute: a }) => a.kind === "@attribute" && a.name === "compute",
|
|
126
|
+
)
|
|
127
|
+
) {
|
|
128
|
+
return "GPUShaderStage.COMPUTE";
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
attributes.find(
|
|
132
|
+
({ attribute: a }) => a.kind === "@attribute" && a.name === "vertex",
|
|
133
|
+
)
|
|
134
|
+
) {
|
|
135
|
+
return "GPUShaderStage.VERTEX";
|
|
136
|
+
}
|
|
137
|
+
if (
|
|
138
|
+
attributes.find(
|
|
139
|
+
({ attribute: a }) => a.kind === "@attribute" && a.name === "fragment",
|
|
140
|
+
)
|
|
141
|
+
) {
|
|
142
|
+
return "GPUShaderStage.FRAGMENT";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
identElemLog(struct.name, "unknown entry point type for binding struct");
|
|
146
|
+
return "GPUShaderStage.COMPUTE";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @return a GPUBindGroupLayoutEntry corresponding to one member
|
|
151
|
+
* of a WESL binding struct.
|
|
152
|
+
*/
|
|
153
|
+
function memberToLayoutEntry(
|
|
154
|
+
member: StructMemberElem,
|
|
155
|
+
visibility: string,
|
|
156
|
+
): string {
|
|
157
|
+
const bindingParam = findMap(member.attributes ?? [], ({ attribute: a }) =>
|
|
158
|
+
a.kind === "@attribute" && a.name === "binding" ? a : undefined,
|
|
159
|
+
)?.params?.[0];
|
|
160
|
+
const binding = bindingParam ? paramText(bindingParam) : "?";
|
|
161
|
+
|
|
162
|
+
const src = `
|
|
163
|
+
{
|
|
164
|
+
binding: ${binding},
|
|
165
|
+
visibility: ${visibility},
|
|
166
|
+
${layoutEntry(member)}
|
|
167
|
+
}`;
|
|
168
|
+
return src;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @return the guts of the GPUBindGroupLayoutEntry for this binding struct member.
|
|
172
|
+
* ptr references to storage arrays become 'buffer' GPUBufferBindingLayout intances,
|
|
173
|
+
* references to WGSL samplers become 'sampler' GPUSamplerBindingLayout instances, etc.
|
|
174
|
+
*/
|
|
175
|
+
function layoutEntry(member: StructMemberElem): string {
|
|
176
|
+
const { typeRef } = member;
|
|
177
|
+
let entry: string | undefined;
|
|
178
|
+
const { name: typeName } = typeRef;
|
|
179
|
+
entry = ptrLayoutEntry(typeRef) ?? storageTextureLayoutEntry(typeRef);
|
|
180
|
+
if (!entry && typeof typeName !== "string" && typeName.std) {
|
|
181
|
+
entry =
|
|
182
|
+
samplerLayoutEntry(typeRef) ??
|
|
183
|
+
textureLayoutEntry(typeRef) ??
|
|
184
|
+
externalTextureLayoutEntry(typeRef);
|
|
185
|
+
}
|
|
186
|
+
if (!entry) {
|
|
187
|
+
console.error(`unhandled type`, typeName);
|
|
188
|
+
entry = `{ }`;
|
|
189
|
+
}
|
|
190
|
+
return entry;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function ptrLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
194
|
+
if (typeRef.name.originalName === "ptr") {
|
|
195
|
+
const param1 = typeRef.templateParams?.[0];
|
|
196
|
+
const param3 = typeRef.templateParams?.[2];
|
|
197
|
+
if (param1?.kind === "type" && param1.name.originalName === "uniform") {
|
|
198
|
+
return `buffer: { type: "uniform" }`;
|
|
199
|
+
} else if (
|
|
200
|
+
param1?.kind === "type" &&
|
|
201
|
+
param1.name.originalName === "storage"
|
|
202
|
+
) {
|
|
203
|
+
if (param3?.kind === "type" && param3.name.originalName === "read") {
|
|
204
|
+
return `buffer: { type: "read-only-storage" }`;
|
|
205
|
+
} else {
|
|
206
|
+
return `buffer: { type: "storage" }`;
|
|
207
|
+
}
|
|
208
|
+
// LATER what do we do with the element type (2nd parameter)
|
|
209
|
+
// LATER should there be an ability to set hasDynamicOffset?
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function samplerLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
215
|
+
const { originalName } = typeRef.name as RefIdent;
|
|
216
|
+
if (originalName === "sampler") {
|
|
217
|
+
// LATER how do we set: type GPUSamplerBindingType = | "filtering" | "non-filtering";
|
|
218
|
+
// (just assuming filtering as a placeholder for now)
|
|
219
|
+
return `sampler: { type: "filtering" }`;
|
|
220
|
+
}
|
|
221
|
+
if (originalName === "sampler_comparison") {
|
|
222
|
+
return `sampler: { type: "comparison" }`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const textureTypes = matchOneOf(sampledTextureTypes);
|
|
227
|
+
const multiNames = matchOneOf(multisampledTextureTypes);
|
|
228
|
+
|
|
229
|
+
function textureLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
230
|
+
const { originalName } = typeRef.name as RefIdent;
|
|
231
|
+
const multisampled =
|
|
232
|
+
multiNames.test(originalName) ? ", multisampled: true, " : "";
|
|
233
|
+
if (multisampled || textureTypes.test(originalName)) {
|
|
234
|
+
// LATER viewDimension
|
|
235
|
+
const sampleType = getSampleType(typeRef);
|
|
236
|
+
return `texture: { sampleType: "${sampleType}"${multisampled} }`;
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
|
|
240
|
+
function getSampleType(typeRef: TypeRefElem): GPUTextureSampleType {
|
|
241
|
+
const firstParam = typeRef.templateParams?.[0] as TypeRefElem;
|
|
242
|
+
const texelType = (firstParam.name as RefIdent)
|
|
243
|
+
.originalName as WgslTexelType;
|
|
244
|
+
const sampleType = texelTypeToSampleType(texelType);
|
|
245
|
+
return sampleType;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function storageTextureLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
250
|
+
if (textureStorage.test(typeRef.name.originalName)) {
|
|
251
|
+
const firstParam = typeRef.templateParams?.[0];
|
|
252
|
+
const secondParam = typeRef.templateParams?.[1];
|
|
253
|
+
assertThat(firstParam?.kind === "type"); // LATER: Temp hack
|
|
254
|
+
assertThat(secondParam?.kind === "type"); // LATER: Temp hack
|
|
255
|
+
const sampleType = formatToTextureSampleType(
|
|
256
|
+
firstParam.name.originalName as GPUTextureFormat,
|
|
257
|
+
);
|
|
258
|
+
const access = accessMode(secondParam.name.originalName);
|
|
259
|
+
return `storageTexture: { format: "${firstParam.name.originalName}", sampleType: "${sampleType}", access: "${access}" }`;
|
|
260
|
+
}
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function externalTextureLayoutEntry(typeRef: TypeRefElem): string | undefined {
|
|
265
|
+
const { originalName } = typeRef.name as RefIdent;
|
|
266
|
+
if (originalName === "texture_external") {
|
|
267
|
+
// LATER. how would we set the required source: HTMLVideoElement or VideoFrame?
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function paramText(
|
|
273
|
+
expression: UnknownExpressionElem | NameElem | TranslateTimeExpressionElem,
|
|
274
|
+
): string {
|
|
275
|
+
assertThat(
|
|
276
|
+
expression.kind === "expression",
|
|
277
|
+
"Only expression elements are supported in this position",
|
|
278
|
+
);
|
|
279
|
+
const text = expression.contents[0] as TextElem;
|
|
280
|
+
return text.srcModule.src.slice(expression.start, expression.end);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function formatToTextureSampleType(
|
|
284
|
+
format: GPUTextureFormat,
|
|
285
|
+
float32Filterable = false,
|
|
286
|
+
): GPUTextureSampleType {
|
|
287
|
+
if (format.includes("32float")) {
|
|
288
|
+
return float32Filterable ? "float" : "unfilterable-float";
|
|
289
|
+
}
|
|
290
|
+
if (format.includes("float") || format.includes("unorm")) {
|
|
291
|
+
return "float";
|
|
292
|
+
}
|
|
293
|
+
if (format.includes("uint")) {
|
|
294
|
+
return "uint";
|
|
295
|
+
}
|
|
296
|
+
if (format.includes("sint")) {
|
|
297
|
+
return "sint";
|
|
298
|
+
}
|
|
299
|
+
throw new Error(`native sample type unknwon for texture format ${format}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export type WgslTexelType = "f32" | "u32" | "i32";
|
|
303
|
+
|
|
304
|
+
/** return the wgsl element type for a given texture format */
|
|
305
|
+
export function formatToTexelType(format: GPUTextureFormat): WgslTexelType {
|
|
306
|
+
if (format.includes("float")) return "f32";
|
|
307
|
+
if (format.includes("unorm")) return "f32";
|
|
308
|
+
if (format.includes("uint")) return "u32";
|
|
309
|
+
if (format.includes("sint")) return "i32";
|
|
310
|
+
throw new Error(`unknown format ${format}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** @return the webgpu GPUTextureSampleType from the wgsl texel type */
|
|
314
|
+
export function texelTypeToSampleType(
|
|
315
|
+
type: WgslTexelType,
|
|
316
|
+
): GPUTextureSampleType {
|
|
317
|
+
if (type === "f32") return "float";
|
|
318
|
+
if (type === "u32") return "uint";
|
|
319
|
+
if (type === "i32") return "sint";
|
|
320
|
+
throw new Error(`unknown texel type ${type}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function accessMode(access: string): GPUStorageTextureAccess {
|
|
324
|
+
if (access === "read") {
|
|
325
|
+
return "read-only";
|
|
326
|
+
}
|
|
327
|
+
if (access === "write") {
|
|
328
|
+
return "write-only";
|
|
329
|
+
}
|
|
330
|
+
if (access === "read_write") {
|
|
331
|
+
return "read-write";
|
|
332
|
+
}
|
|
333
|
+
throw new Error(`unknown access mode: ${access}`);
|
|
334
|
+
}
|