@363045841yyt/klinechart-core 0.9.0-alpha.0 → 0.9.0-alpha.2

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 (372) hide show
  1. package/dist/controllers/createChartController.d.ts.map +1 -1
  2. package/dist/controllers/createChartController.js +31 -13
  3. package/dist/controllers/createChartController.js.map +1 -1
  4. package/dist/controllers/index.d.ts +1 -0
  5. package/dist/controllers/index.d.ts.map +1 -1
  6. package/dist/controllers/index.js.map +1 -1
  7. package/dist/controllers/types.d.ts +12 -0
  8. package/dist/controllers/types.d.ts.map +1 -1
  9. package/dist/engine/chart.d.ts +29 -11
  10. package/dist/engine/chart.d.ts.map +1 -1
  11. package/dist/engine/chart.js +147 -25
  12. package/dist/engine/chart.js.map +1 -1
  13. package/dist/engine/controller/interaction.d.ts +29 -4
  14. package/dist/engine/controller/interaction.d.ts.map +1 -1
  15. package/dist/engine/controller/interaction.js +78 -31
  16. package/dist/engine/controller/interaction.js.map +1 -1
  17. package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
  18. package/dist/engine/data/incrementalLoadHint.js +5 -1
  19. package/dist/engine/data/incrementalLoadHint.js.map +1 -1
  20. package/dist/engine/drawing/DrawingState.d.ts +19 -34
  21. package/dist/engine/drawing/DrawingState.d.ts.map +1 -1
  22. package/dist/engine/drawing/DrawingState.js +110 -92
  23. package/dist/engine/drawing/DrawingState.js.map +1 -1
  24. package/dist/engine/drawing/index.d.ts +4 -1
  25. package/dist/engine/drawing/index.d.ts.map +1 -1
  26. package/dist/engine/drawing/index.js +9 -4
  27. package/dist/engine/drawing/index.js.map +1 -1
  28. package/dist/engine/drawing/interaction.d.ts +9 -29
  29. package/dist/engine/drawing/interaction.d.ts.map +1 -1
  30. package/dist/engine/drawing/interaction.js +23 -40
  31. package/dist/engine/drawing/interaction.js.map +1 -1
  32. package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
  33. package/dist/engine/indicators/chartIndicatorManager.js +4 -12
  34. package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
  35. package/dist/engine/layout/chartPaneLayout.d.ts +0 -2
  36. package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -1
  37. package/dist/engine/layout/chartPaneLayout.js +5 -9
  38. package/dist/engine/layout/chartPaneLayout.js.map +1 -1
  39. package/dist/engine/layout/pane.d.ts +0 -39
  40. package/dist/engine/layout/pane.d.ts.map +1 -1
  41. package/dist/engine/layout/pane.js +0 -9
  42. package/dist/engine/layout/pane.js.map +1 -1
  43. package/dist/engine/paneRenderer.d.ts +1 -10
  44. package/dist/engine/paneRenderer.d.ts.map +1 -1
  45. package/dist/engine/paneRenderer.js +2 -19
  46. package/dist/engine/paneRenderer.js.map +1 -1
  47. package/dist/engine/render/chartRenderer.d.ts +49 -8
  48. package/dist/engine/render/chartRenderer.d.ts.map +1 -1
  49. package/dist/engine/render/chartRenderer.js +143 -72
  50. package/dist/engine/render/chartRenderer.js.map +1 -1
  51. package/dist/engine/renderers/Indicator/atr.js +3 -13
  52. package/dist/engine/renderers/Indicator/atr.js.map +1 -1
  53. package/dist/engine/renderers/Indicator/boll.d.ts.map +1 -1
  54. package/dist/engine/renderers/Indicator/boll.js +12 -24
  55. package/dist/engine/renderers/Indicator/boll.js.map +1 -1
  56. package/dist/engine/renderers/Indicator/cci.js +6 -13
  57. package/dist/engine/renderers/Indicator/cci.js.map +1 -1
  58. package/dist/engine/renderers/Indicator/chaikinVol.js +3 -11
  59. package/dist/engine/renderers/Indicator/chaikinVol.js.map +1 -1
  60. package/dist/engine/renderers/Indicator/cmf.js +3 -11
  61. package/dist/engine/renderers/Indicator/cmf.js.map +1 -1
  62. package/dist/engine/renderers/Indicator/dema.js +3 -11
  63. package/dist/engine/renderers/Indicator/dema.js.map +1 -1
  64. package/dist/engine/renderers/Indicator/donchian.js +3 -11
  65. package/dist/engine/renderers/Indicator/donchian.js.map +1 -1
  66. package/dist/engine/renderers/Indicator/ene.d.ts.map +1 -1
  67. package/dist/engine/renderers/Indicator/ene.js +12 -24
  68. package/dist/engine/renderers/Indicator/ene.js.map +1 -1
  69. package/dist/engine/renderers/Indicator/expma.d.ts.map +1 -1
  70. package/dist/engine/renderers/Indicator/expma.js +5 -11
  71. package/dist/engine/renderers/Indicator/expma.js.map +1 -1
  72. package/dist/engine/renderers/Indicator/fastk.js +6 -13
  73. package/dist/engine/renderers/Indicator/fastk.js.map +1 -1
  74. package/dist/engine/renderers/Indicator/fib.js +3 -11
  75. package/dist/engine/renderers/Indicator/fib.js.map +1 -1
  76. package/dist/engine/renderers/Indicator/hma.js +3 -11
  77. package/dist/engine/renderers/Indicator/hma.js.map +1 -1
  78. package/dist/engine/renderers/Indicator/hv.js +3 -11
  79. package/dist/engine/renderers/Indicator/hv.js.map +1 -1
  80. package/dist/engine/renderers/Indicator/ichimoku.js +24 -18
  81. package/dist/engine/renderers/Indicator/ichimoku.js.map +1 -1
  82. package/dist/engine/renderers/Indicator/kama.js +3 -11
  83. package/dist/engine/renderers/Indicator/kama.js.map +1 -1
  84. package/dist/engine/renderers/Indicator/keltner.js +3 -11
  85. package/dist/engine/renderers/Indicator/keltner.js.map +1 -1
  86. package/dist/engine/renderers/Indicator/kst.js +9 -17
  87. package/dist/engine/renderers/Indicator/kst.js.map +1 -1
  88. package/dist/engine/renderers/Indicator/ma.d.ts.map +1 -1
  89. package/dist/engine/renderers/Indicator/ma.js +10 -21
  90. package/dist/engine/renderers/Indicator/ma.js.map +1 -1
  91. package/dist/engine/renderers/Indicator/macd.js +14 -44
  92. package/dist/engine/renderers/Indicator/macd.js.map +1 -1
  93. package/dist/engine/renderers/Indicator/mfi.js +3 -11
  94. package/dist/engine/renderers/Indicator/mfi.js.map +1 -1
  95. package/dist/engine/renderers/Indicator/mom.js +6 -13
  96. package/dist/engine/renderers/Indicator/mom.js.map +1 -1
  97. package/dist/engine/renderers/Indicator/obv.js +3 -11
  98. package/dist/engine/renderers/Indicator/obv.js.map +1 -1
  99. package/dist/engine/renderers/Indicator/parkinson.js +3 -11
  100. package/dist/engine/renderers/Indicator/parkinson.js.map +1 -1
  101. package/dist/engine/renderers/Indicator/pvt.js +3 -11
  102. package/dist/engine/renderers/Indicator/pvt.js.map +1 -1
  103. package/dist/engine/renderers/Indicator/roc.js +3 -11
  104. package/dist/engine/renderers/Indicator/roc.js.map +1 -1
  105. package/dist/engine/renderers/Indicator/rsi.js +12 -20
  106. package/dist/engine/renderers/Indicator/rsi.js.map +1 -1
  107. package/dist/engine/renderers/Indicator/shared/webglBand.d.ts +0 -2
  108. package/dist/engine/renderers/Indicator/shared/webglBand.d.ts.map +1 -1
  109. package/dist/engine/renderers/Indicator/shared/webglBand.js +0 -6
  110. package/dist/engine/renderers/Indicator/shared/webglBand.js.map +1 -1
  111. package/dist/engine/renderers/Indicator/stoch.js +9 -17
  112. package/dist/engine/renderers/Indicator/stoch.js.map +1 -1
  113. package/dist/engine/renderers/Indicator/tema.js +3 -11
  114. package/dist/engine/renderers/Indicator/tema.js.map +1 -1
  115. package/dist/engine/renderers/Indicator/trix.js +3 -11
  116. package/dist/engine/renderers/Indicator/trix.js.map +1 -1
  117. package/dist/engine/renderers/Indicator/vma.js +3 -11
  118. package/dist/engine/renderers/Indicator/vma.js.map +1 -1
  119. package/dist/engine/renderers/Indicator/vwap.js +3 -11
  120. package/dist/engine/renderers/Indicator/vwap.js.map +1 -1
  121. package/dist/engine/renderers/Indicator/wma.js +3 -11
  122. package/dist/engine/renderers/Indicator/wma.js.map +1 -1
  123. package/dist/engine/renderers/Indicator/wmsr.js +6 -13
  124. package/dist/engine/renderers/Indicator/wmsr.js.map +1 -1
  125. package/dist/engine/renderers/candle.d.ts.map +1 -1
  126. package/dist/engine/renderers/candle.js +21 -28
  127. package/dist/engine/renderers/candle.js.map +1 -1
  128. package/dist/engine/renderers/candleViaRenderer.d.ts +19 -0
  129. package/dist/engine/renderers/candleViaRenderer.d.ts.map +1 -0
  130. package/dist/engine/renderers/candleViaRenderer.js +15 -0
  131. package/dist/engine/renderers/candleViaRenderer.js.map +1 -0
  132. package/dist/engine/renderers/linesViaRenderer.d.ts +55 -0
  133. package/dist/engine/renderers/linesViaRenderer.d.ts.map +1 -0
  134. package/dist/engine/renderers/linesViaRenderer.js +169 -0
  135. package/dist/engine/renderers/linesViaRenderer.js.map +1 -0
  136. package/dist/engine/renderers/rectsViaRenderer.d.ts +18 -0
  137. package/dist/engine/renderers/rectsViaRenderer.d.ts.map +1 -0
  138. package/dist/engine/renderers/rectsViaRenderer.js +63 -0
  139. package/dist/engine/renderers/rectsViaRenderer.js.map +1 -0
  140. package/dist/engine/renderers/subVolume.js +7 -26
  141. package/dist/engine/renderers/subVolume.js.map +1 -1
  142. package/dist/engine/renderers/timeAxis.js +1 -1
  143. package/dist/engine/renderers/timeAxis.js.map +1 -1
  144. package/dist/engine/state/chartStateKernel.d.ts +11 -2
  145. package/dist/engine/state/chartStateKernel.d.ts.map +1 -1
  146. package/dist/engine/state/chartStateKernel.js +28 -12
  147. package/dist/engine/state/chartStateKernel.js.map +1 -1
  148. package/dist/engine/state/index.d.ts +1 -1
  149. package/dist/engine/state/index.d.ts.map +1 -1
  150. package/dist/engine/state/index.js +1 -1
  151. package/dist/engine/state/index.js.map +1 -1
  152. package/dist/engine/state/interactionState.d.ts +15 -7
  153. package/dist/engine/state/interactionState.d.ts.map +1 -1
  154. package/dist/engine/state/interactionState.js +55 -60
  155. package/dist/engine/state/interactionState.js.map +1 -1
  156. package/dist/engine/state/rendererState.d.ts +14 -0
  157. package/dist/engine/state/rendererState.d.ts.map +1 -0
  158. package/dist/engine/state/rendererState.js +25 -0
  159. package/dist/engine/state/rendererState.js.map +1 -0
  160. package/dist/engine/state/themeState.d.ts +11 -4
  161. package/dist/engine/state/themeState.d.ts.map +1 -1
  162. package/dist/engine/state/themeState.js +13 -5
  163. package/dist/engine/state/themeState.js.map +1 -1
  164. package/dist/engine/state/viewportState.d.ts.map +1 -1
  165. package/dist/engine/state/viewportState.js +7 -3
  166. package/dist/engine/state/viewportState.js.map +1 -1
  167. package/dist/engine/subPaneManager.d.ts.map +1 -1
  168. package/dist/engine/subPaneManager.js +2 -10
  169. package/dist/engine/subPaneManager.js.map +1 -1
  170. package/dist/foundation/config/chartSettings.d.ts +16 -4
  171. package/dist/foundation/config/chartSettings.d.ts.map +1 -1
  172. package/dist/foundation/config/chartSettings.js +21 -6
  173. package/dist/foundation/config/chartSettings.js.map +1 -1
  174. package/dist/foundation/plugin/index.d.ts +0 -1
  175. package/dist/foundation/plugin/index.d.ts.map +1 -1
  176. package/dist/foundation/plugin/rendererPluginManager.d.ts +1 -22
  177. package/dist/foundation/plugin/rendererPluginManager.d.ts.map +1 -1
  178. package/dist/foundation/plugin/rendererPluginManager.js +0 -84
  179. package/dist/foundation/plugin/rendererPluginManager.js.map +1 -1
  180. package/dist/foundation/plugin/types.d.ts +11 -17
  181. package/dist/foundation/plugin/types.d.ts.map +1 -1
  182. package/dist/foundation/plugin/types.js.map +1 -1
  183. package/dist/foundation/reactivity/frameTransaction.d.ts +71 -0
  184. package/dist/foundation/reactivity/frameTransaction.d.ts.map +1 -0
  185. package/dist/foundation/reactivity/frameTransaction.js +170 -0
  186. package/dist/foundation/reactivity/frameTransaction.js.map +1 -0
  187. package/dist/foundation/reactivity/index.d.ts +4 -2
  188. package/dist/foundation/reactivity/index.d.ts.map +1 -1
  189. package/dist/foundation/reactivity/index.js +2 -1
  190. package/dist/foundation/reactivity/index.js.map +1 -1
  191. package/dist/foundation/reactivity/signal.d.ts +20 -0
  192. package/dist/foundation/reactivity/signal.d.ts.map +1 -1
  193. package/dist/foundation/reactivity/signal.js +27 -0
  194. package/dist/foundation/reactivity/signal.js.map +1 -1
  195. package/dist/foundation/tokens/theme-dark.d.ts.map +1 -1
  196. package/dist/foundation/tokens/theme-dark.js +2 -1
  197. package/dist/foundation/tokens/theme-dark.js.map +1 -1
  198. package/dist/foundation/tokens/theme-light.d.ts.map +1 -1
  199. package/dist/foundation/tokens/theme-light.js +2 -1
  200. package/dist/foundation/tokens/theme-light.js.map +1 -1
  201. package/dist/rendering/render/Renderer.d.ts +29 -14
  202. package/dist/rendering/render/Renderer.d.ts.map +1 -1
  203. package/dist/rendering/render/Renderer.js +3 -10
  204. package/dist/rendering/render/Renderer.js.map +1 -1
  205. package/dist/rendering/render/SurfaceBackend.d.ts +5 -7
  206. package/dist/rendering/render/SurfaceBackend.d.ts.map +1 -1
  207. package/dist/rendering/render/SurfaceBackend.js +5 -7
  208. package/dist/rendering/render/SurfaceBackend.js.map +1 -1
  209. package/dist/rendering/render/createCanvas2DRenderer.d.ts +3 -0
  210. package/dist/rendering/render/createCanvas2DRenderer.d.ts.map +1 -0
  211. package/dist/rendering/render/createCanvas2DRenderer.js +74 -0
  212. package/dist/rendering/render/createCanvas2DRenderer.js.map +1 -0
  213. package/dist/rendering/render/createDefaultRendererHost.d.ts +4 -0
  214. package/dist/rendering/render/createDefaultRendererHost.d.ts.map +1 -0
  215. package/dist/rendering/render/createDefaultRendererHost.js +54 -0
  216. package/dist/rendering/render/createDefaultRendererHost.js.map +1 -0
  217. package/dist/rendering/render/createWebGLRenderer.d.ts.map +1 -1
  218. package/dist/rendering/render/createWebGLRenderer.js +42 -80
  219. package/dist/rendering/render/createWebGLRenderer.js.map +1 -1
  220. package/dist/rendering/render/createWebGPURenderer.d.ts +11 -0
  221. package/dist/rendering/render/createWebGPURenderer.d.ts.map +1 -0
  222. package/dist/rendering/render/createWebGPURenderer.js +619 -0
  223. package/dist/rendering/render/createWebGPURenderer.js.map +1 -0
  224. package/dist/rendering/render/createWebGPUSurfaceBackend.d.ts +15 -0
  225. package/dist/rendering/render/createWebGPUSurfaceBackend.d.ts.map +1 -0
  226. package/dist/rendering/render/createWebGPUSurfaceBackend.js +92 -0
  227. package/dist/rendering/render/createWebGPUSurfaceBackend.js.map +1 -0
  228. package/dist/rendering/render/frameMetrics.d.ts +19 -0
  229. package/dist/rendering/render/frameMetrics.d.ts.map +1 -0
  230. package/dist/rendering/render/frameMetrics.js +43 -0
  231. package/dist/rendering/render/frameMetrics.js.map +1 -0
  232. package/dist/rendering/render/index.d.ts +10 -0
  233. package/dist/rendering/render/index.d.ts.map +1 -1
  234. package/dist/rendering/render/index.js +6 -0
  235. package/dist/rendering/render/index.js.map +1 -1
  236. package/dist/rendering/render/rendererHost.d.ts +34 -0
  237. package/dist/rendering/render/rendererHost.d.ts.map +1 -0
  238. package/dist/rendering/render/rendererHost.js +144 -0
  239. package/dist/rendering/render/rendererHost.js.map +1 -0
  240. package/dist/rendering/render/webgpuResourceTable.d.ts +25 -0
  241. package/dist/rendering/render/webgpuResourceTable.d.ts.map +1 -0
  242. package/dist/rendering/render/webgpuResourceTable.js +54 -0
  243. package/dist/rendering/render/webgpuResourceTable.js.map +1 -0
  244. package/dist/rendering/scene/createLayerFromPlugin.d.ts.map +1 -1
  245. package/dist/rendering/scene/createLayerFromPlugin.js +10 -2
  246. package/dist/rendering/scene/createLayerFromPlugin.js.map +1 -1
  247. package/dist/rendering/scene/layerRegistry.d.ts +3 -5
  248. package/dist/rendering/scene/layerRegistry.d.ts.map +1 -1
  249. package/dist/rendering/scene/layerRegistry.js +1 -1
  250. package/dist/rendering/scene/layerRegistry.js.map +1 -1
  251. package/dist/rendering/scene/retainedScene.d.ts +60 -0
  252. package/dist/rendering/scene/retainedScene.d.ts.map +1 -0
  253. package/dist/rendering/scene/retainedScene.js +42 -0
  254. package/dist/rendering/scene/retainedScene.js.map +1 -0
  255. package/dist/version.d.ts +1 -1
  256. package/dist/version.js +1 -1
  257. package/package.json +2 -1
  258. package/src/__tests__/signal.test.ts +47 -1
  259. package/src/controllers/__tests__/createChartController.theme.test.ts +102 -0
  260. package/src/controllers/createChartController.ts +36 -12
  261. package/src/controllers/index.ts +5 -0
  262. package/src/controllers/types.ts +14 -0
  263. package/src/engine/__tests__/chart.dpr.test.ts +64 -0
  264. package/src/engine/__tests__/paneRenderer.resize.test.ts +0 -7
  265. package/src/engine/__tests__/renderSinglePath.test.ts +121 -0
  266. package/src/engine/chart.ts +172 -25
  267. package/src/engine/controller/__tests__/interaction.dpr.test.ts +45 -41
  268. package/src/engine/controller/interaction.ts +81 -30
  269. package/src/engine/data/__tests__/chartDataManager.incrementalLoad.test.ts +1 -0
  270. package/src/engine/data/incrementalLoadHint.ts +6 -1
  271. package/src/engine/drawing/DrawingState.ts +107 -89
  272. package/src/engine/drawing/__tests__/drawingState.workCopy.test.ts +95 -36
  273. package/src/engine/drawing/__tests__/drawingStore.projection.test.ts +22 -0
  274. package/src/engine/drawing/index.ts +12 -4
  275. package/src/engine/drawing/interaction.ts +25 -40
  276. package/src/engine/indicators/chartIndicatorManager.ts +6 -16
  277. package/src/engine/layout/chartPaneLayout.ts +6 -11
  278. package/src/engine/layout/pane.ts +0 -44
  279. package/src/engine/paneRenderer.ts +3 -34
  280. package/src/engine/render/chartRenderer.ts +181 -81
  281. package/src/engine/renderers/Indicator/atr.ts +5 -17
  282. package/src/engine/renderers/Indicator/boll.ts +15 -26
  283. package/src/engine/renderers/Indicator/cci.ts +7 -17
  284. package/src/engine/renderers/Indicator/chaikinVol.ts +3 -15
  285. package/src/engine/renderers/Indicator/cmf.ts +3 -15
  286. package/src/engine/renderers/Indicator/dema.ts +3 -15
  287. package/src/engine/renderers/Indicator/donchian.ts +3 -12
  288. package/src/engine/renderers/Indicator/ene.ts +15 -25
  289. package/src/engine/renderers/Indicator/expma.ts +4 -13
  290. package/src/engine/renderers/Indicator/fastk.ts +7 -17
  291. package/src/engine/renderers/Indicator/fib.ts +3 -12
  292. package/src/engine/renderers/Indicator/hma.ts +3 -15
  293. package/src/engine/renderers/Indicator/hv.ts +3 -15
  294. package/src/engine/renderers/Indicator/ichimoku.ts +21 -24
  295. package/src/engine/renderers/Indicator/kama.ts +3 -15
  296. package/src/engine/renderers/Indicator/keltner.ts +3 -12
  297. package/src/engine/renderers/Indicator/kst.ts +9 -20
  298. package/src/engine/renderers/Indicator/ma.ts +9 -21
  299. package/src/engine/renderers/Indicator/macd.ts +27 -102
  300. package/src/engine/renderers/Indicator/mfi.ts +3 -15
  301. package/src/engine/renderers/Indicator/mom.ts +7 -17
  302. package/src/engine/renderers/Indicator/obv.ts +3 -15
  303. package/src/engine/renderers/Indicator/parkinson.ts +3 -15
  304. package/src/engine/renderers/Indicator/pvt.ts +3 -15
  305. package/src/engine/renderers/Indicator/roc.ts +3 -15
  306. package/src/engine/renderers/Indicator/rsi.ts +12 -23
  307. package/src/engine/renderers/Indicator/shared/webglBand.ts +0 -13
  308. package/src/engine/renderers/Indicator/stoch.ts +9 -20
  309. package/src/engine/renderers/Indicator/tema.ts +3 -15
  310. package/src/engine/renderers/Indicator/trix.ts +3 -12
  311. package/src/engine/renderers/Indicator/vma.ts +3 -15
  312. package/src/engine/renderers/Indicator/vwap.ts +3 -15
  313. package/src/engine/renderers/Indicator/wma.ts +3 -15
  314. package/src/engine/renderers/Indicator/wmsr.ts +7 -17
  315. package/src/engine/renderers/__tests__/candle.sceneRenderer.test.ts +208 -0
  316. package/src/engine/renderers/__tests__/candleViaRenderer.test.ts +100 -0
  317. package/src/engine/renderers/__tests__/linesViaRenderer.test.ts +263 -0
  318. package/src/engine/renderers/__tests__/rectsViaRenderer.test.ts +87 -0
  319. package/src/engine/renderers/candle.ts +26 -69
  320. package/src/engine/renderers/candleViaRenderer.ts +38 -0
  321. package/src/engine/renderers/linesViaRenderer.ts +226 -0
  322. package/src/engine/renderers/rectsViaRenderer.ts +83 -0
  323. package/src/engine/renderers/subVolume.ts +9 -61
  324. package/src/engine/renderers/timeAxis.ts +1 -1
  325. package/src/engine/state/__tests__/effectiveTheme.test.ts +49 -0
  326. package/src/engine/state/__tests__/rendererState.test.ts +36 -0
  327. package/src/engine/state/__tests__/settingsState.test.ts +1 -1
  328. package/src/engine/state/chartStateKernel.ts +38 -17
  329. package/src/engine/state/index.ts +6 -1
  330. package/src/engine/state/interactionState.ts +73 -102
  331. package/src/engine/state/rendererState.ts +32 -0
  332. package/src/engine/state/themeState.ts +15 -8
  333. package/src/engine/state/viewportState.ts +6 -3
  334. package/src/engine/subPaneManager.ts +2 -16
  335. package/src/foundation/config/__tests__/chartSettings.test.ts +25 -0
  336. package/src/foundation/config/chartSettings.ts +24 -7
  337. package/src/foundation/plugin/index.ts +0 -1
  338. package/src/foundation/plugin/rendererPluginManager.ts +0 -99
  339. package/src/foundation/plugin/types.ts +11 -18
  340. package/src/foundation/reactivity/__tests__/frameTransaction.test.ts +306 -0
  341. package/src/foundation/reactivity/frameTransaction.ts +239 -0
  342. package/src/foundation/reactivity/index.ts +8 -0
  343. package/src/foundation/reactivity/signal.ts +39 -0
  344. package/src/foundation/tokens/__tests__/__snapshots__/baseline.test.ts.snap +2 -2
  345. package/src/foundation/tokens/theme-dark.ts +2 -1
  346. package/src/foundation/tokens/theme-light.ts +2 -1
  347. package/src/rendering/render/Renderer.ts +27 -14
  348. package/src/rendering/render/SurfaceBackend.ts +5 -7
  349. package/src/rendering/render/__tests__/canvas2DRenderer.test.ts +34 -0
  350. package/src/rendering/render/__tests__/contract.test.ts +2 -2
  351. package/src/rendering/render/__tests__/frameMetrics.test.ts +24 -0
  352. package/src/rendering/render/__tests__/rendererHost.test.ts +157 -0
  353. package/src/rendering/render/__tests__/webglRenderer.fallback.test.ts +14 -74
  354. package/src/rendering/render/__tests__/webglRenderer.test.ts +64 -6
  355. package/src/rendering/render/__tests__/webgpuRenderer.test.ts +370 -0
  356. package/src/rendering/render/__tests__/webgpuResourceTable.test.ts +62 -0
  357. package/src/rendering/render/__tests__/webgpuSurfaceBackend.test.ts +120 -0
  358. package/src/rendering/render/createCanvas2DRenderer.ts +83 -0
  359. package/src/rendering/render/createDefaultRendererHost.ts +74 -0
  360. package/src/rendering/render/createWebGLRenderer.ts +41 -87
  361. package/src/rendering/render/createWebGPURenderer.ts +677 -0
  362. package/src/rendering/render/createWebGPUSurfaceBackend.ts +106 -0
  363. package/src/rendering/render/frameMetrics.ts +54 -0
  364. package/src/rendering/render/index.ts +30 -0
  365. package/src/rendering/render/rendererHost.ts +210 -0
  366. package/src/rendering/render/webgpuResourceTable.ts +87 -0
  367. package/src/rendering/scene/__tests__/layerFromPlugin.test.ts +29 -2
  368. package/src/rendering/scene/__tests__/retainedScene.test.ts +59 -0
  369. package/src/rendering/scene/createLayerFromPlugin.ts +9 -2
  370. package/src/rendering/scene/layerRegistry.ts +4 -6
  371. package/src/rendering/scene/retainedScene.ts +101 -0
  372. package/src/version.ts +1 -1
