typegpu
Version:
A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.
365 lines (364 loc) • 16.1 kB
TypeScript
import type { Block, FuncParameter } from 'tinyest';
import type { IndexFlag, TgpuBuffer, UniformFlag, VertexFlag } from './core/buffer/buffer.ts';
import type { TgpuConst } from './core/constant/tgpuConstant.ts';
import type { TgpuDeclare } from './core/declare/tgpuDeclare.ts';
import type { TgpuComputeFn } from './core/function/tgpuComputeFn.ts';
import type { TgpuFn } from './core/function/tgpuFn.ts';
import type { TgpuFragmentFn } from './core/function/tgpuFragmentFn.ts';
import type { SeparatedEntryArgs } from './core/function/fnTypes.ts';
import type { TgpuVertexFn } from './core/function/tgpuVertexFn.ts';
import type { TgpuComputePipeline } from './core/pipeline/computePipeline.ts';
import type { TgpuRenderPipeline } from './core/pipeline/renderPipeline.ts';
import type { TgpuSampler } from './core/sampler/sampler.ts';
import { type Eventual, type SlotValuePair, type TgpuAccessor, type TgpuLazy, type TgpuSlot } from './core/slot/slotTypes.ts';
import type { TgpuExternalTexture } from './core/texture/externalTexture.ts';
import type { TgpuTexture, TgpuTextureView } from './core/texture/texture.ts';
import type { TgpuVar } from './core/variable/tgpuVariable.ts';
import { type AnyData, UnknownData } from './data/dataTypes.ts';
import type { MapValueToSnippet, ResolvedSnippet, Snippet } from './data/snippet.ts';
import { type AnyMatInstance, type AnyVecInstance, type BaseData } from './data/wgslTypes.ts';
import { $cast, $gpuCallable, $gpuValueOf, $internal, $ownSnippet, $resolve } from './shared/symbols.ts';
import type { TgpuBindGroupLayout, TgpuLayoutEntry } from './tgpuBindGroupLayout.ts';
import type { WgslEnableExtension } from './wgslExtensions.ts';
import type { Infer } from './shared/repr.ts';
import type { ShaderGenerator } from './tgsl/shaderGenerator.ts';
import type { StorageFlag } from './extension.ts';
import type { TgpuBufferBinding } from './core/buffer/bufferBinding.ts';
import type { ShelllessRepository } from './tgsl/shellless.ts';
import type { SupportedLogOp } from './tgsl/consoleLog/types.ts';
export type ResolvableObject = SelfResolvable | TgpuLazy<unknown> | TgpuConst | TgpuDeclare | TgpuBindGroupLayout | TgpuFn | TgpuComputeFn | TgpuFragmentFn | TgpuComputePipeline | TgpuRenderPipeline | TgpuVertexFn | TgpuSampler | TgpuAccessor | TgpuExternalTexture | TgpuTexture | TgpuTextureView | TgpuBufferBinding<BaseData> | TgpuVar | AnyVecInstance | AnyMatInstance | AnyData | ((...args: never[]) => unknown);
export type Wgsl = Eventual<number | boolean | ResolvableObject>;
export type ShaderStage = 'compute' | 'vertex' | 'fragment';
export interface ResolveFunctionOptions {
functionType: 'normal' | ShaderStage;
workgroupSize?: readonly number[] | undefined;
name: string;
argTypes: BaseData[];
/**
* The return type of the function. If undefined, the type should be inferred
* from the implementation (relevant for shellless functions).
*/
returnType: BaseData | undefined;
body: Block;
params: FuncParameter[];
externalMap: Record<string, unknown>;
/**
* For entry functions: positional args and optional data struct.
* When provided, takes precedence over `argTypes` for WGSL header generation.
*/
entryInput?: SeparatedEntryArgs | undefined;
}
export type ItemLayer = {
type: 'item';
usedSlots: Set<TgpuSlot<unknown>>;
};
export type FunctionArgumentAccess = () => Snippet | undefined;
export interface FunctionArgument {
name: string;
access: FunctionArgumentAccess;
decoratedType: BaseData;
used: boolean;
}
export type FunctionScopeLayer = {
type: 'functionScope';
functionType: 'normal' | 'compute' | 'vertex' | 'fragment';
argAccess: Record<string, FunctionArgumentAccess>;
externalMap: Record<string, unknown>;
/**
* The return type of the function. If undefined, the type should be inferred
* from the implementation (relevant for shellless functions).
*/
returnType: BaseData | undefined;
/**
* All types used in `return` statements.
*/
reportedReturnTypes: Set<BaseData>;
/**
* Maps variables to their modifier placeholders
*/
placeholderForVariable: Map<Snippet, string>;
/**
* Local variables that need `var` modifier.
*/
modifiedVariables: Set<Snippet>;
};
export type SlotBindingLayer = {
type: 'slotBinding';
bindingMap: WeakMap<TgpuSlot<unknown>, unknown>;
};
export type BlockScopeLayer = {
type: 'blockScope';
takenLocalIdentifiers: Set<string>;
declarations: Map<string, Snippet>;
externals: Map<string, Snippet>;
};
export type StackLayer = ItemLayer | SlotBindingLayer | FunctionScopeLayer | BlockScopeLayer;
export interface ItemStateStack {
readonly itemDepth: number;
readonly topItem: ItemLayer;
readonly topBlockScope: BlockScopeLayer | undefined;
readonly topFunctionScope: FunctionScopeLayer | undefined;
pushItem(): void;
pushSlotBindings(pairs: SlotValuePair[]): void;
pushFunctionScope(functionType: 'normal' | ShaderStage, argAccess: Record<string, FunctionArgumentAccess>,
/**
* The return type of the function. If undefined, the type should be inferred
* from the implementation (relevant for shellless functions).
*/
returnType: BaseData | undefined, externalMap: Record<string, unknown>): FunctionScopeLayer;
pushBlockScope(): void;
setBlockExternals(externals: Record<string, Snippet>): void;
clearBlockExternals(): void;
pop<T extends StackLayer['type']>(type: T): Extract<StackLayer, {
type: T;
}>;
pop(): StackLayer | undefined;
readSlot<T>(slot: TgpuSlot<T>): T | undefined;
getSnippetById(id: string): Snippet | undefined;
defineBlockVariable(id: string, snippet: Snippet): void;
}
/**
* # What are execution modes/states? 🤷
* They're used to control how each TypeGPU resource reacts
* to actions upon them.
*
* ## Normal mode
* This is the default mode, where resources are acted upon
* by code either:
* - Not wrapped inside any of our execution-altering APIs
* like tgpu.resolve or tgpu.simulate.
* - Inside tgpu.lazy definitions, where we're taking a break
* from codegen/simulation to create resources on-demand.
*
* ```ts
* const count = tgpu.privateVar(d.f32);
* count.$ += 1; // Illegal in top-level
*
* const root = await tgpu.init();
* const countMutable = root.createMutable(d.f32);
* countMutable.$ = [1, 2, 3]; // Illegal in top-level
* countMutable.write([1, 2, 3]); // OK!
* ```
*
* ## Codegen mode
* Brought upon by `tgpu.resolve()` (or higher-level APIs using it like our pipelines).
* Resources are expected to generate WGSL code that represents them, instead of
* fulfilling their task in JS.
*
* ```ts
* const foo = tgpu.fn([], d.f32)(() => 123);
* // The following is running in `codegen` mode
* console.log(foo()); // Prints `foo_0()`
* ```
*
* ## Simulate mode
* Callbacks passed to `tgpu.simulate()` are executed in this mode. Each 'simulation'
* is isolated, and does not share state with other simulations (even nested ones).
* Variables and buffers can be accessed and mutated directly, and their state
* is returned at the end of the simulation.
*
* ```ts
* const var = tgpu.privateVar(d.f32, 0);
*
* const result = tgpu.simulate(() => {
* // This is running in `simulate` mode
* var.$ += 1; // Direct access is legal
* return var.$; // Returns 1
* });
*
* console.log(result.value); // Prints 1
* ```
*/
export type ExecMode = 'normal' | 'codegen' | 'simulate';
export declare class NormalState {
readonly type: "normal";
}
export declare class CodegenState {
readonly type: "codegen";
}
export declare class SimulationState {
readonly type: "simulate";
readonly buffers: Map<TgpuBuffer<BaseData>, unknown>;
readonly vars: {
private: Map<TgpuVar, unknown>;
workgroup: Map<TgpuVar, unknown>;
};
constructor(buffers: Map<TgpuBuffer<BaseData>, unknown>, vars: {
private: Map<TgpuVar, unknown>;
workgroup: Map<TgpuVar, unknown>;
});
}
export type ExecState = NormalState | CodegenState | SimulationState;
/**
* Passed into each resolvable item. All items in a tree share a resolution ctx,
* but there can be layers added and removed from the item stack when going down
* and up the tree.
*/
export interface ResolutionCtx {
[$internal]: {
itemStateStack: ItemStateStack;
};
readonly pre: string;
readonly mode: ExecState;
readonly enableExtensions: WgslEnableExtension[] | undefined;
readonly gen: ShaderGenerator;
/**
* Used by `typedExpression` to signal downstream
* expression resolution what type is expected of them.
*
* It is used exclusively for inferring the types of structs and arrays.
* It is modified exclusively by `typedExpression` function.
*/
expectedType: (BaseData | BaseData[]) | undefined;
readonly topFunctionScope: FunctionScopeLayer | undefined;
readonly topFunctionReturnType: BaseData | undefined;
readonly blockDepth: number;
readonly shelllessRepo: ShelllessRepository;
/**
* Adds a module-scope declaration to the resolution output.
* @param declaration - The WGSL code of the declaration.
* @param name - The identifier the declaration declares (a fn, struct, var,
* const or alias name), if it declares one. Reported back to the caller
* through `ResolutionResult.declarations`.
*/
addDeclaration(declaration: string, name?: string): void;
withResetIndentLevel<T>(callback: () => T): T;
/**
* Reserves a bind group number, and returns a placeholder that will be replaced
* with a concrete number at the end of the resolution process.
*/
allocateLayoutEntry(layout: TgpuBindGroupLayout): string;
/**
* Reserves a spot in the catch-all bind group, without the indirection of a bind-group.
* This means the resource is 'fixed', and cannot be swapped between code execution.
*/
allocateFixedEntry(layoutEntry: TgpuLayoutEntry, resource: object): {
group: string;
binding: number;
};
withSlots<T>(pairs: SlotValuePair[], callback: () => T): T;
pushMode(state: ExecState): void;
popMode(expected?: ExecMode): void;
/**
* Unwraps all layers of slot/lazy indirection and returns the concrete value if available.
* @throws {MissingSlotValueError}
*/
unwrap<T>(eventual: Eventual<T>): T;
/**
* Returns the snippet representing `item`.
*
* @param item The value to resolve
* @param schema Additional information about the item's data type
*/
resolve(item: unknown, schema?: BaseData | UnknownData): ResolvedSnippet;
/**
* Equivalent to `snip(ctx.resolve(snippet.value, snippet.dataType).value, snippet.dataType, snippet.origin, snippet.possibleSideEffects)`.
*/
resolveSnippet(snippet: Snippet): ResolvedSnippet;
resolveFunction(options: ResolveFunctionOptions): {
code: string;
returnType: BaseData;
};
withVaryingLocations<T>(locations: Record<string, number>, callback: () => T): T;
get varyingLocations(): Record<string, number> | undefined;
/**
* Temporarily renames the item.
* Useful for resolutions with slots,
* since functions with different slots should have different names,
* and all hold the same inner function that is being resolved multiple times.
* @param item the item to rename
* @param name the temporary name to assign to the item (if missing, just returns `callback()`)
*/
withRenamed<T>(item: object, name: string | undefined, callback: () => T): T;
/**
* @param primer The basis for the unique identifier. Depending on the strategy, or
* the names already taken, this may be modified to ensure uniqueness.
* @param scope The scope in which to generate the identifier. 'global' means
* the identifier is meant to be unique across the entire program, while
* 'block' means it cannot shadow any existing identifiers visible from
* within the current block. After the block is popped, any identifiers
* defined within it are no longer visible.
* @returns an identifier that is unique within the given scope
*/
makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string;
isIdentifierBanned(name: string): boolean;
/**
* @param name The name to check.
* @param scope The scope in which we want to place the identifier.
*/
isIdentifierTaken(name: string, scope: 'global' | 'block'): boolean;
/**
* Makes sure the given identifier cannot be generated by {@link makeUniqueIdentifier}
* within the given scope.
* @param name The name to reserve
* @param scope See {@link makeUniqueIdentifier} for a description of the scope parameter.
*/
reserveIdentifier(name: string, scope: 'global' | 'block'): void;
indent(): string;
dedent(): string;
/**
* Returns a version of `code` with one level of indentation less.
*
* @note If a line has no indentation, it will be kept as is, which
* can cause some lines to dedent and some to be left as they are.
*/
getDedented(code: string): string;
pushBlockScope(): void;
popBlockScope(): void;
generateLog(op: SupportedLogOp, args: Snippet[]): Snippet;
getById(id: string): Snippet | null;
defineVariable(id: string, snippet: Snippet): void;
setBlockExternals(externals: Record<string, Snippet>): void;
clearBlockExternals(): void;
/**
* Types that are used in `return` statements are
* reported using this function, and used to infer
* the return type of the owning function.
*/
reportReturnType(dataType: BaseData): void;
}
/**
* Houses a method on the symbol '$resolve` that returns a
* code string representing it, as opposed to offloading the
* resolution to another mechanism.
*/
export interface SelfResolvable {
[$internal]: unknown;
[$resolve](ctx: ResolutionCtx): ResolvedSnippet;
toString(): string;
}
export declare function isSelfResolvable(value: unknown): value is SelfResolvable;
export interface WithGPUValue<T> {
readonly [$gpuValueOf]: T;
}
export interface WithOwnSnippet {
readonly [$ownSnippet]: Snippet;
}
export declare function getOwnSnippet(value: unknown): Snippet | undefined;
export interface GPUCallable<TArgs extends unknown[] = unknown[]> {
[$gpuCallable]: {
strictSignature?: {
argTypes: (BaseData | BaseData[])[];
returnType: BaseData;
} | undefined;
call(ctx: ResolutionCtx, args: MapValueToSnippet<TArgs>): Snippet;
};
}
export declare function isGPUCallable(value: unknown): value is GPUCallable;
export type WithCast<T = BaseData> = GPUCallable<[v?: Infer<T>]> & {
readonly [$cast]: (v?: Infer<T>) => Infer<T>;
};
export declare function hasCast(value: unknown): value is WithCast;
type AnyFn = (...args: never[]) => unknown;
export type DualFn<T extends AnyFn> = T & GPUCallable<Parameters<T>>;
export declare function isKnownAtComptime(snippet: Snippet): boolean;
export declare function isWgsl(value: unknown): value is Wgsl;
export type BindableBufferUsage = 'uniform' | 'readonly' | 'mutable';
export type BufferUsage = 'uniform' | 'readonly' | 'mutable' | 'vertex';
export declare function isGPUBuffer(value: unknown): value is GPUBuffer;
export declare function isBuffer(value: unknown): value is TgpuBuffer<BaseData>;
export declare function isUsableAsVertex<T extends TgpuBuffer<BaseData>>(buffer: T): buffer is T & VertexFlag;
export declare function isUsableAsIndex<T extends TgpuBuffer<BaseData>>(buffer: T): buffer is T & IndexFlag;
export declare function isUsableAsUniform<T extends TgpuBuffer<BaseData>>(buffer: T): buffer is T & UniformFlag;
export declare function isUsableAsStorage<T>(value: T): value is T & StorageFlag;
export {};