@aardworx/wombat.rendering 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/buffer.d.ts.map +1 -1
- package/dist/core/buffer.js +65 -5
- package/dist/core/buffer.js.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/provider.d.ts +38 -0
- package/dist/core/provider.d.ts.map +1 -0
- package/dist/core/provider.js +104 -0
- package/dist/core/provider.js.map +1 -0
- package/dist/core/renderObject.d.ts +17 -10
- package/dist/core/renderObject.d.ts.map +1 -1
- package/dist/core/sampler.d.ts.map +1 -1
- package/dist/core/sampler.js +71 -2
- package/dist/core/sampler.js.map +1 -1
- package/dist/core/texture.d.ts +9 -0
- package/dist/core/texture.d.ts.map +1 -1
- package/dist/core/texture.js +0 -0
- package/dist/core/texture.js.map +1 -1
- package/dist/resources/adaptiveTexture.d.ts.map +1 -1
- package/dist/resources/adaptiveTexture.js +7 -0
- package/dist/resources/adaptiveTexture.js.map +1 -1
- package/dist/resources/preparedRenderObject.d.ts.map +1 -1
- package/dist/resources/preparedRenderObject.js +20 -18
- package/dist/resources/preparedRenderObject.js.map +1 -1
- package/dist/resources/uniformBuffer.d.ts.map +1 -1
- package/dist/resources/uniformBuffer.js +23 -0
- package/dist/resources/uniformBuffer.js.map +1 -1
- package/dist/runtime/derivedUniforms/slots.d.ts.map +1 -1
- package/dist/runtime/derivedUniforms/slots.js +4 -0
- package/dist/runtime/derivedUniforms/slots.js.map +1 -1
- package/dist/runtime/heapAdapter.d.ts.map +1 -1
- package/dist/runtime/heapAdapter.js +72 -29
- package/dist/runtime/heapAdapter.js.map +1 -1
- package/dist/runtime/heapDecoder.d.ts +52 -0
- package/dist/runtime/heapDecoder.d.ts.map +1 -0
- package/dist/runtime/heapDecoder.js +363 -0
- package/dist/runtime/heapDecoder.js.map +1 -0
- package/dist/runtime/heapEffectIR.d.ts.map +1 -1
- package/dist/runtime/heapEffectIR.js +300 -3
- package/dist/runtime/heapEffectIR.js.map +1 -1
- package/dist/runtime/heapEligibility.d.ts.map +1 -1
- package/dist/runtime/heapEligibility.js +42 -5
- package/dist/runtime/heapEligibility.js.map +1 -1
- package/dist/runtime/heapIrBuilders.d.ts +64 -0
- package/dist/runtime/heapIrBuilders.d.ts.map +1 -0
- package/dist/runtime/heapIrBuilders.js +311 -0
- package/dist/runtime/heapIrBuilders.js.map +1 -0
- package/dist/runtime/heapScene.d.ts.map +1 -1
- package/dist/runtime/heapScene.js +329 -112
- package/dist/runtime/heapScene.js.map +1 -1
- package/dist/runtime/textureAtlas/atlasPool.d.ts +67 -4
- package/dist/runtime/textureAtlas/atlasPool.d.ts.map +1 -1
- package/dist/runtime/textureAtlas/atlasPool.js +152 -18
- package/dist/runtime/textureAtlas/atlasPool.js.map +1 -1
- package/package.json +3 -3
- package/src/core/buffer.ts +58 -6
- package/src/core/index.ts +11 -0
- package/src/core/provider.ts +143 -0
- package/src/core/renderObject.ts +17 -10
- package/src/core/sampler.ts +64 -2
- package/src/core/texture.ts +0 -0
- package/src/resources/adaptiveTexture.ts +7 -0
- package/src/resources/preparedRenderObject.ts +21 -16
- package/src/resources/uniformBuffer.ts +21 -0
- package/src/runtime/derivedUniforms/slots.ts +4 -0
- package/src/runtime/heapAdapter.ts +63 -29
- package/src/runtime/heapDecoder.ts +446 -0
- package/src/runtime/heapEffectIR.ts +314 -3
- package/src/runtime/heapEligibility.ts +34 -5
- package/src/runtime/heapIrBuilders.ts +365 -0
- package/src/runtime/heapScene.ts +338 -96
- package/src/runtime/textureAtlas/atlasPool.ts +152 -16
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// Shared IR builders for the heap-rendering path.
|
|
2
|
+
//
|
|
3
|
+
// Centralises the tiny utility helpers (constU32 / item / add / …) plus
|
|
4
|
+
// the heap-load expressions (loadHeaderRef / loadUniformByRef /
|
|
5
|
+
// loadAttributeByRef / loadInstanceByRef). Both `heapEffectIR.ts` (the
|
|
6
|
+
// substitute-based legacy path) and `heapDecoder.ts` (the
|
|
7
|
+
// composition-based decoder synthesis) consume these.
|
|
8
|
+
//
|
|
9
|
+
// Storage buffers heapU32 / headersU32 / heapF32 / heapV4f are exposed
|
|
10
|
+
// as `ReadInput("Uniform", name)` of array<...> type; indexing is
|
|
11
|
+
// `Item(buffer, index)`. The buffers themselves are added as
|
|
12
|
+
// StorageBuffer ValueDefs (see `heapStorageBufferDecls`) so the WGSL
|
|
13
|
+
// emitter materialises the `@group(0) @binding(N) var<storage, read>`
|
|
14
|
+
// declarations.
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
type Expr, type Stmt, type Type, type ValueDef,
|
|
18
|
+
Tu32, Tf32, Vec,
|
|
19
|
+
} from "@aardworx/wombat.shader/ir";
|
|
20
|
+
|
|
21
|
+
// ─── Type shorthands ───────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export const Tbool: Type = { kind: "Bool" };
|
|
24
|
+
export const Tvec2 = Vec(Tf32, 2);
|
|
25
|
+
export const Tvec3 = Vec(Tf32, 3);
|
|
26
|
+
export const Tvec4 = Vec(Tf32, 4);
|
|
27
|
+
export const Tmat4 = { kind: "Matrix" as const, element: Tf32, rows: 4, cols: 4 } as Type;
|
|
28
|
+
export const TarrU32: Type = { kind: "Array", element: Tu32, length: "runtime" } as Type;
|
|
29
|
+
export const TarrF32: Type = { kind: "Array", element: Tf32, length: "runtime" } as Type;
|
|
30
|
+
export const TarrVec4: Type = { kind: "Array", element: Tvec4, length: "runtime" } as Type;
|
|
31
|
+
|
|
32
|
+
// ─── Const + leaf expression builders ─────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export const constU32 = (n: number): Expr => ({
|
|
35
|
+
kind: "Const", type: Tu32, value: { kind: "Int", signed: false, value: n },
|
|
36
|
+
});
|
|
37
|
+
export const constF32 = (n: number): Expr => ({
|
|
38
|
+
kind: "Const", type: Tf32, value: { kind: "Float", value: n },
|
|
39
|
+
});
|
|
40
|
+
export const readScope = (
|
|
41
|
+
scope: "Uniform" | "Input" | "Builtin",
|
|
42
|
+
name: string,
|
|
43
|
+
type: Type,
|
|
44
|
+
): Expr => ({ kind: "ReadInput", scope, name, type });
|
|
45
|
+
|
|
46
|
+
// ─── Compound expression builders ─────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export const item = (target: Expr, index: Expr, elemType: Type): Expr => ({
|
|
49
|
+
kind: "Item", target, index, type: elemType,
|
|
50
|
+
});
|
|
51
|
+
export const add = (lhs: Expr, rhs: Expr, type: Type): Expr => ({ kind: "Add", lhs, rhs, type });
|
|
52
|
+
export const sub = (lhs: Expr, rhs: Expr, type: Type): Expr => ({ kind: "Sub", lhs, rhs, type });
|
|
53
|
+
export const mul = (lhs: Expr, rhs: Expr, type: Type): Expr => ({ kind: "Mul", lhs, rhs, type });
|
|
54
|
+
export const div = (lhs: Expr, rhs: Expr, type: Type): Expr => ({ kind: "Div", lhs, rhs, type });
|
|
55
|
+
export const mod = (lhs: Expr, rhs: Expr, type: Type): Expr => ({ kind: "Mod", lhs, rhs, type });
|
|
56
|
+
export const eqU32 = (lhs: Expr, rhs: Expr): Expr => ({ kind: "Eq", lhs, rhs, type: Tbool });
|
|
57
|
+
export const geU32 = (lhs: Expr, rhs: Expr): Expr => ({ kind: "Ge", lhs, rhs, type: Tbool });
|
|
58
|
+
export const leU32 = (lhs: Expr, rhs: Expr): Expr => ({ kind: "Le", lhs, rhs, type: Tbool });
|
|
59
|
+
export const shr = (lhs: Expr, rhs: Expr, type: Type): Expr => ({
|
|
60
|
+
kind: "ShiftRight", lhs, rhs, type,
|
|
61
|
+
});
|
|
62
|
+
export const select = (cond: Expr, ifTrue: Expr, ifFalse: Expr, type: Type): Expr => ({
|
|
63
|
+
kind: "Conditional", cond, ifTrue, ifFalse, type,
|
|
64
|
+
});
|
|
65
|
+
export const newVec = (components: Expr[], type: Type): Expr => ({
|
|
66
|
+
kind: "NewVector", components, type,
|
|
67
|
+
});
|
|
68
|
+
export const matFromRows = (rows: Expr[], type: Type): Expr => ({
|
|
69
|
+
kind: "MatrixFromRows", rows, type,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ─── Storage-buffer references (Expr-form) ────────────────────────────
|
|
73
|
+
|
|
74
|
+
export const heapU32 = readScope("Uniform", "heapU32", TarrU32);
|
|
75
|
+
export const headersU32 = readScope("Uniform", "headersU32", TarrU32);
|
|
76
|
+
export const heapF32 = readScope("Uniform", "heapF32", TarrF32);
|
|
77
|
+
export const heapV4f = readScope("Uniform", "heapV4f", TarrVec4);
|
|
78
|
+
export const drawTable = readScope("Uniform", "drawTable", TarrU32);
|
|
79
|
+
export const indexStorage = readScope("Uniform", "indexStorage", TarrU32);
|
|
80
|
+
export const firstDrawInTile = readScope("Uniform", "firstDrawInTile", TarrU32);
|
|
81
|
+
|
|
82
|
+
// ─── Storage-buffer ValueDefs ─────────────────────────────────────────
|
|
83
|
+
//
|
|
84
|
+
// Bindings 0..3 are heap-arena typed views; 4..6 are megacall lookup
|
|
85
|
+
// buffers. Order matches the BindGroupLayout entries the runtime sets
|
|
86
|
+
// up — both sides walk module ValueDefs in declaration order and increment
|
|
87
|
+
// a slot counter to derive `@binding(N)`.
|
|
88
|
+
|
|
89
|
+
// `keep: true` on every heap-rendering binding: the host-side
|
|
90
|
+
// BindGroupLayout pins these at slots 0..6, so the WGSL must declare
|
|
91
|
+
// them at the same slots whether or not any code path on the current
|
|
92
|
+
// effect happens to reference each one (e.g. a shader with no mat4
|
|
93
|
+
// uniforms doesn't touch heapV4f, but the BGL still has slot 3).
|
|
94
|
+
export function heapArenaStorageDecls(): ValueDef[] {
|
|
95
|
+
return [
|
|
96
|
+
{ kind: "StorageBuffer", binding: { group: 0, slot: 0 }, name: "heapU32", layout: TarrU32, access: "read", keep: true },
|
|
97
|
+
{ kind: "StorageBuffer", binding: { group: 0, slot: 1 }, name: "headersU32", layout: TarrU32, access: "read", keep: true },
|
|
98
|
+
{ kind: "StorageBuffer", binding: { group: 0, slot: 2 }, name: "heapF32", layout: TarrF32, access: "read", keep: true },
|
|
99
|
+
{ kind: "StorageBuffer", binding: { group: 0, slot: 3 }, name: "heapV4f", layout: TarrVec4, access: "read", keep: true },
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function megacallLookupStorageDecls(): ValueDef[] {
|
|
104
|
+
return [
|
|
105
|
+
{ kind: "StorageBuffer", binding: { group: 0, slot: 4 }, name: "drawTable", layout: TarrU32, access: "read", keep: true },
|
|
106
|
+
{ kind: "StorageBuffer", binding: { group: 0, slot: 5 }, name: "indexStorage", layout: TarrU32, access: "read", keep: true },
|
|
107
|
+
{ kind: "StorageBuffer", binding: { group: 0, slot: 6 }, name: "firstDrawInTile", layout: TarrU32, access: "read", keep: true },
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Heap-load address builders ───────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/** `headersU32[drawIdx * stride + offset]` — a u32 ref slot in the drawHeader. */
|
|
114
|
+
export function loadHeaderRef(drawIdx: Expr, fieldByteOffset: number, strideU32: number): Expr {
|
|
115
|
+
const fieldU32 = fieldByteOffset / 4;
|
|
116
|
+
if (!Number.isInteger(fieldU32)) {
|
|
117
|
+
throw new Error(`loadHeaderRef: byteOffset ${fieldByteOffset} is not 4-byte aligned`);
|
|
118
|
+
}
|
|
119
|
+
const drawOff = mul(drawIdx, constU32(strideU32), Tu32);
|
|
120
|
+
const idx = fieldU32 === 0 ? drawOff : add(drawOff, constU32(fieldU32), Tu32);
|
|
121
|
+
return item(headersU32, idx, Tu32);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Load a per-draw uniform value from the heap given its `u32` ref.
|
|
126
|
+
* Matrix → 4 vec4 columns from heapV4f; vec4 → one heapV4f read;
|
|
127
|
+
* vec3/2/scalar → heapF32 reads + NewVector.
|
|
128
|
+
*/
|
|
129
|
+
export function loadUniformByRef(refIdent: Expr, wgslType: string): Expr {
|
|
130
|
+
switch (wgslType) {
|
|
131
|
+
case "mat4x4<f32>": {
|
|
132
|
+
const base = div(add(refIdent, constU32(16), Tu32), constU32(16), Tu32);
|
|
133
|
+
const cols: Expr[] = [];
|
|
134
|
+
for (let i = 0; i < 4; i++) {
|
|
135
|
+
const idx = i === 0 ? base : add(base, constU32(i), Tu32);
|
|
136
|
+
cols.push(item(heapV4f, idx, Tvec4));
|
|
137
|
+
}
|
|
138
|
+
return matFromRows(cols, Tmat4);
|
|
139
|
+
}
|
|
140
|
+
case "vec4<f32>":
|
|
141
|
+
return item(heapV4f, div(add(refIdent, constU32(16), Tu32), constU32(16), Tu32), Tvec4);
|
|
142
|
+
case "vec3<f32>": {
|
|
143
|
+
const base = div(add(refIdent, constU32(16), Tu32), constU32(4), Tu32);
|
|
144
|
+
return newVec([
|
|
145
|
+
item(heapF32, base, Tf32),
|
|
146
|
+
item(heapF32, add(base, constU32(1), Tu32), Tf32),
|
|
147
|
+
item(heapF32, add(base, constU32(2), Tu32), Tf32),
|
|
148
|
+
], Tvec3);
|
|
149
|
+
}
|
|
150
|
+
case "vec2<f32>": {
|
|
151
|
+
const base = div(add(refIdent, constU32(16), Tu32), constU32(4), Tu32);
|
|
152
|
+
return newVec([
|
|
153
|
+
item(heapF32, base, Tf32),
|
|
154
|
+
item(heapF32, add(base, constU32(1), Tu32), Tf32),
|
|
155
|
+
], Tvec2);
|
|
156
|
+
}
|
|
157
|
+
case "f32":
|
|
158
|
+
return item(heapF32, div(add(refIdent, constU32(16), Tu32), constU32(4), Tu32), Tf32);
|
|
159
|
+
case "u32":
|
|
160
|
+
return item(heapU32, div(add(refIdent, constU32(16), Tu32), constU32(4), Tu32), Tu32);
|
|
161
|
+
default:
|
|
162
|
+
throw new Error(`heap: no IR loader for uniform type '${wgslType}'`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Cyclic per-vertex/per-instance attribute load. `idx` is the vertex or
|
|
168
|
+
* instance index. The allocation header at `ref/4 + 1` holds the
|
|
169
|
+
* element length so `idx % length` broadcasts length-1 allocations.
|
|
170
|
+
*/
|
|
171
|
+
export function loadAttributeByRef(refIdent: Expr, idx: Expr, wgslType: string): Expr {
|
|
172
|
+
const dataF32Base = div(add(refIdent, constU32(16), Tu32), constU32(4), Tu32);
|
|
173
|
+
const cyclic = (elemSize: number): Expr => {
|
|
174
|
+
const length = item(heapU32, add(div(refIdent, constU32(4), Tu32), constU32(1), Tu32), Tu32);
|
|
175
|
+
return mul(mod(idx, length, Tu32), constU32(elemSize), Tu32);
|
|
176
|
+
};
|
|
177
|
+
switch (wgslType) {
|
|
178
|
+
case "vec3<f32>": {
|
|
179
|
+
const base = add(dataF32Base, cyclic(3), Tu32);
|
|
180
|
+
return newVec([
|
|
181
|
+
item(heapF32, base, Tf32),
|
|
182
|
+
item(heapF32, add(base, constU32(1), Tu32), Tf32),
|
|
183
|
+
item(heapF32, add(base, constU32(2), Tu32), Tf32),
|
|
184
|
+
], Tvec3);
|
|
185
|
+
}
|
|
186
|
+
case "vec4<f32>": {
|
|
187
|
+
const strideBytes = item(heapU32, add(div(refIdent, constU32(4), Tu32), constU32(2), Tu32), Tu32);
|
|
188
|
+
const strideF = div(strideBytes, constU32(4), Tu32);
|
|
189
|
+
const cycled = mod(idx, item(heapU32, add(div(refIdent, constU32(4), Tu32), constU32(1), Tu32), Tu32), Tu32);
|
|
190
|
+
const off = mul(cycled, strideF, Tu32);
|
|
191
|
+
const base = add(dataF32Base, off, Tu32);
|
|
192
|
+
const w = select(eqU32(strideF, constU32(4)), item(heapF32, add(base, constU32(3), Tu32), Tf32), constF32(1.0), Tf32);
|
|
193
|
+
return newVec([
|
|
194
|
+
item(heapF32, base, Tf32),
|
|
195
|
+
item(heapF32, add(base, constU32(1), Tu32), Tf32),
|
|
196
|
+
item(heapF32, add(base, constU32(2), Tu32), Tf32),
|
|
197
|
+
w,
|
|
198
|
+
], Tvec4);
|
|
199
|
+
}
|
|
200
|
+
case "vec2<f32>": {
|
|
201
|
+
const base = add(dataF32Base, cyclic(2), Tu32);
|
|
202
|
+
return newVec([
|
|
203
|
+
item(heapF32, base, Tf32),
|
|
204
|
+
item(heapF32, add(base, constU32(1), Tu32), Tf32),
|
|
205
|
+
], Tvec2);
|
|
206
|
+
}
|
|
207
|
+
case "f32": {
|
|
208
|
+
const base = add(dataF32Base, cyclic(1), Tu32);
|
|
209
|
+
return item(heapF32, base, Tf32);
|
|
210
|
+
}
|
|
211
|
+
case "u32": {
|
|
212
|
+
const base = add(div(refIdent, constU32(4), Tu32), add(constU32(4), cyclic(1), Tu32), Tu32);
|
|
213
|
+
return item(heapU32, base, Tu32);
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
throw new Error(`heap: no IR loader for attribute type '${wgslType}'`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Per-instance uniform/attribute load: read element `iidx` from the
|
|
222
|
+
* allocation (no cyclic-modulo wrap — instance count is exact).
|
|
223
|
+
*/
|
|
224
|
+
export function loadInstanceByRef(refIdent: Expr, iidx: Expr, wgslType: string): Expr {
|
|
225
|
+
const dataF32Base = div(add(refIdent, constU32(16), Tu32), constU32(4), Tu32);
|
|
226
|
+
const dataVec4Base = div(add(refIdent, constU32(16), Tu32), constU32(16), Tu32);
|
|
227
|
+
switch (wgslType) {
|
|
228
|
+
case "mat4x4<f32>": {
|
|
229
|
+
const off = mul(iidx, constU32(4), Tu32);
|
|
230
|
+
const base = add(dataVec4Base, off, Tu32);
|
|
231
|
+
const cols: Expr[] = [];
|
|
232
|
+
for (let i = 0; i < 4; i++) {
|
|
233
|
+
cols.push(item(heapV4f, add(base, constU32(i), Tu32), Tvec4));
|
|
234
|
+
}
|
|
235
|
+
return matFromRows(cols, Tmat4);
|
|
236
|
+
}
|
|
237
|
+
case "vec4<f32>":
|
|
238
|
+
return item(heapV4f, add(dataVec4Base, iidx, Tu32), Tvec4);
|
|
239
|
+
case "vec3<f32>": {
|
|
240
|
+
const off = mul(iidx, constU32(3), Tu32);
|
|
241
|
+
const base = add(dataF32Base, off, Tu32);
|
|
242
|
+
return newVec([
|
|
243
|
+
item(heapF32, base, Tf32),
|
|
244
|
+
item(heapF32, add(base, constU32(1), Tu32), Tf32),
|
|
245
|
+
item(heapF32, add(base, constU32(2), Tu32), Tf32),
|
|
246
|
+
], Tvec3);
|
|
247
|
+
}
|
|
248
|
+
case "vec2<f32>": {
|
|
249
|
+
const off = mul(iidx, constU32(2), Tu32);
|
|
250
|
+
const base = add(dataF32Base, off, Tu32);
|
|
251
|
+
return newVec([
|
|
252
|
+
item(heapF32, base, Tf32),
|
|
253
|
+
item(heapF32, add(base, constU32(1), Tu32), Tf32),
|
|
254
|
+
], Tvec2);
|
|
255
|
+
}
|
|
256
|
+
case "f32":
|
|
257
|
+
return item(heapF32, add(dataF32Base, iidx, Tu32), Tf32);
|
|
258
|
+
case "u32":
|
|
259
|
+
return item(heapU32, add(div(add(refIdent, constU32(16), Tu32), constU32(4), Tu32), iidx, Tu32), Tu32);
|
|
260
|
+
default:
|
|
261
|
+
throw new Error(`heap: no IR loader for per-instance type '${wgslType}'`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─── Megacall search prelude (IR-level) ───────────────────────────────
|
|
266
|
+
//
|
|
267
|
+
// Reproduces the binary-search prelude (currently emitted as raw WGSL by
|
|
268
|
+
// `megacallSearchPrelude()` in heapEffect.ts) as a sequence of IR Stmts.
|
|
269
|
+
// Returns:
|
|
270
|
+
// - the Stmt list to prepend at the top of the entry body
|
|
271
|
+
// - Var handles for `heap_drawIdx`, `instId`, `vid` that body code can
|
|
272
|
+
// reference via `{kind: "Var", var: ...}` expressions
|
|
273
|
+
//
|
|
274
|
+
// The bit-tile cadence (tile = 64 emits) matches the prefix-sum scan
|
|
275
|
+
// kernel's tile size; if that changes upstream this MUST change too.
|
|
276
|
+
|
|
277
|
+
import type { Var } from "@aardworx/wombat.shader/ir";
|
|
278
|
+
|
|
279
|
+
export interface MegacallLocals {
|
|
280
|
+
readonly heapDrawIdx: Var;
|
|
281
|
+
readonly instId: Var;
|
|
282
|
+
readonly vid: Var;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const TILE_BITS = 6; // 64 emits per tile — must match the scan kernel.
|
|
286
|
+
const RECORD_U32 = 5; // drawTable record stride — must match heapScene.
|
|
287
|
+
|
|
288
|
+
export function buildMegacallPrelude(emitIdx: Expr): { stmts: Stmt[]; locals: MegacallLocals } {
|
|
289
|
+
// Local Var declarations. `mutable: false` for the result locals;
|
|
290
|
+
// `mutable: true` for the lo/hi loop indices.
|
|
291
|
+
const tileIdx: Var = { name: "_tileIdx", type: Tu32, mutable: false };
|
|
292
|
+
const lo: Var = { name: "lo", type: Tu32, mutable: true };
|
|
293
|
+
const hi: Var = { name: "hi", type: Tu32, mutable: true };
|
|
294
|
+
const mid: Var = { name: "_mid", type: Tu32, mutable: false };
|
|
295
|
+
const slot: Var = { name: "_slot", type: Tu32, mutable: false };
|
|
296
|
+
const firstEmit: Var = { name: "_firstEmit", type: Tu32, mutable: false };
|
|
297
|
+
const heapDrawIdx: Var = { name: "heap_drawIdx", type: Tu32, mutable: false };
|
|
298
|
+
const indexStart: Var = { name: "_indexStart", type: Tu32, mutable: false };
|
|
299
|
+
const indexCount: Var = { name: "_indexCount", type: Tu32, mutable: false };
|
|
300
|
+
const localOff: Var = { name: "_local", type: Tu32, mutable: false };
|
|
301
|
+
const instId: Var = { name: "instId", type: Tu32, mutable: false };
|
|
302
|
+
const vid: Var = { name: "vid", type: Tu32, mutable: false };
|
|
303
|
+
|
|
304
|
+
const varExpr = (v: Var): Expr => ({ kind: "Var", var: v, type: v.type });
|
|
305
|
+
|
|
306
|
+
const stmts: Stmt[] = [];
|
|
307
|
+
// let _tileIdx = emitIdx >> 6u;
|
|
308
|
+
stmts.push({ kind: "Declare", var: tileIdx, init: { kind: "Expr", value: shr(emitIdx, constU32(TILE_BITS), Tu32) } });
|
|
309
|
+
// var lo: u32 = firstDrawInTile[_tileIdx];
|
|
310
|
+
stmts.push({ kind: "Declare", var: lo, init: { kind: "Expr", value: item(firstDrawInTile, varExpr(tileIdx), Tu32) } });
|
|
311
|
+
// var hi: u32 = firstDrawInTile[_tileIdx + 1u];
|
|
312
|
+
stmts.push({ kind: "Declare", var: hi, init: { kind: "Expr", value: item(firstDrawInTile, add(varExpr(tileIdx), constU32(1), Tu32), Tu32) } });
|
|
313
|
+
|
|
314
|
+
// loop {
|
|
315
|
+
// if (lo >= hi) { break; }
|
|
316
|
+
// let _mid = (lo + hi + 1u) >> 1u;
|
|
317
|
+
// if (drawTable[_mid * 5u] <= emitIdx) { lo = _mid; } else { hi = _mid - 1u; }
|
|
318
|
+
// }
|
|
319
|
+
const loopBody: Stmt[] = [];
|
|
320
|
+
loopBody.push({
|
|
321
|
+
kind: "If",
|
|
322
|
+
cond: geU32(varExpr(lo), varExpr(hi)),
|
|
323
|
+
then: { kind: "Break" },
|
|
324
|
+
});
|
|
325
|
+
// (lo + hi + 1) >> 1 — half-up midpoint
|
|
326
|
+
const midInit = shr(
|
|
327
|
+
add(add(varExpr(lo), varExpr(hi), Tu32), constU32(1), Tu32),
|
|
328
|
+
constU32(1),
|
|
329
|
+
Tu32,
|
|
330
|
+
);
|
|
331
|
+
loopBody.push({ kind: "Declare", var: mid, init: { kind: "Expr", value: midInit } });
|
|
332
|
+
// drawTable[_mid * 5u]
|
|
333
|
+
const midDrawStart = item(drawTable, mul(varExpr(mid), constU32(RECORD_U32), Tu32), Tu32);
|
|
334
|
+
loopBody.push({
|
|
335
|
+
kind: "If",
|
|
336
|
+
cond: leU32(midDrawStart, emitIdx),
|
|
337
|
+
then: { kind: "Write", target: { kind: "LVar", var: lo, type: Tu32 }, value: varExpr(mid) },
|
|
338
|
+
else: { kind: "Write", target: { kind: "LVar", var: hi, type: Tu32 }, value: sub(varExpr(mid), constU32(1), Tu32) },
|
|
339
|
+
});
|
|
340
|
+
stmts.push({ kind: "Loop", body: { kind: "Sequential", body: loopBody } });
|
|
341
|
+
|
|
342
|
+
// let _slot = lo;
|
|
343
|
+
stmts.push({ kind: "Declare", var: slot, init: { kind: "Expr", value: varExpr(lo) } });
|
|
344
|
+
const slotBase = mul(varExpr(slot), constU32(RECORD_U32), Tu32);
|
|
345
|
+
// let _firstEmit = drawTable[_slot * 5u + 0u];
|
|
346
|
+
stmts.push({ kind: "Declare", var: firstEmit, init: { kind: "Expr", value: item(drawTable, slotBase, Tu32) } });
|
|
347
|
+
// let heap_drawIdx = drawTable[_slot * 5u + 1u];
|
|
348
|
+
stmts.push({ kind: "Declare", var: heapDrawIdx, init: { kind: "Expr", value: item(drawTable, add(slotBase, constU32(1), Tu32), Tu32) } });
|
|
349
|
+
// let _indexStart = drawTable[_slot * 5u + 2u];
|
|
350
|
+
stmts.push({ kind: "Declare", var: indexStart, init: { kind: "Expr", value: item(drawTable, add(slotBase, constU32(2), Tu32), Tu32) } });
|
|
351
|
+
// let _indexCount = drawTable[_slot * 5u + 3u];
|
|
352
|
+
stmts.push({ kind: "Declare", var: indexCount, init: { kind: "Expr", value: item(drawTable, add(slotBase, constU32(3), Tu32), Tu32) } });
|
|
353
|
+
// let _local = emitIdx - _firstEmit;
|
|
354
|
+
stmts.push({ kind: "Declare", var: localOff, init: { kind: "Expr", value: sub(emitIdx, varExpr(firstEmit), Tu32) } });
|
|
355
|
+
// let instId = _local / _indexCount;
|
|
356
|
+
stmts.push({ kind: "Declare", var: instId, init: { kind: "Expr", value: div(varExpr(localOff), varExpr(indexCount), Tu32) } });
|
|
357
|
+
// let vid = indexStorage[_indexStart + (_local % _indexCount)];
|
|
358
|
+
stmts.push({
|
|
359
|
+
kind: "Declare",
|
|
360
|
+
var: vid,
|
|
361
|
+
init: { kind: "Expr", value: item(indexStorage, add(varExpr(indexStart), mod(varExpr(localOff), varExpr(indexCount), Tu32), Tu32), Tu32) },
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return { stmts, locals: { heapDrawIdx, instId, vid } };
|
|
365
|
+
}
|