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

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 (190) hide show
  1. package/dist/controllers/createChartController.d.ts.map +1 -1
  2. package/dist/controllers/createChartController.js +9 -8
  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 +5 -0
  8. package/dist/controllers/types.d.ts.map +1 -1
  9. package/dist/data/binance.d.ts +1 -1
  10. package/dist/data/binance.d.ts.map +1 -1
  11. package/dist/data/binance.js +1 -1
  12. package/dist/data/binance.js.map +1 -1
  13. package/dist/engine/chart.d.ts +9 -9
  14. package/dist/engine/chart.d.ts.map +1 -1
  15. package/dist/engine/chart.js +69 -96
  16. package/dist/engine/chart.js.map +1 -1
  17. package/dist/engine/chartTypes.d.ts +5 -0
  18. package/dist/engine/chartTypes.d.ts.map +1 -1
  19. package/dist/engine/controller/interaction.d.ts +4 -2
  20. package/dist/engine/controller/interaction.d.ts.map +1 -1
  21. package/dist/engine/controller/interaction.js +18 -10
  22. package/dist/engine/controller/interaction.js.map +1 -1
  23. package/dist/engine/data/chartDataManager.d.ts +12 -18
  24. package/dist/engine/data/chartDataManager.d.ts.map +1 -1
  25. package/dist/engine/data/chartDataManager.js +46 -37
  26. package/dist/engine/data/chartDataManager.js.map +1 -1
  27. package/dist/engine/data/incrementalLoadHint.d.ts +2 -4
  28. package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
  29. package/dist/engine/data/incrementalLoadHint.js +5 -2
  30. package/dist/engine/data/incrementalLoadHint.js.map +1 -1
  31. package/dist/engine/data/scrollCompensator.d.ts +3 -13
  32. package/dist/engine/data/scrollCompensator.d.ts.map +1 -1
  33. package/dist/engine/data/scrollCompensator.js +13 -14
  34. package/dist/engine/data/scrollCompensator.js.map +1 -1
  35. package/dist/engine/indicators/chartIndicatorManager.d.ts +16 -17
  36. package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
  37. package/dist/engine/indicators/chartIndicatorManager.js +37 -37
  38. package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
  39. package/dist/engine/layout/chartPaneLayout.d.ts +11 -7
  40. package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -1
  41. package/dist/engine/layout/chartPaneLayout.js +58 -16
  42. package/dist/engine/layout/chartPaneLayout.js.map +1 -1
  43. package/dist/engine/paneRenderer.d.ts +2 -0
  44. package/dist/engine/paneRenderer.d.ts.map +1 -1
  45. package/dist/engine/paneRenderer.js +15 -5
  46. package/dist/engine/paneRenderer.js.map +1 -1
  47. package/dist/engine/render/chartRenderer.d.ts +28 -22
  48. package/dist/engine/render/chartRenderer.d.ts.map +1 -1
  49. package/dist/engine/render/chartRenderer.js +117 -69
  50. package/dist/engine/render/chartRenderer.js.map +1 -1
  51. package/dist/engine/render/layers/leftYAxisLayer.d.ts +9 -2
  52. package/dist/engine/render/layers/leftYAxisLayer.d.ts.map +1 -1
  53. package/dist/engine/render/layers/leftYAxisLayer.js +11 -2
  54. package/dist/engine/render/layers/leftYAxisLayer.js.map +1 -1
  55. package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts +6 -4
  56. package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts.map +1 -1
  57. package/dist/engine/render/layers/mainIndicatorLegendLayer.js +6 -9
  58. package/dist/engine/render/layers/mainIndicatorLegendLayer.js.map +1 -1
  59. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts +2 -0
  60. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts.map +1 -0
  61. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js +19 -0
  62. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js.map +1 -0
  63. package/dist/engine/render/layers/yAxisLayer.d.ts +9 -2
  64. package/dist/engine/render/layers/yAxisLayer.d.ts.map +1 -1
  65. package/dist/engine/render/layers/yAxisLayer.js +11 -2
  66. package/dist/engine/render/layers/yAxisLayer.js.map +1 -1
  67. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts +7 -3
  68. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -1
  69. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +234 -330
  70. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
  71. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts +72 -0
  72. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts.map +1 -0
  73. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js +177 -0
  74. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js.map +1 -0
  75. package/dist/engine/renderers/leftYAxis.d.ts +15 -2
  76. package/dist/engine/renderers/leftYAxis.d.ts.map +1 -1
  77. package/dist/engine/renderers/leftYAxis.js +56 -16
  78. package/dist/engine/renderers/leftYAxis.js.map +1 -1
  79. package/dist/engine/renderers/yAxis.d.ts +16 -6
  80. package/dist/engine/renderers/yAxis.d.ts.map +1 -1
  81. package/dist/engine/renderers/yAxis.js +49 -16
  82. package/dist/engine/renderers/yAxis.js.map +1 -1
  83. package/dist/engine/state/viewportState.d.ts +4 -1
  84. package/dist/engine/state/viewportState.d.ts.map +1 -1
  85. package/dist/engine/state/viewportState.js +40 -8
  86. package/dist/engine/state/viewportState.js.map +1 -1
  87. package/dist/engine/subPaneManager.d.ts +5 -3
  88. package/dist/engine/subPaneManager.d.ts.map +1 -1
  89. package/dist/engine/subPaneManager.js +3 -2
  90. package/dist/engine/subPaneManager.js.map +1 -1
  91. package/dist/engine/utils/chartZoomController.d.ts +9 -7
  92. package/dist/engine/utils/chartZoomController.d.ts.map +1 -1
  93. package/dist/engine/utils/chartZoomController.js +13 -12
  94. package/dist/engine/utils/chartZoomController.js.map +1 -1
  95. package/dist/engine/viewport/chartViewportManager.d.ts +5 -16
  96. package/dist/engine/viewport/chartViewportManager.d.ts.map +1 -1
  97. package/dist/engine/viewport/chartViewportManager.js +7 -48
  98. package/dist/engine/viewport/chartViewportManager.js.map +1 -1
  99. package/dist/engine/viewport/viewport.d.ts +14 -0
  100. package/dist/engine/viewport/viewport.d.ts.map +1 -1
  101. package/dist/engine/viewport/viewport.js +10 -0
  102. package/dist/engine/viewport/viewport.js.map +1 -1
  103. package/dist/foundation/plugin/rendererPluginManager.test.js +15 -0
  104. package/dist/foundation/plugin/rendererPluginManager.test.js.map +1 -1
  105. package/dist/foundation/plugin/types.d.ts +3 -0
  106. package/dist/foundation/plugin/types.d.ts.map +1 -1
  107. package/dist/foundation/plugin/types.js.map +1 -1
  108. package/dist/foundation/reactivity/frameTransaction.d.ts +11 -0
  109. package/dist/foundation/reactivity/frameTransaction.d.ts.map +1 -1
  110. package/dist/foundation/reactivity/frameTransaction.js +17 -2
  111. package/dist/foundation/reactivity/frameTransaction.js.map +1 -1
  112. package/dist/rendering/render/Renderer.d.ts +58 -74
  113. package/dist/rendering/render/Renderer.d.ts.map +1 -1
  114. package/dist/rendering/render/Renderer.js +8 -18
  115. package/dist/rendering/render/Renderer.js.map +1 -1
  116. package/dist/rendering/render/createWebGPURenderer.d.ts.map +1 -1
  117. package/dist/rendering/render/createWebGPURenderer.js +30 -17
  118. package/dist/rendering/render/createWebGPURenderer.js.map +1 -1
  119. package/dist/rendering/render/createWebGPUSurfaceBackend.js +3 -3
  120. package/dist/rendering/render/createWebGPUSurfaceBackend.js.map +1 -1
  121. package/dist/rendering/render/physicalLine.d.ts +4 -0
  122. package/dist/rendering/render/physicalLine.d.ts.map +1 -0
  123. package/dist/rendering/render/physicalLine.js +37 -0
  124. package/dist/rendering/render/physicalLine.js.map +1 -0
  125. package/dist/rendering/render/physicalRegion.d.ts +14 -0
  126. package/dist/rendering/render/physicalRegion.d.ts.map +1 -0
  127. package/dist/rendering/render/physicalRegion.js +15 -0
  128. package/dist/rendering/render/physicalRegion.js.map +1 -0
  129. package/dist/rendering/scene/createLayerFromPlugin.d.ts.map +1 -1
  130. package/dist/rendering/scene/createLayerFromPlugin.js +8 -3
  131. package/dist/rendering/scene/createLayerFromPlugin.js.map +1 -1
  132. package/dist/rendering/scene/createScene.d.ts.map +1 -1
  133. package/dist/rendering/scene/createScene.js +4 -3
  134. package/dist/rendering/scene/createScene.js.map +1 -1
  135. package/dist/version.d.ts +1 -1
  136. package/dist/version.js +1 -1
  137. package/package.json +1 -1
  138. package/src/__tests__/stateKernel.test.ts +1 -0
  139. package/src/controllers/createChartController.ts +9 -8
  140. package/src/controllers/index.ts +11 -0
  141. package/src/controllers/types.ts +7 -0
  142. package/src/data/binance.ts +1 -1
  143. package/src/engine/__tests__/chart.dpr.test.ts +3 -2
  144. package/src/engine/__tests__/paneRenderer.resize.test.ts +52 -54
  145. package/src/engine/__tests__/subPaneManager.test.ts +5 -3
  146. package/src/engine/chart.ts +76 -109
  147. package/src/engine/chartTypes.ts +5 -0
  148. package/src/engine/controller/__tests__/interaction.dpr.test.ts +39 -2
  149. package/src/engine/controller/interaction.ts +18 -10
  150. package/src/engine/data/__tests__/chartDataManager.incrementalLoad.test.ts +37 -17
  151. package/src/engine/data/__tests__/scrollCompensator.geometry.test.ts +87 -75
  152. package/src/engine/data/chartDataManager.ts +54 -59
  153. package/src/engine/data/incrementalLoadHint.ts +8 -7
  154. package/src/engine/data/scrollCompensator.ts +16 -24
  155. package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +18 -26
  156. package/src/engine/indicators/chartIndicatorManager.ts +62 -62
  157. package/src/engine/layout/chartPaneLayout.ts +73 -21
  158. package/src/engine/paneRenderer.ts +17 -10
  159. package/src/engine/render/chartRenderer.ts +187 -129
  160. package/src/engine/render/layers/leftYAxisLayer.ts +29 -7
  161. package/src/engine/render/layers/mainIndicatorLegendLayer.test.ts +26 -0
  162. package/src/engine/render/layers/mainIndicatorLegendLayer.ts +13 -10
  163. package/src/engine/render/layers/yAxisLayer.ts +29 -7
  164. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +270 -403
  165. package/src/engine/renderers/Indicator/mainIndicatorLegendContext.ts +282 -0
  166. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +73 -2
  167. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +10 -6
  168. package/src/engine/renderers/leftYAxis.ts +60 -18
  169. package/src/engine/renderers/yAxis.ts +57 -22
  170. package/src/engine/state/viewportState.ts +44 -8
  171. package/src/engine/subPaneManager.ts +8 -5
  172. package/src/engine/utils/chartZoomController.ts +22 -19
  173. package/src/engine/viewport/__tests__/visibleRange.clamp.test.ts +50 -0
  174. package/src/engine/viewport/chartViewportManager.ts +9 -60
  175. package/src/engine/viewport/viewport.ts +14 -0
  176. package/src/foundation/plugin/rendererPluginManager.test.ts +18 -0
  177. package/src/foundation/plugin/types.ts +3 -0
  178. package/src/foundation/reactivity/frameTransaction.ts +20 -6
  179. package/src/rendering/render/Renderer.ts +69 -85
  180. package/src/rendering/render/__tests__/physicalLine.test.ts +51 -0
  181. package/src/rendering/render/__tests__/physicalRegion.test.ts +23 -0
  182. package/src/rendering/render/__tests__/webgpuRenderer.test.ts +31 -1
  183. package/src/rendering/render/__tests__/webgpuSurfaceBackend.test.ts +11 -0
  184. package/src/rendering/render/createWebGPURenderer.ts +30 -17
  185. package/src/rendering/render/createWebGPUSurfaceBackend.ts +3 -3
  186. package/src/rendering/render/physicalLine.ts +45 -0
  187. package/src/rendering/render/physicalRegion.ts +32 -0
  188. package/src/rendering/scene/createLayerFromPlugin.ts +8 -3
  189. package/src/rendering/scene/createScene.ts +4 -3
  190. package/src/version.ts +1 -1
