@jbrowse/render-core 5.0.0-beta.8 → 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.
- package/esm/RenderCanvas.d.ts +2 -2
- package/esm/RenderLifecycleMixin.d.ts +7 -6
- package/esm/RenderLifecycleMixin.js +7 -6
- package/esm/canvas2dUtils.d.ts +58 -17
- package/esm/canvas2dUtils.js +82 -30
- package/esm/computePipeline.d.ts +4 -3
- package/esm/computePipeline.js +21 -14
- package/esm/coverageBand.d.ts +3 -3
- package/esm/coverageBand.js +2 -2
- package/esm/coverageBandBuffers.d.ts +2 -3
- package/esm/createRenderingBackend.d.ts +1 -1
- package/esm/displayPhase.d.ts +79 -109
- package/esm/displayPhase.js +42 -83
- package/esm/gpuDevice.d.ts +11 -10
- package/esm/gpuDevice.js +10 -11
- package/esm/graphicsCapabilities.d.ts +6 -7
- package/esm/graphicsCapabilities.js +3 -4
- package/esm/hal/createHal.js +1 -1
- package/esm/hal/deviceGpuCache.d.ts +25 -30
- package/esm/hal/deviceGpuCache.js +85 -97
- package/esm/hal/index.d.ts +2 -2
- package/esm/hal/index.js +1 -1
- package/esm/hal/mockHal.d.ts +8 -6
- package/esm/hal/mockHal.js +7 -2
- package/esm/hal/passIds.d.ts +8 -7
- package/esm/hal/passIds.js +11 -10
- package/esm/hal/types.d.ts +28 -12
- package/esm/hal/webgl2Hal.d.ts +11 -3
- package/esm/hal/webgl2Hal.js +116 -145
- package/esm/hal/webgpuHal.d.ts +8 -8
- package/esm/hal/webgpuHal.js +97 -124
- package/esm/instancePass.d.ts +2 -1
- package/esm/mapUploadSync.d.ts +2 -2
- package/esm/mapUploadSync.js +2 -2
- package/esm/marks/barMark.d.ts +6 -3
- package/esm/marks/barMark.js +19 -13
- package/esm/marks/drawAgainstHit.d.ts +58 -39
- package/esm/marks/drawAgainstHit.js +136 -96
- package/esm/marks/glyphPaint.d.ts +14 -2
- package/esm/marks/glyphPaint.js +16 -2
- package/esm/marks/index.d.ts +4 -2
- package/esm/marks/index.js +3 -2
- package/esm/marks/markBackend.d.ts +2 -2
- package/esm/marks/markBackend.js +2 -2
- package/esm/marks/markHit.d.ts +2 -2
- package/esm/marks/markHit.js +2 -2
- package/esm/marks/markPlan.d.ts +2 -2
- package/esm/marks/markPlan.js +2 -2
- package/esm/marks/markRamp.d.ts +1 -3
- package/esm/marks/markRamp.js +1 -5
- package/esm/marks/nearestMarkHit.d.ts +49 -0
- package/esm/marks/nearestMarkHit.js +126 -0
- package/esm/marks/pointMark.d.ts +9 -2
- package/esm/marks/pointMark.js +36 -31
- package/esm/marks/rowLane.d.ts +17 -0
- package/esm/marks/rowLane.js +11 -0
- package/esm/marks/spanMark.d.ts +6 -26
- package/esm/marks/spanMark.js +34 -31
- package/esm/marks/types.d.ts +51 -181
- package/esm/marks/types.js +91 -131
- package/esm/namedReactions.d.ts +3 -4
- package/esm/namedReactions.js +3 -4
- package/esm/scoreScale.d.ts +20 -0
- package/esm/scoreScale.js +77 -0
- package/esm/shaders/barMark.generated.d.ts +2 -3
- package/esm/shaders/barMark.generated.js +4 -3
- package/esm/shaders/barMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/barMark.glsl.generated.js +4 -0
- package/esm/shaders/barMark.iface.generated.d.ts +12 -6
- package/esm/shaders/barMark.iface.generated.js +27 -15
- package/esm/shaders/barMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/barMark.wgsl.generated.js +3 -0
- package/esm/shaders/coverageBar.generated.d.ts +2 -3
- package/esm/shaders/coverageBar.generated.js +4 -3
- package/esm/shaders/coverageBar.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageBar.glsl.generated.js +4 -0
- package/esm/shaders/coverageBar.iface.generated.js +1 -1
- package/esm/shaders/coverageBar.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageBar.wgsl.generated.js +3 -0
- package/esm/shaders/coverageIndicator.generated.d.ts +2 -3
- package/esm/shaders/coverageIndicator.generated.js +4 -3
- package/esm/shaders/coverageIndicator.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageIndicator.glsl.generated.js +4 -0
- package/esm/shaders/coverageIndicator.iface.generated.js +1 -1
- package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageIndicator.wgsl.generated.js +3 -0
- package/esm/shaders/coverageInterbase.generated.d.ts +2 -3
- package/esm/shaders/coverageInterbase.generated.js +4 -3
- package/esm/shaders/coverageInterbase.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageInterbase.glsl.generated.js +4 -0
- package/esm/shaders/coverageInterbase.iface.generated.js +1 -1
- package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageInterbase.wgsl.generated.js +3 -0
- package/esm/shaders/coverageMod.generated.d.ts +2 -3
- package/esm/shaders/coverageMod.generated.js +4 -3
- package/esm/shaders/coverageMod.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageMod.glsl.generated.js +4 -0
- package/esm/shaders/coverageMod.iface.generated.js +1 -1
- package/esm/shaders/coverageMod.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageMod.wgsl.generated.js +3 -0
- package/esm/shaders/coverageSnp.generated.d.ts +2 -3
- package/esm/shaders/coverageSnp.generated.js +4 -3
- package/esm/shaders/coverageSnp.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageSnp.glsl.generated.js +4 -0
- package/esm/shaders/coverageSnp.iface.generated.js +1 -1
- package/esm/shaders/coverageSnp.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageSnp.wgsl.generated.js +3 -0
- package/esm/shaders/pointMark.generated.d.ts +2 -3
- package/esm/shaders/pointMark.generated.js +4 -3
- package/esm/shaders/pointMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/pointMark.glsl.generated.js +4 -0
- package/esm/shaders/pointMark.iface.generated.d.ts +11 -3
- package/esm/shaders/pointMark.iface.generated.js +23 -9
- package/esm/shaders/pointMark.js.generated.d.ts +1 -1
- package/esm/shaders/pointMark.js.generated.js +51 -29
- package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/pointMark.wgsl.generated.js +3 -0
- package/esm/shaders/scoreScale.js.generated.js +46 -22
- package/esm/shaders/spanMark.generated.d.ts +2 -3
- package/esm/shaders/spanMark.generated.js +4 -3
- package/esm/shaders/spanMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/spanMark.glsl.generated.js +4 -0
- package/esm/shaders/spanMark.iface.generated.js +1 -1
- package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/spanMark.wgsl.generated.js +3 -0
- package/esm/slangPass.d.ts +9 -10
- package/esm/slangPass.js +9 -3
- package/esm/useGraphicsCapabilities.d.ts +1 -1
- package/esm/useGraphicsCapabilities.js +1 -1
- package/esm/webgpuUtils.d.ts +3 -11
- package/esm/webgpuUtils.js +12 -33
- package/package.json +3 -2
- package/src/shaders/barMark.generated.ts +6 -5
- package/src/shaders/barMark.glsl.generated.ts +6 -0
- package/src/shaders/barMark.iface.generated.ts +31 -15
- package/src/shaders/barMark.slang +11 -3
- package/src/shaders/barMark.wgsl.generated.ts +4 -0
- package/src/shaders/colorPack.slang +3 -2
- package/src/shaders/coverageBand.slang +5 -11
- package/src/shaders/coverageBar.generated.ts +6 -5
- package/src/shaders/coverageBar.glsl.generated.ts +6 -0
- package/src/shaders/coverageBar.iface.generated.ts +1 -1
- package/src/shaders/coverageBar.slang +1 -1
- package/src/shaders/coverageBar.wgsl.generated.ts +4 -0
- package/src/shaders/coverageIndicator.generated.ts +6 -5
- package/src/shaders/coverageIndicator.glsl.generated.ts +6 -0
- package/src/shaders/coverageIndicator.iface.generated.ts +1 -1
- package/src/shaders/coverageIndicator.wgsl.generated.ts +4 -0
- package/src/shaders/coverageInterbase.generated.ts +6 -5
- package/src/shaders/coverageInterbase.glsl.generated.ts +6 -0
- package/src/shaders/coverageInterbase.iface.generated.ts +1 -1
- package/src/shaders/coverageInterbase.wgsl.generated.ts +4 -0
- package/src/shaders/coverageMod.generated.ts +6 -5
- package/src/shaders/coverageMod.glsl.generated.ts +6 -0
- package/src/shaders/coverageMod.iface.generated.ts +1 -1
- package/src/shaders/coverageMod.wgsl.generated.ts +4 -0
- package/src/shaders/coverageSnp.generated.ts +6 -5
- package/src/shaders/coverageSnp.glsl.generated.ts +6 -0
- package/src/shaders/coverageSnp.iface.generated.ts +1 -1
- package/src/shaders/coverageSnp.wgsl.generated.ts +4 -0
- package/src/shaders/hpmath.slang +18 -6
- package/src/shaders/pointMark.generated.ts +6 -6
- package/src/shaders/pointMark.glsl.generated.ts +6 -0
- package/src/shaders/pointMark.iface.generated.ts +28 -9
- package/src/shaders/pointMark.js.generated.ts +48 -29
- package/src/shaders/pointMark.slang +18 -5
- package/src/shaders/pointMark.wgsl.generated.ts +4 -0
- package/src/shaders/scoreScale.js.generated.ts +43 -22
- package/src/shaders/scoreScale.slang +37 -53
- package/src/shaders/spanMark.generated.ts +6 -5
- package/src/shaders/spanMark.glsl.generated.ts +6 -0
- package/src/shaders/spanMark.iface.generated.ts +1 -1
- package/src/shaders/spanMark.wgsl.generated.ts +4 -0
- package/src/shaders/valueScale.slang +8 -45
package/esm/hal/webgl2Hal.js
CHANGED
|
@@ -43,8 +43,7 @@ function bindUniformBlock(gl, program, blockName, bindingPoint) {
|
|
|
43
43
|
gl.uniformBlockBinding(program, idx, bindingPoint);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
//
|
|
47
|
-
// enable verbose logging. Kept guarded so production builds stay quiet.
|
|
46
|
+
// `DEBUG.webgl2 = true` in devtools, or `?webgl2-debug=1`, logs verbosely.
|
|
48
47
|
function debugEnabled() {
|
|
49
48
|
if (typeof window === 'undefined') {
|
|
50
49
|
return false;
|
|
@@ -91,59 +90,33 @@ function glBlendFactor(gl, factor) {
|
|
|
91
90
|
return gl.ONE_MINUS_SRC_ALPHA;
|
|
92
91
|
}
|
|
93
92
|
}
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
// lose. These counters surface the leak when it happens.
|
|
93
|
+
// Live-context counts for the debug log: a leaked context evicts the oldest
|
|
94
|
+
// once the browser's cap is reached.
|
|
97
95
|
let totalCreated = 0;
|
|
98
96
|
let totalDisposed = 0;
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// nothing to ask — but "ask the driver" was never what the guard was for. Its
|
|
103
|
-
// job is to turn one pathological upload into the "zoom in" banner, and without
|
|
104
|
-
// it the same upload that banners on WebGPU takes Chrome's context down here
|
|
105
|
-
// instead. That is strictly worse than a blank track on a page at the context
|
|
106
|
-
// ceiling: the loss evicts a sibling, whose recovery evicts another, and
|
|
107
|
-
// GPU_CONTEXT_BUDGET.md is that cascade.
|
|
108
|
-
//
|
|
109
|
-
// 256 MiB is WebGPU's *spec default* `maxBufferSize`. It is not parity with
|
|
110
|
-
// what the WebGPU backend actually refuses at: `gpuDevice.acquire` raises the
|
|
111
|
-
// limit to `adapter.limits.maxBufferSize`, which is 2147483644 bytes (~2 GiB)
|
|
112
|
-
// on the Firefox Nightly / Intel UHD 630 this was checked on, so WebGL2 is the
|
|
113
|
-
// stricter of the two and a region can banner here while rendering there. That
|
|
114
|
-
// asymmetry is accepted — a quarter-gigabyte single vertex buffer is not
|
|
115
|
-
// something to hand an API that answers a failed allocation by dropping the
|
|
116
|
-
// context.
|
|
117
|
-
//
|
|
118
|
-
// Deliberately a ceiling and not a budget: it catches the single upload
|
|
119
|
-
// nothing else bounds, and says nothing about the total, which is
|
|
120
|
-
// ARCHITECTURAL_LIMITS.md §"No session-level GPU memory budget".
|
|
97
|
+
// WebGPU's spec-default `maxBufferSize`, since WebGL2 has none to query: an
|
|
98
|
+
// upload past it banners rather than dropping the context
|
|
99
|
+
// (ARCHITECTURAL_LIMITS.md).
|
|
121
100
|
const MAX_VERTEX_BUFFER_BYTES = 256 * 1024 * 1024;
|
|
122
|
-
// Behavioral parity with WebGPUHal is enforced by tests, not by this file:
|
|
123
|
-
// products/jbrowse-web/browser-tests/compare-backends.ts pixel-diffs webgl vs
|
|
124
|
-
// webgpu vs canvas2d output, and shared buffer bookkeeping is covered by
|
|
125
|
-
// hal/regionRegistry.test.ts. Neither HAL is where attribute layout is checked —
|
|
126
|
-
// `assertVertexInputsMatch` does that at `pnpm gen:shaders` time, per shader and
|
|
127
|
-
// per target. Mirror any behavior change in webgpuHal.ts — and note that
|
|
128
|
-
// `GpuHalBase` already holds the parts that were only ever mirrored: the
|
|
129
|
-
// descriptor map, the buffer registry, both upload shells and their over-limit
|
|
130
|
-
// wording, and the dispose guard.
|
|
131
101
|
export class WebGL2Hal extends GpuHalBase {
|
|
132
102
|
gl;
|
|
133
103
|
canvas;
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
104
|
+
// `null` is a pass whose link failed, so it is reported once, not per frame.
|
|
105
|
+
passes = new Map();
|
|
106
|
+
glsl;
|
|
107
|
+
// By vertex source, then fragment source, attribute names and sampler as
|
|
108
|
+
// JSON: every pass id over one program shares it, and a failed link keeps
|
|
109
|
+
// its error.
|
|
110
|
+
programs = new Map();
|
|
111
|
+
passTextures = new Map();
|
|
137
112
|
ubo;
|
|
138
|
-
//
|
|
139
|
-
// now read on every upload, not only on a texture one.
|
|
113
|
+
// `getParameter` is a synchronous driver query; `limits()` runs per upload.
|
|
140
114
|
maxTextureDim;
|
|
141
115
|
debug = false;
|
|
142
116
|
instanceId = 0;
|
|
143
117
|
firstDrawSeen = new Set();
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
// check alone can't guard dispose().
|
|
118
|
+
// Never cleared: GL objects from before a loss stay invalid after restore,
|
|
119
|
+
// when isContextLost() already answers false.
|
|
147
120
|
contextWasLost = false;
|
|
148
121
|
contextLostListener = null;
|
|
149
122
|
contextRestoredListener = null;
|
|
@@ -156,9 +129,18 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
156
129
|
console.error(`[WebGL2Hal] GL error at "${label}": ${glErrorName(this.gl, err)}`);
|
|
157
130
|
}
|
|
158
131
|
}
|
|
159
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Loads every pass's GLSL before the context is acquired, so a load that
|
|
134
|
+
* fails leaves the canvas free for Canvas2D.
|
|
135
|
+
*/
|
|
136
|
+
static async create(canvas, descriptors) {
|
|
137
|
+
const glsl = await Promise.all(descriptors.map(async (d) => [d.id, await d.source.glsl()]));
|
|
138
|
+
return new WebGL2Hal(canvas, descriptors, new Map(glsl));
|
|
139
|
+
}
|
|
140
|
+
constructor(canvas, descriptors, glsl) {
|
|
160
141
|
super(descriptors, 'WebGL2Hal');
|
|
161
142
|
this.canvas = canvas;
|
|
143
|
+
this.glsl = glsl;
|
|
162
144
|
this.debug = debugEnabled();
|
|
163
145
|
totalCreated += 1;
|
|
164
146
|
this.instanceId = totalCreated;
|
|
@@ -178,23 +160,14 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
178
160
|
canvas.addEventListener('webglcontextrestored', onContextRestored, false);
|
|
179
161
|
this.contextLostListener = onContextLost;
|
|
180
162
|
this.contextRestoredListener = onContextRestored;
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
// (edge pixel: rgb = color*alpha, a = alpha). With premultipliedAlpha:true
|
|
185
|
-
// the browser compositor reads those as premultiplied and composites correctly:
|
|
186
|
-
// output = fb.rgb + bg*(1-fb.a)
|
|
187
|
-
// With premultipliedAlpha:false the compositor treats them as straight alpha and
|
|
188
|
-
// multiplies rgb by alpha a second time, making AA edges appear too dark.
|
|
189
|
-
// The WebGPU HAL uses alphaMode:'premultiplied' for the same reason.
|
|
163
|
+
// The blend leaves premultiplied values in the framebuffer, which a
|
|
164
|
+
// straight-alpha compositor would darken at AA edges; WebGPU's alphaMode
|
|
165
|
+
// matches.
|
|
190
166
|
const gl = canvas.getContext('webgl2', {
|
|
191
167
|
antialias: true,
|
|
192
168
|
premultipliedAlpha: true,
|
|
193
169
|
});
|
|
194
170
|
if (!gl) {
|
|
195
|
-
// Was a bare 'WebGL2 not supported', which is the wrong diagnosis for the
|
|
196
|
-
// case that actually reaches here in production: a re-init on a canvas the
|
|
197
|
-
// WebGPU rung already committed. See canvasContext.ts.
|
|
198
171
|
throw canvasContextError(canvas, 'webgl2');
|
|
199
172
|
}
|
|
200
173
|
noteCanvasContext(canvas, 'webgl2');
|
|
@@ -203,29 +176,20 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
203
176
|
this.ubo = gl.createBuffer();
|
|
204
177
|
gl.bindBuffer(gl.UNIFORM_BUFFER, this.ubo);
|
|
205
178
|
gl.bufferData(gl.UNIFORM_BUFFER, this.uniformByteSize, gl.DYNAMIC_DRAW);
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
// most of them behind a colorBy/arc/per-base setting — and linking one
|
|
210
|
-
// costs tens of ms of driver time on the main thread. A profile of a
|
|
211
|
-
// three-track LGV linked 29 programs and drew with 14.
|
|
212
|
-
//
|
|
213
|
-
// The first descriptor still links eagerly as a canary, so a GL stack that
|
|
214
|
-
// can't compile our shaders at all throws from the constructor and
|
|
215
|
-
// `createGpuHal` falls back to Canvas2D — the ladder only runs at
|
|
216
|
-
// construction. Per-pass compile failures after that are caught in
|
|
217
|
-
// `getPass` and surfaced through the error handler.
|
|
179
|
+
// Programs link on first draw (`getPass`). The first links here as a
|
|
180
|
+
// canary, so a GL stack that cannot compile our shaders throws while
|
|
181
|
+
// `createGpuHal` can still fall back to Canvas2D.
|
|
218
182
|
const canary = descriptors[0];
|
|
219
183
|
if (canary) {
|
|
220
|
-
this.
|
|
184
|
+
this.passes.set(canary.id, {
|
|
185
|
+
descriptor: canary,
|
|
186
|
+
linked: this.link(canary),
|
|
187
|
+
});
|
|
221
188
|
}
|
|
222
189
|
gl.enable(gl.BLEND);
|
|
223
190
|
}
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
// is WebGPU's. Past it, an unguarded bufferData loses the context in Chrome
|
|
227
|
-
// and throws RangeError in Firefox; neither is a getError() case, so the
|
|
228
|
-
// base's report is the only place the display can be told.
|
|
191
|
+
// Past the ceiling an unguarded bufferData loses the context in Chrome and
|
|
192
|
+
// throws in Firefox, neither of them a getError() case.
|
|
229
193
|
limits() {
|
|
230
194
|
return {
|
|
231
195
|
maxBufferBytes: MAX_VERTEX_BUFFER_BYTES,
|
|
@@ -239,28 +203,55 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
239
203
|
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
|
|
240
204
|
return { vbo, count };
|
|
241
205
|
}
|
|
242
|
-
// No mid-frame deferral, unlike `WebGPUHal
|
|
243
|
-
//
|
|
244
|
-
// beginFrame and endFrame is defined. The parity that matters is the
|
|
245
|
-
// caller's — the same upload sequence is legal on both.
|
|
206
|
+
// No mid-frame deferral, unlike `WebGPUHal`: GL is immediate-mode, so a draw
|
|
207
|
+
// already issued has consumed the buffer.
|
|
246
208
|
destroyBuffer(buf) {
|
|
247
209
|
const gl = this.gl;
|
|
248
210
|
if (!this.contextWasLost && !gl.isContextLost()) {
|
|
249
211
|
gl.deleteBuffer(buf.vbo);
|
|
250
212
|
}
|
|
251
213
|
}
|
|
252
|
-
|
|
214
|
+
// Throws on a failed link, and again on every later ask for that content.
|
|
215
|
+
link(desc) {
|
|
253
216
|
const gl = this.gl;
|
|
254
|
-
const
|
|
217
|
+
const stages = this.glsl.get(desc.id);
|
|
218
|
+
if (!stages) {
|
|
219
|
+
throw new Error(`no GLSL was loaded for pass "${desc.id}"`);
|
|
220
|
+
}
|
|
221
|
+
const { GLSL_VERTEX, GLSL_FRAGMENT } = stages;
|
|
222
|
+
const tb = desc.textures?.[0];
|
|
223
|
+
const names = desc.vertexAttributes.map(attr => attr.name);
|
|
224
|
+
const sampler = tb && [tb.glUniformName, tb.glTextureUnit];
|
|
225
|
+
const key = JSON.stringify([GLSL_FRAGMENT, names, sampler]);
|
|
226
|
+
let byVertex = this.programs.get(GLSL_VERTEX);
|
|
227
|
+
if (!byVertex) {
|
|
228
|
+
byVertex = new Map();
|
|
229
|
+
this.programs.set(GLSL_VERTEX, byVertex);
|
|
230
|
+
}
|
|
231
|
+
const known = byVertex.get(key);
|
|
232
|
+
if (known instanceof Error) {
|
|
233
|
+
throw known;
|
|
234
|
+
}
|
|
235
|
+
if (known) {
|
|
236
|
+
return known;
|
|
237
|
+
}
|
|
238
|
+
let program;
|
|
239
|
+
try {
|
|
240
|
+
program = createProgram(gl, GLSL_VERTEX, GLSL_FRAGMENT);
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
byVertex.set(key, e instanceof Error ? e : new Error(String(e)));
|
|
244
|
+
throw e;
|
|
245
|
+
}
|
|
255
246
|
bindUniformBlock(gl, program, 'Uniforms', 0);
|
|
256
247
|
this.checkGlError(`link pass "${desc.id}"`);
|
|
257
|
-
const attrLocs =
|
|
248
|
+
const attrLocs = names.map(name => gl.getAttribLocation(program, name));
|
|
258
249
|
if (this.debug) {
|
|
259
|
-
const pairs =
|
|
250
|
+
const pairs = names.map((name, i) => `${name}@${attrLocs[i]}`);
|
|
260
251
|
console.warn(`[WebGL2Hal] pass "${desc.id}" stride=${desc.instanceStride} attrs: ${pairs.join(', ')}`);
|
|
261
|
-
const missing =
|
|
252
|
+
const missing = names.filter((_, i) => attrLocs[i] < 0);
|
|
262
253
|
if (missing.length > 0) {
|
|
263
|
-
console.warn(`[WebGL2Hal] pass "${desc.id}" missing attribute locations: ${missing.
|
|
254
|
+
console.warn(`[WebGL2Hal] pass "${desc.id}" missing attribute locations: ${missing.join(', ')}`);
|
|
264
255
|
}
|
|
265
256
|
}
|
|
266
257
|
const vao = gl.createVertexArray();
|
|
@@ -272,26 +263,14 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
272
263
|
}
|
|
273
264
|
}
|
|
274
265
|
gl.bindVertexArray(null);
|
|
275
|
-
let textureState = null;
|
|
276
|
-
const tb = desc.textures?.[0];
|
|
277
266
|
if (tb) {
|
|
278
|
-
// Bind the sampler uniform to the texture unit once — it never changes.
|
|
279
267
|
gl.useProgram(program);
|
|
280
268
|
gl.uniform1i(gl.getUniformLocation(program, tb.glUniformName), tb.glTextureUnit);
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
vao,
|
|
286
|
-
descriptor: desc,
|
|
287
|
-
textureState,
|
|
288
|
-
attrLocs,
|
|
289
|
-
};
|
|
290
|
-
this.passes.set(desc.id, state);
|
|
291
|
-
return state;
|
|
269
|
+
}
|
|
270
|
+
const linked = { program, vao, attrLocs };
|
|
271
|
+
byVertex.set(key, linked);
|
|
272
|
+
return linked;
|
|
292
273
|
}
|
|
293
|
-
// Compiled pass for `passId`, building it the first time it's asked for.
|
|
294
|
-
// Returns undefined for an unknown id or a pass whose program failed to link.
|
|
295
274
|
getPass(passId) {
|
|
296
275
|
const existing = this.passes.get(passId);
|
|
297
276
|
if (existing !== undefined) {
|
|
@@ -302,7 +281,9 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
302
281
|
return undefined;
|
|
303
282
|
}
|
|
304
283
|
try {
|
|
305
|
-
|
|
284
|
+
const pass = { descriptor: desc, linked: this.link(desc) };
|
|
285
|
+
this.passes.set(passId, pass);
|
|
286
|
+
return pass;
|
|
306
287
|
}
|
|
307
288
|
catch (e) {
|
|
308
289
|
this.passes.set(passId, null);
|
|
@@ -315,12 +296,9 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
315
296
|
}
|
|
316
297
|
createTexture(passId, binding, data, width, height) {
|
|
317
298
|
const gl = this.gl;
|
|
318
|
-
const
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
if (ts.texture) {
|
|
323
|
-
gl.deleteTexture(ts.texture);
|
|
299
|
+
const existing = this.passTextures.get(passId);
|
|
300
|
+
if (existing) {
|
|
301
|
+
gl.deleteTexture(existing);
|
|
324
302
|
}
|
|
325
303
|
const tex = gl.createTexture();
|
|
326
304
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
@@ -337,7 +315,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
337
315
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
338
316
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
339
317
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
340
|
-
|
|
318
|
+
this.passTextures.set(passId, tex);
|
|
341
319
|
}
|
|
342
320
|
writeUniforms(data) {
|
|
343
321
|
const gl = this.gl;
|
|
@@ -369,8 +347,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
369
347
|
}
|
|
370
348
|
drawPass(passId, regionKey, bufferPassId) {
|
|
371
349
|
const gl = this.gl;
|
|
372
|
-
// Buffer first,
|
|
373
|
-
// the reason its shader gets compiled.
|
|
350
|
+
// Buffer first, so a pass with nothing to draw links no program.
|
|
374
351
|
const regionBuf = this.regions.get(regionKey, bufferPassId ?? passId);
|
|
375
352
|
if (!regionBuf || regionBuf.count === 0) {
|
|
376
353
|
return;
|
|
@@ -380,10 +357,10 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
380
357
|
return;
|
|
381
358
|
}
|
|
382
359
|
this.applyBlendState(pass.descriptor);
|
|
383
|
-
gl.useProgram(pass.program);
|
|
384
|
-
gl.bindVertexArray(pass.vao);
|
|
360
|
+
gl.useProgram(pass.linked.program);
|
|
361
|
+
gl.bindVertexArray(pass.linked.vao);
|
|
385
362
|
this.bindAttributes(pass, regionBuf.vbo);
|
|
386
|
-
this.
|
|
363
|
+
this.bindTexture(passId, pass.descriptor);
|
|
387
364
|
const topo = pass.descriptor.topology ?? 'triangle-list';
|
|
388
365
|
const glMode = topo === 'triangle-strip'
|
|
389
366
|
? gl.TRIANGLE_STRIP
|
|
@@ -409,10 +386,6 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
409
386
|
if (this.debug) {
|
|
410
387
|
console.warn(`[WebGL2Hal #${this.instanceId}] DISPOSING context (live=${totalCreated - totalDisposed}/${totalCreated})`);
|
|
411
388
|
}
|
|
412
|
-
// Remove canvas event listeners to prevent closure references from keeping
|
|
413
|
-
// the context alive after disposal. This is critical for test suites where
|
|
414
|
-
// multiple contexts are created in sequence (e.g. Puppeteer page navigation).
|
|
415
|
-
// Double-dispose is short-circuited above, so listeners are guaranteed set.
|
|
416
389
|
if (this.contextLostListener) {
|
|
417
390
|
this.canvas.removeEventListener('webglcontextlost', this.contextLostListener);
|
|
418
391
|
}
|
|
@@ -421,24 +394,24 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
421
394
|
}
|
|
422
395
|
this.regions.deleteAll();
|
|
423
396
|
if (!this.contextWasLost && !gl.isContextLost()) {
|
|
424
|
-
for (const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
gl.deleteTexture(pass.textureState.texture);
|
|
397
|
+
for (const byVertex of this.programs.values()) {
|
|
398
|
+
for (const linked of byVertex.values()) {
|
|
399
|
+
if (!(linked instanceof Error)) {
|
|
400
|
+
gl.deleteVertexArray(linked.vao);
|
|
401
|
+
gl.deleteProgram(linked.program);
|
|
430
402
|
}
|
|
431
403
|
}
|
|
432
404
|
}
|
|
405
|
+
for (const texture of this.passTextures.values()) {
|
|
406
|
+
gl.deleteTexture(texture);
|
|
407
|
+
}
|
|
433
408
|
gl.deleteBuffer(this.ubo);
|
|
434
409
|
}
|
|
435
410
|
this.passes.clear();
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
// let the browser reclaim the context when the canvas is GC'd. If we
|
|
441
|
-
// need explicit release again, gate it on navigator.userAgent.
|
|
411
|
+
this.programs.clear();
|
|
412
|
+
this.passTextures.clear();
|
|
413
|
+
// No WEBGL_lose_context.loseContext(): Firefox treats it as a driver-wide
|
|
414
|
+
// reset that blanks sibling live contexts, so the canvas's GC reclaims it.
|
|
442
415
|
}
|
|
443
416
|
applyBlendState(desc) {
|
|
444
417
|
const gl = this.gl;
|
|
@@ -449,30 +422,28 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
449
422
|
gl.enable(gl.BLEND);
|
|
450
423
|
const bs = desc.blendState;
|
|
451
424
|
if (bs?.op === 'max') {
|
|
452
|
-
// MIN/MAX ignore blend factors: the framebuffer keeps the per-channel
|
|
453
|
-
// max(src, dst). Used by same-color AA lines so overlapping segments union
|
|
454
|
-
// instead of darkening. Reset explicitly below for every other pass.
|
|
455
425
|
gl.blendEquation(gl.MAX);
|
|
456
426
|
}
|
|
427
|
+
else if (bs?.op === 'behind') {
|
|
428
|
+
gl.blendEquation(gl.FUNC_ADD);
|
|
429
|
+
gl.blendFuncSeparate(gl.ONE_MINUS_DST_ALPHA, gl.ONE, gl.ONE_MINUS_DST_ALPHA, gl.ONE);
|
|
430
|
+
}
|
|
457
431
|
else {
|
|
458
432
|
gl.blendEquation(gl.FUNC_ADD);
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
// Alpha: out = src_alpha * 1 + dst_alpha * (1 - src_alpha)
|
|
462
|
-
// The alpha channel uses ONE/ONE_MINUS_SRC_ALPHA regardless of the custom blend state;
|
|
463
|
-
// using the RGB srcFactor for alpha too would give out_alpha = src_alpha² + ..., which is wrong.
|
|
433
|
+
// Alpha keeps ONE / ONE_MINUS_SRC_ALPHA whatever the RGB factors: the
|
|
434
|
+
// RGB srcFactor on alpha would square it.
|
|
464
435
|
const src = bs ? glBlendFactor(gl, bs.srcFactor) : gl.SRC_ALPHA;
|
|
465
436
|
const dst = bs ? glBlendFactor(gl, bs.dstFactor) : gl.ONE_MINUS_SRC_ALPHA;
|
|
466
437
|
gl.blendFuncSeparate(src, dst, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
467
438
|
}
|
|
468
439
|
}
|
|
469
|
-
|
|
440
|
+
bindTexture(passId, desc) {
|
|
470
441
|
const gl = this.gl;
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
gl.
|
|
475
|
-
|
|
442
|
+
const tb = desc.textures?.[0];
|
|
443
|
+
const texture = tb && this.passTextures.get(passId);
|
|
444
|
+
if (tb && texture) {
|
|
445
|
+
gl.activeTexture(gl.TEXTURE0 + tb.glTextureUnit);
|
|
446
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
476
447
|
}
|
|
477
448
|
}
|
|
478
449
|
bindAttributes(pass, vbo) {
|
|
@@ -480,7 +451,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
480
451
|
const desc = pass.descriptor;
|
|
481
452
|
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
482
453
|
for (let i = 0; i < desc.vertexAttributes.length; i++) {
|
|
483
|
-
const loc = pass.attrLocs[i];
|
|
454
|
+
const loc = pass.linked.attrLocs[i];
|
|
484
455
|
if (loc < 0) {
|
|
485
456
|
continue;
|
|
486
457
|
}
|
package/esm/hal/webgpuHal.d.ts
CHANGED
|
@@ -11,13 +11,11 @@ export declare class WebGPUHal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
11
11
|
private pipelines;
|
|
12
12
|
private passTextures;
|
|
13
13
|
private passBindGroups;
|
|
14
|
-
private layouts;
|
|
15
14
|
private alignedUniformSize;
|
|
16
15
|
private uniformRingBuffer;
|
|
17
16
|
private uniformStaging;
|
|
18
17
|
private uniformSlot;
|
|
19
18
|
private warnedUniformSlots;
|
|
20
|
-
private uniformOnlyBindGroup;
|
|
21
19
|
private sampleCount;
|
|
22
20
|
private msaaTexture;
|
|
23
21
|
private msaaView;
|
|
@@ -36,7 +34,7 @@ export declare class WebGPUHal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
36
34
|
* selection spanning two sections re-uploads `OVERLAY_REGION` once per
|
|
37
35
|
* section, inside the block loop.
|
|
38
36
|
*
|
|
39
|
-
* Deferring
|
|
37
|
+
* Deferring the destroy makes mid-frame replacement legal instead of merely
|
|
40
38
|
* warned about; synteny's `ensureUploaded` has always deleted a pass mid-frame
|
|
41
39
|
* that the open pass never referenced, and that case was never the bug.
|
|
42
40
|
*/
|
|
@@ -49,6 +47,9 @@ export declare class WebGPUHal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
49
47
|
private swapChainUnrecoverable;
|
|
50
48
|
private reportedSurfaceFailure;
|
|
51
49
|
private constructor();
|
|
50
|
+
private bindGroupLayoutOf;
|
|
51
|
+
private createBindGroup;
|
|
52
|
+
private bindingResource;
|
|
52
53
|
protected limits(): {
|
|
53
54
|
maxBufferBytes: number;
|
|
54
55
|
maxTextureDimensionPx: number;
|
|
@@ -81,13 +82,12 @@ export declare class WebGPUHal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
81
82
|
* (and a dropped draw) the moment one side samples a texture.
|
|
82
83
|
*
|
|
83
84
|
* Nothing in a bind group varies per region either: it references the
|
|
84
|
-
* HAL-wide uniform ring buffer plus the pass's own texture/sampler.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* Returns undefined when a pass needs a texture that hasn't arrived yet;
|
|
88
|
-
* drawPass skips those.
|
|
85
|
+
* HAL-wide uniform ring buffer plus the pass's own texture/sampler. Returns
|
|
86
|
+
* undefined when a pass needs a texture that hasn't arrived yet; drawPass
|
|
87
|
+
* skips those.
|
|
89
88
|
*/
|
|
90
89
|
private getBindGroup;
|
|
90
|
+
private bindTexture;
|
|
91
91
|
/**
|
|
92
92
|
* The one place a GPU resource this HAL owns is released — see
|
|
93
93
|
* {@link pendingDestroy} for why the frame decides when.
|