@perspective-dev/viewer-charts 4.5.0 → 4.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE.md +193 -0
  2. package/dist/cdn/perspective-viewer-charts.js +2 -2
  3. package/dist/cdn/perspective-viewer-charts.js.map +3 -3
  4. package/dist/esm/axis/bar-axis.d.ts +9 -1
  5. package/dist/esm/axis/categorical-axis.d.ts +0 -2
  6. package/dist/esm/charts/cartesian/cartesian.d.ts +26 -0
  7. package/dist/esm/charts/common/category-axis-resolver.d.ts +43 -1
  8. package/dist/esm/charts/common/expand-domain.d.ts +20 -0
  9. package/dist/esm/charts/common/tree-chart.d.ts +7 -0
  10. package/dist/esm/charts/common/tree-chrome.d.ts +23 -1
  11. package/dist/esm/charts/common/tree-interact.d.ts +46 -0
  12. package/dist/esm/charts/series/glyphs/draw-lines.d.ts +11 -4
  13. package/dist/esm/charts/series/series-build.d.ts +38 -2
  14. package/dist/esm/charts/series/series-render.d.ts +1 -4
  15. package/dist/esm/charts/series/series-type.d.ts +19 -17
  16. package/dist/esm/charts/series/series.d.ts +16 -0
  17. package/dist/esm/charts/sunburst/sunburst-interact.d.ts +1 -1
  18. package/dist/esm/charts/treemap/treemap-interact.d.ts +1 -6
  19. package/dist/esm/config.d.ts +19 -0
  20. package/dist/esm/interaction/host-sink-message.d.ts +10 -28
  21. package/dist/esm/interaction/raw-event-forwarder.d.ts +6 -7
  22. package/dist/esm/interaction/zoom-controller.d.ts +31 -20
  23. package/dist/esm/interaction/zoom-router.d.ts +3 -26
  24. package/dist/esm/perspective-viewer-charts.js +2 -2
  25. package/dist/esm/perspective-viewer-charts.js.map +3 -3
  26. package/dist/esm/plugin/plugin.d.ts +15 -3
  27. package/dist/esm/render/scheduler.d.ts +23 -0
  28. package/dist/esm/theme/palette.d.ts +0 -5
  29. package/dist/esm/transport/protocol.d.ts +2 -7
  30. package/dist/esm/webgl/context-manager.d.ts +100 -117
  31. package/dist/esm/webgl/context-pool.d.ts +40 -0
  32. package/dist/esm/webgl/gl-context.d.ts +54 -0
  33. package/dist/esm/worker/renderer.worker.d.ts +2 -4
  34. package/package.json +45 -45
  35. package/src/ts/axis/bar-axis.ts +74 -45
  36. package/src/ts/axis/categorical-axis.ts +0 -2
  37. package/src/ts/charts/candlestick/candlestick-render.ts +10 -7
  38. package/src/ts/charts/candlestick/candlestick.ts +10 -29
  39. package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +36 -2
  40. package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +36 -2
  41. package/src/ts/charts/cartesian/cartesian-build.ts +143 -9
  42. package/src/ts/charts/cartesian/cartesian-render.ts +205 -30
  43. package/src/ts/charts/cartesian/cartesian.ts +43 -4
  44. package/src/ts/charts/cartesian/glyphs/density.ts +36 -41
  45. package/src/ts/charts/cartesian/glyphs/lines.ts +13 -15
  46. package/src/ts/charts/cartesian/glyphs/points.ts +12 -17
  47. package/src/ts/charts/chart-base.ts +20 -6
  48. package/src/ts/charts/chart.ts +1 -1
  49. package/src/ts/charts/common/category-axis-resolver.ts +135 -1
  50. package/src/ts/charts/common/expand-domain.ts +40 -0
  51. package/src/ts/charts/common/tree-chart.ts +16 -0
  52. package/src/ts/charts/common/tree-chrome.ts +86 -1
  53. package/src/ts/charts/common/tree-interact.ts +209 -0
  54. package/src/ts/charts/heatmap/heatmap-render.ts +9 -11
  55. package/src/ts/charts/series/glyphs/draw-areas.ts +30 -1
  56. package/src/ts/charts/series/glyphs/draw-lines.ts +151 -76
  57. package/src/ts/charts/series/series-build.ts +394 -21
  58. package/src/ts/charts/series/series-render.ts +159 -38
  59. package/src/ts/charts/series/series-type.ts +37 -17
  60. package/src/ts/charts/series/series.ts +63 -68
  61. package/src/ts/charts/sunburst/sunburst-interact.ts +18 -162
  62. package/src/ts/charts/sunburst/sunburst-render.ts +24 -89
  63. package/src/ts/charts/sunburst/sunburst.ts +1 -15
  64. package/src/ts/charts/treemap/treemap-interact.ts +22 -189
  65. package/src/ts/charts/treemap/treemap-render.ts +19 -46
  66. package/src/ts/charts/treemap/treemap.ts +1 -16
  67. package/src/ts/config.ts +21 -1
  68. package/src/ts/interaction/host-sink-message.ts +33 -22
  69. package/src/ts/interaction/raw-event-forwarder.ts +10 -12
  70. package/src/ts/interaction/zoom-controller.ts +120 -83
  71. package/src/ts/interaction/zoom-router.ts +3 -126
  72. package/src/ts/map/tile-layer.ts +13 -13
  73. package/src/ts/plugin/plugin.ts +176 -196
  74. package/src/ts/render/scheduler.ts +125 -42
  75. package/src/ts/shaders/line-uniform.frag.glsl +2 -1
  76. package/src/ts/shaders/line-uniform.vert.glsl +19 -0
  77. package/src/ts/theme/palette.ts +1 -4
  78. package/src/ts/transport/protocol.ts +3 -8
  79. package/src/ts/webgl/context-manager.ts +195 -189
  80. package/src/ts/webgl/context-pool.ts +78 -0
  81. package/src/ts/webgl/gl-context.ts +191 -0
  82. package/src/ts/worker/dispatch.ts +0 -1
  83. package/src/ts/worker/renderer.worker.ts +72 -65
  84. package/src/ts/worker/session-host.ts +29 -0