@@ -1,60 +1,95 @@
1
1
  /**
2
- * High-level renderer interface the drawing primitives the chart's
3
- * scene layers (candles, volume, indicators, drawings) call into.
2
+ * 渲染器接口Scene 各层(蜡烛、指标、drawing 等)通过它画图。
4
3
  *
5
- * WebGL (`createWebGLRenderer`) implements this contract today; P1 WebGPU
6
- * will implement the same surface. Call sites only use sceneRenderer
7
- * (fail-closed boolean → Canvas2D), never dual-path surfaces.
4
+ * WebGL WebGPU 实现同一套接口。调用方只走 sceneRenderer(返回 false fail-closed 降级 Canvas2D),不走双路径。
8
5
  *
9
- * Design notes:
10
- * - `drawInstances` is the workhorse: render N copies of a unit geometry
11
- * parameterized by an instance buffer. K-lines, volume bars, footprint
12
- * cells, heatmap tiles all reduce to instanced primitives — see
13
- * `docs/ROADMAP.md` §2.3 for the K-line example.
14
- * - `drawLines` covers MA-style continuous polylines, drawings, axes.
15
- * - `dispatchCompute` exists only because WebGPU has compute shaders;
16
- * the WebGL implementation throws on call so callers can feature-detect
17
- * via `caps.compute`. Volume Profile binning, M4 downsampling, footprint
18
- * aggregation will use it on the WebGPU path.
19
- * - `setUniforms` is intentionally schemaless on the interface — the
20
- * uniform layout is owned by each shader/program. Callers pass a
21
- * structured object that the backend translates into uniform writes.
6
+ * 设计说明:
7
+ * - drawInstances 是主力:一份 unit geometry instance buffer 渲染 N 份。
8
+ * 蜡烛、成交量柱、footprint 格子、heatmap 磁贴都归约为 instanced 图元。
9
+ * - drawLines MA-style 折线、drawing、坐标轴。
10
+ * - dispatchCompute 只有 WebGPU 有;WebGL 实现会抛异常,调用方先查 caps.compute。
11
+ * - uniform 布局由各 shader/pipeline 自行定义,接口不做 schema 约束。
22
12
  */
