@jbrowse/render-core 5.0.0-beta.1 → 5.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/RenderLifecycleMixin.d.ts +25 -6
- package/esm/RenderLifecycleMixin.js +30 -7
- package/esm/canvas2dUtils.d.ts +25 -0
- package/esm/canvas2dUtils.js +27 -0
- package/esm/colorRampLut.d.ts +4 -4
- package/esm/colorRampLut.js +4 -4
- package/esm/computePipeline.d.ts +13 -0
- package/esm/computePipeline.js +55 -0
- package/esm/contractReports.d.ts +54 -0
- package/esm/contractReports.js +112 -0
- package/esm/coverageBand.d.ts +0 -1
- package/esm/coverageBand.js +0 -1
- package/esm/coverageBandBuffers.d.ts +0 -6
- package/esm/coverageBandBuffers.js +1 -13
- package/esm/createRenderingBackend.d.ts +14 -18
- package/esm/createRenderingBackend.js +4 -2
- package/esm/displayPhase.d.ts +10 -1
- package/esm/displayPhase.js +2 -1
- package/esm/encodeMemo.d.ts +14 -0
- package/esm/encodeMemo.js +48 -0
- package/esm/gpuDevice.d.ts +9 -0
- package/esm/gpuDevice.js +12 -3
- package/esm/graphicsCapabilities.d.ts +18 -15
- package/esm/graphicsCapabilities.js +23 -16
- package/esm/hal/createHal.d.ts +1 -2
- package/esm/hal/createHal.js +14 -4
- package/esm/hal/deviceGpuCache.js +2 -2
- package/esm/hal/gpuHalBase.d.ts +1 -1
- package/esm/hal/gpuHalBase.js +2 -3
- package/esm/hal/mockHal.d.ts +0 -1
- package/esm/hal/mockHal.js +0 -10
- package/esm/hal/regionRegistry.d.ts +0 -1
- package/esm/hal/regionRegistry.js +3 -15
- package/esm/hal/types.d.ts +5 -1
- package/esm/hal/webgl2Hal.d.ts +1 -1
- package/esm/hal/webgl2Hal.js +2 -2
- package/esm/hal/webgpuHal.d.ts +2 -2
- package/esm/hal/webgpuHal.js +13 -10
- package/esm/installUpload.d.ts +4 -1
- package/esm/installUpload.js +10 -41
- package/esm/instanceCache.d.ts +10 -7
- package/esm/instanceCache.js +14 -15
- package/esm/mapUploadSync.d.ts +0 -8
- package/esm/mapUploadSync.js +0 -1
- package/esm/marks/colorFill.d.ts +17 -0
- package/esm/marks/colorFill.js +26 -0
- package/esm/marks/drawAgainstHit.d.ts +47 -0
- package/esm/marks/drawAgainstHit.js +140 -0
- package/esm/marks/glyphPaint.d.ts +16 -0
- package/esm/marks/glyphPaint.js +41 -0
- package/esm/marks/index.d.ts +9 -0
- package/esm/marks/index.js +6 -0
- package/esm/marks/markBackend.d.ts +87 -0
- package/esm/marks/markBackend.js +133 -0
- package/esm/marks/markPaint.d.ts +13 -0
- package/esm/marks/markPaint.js +18 -0
- package/esm/marks/pointMark.d.ts +24 -0
- package/esm/marks/pointMark.js +98 -0
- package/esm/marks/spanMark.d.ts +45 -0
- package/esm/marks/spanMark.js +71 -0
- package/esm/marks/types.d.ts +206 -0
- package/esm/marks/types.js +125 -0
- package/esm/perRegionRenderingBackend.d.ts +15 -3
- package/esm/perRegionRenderingBackend.js +26 -3
- package/esm/regionDataMap.d.ts +2 -2
- package/esm/regionDataMap.js +25 -23
- package/esm/renderingBackendBase.d.ts +11 -14
- package/esm/renderingBackendBase.js +7 -7
- package/esm/shaders/capsule.js.generated.d.ts +1 -0
- package/esm/shaders/capsule.js.generated.js +11 -0
- package/esm/shaders/coverageBar.generated.d.ts +2 -2
- package/esm/shaders/coverageBar.generated.js +2 -2
- package/esm/shaders/coverageMod.generated.d.ts +2 -2
- package/esm/shaders/coverageMod.generated.js +2 -2
- package/esm/shaders/coverageSnp.generated.d.ts +2 -2
- package/esm/shaders/coverageSnp.generated.js +2 -2
- package/esm/shaders/hpmath.js.generated.d.ts +1 -0
- package/esm/shaders/hpmath.js.generated.js +4 -1
- package/esm/shaders/pointMark.consts.generated.d.ts +4 -0
- package/esm/shaders/pointMark.consts.generated.js +6 -0
- package/esm/shaders/pointMark.generated.d.ts +5 -0
- package/esm/shaders/pointMark.generated.js +7 -0
- package/esm/shaders/pointMark.iface.generated.d.ts +55 -0
- package/esm/shaders/pointMark.iface.generated.js +100 -0
- package/esm/shaders/pointMark.js.generated.d.ts +1 -0
- package/esm/shaders/pointMark.js.generated.js +16 -0
- package/esm/shaders/rowRect.generated.d.ts +0 -1
- package/esm/shaders/rowRect.generated.js +0 -1
- package/esm/shaders/spanMark.generated.d.ts +4 -0
- package/esm/shaders/spanMark.generated.js +6 -0
- package/esm/shaders/spanMark.iface.generated.d.ts +58 -0
- package/esm/shaders/spanMark.iface.generated.js +122 -0
- package/esm/sharedBackendKey.d.ts +18 -0
- package/esm/{keyedRenderingBackend.js → sharedBackendKey.js} +5 -0
- package/esm/slangPass.d.ts +2 -0
- package/esm/slangPass.js +2 -0
- package/package.json +14 -4
- package/src/shaders/capsule.js.generated.ts +14 -0
- package/src/shaders/capsule.slang +12 -4
- package/src/shaders/colorPack.slang +5 -1
- package/src/shaders/coverageBand.slang +38 -15
- package/src/shaders/coverageBar.generated.ts +2 -2
- package/src/shaders/coverageBar.slang +9 -0
- package/src/shaders/coverageMod.generated.ts +2 -2
- package/src/shaders/coverageSnp.generated.ts +2 -2
- package/src/shaders/hpmath.js.generated.ts +5 -1
- package/src/shaders/hpmath.slang +29 -10
- package/src/shaders/pointMark.consts.generated.ts +10 -0
- package/src/shaders/pointMark.generated.ts +12 -0
- package/src/shaders/pointMark.iface.generated.ts +146 -0
- package/src/shaders/pointMark.js.generated.ts +21 -0
- package/src/shaders/pointMark.slang +192 -0
- package/src/shaders/rowRect.generated.ts +0 -2
- package/src/shaders/rowRect.slang +33 -44
- package/src/shaders/spanMark.generated.ts +10 -0
- package/src/shaders/spanMark.iface.generated.ts +168 -0
- package/src/shaders/spanMark.slang +22 -0
- package/esm/globalRenderingBackend.d.ts +0 -90
- package/esm/globalRenderingBackend.js +0 -53
- package/esm/keyedRenderingBackend.d.ts +0 -41
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { computed } from 'mobx';
|
|
2
|
+
/**
|
|
3
|
+
* A per-key encode memo over a display's cell map: `encode` runs for a key
|
|
4
|
+
* when its data reference moves, every key re-encodes when `inputs` yields a
|
|
5
|
+
* new identity, and a departed key is dropped. `inputs` runs through a
|
|
6
|
+
* `computed`, so a getter that allocates a fresh object per read re-encodes
|
|
7
|
+
* only when what it read changed.
|
|
8
|
+
*
|
|
9
|
+
* The returned map is a new instance whenever an entry moved and the same one
|
|
10
|
+
* otherwise, so an MST view holding it invalidates its observers exactly when
|
|
11
|
+
* a cell did. `installUpload` runs its `encode` path on this; a display that
|
|
12
|
+
* needs the encoded map itself — for a hit test, an SVG export — holds one in
|
|
13
|
+
* a `.views` closure and hands the installer identity cells.
|
|
14
|
+
*/
|
|
15
|
+
export function createEncodeMemo(cells, inputs, encode) {
|
|
16
|
+
const encoded = new Map();
|
|
17
|
+
const encodedFrom = new Map();
|
|
18
|
+
const props = inputs && computed(inputs);
|
|
19
|
+
let lastProps;
|
|
20
|
+
let snapshot = encoded;
|
|
21
|
+
return () => {
|
|
22
|
+
const current = cells();
|
|
23
|
+
const p = props ? props.get() : undefined;
|
|
24
|
+
if (p !== lastProps) {
|
|
25
|
+
lastProps = p;
|
|
26
|
+
encodedFrom.clear();
|
|
27
|
+
}
|
|
28
|
+
let changed = false;
|
|
29
|
+
for (const [key, data] of current) {
|
|
30
|
+
if (encodedFrom.get(key) !== data) {
|
|
31
|
+
encoded.set(key, encode(data, p, key));
|
|
32
|
+
encodedFrom.set(key, data);
|
|
33
|
+
changed = true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const key of encoded.keys()) {
|
|
37
|
+
if (!current.has(key)) {
|
|
38
|
+
encoded.delete(key);
|
|
39
|
+
encodedFrom.delete(key);
|
|
40
|
+
changed = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (changed) {
|
|
44
|
+
snapshot = new Map(encoded);
|
|
45
|
+
}
|
|
46
|
+
return snapshot;
|
|
47
|
+
};
|
|
48
|
+
}
|
package/esm/gpuDevice.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RendererName } from './graphicsCapabilities.ts';
|
|
1
2
|
interface GpuDeviceCell {
|
|
2
3
|
device: GPUDevice | null;
|
|
3
4
|
/** Serializes concurrent calls during async init and after recovery. */
|
|
@@ -30,6 +31,12 @@ interface GpuDeviceCell {
|
|
|
30
31
|
* @see setGpuOverride
|
|
31
32
|
*/
|
|
32
33
|
gpuOverride: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* The rung the last `createGpuHal` returned, so `effectiveRenderer` reports
|
|
36
|
+
* what drew rather than what the probe predicted. Optional because a cell an
|
|
37
|
+
* older copy built has none, and "nothing built yet" is a real state there.
|
|
38
|
+
*/
|
|
39
|
+
builtRenderer?: RendererName;
|
|
33
40
|
}
|
|
34
41
|
declare global {
|
|
35
42
|
var __jbrowseRenderCoreGpuDeviceV1: GpuDeviceCell | undefined;
|
|
@@ -61,6 +68,8 @@ export type GpuOverride = (typeof GPU_OVERRIDES)[number];
|
|
|
61
68
|
*/
|
|
62
69
|
export declare function setGpuOverride(value: string | null): void;
|
|
63
70
|
export declare function getGpuOverride(): string | null;
|
|
71
|
+
export declare function setBuiltRenderer(name: RendererName): void;
|
|
72
|
+
export declare function getBuiltRenderer(): RendererName | undefined;
|
|
64
73
|
/**
|
|
65
74
|
* Whether the GPU is off for the whole page — either pinned by `?renderer=` at
|
|
66
75
|
* startup or switched off from a display's GPU-error banner after an
|
package/esm/gpuDevice.js
CHANGED
|
@@ -24,9 +24,11 @@
|
|
|
24
24
|
* field added later would read `undefined` on a cell built by an older copy.
|
|
25
25
|
* Changing this interface therefore means bumping the `V1` in the global's
|
|
26
26
|
* name, which costs the sharing between the two versions — the point of the
|
|
27
|
-
* suffix is that losing it is a decision rather than a silent misread.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* suffix is that losing it is a decision rather than a silent misread. The one
|
|
28
|
+
* exception is an optional field whose `undefined` is an honest state, as
|
|
29
|
+
* `builtRenderer`'s is. Reset in place (see `resetGpuDeviceForTests`); never
|
|
30
|
+
* reassign the cell itself, or copies holding the old object diverge from the
|
|
31
|
+
* new one.
|
|
30
32
|
*/
|
|
31
33
|
const cell = (globalThis.__jbrowseRenderCoreGpuDeviceV1 ??= {
|
|
32
34
|
device: null,
|
|
@@ -216,6 +218,12 @@ export function setGpuOverride(value) {
|
|
|
216
218
|
export function getGpuOverride() {
|
|
217
219
|
return cell.gpuOverride;
|
|
218
220
|
}
|
|
221
|
+
export function setBuiltRenderer(name) {
|
|
222
|
+
cell.builtRenderer = name;
|
|
223
|
+
}
|
|
224
|
+
export function getBuiltRenderer() {
|
|
225
|
+
return cell.builtRenderer;
|
|
226
|
+
}
|
|
219
227
|
/**
|
|
220
228
|
* Whether the GPU is off for the whole page — either pinned by `?renderer=` at
|
|
221
229
|
* startup or switched off from a display's GPU-error banner after an
|
|
@@ -248,6 +256,7 @@ export function resetGpuDeviceForTests() {
|
|
|
248
256
|
cell.devicePromise = null;
|
|
249
257
|
cell.hadDevice = false;
|
|
250
258
|
cell.deviceLostListeners.clear();
|
|
259
|
+
cell.builtRenderer = undefined;
|
|
251
260
|
}
|
|
252
261
|
export function getGpuDevice() {
|
|
253
262
|
const override = getGpuOverride();
|
|
@@ -65,22 +65,25 @@ export declare function isSoftwareRenderer(glRenderer: string): boolean;
|
|
|
65
65
|
*/
|
|
66
66
|
export declare function getGraphicsCapabilities(): Promise<GraphicsCapabilities>;
|
|
67
67
|
/**
|
|
68
|
-
* Which rung
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* *and* the page-wide pin from `?renderer=` or the
|
|
73
|
-
* GPU" button.
|
|
68
|
+
* Which rung draws. Once `createGpuHal` has built one on this page, the rung it
|
|
69
|
+
* returned is the answer — the adapter probe says WebGPU where
|
|
70
|
+
* `WebGPUHal.create` then throws and the ladder lands on WebGL2. Before any
|
|
71
|
+
* backend exists it is the ladder read as an answer rather than run, from what
|
|
72
|
+
* the machine can do *and* the page-wide pin from `?renderer=` or the
|
|
73
|
+
* GPU-error banner's "disable GPU" button. This is the question every consumer
|
|
74
|
+
* asks: the About widget's "Graphics:" line, the stack-trace dialog's
|
|
75
|
+
* environment block, the analytics field.
|
|
74
76
|
*
|
|
75
|
-
* The pin is checked
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
77
|
+
* The Canvas2D pin is checked ahead of the record because the banner's
|
|
78
|
+
* "disable GPU" sets it before the displays rebuild on the new rung. The other
|
|
79
|
+
* pins win outright in the prediction, because **a pin never falls through to
|
|
80
|
+
* the next rung** — `createGpuHal` throws instead, on the reasoning that a
|
|
81
|
+
* pinned renderer which silently substitutes another makes any comparison
|
|
82
|
+
* against it wrong. So the pinned rung *is* the answer, and a pin that cannot
|
|
83
|
+
* be honored surfaces as a `renderError` rather than as a different string
|
|
84
|
+
* here. The one case the prediction cannot resolve from capabilities alone
|
|
85
|
+
* (`?renderer=webgl` on a WebGPU machine, where the WebGL2 probe was skipped)
|
|
86
|
+
* is also the case where the pin decides on its own.
|
|
84
87
|
*
|
|
85
88
|
* **`softwareWebgl` skips the WebGL2 rung here because it skips it there**, and
|
|
86
89
|
* only where the ladder skips it — unpinned. Leaving it out is not a rounding
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getGpuOverride, isGpuRenderingDisabled } from './gpuDevice.js';
|
|
1
|
+
import { getBuiltRenderer, getGpuOverride, isGpuRenderingDisabled, } from './gpuDevice.js';
|
|
2
2
|
// Substrings of UNMASKED_RENDERER_WEBGL that mean "no GPU is involved". Kept
|
|
3
3
|
// conservative and specific — a wrong `true` here would describe a real GPU as
|
|
4
4
|
// software, and the reports built from it are what a later ladder decision would
|
|
@@ -111,22 +111,25 @@ export function getGraphicsCapabilities() {
|
|
|
111
111
|
return capabilities;
|
|
112
112
|
}
|
|
113
113
|
/**
|
|
114
|
-
* Which rung
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* *and* the page-wide pin from `?renderer=` or the
|
|
119
|
-
* GPU" button.
|
|
114
|
+
* Which rung draws. Once `createGpuHal` has built one on this page, the rung it
|
|
115
|
+
* returned is the answer — the adapter probe says WebGPU where
|
|
116
|
+
* `WebGPUHal.create` then throws and the ladder lands on WebGL2. Before any
|
|
117
|
+
* backend exists it is the ladder read as an answer rather than run, from what
|
|
118
|
+
* the machine can do *and* the page-wide pin from `?renderer=` or the
|
|
119
|
+
* GPU-error banner's "disable GPU" button. This is the question every consumer
|
|
120
|
+
* asks: the About widget's "Graphics:" line, the stack-trace dialog's
|
|
121
|
+
* environment block, the analytics field.
|
|
120
122
|
*
|
|
121
|
-
* The pin is checked
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
123
|
+
* The Canvas2D pin is checked ahead of the record because the banner's
|
|
124
|
+
* "disable GPU" sets it before the displays rebuild on the new rung. The other
|
|
125
|
+
* pins win outright in the prediction, because **a pin never falls through to
|
|
126
|
+
* the next rung** — `createGpuHal` throws instead, on the reasoning that a
|
|
127
|
+
* pinned renderer which silently substitutes another makes any comparison
|
|
128
|
+
* against it wrong. So the pinned rung *is* the answer, and a pin that cannot
|
|
129
|
+
* be honored surfaces as a `renderError` rather than as a different string
|
|
130
|
+
* here. The one case the prediction cannot resolve from capabilities alone
|
|
131
|
+
* (`?renderer=webgl` on a WebGPU machine, where the WebGL2 probe was skipped)
|
|
132
|
+
* is also the case where the pin decides on its own.
|
|
130
133
|
*
|
|
131
134
|
* **`softwareWebgl` skips the WebGL2 rung here because it skips it there**, and
|
|
132
135
|
* only where the ladder skips it — unpinned. Leaving it out is not a rounding
|
|
@@ -145,6 +148,10 @@ export function effectiveRenderer(c) {
|
|
|
145
148
|
if (isGpuRenderingDisabled()) {
|
|
146
149
|
return 'Canvas2D';
|
|
147
150
|
}
|
|
151
|
+
const built = getBuiltRenderer();
|
|
152
|
+
if (built) {
|
|
153
|
+
return built;
|
|
154
|
+
}
|
|
148
155
|
const override = getGpuOverride();
|
|
149
156
|
if (override === 'webgl') {
|
|
150
157
|
return 'WebGL2';
|
package/esm/hal/createHal.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { GpuHal, PipelineDescriptor, SampleCount } from './types.ts';
|
|
2
2
|
export interface GpuHalOptions {
|
|
3
3
|
passes: PipelineDescriptor[];
|
|
4
|
-
uniformByteSize: number;
|
|
5
4
|
/**
|
|
6
5
|
* Reaches the WebGPU rung only. The WebGL2 rung draws to the default
|
|
7
6
|
* framebuffer with `antialias: true`, so its multisampling is the browser's
|
|
@@ -19,4 +18,4 @@ export interface GpuHalOptions {
|
|
|
19
18
|
*/
|
|
20
19
|
failures?: unknown[];
|
|
21
20
|
}
|
|
22
|
-
export declare function createGpuHal(canvas: HTMLCanvasElement,
|
|
21
|
+
export declare function createGpuHal(canvas: HTMLCanvasElement, options: GpuHalOptions): Promise<GpuHal | null>;
|
package/esm/hal/createHal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getGpuOverride, isGpuRenderingDisabled } from '../gpuDevice.js';
|
|
1
|
+
import { getGpuOverride, isGpuRenderingDisabled, setBuiltRenderer, } from '../gpuDevice.js';
|
|
2
2
|
import { getGraphicsCapabilities } from '../graphicsCapabilities.js';
|
|
3
3
|
import { WebGL2Hal } from './webgl2Hal.js';
|
|
4
4
|
import { WebGPUHal } from './webgpuHal.js';
|
|
@@ -17,14 +17,24 @@ function warnSoftwareRasterizerOnce(glRenderer) {
|
|
|
17
17
|
// Ladder: WebGPU → WebGL2 → Canvas2D (null). The `?renderer=` URL param pins it
|
|
18
18
|
// to a single rung for debugging — `webgpu`, `webgl`, or `canvas2d`/`canvas` —
|
|
19
19
|
// and a pin never falls through to the next rung. See GPU_OVERRIDES.
|
|
20
|
-
|
|
20
|
+
//
|
|
21
|
+
// Records the rung it returned on the page-wide cell, which is how
|
|
22
|
+
// `effectiveRenderer` gets to report what drew rather than what the adapter
|
|
23
|
+
// probe predicted — the two part when `WebGPUHal.create` throws and the ladder
|
|
24
|
+
// lands on WebGL2.
|
|
25
|
+
export async function createGpuHal(canvas, options) {
|
|
26
|
+
const hal = await climbLadder(canvas, options);
|
|
27
|
+
setBuiltRenderer(hal === null ? 'Canvas2D' : hal instanceof WebGPUHal ? 'WebGPU' : 'WebGL2');
|
|
28
|
+
return hal;
|
|
29
|
+
}
|
|
30
|
+
async function climbLadder(canvas, { passes, sampleCount, failures }) {
|
|
21
31
|
if (isGpuRenderingDisabled()) {
|
|
22
32
|
return null;
|
|
23
33
|
}
|
|
24
34
|
const override = getGpuOverride();
|
|
25
35
|
if (override !== 'webgl') {
|
|
26
36
|
try {
|
|
27
|
-
const webgpu = await WebGPUHal.create(canvas, passes,
|
|
37
|
+
const webgpu = await WebGPUHal.create(canvas, passes, sampleCount);
|
|
28
38
|
if (webgpu) {
|
|
29
39
|
return webgpu;
|
|
30
40
|
}
|
|
@@ -81,7 +91,7 @@ export async function createGpuHal(canvas, { passes, uniformByteSize, sampleCoun
|
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
try {
|
|
84
|
-
return new WebGL2Hal(canvas, passes
|
|
94
|
+
return new WebGL2Hal(canvas, passes);
|
|
85
95
|
}
|
|
86
96
|
catch (e) {
|
|
87
97
|
console.warn('[GPU] WebGL2 unavailable, falling back to Canvas2D:', e);
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* is the one pipeline input the descriptor cannot express, so it is the inner
|
|
22
22
|
* key; see the `pipelines` field.
|
|
23
23
|
*
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
24
|
+
* The descriptor's `uniformByteSize` never reaches the pipeline, so it cannot
|
|
25
|
+
* make two HALs disagree here:
|
|
26
26
|
* `createUniformOnlyBindGroupLayout` declares no `minBindingSize`, so the
|
|
27
27
|
* layout — and thus the pipeline built against it — is the same whatever a
|
|
28
28
|
* display's `uniformByteSize` is. The size reaches the GPU through the bind
|
package/esm/hal/gpuHalBase.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export declare abstract class GpuHalBase<Buf extends {
|
|
|
31
31
|
protected descriptors: Map<string, PipelineDescriptor>;
|
|
32
32
|
protected regions: RegionRegistry<Buf>;
|
|
33
33
|
protected oom: OomReporter;
|
|
34
|
+
readonly uniformByteSize: number;
|
|
34
35
|
protected disposed: boolean;
|
|
35
36
|
constructor(descriptors: PipelineDescriptor[], halName: string);
|
|
36
37
|
protected abstract limits(): HalLimits;
|
|
@@ -49,7 +50,6 @@ export declare abstract class GpuHalBase<Buf extends {
|
|
|
49
50
|
getBufferCount(regionKey: number, passId: string): number;
|
|
50
51
|
deleteBuffer(regionKey: number, passId: string): void;
|
|
51
52
|
deleteRegion(regionKey: number): void;
|
|
52
|
-
pruneRegions(active: Iterable<number>): void;
|
|
53
53
|
uploadTexture(passId: string, data: Uint8Array, width: number, height: number): void;
|
|
54
54
|
dispose(): void;
|
|
55
55
|
}
|
package/esm/hal/gpuHalBase.js
CHANGED
|
@@ -19,11 +19,13 @@ export class GpuHalBase {
|
|
|
19
19
|
descriptors;
|
|
20
20
|
regions;
|
|
21
21
|
oom;
|
|
22
|
+
uniformByteSize;
|
|
22
23
|
// Guards dispose() against double invocation (pagehide + React cleanup can
|
|
23
24
|
// both fire).
|
|
24
25
|
disposed = false;
|
|
25
26
|
constructor(descriptors, halName) {
|
|
26
27
|
this.descriptors = new Map(descriptors.map(d => [d.id, d]));
|
|
28
|
+
this.uniformByteSize = Math.max(0, ...descriptors.map(d => d.uniformByteSize));
|
|
27
29
|
this.oom = new OomReporter(halName);
|
|
28
30
|
this.regions = new RegionRegistry(buf => {
|
|
29
31
|
this.destroyBuffer(buf);
|
|
@@ -55,9 +57,6 @@ export class GpuHalBase {
|
|
|
55
57
|
deleteRegion(regionKey) {
|
|
56
58
|
this.regions.deleteRegion(regionKey);
|
|
57
59
|
}
|
|
58
|
-
pruneRegions(active) {
|
|
59
|
-
this.regions.prune(active);
|
|
60
|
-
}
|
|
61
60
|
uploadTexture(passId, data, width, height) {
|
|
62
61
|
// Read the binding off the descriptor, so a pass with no texture is
|
|
63
62
|
// answered without touching the backend's pipeline state at all.
|
package/esm/hal/mockHal.d.ts
CHANGED
|
@@ -102,7 +102,6 @@ export declare class MockHal extends GpuHalBase<MockBuffer> implements GpuHal {
|
|
|
102
102
|
uploadBuffer(regionKey: number, passId: string, data: ArrayBuffer | ArrayBufferView, count: number): void;
|
|
103
103
|
deleteBuffer(regionKey: number, passId: string): void;
|
|
104
104
|
deleteRegion(regionKey: number): void;
|
|
105
|
-
pruneRegions(active: Iterable<number>): void;
|
|
106
105
|
private noteReplaced;
|
|
107
106
|
private noteRegionReplaced;
|
|
108
107
|
writeUniforms(data: ArrayBuffer): void;
|
package/esm/hal/mockHal.js
CHANGED
|
@@ -119,16 +119,6 @@ export class MockHal extends GpuHalBase {
|
|
|
119
119
|
this.noteRegionReplaced(regionKey);
|
|
120
120
|
super.deleteRegion(regionKey);
|
|
121
121
|
}
|
|
122
|
-
pruneRegions(active) {
|
|
123
|
-
const activeSet = new Set(active);
|
|
124
|
-
this.record('pruneRegions', [...activeSet]);
|
|
125
|
-
for (const regionKey of this.drawnThisFrame.keys()) {
|
|
126
|
-
if (!activeSet.has(regionKey)) {
|
|
127
|
-
this.noteRegionReplaced(regionKey);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
super.pruneRegions(activeSet);
|
|
131
|
-
}
|
|
132
122
|
noteReplaced(regionKey, passId) {
|
|
133
123
|
if (this.frameOpen && this.drawnThisFrame.get(regionKey)?.has(passId)) {
|
|
134
124
|
this.replacedWhileDrawnLog.push(`${regionKey}:${passId}`);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
// Per-(region, pass) buffer registry shared by WebGL2Hal and WebGPUHal
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// near-identical deleteBuffer/deleteRegion/deleteAll/prune/getOrCreate
|
|
5
|
-
// implementations; centralizing avoids drift when one side's lifecycle is
|
|
6
|
-
// tweaked and the other isn't. Contract pinned by regionRegistry.test.ts.
|
|
1
|
+
// Per-(region, pass) buffer registry shared by WebGL2Hal and WebGPUHal: the
|
|
2
|
+
// nested map plus destroy semantics, so each HAL supplies only its leaf-buffer
|
|
3
|
+
// type and destroy hook.
|
|
7
4
|
export class RegionRegistry {
|
|
8
5
|
destroy;
|
|
9
6
|
regions = new Map();
|
|
@@ -54,13 +51,4 @@ export class RegionRegistry {
|
|
|
54
51
|
}
|
|
55
52
|
this.regions.clear();
|
|
56
53
|
}
|
|
57
|
-
// Delete every region whose key is NOT in `active`.
|
|
58
|
-
prune(active) {
|
|
59
|
-
const activeSet = new Set(active);
|
|
60
|
-
for (const regionKey of this.regions.keys()) {
|
|
61
|
-
if (!activeSet.has(regionKey)) {
|
|
62
|
-
this.deleteRegion(regionKey);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
54
|
}
|
package/esm/hal/types.d.ts
CHANGED
|
@@ -80,15 +80,20 @@ export interface PipelineDescriptor {
|
|
|
80
80
|
glslVertex: string;
|
|
81
81
|
glslFragment: string;
|
|
82
82
|
instanceStride: number;
|
|
83
|
+
/** Bytes of the uniform block the shader binds; the HAL's slot is the largest over its passes. */
|
|
84
|
+
uniformByteSize: number;
|
|
83
85
|
verticesPerInstance: number;
|
|
84
86
|
blend: boolean;
|
|
85
87
|
blendState?: BlendState;
|
|
86
88
|
vertexAttributes: readonly VertexAttributeLayout[];
|
|
87
89
|
topology?: 'triangle-list' | 'triangle-strip' | 'line-list';
|
|
90
|
+
coverage?: 'analytic';
|
|
88
91
|
textures?: readonly [TextureBinding, ...TextureBinding[]];
|
|
89
92
|
bindings?: readonly ShaderBinding[];
|
|
90
93
|
}
|
|
91
94
|
export interface GpuHal {
|
|
95
|
+
/** The uniform slot size, the largest block any registered pass binds. */
|
|
96
|
+
readonly uniformByteSize: number;
|
|
92
97
|
/**
|
|
93
98
|
* Size the canvas, and report the scale its backing store actually got.
|
|
94
99
|
* Callers derive every device-px rect from THAT rather than from `getDpr()`:
|
|
@@ -101,7 +106,6 @@ export interface GpuHal {
|
|
|
101
106
|
getBufferCount(regionKey: number, passId: string): number;
|
|
102
107
|
deleteBuffer(regionKey: number, passId: string): void;
|
|
103
108
|
deleteRegion(regionKey: number): void;
|
|
104
|
-
pruneRegions(active: Iterable<number>): void;
|
|
105
109
|
uploadTexture(passId: string, data: Uint8Array, width: number, height: number): void;
|
|
106
110
|
writeUniforms(data: ArrayBuffer): void;
|
|
107
111
|
beginFrame(clearR: number, clearG: number, clearB: number, clearA?: number): void;
|
package/esm/hal/webgl2Hal.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare class WebGL2Hal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
17
17
|
private contextLostListener;
|
|
18
18
|
private contextRestoredListener;
|
|
19
19
|
private checkGlError;
|
|
20
|
-
constructor(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[]
|
|
20
|
+
constructor(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[]);
|
|
21
21
|
protected limits(): {
|
|
22
22
|
maxBufferBytes: number;
|
|
23
23
|
maxTextureDimensionPx: number;
|
package/esm/hal/webgl2Hal.js
CHANGED
|
@@ -156,7 +156,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
156
156
|
console.error(`[WebGL2Hal] GL error at "${label}": ${glErrorName(this.gl, err)}`);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
constructor(canvas, descriptors
|
|
159
|
+
constructor(canvas, descriptors) {
|
|
160
160
|
super(descriptors, 'WebGL2Hal');
|
|
161
161
|
this.canvas = canvas;
|
|
162
162
|
this.debug = debugEnabled();
|
|
@@ -202,7 +202,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
202
202
|
this.maxTextureDim = Number(gl.getParameter(gl.MAX_TEXTURE_SIZE));
|
|
203
203
|
this.ubo = gl.createBuffer();
|
|
204
204
|
gl.bindBuffer(gl.UNIFORM_BUFFER, this.ubo);
|
|
205
|
-
gl.bufferData(gl.UNIFORM_BUFFER, uniformByteSize, gl.DYNAMIC_DRAW);
|
|
205
|
+
gl.bufferData(gl.UNIFORM_BUFFER, this.uniformByteSize, gl.DYNAMIC_DRAW);
|
|
206
206
|
this.passes = new Map();
|
|
207
207
|
// Programs are built on first use (see `getPass`), not here: a renderer
|
|
208
208
|
// declares every pass it could ever draw — alignments alone declares 21,
|
package/esm/hal/webgpuHal.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export declare class WebGPUHal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
66
66
|
* configured context behind with nothing to release it.
|
|
67
67
|
*/
|
|
68
68
|
private configureContext;
|
|
69
|
-
static create(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[],
|
|
69
|
+
static create(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[], sampleCount: SampleCount): Promise<WebGPUHal | null>;
|
|
70
70
|
resize(width: number, height: number): import("./types.ts").CanvasScale;
|
|
71
71
|
private recreateMsaaTexture;
|
|
72
72
|
/**
|
|
@@ -92,7 +92,7 @@ export declare class WebGPUHal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
92
92
|
* {@link pendingDestroy} for why the frame decides when.
|
|
93
93
|
*
|
|
94
94
|
* Every buffer release routes here through the `RegionRegistry` destroy hook,
|
|
95
|
-
* so `uploadBuffer`, `deleteBuffer`, `deleteRegion
|
|
95
|
+
* so `uploadBuffer`, `deleteBuffer`, `deleteRegion` and
|
|
96
96
|
* `dispose` are all covered without any of them knowing a frame is open.
|
|
97
97
|
*/
|
|
98
98
|
private destroyWhenIdle;
|
package/esm/hal/webgpuHal.js
CHANGED
|
@@ -15,11 +15,13 @@ class ShaderCompileError extends Error {
|
|
|
15
15
|
// aligned slot in the uniform ring buffer.
|
|
16
16
|
//
|
|
17
17
|
// The ring is allocated eagerly at this many slots, as a GPU buffer AND a CPU
|
|
18
|
-
// staging array, whether a renderer writes 4 slots or 1900 — so the cost is
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
18
|
+
// staging array, whether a renderer writes 4 slots or 1900 — so the cost is per
|
|
19
|
+
// display, and both terms are in it. A display whose uniform fits the 256-byte
|
|
20
|
+
// minimum alignment pays 512 KB each side; alignments' 640-byte uniform aligns
|
|
21
|
+
// to 768, so 1.5 MiB each side, per track. A frame is measured using 2-4 of
|
|
22
|
+
// those slots, which is what makes the count above the oversized term rather
|
|
23
|
+
// than any struct:
|
|
24
|
+
// agent-docs/ideas/size-the-uniform-ring-to-its-measured-occupancy.md.
|
|
23
25
|
//
|
|
24
26
|
// Exhausting it does not throw: the write is dropped and its draws render
|
|
25
27
|
// against the previous batch's uniforms, which is wrong data rather than stale
|
|
@@ -196,7 +198,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
196
198
|
// than once per frame, and a reconfigure that failed is not attempted again.
|
|
197
199
|
warnedSwapChainLoss = false;
|
|
198
200
|
swapChainUnrecoverable = false;
|
|
199
|
-
constructor(device, canvas, context, descriptors,
|
|
201
|
+
constructor(device, canvas, context, descriptors, sampleCount, pipelines, layouts) {
|
|
200
202
|
super(descriptors, 'WebGPUHal');
|
|
201
203
|
this.device = device;
|
|
202
204
|
this.canvas = canvas;
|
|
@@ -206,7 +208,8 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
206
208
|
this.sampleCount = sampleCount;
|
|
207
209
|
// Align uniform slots to device requirements for dynamic offsets
|
|
208
210
|
const alignment = device.limits.minUniformBufferOffsetAlignment;
|
|
209
|
-
this.alignedUniformSize =
|
|
211
|
+
this.alignedUniformSize =
|
|
212
|
+
Math.ceil(this.uniformByteSize / alignment) * alignment;
|
|
210
213
|
const ringSize = MAX_UNIFORM_SLOTS * this.alignedUniformSize;
|
|
211
214
|
this.uniformRingBuffer = device.createBuffer({
|
|
212
215
|
size: ringSize,
|
|
@@ -245,7 +248,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
245
248
|
});
|
|
246
249
|
noteCanvasConfigured(this.canvas, this);
|
|
247
250
|
}
|
|
248
|
-
static async create(canvas, descriptors,
|
|
251
|
+
static async create(canvas, descriptors, sampleCount) {
|
|
249
252
|
const device = await getGpuDevice();
|
|
250
253
|
if (!device) {
|
|
251
254
|
return null;
|
|
@@ -266,7 +269,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
266
269
|
throw canvasContextError(canvas, 'webgpu');
|
|
267
270
|
}
|
|
268
271
|
noteCanvasContext(canvas, 'webgpu');
|
|
269
|
-
return new WebGPUHal(device, canvas, context, descriptors,
|
|
272
|
+
return new WebGPUHal(device, canvas, context, descriptors, sampleCount, pipelines, layouts);
|
|
270
273
|
}
|
|
271
274
|
resize(width, height) {
|
|
272
275
|
const { changed, scale } = syncCanvasSize(this.canvas, width, height);
|
|
@@ -352,7 +355,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
352
355
|
* {@link pendingDestroy} for why the frame decides when.
|
|
353
356
|
*
|
|
354
357
|
* Every buffer release routes here through the `RegionRegistry` destroy hook,
|
|
355
|
-
* so `uploadBuffer`, `deleteBuffer`, `deleteRegion
|
|
358
|
+
* so `uploadBuffer`, `deleteBuffer`, `deleteRegion` and
|
|
356
359
|
* `dispose` are all covered without any of them knowing a frame is open.
|
|
357
360
|
*/
|
|
358
361
|
destroyWhenIdle(resource) {
|
package/esm/installUpload.d.ts
CHANGED
|
@@ -74,7 +74,10 @@ export declare function oneCell<K, T>(key: K, value: T | undefined | null): Read
|
|
|
74
74
|
*
|
|
75
75
|
* **Calling again only swaps the backend.** Displays wire this from
|
|
76
76
|
* `startRenderingBackend`, which fires again on every context-loss recovery;
|
|
77
|
-
* the diff and the encode
|
|
77
|
+
* the diff and the encode memo live in the setup thunk and survive it.
|
|
78
|
+
*
|
|
79
|
+
* `encode` runs through `createEncodeMemo`. A display that needs the encoded
|
|
80
|
+
* map itself holds that memo in a `.views` closure and hands identity cells.
|
|
78
81
|
*
|
|
79
82
|
* @see installUpload.test.ts for the upload, encode and release counts.
|
|
80
83
|
* @see ADR-078 for why this is one autorun and a diff.
|
package/esm/installUpload.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getType } from '@jbrowse/mobx-state-tree';
|
|
2
|
-
import {
|
|
2
|
+
import { contractReportsOn, reportContractViolation, } from './contractReports.js';
|
|
3
|
+
import { createEncodeMemo } from './encodeMemo.js';
|
|
3
4
|
import { createMapUploadSync } from './mapUploadSync.js';
|
|
4
5
|
import { isCheckedAtTheStore } from './regionDataMap.js';
|
|
5
6
|
/**
|
|
@@ -29,7 +30,7 @@ function checkPayloads(self, cells) {
|
|
|
29
30
|
}
|
|
30
31
|
if (!named.has(key)) {
|
|
31
32
|
named.add(key);
|
|
32
|
-
|
|
33
|
+
reportContractViolation('display', `${getType(self).name}: cell ${String(key)} ` +
|
|
33
34
|
`handed to installUpload is \`${String(value)}\`. A fetch answers ` +
|
|
34
35
|
'a payload or nothing; leave the key out until it has one.');
|
|
35
36
|
}
|
|
@@ -46,49 +47,17 @@ export function installUpload(self, backend, { cells, inputs, encode, render })
|
|
|
46
47
|
b.release(key);
|
|
47
48
|
},
|
|
48
49
|
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
upload: b => {
|
|
53
|
-
const current = own();
|
|
54
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
55
|
-
checkPayloads(self, current);
|
|
56
|
-
}
|
|
57
|
-
return sync(b, current);
|
|
58
|
-
},
|
|
59
|
-
render: b => render(b, own()),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
const encoded = new Map();
|
|
63
|
-
const encodedFrom = new Map();
|
|
64
|
-
const props = inputs && computed(inputs);
|
|
65
|
-
let lastProps;
|
|
50
|
+
const own = encode
|
|
51
|
+
? createEncodeMemo(cells, inputs, encode)
|
|
52
|
+
: cells;
|
|
66
53
|
return {
|
|
67
54
|
upload: b => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
checkPayloads(self, current);
|
|
71
|
-
}
|
|
72
|
-
const p = props ? props.get() : undefined;
|
|
73
|
-
if (p !== lastProps) {
|
|
74
|
-
lastProps = p;
|
|
75
|
-
encodedFrom.clear();
|
|
76
|
-
}
|
|
77
|
-
for (const [key, data] of current) {
|
|
78
|
-
if (encodedFrom.get(key) !== data) {
|
|
79
|
-
encoded.set(key, encode(data, p, key));
|
|
80
|
-
encodedFrom.set(key, data);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
for (const key of encoded.keys()) {
|
|
84
|
-
if (!current.has(key)) {
|
|
85
|
-
encoded.delete(key);
|
|
86
|
-
encodedFrom.delete(key);
|
|
87
|
-
}
|
|
55
|
+
if (contractReportsOn()) {
|
|
56
|
+
checkPayloads(self, cells());
|
|
88
57
|
}
|
|
89
|
-
return sync(b,
|
|
58
|
+
return sync(b, own());
|
|
90
59
|
},
|
|
91
|
-
render: b => render(b,
|
|
60
|
+
render: b => render(b, own()),
|
|
92
61
|
};
|
|
93
62
|
});
|
|
94
63
|
}
|