@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
@@ -1,9 +1,10 @@
1
1
  /// <reference types="@webgpu/types" />
2
- import { syncCanvasSize } from '../canvas2dUtils.js';
2
+ import { getDpr, syncCanvasSize } from '../canvas2dUtils.js';
3
3
  import { canvasConfiguredBy, canvasContextError, noteCanvasConfigured, noteCanvasContext, } from '../canvasContext.js';
4
+ import { createGpuSurfaceLostError } from '../gpuContextLostError.js';
4
5
  import { getGpuDevice } from '../gpuDevice.js';
5
- import { STANDARD_BLEND_STATE, createUniformOnlyBindGroup, createVertexBuffer, toGpuVertexFormat, } from '../webgpuUtils.js';
6
- import { getDeviceLayouts, getOrBuildPipeline } from './deviceGpuCache.js';
6
+ import { createVertexBuffer } from '../webgpuUtils.js';
7
+ import { bindGroupLayoutEntries, getOrBuildPipeline, getPassLayout, pipelineRecipe, } from './deviceGpuCache.js';
7
8
  import { GpuHalBase } from './gpuHalBase.js';
8
9
  class ShaderCompileError extends Error {
9
10
  constructor(passId, details) {
@@ -11,21 +12,15 @@ class ShaderCompileError extends Error {
11
12
  this.name = 'ShaderCompileError';
12
13
  }
13
14
  }
14
- // Maximum number of writeUniforms() calls per frame. Each call occupies one
15
- // aligned slot in the uniform ring buffer.
15
+ // Each writeUniforms() call in a frame occupies one aligned slot in the uniform
16
+ // ring, a GPU buffer and a CPU staging array per display, each slot sized by
17
+ // the largest struct a registered pass declares. A frame is measured using 2-4
18
+ // slots, so a ring starts at INITIAL_UNIFORM_SLOTS and doubles when a frame asks
19
+ // for more: agent-docs/reference/ARCHITECTURAL_LIMITS.md §"The uniform ring".
16
20
  //
17
- // The ring is allocated eagerly at this many slots, as a GPU buffer AND a CPU
18
- // staging array, whether a renderer writes 4 slots or 1900 — so the cost is
19
- // per display and is the aligned uniform size that decides it, not the slot
20
- // count. A display whose uniform fits the 256-byte minimum alignment pays
21
- // 512 KB each side; alignments' 864-byte uniform aligns to 1024, so 2 MiB each
22
- // side. Trivial once, per-track at ten open alignments tracks.
23
- //
24
- // Exhausting it does not throw: the write is dropped and its draws render
25
- // against the previous batch's uniforms, which is wrong data rather than stale
26
- // data. If we ever hit the cap, switch to a dynamic-growth buffer (recreate
27
- // buffer + every region's bind group) rather than just bumping the constant
28
- // again.
21
+ // Past MAX_UNIFORM_SLOTS a write is dropped and its draws render against
22
+ // another batch's uniforms, without an error.
23
+ const INITIAL_UNIFORM_SLOTS = 16;
29
24
  const MAX_UNIFORM_SLOTS = 2048;
30
25
  // Warn while there is still headroom, because the cap itself is not a place to
31
26
  // find out. A renderer's per-frame write count is rarely one number: alignments
@@ -35,58 +30,22 @@ const MAX_UNIFORM_SLOTS = 2048;
35
30
  // the "~50 writes/frame" this file used to claim, and nothing reported the
36
31
  // difference between 50 and 1900.
37
32
  const UNIFORM_SLOT_WARN_AT = MAX_UNIFORM_SLOTS / 2;
38
- function gpuBlendState(bs) {
39
- // Max ignores its factors, but WebGPU still validates them and rejects
40
- // anything but 'one' on both channels ("Destination blend factor ... is
41
- // defined and not BlendFactor::One when blend operation is
42
- // BlendOperation::Max"). Same blend as webgl2Hal's bare glBlendEquation(MAX).
43
- const max = { srcFactor: 'one', dstFactor: 'one', operation: 'max' };
44
- // Otherwise RGB uses the caller-supplied factors and alpha always accumulates
45
- // through ONE / ONE_MINUS_SRC_ALPHA (matches webgl2Hal.applyBlendState).
46
- return bs.op === 'max'
47
- ? { color: max, alpha: max }
48
- : {
49
- color: { srcFactor: bs.srcFactor, dstFactor: bs.dstFactor },
50
- alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },
51
- };
52
- }
53
- // Every pass reads per-instance data from a vertex buffer (no storage binding
54
- // at 0) because Slang-generated shaders cross-compile to GLSL ES, which has no
55
- // SSBOs. The two @group(0) layouts a pass is built against — uniform-only, and
56
- // uniform + texture + sampler — belong to the device, not to this HAL, and live
57
- // in `deviceGpuCache.ts` with the pipelines.
58
- async function buildPipeline(device, desc, layouts, sampleCount) {
59
- const module = device.createShaderModule({ code: desc.wgslSource });
33
+ // Every pass reads per-instance data from a vertex buffer, because Slang's
34
+ // GLSL ES target has no SSBOs.
35
+ async function buildPipeline(device, recipe, passId) {
36
+ const module = device.createShaderModule({ code: recipe.wgslSource });
60
37
  const info = await module.getCompilationInfo();
61
38
  const errors = info.messages.filter(m => m.type === 'error');
62
39
  if (errors.length > 0) {
63
40
  const details = errors
64
41
  .map(m => `line ${m.lineNum}: ${m.message}`)
65
42
  .join('; ');
66
- throw new ShaderCompileError(desc.id, details);
67
- }
68
- const blend = desc.blend
69
- ? desc.blendState
70
- ? gpuBlendState(desc.blendState)
71
- : STANDARD_BLEND_STATE
72
- : undefined;
73
- // Every pass feeds @location(N) inputs from a bound vertex buffer.
74
- const vertexBuffers = [
75
- {
76
- arrayStride: desc.instanceStride,
77
- stepMode: 'instance',
78
- attributes: desc.vertexAttributes.map((attr, i) => ({
79
- shaderLocation: i,
80
- offset: attr.offsetBytes,
81
- format: toGpuVertexFormat(attr),
82
- })),
83
- },
84
- ];
43
+ throw new ShaderCompileError(passId, details);
44
+ }
45
+ const { blend } = recipe;
85
46
  return device.createRenderPipelineAsync({
86
- layout: desc.textures?.length
87
- ? layouts.texturedPipelineLayout
88
- : layouts.uniformOnlyPipelineLayout,
89
- vertex: { module, entryPoint: 'vs_main', buffers: vertexBuffers },
47
+ layout: getPassLayout(device, recipe.bindGroupLayout).pipelineLayout,
48
+ vertex: { module, entryPoint: 'vs_main', buffers: [recipe.vertexBuffer] },
90
49
  fragment: {
91
50
  module,
92
51
  entryPoint: 'fs_main',
@@ -97,17 +56,18 @@ async function buildPipeline(device, desc, layouts, sampleCount) {
97
56
  },
98
57
  ],
99
58
  },
100
- primitive: { topology: desc.topology ?? 'triangle-list' },
101
- multisample: sampleCount === 1 ? undefined : { count: sampleCount },
59
+ primitive: { topology: recipe.topology },
60
+ multisample: recipe.sampleCount === 1 ? undefined : { count: recipe.sampleCount },
102
61
  });
