@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,94 @@
1
+ /**
2
+ * Framework-agnostic MotionGPU core entrypoint.
3
+ *
4
+ * This surface is intended for building framework adapters (Svelte/React/Vue).
5
+ */
6
+ export { defineMaterial, resolveMaterial } from './material.js';
7
+ export { toMotionGPUErrorReport } from './error-report.js';
8
+ export { createCurrentWritable } from './current-value.js';
9
+ export { createFrameRegistry } from './frame-registry.js';
10
+ export { createMotionGPURuntimeLoop } from './runtime-loop.js';
11
+ export { loadTexturesFromUrls } from './texture-loader.js';
12
+ export {
13
+ BlitPass,
14
+ CopyPass,
15
+ ShaderPass,
16
+ ComputePass,
17
+ PingPongComputePass
18
+ } from '../passes/index.js';
19
+ export type { CurrentReadable, CurrentWritable, Subscribable } from './current-value.js';
20
+ export type {
21
+ MotionGPUErrorCode,
22
+ MotionGPUErrorContext,
23
+ MotionGPUErrorPhase,
24
+ MotionGPUErrorReport,
25
+ MotionGPUErrorSeverity,
26
+ MotionGPUErrorSource,
27
+ MotionGPUErrorSourceLine
28
+ } from './error-report.js';
29
+ export type {
30
+ FrameCallback,
31
+ FrameKey,
32
+ FrameProfilingSnapshot,
33
+ FrameRegistry,
34
+ FrameRunTimings,
35
+ FrameScheduleSnapshot,
36
+ FrameStage,
37
+ FrameStageCallback,
38
+ FrameTask,
39
+ FrameTaskInvalidation,
40
+ FrameTaskInvalidationToken,
41
+ FrameTimingStats,
42
+ UseFrameOptions,
43
+ UseFrameResult
44
+ } from './frame-registry.js';
45
+ export type {
46
+ FragMaterial,
47
+ FragMaterialInput,
48
+ MaterialDefineValue,
49
+ MaterialDefines,
50
+ MaterialIncludes,
51
+ ResolvedMaterial,
52
+ TypedMaterialDefineValue
53
+ } from './material.js';
54
+ export type { MotionGPURuntimeLoop, MotionGPURuntimeLoopOptions } from './runtime-loop.js';
55
+ export type { LoadedTexture, TextureDecodeOptions, TextureLoadOptions } from './texture-loader.js';
56
+ export type {
57
+ FrameInvalidationToken,
58
+ FrameState,
59
+ OutputColorSpace,
60
+ AnyPass,
61
+ ComputePassLike,
62
+ RenderPass,
63
+ RenderPassContext,
64
+ RenderPassFlags,
65
+ RenderPassInputSlot,
66
+ RenderPassOutputSlot,
67
+ RenderMode,
68
+ RenderTarget,
69
+ RenderTargetDefinition,
70
+ RenderTargetDefinitionMap,
71
+ TextureData,
72
+ TextureDefinition,
73
+ TextureDefinitionMap,
74
+ TextureMap,
75
+ TextureSource,
76
+ TextureUpdateMode,
77
+ TextureValue,
78
+ TypedUniform,
79
+ UniformLayout,
80
+ UniformLayoutEntry,
81
+ UniformMap,
82
+ UniformMat4Value,
83
+ UniformType,
84
+ UniformValue
85
+ } from './types.js';
86
+ export type {
87
+ StorageBufferAccess,
88
+ StorageBufferDefinition,
89
+ StorageBufferDefinitionMap,
90
+ StorageBufferType,
91
+ ComputePassContext
92
+ } from './types.js';
93
+ export type { ComputePassOptions, ComputeDispatchContext } from '../passes/ComputePass.js';
94
+ export type { PingPongComputePassOptions } from '../passes/PingPongComputePass.js';
@@ -0,0 +1,295 @@
1
+ import { assertUniformName } from './uniforms.js';
2
+ import type {
3
+ MaterialDefineValue,
4
+ MaterialDefines,
5
+ MaterialIncludes,
6
+ TypedMaterialDefineValue
7
+ } from './material.js';
8
+
9
+ const INCLUDE_DIRECTIVE_PATTERN = /^\s*#include\s+<([A-Za-z_][A-Za-z0-9_]*)>\s*$/;
10
+
11
+ /**
12
+ * Source location metadata for one generated fragment line.
13
+ */
14
+ export interface MaterialSourceLocation {
15
+ /**
16
+ * Origin category for this generated line.
17
+ */
18
+ kind: 'fragment' | 'include' | 'define';
19
+ /**
20
+ * 1-based line in the origin source.
21
+ */
22
+ line: number;
23
+ /**
24
+ * Include chunk identifier when `kind === "include"`.
25
+ */
26
+ include?: string;
27
+ /**
28
+ * Define identifier when `kind === "define"`.
29
+ */
30
+ define?: string;
31
+ }
32
+
33
+ /**
34
+ * 1-based line map from generated fragment WGSL to user source locations.
35
+ */
36
+ export type MaterialLineMap = Array<MaterialSourceLocation | null>;
37
+
38
+ /**
39
+ * Preprocess output used by material resolution and diagnostics mapping.
40
+ */
41
+ export interface PreprocessedMaterialFragment {
42
+ /**
43
+ * Final fragment source after defines/include expansion.
44
+ */
45
+ fragment: string;
46
+ /**
47
+ * 1-based generated-line source map.
48
+ */
49
+ lineMap: MaterialLineMap;
50
+ /**
51
+ * Deterministic WGSL define block used to build the final fragment source.
52
+ */
53
+ defineBlockSource: string;
54
+ }
55
+
56
+ function normalizeTypedDefine(
57
+ name: string,
58
+ define: TypedMaterialDefineValue
59
+ ): TypedMaterialDefineValue {
60
+ const value = define.value;
61
+
62
+ if (define.type === 'bool') {
63
+ if (typeof value !== 'boolean') {
64
+ throw new Error(`Invalid define value for "${name}". bool define requires boolean value.`);
65
+ }
66
+
67
+ return {
68
+ type: 'bool',
69
+ value
70
+ };
71
+ }
72
+
73
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
74
+ throw new Error(`Invalid define value for "${name}". Numeric define must be finite.`);
75
+ }
76
+
77
+ if ((define.type === 'i32' || define.type === 'u32') && !Number.isInteger(value)) {
78
+ throw new Error(`Invalid define value for "${name}". ${define.type} define requires integer.`);
79
+ }
80
+
81
+ if (define.type === 'u32' && value < 0) {
82
+ throw new Error(`Invalid define value for "${name}". u32 define must be >= 0.`);
83
+ }
84
+
85
+ return {
86
+ type: define.type,
87
+ value
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Validates and normalizes define entries.
93
+ */
94
+ export function normalizeDefines<TDefineKey extends string>(
95
+ defines: MaterialDefines<TDefineKey> | undefined
96
+ ): MaterialDefines<TDefineKey> {
97
+ const resolved: MaterialDefines<TDefineKey> = {} as MaterialDefines<TDefineKey>;
98
+
99
+ for (const [name, value] of Object.entries(defines ?? {}) as Array<
100
+ [TDefineKey, MaterialDefineValue]
101
+ >) {
102
+ assertUniformName(name);
103
+
104
+ if (typeof value === 'boolean') {
105
+ resolved[name] = value;
106
+ continue;
107
+ }
108
+
109
+ if (typeof value === 'number') {
110
+ if (!Number.isFinite(value)) {
111
+ throw new Error(`Invalid define value for "${name}". Define numbers must be finite.`);
112
+ }
113
+ resolved[name] = value;
114
+ continue;
115
+ }
116
+
117
+ const normalized = normalizeTypedDefine(name, value);
118
+ resolved[name] = Object.freeze(normalized);
119
+ }
120
+
121
+ return resolved;
122
+ }
123
+
124
+ /**
125
+ * Validates include map identifiers and source chunks.
126
+ */
127
+ export function normalizeIncludes<TIncludeKey extends string>(
128
+ includes: MaterialIncludes<TIncludeKey> | undefined
129
+ ): MaterialIncludes<TIncludeKey> {
130
+ const resolved: MaterialIncludes<TIncludeKey> = {} as MaterialIncludes<TIncludeKey>;
131
+
132
+ for (const [name, source] of Object.entries(includes ?? {}) as Array<[TIncludeKey, string]>) {
133
+ assertUniformName(name);
134
+ if (typeof source !== 'string' || source.trim().length === 0) {
135
+ throw new Error(`Invalid include "${name}". Include source must be a non-empty WGSL string.`);
136
+ }
137
+ resolved[name] = source;
138
+ }
139
+
140
+ return resolved;
141
+ }
142
+
143
+ /**
144
+ * Converts one define declaration to WGSL `const`.
145
+ */
146
+ export function toDefineLine(key: string, value: MaterialDefineValue): string {
147
+ if (typeof value === 'boolean') {
148
+ return `const ${key}: bool = ${value ? 'true' : 'false'};`;
149
+ }
150
+
151
+ if (typeof value === 'number') {
152
+ const valueLiteral = Number.isInteger(value) ? `${value}.0` : `${value}`;
153
+ return `const ${key}: f32 = ${valueLiteral};`;
154
+ }
155
+
156
+ if (value.type === 'bool') {
157
+ return `const ${key}: bool = ${value.value ? 'true' : 'false'};`;
158
+ }
159
+
160
+ if (value.type === 'f32') {
161
+ const numberValue = value.value as number;
162
+ const valueLiteral = Number.isInteger(numberValue) ? `${numberValue}.0` : `${numberValue}`;
163
+ return `const ${key}: f32 = ${valueLiteral};`;
164
+ }
165
+
166
+ if (value.type === 'i32') {
167
+ return `const ${key}: i32 = ${value.value};`;
168
+ }
169
+
170
+ return `const ${key}: u32 = ${value.value}u;`;
171
+ }
172
+
173
+ function expandChunk(
174
+ source: string,
175
+ kind: 'fragment' | 'include',
176
+ includeName: string | undefined,
177
+ includes: Record<string, string>,
178
+ stack: string[],
179
+ expandedIncludes: Set<string>
180
+ ): { lines: string[]; mapEntries: MaterialSourceLocation[] } {
181
+ const sourceLines = source.split('\n');
182
+ const lines: string[] = [];
183
+ const mapEntries: MaterialSourceLocation[] = [];
184
+
185
+ for (let index = 0; index < sourceLines.length; index += 1) {
186
+ const sourceLine = sourceLines[index];
187
+ if (sourceLine === undefined) {
188
+ continue;
189
+ }
190
+
191
+ const includeMatch = sourceLine.match(INCLUDE_DIRECTIVE_PATTERN);
192
+ if (!includeMatch) {
193
+ lines.push(sourceLine);
194
+ mapEntries.push({
195
+ kind,
196
+ line: index + 1,
197
+ ...(kind === 'include' && includeName ? { include: includeName } : {})
198
+ });
199
+ continue;
200
+ }
201
+
202
+ const includeKey = includeMatch[1];
203
+ if (!includeKey) {
204
+ throw new Error('Invalid include directive in fragment shader.');
205
+ }
206
+
207
+ assertUniformName(includeKey);
208
+ const includeSource = includes[includeKey];
209
+ if (!includeSource) {
210
+ throw new Error(`Unknown include "${includeKey}" referenced in fragment shader.`);
211
+ }
212
+
213
+ if (stack.includes(includeKey)) {
214
+ throw new Error(
215
+ `Circular include detected for "${includeKey}". Include stack: ${[...stack, includeKey].join(' -> ')}.`
216
+ );
217
+ }
218
+
219
+ if (expandedIncludes.has(includeKey)) {
220
+ continue;
221
+ }
222
+ expandedIncludes.add(includeKey);
223
+
224
+ const nested = expandChunk(
225
+ includeSource,
226
+ 'include',
227
+ includeKey,
228
+ includes,
229
+ [...stack, includeKey],
230
+ expandedIncludes
231
+ );
232
+ lines.push(...nested.lines);
233
+ mapEntries.push(...nested.mapEntries);
234
+ }
235
+
236
+ return { lines, mapEntries };
237
+ }
238
+
239
+ /**
240
+ * Preprocesses material fragment with deterministic define/include expansion and line mapping.
241
+ */
242
+ export function preprocessMaterialFragment<
243
+ TDefineKey extends string,
244
+ TIncludeKey extends string
245
+ >(input: {
246
+ fragment: string;
247
+ defines?: MaterialDefines<TDefineKey>;
248
+ includes?: MaterialIncludes<TIncludeKey>;
249
+ }): PreprocessedMaterialFragment {
250
+ const normalizedDefines = normalizeDefines(input.defines);
251
+ const normalizedIncludes = normalizeIncludes(input.includes);
252
+
253
+ const fragmentExpanded = expandChunk(
254
+ input.fragment,
255
+ 'fragment',
256
+ undefined,
257
+ normalizedIncludes,
258
+ [],
259
+ new Set()
260
+ );
261
+ const defineEntries = (
262
+ Object.entries(normalizedDefines) as Array<[TDefineKey, MaterialDefineValue]>
263
+ ).sort(([a], [b]) => a.localeCompare(b));
264
+ const lines: string[] = [];
265
+ const defineLines: string[] = [];
266
+ const mapEntries: Array<MaterialSourceLocation | null> = [];
267
+
268
+ for (let index = 0; index < defineEntries.length; index += 1) {
269
+ const entry = defineEntries[index];
270
+ if (!entry) {
271
+ continue;
272
+ }
273
+
274
+ const [name, value] = entry;
275
+ const defineLine = toDefineLine(name, value);
276
+ lines.push(defineLine);
277
+ defineLines.push(defineLine);
278
+ mapEntries.push({ kind: 'define', line: index + 1, define: name });
279
+ }
280
+
281
+ if (defineEntries.length > 0) {
282
+ lines.push('');
283
+ mapEntries.push(null);
284
+ }
285
+
286
+ lines.push(...fragmentExpanded.lines);
287
+ mapEntries.push(...fragmentExpanded.mapEntries);
288
+
289
+ const lineMap: MaterialLineMap = [null, ...mapEntries];
290
+ return {
291
+ fragment: lines.join('\n'),
292
+ lineMap,
293
+ defineBlockSource: defineLines.join('\n')
294
+ };
295
+ }