@aardworx/wombat.rendering 0.9.13 → 0.9.15
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/derivedModes/modeKeyCpu.d.ts +45 -0
- package/dist/runtime/derivedModes/modeKeyCpu.d.ts.map +1 -0
- package/dist/runtime/derivedModes/modeKeyCpu.js +179 -0
- package/dist/runtime/derivedModes/modeKeyCpu.js.map +1 -0
- package/dist/runtime/derivedModes/partition.d.ts +36 -0
- package/dist/runtime/derivedModes/partition.d.ts.map +1 -0
- package/dist/runtime/derivedModes/partition.js +170 -0
- package/dist/runtime/derivedModes/partition.js.map +1 -0
- package/dist/runtime/derivedModes/slotTable.d.ts +70 -0
- package/dist/runtime/derivedModes/slotTable.d.ts.map +1 -0
- package/dist/runtime/derivedModes/slotTable.js +130 -0
- package/dist/runtime/derivedModes/slotTable.js.map +1 -0
- package/dist/runtime/derivedUniforms/marker.d.ts +11 -3
- package/dist/runtime/derivedUniforms/marker.d.ts.map +1 -1
- package/dist/runtime/derivedUniforms/marker.js +10 -5
- package/dist/runtime/derivedUniforms/marker.js.map +1 -1
- package/dist/runtime/heapScene/growBuffer.d.ts +33 -0
- package/dist/runtime/heapScene/growBuffer.d.ts.map +1 -0
- package/dist/runtime/heapScene/growBuffer.js +76 -0
- package/dist/runtime/heapScene/growBuffer.js.map +1 -0
- package/dist/runtime/heapScene/packers.d.ts +25 -0
- package/dist/runtime/heapScene/packers.d.ts.map +1 -0
- package/dist/runtime/heapScene/packers.js +111 -0
- package/dist/runtime/heapScene/packers.js.map +1 -0
- package/dist/runtime/heapScene/pools.d.ts +181 -0
- package/dist/runtime/heapScene/pools.d.ts.map +1 -0
- package/dist/runtime/heapScene/pools.js +417 -0
- package/dist/runtime/heapScene/pools.js.map +1 -0
- package/dist/runtime/heapScene/scanKernel.d.ts +11 -0
- package/dist/runtime/heapScene/scanKernel.d.ts.map +1 -0
- package/dist/runtime/heapScene/scanKernel.js +181 -0
- package/dist/runtime/heapScene/scanKernel.js.map +1 -0
- package/dist/runtime/heapScene.d.ts.map +1 -1
- package/dist/runtime/heapScene.js +228 -937
- package/dist/runtime/heapScene.js.map +1 -1
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/pipelineCache/bitfield.d.ts +5 -0
- package/dist/runtime/pipelineCache/bitfield.d.ts.map +1 -0
- package/dist/runtime/pipelineCache/bitfield.js +201 -0
- package/dist/runtime/pipelineCache/bitfield.js.map +1 -0
- package/dist/runtime/pipelineCache/cache.d.ts +36 -0
- package/dist/runtime/pipelineCache/cache.d.ts.map +1 -0
- package/dist/runtime/pipelineCache/cache.js +108 -0
- package/dist/runtime/pipelineCache/cache.js.map +1 -0
- package/dist/runtime/pipelineCache/descriptor.d.ts +58 -0
- package/dist/runtime/pipelineCache/descriptor.d.ts.map +1 -0
- package/dist/runtime/pipelineCache/descriptor.js +100 -0
- package/dist/runtime/pipelineCache/descriptor.js.map +1 -0
- package/dist/runtime/pipelineCache/index.d.ts +5 -0
- package/dist/runtime/pipelineCache/index.d.ts.map +1 -0
- package/dist/runtime/pipelineCache/index.js +12 -0
- package/dist/runtime/pipelineCache/index.js.map +1 -0
- package/package.json +1 -1
- package/src/runtime/derivedModes/modeKeyCpu.ts +213 -0
- package/src/runtime/derivedModes/partition.ts +206 -0
- package/src/runtime/derivedModes/slotTable.ts +153 -0
- package/src/runtime/derivedUniforms/marker.ts +19 -6
- package/src/runtime/heapScene/growBuffer.ts +75 -0
- package/src/runtime/heapScene/packers.ts +127 -0
- package/src/runtime/heapScene/pools.ts +492 -0
- package/src/runtime/heapScene/scanKernel.ts +184 -0
- package/src/runtime/heapScene.ts +283 -1064
- package/src/runtime/index.ts +40 -0
- package/src/runtime/pipelineCache/bitfield.ts +225 -0
- package/src/runtime/pipelineCache/cache.ts +129 -0
- package/src/runtime/pipelineCache/descriptor.ts +150 -0
- package/src/runtime/pipelineCache/index.ts +37 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PipelineState } from "../../core/pipelineState.js";
|
|
2
|
+
import type { FramebufferSignature } from "../../core/framebufferSignature.js";
|
|
3
|
+
import type { IDisposable } from "@aardworx/wombat.adaptive";
|
|
4
|
+
import { type PipelineStateDescriptor } from "../pipelineCache/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* One-shot snapshot of the current values of every leaf aval in a
|
|
7
|
+
* `PipelineState`, mapped onto a `PipelineStateDescriptor` whose
|
|
8
|
+
* `attachments` array follows the framebuffer's `colorNames` order
|
|
9
|
+
* (missing entries default to non-blended / 0xF writeMask, matching
|
|
10
|
+
* `BLEND_DEFAULT` semantics in heapScene).
|
|
11
|
+
*/
|
|
12
|
+
export declare function snapshotDescriptor(ps: PipelineState | undefined, signature: FramebufferSignature): PipelineStateDescriptor;
|
|
13
|
+
/**
|
|
14
|
+
* Subscribes to every leaf aval in a `PipelineState` and invokes
|
|
15
|
+
* `onDirty` when any of them marks. Use one tracker per RO; call
|
|
16
|
+
* `recompute()` after a dirty signal to refresh the descriptor +
|
|
17
|
+
* modeKey, and `dispose()` when the RO is removed.
|
|
18
|
+
*
|
|
19
|
+
* Reactive cvals are the typical case; constant avals also "subscribe"
|
|
20
|
+
* — `addMarkingCallback` is a no-op for an aval that never marks, so
|
|
21
|
+
* no special-casing required.
|
|
22
|
+
*/
|
|
23
|
+
export declare class ModeKeyTracker implements IDisposable {
|
|
24
|
+
readonly ps: PipelineState | undefined;
|
|
25
|
+
readonly signature: FramebufferSignature;
|
|
26
|
+
private readonly onDirty;
|
|
27
|
+
private readonly subs;
|
|
28
|
+
private cachedDescriptor;
|
|
29
|
+
private cachedModeKey;
|
|
30
|
+
constructor(ps: PipelineState | undefined, signature: FramebufferSignature, onDirty: () => void);
|
|
31
|
+
get descriptor(): PipelineStateDescriptor;
|
|
32
|
+
get modeKey(): bigint;
|
|
33
|
+
/**
|
|
34
|
+
* Snapshot current aval values and rebuild descriptor + modeKey.
|
|
35
|
+
* Returns true iff the modeKey changed (so the bucket only needs to
|
|
36
|
+
* upload + repartition when something actually moved).
|
|
37
|
+
*/
|
|
38
|
+
recompute(): boolean;
|
|
39
|
+
dispose(): void;
|
|
40
|
+
private subscribeAll;
|
|
41
|
+
private sub;
|
|
42
|
+
private subBlendState;
|
|
43
|
+
private subStencilFaces;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=modeKeyCpu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modeKeyCpu.d.ts","sourceRoot":"","sources":["../../../src/runtime/derivedModes/modeKeyCpu.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAEV,aAAa,EAKd,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,KAAK,EAAQ,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAGnE,OAAO,EAIL,KAAK,uBAAuB,EAI7B,MAAM,2BAA2B,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,aAAa,GAAG,SAAS,EAC7B,SAAS,EAAE,oBAAoB,GAC9B,uBAAuB,CAsBzB;AAkDD;;;;;;;;;GASG;AACH,qBAAa,cAAe,YAAW,WAAW;IAChD,QAAQ,CAAC,EAAE,EAAE,aAAa,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;IACrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,aAAa,CAAS;gBAG5B,EAAE,EAAE,aAAa,GAAG,SAAS,EAC7B,SAAS,EAAE,oBAAoB,EAC/B,OAAO,EAAE,MAAM,IAAI;IAUrB,IAAI,UAAU,IAAI,uBAAuB,CAAkC;IAC3E,IAAI,OAAO,IAAI,MAAM,CAA+B;IAEpD;;;;OAIG;IACH,SAAS,IAAI,OAAO;IASpB,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,YAAY;IA6BpB,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,eAAe;CAIxB"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Per-RO modeKey production (CPU side).
|
|
2
|
+
//
|
|
3
|
+
// Task 1 of the derived-modes work: turn a `PipelineState` (the
|
|
4
|
+
// aval-shaped struct from core/pipelineState.ts) plus the surrounding
|
|
5
|
+
// `FramebufferSignature` into a canonical `PipelineStateDescriptor`
|
|
6
|
+
// and its bitfield-encoded modeKey. Subscribes to every input aval
|
|
7
|
+
// so that mutating a `cval` for cullMode / blendFactor / etc. fires
|
|
8
|
+
// a dirty callback, the bucket flushes the changed RO's modeKey to
|
|
9
|
+
// GPU on the next frame, and the partition kernel re-buckets the
|
|
10
|
+
// record into its new slot. No rule-IR machinery yet — Task 2 layers
|
|
11
|
+
// `derivedMode(...)` on top of this.
|
|
12
|
+
import { addMarkingCallback } from "@aardworx/wombat.adaptive";
|
|
13
|
+
import { encodeModeKey, DEFAULT_DESCRIPTOR, DEFAULT_ATTACHMENT_BLEND, } from "../pipelineCache/index.js";
|
|
14
|
+
/**
|
|
15
|
+
* One-shot snapshot of the current values of every leaf aval in a
|
|
16
|
+
* `PipelineState`, mapped onto a `PipelineStateDescriptor` whose
|
|
17
|
+
* `attachments` array follows the framebuffer's `colorNames` order
|
|
18
|
+
* (missing entries default to non-blended / 0xF writeMask, matching
|
|
19
|
+
* `BLEND_DEFAULT` semantics in heapScene).
|
|
20
|
+
*/
|
|
21
|
+
export function snapshotDescriptor(ps, signature) {
|
|
22
|
+
if (ps === undefined) {
|
|
23
|
+
return buildAttachmentsFor(DEFAULT_DESCRIPTOR, signature);
|
|
24
|
+
}
|
|
25
|
+
const r = ps.rasterizer;
|
|
26
|
+
const depth = ps.depth !== undefined ? snapshotDepth(ps.depth, signature) : undefined;
|
|
27
|
+
const atc = ps.alphaToCoverage !== undefined ? ps.alphaToCoverage.force( /* allow-force */) : false;
|
|
28
|
+
const blends = ps.blends !== undefined ? ps.blends.force( /* allow-force */) : undefined;
|
|
29
|
+
const attachments = signature.colorNames.map((name) => {
|
|
30
|
+
const b = blends?.tryFind(name);
|
|
31
|
+
return b !== undefined ? snapshotAttachment(b) : DEFAULT_ATTACHMENT_BLEND;
|
|
32
|
+
});
|
|
33
|
+
const out = {
|
|
34
|
+
topology: r.topology.force( /* allow-force */),
|
|
35
|
+
stripIndexFormat: stripFormatFor(r.topology.force( /* allow-force */)),
|
|
36
|
+
frontFace: r.frontFace.force( /* allow-force */),
|
|
37
|
+
cullMode: r.cullMode.force( /* allow-force */),
|
|
38
|
+
...(depth !== undefined ? { depth } : {}),
|
|
39
|
+
attachments,
|
|
40
|
+
alphaToCoverage: atc,
|
|
41
|
+
};
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function buildAttachmentsFor(base, signature) {
|
|
45
|
+
// When `ps` is undefined, every named attachment uses defaults.
|
|
46
|
+
return {
|
|
47
|
+
...base,
|
|
48
|
+
attachments: signature.colorNames.map(() => DEFAULT_ATTACHMENT_BLEND),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function snapshotDepth(d, signature) {
|
|
52
|
+
if (signature.depthStencil === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
return {
|
|
55
|
+
write: d.write.force( /* allow-force */),
|
|
56
|
+
compare: d.compare.force( /* allow-force */),
|
|
57
|
+
clamp: d.clamp !== undefined ? d.clamp.force( /* allow-force */) : false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function snapshotAttachment(b) {
|
|
61
|
+
const color = snapshotBlendComponent(b.color);
|
|
62
|
+
const alpha = snapshotBlendComponent(b.alpha);
|
|
63
|
+
const writeMask = b.writeMask.force( /* allow-force */) & 0xF;
|
|
64
|
+
// "Blend enabled" derives from whether the components differ from
|
|
65
|
+
// the no-op (src=one, dst=zero, op=add) shape. This mirrors WebGPU's
|
|
66
|
+
// contract: a blend descriptor MUST be present for blending to occur,
|
|
67
|
+
// and the no-op shape is the canonical disabled state.
|
|
68
|
+
const enabled = !(color.srcFactor === "one" && color.dstFactor === "zero" && color.operation === "add" &&
|
|
69
|
+
alpha.srcFactor === "one" && alpha.dstFactor === "zero" && alpha.operation === "add");
|
|
70
|
+
return { enabled, color, alpha, writeMask };
|
|
71
|
+
}
|
|
72
|
+
function snapshotBlendComponent(c) {
|
|
73
|
+
return {
|
|
74
|
+
srcFactor: c.srcFactor.force( /* allow-force */),
|
|
75
|
+
dstFactor: c.dstFactor.force( /* allow-force */),
|
|
76
|
+
operation: c.operation.force( /* allow-force */),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function stripFormatFor(topology) {
|
|
80
|
+
return topology === "line-strip" || topology === "triangle-strip" ? "uint32" : undefined;
|
|
81
|
+
}
|
|
82
|
+
// ─── Reactive tracker ──────────────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Subscribes to every leaf aval in a `PipelineState` and invokes
|
|
85
|
+
* `onDirty` when any of them marks. Use one tracker per RO; call
|
|
86
|
+
* `recompute()` after a dirty signal to refresh the descriptor +
|
|
87
|
+
* modeKey, and `dispose()` when the RO is removed.
|
|
88
|
+
*
|
|
89
|
+
* Reactive cvals are the typical case; constant avals also "subscribe"
|
|
90
|
+
* — `addMarkingCallback` is a no-op for an aval that never marks, so
|
|
91
|
+
* no special-casing required.
|
|
92
|
+
*/
|
|
93
|
+
export class ModeKeyTracker {
|
|
94
|
+
ps;
|
|
95
|
+
signature;
|
|
96
|
+
onDirty;
|
|
97
|
+
subs = [];
|
|
98
|
+
cachedDescriptor;
|
|
99
|
+
cachedModeKey;
|
|
100
|
+
constructor(ps, signature, onDirty) {
|
|
101
|
+
this.ps = ps;
|
|
102
|
+
this.signature = signature;
|
|
103
|
+
this.onDirty = onDirty;
|
|
104
|
+
this.cachedDescriptor = snapshotDescriptor(ps, signature);
|
|
105
|
+
this.cachedModeKey = encodeModeKey(this.cachedDescriptor);
|
|
106
|
+
this.subscribeAll();
|
|
107
|
+
}
|
|
108
|
+
get descriptor() { return this.cachedDescriptor; }
|
|
109
|
+
get modeKey() { return this.cachedModeKey; }
|
|
110
|
+
/**
|
|
111
|
+
* Snapshot current aval values and rebuild descriptor + modeKey.
|
|
112
|
+
* Returns true iff the modeKey changed (so the bucket only needs to
|
|
113
|
+
* upload + repartition when something actually moved).
|
|
114
|
+
*/
|
|
115
|
+
recompute() {
|
|
116
|
+
const next = snapshotDescriptor(this.ps, this.signature);
|
|
117
|
+
const nextKey = encodeModeKey(next);
|
|
118
|
+
if (nextKey === this.cachedModeKey)
|
|
119
|
+
return false;
|
|
120
|
+
this.cachedDescriptor = next;
|
|
121
|
+
this.cachedModeKey = nextKey;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
dispose() {
|
|
125
|
+
for (const s of this.subs)
|
|
126
|
+
s.dispose();
|
|
127
|
+
this.subs.length = 0;
|
|
128
|
+
}
|
|
129
|
+
subscribeAll() {
|
|
130
|
+
if (this.ps === undefined)
|
|
131
|
+
return;
|
|
132
|
+
const ps = this.ps;
|
|
133
|
+
this.sub(ps.rasterizer.topology);
|
|
134
|
+
this.sub(ps.rasterizer.cullMode);
|
|
135
|
+
this.sub(ps.rasterizer.frontFace);
|
|
136
|
+
if (ps.rasterizer.depthBias !== undefined)
|
|
137
|
+
this.sub(ps.rasterizer.depthBias);
|
|
138
|
+
if (ps.depth !== undefined) {
|
|
139
|
+
this.sub(ps.depth.write);
|
|
140
|
+
this.sub(ps.depth.compare);
|
|
141
|
+
if (ps.depth.clamp !== undefined)
|
|
142
|
+
this.sub(ps.depth.clamp);
|
|
143
|
+
}
|
|
144
|
+
if (ps.stencil !== undefined) {
|
|
145
|
+
// Stencil enabled/reference/masks contribute to neither modeKey
|
|
146
|
+
// nor cache key (v1 carves stencil out — only the "enabled"
|
|
147
|
+
// flag matters and we surface it via snapshotDescriptor). We
|
|
148
|
+
// still subscribe so callers can repackage if a future change
|
|
149
|
+
// flips the enable bit.
|
|
150
|
+
this.subStencilFaces(ps.stencil);
|
|
151
|
+
}
|
|
152
|
+
if (ps.blends !== undefined) {
|
|
153
|
+
this.sub(ps.blends);
|
|
154
|
+
const map = ps.blends.force( /* allow-force */);
|
|
155
|
+
for (const [, bs] of map)
|
|
156
|
+
this.subBlendState(bs);
|
|
157
|
+
}
|
|
158
|
+
if (ps.alphaToCoverage !== undefined)
|
|
159
|
+
this.sub(ps.alphaToCoverage);
|
|
160
|
+
// blendConstant is dynamic state; not in the modeKey, no subscription needed.
|
|
161
|
+
}
|
|
162
|
+
sub(a) {
|
|
163
|
+
this.subs.push(addMarkingCallback(a, this.onDirty));
|
|
164
|
+
}
|
|
165
|
+
subBlendState(b) {
|
|
166
|
+
this.sub(b.color.srcFactor);
|
|
167
|
+
this.sub(b.color.dstFactor);
|
|
168
|
+
this.sub(b.color.operation);
|
|
169
|
+
this.sub(b.alpha.srcFactor);
|
|
170
|
+
this.sub(b.alpha.dstFactor);
|
|
171
|
+
this.sub(b.alpha.operation);
|
|
172
|
+
this.sub(b.writeMask);
|
|
173
|
+
}
|
|
174
|
+
subStencilFaces(_s) {
|
|
175
|
+
// v1: stencil isn't in the modeKey; deliberately not subscribing
|
|
176
|
+
// to face avals here. Wire up in v2 when stencil enters the key.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=modeKeyCpu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modeKeyCpu.js","sourceRoot":"","sources":["../../../src/runtime/derivedModes/modeKeyCpu.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** v1 cap on distinct pipeline slots per bucket. */
|
|
2
|
+
export declare const MAX_SLOTS_PER_BUCKET = 256;
|
|
3
|
+
export interface PartitionInput {
|
|
4
|
+
/** Per-record slot index (length = numRecords). */
|
|
5
|
+
readonly slot: ReadonlyArray<number>;
|
|
6
|
+
/** Per-record (indexCount * instanceCount) — emit count contributed
|
|
7
|
+
* to its slot's drawIndirect vertex total. */
|
|
8
|
+
readonly emitCount: ReadonlyArray<number>;
|
|
9
|
+
/** Number of distinct slots used. Slots outside this range produce
|
|
10
|
+
* an error. */
|
|
11
|
+
readonly slotCount: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PartitionResult {
|
|
14
|
+
/** Per-slot record count. Length = slotCount. */
|
|
15
|
+
readonly slotCounts: Uint32Array;
|
|
16
|
+
/** Per-slot cumulative record-count prefix sum (length = slotCount + 1). */
|
|
17
|
+
readonly slotOffsets: Uint32Array;
|
|
18
|
+
/** Per-slot total emit count (vertex count for the drawIndirect). */
|
|
19
|
+
readonly slotTotalEmit: Uint32Array;
|
|
20
|
+
/** Permuted record indices, grouped by slot. Length = numRecords. */
|
|
21
|
+
readonly slotRecords: Uint32Array;
|
|
22
|
+
/** Slot-local prefix sum of emitCounts, length = numRecords + slotCount.
|
|
23
|
+
* For slot s, the prefix array is
|
|
24
|
+
* slotEmitPrefix[slotOffsets[s] + s ..= slotOffsets[s+1] + s]
|
|
25
|
+
* (one extra entry per slot for the upper bound). */
|
|
26
|
+
readonly slotEmitPrefix: Uint32Array;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Pure-CPU partition. Reference implementation for the WGSL kernel
|
|
30
|
+
* below. Order within a slot is stable wrt. input record order so
|
|
31
|
+
* tests can predict the permuted layout deterministically.
|
|
32
|
+
*/
|
|
33
|
+
export declare function partitionCPU(input: PartitionInput): PartitionResult;
|
|
34
|
+
export declare const PARTITION_HISTOGRAM_WGSL = "\nstruct PartitionInputs {\n numRecords: u32,\n slotCount: u32,\n _pad0: u32,\n _pad1: u32,\n};\n@group(0) @binding(0) var<uniform> uIn: PartitionInputs;\n@group(0) @binding(1) var<storage, read> slot: array<u32>;\n@group(0) @binding(2) var<storage, read> emitCount: array<u32>;\n@group(0) @binding(3) var<storage, read_write> slotCounts: array<atomic<u32>>;\n@group(0) @binding(4) var<storage, read_write> slotTotalEmit: array<atomic<u32>>;\n\n@compute @workgroup_size(64)\nfn histogram(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x;\n if (i >= uIn.numRecords) { return; }\n let s = slot[i];\n atomicAdd(&slotCounts[s], 1u);\n atomicAdd(&slotTotalEmit[s], emitCount[i]);\n}\n";
|
|
35
|
+
export declare const PARTITION_SCATTER_WGSL = "\nstruct PartitionInputs {\n numRecords: u32,\n slotCount: u32,\n _pad0: u32,\n _pad1: u32,\n};\n@group(0) @binding(0) var<uniform> uIn: PartitionInputs;\n@group(0) @binding(1) var<storage, read> slot: array<u32>;\n@group(0) @binding(2) var<storage, read> slotOffsets: array<u32>;\n@group(0) @binding(3) var<storage, read_write> cursors: array<atomic<u32>>;\n@group(0) @binding(4) var<storage, read_write> slotRecords: array<u32>;\n\n@compute @workgroup_size(64)\nfn scatter(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x;\n if (i >= uIn.numRecords) { return; }\n let s = slot[i];\n let base = slotOffsets[s];\n let k = atomicAdd(&cursors[s], 1u);\n slotRecords[base + k] = i;\n}\n";
|
|
36
|
+
//# sourceMappingURL=partition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition.d.ts","sourceRoot":"","sources":["../../../src/runtime/derivedModes/partition.ts"],"names":[],"mappings":"AAgCA,oDAAoD;AACpD,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAW,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;kDAC8C;IAC9C,QAAQ,CAAC,SAAS,EAAM,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;mBACe;IACf,QAAQ,CAAC,SAAS,EAAM,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,UAAU,EAAM,WAAW,CAAC;IACrC,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAK,WAAW,CAAC;IACrC,qEAAqE;IACrE,QAAQ,CAAC,aAAa,EAAG,WAAW,CAAC;IACrC,qEAAqE;IACrE,QAAQ,CAAC,WAAW,EAAK,WAAW,CAAC;IACrC;;;yDAGqD;IACrD,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAqDnE;AAwCD,eAAO,MAAM,wBAAwB,uwBAqBpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,2wBAsBlC,CAAC"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Per-frame partition kernel — turns per-RO slot assignments into
|
|
2
|
+
// per-slot draw metadata (record lists + cumulative emit prefix sums
|
|
3
|
+
// + total indexCount × instanceCount per slot).
|
|
4
|
+
//
|
|
5
|
+
// Each bucket runs this kernel once per frame (dirty-gated). Output
|
|
6
|
+
// feeds one `drawIndirect` per slot in the encode loop. Records routed
|
|
7
|
+
// to the same slot share an indirect call; records in different slots
|
|
8
|
+
// land in separate `setPipeline + drawIndirect` calls.
|
|
9
|
+
//
|
|
10
|
+
// The CPU function `partitionCPU` is the algorithmic reference and
|
|
11
|
+
// the basis for unit tests. The WGSL kernel below mirrors the same
|
|
12
|
+
// algorithm using two compute passes (histogram + scatter), with an
|
|
13
|
+
// exclusive prefix scan on the histogram in between.
|
|
14
|
+
//
|
|
15
|
+
// Layout choices:
|
|
16
|
+
//
|
|
17
|
+
// - Records are NOT physically re-sorted. Instead, the kernel writes
|
|
18
|
+
// a permuted index list `slotRecords[]` (one u32 per record), plus
|
|
19
|
+
// per-slot offsets `slotOffsets[]` and counts `slotCounts[]`. The
|
|
20
|
+
// existing drawTable stays put — the VS prelude does one extra
|
|
21
|
+
// read of `slotRecords[…]` to map from "drawIndex within slot"
|
|
22
|
+
// back to "drawIndex in the full drawTable."
|
|
23
|
+
// - `slotEmitPrefix[]` is the slot-local prefix-sum of
|
|
24
|
+
// `record.indexCount * record.instanceCount`. The VS prelude
|
|
25
|
+
// binary-searches THIS (per-slot) instead of the global table.
|
|
26
|
+
// - `slotTotalEmit[]` is the dispatched vertex count for that
|
|
27
|
+
// slot's drawIndirect.
|
|
28
|
+
//
|
|
29
|
+
// Buffers grow lazily; the partition wrapper resizes as slot counts
|
|
30
|
+
// or record counts change. v1 caps slots per bucket at MAX_SLOTS
|
|
31
|
+
// (256), exceeded slots throw with a friendly diagnostic.
|
|
32
|
+
/** v1 cap on distinct pipeline slots per bucket. */
|
|
33
|
+
export const MAX_SLOTS_PER_BUCKET = 256;
|
|
34
|
+
/**
|
|
35
|
+
* Pure-CPU partition. Reference implementation for the WGSL kernel
|
|
36
|
+
* below. Order within a slot is stable wrt. input record order so
|
|
37
|
+
* tests can predict the permuted layout deterministically.
|
|
38
|
+
*/
|
|
39
|
+
export function partitionCPU(input) {
|
|
40
|
+
const { slot, emitCount, slotCount } = input;
|
|
41
|
+
const numRecords = slot.length;
|
|
42
|
+
if (emitCount.length !== numRecords) {
|
|
43
|
+
throw new Error("partition: slot.length != emitCount.length");
|
|
44
|
+
}
|
|
45
|
+
if (slotCount > MAX_SLOTS_PER_BUCKET) {
|
|
46
|
+
throw new Error(`partition: slotCount ${slotCount} exceeds cap ${MAX_SLOTS_PER_BUCKET}`);
|
|
47
|
+
}
|
|
48
|
+
const slotCounts = new Uint32Array(slotCount);
|
|
49
|
+
const slotOffsets = new Uint32Array(slotCount + 1);
|
|
50
|
+
const slotTotalEmit = new Uint32Array(slotCount);
|
|
51
|
+
const slotRecords = new Uint32Array(numRecords);
|
|
52
|
+
const slotEmitPrefix = new Uint32Array(numRecords + slotCount);
|
|
53
|
+
// Pass 1: histogram + per-slot emit sum.
|
|
54
|
+
for (let i = 0; i < numRecords; i++) {
|
|
55
|
+
const s = slot[i];
|
|
56
|
+
if (s < 0 || s >= slotCount) {
|
|
57
|
+
throw new Error(`partition: record ${i} has out-of-range slot ${s} (slotCount=${slotCount})`);
|
|
58
|
+
}
|
|
59
|
+
slotCounts[s] = slotCounts[s] + 1;
|
|
60
|
+
slotTotalEmit[s] = slotTotalEmit[s] + emitCount[i];
|
|
61
|
+
}
|
|
62
|
+
// Pass 2: exclusive prefix sum -> per-slot start offsets.
|
|
63
|
+
let acc = 0;
|
|
64
|
+
for (let s = 0; s < slotCount; s++) {
|
|
65
|
+
slotOffsets[s] = acc;
|
|
66
|
+
acc += slotCounts[s];
|
|
67
|
+
}
|
|
68
|
+
slotOffsets[slotCount] = acc;
|
|
69
|
+
// Pass 3: scatter — second pass over records to place each at its
|
|
70
|
+
// slot's current cursor, emitting the within-slot prefix sums.
|
|
71
|
+
const cursors = new Uint32Array(slotCount);
|
|
72
|
+
for (let i = 0; i < numRecords; i++) {
|
|
73
|
+
const s = slot[i];
|
|
74
|
+
const base = slotOffsets[s]; // first slotRecords[] index for this slot
|
|
75
|
+
const prefBase = base + s; // first slotEmitPrefix[] index for this slot
|
|
76
|
+
const k = cursors[s]; // within-slot position
|
|
77
|
+
slotRecords[base + k] = i;
|
|
78
|
+
// Slot prefix sums are length (slotCounts[s] + 1): first entry
|
|
79
|
+
// is 0, then cumulative; the last entry equals slotTotalEmit[s].
|
|
80
|
+
if (k === 0) {
|
|
81
|
+
slotEmitPrefix[prefBase] = 0;
|
|
82
|
+
}
|
|
83
|
+
slotEmitPrefix[prefBase + k + 1] = slotEmitPrefix[prefBase + k] + emitCount[i];
|
|
84
|
+
cursors[s] = k + 1;
|
|
85
|
+
}
|
|
86
|
+
return { slotCounts, slotOffsets, slotTotalEmit, slotRecords, slotEmitPrefix };
|
|
87
|
+
}
|
|
88
|
+
// ─── WGSL kernel ───────────────────────────────────────────────────────
|
|
89
|
+
//
|
|
90
|
+
// Mirrors `partitionCPU` in two compute passes:
|
|
91
|
+
//
|
|
92
|
+
// Pass A (histogram):
|
|
93
|
+
// - one thread per record
|
|
94
|
+
// - read slot[i], emitCount[i]
|
|
95
|
+
// - atomicAdd(slotCounts[s], 1)
|
|
96
|
+
// - atomicAdd(slotTotalEmit[s], emitCount[i])
|
|
97
|
+
//
|
|
98
|
+
// Pass B (scatter):
|
|
99
|
+
// - one thread per record
|
|
100
|
+
// - look up slot[i]
|
|
101
|
+
// - atomicAdd(cursors[s], 1) -> within-slot position k
|
|
102
|
+
// - slotRecords[slotOffsets[s] + k] = i
|
|
103
|
+
// - slotEmitPrefix[slotOffsets[s] + s + k + 1] is computed by a
|
|
104
|
+
// second scan pass (the within-slot prefix sum). v1 emits a
|
|
105
|
+
// serial CPU-side scan for simplicity; v2 should fold this into
|
|
106
|
+
// a per-slot warp-scan once buckets get large.
|
|
107
|
+
//
|
|
108
|
+
// Between the two passes, the CPU runs an exclusive scan on
|
|
109
|
+
// `slotCounts` to produce `slotOffsets` (small array, ≤ MAX_SLOTS).
|
|
110
|
+
//
|
|
111
|
+
// The within-slot prefix scan in pass B uses a non-atomic read of
|
|
112
|
+
// `slotEmitPrefix[prefBase + k]` — this is safe because the only
|
|
113
|
+
// thread that writes index `prefBase + k` is the one whose `cursors`
|
|
114
|
+
// fetchAdd returned `k`, and that thread completed before any thread
|
|
115
|
+
// whose fetchAdd returned `k+1` could read `prefBase + k`.
|
|
116
|
+
//
|
|
117
|
+
// (Wait — that's only true if we have a memory barrier across
|
|
118
|
+
// invocations, which WGSL doesn't guarantee inside one dispatch. In
|
|
119
|
+
// practice v1 punts: the kernel emits the histogram + slotRecords,
|
|
120
|
+
// and the host or a follow-up pass computes slotEmitPrefix. The
|
|
121
|
+
// kernel below reflects this: it produces slotCounts, slotOffsets
|
|
122
|
+
// (after host scan), slotTotalEmit, and slotRecords. The
|
|
123
|
+
// slotEmitPrefix scan is performed in a third compute pass on the
|
|
124
|
+
// already-scattered records.)
|
|
125
|
+
export const PARTITION_HISTOGRAM_WGSL = /* wgsl */ `
|
|
126
|
+
struct PartitionInputs {
|
|
127
|
+
numRecords: u32,
|
|
128
|
+
slotCount: u32,
|
|
129
|
+
_pad0: u32,
|
|
130
|
+
_pad1: u32,
|
|
131
|
+
};
|
|
132
|
+
@group(0) @binding(0) var<uniform> uIn: PartitionInputs;
|
|
133
|
+
@group(0) @binding(1) var<storage, read> slot: array<u32>;
|
|
134
|
+
@group(0) @binding(2) var<storage, read> emitCount: array<u32>;
|
|
135
|
+
@group(0) @binding(3) var<storage, read_write> slotCounts: array<atomic<u32>>;
|
|
136
|
+
@group(0) @binding(4) var<storage, read_write> slotTotalEmit: array<atomic<u32>>;
|
|
137
|
+
|
|
138
|
+
@compute @workgroup_size(64)
|
|
139
|
+
fn histogram(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
140
|
+
let i = gid.x;
|
|
141
|
+
if (i >= uIn.numRecords) { return; }
|
|
142
|
+
let s = slot[i];
|
|
143
|
+
atomicAdd(&slotCounts[s], 1u);
|
|
144
|
+
atomicAdd(&slotTotalEmit[s], emitCount[i]);
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
export const PARTITION_SCATTER_WGSL = /* wgsl */ `
|
|
148
|
+
struct PartitionInputs {
|
|
149
|
+
numRecords: u32,
|
|
150
|
+
slotCount: u32,
|
|
151
|
+
_pad0: u32,
|
|
152
|
+
_pad1: u32,
|
|
153
|
+
};
|
|
154
|
+
@group(0) @binding(0) var<uniform> uIn: PartitionInputs;
|
|
155
|
+
@group(0) @binding(1) var<storage, read> slot: array<u32>;
|
|
156
|
+
@group(0) @binding(2) var<storage, read> slotOffsets: array<u32>;
|
|
157
|
+
@group(0) @binding(3) var<storage, read_write> cursors: array<atomic<u32>>;
|
|
158
|
+
@group(0) @binding(4) var<storage, read_write> slotRecords: array<u32>;
|
|
159
|
+
|
|
160
|
+
@compute @workgroup_size(64)
|
|
161
|
+
fn scatter(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
162
|
+
let i = gid.x;
|
|
163
|
+
if (i >= uIn.numRecords) { return; }
|
|
164
|
+
let s = slot[i];
|
|
165
|
+
let base = slotOffsets[s];
|
|
166
|
+
let k = atomicAdd(&cursors[s], 1u);
|
|
167
|
+
slotRecords[base + k] = i;
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
//# sourceMappingURL=partition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition.js","sourceRoot":"","sources":["../../../src/runtime/derivedModes/partition.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PipelineCache, type PipelineBuilder, type PipelineStateDescriptor } from "../pipelineCache/index.js";
|
|
2
|
+
export interface SlotTableEntry {
|
|
3
|
+
readonly slotIndex: number;
|
|
4
|
+
readonly modeKey: bigint;
|
|
5
|
+
readonly descriptor: PipelineStateDescriptor;
|
|
6
|
+
/** Refcount of ROs currently bound to this slot. v1 doesn't GC at 0. */
|
|
7
|
+
refCount: number;
|
|
8
|
+
/** Populated lazily by precompile; null until linked. */
|
|
9
|
+
pipeline: GPURenderPipeline | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* One SlotTable per `(effect, textureSet)` bucket. The cache may be
|
|
13
|
+
* shared across buckets — pipelines that happen to share a descriptor
|
|
14
|
+
* (rare across distinct effects, since pipeline objects bake the
|
|
15
|
+
* shader / layout) still dedupe at the cache level if a builder
|
|
16
|
+
* legitimately produces the same GPURenderPipeline for the same
|
|
17
|
+
* descriptor key.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SlotTable {
|
|
20
|
+
readonly cache: PipelineCache;
|
|
21
|
+
readonly builder: PipelineBuilder;
|
|
22
|
+
private readonly entries;
|
|
23
|
+
private readonly byKey;
|
|
24
|
+
/** Tracks pending precompile promises so ready() waits for the union. */
|
|
25
|
+
private pending;
|
|
26
|
+
private version;
|
|
27
|
+
constructor(cache: PipelineCache, builder: PipelineBuilder);
|
|
28
|
+
/** Total slot count (linked or pending). */
|
|
29
|
+
get slotCount(): number;
|
|
30
|
+
/**
|
|
31
|
+
* Bumps whenever a new slot is appended. Consumers (partition setup,
|
|
32
|
+
* indirect-args resize, GPU lookup-table upload) compare against
|
|
33
|
+
* their last-seen value to know when to re-upload.
|
|
34
|
+
*/
|
|
35
|
+
get layoutVersion(): number;
|
|
36
|
+
/** Iterate all slots in slot-index order. */
|
|
37
|
+
all(): IterableIterator<SlotTableEntry>;
|
|
38
|
+
/**
|
|
39
|
+
* Add (or reference-bump) the slot for `descriptor`. Returns the
|
|
40
|
+
* existing slot if it already exists, otherwise creates one and
|
|
41
|
+
* kicks off compilation via `builder` (async by default).
|
|
42
|
+
*
|
|
43
|
+
* Pass `sync: true` on the runtime-mutation path to use
|
|
44
|
+
* `builder.createSync` instead — the returned slot's `pipeline` will
|
|
45
|
+
* be populated immediately.
|
|
46
|
+
*/
|
|
47
|
+
addRO(descriptor: PipelineStateDescriptor, options?: {
|
|
48
|
+
sync?: boolean;
|
|
49
|
+
}): SlotTableEntry;
|
|
50
|
+
/** Drop one reference from the slot. Pipelines stay resident. */
|
|
51
|
+
removeRO(modeKey: bigint): void;
|
|
52
|
+
/**
|
|
53
|
+
* Move an RO from one slot to another (reactive aval mutation
|
|
54
|
+
* caused its modeKey to change). Returns the new (possibly newly
|
|
55
|
+
* allocated) slot.
|
|
56
|
+
*/
|
|
57
|
+
rebindRO(fromModeKey: bigint, toDescriptor: PipelineStateDescriptor, options?: {
|
|
58
|
+
sync?: boolean;
|
|
59
|
+
}): SlotTableEntry;
|
|
60
|
+
/** Sync slot lookup by modeKey. Returns the entry or null. */
|
|
61
|
+
lookup(modeKey: bigint): SlotTableEntry | null;
|
|
62
|
+
/**
|
|
63
|
+
* Resolves when every currently-registered slot has a linked
|
|
64
|
+
* pipeline. Subsequent `addRO` calls after this resolves with
|
|
65
|
+
* `sync: false` will produce fresh pending work; call `ready()`
|
|
66
|
+
* again to wait.
|
|
67
|
+
*/
|
|
68
|
+
ready(): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=slotTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slotTable.d.ts","sourceRoot":"","sources":["../../../src/runtime/derivedModes/slotTable.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,aAAa,EAEb,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC7B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAI,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,uBAAuB,CAAC;IAC7C,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,qBAAa,SAAS;IAQlB,QAAQ,CAAC,KAAK,EAAE,aAAa;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe;IARnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,yEAAyE;IACzE,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,OAAO,CAAK;gBAGT,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,eAAe;IAGnC,4CAA4C;IAC5C,IAAI,SAAS,IAAI,MAAM,CAAgC;IACvD;;;;OAIG;IACH,IAAI,aAAa,IAAI,MAAM,CAAyB;IACpD,6CAA6C;IAC5C,GAAG,IAAI,gBAAgB,CAAC,cAAc,CAAC;IAExC;;;;;;;;OAQG;IACH,KAAK,CACH,UAAU,EAAE,uBAAuB,EACnC,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAO,GAC/B,cAAc;IA8BjB,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAM/B;;;;OAIG;IACH,QAAQ,CACN,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,uBAAuB,EACrC,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAO,GAC/B,cAAc;IAKjB,8DAA8D;IAC9D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAI9C;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
|