@aardworx/wombat.rendering 0.9.33 → 0.18.1
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/kernelCodegen.d.ts +90 -0
- package/dist/runtime/derivedModes/kernelCodegen.d.ts.map +1 -0
- package/dist/runtime/derivedModes/kernelCodegen.js +623 -0
- package/dist/runtime/derivedModes/kernelCodegen.js.map +1 -0
- package/dist/runtime/derivedModes/modeKeyCpu.d.ts.map +1 -1
- package/dist/runtime/derivedModes/modeKeyCpu.js +23 -67
- package/dist/runtime/derivedModes/modeKeyCpu.js.map +1 -1
- package/dist/runtime/derivedModes/partitionDispatcher.d.ts +79 -0
- package/dist/runtime/derivedModes/partitionDispatcher.d.ts.map +1 -0
- package/dist/runtime/derivedModes/partitionDispatcher.js +303 -0
- package/dist/runtime/derivedModes/partitionDispatcher.js.map +1 -0
- package/dist/runtime/derivedModes/partitionKernel.d.ts +12 -0
- package/dist/runtime/derivedModes/partitionKernel.d.ts.map +1 -0
- package/dist/runtime/derivedModes/partitionKernel.js +127 -0
- package/dist/runtime/derivedModes/partitionKernel.js.map +1 -0
- package/dist/runtime/derivedModes/partitionKernelLayout.d.ts +4 -0
- package/dist/runtime/derivedModes/partitionKernelLayout.d.ts.map +1 -0
- package/dist/runtime/derivedModes/partitionKernelLayout.js +24 -0
- package/dist/runtime/derivedModes/partitionKernelLayout.js.map +1 -0
- package/dist/runtime/derivedModes/rule.d.ts +38 -86
- package/dist/runtime/derivedModes/rule.d.ts.map +1 -1
- package/dist/runtime/derivedModes/rule.js +34 -74
- package/dist/runtime/derivedModes/rule.js.map +1 -1
- package/dist/runtime/derivedUniforms/dispatch.d.ts +10 -8
- package/dist/runtime/derivedUniforms/dispatch.d.ts.map +1 -1
- package/dist/runtime/derivedUniforms/dispatch.js +31 -18
- package/dist/runtime/derivedUniforms/dispatch.js.map +1 -1
- package/dist/runtime/derivedUniforms/marker.d.ts +90 -11
- package/dist/runtime/derivedUniforms/marker.d.ts.map +1 -1
- package/dist/runtime/derivedUniforms/marker.js +161 -15
- package/dist/runtime/derivedUniforms/marker.js.map +1 -1
- package/dist/runtime/derivedUniforms/sceneIntegration.d.ts +25 -6
- package/dist/runtime/derivedUniforms/sceneIntegration.d.ts.map +1 -1
- package/dist/runtime/derivedUniforms/sceneIntegration.js +61 -20
- package/dist/runtime/derivedUniforms/sceneIntegration.js.map +1 -1
- package/dist/runtime/heapEligibility.d.ts.map +1 -1
- package/dist/runtime/heapEligibility.js +22 -2
- package/dist/runtime/heapEligibility.js.map +1 -1
- package/dist/runtime/heapScene/chunkedArena.d.ts +76 -0
- package/dist/runtime/heapScene/chunkedArena.d.ts.map +1 -0
- package/dist/runtime/heapScene/chunkedArena.js +223 -0
- package/dist/runtime/heapScene/chunkedArena.js.map +1 -0
- package/dist/runtime/heapScene/freelist.d.ts +50 -0
- package/dist/runtime/heapScene/freelist.d.ts.map +1 -0
- package/dist/runtime/heapScene/freelist.js +160 -0
- package/dist/runtime/heapScene/freelist.js.map +1 -0
- package/dist/runtime/heapScene/growBuffer.d.ts +23 -2
- package/dist/runtime/heapScene/growBuffer.d.ts.map +1 -1
- package/dist/runtime/heapScene/growBuffer.js +35 -3
- package/dist/runtime/heapScene/growBuffer.js.map +1 -1
- package/dist/runtime/heapScene/pools.d.ts +65 -42
- package/dist/runtime/heapScene/pools.d.ts.map +1 -1
- package/dist/runtime/heapScene/pools.js +206 -134
- package/dist/runtime/heapScene/pools.js.map +1 -1
- package/dist/runtime/heapScene.d.ts +12 -0
- package/dist/runtime/heapScene.d.ts.map +1 -1
- package/dist/runtime/heapScene.js +938 -85
- package/dist/runtime/heapScene.js.map +1 -1
- package/dist/runtime/hybridScene.d.ts +7 -0
- package/dist/runtime/hybridScene.d.ts.map +1 -1
- package/dist/runtime/hybridScene.js +1 -0
- package/dist/runtime/hybridScene.js.map +1 -1
- package/dist/runtime/index.d.ts +4 -3
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +4 -3
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/renderTask.d.ts +5 -0
- package/dist/runtime/renderTask.d.ts.map +1 -1
- package/dist/runtime/renderTask.js +1 -0
- package/dist/runtime/renderTask.js.map +1 -1
- package/dist/runtime/runtime.d.ts +6 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +1 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/runtime/derivedModes/kernelCodegen.ts +673 -0
- package/src/runtime/derivedModes/modeKeyCpu.ts +22 -60
- package/src/runtime/derivedModes/partitionDispatcher.ts +349 -0
- package/src/runtime/derivedModes/partitionKernelLayout.ts +25 -0
- package/src/runtime/derivedModes/rule.ts +66 -145
- package/src/runtime/derivedUniforms/dispatch.ts +48 -21
- package/src/runtime/derivedUniforms/marker.ts +210 -16
- package/src/runtime/derivedUniforms/sceneIntegration.ts +67 -20
- package/src/runtime/heapEligibility.ts +22 -1
- package/src/runtime/heapScene/chunkedArena.ts +238 -0
- package/src/runtime/heapScene/freelist.ts +161 -0
- package/src/runtime/heapScene/growBuffer.ts +39 -2
- package/src/runtime/heapScene/pools.ts +231 -143
- package/src/runtime/heapScene.ts +1119 -81
- package/src/runtime/hybridScene.ts +8 -0
- package/src/runtime/index.ts +12 -9
- package/src/runtime/renderTask.ts +6 -0
- package/src/runtime/runtime.ts +7 -0
- package/src/runtime/derivedModes/gpuDispatcher.ts +0 -226
- package/src/runtime/derivedModes/gpuKernel.ts +0 -98
|
@@ -1,35 +1,44 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Derived-mode rule — branded combination of a shader-IR rule
|
|
2
|
+
// expression (from `rule(...)` in wombat.shader) + an axis + the
|
|
3
|
+
// SG-declared aval. The IR is what gets compiled into the partition
|
|
4
|
+
// kernel; the declared aval drives per-frame re-specialisation.
|
|
4
5
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// shapes (a uniform-driven enum flip, a determinant-sign cull flip,
|
|
8
|
-
// etc.) and pairs naturally with the bucket-level rebuild fast path
|
|
9
|
-
// shipped in 0.9.16 — only ROs whose inputs actually changed get
|
|
10
|
-
// re-evaluated, and same-key buckets re-bind their pipeline once
|
|
11
|
-
// rather than per-RO.
|
|
6
|
+
// Authoring flow (with `@boperators/plugin-vite` + the shader-vite
|
|
7
|
+
// plugin both installed):
|
|
12
8
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// CPU-side bottleneck. See docs/derived-mode-rules-plan.md.
|
|
9
|
+
// declare const u: { ModelTrafo: M44f };
|
|
10
|
+
// declare const declared: number;
|
|
16
11
|
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
12
|
+
// const cullExpr = rule(() => {
|
|
13
|
+
// const m: M33f = mat3(u.ModelTrafo);
|
|
14
|
+
// const det = determinant(m);
|
|
15
|
+
// return det < 0 ? flipCull(declared) : declared;
|
|
16
|
+
// });
|
|
17
|
+
//
|
|
18
|
+
// const cullRule = derivedMode("cull", cullExpr, { declared: cullModeC });
|
|
19
|
+
//
|
|
20
|
+
// At runtime, heapScene reads `cullRule.expr.template`, extracts the
|
|
21
|
+
// entry's body Stmt, calls `analyseOutputSet(body)` to obtain the
|
|
22
|
+
// symbolic set, and for each declared-aval value resolves the set
|
|
23
|
+
// via `evaluateSet({"declared": current}, intrinsics)` to size the
|
|
24
|
+
// bucket's slots + codegen the partition kernel.
|
|
20
25
|
|
|
21
|
-
import type {
|
|
26
|
+
import type { RuleExpr } from "@aardworx/wombat.shader";
|
|
27
|
+
import type { aval } from "@aardworx/wombat.adaptive";
|
|
28
|
+
import type { CullMode, FrontFace, Topology, AttachmentBlend } from "../pipelineCache/index.js";
|
|
22
29
|
|
|
23
|
-
/** Discrete pipeline-state axes a `derivedMode` rule can target. v1
|
|
24
|
-
* supports cull / frontFace / topology / depthCompare / depthWrite /
|
|
25
|
-
* blendEnabled. Stencil + per-attachment blend factor/operation are
|
|
26
|
-
* deferred — same carve-outs as `derived-modes.md`. */
|
|
27
30
|
export type ModeAxis =
|
|
28
31
|
| "cull" | "frontFace" | "topology"
|
|
29
32
|
| "depthCompare" | "depthWrite"
|
|
30
|
-
| "alphaToCoverage"
|
|
33
|
+
| "alphaToCoverage"
|
|
34
|
+
/** Per-attachment blend state. The rule's u32 output indexes into
|
|
35
|
+
* the `values` array on `DerivedModeOptions` (or the `resolve`
|
|
36
|
+
* callback); each entry is a full {@link AttachmentBlend} struct
|
|
37
|
+
* with srcFactor / dstFactor / operation / writeMask / enabled
|
|
38
|
+
* fields — letting blend rules specify the entire mode object,
|
|
39
|
+
* not just an enum index. */
|
|
40
|
+
| "blend";
|
|
31
41
|
|
|
32
|
-
/** Output value type per axis. */
|
|
33
42
|
export type ModeValue<A extends ModeAxis> =
|
|
34
43
|
A extends "cull" ? CullMode :
|
|
35
44
|
A extends "frontFace" ? FrontFace :
|
|
@@ -37,147 +46,59 @@ export type ModeValue<A extends ModeAxis> =
|
|
|
37
46
|
A extends "depthCompare" ? GPUCompareFunction :
|
|
38
47
|
A extends "depthWrite" ? boolean :
|
|
39
48
|
A extends "alphaToCoverage" ? boolean :
|
|
49
|
+
A extends "blend" ? AttachmentBlend :
|
|
40
50
|
never;
|
|
41
51
|
|
|
42
|
-
/**
|
|
43
|
-
* The closure signature a `derivedMode` rule body takes:
|
|
44
|
-
* - `u`: a proxy whose properties resolve to the RO's uniform values
|
|
45
|
-
* at evaluation time. The proxy is type-erased in v1 — declare the
|
|
46
|
-
* uniforms you'll read via `UniformScope` augmentation on the
|
|
47
|
-
* wombat.shader side for compile-time safety, or annotate locally.
|
|
48
|
-
* - `declared`: the SG-declared value for this axis at this leaf.
|
|
49
|
-
* Identity rules `(u, d) => d` are valid and useful for axes that
|
|
50
|
-
* want to pass declared through unchanged.
|
|
51
|
-
*/
|
|
52
|
-
export type DerivedModeBuilder<A extends ModeAxis> = (
|
|
53
|
-
u: Record<string, unknown>,
|
|
54
|
-
declared: ModeValue<A>,
|
|
55
|
-
) => ModeValue<A>;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Opt-in GPU evaluation marker. When present on a rule, the heap
|
|
59
|
-
* scene runs a hard-coded WGSL kernel (see `./gpuKernel.ts`) instead
|
|
60
|
-
* of the CPU closure. The closure is retained for fallback (e.g.
|
|
61
|
-
* MockGPU tests, devices without compute, or before frame 0).
|
|
62
|
-
*
|
|
63
|
-
* v1 supports one kernel: `"flipCullByDeterminant"`. v2 generalizes
|
|
64
|
-
* via a traced IR builder.
|
|
65
|
-
*/
|
|
66
|
-
export interface GpuRuleSpec<A extends ModeAxis = ModeAxis> {
|
|
67
|
-
readonly kernel: "flipCullByDeterminant";
|
|
68
|
-
/** Name of the input uniform the kernel reads (e.g. "ModelTrafo"). */
|
|
69
|
-
readonly inputUniform: string;
|
|
70
|
-
/**
|
|
71
|
-
* Declared (SG-scope) value the kernel mutates by. Either a plain
|
|
72
|
-
* mode value (baked at scene-build) or an `aval` for reactive
|
|
73
|
-
* declared — the dispatcher re-uploads the kernel uniform on each
|
|
74
|
-
* dispatch from the aval's current value, so flipping it via a UI
|
|
75
|
-
* cval re-runs the rule with the new declared.
|
|
76
|
-
*/
|
|
77
|
-
readonly declared:
|
|
78
|
-
| ModeValue<A>
|
|
79
|
-
| import("@aardworx/wombat.adaptive").aval<ModeValue<A>>;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
52
|
export interface DerivedModeRule<A extends ModeAxis = ModeAxis> {
|
|
83
53
|
readonly __derivedModeRule: true;
|
|
84
|
-
readonly axis:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
54
|
+
readonly axis: A;
|
|
55
|
+
/** Shader-IR rule body — the entry's return Expr is the rule. For
|
|
56
|
+
* axes whose `ModeValue<A>` is a struct (e.g. blend's
|
|
57
|
+
* `AttachmentBlend`) the body returns object literals directly;
|
|
58
|
+
* the renderer fingerprints distinct returns and auto-assigns
|
|
59
|
+
* slot indices. For enum axes (cull etc.) the body returns u32
|
|
60
|
+
* enum indices that the renderer maps via the canonical table. */
|
|
61
|
+
readonly expr: RuleExpr<ModeValue<A> | number>;
|
|
88
62
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
63
|
+
* SG-context value for this axis. Filled in by `<Sg CullMode>` /
|
|
64
|
+
* `<Sg DepthTest>` / etc. at traversal time — users never write
|
|
65
|
+
* it. The rule body's `declared` ambient leaf resolves to this;
|
|
66
|
+
* when the underlying aval marks, heapScene re-specialises +
|
|
67
|
+
* swaps the cached kernel + pipeline set.
|
|
68
|
+
*
|
|
69
|
+
* `undefined` is the initial state right after `derivedMode(...)`;
|
|
70
|
+
* the SG fills it in before the rule reaches the renderer.
|
|
93
71
|
*/
|
|
94
|
-
readonly
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Author a derived mode rule.
|
|
99
|
-
*
|
|
100
|
-
* derivedMode("cull", (u, declared) =>
|
|
101
|
-
* u.WindingFlipped ? flipCull(declared) : declared);
|
|
102
|
-
*
|
|
103
|
-
* derivedMode("blend", (u) => u.Premultiplied ? "premul" : "straight");
|
|
104
|
-
*
|
|
105
|
-
* // Determinant-flip-cull (the motivating case):
|
|
106
|
-
* derivedMode("cull", (u, declared) => {
|
|
107
|
-
* const m = u.ModelTrafo as M44d;
|
|
108
|
-
* const det =
|
|
109
|
-
* m.M00 * (m.M11 * m.M22 - m.M12 * m.M21) -
|
|
110
|
-
* m.M01 * (m.M10 * m.M22 - m.M12 * m.M20) +
|
|
111
|
-
* m.M02 * (m.M10 * m.M21 - m.M11 * m.M20);
|
|
112
|
-
* return det < 0 ? flipCull(declared) : declared;
|
|
113
|
-
* });
|
|
114
|
-
*/
|
|
115
|
-
export function derivedMode<A extends ModeAxis>(
|
|
116
|
-
axis: A,
|
|
117
|
-
evaluate: DerivedModeBuilder<A>,
|
|
118
|
-
options: { readonly domain?: ReadonlyArray<ModeValue<A>> } = {},
|
|
119
|
-
): DerivedModeRule<A> {
|
|
120
|
-
const out: DerivedModeRule<A> = options.domain !== undefined
|
|
121
|
-
? { __derivedModeRule: true, axis, evaluate, domain: options.domain }
|
|
122
|
-
: { __derivedModeRule: true, axis, evaluate };
|
|
123
|
-
return out;
|
|
72
|
+
readonly declared: aval<ModeValue<A>> | ModeValue<A> | undefined;
|
|
124
73
|
}
|
|
125
74
|
|
|
126
|
-
/** Brand check — distinguishes a DerivedModeRule from an aval/raw value
|
|
127
|
-
* at runtime so PipelineState consumers can route accordingly. */
|
|
128
75
|
export function isDerivedModeRule(x: unknown): x is DerivedModeRule {
|
|
129
76
|
return typeof x === "object" && x !== null
|
|
130
77
|
&& (x as { __derivedModeRule?: unknown }).__derivedModeRule === true;
|
|
131
78
|
}
|
|
132
79
|
|
|
133
|
-
/** Convenience: flip 'back' ↔ 'front' (and pass through 'none').
|
|
134
|
-
* Useful in `(u, declared) => mirrored ? flipCull(declared) : declared`. */
|
|
135
|
-
export function flipCull(c: CullMode): CullMode {
|
|
136
|
-
return c === "back" ? "front" : c === "front" ? "back" : "none";
|
|
137
|
-
}
|
|
138
80
|
|
|
139
81
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* heapScene's compute path is available; the same logic is mirrored
|
|
143
|
-
* in the CPU closure for tests and fallback.
|
|
82
|
+
* Brand a `RuleExpr` (the shader-IR artefact emitted by the
|
|
83
|
+
* `rule(...)` build-time marker) as a derived-mode rule for `axis`.
|
|
144
84
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
85
|
+
* The `declared` option carries the SG-context value the rule reads
|
|
86
|
+
* via `declare const declared: number` in its body. heapScene
|
|
87
|
+
* substitutes it (as a Const) before codegen — when `declared` is a
|
|
88
|
+
* cval, every mark triggers a re-specialise + cached-kernel swap.
|
|
147
89
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* carry this rule.
|
|
90
|
+
* `domain` is NOT a parameter: the renderer infers the per-declared
|
|
91
|
+
* set of possible outputs from the rule's IR via
|
|
92
|
+
* `analyseOutputSet` + `evaluateSet`.
|
|
152
93
|
*/
|
|
153
|
-
export function
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
| import("@aardworx/wombat.adaptive").aval<CullMode>
|
|
158
|
-
= "back",
|
|
159
|
-
): DerivedModeRule<"cull"> {
|
|
94
|
+
export function derivedMode<A extends ModeAxis>(
|
|
95
|
+
axis: A,
|
|
96
|
+
expr: RuleExpr<ModeValue<A> | number>,
|
|
97
|
+
): DerivedModeRule<A> {
|
|
160
98
|
return {
|
|
161
99
|
__derivedModeRule: true,
|
|
162
|
-
axis
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
evaluate: (u, declaredFromCtx) => {
|
|
166
|
-
// Accept Trafo3d ({forward: M44d}) — the wombat.base shape used
|
|
167
|
-
// by SG ModelTrafo — or any object with M00..M22 accessors.
|
|
168
|
-
const raw = u[inputUniform];
|
|
169
|
-
type M = { M00: number; M01: number; M02: number;
|
|
170
|
-
M10: number; M11: number; M12: number;
|
|
171
|
-
M20: number; M21: number; M22: number };
|
|
172
|
-
const m: M | undefined =
|
|
173
|
-
(raw as { forward?: M }).forward
|
|
174
|
-
?? (raw as M);
|
|
175
|
-
if (m === undefined || typeof m.M00 !== "number") return declaredFromCtx;
|
|
176
|
-
const det =
|
|
177
|
-
m.M00 * (m.M11 * m.M22 - m.M12 * m.M21)
|
|
178
|
-
- m.M01 * (m.M10 * m.M22 - m.M12 * m.M20)
|
|
179
|
-
+ m.M02 * (m.M10 * m.M21 - m.M11 * m.M20);
|
|
180
|
-
return det < 0 ? flipCull(declaredFromCtx) : declaredFromCtx;
|
|
181
|
-
},
|
|
100
|
+
axis,
|
|
101
|
+
expr,
|
|
102
|
+
declared: undefined, // SG fills this in at traversal time
|
|
182
103
|
};
|
|
183
104
|
}
|
|
@@ -32,12 +32,13 @@ export function uploadConstituentsRange(
|
|
|
32
32
|
device.queue.writeBuffer(buf, startByte, mirror.buffer, mirror.byteOffset + startByte, endByte - startByte);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
/** Live getters for the GPU buffers the kernel binds
|
|
35
|
+
/** Live getters for the scene-wide GPU buffers the kernel binds. The
|
|
36
|
+
* main-heap binding is per-dispatch and supplied to `encodeChunk`,
|
|
37
|
+
* since one DerivedUniformsScene now serves multiple arena chunks
|
|
38
|
+
* (§3) and each chunk has its own main-heap GPUBuffer. */
|
|
36
39
|
export interface DerivedUniformsResources {
|
|
37
40
|
/** `array<vec2<f32>>` — df32 trafo halves (the constituents heap). */
|
|
38
41
|
readonly constituentsBuf: () => GPUBuffer;
|
|
39
|
-
/** `array<f32>` — the main heap (drawHeaders); written by the kernel. */
|
|
40
|
-
readonly mainHeapBuf: () => GPUBuffer;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function bglEntries(): GPUBindGroupLayoutEntry[] {
|
|
@@ -145,57 +146,83 @@ class UberPipeline {
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
/** Per-chunk dispatch state: GPU mirror of that chunk's records +
|
|
150
|
+
* a cached bind group keyed on (consts, heap, recs). */
|
|
151
|
+
interface ChunkState {
|
|
152
|
+
readonly gpu: RecordsGpu;
|
|
153
|
+
cachedBg: GPUBindGroup | undefined;
|
|
154
|
+
bgKey: [GPUBuffer | undefined, GPUBuffer | undefined, GPUBuffer | undefined];
|
|
155
|
+
}
|
|
156
|
+
|
|
148
157
|
export class DerivedUniformsDispatcher {
|
|
149
158
|
private readonly device: GPUDevice;
|
|
150
159
|
private readonly resources: DerivedUniformsResources;
|
|
151
|
-
private readonly gpu: RecordsGpu;
|
|
152
160
|
private readonly pipe: UberPipeline;
|
|
153
|
-
private
|
|
154
|
-
private bgKey: [GPUBuffer | undefined, GPUBuffer | undefined, GPUBuffer | undefined] = [undefined, undefined, undefined];
|
|
161
|
+
private readonly byChunk = new Map<RecordsBuffer, ChunkState>();
|
|
155
162
|
|
|
156
163
|
constructor(device: GPUDevice, resources: DerivedUniformsResources) {
|
|
157
164
|
this.device = device;
|
|
158
165
|
this.resources = resources;
|
|
159
|
-
this.gpu = new RecordsGpu(device);
|
|
160
166
|
this.pipe = new UberPipeline(device);
|
|
161
167
|
}
|
|
162
168
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
169
|
+
/** Run the uber-kernel for ONE chunk's records, binding
|
|
170
|
+
* `mainHeapGetter()` as the heap target. No-op if the chunk's
|
|
171
|
+
* records buffer is empty or the registry has no rules. */
|
|
172
|
+
encodeChunk(
|
|
173
|
+
enc: GPUCommandEncoder,
|
|
174
|
+
registry: DerivedUniformRegistry,
|
|
175
|
+
records: RecordsBuffer,
|
|
176
|
+
mainHeapGetter: () => GPUBuffer,
|
|
177
|
+
): boolean {
|
|
165
178
|
if (records.recordCount === 0) return false;
|
|
166
179
|
const pipeline = this.pipe.pipeline(registry, records.strideWords);
|
|
167
180
|
if (pipeline === undefined) return false;
|
|
168
|
-
this.
|
|
181
|
+
const state = this.stateFor(records);
|
|
182
|
+
state.gpu.sync(records);
|
|
169
183
|
const pass = enc.beginComputePass({ label: "derivedUniforms.uber" });
|
|
170
184
|
pass.setPipeline(pipeline);
|
|
171
|
-
pass.setBindGroup(0, this.bindGroup());
|
|
185
|
+
pass.setBindGroup(0, this.bindGroup(state, mainHeapGetter()));
|
|
172
186
|
pass.dispatchWorkgroups(Math.ceil(records.recordCount / 64));
|
|
173
187
|
pass.end();
|
|
174
188
|
return true;
|
|
175
189
|
}
|
|
176
190
|
|
|
177
191
|
dispose(): void {
|
|
178
|
-
this.gpu.dispose();
|
|
192
|
+
for (const s of this.byChunk.values()) s.gpu.dispose();
|
|
193
|
+
this.byChunk.clear();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private stateFor(records: RecordsBuffer): ChunkState {
|
|
197
|
+
let s = this.byChunk.get(records);
|
|
198
|
+
if (s === undefined) {
|
|
199
|
+
s = {
|
|
200
|
+
gpu: new RecordsGpu(this.device),
|
|
201
|
+
cachedBg: undefined,
|
|
202
|
+
bgKey: [undefined, undefined, undefined],
|
|
203
|
+
};
|
|
204
|
+
this.byChunk.set(records, s);
|
|
205
|
+
}
|
|
206
|
+
return s;
|
|
179
207
|
}
|
|
180
208
|
|
|
181
|
-
private bindGroup(): GPUBindGroup {
|
|
209
|
+
private bindGroup(state: ChunkState, heap: GPUBuffer): GPUBindGroup {
|
|
182
210
|
const consts = this.resources.constituentsBuf();
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return this.cachedBg;
|
|
211
|
+
const recs = state.gpu.buffer();
|
|
212
|
+
if (state.cachedBg !== undefined && state.bgKey[0] === consts && state.bgKey[1] === heap && state.bgKey[2] === recs) {
|
|
213
|
+
return state.cachedBg;
|
|
187
214
|
}
|
|
188
|
-
|
|
215
|
+
state.cachedBg = this.device.createBindGroup({
|
|
189
216
|
label: "derivedUniforms.uber",
|
|
190
217
|
layout: this.pipe.bgl,
|
|
191
218
|
entries: [
|
|
192
219
|
{ binding: 0, resource: { buffer: consts } },
|
|
193
220
|
{ binding: 1, resource: { buffer: heap } },
|
|
194
221
|
{ binding: 2, resource: { buffer: recs } },
|
|
195
|
-
{ binding: 3, resource: { buffer:
|
|
222
|
+
{ binding: 3, resource: { buffer: state.gpu.countBuf } },
|
|
196
223
|
],
|
|
197
224
|
});
|
|
198
|
-
|
|
199
|
-
return
|
|
225
|
+
state.bgKey = [consts, heap, recs];
|
|
226
|
+
return state.cachedBg;
|
|
200
227
|
}
|
|
201
228
|
}
|
|
@@ -49,27 +49,79 @@ function intrinsicExpr(name: string, type: Type, args: readonly Expr[]): Expr {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/**
|
|
52
|
+
/** Convertible-to-DerivedExpr operand. `aardvark-operators` rewrites
|
|
53
|
+
* e.g. `det < 0` to `det.lt(0)` — so every binary method accepts a
|
|
54
|
+
* raw number and lifts it to a Const matching the receiver's
|
|
55
|
+
* numeric type (f32 / u32 / i32). */
|
|
56
|
+
export type DerivedOperand = DerivedExpr | number;
|
|
57
|
+
|
|
58
|
+
/** True iff `t` is a numeric scalar IR type (Float / Int). */
|
|
59
|
+
function isNumericScalar(t: Type): boolean {
|
|
60
|
+
return t.kind === "Float" || t.kind === "Int";
|
|
61
|
+
}
|
|
62
|
+
/** Element type for scalar arithmetic: scalar types pass through,
|
|
63
|
+
* vector/matrix types unwrap to their element type, anything else
|
|
64
|
+
* defaults to f32 (the natural numeric on the GPU side). */
|
|
65
|
+
function scalarLiftType(t: Type): Type {
|
|
66
|
+
if (isNumericScalar(t)) return t;
|
|
67
|
+
if (t.kind === "Vector" || t.kind === "Matrix") return t.element;
|
|
68
|
+
return Tf32;
|
|
69
|
+
}
|
|
70
|
+
function liftConst(value: number, target: Type): Expr {
|
|
71
|
+
if (target.kind === "Int") {
|
|
72
|
+
if (target.signed) {
|
|
73
|
+
return { kind: "Const", type: target, value: { kind: "Int", value: value | 0, signed: true } } as Expr;
|
|
74
|
+
}
|
|
75
|
+
return { kind: "Const", type: target, value: { kind: "Int", value: value >>> 0, signed: false } } as Expr;
|
|
76
|
+
}
|
|
77
|
+
// Default: f32 const.
|
|
78
|
+
return { kind: "Const", type: Tf32, value: { kind: "Float", value } } as Expr;
|
|
79
|
+
}
|
|
80
|
+
/** Coerce `DerivedOperand` to a DerivedExpr, lifting raw numbers to a
|
|
81
|
+
* Const expression whose type matches the receiver's scalar type. */
|
|
82
|
+
function lift(other: DerivedOperand, receiverType: Type): DerivedExpr {
|
|
83
|
+
if (other instanceof DerivedExpr) return other;
|
|
84
|
+
return new DerivedExpr(liftConst(other, scalarLiftType(receiverType)));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A traced expression in a `derivedUniform` / `derivedMode` builder.
|
|
89
|
+
*
|
|
90
|
+
* Plays nicely with `boperators` (https://npmjs.com/package/boperators):
|
|
91
|
+
* the named-static operator methods (`static ["+"]`, `static ["*"]`,
|
|
92
|
+
* `static ["<"]`, …) make `a + b`, `a * b`, `a < b`, etc. on
|
|
93
|
+
* DerivedExprs rewrite to those statics at build time. Without the
|
|
94
|
+
* plugin, the same logic is reachable via the instance methods
|
|
95
|
+
* (`.add`, `.mul`, `.lt`, …).
|
|
96
|
+
*
|
|
97
|
+
* Ternary `a < b ? x : y` isn't rewritten — call `.select` on the
|
|
98
|
+
* resulting bool DerivedExpr:
|
|
99
|
+
*
|
|
100
|
+
* (det < 0).select(flipped, declared)
|
|
101
|
+
*/
|
|
53
102
|
export class DerivedExpr {
|
|
54
103
|
constructor(readonly ir: Expr) {}
|
|
55
104
|
get type(): Type { return this.ir.type; }
|
|
56
105
|
|
|
57
106
|
/** Matrix product `this · other` (also matrix·vector / vector·matrix by operand types). */
|
|
58
|
-
mul(other:
|
|
59
|
-
const
|
|
107
|
+
mul(other: DerivedOperand): DerivedExpr {
|
|
108
|
+
const o = lift(other, this.ir.type);
|
|
109
|
+
const a = this.ir.type, b = o.ir.type;
|
|
60
110
|
if (a.kind === "Matrix" && b.kind === "Matrix") {
|
|
61
|
-
return new DerivedExpr({ kind: "MulMatMat", lhs: this.ir, rhs:
|
|
111
|
+
return new DerivedExpr({ kind: "MulMatMat", lhs: this.ir, rhs: o.ir, type: { kind: "Matrix", element: a.element, rows: a.rows, cols: b.cols } });
|
|
62
112
|
}
|
|
63
113
|
if (a.kind === "Matrix" && b.kind === "Vector") {
|
|
64
|
-
return new DerivedExpr({ kind: "MulMatVec", lhs: this.ir, rhs:
|
|
114
|
+
return new DerivedExpr({ kind: "MulMatVec", lhs: this.ir, rhs: o.ir, type: { kind: "Vector", element: b.element, dim: a.rows } });
|
|
65
115
|
}
|
|
66
116
|
if (a.kind === "Vector" && b.kind === "Matrix") {
|
|
67
|
-
return new DerivedExpr({ kind: "MulVecMat", lhs: this.ir, rhs:
|
|
117
|
+
return new DerivedExpr({ kind: "MulVecMat", lhs: this.ir, rhs: o.ir, type: { kind: "Vector", element: a.element, dim: b.cols } });
|
|
68
118
|
}
|
|
69
|
-
return new DerivedExpr({ kind: "Mul", lhs: this.ir, rhs:
|
|
119
|
+
return new DerivedExpr({ kind: "Mul", lhs: this.ir, rhs: o.ir, type: a.kind === "Float" || a.kind === "Int" ? b : a });
|
|
70
120
|
}
|
|
71
|
-
add(other:
|
|
72
|
-
sub(other:
|
|
121
|
+
add(other: DerivedOperand): DerivedExpr { const o = lift(other, this.ir.type); return new DerivedExpr({ kind: "Add", lhs: this.ir, rhs: o.ir, type: this.ir.type }); }
|
|
122
|
+
sub(other: DerivedOperand): DerivedExpr { const o = lift(other, this.ir.type); return new DerivedExpr({ kind: "Sub", lhs: this.ir, rhs: o.ir, type: this.ir.type }); }
|
|
123
|
+
div(other: DerivedOperand): DerivedExpr { const o = lift(other, this.ir.type); return new DerivedExpr({ kind: "Div", lhs: this.ir, rhs: o.ir, type: this.ir.type }); }
|
|
124
|
+
mod(other: DerivedOperand): DerivedExpr { const o = lift(other, this.ir.type); return new DerivedExpr({ kind: "Mod", lhs: this.ir, rhs: o.ir, type: this.ir.type }); }
|
|
73
125
|
neg(): DerivedExpr { return new DerivedExpr({ kind: "Neg", value: this.ir, type: this.ir.type }); }
|
|
74
126
|
|
|
75
127
|
/** Inverse (of a constituent trafo this reads its stored backward half; otherwise unsupported in codegen). */
|
|
@@ -130,18 +182,160 @@ export class DerivedExpr {
|
|
|
130
182
|
log2(): DerivedExpr { return this.u1("log2"); }
|
|
131
183
|
sign(): DerivedExpr { return this.u1("sign"); }
|
|
132
184
|
normalize(): DerivedExpr { return this.u1("normalize"); }
|
|
133
|
-
min(o:
|
|
134
|
-
max(o:
|
|
135
|
-
pow(o:
|
|
136
|
-
atan2(o:
|
|
137
|
-
step(edge:
|
|
138
|
-
clamp(lo:
|
|
139
|
-
mix(o:
|
|
185
|
+
min(o: DerivedOperand): DerivedExpr { return this.bin("min", lift(o, this.ir.type)); }
|
|
186
|
+
max(o: DerivedOperand): DerivedExpr { return this.bin("max", lift(o, this.ir.type)); }
|
|
187
|
+
pow(o: DerivedOperand): DerivedExpr { return this.bin("pow", lift(o, this.ir.type)); }
|
|
188
|
+
atan2(o: DerivedOperand): DerivedExpr { return this.bin("atan2", lift(o, this.ir.type)); }
|
|
189
|
+
step(edge: DerivedOperand): DerivedExpr { const e = lift(edge, this.ir.type); return new DerivedExpr(intrinsicExpr("step", this.ir.type, [e.ir, this.ir])); }
|
|
190
|
+
clamp(lo: DerivedOperand, hi: DerivedOperand): DerivedExpr { const l = lift(lo, this.ir.type); const h = lift(hi, this.ir.type); return new DerivedExpr(intrinsicExpr("clamp", this.ir.type, [this.ir, l.ir, h.ir])); }
|
|
191
|
+
mix(o: DerivedOperand, t: DerivedOperand): DerivedExpr { const oo = lift(o, this.ir.type); const tt = lift(t, this.ir.type); return new DerivedExpr(intrinsicExpr("mix", this.ir.type, [this.ir, oo.ir, tt.ir])); }
|
|
140
192
|
dot(o: DerivedExpr): DerivedExpr { return new DerivedExpr(intrinsicExpr("dot", elemOf(this.ir.type), [this.ir, o.ir])); }
|
|
141
193
|
cross(o: DerivedExpr): DerivedExpr { return new DerivedExpr(intrinsicExpr("cross", this.ir.type, [this.ir, o.ir])); }
|
|
142
194
|
length(): DerivedExpr { return new DerivedExpr(intrinsicExpr("length", elemOf(this.ir.type), [this.ir])); }
|
|
143
195
|
distance(o: DerivedExpr): DerivedExpr { return new DerivedExpr(intrinsicExpr("distance", elemOf(this.ir.type), [this.ir, o.ir])); }
|
|
144
196
|
reflect(n: DerivedExpr): DerivedExpr { return new DerivedExpr(intrinsicExpr("reflect", this.ir.type, [this.ir, n.ir])); }
|
|
197
|
+
|
|
198
|
+
// ── Comparisons (produce a bool DerivedExpr). Receivers + arg can be
|
|
199
|
+
// raw numbers — `aardvark-operators` lifts `a < 2` to `a.lt(2)`,
|
|
200
|
+
// and we lift `2` to a const matching the receiver's scalar type. ──
|
|
201
|
+
private cmp(kind: "Eq" | "Neq" | "Lt" | "Le" | "Gt" | "Ge", other: DerivedOperand): DerivedExpr {
|
|
202
|
+
const o = lift(other, this.ir.type);
|
|
203
|
+
const ir = { kind, lhs: this.ir, rhs: o.ir, type: { kind: "Bool" } as Type } as Expr;
|
|
204
|
+
return new DerivedExpr(ir);
|
|
205
|
+
}
|
|
206
|
+
eq(o: DerivedOperand): DerivedExpr { return this.cmp("Eq", o); }
|
|
207
|
+
ne(o: DerivedOperand): DerivedExpr { return this.cmp("Neq", o); }
|
|
208
|
+
lt(o: DerivedOperand): DerivedExpr { return this.cmp("Lt", o); }
|
|
209
|
+
le(o: DerivedOperand): DerivedExpr { return this.cmp("Le", o); }
|
|
210
|
+
gt(o: DerivedOperand): DerivedExpr { return this.cmp("Gt", o); }
|
|
211
|
+
ge(o: DerivedOperand): DerivedExpr { return this.cmp("Ge", o); }
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Ternary select. The RECEIVER must be a bool — typically the
|
|
215
|
+
* result of a comparison — and the args are the then / else
|
|
216
|
+
* branches:
|
|
217
|
+
*
|
|
218
|
+
* (det < 0).select(flipped, declared) // det<0 ? flipped : declared
|
|
219
|
+
*
|
|
220
|
+
* The result type follows `then` (which must be widening-
|
|
221
|
+
* compatible with `else`). Raw numbers are lifted using `then`'s
|
|
222
|
+
* type when `then` is a DerivedExpr, else f32.
|
|
223
|
+
*/
|
|
224
|
+
select(thenE: DerivedOperand, elseE: DerivedOperand): DerivedExpr {
|
|
225
|
+
if (this.ir.type.kind !== "Bool") {
|
|
226
|
+
throw new Error("DerivedExpr.select(then, else): receiver must be a bool (typically a comparison)");
|
|
227
|
+
}
|
|
228
|
+
const t = thenE instanceof DerivedExpr ? thenE : lift(thenE, elseE instanceof DerivedExpr ? elseE.ir.type : Tf32);
|
|
229
|
+
const e = elseE instanceof DerivedExpr ? elseE : lift(elseE, t.ir.type);
|
|
230
|
+
const ir = { kind: "Conditional", cond: this.ir, ifTrue: t.ir, ifFalse: e.ir, type: t.ir.type } as Expr;
|
|
231
|
+
return new DerivedExpr(ir);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Determinant of a square matrix expression — yields a scalar f32. */
|
|
235
|
+
determinant(): DerivedExpr {
|
|
236
|
+
if (this.ir.type.kind !== "Matrix") {
|
|
237
|
+
throw new Error("DerivedExpr.determinant(): receiver must be a matrix expression");
|
|
238
|
+
}
|
|
239
|
+
return new DerivedExpr({ kind: "Determinant", value: this.ir, type: Tf32 } as Expr);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── Static literal builders. Mode rules in particular need u32 constants for enum
|
|
243
|
+
// indices (slot lookup). Floats for thresholds (e.g. determinant against zero).
|
|
244
|
+
static f32(value: number): DerivedExpr {
|
|
245
|
+
return new DerivedExpr({ kind: "Const", type: Tf32, value: { kind: "Float", value } } as Expr);
|
|
246
|
+
}
|
|
247
|
+
static u32(value: number): DerivedExpr {
|
|
248
|
+
return new DerivedExpr({ kind: "Const", type: Tu32, value: { kind: "Int", value: value >>> 0, signed: false } } as Expr);
|
|
249
|
+
}
|
|
250
|
+
static i32(value: number): DerivedExpr {
|
|
251
|
+
return new DerivedExpr({ kind: "Const", type: Ti32, value: { kind: "Int", value: value | 0, signed: true } } as Expr);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ─── boperators static dispatch ──────────────────────────────────────
|
|
255
|
+
// `a OP b` (where at least one operand is a DerivedExpr) is rewritten
|
|
256
|
+
// by the boperators plugin to `DerivedExpr["OP"](a, b)`. Either-order
|
|
257
|
+
// overloads (`number OP expr`, `expr OP number`) are handled by lifting
|
|
258
|
+
// the raw number to a Const matching the other operand's scalar type.
|
|
259
|
+
|
|
260
|
+
static "+"(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
261
|
+
static "+"(a: DerivedExpr, b: number): DerivedExpr;
|
|
262
|
+
static "+"(a: number, b: DerivedExpr): DerivedExpr;
|
|
263
|
+
static "+"(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
264
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
265
|
+
return x.add(b);
|
|
266
|
+
}
|
|
267
|
+
static "-"(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
268
|
+
static "-"(a: DerivedExpr, b: number): DerivedExpr;
|
|
269
|
+
static "-"(a: number, b: DerivedExpr): DerivedExpr;
|
|
270
|
+
static "-"(a: DerivedExpr): DerivedExpr; // unary minus
|
|
271
|
+
static "-"(a: DerivedOperand, b?: DerivedOperand): DerivedExpr {
|
|
272
|
+
if (b === undefined) return (a as DerivedExpr).neg();
|
|
273
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
274
|
+
return x.sub(b);
|
|
275
|
+
}
|
|
276
|
+
static "*"(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
277
|
+
static "*"(a: DerivedExpr, b: number): DerivedExpr;
|
|
278
|
+
static "*"(a: number, b: DerivedExpr): DerivedExpr;
|
|
279
|
+
static "*"(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
280
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
281
|
+
return x.mul(b);
|
|
282
|
+
}
|
|
283
|
+
static "/"(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
284
|
+
static "/"(a: DerivedExpr, b: number): DerivedExpr;
|
|
285
|
+
static "/"(a: number, b: DerivedExpr): DerivedExpr;
|
|
286
|
+
static "/"(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
287
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
288
|
+
return x.div(b);
|
|
289
|
+
}
|
|
290
|
+
static "%"(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
291
|
+
static "%"(a: DerivedExpr, b: number): DerivedExpr;
|
|
292
|
+
static "%"(a: number, b: DerivedExpr): DerivedExpr;
|
|
293
|
+
static "%"(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
294
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
295
|
+
return x.mod(b);
|
|
296
|
+
}
|
|
297
|
+
static "<"(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
298
|
+
static "<"(a: DerivedExpr, b: number): DerivedExpr;
|
|
299
|
+
static "<"(a: number, b: DerivedExpr): DerivedExpr;
|
|
300
|
+
static "<"(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
301
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
302
|
+
return x.lt(b);
|
|
303
|
+
}
|
|
304
|
+
static "<="(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
305
|
+
static "<="(a: DerivedExpr, b: number): DerivedExpr;
|
|
306
|
+
static "<="(a: number, b: DerivedExpr): DerivedExpr;
|
|
307
|
+
static "<="(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
308
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
309
|
+
return x.le(b);
|
|
310
|
+
}
|
|
311
|
+
static ">"(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
312
|
+
static ">"(a: DerivedExpr, b: number): DerivedExpr;
|
|
313
|
+
static ">"(a: number, b: DerivedExpr): DerivedExpr;
|
|
314
|
+
static ">"(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
315
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
316
|
+
return x.gt(b);
|
|
317
|
+
}
|
|
318
|
+
static ">="(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
319
|
+
static ">="(a: DerivedExpr, b: number): DerivedExpr;
|
|
320
|
+
static ">="(a: number, b: DerivedExpr): DerivedExpr;
|
|
321
|
+
static ">="(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
322
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
323
|
+
return x.ge(b);
|
|
324
|
+
}
|
|
325
|
+
static "=="(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
326
|
+
static "=="(a: DerivedExpr, b: number): DerivedExpr;
|
|
327
|
+
static "=="(a: number, b: DerivedExpr): DerivedExpr;
|
|
328
|
+
static "=="(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
329
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
330
|
+
return x.eq(b);
|
|
331
|
+
}
|
|
332
|
+
static "!="(a: DerivedExpr, b: DerivedExpr): DerivedExpr;
|
|
333
|
+
static "!="(a: DerivedExpr, b: number): DerivedExpr;
|
|
334
|
+
static "!="(a: number, b: DerivedExpr): DerivedExpr;
|
|
335
|
+
static "!="(a: DerivedOperand, b: DerivedOperand): DerivedExpr {
|
|
336
|
+
const x = a instanceof DerivedExpr ? a : lift(a, (b as DerivedExpr).ir.type);
|
|
337
|
+
return x.ne(b);
|
|
338
|
+
}
|
|
145
339
|
}
|
|
146
340
|
|
|
147
341
|
/** The `u` passed to a `derivedUniform` builder. `u.<Name>` is a uniform leaf — a mat4
|