@motion-core/motion-gpu 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +14 -6
- package/dist/core/advanced.d.ts +1 -0
- package/dist/core/advanced.d.ts.map +1 -0
- package/dist/core/advanced.js +14 -5
- package/dist/core/compute-shader.d.ts +87 -0
- package/dist/core/compute-shader.d.ts.map +1 -0
- package/dist/core/compute-shader.js +205 -0
- package/dist/core/compute-shader.js.map +1 -0
- package/dist/core/current-value.d.ts +1 -0
- package/dist/core/current-value.d.ts.map +1 -0
- package/dist/core/current-value.js +35 -34
- package/dist/core/current-value.js.map +1 -0
- package/dist/core/error-diagnostics.d.ts +1 -0
- package/dist/core/error-diagnostics.d.ts.map +1 -0
- package/dist/core/error-diagnostics.js +70 -137
- package/dist/core/error-diagnostics.js.map +1 -0
- package/dist/core/error-report.d.ts +2 -1
- package/dist/core/error-report.d.ts.map +1 -0
- package/dist/core/error-report.js +247 -233
- package/dist/core/error-report.js.map +1 -0
- package/dist/core/frame-registry.d.ts +1 -0
- package/dist/core/frame-registry.d.ts.map +1 -0
- package/dist/core/frame-registry.js +546 -662
- package/dist/core/frame-registry.js.map +1 -0
- package/dist/core/index.d.ts +6 -2
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -12
- package/dist/core/material-preprocess.d.ts +1 -0
- package/dist/core/material-preprocess.d.ts.map +1 -0
- package/dist/core/material-preprocess.js +131 -152
- package/dist/core/material-preprocess.js.map +1 -0
- package/dist/core/material.d.ts +23 -6
- package/dist/core/material.d.ts.map +1 -0
- package/dist/core/material.js +290 -317
- package/dist/core/material.js.map +1 -0
- package/dist/core/recompile-policy.d.ts +1 -0
- package/dist/core/recompile-policy.d.ts.map +1 -0
- package/dist/core/recompile-policy.js +18 -13
- package/dist/core/recompile-policy.js.map +1 -0
- package/dist/core/render-graph.d.ts +8 -3
- package/dist/core/render-graph.d.ts.map +1 -0
- package/dist/core/render-graph.js +77 -68
- package/dist/core/render-graph.js.map +1 -0
- package/dist/core/render-targets.d.ts +1 -0
- package/dist/core/render-targets.d.ts.map +1 -0
- package/dist/core/render-targets.js +52 -53
- package/dist/core/render-targets.js.map +1 -0
- package/dist/core/renderer.d.ts +1 -0
- package/dist/core/renderer.d.ts.map +1 -0
- package/dist/core/renderer.js +1337 -1081
- package/dist/core/renderer.js.map +1 -0
- package/dist/core/runtime-loop.d.ts +3 -2
- package/dist/core/runtime-loop.d.ts.map +1 -0
- package/dist/core/runtime-loop.js +353 -362
- package/dist/core/runtime-loop.js.map +1 -0
- package/dist/core/scheduler-helpers.d.ts +1 -0
- package/dist/core/scheduler-helpers.d.ts.map +1 -0
- package/dist/core/scheduler-helpers.js +52 -51
- package/dist/core/scheduler-helpers.js.map +1 -0
- package/dist/core/shader.d.ts +10 -1
- package/dist/core/shader.d.ts.map +1 -0
- package/dist/core/shader.js +109 -115
- package/dist/core/shader.js.map +1 -0
- package/dist/core/storage-buffers.d.ts +37 -0
- package/dist/core/storage-buffers.d.ts.map +1 -0
- package/dist/core/storage-buffers.js +95 -0
- package/dist/core/storage-buffers.js.map +1 -0
- package/dist/core/texture-loader.d.ts +1 -0
- package/dist/core/texture-loader.d.ts.map +1 -0
- package/dist/core/texture-loader.js +209 -273
- package/dist/core/texture-loader.js.map +1 -0
- package/dist/core/textures.d.ts +13 -0
- package/dist/core/textures.d.ts.map +1 -0
- package/dist/core/textures.js +111 -116
- package/dist/core/textures.js.map +1 -0
- package/dist/core/types.d.ts +147 -4
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +0 -4
- package/dist/core/uniforms.d.ts +1 -0
- package/dist/core/uniforms.d.ts.map +1 -0
- package/dist/core/uniforms.js +170 -191
- package/dist/core/uniforms.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -6
- package/dist/passes/BlitPass.d.ts +1 -0
- package/dist/passes/BlitPass.d.ts.map +1 -0
- package/dist/passes/BlitPass.js +23 -18
- package/dist/passes/BlitPass.js.map +1 -0
- package/dist/passes/ComputePass.d.ts +83 -0
- package/dist/passes/ComputePass.d.ts.map +1 -0
- package/dist/passes/ComputePass.js +92 -0
- package/dist/passes/ComputePass.js.map +1 -0
- package/dist/passes/CopyPass.d.ts +1 -0
- package/dist/passes/CopyPass.d.ts.map +1 -0
- package/dist/passes/CopyPass.js +58 -52
- package/dist/passes/CopyPass.js.map +1 -0
- package/dist/passes/FullscreenPass.d.ts +1 -0
- package/dist/passes/FullscreenPass.d.ts.map +1 -0
- package/dist/passes/FullscreenPass.js +127 -130
- package/dist/passes/FullscreenPass.js.map +1 -0
- package/dist/passes/PingPongComputePass.d.ts +104 -0
- package/dist/passes/PingPongComputePass.d.ts.map +1 -0
- package/dist/passes/PingPongComputePass.js +132 -0
- package/dist/passes/PingPongComputePass.js.map +1 -0
- package/dist/passes/ShaderPass.d.ts +1 -0
- package/dist/passes/ShaderPass.d.ts.map +1 -0
- package/dist/passes/ShaderPass.js +41 -37
- package/dist/passes/ShaderPass.js.map +1 -0
- package/dist/passes/index.d.ts +3 -0
- package/dist/passes/index.d.ts.map +1 -0
- package/dist/passes/index.js +6 -3
- package/dist/react/FragCanvas.d.ts +3 -2
- package/dist/react/FragCanvas.d.ts.map +1 -0
- package/dist/react/FragCanvas.js +234 -211
- package/dist/react/FragCanvas.js.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.js +200 -14
- package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
- package/dist/react/Portal.d.ts +1 -0
- package/dist/react/Portal.d.ts.map +1 -0
- package/dist/react/Portal.js +18 -21
- package/dist/react/Portal.js.map +1 -0
- package/dist/react/advanced.d.ts +1 -0
- package/dist/react/advanced.d.ts.map +1 -0
- package/dist/react/advanced.js +14 -6
- package/dist/react/frame-context.d.ts +1 -0
- package/dist/react/frame-context.d.ts.map +1 -0
- package/dist/react/frame-context.js +88 -94
- package/dist/react/frame-context.js.map +1 -0
- package/dist/react/index.d.ts +6 -2
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +12 -9
- package/dist/react/motiongpu-context.d.ts +1 -0
- package/dist/react/motiongpu-context.d.ts.map +1 -0
- package/dist/react/motiongpu-context.js +18 -15
- package/dist/react/motiongpu-context.js.map +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/react/use-motiongpu-user-context.js +83 -82
- package/dist/react/use-motiongpu-user-context.js.map +1 -0
- package/dist/react/use-texture.d.ts +1 -0
- package/dist/react/use-texture.d.ts.map +1 -0
- package/dist/react/use-texture.js +132 -152
- package/dist/react/use-texture.js.map +1 -0
- package/dist/svelte/FragCanvas.svelte +2 -2
- package/dist/svelte/FragCanvas.svelte.d.ts +3 -2
- package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
- package/dist/svelte/Portal.svelte.d.ts +1 -0
- package/dist/svelte/Portal.svelte.d.ts.map +1 -0
- package/dist/svelte/advanced.d.ts +1 -0
- package/dist/svelte/advanced.d.ts.map +1 -0
- package/dist/svelte/advanced.js +13 -6
- package/dist/svelte/frame-context.d.ts +1 -0
- package/dist/svelte/frame-context.d.ts.map +1 -0
- package/dist/svelte/frame-context.js +27 -27
- package/dist/svelte/frame-context.js.map +1 -0
- package/dist/svelte/index.d.ts +6 -2
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +12 -9
- package/dist/svelte/motiongpu-context.d.ts +1 -0
- package/dist/svelte/motiongpu-context.d.ts.map +1 -0
- package/dist/svelte/motiongpu-context.js +24 -21
- package/dist/svelte/motiongpu-context.js.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.js +69 -70
- package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
- package/dist/svelte/use-texture.d.ts +1 -0
- package/dist/svelte/use-texture.d.ts.map +1 -0
- package/dist/svelte/use-texture.js +125 -147
- package/dist/svelte/use-texture.js.map +1 -0
- package/package.json +12 -7
- package/src/lib/advanced.ts +6 -0
- package/src/lib/core/advanced.ts +12 -0
- package/src/lib/core/compute-shader.ts +326 -0
- package/src/lib/core/current-value.ts +64 -0
- package/src/lib/core/error-diagnostics.ts +236 -0
- package/src/lib/core/error-report.ts +535 -0
- package/src/lib/core/frame-registry.ts +1190 -0
- package/src/lib/core/index.ts +94 -0
- package/src/lib/core/material-preprocess.ts +295 -0
- package/src/lib/core/material.ts +748 -0
- package/src/lib/core/recompile-policy.ts +31 -0
- package/src/lib/core/render-graph.ts +173 -0
- package/src/lib/core/render-targets.ts +107 -0
- package/src/lib/core/renderer.ts +2161 -0
- package/src/lib/core/runtime-loop.ts +537 -0
- package/src/lib/core/scheduler-helpers.ts +136 -0
- package/src/lib/core/shader.ts +301 -0
- package/src/lib/core/storage-buffers.ts +142 -0
- package/src/lib/core/texture-loader.ts +482 -0
- package/src/lib/core/textures.ts +257 -0
- package/src/lib/core/types.ts +743 -0
- package/src/lib/core/uniforms.ts +282 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/passes/BlitPass.ts +54 -0
- package/src/lib/passes/ComputePass.ts +136 -0
- package/src/lib/passes/CopyPass.ts +80 -0
- package/src/lib/passes/FullscreenPass.ts +173 -0
- package/src/lib/passes/PingPongComputePass.ts +180 -0
- package/src/lib/passes/ShaderPass.ts +89 -0
- package/src/lib/passes/index.ts +9 -0
- package/src/lib/react/FragCanvas.tsx +345 -0
- package/src/lib/react/MotionGPUErrorOverlay.tsx +524 -0
- package/src/lib/react/Portal.tsx +34 -0
- package/src/lib/react/advanced.ts +36 -0
- package/src/lib/react/frame-context.ts +169 -0
- package/src/lib/react/index.ts +68 -0
- package/src/lib/react/motiongpu-context.ts +88 -0
- package/src/lib/react/use-motiongpu-user-context.ts +186 -0
- package/src/lib/react/use-texture.ts +233 -0
- package/src/lib/svelte/FragCanvas.svelte +249 -0
- package/src/lib/svelte/MotionGPUErrorOverlay.svelte +512 -0
- package/src/lib/svelte/Portal.svelte +31 -0
- package/src/lib/svelte/advanced.ts +32 -0
- package/src/lib/svelte/frame-context.ts +87 -0
- package/src/lib/svelte/index.ts +68 -0
- package/src/lib/svelte/motiongpu-context.ts +97 -0
- package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
- package/src/lib/svelte/use-texture.ts +232 -0
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
import { normalizeTextureDefinition } from './textures.js';
|
|
2
|
+
import type { MaterialSourceMetadata } from './error-diagnostics.js';
|
|
3
|
+
import {
|
|
4
|
+
assertUniformName,
|
|
5
|
+
assertUniformValueForType,
|
|
6
|
+
inferUniformType,
|
|
7
|
+
resolveUniformLayout
|
|
8
|
+
} from './uniforms.js';
|
|
9
|
+
import {
|
|
10
|
+
normalizeDefines,
|
|
11
|
+
normalizeIncludes,
|
|
12
|
+
preprocessMaterialFragment,
|
|
13
|
+
toDefineLine,
|
|
14
|
+
type MaterialLineMap,
|
|
15
|
+
type PreprocessedMaterialFragment
|
|
16
|
+
} from './material-preprocess.js';
|
|
17
|
+
import { assertStorageBufferDefinition, assertStorageTextureFormat } from './storage-buffers.js';
|
|
18
|
+
import type {
|
|
19
|
+
StorageBufferDefinition,
|
|
20
|
+
StorageBufferDefinitionMap,
|
|
21
|
+
TextureData,
|
|
22
|
+
TextureDefinition,
|
|
23
|
+
TextureDefinitionMap,
|
|
24
|
+
TextureValue,
|
|
25
|
+
TypedUniform,
|
|
26
|
+
UniformMap,
|
|
27
|
+
UniformValue
|
|
28
|
+
} from './types.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Typed compile-time define declaration.
|
|
32
|
+
*/
|
|
33
|
+
export type TypedMaterialDefineValue =
|
|
34
|
+
| {
|
|
35
|
+
/**
|
|
36
|
+
* WGSL scalar type.
|
|
37
|
+
*/
|
|
38
|
+
type: 'bool';
|
|
39
|
+
/**
|
|
40
|
+
* Literal value for the selected WGSL type.
|
|
41
|
+
*/
|
|
42
|
+
value: boolean;
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
/**
|
|
46
|
+
* WGSL scalar type.
|
|
47
|
+
*/
|
|
48
|
+
type: 'f32' | 'i32' | 'u32';
|
|
49
|
+
/**
|
|
50
|
+
* Literal value for the selected WGSL type.
|
|
51
|
+
*/
|
|
52
|
+
value: number;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Allowed value types for WGSL `const` define injection.
|
|
57
|
+
*/
|
|
58
|
+
export type MaterialDefineValue = boolean | number | TypedMaterialDefineValue;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Define map keyed by uniform-compatible identifier names.
|
|
62
|
+
*/
|
|
63
|
+
export type MaterialDefines<TKey extends string = string> = Record<TKey, MaterialDefineValue>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Include map keyed by include identifier used in `#include <name>` directives.
|
|
67
|
+
*/
|
|
68
|
+
export type MaterialIncludes<TKey extends string = string> = Record<TKey, string>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* External material input accepted by {@link defineMaterial}.
|
|
72
|
+
*/
|
|
73
|
+
export interface FragMaterialInput<
|
|
74
|
+
TUniformKey extends string = string,
|
|
75
|
+
TTextureKey extends string = string,
|
|
76
|
+
TDefineKey extends string = string,
|
|
77
|
+
TIncludeKey extends string = string,
|
|
78
|
+
TStorageBufferKey extends string = string
|
|
79
|
+
> {
|
|
80
|
+
/**
|
|
81
|
+
* User WGSL source containing `frag(uv: vec2f) -> vec4f`.
|
|
82
|
+
*/
|
|
83
|
+
fragment: string;
|
|
84
|
+
/**
|
|
85
|
+
* Initial uniform values.
|
|
86
|
+
*/
|
|
87
|
+
uniforms?: UniformMap<TUniformKey>;
|
|
88
|
+
/**
|
|
89
|
+
* Texture definitions keyed by texture uniform name.
|
|
90
|
+
*/
|
|
91
|
+
textures?: TextureDefinitionMap<TTextureKey>;
|
|
92
|
+
/**
|
|
93
|
+
* Optional compile-time define constants injected into WGSL.
|
|
94
|
+
*/
|
|
95
|
+
defines?: MaterialDefines<TDefineKey>;
|
|
96
|
+
/**
|
|
97
|
+
* Optional WGSL include chunks used by `#include <name>` directives.
|
|
98
|
+
*/
|
|
99
|
+
includes?: MaterialIncludes<TIncludeKey>;
|
|
100
|
+
/**
|
|
101
|
+
* Optional storage buffer definitions for compute shaders.
|
|
102
|
+
*/
|
|
103
|
+
storageBuffers?: StorageBufferDefinitionMap<TStorageBufferKey>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Normalized and immutable material declaration consumed by `FragCanvas`.
|
|
108
|
+
*/
|
|
109
|
+
export interface FragMaterial<
|
|
110
|
+
TUniformKey extends string = string,
|
|
111
|
+
TTextureKey extends string = string,
|
|
112
|
+
TDefineKey extends string = string,
|
|
113
|
+
TIncludeKey extends string = string,
|
|
114
|
+
TStorageBufferKey extends string = string
|
|
115
|
+
> {
|
|
116
|
+
/**
|
|
117
|
+
* User WGSL source containing `frag(uv: vec2f) -> vec4f`.
|
|
118
|
+
*/
|
|
119
|
+
readonly fragment: string;
|
|
120
|
+
/**
|
|
121
|
+
* Initial uniform values.
|
|
122
|
+
*/
|
|
123
|
+
readonly uniforms: Readonly<UniformMap<TUniformKey>>;
|
|
124
|
+
/**
|
|
125
|
+
* Texture definitions keyed by texture uniform name.
|
|
126
|
+
*/
|
|
127
|
+
readonly textures: Readonly<TextureDefinitionMap<TTextureKey>>;
|
|
128
|
+
/**
|
|
129
|
+
* Optional compile-time define constants injected into WGSL.
|
|
130
|
+
*/
|
|
131
|
+
readonly defines: Readonly<MaterialDefines<TDefineKey>>;
|
|
132
|
+
/**
|
|
133
|
+
* Optional WGSL include chunks used by `#include <name>` directives.
|
|
134
|
+
*/
|
|
135
|
+
readonly includes: Readonly<MaterialIncludes<TIncludeKey>>;
|
|
136
|
+
/**
|
|
137
|
+
* Storage buffer definitions for compute shaders. Empty when not provided.
|
|
138
|
+
*/
|
|
139
|
+
readonly storageBuffers: Readonly<StorageBufferDefinitionMap<TStorageBufferKey>>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Fully resolved, immutable material snapshot used for renderer creation/caching.
|
|
144
|
+
*/
|
|
145
|
+
export interface ResolvedMaterial<
|
|
146
|
+
TUniformKey extends string = string,
|
|
147
|
+
TTextureKey extends string = string,
|
|
148
|
+
TIncludeKey extends string = string,
|
|
149
|
+
TStorageBufferKey extends string = string
|
|
150
|
+
> {
|
|
151
|
+
/**
|
|
152
|
+
* Final fragment WGSL after define injection.
|
|
153
|
+
*/
|
|
154
|
+
fragmentWgsl: string;
|
|
155
|
+
/**
|
|
156
|
+
* 1-based map from generated fragment lines to user source lines.
|
|
157
|
+
*/
|
|
158
|
+
fragmentLineMap: MaterialLineMap;
|
|
159
|
+
/**
|
|
160
|
+
* Cloned uniforms.
|
|
161
|
+
*/
|
|
162
|
+
uniforms: UniformMap<TUniformKey>;
|
|
163
|
+
/**
|
|
164
|
+
* Cloned texture definitions.
|
|
165
|
+
*/
|
|
166
|
+
textures: TextureDefinitionMap<TTextureKey>;
|
|
167
|
+
/**
|
|
168
|
+
* Resolved packed uniform layout.
|
|
169
|
+
*/
|
|
170
|
+
uniformLayout: ReturnType<typeof resolveUniformLayout>;
|
|
171
|
+
/**
|
|
172
|
+
* Sorted texture keys.
|
|
173
|
+
*/
|
|
174
|
+
textureKeys: TTextureKey[];
|
|
175
|
+
/**
|
|
176
|
+
* Deterministic JSON signature for cache invalidation.
|
|
177
|
+
*/
|
|
178
|
+
signature: string;
|
|
179
|
+
/**
|
|
180
|
+
* Original user fragment source before preprocessing.
|
|
181
|
+
*/
|
|
182
|
+
fragmentSource: string;
|
|
183
|
+
/**
|
|
184
|
+
* Normalized include sources map.
|
|
185
|
+
*/
|
|
186
|
+
includeSources: MaterialIncludes<TIncludeKey>;
|
|
187
|
+
/**
|
|
188
|
+
* Deterministic define block source used for diagnostics mapping.
|
|
189
|
+
*/
|
|
190
|
+
defineBlockSource: string;
|
|
191
|
+
/**
|
|
192
|
+
* Source metadata used for diagnostics.
|
|
193
|
+
*/
|
|
194
|
+
source: Readonly<MaterialSourceMetadata> | null;
|
|
195
|
+
/**
|
|
196
|
+
* Sorted storage buffer keys. Empty array when no storage buffers declared.
|
|
197
|
+
*/
|
|
198
|
+
storageBufferKeys: TStorageBufferKey[];
|
|
199
|
+
/**
|
|
200
|
+
* Sorted storage texture keys (textures with storage: true).
|
|
201
|
+
*/
|
|
202
|
+
storageTextureKeys: TTextureKey[];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Strict fragment contract used by MotionGPU.
|
|
207
|
+
*/
|
|
208
|
+
const FRAGMENT_FUNCTION_SIGNATURE_PATTERN =
|
|
209
|
+
/\bfn\s+frag\s*\(\s*([^)]*?)\s*\)\s*->\s*([A-Za-z_][A-Za-z0-9_<>\s]*)\s*(?:\{|$)/m;
|
|
210
|
+
const FRAGMENT_FUNCTION_NAME_PATTERN = /\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Cache of resolved material snapshots keyed by immutable material instance.
|
|
214
|
+
*/
|
|
215
|
+
type AnyFragMaterial = FragMaterial<string, string, string, string, string>;
|
|
216
|
+
type AnyResolvedMaterial = ResolvedMaterial<string, string, string, string>;
|
|
217
|
+
|
|
218
|
+
const resolvedMaterialCache = new WeakMap<AnyFragMaterial, AnyResolvedMaterial>();
|
|
219
|
+
const preprocessedFragmentCache = new WeakMap<AnyFragMaterial, PreprocessedMaterialFragment>();
|
|
220
|
+
const materialSourceMetadataCache = new WeakMap<AnyFragMaterial, MaterialSourceMetadata | null>();
|
|
221
|
+
|
|
222
|
+
function getCachedResolvedMaterial<
|
|
223
|
+
TUniformKey extends string,
|
|
224
|
+
TTextureKey extends string,
|
|
225
|
+
TIncludeKey extends string,
|
|
226
|
+
TStorageBufferKey extends string
|
|
227
|
+
>(
|
|
228
|
+
material: FragMaterial<TUniformKey, TTextureKey, string, TIncludeKey, TStorageBufferKey>
|
|
229
|
+
): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> | null {
|
|
230
|
+
const cached = resolvedMaterialCache.get(material);
|
|
231
|
+
if (!cached) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Invariant: the cache key is the same material object used to produce this resolved payload.
|
|
236
|
+
return cached as ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const STACK_TRACE_CHROME_PATTERN = /^\s*at\s+(?:(.*?)\s+\()?(.+?):(\d+):(\d+)\)?$/;
|
|
240
|
+
const STACK_TRACE_FIREFOX_PATTERN = /^(.*?)@(.+?):(\d+):(\d+)$/;
|
|
241
|
+
|
|
242
|
+
function getPathBasename(path: string): string {
|
|
243
|
+
const normalized = path.split(/[?#]/)[0] ?? path;
|
|
244
|
+
const parts = normalized.split(/[\\/]/);
|
|
245
|
+
const last = parts[parts.length - 1];
|
|
246
|
+
return last && last.length > 0 ? last : path;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function normalizeSignaturePart(value: string): string {
|
|
250
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function listFunctionNames(fragment: string): string[] {
|
|
254
|
+
const names = new Set<string>();
|
|
255
|
+
for (const match of fragment.matchAll(FRAGMENT_FUNCTION_NAME_PATTERN)) {
|
|
256
|
+
const name = match[1];
|
|
257
|
+
if (!name) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
names.add(name);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return Array.from(names);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function captureMaterialSourceFromStack(): MaterialSourceMetadata | null {
|
|
267
|
+
const stack = new Error().stack;
|
|
268
|
+
if (!stack) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const stackLines = stack.split('\n').slice(1);
|
|
273
|
+
for (const rawLine of stackLines) {
|
|
274
|
+
const line = rawLine.trim();
|
|
275
|
+
if (line.length === 0) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const chromeMatch = line.match(STACK_TRACE_CHROME_PATTERN);
|
|
280
|
+
const firefoxMatch = line.match(STACK_TRACE_FIREFOX_PATTERN);
|
|
281
|
+
const functionName = chromeMatch?.[1] ?? firefoxMatch?.[1] ?? undefined;
|
|
282
|
+
const file = chromeMatch?.[2] ?? firefoxMatch?.[2];
|
|
283
|
+
const lineValue = chromeMatch?.[3] ?? firefoxMatch?.[3];
|
|
284
|
+
const columnValue = chromeMatch?.[4] ?? firefoxMatch?.[4];
|
|
285
|
+
|
|
286
|
+
if (!file || !lineValue || !columnValue) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (file.includes('/core/material') || file.includes('\\core\\material')) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const parsedLine = Number.parseInt(lineValue, 10);
|
|
295
|
+
const parsedColumn = Number.parseInt(columnValue, 10);
|
|
296
|
+
if (!Number.isFinite(parsedLine) || !Number.isFinite(parsedColumn)) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
component: getPathBasename(file),
|
|
302
|
+
file,
|
|
303
|
+
line: parsedLine,
|
|
304
|
+
column: parsedColumn,
|
|
305
|
+
...(functionName ? { functionName } : {})
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function resolveSourceMetadata(
|
|
313
|
+
source: MaterialSourceMetadata | undefined
|
|
314
|
+
): MaterialSourceMetadata | null {
|
|
315
|
+
const captured = captureMaterialSourceFromStack();
|
|
316
|
+
const component = source?.component ?? captured?.component;
|
|
317
|
+
const file = source?.file ?? captured?.file;
|
|
318
|
+
const line = source?.line ?? captured?.line;
|
|
319
|
+
const column = source?.column ?? captured?.column;
|
|
320
|
+
const functionName = source?.functionName ?? captured?.functionName;
|
|
321
|
+
|
|
322
|
+
if (
|
|
323
|
+
component === undefined &&
|
|
324
|
+
file === undefined &&
|
|
325
|
+
line === undefined &&
|
|
326
|
+
column === undefined &&
|
|
327
|
+
functionName === undefined
|
|
328
|
+
) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
...(component !== undefined ? { component } : {}),
|
|
334
|
+
...(file !== undefined ? { file } : {}),
|
|
335
|
+
...(line !== undefined ? { line } : {}),
|
|
336
|
+
...(column !== undefined ? { column } : {}),
|
|
337
|
+
...(functionName !== undefined ? { functionName } : {})
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Asserts that material has been normalized by {@link defineMaterial}.
|
|
343
|
+
*/
|
|
344
|
+
function assertDefinedMaterial(material: AnyFragMaterial): void {
|
|
345
|
+
if (
|
|
346
|
+
!Object.isFrozen(material) ||
|
|
347
|
+
!material.uniforms ||
|
|
348
|
+
!material.textures ||
|
|
349
|
+
!material.defines ||
|
|
350
|
+
!material.includes
|
|
351
|
+
) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
'Invalid material instance. Create materials with defineMaterial(...) before passing them to <FragCanvas>.'
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Clones uniform value input to decouple material instances from external objects.
|
|
360
|
+
*/
|
|
361
|
+
function cloneUniformValue(value: UniformValue): UniformValue {
|
|
362
|
+
if (typeof value === 'number') {
|
|
363
|
+
return value;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (Array.isArray(value)) {
|
|
367
|
+
return Object.freeze([...value]) as UniformValue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (typeof value === 'object' && value !== null && 'type' in value && 'value' in value) {
|
|
371
|
+
const typed = value as TypedUniform;
|
|
372
|
+
const typedValue = typed.value as unknown;
|
|
373
|
+
|
|
374
|
+
let clonedTypedValue = typedValue;
|
|
375
|
+
if (typedValue instanceof Float32Array) {
|
|
376
|
+
clonedTypedValue = new Float32Array(typedValue);
|
|
377
|
+
} else if (Array.isArray(typedValue)) {
|
|
378
|
+
clonedTypedValue = Object.freeze([...typedValue]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return Object.freeze({
|
|
382
|
+
type: typed.type,
|
|
383
|
+
value: clonedTypedValue
|
|
384
|
+
}) as UniformValue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return value;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Clones optional texture value payload.
|
|
392
|
+
*/
|
|
393
|
+
function cloneTextureValue(value: TextureValue | undefined): TextureValue {
|
|
394
|
+
if (value === undefined || value === null) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (typeof value === 'object' && 'source' in value) {
|
|
399
|
+
const data = value as TextureData;
|
|
400
|
+
return {
|
|
401
|
+
source: data.source,
|
|
402
|
+
...(data.width !== undefined ? { width: data.width } : {}),
|
|
403
|
+
...(data.height !== undefined ? { height: data.height } : {}),
|
|
404
|
+
...(data.colorSpace !== undefined ? { colorSpace: data.colorSpace } : {}),
|
|
405
|
+
...(data.flipY !== undefined ? { flipY: data.flipY } : {}),
|
|
406
|
+
...(data.premultipliedAlpha !== undefined
|
|
407
|
+
? { premultipliedAlpha: data.premultipliedAlpha }
|
|
408
|
+
: {}),
|
|
409
|
+
...(data.generateMipmaps !== undefined ? { generateMipmaps: data.generateMipmaps } : {}),
|
|
410
|
+
...(data.update !== undefined ? { update: data.update } : {})
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return value;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Clones and validates fragment source contract.
|
|
419
|
+
*/
|
|
420
|
+
function resolveFragment(fragment: string): string {
|
|
421
|
+
if (typeof fragment !== 'string' || fragment.trim().length === 0) {
|
|
422
|
+
throw new Error('Material fragment shader must be a non-empty WGSL string.');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const signature = fragment.match(FRAGMENT_FUNCTION_SIGNATURE_PATTERN);
|
|
426
|
+
if (!signature) {
|
|
427
|
+
const discoveredFunctions = listFunctionNames(fragment).slice(0, 4);
|
|
428
|
+
const discoveredLabel =
|
|
429
|
+
discoveredFunctions.length > 0
|
|
430
|
+
? `Found: ${discoveredFunctions.map((name) => `\`${name}(...)\``).join(', ')}.`
|
|
431
|
+
: 'No WGSL function declarations were found.';
|
|
432
|
+
|
|
433
|
+
throw new Error(
|
|
434
|
+
`Material fragment contract mismatch: missing entrypoint \`fn frag(uv: vec2f) -> vec4f\`. ${discoveredLabel}`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const params = normalizeSignaturePart(signature[1] ?? '');
|
|
439
|
+
const returnType = normalizeSignaturePart(signature[2] ?? '');
|
|
440
|
+
|
|
441
|
+
if (params !== 'uv: vec2f') {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`Material fragment contract mismatch for \`frag\`: expected parameter list \`(uv: vec2f)\`, received \`(${params || '...'})\`.`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (returnType !== 'vec4f') {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`Material fragment contract mismatch for \`frag\`: expected return type \`vec4f\`, received \`${returnType}\`.`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return fragment;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Clones and validates uniform declarations.
|
|
458
|
+
*/
|
|
459
|
+
function resolveUniforms<TUniformKey extends string>(
|
|
460
|
+
uniforms: UniformMap<TUniformKey> | undefined
|
|
461
|
+
): UniformMap<TUniformKey> {
|
|
462
|
+
const resolved: UniformMap<TUniformKey> = {} as UniformMap<TUniformKey>;
|
|
463
|
+
|
|
464
|
+
for (const [name, value] of Object.entries(uniforms ?? {}) as Array<
|
|
465
|
+
[TUniformKey, UniformValue]
|
|
466
|
+
>) {
|
|
467
|
+
assertUniformName(name);
|
|
468
|
+
const clonedValue = cloneUniformValue(value);
|
|
469
|
+
const type = inferUniformType(clonedValue);
|
|
470
|
+
assertUniformValueForType(type, clonedValue);
|
|
471
|
+
resolved[name] = clonedValue;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
resolveUniformLayout(resolved);
|
|
475
|
+
return resolved;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Clones and validates texture declarations.
|
|
480
|
+
*/
|
|
481
|
+
function resolveTextures<TTextureKey extends string>(
|
|
482
|
+
textures: TextureDefinitionMap<TTextureKey> | undefined
|
|
483
|
+
): TextureDefinitionMap<TTextureKey> {
|
|
484
|
+
const resolved: TextureDefinitionMap<TTextureKey> = {} as TextureDefinitionMap<TTextureKey>;
|
|
485
|
+
|
|
486
|
+
for (const [name, definition] of Object.entries(textures ?? {}) as Array<
|
|
487
|
+
[TTextureKey, TextureDefinition]
|
|
488
|
+
>) {
|
|
489
|
+
assertUniformName(name);
|
|
490
|
+
const source = definition?.source;
|
|
491
|
+
const normalizedSource = cloneTextureValue(source);
|
|
492
|
+
|
|
493
|
+
const clonedDefinition: TextureDefinition = {
|
|
494
|
+
...(definition ?? {}),
|
|
495
|
+
...(source !== undefined ? { source: normalizedSource } : {})
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
resolved[name] = Object.freeze(clonedDefinition);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return resolved;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Clones and validates define declarations.
|
|
506
|
+
*/
|
|
507
|
+
function resolveDefines<TDefineKey extends string>(
|
|
508
|
+
defines: MaterialDefines<TDefineKey> | undefined
|
|
509
|
+
): MaterialDefines<TDefineKey> {
|
|
510
|
+
return normalizeDefines(defines);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Clones and validates include declarations.
|
|
515
|
+
*/
|
|
516
|
+
function resolveIncludes<TIncludeKey extends string>(
|
|
517
|
+
includes: MaterialIncludes<TIncludeKey> | undefined
|
|
518
|
+
): MaterialIncludes<TIncludeKey> {
|
|
519
|
+
return normalizeIncludes(includes);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Builds a deterministic texture-config signature map used in material cache signatures.
|
|
524
|
+
*
|
|
525
|
+
* @param textures - Raw texture definitions from material input.
|
|
526
|
+
* @param textureKeys - Sorted texture keys.
|
|
527
|
+
* @returns Compact signature entries describing effective texture config per key.
|
|
528
|
+
*/
|
|
529
|
+
function buildTextureConfigSignature<TTextureKey extends string>(
|
|
530
|
+
textures: TextureDefinitionMap<TTextureKey>,
|
|
531
|
+
textureKeys: TTextureKey[]
|
|
532
|
+
): Record<TTextureKey, string> {
|
|
533
|
+
const signature = {} as Record<TTextureKey, string>;
|
|
534
|
+
|
|
535
|
+
for (const key of textureKeys) {
|
|
536
|
+
const normalized = normalizeTextureDefinition(textures[key]);
|
|
537
|
+
signature[key] = [
|
|
538
|
+
normalized.colorSpace,
|
|
539
|
+
normalized.flipY ? '1' : '0',
|
|
540
|
+
normalized.generateMipmaps ? '1' : '0',
|
|
541
|
+
normalized.premultipliedAlpha ? '1' : '0',
|
|
542
|
+
normalized.anisotropy,
|
|
543
|
+
normalized.filter,
|
|
544
|
+
normalized.addressModeU,
|
|
545
|
+
normalized.addressModeV
|
|
546
|
+
].join(':');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return signature;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Creates a stable WGSL define block from the provided map.
|
|
554
|
+
*
|
|
555
|
+
* @param defines - Optional material defines.
|
|
556
|
+
* @returns Joined WGSL const declarations ordered by key.
|
|
557
|
+
*/
|
|
558
|
+
export function buildDefinesBlock(defines: MaterialDefines | undefined): string {
|
|
559
|
+
const normalizedDefines = normalizeDefines(defines);
|
|
560
|
+
if (Object.keys(normalizedDefines).length === 0) {
|
|
561
|
+
return '';
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return Object.entries(normalizedDefines)
|
|
565
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
566
|
+
.map(([key, value]) => {
|
|
567
|
+
assertUniformName(key);
|
|
568
|
+
return toDefineLine(key, value);
|
|
569
|
+
})
|
|
570
|
+
.join('\n');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Prepends resolved defines to a fragment shader.
|
|
575
|
+
*
|
|
576
|
+
* @param fragment - Raw WGSL fragment source.
|
|
577
|
+
* @param defines - Optional define map.
|
|
578
|
+
* @returns Fragment source with a leading define block when defines are present.
|
|
579
|
+
*/
|
|
580
|
+
export function applyMaterialDefines(
|
|
581
|
+
fragment: string,
|
|
582
|
+
defines: MaterialDefines | undefined
|
|
583
|
+
): string {
|
|
584
|
+
const defineBlock = buildDefinesBlock(defines);
|
|
585
|
+
if (defineBlock.length === 0) {
|
|
586
|
+
return fragment;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return `${defineBlock}\n\n${fragment}`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Creates an immutable material object with validated shader/uniform/texture contracts.
|
|
594
|
+
*
|
|
595
|
+
* @param input - User material declaration.
|
|
596
|
+
* @returns Frozen material object safe to share and cache.
|
|
597
|
+
*/
|
|
598
|
+
export function defineMaterial<
|
|
599
|
+
TUniformKey extends string = string,
|
|
600
|
+
TTextureKey extends string = string,
|
|
601
|
+
TDefineKey extends string = string,
|
|
602
|
+
TIncludeKey extends string = string,
|
|
603
|
+
TStorageBufferKey extends string = string
|
|
604
|
+
>(
|
|
605
|
+
input: FragMaterialInput<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey>
|
|
606
|
+
): FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey> {
|
|
607
|
+
const fragment = resolveFragment(input.fragment);
|
|
608
|
+
const uniforms = Object.freeze(resolveUniforms(input.uniforms));
|
|
609
|
+
const textures = Object.freeze(resolveTextures(input.textures));
|
|
610
|
+
const defines = Object.freeze(resolveDefines(input.defines));
|
|
611
|
+
const includes = Object.freeze(resolveIncludes(input.includes));
|
|
612
|
+
const source = Object.freeze(resolveSourceMetadata(undefined));
|
|
613
|
+
|
|
614
|
+
// Validate and freeze storage buffers
|
|
615
|
+
const rawStorageBuffers =
|
|
616
|
+
input.storageBuffers ?? ({} as StorageBufferDefinitionMap<TStorageBufferKey>);
|
|
617
|
+
for (const [name, definition] of Object.entries(rawStorageBuffers) as Array<
|
|
618
|
+
[string, StorageBufferDefinition]
|
|
619
|
+
>) {
|
|
620
|
+
assertStorageBufferDefinition(name, definition);
|
|
621
|
+
}
|
|
622
|
+
const storageBuffers = Object.freeze(
|
|
623
|
+
Object.fromEntries(
|
|
624
|
+
Object.entries(rawStorageBuffers).map(([name, definition]) => {
|
|
625
|
+
const def = definition as StorageBufferDefinition;
|
|
626
|
+
const cloned: StorageBufferDefinition = {
|
|
627
|
+
size: def.size,
|
|
628
|
+
type: def.type,
|
|
629
|
+
...(def.access !== undefined ? { access: def.access } : {}),
|
|
630
|
+
...(def.initialData !== undefined
|
|
631
|
+
? { initialData: def.initialData.slice() as typeof def.initialData }
|
|
632
|
+
: {})
|
|
633
|
+
};
|
|
634
|
+
return [name, Object.freeze(cloned)];
|
|
635
|
+
})
|
|
636
|
+
)
|
|
637
|
+
) as Readonly<StorageBufferDefinitionMap<TStorageBufferKey>>;
|
|
638
|
+
|
|
639
|
+
// Validate storage textures
|
|
640
|
+
for (const [name, definition] of Object.entries(textures) as Array<[string, TextureDefinition]>) {
|
|
641
|
+
if (definition?.storage) {
|
|
642
|
+
if (!definition.format) {
|
|
643
|
+
throw new Error(`Texture "${name}" with storage:true requires a \`format\` field.`);
|
|
644
|
+
}
|
|
645
|
+
assertStorageTextureFormat(name, definition.format);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const preprocessed = preprocessMaterialFragment({
|
|
650
|
+
fragment,
|
|
651
|
+
defines,
|
|
652
|
+
includes
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const material: FragMaterial<
|
|
656
|
+
TUniformKey,
|
|
657
|
+
TTextureKey,
|
|
658
|
+
TDefineKey,
|
|
659
|
+
TIncludeKey,
|
|
660
|
+
TStorageBufferKey
|
|
661
|
+
> = Object.freeze({
|
|
662
|
+
fragment,
|
|
663
|
+
uniforms,
|
|
664
|
+
textures,
|
|
665
|
+
defines,
|
|
666
|
+
includes,
|
|
667
|
+
storageBuffers
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
preprocessedFragmentCache.set(material, preprocessed);
|
|
671
|
+
materialSourceMetadataCache.set(material, source);
|
|
672
|
+
return material;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Resolves a material to renderer-ready data and a deterministic signature.
|
|
677
|
+
*
|
|
678
|
+
* @param material - Material input created via {@link defineMaterial}.
|
|
679
|
+
* @returns Resolved material with packed uniform layout, sorted texture keys and cache signature.
|
|
680
|
+
*/
|
|
681
|
+
export function resolveMaterial<
|
|
682
|
+
TUniformKey extends string = string,
|
|
683
|
+
TTextureKey extends string = string,
|
|
684
|
+
TDefineKey extends string = string,
|
|
685
|
+
TIncludeKey extends string = string,
|
|
686
|
+
TStorageBufferKey extends string = string
|
|
687
|
+
>(
|
|
688
|
+
material: FragMaterial<TUniformKey, TTextureKey, TDefineKey, TIncludeKey, TStorageBufferKey>
|
|
689
|
+
): ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> {
|
|
690
|
+
assertDefinedMaterial(material);
|
|
691
|
+
|
|
692
|
+
const cached = getCachedResolvedMaterial(material);
|
|
693
|
+
if (cached) {
|
|
694
|
+
return cached;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const uniforms = material.uniforms as UniformMap<TUniformKey>;
|
|
698
|
+
const textures = material.textures as TextureDefinitionMap<TTextureKey>;
|
|
699
|
+
const uniformLayout = resolveUniformLayout(uniforms);
|
|
700
|
+
const textureKeys = Object.keys(textures).sort() as TTextureKey[];
|
|
701
|
+
const preprocessed =
|
|
702
|
+
preprocessedFragmentCache.get(material) ??
|
|
703
|
+
preprocessMaterialFragment({
|
|
704
|
+
fragment: material.fragment,
|
|
705
|
+
defines: material.defines,
|
|
706
|
+
includes: material.includes
|
|
707
|
+
});
|
|
708
|
+
const fragmentWgsl = preprocessed.fragment;
|
|
709
|
+
const textureConfig = buildTextureConfigSignature(textures, textureKeys);
|
|
710
|
+
|
|
711
|
+
const storageBufferKeys = Object.keys(
|
|
712
|
+
material.storageBuffers ?? {}
|
|
713
|
+
).sort() as TStorageBufferKey[];
|
|
714
|
+
const storageTextureKeys = textureKeys.filter(
|
|
715
|
+
(key) => (textures[key] as TextureDefinition)?.storage === true
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
const signature = JSON.stringify({
|
|
719
|
+
fragmentWgsl,
|
|
720
|
+
uniforms: uniformLayout.entries.map((entry) => `${entry.name}:${entry.type}`),
|
|
721
|
+
textureKeys,
|
|
722
|
+
textureConfig,
|
|
723
|
+
storageBufferKeys: storageBufferKeys.map((key) => {
|
|
724
|
+
const def = (material.storageBuffers as StorageBufferDefinitionMap)[key];
|
|
725
|
+
return `${key}:${def?.type ?? '?'}:${def?.size ?? 0}:${def?.access ?? 'read-write'}`;
|
|
726
|
+
}),
|
|
727
|
+
storageTextureKeys
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
const resolved: ResolvedMaterial<TUniformKey, TTextureKey, TIncludeKey, TStorageBufferKey> = {
|
|
731
|
+
fragmentWgsl,
|
|
732
|
+
fragmentLineMap: preprocessed.lineMap,
|
|
733
|
+
uniforms,
|
|
734
|
+
textures,
|
|
735
|
+
uniformLayout,
|
|
736
|
+
textureKeys,
|
|
737
|
+
signature,
|
|
738
|
+
fragmentSource: material.fragment,
|
|
739
|
+
includeSources: material.includes as MaterialIncludes<TIncludeKey>,
|
|
740
|
+
defineBlockSource: preprocessed.defineBlockSource,
|
|
741
|
+
source: materialSourceMetadataCache.get(material) ?? null,
|
|
742
|
+
storageBufferKeys,
|
|
743
|
+
storageTextureKeys
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
resolvedMaterialCache.set(material, resolved);
|
|
747
|
+
return resolved;
|
|
748
|
+
}
|