@deck.gl/extensions 9.4.0-alpha.2 → 9.4.0-beta.3

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 (43) hide show
  1. package/dist/clip/clip-extension.d.ts +3 -0
  2. package/dist/clip/clip-extension.d.ts.map +1 -1
  3. package/dist/clip/clip-extension.js +7 -0
  4. package/dist/clip/clip-extension.js.map +1 -1
  5. package/dist/dist.dev.js +2195 -142
  6. package/dist/fill-style/fill-style-extension.d.ts +38 -6
  7. package/dist/fill-style/fill-style-extension.d.ts.map +1 -1
  8. package/dist/fill-style/fill-style-extension.js +88 -10
  9. package/dist/fill-style/fill-style-extension.js.map +1 -1
  10. package/dist/fill-style/procedural-pattern.d.ts +65 -0
  11. package/dist/fill-style/procedural-pattern.d.ts.map +1 -0
  12. package/dist/fill-style/procedural-pattern.js +135 -0
  13. package/dist/fill-style/procedural-pattern.js.map +1 -0
  14. package/dist/fill-style/shader-module.d.ts +13 -4
  15. package/dist/fill-style/shader-module.d.ts.map +1 -1
  16. package/dist/fill-style/shader-module.js +212 -21
  17. package/dist/fill-style/shader-module.js.map +1 -1
  18. package/dist/index.cjs +3484 -215
  19. package/dist/index.cjs.map +3 -4
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/mask/shader-module.d.ts +1 -0
  23. package/dist/mask/shader-module.d.ts.map +1 -1
  24. package/dist/path-style/path-style-extension.d.ts +49 -6
  25. package/dist/path-style/path-style-extension.d.ts.map +1 -1
  26. package/dist/path-style/path-style-extension.js +237 -37
  27. package/dist/path-style/path-style-extension.js.map +1 -1
  28. package/dist/path-style/shaders.glsl.d.ts +30 -1
  29. package/dist/path-style/shaders.glsl.d.ts.map +1 -1
  30. package/dist/path-style/shaders.glsl.js +136 -21
  31. package/dist/path-style/shaders.glsl.js.map +1 -1
  32. package/dist/terrain/shader-module.d.ts +17 -2
  33. package/dist/terrain/shader-module.d.ts.map +1 -1
  34. package/dist/utils/debug.d.ts.map +1 -1
  35. package/dist.min.js +1251 -96
  36. package/package.json +6 -6
  37. package/src/clip/clip-extension.ts +15 -1
  38. package/src/fill-style/fill-style-extension.ts +135 -13
  39. package/src/fill-style/procedural-pattern.ts +203 -0
  40. package/src/fill-style/shader-module.ts +233 -23
  41. package/src/index.ts +10 -1
  42. package/src/path-style/path-style-extension.ts +350 -44
  43. package/src/path-style/shaders.glsl.ts +268 -27
