@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.
- package/dist/core/compute-bindgroup-cache.d.ts +13 -0
- package/dist/core/compute-bindgroup-cache.d.ts.map +1 -0
- package/dist/core/compute-bindgroup-cache.js +45 -0
- package/dist/core/compute-bindgroup-cache.js.map +1 -0
- package/dist/core/compute-shader.d.ts +48 -0
- package/dist/core/compute-shader.d.ts.map +1 -1
- package/dist/core/compute-shader.js +34 -1
- package/dist/core/compute-shader.js.map +1 -1
- package/dist/core/error-diagnostics.d.ts +8 -1
- package/dist/core/error-diagnostics.d.ts.map +1 -1
- package/dist/core/error-diagnostics.js +7 -3
- package/dist/core/error-diagnostics.js.map +1 -1
- package/dist/core/error-report.d.ts.map +1 -1
- package/dist/core/error-report.js +19 -1
- package/dist/core/error-report.js.map +1 -1
- package/dist/core/material.d.ts.map +1 -1
- package/dist/core/material.js +2 -1
- package/dist/core/material.js.map +1 -1
- package/dist/core/renderer.d.ts.map +1 -1
- package/dist/core/renderer.js +150 -85
- package/dist/core/renderer.js.map +1 -1
- package/dist/core/runtime-loop.d.ts.map +1 -1
- package/dist/core/runtime-loop.js +26 -14
- package/dist/core/runtime-loop.js.map +1 -1
- package/dist/core/shader.d.ts +7 -2
- package/dist/core/shader.d.ts.map +1 -1
- package/dist/core/shader.js +1 -0
- package/dist/core/shader.js.map +1 -1
- package/dist/core/textures.d.ts +4 -0
- package/dist/core/textures.d.ts.map +1 -1
- package/dist/core/textures.js +2 -1
- package/dist/core/textures.js.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/lib/core/compute-bindgroup-cache.ts +73 -0
- package/src/lib/core/compute-shader.ts +86 -0
- package/src/lib/core/error-diagnostics.ts +29 -4
- package/src/lib/core/error-report.ts +26 -1
- package/src/lib/core/material.ts +2 -1
- package/src/lib/core/renderer.ts +198 -92
- package/src/lib/core/runtime-loop.ts +37 -16
- package/src/lib/core/shader.ts +13 -2
- package/src/lib/core/textures.ts +6 -1
- 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":"
|
|
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"}
|
package/dist/core/renderer.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
}) :
|
|
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:
|
|
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
|
|
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
|
-
}] :
|
|
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
|
-
|
|
727
|
-
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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 (
|
|
752
|
-
const
|
|
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
|
|
770
|
-
|
|
771
|
-
|
|
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 (
|
|
776
|
-
const
|
|
777
|
-
const
|
|
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:
|
|
845
|
+
resource: view
|
|
795
846
|
};
|
|
796
847
|
});
|
|
797
|
-
return
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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:
|
|
874
|
+
resource: pair.viewB
|
|
811
875
|
}, {
|
|
812
876
|
binding: 1,
|
|
813
|
-
resource:
|
|
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
|
|
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) {
|