@363045841yyt/klinechart-core 0.7.3 → 0.7.5-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.
- package/README.md +201 -201
- package/README.zh-CN.md +201 -201
- package/dist/engine/renderers/webgl/candleSurface.js +47 -47
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -2
- package/dist/version.js.map +1 -1
- package/package.json +129 -122
- package/src/__tests__/signal.test.ts +124 -124
- package/src/config/chartSettings.ts +66 -66
- package/src/controllers/__tests__/drawing.test.ts +214 -214
- package/src/controllers/__tests__/indicatorSelector.test.ts +481 -481
- package/src/controllers/__tests__/toolbar.test.ts +225 -225
- package/src/controllers/createChartController.ts +665 -665
- package/src/controllers/createDrawingController.ts +96 -96
- package/src/controllers/createIndicatorSelectorController.ts +307 -307
- package/src/controllers/createToolbarController.ts +146 -146
- package/src/controllers/index.ts +19 -19
- package/src/controllers/types.ts +284 -284
- package/src/engine/__tests__/chart.dpr.test.ts +401 -401
- package/src/engine/__tests__/paneRenderer.resize.test.ts +92 -92
- package/src/engine/chart-store.ts +121 -121
- package/src/engine/chart.d.ts +617 -617
- package/src/engine/chart.ts +2815 -2815
- package/src/engine/controller/__tests__/interaction.dpr.test.ts +259 -259
- package/src/engine/controller/interaction.ts +722 -722
- package/src/engine/controller/markerInteraction.ts +130 -130
- package/src/engine/controller/pinchTracker.ts +82 -82
- package/src/engine/controller/tooltipPosition.ts +48 -48
- package/src/engine/draw/__tests__/pixelAlign.spec.ts +176 -176
- package/src/engine/draw/pixelAlign.ts +259 -259
- package/src/engine/drawing/index.ts +655 -655
- package/src/engine/drawing/interaction.ts +842 -842
- package/src/engine/drawing/plugin.ts +343 -343
- package/src/engine/indicators/__tests__/__fixtures__/golden/atr.json +38 -38
- package/src/engine/indicators/__tests__/__fixtures__/golden/dema.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/hma.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/index.ts +55 -55
- package/src/engine/indicators/__tests__/__fixtures__/golden/kama.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/tema.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/wma.json +40 -40
- package/src/engine/indicators/__tests__/__fixtures__/synthetic.ts +65 -65
- package/src/engine/indicators/__tests__/_propertyAssertions.ts +76 -76
- package/src/engine/indicators/__tests__/atr.test.ts +153 -153
- package/src/engine/indicators/__tests__/calculators.test.ts +614 -614
- package/src/engine/indicators/__tests__/cmf-mfi.test.ts +100 -100
- package/src/engine/indicators/__tests__/dema.test.ts +73 -73
- package/src/engine/indicators/__tests__/donchian.test.ts +70 -70
- package/src/engine/indicators/__tests__/hma.test.ts +73 -73
- package/src/engine/indicators/__tests__/ichimoku.test.ts +105 -105
- package/src/engine/indicators/__tests__/kama.test.ts +80 -80
- package/src/engine/indicators/__tests__/keltner.test.ts +65 -65
- package/src/engine/indicators/__tests__/pivot-fib.test.ts +110 -110
- package/src/engine/indicators/__tests__/roc.test.ts +68 -68
- package/src/engine/indicators/__tests__/sar.test.ts +86 -86
- package/src/engine/indicators/__tests__/scheduler.test.ts +831 -831
- package/src/engine/indicators/__tests__/soa.test.ts +533 -533
- package/src/engine/indicators/__tests__/structure.test.ts +110 -110
- package/src/engine/indicators/__tests__/supertrend.test.ts +65 -65
- package/src/engine/indicators/__tests__/tema.test.ts +68 -68
- package/src/engine/indicators/__tests__/trix.test.ts +70 -70
- package/src/engine/indicators/__tests__/volatility.test.ts +117 -117
- package/src/engine/indicators/__tests__/volume.test.ts +115 -115
- package/src/engine/indicators/__tests__/volumeProfile.test.ts +74 -74
- package/src/engine/indicators/__tests__/vwap.test.ts +69 -69
- package/src/engine/indicators/__tests__/wma.test.ts +112 -112
- package/src/engine/indicators/__tests__/zones.test.ts +95 -95
- package/src/engine/indicators/atrState.ts +27 -27
- package/src/engine/indicators/bollState.ts +51 -51
- package/src/engine/indicators/calculators.ts +2593 -2593
- package/src/engine/indicators/cciState.ts +25 -25
- package/src/engine/indicators/chaikinVolState.ts +32 -32
- package/src/engine/indicators/cmfState.ts +27 -27
- package/src/engine/indicators/demaState.ts +27 -27
- package/src/engine/indicators/donchianState.ts +43 -43
- package/src/engine/indicators/eneState.ts +43 -43
- package/src/engine/indicators/expmaState.ts +43 -43
- package/src/engine/indicators/fastkState.ts +25 -25
- package/src/engine/indicators/fibState.ts +41 -41
- package/src/engine/indicators/hmaState.ts +27 -27
- package/src/engine/indicators/hvState.ts +28 -28
- package/src/engine/indicators/ichimokuState.ts +70 -70
- package/src/engine/indicators/indicator.worker.ts +169 -169
- package/src/engine/indicators/indicatorDefinitionRegistry.ts +62 -62
- package/src/engine/indicators/indicatorMetadata.ts +110 -110
- package/src/engine/indicators/indicatorRegistry.ts +106 -106
- package/src/engine/indicators/indicatorRuntime.ts +1548 -1548
- package/src/engine/indicators/kamaState.ts +34 -34
- package/src/engine/indicators/keltnerState.ts +49 -49
- package/src/engine/indicators/kstState.ts +42 -42
- package/src/engine/indicators/maState.ts +36 -36
- package/src/engine/indicators/macdState.ts +76 -76
- package/src/engine/indicators/mfiState.ts +27 -27
- package/src/engine/indicators/momState.ts +25 -25
- package/src/engine/indicators/obvState.ts +25 -25
- package/src/engine/indicators/parkinsonState.ts +28 -28
- package/src/engine/indicators/pivotState.ts +51 -51
- package/src/engine/indicators/pvtState.ts +25 -25
- package/src/engine/indicators/rocState.ts +27 -27
- package/src/engine/indicators/rsiState.ts +65 -65
- package/src/engine/indicators/sarState.ts +41 -41
- package/src/engine/indicators/scheduler.ts +1205 -1205
- package/src/engine/indicators/soa.ts +352 -352
- package/src/engine/indicators/stateComposer.ts +1262 -1262
- package/src/engine/indicators/stochState.ts +26 -26
- package/src/engine/indicators/structureState.ts +69 -69
- package/src/engine/indicators/supertrendState.ts +37 -37
- package/src/engine/indicators/temaState.ts +27 -27
- package/src/engine/indicators/trixState.ts +35 -35
- package/src/engine/indicators/vmaState.ts +27 -27
- package/src/engine/indicators/volumeProfileState.ts +63 -63
- package/src/engine/indicators/vwapState.ts +29 -29
- package/src/engine/indicators/wmaState.ts +27 -27
- package/src/engine/indicators/wmsrState.ts +25 -25
- package/src/engine/indicators/workerProtocol.ts +613 -613
- package/src/engine/indicators/zonesState.ts +47 -47
- package/src/engine/layout/pane.ts +161 -161
- package/src/engine/marker/registry.ts +265 -265
- package/src/engine/paneRenderer.ts +169 -169
- package/src/engine/renderers/Indicator/atr.ts +237 -237
- package/src/engine/renderers/Indicator/boll.ts +317 -317
- package/src/engine/renderers/Indicator/cci.ts +275 -275
- package/src/engine/renderers/Indicator/chaikinVol.ts +138 -138
- package/src/engine/renderers/Indicator/cmf.ts +137 -137
- package/src/engine/renderers/Indicator/dema.ts +136 -136
- package/src/engine/renderers/Indicator/donchian.ts +137 -137
- package/src/engine/renderers/Indicator/ene.ts +271 -271
- package/src/engine/renderers/Indicator/expma.ts +197 -197
- package/src/engine/renderers/Indicator/fastk.ts +316 -316
- package/src/engine/renderers/Indicator/fib.ts +141 -141
- package/src/engine/renderers/Indicator/hma.ts +136 -136
- package/src/engine/renderers/Indicator/hv.ts +124 -124
- package/src/engine/renderers/Indicator/ichimoku.ts +181 -181
- package/src/engine/renderers/Indicator/index.ts +241 -241
- package/src/engine/renderers/Indicator/indicatorData.ts +650 -650
- package/src/engine/renderers/Indicator/kama.ts +136 -136
- package/src/engine/renderers/Indicator/keltner.ts +137 -137
- package/src/engine/renderers/Indicator/kst.ts +302 -302
- package/src/engine/renderers/Indicator/ma.ts +200 -200
- package/src/engine/renderers/Indicator/macd.ts +477 -477
- package/src/engine/renderers/Indicator/macdLegend.ts +141 -141
- package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +272 -272
- package/src/engine/renderers/Indicator/mfi.ts +142 -142
- package/src/engine/renderers/Indicator/mom.ts +311 -311
- package/src/engine/renderers/Indicator/obv.ts +123 -123
- package/src/engine/renderers/Indicator/parkinson.ts +124 -124
- package/src/engine/renderers/Indicator/pivot.ts +131 -131
- package/src/engine/renderers/Indicator/pvt.ts +123 -123
- package/src/engine/renderers/Indicator/roc.ts +143 -143
- package/src/engine/renderers/Indicator/rsi.ts +390 -390
- package/src/engine/renderers/Indicator/sar.ts +113 -113
- package/src/engine/renderers/Indicator/scale/atr_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/cci_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/fastk_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/indicator_scale.ts +204 -204
- package/src/engine/renderers/Indicator/scale/kst_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/macd_scale.ts +22 -22
- package/src/engine/renderers/Indicator/scale/mom_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/rsi_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/stoch_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/volume_scale.ts +26 -26
- package/src/engine/renderers/Indicator/scale/wmsr_scale.ts +19 -19
- package/src/engine/renderers/Indicator/stoch.ts +359 -359
- package/src/engine/renderers/Indicator/structure.ts +126 -126
- package/src/engine/renderers/Indicator/subPaneConfig.ts +265 -265
- package/src/engine/renderers/Indicator/supertrend.ts +115 -115
- package/src/engine/renderers/Indicator/tema.ts +136 -136
- package/src/engine/renderers/Indicator/trix.ts +158 -158
- package/src/engine/renderers/Indicator/vma.ts +124 -124
- package/src/engine/renderers/Indicator/volumeProfile.ts +125 -125
- package/src/engine/renderers/Indicator/vwap.ts +123 -123
- package/src/engine/renderers/Indicator/wma.ts +136 -136
- package/src/engine/renderers/Indicator/wmsr.ts +328 -328
- package/src/engine/renderers/Indicator/zones.ts +104 -104
- package/src/engine/renderers/__tests__/boll.renderer.test.ts +314 -314
- package/src/engine/renderers/__tests__/ene.renderer.test.ts +305 -305
- package/src/engine/renderers/__tests__/expma.renderer.test.ts +279 -279
- package/src/engine/renderers/__tests__/ma.renderer.test.ts +426 -426
- package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +502 -502
- package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +173 -173
- package/src/engine/renderers/candle.ts +459 -459
- package/src/engine/renderers/crosshair.ts +69 -69
- package/src/engine/renderers/customMarkers.ts +162 -162
- package/src/engine/renderers/extremaMarkers.ts +246 -246
- package/src/engine/renderers/gridLines.ts +90 -90
- package/src/engine/renderers/lastPrice.ts +97 -97
- package/src/engine/renderers/paneTitle.ts +136 -136
- package/src/engine/renderers/subVolume.ts +236 -236
- package/src/engine/renderers/timeAxis.ts +121 -121
- package/src/engine/renderers/webgl/candleSurface.ts +955 -955
- package/src/engine/renderers/webgl/sharedWebGLSurface.ts +146 -146
- package/src/engine/renderers/yAxis.ts +105 -105
- package/src/engine/scale/__tests__/logFormula.spec.ts +148 -148
- package/src/engine/scale/logFormula.ts +130 -130
- package/src/engine/scale/price.ts +39 -39
- package/src/engine/scale/priceScale.ts +264 -264
- package/src/engine/subPaneManager.ts +427 -427
- package/src/engine/theme/colors.ts +642 -642
- package/src/engine/theme/fonts.ts +20 -20
- package/src/engine/utils/klineConfig.ts +49 -49
- package/src/engine/utils/tickCount.ts +11 -11
- package/src/engine/utils/tickPosition.ts +214 -214
- package/src/engine/utils/zoom.ts +83 -83
- package/src/engine/viewport/viewport.ts +67 -67
- package/src/index.ts +3 -3
- package/src/plugin/ConfigManager.ts +93 -93
- package/src/plugin/EventBus.ts +77 -77
- package/src/plugin/HookSystem.ts +106 -106
- package/src/plugin/PluginHost.ts +243 -243
- package/src/plugin/PluginRegistry.ts +92 -92
- package/src/plugin/StateStore.ts +73 -73
- package/src/plugin/index.ts +19 -19
- package/src/plugin/rendererPluginManager.ts +368 -368
- package/src/plugin/stateKeys.ts +8 -8
- package/src/plugin/types.ts +526 -526
- package/src/reactivity/index.ts +2 -2
- package/src/reactivity/signal.ts +119 -119
- package/src/semantic/controller.ts +251 -251
- package/src/semantic/drawShape.ts +260 -260
- package/src/semantic/index.ts +28 -28
- package/src/semantic/schema.json +256 -256
- package/src/semantic/types.ts +251 -251
- package/src/semantic/validator.ts +349 -349
- package/src/types/kLine.ts +13 -13
- package/src/types/price.ts +56 -56
- package/src/types/volumePrice.ts +33 -33
- package/src/utils/dateFormat.ts +208 -208
- package/src/utils/kLineDraw/axis.ts +562 -562
- package/src/utils/priceToY.ts +34 -34
- package/src/utils/volumePrice.ts +202 -202
- package/src/version.ts +1 -1
|
@@ -1,955 +1,955 @@
|
|
|
1
|
-
import { SharedWebGLSurface, type PhysicalRegion, type WebGLCompositeOptions, type WebGLRegion } from './sharedWebGLSurface'
|
|
2
|
-
|
|
3
|
-
type Rect = {
|
|
4
|
-
x: number
|
|
5
|
-
y: number
|
|
6
|
-
width: number
|
|
7
|
-
height: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type LineStrip = {
|
|
11
|
-
points: Array<{ x: number; y: number }>
|
|
12
|
-
width: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type ColoredLineStrip = LineStrip & {
|
|
16
|
-
color: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type FilledBand = {
|
|
20
|
-
upperPoints: Array<{ x: number; y: number }>
|
|
21
|
-
lowerPoints: Array<{ x: number; y: number }>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type FloatColor = readonly [number, number, number, number]
|
|
25
|
-
|
|
26
|
-
type RectWebGLHandles = {
|
|
27
|
-
program: WebGLProgram
|
|
28
|
-
vao: WebGLVertexArrayObject
|
|
29
|
-
unitBuffer: WebGLBuffer
|
|
30
|
-
rectBuffer: WebGLBuffer
|
|
31
|
-
resolutionLocation: WebGLUniformLocation
|
|
32
|
-
scrollXLocation: WebGLUniformLocation
|
|
33
|
-
colorLocation: WebGLUniformLocation
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type BasicLineWebGLHandles = {
|
|
37
|
-
program: WebGLProgram
|
|
38
|
-
vao: WebGLVertexArrayObject
|
|
39
|
-
vertexBuffer: WebGLBuffer
|
|
40
|
-
resolutionLocation: WebGLUniformLocation
|
|
41
|
-
scrollXLocation: WebGLUniformLocation
|
|
42
|
-
colorLocation: WebGLUniformLocation
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type LineWebGLHandles = {
|
|
46
|
-
basic: BasicLineWebGLHandles
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
type LineMsaaTargets = {
|
|
50
|
-
samples: number
|
|
51
|
-
widthPx: number
|
|
52
|
-
heightPx: number
|
|
53
|
-
msaaFramebuffer: WebGLFramebuffer
|
|
54
|
-
msaaColorRenderbuffer: WebGLRenderbuffer
|
|
55
|
-
resolveFramebuffer: WebGLFramebuffer
|
|
56
|
-
resolveTexture: WebGLTexture
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const RECT_VERTEX_SHADER_SOURCE = `#version 300 es
|
|
60
|
-
precision mediump float;
|
|
61
|
-
|
|
62
|
-
in vec2 a_unit;
|
|
63
|
-
in vec4 a_rect;
|
|
64
|
-
|
|
65
|
-
uniform vec2 u_resolution;
|
|
66
|
-
uniform float u_scrollX;
|
|
67
|
-
|
|
68
|
-
void main() {
|
|
69
|
-
vec2 position = vec2(
|
|
70
|
-
a_rect.x - u_scrollX + a_unit.x * a_rect.z,
|
|
71
|
-
a_rect.y + a_unit.y * a_rect.w
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
vec2 zeroToOne = position / u_resolution;
|
|
75
|
-
vec2 clip = vec2(
|
|
76
|
-
zeroToOne.x * 2.0 - 1.0,
|
|
77
|
-
1.0 - zeroToOne.y * 2.0
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
gl_Position = vec4(clip, 0.0, 1.0);
|
|
81
|
-
}`
|
|
82
|
-
|
|
83
|
-
const LINE_VERTEX_SHADER_SOURCE = `#version 300 es
|
|
84
|
-
precision mediump float;
|
|
85
|
-
|
|
86
|
-
in vec2 a_position;
|
|
87
|
-
|
|
88
|
-
uniform vec2 u_resolution;
|
|
89
|
-
uniform float u_scrollX;
|
|
90
|
-
|
|
91
|
-
void main() {
|
|
92
|
-
vec2 position = vec2(a_position.x - u_scrollX, a_position.y);
|
|
93
|
-
vec2 zeroToOne = position / u_resolution;
|
|
94
|
-
vec2 clip = vec2(
|
|
95
|
-
zeroToOne.x * 2.0 - 1.0,
|
|
96
|
-
1.0 - zeroToOne.y * 2.0
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
gl_Position = vec4(clip, 0.0, 1.0);
|
|
100
|
-
}`
|
|
101
|
-
|
|
102
|
-
const FRAGMENT_SHADER_SOURCE = `#version 300 es
|
|
103
|
-
precision mediump float;
|
|
104
|
-
|
|
105
|
-
uniform vec4 u_color;
|
|
106
|
-
out vec4 outColor;
|
|
107
|
-
|
|
108
|
-
void main() {
|
|
109
|
-
outColor = u_color;
|
|
110
|
-
}`
|
|
111
|
-
|
|
112
|
-
const UNIT_QUAD = new Float32Array([
|
|
113
|
-
0, 0,
|
|
114
|
-
1, 0,
|
|
115
|
-
0, 1,
|
|
116
|
-
0, 1,
|
|
117
|
-
1, 0,
|
|
118
|
-
1, 1,
|
|
119
|
-
])
|
|
120
|
-
|
|
121
|
-
export class CandleWebGLSurface {
|
|
122
|
-
private shared: SharedWebGLSurface
|
|
123
|
-
private handles: RectWebGLHandles | null = null
|
|
124
|
-
private logicalWidth = 0
|
|
125
|
-
private logicalHeight = 0
|
|
126
|
-
private available = false
|
|
127
|
-
private rectCapacity = 0
|
|
128
|
-
private rectScratch = new Float32Array(0)
|
|
129
|
-
private region: WebGLRegion | null = null
|
|
130
|
-
|
|
131
|
-
constructor(shared: SharedWebGLSurface) {
|
|
132
|
-
this.shared = shared
|
|
133
|
-
this.handles = this.initRectHandles()
|
|
134
|
-
this.available = this.handles !== null
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
isAvailable(): boolean {
|
|
138
|
-
return this.available
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
getCanvas(): HTMLCanvasElement {
|
|
142
|
-
return this.shared.getCanvas()
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
setRegion(region: WebGLRegion): void {
|
|
146
|
-
this.region = region
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
resize(width: number, height: number, _dpr: number): void {
|
|
150
|
-
this.logicalWidth = width
|
|
151
|
-
this.logicalHeight = height
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
clear(): void {
|
|
155
|
-
if (!this.region || this.logicalWidth <= 0 || this.logicalHeight <= 0) return
|
|
156
|
-
this.shared.clearRegion(this.region)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
compositeTo(ctx: CanvasRenderingContext2D, options: WebGLCompositeOptions = {}): void {
|
|
160
|
-
if (!this.region) return
|
|
161
|
-
this.shared.compositeRegionTo(ctx, this.region, options)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** 直接传入已打包的 Float32Array:每 4 个元素为一组 (x, y, width, height) */
|
|
165
|
-
drawRectBuffer(rectData: Float32Array, rectCount: number, color: string, scrollLeft: number): boolean {
|
|
166
|
-
const handles = this.handles
|
|
167
|
-
if (!handles || rectCount === 0 || this.logicalWidth <= 0 || this.logicalHeight <= 0) {
|
|
168
|
-
return false
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const colorValue = parseColor(color)
|
|
172
|
-
if (!colorValue) return false
|
|
173
|
-
|
|
174
|
-
const floatCount = rectCount * 4
|
|
175
|
-
const gl = this.shared.getGL()
|
|
176
|
-
if (!gl || !this.region || !this.shared.bindRegion(this.region)) return false
|
|
177
|
-
|
|
178
|
-
gl.useProgram(handles.program)
|
|
179
|
-
gl.bindVertexArray(handles.vao)
|
|
180
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, handles.rectBuffer)
|
|
181
|
-
|
|
182
|
-
if (this.rectCapacity < floatCount) {
|
|
183
|
-
this.rectCapacity = nextBufferFloatCapacity(floatCount)
|
|
184
|
-
gl.bufferData(gl.ARRAY_BUFFER, this.rectCapacity * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW)
|
|
185
|
-
}
|
|
186
|
-
gl.bufferSubData(gl.ARRAY_BUFFER, 0, rectData)
|
|
187
|
-
|
|
188
|
-
if (colorValue[3] === 1) {
|
|
189
|
-
gl.disable(gl.BLEND)
|
|
190
|
-
} else {
|
|
191
|
-
gl.enable(gl.BLEND)
|
|
192
|
-
}
|
|
193
|
-
gl.uniform2f(handles.resolutionLocation, this.logicalWidth, this.logicalHeight)
|
|
194
|
-
gl.uniform1f(handles.scrollXLocation, scrollLeft)
|
|
195
|
-
gl.uniform4f(handles.colorLocation, colorValue[0], colorValue[1], colorValue[2], colorValue[3])
|
|
196
|
-
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, rectCount)
|
|
197
|
-
gl.bindVertexArray(null)
|
|
198
|
-
return true
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
drawRects(rects: Rect[], color: string, scrollLeft: number): boolean {
|
|
202
|
-
const handles = this.handles
|
|
203
|
-
if (!handles || !rects.length || this.logicalWidth <= 0 || this.logicalHeight <= 0) {
|
|
204
|
-
return false
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const floatCount = rects.length * 4
|
|
208
|
-
if (this.rectScratch.length < floatCount) {
|
|
209
|
-
this.rectScratch = new Float32Array(nextBufferFloatCapacity(floatCount))
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
for (let i = 0; i < rects.length; i++) {
|
|
213
|
-
const rect = rects[i]!
|
|
214
|
-
const offset = i * 4
|
|
215
|
-
this.rectScratch[offset] = rect.x
|
|
216
|
-
this.rectScratch[offset + 1] = rect.y
|
|
217
|
-
this.rectScratch[offset + 2] = rect.width
|
|
218
|
-
this.rectScratch[offset + 3] = rect.height
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return this.drawRectBuffer(this.rectScratch.subarray(0, floatCount), rects.length, color, scrollLeft)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
destroy(): void {
|
|
225
|
-
const handles = this.handles
|
|
226
|
-
if (!handles) return
|
|
227
|
-
|
|
228
|
-
const gl = this.shared.getGL()
|
|
229
|
-
if (gl) {
|
|
230
|
-
const { program, vao, unitBuffer, rectBuffer } = handles
|
|
231
|
-
gl.deleteBuffer(unitBuffer)
|
|
232
|
-
gl.deleteBuffer(rectBuffer)
|
|
233
|
-
gl.deleteVertexArray(vao)
|
|
234
|
-
gl.deleteProgram(program)
|
|
235
|
-
}
|
|
236
|
-
this.handles = null
|
|
237
|
-
this.available = false
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
private initRectHandles(): RectWebGLHandles | null {
|
|
241
|
-
const gl = this.shared.getGL()
|
|
242
|
-
if (!gl) return null
|
|
243
|
-
|
|
244
|
-
const vertexShader = createShader(gl, gl.VERTEX_SHADER, RECT_VERTEX_SHADER_SOURCE)
|
|
245
|
-
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE)
|
|
246
|
-
if (!vertexShader || !fragmentShader) {
|
|
247
|
-
if (vertexShader) gl.deleteShader(vertexShader)
|
|
248
|
-
if (fragmentShader) gl.deleteShader(fragmentShader)
|
|
249
|
-
return null
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const program = createProgram(gl, vertexShader, fragmentShader)
|
|
253
|
-
gl.deleteShader(vertexShader)
|
|
254
|
-
gl.deleteShader(fragmentShader)
|
|
255
|
-
if (!program) return null
|
|
256
|
-
|
|
257
|
-
const vao = gl.createVertexArray()
|
|
258
|
-
const unitBuffer = gl.createBuffer()
|
|
259
|
-
const rectBuffer = gl.createBuffer()
|
|
260
|
-
if (!vao || !unitBuffer || !rectBuffer) {
|
|
261
|
-
if (vao) gl.deleteVertexArray(vao)
|
|
262
|
-
if (unitBuffer) gl.deleteBuffer(unitBuffer)
|
|
263
|
-
if (rectBuffer) gl.deleteBuffer(rectBuffer)
|
|
264
|
-
gl.deleteProgram(program)
|
|
265
|
-
return null
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution')
|
|
269
|
-
const scrollXLocation = gl.getUniformLocation(program, 'u_scrollX')
|
|
270
|
-
const colorLocation = gl.getUniformLocation(program, 'u_color')
|
|
271
|
-
if (!resolutionLocation || !scrollXLocation || !colorLocation) {
|
|
272
|
-
gl.deleteBuffer(unitBuffer)
|
|
273
|
-
gl.deleteBuffer(rectBuffer)
|
|
274
|
-
gl.deleteVertexArray(vao)
|
|
275
|
-
gl.deleteProgram(program)
|
|
276
|
-
return null
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const unitLocation = gl.getAttribLocation(program, 'a_unit')
|
|
280
|
-
const rectLocation = gl.getAttribLocation(program, 'a_rect')
|
|
281
|
-
if (unitLocation < 0 || rectLocation < 0) {
|
|
282
|
-
gl.deleteBuffer(unitBuffer)
|
|
283
|
-
gl.deleteBuffer(rectBuffer)
|
|
284
|
-
gl.deleteVertexArray(vao)
|
|
285
|
-
gl.deleteProgram(program)
|
|
286
|
-
return null
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
gl.bindVertexArray(vao)
|
|
290
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, unitBuffer)
|
|
291
|
-
gl.bufferData(gl.ARRAY_BUFFER, UNIT_QUAD, gl.STATIC_DRAW)
|
|
292
|
-
gl.enableVertexAttribArray(unitLocation)
|
|
293
|
-
gl.vertexAttribPointer(unitLocation, 2, gl.FLOAT, false, 0, 0)
|
|
294
|
-
gl.vertexAttribDivisor(unitLocation, 0)
|
|
295
|
-
|
|
296
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, rectBuffer)
|
|
297
|
-
gl.enableVertexAttribArray(rectLocation)
|
|
298
|
-
gl.vertexAttribPointer(rectLocation, 4, gl.FLOAT, false, 16, 0)
|
|
299
|
-
gl.vertexAttribDivisor(rectLocation, 1)
|
|
300
|
-
gl.bindVertexArray(null)
|
|
301
|
-
|
|
302
|
-
gl.enable(gl.BLEND)
|
|
303
|
-
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
|
304
|
-
|
|
305
|
-
return {
|
|
306
|
-
program,
|
|
307
|
-
vao,
|
|
308
|
-
unitBuffer,
|
|
309
|
-
rectBuffer,
|
|
310
|
-
resolutionLocation,
|
|
311
|
-
scrollXLocation,
|
|
312
|
-
colorLocation,
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export class LineWebGLSurface {
|
|
318
|
-
private shared: SharedWebGLSurface
|
|
319
|
-
private handles: LineWebGLHandles | null = null
|
|
320
|
-
private logicalWidth = 0
|
|
321
|
-
private logicalHeight = 0
|
|
322
|
-
private dpr = 1
|
|
323
|
-
private available = false
|
|
324
|
-
private vertexCapacity = 0
|
|
325
|
-
private fillScratch = new Float32Array(0)
|
|
326
|
-
private lineScratch = new Float32Array(0)
|
|
327
|
-
private region: WebGLRegion | null = null
|
|
328
|
-
private msaaTargets: LineMsaaTargets | null = null
|
|
329
|
-
|
|
330
|
-
// Geometry cache: 以 points 数组引用 + halfWidth 为 key,避免每帧重算法线/miter
|
|
331
|
-
private geoCache = new WeakMap<Array<{ x: number; y: number }>, Map<number, { vertices: Float32Array; vertexCount: number }>>()
|
|
332
|
-
|
|
333
|
-
constructor(shared: SharedWebGLSurface) {
|
|
334
|
-
this.shared = shared
|
|
335
|
-
this.handles = this.initLineHandles()
|
|
336
|
-
this.available = this.handles !== null
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
isAvailable(): boolean {
|
|
340
|
-
return this.available
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
getCanvas(): HTMLCanvasElement {
|
|
344
|
-
return this.shared.getCanvas()
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
setRegion(region: WebGLRegion): void {
|
|
348
|
-
this.region = region
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
resize(width: number, height: number, dpr: number): void {
|
|
352
|
-
this.logicalWidth = width
|
|
353
|
-
this.logicalHeight = height
|
|
354
|
-
this.dpr = dpr
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
clear(): void {
|
|
358
|
-
if (!this.region || this.logicalWidth <= 0 || this.logicalHeight <= 0) return
|
|
359
|
-
this.shared.clearRegion(this.region)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
compositeTo(ctx: CanvasRenderingContext2D, options: WebGLCompositeOptions = {}): void {
|
|
363
|
-
if (!this.region) return
|
|
364
|
-
this.shared.compositeRegionTo(ctx, this.region, options)
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
drawLineStrips(lines: ColoredLineStrip[], scrollLeft: number): boolean {
|
|
368
|
-
const handles = this.handles
|
|
369
|
-
if (!handles || lines.length === 0 || this.logicalWidth <= 0 || this.logicalHeight <= 0) {
|
|
370
|
-
return false
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
type DrawCmd = {
|
|
374
|
-
colorValue: FloatColor
|
|
375
|
-
mode: number
|
|
376
|
-
firstVertex: number
|
|
377
|
-
pointCount: number
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const gl = this.shared.getGL()
|
|
381
|
-
const region = this.region
|
|
382
|
-
if (!gl || !region) return false
|
|
383
|
-
|
|
384
|
-
const drawCmds: DrawCmd[] = []
|
|
385
|
-
let totalFloats = 0
|
|
386
|
-
|
|
387
|
-
for (const line of lines) {
|
|
388
|
-
if (line.points.length < 2) return false
|
|
389
|
-
|
|
390
|
-
const colorValue = parseColor(line.color)
|
|
391
|
-
if (!colorValue) return false
|
|
392
|
-
|
|
393
|
-
if (line.width === 1) {
|
|
394
|
-
const { vertexCount, vertices } = this.getThinLineVertices(line.points)
|
|
395
|
-
drawCmds.push({ colorValue, mode: gl.LINE_STRIP, firstVertex: totalFloats / 2, pointCount: vertexCount })
|
|
396
|
-
totalFloats += vertices.length
|
|
397
|
-
} else {
|
|
398
|
-
const geometry = this.getLineGeometry(line)
|
|
399
|
-
if (!geometry) return false
|
|
400
|
-
drawCmds.push({ colorValue, mode: gl.TRIANGLES, firstVertex: totalFloats / 2, pointCount: geometry.vertexCount })
|
|
401
|
-
totalFloats += geometry.vertices.length
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (this.lineScratch.length < totalFloats) {
|
|
406
|
-
this.lineScratch = new Float32Array(nextBufferFloatCapacity(totalFloats))
|
|
407
|
-
}
|
|
408
|
-
let floatOffset = 0
|
|
409
|
-
for (const line of lines) {
|
|
410
|
-
const vertices = line.width === 1
|
|
411
|
-
? this.getThinLineVertices(line.points).vertices
|
|
412
|
-
: this.getLineGeometry(line)!.vertices
|
|
413
|
-
this.lineScratch.set(vertices, floatOffset)
|
|
414
|
-
floatOffset += vertices.length
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const physical = this.shared.getPhysicalRegion(region)
|
|
418
|
-
const msaaTargets = physical ? this.ensureLineMsaaTargets(gl, physical) : null
|
|
419
|
-
const useMsaa = msaaTargets !== null
|
|
420
|
-
|
|
421
|
-
if (useMsaa) {
|
|
422
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, msaaTargets.msaaFramebuffer)
|
|
423
|
-
gl.viewport(0, 0, msaaTargets.widthPx, msaaTargets.heightPx)
|
|
424
|
-
gl.disable(gl.SCISSOR_TEST)
|
|
425
|
-
gl.clearColor(0, 0, 0, 0)
|
|
426
|
-
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
427
|
-
} else if (!this.shared.bindRegion(region)) {
|
|
428
|
-
return false
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
gl.useProgram(handles.basic.program)
|
|
432
|
-
gl.bindVertexArray(handles.basic.vao)
|
|
433
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, handles.basic.vertexBuffer)
|
|
434
|
-
|
|
435
|
-
if (this.vertexCapacity < totalFloats) {
|
|
436
|
-
this.vertexCapacity = nextBufferFloatCapacity(totalFloats)
|
|
437
|
-
gl.bufferData(gl.ARRAY_BUFFER, this.vertexCapacity * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW)
|
|
438
|
-
}
|
|
439
|
-
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.lineScratch.subarray(0, totalFloats))
|
|
440
|
-
|
|
441
|
-
gl.uniform2f(handles.basic.resolutionLocation, this.logicalWidth, this.logicalHeight)
|
|
442
|
-
gl.uniform1f(handles.basic.scrollXLocation, scrollLeft)
|
|
443
|
-
|
|
444
|
-
for (const cmd of drawCmds) {
|
|
445
|
-
gl.uniform4f(handles.basic.colorLocation, cmd.colorValue[0], cmd.colorValue[1], cmd.colorValue[2], cmd.colorValue[3])
|
|
446
|
-
gl.drawArrays(cmd.mode, cmd.firstVertex, cmd.pointCount)
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
gl.bindVertexArray(null)
|
|
450
|
-
|
|
451
|
-
if (useMsaa && msaaTargets && physical) {
|
|
452
|
-
this.resolveLineMsaaToSharedRegion(gl, msaaTargets, physical)
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
456
|
-
return true
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
private getThinLineVertices(points: Array<{ x: number; y: number }>): { vertices: Float32Array; vertexCount: number } {
|
|
460
|
-
let widthMap = this.geoCache.get(points)
|
|
461
|
-
if (widthMap) {
|
|
462
|
-
const cached = widthMap.get(0)
|
|
463
|
-
if (cached) return cached
|
|
464
|
-
} else {
|
|
465
|
-
widthMap = new Map()
|
|
466
|
-
this.geoCache.set(points, widthMap)
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const vertexCount = points.length
|
|
470
|
-
const vertices = new Float32Array(vertexCount * 2)
|
|
471
|
-
let writeIndex = 0
|
|
472
|
-
for (const point of points) {
|
|
473
|
-
vertices[writeIndex++] = point.x
|
|
474
|
-
vertices[writeIndex++] = point.y
|
|
475
|
-
}
|
|
476
|
-
const result = { vertices, vertexCount }
|
|
477
|
-
widthMap.set(0, result)
|
|
478
|
-
return result
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
private getLineGeometry(line: LineStrip): { vertices: Float32Array; vertexCount: number } | null {
|
|
482
|
-
const halfWidth = line.width / 2
|
|
483
|
-
let widthMap = this.geoCache.get(line.points)
|
|
484
|
-
if (widthMap) {
|
|
485
|
-
const cached = widthMap.get(halfWidth)
|
|
486
|
-
if (cached) return cached
|
|
487
|
-
} else {
|
|
488
|
-
widthMap = new Map()
|
|
489
|
-
this.geoCache.set(line.points, widthMap)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const geometry = buildJoinedPolylineGeometry(line.points, halfWidth)
|
|
493
|
-
if (geometry) widthMap.set(halfWidth, geometry)
|
|
494
|
-
return geometry
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
drawFilledBand(band: FilledBand, color: string, scrollLeft: number): boolean {
|
|
498
|
-
const handles = this.handles
|
|
499
|
-
const pointCount = Math.min(band.upperPoints.length, band.lowerPoints.length)
|
|
500
|
-
if (!handles || pointCount < 2 || this.logicalWidth <= 0 || this.logicalHeight <= 0) {
|
|
501
|
-
return false
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const colorValue = parseColor(color)
|
|
505
|
-
if (!colorValue) return false
|
|
506
|
-
|
|
507
|
-
const vertexCount = pointCount * 2
|
|
508
|
-
const floatCount = vertexCount * 2
|
|
509
|
-
if (this.fillScratch.length < floatCount) {
|
|
510
|
-
this.fillScratch = new Float32Array(nextBufferFloatCapacity(floatCount))
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
let writeIndex = 0
|
|
514
|
-
for (let i = 0; i < pointCount; i++) {
|
|
515
|
-
const upper = band.upperPoints[i]!
|
|
516
|
-
const lower = band.lowerPoints[i]!
|
|
517
|
-
this.fillScratch[writeIndex++] = upper.x
|
|
518
|
-
this.fillScratch[writeIndex++] = upper.y
|
|
519
|
-
this.fillScratch[writeIndex++] = lower.x
|
|
520
|
-
this.fillScratch[writeIndex++] = lower.y
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const gl = this.shared.getGL()
|
|
524
|
-
if (!gl || !this.region || !this.shared.bindRegion(this.region)) return false
|
|
525
|
-
|
|
526
|
-
gl.useProgram(handles.basic.program)
|
|
527
|
-
gl.bindVertexArray(handles.basic.vao)
|
|
528
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, handles.basic.vertexBuffer)
|
|
529
|
-
|
|
530
|
-
if (this.vertexCapacity < floatCount) {
|
|
531
|
-
this.vertexCapacity = nextBufferFloatCapacity(floatCount)
|
|
532
|
-
gl.bufferData(gl.ARRAY_BUFFER, this.vertexCapacity * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW)
|
|
533
|
-
}
|
|
534
|
-
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.fillScratch.subarray(0, floatCount))
|
|
535
|
-
|
|
536
|
-
gl.uniform2f(handles.basic.resolutionLocation, this.logicalWidth, this.logicalHeight)
|
|
537
|
-
gl.uniform1f(handles.basic.scrollXLocation, scrollLeft)
|
|
538
|
-
gl.uniform4f(handles.basic.colorLocation, colorValue[0], colorValue[1], colorValue[2], colorValue[3])
|
|
539
|
-
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount)
|
|
540
|
-
gl.bindVertexArray(null)
|
|
541
|
-
return true
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
destroy(): void {
|
|
545
|
-
const handles = this.handles
|
|
546
|
-
const gl = this.shared.getGL()
|
|
547
|
-
if (gl) {
|
|
548
|
-
this.destroyLineMsaaTargets(gl)
|
|
549
|
-
}
|
|
550
|
-
if (!handles) {
|
|
551
|
-
this.vertexCapacity = 0
|
|
552
|
-
return
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (gl) {
|
|
556
|
-
const { basic } = handles
|
|
557
|
-
gl.deleteBuffer(basic.vertexBuffer)
|
|
558
|
-
gl.deleteVertexArray(basic.vao)
|
|
559
|
-
gl.deleteProgram(basic.program)
|
|
560
|
-
}
|
|
561
|
-
this.handles = null
|
|
562
|
-
this.available = false
|
|
563
|
-
this.vertexCapacity = 0
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
private ensureLineMsaaTargets(gl: WebGL2RenderingContext, physical: PhysicalRegion): LineMsaaTargets | null {
|
|
567
|
-
const preferredSamples = 4
|
|
568
|
-
const maxSamples = Number(gl.getParameter(gl.MAX_SAMPLES)) || 0
|
|
569
|
-
const samples = Math.max(1, Math.min(preferredSamples, maxSamples))
|
|
570
|
-
if (samples <= 1) return null
|
|
571
|
-
|
|
572
|
-
const existing = this.msaaTargets
|
|
573
|
-
if (
|
|
574
|
-
existing
|
|
575
|
-
&& existing.widthPx === physical.widthPx
|
|
576
|
-
&& existing.heightPx === physical.heightPx
|
|
577
|
-
&& existing.samples === samples
|
|
578
|
-
) {
|
|
579
|
-
return existing
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
this.destroyLineMsaaTargets(gl)
|
|
583
|
-
|
|
584
|
-
const msaaFramebuffer = gl.createFramebuffer()
|
|
585
|
-
const msaaColorRenderbuffer = gl.createRenderbuffer()
|
|
586
|
-
const resolveFramebuffer = gl.createFramebuffer()
|
|
587
|
-
const resolveTexture = gl.createTexture()
|
|
588
|
-
if (!msaaFramebuffer || !msaaColorRenderbuffer || !resolveFramebuffer || !resolveTexture) {
|
|
589
|
-
if (msaaFramebuffer) gl.deleteFramebuffer(msaaFramebuffer)
|
|
590
|
-
if (msaaColorRenderbuffer) gl.deleteRenderbuffer(msaaColorRenderbuffer)
|
|
591
|
-
if (resolveFramebuffer) gl.deleteFramebuffer(resolveFramebuffer)
|
|
592
|
-
if (resolveTexture) gl.deleteTexture(resolveTexture)
|
|
593
|
-
return null
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, msaaFramebuffer)
|
|
597
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, msaaColorRenderbuffer)
|
|
598
|
-
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, physical.widthPx, physical.heightPx)
|
|
599
|
-
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, msaaColorRenderbuffer)
|
|
600
|
-
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
601
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
602
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, null)
|
|
603
|
-
gl.deleteFramebuffer(msaaFramebuffer)
|
|
604
|
-
gl.deleteRenderbuffer(msaaColorRenderbuffer)
|
|
605
|
-
gl.deleteFramebuffer(resolveFramebuffer)
|
|
606
|
-
gl.deleteTexture(resolveTexture)
|
|
607
|
-
return null
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer)
|
|
611
|
-
gl.bindTexture(gl.TEXTURE_2D, resolveTexture)
|
|
612
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
|
613
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
|
614
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
615
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
616
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, physical.widthPx, physical.heightPx, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)
|
|
617
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resolveTexture, 0)
|
|
618
|
-
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
619
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
620
|
-
gl.bindTexture(gl.TEXTURE_2D, null)
|
|
621
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, null)
|
|
622
|
-
gl.deleteFramebuffer(msaaFramebuffer)
|
|
623
|
-
gl.deleteRenderbuffer(msaaColorRenderbuffer)
|
|
624
|
-
gl.deleteFramebuffer(resolveFramebuffer)
|
|
625
|
-
gl.deleteTexture(resolveTexture)
|
|
626
|
-
return null
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
630
|
-
gl.bindTexture(gl.TEXTURE_2D, null)
|
|
631
|
-
gl.bindRenderbuffer(gl.RENDERBUFFER, null)
|
|
632
|
-
|
|
633
|
-
const targets = {
|
|
634
|
-
samples,
|
|
635
|
-
widthPx: physical.widthPx,
|
|
636
|
-
heightPx: physical.heightPx,
|
|
637
|
-
msaaFramebuffer,
|
|
638
|
-
msaaColorRenderbuffer,
|
|
639
|
-
resolveFramebuffer,
|
|
640
|
-
resolveTexture,
|
|
641
|
-
}
|
|
642
|
-
this.msaaTargets = targets
|
|
643
|
-
return targets
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
private destroyLineMsaaTargets(gl: WebGL2RenderingContext): void {
|
|
647
|
-
const targets = this.msaaTargets
|
|
648
|
-
if (!targets) return
|
|
649
|
-
gl.deleteFramebuffer(targets.msaaFramebuffer)
|
|
650
|
-
gl.deleteRenderbuffer(targets.msaaColorRenderbuffer)
|
|
651
|
-
gl.deleteFramebuffer(targets.resolveFramebuffer)
|
|
652
|
-
gl.deleteTexture(targets.resolveTexture)
|
|
653
|
-
this.msaaTargets = null
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
private resolveLineMsaaToSharedRegion(gl: WebGL2RenderingContext, targets: LineMsaaTargets, physical: PhysicalRegion): void {
|
|
657
|
-
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, targets.msaaFramebuffer)
|
|
658
|
-
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, targets.resolveFramebuffer)
|
|
659
|
-
gl.blitFramebuffer(
|
|
660
|
-
0, 0, targets.widthPx, targets.heightPx,
|
|
661
|
-
0, 0, targets.widthPx, targets.heightPx,
|
|
662
|
-
gl.COLOR_BUFFER_BIT,
|
|
663
|
-
gl.NEAREST,
|
|
664
|
-
)
|
|
665
|
-
|
|
666
|
-
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, targets.resolveFramebuffer)
|
|
667
|
-
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null)
|
|
668
|
-
const destY = this.shared.getCanvas().height - physical.sourceY - physical.heightPx
|
|
669
|
-
gl.disable(gl.SCISSOR_TEST)
|
|
670
|
-
gl.blitFramebuffer(
|
|
671
|
-
0, 0, targets.widthPx, targets.heightPx,
|
|
672
|
-
physical.sourceX, destY, physical.sourceX + physical.widthPx, destY + physical.heightPx,
|
|
673
|
-
gl.COLOR_BUFFER_BIT,
|
|
674
|
-
gl.NEAREST,
|
|
675
|
-
)
|
|
676
|
-
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null)
|
|
677
|
-
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null)
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
private initLineHandles(): LineWebGLHandles | null {
|
|
681
|
-
const gl = this.shared.getGL()
|
|
682
|
-
if (!gl) return null
|
|
683
|
-
|
|
684
|
-
const vertexShader = createShader(gl, gl.VERTEX_SHADER, LINE_VERTEX_SHADER_SOURCE)
|
|
685
|
-
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE)
|
|
686
|
-
if (!vertexShader || !fragmentShader) {
|
|
687
|
-
if (vertexShader) gl.deleteShader(vertexShader)
|
|
688
|
-
if (fragmentShader) gl.deleteShader(fragmentShader)
|
|
689
|
-
return null
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
const program = createProgram(gl, vertexShader, fragmentShader)
|
|
693
|
-
gl.deleteShader(vertexShader)
|
|
694
|
-
gl.deleteShader(fragmentShader)
|
|
695
|
-
if (!program) return null
|
|
696
|
-
|
|
697
|
-
const vao = gl.createVertexArray()
|
|
698
|
-
const vertexBuffer = gl.createBuffer()
|
|
699
|
-
if (!vao || !vertexBuffer) {
|
|
700
|
-
if (vao) gl.deleteVertexArray(vao)
|
|
701
|
-
if (vertexBuffer) gl.deleteBuffer(vertexBuffer)
|
|
702
|
-
gl.deleteProgram(program)
|
|
703
|
-
return null
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution')
|
|
707
|
-
const scrollXLocation = gl.getUniformLocation(program, 'u_scrollX')
|
|
708
|
-
const colorLocation = gl.getUniformLocation(program, 'u_color')
|
|
709
|
-
if (!resolutionLocation || !scrollXLocation || !colorLocation) {
|
|
710
|
-
gl.deleteBuffer(vertexBuffer)
|
|
711
|
-
gl.deleteVertexArray(vao)
|
|
712
|
-
gl.deleteProgram(program)
|
|
713
|
-
return null
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
const positionLocation = gl.getAttribLocation(program, 'a_position')
|
|
717
|
-
if (positionLocation < 0) {
|
|
718
|
-
gl.deleteBuffer(vertexBuffer)
|
|
719
|
-
gl.deleteVertexArray(vao)
|
|
720
|
-
gl.deleteProgram(program)
|
|
721
|
-
return null
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
gl.bindVertexArray(vao)
|
|
725
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
|
|
726
|
-
gl.enableVertexAttribArray(positionLocation)
|
|
727
|
-
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0)
|
|
728
|
-
gl.bindVertexArray(null)
|
|
729
|
-
|
|
730
|
-
gl.enable(gl.BLEND)
|
|
731
|
-
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
|
732
|
-
|
|
733
|
-
return {
|
|
734
|
-
basic: {
|
|
735
|
-
program,
|
|
736
|
-
vao,
|
|
737
|
-
vertexBuffer,
|
|
738
|
-
resolutionLocation,
|
|
739
|
-
scrollXLocation,
|
|
740
|
-
colorLocation,
|
|
741
|
-
},
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
function nextBufferFloatCapacity(required: number): number {
|
|
746
|
-
let capacity = 1
|
|
747
|
-
while (capacity < required) {
|
|
748
|
-
capacity <<= 1
|
|
749
|
-
}
|
|
750
|
-
return capacity
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
interface PolylineNormal {
|
|
754
|
-
nx: number
|
|
755
|
-
ny: number
|
|
756
|
-
valid: boolean
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
function buildJoinedPolylineGeometry(points: Array<{ x: number; y: number }>, halfWidth: number) {
|
|
760
|
-
if (points.length < 2) return null
|
|
761
|
-
|
|
762
|
-
// 使用固定结构数组,避免动态对象分配
|
|
763
|
-
const normals: PolylineNormal[] = new Array(points.length - 1)
|
|
764
|
-
let validSegmentCount = 0
|
|
765
|
-
|
|
766
|
-
// 第一遍:计算所有法线(用 sqrt 替代 hypot,缓存逆长度)
|
|
767
|
-
for (let i = 0; i < points.length - 1; i++) {
|
|
768
|
-
const start = points[i]!
|
|
769
|
-
const end = points[i + 1]!
|
|
770
|
-
const dx = end.x - start.x
|
|
771
|
-
const dy = end.y - start.y
|
|
772
|
-
const lenSq = dx * dx + dy * dy
|
|
773
|
-
if (lenSq <= 0) {
|
|
774
|
-
normals[i] = { nx: 0, ny: 0, valid: false }
|
|
775
|
-
continue
|
|
776
|
-
}
|
|
777
|
-
const invLen = 1 / Math.sqrt(lenSq)
|
|
778
|
-
normals[i] = { nx: -dy * invLen, ny: dx * invLen, valid: true }
|
|
779
|
-
validSegmentCount++
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
if (validSegmentCount === 0) return null
|
|
783
|
-
|
|
784
|
-
// 预分配顶点数组:每对有效相邻点生成12个float(6个顶点 * 2个坐标)
|
|
785
|
-
const maxVerticesFloats = (points.length - 1) * 12
|
|
786
|
-
const vertices = new Float32Array(maxVerticesFloats)
|
|
787
|
-
let vertexWriteIndex = 0
|
|
788
|
-
|
|
789
|
-
// 计算 miter 并直接写入顶点
|
|
790
|
-
for (let i = 0; i < points.length - 1; i++) {
|
|
791
|
-
const curr = points[i]!
|
|
792
|
-
const next = points[i + 1]!
|
|
793
|
-
|
|
794
|
-
const prevNormal = i > 0 ? normals[i - 1] : null
|
|
795
|
-
const currNormal = normals[i]!
|
|
796
|
-
|
|
797
|
-
if (!currNormal.valid) continue
|
|
798
|
-
if (!prevNormal && !currNormal.valid) continue
|
|
799
|
-
|
|
800
|
-
// 计算 curr 点的 miter 法线
|
|
801
|
-
let miterNX = 0
|
|
802
|
-
let miterNY = 0
|
|
803
|
-
if (prevNormal?.valid && currNormal.valid) {
|
|
804
|
-
miterNX = prevNormal.nx + currNormal.nx
|
|
805
|
-
miterNY = prevNormal.ny + currNormal.ny
|
|
806
|
-
const miterLenSq = miterNX * miterNX + miterNY * miterNY
|
|
807
|
-
if (miterLenSq > 1e-12) {
|
|
808
|
-
const invMiter = 1 / Math.sqrt(miterLenSq)
|
|
809
|
-
miterNX *= invMiter
|
|
810
|
-
miterNY *= invMiter
|
|
811
|
-
const dot = miterNX * currNormal.nx + miterNY * currNormal.ny
|
|
812
|
-
const scale = 1 / Math.max(0.2, Math.abs(dot))
|
|
813
|
-
miterNX *= scale
|
|
814
|
-
miterNY *= scale
|
|
815
|
-
} else {
|
|
816
|
-
miterNX = currNormal.nx
|
|
817
|
-
miterNY = currNormal.ny
|
|
818
|
-
}
|
|
819
|
-
} else if (currNormal.valid) {
|
|
820
|
-
miterNX = currNormal.nx
|
|
821
|
-
miterNY = currNormal.ny
|
|
822
|
-
} else if (prevNormal?.valid) {
|
|
823
|
-
miterNX = prevNormal.nx
|
|
824
|
-
miterNY = prevNormal.ny
|
|
825
|
-
} else {
|
|
826
|
-
continue
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// 计算 next 点的法线(用于下一对点)
|
|
830
|
-
let nextMiterNX = currNormal.nx
|
|
831
|
-
let nextMiterNY = currNormal.ny
|
|
832
|
-
const nextNormal = i < points.length - 2 ? normals[i + 1] : null
|
|
833
|
-
if (nextNormal?.valid && currNormal.valid) {
|
|
834
|
-
nextMiterNX = currNormal.nx + nextNormal.nx
|
|
835
|
-
nextMiterNY = currNormal.ny + nextNormal.ny
|
|
836
|
-
const miterLenSq = nextMiterNX * nextMiterNX + nextMiterNY * nextMiterNY
|
|
837
|
-
if (miterLenSq > 1e-12) {
|
|
838
|
-
const invMiter = 1 / Math.sqrt(miterLenSq)
|
|
839
|
-
nextMiterNX *= invMiter
|
|
840
|
-
nextMiterNY *= invMiter
|
|
841
|
-
const dot = nextMiterNX * nextNormal.nx + nextMiterNY * nextNormal.ny
|
|
842
|
-
const scale = 1 / Math.max(0.2, Math.abs(dot))
|
|
843
|
-
nextMiterNX *= scale
|
|
844
|
-
nextMiterNY *= scale
|
|
845
|
-
} else {
|
|
846
|
-
nextMiterNX = currNormal.nx
|
|
847
|
-
nextMiterNY = currNormal.ny
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// 计算四个角点
|
|
852
|
-
const leftAX = curr.x + miterNX * halfWidth
|
|
853
|
-
const leftAY = curr.y + miterNY * halfWidth
|
|
854
|
-
const rightAX = curr.x - miterNX * halfWidth
|
|
855
|
-
const rightAY = curr.y - miterNY * halfWidth
|
|
856
|
-
const leftBX = next.x + nextMiterNX * halfWidth
|
|
857
|
-
const leftBY = next.y + nextMiterNY * halfWidth
|
|
858
|
-
const rightBX = next.x - nextMiterNX * halfWidth
|
|
859
|
-
const rightBY = next.y - nextMiterNY * halfWidth
|
|
860
|
-
|
|
861
|
-
// 写入两个三角形(6个顶点)
|
|
862
|
-
vertices[vertexWriteIndex++] = leftAX
|
|
863
|
-
vertices[vertexWriteIndex++] = leftAY
|
|
864
|
-
vertices[vertexWriteIndex++] = rightAX
|
|
865
|
-
vertices[vertexWriteIndex++] = rightAY
|
|
866
|
-
vertices[vertexWriteIndex++] = leftBX
|
|
867
|
-
vertices[vertexWriteIndex++] = leftBY
|
|
868
|
-
vertices[vertexWriteIndex++] = leftBX
|
|
869
|
-
vertices[vertexWriteIndex++] = leftBY
|
|
870
|
-
vertices[vertexWriteIndex++] = rightAX
|
|
871
|
-
vertices[vertexWriteIndex++] = rightAY
|
|
872
|
-
vertices[vertexWriteIndex++] = rightBX
|
|
873
|
-
vertices[vertexWriteIndex++] = rightBY
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
if (vertexWriteIndex === 0) return null
|
|
877
|
-
|
|
878
|
-
return {
|
|
879
|
-
vertices: vertexWriteIndex === maxVerticesFloats ? vertices : vertices.subarray(0, vertexWriteIndex),
|
|
880
|
-
vertexCount: vertexWriteIndex / 2,
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
function createShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader | null {
|
|
885
|
-
const shader = gl.createShader(type)
|
|
886
|
-
if (!shader) return null
|
|
887
|
-
|
|
888
|
-
gl.shaderSource(shader, source)
|
|
889
|
-
gl.compileShader(shader)
|
|
890
|
-
|
|
891
|
-
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
892
|
-
return shader
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
gl.deleteShader(shader)
|
|
896
|
-
return null
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function createProgram(gl: WebGL2RenderingContext, vertexShader: WebGLShader, fragmentShader: WebGLShader): WebGLProgram | null {
|
|
900
|
-
const program = gl.createProgram()
|
|
901
|
-
if (!program) return null
|
|
902
|
-
|
|
903
|
-
gl.attachShader(program, vertexShader)
|
|
904
|
-
gl.attachShader(program, fragmentShader)
|
|
905
|
-
gl.linkProgram(program)
|
|
906
|
-
|
|
907
|
-
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
908
|
-
return program
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
gl.deleteProgram(program)
|
|
912
|
-
return null
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
const colorCache = new Map<string, FloatColor>()
|
|
916
|
-
|
|
917
|
-
function parseColor(color: string): FloatColor | null {
|
|
918
|
-
const cached = colorCache.get(color)
|
|
919
|
-
if (cached) return cached
|
|
920
|
-
|
|
921
|
-
const normalized = color.trim().toLowerCase()
|
|
922
|
-
let result: FloatColor | null = null
|
|
923
|
-
|
|
924
|
-
if (normalized.startsWith('rgba(')) {
|
|
925
|
-
const values = normalized.slice(5, -1).split(',').map((part) => Number(part.trim()))
|
|
926
|
-
if (values.length === 4 && values.every((value) => Number.isFinite(value))) {
|
|
927
|
-
result = [values[0]! / 255, values[1]! / 255, values[2]! / 255, values[3]!]
|
|
928
|
-
}
|
|
929
|
-
} else if (normalized.startsWith('rgb(')) {
|
|
930
|
-
const values = normalized.slice(4, -1).split(',').map((part) => Number(part.trim()))
|
|
931
|
-
if (values.length === 3 && values.every((value) => Number.isFinite(value))) {
|
|
932
|
-
result = [values[0]! / 255, values[1]! / 255, values[2]! / 255, 1]
|
|
933
|
-
}
|
|
934
|
-
} else if (normalized.startsWith('#')) {
|
|
935
|
-
const hex = normalized.slice(1)
|
|
936
|
-
if (hex.length === 6) {
|
|
937
|
-
result = [
|
|
938
|
-
Number.parseInt(hex.slice(0, 2), 16) / 255,
|
|
939
|
-
Number.parseInt(hex.slice(2, 4), 16) / 255,
|
|
940
|
-
Number.parseInt(hex.slice(4, 6), 16) / 255,
|
|
941
|
-
1,
|
|
942
|
-
]
|
|
943
|
-
} else if (hex.length === 3) {
|
|
944
|
-
result = [
|
|
945
|
-
Number.parseInt(hex[0]! + hex[0]!, 16) / 255,
|
|
946
|
-
Number.parseInt(hex[1]! + hex[1]!, 16) / 255,
|
|
947
|
-
Number.parseInt(hex[2]! + hex[2]!, 16) / 255,
|
|
948
|
-
1,
|
|
949
|
-
]
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
if (result) colorCache.set(color, result)
|
|
954
|
-
return result
|
|
955
|
-
}
|
|
1
|
+
import { SharedWebGLSurface, type PhysicalRegion, type WebGLCompositeOptions, type WebGLRegion } from './sharedWebGLSurface'
|
|
2
|
+
|
|
3
|
+
type Rect = {
|
|
4
|
+
x: number
|
|
5
|
+
y: number
|
|
6
|
+
width: number
|
|
7
|
+
height: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type LineStrip = {
|
|
11
|
+
points: Array<{ x: number; y: number }>
|
|
12
|
+
width: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type ColoredLineStrip = LineStrip & {
|
|
16
|
+
color: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type FilledBand = {
|
|
20
|
+
upperPoints: Array<{ x: number; y: number }>
|
|
21
|
+
lowerPoints: Array<{ x: number; y: number }>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type FloatColor = readonly [number, number, number, number]
|
|
25
|
+
|
|
26
|
+
type RectWebGLHandles = {
|
|
27
|
+
program: WebGLProgram
|
|
28
|
+
vao: WebGLVertexArrayObject
|
|
29
|
+
unitBuffer: WebGLBuffer
|
|
30
|
+
rectBuffer: WebGLBuffer
|
|
31
|
+
resolutionLocation: WebGLUniformLocation
|
|
32
|
+
scrollXLocation: WebGLUniformLocation
|
|
33
|
+
colorLocation: WebGLUniformLocation
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type BasicLineWebGLHandles = {
|
|
37
|
+
program: WebGLProgram
|
|
38
|
+
vao: WebGLVertexArrayObject
|
|
39
|
+
vertexBuffer: WebGLBuffer
|
|
40
|
+
resolutionLocation: WebGLUniformLocation
|
|
41
|
+
scrollXLocation: WebGLUniformLocation
|
|
42
|
+
colorLocation: WebGLUniformLocation
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type LineWebGLHandles = {
|
|
46
|
+
basic: BasicLineWebGLHandles
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type LineMsaaTargets = {
|
|
50
|
+
samples: number
|
|
51
|
+
widthPx: number
|
|
52
|
+
heightPx: number
|
|
53
|
+
msaaFramebuffer: WebGLFramebuffer
|
|
54
|
+
msaaColorRenderbuffer: WebGLRenderbuffer
|
|
55
|
+
resolveFramebuffer: WebGLFramebuffer
|
|
56
|
+
resolveTexture: WebGLTexture
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const RECT_VERTEX_SHADER_SOURCE = `#version 300 es
|
|
60
|
+
precision mediump float;
|
|
61
|
+
|
|
62
|
+
in vec2 a_unit;
|
|
63
|
+
in vec4 a_rect;
|
|
64
|
+
|
|
65
|
+
uniform vec2 u_resolution;
|
|
66
|
+
uniform float u_scrollX;
|
|
67
|
+
|
|
68
|
+
void main() {
|
|
69
|
+
vec2 position = vec2(
|
|
70
|
+
a_rect.x - u_scrollX + a_unit.x * a_rect.z,
|
|
71
|
+
a_rect.y + a_unit.y * a_rect.w
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
vec2 zeroToOne = position / u_resolution;
|
|
75
|
+
vec2 clip = vec2(
|
|
76
|
+
zeroToOne.x * 2.0 - 1.0,
|
|
77
|
+
1.0 - zeroToOne.y * 2.0
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
gl_Position = vec4(clip, 0.0, 1.0);
|
|
81
|
+
}`
|
|
82
|
+
|
|
83
|
+
const LINE_VERTEX_SHADER_SOURCE = `#version 300 es
|
|
84
|
+
precision mediump float;
|
|
85
|
+
|
|
86
|
+
in vec2 a_position;
|
|
87
|
+
|
|
88
|
+
uniform vec2 u_resolution;
|
|
89
|
+
uniform float u_scrollX;
|
|
90
|
+
|
|
91
|
+
void main() {
|
|
92
|
+
vec2 position = vec2(a_position.x - u_scrollX, a_position.y);
|
|
93
|
+
vec2 zeroToOne = position / u_resolution;
|
|
94
|
+
vec2 clip = vec2(
|
|
95
|
+
zeroToOne.x * 2.0 - 1.0,
|
|
96
|
+
1.0 - zeroToOne.y * 2.0
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
gl_Position = vec4(clip, 0.0, 1.0);
|
|
100
|
+
}`
|
|
101
|
+
|
|
102
|
+
const FRAGMENT_SHADER_SOURCE = `#version 300 es
|
|
103
|
+
precision mediump float;
|
|
104
|
+
|
|
105
|
+
uniform vec4 u_color;
|
|
106
|
+
out vec4 outColor;
|
|
107
|
+
|
|
108
|
+
void main() {
|
|
109
|
+
outColor = u_color;
|
|
110
|
+
}`
|
|
111
|
+
|
|
112
|
+
const UNIT_QUAD = new Float32Array([
|
|
113
|
+
0, 0,
|
|
114
|
+
1, 0,
|
|
115
|
+
0, 1,
|
|
116
|
+
0, 1,
|
|
117
|
+
1, 0,
|
|
118
|
+
1, 1,
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
export class CandleWebGLSurface {
|
|
122
|
+
private shared: SharedWebGLSurface
|
|
123
|
+
private handles: RectWebGLHandles | null = null
|
|
124
|
+
private logicalWidth = 0
|
|
125
|
+
private logicalHeight = 0
|
|
126
|
+
private available = false
|
|
127
|
+
private rectCapacity = 0
|
|
128
|
+
private rectScratch = new Float32Array(0)
|
|
129
|
+
private region: WebGLRegion | null = null
|
|
130
|
+
|
|
131
|
+
constructor(shared: SharedWebGLSurface) {
|
|
132
|
+
this.shared = shared
|
|
133
|
+
this.handles = this.initRectHandles()
|
|
134
|
+
this.available = this.handles !== null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
isAvailable(): boolean {
|
|
138
|
+
return this.available
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getCanvas(): HTMLCanvasElement {
|
|
142
|
+
return this.shared.getCanvas()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setRegion(region: WebGLRegion): void {
|
|
146
|
+
this.region = region
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
resize(width: number, height: number, _dpr: number): void {
|
|
150
|
+
this.logicalWidth = width
|
|
151
|
+
this.logicalHeight = height
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
clear(): void {
|
|
155
|
+
if (!this.region || this.logicalWidth <= 0 || this.logicalHeight <= 0) return
|
|
156
|
+
this.shared.clearRegion(this.region)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
compositeTo(ctx: CanvasRenderingContext2D, options: WebGLCompositeOptions = {}): void {
|
|
160
|
+
if (!this.region) return
|
|
161
|
+
this.shared.compositeRegionTo(ctx, this.region, options)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 直接传入已打包的 Float32Array:每 4 个元素为一组 (x, y, width, height) */
|
|
165
|
+
drawRectBuffer(rectData: Float32Array, rectCount: number, color: string, scrollLeft: number): boolean {
|
|
166
|
+
const handles = this.handles
|
|
167
|
+
if (!handles || rectCount === 0 || this.logicalWidth <= 0 || this.logicalHeight <= 0) {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const colorValue = parseColor(color)
|
|
172
|
+
if (!colorValue) return false
|
|
173
|
+
|
|
174
|
+
const floatCount = rectCount * 4
|
|
175
|
+
const gl = this.shared.getGL()
|
|
176
|
+
if (!gl || !this.region || !this.shared.bindRegion(this.region)) return false
|
|
177
|
+
|
|
178
|
+
gl.useProgram(handles.program)
|
|
179
|
+
gl.bindVertexArray(handles.vao)
|
|
180
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, handles.rectBuffer)
|
|
181
|
+
|
|
182
|
+
if (this.rectCapacity < floatCount) {
|
|
183
|
+
this.rectCapacity = nextBufferFloatCapacity(floatCount)
|
|
184
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.rectCapacity * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW)
|
|
185
|
+
}
|
|
186
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, rectData)
|
|
187
|
+
|
|
188
|
+
if (colorValue[3] === 1) {
|
|
189
|
+
gl.disable(gl.BLEND)
|
|
190
|
+
} else {
|
|
191
|
+
gl.enable(gl.BLEND)
|
|
192
|
+
}
|
|
193
|
+
gl.uniform2f(handles.resolutionLocation, this.logicalWidth, this.logicalHeight)
|
|
194
|
+
gl.uniform1f(handles.scrollXLocation, scrollLeft)
|
|
195
|
+
gl.uniform4f(handles.colorLocation, colorValue[0], colorValue[1], colorValue[2], colorValue[3])
|
|
196
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, rectCount)
|
|
197
|
+
gl.bindVertexArray(null)
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
drawRects(rects: Rect[], color: string, scrollLeft: number): boolean {
|
|
202
|
+
const handles = this.handles
|
|
203
|
+
if (!handles || !rects.length || this.logicalWidth <= 0 || this.logicalHeight <= 0) {
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const floatCount = rects.length * 4
|
|
208
|
+
if (this.rectScratch.length < floatCount) {
|
|
209
|
+
this.rectScratch = new Float32Array(nextBufferFloatCapacity(floatCount))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (let i = 0; i < rects.length; i++) {
|
|
213
|
+
const rect = rects[i]!
|
|
214
|
+
const offset = i * 4
|
|
215
|
+
this.rectScratch[offset] = rect.x
|
|
216
|
+
this.rectScratch[offset + 1] = rect.y
|
|
217
|
+
this.rectScratch[offset + 2] = rect.width
|
|
218
|
+
this.rectScratch[offset + 3] = rect.height
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return this.drawRectBuffer(this.rectScratch.subarray(0, floatCount), rects.length, color, scrollLeft)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
destroy(): void {
|
|
225
|
+
const handles = this.handles
|
|
226
|
+
if (!handles) return
|
|
227
|
+
|
|
228
|
+
const gl = this.shared.getGL()
|
|
229
|
+
if (gl) {
|
|
230
|
+
const { program, vao, unitBuffer, rectBuffer } = handles
|
|
231
|
+
gl.deleteBuffer(unitBuffer)
|
|
232
|
+
gl.deleteBuffer(rectBuffer)
|
|
233
|
+
gl.deleteVertexArray(vao)
|
|
234
|
+
gl.deleteProgram(program)
|
|
235
|
+
}
|
|
236
|
+
this.handles = null
|
|
237
|
+
this.available = false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private initRectHandles(): RectWebGLHandles | null {
|
|
241
|
+
const gl = this.shared.getGL()
|
|
242
|
+
if (!gl) return null
|
|
243
|
+
|
|
244
|
+
const vertexShader = createShader(gl, gl.VERTEX_SHADER, RECT_VERTEX_SHADER_SOURCE)
|
|
245
|
+
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE)
|
|
246
|
+
if (!vertexShader || !fragmentShader) {
|
|
247
|
+
if (vertexShader) gl.deleteShader(vertexShader)
|
|
248
|
+
if (fragmentShader) gl.deleteShader(fragmentShader)
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const program = createProgram(gl, vertexShader, fragmentShader)
|
|
253
|
+
gl.deleteShader(vertexShader)
|
|
254
|
+
gl.deleteShader(fragmentShader)
|
|
255
|
+
if (!program) return null
|
|
256
|
+
|
|
257
|
+
const vao = gl.createVertexArray()
|
|
258
|
+
const unitBuffer = gl.createBuffer()
|
|
259
|
+
const rectBuffer = gl.createBuffer()
|
|
260
|
+
if (!vao || !unitBuffer || !rectBuffer) {
|
|
261
|
+
if (vao) gl.deleteVertexArray(vao)
|
|
262
|
+
if (unitBuffer) gl.deleteBuffer(unitBuffer)
|
|
263
|
+
if (rectBuffer) gl.deleteBuffer(rectBuffer)
|
|
264
|
+
gl.deleteProgram(program)
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution')
|
|
269
|
+
const scrollXLocation = gl.getUniformLocation(program, 'u_scrollX')
|
|
270
|
+
const colorLocation = gl.getUniformLocation(program, 'u_color')
|
|
271
|
+
if (!resolutionLocation || !scrollXLocation || !colorLocation) {
|
|
272
|
+
gl.deleteBuffer(unitBuffer)
|
|
273
|
+
gl.deleteBuffer(rectBuffer)
|
|
274
|
+
gl.deleteVertexArray(vao)
|
|
275
|
+
gl.deleteProgram(program)
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const unitLocation = gl.getAttribLocation(program, 'a_unit')
|
|
280
|
+
const rectLocation = gl.getAttribLocation(program, 'a_rect')
|
|
281
|
+
if (unitLocation < 0 || rectLocation < 0) {
|
|
282
|
+
gl.deleteBuffer(unitBuffer)
|
|
283
|
+
gl.deleteBuffer(rectBuffer)
|
|
284
|
+
gl.deleteVertexArray(vao)
|
|
285
|
+
gl.deleteProgram(program)
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
gl.bindVertexArray(vao)
|
|
290
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, unitBuffer)
|
|
291
|
+
gl.bufferData(gl.ARRAY_BUFFER, UNIT_QUAD, gl.STATIC_DRAW)
|
|
292
|
+
gl.enableVertexAttribArray(unitLocation)
|
|
293
|
+
gl.vertexAttribPointer(unitLocation, 2, gl.FLOAT, false, 0, 0)
|
|
294
|
+
gl.vertexAttribDivisor(unitLocation, 0)
|
|
295
|
+
|
|
296
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, rectBuffer)
|
|
297
|
+
gl.enableVertexAttribArray(rectLocation)
|
|
298
|
+
gl.vertexAttribPointer(rectLocation, 4, gl.FLOAT, false, 16, 0)
|
|
299
|
+
gl.vertexAttribDivisor(rectLocation, 1)
|
|
300
|
+
gl.bindVertexArray(null)
|
|
301
|
+
|
|
302
|
+
gl.enable(gl.BLEND)
|
|
303
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
program,
|
|
307
|
+
vao,
|
|
308
|
+
unitBuffer,
|
|
309
|
+
rectBuffer,
|
|
310
|
+
resolutionLocation,
|
|
311
|
+
scrollXLocation,
|
|
312
|
+
colorLocation,
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export class LineWebGLSurface {
|
|
318
|
+
private shared: SharedWebGLSurface
|
|
319
|
+
private handles: LineWebGLHandles | null = null
|
|
320
|
+
private logicalWidth = 0
|
|
321
|
+
private logicalHeight = 0
|
|
322
|
+
private dpr = 1
|
|
323
|
+
private available = false
|
|
324
|
+
private vertexCapacity = 0
|
|
325
|
+
private fillScratch = new Float32Array(0)
|
|
326
|
+
private lineScratch = new Float32Array(0)
|
|
327
|
+
private region: WebGLRegion | null = null
|
|
328
|
+
private msaaTargets: LineMsaaTargets | null = null
|
|
329
|
+
|
|
330
|
+
// Geometry cache: 以 points 数组引用 + halfWidth 为 key,避免每帧重算法线/miter
|
|
331
|
+
private geoCache = new WeakMap<Array<{ x: number; y: number }>, Map<number, { vertices: Float32Array; vertexCount: number }>>()
|
|
332
|
+
|
|
333
|
+
constructor(shared: SharedWebGLSurface) {
|
|
334
|
+
this.shared = shared
|
|
335
|
+
this.handles = this.initLineHandles()
|
|
336
|
+
this.available = this.handles !== null
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
isAvailable(): boolean {
|
|
340
|
+
return this.available
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
getCanvas(): HTMLCanvasElement {
|
|
344
|
+
return this.shared.getCanvas()
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
setRegion(region: WebGLRegion): void {
|
|
348
|
+
this.region = region
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
resize(width: number, height: number, dpr: number): void {
|
|
352
|
+
this.logicalWidth = width
|
|
353
|
+
this.logicalHeight = height
|
|
354
|
+
this.dpr = dpr
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
clear(): void {
|
|
358
|
+
if (!this.region || this.logicalWidth <= 0 || this.logicalHeight <= 0) return
|
|
359
|
+
this.shared.clearRegion(this.region)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
compositeTo(ctx: CanvasRenderingContext2D, options: WebGLCompositeOptions = {}): void {
|
|
363
|
+
if (!this.region) return
|
|
364
|
+
this.shared.compositeRegionTo(ctx, this.region, options)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
drawLineStrips(lines: ColoredLineStrip[], scrollLeft: number): boolean {
|
|
368
|
+
const handles = this.handles
|
|
369
|
+
if (!handles || lines.length === 0 || this.logicalWidth <= 0 || this.logicalHeight <= 0) {
|
|
370
|
+
return false
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
type DrawCmd = {
|
|
374
|
+
colorValue: FloatColor
|
|
375
|
+
mode: number
|
|
376
|
+
firstVertex: number
|
|
377
|
+
pointCount: number
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const gl = this.shared.getGL()
|
|
381
|
+
const region = this.region
|
|
382
|
+
if (!gl || !region) return false
|
|
383
|
+
|
|
384
|
+
const drawCmds: DrawCmd[] = []
|
|
385
|
+
let totalFloats = 0
|
|
386
|
+
|
|
387
|
+
for (const line of lines) {
|
|
388
|
+
if (line.points.length < 2) return false
|
|
389
|
+
|
|
390
|
+
const colorValue = parseColor(line.color)
|
|
391
|
+
if (!colorValue) return false
|
|
392
|
+
|
|
393
|
+
if (line.width === 1) {
|
|
394
|
+
const { vertexCount, vertices } = this.getThinLineVertices(line.points)
|
|
395
|
+
drawCmds.push({ colorValue, mode: gl.LINE_STRIP, firstVertex: totalFloats / 2, pointCount: vertexCount })
|
|
396
|
+
totalFloats += vertices.length
|
|
397
|
+
} else {
|
|
398
|
+
const geometry = this.getLineGeometry(line)
|
|
399
|
+
if (!geometry) return false
|
|
400
|
+
drawCmds.push({ colorValue, mode: gl.TRIANGLES, firstVertex: totalFloats / 2, pointCount: geometry.vertexCount })
|
|
401
|
+
totalFloats += geometry.vertices.length
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (this.lineScratch.length < totalFloats) {
|
|
406
|
+
this.lineScratch = new Float32Array(nextBufferFloatCapacity(totalFloats))
|
|
407
|
+
}
|
|
408
|
+
let floatOffset = 0
|
|
409
|
+
for (const line of lines) {
|
|
410
|
+
const vertices = line.width === 1
|
|
411
|
+
? this.getThinLineVertices(line.points).vertices
|
|
412
|
+
: this.getLineGeometry(line)!.vertices
|
|
413
|
+
this.lineScratch.set(vertices, floatOffset)
|
|
414
|
+
floatOffset += vertices.length
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const physical = this.shared.getPhysicalRegion(region)
|
|
418
|
+
const msaaTargets = physical ? this.ensureLineMsaaTargets(gl, physical) : null
|
|
419
|
+
const useMsaa = msaaTargets !== null
|
|
420
|
+
|
|
421
|
+
if (useMsaa) {
|
|
422
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, msaaTargets.msaaFramebuffer)
|
|
423
|
+
gl.viewport(0, 0, msaaTargets.widthPx, msaaTargets.heightPx)
|
|
424
|
+
gl.disable(gl.SCISSOR_TEST)
|
|
425
|
+
gl.clearColor(0, 0, 0, 0)
|
|
426
|
+
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
427
|
+
} else if (!this.shared.bindRegion(region)) {
|
|
428
|
+
return false
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
gl.useProgram(handles.basic.program)
|
|
432
|
+
gl.bindVertexArray(handles.basic.vao)
|
|
433
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, handles.basic.vertexBuffer)
|
|
434
|
+
|
|
435
|
+
if (this.vertexCapacity < totalFloats) {
|
|
436
|
+
this.vertexCapacity = nextBufferFloatCapacity(totalFloats)
|
|
437
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.vertexCapacity * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW)
|
|
438
|
+
}
|
|
439
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.lineScratch.subarray(0, totalFloats))
|
|
440
|
+
|
|
441
|
+
gl.uniform2f(handles.basic.resolutionLocation, this.logicalWidth, this.logicalHeight)
|
|
442
|
+
gl.uniform1f(handles.basic.scrollXLocation, scrollLeft)
|
|
443
|
+
|
|
444
|
+
for (const cmd of drawCmds) {
|
|
445
|
+
gl.uniform4f(handles.basic.colorLocation, cmd.colorValue[0], cmd.colorValue[1], cmd.colorValue[2], cmd.colorValue[3])
|
|
446
|
+
gl.drawArrays(cmd.mode, cmd.firstVertex, cmd.pointCount)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
gl.bindVertexArray(null)
|
|
450
|
+
|
|
451
|
+
if (useMsaa && msaaTargets && physical) {
|
|
452
|
+
this.resolveLineMsaaToSharedRegion(gl, msaaTargets, physical)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
456
|
+
return true
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private getThinLineVertices(points: Array<{ x: number; y: number }>): { vertices: Float32Array; vertexCount: number } {
|
|
460
|
+
let widthMap = this.geoCache.get(points)
|
|
461
|
+
if (widthMap) {
|
|
462
|
+
const cached = widthMap.get(0)
|
|
463
|
+
if (cached) return cached
|
|
464
|
+
} else {
|
|
465
|
+
widthMap = new Map()
|
|
466
|
+
this.geoCache.set(points, widthMap)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const vertexCount = points.length
|
|
470
|
+
const vertices = new Float32Array(vertexCount * 2)
|
|
471
|
+
let writeIndex = 0
|
|
472
|
+
for (const point of points) {
|
|
473
|
+
vertices[writeIndex++] = point.x
|
|
474
|
+
vertices[writeIndex++] = point.y
|
|
475
|
+
}
|
|
476
|
+
const result = { vertices, vertexCount }
|
|
477
|
+
widthMap.set(0, result)
|
|
478
|
+
return result
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private getLineGeometry(line: LineStrip): { vertices: Float32Array; vertexCount: number } | null {
|
|
482
|
+
const halfWidth = line.width / 2
|
|
483
|
+
let widthMap = this.geoCache.get(line.points)
|
|
484
|
+
if (widthMap) {
|
|
485
|
+
const cached = widthMap.get(halfWidth)
|
|
486
|
+
if (cached) return cached
|
|
487
|
+
} else {
|
|
488
|
+
widthMap = new Map()
|
|
489
|
+
this.geoCache.set(line.points, widthMap)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const geometry = buildJoinedPolylineGeometry(line.points, halfWidth)
|
|
493
|
+
if (geometry) widthMap.set(halfWidth, geometry)
|
|
494
|
+
return geometry
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
drawFilledBand(band: FilledBand, color: string, scrollLeft: number): boolean {
|
|
498
|
+
const handles = this.handles
|
|
499
|
+
const pointCount = Math.min(band.upperPoints.length, band.lowerPoints.length)
|
|
500
|
+
if (!handles || pointCount < 2 || this.logicalWidth <= 0 || this.logicalHeight <= 0) {
|
|
501
|
+
return false
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const colorValue = parseColor(color)
|
|
505
|
+
if (!colorValue) return false
|
|
506
|
+
|
|
507
|
+
const vertexCount = pointCount * 2
|
|
508
|
+
const floatCount = vertexCount * 2
|
|
509
|
+
if (this.fillScratch.length < floatCount) {
|
|
510
|
+
this.fillScratch = new Float32Array(nextBufferFloatCapacity(floatCount))
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
let writeIndex = 0
|
|
514
|
+
for (let i = 0; i < pointCount; i++) {
|
|
515
|
+
const upper = band.upperPoints[i]!
|
|
516
|
+
const lower = band.lowerPoints[i]!
|
|
517
|
+
this.fillScratch[writeIndex++] = upper.x
|
|
518
|
+
this.fillScratch[writeIndex++] = upper.y
|
|
519
|
+
this.fillScratch[writeIndex++] = lower.x
|
|
520
|
+
this.fillScratch[writeIndex++] = lower.y
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const gl = this.shared.getGL()
|
|
524
|
+
if (!gl || !this.region || !this.shared.bindRegion(this.region)) return false
|
|
525
|
+
|
|
526
|
+
gl.useProgram(handles.basic.program)
|
|
527
|
+
gl.bindVertexArray(handles.basic.vao)
|
|
528
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, handles.basic.vertexBuffer)
|
|
529
|
+
|
|
530
|
+
if (this.vertexCapacity < floatCount) {
|
|
531
|
+
this.vertexCapacity = nextBufferFloatCapacity(floatCount)
|
|
532
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.vertexCapacity * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW)
|
|
533
|
+
}
|
|
534
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.fillScratch.subarray(0, floatCount))
|
|
535
|
+
|
|
536
|
+
gl.uniform2f(handles.basic.resolutionLocation, this.logicalWidth, this.logicalHeight)
|
|
537
|
+
gl.uniform1f(handles.basic.scrollXLocation, scrollLeft)
|
|
538
|
+
gl.uniform4f(handles.basic.colorLocation, colorValue[0], colorValue[1], colorValue[2], colorValue[3])
|
|
539
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount)
|
|
540
|
+
gl.bindVertexArray(null)
|
|
541
|
+
return true
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
destroy(): void {
|
|
545
|
+
const handles = this.handles
|
|
546
|
+
const gl = this.shared.getGL()
|
|
547
|
+
if (gl) {
|
|
548
|
+
this.destroyLineMsaaTargets(gl)
|
|
549
|
+
}
|
|
550
|
+
if (!handles) {
|
|
551
|
+
this.vertexCapacity = 0
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (gl) {
|
|
556
|
+
const { basic } = handles
|
|
557
|
+
gl.deleteBuffer(basic.vertexBuffer)
|
|
558
|
+
gl.deleteVertexArray(basic.vao)
|
|
559
|
+
gl.deleteProgram(basic.program)
|
|
560
|
+
}
|
|
561
|
+
this.handles = null
|
|
562
|
+
this.available = false
|
|
563
|
+
this.vertexCapacity = 0
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private ensureLineMsaaTargets(gl: WebGL2RenderingContext, physical: PhysicalRegion): LineMsaaTargets | null {
|
|
567
|
+
const preferredSamples = 4
|
|
568
|
+
const maxSamples = Number(gl.getParameter(gl.MAX_SAMPLES)) || 0
|
|
569
|
+
const samples = Math.max(1, Math.min(preferredSamples, maxSamples))
|
|
570
|
+
if (samples <= 1) return null
|
|
571
|
+
|
|
572
|
+
const existing = this.msaaTargets
|
|
573
|
+
if (
|
|
574
|
+
existing
|
|
575
|
+
&& existing.widthPx === physical.widthPx
|
|
576
|
+
&& existing.heightPx === physical.heightPx
|
|
577
|
+
&& existing.samples === samples
|
|
578
|
+
) {
|
|
579
|
+
return existing
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
this.destroyLineMsaaTargets(gl)
|
|
583
|
+
|
|
584
|
+
const msaaFramebuffer = gl.createFramebuffer()
|
|
585
|
+
const msaaColorRenderbuffer = gl.createRenderbuffer()
|
|
586
|
+
const resolveFramebuffer = gl.createFramebuffer()
|
|
587
|
+
const resolveTexture = gl.createTexture()
|
|
588
|
+
if (!msaaFramebuffer || !msaaColorRenderbuffer || !resolveFramebuffer || !resolveTexture) {
|
|
589
|
+
if (msaaFramebuffer) gl.deleteFramebuffer(msaaFramebuffer)
|
|
590
|
+
if (msaaColorRenderbuffer) gl.deleteRenderbuffer(msaaColorRenderbuffer)
|
|
591
|
+
if (resolveFramebuffer) gl.deleteFramebuffer(resolveFramebuffer)
|
|
592
|
+
if (resolveTexture) gl.deleteTexture(resolveTexture)
|
|
593
|
+
return null
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, msaaFramebuffer)
|
|
597
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, msaaColorRenderbuffer)
|
|
598
|
+
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, physical.widthPx, physical.heightPx)
|
|
599
|
+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, msaaColorRenderbuffer)
|
|
600
|
+
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
601
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
602
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null)
|
|
603
|
+
gl.deleteFramebuffer(msaaFramebuffer)
|
|
604
|
+
gl.deleteRenderbuffer(msaaColorRenderbuffer)
|
|
605
|
+
gl.deleteFramebuffer(resolveFramebuffer)
|
|
606
|
+
gl.deleteTexture(resolveTexture)
|
|
607
|
+
return null
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, resolveFramebuffer)
|
|
611
|
+
gl.bindTexture(gl.TEXTURE_2D, resolveTexture)
|
|
612
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
|
613
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
|
614
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
615
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
616
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, physical.widthPx, physical.heightPx, 0, gl.RGBA, gl.UNSIGNED_BYTE, null)
|
|
617
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resolveTexture, 0)
|
|
618
|
+
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
619
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
620
|
+
gl.bindTexture(gl.TEXTURE_2D, null)
|
|
621
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null)
|
|
622
|
+
gl.deleteFramebuffer(msaaFramebuffer)
|
|
623
|
+
gl.deleteRenderbuffer(msaaColorRenderbuffer)
|
|
624
|
+
gl.deleteFramebuffer(resolveFramebuffer)
|
|
625
|
+
gl.deleteTexture(resolveTexture)
|
|
626
|
+
return null
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
630
|
+
gl.bindTexture(gl.TEXTURE_2D, null)
|
|
631
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, null)
|
|
632
|
+
|
|
633
|
+
const targets = {
|
|
634
|
+
samples,
|
|
635
|
+
widthPx: physical.widthPx,
|
|
636
|
+
heightPx: physical.heightPx,
|
|
637
|
+
msaaFramebuffer,
|
|
638
|
+
msaaColorRenderbuffer,
|
|
639
|
+
resolveFramebuffer,
|
|
640
|
+
resolveTexture,
|
|
641
|
+
}
|
|
642
|
+
this.msaaTargets = targets
|
|
643
|
+
return targets
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private destroyLineMsaaTargets(gl: WebGL2RenderingContext): void {
|
|
647
|
+
const targets = this.msaaTargets
|
|
648
|
+
if (!targets) return
|
|
649
|
+
gl.deleteFramebuffer(targets.msaaFramebuffer)
|
|
650
|
+
gl.deleteRenderbuffer(targets.msaaColorRenderbuffer)
|
|
651
|
+
gl.deleteFramebuffer(targets.resolveFramebuffer)
|
|
652
|
+
gl.deleteTexture(targets.resolveTexture)
|
|
653
|
+
this.msaaTargets = null
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
private resolveLineMsaaToSharedRegion(gl: WebGL2RenderingContext, targets: LineMsaaTargets, physical: PhysicalRegion): void {
|
|
657
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, targets.msaaFramebuffer)
|
|
658
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, targets.resolveFramebuffer)
|
|
659
|
+
gl.blitFramebuffer(
|
|
660
|
+
0, 0, targets.widthPx, targets.heightPx,
|
|
661
|
+
0, 0, targets.widthPx, targets.heightPx,
|
|
662
|
+
gl.COLOR_BUFFER_BIT,
|
|
663
|
+
gl.NEAREST,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, targets.resolveFramebuffer)
|
|
667
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null)
|
|
668
|
+
const destY = this.shared.getCanvas().height - physical.sourceY - physical.heightPx
|
|
669
|
+
gl.disable(gl.SCISSOR_TEST)
|
|
670
|
+
gl.blitFramebuffer(
|
|
671
|
+
0, 0, targets.widthPx, targets.heightPx,
|
|
672
|
+
physical.sourceX, destY, physical.sourceX + physical.widthPx, destY + physical.heightPx,
|
|
673
|
+
gl.COLOR_BUFFER_BIT,
|
|
674
|
+
gl.NEAREST,
|
|
675
|
+
)
|
|
676
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null)
|
|
677
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private initLineHandles(): LineWebGLHandles | null {
|
|
681
|
+
const gl = this.shared.getGL()
|
|
682
|
+
if (!gl) return null
|
|
683
|
+
|
|
684
|
+
const vertexShader = createShader(gl, gl.VERTEX_SHADER, LINE_VERTEX_SHADER_SOURCE)
|
|
685
|
+
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE)
|
|
686
|
+
if (!vertexShader || !fragmentShader) {
|
|
687
|
+
if (vertexShader) gl.deleteShader(vertexShader)
|
|
688
|
+
if (fragmentShader) gl.deleteShader(fragmentShader)
|
|
689
|
+
return null
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const program = createProgram(gl, vertexShader, fragmentShader)
|
|
693
|
+
gl.deleteShader(vertexShader)
|
|
694
|
+
gl.deleteShader(fragmentShader)
|
|
695
|
+
if (!program) return null
|
|
696
|
+
|
|
697
|
+
const vao = gl.createVertexArray()
|
|
698
|
+
const vertexBuffer = gl.createBuffer()
|
|
699
|
+
if (!vao || !vertexBuffer) {
|
|
700
|
+
if (vao) gl.deleteVertexArray(vao)
|
|
701
|
+
if (vertexBuffer) gl.deleteBuffer(vertexBuffer)
|
|
702
|
+
gl.deleteProgram(program)
|
|
703
|
+
return null
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution')
|
|
707
|
+
const scrollXLocation = gl.getUniformLocation(program, 'u_scrollX')
|
|
708
|
+
const colorLocation = gl.getUniformLocation(program, 'u_color')
|
|
709
|
+
if (!resolutionLocation || !scrollXLocation || !colorLocation) {
|
|
710
|
+
gl.deleteBuffer(vertexBuffer)
|
|
711
|
+
gl.deleteVertexArray(vao)
|
|
712
|
+
gl.deleteProgram(program)
|
|
713
|
+
return null
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const positionLocation = gl.getAttribLocation(program, 'a_position')
|
|
717
|
+
if (positionLocation < 0) {
|
|
718
|
+
gl.deleteBuffer(vertexBuffer)
|
|
719
|
+
gl.deleteVertexArray(vao)
|
|
720
|
+
gl.deleteProgram(program)
|
|
721
|
+
return null
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
gl.bindVertexArray(vao)
|
|
725
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
|
|
726
|
+
gl.enableVertexAttribArray(positionLocation)
|
|
727
|
+
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0)
|
|
728
|
+
gl.bindVertexArray(null)
|
|
729
|
+
|
|
730
|
+
gl.enable(gl.BLEND)
|
|
731
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
|
732
|
+
|
|
733
|
+
return {
|
|
734
|
+
basic: {
|
|
735
|
+
program,
|
|
736
|
+
vao,
|
|
737
|
+
vertexBuffer,
|
|
738
|
+
resolutionLocation,
|
|
739
|
+
scrollXLocation,
|
|
740
|
+
colorLocation,
|
|
741
|
+
},
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function nextBufferFloatCapacity(required: number): number {
|
|
746
|
+
let capacity = 1
|
|
747
|
+
while (capacity < required) {
|
|
748
|
+
capacity <<= 1
|
|
749
|
+
}
|
|
750
|
+
return capacity
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
interface PolylineNormal {
|
|
754
|
+
nx: number
|
|
755
|
+
ny: number
|
|
756
|
+
valid: boolean
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function buildJoinedPolylineGeometry(points: Array<{ x: number; y: number }>, halfWidth: number) {
|
|
760
|
+
if (points.length < 2) return null
|
|
761
|
+
|
|
762
|
+
// 使用固定结构数组,避免动态对象分配
|
|
763
|
+
const normals: PolylineNormal[] = new Array(points.length - 1)
|
|
764
|
+
let validSegmentCount = 0
|
|
765
|
+
|
|
766
|
+
// 第一遍:计算所有法线(用 sqrt 替代 hypot,缓存逆长度)
|
|
767
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
768
|
+
const start = points[i]!
|
|
769
|
+
const end = points[i + 1]!
|
|
770
|
+
const dx = end.x - start.x
|
|
771
|
+
const dy = end.y - start.y
|
|
772
|
+
const lenSq = dx * dx + dy * dy
|
|
773
|
+
if (lenSq <= 0) {
|
|
774
|
+
normals[i] = { nx: 0, ny: 0, valid: false }
|
|
775
|
+
continue
|
|
776
|
+
}
|
|
777
|
+
const invLen = 1 / Math.sqrt(lenSq)
|
|
778
|
+
normals[i] = { nx: -dy * invLen, ny: dx * invLen, valid: true }
|
|
779
|
+
validSegmentCount++
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (validSegmentCount === 0) return null
|
|
783
|
+
|
|
784
|
+
// 预分配顶点数组:每对有效相邻点生成12个float(6个顶点 * 2个坐标)
|
|
785
|
+
const maxVerticesFloats = (points.length - 1) * 12
|
|
786
|
+
const vertices = new Float32Array(maxVerticesFloats)
|
|
787
|
+
let vertexWriteIndex = 0
|
|
788
|
+
|
|
789
|
+
// 计算 miter 并直接写入顶点
|
|
790
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
791
|
+
const curr = points[i]!
|
|
792
|
+
const next = points[i + 1]!
|
|
793
|
+
|
|
794
|
+
const prevNormal = i > 0 ? normals[i - 1] : null
|
|
795
|
+
const currNormal = normals[i]!
|
|
796
|
+
|
|
797
|
+
if (!currNormal.valid) continue
|
|
798
|
+
if (!prevNormal && !currNormal.valid) continue
|
|
799
|
+
|
|
800
|
+
// 计算 curr 点的 miter 法线
|
|
801
|
+
let miterNX = 0
|
|
802
|
+
let miterNY = 0
|
|
803
|
+
if (prevNormal?.valid && currNormal.valid) {
|
|
804
|
+
miterNX = prevNormal.nx + currNormal.nx
|
|
805
|
+
miterNY = prevNormal.ny + currNormal.ny
|
|
806
|
+
const miterLenSq = miterNX * miterNX + miterNY * miterNY
|
|
807
|
+
if (miterLenSq > 1e-12) {
|
|
808
|
+
const invMiter = 1 / Math.sqrt(miterLenSq)
|
|
809
|
+
miterNX *= invMiter
|
|
810
|
+
miterNY *= invMiter
|
|
811
|
+
const dot = miterNX * currNormal.nx + miterNY * currNormal.ny
|
|
812
|
+
const scale = 1 / Math.max(0.2, Math.abs(dot))
|
|
813
|
+
miterNX *= scale
|
|
814
|
+
miterNY *= scale
|
|
815
|
+
} else {
|
|
816
|
+
miterNX = currNormal.nx
|
|
817
|
+
miterNY = currNormal.ny
|
|
818
|
+
}
|
|
819
|
+
} else if (currNormal.valid) {
|
|
820
|
+
miterNX = currNormal.nx
|
|
821
|
+
miterNY = currNormal.ny
|
|
822
|
+
} else if (prevNormal?.valid) {
|
|
823
|
+
miterNX = prevNormal.nx
|
|
824
|
+
miterNY = prevNormal.ny
|
|
825
|
+
} else {
|
|
826
|
+
continue
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// 计算 next 点的法线(用于下一对点)
|
|
830
|
+
let nextMiterNX = currNormal.nx
|
|
831
|
+
let nextMiterNY = currNormal.ny
|
|
832
|
+
const nextNormal = i < points.length - 2 ? normals[i + 1] : null
|
|
833
|
+
if (nextNormal?.valid && currNormal.valid) {
|
|
834
|
+
nextMiterNX = currNormal.nx + nextNormal.nx
|
|
835
|
+
nextMiterNY = currNormal.ny + nextNormal.ny
|
|
836
|
+
const miterLenSq = nextMiterNX * nextMiterNX + nextMiterNY * nextMiterNY
|
|
837
|
+
if (miterLenSq > 1e-12) {
|
|
838
|
+
const invMiter = 1 / Math.sqrt(miterLenSq)
|
|
839
|
+
nextMiterNX *= invMiter
|
|
840
|
+
nextMiterNY *= invMiter
|
|
841
|
+
const dot = nextMiterNX * nextNormal.nx + nextMiterNY * nextNormal.ny
|
|
842
|
+
const scale = 1 / Math.max(0.2, Math.abs(dot))
|
|
843
|
+
nextMiterNX *= scale
|
|
844
|
+
nextMiterNY *= scale
|
|
845
|
+
} else {
|
|
846
|
+
nextMiterNX = currNormal.nx
|
|
847
|
+
nextMiterNY = currNormal.ny
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// 计算四个角点
|
|
852
|
+
const leftAX = curr.x + miterNX * halfWidth
|
|
853
|
+
const leftAY = curr.y + miterNY * halfWidth
|
|
854
|
+
const rightAX = curr.x - miterNX * halfWidth
|
|
855
|
+
const rightAY = curr.y - miterNY * halfWidth
|
|
856
|
+
const leftBX = next.x + nextMiterNX * halfWidth
|
|
857
|
+
const leftBY = next.y + nextMiterNY * halfWidth
|
|
858
|
+
const rightBX = next.x - nextMiterNX * halfWidth
|
|
859
|
+
const rightBY = next.y - nextMiterNY * halfWidth
|
|
860
|
+
|
|
861
|
+
// 写入两个三角形(6个顶点)
|
|
862
|
+
vertices[vertexWriteIndex++] = leftAX
|
|
863
|
+
vertices[vertexWriteIndex++] = leftAY
|
|
864
|
+
vertices[vertexWriteIndex++] = rightAX
|
|
865
|
+
vertices[vertexWriteIndex++] = rightAY
|
|
866
|
+
vertices[vertexWriteIndex++] = leftBX
|
|
867
|
+
vertices[vertexWriteIndex++] = leftBY
|
|
868
|
+
vertices[vertexWriteIndex++] = leftBX
|
|
869
|
+
vertices[vertexWriteIndex++] = leftBY
|
|
870
|
+
vertices[vertexWriteIndex++] = rightAX
|
|
871
|
+
vertices[vertexWriteIndex++] = rightAY
|
|
872
|
+
vertices[vertexWriteIndex++] = rightBX
|
|
873
|
+
vertices[vertexWriteIndex++] = rightBY
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (vertexWriteIndex === 0) return null
|
|
877
|
+
|
|
878
|
+
return {
|
|
879
|
+
vertices: vertexWriteIndex === maxVerticesFloats ? vertices : vertices.subarray(0, vertexWriteIndex),
|
|
880
|
+
vertexCount: vertexWriteIndex / 2,
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function createShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader | null {
|
|
885
|
+
const shader = gl.createShader(type)
|
|
886
|
+
if (!shader) return null
|
|
887
|
+
|
|
888
|
+
gl.shaderSource(shader, source)
|
|
889
|
+
gl.compileShader(shader)
|
|
890
|
+
|
|
891
|
+
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
892
|
+
return shader
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
gl.deleteShader(shader)
|
|
896
|
+
return null
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function createProgram(gl: WebGL2RenderingContext, vertexShader: WebGLShader, fragmentShader: WebGLShader): WebGLProgram | null {
|
|
900
|
+
const program = gl.createProgram()
|
|
901
|
+
if (!program) return null
|
|
902
|
+
|
|
903
|
+
gl.attachShader(program, vertexShader)
|
|
904
|
+
gl.attachShader(program, fragmentShader)
|
|
905
|
+
gl.linkProgram(program)
|
|
906
|
+
|
|
907
|
+
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
908
|
+
return program
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
gl.deleteProgram(program)
|
|
912
|
+
return null
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const colorCache = new Map<string, FloatColor>()
|
|
916
|
+
|
|
917
|
+
function parseColor(color: string): FloatColor | null {
|
|
918
|
+
const cached = colorCache.get(color)
|
|
919
|
+
if (cached) return cached
|
|
920
|
+
|
|
921
|
+
const normalized = color.trim().toLowerCase()
|
|
922
|
+
let result: FloatColor | null = null
|
|
923
|
+
|
|
924
|
+
if (normalized.startsWith('rgba(')) {
|
|
925
|
+
const values = normalized.slice(5, -1).split(',').map((part) => Number(part.trim()))
|
|
926
|
+
if (values.length === 4 && values.every((value) => Number.isFinite(value))) {
|
|
927
|
+
result = [values[0]! / 255, values[1]! / 255, values[2]! / 255, values[3]!]
|
|
928
|
+
}
|
|
929
|
+
} else if (normalized.startsWith('rgb(')) {
|
|
930
|
+
const values = normalized.slice(4, -1).split(',').map((part) => Number(part.trim()))
|
|
931
|
+
if (values.length === 3 && values.every((value) => Number.isFinite(value))) {
|
|
932
|
+
result = [values[0]! / 255, values[1]! / 255, values[2]! / 255, 1]
|
|
933
|
+
}
|
|
934
|
+
} else if (normalized.startsWith('#')) {
|
|
935
|
+
const hex = normalized.slice(1)
|
|
936
|
+
if (hex.length === 6) {
|
|
937
|
+
result = [
|
|
938
|
+
Number.parseInt(hex.slice(0, 2), 16) / 255,
|
|
939
|
+
Number.parseInt(hex.slice(2, 4), 16) / 255,
|
|
940
|
+
Number.parseInt(hex.slice(4, 6), 16) / 255,
|
|
941
|
+
1,
|
|
942
|
+
]
|
|
943
|
+
} else if (hex.length === 3) {
|
|
944
|
+
result = [
|
|
945
|
+
Number.parseInt(hex[0]! + hex[0]!, 16) / 255,
|
|
946
|
+
Number.parseInt(hex[1]! + hex[1]!, 16) / 255,
|
|
947
|
+
Number.parseInt(hex[2]! + hex[2]!, 16) / 255,
|
|
948
|
+
1,
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (result) colorCache.set(color, result)
|
|
954
|
+
return result
|
|
955
|
+
}
|