package/dist/dist.dev.js CHANGED
@@ -13,7 +13,11 @@ var __exports__ = (() => {
13
13
  var __getProtoOf = Object.getPrototypeOf;
14
14
  var __hasOwnProp = Object.prototype.hasOwnProperty;
15
15
  var __commonJS = (cb, mod) => function __require() {
16
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
+ try {
17
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
18
+ } catch (e) {
19
+ throw mod = 0, e;
20
+ }
17
21
  };
18
22
  var __export = (target, all) => {
19
23
  for (var name in all)
@@ -918,8 +922,7 @@ void main() {
918
922
  */
919
923
  _updateCategoryBitMask(params, extension) {
920
924
  const { categorySize } = extension.opts;
921
- if (!categorySize)
922
- return;
925
+ if (!categorySize) return;
923
926
  const { filterCategories } = this.props;
924
927
  const categoryBitMask = new Uint32Array([0, 0, 0, 0]);
925
928
  const categoryFilters = categorySize === 1 ? [filterCategories] : filterCategories;
@@ -1173,10 +1176,10 @@ void main() {
1173
1176
  out[2] = a[2] * b;
1174
1177
  return out;
1175
1178
  }
1176
- function scaleAndAdd(out, a, b, scale2) {
1177
- out[0] = a[0] + b[0] * scale2;
1178
- out[1] = a[1] + b[1] * scale2;
1179
- out[2] = a[2] + b[2] * scale2;
1179
+ function scaleAndAdd(out, a, b, scale3) {
1180
+ out[0] = a[0] + b[0] * scale3;
1181
+ out[1] = a[1] + b[1] * scale3;
1182
+ out[2] = a[2] + b[2] * scale3;
1180
1183
  return out;
1181
1184
  }
1182
1185
  function distance(a, b) {
@@ -1213,13 +1216,13 @@ void main() {
1213
1216
  const x = a[0];
1214
1217
  const y = a[1];
1215
1218
  const z = a[2];
1216
- let len2 = x * x + y * y + z * z;
1217
- if (len2 > 0) {
1218
- len2 = 1 / Math.sqrt(len2);
1219
+ let len3 = x * x + y * y + z * z;
1220
+ if (len3 > 0) {
1221
+ len3 = 1 / Math.sqrt(len3);
1219
1222
  }
1220
- out[0] = a[0] * len2;
1221
- out[1] = a[1] * len2;
1222
- out[2] = a[2] * len2;
1223
+ out[0] = a[0] * len3;
1224
+ out[1] = a[1] * len3;
1225
+ out[2] = a[2] * len3;
1223
1226
  return out;
1224
1227
  }
1225
1228
  function dot(a, b) {
@@ -1280,14 +1283,14 @@ void main() {
1280
1283
  out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;
1281
1284
  return out;
1282
1285
  }
1283
- function random(out, scale2) {
1284
- scale2 = scale2 === void 0 ? 1 : scale2;
1286
+ function random(out, scale3) {
1287
+ scale3 = scale3 === void 0 ? 1 : scale3;
1285
1288
  const r = RANDOM() * 2 * Math.PI;
1286
1289
  const z = RANDOM() * 2 - 1;
1287
- const zScale = Math.sqrt(1 - z * z) * scale2;
1290
+ const zScale = Math.sqrt(1 - z * z) * scale3;
1288
1291
  out[0] = Math.cos(r) * zScale;
1289
1292
  out[1] = Math.sin(r) * zScale;
1290
- out[2] = z * scale2;
1293
+ out[2] = z * scale3;
1291
1294
  return out;
1292
1295
  }
1293
1296
  function transformMat4(out, a, m) {
@@ -1417,7 +1420,7 @@ void main() {
1417
1420
  var sqrDist = squaredDistance;
1418
1421
  var len = length;
1419
1422
  var sqrLen = squaredLength;
1420
- var forEach = function() {
1423
+ var forEach = (function() {
1421
1424
  const vec = create();
1422
1425
  return function(a, stride, offset, count, fn, arg) {
1423
1426
  let i;
@@ -1444,7 +1447,373 @@ void main() {
1444
1447
  }
1445
1448
  return a;
1446
1449
  };
1447
- }();
1450
+ })();
1451
+
1452
+ // ../../node_modules/@math.gl/core/dist/gl-matrix/vec4.js
1453
+ var vec4_exports = {};
1454
+ __export(vec4_exports, {
1455
+ add: () => add2,
1456
+ ceil: () => ceil2,
1457
+ clone: () => clone2,
1458
+ copy: () => copy2,
1459
+ create: () => create2,
1460
+ cross: () => cross2,
1461
+ dist: () => dist2,
1462
+ distance: () => distance2,
1463
+ div: () => div2,
1464
+ divide: () => divide2,
1465
+ dot: () => dot2,
1466
+ equals: () => equals3,
1467
+ exactEquals: () => exactEquals2,
1468
+ floor: () => floor2,
1469
+ forEach: () => forEach2,
1470
+ fromValues: () => fromValues2,
1471
+ inverse: () => inverse2,
1472
+ len: () => len2,
1473
+ length: () => length2,
1474
+ lerp: () => lerp2,
1475
+ max: () => max2,
1476
+ min: () => min2,
1477
+ mul: () => mul2,
1478
+ multiply: () => multiply2,
1479
+ negate: () => negate2,
1480
+ normalize: () => normalize2,
1481
+ random: () => random2,
1482
+ round: () => round3,
1483
+ scale: () => scale2,
1484
+ scaleAndAdd: () => scaleAndAdd2,
1485
+ set: () => set2,
1486
+ sqrDist: () => sqrDist2,
1487
+ sqrLen: () => sqrLen2,
1488
+ squaredDistance: () => squaredDistance2,
1489
+ squaredLength: () => squaredLength2,
1490
+ str: () => str2,
1491
+ sub: () => sub2,
1492
+ subtract: () => subtract2,
1493
+ transformMat4: () => transformMat42,
1494
+ transformQuat: () => transformQuat2,
1495
+ zero: () => zero2
1496
+ });
1497
+ function create2() {
1498
+ const out = new ARRAY_TYPE(4);
1499
+ if (ARRAY_TYPE != Float32Array) {
1500
+ out[0] = 0;
1501
+ out[1] = 0;
1502
+ out[2] = 0;
1503
+ out[3] = 0;
1504
+ }
1505
+ return out;
1506
+ }
1507
+ function clone2(a) {
1508
+ const out = new ARRAY_TYPE(4);
1509
+ out[0] = a[0];
1510
+ out[1] = a[1];
1511
+ out[2] = a[2];
1512
+ out[3] = a[3];
1513
+ return out;
1514
+ }
1515
+ function fromValues2(x, y, z, w) {
1516
+ const out = new ARRAY_TYPE(4);
1517
+ out[0] = x;
1518
+ out[1] = y;
1519
+ out[2] = z;
1520
+ out[3] = w;
1521
+ return out;
1522
+ }
1523
+ function copy2(out, a) {
1524
+ out[0] = a[0];
1525
+ out[1] = a[1];
1526
+ out[2] = a[2];
1527
+ out[3] = a[3];
1528
+ return out;
1529
+ }
1530
+ function set2(out, x, y, z, w) {
1531
+ out[0] = x;
1532
+ out[1] = y;
1533
+ out[2] = z;
1534
+ out[3] = w;
1535
+ return out;
1536
+ }
1537
+ function add2(out, a, b) {
1538
+ out[0] = a[0] + b[0];
1539
+ out[1] = a[1] + b[1];
1540
+ out[2] = a[2] + b[2];
1541
+ out[3] = a[3] + b[3];
1542
+ return out;
1543
+ }
1544
+ function subtract2(out, a, b) {
1545
+ out[0] = a[0] - b[0];
1546
+ out[1] = a[1] - b[1];
1547
+ out[2] = a[2] - b[2];
1548
+ out[3] = a[3] - b[3];
1549
+ return out;
1550
+ }
1551
+ function multiply2(out, a, b) {
1552
+ out[0] = a[0] * b[0];
1553
+ out[1] = a[1] * b[1];
1554
+ out[2] = a[2] * b[2];
1555
+ out[3] = a[3] * b[3];
1556
+ return out;
1557
+ }
1558
+ function divide2(out, a, b) {
1559
+ out[0] = a[0] / b[0];
1560
+ out[1] = a[1] / b[1];
1561
+ out[2] = a[2] / b[2];
1562
+ out[3] = a[3] / b[3];
1563
+ return out;
1564
+ }
1565
+ function ceil2(out, a) {
1566
+ out[0] = Math.ceil(a[0]);
1567
+ out[1] = Math.ceil(a[1]);
1568
+ out[2] = Math.ceil(a[2]);
1569
+ out[3] = Math.ceil(a[3]);
1570
+ return out;
1571
+ }
1572
+ function floor2(out, a) {
1573
+ out[0] = Math.floor(a[0]);
1574
+ out[1] = Math.floor(a[1]);
1575
+ out[2] = Math.floor(a[2]);
1576
+ out[3] = Math.floor(a[3]);
1577
+ return out;
1578
+ }
1579
+ function min2(out, a, b) {
1580
+ out[0] = Math.min(a[0], b[0]);
1581
+ out[1] = Math.min(a[1], b[1]);
1582
+ out[2] = Math.min(a[2], b[2]);
1583
+ out[3] = Math.min(a[3], b[3]);
1584
+ return out;
1585
+ }
1586
+ function max2(out, a, b) {
1587
+ out[0] = Math.max(a[0], b[0]);
1588
+ out[1] = Math.max(a[1], b[1]);
1589
+ out[2] = Math.max(a[2], b[2]);
1590
+ out[3] = Math.max(a[3], b[3]);
1591
+ return out;
1592
+ }
1593
+ function round3(out, a) {
1594
+ out[0] = round(a[0]);
1595
+ out[1] = round(a[1]);
1596
+ out[2] = round(a[2]);
1597
+ out[3] = round(a[3]);
1598
+ return out;
1599
+ }
1600
+ function scale2(out, a, b) {
1601
+ out[0] = a[0] * b;
1602
+ out[1] = a[1] * b;
1603
+ out[2] = a[2] * b;
1604
+ out[3] = a[3] * b;
1605
+ return out;
1606
+ }
1607
+ function scaleAndAdd2(out, a, b, scale3) {
1608
+ out[0] = a[0] + b[0] * scale3;
1609
+ out[1] = a[1] + b[1] * scale3;
1610
+ out[2] = a[2] + b[2] * scale3;
1611
+ out[3] = a[3] + b[3] * scale3;
1612
+ return out;
1613
+ }
1614
+ function distance2(a, b) {
1615
+ const x = b[0] - a[0];
1616
+ const y = b[1] - a[1];
1617
+ const z = b[2] - a[2];
1618
+ const w = b[3] - a[3];
1619
+ return Math.sqrt(x * x + y * y + z * z + w * w);
1620
+ }
1621
+ function squaredDistance2(a, b) {
1622
+ const x = b[0] - a[0];
1623
+ const y = b[1] - a[1];
1624
+ const z = b[2] - a[2];
1625
+ const w = b[3] - a[3];
1626
+ return x * x + y * y + z * z + w * w;
1627
+ }
1628
+ function length2(a) {
1629
+ const x = a[0];
1630
+ const y = a[1];
1631
+ const z = a[2];
1632
+ const w = a[3];
1633
+ return Math.sqrt(x * x + y * y + z * z + w * w);
1634
+ }
1635
+ function squaredLength2(a) {
1636
+ const x = a[0];
1637
+ const y = a[1];
1638
+ const z = a[2];
1639
+ const w = a[3];
1640
+ return x * x + y * y + z * z + w * w;
1641
+ }
1642
+ function negate2(out, a) {
1643
+ out[0] = -a[0];
1644
+ out[1] = -a[1];
1645
+ out[2] = -a[2];
1646
+ out[3] = -a[3];
1647
+ return out;
1648
+ }
1649
+ function inverse2(out, a) {
1650
+ out[0] = 1 / a[0];
1651
+ out[1] = 1 / a[1];
1652
+ out[2] = 1 / a[2];
1653
+ out[3] = 1 / a[3];
1654
+ return out;
1655
+ }
1656
+ function normalize2(out, a) {
1657
+ const x = a[0];
1658
+ const y = a[1];
1659
+ const z = a[2];
1660
+ const w = a[3];
1661
+ let len3 = x * x + y * y + z * z + w * w;
1662
+ if (len3 > 0) {
1663
+ len3 = 1 / Math.sqrt(len3);
1664
+ }
1665
+ out[0] = x * len3;
1666
+ out[1] = y * len3;
1667
+ out[2] = z * len3;
1668
+ out[3] = w * len3;
1669
+ return out;
1670
+ }
1671
+ function dot2(a, b) {
1672
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
1673
+ }
1674
+ function cross2(out, u, v, w) {
1675
+ const A = v[0] * w[1] - v[1] * w[0];
1676
+ const B = v[0] * w[2] - v[2] * w[0];
1677
+ const C = v[0] * w[3] - v[3] * w[0];
1678
+ const D = v[1] * w[2] - v[2] * w[1];
1679
+ const E = v[1] * w[3] - v[3] * w[1];
1680
+ const F = v[2] * w[3] - v[3] * w[2];
1681
+ const G = u[0];
1682
+ const H = u[1];
1683
+ const I = u[2];
1684
+ const J = u[3];
1685
+ out[0] = H * F - I * E + J * D;
1686
+ out[1] = -(G * F) + I * C - J * B;
1687
+ out[2] = G * E - H * C + J * A;
1688
+ out[3] = -(G * D) + H * B - I * A;
1689
+ return out;
1690
+ }
1691
+ function lerp2(out, a, b, t) {
1692
+ const ax = a[0];
1693
+ const ay = a[1];
1694
+ const az = a[2];
1695
+ const aw = a[3];
1696
+ out[0] = ax + t * (b[0] - ax);
1697
+ out[1] = ay + t * (b[1] - ay);
1698
+ out[2] = az + t * (b[2] - az);
1699
+ out[3] = aw + t * (b[3] - aw);
1700
+ return out;
1701
+ }
1702
+ function random2(out, scale3) {
1703
+ scale3 = scale3 === void 0 ? 1 : scale3;
1704
+ let v1;
1705
+ let v2;
1706
+ let v3;
1707
+ let v4;
1708
+ let s1;
1709
+ let s2;
1710
+ do {
1711
+ v1 = RANDOM() * 2 - 1;
1712
+ v2 = RANDOM() * 2 - 1;
1713
+ s1 = v1 * v1 + v2 * v2;
1714
+ } while (s1 >= 1);
1715
+ do {
1716
+ v3 = RANDOM() * 2 - 1;
1717
+ v4 = RANDOM() * 2 - 1;
1718
+ s2 = v3 * v3 + v4 * v4;
1719
+ } while (s2 >= 1);
1720
+ const d = Math.sqrt((1 - s1) / s2);
1721
+ out[0] = scale3 * v1;
1722
+ out[1] = scale3 * v2;
1723
+ out[2] = scale3 * v3 * d;
1724
+ out[3] = scale3 * v4 * d;
1725
+ return out;
1726
+ }
1727
+ function transformMat42(out, a, m) {
1728
+ const x = a[0];
1729
+ const y = a[1];
1730
+ const z = a[2];
1731
+ const w = a[3];
1732
+ out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
1733
+ out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
1734
+ out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
1735
+ out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
1736
+ return out;
1737
+ }
1738
+ function transformQuat2(out, a, q) {
1739
+ const x = a[0];
1740
+ const y = a[1];
1741
+ const z = a[2];
1742
+ const qx = q[0];
1743
+ const qy = q[1];
1744
+ const qz = q[2];
1745
+ const qw = q[3];
1746
+ const ix = qw * x + qy * z - qz * y;
1747
+ const iy = qw * y + qz * x - qx * z;
1748
+ const iz = qw * z + qx * y - qy * x;
1749
+ const iw = -qx * x - qy * y - qz * z;
1750
+ out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
1751
+ out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
1752
+ out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
1753
+ out[3] = a[3];
1754
+ return out;
1755
+ }
1756
+ function zero2(out) {
1757
+ out[0] = 0;
1758
+ out[1] = 0;
1759
+ out[2] = 0;
1760
+ out[3] = 0;
1761
+ return out;
1762
+ }
1763
+ function str2(a) {
1764
+ return `vec4(${a[0]}, ${a[1]}, ${a[2]}, ${a[3]})`;
1765
+ }
1766
+ function exactEquals2(a, b) {
1767
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
1768
+ }
1769
+ function equals3(a, b) {
1770
+ const a0 = a[0];
1771
+ const a1 = a[1];
1772
+ const a2 = a[2];
1773
+ const a3 = a[3];
1774
+ const b0 = b[0];
1775
+ const b1 = b[1];
1776
+ const b2 = b[2];
1777
+ const b3 = b[3];
1778
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1, Math.abs(a3), Math.abs(b3));
1779
+ }
1780
+ var sub2 = subtract2;
1781
+ var mul2 = multiply2;
1782
+ var div2 = divide2;
1783
+ var dist2 = distance2;
1784
+ var sqrDist2 = squaredDistance2;
1785
+ var len2 = length2;
1786
+ var sqrLen2 = squaredLength2;
1787
+ var forEach2 = (function() {
1788
+ const vec = create2();
1789
+ return function(a, stride, offset, count, fn, arg) {
1790
+ let i;
1791
+ let l;
1792
+ if (!stride) {
1793
+ stride = 4;
1794
+ }
1795
+ if (!offset) {
1796
+ offset = 0;
1797
+ }
1798
+ if (count) {
1799
+ l = Math.min(count * stride + offset, a.length);
1800
+ } else {
1801
+ l = a.length;
1802
+ }
1803
+ for (i = offset; i < l; i += stride) {
1804
+ vec[0] = a[i];
1805
+ vec[1] = a[i + 1];
1806
+ vec[2] = a[i + 2];
1807
+ vec[3] = a[i + 3];
1808
+ fn(vec, vec, arg);
1809
+ a[i] = vec[0];
1810
+ a[i + 1] = vec[1];
1811
+ a[i + 2] = vec[2];
1812
+ a[i + 3] = vec[3];
1813
+ }
1814
+ return a;
1815
+ };
1816
+ })();
1448
1817
 
1449
1818
  // ../../node_modules/@luma.gl/shadertools/dist/modules/math/fp64/fp64-utils.js
1450
1819
  function fp64ify(a, out = [], startIndex = 0) {
@@ -1673,6 +2042,462 @@ vec2 sqrt_fp64(vec2 a) {
1673
2042
  `
1674
2043
  );
1675
2044
 
2045
+ // ../../node_modules/@luma.gl/shadertools/dist/modules/math/fp64/fp64-arithmetic-wgsl-integer.js
2046
+ var fp64arithmeticWGSLInteger = (
2047
+ /* wgsl */
2048
+ `struct Fp64F32Bits {
2049
+ sign: u32,
2050
+ baseExponent: i32,
2051
+ significand: u32,
2052
+ isZero: bool,
2053
+ isInf: bool,
2054
+ isNan: bool,
2055
+ };
2056
+
2057
+ // Decode an f32 as (-1)^sign * significand * 2^baseExponent.
2058
+ fn fp64_decode_f32_bits(bits: u32) -> Fp64F32Bits {
2059
+ let sign = bits >> 31u;
2060
+ let exponentBits = (bits >> 23u) & 0xffu;
2061
+ let fraction = bits & 0x7fffffu;
2062
+
2063
+ if (exponentBits == 0xffu) {
2064
+ return Fp64F32Bits(sign, 0, 0u, false, fraction == 0u, fraction != 0u);
2065
+ }
2066
+ if (exponentBits == 0u) {
2067
+ return Fp64F32Bits(sign, -149, fraction, fraction == 0u, false, false);
2068
+ }
2069
+ return Fp64F32Bits(sign, i32(exponentBits) - 150, 0x800000u | fraction, false, false, false);
2070
+ }
2071
+
2072
+ fn fp64_f32_magnitude_compare(aBits: u32, bBits: u32) -> i32 {
2073
+ let aMagnitude = aBits & 0x7fffffffu;
2074
+ let bMagnitude = bBits & 0x7fffffffu;
2075
+ if (aMagnitude == bMagnitude) {
2076
+ return 0;
2077
+ }
2078
+ return select(-1, 1, aMagnitude > bMagnitude);
2079
+ }
2080
+
2081
+ fn fp64_make_residual_f32_bits(
2082
+ exactSign: u32,
2083
+ exactMagnitude: vec2u,
2084
+ exactBaseExponent: i32,
2085
+ highBits: u32
2086
+ ) -> u32 {
2087
+ if (fp64_u64_is_zero(exactMagnitude)) {
2088
+ return 0u;
2089
+ }
2090
+
2091
+ let high = fp64_decode_f32_bits(highBits);
2092
+ if (high.isInf || high.isNan) {
2093
+ return exactSign << 31u;
2094
+ }
2095
+ if (high.isZero) {
2096
+ return fp64_make_f32_bits_from_u64(exactSign, exactMagnitude, exactBaseExponent);
2097
+ }
2098
+
2099
+ let commonBaseExponent = min(exactBaseExponent, high.baseExponent);
2100
+ let exactShift = exactBaseExponent - commonBaseExponent;
2101
+ let highShift = high.baseExponent - commonBaseExponent;
2102
+
2103
+ // A normal two-sum/two-product residual never needs a shift this large.
2104
+ // This guard gives deterministic underflow behavior outside that contract.
2105
+ if (exactShift >= 64 || highShift >= 64) {
2106
+ return exactSign << 31u;
2107
+ }
2108
+
2109
+ let exactAligned = fp64_u64_shift_left(exactMagnitude, u32(exactShift));
2110
+ let highAligned = fp64_u64_shift_left(vec2u(0u, high.significand), u32(highShift));
2111
+ let comparison = fp64_u64_compare(exactAligned, highAligned);
2112
+ if (comparison == 0) {
2113
+ return 0u;
2114
+ }
2115
+
2116
+ var residualSign = exactSign;
2117
+ var residualMagnitude: vec2u;
2118
+ if (comparison > 0) {
2119
+ residualMagnitude = fp64_u64_sub(exactAligned, highAligned);
2120
+ } else {
2121
+ residualSign = exactSign ^ 1u;
2122
+ residualMagnitude = fp64_u64_sub(highAligned, exactAligned);
2123
+ }
2124
+ return fp64_make_f32_bits_from_u64(
2125
+ residualSign,
2126
+ residualMagnitude,
2127
+ commonBaseExponent
2128
+ );
2129
+ }
2130
+
2131
+ fn fp64_split_accumulator_bits(
2132
+ sign: u32,
2133
+ magnitude: vec2u,
2134
+ baseExponent: i32
2135
+ ) -> vec2u {
2136
+ let highBits = fp64_make_f32_bits_from_u64(sign, magnitude, baseExponent);
2137
+ let lowBits = fp64_make_residual_f32_bits(sign, magnitude, baseExponent, highBits);
2138
+ return vec2u(highBits, lowBits);
2139
+ }
2140
+
2141
+ fn fp64_two_sum_integer_bits(aBits: u32, bBits: u32) -> vec2u {
2142
+ let a = fp64_decode_f32_bits(aBits);
2143
+ let b = fp64_decode_f32_bits(bBits);
2144
+
2145
+ if (a.isNan || b.isNan) {
2146
+ return vec2u(0x7fc00000u, 0u);
2147
+ }
2148
+ if (a.isInf || b.isInf) {
2149
+ if (a.isInf && b.isInf && a.sign != b.sign) {
2150
+ return vec2u(0x7fc00000u, 0u);
2151
+ }
2152
+ return select(vec2u(bBits, 0u), vec2u(aBits, 0u), a.isInf);
2153
+ }
2154
+ if (a.isZero && b.isZero) {
2155
+ return vec2u((a.sign & b.sign) << 31u, 0u);
2156
+ }
2157
+ if (a.isZero) {
2158
+ return vec2u(bBits, 0u);
2159
+ }
2160
+ if (b.isZero) {
2161
+ return vec2u(aBits, 0u);
2162
+ }
2163
+
2164
+ let exponentDifference = select(
2165
+ b.baseExponent - a.baseExponent,
2166
+ a.baseExponent - b.baseExponent,
2167
+ a.baseExponent >= b.baseExponent
2168
+ );
2169
+
2170
+ // Beyond half an ulp, rounding cannot change the larger operand. Returning
2171
+ // the smaller operand intact also avoids an unbounded integer alignment.
2172
+ // At a power-of-two boundary the spacing below the larger operand is half
2173
+ // the spacing above it, so an opposite-sign gap-25 operand can still change
2174
+ // the rounded high limb. Gap 26 is the first universally safe early-out.
2175
+ if (exponentDifference > 25) {
2176
+ if (fp64_f32_magnitude_compare(aBits, bBits) >= 0) {
2177
+ return vec2u(aBits, bBits);
2178
+ }
2179
+ return vec2u(bBits, aBits);
2180
+ }
2181
+
2182
+ let commonBaseExponent = min(a.baseExponent, b.baseExponent);
2183
+ let aMagnitude = fp64_u64_shift_left(
2184
+ vec2u(0u, a.significand),
2185
+ u32(a.baseExponent - commonBaseExponent)
2186
+ );
2187
+ let bMagnitude = fp64_u64_shift_left(
2188
+ vec2u(0u, b.significand),
2189
+ u32(b.baseExponent - commonBaseExponent)
2190
+ );
2191
+
2192
+ var resultSign = a.sign;
2193
+ var resultMagnitude: vec2u;
2194
+ if (a.sign == b.sign) {
2195
+ resultMagnitude = fp64_u64_add(aMagnitude, bMagnitude);
2196
+ } else {
2197
+ let comparison = fp64_u64_compare(aMagnitude, bMagnitude);
2198
+ if (comparison == 0) {
2199
+ return vec2u(0u, 0u);
2200
+ }
2201
+ if (comparison > 0) {
2202
+ resultMagnitude = fp64_u64_sub(aMagnitude, bMagnitude);
2203
+ } else {
2204
+ resultSign = b.sign;
2205
+ resultMagnitude = fp64_u64_sub(bMagnitude, aMagnitude);
2206
+ }
2207
+ }
2208
+
2209
+ return fp64_split_accumulator_bits(resultSign, resultMagnitude, commonBaseExponent);
2210
+ }
2211
+
2212
+ fn fp64_two_sum_integer(a: f32, b: f32) -> vec2f {
2213
+ let resultBits = fp64_two_sum_integer_bits(bitcast<u32>(a), bitcast<u32>(b));
2214
+ return vec2f(bitcast<f32>(resultBits.x), bitcast<f32>(resultBits.y));
2215
+ }
2216
+
2217
+ fn fp64_multiply_significands(a: u32, b: u32) -> vec2u {
2218
+ let aLow = a & 0xffffu;
2219
+ let aHigh = a >> 16u;
2220
+ let bLow = b & 0xffffu;
2221
+ let bHigh = b >> 16u;
2222
+ let lowProduct = aLow * bLow;
2223
+ let crossProduct = aLow * bHigh + aHigh * bLow;
2224
+ let highProduct = aHigh * bHigh;
2225
+
2226
+ var result = vec2u(0u, lowProduct);
2227
+ result = fp64_u64_add(
2228
+ result,
2229
+ fp64_u64_shift_left(vec2u(0u, crossProduct), 16u)
2230
+ );
2231
+ result = fp64_u64_add(result, vec2u(highProduct, 0u));
2232
+ return result;
2233
+ }
2234
+
2235
+ fn fp64_two_prod_integer_bits(aBits: u32, bBits: u32) -> vec2u {
2236
+ let a = fp64_decode_f32_bits(aBits);
2237
+ let b = fp64_decode_f32_bits(bBits);
2238
+ let resultSign = a.sign ^ b.sign;
2239
+
2240
+ if (a.isNan || b.isNan || ((a.isZero || b.isZero) && (a.isInf || b.isInf))) {
2241
+ return vec2u(0x7fc00000u, 0u);
2242
+ }
2243
+ if (a.isInf || b.isInf) {
2244
+ return vec2u((resultSign << 31u) | 0x7f800000u, resultSign << 31u);
2245
+ }
2246
+ if (a.isZero || b.isZero) {
2247
+ return vec2u(resultSign << 31u, resultSign << 31u);
2248
+ }
2249
+
2250
+ let magnitude = fp64_multiply_significands(a.significand, b.significand);
2251
+ return fp64_split_accumulator_bits(
2252
+ resultSign,
2253
+ magnitude,
2254
+ a.baseExponent + b.baseExponent
2255
+ );
2256
+ }
2257
+
2258
+ fn fp64_two_prod_integer(a: f32, b: f32) -> vec2f {
2259
+ let resultBits = fp64_two_prod_integer_bits(bitcast<u32>(a), bitcast<u32>(b));
2260
+ return vec2f(bitcast<f32>(resultBits.x), bitcast<f32>(resultBits.y));
2261
+ }
2262
+
2263
+ fn fp64_round_add_integer(a: f32, b: f32) -> f32 {
2264
+ return fp64_two_sum_integer(a, b).x;
2265
+ }
2266
+
2267
+ fn fp64_round_mul_integer(a: f32, b: f32) -> f32 {
2268
+ return fp64_two_prod_integer(a, b).x;
2269
+ }
2270
+
2271
+ #ifndef LUMA_FP64_PREDICATE_ONLY
2272
+ fn fp64_f32_finite_exponent(value: Fp64F32Bits) -> i32 {
2273
+ let mostSignificantBit = 31u - countLeadingZeros(value.significand);
2274
+ return value.baseExponent + i32(mostSignificantBit);
2275
+ }
2276
+
2277
+ fn fp64_scale_f32_integer(value: f32, exponent: i32) -> f32 {
2278
+ let decoded = fp64_decode_f32_bits(bitcast<u32>(value));
2279
+ if (decoded.isZero || decoded.isInf || decoded.isNan) {
2280
+ return value;
2281
+ }
2282
+ let resultBits = fp64_make_f32_bits_from_u64(
2283
+ decoded.sign,
2284
+ vec2u(0u, decoded.significand),
2285
+ decoded.baseExponent + exponent
2286
+ );
2287
+ return bitcast<f32>(resultBits);
2288
+ }
2289
+
2290
+ // Divide normalized significands so the hardware operation cannot overflow,
2291
+ // underflow, or flush a subnormal result. Reapply the exponent with integer
2292
+ // packing, which also produces subnormal correction limbs without relying on
2293
+ // floating-point arithmetic to preserve them.
2294
+ fn fp64_divide_f32_integer(aValue: f32, bValue: f32) -> f32 {
2295
+ let a = fp64_decode_f32_bits(bitcast<u32>(aValue));
2296
+ let b = fp64_decode_f32_bits(bitcast<u32>(bValue));
2297
+ if (a.isZero || b.isZero || a.isInf || b.isInf || a.isNan || b.isNan) {
2298
+ return aValue / bValue;
2299
+ }
2300
+
2301
+ let aMostSignificantBit = 31u - countLeadingZeros(a.significand);
2302
+ let bMostSignificantBit = 31u - countLeadingZeros(b.significand);
2303
+ let normalizedABits = fp64_make_f32_bits_from_u64(
2304
+ a.sign,
2305
+ vec2u(0u, a.significand),
2306
+ -i32(aMostSignificantBit)
2307
+ );
2308
+ let normalizedBBits = fp64_make_f32_bits_from_u64(
2309
+ b.sign,
2310
+ vec2u(0u, b.significand),
2311
+ -i32(bMostSignificantBit)
2312
+ );
2313
+ let normalizedQuotient = bitcast<f32>(normalizedABits) / bitcast<f32>(normalizedBBits);
2314
+ let quotient = fp64_decode_f32_bits(bitcast<u32>(normalizedQuotient));
2315
+ let exponentShift =
2316
+ a.baseExponent + i32(aMostSignificantBit) -
2317
+ b.baseExponent - i32(bMostSignificantBit);
2318
+ let quotientBits = fp64_make_f32_bits_from_u64(
2319
+ quotient.sign,
2320
+ vec2u(0u, quotient.significand),
2321
+ quotient.baseExponent + exponentShift
2322
+ );
2323
+ return bitcast<f32>(quotientBits);
2324
+ }
2325
+ #endif
2326
+
2327
+ #ifndef LUMA_FP64_PREDICATE_ONLY
2328
+ fn split(a: f32) -> vec2f {
2329
+ let aBits = bitcast<u32>(a);
2330
+ let decoded = fp64_decode_f32_bits(aBits);
2331
+ if (decoded.isZero || decoded.isInf || decoded.isNan) {
2332
+ return vec2f(a, 0.0);
2333
+ }
2334
+
2335
+ var roundedHigh = decoded.significand >> 12u;
2336
+ let remainder = decoded.significand & 0xfffu;
2337
+ if (remainder > 0x800u || (remainder == 0x800u && (roundedHigh & 1u) == 1u)) {
2338
+ roundedHigh = roundedHigh + 1u;
2339
+ }
2340
+ var highMagnitude = vec2u(0u, roundedHigh << 12u);
2341
+ var highBits = fp64_make_f32_bits_from_u64(
2342
+ decoded.sign,
2343
+ highMagnitude,
2344
+ decoded.baseExponent
2345
+ );
2346
+ // Rounding the high limb of a maximum-exponent value can overflow even
2347
+ // though the original value is finite. Truncate only in that boundary case
2348
+ // so split remains an exact finite decomposition.
2349
+ if (fp64_decode_f32_bits(highBits).isInf) {
2350
+ roundedHigh = decoded.significand >> 12u;
2351
+ highMagnitude = vec2u(0u, roundedHigh << 12u);
2352
+ highBits = fp64_make_f32_bits_from_u64(
2353
+ decoded.sign,
2354
+ highMagnitude,
2355
+ decoded.baseExponent
2356
+ );
2357
+ }
2358
+ let lowBits = fp64_make_residual_f32_bits(
2359
+ decoded.sign,
2360
+ vec2u(0u, decoded.significand),
2361
+ decoded.baseExponent,
2362
+ highBits
2363
+ );
2364
+ return vec2f(bitcast<f32>(highBits), bitcast<f32>(lowBits));
2365
+ }
2366
+
2367
+ fn split2(a: vec2f) -> vec2f {
2368
+ var result = split(a.x);
2369
+ result.y = fp64_round_add_integer(result.y, a.y);
2370
+ return result;
2371
+ }
2372
+ #endif
2373
+
2374
+ #ifndef LUMA_FP64_PREDICATE_ONLY
2375
+ fn quickTwoSum(a: f32, b: f32) -> vec2f {
2376
+ return fp64_two_sum_integer(a, b);
2377
+ }
2378
+ #endif
2379
+
2380
+ fn twoSum(a: f32, b: f32) -> vec2f {
2381
+ return fp64_two_sum_integer(a, b);
2382
+ }
2383
+
2384
+ fn twoSub(a: f32, b: f32) -> vec2f {
2385
+ let bBits = bitcast<u32>(b) ^ 0x80000000u;
2386
+ let resultBits = fp64_two_sum_integer_bits(bitcast<u32>(a), bBits);
2387
+ return vec2f(bitcast<f32>(resultBits.x), bitcast<f32>(resultBits.y));
2388
+ }
2389
+
2390
+ #ifndef LUMA_FP64_PREDICATE_ONLY
2391
+ fn twoSqr(a: f32) -> vec2f {
2392
+ return fp64_two_prod_integer(a, a);
2393
+ }
2394
+
2395
+ fn twoProd(a: f32, b: f32) -> vec2f {
2396
+ return fp64_two_prod_integer(a, b);
2397
+ }
2398
+ #endif
2399
+
2400
+ fn sum_fp64(a: vec2f, b: vec2f) -> vec2f {
2401
+ var sum = fp64_two_sum_integer(a.x, b.x);
2402
+ let lowSum = fp64_two_sum_integer(a.y, b.y);
2403
+ sum.y = fp64_round_add_integer(sum.y, lowSum.x);
2404
+ sum = fp64_two_sum_integer(sum.x, sum.y);
2405
+ sum.y = fp64_round_add_integer(sum.y, lowSum.y);
2406
+ return fp64_two_sum_integer(sum.x, sum.y);
2407
+ }
2408
+
2409
+ fn sub_fp64(a: vec2f, b: vec2f) -> vec2f {
2410
+ let negatedB = vec2f(
2411
+ bitcast<f32>(bitcast<u32>(b.x) ^ 0x80000000u),
2412
+ bitcast<f32>(bitcast<u32>(b.y) ^ 0x80000000u)
2413
+ );
2414
+ return sum_fp64(a, negatedB);
2415
+ }
2416
+
2417
+ fn mul_fp64(a: vec2f, b: vec2f) -> vec2f {
2418
+ var product = fp64_two_prod_integer(a.x, b.x);
2419
+ let crossProduct1 = fp64_round_mul_integer(a.x, b.y);
2420
+ product.y = fp64_round_add_integer(product.y, crossProduct1);
2421
+ product = fp64_two_sum_integer(product.x, product.y);
2422
+ let crossProduct2 = fp64_round_mul_integer(a.y, b.x);
2423
+ product.y = fp64_round_add_integer(product.y, crossProduct2);
2424
+ return fp64_two_sum_integer(product.x, product.y);
2425
+ }
2426
+
2427
+ #ifndef LUMA_FP64_PREDICATE_ONLY
2428
+ fn fp64_scale_fp64_integer(value: vec2f, exponent: i32) -> vec2f {
2429
+ let high = fp64_scale_f32_integer(value.x, exponent);
2430
+ let low = fp64_scale_f32_integer(value.y, exponent);
2431
+ return sum_fp64(vec2f(high, 0.0), vec2f(low, 0.0));
2432
+ }
2433
+
2434
+ fn fp64_div_fp64_normalized(a: vec2f, b: vec2f) -> vec2f {
2435
+ let quotientHigh = fp64_divide_f32_integer(a.x, b.x);
2436
+ var quotient = vec2f(quotientHigh, 0.0);
2437
+
2438
+ let remainder = sub_fp64(a, mul_fp64(b, quotient));
2439
+ let quotientLow = fp64_divide_f32_integer(remainder.x, b.x);
2440
+ quotient = sum_fp64(quotient, vec2f(quotientLow, 0.0));
2441
+
2442
+ let secondRemainder = sub_fp64(a, mul_fp64(b, quotient));
2443
+ let correction = fp64_divide_f32_integer(secondRemainder.x, b.x);
2444
+ return sum_fp64(quotient, vec2f(correction, 0.0));
2445
+ }
2446
+
2447
+ fn div_fp64(a: vec2f, b: vec2f) -> vec2f {
2448
+ let decodedA = fp64_decode_f32_bits(bitcast<u32>(a.x));
2449
+ let decodedB = fp64_decode_f32_bits(bitcast<u32>(b.x));
2450
+ if (
2451
+ decodedA.isZero || decodedB.isZero ||
2452
+ decodedA.isInf || decodedB.isInf ||
2453
+ decodedA.isNan || decodedB.isNan
2454
+ ) {
2455
+ return fp64_div_fp64_normalized(a, b);
2456
+ }
2457
+
2458
+ let exponentA = fp64_f32_finite_exponent(decodedA);
2459
+ let exponentB = fp64_f32_finite_exponent(decodedB);
2460
+ // Correct the quotient near unity so b * q and the remainder stay clear of
2461
+ // both f32 underflow and overflow. The exponent difference is applied once.
2462
+ let normalizedA = fp64_scale_fp64_integer(a, -exponentA);
2463
+ let normalizedB = fp64_scale_fp64_integer(b, -exponentB);
2464
+ let normalizedQuotient = fp64_div_fp64_normalized(normalizedA, normalizedB);
2465
+ return fp64_scale_fp64_integer(normalizedQuotient, exponentA - exponentB);
2466
+ }
2467
+
2468
+ fn fp64_sqrt_fp64_normalized(a: vec2f) -> vec2f {
2469
+ let estimate = sqrt(a.x);
2470
+ let difference = sub_fp64(a, fp64_two_prod_integer(estimate, estimate)).x;
2471
+ let denominator = fp64_round_add_integer(estimate, estimate);
2472
+ let correction = fp64_divide_f32_integer(difference, denominator);
2473
+ return sum_fp64(vec2f(estimate, 0.0), vec2f(correction, 0.0));
2474
+ }
2475
+
2476
+ fn sqrt_fp64(a: vec2f) -> vec2f {
2477
+ let decoded = fp64_decode_f32_bits(bitcast<u32>(a.x));
2478
+ let decodedLow = fp64_decode_f32_bits(bitcast<u32>(a.y));
2479
+ if (decoded.isZero && decodedLow.isZero) {
2480
+ return vec2f(0.0, 0.0);
2481
+ }
2482
+ if (decoded.sign == 1u) {
2483
+ let nanValue = fp64_nan(a.x);
2484
+ return vec2f(nanValue, nanValue);
2485
+ }
2486
+
2487
+ if (decoded.isInf || decoded.isNan) {
2488
+ return fp64_sqrt_fp64_normalized(a);
2489
+ }
2490
+ let exponent = fp64_f32_finite_exponent(decoded);
2491
+ // An even scale lets the final square-root rescale use an integer exponent.
2492
+ let evenExponent = exponent - (exponent & 1);
2493
+ let normalizedA = fp64_scale_fp64_integer(a, -evenExponent);
2494
+ let normalizedRoot = fp64_sqrt_fp64_normalized(normalizedA);
2495
+ return fp64_scale_fp64_integer(normalizedRoot, evenExponent / 2);
2496
+ }
2497
+ #endif
2498
+ `
2499
+ );
2500
+
1676
2501
  // ../../node_modules/@luma.gl/shadertools/dist/modules/math/fp64/fp64-arithmetic-wgsl.js
1677
2502
  var fp64arithmeticWGSL = (
1678
2503
  /* wgsl */
@@ -1683,6 +2508,7 @@ vec2 sqrt_fp64(vec2 a) {
1683
2508
 
1684
2509
  @group(0) @binding(auto) var<uniform> fp64arithmetic : Fp64ArithmeticUniforms;
1685
2510
 
2511
+ #ifndef LUMA_FP64_F32_INPUT_ONLY
1686
2512
  struct Fp64Bits {
1687
2513
  sign: u32,
1688
2514
  exponent: i32,
@@ -1691,11 +2517,14 @@ struct Fp64Bits {
1691
2517
  isInf: bool,
1692
2518
  isNan: bool,
1693
2519
  };
2520
+ #endif
1694
2521
 
2522
+ #ifndef LUMA_FP64_PREDICATE_ONLY
1695
2523
  fn fp64_nan(seed: f32) -> f32 {
1696
2524
  let nanBits = 0x7fc00000u | select(0u, 1u, seed < 0.0);
1697
2525
  return bitcast<f32>(nanBits);
1698
2526
  }
2527
+ #endif
1699
2528
 
1700
2529
  fn fp64_u64_is_zero(value: vec2u) -> bool {
1701
2530
  return value.x == 0u && value.y == 0u;
@@ -1783,6 +2612,7 @@ fn fp64_u64_has_bits_below(value: vec2u, bitCount: u32) -> bool {
1783
2612
  return (value.y & lowMask) != 0u;
1784
2613
  }
1785
2614
 
2615
+ #ifndef LUMA_FP64_F32_INPUT_ONLY
1786
2616
  fn fp64_u64_shift_right_sticky(value: vec2u, shift: u32) -> vec2u {
1787
2617
  var shifted = fp64_u64_shift_right(value, shift);
1788
2618
  if (fp64_u64_has_bits_below(value, shift)) {
@@ -1790,6 +2620,7 @@ fn fp64_u64_shift_right_sticky(value: vec2u, shift: u32) -> vec2u {
1790
2620
  }
1791
2621
  return shifted;
1792
2622
  }
2623
+ #endif
1793
2624
 
1794
2625
  fn fp64_u64_count_leading_zeros(value: vec2u) -> u32 {
1795
2626
  if (value.x != 0u) {
@@ -1813,6 +2644,22 @@ fn fp64_round_shift_right_to_u32(value: vec2u, shift: u32) -> u32 {
1813
2644
  return rounded;
1814
2645
  }
1815
2646
 
2647
+ #ifndef LUMA_FP64_F32_INPUT_ONLY
2648
+ fn fp64_round_shift_right(value: vec2u, shift: u32) -> vec2u {
2649
+ if (shift == 0u) {
2650
+ return value;
2651
+ }
2652
+
2653
+ var rounded = fp64_u64_shift_right(value, shift);
2654
+ let guard = fp64_u64_get_bit(value, shift - 1u);
2655
+ let hasTrailingBits = fp64_u64_has_bits_below(value, shift - 1u);
2656
+ if (guard && (hasTrailingBits || (rounded.y & 1u) == 1u)) {
2657
+ rounded = fp64_u64_add(rounded, vec2u(0u, 1u));
2658
+ }
2659
+ return rounded;
2660
+ }
2661
+ #endif
2662
+
1816
2663
  fn fp64_make_f32_bits_from_u64(sign: u32, significand: vec2u, baseExponent: i32) -> u32 {
1817
2664
  if (fp64_u64_is_zero(significand)) {
1818
2665
  return sign << 31u;
@@ -1860,6 +2707,7 @@ fn fp64_make_f32_bits_from_u64(sign: u32, significand: vec2u, baseExponent: i32)
1860
2707
  return (sign << 31u) | mantissa;
1861
2708
  }
1862
2709
 
2710
+ #ifndef LUMA_FP64_F32_INPUT_ONLY
1863
2711
  fn fp64_decode_bits(bits: vec2u) -> Fp64Bits {
1864
2712
  let sign = bits.x >> 31u;
1865
2713
  let exponentBits = (bits.x >> 20u) & 0x7ffu;
@@ -1886,6 +2734,261 @@ fn fp64_finite_magnitude_compare(a: Fp64Bits, b: Fp64Bits) -> i32 {
1886
2734
  }
1887
2735
  return fp64_u64_compare(a.significand, b.significand);
1888
2736
  }
2737
+ #endif
2738
+
2739
+ #ifndef LUMA_FP64_F32_INPUT_ONLY
2740
+ struct Fp64RawF32Bits {
2741
+ sign: u32,
2742
+ baseExponent: i32,
2743
+ significand: u32,
2744
+ isZero: bool,
2745
+ isInf: bool,
2746
+ isNan: bool,
2747
+ };
2748
+
2749
+ // Decode an f32 as (-1)^sign * significand * 2^baseExponent. This shared
2750
+ // integer representation lets normalization remain independent of the
2751
+ // selected double-single arithmetic implementation.
2752
+ fn fp64_decode_raw_f32_bits(bits: u32) -> Fp64RawF32Bits {
2753
+ let sign = bits >> 31u;
2754
+ let exponentBits = (bits >> 23u) & 0xffu;
2755
+ let fraction = bits & 0x7fffffu;
2756
+
2757
+ if (exponentBits == 0xffu) {
2758
+ return Fp64RawF32Bits(sign, 0, 0u, false, fraction == 0u, fraction != 0u);
2759
+ }
2760
+ if (exponentBits == 0u) {
2761
+ return Fp64RawF32Bits(sign, -149, fraction, fraction == 0u, false, false);
2762
+ }
2763
+ return Fp64RawF32Bits(
2764
+ sign,
2765
+ i32(exponentBits) - 150,
2766
+ 0x800000u | fraction,
2767
+ false,
2768
+ false,
2769
+ false
2770
+ );
2771
+ }
2772
+
2773
+ fn fp64_raw_f32_magnitude_compare(aBits: u32, bBits: u32) -> i32 {
2774
+ let aMagnitude = aBits & 0x7fffffffu;
2775
+ let bMagnitude = bBits & 0x7fffffffu;
2776
+ if (aMagnitude == bMagnitude) {
2777
+ return 0;
2778
+ }
2779
+ return select(-1, 1, aMagnitude > bMagnitude);
2780
+ }
2781
+
2782
+ fn fp64_make_raw_residual_f32_bits(
2783
+ exactSign: u32,
2784
+ exactMagnitude: vec2u,
2785
+ exactBaseExponent: i32,
2786
+ highBits: u32
2787
+ ) -> u32 {
2788
+ if (fp64_u64_is_zero(exactMagnitude)) {
2789
+ return 0u;
2790
+ }
2791
+
2792
+ let high = fp64_decode_raw_f32_bits(highBits);
2793
+ if (high.isInf || high.isNan) {
2794
+ return 0u;
2795
+ }
2796
+ if (high.isZero) {
2797
+ return fp64_make_f32_bits_from_u64(exactSign, exactMagnitude, exactBaseExponent);
2798
+ }
2799
+
2800
+ let commonBaseExponent = min(exactBaseExponent, high.baseExponent);
2801
+ let exactShift = exactBaseExponent - commonBaseExponent;
2802
+ let highShift = high.baseExponent - commonBaseExponent;
2803
+ if (exactShift >= 64 || highShift >= 64) {
2804
+ return 0u;
2805
+ }
2806
+
2807
+ let exactAligned = fp64_u64_shift_left(exactMagnitude, u32(exactShift));
2808
+ let highAligned = fp64_u64_shift_left(vec2u(0u, high.significand), u32(highShift));
2809
+ let comparison = fp64_u64_compare(exactAligned, highAligned);
2810
+ if (comparison == 0) {
2811
+ return 0u;
2812
+ }
2813
+
2814
+ var residualSign = exactSign;
2815
+ var residualMagnitude: vec2u;
2816
+ if (comparison > 0) {
2817
+ residualMagnitude = fp64_u64_sub(exactAligned, highAligned);
2818
+ } else {
2819
+ residualSign = exactSign ^ 1u;
2820
+ residualMagnitude = fp64_u64_sub(highAligned, exactAligned);
2821
+ }
2822
+ return fp64_make_f32_bits_from_u64(
2823
+ residualSign,
2824
+ residualMagnitude,
2825
+ commonBaseExponent
2826
+ );
2827
+ }
2828
+
2829
+ fn fp64_split_raw_accumulator_bits(
2830
+ sign: u32,
2831
+ magnitude: vec2u,
2832
+ baseExponent: i32
2833
+ ) -> vec2u {
2834
+ if (fp64_u64_is_zero(magnitude)) {
2835
+ return vec2u(0u);
2836
+ }
2837
+ let highBits = fp64_make_f32_bits_from_u64(sign, magnitude, baseExponent);
2838
+ let rawLowBits = fp64_make_raw_residual_f32_bits(sign, magnitude, baseExponent, highBits);
2839
+ let lowBits = select(rawLowBits, 0u, (rawLowBits & 0x7fffffffu) == 0u);
2840
+ if ((highBits & 0x7fffffffu) == 0u && (lowBits & 0x7fffffffu) == 0u) {
2841
+ return vec2u(0u);
2842
+ }
2843
+ return vec2u(highBits, lowBits);
2844
+ }
2845
+ #endif
2846
+
2847
+ #ifndef LUMA_FP64_F32_INPUT_ONLY
2848
+ // Round an arithmetic accumulator to binary64 before splitting it. The
2849
+ // aligned add/subtract paths retain three guard bits plus a sticky bit, which
2850
+ // is sufficient for round-to-nearest-even at the binary64 boundary.
2851
+ fn fp64_split_binary64_accumulator_bits(
2852
+ sign: u32,
2853
+ magnitude: vec2u,
2854
+ baseExponent: i32
2855
+ ) -> vec2u {
2856
+ if (fp64_u64_is_zero(magnitude)) {
2857
+ return vec2u(0u);
2858
+ }
2859
+
2860
+ let mostSignificantBit = 63u - fp64_u64_count_leading_zeros(magnitude);
2861
+ let exponent = baseExponent + i32(mostSignificantBit);
2862
+ if (exponent > 1023) {
2863
+ return vec2u((sign << 31u) | 0x7f800000u, 0u);
2864
+ }
2865
+
2866
+ var roundedMagnitude = magnitude;
2867
+ var roundedBaseExponent = baseExponent;
2868
+ if (exponent >= -1022) {
2869
+ if (mostSignificantBit > 52u) {
2870
+ let shift = mostSignificantBit - 52u;
2871
+ roundedMagnitude = fp64_round_shift_right(magnitude, shift);
2872
+ roundedBaseExponent = baseExponent + i32(shift);
2873
+ }
2874
+ } else {
2875
+ let shift = -1074 - baseExponent;
2876
+ if (shift > 0) {
2877
+ roundedMagnitude = fp64_round_shift_right(magnitude, u32(shift));
2878
+ roundedBaseExponent = -1074;
2879
+ }
2880
+ }
2881
+
2882
+ if (fp64_u64_is_zero(roundedMagnitude)) {
2883
+ return vec2u(0u);
2884
+ }
2885
+ return fp64_split_raw_accumulator_bits(sign, roundedMagnitude, roundedBaseExponent);
2886
+ }
2887
+ #endif
2888
+
2889
+ #ifndef LUMA_FP64_PREDICATE_ONLY
2890
+ fn fp64_add_raw_f32_bits(aBits: u32, bBits: u32) -> vec2u {
2891
+ let a = fp64_decode_raw_f32_bits(aBits);
2892
+ let b = fp64_decode_raw_f32_bits(bBits);
2893
+
2894
+ if (a.isNan || b.isNan) {
2895
+ return vec2u(0x7fc00000u, 0u);
2896
+ }
2897
+ if (a.isInf || b.isInf) {
2898
+ if (a.isInf && b.isInf && a.sign != b.sign) {
2899
+ return vec2u(0x7fc00000u, 0u);
2900
+ }
2901
+ return select(vec2u(bBits, 0u), vec2u(aBits, 0u), a.isInf);
2902
+ }
2903
+ if (a.isZero && b.isZero) {
2904
+ return vec2u(0u);
2905
+ }
2906
+ if (a.isZero) {
2907
+ return vec2u(bBits, 0u);
2908
+ }
2909
+ if (b.isZero) {
2910
+ return vec2u(aBits, 0u);
2911
+ }
2912
+
2913
+ let exponentDifference = abs(a.baseExponent - b.baseExponent);
2914
+ if (exponentDifference > 25) {
2915
+ if (fp64_raw_f32_magnitude_compare(aBits, bBits) >= 0) {
2916
+ return vec2u(aBits, bBits);
2917
+ }
2918
+ return vec2u(bBits, aBits);
2919
+ }
2920
+
2921
+ let commonBaseExponent = min(a.baseExponent, b.baseExponent);
2922
+ let aMagnitude = fp64_u64_shift_left(
2923
+ vec2u(0u, a.significand),
2924
+ u32(a.baseExponent - commonBaseExponent)
2925
+ );
2926
+ let bMagnitude = fp64_u64_shift_left(
2927
+ vec2u(0u, b.significand),
2928
+ u32(b.baseExponent - commonBaseExponent)
2929
+ );
2930
+
2931
+ var resultSign = a.sign;
2932
+ var resultMagnitude: vec2u;
2933
+ if (a.sign == b.sign) {
2934
+ resultMagnitude = fp64_u64_add(aMagnitude, bMagnitude);
2935
+ } else {
2936
+ let comparison = fp64_u64_compare(aMagnitude, bMagnitude);
2937
+ if (comparison == 0) {
2938
+ return vec2u(0u);
2939
+ }
2940
+ if (comparison > 0) {
2941
+ resultMagnitude = fp64_u64_sub(aMagnitude, bMagnitude);
2942
+ } else {
2943
+ resultSign = b.sign;
2944
+ resultMagnitude = fp64_u64_sub(bMagnitude, aMagnitude);
2945
+ }
2946
+ }
2947
+
2948
+ return fp64_split_raw_accumulator_bits(
2949
+ resultSign,
2950
+ resultMagnitude,
2951
+ commonBaseExponent
2952
+ );
2953
+ }
2954
+ #endif
2955
+
2956
+ #ifndef LUMA_FP64_F32_INPUT_ONLY
2957
+ fn fp64_add_aligned_magnitudes_to_fp64_bits(
2958
+ sign: u32,
2959
+ larger: Fp64Bits,
2960
+ smaller: Fp64Bits
2961
+ ) -> vec2u {
2962
+ let largeSignificand = fp64_u64_shift_left(larger.significand, 3u);
2963
+ let smallSignificand = fp64_u64_shift_right_sticky(
2964
+ fp64_u64_shift_left(smaller.significand, 3u),
2965
+ u32(larger.exponent - smaller.exponent)
2966
+ );
2967
+ let resultSignificand = fp64_u64_add(largeSignificand, smallSignificand);
2968
+ return fp64_split_binary64_accumulator_bits(
2969
+ sign,
2970
+ resultSignificand,
2971
+ larger.exponent - 55
2972
+ );
2973
+ }
2974
+
2975
+ fn fp64_sub_aligned_magnitudes_to_fp64_bits(
2976
+ sign: u32,
2977
+ larger: Fp64Bits,
2978
+ smaller: Fp64Bits
2979
+ ) -> vec2u {
2980
+ let largeSignificand = fp64_u64_shift_left(larger.significand, 3u);
2981
+ let smallSignificand = fp64_u64_shift_right_sticky(
2982
+ fp64_u64_shift_left(smaller.significand, 3u),
2983
+ u32(larger.exponent - smaller.exponent)
2984
+ );
2985
+ let resultSignificand = fp64_u64_sub(largeSignificand, smallSignificand);
2986
+ return fp64_split_binary64_accumulator_bits(
2987
+ sign,
2988
+ resultSignificand,
2989
+ larger.exponent - 55
2990
+ );
2991
+ }
1889
2992
 
1890
2993
  fn fp64_add_aligned_magnitudes_to_f32_bits(sign: u32, larger: Fp64Bits, smaller: Fp64Bits) -> u32 {
1891
2994
  let largeSignificand = fp64_u64_shift_left(larger.significand, 3u);
@@ -1907,54 +3010,106 @@ fn fp64_sub_aligned_magnitudes_to_f32_bits(sign: u32, larger: Fp64Bits, smaller:
1907
3010
  return fp64_make_f32_bits_from_u64(sign, resultSignificand, larger.exponent - 55);
1908
3011
  }
1909
3012
 
1910
- // Subtract two raw binary64 values and round the exact result once to f32.
1911
- // The input words are canonical high/low words: .x contains sign/exponent/high
1912
- // fraction bits, and .y contains the low 32 fraction bits.
1913
- fn sub_fp64u32_to_f32_bits(aBits: vec2u, bBits: vec2u) -> u32 {
3013
+ // Subtract two raw binary64 values and round the exact result once to f32.
3014
+ // The input words are canonical high/low words: .x contains sign/exponent/high
3015
+ // fraction bits, and .y contains the low 32 fraction bits.
3016
+ fn sub_fp64u32_to_f32_bits(aBits: vec2u, bBits: vec2u) -> u32 {
3017
+ let a = fp64_decode_bits(aBits);
3018
+ let b = fp64_decode_bits(bBits);
3019
+ let bSubtractionSign = b.sign ^ 1u;
3020
+
3021
+ if (a.isNan || b.isNan) {
3022
+ return 0x7fc00000u;
3023
+ }
3024
+ if (a.isInf && b.isInf) {
3025
+ if (a.sign == bSubtractionSign) {
3026
+ return (a.sign << 31u) | 0x7f800000u;
3027
+ }
3028
+ return 0x7fc00000u;
3029
+ }
3030
+ if (a.isInf) {
3031
+ return (a.sign << 31u) | 0x7f800000u;
3032
+ }
3033
+ if (b.isInf) {
3034
+ return (bSubtractionSign << 31u) | 0x7f800000u;
3035
+ }
3036
+ if (a.isZero && b.isZero) {
3037
+ return select(0u, 0x80000000u, a.sign == 1u && b.sign == 0u);
3038
+ }
3039
+
3040
+ let magnitudeComparison = fp64_finite_magnitude_compare(a, b);
3041
+ if (a.sign == bSubtractionSign) {
3042
+ if (magnitudeComparison >= 0) {
3043
+ return fp64_add_aligned_magnitudes_to_f32_bits(a.sign, a, b);
3044
+ }
3045
+ return fp64_add_aligned_magnitudes_to_f32_bits(a.sign, b, a);
3046
+ }
3047
+
3048
+ if (magnitudeComparison == 0) {
3049
+ return 0u;
3050
+ }
3051
+ if (magnitudeComparison > 0) {
3052
+ return fp64_sub_aligned_magnitudes_to_f32_bits(a.sign, a, b);
3053
+ }
3054
+ return fp64_sub_aligned_magnitudes_to_f32_bits(bSubtractionSign, b, a);
3055
+ }
3056
+
3057
+ fn sub_fp64u32_to_f32(aBits: vec2u, bBits: vec2u) -> f32 {
3058
+ return bitcast<f32>(sub_fp64u32_to_f32_bits(aBits, bBits));
3059
+ }
3060
+
3061
+ // Subtract two raw binary64 values, round once to binary64, then split the
3062
+ // result into normalized f32 limbs. Finite results must fit within the f32
3063
+ // exponent range; larger magnitudes map to infinity and smaller magnitudes
3064
+ // map to zero. The input words use canonical high/low word order.
3065
+ fn sub_fp64u32_to_fp64_bits(aBits: vec2u, bBits: vec2u) -> vec2u {
1914
3066
  let a = fp64_decode_bits(aBits);
1915
3067
  let b = fp64_decode_bits(bBits);
1916
3068
  let bSubtractionSign = b.sign ^ 1u;
1917
3069
 
1918
3070
  if (a.isNan || b.isNan) {
1919
- return 0x7fc00000u;
3071
+ return vec2u(0x7fc00000u, 0u);
1920
3072
  }
1921
3073
  if (a.isInf && b.isInf) {
1922
3074
  if (a.sign == bSubtractionSign) {
1923
- return (a.sign << 31u) | 0x7f800000u;
3075
+ return vec2u((a.sign << 31u) | 0x7f800000u, 0u);
1924
3076
  }
1925
- return 0x7fc00000u;
3077
+ return vec2u(0x7fc00000u, 0u);
1926
3078
  }
1927
3079
  if (a.isInf) {
1928
- return (a.sign << 31u) | 0x7f800000u;
3080
+ return vec2u((a.sign << 31u) | 0x7f800000u, 0u);
1929
3081
  }
1930
3082
  if (b.isInf) {
1931
- return (bSubtractionSign << 31u) | 0x7f800000u;
3083
+ return vec2u((bSubtractionSign << 31u) | 0x7f800000u, 0u);
1932
3084
  }
1933
3085
  if (a.isZero && b.isZero) {
1934
- return select(0u, 0x80000000u, a.sign == 1u && b.sign == 0u);
3086
+ return vec2u(0u);
1935
3087
  }
1936
3088
 
1937
3089
  let magnitudeComparison = fp64_finite_magnitude_compare(a, b);
1938
3090
  if (a.sign == bSubtractionSign) {
1939
3091
  if (magnitudeComparison >= 0) {
1940
- return fp64_add_aligned_magnitudes_to_f32_bits(a.sign, a, b);
3092
+ return fp64_add_aligned_magnitudes_to_fp64_bits(a.sign, a, b);
1941
3093
  }
1942
- return fp64_add_aligned_magnitudes_to_f32_bits(a.sign, b, a);
3094
+ return fp64_add_aligned_magnitudes_to_fp64_bits(a.sign, b, a);
1943
3095
  }
1944
3096
 
1945
3097
  if (magnitudeComparison == 0) {
1946
- return 0u;
3098
+ return vec2u(0u);
1947
3099
  }
1948
3100
  if (magnitudeComparison > 0) {
1949
- return fp64_sub_aligned_magnitudes_to_f32_bits(a.sign, a, b);
3101
+ return fp64_sub_aligned_magnitudes_to_fp64_bits(a.sign, a, b);
1950
3102
  }
1951
- return fp64_sub_aligned_magnitudes_to_f32_bits(bSubtractionSign, b, a);
3103
+ return fp64_sub_aligned_magnitudes_to_fp64_bits(bSubtractionSign, b, a);
1952
3104
  }
1953
3105
 
1954
- fn sub_fp64u32_to_f32(aBits: vec2u, bBits: vec2u) -> f32 {
1955
- return bitcast<f32>(sub_fp64u32_to_f32_bits(aBits, bBits));
3106
+ fn sub_fp64u32_to_fp64(aBits: vec2u, bBits: vec2u) -> vec2f {
3107
+ let resultBits = sub_fp64u32_to_fp64_bits(aBits, bBits);
3108
+ return vec2f(bitcast<f32>(resultBits.x), bitcast<f32>(resultBits.y));
1956
3109
  }
3110
+ #endif
1957
3111
 
3112
+ #ifndef LUMA_FP64_PREDICATE_ONLY
1958
3113
  fn fp64_runtime_zero() -> f32 {
1959
3114
  return fp64arithmetic.ONE * 0.0;
1960
3115
  }
@@ -1966,7 +3121,11 @@ fn prevent_fp64_optimization(value: f32) -> f32 {
1966
3121
  return value;
1967
3122
  #endif
1968
3123
  }
3124
+ #endif
1969
3125
 
3126
+ #ifdef LUMA_FP64_INTEGER_ARITHMETIC
3127
+ ${fp64arithmeticWGSLInteger}
3128
+ #else
1970
3129
  fn split(a: f32) -> vec2f {
1971
3130
  let splitValue = prevent_fp64_optimization(fp64arithmetic.SPLIT + fp64_runtime_zero());
1972
3131
  let t = prevent_fp64_optimization(a * splitValue);
@@ -2117,6 +3276,7 @@ fn mul_fp64(a: vec2f, b: vec2f) -> vec2f {
2117
3276
  return prod;
2118
3277
  }
2119
3278
 
3279
+ #ifndef LUMA_FP64_PREDICATE_ONLY
2120
3280
  fn div_fp64(a: vec2f, b: vec2f) -> vec2f {
2121
3281
  let xn = prevent_fp64_optimization(1.0 / b.x);
2122
3282
  let yn = mul_fp64(a, vec2f(xn, fp64_runtime_zero()));
@@ -2149,6 +3309,97 @@ fn sqrt_fp64(a: vec2f) -> vec2f {
2149
3309
  return sum_fp64(vec2f(yn, 0.0), prod);
2150
3310
  #endif
2151
3311
  }
3312
+ #endif
3313
+ #endif
3314
+
3315
+ #ifndef LUMA_FP64_PREDICATE_ONLY
3316
+ fn fp64_f32_bits_is_nan(bits: u32) -> bool {
3317
+ return (bits & 0x7fffffffu) > 0x7f800000u;
3318
+ }
3319
+
3320
+ fn fp64_f32_bits_is_inf(bits: u32) -> bool {
3321
+ return (bits & 0x7fffffffu) == 0x7f800000u;
3322
+ }
3323
+
3324
+ fn fp64_compare_f32_bits(aBits: u32, bBits: u32) -> i32 {
3325
+ let aMagnitude = aBits & 0x7fffffffu;
3326
+ let bMagnitude = bBits & 0x7fffffffu;
3327
+ if (aMagnitude == 0u && bMagnitude == 0u) {
3328
+ return 0;
3329
+ }
3330
+ let aSign = aBits >> 31u;
3331
+ let bSign = bBits >> 31u;
3332
+ if (aSign != bSign) {
3333
+ return select(1, -1, aSign == 1u);
3334
+ }
3335
+ if (aMagnitude == bMagnitude) {
3336
+ return 0;
3337
+ }
3338
+ let magnitudeComparison = select(-1, 1, aMagnitude > bMagnitude);
3339
+ return select(magnitudeComparison, -magnitudeComparison, aSign == 1u);
3340
+ }
3341
+
3342
+ // Normalize an arbitrary pair of finite f32 limbs with integer accumulation.
3343
+ // This is independent of LUMA_FP64_INTEGER_ARITHMETIC and canonicalizes every
3344
+ // representation of zero to vec2f(+0.0, +0.0).
3345
+ fn normalize_fp64(value: vec2f) -> vec2f {
3346
+ let resultBits = fp64_add_raw_f32_bits(bitcast<u32>(value.x), bitcast<u32>(value.y));
3347
+ return vec2f(bitcast<f32>(resultBits.x), bitcast<f32>(resultBits.y));
3348
+ }
3349
+
3350
+ fn is_nan_fp64(value: vec2f) -> bool {
3351
+ let normalized = normalize_fp64(value);
3352
+ return fp64_f32_bits_is_nan(bitcast<u32>(normalized.x)) ||
3353
+ fp64_f32_bits_is_nan(bitcast<u32>(normalized.y));
3354
+ }
3355
+
3356
+ fn is_finite_fp64(value: vec2f) -> bool {
3357
+ let normalized = normalize_fp64(value);
3358
+ let highBits = bitcast<u32>(normalized.x);
3359
+ let lowBits = bitcast<u32>(normalized.y);
3360
+ return !fp64_f32_bits_is_nan(highBits) && !fp64_f32_bits_is_nan(lowBits) &&
3361
+ !fp64_f32_bits_is_inf(highBits) && !fp64_f32_bits_is_inf(lowBits);
3362
+ }
3363
+
3364
+ // Returns -1, 0, or 1. NaN is unordered and returns 0; call is_nan_fp64 or
3365
+ // is_finite_fp64 first when 0 must mean a finite zero.
3366
+ fn sign_fp64(value: vec2f) -> i32 {
3367
+ let normalized = normalize_fp64(value);
3368
+ let highBits = bitcast<u32>(normalized.x);
3369
+ let lowBits = bitcast<u32>(normalized.y);
3370
+ if (fp64_f32_bits_is_nan(highBits) || fp64_f32_bits_is_nan(lowBits)) {
3371
+ return 0;
3372
+ }
3373
+ if ((highBits & 0x7fffffffu) != 0u) {
3374
+ return select(1, -1, (highBits >> 31u) == 1u);
3375
+ }
3376
+ if ((lowBits & 0x7fffffffu) != 0u) {
3377
+ return select(1, -1, (lowBits >> 31u) == 1u);
3378
+ }
3379
+ return 0;
3380
+ }
3381
+
3382
+ // Compares double-single values and returns -1, 0, or 1. NaN is unordered
3383
+ // and returns 0; callers that require equality semantics must first check
3384
+ // is_nan_fp64 or is_finite_fp64.
3385
+ fn compare_fp64(a: vec2f, b: vec2f) -> i32 {
3386
+ let normalizedA = normalize_fp64(a);
3387
+ let normalizedB = normalize_fp64(b);
3388
+ let aHighBits = bitcast<u32>(normalizedA.x);
3389
+ let aLowBits = bitcast<u32>(normalizedA.y);
3390
+ let bHighBits = bitcast<u32>(normalizedB.x);
3391
+ let bLowBits = bitcast<u32>(normalizedB.y);
3392
+ if (fp64_f32_bits_is_nan(aHighBits) || fp64_f32_bits_is_nan(aLowBits) ||
3393
+ fp64_f32_bits_is_nan(bHighBits) || fp64_f32_bits_is_nan(bLowBits)) {
3394
+ return 0;
3395
+ }
3396
+ let highComparison = fp64_compare_f32_bits(aHighBits, bHighBits);
3397
+ if (highComparison != 0) {
3398
+ return highComparison;
3399
+ }
3400
+ return fp64_compare_f32_bits(aLowBits, bLowBits);
3401
+ }
3402
+ #endif
2152
3403
  `
2153
3404
  );
2154
3405
 
@@ -2972,14 +4223,14 @@ vec4 project_position_to_clipspace(
2972
4223
  var getMemoizedUniforms = (0, import_core5._memoize)(calculateUniforms);
2973
4224
  function getUniforms2(opts) {
2974
4225
  if (opts && "viewport" in opts) {
2975
- const { viewProjectionMatrix, scale: scale2 } = opts.viewport;
2976
- return getMemoizedUniforms({ viewProjectionMatrix, scale: scale2 });
4226
+ const { viewProjectionMatrix, scale: scale3 } = opts.viewport;
4227
+ return getMemoizedUniforms({ viewProjectionMatrix, scale: scale3 });
2977
4228
  }
2978
4229
  return {};
2979
4230
  }
2980
4231
  function calculateUniforms({
2981
4232
  viewProjectionMatrix,
2982
- scale: scale2
4233
+ scale: scale3
2983
4234
  }) {
2984
4235
  const glViewProjectionMatrixFP64 = fp64ifyMatrix42(viewProjectionMatrix);
2985
4236
  const viewProjectionMatrix64High = new Float32Array(16);
@@ -2993,7 +4244,7 @@ vec4 project_position_to_clipspace(
2993
4244
  }
2994
4245
  }
2995
4246
  return {
2996
- scale: fp64ify2(scale2),
4247
+ scale: fp64ify2(scale3),
2997
4248
  viewProjectionMatrix: [...viewProjectionMatrix64High],
2998
4249
  viewProjectionMatrix64Low: [...viewProjectionMatrix64Low]
2999
4250
  };
@@ -3026,27 +4277,121 @@ vec4 project_position_to_clipspace(
3026
4277
  "vs:#decl": `
3027
4278
  in vec2 instanceDashArrays;
3028
4279
  #ifdef HIGH_PRECISION_DASH
3029
- in float instanceDashOffsets;
4280
+ // [distance from the start of the path, total length of the path], in common space.
4281
+ in vec2 instanceDashOffsets;
3030
4282
  #endif
3031
4283
  out vec2 vDashArray;
3032
4284
  out float vDashOffset;
4285
+ out float vDashPathLength;
4286
+
4287
+ // Also declared in the fragment stage. The vertex stage needs dashAlignMode so that it can
4288
+ // reduce the dash phase modulo the same period the fragment stage will test against, and
4289
+ // dashUnits to scale the dash array into that period's units.
4290
+ layout(std140) uniform pathStyleUniforms {
4291
+ float dashAlignMode;
4292
+ bool dashGapPickable;
4293
+ highp int dashUnits;
4294
+ } pathStyle;
3033
4295
  `,
3034
4296
  "vs:#main-end": `
3035
- vDashArray = instanceDashArrays;
4297
+ // How many screen pixels one unit of vPathPosition.y covers. The two extrusion branches of
4298
+ // the path layer leave this in different units, and both differences have to be undone here
4299
+ // for a billboarded path to dash like a flat one.
4300
+ //
4301
+ // Flat paths extrude in common space, so \`width\` is in common units and a unit of
4302
+ // vPathPosition.y is width.x * project.scale pixels.
4303
+ //
4304
+ // Billboarded paths extrude in clip space, so \`width\` is already in pixels at this point -
4305
+ // the clip-space conversion happens inside getLineJoinOffset. That conversion also folds in
4306
+ // project.focalDistance, which scales the half-width the segment delta is divided by but not
4307
+ // the delta itself, so a unit of vPathPosition.y ends up focalDistance pixels wider than the
4308
+ // stroke it is supposed to be relative to.
4309
+ float dashWidthPixels = path.billboard
4310
+ ? width.x * project.focalDistance
4311
+ : width.x * project.scale;
4312
+
4313
+ // The actual half stroke width on screen, which is what 'widths' is relative to. It differs
4314
+ // from dashWidthPixels by exactly the spurious focalDistance above, so expressing 'widths'
4315
+ // as a ratio of the two corrects the billboard case onto the flat one - getDashArray is
4316
+ // documented relative to the stroke, and flat paths already honor that.
4317
+ float strokeHalfWidthPixels = path.billboard ? width.x : width.x * project.scale;
4318
+
4319
+ // Everything reduces to "how many screen pixels is one dash unit", divided through by the
4320
+ // pixels per unit of vPathPosition.y, so the fragment shader needs no notion of units at all.
4321
+ // Keep the cases in sync with DASH_UNITS in path-style-extension.ts.
4322
+ float dashUnitPixels = strokeHalfWidthPixels;
4323
+ if (pathStyle.dashUnits == 1) {
4324
+ dashUnitPixels = 1.0;
4325
+ } else if (pathStyle.dashUnits == 2) {
4326
+ dashUnitPixels = project_size_to_pixel(1.0);
4327
+ } else if (pathStyle.dashUnits == 3) {
4328
+ dashUnitPixels = project.scale;
4329
+ }
4330
+ vDashArray = instanceDashArrays * (dashUnitPixels / dashWidthPixels);
4331
+
3036
4332
  #ifdef HIGH_PRECISION_DASH
3037
- vDashOffset = instanceDashOffsets / width.x;
4333
+ // instanceDashOffsets accumulates common-space distance on the CPU. Convert it to pixels, then
4334
+ // divide by the pixel half-width so both extrusion branches use the same dash coordinate.
4335
+ vec2 dashOffsetAndLength = (instanceDashOffsets * project.scale) / dashWidthPixels;
4336
+ vDashPathLength = dashOffsetAndLength.y;
4337
+
4338
+ // Reduce the phase into the first period here rather than in the fragment shader. On a long
4339
+ // path at high zoom the raw offset reaches into the millions, and adding the much smaller
4340
+ // vPathPosition.y to it in fp32 loses the latter entirely, freezing the pattern mid-segment.
4341
+ // The dash function is periodic, so this discards nothing - but it has to be reduced modulo
4342
+ // whichever period the fragment stage will actually test against, hence dashAlignMode here.
4343
+ float dashUnitLength = vDashArray.x + vDashArray.y;
4344
+ float dashPeriod = pathStyle.dashAlignMode == 0.0
4345
+ ? dashUnitLength
4346
+ : vDashPathLength / max(round(vDashPathLength / max(dashUnitLength, 0.0001)), 1.0);
4347
+ vDashOffset = dashPeriod > 0.0 ? mod(dashOffsetAndLength.x, dashPeriod) : 0.0;
3038
4348
  #else
3039
4349
  vDashOffset = 0.0;
4350
+ vDashPathLength = 0.0;
3040
4351
  #endif
3041
4352
  `,
3042
4353
  "fs:#decl": `
3043
4354
  layout(std140) uniform pathStyleUniforms {
3044
4355
  float dashAlignMode;
3045
4356
  bool dashGapPickable;
4357
+ highp int dashUnits;
3046
4358
  } pathStyle;
3047
4359
 
3048
4360
  in vec2 vDashArray;
3049
4361
  in float vDashOffset;
4362
+ in float vDashPathLength;
4363
+
4364
+ // Integral of the dash square wave from 0 to position, i.e. how much solid stroke lies before it.
4365
+ float dashPatternIntegral(float position, float solidLength, float unitLength) {
4366
+ return floor(position / unitLength) * solidLength +
4367
+ min(mod(position, unitLength), solidLength);
4368
+ }
4369
+
4370
+ // Fraction of the filter-width interval centered on position that is covered by solid stroke.
4371
+ //
4372
+ // Testing the dash pattern with a single comparison per fragment aliases as soon as one
4373
+ // period approaches one pixel: the stroke breaks into moire or, when the phase happens to
4374
+ // land inside a dash, reads as solid. Integrating the wave over the fragment footprint
4375
+ // instead is the closed form of what a mipmapped dash texture approximates, and it degrades
4376
+ // the way one wants - once a period drops below a pixel the result converges on the duty
4377
+ // cycle solidLength / unitLength, so the stroke fades to a uniformly lighter line.
4378
+ //
4379
+ // position is reduced into the first period first. The integral satisfies
4380
+ // F(position + periodIndex * unitLength) = F(position) + periodIndex * solidLength,
4381
+ // so the same periodIndex cancels out of the
4382
+ // difference below, and keeping the arguments small avoids subtracting two large nearly
4383
+ // equal numbers in fp32 on long paths.
4384
+ float dashPatternCoverage(
4385
+ float position,
4386
+ float solidLength,
4387
+ float unitLength,
4388
+ float filterWidth
4389
+ ) {
4390
+ float halfFilter = 0.5 * filterWidth;
4391
+ float reducedPosition = mod(position, unitLength);
4392
+ return (dashPatternIntegral(reducedPosition + halfFilter, solidLength, unitLength) -
4393
+ dashPatternIntegral(reducedPosition - halfFilter, solidLength, unitLength)) / filterWidth;
4394
+ }
3050
4395
  `,
3051
4396
  // if given position is in the gap part of the dashed line
3052
4397
  // dashArray.x: solid stroke length, relative to width
@@ -3057,41 +4402,130 @@ in float vDashOffset;
3057
4402
  // 1 - stretch to fit, draw half dash at each end for nicer joints
3058
4403
  // o-- ---- ---- ---- --o-- --o-- ---- --o
3059
4404
  "fs:#main-start": `
4405
+ float dashCoverage = 1.0;
4406
+ bool shouldDiscardDash = false;
4407
+ bool inRoundedDashGap = false;
4408
+ float roundedDashResolvedCoverage = 1.0;
4409
+ float roundedDashSubPixelBlend = 0.0;
4410
+ float roundedDashDutyCycle = 1.0;
4411
+
3060
4412
  float solidLength = vDashArray.x;
3061
4413
  float gapLength = vDashArray.y;
3062
4414
  float unitLength = solidLength + gapLength;
3063
4415
 
3064
- float offset;
3065
-
3066
- if (unitLength > 0.0) {
4416
+ if (unitLength > 0.0 && gapLength > 0.0) {
4417
+ float offset;
3067
4418
  if (pathStyle.dashAlignMode == 0.0) {
3068
4419
  offset = vDashOffset;
3069
4420
  } else {
3070
- unitLength = vPathLength / round(vPathLength / unitLength);
4421
+ // Justified: stretch the period so a whole number of them spans the run, and start
4422
+ // half a dash in so both ends finish on a joint. Rounding up to at least one period
4423
+ // matters - a run shorter than half a period used to round to zero, which made
4424
+ // unitLength infinite and rendered it solid.
4425
+ #ifdef HIGH_PRECISION_DASH
4426
+ // Justify across the entire path rather than each segment separately, so the gaps stay
4427
+ // even instead of being stretched by a different amount on every segment. vDashOffset
4428
+ // already carries the distance to the start of this segment, reduced modulo this same
4429
+ // period in the vertex shader.
4430
+ unitLength = vDashPathLength / max(round(vDashPathLength / unitLength), 1.0);
4431
+ #else
4432
+ unitLength = vPathLength / max(round(vPathLength / unitLength), 1.0);
4433
+ #endif
4434
+ // A short segment or path can make the fitted period shorter than the requested dash.
4435
+ // Bound the solid interval to that period so coverage and duty cycle remain valid.
4436
+ solidLength = min(solidLength, unitLength);
3071
4437
  offset = solidLength / 2.0;
4438
+ #ifdef HIGH_PRECISION_DASH
4439
+ offset += vDashOffset;
4440
+ #endif
3072
4441
  }
3073
4442
 
3074
- float unitOffset = mod(vPathPosition.y + offset, unitLength);
4443
+ float alongPath = vPathPosition.y + offset;
4444
+ float unitOffset = mod(alongPath, unitLength);
4445
+ float filterWidth = max(fwidth(alongPath), 0.0001);
3075
4446
 
3076
- if (gapLength > 0.0 && unitOffset > solidLength) {
3077
- if (path.capType <= 0.5) {
3078
- if (!(pathStyle.dashGapPickable && bool(picking.isActive))) {
3079
- discard;
3080
- }
3081
- } else {
3082
- // caps are rounded, test the distance to solid ends
3083
- float distToEnd = length(vec2(
4447
+ // Picking stays a hard in-or-out test. A blended picking colour decodes to the wrong
4448
+ // index, and dashGapPickable is defined in terms of whole gaps rather than coverage.
4449
+ if (bool(picking.isActive)) {
4450
+ bool inGap = unitOffset > solidLength;
4451
+ // Only measure the rounded end-cap distance after the hard interval test has found a
4452
+ // gap. Inside a solid interval the longitudinal distance is negative; taking its
4453
+ // vector length would turn it positive and incorrectly discard the middle of a dash.
4454
+ if (inGap && path.capType > 0.5) {
4455
+ inGap = length(vec2(
3084
4456
  min(unitOffset - solidLength, unitLength - unitOffset),
3085
4457
  vPathPosition.x
3086
- ));
3087
- if (distToEnd > 1.0) {
3088
- if (!(pathStyle.dashGapPickable && bool(picking.isActive))) {
3089
- discard;
3090
- }
3091
- }
4458
+ )) > 1.0;
3092
4459
  }
4460
+ if (inGap && !pathStyle.dashGapPickable) {
4461
+ shouldDiscardDash = true;
4462
+ }
4463
+ } else if (path.capType <= 0.5) {
4464
+ dashCoverage = dashPatternCoverage(alongPath, solidLength, unitLength, filterWidth);
4465
+ } else {
4466
+ // Rounded caps: the dash end is an arc, so resolve the 2D distance to the nearer solid
4467
+ // end rather than the 1D position along the path. Only filter fragments in the gap;
4468
+ // PathLayer already antialiases the ordinary sides of the solid stroke.
4469
+ float distanceAlongGap = min(unitOffset - solidLength, unitLength - unitOffset);
4470
+ // Clamp before taking the derivative so the distance stays continuous across the
4471
+ // wrapped period boundary. Evaluate fwidth for every fragment in the quad; derivatives
4472
+ // are undefined inside the non-uniform gap branch below.
4473
+ float distanceToEnd = length(vec2(max(distanceAlongGap, 0.0), vPathPosition.x));
4474
+ float capEdgePixels = (1.0 - distanceToEnd) / max(fwidth(distanceToEnd), 1e-6);
4475
+ if (distanceAlongGap > 0.0) {
4476
+ inRoundedDashGap = true;
4477
+ roundedDashResolvedCoverage = smoothedge(0.0, capEdgePixels);
4478
+ dashCoverage = roundedDashResolvedCoverage;
4479
+ }
4480
+ // That smoothstep resolves one dash end at a time, so it stops meaning anything once a
4481
+ // whole period fits inside the filter footprint. Start fading only at that boundary;
4482
+ // blending resolvable periods would attenuate the solid body of every rounded dash.
4483
+ float subPixelBlend = smoothstep(unitLength, 2.0 * unitLength, filterWidth);
4484
+ roundedDashSubPixelBlend = subPixelBlend;
4485
+ // At sub-pixel scale, preserve the area of the repeated capsule rather than falling
4486
+ // back to the rectangular duty cycle. Each scanline contains the solid body plus the
4487
+ // two circular cap intrusions, capped when neighboring caps overlap across the gap.
4488
+ float boundedSolidLength = min(solidLength, unitLength);
4489
+ float effectiveGap = max(unitLength - boundedSolidLength, 0.0);
4490
+ float capSpan = 2.0 * sqrt(max(1.0 - vPathPosition.x * vPathPosition.x, 0.0));
4491
+ float roundedDutyCycle = clamp(
4492
+ (boundedSolidLength + min(effectiveGap, capSpan)) / unitLength,
4493
+ 0.0,
4494
+ 1.0
4495
+ );
4496
+ roundedDashDutyCycle = roundedDutyCycle;
4497
+ dashCoverage = mix(dashCoverage, roundedDutyCycle, subPixelBlend);
3093
4498
  }
4499
+
4500
+ dashCoverage = clamp(dashCoverage, 0.0, 1.0);
4501
+ // Fully transparent fragments would still write depth and occlude whatever is behind.
4502
+ shouldDiscardDash = shouldDiscardDash || dashCoverage <= 0.0;
4503
+ }
4504
+ `,
4505
+ "fs:#main-end": `
4506
+ // PathLayer computes analytic-edge derivatives in its fragment body. A discard in
4507
+ // #main-start can remove helper invocations and make those derivatives undefined, so all
4508
+ // dash-related termination is deferred until the layer has completed that work.
4509
+ #ifdef ANTIALIASING
4510
+ if (inRoundedDashGap) {
4511
+ // The rounded dash cap and PathLayer silhouette are two geometric constraints on the
4512
+ // same fragment. Intersect their coverage instead of multiplying two edge ramps; at the
4513
+ // cap/body shoulder both ramps are half covered and multiplication would create a dark
4514
+ // quarter-covered notch. Keep the sub-pixel duty cycle separable from the path silhouette.
4515
+ float pathCoverage = smoothedge(0.0, edgePixels);
4516
+ float resolvedCapMultiplier =
4517
+ min(pathCoverage, roundedDashResolvedCoverage) / max(pathCoverage, 1e-6);
4518
+ dashCoverage = mix(
4519
+ resolvedCapMultiplier,
4520
+ roundedDashDutyCycle,
4521
+ roundedDashSubPixelBlend
4522
+ );
4523
+ }
4524
+ #endif
4525
+ if (shouldDiscardDash) {
4526
+ discard;
3094
4527
  }
4528
+ fragColor.a *= dashCoverage;
3095
4529
  `
3096
4530
  }
3097
4531
  };
@@ -3313,13 +4747,42 @@ in float instanceOffsets;
3313
4747
  vPathPosition.x = (vPathPosition.x + offsetDir) * offsetWidth - offsetDir;
3314
4748
  vPathPosition.y *= offsetWidth;
3315
4749
  vPathLength *= offsetWidth;
4750
+ #ifdef DASH_ENABLED
4751
+ // DECKGL_FILTER_SIZE above widened the stroke by offsetWidth, so these rescalings restore
4752
+ // units of the original half-width. The dash block merges ahead of this one.
4753
+ if (pathStyle.dashUnits != 0) {
4754
+ // Absolute dash units must not inherit the artificial width used to build offset
4755
+ // geometry. Relative 'widths' units intentionally remain tied to the original stroke.
4756
+ vDashArray *= offsetWidth;
4757
+ }
4758
+ vPathBounds *= offsetWidth;
4759
+ #ifdef HIGH_PRECISION_DASH
4760
+ vDashPathLength *= offsetWidth;
4761
+ // The dash block reduced the CPU path offset modulo a period expressed in widened-width
4762
+ // units. Multiplying that remainder cannot recover the original phase after wrapping, so
4763
+ // redo the reduction with both the restored path offset and restored justified period.
4764
+ float restoredDashUnitLength = vDashArray.x + vDashArray.y;
4765
+ float restoredDashPeriod = pathStyle.dashAlignMode == 0.0
4766
+ ? restoredDashUnitLength
4767
+ : vDashPathLength /
4768
+ max(round(vDashPathLength / max(restoredDashUnitLength, 0.0001)), 1.0);
4769
+ vDashOffset = restoredDashPeriod > 0.0
4770
+ ? mod(dashOffsetAndLength.x * offsetWidth, restoredDashPeriod)
4771
+ : 0.0;
4772
+ #else
4773
+ vDashOffset *= offsetWidth;
4774
+ #endif
4775
+ #endif
3316
4776
  `,
3317
- "fs:#main-start": `
3318
- float isInside;
3319
- isInside = step(-1.0, vPathPosition.x) * step(vPathPosition.x, 1.0);
3320
- if (isInside == 0.0) {
4777
+ "fs:#main-end": `
4778
+ #ifndef ANTIALIASING
4779
+ // With analytic antialiasing, PathLayer evaluates this boundary using the remapped
4780
+ // vPathPosition and retains the complete centered coverage ramp. The hard clip remains for
4781
+ // the original non-AA path.
4782
+ if (abs(vPathPosition.x) > 1.0) {
3321
4783
  discard;
3322
4784
  }
4785
+ #endif
3323
4786
  `
3324
4787
  }
3325
4788
  };
@@ -3329,15 +4792,73 @@ in float instanceOffsets;
3329
4792
  getDashArray: { type: "accessor", value: [0, 0] },
3330
4793
  getOffset: { type: "accessor", value: 0 },
3331
4794
  dashJustified: false,
3332
- dashGapPickable: false
4795
+ dashGapPickable: false,
4796
+ dashUnits: "widths"
3333
4797
  };
4798
+ var DASH_UNITS = {
4799
+ widths: 0,
4800
+ pixels: 1,
4801
+ meters: 2,
4802
+ common: 3
4803
+ };
4804
+ var PATH_STYLE_ATTRIBUTES = ["instanceDashArrays", "instanceDashOffsets", "instanceOffsets"];
4805
+ function dashMetricsProjectionPropsChanged({
4806
+ props,
4807
+ oldProps
4808
+ }) {
4809
+ return !(0, import_core7._deepEqual)(props.modelMatrix, oldProps.modelMatrix, 2) || props.coordinateSystem !== oldProps.coordinateSystem || !(0, import_core7._deepEqual)(props.coordinateOrigin, oldProps.coordinateOrigin, 1);
4810
+ }
4811
+ function projectRenderedPathPosition(layer, position, projectUniforms) {
4812
+ const viewport = layer.context.viewport;
4813
+ if (!(viewport instanceof import_core7.WebMercatorViewport) || viewport.zoom < 12) {
4814
+ return layer.projectPosition(position, { autoOffset: false });
4815
+ }
4816
+ const positionWorld = vec4_exports.transformMat4(
4817
+ [],
4818
+ [position[0], position[1], position[2] || 0, 1],
4819
+ projectUniforms.modelMatrix
4820
+ );
4821
+ const coordinateSystem = layer.props.coordinateSystem === "default" ? "lnglat" : layer.props.coordinateSystem;
4822
+ if (coordinateSystem === "lnglat") {
4823
+ if (Math.abs(positionWorld[1] - projectUniforms.coordinateOrigin[1]) > 0.25) {
4824
+ let longitude = positionWorld[0];
4825
+ if (projectUniforms.wrapLongitude) {
4826
+ longitude = ((longitude + 180) % 360 + 360) % 360 - 180;
4827
+ }
4828
+ const commonPosition = viewport.projectPosition([longitude, positionWorld[1], 0]);
4829
+ return [
4830
+ commonPosition[0] - projectUniforms.commonOrigin[0],
4831
+ commonPosition[1] - projectUniforms.commonOrigin[1],
4832
+ positionWorld[2] * projectUniforms.commonUnitsPerMeter[2]
4833
+ ];
4834
+ }
4835
+ vec3_exports.sub(positionWorld, positionWorld, projectUniforms.coordinateOrigin);
4836
+ } else if (coordinateSystem === "cartesian") {
4837
+ vec3_exports.sub(positionWorld, positionWorld, projectUniforms.coordinateOrigin);
4838
+ }
4839
+ const deltaLatitude = positionWorld[1];
4840
+ const scale3 = vec3_exports.scaleAndAdd(
4841
+ [],
4842
+ projectUniforms.commonUnitsPerWorldUnit,
4843
+ projectUniforms.commonUnitsPerWorldUnit2,
4844
+ deltaLatitude
4845
+ );
4846
+ return vec3_exports.multiply([], positionWorld, scale3);
4847
+ }
3334
4848
  var PathStyleExtension = class extends import_core7.LayerExtension {
3335
4849
  constructor({
3336
4850
  dash = false,
3337
4851
  offset = false,
4852
+ dashMode,
3338
4853
  highPrecisionDash = false
3339
4854
  } = {}) {
3340
- super({ dash: dash || highPrecisionDash, offset, highPrecisionDash });
4855
+ const resolvedDashMode = dashMode ?? (highPrecisionDash ? "path" : "segment");
4856
+ super({
4857
+ dash: dash || highPrecisionDash || dashMode !== void 0,
4858
+ offset,
4859
+ dashMode: resolvedDashMode,
4860
+ highPrecisionDash: resolvedDashMode === "path"
4861
+ });
3341
4862
  }
3342
4863
  getLayerType(layer) {
3343
4864
  if ("pathTesselator" in layer.state) {
@@ -3352,6 +4873,59 @@ in float instanceOffsets;
3352
4873
  }
3353
4874
  return null;
3354
4875
  }
4876
+ synchronizeAttributes(layer) {
4877
+ const attributeManager = layer.getAttributeManager();
4878
+ const layerType = this.getLayerType(layer);
4879
+ if (!attributeManager || !layerType) {
4880
+ return false;
4881
+ }
4882
+ const attributes = attributeManager.getAttributes();
4883
+ const desiredAttributes = /* @__PURE__ */ new Set();
4884
+ if (this.opts.dash) {
4885
+ desiredAttributes.add("instanceDashArrays");
4886
+ }
4887
+ if (layerType === "path" && this.opts.dash && this.opts.dashMode === "path") {
4888
+ desiredAttributes.add("instanceDashOffsets");
4889
+ }
4890
+ if (layerType === "path" && this.opts.offset) {
4891
+ desiredAttributes.add("instanceOffsets");
4892
+ }
4893
+ let topologyChanged = false;
4894
+ for (const attributeName of PATH_STYLE_ATTRIBUTES) {
4895
+ if (attributes[attributeName] && !desiredAttributes.has(attributeName)) {
4896
+ attributeManager.remove([attributeName]);
4897
+ topologyChanged = true;
4898
+ }
4899
+ }
4900
+ if (desiredAttributes.has("instanceDashArrays") && !attributes.instanceDashArrays) {
4901
+ attributeManager.addInstanced({
4902
+ instanceDashArrays: { size: 2, accessor: "getDashArray" }
4903
+ });
4904
+ topologyChanged = true;
4905
+ }
4906
+ if (desiredAttributes.has("instanceDashOffsets") && !attributes.instanceDashOffsets) {
4907
+ attributeManager.addInstanced({
4908
+ instanceDashOffsets: {
4909
+ // [distance from the start of the path, total length of the path]
4910
+ size: 2,
4911
+ // Recalculate from rendered geometry when getPath changes. A binary getPath buffer
4912
+ // contains positions, not dash metrics, so keep it as an update trigger rather than
4913
+ // binding it directly to this attribute.
4914
+ accessor: ["getPath"],
4915
+ // eslint-disable-next-line @typescript-eslint/unbound-method
4916
+ update: this.calculateDashMetrics
4917
+ }
4918
+ });
4919
+ topologyChanged = true;
4920
+ }
4921
+ if (desiredAttributes.has("instanceOffsets") && !attributes.instanceOffsets) {
4922
+ attributeManager.addInstanced({
4923
+ instanceOffsets: { size: 1, accessor: "getOffset" }
4924
+ });
4925
+ topologyChanged = true;
4926
+ }
4927
+ return topologyChanged;
4928
+ }
3355
4929
  isEnabled(layer) {
3356
4930
  return this.getLayerType(layer) !== null;
3357
4931
  }
@@ -3378,57 +4952,54 @@ in float instanceOffsets;
3378
4952
  const defines = {};
3379
4953
  if (extension.opts.dash) {
3380
4954
  result = (0, import_core7._mergeShaders)(result, dashShaders);
3381
- if (extension.opts.highPrecisionDash) {
4955
+ defines.DASH_ENABLED = true;
4956
+ if (extension.opts.dashMode === "path") {
3382
4957
  defines.HIGH_PRECISION_DASH = true;
3383
4958
  }
3384
4959
  }
3385
4960
  if (extension.opts.offset) {
3386
4961
  result = (0, import_core7._mergeShaders)(result, offsetShaders);
4962
+ defines.PATH_STYLE_OFFSET = true;
3387
4963
  }
3388
4964
  const { inject: inject6 } = result;
3389
4965
  const pathStyle = {
3390
4966
  name: "pathStyle",
3391
- inject: inject6,
3392
- uniformTypes: {
3393
- dashAlignMode: "f32",
3394
- dashGapPickable: "i32"
3395
- }
4967
+ inject: inject6
3396
4968
  };
4969
+ if (extension.opts.dash) {
4970
+ pathStyle.uniformTypes = {
4971
+ dashAlignMode: "f32",
4972
+ dashGapPickable: "i32",
4973
+ dashUnits: "i32"
4974
+ };
4975
+ }
3397
4976
  return {
3398
4977
  modules: [pathStyle],
3399
4978
  defines
3400
4979
  };
3401
4980
  }
3402
4981
  initializeState(context, extension) {
3403
- const attributeManager = this.getAttributeManager();
3404
- const layerType = extension.getLayerType(this);
3405
- if (!attributeManager || !layerType) {
3406
- return;
3407
- }
3408
- if (extension.opts.dash) {
3409
- attributeManager.addInstanced({
3410
- instanceDashArrays: { size: 2, accessor: "getDashArray" },
3411
- ...layerType === "path" && extension.opts.highPrecisionDash ? {
3412
- instanceDashOffsets: {
3413
- size: 1,
3414
- accessor: "getPath",
3415
- transform: extension.getDashOffsets.bind(this)
3416
- }
3417
- } : {}
3418
- });
3419
- }
3420
- if (layerType === "path" && extension.opts.offset) {
3421
- attributeManager.addInstanced({
3422
- instanceOffsets: { size: 1, accessor: "getOffset" }
3423
- });
3424
- }
4982
+ extension.synchronizeAttributes(this);
3425
4983
  }
3426
4984
  updateState(params, extension) {
3427
4985
  if (!extension.isEnabled(this)) {
3428
4986
  return;
3429
4987
  }
4988
+ if (params.changeFlags.extensionsChanged) {
4989
+ const topologyChanged = extension.synchronizeAttributes(this);
4990
+ const attributeManager = this.getAttributeManager();
4991
+ if (attributeManager && topologyChanged) {
4992
+ attributeManager.invalidateAll();
4993
+ for (const model of this.getModels()) {
4994
+ model.setBufferLayout(attributeManager.getBufferLayouts(model));
4995
+ }
4996
+ }
4997
+ }
3430
4998
  if (extension.opts.dash) {
3431
4999
  const layerType = extension.getLayerType(this);
5000
+ if (layerType === "path" && extension.opts.dashMode === "path" && dashMetricsProjectionPropsChanged(params)) {
5001
+ this.getAttributeManager()?.invalidate("instanceDashOffsets");
5002
+ }
3432
5003
  if (layerType === "scatterplot" || layerType === "textBackground") {
3433
5004
  const pathStyleProps = {
3434
5005
  dashGapPickable: Boolean(this.props.dashGapPickable)
@@ -3437,12 +5008,101 @@ in float instanceOffsets;
3437
5008
  } else {
3438
5009
  const pathStyleProps = {
3439
5010
  dashAlignMode: this.props.dashJustified ? 1 : 0,
3440
- dashGapPickable: Boolean(this.props.dashGapPickable)
5011
+ dashGapPickable: Boolean(this.props.dashGapPickable),
5012
+ dashUnits: DASH_UNITS[this.props.dashUnits ?? "widths"]
3441
5013
  };
3442
5014
  this.setShaderModuleProps({ pathStyle: pathStyleProps });
3443
5015
  }
3444
5016
  }
3445
5017
  }
5018
+ calculateDashMetrics(attribute, { startRow, endRow }) {
5019
+ const pathTesselator = this.state.pathTesselator;
5020
+ const vertexStarts = pathTesselator?.vertexStarts;
5021
+ const instanceCount = pathTesselator?.instanceCount;
5022
+ const packedPositions = pathTesselator?.get("positions");
5023
+ if (!vertexStarts || instanceCount === void 0 || typeof pathTesselator?.getPathSegmentIndices !== "function") {
5024
+ throw new Error("PathStyleExtension requires PathLayer tessellation data for path dashes.");
5025
+ }
5026
+ const binaryPath = this.props.data?.attributes?.getPath;
5027
+ const binaryValue = ArrayBuffer.isView(binaryPath) ? binaryPath : binaryPath?.value;
5028
+ const hasReadableBinaryPath = ArrayBuffer.isView(binaryValue);
5029
+ const usePackedPositions = packedPositions && (!binaryPath || pathTesselator.normalize || pathTesselator.opts?.isWebGPU);
5030
+ if (binaryPath && !hasReadableBinaryPath || !usePackedPositions && !hasReadableBinaryPath) {
5031
+ throw new Error(
5032
+ "PathStyleExtension cannot calculate whole-path dash metrics from GPU-only getPath data; supply data.attributes.instanceDashOffsets."
5033
+ );
5034
+ }
5035
+ const output = attribute.value;
5036
+ const metricSize = attribute.size;
5037
+ const firstRow = Math.max(0, startRow);
5038
+ const lastRow = Math.min(endRow, vertexStarts.length - 1);
5039
+ let binarySize = 3;
5040
+ let binaryStride = 3;
5041
+ let binaryOffset = 0;
5042
+ if (!usePackedPositions) {
5043
+ const bytesPerElement = binaryValue.BYTES_PER_ELEMENT;
5044
+ binarySize = binaryPath?.size || (this.props.positionFormat === "XY" ? 2 : 3);
5045
+ binaryStride = binaryPath?.stride ? binaryPath.stride / bytesPerElement : binarySize;
5046
+ binaryOffset = binaryPath?.offset ? binaryPath.offset / bytesPerElement : 0;
5047
+ }
5048
+ const sourcePosition = new Array(3);
5049
+ const projectUniforms = import_core7.project.getUniforms({
5050
+ viewport: this.context.viewport,
5051
+ modelMatrix: this.props.modelMatrix,
5052
+ coordinateSystem: this.props.coordinateSystem,
5053
+ coordinateOrigin: this.props.coordinateOrigin,
5054
+ // PathLayer normalizes/cuts wrapped paths on the CPU; its shader never wraps again.
5055
+ autoWrapLongitude: this.wrapLongitude
5056
+ });
5057
+ const readPosition = (vertexIndex) => {
5058
+ if (usePackedPositions) {
5059
+ sourcePosition[0] = packedPositions[vertexIndex * 3];
5060
+ sourcePosition[1] = packedPositions[vertexIndex * 3 + 1];
5061
+ sourcePosition[2] = packedPositions[vertexIndex * 3 + 2];
5062
+ } else {
5063
+ const sourceIndex = binaryOffset + vertexIndex * binaryStride;
5064
+ sourcePosition[0] = binaryValue[sourceIndex];
5065
+ sourcePosition[1] = binaryValue[sourceIndex + 1];
5066
+ sourcePosition[2] = binarySize === 3 ? binaryValue[sourceIndex + 2] : 0;
5067
+ }
5068
+ return sourcePosition;
5069
+ };
5070
+ attribute.startIndices = vertexStarts;
5071
+ for (let row = firstRow; row < lastRow; row++) {
5072
+ const rowStart = vertexStarts[row];
5073
+ const rowEnd = Math.min(vertexStarts[row + 1] ?? instanceCount, instanceCount);
5074
+ output.fill(0, rowStart * metricSize, rowEnd * metricSize);
5075
+ const validSegments = pathTesselator.getPathSegmentIndices(row);
5076
+ let phase = 0;
5077
+ let previousSegment = -2;
5078
+ let previousEnd = null;
5079
+ for (const segmentIndex of validSegments) {
5080
+ const outputIndex = segmentIndex * metricSize;
5081
+ output[outputIndex] = phase;
5082
+ const start = segmentIndex === previousSegment + 1 && previousEnd ? previousEnd : projectRenderedPathPosition(this, readPosition(segmentIndex), projectUniforms);
5083
+ const end = projectRenderedPathPosition(
5084
+ this,
5085
+ readPosition(segmentIndex + 1),
5086
+ projectUniforms
5087
+ );
5088
+ phase += vec3_exports.dist(start, end);
5089
+ previousSegment = segmentIndex;
5090
+ previousEnd = end;
5091
+ }
5092
+ if (metricSize > 1) {
5093
+ for (let vertexIndex = rowStart; vertexIndex < rowEnd; vertexIndex++) {
5094
+ output[vertexIndex * metricSize + 1] = phase;
5095
+ }
5096
+ }
5097
+ }
5098
+ }
5099
+ /**
5100
+ * Calculates the distance from the start of a path to each rendered segment.
5101
+ *
5102
+ * The final entry is zero because PathLayer reserves the final vertex as invalid padding.
5103
+ * This scalar return shape is retained for compatibility; path-mode rendering uses an
5104
+ * internal attribute containing both each segment offset and the total path length.
5105
+ */
3446
5106
  getDashOffsets(path) {
3447
5107
  const result = [0];
3448
5108
  const positionSize = this.props.positionFormat === "XY" ? 2 : 3;
@@ -3452,7 +5112,7 @@ in float instanceOffsets;
3452
5112
  let prevP;
3453
5113
  for (let i = 0; i < geometrySize - 1; i++) {
3454
5114
  p = isNested ? path[i] : path.slice(i * positionSize, i * positionSize + positionSize);
3455
- p = this.projectPosition(p);
5115
+ p = this.projectPosition(p, { autoOffset: false });
3456
5116
  if (i > 0) {
3457
5117
  result[i] = result[i - 1] + vec3_exports.dist(prevP, p);
3458
5118
  }
@@ -3470,12 +5130,26 @@ in float instanceOffsets;
3470
5130
 
3471
5131
  // src/fill-style/shader-module.ts
3472
5132
  var import_core9 = __toESM(require_core(), 1);
5133
+ var FILL_UV_SCALE = 512 / 4e7;
5134
+ function getPatternUnitScale(sizeUnits, viewport) {
5135
+ switch (sizeUnits) {
5136
+ case "pixels":
5137
+ return 2 ** -Math.round(viewport.zoom);
5138
+ case "common":
5139
+ return 1;
5140
+ default:
5141
+ return FILL_UV_SCALE;
5142
+ }
5143
+ }
3473
5144
  var uniformBlock3 = (
3474
5145
  /* glsl */
3475
5146
  `layout(std140) uniform fillUniforms {
3476
5147
  vec2 patternTextureSize;
5148
+ float patternUnitScale;
3477
5149
  bool patternEnabled;
3478
5150
  bool patternMask;
5151
+ bool procedural;
5152
+ bool flipY;
3479
5153
  vec2 uvCoordinateOrigin;
3480
5154
  vec2 uvCoordinateOrigin64Low;
3481
5155
  } fill;
@@ -3484,13 +5158,20 @@ in float instanceOffsets;
3484
5158
  var patternVs = (
3485
5159
  /* glsl */
3486
5160
  `
5161
+ uniform sampler2D fill_patternTexture;
5162
+
3487
5163
  in vec4 fillPatternFrames;
3488
5164
  in float fillPatternScales;
3489
5165
  in vec2 fillPatternOffsets;
5166
+ in vec4 fillPatternBackgroundColors;
3490
5167
 
3491
5168
  out vec2 fill_uv;
3492
5169
  out vec4 fill_patternBounds;
3493
5170
  out vec4 fill_patternPlacement;
5171
+ out vec4 fill_backgroundColor;
5172
+ out float fill_patternCoordinateScale;
5173
+ flat out vec4 fill_patternParams0;
5174
+ flat out vec4 fill_patternParams1;
3494
5175
  `
3495
5176
  );
3496
5177
  var vs3 = `
@@ -3504,9 +5185,136 @@ uniform sampler2D fill_patternTexture;
3504
5185
 
3505
5186
  in vec4 fill_patternBounds;
3506
5187
  in vec4 fill_patternPlacement;
5188
+ in vec4 fill_backgroundColor;
3507
5189
  in vec2 fill_uv;
5190
+ in float fill_patternCoordinateScale;
5191
+ flat in vec4 fill_patternParams0;
5192
+ flat in vec4 fill_patternParams1;
5193
+
5194
+ const float FILL_PATTERN_HATCH = 1.0;
5195
+ const float FILL_PATTERN_CROSS_HATCH = 2.0;
5196
+ const float FILL_PATTERN_DOTS = 3.0;
5197
+
5198
+ float fill_getPatternCoordinate(vec2 axis, float period, float offset) {
5199
+ float coordinateScale = max(abs(fill_patternCoordinateScale), 1.0e-20);
5200
+ vec2 coordinateOrigin = fill.uvCoordinateOrigin;
5201
+ vec2 coordinateOffset = fill.uvCoordinateOrigin64Low + fill_uv;
5202
+ float yDirection = fill.flipY ? 1.0 : -1.0;
5203
+ coordinateOrigin.y *= yDirection;
5204
+ coordinateOffset.y *= yDirection;
5205
+
5206
+ // Reduce the high part before adding the local offset to retain precision in large coordinates.
5207
+ float highPart = mod(dot(coordinateOrigin, axis) / coordinateScale, period);
5208
+ float lowPart = dot(coordinateOffset, axis) / coordinateScale;
5209
+ return mod(highPart + lowPart + offset * period, period);
5210
+ }
5211
+
5212
+ vec2 fill_getPatternCoords(
5213
+ vec2 axis0,
5214
+ vec2 axis1,
5215
+ vec2 period,
5216
+ vec2 offset
5217
+ ) {
5218
+ return vec2(
5219
+ fill_getPatternCoordinate(axis0, period.x, offset.x),
5220
+ fill_getPatternCoordinate(axis1, period.y, offset.y)
5221
+ );
5222
+ }
5223
+
5224
+ float fill_getScreenSpaceAlpha(float signedDistance) {
5225
+ float distanceDerivative = fwidth(signedDistance);
5226
+ if (distanceDerivative == 0.0) {
5227
+ return signedDistance <= 0.0 ? 1.0 : 0.0;
5228
+ }
5229
+ // Normalize the SDF to screen pixels so the coverage ramp is independent of pattern units.
5230
+ float distanceInPixels = signedDistance / distanceDerivative;
5231
+ return 1.0 - smoothstep(-0.5, 0.5, distanceInPixels);
5232
+ }
5233
+
5234
+ float fill_getLineAlpha(float position, float center, float period, float strokeWidth) {
5235
+ float distanceToLine = abs(mod(position - center + period * 0.5, period) - period * 0.5);
5236
+ return fill_getScreenSpaceAlpha(distanceToLine - strokeWidth * 0.5);
5237
+ }
5238
+
5239
+ float fill_getHatchAlpha(float angle, float strokeWidth, vec2 gaps, bool alternating) {
5240
+ vec2 lineDirection = vec2(cos(angle), sin(angle));
5241
+ vec2 lineNormal = vec2(-lineDirection.y, lineDirection.x);
5242
+ float period = alternating
5243
+ ? strokeWidth * 2.0 + gaps.x + gaps.y
5244
+ : strokeWidth + gaps.x;
5245
+ float offset = dot(fill_patternPlacement.xy, lineNormal);
5246
+ float position = fill_getPatternCoordinate(lineNormal, period, offset);
5247
+ float alpha = fill_getLineAlpha(position, 0.0, period, strokeWidth);
5248
+ if (alternating) {
5249
+ alpha = max(
5250
+ alpha,
5251
+ fill_getLineAlpha(position, strokeWidth + gaps.x, period, strokeWidth)
5252
+ );
5253
+ }
5254
+ return alpha;
5255
+ }
5256
+
5257
+ float fill_getDotAlpha(float angle, float skew, float radius, float gap) {
5258
+ vec2 axis0 = vec2(cos(angle), sin(angle));
5259
+ float axis1Angle = angle + 1.5707963267948966 - skew;
5260
+ vec2 axis1 = vec2(cos(axis1Angle), sin(axis1Angle));
5261
+ float determinant = axis0.x * axis1.y - axis0.y * axis1.x;
5262
+
5263
+ // The reciprocal basis maps common coordinates into the skewed dot lattice.
5264
+ vec2 reciprocal0 = vec2(axis1.y, -axis1.x) / determinant;
5265
+ vec2 reciprocal1 = vec2(-axis0.y, axis0.x) / determinant;
5266
+ float period = radius * 2.0 + gap;
5267
+ vec2 latticeCoords = fill_getPatternCoords(
5268
+ reciprocal0,
5269
+ reciprocal1,
5270
+ vec2(period),
5271
+ fill_patternPlacement.xy
5272
+ );
5273
+ vec2 cellOffset = mod(latticeCoords + period * 0.5, period) - period * 0.5;
5274
+ float distanceToDotSquared = 1.0e20;
5275
+ // In a skewed lattice the closest dot is not necessarily in the independently wrapped cell.
5276
+ // Include adjacent cells to keep the repeated distance field continuous at cell boundaries.
5277
+ for (int x = -1; x <= 1; x++) {
5278
+ for (int y = -1; y <= 1; y++) {
5279
+ vec2 neighborOffset = cellOffset + vec2(float(x), float(y)) * period;
5280
+ vec2 dotOffset = axis0 * neighborOffset.x + axis1 * neighborOffset.y;
5281
+ distanceToDotSquared = min(distanceToDotSquared, dot(dotOffset, dotOffset));
5282
+ }
5283
+ }
5284
+ float distanceToDot = sqrt(distanceToDotSquared);
5285
+ return fill_getScreenSpaceAlpha(distanceToDot - radius);
5286
+ }
5287
+
5288
+ float fill_getProceduralPatternAlpha() {
5289
+ float patternType = fill_patternParams0.x;
5290
+ float size = fill_patternParams0.y;
5291
+ vec2 gaps = fill_patternParams0.zw;
5292
+ vec2 angles = fill_patternParams1.xy;
5293
+ float elementCount = fill_patternParams1.z;
5294
+
5295
+ if (patternType == FILL_PATTERN_HATCH) {
5296
+ return fill_getHatchAlpha(angles.x, size, gaps, elementCount == 2.0);
5297
+ }
5298
+ if (patternType == FILL_PATTERN_CROSS_HATCH) {
5299
+ return max(
5300
+ fill_getHatchAlpha(angles.x, size, gaps, false),
5301
+ fill_getHatchAlpha(angles.y, size, gaps, false)
5302
+ );
5303
+ }
5304
+ if (patternType == FILL_PATTERN_DOTS) {
5305
+ return fill_getDotAlpha(angles.x, angles.y, size, gaps.x);
5306
+ }
5307
+ return 1.0;
5308
+ }
3508
5309
 
3509
- const float FILL_UV_SCALE = 512.0 / 40000000.0;
5310
+ // Draw the pattern over the background fill (Porter-Duff source-over)
5311
+ vec4 fill_blendOverBackground(vec4 pattern, vec4 background) {
5312
+ if (background.a == 0.0 || layer.opacity == 0.0) return pattern;
5313
+ float patternAlpha = pattern.a / layer.opacity;
5314
+ float blendedAlpha = patternAlpha + background.a * (1.0 - patternAlpha);
5315
+ vec3 blendedRGB = mix(background.rgb, pattern.rgb, patternAlpha / blendedAlpha);
5316
+ return vec4(blendedRGB, blendedAlpha * layer.opacity);
5317
+ }
3510
5318
  `
3511
5319
  );
3512
5320
  var fs3 = `
@@ -3517,16 +5325,32 @@ ${patternFs}
3517
5325
  "vs:DECKGL_FILTER_GL_POSITION": (
3518
5326
  /* glsl */
3519
5327
  `
3520
- fill_uv = geometry.position.xy;
3521
- `
3522
- ),
3523
- "vs:DECKGL_FILTER_COLOR": (
3524
- /* glsl */
3525
- `
3526
5328
  if (fill.patternEnabled) {
3527
- fill_patternBounds = fillPatternFrames / vec4(fill.patternTextureSize, fill.patternTextureSize);
3528
5329
  fill_patternPlacement.xy = fillPatternOffsets;
3529
- fill_patternPlacement.zw = fillPatternScales * fillPatternFrames.zw;
5330
+ fill_backgroundColor = fillPatternBackgroundColors;
5331
+ if (fill.procedural) {
5332
+ fill_uv = geometry.position.xy;
5333
+ fill_patternCoordinateScale = fill.patternUnitScale * fillPatternScales;
5334
+ int texelIndex = int(fillPatternFrames.x) * 4;
5335
+ fill_patternParams0 = vec4(
5336
+ texelFetch(fill_patternTexture, ivec2(texelIndex, 0), 0).rg,
5337
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 1, 0), 0).rg
5338
+ );
5339
+ fill_patternParams1 = vec4(
5340
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 2, 0), 0).rg,
5341
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 3, 0), 0).rg
5342
+ );
5343
+ } else {
5344
+ fill_patternBounds = fillPatternFrames / vec4(fill.patternTextureSize, fill.patternTextureSize);
5345
+ vec2 patternFrameCommon = fill.patternUnitScale * fillPatternScales * fillPatternFrames.zw;
5346
+ // Reduce the coordinate origin to within one tile before adding the vertex position. The
5347
+ // origin is large in common space, and fp32 cannot carry the sum at full precision.
5348
+ vec2 origin = mod(fill.uvCoordinateOrigin, patternFrameCommon) + fill.uvCoordinateOrigin64Low;
5349
+ fill_uv = (origin + geometry.position.xy) / patternFrameCommon;
5350
+ // Pattern atlases use top-left coordinates, so reverse common-space Y in bottom-left views.
5351
+ fill_uv.y *= fill.flipY ? 1.0 : -1.0;
5352
+ fill_uv += fillPatternOffsets;
5353
+ }
3530
5354
  }
3531
5355
  `
3532
5356
  ),
@@ -3534,17 +5358,25 @@ ${patternFs}
3534
5358
  /* glsl */
3535
5359
  `
3536
5360
  if (fill.patternEnabled) {
3537
- vec2 scale = FILL_UV_SCALE * fill_patternPlacement.zw;
3538
- vec2 patternUV = mod(mod(fill.uvCoordinateOrigin, scale) + fill.uvCoordinateOrigin64Low + fill_uv, scale) / scale;
3539
- patternUV = mod(fill_patternPlacement.xy + patternUV, 1.0);
3540
-
3541
- vec2 texCoords = fill_patternBounds.xy + fill_patternBounds.zw * patternUV;
3542
-
3543
- vec4 patternColor = texture(fill_patternTexture, texCoords);
3544
- color.a *= patternColor.a;
3545
- if (!fill.patternMask) {
3546
- color.rgb = patternColor.rgb;
5361
+ if (fill.procedural) {
5362
+ color.a *= fill_getProceduralPatternAlpha();
5363
+ } else {
5364
+ vec2 patternUV = fract(fill_uv);
5365
+ vec2 texCoords = fill_patternBounds.xy + fill_patternBounds.zw * patternUV;
5366
+
5367
+ // Tiling is emulated by wrapping the coordinate, so texCoords jumps from the end of the
5368
+ // frame back to its start once per tile, leading to the wrong mip level being selected,
5369
+ // which leads to artifacts at pattern edges. fill_uv is continuous across the primitive,
5370
+ // meaning that the correct mip level is always selected
5371
+ vec4 grad = fill_patternBounds.zwzw * vec4(dFdx(fill_uv), dFdy(fill_uv));
5372
+ vec4 patternColor = textureGrad(fill_patternTexture, texCoords, grad.xy, grad.zw);
5373
+
5374
+ color.a *= patternColor.a;
5375
+ if (!fill.patternMask) {
5376
+ color.rgb = patternColor.rgb;
5377
+ }
3547
5378
  }
5379
+ color = fill_blendOverBackground(color, fill_backgroundColor);
3548
5380
  }
3549
5381
  `
3550
5382
  )
@@ -3560,17 +5392,28 @@ ${patternFs}
3560
5392
  uniforms.patternTextureSize = [fillPatternTexture.width, fillPatternTexture.height];
3561
5393
  }
3562
5394
  if ("project" in opts) {
3563
- const { fillPatternMask = true, fillPatternEnabled = true } = opts;
5395
+ const {
5396
+ fillPatternMask = true,
5397
+ fillPatternEnabled = true,
5398
+ fillPatternSizeUnits = "meters",
5399
+ fillPatternCommonFrame = null,
5400
+ procedural = false
5401
+ } = opts;
3564
5402
  const projectUniforms = import_core9.project.getUniforms(opts.project);
3565
5403
  const { commonOrigin: coordinateOriginCommon } = projectUniforms;
3566
- const coordinateOriginCommon64Low = [
3567
- (0, import_core9.fp64LowPart)(coordinateOriginCommon[0]),
3568
- (0, import_core9.fp64LowPart)(coordinateOriginCommon[1])
3569
- ];
3570
- uniforms.uvCoordinateOrigin = coordinateOriginCommon.slice(0, 2);
3571
- uniforms.uvCoordinateOrigin64Low = coordinateOriginCommon64Low;
5404
+ const unitScale = getPatternUnitScale(fillPatternSizeUnits, opts.project.viewport);
5405
+ const origin = [coordinateOriginCommon[0], coordinateOriginCommon[1]];
5406
+ if (fillPatternCommonFrame) {
5407
+ origin[0] %= unitScale * fillPatternCommonFrame[0];
5408
+ origin[1] %= unitScale * fillPatternCommonFrame[1];
5409
+ }
5410
+ uniforms.uvCoordinateOrigin = origin;
5411
+ uniforms.uvCoordinateOrigin64Low = [(0, import_core9.fp64LowPart)(origin[0]), (0, import_core9.fp64LowPart)(origin[1])];
5412
+ uniforms.patternUnitScale = unitScale;
3572
5413
  uniforms.patternMask = fillPatternMask;
3573
5414
  uniforms.patternEnabled = fillPatternEnabled;
5415
+ uniforms.procedural = procedural;
5416
+ uniforms.flipY = opts.project.viewport?.flipY ?? false;
3574
5417
  }
3575
5418
  return uniforms;
3576
5419
  }
@@ -3583,31 +5426,172 @@ ${patternFs}
3583
5426
  getUniforms: getPatternUniforms,
3584
5427
  uniformTypes: {
3585
5428
  patternTextureSize: "vec2<f32>",
5429
+ patternUnitScale: "f32",
3586
5430
  patternEnabled: "i32",
3587
5431
  patternMask: "i32",
5432
+ procedural: "i32",
5433
+ flipY: "i32",
3588
5434
  uvCoordinateOrigin: "vec2<f32>",
3589
5435
  uvCoordinateOrigin64Low: "vec2<f32>"
3590
5436
  }
3591
5437
  };
3592
5438
 
5439
+ // src/fill-style/procedural-pattern.ts
5440
+ var PROCEDURAL_PATTERN_TEXTURE_FORMAT = "rg32float";
5441
+ var PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL = 2;
5442
+ var PROCEDURAL_PATTERN_TEXELS_PER_PATTERN = 4;
5443
+ var PROCEDURAL_PATTERN_TYPES = {
5444
+ none: 0,
5445
+ hatch: 1,
5446
+ crossHatch: 2,
5447
+ dots: 3
5448
+ };
5449
+ var PROCEDURAL_PATTERN_OFFSETS = {
5450
+ type: 0,
5451
+ size: 1,
5452
+ gap0: 2,
5453
+ gap1: 3,
5454
+ angle0: 4,
5455
+ angle1: 5,
5456
+ /** Number of alternating gaps, hatch directions, or dot-grid axes, depending on type. */
5457
+ elementCount: 6
5458
+ };
5459
+ var COMPONENTS_PER_PATTERN = PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL * PROCEDURAL_PATTERN_TEXELS_PER_PATTERN;
5460
+ var DEGREES_TO_RADIANS2 = Math.PI / 180;
5461
+ var DEFAULT_SIZE = 1;
5462
+ var DEFAULT_GAP = 1;
5463
+ var DEFAULT_HATCH_ANGLE = 0;
5464
+ var DEFAULT_CROSS_HATCH_ANGLES = [45, 135];
5465
+ var DEFAULT_DOT_ANGLE = 0;
5466
+ var DEFAULT_DOT_SKEW = 0;
5467
+ function packProceduralPatterns(mapping) {
5468
+ const entries = Object.entries(mapping);
5469
+ const data = new Float32Array((entries.length + 1) * COMPONENTS_PER_PATTERN);
5470
+ const patternIndices = /* @__PURE__ */ new Map();
5471
+ entries.forEach(([id, config2], entryIndex) => {
5472
+ const patternIndex = entryIndex + 1;
5473
+ const offset = patternIndex * COMPONENTS_PER_PATTERN;
5474
+ patternIndices.set(id, patternIndex);
5475
+ switch (config2.type) {
5476
+ case "hatch": {
5477
+ const gap = config2.gap === void 0 ? DEFAULT_GAP : config2.gap;
5478
+ const strokeWidth = config2.strokeWidth === void 0 ? DEFAULT_SIZE : config2.strokeWidth;
5479
+ const angle2 = config2.angle === void 0 ? DEFAULT_HATCH_ANGLE : config2.angle;
5480
+ if (Array.isArray(gap) && gap.length !== 2) {
5481
+ throw new Error(`${id}.gap must be a number or a two-element array`);
5482
+ }
5483
+ const gaps = Array.isArray(gap) ? gap : [gap, gap];
5484
+ validatePositive(strokeWidth, `${id}.strokeWidth`);
5485
+ validateNonNegative(gaps[0], `${id}.gap[0]`);
5486
+ validateNonNegative(gaps[1], `${id}.gap[1]`);
5487
+ validateAngle(angle2, `${id}.angle`);
5488
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.hatch;
5489
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = strokeWidth;
5490
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gaps[0];
5491
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gaps[1];
5492
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angle2 * DEGREES_TO_RADIANS2;
5493
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = Array.isArray(gap) ? 2 : 1;
5494
+ break;
5495
+ }
5496
+ case "cross-hatch": {
5497
+ const angles = config2.angles === void 0 ? DEFAULT_CROSS_HATCH_ANGLES : config2.angles;
5498
+ const strokeWidth = config2.strokeWidth === void 0 ? DEFAULT_SIZE : config2.strokeWidth;
5499
+ const gap = config2.gap === void 0 ? DEFAULT_GAP : config2.gap;
5500
+ if (!Array.isArray(angles) || angles.length !== 2) {
5501
+ throw new Error(`${id}.angles must be a two-element array`);
5502
+ }
5503
+ validatePositive(strokeWidth, `${id}.strokeWidth`);
5504
+ validateNonNegative(gap, `${id}.gap`);
5505
+ validateAngle(angles[0], `${id}.angles[0]`);
5506
+ validateAngle(angles[1], `${id}.angles[1]`);
5507
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.crossHatch;
5508
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = strokeWidth;
5509
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gap;
5510
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gap;
5511
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angles[0] * DEGREES_TO_RADIANS2;
5512
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle1] = angles[1] * DEGREES_TO_RADIANS2;
5513
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = 2;
5514
+ break;
5515
+ }
5516
+ case "dots": {
5517
+ const radius = config2.radius === void 0 ? DEFAULT_SIZE : config2.radius;
5518
+ const gap = config2.gap === void 0 ? DEFAULT_GAP : config2.gap;
5519
+ const angle2 = config2.angle === void 0 ? DEFAULT_DOT_ANGLE : config2.angle;
5520
+ const skew = config2.skew === void 0 ? DEFAULT_DOT_SKEW : config2.skew;
5521
+ validatePositive(radius, `${id}.radius`);
5522
+ validateNonNegative(gap, `${id}.gap`);
5523
+ validateAngle(angle2, `${id}.angle`);
5524
+ validateSkew(skew, `${id}.skew`);
5525
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.dots;
5526
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = radius;
5527
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gap;
5528
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gap;
5529
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angle2 * DEGREES_TO_RADIANS2;
5530
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle1] = skew * DEGREES_TO_RADIANS2;
5531
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = 2;
5532
+ break;
5533
+ }
5534
+ default:
5535
+ throw new Error(`${id}.type is not a supported procedural pattern type`);
5536
+ }
5537
+ });
5538
+ return {
5539
+ data,
5540
+ width: data.length / PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL,
5541
+ height: 1,
5542
+ patternIndices
5543
+ };
5544
+ }
5545
+ function validatePositive(value, name) {
5546
+ if (!Number.isFinite(value) || value <= 0) {
5547
+ throw new Error(`${name} must be a finite number greater than 0`);
5548
+ }
5549
+ }
5550
+ function validateNonNegative(value, name) {
5551
+ if (!Number.isFinite(value) || value < 0) {
5552
+ throw new Error(`${name} must be a finite number greater than or equal to 0`);
5553
+ }
5554
+ }
5555
+ function validateAngle(value, name) {
5556
+ if (!Number.isFinite(value)) {
5557
+ throw new Error(`${name} must be a finite number`);
5558
+ }
5559
+ }
5560
+ function validateSkew(value, name) {
5561
+ if (!Number.isFinite(value) || value <= -90 || value >= 90) {
5562
+ throw new Error(`${name} must be a finite number greater than -90 and less than 90`);
5563
+ }
5564
+ }
5565
+
3593
5566
  // src/fill-style/fill-style-extension.ts
5567
+ function leastCommonMultiple(a, b) {
5568
+ if (!Number.isInteger(b) || b <= 0) return 0;
5569
+ if (a === 0) return b;
5570
+ let [x, y] = [a, b];
5571
+ while (y > 0) [x, y] = [y, x % y];
5572
+ return a / x * b;
5573
+ }
3594
5574
  var defaultProps4 = {
3595
5575
  fillPatternEnabled: true,
3596
5576
  fillPatternAtlas: {
3597
5577
  type: "image",
3598
5578
  value: null,
3599
- async: true,
3600
- parameters: { lodMaxClamp: 0 }
5579
+ async: true
3601
5580
  },
3602
5581
  fillPatternMapping: { type: "object", value: {}, async: true },
3603
5582
  fillPatternMask: true,
5583
+ fillPatternSizeUnits: "meters",
3604
5584
  getFillPattern: { type: "accessor", value: (d) => d.pattern },
3605
5585
  getFillPatternScale: { type: "accessor", value: 1 },
3606
- getFillPatternOffset: { type: "accessor", value: [0, 0] }
5586
+ getFillPatternOffset: { type: "accessor", value: [0, 0] },
5587
+ getFillPatternBackgroundColor: { type: "accessor", value: [0, 0, 0, 0] }
3607
5588
  };
3608
5589
  var FillStyleExtension = class extends import_core10.LayerExtension {
3609
- constructor({ pattern = false } = {}) {
3610
- super({ pattern });
5590
+ constructor({
5591
+ pattern = false,
5592
+ proceduralPattern = false
5593
+ } = {}) {
5594
+ super({ pattern: pattern || proceduralPattern, proceduralPattern });
3611
5595
  }
3612
5596
  isEnabled(layer) {
3613
5597
  return layer.getAttributeManager() !== null && !("pathTesselator" in layer.state);
@@ -3631,7 +5615,7 @@ ${patternFs}
3631
5615
  size: 4,
3632
5616
  stepMode: "dynamic",
3633
5617
  accessor: "getFillPattern",
3634
- transform: extension.getPatternFrame.bind(this)
5618
+ transform: extension.opts.proceduralPattern ? extension.getProceduralPatternIndex.bind(this) : extension.getPatternFrame.bind(this)
3635
5619
  },
3636
5620
  fillPatternScales: {
3637
5621
  size: 1,
@@ -3643,6 +5627,13 @@ ${patternFs}
3643
5627
  size: 2,
3644
5628
  stepMode: "dynamic",
3645
5629
  accessor: "getFillPatternOffset"
5630
+ },
5631
+ fillPatternBackgroundColors: {
5632
+ size: 4,
5633
+ type: "unorm8",
5634
+ stepMode: "dynamic",
5635
+ accessor: "getFillPatternBackgroundColor",
5636
+ defaultValue: [0, 0, 0, 255]
3646
5637
  }
3647
5638
  });
3648
5639
  }
@@ -3651,7 +5642,8 @@ ${patternFs}
3651
5642
  data: new Uint8Array(4),
3652
5643
  width: 1,
3653
5644
  height: 1
3654
- })
5645
+ }),
5646
+ ...extension.opts.proceduralPattern && extension.createProceduralPatternTexture.call(this, {})
3655
5647
  });
3656
5648
  }
3657
5649
  updateState({ props, oldProps }, extension) {
@@ -3659,30 +5651,84 @@ ${patternFs}
3659
5651
  return;
3660
5652
  }
3661
5653
  if (props.fillPatternMapping && props.fillPatternMapping !== oldProps.fillPatternMapping) {
5654
+ if (extension.opts.proceduralPattern && typeof props.fillPatternMapping !== "string") {
5655
+ const oldTexture = this.state.proceduralPatternTexture;
5656
+ this.setState(
5657
+ extension.createProceduralPatternTexture.call(
5658
+ this,
5659
+ props.fillPatternMapping
5660
+ )
5661
+ );
5662
+ oldTexture?.delete();
5663
+ }
3662
5664
  this.getAttributeManager().invalidate("getFillPattern");
3663
5665
  }
5666
+ if (props.fillPatternMapping !== oldProps.fillPatternMapping || props.getFillPatternScale !== oldProps.getFillPatternScale) {
5667
+ this.setState({ commonFrame: extension.getCommonFrame(props) });
5668
+ }
3664
5669
  }
