@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
|
@@ -95,11 +95,11 @@ export declare function RenderLifecycleMixin(): import("@jbrowse/mobx-state-tree
|
|
|
95
95
|
* never called and `canvasDrawn` can never flip).
|
|
96
96
|
*
|
|
97
97
|
* Lives here, beside `canvasDrawn`, because every consumer of "has this
|
|
98
|
-
* display painted" needs the pair
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
98
|
+
* display painted" needs the pair. Both LGV fetch foundations fill it
|
|
99
|
+
* with `!fetchInert`, so a display there states its placeholder once,
|
|
100
|
+
* through that hook; the raw default reaches only a display composing
|
|
101
|
+
* this mixin outside them. See `painted` below for the reader that was
|
|
102
|
+
* missed while the two were separate overrides.
|
|
103
103
|
*/
|
|
104
104
|
readonly rendersCanvas: boolean;
|
|
105
105
|
/**
|
|
@@ -119,6 +119,23 @@ export declare function RenderLifecycleMixin(): import("@jbrowse/mobx-state-tree
|
|
|
119
119
|
* and the two families compose the two mixins in opposite orders.
|
|
120
120
|
*/
|
|
121
121
|
readonly paintInert: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* #getter
|
|
124
|
+
* Overridable hook (default false): what is on the canvas was painted
|
|
125
|
+
* from data a later change has made wrong, so `painted` below should
|
|
126
|
+
* answer *pending* until the repaint lands even though `canvasDrawn` is
|
|
127
|
+
* still true. The per-region fetch foundation fills it with
|
|
128
|
+
* `staleSettingsDrawn` — held data drawn under settings that have since
|
|
129
|
+
* moved — which is the state `clearAllRpcData` used to express by
|
|
130
|
+
* resetting `canvasDrawn` on every settings change, blanking the display
|
|
131
|
+
* to do it. A capture waiting on `data-display-drawn` then waits for the
|
|
132
|
+
* refetch rather than snapshotting the previous setting's pixels.
|
|
133
|
+
*
|
|
134
|
+
* A hook and not a reset of the flag, because the flag is re-marked by
|
|
135
|
+
* any redraw — a pan between the settings change and the refetch would
|
|
136
|
+
* report the stale canvas drawn — and a derivation cannot be raced.
|
|
137
|
+
*/
|
|
138
|
+
readonly paintSuperseded: boolean;
|
|
122
139
|
} & {
|
|
123
140
|
/**
|
|
124
141
|
* #getter
|
|
@@ -140,7 +157,9 @@ export declare function RenderLifecycleMixin(): import("@jbrowse/mobx-state-tree
|
|
|
140
157
|
*
|
|
141
158
|
* `paintInert` is the third term and the same argument once more, for the
|
|
142
159
|
* state where a display *would* paint a canvas and never gets to — a fetch
|
|
143
|
-
* that failed before first paint.
|
|
160
|
+
* that failed before first paint. `paintSuperseded` is the fourth, and
|
|
161
|
+
* the one that subtracts: a canvas painted from data a settings change
|
|
162
|
+
* has made wrong is drawn and not finished. See both hooks.
|
|
144
163
|
*/
|
|
145
164
|
readonly painted: boolean;
|
|
146
165
|
} & {
|
|
@@ -104,11 +104,11 @@ export function RenderLifecycleMixin() {
|
|
|
104
104
|
* never called and `canvasDrawn` can never flip).
|
|
105
105
|
*
|
|
106
106
|
* Lives here, beside `canvasDrawn`, because every consumer of "has this
|
|
107
|
-
* display painted" needs the pair
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
107
|
+
* display painted" needs the pair. Both LGV fetch foundations fill it
|
|
108
|
+
* with `!fetchInert`, so a display there states its placeholder once,
|
|
109
|
+
* through that hook; the raw default reaches only a display composing
|
|
110
|
+
* this mixin outside them. See `painted` below for the reader that was
|
|
111
|
+
* missed while the two were separate overrides.
|
|
112
112
|
*/
|
|
113
113
|
get rendersCanvas() {
|
|
114
114
|
return true;
|
|
@@ -132,6 +132,25 @@ export function RenderLifecycleMixin() {
|
|
|
132
132
|
get paintInert() {
|
|
133
133
|
return false;
|
|
134
134
|
},
|
|
135
|
+
/**
|
|
136
|
+
* #getter
|
|
137
|
+
* Overridable hook (default false): what is on the canvas was painted
|
|
138
|
+
* from data a later change has made wrong, so `painted` below should
|
|
139
|
+
* answer *pending* until the repaint lands even though `canvasDrawn` is
|
|
140
|
+
* still true. The per-region fetch foundation fills it with
|
|
141
|
+
* `staleSettingsDrawn` — held data drawn under settings that have since
|
|
142
|
+
* moved — which is the state `clearAllRpcData` used to express by
|
|
143
|
+
* resetting `canvasDrawn` on every settings change, blanking the display
|
|
144
|
+
* to do it. A capture waiting on `data-display-drawn` then waits for the
|
|
145
|
+
* refetch rather than snapshotting the previous setting's pixels.
|
|
146
|
+
*
|
|
147
|
+
* A hook and not a reset of the flag, because the flag is re-marked by
|
|
148
|
+
* any redraw — a pan between the settings change and the refetch would
|
|
149
|
+
* report the stale canvas drawn — and a derivation cannot be raced.
|
|
150
|
+
*/
|
|
151
|
+
get paintSuperseded() {
|
|
152
|
+
return false;
|
|
153
|
+
},
|
|
135
154
|
}))
|
|
136
155
|
.views(self => ({
|
|
137
156
|
/**
|
|
@@ -154,10 +173,14 @@ export function RenderLifecycleMixin() {
|
|
|
154
173
|
*
|
|
155
174
|
* `paintInert` is the third term and the same argument once more, for the
|
|
156
175
|
* state where a display *would* paint a canvas and never gets to — a fetch
|
|
157
|
-
* that failed before first paint.
|
|
176
|
+
* that failed before first paint. `paintSuperseded` is the fourth, and
|
|
177
|
+
* the one that subtracts: a canvas painted from data a settings change
|
|
178
|
+
* has made wrong is drawn and not finished. See both hooks.
|
|
158
179
|
*/
|
|
159
180
|
get painted() {
|
|
160
|
-
return self.canvasDrawn
|
|
181
|
+
return ((self.canvasDrawn && !self.paintSuperseded) ||
|
|
182
|
+
!self.rendersCanvas ||
|
|
183
|
+
self.paintInert);
|
|
161
184
|
},
|
|
162
185
|
}))
|
|
163
186
|
.actions(self => ({
|
package/esm/canvas2dUtils.d.ts
CHANGED
|
@@ -203,6 +203,31 @@ export declare function strokeRectInside(ctx: {
|
|
|
203
203
|
* Scalar in, scalar out: runs per feature per frame, so it must not allocate.
|
|
204
204
|
*/
|
|
205
205
|
export declare function spanLeft(x1: number, x2: number, width: number): number;
|
|
206
|
+
/**
|
|
207
|
+
* The `fillRect` x and width a genomic span paints as, given a bare
|
|
208
|
+
* `makeBpMapper`: `spanLeft`'s pivot and the min-width floor that goes with it,
|
|
209
|
+
* as one value a painter and its hit box can both read.
|
|
210
|
+
*
|
|
211
|
+
* `minWidth` defaults to 0, which makes this plain screen ordering — the
|
|
212
|
+
* leftmost projected edge and the unsigned distance. Pass a floor and the span
|
|
213
|
+
* grows away from `startBp`, which is the reversed-block rule `spanLeft` owns:
|
|
214
|
+
* anchoring the leftmost edge instead slides a sub-pixel mark by up to the floor
|
|
215
|
+
* on a flipped region and never on a forward one.
|
|
216
|
+
*
|
|
217
|
+
* Reach for this over `spanLeft` wherever the *numbers* are wanted rather than a
|
|
218
|
+
* fill — a hover box, an SVG rect, a cull test, an overlay div — which is where
|
|
219
|
+
* the pivot and the floor were being restated separately. The multi-row painter
|
|
220
|
+
* and the hover box it has to line up with each spelled out both.
|
|
221
|
+
* `fillBpSpan` is the same span when a `fillRect` is the only consumer.
|
|
222
|
+
*
|
|
223
|
+
* Not for a per-instance paint loop: this allocates a result per call, so the
|
|
224
|
+
* span mark's painter and wiggle's two bar loops take `spanLeft` and the two
|
|
225
|
+
* numbers inline, and stay the last hand-written copies of this arithmetic.
|
|
226
|
+
*/
|
|
227
|
+
export declare function spanRect(toX: (bp: number) => number, startBp: number, endBp: number, minWidth?: number): {
|
|
228
|
+
left: number;
|
|
229
|
+
width: number;
|
|
230
|
+
};
|
|
206
231
|
export declare function makeRampFillStyleLut(ramp: Uint8Array): (t: number) => string;
|
|
207
232
|
export declare function bpToScreenPx(absBp: number, regionStart: number, regionEnd: number, screenStartPx: number, screenEndPx: number, reversed?: boolean): number;
|
|
208
233
|
export declare function makeBpMapper(bounds: BpRegionBounds): (bp: number) => number;
|
package/esm/canvas2dUtils.js
CHANGED
|
@@ -334,6 +334,33 @@ export function strokeRectInside(ctx, x, y, w, h, lineWidth = 1) {
|
|
|
334
334
|
export function spanLeft(x1, x2, width) {
|
|
335
335
|
return x2 < x1 ? x1 - width : x1;
|
|
336
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* The `fillRect` x and width a genomic span paints as, given a bare
|
|
339
|
+
* `makeBpMapper`: `spanLeft`'s pivot and the min-width floor that goes with it,
|
|
340
|
+
* as one value a painter and its hit box can both read.
|
|
341
|
+
*
|
|
342
|
+
* `minWidth` defaults to 0, which makes this plain screen ordering — the
|
|
343
|
+
* leftmost projected edge and the unsigned distance. Pass a floor and the span
|
|
344
|
+
* grows away from `startBp`, which is the reversed-block rule `spanLeft` owns:
|
|
345
|
+
* anchoring the leftmost edge instead slides a sub-pixel mark by up to the floor
|
|
346
|
+
* on a flipped region and never on a forward one.
|
|
347
|
+
*
|
|
348
|
+
* Reach for this over `spanLeft` wherever the *numbers* are wanted rather than a
|
|
349
|
+
* fill — a hover box, an SVG rect, a cull test, an overlay div — which is where
|
|
350
|
+
* the pivot and the floor were being restated separately. The multi-row painter
|
|
351
|
+
* and the hover box it has to line up with each spelled out both.
|
|
352
|
+
* `fillBpSpan` is the same span when a `fillRect` is the only consumer.
|
|
353
|
+
*
|
|
354
|
+
* Not for a per-instance paint loop: this allocates a result per call, so the
|
|
355
|
+
* span mark's painter and wiggle's two bar loops take `spanLeft` and the two
|
|
356
|
+
* numbers inline, and stay the last hand-written copies of this arithmetic.
|
|
357
|
+
*/
|
|
358
|
+
export function spanRect(toX, startBp, endBp, minWidth = 0) {
|
|
359
|
+
const x1 = toX(startBp);
|
|
360
|
+
const x2 = toX(endBp);
|
|
361
|
+
const width = Math.max(minWidth, Math.abs(x2 - x1));
|
|
362
|
+
return { left: spanLeft(x1, x2, width), width };
|
|
363
|
+
}
|
|
337
364
|
// Quantize a 0..1 ramp position to a 256-entry index. Shared so the direct
|
|
338
365
|
// lookup and the fillStyle LUT below can't quantize differently — a half-step
|
|
339
366
|
// of drift between them shows up as a one-shade mismatch between a cell's fill
|
package/esm/colorRampLut.d.ts
CHANGED
|
@@ -9,9 +9,9 @@ export declare const COLOR_RAMP_LUT_ENTRIES = 256;
|
|
|
9
9
|
/**
|
|
10
10
|
* Upload a 256-entry RGBA colour-ramp LUT as each named pass's 256×1 texture —
|
|
11
11
|
* the runtime half of the shared ramp mechanism whose shader half is
|
|
12
|
-
* `shaders/colorRampLut.slang`.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* LUT contents.
|
|
12
|
+
* `shaders/colorRampLut.slang`. Called by `createMarkBackend` alone now, for
|
|
13
|
+
* every mark declaring a `texture`: hic, LD's two shader variants and wiggle
|
|
14
|
+
* density all put their ramps behind their samplers through it, each keeping
|
|
15
|
+
* its own scale and its own LUT contents.
|
|
16
16
|
*/
|
|
17
17
|
export declare function uploadColorRampLut(hal: GpuHal, ramp: Uint8Array, passIds: readonly string[]): void;
|
package/esm/colorRampLut.js
CHANGED
|
@@ -9,10 +9,10 @@ export const COLOR_RAMP_LUT_ENTRIES = RAMP_LUT_ENTRIES;
|
|
|
9
9
|
/**
|
|
10
10
|
* Upload a 256-entry RGBA colour-ramp LUT as each named pass's 256×1 texture —
|
|
11
11
|
* the runtime half of the shared ramp mechanism whose shader half is
|
|
12
|
-
* `shaders/colorRampLut.slang`.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* LUT contents.
|
|
12
|
+
* `shaders/colorRampLut.slang`. Called by `createMarkBackend` alone now, for
|
|
13
|
+
* every mark declaring a `texture`: hic, LD's two shader variants and wiggle
|
|
14
|
+
* density all put their ramps behind their samplers through it, each keeping
|
|
15
|
+
* its own scale and its own LUT contents.
|
|
16
16
|
*/
|
|
17
17
|
export function uploadColorRampLut(hal, ramp, passIds) {
|
|
18
18
|
if (ramp.length !== COLOR_RAMP_LUT_ENTRIES * 4) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ShaderBinding } from './hal/index.ts';
|
|
2
|
+
export interface ComputePipelineState {
|
|
3
|
+
device: GPUDevice;
|
|
4
|
+
pipeline: GPUComputePipeline;
|
|
5
|
+
bindGroupLayout: GPUBindGroupLayout;
|
|
6
|
+
bindings: readonly ShaderBinding[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* One compute pipeline per kernel, built on first use against the current
|
|
10
|
+
* device and rebuilt after a device loss. Concurrent first callers share the
|
|
11
|
+
* one async build.
|
|
12
|
+
*/
|
|
13
|
+
export declare function makeComputePipelineCache(code: string, entryPoint: string, bindings: readonly ShaderBinding[]): (device: GPUDevice) => Promise<ComputePipelineState>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/// <reference types="@webgpu/types" />
|
|
2
|
+
import { onDeviceLost } from './gpuDevice.js';
|
|
3
|
+
// Built from the kernel's own reflected binding table rather than restated by
|
|
4
|
+
// the caller, so a mismatch shows up as a validation failure at pipeline
|
|
5
|
+
// creation rather than as a wrong buffer under a right-looking index.
|
|
6
|
+
function makeBindGroupLayout(device, bindings) {
|
|
7
|
+
return device.createBindGroupLayout({
|
|
8
|
+
entries: bindings.map(b => {
|
|
9
|
+
if (b.kind === 'texture' || b.kind === 'sampler') {
|
|
10
|
+
throw new Error(`compute kernel: binding ${b.index} ('${b.name}') is a ${b.kind}, ` +
|
|
11
|
+
`which a buffer-only dispatch does not bind`);
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
binding: b.index,
|
|
15
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
16
|
+
buffer: { type: b.kind },
|
|
17
|
+
};
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One compute pipeline per kernel, built on first use against the current
|
|
23
|
+
* device and rebuilt after a device loss. Concurrent first callers share the
|
|
24
|
+
* one async build.
|
|
25
|
+
*/
|
|
26
|
+
export function makeComputePipelineCache(code, entryPoint, bindings) {
|
|
27
|
+
let state = null;
|
|
28
|
+
let statePromise = null;
|
|
29
|
+
onDeviceLost(() => {
|
|
30
|
+
state = null;
|
|
31
|
+
statePromise = null;
|
|
32
|
+
});
|
|
33
|
+
return async function ensurePipeline(device) {
|
|
34
|
+
if (state?.device === device) {
|
|
35
|
+
return state;
|
|
36
|
+
}
|
|
37
|
+
if (statePromise) {
|
|
38
|
+
return statePromise;
|
|
39
|
+
}
|
|
40
|
+
statePromise = (async () => {
|
|
41
|
+
const module = device.createShaderModule({ code });
|
|
42
|
+
const bindGroupLayout = makeBindGroupLayout(device, bindings);
|
|
43
|
+
const pipeline = await device.createComputePipelineAsync({
|
|
44
|
+
layout: device.createPipelineLayout({
|
|
45
|
+
bindGroupLayouts: [bindGroupLayout],
|
|
46
|
+
}),
|
|
47
|
+
compute: { module, entryPoint },
|
|
48
|
+
});
|
|
49
|
+
state = { device, pipeline, bindGroupLayout, bindings };
|
|
50
|
+
statePromise = null;
|
|
51
|
+
return state;
|
|
52
|
+
})();
|
|
53
|
+
return statePromise;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A contract family — the `<family>` in the `[jbrowse <family> contract]`
|
|
3
|
+
* prefix every contract check in the tree reports under.
|
|
4
|
+
* `config/jest/console.js` matches `\w+`, so a new one reaches the test gate
|
|
5
|
+
* without touching it.
|
|
6
|
+
*/
|
|
7
|
+
export type ContractFamily = 'display' | 'session' | 'figure';
|
|
8
|
+
export interface ContractReport {
|
|
9
|
+
family: ContractFamily;
|
|
10
|
+
message: string;
|
|
11
|
+
/** what armed the channel, so a host can say why it is showing this */
|
|
12
|
+
armedBy: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether the checks should run at all.
|
|
16
|
+
*
|
|
17
|
+
* Read it where a check costs something to run — the `MutationObserver` a live
|
|
18
|
+
* figure installs, the per-frame payload walk in `installUpload`. A check whose
|
|
19
|
+
* cost is a `WeakSet` lookup should run unconditionally and report through
|
|
20
|
+
* {@link reportContractViolation} instead, so that arming the channel late
|
|
21
|
+
* still surfaces what it found.
|
|
22
|
+
*/
|
|
23
|
+
export declare function contractReportsOn(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Turn the channel on in a production build, saying what turned it on.
|
|
26
|
+
*
|
|
27
|
+
* The reason is not decoration: a notification that appears in an app the
|
|
28
|
+
* reader did not build has to answer "why am I seeing this" in its own text, or
|
|
29
|
+
* it reads as the app being broken rather than as a plugin being under
|
|
30
|
+
* development. First caller wins, so the earliest evidence is the one quoted.
|
|
31
|
+
*/
|
|
32
|
+
export declare function enableContractReports(reason: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Where a host puts contract violations that reach a user — a session
|
|
35
|
+
* notification in the apps. Installing one drains whatever was found before the
|
|
36
|
+
* host existed, and the disposer only clears the sink it installed, so a
|
|
37
|
+
* session torn down after its replacement was built does not take the live
|
|
38
|
+
* one's sink with it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function setContractReportSink(fn: (report: ContractReport) => void): () => void;
|
|
41
|
+
/**
|
|
42
|
+
* Report a violation of an ordering contract no type can state.
|
|
43
|
+
*
|
|
44
|
+
* In tree this is the `console.error` it has always been, and the jest gate is
|
|
45
|
+
* what listens. Out of tree it is the only thing that reaches a plugin author
|
|
46
|
+
* at all: their display runs in a production build of somebody's app, which
|
|
47
|
+
* strips nothing here, and a violation surfaces the moment anything arms the
|
|
48
|
+
* channel — a `localStorage` flag, a plugin loaded from localhost, or a site
|
|
49
|
+
* whose config asks for it.
|
|
50
|
+
*
|
|
51
|
+
* `agent-docs/reference/ARCHITECTURAL_LIMITS.md` §"Ordering is the contract" is
|
|
52
|
+
* the register of what reports here.
|
|
53
|
+
*/
|
|
54
|
+
export declare function reportContractViolation(family: ContractFamily, message: string): void;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const DEVELOPER_MODE_KEY = 'jbrowseDeveloperMode';
|
|
2
|
+
// The flag a plugin author can set on any deployed JBrowse without owning it or
|
|
3
|
+
// asking anyone, which is the population this whole channel is for.
|
|
4
|
+
function storageFlag() {
|
|
5
|
+
try {
|
|
6
|
+
return typeof localStorage === 'undefined' ||
|
|
7
|
+
localStorage.getItem(DEVELOPER_MODE_KEY) === null
|
|
8
|
+
? undefined
|
|
9
|
+
: `localStorage.${DEVELOPER_MODE_KEY} is set`;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// storage can be denied outright, and a diagnostic is not the thing to take
|
|
13
|
+
// the app down with
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Module scope for the same reason `numberGrouping` is: a check is called from
|
|
18
|
+
// a mixin factory, an MST action and a React effect that cannot share a
|
|
19
|
+
// closure, and each realm resolves this once at startup rather than through an
|
|
20
|
+
// observable a worker could not see.
|
|
21
|
+
let armedBy = process.env.NODE_ENV === 'production'
|
|
22
|
+
? storageFlag()
|
|
23
|
+
: 'this is a development build';
|
|
24
|
+
let sink;
|
|
25
|
+
const unlogged = [];
|
|
26
|
+
const undelivered = [];
|
|
27
|
+
const PENDING_LIMIT = 20;
|
|
28
|
+
function remember(queue, found) {
|
|
29
|
+
if (queue.length < PENDING_LIMIT) {
|
|
30
|
+
queue.push(found);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether the checks should run at all.
|
|
35
|
+
*
|
|
36
|
+
* Read it where a check costs something to run — the `MutationObserver` a live
|
|
37
|
+
* figure installs, the per-frame payload walk in `installUpload`. A check whose
|
|
38
|
+
* cost is a `WeakSet` lookup should run unconditionally and report through
|
|
39
|
+
* {@link reportContractViolation} instead, so that arming the channel late
|
|
40
|
+
* still surfaces what it found.
|
|
41
|
+
*/
|
|
42
|
+
export function contractReportsOn() {
|
|
43
|
+
return armedBy !== undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Turn the channel on in a production build, saying what turned it on.
|
|
47
|
+
*
|
|
48
|
+
* The reason is not decoration: a notification that appears in an app the
|
|
49
|
+
* reader did not build has to answer "why am I seeing this" in its own text, or
|
|
50
|
+
* it reads as the app being broken rather than as a plugin being under
|
|
51
|
+
* development. First caller wins, so the earliest evidence is the one quoted.
|
|
52
|
+
*/
|
|
53
|
+
export function enableContractReports(reason) {
|
|
54
|
+
armedBy ??= reason;
|
|
55
|
+
for (const found of unlogged.splice(0)) {
|
|
56
|
+
emit(found, armedBy);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Where a host puts contract violations that reach a user — a session
|
|
61
|
+
* notification in the apps. Installing one drains whatever was found before the
|
|
62
|
+
* host existed, and the disposer only clears the sink it installed, so a
|
|
63
|
+
* session torn down after its replacement was built does not take the live
|
|
64
|
+
* one's sink with it.
|
|
65
|
+
*/
|
|
66
|
+
export function setContractReportSink(fn) {
|
|
67
|
+
sink = fn;
|
|
68
|
+
if (armedBy !== undefined) {
|
|
69
|
+
for (const found of undelivered.splice(0)) {
|
|
70
|
+
fn({ ...found, armedBy });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return () => {
|
|
74
|
+
if (sink === fn) {
|
|
75
|
+
sink = undefined;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function emit(found, armed) {
|
|
80
|
+
// `console.error`, never a throw: an error escaping `afterAttach` is read by
|
|
81
|
+
// the session loader as an invalid track and the display is silently dropped,
|
|
82
|
+
// which would hide the very violation being reported. The prefix is what
|
|
83
|
+
// `config/jest/console.js` buffers and `contractGate.js` fails a test on.
|
|
84
|
+
console.error(`[jbrowse ${found.family} contract] ${found.message}`);
|
|
85
|
+
if (sink === undefined) {
|
|
86
|
+
remember(undelivered, found);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
sink({ ...found, armedBy: armed });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Report a violation of an ordering contract no type can state.
|
|
94
|
+
*
|
|
95
|
+
* In tree this is the `console.error` it has always been, and the jest gate is
|
|
96
|
+
* what listens. Out of tree it is the only thing that reaches a plugin author
|
|
97
|
+
* at all: their display runs in a production build of somebody's app, which
|
|
98
|
+
* strips nothing here, and a violation surfaces the moment anything arms the
|
|
99
|
+
* channel — a `localStorage` flag, a plugin loaded from localhost, or a site
|
|
100
|
+
* whose config asks for it.
|
|
101
|
+
*
|
|
102
|
+
* `agent-docs/reference/ARCHITECTURAL_LIMITS.md` §"Ordering is the contract" is
|
|
103
|
+
* the register of what reports here.
|
|
104
|
+
*/
|
|
105
|
+
export function reportContractViolation(family, message) {
|
|
106
|
+
if (armedBy === undefined) {
|
|
107
|
+
remember(unlogged, { family, message });
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
emit({ family, message }, armedBy);
|
|
111
|
+
}
|
|
112
|
+
}
|
package/esm/coverageBand.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { CoverageBandBuffers } from './coverageBandBuffers.ts';
|
|
2
2
|
import type { InstancePass } from './instancePass.ts';
|
|
3
|
-
export { coverageBandBuffers } from './coverageBandBuffers.ts';
|
|
4
3
|
export type { CoverageBandBuffers } from './coverageBandBuffers.ts';
|
|
5
4
|
export declare const COVERAGE_BAND_UNIFORMS_SIZE_BYTES = 112;
|
|
6
5
|
/**
|
package/esm/coverageBand.js
CHANGED
|
@@ -4,7 +4,6 @@ import * as coverageInterbaseShader from './shaders/coverageInterbase.generated.
|
|
|
4
4
|
import * as coverageModShader from './shaders/coverageMod.generated.js';
|
|
5
5
|
import * as coverageSnpShader from './shaders/coverageSnp.generated.js';
|
|
6
6
|
import { slangPass } from './slangPass.js';
|
|
7
|
-
export { coverageBandBuffers } from './coverageBandBuffers.js';
|
|
8
7
|
export const COVERAGE_BAND_UNIFORMS_SIZE_BYTES = coverageBarShader.UNIFORMS_SIZE_BYTES;
|
|
9
8
|
/**
|
|
10
9
|
* The band's z-order, back to front: the depth bars, the SNP slices stacked
|
|
@@ -22,9 +22,3 @@ export interface CoverageBandBuffers {
|
|
|
22
22
|
interbasePackedBuffer: ArrayBuffer;
|
|
23
23
|
indicatorPackedBuffer: ArrayBuffer;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Just the band's four buffers out of a wider per-region payload — the field set
|
|
27
|
-
* stated once, so a display carrying them into its own upload payload cannot
|
|
28
|
-
* spell three of them.
|
|
29
|
-
*/
|
|
30
|
-
export declare function coverageBandBuffers(src: CoverageBandBuffers): CoverageBandBuffers;
|
|
@@ -1,13 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Just the band's four buffers out of a wider per-region payload — the field set
|
|
3
|
-
* stated once, so a display carrying them into its own upload payload cannot
|
|
4
|
-
* spell three of them.
|
|
5
|
-
*/
|
|
6
|
-
export function coverageBandBuffers(src) {
|
|
7
|
-
return {
|
|
8
|
-
coveragePackedBuffer: src.coveragePackedBuffer,
|
|
9
|
-
snpPackedBuffer: src.snpPackedBuffer,
|
|
10
|
-
interbasePackedBuffer: src.interbasePackedBuffer,
|
|
11
|
-
indicatorPackedBuffer: src.indicatorPackedBuffer,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
1
|
+
export {};
|
|
@@ -7,31 +7,27 @@ import type { GpuHal, PipelineDescriptor, SampleCount } from './hal/types.ts';
|
|
|
7
7
|
*/
|
|
8
8
|
export interface RenderingBackendOptions<TRenderingBackend> {
|
|
9
9
|
passes: PipelineDescriptor[];
|
|
10
|
-
uniformByteSize: number;
|
|
11
10
|
/**
|
|
12
|
-
* Samples per pixel this display's WebGPU target is allocated at
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Samples per pixel this display's WebGPU target is allocated at. Omitted, it
|
|
12
|
+
* is derived from `passes`: 1 when every registered pass declares
|
|
13
|
+
* `//! coverage: analytic`, else 4. Registered, not drawn — an inactive pass
|
|
14
|
+
* that leans on MSAA holds its display at 4. Passing it overrides the
|
|
15
|
+
* derivation in either direction.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* conflation at the shared edges of tiled cells, which is Hi-C and LD.
|
|
24
|
-
*
|
|
25
|
-
* Setting it to 1 allocates **no** target rather than a smaller one. Which
|
|
26
|
-
* displays should is a look-at-the-pixels decision taken one display at a
|
|
27
|
-
* time; `ideas/arc-antialiasing-without-msaa.md` is the survey and the
|
|
28
|
-
* captures.
|
|
17
|
+
* The cost is one colour attachment the size of the canvas — an empty 600px
|
|
18
|
+
* track pays what a full one does, and eight ordinary tracks on a retina
|
|
19
|
+
* panel hold 109.7 MiB nothing counts (ARCHITECTURAL_LIMITS.md §"The MSAA
|
|
20
|
+
* target is the largest per-display allocation"); 1 allocates none. Whether a
|
|
21
|
+
* display's pixels survive the flip is a look-at-the-pixels decision taken
|
|
22
|
+
* one display at a time; `ideas/arc-antialiasing-without-msaa.md` is the
|
|
23
|
+
* survey and the captures.
|
|
29
24
|
*/
|
|
30
25
|
sampleCount?: SampleCount;
|
|
31
26
|
createGpuBackend: (hal: GpuHal) => TRenderingBackend;
|
|
32
27
|
createCanvas2DBackend: (canvas: HTMLCanvasElement) => TRenderingBackend;
|
|
33
28
|
}
|
|
34
|
-
export declare function
|
|
29
|
+
export declare function deriveSampleCount(passes: readonly PipelineDescriptor[]): SampleCount;
|
|
30
|
+
export declare function createRenderingBackend<TRenderingBackend>(canvas: HTMLCanvasElement, { passes, sampleCount, createGpuBackend, createCanvas2DBackend, }: RenderingBackendOptions<TRenderingBackend>): Promise<TRenderingBackend>;
|
|
35
31
|
/**
|
|
36
32
|
* Factory for a **Canvas2D-only** display — one that ships no GPU shader path.
|
|
37
33
|
*
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { assertUniquePassIds, createGpuHal } from './hal/index.js';
|
|
2
|
-
export
|
|
2
|
+
export function deriveSampleCount(passes) {
|
|
3
|
+
return passes.every(p => p.coverage === 'analytic') ? 1 : 4;
|
|
4
|
+
}
|
|
5
|
+
export async function createRenderingBackend(canvas, { passes, sampleCount = deriveSampleCount(passes), createGpuBackend, createCanvas2DBackend, }) {
|
|
3
6
|
// Before the ladder, so a duplicate id is reported on every machine rather
|
|
4
7
|
// than only the ones that reach a GPU rung — the bug it prevents is silent
|
|
5
8
|
// and GPU-only, which is precisely why a developer on the Canvas2D fallback
|
|
@@ -15,7 +18,6 @@ export async function createRenderingBackend(canvas, { passes, uniformByteSize,
|
|
|
15
18
|
const ladderFailures = [];
|
|
16
19
|
const hal = await createGpuHal(canvas, {
|
|
17
20
|
passes,
|
|
18
|
-
uniformByteSize,
|
|
19
21
|
sampleCount,
|
|
20
22
|
failures: ladderFailures,
|
|
21
23
|
});
|
package/esm/displayPhase.d.ts
CHANGED
|
@@ -83,6 +83,15 @@ export interface DisplayLoadingInputs {
|
|
|
83
83
|
* click.
|
|
84
84
|
*/
|
|
85
85
|
isLoadingOrCanceled: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* A load this display depends on beyond its primary fetch has not landed for
|
|
88
|
+
* the first time, so the frame the primary fetch calls current is still
|
|
89
|
+
* missing something (multi-way synteny's lane genes and links). A loading
|
|
90
|
+
* term rather than a `dataSuperseded` fold: that flag holds the export
|
|
91
|
+
* through every later refetch too, and a scrim over lanes that are already
|
|
92
|
+
* drawn is the thing a display saying this wants to avoid.
|
|
93
|
+
*/
|
|
94
|
+
awaitingDependentData: boolean;
|
|
86
95
|
/**
|
|
87
96
|
* Whether this display paints a canvas in its current configuration. Gates
|
|
88
97
|
* the pre-first-paint term alone, because a display that is showing a
|
|
@@ -142,7 +151,7 @@ export interface DisplayLoadingInputs {
|
|
|
142
151
|
* IntersectionObserver's first callback, which is exactly when a display must
|
|
143
152
|
* not flash `ready`. Only the paint it will never do is excused.
|
|
144
153
|
*/
|
|
145
|
-
export declare function computeLoadingTerm({ fetchInert, viewportEmpty, isLoadingOrCanceled, rendersCanvas, canvasDrawn, }: DisplayLoadingInputs, viewportCurrent: () => boolean, hostMounted?: () => boolean): boolean;
|
|
154
|
+
export declare function computeLoadingTerm({ fetchInert, viewportEmpty, isLoadingOrCanceled, awaitingDependentData, rendersCanvas, canvasDrawn, }: DisplayLoadingInputs, viewportCurrent: () => boolean, hostMounted?: () => boolean): boolean;
|
|
146
155
|
/**
|
|
147
156
|
* The same ranking minus `renderError`, for a display with no rendering backend
|
|
148
157
|
* to fail (arc's main-thread SVG). `computeDisplayPhase` delegates here rather
|
package/esm/displayPhase.js
CHANGED
|
@@ -70,10 +70,11 @@ export function computeDisplayPhase({ renderError, regionTooLarge, error }, load
|
|
|
70
70
|
* IntersectionObserver's first callback, which is exactly when a display must
|
|
71
71
|
* not flash `ready`. Only the paint it will never do is excused.
|
|
72
72
|
*/
|
|
73
|
-
export function computeLoadingTerm({ fetchInert, viewportEmpty, isLoadingOrCanceled, rendersCanvas, canvasDrawn, }, viewportCurrent, hostMounted = () => true) {
|
|
73
|
+
export function computeLoadingTerm({ fetchInert, viewportEmpty, isLoadingOrCanceled, awaitingDependentData, rendersCanvas, canvasDrawn, }, viewportCurrent, hostMounted = () => true) {
|
|
74
74
|
return (!fetchInert &&
|
|
75
75
|
!viewportEmpty &&
|
|
76
76
|
(isLoadingOrCanceled ||
|
|
77
|
+
awaitingDependentData ||
|
|
77
78
|
(rendersCanvas && hostMounted() && !canvasDrawn) ||
|
|
78
79
|
!viewportCurrent()));
|
|
79
80
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A per-key encode memo over a display's cell map: `encode` runs for a key
|
|
3
|
+
* when its data reference moves, every key re-encodes when `inputs` yields a
|
|
4
|
+
* new identity, and a departed key is dropped. `inputs` runs through a
|
|
5
|
+
* `computed`, so a getter that allocates a fresh object per read re-encodes
|
|
6
|
+
* only when what it read changed.
|
|
7
|
+
*
|
|
8
|
+
* The returned map is a new instance whenever an entry moved and the same one
|
|
9
|
+
* otherwise, so an MST view holding it invalidates its observers exactly when
|
|
10
|
+
* a cell did. `installUpload` runs its `encode` path on this; a display that
|
|
11
|
+
* needs the encoded map itself — for a hit test, an SVG export — holds one in
|
|
12
|
+
* a `.views` closure and hands the installer identity cells.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createEncodeMemo<K, Data, Props, Encoded>(cells: () => ReadonlyMap<K, Data>, inputs: (() => Props) | undefined, encode: (data: Data, props: Props, key: K) => Encoded): () => ReadonlyMap<K, Encoded>;
|