@jbrowse/render-core 5.0.0-beta.7 → 5.0.0-beta.9

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 (174) hide show
  1. package/esm/RenderCanvas.d.ts +2 -2
  2. package/esm/RenderLifecycleMixin.d.ts +7 -6
  3. package/esm/RenderLifecycleMixin.js +7 -6
  4. package/esm/canvas2dUtils.d.ts +58 -17
  5. package/esm/canvas2dUtils.js +82 -30
  6. package/esm/computePipeline.d.ts +4 -3
  7. package/esm/computePipeline.js +21 -14
  8. package/esm/coverageBand.d.ts +3 -3
  9. package/esm/coverageBand.js +2 -2
  10. package/esm/coverageBandBuffers.d.ts +2 -3
  11. package/esm/createRenderingBackend.d.ts +1 -1
  12. package/esm/displayPhase.d.ts +79 -109
  13. package/esm/displayPhase.js +42 -83
  14. package/esm/gpuDevice.d.ts +11 -10
  15. package/esm/gpuDevice.js +10 -11
  16. package/esm/graphicsCapabilities.d.ts +6 -7
  17. package/esm/graphicsCapabilities.js +3 -4
  18. package/esm/hal/createHal.js +1 -1
  19. package/esm/hal/deviceGpuCache.d.ts +25 -30
  20. package/esm/hal/deviceGpuCache.js +85 -97
  21. package/esm/hal/index.d.ts +2 -2
  22. package/esm/hal/index.js +1 -1
  23. package/esm/hal/mockHal.d.ts +8 -6
  24. package/esm/hal/mockHal.js +7 -2
  25. package/esm/hal/passIds.d.ts +8 -7
  26. package/esm/hal/passIds.js +11 -10
  27. package/esm/hal/types.d.ts +28 -12
  28. package/esm/hal/webgl2Hal.d.ts +11 -3
  29. package/esm/hal/webgl2Hal.js +116 -145
  30. package/esm/hal/webgpuHal.d.ts +8 -8
  31. package/esm/hal/webgpuHal.js +97 -124
  32. package/esm/instancePass.d.ts +2 -1
  33. package/esm/mapUploadSync.d.ts +2 -2
  34. package/esm/mapUploadSync.js +2 -2
  35. package/esm/marks/barMark.d.ts +6 -3
  36. package/esm/marks/barMark.js +19 -13
  37. package/esm/marks/drawAgainstHit.d.ts +58 -39
  38. package/esm/marks/drawAgainstHit.js +136 -96
  39. package/esm/marks/glyphPaint.d.ts +14 -2
  40. package/esm/marks/glyphPaint.js +16 -2
  41. package/esm/marks/index.d.ts +4 -2
  42. package/esm/marks/index.js +3 -2
  43. package/esm/marks/markBackend.d.ts +2 -2
  44. package/esm/marks/markBackend.js +2 -2
  45. package/esm/marks/markHit.d.ts +2 -2
  46. package/esm/marks/markHit.js +2 -2
  47. package/esm/marks/markPlan.d.ts +2 -2
  48. package/esm/marks/markPlan.js +2 -2
  49. package/esm/marks/markRamp.d.ts +1 -3
  50. package/esm/marks/markRamp.js +1 -5
  51. package/esm/marks/nearestMarkHit.d.ts +49 -0
  52. package/esm/marks/nearestMarkHit.js +126 -0
  53. package/esm/marks/pointMark.d.ts +9 -2
  54. package/esm/marks/pointMark.js +36 -31
  55. package/esm/marks/rowLane.d.ts +17 -0
  56. package/esm/marks/rowLane.js +11 -0
  57. package/esm/marks/spanMark.d.ts +6 -26
  58. package/esm/marks/spanMark.js +34 -31
  59. package/esm/marks/types.d.ts +51 -181
  60. package/esm/marks/types.js +91 -131
  61. package/esm/namedReactions.d.ts +3 -4
  62. package/esm/namedReactions.js +3 -4
  63. package/esm/scoreScale.d.ts +20 -0
  64. package/esm/scoreScale.js +77 -0
  65. package/esm/shaders/barMark.generated.d.ts +2 -3
  66. package/esm/shaders/barMark.generated.js +4 -3
  67. package/esm/shaders/barMark.glsl.generated.d.ts +2 -0
  68. package/esm/shaders/barMark.glsl.generated.js +4 -0
  69. package/esm/shaders/barMark.iface.generated.d.ts +12 -6
  70. package/esm/shaders/barMark.iface.generated.js +27 -15
  71. package/esm/shaders/barMark.wgsl.generated.d.ts +1 -0
  72. package/esm/shaders/barMark.wgsl.generated.js +3 -0
  73. package/esm/shaders/coverageBar.generated.d.ts +2 -3
  74. package/esm/shaders/coverageBar.generated.js +4 -3
  75. package/esm/shaders/coverageBar.glsl.generated.d.ts +2 -0
  76. package/esm/shaders/coverageBar.glsl.generated.js +4 -0
  77. package/esm/shaders/coverageBar.iface.generated.js +1 -1
  78. package/esm/shaders/coverageBar.wgsl.generated.d.ts +1 -0
  79. package/esm/shaders/coverageBar.wgsl.generated.js +3 -0
  80. package/esm/shaders/coverageIndicator.generated.d.ts +2 -3
  81. package/esm/shaders/coverageIndicator.generated.js +4 -3
  82. package/esm/shaders/coverageIndicator.glsl.generated.d.ts +2 -0
  83. package/esm/shaders/coverageIndicator.glsl.generated.js +4 -0
  84. package/esm/shaders/coverageIndicator.iface.generated.js +1 -1
  85. package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +1 -0
  86. package/esm/shaders/coverageIndicator.wgsl.generated.js +3 -0
  87. package/esm/shaders/coverageInterbase.generated.d.ts +2 -3
  88. package/esm/shaders/coverageInterbase.generated.js +4 -3
  89. package/esm/shaders/coverageInterbase.glsl.generated.d.ts +2 -0
  90. package/esm/shaders/coverageInterbase.glsl.generated.js +4 -0
  91. package/esm/shaders/coverageInterbase.iface.generated.js +1 -1
  92. package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -0
  93. package/esm/shaders/coverageInterbase.wgsl.generated.js +3 -0
  94. package/esm/shaders/coverageMod.generated.d.ts +2 -3
  95. package/esm/shaders/coverageMod.generated.js +4 -3
  96. package/esm/shaders/coverageMod.glsl.generated.d.ts +2 -0
  97. package/esm/shaders/coverageMod.glsl.generated.js +4 -0
  98. package/esm/shaders/coverageMod.iface.generated.js +1 -1
  99. package/esm/shaders/coverageMod.wgsl.generated.d.ts +1 -0
  100. package/esm/shaders/coverageMod.wgsl.generated.js +3 -0
  101. package/esm/shaders/coverageSnp.generated.d.ts +2 -3
  102. package/esm/shaders/coverageSnp.generated.js +4 -3
  103. package/esm/shaders/coverageSnp.glsl.generated.d.ts +2 -0
  104. package/esm/shaders/coverageSnp.glsl.generated.js +4 -0
  105. package/esm/shaders/coverageSnp.iface.generated.js +1 -1
  106. package/esm/shaders/coverageSnp.wgsl.generated.d.ts +1 -0
  107. package/esm/shaders/coverageSnp.wgsl.generated.js +3 -0
  108. package/esm/shaders/pointMark.generated.d.ts +2 -3
  109. package/esm/shaders/pointMark.generated.js +4 -3
  110. package/esm/shaders/pointMark.glsl.generated.d.ts +2 -0
  111. package/esm/shaders/pointMark.glsl.generated.js +4 -0
  112. package/esm/shaders/pointMark.iface.generated.d.ts +11 -3
  113. package/esm/shaders/pointMark.iface.generated.js +23 -9
  114. package/esm/shaders/pointMark.js.generated.d.ts +1 -1
  115. package/esm/shaders/pointMark.js.generated.js +51 -29
  116. package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -0
  117. package/esm/shaders/pointMark.wgsl.generated.js +3 -0
  118. package/esm/shaders/scoreScale.js.generated.js +46 -22
  119. package/esm/shaders/spanMark.generated.d.ts +2 -3
  120. package/esm/shaders/spanMark.generated.js +4 -3
  121. package/esm/shaders/spanMark.glsl.generated.d.ts +2 -0
  122. package/esm/shaders/spanMark.glsl.generated.js +4 -0
  123. package/esm/shaders/spanMark.iface.generated.js +1 -1
  124. package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -0
  125. package/esm/shaders/spanMark.wgsl.generated.js +3 -0
  126. package/esm/slangPass.d.ts +9 -10
  127. package/esm/slangPass.js +9 -3
  128. package/esm/useGraphicsCapabilities.d.ts +1 -1
  129. package/esm/useGraphicsCapabilities.js +1 -1
  130. package/esm/webgpuUtils.d.ts +3 -11
  131. package/esm/webgpuUtils.js +12 -33
  132. package/package.json +3 -2
  133. package/src/shaders/barMark.generated.ts +6 -5
  134. package/src/shaders/barMark.glsl.generated.ts +6 -0
  135. package/src/shaders/barMark.iface.generated.ts +31 -15
  136. package/src/shaders/barMark.slang +11 -3
  137. package/src/shaders/barMark.wgsl.generated.ts +4 -0
  138. package/src/shaders/colorPack.slang +3 -2
  139. package/src/shaders/coverageBand.slang +5 -11
  140. package/src/shaders/coverageBar.generated.ts +6 -5
  141. package/src/shaders/coverageBar.glsl.generated.ts +6 -0
  142. package/src/shaders/coverageBar.iface.generated.ts +1 -1
  143. package/src/shaders/coverageBar.slang +1 -1
  144. package/src/shaders/coverageBar.wgsl.generated.ts +4 -0
  145. package/src/shaders/coverageIndicator.generated.ts +6 -5
  146. package/src/shaders/coverageIndicator.glsl.generated.ts +6 -0
  147. package/src/shaders/coverageIndicator.iface.generated.ts +1 -1
  148. package/src/shaders/coverageIndicator.wgsl.generated.ts +4 -0
  149. package/src/shaders/coverageInterbase.generated.ts +6 -5
  150. package/src/shaders/coverageInterbase.glsl.generated.ts +6 -0
  151. package/src/shaders/coverageInterbase.iface.generated.ts +1 -1
  152. package/src/shaders/coverageInterbase.wgsl.generated.ts +4 -0
  153. package/src/shaders/coverageMod.generated.ts +6 -5
  154. package/src/shaders/coverageMod.glsl.generated.ts +6 -0
  155. package/src/shaders/coverageMod.iface.generated.ts +1 -1
  156. package/src/shaders/coverageMod.wgsl.generated.ts +4 -0
  157. package/src/shaders/coverageSnp.generated.ts +6 -5
  158. package/src/shaders/coverageSnp.glsl.generated.ts +6 -0
  159. package/src/shaders/coverageSnp.iface.generated.ts +1 -1
  160. package/src/shaders/coverageSnp.wgsl.generated.ts +4 -0
  161. package/src/shaders/hpmath.slang +18 -6
  162. package/src/shaders/pointMark.generated.ts +6 -6
  163. package/src/shaders/pointMark.glsl.generated.ts +6 -0
  164. package/src/shaders/pointMark.iface.generated.ts +28 -9
  165. package/src/shaders/pointMark.js.generated.ts +48 -29
  166. package/src/shaders/pointMark.slang +18 -5
  167. package/src/shaders/pointMark.wgsl.generated.ts +4 -0
  168. package/src/shaders/scoreScale.js.generated.ts +43 -22
  169. package/src/shaders/scoreScale.slang +37 -53
  170. package/src/shaders/spanMark.generated.ts +6 -5
  171. package/src/shaders/spanMark.glsl.generated.ts +6 -0
  172. package/src/shaders/spanMark.iface.generated.ts +1 -1
  173. package/src/shaders/spanMark.wgsl.generated.ts +4 -0
  174. package/src/shaders/valueScale.slang +8 -45
