@supersoniks/concorde 4.9.97-visual-stack.0 → 4.9.98-visual-stack.1

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 (171) hide show
  1. package/build-infos.json +1 -1
  2. package/concorde-3d.bundle.js +201 -152
  3. package/concorde-3d.es.js +14621 -13823
  4. package/concorde-core.bundle.js +1 -1
  5. package/concorde-core.es.js +1 -1
  6. package/concorde-hugging-face-infer.bundle.js +1 -1
  7. package/concorde-hugging-face-infer.es.js +1 -1
  8. package/concorde-shader.bundle.js +31 -20
  9. package/concorde-shader.es.js +3236 -2865
  10. package/concorde-webgpu.bundle.js +43 -32
  11. package/concorde-webgpu.es.js +2977 -2618
  12. package/dist/concorde-3d.bundle.js +201 -152
  13. package/dist/concorde-3d.es.js +14621 -13823
  14. package/dist/concorde-core.bundle.js +1 -1
  15. package/dist/concorde-core.es.js +1 -1
  16. package/dist/concorde-hugging-face-infer.bundle.js +1 -1
  17. package/dist/concorde-hugging-face-infer.es.js +1 -1
  18. package/dist/concorde-shader.bundle.js +31 -20
  19. package/dist/concorde-shader.es.js +3236 -2865
  20. package/dist/concorde-webgpu.bundle.js +43 -32
  21. package/dist/concorde-webgpu.es.js +2977 -2618
  22. package/dist/js/core/components/functional/3d/3d.js +100 -2
  23. package/dist/js/core/components/functional/3d/3d.js.map +1 -1
  24. package/dist/js/core/components/functional/3d/constants.js +3 -0
  25. package/dist/js/core/components/functional/3d/constants.js.map +1 -1
  26. package/dist/js/core/components/functional/3d/ensure.js +6 -3
  27. package/dist/js/core/components/functional/3d/ensure.js.map +1 -1
  28. package/dist/js/core/components/functional/3d/form-params.js +2 -0
  29. package/dist/js/core/components/functional/3d/form-params.js.map +1 -1
  30. package/dist/js/core/components/functional/3d/frame-source.js +22 -0
  31. package/dist/js/core/components/functional/3d/frame-source.js.map +1 -0
  32. package/dist/js/core/components/functional/3d/life.js +2 -1
  33. package/dist/js/core/components/functional/3d/life.js.map +1 -1
  34. package/dist/js/core/components/functional/3d/out-publish.js +3 -2
  35. package/dist/js/core/components/functional/3d/out-publish.js.map +1 -1
  36. package/dist/js/core/components/functional/3d/playback.js +6 -1
  37. package/dist/js/core/components/functional/3d/playback.js.map +1 -1
  38. package/dist/js/core/components/functional/3d/post-bind.js +207 -0
  39. package/dist/js/core/components/functional/3d/post-bind.js.map +1 -0
  40. package/dist/js/core/components/functional/3d/post-channels.js +39 -0
  41. package/dist/js/core/components/functional/3d/post-channels.js.map +1 -0
  42. package/dist/js/core/components/functional/3d/post-fx.js +495 -0
  43. package/dist/js/core/components/functional/3d/post-fx.js.map +1 -0
  44. package/dist/js/core/components/functional/3d/props.js +142 -0
  45. package/dist/js/core/components/functional/3d/props.js.map +1 -1
  46. package/dist/js/core/components/functional/3d/reload.js +5 -0
  47. package/dist/js/core/components/functional/3d/reload.js.map +1 -1
  48. package/dist/js/core/components/functional/3d/runtime.js +80 -3
  49. package/dist/js/core/components/functional/3d/runtime.js.map +1 -1
  50. package/dist/js/core/components/functional/3d/scene-runtime.js +1 -1
  51. package/dist/js/core/components/functional/3d/scene-runtime.js.map +1 -1
  52. package/dist/js/core/components/functional/3d/tune.js +21 -0
  53. package/dist/js/core/components/functional/3d/tune.js.map +1 -1
  54. package/dist/js/core/components/functional/3d/updated.js +13 -1
  55. package/dist/js/core/components/functional/3d/updated.js.map +1 -1
  56. package/dist/js/core/components/functional/shader/gl-runtime.js +280 -31
  57. package/dist/js/core/components/functional/shader/gl-runtime.js.map +1 -1
  58. package/dist/js/core/components/functional/shader/shader.js +207 -61
  59. package/dist/js/core/components/functional/shader/shader.js.map +1 -1
  60. package/dist/js/core/components/functional/shader/shadertoy-prelude.js +39 -5
  61. package/dist/js/core/components/functional/shader/shadertoy-prelude.js.map +1 -1
  62. package/dist/js/core/components/functional/shader/types.js +58 -7
  63. package/dist/js/core/components/functional/shader/types.js.map +1 -1
  64. package/dist/js/core/components/functional/webgpu/gl-fallback.js +27 -6
  65. package/dist/js/core/components/functional/webgpu/gl-fallback.js.map +1 -1
  66. package/dist/js/core/components/functional/webgpu/gpu-runtime.js +115 -13
  67. package/dist/js/core/components/functional/webgpu/gpu-runtime.js.map +1 -1
  68. package/dist/js/core/components/functional/webgpu/helpers.js +8 -2
  69. package/dist/js/core/components/functional/webgpu/helpers.js.map +1 -1
  70. package/dist/js/core/components/functional/webgpu/webgpu.js +43 -1
  71. package/dist/js/core/components/functional/webgpu/webgpu.js.map +1 -1
  72. package/dist/js/core/utils/mediaRef.js +86 -0
  73. package/dist/js/core/utils/mediaRef.js.map +1 -1
  74. package/dist/types/core/components/functional/3d/3d.d.ts +31 -3
  75. package/dist/types/core/components/functional/3d/3d.d.ts.map +1 -1
  76. package/dist/types/core/components/functional/3d/constants.d.ts +1 -1
  77. package/dist/types/core/components/functional/3d/constants.d.ts.map +1 -1
  78. package/dist/types/core/components/functional/3d/ensure.d.ts +3 -0
  79. package/dist/types/core/components/functional/3d/ensure.d.ts.map +1 -1
  80. package/dist/types/core/components/functional/3d/form-params.d.ts.map +1 -1
  81. package/dist/types/core/components/functional/3d/frame-source.d.ts +23 -0
  82. package/dist/types/core/components/functional/3d/frame-source.d.ts.map +1 -0
  83. package/dist/types/core/components/functional/3d/life.d.ts +2 -0
  84. package/dist/types/core/components/functional/3d/life.d.ts.map +1 -1
  85. package/dist/types/core/components/functional/3d/out-publish.d.ts +1 -0
  86. package/dist/types/core/components/functional/3d/out-publish.d.ts.map +1 -1
  87. package/dist/types/core/components/functional/3d/playback.d.ts +9 -0
  88. package/dist/types/core/components/functional/3d/playback.d.ts.map +1 -1
  89. package/dist/types/core/components/functional/3d/post-bind.d.ts +52 -0
  90. package/dist/types/core/components/functional/3d/post-bind.d.ts.map +1 -0
  91. package/dist/types/core/components/functional/3d/post-channels.d.ts +40 -0
  92. package/dist/types/core/components/functional/3d/post-channels.d.ts.map +1 -0
  93. package/dist/types/core/components/functional/3d/post-fx.d.ts +73 -0
  94. package/dist/types/core/components/functional/3d/post-fx.d.ts.map +1 -0
  95. package/dist/types/core/components/functional/3d/props.d.ts +42 -0
  96. package/dist/types/core/components/functional/3d/props.d.ts.map +1 -1
  97. package/dist/types/core/components/functional/3d/reload.d.ts +3 -0
  98. package/dist/types/core/components/functional/3d/reload.d.ts.map +1 -1
  99. package/dist/types/core/components/functional/3d/runtime.d.ts +16 -1
  100. package/dist/types/core/components/functional/3d/runtime.d.ts.map +1 -1
  101. package/dist/types/core/components/functional/3d/scene-runtime.d.ts +1 -1
  102. package/dist/types/core/components/functional/3d/scene-runtime.d.ts.map +1 -1
  103. package/dist/types/core/components/functional/3d/tune.d.ts +5 -0
  104. package/dist/types/core/components/functional/3d/tune.d.ts.map +1 -1
  105. package/dist/types/core/components/functional/3d/types.d.ts +16 -0
  106. package/dist/types/core/components/functional/3d/types.d.ts.map +1 -1
  107. package/dist/types/core/components/functional/3d/updated.d.ts.map +1 -1
  108. package/dist/types/core/components/functional/shader/gl-runtime.d.ts +53 -2
  109. package/dist/types/core/components/functional/shader/gl-runtime.d.ts.map +1 -1
  110. package/dist/types/core/components/functional/shader/shader.d.ts +41 -9
  111. package/dist/types/core/components/functional/shader/shader.d.ts.map +1 -1
  112. package/dist/types/core/components/functional/shader/shadertoy-prelude.d.ts +16 -2
  113. package/dist/types/core/components/functional/shader/shadertoy-prelude.d.ts.map +1 -1
  114. package/dist/types/core/components/functional/shader/types.d.ts +35 -5
  115. package/dist/types/core/components/functional/shader/types.d.ts.map +1 -1
  116. package/dist/types/core/components/functional/webgpu/gl-fallback.d.ts +4 -0
  117. package/dist/types/core/components/functional/webgpu/gl-fallback.d.ts.map +1 -1
  118. package/dist/types/core/components/functional/webgpu/gpu-runtime.d.ts +14 -0
  119. package/dist/types/core/components/functional/webgpu/gpu-runtime.d.ts.map +1 -1
  120. package/dist/types/core/components/functional/webgpu/helpers.d.ts +1 -0
  121. package/dist/types/core/components/functional/webgpu/helpers.d.ts.map +1 -1
  122. package/dist/types/core/components/functional/webgpu/webgpu.d.ts +11 -1
  123. package/dist/types/core/components/functional/webgpu/webgpu.d.ts.map +1 -1
  124. package/dist/types/core/utils/mediaRef.d.ts +34 -0
  125. package/dist/types/core/utils/mediaRef.d.ts.map +1 -1
  126. package/package.json +49 -1
  127. package/src/core/components/functional/3d/3d.md +46 -3
  128. package/src/core/components/functional/3d/3d.spec.ts +42 -0
  129. package/src/core/components/functional/3d/3d.ts +112 -3
  130. package/src/core/components/functional/3d/constants.ts +3 -0
  131. package/src/core/components/functional/3d/ensure.ts +11 -3
  132. package/src/core/components/functional/3d/form-params.ts +2 -0
  133. package/src/core/components/functional/3d/frame-source.ts +30 -0
  134. package/src/core/components/functional/3d/life.ts +4 -1
  135. package/src/core/components/functional/3d/out-publish.ts +4 -3
  136. package/src/core/components/functional/3d/playback.ts +13 -1
  137. package/src/core/components/functional/3d/post-bind.ts +262 -0
  138. package/src/core/components/functional/3d/post-channels.spec.ts +36 -0
  139. package/src/core/components/functional/3d/post-channels.ts +69 -0
  140. package/src/core/components/functional/3d/post-fx.ts +594 -0
  141. package/src/core/components/functional/3d/props.ts +45 -0
  142. package/src/core/components/functional/3d/reload.ts +8 -0
  143. package/src/core/components/functional/3d/runtime.ts +93 -4
  144. package/src/core/components/functional/3d/scene-runtime.ts +3 -0
  145. package/src/core/components/functional/3d/tune.ts +21 -0
  146. package/src/core/components/functional/3d/types.ts +14 -0
  147. package/src/core/components/functional/3d/updated.ts +17 -1
  148. package/src/core/components/functional/shader/gl-runtime.ts +374 -36
  149. package/src/core/components/functional/shader/shader.md +36 -11
  150. package/src/core/components/functional/shader/shader.spec.ts +350 -3
  151. package/src/core/components/functional/shader/shader.ts +236 -59
  152. package/src/core/components/functional/shader/shadertoy-prelude.ts +49 -5
  153. package/src/core/components/functional/shader/types.ts +92 -13
  154. package/src/core/components/functional/webgpu/gl-fallback.ts +28 -4
  155. package/src/core/components/functional/webgpu/gpu-runtime.ts +154 -18
  156. package/src/core/components/functional/webgpu/helpers.ts +8 -2
  157. package/src/core/components/functional/webgpu/webgpu.md +13 -2
  158. package/src/core/components/functional/webgpu/webgpu.spec.ts +37 -0
  159. package/src/core/components/functional/webgpu/webgpu.ts +48 -1
  160. package/src/core/utils/mediaRef.spec.ts +61 -0
  161. package/src/core/utils/mediaRef.ts +108 -0
  162. package/src/docs/_misc/visual-stack.md +37 -3
  163. package/src/docs/components/docs-demo-sources.ts +42 -0
  164. package/src/docs/docs.ts +3 -0
  165. package/src/docs/example/docs-3d-post-depth-demo.ts +68 -0
  166. package/src/docs/example/docs-3d-shader-live-demo.ts +98 -0
  167. package/src/docs/example/docs-3d-webgpu-live-demo.ts +83 -0
  168. package/src/docs/example/docs-harris-webgpu-demo.ts +53 -55
  169. package/src/docs/search/docs-search.json +2583 -863
  170. package/src/tsconfig.json +36 -0
  171. package/src/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,594 @@
