@graphty/webgpu-graph-algorithms 0.6.3 → 0.6.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.
Files changed (167) hide show
  1. package/README.md +62 -32
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{context-BXqgCifx.js → context-Dvq-Cc6v.js} +71 -25
  4. package/dist/chunks/context-Dvq-Cc6v.js.map +1 -0
  5. package/dist/node.js +1 -1
  6. package/dist/src/accelerator.d.ts +8 -6
  7. package/dist/src/accelerator.d.ts.map +1 -1
  8. package/dist/src/accelerator.js +57 -6
  9. package/dist/src/accelerator.js.map +1 -1
  10. package/dist/src/algorithms/bellman-ford.d.ts +60 -0
  11. package/dist/src/algorithms/bellman-ford.d.ts.map +1 -0
  12. package/dist/src/algorithms/bellman-ford.js +301 -0
  13. package/dist/src/algorithms/bellman-ford.js.map +1 -0
  14. package/dist/src/algorithms/bfs.d.ts +67 -0
  15. package/dist/src/algorithms/bfs.d.ts.map +1 -0
  16. package/dist/src/algorithms/bfs.js +534 -0
  17. package/dist/src/algorithms/bfs.js.map +1 -0
  18. package/dist/src/algorithms/closeness.d.ts +53 -0
  19. package/dist/src/algorithms/closeness.d.ts.map +1 -0
  20. package/dist/src/algorithms/closeness.js +323 -0
  21. package/dist/src/algorithms/closeness.js.map +1 -0
  22. package/dist/src/algorithms/scope.d.ts +5 -3
  23. package/dist/src/algorithms/scope.d.ts.map +1 -1
  24. package/dist/src/algorithms/scope.js +3 -0
  25. package/dist/src/algorithms/scope.js.map +1 -1
  26. package/dist/src/algorithms/sssp.d.ts +71 -0
  27. package/dist/src/algorithms/sssp.d.ts.map +1 -0
  28. package/dist/src/algorithms/sssp.js +585 -0
  29. package/dist/src/algorithms/sssp.js.map +1 -0
  30. package/dist/src/constants.d.ts +12 -0
  31. package/dist/src/constants.d.ts.map +1 -1
  32. package/dist/src/constants.js +12 -0
  33. package/dist/src/constants.js.map +1 -1
  34. package/dist/src/index.d.ts +8 -2
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/index.js +7 -1
  37. package/dist/src/index.js.map +1 -1
  38. package/dist/src/kernel/prelude.d.ts +4 -4
  39. package/dist/src/kernel/prelude.d.ts.map +1 -1
  40. package/dist/src/kernel/prelude.js +39 -5
  41. package/dist/src/kernel/prelude.js.map +1 -1
  42. package/dist/src/kernel/uniform-ring.d.ts +8 -0
  43. package/dist/src/kernel/uniform-ring.d.ts.map +1 -1
  44. package/dist/src/kernel/uniform-ring.js +13 -0
  45. package/dist/src/kernel/uniform-ring.js.map +1 -1
  46. package/dist/src/kernels.d.ts +44 -4
  47. package/dist/src/kernels.d.ts.map +1 -1
  48. package/dist/src/kernels.js +371 -3
  49. package/dist/src/kernels.js.map +1 -1
  50. package/dist/src/primitives/advance.d.ts +62 -0
  51. package/dist/src/primitives/advance.d.ts.map +1 -0
  52. package/dist/src/primitives/advance.js +95 -0
  53. package/dist/src/primitives/advance.js.map +1 -0
  54. package/dist/src/primitives/compact.d.ts +89 -0
  55. package/dist/src/primitives/compact.d.ts.map +1 -0
  56. package/dist/src/primitives/compact.js +233 -0
  57. package/dist/src/primitives/compact.js.map +1 -0
  58. package/dist/src/primitives/core-shape.d.ts +22 -1
  59. package/dist/src/primitives/core-shape.d.ts.map +1 -1
  60. package/dist/src/primitives/core-shape.js +33 -3
  61. package/dist/src/primitives/core-shape.js.map +1 -1
  62. package/dist/src/primitives/frontier.d.ts +156 -0
  63. package/dist/src/primitives/frontier.d.ts.map +1 -0
  64. package/dist/src/primitives/frontier.js +259 -0
  65. package/dist/src/primitives/frontier.js.map +1 -0
  66. package/dist/src/types/accelerator.d.ts +16 -7
  67. package/dist/src/types/accelerator.d.ts.map +1 -1
  68. package/dist/src/types/traversal.d.ts +53 -0
  69. package/dist/src/types/traversal.d.ts.map +1 -0
  70. package/dist/src/types/traversal.js +10 -0
  71. package/dist/src/types/traversal.js.map +1 -0
  72. package/dist/src/wgsl/advance-expand.wgsl.d.ts +19 -0
  73. package/dist/src/wgsl/advance-expand.wgsl.d.ts.map +1 -0
  74. package/dist/src/wgsl/advance-expand.wgsl.js +69 -0
  75. package/dist/src/wgsl/advance-expand.wgsl.js.map +1 -0
  76. package/dist/src/wgsl/bf-relax.wgsl.d.ts +22 -0
  77. package/dist/src/wgsl/bf-relax.wgsl.d.ts.map +1 -0
  78. package/dist/src/wgsl/bf-relax.wgsl.js +58 -0
  79. package/dist/src/wgsl/bf-relax.wgsl.js.map +1 -0
  80. package/dist/src/wgsl/bfs-bitset-build.wgsl.d.ts +15 -0
  81. package/dist/src/wgsl/bfs-bitset-build.wgsl.d.ts.map +1 -0
  82. package/dist/src/wgsl/bfs-bitset-build.wgsl.js +24 -0
  83. package/dist/src/wgsl/bfs-bitset-build.wgsl.js.map +1 -0
  84. package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts +20 -0
  85. package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts.map +1 -0
  86. package/dist/src/wgsl/bfs-bottom-up.wgsl.js +67 -0
  87. package/dist/src/wgsl/bfs-bottom-up.wgsl.js.map +1 -0
  88. package/dist/src/wgsl/bfs-contract.wgsl.d.ts +20 -0
  89. package/dist/src/wgsl/bfs-contract.wgsl.d.ts.map +1 -0
  90. package/dist/src/wgsl/bfs-contract.wgsl.js +55 -0
  91. package/dist/src/wgsl/bfs-contract.wgsl.js.map +1 -0
  92. package/dist/src/wgsl/bfs-fused.wgsl.d.ts +25 -0
  93. package/dist/src/wgsl/bfs-fused.wgsl.d.ts.map +1 -0
  94. package/dist/src/wgsl/bfs-fused.wgsl.js +78 -0
  95. package/dist/src/wgsl/bfs-fused.wgsl.js.map +1 -0
  96. package/dist/src/wgsl/bfs-unvisited-flags.wgsl.d.ts +18 -0
  97. package/dist/src/wgsl/bfs-unvisited-flags.wgsl.d.ts.map +1 -0
  98. package/dist/src/wgsl/bfs-unvisited-flags.wgsl.js +42 -0
  99. package/dist/src/wgsl/bfs-unvisited-flags.wgsl.js.map +1 -0
  100. package/dist/src/wgsl/closeness-reduce.wgsl.d.ts +17 -0
  101. package/dist/src/wgsl/closeness-reduce.wgsl.d.ts.map +1 -0
  102. package/dist/src/wgsl/closeness-reduce.wgsl.js +65 -0
  103. package/dist/src/wgsl/closeness-reduce.wgsl.js.map +1 -0
  104. package/dist/src/wgsl/closeness-sweep.wgsl.d.ts +20 -0
  105. package/dist/src/wgsl/closeness-sweep.wgsl.d.ts.map +1 -0
  106. package/dist/src/wgsl/closeness-sweep.wgsl.js +96 -0
  107. package/dist/src/wgsl/closeness-sweep.wgsl.js.map +1 -0
  108. package/dist/src/wgsl/compact-scatter.wgsl.d.ts +9 -0
  109. package/dist/src/wgsl/compact-scatter.wgsl.d.ts.map +1 -0
  110. package/dist/src/wgsl/compact-scatter.wgsl.js +17 -0
  111. package/dist/src/wgsl/compact-scatter.wgsl.js.map +1 -0
  112. package/dist/src/wgsl/dedupe-claim.wgsl.d.ts +10 -0
  113. package/dist/src/wgsl/dedupe-claim.wgsl.d.ts.map +1 -0
  114. package/dist/src/wgsl/dedupe-claim.wgsl.js +19 -0
  115. package/dist/src/wgsl/dedupe-claim.wgsl.js.map +1 -0
  116. package/dist/src/wgsl/dedupe-filter.wgsl.d.ts +12 -0
  117. package/dist/src/wgsl/dedupe-filter.wgsl.d.ts.map +1 -0
  118. package/dist/src/wgsl/dedupe-filter.wgsl.js +46 -0
  119. package/dist/src/wgsl/dedupe-filter.wgsl.js.map +1 -0
  120. package/dist/src/wgsl/frontier-finalize.wgsl.d.ts +59 -0
  121. package/dist/src/wgsl/frontier-finalize.wgsl.d.ts.map +1 -0
  122. package/dist/src/wgsl/frontier-finalize.wgsl.js +210 -0
  123. package/dist/src/wgsl/frontier-finalize.wgsl.js.map +1 -0
  124. package/dist/src/wgsl/sssp-pred.wgsl.d.ts +28 -0
  125. package/dist/src/wgsl/sssp-pred.wgsl.d.ts.map +1 -0
  126. package/dist/src/wgsl/sssp-pred.wgsl.js +80 -0
  127. package/dist/src/wgsl/sssp-pred.wgsl.js.map +1 -0
  128. package/dist/src/wgsl/sssp-relax.wgsl.d.ts +30 -0
  129. package/dist/src/wgsl/sssp-relax.wgsl.d.ts.map +1 -0
  130. package/dist/src/wgsl/sssp-relax.wgsl.js +72 -0
  131. package/dist/src/wgsl/sssp-relax.wgsl.js.map +1 -0
  132. package/dist/webgpu-graph-algorithms.js +3207 -377
  133. package/dist/webgpu-graph-algorithms.js.map +1 -1
  134. package/package.json +5 -4
  135. package/src/accelerator.ts +65 -7
  136. package/src/algorithms/bellman-ford.ts +387 -0
  137. package/src/algorithms/bfs.ts +626 -0
  138. package/src/algorithms/closeness.ts +395 -0
  139. package/src/algorithms/scope.ts +13 -3
  140. package/src/algorithms/sssp.ts +767 -0
  141. package/src/constants.ts +12 -0
  142. package/src/index.ts +14 -1
  143. package/src/kernel/prelude.ts +39 -4
  144. package/src/kernel/uniform-ring.ts +14 -0
  145. package/src/kernels.ts +450 -6
  146. package/src/primitives/advance.ts +130 -0
  147. package/src/primitives/compact.ts +323 -0
  148. package/src/primitives/core-shape.ts +41 -3
  149. package/src/primitives/frontier.ts +388 -0
  150. package/src/types/accelerator.ts +18 -5
  151. package/src/types/traversal.ts +56 -0
  152. package/src/wgsl/advance-expand.wgsl.ts +68 -0
  153. package/src/wgsl/bf-relax.wgsl.ts +57 -0
  154. package/src/wgsl/bfs-bitset-build.wgsl.ts +23 -0
  155. package/src/wgsl/bfs-bottom-up.wgsl.ts +66 -0
  156. package/src/wgsl/bfs-contract.wgsl.ts +54 -0
  157. package/src/wgsl/bfs-fused.wgsl.ts +77 -0
  158. package/src/wgsl/bfs-unvisited-flags.wgsl.ts +41 -0
  159. package/src/wgsl/closeness-reduce.wgsl.ts +64 -0
  160. package/src/wgsl/closeness-sweep.wgsl.ts +95 -0
  161. package/src/wgsl/compact-scatter.wgsl.ts +16 -0
  162. package/src/wgsl/dedupe-claim.wgsl.ts +18 -0
  163. package/src/wgsl/dedupe-filter.wgsl.ts +45 -0
  164. package/src/wgsl/frontier-finalize.wgsl.ts +209 -0
  165. package/src/wgsl/sssp-pred.wgsl.ts +79 -0
  166. package/src/wgsl/sssp-relax.wgsl.ts +71 -0
  167. package/dist/chunks/context-BXqgCifx.js.map +0 -1
