vireglass 2.2.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.
package/dist/index.d.cts CHANGED
@@ -288,6 +288,10 @@ declare function convertReturns(mainBody: string): string;
288
288
  * origin, y up. The flip via `u_resolution.y - gl_FragCoord.y` restores the original convention
289
289
  * (both coordinate systems center the pixel at `+0.5`, so no X shift or constant is needed).
290
290
  * `out vec4 fragColor` and `uniform vec2 u_resolution` are declared in the prologue (`addPrologue`).
291
+ *
292
+ * `xy` and `.eval()` are flipped together — but only inside a shader THIS function translates. A
293
+ * `VireGlassBackdropPass` (`web/renderer.ts`) that reads its own previous frame from an FBO is raw
294
+ * `gl_FragCoord`, unflipped; reusing AGSL-style helpers there needs an explicit flip of its own.
291
295
  */
292
296
  declare function convertEntryPoint(src: string): string;
293
297
  /** `#version 300 es` + `precision highp float;` — no equivalent in AGSL, WebGL2 requires them as
package/dist/index.d.ts CHANGED
@@ -288,6 +288,10 @@ declare function convertReturns(mainBody: string): string;
288
288
  * origin, y up. The flip via `u_resolution.y - gl_FragCoord.y` restores the original convention
289
289
  * (both coordinate systems center the pixel at `+0.5`, so no X shift or constant is needed).
290
290
  * `out vec4 fragColor` and `uniform vec2 u_resolution` are declared in the prologue (`addPrologue`).
291
+ *
292
+ * `xy` and `.eval()` are flipped together — but only inside a shader THIS function translates. A
293
+ * `VireGlassBackdropPass` (`web/renderer.ts`) that reads its own previous frame from an FBO is raw
294
+ * `gl_FragCoord`, unflipped; reusing AGSL-style helpers there needs an explicit flip of its own.
291
295
  */
292
296
  declare function convertEntryPoint(src: string): string;
