@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,370 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { createWebGPURenderer } from '../createWebGPURenderer'
4
+ import { createFrameMetrics, getFrameMetrics, resetFrameMetrics } from '../frameMetrics'
5
+
6
+ function deferred<T>() {
7
+ let resolve!: (value: T) => void
8
+ const promise = new Promise<T>((done) => {
9
+ resolve = done
10
+ })
11
+ return { promise, resolve }
12
+ }
13
+
14
+ function makeWebGPU() {
15
+ const passes: Array<ReturnType<typeof makePass>> = []
16
+ const renderPassDescriptors: GPURenderPassDescriptor[] = []
17
+ const pipelineDescriptors: GPURenderPipelineDescriptor[] = []
18
+ const buffers: Array<{ destroy: ReturnType<typeof vi.fn> }> = []
19
+ const lost = deferred<GPUDeviceLostInfo>()
20
+
21
+ function makePass() {
22
+ return {
23
+ setViewport: vi.fn(),
24
+ setScissorRect: vi.fn(),
25
+ setPipeline: vi.fn(),
26
+ setVertexBuffer: vi.fn(),
27
+ setBindGroup: vi.fn(),
28
+ draw: vi.fn(),
29
+ end: vi.fn(),
30
+ }
31
+ }
32
+
33
+ const queue = {
34
+ writeBuffer: vi.fn(),
35
+ submit: vi.fn(),
36
+ onSubmittedWorkDone: vi.fn(async () => {}),
37
+ }
38
+ const device = {
39
+ queue,
40
+ lost: lost.promise,
41
+ createBuffer: vi.fn(() => {
42
+ const buffer = { destroy: vi.fn() }
43
+ buffers.push(buffer)
44
+ return buffer
45
+ }),
46
+ createShaderModule: vi.fn((descriptor) => descriptor),
47
+ createRenderPipeline: vi.fn((descriptor: GPURenderPipelineDescriptor) => {
48
+ pipelineDescriptors.push(descriptor)
49
+ return { getBindGroupLayout: vi.fn(() => ({})) }
50
+ }),
51
+ createBindGroup: vi.fn((descriptor) => descriptor),
52
+ createTexture: vi.fn(() => ({
53
+ createView: vi.fn(() => ({ kind: 'msaa-view' })),
54
+ destroy: vi.fn(),
55
+ })),
56
+ createCommandEncoder: vi.fn(() => ({
57
+ beginRenderPass: vi.fn((descriptor: GPURenderPassDescriptor) => {
58
+ renderPassDescriptors.push(descriptor)
59
+ const pass = makePass()
60
+ passes.push(pass)
61
+ return pass
62
+ }),
63
+ finish: vi.fn(() => ({ kind: 'commands' })),
64
+ })),
65
+ }
66
+ const adapter = { requestDevice: vi.fn(async () => device) }
67
+ const gpu = {
68
+ requestAdapter: vi.fn(async () => adapter),
69
+ getPreferredCanvasFormat: vi.fn(() => 'bgra8unorm'),
70
+ }
71
+ const context = {
72
+ configure: vi.fn(),
73
+ unconfigure: vi.fn(),
74
+ getCurrentTexture: vi.fn(() => ({ createView: vi.fn(() => ({ kind: 'target-view' })) })),
75
+ }
76
+ const canvas = {
77
+ width: 1,
78
+ height: 1,
79
+ style: { width: '', height: '' },
80
+ getContext: vi.fn(() => context),
81
+ } as unknown as HTMLCanvasElement
82
+
83
+ return {
84
+ gpu,
85
+ adapter,
86
+ device,
87
+ queue,
88
+ context,
89
+ canvas,
90
+ passes,
91
+ renderPassDescriptors,
92
+ pipelineDescriptors,
93
+ buffers,
94
+ lost,
95
+ }
96
+ }
97
+
98
+ describe('createWebGPURenderer', () => {
99
+ it('requests a device and reports MVP capabilities', async () => {
100
+ const fake = makeWebGPU()
101
+ const renderer = await createWebGPURenderer({
102
+ gpu: fake.gpu as unknown as GPU,
103
+ canvas: fake.canvas,
104
+ })
105
+
106
+ expect(fake.gpu.requestAdapter).toHaveBeenCalledOnce()
107
+ expect(fake.adapter.requestDevice).toHaveBeenCalledOnce()
108
+ expect(renderer.caps).toMatchObject({ name: 'webgpu', compute: false, storageBuffer: false })
109
+ expect(() => renderer.createComputePipeline({})).toThrow('compute not supported')
110
+ })
111
+
112
+ it('uploads owned buffers and delays destruction until submitted work completes', async () => {
113
+ const fake = makeWebGPU()
114
+ const renderer = await createWebGPURenderer({
115
+ gpu: fake.gpu as unknown as GPU,
116
+ canvas: fake.canvas,
117
+ })
118
+ const handle = renderer.createBuffer('instance', 16)
119
+ const data = new Float32Array([1, 2, 3, 4])
120
+
121
+ renderer.writeBuffer(handle, data)
122
+ renderer.destroyBuffer(handle)
123
+ await Promise.resolve()
124
+ await Promise.resolve()
125
+
126
+ expect(fake.queue.writeBuffer).toHaveBeenCalledWith(fake.buffers[0], 0, data.buffer, 0, 16)
127
+ expect(fake.queue.onSubmittedWorkDone).toHaveBeenCalledOnce()
128
+ expect(fake.buffers[0]?.destroy).toHaveBeenCalledOnce()
129
+ })
130
+
131
+ it('draws rectangle instances into an MSAA region', async () => {
132
+ const fake = makeWebGPU()
133
+ const renderer = await createWebGPURenderer({
134
+ gpu: fake.gpu as unknown as GPU,
135
+ canvas: fake.canvas,
136
+ })
137
+ renderer.surface.resize(200, 100, 2)
138
+ renderer.beginFrame({ x: 10, y: 5, width: 80, height: 40, dpr: 2 })
139
+ const vertices = renderer.createBuffer('vertex', 4)
140
+ const instances = renderer.createBuffer('instance', 16)
141
+ const pipeline = renderer.createPipeline({ type: 'candle' })
142
+
143
+ const drawn = renderer.drawInstances({
144
+ pipeline,
145
+ vertices,
146
+ instances,
147
+ instanceCount: 3,
148
+ vertexCount: 6,
149
+ uniforms: { color: '#ff0000', scrollLeft: 4 },
150
+ })
151
+
152
+ expect(drawn).toBe(true)
153
+ expect(fake.queue.submit).not.toHaveBeenCalled()
154
+ renderer.endFrame()
155
+ expect(fake.device.createTexture).toHaveBeenCalledWith(
156
+ expect.objectContaining({ sampleCount: 4, format: 'bgra8unorm' }),
157
+ )
158
+ expect(fake.passes[0]?.setViewport).toHaveBeenCalledWith(20, 10, 160, 80, 0, 1)
159
+ expect(fake.passes[0]?.setScissorRect).toHaveBeenCalledWith(20, 10, 160, 80)
160
+ expect(fake.passes[0]?.draw).toHaveBeenCalledWith(6, 3)
161
+ expect(fake.queue.submit).toHaveBeenCalledOnce()
162
+ })
163
+
164
+ it('records multiple draws and submits once on endFrame', async () => {
165
+ const fake = makeWebGPU()
166
+ const renderer = await createWebGPURenderer({
167
+ gpu: fake.gpu as unknown as GPU,
168
+ canvas: fake.canvas,
169
+ })
170
+ renderer.surface.resize(200, 100, 1)
171
+ renderer.beginFrame({ x: 0, y: 0, width: 200, height: 100, dpr: 1 })
172
+ const pipeline = renderer.createPipeline({ type: 'candle' })
173
+ const instances = renderer.createBuffer('instance', 16)
174
+ renderer.writeBuffer(instances, new Float32Array([0, 0, 10, 20]))
175
+ const vertices = renderer.createBuffer('vertex', 4)
176
+ const params = {
177
+ pipeline,
178
+ vertices,
179
+ instances,
180
+ instanceCount: 1,
181
+ vertexCount: 6,
182
+ uniforms: { color: '#f00', scrollLeft: 0 },
183
+ }
184
+
185
+ expect(renderer.drawInstances(params)).toBe(true)
186
+ expect(renderer.drawInstances(params)).toBe(true)
187
+ expect(fake.queue.submit).not.toHaveBeenCalled()
188
+ renderer.endFrame()
189
+ expect(fake.queue.submit).toHaveBeenCalledTimes(1)
190
+ })
191
+
192
+ it('preserves earlier rectangle batches within the same frame', async () => {
193
+ const fake = makeWebGPU()
194
+ const renderer = await createWebGPURenderer({
195
+ gpu: fake.gpu as unknown as GPU,
196
+ canvas: fake.canvas,
197
+ })
198
+ renderer.surface.resize(200, 100, 1)
199
+ renderer.beginFrame({ x: 0, y: 0, width: 200, height: 100, dpr: 1 })
200
+ const vertices = renderer.createBuffer('vertex', 4)
201
+ const instances = renderer.createBuffer('instance', 16)
202
+ const pipeline = renderer.createPipeline({ type: 'candle' })
203
+ const params = {
204
+ pipeline,
205
+ vertices,
206
+ instances,
207
+ instanceCount: 1,
208
+ vertexCount: 6,
209
+ uniforms: { color: '#ff0000' },
210
+ }
211
+
212
+ expect(renderer.drawInstances(params)).toBe(true)
213
+ expect(renderer.drawInstances(params)).toBe(true)
214
+ renderer.endFrame()
215
+
216
+ expect(fake.renderPassDescriptors).toHaveLength(1)
217
+ expect(fake.renderPassDescriptors[0]?.colorAttachments[0]).toMatchObject({
218
+ loadOp: 'clear',
219
+ storeOp: 'store',
220
+ })
221
+ expect(fake.passes[0]?.draw).toHaveBeenCalledTimes(2)
222
+ })
223
+
224
+ it('draws multiple line strips in one render pass', async () => {
225
+ const fake = makeWebGPU()
226
+ const renderer = await createWebGPURenderer({
227
+ gpu: fake.gpu as unknown as GPU,
228
+ canvas: fake.canvas,
229
+ })
230
+ renderer.surface.resize(200, 100, 1)
231
+ renderer.beginFrame({ x: 0, y: 0, width: 200, height: 100, dpr: 1 })
232
+ const pipeline = renderer.createPipeline({ type: 'line' })
233
+
234
+ const drawn = renderer.drawLines({
235
+ pipeline,
236
+ strips: [
237
+ {
238
+ points: [
239
+ { x: 0, y: 1 },
240
+ { x: 2, y: 3 },
241
+ ],
242
+ color: '#f00',
243
+ width: 1,
244
+ },
245
+ {
246
+ points: [
247
+ { x: 4, y: 5 },
248
+ { x: 6, y: 7 },
249
+ ],
250
+ color: '#0f0',
251
+ width: 2,
252
+ },
253
+ ],
254
+ uniforms: { scrollLeft: 12 },
255
+ })
256
+
257
+ expect(drawn).toBe(true)
258
+ expect(fake.queue.submit).not.toHaveBeenCalled()
259
+ renderer.endFrame()
260
+ expect(fake.passes).toHaveLength(1)
261
+ expect(fake.passes[0]?.draw).toHaveBeenCalledTimes(2)
262
+ const uniformWrites = fake.queue.writeBuffer.mock.calls.filter((call) => call[4] === 32)
263
+ expect(uniformWrites).toHaveLength(2)
264
+ for (const call of uniformWrites) {
265
+ expect(new Float32Array(call[2] as ArrayBuffer)[2]).toBe(12)
266
+ }
267
+ expect(fake.queue.submit).toHaveBeenCalledOnce()
268
+ })
269
+
270
+ it('uses triangle-strip for filled bands and reports device loss', async () => {
271
+ const fake = makeWebGPU()
272
+ const onDeviceLost = vi.fn()
273
+ const renderer = await createWebGPURenderer({
274
+ gpu: fake.gpu as unknown as GPU,
275
+ canvas: fake.canvas,
276
+ onDeviceLost,
277
+ })
278
+ renderer.surface.resize(100, 100, 1)
279
+ renderer.beginFrame({ x: 0, y: 0, width: 100, height: 100, dpr: 1 })
280
+ const vertices = renderer.createBuffer('vertex', 32)
281
+ const pipeline = renderer.createPipeline({ type: 'fill' })
282
+
283
+ expect(renderer.drawLines({ pipeline, vertices, vertexCount: 4 })).toBe(true)
284
+ renderer.endFrame()
285
+ expect(
286
+ fake.pipelineDescriptors.some((item) => item.primitive?.topology === 'triangle-strip'),
287
+ ).toBe(true)
288
+
289
+ const info = { reason: 'unknown', message: 'device reset' } as GPUDeviceLostInfo
290
+ fake.lost.resolve(info)
291
+ await Promise.resolve()
292
+ expect(onDeviceLost).toHaveBeenCalledWith(info)
293
+ })
294
+
295
+ it('reuses strip ResourceTable buffers when geometry revision is unchanged', async () => {
296
+ resetFrameMetrics()
297
+ const fake = makeWebGPU()
298
+ const metrics = createFrameMetrics()
299
+ const renderer = await createWebGPURenderer({
300
+ gpu: fake.gpu as unknown as GPU,
301
+ canvas: fake.canvas,
302
+ metrics,
303
+ })
304
+ renderer.surface.resize(200, 100, 1)
305
+ const pipeline = renderer.createPipeline({ type: 'line' })
306
+ const strips = [
307
+ {
308
+ points: [
309
+ { x: 0, y: 1 },
310
+ { x: 2, y: 3 },
311
+ ],
312
+ color: '#f00',
313
+ width: 1,
314
+ },
315
+ ]
316
+
317
+ renderer.beginFrame({ x: 0, y: 0, width: 200, height: 100, dpr: 1 })
318
+ expect(renderer.drawLines({ pipeline, strips, uniforms: { scrollLeft: 0 } })).toBe(true)
319
+ renderer.endFrame()
320
+ const createsAfterFirst = fake.device.createBuffer.mock.calls.length
321
+ // strip 点列 4 floats = 16 bytes;uniform 为 32 bytes
322
+ const vertexWritesAfterFirst = fake.queue.writeBuffer.mock.calls.filter((c) => c[4] === 16)
323
+ .length
324
+ const uniformWritesAfterFirst = fake.queue.writeBuffer.mock.calls.filter((c) => c[4] === 32)
325
+ .length
326
+
327
+ renderer.beginFrame({ x: 0, y: 0, width: 200, height: 100, dpr: 1 })
328
+ expect(renderer.drawLines({ pipeline, strips, uniforms: { scrollLeft: 12 } })).toBe(true)
329
+ renderer.endFrame()
330
+
331
+ // 几何未变:strip vertex 不新建、不重传;uniform 仍写
332
+ expect(fake.device.createBuffer.mock.calls.length).toBe(createsAfterFirst)
333
+ expect(
334
+ fake.queue.writeBuffer.mock.calls.filter((c) => c[4] === 16).length,
335
+ ).toBe(vertexWritesAfterFirst)
336
+ expect(
337
+ fake.queue.writeBuffer.mock.calls.filter((c) => c[4] === 32).length,
338
+ ).toBeGreaterThan(uniformWritesAfterFirst)
339
+ expect(getFrameMetrics().queueSubmitCount).toBe(1)
340
+ })
341
+
342
+ it('records submit and draw metrics on endFrame', async () => {
343
+ resetFrameMetrics()
344
+ const fake = makeWebGPU()
345
+ const metrics = createFrameMetrics()
346
+ const renderer = await createWebGPURenderer({
347
+ gpu: fake.gpu as unknown as GPU,
348
+ canvas: fake.canvas,
349
+ metrics,
350
+ })
351
+ renderer.surface.resize(200, 100, 1)
352
+ renderer.beginFrame({ x: 0, y: 0, width: 200, height: 100, dpr: 1 })
353
+ const pipeline = renderer.createPipeline({ type: 'candle' })
354
+ const instances = renderer.createBuffer('instance', 16)
355
+ const vertices = renderer.createBuffer('vertex', 4)
356
+ renderer.drawInstances({
357
+ pipeline,
358
+ vertices,
359
+ instances,
360
+ instanceCount: 1,
361
+ vertexCount: 6,
362
+ uniforms: { color: '#f00', scrollLeft: 0 },
363
+ })
364
+ renderer.endFrame()
365
+ expect(getFrameMetrics()).toMatchObject({
366
+ queueSubmitCount: 1,
367
+ drawCallCount: 1,
368
+ })
369
+ })
370
+ })
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { createFrameMetrics } from '../frameMetrics'
4
+ import { createWebGPUResourceTable } from '../webgpuResourceTable'
5
+
6
+ function makeDevice() {
7
+ const buffers: Array<{ destroy: ReturnType<typeof vi.fn>; size: number }> = []
8
+ const queue = {
9
+ writeBuffer: vi.fn(),
10
+ }
11
+ const device = {
12
+ queue,
13
+ createBuffer: vi.fn(({ size }: { size: number }) => {
14
+ const buffer = { destroy: vi.fn(), size }
15
+ buffers.push(buffer)
16
+ return buffer
17
+ }),
18
+ }
19
+ return { device, queue, buffers }
20
+ }
21
+
22
+ describe('createWebGPUResourceTable', () => {
23
+ it('reuses buffer when revision unchanged', () => {
24
+ const fake = makeDevice()
25
+ const metrics = createFrameMetrics()
26
+ metrics.beginFrame()
27
+ const table = createWebGPUResourceTable({
28
+ device: fake.device as unknown as GPUDevice,
29
+ metrics,
30
+ })
31
+ const data = new Float32Array([1, 2, 3, 4])
32
+ const a = table.ensureUploaded({ key: 'k', revision: 1, data, usage: 'vertex' })
33
+ const b = table.ensureUploaded({ key: 'k', revision: 1, data, usage: 'vertex' })
34
+ expect(a.buffer).toBe(b.buffer)
35
+ expect(fake.device.createBuffer).toHaveBeenCalledTimes(1)
36
+ expect(fake.queue.writeBuffer).toHaveBeenCalledTimes(1)
37
+ })
38
+
39
+ it('uploads again when revision changes', () => {
40
+ const fake = makeDevice()
41
+ const metrics = createFrameMetrics()
42
+ metrics.beginFrame()
43
+ const table = createWebGPUResourceTable({
44
+ device: fake.device as unknown as GPUDevice,
45
+ metrics,
46
+ })
47
+ table.ensureUploaded({
48
+ key: 'k',
49
+ revision: 1,
50
+ data: new Float32Array([1, 2, 3, 4]),
51
+ usage: 'vertex',
52
+ })
53
+ table.ensureUploaded({
54
+ key: 'k',
55
+ revision: 2,
56
+ data: new Float32Array([5, 6, 7, 8]),
57
+ usage: 'vertex',
58
+ })
59
+ expect(fake.device.createBuffer).toHaveBeenCalledTimes(1)
60
+ expect(fake.queue.writeBuffer).toHaveBeenCalledTimes(2)
61
+ })
62
+ })
@@ -0,0 +1,120 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { createWebGPUSurfaceBackend } from '../createWebGPUSurfaceBackend'
4
+
5
+ function makeSurface() {
6
+ const context = {
7
+ configure: vi.fn(),
8
+ unconfigure: vi.fn(),
9
+ getCurrentTexture: vi.fn(() => ({ createView: vi.fn(() => ({ id: 'view' })) })),
10
+ }
11
+ const canvas = {
12
+ width: 1,
13
+ height: 1,
14
+ style: { width: '', height: '' },
15
+ getContext: vi.fn(() => context),
16
+ } as unknown as HTMLCanvasElement
17
+ const device = {} as GPUDevice
18
+ const surface = createWebGPUSurfaceBackend({ canvas, device, format: 'bgra8unorm' })
19
+ return { canvas, context, device, surface }
20
+ }
21
+
22
+ describe('createWebGPUSurfaceBackend', () => {
23
+ it('configures a premultiplied WebGPU canvas', () => {
24
+ const { context, device } = makeSurface()
25
+
26
+ expect(context.configure).toHaveBeenCalledWith({
27
+ device,
28
+ format: 'bgra8unorm',
29
+ alphaMode: 'premultiplied',
30
+ usage: 0x10,
31
+ })
32
+ })
33
+
34
+ it('resizes the backing canvas using DPR and binds a logical region', () => {
35
+ const { canvas, surface } = makeSurface()
36
+
37
+ surface.resize(320, 180, 1.5)
38
+
39
+ expect(canvas.width).toBe(480)
40
+ expect(canvas.height).toBe(270)
41
+ expect(surface.bindRegion({ x: 10, y: 20, width: 100, height: 50, dpr: 1.5 })).toBe(true)
42
+ expect(surface.getBoundRegion()).toEqual({ x: 10, y: 20, width: 100, height: 50, dpr: 1.5 })
43
+ })
44
+
45
+ it('sets CSS size on resize for hybrid DOM mounting', () => {
46
+ const { canvas, surface } = makeSurface()
47
+
48
+ surface.resize(320, 180, 1.5)
49
+
50
+ expect(canvas.style.width).toBe('320px')
51
+ expect(canvas.style.height).toBe('180px')
52
+ })
53
+
54
+ it('compositeTo is a no-op under hybrid DOM (M2)', () => {
55
+ const { surface } = makeSurface()
56
+ const target = {
57
+ save: vi.fn(),
58
+ restore: vi.fn(),
59
+ setTransform: vi.fn(),
60
+ drawImage: vi.fn(),
61
+ } as unknown as CanvasRenderingContext2D
62
+
63
+ surface.compositeTo(target, { x: 10, y: 20, width: 100, height: 50, dpr: 2 })
64
+
65
+ expect(target.drawImage).not.toHaveBeenCalled()
66
+ })
67
+
68
+ it('clearRegion submits a transparent clear of the WebGPU canvas', () => {
69
+ const context = {
70
+ configure: vi.fn(),
71
+ unconfigure: vi.fn(),
72
+ getCurrentTexture: vi.fn(() => ({ createView: vi.fn(() => ({ id: 'view' })) })),
73
+ }
74
+ const canvas = {
75
+ width: 100,
76
+ height: 50,
77
+ style: { width: '', height: '' },
78
+ getContext: vi.fn(() => context),
79
+ } as unknown as HTMLCanvasElement
80
+ const pass = { end: vi.fn() }
81
+ const encoder = {
82
+ beginRenderPass: vi.fn(() => pass),
83
+ finish: vi.fn(() => ({ kind: 'commands' })),
84
+ }
85
+ const queue = { submit: vi.fn() }
86
+ const device = {
87
+ createCommandEncoder: vi.fn(() => encoder),
88
+ queue,
89
+ } as unknown as GPUDevice
90
+ const surface = createWebGPUSurfaceBackend({ canvas, device, format: 'bgra8unorm' })
91
+
92
+ surface.clearRegion({ x: 0, y: 0, width: 100, height: 50, dpr: 1 })
93
+
94
+ expect(device.createCommandEncoder).toHaveBeenCalledOnce()
95
+ expect(encoder.beginRenderPass).toHaveBeenCalledWith(
96
+ expect.objectContaining({
97
+ colorAttachments: [
98
+ expect.objectContaining({
99
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
100
+ loadOp: 'clear',
101
+ storeOp: 'store',
102
+ }),
103
+ ],
104
+ }),
105
+ )
106
+ expect(pass.end).toHaveBeenCalledOnce()
107
+ expect(queue.submit).toHaveBeenCalledOnce()
108
+ })
109
+
110
+ it('unconfigures once and rejects work after dispose', () => {
111
+ const { context, surface } = makeSurface()
112
+
113
+ surface.dispose()
114
+ surface.dispose()
115
+
116
+ expect(context.unconfigure).toHaveBeenCalledOnce()
117
+ expect(surface.isAvailable()).toBe(false)
118
+ expect(surface.bindRegion({ x: 0, y: 0, width: 10, height: 10, dpr: 1 })).toBe(false)
119
+ })
120
+ })
@@ -0,0 +1,83 @@
1
+ import type {
2
+ BufferHandle,
3
+ BufferUsage,
4
+ ComputePipelineHandle,
5
+ DispatchComputeParams,
6
+ DrawInstancesParams,
7
+ DrawLinesParams,
8
+ PipelineHandle,
9
+ Renderer,
10
+ } from './Renderer'
11
+ import type { SurfaceBackend } from './SurfaceBackend'
12
+
13
+ export function createCanvas2DRenderer(): Renderer {
14
+ let disposed = false
15
+ const buffers = new WeakSet<object>()
16
+ const pipelines = new WeakSet<object>()
17
+
18
+ const surface: SurfaceBackend = {
19
+ isAvailable: () => false,
20
+ resize: () => {},
21
+ bindRegion: () => false,
22
+ clearRegion: () => {},
23
+ compositeTo: () => {},
24
+ dispose: () => {},
25
+ }
26
+
27
+ function assertActive(): void {
28
+ if (disposed) throw new Error('Renderer is disposed')
29
+ }
30
+
31
+ return {
32
+ surface,
33
+ caps: {
34
+ compute: false,
35
+ storageBuffer: false,
36
+ maxInstances: 0,
37
+ name: 'canvas2d',
38
+ },
39
+ createBuffer(_usage: BufferUsage, _sizeBytes: number): BufferHandle {
40
+ assertActive()
41
+ const handle = {}
42
+ buffers.add(handle)
43
+ return handle as BufferHandle
44
+ },
45
+ writeBuffer(handle: BufferHandle): void {
46
+ if (disposed || !buffers.has(handle as object)) return
47
+ },
48
+ destroyBuffer(handle: BufferHandle): void {
49
+ if (disposed) return
50
+ buffers.delete(handle as object)
51
+ },
52
+ createPipeline(_descriptor: unknown): PipelineHandle {
53
+ assertActive()
54
+ const handle = {}
55
+ pipelines.add(handle)
56
+ return handle as PipelineHandle
57
+ },
58
+ destroyPipeline(handle: PipelineHandle): void {
59
+ if (disposed) return
60
+ pipelines.delete(handle as object)
61
+ },
62
+ createComputePipeline(_descriptor: unknown): ComputePipelineHandle {
63
+ throw new Error('compute not supported on Canvas2D backend (caps.compute === false)')
64
+ },
65
+ destroyComputePipeline(_handle: ComputePipelineHandle): void {},
66
+ beginFrame(): void {},
67
+ drawInstances(_params: DrawInstancesParams): boolean {
68
+ return false
69
+ },
70
+ drawLines(_params: DrawLinesParams): boolean {
71
+ return false
72
+ },
73
+ dispatchCompute(_params: DispatchComputeParams): void {
74
+ throw new Error('dispatchCompute requires a backend with caps.compute === true')
75
+ },
76
+ endFrame(): void {},
77
+ dispose(): void {
78
+ if (disposed) return
79
+ disposed = true
80
+ surface.dispose()
81
+ },
82
+ }
83
+ }
@@ -0,0 +1,74 @@
1
+ import { SharedWebGLSurface } from '../../engine/renderers/webgl/sharedWebGLSurface'
2
+ import { createCanvas2DRenderer } from './createCanvas2DRenderer'
3
+ import { createWebGLRenderer } from './createWebGLRenderer'
4
+ import { createWebGLSurfaceBackend } from './createWebGLSurfaceBackend'
5
+ import { createWebGPURenderer } from './createWebGPURenderer'
6
+ import {
7
+ createRendererHost,
8
+ createRendererHostFromRenderer,
9
+ type RendererBackend,
10
+ type RendererHost,
11
+ type RendererHostDependencies,
12
+ type RendererHostListeners,
13
+ } from './rendererHost'
14
+ import type { Renderer } from './Renderer'
15
+
16
+ function createWebGLBackendRenderer(): Renderer {
17
+ const shared = new SharedWebGLSurface()
18
+ const surface = createWebGLSurfaceBackend(shared)
19
+ if (!surface.isAvailable()) {
20
+ surface.dispose()
21
+ throw new Error('WebGL2 unavailable')
22
+ }
23
+ return createWebGLRenderer(surface, shared)
24
+ }
25
+
26
+ function createDependencies(hostRef: { current: RendererHost | null }): RendererHostDependencies {
27
+ return {
28
+ createWebGPU: () =>
29
+ createWebGPURenderer({
30
+ onDeviceLost: (info) => {
31
+ void hostRef.current?.handleDeviceLost(info.message || 'WebGPU device lost')
32
+ },
33
+ }),
34
+ createWebGL: createWebGLBackendRenderer,
35
+ createCanvas: createCanvas2DRenderer,
36
+ }
37
+ }
38
+
39
+ export async function createDefaultRendererHost(
40
+ preference: RendererBackend,
41
+ listeners: RendererHostListeners = {},
42
+ ): Promise<RendererHost> {
43
+ const hostRef = { current: null as RendererHost | null }
44
+ const host = await createRendererHost(preference, createDependencies(hostRef))
45
+ hostRef.current = host
46
+ host.setListeners(listeners)
47
+ return host
48
+ }
49
+
50
+ export function createDefaultRendererHostSync(
51
+ listeners: RendererHostListeners = {},
52
+ ): RendererHost {
53
+ const hostRef = { current: null as RendererHost | null }
54
+ const deps = createDependencies(hostRef)
55
+ let renderer: Renderer
56
+ let effective: RendererBackend
57
+ let error: string | null = null
58
+ try {
59
+ renderer = createWebGLBackendRenderer()
60
+ effective = 'webgl'
61
+ } catch (cause) {
62
+ renderer = createCanvas2DRenderer()
63
+ effective = 'canvas'
64
+ error = cause instanceof Error ? cause.message : String(cause)
65
+ }
66
+ const host = createRendererHostFromRenderer(
67
+ 'webgl',
68
+ { renderer, effective, error },
69
+ deps,
70
+ )
71
+ hostRef.current = host
72
+ host.setListeners(listeners)
73
+ return host
74
+ }