23
13
 
24
14
  import type { SurfaceBackend, SurfaceRegion } from './SurfaceBackend'
25
15
 
26
- /** Reported by the backend so feature-conditional code can branch. */
16
+ /** 后端上报的能力位,调用方据此分支 */
27
17
  export type RendererCapabilities = {
28
- /** WebGPU compute shaders available. WebGL backends report false. */
18
+ /** WebGPU compute shader 是否可用;WebGL 返回 false */
29
19
  compute: boolean
30
- /** Storage buffers usable as ring buffers / scatter-add targets. */
20
+ /** storage buffer 是否可用 */
31
21
  storageBuffer: boolean
32
- /** Maximum elements per drawInstances call (engine-imposed cap). */
22
+ /** 单次 drawInstances 上限(引擎强加) */
33
23
  maxInstances: number
34
- /** Friendly name, e.g. "webgl2" or "webgpu". */
24
+ /** 名称,如 "webgl2" "webgpu" */
35
25
  name: string
36
26
  }
37
27
 
38
- /** Vertex/instance buffer wrapper. Backends own the GPU handle. */
28
+ /**
29
+ * 各后端(WebGL/WebGPU/Canvas2D)都实现的绘制接口。
30
+ * 后端持有 GPU 资源,调用方通过不透明 handle 操作。
31
+ */
32
+ export interface Renderer {
33
+ /** 底层 surface,用于 region 绑定和最终合成 */
34
+ readonly surface: SurfaceBackend
35
+
36
+ /** 能力位标志 */
37
+ readonly caps: RendererCapabilities
38
+
39
+ // --- 资源生命周期 ---
40
+
41
+ createBuffer(usage: BufferUsage, sizeBytes: number): BufferHandle
42
+ writeBuffer(handle: BufferHandle, data: ArrayBufferView, offsetBytes?: number): void
43
+ destroyBuffer(handle: BufferHandle): void
44
+
45
+ /** 编译 shader pipeline。descriptor 格式后端自定,适配层通常提供工厂函数(如 createCandlePipeline) */
46
+ createPipeline(descriptor: unknown): PipelineHandle
47
+ destroyPipeline(handle: PipelineHandle): void
48
+
49
+ /** WebGPU only — WebGL 会抛异常 */
50
+ createComputePipeline(descriptor: unknown): ComputePipelineHandle
51
+ destroyComputePipeline(handle: ComputePipelineHandle): void
52
+
53
+ // --- 帧 ---
54
+
55
+ beginFrame(region: SurfaceRegion): void
56
+ /**
57
+ * 返回 true 表示本批已成功提交 GPU(或 instanceCount<=0 无需绘制)。
58
+ * 返回 false 表示未画上(无 surface / pipeline 不匹配 / 资源缺失)——调用方应 fail-closed 走 2D。
59
+ */
60
+ drawInstances(params: DrawInstancesParams): boolean
61
+ /**
62
+ * 返回 true 表示本批折线/填充已成功提交 GPU。
63
+ * 返回 false 表示未画上——调用方应 fail-closed 走 2D。
64
+ */
65
+ drawLines(params: DrawLinesParams): boolean
66
+ /** WebGPU only — WebGL 会抛异常,调用方先查 caps */
67
+ dispatchCompute(params: DispatchComputeParams): void
68
+ endFrame(): void
69
+
70
+ dispose(): void
71
+ }
72
+
73
+ /** 不透明 buffer handle,后端持有 GPU 资源 */
39
74
  export type BufferHandle = { readonly __brand: 'BufferHandle' }
