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

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 (353) hide show
  1. package/esm/RenderCanvas.d.ts +3 -3
  2. package/esm/RenderCanvas.js +1 -1
  3. package/esm/RenderLifecycleMixin.d.ts +52 -20
  4. package/esm/RenderLifecycleMixin.js +89 -23
  5. package/esm/canvas2dUtils.d.ts +90 -23
  6. package/esm/canvas2dUtils.js +120 -37
  7. package/esm/colorRampLut.d.ts +5 -5
  8. package/esm/colorRampLut.js +6 -6
  9. package/esm/computePipeline.d.ts +14 -0
  10. package/esm/computePipeline.js +62 -0
  11. package/esm/contractReports.d.ts +54 -0
  12. package/esm/contractReports.js +112 -0
  13. package/esm/coverageBand.d.ts +13 -8
  14. package/esm/coverageBand.js +15 -5
  15. package/esm/coverageBandBuffers.d.ts +2 -9
  16. package/esm/coverageBandBuffers.js +1 -13
  17. package/esm/createRenderingBackend.d.ts +16 -20
  18. package/esm/createRenderingBackend.js +6 -4
  19. package/esm/displayPhase.d.ts +83 -104
  20. package/esm/displayPhase.js +42 -82
  21. package/esm/encodeMemo.d.ts +14 -0
  22. package/esm/encodeMemo.js +48 -0
  23. package/esm/gpuContextLostError.d.ts +30 -0
  24. package/esm/gpuContextLostError.js +40 -0
  25. package/esm/gpuDevice.d.ts +29 -10
  26. package/esm/gpuDevice.js +35 -16
  27. package/esm/graphicsCapabilities.d.ts +27 -19
  28. package/esm/graphicsCapabilities.js +41 -19
  29. package/esm/hal/createHal.d.ts +4 -5
  30. package/esm/hal/createHal.js +15 -5
  31. package/esm/hal/deviceGpuCache.d.ts +25 -30
  32. package/esm/hal/deviceGpuCache.js +85 -97
  33. package/esm/hal/gpuHalBase.d.ts +5 -4
  34. package/esm/hal/gpuHalBase.js +25 -14
  35. package/esm/hal/index.d.ts +2 -2
  36. package/esm/hal/index.js +1 -1
  37. package/esm/hal/mockHal.d.ts +15 -10
  38. package/esm/hal/mockHal.js +31 -18
  39. package/esm/hal/oomReporter.d.ts +7 -0
  40. package/esm/hal/oomReporter.js +10 -0
  41. package/esm/hal/passIds.d.ts +8 -7
  42. package/esm/hal/passIds.js +11 -10
  43. package/esm/hal/regionRegistry.d.ts +0 -1
  44. package/esm/hal/regionRegistry.js +3 -15
  45. package/esm/hal/types.d.ts +53 -18
  46. package/esm/hal/webgl2Hal.d.ts +23 -6
  47. package/esm/hal/webgl2Hal.js +158 -155
  48. package/esm/hal/webgpuHal.d.ts +29 -13
  49. package/esm/hal/webgpuHal.js +220 -153
  50. package/esm/installUpload.d.ts +16 -3
  51. package/esm/installUpload.js +13 -41
  52. package/esm/instanceCache.d.ts +10 -7
  53. package/esm/instanceCache.js +14 -15
  54. package/esm/instancePass.d.ts +2 -1
  55. package/esm/mapUploadSync.d.ts +2 -10
  56. package/esm/mapUploadSync.js +2 -3
  57. package/esm/marks/barMark.d.ts +30 -0
  58. package/esm/marks/barMark.js +149 -0
  59. package/esm/marks/colorFill.d.ts +17 -0
  60. package/esm/marks/colorFill.js +26 -0
  61. package/esm/marks/drawAgainstHit.d.ts +113 -0
  62. package/esm/marks/drawAgainstHit.js +407 -0
  63. package/esm/marks/ellipseDistance.d.ts +10 -0
  64. package/esm/marks/ellipseDistance.js +136 -0
  65. package/esm/marks/glyphPaint.d.ts +36 -0
  66. package/esm/marks/glyphPaint.js +77 -0
  67. package/esm/marks/index.d.ts +33 -0
  68. package/esm/marks/index.js +20 -0
  69. package/esm/marks/lineMark.d.ts +37 -0
  70. package/esm/marks/lineMark.js +416 -0
  71. package/esm/marks/linkMark.d.ts +76 -0
  72. package/esm/marks/linkMark.js +585 -0
  73. package/esm/marks/markBackend.d.ts +118 -0
  74. package/esm/marks/markBackend.js +196 -0
  75. package/esm/marks/markHit.d.ts +32 -0
  76. package/esm/marks/markHit.js +61 -0
  77. package/esm/marks/markInk.d.ts +18 -0
  78. package/esm/marks/markInk.js +71 -0
  79. package/esm/marks/markPaint.d.ts +14 -0
  80. package/esm/marks/markPaint.js +32 -0
  81. package/esm/marks/markPlan.d.ts +25 -0
  82. package/esm/marks/markPlan.js +20 -0
  83. package/esm/marks/markRamp.d.ts +74 -0
  84. package/esm/marks/markRamp.js +205 -0
  85. package/esm/marks/nearestMarkHit.d.ts +55 -0
  86. package/esm/marks/nearestMarkHit.js +158 -0
  87. package/esm/marks/pathRecorder.d.ts +14 -0
  88. package/esm/marks/pathRecorder.js +80 -0
  89. package/esm/marks/pointMark.d.ts +42 -0
  90. package/esm/marks/pointMark.js +125 -0
  91. package/esm/marks/rowLane.d.ts +39 -0
  92. package/esm/marks/rowLane.js +41 -0
  93. package/esm/marks/rowSpanIndex.d.ts +30 -0
  94. package/esm/marks/rowSpanIndex.js +151 -0
  95. package/esm/marks/rowTable.d.ts +41 -0
  96. package/esm/marks/rowTable.js +75 -0
  97. package/esm/marks/ruleMark.d.ts +21 -0
  98. package/esm/marks/ruleMark.js +91 -0
  99. package/esm/marks/spanMark.d.ts +42 -0
  100. package/esm/marks/spanMark.js +81 -0
  101. package/esm/marks/types.d.ts +261 -0
  102. package/esm/marks/types.js +175 -0
  103. package/esm/marks/valueScale.d.ts +23 -0
  104. package/esm/marks/valueScale.js +11 -0
  105. package/esm/namedReactions.d.ts +3 -4
  106. package/esm/namedReactions.js +3 -4
  107. package/esm/perRegionRenderingBackend.d.ts +21 -3
  108. package/esm/perRegionRenderingBackend.js +37 -3
  109. package/esm/regionDataMap.d.ts +2 -2
  110. package/esm/regionDataMap.js +25 -23
  111. package/esm/renderBlock.d.ts +14 -0
  112. package/esm/renderBlock.js +25 -0
  113. package/esm/renderingBackendBase.d.ts +20 -14
  114. package/esm/renderingBackendBase.js +12 -7
  115. package/esm/scoreScale.d.ts +27 -0
  116. package/esm/scoreScale.js +88 -0
  117. package/esm/shaders/barMark.generated.d.ts +3 -0
  118. package/esm/shaders/barMark.generated.js +7 -0
  119. package/esm/shaders/barMark.glsl.generated.d.ts +2 -0
  120. package/esm/shaders/barMark.glsl.generated.js +4 -0
  121. package/esm/shaders/barMark.iface.generated.d.ts +90 -0
  122. package/esm/shaders/barMark.iface.generated.js +205 -0
  123. package/esm/shaders/barMark.wgsl.generated.d.ts +1 -0
  124. package/esm/shaders/barMark.wgsl.generated.js +3 -0
  125. package/esm/shaders/capsule.js.generated.d.ts +1 -0
  126. package/esm/shaders/capsule.js.generated.js +11 -0
  127. package/esm/shaders/clipStrip.generated.d.ts +2 -0
  128. package/esm/shaders/clipStrip.generated.js +4 -0
  129. package/esm/shaders/clipStrip.js.generated.d.ts +1 -0
  130. package/esm/shaders/clipStrip.js.generated.js +21 -0
  131. package/esm/shaders/colorRampLut.js.generated.d.ts +1 -0
  132. package/esm/shaders/colorRampLut.js.generated.js +17 -0
  133. package/esm/shaders/coverageBar.generated.d.ts +2 -3
  134. package/esm/shaders/coverageBar.generated.js +4 -3
  135. package/esm/shaders/coverageBar.glsl.generated.d.ts +2 -0
  136. package/esm/shaders/coverageBar.glsl.generated.js +4 -0
  137. package/esm/shaders/coverageBar.iface.generated.js +1 -1
  138. package/esm/shaders/coverageBar.wgsl.generated.d.ts +1 -0
  139. package/esm/shaders/coverageBar.wgsl.generated.js +3 -0
  140. package/esm/shaders/coverageClip.generated.d.ts +3 -0
  141. package/esm/shaders/coverageClip.generated.js +7 -0
  142. package/esm/shaders/coverageClip.glsl.generated.d.ts +2 -0
  143. package/esm/shaders/coverageClip.glsl.generated.js +4 -0
  144. package/esm/shaders/coverageClip.iface.generated.d.ts +85 -0
  145. package/esm/shaders/coverageClip.iface.generated.js +114 -0
  146. package/esm/shaders/coverageClip.wgsl.generated.d.ts +1 -0
  147. package/esm/shaders/coverageClip.wgsl.generated.js +3 -0
  148. package/esm/shaders/coverageIndicator.generated.d.ts +2 -3
  149. package/esm/shaders/coverageIndicator.generated.js +4 -3
  150. package/esm/shaders/coverageIndicator.glsl.generated.d.ts +2 -0
  151. package/esm/shaders/coverageIndicator.glsl.generated.js +4 -0
  152. package/esm/shaders/coverageIndicator.iface.generated.js +1 -1
  153. package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +1 -0
  154. package/esm/shaders/coverageIndicator.wgsl.generated.js +3 -0
  155. package/esm/shaders/coverageInterbase.generated.d.ts +2 -3
  156. package/esm/shaders/coverageInterbase.generated.js +4 -3
  157. package/esm/shaders/coverageInterbase.glsl.generated.d.ts +2 -0
  158. package/esm/shaders/coverageInterbase.glsl.generated.js +4 -0
  159. package/esm/shaders/coverageInterbase.iface.generated.js +1 -1
  160. package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -0
  161. package/esm/shaders/coverageInterbase.wgsl.generated.js +3 -0
  162. package/esm/shaders/coverageMod.generated.d.ts +2 -3
  163. package/esm/shaders/coverageMod.generated.js +4 -3
  164. package/esm/shaders/coverageMod.glsl.generated.d.ts +2 -0
  165. package/esm/shaders/coverageMod.glsl.generated.js +4 -0
  166. package/esm/shaders/coverageMod.iface.generated.js +1 -1
  167. package/esm/shaders/coverageMod.wgsl.generated.d.ts +1 -0
  168. package/esm/shaders/coverageMod.wgsl.generated.js +3 -0
  169. package/esm/shaders/coverageSnp.generated.d.ts +2 -3
  170. package/esm/shaders/coverageSnp.generated.js +4 -3
  171. package/esm/shaders/coverageSnp.glsl.generated.d.ts +2 -0
  172. package/esm/shaders/coverageSnp.glsl.generated.js +4 -0
  173. package/esm/shaders/coverageSnp.iface.generated.js +1 -1
  174. package/esm/shaders/coverageSnp.wgsl.generated.d.ts +1 -0
  175. package/esm/shaders/coverageSnp.wgsl.generated.js +3 -0
  176. package/esm/shaders/curveDistance.js.generated.d.ts +5 -0
  177. package/esm/shaders/curveDistance.js.generated.js +87 -0
  178. package/esm/shaders/hpmath.js.generated.d.ts +2 -1
  179. package/esm/shaders/hpmath.js.generated.js +5 -2
  180. package/esm/shaders/lineCenterMark.generated.d.ts +3 -0
  181. package/esm/shaders/lineCenterMark.generated.js +7 -0
  182. package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -0
  183. package/esm/shaders/lineCenterMark.glsl.generated.js +4 -0
  184. package/esm/shaders/lineCenterMark.iface.generated.d.ts +104 -0
  185. package/esm/shaders/lineCenterMark.iface.generated.js +237 -0
  186. package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -0
  187. package/esm/shaders/lineCenterMark.wgsl.generated.js +3 -0
  188. package/esm/shaders/lineCommon.generated.d.ts +2 -0
  189. package/esm/shaders/lineCommon.generated.js +4 -0
  190. package/esm/shaders/lineStepMark.generated.d.ts +3 -0
  191. package/esm/shaders/lineStepMark.generated.js +7 -0
  192. package/esm/shaders/lineStepMark.glsl.generated.d.ts +2 -0
  193. package/esm/shaders/lineStepMark.glsl.generated.js +4 -0
  194. package/esm/shaders/lineStepMark.iface.generated.d.ts +98 -0
  195. package/esm/shaders/lineStepMark.iface.generated.js +225 -0
  196. package/esm/shaders/lineStepMark.wgsl.generated.d.ts +1 -0
  197. package/esm/shaders/lineStepMark.wgsl.generated.js +3 -0
  198. package/esm/shaders/linkMark.consts.generated.d.ts +14 -0
  199. package/esm/shaders/linkMark.consts.generated.js +16 -0
  200. package/esm/shaders/linkMark.generated.d.ts +4 -0
  201. package/esm/shaders/linkMark.generated.js +8 -0
  202. package/esm/shaders/linkMark.glsl.generated.d.ts +2 -0
  203. package/esm/shaders/linkMark.glsl.generated.js +4 -0
  204. package/esm/shaders/linkMark.iface.generated.d.ts +142 -0
  205. package/esm/shaders/linkMark.iface.generated.js +1829 -0
  206. package/esm/shaders/linkMark.js.generated.d.ts +7 -0
  207. package/esm/shaders/linkMark.js.generated.js +159 -0
  208. package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -0
  209. package/esm/shaders/linkMark.wgsl.generated.js +3 -0
  210. package/esm/shaders/markColor.generated.d.ts +9 -0
  211. package/esm/shaders/markColor.generated.js +11 -0
  212. package/esm/shaders/pointGlyph.js.generated.d.ts +2 -0
  213. package/esm/shaders/pointGlyph.js.generated.js +15 -0
  214. package/esm/shaders/pointMark.consts.generated.d.ts +7 -0
  215. package/esm/shaders/pointMark.consts.generated.js +9 -0
  216. package/esm/shaders/pointMark.generated.d.ts +4 -0
  217. package/esm/shaders/pointMark.generated.js +8 -0
  218. package/esm/shaders/pointMark.glsl.generated.d.ts +2 -0
  219. package/esm/shaders/pointMark.glsl.generated.js +4 -0
  220. package/esm/shaders/pointMark.iface.generated.d.ts +98 -0
  221. package/esm/shaders/pointMark.iface.generated.js +219 -0
  222. package/esm/shaders/pointMark.js.generated.d.ts +3 -0
  223. package/esm/shaders/pointMark.js.generated.js +93 -0
  224. package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -0
  225. package/esm/shaders/pointMark.wgsl.generated.js +3 -0
  226. package/esm/shaders/rowRect.generated.d.ts +0 -1
  227. package/esm/shaders/rowRect.generated.js +0 -1
  228. package/esm/shaders/rowTable.generated.d.ts +1 -0
  229. package/esm/shaders/rowTable.generated.js +3 -0
  230. package/esm/shaders/rowTable.js.generated.d.ts +5 -0
  231. package/esm/shaders/rowTable.js.generated.js +29 -0
  232. package/esm/shaders/scoreScale.generated.d.ts +2 -0
  233. package/esm/shaders/scoreScale.generated.js +4 -0
  234. package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
  235. package/esm/shaders/scoreScale.js.generated.js +50 -23
  236. package/esm/shaders/spanMark.generated.d.ts +3 -0
  237. package/esm/shaders/spanMark.generated.js +7 -0
  238. package/esm/shaders/spanMark.glsl.generated.d.ts +2 -0
  239. package/esm/shaders/spanMark.glsl.generated.js +4 -0
  240. package/esm/shaders/spanMark.iface.generated.d.ts +81 -0
  241. package/esm/shaders/spanMark.iface.generated.js +215 -0
  242. package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -0
  243. package/esm/shaders/spanMark.wgsl.generated.js +3 -0
  244. package/esm/sharedBackendKey.d.ts +18 -0
  245. package/esm/{keyedRenderingBackend.js → sharedBackendKey.js} +5 -0
  246. package/esm/slangPass.d.ts +17 -11
  247. package/esm/slangPass.js +12 -4
  248. package/esm/useGraphicsCapabilities.d.ts +1 -1
  249. package/esm/useGraphicsCapabilities.js +1 -1
  250. package/esm/useRenderingBackend.d.ts +13 -13
  251. package/esm/useRenderingBackend.js +58 -20
  252. package/esm/webgpuUtils.d.ts +3 -11
  253. package/esm/webgpuUtils.js +12 -33
  254. package/package.json +32 -7
  255. package/src/shaders/antialias.slang +46 -0
  256. package/src/shaders/arcHull.test.ts +267 -0
  257. package/src/shaders/barMark.generated.ts +11 -0
  258. package/src/shaders/barMark.glsl.generated.ts +6 -0
  259. package/src/shaders/barMark.iface.generated.ts +285 -0
  260. package/src/shaders/barMark.slang +167 -0
  261. package/src/shaders/barMark.wgsl.generated.ts +4 -0
  262. package/src/shaders/capsule.js.generated.ts +14 -0
  263. package/src/shaders/capsule.slang +13 -5
  264. package/src/shaders/clipStrip.generated.ts +6 -0
  265. package/src/shaders/clipStrip.js.generated.ts +22 -0
  266. package/src/shaders/clipStrip.slang +34 -0
  267. package/src/shaders/clipStrip.test.ts +39 -0
  268. package/src/shaders/colorPack.slang +5 -1
  269. package/src/shaders/colorRampLut.js.generated.ts +22 -0
  270. package/src/shaders/colorRampLut.slang +15 -1
  271. package/src/shaders/coverageBand.slang +64 -32
  272. package/src/shaders/coverageBar.generated.ts +6 -5
  273. package/src/shaders/coverageBar.glsl.generated.ts +6 -0
  274. package/src/shaders/coverageBar.iface.generated.ts +1 -1
  275. package/src/shaders/coverageBar.slang +10 -10
  276. package/src/shaders/coverageBar.wgsl.generated.ts +4 -0
  277. package/src/shaders/coverageClip.generated.ts +11 -0
  278. package/src/shaders/coverageClip.glsl.generated.ts +6 -0
  279. package/src/shaders/coverageClip.iface.generated.ts +172 -0
  280. package/src/shaders/coverageClip.slang +38 -0
  281. package/src/shaders/coverageClip.wgsl.generated.ts +4 -0
  282. package/src/shaders/coverageIndicator.generated.ts +6 -5
  283. package/src/shaders/coverageIndicator.glsl.generated.ts +6 -0
  284. package/src/shaders/coverageIndicator.iface.generated.ts +1 -1
  285. package/src/shaders/coverageIndicator.wgsl.generated.ts +4 -0
  286. package/src/shaders/coverageInterbase.generated.ts +6 -5
  287. package/src/shaders/coverageInterbase.glsl.generated.ts +6 -0
  288. package/src/shaders/coverageInterbase.iface.generated.ts +1 -1
  289. package/src/shaders/coverageInterbase.wgsl.generated.ts +4 -0
  290. package/src/shaders/coverageMod.generated.ts +6 -5
  291. package/src/shaders/coverageMod.glsl.generated.ts +6 -0
  292. package/src/shaders/coverageMod.iface.generated.ts +1 -1
  293. package/src/shaders/coverageMod.wgsl.generated.ts +4 -0
  294. package/src/shaders/coverageSnp.generated.ts +6 -5
  295. package/src/shaders/coverageSnp.glsl.generated.ts +6 -0
  296. package/src/shaders/coverageSnp.iface.generated.ts +1 -1
  297. package/src/shaders/coverageSnp.wgsl.generated.ts +4 -0
  298. package/src/shaders/curveDistance.js.generated.ts +91 -0
  299. package/src/shaders/curveDistance.slang +265 -0
  300. package/src/shaders/hpClampFloor.test.ts +87 -0
  301. package/src/shaders/hpmath.js.generated.ts +6 -2
  302. package/src/shaders/hpmath.slang +100 -48
  303. package/src/shaders/insetValueScale.test.ts +67 -0
  304. package/src/shaders/lineCenterMark.generated.ts +11 -0
  305. package/src/shaders/lineCenterMark.glsl.generated.ts +6 -0
  306. package/src/shaders/lineCenterMark.iface.generated.ts +328 -0
  307. package/src/shaders/lineCenterMark.slang +127 -0
  308. package/src/shaders/lineCenterMark.wgsl.generated.ts +4 -0
  309. package/src/shaders/lineCommon.generated.ts +6 -0
  310. package/src/shaders/lineCommon.slang +102 -0
  311. package/src/shaders/lineStepMark.generated.ts +11 -0
  312. package/src/shaders/lineStepMark.glsl.generated.ts +6 -0
  313. package/src/shaders/lineStepMark.iface.generated.ts +311 -0
  314. package/src/shaders/lineStepMark.slang +125 -0
  315. package/src/shaders/lineStepMark.wgsl.generated.ts +4 -0
  316. package/src/shaders/linkMark.consts.generated.ts +30 -0
  317. package/src/shaders/linkMark.generated.ts +12 -0
  318. package/src/shaders/linkMark.glsl.generated.ts +6 -0
  319. package/src/shaders/linkMark.iface.generated.ts +1954 -0
  320. package/src/shaders/linkMark.js.generated.ts +167 -0
  321. package/src/shaders/linkMark.slang +439 -0
  322. package/src/shaders/linkMark.wgsl.generated.ts +4 -0
  323. package/src/shaders/markColor.generated.ts +20 -0
  324. package/src/shaders/markColor.slang +128 -0
  325. package/src/shaders/pointGlyph.js.generated.ts +20 -0
  326. package/src/shaders/pointGlyph.slang +23 -7
  327. package/src/shaders/pointGlyphPad.test.ts +242 -0
  328. package/src/shaders/pointMark.consts.generated.ts +16 -0
  329. package/src/shaders/pointMark.generated.ts +12 -0
  330. package/src/shaders/pointMark.glsl.generated.ts +6 -0
  331. package/src/shaders/pointMark.iface.generated.ts +304 -0
  332. package/src/shaders/pointMark.js.generated.ts +99 -0
  333. package/src/shaders/pointMark.slang +227 -0
  334. package/src/shaders/pointMark.wgsl.generated.ts +4 -0
  335. package/src/shaders/rowRect.generated.ts +0 -2
  336. package/src/shaders/rowRect.slang +39 -58
  337. package/src/shaders/rowTable.generated.ts +4 -0
  338. package/src/shaders/rowTable.js.generated.ts +37 -0
  339. package/src/shaders/rowTable.slang +98 -0
  340. package/src/shaders/scoreScale.generated.ts +6 -0
  341. package/src/shaders/scoreScale.js.generated.ts +48 -23
  342. package/src/shaders/scoreScale.slang +49 -55
  343. package/src/shaders/sdEllipse.test.ts +211 -0
  344. package/src/shaders/spanMark.generated.ts +11 -0
  345. package/src/shaders/spanMark.glsl.generated.ts +6 -0
  346. package/src/shaders/spanMark.iface.generated.ts +289 -0
  347. package/src/shaders/spanMark.slang +77 -0
  348. package/src/shaders/spanMark.wgsl.generated.ts +4 -0
  349. package/src/shaders/valueScale.slang +26 -0
  350. package/src/shaders/wideCircleLeg.test.ts +85 -0
  351. package/esm/globalRenderingBackend.d.ts +0 -90
  352. package/esm/globalRenderingBackend.js +0 -53
  353. 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,17 @@ 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. Each pass
