@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
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFrameSource,
|
|
3
|
+
type SonicFrameSource,
|
|
4
|
+
} from "@supersoniks/concorde/core/utils/mediaRef";
|
|
1
5
|
import { buildFragmentSource, FULLSCREEN_VERTEX } from "./shadertoy-prelude";
|
|
2
6
|
import {
|
|
3
7
|
ChannelSource,
|
|
4
8
|
PassChannels,
|
|
5
9
|
ReadRectResult,
|
|
10
|
+
ShaderBufferFormat,
|
|
6
11
|
ShaderCompileError,
|
|
7
12
|
ShaderPassId,
|
|
8
13
|
ShaderSources,
|
|
14
|
+
parseBufferFormat,
|
|
9
15
|
} from "./types";
|
|
10
16
|
|
|
11
17
|
const PASS_ORDER: ShaderPassId[] = [
|
|
@@ -55,6 +61,10 @@ type ExternalTexture = {
|
|
|
55
61
|
height: number;
|
|
56
62
|
url: string;
|
|
57
63
|
video?: HTMLVideoElement;
|
|
64
|
+
/** Source live (`#élément`) — image / vidéo / canvas / SonicFrameSource. */
|
|
65
|
+
source?: TexImageSource | SonicFrameSource | null;
|
|
66
|
+
lastSeq: number;
|
|
67
|
+
lastSrc?: string;
|
|
58
68
|
};
|
|
59
69
|
|
|
60
70
|
export type FrameUniforms = {
|
|
@@ -65,10 +75,91 @@ export type FrameUniforms = {
|
|
|
65
75
|
params: [number, number, number, number];
|
|
66
76
|
};
|
|
67
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Décide si un slot élément doit être re-uploadé cette frame.
|
|
80
|
+
* Exporté pour les tests unitaires (stratégie par type).
|
|
81
|
+
*/
|
|
82
|
+
export function shouldUploadElementFrame(
|
|
83
|
+
source: unknown,
|
|
84
|
+
lastSeq: number,
|
|
85
|
+
lastSrc?: string,
|
|
86
|
+
): { upload: boolean; texSource: TexImageSource | null; nextSrc?: string } {
|
|
87
|
+
if (!source) return { upload: false, texSource: null };
|
|
88
|
+
if (
|
|
89
|
+
typeof HTMLImageElement !== "undefined" &&
|
|
90
|
+
source instanceof HTMLImageElement
|
|
91
|
+
) {
|
|
92
|
+
const src = source.currentSrc || source.src || "";
|
|
93
|
+
if (!src || !source.complete || source.naturalWidth < 1) {
|
|
94
|
+
return { upload: false, texSource: null };
|
|
95
|
+
}
|
|
96
|
+
if (lastSrc === src && lastSeq > 0) {
|
|
97
|
+
return { upload: false, texSource: null, nextSrc: src };
|
|
98
|
+
}
|
|
99
|
+
return { upload: true, texSource: source, nextSrc: src };
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
typeof HTMLVideoElement !== "undefined" &&
|
|
103
|
+
source instanceof HTMLVideoElement
|
|
104
|
+
) {
|
|
105
|
+
if (source.readyState < 2 || source.videoWidth < 1) {
|
|
106
|
+
return { upload: false, texSource: null };
|
|
107
|
+
}
|
|
108
|
+
return { upload: true, texSource: source };
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
typeof HTMLCanvasElement !== "undefined" &&
|
|
112
|
+
source instanceof HTMLCanvasElement
|
|
113
|
+
) {
|
|
114
|
+
if (source.width < 1 || source.height < 1) {
|
|
115
|
+
return { upload: false, texSource: null };
|
|
116
|
+
}
|
|
117
|
+
return { upload: true, texSource: source };
|
|
118
|
+
}
|
|
119
|
+
if (isFrameSource(source)) {
|
|
120
|
+
if (source.frameSeq === lastSeq) {
|
|
121
|
+
return { upload: false, texSource: null };
|
|
122
|
+
}
|
|
123
|
+
const canvas = source.getFrameCanvas();
|
|
124
|
+
if (!canvas || canvas.width < 1 || canvas.height < 1) {
|
|
125
|
+
return { upload: false, texSource: null };
|
|
126
|
+
}
|
|
127
|
+
return { upload: true, texSource: canvas };
|
|
128
|
+
}
|
|
129
|
+
return { upload: false, texSource: null };
|
|
130
|
+
}
|
|
131
|
+
|
|
68
132
|
function isVideoUrl(url: string): boolean {
|
|
69
133
|
return /\.(webm|mp4|ogg|mov)(\?|#|$)/i.test(url);
|
|
70
134
|
}
|
|
71
135
|
|
|
136
|
+
function texImageSourceSize(
|
|
137
|
+
src: TexImageSource,
|
|
138
|
+
): { width: number; height: number } {
|
|
139
|
+
if (
|
|
140
|
+
typeof HTMLVideoElement !== "undefined" &&
|
|
141
|
+
src instanceof HTMLVideoElement
|
|
142
|
+
) {
|
|
143
|
+
return { width: src.videoWidth || 1, height: src.videoHeight || 1 };
|
|
144
|
+
}
|
|
145
|
+
if (
|
|
146
|
+
typeof HTMLImageElement !== "undefined" &&
|
|
147
|
+
src instanceof HTMLImageElement
|
|
148
|
+
) {
|
|
149
|
+
return {
|
|
150
|
+
width: src.naturalWidth || src.width || 1,
|
|
151
|
+
height: src.naturalHeight || src.height || 1,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if ("width" in src && "height" in src) {
|
|
155
|
+
return {
|
|
156
|
+
width: Math.max(1, Number(src.width) || 1),
|
|
157
|
+
height: Math.max(1, Number(src.height) || 1),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return { width: 1, height: 1 };
|
|
161
|
+
}
|
|
162
|
+
|
|
72
163
|
function compileShader(
|
|
73
164
|
gl: WebGL2RenderingContext,
|
|
74
165
|
type: number,
|
|
@@ -146,27 +237,110 @@ function getUniforms(
|
|
|
146
237
|
};
|
|
147
238
|
}
|
|
148
239
|
|
|
240
|
+
type BufferGlFormat = {
|
|
241
|
+
requested: ShaderBufferFormat;
|
|
242
|
+
active: ShaderBufferFormat;
|
|
243
|
+
internal: number;
|
|
244
|
+
format: number;
|
|
245
|
+
type: number;
|
|
246
|
+
filter: number;
|
|
247
|
+
float: boolean;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
let warnedFloatFallback = false;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Résout le format GPU des buffers A–D.
|
|
254
|
+
* Exporté pour tests (mock `getExtension`).
|
|
255
|
+
*/
|
|
256
|
+
export function resolveBufferGlFormat(
|
|
257
|
+
gl: WebGL2RenderingContext,
|
|
258
|
+
requested: ShaderBufferFormat,
|
|
259
|
+
): BufferGlFormat {
|
|
260
|
+
const want = parseBufferFormat(requested);
|
|
261
|
+
if (want === "rgba8") {
|
|
262
|
+
return {
|
|
263
|
+
requested: want,
|
|
264
|
+
active: "rgba8",
|
|
265
|
+
internal: gl.RGBA8,
|
|
266
|
+
format: gl.RGBA,
|
|
267
|
+
type: gl.UNSIGNED_BYTE,
|
|
268
|
+
filter: gl.LINEAR,
|
|
269
|
+
float: false,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const ext = gl.getExtension("EXT_color_buffer_float");
|
|
273
|
+
if (!ext) {
|
|
274
|
+
if (!warnedFloatFallback) {
|
|
275
|
+
warnedFloatFallback = true;
|
|
276
|
+
console.warn(
|
|
277
|
+
`[sonic-shader] buffer-format="${want}" indisponible (pas d'EXT_color_buffer_float) → rgba8`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
return resolveBufferGlFormat(gl, "rgba8");
|
|
281
|
+
}
|
|
282
|
+
if (want === "rgba16f") {
|
|
283
|
+
return {
|
|
284
|
+
requested: want,
|
|
285
|
+
active: "rgba16f",
|
|
286
|
+
internal: gl.RGBA16F,
|
|
287
|
+
format: gl.RGBA,
|
|
288
|
+
type: gl.HALF_FLOAT,
|
|
289
|
+
filter: gl.LINEAR,
|
|
290
|
+
float: true,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
// rgba32f
|
|
294
|
+
const linearOk = !!gl.getExtension("OES_texture_float_linear");
|
|
295
|
+
return {
|
|
296
|
+
requested: want,
|
|
297
|
+
active: "rgba32f",
|
|
298
|
+
internal: gl.RGBA32F,
|
|
299
|
+
format: gl.RGBA,
|
|
300
|
+
type: gl.FLOAT,
|
|
301
|
+
filter: linearOk ? gl.LINEAR : gl.NEAREST,
|
|
302
|
+
float: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Reset du warn unique (tests). */
|
|
307
|
+
export function resetBufferFormatWarnFlag() {
|
|
308
|
+
warnedFloatFallback = false;
|
|
309
|
+
}
|
|
310
|
+
|
|
149
311
|
function createEmptyTexture(
|
|
150
312
|
gl: WebGL2RenderingContext,
|
|
151
313
|
width: number,
|
|
152
314
|
height: number,
|
|
315
|
+
fmt?: BufferGlFormat,
|
|
153
316
|
): WebGLTexture {
|
|
317
|
+
const f =
|
|
318
|
+
fmt ??
|
|
319
|
+
({
|
|
320
|
+
requested: "rgba8",
|
|
321
|
+
active: "rgba8",
|
|
322
|
+
internal: gl.RGBA8,
|
|
323
|
+
format: gl.RGBA,
|
|
324
|
+
type: gl.UNSIGNED_BYTE,
|
|
325
|
+
filter: gl.LINEAR,
|
|
326
|
+
float: false,
|
|
327
|
+
} satisfies BufferGlFormat);
|
|
154
328
|
const tex = gl.createTexture();
|
|
155
329
|
if (!tex) throw new Error("Unable to create texture");
|
|
156
330
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
157
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,
|
|
158
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER,
|
|
331
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, f.filter);
|
|
332
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, f.filter);
|
|
159
333
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
160
334
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
161
335
|
gl.texImage2D(
|
|
162
336
|
gl.TEXTURE_2D,
|
|
163
337
|
0,
|
|
164
|
-
|
|
338
|
+
f.internal,
|
|
165
339
|
width,
|
|
166
340
|
height,
|
|
167
341
|
0,
|
|
168
|
-
|
|
169
|
-
|
|
342
|
+
f.format,
|
|
343
|
+
f.type,
|
|
170
344
|
null,
|
|
171
345
|
);
|
|
172
346
|
return tex;
|
|
@@ -198,10 +372,11 @@ function createPingPong(
|
|
|
198
372
|
gl: WebGL2RenderingContext,
|
|
199
373
|
width: number,
|
|
200
374
|
height: number,
|
|
375
|
+
fmt: BufferGlFormat,
|
|
201
376
|
): PingPong {
|
|
202
377
|
const textures: [WebGLTexture, WebGLTexture] = [
|
|
203
|
-
createEmptyTexture(gl, width, height),
|
|
204
|
-
createEmptyTexture(gl, width, height),
|
|
378
|
+
createEmptyTexture(gl, width, height, fmt),
|
|
379
|
+
createEmptyTexture(gl, width, height, fmt),
|
|
205
380
|
];
|
|
206
381
|
const framebuffers: [WebGLFramebuffer, WebGLFramebuffer] = [
|
|
207
382
|
createFramebuffer(gl, textures[0]),
|
|
@@ -221,9 +396,12 @@ export function parseChannelSource(
|
|
|
221
396
|
): ChannelSource {
|
|
222
397
|
const v = (value ?? "").trim();
|
|
223
398
|
if (!v) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
399
|
+
const fb = (fallbackUrl ?? "").trim();
|
|
400
|
+
if (fb.startsWith("#")) return { kind: "element", key: fb };
|
|
401
|
+
return fb ? { kind: "url", url: fb } : { kind: "none" };
|
|
402
|
+
}
|
|
403
|
+
if (v.startsWith("#")) {
|
|
404
|
+
return { kind: "element", key: v };
|
|
227
405
|
}
|
|
228
406
|
const lower = v.toLowerCase();
|
|
229
407
|
if (lower === "self") return { kind: "self" };
|
|
@@ -256,6 +434,8 @@ export class ShaderToyRuntime {
|
|
|
256
434
|
private passChannels = new Map<ShaderPassId, PassChannels>();
|
|
257
435
|
private activePasses: ShaderPassId[] = [];
|
|
258
436
|
private blackTexture: WebGLTexture | null = null;
|
|
437
|
+
private elementResolver: ((key: string) => Element | null) | null = null;
|
|
438
|
+
private bufferFmt: BufferGlFormat;
|
|
259
439
|
|
|
260
440
|
constructor(canvas: HTMLCanvasElement) {
|
|
261
441
|
const gl = canvas.getContext("webgl2", {
|
|
@@ -271,6 +451,7 @@ export class ShaderToyRuntime {
|
|
|
271
451
|
}
|
|
272
452
|
this.canvas = canvas;
|
|
273
453
|
this.gl = gl;
|
|
454
|
+
this.bufferFmt = resolveBufferGlFormat(gl, "rgba8");
|
|
274
455
|
this.initGeometry();
|
|
275
456
|
this.blackTexture = createEmptyTexture(gl, 1, 1);
|
|
276
457
|
gl.bindTexture(gl.TEXTURE_2D, this.blackTexture);
|
|
@@ -287,6 +468,42 @@ export class ShaderToyRuntime {
|
|
|
287
468
|
);
|
|
288
469
|
}
|
|
289
470
|
|
|
471
|
+
/** Format réellement utilisé pour les buffers A–D (après repli éventuel). */
|
|
472
|
+
get activeBufferFormat(): ShaderBufferFormat {
|
|
473
|
+
return this.bufferFmt.active;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Change le format des buffers A–D. Recrée les FBO si la taille est déjà connue.
|
|
478
|
+
* Retourne le format actif (peut différer si extension manquante).
|
|
479
|
+
*/
|
|
480
|
+
setBufferFormat(requested: ShaderBufferFormat): ShaderBufferFormat {
|
|
481
|
+
const next = resolveBufferGlFormat(this.gl, requested);
|
|
482
|
+
if (
|
|
483
|
+
next.active === this.bufferFmt.active &&
|
|
484
|
+
next.filter === this.bufferFmt.filter
|
|
485
|
+
) {
|
|
486
|
+
this.bufferFmt = next;
|
|
487
|
+
return next.active;
|
|
488
|
+
}
|
|
489
|
+
this.bufferFmt = next;
|
|
490
|
+
if (this.width >= 1 && this.height >= 1) {
|
|
491
|
+
for (const id of BUFFER_PASSES) {
|
|
492
|
+
const existing = this.buffers.get(id);
|
|
493
|
+
if (existing) destroyPingPong(this.gl, existing);
|
|
494
|
+
if (this.programs.has(id)) {
|
|
495
|
+
this.buffers.set(
|
|
496
|
+
id,
|
|
497
|
+
createPingPong(this.gl, this.width, this.height, this.bufferFmt),
|
|
498
|
+
);
|
|
499
|
+
} else {
|
|
500
|
+
this.buffers.delete(id);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return next.active;
|
|
505
|
+
}
|
|
506
|
+
|
|
290
507
|
/** True si le contexte a été perdu / déjà disposé. */
|
|
291
508
|
get isLost(): boolean {
|
|
292
509
|
return this.gl.isContextLost();
|
|
@@ -323,7 +540,10 @@ export class ShaderToyRuntime {
|
|
|
323
540
|
const existing = this.buffers.get(id);
|
|
324
541
|
if (existing) destroyPingPong(this.gl, existing);
|
|
325
542
|
if (this.programs.has(id)) {
|
|
326
|
-
this.buffers.set(
|
|
543
|
+
this.buffers.set(
|
|
544
|
+
id,
|
|
545
|
+
createPingPong(this.gl, w, h, this.bufferFmt),
|
|
546
|
+
);
|
|
327
547
|
}
|
|
328
548
|
}
|
|
329
549
|
}
|
|
@@ -365,7 +585,10 @@ export class ShaderToyRuntime {
|
|
|
365
585
|
if (pass !== "image" && this.width > 0) {
|
|
366
586
|
const prev = this.buffers.get(pass);
|
|
367
587
|
if (prev) destroyPingPong(this.gl, prev);
|
|
368
|
-
this.buffers.set(
|
|
588
|
+
this.buffers.set(
|
|
589
|
+
pass,
|
|
590
|
+
createPingPong(this.gl, this.width, this.height, this.bufferFmt),
|
|
591
|
+
);
|
|
369
592
|
}
|
|
370
593
|
}
|
|
371
594
|
|
|
@@ -378,40 +601,132 @@ export class ShaderToyRuntime {
|
|
|
378
601
|
}
|
|
379
602
|
}
|
|
380
603
|
|
|
604
|
+
/**
|
|
605
|
+
* Met à jour les bindings de canaux sans recompiler les programmes.
|
|
606
|
+
* Les URLs / clés `#élément` absentes des textures sont allouées à la demande
|
|
607
|
+
* (via `ensureTextures` / `ensureElementTexture` côté composant).
|
|
608
|
+
*/
|
|
609
|
+
setPassChannels(channels: Map<ShaderPassId, PassChannels>) {
|
|
610
|
+
if (this.gl.isContextLost()) {
|
|
611
|
+
throw new Error("WebGL context lost");
|
|
612
|
+
}
|
|
613
|
+
this.passChannels = channels;
|
|
614
|
+
}
|
|
615
|
+
|
|
381
616
|
async ensureTextures(urls: string[]) {
|
|
382
617
|
const unique = [...new Set(urls.filter(Boolean))];
|
|
383
618
|
await Promise.all(unique.map((url) => this.loadTexture(url)));
|
|
384
619
|
}
|
|
385
620
|
|
|
386
|
-
/**
|
|
387
|
-
|
|
621
|
+
/** Alloue un slot texture vide pour un canal `#élément` (upload live ensuite). */
|
|
622
|
+
ensureElementTexture(key: string) {
|
|
623
|
+
if (this.external.has(key)) return;
|
|
388
624
|
const gl = this.gl;
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
625
|
+
const tex = gl.createTexture();
|
|
626
|
+
if (!tex) return;
|
|
627
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
628
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
629
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
630
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
631
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
632
|
+
gl.texImage2D(
|
|
633
|
+
gl.TEXTURE_2D,
|
|
634
|
+
0,
|
|
635
|
+
gl.RGBA8,
|
|
636
|
+
1,
|
|
637
|
+
1,
|
|
638
|
+
0,
|
|
639
|
+
gl.RGBA,
|
|
640
|
+
gl.UNSIGNED_BYTE,
|
|
641
|
+
new Uint8Array([0, 0, 0, 255]),
|
|
642
|
+
);
|
|
643
|
+
this.external.set(key, {
|
|
644
|
+
texture: tex,
|
|
645
|
+
width: 1,
|
|
646
|
+
height: 1,
|
|
647
|
+
url: key,
|
|
648
|
+
lastSeq: 0,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Upload live des canaux élément / vidéo.
|
|
654
|
+
* `resolveEl(key)` fournit l’Element (résolution `#id` côté host).
|
|
655
|
+
*/
|
|
656
|
+
updateElementTextures(resolveEl?: (key: string) => Element | null) {
|
|
657
|
+
const gl = this.gl;
|
|
658
|
+
for (const [key, ext] of this.external) {
|
|
659
|
+
let source: unknown = ext.video ?? ext.source ?? null;
|
|
660
|
+
// Canaux #id : re-résoudre chaque frame (élément peut apparaître / partir).
|
|
661
|
+
if (key.startsWith("#") && resolveEl) {
|
|
662
|
+
source = resolveEl(key);
|
|
663
|
+
ext.source = source as TexImageSource | SonicFrameSource | null;
|
|
664
|
+
}
|
|
665
|
+
if (
|
|
666
|
+
typeof HTMLVideoElement !== "undefined" &&
|
|
667
|
+
source instanceof HTMLVideoElement
|
|
668
|
+
) {
|
|
669
|
+
ext.video = source;
|
|
670
|
+
}
|
|
671
|
+
const decision = shouldUploadElementFrame(
|
|
672
|
+
source,
|
|
673
|
+
ext.lastSeq,
|
|
674
|
+
ext.lastSrc,
|
|
675
|
+
);
|
|
676
|
+
if (!decision.upload || !decision.texSource) continue;
|
|
677
|
+
const size = texImageSourceSize(decision.texSource);
|
|
392
678
|
gl.bindTexture(gl.TEXTURE_2D, ext.texture);
|
|
393
679
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
|
|
394
|
-
|
|
395
|
-
gl.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
680
|
+
if (ext.width === size.width && ext.height === size.height && ext.lastSeq > 0) {
|
|
681
|
+
gl.texSubImage2D(
|
|
682
|
+
gl.TEXTURE_2D,
|
|
683
|
+
0,
|
|
684
|
+
0,
|
|
685
|
+
0,
|
|
686
|
+
gl.RGBA,
|
|
687
|
+
gl.UNSIGNED_BYTE,
|
|
688
|
+
decision.texSource,
|
|
689
|
+
);
|
|
690
|
+
} else {
|
|
691
|
+
gl.texImage2D(
|
|
692
|
+
gl.TEXTURE_2D,
|
|
693
|
+
0,
|
|
694
|
+
gl.RGBA8,
|
|
695
|
+
gl.RGBA,
|
|
696
|
+
gl.UNSIGNED_BYTE,
|
|
697
|
+
decision.texSource,
|
|
698
|
+
);
|
|
699
|
+
ext.width = size.width;
|
|
700
|
+
ext.height = size.height;
|
|
701
|
+
}
|
|
402
702
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
|
|
403
|
-
|
|
404
|
-
|
|
703
|
+
if (isFrameSource(source)) {
|
|
704
|
+
ext.lastSeq = source.frameSeq;
|
|
705
|
+
} else {
|
|
706
|
+
ext.lastSeq = (ext.lastSeq || 0) + 1;
|
|
707
|
+
}
|
|
708
|
+
if (decision.nextSrc !== undefined) ext.lastSrc = decision.nextSrc;
|
|
405
709
|
}
|
|
406
710
|
}
|
|
407
711
|
|
|
712
|
+
/** @deprecated alias — préférer `updateElementTextures`. */
|
|
713
|
+
updateVideoTextures() {
|
|
714
|
+
this.updateElementTextures();
|
|
715
|
+
}
|
|
716
|
+
|
|
408
717
|
setVideosPlaying(playing: boolean) {
|
|
409
718
|
for (const ext of this.external.values()) {
|
|
410
|
-
|
|
719
|
+
const video =
|
|
720
|
+
ext.video ||
|
|
721
|
+
(typeof HTMLVideoElement !== "undefined" &&
|
|
722
|
+
ext.source instanceof HTMLVideoElement
|
|
723
|
+
? ext.source
|
|
724
|
+
: null);
|
|
725
|
+
if (!video) continue;
|
|
411
726
|
if (playing) {
|
|
412
|
-
void
|
|
727
|
+
void video.play().catch(() => {});
|
|
413
728
|
} else {
|
|
414
|
-
|
|
729
|
+
video.pause();
|
|
415
730
|
}
|
|
416
731
|
}
|
|
417
732
|
}
|
|
@@ -450,6 +765,7 @@ export class ShaderToyRuntime {
|
|
|
450
765
|
width: img.naturalWidth || img.width,
|
|
451
766
|
height: img.naturalHeight || img.height,
|
|
452
767
|
url,
|
|
768
|
+
lastSeq: 0,
|
|
453
769
|
});
|
|
454
770
|
resolve();
|
|
455
771
|
};
|
|
@@ -503,6 +819,7 @@ export class ShaderToyRuntime {
|
|
|
503
819
|
height: video.videoHeight,
|
|
504
820
|
url,
|
|
505
821
|
video,
|
|
822
|
+
lastSeq: 0,
|
|
506
823
|
});
|
|
507
824
|
void video.play().catch(() => {});
|
|
508
825
|
resolve();
|
|
@@ -524,8 +841,9 @@ export class ShaderToyRuntime {
|
|
|
524
841
|
height: 1,
|
|
525
842
|
};
|
|
526
843
|
if (source.kind === "none") return black;
|
|
527
|
-
if (source.kind === "url") {
|
|
528
|
-
const
|
|
844
|
+
if (source.kind === "url" || source.kind === "element") {
|
|
845
|
+
const key = source.kind === "url" ? source.url : source.key;
|
|
846
|
+
const ext = this.external.get(key);
|
|
529
847
|
return ext
|
|
530
848
|
? { texture: ext.texture, width: ext.width, height: ext.height }
|
|
531
849
|
: black;
|
|
@@ -620,10 +938,15 @@ export class ShaderToyRuntime {
|
|
|
620
938
|
}
|
|
621
939
|
}
|
|
622
940
|
|
|
941
|
+
/** Host fournit la résolution `#id` / live Element. */
|
|
942
|
+
setElementResolver(fn: ((key: string) => Element | null) | null) {
|
|
943
|
+
this.elementResolver = fn;
|
|
944
|
+
}
|
|
945
|
+
|
|
623
946
|
render(frame: FrameUniforms) {
|
|
624
947
|
const gl = this.gl;
|
|
625
948
|
if (!this.vao || this.width < 1 || this.height < 1) return;
|
|
626
|
-
this.
|
|
949
|
+
this.updateElementTextures(this.elementResolver ?? undefined);
|
|
627
950
|
gl.bindVertexArray(this.vao);
|
|
628
951
|
gl.viewport(0, 0, this.width, this.height);
|
|
629
952
|
gl.disable(gl.DEPTH_TEST);
|
|
@@ -665,6 +988,7 @@ export class ShaderToyRuntime {
|
|
|
665
988
|
|
|
666
989
|
/**
|
|
667
990
|
* Lit un rectangle d’un buffer ping-pong (dernier write) ou de l’Image (`pass === "image"`).
|
|
991
|
+
* Les buffers flottants renvoient un `Float32Array` ; Image / rgba8 → `Uint8Array`.
|
|
668
992
|
*/
|
|
669
993
|
readBufferRect(
|
|
670
994
|
pass: ShaderPassId,
|
|
@@ -676,7 +1000,14 @@ export class ShaderToyRuntime {
|
|
|
676
1000
|
if (pass === "image") return this.readRect(x, y, w, h);
|
|
677
1001
|
const pp = this.buffers.get(pass);
|
|
678
1002
|
if (!pp) return null;
|
|
679
|
-
return this.readFramebufferRect(
|
|
1003
|
+
return this.readFramebufferRect(
|
|
1004
|
+
pp.framebuffers[pp.index],
|
|
1005
|
+
x,
|
|
1006
|
+
y,
|
|
1007
|
+
w,
|
|
1008
|
+
h,
|
|
1009
|
+
this.bufferFmt.float,
|
|
1010
|
+
);
|
|
680
1011
|
}
|
|
681
1012
|
|
|
682
1013
|
/**
|
|
@@ -695,6 +1026,7 @@ export class ShaderToyRuntime {
|
|
|
695
1026
|
y: number,
|
|
696
1027
|
w: number,
|
|
697
1028
|
h: number,
|
|
1029
|
+
asFloat = false,
|
|
698
1030
|
): ReadRectResult | null {
|
|
699
1031
|
const gl = this.gl;
|
|
700
1032
|
if (gl.isContextLost() || this.width < 1 || this.height < 1) return null;
|
|
@@ -714,9 +1046,15 @@ export class ShaderToyRuntime {
|
|
|
714
1046
|
if (px + pw > this.width) pw = this.width - px;
|
|
715
1047
|
if (py + ph > this.height) ph = this.height - py;
|
|
716
1048
|
if (pw < 1 || ph < 1) return null;
|
|
717
|
-
const data = new Uint8Array(pw * ph * 4);
|
|
718
1049
|
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
719
|
-
|
|
1050
|
+
let data: Uint8Array | Float32Array;
|
|
1051
|
+
if (asFloat) {
|
|
1052
|
+
data = new Float32Array(pw * ph * 4);
|
|
1053
|
+
gl.readPixels(px, py, pw, ph, gl.RGBA, gl.FLOAT, data);
|
|
1054
|
+
} else {
|
|
1055
|
+
data = new Uint8Array(pw * ph * 4);
|
|
1056
|
+
gl.readPixels(px, py, pw, ph, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
1057
|
+
}
|
|
720
1058
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
721
1059
|
return { x: px, y: py, width: pw, height: ph, data };
|
|
722
1060
|
}
|
|
@@ -24,7 +24,7 @@ CDN (after the core): `concorde-shader.bundle.js` — see [CDN addons](#docs/_ge
|
|
|
24
24
|
|
|
25
25
|
Uniforms supportés : `iResolution`, `iTime`, `iTimeDelta`, `iFrame`, `iFrameRate`, `iMouse`, `iDate`, `iChannel0–3`, `iChannelResolution`, `iChannelTime`, `iSampleRate`, `uParam0–3`.
|
|
26
26
|
|
|
27
|
-
Textures `channel0–3` : images
|
|
27
|
+
Textures `channel0–3` : images, vidéos (`.webm` / `.mp4` / …), **ou** éléments page via `#id` (`<img>`, `<video>`, `<canvas>`, `sonic-3d` / `sonic-webgpu` / `sonic-shader`).
|
|
28
28
|
|
|
29
29
|
## Multipass sur une photo
|
|
30
30
|
|
|
@@ -34,20 +34,22 @@ Buffer A simule une height-map d’ondes (ripple) ; Image réfracte la photo via
|
|
|
34
34
|
|
|
35
35
|
Photo + depth parallax (HF): [Visual stack](#docs/_misc/visual-stack.md/visual-stack).
|
|
36
36
|
|
|
37
|
+
Live 3D → shader (no JPEG): [Visual stack](#docs/_misc/visual-stack.md/visual-stack).
|
|
38
|
+
|
|
37
39
|
## Chaînage d’effets (officiel)
|
|
38
40
|
|
|
39
|
-
**Règle :** brancher des passes dans **un** `sonic-shader` (buffers `A–D` → `Image`), pas deux composants via dataProvider.
|
|
41
|
+
**Règle :** brancher des passes dans **un** `sonic-shader` (buffers `A–D` → `Image`), pas deux composants via dataProvider — **sauf** pont live `#élément` (copie canvas chaque frame).
|
|
40
42
|
|
|
41
43
|
| Besoin | Mécanisme |
|
|
42
44
|
|--------|-----------|
|
|
43
45
|
| Sortie d’un effet → entrée du suivant | `image-ch0="buffer-a"` (ou `buffer-b`… / `self`) — **même contexte GPU**, FBO ping-pong |
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
Les passes intermédiaires sont **déjà offscreen** (FBO). Pas besoin d’un second `sonic-shader` invisible.
|
|
46
|
+
| Scene 3D / autre canvas → post | `channel0="#plateau"` — copie `texImage2D` chaque `frameSeq` |
|
|
47
|
+
| Params / sync UI | `dataProvider` → `uParam0…3` **et** canaux (`channel0`, `bufferACh0`…) — string URL, `#id`, ou `SonicMediaRef` (`url` / `element`). Changer un canal ne recompile pas (rebind). |
|
|
48
|
+
| Hors viewport | `release-offscreen` : `auto` (défaut) libère sauf si une passe lit `self` ; `true` / `false` forcent |
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
Les passes intermédiaires sont **déjà offscreen** (FBO). Pas besoin d’un second `sonic-shader` invisible pour du multipass interne.
|
|
50
51
|
|
|
52
|
+
Deux instances séparées ne partagent pas leurs textures WebGL (contextes distincts). Le pont live `#id` **copie** le canvas source ; `frame-out` JPEG reste pour export / freeze.
|
|
51
53
|
Démo pédagogique : Buffer A = Sobel → Image = overlay + `param0`.
|
|
52
54
|
|
|
53
55
|
<docs-lit-demo for="docs-shader-chain-demo"></docs-lit-demo>
|
|
@@ -136,6 +138,22 @@ onExtract() {
|
|
|
136
138
|
|
|
137
139
|
Live Harris → patches → WebGPU: [Visual stack](#docs/_misc/visual-stack.md/visual-stack).
|
|
138
140
|
|
|
141
|
+
## Précision des buffers (`buffer-format`)
|
|
142
|
+
|
|
143
|
+
Par défaut, les buffers A–D sont en **RGBA8** (unsigned). Un tenseur Harris naïf (`Ix*Ix`, `Iy*Iy`, `Ix*Iy`) **perd le signe** de `IxIy` et sature les carrés → bords au lieu de coins.
|
|
144
|
+
|
|
145
|
+
| Valeur | Usage |
|
|
146
|
+
|--------|--------|
|
|
147
|
+
| `rgba8` (défaut) | Couleur / masques 0–1 |
|
|
148
|
+
| `rgba16f` | Calculs signés (Harris, gradients) — recommandé |
|
|
149
|
+
| `rgba32f` | Haute précision ; filtrage `NEAREST` si `OES_texture_float_linear` manque |
|
|
150
|
+
|
|
151
|
+
Repli auto en `rgba8` si `EXT_color_buffer_float` est absent (`console.warn` une fois). L’attribut reflété `active-buffer-format` indique le format réellement utilisé. La passe Image reste le canvas RGBA8.
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<sonic-shader buffer-format="rgba16f" buffer-a="…" …></sonic-shader>
|
|
155
|
+
```
|
|
156
|
+
|
|
139
157
|
## API typée (`SonicShaderConfig`)
|
|
140
158
|
|
|
141
159
|
| Champ / attribut | Type | Rôle |
|
|
@@ -143,8 +161,9 @@ Live Harris → patches → WebGPU: [Visual stack](#docs/_misc/visual-stack.md/v
|
|
|
143
161
|
| `image` | `string` | GLSL pass Image (**requis**, propriété JS) |
|
|
144
162
|
| `bufferA`…`bufferD` / `buffer-a`… | `string` | Passes multipass |
|
|
145
163
|
| `common` | `string` | GLSL partagé |
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
164
|
+
| `bufferFormat` / `buffer-format` | `rgba8` \| `rgba16f` \| `rgba32f` | Format buffers A–D (défaut `rgba8`) ; `active-buffer-format` reflète le format réellement utilisé |
|
|
165
|
+
| `channel0`…`channel3` | `string` | URLs, `#id` élément, ou refs buffers (toutes passes) |
|
|
166
|
+
| `imageCh0`… / `image-ch0`… | `string` | Override canal : URL, `#id`, `self`, `buffer-a`…, `none` |
|
|
148
167
|
| `play` | `boolean` | Boucle RAF (défaut `true`) |
|
|
149
168
|
| `playOnHover` / `play-on-hover` | `boolean` | Anime seulement au hover / transition |
|
|
150
169
|
| `mouse` | `boolean` | Pointeur → `iMouse` (défaut `true`) |
|
|
@@ -168,7 +187,13 @@ Live Harris → patches → WebGPU: [Visual stack](#docs/_misc/visual-stack.md/v
|
|
|
168
187
|
| `extractDataProvider` / `extract-data-provider` | `string` | Publisher extract (défaut = out) |
|
|
169
188
|
| `aspectRatio` / `aspect-ratio` | `string` | Ratio CSS forcé |
|
|
170
189
|
| `dprMax` / `dpr-max` | `number` | Plafond DPR (défaut `2`) |
|
|
171
|
-
| `releaseOffscreen` / `release-offscreen` | `boolean` | Hors viewport : libère textures/FBO (garde le contexte). `dispose` + loseContext au disconnect
|
|
190
|
+
| `releaseOffscreen` / `release-offscreen` | `boolean` \| `"auto"` | Hors viewport : libère textures/FBO (garde le contexte). `auto` (défaut) : ne libère pas si une passe lit `self`. `dispose` + loseContext au disconnect |
|
|
191
|
+
|
|
192
|
+
## État et visibilité
|
|
193
|
+
|
|
194
|
+
Un shader qui garde un état via `self` (feedback frame précédente) n’est plus réinitialisé quand il sort de l’écran : avec `release-offscreen="auto"` (défaut), seuls le RAF et les vidéos sont mis en pause ; les FBO restent. Forcer `release-offscreen` (attribut booléen / `true`) restaure l’ancien comportement (libération hors viewport même avec `self`).
|
|
195
|
+
|
|
196
|
+
Changer `channel0…3` / `*-ch*` re-lie les textures **sans** recompiler ni vider les buffers.
|
|
172
197
|
|
|
173
198
|
## Markup
|
|
174
199
|
|
|
@@ -182,7 +207,7 @@ Live Harris → patches → WebGPU: [Visual stack](#docs/_misc/visual-stack.md/v
|
|
|
182
207
|
hover-speed="12"
|
|
183
208
|
play
|
|
184
209
|
play-on-hover
|
|
185
|
-
release-offscreen
|
|
210
|
+
release-offscreen="auto"
|
|
186
211
|
channel0="/docs/shader-samples/color.jpg"
|
|
187
212
|
></sonic-shader>
|
|
188
213
|
</template>
|