@perspective-dev/viewer-charts 4.5.1 → 5.0.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/dist/cdn/perspective-viewer-charts.js +2 -2
- package/dist/cdn/perspective-viewer-charts.js.map +3 -3
- package/dist/esm/charts/candlestick/candlestick.d.ts +8 -10
- package/dist/esm/charts/chart-base.d.ts +58 -4
- package/dist/esm/charts/chart.d.ts +45 -9
- package/dist/esm/charts/series/glyphs/draw-areas.d.ts +3 -2
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +8 -1
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +3 -2
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +5 -2
- package/dist/esm/charts/series/series-build.d.ts +11 -0
- package/dist/esm/charts/series/series-interact.d.ts +10 -0
- package/dist/esm/charts/series/series-render.d.ts +17 -1
- package/dist/esm/charts/series/series.d.ts +53 -10
- package/dist/esm/config.d.ts +19 -0
- package/dist/esm/data/view-reader.d.ts +10 -1
- package/dist/esm/event-detail.d.ts +7 -0
- package/dist/esm/layout/ticks.d.ts +5 -0
- package/dist/esm/perspective-viewer-charts.js +2 -2
- package/dist/esm/perspective-viewer-charts.js.map +3 -3
- package/dist/esm/plugin/charts.d.ts +6 -3
- package/dist/esm/plugin/plugin.d.ts +46 -4
- package/dist/esm/render/scheduler.d.ts +34 -8
- package/dist/esm/transport/protocol.d.ts +31 -2
- package/dist/esm/transport/renderer-transport.d.ts +74 -3
- package/dist/esm/webgl/context-manager.d.ts +100 -117
- package/dist/esm/webgl/context-pool.d.ts +40 -0
- package/dist/esm/webgl/gl-context.d.ts +54 -0
- package/dist/esm/webgl/instanced-attrs.d.ts +7 -1
- package/dist/esm/worker/renderer.worker.d.ts +12 -0
- package/package.json +1 -1
- package/src/ts/charts/candlestick/candlestick-render.ts +4 -1
- package/src/ts/charts/candlestick/candlestick.ts +12 -17
- package/src/ts/charts/cartesian/cartesian-render.ts +80 -63
- package/src/ts/charts/chart-base.ts +85 -5
- package/src/ts/charts/chart.ts +47 -9
- package/src/ts/charts/common/category-axis-resolver.ts +5 -1
- package/src/ts/charts/common/tree-data.ts +61 -16
- package/src/ts/charts/heatmap/heatmap-render.ts +6 -2
- package/src/ts/charts/series/glyphs/draw-areas.ts +10 -1
- package/src/ts/charts/series/glyphs/draw-bars.ts +30 -9
- package/src/ts/charts/series/glyphs/draw-lines.ts +10 -1
- package/src/ts/charts/series/glyphs/draw-scatter.ts +56 -3
- package/src/ts/charts/series/series-build.ts +21 -3
- package/src/ts/charts/series/series-interact.ts +86 -13
- package/src/ts/charts/series/series-render.ts +660 -17
- package/src/ts/charts/series/series.ts +96 -26
- package/src/ts/charts/sunburst/sunburst-interact.ts +2 -3
- package/src/ts/charts/sunburst/sunburst-render.ts +3 -1
- package/src/ts/charts/treemap/treemap-interact.ts +2 -5
- package/src/ts/charts/treemap/treemap-render.ts +3 -1
- package/src/ts/charts/treemap/treemap.ts +6 -1
- package/src/ts/config.ts +21 -1
- package/src/ts/data/view-reader.ts +22 -2
- package/src/ts/event-detail.ts +8 -0
- package/src/ts/index.ts +13 -7
- package/src/ts/interaction/raw-event-forwarder.ts +7 -0
- package/src/ts/interaction/zoom-controller.ts +8 -0
- package/src/ts/layout/ticks.ts +9 -0
- package/src/ts/plugin/charts.ts +20 -4
- package/src/ts/plugin/plugin.ts +157 -23
- package/src/ts/render/scheduler.ts +164 -55
- package/src/ts/transport/protocol.ts +33 -0
- package/src/ts/transport/renderer-transport.ts +185 -9
- package/src/ts/webgl/context-manager.ts +195 -189
- package/src/ts/webgl/context-pool.ts +78 -0
- package/src/ts/webgl/gl-context.ts +191 -0
- package/src/ts/webgl/instanced-attrs.ts +15 -1
- package/src/ts/worker/dispatch.ts +4 -1
- package/src/ts/worker/renderer.worker.ts +94 -22
- package/src/ts/worker/session-host.ts +29 -0
|
@@ -30,9 +30,12 @@ export interface ChartTypeConfig {
|
|
|
30
30
|
* Per-chart-type overrides for `DEFAULT_PLUGIN_CONFIG`. Used when a
|
|
31
31
|
* field's sensible default differs by chart family — currently
|
|
32
32
|
* `include_zero` (true for Y Bar / Y Area / X Bar, false for line
|
|
33
|
-
* / scatter / cartesian / financial)
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* / scatter / cartesian / financial) and `facet_mode` ("overlay"
|
|
34
|
+
* for the band-pipeline families — series / financial — whose
|
|
35
|
+
* historical split_by rendering is the single stacked/colored
|
|
36
|
+
* plot; "grid" elsewhere). Applied at schema generation and at
|
|
37
|
+
* `restore({})` so the effective default matches the surfaced UI
|
|
38
|
+
* default.
|
|
36
39
|
*/
|
|
37
40
|
plugin_field_defaults?: Partial<PluginConfig>;
|
|
38
41
|
}
|
|
@@ -10,8 +10,6 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
|
|
|
10
10
|
private _renderer;
|
|
11
11
|
private _rendererPromise;
|
|
12
12
|
private _rawEventForwarder;
|
|
13
|
-
private _generation;
|
|
14
|
-
private _renderBlitMode;
|
|
15
13
|
private _resetClickAbort;
|
|
16
14
|
/**
|
|
17
15
|
* Plugin-scoped global config. Seeded lazily from
|
|
@@ -64,7 +62,21 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
|
|
|
64
62
|
*/
|
|
65
63
|
private _setupInteraction;
|
|
66
64
|
private _buildRenderer;
|
|
67
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Select the GL presentation strategy for *all* chart-type plugins
|
|
67
|
+
* in this renderer. Static + process-global: the value is shared by
|
|
68
|
+
* every per-chart-type subclass, so it must be set once before the
|
|
69
|
+
* charts that should use it build their renderers (a renderer reads
|
|
70
|
+
* {@link BLIT_MODE} at construction in `_buildRenderer`; charts
|
|
71
|
+
* already built keep their mode until torn down and rebuilt).
|
|
72
|
+
*
|
|
73
|
+
* - `"direct"` — each chart owns a GL context 1:1 with its visible
|
|
74
|
+
* canvas (lowest latency; bounded by the browser's ~16-context
|
|
75
|
+
* cap).
|
|
76
|
+
* - `"blit"` — charts render off-screen and share a pool of GL
|
|
77
|
+
* contexts ([webgl/context-pool.ts]), so a page can exceed the cap.
|
|
78
|
+
*/
|
|
79
|
+
static setBlitMode(mode: "direct" | "blit"): void;
|
|
68
80
|
get_static_config(): PluginStaticConfig;
|
|
69
81
|
column_config_schema(column_type: string, _group: string | undefined, _column_name: string, current_value: Record<string, unknown> | null, _viewer_config?: {
|
|
70
82
|
group_by?: string[];
|
|
@@ -84,10 +96,40 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
|
|
|
84
96
|
};
|
|
85
97
|
draw(view: View): Promise<void>;
|
|
86
98
|
update(view: View): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Shared body of `draw` / `update`. No re-entrancy guard: the host
|
|
101
|
+
* serializes every rendering call (`draw`, `update`, `render`,
|
|
102
|
+
* `resize`) and `delete` on its per-renderer draw lock, so this
|
|
103
|
+
* method never overlaps itself or a teardown. The one exception —
|
|
104
|
+
* an external DOM disconnect `delete()`ing the element mid-draw —
|
|
105
|
+
* is absorbed by `RendererTransport`, whose post-`destroy()`
|
|
106
|
+
* requests settle immediately instead of pending forever.
|
|
107
|
+
*/
|
|
87
108
|
private _drawImpl;
|
|
88
109
|
clear(): Promise<void>;
|
|
89
110
|
resize(): Promise<void>;
|
|
90
|
-
|
|
111
|
+
/**
|
|
112
|
+
* OPTIONAL host presize protocol: render at the TARGET element box
|
|
113
|
+
* `(width, height)` — the box the host's pending layout commit will
|
|
114
|
+
* produce — holding the resulting frame offscreen, so nothing on
|
|
115
|
+
* screen changes during the round-trip. Resolves, once the resized
|
|
116
|
+
* frame is staged, to a present closure; the host calls it in the
|
|
117
|
+
* same task as the layout commit, landing geometry and pixels in
|
|
118
|
+
* one paint. Plugins without this method get the host's held
|
|
119
|
+
* style-override presize path instead.
|
|
120
|
+
*/
|
|
121
|
+
presize(width: number, height: number): Promise<(() => void) | void>;
|
|
122
|
+
restyle(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Clear any active selection state (pinned tooltip) WITHOUT emitting
|
|
125
|
+
* selection events — the host calls this when a global filter
|
|
126
|
+
* contributed by this panel's selection is removed from the global
|
|
127
|
+
* filter bar, so the pin can't outlive the filter it produced. The
|
|
128
|
+
* transport's `_lastInsertConfig` (remove-set memory) is deliberately
|
|
129
|
+
* RETAINED so a subsequent selection still replaces any leftover
|
|
130
|
+
* clauses rather than accumulating.
|
|
131
|
+
*/
|
|
132
|
+
deselect(): Promise<void>;
|
|
91
133
|
render(view: View): Promise<Blob>;
|
|
92
134
|
restore(config: any, columns_config?: Record<string, any>): void;
|
|
93
135
|
delete(): void;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { WebGLContextManager } from "../webgl/context-manager";
|
|
2
2
|
/**
|
|
3
|
-
* Request a coalesced render of `glManager`
|
|
4
|
-
*
|
|
5
|
-
* Phase 2
|
|
3
|
+
* Request a coalesced render of `glManager`. `fullRender` submits the
|
|
4
|
+
* GL commands (Phase 1, synchronous); `render2D` draws the 2D layers
|
|
5
|
+
* (gridlines + chrome) and runs in Phase 2 *after* `awaitGpuFence`, so
|
|
6
|
+
* the 2D canvases don't present ahead of the GL frame. Returns a
|
|
7
|
+
* promise that resolves when this entry's Phase 2 (`awaitGpuFence` +
|
|
8
|
+
* `render2D` + `endFrame`) completes.
|
|
6
9
|
*
|
|
7
10
|
* If a request is already pending for the same glManager, the new
|
|
8
|
-
* call's `fullRender`
|
|
9
|
-
* wins; closures read chart state lazily so this is
|
|
10
|
-
* no-op, but keeps the
|
|
11
|
-
* resolves alongside the existing waiters.
|
|
11
|
+
* call's `fullRender` / `render2D` closures replace the prior ones
|
|
12
|
+
* (latest call wins; closures read chart state lazily so this is
|
|
13
|
+
* functionally a no-op, but keeps the closures fresh) and the returned
|
|
14
|
+
* promise resolves alongside the existing waiters.
|
|
12
15
|
*/
|
|
13
|
-
export declare function requestRender(glManager: WebGLContextManager, fullRender: () => void): Promise<void>;
|
|
16
|
+
export declare function requestRender(glManager: WebGLContextManager, fullRender: () => void, render2D: () => void): Promise<void>;
|
|
14
17
|
/**
|
|
15
18
|
* Run `op` synchronously if no drain `present()` is currently active
|
|
16
19
|
* for `glManager`. Otherwise queue `op` to run as soon as that
|
|
@@ -34,6 +37,29 @@ export declare function requestRender(glManager: WebGLContextManager, fullRender
|
|
|
34
37
|
* picks it up for the next RAF.
|
|
35
38
|
*/
|
|
36
39
|
export declare function deferIfDraining(glManager: WebGLContextManager, op: () => void): void;
|
|
40
|
+
/**
|
|
41
|
+
* Drop every scheduler reference to `glManager` — called from
|
|
42
|
+
* `WebGLContextManager`'s owning renderer at teardown, *before*
|
|
43
|
+
* `glManager.destroy()` loses the GL context.
|
|
44
|
+
*
|
|
45
|
+
* Without this, a frame queued for the next RAF (an `Entry` in
|
|
46
|
+
* `pending`) or a `deferIfDraining` op still parked in `deferred`
|
|
47
|
+
* would survive the destroy and, on the next `drain()`, drive
|
|
48
|
+
* `fullRender` / `present` against a context-lost manager. In blit
|
|
49
|
+
* mode that surfaces as `endFrame`'s `transferToImageBitmap` throwing
|
|
50
|
+
* "Cannot transfer to ImageBitmap because WebGL context is lost" —
|
|
51
|
+
* logged as "scheduler: present failed". In direct mode it's a wasted
|
|
52
|
+
* paint against a dead context.
|
|
53
|
+
*
|
|
54
|
+
* Outstanding waiters for a destroyed manager are resolved (not
|
|
55
|
+
* rejected): the caller asked to tear the chart down, so its awaited
|
|
56
|
+
* `draw()` should observe a clean no-op rather than an error it would
|
|
57
|
+
* have to suppress. An in-flight `present()` (manager in `inFlight`)
|
|
58
|
+
* is left to unwind on its own — its `finally` clause will not find a
|
|
59
|
+
* `deferred` entry to flush, and the manager is already gone from
|
|
60
|
+
* `pending`, so it cannot re-enqueue.
|
|
61
|
+
*/
|
|
62
|
+
export declare function unregister(glManager: WebGLContextManager): void;
|
|
37
63
|
/**
|
|
38
64
|
* Test-only: clear pending state. Production callers must not use
|
|
39
65
|
* this — outstanding waiters are silently dropped.
|
|
@@ -9,8 +9,8 @@ export type { ThemeSnapshot };
|
|
|
9
9
|
* host's real `Client` — that's pure protobuf bytes; this one is the
|
|
10
10
|
* chart's own renderer control plane.
|
|
11
11
|
*/
|
|
12
|
-
export type ControlMsg = InitMsg | SetViewByNameMsg | SetColumnsConfigMsg | SetPluginConfigMsg | SetBufferMaxCapacityMsg | LoadAndRenderMsg | RedrawMsg | ResizeMsg | ClearMsg | InvalidateThemeMsg | RestoreZoomMsg | ResetAllZoomsMsg | ResetExpandedDomainMsg | SaveZoomReqMsg | SnapshotPngReqMsg | InteractionMsg | DestroyMsg;
|
|
13
|
-
export type WorkerMsg = ReadyMsg | ZoomChangedMsg | SaveZoomReplyMsg | SnapshotPngReplyMsg | PinTooltipMsg | DismissTooltipMsg | SetCursorMsg | UserClickMsg | UserSelectMsg | LoadAndRenderAckMsg | FrameBitmapMsg | ErrorMsg;
|
|
12
|
+
export type ControlMsg = InitMsg | SetViewByNameMsg | SetColumnsConfigMsg | SetPluginConfigMsg | SetBufferMaxCapacityMsg | LoadAndRenderMsg | RedrawMsg | DeselectMsg | ResizeMsg | ClearMsg | InvalidateThemeMsg | RestoreZoomMsg | ResetAllZoomsMsg | ResetExpandedDomainMsg | SaveZoomReqMsg | SnapshotPngReqMsg | InteractionMsg | DestroyMsg;
|
|
13
|
+
export type WorkerMsg = ReadyMsg | ZoomChangedMsg | SaveZoomReplyMsg | SnapshotPngReplyMsg | PinTooltipMsg | DismissTooltipMsg | SetCursorMsg | UserClickMsg | UserSelectMsg | LoadAndRenderAckMsg | ResizeAckMsg | FrameBitmapMsg | ErrorMsg;
|
|
14
14
|
/**
|
|
15
15
|
* Session-tagged envelopes for the shared-worker transport. Every
|
|
16
16
|
* message between the host and the shared `Worker` carries a numeric
|
|
@@ -230,12 +230,41 @@ export interface LoadAndRenderAckMsg {
|
|
|
230
230
|
export interface RedrawMsg {
|
|
231
231
|
kind: "redraw";
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Host → worker: silently clear the chart's selection state (pinned
|
|
235
|
+
* tooltip) — no `userSelect` reply, so the host filter-bar removal that
|
|
236
|
+
* triggered it can't echo back into the host's filter set. The worker's
|
|
237
|
+
* `TooltipController.dismiss()` posts `dismissTooltip` to tear down the
|
|
238
|
+
* host-side pinned artifact.
|
|
239
|
+
*/
|
|
240
|
+
export interface DeselectMsg {
|
|
241
|
+
kind: "deselect";
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Host → worker: resize the chart to a new CSS box. Replied with
|
|
245
|
+
* {@link ResizeAckMsg} AFTER the resized frame PRESENTS — the host's
|
|
246
|
+
* `plugin.resize()` promise is the viewer's presize contract ("the
|
|
247
|
+
* plugin has repainted at the target box when this resolves"), so an
|
|
248
|
+
* ack at message-receipt would let the settings-pane layout commit
|
|
249
|
+
* against the old-dimensions bitmap (the aspect-ratio warp).
|
|
250
|
+
*/
|
|
233
251
|
export interface ResizeMsg {
|
|
234
252
|
kind: "resize";
|
|
253
|
+
msgId: number;
|
|
235
254
|
cssWidth: number;
|
|
236
255
|
cssHeight: number;
|
|
237
256
|
dpr: number;
|
|
238
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Worker → host reply to a `ResizeMsg`, posted after the resized
|
|
260
|
+
* frame's present completes. Always sent — present failures and
|
|
261
|
+
* torn-down charts included — so the host's awaited promise resolves
|
|
262
|
+
* deterministically.
|
|
263
|
+
*/
|
|
264
|
+
export interface ResizeAckMsg {
|
|
265
|
+
kind: "resizeAck";
|
|
266
|
+
msgId: number;
|
|
267
|
+
}
|
|
239
268
|
export interface ClearMsg {
|
|
240
269
|
kind: "clear";
|
|
241
270
|
}
|
|
@@ -20,11 +20,21 @@ import type { InteractionEvent } from "./protocol";
|
|
|
20
20
|
*/
|
|
21
21
|
export declare class RendererTransport {
|
|
22
22
|
private _handle;
|
|
23
|
+
/**
|
|
24
|
+
* Set by {@link destroy}. Requests allocated after teardown settle
|
|
25
|
+
* immediately in `_allocPending` — `_post` is already a no-op once
|
|
26
|
+
* `_handle` is null, so a late request would otherwise never
|
|
27
|
+
* receive its reply and pend forever.
|
|
28
|
+
*/
|
|
29
|
+
private _destroyed;
|
|
23
30
|
private _proxyChannel;
|
|
24
31
|
private _proxySession;
|
|
25
32
|
private _client;
|
|
26
33
|
private _view;
|
|
27
34
|
private _tableName;
|
|
35
|
+
/** Source panel `slot`, tagged onto dispatched interaction events so the
|
|
36
|
+
* host can route them (master/detail, global filter) to the right panel. */
|
|
37
|
+
private _panel;
|
|
28
38
|
private _clientWorkerURL;
|
|
29
39
|
private _clientWasm;
|
|
30
40
|
private _chartTag;
|
|
@@ -63,6 +73,30 @@ export declare class RendererTransport {
|
|
|
63
73
|
* canvas's drawing buffer is the worker's transferred GL canvas).
|
|
64
74
|
*/
|
|
65
75
|
private _displayCtx;
|
|
76
|
+
/**
|
|
77
|
+
* Present-hold state for the staged-present presize protocol
|
|
78
|
+
* ({@link presize}). While holding, inbound `frameBitmap`s are
|
|
79
|
+
* STAGED (latest wins) instead of blitted, so the on-screen frame
|
|
80
|
+
* — old dimensions in the old, unchanged box — stays put until the
|
|
81
|
+
* host invokes the present closure `presize` resolved to, in the
|
|
82
|
+
* same task as its layout commit. Entered only by `presize`;
|
|
83
|
+
* exited by the present closure, by `presize`'s rejection path, or
|
|
84
|
+
* by `destroy` — every settle path releases the hold or hands the
|
|
85
|
+
* caller the release.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* The last `(cssWidth, cssHeight, dpr)` posted to the worker — at init
|
|
89
|
+
* or any resize — i.e. the dimensions the worker has already rendered
|
|
90
|
+
* (or has an in-flight render for). {@link resize} short-circuits when
|
|
91
|
+
* its measurement matches (±0.5px, the presize protocol's own
|
|
92
|
+
* threshold): reactive resizes fan in from several host paths per
|
|
93
|
+
* layout transition (the post-commit fan-out, activation nudges, the
|
|
94
|
+
* fresh-restore tail), and every worker resize is an unconditional
|
|
95
|
+
* full re-render.
|
|
96
|
+
*/
|
|
97
|
+
private _lastPostedSize;
|
|
98
|
+
private _holdPresent;
|
|
99
|
+
private _stagedFrame;
|
|
66
100
|
/**
|
|
67
101
|
* Host-side sink for tooltip + cursor side-effects. The chart
|
|
68
102
|
* inside the renderer calls into a `MessageHostSink` that posts
|
|
@@ -85,6 +119,7 @@ export declare class RendererTransport {
|
|
|
85
119
|
client: Client;
|
|
86
120
|
view: View;
|
|
87
121
|
tableName?: string;
|
|
122
|
+
panel?: string;
|
|
88
123
|
clientWasm: WebAssembly.Module;
|
|
89
124
|
clientWorkerURL: URL;
|
|
90
125
|
chartTag: string;
|
|
@@ -125,7 +160,8 @@ export declare class RendererTransport {
|
|
|
125
160
|
* `loadAndRenderAck`. Per the worker's "resolve on stale"
|
|
126
161
|
* contract, a mid-flight cancellation (a newer `loadAndRender`
|
|
127
162
|
* superseding this one) still acks — the host's awaiter just
|
|
128
|
-
* resolves quietly.
|
|
163
|
+
* resolves quietly. Calls issued after `destroy()` resolve
|
|
164
|
+
* immediately (see `_allocPending`).
|
|
129
165
|
*/
|
|
130
166
|
loadAndRender(opts: {
|
|
131
167
|
viewerConfig: {
|
|
@@ -138,7 +174,41 @@ export declare class RendererTransport {
|
|
|
138
174
|
};
|
|
139
175
|
}): Promise<void>;
|
|
140
176
|
redraw(): void;
|
|
141
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Silently clear the worker chart's selection state (pinned tooltip) —
|
|
179
|
+
* no selection events are emitted; the worker echoes `dismissTooltip`
|
|
180
|
+
* to drop the host-side pinned artifact. See `DeselectMsg`.
|
|
181
|
+
*/
|
|
182
|
+
deselect(): void;
|
|
183
|
+
/**
|
|
184
|
+
* Resize the chart to the GL canvas's CURRENT CSS box and resolve
|
|
185
|
+
* once the resized frame has PRESENTED (the worker's `resizeAck`) —
|
|
186
|
+
* not at message-post, which would let callers proceed while the
|
|
187
|
+
* old-dimensions bitmap is still on screen. Reactive geometry
|
|
188
|
+
* changes only; anticipated ones go through {@link presize}.
|
|
189
|
+
*
|
|
190
|
+
* A measurement matching {@link _lastPostedSize} resolves immediately
|
|
191
|
+
* WITHOUT a worker round trip: the worker has already rendered (or is
|
|
192
|
+
* presenting) that exact frame, and re-posting would be a full
|
|
193
|
+
* re-render of an identical bitmap.
|
|
194
|
+
*/
|
|
195
|
+
resize(): Promise<void>;
|
|
196
|
+
/**
|
|
197
|
+
* Staged-present variant of {@link resize}: render at the given
|
|
198
|
+
* TARGET canvas CSS box (no measurement — the caller supplies the
|
|
199
|
+
* box the pending layout commit will produce), HOLDING the
|
|
200
|
+
* resulting frame (and any others that arrive meanwhile) offscreen
|
|
201
|
+
* instead of blitting, so nothing on screen changes. Resolves,
|
|
202
|
+
* once the resized frame is staged, to a present closure: calling
|
|
203
|
+
* it blits the latest staged frame and exits the hold — the host
|
|
204
|
+
* runs it in the same task as its layout commit so geometry and
|
|
205
|
+
* pixels land in one paint. On rejection (teardown) the hold is
|
|
206
|
+
* released here, so every settle path either frees the display or
|
|
207
|
+
* hands the caller the release.
|
|
208
|
+
*/
|
|
209
|
+
presize(cssWidth: number, cssHeight: number): Promise<(() => void) | void>;
|
|
210
|
+
private _postResize;
|
|
211
|
+
private _presentStaged;
|
|
142
212
|
clear(): void;
|
|
143
213
|
invalidateTheme(): void;
|
|
144
214
|
saveZoom(): Promise<void>;
|
|
@@ -146,7 +216,8 @@ export declare class RendererTransport {
|
|
|
146
216
|
* Allocate a pending request slot of the given `kind`. Returns the
|
|
147
217
|
* id (encoded into the outgoing `ControlMsg`) and a promise that
|
|
148
218
|
* resolves / rejects when the matching reply arrives or
|
|
149
|
-
* `destroy()` drains the table.
|
|
219
|
+
* `destroy()` drains the table. Requests issued *after* `destroy()`
|
|
220
|
+
* settle immediately with the same per-kind semantics as the drain.
|
|
150
221
|
*/
|
|
151
222
|
private _allocPending;
|
|
152
223
|
restoreZoom(state: any): void;
|
|
@@ -1,62 +1,78 @@
|
|
|
1
1
|
import { ShaderRegistry } from "./shader-registry";
|
|
2
2
|
import { BufferPool } from "./buffer-pool";
|
|
3
|
-
|
|
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/
|
|
8
|
-
* cycle). Trades ~30-100ms of init time for elimination of
|
|
9
|
-
* compile/link cost on the first-frame path
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
|
18
|
-
private
|
|
19
|
-
private
|
|
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
|
|
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
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
-
*
|
|
83
|
-
* `canvas.width = N` assignment (which clears the drawing buffer
|
|
84
|
-
*
|
|
85
|
-
* paired in the same synchronous task as the
|
|
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
|
-
*
|
|
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}.
|
|
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
|
-
*
|
|
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
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
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
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
171
|
-
*
|
|
172
|
-
* ~4ms
|
|
173
|
-
*
|
|
174
|
-
*
|
|
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
|
+
}
|