64
+ // compiles as soon as its own WGSL arrives.
109
65
  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))));
66
+ const built = await Promise.all(descriptors.map(async (desc) => {
67
+ const { WGSL_SOURCE } = await desc.source.wgsl();
68
+ return getOrBuildPipeline(device, pipelineRecipe(desc, WGSL_SOURCE, sampleCount), recipe => buildPipeline(device, recipe, desc.id));
69
+ }));
111
70
  return new Map(descriptors.map((desc, i) => [desc.id, built[i]]));
112
71
  }
113
72
  // Behavioral parity with WebGL2Hal is enforced by tests, not by this file:
@@ -124,30 +83,30 @@ export class WebGPUHal extends GpuHalBase {
124
83
  canvas;
125
84
  context;
126
85
  pipelines;
86
+ // Each pass's textures, by the sampler that reads each.
127
87
  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`.
88
+ // Every pass's bind group, by the pass drawn. A pass that binds no texture
89
+ // has one from construction, shared with each pass over the same layout; a
90
+ // textured pass gets its own when its texture arrives, dropped when
91
+ // `uploadTexture` replaces the texture. The layouts are the device's, shared
92
+ // with every display on it; the groups are this HAL's, since they name its
93
+ // uniform ring buffer.
131
94
  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
95
  // Uniform ring buffer: holds up to MAX_UNIFORM_SLOTS sets of uniforms so
137
96
  // that all draw calls in a frame can reference different uniform data via
138
97
  // dynamic offsets, enabling a single command encoder + submit per frame.
139
98
  alignedUniformSize;
140
99
  uniformRingBuffer;
141
100
  uniformStaging;
101
+ uniformSlots = INITIAL_UNIFORM_SLOTS;
142
102
  uniformSlot = 0;
103
+ // Rings this frame outgrew, each with the staged bytes its encoded draws
104
+ // read: written at submit, released after it.
105
+ outgrownRings = [];
143
106
  // Once per HAL, not once per frame: at 60fps a per-frame warning is a
144
107
  // console the developer stops reading, and the fact is about the renderer
145
108
  // rather than about this frame.
146
109
  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
110
  // Samples per pixel this display renders at, stated by whoever built it (see
152
111
  // `RenderingBackendOptions.sampleCount`). Every render-pass, texture and
153
112
  // pipeline decision below reads it, so the two shapes cannot mismatch — and
@@ -175,7 +134,7 @@ export class WebGPUHal extends GpuHalBase {
175
134
  * selection spanning two sections re-uploads `OVERLAY_REGION` once per
176
135
  * section, inside the block loop.
177
136
  *
178
- * Deferring is what makes mid-frame replacement legal instead of merely
137
+ * Deferring the destroy makes mid-frame replacement legal instead of merely
179
138
  * warned about; synteny's `ensureUploaded` has always deleted a pass mid-frame
180
139
  * that the open pass never referenced, and that case was never the bug.
181
140
  */
@@ -196,26 +155,93 @@ export class WebGPUHal extends GpuHalBase {
196
155
  // than once per frame, and a reconfigure that failed is not attempted again.
197
156
  warnedSwapChainLoss = false;
198
157
  swapChainUnrecoverable = false;
199
- constructor(device, canvas, context, descriptors, uniformByteSize, sampleCount, pipelines, layouts) {
158
+ reportedSurfaceFailure = false;
159
+ constructor(device, canvas, context, descriptors, sampleCount, pipelines) {
200
160
  super(descriptors, 'WebGPUHal');
201
161
  this.device = device;
202
162
  this.canvas = canvas;
203
163
  this.context = context;
204
164
  this.pipelines = pipelines;
205
- this.layouts = layouts;
206
165
  this.sampleCount = sampleCount;
207
166
  // Align uniform slots to device requirements for dynamic offsets
208
167
  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
- });
168
+ this.alignedUniformSize =
169
+ Math.ceil(this.uniformByteSize / alignment) * alignment;
170
+ const ringSize = this.uniformSlots * this.alignedUniformSize;
171
+ this.uniformRingBuffer = this.createRing(ringSize);
215
172
  this.uniformStaging = new Uint8Array(ringSize);
216
- this.uniformOnlyBindGroup = createUniformOnlyBindGroup(device, layouts.uniformOnlyBindGroupLayout, this.uniformRingBuffer, this.alignedUniformSize);
173
+ this.bindUntexturedPasses();
217
174
  this.configureContext();
218
175
  }
176
+ createRing(size) {
177
+ return this.device.createBuffer({
178
+ size,
179
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
180
+ });
181
+ }
182
+ // Every pass's bind group names the ring buffer, so a new ring rebinds them:
183
+ // an untextured pass here, a textured one on its next draw (`bindTexture`).
184
+ bindUntexturedPasses() {
185
+ const byLayout = new Map();
186
+ for (const desc of this.descriptors.values()) {
187
+ if (desc.bindings.some(b => b.kind === 'texture')) {
188
+ this.passBindGroups.delete(desc.id);
189
+ continue;
190
+ }
191
+ const layout = this.bindGroupLayoutOf(desc.bindings);
192
+ let group = byLayout.get(layout);
193
+ if (!group) {
194
+ group = this.createBindGroup(desc.bindings, layout);
195
+ byLayout.set(layout, group);
196
+ }
197
+ this.passBindGroups.set(desc.id, group);
198
+ }
199
+ }
200
+ // Doubles the ring mid-frame. The draws already encoded bind the old buffer,
201
+ // so it takes the slots staged so far at submit and is released after.
202
+ growRing() {
203
+ const bytes = this.uniformSlot * this.alignedUniformSize;
204
+ this.outgrownRings.push({ buffer: this.uniformRingBuffer, bytes });
205
+ this.destroyWhenIdle(this.uniformRingBuffer);
206
+ this.uniformSlots = Math.min(this.uniformSlots * 2, MAX_UNIFORM_SLOTS);
207
+ const size = this.uniformSlots * this.alignedUniformSize;
208
+ this.uniformRingBuffer = this.createRing(size);
209
+ const staging = new Uint8Array(size);
210
+ staging.set(this.uniformStaging.subarray(0, bytes));
211
+ this.uniformStaging = staging;
212
+ this.bindUntexturedPasses();
213
+ }
214
+ bindGroupLayoutOf(bindings) {
215
+ return getPassLayout(this.device, bindGroupLayoutEntries(bindings))
216
+ .bindGroupLayout;
217
+ }
218
+ // One entry per binding in the pass's table, the same table its layout was
219
+ // built from, so the group cannot name a binding the layout lacks.
220
+ createBindGroup(bindings, layout, textures) {
221
+ return this.device.createBindGroup({
222
+ layout,
223
+ entries: bindings.map(b => ({
224
+ binding: b.index,
225
+ resource: this.bindingResource(b, textures?.get(b.name)),
226
+ })),
227
+ });
228
+ }
229
+ bindingResource(b, texture) {
230
+ if (b.kind === 'uniform') {
231
+ return {
232
+ buffer: this.uniformRingBuffer,
233
+ offset: 0,
234
+ size: this.alignedUniformSize,
235
+ };
236
+ }
237
+ if (texture && b.kind === 'texture') {
238
+ return texture.texture.createView();
239
+ }
240
+ if (texture && b.kind === 'sampler') {
241
+ return texture.sampler;
242
+ }
243
+ throw new Error(`[WebGPUHal] nothing to bind for the ${b.kind} '${b.name}' at binding ${b.index}`);
244
+ }
219
245
  limits() {
220
246
  const { maxBufferSize, maxTextureDimension2D } = this.device.limits;
221
247
  return {
@@ -245,17 +271,16 @@ export class WebGPUHal extends GpuHalBase {
245
271
  });
246
272
  noteCanvasConfigured(this.canvas, this);
247
273
  }
248
- static async create(canvas, descriptors, uniformByteSize, sampleCount) {
274
+ static async create(canvas, descriptors, sampleCount) {
249
275
  const device = await getGpuDevice();
250
276
  if (!device) {
251
277
  return null;
252
278
  }
253
279
  // 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);
280
+ // context type is permanent once acquired, so if a shader fails to load or
281
+ // compile here the canvas stays pristine and createGpuHal's WebGL2 fallback
282
+ // can still claim it — otherwise a partial WebGPU init would drop us all the
283
+ // way to Canvas2D on a WebGL2-capable machine.
259
284
  const pipelines = await resolvePipelines(device, descriptors, sampleCount);
260
285
  const context = canvas.getContext('webgpu');
261
286
  if (!context) {
@@ -266,9 +291,14 @@ export class WebGPUHal extends GpuHalBase {
266
291
  throw canvasContextError(canvas, 'webgpu');
267
292
  }
268
293
  noteCanvasContext(canvas, 'webgpu');
269
- return new WebGPUHal(device, canvas, context, descriptors, uniformByteSize, sampleCount, pipelines, layouts);
294
+ return new WebGPUHal(device, canvas, context, descriptors, sampleCount, pipelines);
270
295
  }
271
296
  resize(width, height) {
297
+ // A render autorun can fire between `dispose` and the model dropping its
298
+ // backend reference, and nothing destroys what a disposed HAL allocates.
299
+ if (this.disposed) {
300
+ return { x: getDpr(), y: getDpr() };
301
+ }
272
302
  const { changed, scale } = syncCanvasSize(this.canvas, width, height);
273
303
  // `!this.msaaTexture` covers the first frame and a rebuild that bailed. At
274
304
  // sampleCount 1 there is never a texture, so without the count in the
@@ -279,6 +309,11 @@ export class WebGPUHal extends GpuHalBase {
279
309
  }
280
310
  return scale;
281
311
  }
312
+ releaseRenderTargets() {
313
+ if (!this.disposed) {
314
+ this.recreateMsaaTexture(0, 0);
315
+ }
316
+ }
282
317
  recreateMsaaTexture(width, height) {
283
318
  this.msaaTexture?.destroy();
284
319
  this.msaaTexture = null;
@@ -309,50 +344,32 @@ export class WebGPUHal extends GpuHalBase {
309
344
  * (and a dropped draw) the moment one side samples a texture.
310
345
  *
311
346
  * 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.
347
+ * HAL-wide uniform ring buffer plus the pass's own texture/sampler. Returns
348
+ * undefined when a pass needs a texture that hasn't arrived yet; drawPass
349
+ * skips those.
317
350
  */
318
351
  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;
352
+ return this.passBindGroups.get(passId) ?? this.bindTexture(passId);
353
+ }
354
+ // Built once every texture the pass samples has arrived.
355
+ bindTexture(passId) {
356
+ const textures = this.passTextures.get(passId);
357
+ const desc = this.descriptors.get(passId);
358
+ if (!textures ||
359
+ !desc ||
360
+ desc.bindings.some(b => b.kind === 'texture' && !textures.has(b.name))) {
361
+ return undefined;
347
362
  }
348
- return this.uniformOnlyBindGroup;
363
+ const group = this.createBindGroup(desc.bindings, this.bindGroupLayoutOf(desc.bindings), textures);
364
+ this.passBindGroups.set(passId, group);
365
+ return group;
349
366
  }
350
367
  /**
351
368
  * The one place a GPU resource this HAL owns is released — see
352
369
  * {@link pendingDestroy} for why the frame decides when.
353
370
  *
354
371
  * Every buffer release routes here through the `RegionRegistry` destroy hook,
355
- * so `uploadBuffer`, `deleteBuffer`, `deleteRegion`, `pruneRegions` and
372
+ * so `uploadBuffer`, `deleteBuffer`, `deleteRegion` and
356
373
  * `dispose` are all covered without any of them knowing a frame is open.
357
374
  */
358
375
  destroyWhenIdle(resource) {
@@ -370,29 +387,42 @@ export class WebGPUHal extends GpuHalBase {
370
387
  this.pendingDestroy.length = 0;
371
388
  }
372
389
  createTexture(passId, binding, data, width, height) {
373
- const existing = this.passTextures.get(passId);
390
+ let byName = this.passTextures.get(passId);
391
+ if (!byName) {
392
+ byName = new Map();
393
+ this.passTextures.set(passId, byName);
394
+ }
395
+ const existing = byName.get(binding.name);
374
396
  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.
397
+ // Same hazard as a buffer, and no longer a hypothetical one: every
398
+ // `uploadTexture` in tree is a colour ramp, and since the ramp became a
399
+ // `defineMark` `texture` they are all written by `GpuMarkBackend`'s
400
+ // `bindRamp` from inside `drawRegion` — mid-frame, between an encoded
401
+ // draw and its submit. The deferral is what makes replacing one there
402
+ // safe.
381
403
  this.destroyWhenIdle(existing.texture);
382
404
  }
405
+ const bytes = data instanceof Uint8Array;
383
406
  const texture = this.device.createTexture({
384
407
  size: [width, height],
385
408
  format: 'rgba8unorm',
386
- usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
409
+ usage: GPUTextureUsage.TEXTURE_BINDING |
410
+ GPUTextureUsage.COPY_DST |
411
+ (bytes ? 0 : GPUTextureUsage.RENDER_ATTACHMENT),
387
412
  });
388
- this.device.queue.writeTexture({ texture }, data, { bytesPerRow: width * 4 }, { width, height });
413
+ if (bytes) {
414
+ this.device.queue.writeTexture({ texture }, data, { bytesPerRow: width * 4 }, { width, height });
415
+ }
416
+ else {
417
+ this.device.queue.copyExternalImageToTexture({ source: data }, { texture, premultipliedAlpha: true }, { width, height });
418
+ }
389
419
  const sampler = this.device.createSampler({
390
420
  magFilter: binding.filter,
391
421
  minFilter: binding.filter,
392
422
  addressModeU: 'clamp-to-edge',
393
423
  addressModeV: 'clamp-to-edge',
394
424
  });
395
- this.passTextures.set(passId, { texture, sampler });
425
+ byName.set(binding.name, { texture, sampler });
396
426
  // Drop the cached bind group so the next draw rebuilds it against the new
397
427
  // texture (and so a pass drawn before its first texture arrived stops
398
428
  // being skipped).
@@ -401,14 +431,17 @@ export class WebGPUHal extends GpuHalBase {
401
431
  writeUniforms(data) {
402
432
  if (this.currentEncoder) {
403
433
  // Inside a frame: stage data at the current slot for batched upload
404
- if (this.uniformSlot >= MAX_UNIFORM_SLOTS) {
434
+ if (this.uniformSlot >= this.uniformSlots &&
435
+ this.uniformSlots < MAX_UNIFORM_SLOTS) {
436
+ this.growRing();
437
+ }
438
+ if (this.uniformSlot >= this.uniformSlots) {
405
439
  console.error(`[WebGPUHal] uniform ring buffer exhausted at ${MAX_UNIFORM_SLOTS} ` +
406
440
  `writeUniforms calls in one frame — this write is dropped, so the ` +
407
441
  `paired draw renders with the previous batch's uniforms (wrong ` +
408
442
  `data, not last-frame-stale). This indicates a renderer doing far ` +
409
443
  `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).`);