@@ -5,11 +5,9 @@ import type { InstancePass } from '../instancePass.ts';
5
5
  import type { RenderBlock } from '../renderBlock.ts';
6
6
  import type { FrameDimensions } from '../renderingBackendBase.ts';
7
7
  /**
8
- * The 2D-context subset a shape's painter needs, structural for the same reason
9
- * `ClipContext2D` is: render-core must not depend on `@jbrowse/core`, where the
10
- * real `Ctx2D = CanvasRenderingContext2D | SvgCanvas` union lives. Both members
11
- * satisfy it, so a display passes its `Ctx2D` straight in — which is also what
12
- * makes one painter the on-screen fallback and the SVG export path.
8
+ * The 2D-context subset a shape's painter needs, which both a canvas and
9
+ * `@jbrowse/core`'s `SvgCanvas` satisfy: one painter is the fallback and the
10
+ * SVG export.
13
11
  */
14
12
  export interface MarkContext2D extends ClipContext2D {
15
13
  fillStyle: string | CanvasGradient | CanvasPattern;
@@ -34,9 +32,8 @@ export interface MarkContext2D extends ClipContext2D {
34
32
  drawImage?(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
35
33
  }
36
34
  /**
37
- * A canvas a mark's pass samples in place of a colour ramp, with the size the
38
- * texture is allocated at. A fresh object per repaint of the canvas: the
39
- * backend uploads on identity, and a canvas element's identity never moves.
35
+ * A canvas a mark's pass samples in place of a colour ramp. Hand a fresh object
36
+ * per repaint: the backend uploads on identity.
40
37
  */
41
38
  export interface MarkImage {
42
39
  image: HTMLCanvasElement | OffscreenCanvas;
@@ -46,14 +43,10 @@ export interface MarkImage {
46
43
  /** What a mark's pass binds: a 256-entry RGBA ramp, or a canvas. */
47
44
  export type MarkTexture = Uint8Array | MarkImage;
48
45
  export type MarkFrame = FrameDimensions;
49
- /** How a declared value scale reads its domain. */
50
46
  export type MarkValueScaleType = 'linear' | 'log';
51
47
  /**
52
- * The quantitative colour scale a frame paints a ramp channel through: the
53
- * domain, unioned over the loaded regions by the display; the declared scale
54
- * type; and the 256-entry RGBA LUT the pass's sampler binds. A shape that
55
- * reads it takes the raw values in `colorValue` and resolves them per frame,
56
- * so a pan that widens the domain uploads no instance bytes.
48
+ * The colour scale a ramp channel's raw `colorValue`s resolve through each
49
+ * frame, with the 256-entry RGBA LUT the pass's sampler binds.
57
50
  */
58
51
  export interface MarkRamp {
59
52
  domain: [number, number];
@@ -80,20 +73,10 @@ export interface InkRect {
80
73
  height: number;
81
74
  }
82
75
  /**
83
- * A shape: one hand-written `.slang`, one packer, one Canvas2D painter and one
84
- * hit test, all reading the same channel arrays.
85
- *
86
- * `TChannels` is the shape's own vocabulary — parallel typed arrays plus a
87
- * count. `TParams` is everything else the drawing needs, which reaches the GPU
88
- * as uniforms and the painter as arguments; the two must be the same values or
89
- * the backends diverge, which is what having one object per shape prevents.
90
- *
91
- * There is no `MarkShape` constructor. A shape is written out once and
92
- * admitted by a consumer rather than by completeness — the ADR-040 bar. It
93
- * lives here when two displays share it (`span`) or its geometry is generic
94
- * (`point`); a display whose shader's generated twins feed its own hit test
95
- * keeps the shape beside them (variants' `cellMark`) and still declares it
96
- * through `defineMark`.
76
+ * A shape: one `.slang` pass, a uniform writer and a Canvas2D painter over the
77
+ * same channel arrays, and the geometry its hit test reads. `TParams` reaches
78
+ * the GPU as uniforms and the painter as arguments, so both backends draw the
79
+ * same values.
97
80
  */
98
81
  export interface MarkShape<TChannels, TParams> {
99
82
  readonly id: string;
@@ -101,196 +84,83 @@ export interface MarkShape<TChannels, TParams> {
101
84
  writeUniforms(scratch: ArrayBuffer, clip: BlockClipResult, block: RenderBlock, frame: MarkFrame, params: TParams): void;
102
85
  paintBlock(ctx: MarkContext2D, channels: TChannels, block: RenderBlock, frame: MarkFrame, params: TParams): void;
103
86
  /**
104
- * The shape's draw predicate over (block, frame, params) — not over its
105
- * instances, which it is never handed. Both backends skip the block when it
106
- * says no, and so does `hitNearest`: a block with no ink on it has nothing
107
- * to be near. Optional: a shape whose every block can carry ink leaves it
108
- * off.
109
- *
110
- * Placement is one use: the canvas continuation marker exists only where a
111
- * block meets a canvas edge, and an interior block would otherwise shade a
112
- * whole pileup's worth of vertices to draw nothing. Settings are the other,
113
- * and the general one: the coverage band's layers read `hasDomain` off
114
- * `params` so an unresolved autoscale draws no depth-scaled layer rather
115
- * than bars of arbitrary height, and `showInterbase` off it so the histogram
116
- * and its triangles turn off together.
87
+ * Whether the block carries any ink under these params. Both backends and the
88
+ * hit test skip a block it declines.
117
89
  */
118
90
  paintsBlock?(block: RenderBlock, frame: MarkFrame, params: TParams): boolean;
119
91
  /**
120
- * The rect `paintBlock` fills for instance `i`, or undefined for an instance
121
- * it skips — a row the viewport has scrolled past, a bar with no height. A
122
- * shape whose every instance is one box declares this and nothing about
123
- * hovering: `defineMark` derives `hitNearest` from it where none is
124
- * declared, and a display's highlight guide reads it for the instances it
125
- * names. A shape whose ink is not a box (a ribbon, an arc, a capsule) leaves
126
- * it off and keeps its own hit test.
127
- *
128
- * Painter-only overdraw — `span`'s seam — is not ink; `drawAgainstHit.ts`
129
- * holds the two to each other within a pixel.
92
+ * The rect `paintBlock` fills for instance `i`, undefined where it paints
93
+ * nothing. `defineMark` derives `hitNearest` from it where none is declared,
94
+ * and a highlight guide draws it. Painter-only overdraw, `span`'s seam, is
95
+ * not ink.
130
96
  */
131
97
  ink?(channels: TChannels, block: RenderBlock, frame: MarkFrame, params: TParams, i: number): InkRect | undefined;
132
98
  /**
133
99
  * The nearest drawn ink to `(xPx, yPx)` among `candidates`, or undefined if
134
- * nothing beats `maxDistSq`.
135
- *
136
- * The candidate set is the caller's, not the shape's: a display with a
137
- * worker-built index (GWAS's Flatbush over (bp, value)) hands in what the
138
- * index answered, and one without hands in every instance. What the shape
139
- * owns is where its ink actually is — the half that drifts from the painter.
140
- *
141
- * Distance 0 means the point is on the ink `paintBlock` fills, edges
142
- * included, and a caller wanting containment alone passes a bound that
143
- * admits nothing else (`Number.MIN_VALUE`). Only a strictly nearer candidate
144
- * replaces the best, so on a tie the first candidate wins — a caller that
145
- * iterates back to front gets the last-painted mark, which is the one on
146
- * top. `drawAgainstHit.ts` is the sweep that holds a shape to this.
147
- *
148
- * Optional, because a shape earns it from a consumer like any other member.
149
- * MAF answers a hit from row/column arithmetic over the alignment rather than
150
- * from `span`'s channels, so it reads none. A shape with `ink` and no hit test
151
- * gets `inkHitNearest` — the nearest of its boxes — through `shapeHitNearest`.
100
+ * nothing beats `maxDistSq`. Distance 0 is on the ink, edges included. Only a
101
+ * strictly nearer candidate replaces the best, so candidates handed back to
102
+ * front answer a tie with the mark on top.
152
103
  */
153
104
  hitNearest?(channels: TChannels, block: RenderBlock, frame: MarkFrame, params: TParams, xPx: number, yPx: number, candidates: Iterable<number>, maxDistSq: number): MarkHit | undefined;
105
+ /**
106
+ * The values an instance can hold and still put ink within `radiusPx` of
107
+ * canvas y `yPx`, which a display's index over (bp, value) is searched with.
108
+ * A shape with no value axis leaves it off.
109
+ */
110
+ valueWindow?(yPx: number, radiusPx: number, frame: MarkFrame, params: TParams): [number, number];
154
111
  }
155
112
  /**
156
- * Which uniform struct is standing in the HAL's current slot, so the next mark
157
- * of the SAME block can draw off it instead of packing and staging the same
158
- * bytes again. Compared by reference and never called or read for its values:
159
- * one writer plus one `params` lens is one struct, because `writeUniforms` sees
160
- * nothing but the block, the clip, the frame and what that lens returned.
161
- *
162
- * A display declaring several marks over one writer and one lens is the common
163
- * shape, not an edge case — the coverage band is five, the feature glyphs are
164
- * five — and every mark after the first was restating the previous one's write.
165
- *
166
- * **The caller owns one of these per block and hands the same one to every mark
167
- * of that block.** A stale one drawn against the next block's clip is silently
168
- * the wrong geometry, so it is a fresh object per block rather than a field
169
- * that has to be remembered to reset. A caller that writes uniforms of its own
170
- * between marks (alignments' per-section pileup write) starts a new one after
171
- * it, or hands none at all.
113
+ * The uniform writer and `params` lens whose struct is in the HAL's slot, so a
114
+ * later mark of the same block over both draws without writing it again. Hand
115
+ * every mark of a block one fresh object: a stale one draws against the
116
+ * previous block's clip.
172
117
  */
173
118
  export interface StagedUniforms {
174
119
  writer: unknown;
175
120
  params: unknown;
176
121
  }
177
- /**
178
- * A mark's pass as a frame plan draws it: what `hal.drawPass(id, regionKey,
179
- * bufferOf)` takes and nothing else, resolved once per frame so the per-block
180
- * loop reads two fields per mark.
181
- */
122
+ /** What `hal.drawPass` takes for one mark of a frame plan. */
182
123
  export interface PlannedPass {
183
124
  readonly id: string;
184
125
  readonly bufferOf: string | undefined;
185
126
  }
186
127
  /**
187
- * A shape bound to one display's region payload and render state — what
188
- * `defineMark` returns and what a display actually holds.
189
- *
190
- * The shape's own generics are erased here on purpose: a display draws a list
191
- * of marks over heterogeneous shapes, and only the binding knows how to reach
192
- * either one's channels.
128
+ * A shape bound to a display's region payload and render state, its generics
129
+ * erased so one list holds several shapes.
193
130
  */
194
131
  export interface Mark<TRegion, TState extends MarkFrame> {
195
132
  readonly pass: InstancePass<TRegion>;
196
- /**
197
- * The pass id whose instance buffer this mark draws from, for a mark that
198
- * is registered but never uploaded to: the canvas chevrons ride the line
199
- * buffer and the continuation markers ride rect's. `defineMark` refuses the
200
- * pairing unless the two passes declare one instance struct.
201
- */
133
+ /** The pass whose uploaded instance buffer this mark draws from. */
202
134
  readonly bufferOf?: string;
203
- /**
204
- * The 256-entry RGBA colour ramp this mark's pass samples this frame, or
205
- * undefined when it samples none. `createMarkBackend` uploads it per pass and
206
- * only when the bytes' identity moves, and binds an inert table for the
207
- * undefined case — a shader owns its sampler unconditionally, and a textured
208
- * pass with no texture never draws on the WebGPU HAL.
209
- */
135
+ /** What the pass samples this frame; undefined binds an inert table. */
210
136
  readonly texture?: (state: TState, region: TRegion) => MarkTexture | undefined;
211
137
  /**
212
- * The frame-level gate: whether the mark draws at all under `state`, read
213
- * off the display-wide state and nothing else. `planMarks` asks it once per
214
- * frame; `drawRegion`, `paintBlock` and `hitNearest` ask it per block, so a
215
- * list drawn through `drawMarks` gates like one drawn through a plan — and
216
- * so does the hit test, which is what stops a switched-off layer answering a
217
- * hover over blank pixels. A question about the block is `paintsBlock`, on
218
- * the shape.
138
+ * Whether the mark draws at all under `state`. `planMarks` asks once per
139
+ * frame and every other consumer per block, before any lens.
219
140
  */
220
141
  readonly enabled?: (state: TState) => boolean;
221
- /**
222
- * The pass a frame plan draws for this mark, or undefined for a mark the plan
223
- * form cannot carry: one with a `band` (a plan's caller owns the scissor) or a
224
- * `paintsBlock` (a plan asks the block nothing).
225
- */
142
+ /** The pass a frame plan draws; undefined with a `band` or `paintsBlock`. */
226
143
  readonly planned?: PlannedPass;
227
144
  drawRegion(hal: GpuHal, scratch: ArrayBuffer, block: RenderBlock, clip: BlockClipResult, region: TRegion, state: TState, regionKey: number, staged?: StagedUniforms): void;
228
145
  paintBlock(ctx: MarkContext2D, region: TRegion, block: RenderBlock, state: TState): void;
229
146
  hitNearest?(region: TRegion, block: RenderBlock, state: TState, xPx: number, yPx: number, candidates: Iterable<number>, maxDistSq: number): MarkHit | undefined;
230
- /**
231
- * The box instance `i` painted under the mark's gates, clipped to its band
232
- * where it has one: what a highlight guide draws for the instance a display
233
- * names. Undefined off a shape with no `ink`, and for an instance nothing
234
- * drew.
235
- */
147
+ /** The box instance `i` painted under the mark's gates, clipped to a band. */
236
148
  ink?(region: TRegion, block: RenderBlock, state: TState, i: number): InkRect | undefined;
149
+ /** The shape's `valueWindow` under the mark's gates, if they open. */
150
+ valueWindow?(region: TRegion, block: RenderBlock, state: TState, yPx: number, radiusPx: number): [number, number] | undefined;
237
151
  }
238
152
  /**
239
- * `{ shape, channels }` over typed arrays — the declaration a display writes in
240
- * place of a `GpuXxxRenderer` class, a `Canvas2DXxxRenderer` class, a factory
241
- * and a pass list.
242
- *
243
- * `channels` and `params` are projections, not copies: `channels` names which
244
- * of the region's arrays feed the shape's lanes, and `params` names which of
245
- * the render state's values reach the uniforms — and which of the region's,
246
- * for a value the payload carries rather than the frame (the variant matrix's
247
- * column count). Both run once per block per frame, never per instance.
248
- *
249
- * **`channels` answers `undefined` for a region this mark has nothing in.**
250
- * That is how one mark list serves a display whose cells are a union — synteny
251
- * draws ribbon cells, outline cells and glyph-lane cells off one list, and a
252
- * region key holds exactly one of the three. The mark then packs an empty
253
- * buffer (which every HAL reads as the release), draws nothing and paints
254
- * nothing, so the kind test is stated once per mark instead of once per
255
- * backend.
256
- *
257
- * **`params` is handed the block**, because the key a display's per-track
258
- * state is filed under is the block's `displayedRegionIndex` and nothing else
259
- * in the lens's reach says which track this is. Every method the lens feeds
260
- * already receives the block; the lens receiving it is what lets it stay a
261
- * pick rather than pushing the lookup into the shape.
262
- *
263
- * `bufferOf` names the mark whose uploaded buffer this one draws from, so the
264
- * declaration says what the HAL's `drawPass(id, region, bufferPassId)` says:
265
- * the chevron mark's channels are the line mark's, packed once under the line
266
- * pass.
267
- *
268
- * `texture` is the colour ramp the mark's pass samples, for a shape that
269
- * resolves a per-instance scalar through a LUT (wiggle density, and the same
270
- * 256-entry table HiC and LD bind). It is state, not a channel: the backend
271
- * uploads it once per pass and re-uploads only when the bytes' identity moves,
272
- * so a ramp change costs one texture and no instance byte.
273
- *
274
- * **Per pass per FRAME, though the lens is handed a region too.** LD reads its
275
- * ramp off the payload's own metric — a pre-computed file with no D' column
276
- * downgrades the request, and the metric that arrived is the authoritative one
277
- * — which is exact because LD holds one region. A display with several regions
278
- * wanting different ramps for one pass would re-upload the texture per region
279
- * per frame; correct, since each recorded draw keeps the table bound when it
280
- * was encoded, but a texture per region per frame is not what this is for.
281
- *
282
- * `band` is the strip of the canvas the mark is clipped to, for a display that
283
- * stacks bands on one canvas (MAF's coverage strip over its rows viewport).
284
- * The GPU scissors to it and Canvas2D clips to it, a zero-height band draws
285
- * nothing, and the shape still places Y against the whole canvas — the band is
286
- * a clip, never an offset, which is what lets one `scrollTop` serve both
287
- * backends. A mark without one paints wherever its shape puts ink.
153
+ * Binds a shape to a display's payload and render state through two picks run
154
+ * once per block per frame: `channels` names the region's arrays for the
155
+ * shape's lanes, and answers undefined for a region the mark has nothing in,
156
+ * which then packs, draws and paints nothing; `params` names what reaches the
157
+ * uniforms.
288
158
  *
289
- * `enabled` is the setting that turns the mark off for a whole frame — the
290
- * pileup's "show mismatches" — and it is one gate for the three consumers: a
291
- * mark that does not draw does not answer a hover either. It reads the state
292
- * alone so `planMarks` can resolve it once per frame; a gate that needs the
293
- * block is the shape's `paintsBlock`.
159
+ * `bufferOf` draws off another mark's uploaded buffer of the same instance
160
+ * struct. `texture` binds per pass per frame and re-uploads when its identity
161
+ * moves, so ramps that differ by region re-upload per region. `band` clips the
162
+ * mark to a strip without offsetting it. `enabled` turns the mark off for a
163
+ * whole frame, for every consumer.
294
164
  */
295
165
  export declare function defineMark<TRegion, TState extends MarkFrame, TChannels, TParams>(spec: {
296
166
  shape: MarkShape<TChannels, TParams>;
@@ -1,66 +1,23 @@
1
1
  import { devicePxBand, withClip } from '../canvas2dUtils.js';
2
2
  import { shapeHitNearest } from './markHit.js';
3
3
  /**
4
- * `{ shape, channels }` over typed arrays — the declaration a display writes in
5
- * place of a `GpuXxxRenderer` class, a `Canvas2DXxxRenderer` class, a factory
6
- * and a pass list.
4
+ * Binds a shape to a display's payload and render state through two picks run
5
+ * once per block per frame: `channels` names the region's arrays for the
6
+ * shape's lanes, and answers undefined for a region the mark has nothing in,
7
+ * which then packs, draws and paints nothing; `params` names what reaches the
8
+ * uniforms.
7
9
  *
8
- * `channels` and `params` are projections, not copies: `channels` names which
9
- * of the region's arrays feed the shape's lanes, and `params` names which of
10
- * the render state's values reach the uniforms — and which of the region's,
11
- * for a value the payload carries rather than the frame (the variant matrix's
12
- * column count). Both run once per block per frame, never per instance.
13
- *
14
- * **`channels` answers `undefined` for a region this mark has nothing in.**
15
- * That is how one mark list serves a display whose cells are a union — synteny
16
- * draws ribbon cells, outline cells and glyph-lane cells off one list, and a
17
- * region key holds exactly one of the three. The mark then packs an empty
18
- * buffer (which every HAL reads as the release), draws nothing and paints
19
- * nothing, so the kind test is stated once per mark instead of once per
20
- * backend.
21
- *
22
- * **`params` is handed the block**, because the key a display's per-track
23
- * state is filed under is the block's `displayedRegionIndex` and nothing else
24
- * in the lens's reach says which track this is. Every method the lens feeds
25
- * already receives the block; the lens receiving it is what lets it stay a
26
- * pick rather than pushing the lookup into the shape.
27
- *
28
- * `bufferOf` names the mark whose uploaded buffer this one draws from, so the
29
- * declaration says what the HAL's `drawPass(id, region, bufferPassId)` says:
30
- * the chevron mark's channels are the line mark's, packed once under the line
31
- * pass.
32
- *
33
- * `texture` is the colour ramp the mark's pass samples, for a shape that
34
- * resolves a per-instance scalar through a LUT (wiggle density, and the same
35
- * 256-entry table HiC and LD bind). It is state, not a channel: the backend
36
- * uploads it once per pass and re-uploads only when the bytes' identity moves,
37
- * so a ramp change costs one texture and no instance byte.
38
- *
39
- * **Per pass per FRAME, though the lens is handed a region too.** LD reads its
40
- * ramp off the payload's own metric — a pre-computed file with no D' column
41
- * downgrades the request, and the metric that arrived is the authoritative one
42
- * — which is exact because LD holds one region. A display with several regions
43
- * wanting different ramps for one pass would re-upload the texture per region
44
- * per frame; correct, since each recorded draw keeps the table bound when it
45
- * was encoded, but a texture per region per frame is not what this is for.
46
- *
47
- * `band` is the strip of the canvas the mark is clipped to, for a display that
48
- * stacks bands on one canvas (MAF's coverage strip over its rows viewport).
49
- * The GPU scissors to it and Canvas2D clips to it, a zero-height band draws
50
- * nothing, and the shape still places Y against the whole canvas — the band is
51
- * a clip, never an offset, which is what lets one `scrollTop` serve both
52
- * backends. A mark without one paints wherever its shape puts ink.
53
- *
54
- * `enabled` is the setting that turns the mark off for a whole frame — the
55
- * pileup's "show mismatches" — and it is one gate for the three consumers: a
56
- * mark that does not draw does not answer a hover either. It reads the state
57
- * alone so `planMarks` can resolve it once per frame; a gate that needs the
58
- * block is the shape's `paintsBlock`.
10
+ * `bufferOf` draws off another mark's uploaded buffer of the same instance
11
+ * struct. `texture` binds per pass per frame and re-uploads when its identity
12
+ * moves, so ramps that differ by region re-upload per region. `band` clips the
13
+ * mark to a strip without offsetting it. `enabled` turns the mark off for a
14
+ * whole frame, for every consumer.
59
15
  */
60
16
  export function defineMark(spec) {
61
17
  const { shape, channels, params, band, texture, enabled } = spec;
62
18
  const hitNearest = shapeHitNearest(shape);
63
19
  const shapeInk = shape.ink?.bind(shape);
20
+ const shapeValueWindow = shape.valueWindow?.bind(shape);
64
21
  const lender = spec.bufferOf?.pass;
65
22
  if (lender &&
66
23
  (lender.instanceStride !== shape.pass.instanceStride ||
@@ -70,6 +27,26 @@ export function defineMark(spec) {
70
27
  }
71
28
  const bufferOf = lender?.id;
72
29
  const planned = band || shape.paintsBlock ? undefined : { id: shape.pass.id, bufferOf };
30
+ // `channels` gates `params`: a union payload's params lens may only read its
31
+ // own kind of region.
32
+ function resolve(region, block, state) {
33
+ if (enabled && !enabled(state)) {
34
+ return undefined;
35
+ }
36
+ const strip = band?.(state);
37
+ if (strip && !bandIsOpen(strip)) {
38
+ return undefined;
39
+ }
40
+ const c = channels(region);
41
+ if (c === undefined) {
42
+ return undefined;
43
+ }
44
+ const p = params(state, region, block);
45
+ if (shape.paintsBlock && !shape.paintsBlock(block, state, p)) {
46
+ return undefined;
47
+ }
48
+ return { channels: c, params: p, strip };
49
+ }
73
50
  return {
74
51
  pass: {
75
52
  ...shape.pass,
@@ -82,107 +59,92 @@ export function defineMark(spec) {
82
59
  texture,
83
60
  enabled,
84
61
  planned,
62
+ // `resolve`'s gates inline: its picks leaving as a record or through
63
+ // closure state measured 0.80-0.86x of these locals on
64
+ // markUniformDedupe.bench.ts.
85
65
  drawRegion(hal, scratch, block, clip, region, state, regionKey, staged) {
86
66
  if (enabled && !enabled(state)) {
87
67
  return;
88
68
  }
89
69
  const strip = band?.(state);
90
- const scissor = strip
91
- ? devicePxBand(strip.top, strip.height, clip.scaleY, clip.pxH)
92
- : undefined;
93
- if (scissor?.height !== 0 && channels(region) !== undefined) {
94
- const p = params(state, region, block);
95
- if (!shape.paintsBlock || shape.paintsBlock(block, state, p)) {
96
- if (scissor) {
97
- hal.setScissor(clip.pxX, scissor.top, clip.pxW, scissor.height);
98
- }
99
- if (staged?.writer !== shape.writeUniforms ||
100
- staged.params !== params) {
101
- shape.writeUniforms(scratch, clip, block, state, p);
102
- hal.writeUniforms(scratch);
103
- if (staged) {
104
- staged.writer = shape.writeUniforms;
105
- staged.params = params;
106
- }
107
- }
108
- hal.drawPass(shape.pass.id, regionKey, bufferOf);
109
- if (scissor) {
110
- hal.setScissor(clip.pxX, 0, clip.pxW, clip.pxH);
111
- }
70
+ if (strip && !bandIsOpen(strip)) {
71
+ return;
72
+ }
73
+ if (channels(region) === undefined) {
74
+ return;
75
+ }
76
+ const p = params(state, region, block);
77
+ if (shape.paintsBlock && !shape.paintsBlock(block, state, p)) {
78
+ return;
79
+ }
80
+ const scissor = strip && devicePxBand(strip.top, strip.height, clip.scaleY, clip.pxH);
81
+ if (scissor?.height === 0) {
82
+ return;
83
+ }
84
+ if (scissor) {
85
+ hal.setScissor(clip.pxX, scissor.top, clip.pxW, scissor.height);
86
+ }
87
+ if (staged?.writer !== shape.writeUniforms || staged.params !== params) {
88
+ shape.writeUniforms(scratch, clip, block, state, p);
89
+ hal.writeUniforms(scratch);
90
+ if (staged) {
91
+ staged.writer = shape.writeUniforms;
92
+ staged.params = params;
112
93
  }
113
94
  }
95
+ hal.drawPass(shape.pass.id, regionKey, bufferOf);
96
+ if (scissor) {
97
+ hal.setScissor(clip.pxX, 0, clip.pxW, clip.pxH);
98
+ }
114
99
  },
115
100
  paintBlock(ctx, region, block, state) {
116
- if (enabled && !enabled(state)) {
101
+ const open = resolve(region, block, state);
102
+ if (!open) {
117
103
  return;
118
104
  }
119
- const strip = band?.(state);
120
- const c = channels(region);
121
- if ((!strip || strip.height > 0) && c !== undefined) {
122
- const p = params(state, region, block);
123
- if (!shape.paintsBlock || shape.paintsBlock(block, state, p)) {
124
- const paint = () => {
125
- shape.paintBlock(ctx, c, block, state, p);
126
- };
127
- if (strip) {
128
- withClip(ctx, 0, strip.top, state.canvasWidth, strip.height, paint);
129
- }
130
- else {
131
- paint();
132
- }
133
- }
105
+ const { strip } = open;
106
+ if (strip) {
107
+ withClip(ctx, 0, strip.top, state.canvasWidth, strip.height, () => {
108
+ shape.paintBlock(ctx, open.channels, block, state, open.params);
109
+ });
110
+ }
111
+ else {
112
+ shape.paintBlock(ctx, open.channels, block, state, open.params);
134
113
  }
135
114
  },
136
115
  hitNearest: hitNearest
137
116
  ? (region, block, state, xPx, yPx, candidates, maxDistSq) => {
138
- const strip = band?.(state);
139
- const c = channels(region);
140
- let hit;
141
- // The three gates `paintBlock` takes, in its order — the band on its
142
- // HEIGHT, as there, so a band both backends decline answers nothing
143
- // either. The cursor being inside the strip is a fourth, and the ink
144
- // being inside it the fifth: a band is a CLIP, so ink outside one was
145
- // never drawn and cannot be the nearest thing to anything.
146
- //
147
- // The fifth gate REJECTS rather than re-runs, and that is the one
148
- // conservative edge here: the shape picks its winner without knowing
149
- // about the band, so a clipped-away nearest masks a farther candidate
150
- // whose ink IS inside the strip, and the mark answers nothing instead
151
- // of that one. Safe in the direction that matters — it never claims a
152
- // hover over pixels neither backend drew — and closing it would mean
153
- // pushing the band down into every shape's own ink test, where the
154
- // band is deliberately not.
155
- if ((!enabled || enabled(state)) &&
156
- (!strip || strip.height > 0) &&
157
- c !== undefined &&
158
- !bandExcludes(strip, yPx)) {
159
- const p = params(state, region, block);
160
- if (!shape.paintsBlock || shape.paintsBlock(block, state, p)) {
161
- hit = hitNearest(c, block, state, p, xPx, yPx, candidates, maxDistSq);
162
- }
117
+ const open = resolve(region, block, state);
118
+ if (!open || bandExcludes(open.strip, yPx)) {
119
+ return undefined;
163
120
  }
164
- return hit && !bandExcludes(strip, hit.y) ? hit : undefined;
121
+ const hit = hitNearest(open.channels, block, state, open.params, xPx, yPx, candidates, maxDistSq);
122
+ // Rejecting a nearest hit the band clipped away, rather than asking
123
+ // again, can mask a farther candidate inside the strip.
124
+ return hit && !bandExcludes(open.strip, hit.y) ? hit : undefined;
165
125
  }
166
126
  : undefined,
167
127
  ink: shapeInk
168
128
  ? (region, block, state, i) => {
169
- const strip = band?.(state);
170
- const c = channels(region);
171
- if ((enabled && !enabled(state)) ||
172
- (strip && strip.height <= 0) ||
173
- c === undefined) {
174
- return undefined;
175
- }
176
- const p = params(state, region, block);
177
- if (shape.paintsBlock && !shape.paintsBlock(block, state, p)) {
129
+ const open = resolve(region, block, state);
130
+ if (!open) {
178
131
  return undefined;
179
132
  }
180
- const r = shapeInk(c, block, state, p, i);
181
- return r && strip ? clipToBand(r, strip) : r;
133
+ const r = shapeInk(open.channels, block, state, open.params, i);
134
+ return r && open.strip ? clipToBand(r, open.strip) : r;
135
+ }
136
+ : undefined,
137
+ valueWindow: shapeValueWindow
138
+ ? (region, block, state, yPx, radiusPx) => {
139
+ const open = resolve(region, block, state);
140
+ return open && shapeValueWindow(yPx, radiusPx, state, open.params);
182
141
  }
183
142
  : undefined,
184
143
  };
185
144
  }
145
+ function bandIsOpen(strip) {
146
+ return strip.height > 0 && Number.isFinite(strip.top + strip.height);
147
+ }
186
148
  function clipToBand(r, strip) {
187
149
  const top = Math.max(r.top, strip.top);
188
150
  const bottom = Math.min(r.top + r.height, strip.top + strip.height);
@@ -190,8 +152,6 @@ function clipToBand(r, strip) {
190
152
  ? { left: r.left, top, width: r.width, height: bottom - top }
191
153
  : undefined;
192
154
  }
193
- // What a mark with nothing in this region packs. An empty pack IS the release
194
- // — every HAL deletes the pass's prior buffer before it looks at the count.
195
155
  const NOTHING = new ArrayBuffer(0);
196
156
  function bandExcludes(strip, yPx) {
197
157
  return (strip !== undefined && (yPx < strip.top || yPx > strip.top + strip.height));
@@ -12,10 +12,9 @@ export declare function recordNamedReaction(self: object, name: string, disposer
12
12
  *
13
13
  * The two halves belong together: `reactionDependencies` can only see a
14
14
  * reaction that was recorded, so an installer that calls `autorun` and
15
- * `addDisposer` directly silently opts its dependency set out of the tests that
16
- * are supposed to pin it — and nothing fails, which is the same shape of
17
- * quiet-hole the mechanism exists to close. Every installer goes through here
18
- * so there is no second spelling to forget.
15
+ * `addDisposer` directly leaves its dependency set out of the tests that are
16
+ * supposed to pin it, and those tests still pass. Every installer goes through
17
+ * here so there is no second spelling to forget.
19
18
  */
20
19
  export declare function namedAutorun(self: IAnyStateTreeNode, body: () => void, options: IAutorunOptions & {
21
20
  name: string;
@@ -23,10 +23,9 @@ export function recordNamedReaction(self, name, disposer) {
23
23
  *
24
24
  * The two halves belong together: `reactionDependencies` can only see a
25
25
  * reaction that was recorded, so an installer that calls `autorun` and
26
- * `addDisposer` directly silently opts its dependency set out of the tests that
27
- * are supposed to pin it — and nothing fails, which is the same shape of
28
- * quiet-hole the mechanism exists to close. Every installer goes through here
29
- * so there is no second spelling to forget.
26
+ * `addDisposer` directly leaves its dependency set out of the tests that are
27
+ * supposed to pin it, and those tests still pass. Every installer goes through
28
+ * here so there is no second spelling to forget.
30
29
  */
31
30
  export function namedAutorun(self, body, options) {
32
31
  const disposer = autorun(body, options);