@jbrowse/render-core 5.0.0-beta.2 → 5.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/RenderLifecycleMixin.d.ts +20 -1
- package/esm/RenderLifecycleMixin.js +25 -2
- package/esm/canvas2dUtils.d.ts +25 -0
- package/esm/canvas2dUtils.js +27 -0
- package/esm/colorRampLut.d.ts +4 -4
- package/esm/colorRampLut.js +4 -4
- package/esm/computePipeline.d.ts +13 -0
- package/esm/computePipeline.js +55 -0
- package/esm/coverageBand.d.ts +0 -1
- package/esm/coverageBand.js +0 -1
- package/esm/coverageBandBuffers.d.ts +0 -6
- package/esm/coverageBandBuffers.js +1 -13
- package/esm/createRenderingBackend.d.ts +14 -18
- package/esm/createRenderingBackend.js +4 -2
- package/esm/encodeMemo.d.ts +14 -0
- package/esm/encodeMemo.js +48 -0
- package/esm/gpuDevice.d.ts +9 -0
- package/esm/gpuDevice.js +12 -3
- package/esm/graphicsCapabilities.d.ts +18 -15
- package/esm/graphicsCapabilities.js +23 -16
- package/esm/hal/createHal.d.ts +1 -2
- package/esm/hal/createHal.js +14 -4
- package/esm/hal/deviceGpuCache.js +2 -2
- package/esm/hal/gpuHalBase.d.ts +1 -1
- package/esm/hal/gpuHalBase.js +2 -3
- package/esm/hal/mockHal.d.ts +0 -1
- package/esm/hal/mockHal.js +0 -10
- package/esm/hal/regionRegistry.d.ts +0 -1
- package/esm/hal/regionRegistry.js +3 -15
- package/esm/hal/types.d.ts +5 -1
- package/esm/hal/webgl2Hal.d.ts +1 -1
- package/esm/hal/webgl2Hal.js +2 -2
- package/esm/hal/webgpuHal.d.ts +2 -2
- package/esm/hal/webgpuHal.js +19 -16
- package/esm/installUpload.d.ts +4 -1
- package/esm/installUpload.js +7 -39
- package/esm/instanceCache.d.ts +10 -7
- package/esm/instanceCache.js +14 -15
- package/esm/mapUploadSync.d.ts +0 -8
- package/esm/mapUploadSync.js +0 -1
- package/esm/marks/colorFill.d.ts +17 -0
- package/esm/marks/colorFill.js +26 -0
- package/esm/marks/drawAgainstHit.d.ts +47 -0
- package/esm/marks/drawAgainstHit.js +140 -0
- package/esm/marks/glyphPaint.d.ts +16 -0
- package/esm/marks/glyphPaint.js +41 -0
- package/esm/marks/index.d.ts +9 -0
- package/esm/marks/index.js +6 -0
- package/esm/marks/markBackend.d.ts +87 -0
- package/esm/marks/markBackend.js +137 -0
- package/esm/marks/markHit.d.ts +25 -0
- package/esm/marks/markHit.js +46 -0
- package/esm/marks/markPaint.d.ts +13 -0
- package/esm/marks/markPaint.js +18 -0
- package/esm/marks/pointMark.d.ts +24 -0
- package/esm/marks/pointMark.js +91 -0
- package/esm/marks/spanMark.d.ts +45 -0
- package/esm/marks/spanMark.js +59 -0
- package/esm/marks/types.d.ts +216 -0
- package/esm/marks/types.js +146 -0
- package/esm/perRegionRenderingBackend.d.ts +15 -3
- package/esm/perRegionRenderingBackend.js +26 -3
- package/esm/regionDataMap.d.ts +2 -2
- package/esm/regionDataMap.js +2 -2
- package/esm/renderBlock.d.ts +14 -0
- package/esm/renderBlock.js +25 -0
- package/esm/renderingBackendBase.d.ts +11 -14
- package/esm/renderingBackendBase.js +7 -7
- package/esm/shaders/capsule.js.generated.d.ts +1 -0
- package/esm/shaders/capsule.js.generated.js +11 -0
- package/esm/shaders/coverageBar.generated.d.ts +2 -2
- package/esm/shaders/coverageBar.generated.js +2 -2
- package/esm/shaders/coverageMod.generated.d.ts +2 -2
- package/esm/shaders/coverageMod.generated.js +2 -2
- package/esm/shaders/coverageSnp.generated.d.ts +2 -2
- package/esm/shaders/coverageSnp.generated.js +2 -2
- package/esm/shaders/pointMark.consts.generated.d.ts +4 -0
- package/esm/shaders/pointMark.consts.generated.js +6 -0
- package/esm/shaders/pointMark.generated.d.ts +5 -0
- package/esm/shaders/pointMark.generated.js +7 -0
- package/esm/shaders/pointMark.iface.generated.d.ts +55 -0
- package/esm/shaders/pointMark.iface.generated.js +100 -0
- package/esm/shaders/pointMark.js.generated.d.ts +2 -0
- package/esm/shaders/pointMark.js.generated.js +19 -0
- package/esm/shaders/spanMark.generated.d.ts +4 -0
- package/esm/shaders/spanMark.generated.js +6 -0
- package/esm/shaders/spanMark.iface.generated.d.ts +58 -0
- package/esm/shaders/spanMark.iface.generated.js +122 -0
- package/esm/sharedBackendKey.d.ts +18 -0
- package/esm/{keyedRenderingBackend.js → sharedBackendKey.js} +5 -0
- package/esm/slangPass.d.ts +2 -0
- package/esm/slangPass.js +2 -0
- package/package.json +14 -4
- package/src/shaders/capsule.js.generated.ts +14 -0
- package/src/shaders/capsule.slang +12 -4
- package/src/shaders/colorPack.slang +5 -1
- package/src/shaders/coverageBand.slang +38 -15
- package/src/shaders/coverageBar.generated.ts +2 -2
- package/src/shaders/coverageBar.slang +9 -0
- package/src/shaders/coverageMod.generated.ts +2 -2
- package/src/shaders/coverageSnp.generated.ts +2 -2
- package/src/shaders/hpmath.slang +7 -1
- package/src/shaders/pointMark.consts.generated.ts +10 -0
- package/src/shaders/pointMark.generated.ts +12 -0
- package/src/shaders/pointMark.iface.generated.ts +146 -0
- package/src/shaders/pointMark.js.generated.ts +25 -0
- package/src/shaders/pointMark.slang +205 -0
- package/src/shaders/rowRect.slang +7 -8
- package/src/shaders/spanMark.generated.ts +10 -0
- package/src/shaders/spanMark.iface.generated.ts +168 -0
- package/src/shaders/spanMark.slang +22 -0
- package/esm/globalRenderingBackend.d.ts +0 -90
- package/esm/globalRenderingBackend.js +0 -53
- package/esm/keyedRenderingBackend.d.ts +0 -41
package/esm/hal/createHal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getGpuOverride, isGpuRenderingDisabled } from '../gpuDevice.js';
|
|
1
|
+
import { getGpuOverride, isGpuRenderingDisabled, setBuiltRenderer, } from '../gpuDevice.js';
|
|
2
2
|
import { getGraphicsCapabilities } from '../graphicsCapabilities.js';
|
|
3
3
|
import { WebGL2Hal } from './webgl2Hal.js';
|
|
4
4
|
import { WebGPUHal } from './webgpuHal.js';
|
|
@@ -17,14 +17,24 @@ function warnSoftwareRasterizerOnce(glRenderer) {
|
|
|
17
17
|
// Ladder: WebGPU → WebGL2 → Canvas2D (null). The `?renderer=` URL param pins it
|
|
18
18
|
// to a single rung for debugging — `webgpu`, `webgl`, or `canvas2d`/`canvas` —
|
|
19
19
|
// and a pin never falls through to the next rung. See GPU_OVERRIDES.
|
|
20
|
-
|
|
20
|
+
//
|
|
21
|
+
// Records the rung it returned on the page-wide cell, which is how
|
|
22
|
+
// `effectiveRenderer` gets to report what drew rather than what the adapter
|
|
23
|
+
// probe predicted — the two part when `WebGPUHal.create` throws and the ladder
|
|
24
|
+
// lands on WebGL2.
|
|
25
|
+
export async function createGpuHal(canvas, options) {
|
|
26
|
+
const hal = await climbLadder(canvas, options);
|
|
27
|
+
setBuiltRenderer(hal === null ? 'Canvas2D' : hal instanceof WebGPUHal ? 'WebGPU' : 'WebGL2');
|
|
28
|
+
return hal;
|
|
29
|
+
}
|
|
30
|
+
async function climbLadder(canvas, { passes, sampleCount, failures }) {
|
|
21
31
|
if (isGpuRenderingDisabled()) {
|
|
22
32
|
return null;
|
|
23
33
|
}
|
|
24
34
|
const override = getGpuOverride();
|
|
25
35
|
if (override !== 'webgl') {
|
|
26
36
|
try {
|
|
27
|
-
const webgpu = await WebGPUHal.create(canvas, passes,
|
|
37
|
+
const webgpu = await WebGPUHal.create(canvas, passes, sampleCount);
|
|
28
38
|
if (webgpu) {
|
|
29
39
|
return webgpu;
|
|
30
40
|
}
|
|
@@ -81,7 +91,7 @@ export async function createGpuHal(canvas, { passes, uniformByteSize, sampleCoun
|
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
try {
|
|
84
|
-
return new WebGL2Hal(canvas, passes
|
|
94
|
+
return new WebGL2Hal(canvas, passes);
|
|
85
95
|
}
|
|
86
96
|
catch (e) {
|
|
87
97
|
console.warn('[GPU] WebGL2 unavailable, falling back to Canvas2D:', e);
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* is the one pipeline input the descriptor cannot express, so it is the inner
|
|
22
22
|
* key; see the `pipelines` field.
|
|
23
23
|
*
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
24
|
+
* The descriptor's `uniformByteSize` never reaches the pipeline, so it cannot
|
|
25
|
+
* make two HALs disagree here:
|
|
26
26
|
* `createUniformOnlyBindGroupLayout` declares no `minBindingSize`, so the
|
|
27
27
|
* layout — and thus the pipeline built against it — is the same whatever a
|
|
28
28
|
* display's `uniformByteSize` is. The size reaches the GPU through the bind
|
package/esm/hal/gpuHalBase.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export declare abstract class GpuHalBase<Buf extends {
|
|
|
31
31
|
protected descriptors: Map<string, PipelineDescriptor>;
|
|
32
32
|
protected regions: RegionRegistry<Buf>;
|
|
33
33
|
protected oom: OomReporter;
|
|
34
|
+
readonly uniformByteSize: number;
|
|
34
35
|
protected disposed: boolean;
|
|
35
36
|
constructor(descriptors: PipelineDescriptor[], halName: string);
|
|
36
37
|
protected abstract limits(): HalLimits;
|
|
@@ -49,7 +50,6 @@ export declare abstract class GpuHalBase<Buf extends {
|
|
|
49
50
|
getBufferCount(regionKey: number, passId: string): number;
|
|
50
51
|
deleteBuffer(regionKey: number, passId: string): void;
|
|
51
52
|
deleteRegion(regionKey: number): void;
|
|
52
|
-
pruneRegions(active: Iterable<number>): void;
|
|
53
53
|
uploadTexture(passId: string, data: Uint8Array, width: number, height: number): void;
|
|
54
54
|
dispose(): void;
|
|
55
55
|
}
|
package/esm/hal/gpuHalBase.js
CHANGED
|
@@ -19,11 +19,13 @@ export class GpuHalBase {
|
|
|
19
19
|
descriptors;
|
|
20
20
|
regions;
|
|
21
21
|
oom;
|
|
22
|
+
uniformByteSize;
|
|
22
23
|
// Guards dispose() against double invocation (pagehide + React cleanup can
|
|
23
24
|
// both fire).
|
|
24
25
|
disposed = false;
|
|
25
26
|
constructor(descriptors, halName) {
|
|
26
27
|
this.descriptors = new Map(descriptors.map(d => [d.id, d]));
|
|
28
|
+
this.uniformByteSize = Math.max(0, ...descriptors.map(d => d.uniformByteSize));
|
|
27
29
|
this.oom = new OomReporter(halName);
|
|
28
30
|
this.regions = new RegionRegistry(buf => {
|
|
29
31
|
this.destroyBuffer(buf);
|
|
@@ -55,9 +57,6 @@ export class GpuHalBase {
|
|
|
55
57
|
deleteRegion(regionKey) {
|
|
56
58
|
this.regions.deleteRegion(regionKey);
|
|
57
59
|
}
|
|
58
|
-
pruneRegions(active) {
|
|
59
|
-
this.regions.prune(active);
|
|
60
|
-
}
|
|
61
60
|
uploadTexture(passId, data, width, height) {
|
|
62
61
|
// Read the binding off the descriptor, so a pass with no texture is
|
|
63
62
|
// answered without touching the backend's pipeline state at all.
|
package/esm/hal/mockHal.d.ts
CHANGED
|
@@ -102,7 +102,6 @@ export declare class MockHal extends GpuHalBase<MockBuffer> implements GpuHal {
|
|
|
102
102
|
uploadBuffer(regionKey: number, passId: string, data: ArrayBuffer | ArrayBufferView, count: number): void;
|
|
103
103
|
deleteBuffer(regionKey: number, passId: string): void;
|
|
104
104
|
deleteRegion(regionKey: number): void;
|
|
105
|
-
pruneRegions(active: Iterable<number>): void;
|
|
106
105
|
private noteReplaced;
|
|
107
106
|
private noteRegionReplaced;
|
|
108
107
|
writeUniforms(data: ArrayBuffer): void;
|
package/esm/hal/mockHal.js
CHANGED
|
@@ -119,16 +119,6 @@ export class MockHal extends GpuHalBase {
|
|
|
119
119
|
this.noteRegionReplaced(regionKey);
|
|
120
120
|
super.deleteRegion(regionKey);
|
|
121
121
|
}
|
|
122
|
-
pruneRegions(active) {
|
|
123
|
-
const activeSet = new Set(active);
|
|
124
|
-
this.record('pruneRegions', [...activeSet]);
|
|
125
|
-
for (const regionKey of this.drawnThisFrame.keys()) {
|
|
126
|
-
if (!activeSet.has(regionKey)) {
|
|
127
|
-
this.noteRegionReplaced(regionKey);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
super.pruneRegions(activeSet);
|
|
131
|
-
}
|
|
132
122
|
noteReplaced(regionKey, passId) {
|
|
133
123
|
if (this.frameOpen && this.drawnThisFrame.get(regionKey)?.has(passId)) {
|
|
134
124
|
this.replacedWhileDrawnLog.push(`${regionKey}:${passId}`);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
// Per-(region, pass) buffer registry shared by WebGL2Hal and WebGPUHal
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// near-identical deleteBuffer/deleteRegion/deleteAll/prune/getOrCreate
|
|
5
|
-
// implementations; centralizing avoids drift when one side's lifecycle is
|
|
6
|
-
// tweaked and the other isn't. Contract pinned by regionRegistry.test.ts.
|
|
1
|
+
// Per-(region, pass) buffer registry shared by WebGL2Hal and WebGPUHal: the
|
|
2
|
+
// nested map plus destroy semantics, so each HAL supplies only its leaf-buffer
|
|
3
|
+
// type and destroy hook.
|
|
7
4
|
export class RegionRegistry {
|
|
8
5
|
destroy;
|
|
9
6
|
regions = new Map();
|
|
@@ -54,13 +51,4 @@ export class RegionRegistry {
|
|
|
54
51
|
}
|
|
55
52
|
this.regions.clear();
|
|
56
53
|
}
|
|
57
|
-
// Delete every region whose key is NOT in `active`.
|
|
58
|
-
prune(active) {
|
|
59
|
-
const activeSet = new Set(active);
|
|
60
|
-
for (const regionKey of this.regions.keys()) {
|
|
61
|
-
if (!activeSet.has(regionKey)) {
|
|
62
|
-
this.deleteRegion(regionKey);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
54
|
}
|
package/esm/hal/types.d.ts
CHANGED
|
@@ -80,15 +80,20 @@ export interface PipelineDescriptor {
|
|
|
80
80
|
glslVertex: string;
|
|
81
81
|
glslFragment: string;
|
|
82
82
|
instanceStride: number;
|
|
83
|
+
/** Bytes of the uniform block the shader binds; the HAL's slot is the largest over its passes. */
|
|
84
|
+
uniformByteSize: number;
|
|
83
85
|
verticesPerInstance: number;
|
|
84
86
|
blend: boolean;
|
|
85
87
|
blendState?: BlendState;
|
|
86
88
|
vertexAttributes: readonly VertexAttributeLayout[];
|
|
87
89
|
topology?: 'triangle-list' | 'triangle-strip' | 'line-list';
|
|
90
|
+
coverage?: 'analytic';
|
|
88
91
|
textures?: readonly [TextureBinding, ...TextureBinding[]];
|
|
89
92
|
bindings?: readonly ShaderBinding[];
|
|
90
93
|
}
|
|
91
94
|
export interface GpuHal {
|
|
95
|
+
/** The uniform slot size, the largest block any registered pass binds. */
|
|
96
|
+
readonly uniformByteSize: number;
|
|
92
97
|
/**
|
|
93
98
|
* Size the canvas, and report the scale its backing store actually got.
|
|
94
99
|
* Callers derive every device-px rect from THAT rather than from `getDpr()`:
|
|
@@ -101,7 +106,6 @@ export interface GpuHal {
|
|
|
101
106
|
getBufferCount(regionKey: number, passId: string): number;
|
|
102
107
|
deleteBuffer(regionKey: number, passId: string): void;
|
|
103
108
|
deleteRegion(regionKey: number): void;
|
|
104
|
-
pruneRegions(active: Iterable<number>): void;
|
|
105
109
|
uploadTexture(passId: string, data: Uint8Array, width: number, height: number): void;
|
|
106
110
|
writeUniforms(data: ArrayBuffer): void;
|
|
107
111
|
beginFrame(clearR: number, clearG: number, clearB: number, clearA?: number): void;
|
package/esm/hal/webgl2Hal.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare class WebGL2Hal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
17
17
|
private contextLostListener;
|
|
18
18
|
private contextRestoredListener;
|
|
19
19
|
private checkGlError;
|
|
20
|
-
constructor(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[]
|
|
20
|
+
constructor(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[]);
|
|
21
21
|
protected limits(): {
|
|
22
22
|
maxBufferBytes: number;
|
|
23
23
|
maxTextureDimensionPx: number;
|
package/esm/hal/webgl2Hal.js
CHANGED
|
@@ -156,7 +156,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
156
156
|
console.error(`[WebGL2Hal] GL error at "${label}": ${glErrorName(this.gl, err)}`);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
constructor(canvas, descriptors
|
|
159
|
+
constructor(canvas, descriptors) {
|
|
160
160
|
super(descriptors, 'WebGL2Hal');
|
|
161
161
|
this.canvas = canvas;
|
|
162
162
|
this.debug = debugEnabled();
|
|
@@ -202,7 +202,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
202
202
|
this.maxTextureDim = Number(gl.getParameter(gl.MAX_TEXTURE_SIZE));
|
|
203
203
|
this.ubo = gl.createBuffer();
|
|
204
204
|
gl.bindBuffer(gl.UNIFORM_BUFFER, this.ubo);
|
|
205
|
-
gl.bufferData(gl.UNIFORM_BUFFER, uniformByteSize, gl.DYNAMIC_DRAW);
|
|
205
|
+
gl.bufferData(gl.UNIFORM_BUFFER, this.uniformByteSize, gl.DYNAMIC_DRAW);
|
|
206
206
|
this.passes = new Map();
|
|
207
207
|
// Programs are built on first use (see `getPass`), not here: a renderer
|
|
208
208
|
// declares every pass it could ever draw — alignments alone declares 21,
|
package/esm/hal/webgpuHal.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ export declare class WebGPUHal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
66
66
|
* configured context behind with nothing to release it.
|
|
67
67
|
*/
|
|
68
68
|
private configureContext;
|
|
69
|
-
static create(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[],
|
|
69
|
+
static create(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[], sampleCount: SampleCount): Promise<WebGPUHal | null>;
|
|
70
70
|
resize(width: number, height: number): import("./types.ts").CanvasScale;
|
|
71
71
|
private recreateMsaaTexture;
|
|
72
72
|
/**
|
|
@@ -92,7 +92,7 @@ export declare class WebGPUHal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
92
92
|
* {@link pendingDestroy} for why the frame decides when.
|
|
93
93
|
*
|
|
94
94
|
* Every buffer release routes here through the `RegionRegistry` destroy hook,
|
|
95
|
-
* so `uploadBuffer`, `deleteBuffer`, `deleteRegion
|
|
95
|
+
* so `uploadBuffer`, `deleteBuffer`, `deleteRegion` and
|
|
96
96
|
* `dispose` are all covered without any of them knowing a frame is open.
|
|
97
97
|
*/
|
|
98
98
|
private destroyWhenIdle;
|
package/esm/hal/webgpuHal.js
CHANGED
|
@@ -15,11 +15,13 @@ class ShaderCompileError extends Error {
|
|
|
15
15
|
// aligned slot in the uniform ring buffer.
|
|
16
16
|
//
|
|
17
17
|
// The ring is allocated eagerly at this many slots, as a GPU buffer AND a CPU
|
|
18
|
-
// staging array, whether a renderer writes 4 slots or 1900 — so the cost is
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
18
|
+
// staging array, whether a renderer writes 4 slots or 1900 — so the cost is per
|
|
19
|
+
// display, and both terms are in it. A display whose uniform fits the 256-byte
|
|
20
|
+
// minimum alignment pays 512 KB each side; alignments' 640-byte uniform aligns
|
|
21
|
+
// to 768, so 1.5 MiB each side, per track. A frame is measured using 2-4 of
|
|
22
|
+
// those slots, which is what makes the count above the oversized term rather
|
|
23
|
+
// than any struct:
|
|
24
|
+
// agent-docs/ideas/size-the-uniform-ring-to-its-measured-occupancy.md.
|
|
23
25
|
//
|
|
24
26
|
// Exhausting it does not throw: the write is dropped and its draws render
|
|
25
27
|
// against the previous batch's uniforms, which is wrong data rather than stale
|
|
@@ -196,7 +198,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
196
198
|
// than once per frame, and a reconfigure that failed is not attempted again.
|
|
197
199
|
warnedSwapChainLoss = false;
|
|
198
200
|
swapChainUnrecoverable = false;
|
|
199
|
-
constructor(device, canvas, context, descriptors,
|
|
201
|
+
constructor(device, canvas, context, descriptors, sampleCount, pipelines, layouts) {
|
|
200
202
|
super(descriptors, 'WebGPUHal');
|
|
201
203
|
this.device = device;
|
|
202
204
|
this.canvas = canvas;
|
|
@@ -206,7 +208,8 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
206
208
|
this.sampleCount = sampleCount;
|
|
207
209
|
// Align uniform slots to device requirements for dynamic offsets
|
|
208
210
|
const alignment = device.limits.minUniformBufferOffsetAlignment;
|
|
209
|
-
this.alignedUniformSize =
|
|
211
|
+
this.alignedUniformSize =
|
|
212
|
+
Math.ceil(this.uniformByteSize / alignment) * alignment;
|
|
210
213
|
const ringSize = MAX_UNIFORM_SLOTS * this.alignedUniformSize;
|
|
211
214
|
this.uniformRingBuffer = device.createBuffer({
|
|
212
215
|
size: ringSize,
|
|
@@ -245,7 +248,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
245
248
|
});
|
|
246
249
|
noteCanvasConfigured(this.canvas, this);
|
|
247
250
|
}
|
|
248
|
-
static async create(canvas, descriptors,
|
|
251
|
+
static async create(canvas, descriptors, sampleCount) {
|
|
249
252
|
const device = await getGpuDevice();
|
|
250
253
|
if (!device) {
|
|
251
254
|
return null;
|
|
@@ -266,7 +269,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
266
269
|
throw canvasContextError(canvas, 'webgpu');
|
|
267
270
|
}
|
|
268
271
|
noteCanvasContext(canvas, 'webgpu');
|
|
269
|
-
return new WebGPUHal(device, canvas, context, descriptors,
|
|
272
|
+
return new WebGPUHal(device, canvas, context, descriptors, sampleCount, pipelines, layouts);
|
|
270
273
|
}
|
|
271
274
|
resize(width, height) {
|
|
272
275
|
const { changed, scale } = syncCanvasSize(this.canvas, width, height);
|
|
@@ -352,7 +355,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
352
355
|
* {@link pendingDestroy} for why the frame decides when.
|
|
353
356
|
*
|
|
354
357
|
* Every buffer release routes here through the `RegionRegistry` destroy hook,
|
|
355
|
-
* so `uploadBuffer`, `deleteBuffer`, `deleteRegion
|
|
358
|
+
* so `uploadBuffer`, `deleteBuffer`, `deleteRegion` and
|
|
356
359
|
* `dispose` are all covered without any of them knowing a frame is open.
|
|
357
360
|
*/
|
|
358
361
|
destroyWhenIdle(resource) {
|
|
@@ -372,12 +375,12 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
372
375
|
createTexture(passId, binding, data, width, height) {
|
|
373
376
|
const existing = this.passTextures.get(passId);
|
|
374
377
|
if (existing) {
|
|
375
|
-
// Same hazard as a buffer,
|
|
376
|
-
// `uploadTexture` in tree is a colour ramp
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
378
|
+
// Same hazard as a buffer, and no longer a hypothetical one: every
|
|
379
|
+
// `uploadTexture` in tree is a colour ramp, and since the ramp became a
|
|
380
|
+
// `defineMark` `texture` they are all written by `GpuMarkBackend`'s
|
|
381
|
+
// `bindRamp` from inside `drawRegion` — mid-frame, between an encoded
|
|
382
|
+
// draw and its submit. The deferral is what makes replacing one there
|
|
383
|
+
// safe.
|
|
381
384
|
this.destroyWhenIdle(existing.texture);
|
|
382
385
|
}
|
|
383
386
|
const texture = this.device.createTexture({
|
package/esm/installUpload.d.ts
CHANGED
|
@@ -74,7 +74,10 @@ export declare function oneCell<K, T>(key: K, value: T | undefined | null): Read
|
|
|
74
74
|
*
|
|
75
75
|
* **Calling again only swaps the backend.** Displays wire this from
|
|
76
76
|
* `startRenderingBackend`, which fires again on every context-loss recovery;
|
|
77
|
-
* the diff and the encode
|
|
77
|
+
* the diff and the encode memo live in the setup thunk and survive it.
|
|
78
|
+
*
|
|
79
|
+
* `encode` runs through `createEncodeMemo`. A display that needs the encoded
|
|
80
|
+
* map itself holds that memo in a `.views` closure and hands identity cells.
|
|
78
81
|
*
|
|
79
82
|
* @see installUpload.test.ts for the upload, encode and release counts.
|
|
80
83
|
* @see ADR-078 for why this is one autorun and a diff.
|
package/esm/installUpload.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getType } from '@jbrowse/mobx-state-tree';
|
|
2
|
-
import { computed } from 'mobx';
|
|
3
2
|
import { contractReportsOn, reportContractViolation, } from './contractReports.js';
|
|
3
|
+
import { createEncodeMemo } from './encodeMemo.js';
|
|
4
4
|
import { createMapUploadSync } from './mapUploadSync.js';
|
|
5
5
|
import { isCheckedAtTheStore } from './regionDataMap.js';
|
|
6
6
|
/**
|
|
@@ -47,49 +47,17 @@ export function installUpload(self, backend, { cells, inputs, encode, render })
|
|
|
47
47
|
b.release(key);
|
|
48
48
|
},
|
|
49
49
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
upload: b => {
|
|
54
|
-
const current = own();
|
|
55
|
-
if (contractReportsOn()) {
|
|
56
|
-
checkPayloads(self, current);
|
|
57
|
-
}
|
|
58
|
-
return sync(b, current);
|
|
59
|
-
},
|
|
60
|
-
render: b => render(b, own()),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
const encoded = new Map();
|
|
64
|
-
const encodedFrom = new Map();
|
|
65
|
-
const props = inputs && computed(inputs);
|
|
66
|
-
let lastProps;
|
|
50
|
+
const own = encode
|
|
51
|
+
? createEncodeMemo(cells, inputs, encode)
|
|
52
|
+
: cells;
|
|
67
53
|
return {
|
|
68
54
|
upload: b => {
|
|
69
|
-
const current = cells();
|
|
70
55
|
if (contractReportsOn()) {
|
|
71
|
-
checkPayloads(self,
|
|
72
|
-
}
|
|
73
|
-
const p = props ? props.get() : undefined;
|
|
74
|
-
if (p !== lastProps) {
|
|
75
|
-
lastProps = p;
|
|
76
|
-
encodedFrom.clear();
|
|
77
|
-
}
|
|
78
|
-
for (const [key, data] of current) {
|
|
79
|
-
if (encodedFrom.get(key) !== data) {
|
|
80
|
-
encoded.set(key, encode(data, p, key));
|
|
81
|
-
encodedFrom.set(key, data);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
for (const key of encoded.keys()) {
|
|
85
|
-
if (!current.has(key)) {
|
|
86
|
-
encoded.delete(key);
|
|
87
|
-
encodedFrom.delete(key);
|
|
88
|
-
}
|
|
56
|
+
checkPayloads(self, cells());
|
|
89
57
|
}
|
|
90
|
-
return sync(b,
|
|
58
|
+
return sync(b, own());
|
|
91
59
|
},
|
|
92
|
-
render: b => render(b,
|
|
60
|
+
render: b => render(b, own()),
|
|
93
61
|
};
|
|
94
62
|
});
|
|
95
63
|
}
|
package/esm/instanceCache.d.ts
CHANGED
|
@@ -14,9 +14,8 @@ export interface InstanceCacheOpts<TData> {
|
|
|
14
14
|
colorOffsetWords: number;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* A
|
|
18
|
-
*
|
|
19
|
-
* other one.
|
|
17
|
+
* A cache of packed instance bytes with a geometry/color split: a recolor
|
|
18
|
+
* patches the one color lane in place instead of re-packing every other one.
|
|
20
19
|
*
|
|
21
20
|
* The split is worth having because the two inputs move on completely
|
|
22
21
|
* different schedules — geometry on an RPC refetch, colors on a `colorBy`
|
|
@@ -32,12 +31,16 @@ export interface InstanceCacheOpts<TData> {
|
|
|
32
31
|
* problem. `installUpload` exists for the same reason one
|
|
33
32
|
* layer up — an upload memo is not a hand-rolled `let`.
|
|
34
33
|
*
|
|
34
|
+
* **The geometry token is the key**, so nothing has to evict: a payload whose
|
|
35
|
+
* coordinates were replaced takes its bytes with it, and a cache shared by
|
|
36
|
+
* several displays cannot hand one display another's buffer — two displays
|
|
37
|
+
* never hold one coordinate array. That is also what lets a mark's `pack`
|
|
38
|
+
* hold one of these, a mark list being module-level and shared.
|
|
39
|
+
*
|
|
35
40
|
* The GPU re-upload still happens on a recolor (no HAL does partial buffer
|
|
36
41
|
* updates); what is skipped is the dominant CPU interleave.
|
|
37
42
|
*/
|
|
38
43
|
export declare function createInstanceCache<TData>(opts: InstanceCacheOpts<TData>): {
|
|
39
|
-
/** Packed bytes for `
|
|
40
|
-
get(
|
|
41
|
-
delete(key: number): void;
|
|
42
|
-
clear(): void;
|
|
44
|
+
/** Packed bytes for `data`, re-packing or re-coloring only as needed. */
|
|
45
|
+
get(data: TData): ArrayBuffer;
|
|
43
46
|
};
|
package/esm/instanceCache.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A
|
|
3
|
-
*
|
|
4
|
-
* other one.
|
|
2
|
+
* A cache of packed instance bytes with a geometry/color split: a recolor
|
|
3
|
+
* patches the one color lane in place instead of re-packing every other one.
|
|
5
4
|
*
|
|
6
5
|
* The split is worth having because the two inputs move on completely
|
|
7
6
|
* different schedules — geometry on an RPC refetch, colors on a `colorBy`
|
|
@@ -17,19 +16,25 @@
|
|
|
17
16
|
* problem. `installUpload` exists for the same reason one
|
|
18
17
|
* layer up — an upload memo is not a hand-rolled `let`.
|
|
19
18
|
*
|
|
19
|
+
* **The geometry token is the key**, so nothing has to evict: a payload whose
|
|
20
|
+
* coordinates were replaced takes its bytes with it, and a cache shared by
|
|
21
|
+
* several displays cannot hand one display another's buffer — two displays
|
|
22
|
+
* never hold one coordinate array. That is also what lets a mark's `pack`
|
|
23
|
+
* hold one of these, a mark list being module-level and shared.
|
|
24
|
+
*
|
|
20
25
|
* The GPU re-upload still happens on a recolor (no HAL does partial buffer
|
|
21
26
|
* updates); what is skipped is the dominant CPU interleave.
|
|
22
27
|
*/
|
|
23
28
|
export function createInstanceCache(opts) {
|
|
24
29
|
const { geomToken, colors, interleave, strideWords, colorOffsetWords } = opts;
|
|
25
|
-
const cache = new
|
|
30
|
+
const cache = new WeakMap();
|
|
26
31
|
return {
|
|
27
|
-
/** Packed bytes for `
|
|
28
|
-
get(
|
|
32
|
+
/** Packed bytes for `data`, re-packing or re-coloring only as needed. */
|
|
33
|
+
get(data) {
|
|
29
34
|
const geom = geomToken(data);
|
|
30
35
|
const cols = colors(data);
|
|
31
|
-
const hit = cache.get(
|
|
32
|
-
if (hit
|
|
36
|
+
const hit = cache.get(geom);
|
|
37
|
+
if (hit) {
|
|
33
38
|
if (hit.colors !== cols) {
|
|
34
39
|
const u32 = new Uint32Array(hit.buf);
|
|
35
40
|
for (let i = 0, n = cols.length; i < n; i++) {
|
|
@@ -40,14 +45,8 @@ export function createInstanceCache(opts) {
|
|
|
40
45
|
return hit.buf;
|
|
41
46
|
}
|
|
42
47
|
const buf = interleave(data);
|
|
43
|
-
cache.set(
|
|
48
|
+
cache.set(geom, { colors: cols, buf });
|
|
44
49
|
return buf;
|
|
45
50
|
},
|
|
46
|
-
delete(key) {
|
|
47
|
-
cache.delete(key);
|
|
48
|
-
},
|
|
49
|
-
clear() {
|
|
50
|
-
cache.clear();
|
|
51
|
-
},
|
|
52
51
|
};
|
|
53
52
|
}
|
package/esm/mapUploadSync.d.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
export interface MapUploadTarget<K, T, B> {
|
|
2
2
|
upload: (backend: B, key: K, data: T) => void;
|
|
3
|
-
/**
|
|
4
|
-
* How a key that left the map is released. `remove` deletes it by itself;
|
|
5
|
-
* `prune` is handed the keys still present and releases everything else.
|
|
6
|
-
* `installUpload` takes the first, which is the only correct shape on a
|
|
7
|
-
* canvas several displays share and does the same job on a display's own
|
|
8
|
-
* map.
|
|
9
|
-
*/
|
|
10
3
|
remove?: (backend: B, key: K) => void;
|
|
11
|
-
prune?: (backend: B, active: ReadonlySet<K>) => void;
|
|
12
4
|
}
|
|
13
5
|
/**
|
|
14
6
|
* The one reference diff every keyed GPU upload in the tree runs on. Uploads
|
package/esm/mapUploadSync.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Packed ABGR → `rgba(...)`, and the run-tracking fill a painter wraps it in.
|
|
3
|
+
*
|
|
4
|
+
* `@jbrowse/core/util/colorBits` re-exports `abgrToCssRgba` from here: core
|
|
5
|
+
* depends on render-core, never the reverse. The direction that genuinely
|
|
6
|
+
* needs core is resolving a CSS colour to a packed number, which is why a
|
|
7
|
+
* shape takes packed colours and the display resolves them.
|
|
8
|
+
*/
|
|
9
|
+
export declare function abgrToCssRgba(c: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* A fill-style setter that only touches `ctx.fillStyle` when the colour
|
|
12
|
+
* changes. Most of a painting is runs of one colour, and both the string
|
|
13
|
+
* allocation and the context write cost more than the comparison.
|
|
14
|
+
*/
|
|
15
|
+
export declare function makeAbgrFill(ctx: {
|
|
16
|
+
fillStyle: string | CanvasGradient | CanvasPattern;
|
|
17
|
+
}): (abgr: number) => void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Packed ABGR → `rgba(...)`, and the run-tracking fill a painter wraps it in.
|
|
3
|
+
*
|
|
4
|
+
* `@jbrowse/core/util/colorBits` re-exports `abgrToCssRgba` from here: core
|
|
5
|
+
* depends on render-core, never the reverse. The direction that genuinely
|
|
6
|
+
* needs core is resolving a CSS colour to a packed number, which is why a
|
|
7
|
+
* shape takes packed colours and the display resolves them.
|
|
8
|
+
*/
|
|
9
|
+
export function abgrToCssRgba(c) {
|
|
10
|
+
const a = ((c >>> 24) & 255) / 255;
|
|
11
|
+
return `rgba(${c & 255},${(c >>> 8) & 255},${(c >>> 16) & 255},${a})`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A fill-style setter that only touches `ctx.fillStyle` when the colour
|
|
15
|
+
* changes. Most of a painting is runs of one colour, and both the string
|
|
16
|
+
* allocation and the context write cost more than the comparison.
|
|
17
|
+
*/
|
|
18
|
+
export function makeAbgrFill(ctx) {
|
|
19
|
+
let last;
|
|
20
|
+
return (abgr) => {
|
|
21
|
+
if (abgr !== last) {
|
|
22
|
+
last = abgr;
|
|
23
|
+
ctx.fillStyle = abgrToCssRgba(abgr);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RenderBlock } from '../renderBlock.ts';
|
|
2
|
+
import type { MarkContext2D, MarkFrame, MarkShape } from './types.ts';
|
|
3
|
+
export interface RecordedRect {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
w: number;
|
|
7
|
+
h: number;
|
|
8
|
+
fillStyle: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A `MarkContext2D` that records every rect a painter fills, in paint order —
|
|
12
|
+
* `fillRect` and path `rect` alike, so a shape that batches by colour records
|
|
13
|
+
* the same list as one that fills per instance. A path traced with
|
|
14
|
+
* `moveTo`/`lineTo` and filled records its bounding box, which is what a
|
|
15
|
+
* shape whose hit test answers a polygon's box (the variant inversion
|
|
16
|
+
* triangle) is held to.
|
|
17
|
+
*
|
|
18
|
+
* A **stroked** path records one rect per `moveTo`/`lineTo` edge, each the
|
|
19
|
+
* edge's bounding box widened by half the current `lineWidth` — the extent a
|
|
20
|
+
* round cap reaches on all four sides. Per edge rather than per `stroke()`,
|
|
21
|
+
* because a painter that batches a colour run into one path (dotplot's) would
|
|
22
|
+
* otherwise record the run's hull as a single instance. `fillStyle` on such a
|
|
23
|
+
* record is the `strokeStyle` it went down in; the field is the colour of the
|
|
24
|
+
* ink either way.
|
|
25
|
+
*/
|
|
26
|
+
export declare function recordingContext(): {
|
|
27
|
+
ctx: MarkContext2D;
|
|
28
|
+
calls: RecordedRect[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The draw-against-hit gate for a shape whose instances paint as rects: paint
|
|
32
|
+
* the block, then walk it in `step` px and hold `hitNearest` to what the
|
|
33
|
+
* painter recorded. A hit at distance 0 has to sit on the rect of the index it
|
|
34
|
+
* names, and a point on a rect has to answer that rect — the last-painted one
|
|
35
|
+
* where rects overlap, since the sweep hands candidates in back to front.
|
|
36
|
+
*
|
|
37
|
+
* Returns the violations rather than asserting, so a test reads as
|
|
38
|
+
* `expect(sweep(...)).toEqual([])`. `maxDistSq` is the caller's, because a
|
|
39
|
+
* shape whose hit target is wider than its ink (`point`) needs a bound that
|
|
40
|
+
* admits the whole rect.
|
|
41
|
+
*/
|
|
42
|
+
export declare function sweepDrawAgainstHit<C extends {
|
|
43
|
+
count: number;
|
|
44
|
+
}, P>(shape: MarkShape<C, P>, channels: C, block: RenderBlock, frame: MarkFrame, params: P, { maxDistSq, step }: {
|
|
45
|
+
maxDistSq: number;
|
|
46
|
+
step?: number;
|
|
47
|
+
}): string[];
|