444
+ `site before raising the cap.`);
412
445
  return;
413
446
  }
414
447
  const offset = this.uniformSlot * this.alignedUniformSize;
@@ -471,6 +504,21 @@ export class WebGPUHal extends GpuHalBase {
471
504
  }
472
505
  }
473
506
  }
507
+ /**
508
+ * Once per HAL, not once per frame: the refusal holds for every frame at this
509
+ * canvas size, and dotplot and synteny keep their canvas mounted through a
510
+ * `renderError`, so a per-frame report there never stops.
511
+ *
512
+ * Not terminal — a refusal tracks the canvas dimensions, so a resize can clear
513
+ * it. `swapChainUnrecoverable` stays for the case it was written for.
514
+ */
515
+ reportSurfaceFailure(message) {
516
+ if (this.reportedSurfaceFailure) {
517
+ return;
518
+ }
519
+ this.reportedSurfaceFailure = true;
520
+ this.oom.reportClassified(createGpuSurfaceLostError(message));
521
+ }
474
522
  beginFrame(clearR, clearG, clearB, clearA = 1) {
475
523
  // Skip the frame entirely rather than encode one that cannot be valid.
476
524
  // Zero-size canvas: nothing to draw. Missing MSAA target while MSAA is
@@ -486,10 +534,23 @@ export class WebGPUHal extends GpuHalBase {
486
534
  (this.sampleCount > 1 && !this.msaaView)) {
487
535
  return;
488
536
  }
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.
537
+ // A driver can refuse the canvas image without throwing — Dawn returns an
538
+ // error texture and an error view, and the frame encodes against it — so the
539
+ // acquisition needs a scope, and its own rather than the frame's: those stay
540
+ // pushed iff an encoder is created, and one left on the device's stack
541
+ // swallows every later frame's errors. Popped before the guard below, so
542
+ // every path balances. Awaiting the pop here would suspend across a
543
+ // GPU-process round trip and destroy the canvas texture mid-frame.
544
+ this.device.pushErrorScope('validation');
492
545
  const textureView = this.acquireTextureView();
546
+ void this.device
547
+ .popErrorScope()
548
+ .then(err => {
549
+ if (err) {
550
+ this.reportSurfaceFailure(err.message);
551
+ }
552
+ })
553
+ .catch(() => { });
493
554
  if (!textureView) {
494
555
  return;
495
556
  }
@@ -526,7 +587,7 @@ export class WebGPUHal extends GpuHalBase {
526
587
  ],
527
588
  });
