@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
@@ -1,9 +1,94 @@
1
1
  import { expect, describe, it, vi, afterEach, beforeAll } from "vitest";
2
2
  import TestUtils from "@supersoniks/concorde/test-utils/TestUtils";
3
- import { parseChannelSource } from "./gl-runtime";
3
+ import {
4
+ parseChannelSource,
5
+ shouldUploadElementFrame,
6
+ } from "./gl-runtime";
4
7
  import { buildFragmentSource } from "./shadertoy-prelude";
5
8
  import "./shader";
6
- import { SonicShader } from "./shader";
9
+ import { SonicShader, channelFormValue } from "./shader";
10
+
11
+ describe("parseBufferFormat", () => {
12
+ it("accepte rgba8 / rgba16f / rgba32f", async () => {
13
+ const { parseBufferFormat } = await import("./types");
14
+ expect(parseBufferFormat("rgba8")).toBe("rgba8");
15
+ expect(parseBufferFormat("rgba16f")).toBe("rgba16f");
16
+ expect(parseBufferFormat("half")).toBe("rgba16f");
17
+ expect(parseBufferFormat("rgba32f")).toBe("rgba32f");
18
+ expect(parseBufferFormat("float")).toBe("rgba32f");
19
+ expect(parseBufferFormat("")).toBe("rgba8");
20
+ expect(parseBufferFormat("nope")).toBe("rgba8");
21
+ });
22
+ });
23
+
24
+ describe("resolveBufferGlFormat", () => {
25
+ it("reste rgba8 sans extension float", async () => {
26
+ const { resolveBufferGlFormat, resetBufferFormatWarnFlag } = await import(
27
+ "./gl-runtime"
28
+ );
29
+ resetBufferFormatWarnFlag();
30
+ const gl = {
31
+ RGBA8: 0x8058,
32
+ RGBA16F: 0x881a,
33
+ RGBA32F: 0x8814,
34
+ RGBA: 0x1908,
35
+ UNSIGNED_BYTE: 0x1401,
36
+ HALF_FLOAT: 0x140b,
37
+ FLOAT: 0x1406,
38
+ LINEAR: 0x2601,
39
+ NEAREST: 0x2600,
40
+ getExtension: () => null,
41
+ } as unknown as WebGL2RenderingContext;
42
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
43
+ const fmt = resolveBufferGlFormat(gl, "rgba16f");
44
+ expect(fmt.active).toBe("rgba8");
45
+ expect(fmt.float).toBe(false);
46
+ expect(warn).toHaveBeenCalledOnce();
47
+ warn.mockRestore();
48
+ });
49
+
50
+ it("accepte rgba16f avec EXT_color_buffer_float", async () => {
51
+ const { resolveBufferGlFormat, resetBufferFormatWarnFlag } = await import(
52
+ "./gl-runtime"
53
+ );
54
+ resetBufferFormatWarnFlag();
55
+ const gl = {
56
+ RGBA8: 0x8058,
57
+ RGBA16F: 0x881a,
58
+ RGBA32F: 0x8814,
59
+ RGBA: 0x1908,
60
+ UNSIGNED_BYTE: 0x1401,
61
+ HALF_FLOAT: 0x140b,
62
+ FLOAT: 0x1406,
63
+ LINEAR: 0x2601,
64
+ NEAREST: 0x2600,
65
+ getExtension: (name: string) =>
66
+ name === "EXT_color_buffer_float" ? {} : null,
67
+ } as unknown as WebGL2RenderingContext;
68
+ const fmt = resolveBufferGlFormat(gl, "rgba16f");
69
+ expect(fmt.active).toBe("rgba16f");
70
+ expect(fmt.float).toBe(true);
71
+ expect(fmt.type).toBe(gl.HALF_FLOAT);
72
+ });
73
+ });
74
+
75
+ describe("findPeaks float", () => {
76
+ it("lit des scores Float32 déjà normalisés", async () => {
77
+ const { findPeaks } = await import("./types");
78
+ const w = 8;
79
+ const h = 8;
80
+ const data = new Float32Array(w * h * 4);
81
+ data[(3 * w + 3) * 4] = 0.9;
82
+ data[(3 * w + 4) * 4] = 0.4;
83
+ const peaks = findPeaks(data, w, h, {
84
+ threshold: 0.5,
85
+ maxCount: 2,
86
+ minDistance: 2,
87
+ });
88
+ expect(peaks.length).toBeGreaterThanOrEqual(1);
89
+ expect(peaks[0].score).toBeCloseTo(0.9, 5);
90
+ });
91
+ });
7
92
 