3665
5670
  draw(params, extension) {
3666
5671
  if (!extension.isEnabled(this)) {
3667
5672
  return;
3668
5673
  }
3669
- const { fillPatternAtlas, fillPatternEnabled, fillPatternMask } = this.props;
5674
+ const { fillPatternAtlas, fillPatternEnabled, fillPatternMask, fillPatternSizeUnits } = this.props;
3670
5675
  const fillProps = {
3671
5676
  project: params.shaderModuleProps.project,
3672
5677
  fillPatternEnabled,
3673
5678
  fillPatternMask,
3674
- fillPatternTexture: fillPatternAtlas || this.state.emptyTexture
5679
+ fillPatternSizeUnits,
5680
+ procedural: extension.opts.proceduralPattern,
5681
+ fillPatternTexture: extension.opts.proceduralPattern ? this.state.proceduralPatternTexture || this.state.emptyTexture : fillPatternAtlas || this.state.emptyTexture,
5682
+ fillPatternCommonFrame: this.state.commonFrame
3675
5683
  };
3676
5684
  this.setShaderModuleProps({ fill: fillProps });
3677
5685
  }
3678
5686
  finalizeState() {
3679
5687
  const emptyTexture = this.state.emptyTexture;
3680
5688
  emptyTexture?.delete();
5689
+ const proceduralPatternTexture = this.state.proceduralPatternTexture;
5690
+ proceduralPatternTexture?.delete();
3681
5691
  }
