typegpu
Version:
A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.
101 lines (100 loc) • 4.07 kB
JavaScript
import { snip } from "../../data/snippet.js";
import { IllegalVarAccessError } from "../../errors.js";
import { isInsideTgpuFn } from "../../execMode.js";
import { makeDereferenceable } from "../../tgsl/makeDereferenceable.js";
import { makeResolvable } from "../../tgsl/makeResolvable.js";
import { getName, setName } from "../../shared/meta.js";
import { $gpuValueOf, $internal, $soul } from "../../shared/symbols.js";
/**
* Defines a variable scoped to each entry function (private).
*
* @param dataType The schema of the held data's type
* @param initialValue If not provided, the variable will be initialized to the dataType's "zero-value".
*/
export function privateVar(dataType, initialValue) {
return new TgpuVarImpl('private', dataType, initialValue);
}
/**
* Defines a variable scoped to the whole workgroup, shared between entry functions
* of the same invocation.
*
* @param dataType The schema of the held data's type
*/
export function workgroupVar(dataType) {
return new TgpuVarImpl('workgroup', dataType);
}
export function isVariable(value) {
return value instanceof TgpuVarImpl;
}
// --------------
// Implementation
// --------------
class TgpuVarImpl {
[$soul];
static {
const prototype = TgpuVarImpl.prototype;
prototype.resourceType = 'var';
prototype[$internal] = {};
makeDereferenceable(makeResolvable(prototype, {
asString() {
return `var:${getName(this) ?? '<unnamed>'}`;
},
resolve(ctx) {
const id = ctx.makeUniqueIdentifier(getName(this), 'global');
const init = this[$soul].initialValue
? snip(this[$soul].initialValue, this[$soul].dataType, 'constant')
: undefined;
return ctx.gen.declareGlobalVar({
scope: this[$soul].scope,
id,
dataType: this[$soul].dataType,
init,
});
},
}), {
codegenMode: {
getBaseSnippet(trackingProxy) {
return snip(trackingProxy, this[$soul].dataType, this[$soul].scope,
/* possibleSideEffects */ false);
},
},
simulateMode: {
get(state) {
if (!state.vars[this[$soul].scope].has(this)) {
// Not initialized yet
state.vars[this[$soul].scope].set(this, this[$soul].initialValue);
}
return state.vars[this[$soul].scope].get(this);
},
set(state, value) {
state.vars[this[$soul].scope].set(this, value);
},
},
normalMode: {
get() {
throw new IllegalVarAccessError(isInsideTgpuFn()
? `Cannot access variable '${getName(this) ?? '<unnamed>'}'. TypeGPU functions that depends on GPU resources need to be part of a compute dispatch, draw call or simulation`
: 'TypeGPU variables are inaccessible during normal JS execution. If you wanted to simulate GPU behavior, try `tgpu.simulate()`');
},
set(_value) {
throw new IllegalVarAccessError(isInsideTgpuFn()
? `Cannot access variable ${getName(this) ?? '<unnamed>'}. TypeGPU functions that depends on GPU resources need to be part of a compute dispatch, draw call or simulation`
: 'TypeGPU variables are inaccessible during normal JS execution. If you wanted to simulate GPU behavior, try `tgpu.simulate()`');
},
},
});
}
constructor(scope, dataType, initialValue) {
this[$soul] = {
type: 'var',
scope,
dataType,
initialValue,
label: undefined,
};
}
$name(label) {
setName(this, label);
return this;
}
}