@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.
Files changed (164) hide show
  1. package/LICENSE +201 -0
  2. package/esm/OverlayCanvas.d.ts +29 -0
  3. package/esm/OverlayCanvas.js +43 -0
  4. package/esm/RenderCanvas.d.ts +76 -0
  5. package/esm/RenderCanvas.js +38 -0
  6. package/esm/RenderLifecycleMixin.d.ts +180 -0
  7. package/esm/RenderLifecycleMixin.js +279 -0
  8. package/esm/ScrollLockedOverlay.d.ts +28 -0
  9. package/esm/ScrollLockedOverlay.js +42 -0
  10. package/esm/blockClipUtils.d.ts +27 -0
  11. package/esm/blockClipUtils.js +101 -0
  12. package/esm/canvas2dUtils.d.ts +372 -0
  13. package/esm/canvas2dUtils.js +564 -0
  14. package/esm/canvasContext.d.ts +68 -0
  15. package/esm/canvasContext.js +74 -0
  16. package/esm/colorRampLut.d.ts +17 -0
  17. package/esm/colorRampLut.js +25 -0
  18. package/esm/coverageBand.d.ts +107 -0
  19. package/esm/coverageBand.js +109 -0
  20. package/esm/coverageBandBuffers.d.ts +30 -0
  21. package/esm/coverageBandBuffers.js +13 -0
  22. package/esm/createRenderingBackend.d.ts +52 -0
  23. package/esm/createRenderingBackend.js +58 -0
  24. package/esm/displayPhase.d.ts +155 -0
  25. package/esm/displayPhase.js +97 -0
  26. package/esm/globalRenderingBackend.d.ts +90 -0
  27. package/esm/globalRenderingBackend.js +53 -0
  28. package/esm/gpuDevice.d.ts +91 -0
  29. package/esm/gpuDevice.js +277 -0
  30. package/esm/graphicsCapabilities.d.ts +98 -0
  31. package/esm/graphicsCapabilities.js +165 -0
  32. package/esm/hal/createHal.d.ts +22 -0
  33. package/esm/hal/createHal.js +91 -0
  34. package/esm/hal/deviceGpuCache.d.ts +36 -0
  35. package/esm/hal/deviceGpuCache.js +120 -0
  36. package/esm/hal/gpuHalBase.d.ts +55 -0
  37. package/esm/hal/gpuHalBase.js +81 -0
  38. package/esm/hal/index.d.ts +7 -0
  39. package/esm/hal/index.js +5 -0
  40. package/esm/hal/mockHal.d.ts +153 -0
  41. package/esm/hal/mockHal.js +297 -0
  42. package/esm/hal/oomReporter.d.ts +15 -0
  43. package/esm/hal/oomReporter.js +22 -0
  44. package/esm/hal/passIds.d.ts +22 -0
  45. package/esm/hal/passIds.js +36 -0
  46. package/esm/hal/regionRegistry.d.ts +11 -0
  47. package/esm/hal/regionRegistry.js +66 -0
  48. package/esm/hal/types.d.ts +116 -0
  49. package/esm/hal/types.js +1 -0
  50. package/esm/hal/webgl2Hal.d.ts +47 -0
  51. package/esm/hal/webgl2Hal.js +490 -0
  52. package/esm/hal/webgpuHal.d.ts +169 -0
  53. package/esm/hal/webgpuHal.js +722 -0
  54. package/esm/installUpload.d.ts +91 -0
  55. package/esm/installUpload.js +94 -0
  56. package/esm/instanceCache.d.ts +43 -0
  57. package/esm/instanceCache.js +53 -0
  58. package/esm/instancePass.d.ts +50 -0
  59. package/esm/instancePass.js +34 -0
  60. package/esm/keyedRenderingBackend.d.ts +41 -0
  61. package/esm/keyedRenderingBackend.js +19 -0
  62. package/esm/mapUploadSync.d.ts +31 -0
  63. package/esm/mapUploadSync.js +45 -0
  64. package/esm/namedReactions.d.ts +29 -0
  65. package/esm/namedReactions.js +62 -0
  66. package/esm/perRegionRenderingBackend.d.ts +114 -0
  67. package/esm/perRegionRenderingBackend.js +93 -0
  68. package/esm/recoveryBudget.d.ts +40 -0
  69. package/esm/recoveryBudget.js +56 -0
  70. package/esm/regionDataMap.d.ts +60 -0
  71. package/esm/regionDataMap.js +98 -0
  72. package/esm/renderBlock.d.ts +25 -0
  73. package/esm/renderBlock.js +10 -0
  74. package/esm/renderingBackendBase.d.ts +71 -0
  75. package/esm/renderingBackendBase.js +50 -0
  76. package/esm/shaders/antialias.js.generated.d.ts +4 -0
  77. package/esm/shaders/antialias.js.generated.js +25 -0
  78. package/esm/shaders/capsule.generated.d.ts +1 -0
  79. package/esm/shaders/capsule.generated.js +3 -0
  80. package/esm/shaders/colorRampLut.generated.d.ts +1 -0
  81. package/esm/shaders/colorRampLut.generated.js +3 -0
  82. package/esm/shaders/coverageBand.generated.d.ts +2 -0
  83. package/esm/shaders/coverageBand.generated.js +4 -0
  84. package/esm/shaders/coverageBar.generated.d.ts +4 -0
  85. package/esm/shaders/coverageBar.generated.js +6 -0
  86. package/esm/shaders/coverageBar.iface.generated.d.ts +85 -0
  87. package/esm/shaders/coverageBar.iface.generated.js +114 -0
  88. package/esm/shaders/coverageIndicator.generated.d.ts +4 -0
  89. package/esm/shaders/coverageIndicator.generated.js +6 -0
  90. package/esm/shaders/coverageIndicator.iface.generated.d.ts +85 -0
  91. package/esm/shaders/coverageIndicator.iface.generated.js +114 -0
  92. package/esm/shaders/coverageInterbase.generated.d.ts +4 -0
  93. package/esm/shaders/coverageInterbase.generated.js +6 -0
  94. package/esm/shaders/coverageInterbase.iface.generated.d.ts +93 -0
  95. package/esm/shaders/coverageInterbase.iface.generated.js +134 -0
  96. package/esm/shaders/coverageMod.generated.d.ts +4 -0
  97. package/esm/shaders/coverageMod.generated.js +6 -0
  98. package/esm/shaders/coverageMod.iface.generated.d.ts +97 -0
  99. package/esm/shaders/coverageMod.iface.generated.js +144 -0
  100. package/esm/shaders/coverageSnp.generated.d.ts +4 -0
  101. package/esm/shaders/coverageSnp.generated.js +6 -0
  102. package/esm/shaders/coverageSnp.iface.generated.d.ts +97 -0
  103. package/esm/shaders/coverageSnp.iface.generated.js +144 -0
  104. package/esm/shaders/hpmath.js.generated.d.ts +3 -0
  105. package/esm/shaders/hpmath.js.generated.js +48 -0
  106. package/esm/shaders/pointGlyph.generated.d.ts +1 -0
  107. package/esm/shaders/pointGlyph.generated.js +3 -0
  108. package/esm/shaders/pointGlyph.js.generated.d.ts +1 -0
  109. package/esm/shaders/pointGlyph.js.generated.js +7 -0
  110. package/esm/shaders/rowRect.generated.d.ts +3 -0
  111. package/esm/shaders/rowRect.generated.js +5 -0
  112. package/esm/shaders/rowRect.js.generated.d.ts +2 -0
  113. package/esm/shaders/rowRect.js.generated.js +13 -0
  114. package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
  115. package/esm/shaders/scoreScale.js.generated.js +46 -0
  116. package/esm/slangPass.d.ts +49 -0
  117. package/esm/slangPass.js +22 -0
  118. package/esm/useEventCallback.d.ts +1 -0
  119. package/esm/useEventCallback.js +19 -0
  120. package/esm/useGraphicsCapabilities.d.ts +17 -0
  121. package/esm/useGraphicsCapabilities.js +38 -0
  122. package/esm/useRenderingBackend.d.ts +83 -0
  123. package/esm/useRenderingBackend.js +422 -0
  124. package/esm/useTabVisibilityRerender.d.ts +13 -0
  125. package/esm/useTabVisibilityRerender.js +39 -0
  126. package/esm/webgpuUtils.d.ts +23 -0
  127. package/esm/webgpuUtils.js +76 -0
  128. package/package.json +91 -0
  129. package/src/shaders/antialias.js.generated.ts +33 -0
  130. package/src/shaders/antialias.slang +112 -0
  131. package/src/shaders/capsule.generated.ts +4 -0
  132. package/src/shaders/capsule.slang +62 -0
  133. package/src/shaders/colorPack.slang +26 -0
  134. package/src/shaders/colorRampLut.generated.ts +4 -0
  135. package/src/shaders/colorRampLut.slang +51 -0
  136. package/src/shaders/coverageBand.generated.ts +6 -0
  137. package/src/shaders/coverageBand.slang +276 -0
  138. package/src/shaders/coverageBar.generated.ts +10 -0
  139. package/src/shaders/coverageBar.iface.generated.ts +172 -0
  140. package/src/shaders/coverageBar.slang +37 -0
  141. package/src/shaders/coverageIndicator.generated.ts +10 -0
  142. package/src/shaders/coverageIndicator.iface.generated.ts +172 -0
  143. package/src/shaders/coverageIndicator.slang +61 -0
  144. package/src/shaders/coverageInterbase.generated.ts +10 -0
  145. package/src/shaders/coverageInterbase.iface.generated.ts +198 -0
  146. package/src/shaders/coverageInterbase.slang +63 -0
  147. package/src/shaders/coverageMod.generated.ts +10 -0
  148. package/src/shaders/coverageMod.iface.generated.ts +211 -0
  149. package/src/shaders/coverageMod.slang +36 -0
  150. package/src/shaders/coverageSnp.generated.ts +10 -0
  151. package/src/shaders/coverageSnp.iface.generated.ts +211 -0
  152. package/src/shaders/coverageSnp.slang +52 -0
  153. package/src/shaders/diagonalGrid.slang +25 -0
  154. package/src/shaders/glyphEdgeAlpha.test.ts +241 -0
  155. package/src/shaders/hpmath.js.generated.ts +47 -0
  156. package/src/shaders/hpmath.slang +226 -0
  157. package/src/shaders/pointGlyph.generated.ts +4 -0
  158. package/src/shaders/pointGlyph.js.generated.ts +9 -0
  159. package/src/shaders/pointGlyph.slang +93 -0
  160. package/src/shaders/rowRect.generated.ts +8 -0
  161. package/src/shaders/rowRect.js.generated.ts +17 -0
  162. package/src/shaders/rowRect.slang +160 -0
  163. package/src/shaders/scoreScale.js.generated.ts +51 -0
  164. 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;