@agent-native/toolkit 0.201.1 → 0.202.0-nightly-20261006033736

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 (229) hide show
  1. package/agent-native.eject.json +39 -12
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +292 -23
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/agentkit/react/context.d.ts +5 -0
  6. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/context.js +5 -0
  8. package/dist/app/agentkit/react/context.js.map +1 -1
  9. package/dist/app/agentkit/react/styles.css +78 -3
  10. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  11. package/dist/app/auth/AuthPage.js +7 -14
  12. package/dist/app/auth/AuthPage.js.map +1 -1
  13. package/dist/app/auth/server.d.ts +6 -0
  14. package/dist/app/auth/server.d.ts.map +1 -0
  15. package/dist/app/auth/server.js +19 -0
  16. package/dist/app/auth/server.js.map +1 -0
  17. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  18. package/dist/app/chat/AgentKitAssistantChat.js +164 -83
  19. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  20. package/dist/app/chat/AgentSidebar.d.ts +2 -1
  21. package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
  22. package/dist/app/chat/AgentSidebar.js +2 -2
  23. package/dist/app/chat/AgentSidebar.js.map +1 -1
  24. package/dist/app/chat/agentkit-chat/guided-questions.d.ts.map +1 -1
  25. package/dist/app/chat/agentkit-chat/guided-questions.js +13 -3
  26. package/dist/app/chat/agentkit-chat/guided-questions.js.map +1 -1
  27. package/dist/app/chat/chat/surface-types.d.ts +2 -0
  28. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  29. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  30. package/dist/app/i18n/catalogs/ar-SA.js +41 -0
  31. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  32. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  33. package/dist/app/i18n/catalogs/de-DE.js +41 -0
  34. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  35. package/dist/app/i18n/catalogs/en-US.d.ts +41 -0
  36. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  37. package/dist/app/i18n/catalogs/en-US.js +41 -0
  38. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  39. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  40. package/dist/app/i18n/catalogs/es-ES.js +41 -0
  41. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  42. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  43. package/dist/app/i18n/catalogs/fr-FR.js +41 -0
  44. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  45. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  46. package/dist/app/i18n/catalogs/hi-IN.js +41 -0
  47. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  48. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  49. package/dist/app/i18n/catalogs/ja-JP.js +41 -0
  50. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  51. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  52. package/dist/app/i18n/catalogs/ko-KR.js +41 -0
  53. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  54. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  55. package/dist/app/i18n/catalogs/pt-BR.js +41 -0
  56. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  57. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  58. package/dist/app/i18n/catalogs/zh-CN.js +41 -0
  59. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  60. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  61. package/dist/app/i18n/catalogs/zh-TW.js +41 -0
  62. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  63. package/dist/app/shared/StarfieldBackground.d.ts +1 -6
  64. package/dist/app/shared/StarfieldBackground.d.ts.map +1 -1
  65. package/dist/app/shared/StarfieldBackground.js +1 -368
  66. package/dist/app/shared/StarfieldBackground.js.map +1 -1
  67. package/dist/app/shared/WaveBackground.d.ts +5 -0
  68. package/dist/app/shared/WaveBackground.d.ts.map +1 -0
  69. package/dist/app/shared/WaveBackground.js +34 -0
  70. package/dist/app/shared/WaveBackground.js.map +1 -0
  71. package/dist/app/shared/WebGlWaveBackground.d.ts +9 -0
  72. package/dist/app/shared/WebGlWaveBackground.d.ts.map +1 -0
  73. package/dist/app/shared/WebGlWaveBackground.js +433 -0
  74. package/dist/app/shared/WebGlWaveBackground.js.map +1 -0
  75. package/dist/app/shared/index.d.ts +1 -0
  76. package/dist/app/shared/index.d.ts.map +1 -1
  77. package/dist/app/shared/index.js +1 -0
  78. package/dist/app/shared/index.js.map +1 -1
  79. package/dist/app/shared/ocean/bloom-blur.wgsl +43 -0
  80. package/dist/app/shared/ocean/bloom-blur.wgsl.js +1 -0
  81. package/dist/app/shared/ocean/bloom-bright.wgsl +21 -0
  82. package/dist/app/shared/ocean/bloom-bright.wgsl.js +1 -0
  83. package/dist/app/shared/ocean/bloom-composite.wgsl +40 -0
  84. package/dist/app/shared/ocean/bloom-composite.wgsl.js +1 -0
  85. package/dist/app/shared/ocean/brand-colors.d.ts +9 -0
  86. package/dist/app/shared/ocean/brand-colors.d.ts.map +1 -0
  87. package/dist/app/shared/ocean/brand-colors.js +45 -0
  88. package/dist/app/shared/ocean/brand-colors.js.map +1 -0
  89. package/dist/app/shared/ocean/camera.d.ts +5 -0
  90. package/dist/app/shared/ocean/camera.d.ts.map +1 -0
  91. package/dist/app/shared/ocean/camera.js +24 -0
  92. package/dist/app/shared/ocean/camera.js.map +1 -0
  93. package/dist/app/shared/ocean/hero-ocean-background.d.ts +8 -0
  94. package/dist/app/shared/ocean/hero-ocean-background.d.ts.map +1 -0
  95. package/dist/app/shared/ocean/hero-ocean-background.js +186 -0
  96. package/dist/app/shared/ocean/hero-ocean-background.js.map +1 -0
  97. package/dist/app/shared/ocean/ifft-stage.wgsl +36 -0
  98. package/dist/app/shared/ocean/ifft-stage.wgsl.js +1 -0
  99. package/dist/app/shared/ocean/initial-spectrum.wgsl +36 -0
  100. package/dist/app/shared/ocean/initial-spectrum.wgsl.js +1 -0
  101. package/dist/app/shared/ocean/noise.wgsl +24 -0
  102. package/dist/app/shared/ocean/noise.wgsl.js +1 -0
  103. package/dist/app/shared/ocean/normal-foam.wgsl +32 -0
  104. package/dist/app/shared/ocean/normal-foam.wgsl.js +1 -0
  105. package/dist/app/shared/ocean/ocean-colors.d.ts +6 -0
  106. package/dist/app/shared/ocean/ocean-colors.d.ts.map +1 -0
  107. package/dist/app/shared/ocean/ocean-colors.js +6 -0
  108. package/dist/app/shared/ocean/ocean-colors.js.map +1 -0
  109. package/dist/app/shared/ocean/ocean-common.wgsl +15 -0
  110. package/dist/app/shared/ocean/ocean-common.wgsl.js +1 -0
  111. package/dist/app/shared/ocean/ocean-graph.d.ts +12 -0
  112. package/dist/app/shared/ocean/ocean-graph.d.ts.map +1 -0
  113. package/dist/app/shared/ocean/ocean-graph.js +16 -0
  114. package/dist/app/shared/ocean/ocean-graph.js.map +1 -0
  115. package/dist/app/shared/ocean/particles.wgsl +139 -0
  116. package/dist/app/shared/ocean/particles.wgsl.js +1 -0
  117. package/dist/app/shared/ocean/present.wgsl +40 -0
  118. package/dist/app/shared/ocean/present.wgsl.js +1 -0
  119. package/dist/app/shared/ocean/renderer.d.ts +66 -0
  120. package/dist/app/shared/ocean/renderer.d.ts.map +1 -0
  121. package/dist/app/shared/ocean/renderer.js +535 -0
  122. package/dist/app/shared/ocean/renderer.js.map +1 -0
  123. package/dist/app/shared/ocean/spectrum.wgsl +35 -0
  124. package/dist/app/shared/ocean/spectrum.wgsl.js +1 -0
  125. package/dist/app/shared/ocean/tuning.d.ts +48 -0
  126. package/dist/app/shared/ocean/tuning.d.ts.map +1 -0
  127. package/dist/app/shared/ocean/tuning.js +74 -0
  128. package/dist/app/shared/ocean/tuning.js.map +1 -0
  129. package/dist/app/shared/ocean/webgpu-support.d.ts +3 -0
  130. package/dist/app/shared/ocean/webgpu-support.d.ts.map +1 -0
  131. package/dist/app/shared/ocean/webgpu-support.js +13 -0
  132. package/dist/app/shared/ocean/webgpu-support.js.map +1 -0
  133. package/dist/app/sharing/AccessRequestApprovalPage.d.ts +23 -0
  134. package/dist/app/sharing/AccessRequestApprovalPage.d.ts.map +1 -0
  135. package/dist/app/sharing/AccessRequestApprovalPage.js +95 -0
  136. package/dist/app/sharing/AccessRequestApprovalPage.js.map +1 -0
  137. package/dist/app/sharing/AccessRequestsSection.d.ts +5 -0
  138. package/dist/app/sharing/AccessRequestsSection.d.ts.map +1 -0
  139. package/dist/app/sharing/AccessRequestsSection.js +47 -0
  140. package/dist/app/sharing/AccessRequestsSection.js.map +1 -0
  141. package/dist/app/sharing/ResourceAccessScreen.d.ts +53 -0
  142. package/dist/app/sharing/ResourceAccessScreen.d.ts.map +1 -0
  143. package/dist/app/sharing/ResourceAccessScreen.js +132 -0
  144. package/dist/app/sharing/ResourceAccessScreen.js.map +1 -0
  145. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  146. package/dist/app/sharing/ShareButton.js +4 -2
  147. package/dist/app/sharing/ShareButton.js.map +1 -1
  148. package/dist/app/sharing/ShareDialog.d.ts.map +1 -1
  149. package/dist/app/sharing/ShareDialog.js +7 -5
  150. package/dist/app/sharing/ShareDialog.js.map +1 -1
  151. package/dist/app/sharing/index.d.ts +2 -0
  152. package/dist/app/sharing/index.d.ts.map +1 -1
  153. package/dist/app/sharing/index.js +2 -0
  154. package/dist/app/sharing/index.js.map +1 -1
  155. package/dist/app/styles/agent-native.css +21 -1
  156. package/dist/app/styles/tokens/agent-kit.css +3 -1
  157. package/dist/composer/AgentSuggestionBar.d.ts +1 -1
  158. package/dist/composer/AgentSuggestionBar.d.ts.map +1 -1
  159. package/dist/composer/AgentSuggestionBar.js +75 -6
  160. package/dist/composer/AgentSuggestionBar.js.map +1 -1
  161. package/package.json +14 -4
  162. package/src/app/agentkit/react/components.tsx +492 -60
  163. package/src/app/agentkit/react/context.tsx +10 -0
  164. package/src/app/agentkit/react/lifecycle.spec.tsx +487 -1
  165. package/src/app/agentkit/react/styles.css +78 -3
  166. package/src/app/auth/AuthPage.spec.tsx +11 -7
  167. package/src/app/auth/AuthPage.tsx +16 -35
  168. package/src/app/auth/server.spec.ts +68 -0
  169. package/src/app/auth/server.ts +29 -0
  170. package/src/app/chat/AgentKitAssistantChat.spec.tsx +287 -290
  171. package/src/app/chat/AgentKitAssistantChat.tsx +219 -116
  172. package/src/app/chat/AgentSidebar.tsx +3 -0
  173. package/src/app/chat/agentkit-chat/guided-questions.flow.spec.tsx +85 -0
  174. package/src/app/chat/agentkit-chat/guided-questions.tsx +12 -5
  175. package/src/app/chat/chat/surface-types.ts +2 -0
  176. package/src/app/i18n/catalogs/ar-SA.ts +44 -0
  177. package/src/app/i18n/catalogs/de-DE.ts +55 -0
  178. package/src/app/i18n/catalogs/en-US.ts +46 -0
  179. package/src/app/i18n/catalogs/es-ES.ts +53 -0
  180. package/src/app/i18n/catalogs/fr-FR.ts +53 -0
  181. package/src/app/i18n/catalogs/hi-IN.ts +47 -0
  182. package/src/app/i18n/catalogs/ja-JP.ts +54 -0
  183. package/src/app/i18n/catalogs/ko-KR.ts +50 -0
  184. package/src/app/i18n/catalogs/pt-BR.ts +52 -0
  185. package/src/app/i18n/catalogs/zh-CN.ts +42 -0
  186. package/src/app/i18n/catalogs/zh-TW.ts +43 -0
  187. package/src/app/shared/StarfieldBackground.tsx +4 -423
  188. package/src/app/shared/WaveBackground.spec.tsx +104 -0
  189. package/src/app/shared/WaveBackground.tsx +60 -0
  190. package/src/app/shared/WebGlWaveBackground.spec.tsx +106 -0
  191. package/src/app/shared/WebGlWaveBackground.tsx +499 -0
  192. package/src/app/shared/index.ts +1 -0
  193. package/src/app/shared/ocean/bloom-blur.wgsl +43 -0
  194. package/src/app/shared/ocean/bloom-bright.wgsl +21 -0
  195. package/src/app/shared/ocean/bloom-composite.wgsl +40 -0
  196. package/src/app/shared/ocean/brand-colors.ts +49 -0
  197. package/src/app/shared/ocean/camera.ts +28 -0
  198. package/src/app/shared/ocean/hero-ocean-background.spec.tsx +369 -0
  199. package/src/app/shared/ocean/hero-ocean-background.tsx +238 -0
  200. package/src/app/shared/ocean/ifft-stage.wgsl +36 -0
  201. package/src/app/shared/ocean/initial-spectrum.wgsl +36 -0
  202. package/src/app/shared/ocean/noise.wgsl +24 -0
  203. package/src/app/shared/ocean/normal-foam.wgsl +32 -0
  204. package/src/app/shared/ocean/ocean-colors.ts +11 -0
  205. package/src/app/shared/ocean/ocean-common.wgsl +15 -0
  206. package/src/app/shared/ocean/ocean-graph.ts +29 -0
  207. package/src/app/shared/ocean/particles.wgsl +139 -0
  208. package/src/app/shared/ocean/present.wgsl +40 -0
  209. package/src/app/shared/ocean/renderer.spec.ts +100 -0
  210. package/src/app/shared/ocean/renderer.ts +693 -0
  211. package/src/app/shared/ocean/spectrum.wgsl +35 -0
  212. package/src/app/shared/ocean/tuning.ts +131 -0
  213. package/src/app/shared/ocean/webgpu-support.ts +12 -0
  214. package/src/app/sharing/AccessRequestApprovalPage.spec.tsx +276 -0
  215. package/src/app/sharing/AccessRequestApprovalPage.tsx +307 -0
  216. package/src/app/sharing/AccessRequestsSection.tsx +163 -0
  217. package/src/app/sharing/ResourceAccessScreen.spec.tsx +235 -0
  218. package/src/app/sharing/ResourceAccessScreen.tsx +331 -0
  219. package/src/app/sharing/ShareButton.spec.tsx +171 -5
  220. package/src/app/sharing/ShareButton.tsx +15 -2
  221. package/src/app/sharing/ShareDialog.spec.tsx +71 -2
  222. package/src/app/sharing/ShareDialog.tsx +24 -2
  223. package/src/app/sharing/index.ts +2 -0
  224. package/src/app/styles/agent-native.css +21 -1
  225. package/src/app/styles/agent-native.spec.ts +16 -3
  226. package/src/app/styles/tokens/agent-kit.css +3 -1
  227. package/src/composer/AgentSuggestionBar.overflow.spec.tsx +277 -0
  228. package/src/composer/AgentSuggestionBar.tsx +113 -8
  229. package/src/wgsl-env.d.ts +6 -0
