@jbrowse/render-core 5.0.0-beta.1 → 5.0.0-beta.10

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 (352) hide show
  1. package/esm/RenderCanvas.d.ts +2 -2
  2. package/esm/RenderLifecycleMixin.d.ts +52 -20
  3. package/esm/RenderLifecycleMixin.js +89 -23
  4. package/esm/canvas2dUtils.d.ts +90 -23
  5. package/esm/canvas2dUtils.js +119 -36
  6. package/esm/colorRampLut.d.ts +5 -5
  7. package/esm/colorRampLut.js +6 -6
  8. package/esm/computePipeline.d.ts +14 -0
  9. package/esm/computePipeline.js +62 -0
  10. package/esm/contractReports.d.ts +54 -0
  11. package/esm/contractReports.js +112 -0
  12. package/esm/coverageBand.d.ts +13 -8
  13. package/esm/coverageBand.js +15 -5
  14. package/esm/coverageBandBuffers.d.ts +2 -9
  15. package/esm/coverageBandBuffers.js +1 -13
  16. package/esm/createRenderingBackend.d.ts +14 -18
  17. package/esm/createRenderingBackend.js +4 -2
  18. package/esm/displayPhase.d.ts +83 -104
  19. package/esm/displayPhase.js +42 -82
  20. package/esm/encodeMemo.d.ts +14 -0
  21. package/esm/encodeMemo.js +48 -0
  22. package/esm/gpuContextLostError.d.ts +30 -0
  23. package/esm/gpuContextLostError.js +40 -0
  24. package/esm/gpuDevice.d.ts +29 -10
  25. package/esm/gpuDevice.js +35 -16
  26. package/esm/graphicsCapabilities.d.ts +27 -19
  27. package/esm/graphicsCapabilities.js +40 -18
  28. package/esm/hal/createHal.d.ts +4 -5
  29. package/esm/hal/createHal.js +14 -4
  30. package/esm/hal/deviceGpuCache.d.ts +25 -30
  31. package/esm/hal/deviceGpuCache.js +88 -97
  32. package/esm/hal/gpuHalBase.d.ts +5 -4
  33. package/esm/hal/gpuHalBase.js +25 -14
  34. package/esm/hal/index.d.ts +2 -2
  35. package/esm/hal/index.js +1 -1
  36. package/esm/hal/mockHal.d.ts +15 -10
  37. package/esm/hal/mockHal.js +31 -18
  38. package/esm/hal/oomReporter.d.ts +7 -0
  39. package/esm/hal/oomReporter.js +10 -0
  40. package/esm/hal/passIds.d.ts +8 -7
  41. package/esm/hal/passIds.js +11 -10
  42. package/esm/hal/regionRegistry.d.ts +0 -1
  43. package/esm/hal/regionRegistry.js +3 -15
  44. package/esm/hal/types.d.ts +55 -18
  45. package/esm/hal/webgl2Hal.d.ts +23 -6
  46. package/esm/hal/webgl2Hal.js +161 -155
  47. package/esm/hal/webgpuHal.d.ts +29 -13
  48. package/esm/hal/webgpuHal.js +221 -153
  49. package/esm/installUpload.d.ts +16 -3
  50. package/esm/installUpload.js +13 -41
  51. package/esm/instanceCache.d.ts +10 -7
  52. package/esm/instanceCache.js +14 -15
  53. package/esm/instancePass.d.ts +2 -1
  54. package/esm/mapUploadSync.d.ts +2 -10
  55. package/esm/mapUploadSync.js +2 -3
  56. package/esm/marks/barMark.d.ts +30 -0
  57. package/esm/marks/barMark.js +149 -0
  58. package/esm/marks/colorFill.d.ts +17 -0
  59. package/esm/marks/colorFill.js +26 -0
  60. package/esm/marks/drawAgainstHit.d.ts +113 -0
  61. package/esm/marks/drawAgainstHit.js +407 -0
  62. package/esm/marks/ellipseDistance.d.ts +10 -0
  63. package/esm/marks/ellipseDistance.js +136 -0
  64. package/esm/marks/glyphPaint.d.ts +36 -0
  65. package/esm/marks/glyphPaint.js +77 -0
  66. package/esm/marks/index.d.ts +33 -0
  67. package/esm/marks/index.js +20 -0
  68. package/esm/marks/lineMark.d.ts +37 -0
  69. package/esm/marks/lineMark.js +416 -0
  70. package/esm/marks/linkMark.d.ts +76 -0
  71. package/esm/marks/linkMark.js +585 -0
  72. package/esm/marks/markBackend.d.ts +118 -0
  73. package/esm/marks/markBackend.js +196 -0
  74. package/esm/marks/markHit.d.ts +32 -0
  75. package/esm/marks/markHit.js +61 -0
  76. package/esm/marks/markInk.d.ts +18 -0
  77. package/esm/marks/markInk.js +71 -0
  78. package/esm/marks/markPaint.d.ts +14 -0
  79. package/esm/marks/markPaint.js +32 -0
  80. package/esm/marks/markPlan.d.ts +25 -0
  81. package/esm/marks/markPlan.js +20 -0
  82. package/esm/marks/markRamp.d.ts +72 -0
  83. package/esm/marks/markRamp.js +197 -0
  84. package/esm/marks/nearestMarkHit.d.ts +55 -0
  85. package/esm/marks/nearestMarkHit.js +158 -0
  86. package/esm/marks/pathRecorder.d.ts +14 -0
  87. package/esm/marks/pathRecorder.js +80 -0
  88. package/esm/marks/pointMark.d.ts +42 -0
  89. package/esm/marks/pointMark.js +125 -0
  90. package/esm/marks/rowLane.d.ts +39 -0
  91. package/esm/marks/rowLane.js +41 -0
  92. package/esm/marks/rowSpanIndex.d.ts +29 -0
  93. package/esm/marks/rowSpanIndex.js +141 -0
  94. package/esm/marks/rowTable.d.ts +41 -0
  95. package/esm/marks/rowTable.js +75 -0
  96. package/esm/marks/ruleMark.d.ts +21 -0
  97. package/esm/marks/ruleMark.js +91 -0
  98. package/esm/marks/spanMark.d.ts +42 -0
  99. package/esm/marks/spanMark.js +81 -0
  100. package/esm/marks/types.d.ts +261 -0
  101. package/esm/marks/types.js +175 -0
  102. package/esm/marks/valueScale.d.ts +23 -0
  103. package/esm/marks/valueScale.js +11 -0
  104. package/esm/namedReactions.d.ts +3 -4
  105. package/esm/namedReactions.js +3 -4
  106. package/esm/perRegionRenderingBackend.d.ts +21 -3
  107. package/esm/perRegionRenderingBackend.js +37 -3
  108. package/esm/regionDataMap.d.ts +2 -2
  109. package/esm/regionDataMap.js +25 -23
  110. package/esm/renderBlock.d.ts +14 -0
  111. package/esm/renderBlock.js +25 -0
  112. package/esm/renderingBackendBase.d.ts +20 -14
  113. package/esm/renderingBackendBase.js +12 -7
  114. package/esm/scoreScale.d.ts +27 -0
  115. package/esm/scoreScale.js +88 -0
  116. package/esm/shaders/barMark.generated.d.ts +3 -0
  117. package/esm/shaders/barMark.generated.js +7 -0
  118. package/esm/shaders/barMark.glsl.generated.d.ts +2 -0
  119. package/esm/shaders/barMark.glsl.generated.js +4 -0
  120. package/esm/shaders/barMark.iface.generated.d.ts +90 -0
  121. package/esm/shaders/barMark.iface.generated.js +205 -0
  122. package/esm/shaders/barMark.wgsl.generated.d.ts +1 -0
  123. package/esm/shaders/barMark.wgsl.generated.js +3 -0
  124. package/esm/shaders/capsule.js.generated.d.ts +1 -0
  125. package/esm/shaders/capsule.js.generated.js +11 -0
  126. package/esm/shaders/clipStrip.generated.d.ts +2 -0
  127. package/esm/shaders/clipStrip.generated.js +4 -0
  128. package/esm/shaders/clipStrip.js.generated.d.ts +1 -0
  129. package/esm/shaders/clipStrip.js.generated.js +21 -0
  130. package/esm/shaders/colorRampLut.js.generated.d.ts +1 -0
  131. package/esm/shaders/colorRampLut.js.generated.js +17 -0
  132. package/esm/shaders/coverageBar.generated.d.ts +2 -3
  133. package/esm/shaders/coverageBar.generated.js +4 -3
  134. package/esm/shaders/coverageBar.glsl.generated.d.ts +2 -0
  135. package/esm/shaders/coverageBar.glsl.generated.js +4 -0
  136. package/esm/shaders/coverageBar.iface.generated.js +1 -1
  137. package/esm/shaders/coverageBar.wgsl.generated.d.ts +1 -0
  138. package/esm/shaders/coverageBar.wgsl.generated.js +3 -0
  139. package/esm/shaders/coverageClip.generated.d.ts +3 -0
  140. package/esm/shaders/coverageClip.generated.js +7 -0
  141. package/esm/shaders/coverageClip.glsl.generated.d.ts +2 -0
  142. package/esm/shaders/coverageClip.glsl.generated.js +4 -0
  143. package/esm/shaders/coverageClip.iface.generated.d.ts +85 -0
  144. package/esm/shaders/coverageClip.iface.generated.js +114 -0
  145. package/esm/shaders/coverageClip.wgsl.generated.d.ts +1 -0
  146. package/esm/shaders/coverageClip.wgsl.generated.js +3 -0
  147. package/esm/shaders/coverageIndicator.generated.d.ts +2 -3
  148. package/esm/shaders/coverageIndicator.generated.js +4 -3
  149. package/esm/shaders/coverageIndicator.glsl.generated.d.ts +2 -0
  150. package/esm/shaders/coverageIndicator.glsl.generated.js +4 -0
  151. package/esm/shaders/coverageIndicator.iface.generated.js +1 -1
  152. package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +1 -0
  153. package/esm/shaders/coverageIndicator.wgsl.generated.js +3 -0
  154. package/esm/shaders/coverageInterbase.generated.d.ts +2 -3
  155. package/esm/shaders/coverageInterbase.generated.js +4 -3
  156. package/esm/shaders/coverageInterbase.glsl.generated.d.ts +2 -0
  157. package/esm/shaders/coverageInterbase.glsl.generated.js +4 -0
  158. package/esm/shaders/coverageInterbase.iface.generated.js +1 -1
  159. package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -0
  160. package/esm/shaders/coverageInterbase.wgsl.generated.js +3 -0
  161. package/esm/shaders/coverageMod.generated.d.ts +2 -3
  162. package/esm/shaders/coverageMod.generated.js +4 -3
  163. package/esm/shaders/coverageMod.glsl.generated.d.ts +2 -0
  164. package/esm/shaders/coverageMod.glsl.generated.js +4 -0
  165. package/esm/shaders/coverageMod.iface.generated.js +1 -1
  166. package/esm/shaders/coverageMod.wgsl.generated.d.ts +1 -0
  167. package/esm/shaders/coverageMod.wgsl.generated.js +3 -0
  168. package/esm/shaders/coverageSnp.generated.d.ts +2 -3
  169. package/esm/shaders/coverageSnp.generated.js +4 -3
  170. package/esm/shaders/coverageSnp.glsl.generated.d.ts +2 -0
  171. package/esm/shaders/coverageSnp.glsl.generated.js +4 -0
  172. package/esm/shaders/coverageSnp.iface.generated.js +1 -1
  173. package/esm/shaders/coverageSnp.wgsl.generated.d.ts +1 -0
  174. package/esm/shaders/coverageSnp.wgsl.generated.js +3 -0
  175. package/esm/shaders/curveDistance.js.generated.d.ts +5 -0
  176. package/esm/shaders/curveDistance.js.generated.js +87 -0
  177. package/esm/shaders/hpmath.js.generated.d.ts +2 -1
  178. package/esm/shaders/hpmath.js.generated.js +5 -2
  179. package/esm/shaders/lineCenterMark.generated.d.ts +3 -0
  180. package/esm/shaders/lineCenterMark.generated.js +7 -0
  181. package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -0
  182. package/esm/shaders/lineCenterMark.glsl.generated.js +4 -0
  183. package/esm/shaders/lineCenterMark.iface.generated.d.ts +104 -0
  184. package/esm/shaders/lineCenterMark.iface.generated.js +237 -0
  185. package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -0
  186. package/esm/shaders/lineCenterMark.wgsl.generated.js +3 -0
  187. package/esm/shaders/lineCommon.generated.d.ts +2 -0
  188. package/esm/shaders/lineCommon.generated.js +4 -0
  189. package/esm/shaders/lineStepMark.generated.d.ts +3 -0
  190. package/esm/shaders/lineStepMark.generated.js +7 -0
  191. package/esm/shaders/lineStepMark.glsl.generated.d.ts +2 -0
  192. package/esm/shaders/lineStepMark.glsl.generated.js +4 -0
  193. package/esm/shaders/lineStepMark.iface.generated.d.ts +98 -0
  194. package/esm/shaders/lineStepMark.iface.generated.js +225 -0
  195. package/esm/shaders/lineStepMark.wgsl.generated.d.ts +1 -0
  196. package/esm/shaders/lineStepMark.wgsl.generated.js +3 -0
  197. package/esm/shaders/linkMark.consts.generated.d.ts +14 -0
  198. package/esm/shaders/linkMark.consts.generated.js +16 -0
  199. package/esm/shaders/linkMark.generated.d.ts +4 -0
  200. package/esm/shaders/linkMark.generated.js +8 -0
  201. package/esm/shaders/linkMark.glsl.generated.d.ts +2 -0
  202. package/esm/shaders/linkMark.glsl.generated.js +4 -0
  203. package/esm/shaders/linkMark.iface.generated.d.ts +142 -0
  204. package/esm/shaders/linkMark.iface.generated.js +1829 -0
  205. package/esm/shaders/linkMark.js.generated.d.ts +7 -0
  206. package/esm/shaders/linkMark.js.generated.js +159 -0
  207. package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -0
  208. package/esm/shaders/linkMark.wgsl.generated.js +3 -0
  209. package/esm/shaders/markColor.generated.d.ts +9 -0
  210. package/esm/shaders/markColor.generated.js +11 -0
  211. package/esm/shaders/pointGlyph.js.generated.d.ts +2 -0
  212. package/esm/shaders/pointGlyph.js.generated.js +15 -0
  213. package/esm/shaders/pointMark.consts.generated.d.ts +7 -0
  214. package/esm/shaders/pointMark.consts.generated.js +9 -0
  215. package/esm/shaders/pointMark.generated.d.ts +4 -0
  216. package/esm/shaders/pointMark.generated.js +8 -0
  217. package/esm/shaders/pointMark.glsl.generated.d.ts +2 -0
  218. package/esm/shaders/pointMark.glsl.generated.js +4 -0
  219. package/esm/shaders/pointMark.iface.generated.d.ts +98 -0
  220. package/esm/shaders/pointMark.iface.generated.js +219 -0
  221. package/esm/shaders/pointMark.js.generated.d.ts +3 -0
  222. package/esm/shaders/pointMark.js.generated.js +93 -0
  223. package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -0
  224. package/esm/shaders/pointMark.wgsl.generated.js +3 -0
  225. package/esm/shaders/rowRect.generated.d.ts +0 -1
  226. package/esm/shaders/rowRect.generated.js +0 -1
  227. package/esm/shaders/rowTable.generated.d.ts +1 -0
  228. package/esm/shaders/rowTable.generated.js +3 -0
  229. package/esm/shaders/rowTable.js.generated.d.ts +5 -0
  230. package/esm/shaders/rowTable.js.generated.js +29 -0
  231. package/esm/shaders/scoreScale.generated.d.ts +2 -0
  232. package/esm/shaders/scoreScale.generated.js +4 -0
  233. package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
  234. package/esm/shaders/scoreScale.js.generated.js +50 -23
  235. package/esm/shaders/spanMark.generated.d.ts +3 -0
  236. package/esm/shaders/spanMark.generated.js +7 -0
  237. package/esm/shaders/spanMark.glsl.generated.d.ts +2 -0
  238. package/esm/shaders/spanMark.glsl.generated.js +4 -0
  239. package/esm/shaders/spanMark.iface.generated.d.ts +81 -0
  240. package/esm/shaders/spanMark.iface.generated.js +215 -0
  241. package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -0
  242. package/esm/shaders/spanMark.wgsl.generated.js +3 -0
  243. package/esm/sharedBackendKey.d.ts +18 -0
  244. package/esm/{keyedRenderingBackend.js → sharedBackendKey.js} +5 -0
  245. package/esm/slangPass.d.ts +18 -11
  246. package/esm/slangPass.js +12 -4
  247. package/esm/useGraphicsCapabilities.d.ts +1 -1
  248. package/esm/useGraphicsCapabilities.js +1 -1
  249. package/esm/useRenderingBackend.d.ts +13 -13
  250. package/esm/useRenderingBackend.js +58 -20
  251. package/esm/webgpuUtils.d.ts +3 -11
  252. package/esm/webgpuUtils.js +12 -33
  253. package/package.json +32 -7
  254. package/src/shaders/antialias.slang +46 -0
  255. package/src/shaders/arcHull.test.ts +267 -0
  256. package/src/shaders/barMark.generated.ts +11 -0
  257. package/src/shaders/barMark.glsl.generated.ts +6 -0
  258. package/src/shaders/barMark.iface.generated.ts +285 -0
  259. package/src/shaders/barMark.slang +167 -0
  260. package/src/shaders/barMark.wgsl.generated.ts +4 -0
  261. package/src/shaders/capsule.js.generated.ts +14 -0
  262. package/src/shaders/capsule.slang +12 -4
  263. package/src/shaders/clipStrip.generated.ts +6 -0
  264. package/src/shaders/clipStrip.js.generated.ts +22 -0
  265. package/src/shaders/clipStrip.slang +34 -0
  266. package/src/shaders/clipStrip.test.ts +39 -0
  267. package/src/shaders/colorPack.slang +5 -1
  268. package/src/shaders/colorRampLut.js.generated.ts +22 -0
  269. package/src/shaders/colorRampLut.slang +15 -1
  270. package/src/shaders/coverageBand.slang +64 -32
  271. package/src/shaders/coverageBar.generated.ts +6 -5
  272. package/src/shaders/coverageBar.glsl.generated.ts +6 -0
  273. package/src/shaders/coverageBar.iface.generated.ts +1 -1
  274. package/src/shaders/coverageBar.slang +10 -10
  275. package/src/shaders/coverageBar.wgsl.generated.ts +4 -0
  276. package/src/shaders/coverageClip.generated.ts +11 -0
  277. package/src/shaders/coverageClip.glsl.generated.ts +6 -0
  278. package/src/shaders/coverageClip.iface.generated.ts +172 -0
  279. package/src/shaders/coverageClip.slang +38 -0
  280. package/src/shaders/coverageClip.wgsl.generated.ts +4 -0
  281. package/src/shaders/coverageIndicator.generated.ts +6 -5
  282. package/src/shaders/coverageIndicator.glsl.generated.ts +6 -0
  283. package/src/shaders/coverageIndicator.iface.generated.ts +1 -1
  284. package/src/shaders/coverageIndicator.wgsl.generated.ts +4 -0
  285. package/src/shaders/coverageInterbase.generated.ts +6 -5
  286. package/src/shaders/coverageInterbase.glsl.generated.ts +6 -0
  287. package/src/shaders/coverageInterbase.iface.generated.ts +1 -1
  288. package/src/shaders/coverageInterbase.wgsl.generated.ts +4 -0
  289. package/src/shaders/coverageMod.generated.ts +6 -5
  290. package/src/shaders/coverageMod.glsl.generated.ts +6 -0
  291. package/src/shaders/coverageMod.iface.generated.ts +1 -1
  292. package/src/shaders/coverageMod.wgsl.generated.ts +4 -0
  293. package/src/shaders/coverageSnp.generated.ts +6 -5
  294. package/src/shaders/coverageSnp.glsl.generated.ts +6 -0
  295. package/src/shaders/coverageSnp.iface.generated.ts +1 -1
  296. package/src/shaders/coverageSnp.wgsl.generated.ts +4 -0
  297. package/src/shaders/curveDistance.js.generated.ts +91 -0
  298. package/src/shaders/curveDistance.slang +265 -0
  299. package/src/shaders/hpClampFloor.test.ts +87 -0
  300. package/src/shaders/hpmath.js.generated.ts +6 -2
  301. package/src/shaders/hpmath.slang +100 -48
  302. package/src/shaders/insetValueScale.test.ts +67 -0
  303. package/src/shaders/lineCenterMark.generated.ts +11 -0
  304. package/src/shaders/lineCenterMark.glsl.generated.ts +6 -0
  305. package/src/shaders/lineCenterMark.iface.generated.ts +328 -0
  306. package/src/shaders/lineCenterMark.slang +127 -0
  307. package/src/shaders/lineCenterMark.wgsl.generated.ts +4 -0
  308. package/src/shaders/lineCommon.generated.ts +6 -0
  309. package/src/shaders/lineCommon.slang +102 -0
  310. package/src/shaders/lineStepMark.generated.ts +11 -0
  311. package/src/shaders/lineStepMark.glsl.generated.ts +6 -0
  312. package/src/shaders/lineStepMark.iface.generated.ts +311 -0
  313. package/src/shaders/lineStepMark.slang +125 -0
  314. package/src/shaders/lineStepMark.wgsl.generated.ts +4 -0
  315. package/src/shaders/linkMark.consts.generated.ts +30 -0
  316. package/src/shaders/linkMark.generated.ts +12 -0
  317. package/src/shaders/linkMark.glsl.generated.ts +6 -0
  318. package/src/shaders/linkMark.iface.generated.ts +1954 -0
  319. package/src/shaders/linkMark.js.generated.ts +167 -0
  320. package/src/shaders/linkMark.slang +439 -0
  321. package/src/shaders/linkMark.wgsl.generated.ts +4 -0
  322. package/src/shaders/markColor.generated.ts +20 -0
  323. package/src/shaders/markColor.slang +128 -0
  324. package/src/shaders/pointGlyph.js.generated.ts +20 -0
  325. package/src/shaders/pointGlyph.slang +23 -7
  326. package/src/shaders/pointGlyphPad.test.ts +242 -0
  327. package/src/shaders/pointMark.consts.generated.ts +16 -0
  328. package/src/shaders/pointMark.generated.ts +12 -0
  329. package/src/shaders/pointMark.glsl.generated.ts +6 -0
  330. package/src/shaders/pointMark.iface.generated.ts +304 -0
  331. package/src/shaders/pointMark.js.generated.ts +99 -0
  332. package/src/shaders/pointMark.slang +227 -0
  333. package/src/shaders/pointMark.wgsl.generated.ts +4 -0
  334. package/src/shaders/rowRect.generated.ts +0 -2
  335. package/src/shaders/rowRect.slang +39 -58
  336. package/src/shaders/rowTable.generated.ts +4 -0
  337. package/src/shaders/rowTable.js.generated.ts +37 -0
  338. package/src/shaders/rowTable.slang +98 -0
  339. package/src/shaders/scoreScale.generated.ts +6 -0
  340. package/src/shaders/scoreScale.js.generated.ts +48 -23
  341. package/src/shaders/scoreScale.slang +49 -55
  342. package/src/shaders/sdEllipse.test.ts +211 -0
  343. package/src/shaders/spanMark.generated.ts +11 -0
  344. package/src/shaders/spanMark.glsl.generated.ts +6 -0
  345. package/src/shaders/spanMark.iface.generated.ts +289 -0
  346. package/src/shaders/spanMark.slang +77 -0
  347. package/src/shaders/spanMark.wgsl.generated.ts +4 -0
  348. package/src/shaders/valueScale.slang +26 -0
  349. package/src/shaders/wideCircleLeg.test.ts +85 -0
  350. package/esm/globalRenderingBackend.d.ts +0 -90
  351. package/esm/globalRenderingBackend.js +0 -53
  352. package/esm/keyedRenderingBackend.d.ts +0 -41
