@jbrowse/render-core 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/esm/OverlayCanvas.d.ts +29 -0
- package/esm/OverlayCanvas.js +43 -0
- package/esm/RenderCanvas.d.ts +76 -0
- package/esm/RenderCanvas.js +38 -0
- package/esm/RenderLifecycleMixin.d.ts +180 -0
- package/esm/RenderLifecycleMixin.js +279 -0
- package/esm/ScrollLockedOverlay.d.ts +28 -0
- package/esm/ScrollLockedOverlay.js +42 -0
- package/esm/blockClipUtils.d.ts +27 -0
- package/esm/blockClipUtils.js +101 -0
- package/esm/canvas2dUtils.d.ts +372 -0
- package/esm/canvas2dUtils.js +564 -0
- package/esm/canvasContext.d.ts +68 -0
- package/esm/canvasContext.js +74 -0
- package/esm/colorRampLut.d.ts +17 -0
- package/esm/colorRampLut.js +25 -0
- package/esm/coverageBand.d.ts +107 -0
- package/esm/coverageBand.js +109 -0
- package/esm/coverageBandBuffers.d.ts +30 -0
- package/esm/coverageBandBuffers.js +13 -0
- package/esm/createRenderingBackend.d.ts +52 -0
- package/esm/createRenderingBackend.js +58 -0
- package/esm/displayPhase.d.ts +155 -0
- package/esm/displayPhase.js +97 -0
- package/esm/globalRenderingBackend.d.ts +90 -0
- package/esm/globalRenderingBackend.js +53 -0
- package/esm/gpuDevice.d.ts +91 -0
- package/esm/gpuDevice.js +277 -0
- package/esm/graphicsCapabilities.d.ts +98 -0
- package/esm/graphicsCapabilities.js +165 -0
- package/esm/hal/createHal.d.ts +22 -0
- package/esm/hal/createHal.js +91 -0
- package/esm/hal/deviceGpuCache.d.ts +36 -0
- package/esm/hal/deviceGpuCache.js +120 -0
- package/esm/hal/gpuHalBase.d.ts +55 -0
- package/esm/hal/gpuHalBase.js +81 -0
- package/esm/hal/index.d.ts +7 -0
- package/esm/hal/index.js +5 -0
- package/esm/hal/mockHal.d.ts +153 -0
- package/esm/hal/mockHal.js +297 -0
- package/esm/hal/oomReporter.d.ts +15 -0
- package/esm/hal/oomReporter.js +22 -0
- package/esm/hal/passIds.d.ts +22 -0
- package/esm/hal/passIds.js +36 -0
- package/esm/hal/regionRegistry.d.ts +11 -0
- package/esm/hal/regionRegistry.js +66 -0
- package/esm/hal/types.d.ts +116 -0
- package/esm/hal/types.js +1 -0
- package/esm/hal/webgl2Hal.d.ts +47 -0
- package/esm/hal/webgl2Hal.js +490 -0
- package/esm/hal/webgpuHal.d.ts +169 -0
- package/esm/hal/webgpuHal.js +722 -0
- package/esm/installUpload.d.ts +91 -0
- package/esm/installUpload.js +94 -0
- package/esm/instanceCache.d.ts +43 -0
- package/esm/instanceCache.js +53 -0
- package/esm/instancePass.d.ts +50 -0
- package/esm/instancePass.js +34 -0
- package/esm/keyedRenderingBackend.d.ts +41 -0
- package/esm/keyedRenderingBackend.js +19 -0
- package/esm/mapUploadSync.d.ts +31 -0
- package/esm/mapUploadSync.js +45 -0
- package/esm/namedReactions.d.ts +29 -0
- package/esm/namedReactions.js +62 -0
- package/esm/perRegionRenderingBackend.d.ts +114 -0
- package/esm/perRegionRenderingBackend.js +93 -0
- package/esm/recoveryBudget.d.ts +40 -0
- package/esm/recoveryBudget.js +56 -0
- package/esm/regionDataMap.d.ts +60 -0
- package/esm/regionDataMap.js +98 -0
- package/esm/renderBlock.d.ts +25 -0
- package/esm/renderBlock.js +10 -0
- package/esm/renderingBackendBase.d.ts +71 -0
- package/esm/renderingBackendBase.js +50 -0
- package/esm/shaders/antialias.js.generated.d.ts +4 -0
- package/esm/shaders/antialias.js.generated.js +25 -0
- package/esm/shaders/capsule.generated.d.ts +1 -0
- package/esm/shaders/capsule.generated.js +3 -0
- package/esm/shaders/colorRampLut.generated.d.ts +1 -0
- package/esm/shaders/colorRampLut.generated.js +3 -0
- package/esm/shaders/coverageBand.generated.d.ts +2 -0
- package/esm/shaders/coverageBand.generated.js +4 -0
- package/esm/shaders/coverageBar.generated.d.ts +4 -0
- package/esm/shaders/coverageBar.generated.js +6 -0
- package/esm/shaders/coverageBar.iface.generated.d.ts +85 -0
- package/esm/shaders/coverageBar.iface.generated.js +114 -0
- package/esm/shaders/coverageIndicator.generated.d.ts +4 -0
- package/esm/shaders/coverageIndicator.generated.js +6 -0
- package/esm/shaders/coverageIndicator.iface.generated.d.ts +85 -0
- package/esm/shaders/coverageIndicator.iface.generated.js +114 -0
- package/esm/shaders/coverageInterbase.generated.d.ts +4 -0
- package/esm/shaders/coverageInterbase.generated.js +6 -0
- package/esm/shaders/coverageInterbase.iface.generated.d.ts +93 -0
- package/esm/shaders/coverageInterbase.iface.generated.js +134 -0
- package/esm/shaders/coverageMod.generated.d.ts +4 -0
- package/esm/shaders/coverageMod.generated.js +6 -0
- package/esm/shaders/coverageMod.iface.generated.d.ts +97 -0
- package/esm/shaders/coverageMod.iface.generated.js +144 -0
- package/esm/shaders/coverageSnp.generated.d.ts +4 -0
- package/esm/shaders/coverageSnp.generated.js +6 -0
- package/esm/shaders/coverageSnp.iface.generated.d.ts +97 -0
- package/esm/shaders/coverageSnp.iface.generated.js +144 -0
- package/esm/shaders/hpmath.js.generated.d.ts +3 -0
- package/esm/shaders/hpmath.js.generated.js +48 -0
- package/esm/shaders/pointGlyph.generated.d.ts +1 -0
- package/esm/shaders/pointGlyph.generated.js +3 -0
- package/esm/shaders/pointGlyph.js.generated.d.ts +1 -0
- package/esm/shaders/pointGlyph.js.generated.js +7 -0
- package/esm/shaders/rowRect.generated.d.ts +3 -0
- package/esm/shaders/rowRect.generated.js +5 -0
- package/esm/shaders/rowRect.js.generated.d.ts +2 -0
- package/esm/shaders/rowRect.js.generated.js +13 -0
- package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
- package/esm/shaders/scoreScale.js.generated.js +46 -0
- package/esm/slangPass.d.ts +49 -0
- package/esm/slangPass.js +22 -0
- package/esm/useEventCallback.d.ts +1 -0
- package/esm/useEventCallback.js +19 -0
- package/esm/useGraphicsCapabilities.d.ts +17 -0
- package/esm/useGraphicsCapabilities.js +38 -0
- package/esm/useRenderingBackend.d.ts +83 -0
- package/esm/useRenderingBackend.js +422 -0
- package/esm/useTabVisibilityRerender.d.ts +13 -0
- package/esm/useTabVisibilityRerender.js +39 -0
- package/esm/webgpuUtils.d.ts +23 -0
- package/esm/webgpuUtils.js +76 -0
- package/package.json +91 -0
- package/src/shaders/antialias.js.generated.ts +33 -0
- package/src/shaders/antialias.slang +112 -0
- package/src/shaders/capsule.generated.ts +4 -0
- package/src/shaders/capsule.slang +62 -0
- package/src/shaders/colorPack.slang +26 -0
- package/src/shaders/colorRampLut.generated.ts +4 -0
- package/src/shaders/colorRampLut.slang +51 -0
- package/src/shaders/coverageBand.generated.ts +6 -0
- package/src/shaders/coverageBand.slang +276 -0
- package/src/shaders/coverageBar.generated.ts +10 -0
- package/src/shaders/coverageBar.iface.generated.ts +172 -0
- package/src/shaders/coverageBar.slang +37 -0
- package/src/shaders/coverageIndicator.generated.ts +10 -0
- package/src/shaders/coverageIndicator.iface.generated.ts +172 -0
- package/src/shaders/coverageIndicator.slang +61 -0
- package/src/shaders/coverageInterbase.generated.ts +10 -0
- package/src/shaders/coverageInterbase.iface.generated.ts +198 -0
- package/src/shaders/coverageInterbase.slang +63 -0
- package/src/shaders/coverageMod.generated.ts +10 -0
- package/src/shaders/coverageMod.iface.generated.ts +211 -0
- package/src/shaders/coverageMod.slang +36 -0
- package/src/shaders/coverageSnp.generated.ts +10 -0
- package/src/shaders/coverageSnp.iface.generated.ts +211 -0
- package/src/shaders/coverageSnp.slang +52 -0
- package/src/shaders/diagonalGrid.slang +25 -0
- package/src/shaders/glyphEdgeAlpha.test.ts +241 -0
- package/src/shaders/hpmath.js.generated.ts +47 -0
- package/src/shaders/hpmath.slang +226 -0
- package/src/shaders/pointGlyph.generated.ts +4 -0
- package/src/shaders/pointGlyph.js.generated.ts +9 -0
- package/src/shaders/pointGlyph.slang +93 -0
- package/src/shaders/rowRect.generated.ts +8 -0
- package/src/shaders/rowRect.js.generated.ts +17 -0
- package/src/shaders/rowRect.slang +160 -0
- package/src/shaders/scoreScale.js.generated.ts +51 -0
- package/src/shaders/scoreScale.slang +68 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const KIND_LABELS = {
|
|
2
|
+
webgpu: 'WebGPU',
|
|
3
|
+
webgl2: 'WebGL2',
|
|
4
|
+
'2d': 'Canvas 2D',
|
|
5
|
+
};
|
|
6
|
+
const acquired = new WeakMap();
|
|
7
|
+
const configuredBy = new WeakMap();
|
|
8
|
+
/**
|
|
9
|
+
* Record `owner` as the holder of `canvas`'s current WebGPU configuration.
|
|
10
|
+
*
|
|
11
|
+
* `getContext('webgpu')` hands back the **same** `GPUCanvasContext` object every
|
|
12
|
+
* time, so a configuration is per-element state that two HALs can end up
|
|
13
|
+
* sharing: a re-init on a reused element (a display whose `model` prop swaps
|
|
14
|
+
* under it, an init that overlaps a cancelled one) builds a second HAL on the
|
|
15
|
+
* same context, and `unconfigure()` from whichever loses the race takes the
|
|
16
|
+
* winner's swap chain with it. Firefox then throws `InvalidStateError:
|
|
17
|
+
* GPUCanvasContext.getCurrentTexture: Canvas not configured` on every frame the
|
|
18
|
+
* live HAL draws, forever — the browser fires no context-lost event for it, so
|
|
19
|
+
* none of the loss recovery in `useRenderingBackend` ever runs.
|
|
20
|
+
*
|
|
21
|
+
* Last configure wins, which is the live one: a cancelled init is disposed, and
|
|
22
|
+
* disposal is the only thing that unconfigures.
|
|
23
|
+
*/
|
|
24
|
+
export function noteCanvasConfigured(canvas, owner) {
|
|
25
|
+
configuredBy.set(canvas, owner);
|
|
26
|
+
}
|
|
27
|
+
/** Whether `owner`'s configuration is still the one on `canvas`. */
|
|
28
|
+
export function canvasConfiguredBy(canvas, owner) {
|
|
29
|
+
return configuredBy.get(canvas) === owner;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Record that `canvas` is now permanently committed to `kind`. Call on every
|
|
33
|
+
* **successful** `getContext`, in every HAL and every Canvas2D backend — a kind
|
|
34
|
+
* that isn't recorded is one the diagnosis below can't name, and it will fall
|
|
35
|
+
* back to the vaguer message.
|
|
36
|
+
*/
|
|
37
|
+
export function noteCanvasContext(canvas, kind) {
|
|
38
|
+
acquired.set(canvas, kind);
|
|
39
|
+
}
|
|
40
|
+
/** The kind this canvas is committed to, if we were the ones who took it. */
|
|
41
|
+
export function acquiredCanvasContext(canvas) {
|
|
42
|
+
return acquired.get(canvas);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `getContext('2d')` + null-check + bookkeeping, as one call. Every Canvas2D
|
|
46
|
+
* backend hand-wrote that ritual with the same bare message, which is four
|
|
47
|
+
* copies of a diagnosis to keep in step — and three of them belong to the two
|
|
48
|
+
* consumers whose canvas *never* unmounts (dotplot, the synteny level), i.e.
|
|
49
|
+
* exactly where a reused element is reachable.
|
|
50
|
+
*/
|
|
51
|
+
export function acquireCanvas2D(canvas) {
|
|
52
|
+
const ctx = canvas.getContext('2d');
|
|
53
|
+
if (!ctx) {
|
|
54
|
+
throw canvasContextError(canvas, '2d');
|
|
55
|
+
}
|
|
56
|
+
noteCanvasContext(canvas, '2d');
|
|
57
|
+
return ctx;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The error to throw when `getContext(requested)` returned null.
|
|
61
|
+
*
|
|
62
|
+
* Keeps each caller's historical message as the first clause (`Canvas 2D context
|
|
63
|
+
* not available`, and tests match on it) and appends the *reason*, which is the
|
|
64
|
+
* part that was missing: either "this element is already committed to X" — the
|
|
65
|
+
* recoverable case, with the remedy named — or an honest statement of the two
|
|
66
|
+
* possibilities when we don't know.
|
|
67
|
+
*/
|
|
68
|
+
export function canvasContextError(canvas, requested) {
|
|
69
|
+
const prior = acquiredCanvasContext(canvas);
|
|
70
|
+
const head = `${KIND_LABELS[requested]} context not available`;
|
|
71
|
+
return new Error(prior !== undefined && prior !== requested
|
|
72
|
+
? `${head}: this <canvas> element is already committed to a ${KIND_LABELS[prior]} context, and a canvas's context kind is permanent. This is a re-init on a reused element — it needs a fresh one. Mount the canvas with RenderCanvas (@jbrowse/render-core/RenderCanvas), which keys it on useRenderingBackend's canvasKey.`
|
|
73
|
+
: `${head}: getContext('${requested}') returned null. Either this browser/machine does not support it, or the element is already committed to a different context kind (a canvas's kind is permanent once acquired).`);
|
|
74
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GpuHal } from './hal/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Entries in a colour-ramp LUT, and so the width of the texture every ramp
|
|
4
|
+
* consumer binds. Off the shader that samples it: `rampColor` divides by this
|
|
5
|
+
* to land entry i on its own texel center, so a table built to a different
|
|
6
|
+
* number is a half-shade of drift at every entry.
|
|
7
|
+
*/
|
|
8
|
+
export declare const COLOR_RAMP_LUT_ENTRIES = 256;
|
|
9
|
+
/**
|
|
10
|
+
* Upload a 256-entry RGBA colour-ramp LUT as each named pass's 256×1 texture —
|
|
11
|
+
* the runtime half of the shared ramp mechanism whose shader half is
|
|
12
|
+
* `shaders/colorRampLut.slang`. HiC, LD (whose two shader variants are the
|
|
13
|
+
* reason this takes a pass list) and wiggle density all put their ramps behind
|
|
14
|
+
* their samplers through this one call; each keeps its own scale and its own
|
|
15
|
+
* LUT contents.
|
|
16
|
+
*/
|
|
17
|
+
export declare function uploadColorRampLut(hal: GpuHal, ramp: Uint8Array, passIds: readonly string[]): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RAMP_LUT_ENTRIES } from './shaders/colorRampLut.generated.js';
|
|
2
|
+
/**
|
|
3
|
+
* Entries in a colour-ramp LUT, and so the width of the texture every ramp
|
|
4
|
+
* consumer binds. Off the shader that samples it: `rampColor` divides by this
|
|
5
|
+
* to land entry i on its own texel center, so a table built to a different
|
|
6
|
+
* number is a half-shade of drift at every entry.
|
|
7
|
+
*/
|
|
8
|
+
export const COLOR_RAMP_LUT_ENTRIES = RAMP_LUT_ENTRIES;
|
|
9
|
+
/**
|
|
10
|
+
* Upload a 256-entry RGBA colour-ramp LUT as each named pass's 256×1 texture —
|
|
11
|
+
* the runtime half of the shared ramp mechanism whose shader half is
|
|
12
|
+
* `shaders/colorRampLut.slang`. HiC, LD (whose two shader variants are the
|
|
13
|
+
* reason this takes a pass list) and wiggle density all put their ramps behind
|
|
14
|
+
* their samplers through this one call; each keeps its own scale and its own
|
|
15
|
+
* LUT contents.
|
|
16
|
+
*/
|
|
17
|
+
export function uploadColorRampLut(hal, ramp, passIds) {
|
|
18
|
+
if (ramp.length !== COLOR_RAMP_LUT_ENTRIES * 4) {
|
|
19
|
+
throw new Error(`uploadColorRampLut: expected a ${COLOR_RAMP_LUT_ENTRIES}-entry RGBA LUT ` +
|
|
20
|
+
`(${COLOR_RAMP_LUT_ENTRIES * 4} bytes), got ${ramp.length} bytes`);
|
|
21
|
+
}
|
|
22
|
+
for (const passId of passIds) {
|
|
23
|
+
hal.uploadTexture(passId, ramp, COLOR_RAMP_LUT_ENTRIES, 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { CoverageBandBuffers } from './coverageBandBuffers.ts';
|
|
2
|
+
import type { InstancePass } from './instancePass.ts';
|
|
3
|
+
export { coverageBandBuffers } from './coverageBandBuffers.ts';
|
|
4
|
+
export type { CoverageBandBuffers } from './coverageBandBuffers.ts';
|
|
5
|
+
export declare const COVERAGE_BAND_UNIFORMS_SIZE_BYTES = 112;
|
|
6
|
+
/**
|
|
7
|
+
* The fifth buffer, base modifications stacked in the same bars. Separate
|
|
8
|
+
* because only one producer emits it — a MAF alignment carries no modification
|
|
9
|
+
* calls — and a display that has no mod data must not have to ship an empty
|
|
10
|
+
* buffer to satisfy the shape.
|
|
11
|
+
*/
|
|
12
|
+
export interface CoverageBandModBuffer {
|
|
13
|
+
modCovPackedBuffer: ArrayBuffer;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One draw layer of the coverage band, and the id of the GPU pass that draws it.
|
|
17
|
+
*
|
|
18
|
+
* The band is the same five marks wherever it appears — a display either has a
|
|
19
|
+
* layer's data or does not, and no display reorders them.
|
|
20
|
+
*/
|
|
21
|
+
export type CoverageLayerId = 'coverage' | 'snpCov' | 'modCov' | 'interbase' | 'indicator';
|
|
22
|
+
/**
|
|
23
|
+
* The band's z-order, back to front: the depth bars, the SNP slices stacked
|
|
24
|
+
* inside them, the modification slices stacked on those, the interbase histogram
|
|
25
|
+
* hanging from the band top, and its indicator triangles above that.
|
|
26
|
+
*
|
|
27
|
+
* The order is load-bearing rather than cosmetic — the interbase bars hang down
|
|
28
|
+
* over the lower half the depth bars grow up into, so the two overlap at any
|
|
29
|
+
* real depth — and it is one fact, so it is stated once here and every backend
|
|
30
|
+
* of every display iterates it. It was stated per backend per display, which is
|
|
31
|
+
* how "MAF drew its band in a different order on the fallback" was a screenshot
|
|
32
|
+
* to catch rather than a compile error.
|
|
33
|
+
*
|
|
34
|
+
* What stays per display is the GATING, which genuinely differs: MAF has no
|
|
35
|
+
* `showInterbaseIndicators` setting and no modification data at all.
|
|
36
|
+
*/
|
|
37
|
+
export declare const COVERAGE_BAND_LAYER_ORDER: readonly CoverageLayerId[];
|
|
38
|
+
/**
|
|
39
|
+
* Whatever a display attaches to each band layer — a GPU pass, a Canvas2D
|
|
40
|
+
* painter, a gate — resolved into paint order.
|
|
41
|
+
*
|
|
42
|
+
* The argument is exhaustive over `CoverageLayerId`, so a layer added to the
|
|
43
|
+
* order above is a compile error in every display until it is wired. `undefined`
|
|
44
|
+
* is how a display says it has no such layer (MAF carries no modification
|
|
45
|
+
* calls); it is dropped rather than drawn empty, which keeps a display that has
|
|
46
|
+
* no data for a layer from having to ship an empty buffer to satisfy the shape.
|
|
47
|
+
*/
|
|
48
|
+
export declare function orderCoverageBandLayers<T>(byId: Record<CoverageLayerId, T | undefined>): (T & ({} | null))[];
|
|
49
|
+
export declare const COVERAGE_BAR_PASS: InstancePass<Pick<CoverageBandBuffers, 'coveragePackedBuffer'>>;
|
|
50
|
+
export declare const COVERAGE_SNP_PASS: InstancePass<Pick<CoverageBandBuffers, 'snpPackedBuffer'>>;
|
|
51
|
+
export declare const COVERAGE_MOD_PASS: InstancePass<CoverageBandModBuffer>;
|
|
52
|
+
export declare const COVERAGE_INTERBASE_PASS: InstancePass<Pick<CoverageBandBuffers, 'interbasePackedBuffer'>>;
|
|
53
|
+
export declare const COVERAGE_INDICATOR_PASS: InstancePass<Pick<CoverageBandBuffers, 'indicatorPackedBuffer'>>;
|
|
54
|
+
/** Palette slots the band paints by name, each a packed ABGR u32. */
|
|
55
|
+
export interface CoverageBandColors {
|
|
56
|
+
coverage: number;
|
|
57
|
+
baseA: number;
|
|
58
|
+
baseC: number;
|
|
59
|
+
baseG: number;
|
|
60
|
+
baseT: number;
|
|
61
|
+
baseN: number;
|
|
62
|
+
insertionIndicator: number;
|
|
63
|
+
softclipIndicator: number;
|
|
64
|
+
hardclipIndicator: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Everything the band's UBO holds that a caller actually decides. `hpZero` is
|
|
68
|
+
* absent because it is not one: it is a fixed sentinel the HP math needs — see
|
|
69
|
+
* `writeCoverageBandUniforms`.
|
|
70
|
+
*/
|
|
71
|
+
export interface CoverageBandUniformValues {
|
|
72
|
+
/** HP-split viewport start + visible span; keep `bpLen` POSITIVE and flip via `reversed`. */
|
|
73
|
+
bpHi: number;
|
|
74
|
+
bpLo: number;
|
|
75
|
+
bpLen: number;
|
|
76
|
+
/** The span clip [-1,1] covers horizontally — a block's scissored width, not the canvas. */
|
|
77
|
+
canvasW: number;
|
|
78
|
+
canvasH: number;
|
|
79
|
+
reversed: boolean;
|
|
80
|
+
/** Band box in CSS px: its height, the scalebar-label inset, and its top edge on the canvas. */
|
|
81
|
+
covHeight: number;
|
|
82
|
+
covYOffset: number;
|
|
83
|
+
covTop: number;
|
|
84
|
+
/** The region's own peak depth, which is what the buffers' `relDepth` is a fraction of. */
|
|
85
|
+
regionMaxDepth: number;
|
|
86
|
+
/** The display's autoscaled depth domain. `undefined` max = not resolved yet. */
|
|
87
|
+
domainMin: number;
|
|
88
|
+
domainMax: number | undefined;
|
|
89
|
+
/** 0 = linear, 1 = log2, 2 = symlog, matching wiggle-core's SCALE_TYPE_*. */
|
|
90
|
+
scaleType: number;
|
|
91
|
+
symlogConstant: number;
|
|
92
|
+
binSize: number;
|
|
93
|
+
/** `interbaseBarHeightPx` — the one rule the draw, the hit test and this share. */
|
|
94
|
+
interbaseHeight: number;
|
|
95
|
+
snpMinFrequency: number;
|
|
96
|
+
colors: CoverageBandColors;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Fill the coverage band's uniform buffer. Total-write (the generated packer),
|
|
100
|
+
* so a field left out is a compile error rather than last frame's value.
|
|
101
|
+
*
|
|
102
|
+
* `hpZero` is derived here rather than at each call site because getting it
|
|
103
|
+
* wrong is a silently wrong band, not a broken one: it MUST be 0, since the HP
|
|
104
|
+
* math materializes +inf as `1/hpZero` to stop the compiler folding the hi/lo
|
|
105
|
+
* split it exists to preserve.
|
|
106
|
+
*/
|
|
107
|
+
export declare function writeCoverageBandUniforms(buf: ArrayBuffer, v: CoverageBandUniformValues): void;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as coverageBarShader from './shaders/coverageBar.generated.js';
|
|
2
|
+
import * as coverageIndicatorShader from './shaders/coverageIndicator.generated.js';
|
|
3
|
+
import * as coverageInterbaseShader from './shaders/coverageInterbase.generated.js';
|
|
4
|
+
import * as coverageModShader from './shaders/coverageMod.generated.js';
|
|
5
|
+
import * as coverageSnpShader from './shaders/coverageSnp.generated.js';
|
|
6
|
+
import { slangPass } from './slangPass.js';
|
|
7
|
+
export { coverageBandBuffers } from './coverageBandBuffers.js';
|
|
8
|
+
export const COVERAGE_BAND_UNIFORMS_SIZE_BYTES = coverageBarShader.UNIFORMS_SIZE_BYTES;
|
|
9
|
+
/**
|
|
10
|
+
* The band's z-order, back to front: the depth bars, the SNP slices stacked
|
|
11
|
+
* inside them, the modification slices stacked on those, the interbase histogram
|
|
12
|
+
* hanging from the band top, and its indicator triangles above that.
|
|
13
|
+
*
|
|
14
|
+
* The order is load-bearing rather than cosmetic — the interbase bars hang down
|
|
15
|
+
* over the lower half the depth bars grow up into, so the two overlap at any
|
|
16
|
+
* real depth — and it is one fact, so it is stated once here and every backend
|
|
17
|
+
* of every display iterates it. It was stated per backend per display, which is
|
|
18
|
+
* how "MAF drew its band in a different order on the fallback" was a screenshot
|
|
19
|
+
* to catch rather than a compile error.
|
|
20
|
+
*
|
|
21
|
+
* What stays per display is the GATING, which genuinely differs: MAF has no
|
|
22
|
+
* `showInterbaseIndicators` setting and no modification data at all.
|
|
23
|
+
*/
|
|
24
|
+
export const COVERAGE_BAND_LAYER_ORDER = [
|
|
25
|
+
'coverage',
|
|
26
|
+
'snpCov',
|
|
27
|
+
'modCov',
|
|
28
|
+
'interbase',
|
|
29
|
+
'indicator',
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Whatever a display attaches to each band layer — a GPU pass, a Canvas2D
|
|
33
|
+
* painter, a gate — resolved into paint order.
|
|
34
|
+
*
|
|
35
|
+
* The argument is exhaustive over `CoverageLayerId`, so a layer added to the
|
|
36
|
+
* order above is a compile error in every display until it is wired. `undefined`
|
|
37
|
+
* is how a display says it has no such layer (MAF carries no modification
|
|
38
|
+
* calls); it is dropped rather than drawn empty, which keeps a display that has
|
|
39
|
+
* no data for a layer from having to ship an empty buffer to satisfy the shape.
|
|
40
|
+
*/
|
|
41
|
+
export function orderCoverageBandLayers(byId) {
|
|
42
|
+
return COVERAGE_BAND_LAYER_ORDER.map(id => byId[id]).filter(layer => layer !== undefined);
|
|
43
|
+
}
|
|
44
|
+
// Each pass's instances are packed by the WORKER (or, for MAF, by the RPC that
|
|
45
|
+
// stands in for one) in the shader's own layout, so every "packer" here is the
|
|
46
|
+
// field the producer filled, uploaded verbatim. The layouts are generated from
|
|
47
|
+
// these shaders into @jbrowse/alignments-core, which is what the producers pack
|
|
48
|
+
// through.
|
|
49
|
+
export const COVERAGE_BAR_PASS = {
|
|
50
|
+
...slangPass({ id: 'coverage', mod: coverageBarShader }),
|
|
51
|
+
pack: data => data.coveragePackedBuffer,
|
|
52
|
+
};
|
|
53
|
+
export const COVERAGE_SNP_PASS = {
|
|
54
|
+
...slangPass({ id: 'snpCov', mod: coverageSnpShader }),
|
|
55
|
+
pack: data => data.snpPackedBuffer,
|
|
56
|
+
};
|
|
57
|
+
export const COVERAGE_MOD_PASS = {
|
|
58
|
+
...slangPass({ id: 'modCov', mod: coverageModShader }),
|
|
59
|
+
pack: data => data.modCovPackedBuffer,
|
|
60
|
+
};
|
|
61
|
+
export const COVERAGE_INTERBASE_PASS = {
|
|
62
|
+
...slangPass({ id: 'interbase', mod: coverageInterbaseShader }),
|
|
63
|
+
pack: data => data.interbasePackedBuffer,
|
|
64
|
+
};
|
|
65
|
+
export const COVERAGE_INDICATOR_PASS = {
|
|
66
|
+
...slangPass({ id: 'indicator', mod: coverageIndicatorShader }),
|
|
67
|
+
pack: data => data.indicatorPackedBuffer,
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Fill the coverage band's uniform buffer. Total-write (the generated packer),
|
|
71
|
+
* so a field left out is a compile error rather than last frame's value.
|
|
72
|
+
*
|
|
73
|
+
* `hpZero` is derived here rather than at each call site because getting it
|
|
74
|
+
* wrong is a silently wrong band, not a broken one: it MUST be 0, since the HP
|
|
75
|
+
* math materializes +inf as `1/hpZero` to stop the compiler folding the hi/lo
|
|
76
|
+
* split it exists to preserve.
|
|
77
|
+
*/
|
|
78
|
+
export function writeCoverageBandUniforms(buf, v) {
|
|
79
|
+
const { domainMax, colors } = v;
|
|
80
|
+
coverageBarShader.writeUniforms(buf, {
|
|
81
|
+
bpHi: v.bpHi,
|
|
82
|
+
bpLo: v.bpLo,
|
|
83
|
+
bpLen: v.bpLen,
|
|
84
|
+
hpZero: 0,
|
|
85
|
+
canvasW: v.canvasW,
|
|
86
|
+
canvasH: v.canvasH,
|
|
87
|
+
covHeight: v.covHeight,
|
|
88
|
+
covYOffset: v.covYOffset,
|
|
89
|
+
covTop: v.covTop,
|
|
90
|
+
regionMaxDepth: v.regionMaxDepth,
|
|
91
|
+
depthDomainMax: domainMax ?? 0,
|
|
92
|
+
depthDomainMin: v.domainMin,
|
|
93
|
+
coverageSymlogConstant: v.symlogConstant,
|
|
94
|
+
binSize: v.binSize,
|
|
95
|
+
interbaseHeight: v.interbaseHeight,
|
|
96
|
+
snpMinFreq: v.snpMinFrequency,
|
|
97
|
+
reversed: v.reversed ? 1 : 0,
|
|
98
|
+
coverageScaleType: v.scaleType,
|
|
99
|
+
colorCoverage: colors.coverage,
|
|
100
|
+
colorBaseA: colors.baseA,
|
|
101
|
+
colorBaseC: colors.baseC,
|
|
102
|
+
colorBaseG: colors.baseG,
|
|
103
|
+
colorBaseT: colors.baseT,
|
|
104
|
+
colorBaseN: colors.baseN,
|
|
105
|
+
colorInsertionIndicator: colors.insertionIndicator,
|
|
106
|
+
colorSoftclipIndicator: colors.softclipIndicator,
|
|
107
|
+
colorHardclipIndicator: colors.hardclipIndicator,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four coverage-band buffers a producer packs per region, in the layouts
|
|
3
|
+
* `coverageBand.slang`'s passes read. Every field is filled by
|
|
4
|
+
* `@jbrowse/alignments-core` — `packCoverageBinsForGpu`, `computeSNPCoverage`
|
|
5
|
+
* and `computeInterbaseCoverage` (which emits the last two) — so a display gets
|
|
6
|
+
* these by running that pipeline, not by shaping them itself.
|
|
7
|
+
*
|
|
8
|
+
* Structural rather than nominal, and named after the layout rather than after
|
|
9
|
+
* either display: the alignments worker's `CoverageUploadData` and the MAF
|
|
10
|
+
* worker's `MafCoverageRegion` both satisfy it, which is what lets the passes
|
|
11
|
+
* in `coverageBand.ts` carry their own packers instead of each plugin
|
|
12
|
+
* restating four field reads.
|
|
13
|
+
*
|
|
14
|
+
* Its own module, apart from the GPU passes in `coverageBand.ts`: a state
|
|
15
|
+
* model reaching for just this shape has no reason to statically pull in
|
|
16
|
+
* every coverage shader — GPU pass descriptors are a display's *renderer*
|
|
17
|
+
* concern, which is lazy-loaded, and a state model is not.
|
|
18
|
+
*/
|
|
19
|
+
export interface CoverageBandBuffers {
|
|
20
|
+
coveragePackedBuffer: ArrayBuffer;
|
|
21
|
+
snpPackedBuffer: ArrayBuffer;
|
|
22
|
+
interbasePackedBuffer: ArrayBuffer;
|
|
23
|
+
indicatorPackedBuffer: ArrayBuffer;
|
|
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;
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { GpuHal, PipelineDescriptor, SampleCount } from './hal/types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Options for `createRenderingBackend`. The two factories are an options object
|
|
4
|
+
* (not positional args) on purpose: both are single-arg `(x) => new Backend(x)`
|
|
5
|
+
* lambdas, so positionally they're trivially swappable by mistake — naming them
|
|
6
|
+
* makes the GPU vs Canvas2D path unambiguous at every call site.
|
|
7
|
+
*/
|
|
8
|
+
export interface RenderingBackendOptions<TRenderingBackend> {
|
|
9
|
+
passes: PipelineDescriptor[];
|
|
10
|
+
uniformByteSize: number;
|
|
11
|
+
/**
|
|
12
|
+
* Samples per pixel this display's WebGPU target is allocated at — 4 unless
|
|
13
|
+
* stated, which is what every display asked for while this was a build-time
|
|
14
|
+
* constant.
|
|
15
|
+
*
|
|
16
|
+
* **It is a property of the display, not of the build**, because what it buys
|
|
17
|
+
* differs per display and what it costs does not. The cost is one colour
|
|
18
|
+
* attachment the size of the canvas, so an empty 600px track pays what a full
|
|
19
|
+
* one does and eight ordinary tracks on a retina panel hold 109.7 MiB nothing
|
|
20
|
+
* counts (ARCHITECTURAL_LIMITS.md §"The MSAA target is the largest
|
|
21
|
+
* per-display allocation"). What it buys is nothing at all for a display
|
|
22
|
+
* whose fragments compute their own coverage, and the one fix there is for
|
|
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.
|
|
29
|
+
*/
|
|
30
|
+
sampleCount?: SampleCount;
|
|
31
|
+
createGpuBackend: (hal: GpuHal) => TRenderingBackend;
|
|
32
|
+
createCanvas2DBackend: (canvas: HTMLCanvasElement) => TRenderingBackend;
|
|
33
|
+
}
|
|
34
|
+
export declare function createRenderingBackend<TRenderingBackend>(canvas: HTMLCanvasElement, { passes, uniformByteSize, sampleCount, createGpuBackend, createCanvas2DBackend, }: RenderingBackendOptions<TRenderingBackend>): Promise<TRenderingBackend>;
|
|
35
|
+
/**
|
|
36
|
+
* Factory for a **Canvas2D-only** display — one that ships no GPU shader path.
|
|
37
|
+
*
|
|
38
|
+
* Every display must already provide a Canvas2D draw function (SVG export needs
|
|
39
|
+
* it), so for a display whose data volume never demands the GPU (gene-scale
|
|
40
|
+
* annotations, low-density score tracks, text/sequence), the GPU renderer +
|
|
41
|
+
* shader are pure overhead. Such a display skips `createRenderingBackend`'s HAL
|
|
42
|
+
* ladder entirely and returns its Canvas2D backend directly through this
|
|
43
|
+
* helper. The backend then plugs into the exact same `RenderLifecycleMixin` /
|
|
44
|
+
* `DisplayChrome` machinery as a GPU display — the lifecycle is backend-
|
|
45
|
+
* agnostic, so nothing downstream knows or cares there's no HAL.
|
|
46
|
+
*
|
|
47
|
+
* Reference: `plugins/sequence`'s `SequenceRenderer`. Promote to the dual-path
|
|
48
|
+
* `createRenderingBackend` only once a profile shows Canvas2D can't keep 60fps
|
|
49
|
+
* at the display's real feature counts (≳100K features/frame — see
|
|
50
|
+
* RFC-001 §3a).
|
|
51
|
+
*/
|
|
52
|
+
export declare function createCanvas2DBackend<TRenderingBackend>(canvas: HTMLCanvasElement, createCanvas2DRenderingBackend: (canvas: HTMLCanvasElement) => TRenderingBackend): Promise<TRenderingBackend>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { assertUniquePassIds, createGpuHal } from './hal/index.js';
|
|
2
|
+
export async function createRenderingBackend(canvas, { passes, uniformByteSize, sampleCount = 4, createGpuBackend, createCanvas2DBackend, }) {
|
|
3
|
+
// Before the ladder, so a duplicate id is reported on every machine rather
|
|
4
|
+
// than only the ones that reach a GPU rung — the bug it prevents is silent
|
|
5
|
+
// and GPU-only, which is precisely why a developer on the Canvas2D fallback
|
|
6
|
+
// must not be the one who cannot see it.
|
|
7
|
+
assertUniquePassIds(passes);
|
|
8
|
+
// Each rung's reason is collected rather than dropped in the console: if
|
|
9
|
+
// Canvas2D — the rung that cannot itself be fallen back from — also fails,
|
|
10
|
+
// "Canvas 2D context not available" on its own says nothing about *why* the
|
|
11
|
+
// two rungs above it declined, and those are usually the interesting part (a
|
|
12
|
+
// committed context from a previous init, a driver that lost the device). The
|
|
13
|
+
// error UI's stack-trace dialog walks `AggregateError.errors`, so bundling
|
|
14
|
+
// them is what puts them in front of whoever reports the bug.
|
|
15
|
+
const ladderFailures = [];
|
|
16
|
+
const hal = await createGpuHal(canvas, {
|
|
17
|
+
passes,
|
|
18
|
+
uniformByteSize,
|
|
19
|
+
sampleCount,
|
|
20
|
+
failures: ladderFailures,
|
|
21
|
+
});
|
|
22
|
+
if (hal) {
|
|
23
|
+
return createGpuBackend(hal);
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return createCanvas2DBackend(canvas);
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
if (ladderFailures.length === 0) {
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
// `e` is the last entry of the errors array, which says more than `cause`
|
|
33
|
+
// would: it places the Canvas2D failure in order behind the WebGPU and
|
|
34
|
+
// WebGL ones.
|
|
35
|
+
// oxlint-disable-next-line eslint/preserve-caught-error
|
|
36
|
+
throw new AggregateError([...ladderFailures, e], `No rendering backend could be created for this canvas: ${e}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Factory for a **Canvas2D-only** display — one that ships no GPU shader path.
|
|
41
|
+
*
|
|
42
|
+
* Every display must already provide a Canvas2D draw function (SVG export needs
|
|
43
|
+
* it), so for a display whose data volume never demands the GPU (gene-scale
|
|
44
|
+
* annotations, low-density score tracks, text/sequence), the GPU renderer +
|
|
45
|
+
* shader are pure overhead. Such a display skips `createRenderingBackend`'s HAL
|
|
46
|
+
* ladder entirely and returns its Canvas2D backend directly through this
|
|
47
|
+
* helper. The backend then plugs into the exact same `RenderLifecycleMixin` /
|
|
48
|
+
* `DisplayChrome` machinery as a GPU display — the lifecycle is backend-
|
|
49
|
+
* agnostic, so nothing downstream knows or cares there's no HAL.
|
|
50
|
+
*
|
|
51
|
+
* Reference: `plugins/sequence`'s `SequenceRenderer`. Promote to the dual-path
|
|
52
|
+
* `createRenderingBackend` only once a profile shows Canvas2D can't keep 60fps
|
|
53
|
+
* at the display's real feature counts (≳100K features/frame — see
|
|
54
|
+
* RFC-001 §3a).
|
|
55
|
+
*/
|
|
56
|
+
export function createCanvas2DBackend(canvas, createCanvas2DRenderingBackend) {
|
|
57
|
+
return Promise.resolve(createCanvas2DRenderingBackend(canvas));
|
|
58
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mutually-exclusive visual state of a GPU display. Computing it in one
|
|
3
|
+
* place means the precedence — renderError > tooLarge > error > loading > ready
|
|
4
|
+
* — lives in a single function instead of being re-encoded by subtraction
|
|
5
|
+
* (`&& !regionTooLarge && !error && !renderError`) in every display model AND
|
|
6
|
+
* mirrored a second time by `DisplayChrome`'s JSX order. `DisplayChrome`
|
|
7
|
+
* switches on it; the loading scrim's visibility is just
|
|
8
|
+
* `displayPhase === 'loading'`, computed once in the chrome.
|
|
9
|
+
*
|
|
10
|
+
* `renderError` and `tooLarge` replace the whole display subtree (their own
|
|
11
|
+
* root element, so the transition unmounts the canvas → `canvasRef(null)` →
|
|
12
|
+
* `backend.dispose()`). `error` and `loading` are overlays rendered *over* the
|
|
13
|
+
* still-mounted canvas, so they share the `ready` branch's root.
|
|
14
|
+
*/
|
|
15
|
+
export type DisplayPhase = 'renderError' | DisplayStatusPhase;
|
|
16
|
+
/**
|
|
17
|
+
* The phases a display without a rendering backend can be in — every phase
|
|
18
|
+
* except the one that reports a backend failure. Its own type because
|
|
19
|
+
* `renderError` is the only phase whose UI needs something no model can supply
|
|
20
|
+
* (the backend hook's `retry()`), which is exactly the line between the shared
|
|
21
|
+
* status chrome and the GPU chrome wrapped around it: `DisplayStatusChrome`
|
|
22
|
+
* takes this, `DisplayChrome` takes the wider union and peels off `renderError`
|
|
23
|
+
* before delegating. A main-thread SVG display (arc) computes this one and is
|
|
24
|
+
* then *unable* to claim a phase whose banner has no retry to offer.
|
|
25
|
+
*/
|
|
26
|
+
export type DisplayStatusPhase = 'tooLarge' | 'error' | 'loading' | 'ready';
|
|
27
|
+
export interface DisplayStatusPhaseInputs {
|
|
28
|
+
regionTooLarge: boolean;
|
|
29
|
+
error: unknown;
|
|
30
|
+
}
|
|
31
|
+
export interface DisplayPhaseInputs extends DisplayStatusPhaseInputs {
|
|
32
|
+
renderError: unknown;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `loading` is the only display-specific axis: MultiRegion passes
|
|
36
|
+
* "!isReady || stale viewport", Global passes "fetch in flight", sequence
|
|
37
|
+
* layers a zoom gate on top. The terminal precedence above it is identical for
|
|
38
|
+
* every display, so it lives here — pass the display's loading condition and
|
|
39
|
+
* get the resolved phase back.
|
|
40
|
+
*
|
|
41
|
+
* `loading` is a **thunk**, evaluated only after the three terminal states are
|
|
42
|
+
* ruled out. This is load-bearing for MobX, not a micro-optimization: the
|
|
43
|
+
* loading condition typically reads the containing view (`visibleRegions`,
|
|
44
|
+
* `loadedRegions`, …), a large reactive dependency set. Evaluating it eagerly
|
|
45
|
+
* would make every reader of `displayPhase` subscribe to all of it even while a
|
|
46
|
+
* banner is up — and `DisplayChrome`'s observer, re-firing on that churn during
|
|
47
|
+
* a terminal state, then fails to commit the banner subtree to the DOM (the
|
|
48
|
+
* React-reconciliation hazard documented in DisplayChrome.tsx). Short-circuiting
|
|
49
|
+
* keeps the tracked set to just the terminal flags when one is active, matching
|
|
50
|
+
* the old direct-read early-returns.
|
|
51
|
+
*/
|
|
52
|
+
export declare function computeDisplayPhase({ renderError, regionTooLarge, error }: DisplayPhaseInputs, loading: () => boolean): DisplayPhase;
|
|
53
|
+
/**
|
|
54
|
+
* The five terms the `loading` condition is built from, minus the one that is a
|
|
55
|
+
* thunk. All cheap flags on the display itself, so they are read eagerly the
|
|
56
|
+
* same way `computeDisplayPhase` reads its terminals.
|
|
57
|
+
*/
|
|
58
|
+
export interface DisplayLoadingInputs {
|
|
59
|
+
/**
|
|
60
|
+
* The display is deliberately showing a static message instead of data
|
|
61
|
+
* (sequence past base resolution, LD with the triangle off), so no scrim at
|
|
62
|
+
* all. Suppresses every term below — which is why `rendersCanvas: false` is
|
|
63
|
+
* not a substitute: that one drops the pre-first-paint term alone, leaving the
|
|
64
|
+
* fetch terms free to scrim over the placeholder, and `isLoadingOrCanceled`
|
|
65
|
+
* includes a cancel that is deliberately durable.
|
|
66
|
+
*/
|
|
67
|
+
fetchInert: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The view holds no content block, so there is nothing to fetch and nothing
|
|
70
|
+
* to paint: `showAllRegions` over a region set whose every member falls under
|
|
71
|
+
* `minimumBlockWidth`, which is a scaffold-level assembly and little else (see
|
|
72
|
+
* `viewportEmpty`). Suppresses every term below for the same reason
|
|
73
|
+
* `fetchInert` does, and it is the pre-first-paint one that makes it
|
|
74
|
+
* necessary: no fetch is issued, so `canvasDrawn` is never set and the scrim
|
|
75
|
+
* would sit there for as long as the viewport stays off content.
|
|
76
|
+
*/
|
|
77
|
+
viewportEmpty: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* A fetch is in flight, or the user canceled one. **Never a bare
|
|
80
|
+
* `isLoading`**: cancel drops the stop token synchronously, so without the
|
|
81
|
+
* second term the phase falls to `ready` and the overlay carrying the Retry
|
|
82
|
+
* button unmounts, leaving the display stopped and empty with nothing to
|
|
83
|
+
* click.
|
|
84
|
+
*/
|
|
85
|
+
isLoadingOrCanceled: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Whether this display paints a canvas in its current configuration. Gates
|
|
88
|
+
* the pre-first-paint term alone, because a display that is showing a
|
|
89
|
+
* deliberate non-canvas placeholder (LD with the triangle off) never flips
|
|
90
|
+
* `canvasDrawn` and would sit under the scrim forever.
|
|
91
|
+
*/
|
|
92
|
+
rendersCanvas: boolean;
|
|
93
|
+
/** first paint has happened */
|
|
94
|
+
canvasDrawn: boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The `loading` term itself — the one axis `computeDisplayPhase` leaves to its
|
|
98
|
+
* caller — expressed once for both LGV display families.
|
|
99
|
+
*
|
|
100
|
+
* It was two hand-written expressions, and they had already drifted apart in
|
|
101
|
+
* three ways that were equivalent only by accident: the per-region family spelled
|
|
102
|
+
* the cancel term out by hand (`!isReady || … || fetchCanceled`, over a bare
|
|
103
|
+
* `isLoading`) rather than reading `isLoadingOrCanceled`, and each family carried
|
|
104
|
+
* exactly one of the two suppression hooks — `fetchInert` on per-region,
|
|
105
|
+
* `rendersCanvas` on global — so a display in the other family could express its
|
|
106
|
+
* case only by overriding `displayPhase`, which is the thing both hooks exist to
|
|
107
|
+
* prevent. Single-sourcing the expression is the same argument the precedence
|
|
108
|
+
* above already won, one level down: a term added here reaches every display,
|
|
109
|
+
* instead of reaching whichever family the author happened to be reading.
|
|
110
|
+
*
|
|
111
|
+
* The four inputs all now live on a mixin every foundation composes —
|
|
112
|
+
* `fetchInert` / `isLoadingOrCanceled` on `FetchMixin`, `rendersCanvas` /
|
|
113
|
+
* `canvasDrawn` on `RenderLifecycleMixin` — so `foundationDisplayPhase` passes
|
|
114
|
+
* the model straight through and the only thing a family supplies is
|
|
115
|
+
* `viewportCurrent`: per-region its staleness predicate, global `() => true` (it
|
|
116
|
+
* keeps the last frame up through a refetch rather than scrimming). Two hooks
|
|
117
|
+
* used to be hard-coded per family instead, which is how the second drift
|
|
118
|
+
* happened after the first was fixed — LD needed `fetchInert` and the
|
|
119
|
+
* global family did not have it.
|
|
120
|
+
*
|
|
121
|
+
* `viewportCurrent` is a **thunk** for the same MobX reason `computeDisplayPhase`'s
|
|
122
|
+
* `loading` is: it reads the containing view (`visibleRegions`, `loadedRegions`),
|
|
123
|
+
* while the flag inputs above are on the display itself. Short-circuiting keeps a
|
|
124
|
+
* suppressed or already-loading display from subscribing to viewport churn.
|
|
125
|
+
*
|
|
126
|
+
* `hostMounted` is a thunk for the same reason, and gates the **pre-first-paint
|
|
127
|
+
* term only**. A container that mounts a view's body lazily — `ViewContainer`
|
|
128
|
+
* does, to stay under the WebGL2 context ceiling — leaves an off-screen view
|
|
129
|
+
* with no canvas, so `markCanvasDrawn` is never called and `!canvasDrawn` stays
|
|
130
|
+
* true with nothing able to resolve it. That parked `[data-app-phase="ready"]`
|
|
131
|
+
* for the whole app on a view the user could not see, and every capture and e2e
|
|
132
|
+
* gate waiting on it burned its full timeout.
|
|
133
|
+
*
|
|
134
|
+
* It is a required flag on every view and on the duck-typed host contracts, not
|
|
135
|
+
* an optional one defaulted here: a stand-in that forgot it would silently
|
|
136
|
+
* excuse the first paint and let a readiness gate fire over a view that had not
|
|
137
|
+
* started.
|
|
138
|
+
*
|
|
139
|
+
* The fetch terms deliberately stay live while unmounted: an off-screen display
|
|
140
|
+
* still fetches, so reporting it idle would let a readiness gate fire over work
|
|
141
|
+
* genuinely in flight — and during cold load `visible` is false until the
|
|
142
|
+
* IntersectionObserver's first callback, which is exactly when a display must
|
|
143
|
+
* not flash `ready`. Only the paint it will never do is excused.
|
|
144
|
+
*/
|
|
145
|
+
export declare function computeLoadingTerm({ fetchInert, viewportEmpty, isLoadingOrCanceled, rendersCanvas, canvasDrawn, }: DisplayLoadingInputs, viewportCurrent: () => boolean, hostMounted?: () => boolean): boolean;
|
|
146
|
+
/**
|
|
147
|
+
* The same ranking minus `renderError`, for a display with no rendering backend
|
|
148
|
+
* to fail (arc's main-thread SVG). `computeDisplayPhase` delegates here rather
|
|
149
|
+
* than restating the tail, so there is still exactly one place the order lives —
|
|
150
|
+
* and the narrower return type is what lets `DisplayStatusChrome` accept a
|
|
151
|
+
* backend-less display without either a cast or a dead branch.
|
|
152
|
+
*
|
|
153
|
+
* `loading` is a thunk here for the same reason as above.
|
|
154
|
+
*/
|
|
155
|
+
export declare function computeDisplayStatusPhase({ regionTooLarge, error }: DisplayStatusPhaseInputs, loading: () => boolean): DisplayStatusPhase;
|