293
297
  /** `#version 300 es` + `precision highp float;` — no equivalent in AGSL, WebGL2 requires them as
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { SURFACE_SHADER, addPrologue, convertEntryPoint, convertEvalCalls, convertHalfTypes, convertReturns, convertShaderUniforms, convertVecTypes, toGLSL } from './chunk-2XJJO7SC.js';
1
+ export { SURFACE_SHADER, addPrologue, convertEntryPoint, convertEvalCalls, convertHalfTypes, convertReturns, convertShaderUniforms, convertVecTypes, toGLSL } from './chunk-PM2SWCSF.js';
2
2
  export { GLASS_SCALE_DEFAULT, GRADIENT, NO_ACCESSIBILITY, SCROLL_EDGE_ENGAGE_DP, SETTLE, SMOOTH, accentAmount, accentTone, aggregate, applyAccessibility, applyAppear, applyGlassScale, concentricFit, concentricInset, concentricRadius, contrastRimLuma, createDeform, createGroupState, elasticAllowed, probeValuesAt, raiseIntoGlass, resolveShape, scrollEdgeReachDp, scrollEdgeStrength, scrollEdgeStyle, shadowTint, smoothPlane } from './chunk-CTTZNZFV.js';
3
3
  export { CONFIRMATIONS, FADE_MS, FLIP_LUMA, INK_DARK, INK_LIGHT, RETURN_LUMA, ambientFrom, decisiveLuma, shouldInkBeLight } from './chunk-JNRZNMJJ.js';
4
4
  export { ACCENT_AMOUNT, ADAPT_RADIUS, ALL_EFFECTS_ON, DEBUG_MODES, DYNAMIC_UNIFORMS, EFFECTS, FRESNEL_EXPONENT, ICON_UNIFORMS, LEGACY_NAMES, LEGACY_OPTICS, LENS_SHADER, MATERIAL_PRESETS, MATERIAL_RANGES, OVERLAY_UNIFORMS, PRESET_NAMES, PRODUCT_DIM, REST_LIGHT, SURFACE_LENGTH_UNIFORMS, VG_FALLOFF, VG_SDF, VIREGLASS_CLEAR_MATERIAL, VIREGLASS_CONTROL_MATERIAL, VIREGLASS_LYRICS_MATERIAL, VIREGLASS_MATERIAL, VIREGLASS_MATERIAL_V4, VIREGLASS_MATERIAL_V5, VIREGLASS_SHEET_MATERIAL, absorption, activeMaterial, applyToggles, blur, bodyDensity, colorPickup, debugIndex, diffraction, dispersion, edgeDensity, edgeLight, fresnelF0, fresnelStrength, gatherRadius, iorSpread, iridescence, lensMagnify, materialForInk, mediumTint, morphBetween, morphOutOf, neckToBridge, refractionScale, refractionStrength, resolveMaterial, resolveOptics, sceneDistance, sceneGradient, sdfRoundedRect, sdfRoundedRectGradient, smin, specularPower, specularStrength, toDeviceSurfaceUniforms, toLensProps, toSurfaceUniforms, touchWarp } from './chunk-H5QTBF67.js';
@@ -1546,6 +1546,7 @@ function createTexture(gl, options) {
1546
1546
  const internalFormat = options.internalFormat ?? gl.RGBA8;
1547
1547
  const format = options.format ?? gl.RGBA;
1548
1548
  const type = options.type ?? gl.UNSIGNED_BYTE;
1549
+ const wrap = options.wrap ?? gl.CLAMP_TO_EDGE;
1549
1550
  gl.bindTexture(gl.TEXTURE_2D, texture);
1550
1551
  gl.texImage2D(
1551
1552
  gl.TEXTURE_2D,
@@ -1560,11 +1561,45 @@ function createTexture(gl, options) {
1560
1561
  );
1561
1562
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1562
1563
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1563
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1564
- 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);
1565
1566
  gl.bindTexture(gl.TEXTURE_2D, null);
1566
1567
  return texture;
1567
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
+ }
1568
1603
  function createFramebuffer(gl, texture) {
1569
1604
  const fbo = gl.createFramebuffer();
1570
1605
  if (!fbo) throw new Error("vireglass/web: gl.createFramebuffer returned null");
@@ -1584,6 +1619,62 @@ function bindTextureAt(gl, unit, texture, program, uniformName) {
1584
1619
  gl.uniform1i(loc, unit);
1585
1620
  }
1586
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
+
1587
1678
  // src/web/probe.ts
1588
1679
  var PROBE_GRID_WIDTH = 48;
1589
1680
  var PROBE_GRID_HEIGHT = 96;
@@ -1804,40 +1895,6 @@ void main() {
1804
1895
  fragColor = texture(u_content, uv);
1805
1896
  }
1806
1897
  `;
1807
- function locationCache(gl, program) {
1808
- const cache = /* @__PURE__ */ new Map();
1809
- return (name) => {
1810
- let loc = cache.get(name);
1811
- if (loc === void 0) {
1812
- loc = gl.getUniformLocation(program, name);
1813
- cache.set(name, loc);
1814
- }
1815
- return loc;
1816
- };
1817
- }
1818
- function setUniform(gl, loc, value) {
1819
- if (!loc) return;
1820
- if (typeof value === "number") {
1821
- gl.uniform1f(loc, value);
1822
- return;
1823
- }
1824
- switch (value.length) {
1825
- case 1:
1826
- gl.uniform1f(loc, value[0]);
1827
- break;
1828
- case 2:
1829
- gl.uniform2f(loc, value[0], value[1]);
1830
- break;
1831
- case 3:
1832
- gl.uniform3f(loc, value[0], value[1], value[2]);
1833
- break;
1834
- case 4:
1835
- gl.uniform4f(loc, value[0], value[1], value[2], value[3]);
1836
- break;
1837
- default:
1838
- throw new Error(`vireglass/web: unsupported uniform size (${value.length})`);
1839
- }
1840
- }
1841
1898
  function applyChannel(gl, get, names, sizes, values) {
1842
1899
  let cursor = 0;
1843
1900
  for (let i = 0; i < names.length; i += 1) {
@@ -1900,6 +1957,12 @@ function createVireGlassRenderer(canvas) {
1900
1957
  width: Math.max(width, 1),
1901
1958
  height: Math.max(height, 1)
1902
1959
  });
1960
+ let backdropFbo = null;
1961
+ function ensureBackdropFbo() {
1962
+ if (!backdropFbo) backdropFbo = createFramebuffer(gl, contentTexture);
1963
+ return backdropFbo;
1964
+ }
1965
+ const gpuTimer = createGpuTimer(gl);
1903
1966
  const iconTexture = createTexture(gl, { width: 1, height: 1 });
1904
1967
  const colorTexture = createTexture(gl, { width: 1, height: 1 });
1905
1968
  gl.bindTexture(gl.TEXTURE_2D, iconTexture);
@@ -1928,128 +1991,164 @@ function createVireGlassRenderer(canvas) {
1928
1991
  if (!sceneCtx) throw new Error("vireglass/web: scene 2D context unavailable");
1929
1992
  gl.deleteTexture(contentTexture);
1930
1993
  contentTexture = createTexture(gl, { width, height });
1994
+ if (backdropFbo) {
1995
+ gl.deleteFramebuffer(backdropFbo);
1996
+ backdropFbo = null;
1997
+ }
1931
1998
  }
1932
1999
  function render(options) {
1933
2000
  if (!sceneCtx) throw new Error("vireglass/web: renderer not initialized (resize was never called)");
1934
- sceneCtx.clearRect(0, 0, width, height);
1935
- options.scene(sceneCtx, width, height, options.offsetX ?? 0, options.offsetY ?? 0, options.density);
1936
- gl.bindTexture(gl.TEXTURE_2D, contentTexture);
1937
- gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, sceneCanvas);
1938
- gl.bindTexture(gl.TEXTURE_2D, null);
1939
- if (options.colorLayer) {
1940
- gl.bindTexture(gl.TEXTURE_2D, colorTexture);
1941
- 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
+ );
1942
2006
  }