@@ -0,0 +1,265 @@
1
+ // Exact distances to the two curves a stroked arc is measured against - an
2
+ // axis-aligned ellipse, and a circle whose radius dwarfs the band it is
3
+ // clipped to - and the hull vertices that have to clear them. Shared by the
4
+ // alignments arc band and the link mark, which draw the same half-ellipse and
5
+ // degenerate it to the same circle.
6
+ //
7
+ // The fragment ramps its alpha to zero at `halfWidth + aaHalfPx`, so the hull
8
+ // has to cover everything nearer the curve than that. Both flanks of a hull
9
+ // sampled from the offset curve fall short of it at a clamped dome's feet.
10
+ //! js-export: distToWideCirclePx, wideCircleLeg, wideCircleLegStep, ellipseHullPoint, wideCircleHullPoint
11
+ //! js-skip: nearCircleDistancePx — a float32 remedy with no float64 caller. The hit tests' copy of this file's solve (marks/ellipseDistance.ts) holds the ellipse at every aspect, so nothing outside the shader has an approximation to want.
12
+ //! oracle-skip: ellipseHullPoint — a miter between near-parallel tangent lines, so float32 and float64 part on the probe's arbitrary directions and radii; over the angles the hull steps through they agree to 0.004 px, and arcHull.test.ts referees the twin against every inked point instead
13
+ //! oracle-skip: wideCircleHullPoint — the foot-relative frame cancels at half steps and angles the band never reaches; arcHull.test.ts referees it with the legs
14
+
15
+ module curveDistance;
16
+
17
+ // How near a circle an ellipse has to be before `sdEllipse` stops solving and
18
+ // measures instead: radii within ~2.5%, as |ay² - ax²| against ay². The two
19
+ // formulas' worst errors over a 6px stroke meet here at ~0.01px — below it the
20
+ // solve degrades with radius (0.10px at r=4000), above it the measure degrades
21
+ // with curvature (0.01px at r=4).
22
+ static const float ELLIPSE_CIRCLE_TOL = 0.05;
23
+
24
+ // The distance to an ellipse the solve cannot reach. Every term in it divides
25
+ // by `l = ay² - ax²`, a difference of two nearly equal squares, so float32 has
26
+ // lost the ellipse before the quartic is assembled: at 250px radii 1e-5 apart
27
+ // the solve is 3.6px out and 36% of the stroke's samples flip. Not a corner —
28
+ // a clamped dome pins ry to the band while rx tracks the pair's width, so a
29
+ // zoom walks rx through ry and through every ratio on the way.
30
+ //
31
+ // The implicit field |p/ab| - 1 over its own gradient instead: exact on a true
32
+ // circle, which is why it replaces the exact-circle branch rather than sitting
33
+ // beside it, and first-order elsewhere.
34
+ float nearCircleDistancePx(float qx, float qy, float ax, float ay) {
35
+ float kx = qx / ax;
36
+ float ky = qy / ay;
37
+ float k = sqrt(kx * kx + ky * ky);
38
+ float gx = qx / (ax * ax);
39
+ float gy = qy / (ay * ay);
40
+ return abs(k * (k - 1.0) / max(sqrt(gx * gx + gy * gy), 1e-20));
41
+ }
42
+
43
+ // Inigo Quilez's closed-form solve of the quartic. Chosen over the iterative
44
+ // alternatives because the arc band produces very flat ellipses (a 25px band
45
+ // under a 1500px pair is 44:1, and it goes past 88:1): measured against an
46
+ // fp64 reference at fp32 precision, this stays within 0.031px out to 88:1,
47
+ // where a seeded Newton on the parametric angle runs away entirely.
48
+ //
49
+ // THAT MEASUREMENT IS THE FLAT END, and the solve fails at the round one, in
50
+ // float32 alone — the same source in float64 is exact to 1e-9. It breaks the
51
+ // picture rather than nudging it, because the stroke reads this through a
52
+ // half-pixel ramp: a dome at 380x249px drew as scattered dots on WebGL where
53
+ // Canvas2D drew a line. Two guards below, each where its failure arises.
54
+ //
55
+ // NOT `//! js-export`ed, and the hit tests carry a float64 copy of this formula
56
+ // (render-core marks/ellipseDistance.ts) BECAUSE of that. The reasoning is
57
+ // worth keeping, since "lift it like linkRadiiPx" is the obvious first move and
58
+ // it was tried:
59
+ //
60
+ // - The emitter refuses float2 params, so lifting means splitting a scalar
61
+ // core out (the ADR-051 step-1 pattern). Necessary but NOT sufficient, and
62
+ // the shortfall is SILENT: the core still calls acos/cos/sin, none of which
63
+ // are in the emitter's MATH_BUILTINS, so the emitter refuses it — and
64
+ // check-oracle's `emittableOf` DROPS a refused candidate from the sweep
65
+ // rather than failing on it. Do the split alone and the oracle stays green
66
+ // on an unchanged comparison count, which reads as "the problem went away".
67
+ // It has not; nothing has been checked.
68
+ // - Add those three builtins and it does enter the sweep — which takes every
69
+ // declared function with an emittable scalar signature, not only the
70
+ // `//! js-export`ed ones, and `//! js-skip` does not exempt one either. Then
71
+ // it fails, at the oracle's 1e-5 relative: 90 of the 400 tuples, 42 of them
72
+ // on inputs with both radii positive (the sweep also generates negative
73
+ // radii, which `linkRadiiPx` does not produce).
74
+ // - And NOT because the twin is wrong. On every one of those 42 the float64
75
+ // twin matches a brute-force minimum over the parametric angle to 1.1e-6
76
+ // relative — inside the oracle's own tolerance — and slangc's float32 is the
77
+ // side that has drifted: `(1.5, 0, 65536, 3)` is 3.0, where the C++ answers
78
+ // 3.3541019. float32 loses the solver, float64 does not.
79
+ // - So the oracle cannot referee this function at any tolerance it could keep
80
+ // for the rest of the tree, the same way it cannot referee `mix`/`step`
81
+ // (SHADER_JS_CODEGEN.md). Exporting it would have meant a suppression in
82
+ // the strongest gate here, to share a NUMERICAL METHOD rather than a
83
+ // decision.
84
+ //
85
+ // What actually has to agree is WHICH ELLIPSE, and that is the radii pair
86
+ // `linkMark.slang` `//! js-export`s (`linkRadiiPx`), which is what its hit test
87
+ // reads. Given the same two radii, the distance to them is geometry with
88
+ // one right answer, and the copy is pinned against a brute-force reference
89
+ // rather than against this (see ellipseDistance.test.ts), which is the
90
+ // stronger check of the two.
91
+ public float sdEllipse(float2 p, float2 radii) {
92
+ float2 q = abs(p);
93
+ float2 ab = radii;
94
+ if (q.x > q.y) { q = q.yx; ab = ab.yx; }
95
+ // Both radii zero is the one shape the near-circle test below cannot catch:
96
+ // its own threshold is `ab.y * ab.y`, so it reads `abs(l) < 0` and lets a
97
+ // zero `l` through to divide. A link pair whose two ends land on the same
98
+ // pixel reaches it (`linkRadiiPx` of a zero half-width), and the ellipse is
99
+ // then the point at the origin.
100
+ if (dot(ab, ab) <= 0.0) { return length(q); }
101
+ float l = ab.y * ab.y - ab.x * ab.x;
102
+ if (abs(l) < ELLIPSE_CIRCLE_TOL * ab.y * ab.y) {
103
+ return nearCircleDistancePx(q.x, q.y, ab.x, ab.y);
104
+ }
105
+ float m = ab.x * q.x / l; float m2 = m * m;
106
+ float n = ab.y * q.y / l; float n2 = n * n;
107
+ float c = (m2 + n2 - 1.0) / 3.0;
108
+ float c3 = c * c * c;
109
+ float d = c3 + m2 * n2;
110
+ float g = m + m * n2;
111
+ float co;
112
+ if (d < 0.0) {
113
+ float h = acos(clamp((c3 + m2 * n2 * 2.0) / c3, -1.0, 1.0)) / 3.0;
114
+ float s = cos(h);
115
+ float t = sin(h) * sqrt(3.0);
116
+ float rx = sqrt(max(-c * (s + t + 2.0) + m2, 0.0));
117
+ float ry = sqrt(max(-c * (s - t + 2.0) + m2, 0.0));
118
+ co = (ry + sign(l) * rx + abs(g) / max(rx * ry, 1e-20) - m) / 2.0;
119
+ } else {
120
+ float qq = c3 + m2 * n2 * 2.0;
121
+ float h = 2.0 * m * n * sqrt(d);
122
+ // The two cube roots are of `qq + h` and `qq - h`, whose product is c3*c3
123
+ // identically. So the lower one comes from that quotient rather than from
124
+ // the subtraction, which cancels to nothing exactly where the dome lives:
125
+ // at 380x249px the two agree to every float32 digit they have, `qq - h`
126
+ // rounds to zero, and the distance lands up to 3px off the curve.
127
+ float w = qq + h;
128
+ float aw = abs(w);
129
+ float s = sign(w) * pow(aw, 1.0 / 3.0);
130
+ float t = aw > 0.0 ? sign(w) * pow(c3 * (c3 / aw), 1.0 / 3.0) : 0.0;
131
+ float rx = -s - t - c * 4.0 + 2.0 * m2;
132
+ float ry = (s - t) * sqrt(3.0);
133
+ float rm = sqrt(rx * rx + ry * ry);
134
+ co = (ry / sqrt(max(rm - rx, 1e-20)) + 2.0 * g / max(rm, 1e-20) - m) / 2.0;
135
+ }
136
+ co = clamp(co, -1.0, 1.0);
137
+ float2 r = ab * float2(co, sqrt(max(1.0 - co * co, 0.0)));
138
+ return length(r - q);
139
+ }
140
+
141
+ // Distance from a point to a circle whose radius dwarfs the band it is clipped
142
+ // to — the far-pair case, where r reaches millions of px. `legLocal` is the
143
+ // point relative to the endpoint the circle meets the baseline at, x positive
144
+ // pointing AWAY from the circle.
145
+ //
146
+ // Exact, but written so nothing cancels: the centre sits at (-r, 0) in that
147
+ // frame, so |p-c|² = r² + 2r·x + x² + y² identically and
148
+ // |p-c| - r = rk / (sqrt(1+k) + 1) with both `rk` and `k` assembled from small
149
+ // terms. Evaluating `length(p - c) - r`, or even recovering x as
150
+ // `abs(p.x) - r` from a centre-relative point, loses every significant digit
151
+ // past r ≈ 1e6 px.
152
+ //
153
+ // Scalar core, `//! js-export`ed for the hit tests — and for the same reason
154
+ // it is written this way at all: a hit test asking "is the cursor on this leg"
155
+ // from `length(p - c) - r` would lose every significant digit exactly where
156
+ // far pairs live.
157
+ public float distToWideCirclePx(float x, float y, float r) {
158
+ float rk = 2.0 * x + (x * x + y * y) / r;
159
+ return abs(rk / (sqrt(max(1.0 + rk / r, 0.0)) + 1.0));
160
+ }
161
+
162
+ public float distToWideCircle(float2 legLocal, float r) {
163
+ return distToWideCirclePx(legLocal.x, legLocal.y, r);
164
+ }
165
+
166
+ // How far around a far circle each leg's hull sweeps, in radians from its foot:
167
+ // to the band's edge and no further. Sweeping the whole quarter spends nearly
168
+ // every segment off-screen, and at r = 3e6 px the whole band then falls inside
169
+ // the first chord, which recedes from the near-vertical curve at a rate set by
170
+ // the angle per segment — so the leg draws as a tapering triangle however many
171
+ // segments there are. Clamped, so a circle small enough to apex inside the band
172
+ // still gets its full quarter and draws as a dome.
173
+ public float legSweepAngle(float radiusPx, float yReachPx) {
174
+ return asin(clamp(yReachPx / max(radiusPx, 1e-6), 0.0, 1.0));
175
+ }
176
+
177
+ // Which leg vertex pair `seg` of a `segments`-long strip builds (x: +1 the right
178
+ // leg, -1 the left), and its angle from that leg's foot (y). The strip has
179
+ // `segments + 1` pairs, one more than either leg needs, and at an even
180
+ // `segments` the right leg takes it. Each leg is spread over ITS OWN count, so
181
+ // both reach `legSweep` — and both do so in equal angular steps, which is what
182
+ // puts a chord's sagitta below a pixel everywhere the band can show. The quad
183
+ // between the two tips is the seam the fragment discards.
184
+ //
185
+ // `segments` is at least 3; below that the left leg is a single pair and has no
186
+ // span to spread. Both callers pass a compile-time 64.
187
+ public float2 wideCircleLeg(uint seg, uint segments, float legSweep) {
188
+ uint half = segments / 2u;
189
+ if (seg <= half) {
190
+ return float2(1.0, float(seg) / float(half) * legSweep);
191
+ }
192
+ return float2(-1.0,
193
+ float(segments - seg) / float(segments - half - 1u) * legSweep);
194
+ }
195
+
196
+ // The angle between neighbouring vertex pairs on `seg`'s leg, which is not the
197
+ // same on both legs.
198
+ public float wideCircleLegStep(uint seg, uint segments, float legSweep) {
199
+ uint half = segments / 2u;
200
+ return legSweep / float(seg <= half ? half : segments - half - 1u);
201
+ }
202
+
203
+ // One hull vertex at parametric angle `a` for a hull stepping `2 * halfStep`,
204
+ // `padPx` from the half-ellipse: positive on the outer flank, negative on the
205
+ // inner.
206
+ //
207
+ // Outside, the offset region is convex, so the vertex is where the offset
208
+ // tangent lines at `a` +/- the half step meet: `p(a) * sec(halfStep)` plus the
209
+ // miter `padPx * (n1 + n2) / (1 + n1 . n2)`. Chords through points on the
210
+ // offset curve would sag inside it. The divisor's floor keeps a collapsed band
211
+ // (ry 0, the foot's normals opposed) finite.
212
+ //
213
+ // Inside, a chord between offset points already stays clear of the ink, until
214
+ // the curve turns tighter than the pad and the offset folds past an axis. A
215
+ // folded vertex moves to where the fold crosses that axis, the one point there
216
+ // exactly the pad from the curve.
217
+ public float2 ellipseHullPoint(float cosA, float sinA, float cosHalf,
218
+ float sinHalf, float rx, float ry, float padPx) {
219
+ float px = cosA * rx;
220
+ float py = sinA * ry;
221
+ if (padPx <= 0.0) {
222
+ float d = -padPx;
223
+ float gx = ry * cosA;
224
+ float gy = rx * sinA;
225
+ float glen = sqrt(gx * gx + gy * gy);
226
+ bool acrossX = ry * glen < d * rx;
227
+ bool acrossY = rx * glen < d * ry;
228
+ if (acrossX && acrossY) { return float2(0.0, 0.0); }
229
+ if (acrossX) {
230
+ float foldX = sqrt(max(ry * ry - d * d, 0.0) * max(rx * rx - ry * ry, 0.0));
231
+ return float2(sign(cosA) * foldX / max(ry, 1e-6), 0.0);
232
+ }
233
+ if (acrossY) {
234
+ float foldY = sqrt(max(rx * rx - d * d, 0.0) * max(ry * ry - rx * rx, 0.0));
235
+ return float2(0.0, sign(sinA) * foldY / max(rx, 1e-6));
236
+ }
237
+ float k = d / max(glen, 1e-6);
238
+ return float2(px - gx * k, py - gy * k);
239
+ }
240
+ float ax = ry * (cosA * cosHalf + sinA * sinHalf);
241
+ float ay = rx * (sinA * cosHalf - cosA * sinHalf);
242
+ float bx = ry * (cosA * cosHalf - sinA * sinHalf);
243
+ float by = rx * (sinA * cosHalf + cosA * sinHalf);
244
+ float al = max(sqrt(ax * ax + ay * ay), 1e-20);
245
+ float bl = max(sqrt(bx * bx + by * by), 1e-20);
246
+ float nx = ax / al + bx / bl;
247
+ float ny = ay / al + by / bl;
248
+ float m = 2.0 * padPx / max(nx * nx + ny * ny, 2e-4);
249
+ return float2(px / cosHalf + nx * m, py / cosHalf + ny * m);
250
+ }
251
+
252
+ // The same vertex for a far pair's leg, in the foot-relative frame
253
+ // `distToWideCirclePx` measures in. A circle's normals are radial, so the outer
254
+ // flank scales the offset radius by `sec(halfStep)` and the inner one takes the
255
+ // offset point as it is. The vertex's standoff from the circle is carried as
256
+ // `grow` rather than recovered from `r * cos(b) - r`, which cancels at the radii
257
+ // far pairs reach.
258
+ public float2 wideCircleHullPoint(float sinB, float sinHalfB, float cosHalf,
259
+ float radiusPx, float padPx) {
260
+ float sec = padPx > 0.0 ? 1.0 / max(cosHalf, 1e-6) : 1.0;
261
+ float grow = radiusPx * (sec - 1.0) + sec * padPx;
262
+ float sh2 = sinHalfB * sinHalfB;
263
+ return float2(grow * (1.0 - 2.0 * sh2) - 2.0 * radiusPx * sh2,
264
+ (radiusPx + grow) * sinB);
265
+ }
@@ -0,0 +1,87 @@
1
+ // `hpToClipX` and `hpScaleLinear` clamp each half of the hi/lo split to a floor
2
+ // nothing can reach, and the clamp is there so no backend folds the split into
3
+ // one large subtraction. The floor used to be `-1.0 / hpZero`, and `hpZero` is
4
+ // uploaded as exactly 0 — a division by zero in every emitted backend, which
5
+ // WGSL lets an implementation answer with an indeterminate value rather than an
6
+ // infinity. (`coverageBandMarks.test.ts`, `coverageBandUboParity.test.ts` and
7
+ // maf's `coverageBandDraw.test.ts` are what hold the uploaded value at 0.)
8
+ //
9
+ // So: the floor must be a finite literal, the two clamps must survive, and
10
+ // `hpZero` must reach neither as a divisor.
11
+ import { GLSL_VERTEX as barMarkGlsl } from './barMark.glsl.generated.ts'
12
+ import { WGSL_SOURCE as barMarkWgsl } from './barMark.wgsl.generated.ts'
13
+ import { GLSL_VERTEX as coverageBarGlsl } from './coverageBar.glsl.generated.ts'
14
+ import { WGSL_SOURCE as coverageBarWgsl } from './coverageBar.wgsl.generated.ts'
15
+ import { GLSL_VERTEX as coverageIndicatorGlsl } from './coverageIndicator.glsl.generated.ts'
16
+ import { WGSL_SOURCE as coverageIndicatorWgsl } from './coverageIndicator.wgsl.generated.ts'
17
+ import { GLSL_VERTEX as coverageInterbaseGlsl } from './coverageInterbase.glsl.generated.ts'
18
+ import { WGSL_SOURCE as coverageInterbaseWgsl } from './coverageInterbase.wgsl.generated.ts'
19
+ import { GLSL_VERTEX as coverageModGlsl } from './coverageMod.glsl.generated.ts'
20
+ import { WGSL_SOURCE as coverageModWgsl } from './coverageMod.wgsl.generated.ts'
21
+ import { GLSL_VERTEX as coverageSnpGlsl } from './coverageSnp.glsl.generated.ts'
22
+ import { WGSL_SOURCE as coverageSnpWgsl } from './coverageSnp.wgsl.generated.ts'
23
+ import { GLSL_VERTEX as pointMarkGlsl } from './pointMark.glsl.generated.ts'
24
+ import { WGSL_SOURCE as pointMarkWgsl } from './pointMark.wgsl.generated.ts'
25
+ import { GLSL_VERTEX as spanMarkGlsl } from './spanMark.glsl.generated.ts'
26
+ import { WGSL_SOURCE as spanMarkWgsl } from './spanMark.wgsl.generated.ts'
27
+
28
+ const SOURCES = {
29
+ 'barMark wgsl': barMarkWgsl,
30
+ 'barMark glsl': barMarkGlsl,
31
+ 'coverageBar wgsl': coverageBarWgsl,
32
+ 'coverageBar glsl': coverageBarGlsl,
33
+ 'coverageIndicator wgsl': coverageIndicatorWgsl,
34
+ 'coverageIndicator glsl': coverageIndicatorGlsl,
35
+ 'coverageInterbase wgsl': coverageInterbaseWgsl,
36
+ 'coverageInterbase glsl': coverageInterbaseGlsl,
37
+ 'coverageMod wgsl': coverageModWgsl,
38
+ 'coverageMod glsl': coverageModGlsl,
39
+ 'coverageSnp wgsl': coverageSnpWgsl,
40
+ 'coverageSnp glsl': coverageSnpGlsl,
41
+ 'pointMark wgsl': pointMarkWgsl,
42
+ 'pointMark glsl': pointMarkGlsl,
43
+ 'spanMark wgsl': spanMarkWgsl,
44
+ 'spanMark glsl': spanMarkGlsl,
45
+ }
46
+
47
+ function bodyOf(source: string, name: string) {
48
+ const open = source.indexOf('{', source.indexOf(`${name}(`))
49
+ const close = source.indexOf('}', open)
50
+ return source.slice(open + 1, close)
51
+ }
52
+
53
+ // The bp coordinates the split carries are uint32, so no difference of two of
54
+ // them reaches -2^33.
55
+ const BP_REACH = -(2 ** 33)
56
+
57
+ const CASES = Object.entries(SOURCES).flatMap(([where, source]) =>
58
+ ['hpToClipX_0', 'hpScaleLinear_0']
59
+ .filter(name => source.includes(`${name}(`))
60
+ .map(name => [`${where} ${name}`, bodyOf(source, name)] as const),
61
+ )
62
+
63
+ test('every hp conversion the emitted backends carry is covered', () => {
64
+ expect(CASES.length).toBe(Object.keys(SOURCES).length)
65
+ })
66
+
67
+ describe.each(CASES)('%s', (_where, body) => {
68
+ test('never divides by the hpZero uniform', () => {
69
+ expect(body).toMatch(/hpZero_\d+/)
70
+ expect(body).not.toMatch(/\/\s*(-\s*)?\(?\s*hpZero_\d+/)
71
+ })
72
+
73
+ test('clamps both halves against a finite floor below any bp difference', () => {
74
+ const floors = [...body.matchAll(/max\([^,]+,\s*(\w+)\)/g)].map(m => m[1])
75
+ expect(floors).toHaveLength(2)
76
+ expect(new Set(floors).size).toBe(1)
77
+
78
+ const assigned = new RegExp(`${floors[0]}[^=]*=\\s*([^;]+);`).exec(body)
79
+ const literal = /-?\d[\d.]*e[+-]\d+/.exec(assigned?.[1] ?? '')?.[0]
80
+ expect(literal).toBeDefined()
81
+
82
+ const floor = Number(literal)
83
+ expect(Number.isFinite(floor)).toBe(true)
84
+ expect(floor).toBeLessThan(BP_REACH)
85
+ expect(Math.fround(floor)).toBe(floor)
86
+ })
87
+ })
@@ -26,8 +26,12 @@ export function snapBoxHeightPx(heightPx: number): number {
26
26
  return _t1
27
27
  }
28
28
 
29
- export function snapBoxCenterYPx(centerY: number, heightPx: number, scrollY: number): number {
30
- return ((Math.floor((((centerY - (heightPx * 0.5)) - scrollY) + 0.5)) + Math.floor((snapBoxHeightPx(heightPx) * 0.5))) + 0.5)
29
+ export function snapBoxTopPx(topY: number, heightPx: number, scrollY: number): number {
30
+ return Math.floor((((((topY + (heightPx * 0.5)) - (snapBoxHeightPx(heightPx) * 0.5)) - scrollY) + 0.5) + 0.015625))
31
+ }
32
+
33
+ export function snapBoxCenterYPx(centerY: number, scrollY: number): number {
34
+ return (Math.floor(((centerY - scrollY) + 0.015625)) + 0.5)
31
35
  }
32
36
 
33
37
  export function extendToMinWidthPx(x1: number, x2: number, minWidth: number): number {
@@ -8,7 +8,14 @@
8
8
  // must make identically to the GPU — which pixel row a glyph centers on, how a
9
9
  // sub-pixel span is widened. `pnpm gen:shaders` transliterates them into
10
10
  // hpmath.js.generated.ts; nothing downstream re-derives them by hand.
11
- //! js-export: snapBoxHeightPx, snapBoxCenterYPx, extendToMinWidthPx
11
+ //
12
+ // `expandToMinWidthLeftPx`/`RightPx` are exported too, but by `coverageBar.slang`, into
13
+ // @jbrowse/alignments-core — the twin's consumer (`fillSpanRect`) lives in a
14
+ // package that cannot import render-core, and this file's own redirect would
15
+ // move every export above with it. adr-051 §"a shader may also export a
16
+ // function it only imports" is the mechanism; grep the name before assuming it
17
+ // is unlifted.
18
+ //! js-export: snapBoxHeightPx, snapBoxTopPx, snapBoxCenterYPx, extendToMinWidthPx
12
19
  //! js-skip: hpSplitUint — the hi/lo float32 precision split exists because a GPU has no float64; the Canvas2D path just uses a number
13
20
  //! js-skip: quadLocal — maps a vertex id to a quad corner — Canvas2D has no vertices, it calls fillRect
14
21
  //! js-skip: yPxToClipY — clip space is the GPU coordinate system; the px-space decision it wraps is what gets exported (snapBoxCenterYPx and friends)
@@ -17,13 +24,14 @@
17
24
  //! js-skip: pxToClipLen — the length half of the same conversion; a px dimension is already in px on the Canvas2D side, so there is nothing to convert
18
25
  //! js-skip: clipLenToPx — the inverse of pxToClipLen, same reason
19
26
  //! js-skip: extendToMinWidthX — clip-space wrapper over the exported extendToMinWidthPx, which is the decision
20
- //! js-skip: expandToMinWidthPx — the midpoint rule has no Canvas2D counterpart: the pileup floors a cell width one-sidedly and adds a seam fudge the shader deliberately omits (rendererTypes.ts pileupCellWidth), and the variant matrix floors in physical px against a canvas the Canvas2D twin sizes itself. A different rule, not a twin
21
27
  //! js-skip: expandToMinWidthX — clip-space wrapper over expandToMinWidthPx, same reason as extendToMinWidthX
28
+ //! js-skip: expandToMinWidthPx — the float2 over expandToMinWidthLeftPx and RightPx, which coverageBar.slang exports as the twins; a float2 twin is a tuple per call, and the pileup walk allocating one per span measured 0.85x against 0.78x of its hand painter
22
29
  //! js-skip: snapToPixelX — clip in, clip out; its px core is `floor(x + 0.5)`, which is Math.round and needs no twin
23
30
  //! js-skip: snapBoxCenterY — clip-space wrapper over the exported snapBoxCenterYPx
24
31
  module hpmath;
25
32
 
26
33
  public static const uint HP_LOW_MASK = 0xFFFu;
34
+ public static const float HP_LOW_SPAN = 4096.0;
27
35
 
28
36
  public float2 hpSplitUint(uint value) {
29
37
  uint lo = value & HP_LOW_MASK;
@@ -31,33 +39,52 @@ public float2 hpSplitUint(uint value) {
31
39
  return float2(float(hi), float(lo));
32
40
  }
33
41
 
34
- // `max(-inf)` and `dot()` exist to thwart the compiler — without them it
35
- // collapses (hi_a+lo_a)-(hi_b+lo_b) into one large subtraction and the
36
- // float32 precision the hi/lo split exists to preserve is gone. `hpZero` is
37
- // a tiny CPU-supplied number; `-1/hpZero` is smaller than any real diff.
42
+ // A floor no bp difference can reach, so clamping to it changes no result.
43
+ // The clamp is there for the compiler: `max()` and `dot()` together stop it
44
+ // collapsing (hi_a+lo_a)-(hi_b+lo_b) into one large subtraction, which loses
45
+ // the float32 precision the hi/lo split exists to preserve. `hpZero` arrives
46
+ // as exactly 0 and carries no magnitude of its own — it makes the floor a
47
+ // runtime value, which is what a backend cannot fold the `max` against. The
48
+ // earlier spelling built the floor as `-1.0 / hpZero`, an infinity WGSL lets
49
+ // an implementation answer with an indeterminate value instead.
50
+ static const float HP_CLAMP_FLOOR = -3.0e38;
51
+
52
+ // Add a bp offset to an already-split position, keeping the lo lane inside the
53
+ // 0..4095 band the split exists for. Dropping the offset straight into lo makes
54
+ // it carry genomic magnitude, and `hpToClipX` then multiplies two cancelling
55
+ // large terms by `step` — the catastrophic subtraction the split avoids, moved
56
+ // one line later.
57
+ public float2 hpAddBp(float2 splitPos, float offsetBp) {
58
+ float hi = floor(offsetBp / HP_LOW_SPAN) * HP_LOW_SPAN;
59
+ return float2(splitPos.x + hi, splitPos.y + (offsetBp - hi));
60
+ }
38
61
  public float hpToClipX(float2 splitPos, float3 bpRange, float hpZero) {
39
- float inf = 1.0 / hpZero;
62
+ float floorBp = HP_CLAMP_FLOOR + hpZero;
40
63
  float step = 2.0 / bpRange.z;
41
- float hi = max(splitPos.x - bpRange.x, -inf);
42
- float lo = max(splitPos.y - bpRange.y, -inf);
64
+ float hi = max(splitPos.x - bpRange.x, floorBp);
65
+ float lo = max(splitPos.y - bpRange.y, floorBp);
43
66
  return dot(float3(-1.0, hi, lo), float3(1.0, step, step));
44
67
  }
45
68
 
46
69
  public float hpScaleLinear(float2 splitPos, float3 bpRange, float hpZero) {
47
- float inf = 1.0 / hpZero;
70
+ float floorBp = HP_CLAMP_FLOOR + hpZero;
48
71
  float step = 1.0 / bpRange.z;
49
- float hi = max(splitPos.x - bpRange.x, -inf);
50
- float lo = max(splitPos.y - bpRange.y, -inf);
72
+ float hi = max(splitPos.x - bpRange.x, floorBp);
73
+ float lo = max(splitPos.y - bpRange.y, floorBp);
51
74
  return dot(float2(hi, lo), float2(step, step));
52
75
  }
53
76
 
54
77
  // Exact-as-possible bp offset from a split position to the viewport start
55
- // stored in bpRange.xy. Subtracts the hi/lo components separately (same
56
- // ordering as hpToClipX) so the result stays precise at high genomic
57
- // coordinates — recombining bpStartHi+bpStartLo first would lose ~256bp near
58
- // 3e9. Unlike hpToClipX this returns bp, not clip space.
59
- public float hpViewportOffsetBp(float2 splitPos, float3 bpRange) {
60
- return (bpRange.x - splitPos.x) + (bpRange.y - splitPos.y);
78
+ // stored in bpRange.xy. Subtracts the hi/lo components separately so the result
79
+ // stays precise at high genomic coordinates — recombining bpStartHi+bpStartLo
80
+ // first would lose ~256bp near 3e9 — and takes `hpZero` for the same clamp
81
+ // hpToClipX does, which is what stops the compiler recombining them. Unlike
82
+ // hpToClipX this returns bp, not clip space.
83
+ public float hpViewportOffsetBp(float2 splitPos, float3 bpRange, float hpZero) {
84
+ float floorBp = HP_CLAMP_FLOOR + hpZero;
85
+ float hi = max(bpRange.x - splitPos.x, floorBp);
86
+ float lo = max(bpRange.y - splitPos.y, floorBp);
87
+ return hi + lo;
61
88
  }
62
89
 
63
90
  public float snapToPixelX(float clipX, float canvasWidth) {
@@ -91,30 +118,44 @@ public float snapBoxHeightPx(float heightPx) {
91
118
  return even && hPx >= 2.0 && hPx <= THIN_BOX_PX ? hPx + 1.0 : hPx;
92
119
  }
93
120
 
94
- // Center a thin glyph on the *drawn* middle row of the box it annotates, given
95
- // the box's real center `centerY` and height `heightPx`. The box is rasterized
96
- // with the rect shader's edge snapping (top = floor(top+0.5), height =
97
- // snapBoxHeightPx(h)); reproducing that here lands the line/chevron exactly on
98
- // the box's center row instead of the ~1px bias a plain pixel-center snap
99
- // (`floor(centerY - scrollY + 0.5) + 0.5`) gives. Thin boxes are drawn at an odd
100
- // height precisely so that center row exists; a taller even box still has none,
101
- // and stays consistent (just below the seam).
121
+ // A whole-pixel box centered in a whole-pixel row puts the center of an
122
+ // odd-height box exactly on a row boundary, where float error in how each
123
+ // caller sums its center would otherwise pick the row above or below.
124
+ static const float SNAP_TIE_BIAS_PX = 1.0 / 64.0;
125
+
126
+ // The pixel row a box's top edge lands on. Snapped about the box's CENTER, not
127
+ // its top: `snapBoxHeightPx` rounds the height and sometimes nudges it a whole
128
+ // pixel, and anchoring the top spends all of that off the BOTTOM edge. A box the
129
+ // layout deliberately centered inside its row then isn't — a UTR is
130
+ // center-shrunk to 0.65 of the transcript body, so at superCompact's 3px row its
131
+ // 1.95px box rounded up to 3px and drew a full pixel below the CDS beside it,
132
+ // while the intron line and strand arrow (which snap to the row's center row)
133
+ // sat on its top edge. Top-anchored and center-anchored agree wherever the
134
+ // height survives the snap unchanged, which is every full-height feature box.
135
+ public float snapBoxTopPx(float topY, float heightPx, float scrollY) {
136
+ float centerY = topY + heightPx * 0.5;
137
+ return floor(
138
+ centerY - snapBoxHeightPx(heightPx) * 0.5 - scrollY + 0.5 + SNAP_TIE_BIAS_PX
139
+ );
140
+ }
141
+
142
+ // The pixel row holding the box's center, which a thin glyph (intron line,
143
+ // chevron, strand arrow) draws on. It is the middle row of every odd-height box
144
+ // with that center, so a center-shrunk UTR and its transcript's arrow agree
145
+ // whatever their parities; an even-height box's center row is whichever of its
146
+ // two middle rows the center falls in. Taking the lower middle row of an even
147
+ // box instead put the arrow a pixel under the UTR on an isoform stack, whose
148
+ // rows sit at fractional tops.
102
149
  //
103
- // Split in two on purpose. The decision — which pixel row is the center — is
104
- // resolution math the Canvas2D and SVG paths need in *screen px*; only the
105
- // clip-space conversion is GPU-specific. `//! js-export` lifts the px half into
106
- // TypeScript, so the two backends share the rule instead of the Canvas2D twin
107
- // re-deriving it (see adr-051). Keep new shared decisions in this shape: pure
108
- // px-in/px-out below, the `yPxToClipY` wrapper above it.
109
- public float snapBoxCenterYPx(float centerY, float heightPx, float scrollY) {
110
- float topPx = floor(centerY - heightPx * 0.5 - scrollY + 0.5);
111
- return topPx + floor(snapBoxHeightPx(heightPx) * 0.5) + 0.5;
112
- }
113
-
114
- public float snapBoxCenterY(
115
- float centerY, float heightPx, float scrollY, float canvasHeight
116
- ) {
117
- return yPxToClipY(snapBoxCenterYPx(centerY, heightPx, scrollY), canvasHeight);
150
+ // Split from `snapBoxCenterY` because the Canvas2D and SVG paths need this
151
+ // decision in screen px; only the clip-space conversion is GPU-specific
152
+ // (adr-051).
153
+ public float snapBoxCenterYPx(float centerY, float scrollY) {
154
+ return floor(centerY - scrollY + SNAP_TIE_BIAS_PX) + 0.5;
155
+ }
156
+
157
+ public float snapBoxCenterY(float centerY, float scrollY, float canvasHeight) {
158
+ return yPxToClipY(snapBoxCenterYPx(centerY, scrollY), canvasHeight);
118
159
  }
119
160
 
120
161
  // Extend the free edge `x2` away from the anchor `x1` so the span is at least
@@ -138,7 +179,7 @@ public float extendToMinWidthPx(float x1, float x2, float minWidth) {
138
179
  public float extendToMinWidthX(
139
180
  float x1, float x2, float minWidthPx, float canvasWidth
140
181
  ) {
141
- return extendToMinWidthPx(x1, x2, minWidthPx * 2.0 / canvasWidth);
182
+ return extendToMinWidthPx(x1, x2, pxToClipLen(minWidthPx, canvasWidth));
142
183
  }
143
184
 
144
185
  // The other min-width rule: widen a [x1, x2] span about its MIDPOINT, so both
@@ -154,18 +195,29 @@ public float extendToMinWidthX(
154
195
  // what lets the alignments passes (clip space, a 1 CSS px floor) and the variant
155
196
  // matrix (physical px, its own MIN_COL_PX) run one rule instead of two spellings
156
197
  // of it.
198
+ // The two edges as scalars, which is what the Canvas2D twins export: a
199
+ // `float2` twin comes back as a tuple, and the pileup walk allocating one per
200
+ // span measured 0.85x against 0.78x of its hand painter
201
+ // (plugins/alignments/benches/rectWalker.bench.ts).
202
+ public float expandToMinWidthLeftPx(float x1, float x2, float minWidth) {
203
+ return x2 - x1 < minWidth ? (x1 + x2) * 0.5 - minWidth * 0.5 : x1;
204
+ }
205
+
206
+ public float expandToMinWidthRightPx(float x1, float x2, float minWidth) {
207
+ return x2 - x1 < minWidth ? (x1 + x2) * 0.5 + minWidth * 0.5 : x2;
208
+ }
209
+
157
210
  public float2 expandToMinWidthPx(float x1, float x2, float minWidth) {
158
- if (x2 - x1 < minWidth) {
159
- float mid = (x1 + x2) * 0.5;
160
- return float2(mid - minWidth * 0.5, mid + minWidth * 0.5);
161
- }
162
- return float2(x1, x2);
211
+ return float2(
212
+ expandToMinWidthLeftPx(x1, x2, minWidth),
213
+ expandToMinWidthRightPx(x1, x2, minWidth)
214
+ );
163
215
  }
164
216
 
165
217
  public float2 expandToMinWidthX(
166
218
  float x1, float x2, float minWidthPx, float canvasWidth
167
219
  ) {
168
- return expandToMinWidthPx(x1, x2, minWidthPx * 2.0 / canvasWidth);
220
+ return expandToMinWidthPx(x1, x2, pxToClipLen(minWidthPx, canvasWidth));
169
221
  }
170
222
 
171
223
  // vid%6: 0→(0,0) 1→(1,0) 2→(0,1) 3→(0,1) 4→(1,0) 5→(1,1)
@@ -0,0 +1,67 @@
1
+ import * as linkGlsl from './linkMark.glsl.generated.ts'
2
+ import { linkValuePx } from './linkMark.js.generated.ts'
3
+ import * as linkWgsl from './linkMark.wgsl.generated.ts'
4
+ import * as pointGlsl from './pointMark.glsl.generated.ts'
5
+ import { pointYPx } from './pointMark.js.generated.ts'
6
+ import * as pointWgsl from './pointMark.wgsl.generated.ts'
7
+ import { SCALE_TYPE_LOG, SCALE_TYPE_SYMLOG } from './scoreScale.generated.ts'
8
+
9
+ // `valueScale.slang`'s `insetValueYPx` — the value scale over a range inset at
10
+ // both ends — with the two anchors its consumers add. A point measures down
11
+ // from the top of its row band, a link measures up from the band's baseline,
12
+ // so the two are reflections of one number and were two open-coded copies of
13
+ // it until they shared one.
14
+
15
+ const HEIGHTS = [8, 12, 20, 40, 100]
16
+ const INSETS = [0, 1, 2.5, 5, 9]
17
+ const DOMAINS: [number, number][] = [
18
+ [0, 1],
19
+ [-1, 1],
20
+ [0, 100],
21
+ [1, 1024],
22
+ [0.01, 0.5],
23
+ [5, 5],
24
+ ]
25
+ const SCALES = [0, SCALE_TYPE_LOG, SCALE_TYPE_SYMLOG]
26
+
27
+ test('a link measures the same value up that a point measures down', () => {
28
+ for (const h of HEIGHTS) {
29
+ for (const insetPx of INSETS) {
30
+ for (const [min, max] of DOMAINS) {
31
+ for (const scale of SCALES) {
32
+ for (const v of [min, max, (min + max) / 2, min - 1, max + 1, 0.02]) {
33
+ expect(linkValuePx(v, min, max, h, scale, insetPx, 1)).toBeCloseTo(
34
+ h - pointYPx(v, min, max, h, scale, insetPx, 1),
35
+ 9,
36
+ )
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ })
43
+
44
+ // The half-height clamp, which is the part of this that is not obvious. A band
45
+ // shorter than its two insets has no range left, and without the clamp the
46
+ // range goes negative and the scale runs backwards — an edge value drawn on
47
+ // the wrong side of the plot rather than merely squeezed.
48
+ test('a band shorter than two insets collapses to its midline, never inverts', () => {
49
+ for (const scale of SCALES) {
50
+ expect(pointYPx(0, 0, 100, 8, scale, 5, 1)).toBe(4)
51
+ expect(pointYPx(100, 0, 100, 8, scale, 5, 1)).toBe(4)
52
+ expect(linkValuePx(0, 0, 100, 8, scale, 5, 1)).toBe(4)
53
+ expect(linkValuePx(100, 0, 100, 8, scale, 5, 1)).toBe(4)
54
+ }
55
+ })
56
+
57
+ // The numbers above pin what the inset scale is. This pins that both shapes
58
+ // reach it on both backends: a numeric test of a function a shader has quietly
59
+ // stopped calling passes whatever that shader draws.
60
+ test.each([
61
+ ['pointMark', pointWgsl.WGSL_SOURCE, pointGlsl.GLSL_VERTEX],
62
+ ['linkMark', linkWgsl.WGSL_SOURCE, linkGlsl.GLSL_VERTEX],
63
+ ])('%s reads the shared inset scale on every backend', (_name, wgsl, glsl) => {
64
+ for (const src of [wgsl, glsl]) {
65
+ expect(src).toContain('insetValueYPx_0(')
66
+ }
67
+ })