103
62
  }
104
- // Resolve every declared pass to its pipeline, taking the device-wide cache's
105
- // answer wherever another display of this type already built one. Still every
106
- // pass up front rather than on first draw — the WebGL2 side compiles lazily
107
- // (`getPass`) and this side does not; see ARCHITECTURAL_LIMITS.md §"Every
108
- // WebGPU display resolves its whole pass list before it can paint".
63
+ // Every declared pass up front, unlike WebGL2's first-draw link; see
64
+ // ARCHITECTURAL_LIMITS.md §"Every WebGPU display resolves its whole pass list
65
+ // before it can paint". Each pass compiles as soon as its own WGSL arrives.
109
66
  async function resolvePipelines(device, descriptors, sampleCount) {
110
- const built = await Promise.all(descriptors.map(desc => getOrBuildPipeline(device, desc, sampleCount, (layouts, count) => buildPipeline(device, desc, layouts, count))));
67
+ const built = await Promise.all(descriptors.map(async (desc) => {
68
+ const { WGSL_SOURCE } = await desc.source.wgsl();
69
+ return getOrBuildPipeline(device, pipelineRecipe(desc, WGSL_SOURCE, sampleCount), recipe => buildPipeline(device, recipe, desc.id));
70
+ }));
111
71
  return new Map(descriptors.map((desc, i) => [desc.id, built[i]]));
112
72
  }