1943
- if (options.iconMask) {
1944
- gl.bindTexture(gl.TEXTURE_2D, iconTexture);
1945
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.iconMask);
1946
- 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");
1947
2009
  }
1948
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
1949
- gl.viewport(0, 0, width, height);
1950
- gl.disable(gl.BLEND);
1951
- gl.disable(gl.SCISSOR_TEST);
1952
- gl.useProgram(blitProgram);
1953
- bindTextureAt(gl, 0, contentTexture, blitProgram, "u_content");
1954
- setUniform(gl, blitLoc("u_resolution"), [width, height]);
1955
- drawFullscreenTriangle(gl);
1956
- const probes = [];
1957
- for (let index = 0; index < options.pieces.length; index += 1) {
1958
- const piece = options.pieces[index];
1959
- const halfWidth = piece.geometry.width * options.density / 2;
1960
- const halfHeight = piece.geometry.height * options.density / 2;
1961
- const rect = {
1962
- centerX: piece.centerX,
1963
- centerY: piece.centerY,
1964
- halfWidth,
1965
- halfHeight
1966
- };
1967
- const fresh = index === 0 ? probe.sample(contentTexture, width, height, rect) : probe.statsFor(width, height, rect);
1968
- const stats = fresh ? settleStats(index, fresh) : null;
1969
- probes.push(stats);
1970
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
1971
- gl.viewport(0, 0, width, height);
1972
- gl.enable(gl.BLEND);
1973
- gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
1974
- const reachOf = (m) => m ? Math.hypot(m.offsetX, m.offsetY) + Math.max(m.width, m.height) / 2 + m.smoothing : 0;
1975
- const morphReach = Math.max(reachOf(piece.morph), reachOf(piece.morph2));
1976
- 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;
1977
- const padPx = (lensPadDp(piece.geometry, piece.optics) + surfacePadDp(piece.geometry, 0) + morphReach + touchReach) * options.density;
1978
- const left = Math.max(0, Math.floor(piece.centerX - halfWidth - padPx));
1979
- const right = Math.min(width, Math.ceil(piece.centerX + halfWidth + padPx));
1980
- const top = Math.max(0, Math.floor(height - (piece.centerY + halfHeight + padPx)));
1981
- const bottom = Math.min(height, Math.ceil(height - (piece.centerY - halfHeight - padPx)));
1982
- gl.enable(gl.SCISSOR_TEST);
1983
- gl.scissor(left, top, Math.max(0, right - left), Math.max(0, bottom - top));
1984
- gl.useProgram(lensProgram);
1985
- bindTextureAt(gl, 0, contentTexture, lensProgram, "content");
1986
- setUniform(gl, lensLoc("u_contentSize"), [width, height]);
1987
- setUniform(gl, lensLoc("u_resolution"), [width, height]);
1988
- setUniform(gl, lensLoc("u_center"), [piece.centerX, piece.centerY]);
1989
- setUniform(gl, lensLoc("u_reach"), Math.max(width, height));
1990
- setUniform(gl, lensLoc("u_contentMin"), [1, 1]);
1991
- setUniform(gl, lensLoc("u_contentMax"), [width - 1, height - 1]);
1992
- if (stats) {
1993
- setUniform(gl, lensLoc("u_probeLuma"), stats.luma);
1994
- setUniform(gl, lensLoc("u_probeBusy"), stats.busy);
1995
- setUniform(gl, lensLoc("u_probeRange"), [stats.lo, stats.hi]);
1996
- setUniform(gl, lensLoc("u_probeSlope"), [stats.slopeX, stats.slopeY]);
1997
- 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);
1998
2030
  } else {
1999
- 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);
2000
2036
  }
