@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
@@ -1,120 +1,108 @@
1
1
  /// <reference types="@webgpu/types" />
2
+ import { STANDARD_BLEND_STATE, stageVisibility, toGpuVertexFormat, } from '../webgpuUtils.js';
3
+ // A device's layouts and pipelines, shared by every WebGPUHal on it. Held per
4
+ // module copy rather than on the `globalThis` cell: a second bundled copy
5
+ // builds a duplicate set, never a wrong one, since WebGPU compares layouts
6
+ // structurally.
7
+ const perDevice = new WeakMap();
2
8
  /**
3
- * The GPU objects that belong to the **device** rather than to one `WebGPUHal`,
4
- * memoized so the second display of a track type builds none of them.
5
- *
6
- * WebGL2 has no equivalent and wants none: a program is owned by the context
7
- * that linked it, and each display owns a context, so nothing there is
8
- * shareable. WebGPU inverts that — `gpuDevice.ts` hands every display one
9
- * device, and a `GPURenderPipeline` is a device object — and until this module
10
- * the HAL kept building per-display copies anyway. Ten alignments tracks
11
- * compiled 230 pipelines for 23 distinct programs.
12
- *
13
- * **The key is the descriptor object, and that is what makes it correct.** A
14
- * plugin's pass list is a module-level const (`ALIGNMENTS_PASSES`) and
15
- * `slangPass` reads `wgslSource` off a generated module const, so every display
16
- * of a type hands the HAL the *same* `PipelineDescriptor` objects by reference.
17
- * Identity therefore means "same shader, same layout, same blend, same
18
- * topology" without comparing any of them, and two passes that merely share a
19
- * `.slang` shape module (MAF and multi-row both draw `rowRect`) get separate
20
- * entries because `slangPass` built them separate objects. The **sample count**
21
- * is the one pipeline input the descriptor cannot express, so it is the inner
22
- * key; see the `pipelines` field.
23
- *
24
- * The descriptor's `uniformByteSize` never reaches the pipeline, so it cannot
25
- * make two HALs disagree here:
26
- * `createUniformOnlyBindGroupLayout` declares no `minBindingSize`, so the
27
- * layout — and thus the pipeline built against it — is the same whatever a
28
- * display's `uniformByteSize` is. The size reaches the GPU through the bind
29
- * group's `size` and the dynamic offset, both of which stay per-HAL.
30
- *
31
- * Per copy of this module rather than on the `globalThis` cell, for the reason
32
- * `createHal.ts` gives for `warnedSoftwareRasterizer`: a second bundled copy
33
- * builds a second set, which costs a duplicate and never a wrong answer.
34
- * Layouts compare structurally in WebGPU, so a bind group from one copy is
35
- * valid against a pipeline from the other.
9
+ * A render pass's bind-group layout, read off its shader's reflected table:
10
+ * each binding visible to exactly the stages that read it, so the layout
11
+ * cannot disagree with the shader about which stage reads what. The uniform
12
+ * block takes a dynamic offset into the HAL's ring.
36
13
  */
