@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.
- package/build-infos.json +1 -1
- package/concorde-3d.bundle.js +201 -152
- package/concorde-3d.es.js +14621 -13823
- package/concorde-core.bundle.js +1 -1
- package/concorde-core.es.js +1 -1
- package/concorde-hugging-face-infer.bundle.js +1 -1
- package/concorde-hugging-face-infer.es.js +1 -1
- package/concorde-shader.bundle.js +31 -20
- package/concorde-shader.es.js +3236 -2865
- package/concorde-webgpu.bundle.js +43 -32
- package/concorde-webgpu.es.js +2977 -2618
- package/dist/concorde-3d.bundle.js +201 -152
- package/dist/concorde-3d.es.js +14621 -13823
- package/dist/concorde-core.bundle.js +1 -1
- package/dist/concorde-core.es.js +1 -1
- package/dist/concorde-hugging-face-infer.bundle.js +1 -1
- package/dist/concorde-hugging-face-infer.es.js +1 -1
- package/dist/concorde-shader.bundle.js +31 -20
- package/dist/concorde-shader.es.js +3236 -2865
- package/dist/concorde-webgpu.bundle.js +43 -32
- package/dist/concorde-webgpu.es.js +2977 -2618
- package/dist/js/core/components/functional/3d/3d.js +100 -2
- package/dist/js/core/components/functional/3d/3d.js.map +1 -1
- package/dist/js/core/components/functional/3d/constants.js +3 -0
- package/dist/js/core/components/functional/3d/constants.js.map +1 -1
- package/dist/js/core/components/functional/3d/ensure.js +6 -3
- package/dist/js/core/components/functional/3d/ensure.js.map +1 -1
- package/dist/js/core/components/functional/3d/form-params.js +2 -0
- package/dist/js/core/components/functional/3d/form-params.js.map +1 -1
- package/dist/js/core/components/functional/3d/frame-source.js +22 -0
- package/dist/js/core/components/functional/3d/frame-source.js.map +1 -0
- package/dist/js/core/components/functional/3d/life.js +2 -1
- package/dist/js/core/components/functional/3d/life.js.map +1 -1
- package/dist/js/core/components/functional/3d/out-publish.js +3 -2
- package/dist/js/core/components/functional/3d/out-publish.js.map +1 -1
- package/dist/js/core/components/functional/3d/playback.js +6 -1
- package/dist/js/core/components/functional/3d/playback.js.map +1 -1
- package/dist/js/core/components/functional/3d/post-bind.js +207 -0
- package/dist/js/core/components/functional/3d/post-bind.js.map +1 -0
- package/dist/js/core/components/functional/3d/post-channels.js +39 -0
- package/dist/js/core/components/functional/3d/post-channels.js.map +1 -0
- package/dist/js/core/components/functional/3d/post-fx.js +495 -0
- package/dist/js/core/components/functional/3d/post-fx.js.map +1 -0
- package/dist/js/core/components/functional/3d/props.js +142 -0
- package/dist/js/core/components/functional/3d/props.js.map +1 -1
- package/dist/js/core/components/functional/3d/reload.js +5 -0
- package/dist/js/core/components/functional/3d/reload.js.map +1 -1
- package/dist/js/core/components/functional/3d/runtime.js +80 -3
- package/dist/js/core/components/functional/3d/runtime.js.map +1 -1
- package/dist/js/core/components/functional/3d/scene-runtime.js +1 -1
- package/dist/js/core/components/functional/3d/scene-runtime.js.map +1 -1
- package/dist/js/core/components/functional/3d/tune.js +21 -0
- package/dist/js/core/components/functional/3d/tune.js.map +1 -1
- package/dist/js/core/components/functional/3d/updated.js +13 -1
- package/dist/js/core/components/functional/3d/updated.js.map +1 -1
- package/dist/js/core/components/functional/shader/gl-runtime.js +280 -31
- package/dist/js/core/components/functional/shader/gl-runtime.js.map +1 -1
- package/dist/js/core/components/functional/shader/shader.js +207 -61
- package/dist/js/core/components/functional/shader/shader.js.map +1 -1
- package/dist/js/core/components/functional/shader/shadertoy-prelude.js +39 -5
- package/dist/js/core/components/functional/shader/shadertoy-prelude.js.map +1 -1
- package/dist/js/core/components/functional/shader/types.js +58 -7
- package/dist/js/core/components/functional/shader/types.js.map +1 -1
- package/dist/js/core/components/functional/webgpu/gl-fallback.js +27 -6
- package/dist/js/core/components/functional/webgpu/gl-fallback.js.map +1 -1
- package/dist/js/core/components/functional/webgpu/gpu-runtime.js +115 -13
- package/dist/js/core/components/functional/webgpu/gpu-runtime.js.map +1 -1
- package/dist/js/core/components/functional/webgpu/helpers.js +8 -2
- package/dist/js/core/components/functional/webgpu/helpers.js.map +1 -1
- package/dist/js/core/components/functional/webgpu/webgpu.js +43 -1
- package/dist/js/core/components/functional/webgpu/webgpu.js.map +1 -1
- package/dist/js/core/utils/mediaRef.js +86 -0
- package/dist/js/core/utils/mediaRef.js.map +1 -1
- package/dist/types/core/components/functional/3d/3d.d.ts +31 -3
- package/dist/types/core/components/functional/3d/3d.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/constants.d.ts +1 -1
- package/dist/types/core/components/functional/3d/constants.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/ensure.d.ts +3 -0
- package/dist/types/core/components/functional/3d/ensure.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/form-params.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/frame-source.d.ts +23 -0
- package/dist/types/core/components/functional/3d/frame-source.d.ts.map +1 -0
- package/dist/types/core/components/functional/3d/life.d.ts +2 -0
- package/dist/types/core/components/functional/3d/life.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/out-publish.d.ts +1 -0
- package/dist/types/core/components/functional/3d/out-publish.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/playback.d.ts +9 -0
- package/dist/types/core/components/functional/3d/playback.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/post-bind.d.ts +52 -0
- package/dist/types/core/components/functional/3d/post-bind.d.ts.map +1 -0
- package/dist/types/core/components/functional/3d/post-channels.d.ts +40 -0
- package/dist/types/core/components/functional/3d/post-channels.d.ts.map +1 -0
- package/dist/types/core/components/functional/3d/post-fx.d.ts +73 -0
- package/dist/types/core/components/functional/3d/post-fx.d.ts.map +1 -0
- package/dist/types/core/components/functional/3d/props.d.ts +42 -0
- package/dist/types/core/components/functional/3d/props.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/reload.d.ts +3 -0
- package/dist/types/core/components/functional/3d/reload.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/runtime.d.ts +16 -1
- package/dist/types/core/components/functional/3d/runtime.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/scene-runtime.d.ts +1 -1
- package/dist/types/core/components/functional/3d/scene-runtime.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/tune.d.ts +5 -0
- package/dist/types/core/components/functional/3d/tune.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/types.d.ts +16 -0
- package/dist/types/core/components/functional/3d/types.d.ts.map +1 -1
- package/dist/types/core/components/functional/3d/updated.d.ts.map +1 -1
- package/dist/types/core/components/functional/shader/gl-runtime.d.ts +53 -2
- package/dist/types/core/components/functional/shader/gl-runtime.d.ts.map +1 -1
- package/dist/types/core/components/functional/shader/shader.d.ts +41 -9
- package/dist/types/core/components/functional/shader/shader.d.ts.map +1 -1
- package/dist/types/core/components/functional/shader/shadertoy-prelude.d.ts +16 -2
- package/dist/types/core/components/functional/shader/shadertoy-prelude.d.ts.map +1 -1
- package/dist/types/core/components/functional/shader/types.d.ts +35 -5
- package/dist/types/core/components/functional/shader/types.d.ts.map +1 -1
- package/dist/types/core/components/functional/webgpu/gl-fallback.d.ts +4 -0
- package/dist/types/core/components/functional/webgpu/gl-fallback.d.ts.map +1 -1
- package/dist/types/core/components/functional/webgpu/gpu-runtime.d.ts +14 -0
- package/dist/types/core/components/functional/webgpu/gpu-runtime.d.ts.map +1 -1
- package/dist/types/core/components/functional/webgpu/helpers.d.ts +1 -0
- package/dist/types/core/components/functional/webgpu/helpers.d.ts.map +1 -1
- package/dist/types/core/components/functional/webgpu/webgpu.d.ts +11 -1
- package/dist/types/core/components/functional/webgpu/webgpu.d.ts.map +1 -1
- package/dist/types/core/utils/mediaRef.d.ts +34 -0
- package/dist/types/core/utils/mediaRef.d.ts.map +1 -1
- package/package.json +49 -1
- package/src/core/components/functional/3d/3d.md +46 -3
- package/src/core/components/functional/3d/3d.spec.ts +42 -0
- package/src/core/components/functional/3d/3d.ts +112 -3
- package/src/core/components/functional/3d/constants.ts +3 -0
- package/src/core/components/functional/3d/ensure.ts +11 -3
- package/src/core/components/functional/3d/form-params.ts +2 -0
- package/src/core/components/functional/3d/frame-source.ts +30 -0
- package/src/core/components/functional/3d/life.ts +4 -1
- package/src/core/components/functional/3d/out-publish.ts +4 -3
- package/src/core/components/functional/3d/playback.ts +13 -1
- package/src/core/components/functional/3d/post-bind.ts +262 -0
- package/src/core/components/functional/3d/post-channels.spec.ts +36 -0
- package/src/core/components/functional/3d/post-channels.ts +69 -0
- package/src/core/components/functional/3d/post-fx.ts +594 -0
- package/src/core/components/functional/3d/props.ts +45 -0
- package/src/core/components/functional/3d/reload.ts +8 -0
- package/src/core/components/functional/3d/runtime.ts +93 -4
- package/src/core/components/functional/3d/scene-runtime.ts +3 -0
- package/src/core/components/functional/3d/tune.ts +21 -0
- package/src/core/components/functional/3d/types.ts +14 -0
- package/src/core/components/functional/3d/updated.ts +17 -1
- package/src/core/components/functional/shader/gl-runtime.ts +374 -36
- package/src/core/components/functional/shader/shader.md +36 -11
- package/src/core/components/functional/shader/shader.spec.ts +350 -3
- package/src/core/components/functional/shader/shader.ts +236 -59
- package/src/core/components/functional/shader/shadertoy-prelude.ts +49 -5
- package/src/core/components/functional/shader/types.ts +92 -13
- package/src/core/components/functional/webgpu/gl-fallback.ts +28 -4
- package/src/core/components/functional/webgpu/gpu-runtime.ts +154 -18
- package/src/core/components/functional/webgpu/helpers.ts +8 -2
- package/src/core/components/functional/webgpu/webgpu.md +13 -2
- package/src/core/components/functional/webgpu/webgpu.spec.ts +37 -0
- package/src/core/components/functional/webgpu/webgpu.ts +48 -1
- package/src/core/utils/mediaRef.spec.ts +61 -0
- package/src/core/utils/mediaRef.ts +108 -0
- package/src/docs/_misc/visual-stack.md +37 -3
- package/src/docs/components/docs-demo-sources.ts +42 -0
- package/src/docs/docs.ts +3 -0
- package/src/docs/example/docs-3d-post-depth-demo.ts +68 -0
- package/src/docs/example/docs-3d-shader-live-demo.ts +98 -0
- package/src/docs/example/docs-3d-webgpu-live-demo.ts +83 -0
- package/src/docs/example/docs-harris-webgpu-demo.ts +53 -55
- package/src/docs/search/docs-search.json +2583 -863
- package/src/tsconfig.json +36 -0
- package/src/tsconfig.tsbuildinfo +1 -1
|
@@ -5,11 +5,64 @@ export type ShaderPassId =
|
|
|
5
5
|
| "bufferC"
|
|
6
6
|
| "bufferD";
|
|
7
7
|
|
|
8
|
+
/** Format interne des buffers A–D (Image = toujours le canvas RGBA8). */
|
|
9
|
+
export type ShaderBufferFormat = "rgba8" | "rgba16f" | "rgba32f";
|
|
10
|
+
|
|
11
|
+
export function parseBufferFormat(
|
|
12
|
+
value: string | null | undefined,
|
|
13
|
+
): ShaderBufferFormat {
|
|
14
|
+
const s = (value ?? "").trim().toLowerCase();
|
|
15
|
+
if (s === "rgba16f" || s === "rgba16" || s === "half") return "rgba16f";
|
|
16
|
+
if (s === "rgba32f" || s === "rgba32" || s === "float") return "rgba32f";
|
|
17
|
+
return "rgba8";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Hors viewport : libérer textures/FBO ?
|
|
22
|
+
* - `true` / `false` — forcé
|
|
23
|
+
* - `auto` (défaut) — libère sauf si une passe lit `self` (état conservé)
|
|
24
|
+
*/
|
|
25
|
+
export type ReleaseOffscreen = boolean | "auto";
|
|
26
|
+
|
|
27
|
+
export function parseReleaseOffscreen(
|
|
28
|
+
value: string | null | undefined,
|
|
29
|
+
): ReleaseOffscreen {
|
|
30
|
+
if (value == null) return "auto";
|
|
31
|
+
const s = value.trim().toLowerCase();
|
|
32
|
+
if (s === "auto") return "auto";
|
|
33
|
+
if (s === "false" || s === "0") return false;
|
|
34
|
+
// attribut booléen présent (`""`) ou `"true"`
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Au moins un canal `self` (feedback frame précédente). */
|
|
39
|
+
export function channelsUseSelf(
|
|
40
|
+
channels: Iterable<readonly ChannelSource[]>,
|
|
41
|
+
): boolean {
|
|
42
|
+
for (const chs of channels) {
|
|
43
|
+
for (const c of chs) {
|
|
44
|
+
if (c.kind === "self") return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function shouldReleaseWhenOffscreen(
|
|
51
|
+
mode: ReleaseOffscreen,
|
|
52
|
+
usesSelf: boolean,
|
|
53
|
+
): boolean {
|
|
54
|
+
if (mode === false) return false;
|
|
55
|
+
if (mode === true) return true;
|
|
56
|
+
return !usesSelf;
|
|
57
|
+
}
|
|
58
|
+
|
|
8
59
|
export type ChannelSource =
|
|
9
60
|
| { kind: "url"; url: string }
|
|
10
61
|
| { kind: "buffer"; buffer: Exclude<ShaderPassId, "image"> }
|
|
11
62
|
| { kind: "self" }
|
|
12
|
-
| { kind: "none" }
|
|
63
|
+
| { kind: "none" }
|
|
64
|
+
/** Élément page (`#id`) — upload live via `updateElementTextures`. */
|
|
65
|
+
| { kind: "element"; key: string; el?: Element | null };
|
|
13
66
|
|
|
14
67
|
export type PassChannels = [
|
|
15
68
|
ChannelSource,
|
|
@@ -159,10 +212,18 @@ export type SonicShaderConfig = {
|
|
|
159
212
|
dprMax?: number;
|
|
160
213
|
|
|
161
214
|
/**
|
|
162
|
-
*
|
|
215
|
+
* Format des buffers A–D : `rgba8` (défaut) \| `rgba16f` \| `rgba32f`.
|
|
216
|
+
* Requis pour des valeurs signées / hors [0,1] (ex. tenseur Harris).
|
|
217
|
+
* Repli auto en `rgba8` si `EXT_color_buffer_float` manque.
|
|
218
|
+
*/
|
|
219
|
+
bufferFormat?: ShaderBufferFormat;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Hors viewport : libère textures/FBO, sans loseContext.
|
|
223
|
+
* `auto` (défaut) : ne libère pas si une passe lit `self`.
|
|
163
224
|
* Le slot navigateur est libéré au disconnect.
|
|
164
225
|
*/
|
|
165
|
-
releaseOffscreen?:
|
|
226
|
+
releaseOffscreen?: ReleaseOffscreen;
|
|
166
227
|
};
|
|
167
228
|
|
|
168
229
|
/** Parse `hover-param` → slot ou `null` si désactivé / invalide. */
|
|
@@ -272,7 +333,8 @@ export type ReadRectResult = {
|
|
|
272
333
|
y: number;
|
|
273
334
|
width: number;
|
|
274
335
|
height: number;
|
|
275
|
-
|
|
336
|
+
/** `Uint8Array` (RGBA8) ou `Float32Array` (buffers flottants). */
|
|
337
|
+
data: Uint8Array | Float32Array;
|
|
276
338
|
};
|
|
277
339
|
|
|
278
340
|
/** Parse `extract-source` / `extract-mask` → pass id ou null. */
|
|
@@ -293,9 +355,10 @@ export function parseExtractPass(
|
|
|
293
355
|
/**
|
|
294
356
|
* NMS simple sur canal R (RGBA row-major, origine bas-gauche comme WebGL).
|
|
295
357
|
* Retourne peaks triés par score décroissant.
|
|
358
|
+
* Accepte Uint8 (0–255) ou Float32 (scores déjà dans ~[0,1]).
|
|
296
359
|
*/
|
|
297
360
|
export function findPeaks(
|
|
298
|
-
data: Uint8Array,
|
|
361
|
+
data: Uint8Array | Float32Array,
|
|
299
362
|
width: number,
|
|
300
363
|
height: number,
|
|
301
364
|
options: FindPeaksOptions = {},
|
|
@@ -306,13 +369,15 @@ export function findPeaks(
|
|
|
306
369
|
if (maxCount === 0 || width < 1 || height < 1 || data.length < width * height * 4) {
|
|
307
370
|
return [];
|
|
308
371
|
}
|
|
309
|
-
const
|
|
372
|
+
const isFloat = data instanceof Float32Array;
|
|
373
|
+
const thr = isFloat ? threshold : threshold * 255;
|
|
374
|
+
const toScore = (v: number) => (isFloat ? v : v / 255);
|
|
310
375
|
const candidates: ShaderPeak[] = [];
|
|
311
376
|
for (let y = 1; y < height - 1; y++) {
|
|
312
377
|
for (let x = 1; x < width - 1; x++) {
|
|
313
378
|
const i = (y * width + x) * 4;
|
|
314
379
|
const v = data[i];
|
|
315
|
-
if (v <
|
|
380
|
+
if (v < thr) continue;
|
|
316
381
|
// Local max 3×3
|
|
317
382
|
let isMax = true;
|
|
318
383
|
for (let dy = -1; dy <= 1 && isMax; dy++) {
|
|
@@ -325,7 +390,7 @@ export function findPeaks(
|
|
|
325
390
|
}
|
|
326
391
|
}
|
|
327
392
|
}
|
|
328
|
-
if (isMax) candidates.push({ x, y, score: v
|
|
393
|
+
if (isMax) candidates.push({ x, y, score: toScore(v) });
|
|
329
394
|
}
|
|
330
395
|
}
|
|
331
396
|
candidates.sort((a, b) => b.score - a.score);
|
|
@@ -401,7 +466,7 @@ export function roiToPixels(
|
|
|
401
466
|
return { x: px, y: py, w: pw, h: ph };
|
|
402
467
|
}
|
|
403
468
|
|
|
404
|
-
export function meanRgba(data: Uint8Array): {
|
|
469
|
+
export function meanRgba(data: Uint8Array | Float32Array): {
|
|
405
470
|
r: number;
|
|
406
471
|
g: number;
|
|
407
472
|
b: number;
|
|
@@ -410,6 +475,7 @@ export function meanRgba(data: Uint8Array): {
|
|
|
410
475
|
} {
|
|
411
476
|
const n = Math.floor(data.length / 4);
|
|
412
477
|
if (n < 1) return { r: 0, g: 0, b: 0, a: 0, luma: 0 };
|
|
478
|
+
const isFloat = data instanceof Float32Array;
|
|
413
479
|
let r = 0;
|
|
414
480
|
let g = 0;
|
|
415
481
|
let b = 0;
|
|
@@ -421,10 +487,11 @@ export function meanRgba(data: Uint8Array): {
|
|
|
421
487
|
b += data[o + 2];
|
|
422
488
|
a += data[o + 3];
|
|
423
489
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
490
|
+
const scale = isFloat ? n : n * 255;
|
|
491
|
+
r /= scale;
|
|
492
|
+
g /= scale;
|
|
493
|
+
b /= scale;
|
|
494
|
+
a /= scale;
|
|
428
495
|
return {
|
|
429
496
|
r,
|
|
430
497
|
g,
|
|
@@ -434,6 +501,18 @@ export function meanRgba(data: Uint8Array): {
|
|
|
434
501
|
};
|
|
435
502
|
}
|
|
436
503
|
|
|
504
|
+
/** Convertit un buffer RGBA (byte ou float 0–1) en Uint8 pour canvas / blob. */
|
|
505
|
+
export function rgbaToUint8(
|
|
506
|
+
data: Uint8Array | Float32Array,
|
|
507
|
+
): Uint8Array {
|
|
508
|
+
if (data instanceof Uint8Array) return data;
|
|
509
|
+
const out = new Uint8Array(data.length);
|
|
510
|
+
for (let i = 0; i < data.length; i++) {
|
|
511
|
+
out[i] = Math.max(0, Math.min(255, Math.round(data[i] * 255)));
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
}
|
|
515
|
+
|
|
437
516
|
export class ShaderCompileError extends Error {
|
|
438
517
|
constructor(
|
|
439
518
|
message: string,
|
|
@@ -45,6 +45,7 @@ export class WebGl2FallbackRuntime {
|
|
|
45
45
|
private lastTime = 0;
|
|
46
46
|
private disposed = false;
|
|
47
47
|
private channelQueue: Promise<void> = Promise.resolve();
|
|
48
|
+
private elementResolver: ((key: string) => Element | null) | null = null;
|
|
48
49
|
|
|
49
50
|
private constructor(canvas: HTMLCanvasElement, toy: ShaderToyRuntime) {
|
|
50
51
|
this.canvas = canvas;
|
|
@@ -57,6 +58,11 @@ export class WebGl2FallbackRuntime {
|
|
|
57
58
|
return new WebGl2FallbackRuntime(canvas, toy);
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
setElementResolver(fn: ((key: string) => Element | null) | null) {
|
|
62
|
+
this.elementResolver = fn;
|
|
63
|
+
this.toy.setElementResolver(fn);
|
|
64
|
+
}
|
|
65
|
+
|
|
60
66
|
/** WGSL ignoré — effet GLSL fixe (canaux / params OK). */
|
|
61
67
|
async setShader(_userBody: string): Promise<void> {
|
|
62
68
|
this.assertAlive();
|
|
@@ -77,6 +83,10 @@ export class WebGl2FallbackRuntime {
|
|
|
77
83
|
this.assertAlive();
|
|
78
84
|
const trimmed = url.trim();
|
|
79
85
|
if (this.channelUrls[index] === trimmed) {
|
|
86
|
+
if (trimmed.startsWith("#")) {
|
|
87
|
+
this.toy.ensureElementTexture(trimmed);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
80
90
|
// Même URL : s’assurer que la texture est bien chargée
|
|
81
91
|
if (trimmed && !this.toy.getTextureSize(trimmed)) {
|
|
82
92
|
await this.toy.ensureTextures([trimmed]);
|
|
@@ -85,8 +95,7 @@ export class WebGl2FallbackRuntime {
|
|
|
85
95
|
}
|
|
86
96
|
this.channelUrls[index] = trimmed;
|
|
87
97
|
this.applySources();
|
|
88
|
-
|
|
89
|
-
if (urls.length) await this.toy.ensureTextures(urls);
|
|
98
|
+
await this.ensureChannelAssets();
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
/** Applique les 4 canaux d’un coup (préféré au boot). */
|
|
@@ -100,8 +109,7 @@ export class WebGl2FallbackRuntime {
|
|
|
100
109
|
urls[3].trim(),
|
|
101
110
|
];
|
|
102
111
|
this.applySources();
|
|
103
|
-
|
|
104
|
-
if (list.length) await this.toy.ensureTextures(list);
|
|
112
|
+
await this.ensureChannelAssets();
|
|
105
113
|
});
|
|
106
114
|
return this.channelQueue;
|
|
107
115
|
}
|
|
@@ -115,9 +123,17 @@ export class WebGl2FallbackRuntime {
|
|
|
115
123
|
this.toy.resize(width, height);
|
|
116
124
|
}
|
|
117
125
|
|
|
126
|
+
updateLiveChannels(resolveEl: (key: string) => Element | null) {
|
|
127
|
+
this.assertAlive();
|
|
128
|
+
this.toy.updateElementTextures(resolveEl);
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
draw(state: WebGpuFrameState) {
|
|
119
132
|
this.assertAlive();
|
|
120
133
|
if (this.toy.isLost) return;
|
|
134
|
+
if (this.elementResolver) {
|
|
135
|
+
this.toy.updateElementTextures(this.elementResolver);
|
|
136
|
+
}
|
|
121
137
|
const timeDelta =
|
|
122
138
|
this.lastTime > 0 ? Math.max(0, state.time - this.lastTime) : 0;
|
|
123
139
|
this.lastTime = state.time;
|
|
@@ -137,6 +153,14 @@ export class WebGl2FallbackRuntime {
|
|
|
137
153
|
this.toy.dispose(loseContext);
|
|
138
154
|
}
|
|
139
155
|
|
|
156
|
+
private async ensureChannelAssets() {
|
|
157
|
+
const urls = this.channelUrls.filter((u) => u && !u.startsWith("#"));
|
|
158
|
+
if (urls.length) await this.toy.ensureTextures(urls);
|
|
159
|
+
for (const key of this.channelUrls) {
|
|
160
|
+
if (key.startsWith("#")) this.toy.ensureElementTexture(key);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
140
164
|
private applySources() {
|
|
141
165
|
const channels = new Map([
|
|
142
166
|
[
|
|
@@ -1,11 +1,53 @@
|
|
|
1
1
|
/// <reference types="@webgpu/types" />
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
isFrameSource,
|
|
5
|
+
type SonicFrameSource,
|
|
6
|
+
} from "@supersoniks/concorde/core/utils/mediaRef";
|
|
7
|
+
import { shouldUploadElementFrame } from "../shader/gl-runtime";
|
|
3
8
|
import { WebGpuCompileError } from "./types";
|
|
4
9
|
import { buildWgslSource } from "./wgsl-prelude";
|
|
5
10
|
|
|
6
11
|
const UNIFORM_BYTES = 64; // resolution+time+frame+mouse+params + pad
|
|
7
12
|
const CHANNEL_COUNT = 4;
|
|
8
13
|
|
|
14
|
+
/** `true` si la texture existante peut être réécrite (même taille). */
|
|
15
|
+
export function channelTextureNeedsRecreate(
|
|
16
|
+
current: { width: number; height: number } | null | undefined,
|
|
17
|
+
nextW: number,
|
|
18
|
+
nextH: number,
|
|
19
|
+
): boolean {
|
|
20
|
+
if (!current) return true;
|
|
21
|
+
return current.width !== nextW || current.height !== nextH;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function externalSourceSize(
|
|
25
|
+
src: TexImageSource,
|
|
26
|
+
): { width: number; height: number } {
|
|
27
|
+
if (
|
|
28
|
+
typeof HTMLVideoElement !== "undefined" &&
|
|
29
|
+
src instanceof HTMLVideoElement
|
|
30
|
+
) {
|
|
31
|
+
return { width: src.videoWidth || 1, height: src.videoHeight || 1 };
|
|
32
|
+
}
|
|
33
|
+
if (
|
|
34
|
+
typeof HTMLImageElement !== "undefined" &&
|
|
35
|
+
src instanceof HTMLImageElement
|
|
36
|
+
) {
|
|
37
|
+
return {
|
|
38
|
+
width: src.naturalWidth || src.width || 1,
|
|
39
|
+
height: src.naturalHeight || src.height || 1,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if ("width" in src && "height" in src) {
|
|
43
|
+
return {
|
|
44
|
+
width: Math.max(1, Number(src.width) || 1),
|
|
45
|
+
height: Math.max(1, Number(src.height) || 1),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { width: 1, height: 1 };
|
|
49
|
+
}
|
|
50
|
+
|
|
9
51
|
/** Adapter WebGPU : high-performance → low-power → défaut. */
|
|
10
52
|
export async function requestBestAdapter(
|
|
11
53
|
gpu: GPU = navigator.gpu!,
|
|
@@ -77,6 +119,13 @@ export class WebGpuRuntime {
|
|
|
77
119
|
null,
|
|
78
120
|
];
|
|
79
121
|
private channelUrls: [string, string, string, string] = ["", "", "", ""];
|
|
122
|
+
private channelLastSeq = [0, 0, 0, 0];
|
|
123
|
+
private channelLastSrc: (string | undefined)[] = [
|
|
124
|
+
undefined,
|
|
125
|
+
undefined,
|
|
126
|
+
undefined,
|
|
127
|
+
undefined,
|
|
128
|
+
];
|
|
80
129
|
private placeholder: GPUTexture | null = null;
|
|
81
130
|
private placeholderView: GPUTextureView | null = null;
|
|
82
131
|
private bindGroup: GPUBindGroup | null = null;
|
|
@@ -185,10 +234,16 @@ export class WebGpuRuntime {
|
|
|
185
234
|
async setChannel(index: 0 | 1 | 2 | 3, url: string): Promise<void> {
|
|
186
235
|
this.assertAlive();
|
|
187
236
|
const trimmed = url.trim();
|
|
237
|
+
if (trimmed.startsWith("#")) {
|
|
238
|
+
this.bindElementChannel(index, trimmed);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
188
241
|
if (this.channelUrls[index] === trimmed && this.channelTextures[index]) {
|
|
189
242
|
return;
|
|
190
243
|
}
|
|
191
244
|
this.channelUrls[index] = trimmed;
|
|
245
|
+
this.channelLastSeq[index] = 0;
|
|
246
|
+
this.channelLastSrc[index] = undefined;
|
|
192
247
|
const token = ++this.channelLoadTokens[index];
|
|
193
248
|
if (!trimmed) {
|
|
194
249
|
this.destroyChannel(index);
|
|
@@ -197,24 +252,35 @@ export class WebGpuRuntime {
|
|
|
197
252
|
}
|
|
198
253
|
const img = await loadImage(trimmed);
|
|
199
254
|
if (token !== this.channelLoadTokens[index] || this.disposed) return;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
255
|
+
this.uploadExternal(index, img, /* flipY */ false);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Upload live des canaux `#id` / FrameSource / video / canvas.
|
|
260
|
+
* Recrée la texture seulement si la taille change (pas de createTexture chaque frame).
|
|
261
|
+
*/
|
|
262
|
+
updateLiveChannels(resolveEl: (key: string) => Element | null) {
|
|
263
|
+
this.assertAlive();
|
|
264
|
+
for (let i = 0; i < CHANNEL_COUNT; i++) {
|
|
265
|
+
const key = this.channelUrls[i];
|
|
266
|
+
if (!key.startsWith("#")) continue;
|
|
267
|
+
const el = resolveEl(key);
|
|
268
|
+
const decision = shouldUploadElementFrame(
|
|
269
|
+
el,
|
|
270
|
+
this.channelLastSeq[i],
|
|
271
|
+
this.channelLastSrc[i],
|
|
272
|
+
);
|
|
273
|
+
if (!decision.upload || !decision.texSource) continue;
|
|
274
|
+
this.uploadExternal(i, decision.texSource, /* flipY */ true);
|
|
275
|
+
if (isFrameSource(el)) {
|
|
276
|
+
this.channelLastSeq[i] = (el as SonicFrameSource).frameSeq;
|
|
277
|
+
} else {
|
|
278
|
+
this.channelLastSeq[i] = (this.channelLastSeq[i] || 0) + 1;
|
|
279
|
+
}
|
|
280
|
+
if (decision.nextSrc !== undefined) {
|
|
281
|
+
this.channelLastSrc[i] = decision.nextSrc;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
218
284
|
}
|
|
219
285
|
|
|
220
286
|
getTextureSize(url: string): { width: number; height: number } | null {
|
|
@@ -311,6 +377,76 @@ export class WebGpuRuntime {
|
|
|
311
377
|
}
|
|
312
378
|
}
|
|
313
379
|
|
|
380
|
+
private bindElementChannel(index: 0 | 1 | 2 | 3, key: string) {
|
|
381
|
+
if (this.channelUrls[index] === key && this.channelTextures[index]) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
this.channelUrls[index] = key;
|
|
385
|
+
this.channelLastSeq[index] = 0;
|
|
386
|
+
this.channelLastSrc[index] = undefined;
|
|
387
|
+
this.channelLoadTokens[index]++;
|
|
388
|
+
if (!this.channelTextures[index]) {
|
|
389
|
+
const texture = this.device.createTexture({
|
|
390
|
+
size: [1, 1],
|
|
391
|
+
format: "rgba8unorm",
|
|
392
|
+
usage:
|
|
393
|
+
GPUTextureUsage.TEXTURE_BINDING |
|
|
394
|
+
GPUTextureUsage.COPY_DST |
|
|
395
|
+
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
396
|
+
});
|
|
397
|
+
this.device.queue.writeTexture(
|
|
398
|
+
{ texture },
|
|
399
|
+
new Uint8Array([0, 0, 0, 255]),
|
|
400
|
+
{ bytesPerRow: 4 },
|
|
401
|
+
[1, 1],
|
|
402
|
+
);
|
|
403
|
+
this.channelTextures[index] = texture;
|
|
404
|
+
this.channelViews[index] = texture.createView();
|
|
405
|
+
this.channelSizes[index] = { width: 1, height: 1 };
|
|
406
|
+
this.bindGroup = null;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private uploadExternal(
|
|
411
|
+
index: number,
|
|
412
|
+
source: TexImageSource,
|
|
413
|
+
flipY: boolean,
|
|
414
|
+
) {
|
|
415
|
+
const { width, height } = externalSourceSize(source);
|
|
416
|
+
const w = Math.max(1, width);
|
|
417
|
+
const h = Math.max(1, height);
|
|
418
|
+
const existing = this.channelTextures[index];
|
|
419
|
+
if (
|
|
420
|
+
existing &&
|
|
421
|
+
!channelTextureNeedsRecreate(this.channelSizes[index], w, h)
|
|
422
|
+
) {
|
|
423
|
+
this.device.queue.copyExternalImageToTexture(
|
|
424
|
+
{ source, flipY },
|
|
425
|
+
{ texture: existing },
|
|
426
|
+
[w, h],
|
|
427
|
+
);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const texture = this.device.createTexture({
|
|
431
|
+
size: [w, h],
|
|
432
|
+
format: "rgba8unorm",
|
|
433
|
+
usage:
|
|
434
|
+
GPUTextureUsage.TEXTURE_BINDING |
|
|
435
|
+
GPUTextureUsage.COPY_DST |
|
|
436
|
+
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
437
|
+
});
|
|
438
|
+
this.device.queue.copyExternalImageToTexture(
|
|
439
|
+
{ source, flipY },
|
|
440
|
+
{ texture },
|
|
441
|
+
[w, h],
|
|
442
|
+
);
|
|
443
|
+
this.destroyChannel(index);
|
|
444
|
+
this.channelTextures[index] = texture;
|
|
445
|
+
this.channelViews[index] = texture.createView();
|
|
446
|
+
this.channelSizes[index] = { width: w, height: h };
|
|
447
|
+
this.bindGroup = null;
|
|
448
|
+
}
|
|
449
|
+
|
|
314
450
|
private writeUniforms(state: WebGpuFrameState) {
|
|
315
451
|
const data = new ArrayBuffer(UNIFORM_BYTES);
|
|
316
452
|
const f32 = new Float32Array(data);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toMediaUrl } from "@supersoniks/concorde/core/utils/mediaRef";
|
|
1
|
+
import { toMediaUrl, mediaElementKey } from "@supersoniks/concorde/core/utils/mediaRef";
|
|
2
2
|
|
|
3
3
|
export const LOG_PREFIX = "[sonic-webgpu]";
|
|
4
4
|
|
|
@@ -37,11 +37,17 @@ export function toNum(v: unknown, fallback: number): number {
|
|
|
37
37
|
return fallback;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** string (url / #id) | MediaRef → url | { element }. */
|
|
40
41
|
export function channelFormValue(value: unknown): string | undefined {
|
|
41
42
|
if (value === null || value === "") return "";
|
|
42
43
|
if (typeof value === "string") return value.trim();
|
|
43
44
|
const url = toMediaUrl(value);
|
|
44
45
|
if (url) return url;
|
|
45
|
-
if (value && typeof value === "object")
|
|
46
|
+
if (value && typeof value === "object") {
|
|
47
|
+
const el = (value as { element?: unknown }).element;
|
|
48
|
+
const key = mediaElementKey(el);
|
|
49
|
+
if (key) return key;
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
46
52
|
return "";
|
|
47
53
|
}
|
|
@@ -41,11 +41,22 @@ Simulation: `param0` = speed, `param1` = mouse attraction. WebGPU compute when a
|
|
|
41
41
|
| `active-backend` | reflect |
|
|
42
42
|
| `param0`…`param3` | floats (particles: speed / attraction) |
|
|
43
43
|
| `targetX` / `targetY` | attractor 0–1 (DP, top-left origin) |
|
|
44
|
-
| `channel0`…`channel3` | textures (image kernel) |
|
|
44
|
+
| `channel0`…`channel3` | textures (image kernel) : URL, `#id` live (`SonicFrameSource`), ou MediaRef `{ element }` |
|
|
45
45
|
| `dataProvider` | params / channels / `targetX`·`Y` |
|
|
46
46
|
| `frame-out` | `frameUrl` + `snapshot` |
|
|
47
47
|
| `play` / `mouse` | loop / pointer |
|
|
48
48
|
|
|
49
|
+
## Live bridge (`#id`)
|
|
50
|
+
|
|
51
|
+
Comme `sonic-shader` : `channel0="#plateau"` copie le canvas source chaque `frameSeq` (`copyExternalImageToTexture`). La texture GPU est **réutilisée** tant que la taille ne change pas (pas de `createTexture` / rebuild bind group chaque frame).
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<sonic-3d id="plateau" play …></sonic-3d>
|
|
55
|
+
<sonic-webgpu channel0="#plateau" kernel="image" play shader="…"></sonic-webgpu>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Demo: [Visual stack — 3D → WebGPU live](#docs/_misc/visual-stack.md/visual-stack).
|
|
59
|
+
|
|
49
60
|
## Markup particles
|
|
50
61
|
|
|
51
62
|
<sonic-code language="html">
|
|
@@ -61,7 +72,7 @@ Simulation: `param0` = speed, `param1` = mouse attraction. WebGPU compute when a
|
|
|
61
72
|
|
|
62
73
|
## Chaining
|
|
63
74
|
|
|
64
|
-
Same MediaRef / DP bridge as the rest of the stack.
|
|
75
|
+
Same MediaRef / DP bridge as the rest of the stack. Live textures entre composants : pont `#id` / `{ element }` (copie canvas), pas de contexte WebGPU partagé avec `sonic-shader`.
|
|
65
76
|
|
|
66
77
|
## CDN
|
|
67
78
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { expect, describe, it, vi, beforeAll } from "vitest";
|
|
2
2
|
import TestUtils from "@supersoniks/concorde/test-utils/TestUtils";
|
|
3
3
|
import { buildWgslSource, DEFAULT_MAIN_IMAGE } from "./wgsl-prelude";
|
|
4
|
+
import { channelTextureNeedsRecreate } from "./gpu-runtime";
|
|
5
|
+
import { channelFormValue } from "./helpers";
|
|
4
6
|
import "./webgpu";
|
|
5
7
|
import { SonicWebGpu } from "./webgpu";
|
|
6
8
|
|
|
@@ -28,6 +30,30 @@ describe("buildWgslSource", () => {
|
|
|
28
30
|
});
|
|
29
31
|
});
|
|
30
32
|
|
|
33
|
+
describe("channelTextureNeedsRecreate", () => {
|
|
34
|
+
it("recrée si absent ou taille différente", () => {
|
|
35
|
+
expect(channelTextureNeedsRecreate(null, 64, 64)).toBe(true);
|
|
36
|
+
expect(channelTextureNeedsRecreate({ width: 32, height: 64 }, 64, 64)).toBe(
|
|
37
|
+
true,
|
|
38
|
+
);
|
|
39
|
+
expect(channelTextureNeedsRecreate({ width: 64, height: 64 }, 64, 64)).toBe(
|
|
40
|
+
false,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("channelFormValue", () => {
|
|
46
|
+
it("accepte url / #id / { element }", () => {
|
|
47
|
+
expect(channelFormValue("https://x/a.png")).toBe("https://x/a.png");
|
|
48
|
+
expect(channelFormValue("#plateau")).toBe("#plateau");
|
|
49
|
+
expect(channelFormValue({ element: "#plateau" })).toBe("#plateau");
|
|
50
|
+
expect(channelFormValue({ element: "scene" })).toBe("#scene");
|
|
51
|
+
expect(channelFormValue({ url: "https://x/a.png" })).toBe(
|
|
52
|
+
"https://x/a.png",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
31
57
|
describe("sonic-webgpu", () => {
|
|
32
58
|
beforeAll(() => {
|
|
33
59
|
if (typeof globalThis.ResizeObserver === "undefined") {
|
|
@@ -67,6 +93,7 @@ describe("sonic-webgpu", () => {
|
|
|
67
93
|
param1: 0.22,
|
|
68
94
|
channel0: { url: "https://example.test/a.png" },
|
|
69
95
|
channel1: "https://example.test/b.png",
|
|
96
|
+
channel2: { element: "#plateau" },
|
|
70
97
|
});
|
|
71
98
|
const el = TestUtils.bootstrap(
|
|
72
99
|
`<sonic-webgpu dataProvider="${id}"></sonic-webgpu>`,
|
|
@@ -78,6 +105,7 @@ describe("sonic-webgpu", () => {
|
|
|
78
105
|
expect(el.param1).toBe(0.22);
|
|
79
106
|
expect(el.channel0).toBe("https://example.test/a.png");
|
|
80
107
|
expect(el.channel1).toBe("https://example.test/b.png");
|
|
108
|
+
expect(el.channel2).toBe("#plateau");
|
|
81
109
|
|
|
82
110
|
pub.param0.set(0.99);
|
|
83
111
|
await new Promise((r) => setTimeout(r, 0));
|
|
@@ -138,4 +166,13 @@ describe("sonic-webgpu", () => {
|
|
|
138
166
|
expect(parseBackendPrefer("webgl2")).toBe("webgl2");
|
|
139
167
|
expect(parseBackendPrefer("nope")).toBe("auto");
|
|
140
168
|
});
|
|
169
|
+
|
|
170
|
+
it("expose getFrameCanvas + frameSeq (SonicFrameSource)", () => {
|
|
171
|
+
const el = TestUtils.bootstrap(
|
|
172
|
+
`<sonic-webgpu></sonic-webgpu>`,
|
|
173
|
+
true,
|
|
174
|
+
)[0] as SonicWebGpu;
|
|
175
|
+
expect(typeof el.getFrameCanvas).toBe("function");
|
|
176
|
+
expect(typeof el.frameSeq).toBe("number");
|
|
177
|
+
});
|
|
141
178
|
});
|