typegpu 0.8.0 → 0.8.1

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.
Files changed (55) hide show
  1. package/chunk-2UXPGML5.js +7 -0
  2. package/chunk-2UXPGML5.js.map +1 -0
  3. package/chunk-3246CM7C.cjs +2 -0
  4. package/chunk-3246CM7C.cjs.map +1 -0
  5. package/{chunk-XEUNEHAZ.js → chunk-4W5Z7BO4.js} +6 -6
  6. package/chunk-4W5Z7BO4.js.map +1 -0
  7. package/{chunk-TRAG63HY.cjs → chunk-5Y6GTBWR.cjs} +3 -3
  8. package/{chunk-TRAG63HY.cjs.map → chunk-5Y6GTBWR.cjs.map} +1 -1
  9. package/{chunk-6WF2EZIT.js → chunk-7S3IK3D4.js} +2 -2
  10. package/{chunk-B254XDWG.js → chunk-7XFSK632.js} +2 -2
  11. package/{chunk-M2P3FJT7.cjs → chunk-PRMFGUQT.cjs} +2 -2
  12. package/{chunk-M2P3FJT7.cjs.map → chunk-PRMFGUQT.cjs.map} +1 -1
  13. package/{chunk-A5APHF7K.cjs → chunk-TRE7NUKE.cjs} +6 -6
  14. package/chunk-TRE7NUKE.cjs.map +1 -0
  15. package/chunk-VOVQAOVG.js +2 -0
  16. package/chunk-VOVQAOVG.js.map +1 -0
  17. package/chunk-ZYGTVBDH.cjs +7 -0
  18. package/chunk-ZYGTVBDH.cjs.map +1 -0
  19. package/common/index.cjs +1 -1
  20. package/common/index.d.cts +1 -1
  21. package/common/index.d.ts +1 -1
  22. package/common/index.js +1 -1
  23. package/data/index.cjs +1 -1
  24. package/data/index.d.cts +3 -3
  25. package/data/index.d.ts +3 -3
  26. package/data/index.js +1 -1
  27. package/index.cjs +51 -51
  28. package/index.cjs.map +1 -1
  29. package/index.d.cts +2 -2
  30. package/index.d.ts +2 -2
  31. package/index.js +40 -40
  32. package/index.js.map +1 -1
  33. package/{matrix-Cn2jQILV.d.cts → matrix-DHFT4O8f.d.cts} +1 -1
  34. package/{matrix-C6mMH7pB.d.ts → matrix-DLipCOZF.d.ts} +1 -1
  35. package/package.json +1 -1
  36. package/std/index.cjs +1 -1
  37. package/std/index.cjs.map +1 -1
  38. package/std/index.d.cts +2 -2
  39. package/std/index.d.ts +2 -2
  40. package/std/index.js +1 -1
  41. package/std/index.js.map +1 -1
  42. package/{tgpuConstant-DzGgwe0I.d.cts → tgpuConstant-BU72w5qs.d.cts} +4 -8
  43. package/{tgpuConstant-DzGgwe0I.d.ts → tgpuConstant-BU72w5qs.d.ts} +4 -8
  44. package/chunk-A5APHF7K.cjs.map +0 -1
  45. package/chunk-LL6NPRFE.cjs +0 -7
  46. package/chunk-LL6NPRFE.cjs.map +0 -1
  47. package/chunk-PO6SIMTQ.js +0 -2
  48. package/chunk-PO6SIMTQ.js.map +0 -1
  49. package/chunk-T2GBOTUH.js +0 -7
  50. package/chunk-T2GBOTUH.js.map +0 -1
  51. package/chunk-U3CXOCRG.cjs +0 -2
  52. package/chunk-U3CXOCRG.cjs.map +0 -1
  53. package/chunk-XEUNEHAZ.js.map +0 -1
  54. /package/{chunk-6WF2EZIT.js.map → chunk-7S3IK3D4.js.map} +0 -0
  55. /package/{chunk-B254XDWG.js.map → chunk-7XFSK632.js.map} +0 -0
