UNPKG

typegpu

Version:

A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.

1 lines 93.1 kB
{"version":3,"sources":["/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","../src/extractGpuValueGetter.ts","../src/shared/utilityTypes.ts","../src/tgsl/generationHelpers.ts","../src/core/valueProxyUtils.ts","../src/core/resolve/resolveData.ts","../src/data/compiledIO.ts","../src/data/dataIO.ts","../src/tgsl/wgslGenerator.ts","../src/resolutionCtx.ts"],"names":["extractGpuValueGetter","object","$gpuValueOf","assertExhaustive","x","location"],"mappings":"AAAA,y3CAAgH,wDAA0C,wDAAgpB,SCK1xBA,EAAAA,CACdC,CAAAA,CAC4B,CAE5B,EAAA,CAAI,uBAAQA,CAAAA,4BAAAA,CAAiBC,mBAAW,GAAA,EAAM,UAAA,CAC5C,OAAQD,CAAAA,CAA6CC,mBAAW,CAAA,CAAE,IAAA,CAChED,CACF,CAGJ,CC4BO,SAASE,EAAAA,CAAiBC,CAAAA,CAAUC,CAAAA,CAAyB,CAClE,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoBD,CAAC,CAAA,IAAA,EAAOC,CAAQ,CAAA,CAAA;AC+e1C;AAEN;AAAoC,KAAA;ACzgBtC,4CAAA;AC4GiB;AACV,OAAA;AAKT;AAwBmB;AACV,OAAA;AAYT;AAAA;AAiGkB;ACzKR;AAeN;AAsBE;AASsB;ACvBhB;AC5B0D;AAAK;AAoGzC;AA8VxB;AACH;AAPgB;AAwFqC;AA4B/B;AClfI;AAubc;AAsG7C","file":"/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","sourcesContent":[null,"import { $gpuValueOf } from './shared/symbols.ts';\nimport type { ResolutionCtx } from './types.ts';\n\nexport type GpuValueGetter = (ctx: ResolutionCtx) => unknown;\n\nexport function extractGpuValueGetter(\n object: unknown,\n): GpuValueGetter | undefined {\n // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value\n if (typeof (object as any)?.[$gpuValueOf] === 'function') {\n return (object as { [$gpuValueOf]: GpuValueGetter })[$gpuValueOf].bind(\n object,\n );\n }\n return undefined;\n}\n","export type Default<T, TDefault> = unknown extends T ? TDefault\n : T extends undefined ? TDefault\n : T;\n\nexport type UnionToIntersection<U> =\n // biome-ignore lint/suspicious/noExplicitAny: <had to be done>\n (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I\n : never;\n\nexport type Prettify<T> =\n & {\n [K in keyof T]: T[K];\n }\n & {};\n\n/**\n * Removes properties from record type that extend `Prop`\n */\nexport type OmitProps<T extends Record<string, unknown>, Prop> = Pick<\n T,\n {\n [Key in keyof T]: T[Key] extends Prop ? never : Key;\n }[keyof T]\n>;\n\n/**\n * The opposite of Readonly<T>\n */\nexport type Mutable<T> = {\n -readonly [P in keyof T]: T[P];\n};\n\n/**\n * Any typed array\n */\nexport type TypedArray =\n | Uint8Array\n | Uint16Array\n | Uint32Array\n | Int32Array\n | Float32Array\n | Float64Array;\n\nexport function assertExhaustive(x: never, location: string): never {\n throw new Error(`Failed to handle ${x} at ${location}`);\n}\n","import { arrayOf } from '../data/array.ts';\nimport {\n type AnyData,\n isDisarray,\n isSnippet,\n isUnstruct,\n snip,\n type Snippet,\n UnknownData,\n} from '../data/dataTypes.ts';\nimport { mat2x2f, mat3x3f, mat4x4f } from '../data/matrix.ts';\nimport {\n abstractFloat,\n abstractInt,\n bool,\n f16,\n f32,\n i32,\n u32,\n} from '../data/numeric.ts';\nimport {\n vec2b,\n vec2f,\n vec2h,\n vec2i,\n vec2u,\n vec3b,\n vec3f,\n vec3h,\n vec3i,\n vec3u,\n vec4b,\n vec4f,\n vec4h,\n vec4i,\n vec4u,\n vecTypeToPrimitive,\n} from '../data/vector.ts';\nimport {\n type AnyWgslData,\n type AnyWgslStruct,\n type F16,\n type F32,\n hasInternalDataType,\n type I32,\n isMat,\n isMatInstance,\n isVec,\n isVecInstance,\n isWgslArray,\n isWgslStruct,\n type U32,\n} from '../data/wgslTypes.ts';\nimport { invariant } from '../errors.ts';\nimport { getResolutionCtx } from '../gpuMode.ts';\nimport { $wgslDataType } from '../shared/symbols.ts';\nimport { assertExhaustive } from '../shared/utilityTypes.ts';\nimport { isNumericSchema } from '../std/numeric.ts';\nimport type { ResolutionCtx } from '../types.ts';\n\ntype SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b';\ntype SwizzleLength = 1 | 2 | 3 | 4;\n\nconst swizzleLenToType: Record<\n SwizzleableType,\n Record<SwizzleLength, AnyData>\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 b: {\n 1: bool,\n 2: vec2b,\n 3: vec3b,\n 4: vec4b,\n },\n} as const;\n\nconst kindToSchema = {\n vec2f: vec2f,\n vec2h: vec2h,\n vec2i: vec2i,\n vec2u: vec2u,\n 'vec2<bool>': vec2b,\n vec3f: vec3f,\n vec3h: vec3h,\n vec3i: vec3i,\n vec3u: vec3u,\n 'vec3<bool>': vec3b,\n vec4f: vec4f,\n vec4h: vec4h,\n vec4i: vec4i,\n vec4u: vec4u,\n 'vec4<bool>': vec4b,\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 'vec2<bool>': bool,\n vec3f: f32,\n vec3h: f16,\n vec3i: i32,\n vec3u: u32,\n 'vec3<bool>': bool,\n vec4f: f32,\n vec4h: f16,\n vec4i: i32,\n vec4u: u32,\n 'vec4<bool>': bool,\n mat2x2f: vec2f,\n mat3x3f: vec3f,\n mat4x4f: vec4f,\n} as const;\n\nexport function getTypeForPropAccess(\n targetType: AnyData,\n propName: string,\n): AnyData | UnknownData {\n if (isWgslStruct(targetType) || isUnstruct(targetType)) {\n return targetType.propTypes[propName] as AnyData ?? UnknownData;\n }\n\n if (targetType === bool || isNumericSchema(targetType)) {\n // No props to be accessed here\n return UnknownData;\n }\n\n const propLength = propName.length;\n if (\n isVec(targetType) &&\n propLength >= 1 &&\n propLength <= 4\n ) {\n const swizzleTypeChar = targetType.type.includes('bool')\n ? 'b'\n : (targetType.type[4] as SwizzleableType);\n const swizzleType =\n swizzleLenToType[swizzleTypeChar][propLength as SwizzleLength];\n if (swizzleType) {\n return swizzleType;\n }\n }\n\n return UnknownData;\n}\n\nexport function getTypeForIndexAccess(\n dataType: AnyData,\n): AnyData | UnknownData {\n // array\n if (isWgslArray(dataType) || isDisarray(dataType)) {\n return dataType.elementType as AnyData;\n }\n\n // vector or matrix\n if (dataType.type in indexableTypeToResult) {\n return indexableTypeToResult[\n dataType.type as keyof typeof indexableTypeToResult\n ];\n }\n\n return UnknownData;\n}\n\nexport function numericLiteralToSnippet(value: string): Snippet | undefined {\n // Hex literals\n if (/^0x[0-9a-f]+$/i.test(value)) {\n return snip(value, abstractInt);\n }\n\n // Binary literals\n if (/^0b[01]+$/i.test(value)) {\n return snip(`${Number.parseInt(value.slice(2), 2)}`, abstractInt);\n }\n\n // Floating point literals\n if (/^-?(?:\\d+\\.\\d*|\\d*\\.\\d+)$/i.test(value)) {\n return snip(value, abstractFloat);\n }\n\n // Floating point literals with scientific notation\n if (/^-?\\d+(?:\\.\\d+)?e-?\\d+$/i.test(value)) {\n return snip(value, abstractFloat);\n }\n\n // Integer literals\n if (/^-?\\d+$/i.test(value)) {\n return snip(value, abstractInt);\n }\n\n return undefined;\n}\n\ntype ConversionAction = 'ref' | 'deref' | 'cast' | 'none';\n\ntype ConversionRankInfo =\n | { rank: number; action: 'cast'; targetType: AnyData }\n | { rank: number; action: Exclude<ConversionAction, 'cast'> };\n\nconst INFINITE_RANK: ConversionRankInfo = {\n rank: Number.POSITIVE_INFINITY,\n action: 'none',\n};\n\nfunction unwrapDecorated(data: AnyData): AnyData {\n if (data.type === 'decorated') {\n return data.inner as AnyData;\n }\n return data;\n}\n\nfunction getVectorComponent(type: AnyData): AnyData | undefined {\n return isVec(type) ? vecTypeToPrimitive[type.type] : undefined;\n}\n\nfunction getAutoConversionRank(\n src: AnyData,\n dest: AnyData,\n): ConversionRankInfo {\n const trueSrc = unwrapDecorated(src);\n const trueDst = unwrapDecorated(dest);\n\n if (trueSrc.type === trueDst.type) {\n return { rank: 0, action: 'none' };\n }\n\n if (trueSrc.type === 'abstractFloat') {\n if (trueDst.type === 'f32') return { rank: 1, action: 'none' };\n if (trueDst.type === 'f16') return { rank: 2, action: 'none' };\n }\n\n if (trueSrc.type === 'abstractInt') {\n if (trueDst.type === 'i32') return { rank: 3, action: 'none' };\n if (trueDst.type === 'u32') return { rank: 4, action: 'none' };\n if (trueDst.type === 'abstractFloat') return { rank: 5, action: 'none' };\n if (trueDst.type === 'f32') return { rank: 6, action: 'none' };\n if (trueDst.type === 'f16') return { rank: 7, action: 'none' };\n }\n\n if (isVec(trueSrc) && isVec(trueDst)) {\n const compSrc = getVectorComponent(trueSrc);\n const compDest = getVectorComponent(trueDst);\n if (compSrc && compDest) {\n return getAutoConversionRank(compSrc, compDest);\n }\n }\n\n if (isMat(trueSrc) && isMat(trueDst)) {\n // Matrix conversion rank depends only on component type (always f32 for now)\n return { rank: 0, action: 'none' };\n }\n\n return INFINITE_RANK;\n}\n\nfunction getImplicitConversionRank(\n src: AnyData,\n dest: AnyData,\n): ConversionRankInfo {\n const trueSrc = unwrapDecorated(src);\n const trueDst = unwrapDecorated(dest);\n\n if (\n trueSrc.type === 'ptr' &&\n getAutoConversionRank(trueSrc.inner as AnyData, trueDst).rank <\n Number.POSITIVE_INFINITY\n ) {\n return { rank: 0, action: 'deref' };\n }\n\n if (\n trueDst.type === 'ptr' &&\n getAutoConversionRank(trueSrc, trueDst.inner as AnyData).rank <\n Number.POSITIVE_INFINITY\n ) {\n return { rank: 1, action: 'ref' };\n }\n\n const primitivePreference = {\n f32: 0,\n f16: 1,\n i32: 2,\n u32: 3,\n bool: 4,\n } as const;\n type PrimitiveType = keyof typeof primitivePreference;\n\n if (\n trueSrc.type in primitivePreference &&\n trueDst.type in primitivePreference\n ) {\n const srcType = trueSrc.type as PrimitiveType;\n const destType = trueDst.type as PrimitiveType;\n\n if (srcType !== destType) {\n const srcPref = primitivePreference[srcType];\n const destPref = primitivePreference[destType];\n\n const rank = destPref < srcPref ? 10 : 20;\n\n return { rank: rank, action: 'cast', targetType: trueDst };\n }\n }\n\n return INFINITE_RANK;\n}\n\nfunction getConversionRank(\n src: AnyData,\n dest: AnyData,\n allowImplicit: boolean,\n): ConversionRankInfo {\n const autoRank = getAutoConversionRank(src, dest);\n if (autoRank.rank < Number.POSITIVE_INFINITY) {\n return autoRank;\n }\n if (allowImplicit) {\n return getImplicitConversionRank(src, dest);\n }\n return INFINITE_RANK;\n}\n\nexport type ConversionResultAction = {\n sourceIndex: number;\n action: ConversionAction;\n targetType?: U32 | F32 | I32 | F16;\n};\n\nexport type ConversionResult = {\n targetType: AnyData;\n actions: ConversionResultAction[];\n hasImplicitConversions?: boolean;\n};\n\nfunction findBestType(\n types: AnyData[],\n uniqueTypes: AnyData[],\n allowImplicit: boolean,\n): ConversionResult | undefined {\n let bestType: AnyData | undefined;\n let minSum = Number.POSITIVE_INFINITY;\n const conversionDetails = new Map<AnyData, ConversionRankInfo[]>();\n\n for (const targetType of uniqueTypes) {\n let currentSum = 0;\n const currentDetails: ConversionRankInfo[] = [];\n let possible = true;\n\n for (const sourceType of types) {\n const conversion = getConversionRank(\n sourceType,\n targetType,\n allowImplicit,\n );\n if (conversion.rank === Number.POSITIVE_INFINITY) {\n possible = false;\n break;\n }\n currentSum += conversion.rank;\n currentDetails.push(conversion);\n }\n\n if (possible && currentSum < minSum) {\n minSum = currentSum;\n bestType = targetType;\n conversionDetails.set(bestType, currentDetails);\n }\n }\n\n if (!bestType) {\n return undefined;\n }\n\n const bestDetails = conversionDetails.get(bestType) as ConversionRankInfo[];\n const actions: ConversionResultAction[] = bestDetails.map(\n (detail, index) => ({\n sourceIndex: index,\n action: detail.action,\n ...(detail.action === 'cast' && {\n targetType: detail.targetType as U32 | F32 | I32 | F16,\n }),\n }),\n );\n\n const hasCasts = actions.some((action) => action.action === 'cast');\n\n return { targetType: bestType, actions, hasImplicitConversions: hasCasts };\n}\n\nexport function concretize(type: AnyWgslData): AnyWgslData {\n if (type.type === 'abstractFloat') {\n return f32;\n }\n\n if (type.type === 'abstractInt') {\n return i32;\n }\n\n return type;\n}\n\nexport function getBestConversion(\n types: AnyData[],\n targetTypes?: AnyData[],\n): ConversionResult | undefined {\n if (types.length === 0) return undefined;\n\n const uniqueTypes = [...new Set(types.map(unwrapDecorated))];\n const uniqueTargetTypes = targetTypes\n ? [...new Set(targetTypes.map(unwrapDecorated))]\n : uniqueTypes;\n\n const explicitResult = findBestType(types, uniqueTargetTypes, false);\n if (explicitResult) {\n return explicitResult;\n }\n\n const implicitResult = findBestType(types, uniqueTargetTypes, true);\n if (implicitResult) {\n implicitResult.hasImplicitConversions = implicitResult.actions.some(\n (action) => action.action === 'cast',\n );\n return implicitResult;\n }\n\n return undefined;\n}\n\nexport function convertType(\n sourceType: AnyData,\n targetType: AnyData,\n allowImplicit = true,\n): ConversionResult | undefined {\n const conversion = getConversionRank(sourceType, targetType, allowImplicit);\n\n if (conversion.rank < Number.POSITIVE_INFINITY) {\n const actionDetail: ConversionResultAction = {\n sourceIndex: 0,\n action: conversion.action,\n };\n if (conversion.action === 'cast') {\n actionDetail.targetType = conversion.targetType as U32 | F32 | I32 | F16;\n }\n return {\n targetType: unwrapDecorated(targetType),\n actions: [actionDetail],\n hasImplicitConversions: conversion.action === 'cast',\n };\n }\n\n return undefined;\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): Snippet | null;\n defineVariable(id: string, dataType: AnyWgslData | UnknownData): Snippet;\n};\n\nfunction applyActionToSnippet(\n ctx: GenerationCtx,\n value: Snippet,\n action: ConversionResultAction,\n targetType: AnyData,\n): Snippet {\n if (action.action === 'none') {\n return snip(value.value, targetType);\n }\n\n const resolvedValue = ctx.resolve(value.value);\n\n switch (action.action) {\n case 'ref':\n return snip(`&${resolvedValue}`, targetType);\n case 'deref':\n return snip(`*${resolvedValue}`, targetType);\n case 'cast': {\n return snip(`${ctx.resolve(targetType)}(${resolvedValue})`, targetType);\n }\n default: {\n assertExhaustive(action.action, 'applyActionToSnippet');\n }\n }\n}\n\nexport function convertToCommonType(\n ctx: GenerationCtx,\n values: Snippet[],\n restrictTo?: AnyData[],\n): Snippet[] | undefined {\n const types = values.map((value) => value.dataType);\n\n if (types.some((type) => type === UnknownData)) {\n return undefined;\n }\n\n const conversion = getBestConversion(types as AnyData[], restrictTo);\n if (!conversion) {\n return undefined;\n }\n\n if (conversion.hasImplicitConversions) {\n console.warn(\n `Implicit conversions from [\\n${\n values\n .map((v) => ` ${v.value}: ${v.dataType.type}`)\n .join(\n ',\\n',\n )\n }\\n] to ${conversion.targetType.type} are supported, but not recommended.\nConsider using explicit conversions instead.`,\n );\n }\n\n return values.map((value, index) => {\n const action = conversion.actions[index];\n invariant(action, 'Action should not be undefined');\n return applyActionToSnippet(ctx, value, action, conversion.targetType);\n });\n}\n\nexport function convertStructValues(\n ctx: GenerationCtx,\n structType: AnyWgslStruct,\n values: Record<string, Snippet>,\n): Snippet[] {\n const propKeys = Object.keys(structType.propTypes);\n\n return propKeys.map((key) => {\n const val = values[key];\n if (!val) {\n throw new Error(`Missing property ${key}`);\n }\n\n const targetType = structType.propTypes[key];\n const converted = convertToCommonType(ctx, [val], [targetType as AnyData]);\n return converted?.[0] ?? val;\n });\n}\n\nexport function coerceToSnippet(value: unknown): Snippet {\n if (isSnippet(value)) {\n // Already a snippet\n return value;\n }\n\n if (hasInternalDataType(value)) {\n // The value knows better about what type it is\n return snip(value, value[$wgslDataType] as AnyData);\n }\n\n if (isVecInstance(value) || isMatInstance(value)) {\n return snip(value, kindToSchema[value.kind]);\n }\n\n if (Array.isArray(value)) {\n const coerced = value.map(coerceToSnippet).filter(Boolean);\n const context = getResolutionCtx() as GenerationCtx | undefined;\n if (!context) {\n throw new Error('Tried to coerce array without a context');\n }\n\n const converted = convertToCommonType(context, coerced as Snippet[]);\n const commonType = getBestConversion(\n coerced.map((v) => v.dataType as AnyData),\n )?.targetType as AnyWgslData | undefined;\n\n if (!converted || !commonType) {\n return snip(value, UnknownData);\n }\n\n return snip(\n converted.map((v) => v.value).join(', '),\n arrayOf(concretize(commonType), value.length),\n );\n }\n\n if (\n typeof value === 'string' || typeof value === 'function' ||\n typeof value === 'object' || typeof value === 'symbol' ||\n typeof value === 'undefined' || value === null\n ) {\n // Nothing representable in WGSL as-is, so unknown\n return snip(value, UnknownData);\n }\n\n if (typeof value === 'number' || typeof value === 'bigint') {\n return snip(\n value,\n numericLiteralToSnippet(String(value))?.dataType ?? UnknownData,\n );\n }\n\n if (typeof value === 'boolean') {\n return snip(value, bool);\n }\n\n return snip(value, UnknownData);\n}\n","import type { AnyData } from '../data/dataTypes.ts';\nimport type { BaseData } from '../data/wgslTypes.ts';\nimport {\n extractGpuValueGetter,\n type GpuValueGetter,\n} from '../extractGpuValueGetter.ts';\nimport { getName } from '../name.ts';\nimport { $wgslDataType } from '../shared/symbols.ts';\nimport { getTypeForPropAccess } from '../tgsl/generationHelpers.ts';\nimport type { ResolutionCtx, SelfResolvable } from '../types.ts';\n\nexport const valueProxyHandler: ProxyHandler<\n & SelfResolvable\n & { readonly [$wgslDataType]: BaseData }\n> = {\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 if (\n prop === 'toString' ||\n prop === Symbol.toStringTag ||\n prop === Symbol.toPrimitive\n ) {\n return () => target.toString();\n }\n\n return new Proxy(\n {\n '~resolve': (ctx: ResolutionCtx) =>\n `${ctx.resolve(target)}.${String(prop)}`,\n\n toString: () =>\n `.value(...).${String(prop)}:${getName(target) ?? '<unnamed>'}`,\n\n [$wgslDataType]: getTypeForPropAccess(\n target[$wgslDataType] as AnyData,\n String(prop),\n ) as BaseData,\n },\n valueProxyHandler,\n );\n },\n};\n\nexport function getGpuValueRecursively<T>(\n ctx: ResolutionCtx,\n value: unknown,\n): T {\n let unwrapped = value;\n let valueGetter: GpuValueGetter | undefined;\n\n // biome-ignore lint/suspicious/noAssignInExpressions: it's exactly what we want biome\n while (valueGetter = extractGpuValueGetter(unwrapped)) {\n unwrapped = valueGetter(ctx);\n }\n\n return unwrapped as T;\n}\n","import { getAttributesString } from '../../data/attributes.ts';\nimport {\n type AnyData,\n type Disarray,\n isLooseData,\n type Unstruct,\n} from '../../data/dataTypes.ts';\nimport { formatToWGSLType } from '../../data/vertexFormatData.ts';\nimport type {\n AnyWgslData,\n BaseData,\n Bool,\n F16,\n F32,\n I32,\n Mat2x2f,\n Mat3x3f,\n Mat4x4f,\n U32,\n Vec2b,\n Vec2f,\n Vec2h,\n Vec2i,\n Vec2u,\n Vec3b,\n Vec3f,\n Vec3h,\n Vec3i,\n Vec3u,\n Vec4b,\n Vec4f,\n Vec4h,\n Vec4i,\n Vec4u,\n WgslArray,\n WgslStruct,\n} from '../../data/wgslTypes.ts';\nimport { getName } from '../../name.ts';\nimport { assertExhaustive } from '../../shared/utilityTypes.ts';\nimport type { ResolutionCtx } from '../../types.ts';\nimport { isAttribute } from '../vertexLayout/connectAttributesToShader.ts';\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 'vec2<bool>',\n 'vec3<bool>',\n 'vec4<bool>',\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 | Vec2b\n | Vec3b\n | Vec4b\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}: ${\n ctx.resolve(property as AnyWgslData)\n },\\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(getName(struct));\n\n ctx.addDeclaration(`\nstruct ${id} {\n${\n Object.entries(struct.propTypes)\n .map((prop) => resolveStructProperty(ctx, prop))\n .join('')\n }\\\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(getName(unstruct));\n\n ctx.addDeclaration(`\nstruct ${id} {\n${\n Object.entries(unstruct.propTypes)\n .map((prop) =>\n isAttribute(prop[1])\n ? resolveStructProperty(ctx, [\n prop[0],\n formatToWGSLType[prop[1].format],\n ])\n : resolveStructProperty(ctx, prop)\n )\n .join('')\n }\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)}, ${\n data.access === 'read-write' ? 'read_write' : data.access\n }>`;\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 if (data.type === 'void') {\n throw new Error('Void has no representation in WGSL');\n }\n\n assertExhaustive(data, 'resolveData');\n}\n","import { roundUp } from '../mathUtils.ts';\nimport type { Infer } from '../shared/repr.ts';\nimport { alignmentOf } from './alignmentOf.ts';\nimport { isDisarray, isUnstruct } from './dataTypes.ts';\nimport { offsetsForProps } from './offsets.ts';\nimport { sizeOf } from './sizeOf.ts';\nimport * as wgsl from './wgslTypes.ts';\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.isAtomic(node) || wgsl.isDecorated(node)) {\n return buildWriter(node.inner, offsetExpr, valueExpr);\n }\n\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}.${\n components[i]\n }, 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 +=\n `output.${writeFunc}((${offsetExpr} + ${byteOffset}), ${valueExpr}.columns[${colIndex}].${\n ['x', 'y', 'z', 'w'][rowIndex]\n }, littleEndian);\\n`;\n }\n\n return code;\n }\n\n const primitive = typeToPrimitive[node.type as keyof typeof typeToPrimitive];\n return `output.${\n primitiveToWriteFunction[primitive]\n }(${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.ts';\nimport alignIO from './alignIO.ts';\nimport { alignmentOf, customAlignmentOf } from './alignmentOf.ts';\nimport type {\n AnyConcreteData,\n AnyData,\n Disarray,\n LooseDecorated,\n Unstruct,\n} from './dataTypes.ts';\nimport { mat2x2f, mat3x3f, mat4x4f } from './matrix.ts';\nimport { sizeOf } from './sizeOf.ts';\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.ts';\nimport type * as wgsl from './wgslTypes.ts';\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() {\n throw new Error('Booleans are not host-shareable');\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 'vec2<bool>'() {\n throw new Error('Booleans are not host-shareable');\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 'vec3<bool>'() {\n throw new Error('Booleans are not host-shareable');\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 'vec4<bool>'() {\n throw new Error('Booleans are not host-shareable');\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: wgsl.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(): boolean {\n throw new Error('Booleans are not host-shareable');\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 'vec2<bool>'() {\n throw new Error('Booleans are not host-shareable');\n },\n\n 'vec3<bool>'() {\n throw new Error('Booleans are not host-shareable');\n },\n\n 'vec4<bool>'() {\n throw new Error('Booleans are not host-shareable');\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: wgsl.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: