vireglass 2.1.0 → 2.3.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.
Files changed (46) hide show
  1. package/README.md +41 -1
  2. package/dist/{chunk-3PGYF2NK.js → chunk-CTTZNZFV.js} +3 -3
  3. package/dist/{chunk-3PGYF2NK.js.map → chunk-CTTZNZFV.js.map} +1 -1
  4. package/dist/{chunk-FL5UZPY5.js → chunk-H5QTBF67.js} +4 -4
  5. package/dist/{chunk-FL5UZPY5.js.map → chunk-H5QTBF67.js.map} +1 -1
  6. package/dist/{chunk-TTOKBARR.js → chunk-MMPK6DCK.js} +10 -3
  7. package/dist/chunk-MMPK6DCK.js.map +1 -0
  8. package/dist/{chunk-BZC2WHCK.js → chunk-PM2SWCSF.js} +4 -4
  9. package/dist/{chunk-BZC2WHCK.js.map → chunk-PM2SWCSF.js.map} +1 -1
  10. package/dist/{chunk-P2ZOODXD.js → chunk-SLRPHDV7.js} +3 -3
  11. package/dist/{chunk-P2ZOODXD.js.map → chunk-SLRPHDV7.js.map} +1 -1
  12. package/dist/dom.cjs +48 -20
  13. package/dist/dom.cjs.map +1 -1
  14. package/dist/dom.d.cts +15 -3
  15. package/dist/dom.d.ts +15 -3
  16. package/dist/dom.js +44 -23
  17. package/dist/dom.js.map +1 -1
  18. package/dist/index.cjs +1 -2
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +4 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +5 -5
  23. package/dist/law.cjs +8 -1
  24. package/dist/law.cjs.map +1 -1
  25. package/dist/law.d.cts +7 -0
  26. package/dist/law.d.ts +7 -0
  27. package/dist/law.js +1 -1
  28. package/dist/reference.js +2 -2
  29. package/dist/web/index.cjs +262 -153
  30. package/dist/web/index.cjs.map +1 -1
  31. package/dist/web/index.d.cts +88 -2
  32. package/dist/web/index.d.ts +88 -2
  33. package/dist/web/index.js +257 -156
  34. package/dist/web/index.js.map +1 -1
  35. package/docs/law.md +2 -2
  36. package/package.json +13 -9
  37. package/src/dom/body.ts +24 -9
  38. package/src/dom/index.ts +44 -10
  39. package/src/dom/probe.ts +27 -3
  40. package/src/law.ts +7 -0
  41. package/src/targets/glsl.ts +4 -0
  42. package/src/web/gl.ts +57 -4
  43. package/src/web/gpu-timer.ts +111 -0
  44. package/src/web/index.ts +1 -0
  45. package/src/web/renderer.ts +305 -218
  46. package/dist/chunk-TTOKBARR.js.map +0 -1
@@ -41,8 +41,7 @@ var BODY = {
41
41
  darkSideFrom: 0.42,
42
42
  darkSideTo: 0.58,
43
43
  /** Ceiling on any single demand, so one requirement cannot make the glass opaque on its own. */
44
- maxDemand: 0.92
45
- };
44
+ maxDemand: 0.92};
46
45
  var DISPERSION = {
47
46
  /** Red bends less than the base index, blue more — and not symmetrically. */
48
47
  redShift: -0.4,
@@ -1547,6 +1546,7 @@ function createTexture(gl, options) {
1547
1546
  const internalFormat = options.internalFormat ?? gl.RGBA8;
1548
1547
  const format = options.format ?? gl.RGBA;
1549
1548
  const type = options.type ?? gl.UNSIGNED_BYTE;
1549
+ const wrap = options.wrap ?? gl.CLAMP_TO_EDGE;
1550
1550
  gl.bindTexture(gl.TEXTURE_2D, texture);
1551
1551
  gl.texImage2D(
1552
1552
  gl.TEXTURE_2D,
@@ -1561,11 +1561,45 @@ function createTexture(gl, options) {
1561
1561
  );
1562
1562
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1563
1563
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1564
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1565
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1564
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);
1565
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);
1566
1566
  gl.bindTexture(gl.TEXTURE_2D, null);
1567
1567
  return texture;
1568
1568
  }
1569
+ function locationCache(gl, program) {
1570
+ const cache = /* @__PURE__ */ new Map();
1571
+ return (name) => {
1572
+ let loc = cache.get(name);
1573
+ if (loc === void 0) {
1574
+ loc = gl.getUniformLocation(program, name);
1575
+ cache.set(name, loc);
1576
+ }
1577
+ return loc;
1578
+ };
1579
+ }
1580
+ function setUniform(gl, loc, value) {
1581
+ if (!loc) return;
1582
+ if (typeof value === "number") {
1583
+ gl.uniform1f(loc, value);
1584
+ return;
1585
+ }
1586
+ switch (value.length) {
1587
+ case 1:
1588
+ gl.uniform1f(loc, value[0]);
1589
+ break;
1590
+ case 2:
1591
+ gl.uniform2f(loc, value[0], value[1]);
1592
+ break;
1593
+ case 3:
1594
+ gl.uniform3f(loc, value[0], value[1], value[2]);
1595
+ break;
1596
+ case 4:
1597
+ gl.uniform4f(loc, value[0], value[1], value[2], value[3]);
1598
+ break;
1599
+ default:
1600
+ throw new Error(`vireglass/web: unsupported uniform size (${value.length})`);
1601
+ }
1602
+ }
1569
1603
  function createFramebuffer(gl, texture) {
1570
1604
  const fbo = gl.createFramebuffer();
1571
1605
  if (!fbo) throw new Error("vireglass/web: gl.createFramebuffer returned null");
@@ -1585,6 +1619,62 @@ function bindTextureAt(gl, unit, texture, program, uniformName) {
1585
1619
  gl.uniform1i(loc, unit);
1586
1620
  }
1587
1621
 
1622
+ // src/web/gpu-timer.ts
1623
+ var GPU_TIMER_RING_SIZE = 3;
1624
+ function createGpuTimer(gl) {
1625
+ const ext = gl.getExtension("EXT_disjoint_timer_query_webgl2");
1626
+ const slots = ext ? Array.from({ length: GPU_TIMER_RING_SIZE }, () => ({
1627
+ query: gl.createQuery(),
1628
+ pending: false
1629
+ })) : [];
1630
+ let cursor = 0;
1631
+ let active = null;
1632
+ let lastMs = null;
1633
+ function poll() {
1634
+ if (!ext) return;
1635
+ for (const slot of slots) {
1636
+ if (!slot.pending) continue;
1637
+ if (!gl.getQueryParameter(slot.query, gl.QUERY_RESULT_AVAILABLE)) continue;
1638
+ slot.pending = false;
1639
+ if (gl.getParameter(ext.GPU_DISJOINT_EXT)) continue;
1640
+ const ns = gl.getQueryParameter(slot.query, gl.QUERY_RESULT);
1641
+ lastMs = ns / 1e6;
1642
+ }
1643
+ }
1644
+ function begin() {
1645
+ active = null;
1646
+ if (!ext || slots.length === 0) return;
1647
+ poll();
1648
+ const slot = slots[cursor];
1649
+ if (slot.pending) return;
1650
+ gl.beginQuery(ext.TIME_ELAPSED_EXT, slot.query);
1651
+ active = slot;
1652
+ }
1653
+ function end() {
1654
+ if (!ext || !active) return;
1655
+ gl.endQuery(ext.TIME_ELAPSED_EXT);
1656
+ active.pending = true;
1657
+ cursor = (cursor + 1) % slots.length;
1658
+ active = null;
1659
+ }
1660
+ function time(fn) {
1661
+ begin();
1662
+ try {
1663
+ return fn();
1664
+ } finally {
1665
+ end();
1666
+ }
1667
+ }
1668
+ function getLastMs() {
1669
+ poll();
1670
+ return lastMs;
1671
+ }
1672
+ function destroy() {
1673
+ for (const slot of slots) gl.deleteQuery(slot.query);
1674
+ }
1675
+ return { time, getLastMs, destroy };
1676
+ }
1677
+
1588
1678
  // src/web/probe.ts
1589
1679
  var PROBE_GRID_WIDTH = 48;
1590
1680
  var PROBE_GRID_HEIGHT = 96;
@@ -1805,40 +1895,6 @@ void main() {
1805
1895
  fragColor = texture(u_content, uv);
1806
1896
  }
1807
1897
  `;
1808
- function locationCache(gl, program) {
1809
- const cache = /* @__PURE__ */ new Map();
1810
- return (name) => {
1811
- let loc = cache.get(name);
1812
- if (loc === void 0) {
1813
- loc = gl.getUniformLocation(program, name);
1814
- cache.set(name, loc);
1815
- }
1816
- return loc;
1817
- };
1818
- }
1819
- function setUniform(gl, loc, value) {
1820
- if (!loc) return;
1821
- if (typeof value === "number") {
1822
- gl.uniform1f(loc, value);
1823
- return;
1824
- }
1825
- switch (value.length) {
1826
- case 1:
1827
- gl.uniform1f(loc, value[0]);
1828
- break;
1829
- case 2:
1830
- gl.uniform2f(loc, value[0], value[1]);
1831
- break;
1832
- case 3:
1833
- gl.uniform3f(loc, value[0], value[1], value[2]);
1834
- break;
1835
- case 4:
1836
- gl.uniform4f(loc, value[0], value[1], value[2], value[3]);
1837
- break;
1838
- default:
1839
- throw new Error(`vireglass/web: unsupported uniform size (${value.length})`);
1840
- }
1841
- }
1842
1898
  function applyChannel(gl, get, names, sizes, values) {
1843
1899
  let cursor = 0;
1844
1900
  for (let i = 0; i < names.length; i += 1) {
@@ -1901,6 +1957,12 @@ function createVireGlassRenderer(canvas) {
1901
1957
  width: Math.max(width, 1),
1902
1958
  height: Math.max(height, 1)
1903
1959
  });
1960
+ let backdropFbo = null;
1961
+ function ensureBackdropFbo() {
1962
+ if (!backdropFbo) backdropFbo = createFramebuffer(gl, contentTexture);
1963
+ return backdropFbo;
1964
+ }
1965
+ const gpuTimer = createGpuTimer(gl);
1904
1966
  const iconTexture = createTexture(gl, { width: 1, height: 1 });
1905
1967
  const colorTexture = createTexture(gl, { width: 1, height: 1 });
1906
1968
  gl.bindTexture(gl.TEXTURE_2D, iconTexture);
@@ -1929,128 +1991,164 @@ function createVireGlassRenderer(canvas) {
1929
1991
  if (!sceneCtx) throw new Error("vireglass/web: scene 2D context unavailable");
1930
1992
  gl.deleteTexture(contentTexture);
1931
1993
  contentTexture = createTexture(gl, { width, height });
1994
+ if (backdropFbo) {
1995
+ gl.deleteFramebuffer(backdropFbo);
1996
+ backdropFbo = null;
1997
+ }
1932
1998
  }
1933
1999
  function render(options) {
1934
2000
  if (!sceneCtx) throw new Error("vireglass/web: renderer not initialized (resize was never called)");
1935
- sceneCtx.clearRect(0, 0, width, height);
1936
- options.scene(sceneCtx, width, height, options.offsetX ?? 0, options.offsetY ?? 0, options.density);
1937
- gl.bindTexture(gl.TEXTURE_2D, contentTexture);
1938
- gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, sceneCanvas);
1939
- gl.bindTexture(gl.TEXTURE_2D, null);
1940
- if (options.colorLayer) {
1941
- gl.bindTexture(gl.TEXTURE_2D, colorTexture);
1942
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.colorLayer);
2001
+ const ctx = sceneCtx;
2002
+ if (options.backdrop && options.scene) {
2003
+ throw new Error(
2004
+ "vireglass/web: render() needs exactly one of options.scene or options.backdrop, not both"
2005
+ );
1943
2006
  }
1944
- if (options.iconMask) {
1945
- gl.bindTexture(gl.TEXTURE_2D, iconTexture);
1946
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.iconMask);
1947
- gl.bindTexture(gl.TEXTURE_2D, null);
2007
+ if (!options.backdrop && !options.scene) {
2008
+ throw new Error("vireglass/web: render() needs either options.scene or options.backdrop");
1948
2009
  }
1949
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
1950
- gl.viewport(0, 0, width, height);
1951
- gl.disable(gl.BLEND);
1952
- gl.disable(gl.SCISSOR_TEST);
1953
- gl.useProgram(blitProgram);
1954
- bindTextureAt(gl, 0, contentTexture, blitProgram, "u_content");
1955
- setUniform(gl, blitLoc("u_resolution"), [width, height]);
1956
- drawFullscreenTriangle(gl);
1957
- const probes = [];
1958
- for (let index = 0; index < options.pieces.length; index += 1) {
1959
- const piece = options.pieces[index];
1960
- const halfWidth = piece.geometry.width * options.density / 2;
1961
- const halfHeight = piece.geometry.height * options.density / 2;
1962
- const rect = {
1963
- centerX: piece.centerX,
1964
- centerY: piece.centerY,
1965
- halfWidth,
1966
- halfHeight
1967
- };
1968
- const fresh = index === 0 ? probe.sample(contentTexture, width, height, rect) : probe.statsFor(width, height, rect);
1969
- const stats = fresh ? settleStats(index, fresh) : null;
1970
- probes.push(stats);
1971
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
1972
- gl.viewport(0, 0, width, height);
1973
- gl.enable(gl.BLEND);
1974
- gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
1975
- const reachOf = (m) => m ? Math.hypot(m.offsetX, m.offsetY) + Math.max(m.width, m.height) / 2 + m.smoothing : 0;
1976
- const morphReach = Math.max(reachOf(piece.morph), reachOf(piece.morph2));
1977
- const touchReach = piece.touch ? Math.hypot(piece.touch.pullX, piece.touch.pullY) + piece.touch.waveAmp * 2 + Math.max(piece.geometry.width, piece.geometry.height) * 0.05 * piece.touch.press : 0;
1978
- const padPx = (lensPadDp(piece.geometry, piece.optics) + surfacePadDp(piece.geometry, 0) + morphReach + touchReach) * options.density;
1979
- const left = Math.max(0, Math.floor(piece.centerX - halfWidth - padPx));
1980
- const right = Math.min(width, Math.ceil(piece.centerX + halfWidth + padPx));
1981
- const top = Math.max(0, Math.floor(height - (piece.centerY + halfHeight + padPx)));
1982
- const bottom = Math.min(height, Math.ceil(height - (piece.centerY - halfHeight - padPx)));
1983
- gl.enable(gl.SCISSOR_TEST);
1984
- gl.scissor(left, top, Math.max(0, right - left), Math.max(0, bottom - top));
1985
- gl.useProgram(lensProgram);
1986
- bindTextureAt(gl, 0, contentTexture, lensProgram, "content");
1987
- setUniform(gl, lensLoc("u_contentSize"), [width, height]);
1988
- setUniform(gl, lensLoc("u_resolution"), [width, height]);
1989
- setUniform(gl, lensLoc("u_center"), [piece.centerX, piece.centerY]);
1990
- setUniform(gl, lensLoc("u_reach"), Math.max(width, height));
1991
- setUniform(gl, lensLoc("u_contentMin"), [1, 1]);
1992
- setUniform(gl, lensLoc("u_contentMax"), [width - 1, height - 1]);
1993
- if (stats) {
1994
- setUniform(gl, lensLoc("u_probeLuma"), stats.luma);
1995
- setUniform(gl, lensLoc("u_probeBusy"), stats.busy);
1996
- setUniform(gl, lensLoc("u_probeRange"), [stats.lo, stats.hi]);
1997
- setUniform(gl, lensLoc("u_probeSlope"), [stats.slopeX, stats.slopeY]);
1998
- setUniform(gl, lensLoc("u_probe"), [stats.r, stats.g, stats.b]);
2010
+ return gpuTimer.time(() => {
2011
+ if (options.backdrop) {
2012
+ const fbo = ensureBackdropFbo();
2013
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
2014
+ gl.viewport(0, 0, width, height);
2015
+ options.backdrop(gl, { framebuffer: fbo, width, height });
2016
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
2017
+ gl.viewport(0, 0, width, height);
2018
+ gl.disable(gl.BLEND);
2019
+ gl.disable(gl.SCISSOR_TEST);
2020
+ gl.disable(gl.DEPTH_TEST);
2021
+ gl.disable(gl.CULL_FACE);
2022
+ gl.disable(gl.STENCIL_TEST);
2023
+ gl.colorMask(true, true, true, true);
2024
+ gl.depthMask(true);
2025
+ gl.bindVertexArray(null);
2026
+ gl.activeTexture(gl.TEXTURE0);
2027
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
2028
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
2029
+ gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.BROWSER_DEFAULT_WEBGL);
1999
2030
  } else {
2000
- setUniform(gl, lensLoc("u_probeLuma"), -1);
2031
+ ctx.clearRect(0, 0, width, height);
2032
+ options.scene?.(ctx, width, height, options.offsetX ?? 0, options.offsetY ?? 0, options.density);
2033
+ gl.bindTexture(gl.TEXTURE_2D, contentTexture);
2034
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, sceneCanvas);
2035
+ gl.bindTexture(gl.TEXTURE_2D, null);
2001
2036
  }
2002
- const lens = toLensProps(piece.optics, piece.geometry, options.density, {
2003
- debug: options.debug,
2004
- morph: piece.morph,
2005
- morph2: piece.morph2,
2006
- touch: piece.touch,
2007
- progress: piece.progress,
2008
- light: piece.light,
2009
- appear: piece.appear,
2010
- accent: piece.accent
2011
- });
2012
- applyChannel(gl, lensLoc, lens.uniformNames, lens.uniformSizes, lens.uniformValues);
2013
- drawFullscreenTriangle(gl);
2014
- gl.useProgram(surfaceProgram);
2015
- const rawSurface = toSurfaceUniforms(piece.optics, piece.geometry, {
2016
- debug: options.debug,
2017
- morph: piece.morph,
2018
- morph2: piece.morph2,
2019
- bodyInLens: true,
2020
- touch: piece.touch,
2021
- progress: piece.progress,
2022
- // The shadow is denser over text, weaker over a flat backdrop (M 11:47). The rule lives
2023
- // in the core: while it lived here, Android didn't have it at all.
2024
- shadow: stats ? shadowOpacity(stats.busy) : 1,
2025
- // The probe has already computed the ambient color — the shadow gets it for free. Taken
2026
- // as-is: `settleStats` has already smoothed it, and rounding would bring back exactly the
2027
- // steps the smoothing was written to remove.
2028
- ambient: stats ? [stats.r, stats.g, stats.b] : void 0,
2029
- appear: piece.appear,
2030
- lift: piece.lift
2031
- });
2032
- applyObject(gl, surfaceLoc, {
2033
- ...toDeviceSurfaceUniforms(rawSurface, options.density),
2034
- u_center: [piece.centerX, piece.centerY]
2035
- });
2036
- setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[0]), piece.press ?? 0);
2037
- setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[1]), piece.active ?? 0);
2038
- const hasIcon = Boolean(piece.icon && options.iconMask);
2039
- setUniform(gl, surfaceLoc(ICON_UNIFORMS[0]), hasIcon ? 1 : 0);
2040
- setUniform(gl, surfaceLoc(ICON_UNIFORMS[1]), 1);
2041
- setUniform(gl, surfaceLoc(ICON_UNIFORMS[2]), piece.inkIdle ?? [1, 1, 1, 1]);
2042
- setUniform(gl, surfaceLoc(ICON_UNIFORMS[3]), piece.inkActive ?? [1, 1, 1, 1]);
2043
- bindTextureAt(gl, 1, iconTexture, surfaceProgram, "u_icon");
2044
- setUniform(gl, surfaceLoc("u_iconSize"), hasIcon ? [width, height] : [1, 1]);
2045
- const hasColor = Boolean(piece.overlay && options.colorLayer);
2046
- setUniform(gl, surfaceLoc(OVERLAY_UNIFORMS[0]), hasColor ? 1 : 0);
2047
- bindTextureAt(gl, 2, colorTexture, surfaceProgram, "u_overlay");
2048
- setUniform(gl, surfaceLoc("u_overlaySize"), hasColor ? [width, height] : [1, 1]);
2049
- setUniform(gl, surfaceLoc("u_resolution"), [width, height]);
2037
+ if (options.colorLayer) {
2038
+ gl.bindTexture(gl.TEXTURE_2D, colorTexture);
2039
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.colorLayer);
2040
+ }
2041
+ if (options.iconMask) {
2042
+ gl.bindTexture(gl.TEXTURE_2D, iconTexture);
2043
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.iconMask);
2044
+ gl.bindTexture(gl.TEXTURE_2D, null);
2045
+ }
2046
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
2047
+ gl.viewport(0, 0, width, height);
2048
+ gl.disable(gl.BLEND);
2049
+ gl.disable(gl.SCISSOR_TEST);
2050
+ gl.useProgram(blitProgram);
2051
+ bindTextureAt(gl, 0, contentTexture, blitProgram, "u_content");
2052
+ setUniform(gl, blitLoc("u_resolution"), [width, height]);
2050
2053
  drawFullscreenTriangle(gl);
2051
- }
2052
- gl.disable(gl.SCISSOR_TEST);
2053
- return { probes };
2054
+ const probes = [];
2055
+ for (let index = 0; index < options.pieces.length; index += 1) {
2056
+ const piece = options.pieces[index];
2057
+ const halfWidth = piece.geometry.width * options.density / 2;
2058
+ const halfHeight = piece.geometry.height * options.density / 2;
2059
+ const rect = {
2060
+ centerX: piece.centerX,
2061
+ centerY: piece.centerY,
2062
+ halfWidth,
2063
+ halfHeight
2064
+ };
2065
+ const fresh = index === 0 ? probe.sample(contentTexture, width, height, rect) : probe.statsFor(width, height, rect);
2066
+ const stats = fresh ? settleStats(index, fresh) : null;
2067
+ probes.push(stats);
2068
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
2069
+ gl.viewport(0, 0, width, height);
2070
+ gl.enable(gl.BLEND);
2071
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
2072
+ const reachOf = (m) => m ? Math.hypot(m.offsetX, m.offsetY) + Math.max(m.width, m.height) / 2 + m.smoothing : 0;
2073
+ const morphReach = Math.max(reachOf(piece.morph), reachOf(piece.morph2));
2074
+ const touchReach = piece.touch ? Math.hypot(piece.touch.pullX, piece.touch.pullY) + piece.touch.waveAmp * 2 + Math.max(piece.geometry.width, piece.geometry.height) * 0.05 * piece.touch.press : 0;
2075
+ const padPx = (lensPadDp(piece.geometry, piece.optics) + surfacePadDp(piece.geometry, 0) + morphReach + touchReach) * options.density;
2076
+ const left = Math.max(0, Math.floor(piece.centerX - halfWidth - padPx));
2077
+ const right = Math.min(width, Math.ceil(piece.centerX + halfWidth + padPx));
2078
+ const top = Math.max(0, Math.floor(height - (piece.centerY + halfHeight + padPx)));
2079
+ const bottom = Math.min(height, Math.ceil(height - (piece.centerY - halfHeight - padPx)));
2080
+ gl.enable(gl.SCISSOR_TEST);
2081
+ gl.scissor(left, top, Math.max(0, right - left), Math.max(0, bottom - top));
2082
+ gl.useProgram(lensProgram);
2083
+ bindTextureAt(gl, 0, contentTexture, lensProgram, "content");
2084
+ setUniform(gl, lensLoc("u_contentSize"), [width, height]);
2085
+ setUniform(gl, lensLoc("u_resolution"), [width, height]);
2086
+ setUniform(gl, lensLoc("u_center"), [piece.centerX, piece.centerY]);
2087
+ setUniform(gl, lensLoc("u_reach"), Math.max(width, height));
2088
+ setUniform(gl, lensLoc("u_contentMin"), [1, 1]);
2089
+ setUniform(gl, lensLoc("u_contentMax"), [width - 1, height - 1]);
2090
+ if (stats) {
2091
+ setUniform(gl, lensLoc("u_probeLuma"), stats.luma);
2092
+ setUniform(gl, lensLoc("u_probeBusy"), stats.busy);
2093
+ setUniform(gl, lensLoc("u_probeRange"), [stats.lo, stats.hi]);
2094
+ setUniform(gl, lensLoc("u_probeSlope"), [stats.slopeX, stats.slopeY]);
2095
+ setUniform(gl, lensLoc("u_probe"), [stats.r, stats.g, stats.b]);
2096
+ } else {
2097
+ setUniform(gl, lensLoc("u_probeLuma"), -1);
2098
+ }
2099
+ const lens = toLensProps(piece.optics, piece.geometry, options.density, {
2100
+ debug: options.debug,
2101
+ morph: piece.morph,
2102
+ morph2: piece.morph2,
2103
+ touch: piece.touch,
2104
+ progress: piece.progress,
2105
+ light: piece.light,
2106
+ appear: piece.appear,
2107
+ accent: piece.accent
2108
+ });
2109
+ applyChannel(gl, lensLoc, lens.uniformNames, lens.uniformSizes, lens.uniformValues);
2110
+ drawFullscreenTriangle(gl);
2111
+ gl.useProgram(surfaceProgram);
2112
+ const rawSurface = toSurfaceUniforms(piece.optics, piece.geometry, {
2113
+ debug: options.debug,
2114
+ morph: piece.morph,
2115
+ morph2: piece.morph2,
2116
+ bodyInLens: true,
2117
+ touch: piece.touch,
2118
+ progress: piece.progress,
2119
+ // The shadow is denser over text, weaker over a flat backdrop (M 11:47). The rule lives
2120
+ // in the core: while it lived here, Android didn't have it at all.
2121
+ shadow: stats ? shadowOpacity(stats.busy) : 1,
2122
+ // The probe has already computed the ambient color — the shadow gets it for free. Taken
2123
+ // as-is: `settleStats` has already smoothed it, and rounding would bring back exactly the
2124
+ // steps the smoothing was written to remove.
2125
+ ambient: stats ? [stats.r, stats.g, stats.b] : void 0,
2126
+ appear: piece.appear,
2127
+ lift: piece.lift
2128
+ });
2129
+ applyObject(gl, surfaceLoc, {
2130
+ ...toDeviceSurfaceUniforms(rawSurface, options.density),
2131
+ u_center: [piece.centerX, piece.centerY]
2132
+ });
2133
+ setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[0]), piece.press ?? 0);
2134
+ setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[1]), piece.active ?? 0);
2135
+ const hasIcon = Boolean(piece.icon && options.iconMask);
2136
+ setUniform(gl, surfaceLoc(ICON_UNIFORMS[0]), hasIcon ? 1 : 0);
2137
+ setUniform(gl, surfaceLoc(ICON_UNIFORMS[1]), 1);
2138
+ setUniform(gl, surfaceLoc(ICON_UNIFORMS[2]), piece.inkIdle ?? [1, 1, 1, 1]);
2139
+ setUniform(gl, surfaceLoc(ICON_UNIFORMS[3]), piece.inkActive ?? [1, 1, 1, 1]);
2140
+ bindTextureAt(gl, 1, iconTexture, surfaceProgram, "u_icon");
2141
+ setUniform(gl, surfaceLoc("u_iconSize"), hasIcon ? [width, height] : [1, 1]);
2142
+ const hasColor = Boolean(piece.overlay && options.colorLayer);
2143
+ setUniform(gl, surfaceLoc(OVERLAY_UNIFORMS[0]), hasColor ? 1 : 0);
2144
+ bindTextureAt(gl, 2, colorTexture, surfaceProgram, "u_overlay");
2145
+ setUniform(gl, surfaceLoc("u_overlaySize"), hasColor ? [width, height] : [1, 1]);
2146
+ setUniform(gl, surfaceLoc("u_resolution"), [width, height]);
2147
+ drawFullscreenTriangle(gl);
2148
+ }
2149
+ gl.disable(gl.SCISSOR_TEST);
2150
+ return { probes };
2151
+ });
2054
2152
  }
2055
2153
  function destroy() {
2056
2154
  probe.destroy();
@@ -2060,13 +2158,24 @@ function createVireGlassRenderer(canvas) {
2060
2158
  gl.deleteTexture(contentTexture);
2061
2159
  gl.deleteTexture(iconTexture);
2062
2160
  gl.deleteTexture(colorTexture);
2161
+ if (backdropFbo) gl.deleteFramebuffer(backdropFbo);
2162
+ gpuTimer.destroy();
2063
2163
  }
2064
- return { resize, render, destroy };
2164
+ return { resize, render, getLastGpuMs: gpuTimer.getLastMs, destroy };
2065
2165
  }
2066
2166
 
2167
+ exports.FULLSCREEN_TRIANGLE_VERTEX_SOURCE = FULLSCREEN_TRIANGLE_VERTEX_SOURCE;
2067
2168
  exports.PROBE_GRID_HEIGHT = PROBE_GRID_HEIGHT;
2068
2169
  exports.PROBE_GRID_WIDTH = PROBE_GRID_WIDTH;
2170
+ exports.bindTextureAt = bindTextureAt;
2171
+ exports.compileShader = compileShader;
2172
+ exports.createFramebuffer = createFramebuffer;
2069
2173
  exports.createProbe = createProbe;
2174
+ exports.createProgram = createProgram;
2175
+ exports.createTexture = createTexture;
2070
2176
  exports.createVireGlassRenderer = createVireGlassRenderer;
2177
+ exports.drawFullscreenTriangle = drawFullscreenTriangle;
2178
+ exports.locationCache = locationCache;
2179
+ exports.setUniform = setUniform;
2071
2180
  //# sourceMappingURL=index.cjs.map
2072
2181
  //# sourceMappingURL=index.cjs.map