@jbrowse/render-core 5.0.0-beta.13 → 5.0.0-beta.14

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 (153) hide show
  1. package/esm/OverlayCanvas.d.ts +3 -2
  2. package/esm/OverlayCanvas.js +4 -2
  3. package/esm/RenderCanvas.d.ts +4 -3
  4. package/esm/RenderCanvas.js +4 -2
  5. package/esm/ScrollLockedOverlay.d.ts +2 -2
  6. package/esm/ScrollLockedOverlay.js +3 -2
  7. package/esm/blockClipUtils.d.ts +5 -5
  8. package/esm/blockClipUtils.js +4 -3
  9. package/esm/canvas2dUtils.d.ts +15 -21
  10. package/esm/canvas2dUtils.js +25 -55
  11. package/esm/colorRampLut.d.ts +2 -2
  12. package/esm/colorRampLut.js +2 -2
  13. package/esm/coverageBand.d.ts +0 -1
  14. package/esm/coverageBand.js +0 -5
  15. package/esm/coverageBandBuffers.d.ts +8 -5
  16. package/esm/createRenderingBackend.d.ts +1 -1
  17. package/esm/displayPhase.d.ts +5 -9
  18. package/esm/displayPhase.js +2 -2
  19. package/esm/hal/mockHal.d.ts +3 -4
  20. package/esm/hal/mockHal.js +5 -4
  21. package/esm/hal/types.d.ts +5 -5
  22. package/esm/hal/webgl2Hal.d.ts +4 -1
  23. package/esm/hal/webgl2Hal.js +15 -6
  24. package/esm/hal/webgpuHal.js +2 -2
  25. package/esm/linkFeet.d.ts +4 -0
  26. package/esm/linkFeet.js +7 -0
  27. package/esm/marks/barMark.d.ts +1 -1
  28. package/esm/marks/colorFill.d.ts +4 -4
  29. package/esm/marks/colorFill.js +4 -4
  30. package/esm/marks/connectorMark.d.ts +48 -0
  31. package/esm/marks/connectorMark.js +285 -0
  32. package/esm/marks/drawAgainstHit.d.ts +1 -1
  33. package/esm/marks/drawAgainstHit.js +4 -4
  34. package/esm/marks/glyphPaint.d.ts +5 -4
  35. package/esm/marks/glyphPaint.js +17 -7
  36. package/esm/marks/index.d.ts +6 -3
  37. package/esm/marks/index.js +5 -3
  38. package/esm/marks/lineMark.d.ts +44 -10
  39. package/esm/marks/lineMark.js +20 -19
  40. package/esm/marks/linkMark.d.ts +22 -5
  41. package/esm/marks/linkMark.js +103 -25
  42. package/esm/marks/markBackend.d.ts +1 -1
  43. package/esm/marks/markBackend.js +1 -1
  44. package/esm/marks/markRamp.d.ts +11 -11
  45. package/esm/marks/markRamp.js +9 -9
  46. package/esm/marks/pointMark.d.ts +11 -5
  47. package/esm/marks/pointMark.js +34 -9
  48. package/esm/marks/rowLane.d.ts +3 -3
  49. package/esm/marks/rowLane.js +2 -2
  50. package/esm/marks/rowTable.d.ts +5 -5
  51. package/esm/marks/rowTable.js +4 -4
  52. package/esm/marks/ruleMark.js +1 -0
  53. package/esm/marks/spanMark.d.ts +4 -4
  54. package/esm/marks/types.d.ts +4 -4
  55. package/esm/marks/types.js +5 -0
  56. package/esm/perRegionRenderingBackend.d.ts +1 -1
  57. package/esm/perRegionRenderingBackend.js +1 -1
  58. package/esm/regionDataMap.d.ts +2 -2
  59. package/esm/regionDataMap.js +2 -2
  60. package/esm/shaders/capsule.js.generated.d.ts +1 -1
  61. package/esm/shaders/capsule.js.generated.js +2 -2
  62. package/esm/shaders/connectorMark.consts.generated.d.ts +11 -0
  63. package/esm/shaders/connectorMark.consts.generated.js +13 -0
  64. package/esm/shaders/{coverageIndicator.generated.d.ts → connectorMark.generated.d.ts} +2 -1
  65. package/esm/shaders/connectorMark.generated.js +8 -0
  66. package/esm/shaders/connectorMark.glsl.generated.d.ts +2 -0
  67. package/esm/shaders/connectorMark.glsl.generated.js +4 -0
  68. package/esm/shaders/connectorMark.iface.generated.d.ts +88 -0
  69. package/esm/shaders/connectorMark.iface.generated.js +1194 -0
  70. package/esm/shaders/connectorMark.js.generated.d.ts +5 -0
  71. package/esm/shaders/connectorMark.js.generated.js +47 -0
  72. package/esm/shaders/connectorMark.wgsl.generated.d.ts +1 -0
  73. package/esm/shaders/connectorMark.wgsl.generated.js +3 -0
  74. package/esm/shaders/coverageInterbase.glsl.generated.d.ts +1 -1
  75. package/esm/shaders/coverageInterbase.glsl.generated.js +1 -1
  76. package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -1
  77. package/esm/shaders/coverageInterbase.wgsl.generated.js +1 -1
  78. package/esm/shaders/curveDistance.js.generated.d.ts +3 -0
  79. package/esm/shaders/curveDistance.js.generated.js +104 -0
  80. package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -2
  81. package/esm/shaders/lineCenterMark.glsl.generated.js +2 -2
  82. package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -1
  83. package/esm/shaders/lineCenterMark.wgsl.generated.js +1 -1
  84. package/esm/shaders/linkMark.consts.generated.d.ts +1 -0
  85. package/esm/shaders/linkMark.consts.generated.js +1 -0
  86. package/esm/shaders/linkMark.glsl.generated.d.ts +2 -2
  87. package/esm/shaders/linkMark.glsl.generated.js +2 -2
  88. package/esm/shaders/linkMark.js.generated.d.ts +2 -1
  89. package/esm/shaders/linkMark.js.generated.js +30 -1
  90. package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -1
  91. package/esm/shaders/linkMark.wgsl.generated.js +1 -1
  92. package/esm/shaders/pointMark.glsl.generated.d.ts +1 -1
  93. package/esm/shaders/pointMark.glsl.generated.js +1 -1
  94. package/esm/shaders/pointMark.iface.generated.d.ts +10 -8
  95. package/esm/shaders/pointMark.iface.generated.js +18 -16
  96. package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -1
  97. package/esm/shaders/pointMark.wgsl.generated.js +1 -1
  98. package/package.json +7 -4
  99. package/src/shaders/arcHull.test.ts +87 -0
  100. package/src/shaders/barMark.slang +2 -2
  101. package/src/shaders/capsule.js.generated.ts +2 -2
  102. package/src/shaders/capsule.slang +69 -24
  103. package/src/shaders/clipStrip.slang +1 -1
  104. package/src/shaders/colorPack.slang +2 -2
  105. package/src/shaders/colorRampLut.slang +7 -7
  106. package/src/shaders/connectorMark.consts.generated.ts +24 -0
  107. package/src/shaders/connectorMark.generated.ts +12 -0
  108. package/src/shaders/connectorMark.glsl.generated.ts +6 -0
  109. package/src/shaders/connectorMark.iface.generated.ts +1270 -0
  110. package/src/shaders/connectorMark.js.generated.ts +52 -0
  111. package/src/shaders/connectorMark.slang +287 -0
  112. package/src/shaders/connectorMark.wgsl.generated.ts +4 -0
  113. package/src/shaders/coverageBand.slang +10 -12
  114. package/src/shaders/coverageInterbase.glsl.generated.ts +1 -1
  115. package/src/shaders/coverageInterbase.slang +24 -21
  116. package/src/shaders/coverageInterbase.wgsl.generated.ts +1 -1
  117. package/src/shaders/coverageSnp.slang +1 -1
  118. package/src/shaders/curveDistance.js.generated.ts +100 -0
  119. package/src/shaders/curveDistance.slang +104 -1
  120. package/src/shaders/farDome.test.ts +117 -0
  121. package/src/shaders/hpClampFloor.test.ts +0 -4
  122. package/src/shaders/lineCenterMark.glsl.generated.ts +2 -2
  123. package/src/shaders/lineCenterMark.slang +14 -22
  124. package/src/shaders/lineCenterMark.wgsl.generated.ts +1 -1
  125. package/src/shaders/lineCommon.slang +10 -10
  126. package/src/shaders/lineStepMark.slang +5 -5
  127. package/src/shaders/linkMark.consts.generated.ts +2 -0
  128. package/src/shaders/linkMark.glsl.generated.ts +2 -2
  129. package/src/shaders/linkMark.js.generated.ts +27 -1
  130. package/src/shaders/linkMark.slang +58 -18
  131. package/src/shaders/linkMark.wgsl.generated.ts +1 -1
  132. package/src/shaders/markColor.slang +10 -10
  133. package/src/shaders/pointMark.glsl.generated.ts +1 -1
  134. package/src/shaders/pointMark.iface.generated.ts +19 -16
  135. package/src/shaders/pointMark.slang +12 -8
  136. package/src/shaders/pointMark.wgsl.generated.ts +1 -1
  137. package/src/shaders/rowRect.slang +3 -3
  138. package/src/shaders/rowTable.slang +7 -7
  139. package/src/shaders/scoreScale.slang +1 -1
  140. package/src/shaders/spanMark.slang +5 -5
  141. package/src/shaders/viewRegion.slang +13 -0
  142. package/esm/shaders/coverageIndicator.generated.js +0 -7
  143. package/esm/shaders/coverageIndicator.glsl.generated.d.ts +0 -2
  144. package/esm/shaders/coverageIndicator.glsl.generated.js +0 -4
  145. package/esm/shaders/coverageIndicator.iface.generated.d.ts +0 -85
  146. package/esm/shaders/coverageIndicator.iface.generated.js +0 -114
  147. package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +0 -1
  148. package/esm/shaders/coverageIndicator.wgsl.generated.js +0 -3
  149. package/src/shaders/coverageIndicator.generated.ts +0 -11
  150. package/src/shaders/coverageIndicator.glsl.generated.ts +0 -6
  151. package/src/shaders/coverageIndicator.iface.generated.ts +0 -172
  152. package/src/shaders/coverageIndicator.slang +0 -61
  153. package/src/shaders/coverageIndicator.wgsl.generated.ts +0 -4