@@ -11,7 +11,6 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
11
11
  private _rendererPromise;
12
12
  private _rawEventForwarder;
13
13
  private _generation;
14
- private _renderBlitMode;
15
14
  private _resetClickAbort;
16
15
  /**
17
16
  * Plugin-scoped global config. Seeded lazily from
@@ -64,7 +63,21 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
64
63
  */
65
64
  private _setupInteraction;
66
65
  private _buildRenderer;
67
- setBlitMode(mode: "direct" | "blit"): void;
66
+ /**
67
+ * Select the GL presentation strategy for *all* chart-type plugins
68
+ * in this renderer. Static + process-global: the value is shared by
69
+ * every per-chart-type subclass, so it must be set once before the
70
+ * charts that should use it build their renderers (a renderer reads
71
+ * {@link BLIT_MODE} at construction in `_buildRenderer`; charts
72
+ * already built keep their mode until torn down and rebuilt).
73
+ *
74
+ * - `"direct"` — each chart owns a GL context 1:1 with its visible
75
+ * canvas (lowest latency; bounded by the browser's ~16-context
76
+ * cap).
77
+ * - `"blit"` — charts render off-screen and share a pool of GL
78
+ * contexts ([webgl/context-pool.ts]), so a page can exceed the cap.
79
+ */
80
+ static setBlitMode(mode: "direct" | "blit"): void;
68
81
  get_static_config(): PluginStaticConfig;