3682
5692
  getPatternFrame(name) {
3683
5693
  const { fillPatternMapping } = this.getCurrentLayer().props;
3684
5694
  const def = fillPatternMapping && fillPatternMapping[name];
3685
- return def ? [def.x, def.y, def.width, def.height] : [0, 0, 0, 0];
5695
+ return def && "x" in def ? [def.x, def.y, def.width, def.height] : [0, 0, 0, 0];
5696
+ }
5697
+ /**
5698
+ * Returns common frame into which all patterns fit as integer multiples
5699
+ */
5700
+ getCommonFrame(props) {
5701
+ const { fillPatternMapping, getFillPatternScale: scale3 } = props;
5702
+ if (typeof scale3 !== "number" || !(scale3 > 0) || typeof fillPatternMapping !== "object") {
5703
+ return null;
5704
+ }
5705
+ let width = 0;
5706
+ let height = 0;
5707
+ for (const name in fillPatternMapping) {
5708
+ const frame = fillPatternMapping[name];
5709
+ if (!("width" in frame)) return null;
5710
+ width = leastCommonMultiple(width, frame.width);
5711
+ height = leastCommonMultiple(height, frame.height);
5712
+ if (!width || !height) return null;
5713
+ }
5714
+ return width ? [scale3 * width, scale3 * height] : null;
5715
+ }
5716
+ getProceduralPatternIndex(name) {
5717
+ const patternIndices = this.state.proceduralPatternIndices;
5718
+ return [patternIndices?.get(name) ?? 0, 0, 0, 0];
5719
+ }
5720
+ createProceduralPatternTexture(mapping) {
5721
+ const packedPatterns = packProceduralPatterns(mapping);
5722
+ return {
5723
+ proceduralPatternIndices: packedPatterns.patternIndices,
5724
+ proceduralPatternTexture: this.context.device.createTexture({
5725
+ data: packedPatterns.data,
5726
+ width: packedPatterns.width,
5727
+ height: packedPatterns.height,
5728
+ format: PROCEDURAL_PATTERN_TEXTURE_FORMAT,
5729
+ sampler: { minFilter: "nearest", magFilter: "nearest" }
5730
+ })
5731
+ };
3686
5732
  }