1
+ /**
2
+ * Post-traitement ShaderToy dans le contexte Three de sonic-3d
3
+ * (RenderTarget scène + DepthTexture → passes A–D → image).
4
+ */
5
+ import * as THREE from "three";
6
+ import {
7
+ isFrameSource,
8
+ } from "@supersoniks/concorde/core/utils/mediaRef";
9
+ import {
10
+ buildThreeFragmentSource,
11
+ THREE_FULLSCREEN_VERTEX,
12
+ } from "../shader/shadertoy-prelude";
13
+ import {
14
+ clampPostScale,
15
+ type PostChannelSource,
16
+ type PostChannels,
17
+ type PostPassId,
18
+ type PostSources,
19
+ } from "./post-channels";
20
+
21
+ export type PostFxUniforms = {
22
+ time: number;
23
+ timeDelta: number;
24
+ frame: number;
25
+ mouse: [number, number, number, number];
26
+ params: [number, number, number, number];
27
+ near: number;
28
+ far: number;
29
+ };
30
+
31
+ export type PostFxConfig = {
32
+ sources: PostSources;
33
+ channels: Map<PostPassId, PostChannels>;
34
+ scale: number;
35
+ };
36
+
37
+ export class PostFxCompileError extends Error {
38
+ pass: string;
39
+ infoLog: string;
40
+ constructor(message: string, pass: string, infoLog = "") {
41
+ super(message);
42
+ this.name = "PostFxCompileError";
43
+ this.pass = pass;
44
+ this.infoLog = infoLog;
45
+ }
46
+ }
47
+
48
+ type PingPong = {
49
+ targets: [THREE.WebGLRenderTarget, THREE.WebGLRenderTarget];
50
+ index: number;
51
+ };
52
+
53
+ type PassMat = {
54
+ material: THREE.RawShaderMaterial;
55
+ uniforms: {
56
+ iResolution: { value: THREE.Vector3 };
57
+ iTime: { value: number };
58
+ iTimeDelta: { value: number };
59
+ iFrameRate: { value: number };
60
+ iFrame: { value: number };
61
+ iChannelTime: { value: Float32Array };
62
+ iChannelResolution: { value: Float32Array };
63
+ iMouse: { value: THREE.Vector4 };
64
+ iDate: { value: THREE.Vector4 };
65
+ iSampleRate: { value: number };
66
+ iChannel0: { value: THREE.Texture | null };
67
+ iChannel1: { value: THREE.Texture | null };
68
+ iChannel2: { value: THREE.Texture | null };
69
+ iChannel3: { value: THREE.Texture | null };
70
+ uParam0: { value: number };
71
+ uParam1: { value: number };
72
+ uParam2: { value: number };
73
+ uParam3: { value: number };
74
+ uNear: { value: number };
75
+ uFar: { value: number };
76
+ };
77
+ };
78
+
79
+ const PASS_ORDER: PostPassId[] = [
80
+ "bufferA",
81
+ "bufferB",
82
+ "bufferC",
83
+ "bufferD",
84
+ "image",
85
+ ];
86
+
87
+ const BLACK = (() => {
88
+ const d = new Uint8Array([0, 0, 0, 255]);
89
+ const t = new THREE.DataTexture(d, 1, 1);
90
+ t.needsUpdate = true;
91
+ t.colorSpace = THREE.NoColorSpace;
92
+ return t;
93
+ })();
94
+
95
+ function makeRT(
96
+ w: number,
97
+ h: number,
98
+ withDepth: boolean,
99
+ ): THREE.WebGLRenderTarget {
100
+ if (withDepth) {
101
+ const depth = new THREE.DepthTexture(w, h);
102
+ depth.format = THREE.DepthFormat;
103
+ depth.type = THREE.UnsignedIntType;
104
+ // LinearFilter sur DepthTexture → sampling invalide / noir sur plusieurs GPU.
105
+ depth.minFilter = THREE.NearestFilter;
106
+ depth.magFilter = THREE.NearestFilter;
107
+ const rt = new THREE.WebGLRenderTarget(w, h, {
108
+ minFilter: THREE.LinearFilter,
109
+ magFilter: THREE.LinearFilter,
110
+ format: THREE.RGBAFormat,
111
+ type: THREE.UnsignedByteType,
112
+ depthTexture: depth,
113
+ });
114
+ rt.texture.colorSpace = THREE.NoColorSpace;
115
+ rt.texture.generateMipmaps = false;
116
+ return rt;
117
+ }
118
+ const rt = new THREE.WebGLRenderTarget(w, h, {
119
+ minFilter: THREE.LinearFilter,
120
+ magFilter: THREE.LinearFilter,
121
+ format: THREE.RGBAFormat,
122
+ type: THREE.UnsignedByteType,
123
+ depthBuffer: false,
124
+ stencilBuffer: false,
125
+ });
126
+ rt.texture.colorSpace = THREE.NoColorSpace;
127
+ rt.texture.generateMipmaps = false;
128
+ return rt;
129
+ }
130
+
131
+ function makePingPong(w: number, h: number): PingPong {
132
+ return {
133
+ targets: [makeRT(w, h, false), makeRT(w, h, false)],
134
+ index: 0,
135
+ };
136
+ }
137
+
138
+ function disposeRT(rt: THREE.WebGLRenderTarget) {
139
+ rt.depthTexture?.dispose();
140
+ rt.dispose();
141
+ }
142
+
143
+ /**
144
+ * Pipeline post FX attaché à un WebGLRenderer Three.
145
+ */
146
+ export class PostFxPipeline {
147
+ private renderer: THREE.WebGLRenderer;
148
+ private sceneRT: THREE.WebGLRenderTarget | null = null;
149
+ private buffers = new Map<Exclude<PostPassId, "image">, PingPong>();
150
+ private passes = new Map<PostPassId, PassMat>();
151
+ private channels = new Map<PostPassId, PostChannels>();
152
+ private activePasses: PostPassId[] = [];
153
+ private quadScene = new THREE.Scene();
154
+ private quadCam = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
155
+ private quadMesh: THREE.Mesh;
156
+ private width = 0;
157
+ private height = 0;
158
+ private scale = 1;
159
+ private urlTextures = new Map<string, THREE.Texture>();
160
+ private loader = new THREE.TextureLoader();
161
+ private elementResolver: ((key: string) => Element | null) | null = null;
162
+ private elementTextures = new Map<string, THREE.CanvasTexture>();
163
+ private elementLastSeq = new Map<string, number>();
164
+ private enabled = false;
165
+
166
+ constructor(renderer: THREE.WebGLRenderer) {
167
+ this.renderer = renderer;
168
+ const geo = new THREE.PlaneGeometry(2, 2);
169
+ this.quadMesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial());
170
+ this.quadScene.add(this.quadMesh);
171
+ // PlaneGeometry face +Z ; ortho à z=0 regarde -Z → face arrière culée → noir.
172
+ this.quadCam.position.set(0, 0, 1);
173
+ this.quadCam.lookAt(0, 0, 0);
174
+ }
175
+
176
+ get isEnabled() {
177
+ return this.enabled;
178
+ }
179
+
180
+ setElementResolver(fn: ((key: string) => Element | null) | null) {
181
+ this.elementResolver = fn;
182
+ }
183
+
184
+ /**
185
+ * Compile / (re)configure. `image` vide → désactive (rendu direct).
186
+ * Lève `PostFxCompileError` si une passe échoue.
187
+ */
188
+ setConfig(config: PostFxConfig | null) {
189
+ this.clearPasses();
190
+ if (!config?.sources.image.trim()) {
191
+ this.enabled = false;
192
+ this.disposeTargets();
193
+ return;
194
+ }
195
+ this.scale = clampPostScale(config.scale);
196
+ this.channels = config.channels;
197
+ const sources = config.sources;
198
+ for (const pass of PASS_ORDER) {
199
+ const code =
200
+ pass === "image"
201
+ ? sources.image
202
+ : pass === "bufferA"
203
+ ? sources.bufferA
204
+ : pass === "bufferB"
205
+ ? sources.bufferB
206
+ : pass === "bufferC"
207
+ ? sources.bufferC
208
+ : sources.bufferD;
209
+ if (!code.trim()) continue;
210
+ try {
211
+ this.passes.set(pass, this.compilePass(sources.common, code, pass));
212
+ this.activePasses.push(pass);
213
+ } catch (err) {
214
+ this.clearPasses();
215
+ this.enabled = false;
216
+ this.disposeTargets();
217
+ if (err instanceof PostFxCompileError) throw err;
218
+ throw new PostFxCompileError(
219
+ err instanceof Error ? err.message : String(err),
220
+ pass,
221
+ );
222
+ }
223
+ }
224
+ this.enabled = this.activePasses.includes("image");
225
+ if (!this.enabled) {
226
+ this.clearPasses();
227
+ this.disposeTargets();
228
+ } else if (this.width > 0 && this.height > 0) {
229
+ this.ensureTargets(this.width, this.height);
230
+ }
231
+ }
232
+
233
+ resize(pixelW: number, pixelH: number) {
234
+ const w = Math.max(1, Math.round(pixelW * this.scale));
235
+ const h = Math.max(1, Math.round(pixelH * this.scale));
236
+ if (w === this.width && h === this.height && this.sceneRT) return;
237
+ if (this.enabled) this.ensureTargets(w, h);
238
+ else {
239
+ this.width = w;
240
+ this.height = h;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Rend la scène 3D dans le RT, puis les passes post vers le canvas.
246
+ */
247
+ render(
248
+ scene: THREE.Scene,
249
+ camera: THREE.Camera,
250
+ frame: PostFxUniforms,
251
+ ) {
252
+ if (this.enabled && !this.sceneRT) {
253
+ const size = new THREE.Vector2();
254
+ this.renderer.getDrawingBufferSize(size);
255
+ if (size.x >= 1 && size.y >= 1) this.ensureTargets(size.x, size.y);
256
+ }
257
+ if (!this.enabled || !this.sceneRT || this.width < 1 || this.height < 1) {
258
+ this.renderer.setRenderTarget(null);
259
+ this.renderer.render(scene, camera);
260
+ return;
261
+ }
262
+ this.renderer.setRenderTarget(this.sceneRT);
263
+ this.renderer.clear();
264
+ this.renderer.render(scene, camera);
265
+
266
+ this.updateElementTextures();
267
+
268
+ for (const pass of this.activePasses) {
269
+ if (pass === "image") continue;
270
+ this.drawPass(pass, frame, false);
271
+ }
272
+ this.drawPass("image", frame, true);
273
+ }
274
+
275
+ dispose() {
276
+ this.clearPasses();
277
+ this.disposeTargets();
278
+ for (const t of this.urlTextures.values()) t.dispose();
279
+ this.urlTextures.clear();
280
+ for (const t of this.elementTextures.values()) t.dispose();
281
+ this.elementTextures.clear();
282
+ this.quadMesh.geometry.dispose();
283
+ (this.quadMesh.material as THREE.Material).dispose();
284
+ }
285
+
286
+ private compilePass(
287
+ common: string,
288
+ userCode: string,
289
+ pass: PostPassId,
290
+ ): PassMat {
291
+ const fragmentShader = buildThreeFragmentSource(common, userCode);
292
+ const uniforms: PassMat["uniforms"] = {
293
+ iResolution: { value: new THREE.Vector3(1, 1, 1) },
294
+ iTime: { value: 0 },
295
+ iTimeDelta: { value: 0 },
296
+ iFrameRate: { value: 60 },
297
+ iFrame: { value: 0 },
298
+ iChannelTime: { value: new Float32Array(4) },
299
+ iChannelResolution: { value: new Float32Array(12) },
300
+ iMouse: { value: new THREE.Vector4() },
301
+ iDate: { value: new THREE.Vector4() },
302
+ iSampleRate: { value: 44100 },
303
+ iChannel0: { value: BLACK },
304
+ iChannel1: { value: BLACK },
305
+ iChannel2: { value: BLACK },
306
+ iChannel3: { value: BLACK },
307
+ uParam0: { value: 0 },
308
+ uParam1: { value: 0 },
309
+ uParam2: { value: 0 },
310
+ uParam3: { value: 0 },
311
+ uNear: { value: 0 },
312
+ uFar: { value: 0 },
313
+ };
314
+ const material = new THREE.RawShaderMaterial({
315
+ glslVersion: THREE.GLSL3,
316
+ vertexShader: THREE_FULLSCREEN_VERTEX,
317
+ fragmentShader,
318
+ uniforms,
319
+ depthTest: false,
320
+ depthWrite: false,
321
+ toneMapped: false,
322
+ side: THREE.DoubleSide,
323
+ });
324
+ const log = this.validateGlsl(fragmentShader, pass);
325
+ if (log) {
326
+ material.dispose();
327
+ throw new PostFxCompileError(`Compile error (${pass}): ${log}`, pass, log);
328
+ }
329
+ return { material, uniforms };
330
+ }
331
+
332
+ private validateGlsl(fragmentBody: string, pass: PostPassId): string | null {
333
+ void pass;
334
+ const gl = this.renderer.getContext();
335
+ if (!gl) return null;
336
+ const vs = gl.createShader(gl.VERTEX_SHADER);
337
+ const fs = gl.createShader(gl.FRAGMENT_SHADER);
338
+ if (!vs || !fs) return "Unable to create shader";
339
+ gl.shaderSource(vs, `#version 300 es\n${THREE_FULLSCREEN_VERTEX}`);
340
+ gl.shaderSource(fs, `#version 300 es\n${fragmentBody}`);
341
+ gl.compileShader(vs);
342
+ gl.compileShader(fs);
343
+ let log = "";
344
+ if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
345
+ log += gl.getShaderInfoLog(vs) || "vertex compile failed";
346
+ }
347
+ if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
348
+ log += gl.getShaderInfoLog(fs) || "fragment compile failed";
349
+ }
350
+ gl.deleteShader(vs);
351
+ gl.deleteShader(fs);
352
+ const trimmed = log.trim();
353
+ return trimmed || null;
354
+ }
355
+
356
+ private ensureTargets(w: number, h: number) {
357
+ const ww = Math.max(1, Math.round(w));
358
+ const hh = Math.max(1, Math.round(h));
359
+ this.disposeTargets();
360
+ this.sceneRT = makeRT(ww, hh, true);
361
+ for (const pass of this.activePasses) {
362
+ if (pass === "image") continue;
363
+ this.buffers.set(pass, makePingPong(ww, hh));
364
+ }
365
+ // disposeTargets remettait width/height à 0 → iResolution nul → écran noir.
366
+ this.width = ww;
367
+ this.height = hh;
368
+ }
369
+
370
+ private disposeTargets() {
371
+ if (this.sceneRT) {
372
+ disposeRT(this.sceneRT);
373
+ this.sceneRT = null;
374
+ }
375
+ for (const pp of this.buffers.values()) {
376
+ disposeRT(pp.targets[0]);
377
+ disposeRT(pp.targets[1]);
378
+ }
379
+ this.buffers.clear();
380
+ }
381
+
382
+ private clearPasses() {
383
+ for (const p of this.passes.values()) p.material.dispose();
384
+ this.passes.clear();
385
+ this.activePasses = [];
386
+ }
387
+
388
+ private drawPass(
389
+ pass: PostPassId,
390
+ frame: PostFxUniforms,
391
+ toScreen: boolean,
392
+ ) {
393
+ const pm = this.passes.get(pass);
394
+ if (!pm) return;
395
+ const channels =
396
+ this.channels.get(pass) ||
397
+ ([
398
+ { kind: "scene" },
399
+ { kind: "none" },
400
+ { kind: "none" },
401
+ { kind: "none" },
402
+ ] as PostChannels);
403
+
404
+ const res = pm.uniforms.iChannelResolution.value;
405
+ for (let i = 0; i < 4; i++) {
406
+ const tex = this.resolveChannel(pass, channels[i]);
407
+ const u = pm.uniforms[
408
+ `iChannel${i}` as "iChannel0" | "iChannel1" | "iChannel2" | "iChannel3"
409
+ ];
410
+ u.value = tex;
411
+ const img = tex.image as { width?: number; height?: number } | undefined;
412
+ const tw =
413
+ (tex as THREE.Texture & { source?: { data?: { width?: number } } })
414
+ .image?.width ||
415
+ img?.width ||
416
+ this.width ||
417
+ 1;
418
+ const th =
419
+ (tex as THREE.Texture & { source?: { data?: { height?: number } } })
420
+ .image?.height ||
421
+ img?.height ||
422
+ this.height ||
423
+ 1;
424
+ // RenderTargets expose width/height on the target, not texture.image
425
+ let rw = tw;
426
+ let rh = th;
427
+ if (tex === this.sceneRT?.texture) {
428
+ rw = this.width;
429
+ rh = this.height;
430
+ } else if (tex === this.sceneRT?.depthTexture) {
431
+ rw = this.width;
432
+ rh = this.height;
433
+ } else {
434
+ for (const [id, pp] of this.buffers) {
435
+ void id;
436
+ if (tex === pp.targets[0].texture || tex === pp.targets[1].texture) {
437
+ rw = this.width;
438
+ rh = this.height;
439
+ break;
440
+ }
441
+ }
442
+ }
443
+ res[i * 3] = rw;
444
+ res[i * 3 + 1] = rh;
445
+ res[i * 3 + 2] = 1;
446
+ }
447
+
448
+ pm.uniforms.iResolution.value.set(this.width, this.height, 1);
449
+ pm.uniforms.iTime.value = frame.time;
450
+ pm.uniforms.iTimeDelta.value = frame.timeDelta;
451
+ pm.uniforms.iFrameRate.value =
452
+ frame.timeDelta > 0 ? 1 / frame.timeDelta : 60;
453
+ pm.uniforms.iFrame.value = frame.frame;
454
+ pm.uniforms.iMouse.value.set(
455
+ frame.mouse[0],
456
+ frame.mouse[1],
457
+ frame.mouse[2],
458
+ frame.mouse[3],
459
+ );
460
+ const d = new Date();
461
+ pm.uniforms.iDate.value.set(
462
+ d.getFullYear(),
463
+ d.getMonth(),
464
+ d.getDate(),
465
+ d.getHours() * 3600 +
466
+ d.getMinutes() * 60 +
467
+ d.getSeconds() +
468
+ d.getMilliseconds() * 0.001,
469
+ );
470
+ pm.uniforms.uParam0.value = frame.params[0];
471
+ pm.uniforms.uParam1.value = frame.params[1];
472
+ pm.uniforms.uParam2.value = frame.params[2];
473
+ pm.uniforms.uParam3.value = frame.params[3];
474
+ pm.uniforms.uNear.value = frame.near;
475
+ pm.uniforms.uFar.value = frame.far;
476
+
477
+ this.quadMesh.material = pm.material;
478
+
479
+ if (toScreen) {
480
+ this.renderer.setRenderTarget(null);
481
+ } else {
482
+ const pp = this.buffers.get(pass as Exclude<PostPassId, "image">);
483
+ if (!pp) return;
484
+ const write = 1 - pp.index;
485
+ this.renderer.setRenderTarget(pp.targets[write]);
486
+ this.renderer.clear();
487
+ }
488
+ this.renderer.render(this.quadScene, this.quadCam);
489
+ if (!toScreen) {
490
+ const pp = this.buffers.get(pass as Exclude<PostPassId, "image">);
491
+ if (pp) pp.index = 1 - pp.index;
492
+ }
493
+ }
494
+
495
+ private resolveChannel(
496
+ pass: PostPassId,
497
+ source: PostChannelSource,
498
+ ): THREE.Texture {
499
+ if (source.kind === "none") return BLACK;
500
+ if (source.kind === "scene") {
501
+ return this.sceneRT?.texture ?? BLACK;
502
+ }
503
+ if (source.kind === "depth") {
504
+ return this.sceneRT?.depthTexture ?? BLACK;
505
+ }
506
+ if (source.kind === "url") {
507
+ return this.getUrlTexture(source.url);
508
+ }
509
+ if (source.kind === "element") {
510
+ return this.getElementTexture(source.key) ?? BLACK;
511
+ }
512
+ const bufferId =
513
+ source.kind === "self"
514
+ ? pass === "image"
515
+ ? null
516
+ : pass
517
+ : source.buffer;
518
+ if (!bufferId) return BLACK;
519
+ const pp = this.buffers.get(bufferId);
520
+ if (!pp) return BLACK;
521
+ return pp.targets[pp.index].texture;
522
+ }
523
+
524
+ private getUrlTexture(url: string): THREE.Texture {
525
+ let t = this.urlTextures.get(url);
526
+ if (t) return t;
527
+ t = this.loader.load(url);
528
+ t.colorSpace = THREE.NoColorSpace;
529
+ t.flipY = true;
530
+ this.urlTextures.set(url, t);
531
+ return t;
532
+ }
533
+
534
+ private getElementTexture(key: string): THREE.Texture | null {
535
+ const el = this.elementResolver?.(key) ?? null;
536
+ if (!el) return null;
537
+ let canvas: HTMLCanvasElement | null = null;
538
+ let seq = -1;
539
+ if (isFrameSource(el)) {
540
+ canvas = el.getFrameCanvas();
541
+ seq = el.frameSeq;
542
+ } else if (el instanceof HTMLCanvasElement) {
543
+ canvas = el;
544
+ seq = (this.elementLastSeq.get(key) ?? 0) + 1;
545
+ } else if (el instanceof HTMLVideoElement) {
546
+ if (el.readyState < 2) return this.elementTextures.get(key) ?? null;
547
+ // CanvasTexture from video via temporary — use VideoTexture
548
+ let vt = this.elementTextures.get(key) as THREE.VideoTexture | undefined;
549
+ if (!vt) {
550
+ vt = new THREE.VideoTexture(el);
551
+ vt.colorSpace = THREE.NoColorSpace;
552
+ this.elementTextures.set(key, vt as unknown as THREE.CanvasTexture);
553
+ }
554
+ vt.needsUpdate = true;
555
+ return vt;
556
+ } else if (el instanceof HTMLImageElement) {
557
+ let it = this.urlTextures.get(key);
558
+ if (!it && el.complete && el.naturalWidth > 0) {
559
+ it = new THREE.Texture(el);
560
+ it.needsUpdate = true;
561
+ it.colorSpace = THREE.NoColorSpace;
562
+ it.flipY = true;
563
+ this.urlTextures.set(key, it);
564
+ }
565
+ return it ?? null;
566
+ }
567
+ if (!canvas || canvas.width < 1) return this.elementTextures.get(key) ?? null;
568
+ let ct = this.elementTextures.get(key);
569
+ if (!ct) {
570
+ ct = new THREE.CanvasTexture(canvas);
571
+ ct.colorSpace = THREE.NoColorSpace;
572
+ ct.flipY = false;
573
+ this.elementTextures.set(key, ct);
574
+ } else if (ct.image !== canvas) {
575
+ ct.image = canvas;
576
+ }
577
+ const last = this.elementLastSeq.get(key);
578
+ if (last !== seq) {
579
+ ct.needsUpdate = true;
580
+ this.elementLastSeq.set(key, seq);
581
+ }
582
+ return ct;
583
+ }
584
+
585
+ private updateElementTextures() {
586
+ for (const [key, tex] of this.elementTextures) {
587
+ if (tex instanceof THREE.VideoTexture) {
588
+ tex.needsUpdate = true;
589
+ continue;
590
+ }
591
+ void this.getElementTexture(key);
592
+ }
593
+ }
594
+ }
@@ -34,8 +34,19 @@ export class Sonic3dProps extends LitElement {
34
34
  @property({ type: Boolean }) pick = false;
35
35
  @property({ type: Boolean, reflect: true }) play = true;
36
36
  @property({ type: Boolean, attribute: "play-on-hover" }) playOnHover = false;
37
+ /**
38
+ * Garde le RAF / runtime hors viewport (pont live `#id`).
39
+ * Aussi auto si un consommateur a appelé `registerFrameConsumer`.
40
+ */
41
+ @property({ type: Boolean, attribute: "play-offscreen" })
42
+ playOffscreen = false;
37
43
  @property({ type: Boolean, attribute: "release-offscreen" })
38
44
  releaseOffscreen = true;
45
+ /**
46
+ * Inertie OrbitControls (`dampingFactor`). `0` = pas d’inertie.
47
+ * Défaut `0.08`.
48
+ */
49
+ @property({ type: Number }) damping = 0.08;
39
50
  @property({ type: Number, attribute: "dpr-max" }) dprMax = 2;
40
51
  @property({ type: String, attribute: "aspect-ratio" }) aspectRatio = "";
41
52
  @property({ type: String }) dataProvider = "";
@@ -47,4 +58,38 @@ export class Sonic3dProps extends LitElement {
47
58
  snapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MS;
48
59
  /** Publie `frameUrl` / `snapshot` (canvas) pour chaînage shader / jsonata. */
49
60
  @property({ type: Boolean, attribute: "frame-out" }) frameOut = false;
61
+
62
+ /* —— Post FX (Lot 2) : passes ShaderToy dans le contexte Three —— */
63
+ @property({ attribute: "post-image" }) postImage = "";
64
+ @property({ attribute: "post-buffer-a" }) postBufferA = "";
65
+ @property({ attribute: "post-buffer-b" }) postBufferB = "";
66
+ @property({ attribute: "post-buffer-c" }) postBufferC = "";
67
+ @property({ attribute: "post-buffer-d" }) postBufferD = "";
68
+ @property({ attribute: "post-common" }) postCommon = "";
69
+ @property({ attribute: "post-image-ch0" }) postImageCh0 = "";
70
+ @property({ attribute: "post-image-ch1" }) postImageCh1 = "";
71
+ @property({ attribute: "post-image-ch2" }) postImageCh2 = "";
72
+ @property({ attribute: "post-image-ch3" }) postImageCh3 = "";
73
+ @property({ attribute: "post-buffer-a-ch0" }) postBufferACh0 = "";
74
+ @property({ attribute: "post-buffer-a-ch1" }) postBufferACh1 = "";
75
+ @property({ attribute: "post-buffer-a-ch2" }) postBufferACh2 = "";
76
+ @property({ attribute: "post-buffer-a-ch3" }) postBufferACh3 = "";
77
+ @property({ attribute: "post-buffer-b-ch0" }) postBufferBCh0 = "";
78
+ @property({ attribute: "post-buffer-b-ch1" }) postBufferBCh1 = "";
79
+ @property({ attribute: "post-buffer-b-ch2" }) postBufferBCh2 = "";
80
+ @property({ attribute: "post-buffer-b-ch3" }) postBufferBCh3 = "";
81
+ @property({ attribute: "post-buffer-c-ch0" }) postBufferCCh0 = "";
82
+ @property({ attribute: "post-buffer-c-ch1" }) postBufferCCh1 = "";
83
+ @property({ attribute: "post-buffer-c-ch2" }) postBufferCCh2 = "";
84
+ @property({ attribute: "post-buffer-c-ch3" }) postBufferCCh3 = "";
85
+ @property({ attribute: "post-buffer-d-ch0" }) postBufferDCh0 = "";
86
+ @property({ attribute: "post-buffer-d-ch1" }) postBufferDCh1 = "";
87
+ @property({ attribute: "post-buffer-d-ch2" }) postBufferDCh2 = "";
88
+ @property({ attribute: "post-buffer-d-ch3" }) postBufferDCh3 = "";
89
+ @property({ type: Number }) param0 = 0;
90
+ @property({ type: Number }) param1 = 0;
91
+ @property({ type: Number }) param2 = 0;
92
+ @property({ type: Number }) param3 = 0;
93
+ /** Échelle des cibles post (0.25–1, défaut 1). */
94
+ @property({ type: Number, attribute: "post-scale" }) postScale = 1;
50
95
  }
@@ -1,6 +1,8 @@
1
1
  import { PublisherManager } from "@supersoniks/concorde/utils";
2
2
  import { asset } from "./asset";
3
3
  import type { FormOrbit } from "./form-params";
4
+ import { frameSource } from "./frame-source";
5
+ import { postBind } from "./post-bind";
4
6
  import { playback } from "./playback";
5
7
  import type { SceneRuntime } from "./runtime";
6
8
  import type { Sonic3dAsset } from "./types";
@@ -16,8 +18,10 @@ export type ReloadHost = {
16
18
  ready: boolean;
17
19
  startTime: number;
18
20
  frame: number;
21
+ frameSeq: number;
19
22
  lastFormOrbit: FormOrbit | null;
20
23
  formPublisher: ReturnType<typeof PublisherManager.get> | null;
24
+ outPublisher: ReturnType<typeof PublisherManager.get> | null;
21
25
  asset: Sonic3dAsset | null;
22
26
  src: string;
23
27
  minDistance: number;
@@ -26,6 +30,7 @@ export type ReloadHost = {
26
30
  maxPolar: number;
27
31
  onFormMutation: () => void;
28
32
  syncPlayback: () => void;
33
+ bindOutPublisher: () => void;
29
34
  dispatchEvent: (event: Event) => boolean;
30
35
  };
31
36
 
@@ -43,7 +48,10 @@ async function run(host: ReloadHost) {
43
48
  host.onFormMutation();
44
49
  playback.syncAutoRotate(host as never);
45
50
  host.runtime.render();
51
+ host.frameSeq++;
46
52
  host.syncPlayback();
53
+ postBind.applyConfig(host as never);
54
+ frameSource.maybePublish(host as never);
47
55
  host.dispatchEvent(
48
56
  new CustomEvent("ready", {
49
57
  detail: { ready: true },