@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.
Files changed (228) hide show
  1. package/README.md +99 -0
  2. package/dist/advanced.d.ts +1 -0
  3. package/dist/advanced.d.ts.map +1 -0
  4. package/dist/advanced.js +14 -6
  5. package/dist/core/advanced.d.ts +1 -0
  6. package/dist/core/advanced.d.ts.map +1 -0
  7. package/dist/core/advanced.js +14 -5
  8. package/dist/core/compute-shader.d.ts +87 -0
  9. package/dist/core/compute-shader.d.ts.map +1 -0
  10. package/dist/core/compute-shader.js +205 -0
  11. package/dist/core/compute-shader.js.map +1 -0
  12. package/dist/core/current-value.d.ts +1 -0
  13. package/dist/core/current-value.d.ts.map +1 -0
  14. package/dist/core/current-value.js +35 -34
  15. package/dist/core/current-value.js.map +1 -0
  16. package/dist/core/error-diagnostics.d.ts +1 -0
  17. package/dist/core/error-diagnostics.d.ts.map +1 -0
  18. package/dist/core/error-diagnostics.js +70 -137
  19. package/dist/core/error-diagnostics.js.map +1 -0
  20. package/dist/core/error-report.d.ts +2 -1
  21. package/dist/core/error-report.d.ts.map +1 -0
  22. package/dist/core/error-report.js +247 -233
  23. package/dist/core/error-report.js.map +1 -0
  24. package/dist/core/frame-registry.d.ts +1 -0
  25. package/dist/core/frame-registry.d.ts.map +1 -0
  26. package/dist/core/frame-registry.js +546 -662
  27. package/dist/core/frame-registry.js.map +1 -0
  28. package/dist/core/index.d.ts +6 -2
  29. package/dist/core/index.d.ts.map +1 -0
  30. package/dist/core/index.js +13 -12
  31. package/dist/core/material-preprocess.d.ts +1 -0
  32. package/dist/core/material-preprocess.d.ts.map +1 -0
  33. package/dist/core/material-preprocess.js +131 -152
  34. package/dist/core/material-preprocess.js.map +1 -0
  35. package/dist/core/material.d.ts +23 -6
  36. package/dist/core/material.d.ts.map +1 -0
  37. package/dist/core/material.js +290 -317
  38. package/dist/core/material.js.map +1 -0
  39. package/dist/core/recompile-policy.d.ts +1 -0
  40. package/dist/core/recompile-policy.d.ts.map +1 -0
  41. package/dist/core/recompile-policy.js +18 -13
  42. package/dist/core/recompile-policy.js.map +1 -0
  43. package/dist/core/render-graph.d.ts +8 -3
  44. package/dist/core/render-graph.d.ts.map +1 -0
  45. package/dist/core/render-graph.js +77 -68
  46. package/dist/core/render-graph.js.map +1 -0
  47. package/dist/core/render-targets.d.ts +1 -0
  48. package/dist/core/render-targets.d.ts.map +1 -0
  49. package/dist/core/render-targets.js +52 -53
  50. package/dist/core/render-targets.js.map +1 -0
  51. package/dist/core/renderer.d.ts +1 -0
  52. package/dist/core/renderer.d.ts.map +1 -0
  53. package/dist/core/renderer.js +1337 -1081
  54. package/dist/core/renderer.js.map +1 -0
  55. package/dist/core/runtime-loop.d.ts +3 -2
  56. package/dist/core/runtime-loop.d.ts.map +1 -0
  57. package/dist/core/runtime-loop.js +353 -362
  58. package/dist/core/runtime-loop.js.map +1 -0
  59. package/dist/core/scheduler-helpers.d.ts +1 -0
  60. package/dist/core/scheduler-helpers.d.ts.map +1 -0
  61. package/dist/core/scheduler-helpers.js +52 -51
  62. package/dist/core/scheduler-helpers.js.map +1 -0
  63. package/dist/core/shader.d.ts +10 -1
  64. package/dist/core/shader.d.ts.map +1 -0
  65. package/dist/core/shader.js +109 -115
  66. package/dist/core/shader.js.map +1 -0
  67. package/dist/core/storage-buffers.d.ts +37 -0
  68. package/dist/core/storage-buffers.d.ts.map +1 -0
  69. package/dist/core/storage-buffers.js +95 -0
  70. package/dist/core/storage-buffers.js.map +1 -0
  71. package/dist/core/texture-loader.d.ts +1 -0
  72. package/dist/core/texture-loader.d.ts.map +1 -0
  73. package/dist/core/texture-loader.js +209 -273
  74. package/dist/core/texture-loader.js.map +1 -0
  75. package/dist/core/textures.d.ts +13 -0
  76. package/dist/core/textures.d.ts.map +1 -0
  77. package/dist/core/textures.js +111 -116
  78. package/dist/core/textures.js.map +1 -0
  79. package/dist/core/types.d.ts +147 -4
  80. package/dist/core/types.d.ts.map +1 -0
  81. package/dist/core/types.js +0 -4
  82. package/dist/core/uniforms.d.ts +1 -0
  83. package/dist/core/uniforms.d.ts.map +1 -0
  84. package/dist/core/uniforms.js +170 -191
  85. package/dist/core/uniforms.js.map +1 -0
  86. package/dist/index.d.ts +1 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +13 -6
  89. package/dist/passes/BlitPass.d.ts +1 -0
  90. package/dist/passes/BlitPass.d.ts.map +1 -0
  91. package/dist/passes/BlitPass.js +23 -18
  92. package/dist/passes/BlitPass.js.map +1 -0
  93. package/dist/passes/ComputePass.d.ts +83 -0
  94. package/dist/passes/ComputePass.d.ts.map +1 -0
  95. package/dist/passes/ComputePass.js +92 -0
  96. package/dist/passes/ComputePass.js.map +1 -0
  97. package/dist/passes/CopyPass.d.ts +1 -0
  98. package/dist/passes/CopyPass.d.ts.map +1 -0
  99. package/dist/passes/CopyPass.js +58 -52
  100. package/dist/passes/CopyPass.js.map +1 -0
  101. package/dist/passes/FullscreenPass.d.ts +1 -0
  102. package/dist/passes/FullscreenPass.d.ts.map +1 -0
  103. package/dist/passes/FullscreenPass.js +127 -130
  104. package/dist/passes/FullscreenPass.js.map +1 -0
  105. package/dist/passes/PingPongComputePass.d.ts +104 -0
  106. package/dist/passes/PingPongComputePass.d.ts.map +1 -0
  107. package/dist/passes/PingPongComputePass.js +132 -0
  108. package/dist/passes/PingPongComputePass.js.map +1 -0
  109. package/dist/passes/ShaderPass.d.ts +1 -0
  110. package/dist/passes/ShaderPass.d.ts.map +1 -0
  111. package/dist/passes/ShaderPass.js +41 -37
  112. package/dist/passes/ShaderPass.js.map +1 -0
  113. package/dist/passes/index.d.ts +3 -0
  114. package/dist/passes/index.d.ts.map +1 -0
  115. package/dist/passes/index.js +6 -3
  116. package/dist/react/FragCanvas.d.ts +3 -2
  117. package/dist/react/FragCanvas.d.ts.map +1 -0
  118. package/dist/react/FragCanvas.js +234 -211
  119. package/dist/react/FragCanvas.js.map +1 -0
  120. package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
  121. package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
  122. package/dist/react/MotionGPUErrorOverlay.js +200 -14
  123. package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
  124. package/dist/react/Portal.d.ts +1 -0
  125. package/dist/react/Portal.d.ts.map +1 -0
  126. package/dist/react/Portal.js +18 -21
  127. package/dist/react/Portal.js.map +1 -0
  128. package/dist/react/advanced.d.ts +1 -0
  129. package/dist/react/advanced.d.ts.map +1 -0
  130. package/dist/react/advanced.js +14 -6
  131. package/dist/react/frame-context.d.ts +1 -0
  132. package/dist/react/frame-context.d.ts.map +1 -0
  133. package/dist/react/frame-context.js +88 -94
  134. package/dist/react/frame-context.js.map +1 -0
  135. package/dist/react/index.d.ts +6 -2
  136. package/dist/react/index.d.ts.map +1 -0
  137. package/dist/react/index.js +12 -9
  138. package/dist/react/motiongpu-context.d.ts +1 -0
  139. package/dist/react/motiongpu-context.d.ts.map +1 -0
  140. package/dist/react/motiongpu-context.js +18 -15
  141. package/dist/react/motiongpu-context.js.map +1 -0
  142. package/dist/react/use-motiongpu-user-context.d.ts +1 -0
  143. package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
  144. package/dist/react/use-motiongpu-user-context.js +83 -82
  145. package/dist/react/use-motiongpu-user-context.js.map +1 -0
  146. package/dist/react/use-texture.d.ts +1 -0
  147. package/dist/react/use-texture.d.ts.map +1 -0
  148. package/dist/react/use-texture.js +132 -152
  149. package/dist/react/use-texture.js.map +1 -0
  150. package/dist/svelte/FragCanvas.svelte +2 -2
  151. package/dist/svelte/FragCanvas.svelte.d.ts +3 -2
  152. package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
  153. package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
  154. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
  155. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
  156. package/dist/svelte/Portal.svelte.d.ts +1 -0
  157. package/dist/svelte/Portal.svelte.d.ts.map +1 -0
  158. package/dist/svelte/advanced.d.ts +1 -0
  159. package/dist/svelte/advanced.d.ts.map +1 -0
  160. package/dist/svelte/advanced.js +13 -6
  161. package/dist/svelte/frame-context.d.ts +1 -0
  162. package/dist/svelte/frame-context.d.ts.map +1 -0
  163. package/dist/svelte/frame-context.js +27 -27
  164. package/dist/svelte/frame-context.js.map +1 -0
  165. package/dist/svelte/index.d.ts +6 -2
  166. package/dist/svelte/index.d.ts.map +1 -0
  167. package/dist/svelte/index.js +12 -9
  168. package/dist/svelte/motiongpu-context.d.ts +1 -0
  169. package/dist/svelte/motiongpu-context.d.ts.map +1 -0
  170. package/dist/svelte/motiongpu-context.js +24 -21
  171. package/dist/svelte/motiongpu-context.js.map +1 -0
  172. package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
  173. package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
  174. package/dist/svelte/use-motiongpu-user-context.js +69 -70
  175. package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
  176. package/dist/svelte/use-texture.d.ts +1 -0
  177. package/dist/svelte/use-texture.d.ts.map +1 -0
  178. package/dist/svelte/use-texture.js +125 -147
  179. package/dist/svelte/use-texture.js.map +1 -0
  180. package/package.json +12 -7
  181. package/src/lib/advanced.ts +6 -0
  182. package/src/lib/core/advanced.ts +12 -0
  183. package/src/lib/core/compute-shader.ts +326 -0
  184. package/src/lib/core/current-value.ts +64 -0
  185. package/src/lib/core/error-diagnostics.ts +236 -0
  186. package/src/lib/core/error-report.ts +535 -0
  187. package/src/lib/core/frame-registry.ts +1190 -0
  188. package/src/lib/core/index.ts +94 -0
  189. package/src/lib/core/material-preprocess.ts +295 -0
  190. package/src/lib/core/material.ts +748 -0
  191. package/src/lib/core/recompile-policy.ts +31 -0
  192. package/src/lib/core/render-graph.ts +173 -0
  193. package/src/lib/core/render-targets.ts +107 -0
  194. package/src/lib/core/renderer.ts +2161 -0
  195. package/src/lib/core/runtime-loop.ts +537 -0
  196. package/src/lib/core/scheduler-helpers.ts +136 -0
  197. package/src/lib/core/shader.ts +301 -0
  198. package/src/lib/core/storage-buffers.ts +142 -0
  199. package/src/lib/core/texture-loader.ts +482 -0
  200. package/src/lib/core/textures.ts +257 -0
  201. package/src/lib/core/types.ts +743 -0
  202. package/src/lib/core/uniforms.ts +282 -0
  203. package/src/lib/index.ts +6 -0
  204. package/src/lib/passes/BlitPass.ts +54 -0
  205. package/src/lib/passes/ComputePass.ts +136 -0
  206. package/src/lib/passes/CopyPass.ts +80 -0
  207. package/src/lib/passes/FullscreenPass.ts +173 -0
  208. package/src/lib/passes/PingPongComputePass.ts +180 -0
  209. package/src/lib/passes/ShaderPass.ts +89 -0
  210. package/src/lib/passes/index.ts +9 -0
  211. package/src/lib/react/FragCanvas.tsx +345 -0
  212. package/src/lib/react/MotionGPUErrorOverlay.tsx +524 -0
  213. package/src/lib/react/Portal.tsx +34 -0
  214. package/src/lib/react/advanced.ts +36 -0
  215. package/src/lib/react/frame-context.ts +169 -0
  216. package/src/lib/react/index.ts +68 -0
  217. package/src/lib/react/motiongpu-context.ts +88 -0
  218. package/src/lib/react/use-motiongpu-user-context.ts +186 -0
  219. package/src/lib/react/use-texture.ts +233 -0
  220. package/src/lib/svelte/FragCanvas.svelte +249 -0
  221. package/src/lib/svelte/MotionGPUErrorOverlay.svelte +512 -0
  222. package/src/lib/svelte/Portal.svelte +31 -0
  223. package/src/lib/svelte/advanced.ts +32 -0
  224. package/src/lib/svelte/frame-context.ts +87 -0
  225. package/src/lib/svelte/index.ts +68 -0
  226. package/src/lib/svelte/motiongpu-context.ts +97 -0
  227. package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
  228. package/src/lib/svelte/use-texture.ts +232 -0
