@graphty/webgpu-graph-algorithms 0.5.1 → 0.6.1

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 (243) hide show
  1. package/README.md +459 -58
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{context-BR7fx3vR.js → context-BXqgCifx.js} +190 -40
  4. package/dist/chunks/context-BXqgCifx.js.map +1 -0
  5. package/dist/node.js +1 -1
  6. package/dist/src/algorithms/components.d.ts.map +1 -1
  7. package/dist/src/algorithms/components.js +12 -13
  8. package/dist/src/algorithms/components.js.map +1 -1
  9. package/dist/src/algorithms/degree.d.ts +6 -8
  10. package/dist/src/algorithms/degree.d.ts.map +1 -1
  11. package/dist/src/algorithms/degree.js +58 -35
  12. package/dist/src/algorithms/degree.js.map +1 -1
  13. package/dist/src/algorithms/pagerank.d.ts.map +1 -1
  14. package/dist/src/algorithms/pagerank.js +16 -14
  15. package/dist/src/algorithms/pagerank.js.map +1 -1
  16. package/dist/src/algorithms/power-iteration.d.ts +2 -2
  17. package/dist/src/algorithms/power-iteration.d.ts.map +1 -1
  18. package/dist/src/algorithms/power-iteration.js +17 -14
  19. package/dist/src/algorithms/power-iteration.js.map +1 -1
  20. package/dist/src/constants.d.ts +38 -8
  21. package/dist/src/constants.d.ts.map +1 -1
  22. package/dist/src/constants.js +38 -8
  23. package/dist/src/constants.js.map +1 -1
  24. package/dist/src/errors.d.ts +3 -2
  25. package/dist/src/errors.d.ts.map +1 -1
  26. package/dist/src/errors.js +2 -1
  27. package/dist/src/errors.js.map +1 -1
  28. package/dist/src/index.d.ts +6 -4
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +8 -3
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/kernel/dispatch.d.ts +8 -3
  33. package/dist/src/kernel/dispatch.d.ts.map +1 -1
  34. package/dist/src/kernel/dispatch.js +18 -7
  35. package/dist/src/kernel/dispatch.js.map +1 -1
  36. package/dist/src/kernel/kernel.d.ts +30 -1
  37. package/dist/src/kernel/kernel.d.ts.map +1 -1
  38. package/dist/src/kernel/kernel.js +49 -5
  39. package/dist/src/kernel/kernel.js.map +1 -1
  40. package/dist/src/kernel/prelude.d.ts.map +1 -1
  41. package/dist/src/kernel/prelude.js +6 -1
  42. package/dist/src/kernel/prelude.js.map +1 -1
  43. package/dist/src/kernel/profiler.d.ts +15 -3
  44. package/dist/src/kernel/profiler.d.ts.map +1 -1
  45. package/dist/src/kernel/profiler.js +27 -4
  46. package/dist/src/kernel/profiler.js.map +1 -1
  47. package/dist/src/kernels.d.ts +17 -7
  48. package/dist/src/kernels.d.ts.map +1 -1
  49. package/dist/src/kernels.js +323 -16
  50. package/dist/src/kernels.js.map +1 -1
  51. package/dist/src/layouts/calibrate.d.ts +51 -0
  52. package/dist/src/layouts/calibrate.d.ts.map +1 -0
  53. package/dist/src/layouts/calibrate.js +172 -0
  54. package/dist/src/layouts/calibrate.js.map +1 -0
  55. package/dist/src/layouts/force-simulation.d.ts +39 -4
  56. package/dist/src/layouts/force-simulation.d.ts.map +1 -1
  57. package/dist/src/layouts/force-simulation.js +71 -19
  58. package/dist/src/layouts/force-simulation.js.map +1 -1
  59. package/dist/src/layouts/forceatlas2.d.ts +107 -36
  60. package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
  61. package/dist/src/layouts/forceatlas2.js +296 -100
  62. package/dist/src/layouts/forceatlas2.js.map +1 -1
  63. package/dist/src/layouts/fruchterman-reingold.d.ts +73 -27
  64. package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -1
  65. package/dist/src/layouts/fruchterman-reingold.js +230 -70
  66. package/dist/src/layouts/fruchterman-reingold.js.map +1 -1
  67. package/dist/src/layouts/model-common.d.ts +41 -3
  68. package/dist/src/layouts/model-common.d.ts.map +1 -1
  69. package/dist/src/layouts/model-common.js +74 -3
  70. package/dist/src/layouts/model-common.js.map +1 -1
  71. package/dist/src/layouts/repulsion-grid.d.ts +152 -0
  72. package/dist/src/layouts/repulsion-grid.d.ts.map +1 -0
  73. package/dist/src/layouts/repulsion-grid.js +318 -0
  74. package/dist/src/layouts/repulsion-grid.js.map +1 -0
  75. package/dist/src/layouts/spring-electrical.d.ts +75 -30
  76. package/dist/src/layouts/spring-electrical.d.ts.map +1 -1
  77. package/dist/src/layouts/spring-electrical.js +231 -74
  78. package/dist/src/layouts/spring-electrical.js.map +1 -1
  79. package/dist/src/memory/residency.d.ts +6 -2
  80. package/dist/src/memory/residency.d.ts.map +1 -1
  81. package/dist/src/memory/residency.js +84 -14
  82. package/dist/src/memory/residency.js.map +1 -1
  83. package/dist/src/primitives/core-shape.d.ts +38 -2
  84. package/dist/src/primitives/core-shape.d.ts.map +1 -1
  85. package/dist/src/primitives/core-shape.js +71 -3
  86. package/dist/src/primitives/core-shape.js.map +1 -1
  87. package/dist/src/primitives/grid-pyramid.d.ts +71 -0
  88. package/dist/src/primitives/grid-pyramid.d.ts.map +1 -0
  89. package/dist/src/primitives/grid-pyramid.js +143 -0
  90. package/dist/src/primitives/grid-pyramid.js.map +1 -0
  91. package/dist/src/primitives/grid.d.ts +118 -0
  92. package/dist/src/primitives/grid.d.ts.map +1 -0
  93. package/dist/src/primitives/grid.js +225 -0
  94. package/dist/src/primitives/grid.js.map +1 -0
  95. package/dist/src/primitives/histogram.d.ts +67 -0
  96. package/dist/src/primitives/histogram.d.ts.map +1 -0
  97. package/dist/src/primitives/histogram.js +190 -0
  98. package/dist/src/primitives/histogram.js.map +1 -0
  99. package/dist/src/primitives/radix-sort.d.ts +75 -0
  100. package/dist/src/primitives/radix-sort.d.ts.map +1 -0
  101. package/dist/src/primitives/radix-sort.js +168 -0
  102. package/dist/src/primitives/radix-sort.js.map +1 -0
  103. package/dist/src/primitives/scan.d.ts +44 -0
  104. package/dist/src/primitives/scan.d.ts.map +1 -0
  105. package/dist/src/primitives/scan.js +151 -0
  106. package/dist/src/primitives/scan.js.map +1 -0
  107. package/dist/src/primitives/segmented-reduce.d.ts +25 -17
  108. package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
  109. package/dist/src/primitives/segmented-reduce.js +166 -47
  110. package/dist/src/primitives/segmented-reduce.js.map +1 -1
  111. package/dist/src/primitives/spmv.d.ts +18 -14
  112. package/dist/src/primitives/spmv.d.ts.map +1 -1
  113. package/dist/src/primitives/spmv.js +94 -58
  114. package/dist/src/primitives/spmv.js.map +1 -1
  115. package/dist/src/primitives/verify.d.ts +49 -0
  116. package/dist/src/primitives/verify.d.ts.map +1 -0
  117. package/dist/src/primitives/verify.js +229 -0
  118. package/dist/src/primitives/verify.js.map +1 -0
  119. package/dist/src/types/context.d.ts +53 -0
  120. package/dist/src/types/context.d.ts.map +1 -1
  121. package/dist/src/types/layout.d.ts +20 -0
  122. package/dist/src/types/layout.d.ts.map +1 -1
  123. package/dist/src/wgsl/counting-scatter.wgsl.d.ts +8 -0
  124. package/dist/src/wgsl/counting-scatter.wgsl.d.ts.map +1 -0
  125. package/dist/src/wgsl/counting-scatter.wgsl.js +17 -0
  126. package/dist/src/wgsl/counting-scatter.wgsl.js.map +1 -0
  127. package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +23 -11
  128. package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -1
  129. package/dist/src/wgsl/fa2-attraction.wgsl.js +98 -20
  130. package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -1
  131. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +6 -2
  132. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
  133. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +22 -1
  134. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -1
  135. package/dist/src/wgsl/grid-cell-key.wgsl.d.ts +8 -0
  136. package/dist/src/wgsl/grid-cell-key.wgsl.d.ts.map +1 -0
  137. package/dist/src/wgsl/grid-cell-key.wgsl.js +30 -0
  138. package/dist/src/wgsl/grid-cell-key.wgsl.js.map +1 -0
  139. package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts +8 -0
  140. package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts.map +1 -0
  141. package/dist/src/wgsl/grid-centroid-hub.wgsl.js +29 -0
  142. package/dist/src/wgsl/grid-centroid-hub.wgsl.js.map +1 -0
  143. package/dist/src/wgsl/grid-centroid.wgsl.d.ts +8 -0
  144. package/dist/src/wgsl/grid-centroid.wgsl.d.ts.map +1 -0
  145. package/dist/src/wgsl/grid-centroid.wgsl.js +29 -0
  146. package/dist/src/wgsl/grid-centroid.wgsl.js.map +1 -0
  147. package/dist/src/wgsl/grid-downsample.wgsl.d.ts +7 -0
  148. package/dist/src/wgsl/grid-downsample.wgsl.d.ts.map +1 -0
  149. package/dist/src/wgsl/grid-downsample.wgsl.js +28 -0
  150. package/dist/src/wgsl/grid-downsample.wgsl.js.map +1 -0
  151. package/dist/src/wgsl/grid-far-field.wgsl.d.ts +13 -0
  152. package/dist/src/wgsl/grid-far-field.wgsl.d.ts.map +1 -0
  153. package/dist/src/wgsl/grid-far-field.wgsl.js +98 -0
  154. package/dist/src/wgsl/grid-far-field.wgsl.js.map +1 -0
  155. package/dist/src/wgsl/grid-near-field.wgsl.d.ts +19 -0
  156. package/dist/src/wgsl/grid-near-field.wgsl.d.ts.map +1 -0
  157. package/dist/src/wgsl/grid-near-field.wgsl.js +129 -0
  158. package/dist/src/wgsl/grid-near-field.wgsl.js.map +1 -0
  159. package/dist/src/wgsl/histogram.wgsl.d.ts +7 -0
  160. package/dist/src/wgsl/histogram.wgsl.d.ts.map +1 -0
  161. package/dist/src/wgsl/histogram.wgsl.js +15 -0
  162. package/dist/src/wgsl/histogram.wgsl.js.map +1 -0
  163. package/dist/src/wgsl/indirect-finalize.wgsl.d.ts +8 -0
  164. package/dist/src/wgsl/indirect-finalize.wgsl.d.ts.map +1 -0
  165. package/dist/src/wgsl/indirect-finalize.wgsl.js +26 -0
  166. package/dist/src/wgsl/indirect-finalize.wgsl.js.map +1 -0
  167. package/dist/src/wgsl/radix-hist.wgsl.d.ts +9 -0
  168. package/dist/src/wgsl/radix-hist.wgsl.d.ts.map +1 -0
  169. package/dist/src/wgsl/radix-hist.wgsl.js +31 -0
  170. package/dist/src/wgsl/radix-hist.wgsl.js.map +1 -0
  171. package/dist/src/wgsl/radix-scatter.wgsl.d.ts +9 -0
  172. package/dist/src/wgsl/radix-scatter.wgsl.d.ts.map +1 -0
  173. package/dist/src/wgsl/radix-scatter.wgsl.js +40 -0
  174. package/dist/src/wgsl/radix-scatter.wgsl.js.map +1 -0
  175. package/dist/src/wgsl/scan-add.wgsl.d.ts +6 -0
  176. package/dist/src/wgsl/scan-add.wgsl.d.ts.map +1 -0
  177. package/dist/src/wgsl/scan-add.wgsl.js +14 -0
  178. package/dist/src/wgsl/scan-add.wgsl.js.map +1 -0
  179. package/dist/src/wgsl/scan-block.wgsl.d.ts +8 -0
  180. package/dist/src/wgsl/scan-block.wgsl.d.ts.map +1 -0
  181. package/dist/src/wgsl/scan-block.wgsl.js +30 -0
  182. package/dist/src/wgsl/scan-block.wgsl.js.map +1 -0
  183. package/dist/src/wgsl/segmented-reduce.wgsl.d.ts +22 -8
  184. package/dist/src/wgsl/segmented-reduce.wgsl.d.ts.map +1 -1
  185. package/dist/src/wgsl/segmented-reduce.wgsl.js +84 -15
  186. package/dist/src/wgsl/segmented-reduce.wgsl.js.map +1 -1
  187. package/dist/src/wgsl/spmv-pull.wgsl.d.ts +22 -11
  188. package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -1
  189. package/dist/src/wgsl/spmv-pull.wgsl.js +110 -36
  190. package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -1
  191. package/dist/tsconfig.build.tsbuildinfo +1 -1
  192. package/dist/webgpu-graph-algorithms.js +3815 -1003
  193. package/dist/webgpu-graph-algorithms.js.map +1 -1
  194. package/package.json +9 -8
  195. package/src/algorithms/components.ts +12 -16
  196. package/src/algorithms/degree.ts +58 -43
  197. package/src/algorithms/pagerank.ts +20 -18
  198. package/src/algorithms/power-iteration.ts +19 -18
  199. package/src/constants.ts +38 -8
  200. package/src/errors.ts +3 -1
  201. package/src/index.ts +14 -4
  202. package/src/kernel/dispatch.ts +18 -7
  203. package/src/kernel/kernel.ts +59 -5
  204. package/src/kernel/prelude.ts +9 -0
  205. package/src/kernel/profiler.ts +28 -4
  206. package/src/kernels.ts +356 -18
  207. package/src/layouts/calibrate.ts +187 -0
  208. package/src/layouts/force-simulation.ts +91 -23
  209. package/src/layouts/forceatlas2.ts +331 -106
  210. package/src/layouts/fruchterman-reingold.ts +255 -74
  211. package/src/layouts/model-common.ts +98 -3
  212. package/src/layouts/repulsion-grid.ts +451 -0
  213. package/src/layouts/spring-electrical.ts +257 -78
  214. package/src/memory/residency.ts +126 -20
  215. package/src/primitives/core-shape.ts +91 -4
  216. package/src/primitives/grid-pyramid.ts +221 -0
  217. package/src/primitives/grid.ts +349 -0
  218. package/src/primitives/histogram.ts +273 -0
  219. package/src/primitives/radix-sort.ts +246 -0
  220. package/src/primitives/scan.ts +197 -0
  221. package/src/primitives/segmented-reduce.ts +214 -56
  222. package/src/primitives/spmv.ts +125 -65
  223. package/src/primitives/verify.ts +249 -0
  224. package/src/types/context.ts +56 -0
  225. package/src/types/layout.ts +22 -0
  226. package/src/wgsl/counting-scatter.wgsl.ts +16 -0
  227. package/src/wgsl/fa2-attraction.wgsl.ts +98 -20
  228. package/src/wgsl/fa2-stats-finalize.wgsl.ts +22 -1
  229. package/src/wgsl/grid-cell-key.wgsl.ts +29 -0
  230. package/src/wgsl/grid-centroid-hub.wgsl.ts +28 -0
  231. package/src/wgsl/grid-centroid.wgsl.ts +28 -0
  232. package/src/wgsl/grid-downsample.wgsl.ts +27 -0
  233. package/src/wgsl/grid-far-field.wgsl.ts +97 -0
  234. package/src/wgsl/grid-near-field.wgsl.ts +128 -0
  235. package/src/wgsl/histogram.wgsl.ts +14 -0
  236. package/src/wgsl/indirect-finalize.wgsl.ts +25 -0
  237. package/src/wgsl/radix-hist.wgsl.ts +30 -0
  238. package/src/wgsl/radix-scatter.wgsl.ts +39 -0
  239. package/src/wgsl/scan-add.wgsl.ts +13 -0
  240. package/src/wgsl/scan-block.wgsl.ts +29 -0
  241. package/src/wgsl/segmented-reduce.wgsl.ts +84 -15
  242. package/src/wgsl/spmv-pull.wgsl.ts +110 -36
  243. package/dist/chunks/context-BR7fx3vR.js.map +0 -1
