liquid-gl 2.1.0 → 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 +1279 -798
- 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;
|
|
@@ -2064,6 +1928,8 @@ const liquidGL = (() => {
|
|
|
2064
1928
|
vec2 texel = 1.0 / u_textureResolution;
|
|
2065
1929
|
vec4 refrCol;
|
|
2066
1930
|
|
|
1931
|
+
vec2 chroma = offset * u_aberration;
|
|
1932
|
+
|
|
2067
1933
|
if (u_frost > 0.0) {
|
|
2068
1934
|
float radius = u_frost * 4.0;
|
|
2069
1935
|
vec4 sum = vec4(0.0);
|
|
@@ -2072,8 +1938,15 @@ const liquidGL = (() => {
|
|
|
2072
1938
|
for (int i = 0; i < SAMPLES; i++) {
|
|
2073
1939
|
float angle = random(v_uv + float(i)) * 6.283185;
|
|
2074
1940
|
float dist = sqrt(random(v_uv - float(i))) * radius;
|
|
2075
|
-
vec2
|
|
2076
|
-
|
|
1941
|
+
vec2 foff = vec2(cos(angle), sin(angle)) * texel * dist;
|
|
1942
|
+
if (u_aberration > 0.0) {
|
|
1943
|
+
sum.r += texture2D(u_tex, sampleUV + foff - chroma).r;
|
|
1944
|
+
sum.g += texture2D(u_tex, sampleUV + foff).g;
|
|
1945
|
+
sum.b += texture2D(u_tex, sampleUV + foff + chroma).b;
|
|
1946
|
+
sum.a += texture2D(u_tex, sampleUV + foff).a;
|
|
1947
|
+
} else {
|
|
1948
|
+
sum += texture2D(u_tex, sampleUV + foff);
|
|
1949
|
+
}
|
|
2077
1950
|
}
|
|
2078
1951
|
refrCol = sum / float(SAMPLES);
|
|
2079
1952
|
} else {
|
|
@@ -2083,12 +1956,11 @@ const liquidGL = (() => {
|
|
|
2083
1956
|
refrCol += texture2D(u_tex, sampleUV + vec2(0.0, texel.y));
|
|
2084
1957
|
refrCol += texture2D(u_tex, sampleUV + vec2(0.0, -texel.y));
|
|
2085
1958
|
refrCol /= 5.0;
|
|
2086
|
-
}
|
|
2087
1959
|
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
1960
|
+
if (u_aberration > 0.0) {
|
|
1961
|
+
refrCol.r = texture2D(u_tex, sampleUV - chroma).r;
|
|
1962
|
+
refrCol.b = texture2D(u_tex, sampleUV + chroma).b;
|
|
1963
|
+
}
|
|
2092
1964
|
}
|
|
2093
1965
|
|
|
2094
1966
|
if (refrCol.a < 0.1) {
|
|
@@ -2124,8 +1996,8 @@ const liquidGL = (() => {
|
|
|
2124
1996
|
gl_FragColor = final;
|
|
2125
1997
|
}`;
|
|
2126
1998
|
|
|
2127
|
-
this.program = createProgram(this.gl, vsSource, fsSource);
|
|
2128
1999
|
const gl = this.gl;
|
|
2000
|
+
this.program = createProgram(gl, vsSource, fsSource);
|
|
2129
2001
|
if (!this.program) throw new Error("liquidGL: Shader failed");
|
|
2130
2002
|
|
|
2131
2003
|
const posBuf = gl.createBuffer();
|
|
@@ -2169,68 +2041,1153 @@ const liquidGL = (() => {
|
|
|
2169
2041
|
};
|
|
2170
2042
|
}
|
|
2171
2043
|
|
|
2172
|
-
|
|
2173
|
-
_resizeCanvas() {
|
|
2174
|
-
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
2175
|
-
this.canvas.width = innerWidth * dpr;
|
|
2176
|
-
this.canvas.height = innerHeight * dpr;
|
|
2177
|
-
this.canvas.style.width = `${innerWidth}px`;
|
|
2178
|
-
this.canvas.style.height = `${innerHeight}px`;
|
|
2044
|
+
resize() {
|
|
2179
2045
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
2180
2046
|
}
|
|
2181
2047
|
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
if (this.
|
|
2185
|
-
this.
|
|
2186
|
-
|
|
2187
|
-
|
|
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
|
+
}
|
|
2188
2068
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
)
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
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
|
+
}
|
|
2200
2084
|
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
maxTex / fullW,
|
|
2204
|
-
maxTex / fullH,
|
|
2205
|
-
);
|
|
2085
|
+
_initVideoBlit() {
|
|
2086
|
+
if (this._vBlitReady !== undefined) return this._vBlitReady;
|
|
2206
2087
|
|
|
2207
|
-
|
|
2208
|
-
const over = (Math.max(fullW, fullH) * scale) / MAX_MOBILE_DIM;
|
|
2209
|
-
if (over > 1) scale = scale / over;
|
|
2210
|
-
}
|
|
2088
|
+
const gl = this.gl;
|
|
2211
2089
|
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
);
|
|
2220
|
-
}
|
|
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
|
+
}`;
|
|
2221
2097
|
|
|
2222
|
-
|
|
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
|
+
}`;
|
|
2223
2106
|
|
|
2224
|
-
|
|
2225
|
-
|
|
2107
|
+
const prog = createProgram(gl, vs, fs);
|
|
2108
|
+
if (!prog) {
|
|
2109
|
+
this._vBlitReady = false;
|
|
2110
|
+
return false;
|
|
2111
|
+
}
|
|
2226
2112
|
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
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
|
+
};
|
|
2230
2119
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
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);
|
|
3187
|
+
|
|
3188
|
+
lensElements.forEach((el) => {
|
|
3189
|
+
el.setAttribute("data-liquidgl-hide", "");
|
|
3190
|
+
undos.push(() => {
|
|
2234
3191
|
el.removeAttribute("data-liquidgl-hide");
|
|
2235
3192
|
});
|
|
2236
3193
|
});
|
|
@@ -2327,30 +3284,16 @@ const liquidGL = (() => {
|
|
|
2327
3284
|
return false;
|
|
2328
3285
|
}
|
|
2329
3286
|
this.staticSnapshotCanvas = srcCanvas;
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2334
|
-
gl.texImage2D(
|
|
2335
|
-
gl.TEXTURE_2D,
|
|
2336
|
-
0,
|
|
2337
|
-
gl.RGBA,
|
|
2338
|
-
gl.RGBA,
|
|
2339
|
-
gl.UNSIGNED_BYTE,
|
|
2340
|
-
srcCanvas,
|
|
2341
|
-
);
|
|
2342
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2343
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2344
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2345
|
-
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
|
+
}
|
|
2346
3290
|
|
|
3291
|
+
this.hasTexture = true;
|
|
2347
3292
|
this.textureWidth = srcCanvas.width;
|
|
2348
3293
|
this.textureHeight = srcCanvas.height;
|
|
2349
3294
|
|
|
2350
3295
|
if (this._videoFrameState) this._videoFrameState = new WeakMap();
|
|
2351
3296
|
|
|
2352
|
-
this._vFboTexture = null;
|
|
2353
|
-
|
|
2354
3297
|
this.render();
|
|
2355
3298
|
|
|
2356
3299
|
if (this._pendingReveal.length) {
|
|
@@ -2371,7 +3314,13 @@ const liquidGL = (() => {
|
|
|
2371
3314
|
this.canvas.style.zIndex = maxZ - 1;
|
|
2372
3315
|
}
|
|
2373
3316
|
|
|
2374
|
-
if (
|
|
3317
|
+
if (this.backend) {
|
|
3318
|
+
lens._activate();
|
|
3319
|
+
} else {
|
|
3320
|
+
this._pendingLensActivation.push(lens);
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
if (!this.hasTexture) {
|
|
2375
3324
|
this._pendingReveal.push(lens);
|
|
2376
3325
|
} else {
|
|
2377
3326
|
lens._reveal();
|
|
@@ -2381,8 +3330,8 @@ const liquidGL = (() => {
|
|
|
2381
3330
|
|
|
2382
3331
|
/* ----------------------------- */
|
|
2383
3332
|
render() {
|
|
2384
|
-
const
|
|
2385
|
-
if (!this.
|
|
3333
|
+
const backend = this.backend;
|
|
3334
|
+
if (!backend || !this.hasTexture) return;
|
|
2386
3335
|
|
|
2387
3336
|
this.lenses.forEach((ln) => {
|
|
2388
3337
|
if (ln._isSticky && !ln._mirrorActive) ln.updateMetrics();
|
|
@@ -2392,15 +3341,9 @@ const liquidGL = (() => {
|
|
|
2392
3341
|
this._scrollUpdateCounter++;
|
|
2393
3342
|
}
|
|
2394
3343
|
|
|
2395
|
-
gl.clearColor(0, 0, 0, 0);
|
|
2396
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
2397
|
-
gl.useProgram(this.program);
|
|
2398
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2399
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2400
|
-
gl.uniform1i(this.u.tex, 0);
|
|
2401
|
-
|
|
2402
3344
|
const time = (Date.now() - this.startTime) / 1000;
|
|
2403
|
-
|
|
3345
|
+
|
|
3346
|
+
backend.beginFrame(this.canvas.width, this.canvas.height, time);
|
|
2404
3347
|
|
|
2405
3348
|
this._updateDynamicVideos();
|
|
2406
3349
|
|
|
@@ -2416,6 +3359,8 @@ const liquidGL = (() => {
|
|
|
2416
3359
|
this._renderLens(lens);
|
|
2417
3360
|
});
|
|
2418
3361
|
|
|
3362
|
+
backend.endFrame();
|
|
3363
|
+
|
|
2419
3364
|
this.lenses.forEach((ln) => {
|
|
2420
3365
|
if (ln._mirrorActive && ln._mirrorCtx) {
|
|
2421
3366
|
const mirror = ln._mirror;
|
|
@@ -2431,6 +3376,7 @@ const liquidGL = (() => {
|
|
|
2431
3376
|
});
|
|
2432
3377
|
|
|
2433
3378
|
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
3379
|
+
const clearRects = [];
|
|
2434
3380
|
this.lenses.forEach((ln) => {
|
|
2435
3381
|
if (ln._mirrorActive && ln.rectPx) {
|
|
2436
3382
|
const { left, top, width, height } = ln.rectPx;
|
|
@@ -2449,19 +3395,15 @@ const liquidGL = (() => {
|
|
|
2449
3395
|
Math.round(height * dpr) + expand * 2,
|
|
2450
3396
|
);
|
|
2451
3397
|
if (w > 0 && h > 0) {
|
|
2452
|
-
|
|
2453
|
-
gl.scissor(x, y, w, h);
|
|
2454
|
-
gl.clearColor(0, 0, 0, 0);
|
|
2455
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
2456
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
3398
|
+
clearRects.push({ x, y, w, h });
|
|
2457
3399
|
}
|
|
2458
3400
|
}
|
|
2459
3401
|
});
|
|
3402
|
+
backend.clearRegions(clearRects);
|
|
2460
3403
|
}
|
|
2461
3404
|
|
|
2462
3405
|
/* ----------------------------- */
|
|
2463
3406
|
_renderLens(lens) {
|
|
2464
|
-
const gl = this.gl;
|
|
2465
3407
|
const rect = lens.rectPx;
|
|
2466
3408
|
if (!rect) return;
|
|
2467
3409
|
|
|
@@ -2484,11 +3426,6 @@ const liquidGL = (() => {
|
|
|
2484
3426
|
const h = Math.round(topPx + rect.height * dpr) - yTop;
|
|
2485
3427
|
const y = this.canvas.height - (yTop + h);
|
|
2486
3428
|
|
|
2487
|
-
gl.viewport(x, y, w, h);
|
|
2488
|
-
gl.uniform2f(this.u.res, w, h);
|
|
2489
|
-
gl.uniform2f(this.u.subpixel, leftPx - x, topPx - yTop);
|
|
2490
|
-
gl.uniform2f(this.u.boxSize, rect.width * dpr, rect.height * dpr);
|
|
2491
|
-
|
|
2492
3429
|
const snapRect =
|
|
2493
3430
|
this._frameSnapRect || this.snapshotTarget.getBoundingClientRect();
|
|
2494
3431
|
const docX = rect.left - snapRect.left;
|
|
@@ -2497,22 +3434,6 @@ const liquidGL = (() => {
|
|
|
2497
3434
|
const topUV = (docY * this.scaleFactor) / this.textureHeight;
|
|
2498
3435
|
const wUV = (rect.width * this.scaleFactor) / this.textureWidth;
|
|
2499
3436
|
const hUV = (rect.height * this.scaleFactor) / this.textureHeight;
|
|
2500
|
-
gl.uniform4f(this.u.bounds, leftUV, topUV, wUV, hUV);
|
|
2501
|
-
|
|
2502
|
-
gl.uniform2f(
|
|
2503
|
-
this.u.textureResolution,
|
|
2504
|
-
this.textureWidth,
|
|
2505
|
-
this.textureHeight,
|
|
2506
|
-
);
|
|
2507
|
-
gl.uniform1f(this.u.refraction, lens.options.refraction);
|
|
2508
|
-
gl.uniform1f(this.u.aberration, lens.options.aberration || 0);
|
|
2509
|
-
gl.uniform1f(this.u.bevelDepth, lens.options.bevelDepth);
|
|
2510
|
-
gl.uniform1f(this.u.bevelWidth, lens.options.bevelWidth);
|
|
2511
|
-
gl.uniform1f(this.u.frost, lens.options.frost);
|
|
2512
|
-
gl.uniform1f(this.u.radius, lens.radiusGl);
|
|
2513
|
-
gl.uniform1i(this.u.specular, lens.options.specular ? 1 : 0);
|
|
2514
|
-
gl.uniform1f(this.u.revealProgress, lens._revealProgress || 1.0);
|
|
2515
|
-
gl.uniform1i(this.u.revealType, lens.revealTypeIndex || 0);
|
|
2516
3437
|
|
|
2517
3438
|
const mag = Math.max(
|
|
2518
3439
|
0.001,
|
|
@@ -2521,12 +3442,32 @@ const liquidGL = (() => {
|
|
|
2521
3442
|
lens.options.magnify !== undefined ? lens.options.magnify : 1.0,
|
|
2522
3443
|
),
|
|
2523
3444
|
);
|
|
2524
|
-
gl.uniform1f(this.u.magnify, mag);
|
|
2525
|
-
|
|
2526
|
-
gl.uniform1f(this.u.tiltX, lens.tiltX || 0);
|
|
2527
|
-
gl.uniform1f(this.u.tiltY, lens.tiltY || 0);
|
|
2528
3445
|
|
|
2529
|
-
|
|
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
|
+
});
|
|
2530
3471
|
}
|
|
2531
3472
|
|
|
2532
3473
|
/* ----------------------------- */
|
|
@@ -2544,56 +3485,6 @@ const liquidGL = (() => {
|
|
|
2544
3485
|
ctx.closePath();
|
|
2545
3486
|
}
|
|
2546
3487
|
|
|
2547
|
-
_initVideoBlit() {
|
|
2548
|
-
if (this._vBlitReady !== undefined) return this._vBlitReady;
|
|
2549
|
-
|
|
2550
|
-
const gl = this.gl;
|
|
2551
|
-
|
|
2552
|
-
const vs = `
|
|
2553
|
-
attribute vec2 a_position;
|
|
2554
|
-
varying vec2 v_uv;
|
|
2555
|
-
void main(){
|
|
2556
|
-
v_uv = (a_position + 1.0) * 0.5;
|
|
2557
|
-
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
2558
|
-
}`;
|
|
2559
|
-
|
|
2560
|
-
const fs = `
|
|
2561
|
-
precision mediump float;
|
|
2562
|
-
varying vec2 v_uv;
|
|
2563
|
-
uniform sampler2D u_src;
|
|
2564
|
-
uniform vec4 u_srcRect;
|
|
2565
|
-
void main(){
|
|
2566
|
-
gl_FragColor = texture2D(u_src, u_srcRect.xy + v_uv * u_srcRect.zw);
|
|
2567
|
-
}`;
|
|
2568
|
-
|
|
2569
|
-
const prog = createProgram(gl, vs, fs);
|
|
2570
|
-
if (!prog) {
|
|
2571
|
-
this._vBlitReady = false;
|
|
2572
|
-
return false;
|
|
2573
|
-
}
|
|
2574
|
-
|
|
2575
|
-
this._vProg = prog;
|
|
2576
|
-
this._vPosLoc = gl.getAttribLocation(prog, "a_position");
|
|
2577
|
-
this._vU = {
|
|
2578
|
-
src: gl.getUniformLocation(prog, "u_src"),
|
|
2579
|
-
srcRect: gl.getUniformLocation(prog, "u_srcRect"),
|
|
2580
|
-
};
|
|
2581
|
-
|
|
2582
|
-
this._vTex = gl.createTexture();
|
|
2583
|
-
gl.bindTexture(gl.TEXTURE_2D, this._vTex);
|
|
2584
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2585
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2586
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2587
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2588
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2589
|
-
|
|
2590
|
-
this._vFbo = gl.createFramebuffer();
|
|
2591
|
-
this._vFboTexture = null;
|
|
2592
|
-
|
|
2593
|
-
this._vBlitReady = true;
|
|
2594
|
-
return true;
|
|
2595
|
-
}
|
|
2596
|
-
|
|
2597
3488
|
_videoIsOpaque(vid) {
|
|
2598
3489
|
if (!vid.videoWidth || !vid.videoHeight) return false;
|
|
2599
3490
|
|
|
@@ -2628,92 +3519,15 @@ const liquidGL = (() => {
|
|
|
2628
3519
|
return opaque;
|
|
2629
3520
|
}
|
|
2630
3521
|
|
|
2631
|
-
_blitVideoToTexture(vid, dstX, dstY, dstW, dstH, srcRect) {
|
|
2632
|
-
const gl = this.gl;
|
|
2633
|
-
|
|
2634
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, this._vFbo);
|
|
2635
|
-
|
|
2636
|
-
if (this._vFboTexture !== this.texture) {
|
|
2637
|
-
gl.framebufferTexture2D(
|
|
2638
|
-
gl.FRAMEBUFFER,
|
|
2639
|
-
gl.COLOR_ATTACHMENT0,
|
|
2640
|
-
gl.TEXTURE_2D,
|
|
2641
|
-
this.texture,
|
|
2642
|
-
0,
|
|
2643
|
-
);
|
|
2644
|
-
if (
|
|
2645
|
-
gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE
|
|
2646
|
-
) {
|
|
2647
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2648
|
-
this._vBlitReady = false;
|
|
2649
|
-
return false;
|
|
2650
|
-
}
|
|
2651
|
-
this._vFboTexture = this.texture;
|
|
2652
|
-
}
|
|
2653
|
-
|
|
2654
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2655
|
-
gl.bindTexture(gl.TEXTURE_2D, this._vTex);
|
|
2656
|
-
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2657
|
-
|
|
2658
|
-
try {
|
|
2659
|
-
gl.texImage2D(
|
|
2660
|
-
gl.TEXTURE_2D,
|
|
2661
|
-
0,
|
|
2662
|
-
gl.RGBA,
|
|
2663
|
-
gl.RGBA,
|
|
2664
|
-
gl.UNSIGNED_BYTE,
|
|
2665
|
-
vid,
|
|
2666
|
-
);
|
|
2667
|
-
} catch (e) {
|
|
2668
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2669
|
-
this._restoreLensProgramState();
|
|
2670
|
-
return false;
|
|
2671
|
-
}
|
|
2672
|
-
|
|
2673
|
-
gl.useProgram(this._vProg);
|
|
2674
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
|
|
2675
|
-
gl.enableVertexAttribArray(this._vPosLoc);
|
|
2676
|
-
gl.vertexAttribPointer(this._vPosLoc, 2, gl.FLOAT, false, 0, 0);
|
|
2677
|
-
|
|
2678
|
-
gl.uniform1i(this._vU.src, 0);
|
|
2679
|
-
gl.uniform4f(
|
|
2680
|
-
this._vU.srcRect,
|
|
2681
|
-
srcRect.u,
|
|
2682
|
-
srcRect.v,
|
|
2683
|
-
srcRect.uw,
|
|
2684
|
-
srcRect.vh,
|
|
2685
|
-
);
|
|
2686
|
-
|
|
2687
|
-
gl.viewport(dstX, dstY, dstW, dstH);
|
|
2688
|
-
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
2689
|
-
|
|
2690
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2691
|
-
this._restoreLensProgramState();
|
|
2692
|
-
|
|
2693
|
-
return true;
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
_restoreLensProgramState() {
|
|
2697
|
-
const gl = this.gl;
|
|
2698
|
-
gl.useProgram(this.program);
|
|
2699
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
|
|
2700
|
-
gl.enableVertexAttribArray(this._posLoc);
|
|
2701
|
-
gl.vertexAttribPointer(this._posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
2702
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2703
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
2704
|
-
gl.uniform1i(this.u.tex, 0);
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
3522
|
/* ----------------------------- */
|
|
2708
3523
|
_updateDynamicVideos() {
|
|
2709
3524
|
if (this._isScrolling && this._scrollUpdateCounter % 2 !== 0) return;
|
|
2710
3525
|
if (
|
|
2711
|
-
!this.
|
|
3526
|
+
!this.hasTexture ||
|
|
2712
3527
|
!this.staticSnapshotCanvas ||
|
|
2713
3528
|
!this._videoNodes.length
|
|
2714
3529
|
)
|
|
2715
3530
|
return;
|
|
2716
|
-
const gl = this.gl;
|
|
2717
3531
|
|
|
2718
3532
|
const snapRect = this.snapshotTarget.getBoundingClientRect();
|
|
2719
3533
|
|
|
@@ -2795,9 +3609,8 @@ const liquidGL = (() => {
|
|
|
2795
3609
|
|
|
2796
3610
|
if (
|
|
2797
3611
|
!isRounded &&
|
|
2798
|
-
this._initVideoBlit() &&
|
|
2799
3612
|
this._videoIsOpaque(vid) &&
|
|
2800
|
-
this.
|
|
3613
|
+
this.backend.blitVideo(vid, dstX, dstY, updW, updH, {
|
|
2801
3614
|
u: srcX / drawW,
|
|
2802
3615
|
v: srcY / drawH,
|
|
2803
3616
|
uw: updW / drawW,
|
|
@@ -2848,25 +3661,14 @@ const liquidGL = (() => {
|
|
|
2848
3661
|
return;
|
|
2849
3662
|
}
|
|
2850
3663
|
|
|
2851
|
-
|
|
2852
|
-
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
2853
|
-
gl.texSubImage2D(
|
|
2854
|
-
gl.TEXTURE_2D,
|
|
2855
|
-
0,
|
|
2856
|
-
dstX,
|
|
2857
|
-
dstY,
|
|
2858
|
-
gl.RGBA,
|
|
2859
|
-
gl.UNSIGNED_BYTE,
|
|
2860
|
-
this._tmpCanvas,
|
|
2861
|
-
);
|
|
3664
|
+
this.backend.uploadRegion(dstX, dstY, this._tmpCanvas);
|
|
2862
3665
|
});
|
|
2863
3666
|
}
|
|
2864
3667
|
|
|
2865
3668
|
/* ----------------------------- */
|
|
2866
3669
|
_updateDynamicNodes() {
|
|
2867
3670
|
if (this._isScrolling && this._scrollUpdateCounter % 2 !== 0) return;
|
|
2868
|
-
|
|
2869
|
-
if (!this.texture || !this._dynMeta) return;
|
|
3671
|
+
if (!this.hasTexture || !this._dynMeta) return;
|
|
2870
3672
|
const snapRect = this.snapshotTarget.getBoundingClientRect();
|
|
2871
3673
|
const maxLensZ = this._getMaxLensZ();
|
|
2872
3674
|
|
|
@@ -2971,16 +3773,7 @@ const liquidGL = (() => {
|
|
|
2971
3773
|
w,
|
|
2972
3774
|
h,
|
|
2973
3775
|
);
|
|
2974
|
-
|
|
2975
|
-
gl.texSubImage2D(
|
|
2976
|
-
gl.TEXTURE_2D,
|
|
2977
|
-
0,
|
|
2978
|
-
x,
|
|
2979
|
-
y,
|
|
2980
|
-
gl.RGBA,
|
|
2981
|
-
gl.UNSIGNED_BYTE,
|
|
2982
|
-
eraseCanvas,
|
|
2983
|
-
);
|
|
3776
|
+
this.backend.uploadRegion(x, y, eraseCanvas);
|
|
2984
3777
|
}
|
|
2985
3778
|
}
|
|
2986
3779
|
|
|
@@ -3074,16 +3867,7 @@ const liquidGL = (() => {
|
|
|
3074
3867
|
this._compositeCtx.drawImage(meta.lastCapture, 0, 0, drawW, drawH);
|
|
3075
3868
|
this._compositeCtx.restore();
|
|
3076
3869
|
|
|
3077
|
-
|
|
3078
|
-
gl.texSubImage2D(
|
|
3079
|
-
gl.TEXTURE_2D,
|
|
3080
|
-
0,
|
|
3081
|
-
dstX,
|
|
3082
|
-
dstY,
|
|
3083
|
-
gl.RGBA,
|
|
3084
|
-
gl.UNSIGNED_BYTE,
|
|
3085
|
-
compositeCanvas,
|
|
3086
|
-
);
|
|
3870
|
+
this.backend.uploadRegion(dstX, dstY, compositeCanvas);
|
|
3087
3871
|
|
|
3088
3872
|
if (this._workerEnabled && meta._heavyAnim) {
|
|
3089
3873
|
const jobId = `${Date.now()}_${Math.random()}`;
|
|
@@ -3393,6 +4177,13 @@ const liquidGL = (() => {
|
|
|
3393
4177
|
this._revealProgress = this.revealTypeIndex === 0 ? 1 : 0;
|
|
3394
4178
|
this.tiltX = 0;
|
|
3395
4179
|
this.tiltY = 0;
|
|
4180
|
+
this._activated = false;
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
/* ----------------------------- */
|
|
4184
|
+
_activate() {
|
|
4185
|
+
if (this._activated) return;
|
|
4186
|
+
this._activated = true;
|
|
3396
4187
|
|
|
3397
4188
|
this.originalShadow = this.el.style.boxShadow;
|
|
3398
4189
|
this.originalOpacity = this.el.style.opacity;
|
|
@@ -3611,6 +4402,7 @@ const liquidGL = (() => {
|
|
|
3611
4402
|
|
|
3612
4403
|
/* ----------------------------- */
|
|
3613
4404
|
_reveal() {
|
|
4405
|
+
if (!this._activated) return;
|
|
3614
4406
|
if (this.revealTypeIndex === 0) {
|
|
3615
4407
|
this.el.style.opacity = this.originalOpacity || 1;
|
|
3616
4408
|
this.renderer.canvas.style.opacity = "1";
|
|
@@ -4062,342 +4854,6 @@ const liquidGL = (() => {
|
|
|
4062
4854
|
}
|
|
4063
4855
|
}
|
|
4064
4856
|
|
|
4065
|
-
/* --------------------------------------------------
|
|
4066
|
-
* Helper GUI System
|
|
4067
|
-
* ------------------------------------------------*/
|
|
4068
|
-
let helperGUIs = [];
|
|
4069
|
-
let lilGuiLoaded = false;
|
|
4070
|
-
let lilGuiLoadPromise = null;
|
|
4071
|
-
|
|
4072
|
-
function loadLilGui() {
|
|
4073
|
-
if (lilGuiLoaded) {
|
|
4074
|
-
return Promise.resolve();
|
|
4075
|
-
}
|
|
4076
|
-
if (lilGuiLoadPromise) {
|
|
4077
|
-
return lilGuiLoadPromise;
|
|
4078
|
-
}
|
|
4079
|
-
|
|
4080
|
-
lilGuiLoadPromise = new Promise((resolve, reject) => {
|
|
4081
|
-
if (typeof lil !== "undefined") {
|
|
4082
|
-
lilGuiLoaded = true;
|
|
4083
|
-
injectHelperStyles();
|
|
4084
|
-
resolve();
|
|
4085
|
-
return;
|
|
4086
|
-
}
|
|
4087
|
-
|
|
4088
|
-
const script = document.createElement("script");
|
|
4089
|
-
script.src =
|
|
4090
|
-
"https://cdn.jsdelivr.net/npm/lil-gui@0.19.1/dist/lil-gui.umd.min.js";
|
|
4091
|
-
script.integrity =
|
|
4092
|
-
"sha384-2eNPNc7Cms+nVcpmQPotBpthLWCwjAGbkp0Y+3MUQqwPbmTpMFmbh2a230Gkns0x";
|
|
4093
|
-
script.crossOrigin = "anonymous";
|
|
4094
|
-
script.onload = () => {
|
|
4095
|
-
lilGuiLoaded = true;
|
|
4096
|
-
injectHelperStyles();
|
|
4097
|
-
resolve();
|
|
4098
|
-
};
|
|
4099
|
-
script.onerror = () => {
|
|
4100
|
-
reject(new Error("Failed to load lil-gui"));
|
|
4101
|
-
};
|
|
4102
|
-
document.head.appendChild(script);
|
|
4103
|
-
});
|
|
4104
|
-
|
|
4105
|
-
return lilGuiLoadPromise;
|
|
4106
|
-
}
|
|
4107
|
-
|
|
4108
|
-
function injectHelperStyles() {
|
|
4109
|
-
if (document.getElementById("liquidgl-helper-styles")) return;
|
|
4110
|
-
|
|
4111
|
-
const style = document.createElement("style");
|
|
4112
|
-
style.id = "liquidgl-helper-styles";
|
|
4113
|
-
style.textContent = `
|
|
4114
|
-
@media screen and (max-width: 768px) {
|
|
4115
|
-
.lil-gui.root.liquidgl-helper {
|
|
4116
|
-
width: 61vw;
|
|
4117
|
-
}
|
|
4118
|
-
}
|
|
4119
|
-
|
|
4120
|
-
.lil-gui.root.liquidgl-helper,
|
|
4121
|
-
.lil-gui.liquidgl-helper .lil-gui {
|
|
4122
|
-
--background-color: rgb(9 9 11 / 85%);
|
|
4123
|
-
--widget-color: rgb(39 39 42 / 50%);
|
|
4124
|
-
--hover-color: rgb(39 39 42 / 70%);
|
|
4125
|
-
--focus-color: rgb(39 39 42 / 90%);
|
|
4126
|
-
--number-color: #fafafa;
|
|
4127
|
-
--string-color: #fafafa;
|
|
4128
|
-
--font-size: 13px;
|
|
4129
|
-
--input-font-size: 13px;
|
|
4130
|
-
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
4131
|
-
--font-family-mono: monospace;
|
|
4132
|
-
--padding: 10px;
|
|
4133
|
-
--spacing: 10px;
|
|
4134
|
-
--widget-height: 28px;
|
|
4135
|
-
--title-height: 28px;
|
|
4136
|
-
--name-width: 45%;
|
|
4137
|
-
--slider-knob-width: 4px;
|
|
4138
|
-
--slider-input-width: 27%;
|
|
4139
|
-
--color-input-width: 27%;
|
|
4140
|
-
--slider-input-min-width: 45px;
|
|
4141
|
-
--color-input-min-width: 45px;
|
|
4142
|
-
--folder-indent: 8px;
|
|
4143
|
-
--widget-padding: 0 10px;
|
|
4144
|
-
--widget-border-radius: 4px;
|
|
4145
|
-
--checkbox-size: 16px;
|
|
4146
|
-
--scrollbar-width: 6px;
|
|
4147
|
-
}
|
|
4148
|
-
|
|
4149
|
-
.lil-gui.root.liquidgl-helper {
|
|
4150
|
-
border-radius: 12px !important;
|
|
4151
|
-
border: 0.5px solid #1e1e20 !important;
|
|
4152
|
-
backdrop-filter: blur(16px);
|
|
4153
|
-
box-shadow: 0 4px 16px rgb(0 0 0 / 20%) !important;
|
|
4154
|
-
position: fixed !important;
|
|
4155
|
-
top: 1rem !important;
|
|
4156
|
-
right: 1rem !important;
|
|
4157
|
-
left: auto !important;
|
|
4158
|
-
z-index: 999999999 !important;
|
|
4159
|
-
}
|
|
4160
|
-
|
|
4161
|
-
.lil-gui.liquidgl-helper .title {
|
|
4162
|
-
background: transparent;
|
|
4163
|
-
border-bottom: 0.5px solid #1e1e20;
|
|
4164
|
-
border-radius: 12px 12px 0 0 !important;
|
|
4165
|
-
}
|
|
4166
|
-
|
|
4167
|
-
.lil-gui.liquidgl-helper .title button {
|
|
4168
|
-
padding: 12px 16px !important;
|
|
4169
|
-
}
|
|
4170
|
-
|
|
4171
|
-
.lil-gui.liquidgl-helper.closed .title {
|
|
4172
|
-
border-radius: 12px !important;
|
|
4173
|
-
border-bottom: none;
|
|
4174
|
-
}
|
|
4175
|
-
|
|
4176
|
-
.lil-gui.liquidgl-helper .children {
|
|
4177
|
-
border-top: 0.5px solid #1e1e20;
|
|
4178
|
-
}
|
|
4179
|
-
|
|
4180
|
-
@media (max-width: 768px) {
|
|
4181
|
-
.lil-gui.root.liquidgl-helper {
|
|
4182
|
-
top: 1rem !important;
|
|
4183
|
-
right: 1rem !important;
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
`;
|
|
4187
|
-
document.head.appendChild(style);
|
|
4188
|
-
}
|
|
4189
|
-
|
|
4190
|
-
function createHelperGUI(lenses, options, instanceIndex) {
|
|
4191
|
-
if (typeof lil === "undefined") return;
|
|
4192
|
-
|
|
4193
|
-
const lensList = (Array.isArray(lenses) ? lenses : [lenses]).filter(
|
|
4194
|
-
Boolean,
|
|
4195
|
-
);
|
|
4196
|
-
if (!lensList.length) return;
|
|
4197
|
-
|
|
4198
|
-
const gui = new lil.GUI({
|
|
4199
|
-
title: `liquidGL Helper ${instanceIndex + 1}`,
|
|
4200
|
-
closeFolders: true,
|
|
4201
|
-
});
|
|
4202
|
-
gui.domElement.classList.add("liquidgl-helper");
|
|
4203
|
-
gui.$title.style.cursor = "default";
|
|
4204
|
-
|
|
4205
|
-
const topOffset = 1 + instanceIndex * 3;
|
|
4206
|
-
gui.domElement.style.top = `${topOffset}rem`;
|
|
4207
|
-
|
|
4208
|
-
const state = lensList[0].options;
|
|
4209
|
-
|
|
4210
|
-
const applyToLenses = (key, value) => {
|
|
4211
|
-
lensList.forEach((ln) => {
|
|
4212
|
-
if (!ln) return;
|
|
4213
|
-
ln.options[key] = value;
|
|
4214
|
-
if (key === "shadow") ln.setShadow(value);
|
|
4215
|
-
if (key === "tilt") ln.setTilt(value);
|
|
4216
|
-
});
|
|
4217
|
-
};
|
|
4218
|
-
|
|
4219
|
-
const refractionFolder = gui.addFolder("Refraction");
|
|
4220
|
-
refractionFolder
|
|
4221
|
-
.add(state, "refraction", 0, 0.1, 0.001)
|
|
4222
|
-
.name("Refraction")
|
|
4223
|
-
.onChange((v) => applyToLenses("refraction", v));
|
|
4224
|
-
refractionFolder
|
|
4225
|
-
.add(state, "aberration", 0, 1, 0.01)
|
|
4226
|
-
.name("Aberration")
|
|
4227
|
-
.onChange((v) => applyToLenses("aberration", v));
|
|
4228
|
-
refractionFolder
|
|
4229
|
-
.add(state, "bevelDepth", 0, 0.2, 0.001)
|
|
4230
|
-
.name("Bevel Depth")
|
|
4231
|
-
.onChange((v) => applyToLenses("bevelDepth", v));
|
|
4232
|
-
refractionFolder
|
|
4233
|
-
.add(state, "bevelWidth", 0, 0.5, 0.001)
|
|
4234
|
-
.name("Bevel Width")
|
|
4235
|
-
.onChange((v) => applyToLenses("bevelWidth", v));
|
|
4236
|
-
refractionFolder
|
|
4237
|
-
.add(state, "magnify", 1, 5, 0.1)
|
|
4238
|
-
.name("Magnify")
|
|
4239
|
-
.onChange((v) => applyToLenses("magnify", v));
|
|
4240
|
-
|
|
4241
|
-
const surfaceFolder = gui.addFolder("Surface");
|
|
4242
|
-
surfaceFolder
|
|
4243
|
-
.add(state, "frost", 0, 10, 0.1)
|
|
4244
|
-
.name("Frost")
|
|
4245
|
-
.onChange((v) => applyToLenses("frost", v));
|
|
4246
|
-
surfaceFolder
|
|
4247
|
-
.add(state, "specular")
|
|
4248
|
-
.name("Specular")
|
|
4249
|
-
.onChange((v) => applyToLenses("specular", v));
|
|
4250
|
-
surfaceFolder
|
|
4251
|
-
.add(state, "shadow")
|
|
4252
|
-
.name("Shadow")
|
|
4253
|
-
.onChange((v) => applyToLenses("shadow", v));
|
|
4254
|
-
|
|
4255
|
-
const tiltFolder = gui.addFolder("Tilt");
|
|
4256
|
-
tiltFolder
|
|
4257
|
-
.add(state, "tilt")
|
|
4258
|
-
.name("Tilt")
|
|
4259
|
-
.onChange((v) => applyToLenses("tilt", v));
|
|
4260
|
-
tiltFolder
|
|
4261
|
-
.add(state, "tiltFactor", 0, 25, 0.1)
|
|
4262
|
-
.name("Tilt Factor")
|
|
4263
|
-
.onChange((v) => applyToLenses("tiltFactor", v));
|
|
4264
|
-
tiltFolder
|
|
4265
|
-
.add(state, "tiltEase", 0, 1000, 10)
|
|
4266
|
-
.name("Tilt Ease")
|
|
4267
|
-
.onChange((v) => applyToLenses("tiltEase", v));
|
|
4268
|
-
|
|
4269
|
-
const initFolder = gui.addFolder("Initialisation");
|
|
4270
|
-
const reinitState = {
|
|
4271
|
-
reveal: options.reveal,
|
|
4272
|
-
resolution: options.resolution,
|
|
4273
|
-
};
|
|
4274
|
-
|
|
4275
|
-
initFolder
|
|
4276
|
-
.add(reinitState, "reveal", ["none", "fade"])
|
|
4277
|
-
.name("Reveal")
|
|
4278
|
-
.onFinishChange((value) => {
|
|
4279
|
-
if (
|
|
4280
|
-
confirm(
|
|
4281
|
-
"Changing reveal applies on the next initialisation. Continue?",
|
|
4282
|
-
)
|
|
4283
|
-
) {
|
|
4284
|
-
applyToLenses("reveal", value);
|
|
4285
|
-
} else {
|
|
4286
|
-
reinitState.reveal = options.reveal;
|
|
4287
|
-
gui.controllersRecursive().forEach((c) => c.updateDisplay());
|
|
4288
|
-
}
|
|
4289
|
-
});
|
|
4290
|
-
|
|
4291
|
-
initFolder
|
|
4292
|
-
.add(reinitState, "resolution", 0.5, 3, 0.25)
|
|
4293
|
-
.name("Resolution")
|
|
4294
|
-
.onFinishChange((value) => {
|
|
4295
|
-
if (
|
|
4296
|
-
confirm(
|
|
4297
|
-
"Changing resolution re-captures the page snapshot. Continue?",
|
|
4298
|
-
)
|
|
4299
|
-
) {
|
|
4300
|
-
applyToLenses("resolution", value);
|
|
4301
|
-
const renderer = window.__liquidGLRenderer__;
|
|
4302
|
-
if (renderer) {
|
|
4303
|
-
renderer._snapshotResolution = Math.max(0.1, Math.min(3.0, value));
|
|
4304
|
-
renderer.captureSnapshot();
|
|
4305
|
-
}
|
|
4306
|
-
} else {
|
|
4307
|
-
reinitState.resolution = options.resolution;
|
|
4308
|
-
gui.controllersRecursive().forEach((c) => c.updateDisplay());
|
|
4309
|
-
}
|
|
4310
|
-
});
|
|
4311
|
-
|
|
4312
|
-
const copyButton = {
|
|
4313
|
-
copySettings: () => {
|
|
4314
|
-
const code = generateInitCode(options);
|
|
4315
|
-
const controller = gui.controllers.find(
|
|
4316
|
-
(c) => c.property === "copySettings",
|
|
4317
|
-
);
|
|
4318
|
-
|
|
4319
|
-
if (!controller) return;
|
|
4320
|
-
|
|
4321
|
-
const originalName = controller._name;
|
|
4322
|
-
controller.disable();
|
|
4323
|
-
controller.name("✓ Copied");
|
|
4324
|
-
|
|
4325
|
-
const copySuccess = () => {
|
|
4326
|
-
setTimeout(() => {
|
|
4327
|
-
controller.name(originalName);
|
|
4328
|
-
controller.enable();
|
|
4329
|
-
}, 1500);
|
|
4330
|
-
};
|
|
4331
|
-
|
|
4332
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
4333
|
-
navigator.clipboard
|
|
4334
|
-
.writeText(code)
|
|
4335
|
-
.then(() => {
|
|
4336
|
-
copySuccess();
|
|
4337
|
-
})
|
|
4338
|
-
.catch((err) => {
|
|
4339
|
-
console.error("Clipboard error:", err);
|
|
4340
|
-
fallbackCopy(code);
|
|
4341
|
-
});
|
|
4342
|
-
} else {
|
|
4343
|
-
fallbackCopy(code);
|
|
4344
|
-
}
|
|
4345
|
-
|
|
4346
|
-
function fallbackCopy(text) {
|
|
4347
|
-
const textarea = document.createElement("textarea");
|
|
4348
|
-
textarea.value = text;
|
|
4349
|
-
textarea.style.position = "fixed";
|
|
4350
|
-
textarea.style.opacity = "0";
|
|
4351
|
-
document.body.appendChild(textarea);
|
|
4352
|
-
textarea.select();
|
|
4353
|
-
try {
|
|
4354
|
-
document.execCommand("copy");
|
|
4355
|
-
copySuccess();
|
|
4356
|
-
} catch (err) {
|
|
4357
|
-
console.error("Copy failed:", err);
|
|
4358
|
-
controller.name(originalName);
|
|
4359
|
-
controller.enable();
|
|
4360
|
-
prompt("Copy this code manually:", text);
|
|
4361
|
-
}
|
|
4362
|
-
document.body.removeChild(textarea);
|
|
4363
|
-
}
|
|
4364
|
-
},
|
|
4365
|
-
};
|
|
4366
|
-
gui.add(copyButton, "copySettings").name("Copy settings");
|
|
4367
|
-
|
|
4368
|
-
refractionFolder.close();
|
|
4369
|
-
surfaceFolder.close();
|
|
4370
|
-
tiltFolder.close();
|
|
4371
|
-
initFolder.close();
|
|
4372
|
-
|
|
4373
|
-
helperGUIs.push({ gui, lenses: lensList });
|
|
4374
|
-
return gui;
|
|
4375
|
-
}
|
|
4376
|
-
|
|
4377
|
-
function generateInitCode(options) {
|
|
4378
|
-
const lines = ["liquidGL({"];
|
|
4379
|
-
|
|
4380
|
-
lines.push(` target: "${options.target}",`);
|
|
4381
|
-
lines.push(` snapshot: "${options.snapshot}",`);
|
|
4382
|
-
lines.push(` resolution: ${options.resolution},`);
|
|
4383
|
-
lines.push(` refraction: ${options.refraction},`);
|
|
4384
|
-
lines.push(` aberration: ${options.aberration},`);
|
|
4385
|
-
lines.push(` bevelDepth: ${options.bevelDepth},`);
|
|
4386
|
-
lines.push(` bevelWidth: ${options.bevelWidth},`);
|
|
4387
|
-
lines.push(` frost: ${options.frost},`);
|
|
4388
|
-
lines.push(` shadow: ${options.shadow},`);
|
|
4389
|
-
lines.push(` specular: ${options.specular},`);
|
|
4390
|
-
lines.push(` reveal: "${options.reveal}",`);
|
|
4391
|
-
lines.push(` tilt: ${options.tilt},`);
|
|
4392
|
-
lines.push(` tiltFactor: ${options.tiltFactor},`);
|
|
4393
|
-
lines.push(` tiltEase: ${options.tiltEase},`);
|
|
4394
|
-
lines.push(` magnify: ${options.magnify},`);
|
|
4395
|
-
lines.push(` helper: false,`);
|
|
4396
|
-
lines.push(`});`);
|
|
4397
|
-
|
|
4398
|
-
return lines.join("\n");
|
|
4399
|
-
}
|
|
4400
|
-
|
|
4401
4857
|
/* --------------------------------------------------
|
|
4402
4858
|
* Public API
|
|
4403
4859
|
* ------------------------------------------------*/
|
|
@@ -4406,6 +4862,7 @@ const liquidGL = (() => {
|
|
|
4406
4862
|
target: ".liquidGL",
|
|
4407
4863
|
snapshot: "body",
|
|
4408
4864
|
resolution: 2.0,
|
|
4865
|
+
engine: "auto",
|
|
4409
4866
|
refraction: 0.01,
|
|
4410
4867
|
aberration: 0,
|
|
4411
4868
|
bevelDepth: 0.08,
|
|
@@ -4423,20 +4880,40 @@ const liquidGL = (() => {
|
|
|
4423
4880
|
};
|
|
4424
4881
|
const options = { ...defaults, ...userOptions };
|
|
4425
4882
|
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
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";
|
|
4433
4894
|
}
|
|
4434
4895
|
|
|
4435
|
-
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;
|
|
4436
4913
|
|
|
4437
|
-
if (
|
|
4914
|
+
if (window.__liquidGLNoWebGL__ === true || noGPU) {
|
|
4438
4915
|
console.warn(
|
|
4439
|
-
"liquidGL: WebGL not available – falling back to CSS backdrop-filter.",
|
|
4916
|
+
"liquidGL: WebGPU/WebGL not available – falling back to CSS backdrop-filter.",
|
|
4440
4917
|
);
|
|
4441
4918
|
const fallbackNodes = document.querySelectorAll(options.target);
|
|
4442
4919
|
fallbackNodes.forEach((node) => {
|
|
@@ -4453,7 +4930,11 @@ const liquidGL = (() => {
|
|
|
4453
4930
|
|
|
4454
4931
|
let renderer = window.__liquidGLRenderer__;
|
|
4455
4932
|
if (!renderer) {
|
|
4456
|
-
renderer = new liquidGLRenderer(
|
|
4933
|
+
renderer = new liquidGLRenderer(
|
|
4934
|
+
options.snapshot,
|
|
4935
|
+
options.resolution,
|
|
4936
|
+
options.engine,
|
|
4937
|
+
);
|
|
4457
4938
|
window.__liquidGLRenderer__ = renderer;
|
|
4458
4939
|
}
|
|
4459
4940
|
|
|
@@ -4478,14 +4959,13 @@ const liquidGL = (() => {
|
|
|
4478
4959
|
}
|
|
4479
4960
|
|
|
4480
4961
|
if (options.helper) {
|
|
4481
|
-
|
|
4482
|
-
.
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
});
|
|
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
|
+
}
|
|
4489
4969
|
}
|
|
4490
4970
|
|
|
4491
4971
|
return instances.length === 1 ? instances[0] : instances;
|
|
@@ -4590,6 +5070,7 @@ const liquidGL = (() => {
|
|
|
4590
5070
|
|
|
4591
5071
|
return { lenis, locomotiveScroll: loco };
|
|
4592
5072
|
};
|
|
5073
|
+
|
|
4593
5074
|
return window.liquidGL;
|
|
4594
5075
|
})();
|
|
4595
5076
|
|