@jbrowse/render-core 5.0.0-beta.2 → 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 +20 -1
- package/esm/RenderLifecycleMixin.js +25 -2
- 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/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/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 +7 -39
- 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 +2 -2
- 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/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/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 +13 -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.slang +7 -1
- 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.slang +7 -8
- 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
|
@@ -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
|
} & {
|
|
@@ -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
|
+
}
|
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
|
});
|
|
@@ -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>;
|
|
@@ -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>;
|