@@ -0,0 +1,326 @@
1
+ import type { StorageBufferAccess, StorageBufferType, UniformLayout } from './types.js';
2
+
3
+ /**
4
+ * Regex contract for compute entrypoint.
5
+ * Matches: @compute @workgroup_size(...) fn compute(
6
+ * with @builtin(global_invocation_id) parameter.
7
+ */
8
+ export const COMPUTE_ENTRY_CONTRACT = /@compute\s+@workgroup_size\s*\([^)]+\)\s*fn\s+compute\s*\(/;
9
+
10
+ /**
11
+ * Regex to extract @workgroup_size values.
12
+ */
13
+ const WORKGROUP_SIZE_PATTERN =
14
+ /@workgroup_size\s*\(\s*(\d+)(?:\s*,\s*(\d+))?(?:\s*,\s*(\d+))?\s*\)/;
15
+
16
+ /**
17
+ * Regex to verify @builtin(global_invocation_id) parameter.
18
+ */
19
+ const GLOBAL_INVOCATION_ID_PATTERN = /@builtin\s*\(\s*global_invocation_id\s*\)/;
20
+ const WORKGROUP_DIMENSION_MIN = 1;
21
+ const WORKGROUP_DIMENSION_MAX = 65535;
22
+
23
+ function extractComputeParamList(compute: string): string | null {
24
+ const computeFnIndex = compute.indexOf('fn compute');
25
+ if (computeFnIndex === -1) {
26
+ return null;
27
+ }
28
+
29
+ const openParenIndex = compute.indexOf('(', computeFnIndex);
30
+ if (openParenIndex === -1) {
31
+ return null;
32
+ }
33
+
34
+ let depth = 0;
35
+ for (let index = openParenIndex; index < compute.length; index += 1) {
36
+ const char = compute[index];
37
+ if (char === '(') {
38
+ depth += 1;
39
+ continue;
40
+ }
41
+
42
+ if (char === ')') {
43
+ depth -= 1;
44
+ if (depth === 0) {
45
+ return compute.slice(openParenIndex + 1, index);
46
+ }
47
+ }
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ function assertWorkgroupDimension(value: number): void {
54
+ if (
55
+ !Number.isFinite(value) ||
56
+ !Number.isInteger(value) ||
57
+ value < WORKGROUP_DIMENSION_MIN ||
58
+ value > WORKGROUP_DIMENSION_MAX
59
+ ) {
60
+ throw new Error(
61
+ `@workgroup_size dimensions must be integers in range ${WORKGROUP_DIMENSION_MIN}-${WORKGROUP_DIMENSION_MAX}, got ${value}.`
62
+ );
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Default uniform field used when no custom uniforms are provided in compute.
68
+ */
69
+ const DEFAULT_UNIFORM_FIELD = 'motiongpu_unused: vec4f,';
70
+
71
+ /**
72
+ * Validates compute shader user code matches the compute contract.
73
+ *
74
+ * @param compute - User compute shader WGSL source.
75
+ * @throws {Error} When shader does not match the compute contract.
76
+ */
77
+ export function assertComputeContract(compute: string): void {
78
+ if (!COMPUTE_ENTRY_CONTRACT.test(compute)) {
79
+ throw new Error(
80
+ 'Compute shader must declare `@compute @workgroup_size(...) fn compute(...)`. ' +
81
+ 'Ensure the function is named `compute` and includes @compute and @workgroup_size annotations.'
82
+ );
83
+ }
84
+
85
+ const params = extractComputeParamList(compute);
86
+ if (!params || !GLOBAL_INVOCATION_ID_PATTERN.test(params)) {
87
+ throw new Error('Compute shader must include a `@builtin(global_invocation_id)` parameter.');
88
+ }
89
+
90
+ extractWorkgroupSize(compute);
91
+ }
92
+
93
+ /**
94
+ * Extracts @workgroup_size values from WGSL compute shader.
95
+ *
96
+ * @param compute - Validated compute shader source.
97
+ * @returns Tuple [x, y, z] with defaults of 1 for omitted dimensions.
98
+ */
99
+ export function extractWorkgroupSize(compute: string): [number, number, number] {
100
+ const match = compute.match(WORKGROUP_SIZE_PATTERN);
101
+ if (!match) {
102
+ throw new Error('Could not extract @workgroup_size from compute shader source.');
103
+ }
104
+
105
+ const x = Number.parseInt(match[1] ?? '1', 10);
106
+ const y = Number.parseInt(match[2] ?? '1', 10);
107
+ const z = Number.parseInt(match[3] ?? '1', 10);
108
+ assertWorkgroupDimension(x);
109
+ assertWorkgroupDimension(y);
110
+ assertWorkgroupDimension(z);
111
+
112
+ return [x, y, z];
113
+ }
114
+
115
+ /**
116
+ * Maps StorageBufferAccess to WGSL var qualifier.
117
+ */
118
+ function toWgslAccessMode(access: StorageBufferAccess): string {
119
+ switch (access) {
120
+ case 'read':
121
+ return 'read';
122
+ case 'read-write':
123
+ return 'read_write';
124
+ default:
125
+ throw new Error(`Unsupported storage buffer access mode "${String(access)}".`);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Builds WGSL struct fields for uniforms used in compute shader preamble.
131
+ */
132
+ function buildUniformStructForCompute(layout: UniformLayout): string {
133
+ if (layout.entries.length === 0) {
134
+ return DEFAULT_UNIFORM_FIELD;
135
+ }
136
+
137
+ return layout.entries.map((entry) => `${entry.name}: ${entry.type},`).join('\n\t');
138
+ }
139
+
140
+ /**
141
+ * Builds storage buffer binding declarations for compute shader.
142
+ *
143
+ * @param storageBufferKeys - Sorted buffer keys.
144
+ * @param definitions - Type/access definitions per key.
145
+ * @param groupIndex - Bind group index for storage buffers.
146
+ * @returns WGSL binding declaration string.
147
+ */
148
+ export function buildComputeStorageBufferBindings(
149
+ storageBufferKeys: string[],
150
+ definitions: Record<string, { type: StorageBufferType; access: StorageBufferAccess }>,
151
+ groupIndex: number
152
+ ): string {
153
+ if (storageBufferKeys.length === 0) {
154
+ return '';
155
+ }
156
+
157
+ const declarations: string[] = [];
158
+
159
+ for (let index = 0; index < storageBufferKeys.length; index += 1) {
160
+ const key = storageBufferKeys[index];
161
+ if (key === undefined) {
162
+ continue;
163
+ }
164
+
165
+ const definition = definitions[key];
166
+ if (!definition) {
167
+ continue;
168
+ }
169
+
170
+ const accessMode = toWgslAccessMode(definition.access);
171
+ declarations.push(
172
+ `@group(${groupIndex}) @binding(${index}) var<storage, ${accessMode}> ${key}: ${definition.type};`
173
+ );
174
+ }
175
+
176
+ return declarations.join('\n');
177
+ }
178
+
179
+ /**
180
+ * Builds storage texture binding declarations for compute shader.
181
+ *
182
+ * @param storageTextureKeys - Sorted storage texture keys.
183
+ * @param definitions - Format definitions per key.
184
+ * @param groupIndex - Bind group index for storage textures.
185
+ * @returns WGSL binding declaration string.
186
+ */
187
+ export function buildComputeStorageTextureBindings(
188
+ storageTextureKeys: string[],
189
+ definitions: Record<string, { format: GPUTextureFormat }>,
190
+ groupIndex: number
191
+ ): string {
192
+ if (storageTextureKeys.length === 0) {
193
+ return '';
194
+ }
195
+
196
+ const declarations: string[] = [];
197
+
198
+ for (let index = 0; index < storageTextureKeys.length; index += 1) {
199
+ const key = storageTextureKeys[index];
200
+ if (key === undefined) {
201
+ continue;
202
+ }
203
+
204
+ const definition = definitions[key];
205
+ if (!definition) {
206
+ continue;
207
+ }
208
+
209
+ declarations.push(
210
+ `@group(${groupIndex}) @binding(${index}) var ${key}: texture_storage_2d<${definition.format}, write>;`
211
+ );
212
+ }
213
+
214
+ return declarations.join('\n');
215
+ }
216
+
217
+ /**
218
+ * Maps storage texture format to sampled texture scalar type for `texture_2d<T>`.
219
+ */
220
+ export function storageTextureSampleScalarType(format: GPUTextureFormat): 'f32' | 'u32' | 'i32' {
221
+ const normalized = String(format).toLowerCase();
222
+ if (normalized.endsWith('uint')) {
223
+ return 'u32';
224
+ }
225
+ if (normalized.endsWith('sint')) {
226
+ return 'i32';
227
+ }
228
+ return 'f32';
229
+ }
230
+
231
+ /**
232
+ * Assembles compute shader WGSL for ping-pong workflows.
233
+ *
234
+ * Exposes two generated bindings under group(2):
235
+ * - `${target}A`: sampled read texture (`texture_2d<T>`)
236
+ * - `${target}B`: storage write texture (`texture_storage_2d<format, write>`)
237
+ */
238
+ export function buildPingPongComputeShaderSource(options: {
239
+ compute: string;
240
+ uniformLayout: UniformLayout;
241
+ storageBufferKeys: string[];
242
+ storageBufferDefinitions: Record<
243
+ string,
244
+ { type: StorageBufferType; access: StorageBufferAccess }
245
+ >;
246
+ target: string;
247
+ targetFormat: GPUTextureFormat;
248
+ }): string {
249
+ const uniformFields = buildUniformStructForCompute(options.uniformLayout);
250
+ const storageBufferBindings = buildComputeStorageBufferBindings(
251
+ options.storageBufferKeys,
252
+ options.storageBufferDefinitions,
253
+ 1
254
+ );
255
+ const sampledType = storageTextureSampleScalarType(options.targetFormat);
256
+ const pingPongTextureBindings = [
257
+ `@group(2) @binding(0) var ${options.target}A: texture_2d<${sampledType}>;`,
258
+ `@group(2) @binding(1) var ${options.target}B: texture_storage_2d<${options.targetFormat}, write>;`
259
+ ].join('\n');
260
+
261
+ return `struct MotionGPUFrame {
262
+ time: f32,
263
+ delta: f32,
264
+ resolution: vec2f,
265
+ };
266
+
267
+ struct MotionGPUUniforms {
268
+ ${uniformFields}
269
+ };
270
+
271
+ @group(0) @binding(0) var<uniform> motiongpuFrame: MotionGPUFrame;
272
+ @group(0) @binding(1) var<uniform> motiongpuUniforms: MotionGPUUniforms;
273
+ ${storageBufferBindings ? '\n' + storageBufferBindings : ''}
274
+ ${pingPongTextureBindings ? '\n' + pingPongTextureBindings : ''}
275
+
276
+ ${options.compute}
277
+ `;
278
+ }
279
+
280
+ /**
281
+ * Assembles full compute shader WGSL with preamble.
282
+ *
283
+ * @param options - Compute shader build options.
284
+ * @returns Complete WGSL source for compute stage.
285
+ */
286
+ export function buildComputeShaderSource(options: {
287
+ compute: string;
288
+ uniformLayout: UniformLayout;
289
+ storageBufferKeys: string[];
290
+ storageBufferDefinitions: Record<
291
+ string,
292
+ { type: StorageBufferType; access: StorageBufferAccess }
293
+ >;
294
+ storageTextureKeys: string[];
295
+ storageTextureDefinitions: Record<string, { format: GPUTextureFormat }>;
296
+ }): string {
297
+ const uniformFields = buildUniformStructForCompute(options.uniformLayout);
298
+ const storageBufferBindings = buildComputeStorageBufferBindings(
299
+ options.storageBufferKeys,
300
+ options.storageBufferDefinitions,
301
+ 1
302
+ );
303
+ const storageTextureBindings = buildComputeStorageTextureBindings(
304
+ options.storageTextureKeys,
305
+ options.storageTextureDefinitions,
306
+ 2
307
+ );
308
+
309
+ return `struct MotionGPUFrame {
310
+ time: f32,
311
+ delta: f32,
312
+ resolution: vec2f,
313
+ };
314
+
315
+ struct MotionGPUUniforms {
316
+ ${uniformFields}
317
+ };
318
+
319
+ @group(0) @binding(0) var<uniform> motiongpuFrame: MotionGPUFrame;
320
+ @group(0) @binding(1) var<uniform> motiongpuUniforms: MotionGPUUniforms;
321
+ ${storageBufferBindings ? '\n' + storageBufferBindings : ''}
322
+ ${storageTextureBindings ? '\n' + storageTextureBindings : ''}
323
+
324
+ ${options.compute}
325
+ `;
326
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Minimal subscribe contract used by MotionGPU core.
3
+ */
4
+ export interface Subscribable<T> {
5
+ subscribe: (run: (value: T) => void) => () => void;
6
+ }
7
+
8
+ /**
9
+ * Readable value with synchronous access to the latest value.
10
+ */
11
+ export interface CurrentReadable<T> extends Subscribable<T> {
12
+ readonly current: T;
13
+ }
14
+
15
+ /**
16
+ * Writable extension of {@link CurrentReadable}.
17
+ */
18
+ export interface CurrentWritable<T> extends CurrentReadable<T> {
19
+ set: (value: T) => void;
20
+ update: (updater: (value: T) => T) => void;
21
+ }
22
+
23
+ /**
24
+ * Creates a writable value with immediate subscription semantics.
25
+ */
26
+ export function createCurrentWritable<T>(
27
+ initialValue: T,
28
+ onChange?: (value: T) => void
29
+ ): CurrentWritable<T> {
30
+ let current = initialValue;
31
+ const subscribers = new Set<(value: T) => void>();
32
+
33
+ const notify = (value: T): void => {
34
+ for (const run of subscribers) {
35
+ run(value);
36
+ }
37
+ };
38
+
39
+ const set = (value: T): void => {
40
+ if (Object.is(current, value)) {
41
+ return;
42
+ }
43
+ current = value;
44
+ notify(value);
45
+ onChange?.(value);
46
+ };
47
+
48
+ return {
49
+ get current() {
50
+ return current;
51
+ },
52
+ subscribe(run) {
53
+ subscribers.add(run);
54
+ run(current);
55
+ return () => {
56
+ subscribers.delete(run);
57
+ };
58
+ },
59
+ set,
60
+ update(updater) {
61
+ set(updater(current));
62
+ }
63
+ };
64
+ }
@@ -0,0 +1,236 @@
1
+ import type { MaterialSourceLocation } from './material-preprocess.js';
2
+
3
+ /**
4
+ * Source metadata for material declaration callsite.
5
+ */
6
+ export interface MaterialSourceMetadata {
7
+ component?: string;
8
+ file?: string;
9
+ line?: number;
10
+ column?: number;
11
+ functionName?: string;
12
+ }
13
+
14
+ /**
15
+ * One WGSL compiler diagnostic enriched with source-location metadata.
16
+ */
17
+ export interface ShaderCompilationDiagnostic {
18
+ generatedLine: number;
19
+ message: string;
20
+ linePos?: number;
21
+ lineLength?: number;
22
+ sourceLocation: MaterialSourceLocation | null;
23
+ }
24
+
25
+ /**
26
+ * Runtime context snapshot captured for shader compilation diagnostics.
27
+ */
28
+ export interface ShaderCompilationRuntimeContext {
29
+ materialSignature?: string;
30
+ passGraph?: {
31
+ passCount: number;
32
+ enabledPassCount: number;
33
+ inputs: string[];
34
+ outputs: string[];
35
+ };
36
+ activeRenderTargets: string[];
37
+ }
38
+
39
+ /**
40
+ * Structured payload attached to WGSL compilation errors.
41
+ */
42
+ export interface ShaderCompilationDiagnosticsPayload {
43
+ kind: 'shader-compilation';
44
+ diagnostics: ShaderCompilationDiagnostic[];
45
+ fragmentSource: string;
46
+ includeSources: Record<string, string>;
47
+ defineBlockSource?: string;
48
+ materialSource: MaterialSourceMetadata | null;
49
+ runtimeContext?: ShaderCompilationRuntimeContext;
50
+ }
51
+
52
+ type MotionGPUErrorWithDiagnostics = Error & {
53
+ motiongpuDiagnostics?: unknown;
54
+ };
55
+
56
+ function isMaterialSourceMetadata(value: unknown): value is MaterialSourceMetadata {
57
+ if (value === null || typeof value !== 'object') {
58
+ return false;
59
+ }
60
+
61
+ const record = value as Record<string, unknown>;
62
+ if (record.component !== undefined && typeof record.component !== 'string') {
63
+ return false;
64
+ }
65
+ if (record.file !== undefined && typeof record.file !== 'string') {
66
+ return false;
67
+ }
68
+ if (record.functionName !== undefined && typeof record.functionName !== 'string') {
69
+ return false;
70
+ }
71
+ if (record.line !== undefined && typeof record.line !== 'number') {
72
+ return false;
73
+ }
74
+ if (record.column !== undefined && typeof record.column !== 'number') {
75
+ return false;
76
+ }
77
+
78
+ return true;
79
+ }
80
+
81
+ function isMaterialSourceLocation(value: unknown): value is MaterialSourceLocation | null {
82
+ if (value === null) {
83
+ return true;
84
+ }
85
+
86
+ if (typeof value !== 'object') {
87
+ return false;
88
+ }
89
+
90
+ const record = value as Record<string, unknown>;
91
+ const kind = record.kind;
92
+ if (kind !== 'fragment' && kind !== 'include' && kind !== 'define') {
93
+ return false;
94
+ }
95
+
96
+ return typeof record.line === 'number';
97
+ }
98
+
99
+ function isShaderCompilationDiagnostic(value: unknown): value is ShaderCompilationDiagnostic {
100
+ if (value === null || typeof value !== 'object') {
101
+ return false;
102
+ }
103
+
104
+ const record = value as Record<string, unknown>;
105
+ if (typeof record.generatedLine !== 'number') {
106
+ return false;
107
+ }
108
+ if (typeof record.message !== 'string') {
109
+ return false;
110
+ }
111
+ if (record.linePos !== undefined && typeof record.linePos !== 'number') {
112
+ return false;
113
+ }
114
+ if (record.lineLength !== undefined && typeof record.lineLength !== 'number') {
115
+ return false;
116
+ }
117
+ if (!isMaterialSourceLocation(record.sourceLocation)) {
118
+ return false;
119
+ }
120
+
121
+ return true;
122
+ }
123
+
124
+ function isStringArray(value: unknown): value is string[] {
125
+ return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
126
+ }
127
+
128
+ function isShaderCompilationRuntimeContext(
129
+ value: unknown
130
+ ): value is ShaderCompilationRuntimeContext {
131
+ if (value === null || typeof value !== 'object') {
132
+ return false;
133
+ }
134
+
135
+ const record = value as Record<string, unknown>;
136
+ if (record.materialSignature !== undefined && typeof record.materialSignature !== 'string') {
137
+ return false;
138
+ }
139
+ if (!isStringArray(record.activeRenderTargets)) {
140
+ return false;
141
+ }
142
+ const passGraph = record.passGraph;
143
+ if (passGraph === undefined) {
144
+ return true;
145
+ }
146
+ if (passGraph === null || typeof passGraph !== 'object') {
147
+ return false;
148
+ }
149
+
150
+ const passGraphRecord = passGraph as Record<string, unknown>;
151
+ if (typeof passGraphRecord.passCount !== 'number') {
152
+ return false;
153
+ }
154
+ if (typeof passGraphRecord.enabledPassCount !== 'number') {
155
+ return false;
156
+ }
157
+ if (!isStringArray(passGraphRecord.inputs) || !isStringArray(passGraphRecord.outputs)) {
158
+ return false;
159
+ }
160
+
161
+ return true;
162
+ }
163
+
164
+ /**
165
+ * Attaches structured diagnostics payload to an Error.
166
+ */
167
+ export function attachShaderCompilationDiagnostics(
168
+ error: Error,
169
+ payload: ShaderCompilationDiagnosticsPayload
170
+ ): Error {
171
+ (error as MotionGPUErrorWithDiagnostics).motiongpuDiagnostics = payload;
172
+ return error;
173
+ }
174
+
175
+ /**
176
+ * Extracts structured diagnostics payload from unknown error value.
177
+ */
178
+ export function getShaderCompilationDiagnostics(
179
+ error: unknown
180
+ ): ShaderCompilationDiagnosticsPayload | null {
181
+ if (!(error instanceof Error)) {
182
+ return null;
183
+ }
184
+
185
+ const payload = (error as MotionGPUErrorWithDiagnostics).motiongpuDiagnostics;
186
+ if (payload === null || typeof payload !== 'object') {
187
+ return null;
188
+ }
189
+
190
+ const record = payload as Record<string, unknown>;
191
+ if (record.kind !== 'shader-compilation') {
192
+ return null;
193
+ }
194
+ if (
195
+ !Array.isArray(record.diagnostics) ||
196
+ !record.diagnostics.every(isShaderCompilationDiagnostic)
197
+ ) {
198
+ return null;
199
+ }
200
+ if (typeof record.fragmentSource !== 'string') {
201
+ return null;
202
+ }
203
+ if (record.defineBlockSource !== undefined && typeof record.defineBlockSource !== 'string') {
204
+ return null;
205
+ }
206
+ if (record.includeSources === null || typeof record.includeSources !== 'object') {
207
+ return null;
208
+ }
209
+ const includeSources = record.includeSources as Record<string, unknown>;
210
+ if (Object.values(includeSources).some((value) => typeof value !== 'string')) {
211
+ return null;
212
+ }
213
+ if (record.materialSource !== null && !isMaterialSourceMetadata(record.materialSource)) {
214
+ return null;
215
+ }
216
+ if (
217
+ record.runtimeContext !== undefined &&
218
+ !isShaderCompilationRuntimeContext(record.runtimeContext)
219
+ ) {
220
+ return null;
221
+ }
222
+
223
+ return {
224
+ kind: 'shader-compilation',
225
+ diagnostics: record.diagnostics as ShaderCompilationDiagnostic[],
226
+ fragmentSource: record.fragmentSource,
227
+ includeSources: includeSources as Record<string, string>,
228
+ ...(record.defineBlockSource !== undefined
229
+ ? { defineBlockSource: record.defineBlockSource as string }
230
+ : {}),
231
+ materialSource: (record.materialSource ?? null) as MaterialSourceMetadata | null,
232
+ ...(record.runtimeContext !== undefined
233
+ ? { runtimeContext: record.runtimeContext as ShaderCompilationRuntimeContext }
234
+ : {})
235
+ };
236
+ }