40
- /** Pipeline / shader program wrapper. */
75
+ /** 不透明 pipeline handle */
41
76
  export type PipelineHandle = { readonly __brand: 'PipelineHandle' }
42
- /** Compute shader handle (WebGPU only). */
77
+ /** WebGPU compute pipeline handle */
43
78
  export type ComputePipelineHandle = { readonly __brand: 'ComputePipelineHandle' }
44
79
 
45
80
  export type BufferUsage = 'vertex' | 'instance' | 'index' | 'uniform' | 'storage'
46
81
 
47
82
  export type DrawInstancesParams = {
48
83
  pipeline: PipelineHandle
49
- /** Unit-geometry buffer (e.g. a single rectangle) shared across instances. */
84
+ /** 所有 instance 共享的 unit geometry(如一个矩形) */
50
85
  vertices: BufferHandle
51
- /** Per-instance attribute buffer (e.g. one row per K-line). */
86
+ /** 每个 instance 的属性 buffer(如一行 K 线数据) */
52
87
  instances: BufferHandle
53
- /** Number of instances to draw. */
88
+ /** 绘制多少个 instance */
54
89
  instanceCount: number
55
- /** Vertices per instance (e.g. 4 for a quad, 6 if using triangles). */
90
+ /** 每个 instance 的顶点数(quad 4,triangle 6 */
56
91
  vertexCount: number
57
- /** Uniform block — opaque object the backend reads. */
92
+ /** uniform 块,后端自行解译 */
58
93
  uniforms?: Record<string, unknown>
59
94
  }
60
95
 