package/src/constants.ts CHANGED
@@ -214,3 +214,15 @@ export const GRID_EXTENT_FLOOR = 1e-6;
214
214
  export const GRID_BBOX_MARGIN = 1.01;
215
215
  /** The key width the grid always sorts with (P4 PD-5): three 8-bit passes cover the 19-bit 2D keys at G = 512 and the 22-bit 3D keys at G = 128, and an odd pass count leaves `sortedIdx` in the scratch pair. */
216
216
  export const GRID_SORT_BITS = 24;
217
+ /** Design 8.4 (P8 PD-7): traversal levels recorded per submit -- one four-byte readback per 32 BFS levels, SSSP rounds or closeness levels. */
218
+ export const MAX_LEVELS_PER_SUBMIT = 32;
219
+ /** Design 8.4 and 6 row 8 (P8): a frontier at most this long runs the fused expand-contract kernel (Merrill's "fleeting iterations"); the default of the `fusedMax` uniform, which a test may set to 0 or `U32_MAX`. */
220
+ export const FUSED_FRONTIER_MAX = 4096;
221
+ /** P8-T4: the indirect dispatch slots `frontier-finalize` writes per level (expand, contract, fused, fill-bits, bitset-build, bottom-up, fused-retry); the args buffer is `MAX_LEVELS_PER_SUBMIT x FRONTIER_CANDIDATES x 16` bytes. */
222
+ export const FRONTIER_CANDIDATES = 7;
223
+ /** Design 8.4 (P8 PD-21): Beamer's beta -- switch back to top-down when `frontierCount * BEAMER_BETA < unvisitedCount` and the frontier is shrinking; alpha is derived from the graph, so it has no constant. */
224
+ export const BEAMER_BETA = 24;
225
+ /** Design 8.4 (P8 PD-22): the near-far split `delta = SSSP_DELTA_FACTOR * avgWeight / avgDegree`, computed on the host from the weight vector the run uses. */
226
+ export const SSSP_DELTA_FACTOR = 32;
227
+ /** The bit pattern of +Infinity, the unreached sentinel of `dist` (P8 PD-9); interpolated into the prelude as `F32_INF_BITS` so no body types the literal. */
228
+ export const F32_INF_BITS = 0x7f800000;
package/src/index.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  * isSoftwareAdapter. P1 adds GpuContext and degree (values) and the context / run / profiler types. P2 adds nothing
9
9
  * (Lease, CommandBatch, UniformRing are internal). P3 adds the layout factory, the accelerator, the two default