528
589
  }
529
- drawPass(passId, regionKey, bufferPassId) {
590
+ drawPass(passId, regionKey, bufferPassId, verticesPerInstance) {
530
591
  if (!this.currentPass) {
531
592
  return;
532
593
  }
@@ -561,7 +622,7 @@ export class WebGPUHal extends GpuHalBase {
561
622
  this.currentPass.setPipeline(pipeline);
562
623
  this.currentPass.setBindGroup(0, bindGroup, [dynamicOffset]);
563
624
  this.currentPass.setVertexBuffer(0, regionBuf.dataBuffer);
564
- this.currentPass.draw(desc.verticesPerInstance, regionBuf.count);
625
+ this.currentPass.draw(verticesPerInstance ?? desc.verticesPerInstance, regionBuf.count);
565
626
  }
566
627
  /**
567
628
  * Close the frame's state whether the submit landed or threw.
@@ -594,6 +655,7 @@ export class WebGPUHal extends GpuHalBase {
594
655
  this.currentPass = null;
595
656
  this.currentEncoder = null;
596
657
  this.currentTextureView = null;
658
+ this.outgrownRings.length = 0;
597
659
  this.drainPendingDestroy();
598
660
  }
599
661
  endFrame() {
@@ -606,6 +668,9 @@ export class WebGPUHal extends GpuHalBase {
606
668
  if (this.currentPass) {
607
669
  this.currentPass.end();
608
670
  }
671
+ for (const ring of this.outgrownRings) {
672
+ this.device.queue.writeBuffer(ring.buffer, 0, this.uniformStaging, 0, ring.bytes);
673
+ }
609
674
  if (slotAtSubmit > 0) {
610
675
  const uploadSize = slotAtSubmit * this.alignedUniformSize;
611
676
  this.device.queue.writeBuffer(this.uniformRingBuffer, 0, this.uniformStaging, 0, uploadSize);
@@ -704,8 +769,10 @@ export class WebGPUHal extends GpuHalBase {
704
769
  }
705
770
  this.regions.deleteAll();
706
771
  this.uniformRingBuffer.destroy();
707
- for (const ts of this.passTextures.values()) {
708
- ts.texture.destroy();
772
+ for (const byName of this.passTextures.values()) {
773
+ for (const ts of byName.values()) {
774
+ ts.texture.destroy();
775
+ }
709
776
  }
710
777
  this.passTextures.clear();
711
778
  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 {};