@cruxgarden/plasma-ui 0.2.3 → 0.2.4
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 +22 -0
- package/dist/index.js +81 -27
- package/dist/renderer.d.ts +20 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.4
|
|
4
|
+
|
|
5
|
+
- **Fixed:** the field stopped rendering after a window resize and never came
|
|
6
|
+
back. Two causes, both now addressed. A drag-resize reallocated the eight
|
|
7
|
+
render targets once per distinct size — 88 allocations across a 60-step drag
|
|
8
|
+
in the repro — and the driver dropped the WebGL context under it. And there
|
|
9
|
+
was no `webglcontextlost` / `webglcontextrestored` handling at all, so once
|
|
10
|
+
the context went, the frame loop kept running against a dead context
|
|
11
|
+
forever. The resize now settles before reallocating (88 allocations became
|
|
12
|
+
8, and the context survives), and a lost context is caught, restored and
|
|
13
|
+
fully rebuilt. `preventDefault` on the loss event is what asks the browser
|
|
14
|
+
to attempt the restore in the first place.
|
|
15
|
+
- **Fixed:** the field never rendered at all under React StrictMode. `destroy()`
|
|
16
|
+
called `WEBGL_lose_context.loseContext()`, but the `<canvas>` belongs to the
|
|
17
|
+
host component and outlives the renderer, so the remount StrictMode performs
|
|
18
|
+
in development got a context that could never draw again. Cleanup now
|
|
19
|
+
deletes its own textures, framebuffers, programs and buffer and leaves the
|
|
20
|
+
canvas usable. A renderer replacing another on the same canvas also zeroes
|
|
21
|
+
the canvas size, so the next allocation is never skipped as a no-op — that
|
|
22
|
+
skip left the multi-target framebuffer with no attachments and drew
|
|
23
|
+
"Framebuffer is incomplete".
|
|
24
|
+
|
|
3
25
|
## 0.2.1
|
|
4
26
|
|
|
5
27
|
Fixes for the reports that came in after 0.2.0, and one new option.
|
package/dist/index.js
CHANGED
|
@@ -419,13 +419,18 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
419
419
|
this.imgSrc = null;
|
|
420
420
|
this.bgColor = null;
|
|
421
421
|
this.srcEl = null;
|
|
422
|
+
// re-uploaded each frame
|
|
423
|
+
this.floatOK = false;
|
|
424
|
+
/** True between webglcontextlost and webglcontextrestored: draw nothing. */
|
|
425
|
+
this.lost = false;
|
|
426
|
+
/** Everything initGL() created, so destroy() can free it by hand. */
|
|
427
|
+
this.owned = { tex: [], fb: [], prog: [], buf: [] };
|
|
422
428
|
this.recs = /* @__PURE__ */ new Map();
|
|
423
429
|
this.nextId = 1;
|
|
424
430
|
this.raf = 0;
|
|
425
431
|
this.last = 0;
|
|
426
432
|
this.time = 0;
|
|
427
433
|
this.dpr = 1;
|
|
428
|
-
this.resizeQueued = false;
|
|
429
434
|
/** Coarse pointer = touch. Only there does a fling run on the compositor with rAF deferred. */
|
|
430
435
|
this.coarse = typeof matchMedia === "function" && matchMedia("(pointer: coarse)").matches;
|
|
431
436
|
this.frozen = false;
|
|
@@ -450,6 +455,26 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
450
455
|
this.lightQuery = typeof matchMedia !== "undefined" ? matchMedia("(prefers-color-scheme: light)") : null;
|
|
451
456
|
this.tintCache = /* @__PURE__ */ new Map();
|
|
452
457
|
this.RP = new Float32Array(MAX_PULSES * 4);
|
|
458
|
+
/**
|
|
459
|
+
* A drag-resize reallocates eight render targets per distinct size, and the
|
|
460
|
+
* driver can drop the context under that. Without these two handlers the
|
|
461
|
+
* field simply stopped: the frame loop kept running against a dead context
|
|
462
|
+
* and nothing ever brought it back. preventDefault is what asks the browser
|
|
463
|
+
* to attempt a restore at all.
|
|
464
|
+
*/
|
|
465
|
+
this.onContextLost = (e) => {
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
this.lost = true;
|
|
468
|
+
cancelAnimationFrame(this.raf);
|
|
469
|
+
this.raf = 0;
|
|
470
|
+
};
|
|
471
|
+
this.onContextRestored = () => {
|
|
472
|
+
this.lost = false;
|
|
473
|
+
this.initGL();
|
|
474
|
+
this.applyResize();
|
|
475
|
+
this.last = 0;
|
|
476
|
+
if (!document.hidden && !this.raf) this.raf = requestAnimationFrame(this.frame);
|
|
477
|
+
};
|
|
453
478
|
/**
|
|
454
479
|
* A hidden tab draws nothing anyone can see. Browsers throttle rAF there
|
|
455
480
|
* but do not all stop it, so the loop stops itself and picks up where it
|
|
@@ -511,12 +536,8 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
511
536
|
* otherwise reallocated every texture repeatedly mid-scroll.
|
|
512
537
|
*/
|
|
513
538
|
this.resize = () => {
|
|
514
|
-
|
|
515
|
-
this.
|
|
516
|
-
requestAnimationFrame(() => {
|
|
517
|
-
this.resizeQueued = false;
|
|
518
|
-
this.applyResize();
|
|
519
|
-
});
|
|
539
|
+
clearTimeout(this.resizeSettle);
|
|
540
|
+
this.resizeSettle = setTimeout(this.applyResize, 120);
|
|
520
541
|
};
|
|
521
542
|
this.applyResize = () => {
|
|
522
543
|
if (this.frozen) {
|
|
@@ -559,6 +580,10 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
559
580
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
560
581
|
};
|
|
561
582
|
this.frame = (now2) => {
|
|
583
|
+
if (this.lost) {
|
|
584
|
+
this.raf = 0;
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
562
587
|
if (!this.oneShot) this.raf = requestAnimationFrame(this.frame);
|
|
563
588
|
const dt = this.last ? Math.min((now2 - this.last) / 1e3, 0.05) : 0.016;
|
|
564
589
|
this.last = now2;
|
|
@@ -696,26 +721,11 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
696
721
|
this.FR = new Float32Array(this.max);
|
|
697
722
|
this.EL = new Float32Array(this.max);
|
|
698
723
|
this.SOLO = new Float32Array(this.max);
|
|
699
|
-
this.
|
|
700
|
-
const sh = makeShaders(this.max);
|
|
701
|
-
this.progs = { bg: this.program(sh.bgFrag), mask: this.program(sh.maskFrag), tint: this.program(sh.tintFrag), blur: this.program(sh.blurFrag), comp: this.program(sh.compFrag) };
|
|
702
|
-
const buf = gl.createBuffer();
|
|
703
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
704
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
705
|
-
gl.enableVertexAttribArray(0);
|
|
706
|
-
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
707
|
-
this.rtA = this.target();
|
|
708
|
-
this.rtB = this.target();
|
|
709
|
-
this.rtC = this.target();
|
|
710
|
-
this.rtT = this.target();
|
|
711
|
-
this.rtFr = this.target();
|
|
712
|
-
this.rtBg = this.target();
|
|
713
|
-
this.rtBgM = this.target();
|
|
714
|
-
this.rtBgH = this.target();
|
|
715
|
-
this.mrtFb = gl.createFramebuffer();
|
|
716
|
-
this.configure(settings, true);
|
|
724
|
+
this.initGL();
|
|
717
725
|
this.mouse.x = this.mouse.tx = innerWidth / 2;
|
|
718
726
|
this.mouse.y = this.mouse.ty = innerHeight / 2;
|
|
727
|
+
canvas.addEventListener("webglcontextlost", this.onContextLost);
|
|
728
|
+
canvas.addEventListener("webglcontextrestored", this.onContextRestored);
|
|
719
729
|
addEventListener("resize", this.resize);
|
|
720
730
|
document.addEventListener("visibilitychange", this.onVisibility);
|
|
721
731
|
if (this.coarse) addEventListener("scroll", this.onScroll, { passive: true });
|
|
@@ -893,8 +903,41 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
893
903
|
bump(e) {
|
|
894
904
|
this.energy = Math.max(this.energy, Math.min(e, 1));
|
|
895
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* Every GL object this renderer owns. A lost context invalidates all of
|
|
908
|
+
* them, so creation lives here rather than in the constructor: the restore
|
|
909
|
+
* handler runs exactly the same path.
|
|
910
|
+
*/
|
|
911
|
+
initGL() {
|
|
912
|
+
const gl = this.gl;
|
|
913
|
+
this.owned = { tex: [], fb: [], prog: [], buf: [] };
|
|
914
|
+
this.floatOK = !!gl.getExtension("EXT_color_buffer_float");
|
|
915
|
+
const sh = makeShaders(this.max);
|
|
916
|
+
this.progs = { bg: this.program(sh.bgFrag), mask: this.program(sh.maskFrag), tint: this.program(sh.tintFrag), blur: this.program(sh.blurFrag), comp: this.program(sh.compFrag) };
|
|
917
|
+
const buf = gl.createBuffer();
|
|
918
|
+
if (buf) this.owned.buf.push(buf);
|
|
919
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
920
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
921
|
+
gl.enableVertexAttribArray(0);
|
|
922
|
+
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
923
|
+
this.rtA = this.target();
|
|
924
|
+
this.rtB = this.target();
|
|
925
|
+
this.rtC = this.target();
|
|
926
|
+
this.rtT = this.target();
|
|
927
|
+
this.rtFr = this.target();
|
|
928
|
+
this.rtBg = this.target();
|
|
929
|
+
this.rtBgM = this.target();
|
|
930
|
+
this.rtBgH = this.target();
|
|
931
|
+
this.mrtFb = gl.createFramebuffer();
|
|
932
|
+
this.owned.fb.push(this.mrtFb);
|
|
933
|
+
this.configure(this.settings, true);
|
|
934
|
+
this.canvas.width = this.canvas.height = 0;
|
|
935
|
+
}
|
|
896
936
|
destroy() {
|
|
897
937
|
cancelAnimationFrame(this.raf);
|
|
938
|
+
clearTimeout(this.resizeSettle);
|
|
939
|
+
this.canvas.removeEventListener("webglcontextlost", this.onContextLost);
|
|
940
|
+
this.canvas.removeEventListener("webglcontextrestored", this.onContextRestored);
|
|
898
941
|
removeEventListener("resize", this.resize);
|
|
899
942
|
document.removeEventListener("visibilitychange", this.onVisibility);
|
|
900
943
|
removeEventListener("scroll", this.onScroll);
|
|
@@ -906,7 +949,14 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
906
949
|
r.el.style.scale = "";
|
|
907
950
|
});
|
|
908
951
|
this.recs.clear();
|
|
909
|
-
this.gl
|
|
952
|
+
const gl = this.gl;
|
|
953
|
+
if (!gl.isContextLost()) {
|
|
954
|
+
this.owned.tex.forEach((t) => gl.deleteTexture(t));
|
|
955
|
+
this.owned.fb.forEach((f) => gl.deleteFramebuffer(f));
|
|
956
|
+
this.owned.prog.forEach((pr) => gl.deleteProgram(pr));
|
|
957
|
+
this.owned.buf.forEach((b) => gl.deleteBuffer(b));
|
|
958
|
+
}
|
|
959
|
+
this.owned = { tex: [], fb: [], prog: [], buf: [] };
|
|
910
960
|
}
|
|
911
961
|
freeze() {
|
|
912
962
|
const c = this.canvas;
|
|
@@ -1149,6 +1199,7 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
1149
1199
|
UNIFORMS.forEach((n) => {
|
|
1150
1200
|
u[n] = gl.getUniformLocation(pr, n);
|
|
1151
1201
|
});
|
|
1202
|
+
this.owned.prog.push(pr);
|
|
1152
1203
|
return { pr, u };
|
|
1153
1204
|
}
|
|
1154
1205
|
target() {
|
|
@@ -1159,7 +1210,10 @@ var _PlasmaRenderer = class _PlasmaRenderer {
|
|
|
1159
1210
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
1160
1211
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1161
1212
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1162
|
-
|
|
1213
|
+
const fb = gl.createFramebuffer();
|
|
1214
|
+
this.owned.tex.push(tex);
|
|
1215
|
+
this.owned.fb.push(fb);
|
|
1216
|
+
return { tex, fb, w: 0, h: 0 };
|
|
1163
1217
|
}
|
|
1164
1218
|
};
|
|
1165
1219
|
_PlasmaRenderer.RUNWAY = 1;
|
package/dist/renderer.d.ts
CHANGED
|
@@ -94,13 +94,17 @@ export declare class PlasmaRenderer {
|
|
|
94
94
|
private bgColor;
|
|
95
95
|
private srcEl;
|
|
96
96
|
private floatOK;
|
|
97
|
+
/** True between webglcontextlost and webglcontextrestored: draw nothing. */
|
|
98
|
+
private lost;
|
|
99
|
+
private resizeSettle;
|
|
100
|
+
/** Everything initGL() created, so destroy() can free it by hand. */
|
|
101
|
+
private owned;
|
|
97
102
|
private recs;
|
|
98
103
|
private nextId;
|
|
99
104
|
private raf;
|
|
100
105
|
private last;
|
|
101
106
|
private time;
|
|
102
107
|
private dpr;
|
|
103
|
-
private resizeQueued;
|
|
104
108
|
/** Coarse pointer = touch. Only there does a fling run on the compositor with rAF deferred. */
|
|
105
109
|
private coarse;
|
|
106
110
|
private frozen;
|
|
@@ -148,6 +152,21 @@ export declare class PlasmaRenderer {
|
|
|
148
152
|
pulse(x: number, y: number, strength?: number): void;
|
|
149
153
|
/** Raise the material's energy (brightens contours and color); it decays on its own. */
|
|
150
154
|
bump(e: number): void;
|
|
155
|
+
/**
|
|
156
|
+
* Every GL object this renderer owns. A lost context invalidates all of
|
|
157
|
+
* them, so creation lives here rather than in the constructor: the restore
|
|
158
|
+
* handler runs exactly the same path.
|
|
159
|
+
*/
|
|
160
|
+
private initGL;
|
|
161
|
+
/**
|
|
162
|
+
* A drag-resize reallocates eight render targets per distinct size, and the
|
|
163
|
+
* driver can drop the context under that. Without these two handlers the
|
|
164
|
+
* field simply stopped: the frame loop kept running against a dead context
|
|
165
|
+
* and nothing ever brought it back. preventDefault is what asks the browser
|
|
166
|
+
* to attempt a restore at all.
|
|
167
|
+
*/
|
|
168
|
+
private onContextLost;
|
|
169
|
+
private onContextRestored;
|
|
151
170
|
destroy(): void;
|
|
152
171
|
/**
|
|
153
172
|
* A hidden tab draws nothing anyone can see. Browsers throttle rAF there
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cruxgarden/plasma-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Liquid panels for React, rendered in WebGL on canvas: every panel is one shared plasma - they fuse on contact, refract, and snap to a grid.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|