10
10
  * tables, the seeder and the layout / accelerator types. P5 adds the two factories, the two default tables and the
11
- * two stats records. P4 adds calibrateLayout and its two records. test/index.test.ts pins the value list and
11
+ * two stats records. P4 adds calibrateLayout and its two records. P8 adds the four traversals and their three result
12
+ * records. test/index.test.ts pins the value list and
12
13
  * test/types/public-api.test-d.ts the type list. This comment must never spell the internal
13
14
  * JSDoc tag: it is the leading comment of the first export statement, and stripInternal would drop that statement
14
15
  * from the emitted declarations.
@@ -54,6 +55,12 @@ export { connectedComponents } from "./algorithms/components.js";
54
55
  export { pageRank, personalizedPageRank } from "./algorithms/pagerank.js";
55
56
  export { eigenvectorCentrality, hits, katzCentrality } from "./algorithms/spectral.js";
56
57
 
58
+ // ==================== algorithms (P8: the frontier family, spec 3.3 lines 807-810, 8.4; the seam's option types in)
59
+ export { bellmanFord } from "./algorithms/bellman-ford.js";
60
+ export { breadthFirstSearch } from "./algorithms/bfs.js";
61
+ export { closenessCentrality } from "./algorithms/closeness.js";
62
+ export { sssp } from "./algorithms/sssp.js";
63
+
57
64
  // ==================== layouts and the accelerator (P3; the two P5 factories; P4's calibrateLayout, spec 2.2)
