@motion-core/motion-gpu 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/core/compute-bindgroup-cache.d.ts +13 -0
  2. package/dist/core/compute-bindgroup-cache.d.ts.map +1 -0
  3. package/dist/core/compute-bindgroup-cache.js +45 -0
  4. package/dist/core/compute-bindgroup-cache.js.map +1 -0
  5. package/dist/core/compute-shader.d.ts +48 -0
  6. package/dist/core/compute-shader.d.ts.map +1 -1
  7. package/dist/core/compute-shader.js +34 -1
  8. package/dist/core/compute-shader.js.map +1 -1
  9. package/dist/core/error-diagnostics.d.ts +8 -1
  10. package/dist/core/error-diagnostics.d.ts.map +1 -1
  11. package/dist/core/error-diagnostics.js +7 -3
  12. package/dist/core/error-diagnostics.js.map +1 -1
  13. package/dist/core/error-report.d.ts.map +1 -1
  14. package/dist/core/error-report.js +19 -1
  15. package/dist/core/error-report.js.map +1 -1
  16. package/dist/core/material.d.ts.map +1 -1
  17. package/dist/core/material.js +2 -1
  18. package/dist/core/material.js.map +1 -1
  19. package/dist/core/renderer.d.ts.map +1 -1
  20. package/dist/core/renderer.js +150 -85
  21. package/dist/core/renderer.js.map +1 -1
  22. package/dist/core/runtime-loop.d.ts.map +1 -1
  23. package/dist/core/runtime-loop.js +26 -14
  24. package/dist/core/runtime-loop.js.map +1 -1
  25. package/dist/core/shader.d.ts +7 -2
  26. package/dist/core/shader.d.ts.map +1 -1
  27. package/dist/core/shader.js +1 -0
  28. package/dist/core/shader.js.map +1 -1
  29. package/dist/core/textures.d.ts +4 -0
  30. package/dist/core/textures.d.ts.map +1 -1
  31. package/dist/core/textures.js +2 -1
  32. package/dist/core/textures.js.map +1 -1
  33. package/dist/core/types.d.ts +1 -1
  34. package/dist/core/types.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/src/lib/core/compute-bindgroup-cache.ts +73 -0
  37. package/src/lib/core/compute-shader.ts +86 -0
  38. package/src/lib/core/error-diagnostics.ts +29 -4
  39. package/src/lib/core/error-report.ts +26 -1
  40. package/src/lib/core/material.ts +2 -1
  41. package/src/lib/core/renderer.ts +198 -92
  42. package/src/lib/core/runtime-loop.ts +37 -16
  43. package/src/lib/core/shader.ts +13 -2
  44. package/src/lib/core/textures.ts +6 -1
  45. package/src/lib/core/types.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"material.js","names":[],"sources":["../../src/lib/core/material.ts"],"sourcesContent":["import { normalizeTextureDefinition } from './textures.js';\nimport type { MaterialSourceMetadata } from './error-diagnostics.js';\nimport {\n\tassertUniformName,\n\tassertUniformValueForType,\n\tinferUniformType,\n\tresolveUniformLayout\n} from './uniforms.js';\nimport {\n\tnormalizeDefines,\n\tnormalizeIncludes,\n\tpreprocessMaterialFragment,\n\ttoDefineLine,\n\ttype MaterialLineMap,\n\ttype PreprocessedMaterialFragment\n} from './material-preprocess.js';\nimport { assertStorageBufferDefinition, assertStorageTextureFormat } from './storage-buffers.js';\nimport type {\n\tStorageBufferDefinition,\n\tStorageBufferDefinitionMap,\n\tTextureData,\n\tTextureDefinition,\n\tTextureDefinitionMap,\n\tTextureValue,\n\tTypedUniform,\n\tUniformMap,\n\tUniformValue\n} from './types.js';\n\n/**\n * Typed compile-time define declaration.\n */\nexport type TypedMaterialDefineValue =\n\t| {\n\t\t\t/**\n\t\t\t * WGSL scalar type.\n\t\t\t */\n\t\t\ttype: 'bool';\n\t\t\t/**\n\t\t\t * Literal value for the selected WGSL type.\n\t\t\t */\n\t\t\tvalue: boolean;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * WGSL scalar type.\n\t\t\t */\n\t\t\ttype: 'f32' | 'i32' | 'u32';\n\t\t\t/**\n\t\t\t * Literal value for the selected WGSL type.\n\t\t\t */\n\t\t\tvalue: number;\n\t };\n\n/**\n * Allowed value types for WGSL `const` define injection.\n */\nexport type MaterialDefineValue = boolean | number | TypedMaterialDefineValue;\n\n/**\n * Define map keyed by uniform-compatible identifier names.\n */\nexport type MaterialDefines<TKey extends string = string> = Record<TKey, MaterialDefineValue>;\n\n/**\n * Include map keyed by include identifier used in `#include <name>` directives.\n */\nexport type MaterialIncludes<TKey extends string = string> = Record<TKey, string>;\n\n/**\n * External material input accepted by {@link defineMaterial}.\n */\nexport interface FragMaterialInput<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTDefineKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n> {\n\t/**\n\t * User WGSL source containing `frag(uv: vec2f) -> vec4f`.\n\t */\n\tfragment: string;\n\t/**\n\t * Initial uniform values.\n\t */\n\tuniforms?: UniformMap<TUniformKey>;\n\t/**\n\t * Texture definitions keyed by texture uniform name.\n\t */\n\ttextures?: TextureDefinitionMap<TTextureKey>;\n\t/**\n\t * Optional compile-time define constants injected into WGSL.\n\t */\n\tdefines?: MaterialDefines<TDefineKey>;\n\t/**\n\t * Optional WGSL include chunks used by `#include <name>` directives.\n\t */\n\tincludes?: MaterialIncludes<TIncludeKey>;\n\t/**\n\t * Optional storage buffer definitions for compute shaders.\n\t */\n\tstorageBuffers?: StorageBufferDefinitionMap<TStorageBufferKey>;\n}\n\n/**\n * Normalized and immutable material declaration consumed by `FragCanvas`.\n */\nexport interface FragMaterial<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTDefineKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n> {\n\t/**\n\t * User WGSL source containing `frag(uv: vec2f) -> vec4f`.\n\t */\n\treadonly fragment: string;\n\t/**\n\t * Initial uniform values.\n\t */\n\treadonly uniforms: Readonly<UniformMap<TUniformKey>>;\n\t/**\n\t * Texture definitions keyed by texture uniform name.\n\t */\n\treadonly textures: Readonly<TextureDefinitionMap<TTextureKey>>;\n\t/**\n\t * Optional compile-time define constants injected into WGSL.\n\t */\n\treadonly defines: Readonly<MaterialDefines<TDefineKey>>;\n\t/**\n\t * Optional WGSL include chunks used by `#include <name>` directives.\n\t */\n\treadonly includes: Readonly<MaterialIncludes<TIncludeKey>>;\n\t/**\n\t * Storage buffer definitions for compute shaders. Empty when not provided.\n\t */\n\treadonly storageBuffers: Readonly<StorageBufferDefinitionMap<TStorageBufferKey>>;\n}\n\n/**\n * Fully resolved, immutable material snapshot used for renderer creation/caching.\n */\nexport interface ResolvedMaterial<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n> {\n\t/**\n\t * Final fragment WGSL after define injection.\n\t */\n\tfragmentWgsl: string;\n\t/**\n\t * 1-based map from generated fragment lines to user source lines.\n\t */\n\tfragmentLineMap: MaterialLineMap;\n\t/**\n\t * Cloned uniforms.\n\t */\n\tuniforms: UniformMap<TUniformKey>;\n\t/**\n\t * Cloned texture definitions.\n\t */\n\ttextures: TextureDefinitionMap<TTextureKey>;\n\t/**\n\t * Resolved packed uniform layout.\n\t */\n\tuniformLayout: ReturnType<typeof resolveUniformLayout>;\n\t/**\n\t * Sorted texture keys.\n\t */\n\ttextureKeys: TTextureKey[];\n\t/**\n\t * Deterministic JSON signature for cache invalidation.\n\t */\n\tsignature: string;\n\t/**\n\t * Original user fragment source before preprocessing.\n\t */\n\tfragmentSource: string;\n\t/**\n\t * Normalized include sources map.\n\t */\n\tincludeSources: MaterialIncludes<TIncludeKey>;\n\t/**\n\t * Deterministic define block source used for diagnostics mapping.\n\t */\n\tdefineBlockSource: string;\n\t/**\n\t * Source metadata used for diagnostics.\n\t */\n\tsource: Readonly<MaterialSourceMetadata> | null;\n\t/**\n\t * Sorted storage buffer keys. Empty array when no storage buffers declared.\n\t */\n\tstorageBufferKeys: TStorageBufferKey[];\n\t/**\n\t * Sorted storage texture keys (textures with storage: true).\n\t */\n\tstorageTextureKeys: TTextureKey[];\n}\n\n/**\n * Strict fragment contract used by MotionGPU.\n */\nconst FRAGMENT_FUNCTION_SIGNATURE_PATTERN =\n\t/\\bfn\\s+frag\\s*\\(\\s*([^)]*?)\\s*\\)\\s*->\\s*([A-Za-z_][A-Za-z0-9_<>\\s]*)\\s*(?:\\{|$)/m;\nconst FRAGMENT_FUNCTION_NAME_PATTERN = /\\bfn\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(/g;\n\n/**\n * Cache of resolved material snapshots keyed by immutable material instance.\n */\ntype AnyFragMaterial = FragMaterial<string, string, string, string, string>;\ntype AnyResolvedMaterial = ResolvedMaterial<string, string, string, string>;\n\nconst resolvedMaterialCache = new WeakMap<AnyFragMaterial, AnyResolvedMaterial>();\nconst preprocessedFragmentCache = new WeakMap<AnyFragMaterial, PreprocessedMaterialFragment>();\nconst materialSourceMetadataCache = new WeakMap<AnyFragMaterial, MaterialSourceMetadata | null>();\n\nfunction getCachedResolvedMaterial<\n\tTUniformKey extends string,\n\tTTextureKey extends string,\n\tTIncludeKey extends string,\n\tTStorageBufferKey extends string\n>(\n\tmaterial: FragMaterial<TUniformKey, TTextureKey, string, TIncludeKey, TStorageBufferKey>\n): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> | null {\n\tconst cached = resolvedMaterialCache.get(material);\n\tif (!cached) {\n\t\treturn null;\n\t}\n\n\t// Invariant: the cache key is the same material object used to produce this resolved payload.\n\treturn cached as ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey>;\n}\n\nconst STACK_TRACE_CHROME_PATTERN = /^\\s*at\\s+(?:(.*?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/;\nconst STACK_TRACE_FIREFOX_PATTERN = /^(.*?)@(.+?):(\\d+):(\\d+)$/;\n\nfunction getPathBasename(path: string): string {\n\tconst normalized = path.split(/[?#]/)[0] ?? path;\n\tconst parts = normalized.split(/[\\\\/]/);\n\tconst last = parts[parts.length - 1];\n\treturn last && last.length > 0 ? last : path;\n}\n\nfunction normalizeSignaturePart(value: string): string {\n\treturn value.replace(/\\s+/g, ' ').trim();\n}\n\nfunction listFunctionNames(fragment: string): string[] {\n\tconst names = new Set<string>();\n\tfor (const match of fragment.matchAll(FRAGMENT_FUNCTION_NAME_PATTERN)) {\n\t\tconst name = match[1];\n\t\tif (!name) {\n\t\t\tcontinue;\n\t\t}\n\t\tnames.add(name);\n\t}\n\n\treturn Array.from(names);\n}\n\nfunction captureMaterialSourceFromStack(): MaterialSourceMetadata | null {\n\tconst stack = new Error().stack;\n\tif (!stack) {\n\t\treturn null;\n\t}\n\n\tconst stackLines = stack.split('\\n').slice(1);\n\tfor (const rawLine of stackLines) {\n\t\tconst line = rawLine.trim();\n\t\tif (line.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst chromeMatch = line.match(STACK_TRACE_CHROME_PATTERN);\n\t\tconst firefoxMatch = line.match(STACK_TRACE_FIREFOX_PATTERN);\n\t\tconst functionName = chromeMatch?.[1] ?? firefoxMatch?.[1] ?? undefined;\n\t\tconst file = chromeMatch?.[2] ?? firefoxMatch?.[2];\n\t\tconst lineValue = chromeMatch?.[3] ?? firefoxMatch?.[3];\n\t\tconst columnValue = chromeMatch?.[4] ?? firefoxMatch?.[4];\n\n\t\tif (!file || !lineValue || !columnValue) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (file.includes('/core/material') || file.includes('\\\\core\\\\material')) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst parsedLine = Number.parseInt(lineValue, 10);\n\t\tconst parsedColumn = Number.parseInt(columnValue, 10);\n\t\tif (!Number.isFinite(parsedLine) || !Number.isFinite(parsedColumn)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn {\n\t\t\tcomponent: getPathBasename(file),\n\t\t\tfile,\n\t\t\tline: parsedLine,\n\t\t\tcolumn: parsedColumn,\n\t\t\t...(functionName ? { functionName } : {})\n\t\t};\n\t}\n\n\treturn null;\n}\n\nfunction resolveSourceMetadata(\n\tsource: MaterialSourceMetadata | undefined\n): MaterialSourceMetadata | null {\n\tconst captured = captureMaterialSourceFromStack();\n\tconst component = source?.component ?? captured?.component;\n\tconst file = source?.file ?? captured?.file;\n\tconst line = source?.line ?? captured?.line;\n\tconst column = source?.column ?? captured?.column;\n\tconst functionName = source?.functionName ?? captured?.functionName;\n\n\tif (\n\t\tcomponent === undefined &&\n\t\tfile === undefined &&\n\t\tline === undefined &&\n\t\tcolumn === undefined &&\n\t\tfunctionName === undefined\n\t) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\t...(component !== undefined ? { component } : {}),\n\t\t...(file !== undefined ? { file } : {}),\n\t\t...(line !== undefined ? { line } : {}),\n\t\t...(column !== undefined ? { column } : {}),\n\t\t...(functionName !== undefined ? { functionName } : {})\n\t};\n}\n\n/**\n * Asserts that material has been normalized by {@link defineMaterial}.\n */\nfunction assertDefinedMaterial(material: AnyFragMaterial): void {\n\tif (\n\t\t!Object.isFrozen(material) ||\n\t\t!material.uniforms ||\n\t\t!material.textures ||\n\t\t!material.defines ||\n\t\t!material.includes\n\t) {\n\t\tthrow new Error(\n\t\t\t'Invalid material instance. Create materials with defineMaterial(...) before passing them to <FragCanvas>.'\n\t\t);\n\t}\n}\n\n/**\n * Clones uniform value input to decouple material instances from external objects.\n */\nfunction cloneUniformValue(value: UniformValue): UniformValue {\n\tif (typeof value === 'number') {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn Object.freeze([...value]) as UniformValue;\n\t}\n\n\tif (typeof value === 'object' && value !== null && 'type' in value && 'value' in value) {\n\t\tconst typed = value as TypedUniform;\n\t\tconst typedValue = typed.value as unknown;\n\n\t\tlet clonedTypedValue = typedValue;\n\t\tif (typedValue instanceof Float32Array) {\n\t\t\tclonedTypedValue = new Float32Array(typedValue);\n\t\t} else if (Array.isArray(typedValue)) {\n\t\t\tclonedTypedValue = Object.freeze([...typedValue]);\n\t\t}\n\n\t\treturn Object.freeze({\n\t\t\ttype: typed.type,\n\t\t\tvalue: clonedTypedValue\n\t\t}) as UniformValue;\n\t}\n\n\treturn value;\n}\n\n/**\n * Clones optional texture value payload.\n */\nfunction cloneTextureValue(value: TextureValue | undefined): TextureValue {\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === 'object' && 'source' in value) {\n\t\tconst data = value as TextureData;\n\t\treturn {\n\t\t\tsource: data.source,\n\t\t\t...(data.width !== undefined ? { width: data.width } : {}),\n\t\t\t...(data.height !== undefined ? { height: data.height } : {}),\n\t\t\t...(data.colorSpace !== undefined ? { colorSpace: data.colorSpace } : {}),\n\t\t\t...(data.flipY !== undefined ? { flipY: data.flipY } : {}),\n\t\t\t...(data.premultipliedAlpha !== undefined\n\t\t\t\t? { premultipliedAlpha: data.premultipliedAlpha }\n\t\t\t\t: {}),\n\t\t\t...(data.generateMipmaps !== undefined ? { generateMipmaps: data.generateMipmaps } : {}),\n\t\t\t...(data.update !== undefined ? { update: data.update } : {})\n\t\t};\n\t}\n\n\treturn value;\n}\n\n/**\n * Clones and validates fragment source contract.\n */\nfunction resolveFragment(fragment: string): string {\n\tif (typeof fragment !== 'string' || fragment.trim().length === 0) {\n\t\tthrow new Error('Material fragment shader must be a non-empty WGSL string.');\n\t}\n\n\tconst signature = fragment.match(FRAGMENT_FUNCTION_SIGNATURE_PATTERN);\n\tif (!signature) {\n\t\tconst discoveredFunctions = listFunctionNames(fragment).slice(0, 4);\n\t\tconst discoveredLabel =\n\t\t\tdiscoveredFunctions.length > 0\n\t\t\t\t? `Found: ${discoveredFunctions.map((name) => `\\`${name}(...)\\``).join(', ')}.`\n\t\t\t\t: 'No WGSL function declarations were found.';\n\n\t\tthrow new Error(\n\t\t\t`Material fragment contract mismatch: missing entrypoint \\`fn frag(uv: vec2f) -> vec4f\\`. ${discoveredLabel}`\n\t\t);\n\t}\n\n\tconst params = normalizeSignaturePart(signature[1] ?? '');\n\tconst returnType = normalizeSignaturePart(signature[2] ?? '');\n\n\tif (params !== 'uv: vec2f') {\n\t\tthrow new Error(\n\t\t\t`Material fragment contract mismatch for \\`frag\\`: expected parameter list \\`(uv: vec2f)\\`, received \\`(${params || '...'})\\`.`\n\t\t);\n\t}\n\n\tif (returnType !== 'vec4f') {\n\t\tthrow new Error(\n\t\t\t`Material fragment contract mismatch for \\`frag\\`: expected return type \\`vec4f\\`, received \\`${returnType}\\`.`\n\t\t);\n\t}\n\n\treturn fragment;\n}\n\n/**\n * Clones and validates uniform declarations.\n */\nfunction resolveUniforms<TUniformKey extends string>(\n\tuniforms: UniformMap<TUniformKey> | undefined\n): UniformMap<TUniformKey> {\n\tconst resolved: UniformMap<TUniformKey> = {} as UniformMap<TUniformKey>;\n\n\tfor (const [name, value] of Object.entries(uniforms ?? {}) as Array<\n\t\t[TUniformKey, UniformValue]\n\t>) {\n\t\tassertUniformName(name);\n\t\tconst clonedValue = cloneUniformValue(value);\n\t\tconst type = inferUniformType(clonedValue);\n\t\tassertUniformValueForType(type, clonedValue);\n\t\tresolved[name] = clonedValue;\n\t}\n\n\tresolveUniformLayout(resolved);\n\treturn resolved;\n}\n\n/**\n * Clones and validates texture declarations.\n */\nfunction resolveTextures<TTextureKey extends string>(\n\ttextures: TextureDefinitionMap<TTextureKey> | undefined\n): TextureDefinitionMap<TTextureKey> {\n\tconst resolved: TextureDefinitionMap<TTextureKey> = {} as TextureDefinitionMap<TTextureKey>;\n\n\tfor (const [name, definition] of Object.entries(textures ?? {}) as Array<\n\t\t[TTextureKey, TextureDefinition]\n\t>) {\n\t\tassertUniformName(name);\n\t\tconst source = definition?.source;\n\t\tconst normalizedSource = cloneTextureValue(source);\n\n\t\tconst clonedDefinition: TextureDefinition = {\n\t\t\t...(definition ?? {}),\n\t\t\t...(source !== undefined ? { source: normalizedSource } : {})\n\t\t};\n\n\t\tresolved[name] = Object.freeze(clonedDefinition);\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Clones and validates define declarations.\n */\nfunction resolveDefines<TDefineKey extends string>(\n\tdefines: MaterialDefines<TDefineKey> | undefined\n): MaterialDefines<TDefineKey> {\n\treturn normalizeDefines(defines);\n}\n\n/**\n * Clones and validates include declarations.\n */\nfunction resolveIncludes<TIncludeKey extends string>(\n\tincludes: MaterialIncludes<TIncludeKey> | undefined\n): MaterialIncludes<TIncludeKey> {\n\treturn normalizeIncludes(includes);\n}\n\n/**\n * Builds a deterministic texture-config signature map used in material cache signatures.\n *\n * @param textures - Raw texture definitions from material input.\n * @param textureKeys - Sorted texture keys.\n * @returns Compact signature entries describing effective texture config per key.\n */\nfunction buildTextureConfigSignature<TTextureKey extends string>(\n\ttextures: TextureDefinitionMap<TTextureKey>,\n\ttextureKeys: TTextureKey[]\n): Record<TTextureKey, string> {\n\tconst signature = {} as Record<TTextureKey, string>;\n\n\tfor (const key of textureKeys) {\n\t\tconst normalized = normalizeTextureDefinition(textures[key]);\n\t\tsignature[key] = [\n\t\t\tnormalized.colorSpace,\n\t\t\tnormalized.flipY ? '1' : '0',\n\t\t\tnormalized.generateMipmaps ? '1' : '0',\n\t\t\tnormalized.premultipliedAlpha ? '1' : '0',\n\t\t\tnormalized.anisotropy,\n\t\t\tnormalized.filter,\n\t\t\tnormalized.addressModeU,\n\t\t\tnormalized.addressModeV\n\t\t].join(':');\n\t}\n\n\treturn signature;\n}\n\n/**\n * Creates a stable WGSL define block from the provided map.\n *\n * @param defines - Optional material defines.\n * @returns Joined WGSL const declarations ordered by key.\n */\nexport function buildDefinesBlock(defines: MaterialDefines | undefined): string {\n\tconst normalizedDefines = normalizeDefines(defines);\n\tif (Object.keys(normalizedDefines).length === 0) {\n\t\treturn '';\n\t}\n\n\treturn Object.entries(normalizedDefines)\n\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t.map(([key, value]) => {\n\t\t\tassertUniformName(key);\n\t\t\treturn toDefineLine(key, value);\n\t\t})\n\t\t.join('\\n');\n}\n\n/**\n * Prepends resolved defines to a fragment shader.\n *\n * @param fragment - Raw WGSL fragment source.\n * @param defines - Optional define map.\n * @returns Fragment source with a leading define block when defines are present.\n */\nexport function applyMaterialDefines(\n\tfragment: string,\n\tdefines: MaterialDefines | undefined\n): string {\n\tconst defineBlock = buildDefinesBlock(defines);\n\tif (defineBlock.length === 0) {\n\t\treturn fragment;\n\t}\n\n\treturn `${defineBlock}\\n\\n${fragment}`;\n}\n\n/**\n * Creates an immutable material object with validated shader/uniform/texture contracts.\n *\n * @param input - User material declaration.\n * @returns Frozen material object safe to share and cache.\n */\nexport function defineMaterial<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTDefineKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n>(\n\tinput: FragMaterialInput<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey>\n): FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey> {\n\tconst fragment = resolveFragment(input.fragment);\n\tconst uniforms = Object.freeze(resolveUniforms(input.uniforms));\n\tconst textures = Object.freeze(resolveTextures(input.textures));\n\tconst defines = Object.freeze(resolveDefines(input.defines));\n\tconst includes = Object.freeze(resolveIncludes(input.includes));\n\tconst source = Object.freeze(resolveSourceMetadata(undefined));\n\n\t// Validate and freeze storage buffers\n\tconst rawStorageBuffers =\n\t\tinput.storageBuffers ?? ({} as StorageBufferDefinitionMap<TStorageBufferKey>);\n\tfor (const [name, definition] of Object.entries(rawStorageBuffers) as Array<\n\t\t[string, StorageBufferDefinition]\n\t>) {\n\t\tassertStorageBufferDefinition(name, definition);\n\t}\n\tconst storageBuffers = Object.freeze(\n\t\tObject.fromEntries(\n\t\t\tObject.entries(rawStorageBuffers).map(([name, definition]) => {\n\t\t\t\tconst def = definition as StorageBufferDefinition;\n\t\t\t\tconst cloned: StorageBufferDefinition = {\n\t\t\t\t\tsize: def.size,\n\t\t\t\t\ttype: def.type,\n\t\t\t\t\t...(def.access !== undefined ? { access: def.access } : {}),\n\t\t\t\t\t...(def.initialData !== undefined\n\t\t\t\t\t\t? { initialData: def.initialData.slice() as typeof def.initialData }\n\t\t\t\t\t\t: {})\n\t\t\t\t};\n\t\t\t\treturn [name, Object.freeze(cloned)];\n\t\t\t})\n\t\t)\n\t) as Readonly<StorageBufferDefinitionMap<TStorageBufferKey>>;\n\n\t// Validate storage textures\n\tfor (const [name, definition] of Object.entries(textures) as Array<[string, TextureDefinition]>) {\n\t\tif (definition?.storage) {\n\t\t\tif (!definition.format) {\n\t\t\t\tthrow new Error(`Texture \"${name}\" with storage:true requires a \\`format\\` field.`);\n\t\t\t}\n\t\t\tassertStorageTextureFormat(name, definition.format);\n\t\t}\n\t}\n\n\tconst preprocessed = preprocessMaterialFragment({\n\t\tfragment,\n\t\tdefines,\n\t\tincludes\n\t});\n\n\tconst material: FragMaterial<\n\t\tTUniformKey,\n\t\tTTextureKey,\n\t\tTDefineKey,\n\t\tTIncludeKey,\n\t\tTStorageBufferKey\n\t> = Object.freeze({\n\t\tfragment,\n\t\tuniforms,\n\t\ttextures,\n\t\tdefines,\n\t\tincludes,\n\t\tstorageBuffers\n\t});\n\n\tpreprocessedFragmentCache.set(material, preprocessed);\n\tmaterialSourceMetadataCache.set(material, source);\n\treturn material;\n}\n\n/**\n * Resolves a material to renderer-ready data and a deterministic signature.\n *\n * @param material - Material input created via {@link defineMaterial}.\n * @returns Resolved material with packed uniform layout, sorted texture keys and cache signature.\n */\nexport function resolveMaterial<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTDefineKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n>(\n\tmaterial: FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey>\n): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> {\n\tassertDefinedMaterial(material);\n\n\tconst cached = getCachedResolvedMaterial(material);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\tconst uniforms = material.uniforms as UniformMap<TUniformKey>;\n\tconst textures = material.textures as TextureDefinitionMap<TTextureKey>;\n\tconst uniformLayout = resolveUniformLayout(uniforms);\n\tconst textureKeys = Object.keys(textures).sort() as TTextureKey[];\n\tconst preprocessed =\n\t\tpreprocessedFragmentCache.get(material) ??\n\t\tpreprocessMaterialFragment({\n\t\t\tfragment: material.fragment,\n\t\t\tdefines: material.defines,\n\t\t\tincludes: material.includes\n\t\t});\n\tconst fragmentWgsl = preprocessed.fragment;\n\tconst textureConfig = buildTextureConfigSignature(textures, textureKeys);\n\n\tconst storageBufferKeys = Object.keys(\n\t\tmaterial.storageBuffers ?? {}\n\t).sort() as TStorageBufferKey[];\n\tconst storageTextureKeys = textureKeys.filter(\n\t\t(key) => (textures[key] as TextureDefinition)?.storage === true\n\t);\n\n\tconst signature = JSON.stringify({\n\t\tfragmentWgsl,\n\t\tuniforms: uniformLayout.entries.map((entry) => `${entry.name}:${entry.type}`),\n\t\ttextureKeys,\n\t\ttextureConfig,\n\t\tstorageBufferKeys: storageBufferKeys.map((key) => {\n\t\t\tconst def = (material.storageBuffers as StorageBufferDefinitionMap)[key];\n\t\t\treturn `${key}:${def?.type ?? '?'}:${def?.size ?? 0}:${def?.access ?? 'read-write'}`;\n\t\t}),\n\t\tstorageTextureKeys\n\t});\n\n\tconst resolved: ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> = {\n\t\tfragmentWgsl,\n\t\tfragmentLineMap: preprocessed.lineMap,\n\t\tuniforms,\n\t\ttextures,\n\t\tuniformLayout,\n\t\ttextureKeys,\n\t\tsignature,\n\t\tfragmentSource: material.fragment,\n\t\tincludeSources: material.includes as MaterialIncludes<TIncludeKey>,\n\t\tdefineBlockSource: preprocessed.defineBlockSource,\n\t\tsource: materialSourceMetadataCache.get(material) ?? null,\n\t\tstorageBufferKeys,\n\t\tstorageTextureKeys\n\t};\n\n\tresolvedMaterialCache.set(material, resolved);\n\treturn resolved;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"material.js","names":[],"sources":["../../src/lib/core/material.ts"],"sourcesContent":["import { normalizeTextureDefinition } from './textures.js';\nimport type { MaterialSourceMetadata } from './error-diagnostics.js';\nimport {\n\tassertUniformName,\n\tassertUniformValueForType,\n\tinferUniformType,\n\tresolveUniformLayout\n} from './uniforms.js';\nimport {\n\tnormalizeDefines,\n\tnormalizeIncludes,\n\tpreprocessMaterialFragment,\n\ttoDefineLine,\n\ttype MaterialLineMap,\n\ttype PreprocessedMaterialFragment\n} from './material-preprocess.js';\nimport { assertStorageBufferDefinition, assertStorageTextureFormat } from './storage-buffers.js';\nimport type {\n\tStorageBufferDefinition,\n\tStorageBufferDefinitionMap,\n\tTextureData,\n\tTextureDefinition,\n\tTextureDefinitionMap,\n\tTextureValue,\n\tTypedUniform,\n\tUniformMap,\n\tUniformValue\n} from './types.js';\n\n/**\n * Typed compile-time define declaration.\n */\nexport type TypedMaterialDefineValue =\n\t| {\n\t\t\t/**\n\t\t\t * WGSL scalar type.\n\t\t\t */\n\t\t\ttype: 'bool';\n\t\t\t/**\n\t\t\t * Literal value for the selected WGSL type.\n\t\t\t */\n\t\t\tvalue: boolean;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * WGSL scalar type.\n\t\t\t */\n\t\t\ttype: 'f32' | 'i32' | 'u32';\n\t\t\t/**\n\t\t\t * Literal value for the selected WGSL type.\n\t\t\t */\n\t\t\tvalue: number;\n\t };\n\n/**\n * Allowed value types for WGSL `const` define injection.\n */\nexport type MaterialDefineValue = boolean | number | TypedMaterialDefineValue;\n\n/**\n * Define map keyed by uniform-compatible identifier names.\n */\nexport type MaterialDefines<TKey extends string = string> = Record<TKey, MaterialDefineValue>;\n\n/**\n * Include map keyed by include identifier used in `#include <name>` directives.\n */\nexport type MaterialIncludes<TKey extends string = string> = Record<TKey, string>;\n\n/**\n * External material input accepted by {@link defineMaterial}.\n */\nexport interface FragMaterialInput<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTDefineKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n> {\n\t/**\n\t * User WGSL source containing `frag(uv: vec2f) -> vec4f`.\n\t */\n\tfragment: string;\n\t/**\n\t * Initial uniform values.\n\t */\n\tuniforms?: UniformMap<TUniformKey>;\n\t/**\n\t * Texture definitions keyed by texture uniform name.\n\t */\n\ttextures?: TextureDefinitionMap<TTextureKey>;\n\t/**\n\t * Optional compile-time define constants injected into WGSL.\n\t */\n\tdefines?: MaterialDefines<TDefineKey>;\n\t/**\n\t * Optional WGSL include chunks used by `#include <name>` directives.\n\t */\n\tincludes?: MaterialIncludes<TIncludeKey>;\n\t/**\n\t * Optional storage buffer definitions for compute shaders.\n\t */\n\tstorageBuffers?: StorageBufferDefinitionMap<TStorageBufferKey>;\n}\n\n/**\n * Normalized and immutable material declaration consumed by `FragCanvas`.\n */\nexport interface FragMaterial<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTDefineKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n> {\n\t/**\n\t * User WGSL source containing `frag(uv: vec2f) -> vec4f`.\n\t */\n\treadonly fragment: string;\n\t/**\n\t * Initial uniform values.\n\t */\n\treadonly uniforms: Readonly<UniformMap<TUniformKey>>;\n\t/**\n\t * Texture definitions keyed by texture uniform name.\n\t */\n\treadonly textures: Readonly<TextureDefinitionMap<TTextureKey>>;\n\t/**\n\t * Optional compile-time define constants injected into WGSL.\n\t */\n\treadonly defines: Readonly<MaterialDefines<TDefineKey>>;\n\t/**\n\t * Optional WGSL include chunks used by `#include <name>` directives.\n\t */\n\treadonly includes: Readonly<MaterialIncludes<TIncludeKey>>;\n\t/**\n\t * Storage buffer definitions for compute shaders. Empty when not provided.\n\t */\n\treadonly storageBuffers: Readonly<StorageBufferDefinitionMap<TStorageBufferKey>>;\n}\n\n/**\n * Fully resolved, immutable material snapshot used for renderer creation/caching.\n */\nexport interface ResolvedMaterial<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n> {\n\t/**\n\t * Final fragment WGSL after define injection.\n\t */\n\tfragmentWgsl: string;\n\t/**\n\t * 1-based map from generated fragment lines to user source lines.\n\t */\n\tfragmentLineMap: MaterialLineMap;\n\t/**\n\t * Cloned uniforms.\n\t */\n\tuniforms: UniformMap<TUniformKey>;\n\t/**\n\t * Cloned texture definitions.\n\t */\n\ttextures: TextureDefinitionMap<TTextureKey>;\n\t/**\n\t * Resolved packed uniform layout.\n\t */\n\tuniformLayout: ReturnType<typeof resolveUniformLayout>;\n\t/**\n\t * Sorted texture keys.\n\t */\n\ttextureKeys: TTextureKey[];\n\t/**\n\t * Deterministic JSON signature for cache invalidation.\n\t */\n\tsignature: string;\n\t/**\n\t * Original user fragment source before preprocessing.\n\t */\n\tfragmentSource: string;\n\t/**\n\t * Normalized include sources map.\n\t */\n\tincludeSources: MaterialIncludes<TIncludeKey>;\n\t/**\n\t * Deterministic define block source used for diagnostics mapping.\n\t */\n\tdefineBlockSource: string;\n\t/**\n\t * Source metadata used for diagnostics.\n\t */\n\tsource: Readonly<MaterialSourceMetadata> | null;\n\t/**\n\t * Sorted storage buffer keys. Empty array when no storage buffers declared.\n\t */\n\tstorageBufferKeys: TStorageBufferKey[];\n\t/**\n\t * Sorted storage texture keys (textures with storage: true).\n\t */\n\tstorageTextureKeys: TTextureKey[];\n}\n\n/**\n * Strict fragment contract used by MotionGPU.\n */\nconst FRAGMENT_FUNCTION_SIGNATURE_PATTERN =\n\t/\\bfn\\s+frag\\s*\\(\\s*([^)]*?)\\s*\\)\\s*->\\s*([A-Za-z_][A-Za-z0-9_<>\\s]*)\\s*(?:\\{|$)/m;\nconst FRAGMENT_FUNCTION_NAME_PATTERN = /\\bfn\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(/g;\n\n/**\n * Cache of resolved material snapshots keyed by immutable material instance.\n */\ntype AnyFragMaterial = FragMaterial<string, string, string, string, string>;\ntype AnyResolvedMaterial = ResolvedMaterial<string, string, string, string>;\n\nconst resolvedMaterialCache = new WeakMap<AnyFragMaterial, AnyResolvedMaterial>();\nconst preprocessedFragmentCache = new WeakMap<AnyFragMaterial, PreprocessedMaterialFragment>();\nconst materialSourceMetadataCache = new WeakMap<AnyFragMaterial, MaterialSourceMetadata | null>();\n\nfunction getCachedResolvedMaterial<\n\tTUniformKey extends string,\n\tTTextureKey extends string,\n\tTIncludeKey extends string,\n\tTStorageBufferKey extends string\n>(\n\tmaterial: FragMaterial<TUniformKey, TTextureKey, string, TIncludeKey, TStorageBufferKey>\n): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> | null {\n\tconst cached = resolvedMaterialCache.get(material);\n\tif (!cached) {\n\t\treturn null;\n\t}\n\n\t// Invariant: the cache key is the same material object used to produce this resolved payload.\n\treturn cached as ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey>;\n}\n\nconst STACK_TRACE_CHROME_PATTERN = /^\\s*at\\s+(?:(.*?)\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/;\nconst STACK_TRACE_FIREFOX_PATTERN = /^(.*?)@(.+?):(\\d+):(\\d+)$/;\n\nfunction getPathBasename(path: string): string {\n\tconst normalized = path.split(/[?#]/)[0] ?? path;\n\tconst parts = normalized.split(/[\\\\/]/);\n\tconst last = parts[parts.length - 1];\n\treturn last && last.length > 0 ? last : path;\n}\n\nfunction normalizeSignaturePart(value: string): string {\n\treturn value.replace(/\\s+/g, ' ').trim();\n}\n\nfunction listFunctionNames(fragment: string): string[] {\n\tconst names = new Set<string>();\n\tfor (const match of fragment.matchAll(FRAGMENT_FUNCTION_NAME_PATTERN)) {\n\t\tconst name = match[1];\n\t\tif (!name) {\n\t\t\tcontinue;\n\t\t}\n\t\tnames.add(name);\n\t}\n\n\treturn Array.from(names);\n}\n\nfunction captureMaterialSourceFromStack(): MaterialSourceMetadata | null {\n\tconst stack = new Error().stack;\n\tif (!stack) {\n\t\treturn null;\n\t}\n\n\tconst stackLines = stack.split('\\n').slice(1);\n\tfor (const rawLine of stackLines) {\n\t\tconst line = rawLine.trim();\n\t\tif (line.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst chromeMatch = line.match(STACK_TRACE_CHROME_PATTERN);\n\t\tconst firefoxMatch = line.match(STACK_TRACE_FIREFOX_PATTERN);\n\t\tconst functionName = chromeMatch?.[1] ?? firefoxMatch?.[1] ?? undefined;\n\t\tconst file = chromeMatch?.[2] ?? firefoxMatch?.[2];\n\t\tconst lineValue = chromeMatch?.[3] ?? firefoxMatch?.[3];\n\t\tconst columnValue = chromeMatch?.[4] ?? firefoxMatch?.[4];\n\n\t\tif (!file || !lineValue || !columnValue) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (file.includes('/core/material') || file.includes('\\\\core\\\\material')) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst parsedLine = Number.parseInt(lineValue, 10);\n\t\tconst parsedColumn = Number.parseInt(columnValue, 10);\n\t\tif (!Number.isFinite(parsedLine) || !Number.isFinite(parsedColumn)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn {\n\t\t\tcomponent: getPathBasename(file),\n\t\t\tfile,\n\t\t\tline: parsedLine,\n\t\t\tcolumn: parsedColumn,\n\t\t\t...(functionName ? { functionName } : {})\n\t\t};\n\t}\n\n\treturn null;\n}\n\nfunction resolveSourceMetadata(\n\tsource: MaterialSourceMetadata | undefined\n): MaterialSourceMetadata | null {\n\tconst captured = captureMaterialSourceFromStack();\n\tconst component = source?.component ?? captured?.component;\n\tconst file = source?.file ?? captured?.file;\n\tconst line = source?.line ?? captured?.line;\n\tconst column = source?.column ?? captured?.column;\n\tconst functionName = source?.functionName ?? captured?.functionName;\n\n\tif (\n\t\tcomponent === undefined &&\n\t\tfile === undefined &&\n\t\tline === undefined &&\n\t\tcolumn === undefined &&\n\t\tfunctionName === undefined\n\t) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\t...(component !== undefined ? { component } : {}),\n\t\t...(file !== undefined ? { file } : {}),\n\t\t...(line !== undefined ? { line } : {}),\n\t\t...(column !== undefined ? { column } : {}),\n\t\t...(functionName !== undefined ? { functionName } : {})\n\t};\n}\n\n/**\n * Asserts that material has been normalized by {@link defineMaterial}.\n */\nfunction assertDefinedMaterial(material: AnyFragMaterial): void {\n\tif (\n\t\t!Object.isFrozen(material) ||\n\t\t!material.uniforms ||\n\t\t!material.textures ||\n\t\t!material.defines ||\n\t\t!material.includes\n\t) {\n\t\tthrow new Error(\n\t\t\t'Invalid material instance. Create materials with defineMaterial(...) before passing them to <FragCanvas>.'\n\t\t);\n\t}\n}\n\n/**\n * Clones uniform value input to decouple material instances from external objects.\n */\nfunction cloneUniformValue(value: UniformValue): UniformValue {\n\tif (typeof value === 'number') {\n\t\treturn value;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn Object.freeze([...value]) as UniformValue;\n\t}\n\n\tif (typeof value === 'object' && value !== null && 'type' in value && 'value' in value) {\n\t\tconst typed = value as TypedUniform;\n\t\tconst typedValue = typed.value as unknown;\n\n\t\tlet clonedTypedValue = typedValue;\n\t\tif (typedValue instanceof Float32Array) {\n\t\t\tclonedTypedValue = new Float32Array(typedValue);\n\t\t} else if (Array.isArray(typedValue)) {\n\t\t\tclonedTypedValue = Object.freeze([...typedValue]);\n\t\t}\n\n\t\treturn Object.freeze({\n\t\t\ttype: typed.type,\n\t\t\tvalue: clonedTypedValue\n\t\t}) as UniformValue;\n\t}\n\n\treturn value;\n}\n\n/**\n * Clones optional texture value payload.\n */\nfunction cloneTextureValue(value: TextureValue | undefined): TextureValue {\n\tif (value === undefined || value === null) {\n\t\treturn null;\n\t}\n\n\tif (typeof value === 'object' && 'source' in value) {\n\t\tconst data = value as TextureData;\n\t\treturn {\n\t\t\tsource: data.source,\n\t\t\t...(data.width !== undefined ? { width: data.width } : {}),\n\t\t\t...(data.height !== undefined ? { height: data.height } : {}),\n\t\t\t...(data.colorSpace !== undefined ? { colorSpace: data.colorSpace } : {}),\n\t\t\t...(data.flipY !== undefined ? { flipY: data.flipY } : {}),\n\t\t\t...(data.premultipliedAlpha !== undefined\n\t\t\t\t? { premultipliedAlpha: data.premultipliedAlpha }\n\t\t\t\t: {}),\n\t\t\t...(data.generateMipmaps !== undefined ? { generateMipmaps: data.generateMipmaps } : {}),\n\t\t\t...(data.update !== undefined ? { update: data.update } : {})\n\t\t};\n\t}\n\n\treturn value;\n}\n\n/**\n * Clones and validates fragment source contract.\n */\nfunction resolveFragment(fragment: string): string {\n\tif (typeof fragment !== 'string' || fragment.trim().length === 0) {\n\t\tthrow new Error('Material fragment shader must be a non-empty WGSL string.');\n\t}\n\n\tconst signature = fragment.match(FRAGMENT_FUNCTION_SIGNATURE_PATTERN);\n\tif (!signature) {\n\t\tconst discoveredFunctions = listFunctionNames(fragment).slice(0, 4);\n\t\tconst discoveredLabel =\n\t\t\tdiscoveredFunctions.length > 0\n\t\t\t\t? `Found: ${discoveredFunctions.map((name) => `\\`${name}(...)\\``).join(', ')}.`\n\t\t\t\t: 'No WGSL function declarations were found.';\n\n\t\tthrow new Error(\n\t\t\t`Material fragment contract mismatch: missing entrypoint \\`fn frag(uv: vec2f) -> vec4f\\`. ${discoveredLabel}`\n\t\t);\n\t}\n\n\tconst params = normalizeSignaturePart(signature[1] ?? '');\n\tconst returnType = normalizeSignaturePart(signature[2] ?? '');\n\n\tif (params !== 'uv: vec2f') {\n\t\tthrow new Error(\n\t\t\t`Material fragment contract mismatch for \\`frag\\`: expected parameter list \\`(uv: vec2f)\\`, received \\`(${params || '...'})\\`.`\n\t\t);\n\t}\n\n\tif (returnType !== 'vec4f') {\n\t\tthrow new Error(\n\t\t\t`Material fragment contract mismatch for \\`frag\\`: expected return type \\`vec4f\\`, received \\`${returnType}\\`.`\n\t\t);\n\t}\n\n\treturn fragment;\n}\n\n/**\n * Clones and validates uniform declarations.\n */\nfunction resolveUniforms<TUniformKey extends string>(\n\tuniforms: UniformMap<TUniformKey> | undefined\n): UniformMap<TUniformKey> {\n\tconst resolved: UniformMap<TUniformKey> = {} as UniformMap<TUniformKey>;\n\n\tfor (const [name, value] of Object.entries(uniforms ?? {}) as Array<\n\t\t[TUniformKey, UniformValue]\n\t>) {\n\t\tassertUniformName(name);\n\t\tconst clonedValue = cloneUniformValue(value);\n\t\tconst type = inferUniformType(clonedValue);\n\t\tassertUniformValueForType(type, clonedValue);\n\t\tresolved[name] = clonedValue;\n\t}\n\n\tresolveUniformLayout(resolved);\n\treturn resolved;\n}\n\n/**\n * Clones and validates texture declarations.\n */\nfunction resolveTextures<TTextureKey extends string>(\n\ttextures: TextureDefinitionMap<TTextureKey> | undefined\n): TextureDefinitionMap<TTextureKey> {\n\tconst resolved: TextureDefinitionMap<TTextureKey> = {} as TextureDefinitionMap<TTextureKey>;\n\n\tfor (const [name, definition] of Object.entries(textures ?? {}) as Array<\n\t\t[TTextureKey, TextureDefinition]\n\t>) {\n\t\tassertUniformName(name);\n\t\tconst source = definition?.source;\n\t\tconst normalizedSource = cloneTextureValue(source);\n\n\t\tconst clonedDefinition: TextureDefinition = {\n\t\t\t...(definition ?? {}),\n\t\t\t...(source !== undefined ? { source: normalizedSource } : {})\n\t\t};\n\n\t\tresolved[name] = Object.freeze(clonedDefinition);\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Clones and validates define declarations.\n */\nfunction resolveDefines<TDefineKey extends string>(\n\tdefines: MaterialDefines<TDefineKey> | undefined\n): MaterialDefines<TDefineKey> {\n\treturn normalizeDefines(defines);\n}\n\n/**\n * Clones and validates include declarations.\n */\nfunction resolveIncludes<TIncludeKey extends string>(\n\tincludes: MaterialIncludes<TIncludeKey> | undefined\n): MaterialIncludes<TIncludeKey> {\n\treturn normalizeIncludes(includes);\n}\n\n/**\n * Builds a deterministic texture-config signature map used in material cache signatures.\n *\n * @param textures - Raw texture definitions from material input.\n * @param textureKeys - Sorted texture keys.\n * @returns Compact signature entries describing effective texture config per key.\n */\nfunction buildTextureConfigSignature<TTextureKey extends string>(\n\ttextures: TextureDefinitionMap<TTextureKey>,\n\ttextureKeys: TTextureKey[]\n): Record<TTextureKey, string> {\n\tconst signature = {} as Record<TTextureKey, string>;\n\n\tfor (const key of textureKeys) {\n\t\tconst normalized = normalizeTextureDefinition(textures[key]);\n\t\tsignature[key] = [\n\t\t\tnormalized.colorSpace,\n\t\t\tnormalized.flipY ? '1' : '0',\n\t\t\tnormalized.generateMipmaps ? '1' : '0',\n\t\t\tnormalized.premultipliedAlpha ? '1' : '0',\n\t\t\tnormalized.anisotropy,\n\t\t\tnormalized.filter,\n\t\t\tnormalized.addressModeU,\n\t\t\tnormalized.addressModeV,\n\t\t\tnormalized.fragmentVisible ? '1' : '0'\n\t\t].join(':');\n\t}\n\n\treturn signature;\n}\n\n/**\n * Creates a stable WGSL define block from the provided map.\n *\n * @param defines - Optional material defines.\n * @returns Joined WGSL const declarations ordered by key.\n */\nexport function buildDefinesBlock(defines: MaterialDefines | undefined): string {\n\tconst normalizedDefines = normalizeDefines(defines);\n\tif (Object.keys(normalizedDefines).length === 0) {\n\t\treturn '';\n\t}\n\n\treturn Object.entries(normalizedDefines)\n\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t.map(([key, value]) => {\n\t\t\tassertUniformName(key);\n\t\t\treturn toDefineLine(key, value);\n\t\t})\n\t\t.join('\\n');\n}\n\n/**\n * Prepends resolved defines to a fragment shader.\n *\n * @param fragment - Raw WGSL fragment source.\n * @param defines - Optional define map.\n * @returns Fragment source with a leading define block when defines are present.\n */\nexport function applyMaterialDefines(\n\tfragment: string,\n\tdefines: MaterialDefines | undefined\n): string {\n\tconst defineBlock = buildDefinesBlock(defines);\n\tif (defineBlock.length === 0) {\n\t\treturn fragment;\n\t}\n\n\treturn `${defineBlock}\\n\\n${fragment}`;\n}\n\n/**\n * Creates an immutable material object with validated shader/uniform/texture contracts.\n *\n * @param input - User material declaration.\n * @returns Frozen material object safe to share and cache.\n */\nexport function defineMaterial<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTDefineKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n>(\n\tinput: FragMaterialInput<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey>\n): FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey> {\n\tconst fragment = resolveFragment(input.fragment);\n\tconst uniforms = Object.freeze(resolveUniforms(input.uniforms));\n\tconst textures = Object.freeze(resolveTextures(input.textures));\n\tconst defines = Object.freeze(resolveDefines(input.defines));\n\tconst includes = Object.freeze(resolveIncludes(input.includes));\n\tconst source = Object.freeze(resolveSourceMetadata(undefined));\n\n\t// Validate and freeze storage buffers\n\tconst rawStorageBuffers =\n\t\tinput.storageBuffers ?? ({} as StorageBufferDefinitionMap<TStorageBufferKey>);\n\tfor (const [name, definition] of Object.entries(rawStorageBuffers) as Array<\n\t\t[string, StorageBufferDefinition]\n\t>) {\n\t\tassertStorageBufferDefinition(name, definition);\n\t}\n\tconst storageBuffers = Object.freeze(\n\t\tObject.fromEntries(\n\t\t\tObject.entries(rawStorageBuffers).map(([name, definition]) => {\n\t\t\t\tconst def = definition as StorageBufferDefinition;\n\t\t\t\tconst cloned: StorageBufferDefinition = {\n\t\t\t\t\tsize: def.size,\n\t\t\t\t\ttype: def.type,\n\t\t\t\t\t...(def.access !== undefined ? { access: def.access } : {}),\n\t\t\t\t\t...(def.initialData !== undefined\n\t\t\t\t\t\t? { initialData: def.initialData.slice() as typeof def.initialData }\n\t\t\t\t\t\t: {})\n\t\t\t\t};\n\t\t\t\treturn [name, Object.freeze(cloned)];\n\t\t\t})\n\t\t)\n\t) as Readonly<StorageBufferDefinitionMap<TStorageBufferKey>>;\n\n\t// Validate storage textures\n\tfor (const [name, definition] of Object.entries(textures) as Array<[string, TextureDefinition]>) {\n\t\tif (definition?.storage) {\n\t\t\tif (!definition.format) {\n\t\t\t\tthrow new Error(`Texture \"${name}\" with storage:true requires a \\`format\\` field.`);\n\t\t\t}\n\t\t\tassertStorageTextureFormat(name, definition.format);\n\t\t}\n\t}\n\n\tconst preprocessed = preprocessMaterialFragment({\n\t\tfragment,\n\t\tdefines,\n\t\tincludes\n\t});\n\n\tconst material: FragMaterial<\n\t\tTUniformKey,\n\t\tTTextureKey,\n\t\tTDefineKey,\n\t\tTIncludeKey,\n\t\tTStorageBufferKey\n\t> = Object.freeze({\n\t\tfragment,\n\t\tuniforms,\n\t\ttextures,\n\t\tdefines,\n\t\tincludes,\n\t\tstorageBuffers\n\t});\n\n\tpreprocessedFragmentCache.set(material, preprocessed);\n\tmaterialSourceMetadataCache.set(material, source);\n\treturn material;\n}\n\n/**\n * Resolves a material to renderer-ready data and a deterministic signature.\n *\n * @param material - Material input created via {@link defineMaterial}.\n * @returns Resolved material with packed uniform layout, sorted texture keys and cache signature.\n */\nexport function resolveMaterial<\n\tTUniformKey extends string = string,\n\tTTextureKey extends string = string,\n\tTDefineKey extends string = string,\n\tTIncludeKey extends string = string,\n\tTStorageBufferKey extends string = string\n>(\n\tmaterial: FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey>\n): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> {\n\tassertDefinedMaterial(material);\n\n\tconst cached = getCachedResolvedMaterial(material);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\n\tconst uniforms = material.uniforms as UniformMap<TUniformKey>;\n\tconst textures = material.textures as TextureDefinitionMap<TTextureKey>;\n\tconst uniformLayout = resolveUniformLayout(uniforms);\n\tconst textureKeys = Object.keys(textures).sort() as TTextureKey[];\n\tconst preprocessed =\n\t\tpreprocessedFragmentCache.get(material) ??\n\t\tpreprocessMaterialFragment({\n\t\t\tfragment: material.fragment,\n\t\t\tdefines: material.defines,\n\t\t\tincludes: material.includes\n\t\t});\n\tconst fragmentWgsl = preprocessed.fragment;\n\tconst textureConfig = buildTextureConfigSignature(textures, textureKeys);\n\n\tconst storageBufferKeys = Object.keys(\n\t\tmaterial.storageBuffers ?? {}\n\t).sort() as TStorageBufferKey[];\n\tconst storageTextureKeys = textureKeys.filter(\n\t\t(key) => (textures[key] as TextureDefinition)?.storage === true\n\t);\n\n\tconst signature = JSON.stringify({\n\t\tfragmentWgsl,\n\t\tuniforms: uniformLayout.entries.map((entry) => `${entry.name}:${entry.type}`),\n\t\ttextureKeys,\n\t\ttextureConfig,\n\t\tstorageBufferKeys: storageBufferKeys.map((key) => {\n\t\t\tconst def = (material.storageBuffers as StorageBufferDefinitionMap)[key];\n\t\t\treturn `${key}:${def?.type ?? '?'}:${def?.size ?? 0}:${def?.access ?? 'read-write'}`;\n\t\t}),\n\t\tstorageTextureKeys\n\t});\n\n\tconst resolved: ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> = {\n\t\tfragmentWgsl,\n\t\tfragmentLineMap: preprocessed.lineMap,\n\t\tuniforms,\n\t\ttextures,\n\t\tuniformLayout,\n\t\ttextureKeys,\n\t\tsignature,\n\t\tfragmentSource: material.fragment,\n\t\tincludeSources: material.includes as MaterialIncludes<TIncludeKey>,\n\t\tdefineBlockSource: preprocessed.defineBlockSource,\n\t\tsource: materialSourceMetadataCache.get(material) ?? null,\n\t\tstorageBufferKeys,\n\t\tstorageTextureKeys\n\t};\n\n\tresolvedMaterialCache.set(material, resolved);\n\treturn resolved;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/lib/core/renderer.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAOX,QAAQ,EACR,eAAe,EAMf,MAAM,YAAY,CAAC;AAgapB;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,YAAY,EAClB,iBAAiB,SAAwB,GACvC,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAwCzC;AAsFD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyhDhF"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/lib/core/renderer.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAOX,QAAQ,EACR,eAAe,EAMf,MAAM,YAAY,CAAC;AAmepB;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,YAAY,EAClB,iBAAiB,SAAwB,GACvC,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAwCzC;AAsFD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+jDhF"}
@@ -5,7 +5,8 @@ import { attachShaderCompilationDiagnostics } from "./error-diagnostics.js";
5
5
  import { buildShaderSourceWithMap, formatShaderSourceLocation } from "./shader.js";
6
6
  import { buildRenderTargetSignature, resolveRenderTargetDefinitions } from "./render-targets.js";
7
7
  import { planRenderGraph } from "./render-graph.js";
8
- import { buildComputeShaderSource, buildPingPongComputeShaderSource, extractWorkgroupSize, storageTextureSampleScalarType } from "./compute-shader.js";
8
+ import { buildComputeShaderSourceWithMap, buildPingPongComputeShaderSourceWithMap, extractWorkgroupSize, storageTextureSampleScalarType } from "./compute-shader.js";
9
+ import { createComputeStorageBindGroupCache } from "./compute-bindgroup-cache.js";
9
10
  //#region src/lib/core/renderer.ts
10
11
  /**
11
12
  * Binding index for frame uniforms (`time`, `delta`, `resolution`).
@@ -74,10 +75,13 @@ async function assertCompilation(module, options) {
74
75
  if (contextLabel.length === 0) return diagnostic.message;
75
76
  return `[${contextLabel.join(" | ")}] ${diagnostic.message}`;
76
77
  }).join("\n");
77
- throw attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`WGSL compilation failed:\n${summary}`), {
78
+ const prefix = options?.errorPrefix ?? "WGSL compilation failed";
79
+ throw attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`${prefix}:\n${summary}`), {
78
80
  kind: "shader-compilation",
81
+ ...options?.shaderStage !== void 0 ? { shaderStage: options.shaderStage } : {},
79
82
  diagnostics,
80
83
  fragmentSource: options?.fragmentSource ?? "",
84
+ ...options?.computeSource !== void 0 ? { computeSource: options.computeSource } : {},
81
85
  includeSources: options?.includeSources ?? {},
82
86
  ...options?.defineBlockSource !== void 0 ? { defineBlockSource: options.defineBlockSource } : {},
83
87
  materialSource: options?.materialSource ?? null,
@@ -87,6 +91,41 @@ async function assertCompilation(module, options) {
87
91
  function toSortedUniqueStrings(values) {
88
92
  return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
89
93
  }
94
+ function extractGeneratedLineFromComputeError(message) {
95
+ const lineMatch = message.match(/\bline\s+(\d+)\b/i);
96
+ if (lineMatch) {
97
+ const parsed = Number.parseInt(lineMatch[1] ?? "", 10);
98
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
99
+ }
100
+ const colonMatch = message.match(/:(\d+):\d+/);
101
+ if (colonMatch) {
102
+ const parsed = Number.parseInt(colonMatch[1] ?? "", 10);
103
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
104
+ }
105
+ return null;
106
+ }
107
+ function toComputeCompilationError(input) {
108
+ const baseError = input.error instanceof Error ? input.error : new Error(String(input.error ?? "Unknown error"));
109
+ const generatedLine = extractGeneratedLineFromComputeError(baseError.message) ?? 0;
110
+ const sourceLocation = generatedLine > 0 ? input.lineMap[generatedLine] ?? null : null;
111
+ const diagnostics = [{
112
+ generatedLine,
113
+ message: baseError.message,
114
+ sourceLocation
115
+ }];
116
+ const contextLabel = [formatShaderSourceLocation(sourceLocation), generatedLine > 0 ? `generated WGSL line ${generatedLine}` : null].filter((value) => Boolean(value));
117
+ const summary = contextLabel.length > 0 ? `[${contextLabel.join(" | ")}] ${baseError.message}` : baseError.message;
118
+ return attachShaderCompilationDiagnostics(/* @__PURE__ */ new Error(`Compute shader compilation failed:\n${summary}`), {
119
+ kind: "shader-compilation",
120
+ shaderStage: "compute",
121
+ diagnostics,
122
+ fragmentSource: "",
123
+ computeSource: input.computeSource,
124
+ includeSources: {},
125
+ materialSource: null,
126
+ runtimeContext: input.runtimeContext
127
+ });
128
+ }
90
129
  function buildPassGraphSnapshot(passes) {
91
130
  const declaredPasses = passes ?? [];
92
131
  let enabledPassCount = 0;
@@ -397,7 +436,8 @@ async function createRenderer(options) {
397
436
  try {
398
437
  const runtimeContext = buildShaderCompilationRuntimeContext(options);
399
438
  const convertLinearToSrgb = shouldConvertLinearToSrgb(options.outputColorSpace, format);
400
- const builtShader = buildShaderSourceWithMap(options.fragmentWgsl, options.uniformLayout, options.textureKeys, {
439
+ const fragmentTextureKeys = options.textureKeys.filter((key) => options.textureDefinitions[key]?.fragmentVisible !== false);
440
+ const builtShader = buildShaderSourceWithMap(options.fragmentWgsl, options.uniformLayout, fragmentTextureKeys, {
401
441
  convertLinearToSrgb,
402
442
  fragmentLineMap: options.fragmentLineMap,
403
443
  ...options.storageBufferKeys !== void 0 ? { storageBufferKeys: options.storageBufferKeys } : {},
@@ -417,10 +457,13 @@ async function createRenderer(options) {
417
457
  const storageBufferDefinitions = options.storageBufferDefinitions ?? {};
418
458
  const storageTextureKeys = options.storageTextureKeys ?? [];
419
459
  const storageTextureKeySet = new Set(storageTextureKeys);
420
- const textureBindings = options.textureKeys.map((key, index) => {
460
+ const fragmentTextureIndexByKey = new Map(fragmentTextureKeys.map((key, index) => [key, index]));
461
+ const textureBindings = options.textureKeys.map((key) => {
421
462
  const config = normalizedTextureDefinitions[key];
422
463
  if (!config) throw new Error(`Missing texture definition for "${key}"`);
423
- const { samplerBinding, textureBinding } = getTextureBindings(index);
464
+ const fragmentTextureIndex = fragmentTextureIndexByKey.get(key);
465
+ const fragmentVisible = fragmentTextureIndex !== void 0;
466
+ const { samplerBinding, textureBinding } = getTextureBindings(fragmentTextureIndex ?? 0);
424
467
  const sampler = device.createSampler({
425
468
  magFilter: config.filter,
426
469
  minFilter: config.filter,
@@ -438,6 +481,7 @@ async function createRenderer(options) {
438
481
  key,
439
482
  samplerBinding,
440
483
  textureBinding,
484
+ fragmentVisible,
441
485
  sampler,
442
486
  fallbackTexture,
443
487
  fallbackView,
@@ -481,7 +525,33 @@ async function createRenderer(options) {
481
525
  }
482
526
  return runtimeBinding;
483
527
  });
484
- const bindGroupLayout = device.createBindGroupLayout({ entries: createBindGroupLayoutEntries(textureBindings) });
528
+ const textureBindingByKey = new Map(textureBindings.map((binding) => [binding.key, binding]));
529
+ const fragmentTextureBindings = textureBindings.filter((binding) => binding.fragmentVisible);
530
+ const computeStorageBufferLayoutEntries = storageBufferKeys.map((key, index) => {
531
+ const bufferType = (storageBufferDefinitions[key]?.access ?? "read-write") === "read" ? "read-only-storage" : "storage";
532
+ return {
533
+ binding: index,
534
+ visibility: GPUShaderStage.COMPUTE,
535
+ buffer: { type: bufferType }
536
+ };
537
+ });
538
+ const computeStorageBufferTopologyKey = storageBufferKeys.map((key) => `${key}:${storageBufferDefinitions[key]?.access ?? "read-write"}`).join("|");
539
+ const computeStorageTextureLayoutEntries = storageTextureKeys.map((key, index) => {
540
+ const config = normalizedTextureDefinitions[key];
541
+ return {
542
+ binding: index,
543
+ visibility: GPUShaderStage.COMPUTE,
544
+ storageTexture: {
545
+ access: "write-only",
546
+ format: config?.format ?? "rgba8unorm",
547
+ viewDimension: "2d"
548
+ }
549
+ };
550
+ });
551
+ const computeStorageTextureTopologyKey = storageTextureKeys.map((key) => `${key}:${normalizedTextureDefinitions[key]?.format ?? "rgba8unorm"}`).join("|");
552
+ const computeStorageBufferBindGroupCache = createComputeStorageBindGroupCache(device);
553
+ const computeStorageTextureBindGroupCache = createComputeStorageBindGroupCache(device);
554
+ const bindGroupLayout = device.createBindGroupLayout({ entries: createBindGroupLayoutEntries(fragmentTextureBindings) });
485
555
  const fragmentStorageBindGroupLayout = storageBufferKeys.length > 0 ? device.createBindGroupLayout({ entries: storageBufferKeys.map((_, index) => ({
486
556
  binding: index,
487
557
  visibility: GPUShaderStage.FRAGMENT,
@@ -625,7 +695,9 @@ async function createRenderer(options) {
625
695
  viewA: textureA.createView(),
626
696
  textureB,
627
697
  viewB: textureB.createView(),
628
- bindGroupLayout
698
+ bindGroupLayout,
699
+ readAWriteBBindGroup: null,
700
+ readBWriteABindGroup: null
629
701
  };
630
702
  pingPongTexturePairs.set(target, pair);
631
703
  return pair;
@@ -652,14 +724,14 @@ async function createRenderer(options) {
652
724
  if (texDef?.format) storageTextureDefs[key] = { format: texDef.format };
653
725
  }
654
726
  const isPingPongPipeline = Boolean(buildOptions.pingPongTarget && buildOptions.pingPongFormat);
655
- const shaderCode = isPingPongPipeline ? buildPingPongComputeShaderSource({
727
+ const builtComputeShader = isPingPongPipeline ? buildPingPongComputeShaderSourceWithMap({
656
728
  compute: buildOptions.computeSource,
657
729
  uniformLayout: options.uniformLayout,
658
730
  storageBufferKeys,
659
731
  storageBufferDefinitions: storageBufferDefs,
660
732
  target: buildOptions.pingPongTarget,
661
733
  targetFormat: buildOptions.pingPongFormat
662
- }) : buildComputeShaderSource({
734
+ }) : buildComputeShaderSourceWithMap({
663
735
  compute: buildOptions.computeSource,
664
736
  uniformLayout: options.uniformLayout,
665
737
  storageBufferKeys,
@@ -667,7 +739,7 @@ async function createRenderer(options) {
667
739
  storageTextureKeys,
668
740
  storageTextureDefinitions: storageTextureDefs
669
741
  });
670
- const computeShaderModule = device.createShaderModule({ code: shaderCode });
742
+ const computeShaderModule = device.createShaderModule({ code: builtComputeShader.code });
671
743
  const workgroupSize = extractWorkgroupSize(buildOptions.computeSource);
672
744
  const computeUniformBGL = device.createBindGroupLayout({ entries: [{
673
745
  binding: FRAME_BINDING,
@@ -681,15 +753,7 @@ async function createRenderer(options) {
681
753
  visibility: GPUShaderStage.COMPUTE,
682
754
  buffer: { type: "uniform" }
683
755
  }] });
684
- const storageBGLEntries = storageBufferKeys.map((key, index) => {
685
- const bufferType = (storageBufferDefinitions[key]?.access ?? "read-write") === "read" ? "read-only-storage" : "storage";
686
- return {
687
- binding: index,
688
- visibility: GPUShaderStage.COMPUTE,
689
- buffer: { type: bufferType }
690
- };
691
- });
692
- const storageBGL = storageBGLEntries.length > 0 ? device.createBindGroupLayout({ entries: storageBGLEntries }) : null;
756
+ const storageBGL = computeStorageBufferLayoutEntries.length > 0 ? device.createBindGroupLayout({ entries: computeStorageBufferLayoutEntries }) : null;
693
757
  const storageTextureBGLEntries = isPingPongPipeline ? [{
694
758
  binding: 0,
695
759
  visibility: GPUShaderStage.COMPUTE,
@@ -706,41 +770,42 @@ async function createRenderer(options) {
706
770
  format: buildOptions.pingPongFormat,
707
771
  viewDimension: "2d"
708
772
  }
709
- }] : storageTextureKeys.map((key, index) => {
710
- const texDef = options.textureDefinitions[key];
711
- return {
712
- binding: index,
713
- visibility: GPUShaderStage.COMPUTE,
714
- storageTexture: {
715
- access: "write-only",
716
- format: texDef?.format ?? "rgba8unorm",
717
- viewDimension: "2d"
718
- }
719
- };
720
- });
773
+ }] : computeStorageTextureLayoutEntries;
721
774
  const storageTextureBGL = storageTextureBGLEntries.length > 0 ? device.createBindGroupLayout({ entries: storageTextureBGLEntries }) : null;
722
775
  const bindGroupLayouts = [computeUniformBGL];
723
776
  if (storageBGL || storageTextureBGL) bindGroupLayouts.push(storageBGL ?? device.createBindGroupLayout({ entries: [] }));
724
777
  if (storageTextureBGL) bindGroupLayouts.push(storageTextureBGL);
725
778
  const computePipelineLayout = device.createPipelineLayout({ bindGroupLayouts });
726
- const entry = {
727
- pipeline: device.createComputePipeline({
779
+ let pipeline;
780
+ try {
781
+ pipeline = device.createComputePipeline({
728
782
  layout: computePipelineLayout,
729
783
  compute: {
730
784
  module: computeShaderModule,
731
785
  entryPoint: "compute"
732
786
  }
733
- }),
734
- bindGroup: device.createBindGroup({
735
- layout: computeUniformBGL,
736
- entries: [{
737
- binding: FRAME_BINDING,
738
- resource: { buffer: frameBuffer }
739
- }, {
740
- binding: UNIFORM_BINDING,
741
- resource: { buffer: uniformBuffer }
742
- }]
743
- }),
787
+ });
788
+ } catch (error) {
789
+ throw toComputeCompilationError({
790
+ error,
791
+ lineMap: builtComputeShader.lineMap,
792
+ computeSource: buildOptions.computeSource,
793
+ runtimeContext
794
+ });
795
+ }
796
+ const computeUniformBindGroup = device.createBindGroup({
797
+ layout: computeUniformBGL,
798
+ entries: [{
799
+ binding: FRAME_BINDING,
800
+ resource: { buffer: frameBuffer }
801
+ }, {
802
+ binding: UNIFORM_BINDING,
803
+ resource: { buffer: uniformBuffer }
804
+ }]
805
+ });
806
+ const entry = {
807
+ pipeline,
808
+ bindGroup: computeUniformBindGroup,
744
809
  workgroupSize,
745
810
  computeSource: buildOptions.computeSource
746
811
  };
@@ -748,71 +813,71 @@ async function createRenderer(options) {
748
813
  return entry;
749
814
  };
750
815
  const getComputeStorageBindGroup = () => {
751
- if (storageBufferKeys.length === 0) return null;
752
- const storageBGLEntries = storageBufferKeys.map((key, index) => {
753
- const bufferType = (storageBufferDefinitions[key]?.access ?? "read-write") === "read" ? "read-only-storage" : "storage";
754
- return {
755
- binding: index,
756
- visibility: GPUShaderStage.COMPUTE,
757
- buffer: { type: bufferType }
758
- };
759
- });
760
- const storageBGL = device.createBindGroupLayout({ entries: storageBGLEntries });
761
- const storageEntries = storageBufferKeys.map((key, index) => {
816
+ if (computeStorageBufferLayoutEntries.length === 0) return null;
817
+ const resources = storageBufferKeys.map((key) => {
762
818
  const buffer = storageBufferMap.get(key);
763
819
  if (!buffer) throw new Error(`Storage buffer "${key}" not allocated.`);
820
+ return buffer;
821
+ });
822
+ const storageEntries = resources.map((buffer, index) => {
764
823
  return {
765
824
  binding: index,
766
825
  resource: { buffer }
767
826
  };
768
827
  });
769
- return device.createBindGroup({
770
- layout: storageBGL,
771
- entries: storageEntries
828
+ return computeStorageBufferBindGroupCache.getOrCreate({
829
+ topologyKey: computeStorageBufferTopologyKey,
830
+ layoutEntries: computeStorageBufferLayoutEntries,
831
+ bindGroupEntries: storageEntries,
832
+ resourceRefs: resources
772
833
  });
773
834
  };
774
835
  const getComputeStorageTextureBindGroup = () => {
775
- if (storageTextureKeys.length === 0) return null;
776
- const entries = storageTextureKeys.map((key, index) => {
777
- const texDef = options.textureDefinitions[key];
778
- return {
779
- binding: index,
780
- visibility: GPUShaderStage.COMPUTE,
781
- storageTexture: {
782
- access: "write-only",
783
- format: texDef?.format ?? "rgba8unorm",
784
- viewDimension: "2d"
785
- }
786
- };
787
- });
788
- const bgl = device.createBindGroupLayout({ entries });
789
- const bgEntries = storageTextureKeys.map((key, index) => {
790
- const binding = textureBindings.find((b) => b.key === key);
836
+ if (computeStorageTextureLayoutEntries.length === 0) return null;
837
+ const resources = storageTextureKeys.map((key) => {
838
+ const binding = textureBindingByKey.get(key);
791
839
  if (!binding || !binding.texture) throw new Error(`Storage texture "${key}" not allocated.`);
840
+ return binding.view;
841
+ });
842
+ const bgEntries = resources.map((view, index) => {
792
843
  return {
793
844
  binding: index,
794
- resource: binding.view
845
+ resource: view
795
846
  };
796
847
  });
797
- return device.createBindGroup({
798
- layout: bgl,
799
- entries: bgEntries
848
+ return computeStorageTextureBindGroupCache.getOrCreate({
849
+ topologyKey: computeStorageTextureTopologyKey,
850
+ layoutEntries: computeStorageTextureLayoutEntries,
851
+ bindGroupEntries: bgEntries,
852
+ resourceRefs: resources
800
853
  });
801
854
  };
802
855
  const getPingPongStorageTextureBindGroup = (target, readFromA) => {
803
856
  const pair = ensurePingPongTexturePair(target);
804
- const readView = readFromA ? pair.viewA : pair.viewB;
805
- const writeView = readFromA ? pair.viewB : pair.viewA;
806
- return device.createBindGroup({
857
+ if (readFromA) {
858
+ if (!pair.readAWriteBBindGroup) pair.readAWriteBBindGroup = device.createBindGroup({
859
+ layout: pair.bindGroupLayout,
860
+ entries: [{
861
+ binding: 0,
862
+ resource: pair.viewA
863
+ }, {
864
+ binding: 1,
865
+ resource: pair.viewB
866
+ }]
867
+ });
868
+ return pair.readAWriteBBindGroup;
869
+ }
870
+ if (!pair.readBWriteABindGroup) pair.readBWriteABindGroup = device.createBindGroup({
807
871
  layout: pair.bindGroupLayout,
808
872
  entries: [{
809
873
  binding: 0,
810
- resource: readView
874
+ resource: pair.viewB
811
875
  }, {
812
876
  binding: 1,
813
- resource: writeView
877
+ resource: pair.viewA
814
878
  }]
815
879
  });
880
+ return pair.readBWriteABindGroup;
816
881
  };
817
882
  const frameBuffer = device.createBuffer({
818
883
  size: 16,
@@ -843,7 +908,7 @@ async function createRenderer(options) {
843
908
  binding: UNIFORM_BINDING,
844
909
  resource: { buffer: uniformBuffer }
845
910
  }];
846
- for (const binding of textureBindings) {
911
+ for (const binding of fragmentTextureBindings) {
847
912
  entries.push({
848
913
  binding: binding.samplerBinding,
849
914
  resource: binding.sampler
@@ -1211,7 +1276,7 @@ async function createRenderer(options) {
1211
1276
  let bindGroupDirty = false;
1212
1277
  for (const binding of textureBindings) {
1213
1278
  if (storageTextureKeySet.has(binding.key)) continue;
1214
- if (updateTextureBinding(binding, textures[binding.key] ?? normalizedTextureDefinitions[binding.key]?.source ?? null, renderMode)) bindGroupDirty = true;
1279
+ if (updateTextureBinding(binding, textures[binding.key] ?? normalizedTextureDefinitions[binding.key]?.source ?? null, renderMode) && binding.fragmentVisible) bindGroupDirty = true;
1215
1280
  }
1216
1281
  if (bindGroupDirty) bindGroup = createBindGroup();
1217
1282
  if (pendingStorageWrites) for (const write of pendingStorageWrites) {