UNPKG

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