69
82
  column_config_schema(column_type: string, _group: string | undefined, _column_name: string, current_value: Record<string, unknown> | null, _viewer_config?: {
70
83
  group_by?: string[];
@@ -88,7 +101,6 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
88
101
  clear(): Promise<void>;
89
102
  resize(): Promise<void>;
90
103
  restyle(): number;
91
- save(): any;
92
104
  render(view: View): Promise<Blob>;
93
105
  restore(config: any, columns_config?: Record<string, any>): void;
94
106
  delete(): void;
@@ -34,6 +34,29 @@ export declare function requestRender(glManager: WebGLContextManager, fullRender
34
34
  * picks it up for the next RAF.
35
35
  */
36
36
  export declare function deferIfDraining(glManager: WebGLContextManager, op: () => void): void;
37
+ /**
38
+ * Drop every scheduler reference to `glManager` — called from
39
+ * `WebGLContextManager`'s owning renderer at teardown, *before*
40
+ * `glManager.destroy()` loses the GL context.
41
+ *
42
+ * Without this, a frame queued for the next RAF (an `Entry` in
43
+ * `pending`) or a `deferIfDraining` op still parked in `deferred`
44
+ * would survive the destroy and, on the next `drain()`, drive
45
+ * `fullRender` / `present` against a context-lost manager. In blit
46
+ * mode that surfaces as `endFrame`'s `transferToImageBitmap` throwing
47
+ * "Cannot transfer to ImageBitmap because WebGL context is lost" —
48
+ * logged as "scheduler: present failed". In direct mode it's a wasted
49
+ * paint against a dead context.
50
+ *
51
+ * Outstanding waiters for a destroyed manager are resolved (not
52
+ * rejected): the caller asked to tear the chart down, so its awaited
53
+ * `draw()` should observe a clean no-op rather than an error it would
54
+ * have to suppress. An in-flight `present()` (manager in `inFlight`)
55
+ * is left to unwind on its own — its `finally` clause will not find a
56
+ * `deferred` entry to flush, and the manager is already gone from
57
+ * `pending`, so it cannot re-enqueue.
58
+ */
59
+ export declare function unregister(glManager: WebGLContextManager): void;
37
60
  /**
38
61
  * Test-only: clear pending state. Production callers must not use
39
62
  * this — outstanding waiters are silently dropped.
@@ -1,10 +1,5 @@
1
1
  import { type GradientStop } from "./gradient";
2
2
  export type Vec3 = [number, number, number];
3
- /**
4
- * Build a series palette of length `count` by sampling the theme gradient
5
- * at evenly-spaced offsets. For count == 1 returns the 50% stop.
6
- */
7
- export declare function interpolatePalette(stops: GradientStop[], count: number): Vec3[];
8
3
  /**
9
4
  * Resolve a series palette: use the discrete `--psp-charts--series-N--color`
10
5
  * palette when available, otherwise fall back to evenly-spaced samples of
@@ -1,6 +1,8 @@
1
1
  import type { FacetConfig, PluginConfig } from "../charts/chart";
2
2
  import type { PerspectiveClickDetail } from "../event-detail";
3
+ import type { ThemeSnapshot } from "../theme/theme";
3
4
  import type { ViewConfig } from "@perspective-dev/client";
5
+ export type { ThemeSnapshot };
4
6
  /**
5
7
  * Worker-mode control-channel messages. Distinct from the perspective
6
8
  * `ProxySession` channel that the worker's `Client` uses to talk to the
@@ -160,13 +162,6 @@ export interface FontFaceDescriptor {
160
162
  featureSettings?: string;
161
163
  display?: string;
162
164
  }
163
- /**
164
- * Theme values resolved on the host via `getComputedStyle` and shipped
165
- * to the renderer scope, which has no DOM. Decoded by the chart via
166
- * `theme/theme.ts::resolveThemeFromVars`. Plain map for
167
- * structured-clone.
168
- */
169
- export type ThemeSnapshot = Record<string, string>;
170
165
  export interface SetViewByNameMsg {
171
166
  kind: "setViewByName";
172
167
  name: string;
@@ -1,62 +1,78 @@
1
1
  import { ShaderRegistry } from "./shader-registry";
2
2
  import { BufferPool } from "./buffer-pool";
3
- export type WebGLCanvas = HTMLCanvasElement | OffscreenCanvas;
3
+ import { GLContext, type WebGLCanvas } from "./gl-context";
4
+ export type { WebGLCanvas } from "./gl-context";
4
5
  export interface WebGLContextManagerOptions {
5
6
  /**
6
7
  * If `true`, compile + link every shader in `SHADER_MANIFEST`
7
- * during construction (and again after a context-loss/restore
8
- * cycle). Trades ~30-100ms of init time for elimination of the
9
- * compile/link cost on the first-frame path of every chart type
10
- * that ends up rendered. Default `false` keeps the original
11
- * lazy-compile behavior — programs are compiled on first
12
- * `getOrCreate(name, ...)` call from a glyph's render path.
8
+ * during context construction (and again after a context-loss/
9
+ * restore cycle). Trades ~30-100ms of init time for elimination of
10
+ * the compile/link cost on the first-frame path. Ignored when the
11
+ * manager is constructed against an already-built shared
12
+ * {@link GLContext} — that context owns the precompile decision.
13
+ * Default `false` keeps lazy-compile behavior.
13
14
  */
14
15
  precompile?: boolean;
15
16
  }
17
+ /**
18
+ * Per-chart render handle. Owns the chart's GPU **buffers** and frame
19
+ * bookkeeping, but only *borrows* its GL **context** — either a private
20
+ * one it constructs (the default 1:1 mode, where the manager is handed a
21
+ * raw canvas) or a shared {@link GLContext} from the
22
+ * [ContextPool](./context-pool.ts) (pooled blit mode, where many
23
+ * managers share K contexts to stay under the browser's per-agent
24
+ * context cap).
25
+ *
26
+ * Buffers are always per-manager: {@link BufferPool} keys buffers by
27
+ * name, so two charts sharing one pool would stomp each other's `"x"` /
28
+ * `"y"` / `"color"`. Shaders, the GL context, and the canvas drawing
29
+ * buffer are shared per {@link GLContext}; the scheduler serializes
30
+ * renders of managers that share a context so the shared drawing buffer
31
+ * only ever holds one chart's frame at a time (see {@link beginFrame}).
32
+ */
16
33
  export declare class WebGLContextManager {
17
- private _canvas;
18
- private _gl;
19
- private _isWebGL2;
20
- private _shaders;
34
+ private _ctx;
35
+ private _ownsCtx;
36
+ private _disposeRestore;
21
37
  private _buffers;
22
38
  private _uploadedCount;
23
39
  private _cssWidth;
24
40
  private _cssHeight;
25
41
  private _dpr;
26
- private _precompile;
42
+ private _destroyed;
27
43
  private _frameCallback;
28
44
  /**
29
45
  * Per-instance `MessageChannel` used by `_yieldToTask` to resume a
30
- * polling `awaitGpuFence` loop on the next task. Allocated lazily —
31
- * the polling path is rarely hit when the GPU is idle, and many
32
- * `WebGLContextManager` instances never need one.
33
- *
46
+ * polling `awaitGpuFence` loop on the next task. Allocated lazily.
34
47
  * Must be per-instance: a module-level singleton races when two
35
- * managers poll concurrently. Both call sites assign
36
- * `port1.onmessage = resolve`, the second assignment overwrites the
37
- * first, and the first poll's promise never settles — leaving its
38
- * `awaitGpuFence` hung. The hang propagates up through the render
39
- * scheduler's `present` → `uploadAndRender` → the worker's
40
- * `uploadChunkAck` → the host's `with_typed_arrays` callback,
41
- * stalling `draw()` indefinitely. (Now that fence waits across
42
- * different `WebGLContextManager`s run in parallel inside one
43
- * scheduler drain, the per-instance discipline matters even
44
- * more — concurrent poll loops are the common case, not the
45
- * exception.)
46
- *
47
- * Cost of per-instance allocation: one extra `MessageChannel` (two
48
- * `MessagePort`s, ~negligible bytes, zero idle CPU) per chart on
49
- * top of the per-chart proxy channel that the transport already
50
- * holds. Bounded by chart count; safe even for pathological pages
51
- * with hundreds of charts. The alternative — allocating a fresh
52
- * channel on every `_yieldToTask` call — would churn ports on every
53
- * fence poll (potentially many per frame on slow GPUs), which is
54
- * far worse for the structured-clone subsystem than holding one
55
- * port pair for the manager's lifetime.
48
+ * managers poll concurrently (the second `onmessage` assignment
49
+ * clobbers the first's resolver, hanging that fence wait). Now that
50
+ * fence waits across managers run in parallel within one scheduler
51
+ * drain, concurrent poll loops are the common case.
56
52
  */
57
53
  private _yieldChannel;
58
- constructor(canvas: WebGLCanvas, options?: WebGLContextManagerOptions);
54
+ private _pendingYieldResolve;
55
+ /**
56
+ * @param source A raw canvas (manager constructs and **owns** a
57
+ * private {@link GLContext}) or an existing shared `GLContext`
58
+ * (manager borrows it; the pool owns its lifecycle).
59
+ */
60
+ constructor(source: WebGLCanvas | GLContext, options?: WebGLContextManagerOptions);
59
61
  get gl(): WebGL2RenderingContext | WebGLRenderingContext;
62
+ /**
63
+ * Identifies the shared GL context behind this manager. The
64
+ * scheduler groups pending renders by this id: managers on the same
65
+ * backend serialize (they share one drawing buffer); managers on
66
+ * different backends render in parallel. In the default 1:1 mode
67
+ * every manager has its own backend, so every render is independent
68
+ * — identical to the pre-pooling behavior.
69
+ */
70
+ get backendId(): number;
71
+ /**
72
+ * True once this manager's context has been lost — its own
73
+ * {@link destroy}, or a browser eviction of the shared context.
74
+ */
75
+ isContextLost(): boolean;
60
76
  get isWebGL2(): boolean;
61
77
  get shaders(): ShaderRegistry;
62
78
  get bufferPool(): BufferPool;
@@ -64,11 +80,10 @@ export declare class WebGLContextManager {
64
80
  set uploadedCount(count: number);
65
81
  /**
66
82
  * Resize the GL canvas's bitmap to match the host's CSS layout. The
67
- * Host is responsible for measuring the DOM element (or otherwise
68
- * deciding the target CSS size) and the device pixel ratio — the
69
- * manager itself does not touch DOM, so the same code path works
70
- * whether the canvas is an `HTMLCanvasElement` (in-process) or an
71
- * `OffscreenCanvas` (in-process via transfer, or in a worker).
83
+ * host measures the DOM element and DPR — the manager never touches
84
+ * DOM, so the same path works for an `HTMLCanvasElement`
85
+ * (in-process), a transferred `OffscreenCanvas` (direct/worker), or
86
+ * a pool-shared `OffscreenCanvas` (pooled blit).
72
87
  */
73
88
  resize(cssWidth: number, cssHeight: number, dpr: number): void;
74
89
  /**
@@ -78,106 +93,74 @@ export declare class WebGLContextManager {
78
93
  */
79
94
  private _pendingResize;
80
95
  /**
81
- * Record a dimension change to be applied at the start of the
82
- * next render's Phase 1, *before* `_fullRender` runs. The actual
83
- * `canvas.width = N` assignment (which clears the drawing buffer
84
- * per the WebGL spec) happens inside `applyPendingResize()`,
85
- * paired in the same synchronous task as the paint that fills
86
- * the new buffer.
96
+ * Record a dimension change to be applied at the start of the next
97
+ * render's Phase 1, before `_fullRender` runs. The actual
98
+ * `canvas.width = N` assignment (which clears the drawing buffer per
99
+ * the WebGL spec) happens inside {@link applyPendingResize} /
100
+ * {@link beginFrame}, paired in the same synchronous task as the
101
+ * paint that fills the new buffer — eliminating the one-frame blank
102
+ * the compositor would otherwise show in direct/in-process modes
103
+ * (where the GL canvas IS the visible canvas).
87
104
  *
88
- * Why split the dimension change off from the existing
89
- * {@link resize} method: in direct / in-process modes the
90
- * GL canvas IS the host's visible canvas, and `canvas.width = N`
91
- * is immediately observable to the browser's compositor as a
92
- * cleared buffer. If the resize lands in the message handler
93
- * (one task) but the matching `_fullRender` lands in the next
94
- * RAF (a later task), the compositor cycles between them and
95
- * presents one full frame of empty canvas — visible flicker.
96
- * Deferring the dimension change to the same RAF as the paint
97
- * eliminates the inter-frame gap; both happen inside Phase 1's
98
- * un-yielded loop.
99
- *
100
- * Multiple `requestResize` calls before the next render coalesce
101
- * to last-write-wins — five rapid width changes from a window
102
- * drag produce one resize+paint, not five.
105
+ * Multiple calls before the next render coalesce to last-write-wins.
103
106
  */
104
107
  requestResize(cssWidth: number, cssHeight: number, dpr: number): void;
105
108
  /**
106
109
  * Apply any pending dimension change recorded by
107
- * {@link requestResize}. Called by the scheduler's Phase 1
108
- * (immediately before each entry's `fullRender`) and by the
109
- * `snapshotPng` bypass path. Returns `true` when a resize was
110
- * applied, `false` when there was nothing pending — useful for
111
- * callers that want to skip a no-op render.
110
+ * {@link requestResize}. Returns `true` when a resize was applied.
112
111
  */
113
112
  applyPendingResize(): boolean;
114
113
  /**
115
- * Last CSS width passed to `resize()`.
114
+ * Prepare the shared GL surface for *this* manager's render. Called
115
+ * by the scheduler immediately before `_fullRender` (and by the
116
+ * `snapshotPng` bypass).
117
+ *
118
+ * - **Own context (1:1):** identical to the old Phase-1 step — apply
119
+ * only a *pending* resize. The context isn't shared, so there is
120
+ * no sibling state to reset and no need to touch the canvas when
121
+ * dimensions are unchanged.
122
+ * - **Shared context (pooled):** a co-tenant chart rendered last and
123
+ * left the shared canvas at *its* dimensions with *its* GL state.
124
+ * Force the canvas to this manager's dimensions and reset the
125
+ * global GL state a sibling might have left dirty (bound
126
+ * framebuffer, viewport, scissor/depth enables) so frames can't
127
+ * bleed across charts. Per-frame draw state (blend, clear) is
128
+ * re-established by the chart's `clearAndSetupFrame`.
116
129
  */
130
+ beginFrame(): void;
117
131
  get cssWidth(): number;
118
- /**
119
- * Last CSS height passed to `resize()`.
120
- */
121
132
  get cssHeight(): number;
122
- /**
123
- * Last device pixel ratio passed to `resize()`.
124
- */
125
133
  get dpr(): number;
126
134
  clear(): void;
127
135
  /**
128
136
  * Register a per-frame hook invoked at the end of each render. In
129
- * blit-mode rendering, the worker installs a callback that
130
- * transfers an `ImageBitmap` from `_canvas` (an `OffscreenCanvas`)
131
- * back to the host so the visible display canvas can `drawImage`
132
- * it. In direct mode the callback is left null and `endFrame` is a
133
- * no-op.
134
- *
135
- * Pass `null` to detach.
137
+ * blit-mode rendering the worker installs a callback that transfers
138
+ * an `ImageBitmap` from the canvas back to the host. In direct mode
139
+ * the callback is null and `endFrame` is a no-op. Pass `null` to
140
+ * detach.
136
141
  */
137
142
  setFrameCallback(cb: ((bitmap: ImageBitmap) => void) | null): void;
138
143
  /**
139
- * Called by chart impls at the bottom of `_fullRender` (and any
140
- * other path that produces a complete frame). When a frame
141
- * callback is registered AND the GL surface is an
142
- * `OffscreenCanvas`, ship its current contents as an
143
- * `ImageBitmap` to the host. Otherwise no-op — direct-mode
144
- * rendering has nothing to ship; the visible canvas already holds
145
- * the drawing buffer.
144
+ * Ship the current canvas contents as an `ImageBitmap` to the host
145
+ * when a frame callback is registered and the surface is an
146
+ * `OffscreenCanvas`. Otherwise no-op (direct mode paints straight to
147
+ * the visible drawing buffer).
146
148
  */
147
149
  endFrame(): void;
148
150
  /**
149
151
  * Resolve when every GL command submitted up to this call has been
150
- * executed by the GPU.
151
- *
152
- * On WebGL2 this issues a `fenceSync(SYNC_GPU_COMMANDS_COMPLETE)`
153
- * and polls `clientWaitSync` with a zero timeout, yielding to the
154
- * task queue between polls. The first poll passes
155
- * `SYNC_FLUSH_COMMANDS_BIT` so the fence becomes reachable without
156
- * a separate `gl.flush()`.
157
- *
158
- * On WebGL1 there is no fenceSync; we fall back to the blocking
159
- * `gl.finish()`. This is acceptable in a worker — never call this
160
- * from the main thread on a heavy frame.
161
- *
162
- * Used as a per-frame "GPU is idle" barrier so callers can serialize
163
- * follow-on work (`endFrame` snapshot, present roundtrip, the next
164
- * chunk upload) against actual GPU completion instead of the
165
- * implicit, implementation-defined timing of `transferToImageBitmap`.
152
+ * executed by the GPU. WebGL2 issues a `fenceSync` and polls
153
+ * `clientWaitSync` (yielding to the task queue between polls); WebGL1
154
+ * falls back to a blocking `gl.finish()` (acceptable in a worker).
166
155
  */
167
156
  awaitGpuFence(): Promise<void>;
168
157
  ensureBufferCapacity(totalRows: number): void;
169
158
  /**
170
- * Yield to the task queue between fence polls. We avoid
171
- * `setTimeout(0)` because Chromium clamps nested `setTimeout` to
172
- * ~4ms in workers, which would inflate the measured cost of
173
- * `awaitGpuFence`. A reused per-instance `MessageChannel` lands the
174
- * resume in the next task with sub-ms latency.
175
- *
176
- * `addEventListener(..., { once: true })` is used over
177
- * `port1.onmessage = ...` so concurrent in-flight resumes (should
178
- * any path ever introduce them) cannot clobber each other's
179
- * resolvers — the previous module-level singleton lost a resolver
180
- * on every overlap and hung one chart's `draw()` indefinitely.
159
+ * Yield to the task queue between fence polls. `setTimeout(0)` is
160
+ * avoided because Chromium clamps nested worker `setTimeout` to
161
+ * ~4ms; a reused per-instance `MessageChannel` resumes in the next
162
+ * task with sub-ms latency. `{ once: true }` over `onmessage = ...`
163
+ * so concurrent resumes can't clobber each other's resolvers.
181
164
  */
182
165
  private _yieldToTask;
183
166
  destroy(): void;
@@ -0,0 +1,40 @@
1
+ import { GLContext } from "./gl-context";
2
+ /**
3
+ * Renderer-scope pool of shared {@link GLContext}s for pooled blit mode.
4
+ *
5
+ * The browser caps live WebGL contexts per agent (~16 in Chromium) and
6
+ * force-loses the oldest past that cap. A page with more than ~16 charts
7
+ * therefore can't give each its own context. This pool caps the number
8
+ * of live contexts at `size`, distributing renderers across them
9
+ * round-robin (sticky for a renderer's lifetime — a renderer keeps the
10
+ * same context so its resident GPU buffers and any cached textures/FBOs
11
+ * stay valid frame to frame). N charts render through K = `size`
12
+ * contexts; the scheduler serializes renders that land on the same
13
+ * context.
14
+ *
15
+ * Pooling applies to blit mode only: direct mode renders into the host's
16
+ * transferred visible canvas, which is permanently 1:1 with a context.
17
+ *
18
+ * One pool per renderer scope (the worker, or the main thread in
19
+ * in-process mode). Contexts are created lazily on first `acquire` and
20
+ * live until renderer-scope teardown — like the contexts they replace,
21
+ * they are not refcounted, because a shared context outlives any single
22
+ * borrowing renderer.
23
+ */
24
+ export declare class ContextPool {
25
+ private _size;
26
+ private _precompile;
27
+ private _contexts;
28
+ private _next;
29
+ constructor(size: number, options?: {
30
+ precompile?: boolean;
31
+ });
32
+ /**
33
+ * Borrow a context for a new renderer. Fills the pool to `size`
34
+ * before reusing, then round-robins. A dead context (browser
35
+ * eviction) is replaced in place so a borrower never receives a lost
36
+ * context.
37
+ */
38
+ acquire(): GLContext;
39
+ private _make;
40
+ }
@@ -0,0 +1,54 @@
1
+ import { ShaderRegistry } from "./shader-registry";
2
+ export type WebGLCanvas = HTMLCanvasElement | OffscreenCanvas;
3
+ export declare class GLContext {
4
+ readonly id: number;
5
+ readonly canvas: WebGLCanvas;
6
+ readonly gl: WebGL2RenderingContext | WebGLRenderingContext;
7
+ readonly isWebGL2: boolean;
8
+ shaders: ShaderRegistry;
9
+ private _maxVertexAttribs;
10
+ private _angle;
11
+ private _precompile;
12
+ private _destroyed;
13
+ /**
14
+ * Per-tenant hooks fired after a `webglcontextrestored`. Each
15
+ * {@link WebGLContextManager} on this context registers one that
16
+ * rebuilds its `BufferPool` (the shaders are rebuilt here, once, for
17
+ * all tenants). On an intentional {@link destroy} these are dropped
18
+ * without firing.
19
+ */
20
+ private _onRestore;
21
+ constructor(canvas: WebGLCanvas, options?: {
22
+ precompile?: boolean;
23
+ });
24
+ /**
25
+ * Return every vertex-attribute slot to its default state: disabled,
26
+ * with a zero instance divisor. Vertex-array state (enable flags,
27
+ * pointers, divisors) is global to a context's default VAO — there
28
+ * are no per-chart VAOs in this codebase — so when many charts share
29
+ * one pooled context, a slot a prior chart enabled stays enabled for
30
+ * the next chart, now pointing at a buffer the prior chart's
31
+ * teardown deleted. Its next `drawArraysInstanced` then fails GL
32
+ * validation: "no buffer is bound to enabled attribute", silently
33
+ * dropping the draw.
34
+ *
35
+ * Glyphs already (re)enable + bind + set the divisor for every slot
36
+ * they use on each frame, so clearing to the default here is a safe
37
+ * baseline — it only removes the *leftover* enables a sibling chart
38
+ * left behind. Called from {@link WebGLContextManager.beginFrame} in
39
+ * shared (pooled) mode only; the 1:1 default never shares a context
40
+ * and so keeps its original, reset-free path.
41
+ */
42
+ resetVertexArrayState(): void;
43
+ /**
44
+ * Register a tenant's post-restore rebuild hook. Returns a disposer.
45
+ */
46
+ onRestore(cb: () => void): () => void;
47
+ /**
48
+ * True once lost — either our own {@link destroy} or a browser
49
+ * eviction of the oldest context past the per-agent cap.
50
+ */
51
+ isContextLost(): boolean;
52
+ get destroyed(): boolean;
53
+ destroy(): void;
54
+ }
@@ -81,10 +81,8 @@ export declare class WorkerRenderer {
81
81
  resetExpandedDomain(): void;
82
82
  /**
83
83
  * Hit-test the cursor against the chart's facet grid (in faceted
84
- * mode) or its current layout (single-plot). Mirrors the resolver
85
- * `_setupZoomRouter` builds on the host for in-process mode — the
86
- * worker owns the facet grid and controllers, so the resolution
87
- * runs here.
84
+ * mode) or its current layout (single-plot). The worker owns the
85
+ * facet grid and controllers, so the resolution runs here.
88
86
  */
89
87
  private _resolveTarget;
90
88
  onInteraction(event: InteractionEvent): void;
package/package.json CHANGED
@@ -1,47 +1,47 @@
1
1
  {
2
- "name": "@perspective-dev/viewer-charts",
3
- "version": "4.5.0",
4
- "description": "Perspective.js WebGL Plugin",
5
- "unpkg": "./dist/cdn/perspective-viewer-charts.js",
6
- "jsdelivr": "./dist/cdn/perspective-viewer-charts.js",
7
- "type": "module",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/esm/index.d.ts",
11
- "default": "./dist/esm/perspective-viewer-charts.js"
12
- },
13
- "./src/*": "./src/*",
14
- "./dist/*": "./dist/*",
15
- "./cdn/": "./dist/cdn/",
16
- "./esm/": "./dist/esm/",
17
- "./package.json": "./package.json"
2
+ "name": "@perspective-dev/viewer-charts",
3
+ "version": "4.5.2",
4
+ "description": "Perspective.js WebGL Plugin",
5
+ "unpkg": "./dist/cdn/perspective-viewer-charts.js",
6
+ "jsdelivr": "./dist/cdn/perspective-viewer-charts.js",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/esm/index.d.ts",
11
+ "default": "./dist/esm/perspective-viewer-charts.js"
18
12
  },
19
- "files": [
20
- "dist/**/*",
21
- "src/**/*"
22
- ],
23
- "types": "dist/esm/index.d.ts",
24
- "scripts": {
25
- "build": "node ./build.mjs",
26
- "clean": "node ./clean.mjs"
27
- },
28
- "publishConfig": {
29
- "access": "public"
30
- },
31
- "repository": {
32
- "type": "git",
33
- "url": "https://github.com/perspective-dev/perspective"
34
- },
35
- "author": "",
36
- "license": "Apache-2.0",
37
- "devDependencies": {
38
- "@perspective-dev/client": "workspace:",
39
- "@perspective-dev/viewer": "workspace:",
40
- "@perspective-dev/esbuild-plugin": "workspace:",
41
- "@perspective-dev/test": "workspace:",
42
- "@types/node": "catalog:",
43
- "lightningcss": "catalog:",
44
- "typescript": "catalog:",
45
- "webpack-glsl-minify": "1.5.0"
46
- }
47
- }
13
+ "./src/*": "./src/*",
14
+ "./dist/*": "./dist/*",
15
+ "./cdn/": "./dist/cdn/",
16
+ "./esm/": "./dist/esm/",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist/**/*",
21
+ "src/**/*"
22
+ ],
23
+ "types": "dist/esm/index.d.ts",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/perspective-dev/perspective"
30
+ },
31
+ "author": "",
32
+ "license": "Apache-2.0",
33
+ "devDependencies": {
34
+ "@perspective-dev/client": "",
35
+ "@perspective-dev/viewer": "",
36
+ "@perspective-dev/esbuild-plugin": "",
37
+ "@perspective-dev/test": "",
38
+ "@types/node": ">=22",
39
+ "lightningcss": "^1.29.0",
40
+ "typescript": ">=5 <6",
41
+ "webpack-glsl-minify": "1.5.0"
42
+ },
43
+ "scripts": {
44
+ "build": "node ./build.mjs",
45
+ "clean": "node ./clean.mjs"
46
+ }
47
+ }