8
93
  describe("parseChannelSource", () => {
9
94
  it("résout self / buffers / url / fallback", () => {
@@ -26,6 +111,81 @@ describe("parseChannelSource", () => {
26
111
  });
27
112
  expect(parseChannelSource("none", "")).toEqual({ kind: "none" });
28
113
  });
114
+
115
+ it("résout #id comme élément (pas URL)", () => {
116
+ expect(parseChannelSource("#plateau", "")).toEqual({
117
+ kind: "element",
118
+ key: "#plateau",
119
+ });
120
+ expect(parseChannelSource("", "#scene")).toEqual({
121
+ kind: "element",
122
+ key: "#scene",
123
+ });
124
+ });
125
+ });
126
+
127
+ describe("channelFormValue", () => {
128
+ it("passe { element } → #id", () => {
129
+ expect(channelFormValue({ element: "#plateau" })).toBe("#plateau");
130
+ expect(channelFormValue({ element: "plateau" })).toBe("#plateau");
131
+ expect(channelFormValue({ url: "https://x/a.png" })).toBe(
132
+ "https://x/a.png",
133
+ );
134
+ expect(channelFormValue({ blob: new Blob() })).toBeUndefined();
135
+ });
136
+ });
137
+
138
+ describe("shouldUploadElementFrame", () => {
139
+ it("canvas : upload chaque frame si taille ok", () => {
140
+ const canvas = document.createElement("canvas");
141
+ canvas.width = 4;
142
+ canvas.height = 4;
143
+ expect(shouldUploadElementFrame(canvas, 0).upload).toBe(true);
144
+ expect(shouldUploadElementFrame(canvas, 3).upload).toBe(true);
145
+ });
146
+
147
+ it("FrameSource : skip si frameSeq inchangé, upload sinon", () => {
148
+ const canvas = document.createElement("canvas");
149
+ canvas.width = 2;
150
+ canvas.height = 2;
151
+ const src = {
152
+ frameSeq: 5,
153
+ getFrameCanvas: () => canvas,
154
+ };
155
+ expect(shouldUploadElementFrame(src, 5).upload).toBe(false);
156
+ const up = shouldUploadElementFrame(src, 4);
157
+ expect(up.upload).toBe(true);
158
+ expect(up.texSource).toBe(canvas);
159
+ });
160
+
161
+ it("source absente → pas d’upload (texture noire côté runtime)", () => {
162
+ expect(shouldUploadElementFrame(null, 0).upload).toBe(false);
163
+ expect(
164
+ shouldUploadElementFrame(
165
+ { frameSeq: 1, getFrameCanvas: () => null },
166
+ 0,
167
+ ).upload,
168
+ ).toBe(false);
169
+ });
170
+
171
+ it("image : upload au changement de src seulement", () => {
172
+ const img = document.createElement("img");
173
+ Object.defineProperty(img, "complete", { value: true });
174
+ Object.defineProperty(img, "naturalWidth", { value: 8 });
175
+ Object.defineProperty(img, "naturalHeight", { value: 8 });
176
+ Object.defineProperty(img, "currentSrc", {
177
+ value: "https://example.test/a.png",
178
+ });
179
+ const first = shouldUploadElementFrame(img, 0);
180
+ expect(first.upload).toBe(true);
181
+ expect(first.nextSrc).toBe("https://example.test/a.png");
182
+ const again = shouldUploadElementFrame(
183
+ img,
184
+ 1,
185
+ "https://example.test/a.png",
186
+ );
187
+ expect(again.upload).toBe(false);
188
+ });
29
189
  });
30
190
 
31
191
  describe("buildFragmentSource", () => {
@@ -146,6 +306,39 @@ describe("meanRgba", () => {
146
306
  });
147
307
  });
148
308
 