@@ -2,7 +2,8 @@
2
2
  * Kernel (spec 5.1; contract 3.9): a compiled pipeline bound to the binding list of its spec. `bind()` creates the
3
3
  * bind groups from a `Record<name, Binding>` keyed by the declared names and caches them by the identity of the
4
4
  * buffers, offsets and sizes (a layout's bind groups are created once per load(), not per iteration); `dispatch()`
5
- * records setPipeline / setBindGroup / dispatchWorkgroups and skips an empty plan (spec 5.6).
5
+ * records setPipeline / setBindGroup / dispatchWorkgroups and skips an empty plan (spec 5.6); `dispatchIndirect()`
6
+ * (P4, spec 5.4) records the same prefix and a dispatchWorkgroupsIndirect over one 16-byte slot of an args buffer.
6
7
  */
7
8
 
8
9
  import { STORAGE_ALIGN } from "../constants.js";
@@ -21,6 +22,9 @@ export interface BoundKernel {
21
22
  readonly dynamicGroups: readonly number[];
22
23
  }
23
24
 
25
+ /** Bytes of one indirect args slot: (x, y, 1, count) as four u32 (spec 5.4; P4 PD-2). */
26
+ export const INDIRECT_ARGS_STRIDE = 16;
27
+
24
28
  /** Buffer identities for the bind-group cache key: every GPUBuffer seen by any Kernel gets one number, once. */
25
29
  const bufferIds = new WeakMap<GPUBuffer, number>();
26
30
  let nextBufferId = 1;
@@ -259,6 +263,50 @@ export class Kernel {
259
263
  plan: DispatchPlan,
260
264
  dynamicOffsets?: readonly number[],
261
265
  ): void {
266
+ this.check(bound, dynamicOffsets);
267
+ if (plan.x === 0) {
268
+ return;
269
+ }
270
+ this.setUp(pass, bound, dynamicOffsets);
271
+ pass.dispatchWorkgroups(plan.x, plan.y, 1);
272
+ }
273
+
274
+ /**
275
+ * setPipeline + the bind groups exactly as dispatch(), then dispatchWorkgroupsIndirect(args.buffer, args.offset +
276
+ * INDIRECT_ARGS_STRIDE * slot) (spec 5.4): the (x, y, 1) of the slot were written by the indirect-finalize kernel
277
+ * earlier in the same pass. A slot beyond the binding is E_INVALID_ARGUMENT.
278
+ * @param pass - the open compute pass
279
+ * @param bound - a BoundKernel of THIS kernel
280
+ * @param args - the args buffer range (usage INDIRECT | STORAGE)
281
+ * @param slot - the 16-byte slot index inside the range
282
+ * @param dynamicOffsets - one byte offset per entry of bound.dynamicGroups
283
+ */
284
+ dispatchIndirect(
285
+ pass: GPUComputePassEncoder,
286
+ bound: BoundKernel,
287
+ args: Binding,
288
+ slot: number,
289
+ dynamicOffsets?: readonly number[],
290
+ ): void {
291
+ this.check(bound, dynamicOffsets);
292
+ if (!Number.isInteger(slot) || slot < 0 || (slot + 1) * INDIRECT_ARGS_STRIDE > args.size) {
293
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${this.spec.id}: args slot ${slot} is outside the binding`, {
294
+ argument: "slot",
295
+ value: slot,
296
+ expected: `0 <= slot < ${Math.floor(args.size / INDIRECT_ARGS_STRIDE)}`,
297
+ });
298
+ }
299
+ this.setUp(pass, bound, dynamicOffsets);
300
+ pass.dispatchWorkgroupsIndirect(args.buffer, args.offset + INDIRECT_ARGS_STRIDE * slot);
301
+ }
302
+
303
+ /**
304
+ * The argument checks dispatch() and dispatchIndirect() share: a BoundKernel of another kernel or an offset list
305
+ * of the wrong length is E_INVALID_ARGUMENT.
306
+ * @param bound - the bound kernel
307
+ * @param dynamicOffsets - the offsets, if any
308
+ */
309
+ private check(bound: BoundKernel, dynamicOffsets: readonly number[] | undefined): void {
262
310
  const { id } = this.spec;
263
311
  if (bound.kernel !== this) {
264
312
  throw new WebGpuGraphError(
@@ -282,9 +330,16 @@ export class Kernel {
282
330
  },
283
331
  );
284
332
  }
285
- if (plan.x === 0) {
286
- return;
287
- }
333
+ }
334
+
335
+ /**
336
+ * setPipeline + setBindGroup for every group (the dynamic offsets replicated over the uniform bindings of each
337
+ * dynamic group), the prefix dispatch() and dispatchIndirect() share.
338
+ * @param pass - the open compute pass
339
+ * @param bound - a BoundKernel of this kernel
340
+ * @param dynamicOffsets - one byte offset per entry of bound.dynamicGroups (absent = 0)
341
+ */
342
+ private setUp(pass: GPUComputePassEncoder, bound: BoundKernel, dynamicOffsets: readonly number[] | undefined): void {
288
343
  pass.setPipeline(this.pipeline);
289
344
  let next = 0;
290
345
  bound.bindGroups.forEach((bindGroup, group) => {
@@ -301,7 +356,6 @@ export class Kernel {
301
356
  pass.setBindGroup(group, bindGroup);
302
357
  }
303
358
  });
304
- pass.dispatchWorkgroups(plan.x, plan.y, 1);
305
359
  }
306
360
 
307
361
  /** Drops cached bind groups (a layout's buffers changed). */
@@ -19,7 +19,11 @@ import {
19
19
  FA2_FLAG_FIRST,
20
20
  FR_COOLING_PATIENCE,
21
21
  FR_COOLING_STEP,
22
+ GRID_BBOX_MARGIN,
23
+ GRID_EXTENT_FLOOR,
24
+ GRID_HUB_CELL,
22
25
  MAX_WORKGROUPS_PER_DIM,
26
+ RADIX_BINS,
23
27
  U32_MAX,
24
28
  WORKGROUP_SIZE,
25
29
  } from "../constants.js";
@@ -55,6 +59,11 @@ const FA2_FLAG_FIRST: u32 = ${FA2_FLAG_FIRST}u;
55
59
  const FA2_FLAG_ADAPTIVE: u32 = ${FA2_FLAG_ADAPTIVE}u;
56
60
  const FR_COOLING_STEP: f32 = ${wgslF32Literal(FR_COOLING_STEP)};
57
61
  const FR_COOLING_PATIENCE: u32 = ${FR_COOLING_PATIENCE}u;
62
+ const GRID_HUB_CELL: u32 = ${GRID_HUB_CELL}u;
63
+ const GRID_EXTENT_FLOOR: f32 = ${wgslF32Literal(GRID_EXTENT_FLOOR)};
64
+ const GRID_BBOX_MARGIN: f32 = ${wgslF32Literal(GRID_BBOX_MARGIN)};
65
+ const RADIX_BINS: u32 = ${RADIX_BINS}u;
66
+ const RADIX_DIGIT_MASK: u32 = ${RADIX_BINS - 1}u;
58
67
  const F32_MAX: f32 = 0x1.fffffep+127;
59
68
  override WG: u32 = ${WORKGROUP_SIZE}u;
60
69
  override USE_PERM: bool = false;
@@ -4,8 +4,10 @@
4
4
  * every CommandBatch pass; `resolveInto(batch)` records the resolve plus a copy into the batch's
5
5
  * staging slot and `timings(bytes, request)` decodes `{ label, ns }` per pass from the batch's
6
6
  * readback bytes. Chromium quantises timestamps to 100 us (`quantised: true`); Dawn-node reports
7
- * 1,024 ns ticks (spec 2.6). The profiler never throws: a full query set drops timings and a device
8
- * without the feature disables it. Slot pairs are handed out in order and the cursor restarts at 0
7
+ * 1,024 ns ticks (spec 2.6). The profiler never throws: a full query set drops the timings of the
8
+ * passes past the budget and marks the batch's request `partial(request)` so a caller summing the
9
+ * rows into a duration falls back to wall time; a device without the feature disables it. Slot
10
+ * pairs are handed out in order and the cursor restarts at 0
9
11
  * after every resolve: queue order guarantees the resolve of batch A reads its slots before batch B
10
12
  * overwrites them, and one resolve buffer suffices for the same reason.
11
13
  */
@@ -36,8 +38,10 @@ export class Profiler {
36
38
  private readonly querySet: GPUQuerySet | null;
37
39
  private readonly resolveBuffer: GPUBuffer | null;
38
40
  private readonly labels = new WeakMap<ReadbackRequest, readonly string[]>();
41
+ private readonly partialRequests = new WeakSet<ReadbackRequest>();
39
42
  private pending: PendingPass[] = [];
40
43
  private cursor = 0;
44
+ private refused = 0;
41
45
  private destroyed = false;
42
46
 
43
47
  /**
@@ -69,12 +73,17 @@ export class Profiler {
69
73
  }
70
74
 
71
75
  /**
72
- * The timestampWrites descriptor for a new pass, or undefined when disabled / out of slots.
76
+ * The timestampWrites descriptor for a new pass, or undefined when disabled / out of slots. A pass refused for
77
+ * want of slots is counted, and the next `resolveInto()` marks its request partial.
73
78
  * @param label - the pass name reported by `timings()`
74
79
  * @returns the descriptor a CommandBatch puts on beginComputePass, or undefined
75
80
  */
76
81
  beginPass(label: string): GPUComputePassTimestampWrites | undefined {
77
- if (this.querySet === null || this.destroyed || this.cursor + 2 > this.slots) {
82
+ if (this.querySet === null || this.destroyed) {
83
+ return undefined;
84
+ }
85
+ if (this.cursor + 2 > this.slots) {
86
+ this.refused++;
78
87
  return undefined;
79
88
  }
80
89
  const begin = this.cursor;
@@ -100,11 +109,25 @@ export class Profiler {
100
109
  request,
101
110
  this.pending.map((pass) => pass.label),
102
111
  );
112
+ if (this.refused > 0) {
113
+ this.partialRequests.add(request);
114
+ }
103
115
  this.pending = [];
104
116
  this.cursor = 0;
117
+ this.refused = 0;
105
118
  return request;
106
119
  }
107
120
 
121
+ /**
122
+ * Whether a pass of the batch was refused a slot pair, so `timings()` covers only its first slots / 2 passes
123
+ * and their sum is not the batch's duration.
124
+ * @param request - the request `resolveInto()` returned
125
+ * @returns true when at least one pass of that batch went untimed
126
+ */
127
+ partial(request: ReadbackRequest): boolean {
128
+ return this.partialRequests.has(request);
129
+ }
130
+
108
131
  /**
109
132
  * Decodes the timings of a batch from its readback bytes.
110
133
  * @param bytes - the resolved readback of the batch
@@ -138,6 +161,7 @@ export class Profiler {
138
161
  this.destroyed = true;
139
162
  this.pending = [];
140
163
  this.cursor = 0;
164
+ this.refused = 0;
141
165
  if (this.querySet !== null) {
142
166
  this.querySet.destroy();
143
167
  }