3687
5733
  };
3688
5734
  FillStyleExtension.defaultProps = defaultProps4;
@@ -3779,6 +5825,9 @@ in vec2 clip_commonPosition;
3779
5825
  clipByInstance = Boolean(this.props.clipByInstance);
3780
5826
  }
3781
5827
  this.state.clipByInstance = clipByInstance;
5828
+ if (this.context.device.type === "webgpu") {
5829
+ return {};
5830
+ }
3782
5831
  return clipByInstance ? {
3783
5832
  modules: [shaderModuleVs],
3784
5833
  inject: injectionVs
@@ -3803,6 +5852,10 @@ in vec2 clip_commonPosition;
3803
5852
  Math.max(corner0[1], corner1[1])
3804
5853
  ];
3805
5854
  }
5855
+ if (this.context.device.type === "webgpu") {
5856
+ clipProps.enabled = true;
5857
+ clipProps.mode = this.state.clipByInstance ? "instance" : "geometry";
5858
+ }
3806
5859
  this.setShaderModuleProps({ clip: clipProps });
3807
5860
  }
3808
5861
  };
@@ -4428,12 +6481,12 @@ in vec2 mask_texCoords;
4428
6481
  if (zoom === void 0) {
4429
6482
  width = width - border * 2;
4430
6483
  height = height - border * 2;
4431
- const scale2 = Math.min(width / (bounds[2] - bounds[0]), height / (bounds[3] - bounds[1]));
4432
- zoom = Math.min(Math.log2(scale2), 20);
6484
+ const scale3 = Math.min(width / (bounds[2] - bounds[0]), height / (bounds[3] - bounds[1]));
6485
+ zoom = Math.min(Math.log2(scale3), 20);
4433
6486
  } else if (!width || !height) {
4434
- const scale2 = 2 ** zoom;
4435
- width = Math.round(Math.abs(bounds[2] - bounds[0]) * scale2);
4436
- height = Math.round(Math.abs(bounds[3] - bounds[1]) * scale2);
6487
+ const scale3 = 2 ** zoom;
6488
+ width = Math.round(Math.abs(bounds[2] - bounds[0]) * scale3);
6489
+ height = Math.round(Math.abs(bounds[3] - bounds[1]) * scale3);
4437
6490
  const maxSize = MAX_VIEWPORT_SIZE - border * 2;
4438
6491
  if (width > maxSize || height > maxSize) {
4439
6492
  const r = maxSize / Math.max(width, height);
@@ -6023,9 +8076,9 @@ if ((terrain.mode == TERRAIN_MODE_USE_COVER) || (terrain.mode == TERRAIN_MODE_US
6023
8076
  }
6024
8077
  this.bounds = bounds;
6025
8078
  this.lastViewport = viewport;
6026
- const scale2 = viewport.scale;
6027
- const pixelWidth = (bounds[2] - bounds[0]) * scale2;
6028
- const pixelHeight = (bounds[3] - bounds[1]) * scale2;
8079
+ const scale3 = viewport.scale;
8080
+ const pixelWidth = (bounds[2] - bounds[0]) * scale3;
8081
+ const pixelHeight = (bounds[3] - bounds[1]) * scale3;
6029
8082
  const centerMerc = viewport.isGeospatial ? lngLatToMercatorCommon([
6030
8083
  viewport.longitude ?? 0,
6031
8084
  viewport.latitude ?? 0