typegpu
Version:
A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.
79 lines (78 loc) • 3.58 kB
JavaScript
import { createShelllessImpl } from "../core/function/shelllessImpl.js";
import { UnknownData } from "../data/dataTypes.js";
import { RefOperator } from "../data/ref.js";
import { isPtr, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
import { WgslTypeError } from "../errors.js";
import { getFunctionMetadata, getName } from "../shared/meta.js";
import { concretize } from "./generationHelpers.js";
function shallowEqualSchemas(a, b) {
if (a.type !== b.type)
return false;
if (isPtr(a) && isPtr(b)) {
return (a.access === b.access &&
a.addressSpace === b.addressSpace &&
a.implicit === b.implicit &&
shallowEqualSchemas(a.inner, b.inner));
}
if (isWgslArray(a) && isWgslArray(b)) {
return a.elementCount === b.elementCount && shallowEqualSchemas(a.elementType, b.elementType);
}
if (isWgslStruct(a) && isWgslStruct(b)) {
// Only structs with the same identity are considered equal
return a === b;
}
return true;
}
export class ShelllessRepository {
cache = new Map();
get(fn, argSnippets) {
const meta = getFunctionMetadata(fn);
if (!meta) {
return undefined;
}
if (!argSnippets && meta.ast.params.length > 0) {
throw new Error(`Cannot resolve '${getName(fn)}' directly, because it expects arguments. Either call it from another function, or wrap it in a shell`);
}
const argTypes = (argSnippets ?? []).map((s, index) => {
if (s.value instanceof RefOperator) {
if (s.dataType === UnknownData) {
throw new WgslTypeError(`d.ref() created with primitive types must be stored in a variable before use`);
}
return s.dataType;
}
if (s.dataType === UnknownData) {
throw new Error(`Passed illegal value ${s.value} as the #${index} argument to ${getName(fn) ?? '<unnamed>'}(...)\n` +
`Shellless functions can only accept arguments representing WGSL resources: constructible WGSL types, d.refs, samplers or texture views.\n` +
`Remember, that arguments such as samplers, texture views, accessors, slots etc. should be dereferenced via '.$' first.`);
}
let type = concretize(s.dataType);
if (isPtr(type) && type.implicit) {
// If the pointer was made implicitly (e.g. by assigning a reference to a const variable),
// then we dereference the pointer before passing it to the function. The main reason for this,
// is that in TypeScript, the type of the function accepts a value, not the value wrapped in
// d.ref<> (so it's not considered mutable from the perspective of the function)
// Example:
// const foo = layout.$.boids;
// bar(foo)
// ^^^
type = type.inner;
}
return type;
});
let cache = this.cache.get(fn);
if (cache) {
const variant = cache.find((v) => v.argTypes.length === argTypes.length &&
v.argTypes.every((t, i) => shallowEqualSchemas(t, argTypes[i])));
if (variant) {
return variant;
}
}
else {
cache = [];
this.cache.set(fn, cache);
}
const shellless = createShelllessImpl(argTypes, fn);
cache.push(shellless);
return shellless;
}
}