@aardworx/wombat.rendering 0.19.7 → 0.19.8

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.
@@ -60,6 +60,39 @@ import { emitPartitionKernel, substituteReadInputInStmt, collectUniformReadsInSt
60
60
  import {} from "./derivedModes/rule.js";
61
61
  import { addMarkingCallback } from "@aardworx/wombat.adaptive";
62
62
  import { analyseOutputSet, evaluateStructural, evaluateStructuralSet, stableStringify, } from "@aardworx/wombat.shader/ir";
63
+ // GrowBuffer + ALIGN16 + MIN_BUFFER_BYTES + POW2 live in ./heapScene/growBuffer.
64
+ // Packers (WGSL-type → JS-value → arena bytes) live in ./heapScene/packers.
65
+ // ---------------------------------------------------------------------------
66
+ // Shared WGSL prelude (struct + bindings + VS)
67
+ // ---------------------------------------------------------------------------
68
+ // Common decls — struct + bindings + helpers. Always concatenated
69
+ // into every group's shader, before its custom VS+FS bodies.
70
+ //
71
+ // One arena GPUBuffer is bound through multiple typed views. Today
72
+ // only `array<DrawHeader>` and `array<f32>` are wired; later we add
73
+ // `array<u32>`, `array<vec2<f32>>`, `array<vec4<f32>>`, etc. (skip
74
+ // `vec3` — its storage stride is 16, doesn't match tight packing).
75
+ //
76
+ // `attrRef` style: each attribute reference in DrawHeader is a byte
77
+ // offset into the arena pointing at the allocation's header. The
78
+ // header is `(u32 typeId, u32 length)`; data follows 16 bytes after
79
+ // the ref. Today every attribute is V3F_TIGHT-encoded so the shader
80
+ // reads 3 consecutive f32s from the f32 view; once we add more
81
+ // encodings, `loadVec3Attr` grows a branch on `typeId & 0xFFFFu`.
82
+ // (Per-bucket WGSL preludes are now generated by `buildBucketLayout`
83
+ // in heapEffect.ts; this module just glues the bucket layout, the
84
+ // shared arena, and the per-bucket DrawHeap together.)
85
+ // ---------------------------------------------------------------------------
86
+ // Geometry packing
87
+ // ---------------------------------------------------------------------------
88
+ /**
89
+ * Geometry triple. Tightly-packed Float32 positions / normals (3
90
+ * floats per vertex) plus Uint32 indices.
91
+ */
92
+ /** drawTable `firstIndex` sentinel marking a non-indexed record: the megacall
93
+ * prelude then uses the local vertex index directly (no indexStorage lookup).
94
+ * Must match the literal in heapEffect.ts's `megacallSearchPrelude`. */
95
+ export const HEAP_NONINDEXED = 0xffffffff;
63
96
  export function buildHeapScene(device, sig, initialDraws, opts = {}) {
64
97
  const atlasPool = opts.atlasPool;
65
98
  const colorAttachmentName = sig.colorNames[0];
@@ -2133,10 +2166,18 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
2133
2166
  const effectFields = fam.fieldsForEffect.get(spec.effect.id);
2134
2167
  // Indices live in their own INDEX-usage buffer (WebGPU constraint).
2135
2168
  // Aval-keyed: 19K instanced clones of the same mesh share one
2136
- // index allocation + one upload.
2137
- const indicesAval = asAval(spec.indices);
2138
- const indicesArr = readPlain(spec.indices);
2139
- const idxAlloc = indexPool.acquire(device, arena.indices, indicesAval, bucket.chunkIdx, indicesArr);
2169
+ // index allocation + one upload. NON-INDEXED draws (spec.indices
2170
+ // omitted) skip the index pool entirely: the drawTable record stores
2171
+ // firstIndex = HEAP_NONINDEXED (sentinel) and indexCount = vertexCount,
2172
+ // and the megacall prelude uses the local vertex index directly.
2173
+ const hasIndices = spec.indices !== undefined;
2174
+ const indicesAval = hasIndices ? asAval(spec.indices) : undefined;
2175
+ const idxAlloc = hasIndices
2176
+ ? indexPool.acquire(device, arena.indices, indicesAval, bucket.chunkIdx, readPlain(spec.indices))
2177
+ : undefined;
2178
+ // Emit count per instance + the firstIndex field written to the record.
2179
+ const emitCount = hasIndices ? idxAlloc.count : (spec.vertexCount ?? 0);
2180
+ const firstIndexField = hasIndices ? idxAlloc.firstIndex : HEAP_NONINDEXED;
2140
2181
  const localSlot = bucket.drawHeap.alloc();
2141
2182
  // Per-RO instancing: read `spec.instanceCount` (defaults to 1).
2142
2183
  const instanceCount = spec.instanceCount !== undefined
@@ -2219,7 +2260,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
2219
2260
  bucket.localPosRefs[localSlot] = perDrawRefs.get("Positions");
2220
2261
  bucket.localNorRefs[localSlot] = perDrawRefs.get("Normals");
2221
2262
  bucket.localEntries[localSlot] = {
2222
- indexCount: idxAlloc.count, firstIndex: idxAlloc.firstIndex, instanceCount,
2263
+ indexCount: emitCount, firstIndex: firstIndexField, instanceCount,
2223
2264
  };
2224
2265
  bucket.localToDrawId[localSlot] = drawId;
2225
2266
  bucket.drawSlots.add(localSlot);
@@ -2327,7 +2368,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
2327
2368
  // it) get 0 — the kernel only reads refs through combo fns that
2328
2369
  // gate on `r.comboId`, so an unread 0 is harmless.
2329
2370
  const uniformRefs = (bucket.uniformOrder ?? []).map(name => perDrawRefs.get(name) ?? 0);
2330
- partition.appendRecord(localSlot, idxAlloc.firstIndex, idxAlloc.count, instanceCount, roComboId, uniformRefs);
2371
+ partition.appendRecord(localSlot, firstIndexField, emitCount, instanceCount, roComboId, uniformRefs);
2331
2372
  bucket.drawIdToRecord.set(drawId, recIdx);
2332
2373
  bucket.recordToDrawId[recIdx] = drawId;
2333
2374
  bucket.drawIdToComboId.set(drawId, roComboId);
@@ -2338,7 +2379,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
2338
2379
  const numRecords = recIdx + 1;
2339
2380
  const recBytes = numRecords * RECORD_BYTES;
2340
2381
  const needBlocks = Math.max(1, Math.ceil(numRecords / SCAN_TILE_SIZE));
2341
- const emitDelta = idxAlloc.count * instanceCount;
2382
+ const emitDelta = emitCount * instanceCount;
2342
2383
  for (const s of bucket.slots) {
2343
2384
  s.drawTableBuf.ensureCapacity(recBytes);
2344
2385
  s.drawTableBuf.setUsed(Math.max(s.drawTableBuf.usedBytes, recBytes));
@@ -2370,8 +2411,8 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
2370
2411
  // firstEmit is GPU-overwritten by the prefix-sum pass; 0 is fine.
2371
2412
  shadow[recIdx * RECORD_U32 + 0] = 0;
2372
2413
  shadow[recIdx * RECORD_U32 + 1] = localSlot;
2373
- shadow[recIdx * RECORD_U32 + 2] = idxAlloc.firstIndex;
2374
- shadow[recIdx * RECORD_U32 + 3] = idxAlloc.count;
2414
+ shadow[recIdx * RECORD_U32 + 2] = firstIndexField;
2415
+ shadow[recIdx * RECORD_U32 + 3] = emitCount;
2375
2416
  shadow[recIdx * RECORD_U32 + 4] = instanceCount;
2376
2417
  roSlot.recordCount = recIdx + 1;
2377
2418
  roSlot.slotToRecord[localSlot] = recIdx;
@@ -2380,7 +2421,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
2380
2421
  roSlot.drawTableDirtyMin = byteOff;
2381
2422
  if (byteOff + RECORD_BYTES > roSlot.drawTableDirtyMax)
2382
2423
  roSlot.drawTableDirtyMax = byteOff + RECORD_BYTES;
2383
- roSlot.totalEmitEstimate += idxAlloc.count * instanceCount;
2424
+ roSlot.totalEmitEstimate += emitCount * instanceCount;
2384
2425
  const newNumTiles = Math.max(1, Math.ceil(roSlot.totalEmitEstimate / TILE_K));
2385
2426
  roSlot.firstDrawInTileBuf.ensureCapacity((newNumTiles + 1) * 4);
2386
2427
  roSlot.scanDirty = true;
@@ -2395,7 +2436,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
2395
2436
  // `subscribeActive` → `activeDirty` and drained in `update()`.
2396
2437
  if (spec.active !== undefined) {
2397
2438
  drawIdToActiveAval.set(drawId, spec.active);
2398
- drawIdToOrigIndexCount.set(drawId, idxAlloc.count);
2439
+ drawIdToOrigIndexCount.set(drawId, emitCount);
2399
2440
  const initial = spec.active.getValue(outerTok);
2400
2441
  subscribeActive(spec.active, drawId);
2401
2442
  if (initial === false) {
@@ -4224,7 +4265,9 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
4224
4265
  let _instCount = drawTable[_slot * 5u + 4u];
4225
4266
  let _local = emitIdx - _firstEmit;
4226
4267
  let _instId = _local / _indexCount;
4227
- let _vid = indexStorage[_indexStart + (_local % _indexCount)];
4268
+ let _li = _local % _indexCount;
4269
+ var _vid: u32 = _li;
4270
+ if (_indexStart != 0xffffffffu) { _vid = indexStorage[_indexStart + _li]; }
4228
4271
  let base = i * 8u;
4229
4272
  outRows[base + 0u] = _slot;
4230
4273
  outRows[base + 1u] = _drawIdx;