2001
- const lens = toLensProps(piece.optics, piece.geometry, options.density, {
2002
- debug: options.debug,
2003
- morph: piece.morph,
2004
- morph2: piece.morph2,
2005
- touch: piece.touch,
2006
- progress: piece.progress,
2007
- light: piece.light,
2008
- appear: piece.appear,
2009
- accent: piece.accent
2010
- });
2011
- applyChannel(gl, lensLoc, lens.uniformNames, lens.uniformSizes, lens.uniformValues);
2012
- drawFullscreenTriangle(gl);
2013
- gl.useProgram(surfaceProgram);
2014
- const rawSurface = toSurfaceUniforms(piece.optics, piece.geometry, {
2015
- debug: options.debug,
2016
- morph: piece.morph,
2017
- morph2: piece.morph2,
2018
- bodyInLens: true,
2019
- touch: piece.touch,
2020
- progress: piece.progress,
2021
- // The shadow is denser over text, weaker over a flat backdrop (M 11:47). The rule lives
2022
- // in the core: while it lived here, Android didn't have it at all.
2023
- shadow: stats ? shadowOpacity(stats.busy) : 1,
2024
- // The probe has already computed the ambient color — the shadow gets it for free. Taken
2025
- // as-is: `settleStats` has already smoothed it, and rounding would bring back exactly the
2026
- // steps the smoothing was written to remove.
2027
- ambient: stats ? [stats.r, stats.g, stats.b] : void 0,
2028
- appear: piece.appear,
2029
- lift: piece.lift
2030
- });
2031
- applyObject(gl, surfaceLoc, {
2032
- ...toDeviceSurfaceUniforms(rawSurface, options.density),
2033
- u_center: [piece.centerX, piece.centerY]
2034
- });
2035
- setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[0]), piece.press ?? 0);
2036
- setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[1]), piece.active ?? 0);
2037
- const hasIcon = Boolean(piece.icon && options.iconMask);
2038
- setUniform(gl, surfaceLoc(ICON_UNIFORMS[0]), hasIcon ? 1 : 0);
2039
- setUniform(gl, surfaceLoc(ICON_UNIFORMS[1]), 1);
2040
- setUniform(gl, surfaceLoc(ICON_UNIFORMS[2]), piece.inkIdle ?? [1, 1, 1, 1]);
2041
- setUniform(gl, surfaceLoc(ICON_UNIFORMS[3]), piece.inkActive ?? [1, 1, 1, 1]);
2042
- bindTextureAt(gl, 1, iconTexture, surfaceProgram, "u_icon");
2043
- setUniform(gl, surfaceLoc("u_iconSize"), hasIcon ? [width, height] : [1, 1]);
2044
- const hasColor = Boolean(piece.overlay && options.colorLayer);
2045
- setUniform(gl, surfaceLoc(OVERLAY_UNIFORMS[0]), hasColor ? 1 : 0);
2046
- bindTextureAt(gl, 2, colorTexture, surfaceProgram, "u_overlay");
2047
- setUniform(gl, surfaceLoc("u_overlaySize"), hasColor ? [width, height] : [1, 1]);
2048
- 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]);
2049
2053
  drawFullscreenTriangle(gl);
2050
- }
2051
- gl.disable(gl.SCISSOR_TEST);
2052
- 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
+ });
2053
2152
  }
2054
2153
  function destroy() {
2055
2154
  probe.destroy();
@@ -2059,13 +2158,24 @@ function createVireGlassRenderer(canvas) {
2059
2158
  gl.deleteTexture(contentTexture);
2060
2159
  gl.deleteTexture(iconTexture);
2061
2160
  gl.deleteTexture(colorTexture);
2161
+ if (backdropFbo) gl.deleteFramebuffer(backdropFbo);
2162
+ gpuTimer.destroy();
2062
2163
  }
2063
- return { resize, render, destroy };
2164
+ return { resize, render, getLastGpuMs: gpuTimer.getLastMs, destroy };
2064
2165
  }
2065
2166
 
2167
+ exports.FULLSCREEN_TRIANGLE_VERTEX_SOURCE = FULLSCREEN_TRIANGLE_VERTEX_SOURCE;
2066
2168
  exports.PROBE_GRID_HEIGHT = PROBE_GRID_HEIGHT;
2067
2169
  exports.PROBE_GRID_WIDTH = PROBE_GRID_WIDTH;
2170
+ exports.bindTextureAt = bindTextureAt;
2171
+ exports.compileShader = compileShader;
2172
+ exports.createFramebuffer = createFramebuffer;
2068
2173
  exports.createProbe = createProbe;
2174
+ exports.createProgram = createProgram;
2175
+ exports.createTexture = createTexture;
2069
2176
  exports.createVireGlassRenderer = createVireGlassRenderer;
2177
+ exports.drawFullscreenTriangle = drawFullscreenTriangle;
2178
+ exports.locationCache = locationCache;
2179
+ exports.setUniform = setUniform;
2070
2180
  //# sourceMappingURL=index.cjs.map
2071
2181
  //# sourceMappingURL=index.cjs.map