@@ -72,7 +107,7 @@ export type DrawLinesParams = {
72
107
  */
73
108
  vertices?: BufferHandle
74
109
  vertexCount?: number
75
- /** Line strip vs disconnected segments. */
110
+ /** strip 连续折线 / list 独立线段 */
76
111
  topology?: 'strip' | 'list'
77
112
  /** 多色折线批量(推荐 MA 等多周期) */
78
113
  strips?: ReadonlyArray<DrawLineStrip>
@@ -81,60 +116,9 @@ export type DrawLinesParams = {
81
116
 
82
117
  export type DispatchComputeParams = {
83
118
  pipeline: ComputePipelineHandle
84
- /** Workgroup counts per axis. */
119
+ /** 各轴 workgroup */
85
120
  workgroups: [number, number?, number?]
86
- /** Input/output buffer bindings, keyed by binding index. */
121
+ /** 输入/输出 buffer 绑定,key binding index */
87
122
  bindings: Record<number, BufferHandle>
88
123
  uniforms?: Record<string, unknown>
89
124
  }
90
-
91
- /**
92
- * The drawing-primitives interface every backend implements.
93
- *
94
- * Backends own the GPU resources; callers receive opaque handles
95
- * (`BufferHandle`, `PipelineHandle`) and pass them back for draws.
96
- */
97
- export interface Renderer {
98
- /** Underlying surface (for region binding and final composite). */
99
- readonly surface: SurfaceBackend
100
-
101
- /** Reported feature flags — caller branches on `caps.compute` etc. */
102
- readonly caps: RendererCapabilities
103
-
104
- // --- resource lifecycle ---
105
-
106
- createBuffer(usage: BufferUsage, sizeBytes: number): BufferHandle
107
- writeBuffer(handle: BufferHandle, data: ArrayBufferView, offsetBytes?: number): void
108
- destroyBuffer(handle: BufferHandle): void
109
-
110
- /**
111
- * Compile a shader pipeline. The exact pipeline descriptor is backend-specific;
112
- * adapters typically expose factory helpers (e.g. `createCandlePipeline`) that
113
- * internally call this with the right vertex/fragment sources.
114
- */
115
- createPipeline(descriptor: unknown): PipelineHandle
116
- destroyPipeline(handle: PipelineHandle): void
117
-
118
- /** WebGPU only — WebGL implementations MUST throw with a clear message. */
119
- createComputePipeline(descriptor: unknown): ComputePipelineHandle
120
- destroyComputePipeline(handle: ComputePipelineHandle): void
121
-
122
- // --- frame ---
123
-
124
- beginFrame(region: SurfaceRegion): void
125
- /**
126
- * 返回 true 表示本批已成功提交 GPU(或 instanceCount<=0 无需绘制)。
127
- * 返回 false 表示未画上(无 surface / pipeline 不匹配 / 资源缺失)——调用方应 fail-closed 走 2D。
128
- */
129
- drawInstances(params: DrawInstancesParams): boolean
130
- /**
131
- * 返回 true 表示本批折线/填充已成功提交 GPU。
132
- * 返回 false 表示未画上——调用方应 fail-closed 走 2D。
133
- */
134
- drawLines(params: DrawLinesParams): boolean
135
- /** WebGPU only — WebGL implementations MUST throw. Caller checks caps first. */
136
- dispatchCompute(params: DispatchComputeParams): void
137
- endFrame(): void
138
-
139
- dispose(): void
140
- }
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { prepareLineStripForPhysicalPixels } from '../physicalLine'
4
+
5
+ describe('prepareLineStripForPhysicalPixels', () => {
6
+ it('snaps a horizontal one-pixel line to the physical pixel center', () => {
7
+ const strip = prepareLineStripForPhysicalPixels(
8
+ {
9
+ points: [
10
+ { x: 0.2, y: 5.1 },
11
+ { x: 10.7, y: 5.1 },
12
+ ],
13
+ color: '#f00',
14
+ width: 1,
15
+ },
16
+ 1.25,
17
+ )
18
+
19
+ expect(strip).toEqual({
20
+ points: [
21
+ { x: 0, y: 5.2 },
22
+ { x: 10.4, y: 5.2 },
23
+ ],
24
+ color: '#f00',
25
+ width: 0.8,
26
+ })
27
+ })
28
+
29
+ it('preserves diagonal vertices while quantizing the physical line width', () => {
30
+ const strip = prepareLineStripForPhysicalPixels(
31
+ {
32
+ points: [
33
+ { x: 0.2, y: 5.1 },
34
+ { x: 10.7, y: 8.4 },
35
+ ],
36
+ color: '#0f0',
37
+ width: 1,
38
+ },
39
+ 1.25,
40
+ )
41
+
42
+ expect(strip).toEqual({
43
+ points: [
44
+ { x: 0.2, y: 5.1 },
45
+ { x: 10.7, y: 8.4 },
46
+ ],
47
+ color: '#0f0',
48
+ width: 0.8,
49
+ })
50
+ })
51
+ })
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { toPhysicalRegion } from '../physicalRegion'
4
+
5
+ describe('toPhysicalRegion', () => {
6
+ it('keeps adjacent fractional-DPR regions contiguous', () => {
7
+ const upper = toPhysicalRegion({ x: 0, y: 0, width: 320, height: 101.2, dpr: 1.25 })
8
+ const lower = toPhysicalRegion({ x: 0, y: 101.2, width: 320, height: 98.8, dpr: 1.25 })
9
+
10
+ expect(upper).toEqual({ x: 0, y: 0, width: 400, height: 127 })
11
+ expect(lower).toEqual({ x: 0, y: 127, width: 400, height: 123 })
12
+ expect(upper.y + upper.height).toBe(lower.y)
13
+ })
14
+
15
+ it('clips a physical region to the backing canvas', () => {
16
+ expect(
17
+ toPhysicalRegion(
18
+ { x: -0.4, y: 10, width: 100.8, height: 20, dpr: 1.25 },
19
+ { width: 125, height: 100 },
20
+ ),
21
+ ).toEqual({ x: 0, y: 13, width: 125, height: 25 })
22
+ })
23
+ })
@@ -159,6 +159,15 @@ describe('createWebGPURenderer', () => {
159
159
  expect(fake.passes[0]?.setScissorRect).toHaveBeenCalledWith(20, 10, 160, 80)
160
160
  expect(fake.passes[0]?.draw).toHaveBeenCalledWith(6, 3)
161
161
  expect(fake.queue.submit).toHaveBeenCalledOnce()
162
+
163
+ const uniformWrite = fake.queue.writeBuffer.mock.calls.find((call) => call[4] === 32)
164
+ expect(uniformWrite).toBeDefined()
165
+ expect(
166
+ Array.from(new Float32Array(uniformWrite![2] as ArrayBuffer, uniformWrite![3], 8)),
167
+ ).toEqual([160, 80, 2, 4, 1, 0, 0, 1])
168
+ expect(
169
+ (fake.pipelineDescriptors[0]?.vertex.module as { code: string }).code,
170
+ ).toContain('round((rect.x - uniforms.scrollLeft) * uniforms.dpr)')
162
171
  })
163
172
 
164
173
  it('records multiple draws and submits once on endFrame', async () => {
@@ -189,6 +198,27 @@ describe('createWebGPURenderer', () => {
189
198
  expect(fake.queue.submit).toHaveBeenCalledTimes(1)
190
199
  })
191
200
 
201
+ it('expands a logical one-pixel line to its physical DPR width', async () => {
202
+ const fake = makeWebGPU()
203
+ const renderer = await createWebGPURenderer({
204
+ gpu: fake.gpu as unknown as GPU,
205
+ canvas: fake.canvas,
206
+ })
207
+ renderer.surface.resize(100, 100, 2)
208
+ renderer.beginFrame({ x: 0, y: 0, width: 100, height: 100, dpr: 2 })
209
+ const pipeline = renderer.createPipeline({ type: 'line' })
210
+
211
+ expect(
212
+ renderer.drawLines({
213
+ pipeline,
214
+ strips: [{ points: [{ x: 0, y: 10 }, { x: 100, y: 10 }], color: '#f00', width: 1 }],
215
+ }),
216
+ ).toBe(true)
217
+ renderer.endFrame()
218
+
219
+ expect(fake.pipelineDescriptors[0]?.primitive?.topology).toBe('triangle-list')
220
+ })
221
+
192
222
  it('preserves earlier rectangle batches within the same frame', async () => {
193
223
  const fake = makeWebGPU()
194
224
  const renderer = await createWebGPURenderer({
@@ -262,7 +292,7 @@ describe('createWebGPURenderer', () => {
262
292
  const uniformWrites = fake.queue.writeBuffer.mock.calls.filter((call) => call[4] === 32)
263
293
  expect(uniformWrites).toHaveLength(2)
264
294
  for (const call of uniformWrites) {
265
- expect(new Float32Array(call[2] as ArrayBuffer)[2]).toBe(12)
295
+ expect(new Float32Array(call[2] as ArrayBuffer)[3]).toBe(12)
266
296
  }
267
297
  expect(fake.queue.submit).toHaveBeenCalledOnce()
268
298
  })
@@ -51,6 +51,17 @@ describe('createWebGPUSurfaceBackend', () => {
51
51
  expect(canvas.style.height).toBe('180px')
52
52
  })
53
53
 
54
+ it('derives CSS size from the rounded backing buffer', () => {
55
+ const { canvas, surface } = makeSurface()
56
+
57
+ surface.resize(101.2, 80.4, 1.25)
58
+
59
+ expect(canvas.width).toBe(127)
60
+ expect(canvas.height).toBe(101)
61
+ expect(canvas.style.width).toBe('101.6px')
62
+ expect(canvas.style.height).toBe('80.8px')
63
+ })
64
+
54
65
  it('compositeTo is a no-op under hybrid DOM (M2)', () => {
55
66
  const { surface } = makeSurface()
56
67
  const target = {
@@ -11,6 +11,8 @@ import type {
11
11
  } from './Renderer'
12
12
  import { createWebGPUSurfaceBackend, type WebGPUSurfaceBackend } from './createWebGPUSurfaceBackend'
13
13
  import { createFrameMetrics } from './frameMetrics'
14
+ import { prepareLineStripForPhysicalPixels } from './physicalLine'
15
+ import { toPhysicalRegion } from './physicalRegion'
14
16
  import { createWebGPUResourceTable } from './webgpuResourceTable'
15
17
 
16
18
  type PipelineType = 'candle' | 'line' | 'fill'
@@ -42,8 +44,8 @@ const GPU_TEXTURE_RENDER_ATTACHMENT = globalThis.GPUTextureUsage?.RENDER_ATTACHM
42
44
  const RECT_SHADER = `
43
45
  struct Uniforms {
44
46
  resolution: vec2f,
47
+ dpr: f32,
45
48
  scrollLeft: f32,
46
- padding: f32,
47
49
  color: vec4f,
48
50
  }
49
51
 
@@ -56,7 +58,14 @@ fn vertexMain(@builtin(vertex_index) vertexIndex: u32, @location(0) rect: vec4f)
56
58
  vec2f(0.0, 1.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0)
57
59
  );
58
60
  let unit = corners[vertexIndex];
59
- let position = vec2f(rect.x - uniforms.scrollLeft + unit.x * rect.z, rect.y + unit.y * rect.w);
61
+ let left = round((rect.x - uniforms.scrollLeft) * uniforms.dpr);
62
+ let top = round(rect.y * uniforms.dpr);
63
+ let right = round((rect.x + rect.z - uniforms.scrollLeft) * uniforms.dpr);
64
+ let bottom = round((rect.y + rect.w) * uniforms.dpr);
65
+ let position = vec2f(
66
+ left + unit.x * max(1.0, right - left),
67
+ top + unit.y * max(1.0, bottom - top),
68
+ );
60
69
  let clip = vec2f(position.x / uniforms.resolution.x * 2.0 - 1.0, 1.0 - position.y / uniforms.resolution.y * 2.0);
61
70
  return vec4f(clip, 0.0, 1.0);
62
71
  }
@@ -70,8 +79,8 @@ fn fragmentMain() -> @location(0) vec4f {
70
79
  const LINE_SHADER = `
71
80
  struct Uniforms {
72
81
  resolution: vec2f,
82
+ dpr: f32,
73
83
  scrollLeft: f32,
74
- padding: f32,
75
84
  color: vec4f,
76
85
  }
77
86
 
@@ -79,7 +88,10 @@ struct Uniforms {
79
88
 
80
89
  @vertex
81
90
  fn vertexMain(@location(0) point: vec2f) -> @builtin(position) vec4f {
82
- let position = vec2f(point.x - uniforms.scrollLeft, point.y);
91
+ let position = vec2f(
92
+ (point.x - uniforms.scrollLeft) * uniforms.dpr,
93
+ point.y * uniforms.dpr,
94
+ );
83
95
  let clip = vec2f(position.x / uniforms.resolution.x * 2.0 - 1.0, 1.0 - position.y / uniforms.resolution.y * 2.0);
84
96
  return vec4f(clip, 0.0, 1.0);
85
97
  }
@@ -368,10 +380,10 @@ export async function createWebGPURenderer(
368
380
  const color = parseColor(colorValue ?? '#000000')
369
381
  if (!color) return null
370
382
  const values = new Float32Array([
371
- region.width,
372
- region.height,
383
+ Math.round(region.width * region.dpr),
384
+ Math.round(region.height * region.dpr),
385
+ region.dpr,
373
386
  scrollLeft,
374
- 0,
375
387
  color[0],
376
388
  color[1],
377
389
  color[2],
@@ -406,10 +418,10 @@ export async function createWebGPURenderer(
406
418
  },
407
419
  ],
408
420
  })
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))
421
+ const { x, y, width, height } = toPhysicalRegion(region, {
422
+ width: canvas.width,
423
+ height: canvas.height,
424
+ })
413
425
  if (width <= 0 || height <= 0) {
414
426
  pass.end()
415
427
  return null
@@ -456,10 +468,10 @@ export async function createWebGPURenderer(
456
468
  const region = draws[0]!.region
457
469
  rawSurface.bindRegion(region)
458
470
 
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))
471
+ const { x, y, width, height } = toPhysicalRegion(region, {
472
+ width: canvas.width,
473
+ height: canvas.height,
474
+ })
463
475
  if (width > 0 && height > 0) {
464
476
  pass.setViewport(x, y, width, height, 0, 1)
465
477
  pass.setScissorRect(x, y, width, height)
@@ -608,8 +620,9 @@ export async function createWebGPURenderer(
608
620
  const scrollLeft = (params.uniforms?.scrollLeft as number) ?? 0
609
621
  for (const strip of params.strips) {
610
622
  if (strip.points.length < 2) return false
611
- const wide = (strip.width ?? 1) > 1
612
- const values = wide ? buildWideLine(strip) : linePoints(strip)
623
+ const physicalStrip = prepareLineStripForPhysicalPixels(strip, currentRegion.dpr)
624
+ const wide = Math.round((physicalStrip.width ?? 1) * currentRegion.dpr) > 1
625
+ const values = wide ? buildWideLine(physicalStrip) : linePoints(physicalStrip)
613
626
  if (!values) return false
614
627
  // 帧内序号作 key:同顺序跨帧复用;revision 未变则不 upload
615
628
  const key = `strip/${stripSeq++}`
@@ -45,10 +45,10 @@ export function createWebGPUSurfaceBackend(
45
45
  const height = Math.max(1, Math.round(heightLogical * dpr))
46
46
  if (canvas.width !== width) canvas.width = width
47
47
  if (canvas.height !== height) canvas.height = height
48
- // 可见 DOM 合成:CSS 尺寸跟逻辑 plot,buffer 跟物理像素
48
+ // CSS 尺寸由物理 buffer 反算,避免浏览器二次缩放。
49
49
  if (canvas.style) {
50
- canvas.style.width = `${Math.max(0, widthLogical)}px`
51
- canvas.style.height = `${Math.max(0, heightLogical)}px`
50
+ canvas.style.width = `${width / dpr}px`
51
+ canvas.style.height = `${height / dpr}px`
52
52
  }
53
53
  },
54
54
  bindRegion(region: SurfaceRegion): boolean {
@@ -0,0 +1,45 @@
1
+ import type { DrawLineStrip } from './Renderer'
2
+
3
+ function physicalLineWidth(width: number, dpr: number): number {
4
+ return Math.max(1, Math.round(width * dpr))
5
+ }
6
+
7
+ function alignedCenter(value: number, widthPx: number, dpr: number): number {
8
+ const base = Math.floor(value * dpr)
9
+ return (base + (widthPx % 2 === 0 ? 0 : 0.5)) / dpr
10
+ }
11
+
12
+ function alignedEdge(value: number, dpr: number): number {
13
+ return Math.round(value * dpr) / dpr
14
+ }
15
+
16
+ /** 仅对轴向线吸附物理像素;斜线保持原始顶点供 MSAA 平滑。 */
17
+ export function prepareLineStripForPhysicalPixels(
18
+ strip: DrawLineStrip,
19
+ dpr: number,
20
+ ): DrawLineStrip {
21
+ const widthPx = physicalLineWidth(strip.width ?? 1, dpr)
22
+ const width = widthPx / dpr
23
+ const first = strip.points[0]
24
+ if (!first) return { ...strip, width }
25
+
26
+ const horizontal = strip.points.every((point) => point.y === first.y)
27
+ const vertical = strip.points.every((point) => point.x === first.x)
28
+ if (!horizontal && !vertical) return { ...strip, width }
29
+
30
+ if (horizontal) {
31
+ const y = alignedCenter(first.y, widthPx, dpr)
32
+ return {
33
+ ...strip,
34
+ width,
35
+ points: strip.points.map((point) => ({ x: alignedEdge(point.x, dpr), y })),
36
+ }
37
+ }
38
+
39
+ const x = alignedCenter(first.x, widthPx, dpr)
40
+ return {
41
+ ...strip,
42
+ width,
43
+ points: strip.points.map((point) => ({ x, y: alignedEdge(point.y, dpr) })),
44
+ }
45
+ }
@@ -0,0 +1,32 @@
1
+ import type { SurfaceRegion } from './SurfaceBackend'
2
+
3
+ export type PhysicalRegion = {
4
+ x: number
5
+ y: number
6
+ width: number
7
+ height: number
8
+ }
9
+
10
+ export type PhysicalBounds = {
11
+ width: number
12
+ height: number
13
+ }
14
+
15
+ /** 将逻辑 region 的两端分别对齐到物理像素,避免相邻 pane 出现缝隙。 */
16
+ export function toPhysicalRegion(
17
+ region: SurfaceRegion,
18
+ bounds?: PhysicalBounds,
19
+ ): PhysicalRegion {
20
+ const left = Math.round(region.x * region.dpr)
21
+ const top = Math.round(region.y * region.dpr)
22
+ const right = Math.round((region.x + region.width) * region.dpr)
23
+ const bottom = Math.round((region.y + region.height) * region.dpr)
24
+ const maxWidth = bounds ? Math.max(0, bounds.width) : Infinity
25
+ const maxHeight = bounds ? Math.max(0, bounds.height) : Infinity
26
+ const x = Math.max(0, Math.min(left, maxWidth))
27
+ const y = Math.max(0, Math.min(top, maxHeight))
28
+ const clippedRight = Math.max(x, Math.min(right, maxWidth))
29
+ const clippedBottom = Math.max(y, Math.min(bottom, maxHeight))
30
+
31
+ return { x, y, width: clippedRight - x, height: clippedBottom - y }
32
+ }
@@ -23,18 +23,23 @@ export function createLayerFromPlugin(
23
23
  set visible(v: boolean) {
24
24
  visible = v
25
25
  },
26
+ /**
27
+ * @Todo 兼容层:把旧 RendererPlugin.draw 包装成 Layer.paint。
28
+ */
26
29
  paint(ctx: PaintContext) {
27
30
  if (!visible) return
28
- // For sub-pane layers, skip if we're painting a different sub-pane
31
+ // 子图 layer 只画自己的 pane
29
32
  if (paneRole === 'sub' && ctx.paneId !== targetPaneId) return
33
+ // 获取业务层 RenderContext(含数据、几何、2D ctx)
30
34
  const context = getContext()
31
35
  if (!context) return
32
- // 注入本帧 Scene Renderer,供已迁路径(candle 等)走统一画笔
36
+ // 注入本帧 sceneRenderer,已迁路径走统一 GPU 画笔,未迁路径仍用 context.ctx
33
37
  context.sceneRenderer = ctx.renderer
34
38
  try {
39
+ // 调用各个渲染器的绘制能力
35
40
  plugin.draw(context)
36
41
  } catch (e) {
37
- // 隔离单层异常,避免中断同 pane 后续 Layer(对齐旧 Manager.render)
42
+ // 异常隔离,不中断同 pane 后续 layer
38
43
  console.error(`[RendererPlugin] ${plugin.name} draw error:`, e)
39
44
  }
40
45
  },
@@ -73,17 +73,18 @@ export function createScene(): Scene {
73
73
  return true
74
74
  }
75
75
 
76
+ /** 按 role 和可见性过滤 layer,z 排序后逐层 paint */
76
77
  const paintPane = (ctx: PaintContext, roles?: ReadonlyArray<LayerRole>): void => {
77
78
  if (disposed) return
79
+ // 选出属于当前 pane 或全局、且可见的 layer
78
80
  let candidates = layerList.filter(
79
81
  (layer) => (layer.paneRole === ctx.paneRole || layer.paneRole === 'global') && layer.visible,
80
82
  )
83
+ // 若传了 roles 参数,进一步按角色过滤(如只画 overlay)
81
84
  if (roles) {
82
85
  candidates = candidates.filter((layer) => roles.includes(layer.role))
83
86
  }
84
- // Stable-sort by z. Native Array.prototype.sort is required
85
- // by ECMAScript 2019+ to be stable, so equal z values preserve the
86
- // surviving registration order from the filter pass.
87
+ // z 升序稳定排序,相同时保留注册顺序(ECMAScript 2019+ 保证 sort 稳定)
87
88
  candidates.sort((a, b) => a.z - b.z)
88
89
  for (const layer of candidates) {
89
90
  layer.paint(ctx)
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.9.0-alpha.2'
1
+ export const VERSION = '0.9.0-alpha.3'