@@ -0,0 +1,693 @@
1
+ import {
2
+ clock,
3
+ draw,
4
+ effect,
5
+ frame,
6
+ frameLoop,
7
+ sampler,
8
+ surface,
9
+ target,
10
+ type Draw,
11
+ type Effect,
12
+ type Frame,
13
+ type FrameLoopHandle,
14
+ type Gpu,
15
+ type ShaderSource,
16
+ type Surface,
17
+ type Target,
18
+ } from "vgpu";
19
+
20
+ import bloomBlurWgsl from "./bloom-blur.wgsl?raw";
21
+ import bloomBrightWgsl from "./bloom-bright.wgsl?raw";
22
+ import bloomCompositeWgsl from "./bloom-composite.wgsl?raw";
23
+ import { oceanCamera } from "./camera.js";
24
+ import ifftStageSource from "./ifft-stage.wgsl?raw";
25
+ import initialSpectrumSource from "./initial-spectrum.wgsl?raw";
26
+ import noiseWgsl from "./noise.wgsl?raw";
27
+ import normalFoamSource from "./normal-foam.wgsl?raw";
28
+ import { DEFAULT_OCEAN_COLORS, type OceanColors } from "./ocean-colors.js";
29
+ import oceanCommonWgsl from "./ocean-common.wgsl?raw";
30
+ import {
31
+ createIfftStageTable,
32
+ OCEAN_RESOLUTION,
33
+ type IfftStage,
34
+ type SimulationTargetName,
35
+ } from "./ocean-graph.js";
36
+ import particlesWgsl from "./particles.wgsl?raw";
37
+ import presentWgsl from "./present.wgsl?raw";
38
+ import spectrumSource from "./spectrum.wgsl?raw";
39
+ import { gaussianCoefficients, OCEAN_TUNING } from "./tuning.js";
40
+
41
+ const ifftStageWgsl = `${oceanCommonWgsl}\n${ifftStageSource}`;
42
+ const initialSpectrumWgsl = `${oceanCommonWgsl}\n${initialSpectrumSource}`;
43
+ const normalFoamWgsl = `${oceanCommonWgsl}\n${normalFoamSource}`;
44
+ const spectrumWgsl = `${oceanCommonWgsl}\n${spectrumSource}`;
45
+
46
+ type Output = Surface | Target;
47
+
48
+ interface RendererOptions {
49
+ readonly canvas: HTMLCanvasElement;
50
+ readonly colors?: OceanColors;
51
+ readonly fps?: number;
52
+ readonly onError?: (error: unknown) => void;
53
+ }
54
+
55
+ type PointerTarget = readonly [number, number, number];
56
+
57
+ const POINTER_POSITION_EASING = 0.22;
58
+ const POINTER_STRENGTH_EASING = 0.16;
59
+
60
+ const SIM_FORMAT: GPUTextureFormat = "rgba32float";
61
+ const HDR_FORMAT: GPUTextureFormat = "rgba16float";
62
+ const TRANSPARENT = [0, 0, 0, 0] as const;
63
+
64
+ export function createRenderer({
65
+ canvas,
66
+ colors,
67
+ fps,
68
+ onError,
69
+ }: RendererOptions) {
70
+ let disposed = false;
71
+ let currentColors: OceanColors = colors ?? DEFAULT_OCEAN_COLORS;
72
+ let pointer: [number, number, number] = [0, 0, 0];
73
+ let pointerTarget: [number, number, number] = [0, 0, 0];
74
+ let loop: FrameLoopHandle | undefined;
75
+ let paused = false;
76
+ let failed = false;
77
+ let rebuilding = false;
78
+ let resizePending = false;
79
+ let releaseErrorListener: (() => void) | undefined;
80
+
81
+ let signalFirstFrame: () => void = () => {};
82
+ let signalFirstFrameFailed: (error: unknown) => void = () => {};
83
+ const firstFrame = new Promise<void>((resolve, reject) => {
84
+ signalFirstFrame = resolve;
85
+ signalFirstFrameFailed = reject;
86
+ });
87
+ firstFrame.catch(() => {});
88
+ let drewOnce = false;
89
+ let gpu: Gpu | undefined;
90
+ let output: Surface | undefined;
91
+ let graph: OceanGraph | undefined;
92
+ let unsubscribeResize: (() => void) | undefined;
93
+ let resizeFrame = 0;
94
+ let resizeGeneration = 0;
95
+
96
+ function dispose(): void {
97
+ if (disposed) return;
98
+ disposed = true;
99
+ resizeGeneration++;
100
+ runCleanups([
101
+ () => {
102
+ if (resizeFrame) cancelAnimationFrame(resizeFrame);
103
+ },
104
+ () => loop?.stop(),
105
+ () => releaseErrorListener?.(),
106
+ () => unsubscribeResize?.(),
107
+ () => gpu?.dispose(),
108
+ ]);
109
+ }
110
+
111
+ function fail(error: unknown): never | void {
112
+ const first = !failed;
113
+ failed = true;
114
+ try {
115
+ dispose();
116
+ // The original failure is rethrown or handed to onError immediately
117
+ // below; a teardown error raised here would replace that real cause.
118
+ // coercion-ok: the caller still receives the failure that started this.
119
+ } catch {
120
+ // coercion-ok: teardown errors must not replace the original renderer failure.
121
+ }
122
+ if (first) signalFirstFrameFailed(error);
123
+ if (!onError) throw error;
124
+ if (first) onError(error);
125
+ }
126
+
127
+ const rebuild = async (generation: number) => {
128
+ if (disposed || !gpu || !output || !graph) return;
129
+ if (sameSize(graph.scene.size, output.size)) return;
130
+ const next = await createGraph(
131
+ gpu,
132
+ output,
133
+ `fft-ocean-resize-${generation}`,
134
+ currentColors,
135
+ );
136
+ if (disposed) return;
137
+ if (generation !== resizeGeneration) {
138
+ try {
139
+ destroyGraph(next);
140
+ } catch {
141
+ // coercion-ok: a newer resize already owns the live graph, so freeing
142
+ // this superseded one is best-effort -- failing wastes GPU memory but
143
+ // cannot corrupt the frame on screen.
144
+ }
145
+ return;
146
+ }
147
+ const previous = graph;
148
+ graph = next;
149
+ destroyGraph(previous);
150
+ };
151
+
152
+ const scheduleResize = () => {
153
+ if (disposed) return;
154
+ if (rebuilding) {
155
+ resizePending = true;
156
+ return;
157
+ }
158
+ if (resizeFrame) return;
159
+ const generation = ++resizeGeneration;
160
+ resizeFrame = requestAnimationFrame(async () => {
161
+ resizeFrame = 0;
162
+ rebuilding = true;
163
+ try {
164
+ await rebuild(generation);
165
+ } catch (error) {
166
+ if (!disposed && generation === resizeGeneration) fail(error);
167
+ } finally {
168
+ rebuilding = false;
169
+ }
170
+ if (resizePending && !disposed) {
171
+ resizePending = false;
172
+ scheduleResize();
173
+ }
174
+ });
175
+ };
176
+
177
+ const initialize = async () => {
178
+ const { init } = await import("vgpu");
179
+ if (disposed) return;
180
+ const nextGpu = await init();
181
+ if (disposed) {
182
+ nextGpu.dispose();
183
+ return;
184
+ }
185
+
186
+ gpu = nextGpu;
187
+ output = surface(gpu, canvas, { dpr: [1, 1.6] });
188
+ const nextGraph = await createGraph(
189
+ gpu,
190
+ output,
191
+ "fft-ocean-live",
192
+ currentColors,
193
+ );
194
+ if (disposed) {
195
+ try {
196
+ destroyGraph(nextGraph);
197
+ } catch {
198
+ // coercion-ok: Cleanup is best-effort after the renderer device is disposed.
199
+ }
200
+ return;
201
+ }
202
+ graph = nextGraph;
203
+
204
+ unsubscribeResize = output.onResize(scheduleResize);
205
+
206
+ releaseErrorListener = nextGpu.onError((error) => {
207
+ if (!disposed) fail(error);
208
+ });
209
+ void nextGpu.gpu.lost.then((info) => {
210
+ if (!disposed) fail(new Error(`WebGPU device lost: ${info.reason}`));
211
+ });
212
+
213
+ const time = clock(gpu);
214
+ loop = frameLoop(
215
+ gpu,
216
+ (currentFrame) => {
217
+ if (disposed || paused || !graph || !output) return;
218
+ try {
219
+ setDynamics(graph, time.time * OCEAN_TUNING.simulation.timeScale);
220
+ updatePointer(graph.particles, time.time);
221
+ renderGraph(currentFrame, graph, output);
222
+ if (!drewOnce) {
223
+ drewOnce = true;
224
+ signalFirstFrame();
225
+ }
226
+ } catch (error) {
227
+ fail(error);
228
+ }
229
+ },
230
+ fps === undefined ? undefined : { fps },
231
+ );
232
+ };
233
+
234
+ const ready = initialize().catch((error: unknown) => {
235
+ if (!disposed) fail(error);
236
+ });
237
+
238
+ function setPaused(next: boolean): void {
239
+ paused = next;
240
+ }
241
+
242
+ function updatePointer(particles: Draw, timeSeconds: number): void {
243
+ pointer[0] += (pointerTarget[0] - pointer[0]) * POINTER_POSITION_EASING;
244
+ pointer[1] += (pointerTarget[1] - pointer[1]) * POINTER_POSITION_EASING;
245
+ pointer[2] += (pointerTarget[2] - pointer[2]) * POINTER_STRENGTH_EASING;
246
+ if (pointer[2] < 0.001 && pointerTarget[2] === 0) pointer[2] = 0;
247
+
248
+ particles.set({
249
+ u: { cursor: [pointer[0], pointer[1], pointer[2], timeSeconds] },
250
+ });
251
+ }
252
+
253
+ function setPointer(next: PointerTarget): void {
254
+ if (disposed) return;
255
+ pointerTarget = [next[0], next[1], next[2]];
256
+ }
257
+
258
+ function setColors(next: OceanColors): void {
259
+ if (disposed) return;
260
+ currentColors = next;
261
+ if (graph) setPresentColors(graph, next);
262
+ }
263
+
264
+ return { ready, firstFrame, dispose, setColors, setPaused, setPointer };
265
+ }
266
+
267
+ export type OceanRenderer = ReturnType<typeof createRenderer>;
268
+
269
+ export async function createGraph(
270
+ gpu: Gpu,
271
+ output: Output,
272
+ label: string,
273
+ colors: OceanColors = DEFAULT_OCEAN_COLORS,
274
+ ): Promise<OceanGraph> {
275
+ const ownedTargets: Target[] = [];
276
+ try {
277
+ const graph = buildGraph(gpu, output, label, colors, (value) => {
278
+ ownedTargets.push(value);
279
+ return value;
280
+ });
281
+ await prewarm(graph, output);
282
+ return graph;
283
+ } catch (error) {
284
+ try {
285
+ destroyTargets(ownedTargets);
286
+ } catch {
287
+ // Partial-allocation cleanup must not replace the construction failure.
288
+ }
289
+ throw error;
290
+ }
291
+ }
292
+
293
+ function buildGraph(
294
+ gpu: Gpu,
295
+ output: Output,
296
+ label: string,
297
+ colors: OceanColors,
298
+ own: (value: Target) => Target,
299
+ ) {
300
+ const resolution = OCEAN_RESOLUTION;
301
+ const createTarget = (
302
+ name: string,
303
+ size: readonly [number, number],
304
+ format: GPUTextureFormat,
305
+ ) => own(target(gpu, { size, format, label: `${label}-${name}` }));
306
+ const simulationTarget = (name: string) =>
307
+ createTarget(name, [resolution, resolution], SIM_FORMAT);
308
+ const simulation = {
309
+ noise: simulationTarget("noise"),
310
+ h0: simulationTarget("h0"),
311
+ spectrum: simulationTarget("spectrum"),
312
+ ping: simulationTarget("ping"),
313
+ pong: simulationTarget("pong"),
314
+ normalFoam: simulationTarget("normal-foam"),
315
+ };
316
+ const sizes = bloomSizes(output.size);
317
+ const scene = createTarget("scene", normalizedSize(output.size), HDR_FORMAT);
318
+ const bright = createTarget("bright", sizes[0]!, HDR_FORMAT);
319
+ const composite = createTarget("composite", sizes[0]!, HDR_FORMAT);
320
+ const linearSampler = sampler(gpu, {
321
+ minFilter: "linear",
322
+ magFilter: "linear",
323
+ });
324
+
325
+ const noiseEffect = configuredEffect(gpu, noiseWgsl, `${label}-noise`);
326
+ const initialSpectrum = configuredEffect(
327
+ gpu,
328
+ initialSpectrumWgsl,
329
+ `${label}-initial-spectrum`,
330
+ {
331
+ u: {
332
+ resolution,
333
+ size: OCEAN_TUNING.simulation.oceanSize,
334
+ windSpeed: OCEAN_TUNING.simulation.windSpeed,
335
+ windAngle: OCEAN_TUNING.simulation.windAngle,
336
+ amplitude: OCEAN_TUNING.simulation.amplitude,
337
+ },
338
+ u_noise: simulation.noise,
339
+ },
340
+ );
341
+ const evolveSpectrum = configuredEffect(
342
+ gpu,
343
+ spectrumWgsl,
344
+ `${label}-spectrum`,
345
+ {
346
+ u: {
347
+ resolution,
348
+ size: OCEAN_TUNING.simulation.oceanSize,
349
+ time: 0,
350
+ choppiness: OCEAN_TUNING.simulation.choppiness,
351
+ },
352
+ u_initialSpectrum: simulation.h0,
353
+ },
354
+ );
355
+
356
+ const simulationTargets: Record<SimulationTargetName, Target> = {
357
+ spectrum: simulation.spectrum,
358
+ ping: simulation.ping,
359
+ pong: simulation.pong,
360
+ };
361
+ const ifft = createIfftStageTable().map((spec: IfftStage) => ({
362
+ spec,
363
+ effect: configuredEffect(
364
+ gpu,
365
+ ifftStageWgsl,
366
+ `${label}-ifft-${spec.index}-${spec.horizontal ? "h" : "v"}`,
367
+ {
368
+ u: {
369
+ resolution,
370
+ subtransformSize: spec.subtransformSize,
371
+ horizontal: spec.horizontal ? 1 : 0,
372
+ },
373
+ u_input: simulationTargets[spec.input],
374
+ },
375
+ ),
376
+ output: simulationTargets[spec.output],
377
+ }));
378
+ const displacement = ifft[ifft.length - 1]!.output;
379
+ const normals = configuredEffect(
380
+ gpu,
381
+ normalFoamWgsl,
382
+ `${label}-normal-foam`,
383
+ {
384
+ u: {
385
+ resolution,
386
+ worldSize: OCEAN_TUNING.simulation.worldSize,
387
+ displacementScale: OCEAN_TUNING.simulation.displacementScale,
388
+ foamThreshold: OCEAN_TUNING.simulation.foamThreshold,
389
+ },
390
+ u_displacement: displacement,
391
+ },
392
+ );
393
+ const particles = draw(gpu, {
394
+ shader: particlesWgsl,
395
+ vertices: 6,
396
+ instances: resolution * resolution,
397
+ blend: {
398
+ color: { src: "src-alpha", dst: "one" },
399
+ alpha: { src: "one", dst: "one" },
400
+ },
401
+ label: `${label}-particles`,
402
+ }).set({
403
+ u_displacement: displacement,
404
+ u_normalFoam: simulation.normalFoam,
405
+ });
406
+ setParticleConstants(particles, output);
407
+ const brightEffect = configuredEffect(
408
+ gpu,
409
+ bloomBrightWgsl,
410
+ `${label}-bloom-bright`,
411
+ {
412
+ uniforms: {
413
+ luminosityThreshold: OCEAN_TUNING.bloom.threshold,
414
+ smoothWidth: OCEAN_TUNING.bloom.smoothWidth,
415
+ _pad0: [0, 0],
416
+ },
417
+ tDiffuse: scene,
418
+ linearSampler,
419
+ },
420
+ );
421
+
422
+ let bloomInput = bright;
423
+ const levels = sizes.map((size, index) => {
424
+ const horizontal = createTarget(`bloom-h${index}`, size, HDR_FORMAT);
425
+ const vertical = createTarget(`bloom-v${index}`, size, HDR_FORMAT);
426
+ const radius = OCEAN_TUNING.bloom.kernelRadii[index]!;
427
+ const horizontalEffect = makeBlur(
428
+ gpu,
429
+ `${label}-blur-h${index}`,
430
+ bloomInput,
431
+ horizontal,
432
+ linearSampler,
433
+ [1, 0],
434
+ radius,
435
+ );
436
+ const verticalEffect = makeBlur(
437
+ gpu,
438
+ `${label}-blur-v${index}`,
439
+ horizontal,
440
+ vertical,
441
+ linearSampler,
442
+ [0, 1],
443
+ radius,
444
+ );
445
+ bloomInput = vertical;
446
+ return { horizontal, vertical, horizontalEffect, verticalEffect };
447
+ });
448
+ const compositeEffect = configuredEffect(
449
+ gpu,
450
+ bloomCompositeWgsl,
451
+ `${label}-bloom-composite`,
452
+ {
453
+ uniforms: {
454
+ bloomStrength: OCEAN_TUNING.bloom.strength,
455
+ bloomRadius: OCEAN_TUNING.bloom.radius,
456
+ bloomFactors0: [1, 0.8, 0.6, 0.4],
457
+ bloomFactors1: [0.2, 0, 0, 0],
458
+ _pad0: [0, 0],
459
+ },
460
+ blurTexture1: levels[0]!.vertical,
461
+ blurTexture2: levels[1]!.vertical,
462
+ blurTexture3: levels[2]!.vertical,
463
+ blurTexture4: levels[3]!.vertical,
464
+ blurTexture5: levels[4]!.vertical,
465
+ linearSampler,
466
+ },
467
+ );
468
+ const present = configuredEffect(gpu, presentWgsl, `${label}-present`, {
469
+ uniforms: presentUniforms(colors),
470
+ sceneHDR: scene,
471
+ bloomTexture: composite,
472
+ linearSampler,
473
+ });
474
+ return {
475
+ simulation,
476
+ scene,
477
+ bloom: { bright, composite, levels },
478
+ effects: {
479
+ noise: noiseEffect,
480
+ initialSpectrum,
481
+ evolveSpectrum,
482
+ normals,
483
+ bright: brightEffect,
484
+ composite: compositeEffect,
485
+ present,
486
+ },
487
+ ifft,
488
+ particles,
489
+ needsInitialSpectrum: true,
490
+ };
491
+ }
492
+
493
+ export type OceanGraph = ReturnType<typeof buildGraph>;
494
+
495
+ function configuredEffect(
496
+ gpu: Gpu,
497
+ shader: string | ShaderSource,
498
+ label: string,
499
+ bindings?: Record<string, unknown>,
500
+ ): Effect {
501
+ const configured = effect(gpu, shader, { label });
502
+ return bindings ? configured.set(bindings) : configured;
503
+ }
504
+
505
+ function makeBlur(
506
+ gpu: Gpu,
507
+ label: string,
508
+ source: Target,
509
+ output: Target,
510
+ linearSampler: GPUSampler,
511
+ direction: readonly [number, number],
512
+ kernelRadius: number,
513
+ ): Effect {
514
+ const blur = effect(gpu, bloomBlurWgsl, { label });
515
+ const coefficients = gaussianCoefficients(kernelRadius);
516
+ blur.set({
517
+ uniforms: {
518
+ direction,
519
+ invSize: output.texelSize,
520
+ gaussianCoefficients0: coefficients.slice(0, 4),
521
+ gaussianCoefficients1: coefficients.slice(4, 8),
522
+ gaussianCoefficients2: coefficients.slice(8, 12),
523
+ gaussianCoefficients3: coefficients.slice(12, 16),
524
+ gaussianCoefficients4: coefficients.slice(16, 20),
525
+ gaussianCoefficients5: coefficients.slice(20, 24),
526
+ },
527
+ colorTexture: source,
528
+ linearSampler,
529
+ });
530
+ return blur;
531
+ }
532
+
533
+ async function prewarm(graph: OceanGraph, output: Output): Promise<void> {
534
+ const results = await Promise.allSettled([
535
+ graph.effects.noise.compile(graph.simulation.noise),
536
+ graph.effects.initialSpectrum.compile(graph.simulation.h0),
537
+ graph.effects.evolveSpectrum.compile(graph.simulation.spectrum),
538
+ ...graph.ifft.map(({ effect, output }) => effect.compile(output)),
539
+ graph.effects.normals.compile(graph.simulation.normalFoam),
540
+ graph.particles.compile(graph.scene),
541
+ graph.effects.bright.compile(graph.bloom.bright),
542
+ ...graph.bloom.levels.flatMap((level) => [
543
+ level.horizontalEffect.compile(level.horizontal),
544
+ level.verticalEffect.compile(level.vertical),
545
+ ]),
546
+ graph.effects.composite.compile(graph.bloom.composite),
547
+ graph.effects.present.compile({ colors: [output.format] }),
548
+ ]);
549
+ const failure = results.find(
550
+ (result): result is PromiseRejectedResult => result.status === "rejected",
551
+ );
552
+ if (failure) throw failure.reason;
553
+ }
554
+
555
+ function presentUniforms(colors: OceanColors) {
556
+ return {
557
+ fgColor: [...colors.fg, 1] as const,
558
+ bgColor: [...colors.bg, 1] as const,
559
+ brightness: OCEAN_TUNING.present.brightness,
560
+ _pad: [0, 0, 0] as const,
561
+ };
562
+ }
563
+
564
+ export function setPresentColors(graph: OceanGraph, colors: OceanColors): void {
565
+ graph.effects.present.set({ uniforms: presentUniforms(colors) });
566
+ }
567
+
568
+ function setDynamics(graph: OceanGraph, timeSeconds: number): void {
569
+ graph.effects.evolveSpectrum.set({
570
+ u: { time: timeSeconds * OCEAN_TUNING.simulation.spectrumTimeScale },
571
+ });
572
+ }
573
+
574
+ function setParticleConstants(particles: Draw, output: Output): void {
575
+ const camera = oceanCamera(output.size);
576
+ const tuning = OCEAN_TUNING;
577
+ particles.set({
578
+ u: {
579
+ view: camera.view,
580
+ projection: camera.projection,
581
+ viewport: [output.size[0], output.size[1], 1, OCEAN_RESOLUTION],
582
+ world: [
583
+ tuning.simulation.worldSize,
584
+ tuning.simulation.displacementScale,
585
+ tuning.particles.pointSize,
586
+ 0,
587
+ ],
588
+ fade: [
589
+ tuning.particles.fadeNear,
590
+ tuning.particles.fadeFar,
591
+ tuning.particles.fadePower,
592
+ 0,
593
+ ],
594
+ oceanColor: tuning.particles.oceanColor,
595
+ neonColor: tuning.particles.neonColor,
596
+ foamColor: tuning.particles.foamColor,
597
+ cursor: [0, 0, 0, 0],
598
+ },
599
+ });
600
+ }
601
+
602
+ export function renderAt(
603
+ gpu: Gpu,
604
+ graph: OceanGraph,
605
+ output: Target,
606
+ time: number,
607
+ ): void {
608
+ setDynamics(graph, time);
609
+ frame(gpu, (currentFrame) => renderGraph(currentFrame, graph, output));
610
+ }
611
+
612
+ export function renderGraph(
613
+ currentFrame: Frame,
614
+ graph: OceanGraph,
615
+ output: Output,
616
+ ): void {
617
+ const pass = (target: Output, drawable: Draw | Effect) =>
618
+ currentFrame.pass({ target, clear: TRANSPARENT }, (encoder) =>
619
+ encoder.draw(drawable),
620
+ );
621
+ if (graph.needsInitialSpectrum) {
622
+ pass(graph.simulation.noise, graph.effects.noise);
623
+ pass(graph.simulation.h0, graph.effects.initialSpectrum);
624
+ graph.needsInitialSpectrum = false;
625
+ }
626
+ pass(graph.simulation.spectrum, graph.effects.evolveSpectrum);
627
+ for (const stage of graph.ifft) {
628
+ pass(stage.output, stage.effect);
629
+ }
630
+ pass(graph.simulation.normalFoam, graph.effects.normals);
631
+ pass(graph.scene, graph.particles);
632
+ pass(graph.bloom.bright, graph.effects.bright);
633
+ for (const level of graph.bloom.levels) {
634
+ pass(level.horizontal, level.horizontalEffect);
635
+ pass(level.vertical, level.verticalEffect);
636
+ }
637
+ pass(graph.bloom.composite, graph.effects.composite);
638
+ pass(output, graph.effects.present);
639
+ }
640
+
641
+ export function bloomSizes(
642
+ size: readonly [number, number],
643
+ ): [number, number][] {
644
+ let width = Math.max(1, Math.round(size[0] / 2));
645
+ let height = Math.max(1, Math.round(size[1] / 2));
646
+ return Array.from({ length: OCEAN_TUNING.bloom.levels }, () => {
647
+ const level: [number, number] = [width, height];
648
+ width = Math.max(1, Math.round(width / 2));
649
+ height = Math.max(1, Math.round(height / 2));
650
+ return level;
651
+ });
652
+ }
653
+
654
+ export function destroyGraph(graph: OceanGraph): void {
655
+ destroyTargets([
656
+ ...Object.values(graph.simulation),
657
+ graph.scene,
658
+ graph.bloom.bright,
659
+ graph.bloom.composite,
660
+ ...graph.bloom.levels.flatMap((level) => [
661
+ level.horizontal,
662
+ level.vertical,
663
+ ]),
664
+ ]);
665
+ }
666
+
667
+ function destroyTargets(targets: readonly Target[]): void {
668
+ runCleanups(
669
+ [...targets].reverse().map((value) => () => value.color.destroy()),
670
+ );
671
+ }
672
+
673
+ function runCleanups(cleanups: readonly (() => void)[]): void {
674
+ let firstError: unknown;
675
+ let failed = false;
676
+ for (const cleanup of cleanups) {
677
+ try {
678
+ cleanup();
679
+ } catch (error) {
680
+ if (!failed) firstError = error;
681
+ failed = true;
682
+ }
683
+ }
684
+ if (failed) throw firstError;
685
+ }
686
+
687
+ function normalizedSize(size: readonly [number, number]): [number, number] {
688
+ return [Math.max(1, Math.floor(size[0])), Math.max(1, Math.floor(size[1]))];
689
+ }
690
+
691
+ function sameSize(a: readonly number[], b: readonly number[]): boolean {
692
+ return a[0] === b[0] && a[1] === b[1];
693
+ }
@@ -0,0 +1,35 @@
1
+ struct SpectrumUniforms {
2
+ resolution: f32,
3
+ size: f32,
4
+ time: f32,
5
+ choppiness: f32,
6
+ };
7
+ @group(0) @binding(0) var<uniform> u: SpectrumUniforms;
8
+ @group(0) @binding(1) var u_initialSpectrum: texture_2d<f32>;
9
+
10
+ @fragment fn fs_main(@builtin(position) position: vec4f) -> @location(0) vec4f {
11
+ let coord = position.xy - vec2f(0.5);
12
+ let n = select(coord.x - u.resolution, coord.x, coord.x < u.resolution * 0.5);
13
+ let m = select(coord.y - u.resolution, coord.y, coord.y < u.resolution * 0.5);
14
+ let k = (2.0 * PI / u.size) * vec2f(n, m);
15
+ let kLen = length(k);
16
+
17
+ let h0 = textureLoad(u_initialSpectrum, vec2u(coord), 0);
18
+ let w = sqrt(G * kLen) * u.time;
19
+ let expp = vec2f(cos(w), sin(w));
20
+ let expm = vec2f(cos(w), -sin(w));
21
+
22
+ let h = cmul(h0.rg, expp) + cmul(h0.ba, expm);
23
+
24
+
25
+
26
+ var kn = vec2f(0.0);
27
+ if (kLen > 0.0) { kn = k / kLen; }
28
+ let negI_h = vec2f(h.y, -h.x);
29
+ let hx = negI_h * kn.x * u.choppiness;
30
+ let hz = negI_h * kn.y * u.choppiness;
31
+
32
+ let cA = vec2f(hx.x - h.y, hx.y + h.x);
33
+ let cB = hz;
34
+ return vec4f(cA, cB);
35
+ }