@pixerate/flair 0.24.0 → 0.25.0
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/CHANGELOG.md +12 -0
- package/dist/catalog/index.js +2 -2
- package/dist/chunk-DBTLCXKY.js +332 -0
- package/dist/chunk-DBTLCXKY.js.map +1 -0
- package/dist/{chunk-VP6IEWIK.js → chunk-H6L67KHI.js} +1 -1
- package/dist/chunk-H6VDFBIO.js +498 -0
- package/dist/chunk-H6VDFBIO.js.map +1 -0
- package/dist/chunk-JL6TI6II.js +724 -0
- package/dist/chunk-JL6TI6II.js.map +1 -0
- package/dist/{chunk-NGKNI3QX.js → chunk-O44ZJTGG.js} +5 -485
- package/dist/chunk-O44ZJTGG.js.map +1 -0
- package/dist/chunk-OKYMKSXK.js +414 -0
- package/dist/chunk-OKYMKSXK.js.map +1 -0
- package/dist/{chunk-OHW7UG6D.js → chunk-ORAFCHX3.js} +26 -2
- package/dist/{chunk-OHW7UG6D.js.map → chunk-ORAFCHX3.js.map} +1 -1
- package/dist/{chunk-OQ4EWZ3B.js → chunk-PYDN5G2P.js} +5 -5
- package/dist/{chunk-ARKC7LDW.js → chunk-RDU63CDO.js} +2 -331
- package/dist/chunk-RDU63CDO.js.map +1 -0
- package/dist/chunk-S2RGHHKZ.js +902 -0
- package/dist/chunk-S2RGHHKZ.js.map +1 -0
- package/dist/{chunk-LVPIIWPP.js → chunk-STKEKXCK.js} +18 -8
- package/dist/chunk-STKEKXCK.js.map +1 -0
- package/dist/chunk-YBGRXVAE.js +407 -0
- package/dist/chunk-YBGRXVAE.js.map +1 -0
- package/dist/cli.js +68 -5
- package/dist/cli.js.map +1 -1
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +14 -12
- package/dist/editor/index.js.map +1 -1
- package/dist/effect-26CPF5HS.js +47 -0
- package/dist/embed/index.d.ts +215 -0
- package/dist/embed/index.js +21 -0
- package/dist/embed/index.js.map +1 -0
- package/dist/frames-VEcqa40g.d.ts +161 -0
- package/dist/index.d.ts +6 -163
- package/dist/index.js +86 -78
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +167 -7
- package/dist/node.js +25 -3
- package/dist/react/index.d.ts +33 -2
- package/dist/react/index.js +54 -9
- package/dist/react/index.js.map +1 -1
- package/dist/recorder-CDPPTJMG.js +31 -0
- package/dist/recorder-CDPPTJMG.js.map +1 -0
- package/dist/{renderer-C2P1qGzP.d.ts → renderer-teaa98RE.d.ts} +1 -1
- package/dist/svelte/index.d.ts +33 -4
- package/dist/svelte/index.js +51 -9
- package/dist/svelte/index.js.map +1 -1
- package/dist/vector/index.d.ts +52 -1
- package/dist/vector/index.js +8 -4
- package/package.json +1 -1
- package/dist/chunk-ARKC7LDW.js.map +0 -1
- package/dist/chunk-LVPIIWPP.js.map +0 -1
- package/dist/chunk-NGKNI3QX.js.map +0 -1
- package/dist/chunk-UP6QQVRS.js +0 -331
- package/dist/chunk-UP6QQVRS.js.map +0 -1
- package/dist/recorder-HKFFHQES.js +0 -10
- /package/dist/{chunk-VP6IEWIK.js.map → chunk-H6L67KHI.js.map} +0 -0
- /package/dist/{chunk-OQ4EWZ3B.js.map → chunk-PYDN5G2P.js.map} +0 -0
- /package/dist/{recorder-HKFFHQES.js.map → effect-26CPF5HS.js.map} +0 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PointerTracker,
|
|
3
|
+
ShaderCompileError,
|
|
4
|
+
TextureCache,
|
|
5
|
+
clicksUniform,
|
|
6
|
+
compileShader,
|
|
7
|
+
createContext,
|
|
8
|
+
drawFrame,
|
|
9
|
+
resetGlCaches,
|
|
10
|
+
trailUniform
|
|
11
|
+
} from "./chunk-RDU63CDO.js";
|
|
12
|
+
import {
|
|
13
|
+
imageUrls,
|
|
14
|
+
parseControls
|
|
15
|
+
} from "./chunk-VYOMXE6I.js";
|
|
16
|
+
|
|
17
|
+
// src/shader/renderer.ts
|
|
18
|
+
var ShaderRenderer = class {
|
|
19
|
+
canvas;
|
|
20
|
+
gl;
|
|
21
|
+
alpha;
|
|
22
|
+
shader = null;
|
|
23
|
+
textures = null;
|
|
24
|
+
/** Live sources by image control, kept to set again on a restored context. */
|
|
25
|
+
live = /* @__PURE__ */ new Map();
|
|
26
|
+
controls = [];
|
|
27
|
+
values;
|
|
28
|
+
source = "";
|
|
29
|
+
raf = 0;
|
|
30
|
+
lastNow = 0;
|
|
31
|
+
frame = 0;
|
|
32
|
+
fpsFrames = 0;
|
|
33
|
+
fpsSince = 0;
|
|
34
|
+
ownsCanvas = false;
|
|
35
|
+
resizeObserver;
|
|
36
|
+
intersectionObserver;
|
|
37
|
+
wasPlayingBeforeHidden = false;
|
|
38
|
+
hasEnteredViewport = false;
|
|
39
|
+
mouse = [0, 0, 0, 0];
|
|
40
|
+
/** `iPointer` and `iClicks`. */
|
|
41
|
+
pointer = new PointerTracker();
|
|
42
|
+
disposed = false;
|
|
43
|
+
/** The latest source's status. */
|
|
44
|
+
status = { ok: true, errors: [] };
|
|
45
|
+
/** `iTime`, in seconds. Use `seek` to change it. */
|
|
46
|
+
time = 0;
|
|
47
|
+
/** Playback speed: 1 is real time. */
|
|
48
|
+
speed = 1;
|
|
49
|
+
/** Device pixels per CSS pixel; lower renders faster. Use `setPixelRatio` to change it. */
|
|
50
|
+
pixelRatio = Math.min(window.devicePixelRatio || 1, 2);
|
|
51
|
+
playing = true;
|
|
52
|
+
fps = 0;
|
|
53
|
+
onStats;
|
|
54
|
+
/** Called with `true` when the GL context is lost and `false` once it is restored and recompiled. */
|
|
55
|
+
onContextLost;
|
|
56
|
+
/** Called when an image control's URL (or live video) cannot be loaded; the control shows black. */
|
|
57
|
+
onImageError;
|
|
58
|
+
/**
|
|
59
|
+
* Creates a canvas in `target` (with `className` or options), or attaches to an existing
|
|
60
|
+
* `HTMLCanvasElement`, and starts the animation loop. Size the canvas with CSS.
|
|
61
|
+
*/
|
|
62
|
+
constructor(target, options) {
|
|
63
|
+
const opts = typeof options === "string" ? { className: options } : options ?? {};
|
|
64
|
+
let canvas;
|
|
65
|
+
if (target instanceof HTMLCanvasElement || "tagName" in target && target.tagName === "CANVAS") {
|
|
66
|
+
canvas = target;
|
|
67
|
+
if (opts.className) canvas.className = opts.className;
|
|
68
|
+
} else {
|
|
69
|
+
canvas = document.createElement("canvas");
|
|
70
|
+
if (opts.className) canvas.className = opts.className;
|
|
71
|
+
target.appendChild(canvas);
|
|
72
|
+
this.ownsCanvas = true;
|
|
73
|
+
}
|
|
74
|
+
this.canvas = canvas;
|
|
75
|
+
this.alpha = Boolean(opts.alpha);
|
|
76
|
+
this.gl = createContext(canvas, true, this.alpha);
|
|
77
|
+
this.textures = this.gl && this.textureCache(this.gl);
|
|
78
|
+
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
79
|
+
this.resizeObserver.observe(canvas);
|
|
80
|
+
const needsObserver = (opts.pauseWhenHidden || opts.playOnEnter) && typeof IntersectionObserver !== "undefined";
|
|
81
|
+
if (needsObserver) {
|
|
82
|
+
this.intersectionObserver = new IntersectionObserver(([entry]) => {
|
|
83
|
+
if (!entry) return;
|
|
84
|
+
if (entry.isIntersecting) {
|
|
85
|
+
if (opts.playOnEnter && !this.hasEnteredViewport) {
|
|
86
|
+
this.hasEnteredViewport = true;
|
|
87
|
+
if (opts.autoplay ?? true) {
|
|
88
|
+
this.play();
|
|
89
|
+
}
|
|
90
|
+
} else if (this.wasPlayingBeforeHidden) {
|
|
91
|
+
this.wasPlayingBeforeHidden = false;
|
|
92
|
+
this.play();
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
if (this.playing && opts.pauseWhenHidden) {
|
|
96
|
+
this.wasPlayingBeforeHidden = true;
|
|
97
|
+
this.pause();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
this.intersectionObserver.observe(canvas);
|
|
102
|
+
}
|
|
103
|
+
const shouldAutoplay = (opts.autoplay ?? true) && !opts.playOnEnter;
|
|
104
|
+
this.playing = shouldAutoplay;
|
|
105
|
+
canvas.addEventListener("webglcontextlost", this.onLost);
|
|
106
|
+
canvas.addEventListener("webglcontextrestored", this.onRestored);
|
|
107
|
+
canvas.addEventListener("pointerdown", this.onPointerDown);
|
|
108
|
+
canvas.addEventListener("pointermove", this.onPointerMove);
|
|
109
|
+
canvas.addEventListener("pointerenter", this.onPointerEnter);
|
|
110
|
+
canvas.addEventListener("pointerleave", this.onPointerLeave);
|
|
111
|
+
window.addEventListener("pointerup", this.onPointerUp);
|
|
112
|
+
this.resize();
|
|
113
|
+
this.raf = requestAnimationFrame(this.tick);
|
|
114
|
+
}
|
|
115
|
+
/** False when the browser has no WebGL2. */
|
|
116
|
+
get supported() {
|
|
117
|
+
return this.gl !== null;
|
|
118
|
+
}
|
|
119
|
+
/** Compiles a new source; returns its status. Keeps the previous program when it fails. */
|
|
120
|
+
setSource(source) {
|
|
121
|
+
this.source = source;
|
|
122
|
+
const controls = parseControls(source);
|
|
123
|
+
if (!this.gl) {
|
|
124
|
+
this.status = {
|
|
125
|
+
ok: false,
|
|
126
|
+
errors: [{ line: 0, message: "WebGL2 is not available in this browser." }]
|
|
127
|
+
};
|
|
128
|
+
return this.status;
|
|
129
|
+
}
|
|
130
|
+
const result = compileShader(this.gl, source, controls);
|
|
131
|
+
if (result.ok) {
|
|
132
|
+
if (this.shader) this.gl.deleteProgram(this.shader.program);
|
|
133
|
+
this.shader = result.shader;
|
|
134
|
+
this.controls = controls;
|
|
135
|
+
this.status = { ok: true, errors: [] };
|
|
136
|
+
} else {
|
|
137
|
+
this.status = { ok: false, errors: result.errors };
|
|
138
|
+
}
|
|
139
|
+
this.syncTextures();
|
|
140
|
+
if (!this.playing) this.render();
|
|
141
|
+
return this.status;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Sets control values (see `controlValue`); a paused preview redraws.
|
|
145
|
+
* Images load in the background and show once they arrive: await
|
|
146
|
+
* `ready()` before a `capture` that needs them.
|
|
147
|
+
*/
|
|
148
|
+
setValues(values) {
|
|
149
|
+
this.values = values;
|
|
150
|
+
this.syncTextures();
|
|
151
|
+
if (!this.playing) this.render();
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Shows a `<video>` (or a canvas) in the image control `name` (the
|
|
155
|
+
* uniform's name, such as `uImage`) in place of its URL, frame by frame;
|
|
156
|
+
* `null` goes back to the URL. The preview follows the video even while
|
|
157
|
+
* the shader's own clock is paused, and the video plays, seeks and loops
|
|
158
|
+
* on its own: the renderer never controls it. A video from another site
|
|
159
|
+
* needs `crossOrigin = "anonymous"` and CORS, or `onImageError` hears why
|
|
160
|
+
* it shows black. The value stays as it is, so documents, presets and
|
|
161
|
+
* agents keep seeing the URL.
|
|
162
|
+
*/
|
|
163
|
+
setLiveSource(name, source) {
|
|
164
|
+
if (source) this.live.set(name, source);
|
|
165
|
+
else this.live.delete(name);
|
|
166
|
+
this.textures?.setLive(name, source);
|
|
167
|
+
if (!this.playing) this.render();
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Settles once the images the current controls show have loaded, and
|
|
171
|
+
* every live video has a frame. Rejects with an {@link ImageLoadError}
|
|
172
|
+
* when one cannot be (it shows black).
|
|
173
|
+
*/
|
|
174
|
+
async ready() {
|
|
175
|
+
await Promise.all([
|
|
176
|
+
this.textures?.ready(imageUrls(this.controls, this.values)),
|
|
177
|
+
this.textures?.liveReady()
|
|
178
|
+
]);
|
|
179
|
+
}
|
|
180
|
+
play() {
|
|
181
|
+
this.playing = true;
|
|
182
|
+
this.emit();
|
|
183
|
+
}
|
|
184
|
+
pause() {
|
|
185
|
+
this.playing = false;
|
|
186
|
+
this.emit();
|
|
187
|
+
}
|
|
188
|
+
/** Back to `iTime` 0 and frame 0. */
|
|
189
|
+
restart() {
|
|
190
|
+
this.time = 0;
|
|
191
|
+
this.pointer.clear();
|
|
192
|
+
this.frame = 0;
|
|
193
|
+
this.render();
|
|
194
|
+
this.emit();
|
|
195
|
+
}
|
|
196
|
+
/** Jumps to `time` seconds (not below 0) and draws it. */
|
|
197
|
+
seek(time) {
|
|
198
|
+
if (time < this.time) this.pointer.clear();
|
|
199
|
+
this.time = Math.max(0, time);
|
|
200
|
+
this.render();
|
|
201
|
+
this.emit();
|
|
202
|
+
}
|
|
203
|
+
setPixelRatio(ratio) {
|
|
204
|
+
this.pixelRatio = ratio;
|
|
205
|
+
this.resize();
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Captures the current frame (optionally at another time) as a data URL
|
|
209
|
+
* (JPEG unless `type` says PNG), at most `maxEdge` pixels on the long side
|
|
210
|
+
* (default 1024). Playback time is left as it was. Null when nothing has
|
|
211
|
+
* compiled yet.
|
|
212
|
+
*/
|
|
213
|
+
capture(options = {}) {
|
|
214
|
+
if (!this.gl || !this.shader) return null;
|
|
215
|
+
const saved = this.time;
|
|
216
|
+
if (options.time !== void 0) this.time = Math.max(0, options.time);
|
|
217
|
+
this.render();
|
|
218
|
+
const { width, height } = this.canvas;
|
|
219
|
+
const scale = Math.min(1, (options.maxEdge ?? 1024) / Math.max(width, height));
|
|
220
|
+
const out = document.createElement("canvas");
|
|
221
|
+
out.width = Math.max(1, Math.round(width * scale));
|
|
222
|
+
out.height = Math.max(1, Math.round(height * scale));
|
|
223
|
+
out.getContext("2d").drawImage(this.canvas, 0, 0, out.width, out.height);
|
|
224
|
+
this.time = saved;
|
|
225
|
+
if (options.time !== void 0) this.render();
|
|
226
|
+
return out.toDataURL(options.type ?? "image/jpeg", 0.85);
|
|
227
|
+
}
|
|
228
|
+
/** Stops the loop, releases the context and removes the canvas. The renderer cannot be used again. */
|
|
229
|
+
dispose() {
|
|
230
|
+
this.disposed = true;
|
|
231
|
+
cancelAnimationFrame(this.raf);
|
|
232
|
+
this.resizeObserver.disconnect();
|
|
233
|
+
this.intersectionObserver?.disconnect();
|
|
234
|
+
this.canvas.removeEventListener("webglcontextlost", this.onLost);
|
|
235
|
+
this.canvas.removeEventListener("webglcontextrestored", this.onRestored);
|
|
236
|
+
this.canvas.removeEventListener("pointerdown", this.onPointerDown);
|
|
237
|
+
this.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
238
|
+
this.canvas.removeEventListener("pointerenter", this.onPointerEnter);
|
|
239
|
+
this.canvas.removeEventListener("pointerleave", this.onPointerLeave);
|
|
240
|
+
window.removeEventListener("pointerup", this.onPointerUp);
|
|
241
|
+
if (this.gl && this.shader) this.gl.deleteProgram(this.shader.program);
|
|
242
|
+
this.textures?.dispose();
|
|
243
|
+
this.textures = null;
|
|
244
|
+
this.gl?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
245
|
+
this.gl = null;
|
|
246
|
+
if (this.ownsCanvas) this.canvas.remove();
|
|
247
|
+
}
|
|
248
|
+
resize() {
|
|
249
|
+
const w = Math.max(1, Math.round(this.canvas.clientWidth * this.pixelRatio));
|
|
250
|
+
const h = Math.max(1, Math.round(this.canvas.clientHeight * this.pixelRatio));
|
|
251
|
+
if (this.canvas.width !== w || this.canvas.height !== h) {
|
|
252
|
+
this.canvas.width = w;
|
|
253
|
+
this.canvas.height = h;
|
|
254
|
+
this.render();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
render(delta = 0) {
|
|
258
|
+
if (!this.gl || !this.shader || this.gl.isContextLost()) return;
|
|
259
|
+
drawFrame(
|
|
260
|
+
this.gl,
|
|
261
|
+
this.shader,
|
|
262
|
+
this.controls,
|
|
263
|
+
this.values,
|
|
264
|
+
{
|
|
265
|
+
width: this.canvas.width,
|
|
266
|
+
height: this.canvas.height,
|
|
267
|
+
time: this.time,
|
|
268
|
+
delta,
|
|
269
|
+
frame: this.frame,
|
|
270
|
+
mouse: this.mouse,
|
|
271
|
+
pointer: this.pointer.pointer(this.time),
|
|
272
|
+
clicks: this.pointer.clicksAt(this.time),
|
|
273
|
+
trail: this.pointer.trailAt(this.time)
|
|
274
|
+
},
|
|
275
|
+
this.textures ?? void 0
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
textureCache(gl) {
|
|
279
|
+
const cache = new TextureCache(gl, (_url, error) => {
|
|
280
|
+
if (error) this.onImageError?.(error);
|
|
281
|
+
else if (!this.playing) this.render();
|
|
282
|
+
});
|
|
283
|
+
for (const [name, source] of this.live) cache.setLive(name, source);
|
|
284
|
+
return cache;
|
|
285
|
+
}
|
|
286
|
+
/** Starts loading the images now shown, and frees the ones no longer shown. */
|
|
287
|
+
syncTextures() {
|
|
288
|
+
if (!this.textures) return;
|
|
289
|
+
const urls = imageUrls(this.controls, this.values);
|
|
290
|
+
this.textures.retain(urls);
|
|
291
|
+
for (const url of urls) this.textures.request(url);
|
|
292
|
+
}
|
|
293
|
+
tick = (now) => {
|
|
294
|
+
if (this.disposed) return;
|
|
295
|
+
this.raf = requestAnimationFrame(this.tick);
|
|
296
|
+
const delta = this.lastNow ? Math.min((now - this.lastNow) / 1e3, 0.1) : 0;
|
|
297
|
+
this.lastNow = now;
|
|
298
|
+
if (this.playing) {
|
|
299
|
+
this.time += delta * this.speed;
|
|
300
|
+
this.frame += 1;
|
|
301
|
+
this.render(delta * this.speed);
|
|
302
|
+
this.fpsFrames += 1;
|
|
303
|
+
} else if (this.textures?.liveChanged()) {
|
|
304
|
+
this.render();
|
|
305
|
+
}
|
|
306
|
+
if (now - this.fpsSince >= 500) {
|
|
307
|
+
this.fps = this.playing ? Math.round(this.fpsFrames * 1e3 / (now - this.fpsSince || 1)) : 0;
|
|
308
|
+
this.fpsFrames = 0;
|
|
309
|
+
this.fpsSince = now;
|
|
310
|
+
this.emit();
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
emit() {
|
|
314
|
+
this.onStats?.({
|
|
315
|
+
time: this.time,
|
|
316
|
+
fps: this.fps,
|
|
317
|
+
playing: this.playing,
|
|
318
|
+
width: this.canvas.width,
|
|
319
|
+
height: this.canvas.height
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/** Pointer position in shader pixels (origin bottom-left, like gl_FragCoord). */
|
|
323
|
+
toPixels(e) {
|
|
324
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
325
|
+
return [
|
|
326
|
+
(e.clientX - rect.left) * (this.canvas.width / rect.width),
|
|
327
|
+
(rect.bottom - e.clientY) * (this.canvas.height / rect.height)
|
|
328
|
+
];
|
|
329
|
+
}
|
|
330
|
+
onPointerDown = (e) => {
|
|
331
|
+
const [x, y] = this.toPixels(e);
|
|
332
|
+
this.mouse = [x, y, x, y];
|
|
333
|
+
this.pointer.down(x, y, this.time);
|
|
334
|
+
if (!this.playing) this.render();
|
|
335
|
+
};
|
|
336
|
+
onPointerMove = (e) => {
|
|
337
|
+
const [x, y] = this.toPixels(e);
|
|
338
|
+
this.pointer.move(x, y, this.time);
|
|
339
|
+
if (this.mouse[2] <= 0 || e.buttons === 0) {
|
|
340
|
+
if (!this.playing) this.render();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
this.mouse = [x, y, this.mouse[2], this.mouse[3]];
|
|
344
|
+
if (!this.playing) this.render();
|
|
345
|
+
};
|
|
346
|
+
onPointerEnter = (e) => {
|
|
347
|
+
this.pointer.move(...this.toPixels(e), this.time);
|
|
348
|
+
this.pointer.hover(true, this.time);
|
|
349
|
+
};
|
|
350
|
+
onPointerLeave = () => {
|
|
351
|
+
this.pointer.hover(false, this.time);
|
|
352
|
+
};
|
|
353
|
+
onPointerUp = () => {
|
|
354
|
+
this.pointer.up(this.time);
|
|
355
|
+
if (this.mouse[2] > 0)
|
|
356
|
+
this.mouse = [this.mouse[0], this.mouse[1], -this.mouse[2], -Math.abs(this.mouse[3])];
|
|
357
|
+
};
|
|
358
|
+
onLost = (e) => {
|
|
359
|
+
e.preventDefault();
|
|
360
|
+
this.shader = null;
|
|
361
|
+
this.textures?.dispose();
|
|
362
|
+
this.textures = null;
|
|
363
|
+
this.onContextLost?.(true);
|
|
364
|
+
};
|
|
365
|
+
onRestored = () => {
|
|
366
|
+
resetGlCaches();
|
|
367
|
+
this.gl = createContext(this.canvas, true, this.alpha);
|
|
368
|
+
this.textures = this.gl && this.textureCache(this.gl);
|
|
369
|
+
this.setSource(this.source);
|
|
370
|
+
this.onContextLost?.(false);
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/shader/frames.ts
|
|
375
|
+
var ShaderFrames = class {
|
|
376
|
+
canvas;
|
|
377
|
+
width;
|
|
378
|
+
height;
|
|
379
|
+
controls;
|
|
380
|
+
gl;
|
|
381
|
+
shader;
|
|
382
|
+
textures;
|
|
383
|
+
values;
|
|
384
|
+
rendered = 0;
|
|
385
|
+
lastTime = null;
|
|
386
|
+
pixels;
|
|
387
|
+
constructor(options) {
|
|
388
|
+
const { source, values, width, height } = options;
|
|
389
|
+
this.width = Math.max(1, Math.round(width));
|
|
390
|
+
this.height = Math.max(1, Math.round(height));
|
|
391
|
+
this.canvas = options.canvas ?? document.createElement("canvas");
|
|
392
|
+
this.canvas.width = this.width;
|
|
393
|
+
this.canvas.height = this.height;
|
|
394
|
+
this.values = values;
|
|
395
|
+
const gl = createContext(this.canvas, true, Boolean(options.alpha));
|
|
396
|
+
if (!gl) throw new Error("WebGL2 is not available in this browser.");
|
|
397
|
+
this.gl = gl;
|
|
398
|
+
this.controls = parseControls(source);
|
|
399
|
+
const compiled = compileShader(gl, source, this.controls);
|
|
400
|
+
if (!compiled.ok) {
|
|
401
|
+
this.dispose();
|
|
402
|
+
throw new ShaderCompileError(compiled.errors);
|
|
403
|
+
}
|
|
404
|
+
this.shader = compiled.shader;
|
|
405
|
+
this.textures = new TextureCache(gl);
|
|
406
|
+
this.syncTextures();
|
|
407
|
+
this.pixels = new Uint8Array(this.width * this.height * 4);
|
|
408
|
+
}
|
|
409
|
+
/** Changes control values for the frames that follow. */
|
|
410
|
+
setValues(values) {
|
|
411
|
+
this.values = values;
|
|
412
|
+
this.syncTextures();
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Draws a `<video>` (or a canvas) in the image control `name` instead of
|
|
416
|
+
* its URL, at the frame it is on when `render` runs; `null` goes back to
|
|
417
|
+
* the URL. See `ShaderRenderer.setLiveSource`.
|
|
418
|
+
*/
|
|
419
|
+
setLiveSource(name, source) {
|
|
420
|
+
this.textures.setLive(name, source);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Settles once every image the controls show has loaded, and every live
|
|
424
|
+
* video has a frame. Rejects with an {@link ImageLoadError} when one
|
|
425
|
+
* cannot be.
|
|
426
|
+
*/
|
|
427
|
+
async ready() {
|
|
428
|
+
await Promise.all([this.textures.ready(imageUrls(this.controls, this.values)), this.textures.liveReady()]);
|
|
429
|
+
}
|
|
430
|
+
/** Draws the frame at `time` seconds onto `canvas`. */
|
|
431
|
+
render(time, options = {}) {
|
|
432
|
+
const gl = this.context();
|
|
433
|
+
const delta = options.delta ?? (this.lastTime === null ? 0 : time - this.lastTime);
|
|
434
|
+
drawFrame(
|
|
435
|
+
gl,
|
|
436
|
+
this.shader,
|
|
437
|
+
this.controls,
|
|
438
|
+
this.values,
|
|
439
|
+
{
|
|
440
|
+
width: this.width,
|
|
441
|
+
height: this.height,
|
|
442
|
+
time,
|
|
443
|
+
delta,
|
|
444
|
+
frame: options.frame ?? this.rendered,
|
|
445
|
+
mouse: options.mouse ?? [0, 0, 0, 0],
|
|
446
|
+
pointer: options.pointer,
|
|
447
|
+
clicks: options.clicks && clicksUniform(options.clicks, time),
|
|
448
|
+
trail: options.trail && trailUniform(options.trail, time)
|
|
449
|
+
},
|
|
450
|
+
this.textures
|
|
451
|
+
);
|
|
452
|
+
this.rendered += 1;
|
|
453
|
+
this.lastTime = time;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* The last rendered frame as RGBA bytes, rows top down (as in `ImageData`;
|
|
457
|
+
* WebGL itself reads bottom up). Pass `out` to reuse a buffer of
|
|
458
|
+
* `width * height * 4` bytes.
|
|
459
|
+
*/
|
|
460
|
+
readPixels(out) {
|
|
461
|
+
const gl = this.context();
|
|
462
|
+
const { width, height } = this;
|
|
463
|
+
const target = out ?? new Uint8Array(width * height * 4);
|
|
464
|
+
if (target.length !== width * height * 4) {
|
|
465
|
+
throw new RangeError(`readPixels needs a buffer of ${width * height * 4} bytes.`);
|
|
466
|
+
}
|
|
467
|
+
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
|
|
468
|
+
const row = width * 4;
|
|
469
|
+
for (let y = 0; y < height; y++) {
|
|
470
|
+
target.set(this.pixels.subarray((height - 1 - y) * row, (height - y) * row), y * row);
|
|
471
|
+
}
|
|
472
|
+
return target;
|
|
473
|
+
}
|
|
474
|
+
/** Releases the GL context now rather than at garbage collection (browsers cap live contexts). */
|
|
475
|
+
dispose() {
|
|
476
|
+
if (!this.gl) return;
|
|
477
|
+
if (this.shader) this.gl.deleteProgram(this.shader.program);
|
|
478
|
+
this.textures?.dispose();
|
|
479
|
+
this.gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
480
|
+
this.gl = null;
|
|
481
|
+
}
|
|
482
|
+
syncTextures() {
|
|
483
|
+
const urls = imageUrls(this.controls, this.values);
|
|
484
|
+
this.textures.retain(urls);
|
|
485
|
+
for (const url of urls) this.textures.request(url);
|
|
486
|
+
}
|
|
487
|
+
context() {
|
|
488
|
+
if (!this.gl) throw new Error("These frames have been disposed.");
|
|
489
|
+
if (this.gl.isContextLost()) throw new Error("The graphics context was lost.");
|
|
490
|
+
return this.gl;
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
export {
|
|
495
|
+
ShaderRenderer,
|
|
496
|
+
ShaderFrames
|
|
497
|
+
};
|
|
498
|
+
//# sourceMappingURL=chunk-H6VDFBIO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shader/renderer.ts","../src/shader/frames.ts"],"sourcesContent":["import { imageUrls, parseControls, type ControlDef, type ControlValue } from \"./controls.js\";\nimport {\n compileShader,\n createContext,\n drawFrame,\n resetGlCaches,\n type CompileError,\n type CompiledShader,\n} from \"./program.js\";\nimport { PointerTracker } from \"./pointer.js\";\nimport { TextureCache, type ImageLoadError, type LiveSource } from \"./textures.js\";\n\n/** Whether the latest source compiled, and if not, why. */\nexport interface CompileStatus {\n ok: boolean;\n errors: CompileError[];\n}\n\n/** What `onStats` reports: on play, pause, seek and restart, and twice a second while running. */\nexport interface RendererStats {\n /** `iTime`, in seconds. */\n time: number;\n /** Frames drawn per second over the last half second (0 while paused). */\n fps: number;\n playing: boolean;\n /** The drawing buffer's size in device pixels. */\n width: number;\n height: number;\n}\n\nexport interface ShaderRendererOptions {\n className?: string;\n /** Enable a transparent WebGL drawing buffer so shader alpha (`fragColor.a`) composites over the page (default false). */\n alpha?: boolean;\n /** Pause rendering when scrolled out of view via IntersectionObserver (default false). */\n pauseWhenHidden?: boolean;\n /** Start playing at once (default true). Set to false to start paused. */\n autoplay?: boolean;\n /** Wait to start playing until the canvas enters the viewport via IntersectionObserver (default false). When true, autoplay is deferred until first visible. */\n playOnEnter?: boolean;\n}\n\n/**\n * Runs one shader on a canvas: the editor's live preview. A source that does\n * not compile leaves the last working program on screen, so a typo never\n * blanks the preview; the errors are reported instead.\n *\n * The renderer owns its canvas (added to `host`, removed on dispose): a\n * canvas keeps its WebGL context for life, so reusing one after disposing\n * (a remount, hot reload) would get the released, lost context back.\n */\nexport class ShaderRenderer {\n readonly canvas: HTMLCanvasElement;\n private gl: WebGL2RenderingContext | null;\n private readonly alpha: boolean;\n private shader: CompiledShader | null = null;\n private textures: TextureCache | null = null;\n /** Live sources by image control, kept to set again on a restored context. */\n private live = new Map<string, LiveSource>();\n private controls: ControlDef[] = [];\n private values: Record<string, ControlValue> | undefined;\n private source = \"\";\n private raf = 0;\n private lastNow = 0;\n private frame = 0;\n private fpsFrames = 0;\n private fpsSince = 0;\n private ownsCanvas = false;\n private resizeObserver: ResizeObserver;\n private intersectionObserver?: IntersectionObserver;\n private wasPlayingBeforeHidden = false;\n private hasEnteredViewport = false;\n private mouse: [number, number, number, number] = [0, 0, 0, 0];\n /** `iPointer` and `iClicks`. */\n private pointer = new PointerTracker();\n private disposed = false;\n /** The latest source's status. */\n status: CompileStatus = { ok: true, errors: [] };\n /** `iTime`, in seconds. Use `seek` to change it. */\n time = 0;\n /** Playback speed: 1 is real time. */\n speed = 1;\n /** Device pixels per CSS pixel; lower renders faster. Use `setPixelRatio` to change it. */\n pixelRatio = Math.min(window.devicePixelRatio || 1, 2);\n playing = true;\n fps = 0;\n onStats?: (stats: RendererStats) => void;\n /** Called with `true` when the GL context is lost and `false` once it is restored and recompiled. */\n onContextLost?: (lost: boolean) => void;\n /** Called when an image control's URL (or live video) cannot be loaded; the control shows black. */\n onImageError?: (error: ImageLoadError) => void;\n\n /**\n * Creates a canvas in `target` (with `className` or options), or attaches to an existing\n * `HTMLCanvasElement`, and starts the animation loop. Size the canvas with CSS.\n */\n constructor(target: HTMLElement | HTMLCanvasElement, options?: string | ShaderRendererOptions) {\n const opts: ShaderRendererOptions =\n typeof options === \"string\" ? { className: options } : (options ?? {});\n let canvas: HTMLCanvasElement;\n if (target instanceof HTMLCanvasElement || (\"tagName\" in target && target.tagName === \"CANVAS\")) {\n canvas = target as HTMLCanvasElement;\n if (opts.className) canvas.className = opts.className;\n } else {\n canvas = document.createElement(\"canvas\");\n if (opts.className) canvas.className = opts.className;\n target.appendChild(canvas);\n this.ownsCanvas = true;\n }\n this.canvas = canvas;\n this.alpha = Boolean(opts.alpha);\n this.gl = createContext(canvas, true, this.alpha);\n this.textures = this.gl && this.textureCache(this.gl);\n this.resizeObserver = new ResizeObserver(() => this.resize());\n this.resizeObserver.observe(canvas);\n\n const needsObserver =\n (opts.pauseWhenHidden || opts.playOnEnter) && typeof IntersectionObserver !== \"undefined\";\n\n if (needsObserver) {\n this.intersectionObserver = new IntersectionObserver(([entry]) => {\n if (!entry) return;\n if (entry.isIntersecting) {\n if (opts.playOnEnter && !this.hasEnteredViewport) {\n this.hasEnteredViewport = true;\n if (opts.autoplay ?? true) {\n this.play();\n }\n } else if (this.wasPlayingBeforeHidden) {\n this.wasPlayingBeforeHidden = false;\n this.play();\n }\n } else {\n if (this.playing && opts.pauseWhenHidden) {\n this.wasPlayingBeforeHidden = true;\n this.pause();\n }\n }\n });\n this.intersectionObserver.observe(canvas);\n }\n\n const shouldAutoplay = (opts.autoplay ?? true) && !opts.playOnEnter;\n this.playing = shouldAutoplay;\n\n canvas.addEventListener(\"webglcontextlost\", this.onLost);\n canvas.addEventListener(\"webglcontextrestored\", this.onRestored);\n canvas.addEventListener(\"pointerdown\", this.onPointerDown);\n canvas.addEventListener(\"pointermove\", this.onPointerMove);\n canvas.addEventListener(\"pointerenter\", this.onPointerEnter);\n canvas.addEventListener(\"pointerleave\", this.onPointerLeave);\n window.addEventListener(\"pointerup\", this.onPointerUp);\n this.resize();\n this.raf = requestAnimationFrame(this.tick);\n }\n\n /** False when the browser has no WebGL2. */\n get supported(): boolean {\n return this.gl !== null;\n }\n\n /** Compiles a new source; returns its status. Keeps the previous program when it fails. */\n setSource(source: string): CompileStatus {\n this.source = source;\n const controls = parseControls(source);\n if (!this.gl) {\n this.status = {\n ok: false,\n errors: [{ line: 0, message: \"WebGL2 is not available in this browser.\" }],\n };\n return this.status;\n }\n const result = compileShader(this.gl, source, controls);\n if (result.ok) {\n if (this.shader) this.gl.deleteProgram(this.shader.program);\n this.shader = result.shader;\n this.controls = controls;\n this.status = { ok: true, errors: [] };\n } else {\n this.status = { ok: false, errors: result.errors };\n }\n this.syncTextures();\n if (!this.playing) this.render();\n return this.status;\n }\n\n /**\n * Sets control values (see `controlValue`); a paused preview redraws.\n * Images load in the background and show once they arrive: await\n * `ready()` before a `capture` that needs them.\n */\n setValues(values: Record<string, ControlValue> | undefined) {\n this.values = values;\n this.syncTextures();\n if (!this.playing) this.render();\n }\n\n /**\n * Shows a `<video>` (or a canvas) in the image control `name` (the\n * uniform's name, such as `uImage`) in place of its URL, frame by frame;\n * `null` goes back to the URL. The preview follows the video even while\n * the shader's own clock is paused, and the video plays, seeks and loops\n * on its own: the renderer never controls it. A video from another site\n * needs `crossOrigin = \"anonymous\"` and CORS, or `onImageError` hears why\n * it shows black. The value stays as it is, so documents, presets and\n * agents keep seeing the URL.\n */\n setLiveSource(name: string, source: LiveSource | null) {\n if (source) this.live.set(name, source);\n else this.live.delete(name);\n this.textures?.setLive(name, source);\n if (!this.playing) this.render();\n }\n\n /**\n * Settles once the images the current controls show have loaded, and\n * every live video has a frame. Rejects with an {@link ImageLoadError}\n * when one cannot be (it shows black).\n */\n async ready(): Promise<void> {\n await Promise.all([\n this.textures?.ready(imageUrls(this.controls, this.values)),\n this.textures?.liveReady(),\n ]);\n }\n\n play() {\n this.playing = true;\n this.emit();\n }\n\n pause() {\n this.playing = false;\n this.emit();\n }\n\n /** Back to `iTime` 0 and frame 0. */\n restart() {\n this.time = 0;\n this.pointer.clear();\n this.frame = 0;\n this.render();\n this.emit();\n }\n\n /** Jumps to `time` seconds (not below 0) and draws it. */\n seek(time: number) {\n // Clicks are stamped with iTime: going back would replay them.\n if (time < this.time) this.pointer.clear();\n this.time = Math.max(0, time);\n this.render();\n this.emit();\n }\n\n setPixelRatio(ratio: number) {\n this.pixelRatio = ratio;\n this.resize();\n }\n\n /**\n * Captures the current frame (optionally at another time) as a data URL\n * (JPEG unless `type` says PNG), at most `maxEdge` pixels on the long side\n * (default 1024). Playback time is left as it was. Null when nothing has\n * compiled yet.\n */\n capture(\n options: { time?: number; maxEdge?: number; type?: \"image/jpeg\" | \"image/png\" } = {},\n ): string | null {\n if (!this.gl || !this.shader) return null;\n const saved = this.time;\n if (options.time !== undefined) this.time = Math.max(0, options.time);\n this.render();\n const { width, height } = this.canvas;\n const scale = Math.min(1, (options.maxEdge ?? 1024) / Math.max(width, height));\n const out = document.createElement(\"canvas\");\n out.width = Math.max(1, Math.round(width * scale));\n out.height = Math.max(1, Math.round(height * scale));\n out.getContext(\"2d\")!.drawImage(this.canvas, 0, 0, out.width, out.height);\n this.time = saved;\n if (options.time !== undefined) this.render();\n return out.toDataURL(options.type ?? \"image/jpeg\", 0.85);\n }\n\n /** Stops the loop, releases the context and removes the canvas. The renderer cannot be used again. */\n dispose() {\n this.disposed = true;\n cancelAnimationFrame(this.raf);\n this.resizeObserver.disconnect();\n this.intersectionObserver?.disconnect();\n this.canvas.removeEventListener(\"webglcontextlost\", this.onLost);\n this.canvas.removeEventListener(\"webglcontextrestored\", this.onRestored);\n this.canvas.removeEventListener(\"pointerdown\", this.onPointerDown);\n this.canvas.removeEventListener(\"pointermove\", this.onPointerMove);\n this.canvas.removeEventListener(\"pointerenter\", this.onPointerEnter);\n this.canvas.removeEventListener(\"pointerleave\", this.onPointerLeave);\n window.removeEventListener(\"pointerup\", this.onPointerUp);\n if (this.gl && this.shader) this.gl.deleteProgram(this.shader.program);\n this.textures?.dispose();\n this.textures = null;\n // Free the context now rather than waiting for GC (browsers cap live contexts).\n this.gl?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n this.gl = null;\n if (this.ownsCanvas) this.canvas.remove();\n }\n\n private resize() {\n // The layout size, not the bounding box: a rotated or scaled canvas keeps its own proportions.\n const w = Math.max(1, Math.round(this.canvas.clientWidth * this.pixelRatio));\n const h = Math.max(1, Math.round(this.canvas.clientHeight * this.pixelRatio));\n if (this.canvas.width !== w || this.canvas.height !== h) {\n this.canvas.width = w;\n this.canvas.height = h;\n this.render();\n }\n }\n\n private render(delta = 0) {\n if (!this.gl || !this.shader || this.gl.isContextLost()) return;\n drawFrame(\n this.gl,\n this.shader,\n this.controls,\n this.values,\n {\n width: this.canvas.width,\n height: this.canvas.height,\n time: this.time,\n delta,\n frame: this.frame,\n mouse: this.mouse,\n pointer: this.pointer.pointer(this.time),\n clicks: this.pointer.clicksAt(this.time),\n trail: this.pointer.trailAt(this.time),\n },\n this.textures ?? undefined,\n );\n }\n\n private textureCache(gl: WebGL2RenderingContext): TextureCache {\n const cache = new TextureCache(gl, (_url, error) => {\n if (error) this.onImageError?.(error);\n else if (!this.playing) this.render();\n });\n for (const [name, source] of this.live) cache.setLive(name, source);\n return cache;\n }\n\n /** Starts loading the images now shown, and frees the ones no longer shown. */\n private syncTextures() {\n if (!this.textures) return;\n const urls = imageUrls(this.controls, this.values);\n this.textures.retain(urls);\n for (const url of urls) this.textures.request(url);\n }\n\n private tick = (now: number) => {\n if (this.disposed) return;\n this.raf = requestAnimationFrame(this.tick);\n const delta = this.lastNow ? Math.min((now - this.lastNow) / 1000, 0.1) : 0;\n this.lastNow = now;\n if (this.playing) {\n this.time += delta * this.speed;\n this.frame += 1;\n this.render(delta * this.speed);\n this.fpsFrames += 1;\n } else if (this.textures?.liveChanged()) {\n // A live video moved on (played, seeked or loaded) while the clock is paused.\n this.render();\n }\n if (now - this.fpsSince >= 500) {\n this.fps = this.playing\n ? Math.round((this.fpsFrames * 1000) / (now - this.fpsSince || 1))\n : 0;\n this.fpsFrames = 0;\n this.fpsSince = now;\n this.emit();\n }\n };\n\n private emit() {\n this.onStats?.({\n time: this.time,\n fps: this.fps,\n playing: this.playing,\n width: this.canvas.width,\n height: this.canvas.height,\n });\n }\n\n /** Pointer position in shader pixels (origin bottom-left, like gl_FragCoord). */\n private toPixels(e: PointerEvent): [number, number] {\n const rect = this.canvas.getBoundingClientRect();\n return [\n (e.clientX - rect.left) * (this.canvas.width / rect.width),\n (rect.bottom - e.clientY) * (this.canvas.height / rect.height),\n ];\n }\n\n private onPointerDown = (e: PointerEvent) => {\n const [x, y] = this.toPixels(e);\n this.mouse = [x, y, x, y];\n this.pointer.down(x, y, this.time);\n if (!this.playing) this.render();\n };\n\n private onPointerMove = (e: PointerEvent) => {\n const [x, y] = this.toPixels(e);\n this.pointer.move(x, y, this.time);\n if (this.mouse[2] <= 0 || e.buttons === 0) {\n if (!this.playing) this.render();\n return;\n }\n this.mouse = [x, y, this.mouse[2], this.mouse[3]];\n if (!this.playing) this.render();\n };\n\n private onPointerEnter = (e: PointerEvent) => {\n this.pointer.move(...this.toPixels(e), this.time);\n this.pointer.hover(true, this.time);\n };\n\n private onPointerLeave = () => {\n this.pointer.hover(false, this.time);\n };\n\n private onPointerUp = () => {\n this.pointer.up(this.time);\n if (this.mouse[2] > 0)\n this.mouse = [this.mouse[0], this.mouse[1], -this.mouse[2], -Math.abs(this.mouse[3])];\n };\n\n private onLost = (e: Event) => {\n e.preventDefault();\n this.shader = null;\n this.textures?.dispose();\n this.textures = null;\n this.onContextLost?.(true);\n };\n\n private onRestored = () => {\n resetGlCaches();\n this.gl = createContext(this.canvas, true, this.alpha);\n this.textures = this.gl && this.textureCache(this.gl);\n this.setSource(this.source);\n this.onContextLost?.(false);\n };\n}\n","import { imageUrls, parseControls, type ControlDef, type ControlValue } from \"./controls.js\";\nimport {\n compileShader,\n createContext,\n drawFrame,\n ShaderCompileError,\n type CompiledShader,\n} from \"./program.js\";\nimport { clicksUniform, trailUniform, type ShaderClick } from \"./pointer.js\";\nimport { TextureCache, type LiveSource } from \"./textures.js\";\n\nexport interface ShaderFramesOptions {\n source: string;\n values?: Record<string, ControlValue>;\n width: number;\n height: number;\n /** Draw on this canvas instead of a new, detached one. It is resized to `width` × `height`. */\n canvas?: HTMLCanvasElement;\n /** Keep the shader's alpha (`fragColor.a`) in the frames, for transparent recordings (default false: opaque). */\n alpha?: boolean;\n}\n\nexport interface RenderOptions {\n /** `iFrame` (default: the number of frames rendered so far). */\n frame?: number;\n /** `iTimeDelta` in seconds (default: the time since the previous `render`, else 0). */\n delta?: number;\n /** `iMouse` (default: all zero, as on a page nobody has touched). */\n mouse?: [number, number, number, number];\n /** `iPointer`: x, y, how hovered (0–1), the press (seconds held, or minus seconds since release; default all zero). */\n pointer?: [number, number, number, number];\n /** Clicks so far, for `iClicks` (the latest `MAX_CLICKS` at or before the frame's time count). */\n clicks?: readonly ShaderClick[];\n /** Where the pointer has been, oldest first, for `iTrail` (the latest `MAX_TRAIL` at or before the frame's time count). */\n trail?: readonly ShaderClick[];\n}\n\n/**\n * Renders a shader at exact times you choose, off screen. There is no\n * animation loop and no clock: frame `n` of a recording at `fps` is\n * `render(start + n / fps)`, so the output is the same on any machine. It\n * suits recorders, video pipelines and anything else stepped by its own\n * clock. The constructor throws a {@link ShaderCompileError} when the source\n * does not compile.\n *\n * Image controls load in the background: `await frames.ready()` before the\n * first `render`, or the frames show black where the images go.\n *\n * A video in an image control (see `setLiveSource`) is drawn at whatever\n * frame it is on. To step it with the shader, seek it first: `await\n * seekVideo(video, t); frames.render(t)`.\n */\nexport class ShaderFrames {\n readonly canvas: HTMLCanvasElement;\n readonly width: number;\n readonly height: number;\n readonly controls: ControlDef[];\n private gl: WebGL2RenderingContext | null;\n private shader: CompiledShader;\n private textures: TextureCache;\n private values: Record<string, ControlValue> | undefined;\n private rendered = 0;\n private lastTime: number | null = null;\n private pixels: Uint8Array;\n\n constructor(options: ShaderFramesOptions) {\n const { source, values, width, height } = options;\n this.width = Math.max(1, Math.round(width));\n this.height = Math.max(1, Math.round(height));\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n this.values = values;\n const gl = createContext(this.canvas, true, Boolean(options.alpha));\n if (!gl) throw new Error(\"WebGL2 is not available in this browser.\");\n this.gl = gl;\n this.controls = parseControls(source);\n const compiled = compileShader(gl, source, this.controls);\n if (!compiled.ok) {\n this.dispose();\n throw new ShaderCompileError(compiled.errors);\n }\n this.shader = compiled.shader;\n this.textures = new TextureCache(gl);\n this.syncTextures();\n this.pixels = new Uint8Array(this.width * this.height * 4);\n }\n\n /** Changes control values for the frames that follow. */\n setValues(values: Record<string, ControlValue> | undefined) {\n this.values = values;\n this.syncTextures();\n }\n\n /**\n * Draws a `<video>` (or a canvas) in the image control `name` instead of\n * its URL, at the frame it is on when `render` runs; `null` goes back to\n * the URL. See `ShaderRenderer.setLiveSource`.\n */\n setLiveSource(name: string, source: LiveSource | null) {\n this.textures.setLive(name, source);\n }\n\n /**\n * Settles once every image the controls show has loaded, and every live\n * video has a frame. Rejects with an {@link ImageLoadError} when one\n * cannot be.\n */\n async ready(): Promise<void> {\n await Promise.all([this.textures.ready(imageUrls(this.controls, this.values)), this.textures.liveReady()]);\n }\n\n /** Draws the frame at `time` seconds onto `canvas`. */\n render(time: number, options: RenderOptions = {}) {\n const gl = this.context();\n const delta = options.delta ?? (this.lastTime === null ? 0 : time - this.lastTime);\n drawFrame(\n gl,\n this.shader,\n this.controls,\n this.values,\n {\n width: this.width,\n height: this.height,\n time,\n delta,\n frame: options.frame ?? this.rendered,\n mouse: options.mouse ?? [0, 0, 0, 0],\n pointer: options.pointer,\n clicks: options.clicks && clicksUniform(options.clicks, time),\n trail: options.trail && trailUniform(options.trail, time),\n },\n this.textures,\n );\n this.rendered += 1;\n this.lastTime = time;\n }\n\n /**\n * The last rendered frame as RGBA bytes, rows top down (as in `ImageData`;\n * WebGL itself reads bottom up). Pass `out` to reuse a buffer of\n * `width * height * 4` bytes.\n */\n readPixels(out?: Uint8Array | Uint8ClampedArray): Uint8Array | Uint8ClampedArray {\n const gl = this.context();\n const { width, height } = this;\n const target = out ?? new Uint8Array(width * height * 4);\n if (target.length !== width * height * 4) {\n throw new RangeError(`readPixels needs a buffer of ${width * height * 4} bytes.`);\n }\n gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);\n const row = width * 4;\n for (let y = 0; y < height; y++) {\n target.set(this.pixels.subarray((height - 1 - y) * row, (height - y) * row), y * row);\n }\n return target;\n }\n\n /** Releases the GL context now rather than at garbage collection (browsers cap live contexts). */\n dispose() {\n if (!this.gl) return;\n if (this.shader) this.gl.deleteProgram(this.shader.program);\n this.textures?.dispose();\n this.gl.getExtension(\"WEBGL_lose_context\")?.loseContext();\n this.gl = null;\n }\n\n private syncTextures() {\n const urls = imageUrls(this.controls, this.values);\n this.textures.retain(urls);\n for (const url of urls) this.textures.request(url);\n }\n\n private context(): WebGL2RenderingContext {\n if (!this.gl) throw new Error(\"These frames have been disposed.\");\n if (this.gl.isContextLost()) throw new Error(\"The graphics context was lost.\");\n return this.gl;\n }\n}\n"],"mappings":"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","names":[]}
|