58
65
  export { createAccelerator } from "./accelerator.js";
59
66
  export { calibrateLayout } from "./layouts/calibrate.js";
@@ -70,6 +77,7 @@ export type {
70
77
  ApspResultLike,
71
78
  BellmanFordResultLike,
72
79
  BetweennessAcceleratorOptions,
80
+ BfsOptions,
73
81
  BfsResultLike,
74
82
  CommunityResultLike,
75
83
  CorenessResultLike,
@@ -83,9 +91,14 @@ export type {
83
91
  MstResultLike,
84
92
  PageRankResultLike,
85
93
  ScoresResultLike,
94
+ SsspOptions,
86
95
  SsspResultLike,
87
96
  } from "./types/accelerator.js";
88
97
 
98
+ // ==================== types: the P8 traversal results (spec 3.3 lines 830-832, 9.7); the option types are the seam's
99
+ // BfsOptions / SsspOptions / HitsOptionsLike above (P8 PD-19)
100
+ export type { GpuBellmanFordResult, GpuBfsResult, GpuSsspResult } from "./types/traversal.js";
101
+
89
102
  // ==================== types: the P7 algorithm results and option records (spec 3.3 lines 815-828, 9.7)
90
103
  export type {
91
104
  ComponentsOptions,
@@ -12,6 +12,7 @@
12
12
  import { INVALID_INDEX } from "@graphty/graph-format";
13
13
 
14
14
  import {
15
+ F32_INF_BITS,
15
16
  FA2_COINCIDENT_SQ,
16
17
  FA2_DISTANCE_FLOOR,
17
18
  FA2_DISTANCE_FLOOR_SQ,
@@ -51,6 +52,7 @@ export function wgslF32Literal(value: number): string {
51
52
  export const PRELUDE_WGSL: string = /* wgsl */ `// ---- prelude: constants, standard overrides, helpers (every module receives this text first)
52
53
  const INVALID_INDEX: u32 = ${INVALID_INDEX}u;
53
54
  const U32_MAX: u32 = ${U32_MAX}u;
55
+ const F32_INF_BITS: u32 = ${F32_INF_BITS}u;
54
56
  const MAX_WORKGROUPS_PER_DIM: u32 = ${MAX_WORKGROUPS_PER_DIM}u;
55
57
  const FA2_DIST_FLOOR: f32 = ${wgslF32Literal(FA2_DISTANCE_FLOOR)};
56
58
  const FA2_DIST_FLOOR_SQ: f32 = ${wgslF32Literal(FA2_DISTANCE_FLOOR_SQ)};
@@ -101,7 +103,7 @@ fn kick_dir(i: u32, j: u32, dim: u32) -> vec3f {
101
103
  /** Line count of PRELUDE_WGSL (the compilation-info formatter subtracts it plus the emitted declarations). */
102
104
  export const PRELUDE_LINES: number = PRELUDE_WGSL.split("\n").length;
103
105
 
104
- /** The workgroup-memory reduction helpers (the twin) (4.3). */
106
+ /** The workgroup-memory reduction helpers (the twin) (4.3), plus the workgroup-memory form of the P8 scan helper. */
105
107
  export const REDUCE_HELPERS_WORKGROUP_WGSL: string = /* wgsl */ `var<workgroup> wg_scratch_v: array<vec4f, WG>;
106
108
  var<workgroup> wg_scratch_u: array<u32, WG>;
107
109
  fn combine_v(a: vec4f, b: vec4f, op: u32) -> vec4f {
@@ -138,9 +140,24 @@ fn wg_reduce_u32(v: u32, lid: u32, op: u32) -> u32 {
138
140
  workgroupBarrier();
139
141
  return total;
140
142
  }
143
+ fn wg_scan_u32(v: u32, lid: u32) -> u32 {
144
+ workgroupBarrier();
145
+ wg_scratch_u[lid] = v;
146
+ workgroupBarrier();
147
+ for (var s = 1u; s < WG; s = s * 2u) { // Hillis-Steele inclusive scan (scan-block's body); uniform: every lane runs every round
148
+ var t = 0u;
149
+ if (lid >= s) { t = wg_scratch_u[lid - s]; }
150
+ workgroupBarrier();
151
+ wg_scratch_u[lid] = wg_scratch_u[lid] + t;
152
+ workgroupBarrier();
153
+ }
154
+ let inclusive = wg_scratch_u[lid];
155
+ workgroupBarrier(); // the scratch is free for the next call
156
+ return inclusive;
157
+ }
141
158
  fn wg_reduce_f32(v: f32, lid: u32, op: u32) -> f32 { return wg_reduce_vec4(vec4f(v, 0.0, 0.0, 0.0), lid, op).x; }`;
142
159
 
143
- /** The subgroup reduction helpers, spliced only with `enable subgroups;` (4.3). */
160
+ /** The subgroup reduction helpers, spliced only with `enable subgroups;` (4.3), plus the subgroup form of the P8 scan helper. */
144
161
  export const REDUCE_HELPERS_SUBGROUP_WGSL: string = /* wgsl */ `override SG_SLOTS: u32 = (WG + SUBGROUP_MIN - 1u) / SUBGROUP_MIN; // one slot per subgroup; the count is largest when the compiler picks the SMALLEST size
145
162
  var<workgroup> sg_counter: atomic<u32>;
146
163
  var<workgroup> sg_val_v: array<vec4f, SG_SLOTS>;
@@ -216,10 +233,28 @@ fn wg_reduce_u32(v: u32, lid: u32, op: u32) -> u32 {
216
233
  workgroupBarrier();
217
234
  return total;
218
235
  }
236
+ fn wg_scan_u32(v: u32, lid: u32) -> u32 {
237
+ workgroupBarrier();
238
+ if (lid == 0u) { atomicStore(&sg_counter, 0u); }
239
+ workgroupBarrier();
240
+ let inSub = subgroupExclusiveAdd(v) + v; // the lane's inclusive sum within its subgroup
241
+ let total = subgroupAdd(v);
242
+ let key = subgroupMin(lid); // the subgroup's identity: its smallest local id (D16)
243
+ var slot = 0u;
244
+ if (subgroupElect()) { slot = atomicAdd(&sg_counter, 1u); }
245
+ slot = subgroupBroadcast(slot, 0u);
246
+ if (subgroupElect()) { sg_val_u[slot] = total; sg_key[slot] = key; }
247
+ workgroupBarrier();
248
+ let count = atomicLoad(&sg_counter);
249
+ var carry = 0u; // the totals of every subgroup whose key is below this lane's: LANE order, whatever order the slots were taken in
250
+ for (var k = 0u; k < count; k = k + 1u) { if (sg_key[k] < key) { carry = carry + sg_val_u[k]; } }
251
+ workgroupBarrier(); // the slots are free for the next call
252
+ return inSub + carry;
253
+ }
219
254
  fn wg_reduce_f32(v: f32, lid: u32, op: u32) -> f32 { return wg_reduce_vec4(vec4f(v, 0.0, 0.0, 0.0), lid, op).x; }`;
220
255
 
221
- /** The helper function names a body may call when its spec lists needs: ["subgroups"]. */
222
- export const REDUCE_HELPER_NAMES = ["wg_reduce_f32", "wg_reduce_u32", "wg_reduce_vec4"] as const;
256
+ /** The helper function names a body may call when its spec lists needs: ["subgroups"]; `wg_scan_u32(v, lid)` is an INCLUSIVE prefix sum over the workgroup in lane order (P8-T1), bitwise the same in both forms. */
257
+ export const REDUCE_HELPER_NAMES = ["wg_reduce_f32", "wg_reduce_u32", "wg_reduce_vec4", "wg_scan_u32"] as const;
223
258
 
224
259
  /**
225
260
  * The WGSL reserved words of spec section 16.2 (the `_reserved` production), frozen; composeWgsl rejects a body or
@@ -18,6 +18,14 @@ import type { UniformBlock, UniformValues } from "./struct-block.js";
18
18
  export class UniformRing {
19
19
  /** Number of slots. */
20
20
  readonly slots: number;
21
+ /**
22
+ * How many reservations covered a DIRTY slot (written since the last `flush()`): the one reuse that corrupts,
23
+ * because the batch being recorded still reads that record and the shadow overwrites it before the flush sends
24
+ * either. A wrap onto slots an earlier, flushed batch wrote is harmless: its `writeBuffer` is ordered before that
25
+ * batch's submit and the next flush after it. A driver sizes its ring so this stays 0 (P8-T12: `bfsRingSlots`).
26
+ */
27
+ overruns = 0;
28
+ private readonly dirty: Uint8Array;
21
29
  private readonly device: GPUDevice;
22
30
  private readonly allocator: AllocationTracker;
23
31
  private readonly buffer: GPUBuffer;
@@ -54,6 +62,7 @@ export class UniformRing {
54
62
  this.label = label;
55
63
  this.shadow = new ArrayBuffer(slots * UNIFORM_SLOT_BYTES);
56
64
  this.view = new DataView(this.shadow);
65
+ this.dirty = new Uint8Array(slots);
57
66
  this.buffer = allocator.createBuffer({
58
67
  label,
59
68
  size: slots * UNIFORM_SLOT_BYTES,
@@ -105,6 +114,9 @@ export class UniformRing {
105
114
  }
106
115
  const first = this.next;
107
116
  this.next += count;
117
+ if (this.dirty.subarray(first, first + count).includes(1)) {
118
+ this.overruns += 1;
119
+ }
108
120
  return first;
109
121
  }
110
122
 
@@ -118,6 +130,7 @@ export class UniformRing {
118
130
  this.checkSlot(slot);
119
131
  this.assertFits(block);
120
132
  block.write(this.view, values, slot * UNIFORM_SLOT_BYTES);
133
+ this.dirty[slot] = 1;
121
134
  if (this.dirtyLo < 0 || slot < this.dirtyLo) {
122
135
  this.dirtyLo = slot;
123
136
  }
@@ -138,6 +151,7 @@ export class UniformRing {
138
151
  const begin = this.dirtyLo * UNIFORM_SLOT_BYTES;
139
152
  const end = (this.dirtyHi + 1) * UNIFORM_SLOT_BYTES;
140
153
  this.device.queue.writeBuffer(this.buffer, begin, this.shadow, begin, end - begin);
154
+ this.dirty.fill(0);
141
155
  this.dirtyLo = -1;
142
156
  this.dirtyHi = -1;
143
157
  }