liquid-gl 2.1.1 → 2.2.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/README.md +23 -9
- package/liquidGL-helper.js +362 -0
- package/liquidGL.js +1260 -787
- package/package.json +7 -2
package/liquidGL.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* liquidGL – Liquid Glass - Powered by WebGL
|
|
2
|
+
* liquidGL – Liquid Glass - Powered by WebGPU/WebGL
|
|
3
3
|
* -----------------------------------------------------------------------------
|
|
4
4
|
*
|
|
5
5
|
* Author: NaughtyDuk© – https://liquidgl.naughtyduk.com
|
|
6
6
|
* Licence: MIT
|
|
7
|
-
* Version: v2.
|
|
7
|
+
* Version: v2.2.0
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const liquidGL = (() => {
|
|
@@ -12,6 +12,13 @@ const liquidGL = (() => {
|
|
|
12
12
|
|
|
13
13
|
const RECAPTURE_INTERVAL_MS = 250;
|
|
14
14
|
|
|
15
|
+
const ENGINE_CHAINS = {
|
|
16
|
+
auto: ["webgpu", "webgl2", "webgl", "experimental-webgl"],
|
|
17
|
+
webgpu: ["webgpu"],
|
|
18
|
+
webgl2: ["webgl2", "webgl", "experimental-webgl"],
|
|
19
|
+
webgl: ["webgl", "experimental-webgl"],
|
|
20
|
+
};
|
|
21
|
+
|
|
15
22
|
/* --------------------------------------------------
|
|
16
23
|
* Utilities
|
|
17
24
|
* ------------------------------------------------*/
|
|
@@ -1804,176 +1811,33 @@ const liquidGL = (() => {
|
|
|
1804
1811
|
})();
|
|
1805
1812
|
|
|
1806
1813
|
/* --------------------------------------------------
|
|
1807
|
-
*
|
|
1814
|
+
* Render backends
|
|
1808
1815
|
* ------------------------------------------------*/
|
|
1809
|
-
class
|
|
1810
|
-
constructor(
|
|
1811
|
-
this.
|
|
1812
|
-
this.canvas =
|
|
1813
|
-
this.canvas.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;`;
|
|
1814
|
-
this.canvas.setAttribute("data-liquid-ignore", "");
|
|
1815
|
-
document.body.appendChild(this.canvas);
|
|
1816
|
+
class WebGLBackend {
|
|
1817
|
+
constructor(canvas, contexts = ["webgl2", "webgl", "experimental-webgl"]) {
|
|
1818
|
+
this.kind = "webgl";
|
|
1819
|
+
this.canvas = canvas;
|
|
1816
1820
|
|
|
1817
1821
|
const ctxAttribs = {
|
|
1818
1822
|
alpha: true,
|
|
1819
1823
|
premultipliedAlpha: true,
|
|
1820
1824
|
preserveDrawingBuffer: true,
|
|
1821
1825
|
};
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
if (!this.gl) throw new Error("liquidGL: WebGL unavailable");
|
|
1827
|
-
|
|
1828
|
-
this.lenses = [];
|
|
1829
|
-
this.texture = null;
|
|
1830
|
-
this.textureWidth = 0;
|
|
1831
|
-
this.textureHeight = 0;
|
|
1832
|
-
this.scaleFactor = 1;
|
|
1833
|
-
this.startTime = Date.now();
|
|
1834
|
-
this._scrollUpdateCounter = 0;
|
|
1835
|
-
|
|
1836
|
-
this._initGL();
|
|
1837
|
-
|
|
1838
|
-
this.snapshotTarget =
|
|
1839
|
-
document.querySelector(snapshotSelector) || document.body;
|
|
1840
|
-
if (!this.snapshotTarget) this.snapshotTarget = document.body;
|
|
1841
|
-
|
|
1842
|
-
this._isScrolling = false;
|
|
1843
|
-
let lastScrollY = window.scrollY;
|
|
1844
|
-
let scrollTimeout;
|
|
1845
|
-
const scrollCheck = () => {
|
|
1846
|
-
if (window.scrollY !== lastScrollY) {
|
|
1847
|
-
this._isScrolling = true;
|
|
1848
|
-
lastScrollY = window.scrollY;
|
|
1849
|
-
clearTimeout(scrollTimeout);
|
|
1850
|
-
scrollTimeout = setTimeout(() => {
|
|
1851
|
-
this._isScrolling = false;
|
|
1852
|
-
}, 200);
|
|
1853
|
-
}
|
|
1854
|
-
requestAnimationFrame(scrollCheck);
|
|
1855
|
-
};
|
|
1856
|
-
requestAnimationFrame(scrollCheck);
|
|
1857
|
-
|
|
1858
|
-
const onResize = debounce(() => {
|
|
1859
|
-
if (this._capturing || this._isScrolling) return;
|
|
1860
|
-
|
|
1861
|
-
if (window.visualViewport && window.visualViewport.scale !== 1) {
|
|
1862
|
-
return;
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
this._dynamicNodes.forEach((node) => {
|
|
1866
|
-
const meta = this._dynMeta.get(node.el);
|
|
1867
|
-
if (meta) {
|
|
1868
|
-
meta.needsRecapture = true;
|
|
1869
|
-
meta.prevDrawRect = null;
|
|
1870
|
-
meta.lastCapture = null;
|
|
1871
|
-
}
|
|
1872
|
-
});
|
|
1873
|
-
|
|
1874
|
-
this._resizeCanvas();
|
|
1875
|
-
this.lenses.forEach((l) => l.updateMetrics());
|
|
1876
|
-
this.captureSnapshot();
|
|
1877
|
-
}, RECAPTURE_INTERVAL_MS);
|
|
1878
|
-
window.addEventListener("resize", onResize, { passive: true });
|
|
1879
|
-
|
|
1880
|
-
if ("ResizeObserver" in window) {
|
|
1881
|
-
new ResizeObserver(onResize).observe(this.snapshotTarget);
|
|
1826
|
+
let gl = null;
|
|
1827
|
+
for (const name of contexts) {
|
|
1828
|
+
gl = canvas.getContext(name, ctxAttribs);
|
|
1829
|
+
if (gl) break;
|
|
1882
1830
|
}
|
|
1831
|
+
if (!gl) throw new Error("liquidGL: WebGL unavailable");
|
|
1883
1832
|
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
this._dynamicNodes = [];
|
|
1888
|
-
this._dynMeta = new WeakMap();
|
|
1889
|
-
this._lastDynamicUpdate = 0;
|
|
1890
|
-
|
|
1891
|
-
const styleEl = document.createElement("style");
|
|
1892
|
-
styleEl.id = "liquid-gl-dynamic-styles";
|
|
1893
|
-
document.head.appendChild(styleEl);
|
|
1894
|
-
this._dynamicStyleSheet = styleEl.sheet;
|
|
1895
|
-
|
|
1896
|
-
this._snapshotResolution = Math.max(
|
|
1897
|
-
0.1,
|
|
1898
|
-
Math.min(3.0, snapshotResolution),
|
|
1899
|
-
);
|
|
1900
|
-
this._pendingReveal = [];
|
|
1901
|
-
|
|
1902
|
-
this._resizeCanvas();
|
|
1903
|
-
this.captureSnapshot();
|
|
1904
|
-
|
|
1905
|
-
/* --------------------------------------------------
|
|
1906
|
-
* Dynamic media (video) support
|
|
1907
|
-
* ------------------------------------------------*/
|
|
1908
|
-
this._videoNodes = Array.from(
|
|
1909
|
-
this.snapshotTarget.querySelectorAll("video"),
|
|
1910
|
-
);
|
|
1911
|
-
this._videoNodes = this._videoNodes.filter((v) => !this._isIgnored(v));
|
|
1912
|
-
this._tmpCanvas = document.createElement("canvas");
|
|
1913
|
-
this._tmpCtx = this._tmpCanvas.getContext("2d");
|
|
1914
|
-
|
|
1915
|
-
this._videoFrameState = new WeakMap();
|
|
1916
|
-
|
|
1917
|
-
this._videoAlphaState = new WeakMap();
|
|
1918
|
-
|
|
1919
|
-
this.canvas.style.opacity = "0";
|
|
1920
|
-
|
|
1921
|
-
this.useExternalTicker = false;
|
|
1922
|
-
|
|
1923
|
-
/* --------------------------------------------------
|
|
1924
|
-
* Inline worker for heavy dynamic nodes
|
|
1925
|
-
* ------------------------------------------------*/
|
|
1926
|
-
this._workerEnabled =
|
|
1927
|
-
typeof OffscreenCanvas !== "undefined" &&
|
|
1928
|
-
typeof Worker !== "undefined" &&
|
|
1929
|
-
typeof ImageBitmap !== "undefined";
|
|
1930
|
-
|
|
1931
|
-
if (this._workerEnabled) {
|
|
1932
|
-
const workerSrc = `
|
|
1933
|
-
/* dynamic-element worker (runs in its own thread) */
|
|
1934
|
-
self.onmessage = async (e) => {
|
|
1935
|
-
const { id, width, height, snap, dyn } = e.data;
|
|
1936
|
-
const off = new OffscreenCanvas(width, height);
|
|
1937
|
-
const ctx = off.getContext('2d');
|
|
1938
|
-
|
|
1939
|
-
ctx.drawImage(snap, 0, 0, width, height);
|
|
1940
|
-
ctx.drawImage(dyn, 0, 0, width, height);
|
|
1941
|
-
|
|
1942
|
-
const bmp = await off.transferToImageBitmap();
|
|
1943
|
-
self.postMessage({ id, bmp }, [bmp]);
|
|
1944
|
-
};
|
|
1945
|
-
`;
|
|
1946
|
-
const blob = new Blob([workerSrc], { type: "application/javascript" });
|
|
1947
|
-
this._dynWorker = new Worker(URL.createObjectURL(blob), {
|
|
1948
|
-
type: "module",
|
|
1949
|
-
});
|
|
1950
|
-
|
|
1951
|
-
this._dynJobs = new Map();
|
|
1952
|
-
|
|
1953
|
-
this._dynWorker.onmessage = (e) => {
|
|
1954
|
-
const { id, bmp } = e.data;
|
|
1955
|
-
const meta = this._dynJobs.get(id);
|
|
1956
|
-
if (!meta) return;
|
|
1957
|
-
this._dynJobs.delete(id);
|
|
1833
|
+
this.gl = gl;
|
|
1834
|
+
this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) || 8192;
|
|
1835
|
+
this.texture = null;
|
|
1958
1836
|
|
|
1959
|
-
|
|
1960
|
-
const gl = this.gl;
|
|
1961
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
1962
|
-
gl.texSubImage2D(
|
|
1963
|
-
gl.TEXTURE_2D,
|
|
1964
|
-
0,
|
|
1965
|
-
x,
|
|
1966
|
-
y,
|
|
1967
|
-
gl.RGBA,
|
|
1968
|
-
gl.UNSIGNED_BYTE,
|
|
1969
|
-
bmp,
|
|
1970
|
-
);
|
|
1971
|
-
};
|
|
1972
|
-
}
|
|
1837
|
+
this._initLensProgram();
|
|
1973
1838
|
}
|
|
1974
1839
|
|
|
1975
|
-
|
|
1976
|
-
_initGL() {
|
|
1840
|
+
_initLensProgram() {
|
|
1977
1841
|
const vsSource = `
|
|
1978
1842
|
attribute vec2 a_position;
|
|
1979
1843
|
varying vec2 v_uv;
|
|
@@ -2132,8 +1996,8 @@ const liquidGL = (() => {
|
|
|
2132
1996
|
gl_FragColor = final;
|
|
2133
1997
|
}`;
|
|
2134
1998
|
|
|
2135
|
-
this.program = createProgram(this.gl, vsSource, fsSource);
|
|
2136
1999
|
const gl = this.gl;
|
|
2000
|
+
this.program = createProgram(gl, vsSource, fsSource);
|
|
2137
2001
|
if (!this.program) throw new Error("liquidGL: Shader failed");
|
|
2138
2002
|
|
|
2139
2003
|
const posBuf = gl.createBuffer();
|
|
@@ -2177,64 +2041,1149 @@ const liquidGL = (() => {
|
|
|
2177
2041
|
};
|
|
2178
2042
|
}
|
|
2179
2043
|
|
|
2180
|
-
|
|
2181
|
-
_resizeCanvas() {
|
|
2182
|
-
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
2183
|
-
this.canvas.width = innerWidth * dpr;
|
|
2184
|
-
this.canvas.height = innerHeight * dpr;
|
|
2185
|
-
this.canvas.style.width = `${innerWidth}px`;
|
|
2186
|
-
this.canvas.style.height = `${innerHeight}px`;
|
|
2044
|
+
resize() {
|
|
2187
2045
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
2188
2046
|
}
|
|
2189
2047
|
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
if (this.
|
|
2193
|
-
this.
|
|
2048
|
+
uploadSnapshot(srcCanvas) {
|
|
2049
|
+
const gl = this.gl;
|
|
2050
|
+
if (!this.texture) this.texture = gl.createTexture();
|
|
2051
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2052
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2053
|
+
gl.texImage2D(
|
|
2054
|
+
gl.TEXTURE_2D,
|
|
2055
|
+
0,
|
|
2056
|
+
gl.RGBA,
|
|
2057
|
+
gl.RGBA,
|
|
2058
|
+
gl.UNSIGNED_BYTE,
|
|
2059
|
+
srcCanvas,
|
|
2060
|
+
);
|
|
2061
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2062
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2063
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2064
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2065
|
+
this._vFboTexture = null;
|
|
2066
|
+
return true;
|
|
2067
|
+
}
|
|
2194
2068
|
|
|
2195
|
-
|
|
2069
|
+
uploadRegion(x, y, source) {
|
|
2070
|
+
if (!this.texture) return;
|
|
2071
|
+
const gl = this.gl;
|
|
2072
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2073
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2074
|
+
gl.texSubImage2D(
|
|
2075
|
+
gl.TEXTURE_2D,
|
|
2076
|
+
0,
|
|
2077
|
+
x,
|
|
2078
|
+
y,
|
|
2079
|
+
gl.RGBA,
|
|
2080
|
+
gl.UNSIGNED_BYTE,
|
|
2081
|
+
source,
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2196
2084
|
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
maxAttempts = 3,
|
|
2200
|
-
delayMs = 500,
|
|
2201
|
-
) => {
|
|
2202
|
-
try {
|
|
2203
|
-
const fullW = this.snapshotTarget.scrollWidth;
|
|
2204
|
-
const fullH = this.snapshotTarget.scrollHeight;
|
|
2205
|
-
const maxTex = this.gl.getParameter(this.gl.MAX_TEXTURE_SIZE) || 8192;
|
|
2206
|
-
const MAX_MOBILE_DIM = 4096;
|
|
2207
|
-
const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
2085
|
+
_initVideoBlit() {
|
|
2086
|
+
if (this._vBlitReady !== undefined) return this._vBlitReady;
|
|
2208
2087
|
|
|
2209
|
-
|
|
2210
|
-
this._snapshotResolution,
|
|
2211
|
-
maxTex / fullW,
|
|
2212
|
-
maxTex / fullH,
|
|
2213
|
-
);
|
|
2088
|
+
const gl = this.gl;
|
|
2214
2089
|
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2090
|
+
const vs = `
|
|
2091
|
+
attribute vec2 a_position;
|
|
2092
|
+
varying vec2 v_uv;
|
|
2093
|
+
void main(){
|
|
2094
|
+
v_uv = (a_position + 1.0) * 0.5;
|
|
2095
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
2096
|
+
}`;
|
|
2219
2097
|
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
}
|
|
2098
|
+
const fs = `
|
|
2099
|
+
precision mediump float;
|
|
2100
|
+
varying vec2 v_uv;
|
|
2101
|
+
uniform sampler2D u_src;
|
|
2102
|
+
uniform vec4 u_srcRect;
|
|
2103
|
+
void main(){
|
|
2104
|
+
gl_FragColor = texture2D(u_src, u_srcRect.xy + v_uv * u_srcRect.zw);
|
|
2105
|
+
}`;
|
|
2229
2106
|
|
|
2230
|
-
|
|
2107
|
+
const prog = createProgram(gl, vs, fs);
|
|
2108
|
+
if (!prog) {
|
|
2109
|
+
this._vBlitReady = false;
|
|
2110
|
+
return false;
|
|
2111
|
+
}
|
|
2231
2112
|
|
|
2232
|
-
|
|
2233
|
-
|
|
2113
|
+
this._vProg = prog;
|
|
2114
|
+
this._vPosLoc = gl.getAttribLocation(prog, "a_position");
|
|
2115
|
+
this._vU = {
|
|
2116
|
+
src: gl.getUniformLocation(prog, "u_src"),
|
|
2117
|
+
srcRect: gl.getUniformLocation(prog, "u_srcRect"),
|
|
2118
|
+
};
|
|
2234
2119
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2120
|
+
this._vTex = gl.createTexture();
|
|
2121
|
+
gl.bindTexture(gl.TEXTURE_2D, this._vTex);
|
|
2122
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2123
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2124
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2125
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2126
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2127
|
+
|
|
2128
|
+
this._vFbo = gl.createFramebuffer();
|
|
2129
|
+
this._vFboTexture = null;
|
|
2130
|
+
|
|
2131
|
+
this._vBlitReady = true;
|
|
2132
|
+
return true;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
_restoreLensProgramState() {
|
|
2136
|
+
const gl = this.gl;
|
|
2137
|
+
gl.useProgram(this.program);
|
|
2138
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
|
|
2139
|
+
gl.enableVertexAttribArray(this._posLoc);
|
|
2140
|
+
gl.vertexAttribPointer(this._posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
2141
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2142
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2143
|
+
gl.uniform1i(this.u.tex, 0);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
blitVideo(vid, dstX, dstY, dstW, dstH, srcRect) {
|
|
2147
|
+
if (!this.texture || !this._initVideoBlit()) return false;
|
|
2148
|
+
const gl = this.gl;
|
|
2149
|
+
|
|
2150
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this._vFbo);
|
|
2151
|
+
|
|
2152
|
+
if (this._vFboTexture !== this.texture) {
|
|
2153
|
+
gl.framebufferTexture2D(
|
|
2154
|
+
gl.FRAMEBUFFER,
|
|
2155
|
+
gl.COLOR_ATTACHMENT0,
|
|
2156
|
+
gl.TEXTURE_2D,
|
|
2157
|
+
this.texture,
|
|
2158
|
+
0,
|
|
2159
|
+
);
|
|
2160
|
+
if (
|
|
2161
|
+
gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE
|
|
2162
|
+
) {
|
|
2163
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2164
|
+
this._vBlitReady = false;
|
|
2165
|
+
return false;
|
|
2166
|
+
}
|
|
2167
|
+
this._vFboTexture = this.texture;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2171
|
+
gl.bindTexture(gl.TEXTURE_2D, this._vTex);
|
|
2172
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2173
|
+
|
|
2174
|
+
try {
|
|
2175
|
+
gl.texImage2D(
|
|
2176
|
+
gl.TEXTURE_2D,
|
|
2177
|
+
0,
|
|
2178
|
+
gl.RGBA,
|
|
2179
|
+
gl.RGBA,
|
|
2180
|
+
gl.UNSIGNED_BYTE,
|
|
2181
|
+
vid,
|
|
2182
|
+
);
|
|
2183
|
+
} catch (e) {
|
|
2184
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2185
|
+
this._restoreLensProgramState();
|
|
2186
|
+
return false;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
gl.useProgram(this._vProg);
|
|
2190
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
|
|
2191
|
+
gl.enableVertexAttribArray(this._vPosLoc);
|
|
2192
|
+
gl.vertexAttribPointer(this._vPosLoc, 2, gl.FLOAT, false, 0, 0);
|
|
2193
|
+
|
|
2194
|
+
gl.uniform1i(this._vU.src, 0);
|
|
2195
|
+
gl.uniform4f(
|
|
2196
|
+
this._vU.srcRect,
|
|
2197
|
+
srcRect.u,
|
|
2198
|
+
srcRect.v,
|
|
2199
|
+
srcRect.uw,
|
|
2200
|
+
srcRect.vh,
|
|
2201
|
+
);
|
|
2202
|
+
|
|
2203
|
+
gl.viewport(dstX, dstY, dstW, dstH);
|
|
2204
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
2205
|
+
|
|
2206
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2207
|
+
this._restoreLensProgramState();
|
|
2208
|
+
|
|
2209
|
+
return true;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
beginFrame(width, height, time) {
|
|
2213
|
+
const gl = this.gl;
|
|
2214
|
+
gl.clearColor(0, 0, 0, 0);
|
|
2215
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
2216
|
+
gl.useProgram(this.program);
|
|
2217
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2218
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2219
|
+
gl.uniform1i(this.u.tex, 0);
|
|
2220
|
+
gl.uniform1f(this.u.time, time);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
drawLens(lens, p) {
|
|
2224
|
+
const gl = this.gl;
|
|
2225
|
+
|
|
2226
|
+
gl.viewport(p.x, p.y, p.w, p.h);
|
|
2227
|
+
gl.uniform2f(this.u.res, p.w, p.h);
|
|
2228
|
+
gl.uniform2f(this.u.subpixel, p.subX, p.subY);
|
|
2229
|
+
gl.uniform2f(this.u.boxSize, p.boxW, p.boxH);
|
|
2230
|
+
gl.uniform4f(
|
|
2231
|
+
this.u.bounds,
|
|
2232
|
+
p.bounds[0],
|
|
2233
|
+
p.bounds[1],
|
|
2234
|
+
p.bounds[2],
|
|
2235
|
+
p.bounds[3],
|
|
2236
|
+
);
|
|
2237
|
+
gl.uniform2f(this.u.textureResolution, p.texW, p.texH);
|
|
2238
|
+
gl.uniform1f(this.u.refraction, p.refraction);
|
|
2239
|
+
gl.uniform1f(this.u.aberration, p.aberration);
|
|
2240
|
+
gl.uniform1f(this.u.bevelDepth, p.bevelDepth);
|
|
2241
|
+
gl.uniform1f(this.u.bevelWidth, p.bevelWidth);
|
|
2242
|
+
gl.uniform1f(this.u.frost, p.frost);
|
|
2243
|
+
gl.uniform1f(this.u.radius, p.radius);
|
|
2244
|
+
gl.uniform1i(this.u.specular, p.specular);
|
|
2245
|
+
gl.uniform1f(this.u.revealProgress, p.revealProgress);
|
|
2246
|
+
gl.uniform1i(this.u.revealType, p.revealType);
|
|
2247
|
+
gl.uniform1f(this.u.magnify, p.magnify);
|
|
2248
|
+
gl.uniform1f(this.u.tiltX, p.tiltX);
|
|
2249
|
+
gl.uniform1f(this.u.tiltY, p.tiltY);
|
|
2250
|
+
|
|
2251
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
endFrame() {}
|
|
2255
|
+
|
|
2256
|
+
clearRegions(rects) {
|
|
2257
|
+
const gl = this.gl;
|
|
2258
|
+
rects.forEach(({ x, y, w, h }) => {
|
|
2259
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
2260
|
+
gl.scissor(x, y, w, h);
|
|
2261
|
+
gl.clearColor(0, 0, 0, 0);
|
|
2262
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
2263
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
/* --------------------------------------------------
|
|
2269
|
+
* WGSL sources (WebGPU backend)
|
|
2270
|
+
* ------------------------------------------------*/
|
|
2271
|
+
const WEBGPU_LENS_WGSL = `
|
|
2272
|
+
struct LensUniforms {
|
|
2273
|
+
resolution: vec2<f32>,
|
|
2274
|
+
textureResolution: vec2<f32>,
|
|
2275
|
+
bounds: vec4<f32>,
|
|
2276
|
+
subpixel: vec2<f32>,
|
|
2277
|
+
boxSize: vec2<f32>,
|
|
2278
|
+
refraction: f32,
|
|
2279
|
+
aberration: f32,
|
|
2280
|
+
bevelDepth: f32,
|
|
2281
|
+
bevelWidth: f32,
|
|
2282
|
+
frost: f32,
|
|
2283
|
+
radius: f32,
|
|
2284
|
+
time: f32,
|
|
2285
|
+
specular: f32,
|
|
2286
|
+
revealProgress: f32,
|
|
2287
|
+
revealType: f32,
|
|
2288
|
+
tiltX: f32,
|
|
2289
|
+
tiltY: f32,
|
|
2290
|
+
magnify: f32,
|
|
2291
|
+
};
|
|
2292
|
+
|
|
2293
|
+
@group(0) @binding(0) var u_tex: texture_2d<f32>;
|
|
2294
|
+
@group(0) @binding(1) var u_samp: sampler;
|
|
2295
|
+
@group(1) @binding(0) var<uniform> u: LensUniforms;
|
|
2296
|
+
|
|
2297
|
+
struct VSOut {
|
|
2298
|
+
@builtin(position) pos: vec4<f32>,
|
|
2299
|
+
@location(0) uv: vec2<f32>,
|
|
2300
|
+
};
|
|
2301
|
+
|
|
2302
|
+
@vertex
|
|
2303
|
+
fn vs(@location(0) a_position: vec2<f32>) -> VSOut {
|
|
2304
|
+
var o: VSOut;
|
|
2305
|
+
o.uv = (a_position + vec2<f32>(1.0)) * 0.5;
|
|
2306
|
+
o.pos = vec4<f32>(a_position, 0.0, 1.0);
|
|
2307
|
+
return o;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
fn udRoundBox(p: vec2<f32>, b: vec2<f32>, r: f32) -> f32 {
|
|
2311
|
+
return length(max(abs(p) - b + vec2<f32>(r), vec2<f32>(0.0))) - r;
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
fn cornerNormal(p: vec2<f32>, b: vec2<f32>, r: f32, fb: vec2<f32>) -> vec2<f32> {
|
|
2315
|
+
let q = abs(p) - b + vec2<f32>(r);
|
|
2316
|
+
let m = max(q, vec2<f32>(0.0));
|
|
2317
|
+
let l = length(m);
|
|
2318
|
+
if (l <= 0.0) { return fb; }
|
|
2319
|
+
let w = smoothstep(0.0, max(r * 0.5, 1.0), min(m.x, m.y));
|
|
2320
|
+
if (w <= 0.0) { return fb; }
|
|
2321
|
+
let s = vec2<f32>(select(1.0, -1.0, p.x < 0.0), select(1.0, -1.0, p.y < 0.0));
|
|
2322
|
+
return normalize(mix(fb, s * (m / l), w));
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
fn random2(st: vec2<f32>) -> f32 {
|
|
2326
|
+
return fract(sin(dot(st, vec2<f32>(12.9898, 78.233))) * 43758.5453123);
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
fn edgeFactor(p_px: vec2<f32>, b_px: vec2<f32>, radius_px: f32) -> f32 {
|
|
2330
|
+
let d = -udRoundBox(p_px, b_px, radius_px);
|
|
2331
|
+
let bevel_px = u.bevelWidth * min(u.boxSize.x, u.boxSize.y);
|
|
2332
|
+
return 1.0 - smoothstep(0.0, bevel_px, d);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
@fragment
|
|
2336
|
+
fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
2337
|
+
var p = in.uv - vec2<f32>(0.5);
|
|
2338
|
+
p.x = p.x * (u.resolution.x / u.resolution.y);
|
|
2339
|
+
|
|
2340
|
+
let p_px = in.uv * u.resolution - u.subpixel - 0.5 * u.boxSize;
|
|
2341
|
+
let b_px = 0.5 * u.boxSize;
|
|
2342
|
+
|
|
2343
|
+
let edge = edgeFactor(p_px, b_px, u.radius);
|
|
2344
|
+
let offsetAmt = edge * u.refraction + pow(edge, 10.0) * u.bevelDepth;
|
|
2345
|
+
let centreBlend = smoothstep(0.15, 0.45, length(p));
|
|
2346
|
+
let refractDir = cornerNormal(p_px, b_px, u.radius, normalize(p));
|
|
2347
|
+
let offset = refractDir * offsetAmt * centreBlend;
|
|
2348
|
+
|
|
2349
|
+
let tiltRefractionScale = 0.05;
|
|
2350
|
+
let deg2rad = 0.017453292519943295;
|
|
2351
|
+
let tiltOffset = vec2<f32>(tan(u.tiltY * deg2rad), -tan(u.tiltX * deg2rad)) * tiltRefractionScale;
|
|
2352
|
+
|
|
2353
|
+
let localUV = (in.uv - vec2<f32>(0.5)) / vec2<f32>(u.magnify) + vec2<f32>(0.5);
|
|
2354
|
+
let flippedUV = vec2<f32>(localUV.x, 1.0 - localUV.y);
|
|
2355
|
+
let mapped = u.bounds.xy + flippedUV * u.bounds.zw;
|
|
2356
|
+
let refracted = mapped + offset - tiltOffset;
|
|
2357
|
+
|
|
2358
|
+
let oob = max(max(-refracted.x, refracted.x - 1.0), max(-refracted.y, refracted.y - 1.0));
|
|
2359
|
+
let blend = 1.0 - smoothstep(0.0, 0.01, oob);
|
|
2360
|
+
let sampleUV = mix(mapped, refracted, blend);
|
|
2361
|
+
|
|
2362
|
+
let baseCol = textureSample(u_tex, u_samp, mapped);
|
|
2363
|
+
|
|
2364
|
+
let texel = vec2<f32>(1.0) / u.textureResolution;
|
|
2365
|
+
var refrCol: vec4<f32>;
|
|
2366
|
+
|
|
2367
|
+
let chroma = offset * u.aberration;
|
|
2368
|
+
|
|
2369
|
+
if (u.frost > 0.0) {
|
|
2370
|
+
let radius = u.frost * 4.0;
|
|
2371
|
+
var sum = vec4<f32>(0.0);
|
|
2372
|
+
|
|
2373
|
+
for (var i = 0; i < 16; i = i + 1) {
|
|
2374
|
+
let fi = f32(i);
|
|
2375
|
+
let angle = random2(in.uv + vec2<f32>(fi)) * 6.283185;
|
|
2376
|
+
let dist = sqrt(random2(in.uv - vec2<f32>(fi))) * radius;
|
|
2377
|
+
let foff = vec2<f32>(cos(angle), sin(angle)) * texel * dist;
|
|
2378
|
+
if (u.aberration > 0.0) {
|
|
2379
|
+
let c0 = textureSample(u_tex, u_samp, sampleUV + foff - chroma);
|
|
2380
|
+
let c1 = textureSample(u_tex, u_samp, sampleUV + foff);
|
|
2381
|
+
let c2 = textureSample(u_tex, u_samp, sampleUV + foff + chroma);
|
|
2382
|
+
sum = sum + vec4<f32>(c0.r, c1.g, c2.b, c1.a);
|
|
2383
|
+
} else {
|
|
2384
|
+
sum = sum + textureSample(u_tex, u_samp, sampleUV + foff);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
refrCol = sum / 16.0;
|
|
2388
|
+
} else {
|
|
2389
|
+
refrCol = textureSample(u_tex, u_samp, sampleUV);
|
|
2390
|
+
refrCol = refrCol + textureSample(u_tex, u_samp, sampleUV + vec2<f32>(texel.x, 0.0));
|
|
2391
|
+
refrCol = refrCol + textureSample(u_tex, u_samp, sampleUV + vec2<f32>(-texel.x, 0.0));
|
|
2392
|
+
refrCol = refrCol + textureSample(u_tex, u_samp, sampleUV + vec2<f32>(0.0, texel.y));
|
|
2393
|
+
refrCol = refrCol + textureSample(u_tex, u_samp, sampleUV + vec2<f32>(0.0, -texel.y));
|
|
2394
|
+
refrCol = refrCol / 5.0;
|
|
2395
|
+
|
|
2396
|
+
if (u.aberration > 0.0) {
|
|
2397
|
+
let chromaR = textureSample(u_tex, u_samp, sampleUV - chroma).r;
|
|
2398
|
+
let chromaB = textureSample(u_tex, u_samp, sampleUV + chroma).b;
|
|
2399
|
+
refrCol = vec4<f32>(chromaR, refrCol.g, chromaB, refrCol.a);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
if (refrCol.a < 0.1) {
|
|
2404
|
+
refrCol = baseCol;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
var finalCol = refrCol;
|
|
2408
|
+
|
|
2409
|
+
let dmask = udRoundBox(p_px, b_px, u.radius);
|
|
2410
|
+
let inShape = 1.0 - smoothstep(-0.5, 0.5, dmask);
|
|
2411
|
+
|
|
2412
|
+
if (u.specular > 0.5) {
|
|
2413
|
+
let lp1 = vec2<f32>(sin(u.time * 0.2), cos(u.time * 0.3)) * 0.6 + vec2<f32>(0.5);
|
|
2414
|
+
let lp2 = vec2<f32>(sin(u.time * -0.4 + 1.5), cos(u.time * 0.25 - 0.5)) * 0.6 + vec2<f32>(0.5);
|
|
2415
|
+
var h = 0.0;
|
|
2416
|
+
h = h + smoothstep(0.4, 0.0, distance(in.uv, lp1)) * 0.1;
|
|
2417
|
+
h = h + smoothstep(0.5, 0.0, distance(in.uv, lp2)) * 0.08;
|
|
2418
|
+
finalCol = vec4<f32>(finalCol.rgb + vec3<f32>(h), finalCol.a);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
if (u.revealType == 1.0) {
|
|
2422
|
+
finalCol = vec4<f32>(finalCol.rgb * u.revealProgress, finalCol.a * u.revealProgress);
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
finalCol = vec4<f32>(finalCol.rgb * inShape, finalCol.a * inShape);
|
|
2426
|
+
|
|
2427
|
+
return finalCol;
|
|
2428
|
+
}`;
|
|
2429
|
+
|
|
2430
|
+
const WEBGPU_BLIT_WGSL = `
|
|
2431
|
+
@group(0) @binding(0) var u_src: texture_2d<f32>;
|
|
2432
|
+
@group(0) @binding(1) var u_samp: sampler;
|
|
2433
|
+
@group(1) @binding(0) var<uniform> u_srcRect: vec4<f32>;
|
|
2434
|
+
|
|
2435
|
+
struct VSOut {
|
|
2436
|
+
@builtin(position) pos: vec4<f32>,
|
|
2437
|
+
@location(0) uv: vec2<f32>,
|
|
2438
|
+
};
|
|
2439
|
+
|
|
2440
|
+
@vertex
|
|
2441
|
+
fn vs(@location(0) a_position: vec2<f32>) -> VSOut {
|
|
2442
|
+
var o: VSOut;
|
|
2443
|
+
o.uv = (a_position + vec2<f32>(1.0)) * 0.5;
|
|
2444
|
+
o.pos = vec4<f32>(a_position, 0.0, 1.0);
|
|
2445
|
+
return o;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
@fragment
|
|
2449
|
+
fn fs(in: VSOut) -> @location(0) vec4<f32> {
|
|
2450
|
+
let uv = vec2<f32>(in.uv.x, 1.0 - in.uv.y);
|
|
2451
|
+
return textureSample(u_src, u_samp, u_srcRect.xy + uv * u_srcRect.zw);
|
|
2452
|
+
}`;
|
|
2453
|
+
|
|
2454
|
+
const WEBGPU_CLEAR_WGSL = `
|
|
2455
|
+
@vertex
|
|
2456
|
+
fn vs(@location(0) a_position: vec2<f32>) -> @builtin(position) vec4<f32> {
|
|
2457
|
+
return vec4<f32>(a_position, 0.0, 1.0);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
@fragment
|
|
2461
|
+
fn fs() -> @location(0) vec4<f32> {
|
|
2462
|
+
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
|
2463
|
+
}`;
|
|
2464
|
+
|
|
2465
|
+
const GPU_UNIFORM_FLOATS = 64;
|
|
2466
|
+
|
|
2467
|
+
class WebGPUBackend {
|
|
2468
|
+
static async create(canvas) {
|
|
2469
|
+
if (typeof navigator === "undefined" || !("gpu" in navigator))
|
|
2470
|
+
return null;
|
|
2471
|
+
try {
|
|
2472
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
2473
|
+
if (!adapter) return null;
|
|
2474
|
+
const device = await adapter.requestDevice();
|
|
2475
|
+
|
|
2476
|
+
const lensModule = device.createShaderModule({
|
|
2477
|
+
code: WEBGPU_LENS_WGSL,
|
|
2478
|
+
});
|
|
2479
|
+
const blitModule = device.createShaderModule({
|
|
2480
|
+
code: WEBGPU_BLIT_WGSL,
|
|
2481
|
+
});
|
|
2482
|
+
const clearModule = device.createShaderModule({
|
|
2483
|
+
code: WEBGPU_CLEAR_WGSL,
|
|
2484
|
+
});
|
|
2485
|
+
const infos = await Promise.all([
|
|
2486
|
+
lensModule.getCompilationInfo(),
|
|
2487
|
+
blitModule.getCompilationInfo(),
|
|
2488
|
+
clearModule.getCompilationInfo(),
|
|
2489
|
+
]);
|
|
2490
|
+
const hasError = infos.some((info) =>
|
|
2491
|
+
info.messages.some((m) => m.type === "error"),
|
|
2492
|
+
);
|
|
2493
|
+
if (hasError) return null;
|
|
2494
|
+
|
|
2495
|
+
return new WebGPUBackend(
|
|
2496
|
+
canvas,
|
|
2497
|
+
device,
|
|
2498
|
+
lensModule,
|
|
2499
|
+
blitModule,
|
|
2500
|
+
clearModule,
|
|
2501
|
+
);
|
|
2502
|
+
} catch (e) {
|
|
2503
|
+
return null;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
constructor(canvas, device, lensModule, blitModule, clearModule) {
|
|
2508
|
+
this.kind = "webgpu";
|
|
2509
|
+
this.canvas = canvas;
|
|
2510
|
+
this.device = device;
|
|
2511
|
+
this.maxTextureSize = device.limits.maxTextureDimension2D || 8192;
|
|
2512
|
+
|
|
2513
|
+
this.ctx = canvas.getContext("webgpu");
|
|
2514
|
+
if (!this.ctx)
|
|
2515
|
+
throw new Error("liquidGL: WebGPU canvas context unavailable");
|
|
2516
|
+
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
2517
|
+
this.ctx.configure({
|
|
2518
|
+
device,
|
|
2519
|
+
format: this.format,
|
|
2520
|
+
alphaMode: "premultiplied",
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2523
|
+
device.lost.then((info) => {
|
|
2524
|
+
if (info.reason !== "destroyed") {
|
|
2525
|
+
console.warn("liquidGL: WebGPU device lost:", info.message);
|
|
2526
|
+
}
|
|
2527
|
+
});
|
|
2528
|
+
|
|
2529
|
+
const quad = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]);
|
|
2530
|
+
this._vb = device.createBuffer({
|
|
2531
|
+
size: quad.byteLength,
|
|
2532
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
2533
|
+
});
|
|
2534
|
+
device.queue.writeBuffer(this._vb, 0, quad);
|
|
2535
|
+
|
|
2536
|
+
this._sampler = device.createSampler({
|
|
2537
|
+
magFilter: "linear",
|
|
2538
|
+
minFilter: "linear",
|
|
2539
|
+
addressModeU: "clamp-to-edge",
|
|
2540
|
+
addressModeV: "clamp-to-edge",
|
|
2541
|
+
});
|
|
2542
|
+
|
|
2543
|
+
this._texSampLayout = device.createBindGroupLayout({
|
|
2544
|
+
entries: [
|
|
2545
|
+
{
|
|
2546
|
+
binding: 0,
|
|
2547
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
2548
|
+
texture: {},
|
|
2549
|
+
},
|
|
2550
|
+
{
|
|
2551
|
+
binding: 1,
|
|
2552
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
2553
|
+
sampler: {},
|
|
2554
|
+
},
|
|
2555
|
+
],
|
|
2556
|
+
});
|
|
2557
|
+
this._dynamicUniformLayout = device.createBindGroupLayout({
|
|
2558
|
+
entries: [
|
|
2559
|
+
{
|
|
2560
|
+
binding: 0,
|
|
2561
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
2562
|
+
buffer: { type: "uniform", hasDynamicOffset: true },
|
|
2563
|
+
},
|
|
2564
|
+
],
|
|
2565
|
+
});
|
|
2566
|
+
this._staticUniformLayout = device.createBindGroupLayout({
|
|
2567
|
+
entries: [
|
|
2568
|
+
{
|
|
2569
|
+
binding: 0,
|
|
2570
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
2571
|
+
buffer: { type: "uniform" },
|
|
2572
|
+
},
|
|
2573
|
+
],
|
|
2574
|
+
});
|
|
2575
|
+
|
|
2576
|
+
const quadBufferLayout = {
|
|
2577
|
+
arrayStride: 8,
|
|
2578
|
+
attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }],
|
|
2579
|
+
};
|
|
2580
|
+
|
|
2581
|
+
this._lensPipe = device.createRenderPipeline({
|
|
2582
|
+
layout: device.createPipelineLayout({
|
|
2583
|
+
bindGroupLayouts: [this._texSampLayout, this._dynamicUniformLayout],
|
|
2584
|
+
}),
|
|
2585
|
+
vertex: {
|
|
2586
|
+
module: lensModule,
|
|
2587
|
+
entryPoint: "vs",
|
|
2588
|
+
buffers: [quadBufferLayout],
|
|
2589
|
+
},
|
|
2590
|
+
fragment: {
|
|
2591
|
+
module: lensModule,
|
|
2592
|
+
entryPoint: "fs",
|
|
2593
|
+
targets: [{ format: this.format }],
|
|
2594
|
+
},
|
|
2595
|
+
primitive: { topology: "triangle-list" },
|
|
2596
|
+
});
|
|
2597
|
+
|
|
2598
|
+
this._blitPipe = device.createRenderPipeline({
|
|
2599
|
+
layout: device.createPipelineLayout({
|
|
2600
|
+
bindGroupLayouts: [this._texSampLayout, this._staticUniformLayout],
|
|
2601
|
+
}),
|
|
2602
|
+
vertex: {
|
|
2603
|
+
module: blitModule,
|
|
2604
|
+
entryPoint: "vs",
|
|
2605
|
+
buffers: [quadBufferLayout],
|
|
2606
|
+
},
|
|
2607
|
+
fragment: {
|
|
2608
|
+
module: blitModule,
|
|
2609
|
+
entryPoint: "fs",
|
|
2610
|
+
targets: [{ format: "rgba8unorm" }],
|
|
2611
|
+
},
|
|
2612
|
+
primitive: { topology: "triangle-list" },
|
|
2613
|
+
});
|
|
2614
|
+
|
|
2615
|
+
this._clearPipe = device.createRenderPipeline({
|
|
2616
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [] }),
|
|
2617
|
+
vertex: {
|
|
2618
|
+
module: clearModule,
|
|
2619
|
+
entryPoint: "vs",
|
|
2620
|
+
buffers: [quadBufferLayout],
|
|
2621
|
+
},
|
|
2622
|
+
fragment: {
|
|
2623
|
+
module: clearModule,
|
|
2624
|
+
entryPoint: "fs",
|
|
2625
|
+
targets: [{ format: this.format }],
|
|
2626
|
+
},
|
|
2627
|
+
primitive: { topology: "triangle-list" },
|
|
2628
|
+
});
|
|
2629
|
+
|
|
2630
|
+
this.texture = null;
|
|
2631
|
+
this._texBindGroup = null;
|
|
2632
|
+
this._videoTex = null;
|
|
2633
|
+
this._videoBindGroup = null;
|
|
2634
|
+
this._videoTexW = 0;
|
|
2635
|
+
this._videoTexH = 0;
|
|
2636
|
+
|
|
2637
|
+
this._blitUniform = device.createBuffer({
|
|
2638
|
+
size: 16,
|
|
2639
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2640
|
+
});
|
|
2641
|
+
this._blitBindGroup = device.createBindGroup({
|
|
2642
|
+
layout: this._staticUniformLayout,
|
|
2643
|
+
entries: [{ binding: 0, resource: { buffer: this._blitUniform } }],
|
|
2644
|
+
});
|
|
2645
|
+
|
|
2646
|
+
this._uniformBuf = null;
|
|
2647
|
+
this._uniformBindGroup = null;
|
|
2648
|
+
this._uniformCapacity = 0;
|
|
2649
|
+
|
|
2650
|
+
this._enc = null;
|
|
2651
|
+
this._drawQueue = [];
|
|
2652
|
+
this._frameTime = 0;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
resize() {}
|
|
2656
|
+
|
|
2657
|
+
_ensureUniformCapacity(lensCount) {
|
|
2658
|
+
const need = lensCount * 256;
|
|
2659
|
+
if (this._uniformBuf && this._uniformCapacity >= need) return;
|
|
2660
|
+
if (this._uniformBuf) this._uniformBuf.destroy();
|
|
2661
|
+
const cap = Math.max(need, 256 * 8);
|
|
2662
|
+
this._uniformBuf = this.device.createBuffer({
|
|
2663
|
+
size: cap,
|
|
2664
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2665
|
+
});
|
|
2666
|
+
this._uniformBindGroup = this.device.createBindGroup({
|
|
2667
|
+
layout: this._dynamicUniformLayout,
|
|
2668
|
+
entries: [
|
|
2669
|
+
{
|
|
2670
|
+
binding: 0,
|
|
2671
|
+
resource: { buffer: this._uniformBuf, offset: 0, size: 112 },
|
|
2672
|
+
},
|
|
2673
|
+
],
|
|
2674
|
+
});
|
|
2675
|
+
this._uniformCapacity = cap;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
uploadSnapshot(srcCanvas) {
|
|
2679
|
+
const w = srcCanvas.width;
|
|
2680
|
+
const h = srcCanvas.height;
|
|
2681
|
+
if (
|
|
2682
|
+
!this.texture ||
|
|
2683
|
+
this.texture.width !== w ||
|
|
2684
|
+
this.texture.height !== h
|
|
2685
|
+
) {
|
|
2686
|
+
if (this.texture) this.texture.destroy();
|
|
2687
|
+
this.texture = this.device.createTexture({
|
|
2688
|
+
size: [w, h],
|
|
2689
|
+
format: "rgba8unorm",
|
|
2690
|
+
usage:
|
|
2691
|
+
GPUTextureUsage.TEXTURE_BINDING |
|
|
2692
|
+
GPUTextureUsage.COPY_DST |
|
|
2693
|
+
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
2694
|
+
});
|
|
2695
|
+
this._texBindGroup = this.device.createBindGroup({
|
|
2696
|
+
layout: this._texSampLayout,
|
|
2697
|
+
entries: [
|
|
2698
|
+
{ binding: 0, resource: this.texture.createView() },
|
|
2699
|
+
{ binding: 1, resource: this._sampler },
|
|
2700
|
+
],
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
try {
|
|
2704
|
+
this.device.queue.copyExternalImageToTexture(
|
|
2705
|
+
{ source: srcCanvas },
|
|
2706
|
+
{ texture: this.texture },
|
|
2707
|
+
{ width: w, height: h },
|
|
2708
|
+
);
|
|
2709
|
+
} catch (e) {
|
|
2710
|
+
console.error("liquidGL: WebGPU snapshot upload failed", e);
|
|
2711
|
+
return false;
|
|
2712
|
+
}
|
|
2713
|
+
return true;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
uploadRegion(x, y, source) {
|
|
2717
|
+
if (!this.texture || !source) return;
|
|
2718
|
+
const w = Math.min(source.width, this.texture.width - x);
|
|
2719
|
+
const h = Math.min(source.height, this.texture.height - y);
|
|
2720
|
+
if (w <= 0 || h <= 0 || x < 0 || y < 0) return;
|
|
2721
|
+
try {
|
|
2722
|
+
this.device.queue.copyExternalImageToTexture(
|
|
2723
|
+
{ source },
|
|
2724
|
+
{ texture: this.texture, origin: [x, y] },
|
|
2725
|
+
{ width: w, height: h },
|
|
2726
|
+
);
|
|
2727
|
+
} catch (e) {
|
|
2728
|
+
console.warn("liquidGL: WebGPU region upload failed", e);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
blitVideo(vid, dstX, dstY, dstW, dstH, srcRect) {
|
|
2733
|
+
if (!this.texture) return false;
|
|
2734
|
+
const device = this.device;
|
|
2735
|
+
const vw = vid.videoWidth;
|
|
2736
|
+
const vh = vid.videoHeight;
|
|
2737
|
+
if (!vw || !vh) return false;
|
|
2738
|
+
|
|
2739
|
+
try {
|
|
2740
|
+
if (!this._vCanvas) {
|
|
2741
|
+
this._vCanvas = document.createElement("canvas");
|
|
2742
|
+
this._vCtx = this._vCanvas.getContext("2d");
|
|
2743
|
+
}
|
|
2744
|
+
if (this._vCanvas.width !== vw || this._vCanvas.height !== vh) {
|
|
2745
|
+
this._vCanvas.width = vw;
|
|
2746
|
+
this._vCanvas.height = vh;
|
|
2747
|
+
}
|
|
2748
|
+
this._vCtx.drawImage(vid, 0, 0, vw, vh);
|
|
2749
|
+
|
|
2750
|
+
if (
|
|
2751
|
+
!this._videoTex ||
|
|
2752
|
+
this._videoTexW !== vw ||
|
|
2753
|
+
this._videoTexH !== vh
|
|
2754
|
+
) {
|
|
2755
|
+
if (this._videoTex) this._videoTex.destroy();
|
|
2756
|
+
this._videoTex = device.createTexture({
|
|
2757
|
+
size: [vw, vh],
|
|
2758
|
+
format: "rgba8unorm",
|
|
2759
|
+
usage:
|
|
2760
|
+
GPUTextureUsage.TEXTURE_BINDING |
|
|
2761
|
+
GPUTextureUsage.COPY_DST |
|
|
2762
|
+
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
2763
|
+
});
|
|
2764
|
+
this._videoTexW = vw;
|
|
2765
|
+
this._videoTexH = vh;
|
|
2766
|
+
this._videoBindGroup = device.createBindGroup({
|
|
2767
|
+
layout: this._texSampLayout,
|
|
2768
|
+
entries: [
|
|
2769
|
+
{ binding: 0, resource: this._videoTex.createView() },
|
|
2770
|
+
{ binding: 1, resource: this._sampler },
|
|
2771
|
+
],
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
device.queue.copyExternalImageToTexture(
|
|
2776
|
+
{ source: this._vCanvas },
|
|
2777
|
+
{ texture: this._videoTex },
|
|
2778
|
+
{ width: vw, height: vh },
|
|
2779
|
+
);
|
|
2780
|
+
device.queue.writeBuffer(
|
|
2781
|
+
this._blitUniform,
|
|
2782
|
+
0,
|
|
2783
|
+
new Float32Array([srcRect.u, srcRect.v, srcRect.uw, srcRect.vh]),
|
|
2784
|
+
);
|
|
2785
|
+
|
|
2786
|
+
const enc = device.createCommandEncoder();
|
|
2787
|
+
const pass = enc.beginRenderPass({
|
|
2788
|
+
colorAttachments: [
|
|
2789
|
+
{
|
|
2790
|
+
view: this.texture.createView(),
|
|
2791
|
+
loadOp: "load",
|
|
2792
|
+
storeOp: "store",
|
|
2793
|
+
},
|
|
2794
|
+
],
|
|
2795
|
+
});
|
|
2796
|
+
pass.setPipeline(this._blitPipe);
|
|
2797
|
+
pass.setVertexBuffer(0, this._vb);
|
|
2798
|
+
pass.setBindGroup(0, this._videoBindGroup);
|
|
2799
|
+
pass.setBindGroup(1, this._blitBindGroup);
|
|
2800
|
+
pass.setViewport(
|
|
2801
|
+
dstX,
|
|
2802
|
+
dstY,
|
|
2803
|
+
Math.max(1, dstW),
|
|
2804
|
+
Math.max(1, dstH),
|
|
2805
|
+
0,
|
|
2806
|
+
1,
|
|
2807
|
+
);
|
|
2808
|
+
pass.draw(6);
|
|
2809
|
+
pass.end();
|
|
2810
|
+
device.queue.submit([enc.finish()]);
|
|
2811
|
+
return true;
|
|
2812
|
+
} catch (e) {
|
|
2813
|
+
return false;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
beginFrame(width, height, time) {
|
|
2818
|
+
this._frameTime = time;
|
|
2819
|
+
this._drawQueue.length = 0;
|
|
2820
|
+
this._enc = this.device.createCommandEncoder();
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
drawLens(lens, p) {
|
|
2824
|
+
const cx = Math.max(0, p.x);
|
|
2825
|
+
const cy = Math.max(0, p.y);
|
|
2826
|
+
const cw = Math.min(this.canvas.width - cx, p.w);
|
|
2827
|
+
const ch = Math.min(this.canvas.height - cy, p.h);
|
|
2828
|
+
if (cw <= 0 || ch <= 0) return;
|
|
2829
|
+
this._drawQueue.push({ p, x: cx, y: cy, w: cw, h: ch });
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
endFrame() {
|
|
2833
|
+
const device = this.device;
|
|
2834
|
+
const draws = this._drawQueue;
|
|
2835
|
+
|
|
2836
|
+
this._ensureUniformCapacity(Math.max(1, draws.length));
|
|
2837
|
+
|
|
2838
|
+
if (draws.length) {
|
|
2839
|
+
const data = new Float32Array(GPU_UNIFORM_FLOATS * draws.length);
|
|
2840
|
+
for (let i = 0; i < draws.length; i++) {
|
|
2841
|
+
const p = draws[i].p;
|
|
2842
|
+
const o = i * GPU_UNIFORM_FLOATS;
|
|
2843
|
+
data[o] = p.w;
|
|
2844
|
+
data[o + 1] = p.h;
|
|
2845
|
+
data[o + 2] = p.texW;
|
|
2846
|
+
data[o + 3] = p.texH;
|
|
2847
|
+
data[o + 4] = p.bounds[0];
|
|
2848
|
+
data[o + 5] = p.bounds[1];
|
|
2849
|
+
data[o + 6] = p.bounds[2];
|
|
2850
|
+
data[o + 7] = p.bounds[3];
|
|
2851
|
+
data[o + 8] = p.subX;
|
|
2852
|
+
data[o + 9] = p.subY;
|
|
2853
|
+
data[o + 10] = p.boxW;
|
|
2854
|
+
data[o + 11] = p.boxH;
|
|
2855
|
+
data[o + 12] = p.refraction;
|
|
2856
|
+
data[o + 13] = p.aberration;
|
|
2857
|
+
data[o + 14] = p.bevelDepth;
|
|
2858
|
+
data[o + 15] = p.bevelWidth;
|
|
2859
|
+
data[o + 16] = p.frost;
|
|
2860
|
+
data[o + 17] = p.radius;
|
|
2861
|
+
data[o + 18] = this._frameTime;
|
|
2862
|
+
data[o + 19] = p.specular;
|
|
2863
|
+
data[o + 20] = p.revealProgress;
|
|
2864
|
+
data[o + 21] = p.revealType;
|
|
2865
|
+
data[o + 22] = p.tiltX;
|
|
2866
|
+
data[o + 23] = p.tiltY;
|
|
2867
|
+
data[o + 24] = p.magnify;
|
|
2868
|
+
}
|
|
2869
|
+
device.queue.writeBuffer(this._uniformBuf, 0, data);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
const view = this.ctx.getCurrentTexture().createView();
|
|
2873
|
+
const pass = this._enc.beginRenderPass({
|
|
2874
|
+
colorAttachments: [
|
|
2875
|
+
{
|
|
2876
|
+
view,
|
|
2877
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
2878
|
+
loadOp: "clear",
|
|
2879
|
+
storeOp: "store",
|
|
2880
|
+
},
|
|
2881
|
+
],
|
|
2882
|
+
});
|
|
2883
|
+
if (draws.length) {
|
|
2884
|
+
pass.setPipeline(this._lensPipe);
|
|
2885
|
+
pass.setVertexBuffer(0, this._vb);
|
|
2886
|
+
pass.setBindGroup(0, this._texBindGroup);
|
|
2887
|
+
for (let i = 0; i < draws.length; i++) {
|
|
2888
|
+
const d = draws[i];
|
|
2889
|
+
pass.setViewport(d.x, d.y, d.w, d.h, 0, 1);
|
|
2890
|
+
pass.setBindGroup(1, this._uniformBindGroup, [i * 256]);
|
|
2891
|
+
pass.draw(6);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
pass.end();
|
|
2895
|
+
|
|
2896
|
+
device.queue.submit([this._enc.finish()]);
|
|
2897
|
+
this._enc = null;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
clearRegions(rects) {
|
|
2901
|
+
if (!rects.length) return;
|
|
2902
|
+
const device = this.device;
|
|
2903
|
+
const enc = device.createCommandEncoder();
|
|
2904
|
+
const pass = enc.beginRenderPass({
|
|
2905
|
+
colorAttachments: [
|
|
2906
|
+
{
|
|
2907
|
+
view: this.ctx.getCurrentTexture().createView(),
|
|
2908
|
+
loadOp: "load",
|
|
2909
|
+
storeOp: "store",
|
|
2910
|
+
},
|
|
2911
|
+
],
|
|
2912
|
+
});
|
|
2913
|
+
pass.setPipeline(this._clearPipe);
|
|
2914
|
+
pass.setVertexBuffer(0, this._vb);
|
|
2915
|
+
rects.forEach(({ x, y, w, h }) => {
|
|
2916
|
+
const cx = Math.max(0, Math.min(this.canvas.width, x));
|
|
2917
|
+
const cy = Math.max(0, Math.min(this.canvas.height, y));
|
|
2918
|
+
const cw = Math.max(0, Math.min(this.canvas.width - cx, w));
|
|
2919
|
+
const ch = Math.max(0, Math.min(this.canvas.height - cy, h));
|
|
2920
|
+
if (cw > 0 && ch > 0) {
|
|
2921
|
+
pass.setScissorRect(cx, cy, cw, ch);
|
|
2922
|
+
pass.draw(6);
|
|
2923
|
+
}
|
|
2924
|
+
});
|
|
2925
|
+
pass.end();
|
|
2926
|
+
device.queue.submit([enc.finish()]);
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
/* --------------------------------------------------
|
|
2931
|
+
* Shared renderer (one per page)
|
|
2932
|
+
* ------------------------------------------------*/
|
|
2933
|
+
class liquidGLRenderer {
|
|
2934
|
+
constructor(snapshotSelector, snapshotResolution = 1.0, engine = "auto") {
|
|
2935
|
+
this._engine = engine;
|
|
2936
|
+
this._naughtyQueued = false;
|
|
2937
|
+
this.canvas = document.createElement("canvas");
|
|
2938
|
+
this.canvas.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;`;
|
|
2939
|
+
this.canvas.setAttribute("data-liquid-ignore", "");
|
|
2940
|
+
document.body.appendChild(this.canvas);
|
|
2941
|
+
|
|
2942
|
+
this.backend = null;
|
|
2943
|
+
this._backendFailed = false;
|
|
2944
|
+
this._pendingLensActivation = [];
|
|
2945
|
+
|
|
2946
|
+
this.lenses = [];
|
|
2947
|
+
this.hasTexture = false;
|
|
2948
|
+
this.textureWidth = 0;
|
|
2949
|
+
this.textureHeight = 0;
|
|
2950
|
+
this.scaleFactor = 1;
|
|
2951
|
+
this.startTime = Date.now();
|
|
2952
|
+
this._scrollUpdateCounter = 0;
|
|
2953
|
+
|
|
2954
|
+
this._backendReady = this._selectBackend();
|
|
2955
|
+
|
|
2956
|
+
this.snapshotTarget =
|
|
2957
|
+
document.querySelector(snapshotSelector) || document.body;
|
|
2958
|
+
if (!this.snapshotTarget) this.snapshotTarget = document.body;
|
|
2959
|
+
|
|
2960
|
+
this._isScrolling = false;
|
|
2961
|
+
let lastScrollY = window.scrollY;
|
|
2962
|
+
let scrollTimeout;
|
|
2963
|
+
const scrollCheck = () => {
|
|
2964
|
+
if (window.scrollY !== lastScrollY) {
|
|
2965
|
+
this._isScrolling = true;
|
|
2966
|
+
lastScrollY = window.scrollY;
|
|
2967
|
+
clearTimeout(scrollTimeout);
|
|
2968
|
+
scrollTimeout = setTimeout(() => {
|
|
2969
|
+
this._isScrolling = false;
|
|
2970
|
+
}, 200);
|
|
2971
|
+
}
|
|
2972
|
+
requestAnimationFrame(scrollCheck);
|
|
2973
|
+
};
|
|
2974
|
+
requestAnimationFrame(scrollCheck);
|
|
2975
|
+
|
|
2976
|
+
const onResize = debounce(() => {
|
|
2977
|
+
if (this._capturing || this._isScrolling) return;
|
|
2978
|
+
|
|
2979
|
+
if (window.visualViewport && window.visualViewport.scale !== 1) {
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
this._dynamicNodes.forEach((node) => {
|
|
2984
|
+
const meta = this._dynMeta.get(node.el);
|
|
2985
|
+
if (meta) {
|
|
2986
|
+
meta.needsRecapture = true;
|
|
2987
|
+
meta.prevDrawRect = null;
|
|
2988
|
+
meta.lastCapture = null;
|
|
2989
|
+
}
|
|
2990
|
+
});
|
|
2991
|
+
|
|
2992
|
+
this._resizeCanvas();
|
|
2993
|
+
this.lenses.forEach((l) => l.updateMetrics());
|
|
2994
|
+
this.captureSnapshot();
|
|
2995
|
+
}, RECAPTURE_INTERVAL_MS);
|
|
2996
|
+
window.addEventListener("resize", onResize, { passive: true });
|
|
2997
|
+
|
|
2998
|
+
if ("ResizeObserver" in window) {
|
|
2999
|
+
new ResizeObserver(onResize).observe(this.snapshotTarget);
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
/* --------------------------------------------------
|
|
3003
|
+
* Dynamic DOM elements (non-video, e.g. animating text)
|
|
3004
|
+
* ------------------------------------------------*/
|
|
3005
|
+
this._dynamicNodes = [];
|
|
3006
|
+
this._dynMeta = new WeakMap();
|
|
3007
|
+
this._lastDynamicUpdate = 0;
|
|
3008
|
+
|
|
3009
|
+
const styleEl = document.createElement("style");
|
|
3010
|
+
styleEl.id = "liquid-gl-dynamic-styles";
|
|
3011
|
+
document.head.appendChild(styleEl);
|
|
3012
|
+
this._dynamicStyleSheet = styleEl.sheet;
|
|
3013
|
+
|
|
3014
|
+
this._snapshotResolution = Math.max(
|
|
3015
|
+
0.1,
|
|
3016
|
+
Math.min(3.0, snapshotResolution),
|
|
3017
|
+
);
|
|
3018
|
+
this._pendingReveal = [];
|
|
3019
|
+
|
|
3020
|
+
this._resizeCanvas();
|
|
3021
|
+
this.captureSnapshot();
|
|
3022
|
+
|
|
3023
|
+
/* --------------------------------------------------
|
|
3024
|
+
* Dynamic media (video) support
|
|
3025
|
+
* ------------------------------------------------*/
|
|
3026
|
+
this._videoNodes = Array.from(
|
|
3027
|
+
this.snapshotTarget.querySelectorAll("video"),
|
|
3028
|
+
);
|
|
3029
|
+
this._videoNodes = this._videoNodes.filter((v) => !this._isIgnored(v));
|
|
3030
|
+
this._tmpCanvas = document.createElement("canvas");
|
|
3031
|
+
this._tmpCtx = this._tmpCanvas.getContext("2d");
|
|
3032
|
+
|
|
3033
|
+
this._videoFrameState = new WeakMap();
|
|
3034
|
+
|
|
3035
|
+
this._videoAlphaState = new WeakMap();
|
|
3036
|
+
|
|
3037
|
+
this.canvas.style.opacity = "0";
|
|
3038
|
+
|
|
3039
|
+
this.useExternalTicker = false;
|
|
3040
|
+
|
|
3041
|
+
/* --------------------------------------------------
|
|
3042
|
+
* Inline worker for heavy dynamic nodes
|
|
3043
|
+
* ------------------------------------------------*/
|
|
3044
|
+
this._workerEnabled =
|
|
3045
|
+
typeof OffscreenCanvas !== "undefined" &&
|
|
3046
|
+
typeof Worker !== "undefined" &&
|
|
3047
|
+
typeof ImageBitmap !== "undefined";
|
|
3048
|
+
|
|
3049
|
+
if (this._workerEnabled) {
|
|
3050
|
+
const workerSrc = `
|
|
3051
|
+
/* dynamic-element worker (runs in its own thread) */
|
|
3052
|
+
self.onmessage = async (e) => {
|
|
3053
|
+
const { id, width, height, snap, dyn } = e.data;
|
|
3054
|
+
const off = new OffscreenCanvas(width, height);
|
|
3055
|
+
const ctx = off.getContext('2d');
|
|
3056
|
+
|
|
3057
|
+
ctx.drawImage(snap, 0, 0, width, height);
|
|
3058
|
+
ctx.drawImage(dyn, 0, 0, width, height);
|
|
3059
|
+
|
|
3060
|
+
const bmp = await off.transferToImageBitmap();
|
|
3061
|
+
self.postMessage({ id, bmp }, [bmp]);
|
|
3062
|
+
};
|
|
3063
|
+
`;
|
|
3064
|
+
const blob = new Blob([workerSrc], { type: "application/javascript" });
|
|
3065
|
+
this._dynWorker = new Worker(URL.createObjectURL(blob), {
|
|
3066
|
+
type: "module",
|
|
3067
|
+
});
|
|
3068
|
+
|
|
3069
|
+
this._dynJobs = new Map();
|
|
3070
|
+
|
|
3071
|
+
this._dynWorker.onmessage = (e) => {
|
|
3072
|
+
const { id, bmp } = e.data;
|
|
3073
|
+
const meta = this._dynJobs.get(id);
|
|
3074
|
+
if (!meta) return;
|
|
3075
|
+
this._dynJobs.delete(id);
|
|
3076
|
+
|
|
3077
|
+
const { x, y } = meta;
|
|
3078
|
+
if (!this.backend || !this.hasTexture) return;
|
|
3079
|
+
this.backend.uploadRegion(x, y, bmp);
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
/* ----------------------------- */
|
|
3085
|
+
async _selectBackend() {
|
|
3086
|
+
const chain = ENGINE_CHAINS[this._engine] || ENGINE_CHAINS.auto;
|
|
3087
|
+
let backend = null;
|
|
3088
|
+
|
|
3089
|
+
if (chain[0] === "webgpu") {
|
|
3090
|
+
try {
|
|
3091
|
+
backend = await WebGPUBackend.create(this.canvas);
|
|
3092
|
+
} catch (e) {
|
|
3093
|
+
backend = null;
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
if (!backend) {
|
|
3098
|
+
const glChain = chain.filter((c) => c !== "webgpu");
|
|
3099
|
+
if (glChain.length) {
|
|
3100
|
+
try {
|
|
3101
|
+
backend = new WebGLBackend(this.canvas, glChain);
|
|
3102
|
+
} catch (e) {
|
|
3103
|
+
backend = null;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
this.backend = backend;
|
|
3109
|
+
|
|
3110
|
+
if (!backend) {
|
|
3111
|
+
this._backendFailed = true;
|
|
3112
|
+
console.warn(
|
|
3113
|
+
"liquidGL: No GPU backend available – lenses will keep their original styles.",
|
|
3114
|
+
);
|
|
3115
|
+
return false;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
const pending = this._pendingLensActivation.splice(0);
|
|
3119
|
+
pending.forEach((ln) => ln._activate());
|
|
3120
|
+
return true;
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
/* ----------------------------- */
|
|
3124
|
+
_resizeCanvas() {
|
|
3125
|
+
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
3126
|
+
this.canvas.width = innerWidth * dpr;
|
|
3127
|
+
this.canvas.height = innerHeight * dpr;
|
|
3128
|
+
this.canvas.style.width = `${innerWidth}px`;
|
|
3129
|
+
this.canvas.style.height = `${innerHeight}px`;
|
|
3130
|
+
if (this.backend) this.backend.resize();
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
/* ----------------------------- */
|
|
3134
|
+
async captureSnapshot() {
|
|
3135
|
+
if (this._capturing) return;
|
|
3136
|
+
this._capturing = true;
|
|
3137
|
+
|
|
3138
|
+
const ready = await this._backendReady;
|
|
3139
|
+
if (!ready) {
|
|
3140
|
+
this._capturing = false;
|
|
3141
|
+
return false;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
const undos = [];
|
|
3145
|
+
|
|
3146
|
+
const attemptCapture = async (
|
|
3147
|
+
attempt = 1,
|
|
3148
|
+
maxAttempts = 3,
|
|
3149
|
+
delayMs = 500,
|
|
3150
|
+
) => {
|
|
3151
|
+
try {
|
|
3152
|
+
const fullW = this.snapshotTarget.scrollWidth;
|
|
3153
|
+
const fullH = this.snapshotTarget.scrollHeight;
|
|
3154
|
+
const maxTex = (this.backend && this.backend.maxTextureSize) || 8192;
|
|
3155
|
+
const MAX_MOBILE_DIM = 4096;
|
|
3156
|
+
const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
3157
|
+
|
|
3158
|
+
let scale = Math.min(
|
|
3159
|
+
this._snapshotResolution,
|
|
3160
|
+
maxTex / fullW,
|
|
3161
|
+
maxTex / fullH,
|
|
3162
|
+
);
|
|
3163
|
+
|
|
3164
|
+
if (isMobileSafari) {
|
|
3165
|
+
const over = (Math.max(fullW, fullH) * scale) / MAX_MOBILE_DIM;
|
|
3166
|
+
if (over > 1) scale = scale / over;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
const maxArea = isMobileSafari ? 4096 * 4096 : 16384 * 16384;
|
|
3170
|
+
if (fullW * fullH * scale * scale > maxArea) {
|
|
3171
|
+
scale = Math.sqrt(maxArea / (fullW * fullH));
|
|
3172
|
+
console.warn(
|
|
3173
|
+
`liquidGL: snapshot area capped, resolution reduced to ${scale.toFixed(
|
|
3174
|
+
3,
|
|
3175
|
+
)} for a ${fullW}x${fullH} document.`,
|
|
3176
|
+
);
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
this.scaleFactor = Math.max(0.1, scale);
|
|
3180
|
+
|
|
3181
|
+
this.canvas.style.visibility = "hidden";
|
|
3182
|
+
undos.push(() => (this.canvas.style.visibility = "visible"));
|
|
3183
|
+
|
|
3184
|
+
const lensElements = this.lenses
|
|
3185
|
+
.flatMap((lens) => [lens.el, lens._shadowEl])
|
|
3186
|
+
.filter(Boolean);
|
|
2238
3187
|
|
|
2239
3188
|
lensElements.forEach((el) => {
|
|
2240
3189
|
el.setAttribute("data-liquidgl-hide", "");
|
|
@@ -2335,30 +3284,16 @@ const liquidGL = (() => {
|
|
|
2335
3284
|
return false;
|
|
2336
3285
|
}
|
|
2337
3286
|
this.staticSnapshotCanvas = srcCanvas;
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2342
|
-
gl.texImage2D(
|
|
2343
|
-
gl.TEXTURE_2D,
|
|
2344
|
-
0,
|
|
2345
|
-
gl.RGBA,
|
|
2346
|
-
gl.RGBA,
|
|
2347
|
-
gl.UNSIGNED_BYTE,
|
|
2348
|
-
srcCanvas,
|
|
2349
|
-
);
|
|
2350
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2351
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2352
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2353
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
3287
|
+
if (!this.backend || !this.backend.uploadSnapshot(srcCanvas)) {
|
|
3288
|
+
return false;
|
|
3289
|
+
}
|
|
2354
3290
|
|
|
3291
|
+
this.hasTexture = true;
|
|
2355
3292
|
this.textureWidth = srcCanvas.width;
|
|
2356
3293
|
this.textureHeight = srcCanvas.height;
|
|
2357
3294
|
|
|
2358
3295
|
if (this._videoFrameState) this._videoFrameState = new WeakMap();
|
|
2359
3296
|
|
|
2360
|
-
this._vFboTexture = null;
|
|
2361
|
-
|
|
2362
3297
|
this.render();
|
|
2363
3298
|
|
|
2364
3299
|
if (this._pendingReveal.length) {
|
|
@@ -2379,7 +3314,13 @@ const liquidGL = (() => {
|
|
|
2379
3314
|
this.canvas.style.zIndex = maxZ - 1;
|
|
2380
3315
|
}
|
|
2381
3316
|
|
|
2382
|
-
if (
|
|
3317
|
+
if (this.backend) {
|
|
3318
|
+
lens._activate();
|
|
3319
|
+
} else {
|
|
3320
|
+
this._pendingLensActivation.push(lens);
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
if (!this.hasTexture) {
|
|
2383
3324
|
this._pendingReveal.push(lens);
|
|
2384
3325
|
} else {
|
|
2385
3326
|
lens._reveal();
|
|
@@ -2389,8 +3330,8 @@ const liquidGL = (() => {
|
|
|
2389
3330
|
|
|
2390
3331
|
/* ----------------------------- */
|
|
2391
3332
|
render() {
|
|
2392
|
-
const
|
|
2393
|
-
if (!this.
|
|
3333
|
+
const backend = this.backend;
|
|
3334
|
+
if (!backend || !this.hasTexture) return;
|
|
2394
3335
|
|
|
2395
3336
|
this.lenses.forEach((ln) => {
|
|
2396
3337
|
if (ln._isSticky && !ln._mirrorActive) ln.updateMetrics();
|
|
@@ -2400,15 +3341,9 @@ const liquidGL = (() => {
|
|
|
2400
3341
|
this._scrollUpdateCounter++;
|
|
2401
3342
|
}
|
|
2402
3343
|
|
|
2403
|
-
gl.clearColor(0, 0, 0, 0);
|
|
2404
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
2405
|
-
gl.useProgram(this.program);
|
|
2406
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2407
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2408
|
-
gl.uniform1i(this.u.tex, 0);
|
|
2409
|
-
|
|
2410
3344
|
const time = (Date.now() - this.startTime) / 1000;
|
|
2411
|
-
|
|
3345
|
+
|
|
3346
|
+
backend.beginFrame(this.canvas.width, this.canvas.height, time);
|
|
2412
3347
|
|
|
2413
3348
|
this._updateDynamicVideos();
|
|
2414
3349
|
|
|
@@ -2424,6 +3359,8 @@ const liquidGL = (() => {
|
|
|
2424
3359
|
this._renderLens(lens);
|
|
2425
3360
|
});
|
|
2426
3361
|
|
|
3362
|
+
backend.endFrame();
|
|
3363
|
+
|
|
2427
3364
|
this.lenses.forEach((ln) => {
|
|
2428
3365
|
if (ln._mirrorActive && ln._mirrorCtx) {
|
|
2429
3366
|
const mirror = ln._mirror;
|
|
@@ -2439,6 +3376,7 @@ const liquidGL = (() => {
|
|
|
2439
3376
|
});
|
|
2440
3377
|
|
|
2441
3378
|
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
3379
|
+
const clearRects = [];
|
|
2442
3380
|
this.lenses.forEach((ln) => {
|
|
2443
3381
|
if (ln._mirrorActive && ln.rectPx) {
|
|
2444
3382
|
const { left, top, width, height } = ln.rectPx;
|
|
@@ -2457,19 +3395,15 @@ const liquidGL = (() => {
|
|
|
2457
3395
|
Math.round(height * dpr) + expand * 2,
|
|
2458
3396
|
);
|
|
2459
3397
|
if (w > 0 && h > 0) {
|
|
2460
|
-
|
|
2461
|
-
gl.scissor(x, y, w, h);
|
|
2462
|
-
gl.clearColor(0, 0, 0, 0);
|
|
2463
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
2464
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
3398
|
+
clearRects.push({ x, y, w, h });
|
|
2465
3399
|
}
|
|
2466
3400
|
}
|
|
2467
3401
|
});
|
|
3402
|
+
backend.clearRegions(clearRects);
|
|
2468
3403
|
}
|
|
2469
3404
|
|
|
2470
3405
|
/* ----------------------------- */
|
|
2471
3406
|
_renderLens(lens) {
|
|
2472
|
-
const gl = this.gl;
|
|
2473
3407
|
const rect = lens.rectPx;
|
|
2474
3408
|
if (!rect) return;
|
|
2475
3409
|
|
|
@@ -2492,11 +3426,6 @@ const liquidGL = (() => {
|
|
|
2492
3426
|
const h = Math.round(topPx + rect.height * dpr) - yTop;
|
|
2493
3427
|
const y = this.canvas.height - (yTop + h);
|
|
2494
3428
|
|
|
2495
|
-
gl.viewport(x, y, w, h);
|
|
2496
|
-
gl.uniform2f(this.u.res, w, h);
|
|
2497
|
-
gl.uniform2f(this.u.subpixel, leftPx - x, topPx - yTop);
|
|
2498
|
-
gl.uniform2f(this.u.boxSize, rect.width * dpr, rect.height * dpr);
|
|
2499
|
-
|
|
2500
3429
|
const snapRect =
|
|
2501
3430
|
this._frameSnapRect || this.snapshotTarget.getBoundingClientRect();
|
|
2502
3431
|
const docX = rect.left - snapRect.left;
|
|
@@ -2505,22 +3434,6 @@ const liquidGL = (() => {
|
|
|
2505
3434
|
const topUV = (docY * this.scaleFactor) / this.textureHeight;
|
|
2506
3435
|
const wUV = (rect.width * this.scaleFactor) / this.textureWidth;
|
|
2507
3436
|
const hUV = (rect.height * this.scaleFactor) / this.textureHeight;
|
|
2508
|
-
gl.uniform4f(this.u.bounds, leftUV, topUV, wUV, hUV);
|
|
2509
|
-
|
|
2510
|
-
gl.uniform2f(
|
|
2511
|
-
this.u.textureResolution,
|
|
2512
|
-
this.textureWidth,
|
|
2513
|
-
this.textureHeight,
|
|
2514
|
-
);
|
|
2515
|
-
gl.uniform1f(this.u.refraction, lens.options.refraction);
|
|
2516
|
-
gl.uniform1f(this.u.aberration, lens.options.aberration || 0);
|
|
2517
|
-
gl.uniform1f(this.u.bevelDepth, lens.options.bevelDepth);
|
|
2518
|
-
gl.uniform1f(this.u.bevelWidth, lens.options.bevelWidth);
|
|
2519
|
-
gl.uniform1f(this.u.frost, lens.options.frost);
|
|
2520
|
-
gl.uniform1f(this.u.radius, lens.radiusGl);
|
|
2521
|
-
gl.uniform1i(this.u.specular, lens.options.specular ? 1 : 0);
|
|
2522
|
-
gl.uniform1f(this.u.revealProgress, lens._revealProgress || 1.0);
|
|
2523
|
-
gl.uniform1i(this.u.revealType, lens.revealTypeIndex || 0);
|
|
2524
3437
|
|
|
2525
3438
|
const mag = Math.max(
|
|
2526
3439
|
0.001,
|
|
@@ -2529,12 +3442,32 @@ const liquidGL = (() => {
|
|
|
2529
3442
|
lens.options.magnify !== undefined ? lens.options.magnify : 1.0,
|
|
2530
3443
|
),
|
|
2531
3444
|
);
|
|
2532
|
-
gl.uniform1f(this.u.magnify, mag);
|
|
2533
|
-
|
|
2534
|
-
gl.uniform1f(this.u.tiltX, lens.tiltX || 0);
|
|
2535
|
-
gl.uniform1f(this.u.tiltY, lens.tiltY || 0);
|
|
2536
3445
|
|
|
2537
|
-
|
|
3446
|
+
this.backend.drawLens(lens, {
|
|
3447
|
+
x,
|
|
3448
|
+
y,
|
|
3449
|
+
w,
|
|
3450
|
+
h,
|
|
3451
|
+
subX: leftPx - x,
|
|
3452
|
+
subY: topPx - yTop,
|
|
3453
|
+
boxW: rect.width * dpr,
|
|
3454
|
+
boxH: rect.height * dpr,
|
|
3455
|
+
bounds: [leftUV, topUV, wUV, hUV],
|
|
3456
|
+
texW: this.textureWidth,
|
|
3457
|
+
texH: this.textureHeight,
|
|
3458
|
+
refraction: lens.options.refraction,
|
|
3459
|
+
aberration: lens.options.aberration || 0,
|
|
3460
|
+
bevelDepth: lens.options.bevelDepth,
|
|
3461
|
+
bevelWidth: lens.options.bevelWidth,
|
|
3462
|
+
frost: lens.options.frost,
|
|
3463
|
+
radius: lens.radiusGl,
|
|
3464
|
+
specular: lens.options.specular ? 1 : 0,
|
|
3465
|
+
revealProgress: lens._revealProgress || 1.0,
|
|
3466
|
+
revealType: lens.revealTypeIndex || 0,
|
|
3467
|
+
magnify: mag,
|
|
3468
|
+
tiltX: lens.tiltX || 0,
|
|
3469
|
+
tiltY: lens.tiltY || 0,
|
|
3470
|
+
});
|
|
2538
3471
|
}
|
|
2539
3472
|
|
|
2540
3473
|
/* ----------------------------- */
|
|
@@ -2552,56 +3485,6 @@ const liquidGL = (() => {
|
|
|
2552
3485
|
ctx.closePath();
|
|
2553
3486
|
}
|
|
2554
3487
|
|
|
2555
|
-
_initVideoBlit() {
|
|
2556
|
-
if (this._vBlitReady !== undefined) return this._vBlitReady;
|
|
2557
|
-
|
|
2558
|
-
const gl = this.gl;
|
|
2559
|
-
|
|
2560
|
-
const vs = `
|
|
2561
|
-
attribute vec2 a_position;
|
|
2562
|
-
varying vec2 v_uv;
|
|
2563
|
-
void main(){
|
|
2564
|
-
v_uv = (a_position + 1.0) * 0.5;
|
|
2565
|
-
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
2566
|
-
}`;
|
|
2567
|
-
|
|
2568
|
-
const fs = `
|
|
2569
|
-
precision mediump float;
|
|
2570
|
-
varying vec2 v_uv;
|
|
2571
|
-
uniform sampler2D u_src;
|
|
2572
|
-
uniform vec4 u_srcRect;
|
|
2573
|
-
void main(){
|
|
2574
|
-
gl_FragColor = texture2D(u_src, u_srcRect.xy + v_uv * u_srcRect.zw);
|
|
2575
|
-
}`;
|
|
2576
|
-
|
|
2577
|
-
const prog = createProgram(gl, vs, fs);
|
|
2578
|
-
if (!prog) {
|
|
2579
|
-
this._vBlitReady = false;
|
|
2580
|
-
return false;
|
|
2581
|
-
}
|
|
2582
|
-
|
|
2583
|
-
this._vProg = prog;
|
|
2584
|
-
this._vPosLoc = gl.getAttribLocation(prog, "a_position");
|
|
2585
|
-
this._vU = {
|
|
2586
|
-
src: gl.getUniformLocation(prog, "u_src"),
|
|
2587
|
-
srcRect: gl.getUniformLocation(prog, "u_srcRect"),
|
|
2588
|
-
};
|
|
2589
|
-
|
|
2590
|
-
this._vTex = gl.createTexture();
|
|
2591
|
-
gl.bindTexture(gl.TEXTURE_2D, this._vTex);
|
|
2592
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2593
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2594
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2595
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2596
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2597
|
-
|
|
2598
|
-
this._vFbo = gl.createFramebuffer();
|
|
2599
|
-
this._vFboTexture = null;
|
|
2600
|
-
|
|
2601
|
-
this._vBlitReady = true;
|
|
2602
|
-
return true;
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
3488
|
_videoIsOpaque(vid) {
|
|
2606
3489
|
if (!vid.videoWidth || !vid.videoHeight) return false;
|
|
2607
3490
|
|
|
@@ -2636,92 +3519,15 @@ const liquidGL = (() => {
|
|
|
2636
3519
|
return opaque;
|
|
2637
3520
|
}
|
|
2638
3521
|
|
|
2639
|
-
_blitVideoToTexture(vid, dstX, dstY, dstW, dstH, srcRect) {
|
|
2640
|
-
const gl = this.gl;
|
|
2641
|
-
|
|
2642
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, this._vFbo);
|
|
2643
|
-
|
|
2644
|
-
if (this._vFboTexture !== this.texture) {
|
|
2645
|
-
gl.framebufferTexture2D(
|
|
2646
|
-
gl.FRAMEBUFFER,
|
|
2647
|
-
gl.COLOR_ATTACHMENT0,
|
|
2648
|
-
gl.TEXTURE_2D,
|
|
2649
|
-
this.texture,
|
|
2650
|
-
0,
|
|
2651
|
-
);
|
|
2652
|
-
if (
|
|
2653
|
-
gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE
|
|
2654
|
-
) {
|
|
2655
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2656
|
-
this._vBlitReady = false;
|
|
2657
|
-
return false;
|
|
2658
|
-
}
|
|
2659
|
-
this._vFboTexture = this.texture;
|
|
2660
|
-
}
|
|
2661
|
-
|
|
2662
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2663
|
-
gl.bindTexture(gl.TEXTURE_2D, this._vTex);
|
|
2664
|
-
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2665
|
-
|
|
2666
|
-
try {
|
|
2667
|
-
gl.texImage2D(
|
|
2668
|
-
gl.TEXTURE_2D,
|
|
2669
|
-
0,
|
|
2670
|
-
gl.RGBA,
|
|
2671
|
-
gl.RGBA,
|
|
2672
|
-
gl.UNSIGNED_BYTE,
|
|
2673
|
-
vid,
|
|
2674
|
-
);
|
|
2675
|
-
} catch (e) {
|
|
2676
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2677
|
-
this._restoreLensProgramState();
|
|
2678
|
-
return false;
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
gl.useProgram(this._vProg);
|
|
2682
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
|
|
2683
|
-
gl.enableVertexAttribArray(this._vPosLoc);
|
|
2684
|
-
gl.vertexAttribPointer(this._vPosLoc, 2, gl.FLOAT, false, 0, 0);
|
|
2685
|
-
|
|
2686
|
-
gl.uniform1i(this._vU.src, 0);
|
|
2687
|
-
gl.uniform4f(
|
|
2688
|
-
this._vU.srcRect,
|
|
2689
|
-
srcRect.u,
|
|
2690
|
-
srcRect.v,
|
|
2691
|
-
srcRect.uw,
|
|
2692
|
-
srcRect.vh,
|
|
2693
|
-
);
|
|
2694
|
-
|
|
2695
|
-
gl.viewport(dstX, dstY, dstW, dstH);
|
|
2696
|
-
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
2697
|
-
|
|
2698
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2699
|
-
this._restoreLensProgramState();
|
|
2700
|
-
|
|
2701
|
-
return true;
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
_restoreLensProgramState() {
|
|
2705
|
-
const gl = this.gl;
|
|
2706
|
-
gl.useProgram(this.program);
|
|
2707
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
|
|
2708
|
-
gl.enableVertexAttribArray(this._posLoc);
|
|
2709
|
-
gl.vertexAttribPointer(this._posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
2710
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2711
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2712
|
-
gl.uniform1i(this.u.tex, 0);
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
3522
|
/* ----------------------------- */
|
|
2716
3523
|
_updateDynamicVideos() {
|
|
2717
3524
|
if (this._isScrolling && this._scrollUpdateCounter % 2 !== 0) return;
|
|
2718
3525
|
if (
|
|
2719
|
-
!this.
|
|
3526
|
+
!this.hasTexture ||
|
|
2720
3527
|
!this.staticSnapshotCanvas ||
|
|
2721
3528
|
!this._videoNodes.length
|
|
2722
3529
|
)
|
|
2723
3530
|
return;
|
|
2724
|
-
const gl = this.gl;
|
|
2725
3531
|
|
|
2726
3532
|
const snapRect = this.snapshotTarget.getBoundingClientRect();
|
|
2727
3533
|
|
|
@@ -2803,9 +3609,8 @@ const liquidGL = (() => {
|
|
|
2803
3609
|
|
|
2804
3610
|
if (
|
|
2805
3611
|
!isRounded &&
|
|
2806
|
-
this._initVideoBlit() &&
|
|
2807
3612
|
this._videoIsOpaque(vid) &&
|
|
2808
|
-
this.
|
|
3613
|
+
this.backend.blitVideo(vid, dstX, dstY, updW, updH, {
|
|
2809
3614
|
u: srcX / drawW,
|
|
2810
3615
|
v: srcY / drawH,
|
|
2811
3616
|
uw: updW / drawW,
|
|
@@ -2856,25 +3661,14 @@ const liquidGL = (() => {
|
|
|
2856
3661
|
return;
|
|
2857
3662
|
}
|
|
2858
3663
|
|
|
2859
|
-
|
|
2860
|
-
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2861
|
-
gl.texSubImage2D(
|
|
2862
|
-
gl.TEXTURE_2D,
|
|
2863
|
-
0,
|
|
2864
|
-
dstX,
|
|
2865
|
-
dstY,
|
|
2866
|
-
gl.RGBA,
|
|
2867
|
-
gl.UNSIGNED_BYTE,
|
|
2868
|
-
this._tmpCanvas,
|
|
2869
|
-
);
|
|
3664
|
+
this.backend.uploadRegion(dstX, dstY, this._tmpCanvas);
|
|
2870
3665
|
});
|
|
2871
3666
|
}
|
|
2872
3667
|
|
|
2873
3668
|
/* ----------------------------- */
|
|
2874
3669
|
_updateDynamicNodes() {
|
|
2875
3670
|
if (this._isScrolling && this._scrollUpdateCounter % 2 !== 0) return;
|
|
2876
|
-
|
|
2877
|
-
if (!this.texture || !this._dynMeta) return;
|
|
3671
|
+
if (!this.hasTexture || !this._dynMeta) return;
|
|
2878
3672
|
const snapRect = this.snapshotTarget.getBoundingClientRect();
|
|
2879
3673
|
const maxLensZ = this._getMaxLensZ();
|
|
2880
3674
|
|
|
@@ -2979,16 +3773,7 @@ const liquidGL = (() => {
|
|
|
2979
3773
|
w,
|
|
2980
3774
|
h,
|
|
2981
3775
|
);
|
|
2982
|
-
|
|
2983
|
-
gl.texSubImage2D(
|
|
2984
|
-
gl.TEXTURE_2D,
|
|
2985
|
-
0,
|
|
2986
|
-
x,
|
|
2987
|
-
y,
|
|
2988
|
-
gl.RGBA,
|
|
2989
|
-
gl.UNSIGNED_BYTE,
|
|
2990
|
-
eraseCanvas,
|
|
2991
|
-
);
|
|
3776
|
+
this.backend.uploadRegion(x, y, eraseCanvas);
|
|
2992
3777
|
}
|
|
2993
3778
|
}
|
|
2994
3779
|
|
|
@@ -3082,16 +3867,7 @@ const liquidGL = (() => {
|
|
|
3082
3867
|
this._compositeCtx.drawImage(meta.lastCapture, 0, 0, drawW, drawH);
|
|
3083
3868
|
this._compositeCtx.restore();
|
|
3084
3869
|
|
|
3085
|
-
|
|
3086
|
-
gl.texSubImage2D(
|
|
3087
|
-
gl.TEXTURE_2D,
|
|
3088
|
-
0,
|
|
3089
|
-
dstX,
|
|
3090
|
-
dstY,
|
|
3091
|
-
gl.RGBA,
|
|
3092
|
-
gl.UNSIGNED_BYTE,
|
|
3093
|
-
compositeCanvas,
|
|
3094
|
-
);
|
|
3870
|
+
this.backend.uploadRegion(dstX, dstY, compositeCanvas);
|
|
3095
3871
|
|
|
3096
3872
|
if (this._workerEnabled && meta._heavyAnim) {
|
|
3097
3873
|
const jobId = `${Date.now()}_${Math.random()}`;
|
|
@@ -3401,6 +4177,13 @@ const liquidGL = (() => {
|
|
|
3401
4177
|
this._revealProgress = this.revealTypeIndex === 0 ? 1 : 0;
|
|
3402
4178
|
this.tiltX = 0;
|
|
3403
4179
|
this.tiltY = 0;
|
|
4180
|
+
this._activated = false;
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
/* ----------------------------- */
|
|
4184
|
+
_activate() {
|
|
4185
|
+
if (this._activated) return;
|
|
4186
|
+
this._activated = true;
|
|
3404
4187
|
|
|
3405
4188
|
this.originalShadow = this.el.style.boxShadow;
|
|
3406
4189
|
this.originalOpacity = this.el.style.opacity;
|
|
@@ -3619,6 +4402,7 @@ const liquidGL = (() => {
|
|
|
3619
4402
|
|
|
3620
4403
|
/* ----------------------------- */
|
|
3621
4404
|
_reveal() {
|
|
4405
|
+
if (!this._activated) return;
|
|
3622
4406
|
if (this.revealTypeIndex === 0) {
|
|
3623
4407
|
this.el.style.opacity = this.originalOpacity || 1;
|
|
3624
4408
|
this.renderer.canvas.style.opacity = "1";
|
|
@@ -4070,342 +4854,6 @@ const liquidGL = (() => {
|
|
|
4070
4854
|
}
|
|
4071
4855
|
}
|
|
4072
4856
|
|
|
4073
|
-
/* --------------------------------------------------
|
|
4074
|
-
* Helper GUI System
|
|
4075
|
-
* ------------------------------------------------*/
|
|
4076
|
-
let helperGUIs = [];
|
|
4077
|
-
let lilGuiLoaded = false;
|
|
4078
|
-
let lilGuiLoadPromise = null;
|
|
4079
|
-
|
|
4080
|
-
function loadLilGui() {
|
|
4081
|
-
if (lilGuiLoaded) {
|
|
4082
|
-
return Promise.resolve();
|
|
4083
|
-
}
|
|
4084
|
-
if (lilGuiLoadPromise) {
|
|
4085
|
-
return lilGuiLoadPromise;
|
|
4086
|
-
}
|
|
4087
|
-
|
|
4088
|
-
lilGuiLoadPromise = new Promise((resolve, reject) => {
|
|
4089
|
-
if (typeof lil !== "undefined") {
|
|
4090
|
-
lilGuiLoaded = true;
|
|
4091
|
-
injectHelperStyles();
|
|
4092
|
-
resolve();
|
|
4093
|
-
return;
|
|
4094
|
-
}
|
|
4095
|
-
|
|
4096
|
-
const script = document.createElement("script");
|
|
4097
|
-
script.src =
|
|
4098
|
-
"https://cdn.jsdelivr.net/npm/lil-gui@0.19.1/dist/lil-gui.umd.min.js";
|
|
4099
|
-
script.integrity =
|
|
4100
|
-
"sha384-2eNPNc7Cms+nVcpmQPotBpthLWCwjAGbkp0Y+3MUQqwPbmTpMFmbh2a230Gkns0x";
|
|
4101
|
-
script.crossOrigin = "anonymous";
|
|
4102
|
-
script.onload = () => {
|
|
4103
|
-
lilGuiLoaded = true;
|
|
4104
|
-
injectHelperStyles();
|
|
4105
|
-
resolve();
|
|
4106
|
-
};
|
|
4107
|
-
script.onerror = () => {
|
|
4108
|
-
reject(new Error("Failed to load lil-gui"));
|
|
4109
|
-
};
|
|
4110
|
-
document.head.appendChild(script);
|
|
4111
|
-
});
|
|
4112
|
-
|
|
4113
|
-
return lilGuiLoadPromise;
|
|
4114
|
-
}
|
|
4115
|
-
|
|
4116
|
-
function injectHelperStyles() {
|
|
4117
|
-
if (document.getElementById("liquidgl-helper-styles")) return;
|
|
4118
|
-
|
|
4119
|
-
const style = document.createElement("style");
|
|
4120
|
-
style.id = "liquidgl-helper-styles";
|
|
4121
|
-
style.textContent = `
|
|
4122
|
-
@media screen and (max-width: 768px) {
|
|
4123
|
-
.lil-gui.root.liquidgl-helper {
|
|
4124
|
-
width: 61vw;
|
|
4125
|
-
}
|
|
4126
|
-
}
|
|
4127
|
-
|
|
4128
|
-
.lil-gui.root.liquidgl-helper,
|
|
4129
|
-
.lil-gui.liquidgl-helper .lil-gui {
|
|
4130
|
-
--background-color: rgb(9 9 11 / 85%);
|
|
4131
|
-
--widget-color: rgb(39 39 42 / 50%);
|
|
4132
|
-
--hover-color: rgb(39 39 42 / 70%);
|
|
4133
|
-
--focus-color: rgb(39 39 42 / 90%);
|
|
4134
|
-
--number-color: #fafafa;
|
|
4135
|
-
--string-color: #fafafa;
|
|
4136
|
-
--font-size: 13px;
|
|
4137
|
-
--input-font-size: 13px;
|
|
4138
|
-
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
4139
|
-
--font-family-mono: monospace;
|
|
4140
|
-
--padding: 10px;
|
|
4141
|
-
--spacing: 10px;
|
|
4142
|
-
--widget-height: 28px;
|
|
4143
|
-
--title-height: 28px;
|
|
4144
|
-
--name-width: 45%;
|
|
4145
|
-
--slider-knob-width: 4px;
|
|
4146
|
-
--slider-input-width: 27%;
|
|
4147
|
-
--color-input-width: 27%;
|
|
4148
|
-
--slider-input-min-width: 45px;
|
|
4149
|
-
--color-input-min-width: 45px;
|
|
4150
|
-
--folder-indent: 8px;
|
|
4151
|
-
--widget-padding: 0 10px;
|
|
4152
|
-
--widget-border-radius: 4px;
|
|
4153
|
-
--checkbox-size: 16px;
|
|
4154
|
-
--scrollbar-width: 6px;
|
|
4155
|
-
}
|
|
4156
|
-
|
|
4157
|
-
.lil-gui.root.liquidgl-helper {
|
|
4158
|
-
border-radius: 12px !important;
|
|
4159
|
-
border: 0.5px solid #1e1e20 !important;
|
|
4160
|
-
backdrop-filter: blur(16px);
|
|
4161
|
-
box-shadow: 0 4px 16px rgb(0 0 0 / 20%) !important;
|
|
4162
|
-
position: fixed !important;
|
|
4163
|
-
top: 1rem !important;
|
|
4164
|
-
right: 1rem !important;
|
|
4165
|
-
left: auto !important;
|
|
4166
|
-
z-index: 999999999 !important;
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
.lil-gui.liquidgl-helper .title {
|
|
4170
|
-
background: transparent;
|
|
4171
|
-
border-bottom: 0.5px solid #1e1e20;
|
|
4172
|
-
border-radius: 12px 12px 0 0 !important;
|
|
4173
|
-
}
|
|
4174
|
-
|
|
4175
|
-
.lil-gui.liquidgl-helper .title button {
|
|
4176
|
-
padding: 12px 16px !important;
|
|
4177
|
-
}
|
|
4178
|
-
|
|
4179
|
-
.lil-gui.liquidgl-helper.closed .title {
|
|
4180
|
-
border-radius: 12px !important;
|
|
4181
|
-
border-bottom: none;
|
|
4182
|
-
}
|
|
4183
|
-
|
|
4184
|
-
.lil-gui.liquidgl-helper .children {
|
|
4185
|
-
border-top: 0.5px solid #1e1e20;
|
|
4186
|
-
}
|
|
4187
|
-
|
|
4188
|
-
@media (max-width: 768px) {
|
|
4189
|
-
.lil-gui.root.liquidgl-helper {
|
|
4190
|
-
top: 1rem !important;
|
|
4191
|
-
right: 1rem !important;
|
|
4192
|
-
}
|
|
4193
|
-
}
|
|
4194
|
-
`;
|
|
4195
|
-
document.head.appendChild(style);
|
|
4196
|
-
}
|
|
4197
|
-
|
|
4198
|
-
function createHelperGUI(lenses, options, instanceIndex) {
|
|
4199
|
-
if (typeof lil === "undefined") return;
|
|
4200
|
-
|
|
4201
|
-
const lensList = (Array.isArray(lenses) ? lenses : [lenses]).filter(
|
|
4202
|
-
Boolean,
|
|
4203
|
-
);
|
|
4204
|
-
if (!lensList.length) return;
|
|
4205
|
-
|
|
4206
|
-
const gui = new lil.GUI({
|
|
4207
|
-
title: `liquidGL Helper ${instanceIndex + 1}`,
|
|
4208
|
-
closeFolders: true,
|
|
4209
|
-
});
|
|
4210
|
-
gui.domElement.classList.add("liquidgl-helper");
|
|
4211
|
-
gui.$title.style.cursor = "default";
|
|
4212
|
-
|
|
4213
|
-
const topOffset = 1 + instanceIndex * 3;
|
|
4214
|
-
gui.domElement.style.top = `${topOffset}rem`;
|
|
4215
|
-
|
|
4216
|
-
const state = lensList[0].options;
|
|
4217
|
-
|
|
4218
|
-
const applyToLenses = (key, value) => {
|
|
4219
|
-
lensList.forEach((ln) => {
|
|
4220
|
-
if (!ln) return;
|
|
4221
|
-
ln.options[key] = value;
|
|
4222
|
-
if (key === "shadow") ln.setShadow(value);
|
|
4223
|
-
if (key === "tilt") ln.setTilt(value);
|
|
4224
|
-
});
|
|
4225
|
-
};
|
|
4226
|
-
|
|
4227
|
-
const refractionFolder = gui.addFolder("Refraction");
|
|
4228
|
-
refractionFolder
|
|
4229
|
-
.add(state, "refraction", 0, 0.1, 0.001)
|
|
4230
|
-
.name("Refraction")
|
|
4231
|
-
.onChange((v) => applyToLenses("refraction", v));
|
|
4232
|
-
refractionFolder
|
|
4233
|
-
.add(state, "aberration", 0, 1, 0.01)
|
|
4234
|
-
.name("Aberration")
|
|
4235
|
-
.onChange((v) => applyToLenses("aberration", v));
|
|
4236
|
-
refractionFolder
|
|
4237
|
-
.add(state, "bevelDepth", 0, 0.2, 0.001)
|
|
4238
|
-
.name("Bevel Depth")
|
|
4239
|
-
.onChange((v) => applyToLenses("bevelDepth", v));
|
|
4240
|
-
refractionFolder
|
|
4241
|
-
.add(state, "bevelWidth", 0, 0.5, 0.001)
|
|
4242
|
-
.name("Bevel Width")
|
|
4243
|
-
.onChange((v) => applyToLenses("bevelWidth", v));
|
|
4244
|
-
refractionFolder
|
|
4245
|
-
.add(state, "magnify", 1, 5, 0.1)
|
|
4246
|
-
.name("Magnify")
|
|
4247
|
-
.onChange((v) => applyToLenses("magnify", v));
|
|
4248
|
-
|
|
4249
|
-
const surfaceFolder = gui.addFolder("Surface");
|
|
4250
|
-
surfaceFolder
|
|
4251
|
-
.add(state, "frost", 0, 10, 0.1)
|
|
4252
|
-
.name("Frost")
|
|
4253
|
-
.onChange((v) => applyToLenses("frost", v));
|
|
4254
|
-
surfaceFolder
|
|
4255
|
-
.add(state, "specular")
|
|
4256
|
-
.name("Specular")
|
|
4257
|
-
.onChange((v) => applyToLenses("specular", v));
|
|
4258
|
-
surfaceFolder
|
|
4259
|
-
.add(state, "shadow")
|
|
4260
|
-
.name("Shadow")
|
|
4261
|
-
.onChange((v) => applyToLenses("shadow", v));
|
|
4262
|
-
|
|
4263
|
-
const tiltFolder = gui.addFolder("Tilt");
|
|
4264
|
-
tiltFolder
|
|
4265
|
-
.add(state, "tilt")
|
|
4266
|
-
.name("Tilt")
|
|
4267
|
-
.onChange((v) => applyToLenses("tilt", v));
|
|
4268
|
-
tiltFolder
|
|
4269
|
-
.add(state, "tiltFactor", 0, 25, 0.1)
|
|
4270
|
-
.name("Tilt Factor")
|
|
4271
|
-
.onChange((v) => applyToLenses("tiltFactor", v));
|
|
4272
|
-
tiltFolder
|
|
4273
|
-
.add(state, "tiltEase", 0, 1000, 10)
|
|
4274
|
-
.name("Tilt Ease")
|
|
4275
|
-
.onChange((v) => applyToLenses("tiltEase", v));
|
|
4276
|
-
|
|
4277
|
-
const initFolder = gui.addFolder("Initialisation");
|
|
4278
|
-
const reinitState = {
|
|
4279
|
-
reveal: options.reveal,
|
|
4280
|
-
resolution: options.resolution,
|
|
4281
|
-
};
|
|
4282
|
-
|
|
4283
|
-
initFolder
|
|
4284
|
-
.add(reinitState, "reveal", ["none", "fade"])
|
|
4285
|
-
.name("Reveal")
|
|
4286
|
-
.onFinishChange((value) => {
|
|
4287
|
-
if (
|
|
4288
|
-
confirm(
|
|
4289
|
-
"Changing reveal applies on the next initialisation. Continue?",
|
|
4290
|
-
)
|
|
4291
|
-
) {
|
|
4292
|
-
applyToLenses("reveal", value);
|
|
4293
|
-
} else {
|
|
4294
|
-
reinitState.reveal = options.reveal;
|
|
4295
|
-
gui.controllersRecursive().forEach((c) => c.updateDisplay());
|
|
4296
|
-
}
|
|
4297
|
-
});
|
|
4298
|
-
|
|
4299
|
-
initFolder
|
|
4300
|
-
.add(reinitState, "resolution", 0.5, 3, 0.25)
|
|
4301
|
-
.name("Resolution")
|
|
4302
|
-
.onFinishChange((value) => {
|
|
4303
|
-
if (
|
|
4304
|
-
confirm(
|
|
4305
|
-
"Changing resolution re-captures the page snapshot. Continue?",
|
|
4306
|
-
)
|
|
4307
|
-
) {
|
|
4308
|
-
applyToLenses("resolution", value);
|
|
4309
|
-
const renderer = window.__liquidGLRenderer__;
|
|
4310
|
-
if (renderer) {
|
|
4311
|
-
renderer._snapshotResolution = Math.max(0.1, Math.min(3.0, value));
|
|
4312
|
-
renderer.captureSnapshot();
|
|
4313
|
-
}
|
|
4314
|
-
} else {
|
|
4315
|
-
reinitState.resolution = options.resolution;
|
|
4316
|
-
gui.controllersRecursive().forEach((c) => c.updateDisplay());
|
|
4317
|
-
}
|
|
4318
|
-
});
|
|
4319
|
-
|
|
4320
|
-
const copyButton = {
|
|
4321
|
-
copySettings: () => {
|
|
4322
|
-
const code = generateInitCode(options);
|
|
4323
|
-
const controller = gui.controllers.find(
|
|
4324
|
-
(c) => c.property === "copySettings",
|
|
4325
|
-
);
|
|
4326
|
-
|
|
4327
|
-
if (!controller) return;
|
|
4328
|
-
|
|
4329
|
-
const originalName = controller._name;
|
|
4330
|
-
controller.disable();
|
|
4331
|
-
controller.name("✓ Copied");
|
|
4332
|
-
|
|
4333
|
-
const copySuccess = () => {
|
|
4334
|
-
setTimeout(() => {
|
|
4335
|
-
controller.name(originalName);
|
|
4336
|
-
controller.enable();
|
|
4337
|
-
}, 1500);
|
|
4338
|
-
};
|
|
4339
|
-
|
|
4340
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
4341
|
-
navigator.clipboard
|
|
4342
|
-
.writeText(code)
|
|
4343
|
-
.then(() => {
|
|
4344
|
-
copySuccess();
|
|
4345
|
-
})
|
|
4346
|
-
.catch((err) => {
|
|
4347
|
-
console.error("Clipboard error:", err);
|
|
4348
|
-
fallbackCopy(code);
|
|
4349
|
-
});
|
|
4350
|
-
} else {
|
|
4351
|
-
fallbackCopy(code);
|
|
4352
|
-
}
|
|
4353
|
-
|
|
4354
|
-
function fallbackCopy(text) {
|
|
4355
|
-
const textarea = document.createElement("textarea");
|
|
4356
|
-
textarea.value = text;
|
|
4357
|
-
textarea.style.position = "fixed";
|
|
4358
|
-
textarea.style.opacity = "0";
|
|
4359
|
-
document.body.appendChild(textarea);
|
|
4360
|
-
textarea.select();
|
|
4361
|
-
try {
|
|
4362
|
-
document.execCommand("copy");
|
|
4363
|
-
copySuccess();
|
|
4364
|
-
} catch (err) {
|
|
4365
|
-
console.error("Copy failed:", err);
|
|
4366
|
-
controller.name(originalName);
|
|
4367
|
-
controller.enable();
|
|
4368
|
-
prompt("Copy this code manually:", text);
|
|
4369
|
-
}
|
|
4370
|
-
document.body.removeChild(textarea);
|
|
4371
|
-
}
|
|
4372
|
-
},
|
|
4373
|
-
};
|
|
4374
|
-
gui.add(copyButton, "copySettings").name("Copy settings");
|
|
4375
|
-
|
|
4376
|
-
refractionFolder.close();
|
|
4377
|
-
surfaceFolder.close();
|
|
4378
|
-
tiltFolder.close();
|
|
4379
|
-
initFolder.close();
|
|
4380
|
-
|
|
4381
|
-
helperGUIs.push({ gui, lenses: lensList });
|
|
4382
|
-
return gui;
|
|
4383
|
-
}
|
|
4384
|
-
|
|
4385
|
-
function generateInitCode(options) {
|
|
4386
|
-
const lines = ["liquidGL({"];
|
|
4387
|
-
|
|
4388
|
-
lines.push(` target: "${options.target}",`);
|
|
4389
|
-
lines.push(` snapshot: "${options.snapshot}",`);
|
|
4390
|
-
lines.push(` resolution: ${options.resolution},`);
|
|
4391
|
-
lines.push(` refraction: ${options.refraction},`);
|
|
4392
|
-
lines.push(` aberration: ${options.aberration},`);
|
|
4393
|
-
lines.push(` bevelDepth: ${options.bevelDepth},`);
|
|
4394
|
-
lines.push(` bevelWidth: ${options.bevelWidth},`);
|
|
4395
|
-
lines.push(` frost: ${options.frost},`);
|
|
4396
|
-
lines.push(` shadow: ${options.shadow},`);
|
|
4397
|
-
lines.push(` specular: ${options.specular},`);
|
|
4398
|
-
lines.push(` reveal: "${options.reveal}",`);
|
|
4399
|
-
lines.push(` tilt: ${options.tilt},`);
|
|
4400
|
-
lines.push(` tiltFactor: ${options.tiltFactor},`);
|
|
4401
|
-
lines.push(` tiltEase: ${options.tiltEase},`);
|
|
4402
|
-
lines.push(` magnify: ${options.magnify},`);
|
|
4403
|
-
lines.push(` helper: false,`);
|
|
4404
|
-
lines.push(`});`);
|
|
4405
|
-
|
|
4406
|
-
return lines.join("\n");
|
|
4407
|
-
}
|
|
4408
|
-
|
|
4409
4857
|
/* --------------------------------------------------
|
|
4410
4858
|
* Public API
|
|
4411
4859
|
* ------------------------------------------------*/
|
|
@@ -4414,6 +4862,7 @@ const liquidGL = (() => {
|
|
|
4414
4862
|
target: ".liquidGL",
|
|
4415
4863
|
snapshot: "body",
|
|
4416
4864
|
resolution: 2.0,
|
|
4865
|
+
engine: "auto",
|
|
4417
4866
|
refraction: 0.01,
|
|
4418
4867
|
aberration: 0,
|
|
4419
4868
|
bevelDepth: 0.08,
|
|
@@ -4431,20 +4880,40 @@ const liquidGL = (() => {
|
|
|
4431
4880
|
};
|
|
4432
4881
|
const options = { ...defaults, ...userOptions };
|
|
4433
4882
|
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4883
|
+
const engineRaw =
|
|
4884
|
+
options.engine !== "auto"
|
|
4885
|
+
? options.engine
|
|
4886
|
+
: new URLSearchParams(window.location.search).get("liquidGL-engine") ||
|
|
4887
|
+
"auto";
|
|
4888
|
+
const engineKey = String(engineRaw).toLowerCase();
|
|
4889
|
+
if (ENGINE_CHAINS[engineKey]) {
|
|
4890
|
+
options.engine = engineKey;
|
|
4891
|
+
} else {
|
|
4892
|
+
console.warn(`liquidGL: Unknown engine "${engineRaw}" – using "auto".`);
|
|
4893
|
+
options.engine = "auto";
|
|
4441
4894
|
}
|
|
4442
4895
|
|
|
4443
|
-
const
|
|
4896
|
+
const chain = ENGINE_CHAINS[options.engine];
|
|
4897
|
+
const hasWebGPU =
|
|
4898
|
+
chain[0] === "webgpu" &&
|
|
4899
|
+
typeof navigator !== "undefined" &&
|
|
4900
|
+
"gpu" in navigator;
|
|
4901
|
+
let hasWebGL = false;
|
|
4902
|
+
const glChain = chain.filter((c) => c !== "webgpu");
|
|
4903
|
+
if (glChain.length) {
|
|
4904
|
+
const testCanvas = document.createElement("canvas");
|
|
4905
|
+
for (const name of glChain) {
|
|
4906
|
+
if (testCanvas.getContext(name)) {
|
|
4907
|
+
hasWebGL = true;
|
|
4908
|
+
break;
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
const noGPU = !hasWebGPU && !hasWebGL;
|
|
4444
4913
|
|
|
4445
|
-
if (
|
|
4914
|
+
if (window.__liquidGLNoWebGL__ === true || noGPU) {
|
|
4446
4915
|
console.warn(
|
|
4447
|
-
"liquidGL: WebGL not available – falling back to CSS backdrop-filter.",
|
|
4916
|
+
"liquidGL: WebGPU/WebGL not available – falling back to CSS backdrop-filter.",
|
|
4448
4917
|
);
|
|
4449
4918
|
const fallbackNodes = document.querySelectorAll(options.target);
|
|
4450
4919
|
fallbackNodes.forEach((node) => {
|
|
@@ -4461,7 +4930,11 @@ const liquidGL = (() => {
|
|
|
4461
4930
|
|
|
4462
4931
|
let renderer = window.__liquidGLRenderer__;
|
|
4463
4932
|
if (!renderer) {
|
|
4464
|
-
renderer = new liquidGLRenderer(
|
|
4933
|
+
renderer = new liquidGLRenderer(
|
|
4934
|
+
options.snapshot,
|
|
4935
|
+
options.resolution,
|
|
4936
|
+
options.engine,
|
|
4937
|
+
);
|
|
4465
4938
|
window.__liquidGLRenderer__ = renderer;
|
|
4466
4939
|
}
|
|
4467
4940
|
|
|
@@ -4486,14 +4959,13 @@ const liquidGL = (() => {
|
|
|
4486
4959
|
}
|
|
4487
4960
|
|
|
4488
4961
|
if (options.helper) {
|
|
4489
|
-
|
|
4490
|
-
.
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
});
|
|
4962
|
+
if (typeof window.__liquidGLHelper__ === "function") {
|
|
4963
|
+
window.__liquidGLHelper__(instances, options);
|
|
4964
|
+
} else {
|
|
4965
|
+
console.error(
|
|
4966
|
+
"liquidGL Helper Not Found - ensure liquidGL-helper.js is available in your project",
|
|
4967
|
+
);
|
|
4968
|
+
}
|
|
4497
4969
|
}
|
|
4498
4970
|
|
|
4499
4971
|
return instances.length === 1 ? instances[0] : instances;
|
|
@@ -4598,6 +5070,7 @@ const liquidGL = (() => {
|
|
|
4598
5070
|
|
|
4599
5071
|
return { lenis, locomotiveScroll: loco };
|
|
4600
5072
|
};
|
|
5073
|
+
|
|
4601
5074
|
return window.liquidGL;
|
|
4602
5075
|
})();
|
|
4603
5076
|
|