@aardworx/wombat.rendering 0.21.12 → 0.21.13
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.
- package/dist/runtime/heapAdapter.d.ts.map +1 -1
- package/dist/runtime/heapAdapter.js +15 -5
- package/dist/runtime/heapAdapter.js.map +1 -1
- package/dist/runtime/heapScene/pools.d.ts +30 -6
- package/dist/runtime/heapScene/pools.d.ts.map +1 -1
- package/dist/runtime/heapScene/pools.js +38 -1
- package/dist/runtime/heapScene/pools.js.map +1 -1
- package/dist/runtime/heapScene.d.ts.map +1 -1
- package/dist/runtime/heapScene.js +182 -3
- package/dist/runtime/heapScene.js.map +1 -1
- package/package.json +1 -1
- package/src/runtime/heapAdapter.ts +16 -5
- package/src/runtime/heapScene/pools.ts +70 -5
- package/src/runtime/heapScene.ts +178 -2
|
@@ -337,6 +337,82 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
337
337
|
slot.scanDirty = true;
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
+
// ─── HeapDrawSpec.instanceCount as an aval ────────────────────────
|
|
341
|
+
// The geometry-morphing / collection-count path (epoch switches,
|
|
342
|
+
// point add/remove): a count tick is ONE drawTable write (record
|
|
343
|
+
// word 4) + emit re-estimate + re-scan — no add/remove churn, no
|
|
344
|
+
// arena activity. Instance ATTRIBUTE payloads ride the sized-repack
|
|
345
|
+
// path independently; per-instance UNIFORM arrays (packed per
|
|
346
|
+
// count) are rejected with an adaptive count at addDraw.
|
|
347
|
+
const drawIdToCountAval = new Map();
|
|
348
|
+
const drawIdToCountCallback = new Map();
|
|
349
|
+
const countDirty = new Set();
|
|
350
|
+
function subscribeCount(av, drawId) {
|
|
351
|
+
drawIdToCountAval.set(drawId, av);
|
|
352
|
+
drawIdToCountCallback.set(drawId, addMarkingCallback(av, () => { countDirty.add(drawId); }));
|
|
353
|
+
}
|
|
354
|
+
function unsubscribeCount(drawId) {
|
|
355
|
+
const cb = drawIdToCountCallback.get(drawId);
|
|
356
|
+
if (cb === undefined)
|
|
357
|
+
return;
|
|
358
|
+
cb.dispose();
|
|
359
|
+
drawIdToCountCallback.delete(drawId);
|
|
360
|
+
drawIdToCountAval.delete(drawId);
|
|
361
|
+
countDirty.delete(drawId);
|
|
362
|
+
}
|
|
363
|
+
/** Update the drawTable record's instanceCount in place — the
|
|
364
|
+
* word-4 sibling of `setEffectiveIndexCount`. */
|
|
365
|
+
function setEffectiveInstanceCount(drawId, newCount) {
|
|
366
|
+
const bucket = drawIdToBucket[drawId];
|
|
367
|
+
if (bucket === undefined)
|
|
368
|
+
return;
|
|
369
|
+
if (bucket.gpuRouted) {
|
|
370
|
+
const partition = bucket.partitionScene;
|
|
371
|
+
const recIdx = bucket.drawIdToRecord.get(drawId);
|
|
372
|
+
if (recIdx === undefined)
|
|
373
|
+
return;
|
|
374
|
+
const ru32 = partition.recordU32;
|
|
375
|
+
const indexCount = partition.masterShadow[recIdx * ru32 + 3];
|
|
376
|
+
const oldCount = partition.masterShadow[recIdx * ru32 + 4];
|
|
377
|
+
partition.masterShadow[recIdx * ru32 + 4] = newCount >>> 0;
|
|
378
|
+
const delta = indexCount * (newCount - oldCount);
|
|
379
|
+
bucket.partitionDirty = true;
|
|
380
|
+
for (const sl of bucket.slots) {
|
|
381
|
+
sl.totalEmitEstimate = Math.max(0, sl.totalEmitEstimate + delta);
|
|
382
|
+
sl.scanDirty = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
const slotIdx = drawIdToSlotIdx[drawId];
|
|
387
|
+
const slot = slotIdx !== undefined ? bucket.slots[slotIdx] : bucket.slots[0];
|
|
388
|
+
if (slot === undefined)
|
|
389
|
+
return;
|
|
390
|
+
const localSlot = drawIdToLocalSlot[drawId];
|
|
391
|
+
if (localSlot === undefined)
|
|
392
|
+
return;
|
|
393
|
+
const recIdx = slot.slotToRecord[localSlot];
|
|
394
|
+
if (recIdx === undefined || recIdx < 0)
|
|
395
|
+
return;
|
|
396
|
+
const shadow = slot.drawTableShadow;
|
|
397
|
+
const indexCount = shadow[recIdx * RECORD_U32 + 3];
|
|
398
|
+
const oldCount = shadow[recIdx * RECORD_U32 + 4];
|
|
399
|
+
shadow[recIdx * RECORD_U32 + 4] = newCount >>> 0;
|
|
400
|
+
const byteOff = recIdx * RECORD_BYTES + 4 * 4;
|
|
401
|
+
if (byteOff < slot.drawTableDirtyMin)
|
|
402
|
+
slot.drawTableDirtyMin = byteOff;
|
|
403
|
+
if (byteOff + 4 > slot.drawTableDirtyMax)
|
|
404
|
+
slot.drawTableDirtyMax = byteOff + 4;
|
|
405
|
+
const delta = indexCount * (newCount - oldCount);
|
|
406
|
+
slot.totalEmitEstimate = Math.max(0, slot.totalEmitEstimate + delta);
|
|
407
|
+
slot.scanDirty = true;
|
|
408
|
+
}
|
|
409
|
+
const localSlot = drawIdToLocalSlot[drawId];
|
|
410
|
+
if (localSlot !== undefined) {
|
|
411
|
+
const e = bucket.localEntries[localSlot];
|
|
412
|
+
if (e !== undefined)
|
|
413
|
+
e.instanceCount = newCount;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
340
416
|
function subscribeModeLeaf(av, drawId) {
|
|
341
417
|
let set = modeAvalToDrawIds.get(av);
|
|
342
418
|
if (set === undefined) {
|
|
@@ -2095,7 +2171,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
2095
2171
|
headerDirtyMin: Infinity, headerDirtyMax: 0,
|
|
2096
2172
|
localPosRefs: [], localNorRefs: [],
|
|
2097
2173
|
localEntries: [], localToDrawId: [],
|
|
2098
|
-
localPerDrawAvals: [], localPerDrawRawFieldIdx: [], localPerDrawRefs: [], localLayoutIds: [],
|
|
2174
|
+
localPerDrawAvals: [], localPerDrawAvalFieldIdx: [], localPerDrawRawFieldIdx: [], localPerDrawRefs: [], localLayoutIds: [],
|
|
2099
2175
|
fieldIdx,
|
|
2100
2176
|
posFieldIdx: fieldIdx.get("Positions") ?? -1,
|
|
2101
2177
|
norFieldIdx: fieldIdx.get("Normals") ?? -1,
|
|
@@ -2355,8 +2431,21 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
2355
2431
|
const hasIndices = spec.indices !== undefined;
|
|
2356
2432
|
const indicesAval = hasIndices ? asAval(spec.indices) : undefined;
|
|
2357
2433
|
const indexArr = hasIndices ? readPlain(spec.indices) : undefined;
|
|
2434
|
+
const icAval = spec.instanceCount !== undefined && typeof spec.instanceCount === "object"
|
|
2435
|
+
&& typeof spec.instanceCount.getValue === "function"
|
|
2436
|
+
? spec.instanceCount
|
|
2437
|
+
: undefined;
|
|
2358
2438
|
const instanceCount = spec.instanceCount !== undefined
|
|
2359
2439
|
? readPlain(spec.instanceCount) : 1;
|
|
2440
|
+
if (icAval !== undefined && perInstanceUniforms.size > 0) {
|
|
2441
|
+
// Per-instance UNIFORM arrays are packed `instanceCount` deep at
|
|
2442
|
+
// addDraw — a changing count would need a repack protocol those
|
|
2443
|
+
// packers don't have. Attribute-backed instancing (BufferViews /
|
|
2444
|
+
// raw arrays) is unaffected: the payload rides the sized-repack
|
|
2445
|
+
// path and the count merely selects a prefix.
|
|
2446
|
+
throw new Error("heapScene: adaptive instanceCount is not supported together with per-instance UNIFORM values " +
|
|
2447
|
+
`(fields: ${[...perInstanceUniforms].join(", ")}) — use instance attributes instead`);
|
|
2448
|
+
}
|
|
2360
2449
|
let estBytes = indexArr !== undefined ? ALIGN16(ALLOC_HEADER_PAD_TO + indexArr.byteLength) : 0;
|
|
2361
2450
|
for (const f of layout.drawHeaderFields) {
|
|
2362
2451
|
if (f.kind === "texture-ref" || f.name === "__layoutId" || INLINE_HEADER_UNIFORMS.has(f.name) || !effectFields.has(f.name))
|
|
@@ -2480,6 +2569,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
2480
2569
|
// packs as a single value. Both go through the same pool — sharing
|
|
2481
2570
|
// emerges from aval identity either way.
|
|
2482
2571
|
const perDrawAvals = [];
|
|
2572
|
+
const perDrawAvalFieldIdx = [];
|
|
2483
2573
|
let perDrawRawFieldIdx;
|
|
2484
2574
|
const perDrawRefs = new Int32Array(bucket.layout.drawHeaderFields.length).fill(-1);
|
|
2485
2575
|
const setRef = (name, ref) => {
|
|
@@ -2571,9 +2661,13 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
2571
2661
|
let value;
|
|
2572
2662
|
let placement;
|
|
2573
2663
|
let releasable;
|
|
2664
|
+
let replanFn;
|
|
2574
2665
|
if (f.kind === "attribute-ref" && !isPerInstanceUniformField && isBufferView(provided)) {
|
|
2575
2666
|
const bv = provided;
|
|
2576
2667
|
placement = bufferViewPlacement(f, bv);
|
|
2668
|
+
// Size-variable payload: geometry morphing re-derives the
|
|
2669
|
+
// placement from the view's CURRENT buffer value.
|
|
2670
|
+
replanFn = () => bufferViewPlacement(f, bv);
|
|
2577
2671
|
av = bv.buffer;
|
|
2578
2672
|
value = bv.buffer.getValue(tok);
|
|
2579
2673
|
}
|
|
@@ -2607,12 +2701,16 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
2607
2701
|
placement = isPerInstanceUniformField
|
|
2608
2702
|
? perInstancePlacementFor(f, value, instanceCount)
|
|
2609
2703
|
: poolPlacementFor(f, value);
|
|
2704
|
+
if (f.kind === "attribute-ref" && !isPerInstanceUniformField) {
|
|
2705
|
+
replanFn = (v) => poolPlacementFor(f, v);
|
|
2706
|
+
}
|
|
2610
2707
|
}
|
|
2611
|
-
const ref = pool.acquire(device, arena.attrs, av, bucket.chunkIdx, value, placement.dataBytes, placement.typeId, placement.length, placement.pack);
|
|
2708
|
+
const ref = pool.acquire(device, arena.attrs, av, bucket.chunkIdx, value, placement.dataBytes, placement.typeId, placement.length, placement.pack, replanFn);
|
|
2612
2709
|
if (releasable !== undefined)
|
|
2613
2710
|
releasable._v = null; // staged — drop the CPU copy
|
|
2614
2711
|
setRef(f.name, ref);
|
|
2615
2712
|
perDrawAvals.push(av);
|
|
2713
|
+
perDrawAvalFieldIdx.push(bucket.fieldIdx.get(f.name));
|
|
2616
2714
|
}
|
|
2617
2715
|
}
|
|
2618
2716
|
bucket.localPosRefs[localSlot] = getRef("Positions");
|
|
@@ -2623,6 +2721,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
2623
2721
|
bucket.localToDrawId[localSlot] = drawId;
|
|
2624
2722
|
bucket.drawSlots.add(localSlot);
|
|
2625
2723
|
bucket.localPerDrawAvals[localSlot] = perDrawAvals;
|
|
2724
|
+
bucket.localPerDrawAvalFieldIdx[localSlot] = perDrawAvalFieldIdx;
|
|
2626
2725
|
bucket.localPerDrawRawFieldIdx[localSlot] = perDrawRawFieldIdx;
|
|
2627
2726
|
bucket.localPerDrawRefs[localSlot] = perDrawRefs;
|
|
2628
2727
|
const layoutId = fam.schema.layoutIdOf.get(spec.effect);
|
|
@@ -2815,6 +2914,9 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
2815
2914
|
setEffectiveIndexCount(drawId, 0);
|
|
2816
2915
|
}
|
|
2817
2916
|
}
|
|
2917
|
+
if (icAval !== undefined) {
|
|
2918
|
+
subscribeCount(icAval, drawId);
|
|
2919
|
+
}
|
|
2818
2920
|
// ─── Reactive rebucket: track PS-modeKey changes ────────────────
|
|
2819
2921
|
// When any mode-axis aval marks (e.g. cullCval.value = 'front'),
|
|
2820
2922
|
// schedule this RO for rebucket on the next update(). Today's
|
|
@@ -2931,6 +3033,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
2931
3033
|
// Tear down `active` subscription (if any) so its callback won't
|
|
2932
3034
|
// fire after the underlying drawTable record is gone.
|
|
2933
3035
|
unsubscribeActive(drawId);
|
|
3036
|
+
unsubscribeCount(drawId);
|
|
2934
3037
|
// §7: deregister this RO's derivation records and release slots.
|
|
2935
3038
|
if (derivedScene !== undefined) {
|
|
2936
3039
|
const reg = derivedByDrawId.get(drawId);
|
|
@@ -3072,6 +3175,7 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
3072
3175
|
bucket.localAtlasTextures[localSlot] = undefined;
|
|
3073
3176
|
bucket.localAtlasArrIdx[localSlot] = undefined;
|
|
3074
3177
|
bucket.localPerDrawAvals[localSlot] = undefined;
|
|
3178
|
+
bucket.localPerDrawAvalFieldIdx[localSlot] = undefined;
|
|
3075
3179
|
bucket.localPerDrawRefs[localSlot] = undefined;
|
|
3076
3180
|
bucket.localLayoutIds[localSlot] = undefined;
|
|
3077
3181
|
bucket.localPosRefs[localSlot] = undefined;
|
|
@@ -3471,11 +3575,74 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
3471
3575
|
// One writeBuffer per dirty aval, regardless of how many draws
|
|
3472
3576
|
// reference it — sharing pays off here.
|
|
3473
3577
|
if (allocDirty.size > 0) {
|
|
3578
|
+
// aval → (chunkIdx → newRef) for allocations the sized repack
|
|
3579
|
+
// moved (payload grew/shrank → realloc'd within the chunk).
|
|
3580
|
+
let movedByAval;
|
|
3474
3581
|
for (const av of allocDirty) {
|
|
3475
|
-
pool.repack(device, arena.attrs, av, av.getValue(tok));
|
|
3582
|
+
const moved = pool.repack(device, arena.attrs, av, av.getValue(tok));
|
|
3476
3583
|
totalDirtyBytes += pool.totalDataBytes(av);
|
|
3584
|
+
if (moved !== undefined) {
|
|
3585
|
+
for (const m of moved) {
|
|
3586
|
+
let byChunk = (movedByAval ??= new Map()).get(m.av);
|
|
3587
|
+
if (byChunk === undefined) {
|
|
3588
|
+
byChunk = new Map();
|
|
3589
|
+
movedByAval.set(m.av, byChunk);
|
|
3590
|
+
}
|
|
3591
|
+
byChunk.set(m.chunkIdx, m.newRef);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3477
3594
|
}
|
|
3478
3595
|
allocDirty.clear();
|
|
3596
|
+
if (movedByAval !== undefined) {
|
|
3597
|
+
// Re-seat every drawHeader field whose aval's allocation
|
|
3598
|
+
// moved. Matched by AVAL IDENTITY via the parallel
|
|
3599
|
+
// `localPerDrawAvalFieldIdx` — never by numeric offset (a
|
|
3600
|
+
// released offset can be re-issued to a DIFFERENT aval in
|
|
3601
|
+
// this same drain). Mirrors the compaction remap loop.
|
|
3602
|
+
for (const bucket of buckets) {
|
|
3603
|
+
for (const localSlot of bucket.drawSlots) {
|
|
3604
|
+
const avals = bucket.localPerDrawAvals[localSlot];
|
|
3605
|
+
const fidx = bucket.localPerDrawAvalFieldIdx[localSlot];
|
|
3606
|
+
const refs = bucket.localPerDrawRefs[localSlot];
|
|
3607
|
+
if (avals === undefined || fidx === undefined || refs === undefined)
|
|
3608
|
+
continue;
|
|
3609
|
+
let changed = false;
|
|
3610
|
+
for (let i = 0; i < avals.length; i++) {
|
|
3611
|
+
const byChunk = movedByAval.get(avals[i]);
|
|
3612
|
+
if (byChunk === undefined)
|
|
3613
|
+
continue;
|
|
3614
|
+
const nn = byChunk.get(bucket.chunkIdx);
|
|
3615
|
+
if (nn === undefined)
|
|
3616
|
+
continue;
|
|
3617
|
+
if (bucket.gpuRouted) {
|
|
3618
|
+
throw new Error("heapScene: payload realloc under a GPU-routed (derived-modes) bucket is not supported yet");
|
|
3619
|
+
}
|
|
3620
|
+
if (refs[fidx[i]] !== nn) {
|
|
3621
|
+
refs[fidx[i]] = nn;
|
|
3622
|
+
changed = true;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
if (!changed)
|
|
3626
|
+
continue;
|
|
3627
|
+
const pr = bucket.posFieldIdx >= 0 ? refs[bucket.posFieldIdx] : -1;
|
|
3628
|
+
const nr = bucket.norFieldIdx >= 0 ? refs[bucket.norFieldIdx] : -1;
|
|
3629
|
+
bucket.localPosRefs[localSlot] = pr < 0 ? undefined : pr;
|
|
3630
|
+
bucket.localNorRefs[localSlot] = nr < 0 ? undefined : nr;
|
|
3631
|
+
packBucketHeader(bucket, localSlot, refs, bucket.localLayoutIds[localSlot] ?? 0);
|
|
3632
|
+
if (bucket.isAtlasBucket) {
|
|
3633
|
+
const ts = bucket.localAtlasTextures[localSlot];
|
|
3634
|
+
if (ts !== undefined)
|
|
3635
|
+
packAtlasTextureFields(bucket, localSlot, ts);
|
|
3636
|
+
}
|
|
3637
|
+
const byteOff = localSlot * bucket.layout.drawHeaderBytes;
|
|
3638
|
+
if (byteOff < bucket.headerDirtyMin)
|
|
3639
|
+
bucket.headerDirtyMin = byteOff;
|
|
3640
|
+
const end = byteOff + bucket.layout.drawHeaderBytes;
|
|
3641
|
+
if (end > bucket.headerDirtyMax)
|
|
3642
|
+
bucket.headerDirtyMax = end;
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3479
3646
|
}
|
|
3480
3647
|
// 1c. HeapDrawSpec.active drain — flip drawTable.indexCount
|
|
3481
3648
|
// between origIndexCount (visible) and 0 (hidden) for any RO
|
|
@@ -3493,6 +3660,18 @@ export function buildHeapScene(device, sig, initialDraws, opts = {}) {
|
|
|
3493
3660
|
}
|
|
3494
3661
|
activeDirty.clear();
|
|
3495
3662
|
}
|
|
3663
|
+
// 1d. HeapDrawSpec.instanceCount aval drain — write the new
|
|
3664
|
+
// count into drawTable word 4 (emit = indexCount × count;
|
|
3665
|
+
// 0 draws nothing). One record write + re-scan per tick.
|
|
3666
|
+
if (countDirty.size > 0) {
|
|
3667
|
+
for (const did of countDirty) {
|
|
3668
|
+
const av = drawIdToCountAval.get(did);
|
|
3669
|
+
if (av === undefined)
|
|
3670
|
+
continue;
|
|
3671
|
+
setEffectiveInstanceCount(did, Math.max(0, av.getValue(tok) | 0));
|
|
3672
|
+
}
|
|
3673
|
+
countDirty.clear();
|
|
3674
|
+
}
|
|
3496
3675
|
// 1b. Atlas-texture aval reactivity: an `aval<ITexture>` that
|
|
3497
3676
|
// drives an atlas placement was marked. Repack the pool entry
|
|
3498
3677
|
// and rewrite the drawHeader fields of every (bucket, slot)
|