37
- const perDevice = new WeakMap();
38
- function createLayouts(device) {
39
- const uniformEntry = {
40
- binding: 1,
41
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
42
- buffer: { type: 'uniform', hasDynamicOffset: true },
43
- };
44
- const uniformOnlyBindGroupLayout = device.createBindGroupLayout({
45
- entries: [uniformEntry],
46
- });
47
- const texturedBindGroupLayout = device.createBindGroupLayout({
48
- entries: [
49
- uniformEntry,
50
- {
51
- binding: 2,
52
- visibility: GPUShaderStage.FRAGMENT,
53
- texture: { sampleType: 'float' },
54
- },
55
- {
56
- binding: 3,
57
- visibility: GPUShaderStage.FRAGMENT,
58
- sampler: { type: 'filtering' },
59
- },
60
- ],
14
+ export function bindGroupLayoutEntries(bindings) {
15
+ return bindings.map((b) => {
16
+ const at = { binding: b.index, visibility: stageVisibility(b.stages) };
17
+ switch (b.kind) {
18
+ case 'uniform':
19
+ return { ...at, buffer: { type: 'uniform', hasDynamicOffset: true } };
20
+ case 'texture':
21
+ return { ...at, texture: { sampleType: 'float' } };
22
+ case 'sampler':
23
+ return { ...at, sampler: { type: 'filtering' } };
24
+ default:
25
+ throw new Error(`a render pass binds no ${b.kind}, and '${b.name}' is one at ` +
26
+ `binding ${b.index}`);
27
+ }
61
28
  });
29
+ }
30
+ function gpuBlendState(bs) {
31
+ // WebGPU rejects any factor but 'one' under max, which ignores them. Alpha
32
+ // accumulates as webgl2Hal's applyBlendState does.
33
+ const max = { srcFactor: 'one', dstFactor: 'one', operation: 'max' };
34
+ const behind = { srcFactor: 'one-minus-dst-alpha', dstFactor: 'one' };
35
+ return bs.op === 'max'
36
+ ? { color: max, alpha: max }
37
+ : bs.op === 'behind'
38
+ ? { color: behind, alpha: behind }
39
+ : {
40
+ color: { srcFactor: bs.srcFactor, dstFactor: bs.dstFactor },
41
+ alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },
42
+ };
43
+ }
44
+ export function pipelineRecipe(desc, wgslSource, sampleCount) {
62
45
  return {
63
- uniformOnlyBindGroupLayout,
64
- uniformOnlyPipelineLayout: device.createPipelineLayout({
65
- bindGroupLayouts: [uniformOnlyBindGroupLayout],
66
- }),
67
- texturedBindGroupLayout,
68
- texturedPipelineLayout: device.createPipelineLayout({
69
- bindGroupLayouts: [texturedBindGroupLayout],
70
- }),
46
+ wgslSource,
47
+ bindGroupLayout: bindGroupLayoutEntries(desc.bindings),
48
+ vertexBuffer: {
49
+ arrayStride: desc.instanceStride,
50
+ stepMode: 'instance',
51
+ attributes: desc.vertexAttributes.map((attr, i) => ({
52
+ shaderLocation: i,
53
+ offset: attr.offsetBytes,
54
+ format: toGpuVertexFormat(attr),
55
+ })),
56
+ },
57
+ blend: desc.blend
58
+ ? desc.blendState
59
+ ? gpuBlendState(desc.blendState)
60
+ : STANDARD_BLEND_STATE
61
+ : undefined,
62
+ topology: desc.topology ?? 'triangle-list',
63
+ sampleCount,
71
64
  };
72
65
  }
73
66
  function cacheFor(device) {
74
67
  let entry = perDevice.get(device);
75
68
  if (!entry) {
76
- entry = { layouts: createLayouts(device), pipelines: new WeakMap() };
69
+ entry = { layouts: new Map(), pipelines: new Map() };
77
70
  perDevice.set(device, entry);
78
71
  }
79
72
  return entry;
80
73
  }
81
- /**
82
- * The bind group and pipeline layouts for `device`, built once per device.
83
- *
84
- * A device that is lost drops out of the map with itself — `gpuDevice.ts`
85
- * releases its reference in the `.lost` handler and the next acquisition is a
86
- * new object, so the layouts and pipelines built against the dead one are
87
- * unreachable and there is no cache to invalidate by hand.
88
- */
89
- export function getDeviceLayouts(device) {
90
- return cacheFor(device).layouts;
74
+ export function getPassLayout(device, entries) {
75
+ const { layouts } = cacheFor(device);
76
+ const key = JSON.stringify(entries);
77
+ let layout = layouts.get(key);
78
+ if (!layout) {
79
+ const bindGroupLayout = device.createBindGroupLayout({ entries });
80
+ layout = {
81
+ bindGroupLayout,
82
+ pipelineLayout: device.createPipelineLayout({
83
+ bindGroupLayouts: [bindGroupLayout],
84
+ }),
85
+ };
86
+ layouts.set(key, layout);
87
+ }
88
+ return layout;
91
89
  }
92
- /**
93
- * The pipeline for `desc` at `sampleCount` on `device`, building it through
94
- * `build` on the first ask and handing every later one the same promise.
95
- *
96
- * `build` receives the sample count rather than closing over one, so the key
97
- * and the pipeline stored under it cannot disagree.
98
- */
99
- export function getOrBuildPipeline(device, desc, sampleCount, build) {
100
- const { layouts, pipelines } = cacheFor(device);
101
- let bySampleCount = pipelines.get(desc);
102
- if (!bySampleCount) {
103
- bySampleCount = new Map();
104
- pipelines.set(desc, bySampleCount);
90
+ export function getOrBuildPipeline(device, recipe, build) {
91
+ const { pipelines } = cacheFor(device);
92
+ const { wgslSource, ...rest } = recipe;
93
+ let bySource = pipelines.get(wgslSource);
94
+ if (!bySource) {
95
+ bySource = new Map();
96
+ pipelines.set(wgslSource, bySource);
105
97
  }
106
- let pipeline = bySampleCount.get(sampleCount);
98
+ const key = JSON.stringify(rest);
99
+ let pipeline = bySource.get(key);
107
100
  if (!pipeline) {
108
- pipeline = build(layouts, sampleCount);
109
- bySampleCount.set(sampleCount, pipeline);
101
+ pipeline = build(recipe);
102
+ bySource.set(key, pipeline);
110
103
  }
111
104
  return pipeline;
112
105
  }
113
- /**
114
- * Drop every device's entry. Tests only — nothing in the app invalidates this
115
- * cache, because losing the device is what invalidates it (see
116
- * {@link getDeviceLayouts}).
117
- */
118
106
  export function resetDeviceGpuCacheForTests(device) {
119
107
  perDevice.delete(device);
120
108
  }
@@ -1,7 +1,7 @@
1
1
  export { createGpuHal } from './createHal.ts';
2
- export { MockHal } from './mockHal.ts';
2
+ export { MOCK_SHADER_SOURCE, MockHal } from './mockHal.ts';
3
3
  export { assertUniquePassIds } from './passIds.ts';
4
4
  export { WebGL2Hal } from './webgl2Hal.ts';
5
5
  export { WebGPUHal } from './webgpuHal.ts';
6
6
  export type { GpuHalOptions } from './createHal.ts';
7
- export type { BlendState, VertexAttributeLayout, GpuHal, PipelineDescriptor, SampleCount, ShaderBinding, TextureBinding, TextureSource, } from './types.ts';
7
+ export type { BlendState, VertexAttributeLayout, GpuHal, PipelineDescriptor, SampleCount, ShaderBinding, ShaderSource, ShaderStage, TextureBinding, TextureSource, } from './types.ts';
package/esm/hal/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createGpuHal } from './createHal.js';
2
- export { MockHal } from './mockHal.js';
2
+ export { MOCK_SHADER_SOURCE, MockHal } from './mockHal.js';
3
3
  export { assertUniquePassIds } from './passIds.js';
4
4
  export { WebGL2Hal } from './webgl2Hal.js';
5
5
  export { WebGPUHal } from './webgpuHal.js';
@@ -1,5 +1,7 @@
1
1
  import { GpuHalBase } from './gpuHalBase.ts';
2
- import type { GpuHal, PipelineDescriptor, TextureBinding, TextureSource } from './types.ts';
2
+ import type { GpuHal, PipelineDescriptor, ShaderSource, TextureBinding, TextureSource } from './types.ts';
3
+ /** Text for a pass built only for a `MockHal`, which never loads it. */
4
+ export declare const MOCK_SHADER_SOURCE: ShaderSource;
3
5
  export interface MockCall {
4
6
  method: string;
5
7
  args: unknown[];
@@ -17,7 +19,7 @@ export interface MockRect {
17
19
  /**
18
20
  * One draw, with the state that was in force when it was issued — the clip, and
19
21
  * which `writeUniforms` it reads. `null` scissor/viewport means unclipped, the
20
- * full canvas, which is what both HALs owe after a `clearScissor` /
22
+ * full canvas, the state both HALs must restore after a `clearScissor` /
21
23
  * `clearViewport`.
22
24
  *
23
25
  * `uniformWrite` indexes {@link MockHal.getUniformWritesF32}, or is `-1` when
@@ -32,8 +34,8 @@ export interface MockRect {
32
34
  * does an immediate `bufferSubData` into one UBO. They agree only while a
33
35
  * renderer writes then draws, which is the convention every renderer in tree
34
36
  * follows and nothing enforces: batching the writes and then issuing the draws
35
- * is right on WebGL2 and Canvas2D and silently wrong on WebGPU. This field is
36
- * what lets a backend test pin its renderer's pairing.
37
+ * is right on WebGL2 and Canvas2D and wrong on WebGPU, where the draws read the
38
+ * last write. `uniformWrite` lets a backend test pin its renderer's pairing.
37
39
  */
38
40
  export interface MockDraw {
39
41
  passId: string;
@@ -59,8 +61,8 @@ export declare class MockHal extends GpuHalBase<MockBuffer> implements GpuHal {
59
61
  * rect without re-issuing a full-canvas one, so every draw after it went on
60
62
  * being clipped to the previous block while WebGL2 drew them unclipped — right
61
63
  * on Canvas2D, right on WebGL2, wrong on WebGPU alone. Nothing in tree clears
62
- * mid-frame today, which is exactly why nothing caught it. Recording the
63
- * effective clip per draw is what makes that assertable at all.
64
+ * mid-frame today, so no test caught it. Recording the effective clip per draw
65
+ * makes that assertable.
64
66
  */
65
67
  private scissor;
66
68
  private viewport;
@@ -1,6 +1,11 @@
1
1
  import { getDpr } from '../canvas2dUtils.js';
2
2
  import { GpuHalBase } from './gpuHalBase.js';
3
3
  import { assertUniquePassIds } from './passIds.js';
4
+ /** Text for a pass built only for a `MockHal`, which never loads it. */
5
+ export const MOCK_SHADER_SOURCE = {
6
+ wgsl: () => Promise.resolve({ WGSL_SOURCE: '' }),
7
+ glsl: () => Promise.resolve({ GLSL_VERTEX: '', GLSL_FRAGMENT: '' }),
8
+ };
4
9
  export class MockHal extends GpuHalBase {
5
10
  calls = [];
6
11
  // Every write of the frame, in order, not just the last. A renderer that
@@ -23,8 +28,8 @@ export class MockHal extends GpuHalBase {
23
28
  * rect without re-issuing a full-canvas one, so every draw after it went on
24
29
  * being clipped to the previous block while WebGL2 drew them unclipped — right
25
30
  * on Canvas2D, right on WebGL2, wrong on WebGPU alone. Nothing in tree clears
26
- * mid-frame today, which is exactly why nothing caught it. Recording the
27
- * effective clip per draw is what makes that assertable at all.
31
+ * mid-frame today, so no test caught it. Recording the effective clip per draw
32
+ * makes that assertable.
28
33
  */
29
34
  scissor = null;
30
35
  viewport = null;
@@ -2,13 +2,14 @@ import type { PipelineDescriptor } from './types.ts';
2
2
  /**
3
3
  * Throw if two passes in one display's registry share an `id`.
4
4
  *
5
- * A pass id is two keys at once — the pipeline (`pipelines.set(desc.id, …)` in
6
- * both HALs) and the instance buffer (`uploadBuffer(regionKey, passId, …)`).
7
- * A duplicate therefore collides both: the second registration replaces the
8
- * first's pipeline, both passes upload to one buffer so the last write wins,
9
- * and whichever shader has the larger stride reads off the end of it. No
10
- * validation error, no throw, no failing test — just wrong pixels on the GPU
11
- * backends while Canvas2D keeps drawing correctly.
5
+ * A pass id names a slot: the descriptor a draw of that id uses, its instance
6
+ * buffer per region, and its texture. Compiled pipelines and programs are keyed
7
+ * by content instead, so a second id over one shader costs no compile. A
8
+ * duplicate id collides the slot: the later descriptor answers every draw of
9
+ * it, both passes upload to one buffer so the last write wins, and whichever
10
+ * shader has the larger stride reads off the end of it. No validation error, no
11
+ * throw, no failing test — just wrong pixels on the GPU backends while Canvas2D
12
+ * keeps drawing correctly.
12
13
  *
13
14
  * Nothing structural stops it. A display's pass list is concatenated from
14
15
  * several registries (`ALIGNMENTS_PASSES` merges three plus a standalone
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Throw if two passes in one display's registry share an `id`.
3
3
  *
4
- * A pass id is two keys at once — the pipeline (`pipelines.set(desc.id, …)` in
5
- * both HALs) and the instance buffer (`uploadBuffer(regionKey, passId, …)`).
6
- * A duplicate therefore collides both: the second registration replaces the
7
- * first's pipeline, both passes upload to one buffer so the last write wins,
8
- * and whichever shader has the larger stride reads off the end of it. No
9
- * validation error, no throw, no failing test — just wrong pixels on the GPU
10
- * backends while Canvas2D keeps drawing correctly.
4
+ * A pass id names a slot: the descriptor a draw of that id uses, its instance
5
+ * buffer per region, and its texture. Compiled pipelines and programs are keyed
6
+ * by content instead, so a second id over one shader costs no compile. A
7
+ * duplicate id collides the slot: the later descriptor answers every draw of
8
+ * it, both passes upload to one buffer so the last write wins, and whichever
9
+ * shader has the larger stride reads off the end of it. No validation error, no
10
+ * throw, no failing test — just wrong pixels on the GPU backends while Canvas2D
11
+ * keeps drawing correctly.
11
12
  *
12
13
  * Nothing structural stops it. A display's pass list is concatenated from
13
14
  * several registries (`ALIGNMENTS_PASSES` merges three plus a standalone
@@ -29,8 +30,8 @@ export function assertUniquePassIds(passes) {
29
30
  }
30
31
  if (duplicates.size > 0) {
31
32
  throw new Error(`duplicate pass id(s) ${[...duplicates].map(id => `'${id}'`).join(', ')}` +
32
- ` — a pass id keys both the pipeline and the instance buffer, so two` +
33
- ` passes sharing one draw each other's instances through their own` +
34
- ` shader. Rename one.`);
33
+ ` — a pass id keys the descriptor, the instance buffer and the` +
34
+ ` texture, so two passes sharing one draw each other's instances` +
35
+ ` through one shader. Rename one.`);
35
36
  }
36
37
  }
@@ -39,21 +39,25 @@ export type BlendState = {
39
39
  dstFactor: BlendFactor;
40
40
  } | {
41
41
  op: 'max';
42
+ } | {
43
+ op: 'behind';
42
44
  };
45
+ export type ShaderStage = 'vertex' | 'fragment' | 'compute';
43
46
  /**
44
47
  * One entry of a shader's GPU binding table, as the Slang codegen reflects it.
45
48
  *
46
- * `kind` is spelled the way WebGPU spells it so a consumer can build a
47
- * `createBindGroupLayout` entry from it directly. That is the point: the binding
48
- * indices used to be asserted by hand in three places that never consulted the
49
- * shader — this HAL's two hardcoded render layouts, and the LD compute driver's
50
- * own. A shader's `BINDINGS` export is the reflected truth, and `pnpm
51
- * gen:shaders` now refuses a render shader whose table is not one the HALs bind.
49
+ * `kind` is spelled the way WebGPU spells it and `stages` names the entry
50
+ * points that read the binding, so a consumer builds a `createBindGroupLayout`
51
+ * entry from it directly, `visibility` included. The WebGPU HAL and the compute
52
+ * driver both do. `pnpm gen:shaders` refuses a render shader whose table is not
53
+ * one the HALs bind, and a `stages` that leaves out a stage the emitted WGSL
54
+ * reads the binding in.
52
55
  */
53
56
  export interface ShaderBinding {
54
57
  index: number;
55
58
  kind: 'uniform' | 'texture' | 'sampler' | 'storage' | 'read-only-storage';
56
59
  name: string;
60
+ stages: readonly ShaderStage[];
57
61
  }
58
62
  /**
59
63
  * What a pass's texture is filled from: RGBA bytes, or a canvas whose pixels
@@ -63,12 +67,26 @@ export interface ShaderBinding {
63
67
  */
64
68
  export type TextureSource = Uint8Array | HTMLCanvasElement | OffscreenCanvas;
65
69
  export interface TextureBinding {
66
- textureBinding: number;
67
- samplerBinding: number;
68
70
  glTextureUnit: number;
69
71
  glUniformName: string;
70
72
  filter: 'linear' | 'nearest';
71
73
  }
74
+ /**
75
+ * A shader's compiled text, one loader per target. `pnpm gen:shaders` emits it
76
+ * as a shader module's `SOURCE`, each loader an `import()` of a module holding
77
+ * nothing but that target's strings, so no realm evaluates shader text until a
78
+ * HAL is built with the pass: WebGPU loads the WGSL, WebGL2 the GLSL, Canvas2D
79
+ * neither, and the RPC worker never builds one.
80
+ */
81
+ export interface ShaderSource {
82
+ wgsl: () => Promise<{
83
+ WGSL_SOURCE: string;
84
+ }>;
85
+ glsl: () => Promise<{
86
+ GLSL_VERTEX: string;
87
+ GLSL_FRAGMENT: string;
88
+ }>;
89
+ }
72
90
  /**
73
91
  * A **pipeline state object (PSO)**: shaders, vertex input layout, blend state,
74
92
  * primitive topology and texture bindings, baked into one immutable
@@ -83,9 +101,7 @@ export interface TextureBinding {
83
101
  */
84
102
  export interface PipelineDescriptor {
85
103
  id: string;
86
- wgslSource: string;
87
- glslVertex: string;
88
- glslFragment: string;
104
+ source: ShaderSource;
89
105
  instanceStride: number;
90
106
  /** Bytes of the uniform block the shader binds; the HAL's slot is the largest over its passes. */
91
107
  uniformByteSize: number;
@@ -96,7 +112,7 @@ export interface PipelineDescriptor {
96
112
  topology?: 'triangle-list' | 'triangle-strip' | 'line-list';
97
113
  coverage?: 'analytic';
98
114
  textures?: readonly [TextureBinding, ...TextureBinding[]];
99
- bindings?: readonly ShaderBinding[];
115
+ bindings: readonly ShaderBinding[];
100
116
  }
101
117
  export interface GpuHal {
102
118
  /** The uniform slot size, the largest block any registered pass binds. */
@@ -8,6 +8,9 @@ export declare class WebGL2Hal extends GpuHalBase<RegionPassBuffer> implements G
8
8
  private gl;
9
9
  private canvas;
10
10
  private passes;
11
+ private glsl;
12
+ private programs;
13
+ private passTextures;
11
14
  private ubo;
12
15
  private maxTextureDim;
13
16
  private debug;
@@ -17,7 +20,12 @@ export declare class WebGL2Hal extends GpuHalBase<RegionPassBuffer> implements G
17
20
  private contextLostListener;
18
21
  private contextRestoredListener;
19
22
  private checkGlError;
20
- constructor(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[]);
23
+ /**
24
+ * Loads every pass's GLSL before the context is acquired, so a load that
25
+ * fails leaves the canvas free for Canvas2D.
26
+ */
27
+ static create(canvas: HTMLCanvasElement, descriptors: PipelineDescriptor[]): Promise<WebGL2Hal>;
28
+ private constructor();
21
29
  protected limits(): {
22
30
  maxBufferBytes: number;
23
31
  maxTextureDimensionPx: number;
@@ -27,7 +35,7 @@ export declare class WebGL2Hal extends GpuHalBase<RegionPassBuffer> implements G
27
35
  count: number;
28
36
  };
29
37
  protected destroyBuffer(buf: RegionPassBuffer): void;
30
- private compilePass;
38
+ private link;
31
39
  private getPass;
32
40
  resize(width: number, height: number): import("./types.ts").CanvasScale;
33
41
  protected createTexture(passId: string, binding: TextureBinding, data: TextureSource, width: number, height: number): void;
@@ -41,7 +49,7 @@ export declare class WebGL2Hal extends GpuHalBase<RegionPassBuffer> implements G
41
49
  endFrame(): void;
42
50
  protected releaseResources(): void;
43
51
  private applyBlendState;
44
- private bindTextures;
52
+ private bindTexture;
45
53
  private bindAttributes;
46
54
  }
47
55
  export {};