@aardworx/wombat.rendering 0.9.33 → 0.19.0

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 (107) hide show
  1. package/dist/core/renderObject.d.ts +16 -0
  2. package/dist/core/renderObject.d.ts.map +1 -1
  3. package/dist/runtime/derivedModes/kernelCodegen.d.ts +90 -0
  4. package/dist/runtime/derivedModes/kernelCodegen.d.ts.map +1 -0
  5. package/dist/runtime/derivedModes/kernelCodegen.js +623 -0
  6. package/dist/runtime/derivedModes/kernelCodegen.js.map +1 -0
  7. package/dist/runtime/derivedModes/modeKeyCpu.d.ts.map +1 -1
  8. package/dist/runtime/derivedModes/modeKeyCpu.js +23 -67
  9. package/dist/runtime/derivedModes/modeKeyCpu.js.map +1 -1
  10. package/dist/runtime/derivedModes/partitionDispatcher.d.ts +79 -0
  11. package/dist/runtime/derivedModes/partitionDispatcher.d.ts.map +1 -0
  12. package/dist/runtime/derivedModes/partitionDispatcher.js +303 -0
  13. package/dist/runtime/derivedModes/partitionDispatcher.js.map +1 -0
  14. package/dist/runtime/derivedModes/partitionKernel.d.ts +12 -0
  15. package/dist/runtime/derivedModes/partitionKernel.d.ts.map +1 -0
  16. package/dist/runtime/derivedModes/partitionKernel.js +127 -0
  17. package/dist/runtime/derivedModes/partitionKernel.js.map +1 -0
  18. package/dist/runtime/derivedModes/partitionKernelLayout.d.ts +4 -0
  19. package/dist/runtime/derivedModes/partitionKernelLayout.d.ts.map +1 -0
  20. package/dist/runtime/derivedModes/partitionKernelLayout.js +24 -0
  21. package/dist/runtime/derivedModes/partitionKernelLayout.js.map +1 -0
  22. package/dist/runtime/derivedModes/rule.d.ts +38 -86
  23. package/dist/runtime/derivedModes/rule.d.ts.map +1 -1
  24. package/dist/runtime/derivedModes/rule.js +34 -74
  25. package/dist/runtime/derivedModes/rule.js.map +1 -1
  26. package/dist/runtime/derivedUniforms/dispatch.d.ts +10 -8
  27. package/dist/runtime/derivedUniforms/dispatch.d.ts.map +1 -1
  28. package/dist/runtime/derivedUniforms/dispatch.js +31 -18
  29. package/dist/runtime/derivedUniforms/dispatch.js.map +1 -1
  30. package/dist/runtime/derivedUniforms/marker.d.ts +90 -11
  31. package/dist/runtime/derivedUniforms/marker.d.ts.map +1 -1
  32. package/dist/runtime/derivedUniforms/marker.js +161 -15
  33. package/dist/runtime/derivedUniforms/marker.js.map +1 -1
  34. package/dist/runtime/derivedUniforms/sceneIntegration.d.ts +25 -6
  35. package/dist/runtime/derivedUniforms/sceneIntegration.d.ts.map +1 -1
  36. package/dist/runtime/derivedUniforms/sceneIntegration.js +61 -20
  37. package/dist/runtime/derivedUniforms/sceneIntegration.js.map +1 -1
  38. package/dist/runtime/heapAdapter.d.ts.map +1 -1
  39. package/dist/runtime/heapAdapter.js +17 -0
  40. package/dist/runtime/heapAdapter.js.map +1 -1
  41. package/dist/runtime/heapEligibility.d.ts.map +1 -1
  42. package/dist/runtime/heapEligibility.js +22 -2
  43. package/dist/runtime/heapEligibility.js.map +1 -1
  44. package/dist/runtime/heapScene/chunkedArena.d.ts +76 -0
  45. package/dist/runtime/heapScene/chunkedArena.d.ts.map +1 -0
  46. package/dist/runtime/heapScene/chunkedArena.js +223 -0
  47. package/dist/runtime/heapScene/chunkedArena.js.map +1 -0
  48. package/dist/runtime/heapScene/freelist.d.ts +55 -0
  49. package/dist/runtime/heapScene/freelist.d.ts.map +1 -0
  50. package/dist/runtime/heapScene/freelist.js +188 -0
  51. package/dist/runtime/heapScene/freelist.js.map +1 -0
  52. package/dist/runtime/heapScene/growBuffer.d.ts +23 -2
  53. package/dist/runtime/heapScene/growBuffer.d.ts.map +1 -1
  54. package/dist/runtime/heapScene/growBuffer.js +35 -3
  55. package/dist/runtime/heapScene/growBuffer.js.map +1 -1
  56. package/dist/runtime/heapScene/pools.d.ts +84 -42
  57. package/dist/runtime/heapScene/pools.d.ts.map +1 -1
  58. package/dist/runtime/heapScene/pools.js +309 -134
  59. package/dist/runtime/heapScene/pools.js.map +1 -1
  60. package/dist/runtime/heapScene.d.ts +24 -0
  61. package/dist/runtime/heapScene.d.ts.map +1 -1
  62. package/dist/runtime/heapScene.js +1158 -92
  63. package/dist/runtime/heapScene.js.map +1 -1
  64. package/dist/runtime/hybridScene.d.ts +15 -0
  65. package/dist/runtime/hybridScene.d.ts.map +1 -1
  66. package/dist/runtime/hybridScene.js +16 -0
  67. package/dist/runtime/hybridScene.js.map +1 -1
  68. package/dist/runtime/index.d.ts +4 -3
  69. package/dist/runtime/index.d.ts.map +1 -1
  70. package/dist/runtime/index.js +4 -3
  71. package/dist/runtime/index.js.map +1 -1
  72. package/dist/runtime/renderTask.d.ts +5 -0
  73. package/dist/runtime/renderTask.d.ts.map +1 -1
  74. package/dist/runtime/renderTask.js +1 -0
  75. package/dist/runtime/renderTask.js.map +1 -1
  76. package/dist/runtime/runtime.d.ts +6 -0
  77. package/dist/runtime/runtime.d.ts.map +1 -1
  78. package/dist/runtime/runtime.js +1 -0
  79. package/dist/runtime/runtime.js.map +1 -1
  80. package/dist/runtime/textureAtlas/atlasPool.d.ts +12 -0
  81. package/dist/runtime/textureAtlas/atlasPool.d.ts.map +1 -1
  82. package/dist/runtime/textureAtlas/atlasPool.js +20 -0
  83. package/dist/runtime/textureAtlas/atlasPool.js.map +1 -1
  84. package/package.json +1 -1
  85. package/src/core/renderObject.ts +16 -0
  86. package/src/runtime/derivedModes/kernelCodegen.ts +673 -0
  87. package/src/runtime/derivedModes/modeKeyCpu.ts +22 -60
  88. package/src/runtime/derivedModes/partitionDispatcher.ts +349 -0
  89. package/src/runtime/derivedModes/partitionKernelLayout.ts +25 -0
  90. package/src/runtime/derivedModes/rule.ts +66 -145
  91. package/src/runtime/derivedUniforms/dispatch.ts +48 -21
  92. package/src/runtime/derivedUniforms/marker.ts +210 -16
  93. package/src/runtime/derivedUniforms/sceneIntegration.ts +67 -20
  94. package/src/runtime/heapAdapter.ts +17 -0
  95. package/src/runtime/heapEligibility.ts +22 -1
  96. package/src/runtime/heapScene/chunkedArena.ts +238 -0
  97. package/src/runtime/heapScene/freelist.ts +196 -0
  98. package/src/runtime/heapScene/growBuffer.ts +39 -2
  99. package/src/runtime/heapScene/pools.ts +350 -143
  100. package/src/runtime/heapScene.ts +1350 -88
  101. package/src/runtime/hybridScene.ts +31 -0
  102. package/src/runtime/index.ts +12 -9
  103. package/src/runtime/renderTask.ts +6 -0
  104. package/src/runtime/runtime.ts +7 -0
  105. package/src/runtime/textureAtlas/atlasPool.ts +19 -0
  106. package/src/runtime/derivedModes/gpuDispatcher.ts +0 -226
  107. package/src/runtime/derivedModes/gpuKernel.ts +0 -98