package/index.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","../src/getGPUValue.ts","../src/core/valueProxyUtils.ts","../src/core/resolve/resolveData.ts","../src/data/offsets.ts","../src/data/compiledIO.ts","../src/data/dataIO.ts","../src/core/buffer/buffer.ts","../src/core/texture/textureUtils.ts","../src/core/texture/texture.ts","../src/tgsl/consoleLog/serializers.ts","../src/resolutionCtx.ts","../src/core/simulate/tgpuSimulate.ts","../src/core/root/init.ts"],"names":["getGPUValue","object","$gpuValueOf","valueProxyHandler","target","prop","targetDataType","getOwnSnippet","propType","getTypeForPropAccess","$internal","$resolve","ctx","snip","property"],"mappings":"AAAA,ipCAAiG,wDAAkG,wDAAoM,wDAAi4B,SCGxvCA,EAAAA,CACdC,CAAAA,CACuD,CACvD,uBAAQA,CAAAA,4BAAAA,CAAmCC,mBAAW,GACxD,CCIO,IAAMC,CAAAA,CAET,CACF,GAAA,CAAIC,CAAAA,CAAQC,CAAAA,CAAM,CAChB,EAAA,CAAIA,EAAAA,GAAQD,CAAAA,CACV,OAAO,OAAA,CAAQ,GAAA,CAAIA,CAAAA,CAAQC,CAAI,CAAA,CAGjC,EAAA,CACEA,CAAAA,GAAS,UAAA,EACTA,CAAAA,GAAS,MAAA,CAAO,WAAA,EAChBA,CAAAA,GAAS,MAAA,CAAO,WAAA,CAEhB,MAAO,CAAA,CAAA,EAAMD,CAAAA,CAAO,QAAA,CAAS,CAAA,CAG/B,EAAA,CAAI,OAAOC,CAAAA,EAAS,QAAA,CAClB,MAAA,CAIF,IAAMC,CAAAA,CADgBC,kCAAAA,CAAoB,CAAA,CACL,QAAA,CAC/BC,CAAAA,CAAWC,iCAAAA,CAAqBH,CAAgB,MAAA,CAAOD,CAAI,CAAC,CAAA,CAClE,EAAA,CAAIG,CAAAA,CAAS,IAAA,GAAS,SAAA,CAKtB,OAAO,IAAI,KAAA,CAAM,CACf,CAACE,mBAAS,CAAA,CAAG,CAAA,CAAA,CACb,CAACC,mBAAQ,CAAA,CAAIC,CAAAA,EACXC,kCAAAA,CAAK,EAAA;AC0GF;AA8BA;AAYT;AC9LO,CAAA;AC8IC;AA2CkE;AAOhE;AAeN;AAsBE;AA6CS;AAea;AAkDsB;AAE9C,QAAA;ACzJ4BC,8BAAAA;AC+O9B,8BAAA;ACjSQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgDA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,EAAA;AAEN,wDAAA;AAAA;AAsHM,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACnFW,MAAA;AC3MS;AAAA;AAAA;AAUjB,CAAA;AACJ,EAAA;AAEI,CAAA;AACJ,EAAA;AAEI,CAAA;AACJ,EAAA;AAEI,CAAA;AACJ,EAAA;AAEM,CAAA;AACN,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEe,CAAA;AACf,EAAA;AACA,EAAA;AAEe,CAAA;AACf,EAAA;AACA,EAAA;AACA,EAAA;AAEe,CAAA;AACf,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEY,CAAA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEY,CAAA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEY,CAAA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAyDG,CAAA;AAyBJ;AAkCqB;AAAA;AAEmB,wBAAA;AAAA;AAAA;AAGZ,kCAAA;AAAA;AAAA;AAGV,oBAAA;AC7KvB,CAAA;AAwlBuC;AA2JrB;AA8CY;AAAA;ACxzBnB;AC+HX;AAAA;AAAA;AAAA;AAyJkC,CAAA","file":"/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","sourcesContent":[null,"import { $gpuValueOf } from './shared/symbols.ts';\nimport type { WithGPUValue } from './types.ts';\n\nexport function getGPUValue(\n object: unknown,\n): WithGPUValue<unknown>[typeof $gpuValueOf] | undefined {\n return (object as WithGPUValue<unknown>)?.[$gpuValueOf];\n}\n","import type { AnyData } from '../data/dataTypes.ts';\nimport { snip, type Snippet } from '../data/snippet.ts';\nimport { getGPUValue } from '../getGPUValue.ts';\nimport { $internal, $ownSnippet, $resolve } from '../shared/symbols.ts';\nimport { getTypeForPropAccess } from '../tgsl/generationHelpers.ts';\nimport {\n getOwnSnippet,\n type SelfResolvable,\n type WithOwnSnippet,\n} from '../types.ts';\n\nexport const valueProxyHandler: ProxyHandler<\n SelfResolvable & WithOwnSnippet\n> = {\n get(target, prop) {\n if (prop in target) {\n return Reflect.get(target, prop);\n }\n\n if (\n prop === 'toString' ||\n prop === Symbol.toStringTag ||\n prop === Symbol.toPrimitive\n ) {\n return () => target.toString();\n }\n\n if (typeof prop === 'symbol') {\n return undefined;\n }\n\n const targetSnippet = getOwnSnippet(target) as Snippet;\n const targetDataType = targetSnippet.dataType as AnyData;\n const propType = getTypeForPropAccess(targetDataType, String(prop));\n if (propType.type === 'unknown') {\n // Prop was not found, must be missing from this object\n return undefined;\n }\n\n return new Proxy({\n [$internal]: true,\n [$resolve]: (ctx) =>\n snip(`${ctx.resolve(target).value}.${String(prop)}`, propType),\n get [$ownSnippet]() {\n return snip(this, propType);\n },\n toString: () => `${String(target)}.${prop}`,\n }, valueProxyHandler);\n },\n};\n\nexport function getGpuValueRecursively<T>(value: unknown): T {\n let unwrapped = value;\n\n while (true) {\n const gpuValue = getGPUValue(unwrapped);\n if (!gpuValue) {\n break;\n }\n unwrapped = gpuValue;\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 { isWgslComparisonSampler, isWgslSampler } from '../../data/sampler.ts';\nimport {\n accessModeMap,\n isWgslStorageTexture,\n isWgslTexture,\n type WgslExternalTexture,\n} from '../../data/texture.ts';\n\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 { isValidIdentifier } from '../../nameRegistry.ts';\nimport { $internal } from '../../shared/symbols.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 'texture_external',\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 | WgslExternalTexture;\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 if (!isValidIdentifier(key)) {\n throw new Error(\n `Property key '${key}' is a reserved WGSL word. Choose a different name.`,\n );\n }\n return ` ${getAttributesString(property)}${key}: ${\n ctx.resolve(property as AnyWgslData).value\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 if (struct[$internal].isAbstruct) {\n throw new Error('Cannot resolve abstract struct types to WGSL.');\n }\n const id = ctx.getUniqueName(struct);\n\n ctx.addDeclaration(`\\\nstruct ${id} {\n${\n Object.entries(struct.propTypes as Record<string, BaseData>)\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.getUniqueName(unstruct);\n\n ctx.addDeclaration(`\\\nstruct ${id} {\n${\n Object.entries(unstruct.propTypes as Record<string, BaseData>)\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 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).value;\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 ).value;\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 ).value;\n }\n\n return ctx.resolve(formatToWGSLType[data.type]).value;\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).value;\n }\n\n if (data.type === 'ptr') {\n if (data.addressSpace === 'storage') {\n return `ptr<storage, ${ctx.resolve(data.inner).value}, ${\n data.access === 'read-write' ? 'read_write' : data.access\n }>`;\n }\n return `ptr<${data.addressSpace}, ${ctx.resolve(data.inner).value}>`;\n }\n\n if (\n data.type === 'abstractInt' ||\n data.type === 'abstractFloat' ||\n data.type === 'void' ||\n data.type === 'u16'\n ) {\n throw new Error(`${data.type} has no representation in WGSL`);\n }\n\n if (isWgslStorageTexture(data)) {\n return `${data.type}<${data.format}, ${accessModeMap[data.access]}>`;\n }\n\n if (isWgslTexture(data)) {\n return data.type.startsWith('texture_depth')\n ? data.type\n : `${data.type}<${data.sampleType.type}>`;\n }\n\n if (isWgslComparisonSampler(data) || isWgslSampler(data)) {\n return data.type;\n }\n\n assertExhaustive(data, 'resolveData');\n}\n","import { Measurer } from 'typed-binary';\nimport { roundUp } from '../mathUtils.ts';\nimport alignIO from './alignIO.ts';\nimport { alignmentOf, customAlignmentOf } from './alignmentOf.ts';\nimport { isUnstruct, type Unstruct } from './dataTypes.ts';\nimport { sizeOf } from './sizeOf.ts';\nimport type { BaseData, WgslStruct } from './wgslTypes.ts';\n\nexport interface OffsetInfo {\n offset: number;\n size: number;\n padding?: number | undefined;\n}\n\nconst cachedOffsets = new WeakMap<\n WgslStruct | Unstruct,\n Record<string, OffsetInfo>\n>();\n\nexport function offsetsForProps<T extends Record<string, BaseData>>(\n struct: WgslStruct<T> | Unstruct<T>,\n): Record<keyof T, OffsetInfo> {\n const cached = cachedOffsets.get(struct);\n if (cached) {\n return cached as Record<keyof T, OffsetInfo>;\n }\n\n const measurer = new Measurer();\n const offsets = {} as Record<keyof T, OffsetInfo>;\n let lastEntry: OffsetInfo | undefined;\n\n for (const key in struct.propTypes) {\n const prop = struct.propTypes[key];\n if (prop === undefined) {\n throw new Error(`Property ${key} is undefined in struct`);\n }\n\n const beforeAlignment = measurer.size;\n\n alignIO(\n measurer,\n isUnstruct(struct) ? customAlignmentOf(prop) : alignmentOf(prop),\n );\n\n if (lastEntry) {\n lastEntry.padding = measurer.size - beforeAlignment;\n }\n\n const propSize = sizeOf(prop);\n offsets[key] = { offset: measurer.size, size: propSize };\n lastEntry = offsets[key];\n measurer.add(propSize);\n }\n\n if (lastEntry) {\n lastEntry.padding = roundUp(sizeOf(struct), alignmentOf(struct)) -\n measurer.size;\n }\n\n cachedOffsets.set(\n struct as\n | WgslStruct<Record<string, BaseData>>\n | Unstruct<Record<string, BaseData>>,\n offsets,\n );\n\n return offsets;\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 { formatToWGSLType, isPackedData } from './vertexFormatData.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 u16: 'u16',\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 f16: 'f16',\n vec2h: 'f16',\n vec3h: 'f16',\n vec4h: 'f16',\n\n mat2x2f: 'f32',\n mat3x3f: 'f32',\n mat4x4f: 'f32',\n} as const;\n\nconst vertexFormatToPrimitive = {\n uint8: 'u8',\n uint8x2: 'u8',\n uint8x4: 'u8',\n sint8: 'i8',\n sint8x2: 'i8',\n sint8x4: 'i8',\n unorm8: 'u8',\n unorm8x2: 'u8',\n unorm8x4: 'u8',\n snorm8: 'i8',\n snorm8x2: 'i8',\n snorm8x4: 'i8',\n uint16: 'u16',\n uint16x2: 'u16',\n uint16x4: 'u16',\n sint16: 'i16',\n sint16x2: 'i16',\n sint16x4: 'i16',\n unorm16: 'u16',\n unorm16x2: 'u16',\n unorm16x4: 'u16',\n snorm16: 'i16',\n snorm16x2: 'i16',\n snorm16x4: 'i16',\n float16: 'f16',\n float16x2: 'f16',\n float16x4: 'f16',\n float32: 'f32',\n float32x2: 'f32',\n float32x3: 'f32',\n float32x4: 'f32',\n uint32: 'u32',\n uint32x2: 'u32',\n uint32x3: 'u32',\n uint32x4: 'u32',\n sint32: 'i32',\n sint32x2: 'i32',\n sint32x3: 'i32',\n sint32x4: 'i32',\n} as const;\n\nconst primitiveToWriteFunction = {\n u32: 'setUint32',\n i32: 'setInt32',\n f32: 'setFloat32',\n u16: 'setUint16',\n i16: 'setInt16',\n f16: 'setFloat16',\n u8: 'setUint8',\n i8: 'setInt8',\n} as const;\n\n/**\n * @privateRemarks\n * based on the `Channel Formats` table https://www.w3.org/TR/WGSL/#texel-formats\n */\nconst vertexFormatValueTransform = {\n unorm8: (value: string) => `Math.round(${value} * 255)`,\n unorm8x2: (value: string) => `Math.round(${value} * 255)`,\n unorm8x4: (value: string) => `Math.round(${value} * 255)`,\n snorm8: (value: string) => `Math.round(${value} * 127)`,\n snorm8x2: (value: string) => `Math.round(${value} * 127)`,\n snorm8x4: (value: string) => `Math.round(${value} * 127)`,\n unorm16: (value: string) => `Math.round(${value} * 65535)`,\n unorm16x2: (value: string) => `Math.round(${value} * 65535)`,\n unorm16x4: (value: string) => `Math.round(${value} * 65535)`,\n snorm16: (value: string) => `Math.round(${value} * 32767)`,\n snorm16x2: (value: string) => `Math.round(${value} * 32767)`,\n snorm16x4: (value: string) => `Math.round(${value} * 32767)`,\n} as const;\n\nconst specialPackedFormats = {\n 'unorm10-10-10-2': {\n writeFunction: 'setUint32',\n generator: (offsetExpr: string, valueExpr: string) =>\n `output.setUint32(${offsetExpr}, ((${valueExpr}.x*1023&0x3FF)<<22)|((${valueExpr}.y*1023&0x3FF)<<12)|((${valueExpr}.z*1023&0x3FF)<<2)|(${valueExpr}.w*3&3), littleEndian);\\n`,\n },\n 'unorm8x4-bgra': {\n writeFunction: 'setUint8',\n generator: (offsetExpr: string, valueExpr: string) => {\n const bgraComponents = ['z', 'y', 'x', 'w'];\n let code = '';\n for (let idx = 0; idx < 4; idx++) {\n code +=\n `output.setUint8((${offsetExpr} + ${idx}), Math.round(${valueExpr}.${\n bgraComponents[idx]\n } * 255), littleEndian);\\n`;\n }\n return code;\n },\n },\n} as const;\n\nexport function buildWriter(\n node: wgsl.BaseData,\n offsetExpr: string,\n valueExpr: string,\n depth = 0,\n): string {\n const loopVar = ['i', 'j', 'k'][depth] || `i${depth}`;\n\n if (wgsl.isAtomic(node) || wgsl.isDecorated(node)) {\n return buildWriter(node.inner, offsetExpr, valueExpr, depth);\n }\n\n if (wgsl.isWgslStruct(node) || isUnstruct(node)) {\n const propOffsets = offsetsForProps(node);\n let code = '';\n for (const [key, propOffset] of Object.entries(propOffsets)) {\n const subSchema = node.propTypes[key];\n if (!subSchema) continue;\n code += buildWriter(\n subSchema,\n `(${offsetExpr} + ${propOffset.offset})`,\n `${valueExpr}.${key}`,\n depth,\n );\n }\n return code;\n }\n\n if (wgsl.isWgslArray(node) || isDisarray(node)) {\n const elementSize = roundUp(\n sizeOf(node.elementType),\n alignmentOf(node),\n );\n let code = '';\n\n code +=\n `for (let ${loopVar} = 0; ${loopVar} < ${node.elementCount}; ${loopVar}++) {\\n`;\n code += buildWriter(\n node.elementType,\n `(${offsetExpr} + ${loopVar} * ${elementSize})`,\n `${valueExpr}[${loopVar}]`,\n depth + 1,\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 idx = 0; idx < elementCount; idx++) {\n const colIndex = Math.floor(idx / matSize);\n const rowIndex = idx % 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 if (isPackedData(node)) {\n const formatName = node.type;\n\n if (formatName in specialPackedFormats) {\n const handler =\n specialPackedFormats[formatName as keyof typeof specialPackedFormats];\n return handler.generator(offsetExpr, valueExpr);\n }\n\n const primitive = vertexFormatToPrimitive[\n formatName as keyof typeof vertexFormatToPrimitive\n ];\n const writeFunc = primitiveToWriteFunction[primitive];\n const wgslType = formatToWGSLType[formatName];\n const componentCount = wgsl.isVec4(wgslType)\n ? 4\n : wgsl.isVec3(wgslType)\n ? 3\n : wgsl.isVec2(wgslType)\n ? 2\n : 1;\n const componentSize = primitive === 'u8' || primitive === 'i8'\n ? 1\n : primitive === 'u16' || primitive === 'i16' || primitive === 'f16'\n ? 2\n : 4;\n const components = ['x', 'y', 'z', 'w'];\n const transform = vertexFormatValueTransform[\n formatName as keyof typeof vertexFormatValueTransform\n ];\n\n let code = '';\n for (let idx = 0; idx < componentCount; idx++) {\n const accessor = componentCount === 1\n ? valueExpr\n : `${valueExpr}.${components[idx]}`;\n const value = transform ? transform(accessor) : accessor;\n code += `output.${writeFunc}((${offsetExpr} + ${\n idx * componentSize\n }), ${value}, littleEndian);\\n`;\n }\n\n return code;\n }\n\n if (!Object.hasOwn(typeToPrimitive, node.type)) {\n throw new Error(\n `Primitive ${node.type} is unsupported by compiled writer`,\n );\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 | ((\n output: DataView,\n offset: number,\n value: Infer<T>,\n littleEndian?: boolean,\n ) => void)\n | undefined {\n if (!EVAL_ALLOWED_IN_ENV) {\n console.warn(\n 'This environment does not allow eval - using default writer as fallback',\n );\n return undefined;\n }\n\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 try {\n const body = buildWriter(schema, 'offset', 'value', 0);\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 } catch (error) {\n console.warn(\n `Failed to compile writer for schema: ${schema}\\nReason: ${\n error instanceof Error ? error.message : String(error)\n }\\nFalling back to default writer`,\n );\n }\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 u16(output, _schema: wgsl.U16, value: number) {\n output.writeUint16(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 as wgsl.BaseData));\n writeData(output, property as wgsl.BaseData, value[key]);\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(Math.round(value * 255));\n },\n unorm8x2(output, _, value: wgsl.v2f) {\n output.writeUint8(Math.round(value.x * 255));\n output.writeUint8(Math.round(value.y * 255));\n },\n unorm8x4(output, _, value: wgsl.v4f) {\n output.writeUint8(Math.round(value.x * 255));\n output.writeUint8(Math.round(value.y * 255));\n output.writeUint8(Math.round(value.z * 255));\n output.writeUint8(Math.round(value.w * 255));\n },\n snorm8(output, _, value: number) {\n output.writeInt8(Math.round(value * 127));\n },\n snorm8x2(output, _, value: wgsl.v2f) {\n output.writeInt8(Math.round(value.x * 127));\n output.writeInt8(Math.round(value.y * 127));\n },\n snorm8x4(output, _, value: wgsl.v4f) {\n output.writeInt8(Math.round(value.x * 127));\n output.writeInt8(Math.round(value.y * 127));\n output.writeInt8(Math.round(value.z * 127));\n output.writeInt8(Math.round(value.w * 127));\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.writeInt16(Math.round(value * 32767));\n },\n snorm16x2(output, _, value: wgsl.v2f) {\n output.writeInt16(Math.round(value.x * 32767));\n output.writeInt16(Math.round(value.y * 32767));\n },\n snorm16x4(output, _, value: wgsl.v4f) {\n output.writeInt16(Math.round(value.x * 32767));\n output.writeInt16(Math.round(value.y * 32767));\n output.writeInt16(Math.round(value.z * 32767));\n output.writeInt16(Math.round(value.w * 32767));\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.y * 1023) & 1023) << 12; // g (10 bits)\n packed |= ((value.z * 1023) & 1023) << 2; // b (10 bits)\n packed |= (value.w * 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 const propTypes = schema.propTypes as Record<string, wgsl.BaseData>;\n for (const [key, property] of Object.entries(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 // TODO: Move texture IO logic here after we expand repr to have in/out variants\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 u16(input: ISerialInput): number {\n return input.readUint16();\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 const propTypes = schema.propTypes as Record<string, wgsl.BaseData>;\n for (const [key, property] of Object.entries(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.readInt8() / 127,\n snorm8x2: (i) => vec2f(i.readInt8() / 127, i.readInt8() / 127),\n snorm8x4: (i) =>\n vec4f(\n i.readInt8() / 127,\n i.readInt8() / 127,\n i.readInt8() / 127,\n i.readInt8() / 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.readInt16() / 32767,\n snorm16x2: (i): wgsl.v2f =>\n vec2f(i.readInt16() / 32767, i.readInt16() / 32767),\n snorm16x4: (i): wgsl.v4f =>\n vec4f(\n i.readInt16() / 32767,\n i.readInt16() / 32767,\n i.readInt16() / 32767,\n i.readInt16() / 32767,\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 const propTypes = schema.propTypes as Record<string, wgsl.BaseData>;\n for (const [key, property] of Object.entries(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 // TODO: Move texture IO logic here after we expand repr to have in/out variants\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 { BufferReader, BufferWriter, getSystemEndianness } from 'typed-binary';\nimport { getCompiledWriterForSchema } from '../../data/compiledIO.ts';\nimport { readData, writeData } from '../../data/dataIO.ts';\nimport type { AnyData } from '../../data/dataTypes.ts';\nimport { getWriteInstructions } from '../../data/partialIO.ts';\nimport { sizeOf } from '../../data/sizeOf.ts';\nimport type { BaseData } from '../../data/wgslTypes.ts';\nimport { isWgslData } from '../../data/wgslTypes.ts';\nimport type { StorageFlag } from '../../extension.ts';\nimport type { TgpuNamable } from '../../shared/meta.ts';\nimport { getName, setName } from '../../shared/meta.ts';\nimport type {\n Infer,\n InferPartial,\n IsValidIndexSchema,\n IsValidStorageSchema,\n IsValidUniformSchema,\n IsValidVertexSchema,\n MemIdentity,\n} from '../../shared/repr.ts';\nimport { $internal } from '../../shared/symbols.ts';\nimport type {\n Prettify,\n UnionToIntersection,\n} from '../../shared/utilityTypes.ts';\nimport { isGPUBuffer } from '../../types.ts';\nimport type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';\nimport {\n asMutable,\n asReadonly,\n asUniform,\n type TgpuBufferMutable,\n type TgpuBufferReadonly,\n type TgpuBufferUniform,\n type TgpuFixedBufferUsage,\n} from './bufferUsage.ts';\n\n// ----------\n// Public API\n// ----------\n\nexport interface UniformFlag {\n usableAsUniform: true;\n}\n\n/**\n * @deprecated Use UniformFlag instead.\n */\nexport type Uniform = UniformFlag;\n\nexport interface VertexFlag {\n usableAsVertex: true;\n}\n\nexport interface IndexFlag {\n usableAsIndex: true;\n}\n\n/**\n * @deprecated Use VertexFlag instead.\n */\nexport type Vertex = VertexFlag;\n\ntype LiteralToUsageType<T extends 'uniform' | 'storage' | 'vertex' | 'index'> =\n T extends 'uniform' ? UniformFlag\n : T extends 'storage' ? StorageFlag\n : T extends 'vertex' ? VertexFlag\n : T extends 'index' ? IndexFlag\n : never;\n\ntype ViewUsages<TBuffer extends TgpuBuffer<BaseData>> =\n | (boolean extends TBuffer['usableAsUniform'] ? never : 'uniform')\n | (boolean extends TBuffer['usableAsStorage'] ? never\n : 'readonly' | 'mutable');\n\ntype UsageTypeToBufferUsage<TData extends BaseData> = {\n uniform: TgpuBufferUniform<TData> & TgpuFixedBufferUsage<TData>;\n mutable: TgpuBufferMutable<TData> & TgpuFixedBufferUsage<TData>;\n readonly: TgpuBufferReadonly<TData> & TgpuFixedBufferUsage<TData>;\n};\n\nconst usageToUsageConstructor = {\n uniform: asUniform,\n mutable: asMutable,\n readonly: asReadonly,\n};\n\n/**\n * Done as an object to later Prettify it\n */\ntype InnerValidUsagesFor<T> = {\n usage:\n | (IsValidStorageSchema<T> extends true ? 'storage' : never)\n | (IsValidUniformSchema<T> extends true ? 'uniform' : never)\n | (IsValidVertexSchema<T> extends true ? 'vertex' : never)\n | (IsValidIndexSchema<T> extends true ? 'index' : never);\n};\n\nexport type ValidUsagesFor<T> = InnerValidUsagesFor<T>['usage'];\n\nexport interface TgpuBuffer<TData extends BaseData> extends TgpuNamable {\n readonly [$internal]: true;\n readonly resourceType: 'buffer';\n readonly dataType: TData;\n readonly initial?: Infer<TData> | undefined;\n\n readonly buffer: GPUBuffer;\n readonly destroyed: boolean;\n\n usableAsUniform: boolean;\n usableAsStorage: boolean;\n usableAsVertex: boolean;\n usableAsIndex: boolean;\n\n $usage<\n T extends [\n Prettify<InnerValidUsagesFor<TData>>['usage'],\n ...Prettify<InnerValidUsagesFor<TData>>['usage'][],\n ],\n >(\n ...usages: T\n ): this & UnionToIntersection<LiteralToUsageType<T[number]>>;\n $addFlags(flags: GPUBufferUsageFlags): this;\n\n as<T extends ViewUsages<this>>(usage: T): UsageTypeToBufferUsage<TData>[T];\n\n compileWriter(): void;\n write(data: Infer<TData>): void;\n writePartial(data: InferPartial<TData>): void;\n clear(): void;\n copyFrom(srcBuffer: TgpuBuffer<MemIdentity<TData>>): void;\n read(): Promise<Infer<TData>>;\n destroy(): void;\n}\n\nexport function INTERNAL_createBuffer<TData extends AnyData>(\n group: ExperimentalTgpuRoot,\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n): TgpuBuffer<TData> {\n if (!isWgslData(typeSchema)) {\n return new TgpuBufferImpl(group, typeSchema, initialOrBuffer, [\n 'storage',\n 'uniform',\n ]);\n }\n\n return new TgpuBufferImpl(group, typeSchema, initialOrBuffer);\n}\n\nexport function isBuffer<T extends TgpuBuffer<AnyData>>(\n value: T | unknown,\n): value is T {\n return (value as TgpuBuffer<AnyData>).resourceType === 'buffer';\n}\n\nexport function isUsableAsVertex<T extends TgpuBuffer<AnyData>>(\n buffer: T,\n): buffer is T & VertexFlag {\n return !!(buffer as unknown as VertexFlag).usableAsVertex;\n}\n\nexport function isUsableAsIndex<T extends TgpuBuffer<AnyData>>(\n buffer: T,\n): buffer is T & IndexFlag {\n return !!(buffer as unknown as IndexFlag).usableAsIndex;\n}\n\n// --------------\n// Implementation\n// --------------\nconst endianness = getSystemEndianness();\n\nclass TgpuBufferImpl<TData extends AnyData> implements TgpuBuffer<TData> {\n public readonly [$internal] = true;\n public readonly resourceType = 'buffer';\n public flags: GPUBufferUsageFlags = GPUBufferUsage.COPY_DST |\n GPUBufferUsage.COPY_SRC;\n private _buffer: GPUBuffer | null = null;\n private _ownBuffer: boolean;\n private _destroyed = false;\n private _hostBuffer: ArrayBuffer | undefined;\n\n readonly initial: Infer<TData> | undefined;\n\n usableAsUniform = false;\n usableAsStorage = false;\n usableAsVertex = false;\n usableAsIndex = false;\n\n constructor(\n private readonly _group: ExperimentalTgpuRoot,\n public readonly dataType: TData,\n public readonly initialOrBuffer?: Infer<TData> | GPUBuffer | undefined,\n private readonly _disallowedUsages?:\n ('uniform' | 'storage' | 'vertex' | 'index')[],\n ) {\n if (isGPUBuffer(initialOrBuffer)) {\n this._ownBuffer = false;\n this._buffer = initialOrBuffer;\n } else {\n this._ownBuffer = true;\n this.initial = initialOrBuffer;\n }\n }\n\n get buffer() {\n const device = this._group.device;\n\n if (this._destroyed) {\n throw new Error('This buffer has been destroyed');\n }\n\n if (!this._buffer) {\n this._buffer = device.createBuffer({\n size: sizeOf(this.dataType),\n usage: this.flags,\n mappedAtCreation: !!this.initial,\n label: getName(this) ?? '<unnamed>',\n });\n\n if (this.initial) {\n this._writeToTarget(this._buffer.getMappedRange(), this.initial);\n this._buffer.unmap();\n }\n }\n\n return this._buffer;\n }\n\n get destroyed() {\n return this._destroyed;\n }\n\n $name(label: string) {\n setName(this, label);\n if (this._buffer) {\n this._buffer.label = label;\n }\n return this;\n }\n\n $usage<T extends ('uniform' | 'storage' | 'vertex' | 'index')[]>(\n ...usages: T\n ): this & UnionToIntersection<LiteralToUsageType<T[number]>> {\n for (const usage of usages) {\n if (this._disallowedUsages?.includes(usage)) {\n throw new Error(\n `Buffer of type ${this.dataType.type} cannot be used as ${usage}`,\n );\n }\n\n this.flags |= usage === 'uniform' ? GPUBufferUsage.UNIFORM : 0;\n this.flags |= usage === 'storage' ? GPUBufferUsage.STORAGE : 0;\n this.flags |= usage === 'vertex' ? GPUBufferUsage.VERTEX : 0;\n this.flags |= usage === 'index' ? GPUBufferUsage.INDEX : 0;\n this.usableAsUniform = this.usableAsUniform || usage === 'uniform';\n this.usableAsStorage = this.usableAsStorage || usage === 'storage';\n this.usableAsVertex = this.usableAsVertex || usage === 'vertex';\n this.usableAsIndex = this.usableAsIndex || usage === 'index';\n }\n return this as this & UnionToIntersection<LiteralToUsageType<T[number]>>;\n }\n\n $addFlags(flags: GPUBufferUsageFlags) {\n if (!this._ownBuffer) {\n throw new Error(\n 'Cannot add flags to a buffer that is not managed by TypeGPU.',\n );\n }\n\n if (flags & GPUBufferUsage.MAP_READ) {\n this.flags = GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ;\n return this;\n }\n\n if (flags & GPUBufferUsage.MAP_WRITE) {\n this.flags = GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE;\n return this;\n }\n\n this.flags |= flags;\n return this;\n }\n\n compileWriter(): void {\n getCompiledWriterForSchema(this.dataType);\n }\n\n private _writeToTarget(\n target: ArrayBuffer,\n data: Infer<TData>,\n ): void {\n const compiledWriter = getCompiledWriterForSchema(this.dataType);\n\n if (compiledWriter) {\n try {\n compiledWriter(\n new DataView(target),\n 0,\n data,\n endianness === 'little',\n );\n return;\n } catch (error) {\n console.error(\n `Error when using compiled writer for buffer ${\n getName(this) ?? '<unnamed>'\n } - this is likely a bug, please submit an issue at https://github.com/software-mansion/TypeGPU/issues\\nUsing fallback writer instead.`,\n error,\n );\n }\n }\n\n writeData(new BufferWriter(target), this.dataType, data);\n }\n\n write(data: Infer<TData>): void {\n const gpuBuffer = this.buffer;\n const device = this._group.device;\n\n if (gpuBuffer.mapState === 'mapped') {\n const mapped = gpuBuffer.getMappedRange();\n this._writeToTarget(mapped, data);\n return;\n }\n\n const size = sizeOf(this.dataType);\n if (!this._hostBuffer) {\n this._hostBuffer = new ArrayBuffer(size);\n }\n\n // Flushing any commands yet to be encoded.\n this._group.flush();\n\n this._writeToTarget(this._hostBuffer, data);\n device.queue.writeBuffer(gpuBuffer, 0, this._hostBuffer, 0, size);\n }\n\n public writePartial(data: InferPartial<TData>): void {\n const gpuBuffer = this.buffer;\n const device = this._group.device;\n\n const instructions = getWriteInstructions(this.dataType, data);\n\n if (gpuBuffer.mapState === 'mapped') {\n const mappedRange = gpuBuffer.getMappedRange();\n const mappedView = new Uint8Array(mappedRange);\n\n for (const instruction of instructions) {\n mappedView.set(instruction.data, instruction.data.byteOffset);\n }\n } else {\n for (const instruction of instructions) {\n device.queue.writeBuffer(\n gpuBuffer,\n instruction.data.byteOffset,\n instruction.data,\n 0,\n instruction.data.byteLength,\n );\n }\n }\n }\n\n public clear(): void {\n const gpuBuffer = this.buffer;\n const device = this._group.device;\n\n if (gpuBuffer.mapState === 'mapped') {\n new Uint8Array(gpuBuffer.getMappedRange()).fill(0);\n return;\n }\n\n // Flushing any commands yet to be encoded.\n this._group.flush();\n\n const encoder = device.createCommandEncoder();\n encoder.clearBuffer(gpuBuffer);\n device.queue.submit([encoder.finish()]);\n }\n\n copyFrom(srcBuffer: TgpuBuffer<MemIdentity<TData>>): void {\n if (this.buffer.mapState === 'mapped') {\n throw new Error('Cannot copy to a mapped buffer.');\n }\n\n const size = sizeOf(this.dataType);\n const encoder = this._group.commandEncoder;\n encoder.copyBufferToBuffer(srcBuffer.buffer, 0, this.buffer, 0, size);\n }\n\n async read(): Promise<Infer<TData>> {\n // Flushing any commands yet to be encoded.\n this._group.flush();\n\n const gpuBuffer = this.buffer;\n const device = this._group.device;\n\n if (gpuBuffer.mapState === 'mapped') {\n const mapped = gpuBuffer.getMappedRange();\n return readData(new BufferReader(mapped), this.dataType);\n }\n\n if (gpuBuffer.usage & GPUBufferUsage.MAP_READ) {\n await gpuBuffer.mapAsync(GPUMapMode.READ);\n const mapped = gpuBuffer.getMappedRange();\n const res = readData(new BufferReader(mapped), this.dataType);\n gpuBuffer.unmap();\n return res;\n }\n\n const stagingBuffer = device.createBuffer({\n size: sizeOf(this.dataType),\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n\n const commandEncoder = device.createCommandEncoder();\n commandEncoder.copyBufferToBuffer(\n gpuBuffer,\n 0,\n stagingBuffer,\n 0,\n sizeOf(this.dataType),\n );\n\n device.queue.submit([commandEncoder.finish()]);\n await device.queue.onSubmittedWorkDone();\n await stagingBuffer.mapAsync(GPUMapMode.READ, 0, sizeOf(this.dataType));\n\n const res = readData(\n new BufferReader(stagingBuffer.getMappedRange()),\n this.dataType,\n );\n\n stagingBuffer.unmap();\n stagingBuffer.destroy();\n\n return res;\n }\n\n as<T extends ViewUsages<this>>(usage: T): UsageTypeToBufferUsage<TData>[T] {\n return usageToUsageConstructor[usage]?.(\n this as never,\n ) as UsageTypeToBufferUsage<TData>[T];\n }\n\n destroy() {\n if (this._destroyed) {\n return;\n }\n this._destroyed = true;\n if (this._ownBuffer) {\n this._buffer?.destroy();\n }\n }\n\n toString(): string {\n return `buffer:${getName(this) ?? '<unnamed>'}`;\n }\n}\n","import {\n getDeviceTextureFormatInfo,\n textureFormats,\n} from './textureFormats.ts';\nimport type { ExternalImageSource } from './texture.ts';\n\nexport function getImageSourceDimensions(\n source: ExternalImageSource,\n): { width: number; height: number } {\n if ('displayWidth' in source && 'displayHeight' in source) {\n return { width: source.displayWidth, height: source.displayHeight };\n }\n return { width: source.width as number, height: source.height as number };\n}\n\ntype CachedResources = {\n vertexShader: GPUShaderModule;\n fragmentShader: GPUShaderModule;\n bindGroupLayout: GPUBindGroupLayout;\n pipelineLayout: GPUPipelineLayout;\n sampler: GPUSampler;\n};\n\nconst deviceResourceCache = new WeakMap<\n GPUDevice,\n Map<string, CachedResources>\n>();\n\nfunction getDeviceCache(device: GPUDevice): Map<string, CachedResources> {\n let cache = deviceResourceCache.get(device);\n if (!cache) {\n cache = new Map<string, CachedResources>();\n deviceResourceCache.set(device, cache);\n }\n return cache;\n}\n\nexport function clearTextureUtilsCache(device: GPUDevice): void {\n const cache = deviceResourceCache.get(device);\n if (cache) {\n cache.clear();\n }\n}\n\nexport function generateTextureMipmaps(\n device: GPUDevice,\n texture: GPUTexture,\n baseMipLevel = 0,\n mipLevels?: number,\n) {\n if (texture.dimension !== '2d') {\n throw new Error(\n 'Cannot generate mipmaps for non-2D textures: only 2D textures are currently supported.',\n );\n }\n\n const actualMipLevels = mipLevels ?? (texture.mipLevelCount - baseMipLevel);\n const formatInfo = getDeviceTextureFormatInfo(texture.format, device);\n\n const hasFloatSampleType = [...formatInfo.sampleTypes].some((type) =>\n type === 'float' || type === 'unfilterable-float'\n );\n\n if (!hasFloatSampleType) {\n throw new Error(\n `Cannot generate mipmaps for format '${texture.format}': only float and unfilterable-float formats are currently supported.`,\n );\n }\n\n if (!formatInfo.canRenderAttachment) {\n throw new Error(\n `Cannot generate mipmaps for format '${texture.format}': format does not support render attachments.`,\n );\n }\n\n // Generate mipmaps for all layers\n for (let layer = 0; layer < texture.depthOrArrayLayers; layer++) {\n for (\n let mip = baseMipLevel;\n mip < baseMipLevel + actualMipLevels - 1;\n mip++\n ) {\n const srcMipLevel = mip;\n const dstMipLevel = mip + 1;\n\n generateMipmapLevel(device, texture, srcMipLevel, dstMipLevel, layer);\n }\n }\n}\n\nexport function resampleImage(\n device: GPUDevice,\n targetTexture: GPUTexture,\n image: ExternalImageSource,\n layer?: number,\n) {\n if (targetTexture.dimension === '3d') {\n throw new Error(\n 'Cannot resample to 3D textures: only 2D textures are currently supported.',\n );\n }\n\n const formatInfo = textureFormats[targetTexture.format];\n\n const hasFloatSampleType = [...formatInfo.sampleTypes].some((type) =>\n type === 'float' || type === 'unfilterable-float'\n );\n\n if (!hasFloatSampleType) {\n throw new Error(\n `Cannot resample to format '${targetTexture.format}': only float and unfilterable-float formats are currently supported.`,\n );\n }\n\n if (!formatInfo.canRenderAttachment) {\n throw new Error(\n `Cannot resample to format '${targetTexture.format}': format does not support render attachments.`,\n );\n }\n\n return resampleWithRenderPipeline(device, targetTexture, image, layer);\n}\n\nfunction resampleWithRenderPipeline(\n device: GPUDevice,\n targetTexture: GPUTexture,\n image: ExternalImageSource,\n layer = 0,\n) {\n const formatInfo = textureFormats[targetTexture.format];\n const canFilter = [...formatInfo.sampleTypes].includes('float');\n\n const cacheKey = `${canFilter ? 'filterable' : 'unfilterable'}`;\n\n const deviceCache = getDeviceCache(device);\n let cached = deviceCache.get(cacheKey);\n if (!cached) {\n const vertexShader = device.createShaderModule({\n code: `\nstruct VertexOutput {\n @builtin(position) pos: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n let pos = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));\n let uv = array<vec2f, 3>(vec2f(0, 1), vec2f(2, 1), vec2f(0, -1));\n\n var output: VertexOutput;\n output.pos = vec4f(pos[vertexIndex], 0, 1);\n output.uv = uv[vertexIndex];\n return output;\n}\n `,\n });\n\n const sampler = device.createSampler({\n magFilter: canFilter ? 'linear' : 'nearest',\n minFilter: canFilter ? 'linear' : 'nearest',\n });\n\n const bindGroupLayout = device.createBindGroupLayout({\n entries: [\n {\n binding: 0,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: 'float',\n },\n },\n {\n binding: 1,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: {\n type: canFilter ? 'filtering' : 'non-filtering',\n },\n },\n ],\n });\n\n const pipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [bindGroupLayout],\n });\n\n const fragmentShader = device.createShaderModule({\n code: `\n@group(0) @binding(0) var inputTexture: texture_2d<f32>;\n@group(0) @binding(1) var inputSampler: sampler;\n\n@fragment\nfn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {\n ${\n canFilter\n ? 'return textureSample(inputTexture, inputSampler, uv);'\n : `let texelCoord = vec2u(uv * vec2f(textureDimensions(inputTexture)));\n return textureLoad(inputTexture, texelCoord, 0);`\n }\n}\n `,\n });\n\n cached = {\n vertexShader,\n fragmentShader,\n bindGroupLayout,\n pipelineLayout,\n sampler,\n };\n deviceCache.set(cacheKey, cached);\n }\n\n const inputTexture = device.createTexture({\n size: [...Object.values(getImageSourceDimensions(image))],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT |\n GPUTextureUsage.COPY_DST,\n });\n\n const { width, height } = getImageSourceDimensions(image);\n device.queue.copyExternalImageToTexture(\n { source: image },\n { texture: inputTexture },\n [width, height, 1],\n );\n\n const renderTexture = device.createTexture({\n size: [targetTexture.width, targetTexture.height, 1],\n format: targetTexture.format,\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,\n });\n\n const pipeline = device.createRenderPipeline({\n layout: cached.pipelineLayout,\n vertex: {\n module: cached.vertexShader,\n },\n fragment: {\n module: cached.fragmentShader,\n targets: [\n {\n format: targetTexture.format,\n },\n ],\n },\n primitive: {\n topology: 'triangle-list',\n },\n });\n\n const bindGroup = device.createBindGroup({\n layout: cached.bindGroupLayout,\n entries: [\n {\n binding: 0,\n resource: inputTexture.createView(),\n },\n {\n binding: 1,\n resource: cached.sampler,\n },\n ],\n });\n\n const encoder = device.createCommandEncoder();\n const renderPass = encoder.beginRenderPass({\n colorAttachments: [\n {\n view: renderTexture.createView(),\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n });\n\n renderPass.setPipeline(pipeline);\n renderPass.setBindGroup(0, bindGroup);\n renderPass.draw(3);\n renderPass.end();\n\n encoder.copyTextureToTexture(\n { texture: renderTexture },\n {\n texture: targetTexture,\n origin: { x: 0, y: 0, z: layer },\n },\n {\n width: targetTexture.width,\n height: targetTexture.height,\n depthOrArrayLayers: 1,\n },\n );\n\n device.queue.submit([encoder.finish()]);\n\n inputTexture.destroy();\n renderTexture.destroy();\n}\n\nfunction generateMipmapLevel(\n device: GPUDevice,\n texture: GPUTexture,\n srcMipLevel: number,\n dstMipLevel: number,\n layer?: number,\n) {\n const formatInfo = getDeviceTextureFormatInfo(texture.format, device);\n const canFilter = [...formatInfo.sampleTypes].includes('float');\n\n const cacheKey = `${canFilter ? 'filterable' : 'unfilterable'}`;\n\n const deviceCache = getDeviceCache(device);\n let cached = deviceCache.get(cacheKey);\n if (!cached) {\n const vertexShader = device.createShaderModule({\n code: `\nstruct VertexOutput {\n @builtin(position) pos: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n let pos = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));\n let uv = array<vec2f, 3>(vec2f(0, 1), vec2f(2, 1), vec2f(0, -1));\n\n var output: VertexOutput;\n output.pos = vec4f(pos[vertexIndex], 0, 1);\n output.uv = uv[vertexIndex];\n return output;\n}\n `,\n });\n\n const fragmentShader = device.createShaderModule({\n code: `\n@group(0) @binding(0) var inputTexture: texture_2d<f32>;\n@group(0) @binding(1) var inputSampler: sampler;\n\n@fragment\nfn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {\n return textureSample(inputTexture, inputSampler, uv);\n}\n `,\n });\n\n const sampler = device.createSampler({\n magFilter: canFilter ? 'linear' : 'nearest',\n minFilter: canFilter ? 'linear' : 'nearest',\n });\n\n const bindGroupLayout = device.createBindGroupLayout({\n entries: [\n {\n binding: 0,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: canFilter ? 'float' : 'unfilterable-float',\n },\n },\n {\n binding: 1,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: {\n type: canFilter ? 'filtering' : 'non-filtering',\n },\n },\n ],\n });\n\n const pipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [bindGroupLayout],\n });\n\n cached = {\n vertexShader,\n fragmentShader,\n bindGroupLayout,\n pipelineLayout,\n sampler,\n };\n deviceCache.set(cacheKey, cached);\n }\n\n const pipeline = device.createRenderPipeline({\n layout: cached.pipelineLayout,\n vertex: {\n module: cached.vertexShader,\n },\n fragment: {\n module: cached.fragmentShader,\n targets: [\n {\n format: texture.format,\n },\n ],\n },\n primitive: {\n topology: 'triangle-list',\n },\n });\n\n const srcTextureView = texture.createView({\n baseMipLevel: srcMipLevel,\n dimension: '2d',\n mipLevelCount: 1,\n ...(layer !== undefined && {\n baseArrayLayer: layer,\n arrayLayerCount: 1,\n }),\n });\n\n const dstTextureView = texture.createView({\n baseMipLevel: dstMipLevel,\n dimension: '2d',\n mipLevelCount: 1,\n ...(layer !== undefined && {\n baseArrayLayer: layer,\n arrayLayerCount: 1,\n }),\n });\n\n const bindGroup = device.createBindGroup({\n layout: cached.bindGroupLayout,\n entries: [\n {\n binding: 0,\n resource: srcTextureView,\n },\n {\n binding: 1,\n resource: cached.sampler,\n },\n ],\n });\n\n const encoder = device.createCommandEncoder();\n const renderPass = encoder.beginRenderPass({\n colorAttachments: [\n {\n view: dstTextureView,\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n });\n\n renderPass.setPipeline(pipeline);\n renderPass.setBindGroup(0, bindGroup);\n renderPass.draw(3);\n renderPass.end();\n\n device.queue.submit([encoder.finish()]);\n}\n","import {\n isWgslStorageTexture,\n textureDescriptorToSchema,\n type TextureSchemaForDescriptor,\n type WgslStorageTexture,\n type WgslTexture,\n type WgslTextureProps,\n} from '../../data/texture.ts';\nimport { inCodegenMode } from '../../execMode.ts';\nimport { type ResolvedSnippet, snip } from '../../data/snippet.ts';\nimport type { Vec4f, Vec4i, Vec4u } from '../../data/wgslTypes.ts';\nimport type { TgpuNamable } from '../../shared/meta.ts';\nimport { getName, setName } from '../../shared/meta.ts';\nimport type { Infer, ValidateTextureViewSchema } from '../../shared/repr.ts';\nimport type {\n TextureFormatInfo,\n ViewDimensionToDimension,\n} from './textureFormats.ts';\nimport {\n $gpuValueOf,\n $internal,\n $ownSnippet,\n $repr,\n $resolve,\n} from '../../shared/symbols.ts';\nimport type {\n Default,\n TypedArray,\n UnionToIntersection,\n} from '../../shared/utilityTypes.ts';\nimport type { LayoutMembership } from '../../tgpuBindGroupLayout.ts';\nimport type { ResolutionCtx, SelfResolvable } from '../../types.ts';\nimport type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';\nimport { valueProxyHandler } from '../valueProxyUtils.ts';\nimport { type TextureFormats, textureFormats } from './textureFormats.ts';\nimport type { TextureProps } from './textureProps.ts';\nimport type { AllowedUsages, LiteralToExtensionMap } from './usageExtension.ts';\nimport {\n generateTextureMipmaps,\n getImageSourceDimensions,\n resampleImage,\n} from './textureUtils.ts';\n\ntype TextureInternals = {\n unwrap(): GPUTexture;\n};\n\ntype TextureViewInternals = {\n readonly unwrap: (() => GPUTextureView) | undefined;\n};\n\n// ----------\n// Public API\n// ----------\n\nexport type TexelData = Vec4u | Vec4i | Vec4f;\n\nexport type ExternalImageSource =\n | HTMLCanvasElement\n | HTMLImageElement\n | HTMLVideoElement\n | ImageBitmap\n | ImageData\n | OffscreenCanvas\n | VideoFrame;\n\ntype TgpuTextureViewDescriptor = {\n /**\n * Which {@link GPUTextureAspect | aspect(s)} of the texture are accessible to the texture view.\n */\n aspect?: GPUTextureAspect;\n /**\n * The first (most detailed) mipmap level accessible to the texture view.\n */\n baseMipLevel?: GPUIntegerCoordinate;\n /**\n * How many mipmap levels, starting with {@link GPUTextureViewDescriptor#baseMipLevel}, are accessible to\n * the texture view.\n */\n mipLevelCount?: GPUIntegerCoordinate;\n /**\n * The index of the first array layer accessible to the texture view.\n */\n baseArrayLayer?: GPUIntegerCoordinate;\n /**\n * How many array layers, starting with {@link GPUTextureViewDescriptor#baseArrayLayer}, are accessible\n * to the texture view.\n */\n arrayLayerCount?: GPUIntegerCoordinate;\n /**\n * The format of the texture view. Must be either the {@link GPUTextureDescriptor#format} of the\n * texture or one of the {@link GPUTextureDescriptor#viewFormats} specified during its creation.\n */\n format?: GPUTextureFormat;\n};\n\ntype DefaultViewSchema<T extends Partial<TextureProps>> =\n TextureSchemaForDescriptor<{\n dimension: Default<T['dimension'], '2d'>;\n sampleType: T['format'] extends keyof TextureFormats\n ? TextureFormats[T['format']]['channelType']\n : TextureFormats[keyof TextureFormats]['channelType'];\n multisampled: Default<T['sampleCount'], 1> extends 1 ? false : true;\n }>;\n\ntype BaseDimension<T extends string> = T extends keyof ViewDimensionToDimension\n ? ViewDimensionToDimension[T]\n : never;\n\ntype OptionalDimension<T extends string> = T extends\n | '2d'\n | '2d-array'\n | 'cube'\n | 'cube-array' ? { dimension?: BaseDimension<T> }\n : { dimension: BaseDimension<T> };\n\ntype MultisampledProps<T extends WgslTexture> = T['multisampled'] extends true\n ? OptionalDimension<T['dimension']> & { sampleCount: 4 }\n : OptionalDimension<T['dimension']> & { sampleCount?: 1 };\n\nexport type PropsForSchema<T extends WgslTexture | WgslStorageTexture> =\n T extends WgslTexture ? {\n size: readonly number[];\n format: GPUTextureFormat;\n } & MultisampledProps<T>\n : T extends WgslStorageTexture ? {\n size: readonly number[];\n format: T['format'];\n } & OptionalDimension<T['dimension']>\n : never;\n\nfunction getDescriptorForProps<T extends TextureProps>(\n props: T,\n): WgslTextureProps {\n return {\n dimension: (props.dimension ?? '2d') as Default<T['dimension'], '2d'>,\n sampleType: textureFormats[props.format].channelType,\n multisampled: !((props.sampleCount ?? 1) === 1) as Default<\n T['sampleCount'],\n 1\n > extends 1 ? false\n : true,\n };\n}\n\nexport type TextureAspect = 'color' | 'depth' | 'stencil';\nexport type DepthStencilFormats =\n | 'depth24plus-stencil8'\n | 'depth32float-stencil8';\nexport type DepthFormats = 'depth16unorm' | 'depth24plus' | 'depth32float';\nexport type StencilFormats = 'stencil8';\n\nexport type AspectsForFormat<T extends GPUTextureFormat> =\n GPUTextureFormat extends T ? TextureAspect[]\n : T extends DepthStencilFormats ? ('depth' | 'stencil')[]\n : T extends DepthFormats ? 'depth'[]\n : T extends StencilFormats ? 'stencil'[]\n : 'color'[];\n\nfunction getAspectsForFormat<T extends GPUTextureFormat>(\n format: T,\n): AspectsForFormat<T> {\n if (format === 'depth24plus-stencil8' || format === 'depth32float-stencil8') {\n return ['depth', 'stencil'] as AspectsForFormat<T>;\n }\n if (\n format === 'depth16unorm' ||\n format === 'depth24plus' ||\n format === 'depth32float'\n ) {\n return ['depth'] as AspectsForFormat<T>;\n }\n if (format === 'stencil8') {\n return ['stencil'] as AspectsForFormat<T>;\n }\n return ['color'] as AspectsForFormat<T>;\n}\n\ntype CopyCompatibleTexture<T extends TextureProps> = TgpuTexture<{\n size: T['size'];\n format: T['format'];\n sampleCount?: T['sampleCount'];\n}>;\n\n/**\n * @param TProps all properties that distinguish this texture apart from other textures on the type level.\n */\nexport interface TgpuTexture<TProps extends TextureProps = TextureProps>\n extends TgpuNamable {\n readonly [$internal]: TextureInternals;\n readonly resourceType: 'texture';\n readonly props: TProps; // <- storing to be able to differentiate structurally between different textures.\n readonly aspects: AspectsForFormat<TProps['format']>;\n readonly destroyed: boolean;\n\n // Extensions\n readonly usableAsStorage: boolean;\n readonly usableAsSampled: boolean;\n readonly usableAsRender: boolean;\n\n $usage<T extends AllowedUsages<TProps>[]>(\n ...usages: T\n ): this & UnionToIntersection<LiteralToExtensionMap[T[number]]>;\n\n createView(\n ...args: this['usableAsSampled'] extends true ? []\n : [ValidateTextureViewSchema<this, WgslTexture>]\n ): TgpuTextureView<DefaultViewSchema<TProps>>;\n createView<T extends WgslTexture | WgslStorageTexture>(\n schema: ValidateTextureViewSchema<this, T>,\n viewDescriptor?: TgpuTextureViewDescriptor & {\n sampleType?: T extends WgslTexture\n ? T['sampleType']['type'] extends 'f32' ? 'float' | 'unfilterable-float'\n : never\n : never;\n },\n ): TgpuTextureView<T>;\n\n clear(mipLevel?: number | 'all'): void;\n generateMipmaps(baseMipLevel?: number, mipLevels?: number): void;\n write(source: ExternalImageSource | ExternalImageSource[]): void;\n write(source: ArrayBuffer | TypedArray | DataView, mipLevel?: number): void;\n // TODO: support copies from GPUBuffers and TgpuBuffers\n copyFrom<T extends CopyCompatibleTexture<TProps>>(source: T): void;\n\n destroy(): void;\n}\n\nexport interface TgpuTextureView<\n TSchema extends WgslStorageTexture | WgslTexture =\n | WgslStorageTexture\n | WgslTexture,\n> {\n readonly [$internal]: TextureViewInternals;\n readonly resourceType: 'texture-view';\n readonly schema: TSchema;\n\n readonly [$gpuValueOf]: Infer<TSchema>;\n value: Infer<TSchema>;\n $: Infer<TSchema>;\n}\n\nexport function INTERNAL_createTexture(\n props: TextureProps,\n branch: ExperimentalTgpuRoot,\n): TgpuTexture<TextureProps> {\n return new TgpuTextureImpl(props, branch);\n}\n\nexport function isTexture<T extends TgpuTexture>(\n value: unknown | T,\n): value is T {\n return (value as T)?.resourceType === 'texture' && !!(value as T)[$internal];\n}\n\nexport function isTextureView<T extends TgpuTextureView>(\n value: unknown | T,\n): value is T {\n return (\n (value as T)?.resourceType === 'texture-view' && !!(value as T)[$internal]\n );\n}\n\n// --------------\n// Implementation\n// --------------\n\nclass TgpuTextureImpl<TProps extends TextureProps>\n implements TgpuTexture<TProps> {\n readonly [$internal]: TextureInternals;\n readonly resourceType = 'texture';\n readonly aspects: AspectsForFormat<this['props']['format']>;\n usableAsSampled = false;\n usableAsStorage = false;\n usableAsRender = false;\n\n #formatInfo: TextureFormatInfo;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: wdym, it is used 10 lines below\n #byteSize: number;\n #destroyed = false;\n #flags = GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC;\n #texture: GPUTexture | null = null;\n #branch: ExperimentalTgpuRoot;\n\n constructor(\n public readonly props: TProps,\n branch: ExperimentalTgpuRoot,\n ) {\n const format = props.format as TProps['format'];\n\n this.#branch = branch;\n this.#formatInfo = textureFormats[format];\n this.#byteSize = (props.size[0] as number) *\n (props.size[1] ?? 1) *\n (props.size[2] ?? 1) *\n this.#formatInfo.texelSize;\n this.aspects = getAspectsForFormat(format);\n\n this[$internal] = {\n unwrap: () => {\n if (this.#destroyed) {\n throw new Error('This texture has been destroyed');\n }\n\n if (!this.#texture) {\n this.#texture = branch.device.createTexture({\n label: getName(this) ?? '<unnamed>',\n format: props.format,\n size: props.size,\n usage: this.#flags,\n dimension: props.dimension ?? '2d',\n viewFormats: props.viewFormats ?? [],\n mipLevelCount: props.mipLevelCount ?? 1,\n sampleCount: props.sampleCount ?? 1,\n });\n }\n\n return this.#texture;\n },\n };\n }\n\n $name(label: string) {\n setName(this, label);\n return this;\n }\n\n $usage<T extends ('sampled' | 'storage' | 'render')[]>(\n ...usages: T\n ): this & UnionToIntersection<LiteralToExtensionMap[T[number]]> {\n const hasStorage = usages.includes('storage');\n const hasSampled = usages.includes('sampled');\n const hasRender = usages.includes('render');\n this.#flags |= hasSampled ? GPUTextureUsage.TEXTURE_BINDING : 0;\n this.#flags |= hasStorage ? GPUTextureUsage.STORAGE_BINDING : 0;\n this.#flags |= hasRender ? GPUTextureUsage.RENDER_ATTACHMENT : 0;\n this.usableAsStorage ||= hasStorage;\n this.usableAsSampled ||= hasSampled;\n this.usableAsRender ||= hasRender;\n\n return this as this & UnionToIntersection<LiteralToExtensionMap[T[number]]>;\n }\n\n createView(\n ...args: this['usableAsSampled'] extends true ? [] : [never]\n ): TgpuTextureView<DefaultViewSchema<TProps>>;\n createView<T extends WgslTexture | WgslStorageTexture>(\n schema: never,\n viewDescriptor?: TgpuTextureViewDescriptor & {\n sampleType?: T extends WgslTexture\n ? T['sampleType']['type'] extends 'f32' ? 'float' | 'unfilterable-float'\n : never\n : never;\n },\n ): TgpuTextureView<T>;\n createView<T extends WgslTexture | WgslStorageTexture>(\n schema?: never,\n viewDescriptor?: TgpuTextureViewDescriptor & {\n sampleType?: T extends WgslTexture\n ? T['sampleType']['type'] extends 'f32' ? 'float' | 'unfilterable-float'\n : never\n : never;\n },\n ): TgpuTextureView<T> {\n return new TgpuFixedTextureViewImpl(\n schema ??\n (textureDescriptorToSchema(getDescriptorForProps(this.props)) as T),\n this as TgpuTexture,\n viewDescriptor,\n );\n }\n\n #clearMipLevel(mip = 0) {\n const scale = 2 ** mip;\n const [width, height, depth] = [\n Math.max(1, Math.floor((this.props.size[0] ?? 1) / scale)),\n Math.max(1, Math.floor((this.props.size[1] ?? 1) / scale)),\n Math.max(1, Math.floor((this.props.size[2] ?? 1) / scale)),\n ];\n\n this.#branch.device.queue.writeTexture(\n { texture: this[$internal].unwrap(), mipLevel: mip },\n new Uint8Array(width * height * depth * this.#formatInfo.texelSize),\n { bytesPerRow: this.#formatInfo.texelSize * width, rowsPerImage: height },\n [width, height, depth],\n );\n }\n\n clear(mipLevel: number | 'all' = 'all') {\n if (mipLevel === 'all') {\n const mipLevels = this.props.mipLevelCount ?? 1;\n for (let i = 0; i < mipLevels; i++) {\n this.#clearMipLevel(i);\n }\n } else {\n this.#clearMipLevel(mipLevel);\n }\n }\n\n generateMipmaps(baseMipLevel = 0, mipLevels?: number) {\n if (this.usableAsRender === false) {\n throw new Error(\n \"generateMipmaps called without specifying 'render' usage. Add it via the $usage('render') method.\",\n );\n }\n\n const actualMipLevels = mipLevels ??\n (this.props.mipLevelCount ?? 1) - baseMipLevel;\n\n if (actualMipLevels <= 1) {\n console.warn(\n `generateMipmaps is a no-op: would generate ${actualMipLevels} mip levels (base: ${baseMipLevel}, total: ${\n this.props.mipLevelCount ?? 1\n })`,\n );\n return;\n }\n\n if (baseMipLevel >= (this.props.mipLevelCount ?? 1)) {\n throw new Error(\n `Base mip level ${baseMipLevel} is out of range. Texture has ${\n this.props.mipLevelCount ?? 1\n } mip levels.`,\n );\n }\n\n generateTextureMipmaps(\n this.#branch.device,\n this[$internal].unwrap(),\n baseMipLevel,\n actualMipLevels,\n );\n }\n\n write(source: ExternalImageSource | ExternalImageSource[]): void;\n write(source: ArrayBuffer | TypedArray | DataView, mipLevel?: number): void;\n write(\n source:\n | ExternalImageSource\n | ExternalImageSource[]\n | ArrayBuffer\n | TypedArray\n | DataView,\n mipLevel = 0,\n ) {\n if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {\n this.#writeBufferData(source, mipLevel);\n return;\n }\n\n const dimension = this.props.dimension ?? '2d';\n const isArray = Array.isArray(source);\n\n if (!isArray) {\n this.#writeSingleLayer(source, dimension === '3d' ? 0 : undefined);\n return;\n }\n\n const layerCount = this.props.size[2] ?? 1;\n if (source.length > layerCount) {\n console.warn(\n `Too many image sources provided. Expected ${layerCount} layers, got ${source.length}. Extra sources will be ignored.`,\n );\n }\n\n for (let layer = 0; layer < Math.min(source.length, layerCount); layer++) {\n const bitmap = source[layer];\n if (bitmap) {\n this.#writeSingleLayer(bitmap, layer);\n }\n }\n }\n\n #writeBufferData(\n source: ArrayBuffer | TypedArray | DataView,\n mipLevel: number,\n ) {\n const mipWidth = Math.max(1, (this.props.size[0] as number) >> mipLevel);\n const mipHeight = Math.max(1, (this.props.size[1] ?? 1) >> mipLevel);\n const mipDepth = Math.max(1, (this.props.size[2] ?? 1) >> mipLevel);\n\n const expectedSize = mipWidth * mipHeight * mipDepth *\n this.#formatInfo.texelSize;\n const actualSize = source.byteLength ?? (source as ArrayBuffer).byteLength;\n\n if (actualSize !== expectedSize) {\n throw new Error(\n `Buffer size mismatch. Expected ${expectedSize} bytes for mip level ${mipLevel}, got ${actualSize} bytes.`,\n );\n }\n\n this.#branch.device.queue.writeTexture(\n {\n texture: this[$internal].unwrap(),\n mipLevel,\n },\n source,\n {\n bytesPerRow: this.#formatInfo.texelSize * mipWidth,\n rowsPerImage: mipHeight,\n },\n [mipWidth, mipHeight, mipDepth],\n );\n }\n\n #writeSingleLayer(source: ExternalImageSource, layer?: number) {\n const targetWidth = this.props.size[0] as number;\n const targetHeight = (this.props.size[1] ?? 1) as number;\n const { width: sourceWidth, height: sourceHeight } =\n getImageSourceDimensions(source);\n const needsResampling = sourceWidth !== targetWidth ||\n sourceHeight !== targetHeight;\n\n if (needsResampling) {\n resampleImage(\n this.#branch.device,\n this[$internal].unwrap(),\n source,\n layer,\n );\n return;\n }\n\n this.#branch.device.queue.copyExternalImageToTexture(\n { source },\n {\n texture: this[$internal].unwrap(),\n ...(layer !== undefined && { origin: { x: 0, y: 0, z: layer } }),\n },\n layer !== undefined ? [targetWidth, targetHeight, 1] : this.props.size,\n );\n }\n\n copyFrom(source: CopyCompatibleTexture<TProps>) {\n if (source.props.format !== this.props.format) {\n throw new Error(\n `Texture format mismatch. Source texture has format ${source.props.format}, target texture has format ${this.props.format}`,\n );\n }\n if (\n source.props.size[0] !== this.props.size[0] ||\n (source.props.size[1] ?? 1) !== (this.props.size[1] ?? 1) ||\n (source.props.size[2] ?? 1) !== (this.props.size[2] ?? 1)\n ) {\n throw new Error(\n `Texture size mismatch. Source texture has size ${\n source.props.size.join(\n 'x',\n )\n }, target texture has size ${this.props.size.join('x')}`,\n );\n }\n\n const commandEncoder = this.#branch.device.createCommandEncoder();\n commandEncoder.copyTextureToTexture(\n { texture: source[$internal].unwrap() },\n { texture: this[$internal].unwrap() },\n source.props.size,\n );\n this.#branch.device.queue.submit([commandEncoder.finish()]);\n }\n\n get destroyed() {\n return this.#destroyed;\n }\n\n destroy() {\n if (this.#destroyed) {\n return;\n }\n this.#destroyed = true;\n this.#texture?.destroy();\n }\n}\n\nclass TgpuFixedTextureViewImpl<T extends WgslTexture | WgslStorageTexture>\n implements TgpuTextureView<T>, SelfResolvable, TgpuNamable {\n /** Type-token, not available at runtime */\n declare readonly [$repr]: Infer<T>;\n readonly [$internal]: TextureViewInternals;\n readonly resourceType = 'texture-view' as const;\n\n #baseTexture: TgpuTexture;\n #view: GPUTextureView | undefined;\n #descriptor:\n | (TgpuTextureViewDescriptor & {\n sampleType?: T extends WgslTexture ? 'float' | 'unfilterable-float'\n : never;\n })\n | undefined;\n\n constructor(\n readonly schema: T,\n baseTexture: TgpuTexture,\n descriptor?: TgpuTextureViewDescriptor,\n ) {\n this.#baseTexture = baseTexture;\n this.#descriptor = descriptor;\n\n this[$internal] = {\n unwrap: () => {\n if (!this.#view) {\n const schema = this.schema;\n let descriptor: GPUTextureViewDescriptor;\n if (isWgslStorageTexture(schema)) {\n descriptor = {\n label: getName(this) ?? '<unnamed>',\n format: this.#descriptor?.format ?? schema.format,\n dimension: schema.dimension,\n };\n } else {\n descriptor = {\n label: getName(this) ?? '<unnamed>',\n format: this.#descriptor?.format ??\n this.#baseTexture.props.format,\n dimension: schema.dimension,\n };\n }\n\n if (this.#descriptor?.mipLevelCount !== undefined) {\n descriptor.mipLevelCount = this.#descriptor.mipLevelCount;\n }\n if (this.#descriptor?.arrayLayerCount !== undefined) {\n descriptor.arrayLayerCount = this.#descriptor.arrayLayerCount;\n }\n\n this.#view = this.#baseTexture[$internal]\n .unwrap()\n .createView(descriptor);\n }\n return this.#view;\n },\n };\n }\n\n $name(label: string) {\n setName(this, label);\n if (this.#view) {\n this.#view.label = label;\n }\n return this;\n }\n\n get [$gpuValueOf](): Infer<T> {\n const schema = this.schema;\n\n return new Proxy(\n {\n [$internal]: true,\n get [$ownSnippet]() {\n return snip(this, schema);\n },\n [$resolve]: (ctx) => ctx.resolve(this),\n toString: () => `${this.toString()}.$`,\n },\n valueProxyHandler,\n ) as unknown as Infer<T>;\n }\n\n get $(): Infer<T> {\n if (inCodegenMode()) {\n return this[$gpuValueOf];\n }\n\n throw new Error(\n 'Direct access to texture view values is possible only as part of a compute dispatch or draw call. Try .read() or .write() instead',\n );\n }\n\n get value(): Infer<T> {\n return this.$;\n }\n\n toString() {\n return `textureView:${getName(this) ?? '<unnamed>'}`;\n }\n\n [$resolve](ctx: ResolutionCtx): ResolvedSnippet {\n const id = ctx.getUniqueName(this);\n const { group, binding } = ctx.allocateFixedEntry(\n isWgslStorageTexture(this.schema)\n ? {\n storageTexture: this.schema,\n }\n : {\n texture: this.schema,\n sampleType: this.#descriptor?.sampleType ??\n this.schema.bindingSampleType[0],\n },\n this,\n );\n\n ctx.addDeclaration(\n `@group(${group}) @binding(${binding}) var ${id}: ${\n ctx.resolve(this.schema).value\n };`,\n );\n\n return snip(id, this.schema);\n }\n}\n\nexport class TgpuLaidOutTextureViewImpl<\n T extends WgslTexture | WgslStorageTexture,\n> implements TgpuTextureView<T>, SelfResolvable {\n /** Type-token, not available at runtime */\n declare readonly [$repr]: Infer<T>;\n readonly [$internal] = { unwrap: undefined };\n readonly resourceType = 'texture-view' as const;\n readonly #membership: LayoutMembership;\n\n constructor(\n readonly schema: T,\n membership: LayoutMembership,\n ) {\n this.#membership = membership;\n setName(this, membership.key);\n }\n\n toString() {\n return `textureView:${getName(this) ?? '<unnamed>'}`;\n }\n\n [$resolve](ctx: ResolutionCtx): ResolvedSnippet {\n const id = ctx.getUniqueName(this);\n const group = ctx.allocateLayoutEntry(this.#membership.layout);\n\n ctx.addDeclaration(\n `@group(${group}) @binding(${this.#membership.idx}) var ${id}: ${\n ctx.resolve(this.schema).value\n };`,\n );\n\n return snip(id, this.schema);\n }\n\n get [$gpuValueOf](): Infer<T> {\n const schema = this.schema;\n return new Proxy(\n {\n [$internal]: true,\n get [$ownSnippet]() {\n return snip(this, schema);\n },\n [$resolve]: (ctx) => ctx.resolve(this),\n toString: () => `${this.toString()}.$`,\n },\n valueProxyHandler,\n ) as unknown as Infer<T>;\n }\n\n get $(): Infer<T> {\n if (inCodegenMode()) {\n return this[$gpuValueOf];\n }\n\n throw new Error(\n 'Direct access to texture views values is possible only as part of a compute dispatch or draw call. Try .read() or .write() instead',\n );\n }\n\n get value(): Infer<T> {\n return this.$;\n }\n}\n","import type { TgpuMutable } from '../../core/buffer/bufferShorthand.ts';\nimport { fn, type TgpuFn } from '../../core/function/tgpuFn.ts';\nimport { slot } from '../../core/slot/slot.ts';\nimport { privateVar } from '../../core/variable/tgpuVariable.ts';\nimport { mat2x2f, mat3x3f, mat4x4f } from '../../data/matrix.ts';\nimport { bool, f16, f32, i32, u32 } from '../../data/numeric.ts';\nimport { sizeOf } from '../../data/sizeOf.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} from '../../data/vector.ts';\nimport {\n type AnyWgslData,\n type Atomic,\n isWgslArray,\n isWgslStruct,\n type U32,\n type Void,\n type WgslArray,\n} from '../../data/wgslTypes.ts';\nimport { getName } from '../../shared/meta.ts';\nimport type { LogGeneratorOptions, SerializedLogCallData } from './types.ts';\n\n// --------------\n// Serializer map\n// --------------\n\ntype SerializerMap = {\n [K in AnyWgslData['type']]?: TgpuFn<\n (args_0: Extract<AnyWgslData, { type: K }>) => Void\n >;\n};\n\nconst dataBlockIndex = privateVar(u32, 0).$name('dataBlockIndex');\nconst dataByteIndex = privateVar(u32, 0).$name('dataByteIndex');\nconst dataBufferSlot = slot().$name('dataBuffer');\nconst nextByteIndex = fn([], u32)`() {\n let i = dataByteIndex;\n dataByteIndex = dataByteIndex + 1u;\n return i;\n}`.$uses({ dataByteIndex })\n .$name('nextByteIndex');\n\nconst nextU32 = 'dataBuffer[dataBlockIndex].serializedData[nextByteIndex()]';\n\nexport const serializerMap: SerializerMap = {\n f32: fn([f32])`(n) => {\n ${nextU32} = bitcast<u32>(n);\n}`,\n f16: fn([f16])`(n) => {\n ${nextU32} = pack2x16float(vec2f(f32(n), 0.0));\n}`,\n i32: fn([i32])`(n) => {\n ${nextU32} = bitcast<u32>(n);\n}`,\n u32: fn([u32])`(n) => {\n ${nextU32} = n;\n}`,\n bool: fn([bool])`(b) => {\n ${nextU32} = u32(b);\n}`,\n vec2f: fn([vec2f])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n}`,\n vec3f: fn([vec3f])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n ${nextU32} = bitcast<u32>(v.z);\n}`,\n vec4f: fn([vec4f])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n ${nextU32} = bitcast<u32>(v.z);\n ${nextU32} = bitcast<u32>(v.w);\n}`,\n vec2h: fn([vec2h])`(v) => {\n ${nextU32} = pack2x16float(vec2f(f32(v.x), f32(v.y)));\n}`,\n vec3h: fn([vec3h])`(v) => {\n ${nextU32} = pack2x16float(vec2f(f32(v.x), f32(v.y)));\n ${nextU32} = pack2x16float(vec2f(f32(v.z), 0.0));\n}`,\n vec4h: fn([vec4h])`(v) => {\n ${nextU32} = pack2x16float(vec2f(f32(v.x), f32(v.y)));\n ${nextU32} = pack2x16float(vec2f(f32(v.z), f32(v.w)));\n}`,\n vec2i: fn([vec2i])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n}`,\n vec3i: fn([vec3i])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n ${nextU32} = bitcast<u32>(v.z);\n}`,\n vec4i: fn([vec4i])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n ${nextU32} = bitcast<u32>(v.z);\n ${nextU32} = bitcast<u32>(v.w);\n}`,\n vec2u: fn([vec2u])`(v) => {\n ${nextU32} = v.x;\n ${nextU32} = v.y;\n}`,\n vec3u: fn([vec3u])`(v) => {\n ${nextU32} = v.x;\n ${nextU32} = v.y;\n ${nextU32} = v.z;\n}`,\n vec4u: fn([vec4u])`(v) => {\n ${nextU32} = v.x;\n ${nextU32} = v.y;\n ${nextU32} = v.z;\n ${nextU32} = v.w;\n}`,\n 'vec2<bool>': fn([vec2b])`(v) => {\n ${nextU32} = u32(v.x);\n ${nextU32} = u32(v.y);\n}`,\n 'vec3<bool>': fn([vec3b])`(v) => {\n ${nextU32} = u32(v.x);\n ${nextU32} = u32(v.y);\n ${nextU32} = u32(v.z);\n}`,\n 'vec4<bool>': fn([vec4b])`(v) => {\n ${nextU32} = u32(v.x);\n ${nextU32} = u32(v.y);\n ${nextU32} = u32(v.z);\n ${nextU32} = u32(v.w);\n}`,\n mat2x2f: fn([mat2x2f])`(m) => {\n ${nextU32} = bitcast<u32>(m[0][0]);\n ${nextU32} = bitcast<u32>(m[0][1]);\n ${nextU32} = bitcast<u32>(m[1][0]);\n ${nextU32} = bitcast<u32>(m[1][1]);\n}`,\n mat3x3f: fn([mat3x3f])`(m) => {\n ${nextU32} = bitcast<u32>(m[0][0]);\n ${nextU32} = bitcast<u32>(m[0][1]);\n ${nextU32} = bitcast<u32>(m[0][2]);\n ${nextU32} = 0u;\n ${nextU32} = bitcast<u32>(m[1][0]);\n ${nextU32} = bitcast<u32>(m[1][1]);\n ${nextU32} = bitcast<u32>(m[1][2]);\n ${nextU32} = 0u;\n ${nextU32} = bitcast<u32>(m[2][0]);\n ${nextU32} = bitcast<u32>(m[2][1]);\n ${nextU32} = bitcast<u32>(m[2][2]);\n ${nextU32} = 0u;\n}`,\n mat4x4f: fn([mat4x4f])`(m) => {\n ${nextU32} = bitcast<u32>(m[0][0]);\n ${nextU32} = bitcast<u32>(m[0][1]);\n ${nextU32} = bitcast<u32>(m[0][2]);\n ${nextU32} = bitcast<u32>(m[0][3]);\n ${nextU32} = bitcast<u32>(m[1][0]);\n ${nextU32} = bitcast<u32>(m[1][1]);\n ${nextU32} = bitcast<u32>(m[1][2]);\n ${nextU32} = bitcast<u32>(m[1][3]);\n ${nextU32} = bitcast<u32>(m[2][0]);\n ${nextU32} = bitcast<u32>(m[2][1]);\n ${nextU32} = bitcast<u32>(m[2][2]);\n ${nextU32} = bitcast<u32>(m[2][3]);\n ${nextU32} = bitcast<u32>(m[3][0]);\n ${nextU32} = bitcast<u32>(m[3][1]);\n ${nextU32} = bitcast<u32>(m[3][2]);\n ${nextU32} = bitcast<u32>(m[3][3]);\n}`,\n};\n\n// rename the functions and add externals\nfor (const [name, serializer] of Object.entries(serializerMap)) {\n serializer\n .$name(\n `serialize${(name[0] as string).toLocaleUpperCase()}${name.slice(1)}`,\n )\n .$uses({ dataBlockIndex, nextByteIndex, dataBuffer: dataBufferSlot });\n}\n\n// -------\n// Helpers\n// -------\n\nfunction generateHeader(argTypes: AnyWgslData[]): string {\n return `(${argTypes.map((_, i) => `_arg_${i}`).join(', ')})`;\n}\n\n/**\n * Returns a serializer TGPU function for a given WGSL data type.\n * If the data type is a base type, one of the preexisting functions (with the `dataBufferSlot` filled) is returned.\n * Otherwise, a new function is generated.\n *\n * @param dataType - The WGSL data type descriptor to return a serializer for\n * @param dataBuffer - A buffer to store serialized log call data (a necessary external for the returned function)\n */\nfunction getSerializer<T extends AnyWgslData>(\n dataType: T,\n dataBuffer: TgpuMutable<WgslArray<SerializedLogCallData>>,\n): TgpuFn<(args_0: T) => Void> {\n const maybeSerializer = serializerMap[dataType.type];\n if (maybeSerializer) {\n return (maybeSerializer as TgpuFn<(args_0: T) => Void>).with(\n dataBufferSlot,\n dataBuffer,\n );\n }\n if (isWgslStruct(dataType)) {\n const props = Object.keys(dataType.propTypes);\n const propTypes = Object.values(dataType.propTypes) as AnyWgslData[];\n const propsSerializer = createCompoundSerializer(propTypes, dataBuffer);\n return fn([dataType])`(arg) {\\n propsSerializer(${\n props.map((prop) => `arg.${prop}`).join(', ')\n });\\n}`\n .$uses({ propsSerializer })\n .$name(`${getName(dataType) ?? 'struct'}Serializer`);\n }\n if (isWgslArray(dataType)) {\n const elementType = dataType.elementType as AnyWgslData;\n const length = dataType.elementCount;\n const elementSerializer = getSerializer(elementType, dataBuffer);\n return fn([dataType])`(arg) {\\n${\n Array\n .from({ length }, (_, i) => ` elementSerializer(arg[${i}]);`)\n .join('\\n')\n }\\n}`\n .$uses({ elementSerializer })\n .$name('arraySerializer');\n }\n throw new Error(`Cannot serialize data of type ${dataType.type}`);\n}\n\n/**\n * Creates a compound serializer TGPU function that serializes multiple arguments of different types to the data buffer.\n *\n * @param dataTypes - Array of WGSL data types that define the types of arguments to be serialized\n * @param dataBuffer - A buffer to store serialized log call data (a necessary external for the returned function)\n */\nfunction createCompoundSerializer(\n dataTypes: AnyWgslData[],\n dataBuffer: TgpuMutable<WgslArray<SerializedLogCallData>>,\n) {\n const usedSerializers: Record<string, unknown> = {};\n\n const shell = fn(dataTypes);\n const header = generateHeader(dataTypes);\n const body = dataTypes.map((arg, i) => {\n usedSerializers[`serializer${i}`] = getSerializer(arg, dataBuffer);\n return ` serializer${i}(_arg_${i});`;\n }).join('\\n');\n\n return shell`${header} {\\n${body}\\n}`\n .$uses(usedSerializers)\n .$name('compoundSerializer');\n}\n\n/**\n * Creates a TGPU function that serializes data to the log buffer.\n *\n * @param id - Identifier for this logging function instance\n * @param dataTypes - Array of WGSL data types that will be logged by this function\n * @param dataBuffer - Mutable buffer array to store serialized log call data\n * @param indexBuffer - Atomic counter buffer to track the next available log data slot\n * @param logOptions - Configuration options\n */\nexport function createLoggingFunction(\n id: number,\n dataTypes: AnyWgslData[],\n dataBuffer: TgpuMutable<WgslArray<SerializedLogCallData>>,\n indexBuffer: TgpuMutable<Atomic<U32>>,\n logOptions: Required<LogGeneratorOptions>,\n): TgpuFn {\n const serializedSize = dataTypes.map(sizeOf).reduce((a, b) => a + b, 0);\n if (serializedSize > logOptions.logSizeLimit) {\n throw new Error(\n `Logged data needs to fit in ${logOptions.logSizeLimit} bytes (one of the logs requires ${serializedSize} bytes). Consider increasing the limit by passing appropriate options to tgpu.init().`,\n );\n }\n\n const compoundSerializer = createCompoundSerializer(dataTypes, dataBuffer)\n .$name(`log${id}serializer`);\n const header = generateHeader(dataTypes);\n\n return fn(dataTypes)`${header} {\n dataBlockIndex = atomicAdd(&indexBuffer, 1);\n if (dataBlockIndex >= ${logOptions.logCountLimit}) {\n return;\n }\n dataBuffer[dataBlockIndex].id = ${id};\n dataByteIndex = 0;\n\n compoundSerializer${header};\n}`.$uses({\n indexBuffer,\n dataBuffer,\n dataBlockIndex,\n dataByteIndex,\n compoundSerializer,\n }).$name(`log${id}`);\n}\n","import { isTgpuFn } from './core/function/tgpuFn.ts';\nimport {\n getUniqueName,\n type Namespace,\n type NamespaceInternal,\n} from './core/resolve/namespace.ts';\nimport { resolveData } from './core/resolve/resolveData.ts';\nimport { stitch } from './core/resolve/stitch.ts';\nimport { ConfigurableImpl } from './core/root/configurableImpl.ts';\nimport type {\n Configurable,\n ExperimentalTgpuRoot,\n} from './core/root/rootTypes.ts';\nimport {\n type Eventual,\n isDerived,\n isProviding,\n isSlot,\n type SlotValuePair,\n type TgpuDerived,\n type TgpuSlot,\n} from './core/slot/slotTypes.ts';\nimport { getAttributesString } from './data/attributes.ts';\nimport { type AnyData, isData, UnknownData } from './data/dataTypes.ts';\nimport { bool } from './data/numeric.ts';\nimport { type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts';\nimport { isWgslArray, isWgslStruct, Void } from './data/wgslTypes.ts';\nimport {\n invariant,\n MissingSlotValueError,\n ResolutionError,\n WgslTypeError,\n} from './errors.ts';\nimport { provideCtx, topLevelState } from './execMode.ts';\nimport { naturalsExcept } from './shared/generators.ts';\nimport { isMarkedInternal } from './shared/symbols.ts';\nimport type { Infer } from './shared/repr.ts';\nimport { safeStringify } from './shared/stringify.ts';\nimport { $internal, $providing, $resolve } from './shared/symbols.ts';\nimport {\n bindGroupLayout,\n type TgpuBindGroup,\n TgpuBindGroupImpl,\n type TgpuBindGroupLayout,\n type TgpuLayoutEntry,\n} from './tgpuBindGroupLayout.ts';\nimport {\n LogGeneratorImpl,\n LogGeneratorNullImpl,\n} from './tgsl/consoleLog/logGenerator.ts';\nimport type { LogGenerator, LogResources } from './tgsl/consoleLog/types.ts';\nimport { getBestConversion } from './tgsl/conversion.ts';\nimport {\n coerceToSnippet,\n concretize,\n numericLiteralToSnippet,\n} from './tgsl/generationHelpers.ts';\nimport type { ShaderGenerator } from './tgsl/shaderGenerator.ts';\nimport wgslGenerator from './tgsl/wgslGenerator.ts';\nimport type {\n ExecMode,\n ExecState,\n FnToWgslOptions,\n FunctionScopeLayer,\n ItemLayer,\n ItemStateStack,\n ResolutionCtx,\n Wgsl,\n} from './types.ts';\nimport { CodegenState, isSelfResolvable, NormalState } from './types.ts';\nimport type { WgslExtension } from './wgslExtensions.ts';\nimport { hasTinyestMetadata } from './shared/meta.ts';\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 enableExtensions?: WgslExtension[] | undefined;\n readonly shaderGenerator?: ShaderGenerator | undefined;\n readonly config?: ((cfg: Configurable) => Configurable) | undefined;\n readonly root?: ExperimentalTgpuRoot | undefined;\n readonly namespace: Namespace;\n};\n\ntype SlotBindingLayer = {\n type: 'slotBinding';\n bindingMap: WeakMap<TgpuSlot<unknown>, unknown>;\n};\n\ntype BlockScopeLayer = {\n type: 'blockScope';\n declarations: Map<string, Snippet>;\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 get topFunctionScope(): FunctionScopeLayer | undefined {\n return this._stack.findLast((e) => e.type === 'functionScope');\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: Snippet[],\n argAliases: Record<string, Snippet>,\n returnType: AnyData | undefined,\n externalMap: Record<string, unknown>,\n ): FunctionScopeLayer {\n const scope: FunctionScopeLayer = {\n type: 'functionScope',\n args,\n argAliases,\n returnType,\n externalMap,\n reportedReturnTypes: new Set(),\n };\n\n this._stack.push(scope);\n return scope;\n }\n\n popFunctionScope() {\n this.pop('functionScope');\n }\n\n pushBlockScope() {\n this._stack.push({\n type: 'blockScope',\n declarations: new Map(),\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 getSnippetById(id: string): Snippet | 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 if (layer.argAliases[id]) {\n return layer.argAliases[id];\n }\n\n const external = layer.externalMap[id];\n\n if (external !== undefined && external !== null) {\n return coerceToSnippet(external);\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 snippet = layer.declarations.get(id);\n if (snippet !== undefined) {\n return snippet;\n }\n } else {\n // Skip\n }\n }\n\n return undefined;\n }\n\n defineBlockVariable(id: string, snippet: Snippet): void {\n if (snippet.dataType.type === 'unknown') {\n throw Error(`Tried to define variable '${id}' of unknown type`);\n }\n\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, snippet);\n return;\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 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 withResetLevel<T>(callback: () => T): T {\n const savedLevel = this.identLevel;\n this.identLevel = 0;\n try {\n return callback();\n } finally {\n this.identLevel = savedLevel;\n }\n }\n}\n\ninterface FixedBindingConfig {\n layoutEntry: TgpuLayoutEntry;\n resource: object;\n}\n\nexport class ResolutionCtxImpl implements ResolutionCtx {\n readonly #namespace: NamespaceInternal;\n readonly #shaderGenerator: ShaderGenerator;\n\n private readonly _indentController = new IndentController();\n private readonly _itemStateStack = new ItemStateStackImpl();\n readonly #modeStack: ExecState[] = [];\n private readonly _declarations: string[] = [];\n private _varyingLocations: Record<string, number> | undefined;\n readonly #currentlyResolvedItems: WeakSet<object> = new WeakSet();\n readonly #logGenerator: LogGenerator;\n\n get varyingLocations() {\n return this._varyingLocations;\n }\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 enableExtensions: WgslExtension[] | undefined;\n public expectedType: AnyData | undefined;\n\n constructor(opts: ResolutionCtxImplOptions) {\n this.enableExtensions = opts.enableExtensions;\n this.#shaderGenerator = opts.shaderGenerator ?? wgslGenerator;\n this.#logGenerator = opts.root\n ? new LogGeneratorImpl(opts.root)\n : new LogGeneratorNullImpl();\n this.#namespace = opts.namespace[$internal];\n }\n\n getUniqueName(resource: object): string {\n return getUniqueName(this.#namespace, resource);\n }\n\n makeNameValid(name: string): string {\n return this.#namespace.nameRegistry.makeValid(name);\n }\n\n get pre(): string {\n return this._indentController.pre;\n }\n\n get topFunctionReturnType() {\n const scope = this._itemStateStack.topFunctionScope;\n invariant(scope, 'Internal error, expected function scope to be present.');\n return scope.returnType;\n }\n\n get shelllessRepo() {\n return this.#namespace.shelllessRepo;\n }\n\n indent(): string {\n return this._indentController.indent();\n }\n\n dedent(): string {\n return this._indentController.dedent();\n }\n\n withResetIndentLevel<T>(callback: () => T): T {\n return this._indentController.withResetLevel(callback);\n }\n\n getById(id: string): Snippet | null {\n const item = this._itemStateStack.getSnippetById(id);\n\n if (item === undefined) {\n return null;\n }\n\n return item;\n }\n\n defineVariable(id: string, snippet: Snippet) {\n this._itemStateStack.defineBlockVariable(id, snippet);\n }\n\n reportReturnType(dataType: AnyData) {\n const scope = this._itemStateStack.topFunctionScope;\n invariant(scope, 'Internal error, expected function scope to be present.');\n scope.reportedReturnTypes.add(dataType);\n }\n\n pushBlockScope() {\n this._itemStateStack.pushBlockScope();\n }\n\n popBlockScope() {\n this._itemStateStack.popBlockScope();\n }\n\n generateLog(op: string, args: Snippet[]): Snippet {\n return this.#logGenerator.generateLog(this, op, args);\n }\n\n get logResources(): LogResources | undefined {\n return this.#logGenerator.logResources;\n }\n\n fnToWgsl(\n options: FnToWgslOptions,\n ): { head: Wgsl; body: Wgsl; returnType: AnyData } {\n const scope = this._itemStateStack.pushFunctionScope(\n options.args,\n options.argAliases,\n options.returnType,\n options.externalMap,\n );\n\n try {\n this.#shaderGenerator.initGenerator(this);\n const body = this.#shaderGenerator.functionDefinition(options.body);\n\n let returnType = options.returnType;\n if (!returnType) {\n const returnTypes = [...scope.reportedReturnTypes];\n if (returnTypes.length === 0) {\n returnType = Void;\n } else {\n const conversion = getBestConversion(returnTypes);\n if (conversion && !conversion.hasImplicitConversions) {\n returnType = conversion.targetType;\n }\n }\n\n if (!returnType) {\n throw new Error(\n `Expected function to have a single return type, got [${\n returnTypes.join(', ')\n }]. Cast explicitly to the desired type.`,\n );\n }\n\n returnType = concretize(returnType);\n }\n\n return {\n head: resolveFunctionHeader(this, options.args, returnType),\n body,\n returnType,\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\n ._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 withVaryingLocations<T>(\n locations: Record<string, number>,\n callback: () => T,\n ): T {\n this._varyingLocations = locations;\n\n try {\n return callback();\n } finally {\n this._varyingLocations = undefined;\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.#namespace.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 // Getting out of codegen or simulation mode so we can execute JS normally.\n this.pushMode(new NormalState());\n\n let result: T;\n try {\n result = derived['~compute']();\n } finally {\n this.popMode('normal');\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.#namespace.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): ResolvedSnippet {\n // All memoized versions of `item`\n const instances = this.#namespace.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: ResolvedSnippet;\n if (isData(item)) {\n result = snip(resolveData(this, item), Void);\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 if (hasTinyestMetadata(item)) {\n // Resolving a function with tinyest metadata directly means calling it with no arguments, since\n // we cannot infer the types of the arguments from a WGSL string.\n const shellless = this.#namespace.shelllessRepo.get(\n item,\n /* no arguments */ undefined,\n );\n if (!shellless) {\n throw new Error(\n `Couldn't resolve ${item.name}. Make sure it's a function that accepts no arguments, or call it from another TypeGPU function.`,\n );\n }\n\n return this.withResetIndentLevel(() => this.resolve(shellless));\n } else {\n throw new TypeError(\n `Unresolvable internal value: ${safeStringify(item)}`,\n );\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.#namespace.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(\n item: unknown,\n schema?: AnyData | UnknownData | undefined,\n exact = false,\n ): ResolvedSnippet {\n if (isTgpuFn(item) || hasTinyestMetadata(item)) {\n if (\n this.#currentlyResolvedItems.has(item) &&\n !this.#namespace.memoizedResolves.has(item)\n ) {\n throw new Error(\n `Recursive function ${item} detected. Recursion is not allowed on the GPU.`,\n );\n }\n this.#currentlyResolvedItems.add(item as object);\n }\n\n if (isProviding(item)) {\n return this.withSlots(\n item[$providing].pairs,\n () => this.resolve(item[$providing].inner, schema, exact),\n );\n }\n\n if (isMarkedInternal(item) || hasTinyestMetadata(item)) {\n // Top-level resolve\n if (this._itemStateStack.itemDepth === 0) {\n try {\n this.pushMode(new CodegenState());\n const result = provideCtx(this, () => this._getOrInstantiate(item));\n return snip(\n `${[...this._declarations].join('\\n\\n')}${result.value}`,\n Void,\n );\n } finally {\n this.popMode('codegen');\n }\n }\n\n return this._getOrInstantiate(item);\n }\n\n // This is a value that comes from the outside, maybe we can coerce it\n if (typeof item === 'number') {\n const reinterpretedType = exact\n ? schema\n : numericLiteralToSnippet(item).dataType;\n const realSchema = schema ?? numericLiteralToSnippet(item).dataType;\n invariant(\n reinterpretedType,\n 'Schema has to be defined for resolving numbers',\n );\n invariant(\n realSchema.type !== 'unknown',\n 'Schema has to be known for resolving numbers',\n );\n\n if (reinterpretedType.type === 'abstractInt') {\n return snip(`${item}`, realSchema);\n }\n if (reinterpretedType.type === 'u32') {\n return snip(`${item}u`, realSchema);\n }\n if (reinterpretedType.type === 'i32') {\n return snip(`${item}i`, realSchema);\n }\n\n const exp = item.toExponential();\n const decimal =\n reinterpretedType.type === 'abstractFloat' && Number.isInteger(item)\n ? `${item}.`\n : `${item}`;\n\n // Just picking the shorter one\n const base = exp.length < decimal.length ? exp : decimal;\n if (reinterpretedType.type === 'f32') {\n return snip(`${base}f`, realSchema);\n }\n if (reinterpretedType.type === 'f16') {\n return snip(`${base}h`, realSchema);\n }\n return snip(base, realSchema);\n }\n\n if (typeof item === 'boolean') {\n return snip(item ? 'true' : 'false', bool);\n }\n\n if (typeof item === 'string') {\n // Already resolved\n return snip(item, Void);\n }\n\n if (schema && isWgslArray(schema)) {\n if (!Array.isArray(item)) {\n throw new WgslTypeError(\n `Cannot coerce ${item} into value of type '${schema}'`,\n );\n }\n\n if (schema.elementCount !== item.length) {\n throw new WgslTypeError(\n `Cannot create value of type '${schema}' from an array of length: ${item.length}`,\n );\n }\n\n const elementTypeString = this.resolve(schema.elementType);\n return snip(\n stitch`array<${elementTypeString}, ${schema.elementCount}>(${\n item.map((element) => snip(element, schema.elementType as AnyData))\n })`,\n schema,\n );\n }\n\n if (Array.isArray(item)) {\n return snip(\n stitch`array(${item.map((element) => this.resolve(element))})`,\n UnknownData,\n ) as ResolvedSnippet;\n }\n\n if (schema && isWgslStruct(schema)) {\n return snip(\n stitch`${this.resolve(schema)}(${\n Object.entries(schema.propTypes).map(([key, propType]) =>\n snip((item as Infer<typeof schema>)[key], propType as AnyData)\n )\n })`,\n schema,\n );\n }\n\n throw new WgslTypeError(\n `Value ${item} (as json: ${safeStringify(item)}) is not resolvable${\n schema ? ` to type ${schema.type}` : ''\n }`,\n );\n }\n\n pushMode(mode: ExecState) {\n this.#modeStack.push(mode);\n }\n\n popMode(expected?: ExecMode) {\n const mode = this.#modeStack.pop();\n if (expected !== undefined) {\n invariant(mode?.type === expected, 'Unexpected mode');\n }\n }\n\n get mode(): ExecState {\n return this.#modeStack[this.#modeStack.length - 1] ?? topLevelState;\n }\n}\n\n/**\n * The results of a WGSL resolution.\n *\n * @param code - The resolved code.\n * @param usedBindGroupLayouts - List of used `tgpu.bindGroupLayout`s.\n * @param catchall - Automatically constructed bind group for buffer usages and buffer shorthands, preceded by its index.\n * @param logResources - Buffers and information about used console.logs needed to decode the raw data.\n */\nexport interface ResolutionResult {\n code: string;\n usedBindGroupLayouts: TgpuBindGroupLayout[];\n catchall: [number, TgpuBindGroup] | undefined;\n logResources: LogResources | undefined;\n}\n\nexport function resolve(\n item: Wgsl,\n options: ResolutionCtxImplOptions,\n): ResolutionResult {\n const ctx = new ResolutionCtxImpl(options);\n const snippet = options.config\n ? ctx.withSlots(\n options.config(new ConfigurableImpl([])).bindings,\n () => ctx.resolve(item),\n )\n : ctx.resolve(item);\n let code = snippet.value;\n\n const memoMap = ctx.bindGroupLayoutsToPlaceholderMap;\n const usedBindGroupLayouts: 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 usedBindGroupLayouts[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() : undefined;\n\n for (const [layout, placeholder] of memoMap.entries()) {\n const idx = layout.index ?? automaticIds.next().value;\n usedBindGroupLayouts[idx] = layout;\n code = code.replaceAll(placeholder, String(idx));\n }\n\n if (options.enableExtensions && options.enableExtensions.length > 0) {\n const extensions = options.enableExtensions.map((ext) => `enable ${ext};`);\n code = `${extensions.join('\\n')}\\n\\n${code}`;\n }\n\n return {\n code,\n usedBindGroupLayouts,\n catchall,\n logResources: ctx.logResources,\n };\n}\n\nexport function resolveFunctionHeader(\n ctx: ResolutionCtx,\n args: Snippet[],\n returnType: AnyData,\n) {\n const argList = args\n .map((arg) => `${arg.value}: ${ctx.resolve(arg.dataType as AnyData).value}`)\n .join(', ');\n\n return returnType.type !== 'void'\n ? `(${argList}) -> ${getAttributesString(returnType)}${\n ctx.resolve(returnType).value\n } `\n : `(${argList}) `;\n}\n","import type { AnyData } from '../../data/dataTypes.ts';\nimport { getResolutionCtx, provideCtx } from '../../execMode.ts';\nimport { ResolutionCtxImpl } from '../../resolutionCtx.ts';\nimport wgslGenerator from '../../tgsl/wgslGenerator.ts';\nimport { SimulationState } from '../../types.ts';\nimport type { TgpuBuffer } from '../buffer/buffer.ts';\nimport { namespace } from '../resolve/namespace.ts';\nimport type { TgpuVar } from '../variable/tgpuVariable.ts';\n\ninterface SimulationResult<T> {\n value: T;\n\n buffers: Map<TgpuBuffer<AnyData>, unknown>;\n privateVars: Map<TgpuVar<'private', AnyData>, unknown>[][][];\n workgroupVars: Map<TgpuVar<'workgroup', AnyData>, unknown>[][][];\n}\n\n/**\n * Runs the provided callback in a simulated environment, giving\n * it access to buffers and variables as if it were running on the GPU.\n *\n * The result of the simulation is returned, and does not affect the actual GPU state,\n * nor does it carry over to other simulations.\n *\n * @param callback The callback to run in the simulated environment.\n * @returns An object containing the result of the simulation, and\n * the final state of the environment.\n *\n * @example\n * const counter = tgpu.privateVar(d.u32);\n *\n * const result = tgpu.simulate(() => {\n * counter.$ += 1;\n * counter.$ += 2;\n * return counter.$;\n * });\n *\n * console.log(result.value); // 3\n */\nexport function simulate<T>(callback: () => T): SimulationResult<T> {\n // We could already be inside a resolution context, for example\n // during derived computation, where users would like to precompute\n // something that happens to require simulation.\n const ctx = getResolutionCtx() ?? new ResolutionCtxImpl({\n // Not relevant\n namespace: namespace(),\n shaderGenerator: wgslGenerator,\n });\n\n // Statically locked to one \"thread\" for now\n const workgroups: readonly [number, number, number] = [1, 1, 1];\n const workgroupSize: readonly [number, number, number] = [1, 1, 1];\n const threads = [\n workgroups[0] * workgroupSize[0],\n workgroups[1] * workgroupSize[1],\n workgroups[2] * workgroupSize[2],\n ] as const;\n\n const buffers = new Map<TgpuBuffer<AnyData>, unknown>();\n\n const workgroupVars = Array.from(\n { length: workgroups[0] },\n () =>\n Array.from(\n { length: workgroups[1] },\n () => Array.from({ length: workgroups[2] }, () => new Map()),\n ),\n );\n\n const privateVars = Array.from(\n { length: threads[0] },\n () =>\n Array.from(\n { length: threads[1] },\n () => Array.from({ length: threads[2] }, () => new Map()),\n ),\n );\n\n const simStates = Array.from(\n { length: threads[0] },\n (_, i) =>\n Array.from(\n { length: threads[1] },\n (_, j) =>\n Array.from({ length: threads[2] }, (_, k) => {\n const wi = Math.floor(i / workgroupSize[0]);\n const wj = Math.floor(j / workgroupSize[1]);\n const wk = Math.floor(k / workgroupSize[2]);\n return new SimulationState(buffers, {\n // biome-ignore lint/style/noNonNullAssertion: it's there, trust me\n private: privateVars[i]![j]![k]!,\n // biome-ignore lint/style/noNonNullAssertion: it's there, trust me\n workgroup: workgroupVars[wi]![wj]![wk]!,\n });\n }),\n ),\n );\n\n // biome-ignore lint/style/noNonNullAssertion: it's there, trust me\n ctx.pushMode(simStates[0]![0]![0]!);\n try {\n const value = provideCtx(ctx, callback);\n return {\n value,\n buffers,\n privateVars,\n workgroupVars,\n };\n } finally {\n ctx.popMode('simulate');\n }\n}\n","import {\n type AnyComputeBuiltin,\n builtin,\n type OmitBuiltins,\n} from '../../builtin.ts';\nimport {\n INTERNAL_createQuerySet,\n isQuerySet,\n type TgpuQuerySet,\n} from '../../core/querySet/querySet.ts';\nimport type { AnyData, Disarray } from '../../data/dataTypes.ts';\nimport type {\n AnyWgslData,\n BaseData,\n U16,\n U32,\n v3u,\n Vec3u,\n WgslArray,\n} from '../../data/wgslTypes.ts';\nimport {\n invariant,\n MissingBindGroupsError,\n MissingVertexBuffersError,\n} from '../../errors.ts';\nimport { WeakMemo } from '../../memo.ts';\nimport { clearTextureUtilsCache } from '../texture/textureUtils.ts';\nimport type { Infer } from '../../shared/repr.ts';\nimport { $internal } from '../../shared/symbols.ts';\nimport type { AnyVertexAttribs } from '../../shared/vertexFormat.ts';\nimport type {\n ExtractBindGroupInputFromLayout,\n TgpuBindGroup,\n TgpuBindGroupLayout,\n TgpuLayoutEntry,\n} from '../../tgpuBindGroupLayout.ts';\nimport {\n isBindGroup,\n isBindGroupLayout,\n TgpuBindGroupImpl,\n} from '../../tgpuBindGroupLayout.ts';\nimport type { LogGeneratorOptions } from '../../tgsl/consoleLog/types.ts';\nimport type { ShaderGenerator } from '../../tgsl/shaderGenerator.ts';\nimport {\n INTERNAL_createBuffer,\n isBuffer,\n type TgpuBuffer,\n type VertexFlag,\n} from '../buffer/buffer.ts';\nimport {\n TgpuBufferShorthandImpl,\n type TgpuMutable,\n type TgpuReadonly,\n type TgpuUniform,\n} from '../buffer/bufferShorthand.ts';\nimport type { TgpuBufferUsage } from '../buffer/bufferUsage.ts';\nimport type { IOLayout } from '../function/fnTypes.ts';\nimport { computeFn, type TgpuComputeFn } from '../function/tgpuComputeFn.ts';\nimport { fn, type TgpuFn } from '../function/tgpuFn.ts';\nimport type { TgpuFragmentFn } from '../function/tgpuFragmentFn.ts';\nimport type { TgpuVertexFn } from '../function/tgpuVertexFn.ts';\nimport {\n INTERNAL_createComputePipeline,\n type TgpuComputePipeline,\n} from '../pipeline/computePipeline.ts';\nimport {\n type AnyFragmentTargets,\n INTERNAL_createRenderPipeline,\n type RenderPipelineCoreOptions,\n type TgpuRenderPipeline,\n} from '../pipeline/renderPipeline.ts';\nimport { isComputePipeline, isRenderPipeline } from '../pipeline/typeGuards.ts';\nimport {\n INTERNAL_createComparisonSampler,\n INTERNAL_createSampler,\n isComparisonSampler,\n isSampler,\n type TgpuComparisonSampler,\n type TgpuFixedComparisonSampler,\n type TgpuFixedSampler,\n type TgpuSampler,\n} from '../sampler/sampler.ts';\nimport type {\n WgslComparisonSamplerProps,\n WgslSamplerProps,\n} from '../../data/sampler.ts';\nimport {\n isAccessor,\n type TgpuAccessor,\n type TgpuSlot,\n} from '../slot/slotTypes.ts';\nimport {\n INTERNAL_createTexture,\n isTexture,\n isTextureView,\n type TgpuTexture,\n type TgpuTextureView,\n} from '../texture/texture.ts';\nimport type { LayoutToAllowedAttribs } from '../vertexLayout/vertexAttribute.ts';\nimport {\n isVertexLayout,\n type TgpuVertexLayout,\n} from '../vertexLayout/vertexLayout.ts';\nimport { ConfigurableImpl } from './configurableImpl.ts';\nimport type {\n Configurable,\n CreateTextureOptions,\n CreateTextureResult,\n ExperimentalTgpuRoot,\n RenderPass,\n TgpuGuardedComputePipeline,\n TgpuRoot,\n WithBinding,\n WithCompute,\n WithFragment,\n WithVertex,\n} from './rootTypes.ts';\nimport { vec3f, vec3u } from '../../data/vector.ts';\nimport { u32 } from '../../data/numeric.ts';\nimport { ceil } from '../../std/numeric.ts';\nimport { allEq } from '../../std/boolean.ts';\n\n/**\n * Changes the given array to a vec of 3 numbers, filling missing values with 1.\n */\nfunction toVec3(arr: readonly (number | undefined)[]): v3u {\n if (arr.includes(0)) {\n throw new Error('Size and workgroupSize cannot contain zeroes.');\n }\n return vec3u(arr[0] ?? 1, arr[1] ?? 1, arr[2] ?? 1);\n}\n\nconst workgroupSizeConfigs = [\n vec3u(1, 1, 1),\n vec3u(256, 1, 1),\n vec3u(16, 16, 1),\n vec3u(8, 8, 4),\n] as const;\n\nexport class TgpuGuardedComputePipelineImpl<TArgs extends number[]>\n implements TgpuGuardedComputePipeline<TArgs> {\n #root: ExperimentalTgpuRoot;\n #pipeline: TgpuComputePipeline;\n #sizeUniform: TgpuUniform<Vec3u>;\n #workgroupSize: v3u;\n\n #lastSize: v3u;\n\n constructor(\n root: ExperimentalTgpuRoot,\n pipeline: TgpuComputePipeline,\n sizeUniform: TgpuUniform<Vec3u>,\n workgroupSize: v3u,\n ) {\n this.#root = root;\n this.#pipeline = pipeline;\n this.#sizeUniform = sizeUniform;\n this.#workgroupSize = workgroupSize;\n this.#lastSize = vec3u();\n }\n\n with(bindGroup: TgpuBindGroup): TgpuGuardedComputePipeline<TArgs> {\n return new TgpuGuardedComputePipelineImpl(\n this.#root,\n this.#pipeline.with(bindGroup),\n this.#sizeUniform,\n this.#workgroupSize,\n );\n }\n\n dispatchThreads(...threads: TArgs): void {\n const sanitizedSize = toVec3(threads);\n const workgroupCount = ceil(\n vec3f(sanitizedSize).div(vec3f(this.#workgroupSize)),\n );\n if (!allEq(sanitizedSize, this.#lastSize)) {\n // Only updating the size if it has changed from the last\n // invocation. This removes the need for flushing.\n this.#lastSize = sanitizedSize;\n this.#sizeUniform.write(sanitizedSize);\n }\n this.#pipeline.dispatchWorkgroups(\n workgroupCount.x,\n workgroupCount.y,\n workgroupCount.z,\n );\n // Yeah, i know we flush here... but it's only a matter of time!\n this.#root.flush();\n }\n}\n\nclass WithBindingImpl implements WithBinding {\n constructor(\n private readonly _getRoot: () => ExperimentalTgpuRoot,\n private readonly _slotBindings: [TgpuSlot<unknown>, unknown][],\n ) {}\n\n with<T extends AnyWgslData>(\n slot: TgpuSlot<T> | TgpuAccessor<T>,\n value: T | TgpuFn<() => T> | TgpuBufferUsage<T> | Infer<T>,\n ): WithBinding {\n return new WithBindingImpl(this._getRoot, [\n ...this._slotBindings,\n [isAccessor(slot) ? slot.slot : slot, value],\n ]);\n }\n\n withCompute<ComputeIn extends Record<string, AnyComputeBuiltin>>(\n entryFn: TgpuComputeFn<ComputeIn>,\n ): WithCompute {\n return new WithComputeImpl(this._getRoot(), this._slotBindings, entryFn);\n }\n\n createGuardedComputePipeline<TArgs extends number[]>(\n callback: (...args: TArgs) => undefined,\n ): TgpuGuardedComputePipeline<TArgs> {\n const root = this._getRoot();\n\n if (callback.length >= 4) {\n throw new Error(\n 'Guarded compute callback only supports up to three dimensions.',\n );\n }\n\n const workgroupSize = workgroupSizeConfigs[callback.length] as v3u;\n const wrappedCallback = fn([u32, u32, u32])(\n callback as (...args: number[]) => void,\n );\n\n const sizeUniform = root.createUniform(vec3u);\n\n // WGSL instead of JS because we do not run unplugin\n // before shipping the typegpu package\n const mainCompute = computeFn({\n workgroupSize,\n in: { id: builtin.globalInvocationId },\n })`{\n if (any(in.id >= sizeUniform)) {\n return;\n }\n wrappedCallback(in.id.x, in.id.y, in.id.z);\n}`.$uses({ sizeUniform, wrappedCallback });\n\n const pipeline = this\n .withCompute(mainCompute)\n .createPipeline();\n\n return new TgpuGuardedComputePipelineImpl(\n root,\n pipeline,\n sizeUniform,\n workgroupSize,\n );\n }\n\n withVertex<VertexIn extends IOLayout>(\n vertexFn: TgpuVertexFn,\n attribs: LayoutToAllowedAttribs<OmitBuiltins<VertexIn>>,\n ): WithVertex {\n return new WithVertexImpl({\n branch: this._getRoot(),\n primitiveState: undefined,\n depthStencilState: undefined,\n slotBindings: this._slotBindings,\n vertexFn,\n vertexAttribs: attribs as AnyVertexAttribs,\n multisampleState: undefined,\n });\n }\n\n pipe(transform: (cfg: Configurable) => Configurable): WithBinding {\n const newCfg = transform(new ConfigurableImpl([]));\n return new WithBindingImpl(this._getRoot, [\n ...this._slotBindings,\n ...newCfg.bindings,\n ]);\n }\n}\n\nclass WithComputeImpl implements WithCompute {\n constructor(\n private readonly _root: ExperimentalTgpuRoot,\n private readonly _slotBindings: [TgpuSlot<unknown>, unknown][],\n private readonly _entryFn: TgpuComputeFn,\n ) {}\n\n createPipeline(): TgpuComputePipeline {\n return INTERNAL_createComputePipeline(\n this._root,\n this._slotBindings,\n this._entryFn,\n );\n }\n}\n\nclass WithVertexImpl implements WithVertex {\n constructor(\n private readonly _options: Omit<\n RenderPipelineCoreOptions,\n 'fragmentFn' | 'targets'\n >,\n ) {}\n\n withFragment(\n fragmentFn: TgpuFragmentFn | 'n/a',\n targets: AnyFragmentTargets | 'n/a',\n _mismatch?: unknown,\n ): WithFragment {\n invariant(typeof fragmentFn !== 'string', 'Just type mismatch validation');\n invariant(typeof targets !== 'string', 'Just type mismatch validation');\n\n return new WithFragmentImpl({\n ...this._options,\n fragmentFn,\n targets,\n });\n }\n\n withPrimitive(\n primitiveState:\n | GPUPrimitiveState\n | Omit<GPUPrimitiveState, 'stripIndexFormat'> & {\n stripIndexFormat?: U32 | U16;\n }\n | undefined,\n ): WithFragment {\n return new WithVertexImpl({ ...this._options, primitiveState });\n }\n\n withDepthStencil(\n depthStencilState: GPUDepthStencilState | undefined,\n ): WithFragment {\n return new WithVertexImpl({ ...this._options, depthStencilState });\n }\n\n withMultisample(\n multisampleState: GPUMultisampleState | undefined,\n ): WithFragment {\n return new WithVertexImpl({ ...this._options, multisampleState });\n }\n\n createPipeline(): TgpuRenderPipeline {\n return INTERNAL_createRenderPipeline({\n ...this._options,\n fragmentFn: null,\n targets: null,\n });\n }\n}\n\nclass WithFragmentImpl implements WithFragment {\n constructor(private readonly _options: RenderPipelineCoreOptions) {}\n\n withPrimitive(\n primitiveState:\n | GPUPrimitiveState\n | Omit<GPUPrimitiveState, 'stripIndexFormat'> & {\n stripIndexFormat?: U32 | U16;\n }\n | undefined,\n ): WithFragment {\n return new WithFragmentImpl({ ...this._options, primitiveState });\n }\n\n withDepthStencil(\n depthStencilState: GPUDepthStencilState | undefined,\n ): WithFragment {\n return new WithFragmentImpl({ ...this._options, depthStencilState });\n }\n\n withMultisample(\n multisampleState: GPUMultisampleState | undefined,\n ): WithFragment {\n return new WithFragmentImpl({ ...this._options, multisampleState });\n }\n\n createPipeline(): TgpuRenderPipeline {\n return INTERNAL_createRenderPipeline(this._options);\n }\n}\n\n/**\n * Holds all data that is necessary to facilitate CPU and GPU communication.\n * Programs that share a root can interact via GPU buffers.\n */\nclass TgpuRootImpl extends WithBindingImpl\n implements TgpuRoot, ExperimentalTgpuRoot {\n '~unstable': Omit<ExperimentalTgpuRoot, keyof TgpuRoot>;\n\n private _unwrappedBindGroupLayouts = new WeakMemo(\n (key: TgpuBindGroupLayout) => key.unwrap(this),\n );\n private _unwrappedBindGroups = new WeakMemo((key: TgpuBindGroup) =>\n key.unwrap(this)\n );\n\n private _commandEncoder: GPUCommandEncoder | null = null;\n\n [$internal]: {\n logOptions: LogGeneratorOptions;\n };\n\n constructor(\n public readonly device: GPUDevice,\n public readonly nameRegistrySetting: 'random' | 'strict',\n private readonly _ownDevice: boolean,\n logOptions: LogGeneratorOptions,\n public readonly shaderGenerator?: ShaderGenerator,\n ) {\n super(() => this, []);\n\n this['~unstable'] = this;\n this[$internal] = {\n logOptions,\n };\n }\n\n get commandEncoder() {\n if (!this._commandEncoder) {\n this._commandEncoder = this.device.createCommandEncoder();\n }\n\n return this._commandEncoder;\n }\n\n get enabledFeatures() {\n return new Set(this.device.features) as ReadonlySet<GPUFeatureName>;\n }\n\n createBuffer<TData extends AnyData>(\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n ): TgpuBuffer<TData> {\n return INTERNAL_createBuffer(this, typeSchema, initialOrBuffer);\n }\n\n createUniform<TData extends AnyWgslData>(\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n ): TgpuUniform<TData> {\n const buffer = INTERNAL_createBuffer(this, typeSchema, initialOrBuffer)\n // biome-ignore lint/suspicious/noExplicitAny: i'm sure it's fine\n .$usage('uniform' as any);\n\n return new TgpuBufferShorthandImpl('uniform', buffer);\n }\n\n createMutable<TData extends AnyWgslData>(\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n ): TgpuMutable<TData> {\n const buffer = INTERNAL_createBuffer(this, typeSchema, initialOrBuffer)\n // biome-ignore lint/suspicious/noExplicitAny: i'm sure it's fine\n .$usage('storage' as any);\n\n return new TgpuBufferShorthandImpl('mutable', buffer);\n }\n\n createReadonly<TData extends AnyWgslData>(\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n ): TgpuReadonly<TData> {\n const buffer = INTERNAL_createBuffer(this, typeSchema, initialOrBuffer)\n // biome-ignore lint/suspicious/noExplicitAny: i'm sure it's fine\n .$usage('storage' as any);\n\n return new TgpuBufferShorthandImpl('readonly', buffer);\n }\n\n createQuerySet<T extends GPUQueryType>(\n type: T,\n count: number,\n rawQuerySet?: GPUQuerySet,\n ): TgpuQuerySet<T> {\n return INTERNAL_createQuerySet(this, type, count, rawQuerySet);\n }\n\n createBindGroup<\n Entries extends Record<string, TgpuLayoutEntry | null> = Record<\n string,\n TgpuLayoutEntry | null\n >,\n >(\n layout: TgpuBindGroupLayout<Entries>,\n entries: ExtractBindGroupInputFromLayout<Entries>,\n ) {\n return new TgpuBindGroupImpl(layout, entries);\n }\n\n destroy() {\n clearTextureUtilsCache(this.device);\n\n if (this._ownDevice) {\n this.device.destroy();\n }\n }\n\n createTexture<\n TWidth extends number,\n THeight extends number,\n TDepth extends number,\n TSize extends\n | readonly [TWidth]\n | readonly [TWidth, THeight]\n | readonly [TWidth, THeight, TDepth],\n TFormat extends GPUTextureFormat,\n TMipLevelCount extends number,\n TSampleCount extends number,\n TViewFormats extends GPUTextureFormat[],\n TDimension extends GPUTextureDimension,\n >(\n props: CreateTextureOptions<\n TSize,\n TFormat,\n TMipLevelCount,\n TSampleCount,\n TViewFormats,\n TDimension\n >,\n ): TgpuTexture<\n CreateTextureResult<\n TSize,\n TFormat,\n TMipLevelCount,\n TSampleCount,\n TViewFormats,\n TDimension\n >\n > {\n const texture = INTERNAL_createTexture(props, this);\n // biome-ignore lint/suspicious/noExplicitAny: <too much type wrangling>\n return texture as any;\n }\n\n createSampler(props: WgslSamplerProps): TgpuFixedSampler {\n return INTERNAL_createSampler(props, this);\n }\n\n createComparisonSampler(\n props: WgslComparisonSamplerProps,\n ): TgpuFixedComparisonSampler {\n return INTERNAL_createComparisonSampler(props, this);\n }\n\n unwrap(resource: TgpuComputePipeline): GPUComputePipeline;\n unwrap(resource: TgpuRenderPipeline): GPURenderPipeline;\n unwrap(resource: TgpuBindGroupLayout): GPUBindGroupLayout;\n unwrap(resource: TgpuBindGroup): GPUBindGroup;\n unwrap(resource: TgpuBuffer<AnyData>): GPUBuffer;\n unwrap(resource: TgpuTexture): GPUTexture;\n unwrap(resource: TgpuTextureView): GPUTextureView;\n unwrap(resource: TgpuVertexLayout): GPUVertexBufferLayout;\n unwrap(resource: TgpuSampler): GPUSampler;\n unwrap(resource: TgpuComparisonSampler): GPUSampler;\n unwrap(resource: TgpuQuerySet<GPUQueryType>): GPUQuerySet;\n unwrap(\n resource:\n | TgpuComputePipeline\n | TgpuRenderPipeline\n | TgpuBindGroupLayout\n | TgpuBindGroup\n | TgpuBuffer<AnyData>\n | TgpuTexture\n | TgpuTextureView\n | TgpuVertexLayout\n | TgpuSampler\n | TgpuComparisonSampler\n | TgpuQuerySet<GPUQueryType>,\n ):\n | GPUComputePipeline\n | GPURenderPipeline\n | GPUBindGroupLayout\n | GPUBindGroup\n | GPUBuffer\n | GPUTexture\n | GPUTextureView\n | GPUVertexBufferLayout\n | GPUSampler\n | GPUQuerySet {\n if (isComputePipeline(resource)) {\n return resource[$internal].rawPipeline;\n }\n\n if (isRenderPipeline(resource)) {\n return resource[$internal].core.unwrap().pipeline;\n }\n\n if (isBindGroupLayout(resource)) {\n return this._unwrappedBindGroupLayouts.getOrMake(resource);\n }\n\n if (isBindGroup(resource)) {\n return this._unwrappedBindGroups.getOrMake(resource);\n }\n\n if (isBuffer(resource)) {\n return resource.buffer;\n }\n\n if (isTexture(resource)) {\n return resource[$internal].unwrap();\n }\n\n if (isTextureView(resource)) {\n if (!resource[$internal].unwrap) {\n throw new Error(\n 'Cannot unwrap laid-out texture view as it has no underlying resource.',\n );\n }\n return resource[$internal].unwrap();\n }\n\n if (isVertexLayout(resource)) {\n return resource.vertexLayout;\n }\n\n if (isSampler(resource) || isComparisonSampler(resource)) {\n if (resource[$internal].unwrap) {\n return resource[$internal].unwrap();\n }\n throw new Error('Cannot unwrap laid-out sampler.');\n }\n\n if (isQuerySet(resource)) {\n return resource.querySet;\n }\n\n throw new Error(`Unknown resource type: ${resource}`);\n }\n\n beginRenderPass(\n descriptor: GPURenderPassDescriptor,\n callback: (pass: RenderPass) => void,\n ): void {\n const pass = this.commandEncoder.beginRenderPass(descriptor);\n\n const bindGroups = new Map<\n TgpuBindGroupLayout,\n TgpuBindGroup | GPUBindGroup\n >();\n const vertexBuffers = new Map<\n TgpuVertexLayout,\n {\n buffer:\n | (TgpuBuffer<WgslArray<BaseData> | Disarray<BaseData>> & VertexFlag)\n | GPUBuffer;\n offset?: number | undefined;\n size?: number | undefined;\n }\n >();\n\n let currentPipeline: TgpuRenderPipeline | undefined;\n\n const setupPassBeforeDraw = () => {\n if (!currentPipeline) {\n throw new Error('Cannot draw without a call to pass.setPipeline');\n }\n\n const { core, priors } = currentPipeline[$internal];\n const memo = core.unwrap();\n\n pass.setPipeline(memo.pipeline);\n\n const missingBindGroups = new Set(memo.usedBindGroupLayouts);\n memo.usedBindGroupLayouts.forEach((layout, idx) => {\n if (memo.catchall && idx === memo.catchall[0]) {\n // Catch-all\n pass.setBindGroup(idx, this.unwrap(memo.catchall[1]));\n missingBindGroups.delete(layout);\n } else {\n const bindGroup = priors.bindGroupLayoutMap?.get(layout) ??\n bindGroups.get(layout);\n if (bindGroup !== undefined) {\n missingBindGroups.delete(layout);\n if (isBindGroup(bindGroup)) {\n pass.setBindGroup(idx, this.unwrap(bindGroup));\n } else {\n pass.setBindGroup(idx, bindGroup);\n }\n }\n }\n });\n\n const missingVertexLayouts = new Set<TgpuVertexLayout>();\n core.usedVertexLayouts.forEach((vertexLayout, idx) => {\n const priorBuffer = priors.vertexLayoutMap?.get(vertexLayout);\n const opts = priorBuffer\n ? {\n buffer: priorBuffer,\n offset: undefined,\n size: undefined,\n }\n : vertexBuffers.get(vertexLayout);\n\n if (!opts || !opts.buffer) {\n missingVertexLayouts.add(vertexLayout);\n } else if (isBuffer(opts.buffer)) {\n pass.setVertexBuffer(\n idx,\n this.unwrap(opts.buffer),\n opts.offset,\n opts.size,\n );\n } else {\n pass.setVertexBuffer(idx, opts.buffer, opts.offset, opts.size);\n }\n });\n\n if (missingBindGroups.size > 0) {\n throw new MissingBindGroupsError(missingBindGroups);\n }\n\n if (missingVertexLayouts.size > 0) {\n throw new MissingVertexBuffersError(missingVertexLayouts);\n }\n };\n\n callback({\n setViewport(...args) {\n pass.setViewport(...args);\n },\n setScissorRect(...args) {\n pass.setScissorRect(...args);\n },\n setBlendConstant(...args) {\n pass.setBlendConstant(...args);\n },\n setStencilReference(...args) {\n pass.setStencilReference(...args);\n },\n beginOcclusionQuery(...args) {\n pass.beginOcclusionQuery(...args);\n },\n endOcclusionQuery(...args) {\n pass.endOcclusionQuery(...args);\n },\n executeBundles(...args) {\n pass.executeBundles(...args);\n },\n setPipeline(pipeline) {\n currentPipeline = pipeline;\n },\n\n setIndexBuffer: (buffer, indexFormat, offset, size) => {\n if (isBuffer(buffer)) {\n pass.setIndexBuffer(this.unwrap(buffer), indexFormat, offset, size);\n } else {\n pass.setIndexBuffer(buffer, indexFormat, offset, size);\n }\n },\n\n setVertexBuffer(vertexLayout, buffer, offset, size) {\n vertexBuffers.set(vertexLayout, { buffer, offset, size });\n },\n\n setBindGroup(bindGroupLayout, bindGroup) {\n bindGroups.set(bindGroupLayout, bindGroup);\n },\n\n draw(vertexCount, instanceCount, firstVertex, firstInstance) {\n setupPassBeforeDraw();\n pass.draw(vertexCount, instanceCount, firstVertex, firstInstance);\n },\n\n drawIndexed(...args) {\n setupPassBeforeDraw();\n pass.drawIndexed(...args);\n },\n\n drawIndirect(...args) {\n setupPassBeforeDraw();\n pass.drawIndirect(...args);\n },\n\n drawIndexedIndirect(...args) {\n setupPassBeforeDraw();\n pass.drawIndexedIndirect(...args);\n },\n });\n\n pass.end();\n }\n\n flush() {\n if (!this._commandEncoder) {\n return;\n }\n\n this.device.queue.submit([this._commandEncoder.finish()]);\n this._commandEncoder = null;\n }\n}\n\n/**\n * Options passed into {@link init}.\n */\nexport type InitOptions = {\n adapter?: GPURequestAdapterOptions | undefined;\n device?:\n | GPUDeviceDescriptor & { optionalFeatures?: Iterable<GPUFeatureName> }\n | undefined;\n /** @default 'random' */\n unstable_names?: 'random' | 'strict' | undefined;\n /**\n * A custom shader code generator, used when resolving TGSL.\n * If not provided, the default WGSL generator will be used.\n */\n shaderGenerator?: ShaderGenerator | undefined;\n unstable_logOptions?: LogGeneratorOptions;\n};\n\n/**\n * Options passed into {@link initFromDevice}.\n */\nexport type InitFromDeviceOptions = {\n device: GPUDevice;\n /** @default 'random' */\n unstable_names?: 'random' | 'strict' | undefined;\n /**\n * A custom shader code generator, used when resolving TGSL.\n * If not provided, the default WGSL generator will be used.\n */\n shaderGenerator?: ShaderGenerator | undefined;\n unstable_logOptions?: LogGeneratorOptions;\n};\n\n/**\n * Requests a new GPU device and creates a root around it.\n * If a specific device should be used instead, use @see initFromDevice.\n *\n * @example\n * When given no options, the function will ask the browser for a suitable GPU device.\n * ```ts\n * const root = await tgpu.init();\n * ```\n *\n * @example\n * If there are specific options that should be used when requesting a device, you can pass those in.\n * ```ts\n * const adapterOptions: GPURequestAdapterOptions = ...;\n * const deviceDescriptor: GPUDeviceDescriptor = ...;\n * const root = await tgpu.init({ adapter: adapterOptions, device: deviceDescriptor });\n * ```\n */\nexport async function init(options?: InitOptions): Promise<TgpuRoot> {\n const {\n adapter: adapterOpt,\n device: deviceOpt,\n unstable_names: names = 'random',\n unstable_logOptions,\n } = options ?? {};\n\n if (!navigator.gpu) {\n throw new Error('WebGPU is not supported by this browser.');\n }\n\n const adapter = await navigator.gpu.requestAdapter(adapterOpt);\n\n if (!adapter) {\n throw new Error('Could not find a compatible GPU');\n }\n\n const availableFeatures: GPUFeatureName[] = [];\n for (const feature of deviceOpt?.requiredFeatures ?? []) {\n if (!adapter.features.has(feature)) {\n throw new Error(\n `Requested feature \"${feature}\" is not supported by the adapter.`,\n );\n }\n availableFeatures.push(feature);\n }\n for (const feature of deviceOpt?.optionalFeatures ?? []) {\n if (adapter.features.has(feature)) {\n availableFeatures.push(feature);\n } else {\n console.warn(\n `Optional feature \"${feature}\" is not supported by the adapter.`,\n );\n }\n }\n\n const device = await adapter.requestDevice({\n ...deviceOpt,\n requiredFeatures: availableFeatures,\n });\n\n return new TgpuRootImpl(\n device,\n names,\n true,\n unstable_logOptions ?? {},\n options?.shaderGenerator,\n );\n}\n\n/**\n * Creates a root from the given device, instead of requesting it like @see init.\n *\n * @example\n * ```ts\n * const device: GPUDevice = ...;\n * const root = tgpu.initFromDevice({ device });\n * ```\n */\nexport function initFromDevice(options: InitFromDeviceOptions): TgpuRoot {\n const {\n device,\n unstable_names: names = 'random',\n unstable_logOptions,\n } = options ?? {};\n\n return new TgpuRootImpl(\n device,\n names,\n false,\n unstable_logOptions ?? {},\n options?.shaderGenerator,\n );\n}\n"]}
1
+ {"version":3,"sources":["/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","../src/core/declare/tgpuDeclare.ts","../src/core/resolve/resolveData.ts","../src/data/alignIO.ts","../src/data/compiledIO.ts","../src/data/dataIO.ts","../src/core/buffer/buffer.ts","../src/core/texture/textureUtils.ts","../src/core/texture/texture.ts","../src/tgsl/consoleLog/serializers.ts","../src/resolutionCtx.ts","../src/core/querySet/querySet.ts","../src/core/root/init.ts"],"names":["declare","declaration","TgpuDeclareImpl","$internal","dependencyMap","$resolve","ctx","externalMap","externals","applyExternals","replacedDeclaration","replaceExternalsInWgsl","snip","Void","alignIO","schema","isTextureView"],"mappings":"AAAA,moCAAiG,wDAAkG,wDAAmN,wDAAy3B,SC6B/vCA,EAAAA,CAAQC,CAAAA,CAAkC,CACxD,OAAO,IAAIC,EAAAA,CAAgBD,CAAW,CACxC,CAMA,IAAMC,EAAAA,WAAN,KAA6D,CAI3D,WAAA,CAAoBD,CAAAA,CAAqB,yEAArB,IAAA,CAAA,WAAA,CAAAA,CAAsB,eAH1C,CAAUE,mBAAS,CAAA,CAAI,CAAA,EAAA,gBACf,gBAAA,CAAkC,CAAC,EAAA,KAI3C,CAAMC,CAAAA,CAA8C,CAClD,OAAA,IAAA,CAAK,gBAAA,CAAiB,IAAA,CAAKA,CAAa,CAAA,CACjC,IACT,CAEA,CAACC,mBAAQ,CAAA,CAAEC,CAAAA,CAAqC,CAC9C,IAAMC,CAAAA,CAA2B,CAAC,CAAA,CAElC,GAAA,CAAA,IAAWC,EAAAA,GAAa,IAAA,CAAK,gBAAA,CAC3BC,iCAAAA,CAAeF,CAAaC,CAAS,CAAA,CAGvC,IAAME,CAAAA,CAAsBC,iCAAAA,CAC1BL,CACAC,CAAAA,CACA,IAAA,CAAK,WACP,CAAA,CAEA,OAAAD,CAAAA,CAAI,cAAA,CAAeI,CAAmB,CAAA,CAC/BE,kCAAAA,EAAK,CAAIC,mBAAI,CACtB,CAEA,QAAA,CAAA,CAAW,CACT,MAAO,CAAA,SAAA,EAAY,IAAA,CAAK,WAAW,CAAA,CAAA;ACkF5B;AA8BA;AAYT;ACxLOC,CAAAA;ACwIC;AA2CkE;AAOhE;AAeN;AAsBE;AA6CS;AAea;AAkDsB;AAE9C,QAAA;ACjJEC,8BAAAA;ACiOJ,8BAAA;AC3RQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgDA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,EAAA;AAEN,wDAAA;AAAA;AAsHM,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AChFIC,MAAAA;AC9MgB;AAAA;AAAA;AAUjB,CAAA;AACJ,EAAA;AAEI,CAAA;AACJ,EAAA;AAEI,CAAA;AACJ,EAAA;AAEI,CAAA;AACJ,EAAA;AAEM,CAAA;AACN,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AAEQ,CAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEe,CAAA;AACf,EAAA;AACA,EAAA;AAEe,CAAA;AACf,EAAA;AACA,EAAA;AACA,EAAA;AAEe,CAAA;AACf,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEY,CAAA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEY,CAAA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEY,CAAA;AACZ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAyDG,CAAA;AAyBJ;AAkCqB;AAAA;AAEmB,wBAAA;AAAA;AAAA;AAGZ,kCAAA;AAAA;AAAA;AAGV,oBAAA;ACtK1B,CAAA;AAglB0C;AA8JhB;AAoCO;AAAA;ACh4B1B;AC6MJ;AAAA;AAAA;AAAA;AA4JK,CAAA","file":"/Users/iwo/Projects/wigsill/packages/typegpu/dist/index.cjs","sourcesContent":[null,"import { type ResolvedSnippet, snip } from '../../data/snippet.ts';\nimport { Void } from '../../data/wgslTypes.ts';\nimport { $internal, $resolve } from '../../shared/symbols.ts';\nimport type { ResolutionCtx, SelfResolvable } from '../../types.ts';\nimport {\n applyExternals,\n type ExternalMap,\n replaceExternalsInWgsl,\n} from '../resolve/externals.ts';\n\n// ----------\n// Public API\n// ----------\n\n/**\n * Extra declaration that shall be included in final WGSL code,\n * when resolving objects that use it.\n */\nexport interface TgpuDeclare {\n $uses(dependencyMap: Record<string, unknown>): this;\n}\n\n/**\n * Allows defining extra declarations that shall be included in the final WGSL code,\n * when resolving objects that use them.\n *\n * Using this API is generally discouraged, as it shouldn't be necessary in any common scenario.\n * It was developed to ensure full compatibility of TypeGPU programs with current and future versions of WGSL.\n */\nexport function declare(declaration: string): TgpuDeclare {\n return new TgpuDeclareImpl(declaration);\n}\n\n// --------------\n// Implementation\n// --------------\n\nclass TgpuDeclareImpl implements TgpuDeclare, SelfResolvable {\n readonly [$internal] = true;\n private externalsToApply: ExternalMap[] = [];\n\n constructor(private declaration: string) {}\n\n $uses(dependencyMap: Record<string, unknown>): this {\n this.externalsToApply.push(dependencyMap);\n return this;\n }\n\n [$resolve](ctx: ResolutionCtx): ResolvedSnippet {\n const externalMap: ExternalMap = {};\n\n for (const externals of this.externalsToApply) {\n applyExternals(externalMap, externals);\n }\n\n const replacedDeclaration = replaceExternalsInWgsl(\n ctx,\n externalMap,\n this.declaration,\n );\n\n ctx.addDeclaration(replacedDeclaration);\n return snip('', Void);\n }\n\n toString() {\n return `declare: ${this.declaration}`;\n }\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 { isWgslComparisonSampler, isWgslSampler } from '../../data/sampler.ts';\nimport {\n accessModeMap,\n isWgslStorageTexture,\n isWgslTexture,\n type WgslExternalTexture,\n} from '../../data/texture.ts';\n\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 { isValidIdentifier } from '../../nameRegistry.ts';\nimport { $internal } from '../../shared/symbols.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 'texture_external',\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 | WgslExternalTexture;\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 if (!isValidIdentifier(key)) {\n throw new Error(\n `Property key '${key}' is a reserved WGSL word. Choose a different name.`,\n );\n }\n return ` ${getAttributesString(property)}${key}: ${\n ctx.resolve(property as AnyWgslData).value\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 if (struct[$internal].isAbstruct) {\n throw new Error('Cannot resolve abstract struct types to WGSL.');\n }\n const id = ctx.getUniqueName(struct);\n\n ctx.addDeclaration(`\\\nstruct ${id} {\n${\n Object.entries(struct.propTypes as Record<string, BaseData>)\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.getUniqueName(unstruct);\n\n ctx.addDeclaration(`\\\nstruct ${id} {\n${\n Object.entries(unstruct.propTypes as Record<string, BaseData>)\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 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).value;\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 ).value;\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 ).value;\n }\n\n return ctx.resolve(formatToWGSLType[data.type]).value;\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).value;\n }\n\n if (data.type === 'ptr') {\n if (data.addressSpace === 'storage') {\n return `ptr<storage, ${ctx.resolve(data.inner).value}, ${\n data.access === 'read-write' ? 'read_write' : data.access\n }>`;\n }\n return `ptr<${data.addressSpace}, ${ctx.resolve(data.inner).value}>`;\n }\n\n if (\n data.type === 'abstractInt' ||\n data.type === 'abstractFloat' ||\n data.type === 'void' ||\n data.type === 'u16'\n ) {\n throw new Error(`${data.type} has no representation in WGSL`);\n }\n\n if (isWgslStorageTexture(data)) {\n return `${data.type}<${data.format}, ${accessModeMap[data.access]}>`;\n }\n\n if (isWgslTexture(data)) {\n return data.type.startsWith('texture_depth')\n ? data.type\n : `${data.type}<${data.sampleType.type}>`;\n }\n\n if (isWgslComparisonSampler(data) || isWgslSampler(data)) {\n return data.type;\n }\n\n assertExhaustive(data, 'resolveData');\n}\n","import type { IMeasurer, ISerialInput, ISerialOutput } from 'typed-binary';\n\n/**\n * @param io the IO to align\n * @param baseAlignment must be power of 2\n */\nfunction alignIO(\n io: ISerialInput | ISerialOutput | IMeasurer,\n baseAlignment: number,\n) {\n const currentPos = 'size' in io ? io.size : io.currentByteOffset;\n\n const bitMask = baseAlignment - 1;\n const offset = currentPos & bitMask;\n\n if ('skipBytes' in io) {\n io.skipBytes((baseAlignment - offset) & bitMask);\n } else {\n io.add((baseAlignment - offset) & bitMask);\n }\n}\n\nexport default alignIO;\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 { formatToWGSLType, isPackedData } from './vertexFormatData.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 u16: 'u16',\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 f16: 'f16',\n vec2h: 'f16',\n vec3h: 'f16',\n vec4h: 'f16',\n\n mat2x2f: 'f32',\n mat3x3f: 'f32',\n mat4x4f: 'f32',\n} as const;\n\nconst vertexFormatToPrimitive = {\n uint8: 'u8',\n uint8x2: 'u8',\n uint8x4: 'u8',\n sint8: 'i8',\n sint8x2: 'i8',\n sint8x4: 'i8',\n unorm8: 'u8',\n unorm8x2: 'u8',\n unorm8x4: 'u8',\n snorm8: 'i8',\n snorm8x2: 'i8',\n snorm8x4: 'i8',\n uint16: 'u16',\n uint16x2: 'u16',\n uint16x4: 'u16',\n sint16: 'i16',\n sint16x2: 'i16',\n sint16x4: 'i16',\n unorm16: 'u16',\n unorm16x2: 'u16',\n unorm16x4: 'u16',\n snorm16: 'i16',\n snorm16x2: 'i16',\n snorm16x4: 'i16',\n float16: 'f16',\n float16x2: 'f16',\n float16x4: 'f16',\n float32: 'f32',\n float32x2: 'f32',\n float32x3: 'f32',\n float32x4: 'f32',\n uint32: 'u32',\n uint32x2: 'u32',\n uint32x3: 'u32',\n uint32x4: 'u32',\n sint32: 'i32',\n sint32x2: 'i32',\n sint32x3: 'i32',\n sint32x4: 'i32',\n} as const;\n\nconst primitiveToWriteFunction = {\n u32: 'setUint32',\n i32: 'setInt32',\n f32: 'setFloat32',\n u16: 'setUint16',\n i16: 'setInt16',\n f16: 'setFloat16',\n u8: 'setUint8',\n i8: 'setInt8',\n} as const;\n\n/**\n * @privateRemarks\n * based on the `Channel Formats` table https://www.w3.org/TR/WGSL/#texel-formats\n */\nconst vertexFormatValueTransform = {\n unorm8: (value: string) => `Math.round(${value} * 255)`,\n unorm8x2: (value: string) => `Math.round(${value} * 255)`,\n unorm8x4: (value: string) => `Math.round(${value} * 255)`,\n snorm8: (value: string) => `Math.round(${value} * 127)`,\n snorm8x2: (value: string) => `Math.round(${value} * 127)`,\n snorm8x4: (value: string) => `Math.round(${value} * 127)`,\n unorm16: (value: string) => `Math.round(${value} * 65535)`,\n unorm16x2: (value: string) => `Math.round(${value} * 65535)`,\n unorm16x4: (value: string) => `Math.round(${value} * 65535)`,\n snorm16: (value: string) => `Math.round(${value} * 32767)`,\n snorm16x2: (value: string) => `Math.round(${value} * 32767)`,\n snorm16x4: (value: string) => `Math.round(${value} * 32767)`,\n} as const;\n\nconst specialPackedFormats = {\n 'unorm10-10-10-2': {\n writeFunction: 'setUint32',\n generator: (offsetExpr: string, valueExpr: string) =>\n `output.setUint32(${offsetExpr}, ((${valueExpr}.x*1023&0x3FF)<<22)|((${valueExpr}.y*1023&0x3FF)<<12)|((${valueExpr}.z*1023&0x3FF)<<2)|(${valueExpr}.w*3&3), littleEndian);\\n`,\n },\n 'unorm8x4-bgra': {\n writeFunction: 'setUint8',\n generator: (offsetExpr: string, valueExpr: string) => {\n const bgraComponents = ['z', 'y', 'x', 'w'];\n let code = '';\n for (let idx = 0; idx < 4; idx++) {\n code +=\n `output.setUint8((${offsetExpr} + ${idx}), Math.round(${valueExpr}.${\n bgraComponents[idx]\n } * 255), littleEndian);\\n`;\n }\n return code;\n },\n },\n} as const;\n\nexport function buildWriter(\n node: wgsl.BaseData,\n offsetExpr: string,\n valueExpr: string,\n depth = 0,\n): string {\n const loopVar = ['i', 'j', 'k'][depth] || `i${depth}`;\n\n if (wgsl.isAtomic(node) || wgsl.isDecorated(node)) {\n return buildWriter(node.inner, offsetExpr, valueExpr, depth);\n }\n\n if (wgsl.isWgslStruct(node) || isUnstruct(node)) {\n const propOffsets = offsetsForProps(node);\n let code = '';\n for (const [key, propOffset] of Object.entries(propOffsets)) {\n const subSchema = node.propTypes[key];\n if (!subSchema) continue;\n code += buildWriter(\n subSchema,\n `(${offsetExpr} + ${propOffset.offset})`,\n `${valueExpr}.${key}`,\n depth,\n );\n }\n return code;\n }\n\n if (wgsl.isWgslArray(node) || isDisarray(node)) {\n const elementSize = roundUp(\n sizeOf(node.elementType),\n alignmentOf(node),\n );\n let code = '';\n\n code +=\n `for (let ${loopVar} = 0; ${loopVar} < ${node.elementCount}; ${loopVar}++) {\\n`;\n code += buildWriter(\n node.elementType,\n `(${offsetExpr} + ${loopVar} * ${elementSize})`,\n `${valueExpr}[${loopVar}]`,\n depth + 1,\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 idx = 0; idx < elementCount; idx++) {\n const colIndex = Math.floor(idx / matSize);\n const rowIndex = idx % 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 if (isPackedData(node)) {\n const formatName = node.type;\n\n if (formatName in specialPackedFormats) {\n const handler =\n specialPackedFormats[formatName as keyof typeof specialPackedFormats];\n return handler.generator(offsetExpr, valueExpr);\n }\n\n const primitive = vertexFormatToPrimitive[\n formatName as keyof typeof vertexFormatToPrimitive\n ];\n const writeFunc = primitiveToWriteFunction[primitive];\n const wgslType = formatToWGSLType[formatName];\n const componentCount = wgsl.isVec4(wgslType)\n ? 4\n : wgsl.isVec3(wgslType)\n ? 3\n : wgsl.isVec2(wgslType)\n ? 2\n : 1;\n const componentSize = primitive === 'u8' || primitive === 'i8'\n ? 1\n : primitive === 'u16' || primitive === 'i16' || primitive === 'f16'\n ? 2\n : 4;\n const components = ['x', 'y', 'z', 'w'];\n const transform = vertexFormatValueTransform[\n formatName as keyof typeof vertexFormatValueTransform\n ];\n\n let code = '';\n for (let idx = 0; idx < componentCount; idx++) {\n const accessor = componentCount === 1\n ? valueExpr\n : `${valueExpr}.${components[idx]}`;\n const value = transform ? transform(accessor) : accessor;\n code += `output.${writeFunc}((${offsetExpr} + ${\n idx * componentSize\n }), ${value}, littleEndian);\\n`;\n }\n\n return code;\n }\n\n if (!Object.hasOwn(typeToPrimitive, node.type)) {\n throw new Error(\n `Primitive ${node.type} is unsupported by compiled writer`,\n );\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 | ((\n output: DataView,\n offset: number,\n value: Infer<T>,\n littleEndian?: boolean,\n ) => void)\n | undefined {\n if (!EVAL_ALLOWED_IN_ENV) {\n console.warn(\n 'This environment does not allow eval - using default writer as fallback',\n );\n return undefined;\n }\n\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 try {\n const body = buildWriter(schema, 'offset', 'value', 0);\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 } catch (error) {\n console.warn(\n `Failed to compile writer for schema: ${schema}\\nReason: ${\n error instanceof Error ? error.message : String(error)\n }\\nFalling back to default writer`,\n );\n }\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 u16(output, _schema: wgsl.U16, value: number) {\n output.writeUint16(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 as wgsl.BaseData));\n writeData(output, property as wgsl.BaseData, value[key]);\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(Math.round(value * 255));\n },\n unorm8x2(output, _, value: wgsl.v2f) {\n output.writeUint8(Math.round(value.x * 255));\n output.writeUint8(Math.round(value.y * 255));\n },\n unorm8x4(output, _, value: wgsl.v4f) {\n output.writeUint8(Math.round(value.x * 255));\n output.writeUint8(Math.round(value.y * 255));\n output.writeUint8(Math.round(value.z * 255));\n output.writeUint8(Math.round(value.w * 255));\n },\n snorm8(output, _, value: number) {\n output.writeInt8(Math.round(value * 127));\n },\n snorm8x2(output, _, value: wgsl.v2f) {\n output.writeInt8(Math.round(value.x * 127));\n output.writeInt8(Math.round(value.y * 127));\n },\n snorm8x4(output, _, value: wgsl.v4f) {\n output.writeInt8(Math.round(value.x * 127));\n output.writeInt8(Math.round(value.y * 127));\n output.writeInt8(Math.round(value.z * 127));\n output.writeInt8(Math.round(value.w * 127));\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.writeInt16(Math.round(value * 32767));\n },\n snorm16x2(output, _, value: wgsl.v2f) {\n output.writeInt16(Math.round(value.x * 32767));\n output.writeInt16(Math.round(value.y * 32767));\n },\n snorm16x4(output, _, value: wgsl.v4f) {\n output.writeInt16(Math.round(value.x * 32767));\n output.writeInt16(Math.round(value.y * 32767));\n output.writeInt16(Math.round(value.z * 32767));\n output.writeInt16(Math.round(value.w * 32767));\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.y * 1023) & 1023) << 12; // g (10 bits)\n packed |= ((value.z * 1023) & 1023) << 2; // b (10 bits)\n packed |= (value.w * 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 const propTypes = schema.propTypes as Record<string, wgsl.BaseData>;\n for (const [key, property] of Object.entries(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 // TODO: Move texture IO logic here after we expand repr to have in/out variants\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 u16(input: ISerialInput): number {\n return input.readUint16();\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 const propTypes = schema.propTypes as Record<string, wgsl.BaseData>;\n for (const [key, property] of Object.entries(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.readInt8() / 127,\n snorm8x2: (i) => vec2f(i.readInt8() / 127, i.readInt8() / 127),\n snorm8x4: (i) =>\n vec4f(\n i.readInt8() / 127,\n i.readInt8() / 127,\n i.readInt8() / 127,\n i.readInt8() / 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.readInt16() / 32767,\n snorm16x2: (i): wgsl.v2f =>\n vec2f(i.readInt16() / 32767, i.readInt16() / 32767),\n snorm16x4: (i): wgsl.v4f =>\n vec4f(\n i.readInt16() / 32767,\n i.readInt16() / 32767,\n i.readInt16() / 32767,\n i.readInt16() / 32767,\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 const propTypes = schema.propTypes as Record<string, wgsl.BaseData>;\n for (const [key, property] of Object.entries(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 // TODO: Move texture IO logic here after we expand repr to have in/out variants\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 { BufferReader, BufferWriter, getSystemEndianness } from 'typed-binary';\nimport { getCompiledWriterForSchema } from '../../data/compiledIO.ts';\nimport { readData, writeData } from '../../data/dataIO.ts';\nimport type { AnyData } from '../../data/dataTypes.ts';\nimport { getWriteInstructions } from '../../data/partialIO.ts';\nimport { sizeOf } from '../../data/sizeOf.ts';\nimport type { BaseData } from '../../data/wgslTypes.ts';\nimport { isWgslData } from '../../data/wgslTypes.ts';\nimport type { StorageFlag } from '../../extension.ts';\nimport type { TgpuNamable } from '../../shared/meta.ts';\nimport { getName, setName } from '../../shared/meta.ts';\nimport type {\n Infer,\n InferPartial,\n IsValidIndexSchema,\n IsValidStorageSchema,\n IsValidUniformSchema,\n IsValidVertexSchema,\n MemIdentity,\n} from '../../shared/repr.ts';\nimport { $internal } from '../../shared/symbols.ts';\nimport type {\n Prettify,\n UnionToIntersection,\n} from '../../shared/utilityTypes.ts';\nimport { isGPUBuffer } from '../../types.ts';\nimport type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';\nimport {\n asMutable,\n asReadonly,\n asUniform,\n type TgpuBufferMutable,\n type TgpuBufferReadonly,\n type TgpuBufferUniform,\n type TgpuFixedBufferUsage,\n} from './bufferUsage.ts';\n\n// ----------\n// Public API\n// ----------\n\nexport interface UniformFlag {\n usableAsUniform: true;\n}\n\n/**\n * @deprecated Use UniformFlag instead.\n */\nexport type Uniform = UniformFlag;\n\nexport interface VertexFlag {\n usableAsVertex: true;\n}\n\nexport interface IndexFlag {\n usableAsIndex: true;\n}\n\n/**\n * @deprecated Use VertexFlag instead.\n */\nexport type Vertex = VertexFlag;\n\ntype LiteralToUsageType<T extends 'uniform' | 'storage' | 'vertex' | 'index'> =\n T extends 'uniform' ? UniformFlag\n : T extends 'storage' ? StorageFlag\n : T extends 'vertex' ? VertexFlag\n : T extends 'index' ? IndexFlag\n : never;\n\ntype ViewUsages<TBuffer extends TgpuBuffer<BaseData>> =\n | (boolean extends TBuffer['usableAsUniform'] ? never : 'uniform')\n | (boolean extends TBuffer['usableAsStorage'] ? never\n : 'readonly' | 'mutable');\n\ntype UsageTypeToBufferUsage<TData extends BaseData> = {\n uniform: TgpuBufferUniform<TData> & TgpuFixedBufferUsage<TData>;\n mutable: TgpuBufferMutable<TData> & TgpuFixedBufferUsage<TData>;\n readonly: TgpuBufferReadonly<TData> & TgpuFixedBufferUsage<TData>;\n};\n\nconst usageToUsageConstructor = {\n uniform: asUniform,\n mutable: asMutable,\n readonly: asReadonly,\n};\n\n/**\n * Done as an object to later Prettify it\n */\ntype InnerValidUsagesFor<T> = {\n usage:\n | (IsValidStorageSchema<T> extends true ? 'storage' : never)\n | (IsValidUniformSchema<T> extends true ? 'uniform' : never)\n | (IsValidVertexSchema<T> extends true ? 'vertex' : never)\n | (IsValidIndexSchema<T> extends true ? 'index' : never);\n};\n\nexport type ValidUsagesFor<T> = InnerValidUsagesFor<T>['usage'];\n\nexport interface TgpuBuffer<TData extends BaseData> extends TgpuNamable {\n readonly [$internal]: true;\n readonly resourceType: 'buffer';\n readonly dataType: TData;\n readonly initial?: Infer<TData> | undefined;\n\n readonly buffer: GPUBuffer;\n readonly destroyed: boolean;\n\n usableAsUniform: boolean;\n usableAsStorage: boolean;\n usableAsVertex: boolean;\n usableAsIndex: boolean;\n\n $usage<\n T extends [\n Prettify<InnerValidUsagesFor<TData>>['usage'],\n ...Prettify<InnerValidUsagesFor<TData>>['usage'][],\n ],\n >(\n ...usages: T\n ): this & UnionToIntersection<LiteralToUsageType<T[number]>>;\n $addFlags(flags: GPUBufferUsageFlags): this;\n\n as<T extends ViewUsages<this>>(usage: T): UsageTypeToBufferUsage<TData>[T];\n\n compileWriter(): void;\n write(data: Infer<TData>): void;\n writePartial(data: InferPartial<TData>): void;\n clear(): void;\n copyFrom(srcBuffer: TgpuBuffer<MemIdentity<TData>>): void;\n read(): Promise<Infer<TData>>;\n destroy(): void;\n}\n\nexport function INTERNAL_createBuffer<TData extends AnyData>(\n group: ExperimentalTgpuRoot,\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n): TgpuBuffer<TData> {\n if (!isWgslData(typeSchema)) {\n return new TgpuBufferImpl(group, typeSchema, initialOrBuffer, [\n 'storage',\n 'uniform',\n ]);\n }\n\n return new TgpuBufferImpl(group, typeSchema, initialOrBuffer);\n}\n\nexport function isBuffer<T extends TgpuBuffer<AnyData>>(\n value: T | unknown,\n): value is T {\n return (value as TgpuBuffer<AnyData>).resourceType === 'buffer';\n}\n\nexport function isUsableAsVertex<T extends TgpuBuffer<AnyData>>(\n buffer: T,\n): buffer is T & VertexFlag {\n return !!(buffer as unknown as VertexFlag).usableAsVertex;\n}\n\nexport function isUsableAsIndex<T extends TgpuBuffer<AnyData>>(\n buffer: T,\n): buffer is T & IndexFlag {\n return !!(buffer as unknown as IndexFlag).usableAsIndex;\n}\n\n// --------------\n// Implementation\n// --------------\nconst endianness = getSystemEndianness();\n\nclass TgpuBufferImpl<TData extends AnyData> implements TgpuBuffer<TData> {\n public readonly [$internal] = true;\n public readonly resourceType = 'buffer';\n public flags: GPUBufferUsageFlags = GPUBufferUsage.COPY_DST |\n GPUBufferUsage.COPY_SRC;\n\n readonly #device: GPUDevice;\n private _buffer: GPUBuffer | null = null;\n private _ownBuffer: boolean;\n private _destroyed = false;\n private _hostBuffer: ArrayBuffer | undefined;\n\n readonly initial: Infer<TData> | undefined;\n\n usableAsUniform = false;\n usableAsStorage = false;\n usableAsVertex = false;\n usableAsIndex = false;\n\n constructor(\n root: ExperimentalTgpuRoot,\n public readonly dataType: TData,\n public readonly initialOrBuffer?: Infer<TData> | GPUBuffer | undefined,\n private readonly _disallowedUsages?:\n ('uniform' | 'storage' | 'vertex' | 'index')[],\n ) {\n this.#device = root.device;\n if (isGPUBuffer(initialOrBuffer)) {\n this._ownBuffer = false;\n this._buffer = initialOrBuffer;\n } else {\n this._ownBuffer = true;\n this.initial = initialOrBuffer;\n }\n }\n\n get buffer() {\n if (this._destroyed) {\n throw new Error('This buffer has been destroyed');\n }\n\n if (!this._buffer) {\n this._buffer = this.#device.createBuffer({\n size: sizeOf(this.dataType),\n usage: this.flags,\n mappedAtCreation: !!this.initial,\n label: getName(this) ?? '<unnamed>',\n });\n\n if (this.initial) {\n this._writeToTarget(this._buffer.getMappedRange(), this.initial);\n this._buffer.unmap();\n }\n }\n\n return this._buffer;\n }\n\n get destroyed() {\n return this._destroyed;\n }\n\n $name(label: string) {\n setName(this, label);\n if (this._buffer) {\n this._buffer.label = label;\n }\n return this;\n }\n\n $usage<T extends ('uniform' | 'storage' | 'vertex' | 'index')[]>(\n ...usages: T\n ): this & UnionToIntersection<LiteralToUsageType<T[number]>> {\n for (const usage of usages) {\n if (this._disallowedUsages?.includes(usage)) {\n throw new Error(\n `Buffer of type ${this.dataType.type} cannot be used as ${usage}`,\n );\n }\n\n this.flags |= usage === 'uniform' ? GPUBufferUsage.UNIFORM : 0;\n this.flags |= usage === 'storage' ? GPUBufferUsage.STORAGE : 0;\n this.flags |= usage === 'vertex' ? GPUBufferUsage.VERTEX : 0;\n this.flags |= usage === 'index' ? GPUBufferUsage.INDEX : 0;\n this.usableAsUniform = this.usableAsUniform || usage === 'uniform';\n this.usableAsStorage = this.usableAsStorage || usage === 'storage';\n this.usableAsVertex = this.usableAsVertex || usage === 'vertex';\n this.usableAsIndex = this.usableAsIndex || usage === 'index';\n }\n return this as this & UnionToIntersection<LiteralToUsageType<T[number]>>;\n }\n\n $addFlags(flags: GPUBufferUsageFlags) {\n if (!this._ownBuffer) {\n throw new Error(\n 'Cannot add flags to a buffer that is not managed by TypeGPU.',\n );\n }\n\n if (flags & GPUBufferUsage.MAP_READ) {\n this.flags = GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ;\n return this;\n }\n\n if (flags & GPUBufferUsage.MAP_WRITE) {\n this.flags = GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE;\n return this;\n }\n\n this.flags |= flags;\n return this;\n }\n\n compileWriter(): void {\n getCompiledWriterForSchema(this.dataType);\n }\n\n private _writeToTarget(\n target: ArrayBuffer,\n data: Infer<TData>,\n ): void {\n const compiledWriter = getCompiledWriterForSchema(this.dataType);\n\n if (compiledWriter) {\n try {\n compiledWriter(\n new DataView(target),\n 0,\n data,\n endianness === 'little',\n );\n return;\n } catch (error) {\n console.error(\n `Error when using compiled writer for buffer ${\n getName(this) ?? '<unnamed>'\n } - this is likely a bug, please submit an issue at https://github.com/software-mansion/TypeGPU/issues\\nUsing fallback writer instead.`,\n error,\n );\n }\n }\n\n writeData(new BufferWriter(target), this.dataType, data);\n }\n\n write(data: Infer<TData>): void {\n const gpuBuffer = this.buffer;\n\n if (gpuBuffer.mapState === 'mapped') {\n const mapped = gpuBuffer.getMappedRange();\n this._writeToTarget(mapped, data);\n return;\n }\n\n const size = sizeOf(this.dataType);\n if (!this._hostBuffer) {\n this._hostBuffer = new ArrayBuffer(size);\n }\n\n this._writeToTarget(this._hostBuffer, data);\n this.#device.queue.writeBuffer(gpuBuffer, 0, this._hostBuffer, 0, size);\n }\n\n public writePartial(data: InferPartial<TData>): void {\n const gpuBuffer = this.buffer;\n\n const instructions = getWriteInstructions(this.dataType, data);\n\n if (gpuBuffer.mapState === 'mapped') {\n const mappedRange = gpuBuffer.getMappedRange();\n const mappedView = new Uint8Array(mappedRange);\n\n for (const instruction of instructions) {\n mappedView.set(instruction.data, instruction.data.byteOffset);\n }\n } else {\n for (const instruction of instructions) {\n this.#device.queue.writeBuffer(\n gpuBuffer,\n instruction.data.byteOffset,\n instruction.data,\n 0,\n instruction.data.byteLength,\n );\n }\n }\n }\n\n public clear(): void {\n const gpuBuffer = this.buffer;\n\n if (gpuBuffer.mapState === 'mapped') {\n new Uint8Array(gpuBuffer.getMappedRange()).fill(0);\n return;\n }\n\n const encoder = this.#device.createCommandEncoder();\n encoder.clearBuffer(gpuBuffer);\n this.#device.queue.submit([encoder.finish()]);\n }\n\n copyFrom(srcBuffer: TgpuBuffer<MemIdentity<TData>>): void {\n if (this.buffer.mapState === 'mapped') {\n throw new Error('Cannot copy to a mapped buffer.');\n }\n\n const size = sizeOf(this.dataType);\n const encoder = this.#device.createCommandEncoder();\n encoder.copyBufferToBuffer(srcBuffer.buffer, 0, this.buffer, 0, size);\n this.#device.queue.submit([encoder.finish()]);\n }\n\n async read(): Promise<Infer<TData>> {\n const gpuBuffer = this.buffer;\n\n if (gpuBuffer.mapState === 'mapped') {\n const mapped = gpuBuffer.getMappedRange();\n return readData(new BufferReader(mapped), this.dataType);\n }\n\n if (gpuBuffer.usage & GPUBufferUsage.MAP_READ) {\n await gpuBuffer.mapAsync(GPUMapMode.READ);\n const mapped = gpuBuffer.getMappedRange();\n const res = readData(new BufferReader(mapped), this.dataType);\n gpuBuffer.unmap();\n return res;\n }\n\n const stagingBuffer = this.#device.createBuffer({\n size: sizeOf(this.dataType),\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n\n const commandEncoder = this.#device.createCommandEncoder();\n commandEncoder.copyBufferToBuffer(\n gpuBuffer,\n 0,\n stagingBuffer,\n 0,\n sizeOf(this.dataType),\n );\n\n this.#device.queue.submit([commandEncoder.finish()]);\n await stagingBuffer.mapAsync(GPUMapMode.READ, 0, sizeOf(this.dataType));\n\n const res = readData(\n new BufferReader(stagingBuffer.getMappedRange()),\n this.dataType,\n );\n\n stagingBuffer.unmap();\n stagingBuffer.destroy();\n\n return res;\n }\n\n as<T extends ViewUsages<this>>(usage: T): UsageTypeToBufferUsage<TData>[T] {\n return usageToUsageConstructor[usage]?.(\n this as never,\n ) as UsageTypeToBufferUsage<TData>[T];\n }\n\n destroy() {\n if (this._destroyed) {\n return;\n }\n this._destroyed = true;\n if (this._ownBuffer) {\n this._buffer?.destroy();\n }\n }\n\n toString(): string {\n return `buffer:${getName(this) ?? '<unnamed>'}`;\n }\n}\n","import {\n getDeviceTextureFormatInfo,\n textureFormats,\n} from './textureFormats.ts';\nimport type { ExternalImageSource } from './texture.ts';\n\nexport function getImageSourceDimensions(\n source: ExternalImageSource,\n): { width: number; height: number } {\n if ('displayWidth' in source && 'displayHeight' in source) {\n return { width: source.displayWidth, height: source.displayHeight };\n }\n return { width: source.width as number, height: source.height as number };\n}\n\ntype CachedResources = {\n vertexShader: GPUShaderModule;\n fragmentShader: GPUShaderModule;\n bindGroupLayout: GPUBindGroupLayout;\n pipelineLayout: GPUPipelineLayout;\n sampler: GPUSampler;\n};\n\nconst deviceResourceCache = new WeakMap<\n GPUDevice,\n Map<string, CachedResources>\n>();\n\nfunction getDeviceCache(device: GPUDevice): Map<string, CachedResources> {\n let cache = deviceResourceCache.get(device);\n if (!cache) {\n cache = new Map<string, CachedResources>();\n deviceResourceCache.set(device, cache);\n }\n return cache;\n}\n\nexport function clearTextureUtilsCache(device: GPUDevice): void {\n const cache = deviceResourceCache.get(device);\n if (cache) {\n cache.clear();\n }\n}\n\nexport function generateTextureMipmaps(\n device: GPUDevice,\n texture: GPUTexture,\n baseMipLevel = 0,\n mipLevels?: number,\n) {\n if (texture.dimension !== '2d') {\n throw new Error(\n 'Cannot generate mipmaps for non-2D textures: only 2D textures are currently supported.',\n );\n }\n\n const actualMipLevels = mipLevels ?? (texture.mipLevelCount - baseMipLevel);\n const formatInfo = getDeviceTextureFormatInfo(texture.format, device);\n\n const hasFloatSampleType = [...formatInfo.sampleTypes].some((type) =>\n type === 'float' || type === 'unfilterable-float'\n );\n\n if (!hasFloatSampleType) {\n throw new Error(\n `Cannot generate mipmaps for format '${texture.format}': only float and unfilterable-float formats are currently supported.`,\n );\n }\n\n if (!formatInfo.canRenderAttachment) {\n throw new Error(\n `Cannot generate mipmaps for format '${texture.format}': format does not support render attachments.`,\n );\n }\n\n // Generate mipmaps for all layers\n for (let layer = 0; layer < texture.depthOrArrayLayers; layer++) {\n for (\n let mip = baseMipLevel;\n mip < baseMipLevel + actualMipLevels - 1;\n mip++\n ) {\n const srcMipLevel = mip;\n const dstMipLevel = mip + 1;\n\n generateMipmapLevel(device, texture, srcMipLevel, dstMipLevel, layer);\n }\n }\n}\n\nexport function resampleImage(\n device: GPUDevice,\n targetTexture: GPUTexture,\n image: ExternalImageSource,\n layer?: number,\n) {\n if (targetTexture.dimension === '3d') {\n throw new Error(\n 'Cannot resample to 3D textures: only 2D textures are currently supported.',\n );\n }\n\n const formatInfo = textureFormats[targetTexture.format];\n\n const hasFloatSampleType = [...formatInfo.sampleTypes].some((type) =>\n type === 'float' || type === 'unfilterable-float'\n );\n\n if (!hasFloatSampleType) {\n throw new Error(\n `Cannot resample to format '${targetTexture.format}': only float and unfilterable-float formats are currently supported.`,\n );\n }\n\n if (!formatInfo.canRenderAttachment) {\n throw new Error(\n `Cannot resample to format '${targetTexture.format}': format does not support render attachments.`,\n );\n }\n\n return resampleWithRenderPipeline(device, targetTexture, image, layer);\n}\n\nfunction resampleWithRenderPipeline(\n device: GPUDevice,\n targetTexture: GPUTexture,\n image: ExternalImageSource,\n layer = 0,\n) {\n const formatInfo = textureFormats[targetTexture.format];\n const canFilter = [...formatInfo.sampleTypes].includes('float');\n\n const cacheKey = `${canFilter ? 'filterable' : 'unfilterable'}`;\n\n const deviceCache = getDeviceCache(device);\n let cached = deviceCache.get(cacheKey);\n if (!cached) {\n const vertexShader = device.createShaderModule({\n code: `\nstruct VertexOutput {\n @builtin(position) pos: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n let pos = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));\n let uv = array<vec2f, 3>(vec2f(0, 1), vec2f(2, 1), vec2f(0, -1));\n\n var output: VertexOutput;\n output.pos = vec4f(pos[vertexIndex], 0, 1);\n output.uv = uv[vertexIndex];\n return output;\n}\n `,\n });\n\n const sampler = device.createSampler({\n magFilter: canFilter ? 'linear' : 'nearest',\n minFilter: canFilter ? 'linear' : 'nearest',\n });\n\n const bindGroupLayout = device.createBindGroupLayout({\n entries: [\n {\n binding: 0,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: 'float',\n },\n },\n {\n binding: 1,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: {\n type: canFilter ? 'filtering' : 'non-filtering',\n },\n },\n ],\n });\n\n const pipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [bindGroupLayout],\n });\n\n const fragmentShader = device.createShaderModule({\n code: `\n@group(0) @binding(0) var inputTexture: texture_2d<f32>;\n@group(0) @binding(1) var inputSampler: sampler;\n\n@fragment\nfn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {\n ${\n canFilter\n ? 'return textureSample(inputTexture, inputSampler, uv);'\n : `let texelCoord = vec2u(uv * vec2f(textureDimensions(inputTexture)));\n return textureLoad(inputTexture, texelCoord, 0);`\n }\n}\n `,\n });\n\n cached = {\n vertexShader,\n fragmentShader,\n bindGroupLayout,\n pipelineLayout,\n sampler,\n };\n deviceCache.set(cacheKey, cached);\n }\n\n const inputTexture = device.createTexture({\n size: [...Object.values(getImageSourceDimensions(image))],\n format: 'rgba8unorm',\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT |\n GPUTextureUsage.COPY_DST,\n });\n\n const { width, height } = getImageSourceDimensions(image);\n device.queue.copyExternalImageToTexture(\n { source: image },\n { texture: inputTexture },\n [width, height, 1],\n );\n\n const renderTexture = device.createTexture({\n size: [targetTexture.width, targetTexture.height, 1],\n format: targetTexture.format,\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,\n });\n\n const pipeline = device.createRenderPipeline({\n layout: cached.pipelineLayout,\n vertex: {\n module: cached.vertexShader,\n },\n fragment: {\n module: cached.fragmentShader,\n targets: [\n {\n format: targetTexture.format,\n },\n ],\n },\n primitive: {\n topology: 'triangle-list',\n },\n });\n\n const bindGroup = device.createBindGroup({\n layout: cached.bindGroupLayout,\n entries: [\n {\n binding: 0,\n resource: inputTexture.createView(),\n },\n {\n binding: 1,\n resource: cached.sampler,\n },\n ],\n });\n\n const encoder = device.createCommandEncoder();\n const renderPass = encoder.beginRenderPass({\n colorAttachments: [\n {\n view: renderTexture.createView(),\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n });\n\n renderPass.setPipeline(pipeline);\n renderPass.setBindGroup(0, bindGroup);\n renderPass.draw(3);\n renderPass.end();\n\n encoder.copyTextureToTexture(\n { texture: renderTexture },\n {\n texture: targetTexture,\n origin: { x: 0, y: 0, z: layer },\n },\n {\n width: targetTexture.width,\n height: targetTexture.height,\n depthOrArrayLayers: 1,\n },\n );\n\n device.queue.submit([encoder.finish()]);\n\n inputTexture.destroy();\n renderTexture.destroy();\n}\n\nfunction generateMipmapLevel(\n device: GPUDevice,\n texture: GPUTexture,\n srcMipLevel: number,\n dstMipLevel: number,\n layer?: number,\n) {\n const formatInfo = getDeviceTextureFormatInfo(texture.format, device);\n const canFilter = [...formatInfo.sampleTypes].includes('float');\n\n const cacheKey = `${canFilter ? 'filterable' : 'unfilterable'}`;\n\n const deviceCache = getDeviceCache(device);\n let cached = deviceCache.get(cacheKey);\n if (!cached) {\n const vertexShader = device.createShaderModule({\n code: `\nstruct VertexOutput {\n @builtin(position) pos: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {\n let pos = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));\n let uv = array<vec2f, 3>(vec2f(0, 1), vec2f(2, 1), vec2f(0, -1));\n\n var output: VertexOutput;\n output.pos = vec4f(pos[vertexIndex], 0, 1);\n output.uv = uv[vertexIndex];\n return output;\n}\n `,\n });\n\n const fragmentShader = device.createShaderModule({\n code: `\n@group(0) @binding(0) var inputTexture: texture_2d<f32>;\n@group(0) @binding(1) var inputSampler: sampler;\n\n@fragment\nfn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {\n return textureSample(inputTexture, inputSampler, uv);\n}\n `,\n });\n\n const sampler = device.createSampler({\n magFilter: canFilter ? 'linear' : 'nearest',\n minFilter: canFilter ? 'linear' : 'nearest',\n });\n\n const bindGroupLayout = device.createBindGroupLayout({\n entries: [\n {\n binding: 0,\n visibility: GPUShaderStage.FRAGMENT,\n texture: {\n sampleType: canFilter ? 'float' : 'unfilterable-float',\n },\n },\n {\n binding: 1,\n visibility: GPUShaderStage.FRAGMENT,\n sampler: {\n type: canFilter ? 'filtering' : 'non-filtering',\n },\n },\n ],\n });\n\n const pipelineLayout = device.createPipelineLayout({\n bindGroupLayouts: [bindGroupLayout],\n });\n\n cached = {\n vertexShader,\n fragmentShader,\n bindGroupLayout,\n pipelineLayout,\n sampler,\n };\n deviceCache.set(cacheKey, cached);\n }\n\n const pipeline = device.createRenderPipeline({\n layout: cached.pipelineLayout,\n vertex: {\n module: cached.vertexShader,\n },\n fragment: {\n module: cached.fragmentShader,\n targets: [\n {\n format: texture.format,\n },\n ],\n },\n primitive: {\n topology: 'triangle-list',\n },\n });\n\n const srcTextureView = texture.createView({\n baseMipLevel: srcMipLevel,\n dimension: '2d',\n mipLevelCount: 1,\n ...(layer !== undefined && {\n baseArrayLayer: layer,\n arrayLayerCount: 1,\n }),\n });\n\n const dstTextureView = texture.createView({\n baseMipLevel: dstMipLevel,\n dimension: '2d',\n mipLevelCount: 1,\n ...(layer !== undefined && {\n baseArrayLayer: layer,\n arrayLayerCount: 1,\n }),\n });\n\n const bindGroup = device.createBindGroup({\n layout: cached.bindGroupLayout,\n entries: [\n {\n binding: 0,\n resource: srcTextureView,\n },\n {\n binding: 1,\n resource: cached.sampler,\n },\n ],\n });\n\n const encoder = device.createCommandEncoder();\n const renderPass = encoder.beginRenderPass({\n colorAttachments: [\n {\n view: dstTextureView,\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n });\n\n renderPass.setPipeline(pipeline);\n renderPass.setBindGroup(0, bindGroup);\n renderPass.draw(3);\n renderPass.end();\n\n device.queue.submit([encoder.finish()]);\n}\n","import {\n isWgslStorageTexture,\n textureDescriptorToSchema,\n type TextureSchemaForDescriptor,\n type WgslStorageTexture,\n type WgslTexture,\n type WgslTextureProps,\n} from '../../data/texture.ts';\nimport { inCodegenMode } from '../../execMode.ts';\nimport { type ResolvedSnippet, snip } from '../../data/snippet.ts';\nimport type { Vec4f, Vec4i, Vec4u } from '../../data/wgslTypes.ts';\nimport type { TgpuNamable } from '../../shared/meta.ts';\nimport { getName, setName } from '../../shared/meta.ts';\nimport type { Infer, ValidateTextureViewSchema } from '../../shared/repr.ts';\nimport type {\n TextureFormatInfo,\n ViewDimensionToDimension,\n} from './textureFormats.ts';\nimport {\n $gpuValueOf,\n $internal,\n $ownSnippet,\n $repr,\n $resolve,\n} from '../../shared/symbols.ts';\nimport type {\n Default,\n TypedArray,\n UnionToIntersection,\n} from '../../shared/utilityTypes.ts';\nimport type { LayoutMembership } from '../../tgpuBindGroupLayout.ts';\nimport type { ResolutionCtx, SelfResolvable } from '../../types.ts';\nimport type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';\nimport { valueProxyHandler } from '../valueProxyUtils.ts';\nimport { type TextureFormats, textureFormats } from './textureFormats.ts';\nimport type { TextureProps } from './textureProps.ts';\nimport type { AllowedUsages, LiteralToExtensionMap } from './usageExtension.ts';\nimport {\n generateTextureMipmaps,\n getImageSourceDimensions,\n resampleImage,\n} from './textureUtils.ts';\n\ntype TextureInternals = {\n unwrap(): GPUTexture;\n};\n\ntype TextureViewInternals = {\n readonly unwrap: (() => GPUTextureView) | undefined;\n};\n\n// ----------\n// Public API\n// ----------\n\nexport type TexelData = Vec4u | Vec4i | Vec4f;\n\nexport type ExternalImageSource =\n | HTMLCanvasElement\n | HTMLImageElement\n | HTMLVideoElement\n | ImageBitmap\n | ImageData\n | OffscreenCanvas\n | VideoFrame;\n\ntype TgpuTextureViewDescriptor = {\n /**\n * Which {@link GPUTextureAspect | aspect(s)} of the texture are accessible to the texture view.\n */\n aspect?: GPUTextureAspect;\n /**\n * The first (most detailed) mipmap level accessible to the texture view.\n */\n baseMipLevel?: GPUIntegerCoordinate;\n /**\n * How many mipmap levels, starting with {@link GPUTextureViewDescriptor#baseMipLevel}, are accessible to\n * the texture view.\n */\n mipLevelCount?: GPUIntegerCoordinate;\n /**\n * The index of the first array layer accessible to the texture view.\n */\n baseArrayLayer?: GPUIntegerCoordinate;\n /**\n * How many array layers, starting with {@link GPUTextureViewDescriptor#baseArrayLayer}, are accessible\n * to the texture view.\n */\n arrayLayerCount?: GPUIntegerCoordinate;\n /**\n * The format of the texture view. Must be either the {@link GPUTextureDescriptor#format} of the\n * texture or one of the {@link GPUTextureDescriptor#viewFormats} specified during its creation.\n */\n format?: GPUTextureFormat;\n};\n\ntype DefaultViewSchema<T extends Partial<TextureProps>> =\n TextureSchemaForDescriptor<{\n dimension: Default<T['dimension'], '2d'>;\n sampleType: T['format'] extends keyof TextureFormats\n ? TextureFormats[T['format']]['channelType']\n : TextureFormats[keyof TextureFormats]['channelType'];\n multisampled: Default<T['sampleCount'], 1> extends 1 ? false : true;\n }>;\n\ntype BaseDimension<T extends string> = T extends keyof ViewDimensionToDimension\n ? ViewDimensionToDimension[T]\n : never;\n\ntype OptionalDimension<T extends string> = T extends\n | '2d'\n | '2d-array'\n | 'cube'\n | 'cube-array' ? { dimension?: BaseDimension<T> }\n : { dimension: BaseDimension<T> };\n\ntype MultisampledProps<T extends WgslTexture> = T['multisampled'] extends true\n ? OptionalDimension<T['dimension']> & { sampleCount: 4 }\n : OptionalDimension<T['dimension']> & { sampleCount?: 1 };\n\nexport type PropsForSchema<T extends WgslTexture | WgslStorageTexture> =\n T extends WgslTexture ? {\n size: readonly number[];\n format: GPUTextureFormat;\n } & MultisampledProps<T>\n : T extends WgslStorageTexture ? {\n size: readonly number[];\n format: T['format'];\n } & OptionalDimension<T['dimension']>\n : never;\n\nfunction getDescriptorForProps<T extends TextureProps>(\n props: T,\n): WgslTextureProps {\n return {\n dimension: (props.dimension ?? '2d') as Default<T['dimension'], '2d'>,\n sampleType: textureFormats[props.format].channelType,\n multisampled: !((props.sampleCount ?? 1) === 1) as Default<\n T['sampleCount'],\n 1\n > extends 1 ? false\n : true,\n };\n}\n\nexport type TextureAspect = 'color' | 'depth' | 'stencil';\nexport type DepthStencilFormats =\n | 'depth24plus-stencil8'\n | 'depth32float-stencil8';\nexport type DepthFormats = 'depth16unorm' | 'depth24plus' | 'depth32float';\nexport type StencilFormats = 'stencil8';\n\nexport type AspectsForFormat<T extends GPUTextureFormat> =\n GPUTextureFormat extends T ? TextureAspect[]\n : T extends DepthStencilFormats ? ('depth' | 'stencil')[]\n : T extends DepthFormats ? 'depth'[]\n : T extends StencilFormats ? 'stencil'[]\n : 'color'[];\n\nfunction getAspectsForFormat<T extends GPUTextureFormat>(\n format: T,\n): AspectsForFormat<T> {\n if (format === 'depth24plus-stencil8' || format === 'depth32float-stencil8') {\n return ['depth', 'stencil'] as AspectsForFormat<T>;\n }\n if (\n format === 'depth16unorm' ||\n format === 'depth24plus' ||\n format === 'depth32float'\n ) {\n return ['depth'] as AspectsForFormat<T>;\n }\n if (format === 'stencil8') {\n return ['stencil'] as AspectsForFormat<T>;\n }\n return ['color'] as AspectsForFormat<T>;\n}\n\ntype CopyCompatibleTexture<T extends TextureProps> = TgpuTexture<{\n size: T['size'];\n format: T['format'];\n sampleCount?: T['sampleCount'];\n}>;\n\n/**\n * @param TProps all properties that distinguish this texture apart from other textures on the type level.\n */\nexport interface TgpuTexture<TProps extends TextureProps = TextureProps>\n extends TgpuNamable {\n readonly [$internal]: TextureInternals;\n readonly resourceType: 'texture';\n readonly props: TProps; // <- storing to be able to differentiate structurally between different textures.\n readonly aspects: AspectsForFormat<TProps['format']>;\n readonly destroyed: boolean;\n\n // Extensions\n readonly usableAsStorage: boolean;\n readonly usableAsSampled: boolean;\n readonly usableAsRender: boolean;\n\n $usage<T extends AllowedUsages<TProps>[]>(\n ...usages: T\n ): this & UnionToIntersection<LiteralToExtensionMap[T[number]]>;\n\n createView(\n ...args: this['usableAsSampled'] extends true ? []\n : [ValidateTextureViewSchema<this, WgslTexture>]\n ): TgpuTextureView<DefaultViewSchema<TProps>>;\n createView<T extends WgslTexture | WgslStorageTexture>(\n schema: ValidateTextureViewSchema<this, T>,\n viewDescriptor?: TgpuTextureViewDescriptor & {\n sampleType?: T extends WgslTexture\n ? T['sampleType']['type'] extends 'f32' ? 'float' | 'unfilterable-float'\n : never\n : never;\n },\n ): TgpuTextureView<T>;\n\n clear(mipLevel?: number | 'all'): void;\n generateMipmaps(baseMipLevel?: number, mipLevels?: number): void;\n write(source: ExternalImageSource | ExternalImageSource[]): void;\n write(source: ArrayBuffer | TypedArray | DataView, mipLevel?: number): void;\n // TODO: support copies from GPUBuffers and TgpuBuffers\n copyFrom<T extends CopyCompatibleTexture<TProps>>(source: T): void;\n\n destroy(): void;\n}\n\nexport interface TgpuTextureView<\n TSchema extends WgslStorageTexture | WgslTexture =\n | WgslStorageTexture\n | WgslTexture,\n> {\n readonly [$internal]: TextureViewInternals;\n readonly resourceType: 'texture-view';\n readonly schema: TSchema;\n\n readonly [$gpuValueOf]: Infer<TSchema>;\n value: Infer<TSchema>;\n $: Infer<TSchema>;\n}\n\nexport function INTERNAL_createTexture(\n props: TextureProps,\n branch: ExperimentalTgpuRoot,\n): TgpuTexture<TextureProps> {\n return new TgpuTextureImpl(props, branch);\n}\n\nexport function isTexture<T extends TgpuTexture>(\n value: unknown | T,\n): value is T {\n return (value as T)?.resourceType === 'texture' && !!(value as T)[$internal];\n}\n\nexport function isTextureView<T extends TgpuTextureView>(\n value: unknown | T,\n): value is T {\n return (\n (value as T)?.resourceType === 'texture-view' && !!(value as T)[$internal]\n );\n}\n\n// --------------\n// Implementation\n// --------------\n\nclass TgpuTextureImpl<TProps extends TextureProps>\n implements TgpuTexture<TProps> {\n readonly [$internal]: TextureInternals;\n readonly resourceType = 'texture';\n readonly aspects: AspectsForFormat<this['props']['format']>;\n usableAsSampled = false;\n usableAsStorage = false;\n usableAsRender = false;\n\n #formatInfo: TextureFormatInfo;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: wdym, it is used 10 lines below\n #byteSize: number;\n #destroyed = false;\n #flags = GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC;\n #texture: GPUTexture | null = null;\n #branch: ExperimentalTgpuRoot;\n\n constructor(\n public readonly props: TProps,\n branch: ExperimentalTgpuRoot,\n ) {\n const format = props.format as TProps['format'];\n\n this.#branch = branch;\n this.#formatInfo = textureFormats[format];\n this.#byteSize = (props.size[0] as number) *\n (props.size[1] ?? 1) *\n (props.size[2] ?? 1) *\n this.#formatInfo.texelSize;\n this.aspects = getAspectsForFormat(format);\n\n this[$internal] = {\n unwrap: () => {\n if (this.#destroyed) {\n throw new Error('This texture has been destroyed');\n }\n\n if (!this.#texture) {\n this.#texture = branch.device.createTexture({\n label: getName(this) ?? '<unnamed>',\n format: props.format,\n size: props.size,\n usage: this.#flags,\n dimension: props.dimension ?? '2d',\n viewFormats: props.viewFormats ?? [],\n mipLevelCount: props.mipLevelCount ?? 1,\n sampleCount: props.sampleCount ?? 1,\n });\n }\n\n return this.#texture;\n },\n };\n }\n\n $name(label: string) {\n setName(this, label);\n return this;\n }\n\n $usage<T extends ('sampled' | 'storage' | 'render')[]>(\n ...usages: T\n ): this & UnionToIntersection<LiteralToExtensionMap[T[number]]> {\n const hasStorage = usages.includes('storage');\n const hasSampled = usages.includes('sampled');\n const hasRender = usages.includes('render');\n this.#flags |= hasSampled ? GPUTextureUsage.TEXTURE_BINDING : 0;\n this.#flags |= hasStorage ? GPUTextureUsage.STORAGE_BINDING : 0;\n this.#flags |= hasRender ? GPUTextureUsage.RENDER_ATTACHMENT : 0;\n this.usableAsStorage ||= hasStorage;\n this.usableAsSampled ||= hasSampled;\n this.usableAsRender ||= hasRender;\n\n return this as this & UnionToIntersection<LiteralToExtensionMap[T[number]]>;\n }\n\n createView(\n ...args: this['usableAsSampled'] extends true ? [] : [never]\n ): TgpuTextureView<DefaultViewSchema<TProps>>;\n createView<T extends WgslTexture | WgslStorageTexture>(\n schema: never,\n viewDescriptor?: TgpuTextureViewDescriptor & {\n sampleType?: T extends WgslTexture\n ? T['sampleType']['type'] extends 'f32' ? 'float' | 'unfilterable-float'\n : never\n : never;\n },\n ): TgpuTextureView<T>;\n createView<T extends WgslTexture | WgslStorageTexture>(\n schema?: never,\n viewDescriptor?: TgpuTextureViewDescriptor & {\n sampleType?: T extends WgslTexture\n ? T['sampleType']['type'] extends 'f32' ? 'float' | 'unfilterable-float'\n : never\n : never;\n },\n ): TgpuTextureView<T> {\n return new TgpuFixedTextureViewImpl(\n schema ??\n (textureDescriptorToSchema(getDescriptorForProps(this.props)) as T),\n this as TgpuTexture,\n viewDescriptor,\n );\n }\n\n #clearMipLevel(mip = 0) {\n const scale = 2 ** mip;\n const [width, height, depth] = [\n Math.max(1, Math.floor((this.props.size[0] ?? 1) / scale)),\n Math.max(1, Math.floor((this.props.size[1] ?? 1) / scale)),\n Math.max(1, Math.floor((this.props.size[2] ?? 1) / scale)),\n ];\n\n this.#branch.device.queue.writeTexture(\n { texture: this[$internal].unwrap(), mipLevel: mip },\n new Uint8Array(width * height * depth * this.#formatInfo.texelSize),\n { bytesPerRow: this.#formatInfo.texelSize * width, rowsPerImage: height },\n [width, height, depth],\n );\n }\n\n clear(mipLevel: number | 'all' = 'all') {\n if (mipLevel === 'all') {\n const mipLevels = this.props.mipLevelCount ?? 1;\n for (let i = 0; i < mipLevels; i++) {\n this.#clearMipLevel(i);\n }\n } else {\n this.#clearMipLevel(mipLevel);\n }\n }\n\n generateMipmaps(baseMipLevel = 0, mipLevels?: number) {\n if (this.usableAsRender === false) {\n throw new Error(\n \"generateMipmaps called without specifying 'render' usage. Add it via the $usage('render') method.\",\n );\n }\n\n const actualMipLevels = mipLevels ??\n (this.props.mipLevelCount ?? 1) - baseMipLevel;\n\n if (actualMipLevels <= 1) {\n console.warn(\n `generateMipmaps is a no-op: would generate ${actualMipLevels} mip levels (base: ${baseMipLevel}, total: ${\n this.props.mipLevelCount ?? 1\n })`,\n );\n return;\n }\n\n if (baseMipLevel >= (this.props.mipLevelCount ?? 1)) {\n throw new Error(\n `Base mip level ${baseMipLevel} is out of range. Texture has ${\n this.props.mipLevelCount ?? 1\n } mip levels.`,\n );\n }\n\n generateTextureMipmaps(\n this.#branch.device,\n this[$internal].unwrap(),\n baseMipLevel,\n actualMipLevels,\n );\n }\n\n write(source: ExternalImageSource | ExternalImageSource[]): void;\n write(source: ArrayBuffer | TypedArray | DataView, mipLevel?: number): void;\n write(\n source:\n | ExternalImageSource\n | ExternalImageSource[]\n | ArrayBuffer\n | TypedArray\n | DataView,\n mipLevel = 0,\n ) {\n if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {\n this.#writeBufferData(source, mipLevel);\n return;\n }\n\n const dimension = this.props.dimension ?? '2d';\n const isArray = Array.isArray(source);\n\n if (!isArray) {\n this.#writeSingleLayer(source, dimension === '3d' ? 0 : undefined);\n return;\n }\n\n const layerCount = this.props.size[2] ?? 1;\n if (source.length > layerCount) {\n console.warn(\n `Too many image sources provided. Expected ${layerCount} layers, got ${source.length}. Extra sources will be ignored.`,\n );\n }\n\n for (let layer = 0; layer < Math.min(source.length, layerCount); layer++) {\n const bitmap = source[layer];\n if (bitmap) {\n this.#writeSingleLayer(bitmap, layer);\n }\n }\n }\n\n #writeBufferData(\n source: ArrayBuffer | TypedArray | DataView,\n mipLevel: number,\n ) {\n const mipWidth = Math.max(1, (this.props.size[0] as number) >> mipLevel);\n const mipHeight = Math.max(1, (this.props.size[1] ?? 1) >> mipLevel);\n const mipDepth = Math.max(1, (this.props.size[2] ?? 1) >> mipLevel);\n\n const expectedSize = mipWidth * mipHeight * mipDepth *\n this.#formatInfo.texelSize;\n const actualSize = source.byteLength ?? (source as ArrayBuffer).byteLength;\n\n if (actualSize !== expectedSize) {\n throw new Error(\n `Buffer size mismatch. Expected ${expectedSize} bytes for mip level ${mipLevel}, got ${actualSize} bytes.`,\n );\n }\n\n this.#branch.device.queue.writeTexture(\n {\n texture: this[$internal].unwrap(),\n mipLevel,\n },\n source,\n {\n bytesPerRow: this.#formatInfo.texelSize * mipWidth,\n rowsPerImage: mipHeight,\n },\n [mipWidth, mipHeight, mipDepth],\n );\n }\n\n #writeSingleLayer(source: ExternalImageSource, layer?: number) {\n const targetWidth = this.props.size[0] as number;\n const targetHeight = (this.props.size[1] ?? 1) as number;\n const { width: sourceWidth, height: sourceHeight } =\n getImageSourceDimensions(source);\n const needsResampling = sourceWidth !== targetWidth ||\n sourceHeight !== targetHeight;\n\n if (needsResampling) {\n resampleImage(\n this.#branch.device,\n this[$internal].unwrap(),\n source,\n layer,\n );\n return;\n }\n\n this.#branch.device.queue.copyExternalImageToTexture(\n { source },\n {\n texture: this[$internal].unwrap(),\n ...(layer !== undefined && { origin: { x: 0, y: 0, z: layer } }),\n },\n layer !== undefined ? [targetWidth, targetHeight, 1] : this.props.size,\n );\n }\n\n copyFrom(source: CopyCompatibleTexture<TProps>) {\n if (source.props.format !== this.props.format) {\n throw new Error(\n `Texture format mismatch. Source texture has format ${source.props.format}, target texture has format ${this.props.format}`,\n );\n }\n if (\n source.props.size[0] !== this.props.size[0] ||\n (source.props.size[1] ?? 1) !== (this.props.size[1] ?? 1) ||\n (source.props.size[2] ?? 1) !== (this.props.size[2] ?? 1)\n ) {\n throw new Error(\n `Texture size mismatch. Source texture has size ${\n source.props.size.join(\n 'x',\n )\n }, target texture has size ${this.props.size.join('x')}`,\n );\n }\n\n const commandEncoder = this.#branch.device.createCommandEncoder();\n commandEncoder.copyTextureToTexture(\n { texture: source[$internal].unwrap() },\n { texture: this[$internal].unwrap() },\n source.props.size,\n );\n this.#branch.device.queue.submit([commandEncoder.finish()]);\n }\n\n get destroyed() {\n return this.#destroyed;\n }\n\n destroy() {\n if (this.#destroyed) {\n return;\n }\n this.#destroyed = true;\n this.#texture?.destroy();\n }\n}\n\nclass TgpuFixedTextureViewImpl<T extends WgslTexture | WgslStorageTexture>\n implements TgpuTextureView<T>, SelfResolvable, TgpuNamable {\n /** Type-token, not available at runtime */\n declare readonly [$repr]: Infer<T>;\n readonly [$internal]: TextureViewInternals;\n readonly resourceType = 'texture-view' as const;\n\n #baseTexture: TgpuTexture;\n #view: GPUTextureView | undefined;\n #descriptor:\n | (TgpuTextureViewDescriptor & {\n sampleType?: T extends WgslTexture ? 'float' | 'unfilterable-float'\n : never;\n })\n | undefined;\n\n constructor(\n readonly schema: T,\n baseTexture: TgpuTexture,\n descriptor?: TgpuTextureViewDescriptor,\n ) {\n this.#baseTexture = baseTexture;\n this.#descriptor = descriptor;\n\n this[$internal] = {\n unwrap: () => {\n if (!this.#view) {\n const schema = this.schema;\n let descriptor: GPUTextureViewDescriptor;\n if (isWgslStorageTexture(schema)) {\n descriptor = {\n label: getName(this) ?? '<unnamed>',\n format: this.#descriptor?.format ?? schema.format,\n dimension: schema.dimension,\n };\n } else {\n descriptor = {\n label: getName(this) ?? '<unnamed>',\n format: this.#descriptor?.format ??\n this.#baseTexture.props.format,\n dimension: schema.dimension,\n };\n }\n\n if (this.#descriptor?.mipLevelCount !== undefined) {\n descriptor.mipLevelCount = this.#descriptor.mipLevelCount;\n }\n if (this.#descriptor?.arrayLayerCount !== undefined) {\n descriptor.arrayLayerCount = this.#descriptor.arrayLayerCount;\n }\n\n this.#view = this.#baseTexture[$internal]\n .unwrap()\n .createView(descriptor);\n }\n return this.#view;\n },\n };\n }\n\n $name(label: string) {\n setName(this, label);\n if (this.#view) {\n this.#view.label = label;\n }\n return this;\n }\n\n get [$gpuValueOf](): Infer<T> {\n const schema = this.schema;\n\n return new Proxy(\n {\n [$internal]: true,\n get [$ownSnippet]() {\n return snip(this, schema);\n },\n [$resolve]: (ctx) => ctx.resolve(this),\n toString: () => `${this.toString()}.$`,\n },\n valueProxyHandler,\n ) as unknown as Infer<T>;\n }\n\n get $(): Infer<T> {\n if (inCodegenMode()) {\n return this[$gpuValueOf];\n }\n\n throw new Error(\n 'Direct access to texture view values is possible only as part of a compute dispatch or draw call. Try .read() or .write() instead',\n );\n }\n\n get value(): Infer<T> {\n return this.$;\n }\n\n toString() {\n return `textureView:${getName(this) ?? '<unnamed>'}`;\n }\n\n [$resolve](ctx: ResolutionCtx): ResolvedSnippet {\n const id = ctx.getUniqueName(this);\n const { group, binding } = ctx.allocateFixedEntry(\n isWgslStorageTexture(this.schema)\n ? {\n storageTexture: this.schema,\n }\n : {\n texture: this.schema,\n sampleType: this.#descriptor?.sampleType ??\n this.schema.bindingSampleType[0],\n },\n this,\n );\n\n ctx.addDeclaration(\n `@group(${group}) @binding(${binding}) var ${id}: ${\n ctx.resolve(this.schema).value\n };`,\n );\n\n return snip(id, this.schema);\n }\n}\n\nexport class TgpuLaidOutTextureViewImpl<\n T extends WgslTexture | WgslStorageTexture,\n> implements TgpuTextureView<T>, SelfResolvable {\n /** Type-token, not available at runtime */\n declare readonly [$repr]: Infer<T>;\n readonly [$internal] = { unwrap: undefined };\n readonly resourceType = 'texture-view' as const;\n readonly #membership: LayoutMembership;\n\n constructor(\n readonly schema: T,\n membership: LayoutMembership,\n ) {\n this.#membership = membership;\n setName(this, membership.key);\n }\n\n toString() {\n return `textureView:${getName(this) ?? '<unnamed>'}`;\n }\n\n [$resolve](ctx: ResolutionCtx): ResolvedSnippet {\n const id = ctx.getUniqueName(this);\n const group = ctx.allocateLayoutEntry(this.#membership.layout);\n\n ctx.addDeclaration(\n `@group(${group}) @binding(${this.#membership.idx}) var ${id}: ${\n ctx.resolve(this.schema).value\n };`,\n );\n\n return snip(id, this.schema);\n }\n\n get [$gpuValueOf](): Infer<T> {\n const schema = this.schema;\n return new Proxy(\n {\n [$internal]: true,\n get [$ownSnippet]() {\n return snip(this, schema);\n },\n [$resolve]: (ctx) => ctx.resolve(this),\n toString: () => `${this.toString()}.$`,\n },\n valueProxyHandler,\n ) as unknown as Infer<T>;\n }\n\n get $(): Infer<T> {\n if (inCodegenMode()) {\n return this[$gpuValueOf];\n }\n\n throw new Error(\n 'Direct access to texture views values is possible only as part of a compute dispatch or draw call. Try .read() or .write() instead',\n );\n }\n\n get value(): Infer<T> {\n return this.$;\n }\n}\n","import type { TgpuMutable } from '../../core/buffer/bufferShorthand.ts';\nimport { fn, type TgpuFn } from '../../core/function/tgpuFn.ts';\nimport { slot } from '../../core/slot/slot.ts';\nimport { privateVar } from '../../core/variable/tgpuVariable.ts';\nimport { mat2x2f, mat3x3f, mat4x4f } from '../../data/matrix.ts';\nimport { bool, f16, f32, i32, u32 } from '../../data/numeric.ts';\nimport { sizeOf } from '../../data/sizeOf.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} from '../../data/vector.ts';\nimport {\n type AnyWgslData,\n type Atomic,\n isWgslArray,\n isWgslStruct,\n type U32,\n type Void,\n type WgslArray,\n} from '../../data/wgslTypes.ts';\nimport { getName } from '../../shared/meta.ts';\nimport type { LogGeneratorOptions, SerializedLogCallData } from './types.ts';\n\n// --------------\n// Serializer map\n// --------------\n\ntype SerializerMap = {\n [K in AnyWgslData['type']]?: TgpuFn<\n (args_0: Extract<AnyWgslData, { type: K }>) => Void\n >;\n};\n\nconst dataBlockIndex = privateVar(u32, 0).$name('dataBlockIndex');\nconst dataByteIndex = privateVar(u32, 0).$name('dataByteIndex');\nconst dataBufferSlot = slot().$name('dataBuffer');\nconst nextByteIndex = fn([], u32)`() {\n let i = dataByteIndex;\n dataByteIndex = dataByteIndex + 1u;\n return i;\n}`.$uses({ dataByteIndex })\n .$name('nextByteIndex');\n\nconst nextU32 = 'dataBuffer[dataBlockIndex].serializedData[nextByteIndex()]';\n\nexport const serializerMap: SerializerMap = {\n f32: fn([f32])`(n) => {\n ${nextU32} = bitcast<u32>(n);\n}`,\n f16: fn([f16])`(n) => {\n ${nextU32} = pack2x16float(vec2f(f32(n), 0.0));\n}`,\n i32: fn([i32])`(n) => {\n ${nextU32} = bitcast<u32>(n);\n}`,\n u32: fn([u32])`(n) => {\n ${nextU32} = n;\n}`,\n bool: fn([bool])`(b) => {\n ${nextU32} = u32(b);\n}`,\n vec2f: fn([vec2f])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n}`,\n vec3f: fn([vec3f])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n ${nextU32} = bitcast<u32>(v.z);\n}`,\n vec4f: fn([vec4f])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n ${nextU32} = bitcast<u32>(v.z);\n ${nextU32} = bitcast<u32>(v.w);\n}`,\n vec2h: fn([vec2h])`(v) => {\n ${nextU32} = pack2x16float(vec2f(f32(v.x), f32(v.y)));\n}`,\n vec3h: fn([vec3h])`(v) => {\n ${nextU32} = pack2x16float(vec2f(f32(v.x), f32(v.y)));\n ${nextU32} = pack2x16float(vec2f(f32(v.z), 0.0));\n}`,\n vec4h: fn([vec4h])`(v) => {\n ${nextU32} = pack2x16float(vec2f(f32(v.x), f32(v.y)));\n ${nextU32} = pack2x16float(vec2f(f32(v.z), f32(v.w)));\n}`,\n vec2i: fn([vec2i])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n}`,\n vec3i: fn([vec3i])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n ${nextU32} = bitcast<u32>(v.z);\n}`,\n vec4i: fn([vec4i])`(v) => {\n ${nextU32} = bitcast<u32>(v.x);\n ${nextU32} = bitcast<u32>(v.y);\n ${nextU32} = bitcast<u32>(v.z);\n ${nextU32} = bitcast<u32>(v.w);\n}`,\n vec2u: fn([vec2u])`(v) => {\n ${nextU32} = v.x;\n ${nextU32} = v.y;\n}`,\n vec3u: fn([vec3u])`(v) => {\n ${nextU32} = v.x;\n ${nextU32} = v.y;\n ${nextU32} = v.z;\n}`,\n vec4u: fn([vec4u])`(v) => {\n ${nextU32} = v.x;\n ${nextU32} = v.y;\n ${nextU32} = v.z;\n ${nextU32} = v.w;\n}`,\n 'vec2<bool>': fn([vec2b])`(v) => {\n ${nextU32} = u32(v.x);\n ${nextU32} = u32(v.y);\n}`,\n 'vec3<bool>': fn([vec3b])`(v) => {\n ${nextU32} = u32(v.x);\n ${nextU32} = u32(v.y);\n ${nextU32} = u32(v.z);\n}`,\n 'vec4<bool>': fn([vec4b])`(v) => {\n ${nextU32} = u32(v.x);\n ${nextU32} = u32(v.y);\n ${nextU32} = u32(v.z);\n ${nextU32} = u32(v.w);\n}`,\n mat2x2f: fn([mat2x2f])`(m) => {\n ${nextU32} = bitcast<u32>(m[0][0]);\n ${nextU32} = bitcast<u32>(m[0][1]);\n ${nextU32} = bitcast<u32>(m[1][0]);\n ${nextU32} = bitcast<u32>(m[1][1]);\n}`,\n mat3x3f: fn([mat3x3f])`(m) => {\n ${nextU32} = bitcast<u32>(m[0][0]);\n ${nextU32} = bitcast<u32>(m[0][1]);\n ${nextU32} = bitcast<u32>(m[0][2]);\n ${nextU32} = 0u;\n ${nextU32} = bitcast<u32>(m[1][0]);\n ${nextU32} = bitcast<u32>(m[1][1]);\n ${nextU32} = bitcast<u32>(m[1][2]);\n ${nextU32} = 0u;\n ${nextU32} = bitcast<u32>(m[2][0]);\n ${nextU32} = bitcast<u32>(m[2][1]);\n ${nextU32} = bitcast<u32>(m[2][2]);\n ${nextU32} = 0u;\n}`,\n mat4x4f: fn([mat4x4f])`(m) => {\n ${nextU32} = bitcast<u32>(m[0][0]);\n ${nextU32} = bitcast<u32>(m[0][1]);\n ${nextU32} = bitcast<u32>(m[0][2]);\n ${nextU32} = bitcast<u32>(m[0][3]);\n ${nextU32} = bitcast<u32>(m[1][0]);\n ${nextU32} = bitcast<u32>(m[1][1]);\n ${nextU32} = bitcast<u32>(m[1][2]);\n ${nextU32} = bitcast<u32>(m[1][3]);\n ${nextU32} = bitcast<u32>(m[2][0]);\n ${nextU32} = bitcast<u32>(m[2][1]);\n ${nextU32} = bitcast<u32>(m[2][2]);\n ${nextU32} = bitcast<u32>(m[2][3]);\n ${nextU32} = bitcast<u32>(m[3][0]);\n ${nextU32} = bitcast<u32>(m[3][1]);\n ${nextU32} = bitcast<u32>(m[3][2]);\n ${nextU32} = bitcast<u32>(m[3][3]);\n}`,\n};\n\n// rename the functions and add externals\nfor (const [name, serializer] of Object.entries(serializerMap)) {\n serializer\n .$name(\n `serialize${(name[0] as string).toLocaleUpperCase()}${name.slice(1)}`,\n )\n .$uses({ dataBlockIndex, nextByteIndex, dataBuffer: dataBufferSlot });\n}\n\n// -------\n// Helpers\n// -------\n\nfunction generateHeader(argTypes: AnyWgslData[]): string {\n return `(${argTypes.map((_, i) => `_arg_${i}`).join(', ')})`;\n}\n\n/**\n * Returns a serializer TGPU function for a given WGSL data type.\n * If the data type is a base type, one of the preexisting functions (with the `dataBufferSlot` filled) is returned.\n * Otherwise, a new function is generated.\n *\n * @param dataType - The WGSL data type descriptor to return a serializer for\n * @param dataBuffer - A buffer to store serialized log call data (a necessary external for the returned function)\n */\nfunction getSerializer<T extends AnyWgslData>(\n dataType: T,\n dataBuffer: TgpuMutable<WgslArray<SerializedLogCallData>>,\n): TgpuFn<(args_0: T) => Void> {\n const maybeSerializer = serializerMap[dataType.type];\n if (maybeSerializer) {\n return (maybeSerializer as TgpuFn<(args_0: T) => Void>).with(\n dataBufferSlot,\n dataBuffer,\n );\n }\n if (isWgslStruct(dataType)) {\n const props = Object.keys(dataType.propTypes);\n const propTypes = Object.values(dataType.propTypes) as AnyWgslData[];\n const propsSerializer = createCompoundSerializer(propTypes, dataBuffer);\n return fn([dataType])`(arg) {\\n propsSerializer(${\n props.map((prop) => `arg.${prop}`).join(', ')\n });\\n}`\n .$uses({ propsSerializer })\n .$name(`${getName(dataType) ?? 'struct'}Serializer`);\n }\n if (isWgslArray(dataType)) {\n const elementType = dataType.elementType as AnyWgslData;\n const length = dataType.elementCount;\n const elementSerializer = getSerializer(elementType, dataBuffer);\n return fn([dataType])`(arg) {\\n${\n Array\n .from({ length }, (_, i) => ` elementSerializer(arg[${i}]);`)\n .join('\\n')\n }\\n}`\n .$uses({ elementSerializer })\n .$name('arraySerializer');\n }\n throw new Error(`Cannot serialize data of type ${dataType.type}`);\n}\n\n/**\n * Creates a compound serializer TGPU function that serializes multiple arguments of different types to the data buffer.\n *\n * @param dataTypes - Array of WGSL data types that define the types of arguments to be serialized\n * @param dataBuffer - A buffer to store serialized log call data (a necessary external for the returned function)\n */\nfunction createCompoundSerializer(\n dataTypes: AnyWgslData[],\n dataBuffer: TgpuMutable<WgslArray<SerializedLogCallData>>,\n) {\n const usedSerializers: Record<string, unknown> = {};\n\n const shell = fn(dataTypes);\n const header = generateHeader(dataTypes);\n const body = dataTypes.map((arg, i) => {\n usedSerializers[`serializer${i}`] = getSerializer(arg, dataBuffer);\n return ` serializer${i}(_arg_${i});`;\n }).join('\\n');\n\n return shell`${header} {\\n${body}\\n}`\n .$uses(usedSerializers)\n .$name('compoundSerializer');\n}\n\n/**\n * Creates a TGPU function that serializes data to the log buffer.\n *\n * @param id - Identifier for this logging function instance\n * @param dataTypes - Array of WGSL data types that will be logged by this function\n * @param dataBuffer - Mutable buffer array to store serialized log call data\n * @param indexBuffer - Atomic counter buffer to track the next available log data slot\n * @param logOptions - Configuration options\n */\nexport function createLoggingFunction(\n id: number,\n dataTypes: AnyWgslData[],\n dataBuffer: TgpuMutable<WgslArray<SerializedLogCallData>>,\n indexBuffer: TgpuMutable<Atomic<U32>>,\n logOptions: Required<LogGeneratorOptions>,\n): TgpuFn {\n const serializedSize = dataTypes.map(sizeOf).reduce((a, b) => a + b, 0);\n if (serializedSize > logOptions.logSizeLimit) {\n throw new Error(\n `Logged data needs to fit in ${logOptions.logSizeLimit} bytes (one of the logs requires ${serializedSize} bytes). Consider increasing the limit by passing appropriate options to tgpu.init().`,\n );\n }\n\n const compoundSerializer = createCompoundSerializer(dataTypes, dataBuffer)\n .$name(`log${id}serializer`);\n const header = generateHeader(dataTypes);\n\n return fn(dataTypes)`${header} {\n dataBlockIndex = atomicAdd(&indexBuffer, 1);\n if (dataBlockIndex >= ${logOptions.logCountLimit}) {\n return;\n }\n dataBuffer[dataBlockIndex].id = ${id};\n dataByteIndex = 0;\n\n compoundSerializer${header};\n}`.$uses({\n indexBuffer,\n dataBuffer,\n dataBlockIndex,\n dataByteIndex,\n compoundSerializer,\n }).$name(`log${id}`);\n}\n","import { isTgpuFn } from './core/function/tgpuFn.ts';\nimport {\n getUniqueName,\n type Namespace,\n type NamespaceInternal,\n} from './core/resolve/namespace.ts';\nimport { resolveData } from './core/resolve/resolveData.ts';\nimport { stitch } from './core/resolve/stitch.ts';\nimport { ConfigurableImpl } from './core/root/configurableImpl.ts';\nimport type {\n Configurable,\n ExperimentalTgpuRoot,\n} from './core/root/rootTypes.ts';\nimport {\n type Eventual,\n isDerived,\n isProviding,\n isSlot,\n type SlotValuePair,\n type TgpuDerived,\n type TgpuSlot,\n} from './core/slot/slotTypes.ts';\nimport { getAttributesString } from './data/attributes.ts';\nimport { type AnyData, isData, UnknownData } from './data/dataTypes.ts';\nimport { bool } from './data/numeric.ts';\nimport { type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts';\nimport { isWgslArray, isWgslStruct, Void } from './data/wgslTypes.ts';\nimport {\n invariant,\n MissingSlotValueError,\n ResolutionError,\n WgslTypeError,\n} from './errors.ts';\nimport { provideCtx, topLevelState } from './execMode.ts';\nimport { naturalsExcept } from './shared/generators.ts';\nimport { isMarkedInternal } from './shared/symbols.ts';\nimport type { Infer } from './shared/repr.ts';\nimport { safeStringify } from './shared/stringify.ts';\nimport { $internal, $providing, $resolve } from './shared/symbols.ts';\nimport {\n bindGroupLayout,\n type TgpuBindGroup,\n TgpuBindGroupImpl,\n type TgpuBindGroupLayout,\n type TgpuLayoutEntry,\n} from './tgpuBindGroupLayout.ts';\nimport {\n LogGeneratorImpl,\n LogGeneratorNullImpl,\n} from './tgsl/consoleLog/logGenerator.ts';\nimport type { LogGenerator, LogResources } from './tgsl/consoleLog/types.ts';\nimport { getBestConversion } from './tgsl/conversion.ts';\nimport {\n coerceToSnippet,\n concretize,\n numericLiteralToSnippet,\n} from './tgsl/generationHelpers.ts';\nimport type { ShaderGenerator } from './tgsl/shaderGenerator.ts';\nimport wgslGenerator from './tgsl/wgslGenerator.ts';\nimport type {\n ExecMode,\n ExecState,\n FnToWgslOptions,\n FunctionScopeLayer,\n ItemLayer,\n ItemStateStack,\n ResolutionCtx,\n Wgsl,\n} from './types.ts';\nimport { CodegenState, isSelfResolvable, NormalState } from './types.ts';\nimport type { WgslExtension } from './wgslExtensions.ts';\nimport { hasTinyestMetadata } from './shared/meta.ts';\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 enableExtensions?: WgslExtension[] | undefined;\n readonly shaderGenerator?: ShaderGenerator | undefined;\n readonly config?: ((cfg: Configurable) => Configurable) | undefined;\n readonly root?: ExperimentalTgpuRoot | undefined;\n readonly namespace: Namespace;\n};\n\ntype SlotBindingLayer = {\n type: 'slotBinding';\n bindingMap: WeakMap<TgpuSlot<unknown>, unknown>;\n};\n\ntype BlockScopeLayer = {\n type: 'blockScope';\n declarations: Map<string, Snippet>;\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 get topFunctionScope(): FunctionScopeLayer | undefined {\n return this._stack.findLast((e) => e.type === 'functionScope');\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: Snippet[],\n argAliases: Record<string, Snippet>,\n returnType: AnyData | undefined,\n externalMap: Record<string, unknown>,\n ): FunctionScopeLayer {\n const scope: FunctionScopeLayer = {\n type: 'functionScope',\n args,\n argAliases,\n returnType,\n externalMap,\n reportedReturnTypes: new Set(),\n };\n\n this._stack.push(scope);\n return scope;\n }\n\n popFunctionScope() {\n this.pop('functionScope');\n }\n\n pushBlockScope() {\n this._stack.push({\n type: 'blockScope',\n declarations: new Map(),\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 getSnippetById(id: string): Snippet | 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 if (layer.argAliases[id]) {\n return layer.argAliases[id];\n }\n\n const external = layer.externalMap[id];\n\n if (external !== undefined && external !== null) {\n return coerceToSnippet(external);\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 snippet = layer.declarations.get(id);\n if (snippet !== undefined) {\n return snippet;\n }\n } else {\n // Skip\n }\n }\n\n return undefined;\n }\n\n defineBlockVariable(id: string, snippet: Snippet): void {\n if (snippet.dataType.type === 'unknown') {\n throw Error(`Tried to define variable '${id}' of unknown type`);\n }\n\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, snippet);\n return;\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 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 withResetLevel<T>(callback: () => T): T {\n const savedLevel = this.identLevel;\n this.identLevel = 0;\n try {\n return callback();\n } finally {\n this.identLevel = savedLevel;\n }\n }\n}\n\ninterface FixedBindingConfig {\n layoutEntry: TgpuLayoutEntry;\n resource: object;\n}\n\nexport class ResolutionCtxImpl implements ResolutionCtx {\n readonly #namespace: NamespaceInternal;\n readonly #shaderGenerator: ShaderGenerator;\n\n private readonly _indentController = new IndentController();\n private readonly _itemStateStack = new ItemStateStackImpl();\n readonly #modeStack: ExecState[] = [];\n private readonly _declarations: string[] = [];\n private _varyingLocations: Record<string, number> | undefined;\n readonly #currentlyResolvedItems: WeakSet<object> = new WeakSet();\n readonly #logGenerator: LogGenerator;\n\n get varyingLocations() {\n return this._varyingLocations;\n }\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 enableExtensions: WgslExtension[] | undefined;\n public expectedType: AnyData | undefined;\n\n constructor(opts: ResolutionCtxImplOptions) {\n this.enableExtensions = opts.enableExtensions;\n this.#shaderGenerator = opts.shaderGenerator ?? wgslGenerator;\n this.#logGenerator = opts.root\n ? new LogGeneratorImpl(opts.root)\n : new LogGeneratorNullImpl();\n this.#namespace = opts.namespace[$internal];\n }\n\n getUniqueName(resource: object): string {\n return getUniqueName(this.#namespace, resource);\n }\n\n makeNameValid(name: string): string {\n return this.#namespace.nameRegistry.makeValid(name);\n }\n\n get pre(): string {\n return this._indentController.pre;\n }\n\n get topFunctionReturnType() {\n const scope = this._itemStateStack.topFunctionScope;\n invariant(scope, 'Internal error, expected function scope to be present.');\n return scope.returnType;\n }\n\n get shelllessRepo() {\n return this.#namespace.shelllessRepo;\n }\n\n indent(): string {\n return this._indentController.indent();\n }\n\n dedent(): string {\n return this._indentController.dedent();\n }\n\n withResetIndentLevel<T>(callback: () => T): T {\n return this._indentController.withResetLevel(callback);\n }\n\n getById(id: string): Snippet | null {\n const item = this._itemStateStack.getSnippetById(id);\n\n if (item === undefined) {\n return null;\n }\n\n return item;\n }\n\n defineVariable(id: string, snippet: Snippet) {\n this._itemStateStack.defineBlockVariable(id, snippet);\n }\n\n reportReturnType(dataType: AnyData) {\n const scope = this._itemStateStack.topFunctionScope;\n invariant(scope, 'Internal error, expected function scope to be present.');\n scope.reportedReturnTypes.add(dataType);\n }\n\n pushBlockScope() {\n this._itemStateStack.pushBlockScope();\n }\n\n popBlockScope() {\n this._itemStateStack.popBlockScope();\n }\n\n generateLog(op: string, args: Snippet[]): Snippet {\n return this.#logGenerator.generateLog(this, op, args);\n }\n\n get logResources(): LogResources | undefined {\n return this.#logGenerator.logResources;\n }\n\n fnToWgsl(\n options: FnToWgslOptions,\n ): { head: Wgsl; body: Wgsl; returnType: AnyData } {\n const scope = this._itemStateStack.pushFunctionScope(\n options.args,\n options.argAliases,\n options.returnType,\n options.externalMap,\n );\n\n try {\n this.#shaderGenerator.initGenerator(this);\n const body = this.#shaderGenerator.functionDefinition(options.body);\n\n let returnType = options.returnType;\n if (!returnType) {\n const returnTypes = [...scope.reportedReturnTypes];\n if (returnTypes.length === 0) {\n returnType = Void;\n } else {\n const conversion = getBestConversion(returnTypes);\n if (conversion && !conversion.hasImplicitConversions) {\n returnType = conversion.targetType;\n }\n }\n\n if (!returnType) {\n throw new Error(\n `Expected function to have a single return type, got [${\n returnTypes.join(', ')\n }]. Cast explicitly to the desired type.`,\n );\n }\n\n returnType = concretize(returnType);\n }\n\n return {\n head: resolveFunctionHeader(this, options.args, returnType),\n body,\n returnType,\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\n ._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 withVaryingLocations<T>(\n locations: Record<string, number>,\n callback: () => T,\n ): T {\n this._varyingLocations = locations;\n\n try {\n return callback();\n } finally {\n this._varyingLocations = undefined;\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.#namespace.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 // Getting out of codegen or simulation mode so we can execute JS normally.\n this.pushMode(new NormalState());\n\n let result: T;\n try {\n result = derived['~compute']();\n } finally {\n this.popMode('normal');\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.#namespace.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): ResolvedSnippet {\n // All memoized versions of `item`\n const instances = this.#namespace.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: ResolvedSnippet;\n if (isData(item)) {\n result = snip(resolveData(this, item), Void);\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 if (hasTinyestMetadata(item)) {\n // Resolving a function with tinyest metadata directly means calling it with no arguments, since\n // we cannot infer the types of the arguments from a WGSL string.\n const shellless = this.#namespace.shelllessRepo.get(\n item,\n /* no arguments */ undefined,\n );\n if (!shellless) {\n throw new Error(\n `Couldn't resolve ${item.name}. Make sure it's a function that accepts no arguments, or call it from another TypeGPU function.`,\n );\n }\n\n return this.withResetIndentLevel(() => this.resolve(shellless));\n } else {\n throw new TypeError(\n `Unresolvable internal value: ${safeStringify(item)}`,\n );\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.#namespace.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(\n item: unknown,\n schema?: AnyData | UnknownData | undefined,\n ): ResolvedSnippet {\n if (isTgpuFn(item) || hasTinyestMetadata(item)) {\n if (\n this.#currentlyResolvedItems.has(item) &&\n !this.#namespace.memoizedResolves.has(item)\n ) {\n throw new Error(\n `Recursive function ${item} detected. Recursion is not allowed on the GPU.`,\n );\n }\n this.#currentlyResolvedItems.add(item as object);\n }\n\n if (isProviding(item)) {\n return this.withSlots(\n item[$providing].pairs,\n () => this.resolve(item[$providing].inner, schema),\n );\n }\n\n if (isMarkedInternal(item) || hasTinyestMetadata(item)) {\n // Top-level resolve\n if (this._itemStateStack.itemDepth === 0) {\n try {\n this.pushMode(new CodegenState());\n const result = provideCtx(this, () => this._getOrInstantiate(item));\n return snip(\n `${[...this._declarations].join('\\n\\n')}${result.value}`,\n Void,\n );\n } finally {\n this.popMode('codegen');\n }\n }\n\n return this._getOrInstantiate(item);\n }\n\n // This is a value that comes from the outside, maybe we can coerce it\n if (typeof item === 'number') {\n const realSchema = schema ?? numericLiteralToSnippet(item).dataType;\n invariant(\n realSchema.type !== 'unknown',\n 'Schema has to be known for resolving numbers',\n );\n\n if (realSchema.type === 'abstractInt') {\n return snip(`${item}`, realSchema);\n }\n if (realSchema.type === 'u32') {\n return snip(`${item}u`, realSchema);\n }\n if (realSchema.type === 'i32') {\n return snip(`${item}i`, realSchema);\n }\n\n const exp = item.toExponential();\n const decimal =\n realSchema.type === 'abstractFloat' && Number.isInteger(item)\n ? `${item}.`\n : `${item}`;\n\n // Just picking the shorter one\n const base = exp.length < decimal.length ? exp : decimal;\n if (realSchema.type === 'f32') {\n return snip(`${base}f`, realSchema);\n }\n if (realSchema.type === 'f16') {\n return snip(`${base}h`, realSchema);\n }\n return snip(base, realSchema);\n }\n\n if (typeof item === 'boolean') {\n return snip(item ? 'true' : 'false', bool);\n }\n\n if (typeof item === 'string') {\n // Already resolved\n return snip(item, Void);\n }\n\n if (schema && isWgslArray(schema)) {\n if (!Array.isArray(item)) {\n throw new WgslTypeError(\n `Cannot coerce ${item} into value of type '${schema}'`,\n );\n }\n\n if (schema.elementCount !== item.length) {\n throw new WgslTypeError(\n `Cannot create value of type '${schema}' from an array of length: ${item.length}`,\n );\n }\n\n const elementTypeString = this.resolve(schema.elementType);\n return snip(\n stitch`array<${elementTypeString}, ${schema.elementCount}>(${\n item.map((element) => snip(element, schema.elementType as AnyData))\n })`,\n schema,\n );\n }\n\n if (Array.isArray(item)) {\n return snip(\n stitch`array(${item.map((element) => this.resolve(element))})`,\n UnknownData,\n ) as ResolvedSnippet;\n }\n\n if (schema && isWgslStruct(schema)) {\n return snip(\n stitch`${this.resolve(schema)}(${\n Object.entries(schema.propTypes).map(([key, propType]) =>\n snip((item as Infer<typeof schema>)[key], propType as AnyData)\n )\n })`,\n schema,\n );\n }\n\n throw new WgslTypeError(\n `Value ${item} (as json: ${safeStringify(item)}) is not resolvable${\n schema ? ` to type ${schema.type}` : ''\n }`,\n );\n }\n\n pushMode(mode: ExecState) {\n this.#modeStack.push(mode);\n }\n\n popMode(expected?: ExecMode) {\n const mode = this.#modeStack.pop();\n if (expected !== undefined) {\n invariant(mode?.type === expected, 'Unexpected mode');\n }\n }\n\n get mode(): ExecState {\n return this.#modeStack[this.#modeStack.length - 1] ?? topLevelState;\n }\n}\n\n/**\n * The results of a WGSL resolution.\n *\n * @param code - The resolved code.\n * @param usedBindGroupLayouts - List of used `tgpu.bindGroupLayout`s.\n * @param catchall - Automatically constructed bind group for buffer usages and buffer shorthands, preceded by its index.\n * @param logResources - Buffers and information about used console.logs needed to decode the raw data.\n */\nexport interface ResolutionResult {\n code: string;\n usedBindGroupLayouts: TgpuBindGroupLayout[];\n catchall: [number, TgpuBindGroup] | undefined;\n logResources: LogResources | undefined;\n}\n\nexport function resolve(\n item: Wgsl,\n options: ResolutionCtxImplOptions,\n): ResolutionResult {\n const ctx = new ResolutionCtxImpl(options);\n const snippet = options.config\n ? ctx.withSlots(\n options.config(new ConfigurableImpl([])).bindings,\n () => ctx.resolve(item),\n )\n : ctx.resolve(item);\n let code = snippet.value;\n\n const memoMap = ctx.bindGroupLayoutsToPlaceholderMap;\n const usedBindGroupLayouts: 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 usedBindGroupLayouts[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() : undefined;\n\n for (const [layout, placeholder] of memoMap.entries()) {\n const idx = layout.index ?? automaticIds.next().value;\n usedBindGroupLayouts[idx] = layout;\n code = code.replaceAll(placeholder, String(idx));\n }\n\n if (options.enableExtensions && options.enableExtensions.length > 0) {\n const extensions = options.enableExtensions.map((ext) => `enable ${ext};`);\n code = `${extensions.join('\\n')}\\n\\n${code}`;\n }\n\n return {\n code,\n usedBindGroupLayouts,\n catchall,\n logResources: ctx.logResources,\n };\n}\n\nexport function resolveFunctionHeader(\n ctx: ResolutionCtx,\n args: Snippet[],\n returnType: AnyData,\n) {\n const argList = args\n .map((arg) => `${arg.value}: ${ctx.resolve(arg.dataType as AnyData).value}`)\n .join(', ');\n\n return returnType.type !== 'void'\n ? `(${argList}) -> ${getAttributesString(returnType)}${\n ctx.resolve(returnType).value\n } `\n : `(${argList}) `;\n}\n","import { setName, type TgpuNamable } from '../../shared/meta.ts';\nimport type { ExperimentalTgpuRoot } from '../../core/root/rootTypes.ts';\nimport { $internal } from '../../shared/symbols.ts';\n\nexport interface TgpuQuerySet<T extends GPUQueryType> extends TgpuNamable {\n readonly resourceType: 'query-set';\n readonly type: T;\n readonly count: number;\n\n readonly querySet: GPUQuerySet;\n readonly destroyed: boolean;\n readonly available: boolean;\n\n readonly [$internal]: {\n readonly readBuffer: GPUBuffer;\n readonly resolveBuffer: GPUBuffer;\n };\n\n resolve(): void;\n read(): Promise<bigint[]>;\n destroy(): void;\n}\n\nexport function INTERNAL_createQuerySet<T extends GPUQueryType>(\n group: ExperimentalTgpuRoot,\n type: T,\n count: number,\n rawQuerySet?: GPUQuerySet,\n): TgpuQuerySet<T> {\n return new TgpuQuerySetImpl(group, type, count, rawQuerySet);\n}\n\nexport function isQuerySet<T extends GPUQueryType>(\n value: unknown,\n): value is TgpuQuerySet<T> {\n const maybe = value as TgpuQuerySet<T>;\n return maybe?.resourceType === 'query-set' && !!maybe[$internal];\n}\n\nclass TgpuQuerySetImpl<T extends GPUQueryType> implements TgpuQuerySet<T> {\n public readonly resourceType = 'query-set' as const;\n\n readonly #device: GPUDevice;\n private _querySet: GPUQuerySet | null = null;\n private readonly _ownQuerySet: boolean;\n private _destroyed = false;\n private _available = true;\n private _readBuffer: GPUBuffer | null = null;\n private _resolveBuffer: GPUBuffer | null = null;\n\n constructor(\n root: ExperimentalTgpuRoot,\n public readonly type: T,\n public readonly count: number,\n private readonly rawQuerySet?: GPUQuerySet,\n ) {\n this.#device = root.device;\n this._ownQuerySet = !rawQuerySet;\n this._querySet = rawQuerySet || null;\n }\n\n get querySet(): GPUQuerySet {\n if (this._destroyed) {\n throw new Error('This QuerySet has been destroyed.');\n }\n if (this.rawQuerySet) {\n return this.rawQuerySet;\n }\n if (this._querySet) {\n return this._querySet;\n }\n\n this._querySet = this.#device.createQuerySet({\n type: this.type,\n count: this.count,\n });\n return this._querySet;\n }\n\n get destroyed(): boolean {\n return this._destroyed;\n }\n\n get available(): boolean {\n return this._available;\n }\n\n get [$internal]() {\n const self = this;\n return {\n get readBuffer(): GPUBuffer {\n if (!self._readBuffer) {\n self._readBuffer = self.#device.createBuffer({\n size: self.count * BigUint64Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,\n });\n }\n return self._readBuffer;\n },\n get resolveBuffer(): GPUBuffer {\n if (!self._resolveBuffer) {\n self._resolveBuffer = self.#device.createBuffer({\n size: self.count * BigUint64Array.BYTES_PER_ELEMENT,\n usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,\n });\n }\n return self._resolveBuffer;\n },\n };\n }\n\n $name(label: string) {\n setName(this, label);\n if (this._querySet) {\n this._querySet.label = label;\n }\n return this;\n }\n\n resolve(): void {\n if (this._destroyed) {\n throw new Error('This QuerySet has been destroyed.');\n }\n if (!this._available) {\n throw new Error('This QuerySet is busy resolving or reading.');\n }\n\n const commandEncoder = this.#device.createCommandEncoder();\n commandEncoder.resolveQuerySet(\n this.querySet,\n 0,\n this.count,\n this[$internal].resolveBuffer,\n 0,\n );\n this.#device.queue.submit([commandEncoder.finish()]);\n }\n\n async read(): Promise<bigint[]> {\n if (!this._resolveBuffer) {\n throw new Error('QuerySet must be resolved before reading.');\n }\n\n this._available = false;\n try {\n const commandEncoder = this.#device.createCommandEncoder();\n commandEncoder.copyBufferToBuffer(\n this[$internal].resolveBuffer,\n 0,\n this[$internal].readBuffer,\n 0,\n this.count * BigUint64Array.BYTES_PER_ELEMENT,\n );\n this.#device.queue.submit([commandEncoder.finish()]);\n\n const readBuffer = this[$internal].readBuffer;\n await readBuffer.mapAsync(GPUMapMode.READ);\n const data = new BigUint64Array(readBuffer.getMappedRange().slice());\n readBuffer.unmap();\n return Array.from(data);\n } finally {\n this._available = true;\n }\n }\n\n destroy(): void {\n if (this._destroyed) {\n return;\n }\n this._destroyed = true;\n\n if (this._querySet && this._ownQuerySet) {\n this._querySet.destroy();\n }\n this._readBuffer?.destroy();\n this._resolveBuffer?.destroy();\n this._readBuffer = this._resolveBuffer = null;\n }\n}\n","import {\n type AnyComputeBuiltin,\n builtin,\n type OmitBuiltins,\n} from '../../builtin.ts';\nimport {\n INTERNAL_createQuerySet,\n isQuerySet,\n type TgpuQuerySet,\n} from '../../core/querySet/querySet.ts';\nimport type { AnyData, Disarray } from '../../data/dataTypes.ts';\nimport type {\n AnyWgslData,\n BaseData,\n U16,\n U32,\n v3u,\n Vec3u,\n WgslArray,\n} from '../../data/wgslTypes.ts';\nimport {\n invariant,\n MissingBindGroupsError,\n MissingVertexBuffersError,\n} from '../../errors.ts';\nimport { WeakMemo } from '../../memo.ts';\nimport { clearTextureUtilsCache } from '../texture/textureUtils.ts';\nimport type { Infer } from '../../shared/repr.ts';\nimport { $internal } from '../../shared/symbols.ts';\nimport type { AnyVertexAttribs } from '../../shared/vertexFormat.ts';\nimport type {\n ExtractBindGroupInputFromLayout,\n TgpuBindGroup,\n TgpuBindGroupLayout,\n TgpuLayoutEntry,\n} from '../../tgpuBindGroupLayout.ts';\nimport {\n isBindGroup,\n isBindGroupLayout,\n TgpuBindGroupImpl,\n} from '../../tgpuBindGroupLayout.ts';\nimport type { LogGeneratorOptions } from '../../tgsl/consoleLog/types.ts';\nimport type { ShaderGenerator } from '../../tgsl/shaderGenerator.ts';\nimport {\n INTERNAL_createBuffer,\n isBuffer,\n type TgpuBuffer,\n type VertexFlag,\n} from '../buffer/buffer.ts';\nimport {\n TgpuBufferShorthandImpl,\n type TgpuMutable,\n type TgpuReadonly,\n type TgpuUniform,\n} from '../buffer/bufferShorthand.ts';\nimport type { TgpuBufferUsage } from '../buffer/bufferUsage.ts';\nimport type { IOLayout } from '../function/fnTypes.ts';\nimport { computeFn, type TgpuComputeFn } from '../function/tgpuComputeFn.ts';\nimport { fn, type TgpuFn } from '../function/tgpuFn.ts';\nimport type { TgpuFragmentFn } from '../function/tgpuFragmentFn.ts';\nimport type { TgpuVertexFn } from '../function/tgpuVertexFn.ts';\nimport {\n INTERNAL_createComputePipeline,\n type TgpuComputePipeline,\n} from '../pipeline/computePipeline.ts';\nimport {\n type AnyFragmentTargets,\n INTERNAL_createRenderPipeline,\n type RenderPipelineCoreOptions,\n type TgpuRenderPipeline,\n} from '../pipeline/renderPipeline.ts';\nimport { isComputePipeline, isRenderPipeline } from '../pipeline/typeGuards.ts';\nimport {\n INTERNAL_createComparisonSampler,\n INTERNAL_createSampler,\n isComparisonSampler,\n isSampler,\n type TgpuComparisonSampler,\n type TgpuFixedComparisonSampler,\n type TgpuFixedSampler,\n type TgpuSampler,\n} from '../sampler/sampler.ts';\nimport type {\n WgslComparisonSamplerProps,\n WgslSamplerProps,\n} from '../../data/sampler.ts';\nimport {\n isAccessor,\n type TgpuAccessor,\n type TgpuSlot,\n} from '../slot/slotTypes.ts';\nimport {\n INTERNAL_createTexture,\n isTexture,\n isTextureView,\n type TgpuTexture,\n type TgpuTextureView,\n} from '../texture/texture.ts';\nimport type { LayoutToAllowedAttribs } from '../vertexLayout/vertexAttribute.ts';\nimport {\n isVertexLayout,\n type TgpuVertexLayout,\n} from '../vertexLayout/vertexLayout.ts';\nimport { ConfigurableImpl } from './configurableImpl.ts';\nimport type {\n Configurable,\n CreateTextureOptions,\n CreateTextureResult,\n ExperimentalTgpuRoot,\n RenderPass,\n TgpuGuardedComputePipeline,\n TgpuRoot,\n WithBinding,\n WithCompute,\n WithFragment,\n WithVertex,\n} from './rootTypes.ts';\nimport { vec3f, vec3u } from '../../data/vector.ts';\nimport { u32 } from '../../data/numeric.ts';\nimport { ceil } from '../../std/numeric.ts';\nimport { allEq } from '../../std/boolean.ts';\n\n/**\n * Changes the given array to a vec of 3 numbers, filling missing values with 1.\n */\nfunction toVec3(arr: readonly (number | undefined)[]): v3u {\n if (arr.includes(0)) {\n throw new Error('Size and workgroupSize cannot contain zeroes.');\n }\n return vec3u(arr[0] ?? 1, arr[1] ?? 1, arr[2] ?? 1);\n}\n\nconst workgroupSizeConfigs = [\n vec3u(1, 1, 1),\n vec3u(256, 1, 1),\n vec3u(16, 16, 1),\n vec3u(8, 8, 4),\n] as const;\n\nexport class TgpuGuardedComputePipelineImpl<TArgs extends number[]>\n implements TgpuGuardedComputePipeline<TArgs> {\n #root: ExperimentalTgpuRoot;\n #pipeline: TgpuComputePipeline;\n #sizeUniform: TgpuUniform<Vec3u>;\n #workgroupSize: v3u;\n\n #lastSize: v3u;\n\n constructor(\n root: ExperimentalTgpuRoot,\n pipeline: TgpuComputePipeline,\n sizeUniform: TgpuUniform<Vec3u>,\n workgroupSize: v3u,\n ) {\n this.#root = root;\n this.#pipeline = pipeline;\n this.#sizeUniform = sizeUniform;\n this.#workgroupSize = workgroupSize;\n this.#lastSize = vec3u();\n }\n\n with(bindGroup: TgpuBindGroup): TgpuGuardedComputePipeline<TArgs> {\n return new TgpuGuardedComputePipelineImpl(\n this.#root,\n this.#pipeline.with(bindGroup),\n this.#sizeUniform,\n this.#workgroupSize,\n );\n }\n\n dispatchThreads(...threads: TArgs): void {\n const sanitizedSize = toVec3(threads);\n const workgroupCount = ceil(\n vec3f(sanitizedSize).div(vec3f(this.#workgroupSize)),\n );\n if (!allEq(sanitizedSize, this.#lastSize)) {\n // Only updating the size if it has changed from the last\n // invocation. This removes the need for flushing.\n this.#lastSize = sanitizedSize;\n this.#sizeUniform.write(sanitizedSize);\n }\n this.#pipeline.dispatchWorkgroups(\n workgroupCount.x,\n workgroupCount.y,\n workgroupCount.z,\n );\n }\n}\n\nclass WithBindingImpl implements WithBinding {\n constructor(\n private readonly _getRoot: () => ExperimentalTgpuRoot,\n private readonly _slotBindings: [TgpuSlot<unknown>, unknown][],\n ) {}\n\n with<T extends AnyWgslData>(\n slot: TgpuSlot<T> | TgpuAccessor<T>,\n value: T | TgpuFn<() => T> | TgpuBufferUsage<T> | Infer<T>,\n ): WithBinding {\n return new WithBindingImpl(this._getRoot, [\n ...this._slotBindings,\n [isAccessor(slot) ? slot.slot : slot, value],\n ]);\n }\n\n withCompute<ComputeIn extends Record<string, AnyComputeBuiltin>>(\n entryFn: TgpuComputeFn<ComputeIn>,\n ): WithCompute {\n return new WithComputeImpl(this._getRoot(), this._slotBindings, entryFn);\n }\n\n createGuardedComputePipeline<TArgs extends number[]>(\n callback: (...args: TArgs) => undefined,\n ): TgpuGuardedComputePipeline<TArgs> {\n const root = this._getRoot();\n\n if (callback.length >= 4) {\n throw new Error(\n 'Guarded compute callback only supports up to three dimensions.',\n );\n }\n\n const workgroupSize = workgroupSizeConfigs[callback.length] as v3u;\n const wrappedCallback = fn([u32, u32, u32])(\n callback as (...args: number[]) => void,\n );\n\n const sizeUniform = root.createUniform(vec3u);\n\n // WGSL instead of JS because we do not run unplugin\n // before shipping the typegpu package\n const mainCompute = computeFn({\n workgroupSize,\n in: { id: builtin.globalInvocationId },\n })`{\n if (any(in.id >= sizeUniform)) {\n return;\n }\n wrappedCallback(in.id.x, in.id.y, in.id.z);\n}`.$uses({ sizeUniform, wrappedCallback });\n\n const pipeline = this\n .withCompute(mainCompute)\n .createPipeline();\n\n return new TgpuGuardedComputePipelineImpl(\n root,\n pipeline,\n sizeUniform,\n workgroupSize,\n );\n }\n\n withVertex<VertexIn extends IOLayout>(\n vertexFn: TgpuVertexFn,\n attribs: LayoutToAllowedAttribs<OmitBuiltins<VertexIn>>,\n ): WithVertex {\n return new WithVertexImpl({\n branch: this._getRoot(),\n primitiveState: undefined,\n depthStencilState: undefined,\n slotBindings: this._slotBindings,\n vertexFn,\n vertexAttribs: attribs as AnyVertexAttribs,\n multisampleState: undefined,\n });\n }\n\n pipe(transform: (cfg: Configurable) => Configurable): WithBinding {\n const newCfg = transform(new ConfigurableImpl([]));\n return new WithBindingImpl(this._getRoot, [\n ...this._slotBindings,\n ...newCfg.bindings,\n ]);\n }\n}\n\nclass WithComputeImpl implements WithCompute {\n constructor(\n private readonly _root: ExperimentalTgpuRoot,\n private readonly _slotBindings: [TgpuSlot<unknown>, unknown][],\n private readonly _entryFn: TgpuComputeFn,\n ) {}\n\n createPipeline(): TgpuComputePipeline {\n return INTERNAL_createComputePipeline(\n this._root,\n this._slotBindings,\n this._entryFn,\n );\n }\n}\n\nclass WithVertexImpl implements WithVertex {\n constructor(\n private readonly _options: Omit<\n RenderPipelineCoreOptions,\n 'fragmentFn' | 'targets'\n >,\n ) {}\n\n withFragment(\n fragmentFn: TgpuFragmentFn | 'n/a',\n targets: AnyFragmentTargets | 'n/a',\n _mismatch?: unknown,\n ): WithFragment {\n invariant(typeof fragmentFn !== 'string', 'Just type mismatch validation');\n invariant(typeof targets !== 'string', 'Just type mismatch validation');\n\n return new WithFragmentImpl({\n ...this._options,\n fragmentFn,\n targets,\n });\n }\n\n withPrimitive(\n primitiveState:\n | GPUPrimitiveState\n | Omit<GPUPrimitiveState, 'stripIndexFormat'> & {\n stripIndexFormat?: U32 | U16;\n }\n | undefined,\n ): WithFragment {\n return new WithVertexImpl({ ...this._options, primitiveState });\n }\n\n withDepthStencil(\n depthStencilState: GPUDepthStencilState | undefined,\n ): WithFragment {\n return new WithVertexImpl({ ...this._options, depthStencilState });\n }\n\n withMultisample(\n multisampleState: GPUMultisampleState | undefined,\n ): WithFragment {\n return new WithVertexImpl({ ...this._options, multisampleState });\n }\n\n createPipeline(): TgpuRenderPipeline {\n return INTERNAL_createRenderPipeline({\n ...this._options,\n fragmentFn: null,\n targets: null,\n });\n }\n}\n\nclass WithFragmentImpl implements WithFragment {\n constructor(private readonly _options: RenderPipelineCoreOptions) {}\n\n withPrimitive(\n primitiveState:\n | GPUPrimitiveState\n | Omit<GPUPrimitiveState, 'stripIndexFormat'> & {\n stripIndexFormat?: U32 | U16;\n }\n | undefined,\n ): WithFragment {\n return new WithFragmentImpl({ ...this._options, primitiveState });\n }\n\n withDepthStencil(\n depthStencilState: GPUDepthStencilState | undefined,\n ): WithFragment {\n return new WithFragmentImpl({ ...this._options, depthStencilState });\n }\n\n withMultisample(\n multisampleState: GPUMultisampleState | undefined,\n ): WithFragment {\n return new WithFragmentImpl({ ...this._options, multisampleState });\n }\n\n createPipeline(): TgpuRenderPipeline {\n return INTERNAL_createRenderPipeline(this._options);\n }\n}\n\n/**\n * Holds all data that is necessary to facilitate CPU and GPU communication.\n * Programs that share a root can interact via GPU buffers.\n */\nclass TgpuRootImpl extends WithBindingImpl\n implements TgpuRoot, ExperimentalTgpuRoot {\n '~unstable': Omit<ExperimentalTgpuRoot, keyof TgpuRoot>;\n\n private _unwrappedBindGroupLayouts = new WeakMemo(\n (key: TgpuBindGroupLayout) => key.unwrap(this),\n );\n private _unwrappedBindGroups = new WeakMemo((key: TgpuBindGroup) =>\n key.unwrap(this)\n );\n\n [$internal]: {\n logOptions: LogGeneratorOptions;\n };\n\n constructor(\n public readonly device: GPUDevice,\n public readonly nameRegistrySetting: 'random' | 'strict',\n private readonly _ownDevice: boolean,\n logOptions: LogGeneratorOptions,\n public readonly shaderGenerator?: ShaderGenerator,\n ) {\n super(() => this, []);\n\n this['~unstable'] = this;\n this[$internal] = {\n logOptions,\n };\n }\n\n get enabledFeatures() {\n return new Set(this.device.features) as ReadonlySet<GPUFeatureName>;\n }\n\n createBuffer<TData extends AnyData>(\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n ): TgpuBuffer<TData> {\n return INTERNAL_createBuffer(this, typeSchema, initialOrBuffer);\n }\n\n createUniform<TData extends AnyWgslData>(\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n ): TgpuUniform<TData> {\n const buffer = INTERNAL_createBuffer(this, typeSchema, initialOrBuffer)\n // biome-ignore lint/suspicious/noExplicitAny: i'm sure it's fine\n .$usage('uniform' as any);\n\n return new TgpuBufferShorthandImpl('uniform', buffer);\n }\n\n createMutable<TData extends AnyWgslData>(\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n ): TgpuMutable<TData> {\n const buffer = INTERNAL_createBuffer(this, typeSchema, initialOrBuffer)\n // biome-ignore lint/suspicious/noExplicitAny: i'm sure it's fine\n .$usage('storage' as any);\n\n return new TgpuBufferShorthandImpl('mutable', buffer);\n }\n\n createReadonly<TData extends AnyWgslData>(\n typeSchema: TData,\n initialOrBuffer?: Infer<TData> | GPUBuffer,\n ): TgpuReadonly<TData> {\n const buffer = INTERNAL_createBuffer(this, typeSchema, initialOrBuffer)\n // biome-ignore lint/suspicious/noExplicitAny: i'm sure it's fine\n .$usage('storage' as any);\n\n return new TgpuBufferShorthandImpl('readonly', buffer);\n }\n\n createQuerySet<T extends GPUQueryType>(\n type: T,\n count: number,\n rawQuerySet?: GPUQuerySet,\n ): TgpuQuerySet<T> {\n return INTERNAL_createQuerySet(this, type, count, rawQuerySet);\n }\n\n createBindGroup<\n Entries extends Record<string, TgpuLayoutEntry | null> = Record<\n string,\n TgpuLayoutEntry | null\n >,\n >(\n layout: TgpuBindGroupLayout<Entries>,\n entries: ExtractBindGroupInputFromLayout<Entries>,\n ) {\n return new TgpuBindGroupImpl(layout, entries);\n }\n\n destroy() {\n clearTextureUtilsCache(this.device);\n\n if (this._ownDevice) {\n this.device.destroy();\n }\n }\n\n createTexture<\n TWidth extends number,\n THeight extends number,\n TDepth extends number,\n TSize extends\n | readonly [TWidth]\n | readonly [TWidth, THeight]\n | readonly [TWidth, THeight, TDepth],\n TFormat extends GPUTextureFormat,\n TMipLevelCount extends number,\n TSampleCount extends number,\n TViewFormats extends GPUTextureFormat[],\n TDimension extends GPUTextureDimension,\n >(\n props: CreateTextureOptions<\n TSize,\n TFormat,\n TMipLevelCount,\n TSampleCount,\n TViewFormats,\n TDimension\n >,\n ): TgpuTexture<\n CreateTextureResult<\n TSize,\n TFormat,\n TMipLevelCount,\n TSampleCount,\n TViewFormats,\n TDimension\n >\n > {\n const texture = INTERNAL_createTexture(props, this);\n // biome-ignore lint/suspicious/noExplicitAny: <too much type wrangling>\n return texture as any;\n }\n\n createSampler(props: WgslSamplerProps): TgpuFixedSampler {\n return INTERNAL_createSampler(props, this);\n }\n\n createComparisonSampler(\n props: WgslComparisonSamplerProps,\n ): TgpuFixedComparisonSampler {\n return INTERNAL_createComparisonSampler(props, this);\n }\n\n unwrap(resource: TgpuComputePipeline): GPUComputePipeline;\n unwrap(resource: TgpuRenderPipeline): GPURenderPipeline;\n unwrap(resource: TgpuBindGroupLayout): GPUBindGroupLayout;\n unwrap(resource: TgpuBindGroup): GPUBindGroup;\n unwrap(resource: TgpuBuffer<AnyData>): GPUBuffer;\n unwrap(resource: TgpuTexture): GPUTexture;\n unwrap(resource: TgpuTextureView): GPUTextureView;\n unwrap(resource: TgpuVertexLayout): GPUVertexBufferLayout;\n unwrap(resource: TgpuSampler): GPUSampler;\n unwrap(resource: TgpuComparisonSampler): GPUSampler;\n unwrap(resource: TgpuQuerySet<GPUQueryType>): GPUQuerySet;\n unwrap(\n resource:\n | TgpuComputePipeline\n | TgpuRenderPipeline\n | TgpuBindGroupLayout\n | TgpuBindGroup\n | TgpuBuffer<AnyData>\n | TgpuTexture\n | TgpuTextureView\n | TgpuVertexLayout\n | TgpuSampler\n | TgpuComparisonSampler\n | TgpuQuerySet<GPUQueryType>,\n ):\n | GPUComputePipeline\n | GPURenderPipeline\n | GPUBindGroupLayout\n | GPUBindGroup\n | GPUBuffer\n | GPUTexture\n | GPUTextureView\n | GPUVertexBufferLayout\n | GPUSampler\n | GPUQuerySet {\n if (isComputePipeline(resource)) {\n return resource[$internal].rawPipeline;\n }\n\n if (isRenderPipeline(resource)) {\n return resource[$internal].core.unwrap().pipeline;\n }\n\n if (isBindGroupLayout(resource)) {\n return this._unwrappedBindGroupLayouts.getOrMake(resource);\n }\n\n if (isBindGroup(resource)) {\n return this._unwrappedBindGroups.getOrMake(resource);\n }\n\n if (isBuffer(resource)) {\n return resource.buffer;\n }\n\n if (isTexture(resource)) {\n return resource[$internal].unwrap();\n }\n\n if (isTextureView(resource)) {\n if (!resource[$internal].unwrap) {\n throw new Error(\n 'Cannot unwrap laid-out texture view as it has no underlying resource.',\n );\n }\n return resource[$internal].unwrap();\n }\n\n if (isVertexLayout(resource)) {\n return resource.vertexLayout;\n }\n\n if (isSampler(resource) || isComparisonSampler(resource)) {\n if (resource[$internal].unwrap) {\n return resource[$internal].unwrap();\n }\n throw new Error('Cannot unwrap laid-out sampler.');\n }\n\n if (isQuerySet(resource)) {\n return resource.querySet;\n }\n\n throw new Error(`Unknown resource type: ${resource}`);\n }\n\n beginRenderPass(\n descriptor: GPURenderPassDescriptor,\n callback: (pass: RenderPass) => void,\n ): void {\n const commandEncoder = this.device.createCommandEncoder();\n const pass = commandEncoder.beginRenderPass(descriptor);\n\n const bindGroups = new Map<\n TgpuBindGroupLayout,\n TgpuBindGroup | GPUBindGroup\n >();\n const vertexBuffers = new Map<\n TgpuVertexLayout,\n {\n buffer:\n | (TgpuBuffer<WgslArray<BaseData> | Disarray<BaseData>> & VertexFlag)\n | GPUBuffer;\n offset?: number | undefined;\n size?: number | undefined;\n }\n >();\n\n let currentPipeline: TgpuRenderPipeline | undefined;\n\n const setupPassBeforeDraw = () => {\n if (!currentPipeline) {\n throw new Error('Cannot draw without a call to pass.setPipeline');\n }\n\n const { core, priors } = currentPipeline[$internal];\n const memo = core.unwrap();\n\n pass.setPipeline(memo.pipeline);\n\n const missingBindGroups = new Set(memo.usedBindGroupLayouts);\n memo.usedBindGroupLayouts.forEach((layout, idx) => {\n if (memo.catchall && idx === memo.catchall[0]) {\n // Catch-all\n pass.setBindGroup(idx, this.unwrap(memo.catchall[1]));\n missingBindGroups.delete(layout);\n } else {\n const bindGroup = priors.bindGroupLayoutMap?.get(layout) ??\n bindGroups.get(layout);\n if (bindGroup !== undefined) {\n missingBindGroups.delete(layout);\n if (isBindGroup(bindGroup)) {\n pass.setBindGroup(idx, this.unwrap(bindGroup));\n } else {\n pass.setBindGroup(idx, bindGroup);\n }\n }\n }\n });\n\n const missingVertexLayouts = new Set<TgpuVertexLayout>();\n core.usedVertexLayouts.forEach((vertexLayout, idx) => {\n const priorBuffer = priors.vertexLayoutMap?.get(vertexLayout);\n const opts = priorBuffer\n ? {\n buffer: priorBuffer,\n offset: undefined,\n size: undefined,\n }\n : vertexBuffers.get(vertexLayout);\n\n if (!opts || !opts.buffer) {\n missingVertexLayouts.add(vertexLayout);\n } else if (isBuffer(opts.buffer)) {\n pass.setVertexBuffer(\n idx,\n this.unwrap(opts.buffer),\n opts.offset,\n opts.size,\n );\n } else {\n pass.setVertexBuffer(idx, opts.buffer, opts.offset, opts.size);\n }\n });\n\n if (missingBindGroups.size > 0) {\n throw new MissingBindGroupsError(missingBindGroups);\n }\n\n if (missingVertexLayouts.size > 0) {\n throw new MissingVertexBuffersError(missingVertexLayouts);\n }\n };\n\n callback({\n setViewport(...args) {\n pass.setViewport(...args);\n },\n setScissorRect(...args) {\n pass.setScissorRect(...args);\n },\n setBlendConstant(...args) {\n pass.setBlendConstant(...args);\n },\n setStencilReference(...args) {\n pass.setStencilReference(...args);\n },\n beginOcclusionQuery(...args) {\n pass.beginOcclusionQuery(...args);\n },\n endOcclusionQuery(...args) {\n pass.endOcclusionQuery(...args);\n },\n executeBundles(...args) {\n pass.executeBundles(...args);\n },\n setPipeline(pipeline) {\n currentPipeline = pipeline;\n },\n\n setIndexBuffer: (buffer, indexFormat, offset, size) => {\n if (isBuffer(buffer)) {\n pass.setIndexBuffer(this.unwrap(buffer), indexFormat, offset, size);\n } else {\n pass.setIndexBuffer(buffer, indexFormat, offset, size);\n }\n },\n\n setVertexBuffer(vertexLayout, buffer, offset, size) {\n vertexBuffers.set(vertexLayout, { buffer, offset, size });\n },\n\n setBindGroup(bindGroupLayout, bindGroup) {\n bindGroups.set(bindGroupLayout, bindGroup);\n },\n\n draw(vertexCount, instanceCount, firstVertex, firstInstance) {\n setupPassBeforeDraw();\n pass.draw(vertexCount, instanceCount, firstVertex, firstInstance);\n },\n\n drawIndexed(...args) {\n setupPassBeforeDraw();\n pass.drawIndexed(...args);\n },\n\n drawIndirect(...args) {\n setupPassBeforeDraw();\n pass.drawIndirect(...args);\n },\n\n drawIndexedIndirect(...args) {\n setupPassBeforeDraw();\n pass.drawIndexedIndirect(...args);\n },\n });\n\n pass.end();\n this.device.queue.submit([commandEncoder.finish()]);\n }\n\n flush() {\n console.warn('flush() has been deprecated, and has no effect.');\n }\n}\n\n/**\n * Options passed into {@link init}.\n */\nexport type InitOptions = {\n adapter?: GPURequestAdapterOptions | undefined;\n device?:\n | GPUDeviceDescriptor & { optionalFeatures?: Iterable<GPUFeatureName> }\n | undefined;\n /** @default 'random' */\n unstable_names?: 'random' | 'strict' | undefined;\n /**\n * A custom shader code generator, used when resolving TGSL.\n * If not provided, the default WGSL generator will be used.\n */\n shaderGenerator?: ShaderGenerator | undefined;\n unstable_logOptions?: LogGeneratorOptions;\n};\n\n/**\n * Options passed into {@link initFromDevice}.\n */\nexport type InitFromDeviceOptions = {\n device: GPUDevice;\n /** @default 'random' */\n unstable_names?: 'random' | 'strict' | undefined;\n /**\n * A custom shader code generator, used when resolving TGSL.\n * If not provided, the default WGSL generator will be used.\n */\n shaderGenerator?: ShaderGenerator | undefined;\n unstable_logOptions?: LogGeneratorOptions;\n};\n\n/**\n * Requests a new GPU device and creates a root around it.\n * If a specific device should be used instead, use @see initFromDevice.\n *\n * @example\n * When given no options, the function will ask the browser for a suitable GPU device.\n * ```ts\n * const root = await tgpu.init();\n * ```\n *\n * @example\n * If there are specific options that should be used when requesting a device, you can pass those in.\n * ```ts\n * const adapterOptions: GPURequestAdapterOptions = ...;\n * const deviceDescriptor: GPUDeviceDescriptor = ...;\n * const root = await tgpu.init({ adapter: adapterOptions, device: deviceDescriptor });\n * ```\n */\nexport async function init(options?: InitOptions): Promise<TgpuRoot> {\n const {\n adapter: adapterOpt,\n device: deviceOpt,\n unstable_names: names = 'random',\n unstable_logOptions,\n } = options ?? {};\n\n if (!navigator.gpu) {\n throw new Error('WebGPU is not supported by this browser.');\n }\n\n const adapter = await navigator.gpu.requestAdapter(adapterOpt);\n\n if (!adapter) {\n throw new Error('Could not find a compatible GPU');\n }\n\n const availableFeatures: GPUFeatureName[] = [];\n for (const feature of deviceOpt?.requiredFeatures ?? []) {\n if (!adapter.features.has(feature)) {\n throw new Error(\n `Requested feature \"${feature}\" is not supported by the adapter.`,\n );\n }\n availableFeatures.push(feature);\n }\n for (const feature of deviceOpt?.optionalFeatures ?? []) {\n if (adapter.features.has(feature)) {\n availableFeatures.push(feature);\n } else {\n console.warn(\n `Optional feature \"${feature}\" is not supported by the adapter.`,\n );\n }\n }\n\n const device = await adapter.requestDevice({\n ...deviceOpt,\n requiredFeatures: availableFeatures,\n });\n\n return new TgpuRootImpl(\n device,\n names,\n true,\n unstable_logOptions ?? {},\n options?.shaderGenerator,\n );\n}\n\n/**\n * Creates a root from the given device, instead of requesting it like @see init.\n *\n * @example\n * ```ts\n * const device: GPUDevice = ...;\n * const root = tgpu.initFromDevice({ device });\n * ```\n */\nexport function initFromDevice(options: InitFromDeviceOptions): TgpuRoot {\n const {\n device,\n unstable_names: names = 'random',\n unstable_logOptions,\n } = options ?? {};\n\n return new TgpuRootImpl(\n device,\n names,\n false,\n unstable_logOptions ?? {},\n options?.shaderGenerator,\n );\n}\n"]}