typegpu 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","../src/core/slot/slotTypes.ts","../src/types.ts","../src/core/valueProxyUtils.ts","../src/core/resolve/resolveData.ts","../src/smol/wgslGenerator.ts","../src/data/dataIO.ts","../src/resolutionCtx.ts","../src/core/resolve/tgpuResolve.ts"],"names":["isSlot","value","isDerived","isProviding","isAccessor","UnknownData","isSelfResolvable","isWgsl","isWgslData","isGPUBuffer","isBufferUsage","valueProxyHandler","target","prop","ctx","op","output","code"],"mappings":"AAAA,iIAA8P,wDAAiR,SCiE/fA,CAAAA,CAAUC,CAAAA,CAAoD,CAC5E,MAAA,CAAQA,CAAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAAA,CAAAA,CAAuB,YAAA,CAAA,GAAiB,MAClD,CAEO,SAASC,CAAAA,CACdD,CAAAA,CACY,CACZ,MAAA,CAAQA,CAAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAAA,CAAAA,CAAa,YAAA,CAAA,GAAiB,SACxC,CAEO,SAASE,EAAAA,CACdF,CAAAA,CACsC,CACtC,MAAA,CAAQA,CAAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAAA,CAAAA,CAAwC,YAAA,CAAA,CAAA,GAAkB,KAAA,CACpE,CAEO,SAASG,EAAAA,CACdH,CAAAA,CAC0B,CAC1B,MAAA,CAAQA,CAAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAAA,CAAAA,CAA2B,YAAA,CAAA,GAAiB,UACtD,CChBO,IAAMI,CAAAA,CAAc,CACzB,IAAA,CAAM,SACR,CAAA,CAiFO,SAASC,EAAAA,CAAiBL,CAAAA,CAAyC,CACxE,OAAO,MAAA,CAAQA,CAAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAAA,CAAAA,CAA2B,UAAA,CAAA,CAAA,EAAgB,UAC5D,CAEO,SAASM,EAAAA,CAAON,CAAAA,CAA+B,CACpD,OACE,OAAOA,CAAAA,EAAU,QAAA,EACjB,OAAOA,CAAAA,EAAU,SAAA,EACjB,OAAOA,CAAAA,EAAU,QAAA,EACjBK,EAAAA,CAAiBL,CAAK,CAAA,EACtBO,iCAAAA,CAAgB,CAAA,EAChBR,CAAAA,CAAOC,CAAK,CAAA,EACZC,CAAAA,CAAUD,CAAK,CAAA,EACfE,EAAAA,CAAYF,CAAK,CAErB,CAKO,SAASQ,EAAAA,CAAYR,CAAAA,CAAoC,CAC9D,MACE,CAAC,CAACA,CAAAA,EACF,OAAOA,CAAAA,EAAU,QAAA,EACjB,gBAAA,GAAoBA,CAAAA,EACpB,UAAA,GAAcA,CAElB,CAEO,SAASS,EAAAA,CAKdT,CAAAA,CAAgC,CAChC,MAAA,CAAQA,CAAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAAA,CAAAA,CAAa,YAAA,CAAA,GAAiB,cACxC,CCpLO,IAAMU,CAAAA,CAA6D,CACxE,GAAA,CAAIC,CAAAA,CAAQC,CAAAA,CAAM,CAChB,EAAA,CAAIA,EAAAA,GAAQD,CAAAA,CACV,OAAO,OAAA,CAAQ,GAAA,CAAIA,CAAAA,CAAQC,CAAI,CAAA,CAGjC,EAAA,CAAIA,CAAAA,GAAS,YAAA,CAIb,OAAO,IAAI,KAAA,CACT,CACE,UAAA,CAAaC,CAAAA,EACX,CAAA,EAAA;AC+FW;AACV,OAAA;AAGA;AAwBU;AACV,OAAA;AAOA;AAAA;ACvGPC;AAkDiE;AAAK;AA6JtE;AA0IQ;AACH;AAPgB;AAkE8B;AAaxB;ACxUvBC;AC8ayC;ACvevCC","file":"/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","sourcesContent":[null,"import type { AnyWgslData } from '../../data';\nimport type { TgpuNamable } from '../../namable';\nimport type { Infer } from '../../shared/repr';\nimport type { Labelled } from '../../types';\nimport type { TgpuFn } from '../function/tgpuFn';\nimport type { TgpuBufferUsage } from './../buffer/bufferUsage';\n\nexport interface TgpuSlot<T> extends TgpuNamable, Labelled {\n readonly resourceType: 'slot';\n '~repr': Infer<T>;\n\n readonly defaultValue: T | undefined;\n\n /**\n * Used to determine if code generated using either value `a` or `b` in place\n * of the slot will be equivalent. Defaults to `Object.is`.\n */\n areEqual(a: T, b: T): boolean;\n\n readonly value: Infer<T>;\n}\n\nexport interface TgpuDerived<T> {\n readonly resourceType: 'derived';\n readonly value: Infer<T>;\n '~repr': Infer<T>;\n readonly '~providing'?: Providing | undefined;\n\n with<TValue>(slot: TgpuSlot<TValue>, value: Eventual<TValue>): TgpuDerived<T>;\n\n /**\n * @internal\n */\n '~compute'(): T;\n}\n\nexport interface TgpuAccessor<T extends AnyWgslData = AnyWgslData>\n extends TgpuNamable,\n Labelled {\n readonly resourceType: 'accessor';\n '~repr': Infer<T>;\n\n readonly schema: T;\n readonly defaultValue:\n | TgpuFn<[], T>\n | TgpuBufferUsage<T>\n | Infer<T>\n | undefined;\n readonly slot: TgpuSlot<TgpuFn<[], T> | TgpuBufferUsage<T> | Infer<T>>;\n\n readonly value: Infer<T>;\n}\n\n/**\n * Represents a value that is available at resolution time.\n */\nexport type Eventual<T> = T | TgpuSlot<T> | TgpuDerived<T>;\n\nexport type SlotValuePair<T = unknown> = [TgpuSlot<T>, T];\n\nexport type Providing = {\n inner: unknown;\n pairs: SlotValuePair[];\n};\n\nexport function isSlot<T>(value: unknown | TgpuSlot<T>): value is TgpuSlot<T> {\n return (value as TgpuSlot<T>)?.resourceType === 'slot';\n}\n\nexport function isDerived<T extends TgpuDerived<unknown>>(\n value: T | unknown,\n): value is T {\n return (value as T)?.resourceType === 'derived';\n}\n\nexport function isProviding(\n value: unknown,\n): value is { '~providing': Providing } {\n return (value as { '~providing': Providing })?.['~providing'] !== undefined;\n}\n\nexport function isAccessor<T extends AnyWgslData>(\n value: unknown | TgpuAccessor<T>,\n): value is TgpuAccessor<T> {\n return (value as TgpuAccessor<T>)?.resourceType === 'accessor';\n}\n","import type { Block } from 'tinyest';\nimport type {\n TgpuBufferMutable,\n TgpuBufferReadonly,\n TgpuBufferUniform,\n TgpuBufferUsage,\n} from './core/buffer/bufferUsage';\nimport type { TgpuConst } from './core/constant/tgpuConstant';\nimport type { TgpuDeclare } from './core/declare/tgpuDeclare';\nimport type { TgpuComputeFn } from './core/function/tgpuComputeFn';\nimport type { TgpuFn } from './core/function/tgpuFn';\nimport type { TgpuFragmentFn } from './core/function/tgpuFragmentFn';\nimport type { TgpuVertexFn } from './core/function/tgpuVertexFn';\nimport type { TgpuComputePipeline } from './core/pipeline/computePipeline';\nimport type { TgpuRenderPipeline } from './core/pipeline/renderPipeline';\nimport type { TgpuSampler } from './core/sampler/sampler';\nimport {\n type Eventual,\n type SlotValuePair,\n type TgpuAccessor,\n isDerived,\n isProviding,\n isSlot,\n} from './core/slot/slotTypes';\nimport type { TgpuExternalTexture } from './core/texture/externalTexture';\nimport type { TgpuAnyTextureView, TgpuTexture } from './core/texture/texture';\nimport type { TgpuVar } from './core/variable/tgpuVariable';\nimport type { AnyData } from './data';\nimport {\n type AbstractFloat,\n type AbstractInt,\n type AnyMatInstance,\n type AnyVecInstance,\n type AnyWgslData,\n type BaseData,\n isWgslData,\n} from './data/wgslTypes';\nimport type { NameRegistry } from './nameRegistry';\nimport type { Infer } from './shared/repr';\nimport type {\n TgpuBindGroupLayout,\n TgpuLayoutEntry,\n} from './tgpuBindGroupLayout';\n\nexport type ResolvableObject =\n | SelfResolvable\n | TgpuBufferUsage\n | TgpuConst\n | TgpuDeclare\n | TgpuFn\n | TgpuComputeFn\n | TgpuFragmentFn\n | TgpuComputePipeline\n | TgpuRenderPipeline\n | TgpuVertexFn\n | TgpuSampler\n | TgpuAccessor\n | TgpuExternalTexture\n | TgpuTexture\n | TgpuAnyTextureView\n | TgpuVar\n | AnyVecInstance\n | AnyMatInstance\n | AnyData\n // biome-ignore lint/suspicious/noExplicitAny: <has to be more permissive than unknown>\n | TgpuFn<any, any>;\n\nexport type Wgsl = Eventual<string | number | boolean | ResolvableObject>;\n\nexport const UnknownData = {\n type: 'unknown',\n};\nexport type UnknownData = typeof UnknownData;\n\nexport type Resource = {\n value: unknown;\n dataType: AnyWgslData | UnknownData | AbstractInt | AbstractFloat;\n};\n\nexport type TgpuShaderStage = 'compute' | 'vertex' | 'fragment';\n\nexport interface FnToWgslOptions {\n args: Resource[];\n returnType: AnyWgslData;\n body: Block;\n externalMap: Record<string, unknown>;\n}\n\n/**\n * Passed into each resolvable item. All items in a tree share a resolution ctx,\n * but there can be layers added and removed from the item stack when going down\n * and up the tree.\n */\nexport interface ResolutionCtx {\n readonly names: NameRegistry;\n\n addDeclaration(declaration: string): void;\n\n /**\n * Reserves a bind group number, and returns a placeholder that will be replaced\n * with a concrete number at the end of the resolution process.\n */\n allocateLayoutEntry(layout: TgpuBindGroupLayout): string;\n\n /**\n * Reserves a spot in the catch-all bind group, without the indirection of a bind-group.\n * This means the resource is 'fixed', and cannot be swapped between code execution.\n */\n allocateFixedEntry(\n layoutEntry: TgpuLayoutEntry,\n resource: object,\n ): {\n group: string;\n binding: number;\n };\n\n withSlots<T>(pairs: SlotValuePair<unknown>[], callback: () => T): T;\n\n /**\n * Unwraps all layers of slot/derived indirection and returns the concrete value if available.\n * @throws {MissingSlotValueError}\n */\n unwrap<T>(eventual: Eventual<T>): T;\n\n resolve(item: unknown): string;\n resolveValue<T extends BaseData>(value: Infer<T>, schema: T): string;\n\n transpileFn(fn: string): {\n argNames: string[];\n body: Block;\n externalNames: string[];\n };\n fnToWgsl(options: FnToWgslOptions): {\n head: Wgsl;\n body: Wgsl;\n };\n}\n\n/**\n * Houses a method '~resolve` that returns a code string\n * representing it, as opposed to offloading the resolution\n * to another mechanism.\n */\nexport interface SelfResolvable {\n '~resolve'(ctx: ResolutionCtx): string;\n toString(): string;\n}\n\nexport interface Labelled {\n readonly label?: string | undefined;\n}\n\nexport function isSelfResolvable(value: unknown): value is SelfResolvable {\n return typeof (value as SelfResolvable)?.['~resolve'] === 'function';\n}\n\nexport function isWgsl(value: unknown): value is Wgsl {\n return (\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n typeof value === 'string' ||\n isSelfResolvable(value) ||\n isWgslData(value) ||\n isSlot(value) ||\n isDerived(value) ||\n isProviding(value)\n );\n}\n\nexport type BindableBufferUsage = 'uniform' | 'readonly' | 'mutable';\nexport type BufferUsage = 'uniform' | 'readonly' | 'mutable' | 'vertex';\n\nexport function isGPUBuffer(value: unknown): value is GPUBuffer {\n return (\n !!value &&\n typeof value === 'object' &&\n 'getMappedRange' in value &&\n 'mapAsync' in value\n );\n}\n\nexport function isBufferUsage<\n T extends\n | TgpuBufferUniform<BaseData>\n | TgpuBufferReadonly<BaseData>\n | TgpuBufferMutable<BaseData>,\n>(value: T | unknown): value is T {\n return (value as T)?.resourceType === 'buffer-usage';\n}\n","import {\n type Labelled,\n type ResolutionCtx,\n type SelfResolvable,\n isBufferUsage,\n} from '../types';\nimport { isAccessor, isDerived, isSlot } from './slot/slotTypes';\n\nexport const valueProxyHandler: ProxyHandler<SelfResolvable & Labelled> = {\n get(target, prop) {\n if (prop in target) {\n return Reflect.get(target, prop);\n }\n\n if (prop === '~providing') {\n return undefined;\n }\n\n return new Proxy(\n {\n '~resolve': (ctx: ResolutionCtx) =>\n `${ctx.resolve(target)}.${String(prop)}`,\n\n toString: () =>\n `.value(...).${String(prop)}:${target.label ?? '<unnamed>'}`,\n },\n valueProxyHandler,\n );\n },\n};\n\nexport function unwrapProxy<T>(value: unknown): T {\n let unwrapped = value;\n\n while (\n isSlot(unwrapped) ||\n isDerived(unwrapped) ||\n isAccessor(unwrapped) ||\n isBufferUsage(unwrapped)\n ) {\n unwrapped = unwrapped.value;\n }\n\n return unwrapped as T;\n}\n","import { getAttributesString } from '../../data/attributes';\nimport {\n type AnyData,\n type Disarray,\n type Unstruct,\n isLooseData,\n} from '../../data/dataTypes';\nimport type { WgslStruct } from '../../data/struct';\nimport { formatToWGSLType } from '../../data/vertexFormatData';\nimport type {\n AnyWgslData,\n BaseData,\n Bool,\n F16,\n F32,\n I32,\n Mat2x2f,\n Mat3x3f,\n Mat4x4f,\n U32,\n Vec2f,\n Vec2h,\n Vec2i,\n Vec2u,\n Vec3f,\n Vec3h,\n Vec3i,\n Vec3u,\n Vec4f,\n Vec4h,\n Vec4i,\n Vec4u,\n WgslArray,\n} from '../../data/wgslTypes';\nimport { assertExhaustive } from '../../shared/utilityTypes';\nimport type { ResolutionCtx } from '../../types';\nimport { isAttribute } from '../vertexLayout/connectAttributesToShader';\n\n/**\n * Schemas for which their `type` property directly\n * translates to the resulting WGSL code.\n */\nconst identityTypes = [\n 'bool',\n 'f32',\n 'f16',\n 'i32',\n 'u32',\n 'vec2f',\n 'vec3f',\n 'vec4f',\n 'vec2h',\n 'vec3h',\n 'vec4h',\n 'vec2i',\n 'vec3i',\n 'vec4i',\n 'vec2u',\n 'vec3u',\n 'vec4u',\n 'mat2x2f',\n 'mat3x3f',\n 'mat4x4f',\n];\n\ntype IdentityType =\n | Bool\n | F32\n | F16\n | I32\n | U32\n | Vec2f\n | Vec3f\n | Vec4f\n | Vec2h\n | Vec3h\n | Vec4h\n | Vec2i\n | Vec3i\n | Vec4i\n | Vec2u\n | Vec3u\n | Vec4u\n | Mat2x2f\n | Mat3x3f\n | Mat4x4f;\n\nfunction isIdentityType(data: AnyWgslData): data is IdentityType {\n return identityTypes.includes(data.type);\n}\n\n/**\n * Resolves a single property of a struct.\n * @param ctx - The resolution context.\n * @param key - The key of the property.\n * @param property - The property itself.\n *\n * @returns The resolved property string.\n */\nfunction resolveStructProperty(\n ctx: ResolutionCtx,\n [key, property]: [string, BaseData],\n) {\n return ` ${getAttributesString(property)}${key}: ${ctx.resolve(property as AnyWgslData)},\\n`;\n}\n\n/**\n * Resolves a struct and adds its declaration to the resolution context.\n * @param ctx - The resolution context.\n * @param struct - The struct to resolve.\n *\n * @returns The resolved struct name.\n */\nfunction resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) {\n const id = ctx.names.makeUnique(struct.label);\n\n ctx.addDeclaration(`\nstruct ${id} {\n${Object.entries(struct.propTypes)\n .map((prop) => resolveStructProperty(ctx, prop))\n .join('')}\\\n}\\n`);\n\n return id;\n}\n\n/**\n * Resolves an unstruct (struct that does not align data by default) to its struct data counterpart.\n * @param ctx - The resolution context.\n * @param unstruct - The unstruct to resolve.\n *\n * @returns The resolved unstruct name.\n *\n * @example\n * ```ts\n * resolveUnstruct(ctx, {\n * uv: d.float16x2, // -> d.vec2f after resolution\n * color: d.snorm8x4, -> d.vec4f after resolution\n * });\n * ```\n */\nfunction resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) {\n const id = ctx.names.makeUnique(unstruct.label);\n\n ctx.addDeclaration(`\nstruct ${id} {\n${Object.entries(unstruct.propTypes)\n .map((prop) =>\n isAttribute(prop[1])\n ? resolveStructProperty(ctx, [prop[0], formatToWGSLType[prop[1].format]])\n : resolveStructProperty(ctx, prop),\n )\n .join('')}\n}\\n`);\n\n return id;\n}\n\n/**\n * Resolves an array.\n * @param ctx - The resolution context.\n * @param array - The array to resolve.\n *\n * @returns The resolved array name along with its element type and count (if not runtime-sized).\n *\n * @example\n * ```ts\n * resolveArray(ctx, d.arrayOf(d.u32, 0)); // 'array<u32>' (not a real pattern, a function is preferred)\n * resolveArray(ctx, d.arrayOf(d.u32, 5)); // 'array<u32, 5>'\n * ```\n */\nfunction resolveArray(ctx: ResolutionCtx, array: WgslArray) {\n const element = ctx.resolve(array.elementType as AnyWgslData);\n\n return array.elementCount === 0\n ? `array<${element}>`\n : `array<${element}, ${array.elementCount}>`;\n}\n\nfunction resolveDisarray(ctx: ResolutionCtx, disarray: Disarray) {\n const element = ctx.resolve(\n isAttribute(disarray.elementType)\n ? formatToWGSLType[disarray.elementType.format]\n : (disarray.elementType as AnyWgslData),\n );\n\n return disarray.elementCount === 0\n ? `array<${element}>`\n : `array<${element}, ${disarray.elementCount}>`;\n}\n\n/**\n * Resolves a WGSL data-type schema to a string.\n * @param ctx - The resolution context.\n * @param data - The data-type to resolve.\n *\n * @returns The resolved data-type string.\n */\nexport function resolveData(ctx: ResolutionCtx, data: AnyData): string {\n if (isLooseData(data)) {\n if (data.type === 'unstruct') {\n return resolveUnstruct(ctx, data);\n }\n\n if (data.type === 'disarray') {\n return resolveDisarray(ctx, data);\n }\n\n if (data.type === 'loose-decorated') {\n return ctx.resolve(\n isAttribute(data.inner)\n ? formatToWGSLType[data.inner.format]\n : data.inner,\n );\n }\n\n return ctx.resolve(formatToWGSLType[data.type]);\n }\n\n if (isIdentityType(data)) {\n return data.type;\n }\n\n if (data.type === 'struct') {\n return resolveStruct(ctx, data);\n }\n\n if (data.type === 'array') {\n return resolveArray(ctx, data);\n }\n\n if (data.type === 'atomic') {\n return `atomic<${resolveData(ctx, data.inner)}>`;\n }\n\n if (data.type === 'decorated') {\n return ctx.resolve(data.inner as AnyWgslData);\n }\n\n if (data.type === 'ptr') {\n if (data.addressSpace === 'storage') {\n return `ptr<storage, ${ctx.resolve(data.inner)}, ${data.access === 'read-write' ? 'read_write' : data.access}>`;\n }\n return `ptr<${data.addressSpace}, ${ctx.resolve(data.inner)}>`;\n }\n\n if (data.type === 'abstractInt' || data.type === 'abstractFloat') {\n throw new Error('Abstract types have no concrete representation in WGSL');\n }\n\n assertExhaustive(data, 'resolveData');\n}\n","import type * as smol from 'tinyest';\nimport * as d from '../data';\nimport { abstractFloat, abstractInt } from '../data/numeric';\nimport * as wgsl from '../data/wgslTypes';\nimport {\n type ResolutionCtx,\n type Resource,\n UnknownData,\n type Wgsl,\n isWgsl,\n} from '../types';\n\nconst parenthesizedOps = [\n '==',\n '!=',\n '<',\n '<=',\n '>',\n '>=',\n '<<',\n '>>',\n '+',\n '-',\n '*',\n '/',\n '%',\n '|',\n '^',\n '&',\n '&&',\n '||',\n];\n\nfunction binaryOperatorToType<\n TL extends wgsl.AnyWgslData | UnknownData,\n TR extends wgsl.AnyWgslData | UnknownData,\n>(\n lhs: TL,\n op: smol.BinaryOperator | smol.AssignmentOperator | smol.LogicalOperator,\n rhs: TR,\n): TL | TR | wgsl.Bool {\n if (\n op === '==' ||\n op === '!=' ||\n op === '<' ||\n op === '<=' ||\n op === '>' ||\n op === '>=' ||\n op === '&&' ||\n op === '||'\n ) {\n return d.bool;\n }\n\n if (op === '=') {\n return rhs;\n }\n\n return lhs;\n}\n\nexport type GenerationCtx = ResolutionCtx & {\n readonly pre: string;\n readonly callStack: unknown[];\n indent(): string;\n dedent(): string;\n pushBlockScope(): void;\n popBlockScope(): void;\n getById(id: string): Resource | null;\n defineVariable(\n id: string,\n dataType: wgsl.AnyWgslData | UnknownData,\n ): Resource;\n};\n\nexport function resolveRes(ctx: GenerationCtx, res: Resource): string {\n if (isWgsl(res.value) || wgsl.isWgslData(res.value)) {\n return ctx.resolve(res.value);\n }\n\n return String(res.value);\n}\n\nfunction assertExhaustive(value: never): never {\n throw new Error(\n `'${JSON.stringify(value)}' was not handled by the WGSL generator.`,\n );\n}\n\nexport function generateBoolean(ctx: GenerationCtx, value: boolean): Resource {\n return value\n ? { value: 'true', dataType: d.bool }\n : { value: 'false', dataType: d.bool };\n}\n\nexport function generateBlock(ctx: GenerationCtx, value: smol.Block): string {\n ctx.pushBlockScope();\n try {\n return `${ctx.indent()}{\n${value.b.map((statement) => generateStatement(ctx, statement)).join('\\n')}\n${ctx.dedent()}}`;\n } finally {\n ctx.popBlockScope();\n }\n}\n\nexport function registerBlockVariable(\n ctx: GenerationCtx,\n id: string,\n dataType: wgsl.AnyWgslData | UnknownData,\n): Resource {\n return ctx.defineVariable(id, dataType);\n}\n\nexport function generateIdentifier(ctx: GenerationCtx, id: string): Resource {\n const res = ctx.getById(id);\n if (!res) {\n throw new Error(`Identifier ${id} not found`);\n }\n\n return res;\n}\n\nexport function generateExpression(\n ctx: GenerationCtx,\n expression: smol.Expression,\n): Resource {\n if (typeof expression === 'string') {\n return generateIdentifier(ctx, expression);\n }\n\n if (typeof expression === 'boolean') {\n return generateBoolean(ctx, expression);\n }\n\n if ('x' in expression) {\n // Logical/Binary/Assignment Expression\n\n const [lhs, op, rhs] = expression.x;\n const lhsExpr = generateExpression(ctx, lhs);\n const lhsStr = resolveRes(ctx, lhsExpr);\n const rhsExpr = generateExpression(ctx, rhs);\n const rhsStr = resolveRes(ctx, rhsExpr);\n\n const type = binaryOperatorToType(lhsExpr.dataType, op, rhsExpr.dataType);\n\n return {\n value: parenthesizedOps.includes(op)\n ? `(${lhsStr} ${op} ${rhsStr})`\n : `${lhsStr} ${op} ${rhsStr}`,\n dataType: type,\n };\n }\n\n if ('u' in expression) {\n // Unary Expression\n\n const [op, arg] = expression.u;\n const argExpr = resolveRes(ctx, generateExpression(ctx, arg));\n return {\n value: `${op}${argExpr}`,\n // TODO: Infer data type from expression type and arguments.\n dataType: UnknownData,\n };\n }\n\n if ('a' in expression) {\n // Member Access\n\n const [targetId, property] = expression.a;\n const target = generateExpression(ctx, targetId);\n const propertyStr = resolveRes(ctx, generateExpression(ctx, property));\n\n if (typeof target.value === 'string') {\n return {\n value: `${target.value}.${propertyStr}`,\n // TODO: Infer data type\n dataType: UnknownData,\n };\n }\n\n if (isWgsl(target.value)) {\n return {\n // biome-ignore lint/suspicious/noExplicitAny: <sorry TypeScript>\n value: (target.value as any)[propertyStr],\n // TODO: Infer data type\n dataType: UnknownData,\n };\n }\n\n if (typeof target.value === 'object') {\n return {\n // biome-ignore lint/suspicious/noExplicitAny: <sorry TypeScript>\n value: (target.value as any)[propertyStr],\n // TODO: Infer data type\n dataType: UnknownData,\n };\n }\n\n throw new Error(`Cannot access member ${propertyStr} of ${target.value}`);\n }\n\n if ('i' in expression) {\n // Index Access\n\n const [target, property] = expression.i;\n const targetStr = resolveRes(ctx, generateExpression(ctx, target));\n const propertyStr = resolveRes(ctx, generateExpression(ctx, property));\n\n return {\n value: `${targetStr}[${propertyStr}]`,\n // TODO: Infer data type\n dataType: UnknownData,\n };\n }\n\n if ('n' in expression) {\n // Numeric Literal\n const value = expression.n;\n\n // Hex literals (since JS does not have float hex literals, we'll assume it's an int)\n const hexRegex = /^0x[0-9a-f]+$/i;\n if (hexRegex.test(value)) {\n return { value: value, dataType: abstractInt };\n }\n\n // Binary literals\n const binRegex = /^0b[01]+$/i;\n if (binRegex.test(value)) {\n // Since wgsl doesn't support binary literals, we'll convert it to a decimal number\n return {\n value: `${Number.parseInt(value.slice(2), 2)}`,\n dataType: abstractInt,\n };\n }\n\n const floatRegex = /^-?(?:\\d+\\.\\d*|\\d*\\.\\d+)$/;\n if (floatRegex.test(value)) {\n return { value, dataType: abstractFloat };\n }\n\n // Floating point literals with scientific notation\n const sciFloatRegex = /^-?\\d+\\.\\d+e-?\\d+$/;\n if (sciFloatRegex.test(value)) {\n return {\n value: value,\n dataType: abstractFloat,\n };\n }\n\n // Integer literals\n const intRegex = /^-?\\d+$/;\n if (intRegex.test(value)) {\n return { value: value, dataType: abstractInt };\n }\n\n throw new Error(`Invalid numeric literal ${value}`);\n }\n\n if ('f' in expression) {\n // Function Call\n\n const [callee, args] = expression.f;\n const id = generateExpression(ctx, callee);\n const idValue = id.value;\n\n ctx.callStack.push(idValue);\n\n const argResources = args.map((arg) => generateExpression(ctx, arg));\n const argValues = argResources.map((res) => resolveRes(ctx, res));\n\n ctx.callStack.pop();\n\n if (typeof idValue === 'string') {\n return {\n value: `${idValue}(${argValues.join(', ')})`,\n dataType: UnknownData,\n };\n }\n\n if (wgsl.isWgslStruct(idValue)) {\n const id = ctx.resolve(idValue);\n\n return {\n value: `${id}(${argValues.join(', ')})`,\n dataType: UnknownData,\n };\n }\n\n // Assuming that `id` is callable\n // TODO: Pass in resources, not just values.\n const result = (idValue as unknown as (...args: unknown[]) => unknown)(\n ...argValues,\n ) as Wgsl;\n // TODO: Make function calls return resources instead of just values.\n return { value: result, dataType: UnknownData };\n }\n\n if ('o' in expression) {\n const obj = expression.o;\n const callee = ctx.callStack[ctx.callStack.length - 1];\n\n const generateEntries = (values: smol.Expression[]) =>\n values\n .map((value) => {\n const valueRes = generateExpression(ctx, value);\n return resolveRes(ctx, valueRes);\n })\n .join(', ');\n\n if (wgsl.isWgslStruct(callee)) {\n const propKeys = Object.keys(callee.propTypes);\n const values = propKeys.map((key) => {\n const val = obj[key];\n if (val === undefined) {\n throw new Error(\n `Missing property ${key} in object literal for struct ${callee}`,\n );\n }\n return val;\n });\n\n return {\n value: generateEntries(values),\n dataType: callee,\n };\n }\n\n return {\n value: generateEntries(Object.values(obj)),\n dataType: UnknownData,\n };\n }\n\n if ('s' in expression) {\n throw new Error('Cannot use string literals in TGSL.');\n }\n\n assertExhaustive(expression);\n}\n\nexport function generateStatement(\n ctx: GenerationCtx,\n statement: smol.Statement,\n): string {\n if (typeof statement === 'string') {\n return `${ctx.pre}${resolveRes(ctx, generateIdentifier(ctx, statement))};`;\n }\n\n if (typeof statement === 'boolean') {\n return `${ctx.pre}${resolveRes(ctx, generateBoolean(ctx, statement))};`;\n }\n\n if ('r' in statement) {\n // check if the thing at the top of the call stack is a struct and the statement is a plain JS object\n // if so wrap the value returned in a constructor of the struct (its resolved name)\n if (\n wgsl.isWgslStruct(ctx.callStack[ctx.callStack.length - 1]) &&\n statement.r !== null &&\n typeof statement.r === 'object' &&\n 'o' in statement.r\n ) {\n const resource = resolveRes(ctx, generateExpression(ctx, statement.r));\n const resolvedStruct = ctx.resolve(\n ctx.callStack[ctx.callStack.length - 1],\n );\n return `${ctx.pre}return ${resolvedStruct}(${resource});`;\n }\n\n return statement.r === null\n ? `${ctx.pre}return;`\n : `${ctx.pre}return ${resolveRes(ctx, generateExpression(ctx, statement.r))};`;\n }\n\n if ('q' in statement) {\n const [cond, cons, alt] = statement.q;\n const condition = resolveRes(ctx, generateExpression(ctx, cond));\n\n ctx.indent(); // {\n const consequent = generateStatement(ctx, cons);\n ctx.dedent(); // }\n\n ctx.indent(); // {\n const alternate = alt ? generateStatement(ctx, alt) : undefined;\n ctx.dedent(); // }\n\n if (!alternate) {\n return `\\\n${ctx.pre}if (${condition})\n${consequent}`;\n }\n\n return `\\\n${ctx.pre}if (${condition})\n${consequent}\n${ctx.pre}else\n${alternate}`;\n }\n\n if ('l' in statement || 'c' in statement) {\n const [rawId, rawValue] = 'l' in statement ? statement.l : statement.c;\n const eq = rawValue ? generateExpression(ctx, rawValue) : undefined;\n\n if (!eq || !rawValue) {\n throw new Error('Cannot create variable without an initial value.');\n }\n\n registerBlockVariable(ctx, rawId, eq.dataType);\n const id = resolveRes(ctx, generateIdentifier(ctx, rawId));\n\n // If the value is a plain JS object it has to be an output struct\n if (\n typeof rawValue === 'object' &&\n 'o' in rawValue &&\n wgsl.isWgslStruct(ctx.callStack[ctx.callStack.length - 1])\n ) {\n const resolvedStruct = ctx.resolve(\n ctx.callStack[ctx.callStack.length - 1],\n );\n return `${ctx.pre}var ${id} = ${resolvedStruct}(${resolveRes(ctx, eq)});`;\n }\n\n return `${ctx.pre}var ${id} = ${resolveRes(ctx, eq)};`;\n }\n\n if ('b' in statement) {\n ctx.pushBlockScope();\n try {\n return generateBlock(ctx, statement);\n } finally {\n ctx.popBlockScope();\n }\n }\n\n // 'j' stands for for (trust me)\n if ('j' in statement) {\n const [init, condition, update, body] = statement.j;\n\n const initStatement = init ? generateStatement(ctx, init) : undefined;\n const initStr = initStatement ? initStatement.slice(0, -1) : '';\n\n const conditionExpr = condition\n ? generateExpression(ctx, condition)\n : undefined;\n const conditionStr = conditionExpr ? resolveRes(ctx, conditionExpr) : '';\n\n const updateStatement = update ? generateStatement(ctx, update) : undefined;\n const updateStr = updateStatement ? updateStatement.slice(0, -1) : '';\n\n ctx.indent();\n const bodyStr = generateStatement(ctx, body);\n ctx.dedent();\n\n return `\\\n${ctx.pre}for (${initStr}; ${conditionStr}; ${updateStr})\n${bodyStr}`;\n }\n\n if ('w' in statement) {\n const [condition, body] = statement.w;\n const conditionStr = resolveRes(ctx, generateExpression(ctx, condition));\n\n ctx.indent();\n const bodyStr = generateStatement(ctx, body);\n ctx.dedent();\n\n return `\\\n${ctx.pre}while (${conditionStr})\n${bodyStr}`;\n }\n\n if ('k' in statement) {\n return `${ctx.pre}continue;`;\n }\n\n if ('d' in statement) {\n return `${ctx.pre}break;`;\n }\n\n return `${ctx.pre}${resolveRes(ctx, generateExpression(ctx, statement))};`;\n}\n\nexport function generateFunction(ctx: GenerationCtx, body: smol.Block): string {\n return generateBlock(ctx, body);\n}\n","import type { ISerialInput, ISerialOutput } from 'typed-binary';\nimport type { Infer, InferRecord } from '../shared/repr';\nimport alignIO from './alignIO';\nimport { alignmentOf, customAlignmentOf } from './alignmentOf';\nimport type {\n AnyConcreteData,\n AnyData,\n Disarray,\n LooseDecorated,\n Unstruct,\n} from './dataTypes';\nimport { mat2x2f, mat3x3f, mat4x4f } from './matrix';\nimport { sizeOf } from './sizeOf';\nimport type { WgslStruct } from './struct';\nimport {\n vec2f,\n vec2h,\n vec2i,\n vec2u,\n vec3f,\n vec3h,\n vec3i,\n vec3u,\n vec4f,\n vec4h,\n vec4i,\n vec4u,\n} from './vector';\nimport type * as wgsl from './wgslTypes';\n\ntype DataWriter<TSchema extends wgsl.BaseData> = (\n output: ISerialOutput,\n schema: TSchema,\n value: Infer<TSchema>,\n) => void;\n\ntype DataReader<TSchema extends wgsl.BaseData> = (\n input: ISerialInput,\n schema: TSchema,\n) => Infer<TSchema>;\n\ntype CompleteDataWriters = {\n [TType in AnyConcreteData['type']]: DataWriter<\n Extract<AnyData, { readonly type: TType }>\n >;\n};\n\ntype CompleteDataReaders = {\n [TType in AnyConcreteData['type']]: DataReader<\n Extract<AnyData, { readonly type: TType }>\n >;\n};\n\nconst dataWriters = {\n bool(output, _schema: wgsl.Bool, value: boolean) {\n output.writeBool(value);\n },\n\n f32(output, _schema: wgsl.F32, value: number) {\n output.writeFloat32(value);\n },\n\n f16(output, _schema: wgsl.F16, value: number) {\n output.writeFloat16(value);\n },\n\n i32(output, _schema: wgsl.I32, value: number) {\n output.writeInt32(value);\n },\n\n u32(output, _schema: wgsl.U32, value: number) {\n output.writeUint32(value);\n },\n\n vec2f(output, _, value: wgsl.v2f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n },\n\n vec2h(output, _, value: wgsl.v2h) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n },\n\n vec2i(output, _, value: wgsl.v2i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n },\n\n vec2u(output, _, value: wgsl.v2u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n },\n\n vec3f(output, _, value: wgsl.v3f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n output.writeFloat32(value.z);\n },\n\n vec3h(output, _, value: wgsl.v3h) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n output.writeFloat16(value.z);\n },\n\n vec3i(output, _, value: wgsl.v3i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n output.writeInt32(value.z);\n },\n\n vec3u(output, _, value: wgsl.v3u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n output.writeUint32(value.z);\n },\n\n vec4f(output, _, value: wgsl.v4f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n output.writeFloat32(value.z);\n output.writeFloat32(value.w);\n },\n\n vec4h(output, _, value: wgsl.v4h) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n output.writeFloat16(value.z);\n output.writeFloat16(value.w);\n },\n\n vec4i(output, _, value: wgsl.v4i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n output.writeInt32(value.z);\n output.writeInt32(value.w);\n },\n\n vec4u(output, _, value: wgsl.v4u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n output.writeUint32(value.z);\n output.writeUint32(value.w);\n },\n\n mat2x2f(output, _, value: wgsl.m2x2f) {\n for (let i = 0; i < value.length; ++i) {\n output.writeFloat32(value[i] as number);\n }\n },\n\n mat3x3f(output, _, value: wgsl.m3x3f) {\n for (let i = 0; i < value.length; ++i) {\n output.writeFloat32(value[i] as number);\n }\n },\n\n mat4x4f(output, _, value: wgsl.m4x4f) {\n for (let i = 0; i < value.length; ++i) {\n output.writeFloat32(value[i] as number);\n }\n },\n\n struct(\n output,\n schema: WgslStruct,\n value: InferRecord<Record<string, wgsl.BaseData>>,\n ) {\n const alignment = alignmentOf(schema);\n alignIO(output, alignment);\n\n for (const [key, property] of Object.entries(schema.propTypes)) {\n alignIO(output, alignmentOf(property));\n writeData(output, property, value[key] as wgsl.BaseData);\n }\n\n alignIO(output, alignment);\n },\n\n array(output, schema: wgsl.WgslArray, value: Infer<wgsl.BaseData>[]) {\n if (schema.elementCount === 0) {\n throw new Error('Cannot write using a runtime-sized schema.');\n }\n\n const alignment = alignmentOf(schema);\n alignIO(output, alignment);\n const beginning = output.currentByteOffset;\n for (let i = 0; i < Math.min(schema.elementCount, value.length); i++) {\n alignIO(output, alignment);\n writeData(output, schema.elementType, value[i]);\n }\n output.seekTo(beginning + sizeOf(schema));\n },\n\n ptr() {\n throw new Error('Pointers are not host-shareable');\n },\n\n atomic(output, schema: wgsl.Atomic, value: number) {\n dataWriters[schema.inner.type]?.(output, schema, value);\n },\n\n decorated(output, schema: wgsl.Decorated, value: unknown) {\n const alignment = customAlignmentOf(schema);\n alignIO(output, alignment);\n\n const beginning = output.currentByteOffset;\n dataWriters[(schema.inner as AnyData)?.type]?.(output, schema.inner, value);\n output.seekTo(beginning + sizeOf(schema));\n },\n\n // Loose Types\n\n uint8(output, _, value: number) {\n output.writeUint8(value);\n },\n uint8x2(output, _, value: wgsl.v2u) {\n output.writeUint8(value.x);\n output.writeUint8(value.y);\n },\n uint8x4(output, _, value: wgsl.v4u) {\n output.writeUint8(value.x);\n output.writeUint8(value.y);\n output.writeUint8(value.z);\n output.writeUint8(value.w);\n },\n sint8(output, _, value: number) {\n output.writeInt8(value);\n },\n sint8x2(output, _, value: wgsl.v2i) {\n output.writeInt8(value.x);\n output.writeInt8(value.y);\n },\n sint8x4(output, _, value: wgsl.v4i) {\n output.writeInt8(value.x);\n output.writeInt8(value.y);\n output.writeInt8(value.z);\n output.writeInt8(value.w);\n },\n unorm8(output, _, value: number) {\n output.writeUint8(value * 255);\n },\n unorm8x2(output, _, value: wgsl.v2f) {\n output.writeUint8(value.x * 255);\n output.writeUint8(value.y * 255);\n },\n unorm8x4(output, _, value: wgsl.v4f) {\n output.writeUint8(value.x * 255);\n output.writeUint8(value.y * 255);\n output.writeUint8(value.z * 255);\n output.writeUint8(value.w * 255);\n },\n snorm8(output, _, value: number) {\n output.writeUint8(value * 127 + 128);\n },\n snorm8x2(output, _, value: wgsl.v2f) {\n output.writeUint8(value.x * 127 + 128);\n output.writeUint8(value.y * 127 + 128);\n },\n snorm8x4(output, _, value: wgsl.v4f) {\n output.writeUint8(value.x * 127 + 128);\n output.writeUint8(value.y * 127 + 128);\n output.writeUint8(value.z * 127 + 128);\n output.writeUint8(value.w * 127 + 128);\n },\n uint16(output, _, value: number) {\n output.writeUint16(value);\n },\n uint16x2(output, _, value: wgsl.v2u) {\n output.writeUint16(value.x);\n output.writeUint16(value.y);\n },\n uint16x4(output, _, value: wgsl.v4u) {\n output.writeUint16(value.x);\n output.writeUint16(value.y);\n output.writeUint16(value.z);\n output.writeUint16(value.w);\n },\n sint16(output, _, value: number) {\n output.writeInt16(value);\n },\n sint16x2(output, _, value: wgsl.v2i) {\n output.writeInt16(value.x);\n output.writeInt16(value.y);\n },\n sint16x4(output, _, value: wgsl.v4i) {\n output.writeInt16(value.x);\n output.writeInt16(value.y);\n output.writeInt16(value.z);\n output.writeInt16(value.w);\n },\n unorm16(output, _, value: number) {\n output.writeUint16(value * 65535);\n },\n unorm16x2(output, _, value: wgsl.v2f) {\n output.writeUint16(value.x * 65535);\n output.writeUint16(value.y * 65535);\n },\n unorm16x4(output, _, value: wgsl.v4f) {\n output.writeUint16(value.x * 65535);\n output.writeUint16(value.y * 65535);\n output.writeUint16(value.z * 65535);\n output.writeUint16(value.w * 65535);\n },\n snorm16(output, _, value: number) {\n output.writeUint16(value * 32767 + 32768);\n },\n snorm16x2(output, _, value: wgsl.v2f) {\n output.writeUint16(value.x * 32767 + 32768);\n output.writeUint16(value.y * 32767 + 32768);\n },\n snorm16x4(output, _, value: wgsl.v4f) {\n output.writeUint16(value.x * 32767 + 32768);\n output.writeUint16(value.y * 32767 + 32768);\n output.writeUint16(value.z * 32767 + 32768);\n output.writeUint16(value.w * 32767 + 32768);\n },\n float16(output, _, value: number) {\n output.writeFloat16(value);\n },\n float16x2(output, _, value: wgsl.v2f) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n },\n float16x4(output, _, value: wgsl.v4f) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n output.writeFloat16(value.z);\n output.writeFloat16(value.w);\n },\n float32(output, _, value: number) {\n output.writeFloat32(value);\n },\n float32x2(output, _, value: wgsl.v2f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n },\n float32x3(output, _, value: wgsl.v3f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n output.writeFloat32(value.z);\n },\n float32x4(output, _, value: wgsl.v4f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n output.writeFloat32(value.z);\n output.writeFloat32(value.w);\n },\n uint32(output, _, value: number) {\n output.writeUint32(value);\n },\n uint32x2(output, _, value: wgsl.v2u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n },\n uint32x3(output, _, value: wgsl.v3u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n output.writeUint32(value.z);\n },\n uint32x4(output, _, value: wgsl.v4u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n output.writeUint32(value.z);\n output.writeUint32(value.w);\n },\n sint32(output, _, value: number) {\n output.writeInt32(value);\n },\n sint32x2(output, _, value: wgsl.v2i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n },\n sint32x3(output, _, value: wgsl.v3i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n output.writeInt32(value.z);\n },\n sint32x4(output, _, value: wgsl.v4i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n output.writeInt32(value.z);\n output.writeInt32(value.w);\n },\n 'unorm10-10-10-2'(output, _, value: wgsl.v4f) {\n let packed = 0;\n packed |= ((value.x * 1023) & 1023) << 22; // r (10 bits)\n packed |= ((value.x * 1023) & 1023) << 12; // g (10 bits)\n packed |= ((value.y * 1023) & 1023) << 2; // b (10 bits)\n packed |= (value.z * 3) & 3; // a (2 bits)\n output.writeUint32(packed);\n },\n 'unorm8x4-bgra'(output, _, value: wgsl.v4f) {\n output.writeUint8(value.z * 255);\n output.writeUint8(value.y * 255);\n output.writeUint8(value.x * 255);\n output.writeUint8(value.w * 255);\n },\n\n disarray(output, schema: Disarray, value: unknown[]) {\n const alignment = alignmentOf(schema);\n\n alignIO(output, alignment);\n const beginning = output.currentByteOffset;\n for (let i = 0; i < Math.min(schema.elementCount, value.length); i++) {\n alignIO(output, alignment);\n dataWriters[(schema.elementType as AnyData)?.type]?.(\n output,\n schema.elementType,\n value[i],\n );\n }\n\n output.seekTo(beginning + sizeOf(schema));\n },\n\n unstruct(output, schema: Unstruct, value) {\n for (const [key, property] of Object.entries(schema.propTypes)) {\n dataWriters[property.type]?.(output, property, value[key]);\n }\n },\n\n 'loose-decorated'(output, schema: LooseDecorated, value: unknown) {\n const alignment = customAlignmentOf(schema);\n alignIO(output, alignment);\n\n const beginning = output.currentByteOffset;\n const writer = dataWriters[(schema.inner as AnyData)?.type];\n writer?.(output, schema.inner, value);\n output.seekTo(beginning + sizeOf(schema));\n return value;\n },\n} satisfies CompleteDataWriters as Record<\n string,\n (output: ISerialOutput, schema: unknown, value: unknown) => void\n>;\n\nexport function writeData<TData extends wgsl.BaseData>(\n output: ISerialOutput,\n schema: TData,\n value: Infer<TData>,\n): void {\n const writer = dataWriters[schema.type];\n if (!writer) {\n throw new Error(`Cannot write data of type '${schema.type}'.`);\n }\n\n writer(output, schema, value);\n}\n\nconst dataReaders = {\n bool(input: ISerialInput): boolean {\n return input.readBool();\n },\n\n f32(input: ISerialInput): number {\n return input.readFloat32();\n },\n\n f16(input: ISerialInput): number {\n return input.readFloat16();\n },\n\n i32(input: ISerialInput): number {\n return input.readInt32();\n },\n\n u32(input: ISerialInput): number {\n return input.readUint32();\n },\n\n vec2f(input: ISerialInput): wgsl.v2f {\n return vec2f(input.readFloat32(), input.readFloat32());\n },\n\n vec3f(input: ISerialInput): wgsl.v3f {\n return vec3f(input.readFloat32(), input.readFloat32(), input.readFloat32());\n },\n\n vec4f(input: ISerialInput): wgsl.v4f {\n return vec4f(\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n );\n },\n\n vec2h(input): wgsl.v2h {\n return vec2h(input.readFloat16(), input.readFloat16());\n },\n\n vec3h(input: ISerialInput): wgsl.v3h {\n return vec3h(input.readFloat16(), input.readFloat16(), input.readFloat16());\n },\n\n vec4h(input: ISerialInput): wgsl.v4h {\n return vec4h(\n input.readFloat16(),\n input.readFloat16(),\n input.readFloat16(),\n input.readFloat16(),\n );\n },\n\n vec2i(input): wgsl.v2i {\n return vec2i(input.readInt32(), input.readInt32());\n },\n\n vec3i(input: ISerialInput): wgsl.v3i {\n return vec3i(input.readInt32(), input.readInt32(), input.readInt32());\n },\n\n vec4i(input: ISerialInput): wgsl.v4i {\n return vec4i(\n input.readInt32(),\n input.readInt32(),\n input.readInt32(),\n input.readInt32(),\n );\n },\n\n vec2u(input): wgsl.v2u {\n return vec2u(input.readUint32(), input.readUint32());\n },\n\n vec3u(input: ISerialInput): wgsl.v3u {\n return vec3u(input.readUint32(), input.readUint32(), input.readUint32());\n },\n\n vec4u(input: ISerialInput): wgsl.v4u {\n return vec4u(\n input.readUint32(),\n input.readUint32(),\n input.readUint32(),\n input.readUint32(),\n );\n },\n\n mat2x2f(input: ISerialInput): wgsl.m2x2f {\n return mat2x2f(\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n );\n },\n\n mat3x3f(input: ISerialInput): wgsl.m3x3f {\n const skipOneAfter = () => {\n const value = input.readFloat32();\n input.readFloat32(); // skipping;\n return value;\n };\n\n return mat3x3f(\n input.readFloat32(),\n input.readFloat32(),\n skipOneAfter(),\n //\n input.readFloat32(),\n input.readFloat32(),\n skipOneAfter(),\n //\n input.readFloat32(),\n input.readFloat32(),\n skipOneAfter(),\n );\n },\n\n mat4x4f(input: ISerialInput): wgsl.m4x4f {\n return mat4x4f(\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n //\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n //\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n //\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n );\n },\n\n struct(input: ISerialInput, schema: WgslStruct) {\n const alignment = alignmentOf(schema);\n alignIO(input, alignment);\n const result = {} as Record<string, unknown>;\n\n for (const [key, property] of Object.entries(schema.propTypes)) {\n alignIO(input, alignmentOf(property));\n result[key] = readData(input, property);\n }\n\n alignIO(input, alignment);\n return result as InferRecord<Record<string, wgsl.BaseData>>;\n },\n\n array(input, schema) {\n if (schema.elementCount === 0) {\n throw new Error('Cannot read using a runtime-sized schema.');\n }\n\n const alignment = alignmentOf(schema);\n const elements: unknown[] = [];\n\n for (let i = 0; i < schema.elementCount; i++) {\n alignIO(input, alignment);\n const elementType = schema.elementType as wgsl.AnyWgslData;\n const value = readData(input, elementType);\n elements.push(value);\n }\n\n alignIO(input, alignment);\n return elements as never[];\n },\n\n ptr() {\n throw new Error('Pointers are not host-shareable');\n },\n\n atomic(input, schema: wgsl.Atomic): number {\n return readData(input, schema.inner);\n },\n\n decorated(input, schema: wgsl.Decorated) {\n const alignment = customAlignmentOf(schema);\n alignIO(input, alignment);\n\n const beginning = input.currentByteOffset;\n const value = readData(input, schema.inner);\n input.seekTo(beginning + sizeOf(schema));\n return value as never;\n },\n\n // Loose Types\n\n uint8: (i) => i.readUint8(),\n uint8x2: (i) => vec2u(i.readUint8(), i.readUint8()),\n uint8x4: (i) =>\n vec4u(i.readUint8(), i.readUint8(), i.readUint8(), i.readUint8()),\n sint8: (i) => i.readInt8(),\n sint8x2: (i) => {\n return vec2i(i.readInt8(), i.readInt8());\n },\n sint8x4: (i) => vec4i(i.readInt8(), i.readInt8(), i.readInt8(), i.readInt8()),\n unorm8: (i) => i.readUint8() / 255,\n unorm8x2: (i) => vec2f(i.readUint8() / 255, i.readUint8() / 255),\n unorm8x4: (i) =>\n vec4f(\n i.readUint8() / 255,\n i.readUint8() / 255,\n i.readUint8() / 255,\n i.readUint8() / 255,\n ),\n snorm8: (i) => (i.readUint8() - 128) / 127,\n snorm8x2: (i) =>\n vec2f((i.readUint8() - 128) / 127, (i.readUint8() - 128) / 127),\n snorm8x4: (i) =>\n vec4f(\n (i.readUint8() - 128) / 127,\n (i.readUint8() - 128) / 127,\n (i.readUint8() - 128) / 127,\n (i.readUint8() - 128) / 127,\n ),\n uint16: (i) => i.readUint16(),\n uint16x2: (i) => vec2u(i.readUint16(), i.readUint16()),\n uint16x4: (i) =>\n vec4u(i.readUint16(), i.readUint16(), i.readUint16(), i.readUint16()),\n sint16: (i) => i.readInt16(),\n sint16x2: (i) => vec2i(i.readInt16(), i.readInt16()),\n sint16x4: (i) =>\n vec4i(i.readInt16(), i.readInt16(), i.readInt16(), i.readInt16()),\n unorm16: (i) => i.readUint16() / 65535,\n unorm16x2: (i) => vec2f(i.readUint16() / 65535, i.readUint16() / 65535),\n unorm16x4: (i) =>\n vec4f(\n i.readUint16() / 65535,\n i.readUint16() / 65535,\n i.readUint16() / 65535,\n i.readUint16() / 65535,\n ),\n snorm16: (i) => (i.readUint16() - 32768) / 32767,\n snorm16x2: (i): wgsl.v2f =>\n vec2f(dataReaders.snorm16(i), dataReaders.snorm16(i)),\n snorm16x4: (i): wgsl.v4f =>\n vec4f(\n dataReaders.snorm16(i),\n dataReaders.snorm16(i),\n dataReaders.snorm16(i),\n dataReaders.snorm16(i),\n ),\n float16(i) {\n return i.readFloat16();\n },\n float16x2: (i) => vec2f(i.readFloat16(), i.readFloat16()),\n float16x4: (i) =>\n vec4f(i.readFloat16(), i.readFloat16(), i.readFloat16(), i.readFloat16()),\n float32: (i) => i.readFloat32(),\n float32x2: (i) => vec2f(i.readFloat32(), i.readFloat32()),\n float32x3: (i) => vec3f(i.readFloat32(), i.readFloat32(), i.readFloat32()),\n float32x4: (i) =>\n vec4f(i.readFloat32(), i.readFloat32(), i.readFloat32(), i.readFloat32()),\n uint32: (i) => i.readUint32(),\n uint32x2: (i) => vec2u(i.readUint32(), i.readUint32()),\n uint32x3: (i) => vec3u(i.readUint32(), i.readUint32(), i.readUint32()),\n uint32x4: (i) =>\n vec4u(i.readUint32(), i.readUint32(), i.readUint32(), i.readUint32()),\n sint32: (i) => i.readInt32(),\n sint32x2: (i) => vec2i(i.readInt32(), i.readInt32()),\n sint32x3: (i) => vec3i(i.readInt32(), i.readInt32(), i.readInt32()),\n sint32x4: (i) =>\n vec4i(i.readInt32(), i.readInt32(), i.readInt32(), i.readInt32()),\n 'unorm10-10-10-2'(i) {\n const packed = i.readUint32();\n const r = (packed >> 22) / 1023;\n const g = ((packed >> 12) & 1023) / 1023;\n const b = ((packed >> 2) & 1023) / 1023;\n const a = (packed & 3) / 3;\n return vec4f(r, g, b, a);\n },\n 'unorm8x4-bgra'(i) {\n const b = i.readByte() / 255;\n const g = i.readByte() / 255;\n const r = i.readByte() / 255;\n const a = i.readByte() / 255;\n return vec4f(r, g, b, a);\n },\n\n unstruct(input, schema: Unstruct) {\n const result = {} as Record<string, unknown>;\n\n for (const [key, property] of Object.entries(schema.propTypes)) {\n result[key] = readData(input, property);\n }\n\n return result as InferRecord<Record<string, wgsl.BaseData>>;\n },\n\n disarray(input, schema: Disarray) {\n const alignment = alignmentOf(schema);\n const elements: unknown[] = [];\n\n for (let i = 0; i < schema.elementCount; i++) {\n alignIO(input, alignment);\n elements.push(readData(input, schema.elementType));\n }\n\n alignIO(input, alignment);\n return elements;\n },\n\n 'loose-decorated'(input, schema: LooseDecorated) {\n alignIO(input, customAlignmentOf(schema));\n\n const beginning = input.currentByteOffset;\n const value = readData(input, schema.inner);\n input.seekTo(beginning + sizeOf(schema));\n return value;\n },\n} satisfies CompleteDataReaders;\n\nexport function readData<TData extends wgsl.BaseData>(\n input: ISerialInput,\n schema: TData,\n): Infer<TData> {\n const reader = (dataReaders as Record<string, unknown>)[\n schema.type\n ] as DataReader<TData>;\n if (!reader) {\n throw new Error(`Cannot read data of type '${schema.type}'.`);\n }\n\n return reader(input, schema);\n}\n","import type { Block } from 'tinyest';\nimport { resolveData } from './core/resolve/resolveData';\nimport {\n type Eventual,\n type SlotValuePair,\n type TgpuDerived,\n type TgpuSlot,\n isDerived,\n isProviding,\n isSlot,\n} from './core/slot/slotTypes';\nimport { isData } from './data';\nimport { getAttributesString } from './data/attributes';\nimport {\n type AnyWgslData,\n type BaseData,\n isWgslArray,\n isWgslStruct,\n} from './data/wgslTypes';\nimport { MissingSlotValueError, ResolutionError } from './errors';\nimport { RuntimeMode, popMode, provideCtx, pushMode } from './gpuMode';\nimport type { JitTranspiler } from './jitTranspiler';\nimport type { NameRegistry } from './nameRegistry';\nimport { naturalsExcept } from './shared/generators';\nimport type { Infer } from './shared/repr';\nimport { generateFunction } from './smol';\nimport {\n type TgpuBindGroup,\n TgpuBindGroupImpl,\n type TgpuBindGroupLayout,\n type TgpuLayoutEntry,\n bindGroupLayout,\n} from './tgpuBindGroupLayout';\nimport type { FnToWgslOptions, ResolutionCtx, Resource, Wgsl } from './types';\nimport { UnknownData, isSelfResolvable, isWgsl } from './types';\n\n/**\n * Inserted into bind group entry definitions that belong\n * to the automatically generated catch-all bind group.\n *\n * A non-occupied group index can only be determined after\n * every resource has been resolved, so this acts as a placeholder\n * to be replaced with an actual numeric index at the very end\n * of the resolution process.\n */\nconst CATCHALL_BIND_GROUP_IDX_MARKER = '#CATCHALL#';\n\nexport type ResolutionCtxImplOptions = {\n readonly names: NameRegistry;\n readonly jitTranspiler?: JitTranspiler | undefined;\n};\n\ntype SlotToValueMap = Map<TgpuSlot<unknown>, unknown>;\n\ntype ItemLayer = {\n type: 'item';\n usedSlots: Set<TgpuSlot<unknown>>;\n};\n\ntype SlotBindingLayer = {\n type: 'slotBinding';\n bindingMap: WeakMap<TgpuSlot<unknown>, unknown>;\n};\n\ntype FunctionScopeLayer = {\n type: 'functionScope';\n args: Resource[];\n externalMap: Record<string, unknown>;\n returnType: AnyWgslData | undefined;\n};\n\ntype BlockScopeLayer = {\n type: 'blockScope';\n declarations: Map<string, AnyWgslData | UnknownData>;\n};\n\nclass ItemStateStack {\n private _stack: (\n | ItemLayer\n | SlotBindingLayer\n | FunctionScopeLayer\n | BlockScopeLayer\n )[] = [];\n private _itemDepth = 0;\n\n get itemDepth(): number {\n return this._itemDepth;\n }\n\n get topItem(): ItemLayer {\n const state = this._stack[this._stack.length - 1];\n if (!state || state.type !== 'item') {\n throw new Error('Internal error, expected item layer to be on top.');\n }\n return state;\n }\n\n pushItem() {\n this._itemDepth++;\n this._stack.push({\n type: 'item',\n usedSlots: new Set(),\n });\n }\n\n pushSlotBindings(pairs: SlotValuePair<unknown>[]) {\n this._stack.push({\n type: 'slotBinding',\n bindingMap: new WeakMap(pairs),\n });\n }\n\n pushFunctionScope(\n args: Resource[],\n returnType: AnyWgslData | undefined,\n externalMap: Record<string, unknown>,\n ) {\n this._stack.push({\n type: 'functionScope',\n args,\n returnType,\n externalMap,\n });\n }\n\n pushBlockScope() {\n this._stack.push({\n type: 'blockScope',\n declarations: new Map<string, AnyWgslData | UnknownData>(),\n });\n }\n\n popBlockScope() {\n const layer = this._stack.pop();\n if (layer?.type !== 'blockScope') {\n throw new Error('Expected block scope layer to be on top.');\n }\n }\n\n pop() {\n const layer = this._stack.pop();\n if (layer?.type === 'item') {\n this._itemDepth--;\n }\n }\n\n readSlot<T>(slot: TgpuSlot<T>): T | undefined {\n for (let i = this._stack.length - 1; i >= 0; --i) {\n const layer = this._stack[i];\n if (layer?.type === 'item') {\n // Binding not available yet, so this layer is dependent on the slot's value.\n layer.usedSlots.add(slot);\n } else if (layer?.type === 'slotBinding') {\n const boundValue = layer.bindingMap.get(slot);\n\n if (boundValue !== undefined) {\n return boundValue as T;\n }\n } else if (\n layer?.type === 'functionScope' ||\n layer?.type === 'blockScope'\n ) {\n // Skip\n } else {\n throw new Error('Unknown layer type.');\n }\n }\n\n return slot.defaultValue;\n }\n\n getResourceById(id: string): Resource | undefined {\n for (let i = this._stack.length - 1; i >= 0; --i) {\n const layer = this._stack[i];\n\n if (layer?.type === 'functionScope') {\n const arg = layer.args.find((a) => a.value === id);\n if (arg !== undefined) {\n return arg;\n }\n\n const external = layer.externalMap[id];\n if (external !== undefined) {\n // TODO: Extract the type of the external value.\n return { value: external, dataType: UnknownData };\n }\n\n // Since functions cannot access resources from the calling scope, we\n // return early here.\n return undefined;\n }\n\n if (layer?.type === 'blockScope') {\n const declarationType = layer.declarations.get(id);\n if (declarationType !== undefined) {\n return { value: id, dataType: declarationType };\n }\n } else {\n // Skip\n }\n }\n\n return undefined;\n }\n\n defineBlockVariable(id: string, type: AnyWgslData | UnknownData): Resource {\n for (let i = this._stack.length - 1; i >= 0; --i) {\n const layer = this._stack[i];\n\n if (layer?.type === 'blockScope') {\n layer.declarations.set(id, type);\n\n return { value: id, dataType: type };\n }\n }\n\n throw new Error('No block scope found to define a variable in.');\n }\n}\n\nconst INDENT = [\n '', // 0\n ' ', // 1\n ' ', // 2\n ' ', // 3\n ' ', // 4\n ' ', // 5\n ' ', // 6\n ' ', // 7\n ' ', // 8\n];\n\nconst N = INDENT.length - 1;\n\nexport class IndentController {\n private identLevel = 0;\n\n get pre(): string {\n return (\n INDENT[this.identLevel] ??\n (INDENT[N] as string).repeat(this.identLevel / N) +\n INDENT[this.identLevel % N]\n );\n }\n\n indent(): string {\n const str = this.pre;\n this.identLevel++;\n return str;\n }\n\n dedent(): string {\n this.identLevel--;\n return this.pre;\n }\n}\n\ninterface FixedBindingConfig {\n layoutEntry: TgpuLayoutEntry;\n resource: object;\n}\n\nexport class ResolutionCtxImpl implements ResolutionCtx {\n private readonly _memoizedResolves = new WeakMap<\n // WeakMap because if the item does not exist anymore,\n // apart from this map, there is no way to access the cached value anyway.\n object,\n { slotToValueMap: SlotToValueMap; result: string }[]\n >();\n private readonly _memoizedDerived = new WeakMap<\n // WeakMap because if the \"derived\" does not exist anymore,\n // apart from this map, there is no way to access the cached value anyway.\n TgpuDerived<unknown>,\n { slotToValueMap: SlotToValueMap; result: unknown }[]\n >();\n\n private readonly _indentController = new IndentController();\n private readonly _jitTranspiler: JitTranspiler | undefined;\n private readonly _itemStateStack = new ItemStateStack();\n private readonly _declarations: string[] = [];\n\n // -- Bindings\n /**\n * A map from registered bind group layouts to random strings put in\n * place of their group index. The whole tree has to be traversed to\n * collect every use of a typed bind group layout, since they can be\n * explicitly imposed group indices, and they cannot collide.\n */\n public readonly bindGroupLayoutsToPlaceholderMap = new Map<\n TgpuBindGroupLayout,\n string\n >();\n private _nextFreeLayoutPlaceholderIdx = 0;\n public readonly fixedBindings: FixedBindingConfig[] = [];\n // --\n\n public readonly callStack: unknown[] = [];\n public readonly names: NameRegistry;\n\n constructor(opts: ResolutionCtxImplOptions) {\n this.names = opts.names;\n this._jitTranspiler = opts.jitTranspiler;\n }\n\n get pre(): string {\n return this._indentController.pre;\n }\n\n indent(): string {\n return this._indentController.indent();\n }\n\n dedent(): string {\n return this._indentController.dedent();\n }\n\n getById(id: string): Resource | null {\n // TODO: Provide access to external values\n // TODO: Provide data type information\n // TODO: Return null if no id is found (when we can properly handle it)\n return (\n this._itemStateStack.getResourceById(id) ?? {\n value: id,\n dataType: UnknownData,\n }\n );\n }\n\n defineVariable(id: string, dataType: AnyWgslData | UnknownData): Resource {\n // TODO: Bring this behavior back when we have type inference\n // const resource = this.getById(id);\n\n // if (resource) {\n // throw new Error(`Resource ${id} already exists in the current scope.`);\n // } else {\n // return this._itemStateStack.defineBlockVariable(id, dataType);\n // }\n return this._itemStateStack.defineBlockVariable(id, dataType);\n }\n\n pushBlockScope() {\n this._itemStateStack.pushBlockScope();\n }\n\n popBlockScope() {\n this._itemStateStack.popBlockScope();\n }\n\n transpileFn(fn: string): {\n argNames: string[];\n body: Block;\n externalNames: string[];\n } {\n if (!this._jitTranspiler) {\n throw new Error(\n 'Tried to execute a tgpu.fn function without providing a JIT transpiler, or transpiling at build time.',\n );\n }\n\n return this._jitTranspiler.transpileFn(fn);\n }\n\n fnToWgsl(options: FnToWgslOptions): { head: Wgsl; body: Wgsl } {\n this._itemStateStack.pushFunctionScope(\n options.args,\n options.returnType,\n options.externalMap,\n );\n const str = generateFunction(this, options.body);\n this._itemStateStack.pop();\n\n const argList = options.args\n .map(\n (arg) => `${arg.value}: ${this.resolve(arg.dataType as AnyWgslData)}`,\n )\n .join(', ');\n\n return {\n head:\n options.returnType !== undefined\n ? `(${argList}) -> ${getAttributesString(options.returnType)} ${this.resolve(options.returnType)}`\n : `(${argList})`,\n body: str,\n };\n }\n\n addDeclaration(declaration: string): void {\n this._declarations.push(declaration);\n }\n\n allocateLayoutEntry(layout: TgpuBindGroupLayout): string {\n const memoMap = this.bindGroupLayoutsToPlaceholderMap;\n let placeholderKey = memoMap.get(layout);\n\n if (!placeholderKey) {\n placeholderKey = `#BIND_GROUP_LAYOUT_${this._nextFreeLayoutPlaceholderIdx++}#`;\n memoMap.set(layout, placeholderKey);\n }\n\n return placeholderKey;\n }\n\n allocateFixedEntry(\n layoutEntry: TgpuLayoutEntry,\n resource: object,\n ): { group: string; binding: number } {\n const binding = this.fixedBindings.length;\n this.fixedBindings.push({ layoutEntry, resource });\n\n return {\n group: CATCHALL_BIND_GROUP_IDX_MARKER,\n binding,\n };\n }\n\n readSlot<T>(slot: TgpuSlot<T>): T {\n const value = this._itemStateStack.readSlot(slot);\n\n if (value === undefined) {\n throw new MissingSlotValueError(slot);\n }\n\n return value;\n }\n\n withSlots<T>(pairs: SlotValuePair<unknown>[], callback: () => T): T {\n this._itemStateStack.pushSlotBindings(pairs);\n\n try {\n return callback();\n } finally {\n this._itemStateStack.pop();\n }\n }\n\n unwrap<T>(eventual: Eventual<T>): T {\n if (isProviding(eventual)) {\n return this.withSlots(\n eventual['~providing'].pairs,\n () => this.unwrap(eventual['~providing'].inner) as T,\n );\n }\n\n let maybeEventual = eventual;\n\n // Unwrapping all layers of slots.\n while (true) {\n if (isSlot(maybeEventual)) {\n maybeEventual = this.readSlot(maybeEventual);\n } else if (isDerived(maybeEventual)) {\n maybeEventual = this._getOrCompute(maybeEventual);\n } else {\n break;\n }\n }\n\n return maybeEventual;\n }\n\n _getOrCompute<T>(derived: TgpuDerived<T>): T {\n // All memoized versions of `derived`\n const instances = this._memoizedDerived.get(derived) ?? [];\n\n this._itemStateStack.pushItem();\n\n try {\n for (const instance of instances) {\n const slotValuePairs = [...instance.slotToValueMap.entries()];\n\n if (\n slotValuePairs.every(([slot, expectedValue]) =>\n slot.areEqual(this._itemStateStack.readSlot(slot), expectedValue),\n )\n ) {\n return instance.result as T;\n }\n }\n\n // If we got here, no item with the given slot-to-value combo exists in cache yet\n const result = derived['~compute']();\n\n // We know which slots the item used while resolving\n const slotToValueMap = new Map<TgpuSlot<unknown>, unknown>();\n for (const usedSlot of this._itemStateStack.topItem.usedSlots) {\n slotToValueMap.set(usedSlot, this._itemStateStack.readSlot(usedSlot));\n }\n\n instances.push({ slotToValueMap, result });\n this._memoizedDerived.set(derived, instances);\n\n return result;\n } catch (err) {\n if (err instanceof ResolutionError) {\n throw err.appendToTrace(derived);\n }\n\n throw new ResolutionError(err, [derived]);\n } finally {\n this._itemStateStack.pop();\n }\n }\n\n /**\n * @param item The item whose resolution should be either retrieved from the cache (if there is a cache hit), or resolved.\n */\n _getOrInstantiate(item: object): string {\n // All memoized versions of `item`\n const instances = this._memoizedResolves.get(item) ?? [];\n\n this._itemStateStack.pushItem();\n\n try {\n for (const instance of instances) {\n const slotValuePairs = [...instance.slotToValueMap.entries()];\n\n if (\n slotValuePairs.every(([slot, expectedValue]) =>\n slot.areEqual(this._itemStateStack.readSlot(slot), expectedValue),\n )\n ) {\n return instance.result;\n }\n }\n\n // If we got here, no item with the given slot-to-value combo exists in cache yet\n let result: string;\n if (isData(item)) {\n result = resolveData(this, item);\n } else if (isDerived(item) || isSlot(item)) {\n result = this.resolve(this.unwrap(item));\n } else if (isSelfResolvable(item)) {\n result = item['~resolve'](this);\n } else {\n result = this.resolveValue(item);\n }\n\n // We know which slots the item used while resolving\n const slotToValueMap = new Map<TgpuSlot<unknown>, unknown>();\n for (const usedSlot of this._itemStateStack.topItem.usedSlots) {\n slotToValueMap.set(usedSlot, this._itemStateStack.readSlot(usedSlot));\n }\n\n instances.push({ slotToValueMap, result });\n this._memoizedResolves.set(item, instances);\n\n return result;\n } catch (err) {\n if (err instanceof ResolutionError) {\n throw err.appendToTrace(item);\n }\n\n throw new ResolutionError(err, [item]);\n } finally {\n this._itemStateStack.pop();\n }\n }\n\n resolve(item: unknown): string {\n if (isProviding(item)) {\n return this.withSlots(item['~providing'].pairs, () =>\n this.resolve(item['~providing'].inner),\n );\n }\n\n if ((item && typeof item === 'object') || typeof item === 'function') {\n if (this._itemStateStack.itemDepth === 0) {\n try {\n pushMode(RuntimeMode.GPU);\n const result = provideCtx(this, () => this._getOrInstantiate(item));\n return `${[...this._declarations].join('\\n\\n')}${result}`;\n } finally {\n popMode(RuntimeMode.GPU);\n }\n }\n\n return this._getOrInstantiate(item);\n }\n\n return String(item);\n }\n\n resolveValue<T extends BaseData>(\n value: Infer<T>,\n schema?: T | undefined,\n ): string {\n if (isWgsl(value)) {\n return this.resolve(value);\n }\n\n if (schema && isWgslArray(schema)) {\n return `array(${(value as unknown[]).map((element) => this.resolveValue(element, schema.elementType))})`;\n }\n\n if (Array.isArray(value)) {\n return `array(${value.map((element) => this.resolveValue(element))})`;\n }\n\n if (schema && isWgslStruct(schema)) {\n return `${this.resolve(schema)}(${Object.entries(schema.propTypes).map(([key, type_]) => this.resolveValue((value as Infer<typeof schema>)[key], type_))})`;\n }\n\n throw new Error(\n `Value ${value} (as json: ${JSON.stringify(value)}) of schema ${schema} is not resolvable to WGSL`,\n );\n }\n}\n\nexport interface ResolutionResult {\n code: string;\n bindGroupLayouts: TgpuBindGroupLayout[];\n catchall: [number, TgpuBindGroup] | null;\n}\n\nexport function resolve(\n item: Wgsl,\n options: ResolutionCtxImplOptions,\n): ResolutionResult {\n const ctx = new ResolutionCtxImpl(options);\n let code = ctx.resolve(item);\n\n const memoMap = ctx.bindGroupLayoutsToPlaceholderMap;\n const bindGroupLayouts: TgpuBindGroupLayout[] = [];\n const takenIndices = new Set<number>(\n [...memoMap.keys()]\n .map((layout) => layout.index)\n .filter((v): v is number => v !== undefined),\n );\n\n const automaticIds = naturalsExcept(takenIndices);\n\n const layoutEntries = ctx.fixedBindings.map(\n (binding, idx) =>\n [String(idx), binding.layoutEntry] as [string, TgpuLayoutEntry],\n );\n\n const createCatchallGroup = () => {\n const catchallIdx = automaticIds.next().value;\n const catchallLayout = bindGroupLayout(Object.fromEntries(layoutEntries));\n bindGroupLayouts[catchallIdx] = catchallLayout;\n code = code.replaceAll(CATCHALL_BIND_GROUP_IDX_MARKER, String(catchallIdx));\n\n return [\n catchallIdx,\n new TgpuBindGroupImpl(\n catchallLayout,\n Object.fromEntries(\n ctx.fixedBindings.map(\n (binding, idx) =>\n // biome-ignore lint/suspicious/noExplicitAny: <it's fine>\n [String(idx), binding.resource] as [string, any],\n ),\n ),\n ),\n ] as [number, TgpuBindGroup];\n };\n\n // Retrieving the catch-all binding index first, because it's inherently\n // the least swapped bind group (fixed and cannot be swapped).\n const catchall = layoutEntries.length > 0 ? createCatchallGroup() : null;\n\n for (const [layout, placeholder] of memoMap.entries()) {\n const idx = layout.index ?? automaticIds.next().value;\n bindGroupLayouts[idx] = layout;\n code = code.replaceAll(placeholder, String(idx));\n }\n\n return {\n code,\n bindGroupLayouts,\n catchall,\n };\n}\n","import type { JitTranspiler } from '../../jitTranspiler';\nimport { RandomNameRegistry, StrictNameRegistry } from '../../nameRegistry';\nimport { resolve as resolveImpl } from '../../resolutionCtx';\nimport type { SelfResolvable, Wgsl } from '../../types';\nimport { applyExternals, replaceExternalsInWgsl } from './externals';\n\nexport interface TgpuResolveOptions {\n /**\n * Map of external names to their resolvable values.\n */\n externals: Record<string, Wgsl | object>;\n /**\n * The code template to use for the resolution. All external names will be replaced with their resolved values.\n * @default ''\n */\n template?: string | undefined;\n /**\n * The naming strategy used for generating identifiers for resolved externals and their dependencies.\n * @default 'random'\n */\n names?: 'strict' | 'random' | undefined;\n /**\n * Optional JIT transpiler for resolving TGSL functions.\n * @experimental\n */\n unstable_jitTranspiler?: JitTranspiler | undefined;\n}\n\n/**\n * Resolves a template with external values. Each external will get resolved to a code string and replaced in the template.\n * Any dependencies of the externals will also be resolved and included in the output.\n * @param options - The options for the resolution.\n *\n * @returns The resolved code.\n *\n * @example\n * ```ts\n * const Gradient = d.struct({\n * from: d.vec3f,\n * to: d.vec3f,\n * });\n *\n * const resolved = tgpu.resolve({\n * template: `\n * fn getGradientAngle(gradient: Gradient) -> f32 {\n * return atan(gradient.to.y - gradient.from.y, gradient.to.x - gradient.from.x);\n * }\n * `,\n * externals: {\n * Gradient,\n * },\n * });\n *\n * console.log(resolved);\n * // struct Gradient_0 {\n * // from: vec3f,\n * // to: vec3f,\n * // }\n * // fn getGradientAngle(gradient: Gradient_0) -> f32 {\n * // return atan(gradient.to.y - gradient.from.y, gradient.to.x - gradient.from.x);\n * // }\n * ```\n */\nexport function resolve(options: TgpuResolveOptions): string {\n const {\n externals,\n template,\n names,\n unstable_jitTranspiler: jitTranspiler,\n } = options;\n\n const dependencies = {} as Record<string, Wgsl>;\n applyExternals(dependencies, externals ?? {});\n\n const resolutionObj: SelfResolvable = {\n '~resolve'(ctx) {\n return replaceExternalsInWgsl(ctx, dependencies, template ?? '');\n },\n\n toString: () => '<root>',\n };\n\n const { code } = resolveImpl(resolutionObj, {\n names:\n names === 'strict' ? new StrictNameRegistry() : new RandomNameRegistry(),\n jitTranspiler,\n });\n\n return code;\n}\n"]}
1
+ {"version":3,"sources":["/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","../src/smol/generationHelpers.ts","../src/core/resolve/resolveData.ts","../src/smol/wgslGenerator.ts","../src/data/compiledIO.ts","../src/data/dataIO.ts","../src/resolutionCtx.ts","../src/core/pipeline/computePipeline.ts"],"names":["swizzleableTypes","swizzleLenToType","f32","vec2f","vec3f","vec4f","f16","vec2h","vec3h","vec4h","i32","vec2i","vec3i","vec4i","u32","vec2u","vec3u","vec4u","kindToSchema","mat2x2f","mat3x3f","mat4x4f","indexableTypeToResult","getTypeForPropAccess","targetType","propName","_a","UnknownData","isDerived","isSlot","ctx","getResolutionCtx","unwrapped","getTypeFromWgsl","target","hasInternalDataType","$internal","isDecorated","targetTypeStr","propLength","swizzleTypeChar","swizzleType","isWgslData","getTypeForIndexAccess","resource","isWgslArray","_b","numericLiteralToResource","bool","kind","value","abstractInt"],"mappings":"AAAA,iIAAgL,wDAAqH,wDAA+Z,IC0C9rBA,EAAAA,CAAmB,CACvB,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,OAAA,CACA,QACF,CAAA,CAKMC,EAAAA,CAGF,CACF,CAAA,CAAG,CACD,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBACL,CAAA,CACA,CAAA,CAAG,CACD,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBACL,CAAA,CACA,CAAA,CAAG,CACD,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBACL,CAAA,CACA,CAAA,CAAG,CACD,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBAAAA,CACH,CAAA,CAAGC,mBACL,CACF,CAAA,CAEMC,EAAAA,CAAe,CACnB,KAAA,CAAOf,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOX,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOX,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,OAAA,CAASE,mBAAAA,CACT,OAAA,CAASC,mBAAAA,CACT,OAAA,CAASC,mBACX,CAAA,CAEMC,EAAAA,CAAwB,CAC5B,KAAA,CAAOpB,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOZ,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOZ,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,KAAA,CAAOI,mBAAAA,CACP,OAAA,CAASX,mBAAAA,CACT,OAAA,CAASC,mBAAAA,CACT,OAAA,CAASC,mBACX,CAAA,CAEO,SAASkB,EAAAA,CACdC,CAAAA,CACAC,CAAAA,CACwB,CAlI1B,IAAAC,CAAAA,CAmIE,EAAA,CACE,OAAOF,CAAAA,EAAe,QAAA,EACtB,OAAOA,CAAAA,EAAe,QAAA,EACtB,OAAOA,CAAAA,EAAe,SAAA,CAEtB,OAAOG,mBAAAA,CAGT,EAAA,CAAIC,iCAAAA,CAAoB,CAAA,EAAKC,iCAAAA,CAAiB,CAAA,CAAG,CAC/C,IAAMC,CAAAA,CAAMC,iCAAAA,CAAiB,CAC7B,EAAA,CAAI,CAACD,CAAAA,CACH,MAAM,IAAI,KAAA,CACR,8DACF,CAAA,CAEF,IAAME,CAAAA,CAAYF,CAAAA,CAAI,MAAA,CAAON,CAAU,CAAA,CAEvC,OAAOS,EAAAA,CAAgBD,CAAiB,CAC1C,CAEA,IAAIE,CAAAA,CAASV,CAAAA,CAKb,GAAA,CAHIW,iCAAAA,CAA0B,CAAA,EAAA,CAC5BD,CAAAA,CAASA,CAAAA,CAAOE,mBAAS,CAAA,CAAE,QAAA,CAAA,CAEtBC,kCAAAA,CAAkB,CAAA,CAAA,CACvBH,CAAAA,CAASA,CAAAA,CAAO,KAAA,CAGlB,IAAMI,CAAAA,CACJ,MAAA,GAAUJ,CAAAA,CAAUA,CAAAA,CAAO,IAAA,CAAkBA,CAAAA,CAAO,IAAA,CAEtD,EAAA,CAAII,CAAAA,GAAkB,QAAA,CACpB,MAAA,CAAQZ,CAAAA,CAAAQ,CAAAA,CAAsB,SAAA,CAAUT,CAAQ,CAAA,CAAA,EAAxC,IAAA,CAAAC,CAAAA,CAA6CC,mBAAAA,CAGvD,IAAMY,CAAAA,CAAad,CAAAA,CAAS,MAAA,CAC5B,EAAA,CACEzB,EAAAA,CAAiB,QAAA,CACfsC,CACF,CAAA,EACAC,CAAAA,EAAc,CAAA,EACdA,CAAAA,EAAc,CAAA,CACd,CACA,IAAMC,CAAAA,CAAkBF,CAAAA,CAAc,CAAC,CAAA,CACjCG,CAAAA,CACJxC,EAAAA,CAAiBuC,CAAe,CAAA,CAAED,CAA2B,CAAA,CAC/D,EAAA,CAAIE,CAAAA,CACF,OAAOA,CAEX,CAEA,OAAOC,iCAAAA,CAAiB,CAAA,CAAIR,CAAAA,CAASP,mBACvC,CAEO,SAASgB,EAAAA,CAAsBC,CAAAA,CAAwC,CAC5E,EAAA,CAAIF,iCAAAA,CAAmB,CAAA,CAAG,CAExB,EAAA,CAAIG,iCAAAA,CAAoB,CAAA,CACtB,OAAOD,CAAAA,CAAS,WAAA,CAIlB,EAAA,CAAIA,CAAAA,CAAS,KAAA,GAAQtB,EAAAA,CACnB,OAAOA,EAAAA,CACLsB,CAAAA,CAAS,IACX,CAEJ,CAEA,OAAOjB,mBACT,CAEO,SAASM,EAAAA,CAAgBW,CAAAA,CAAwC,CA5MxE,IAAAlB,CAAAA,CAAAoB,CAAAA,CA6ME,EAAA,CAAIlB,iCAAAA,CAAkB,CAAA,EAAKC,iCAAAA,CAAe,CAAA,CACxC,OAAOI,EAAAA,CAAgBW,CAAAA,CAAS,KAAa,CAAA,CAG/C,EAAA,CAAI,OAAOA,CAAAA,EAAa,QAAA,CACtB,OAAOjB,mBAAAA,CAET,EAAA,CAAI,OAAOiB,CAAAA,EAAa,QAAA,CACtB,MAAA,CAAOE,CAAAA,CAAAA,CAAApB,CAAAA,CAAAqB,EAAAA,CAAyB,MAAA,CAAOH,CAAQ,CAAC,CAAA,CAAA,EAAzC,IAAA,CAAA,KAAA,CAAA,CAAAlB,CAAAA,CAA4C,QAAA,CAAA,EAA5C,IAAA,CAAAoB,CAAAA,CAAwDnB,mBAAAA,CAEjE,EAAA,CAAI,OAAOiB,CAAAA,EAAa,SAAA,CACtB,OAAOI,mBAAAA,CAGT,EAAA,CAAI,MAAA,GAAUJ,CAAAA,CAAU,CACtB,IAAMK,CAAAA,CAAOL,CAAAA,CAAS,IAAA,CACtB,EAAA,CAAIK,EAAAA,GAAQ/B,EAAAA,CACV,OAAOA,EAAAA,CAAa+B,CAAI,CAE5B,CAEA,OAAOP,iCAAAA,CAAmB,CAAA,CAAIE,CAAAA,CAAWjB,mBAC3C,CAEO,SAASoB,EAAAA,CAAyBG,CAAAA,CAAqC,CAE5E,EAAA,CAAI,gBAAA,CAAiB,IAAA,CAAKA,CAAK,CAAA,CAC7B,MAAO,CAAE,KAAA,CAAAA,CAAAA,CAAO,QAAA,CAAUC,mBAAY,CAAA,CAIxC,EAAA,CAAI,YAAA,CAAa,IAAA,CAAKD,CAAK,CAAA,CACzB,MAAO,CACL,KAAA,CAAO,CAAA,EAAA;AC1HQ;AACV,OAAA;AAGA;AAwBU;AACV,OAAA;AAOA;AAAA;AChDe;AAC2C;AAAK;AAgPD;AAmC7D;AACH;AAPgB;AAkE8B;AAaxB;AC5W0B;AAM7C;AAa+E;AAmB2C;AAOpD;AC0FrD;ACoXoB;ACteN","file":"/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","sourcesContent":[null,"import { isDerived, isSlot } from '../core/slot/slotTypes';\nimport { mat2x2f, mat3x3f, mat4x4f } from '../data/matrix';\nimport {\n abstractFloat,\n abstractInt,\n bool,\n f16,\n f32,\n i32,\n u32,\n} from '../data/numeric';\nimport type { WgslStruct } from '../data/struct';\nimport {\n vec2f,\n vec2h,\n vec2i,\n vec2u,\n vec3f,\n vec3h,\n vec3i,\n vec3u,\n vec4f,\n vec4h,\n vec4i,\n vec4u,\n} from '../data/vector';\nimport {\n type AnyWgslData,\n type BaseData,\n isDecorated,\n isWgslArray,\n isWgslData,\n} from '../data/wgslTypes';\nimport { getResolutionCtx } from '../gpuMode';\nimport { $internal } from '../shared/symbols';\nimport {\n type Resource,\n UnknownData,\n type Wgsl,\n hasInternalDataType,\n} from '../types';\n\nconst swizzleableTypes = [\n 'vec2f',\n 'vec2h',\n 'vec2i',\n 'vec2u',\n 'vec3f',\n 'vec3h',\n 'vec3i',\n 'vec3u',\n 'vec4f',\n 'vec4h',\n 'vec4i',\n 'vec4u',\n 'struct',\n] as const;\n\ntype SwizzleableType = 'f' | 'h' | 'i' | 'u';\ntype SwizzleLength = 1 | 2 | 3 | 4;\n\nconst swizzleLenToType: Record<\n SwizzleableType,\n Record<SwizzleLength, AnyWgslData>\n> = {\n f: {\n 1: f32,\n 2: vec2f,\n 3: vec3f,\n 4: vec4f,\n },\n h: {\n 1: f16,\n 2: vec2h,\n 3: vec3h,\n 4: vec4h,\n },\n i: {\n 1: i32,\n 2: vec2i,\n 3: vec3i,\n 4: vec4i,\n },\n u: {\n 1: u32,\n 2: vec2u,\n 3: vec3u,\n 4: vec4u,\n },\n} as const;\n\nconst kindToSchema = {\n vec2f: vec2f,\n vec2h: vec2h,\n vec2i: vec2i,\n vec2u: vec2u,\n vec3f: vec3f,\n vec3h: vec3h,\n vec3i: vec3i,\n vec3u: vec3u,\n vec4f: vec4f,\n vec4h: vec4h,\n vec4i: vec4i,\n vec4u: vec4u,\n mat2x2f: mat2x2f,\n mat3x3f: mat3x3f,\n mat4x4f: mat4x4f,\n} as const;\n\nconst indexableTypeToResult = {\n vec2f: f32,\n vec2h: f16,\n vec2i: i32,\n vec2u: u32,\n vec3f: f32,\n vec3h: f16,\n vec3i: i32,\n vec3u: u32,\n vec4f: f32,\n vec4h: f16,\n vec4i: i32,\n vec4u: u32,\n mat2x2f: vec2f,\n mat3x3f: vec3f,\n mat4x4f: vec4f,\n} as const;\n\nexport function getTypeForPropAccess(\n targetType: Wgsl,\n propName: string,\n): BaseData | UnknownData {\n if (\n typeof targetType === 'string' ||\n typeof targetType === 'number' ||\n typeof targetType === 'boolean'\n ) {\n return UnknownData;\n }\n\n if (isDerived(targetType) || isSlot(targetType)) {\n const ctx = getResolutionCtx();\n if (!ctx) {\n throw new Error(\n 'Resolution context not found when unwrapping slot or derived',\n );\n }\n const unwrapped = ctx.unwrap(targetType);\n\n return getTypeFromWgsl(unwrapped as Wgsl) as BaseData;\n }\n\n let target = targetType as BaseData;\n\n if (hasInternalDataType(target)) {\n target = target[$internal].dataType;\n }\n while (isDecorated(target)) {\n target = target.inner;\n }\n\n const targetTypeStr =\n 'kind' in target ? (target.kind as string) : target.type;\n\n if (targetTypeStr === 'struct') {\n return (target as WgslStruct).propTypes[propName] ?? UnknownData;\n }\n\n const propLength = propName.length;\n if (\n swizzleableTypes.includes(\n targetTypeStr as (typeof swizzleableTypes)[number],\n ) &&\n propLength >= 1 &&\n propLength <= 4\n ) {\n const swizzleTypeChar = targetTypeStr[4] as SwizzleableType;\n const swizzleType =\n swizzleLenToType[swizzleTypeChar][propLength as SwizzleLength];\n if (swizzleType) {\n return swizzleType;\n }\n }\n\n return isWgslData(target) ? target : UnknownData;\n}\n\nexport function getTypeForIndexAccess(resource: Wgsl): BaseData | UnknownData {\n if (isWgslData(resource)) {\n // array\n if (isWgslArray(resource)) {\n return resource.elementType;\n }\n\n // vector or matrix\n if (resource.type in indexableTypeToResult) {\n return indexableTypeToResult[\n resource.type as keyof typeof indexableTypeToResult\n ];\n }\n }\n\n return UnknownData;\n}\n\nexport function getTypeFromWgsl(resource: Wgsl): BaseData | UnknownData {\n if (isDerived(resource) || isSlot(resource)) {\n return getTypeFromWgsl(resource.value as Wgsl);\n }\n\n if (typeof resource === 'string') {\n return UnknownData;\n }\n if (typeof resource === 'number') {\n return numericLiteralToResource(String(resource))?.dataType ?? UnknownData;\n }\n if (typeof resource === 'boolean') {\n return bool;\n }\n\n if ('kind' in resource) {\n const kind = resource.kind;\n if (kind in kindToSchema) {\n return kindToSchema[kind];\n }\n }\n\n return isWgslData(resource) ? resource : UnknownData;\n}\n\nexport function numericLiteralToResource(value: string): Resource | undefined {\n // Hex literals\n if (/^0x[0-9a-f]+$/i.test(value)) {\n return { value, dataType: abstractInt };\n }\n\n // Binary literals\n if (/^0b[01]+$/i.test(value)) {\n return {\n value: `${Number.parseInt(value.slice(2), 2)}`,\n dataType: abstractInt,\n };\n }\n\n // Floating point literals\n if (/^-?(?:\\d+\\.\\d*|\\d*\\.\\d+)$/i.test(value)) {\n return { value, dataType: abstractFloat };\n }\n\n // Floating point literals with scientific notation\n if (/^-?\\d+(?:\\.\\d+)?e-?\\d+$/i.test(value)) {\n return { value, dataType: abstractFloat };\n }\n\n // Integer literals\n if (/^-?\\d+$/i.test(value)) {\n return { value, dataType: abstractInt };\n }\n\n return undefined;\n}\n","import { getAttributesString } from '../../data/attributes';\nimport {\n type AnyData,\n type Disarray,\n type Unstruct,\n isLooseData,\n} from '../../data/dataTypes';\nimport type { WgslStruct } from '../../data/struct';\nimport { formatToWGSLType } from '../../data/vertexFormatData';\nimport type {\n AnyWgslData,\n BaseData,\n Bool,\n F16,\n F32,\n I32,\n Mat2x2f,\n Mat3x3f,\n Mat4x4f,\n U32,\n Vec2f,\n Vec2h,\n Vec2i,\n Vec2u,\n Vec3f,\n Vec3h,\n Vec3i,\n Vec3u,\n Vec4f,\n Vec4h,\n Vec4i,\n Vec4u,\n WgslArray,\n} from '../../data/wgslTypes';\nimport { assertExhaustive } from '../../shared/utilityTypes';\nimport type { ResolutionCtx } from '../../types';\nimport { isAttribute } from '../vertexLayout/connectAttributesToShader';\n\n/**\n * Schemas for which their `type` property directly\n * translates to the resulting WGSL code.\n */\nconst identityTypes = [\n 'bool',\n 'f32',\n 'f16',\n 'i32',\n 'u32',\n 'vec2f',\n 'vec3f',\n 'vec4f',\n 'vec2h',\n 'vec3h',\n 'vec4h',\n 'vec2i',\n 'vec3i',\n 'vec4i',\n 'vec2u',\n 'vec3u',\n 'vec4u',\n 'mat2x2f',\n 'mat3x3f',\n 'mat4x4f',\n];\n\ntype IdentityType =\n | Bool\n | F32\n | F16\n | I32\n | U32\n | Vec2f\n | Vec3f\n | Vec4f\n | Vec2h\n | Vec3h\n | Vec4h\n | Vec2i\n | Vec3i\n | Vec4i\n | Vec2u\n | Vec3u\n | Vec4u\n | Mat2x2f\n | Mat3x3f\n | Mat4x4f;\n\nfunction isIdentityType(data: AnyWgslData): data is IdentityType {\n return identityTypes.includes(data.type);\n}\n\n/**\n * Resolves a single property of a struct.\n * @param ctx - The resolution context.\n * @param key - The key of the property.\n * @param property - The property itself.\n *\n * @returns The resolved property string.\n */\nfunction resolveStructProperty(\n ctx: ResolutionCtx,\n [key, property]: [string, BaseData],\n) {\n return ` ${getAttributesString(property)}${key}: ${ctx.resolve(property as AnyWgslData)},\\n`;\n}\n\n/**\n * Resolves a struct and adds its declaration to the resolution context.\n * @param ctx - The resolution context.\n * @param struct - The struct to resolve.\n *\n * @returns The resolved struct name.\n */\nfunction resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) {\n const id = ctx.names.makeUnique(struct.label);\n\n ctx.addDeclaration(`\nstruct ${id} {\n${Object.entries(struct.propTypes)\n .map((prop) => resolveStructProperty(ctx, prop))\n .join('')}\\\n}\\n`);\n\n return id;\n}\n\n/**\n * Resolves an unstruct (struct that does not align data by default) to its struct data counterpart.\n * @param ctx - The resolution context.\n * @param unstruct - The unstruct to resolve.\n *\n * @returns The resolved unstruct name.\n *\n * @example\n * ```ts\n * resolveUnstruct(ctx, {\n * uv: d.float16x2, // -> d.vec2f after resolution\n * color: d.snorm8x4, -> d.vec4f after resolution\n * });\n * ```\n */\nfunction resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) {\n const id = ctx.names.makeUnique(unstruct.label);\n\n ctx.addDeclaration(`\nstruct ${id} {\n${Object.entries(unstruct.propTypes)\n .map((prop) =>\n isAttribute(prop[1])\n ? resolveStructProperty(ctx, [prop[0], formatToWGSLType[prop[1].format]])\n : resolveStructProperty(ctx, prop),\n )\n .join('')}\n}\\n`);\n\n return id;\n}\n\n/**\n * Resolves an array.\n * @param ctx - The resolution context.\n * @param array - The array to resolve.\n *\n * @returns The resolved array name along with its element type and count (if not runtime-sized).\n *\n * @example\n * ```ts\n * resolveArray(ctx, d.arrayOf(d.u32, 0)); // 'array<u32>' (not a real pattern, a function is preferred)\n * resolveArray(ctx, d.arrayOf(d.u32, 5)); // 'array<u32, 5>'\n * ```\n */\nfunction resolveArray(ctx: ResolutionCtx, array: WgslArray) {\n const element = ctx.resolve(array.elementType as AnyWgslData);\n\n return array.elementCount === 0\n ? `array<${element}>`\n : `array<${element}, ${array.elementCount}>`;\n}\n\nfunction resolveDisarray(ctx: ResolutionCtx, disarray: Disarray) {\n const element = ctx.resolve(\n isAttribute(disarray.elementType)\n ? formatToWGSLType[disarray.elementType.format]\n : (disarray.elementType as AnyWgslData),\n );\n\n return disarray.elementCount === 0\n ? `array<${element}>`\n : `array<${element}, ${disarray.elementCount}>`;\n}\n\n/**\n * Resolves a WGSL data-type schema to a string.\n * @param ctx - The resolution context.\n * @param data - The data-type to resolve.\n *\n * @returns The resolved data-type string.\n */\nexport function resolveData(ctx: ResolutionCtx, data: AnyData): string {\n if (isLooseData(data)) {\n if (data.type === 'unstruct') {\n return resolveUnstruct(ctx, data);\n }\n\n if (data.type === 'disarray') {\n return resolveDisarray(ctx, data);\n }\n\n if (data.type === 'loose-decorated') {\n return ctx.resolve(\n isAttribute(data.inner)\n ? formatToWGSLType[data.inner.format]\n : data.inner,\n );\n }\n\n return ctx.resolve(formatToWGSLType[data.type]);\n }\n\n if (isIdentityType(data)) {\n return data.type;\n }\n\n if (data.type === 'struct') {\n return resolveStruct(ctx, data);\n }\n\n if (data.type === 'array') {\n return resolveArray(ctx, data);\n }\n\n if (data.type === 'atomic') {\n return `atomic<${resolveData(ctx, data.inner)}>`;\n }\n\n if (data.type === 'decorated') {\n return ctx.resolve(data.inner as AnyWgslData);\n }\n\n if (data.type === 'ptr') {\n if (data.addressSpace === 'storage') {\n return `ptr<storage, ${ctx.resolve(data.inner)}, ${data.access === 'read-write' ? 'read_write' : data.access}>`;\n }\n return `ptr<${data.addressSpace}, ${ctx.resolve(data.inner)}>`;\n }\n\n if (data.type === 'abstractInt' || data.type === 'abstractFloat') {\n throw new Error('Abstract types have no concrete representation in WGSL');\n }\n\n assertExhaustive(data, 'resolveData');\n}\n","import type * as smol from 'tinyest';\nimport * as d from '../data';\nimport * as wgsl from '../data/wgslTypes';\nimport {\n type ResolutionCtx,\n type Resource,\n UnknownData,\n type Wgsl,\n isWgsl,\n} from '../types';\nimport {\n getTypeForIndexAccess,\n getTypeForPropAccess,\n getTypeFromWgsl,\n numericLiteralToResource,\n} from './generationHelpers';\n\nconst parenthesizedOps = [\n '==',\n '!=',\n '<',\n '<=',\n '>',\n '>=',\n '<<',\n '>>',\n '+',\n '-',\n '*',\n '/',\n '%',\n '|',\n '^',\n '&',\n '&&',\n '||',\n];\n\nconst binaryLogicalOps = ['&&', '||', '==', '!=', '<', '<=', '>', '>='];\n\ntype Operator =\n | smol.BinaryOperator\n | smol.AssignmentOperator\n | smol.LogicalOperator\n | smol.UnaryOperator;\n\nfunction operatorToType<\n TL extends wgsl.AnyWgslData | UnknownData,\n TR extends wgsl.AnyWgslData | UnknownData,\n>(lhs: TL, op: Operator, rhs?: TR): TL | TR | wgsl.Bool {\n if (!rhs) {\n if (op === '!' || op === '~') {\n return d.bool;\n }\n\n return lhs;\n }\n\n if (binaryLogicalOps.includes(op)) {\n return d.bool;\n }\n\n if (op === '=') {\n return rhs;\n }\n\n return lhs;\n}\n\nexport type GenerationCtx = ResolutionCtx & {\n readonly pre: string;\n readonly callStack: unknown[];\n indent(): string;\n dedent(): string;\n pushBlockScope(): void;\n popBlockScope(): void;\n getById(id: string): Resource | null;\n defineVariable(\n id: string,\n dataType: wgsl.AnyWgslData | UnknownData,\n ): Resource;\n};\n\nexport function resolveRes(ctx: GenerationCtx, res: Resource): string {\n if (isWgsl(res.value)) {\n return ctx.resolve(res.value);\n }\n\n return String(res.value);\n}\n\nfunction assertExhaustive(value: never): never {\n throw new Error(\n `'${JSON.stringify(value)}' was not handled by the WGSL generator.`,\n );\n}\n\nexport function generateBoolean(ctx: GenerationCtx, value: boolean): Resource {\n return { value: value ? 'true' : 'false', dataType: d.bool };\n}\n\nexport function generateBlock(ctx: GenerationCtx, value: smol.Block): string {\n ctx.pushBlockScope();\n try {\n return `${ctx.indent()}{\n${value.b.map((statement) => generateStatement(ctx, statement)).join('\\n')}\n${ctx.dedent()}}`;\n } finally {\n ctx.popBlockScope();\n }\n}\n\nexport function registerBlockVariable(\n ctx: GenerationCtx,\n id: string,\n dataType: wgsl.AnyWgslData | UnknownData,\n): Resource {\n return ctx.defineVariable(id, dataType);\n}\n\nexport function generateIdentifier(ctx: GenerationCtx, id: string): Resource {\n const res = ctx.getById(id);\n if (!res) {\n throw new Error(`Identifier ${id} not found`);\n }\n\n return res;\n}\n\nexport function generateExpression(\n ctx: GenerationCtx,\n expression: smol.Expression,\n): Resource {\n if (typeof expression === 'string') {\n return generateIdentifier(ctx, expression);\n }\n if (typeof expression === 'boolean') {\n return generateBoolean(ctx, expression);\n }\n\n if ('x' in expression) {\n // Logical/Binary/Assignment Expression\n const [lhs, op, rhs] = expression.x;\n const lhsExpr = generateExpression(ctx, lhs);\n const rhsExpr = generateExpression(ctx, rhs);\n\n const lhsStr = resolveRes(ctx, lhsExpr);\n const rhsStr = resolveRes(ctx, rhsExpr);\n const type = operatorToType(lhsExpr.dataType, op, rhsExpr.dataType);\n\n return {\n value: parenthesizedOps.includes(op)\n ? `(${lhsStr} ${op} ${rhsStr})`\n : `${lhsStr} ${op} ${rhsStr}`,\n dataType: type,\n };\n }\n\n if ('p' in expression) {\n // Update Expression\n const [op, arg] = expression.p;\n const argExpr = generateExpression(ctx, arg);\n const argStr = resolveRes(ctx, argExpr);\n\n return {\n value: `${argStr}${op}`,\n dataType: argExpr.dataType,\n };\n }\n\n if ('u' in expression) {\n // Unary Expression\n const [op, arg] = expression.u;\n const argExpr = generateExpression(ctx, arg);\n const argStr = resolveRes(ctx, argExpr);\n\n const type = operatorToType(argExpr.dataType, op);\n return {\n value: `${op}${argStr}`,\n dataType: type,\n };\n }\n\n if ('a' in expression) {\n // Member Access\n const [targetId, property] = expression.a;\n const target = generateExpression(ctx, targetId);\n\n if (typeof target.value === 'string') {\n return {\n value: `${target.value}.${property}`,\n dataType: getTypeForPropAccess(target.dataType as Wgsl, property),\n };\n }\n // biome-ignore lint/suspicious/noExplicitAny: <sorry TypeScript>\n const propValue = (target.value as any)[property];\n\n if (isWgsl(target.value)) {\n return {\n value: propValue,\n dataType: getTypeForPropAccess(target.value as d.AnyWgslData, property),\n };\n }\n\n if (typeof target.value === 'object') {\n const dataType = isWgsl(propValue)\n ? getTypeFromWgsl(propValue)\n : UnknownData;\n\n return {\n value: propValue,\n dataType,\n };\n }\n\n throw new Error(`Cannot access member ${property} of ${target.value}`);\n }\n\n if ('i' in expression) {\n // Index Access\n const [target, property] = expression.i;\n const targetExpr = generateExpression(ctx, target);\n const propertyExpr = generateExpression(ctx, property);\n const targetStr = resolveRes(ctx, targetExpr);\n const propertyStr = resolveRes(ctx, propertyExpr);\n\n return {\n value: `${targetStr}[${propertyStr}]`,\n dataType: getTypeForIndexAccess(targetExpr.dataType as d.AnyWgslData),\n };\n }\n\n if ('n' in expression) {\n // Numeric Literal\n const type = numericLiteralToResource(expression.n);\n if (!type) {\n throw new Error(`Invalid numeric literal ${expression.n}`);\n }\n return type;\n }\n\n if ('f' in expression) {\n // Function Call\n const [callee, args] = expression.f;\n const id = generateExpression(ctx, callee);\n const idValue = id.value;\n\n ctx.callStack.push(idValue);\n\n const argResources = args.map((arg) => generateExpression(ctx, arg));\n const resolvedResources = argResources.map((res) => ({\n value: resolveRes(ctx, res),\n dataType: res.dataType,\n }));\n const argValues = resolvedResources.map((res) => res.value);\n\n ctx.callStack.pop();\n\n if (typeof idValue === 'string') {\n return {\n value: `${idValue}(${argValues.join(', ')})`,\n dataType: id.dataType,\n };\n }\n\n if (wgsl.isWgslStruct(idValue)) {\n const resolvedId = ctx.resolve(idValue);\n\n return {\n value: `${resolvedId}(${argValues.join(', ')})`,\n dataType: id.dataType,\n };\n }\n\n // Assuming that `id` is callable\n return (idValue as unknown as (...args: unknown[]) => unknown)(\n ...resolvedResources,\n ) as Resource;\n }\n\n if ('o' in expression) {\n // Object Literal\n const obj = expression.o;\n const callee = ctx.callStack[ctx.callStack.length - 1];\n\n const generateEntries = (values: smol.Expression[]) =>\n values\n .map((value) => {\n const valueRes = generateExpression(ctx, value);\n return resolveRes(ctx, valueRes);\n })\n .join(', ');\n\n if (wgsl.isWgslStruct(callee)) {\n const propKeys = Object.keys(callee.propTypes);\n const values = propKeys.map((key) => {\n const val = obj[key];\n if (val === undefined) {\n throw new Error(\n `Missing property ${key} in object literal for struct ${callee}`,\n );\n }\n return val;\n });\n\n return {\n value: generateEntries(values),\n dataType: callee,\n };\n }\n\n return {\n value: generateEntries(Object.values(obj)),\n dataType: UnknownData,\n };\n }\n\n if ('s' in expression) {\n throw new Error('Cannot use string literals in TGSL.');\n }\n\n assertExhaustive(expression);\n}\n\nexport function generateStatement(\n ctx: GenerationCtx,\n statement: smol.Statement,\n): string {\n if (typeof statement === 'string') {\n return `${ctx.pre}${resolveRes(ctx, generateIdentifier(ctx, statement))};`;\n }\n\n if (typeof statement === 'boolean') {\n return `${ctx.pre}${resolveRes(ctx, generateBoolean(ctx, statement))};`;\n }\n\n if ('r' in statement) {\n // check if the thing at the top of the call stack is a struct and the statement is a plain JS object\n // if so wrap the value returned in a constructor of the struct (its resolved name)\n if (\n wgsl.isWgslStruct(ctx.callStack[ctx.callStack.length - 1]) &&\n statement.r !== null &&\n typeof statement.r === 'object' &&\n 'o' in statement.r\n ) {\n const resource = resolveRes(ctx, generateExpression(ctx, statement.r));\n const resolvedStruct = ctx.resolve(\n ctx.callStack[ctx.callStack.length - 1],\n );\n return `${ctx.pre}return ${resolvedStruct}(${resource});`;\n }\n\n return statement.r === null\n ? `${ctx.pre}return;`\n : `${ctx.pre}return ${resolveRes(\n ctx,\n generateExpression(ctx, statement.r),\n )};`;\n }\n\n if ('q' in statement) {\n const [cond, cons, alt] = statement.q;\n const condition = resolveRes(ctx, generateExpression(ctx, cond));\n\n ctx.indent(); // {\n const consequent = generateStatement(ctx, cons);\n ctx.dedent(); // }\n\n ctx.indent(); // {\n const alternate = alt ? generateStatement(ctx, alt) : undefined;\n ctx.dedent(); // }\n\n if (!alternate) {\n return `\\\n${ctx.pre}if (${condition})\n${consequent}`;\n }\n\n return `\\\n${ctx.pre}if (${condition})\n${consequent}\n${ctx.pre}else\n${alternate}`;\n }\n\n if ('l' in statement || 'c' in statement) {\n const [rawId, rawValue] = 'l' in statement ? statement.l : statement.c;\n const eq = rawValue ? generateExpression(ctx, rawValue) : undefined;\n\n if (!eq || !rawValue) {\n throw new Error('Cannot create variable without an initial value.');\n }\n\n registerBlockVariable(ctx, rawId, eq.dataType);\n const id = resolveRes(ctx, generateIdentifier(ctx, rawId));\n\n // If the value is a plain JS object it has to be an output struct\n if (\n typeof rawValue === 'object' &&\n 'o' in rawValue &&\n wgsl.isWgslStruct(ctx.callStack[ctx.callStack.length - 1])\n ) {\n const resolvedStruct = ctx.resolve(\n ctx.callStack[ctx.callStack.length - 1],\n );\n return `${ctx.pre}var ${id} = ${resolvedStruct}(${resolveRes(ctx, eq)});`;\n }\n\n return `${ctx.pre}var ${id} = ${resolveRes(ctx, eq)};`;\n }\n\n if ('b' in statement) {\n ctx.pushBlockScope();\n try {\n return generateBlock(ctx, statement);\n } finally {\n ctx.popBlockScope();\n }\n }\n\n // 'j' stands for for (trust me)\n if ('j' in statement) {\n const [init, condition, update, body] = statement.j;\n\n const initStatement = init ? generateStatement(ctx, init) : undefined;\n const initStr = initStatement ? initStatement.slice(0, -1) : '';\n\n const conditionExpr = condition\n ? generateExpression(ctx, condition)\n : undefined;\n const conditionStr = conditionExpr ? resolveRes(ctx, conditionExpr) : '';\n\n const updateStatement = update ? generateStatement(ctx, update) : undefined;\n const updateStr = updateStatement ? updateStatement.slice(0, -1) : '';\n\n ctx.indent();\n const bodyStr = generateStatement(ctx, body);\n ctx.dedent();\n\n return `\\\n${ctx.pre}for (${initStr}; ${conditionStr}; ${updateStr})\n${bodyStr}`;\n }\n\n if ('w' in statement) {\n const [condition, body] = statement.w;\n const conditionStr = resolveRes(ctx, generateExpression(ctx, condition));\n\n ctx.indent();\n const bodyStr = generateStatement(ctx, body);\n ctx.dedent();\n\n return `\\\n${ctx.pre}while (${conditionStr})\n${bodyStr}`;\n }\n\n if ('k' in statement) {\n return `${ctx.pre}continue;`;\n }\n\n if ('d' in statement) {\n return `${ctx.pre}break;`;\n }\n\n return `${ctx.pre}${resolveRes(ctx, generateExpression(ctx, statement))};`;\n}\n\nexport function generateFunction(ctx: GenerationCtx, body: smol.Block): string {\n return generateBlock(ctx, body);\n}\n","import { roundUp } from '../mathUtils';\nimport type { Infer } from '../shared/repr';\nimport { alignmentOf } from './alignmentOf';\nimport { isDisarray, isUnstruct } from './dataTypes';\nimport { offsetsForProps } from './offsets';\nimport { sizeOf } from './sizeOf';\nimport * as wgsl from './wgslTypes';\n\nexport const EVAL_ALLOWED_IN_ENV: boolean = (() => {\n try {\n new Function('return true');\n return true;\n } catch {\n return false;\n }\n})();\n\nconst compiledWriters = new WeakMap<\n wgsl.BaseData,\n (\n output: DataView,\n offset: number,\n value: unknown,\n littleEndian?: boolean,\n ) => void\n>();\n\nconst typeToPrimitive = {\n u32: 'u32',\n vec2u: 'u32',\n vec3u: 'u32',\n vec4u: 'u32',\n\n i32: 'i32',\n vec2i: 'i32',\n vec3i: 'i32',\n vec4i: 'i32',\n\n f32: 'f32',\n vec2f: 'f32',\n vec3f: 'f32',\n vec4f: 'f32',\n\n vec2h: 'f32',\n vec3h: 'f32',\n vec4h: 'f32',\n\n mat2x2f: 'f32',\n mat3x3f: 'f32',\n mat4x4f: 'f32',\n} as const;\n\nconst primitiveToWriteFunction = {\n u32: 'setUint32',\n i32: 'setInt32',\n f32: 'setFloat32',\n} as const;\n\nexport function buildWriter(\n node: wgsl.BaseData,\n offsetExpr: string,\n valueExpr: string,\n): string {\n if (wgsl.isWgslStruct(node) || isUnstruct(node)) {\n const propOffsets = offsetsForProps(node);\n const sortedProps = Object.entries(propOffsets).sort(\n (a, b) => a[1].offset - b[1].offset,\n );\n let code = '';\n for (const [key, propOffset] of sortedProps) {\n const subSchema = node.propTypes[key];\n if (!subSchema) continue;\n code += buildWriter(\n subSchema,\n `(${offsetExpr} + ${propOffset.offset})`,\n `${valueExpr}.${key}`,\n );\n }\n return code;\n }\n\n if (wgsl.isWgslArray(node) || isDisarray(node)) {\n const arrSchema = node as wgsl.WgslArray;\n const elementSize = roundUp(\n sizeOf(arrSchema.elementType),\n alignmentOf(arrSchema.elementType),\n );\n let code = '';\n\n code += `for (let i = 0; i < ${arrSchema.elementCount}; i++) {\\n`;\n code += buildWriter(\n arrSchema.elementType,\n `(${offsetExpr} + i * ${elementSize})`,\n `${valueExpr}[i]`,\n );\n code += '}\\n';\n\n return code;\n }\n\n if (wgsl.isVec(node)) {\n const primitive = typeToPrimitive[node.type];\n let code = '';\n const writeFunc = primitiveToWriteFunction[primitive];\n const components = ['x', 'y', 'z', 'w'];\n const count = wgsl.isVec2(node) ? 2 : wgsl.isVec3(node) ? 3 : 4;\n\n for (let i = 0; i < count; i++) {\n code += `output.${writeFunc}((${offsetExpr} + ${i * 4}), ${valueExpr}.${components[i]}, littleEndian);\\n`;\n }\n return code;\n }\n\n if (wgsl.isMat(node)) {\n const primitive = typeToPrimitive[node.type];\n const writeFunc = primitiveToWriteFunction[primitive];\n\n const matSize = wgsl.isMat2x2f(node) ? 2 : wgsl.isMat3x3f(node) ? 3 : 4;\n const elementCount = matSize * matSize;\n const rowStride = roundUp(matSize * 4, 8);\n\n let code = '';\n for (let i = 0; i < elementCount; i++) {\n const colIndex = Math.floor(i / matSize);\n const rowIndex = i % matSize;\n const byteOffset = colIndex * rowStride + rowIndex * 4;\n\n code += `output.${writeFunc}((${offsetExpr} + ${byteOffset}), ${valueExpr}.columns[${colIndex}].${['x', 'y', 'z', 'w'][rowIndex]}, littleEndian);\\n`;\n }\n\n return code;\n }\n\n const primitive = typeToPrimitive[node.type as keyof typeof typeToPrimitive];\n return `output.${primitiveToWriteFunction[primitive]}(${offsetExpr}, ${valueExpr}, littleEndian);\\n`;\n}\n\nexport function getCompiledWriterForSchema<T extends wgsl.BaseData>(\n schema: T,\n): (\n output: DataView,\n offset: number,\n value: Infer<T>,\n littleEndian?: boolean,\n) => void {\n if (compiledWriters.has(schema)) {\n return compiledWriters.get(schema) as (\n output: DataView,\n offset: number,\n value: Infer<T>,\n littleEndian?: boolean,\n ) => void;\n }\n\n const body = buildWriter(schema, 'offset', 'value');\n\n const fn = new Function(\n 'output',\n 'offset',\n 'value',\n 'littleEndian=true',\n body,\n ) as (\n output: DataView,\n offset: number,\n value: Infer<T> | unknown,\n littleEndian?: boolean,\n ) => void;\n\n compiledWriters.set(schema, fn);\n\n return fn;\n}\n","import type { ISerialInput, ISerialOutput } from 'typed-binary';\nimport type { Infer, InferRecord } from '../shared/repr';\nimport alignIO from './alignIO';\nimport { alignmentOf, customAlignmentOf } from './alignmentOf';\nimport type {\n AnyConcreteData,\n AnyData,\n Disarray,\n LooseDecorated,\n Unstruct,\n} from './dataTypes';\nimport { mat2x2f, mat3x3f, mat4x4f } from './matrix';\nimport { sizeOf } from './sizeOf';\nimport type { WgslStruct } from './struct';\nimport {\n vec2f,\n vec2h,\n vec2i,\n vec2u,\n vec3f,\n vec3h,\n vec3i,\n vec3u,\n vec4f,\n vec4h,\n vec4i,\n vec4u,\n} from './vector';\nimport type * as wgsl from './wgslTypes';\n\ntype DataWriter<TSchema extends wgsl.BaseData> = (\n output: ISerialOutput,\n schema: TSchema,\n value: Infer<TSchema>,\n) => void;\n\ntype DataReader<TSchema extends wgsl.BaseData> = (\n input: ISerialInput,\n schema: TSchema,\n) => Infer<TSchema>;\n\ntype CompleteDataWriters = {\n [TType in AnyConcreteData['type']]: DataWriter<\n Extract<AnyData, { readonly type: TType }>\n >;\n};\n\ntype CompleteDataReaders = {\n [TType in AnyConcreteData['type']]: DataReader<\n Extract<AnyData, { readonly type: TType }>\n >;\n};\n\nconst dataWriters = {\n bool(output, _schema: wgsl.Bool, value: boolean) {\n output.writeBool(value);\n },\n\n f32(output, _schema: wgsl.F32, value: number) {\n output.writeFloat32(value);\n },\n\n f16(output, _schema: wgsl.F16, value: number) {\n output.writeFloat16(value);\n },\n\n i32(output, _schema: wgsl.I32, value: number) {\n output.writeInt32(value);\n },\n\n u32(output, _schema: wgsl.U32, value: number) {\n output.writeUint32(value);\n },\n\n vec2f(output, _, value: wgsl.v2f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n },\n\n vec2h(output, _, value: wgsl.v2h) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n },\n\n vec2i(output, _, value: wgsl.v2i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n },\n\n vec2u(output, _, value: wgsl.v2u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n },\n\n vec3f(output, _, value: wgsl.v3f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n output.writeFloat32(value.z);\n },\n\n vec3h(output, _, value: wgsl.v3h) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n output.writeFloat16(value.z);\n },\n\n vec3i(output, _, value: wgsl.v3i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n output.writeInt32(value.z);\n },\n\n vec3u(output, _, value: wgsl.v3u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n output.writeUint32(value.z);\n },\n\n vec4f(output, _, value: wgsl.v4f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n output.writeFloat32(value.z);\n output.writeFloat32(value.w);\n },\n\n vec4h(output, _, value: wgsl.v4h) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n output.writeFloat16(value.z);\n output.writeFloat16(value.w);\n },\n\n vec4i(output, _, value: wgsl.v4i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n output.writeInt32(value.z);\n output.writeInt32(value.w);\n },\n\n vec4u(output, _, value: wgsl.v4u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n output.writeUint32(value.z);\n output.writeUint32(value.w);\n },\n\n mat2x2f(output, _, value: wgsl.m2x2f) {\n for (let i = 0; i < value.length; ++i) {\n output.writeFloat32(value[i] as number);\n }\n },\n\n mat3x3f(output, _, value: wgsl.m3x3f) {\n for (let i = 0; i < value.length; ++i) {\n output.writeFloat32(value[i] as number);\n }\n },\n\n mat4x4f(output, _, value: wgsl.m4x4f) {\n for (let i = 0; i < value.length; ++i) {\n output.writeFloat32(value[i] as number);\n }\n },\n\n struct(\n output,\n schema: WgslStruct,\n value: InferRecord<Record<string, wgsl.BaseData>>,\n ) {\n const alignment = alignmentOf(schema);\n alignIO(output, alignment);\n\n for (const [key, property] of Object.entries(schema.propTypes)) {\n alignIO(output, alignmentOf(property));\n writeData(output, property, value[key] as wgsl.BaseData);\n }\n\n alignIO(output, alignment);\n },\n\n array(output, schema: wgsl.WgslArray, value: Infer<wgsl.BaseData>[]) {\n if (schema.elementCount === 0) {\n throw new Error('Cannot write using a runtime-sized schema.');\n }\n\n const alignment = alignmentOf(schema);\n alignIO(output, alignment);\n const beginning = output.currentByteOffset;\n for (let i = 0; i < Math.min(schema.elementCount, value.length); i++) {\n alignIO(output, alignment);\n writeData(output, schema.elementType, value[i]);\n }\n output.seekTo(beginning + sizeOf(schema));\n },\n\n ptr() {\n throw new Error('Pointers are not host-shareable');\n },\n\n atomic(output, schema: wgsl.Atomic, value: number) {\n dataWriters[schema.inner.type]?.(output, schema, value);\n },\n\n decorated(output, schema: wgsl.Decorated, value: unknown) {\n const alignment = customAlignmentOf(schema);\n alignIO(output, alignment);\n\n const beginning = output.currentByteOffset;\n dataWriters[(schema.inner as AnyData)?.type]?.(output, schema.inner, value);\n output.seekTo(beginning + sizeOf(schema));\n },\n\n // Loose Types\n\n uint8(output, _, value: number) {\n output.writeUint8(value);\n },\n uint8x2(output, _, value: wgsl.v2u) {\n output.writeUint8(value.x);\n output.writeUint8(value.y);\n },\n uint8x4(output, _, value: wgsl.v4u) {\n output.writeUint8(value.x);\n output.writeUint8(value.y);\n output.writeUint8(value.z);\n output.writeUint8(value.w);\n },\n sint8(output, _, value: number) {\n output.writeInt8(value);\n },\n sint8x2(output, _, value: wgsl.v2i) {\n output.writeInt8(value.x);\n output.writeInt8(value.y);\n },\n sint8x4(output, _, value: wgsl.v4i) {\n output.writeInt8(value.x);\n output.writeInt8(value.y);\n output.writeInt8(value.z);\n output.writeInt8(value.w);\n },\n unorm8(output, _, value: number) {\n output.writeUint8(value * 255);\n },\n unorm8x2(output, _, value: wgsl.v2f) {\n output.writeUint8(value.x * 255);\n output.writeUint8(value.y * 255);\n },\n unorm8x4(output, _, value: wgsl.v4f) {\n output.writeUint8(value.x * 255);\n output.writeUint8(value.y * 255);\n output.writeUint8(value.z * 255);\n output.writeUint8(value.w * 255);\n },\n snorm8(output, _, value: number) {\n output.writeUint8(value * 127 + 128);\n },\n snorm8x2(output, _, value: wgsl.v2f) {\n output.writeUint8(value.x * 127 + 128);\n output.writeUint8(value.y * 127 + 128);\n },\n snorm8x4(output, _, value: wgsl.v4f) {\n output.writeUint8(value.x * 127 + 128);\n output.writeUint8(value.y * 127 + 128);\n output.writeUint8(value.z * 127 + 128);\n output.writeUint8(value.w * 127 + 128);\n },\n uint16(output, _, value: number) {\n output.writeUint16(value);\n },\n uint16x2(output, _, value: wgsl.v2u) {\n output.writeUint16(value.x);\n output.writeUint16(value.y);\n },\n uint16x4(output, _, value: wgsl.v4u) {\n output.writeUint16(value.x);\n output.writeUint16(value.y);\n output.writeUint16(value.z);\n output.writeUint16(value.w);\n },\n sint16(output, _, value: number) {\n output.writeInt16(value);\n },\n sint16x2(output, _, value: wgsl.v2i) {\n output.writeInt16(value.x);\n output.writeInt16(value.y);\n },\n sint16x4(output, _, value: wgsl.v4i) {\n output.writeInt16(value.x);\n output.writeInt16(value.y);\n output.writeInt16(value.z);\n output.writeInt16(value.w);\n },\n unorm16(output, _, value: number) {\n output.writeUint16(value * 65535);\n },\n unorm16x2(output, _, value: wgsl.v2f) {\n output.writeUint16(value.x * 65535);\n output.writeUint16(value.y * 65535);\n },\n unorm16x4(output, _, value: wgsl.v4f) {\n output.writeUint16(value.x * 65535);\n output.writeUint16(value.y * 65535);\n output.writeUint16(value.z * 65535);\n output.writeUint16(value.w * 65535);\n },\n snorm16(output, _, value: number) {\n output.writeUint16(value * 32767 + 32768);\n },\n snorm16x2(output, _, value: wgsl.v2f) {\n output.writeUint16(value.x * 32767 + 32768);\n output.writeUint16(value.y * 32767 + 32768);\n },\n snorm16x4(output, _, value: wgsl.v4f) {\n output.writeUint16(value.x * 32767 + 32768);\n output.writeUint16(value.y * 32767 + 32768);\n output.writeUint16(value.z * 32767 + 32768);\n output.writeUint16(value.w * 32767 + 32768);\n },\n float16(output, _, value: number) {\n output.writeFloat16(value);\n },\n float16x2(output, _, value: wgsl.v2f) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n },\n float16x4(output, _, value: wgsl.v4f) {\n output.writeFloat16(value.x);\n output.writeFloat16(value.y);\n output.writeFloat16(value.z);\n output.writeFloat16(value.w);\n },\n float32(output, _, value: number) {\n output.writeFloat32(value);\n },\n float32x2(output, _, value: wgsl.v2f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n },\n float32x3(output, _, value: wgsl.v3f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n output.writeFloat32(value.z);\n },\n float32x4(output, _, value: wgsl.v4f) {\n output.writeFloat32(value.x);\n output.writeFloat32(value.y);\n output.writeFloat32(value.z);\n output.writeFloat32(value.w);\n },\n uint32(output, _, value: number) {\n output.writeUint32(value);\n },\n uint32x2(output, _, value: wgsl.v2u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n },\n uint32x3(output, _, value: wgsl.v3u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n output.writeUint32(value.z);\n },\n uint32x4(output, _, value: wgsl.v4u) {\n output.writeUint32(value.x);\n output.writeUint32(value.y);\n output.writeUint32(value.z);\n output.writeUint32(value.w);\n },\n sint32(output, _, value: number) {\n output.writeInt32(value);\n },\n sint32x2(output, _, value: wgsl.v2i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n },\n sint32x3(output, _, value: wgsl.v3i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n output.writeInt32(value.z);\n },\n sint32x4(output, _, value: wgsl.v4i) {\n output.writeInt32(value.x);\n output.writeInt32(value.y);\n output.writeInt32(value.z);\n output.writeInt32(value.w);\n },\n 'unorm10-10-10-2'(output, _, value: wgsl.v4f) {\n let packed = 0;\n packed |= ((value.x * 1023) & 1023) << 22; // r (10 bits)\n packed |= ((value.x * 1023) & 1023) << 12; // g (10 bits)\n packed |= ((value.y * 1023) & 1023) << 2; // b (10 bits)\n packed |= (value.z * 3) & 3; // a (2 bits)\n output.writeUint32(packed);\n },\n 'unorm8x4-bgra'(output, _, value: wgsl.v4f) {\n output.writeUint8(value.z * 255);\n output.writeUint8(value.y * 255);\n output.writeUint8(value.x * 255);\n output.writeUint8(value.w * 255);\n },\n\n disarray(output, schema: Disarray, value: unknown[]) {\n const alignment = alignmentOf(schema);\n\n alignIO(output, alignment);\n const beginning = output.currentByteOffset;\n for (let i = 0; i < Math.min(schema.elementCount, value.length); i++) {\n alignIO(output, alignment);\n dataWriters[(schema.elementType as AnyData)?.type]?.(\n output,\n schema.elementType,\n value[i],\n );\n }\n\n output.seekTo(beginning + sizeOf(schema));\n },\n\n unstruct(output, schema: Unstruct, value) {\n for (const [key, property] of Object.entries(schema.propTypes)) {\n dataWriters[property.type]?.(output, property, value[key]);\n }\n },\n\n 'loose-decorated'(output, schema: LooseDecorated, value: unknown) {\n const alignment = customAlignmentOf(schema);\n alignIO(output, alignment);\n\n const beginning = output.currentByteOffset;\n const writer = dataWriters[(schema.inner as AnyData)?.type];\n writer?.(output, schema.inner, value);\n output.seekTo(beginning + sizeOf(schema));\n return value;\n },\n} satisfies CompleteDataWriters as Record<\n string,\n (output: ISerialOutput, schema: unknown, value: unknown) => void\n>;\n\nexport function writeData<TData extends wgsl.BaseData>(\n output: ISerialOutput,\n schema: TData,\n value: Infer<TData>,\n): void {\n const writer = dataWriters[schema.type];\n if (!writer) {\n throw new Error(`Cannot write data of type '${schema.type}'.`);\n }\n\n writer(output, schema, value);\n}\n\nconst dataReaders = {\n bool(input: ISerialInput): boolean {\n return input.readBool();\n },\n\n f32(input: ISerialInput): number {\n return input.readFloat32();\n },\n\n f16(input: ISerialInput): number {\n return input.readFloat16();\n },\n\n i32(input: ISerialInput): number {\n return input.readInt32();\n },\n\n u32(input: ISerialInput): number {\n return input.readUint32();\n },\n\n vec2f(input: ISerialInput): wgsl.v2f {\n return vec2f(input.readFloat32(), input.readFloat32());\n },\n\n vec3f(input: ISerialInput): wgsl.v3f {\n return vec3f(input.readFloat32(), input.readFloat32(), input.readFloat32());\n },\n\n vec4f(input: ISerialInput): wgsl.v4f {\n return vec4f(\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n );\n },\n\n vec2h(input): wgsl.v2h {\n return vec2h(input.readFloat16(), input.readFloat16());\n },\n\n vec3h(input: ISerialInput): wgsl.v3h {\n return vec3h(input.readFloat16(), input.readFloat16(), input.readFloat16());\n },\n\n vec4h(input: ISerialInput): wgsl.v4h {\n return vec4h(\n input.readFloat16(),\n input.readFloat16(),\n input.readFloat16(),\n input.readFloat16(),\n );\n },\n\n vec2i(input): wgsl.v2i {\n return vec2i(input.readInt32(), input.readInt32());\n },\n\n vec3i(input: ISerialInput): wgsl.v3i {\n return vec3i(input.readInt32(), input.readInt32(), input.readInt32());\n },\n\n vec4i(input: ISerialInput): wgsl.v4i {\n return vec4i(\n input.readInt32(),\n input.readInt32(),\n input.readInt32(),\n input.readInt32(),\n );\n },\n\n vec2u(input): wgsl.v2u {\n return vec2u(input.readUint32(), input.readUint32());\n },\n\n vec3u(input: ISerialInput): wgsl.v3u {\n return vec3u(input.readUint32(), input.readUint32(), input.readUint32());\n },\n\n vec4u(input: ISerialInput): wgsl.v4u {\n return vec4u(\n input.readUint32(),\n input.readUint32(),\n input.readUint32(),\n input.readUint32(),\n );\n },\n\n mat2x2f(input: ISerialInput): wgsl.m2x2f {\n return mat2x2f(\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n );\n },\n\n mat3x3f(input: ISerialInput): wgsl.m3x3f {\n const skipOneAfter = () => {\n const value = input.readFloat32();\n input.readFloat32(); // skipping;\n return value;\n };\n\n return mat3x3f(\n input.readFloat32(),\n input.readFloat32(),\n skipOneAfter(),\n //\n input.readFloat32(),\n input.readFloat32(),\n skipOneAfter(),\n //\n input.readFloat32(),\n input.readFloat32(),\n skipOneAfter(),\n );\n },\n\n mat4x4f(input: ISerialInput): wgsl.m4x4f {\n return mat4x4f(\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n //\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n //\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n //\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n input.readFloat32(),\n );\n },\n\n struct(input: ISerialInput, schema: WgslStruct) {\n const alignment = alignmentOf(schema);\n alignIO(input, alignment);\n const result = {} as Record<string, unknown>;\n\n for (const [key, property] of Object.entries(schema.propTypes)) {\n alignIO(input, alignmentOf(property));\n result[key] = readData(input, property);\n }\n\n alignIO(input, alignment);\n return result as InferRecord<Record<string, wgsl.BaseData>>;\n },\n\n array(input, schema) {\n if (schema.elementCount === 0) {\n throw new Error('Cannot read using a runtime-sized schema.');\n }\n\n const alignment = alignmentOf(schema);\n const elements: unknown[] = [];\n\n for (let i = 0; i < schema.elementCount; i++) {\n alignIO(input, alignment);\n const elementType = schema.elementType as wgsl.AnyWgslData;\n const value = readData(input, elementType);\n elements.push(value);\n }\n\n alignIO(input, alignment);\n return elements as never[];\n },\n\n ptr() {\n throw new Error('Pointers are not host-shareable');\n },\n\n atomic(input, schema: wgsl.Atomic): number {\n return readData(input, schema.inner);\n },\n\n decorated(input, schema: wgsl.Decorated) {\n const alignment = customAlignmentOf(schema);\n alignIO(input, alignment);\n\n const beginning = input.currentByteOffset;\n const value = readData(input, schema.inner);\n input.seekTo(beginning + sizeOf(schema));\n return value as never;\n },\n\n // Loose Types\n\n uint8: (i) => i.readUint8(),\n uint8x2: (i) => vec2u(i.readUint8(), i.readUint8()),\n uint8x4: (i) =>\n vec4u(i.readUint8(), i.readUint8(), i.readUint8(), i.readUint8()),\n sint8: (i) => i.readInt8(),\n sint8x2: (i) => {\n return vec2i(i.readInt8(), i.readInt8());\n },\n sint8x4: (i) => vec4i(i.readInt8(), i.readInt8(), i.readInt8(), i.readInt8()),\n unorm8: (i) => i.readUint8() / 255,\n unorm8x2: (i) => vec2f(i.readUint8() / 255, i.readUint8() / 255),\n unorm8x4: (i) =>\n vec4f(\n i.readUint8() / 255,\n i.readUint8() / 255,\n i.readUint8() / 255,\n i.readUint8() / 255,\n ),\n snorm8: (i) => (i.readUint8() - 128) / 127,\n snorm8x2: (i) =>\n vec2f((i.readUint8() - 128) / 127, (i.readUint8() - 128) / 127),\n snorm8x4: (i) =>\n vec4f(\n (i.readUint8() - 128) / 127,\n (i.readUint8() - 128) / 127,\n (i.readUint8() - 128) / 127,\n (i.readUint8() - 128) / 127,\n ),\n uint16: (i) => i.readUint16(),\n uint16x2: (i) => vec2u(i.readUint16(), i.readUint16()),\n uint16x4: (i) =>\n vec4u(i.readUint16(), i.readUint16(), i.readUint16(), i.readUint16()),\n sint16: (i) => i.readInt16(),\n sint16x2: (i) => vec2i(i.readInt16(), i.readInt16()),\n sint16x4: (i) =>\n vec4i(i.readInt16(), i.readInt16(), i.readInt16(), i.readInt16()),\n unorm16: (i) => i.readUint16() / 65535,\n unorm16x2: (i) => vec2f(i.readUint16() / 65535, i.readUint16() / 65535),\n unorm16x4: (i) =>\n vec4f(\n i.readUint16() / 65535,\n i.readUint16() / 65535,\n i.readUint16() / 65535,\n i.readUint16() / 65535,\n ),\n snorm16: (i) => (i.readUint16() - 32768) / 32767,\n snorm16x2: (i): wgsl.v2f =>\n vec2f(dataReaders.snorm16(i), dataReaders.snorm16(i)),\n snorm16x4: (i): wgsl.v4f =>\n vec4f(\n dataReaders.snorm16(i),\n dataReaders.snorm16(i),\n dataReaders.snorm16(i),\n dataReaders.snorm16(i),\n ),\n float16(i) {\n return i.readFloat16();\n },\n float16x2: (i) => vec2f(i.readFloat16(), i.readFloat16()),\n float16x4: (i) =>\n vec4f(i.readFloat16(), i.readFloat16(), i.readFloat16(), i.readFloat16()),\n float32: (i) => i.readFloat32(),\n float32x2: (i) => vec2f(i.readFloat32(), i.readFloat32()),\n float32x3: (i) => vec3f(i.readFloat32(), i.readFloat32(), i.readFloat32()),\n float32x4: (i) =>\n vec4f(i.readFloat32(), i.readFloat32(), i.readFloat32(), i.readFloat32()),\n uint32: (i) => i.readUint32(),\n uint32x2: (i) => vec2u(i.readUint32(), i.readUint32()),\n uint32x3: (i) => vec3u(i.readUint32(), i.readUint32(), i.readUint32()),\n uint32x4: (i) =>\n vec4u(i.readUint32(), i.readUint32(), i.readUint32(), i.readUint32()),\n sint32: (i) => i.readInt32(),\n sint32x2: (i) => vec2i(i.readInt32(), i.readInt32()),\n sint32x3: (i) => vec3i(i.readInt32(), i.readInt32(), i.readInt32()),\n sint32x4: (i) =>\n vec4i(i.readInt32(), i.readInt32(), i.readInt32(), i.readInt32()),\n 'unorm10-10-10-2'(i) {\n const packed = i.readUint32();\n const r = (packed >> 22) / 1023;\n const g = ((packed >> 12) & 1023) / 1023;\n const b = ((packed >> 2) & 1023) / 1023;\n const a = (packed & 3) / 3;\n return vec4f(r, g, b, a);\n },\n 'unorm8x4-bgra'(i) {\n const b = i.readByte() / 255;\n const g = i.readByte() / 255;\n const r = i.readByte() / 255;\n const a = i.readByte() / 255;\n return vec4f(r, g, b, a);\n },\n\n unstruct(input, schema: Unstruct) {\n const result = {} as Record<string, unknown>;\n\n for (const [key, property] of Object.entries(schema.propTypes)) {\n result[key] = readData(input, property);\n }\n\n return result as InferRecord<Record<string, wgsl.BaseData>>;\n },\n\n disarray(input, schema: Disarray) {\n const alignment = alignmentOf(schema);\n const elements: unknown[] = [];\n\n for (let i = 0; i < schema.elementCount; i++) {\n alignIO(input, alignment);\n elements.push(readData(input, schema.elementType));\n }\n\n alignIO(input, alignment);\n return elements;\n },\n\n 'loose-decorated'(input, schema: LooseDecorated) {\n alignIO(input, customAlignmentOf(schema));\n\n const beginning = input.currentByteOffset;\n const value = readData(input, schema.inner);\n input.seekTo(beginning + sizeOf(schema));\n return value;\n },\n} satisfies CompleteDataReaders;\n\nexport function readData<TData extends wgsl.BaseData>(\n input: ISerialInput,\n schema: TData,\n): Infer<TData> {\n const reader = (dataReaders as Record<string, unknown>)[\n schema.type\n ] as DataReader<TData>;\n if (!reader) {\n throw new Error(`Cannot read data of type '${schema.type}'.`);\n }\n\n return reader(input, schema);\n}\n","import type { Block } from 'tinyest';\nimport { resolveData } from './core/resolve/resolveData';\nimport {\n type Eventual,\n type SlotValuePair,\n type TgpuDerived,\n type TgpuSlot,\n isDerived,\n isProviding,\n isSlot,\n} from './core/slot/slotTypes';\nimport { isData } from './data';\nimport { getAttributesString } from './data/attributes';\nimport {\n type AnyWgslData,\n type BaseData,\n isWgslArray,\n isWgslStruct,\n} from './data/wgslTypes';\nimport { MissingSlotValueError, ResolutionError } from './errors';\nimport { RuntimeMode, popMode, provideCtx, pushMode } from './gpuMode';\nimport type { JitTranspiler } from './jitTranspiler';\nimport type { NameRegistry } from './nameRegistry';\nimport { naturalsExcept } from './shared/generators';\nimport type { Infer } from './shared/repr';\nimport { $internal } from './shared/symbols';\nimport { generateFunction } from './smol';\nimport { getTypeFromWgsl } from './smol/generationHelpers';\nimport {\n type TgpuBindGroup,\n TgpuBindGroupImpl,\n type TgpuBindGroupLayout,\n type TgpuLayoutEntry,\n bindGroupLayout,\n} from './tgpuBindGroupLayout';\nimport type {\n FnToWgslOptions,\n ItemLayer,\n ItemStateStack,\n ResolutionCtx,\n Resource,\n Wgsl,\n} from './types';\nimport { UnknownData, isSelfResolvable, isWgsl } from './types';\n\n/**\n * Inserted into bind group entry definitions that belong\n * to the automatically generated catch-all bind group.\n *\n * A non-occupied group index can only be determined after\n * every resource has been resolved, so this acts as a placeholder\n * to be replaced with an actual numeric index at the very end\n * of the resolution process.\n */\nconst CATCHALL_BIND_GROUP_IDX_MARKER = '#CATCHALL#';\n\nexport type ResolutionCtxImplOptions = {\n readonly names: NameRegistry;\n readonly jitTranspiler?: JitTranspiler | undefined;\n};\n\ntype SlotToValueMap = Map<TgpuSlot<unknown>, unknown>;\n\ntype SlotBindingLayer = {\n type: 'slotBinding';\n bindingMap: WeakMap<TgpuSlot<unknown>, unknown>;\n};\n\ntype FunctionScopeLayer = {\n type: 'functionScope';\n args: Resource[];\n externalMap: Record<string, unknown>;\n returnType: AnyWgslData | undefined;\n};\n\ntype BlockScopeLayer = {\n type: 'blockScope';\n declarations: Map<string, AnyWgslData | UnknownData>;\n};\n\nclass ItemStateStackImpl implements ItemStateStack {\n private _stack: (\n | ItemLayer\n | SlotBindingLayer\n | FunctionScopeLayer\n | BlockScopeLayer\n )[] = [];\n private _itemDepth = 0;\n\n get itemDepth(): number {\n return this._itemDepth;\n }\n\n get topItem(): ItemLayer {\n const state = this._stack[this._stack.length - 1];\n if (!state || state.type !== 'item') {\n throw new Error('Internal error, expected item layer to be on top.');\n }\n return state;\n }\n\n pushItem() {\n this._itemDepth++;\n this._stack.push({\n type: 'item',\n usedSlots: new Set(),\n });\n }\n\n popItem() {\n this.pop('item');\n }\n\n pushSlotBindings(pairs: SlotValuePair<unknown>[]) {\n this._stack.push({\n type: 'slotBinding',\n bindingMap: new WeakMap(pairs),\n });\n }\n\n popSlotBindings() {\n this.pop('slotBinding');\n }\n\n pushFunctionScope(\n args: Resource[],\n returnType: AnyWgslData | undefined,\n externalMap: Record<string, unknown>,\n ) {\n this._stack.push({\n type: 'functionScope',\n args,\n returnType,\n externalMap,\n });\n }\n\n popFunctionScope() {\n this.pop('functionScope');\n }\n\n pushBlockScope() {\n this._stack.push({\n type: 'blockScope',\n declarations: new Map<string, AnyWgslData | UnknownData>(),\n });\n }\n\n popBlockScope() {\n this.pop('blockScope');\n }\n\n pop(type?: (typeof this._stack)[number]['type']) {\n const layer = this._stack[this._stack.length - 1];\n if (!layer || (type && layer.type !== type)) {\n throw new Error(`Internal error, expected a ${type} layer to be on top.`);\n }\n\n this._stack.pop();\n if (type === 'item') {\n this._itemDepth--;\n }\n }\n\n readSlot<T>(slot: TgpuSlot<T>): T | undefined {\n for (let i = this._stack.length - 1; i >= 0; --i) {\n const layer = this._stack[i];\n if (layer?.type === 'item') {\n // Binding not available yet, so this layer is dependent on the slot's value.\n layer.usedSlots.add(slot);\n } else if (layer?.type === 'slotBinding') {\n const boundValue = layer.bindingMap.get(slot);\n\n if (boundValue !== undefined) {\n return boundValue as T;\n }\n } else if (\n layer?.type === 'functionScope' ||\n layer?.type === 'blockScope'\n ) {\n // Skip\n } else {\n throw new Error('Unknown layer type.');\n }\n }\n\n return slot.defaultValue;\n }\n\n getResourceById(id: string): Resource | undefined {\n for (let i = this._stack.length - 1; i >= 0; --i) {\n const layer = this._stack[i];\n\n if (layer?.type === 'functionScope') {\n const arg = layer.args.find((a) => a.value === id);\n if (arg !== undefined) {\n return arg;\n }\n\n const external = layer.externalMap[id];\n if (external !== undefined) {\n return {\n value: external,\n dataType: isWgsl(external)\n ? getTypeFromWgsl(external)\n : UnknownData,\n };\n }\n\n // Since functions cannot access resources from the calling scope, we\n // return early here.\n return undefined;\n }\n\n if (layer?.type === 'blockScope') {\n const declarationType = layer.declarations.get(id);\n if (declarationType !== undefined) {\n return { value: id, dataType: declarationType };\n }\n } else {\n // Skip\n }\n }\n\n return undefined;\n }\n\n defineBlockVariable(id: string, type: AnyWgslData | UnknownData): Resource {\n for (let i = this._stack.length - 1; i >= 0; --i) {\n const layer = this._stack[i];\n\n if (layer?.type === 'blockScope') {\n layer.declarations.set(id, type);\n\n return { value: id, dataType: type };\n }\n }\n\n throw new Error('No block scope found to define a variable in.');\n }\n}\n\nconst INDENT = [\n '', // 0\n ' ', // 1\n ' ', // 2\n ' ', // 3\n ' ', // 4\n ' ', // 5\n ' ', // 6\n ' ', // 7\n ' ', // 8\n];\n\nconst N = INDENT.length - 1;\n\nexport class IndentController {\n private identLevel = 0;\n\n get pre(): string {\n return (\n INDENT[this.identLevel] ??\n (INDENT[N] as string).repeat(this.identLevel / N) +\n INDENT[this.identLevel % N]\n );\n }\n\n indent(): string {\n const str = this.pre;\n this.identLevel++;\n return str;\n }\n\n dedent(): string {\n this.identLevel--;\n return this.pre;\n }\n}\n\ninterface FixedBindingConfig {\n layoutEntry: TgpuLayoutEntry;\n resource: object;\n}\n\nexport class ResolutionCtxImpl implements ResolutionCtx {\n private readonly _memoizedResolves = new WeakMap<\n // WeakMap because if the item does not exist anymore,\n // apart from this map, there is no way to access the cached value anyway.\n object,\n { slotToValueMap: SlotToValueMap; result: string }[]\n >();\n private readonly _memoizedDerived = new WeakMap<\n // WeakMap because if the \"derived\" does not exist anymore,\n // apart from this map, there is no way to access the cached value anyway.\n TgpuDerived<unknown>,\n { slotToValueMap: SlotToValueMap; result: unknown }[]\n >();\n\n private readonly _indentController = new IndentController();\n private readonly _jitTranspiler: JitTranspiler | undefined;\n private readonly _itemStateStack = new ItemStateStackImpl();\n private readonly _declarations: string[] = [];\n\n readonly [$internal] = {\n itemStateStack: this._itemStateStack,\n };\n\n // -- Bindings\n /**\n * A map from registered bind group layouts to random strings put in\n * place of their group index. The whole tree has to be traversed to\n * collect every use of a typed bind group layout, since they can be\n * explicitly imposed group indices, and they cannot collide.\n */\n public readonly bindGroupLayoutsToPlaceholderMap = new Map<\n TgpuBindGroupLayout,\n string\n >();\n private _nextFreeLayoutPlaceholderIdx = 0;\n public readonly fixedBindings: FixedBindingConfig[] = [];\n // --\n\n public readonly callStack: unknown[] = [];\n public readonly names: NameRegistry;\n\n constructor(opts: ResolutionCtxImplOptions) {\n this.names = opts.names;\n this._jitTranspiler = opts.jitTranspiler;\n }\n\n get pre(): string {\n return this._indentController.pre;\n }\n\n indent(): string {\n return this._indentController.indent();\n }\n\n dedent(): string {\n return this._indentController.dedent();\n }\n\n getById(id: string): Resource | null {\n const item = this._itemStateStack.getResourceById(id);\n\n if (item === undefined) {\n return null;\n }\n\n return item;\n }\n\n defineVariable(id: string, dataType: AnyWgslData | UnknownData): Resource {\n return this._itemStateStack.defineBlockVariable(id, dataType);\n }\n\n pushBlockScope() {\n this._itemStateStack.pushBlockScope();\n }\n\n popBlockScope() {\n this._itemStateStack.popBlockScope();\n }\n\n transpileFn(fn: string): {\n argNames: string[];\n body: Block;\n externalNames: string[];\n } {\n if (!this._jitTranspiler) {\n throw new Error(\n 'Tried to execute a tgpu.fn function without providing a JIT transpiler, or transpiling at build time.',\n );\n }\n\n return this._jitTranspiler.transpileFn(fn);\n }\n\n fnToWgsl(options: FnToWgslOptions): { head: Wgsl; body: Wgsl } {\n this._itemStateStack.pushFunctionScope(\n options.args,\n options.returnType,\n options.externalMap,\n );\n\n try {\n const str = generateFunction(this, options.body);\n\n const argList = options.args\n .map(\n (arg) => `${arg.value}: ${this.resolve(arg.dataType as AnyWgslData)}`,\n )\n .join(', ');\n\n return {\n head:\n options.returnType !== undefined\n ? `(${argList}) -> ${getAttributesString(options.returnType)} ${this.resolve(options.returnType)}`\n : `(${argList})`,\n body: str,\n };\n } finally {\n this._itemStateStack.popFunctionScope();\n }\n }\n\n addDeclaration(declaration: string): void {\n this._declarations.push(declaration);\n }\n\n allocateLayoutEntry(layout: TgpuBindGroupLayout): string {\n const memoMap = this.bindGroupLayoutsToPlaceholderMap;\n let placeholderKey = memoMap.get(layout);\n\n if (!placeholderKey) {\n placeholderKey = `#BIND_GROUP_LAYOUT_${this._nextFreeLayoutPlaceholderIdx++}#`;\n memoMap.set(layout, placeholderKey);\n }\n\n return placeholderKey;\n }\n\n allocateFixedEntry(\n layoutEntry: TgpuLayoutEntry,\n resource: object,\n ): { group: string; binding: number } {\n const binding = this.fixedBindings.length;\n this.fixedBindings.push({ layoutEntry, resource });\n\n return {\n group: CATCHALL_BIND_GROUP_IDX_MARKER,\n binding,\n };\n }\n\n readSlot<T>(slot: TgpuSlot<T>): T {\n const value = this._itemStateStack.readSlot(slot);\n\n if (value === undefined) {\n throw new MissingSlotValueError(slot);\n }\n\n return value;\n }\n\n withSlots<T>(pairs: SlotValuePair<unknown>[], callback: () => T): T {\n this._itemStateStack.pushSlotBindings(pairs);\n\n try {\n return callback();\n } finally {\n this._itemStateStack.popSlotBindings();\n }\n }\n\n unwrap<T>(eventual: Eventual<T>): T {\n if (isProviding(eventual)) {\n return this.withSlots(\n eventual['~providing'].pairs,\n () => this.unwrap(eventual['~providing'].inner) as T,\n );\n }\n\n let maybeEventual = eventual;\n\n // Unwrapping all layers of slots.\n while (true) {\n if (isSlot(maybeEventual)) {\n maybeEventual = this.readSlot(maybeEventual);\n } else if (isDerived(maybeEventual)) {\n maybeEventual = this._getOrCompute(maybeEventual);\n } else {\n break;\n }\n }\n\n return maybeEventual;\n }\n\n _getOrCompute<T>(derived: TgpuDerived<T>): T {\n // All memoized versions of `derived`\n const instances = this._memoizedDerived.get(derived) ?? [];\n\n this._itemStateStack.pushItem();\n\n try {\n for (const instance of instances) {\n const slotValuePairs = [...instance.slotToValueMap.entries()];\n\n if (\n slotValuePairs.every(([slot, expectedValue]) =>\n slot.areEqual(this._itemStateStack.readSlot(slot), expectedValue),\n )\n ) {\n return instance.result as T;\n }\n }\n\n // If we got here, no item with the given slot-to-value combo exists in cache yet\n // Derived computations are always done on the CPU\n pushMode(RuntimeMode.CPU);\n\n let result: T;\n try {\n result = derived['~compute']();\n } finally {\n popMode(RuntimeMode.CPU);\n }\n\n // We know which slots the item used while resolving\n const slotToValueMap = new Map<TgpuSlot<unknown>, unknown>();\n for (const usedSlot of this._itemStateStack.topItem.usedSlots) {\n slotToValueMap.set(usedSlot, this._itemStateStack.readSlot(usedSlot));\n }\n\n instances.push({ slotToValueMap, result });\n this._memoizedDerived.set(derived, instances);\n return result;\n } catch (err) {\n if (err instanceof ResolutionError) {\n throw err.appendToTrace(derived);\n }\n\n throw new ResolutionError(err, [derived]);\n } finally {\n this._itemStateStack.popItem();\n }\n }\n\n /**\n * @param item The item whose resolution should be either retrieved from the cache (if there is a cache hit), or resolved.\n */\n _getOrInstantiate(item: object): string {\n // All memoized versions of `item`\n const instances = this._memoizedResolves.get(item) ?? [];\n\n this._itemStateStack.pushItem();\n\n try {\n for (const instance of instances) {\n const slotValuePairs = [...instance.slotToValueMap.entries()];\n\n if (\n slotValuePairs.every(([slot, expectedValue]) =>\n slot.areEqual(this._itemStateStack.readSlot(slot), expectedValue),\n )\n ) {\n return instance.result;\n }\n }\n\n // If we got here, no item with the given slot-to-value combo exists in cache yet\n let result: string;\n if (isData(item)) {\n result = resolveData(this, item);\n } else if (isDerived(item) || isSlot(item)) {\n result = this.resolve(this.unwrap(item));\n } else if (isSelfResolvable(item)) {\n result = item['~resolve'](this);\n } else {\n result = this.resolveValue(item);\n }\n\n // We know which slots the item used while resolving\n const slotToValueMap = new Map<TgpuSlot<unknown>, unknown>();\n for (const usedSlot of this._itemStateStack.topItem.usedSlots) {\n slotToValueMap.set(usedSlot, this._itemStateStack.readSlot(usedSlot));\n }\n\n instances.push({ slotToValueMap, result });\n this._memoizedResolves.set(item, instances);\n\n return result;\n } catch (err) {\n if (err instanceof ResolutionError) {\n throw err.appendToTrace(item);\n }\n\n throw new ResolutionError(err, [item]);\n } finally {\n this._itemStateStack.popItem();\n }\n }\n\n resolve(item: unknown): string {\n if (isProviding(item)) {\n return this.withSlots(item['~providing'].pairs, () =>\n this.resolve(item['~providing'].inner),\n );\n }\n\n if ((item && typeof item === 'object') || typeof item === 'function') {\n if (this._itemStateStack.itemDepth === 0) {\n try {\n pushMode(RuntimeMode.GPU);\n const result = provideCtx(this, () => this._getOrInstantiate(item));\n return `${[...this._declarations].join('\\n\\n')}${result}`;\n } finally {\n popMode(RuntimeMode.GPU);\n }\n }\n\n return this._getOrInstantiate(item);\n }\n\n return String(item);\n }\n\n resolveValue<T extends BaseData>(\n value: Infer<T>,\n schema?: T | undefined,\n ): string {\n if (isWgsl(value)) {\n return this.resolve(value);\n }\n\n if (schema && isWgslArray(schema)) {\n return `array(${(value as unknown[]).map((element) => this.resolveValue(element, schema.elementType))})`;\n }\n\n if (Array.isArray(value)) {\n return `array(${value.map((element) => this.resolveValue(element))})`;\n }\n\n if (schema && isWgslStruct(schema)) {\n return `${this.resolve(schema)}(${Object.entries(schema.propTypes).map(([key, type_]) => this.resolveValue((value as Infer<typeof schema>)[key], type_))})`;\n }\n\n throw new Error(\n `Value ${value} (as json: ${JSON.stringify(value)}) of schema ${schema} is not resolvable to WGSL`,\n );\n }\n}\n\nexport interface ResolutionResult {\n code: string;\n bindGroupLayouts: TgpuBindGroupLayout[];\n catchall: [number, TgpuBindGroup] | null;\n}\n\nexport function resolve(\n item: Wgsl,\n options: ResolutionCtxImplOptions,\n): ResolutionResult {\n const ctx = new ResolutionCtxImpl(options);\n let code = ctx.resolve(item);\n\n const memoMap = ctx.bindGroupLayoutsToPlaceholderMap;\n const bindGroupLayouts: TgpuBindGroupLayout[] = [];\n const takenIndices = new Set<number>(\n [...memoMap.keys()]\n .map((layout) => layout.index)\n .filter((v): v is number => v !== undefined),\n );\n\n const automaticIds = naturalsExcept(takenIndices);\n\n const layoutEntries = ctx.fixedBindings.map(\n (binding, idx) =>\n [String(idx), binding.layoutEntry] as [string, TgpuLayoutEntry],\n );\n\n const createCatchallGroup = () => {\n const catchallIdx = automaticIds.next().value;\n const catchallLayout = bindGroupLayout(Object.fromEntries(layoutEntries));\n bindGroupLayouts[catchallIdx] = catchallLayout;\n code = code.replaceAll(CATCHALL_BIND_GROUP_IDX_MARKER, String(catchallIdx));\n\n return [\n catchallIdx,\n new TgpuBindGroupImpl(\n catchallLayout,\n Object.fromEntries(\n ctx.fixedBindings.map(\n (binding, idx) =>\n // biome-ignore lint/suspicious/noExplicitAny: <it's fine>\n [String(idx), binding.resource] as [string, any],\n ),\n ),\n ),\n ] as [number, TgpuBindGroup];\n };\n\n // Retrieving the catch-all binding index first, because it's inherently\n // the least swapped bind group (fixed and cannot be swapped).\n const catchall = layoutEntries.length > 0 ? createCatchallGroup() : null;\n\n for (const [layout, placeholder] of memoMap.entries()) {\n const idx = layout.index ?? automaticIds.next().value;\n bindGroupLayouts[idx] = layout;\n code = code.replaceAll(placeholder, String(idx));\n }\n\n return {\n code,\n bindGroupLayouts,\n catchall,\n };\n}\n","import { MissingBindGroupsError } from '../../errors';\nimport type { TgpuNamable } from '../../namable';\nimport { resolve } from '../../resolutionCtx';\nimport type {\n TgpuBindGroup,\n TgpuBindGroupLayout,\n} from '../../tgpuBindGroupLayout';\nimport type { TgpuComputeFn } from '../function/tgpuComputeFn';\nimport type { ExperimentalTgpuRoot } from '../root/rootTypes';\nimport type { TgpuSlot } from '../slot/slotTypes';\n\n// ----------\n// Public API\n// ----------\n\nexport interface TgpuComputePipeline extends TgpuNamable {\n readonly resourceType: 'compute-pipeline';\n readonly label: string | undefined;\n\n with(\n bindGroupLayout: TgpuBindGroupLayout,\n bindGroup: TgpuBindGroup,\n ): TgpuComputePipeline;\n\n dispatchWorkgroups(\n x: number,\n y?: number | undefined,\n z?: number | undefined,\n ): void;\n}\n\nexport interface INTERNAL_TgpuComputePipeline {\n readonly rawPipeline: GPUComputePipeline;\n}\n\nexport function INTERNAL_createComputePipeline(\n branch: ExperimentalTgpuRoot,\n slotBindings: [TgpuSlot<unknown>, unknown][],\n entryFn: TgpuComputeFn,\n) {\n return new TgpuComputePipelineImpl(\n new ComputePipelineCore(branch, slotBindings, entryFn),\n {},\n );\n}\n\nexport function isComputePipeline(\n value: unknown,\n): value is TgpuComputePipeline {\n return (value as TgpuComputePipeline)?.resourceType === 'compute-pipeline';\n}\n\n// --------------\n// Implementation\n// --------------\n\ntype TgpuComputePipelinePriors = {\n readonly bindGroupLayoutMap?: Map<TgpuBindGroupLayout, TgpuBindGroup>;\n};\n\ntype Memo = {\n pipeline: GPUComputePipeline;\n bindGroupLayouts: TgpuBindGroupLayout[];\n catchall: [number, TgpuBindGroup] | null;\n};\n\nclass TgpuComputePipelineImpl\n implements TgpuComputePipeline, INTERNAL_TgpuComputePipeline\n{\n public readonly resourceType = 'compute-pipeline';\n\n constructor(\n private readonly _core: ComputePipelineCore,\n private readonly _priors: TgpuComputePipelinePriors,\n ) {}\n\n get label(): string | undefined {\n return this._core.label;\n }\n\n get rawPipeline(): GPUComputePipeline {\n return this._core.unwrap().pipeline;\n }\n\n with(\n bindGroupLayout: TgpuBindGroupLayout,\n bindGroup: TgpuBindGroup,\n ): TgpuComputePipeline {\n return new TgpuComputePipelineImpl(this._core, {\n bindGroupLayoutMap: new Map([\n ...(this._priors.bindGroupLayoutMap ?? []),\n [bindGroupLayout, bindGroup],\n ]),\n });\n }\n\n dispatchWorkgroups(\n x: number,\n y?: number | undefined,\n z?: number | undefined,\n ): void {\n const memo = this._core.unwrap();\n const { branch, label } = this._core;\n\n const pass = branch.commandEncoder.beginComputePass({\n label: label ?? '<unnamed>',\n });\n\n pass.setPipeline(memo.pipeline);\n\n const missingBindGroups = new Set(memo.bindGroupLayouts);\n\n memo.bindGroupLayouts.forEach((layout, idx) => {\n if (memo.catchall && idx === memo.catchall[0]) {\n // Catch-all\n pass.setBindGroup(idx, branch.unwrap(memo.catchall[1]));\n missingBindGroups.delete(layout);\n } else {\n const bindGroup = this._priors.bindGroupLayoutMap?.get(layout);\n if (bindGroup !== undefined) {\n missingBindGroups.delete(layout);\n pass.setBindGroup(idx, branch.unwrap(bindGroup));\n }\n }\n });\n\n if (missingBindGroups.size > 0) {\n throw new MissingBindGroupsError(missingBindGroups);\n }\n\n pass.dispatchWorkgroups(x, y, z);\n pass.end();\n }\n\n $name(label?: string | undefined): this {\n this._core.label = label;\n return this;\n }\n}\n\nclass ComputePipelineCore {\n public label: string | undefined;\n private _memo: Memo | undefined;\n\n constructor(\n public readonly branch: ExperimentalTgpuRoot,\n private readonly _slotBindings: [TgpuSlot<unknown>, unknown][],\n private readonly _entryFn: TgpuComputeFn,\n ) {}\n\n public unwrap(): Memo {\n if (this._memo === undefined) {\n const device = this.branch.device;\n\n // Resolving code\n const { code, bindGroupLayouts, catchall } = resolve(\n {\n '~resolve': (ctx) => {\n ctx.withSlots(this._slotBindings, () => {\n ctx.resolve(this._entryFn);\n });\n return '';\n },\n\n toString: () => `computePipeline:${this.label ?? '<unnamed>'}`,\n },\n {\n names: this.branch.nameRegistry,\n jitTranspiler: this.branch.jitTranspiler,\n },\n );\n\n if (catchall !== null) {\n bindGroupLayouts[catchall[0]]?.$name(\n `${this.label ?? '<unnamed>'} - Automatic Bind Group & Layout`,\n );\n }\n\n this._memo = {\n pipeline: device.createComputePipeline({\n label: this.label ?? '<unnamed>',\n layout: device.createPipelineLayout({\n label: `${this.label ?? '<unnamed>'} - Pipeline Layout`,\n bindGroupLayouts: bindGroupLayouts.map((l) =>\n this.branch.unwrap(l),\n ),\n }),\n compute: {\n module: device.createShaderModule({\n label: `${this.label ?? '<unnamed>'} - Shader`,\n code,\n }),\n },\n }),\n bindGroupLayouts,\n catchall,\n };\n }\n\n return this._memo;\n }\n}\n"]}
package/index.d.cts CHANGED
@@ -3,9 +3,13 @@ import { A as AnyAttribute, a as AnyComputeBuiltin, D as Decorate, I as IsBuilti
3
3
  import * as smol from 'tinyest';
4
4
  import { Block } from 'tinyest';
5
5
 
6
+ declare const $internal: unique symbol;
7
+
6
8
  interface TgpuConst<TDataType extends AnyWgslData = AnyWgslData> extends TgpuNamable {
7
- readonly dataType: TDataType;
8
9
  readonly value: Infer<TDataType>;
10
+ readonly [$internal]: {
11
+ readonly dataType: TDataType;
12
+ };
9
13
  }
10
14
  /**
11
15
  * Creates a module constant with specified value.
@@ -44,6 +48,7 @@ type InferArgs<T extends unknown[]> = {
44
48
  [Idx in keyof T]: Infer<T[Idx]>;
45
49
  };
46
50
  type InferReturn<T> = T extends undefined ? void : Infer<T>;
51
+ type Implementation<Args extends unknown[] = unknown[], Return = unknown> = string | ((...args: Args) => Return);
47
52
  type BaseIOData = F32 | F16 | I32 | U32 | Vec2f | Vec3f | Vec4f | Vec2h | Vec3h | Vec4h | Vec2i | Vec3i | Vec4i | Vec2u | Vec3u | Vec4u;
48
53
  type IOData = BaseIOData | Decorated<BaseIOData, AnyAttribute[]>;
49
54
  type IORecord<TElementType extends IOData = IOData> = Record<string, TElementType>;
@@ -1044,6 +1049,7 @@ interface TgpuBuffer<TData extends BaseData> extends TgpuNamable {
1044
1049
  $usage<T extends RestrictVertexUsages<TData>>(...usages: T): this & UnionToIntersection<LiteralToUsageType<T[number]>>;
1045
1050
  $addFlags(flags: GPUBufferUsageFlags): this;
1046
1051
  as<T extends ViewUsages<this>>(usage: T): UsageTypeToBufferUsage<TData>[T];
1052
+ compileWriter(): void;
1047
1053
  write(data: Infer<TData>): void;
1048
1054
  writePartial(data: InferPartial<TData>): void;
1049
1055
  copyFrom(srcBuffer: TgpuBuffer<MemIdentity<TData>>): void;
@@ -1170,7 +1176,7 @@ type GetStorageTextureRestriction<T extends TgpuLayoutStorageTexture> = Default<
1170
1176
  format: T['storageTexture'];
1171
1177
  dimension: Dimension;
1172
1178
  } : never;
1173
- type LayoutEntryToInput<T extends TgpuLayoutEntry | null> = T extends TgpuLayoutUniform ? (TgpuBuffer<UnwrapRuntimeConstructor<T['uniform']>> & UniformFlag) | GPUBuffer : T extends TgpuLayoutStorage ? (TgpuBuffer<UnwrapRuntimeConstructor<T['storage']>> & StorageFlag) | GPUBuffer : T extends TgpuLayoutSampler ? TgpuSampler | GPUSampler : T extends TgpuLayoutComparisonSampler ? TgpuComparisonSampler | GPUSampler : T extends TgpuLayoutTexture ? GPUTextureView | (Sampled & TgpuTexture<Prettify<TextureProps & GetTextureRestriction<T>>>) : T extends TgpuLayoutStorageTexture ? GPUTextureView | (StorageFlag & TgpuTexture<Prettify<TextureProps & GetStorageTextureRestriction<T>>>) : T extends TgpuLayoutExternalTexture ? GPUExternalTexture : never;
1179
+ type LayoutEntryToInput<T extends TgpuLayoutEntry | null> = T extends TgpuLayoutUniform ? (TgpuBuffer<UnwrapRuntimeConstructor<T['uniform']>> & UniformFlag) | GPUBuffer : T extends TgpuLayoutStorage ? (TgpuBuffer<UnwrapRuntimeConstructor<T['storage']>> & StorageFlag) | GPUBuffer : T extends TgpuLayoutSampler ? TgpuSampler | GPUSampler : T extends TgpuLayoutComparisonSampler ? TgpuComparisonSampler | GPUSampler : T extends TgpuLayoutTexture ? GPUTextureView | (Sampled & TgpuTexture<Prettify<TextureProps & GetTextureRestriction<T>>>) | TgpuSampledTexture<Default<T['viewDimension'], '2d'>, ChannelFormatToSchema[T['texture']]> : T extends TgpuLayoutStorageTexture ? GPUTextureView | (StorageFlag & TgpuTexture<Prettify<TextureProps & GetStorageTextureRestriction<T>>>) | StorageTextureUsageForEntry<T> : T extends TgpuLayoutExternalTexture ? GPUExternalTexture : never;
1174
1180
  type BindLayoutEntry<T extends TgpuLayoutEntry | null> = T extends TgpuLayoutUniform ? TgpuBufferUniform<UnwrapRuntimeConstructor<T['uniform']>> : T extends TgpuLayoutStorage ? StorageUsageForEntry<T> : T extends TgpuLayoutSampler ? TgpuSampler : T extends TgpuLayoutComparisonSampler ? TgpuComparisonSampler : T extends TgpuLayoutTexture ? TgpuSampledTexture<Default<GetDimension<T['viewDimension']>, '2d'>, ChannelFormatToSchema[T['texture']]> : T extends TgpuLayoutStorageTexture ? StorageTextureUsageForEntry<T> : never;
1175
1181
  type TgpuBindGroup<Entries extends Record<string, TgpuLayoutEntry | null> = Record<string, TgpuLayoutEntry | null>> = {
1176
1182
  readonly resourceType: 'bind-group';
@@ -1184,6 +1190,9 @@ interface TgpuBufferUsage<TData extends BaseData = BaseData, TUsage extends Bind
1184
1190
  readonly usage: TUsage;
1185
1191
  readonly '~repr': Infer<TData>;
1186
1192
  value: InferGPU<TData>;
1193
+ readonly [$internal]: {
1194
+ readonly dataType: TData;
1195
+ };
1187
1196
  }
1188
1197
  interface TgpuBufferUniform<TData extends BaseData> extends TgpuBufferUsage<TData, 'uniform'> {
1189
1198
  readonly value: InferGPU<TData>;
@@ -1241,9 +1250,13 @@ declare const UnknownData: {
1241
1250
  type: string;
1242
1251
  };
1243
1252
  type UnknownData = typeof UnknownData;
1253
+ declare const Void: {
1254
+ type: "void";
1255
+ };
1256
+ type Void = typeof Void;
1244
1257
  type Resource = {
1245
1258
  value: unknown;
1246
- dataType: AnyWgslData | UnknownData | AbstractInt | AbstractFloat;
1259
+ dataType: AnyWgslData | UnknownData | AbstractInt | AbstractFloat | Void;
1247
1260
  };
1248
1261
  type TgpuShaderStage = 'compute' | 'vertex' | 'fragment';
1249
1262
  interface FnToWgslOptions {
@@ -1252,6 +1265,26 @@ interface FnToWgslOptions {
1252
1265
  body: Block;
1253
1266
  externalMap: Record<string, unknown>;
1254
1267
  }
1268
+ type ItemLayer = {
1269
+ type: 'item';
1270
+ usedSlots: Set<TgpuSlot<unknown>>;
1271
+ };
1272
+ interface ItemStateStack {
1273
+ readonly itemDepth: number;
1274
+ readonly topItem: ItemLayer;
1275
+ pushItem(): void;
1276
+ popItem(): void;
1277
+ pushSlotBindings(pairs: SlotValuePair<unknown>[]): void;
1278
+ popSlotBindings(): void;
1279
+ pushFunctionScope(args: Resource[], returnType: AnyWgslData | undefined, externalMap: Record<string, unknown>): void;
1280
+ popFunctionScope(): void;
1281
+ pushBlockScope(): void;
1282
+ popBlockScope(): void;
1283
+ pop(type?: 'functionScope' | 'blockScope' | 'slotBinding' | 'item'): void;
1284
+ readSlot<T>(slot: TgpuSlot<T>): T | undefined;
1285
+ getResourceById(id: string): Resource | undefined;
1286
+ defineBlockVariable(id: string, type: AnyWgslData | UnknownData): Resource;
1287
+ }
1255
1288
  /**
1256
1289
  * Passed into each resolvable item. All items in a tree share a resolution ctx,
1257
1290
  * but there can be layers added and removed from the item stack when going down
@@ -1290,6 +1323,9 @@ interface ResolutionCtx {
1290
1323
  head: Wgsl;
1291
1324
  body: Wgsl;
1292
1325
  };
1326
+ [$internal]: {
1327
+ itemStateStack: ItemStateStack;
1328
+ };
1293
1329
  }
1294
1330
  /**
1295
1331
  * Houses a method '~resolve` that returns a code string
@@ -1331,7 +1367,11 @@ interface TgpuFnBase<Args extends AnyWgslData[], Return extends AnyWgslData | un
1331
1367
  with<T>(slot: TgpuSlot<T>, value: Eventual<T>): TgpuFn<Args, Return>;
1332
1368
  with<T extends AnyWgslData>(accessor: TgpuAccessor<T>, value: TgpuFn<[], T> | TgpuBufferUsage<T> | Infer<T>): TgpuFn<Args, Return>;
1333
1369
  }
1334
- type TgpuFn<Args extends AnyWgslData[] = AnyWgslData[], Return extends AnyWgslData | undefined = AnyWgslData | undefined> = TgpuFnBase<Args, Return> & ((...args: InferArgs<Args>) => InferReturn<Return>);
1370
+ type TgpuFn<Args extends AnyWgslData[] = AnyWgslData[], Return extends AnyWgslData | undefined = AnyWgslData | undefined> = TgpuFnBase<Args, Return> & ((...args: InferArgs<Args>) => InferReturn<Return>) & {
1371
+ readonly [$internal]: {
1372
+ implementation: Implementation<InferArgs<Args>, InferReturn<Return>>;
1373
+ };
1374
+ };
1335
1375
  declare function fn<Args extends AnyWgslData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, undefined>;
1336
1376
  declare function fn<Args extends AnyWgslData[] | [], Return extends AnyWgslData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
1337
1377
  declare function isTgpuFn<Args extends AnyWgslData[], Return extends AnyWgslData | undefined = undefined>(value: unknown | TgpuFn<Args, Return>): value is TgpuFn<Args, Return>;
package/index.d.ts CHANGED
@@ -3,9 +3,13 @@ import { A as AnyAttribute, a as AnyComputeBuiltin, D as Decorate, I as IsBuilti
3
3
  import * as smol from 'tinyest';
4
4
  import { Block } from 'tinyest';
5
5
 
6
+ declare const $internal: unique symbol;
7
+
6
8
  interface TgpuConst<TDataType extends AnyWgslData = AnyWgslData> extends TgpuNamable {
7
- readonly dataType: TDataType;
8
9
  readonly value: Infer<TDataType>;
10
+ readonly [$internal]: {
11
+ readonly dataType: TDataType;
12
+ };
9
13
  }
10
14
  /**
11
15
  * Creates a module constant with specified value.
@@ -44,6 +48,7 @@ type InferArgs<T extends unknown[]> = {
44
48
  [Idx in keyof T]: Infer<T[Idx]>;
45
49
  };
46
50
  type InferReturn<T> = T extends undefined ? void : Infer<T>;
51
+ type Implementation<Args extends unknown[] = unknown[], Return = unknown> = string | ((...args: Args) => Return);
47
52
  type BaseIOData = F32 | F16 | I32 | U32 | Vec2f | Vec3f | Vec4f | Vec2h | Vec3h | Vec4h | Vec2i | Vec3i | Vec4i | Vec2u | Vec3u | Vec4u;
48
53
  type IOData = BaseIOData | Decorated<BaseIOData, AnyAttribute[]>;
49
54
  type IORecord<TElementType extends IOData = IOData> = Record<string, TElementType>;
@@ -1044,6 +1049,7 @@ interface TgpuBuffer<TData extends BaseData> extends TgpuNamable {
1044
1049
  $usage<T extends RestrictVertexUsages<TData>>(...usages: T): this & UnionToIntersection<LiteralToUsageType<T[number]>>;
1045
1050
  $addFlags(flags: GPUBufferUsageFlags): this;
1046
1051
  as<T extends ViewUsages<this>>(usage: T): UsageTypeToBufferUsage<TData>[T];
1052
+ compileWriter(): void;
1047
1053
  write(data: Infer<TData>): void;
1048
1054
  writePartial(data: InferPartial<TData>): void;
1049
1055
  copyFrom(srcBuffer: TgpuBuffer<MemIdentity<TData>>): void;
@@ -1170,7 +1176,7 @@ type GetStorageTextureRestriction<T extends TgpuLayoutStorageTexture> = Default<
1170
1176
  format: T['storageTexture'];
1171
1177
  dimension: Dimension;
1172
1178
  } : never;
1173
- type LayoutEntryToInput<T extends TgpuLayoutEntry | null> = T extends TgpuLayoutUniform ? (TgpuBuffer<UnwrapRuntimeConstructor<T['uniform']>> & UniformFlag) | GPUBuffer : T extends TgpuLayoutStorage ? (TgpuBuffer<UnwrapRuntimeConstructor<T['storage']>> & StorageFlag) | GPUBuffer : T extends TgpuLayoutSampler ? TgpuSampler | GPUSampler : T extends TgpuLayoutComparisonSampler ? TgpuComparisonSampler | GPUSampler : T extends TgpuLayoutTexture ? GPUTextureView | (Sampled & TgpuTexture<Prettify<TextureProps & GetTextureRestriction<T>>>) : T extends TgpuLayoutStorageTexture ? GPUTextureView | (StorageFlag & TgpuTexture<Prettify<TextureProps & GetStorageTextureRestriction<T>>>) : T extends TgpuLayoutExternalTexture ? GPUExternalTexture : never;
1179
+ type LayoutEntryToInput<T extends TgpuLayoutEntry | null> = T extends TgpuLayoutUniform ? (TgpuBuffer<UnwrapRuntimeConstructor<T['uniform']>> & UniformFlag) | GPUBuffer : T extends TgpuLayoutStorage ? (TgpuBuffer<UnwrapRuntimeConstructor<T['storage']>> & StorageFlag) | GPUBuffer : T extends TgpuLayoutSampler ? TgpuSampler | GPUSampler : T extends TgpuLayoutComparisonSampler ? TgpuComparisonSampler | GPUSampler : T extends TgpuLayoutTexture ? GPUTextureView | (Sampled & TgpuTexture<Prettify<TextureProps & GetTextureRestriction<T>>>) | TgpuSampledTexture<Default<T['viewDimension'], '2d'>, ChannelFormatToSchema[T['texture']]> : T extends TgpuLayoutStorageTexture ? GPUTextureView | (StorageFlag & TgpuTexture<Prettify<TextureProps & GetStorageTextureRestriction<T>>>) | StorageTextureUsageForEntry<T> : T extends TgpuLayoutExternalTexture ? GPUExternalTexture : never;
1174
1180
  type BindLayoutEntry<T extends TgpuLayoutEntry | null> = T extends TgpuLayoutUniform ? TgpuBufferUniform<UnwrapRuntimeConstructor<T['uniform']>> : T extends TgpuLayoutStorage ? StorageUsageForEntry<T> : T extends TgpuLayoutSampler ? TgpuSampler : T extends TgpuLayoutComparisonSampler ? TgpuComparisonSampler : T extends TgpuLayoutTexture ? TgpuSampledTexture<Default<GetDimension<T['viewDimension']>, '2d'>, ChannelFormatToSchema[T['texture']]> : T extends TgpuLayoutStorageTexture ? StorageTextureUsageForEntry<T> : never;
1175
1181
  type TgpuBindGroup<Entries extends Record<string, TgpuLayoutEntry | null> = Record<string, TgpuLayoutEntry | null>> = {
1176
1182
  readonly resourceType: 'bind-group';
@@ -1184,6 +1190,9 @@ interface TgpuBufferUsage<TData extends BaseData = BaseData, TUsage extends Bind
1184
1190
  readonly usage: TUsage;
1185
1191
  readonly '~repr': Infer<TData>;
1186
1192
  value: InferGPU<TData>;
1193
+ readonly [$internal]: {
1194
+ readonly dataType: TData;
1195
+ };
1187
1196
  }
1188
1197
  interface TgpuBufferUniform<TData extends BaseData> extends TgpuBufferUsage<TData, 'uniform'> {
1189
1198
  readonly value: InferGPU<TData>;
@@ -1241,9 +1250,13 @@ declare const UnknownData: {
1241
1250
  type: string;
1242
1251
  };
1243
1252
  type UnknownData = typeof UnknownData;
1253
+ declare const Void: {
1254
+ type: "void";
1255
+ };
1256
+ type Void = typeof Void;
1244
1257
  type Resource = {
1245
1258
  value: unknown;
1246
- dataType: AnyWgslData | UnknownData | AbstractInt | AbstractFloat;
1259
+ dataType: AnyWgslData | UnknownData | AbstractInt | AbstractFloat | Void;
1247
1260
  };
1248
1261
  type TgpuShaderStage = 'compute' | 'vertex' | 'fragment';
1249
1262
  interface FnToWgslOptions {
@@ -1252,6 +1265,26 @@ interface FnToWgslOptions {
1252
1265
  body: Block;
1253
1266
  externalMap: Record<string, unknown>;
1254
1267
  }
1268
+ type ItemLayer = {
1269
+ type: 'item';
1270
+ usedSlots: Set<TgpuSlot<unknown>>;
1271
+ };
1272
+ interface ItemStateStack {
1273
+ readonly itemDepth: number;
1274
+ readonly topItem: ItemLayer;
1275
+ pushItem(): void;
1276
+ popItem(): void;
1277
+ pushSlotBindings(pairs: SlotValuePair<unknown>[]): void;
1278
+ popSlotBindings(): void;
1279
+ pushFunctionScope(args: Resource[], returnType: AnyWgslData | undefined, externalMap: Record<string, unknown>): void;
1280
+ popFunctionScope(): void;
1281
+ pushBlockScope(): void;
1282
+ popBlockScope(): void;
1283
+ pop(type?: 'functionScope' | 'blockScope' | 'slotBinding' | 'item'): void;
1284
+ readSlot<T>(slot: TgpuSlot<T>): T | undefined;
1285
+ getResourceById(id: string): Resource | undefined;
1286
+ defineBlockVariable(id: string, type: AnyWgslData | UnknownData): Resource;
1287
+ }
1255
1288
  /**
1256
1289
  * Passed into each resolvable item. All items in a tree share a resolution ctx,
1257
1290
  * but there can be layers added and removed from the item stack when going down
@@ -1290,6 +1323,9 @@ interface ResolutionCtx {
1290
1323
  head: Wgsl;
1291
1324
  body: Wgsl;
1292
1325
  };
1326
+ [$internal]: {
1327
+ itemStateStack: ItemStateStack;
1328
+ };
1293
1329
  }
1294
1330
  /**
1295
1331
  * Houses a method '~resolve` that returns a code string
@@ -1331,7 +1367,11 @@ interface TgpuFnBase<Args extends AnyWgslData[], Return extends AnyWgslData | un
1331
1367
  with<T>(slot: TgpuSlot<T>, value: Eventual<T>): TgpuFn<Args, Return>;
1332
1368
  with<T extends AnyWgslData>(accessor: TgpuAccessor<T>, value: TgpuFn<[], T> | TgpuBufferUsage<T> | Infer<T>): TgpuFn<Args, Return>;
1333
1369
  }
1334
- type TgpuFn<Args extends AnyWgslData[] = AnyWgslData[], Return extends AnyWgslData | undefined = AnyWgslData | undefined> = TgpuFnBase<Args, Return> & ((...args: InferArgs<Args>) => InferReturn<Return>);
1370
+ type TgpuFn<Args extends AnyWgslData[] = AnyWgslData[], Return extends AnyWgslData | undefined = AnyWgslData | undefined> = TgpuFnBase<Args, Return> & ((...args: InferArgs<Args>) => InferReturn<Return>) & {
1371
+ readonly [$internal]: {
1372
+ implementation: Implementation<InferArgs<Args>, InferReturn<Return>>;
1373
+ };
1374
+ };
1335
1375
  declare function fn<Args extends AnyWgslData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, undefined>;
1336
1376
  declare function fn<Args extends AnyWgslData[] | [], Return extends AnyWgslData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
1337
1377
  declare function isTgpuFn<Args extends AnyWgslData[], Return extends AnyWgslData | undefined = undefined>(value: unknown | TgpuFn<Args, Return>): value is TgpuFn<Args, Return>;