@@ -1 +1 @@
1
- {"version":3,"file":"pools.d.ts","sourceRoot":"","sources":["../../../src/runtime/heapScene/pools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pools.d.ts","sourceRoot":"","sources":["../../../src/runtime/heapScene/pools.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,9 @@
15
15
  // / `isBufferView` / `asFloat32` helpers that the heap-scene factory
16
16
  // uses to assemble the arena.
17
17
  import { AVal } from "@aardworx/wombat.adaptive";
18
- import { GrowBuffer, ALIGN16 } from "./growBuffer.js";
18
+ import { GrowBuffer, ALIGN16, DEFAULT_MAX_BUFFER_BYTES } from "./growBuffer.js";
19
+ import { Freelist } from "./freelist.js";
20
+ import { ChunkedAttributeArena, ChunkedIndexAllocator } from "./chunkedArena.js";
19
21
  // ─── Per-allocation header layout ──────────────────────────────────────
20
22
  /** Per-allocation header: (u32 typeId, u32 length). Data follows
21
23
  * the header aligned up to 16 bytes (so positions/normals/etc. line
@@ -29,76 +31,137 @@ export const ENC_V3F_TIGHT = 1; // tightly-packed array of vec3<f32> (12 B/elt)
29
31
  export const SEM_POSITIONS = 1;
30
32
  export const SEM_NORMALS = 2;
31
33
  /**
32
- * Aval-keyed pool of arena allocations. One allocation per unique
33
- * aval (object identity). Two draws referencing the same aval share
34
- * the allocation; their DrawHeaders carry the same u32 ref. Holds
35
- * uniforms (fixed-size scalars/vectors/matrices) AND attribute arrays
36
- * (variable-size). The caller decides `dataBytes` + `length` per
37
- * acquisition the pool just keys on aval identity and refcounts.
38
- *
39
- * Sharing emerges from aval identity — no separate "frequency"
40
- * declaration needed. A `cval` shared by all draws → 1 alloc.
41
- * A static positions array shared across instanced draws → 1 alloc.
42
- * Same code path either way.
34
+ * `(aval, chunkIdx)`-keyed pool of chunked-arena allocations. An
35
+ * aval that's referenced from multiple chunks (e.g. two ROs sharing
36
+ * a uniform but landing in different chunks) gets one entry per
37
+ * chunk accepted duplication cost of the multi-draw-call
38
+ * per-chunk render path (§3). Within a single chunk, ROs sharing
39
+ * an aval still share the underlying allocation.
43
40
  */
44
41
  export class UniformPool {
45
- // Keyed by `aval<unknown>` *by reference* (a plain JS `Map`). These
46
- // keys are overwhelmingly reactive `cval`s (per-object trafos,
47
- // colours, …) and the hot path is `acquire`/`release` ~once per
48
- // drawHeader field per RO.
49
42
  byAval = new Map();
50
- has(av) { return this.byAval.has(av); }
51
- entry(av) { return this.byAval.get(av); }
43
+ has(av, chunkIdx) {
44
+ return this.byAval.get(av)?.has(chunkIdx) ?? false;
45
+ }
46
+ /** True when the pool holds at least one entry for `av` in any
47
+ * chunk. Used by the scene's `inputChanged` dispatch to decide
48
+ * whether a marking aval is one of our tracked allocations. */
49
+ hasAny(av) {
50
+ return (this.byAval.get(av)?.size ?? 0) > 0;
51
+ }
52
+ entry(av, chunkIdx) {
53
+ return this.byAval.get(av)?.get(chunkIdx);
54
+ }
55
+ /** First chunk this aval is allocated in, if any. Used by `addRO`
56
+ * to prefer co-locating a new RO with its already-allocated
57
+ * shared inputs (avoids unnecessary duplication). */
58
+ firstChunkContaining(av) {
59
+ const byChunk = this.byAval.get(av);
60
+ if (byChunk === undefined || byChunk.size === 0)
61
+ return undefined;
62
+ return byChunk.keys().next().value;
63
+ }
52
64
  /**
53
- * Acquire (or share) an allocation for `aval`. Caller passes the
54
- * pre-read `value` (so the pool doesn't need a token) plus the
55
- * (`dataBytes`, `typeId`, `length`, `pack`) describing how to lay
56
- * it out. If a new allocation is made, the value is packed and
57
- * uploaded immediately.
65
+ * Acquire (or share) an allocation for `aval` in `chunkIdx`.
66
+ * Returns the byte offset within that chunk's GPUBuffer. If a
67
+ * fresh allocation is made, the value is packed + uploaded
68
+ * immediately into the chunk's CPU shadow.
58
69
  */
59
- acquire(device, arena, av, value, dataBytes, typeId, length, pack) {
60
- const existing = this.byAval.get(av);
70
+ acquire(device, arena, av, chunkIdx, value, dataBytes, typeId, length, pack) {
71
+ let byChunk = this.byAval.get(av);
72
+ const existing = byChunk?.get(chunkIdx);
61
73
  if (existing !== undefined) {
62
74
  existing.refcount++;
63
75
  return existing.ref;
64
76
  }
65
- const ref = arena.alloc(dataBytes);
77
+ const r = arena.alloc(dataBytes, chunkIdx);
78
+ // `arena.alloc` may spill to a different chunk if `chunkIdx`'s
79
+ // GrowBuffer is at its maxCapacity. Callers (heapScene.addRO)
80
+ // bind a bucket's bind groups to a single chunk's buffer — a
81
+ // silent spill makes the bucket's drawHeader refs point into
82
+ // the wrong chunk's buffer (garbage reads / typeId corruption).
83
+ //
84
+ // Hard-fail with diagnostic info instead of returning quietly.
85
+ // The right long-term fix is for addRO to pre-probe the chunk,
86
+ // open a new bucket bound to the spill chunk, and re-route; but
87
+ // until that lands, a clear error beats unexplained artefacts.
88
+ if (r.chunkIdx !== chunkIdx) {
89
+ throw new Error(`UniformPool.acquire: allocator spilled from chunk ${chunkIdx} to chunk ${r.chunkIdx} ` +
90
+ `(${dataBytes} bytes). Caller's bucket is bound to chunk ${chunkIdx}; this would silently ` +
91
+ `corrupt the drawHeader→arena reads. Open a new bucket bound to chunk ${r.chunkIdx} instead.`);
92
+ }
93
+ const finalChunk = r.chunkIdx;
66
94
  const allocBytes = ALIGN16(ALLOC_HEADER_PAD_TO + dataBytes);
67
95
  const buf = new ArrayBuffer(allocBytes);
68
96
  const u32 = new Uint32Array(buf);
69
97
  const f32 = new Float32Array(buf);
70
98
  u32[0] = typeId;
71
99
  u32[1] = length;
72
- // stride_bytes (offset 8): bytes per element. Lets the VS decode
73
- // pick V3- vs V4-tight load expressions for vec4 attributes.
74
100
  u32[2] = length > 0 ? Math.floor(dataBytes / length) : 0;
75
101
  pack(value, f32, ALLOC_HEADER_PAD_TO / 4);
76
- arena.write(ref, new Uint8Array(buf));
102
+ arena.write(finalChunk, r.off, new Uint8Array(buf));
77
103
  void device;
78
- this.byAval.set(av, { ref, dataBytes, typeId, pack, refcount: 1 });
79
- return ref;
104
+ if (byChunk === undefined) {
105
+ byChunk = new Map();
106
+ this.byAval.set(av, byChunk);
107
+ }
108
+ byChunk.set(finalChunk, {
109
+ chunkIdx: finalChunk, ref: r.off, dataBytes, typeId, pack, refcount: 1,
110
+ });
111
+ return r.off;
80
112
  }
81
- /** Decrement refcount; if zero, free the arena allocation. */
82
- release(arena, av) {
83
- const e = this.byAval.get(av);
113
+ release(arena, av, chunkIdx) {
114
+ const byChunk = this.byAval.get(av);
115
+ const e = byChunk?.get(chunkIdx);
84
116
  if (e === undefined)
85
117
  return;
86
118
  e.refcount--;
87
119
  if (e.refcount > 0)
88
120
  return;
89
- arena.release(e.ref, ALIGN16(ALLOC_HEADER_PAD_TO + e.dataBytes));
90
- this.byAval.delete(av);
121
+ // BUG FIX: arena.release's third arg is `dataBytes` (raw), and the
122
+ // chunk's AttributeArena.release internally adds the header padding
123
+ // via `ALIGN16(ALLOC_HEADER_PAD_TO + dataBytes)`. We MUST pass the
124
+ // raw data size here — passing pre-aligned `allocBytes` (the old
125
+ // code) double-pads, freeing 16 extra bytes per release. The freed
126
+ // surplus then gets coalesced with the neighbouring scalar-uniform
127
+ // allocs (any 32 B u32/f32) into a single combined block, and the
128
+ // next attribute alloc handed out from that pool ends up STARTING
129
+ // 16 B inside the previous alloc → overlapping allocations →
130
+ // garbage `typeId`/`length` in the attribute header.
131
+ arena.release(e.chunkIdx, e.ref, e.dataBytes);
132
+ byChunk.delete(chunkIdx);
133
+ if (byChunk.size === 0)
134
+ this.byAval.delete(av);
91
135
  }
92
- /** Re-pack one entry's data region into the arena's CPU shadow. */
136
+ /** Re-pack one entry's data region into every chunk that holds
137
+ * this aval. When an aval is shared across N chunks (each chunk
138
+ * has its own duplicate alloc), every duplicate needs the
139
+ * refresh so all the chunks' ROs see the new value. */
93
140
  repack(device, arena, av, val) {
94
- const e = this.byAval.get(av);
95
- if (e === undefined)
141
+ const byChunk = this.byAval.get(av);
142
+ if (byChunk === undefined)
96
143
  return;
97
- const dst = new Float32Array(e.dataBytes / 4);
98
- e.pack(val, dst, 0);
99
- arena.write(e.ref + ALLOC_HEADER_PAD_TO, new Uint8Array(dst.buffer, dst.byteOffset, e.dataBytes));
144
+ let dst;
145
+ for (const e of byChunk.values()) {
146
+ if (dst === undefined || dst.length !== e.dataBytes / 4) {
147
+ dst = new Float32Array(e.dataBytes / 4);
148
+ }
149
+ e.pack(val, dst, 0);
150
+ arena.write(e.chunkIdx, e.ref + ALLOC_HEADER_PAD_TO, new Uint8Array(dst.buffer, dst.byteOffset, e.dataBytes));
151
+ }
100
152
  void device;
101
153
  }
154
+ /** Total bytes touched by `repack` for this aval — sum across
155
+ * chunks. Used by the dirty-bytes diagnostics. */
156
+ totalDataBytes(av) {
157
+ const byChunk = this.byAval.get(av);
158
+ if (byChunk === undefined)
159
+ return 0;
160
+ let s = 0;
161
+ for (const e of byChunk.values())
162
+ s += e.dataBytes;
163
+ return s;
164
+ }
102
165
  }
103
166
  // ─── IndexPool ─────────────────────────────────────────────────────────
104
167
  /**
@@ -114,7 +177,10 @@ export class UniformPool {
114
177
  * `Uint32Array` view collapse to one allocation.
115
178
  */
116
179
  export class IndexPool {
180
+ /** Per-aval, per-chunk bookkeeping. Same aval acquired in two
181
+ * chunks gets two entries (accepted §3 duplication). */
117
182
  byAval = new Map();
183
+ /** Per-chunk constant-aval value-dedup (§5b in-chunk). */
118
184
  byValueKey = new Map();
119
185
  bufferIds = new WeakMap();
120
186
  nextBufferId = 1;
@@ -126,43 +192,69 @@ export class IndexPool {
126
192
  }
127
193
  return id;
128
194
  }
129
- acquire(device, indices, av, arr) {
130
- const bound = this.byAval.get(av);
195
+ firstChunkContaining(av) {
196
+ const byChunk = this.byAval.get(av);
197
+ if (byChunk === undefined || byChunk.size === 0)
198
+ return undefined;
199
+ return byChunk.keys().next().value;
200
+ }
201
+ acquire(device, indices, av, chunkIdx, arr) {
202
+ let byChunk = this.byAval.get(av);
203
+ const bound = byChunk?.get(chunkIdx);
131
204
  if (bound !== undefined) {
132
205
  bound.perAvalCount++;
133
206
  bound.entry.totalRefcount++;
134
- return { firstIndex: bound.entry.firstIndex, count: bound.entry.count };
207
+ return { chunkIdx: bound.entry.chunkIdx, firstIndex: bound.entry.firstIndex, count: bound.entry.count };
135
208
  }
136
209
  let valueKey;
137
210
  if (av.isConstant) {
138
- valueKey = `${this.bufferIdOf(arr.buffer)}:${arr.byteOffset}:${arr.byteLength}`;
211
+ // Value-dedup is scoped per chunk — two constant avals in the
212
+ // same chunk collapse to one allocation; two constant avals in
213
+ // DIFFERENT chunks each get their own (we can't share across
214
+ // chunks since the index range lives in the chunk's GPUBuffer).
215
+ valueKey = `${chunkIdx}:${this.bufferIdOf(arr.buffer)}:${arr.byteOffset}:${arr.byteLength}`;
139
216
  const shared = this.byValueKey.get(valueKey);
140
217
  if (shared !== undefined) {
141
218
  shared.totalRefcount++;
142
- this.byAval.set(av, { entry: shared, perAvalCount: 1 });
143
- return { firstIndex: shared.firstIndex, count: shared.count };
219
+ if (byChunk === undefined) {
220
+ byChunk = new Map();
221
+ this.byAval.set(av, byChunk);
222
+ }
223
+ byChunk.set(chunkIdx, { entry: shared, perAvalCount: 1 });
224
+ return { chunkIdx: shared.chunkIdx, firstIndex: shared.firstIndex, count: shared.count };
144
225
  }
145
226
  }
146
- const firstIndex = indices.alloc(arr.length);
147
- indices.write(firstIndex * 4, new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength));
227
+ const r = indices.alloc(arr.length, chunkIdx);
228
+ indices.write(r.chunkIdx, r.off * 4, new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength));
148
229
  void device;
149
- const entry = { firstIndex, count: arr.length, totalRefcount: 1, valueKey };
150
- this.byAval.set(av, { entry, perAvalCount: 1 });
230
+ const entry = {
231
+ chunkIdx: r.chunkIdx, firstIndex: r.off, count: arr.length,
232
+ totalRefcount: 1, valueKey,
233
+ };
234
+ if (byChunk === undefined) {
235
+ byChunk = new Map();
236
+ this.byAval.set(av, byChunk);
237
+ }
238
+ byChunk.set(r.chunkIdx, { entry, perAvalCount: 1 });
151
239
  if (valueKey !== undefined)
152
240
  this.byValueKey.set(valueKey, entry);
153
- return { firstIndex, count: arr.length };
241
+ return { chunkIdx: r.chunkIdx, firstIndex: r.off, count: arr.length };
154
242
  }
155
- release(indices, av) {
156
- const bound = this.byAval.get(av);
243
+ release(indices, av, chunkIdx) {
244
+ const byChunk = this.byAval.get(av);
245
+ const bound = byChunk?.get(chunkIdx);
157
246
  if (bound === undefined)
158
247
  return;
159
248
  bound.perAvalCount--;
160
249
  bound.entry.totalRefcount--;
161
- if (bound.perAvalCount === 0)
162
- this.byAval.delete(av);
250
+ if (bound.perAvalCount === 0) {
251
+ byChunk.delete(chunkIdx);
252
+ if (byChunk.size === 0)
253
+ this.byAval.delete(av);
254
+ }
163
255
  if (bound.entry.totalRefcount > 0)
164
256
  return;
165
- indices.release(bound.entry.firstIndex, bound.entry.count);
257
+ indices.release(bound.entry.chunkIdx, bound.entry.firstIndex, bound.entry.count);
166
258
  if (bound.entry.valueKey !== undefined) {
167
259
  this.byValueKey.delete(bound.entry.valueKey);
168
260
  }
@@ -180,6 +272,8 @@ export class DrawHeap {
180
272
  slotBytes;
181
273
  free = [];
182
274
  nextSlot = 0;
275
+ /** DEBUG: tracks slot live/free state. true = live (in use). */
276
+ live = new Set();
183
277
  constructor(buf, slotBytes) {
184
278
  this.buf = buf;
185
279
  this.slotBytes = slotBytes;
@@ -191,11 +285,21 @@ export class DrawHeap {
191
285
  get usedBytes() { return this.nextSlot * this.slotBytes; }
192
286
  alloc() {
193
287
  const slot = this.free.length > 0 ? this.free.pop() : this.nextSlot++;
288
+ if (this.live.has(slot)) {
289
+ throw new Error(`DrawHeap.alloc: returned already-live slot ${slot}`);
290
+ }
291
+ this.live.add(slot);
194
292
  this.buf.ensureCapacity((slot + 1) * this.slotBytes);
195
293
  this.buf.setUsed(Math.max(this.buf.usedBytes, (slot + 1) * this.slotBytes));
196
294
  return slot;
197
295
  }
198
- release(slot) { this.free.push(slot); }
296
+ release(slot) {
297
+ if (!this.live.has(slot)) {
298
+ throw new Error(`DrawHeap.release: slot ${slot} was not live — double-release or release-of-never-allocated.`);
299
+ }
300
+ this.live.delete(slot);
301
+ this.free.push(slot);
302
+ }
199
303
  onResize(cb) { return this.buf.onResize(cb); }
200
304
  destroy() { this.buf.destroy(); }
201
305
  }
@@ -209,7 +313,12 @@ export class DrawHeap {
209
313
  export class AttributeArena {
210
314
  buf;
211
315
  cursor = 0;
212
- freeList = [];
316
+ freelist = new Freelist();
317
+ /** DEBUG: tracks every live allocation by start byte → size. Set
318
+ * on alloc, cleared on release. Used by `assertNoOverlap()` to
319
+ * detect allocator returning overlapping ranges. Enabled in
320
+ * development; cheap (one Map insert/delete per alloc). */
321
+ liveAllocs = new Map();
213
322
  /** CPU shadow of the entire GPU buffer; writes go here first then
214
323
  * flush() emits one writeBuffer per dirty contiguous range. */
215
324
  shadow;
@@ -227,6 +336,15 @@ export class AttributeArena {
227
336
  get buffer() { return this.buf.buffer; }
228
337
  get capacity() { return this.buf.capacity; }
229
338
  get usedBytes() { return this.cursor; }
339
+ /** Read 4 u32s from the CPU shadow starting at byte `off`. Used by
340
+ * the validator to compare against the GPU's read-back values:
341
+ * if CPU shadow is correct but GPU shows garbage → the corruption
342
+ * came from a GPU-side write (a compute kernel writing to a wrong
343
+ * byte). If both differ from what pool.acquire wrote → CPU-side
344
+ * write path is the culprit. */
345
+ peekShadowU32(off, count) {
346
+ return new Uint32Array(this.shadow.buffer, this.shadow.byteOffset + off, count);
347
+ }
230
348
  write(dst, data) {
231
349
  this.shadow.set(data, dst);
232
350
  if (dst < this.dirtyMin)
@@ -246,76 +364,91 @@ export class AttributeArena {
246
364
  * Allocate space for one attribute. Returns the byte ref (offset
247
365
  * to the header — data lives at ref + 16).
248
366
  */
249
- alloc(dataBytes) {
367
+ /** Non-throwing alloc — returns `undefined` when the request
368
+ * would exceed the underlying GrowBuffer's `maxCapacity` cap.
369
+ * Used by `ChunkedAttributeArena` to probe before spilling to
370
+ * the next chunk. */
371
+ tryAlloc(dataBytes) {
250
372
  const allocBytes = ALIGN16(ALLOC_HEADER_PAD_TO + dataBytes);
251
- // First-fit reuse from free list.
252
- for (let i = 0; i < this.freeList.length; i++) {
253
- const f = this.freeList[i];
254
- if (f.size >= allocBytes) {
255
- const ref = f.off;
256
- if (f.size === allocBytes)
257
- this.freeList.splice(i, 1);
258
- else {
259
- f.off += allocBytes;
260
- f.size -= allocBytes;
261
- }
262
- return ref;
263
- }
373
+ const reused = this.freelist.alloc(allocBytes);
374
+ if (reused !== undefined) {
375
+ this.recordAlloc(reused, allocBytes, "freelist");
376
+ return reused;
264
377
  }
265
- const ref = this.cursor;
266
- this.cursor += allocBytes;
267
- this.buf.ensureCapacity(this.cursor);
268
- this.buf.setUsed(this.cursor);
378
+ const next = this.cursor + allocBytes;
379
+ if (next > this.buf.maxCapacity)
380
+ return undefined;
381
+ this.cursor = next;
382
+ this.buf.ensureCapacity(next);
383
+ this.buf.setUsed(next);
384
+ const ref = next - allocBytes;
385
+ this.recordAlloc(ref, allocBytes, "bump");
269
386
  return ref;
270
387
  }
388
+ alloc(dataBytes) {
389
+ const r = this.tryAlloc(dataBytes);
390
+ if (r === undefined) {
391
+ throw new Error(`AttributeArena: allocation of ${dataBytes}B (with header alignment) exceeds chunk cap`);
392
+ }
393
+ return r;
394
+ }
271
395
  release(ref, dataBytes) {
272
396
  const allocBytes = ALIGN16(ALLOC_HEADER_PAD_TO + dataBytes);
273
- insertSortedFreeBlock(this.freeList, ref, allocBytes);
274
- }
275
- onResize(cb) { return this.buf.onResize(cb); }
276
- destroy() { this.buf.destroy(); }
277
- }
278
- /**
279
- * Insert `{off, size}` into a free-list kept sorted by `off`, then
280
- * coalesce with the two immediate neighbours.
281
- */
282
- export function insertSortedFreeBlock(freeList, off, size) {
283
- let lo = 0, hi = freeList.length;
284
- while (lo < hi) {
285
- const mid = (lo + hi) >>> 1;
286
- if (freeList[mid].off <= off)
287
- lo = mid + 1;
288
- else
289
- hi = mid;
290
- }
291
- const prev = lo > 0 ? freeList[lo - 1] : undefined;
292
- if (prev !== undefined && prev.off + prev.size === off) {
293
- prev.size += size;
294
- const next = freeList[lo];
295
- if (next !== undefined && prev.off + prev.size === next.off) {
296
- prev.size += next.size;
297
- freeList.splice(lo, 1);
397
+ const tracked = this.liveAllocs.get(ref);
398
+ if (tracked === undefined) {
399
+ throw new Error(`AttributeArena.release: ref=${ref} (size=${allocBytes}) was not in liveAllocs — ` +
400
+ `double-free or release for an alloc that never happened. ` +
401
+ `Live alloc count=${this.liveAllocs.size}.`);
402
+ }
403
+ if (tracked !== allocBytes) {
404
+ throw new Error(`AttributeArena.release: size mismatch at ref=${ref} — recorded=${tracked} but ` +
405
+ `release args→ allocBytes=${allocBytes} (dataBytes=${dataBytes}). ` +
406
+ `Caller is releasing the wrong size; this WILL corrupt the freelist via over- or under-shrinking.`);
407
+ }
408
+ this.liveAllocs.delete(ref);
409
+ this.freelist.release(ref, allocBytes);
410
+ // §5: shrink the bump cursor back if release exposed a free
411
+ // tail touching it (cascading — `freelist.release` may have
412
+ // coalesced into a single block ending at `cursor`). Reclaims
413
+ // the high-watermark over time in long-lived high-churn scenes.
414
+ while (true) {
415
+ const top = this.freelist.takeBlockEndingAt(this.cursor);
416
+ if (top === undefined)
417
+ break;
418
+ this.cursor = top.off;
419
+ this.buf.setUsed(this.cursor);
298
420
  }
299
- return;
300
421
  }
301
- const next = freeList[lo];
302
- if (next !== undefined && off + size === next.off) {
303
- next.off = off;
304
- next.size += size;
305
- return;
422
+ /** Throws if the just-returned alloc overlaps any live allocation. */
423
+ recordAlloc(off, size, source) {
424
+ // Check against every existing live alloc — O(N). Cheap while N
425
+ // is small (per-tile demos run with ~hundreds of allocs). Wire
426
+ // off behind an env-flag if it ever becomes a hot path.
427
+ for (const [liveOff, liveSize] of this.liveAllocs) {
428
+ // [a, a+sa) vs [b, b+sb) overlap iff a < b+sb && b < a+sa
429
+ if (off < liveOff + liveSize && liveOff < off + size) {
430
+ throw new Error(`AttributeArena.tryAlloc(${source}): returned [${off},${off + size}) ` +
431
+ `(size=${size}) overlaps live alloc at [${liveOff},${liveOff + liveSize}) ` +
432
+ `(size=${liveSize}). Allocator is handing out shared memory!`);
433
+ }
434
+ }
435
+ this.liveAllocs.set(off, size);
306
436
  }
307
- freeList.splice(lo, 0, { off, size });
437
+ onResize(cb) { return this.buf.onResize(cb); }
438
+ destroy() { this.buf.destroy(); }
308
439
  }
309
440
  // ─── IndexAllocator (element-bump) ─────────────────────────────────────
310
441
  /**
311
442
  * Element-bump allocator over an index GrowBuffer (units = u32). Each
312
443
  * draw's index range is allocated as one block; on release the block
313
- * is returned to a free list and can be reused first-fit.
444
+ * is returned to a Freelist and can be reused best-fit.
314
445
  */
315
446
  export class IndexAllocator {
316
447
  buf;
317
448
  cursor = 0; // in u32s, not bytes
318
- freeList = [];
449
+ freelist = new Freelist();
450
+ /** DEBUG: live index allocations tracked by start element. */
451
+ liveAllocs = new Map();
319
452
  shadow;
320
453
  dirtyMin = Infinity;
321
454
  dirtyMax = 0;
@@ -345,39 +478,81 @@ export class IndexAllocator {
345
478
  this.dirtyMin = Infinity;
346
479
  this.dirtyMax = 0;
347
480
  }
348
- alloc(elements) {
349
- for (let i = 0; i < this.freeList.length; i++) {
350
- const f = this.freeList[i];
351
- if (f.size >= elements) {
352
- const off = f.off;
353
- if (f.size === elements)
354
- this.freeList.splice(i, 1);
355
- else {
356
- f.off += elements;
357
- f.size -= elements;
358
- }
359
- return off;
360
- }
481
+ /** Non-throwing alloc — returns `undefined` when the request
482
+ * would exceed the underlying GrowBuffer's `maxCapacity` cap.
483
+ * Used by `ChunkedIndexAllocator` to probe before spilling. */
484
+ tryAlloc(elements) {
485
+ const reused = this.freelist.alloc(elements);
486
+ if (reused !== undefined) {
487
+ this.recordAlloc(reused, elements, "freelist");
488
+ return reused;
361
489
  }
362
- const off = this.cursor;
363
- this.cursor += elements;
364
- this.buf.ensureCapacity(this.cursor * 4);
365
- this.buf.setUsed(this.cursor * 4);
490
+ const nextElts = this.cursor + elements;
491
+ if (nextElts * 4 > this.buf.maxCapacity)
492
+ return undefined;
493
+ this.cursor = nextElts;
494
+ this.buf.ensureCapacity(nextElts * 4);
495
+ this.buf.setUsed(nextElts * 4);
496
+ const off = nextElts - elements;
497
+ this.recordAlloc(off, elements, "bump");
366
498
  return off;
367
499
  }
500
+ recordAlloc(off, size, source) {
501
+ for (const [liveOff, liveSize] of this.liveAllocs) {
502
+ if (off < liveOff + liveSize && liveOff < off + size) {
503
+ throw new Error(`IndexAllocator.tryAlloc(${source}): returned [${off},${off + size}) ` +
504
+ `(size=${size}) overlaps live alloc at [${liveOff},${liveOff + liveSize}) ` +
505
+ `(size=${liveSize}). Index allocator is handing out shared memory!`);
506
+ }
507
+ }
508
+ this.liveAllocs.set(off, size);
509
+ }
510
+ alloc(elements) {
511
+ const r = this.tryAlloc(elements);
512
+ if (r === undefined) {
513
+ throw new Error(`IndexAllocator: allocation of ${elements} elements exceeds chunk cap`);
514
+ }
515
+ return r;
516
+ }
368
517
  release(off, elements) {
369
- insertSortedFreeBlock(this.freeList, off, elements);
518
+ const tracked = this.liveAllocs.get(off);
519
+ if (tracked === undefined) {
520
+ throw new Error(`IndexAllocator.release: off=${off} (elements=${elements}) was not in ` +
521
+ `liveAllocs — double-free or release for an alloc that never happened. ` +
522
+ `Live alloc count=${this.liveAllocs.size}.`);
523
+ }
524
+ if (tracked !== elements) {
525
+ throw new Error(`IndexAllocator.release: size mismatch at off=${off} — recorded=${tracked} ` +
526
+ `but release elements=${elements}. Will corrupt the freelist.`);
527
+ }
528
+ this.liveAllocs.delete(off);
529
+ this.freelist.release(off, elements);
530
+ while (true) {
531
+ const top = this.freelist.takeBlockEndingAt(this.cursor);
532
+ if (top === undefined)
533
+ break;
534
+ this.cursor = top.off;
535
+ this.buf.setUsed(this.cursor * 4);
536
+ }
370
537
  }
371
538
  onResize(cb) { return this.buf.onResize(cb); }
372
539
  destroy() { this.buf.destroy(); }
373
540
  }
374
- export function buildArenaState(device, attrBytesHint, idxBytesHint, label, idxExtraUsage = 0) {
375
- const attrs = new AttributeArena(new GrowBuffer(device, `${label}/attrs`, GPUBufferUsage.STORAGE, attrBytesHint));
376
- const indices = new IndexAllocator(new GrowBuffer(device, `${label}/idx`, GPUBufferUsage.INDEX | idxExtraUsage, idxBytesHint));
541
+ export function buildArenaState(device, attrBytesHint, idxBytesHint, label, idxExtraUsage = 0, maxChunkBytes = undefined) {
542
+ // §3: cap chunk size at min(adapter's maxStorageBufferBindingSize,
543
+ // GrowBuffer's DEFAULT_MAX_BUFFER_BYTES). Lets chunks grow as far
544
+ // as the hardware lets us (typically 2 GiB+ on desktop, ≥ 256 MB
545
+ // on mobile/integrated) while keeping a sensible internal ceiling.
546
+ // Caller can override (the heap-demo uses a tiny 4 MB cap to
547
+ // exercise multi-chunk routing).
548
+ const adapterCap = device.limits.maxStorageBufferBindingSize;
549
+ const cap = maxChunkBytes ?? Math.min(adapterCap, DEFAULT_MAX_BUFFER_BYTES);
550
+ const attrs = new ChunkedAttributeArena(device, `${label}/attrs`, GPUBufferUsage.STORAGE, attrBytesHint, cap);
551
+ const indices = new ChunkedIndexAllocator(device, `${label}/idx`, GPUBufferUsage.INDEX | idxExtraUsage, idxBytesHint, cap);
377
552
  return { attrs, indices };
378
553
  }
379
554
  export function arenaBytes(arena) {
380
- return arena.attrs.usedBytes + arena.indices.usedElements * 4;
555
+ return arena.attrs.totalUsedBytes() + arena.indices.totalUsedElements() * 4;
381
556
  }
382
557
  /** Upload a single attribute — header (typeId, length) + data — into the arena at byte offset `ref`. */
383
558
  export function writeAttribute(device, buf, ref, typeId, length, data) {