UNPKG

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
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; } }