309
+ describe("release-offscreen auto / self", () => {
310
+ it("parseReleaseOffscreen", async () => {
311
+ const { parseReleaseOffscreen } = await import("./types");
312
+ expect(parseReleaseOffscreen(null)).toBe("auto");
313
+ expect(parseReleaseOffscreen("auto")).toBe("auto");
314
+ expect(parseReleaseOffscreen("")).toBe(true);
315
+ expect(parseReleaseOffscreen("true")).toBe(true);
316
+ expect(parseReleaseOffscreen("false")).toBe(false);
317
+ });
318
+
319
+ it("shouldReleaseWhenOffscreen", async () => {
320
+ const { shouldReleaseWhenOffscreen } = await import("./types");
321
+ expect(shouldReleaseWhenOffscreen("auto", true)).toBe(false);
322
+ expect(shouldReleaseWhenOffscreen("auto", false)).toBe(true);
323
+ expect(shouldReleaseWhenOffscreen(true, true)).toBe(true);
324
+ expect(shouldReleaseWhenOffscreen(false, false)).toBe(false);
325
+ });
326
+
327
+ it("channelsUseSelf détecte self", async () => {
328
+ const { channelsUseSelf } = await import("./types");
329
+ expect(
330
+ channelsUseSelf([
331
+ [{ kind: "url", url: "/a.png" }, { kind: "none" }, { kind: "none" }, { kind: "none" }],
332
+ ]),
333
+ ).toBe(false);
334
+ expect(
335
+ channelsUseSelf([
336
+ [{ kind: "self" }, { kind: "none" }, { kind: "none" }, { kind: "none" }],
337
+ ]),
338
+ ).toBe(true);
339
+ });
340
+ });
341
+
149
342
  describe("SonicShader", () => {
150
343
  afterEach(() => {
151
344
  document.body.innerHTML = "";
@@ -207,7 +400,7 @@ describe("SonicShader", () => {
207
400
  expect(el.param0).toBe(0.99);
208
401
  });
209
402
 
210
- it("applique les canaux depuis dataProvider (url | MediaRef)", async () => {
403
+ it("applique les canaux depuis dataProvider (url | MediaRef | element)", async () => {
211
404
  const { PublisherManager } = await import(
212
405
  "@supersoniks/concorde/utils"
213
406
  );
@@ -217,6 +410,7 @@ describe("SonicShader", () => {
217
410
  bufferACh0: { url: "https://example.test/depth.png" },
218
411
  imageCh0: "buffer-a",
219
412
  imageCh1: "https://example.test/color.jpg",
413
+ channel0: { element: "#plateau" },
220
414
  param0: 0.5,
221
415
  });
222
416
  const el = TestUtils.bootstrap(
@@ -228,6 +422,7 @@ describe("SonicShader", () => {
228
422
  expect(el.bufferACh0).toBe("https://example.test/depth.png");
229
423
  expect(el.imageCh0).toBe("buffer-a");
230
424
  expect(el.imageCh1).toBe("https://example.test/color.jpg");
425
+ expect(el.channel0).toBe("#plateau");
231
426
  expect(el.param0).toBe(0.5);
232
427
  });
233
428
 
@@ -285,4 +480,156 @@ describe("SonicShader", () => {
285
480
  expect(el.shadowRoot?.querySelector("canvas")).toBeTruthy();
286
481
  }
287
482
  });
483
+
484
+ it("changement de canal → rebindChannels, pas rebuild", async () => {
485
+ const el = TestUtils.bootstrap(
486
+ `<sonic-shader style="width:32px;height:32px"></sonic-shader>`,
487
+ true,
488
+ )[0] as SonicShader;
489
+ el.image =
490
+ "void mainImage(out vec4 fragColor, in vec2 fragCoord){ fragColor=vec4(1.0); }";
491
+ await el.updateComplete;
492
+
493
+ const rt = {
494
+ isLost: false,
495
+ setPassChannels: vi.fn(),
496
+ ensureTextures: vi.fn(async () => {}),
497
+ ensureElementTexture: vi.fn(),
498
+ resize: vi.fn(),
499
+ dispose: vi.fn(),
500
+ setVideosPlaying: vi.fn(),
501
+ };
502
+ const internal = el as unknown as {
503
+ runtime: typeof rt;
504
+ ready: boolean;
505
+ rebuild: () => Promise<void>;
506
+ rebindChannels: () => Promise<void>;
507
+ drawFrame: (now: number) => void;
508
+ };
509
+ internal.runtime = rt;
510
+ internal.ready = true;
511
+ vi.spyOn(internal, "drawFrame").mockImplementation(() => {});
512
+
513
+ const rebuildSpy = vi.spyOn(internal, "rebuild");
514
+ const rebindSpy = vi.spyOn(internal, "rebindChannels");
515
+ rebuildSpy.mockClear();
516
+ rebindSpy.mockClear();
517
+ rt.setPassChannels.mockClear();
518
+
519
+ el.bufferACh1 = "#autre";
520
+ await el.updateComplete;
521
+ await new Promise((r) => setTimeout(r, 0));
522
+
523
+ expect(rebuildSpy).not.toHaveBeenCalled();
524
+ expect(rebindSpy).toHaveBeenCalled();
525
+ expect(rt.setPassChannels).toHaveBeenCalled();
526
+ });
527
+
528
+ it("release-offscreen=auto : conserve le runtime si self hors écran", async () => {
529
+ const el = TestUtils.bootstrap(
530
+ `<sonic-shader release-offscreen="auto" style="width:32px;height:32px"></sonic-shader>`,
531
+ true,
532
+ )[0] as SonicShader;
533
+ await el.updateComplete;
534
+ expect(el.releaseOffscreen).toBe("auto");
535
+
536
+ const dispose = vi.fn();
537
+ const rt = {
538
+ isLost: false,
539
+ dispose,
540
+ setVideosPlaying: vi.fn(),
541
+ setPassChannels: vi.fn(),
542
+ ensureTextures: vi.fn(async () => {}),
543
+ ensureElementTexture: vi.fn(),
544
+ resize: vi.fn(),
545
+ };
546
+ const internal = el as unknown as {
547
+ runtime: typeof rt | null;
548
+ ready: boolean;
549
+ inView: boolean;
550
+ syncVisibility: () => void;
551
+ drawFrame: (now: number) => void;
552
+ };
553
+ internal.runtime = rt;
554
+ internal.ready = true;
555
+ internal.inView = true;
556
+ vi.spyOn(internal, "drawFrame").mockImplementation(() => {});
557
+
558
+ el.bufferACh0 = "self";
559
+ await el.updateComplete;
560
+ await new Promise((r) => setTimeout(r, 0));
561
+
562
+ internal.inView = false;
563
+ internal.syncVisibility();
564
+ expect(dispose).not.toHaveBeenCalled();
565
+ expect(internal.runtime).toBe(rt);
566
+ });
567
+
568
+ it("release-offscreen=auto : libère hors écran sans self", async () => {
569
+ const el = TestUtils.bootstrap(
570
+ `<sonic-shader release-offscreen="auto" style="width:32px;height:32px"></sonic-shader>`,
571
+ true,
572
+ )[0] as SonicShader;
573
+ await el.updateComplete;
574
+ const dispose = vi.fn();
575
+ const rt = {
576
+ isLost: false,
577
+ dispose,
578
+ setVideosPlaying: vi.fn(),
579
+ };
580
+ const internal = el as unknown as {
581
+ runtime: typeof rt | null;
582
+ ready: boolean;
583
+ inView: boolean;
584
+ syncVisibility: () => void;
585
+ drawFrame: (now: number) => void;
586
+ };
587
+ internal.runtime = rt;
588
+ internal.ready = true;
589
+ internal.inView = false;
590
+ vi.spyOn(internal, "drawFrame").mockImplementation(() => {});
591
+ internal.syncVisibility();
592
+ expect(dispose).toHaveBeenCalledWith(false);
593
+ expect(internal.runtime).toBeNull();
594
+ });
595
+
596
+ it("release-offscreen=true : libère même avec self", async () => {
597
+ const el = TestUtils.bootstrap(
598
+ `<sonic-shader release-offscreen style="width:32px;height:32px"></sonic-shader>`,
599
+ true,
600
+ )[0] as SonicShader;
601
+ await el.updateComplete;
602
+ expect(el.releaseOffscreen).toBe(true);
603
+
604
+ const dispose = vi.fn();
605
+ const rt = {
606
+ isLost: false,
607
+ dispose,
608
+ setVideosPlaying: vi.fn(),
609
+ setPassChannels: vi.fn(),
610
+ ensureTextures: vi.fn(async () => {}),
611
+ ensureElementTexture: vi.fn(),
612
+ resize: vi.fn(),
613
+ };
614
+ const internal = el as unknown as {
615
+ runtime: typeof rt | null;
616
+ ready: boolean;
617
+ inView: boolean;
618
+ syncVisibility: () => void;
619
+ drawFrame: (now: number) => void;
620
+ };
621
+ internal.runtime = rt;
622
+ internal.ready = true;
623
+ internal.inView = true;
624
+ vi.spyOn(internal, "drawFrame").mockImplementation(() => {});
625
+
626
+ el.bufferACh0 = "self";
627
+ await el.updateComplete;
628
+ await new Promise((r) => setTimeout(r, 0));
629
+
630
+ internal.inView = false;
631
+ internal.syncVisibility();
632
+ expect(dispose).toHaveBeenCalledWith(false);
633
+ expect(internal.runtime).toBeNull();
634
+ });
288
635
  });