113
73
  // Behavioral parity with WebGL2Hal is enforced by tests, not by this file:
@@ -124,30 +84,30 @@ export class WebGPUHal extends GpuHalBase {
124
84
  canvas;
125
85
  context;
126
86
  pipelines;
87
+ // Each pass's textures, by the sampler that reads each.
127
88
  passTextures = new Map();
128
- // One bind group per textured pass, built lazily by `getBindGroup` and
129
- // dropped when `uploadTexture` swaps that pass's texture. Uniform-only passes
130
- // are not stored here — they all share `uniformOnlyBindGroup`.
89
+ // Every pass's bind group, by the pass drawn. A pass that binds no texture
90
+ // has one from construction, shared with each pass over the same layout; a
91
+ // textured pass gets its own when its texture arrives, dropped when
92
+ // `uploadTexture` replaces the texture. The layouts are the device's, shared
93
+ // with every display on it; the groups are this HAL's, since they name its
94
+ // uniform ring buffer.
131
95
  passBindGroups = new Map();
132
- // The device's layouts, not this HAL's — shared with every other display on
133
- // the same device. The bind GROUPS above stay per-HAL: they reference this
134
- // HAL's uniform ring buffer.
135
- layouts;
136
96
  // Uniform ring buffer: holds up to MAX_UNIFORM_SLOTS sets of uniforms so
137
97
  // that all draw calls in a frame can reference different uniform data via
138
98
  // dynamic offsets, enabling a single command encoder + submit per frame.
139
99
  alignedUniformSize;
140
100
  uniformRingBuffer;
141
101
  uniformStaging;
102
+ uniformSlots = INITIAL_UNIFORM_SLOTS;
142
103
  uniformSlot = 0;
104
+ // Rings this frame outgrew, each with the staged bytes its encoded draws
105
+ // read: written at submit, released after it.
106
+ outgrownRings = [];
143
107
  // Once per HAL, not once per frame: at 60fps a per-frame warning is a
144
108
  // console the developer stops reading, and the fact is about the renderer
145
109
  // rather than about this frame.
146
110
  warnedUniformSlots = false;
147
- // Shared bind group for every uniform-only pass — only references
148
- // `uniformRingBuffer` (via dynamic offset at draw time), so one instance
149
- // serves all passes/regions instead of allocating a fresh one per upload.
150
- uniformOnlyBindGroup;
151
111
  // Samples per pixel this display renders at, stated by whoever built it (see
152
112
  // `RenderingBackendOptions.sampleCount`). Every render-pass, texture and
153
113
  // pipeline decision below reads it, so the two shapes cannot mismatch — and
@@ -175,7 +135,7 @@ export class WebGPUHal extends GpuHalBase {
175
135
  * selection spanning two sections re-uploads `OVERLAY_REGION` once per
176
136
  * section, inside the block loop.
177
137
  *
178
- * Deferring is what makes mid-frame replacement legal instead of merely
138
+ * Deferring the destroy makes mid-frame replacement legal instead of merely
179
139
  * warned about; synteny's `ensureUploaded` has always deleted a pass mid-frame
180
140
  * that the open pass never referenced, and that case was never the bug.
181
141
  */
@@ -196,26 +156,93 @@ export class WebGPUHal extends GpuHalBase {
196
156
  // than once per frame, and a reconfigure that failed is not attempted again.
197
157
  warnedSwapChainLoss = false;
198
158
  swapChainUnrecoverable = false;
199
- constructor(device, canvas, context, descriptors, uniformByteSize, sampleCount, pipelines, layouts) {
159
+ reportedSurfaceFailure = false;
160
+ constructor(device, canvas, context, descriptors, sampleCount, pipelines) {
200
161
  super(descriptors, 'WebGPUHal');
201
162
  this.device = device;
202
163
  this.canvas = canvas;
203
164
  this.context = context;
204
165
  this.pipelines = pipelines;
205
- this.layouts = layouts;
206
166
  this.sampleCount = sampleCount;
207
167
  // Align uniform slots to device requirements for dynamic offsets
208
168
  const alignment = device.limits.minUniformBufferOffsetAlignment;
209
- this.alignedUniformSize = Math.ceil(uniformByteSize / alignment) * alignment;
210
- const ringSize = MAX_UNIFORM_SLOTS * this.alignedUniformSize;
211
- this.uniformRingBuffer = device.createBuffer({
212
- size: ringSize,
213
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
214
- });
169
+ this.alignedUniformSize =
170
+ Math.ceil(this.uniformByteSize / alignment) * alignment;
171
+ const ringSize = this.uniformSlots * this.alignedUniformSize;
172
+ this.uniformRingBuffer = this.createRing(ringSize);
215
173
  this.uniformStaging = new Uint8Array(ringSize);
216
- this.uniformOnlyBindGroup = createUniformOnlyBindGroup(device, layouts.uniformOnlyBindGroupLayout, this.uniformRingBuffer, this.alignedUniformSize);
174
+ this.bindUntexturedPasses();
217
175
  this.configureContext();
218
176
  }
177
+ createRing(size) {
178
+ return this.device.createBuffer({
179
+ size,
180
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
181
+ });
182
+ }
183
+ // Every pass's bind group names the ring buffer, so a new ring rebinds them:
184
+ // an untextured pass here, a textured one on its next draw (`bindTexture`).
185
+ bindUntexturedPasses() {
186
+ const byLayout = new Map();
187
+ for (const desc of this.descriptors.values()) {
188
+ if (desc.bindings.some(b => b.kind === 'texture')) {
189
+ this.passBindGroups.delete(desc.id);
190
+ continue;
191
+ }
192
+ const layout = this.bindGroupLayoutOf(desc.bindings);
193
+ let group = byLayout.get(layout);
194
+ if (!group) {
195
+ group = this.createBindGroup(desc.bindings, layout);
196
+ byLayout.set(layout, group);
197
+ }
198
+ this.passBindGroups.set(desc.id, group);
199
+ }
200
+ }
201
+ // Doubles the ring mid-frame. The draws already encoded bind the old buffer,
202
+ // so it takes the slots staged so far at submit and is released after.
203
+ growRing() {
204
+ const bytes = this.uniformSlot * this.alignedUniformSize;
205
+ this.outgrownRings.push({ buffer: this.uniformRingBuffer, bytes });
206
+ this.destroyWhenIdle(this.uniformRingBuffer);
207
+ this.uniformSlots = Math.min(this.uniformSlots * 2, MAX_UNIFORM_SLOTS);
208
+ const size = this.uniformSlots * this.alignedUniformSize;
209
+ this.uniformRingBuffer = this.createRing(size);
210
+ const staging = new Uint8Array(size);
211
+ staging.set(this.uniformStaging.subarray(0, bytes));
212
+ this.uniformStaging = staging;
213
+ this.bindUntexturedPasses();
214
+ }
215
+ bindGroupLayoutOf(bindings) {
216
+ return getPassLayout(this.device, bindGroupLayoutEntries(bindings))
217
+ .bindGroupLayout;
218
+ }
219
+ // One entry per binding in the pass's table, the same table its layout was
220
+ // built from, so the group cannot name a binding the layout lacks.
221
+ createBindGroup(bindings, layout, textures) {
222
+ return this.device.createBindGroup({
223
+ layout,
224
+ entries: bindings.map(b => ({
225
+ binding: b.index,
226
+ resource: this.bindingResource(b, textures?.get(b.name)),
227
+ })),
228
+ });
229
+ }
230
+ bindingResource(b, texture) {
231
+ if (b.kind === 'uniform') {
232
+ return {
233
+ buffer: this.uniformRingBuffer,
234
+ offset: 0,
235
+ size: this.alignedUniformSize,
236
+ };
237
+ }
238
+ if (texture && b.kind === 'texture') {
239
+ return texture.texture.createView();
240
+ }
241
+ if (texture && b.kind === 'sampler') {
242
+ return texture.sampler;
243
+ }
244
+ throw new Error(`[WebGPUHal] nothing to bind for the ${b.kind} '${b.name}' at binding ${b.index}`);
245
+ }
219
246
  limits() {
220
247
  const { maxBufferSize, maxTextureDimension2D } = this.device.limits;
221
248
  return {
@@ -245,17 +272,16 @@ export class WebGPUHal extends GpuHalBase {
245
272
  });
246
273
  noteCanvasConfigured(this.canvas, this);
247
274
  }
248
- static async create(canvas, descriptors, uniformByteSize, sampleCount) {
275
+ static async create(canvas, descriptors, sampleCount) {
249
276
  const device = await getGpuDevice();
250
277
  if (!device) {
251
278
  return null;
252
279
  }
253
280
  // Resolve pipelines BEFORE acquiring the canvas's webgpu context. A canvas's
254
- // context type is permanent once acquired, so if shader compilation throws
255
- // here the canvas stays pristine and createGpuHal's WebGL2 fallback can
256
- // still claim it — otherwise a partial WebGPU init would drop us all the way
257
- // to Canvas2D on a WebGL2-capable machine.
258
- const layouts = getDeviceLayouts(device);
281
+ // context type is permanent once acquired, so if a shader fails to load or
282
+ // compile here the canvas stays pristine and createGpuHal's WebGL2 fallback
283
+ // can still claim it — otherwise a partial WebGPU init would drop us all the
284
+ // way to Canvas2D on a WebGL2-capable machine.
259
285
  const pipelines = await resolvePipelines(device, descriptors, sampleCount);
260
286
  const context = canvas.getContext('webgpu');
261
287
  if (!context) {
@@ -266,9 +292,14 @@ export class WebGPUHal extends GpuHalBase {
266
292
  throw canvasContextError(canvas, 'webgpu');
267
293
  }
268
294
  noteCanvasContext(canvas, 'webgpu');
269
- return new WebGPUHal(device, canvas, context, descriptors, uniformByteSize, sampleCount, pipelines, layouts);
295
+ return new WebGPUHal(device, canvas, context, descriptors, sampleCount, pipelines);
270
296
  }
271
297
  resize(width, height) {
298
+ // A render autorun can fire between `dispose` and the model dropping its
299
+ // backend reference, and nothing destroys what a disposed HAL allocates.
300
+ if (this.disposed) {
301
+ return { x: getDpr(), y: getDpr() };
302
+ }
272
303
  const { changed, scale } = syncCanvasSize(this.canvas, width, height);
273
304
  // `!this.msaaTexture` covers the first frame and a rebuild that bailed. At
274
305
  // sampleCount 1 there is never a texture, so without the count in the
@@ -279,6 +310,11 @@ export class WebGPUHal extends GpuHalBase {
279
310
  }
280
311
  return scale;
281
312
  }
313
+ releaseRenderTargets() {
314
+ if (!this.disposed) {
315
+ this.recreateMsaaTexture(0, 0);
316
+ }
317
+ }
282
318
  recreateMsaaTexture(width, height) {
283
319
  this.msaaTexture?.destroy();
284
320
  this.msaaTexture = null;
@@ -309,50 +345,32 @@ export class WebGPUHal extends GpuHalBase {
309
345
  * (and a dropped draw) the moment one side samples a texture.
310
346
  *
311
347
  * Nothing in a bind group varies per region either: it references the
312
- * HAL-wide uniform ring buffer plus the pass's own texture/sampler. So
313
- * uniform-only passes all share `uniformOnlyBindGroup`, and a textured pass
314
- * gets exactly one, cached until `uploadTexture` replaces its texture.
315
- * Returns undefined when a pass needs a texture that hasn't arrived yet;
316
- * drawPass skips those.
348
+ * HAL-wide uniform ring buffer plus the pass's own texture/sampler. Returns
349
+ * undefined when a pass needs a texture that hasn't arrived yet; drawPass
350
+ * skips those.
317
351
  */
318
352
  getBindGroup(passId) {
319
- const tb = this.descriptors.get(passId)?.textures?.[0];
320
- if (tb) {
321
- let bindGroup = this.passBindGroups.get(passId);
322
- if (!bindGroup) {
323
- const texState = this.passTextures.get(passId);
324
- if (texState) {
325
- bindGroup = this.device.createBindGroup({
326
- layout: this.layouts.texturedBindGroupLayout,
327
- entries: [
328
- {
329
- binding: 1,
330
- resource: {
331
- buffer: this.uniformRingBuffer,
332
- offset: 0,
333
- size: this.alignedUniformSize,
334
- },
335
- },
336
- {
337
- binding: tb.textureBinding,
338
- resource: texState.texture.createView(),
339
- },
340
- { binding: tb.samplerBinding, resource: texState.sampler },
341
- ],
342
- });
343
- this.passBindGroups.set(passId, bindGroup);
344
- }
345
- }
346
- return bindGroup;
353
+ return this.passBindGroups.get(passId) ?? this.bindTexture(passId);
354
+ }
355
+ // Built once every texture the pass samples has arrived.
356
+ bindTexture(passId) {
357
+ const textures = this.passTextures.get(passId);
358
+ const desc = this.descriptors.get(passId);
359
+ if (!textures ||
360
+ !desc ||
361
+ desc.bindings.some(b => b.kind === 'texture' && !textures.has(b.name))) {
362
+ return undefined;
347
363
  }
348
- return this.uniformOnlyBindGroup;
364
+ const group = this.createBindGroup(desc.bindings, this.bindGroupLayoutOf(desc.bindings), textures);
365
+ this.passBindGroups.set(passId, group);
366
+ return group;
349
367
  }
350
368
  /**
351
369
  * The one place a GPU resource this HAL owns is released — see
352
370
  * {@link pendingDestroy} for why the frame decides when.
353
371
  *
354
372
  * Every buffer release routes here through the `RegionRegistry` destroy hook,
355
- * so `uploadBuffer`, `deleteBuffer`, `deleteRegion`, `pruneRegions` and
373
+ * so `uploadBuffer`, `deleteBuffer`, `deleteRegion` and
356
374
  * `dispose` are all covered without any of them knowing a frame is open.
357
375
  */
358
376
  destroyWhenIdle(resource) {
@@ -370,29 +388,42 @@ export class WebGPUHal extends GpuHalBase {
370
388
  this.pendingDestroy.length = 0;
371
389
  }
372
390
  createTexture(passId, binding, data, width, height) {
373
- const existing = this.passTextures.get(passId);
391
+ let byName = this.passTextures.get(passId);
392
+ if (!byName) {
393
+ byName = new Map();
394
+ this.passTextures.set(passId, byName);
395
+ }
396
+ const existing = byName.get(binding.name);
374
397
  if (existing) {
375
- // Same hazard as a buffer, one step further from a live caller: every
376
- // `uploadTexture` in tree is a colour ramp written from an upload autorun
377
- // (hic's `LinearHicDisplay/model.ts`, variants' `LDDisplay/shared.ts`),
378
- // which never runs inside a frame. Routing it through the same deferral
379
- // costs nothing and keeps "replaced mid-frame" from being a hazard again
380
- // the first time a renderer builds a texture from render state.
398
+ // Same hazard as a buffer, and no longer a hypothetical one: every
399
+ // `uploadTexture` in tree is a colour ramp, and since the ramp became a
400
+ // `defineMark` `texture` they are all written by `GpuMarkBackend`'s
401
+ // `bindRamp` from inside `drawRegion` — mid-frame, between an encoded
402
+ // draw and its submit. The deferral is what makes replacing one there
403
+ // safe.
381
404
  this.destroyWhenIdle(existing.texture);
382
405
  }
406
+ const bytes = data instanceof Uint8Array;
383
407
  const texture = this.device.createTexture({
384
408
  size: [width, height],
385
409
  format: 'rgba8unorm',
386
- usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
410
+ usage: GPUTextureUsage.TEXTURE_BINDING |
411
+ GPUTextureUsage.COPY_DST |
412
+ (bytes ? 0 : GPUTextureUsage.RENDER_ATTACHMENT),
387
413
  });
388
- this.device.queue.writeTexture({ texture }, data, { bytesPerRow: width * 4 }, { width, height });
414
+ if (bytes) {
415
+ this.device.queue.writeTexture({ texture }, data, { bytesPerRow: width * 4 }, { width, height });
416
+ }
417
+ else {
418
+ this.device.queue.copyExternalImageToTexture({ source: data }, { texture, premultipliedAlpha: true }, { width, height });
419
+ }
389
420
  const sampler = this.device.createSampler({
390
421
  magFilter: binding.filter,
391
422
  minFilter: binding.filter,
392
423
  addressModeU: 'clamp-to-edge',
393
424
  addressModeV: 'clamp-to-edge',
394
425
  });
395
- this.passTextures.set(passId, { texture, sampler });
426
+ byName.set(binding.name, { texture, sampler });
396
427
  // Drop the cached bind group so the next draw rebuilds it against the new
397
428
  // texture (and so a pass drawn before its first texture arrived stops
398
429
  // being skipped).
@@ -401,14 +432,17 @@ export class WebGPUHal extends GpuHalBase {
401
432
  writeUniforms(data) {
402
433
  if (this.currentEncoder) {
403
434
  // Inside a frame: stage data at the current slot for batched upload
404
- if (this.uniformSlot >= MAX_UNIFORM_SLOTS) {
435
+ if (this.uniformSlot >= this.uniformSlots &&
436
+ this.uniformSlots < MAX_UNIFORM_SLOTS) {
437
+ this.growRing();
438
+ }
439
+ if (this.uniformSlot >= this.uniformSlots) {
405
440
  console.error(`[WebGPUHal] uniform ring buffer exhausted at ${MAX_UNIFORM_SLOTS} ` +
406
441
  `writeUniforms calls in one frame — this write is dropped, so the ` +
407
442
  `paired draw renders with the previous batch's uniforms (wrong ` +
408
443
  `data, not last-frame-stale). This indicates a renderer doing far ` +
409
444
  `more per-frame uniform writes than expected; investigate the call ` +
410
- `site before raising the cap (and consider switching to a ` +
411
- `dynamic-growth ring buffer).`);
445
+ `site before raising the cap.`);
412
446
  return;
413
447
  }
414
448
  const offset = this.uniformSlot * this.alignedUniformSize;
@@ -471,6 +505,21 @@ export class WebGPUHal extends GpuHalBase {
471
505
  }
472
506
  }
473
507
  }
508
+ /**
509
+ * Once per HAL, not once per frame: the refusal holds for every frame at this
510
+ * canvas size, and dotplot and synteny keep their canvas mounted through a
511
+ * `renderError`, so a per-frame report there never stops.
512
+ *
513
+ * Not terminal — a refusal tracks the canvas dimensions, so a resize can clear
514
+ * it. `swapChainUnrecoverable` stays for the case it was written for.
515
+ */
516
+ reportSurfaceFailure(message) {
517
+ if (this.reportedSurfaceFailure) {
518
+ return;
519
+ }
520
+ this.reportedSurfaceFailure = true;
521
+ this.oom.reportClassified(createGpuSurfaceLostError(message));
522
+ }
474
523
  beginFrame(clearR, clearG, clearB, clearA = 1) {
475
524
  // Skip the frame entirely rather than encode one that cannot be valid.
476
525
  // Zero-size canvas: nothing to draw. Missing MSAA target while MSAA is
@@ -486,10 +535,23 @@ export class WebGPUHal extends GpuHalBase {
486
535
  (this.sampleCount > 1 && !this.msaaView)) {
487
536
  return;
488
537
  }
489
- // Before the error scopes, so a failed acquisition needs no unwinding: the
490
- // scopes stay pushed iff an encoder is created, which is what endFrame's
491
- // early-return on !currentEncoder is paired with.
538
+ // A driver can refuse the canvas image without throwing — Dawn returns an
539
+ // error texture and an error view, and the frame encodes against it — so the
540
+ // acquisition needs a scope, and its own rather than the frame's: those stay
541
+ // pushed iff an encoder is created, and one left on the device's stack
542
+ // swallows every later frame's errors. Popped before the guard below, so
543
+ // every path balances. Awaiting the pop here would suspend across a
544
+ // GPU-process round trip and destroy the canvas texture mid-frame.
545
+ this.device.pushErrorScope('validation');
492
546
  const textureView = this.acquireTextureView();
547
+ void this.device
548
+ .popErrorScope()
549
+ .then(err => {
550
+ if (err) {
551
+ this.reportSurfaceFailure(err.message);
552
+ }
553
+ })
554
+ .catch(() => { });
493
555
  if (!textureView) {
494
556
  return;
495
557
  }
@@ -526,7 +588,7 @@ export class WebGPUHal extends GpuHalBase {
526
588
  ],
527
589
  });
528
590
  }
529
- drawPass(passId, regionKey, bufferPassId) {
591
+ drawPass(passId, regionKey, bufferPassId, verticesPerInstance) {
530
592
  if (!this.currentPass) {
531
593
  return;
532
594
  }
@@ -561,7 +623,7 @@ export class WebGPUHal extends GpuHalBase {
561
623
  this.currentPass.setPipeline(pipeline);
562
624
  this.currentPass.setBindGroup(0, bindGroup, [dynamicOffset]);
563
625
  this.currentPass.setVertexBuffer(0, regionBuf.dataBuffer);
564
- this.currentPass.draw(desc.verticesPerInstance, regionBuf.count);
626
+ this.currentPass.draw(verticesPerInstance ?? desc.verticesPerInstance, regionBuf.count);
565
627
  }
566
628
  /**
567
629
  * Close the frame's state whether the submit landed or threw.
@@ -594,6 +656,7 @@ export class WebGPUHal extends GpuHalBase {
594
656
  this.currentPass = null;
595
657
  this.currentEncoder = null;
596
658
  this.currentTextureView = null;
659
+ this.outgrownRings.length = 0;
597
660
  this.drainPendingDestroy();
598
661
  }
599
662
  endFrame() {
@@ -606,6 +669,9 @@ export class WebGPUHal extends GpuHalBase {
606
669
  if (this.currentPass) {
607
670
  this.currentPass.end();
608
671
  }
672
+ for (const ring of this.outgrownRings) {
673
+ this.device.queue.writeBuffer(ring.buffer, 0, this.uniformStaging, 0, ring.bytes);
674
+ }
609
675
  if (slotAtSubmit > 0) {
610
676
  const uploadSize = slotAtSubmit * this.alignedUniformSize;
611
677
  this.device.queue.writeBuffer(this.uniformRingBuffer, 0, this.uniformStaging, 0, uploadSize);
@@ -704,8 +770,10 @@ export class WebGPUHal extends GpuHalBase {
704
770
  }
705
771
  this.regions.deleteAll();
706
772
  this.uniformRingBuffer.destroy();
707
- for (const ts of this.passTextures.values()) {
708
- ts.texture.destroy();
773
+ for (const byName of this.passTextures.values()) {
774
+ for (const ts of byName.values()) {
775
+ ts.texture.destroy();
776
+ }
709
777
  }
710
778
  this.passTextures.clear();
711
779
  this.passBindGroups.clear();
@@ -15,6 +15,16 @@ export interface CellTarget<K, Encoded> {
15
15
  upload(key: K, data: Encoded): void;
16
16
  release(key: K): void;
17
17
  }
18
+ /**
19
+ * The other half of what this installer needs off a backend, and the half every
20
+ * backend gets for free by extending `GpuRenderingBackendBase` or
21
+ * `Canvas2DRenderingBackendBase`. Named apart from `CellTarget` because it has
22
+ * nothing to do with cells: the installer passes it straight through as the
23
+ * lifecycle's `releaseTargets` callback.
24
+ */
25
+ export interface TargetRelease {
26
+ releaseOffscreenTargets(): void;
27
+ }
18
28
  /**
19
29
  * What the backend takes, read off the backend, which is the direction the
20
30
  * overloads have to check in: the payload reaching `upload` must be assignable
@@ -74,18 +84,21 @@ export declare function oneCell<K, T>(key: K, value: T | undefined | null): Read
74
84
  *
75
85
  * **Calling again only swaps the backend.** Displays wire this from
76
86
  * `startRenderingBackend`, which fires again on every context-loss recovery;
77
- * the diff and the encode cache live in the setup thunk and survive it.
87
+ * the diff and the encode memo live in the setup thunk and survive it.
88
+ *
89
+ * `encode` runs through `createEncodeMemo`. A display that needs the encoded
90
+ * map itself holds that memo in a `.views` closure and hands identity cells.
78
91
  *
79
92
  * @see installUpload.test.ts for the upload, encode and release counts.
80
93
  * @see ADR-078 for why this is one autorun and a diff.
81
94
  */
82
- export declare function installUpload<K, Data extends EncodedOf<B>, B extends CellTarget<K, unknown>>(self: LifecycleHost, backend: B, spec: {
95
+ export declare function installUpload<K, Data extends EncodedOf<B>, B extends CellTarget<K, unknown> & TargetRelease>(self: LifecycleHost, backend: B, spec: {
83
96
  cells: () => ReadonlyMap<K, Data>;
84
97
  render: (backend: B, encoded: ReadonlyMap<K, Data>) => boolean;
85
98
  encode?: never;
86
99
  inputs?: never;
87
100
  }): void;
88
- export declare function installUpload<K, Data, Props, B extends CellTarget<K, unknown>>(self: LifecycleHost, backend: B, spec: UploadSpec<K, Data, Props, EncodedOf<B>, B> & {
101
+ export declare function installUpload<K, Data, Props, B extends CellTarget<K, unknown> & TargetRelease>(self: LifecycleHost, backend: B, spec: UploadSpec<K, Data, Props, EncodedOf<B>, B> & {
89
102
  encode: (data: Data, props: Props, key: K) => EncodedOf<B>;
90
103
  }): void;
91
104
  export {};