@@ -0,0 +1,677 @@
1
+ import type {
2
+ BufferHandle,
3
+ BufferUsage,
4
+ ComputePipelineHandle,
5
+ DispatchComputeParams,
6
+ DrawInstancesParams,
7
+ DrawLineStrip,
8
+ DrawLinesParams,
9
+ PipelineHandle,
10
+ Renderer,
11
+ } from './Renderer'
12
+ import { createWebGPUSurfaceBackend, type WebGPUSurfaceBackend } from './createWebGPUSurfaceBackend'
13
+ import { createFrameMetrics } from './frameMetrics'
14
+ import { createWebGPUResourceTable } from './webgpuResourceTable'
15
+
16
+ type PipelineType = 'candle' | 'line' | 'fill'
17
+
18
+ type BufferRecord = {
19
+ buffer: GPUBuffer
20
+ size: number
21
+ }
22
+
23
+ type PipelineRecord = {
24
+ type: PipelineType
25
+ }
26
+
27
+ export type CreateWebGPURendererOptions = {
28
+ gpu?: GPU
29
+ canvas?: HTMLCanvasElement
30
+ onDeviceLost?: (info: GPUDeviceLostInfo) => void
31
+ /** 可选帧指标探针;默认使用模块级 createFrameMetrics */
32
+ metrics?: ReturnType<typeof createFrameMetrics>
33
+ }
34
+
35
+ const GPU_BUFFER_COPY_DST = globalThis.GPUBufferUsage?.COPY_DST ?? 0x0008
36
+ const GPU_BUFFER_INDEX = globalThis.GPUBufferUsage?.INDEX ?? 0x0010
37
+ const GPU_BUFFER_VERTEX = globalThis.GPUBufferUsage?.VERTEX ?? 0x0020
38
+ const GPU_BUFFER_UNIFORM = globalThis.GPUBufferUsage?.UNIFORM ?? 0x0040
39
+ const GPU_BUFFER_STORAGE = globalThis.GPUBufferUsage?.STORAGE ?? 0x0080
40
+ const GPU_TEXTURE_RENDER_ATTACHMENT = globalThis.GPUTextureUsage?.RENDER_ATTACHMENT ?? 0x10
41
+
42
+ const RECT_SHADER = `
43
+ struct Uniforms {
44
+ resolution: vec2f,
45
+ scrollLeft: f32,
46
+ padding: f32,
47
+ color: vec4f,
48
+ }
49
+
50
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
51
+
52
+ @vertex
53
+ fn vertexMain(@builtin(vertex_index) vertexIndex: u32, @location(0) rect: vec4f) -> @builtin(position) vec4f {
54
+ let corners = array<vec2f, 6>(
55
+ vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(0.0, 1.0),
56
+ vec2f(0.0, 1.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0)
57
+ );
58
+ let unit = corners[vertexIndex];
59
+ let position = vec2f(rect.x - uniforms.scrollLeft + unit.x * rect.z, rect.y + unit.y * rect.w);
60
+ let clip = vec2f(position.x / uniforms.resolution.x * 2.0 - 1.0, 1.0 - position.y / uniforms.resolution.y * 2.0);
61
+ return vec4f(clip, 0.0, 1.0);
62
+ }
63
+
64
+ @fragment
65
+ fn fragmentMain() -> @location(0) vec4f {
66
+ return uniforms.color;
67
+ }
68
+ `
69
+
70
+ const LINE_SHADER = `
71
+ struct Uniforms {
72
+ resolution: vec2f,
73
+ scrollLeft: f32,
74
+ padding: f32,
75
+ color: vec4f,
76
+ }
77
+
78
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
79
+
80
+ @vertex
81
+ fn vertexMain(@location(0) point: vec2f) -> @builtin(position) vec4f {
82
+ let position = vec2f(point.x - uniforms.scrollLeft, point.y);
83
+ let clip = vec2f(position.x / uniforms.resolution.x * 2.0 - 1.0, 1.0 - position.y / uniforms.resolution.y * 2.0);
84
+ return vec4f(clip, 0.0, 1.0);
85
+ }
86
+
87
+ @fragment
88
+ fn fragmentMain() -> @location(0) vec4f {
89
+ return uniforms.color;
90
+ }
91
+ `
92
+
93
+ const BLEND: GPUBlendState = {
94
+ color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
95
+ alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
96
+ }
97
+
98
+ function bufferUsage(usage: BufferUsage): GPUBufferUsageFlags {
99
+ if (usage === 'index') return GPU_BUFFER_INDEX | GPU_BUFFER_COPY_DST
100
+ if (usage === 'uniform') return GPU_BUFFER_UNIFORM | GPU_BUFFER_COPY_DST
101
+ if (usage === 'storage') return GPU_BUFFER_STORAGE | GPU_BUFFER_COPY_DST
102
+ return GPU_BUFFER_VERTEX | GPU_BUFFER_COPY_DST
103
+ }
104
+
105
+ function parseColor(value: unknown): readonly [number, number, number, number] | null {
106
+ if (typeof value !== 'string') return null
107
+ const color = value.trim().toLowerCase()
108
+ let rgba: [number, number, number, number] | null = null
109
+ if (color.startsWith('#')) {
110
+ const hex = color.slice(1)
111
+ if (hex.length === 3) {
112
+ rgba = [
113
+ Number.parseInt(hex[0]! + hex[0]!, 16) / 255,
114
+ Number.parseInt(hex[1]! + hex[1]!, 16) / 255,
115
+ Number.parseInt(hex[2]! + hex[2]!, 16) / 255,
116
+ 1,
117
+ ]
118
+ } else if (hex.length === 6) {
119
+ rgba = [
120
+ Number.parseInt(hex.slice(0, 2), 16) / 255,
121
+ Number.parseInt(hex.slice(2, 4), 16) / 255,
122
+ Number.parseInt(hex.slice(4, 6), 16) / 255,
123
+ 1,
124
+ ]
125
+ }
126
+ } else {
127
+ const match = color.match(/^rgba?\(([^)]+)\)$/)
128
+ if (match) {
129
+ const values = match[1]!.split(',').map((part) => Number(part.trim()))
130
+ if ((values.length === 3 || values.length === 4) && values.every(Number.isFinite)) {
131
+ rgba = [values[0]! / 255, values[1]! / 255, values[2]! / 255, values[3] ?? 1]
132
+ }
133
+ }
134
+ }
135
+ if (!rgba || rgba.some((component) => !Number.isFinite(component))) return null
136
+ return [rgba[0] * rgba[3], rgba[1] * rgba[3], rgba[2] * rgba[3], rgba[3]]
137
+ }
138
+
139
+ function buildWideLine(strip: DrawLineStrip): Float32Array | null {
140
+ const width = strip.width ?? 1
141
+ if (strip.points.length < 2 || width <= 0) return null
142
+ const output = new Float32Array((strip.points.length - 1) * 12)
143
+ let offset = 0
144
+ for (let index = 0; index < strip.points.length - 1; index++) {
145
+ const start = strip.points[index]!
146
+ const end = strip.points[index + 1]!
147
+ const dx = end.x - start.x
148
+ const dy = end.y - start.y
149
+ const length = Math.sqrt(dx * dx + dy * dy)
150
+ if (length <= 0) continue
151
+ const nx = (-dy / length) * width * 0.5
152
+ const ny = (dx / length) * width * 0.5
153
+ output.set(
154
+ [
155
+ start.x + nx,
156
+ start.y + ny,
157
+ start.x - nx,
158
+ start.y - ny,
159
+ end.x + nx,
160
+ end.y + ny,
161
+ end.x + nx,
162
+ end.y + ny,
163
+ start.x - nx,
164
+ start.y - ny,
165
+ end.x - nx,
166
+ end.y - ny,
167
+ ],
168
+ offset,
169
+ )
170
+ offset += 12
171
+ }
172
+ return offset === 0 ? null : output.subarray(0, offset)
173
+ }
174
+
175
+ function linePoints(strip: DrawLineStrip): Float32Array {
176
+ const values = new Float32Array(strip.points.length * 2)
177
+ for (let index = 0; index < strip.points.length; index++) {
178
+ values[index * 2] = strip.points[index]!.x
179
+ values[index * 2 + 1] = strip.points[index]!.y
180
+ }
181
+ return values
182
+ }
183
+
184
+ /** 几何 revision:按 float32 位型哈希,避免截断精度导致漏 upload */
185
+ function geometryRevision(values: Float32Array): number {
186
+ const bits = new Uint32Array(values.buffer, values.byteOffset, values.length)
187
+ let h = values.length >>> 0
188
+ for (let i = 0; i < bits.length; i++) {
189
+ h = Math.imul(h ^ bits[i]!, 16777619) >>> 0
190
+ }
191
+ return h
192
+ }
193
+
194
+ export async function createWebGPURenderer(
195
+ options: CreateWebGPURendererOptions = {},
196
+ ): Promise<Renderer> {
197
+ const gpu = options.gpu ?? globalThis.navigator?.gpu
198
+ if (!gpu) throw new Error('WebGPU unavailable')
199
+ const adapter = await gpu.requestAdapter()
200
+ if (!adapter) throw new Error('WebGPU adapter unavailable')
201
+ const device = await adapter.requestDevice()
202
+ const canvas = options.canvas ?? globalThis.document?.createElement('canvas')
203
+ if (!canvas) throw new Error('WebGPU canvas unavailable')
204
+ const format = gpu.getPreferredCanvasFormat()
205
+ const rawSurface = createWebGPUSurfaceBackend({ canvas, device, format })
206
+ const metrics = options.metrics ?? createFrameMetrics()
207
+ const resourceTable = createWebGPUResourceTable({ device, metrics })
208
+
209
+ type PendingDraw =
210
+ | {
211
+ kind: 'instances'
212
+ region: import('./SurfaceBackend').SurfaceRegion
213
+ pipeline: GPURenderPipeline
214
+ instanceBuffer: GPUBuffer
215
+ instanceCount: number
216
+ color: unknown
217
+ scrollLeft: number
218
+ }
219
+ | {
220
+ kind: 'lines'
221
+ region: import('./SurfaceBackend').SurfaceRegion
222
+ pipeline: GPURenderPipeline
223
+ vertexBuffer: GPUBuffer
224
+ vertexCount: number
225
+ color: unknown
226
+ scrollLeft: number
227
+ }
228
+
229
+ let disposed = false
230
+ let msaaTexture: GPUTexture | null = null
231
+ let msaaWidth = 0
232
+ let msaaHeight = 0
233
+ let currentRegion: import('./SurfaceBackend').SurfaceRegion | null = null
234
+ let pendingDraws: PendingDraw[] = []
235
+ let metricsFrameOpen = false
236
+ let stripSeq = 0
237
+ /** 本帧 touch 的 strip ResourceTable key;flush 后 prune 未 touch 的 */
238
+ const stripKeysThisFrame = new Set<string>()
239
+ const stripKeysKnown = new Set<string>()
240
+ const buffers = new WeakMap<object, BufferRecord>()
241
+ const bufferRecords = new Set<BufferRecord>()
242
+ const pipelines = new WeakMap<object, PipelineRecord>()
243
+ const pipelineCache = new Map<string, GPURenderPipeline>()
244
+ /** 帧内 uniform 环:跨帧复用,flush 后游标归零 */
245
+ const uniformPool: BufferRecord[] = []
246
+ let uniformPoolUsed = 0
247
+
248
+ void device.lost.then((info) => {
249
+ if (!disposed) options.onDeviceLost?.(info)
250
+ })
251
+
252
+ function openMetricsFrame(): void {
253
+ if (metricsFrameOpen) return
254
+ metrics.beginFrame()
255
+ metricsFrameOpen = true
256
+ stripSeq = 0
257
+ stripKeysThisFrame.clear()
258
+ uniformPoolUsed = 0
259
+ }
260
+
261
+ function pruneUnusedStripKeys(): void {
262
+ for (const key of stripKeysKnown) {
263
+ if (stripKeysThisFrame.has(key)) continue
264
+ resourceTable.destroyKey(key)
265
+ stripKeysKnown.delete(key)
266
+ }
267
+ for (const key of stripKeysThisFrame) stripKeysKnown.add(key)
268
+ }
269
+
270
+ function createBufferRecord(usage: BufferUsage, sizeBytes: number): BufferRecord {
271
+ const size = Math.max(4, Math.ceil(sizeBytes / 4) * 4)
272
+ const record = {
273
+ buffer: device.createBuffer({ size, usage: bufferUsage(usage) }),
274
+ size,
275
+ }
276
+ bufferRecords.add(record)
277
+ metrics.recordBufferCreate()
278
+ return record
279
+ }
280
+
281
+ function deferDestroy(record: BufferRecord): void {
282
+ bufferRecords.delete(record)
283
+ void device.queue.onSubmittedWorkDone().then(() => record.buffer.destroy())
284
+ }
285
+
286
+ function acquireUniform(byteLength: number): BufferRecord {
287
+ if (uniformPoolUsed < uniformPool.length) {
288
+ const existing = uniformPool[uniformPoolUsed]!
289
+ if (existing.size >= byteLength) {
290
+ uniformPoolUsed += 1
291
+ return existing
292
+ }
293
+ }
294
+ const record = createBufferRecord('uniform', byteLength)
295
+ if (uniformPoolUsed < uniformPool.length) {
296
+ const old = uniformPool[uniformPoolUsed]!
297
+ deferDestroy(old)
298
+ uniformPool[uniformPoolUsed] = record
299
+ } else {
300
+ uniformPool.push(record)
301
+ }
302
+ uniformPoolUsed += 1
303
+ return record
304
+ }
305
+
306
+ function getPipeline(kind: 'candle' | 'line-strip' | 'line-wide' | 'fill'): GPURenderPipeline {
307
+ const cached = pipelineCache.get(kind)
308
+ if (cached) return cached
309
+ const isCandle = kind === 'candle'
310
+ const module = device.createShaderModule({ code: isCandle ? RECT_SHADER : LINE_SHADER })
311
+ const topology: GPUPrimitiveTopology =
312
+ kind === 'line-strip' ? 'line-strip' : kind === 'fill' ? 'triangle-strip' : 'triangle-list'
313
+ const pipeline = device.createRenderPipeline({
314
+ layout: 'auto',
315
+ vertex: {
316
+ module,
317
+ entryPoint: 'vertexMain',
318
+ buffers: [
319
+ {
320
+ arrayStride: isCandle ? 16 : 8,
321
+ stepMode: isCandle ? 'instance' : 'vertex',
322
+ attributes: [
323
+ {
324
+ shaderLocation: 0,
325
+ offset: 0,
326
+ format: isCandle ? 'float32x4' : 'float32x2',
327
+ },
328
+ ],
329
+ },
330
+ ],
331
+ },
332
+ fragment: {
333
+ module,
334
+ entryPoint: 'fragmentMain',
335
+ targets: [{ format, blend: BLEND }],
336
+ },
337
+ primitive: { topology },
338
+ multisample: { count: 4 },
339
+ })
340
+ pipelineCache.set(kind, pipeline)
341
+ return pipeline
342
+ }
343
+
344
+ function ensureMsaaView(): GPUTextureView | null {
345
+ const width = canvas.width
346
+ const height = canvas.height
347
+ if (width <= 0 || height <= 0) return null
348
+ if (!msaaTexture || width !== msaaWidth || height !== msaaHeight) {
349
+ msaaTexture?.destroy()
350
+ msaaTexture = device.createTexture({
351
+ size: { width, height },
352
+ sampleCount: 4,
353
+ format,
354
+ usage: GPU_TEXTURE_RENDER_ATTACHMENT,
355
+ })
356
+ msaaWidth = width
357
+ msaaHeight = height
358
+ }
359
+ return msaaTexture.createView()
360
+ }
361
+
362
+ function createUniform(
363
+ pipeline: GPURenderPipeline,
364
+ colorValue: unknown,
365
+ scrollLeft: number,
366
+ region: import('./SurfaceBackend').SurfaceRegion,
367
+ ): { bindGroup: GPUBindGroup } | null {
368
+ const color = parseColor(colorValue ?? '#000000')
369
+ if (!color) return null
370
+ const values = new Float32Array([
371
+ region.width,
372
+ region.height,
373
+ scrollLeft,
374
+ 0,
375
+ color[0],
376
+ color[1],
377
+ color[2],
378
+ color[3],
379
+ ])
380
+ const record = acquireUniform(values.byteLength)
381
+ device.queue.writeBuffer(record.buffer, 0, values.buffer, 0, values.byteLength)
382
+ metrics.recordUpload(values.byteLength)
383
+ const bindGroup = device.createBindGroup({
384
+ layout: pipeline.getBindGroupLayout(0),
385
+ entries: [{ binding: 0, resource: { buffer: record.buffer } }],
386
+ })
387
+ return { bindGroup }
388
+ }
389
+
390
+ function beginPass(
391
+ encoder: GPUCommandEncoder,
392
+ region: import('./SurfaceBackend').SurfaceRegion,
393
+ loadOp: 'clear' | 'load',
394
+ ): GPURenderPassEncoder | null {
395
+ const view = rawSurface.getCurrentTextureView()
396
+ const msaaView = ensureMsaaView()
397
+ if (!view || !msaaView) return null
398
+ const pass = encoder.beginRenderPass({
399
+ colorAttachments: [
400
+ {
401
+ view: msaaView,
402
+ resolveTarget: view,
403
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
404
+ loadOp,
405
+ storeOp: 'store',
406
+ },
407
+ ],
408
+ })
409
+ const x = Math.max(0, Math.round(region.x * region.dpr))
410
+ const y = Math.max(0, Math.round(region.y * region.dpr))
411
+ const width = Math.min(canvas.width - x, Math.round(region.width * region.dpr))
412
+ const height = Math.min(canvas.height - y, Math.round(region.height * region.dpr))
413
+ if (width <= 0 || height <= 0) {
414
+ pass.end()
415
+ return null
416
+ }
417
+ pass.setViewport(x, y, width, height, 0, 1)
418
+ pass.setScissorRect(x, y, width, height)
419
+ return pass
420
+ }
421
+
422
+ function regionKey(region: import('./SurfaceBackend').SurfaceRegion): string {
423
+ return `${region.x},${region.y},${region.width},${region.height},${region.dpr}`
424
+ }
425
+
426
+ function flushPendingDraws(options?: { composite?: boolean }): void {
427
+ const hadDraws = pendingDraws.length > 0
428
+ if (hadDraws) {
429
+ openMetricsFrame()
430
+ const encoder = device.createCommandEncoder()
431
+ const groups = new Map<string, PendingDraw[]>()
432
+ for (const draw of pendingDraws) {
433
+ const key = regionKey(draw.region)
434
+ const list = groups.get(key)
435
+ if (list) list.push(draw)
436
+ else groups.set(key, [draw])
437
+ }
438
+
439
+ // 单 RenderPass:所有 region 组共享一次 clear + resolve,避免 MSAA 多 pass 驱动问题
440
+ const view = rawSurface.getCurrentTextureView()
441
+ const msaaView = ensureMsaaView()
442
+ if (view && msaaView) {
443
+ const pass = encoder.beginRenderPass({
444
+ colorAttachments: [
445
+ {
446
+ view: msaaView,
447
+ resolveTarget: view,
448
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
449
+ loadOp: 'clear',
450
+ storeOp: 'store',
451
+ },
452
+ ],
453
+ })
454
+
455
+ for (const draws of groups.values()) {
456
+ const region = draws[0]!.region
457
+ rawSurface.bindRegion(region)
458
+
459
+ const x = Math.max(0, Math.round(region.x * region.dpr))
460
+ const y = Math.max(0, Math.round(region.y * region.dpr))
461
+ const width = Math.min(canvas.width - x, Math.round(region.width * region.dpr))
462
+ const height = Math.min(canvas.height - y, Math.round(region.height * region.dpr))
463
+ if (width > 0 && height > 0) {
464
+ pass.setViewport(x, y, width, height, 0, 1)
465
+ pass.setScissorRect(x, y, width, height)
466
+
467
+ for (const draw of draws) {
468
+ const uniform = createUniform(draw.pipeline, draw.color, draw.scrollLeft, draw.region)
469
+ if (!uniform) continue
470
+ pass.setPipeline(draw.pipeline)
471
+ if (draw.kind === 'instances') {
472
+ pass.setVertexBuffer(0, draw.instanceBuffer)
473
+ pass.setBindGroup(0, uniform.bindGroup)
474
+ pass.draw(6, draw.instanceCount)
475
+ metrics.recordDraw()
476
+ } else {
477
+ pass.setVertexBuffer(0, draw.vertexBuffer)
478
+ pass.setBindGroup(0, uniform.bindGroup)
479
+ pass.draw(draw.vertexCount, 1)
480
+ metrics.recordDraw()
481
+ }
482
+ }
483
+ }
484
+ }
485
+ pass.end()
486
+ }
487
+ device.queue.submit([encoder.finish()])
488
+ metrics.recordSubmit()
489
+ pendingDraws = []
490
+ }
491
+
492
+ if (options?.composite) {
493
+ if (metricsFrameOpen || hadDraws) {
494
+ openMetricsFrame()
495
+ metrics.recordComposite()
496
+ }
497
+ }
498
+
499
+ // endFrame 收口:prune 未 touch 的 strip 资源;composite 中途 flush 保持 frame open
500
+ if (!options?.composite && metricsFrameOpen) {
501
+ pruneUnusedStripKeys()
502
+ metrics.endFrame()
503
+ metricsFrameOpen = false
504
+ }
505
+ }
506
+
507
+ // M2:compositeTo 为 no-op(可见 GPU canvas);仅 endFrame submit
508
+ const surface: WebGPUSurfaceBackend = {
509
+ ...rawSurface,
510
+ compositeTo(_targetCtx, _region, _compositeOptions) {
511
+ // 禁止中途 flush,保证每 chart 帧单次 queue.submit
512
+ },
513
+ }
514
+
515
+ const renderer: Renderer = {
516
+ surface,
517
+ caps: { compute: false, storageBuffer: false, maxInstances: 1_000_000, name: 'webgpu' },
518
+ createBuffer(usage, sizeBytes): BufferHandle {
519
+ if (disposed) throw new Error('Renderer is disposed')
520
+ openMetricsFrame()
521
+ const handle = {}
522
+ buffers.set(handle, createBufferRecord(usage, sizeBytes))
523
+ return handle as BufferHandle
524
+ },
525
+ writeBuffer(handle, data, offsetBytes = 0): void {
526
+ if (disposed) return
527
+ const record = buffers.get(handle as object)
528
+ if (!record || offsetBytes < 0 || offsetBytes + data.byteLength > record.size) return
529
+ openMetricsFrame()
530
+ device.queue.writeBuffer(
531
+ record.buffer,
532
+ offsetBytes,
533
+ data.buffer as ArrayBuffer,
534
+ data.byteOffset,
535
+ data.byteLength,
536
+ )
537
+ metrics.recordUpload(data.byteLength)
538
+ },
539
+ destroyBuffer(handle): void {
540
+ if (disposed) return
541
+ const record = buffers.get(handle as object)
542
+ if (!record) return
543
+ // 不立即 destroy:helper 可跨帧复用同一 handle;显式 dispose 才释放
544
+ // 兼容仍调用 destroyBuffer 的旧路径:仅解除 handle 映射,GPU buffer 延迟回收
545
+ buffers.delete(handle as object)
546
+ deferDestroy(record)
547
+ },
548
+ createPipeline(descriptor): PipelineHandle {
549
+ if (disposed) throw new Error('Renderer is disposed')
550
+ const type = (descriptor as { type?: PipelineType })?.type
551
+ if (type !== 'candle' && type !== 'line' && type !== 'fill') {
552
+ throw new Error('Unsupported WebGPU pipeline type')
553
+ }
554
+ const handle = {}
555
+ pipelines.set(handle, { type })
556
+ return handle as PipelineHandle
557
+ },
558
+ destroyPipeline(handle): void {
559
+ if (!disposed) pipelines.delete(handle as object)
560
+ },
561
+ createComputePipeline(_descriptor): ComputePipelineHandle {
562
+ throw new Error('compute not supported on WebGPU MVP backend (caps.compute === false)')
563
+ },
564
+ destroyComputePipeline(_handle): void {},
565
+ beginFrame(region): void {
566
+ if (!disposed) {
567
+ openMetricsFrame()
568
+ currentRegion = { ...region }
569
+ rawSurface.bindRegion(region)
570
+ }
571
+ },
572
+ drawInstances(params: DrawInstancesParams): boolean {
573
+ if (
574
+ disposed ||
575
+ params.instanceCount < 0 ||
576
+ params.instanceCount > renderer.caps.maxInstances
577
+ ) {
578
+ return false
579
+ }
580
+ if (params.instanceCount === 0) return true
581
+ const pipelineRecord = pipelines.get(params.pipeline as object)
582
+ const instanceRecord = buffers.get(params.instances as object)
583
+ if (pipelineRecord?.type !== 'candle' || !instanceRecord || !currentRegion) return false
584
+ try {
585
+ openMetricsFrame()
586
+ pendingDraws.push({
587
+ kind: 'instances',
588
+ region: { ...currentRegion },
589
+ pipeline: getPipeline('candle'),
590
+ instanceBuffer: instanceRecord.buffer,
591
+ instanceCount: params.instanceCount,
592
+ color: params.uniforms?.color,
593
+ scrollLeft: (params.uniforms?.scrollLeft as number) ?? 0,
594
+ })
595
+ return true
596
+ } catch {
597
+ return false
598
+ }
599
+ },
600
+ drawLines(params: DrawLinesParams): boolean {
601
+ if (disposed || !currentRegion) return false
602
+ const pipelineRecord = pipelines.get(params.pipeline as object)
603
+ if (!pipelineRecord || pipelineRecord.type === 'candle') return false
604
+ if (params.strips && params.strips.length === 0) return true
605
+ try {
606
+ openMetricsFrame()
607
+ if (params.strips) {
608
+ const scrollLeft = (params.uniforms?.scrollLeft as number) ?? 0
609
+ for (const strip of params.strips) {
610
+ if (strip.points.length < 2) return false
611
+ const wide = (strip.width ?? 1) > 1
612
+ const values = wide ? buildWideLine(strip) : linePoints(strip)
613
+ if (!values) return false
614
+ // 帧内序号作 key:同顺序跨帧复用;revision 未变则不 upload
615
+ const key = `strip/${stripSeq++}`
616
+ stripKeysThisFrame.add(key)
617
+ const uploaded = resourceTable.ensureUploaded({
618
+ key,
619
+ revision: geometryRevision(values),
620
+ data: values,
621
+ usage: 'vertex',
622
+ })
623
+ pendingDraws.push({
624
+ kind: 'lines',
625
+ region: { ...currentRegion },
626
+ pipeline: getPipeline(wide ? 'line-wide' : 'line-strip'),
627
+ vertexBuffer: uploaded.buffer,
628
+ vertexCount: values.length / 2,
629
+ color: strip.color,
630
+ scrollLeft,
631
+ })
632
+ }
633
+ return true
634
+ }
635
+
636
+ if (pipelineRecord.type !== 'fill' || !params.vertices || !params.vertexCount) return false
637
+ const vertexRecord = buffers.get(params.vertices as object)
638
+ if (!vertexRecord || params.vertexCount < 3) return false
639
+ pendingDraws.push({
640
+ kind: 'lines',
641
+ region: { ...currentRegion },
642
+ pipeline: getPipeline('fill'),
643
+ vertexBuffer: vertexRecord.buffer,
644
+ vertexCount: params.vertexCount,
645
+ color: params.uniforms?.color,
646
+ scrollLeft: (params.uniforms?.scrollLeft as number) ?? 0,
647
+ })
648
+ return true
649
+ } catch {
650
+ return false
651
+ }
652
+ },
653
+ dispatchCompute(_params: DispatchComputeParams): void {
654
+ throw new Error('dispatchCompute requires a backend with caps.compute === true')
655
+ },
656
+ endFrame(): void {
657
+ if (!disposed) flushPendingDraws()
658
+ },
659
+ dispose(): void {
660
+ if (disposed) return
661
+ disposed = true
662
+ pendingDraws = []
663
+ stripKeysThisFrame.clear()
664
+ stripKeysKnown.clear()
665
+ msaaTexture?.destroy()
666
+ msaaTexture = null
667
+ resourceTable.destroyAll()
668
+ for (const record of uniformPool) record.buffer.destroy()
669
+ uniformPool.length = 0
670
+ for (const record of bufferRecords) record.buffer.destroy()
671
+ bufferRecords.clear()
672
+ rawSurface.dispose()
673
+ },
674
+ }
675
+
676
+ return renderer
677
+ }