@@ -21,9 +21,10 @@
21
21
  * `inset: 0` therefore drew everything at twice its x on a retina display and
22
22
  * dropped the right half off the edge, while looking entirely plausible.
23
23
  */
24
- export default function OverlayCanvas({ width, height, draw, 'data-testid': testId, }: {
24
+ declare const OverlayCanvas: ({ width, height, draw, 'data-testid': testId, }: {
25
25
  width: number;
26
26
  height: number;
27
27
  draw: (ctx: CanvasRenderingContext2D) => void;
28
28
  'data-testid'?: string;
29
- }): import("react").JSX.Element;
29
+ }) => import("react").JSX.Element;
30
+ export default OverlayCanvas;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useRef } from 'react';
3
+ import { observer } from 'mobx-react';
3
4
  import { getPreparedCanvas2D } from './canvas2dUtils.js';
4
5
  /**
5
6
  * An absolutely-positioned, non-interactive Canvas2D layer that composites over
@@ -24,7 +25,7 @@ import { getPreparedCanvas2D } from './canvas2dUtils.js';
24
25
  * `inset: 0` therefore drew everything at twice its x on a retina display and
25
26
  * dropped the right half off the edge, while looking entirely plausible.
26
27
  */
27
- export default function OverlayCanvas({ width, height, draw, 'data-testid': testId, }) {
28
+ const OverlayCanvas = observer(function OverlayCanvas({ width, height, draw, 'data-testid': testId, }) {
28
29
  const canvasRef = useRef(null);
29
30
  useEffect(() => {
30
31
  const ctx = getPreparedCanvas2D(canvasRef.current, width, height);
@@ -40,4 +41,5 @@ export default function OverlayCanvas({ width, height, draw, 'data-testid': test
40
41
  height,
41
42
  pointerEvents: 'none',
42
43
  } }));
43
- }
44
+ });
45
+ export default OverlayCanvas;
@@ -42,7 +42,7 @@ export interface RenderCanvasHandle {
42
42
  * shared canvas repaints unconditionally, so `canvasDrawn` says nothing
43
43
  * (ADR-009's scope clause) — so `drawn` is passed at the call site.
44
44
  */
45
- export default function RenderCanvas({ handle, drawn, phase, ...canvasProps }: {
45
+ declare const RenderCanvas: ({ handle, drawn, phase, ...canvasProps }: {
46
46
  handle: RenderCanvasHandle;
47
47
  /**
48
48
  * Whether this canvas holds finished content — each view's own `settled`, the
@@ -59,7 +59,7 @@ export default function RenderCanvas({ handle, drawn, phase, ...canvasProps }: {
59
59
  drawn: boolean;
60
60
  /**
61
61
  * The surface's own mutually-exclusive state, published as
62
- * `data-display-phase` — the same attribute `DisplayStatusChrome` gives every
62
+ * `data-display-phase` — the same attribute `DisplayChrome` gives every
63
63
  * LGV display, and the same split of meaning: `drawn` above is FINISHED
64
64
  * CONTENT, this is still WORKING, and an error separates them.
65
65
  *
@@ -73,4 +73,5 @@ export default function RenderCanvas({ handle, drawn, phase, ...canvasProps }: {
73
73
  * display's phase; `comparativeSurfacePhase` is where that ranking lives.
74
74
  */
75
75
  phase: DisplayStatusPhase;
76
- } & Omit<ComponentPropsWithoutRef<'canvas'>, 'ref'>): import("react").JSX.Element;
76
+ } & Omit<ComponentPropsWithoutRef<'canvas'>, 'ref'>) => import("react").JSX.Element;
77
+ export default RenderCanvas;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { observer } from 'mobx-react';
2
3
  /**
3
4
  * The `<canvas>` for a rendering backend owned **outside** `DisplayChrome` — the
4
5
  * sanctioned drop-to-primitive path for the non-LGV views (dotplot, the synteny
@@ -33,6 +34,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
33
34
  * shared canvas repaints unconditionally, so `canvasDrawn` says nothing
34
35
  * (ADR-009's scope clause) — so `drawn` is passed at the call site.
35
36
  */
36
- export default function RenderCanvas({ handle, drawn, phase, ...canvasProps }) {
37
+ const RenderCanvas = observer(function RenderCanvas({ handle, drawn, phase, ...canvasProps }) {
37
38
  return (_jsx("canvas", { "data-display-drawn": drawn, "data-display-phase": phase, ref: handle.canvasRef, ...canvasProps }, handle.canvasKey));
38
- }
39
+ });
40
+ export default RenderCanvas;
@@ -17,7 +17,7 @@ import React from 'react';
17
17
  * is none so mouse events fall through to the canvas; interactive children
18
18
  * re-enable `pointerEvents:auto`.
19
19
  */
20
- export declare function ScrollLockedOverlay({ scrollTop, viewportHeight, contentHeight, children, }: {
20
+ export declare const ScrollLockedOverlay: ({ scrollTop, viewportHeight, contentHeight, children, }: {
21
21
  /** the model's scroll offset — the same value the GPU shader paints from */
22
22
  scrollTop: number;
23
23
  /** the visible viewport height (clips overflow) */
@@ -25,4 +25,4 @@ export declare function ScrollLockedOverlay({ scrollTop, viewportHeight, content
25
25
  /** the full scrollable content height (the children's coordinate space) */
26
26
  contentHeight: number;
27
27
  children: React.ReactNode;
28
- }): React.JSX.Element;
28
+ }) => React.JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React from 'react';
3
+ import { observer } from 'mobx-react';
3
4
  /**
4
5
  * DOM overlay layer for a display that scrolls a fixed GPU canvas virtually (a
5
6
  * `VerticalScrollbar` overlay + `useVirtualScrollWheel`, everything positioned
@@ -18,7 +19,7 @@ import React from 'react';
18
19
  * is none so mouse events fall through to the canvas; interactive children
19
20
  * re-enable `pointerEvents:auto`.
20
21
  */
21
- export function ScrollLockedOverlay({ scrollTop, viewportHeight, contentHeight, children, }) {
22
+ export const ScrollLockedOverlay = observer(function ScrollLockedOverlay({ scrollTop, viewportHeight, contentHeight, children, }) {
22
23
  return (_jsx("div", { style: {
23
24
  position: 'absolute',
24
25
  top: 0,
@@ -39,4 +40,4 @@ export function ScrollLockedOverlay({ scrollTop, viewportHeight, contentHeight,
39
40
  height: contentHeight,
40
41
  transform: `translateY(${-scrollTop}px)`,
41
42
  }, children: children }) }));
42
- }
43
+ });
@@ -8,12 +8,12 @@ export interface BlockClipResult {
8
8
  pxW: number;
9
9
  pxH: number;
10
10
  /**
11
- * The vertical scale the backing store ACTUALLY got — `hal.resize`'s answer,
12
- * not `getDpr()`. Carried through so a renderer scissoring bands out of one
13
- * canvas (`devicePxBand`) has the ratio the clip was built with rather than
14
- * reconstructing it as `pxH / canvasHeight`, which is a division by a height
15
- * that can be 0.
11
+ * The scale the backing store ACTUALLY got on each axis — `hal.resize`'s
12
+ * answer, not `getDpr()`, which a clamped or browser-shrunk canvas departs
13
+ * from. A renderer working in device px reads these rather than dividing
14
+ * `pxH` by a height that can be 0.
16
15
  */
16
+ scaleX: number;
17
17
  scaleY: number;
18
18
  bpStartHi: number;
19
19
  bpStartLo: number;
@@ -43,9 +43,9 @@ export function writeBpRangeUniforms(uniformF32, offsetF32, clip, reversed) {
43
43
  uniformF32[offsetF32 + 2] = len;
44
44
  }
45
45
  export function clipBlock(block, canvasWidth, canvasHeight,
46
- // The scale the backing store actually got, per axis (`hal.resize`), NOT
47
- // `getDpr()`: only one axis clamps at a time, so past `MAX_CANVAS_DIM_PX` a
48
- // canvas is 2x on one axis and less on the other.
46
+ // The scale the backing store actually got, per axis (`hal.resize`). A
47
+ // capped canvas or a browser-shrunk WebGL drawing buffer falls below
48
+ // `getDpr()` on either axis, and the two axes can differ.
49
49
  scale) {
50
50
  const clamp = clampBlockScissor(block.screenStartPx, block.screenEndPx, canvasWidth);
51
51
  if (!clamp) {
@@ -90,6 +90,7 @@ scale) {
90
90
  pxX,
91
91
  pxW,
92
92
  pxH,
93
+ scaleX: scale.x,
93
94
  scaleY: scale.y,
94
95
  bpStartHi,
95
96
  bpStartLo,
@@ -17,40 +17,34 @@ export declare const MAX_DPR = 2;
17
17
  * geometry at a different scale than the canvas it lands on.
18
18
  *
19
19
  * Two places legitimately want something other than this and should keep saying
20
- * so explicitly: `createSvgRasterCanvas` pins 2x because export goes to a file
21
- * rather than a screen, and the analytics / error-report paths read the raw
20
+ * so explicitly: `createSvgRasterCanvas` rasterizes at 2x because export goes
21
+ * to a file, and the analytics / error-report paths read the raw
22
22
  * global because they are reporting the device, not drawing on it.
23
23
  */
24
24
  export declare function getDpr(): number;
25
25
  export declare const MAX_CANVAS_DIM_PX = 8192;
26
26
  /**
27
27
  * The largest CSS-px extent a canvas may take on one axis before its backing
28
- * store (`× dpr`) crosses the ceiling above.
28
+ * store (`× dpr`) reaches {@link MAX_CANVAS_DIM_PX}.
29
29
  *
30
30
  * For displays that size a canvas to their **content** rather than to a
31
- * viewport — a row stack that grows with the row count, so nothing downstream
32
- * bounds it. Past this the backing store stops tracking the CSS size
33
- * (`syncCanvasSize` clamps it) while scissor/viewport rects are still derived
34
- * as `cssPx * dpr`, so WebGPU rejects an out-of-bounds rect and blanks the
35
- * frame, WebGL clamps and paints at the wrong scale, and the Canvas2D fallback
36
- * stretches its top slice over the whole track.
31
+ * viewport, a row stack that grows with the row count. Past it the canvas still
32
+ * draws, at a reduced resolution on that axis (`syncCanvasSize` reports the
33
+ * scale it got), so the cap is about keeping the rows legible: MAF scrolls its
34
+ * overflow into a viewport, and multi-row divides the cap across its rows.
37
35
  *
38
36
  * A function, not a constant: `getDpr()` follows the window, so a display
39
37
  * reading this into a computed re-derives it when the window moves to another
40
- * screen. Where the cap lands is the caller's — MAF scrolls its overflow into a
41
- * viewport, the multi-row painting divides the cap across its rows so they thin
42
- * out and all stay on screen.
38
+ * screen.
43
39
  */
44
40
  export declare function maxCanvasCssPx(): number;
45
41
  /**
46
42
  * The ratio a canvas's backing store ACTUALLY has to its CSS box, per axis.
47
43
  *
48
- * Equal to `getDpr()` on both axes until `backingPx` clamps, and below it after
49
- * — which is the whole point: every device-px rect a renderer hands the GPU has
50
- * to be derived from this rather than from the true dpr, or a clamped canvas
51
- * gets a viewport taller than the target it draws into and the frame is
52
- * rejected outright. See ARCHITECTURAL_LIMITS.md §"A canvas past
53
- * MAX_CANVAS_DIM_PX renders wrong, not smaller".
44
+ * `getDpr()` on both axes until `backingSize` caps the store, and below it
45
+ * after, so every device-px rect a renderer hands the GPU derives from this.
46
+ * See ARCHITECTURAL_LIMITS.md §"A canvas past
47
+ * the backing-store caps draws at reduced resolution".
54
48
  */
55
49
  export interface CanvasScale {
56
50
  x: number;
@@ -78,9 +72,9 @@ export declare function devicePxSpan(cssStart: number, cssEnd: number, dpr: numb
78
72
  * band clamped to zero height is the caller's cue to skip its passes.
79
73
  *
80
74
  * `dpr` is the scale the canvas ACTUALLY got (`hal.resize`'s return), not
81
- * `getDpr()`: the two part company once the backing store clamps at
82
- * `MAX_CANVAS_DIM_PX`, and past that a band built from the true ratio is taller
83
- * than the target.
75
+ * `getDpr()`: a backing store capped at `MAX_CANVAS_DIM_PX`, or a WebGL drawing
76
+ * buffer the browser shrank, falls below it, and a band built from the true
77
+ * ratio is then taller than the target.
84
78
  */
85
79
  export declare function devicePxBand(top: number, height: number, dpr: number, bufH: number): {
86
80
  top: number;
@@ -17,8 +17,8 @@ export const MAX_DPR = 2;
17
17
  * geometry at a different scale than the canvas it lands on.
18
18
  *
19
19
  * Two places legitimately want something other than this and should keep saying
20
- * so explicitly: `createSvgRasterCanvas` pins 2x because export goes to a file
21
- * rather than a screen, and the analytics / error-report paths read the raw
20
+ * so explicitly: `createSvgRasterCanvas` rasterizes at 2x because export goes
21
+ * to a file, and the analytics / error-report paths read the raw
22
22
  * global because they are reporting the device, not drawing on it.
23
23
  */
24
24
  export function getDpr() {
@@ -26,62 +26,36 @@ export function getDpr() {
26
26
  ? 1
27
27
  : Math.min(devicePixelRatio, MAX_DPR);
28
28
  }
29
- // Conservative ceiling (physical/backing px) for a single canvas dimension.
30
- // Browsers throw `InvalidStateError: Canvas exceeds max size` once the backing
31
- // store crosses their (browser/GPU-specific) limit — Firefox caps the area,
32
- // WebGL caps a texture at MAX_TEXTURE_SIZE (>= 8192 on essentially all real
33
- // hardware), Safari/Chrome cap the dimension. 8192 is under all of these, so a
34
- // canvas whose backing store stays within it on both axes can't throw. Callers
35
- // that can produce arbitrarily tall/wide content (e.g. the MAF rows canvas with
36
- // hundreds of species) bound their CSS size against `MAX_CANVAS_DIM_PX / dpr`.
29
+ // Ceiling on one backing-store dimension. Browsers throw `InvalidStateError:
30
+ // Canvas exceeds max size` past their own limit, and a WebGL implementation
31
+ // caps a texture at MAX_TEXTURE_SIZE, at least 8192 on essentially all real
32
+ // hardware. 8192 is under all of them.
37
33
  export const MAX_CANVAS_DIM_PX = 8192;
38
34
  /**
39
35
  * The largest CSS-px extent a canvas may take on one axis before its backing
40
- * store (`× dpr`) crosses the ceiling above.
36
+ * store (`× dpr`) reaches {@link MAX_CANVAS_DIM_PX}.
41
37
  *
42
38
  * For displays that size a canvas to their **content** rather than to a
43
- * viewport — a row stack that grows with the row count, so nothing downstream
44
- * bounds it. Past this the backing store stops tracking the CSS size
45
- * (`syncCanvasSize` clamps it) while scissor/viewport rects are still derived
46
- * as `cssPx * dpr`, so WebGPU rejects an out-of-bounds rect and blanks the
47
- * frame, WebGL clamps and paints at the wrong scale, and the Canvas2D fallback
48
- * stretches its top slice over the whole track.
39
+ * viewport, a row stack that grows with the row count. Past it the canvas still
40
+ * draws, at a reduced resolution on that axis (`syncCanvasSize` reports the
41
+ * scale it got), so the cap is about keeping the rows legible: MAF scrolls its
42
+ * overflow into a viewport, and multi-row divides the cap across its rows.
49
43
  *
50
44
  * A function, not a constant: `getDpr()` follows the window, so a display
51
45
  * reading this into a computed re-derives it when the window moves to another
52
- * screen. Where the cap lands is the caller's — MAF scrolls its overflow into a
53
- * viewport, the multi-row painting divides the cap across its rows so they thin
54
- * out and all stay on screen.
46
+ * screen.
55
47
  */
56
48
  export function maxCanvasCssPx() {
57
49
  return MAX_CANVAS_DIM_PX / getDpr();
58
50
  }
59
- let warnedCanvasClamp = false;
60
- // CSS px → backing-store px, clamped to the safe ceiling (warning once if the
61
- // clamp engages).
62
- //
63
- // This is a last-resort guard against `InvalidStateError: Canvas exceeds max
64
- // size`, NOT a graceful degradation: once clamped the backing store stops
65
- // tracking the CSS size, so the browser *stretches* the smaller image over the
66
- // larger element, and every GPU scissor/viewport rect — computed as
67
- // `cssPx * getDpr()` by `clipBlock` and friends, against the true dpr rather
68
- // than the effective one — can now exceed the backing store (WebGL clamps
69
- // silently, WebGPU rejects the rect and blanks the frame). So model-level
70
- // sizing must keep canvases within the limit on its own: any display sizing a
71
- // canvas to its *content* rather than its viewport bounds itself with
72
- // `maxCanvasCssPx()` above. Registered in
73
- // agent-docs/reference/ARCHITECTURAL_LIMITS.md with the effective-dpr fix that
74
- // retires it.
75
- function backingPx(cssSize, dpr, axis) {
76
- const value = Math.round(cssSize * dpr);
77
- if (value > MAX_CANVAS_DIM_PX) {
78
- if (!warnedCanvasClamp) {
79
- warnedCanvasClamp = true;
80
- console.warn(`Canvas ${axis} ${value}px exceeds the safe limit ${MAX_CANVAS_DIM_PX}px and was clamped`);
81
- }
82
- return MAX_CANVAS_DIM_PX;
83
- }
84
- return value;
51
+ // CSS px → backing-store px, each axis under MAX_CANVAS_DIM_PX. Every device-px
52
+ // rect derives from the scale this leaves (`syncCanvasSize`, `prepareCanvas`),
53
+ // so a capped canvas draws whole at a lower resolution on that axis.
54
+ function backingSize(cssWidth, cssHeight, dpr) {
55
+ return {
56
+ width: Math.min(Math.round(cssWidth * dpr), MAX_CANVAS_DIM_PX),
57
+ height: Math.min(Math.round(cssHeight * dpr), MAX_CANVAS_DIM_PX),
58
+ };
85
59
  }
86
60
  function effectiveScale(cssPx, backing) {
87
61
  // A zero-width or zero-height canvas has no ratio to report; the true dpr is
@@ -93,9 +67,7 @@ function effectiveScale(cssPx, backing) {
93
67
  // ended up with — both HAL impls and any 2D-canvas backend use this so the dpr
94
68
  // math and style-vs-attribute logic live in one place.
95
69
  export function syncCanvasSize(canvas, width, height) {
96
- const dpr = getDpr();
97
- const pw = backingPx(width, dpr, 'width');
98
- const ph = backingPx(height, dpr, 'height');
70
+ const { width: pw, height: ph } = backingSize(width, height, getDpr());
99
71
  const changed = canvas.width !== pw || canvas.height !== ph;
100
72
  if (changed) {
101
73
  canvas.width = pw;
@@ -120,9 +92,7 @@ export function syncCanvasSize(canvas, width, height) {
120
92
  };
121
93
  }
122
94
  export function prepareCanvas(canvas, ctx, canvasWidth, canvasHeight) {
123
- const dpr = getDpr();
124
- const bufW = backingPx(canvasWidth, dpr, 'width');
125
- const bufH = backingPx(canvasHeight, dpr, 'height');
95
+ const { width: bufW, height: bufH } = backingSize(canvasWidth, canvasHeight, getDpr());
126
96
  if (canvas.width !== bufW || canvas.height !== bufH) {
127
97
  canvas.width = bufW;
128
98
  canvas.height = bufH;
@@ -175,9 +145,9 @@ export function devicePxSpan(cssStart, cssEnd, dpr) {
175
145
  * band clamped to zero height is the caller's cue to skip its passes.
176
146
  *
177
147
  * `dpr` is the scale the canvas ACTUALLY got (`hal.resize`'s return), not
178
- * `getDpr()`: the two part company once the backing store clamps at
179
- * `MAX_CANVAS_DIM_PX`, and past that a band built from the true ratio is taller
180
- * than the target.
148
+ * `getDpr()`: a backing store capped at `MAX_CANVAS_DIM_PX`, or a WebGL drawing
149
+ * buffer the browser shrank, falls below it, and a band built from the true
150
+ * ratio is then taller than the target.
181
151
  */
182
152
  export function devicePxBand(top, height, dpr, bufH) {
183
153
  const span = devicePxSpan(top, top + height, dpr);
@@ -1,13 +1,13 @@
1
1
  import type { GpuHal } from './hal/index.ts';
2
2
  /**
3
- * Entries in a colour-ramp LUT, and so the width of the texture every ramp
3
+ * Entries in a color-ramp LUT, and so the width of the texture every ramp
4
4
  * consumer binds. Off the shader that samples it: `rampColor` divides by this
5
5
  * to land entry i on its own texel center, so a table built to a different
6
6
  * number is a half-shade of drift at every entry.
7
7
  */
8
8
  export declare const COLOR_RAMP_LUT_ENTRIES = 256;
9
9
  /**
10
- * Upload a 256-entry RGBA colour-ramp LUT as each named pass's 256×1 texture —
10
+ * Upload a 256-entry RGBA color-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
12
  * `shaders/colorRampLut.slang`. Called by `createMarkBackend` alone now, for
13
13
  * every mark declaring a `texture`: hic, LD's two shader variants and wiggle
@@ -1,13 +1,13 @@
1
1
  import { RAMP_LUT_ENTRIES } from './shaders/colorRampLut.generated.js';
2
2
  /**
3
- * Entries in a colour-ramp LUT, and so the width of the texture every ramp
3
+ * Entries in a color-ramp LUT, and so the width of the texture every ramp
4
4
  * consumer binds. Off the shader that samples it: `rampColor` divides by this
5
5
  * to land entry i on its own texel center, so a table built to a different
6
6
  * number is a half-shade of drift at every entry.
7
7
  */
8
8
  export const COLOR_RAMP_LUT_ENTRIES = RAMP_LUT_ENTRIES;
9
9
  /**
10
- * Upload a 256-entry RGBA colour-ramp LUT as each named pass's 256×1 texture —
10
+ * Upload a 256-entry RGBA color-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
12
  * `shaders/colorRampLut.slang`. Called by `createMarkBackend` alone now, for
13
13
  * every mark declaring a `texture`: hic, LD's two shader variants and wiggle
@@ -55,7 +55,6 @@ export declare const COVERAGE_CLIP_PASS: InstancePass<Pick<CoverageBandBuffers,
55
55
  export declare const COVERAGE_SNP_PASS: InstancePass<Pick<CoverageBandBuffers, 'snpPackedBuffer'>>;
56
56
  export declare const COVERAGE_MOD_PASS: InstancePass<CoverageBandModBuffer>;
57
57
  export declare const COVERAGE_INTERBASE_PASS: InstancePass<Pick<CoverageBandBuffers, 'interbasePackedBuffer'>>;
58
- export declare const COVERAGE_INDICATOR_PASS: InstancePass<Pick<CoverageBandBuffers, 'indicatorPackedBuffer'>>;
59
58
  /** Palette slots the band paints by name, each a packed ABGR u32. */
60
59
  export interface CoverageBandColors {
61
60
  coverage: number;
@@ -1,6 +1,5 @@
1
1
  import * as coverageBarShader from './shaders/coverageBar.generated.js';
2
2
  import * as coverageClipShader from './shaders/coverageClip.generated.js';
3
- import * as coverageIndicatorShader from './shaders/coverageIndicator.generated.js';
4
3
  import * as coverageInterbaseShader from './shaders/coverageInterbase.generated.js';
5
4
  import * as coverageModShader from './shaders/coverageMod.generated.js';
6
5
  import * as coverageSnpShader from './shaders/coverageSnp.generated.js';
@@ -72,10 +71,6 @@ export const COVERAGE_INTERBASE_PASS = {
72
71
  ...slangPass({ id: 'interbase', mod: coverageInterbaseShader }),
73
72
  pack: data => data.interbasePackedBuffer,
74
73
  };
75
- export const COVERAGE_INDICATOR_PASS = {
76
- ...slangPass({ id: 'indicator', mod: coverageIndicatorShader }),
77
- pack: data => data.indicatorPackedBuffer,
78
- };
79
74
  /**
80
75
  * Fill the coverage band's uniform buffer. Total-write (the generated packer),
81
76
  * so a field left out is a compile error rather than last frame's value.
@@ -1,9 +1,10 @@
1
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
2
+ * The coverage-band buffers a producer packs per region: three in the layouts
3
+ * `coverageBand.slang`'s passes read, and the indicator triangles' positions
4
+ * and type codes, which the point mark reads. Every field is filled by
4
5
  * `@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.
6
+ * and `computeInterbaseCoverage` (which emits the last three) — so a display
7
+ * gets these by running that pipeline, not by shaping them itself.
7
8
  *
8
9
  * Structural rather than nominal, and named after the layout rather than after
9
10
  * either display: the alignments worker's `CoverageUploadData` and the MAF
@@ -19,5 +20,7 @@ export interface CoverageBandBuffers {
19
20
  coveragePackedBuffer: ArrayBuffer;
20
21
  snpPackedBuffer: ArrayBuffer;
21
22
  interbasePackedBuffer: ArrayBuffer;
22
- indicatorPackedBuffer: ArrayBuffer;
23
+ indicatorPositions: Uint32Array;
24
+ /** 1 insertion, 2 softclip, 3 hardclip: the most common event there. */
25
+ indicatorTypes: Float32Array;
23
26
  }
@@ -14,7 +14,7 @@ export interface RenderingBackendOptions<TRenderingBackend> {
14
14
  * that leans on MSAA holds its display at 4. Passing it overrides the
15
15
  * derivation in either direction.
16
16
  *
17
- * The cost is one colour attachment the size of the canvas — an empty 600px
17
+ * The cost is one color attachment the size of the canvas — an empty 600px
18
18
  * track pays what a full one does, and eight ordinary tracks on a retina
19
19
  * panel hold 109.7 MiB nothing counts (ARCHITECTURAL_LIMITS.md §"The MSAA
20
20
  * target is the largest per-display allocation"); 1 allocates none. Whether a
@@ -18,13 +18,9 @@
18
18
  */
19
19
  export type DisplayPhase = 'renderError' | DisplayStatusPhase;
20
20
  /**
21
- * The phases a display without a rendering backend can be in — every phase
22
- * except the one that reports a backend failure. Its own type because
23
- * `renderError` is the only phase whose UI needs something no model can supply
24
- * (the backend hook's `retry()`), which is exactly the line between the shared
25
- * status chrome and the GPU chrome wrapped around it: `DisplayStatusChrome`
26
- * takes this, `DisplayChrome` takes the wider union and peels off `renderError`
27
- * before delegating.
21
+ * Every phase except the one that reports a backend failure: what a surface
22
+ * with no rendering backend of its own publishes (the comparative views'
23
+ * shared canvas, the circular view's chords).
28
24
  */
29
25
  export type DisplayStatusPhase = 'tooLarge' | 'error' | DisplayActivityPhase;
30
26
  /**
@@ -127,8 +123,8 @@ export interface DisplayActivityInputs {
127
123
  */
128
124
  export declare function computeActivityPhase({ isMinimized, fetchInert, viewportEmpty, isLoading, fetchCanceled, awaitingDependentData, rendersCanvas, canvasDrawn, }: DisplayActivityInputs, viewportCurrent: () => boolean, hostMounted?: () => boolean): DisplayActivityPhase;
129
125
  /**
130
- * The same ranking minus `renderError`, for a display with no rendering backend
131
- * to fail (arc's main-thread SVG). `computeDisplayPhase` delegates here, so the
126
+ * The same ranking minus `renderError`, for a surface with no rendering backend
127
+ * of its own to fail. `computeDisplayPhase` delegates here, so the
132
128
  * order lives in one place.
133
129
  */
134
130
  export declare function computeDisplayStatusPhase({ regionTooLarge, error }: DisplayStatusPhaseInputs, activity: () => DisplayActivityPhase): DisplayStatusPhase;
@@ -48,8 +48,8 @@ export function computeActivityPhase({ isMinimized, fetchInert, viewportEmpty, i
48
48
  : 'ready';
49
49
  }
50
50
  /**
51
- * The same ranking minus `renderError`, for a display with no rendering backend
52
- * to fail (arc's main-thread SVG). `computeDisplayPhase` delegates here, so the
51
+ * The same ranking minus `renderError`, for a surface with no rendering backend
52
+ * of its own to fail. `computeDisplayPhase` delegates here, so the
53
53
  * order lives in one place.
54
54
  */
55
55
  export function computeDisplayStatusPhase({ regionTooLarge, error }, activity) {
@@ -1,4 +1,5 @@
1
1
  import { GpuHalBase } from './gpuHalBase.ts';
2
+ import type { CanvasScale } from '../canvas2dUtils.ts';
2
3
  import type { GpuHal, PipelineDescriptor, ShaderSource, TextureBinding, TextureSource } from './types.ts';
3
4
  /** Text for a pass built only for a `MockHal`, which never loads it. */
4
5
  export declare const MOCK_SHADER_SOURCE: ShaderSource;
@@ -99,10 +100,8 @@ export declare class MockHal extends GpuHalBase<MockBuffer> implements GpuHal {
99
100
  private record;
100
101
  errorHandler: ((error: Error) => void) | null;
101
102
  setErrorHandler(handler: (error: Error) => void): void;
102
- resize(width: number, height: number): {
103
- x: number;
104
- y: number;
105
- };
103
+ resize(width: number, height: number): CanvasScale;
104
+ reportedScale: CanvasScale | undefined;
106
105
  releaseRenderTargets(): void;
107
106
  uploadBuffer(regionKey: number, passId: string, data: ArrayBuffer | ArrayBufferView, count: number): void;
108
107
  deleteBuffer(regionKey: number, passId: string): void;
@@ -108,11 +108,12 @@ export class MockHal extends GpuHalBase {
108
108
  }
109
109
  this.record('resize', width, height);
110
110
  // The real HALs report the scale their backing store actually got, and
111
- // every device-px rect a renderer builds comes from this — so a mock
112
- // returning nothing is a renderer whose rects are all NaN. `getDpr()` is
113
- // the unclamped answer, which is what a mock canvas of any size would get.
114
- return { x: getDpr(), y: getDpr() };
111
+ // every device-px rect a renderer builds comes from this. `getDpr()` is the
112
+ // unclamped answer; `reportedScale` stands in for a clamped canvas or a
113
+ // drawing buffer the browser shrank.
114
+ return this.reportedScale ?? { x: getDpr(), y: getDpr() };
115
115
  }
116
+ reportedScale = undefined;
116
117
  releaseRenderTargets() {
117
118
  this.record('releaseRenderTargets');
118
119
  }
@@ -20,7 +20,7 @@ export interface VertexAttributeLayout {
20
20
  * 4" — standard sample patterns are defined for no other count), so this is the
21
21
  * API's constraint rather than a policy of ours, and 2 is not an option to add.
22
22
  *
23
- * At 4 the HAL allocates a multisampled colour attachment the size of the
23
+ * At 4 the HAL allocates a multisampled color attachment the size of the
24
24
  * canvas and resolves it into the canvas texture; at 1 it allocates **nothing**
25
25
  * and draws straight into the canvas texture. The bytes are canvas area x dpr²
26
26
  * x samples x 4, unrelated to how much data the display holds, which is why
@@ -119,10 +119,10 @@ export interface GpuHal {
119
119
  readonly uniformByteSize: number;
120
120
  /**
121
121
  * Size the canvas, and report the scale its backing store actually got.
122
- * Callers derive every device-px rect from THAT rather than from `getDpr()`:
123
- * past `MAX_CANVAS_DIM_PX` the two differ, and a rect built from the true dpr
124
- * exceeds the target it draws into (WebGPU rejects the whole pass and the
125
- * track goes blank; WebGL2 clamps and paints at the wrong scale).
122
+ * Callers derive every device-px rect from that scale. It differs from
123
+ * `getDpr()` past the backing-store caps in `canvas2dUtils.ts` and where
124
+ * a browser shrinks a WebGL drawing buffer, and a rect built from the true
125
+ * dpr then lands outside the target.
126
126
  */
127
127
  resize(width: number, height: number): CanvasScale;
128
128
  /**
@@ -37,7 +37,10 @@ export declare class WebGL2Hal extends GpuHalBase<RegionPassBuffer> implements G
37
37
  protected destroyBuffer(buf: RegionPassBuffer): void;
38
38
  private link;
39
39
  private getPass;
40
- resize(width: number, height: number): import("./types.ts").CanvasScale;
40
+ resize(width: number, height: number): {
41
+ x: number;
42
+ y: number;
43
+ };
41
44
  /**
42
45
  * Nothing to give back. WebGL2 multisamples in the drawing buffer the context
43
46
  * owns (`antialias` above one sample), so this HAL holds no separate target —
@@ -304,7 +304,15 @@ export class WebGL2Hal extends GpuHalBase {
304
304
  if (this.disposed) {
305
305
  return { x: getDpr(), y: getDpr() };
306
306
  }
307
- return syncCanvasSize(this.canvas, width, height).scale;
307
+ // Resized past 7680x4320 px of area, Chrome shrinks the drawing buffer and
308
+ // leaves canvas.width/height at the requested size, so the scale comes from
309
+ // the buffer. A lost context reads 0x0 and falls back to syncCanvasSize's.
310
+ const { scale } = syncCanvasSize(this.canvas, width, height);
311
+ const { drawingBufferWidth: bufferW, drawingBufferHeight: bufferH } = this.gl;
312
+ return {
313
+ x: width > 0 && bufferW > 0 ? bufferW / width : scale.x,
314
+ y: height > 0 && bufferH > 0 ? bufferH / height : scale.y,
315
+ };
308
316
  }
309
317
  /**
310
318
  * Nothing to give back. WebGL2 multisamples in the drawing buffer the context
@@ -354,23 +362,24 @@ export class WebGL2Hal extends GpuHalBase {
354
362
  const gl = this.gl;
355
363
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
356
364
  gl.disable(gl.SCISSOR_TEST);
357
- gl.viewport(0, 0, this.canvas.width, this.canvas.height);
365
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
358
366
  gl.clearColor(clearR, clearG, clearB, clearA);
359
367
  gl.clear(gl.COLOR_BUFFER_BIT);
360
368
  }
361
369
  setScissor(x, y, w, h) {
362
370
  const gl = this.gl;
363
371
  gl.enable(gl.SCISSOR_TEST);
364
- gl.scissor(x, this.canvas.height - y - h, w, h);
372
+ gl.scissor(x, gl.drawingBufferHeight - y - h, w, h);
365
373
  }
366
374
  clearScissor() {
367
375
  this.gl.disable(this.gl.SCISSOR_TEST);
368
376
  }
369
377
  setViewport(x, y, w, h) {
370
- this.gl.viewport(x, this.canvas.height - y - h, w, h);
378
+ this.gl.viewport(x, this.gl.drawingBufferHeight - y - h, w, h);
371
379
  }
372
380
  clearViewport() {
373
- this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
381
+ const gl = this.gl;
382
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
374
383
  }
375
384
  drawPass(passId, regionKey, bufferPassId, verticesPerInstance) {
376
385
  const gl = this.gl;
@@ -413,7 +422,7 @@ export class WebGL2Hal extends GpuHalBase {
413
422
  endFrame() {
414
423
  const gl = this.gl;
415
424
  gl.disable(gl.SCISSOR_TEST);
416
- gl.viewport(0, 0, this.canvas.width, this.canvas.height);
425
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
417
426
  }
418
427
  releaseResources() {
419
428
  const gl = this.gl;