@perspective-dev/viewer-charts 4.3.0
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/LICENSE.md +193 -0
- package/dist/cdn/perspective-viewer-charts.js +3 -0
- package/dist/cdn/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/axis/axis-primitives.d.ts +24 -0
- package/dist/esm/axis/bar-axis.d.ts +51 -0
- package/dist/esm/axis/canvas.d.ts +24 -0
- package/dist/esm/axis/categorical-axis-core.d.ts +42 -0
- package/dist/esm/axis/categorical-axis.d.ts +27 -0
- package/dist/esm/axis/facet-chrome.d.ts +13 -0
- package/dist/esm/axis/label-geometry.d.ts +41 -0
- package/dist/esm/axis/legend.d.ts +44 -0
- package/dist/esm/axis/numeric-axis.d.ts +20 -0
- package/dist/esm/charts/candlestick/candlestick-build.d.ts +129 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +10 -0
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +24 -0
- package/dist/esm/charts/candlestick/candlestick.d.ts +144 -0
- package/dist/esm/charts/candlestick/glyphs/draw-candlesticks.d.ts +36 -0
- package/dist/esm/charts/candlestick/glyphs/draw-ohlc.d.ts +33 -0
- package/dist/esm/charts/canvas-types.d.ts +15 -0
- package/dist/esm/charts/cartesian/cartesian-build.d.ts +14 -0
- package/dist/esm/charts/cartesian/cartesian-interact.d.ts +20 -0
- package/dist/esm/charts/cartesian/cartesian-render.d.ts +26 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +239 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +53 -0
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +142 -0
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +23 -0
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +24 -0
- package/dist/esm/charts/cartesian/label-interner.d.ts +21 -0
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +11 -0
- package/dist/esm/charts/chart-base.d.ts +402 -0
- package/dist/esm/charts/chart.d.ts +338 -0
- package/dist/esm/charts/common/band-layout.d.ts +32 -0
- package/dist/esm/charts/common/categorical-y-chart.d.ts +53 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +90 -0
- package/dist/esm/charts/common/chrome-cache.d.ts +18 -0
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +9 -0
- package/dist/esm/charts/common/leaf-color.d.ts +33 -0
- package/dist/esm/charts/common/node-store.d.ts +81 -0
- package/dist/esm/charts/common/tree-chart.d.ts +48 -0
- package/dist/esm/charts/common/tree-chrome.d.ts +31 -0
- package/dist/esm/charts/common/tree-data.d.ts +54 -0
- package/dist/esm/charts/common/visible-extent.d.ts +51 -0
- package/dist/esm/charts/heatmap/heatmap-build.d.ts +86 -0
- package/dist/esm/charts/heatmap/heatmap-interact.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-render.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-y-axis.d.ts +46 -0
- package/dist/esm/charts/heatmap/heatmap.d.ts +117 -0
- package/dist/esm/charts/map/map.d.ts +67 -0
- package/dist/esm/charts/registry.d.ts +14 -0
- package/dist/esm/charts/series/glyphs/draw-areas.d.ts +30 -0
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +15 -0
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +34 -0
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +33 -0
- package/dist/esm/charts/series/series-build.d.ts +228 -0
- package/dist/esm/charts/series/series-interact.d.ts +35 -0
- package/dist/esm/charts/series/series-render.d.ts +41 -0
- package/dist/esm/charts/series/series-type.d.ts +49 -0
- package/dist/esm/charts/series/series.d.ts +317 -0
- package/dist/esm/charts/sunburst/sunburst-interact.d.ts +7 -0
- package/dist/esm/charts/sunburst/sunburst-layout.d.ts +33 -0
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +22 -0
- package/dist/esm/charts/sunburst/sunburst.d.ts +85 -0
- package/dist/esm/charts/treemap/treemap-interact.d.ts +12 -0
- package/dist/esm/charts/treemap/treemap-layout.d.ts +28 -0
- package/dist/esm/charts/treemap/treemap-render.d.ts +18 -0
- package/dist/esm/charts/treemap/treemap.d.ts +74 -0
- package/dist/esm/config.d.ts +27 -0
- package/dist/esm/data/lazy-row.d.ts +32 -0
- package/dist/esm/data/split-groups.d.ts +20 -0
- package/dist/esm/data/view-reader.d.ts +35 -0
- package/dist/esm/event-detail.d.ts +28 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/interaction/hit-test.d.ts +30 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +19 -0
- package/dist/esm/interaction/host-sink-message.d.ts +46 -0
- package/dist/esm/interaction/lazy-tooltip.d.ts +61 -0
- package/dist/esm/interaction/raw-event-forwarder.d.ts +27 -0
- package/dist/esm/interaction/spatial-grid.d.ts +15 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +193 -0
- package/dist/esm/interaction/zoom-controller.d.ts +106 -0
- package/dist/esm/interaction/zoom-router.d.ts +48 -0
- package/dist/esm/layout/facet-grid.d.ts +126 -0
- package/dist/esm/layout/plot-layout.d.ts +104 -0
- package/dist/esm/layout/ticks.d.ts +17 -0
- package/dist/esm/map/mercator.d.ts +102 -0
- package/dist/esm/map/tile-cache.d.ts +38 -0
- package/dist/esm/map/tile-layer.d.ts +66 -0
- package/dist/esm/map/tile-loader.d.ts +52 -0
- package/dist/esm/map/tile-source.d.ts +66 -0
- package/dist/esm/perspective-viewer-charts.js +3 -0
- package/dist/esm/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/plugin/charts.d.ts +40 -0
- package/dist/esm/plugin/plugin.d.ts +95 -0
- package/dist/esm/render/scheduler.d.ts +41 -0
- package/dist/esm/theme/gradient.d.ts +48 -0
- package/dist/esm/theme/palette.d.ts +13 -0
- package/dist/esm/theme/theme-snapshot.d.ts +7 -0
- package/dist/esm/theme/theme.d.ts +53 -0
- package/dist/esm/transport/protocol.d.ts +430 -0
- package/dist/esm/transport/renderer-transport.d.ts +201 -0
- package/dist/esm/utils/css.d.ts +1 -0
- package/dist/esm/utils/font-snapshot.d.ts +50 -0
- package/dist/esm/webgl/buffer-pool.d.ts +62 -0
- package/dist/esm/webgl/context-manager.d.ts +184 -0
- package/dist/esm/webgl/gradient-texture.d.ts +17 -0
- package/dist/esm/webgl/instanced-attrs.d.ts +44 -0
- package/dist/esm/webgl/plot-frame.d.ts +39 -0
- package/dist/esm/webgl/program-cache.d.ts +13 -0
- package/dist/esm/webgl/shader-manifest.d.ts +53 -0
- package/dist/esm/webgl/shader-registry.d.ts +22 -0
- package/dist/esm/worker/boot.d.ts +0 -0
- package/dist/esm/worker/dispatch.d.ts +9 -0
- package/dist/esm/worker/font-loader.d.ts +2 -0
- package/dist/esm/worker/renderer.worker.d.ts +115 -0
- package/dist/esm/worker/session-host.d.ts +26 -0
- package/package.json +47 -0
- package/src/css/perspective-viewer-charts.css +95 -0
- package/src/ts/axis/axis-primitives.ts +125 -0
- package/src/ts/axis/bar-axis.ts +345 -0
- package/src/ts/axis/canvas.ts +64 -0
- package/src/ts/axis/categorical-axis-core.ts +125 -0
- package/src/ts/axis/categorical-axis.ts +716 -0
- package/src/ts/axis/facet-chrome.ts +42 -0
- package/src/ts/axis/label-geometry.ts +188 -0
- package/src/ts/axis/legend.ts +218 -0
- package/src/ts/axis/numeric-axis.ts +353 -0
- package/src/ts/charts/candlestick/candlestick-build.ts +516 -0
- package/src/ts/charts/candlestick/candlestick-interact.ts +256 -0
- package/src/ts/charts/candlestick/candlestick-render.ts +387 -0
- package/src/ts/charts/candlestick/candlestick.ts +367 -0
- package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +432 -0
- package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +317 -0
- package/src/ts/charts/canvas-types.ts +30 -0
- package/src/ts/charts/cartesian/cartesian-build.ts +616 -0
- package/src/ts/charts/cartesian/cartesian-interact.ts +355 -0
- package/src/ts/charts/cartesian/cartesian-render.ts +948 -0
- package/src/ts/charts/cartesian/cartesian.ts +469 -0
- package/src/ts/charts/cartesian/glyph.ts +81 -0
- package/src/ts/charts/cartesian/glyphs/density.ts +1263 -0
- package/src/ts/charts/cartesian/glyphs/lines.ts +320 -0
- package/src/ts/charts/cartesian/glyphs/points.ts +239 -0
- package/src/ts/charts/cartesian/label-interner.ts +56 -0
- package/src/ts/charts/cartesian/tooltip-lines.ts +80 -0
- package/src/ts/charts/chart-base.ts +840 -0
- package/src/ts/charts/chart.ts +427 -0
- package/src/ts/charts/common/band-layout.ts +63 -0
- package/src/ts/charts/common/categorical-y-chart.ts +81 -0
- package/src/ts/charts/common/category-axis-resolver.ts +314 -0
- package/src/ts/charts/common/chrome-cache.ts +79 -0
- package/src/ts/charts/common/draw-tooltip-box.ts +84 -0
- package/src/ts/charts/common/leaf-color.ts +92 -0
- package/src/ts/charts/common/node-store.ts +235 -0
- package/src/ts/charts/common/tree-chart.ts +76 -0
- package/src/ts/charts/common/tree-chrome.ts +123 -0
- package/src/ts/charts/common/tree-data.ts +623 -0
- package/src/ts/charts/common/visible-extent.ts +112 -0
- package/src/ts/charts/heatmap/heatmap-build.ts +426 -0
- package/src/ts/charts/heatmap/heatmap-interact.ts +274 -0
- package/src/ts/charts/heatmap/heatmap-render.ts +815 -0
- package/src/ts/charts/heatmap/heatmap-y-axis.ts +351 -0
- package/src/ts/charts/heatmap/heatmap.ts +368 -0
- package/src/ts/charts/map/map.ts +201 -0
- package/src/ts/charts/registry.ts +65 -0
- package/src/ts/charts/series/glyphs/draw-areas.ts +331 -0
- package/src/ts/charts/series/glyphs/draw-bars.ts +113 -0
- package/src/ts/charts/series/glyphs/draw-lines.ts +320 -0
- package/src/ts/charts/series/glyphs/draw-scatter.ts +328 -0
- package/src/ts/charts/series/series-build.ts +848 -0
- package/src/ts/charts/series/series-interact.ts +604 -0
- package/src/ts/charts/series/series-render.ts +1109 -0
- package/src/ts/charts/series/series-type.ts +99 -0
- package/src/ts/charts/series/series.ts +794 -0
- package/src/ts/charts/sunburst/sunburst-interact.ts +460 -0
- package/src/ts/charts/sunburst/sunburst-layout.ts +238 -0
- package/src/ts/charts/sunburst/sunburst-render.ts +887 -0
- package/src/ts/charts/sunburst/sunburst.ts +248 -0
- package/src/ts/charts/treemap/treemap-interact.ts +445 -0
- package/src/ts/charts/treemap/treemap-layout.ts +328 -0
- package/src/ts/charts/treemap/treemap-render.ts +886 -0
- package/src/ts/charts/treemap/treemap.ts +247 -0
- package/src/ts/config.ts +41 -0
- package/src/ts/data/lazy-row.ts +140 -0
- package/src/ts/data/split-groups.ts +97 -0
- package/src/ts/data/view-reader.ts +107 -0
- package/src/ts/event-detail.ts +44 -0
- package/src/ts/index.ts +53 -0
- package/src/ts/interaction/hit-test.ts +106 -0
- package/src/ts/interaction/host-sink-dom.ts +85 -0
- package/src/ts/interaction/host-sink-message.ts +75 -0
- package/src/ts/interaction/lazy-tooltip.ts +102 -0
- package/src/ts/interaction/raw-event-forwarder.ts +175 -0
- package/src/ts/interaction/spatial-grid.ts +100 -0
- package/src/ts/interaction/tooltip-controller.ts +407 -0
- package/src/ts/interaction/zoom-controller.ts +468 -0
- package/src/ts/interaction/zoom-router.ts +230 -0
- package/src/ts/layout/facet-grid.ts +346 -0
- package/src/ts/layout/plot-layout.ts +277 -0
- package/src/ts/layout/ticks.ts +168 -0
- package/src/ts/map/mercator.ts +204 -0
- package/src/ts/map/tile-cache.ts +96 -0
- package/src/ts/map/tile-layer.ts +382 -0
- package/src/ts/map/tile-loader.ts +143 -0
- package/src/ts/map/tile-source.ts +156 -0
- package/src/ts/plugin/charts.ts +286 -0
- package/src/ts/plugin/plugin.ts +668 -0
- package/src/ts/render/scheduler.ts +339 -0
- package/src/ts/shaders/area.frag.glsl +20 -0
- package/src/ts/shaders/area.vert.glsl +19 -0
- package/src/ts/shaders/bar.frag.glsl +25 -0
- package/src/ts/shaders/bar.vert.glsl +60 -0
- package/src/ts/shaders/candlestick-body.frag.glsl +19 -0
- package/src/ts/shaders/candlestick-body.vert.glsl +34 -0
- package/src/ts/shaders/density-extreme.frag.glsl +30 -0
- package/src/ts/shaders/density-mrt.frag.glsl +44 -0
- package/src/ts/shaders/density-mrt.vert.glsl +48 -0
- package/src/ts/shaders/density-resolve.frag.glsl +89 -0
- package/src/ts/shaders/density-resolve.vert.glsl +23 -0
- package/src/ts/shaders/density-splat.frag.glsl +34 -0
- package/src/ts/shaders/density-splat.vert.glsl +52 -0
- package/src/ts/shaders/gridline.frag.glsl +18 -0
- package/src/ts/shaders/gridline.vert.glsl +18 -0
- package/src/ts/shaders/heatmap.frag.glsl +23 -0
- package/src/ts/shaders/heatmap.vert.glsl +42 -0
- package/src/ts/shaders/line-uniform.frag.glsl +26 -0
- package/src/ts/shaders/line-uniform.vert.glsl +54 -0
- package/src/ts/shaders/line.frag.glsl +28 -0
- package/src/ts/shaders/line.vert.glsl +87 -0
- package/src/ts/shaders/scatter.frag.glsl +39 -0
- package/src/ts/shaders/scatter.vert.glsl +67 -0
- package/src/ts/shaders/sunburst-arc.frag.glsl +19 -0
- package/src/ts/shaders/sunburst-arc.vert.glsl +79 -0
- package/src/ts/shaders/tile.frag.glsl +27 -0
- package/src/ts/shaders/tile.vert.glsl +35 -0
- package/src/ts/shaders/treemap.frag.glsl +19 -0
- package/src/ts/shaders/treemap.vert.glsl +25 -0
- package/src/ts/shaders/y-scatter.frag.glsl +30 -0
- package/src/ts/shaders/y-scatter.vert.glsl +31 -0
- package/src/ts/theme/gradient.ts +312 -0
- package/src/ts/theme/palette.ts +64 -0
- package/src/ts/theme/theme-snapshot.ts +66 -0
- package/src/ts/theme/theme.ts +166 -0
- package/src/ts/transport/protocol.ts +497 -0
- package/src/ts/transport/renderer-transport.ts +788 -0
- package/src/ts/utils/css.ts +36 -0
- package/src/ts/utils/font-snapshot.ts +159 -0
- package/src/ts/webgl/buffer-pool.ts +163 -0
- package/src/ts/webgl/context-manager.ts +414 -0
- package/src/ts/webgl/gradient-texture.ts +84 -0
- package/src/ts/webgl/instanced-attrs.ts +139 -0
- package/src/ts/webgl/plot-frame.ts +91 -0
- package/src/ts/webgl/program-cache.ts +46 -0
- package/src/ts/webgl/shader-manifest.ts +148 -0
- package/src/ts/webgl/shader-registry.ts +97 -0
- package/src/ts/worker/boot.ts +22 -0
- package/src/ts/worker/dispatch.ts +99 -0
- package/src/ts/worker/font-loader.ts +89 -0
- package/src/ts/worker/renderer.worker.ts +734 -0
- package/src/ts/worker/session-host.ts +118 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { ShaderRegistry } from "./shader-registry";
|
|
14
|
+
import { SHADER_MANIFEST } from "./shader-manifest";
|
|
15
|
+
import { BufferPool } from "./buffer-pool";
|
|
16
|
+
|
|
17
|
+
export type WebGLCanvas = HTMLCanvasElement | OffscreenCanvas;
|
|
18
|
+
|
|
19
|
+
export interface WebGLContextManagerOptions {
|
|
20
|
+
/**
|
|
21
|
+
* If `true`, compile + link every shader in `SHADER_MANIFEST`
|
|
22
|
+
* during construction (and again after a context-loss/restore
|
|
23
|
+
* cycle). Trades ~30-100ms of init time for elimination of the
|
|
24
|
+
* compile/link cost on the first-frame path of every chart type
|
|
25
|
+
* that ends up rendered. Default `false` keeps the original
|
|
26
|
+
* lazy-compile behavior — programs are compiled on first
|
|
27
|
+
* `getOrCreate(name, ...)` call from a glyph's render path.
|
|
28
|
+
*/
|
|
29
|
+
precompile?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class WebGLContextManager {
|
|
33
|
+
private _canvas: WebGLCanvas;
|
|
34
|
+
private _gl: WebGL2RenderingContext | WebGLRenderingContext;
|
|
35
|
+
private _isWebGL2: boolean;
|
|
36
|
+
private _shaders: ShaderRegistry;
|
|
37
|
+
private _buffers: BufferPool;
|
|
38
|
+
private _uploadedCount = 0;
|
|
39
|
+
private _cssWidth = 0;
|
|
40
|
+
private _cssHeight = 0;
|
|
41
|
+
private _dpr = 1;
|
|
42
|
+
private _precompile: boolean;
|
|
43
|
+
private _frameCallback: ((bitmap: ImageBitmap) => void) | null = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Per-instance `MessageChannel` used by `_yieldToTask` to resume a
|
|
47
|
+
* polling `awaitGpuFence` loop on the next task. Allocated lazily —
|
|
48
|
+
* the polling path is rarely hit when the GPU is idle, and many
|
|
49
|
+
* `WebGLContextManager` instances never need one.
|
|
50
|
+
*
|
|
51
|
+
* Must be per-instance: a module-level singleton races when two
|
|
52
|
+
* managers poll concurrently. Both call sites assign
|
|
53
|
+
* `port1.onmessage = resolve`, the second assignment overwrites the
|
|
54
|
+
* first, and the first poll's promise never settles — leaving its
|
|
55
|
+
* `awaitGpuFence` hung. The hang propagates up through the render
|
|
56
|
+
* scheduler's `present` → `uploadAndRender` → the worker's
|
|
57
|
+
* `uploadChunkAck` → the host's `with_typed_arrays` callback,
|
|
58
|
+
* stalling `draw()` indefinitely. (Now that fence waits across
|
|
59
|
+
* different `WebGLContextManager`s run in parallel inside one
|
|
60
|
+
* scheduler drain, the per-instance discipline matters even
|
|
61
|
+
* more — concurrent poll loops are the common case, not the
|
|
62
|
+
* exception.)
|
|
63
|
+
*
|
|
64
|
+
* Cost of per-instance allocation: one extra `MessageChannel` (two
|
|
65
|
+
* `MessagePort`s, ~negligible bytes, zero idle CPU) per chart on
|
|
66
|
+
* top of the per-chart proxy channel that the transport already
|
|
67
|
+
* holds. Bounded by chart count; safe even for pathological pages
|
|
68
|
+
* with hundreds of charts. The alternative — allocating a fresh
|
|
69
|
+
* channel on every `_yieldToTask` call — would churn ports on every
|
|
70
|
+
* fence poll (potentially many per frame on slow GPUs), which is
|
|
71
|
+
* far worse for the structured-clone subsystem than holding one
|
|
72
|
+
* port pair for the manager's lifetime.
|
|
73
|
+
*/
|
|
74
|
+
private _yieldChannel: MessageChannel | null = null;
|
|
75
|
+
|
|
76
|
+
constructor(canvas: WebGLCanvas, options: WebGLContextManagerOptions = {}) {
|
|
77
|
+
this._canvas = canvas;
|
|
78
|
+
this._precompile = options.precompile ?? false;
|
|
79
|
+
const gl2 = canvas.getContext("webgl2", {
|
|
80
|
+
antialias: true,
|
|
81
|
+
alpha: true,
|
|
82
|
+
premultipliedAlpha: false,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (gl2) {
|
|
86
|
+
this._gl = gl2 as WebGL2RenderingContext;
|
|
87
|
+
this._isWebGL2 = true;
|
|
88
|
+
} else {
|
|
89
|
+
const gl1 = canvas.getContext("webgl", {
|
|
90
|
+
antialias: true,
|
|
91
|
+
alpha: true,
|
|
92
|
+
premultipliedAlpha: false,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (!gl1) {
|
|
96
|
+
throw new Error("WebGL is not supported");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._gl = gl1 as WebGLRenderingContext;
|
|
100
|
+
this._isWebGL2 = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this._shaders = new ShaderRegistry(this._gl);
|
|
104
|
+
this._buffers = new BufferPool(this._gl);
|
|
105
|
+
|
|
106
|
+
if (this._precompile) {
|
|
107
|
+
this._shaders.precompile(SHADER_MANIFEST);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Both `HTMLCanvasElement` and `OffscreenCanvas` dispatch
|
|
111
|
+
// `webglcontextlost` / `webglcontextrestored` events on the
|
|
112
|
+
// canvas itself. `addEventListener` exists on both.
|
|
113
|
+
(canvas as EventTarget).addEventListener(
|
|
114
|
+
"webglcontextlost",
|
|
115
|
+
(e: Event) => {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
(canvas as EventTarget).addEventListener("webglcontextrestored", () => {
|
|
121
|
+
this._shaders.releaseAll();
|
|
122
|
+
this._buffers.releaseAll();
|
|
123
|
+
this._shaders = new ShaderRegistry(this._gl);
|
|
124
|
+
this._buffers = new BufferPool(this._gl);
|
|
125
|
+
this._uploadedCount = 0;
|
|
126
|
+
|
|
127
|
+
// Re-prime the cache after restore so post-recovery
|
|
128
|
+
// first-frame doesn't re-pay the lazy compile cost.
|
|
129
|
+
if (this._precompile) {
|
|
130
|
+
this._shaders.precompile(SHADER_MANIFEST);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get gl(): WebGL2RenderingContext | WebGLRenderingContext {
|
|
136
|
+
return this._gl;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get isWebGL2(): boolean {
|
|
140
|
+
return this._isWebGL2;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
get shaders(): ShaderRegistry {
|
|
144
|
+
return this._shaders;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get bufferPool(): BufferPool {
|
|
148
|
+
return this._buffers;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get uploadedCount(): number {
|
|
152
|
+
return this._uploadedCount;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
set uploadedCount(count: number) {
|
|
156
|
+
this._uploadedCount = count;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resize the GL canvas's bitmap to match the host's CSS layout. The
|
|
161
|
+
* Host is responsible for measuring the DOM element (or otherwise
|
|
162
|
+
* deciding the target CSS size) and the device pixel ratio — the
|
|
163
|
+
* manager itself does not touch DOM, so the same code path works
|
|
164
|
+
* whether the canvas is an `HTMLCanvasElement` (in-process) or an
|
|
165
|
+
* `OffscreenCanvas` (in-process via transfer, or in a worker).
|
|
166
|
+
*/
|
|
167
|
+
resize(cssWidth: number, cssHeight: number, dpr: number): void {
|
|
168
|
+
this._cssWidth = cssWidth;
|
|
169
|
+
this._cssHeight = cssHeight;
|
|
170
|
+
this._dpr = dpr;
|
|
171
|
+
|
|
172
|
+
const width = Math.round(cssWidth * dpr);
|
|
173
|
+
const height = Math.round(cssHeight * dpr);
|
|
174
|
+
|
|
175
|
+
if (this._canvas.width !== width || this._canvas.height !== height) {
|
|
176
|
+
this._canvas.width = width;
|
|
177
|
+
this._canvas.height = height;
|
|
178
|
+
this._gl.viewport(0, 0, width, height);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Pending dimensions to apply at the start of the next render.
|
|
184
|
+
* `null` when no resize is queued. See {@link requestResize} /
|
|
185
|
+
* {@link applyPendingResize}.
|
|
186
|
+
*/
|
|
187
|
+
private _pendingResize: {
|
|
188
|
+
cssWidth: number;
|
|
189
|
+
cssHeight: number;
|
|
190
|
+
dpr: number;
|
|
191
|
+
} | null = null;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Record a dimension change to be applied at the start of the
|
|
195
|
+
* next render's Phase 1, *before* `_fullRender` runs. The actual
|
|
196
|
+
* `canvas.width = N` assignment (which clears the drawing buffer
|
|
197
|
+
* per the WebGL spec) happens inside `applyPendingResize()`,
|
|
198
|
+
* paired in the same synchronous task as the paint that fills
|
|
199
|
+
* the new buffer.
|
|
200
|
+
*
|
|
201
|
+
* Why split the dimension change off from the existing
|
|
202
|
+
* {@link resize} method: in direct / in-process modes the
|
|
203
|
+
* GL canvas IS the host's visible canvas, and `canvas.width = N`
|
|
204
|
+
* is immediately observable to the browser's compositor as a
|
|
205
|
+
* cleared buffer. If the resize lands in the message handler
|
|
206
|
+
* (one task) but the matching `_fullRender` lands in the next
|
|
207
|
+
* RAF (a later task), the compositor cycles between them and
|
|
208
|
+
* presents one full frame of empty canvas — visible flicker.
|
|
209
|
+
* Deferring the dimension change to the same RAF as the paint
|
|
210
|
+
* eliminates the inter-frame gap; both happen inside Phase 1's
|
|
211
|
+
* un-yielded loop.
|
|
212
|
+
*
|
|
213
|
+
* Multiple `requestResize` calls before the next render coalesce
|
|
214
|
+
* to last-write-wins — five rapid width changes from a window
|
|
215
|
+
* drag produce one resize+paint, not five.
|
|
216
|
+
*/
|
|
217
|
+
requestResize(cssWidth: number, cssHeight: number, dpr: number): void {
|
|
218
|
+
this._pendingResize = { cssWidth, cssHeight, dpr };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Apply any pending dimension change recorded by
|
|
223
|
+
* {@link requestResize}. Called by the scheduler's Phase 1
|
|
224
|
+
* (immediately before each entry's `fullRender`) and by the
|
|
225
|
+
* `snapshotPng` bypass path. Returns `true` when a resize was
|
|
226
|
+
* applied, `false` when there was nothing pending — useful for
|
|
227
|
+
* callers that want to skip a no-op render.
|
|
228
|
+
*/
|
|
229
|
+
applyPendingResize(): boolean {
|
|
230
|
+
if (!this._pendingResize) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const { cssWidth, cssHeight, dpr } = this._pendingResize;
|
|
235
|
+
this._pendingResize = null;
|
|
236
|
+
this.resize(cssWidth, cssHeight, dpr);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Last CSS width passed to `resize()`.
|
|
242
|
+
*/
|
|
243
|
+
get cssWidth(): number {
|
|
244
|
+
return this._cssWidth;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Last CSS height passed to `resize()`.
|
|
249
|
+
*/
|
|
250
|
+
get cssHeight(): number {
|
|
251
|
+
return this._cssHeight;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Last device pixel ratio passed to `resize()`.
|
|
256
|
+
*/
|
|
257
|
+
get dpr(): number {
|
|
258
|
+
return this._dpr;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
clear(): void {
|
|
262
|
+
this._gl.clearColor(0, 0, 0, 0);
|
|
263
|
+
this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
|
|
264
|
+
this._uploadedCount = 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Register a per-frame hook invoked at the end of each render. In
|
|
269
|
+
* blit-mode rendering, the worker installs a callback that
|
|
270
|
+
* transfers an `ImageBitmap` from `_canvas` (an `OffscreenCanvas`)
|
|
271
|
+
* back to the host so the visible display canvas can `drawImage`
|
|
272
|
+
* it. In direct mode the callback is left null and `endFrame` is a
|
|
273
|
+
* no-op.
|
|
274
|
+
*
|
|
275
|
+
* Pass `null` to detach.
|
|
276
|
+
*/
|
|
277
|
+
setFrameCallback(cb: ((bitmap: ImageBitmap) => void) | null): void {
|
|
278
|
+
this._frameCallback = cb;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Called by chart impls at the bottom of `_fullRender` (and any
|
|
283
|
+
* other path that produces a complete frame). When a frame
|
|
284
|
+
* callback is registered AND the GL surface is an
|
|
285
|
+
* `OffscreenCanvas`, ship its current contents as an
|
|
286
|
+
* `ImageBitmap` to the host. Otherwise no-op — direct-mode
|
|
287
|
+
* rendering has nothing to ship; the visible canvas already holds
|
|
288
|
+
* the drawing buffer.
|
|
289
|
+
*/
|
|
290
|
+
endFrame(): void {
|
|
291
|
+
if (!this._frameCallback) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const canvas = this._canvas as
|
|
296
|
+
| OffscreenCanvas
|
|
297
|
+
| (HTMLCanvasElement & { transferToImageBitmap?: never });
|
|
298
|
+
if (
|
|
299
|
+
typeof (canvas as OffscreenCanvas).transferToImageBitmap !==
|
|
300
|
+
"function"
|
|
301
|
+
) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const bitmap = (canvas as OffscreenCanvas).transferToImageBitmap();
|
|
306
|
+
this._frameCallback(bitmap);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Resolve when every GL command submitted up to this call has been
|
|
311
|
+
* executed by the GPU.
|
|
312
|
+
*
|
|
313
|
+
* On WebGL2 this issues a `fenceSync(SYNC_GPU_COMMANDS_COMPLETE)`
|
|
314
|
+
* and polls `clientWaitSync` with a zero timeout, yielding to the
|
|
315
|
+
* task queue between polls. The first poll passes
|
|
316
|
+
* `SYNC_FLUSH_COMMANDS_BIT` so the fence becomes reachable without
|
|
317
|
+
* a separate `gl.flush()`.
|
|
318
|
+
*
|
|
319
|
+
* On WebGL1 there is no fenceSync; we fall back to the blocking
|
|
320
|
+
* `gl.finish()`. This is acceptable in a worker — never call this
|
|
321
|
+
* from the main thread on a heavy frame.
|
|
322
|
+
*
|
|
323
|
+
* Used as a per-frame "GPU is idle" barrier so callers can serialize
|
|
324
|
+
* follow-on work (`endFrame` snapshot, present roundtrip, the next
|
|
325
|
+
* chunk upload) against actual GPU completion instead of the
|
|
326
|
+
* implicit, implementation-defined timing of `transferToImageBitmap`.
|
|
327
|
+
*/
|
|
328
|
+
async awaitGpuFence(): Promise<void> {
|
|
329
|
+
if (!this._isWebGL2) {
|
|
330
|
+
this._gl.finish();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const gl = this._gl as WebGL2RenderingContext;
|
|
335
|
+
const fence = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
336
|
+
if (!fence) {
|
|
337
|
+
gl.finish();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
let flags: GLbitfield = gl.SYNC_FLUSH_COMMANDS_BIT;
|
|
343
|
+
while (true) {
|
|
344
|
+
const status = gl.clientWaitSync(fence, flags, 0);
|
|
345
|
+
if (
|
|
346
|
+
status === gl.ALREADY_SIGNALED ||
|
|
347
|
+
status === gl.CONDITION_SATISFIED
|
|
348
|
+
) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (status === gl.WAIT_FAILED) {
|
|
353
|
+
gl.finish();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
flags = 0;
|
|
358
|
+
await this._yieldToTask();
|
|
359
|
+
}
|
|
360
|
+
} finally {
|
|
361
|
+
gl.deleteSync(fence);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
ensureBufferCapacity(totalRows: number): void {
|
|
366
|
+
this._buffers.ensureCapacity(totalRows);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Yield to the task queue between fence polls. We avoid
|
|
371
|
+
* `setTimeout(0)` because Chromium clamps nested `setTimeout` to
|
|
372
|
+
* ~4ms in workers, which would inflate the measured cost of
|
|
373
|
+
* `awaitGpuFence`. A reused per-instance `MessageChannel` lands the
|
|
374
|
+
* resume in the next task with sub-ms latency.
|
|
375
|
+
*
|
|
376
|
+
* `addEventListener(..., { once: true })` is used over
|
|
377
|
+
* `port1.onmessage = ...` so concurrent in-flight resumes (should
|
|
378
|
+
* any path ever introduce them) cannot clobber each other's
|
|
379
|
+
* resolvers — the previous module-level singleton lost a resolver
|
|
380
|
+
* on every overlap and hung one chart's `draw()` indefinitely.
|
|
381
|
+
*/
|
|
382
|
+
private _yieldToTask(): Promise<void> {
|
|
383
|
+
if (!this._yieldChannel) {
|
|
384
|
+
this._yieldChannel = new MessageChannel();
|
|
385
|
+
// `addEventListener` does not auto-start a `MessagePort` —
|
|
386
|
+
// only `onmessage = ...` does. Start once at allocation so
|
|
387
|
+
// posted resumes are actually delivered.
|
|
388
|
+
this._yieldChannel.port1.start();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return new Promise<void>((resolve) => {
|
|
392
|
+
const ch = this._yieldChannel!;
|
|
393
|
+
ch.port1.addEventListener("message", () => resolve(), {
|
|
394
|
+
once: true,
|
|
395
|
+
});
|
|
396
|
+
ch.port2.postMessage(null);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
destroy(): void {
|
|
401
|
+
this._buffers.releaseAll();
|
|
402
|
+
this._shaders.releaseAll();
|
|
403
|
+
if (this._yieldChannel) {
|
|
404
|
+
this._yieldChannel.port1.close();
|
|
405
|
+
this._yieldChannel.port2.close();
|
|
406
|
+
this._yieldChannel = null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const ext = this._gl.getExtension("WEBGL_lose_context");
|
|
410
|
+
if (ext) {
|
|
411
|
+
ext.loseContext();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import { buildGradientLUT, type GradientStop } from "../theme/gradient";
|
|
14
|
+
import type { WebGLContextManager } from "./context-manager";
|
|
15
|
+
|
|
16
|
+
const LUT_SIZE = 256;
|
|
17
|
+
|
|
18
|
+
export interface GradientTextureCache {
|
|
19
|
+
texture: WebGLTexture;
|
|
20
|
+
|
|
21
|
+
// The `GradientStop[]` reference last uploaded. `resolveTheme` returns a
|
|
22
|
+
// fresh object per render, so comparing the array reference is enough to
|
|
23
|
+
// detect a theme change and skip the upload otherwise.
|
|
24
|
+
lastStops: GradientStop[] | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Allocate a 256×1 RGBA8 texture (once) and re-upload the LUT only when
|
|
29
|
+
* `stops` has changed since the last call. Typical render path: zero
|
|
30
|
+
* GPU work beyond binding an already-uploaded texture.
|
|
31
|
+
*/
|
|
32
|
+
export function ensureGradientTexture(
|
|
33
|
+
glManager: WebGLContextManager,
|
|
34
|
+
cache: GradientTextureCache | null,
|
|
35
|
+
stops: GradientStop[],
|
|
36
|
+
): GradientTextureCache {
|
|
37
|
+
const gl = glManager.gl;
|
|
38
|
+
|
|
39
|
+
let texture: WebGLTexture;
|
|
40
|
+
if (cache?.texture) {
|
|
41
|
+
texture = cache.texture;
|
|
42
|
+
if (cache.lastStops === stops) {
|
|
43
|
+
return cache;
|
|
44
|
+
} // no-op fast path
|
|
45
|
+
} else {
|
|
46
|
+
texture = gl.createTexture()!;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const lut = buildGradientLUT(stops, LUT_SIZE);
|
|
50
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
51
|
+
gl.texImage2D(
|
|
52
|
+
gl.TEXTURE_2D,
|
|
53
|
+
0,
|
|
54
|
+
gl.RGBA,
|
|
55
|
+
LUT_SIZE,
|
|
56
|
+
1,
|
|
57
|
+
0,
|
|
58
|
+
gl.RGBA,
|
|
59
|
+
gl.UNSIGNED_BYTE,
|
|
60
|
+
lut,
|
|
61
|
+
);
|
|
62
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
63
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
64
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
65
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
66
|
+
|
|
67
|
+
return { texture, lastStops: stops };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Bind `texture` to a texture unit and set the sampler uniform. Call after
|
|
72
|
+
* `useProgram`.
|
|
73
|
+
*/
|
|
74
|
+
export function bindGradientTexture(
|
|
75
|
+
glManager: WebGLContextManager,
|
|
76
|
+
texture: WebGLTexture,
|
|
77
|
+
samplerLoc: WebGLUniformLocation | null,
|
|
78
|
+
unit: number = 0,
|
|
79
|
+
): void {
|
|
80
|
+
const gl = glManager.gl;
|
|
81
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
82
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
83
|
+
gl.uniform1i(samplerLoc, unit);
|
|
84
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import type { WebGLContextManager } from "./context-manager";
|
|
14
|
+
|
|
15
|
+
type GL = WebGL2RenderingContext | WebGLRenderingContext;
|
|
16
|
+
|
|
17
|
+
export interface Instancing {
|
|
18
|
+
setDivisor(location: number, divisor: number): void;
|
|
19
|
+
drawArraysInstanced(
|
|
20
|
+
mode: number,
|
|
21
|
+
first: number,
|
|
22
|
+
count: number,
|
|
23
|
+
instances: number,
|
|
24
|
+
): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Return an Instancing helper for the given GL context. On WebGL2, native
|
|
29
|
+
* `vertexAttribDivisor`/`drawArraysInstanced` are used; on WebGL1 the
|
|
30
|
+
* ANGLE_instanced_arrays extension is looked up and cached by caller.
|
|
31
|
+
* Negative attribute locations (optimized-out attributes) are tolerated by
|
|
32
|
+
* `setDivisor` and ignored.
|
|
33
|
+
*/
|
|
34
|
+
export function getInstancing(glManager: WebGLContextManager): Instancing {
|
|
35
|
+
const gl = glManager.gl;
|
|
36
|
+
if (glManager.isWebGL2) {
|
|
37
|
+
const gl2 = gl as WebGL2RenderingContext;
|
|
38
|
+
return {
|
|
39
|
+
setDivisor(location, divisor) {
|
|
40
|
+
if (location < 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
gl2.vertexAttribDivisor(location, divisor);
|
|
45
|
+
},
|
|
46
|
+
drawArraysInstanced(mode, first, count, instances) {
|
|
47
|
+
gl2.drawArraysInstanced(mode, first, count, instances);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ext = gl.getExtension(
|
|
53
|
+
"ANGLE_instanced_arrays",
|
|
54
|
+
) as ANGLE_instanced_arrays | null;
|
|
55
|
+
return {
|
|
56
|
+
setDivisor(location, divisor) {
|
|
57
|
+
if (location < 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
ext?.vertexAttribDivisorANGLE(location, divisor);
|
|
62
|
+
},
|
|
63
|
+
drawArraysInstanced(mode, first, count, instances) {
|
|
64
|
+
ext?.drawArraysInstancedANGLE(mode, first, count, instances);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Allocate the static `[0, 1, 2, 3]` line-strip corner buffer used by
|
|
71
|
+
* every instanced line/wick glyph: each corner index is multiplied by
|
|
72
|
+
* `LINE_WIDTH_PX` in the shader to expand a 2-vertex segment into a
|
|
73
|
+
* `TRIANGLE_STRIP` quad. Identical contents across all callers; one
|
|
74
|
+
* helper avoids the four-way `createBuffer` / `bufferData` boilerplate.
|
|
75
|
+
*/
|
|
76
|
+
export function createLineCornerBuffer(gl: GL): WebGLBuffer {
|
|
77
|
+
const buffer = gl.createBuffer()!;
|
|
78
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
79
|
+
gl.bufferData(
|
|
80
|
+
gl.ARRAY_BUFFER,
|
|
81
|
+
new Float32Array([0, 1, 2, 3]),
|
|
82
|
+
gl.STATIC_DRAW,
|
|
83
|
+
);
|
|
84
|
+
return buffer;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Allocate the static `[(0,0), (1,0), (0,1), (1,1)]` quad-strip corner
|
|
89
|
+
* buffer used by instanced rect glyphs (candlestick body). Used as a
|
|
90
|
+
* `vec2 a_corner` attribute that the shader scales by per-instance
|
|
91
|
+
* width/height to expand into a `TRIANGLE_STRIP` rect.
|
|
92
|
+
*/
|
|
93
|
+
export function createQuadCornerBuffer(gl: GL): WebGLBuffer {
|
|
94
|
+
const buffer = gl.createBuffer()!;
|
|
95
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
96
|
+
gl.bufferData(
|
|
97
|
+
gl.ARRAY_BUFFER,
|
|
98
|
+
new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
|
|
99
|
+
gl.STATIC_DRAW,
|
|
100
|
+
);
|
|
101
|
+
return buffer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Bind a per-instance float attribute from a named buffer in the buffer
|
|
106
|
+
* pool. Returns `true` when the bind happened, `false` when the buffer
|
|
107
|
+
* has not yet been allocated (caller should skip the draw rather than
|
|
108
|
+
* paint zero data). No-op return `true` when `attr` is negative
|
|
109
|
+
* (optimized-out attribute — drawing the rest is still valid).
|
|
110
|
+
*
|
|
111
|
+
* Render-path uses `peek`, never `getOrCreate`: the latter recreates
|
|
112
|
+
* with zero-initialized contents when `_totalCapacity` has grown past
|
|
113
|
+
* the current buffer, which would wipe the previous draw's vertex
|
|
114
|
+
* data and leave `drawArraysInstanced` to render zeros. See
|
|
115
|
+
* {@link BufferPool#peek} for the full rationale.
|
|
116
|
+
*/
|
|
117
|
+
export function bindInstancedFloatAttr(
|
|
118
|
+
glManager: WebGLContextManager,
|
|
119
|
+
instancing: Instancing,
|
|
120
|
+
attr: number,
|
|
121
|
+
name: string,
|
|
122
|
+
components: number,
|
|
123
|
+
): boolean {
|
|
124
|
+
if (attr < 0) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const buf = glManager.bufferPool.peek(name);
|
|
129
|
+
if (!buf) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const gl: GL = glManager.gl;
|
|
134
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buf.buffer);
|
|
135
|
+
gl.enableVertexAttribArray(attr);
|
|
136
|
+
gl.vertexAttribPointer(attr, components, gl.FLOAT, false, 0, 0);
|
|
137
|
+
instancing.setDivisor(attr, 1);
|
|
138
|
+
return true;
|
|
139
|
+
}
|