@multiplekex/shallot 0.1.12 → 0.2.0
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/package.json +3 -4
- package/src/core/builder.ts +71 -32
- package/src/core/component.ts +25 -11
- package/src/core/index.ts +14 -13
- package/src/core/math.ts +135 -0
- package/src/core/runtime.ts +0 -1
- package/src/core/state.ts +9 -68
- package/src/core/xml.ts +381 -265
- package/src/editor/format.ts +5 -0
- package/src/editor/index.ts +101 -0
- package/src/extras/arrows/index.ts +28 -69
- package/src/extras/gradient/index.ts +36 -52
- package/src/extras/lines/index.ts +51 -122
- package/src/extras/orbit/index.ts +40 -15
- package/src/extras/text/font.ts +546 -0
- package/src/extras/text/index.ts +158 -204
- package/src/extras/text/sdf.ts +429 -0
- package/src/standard/activity/index.ts +172 -0
- package/src/standard/compute/graph.ts +23 -23
- package/src/standard/compute/index.ts +76 -61
- package/src/standard/defaults.ts +8 -5
- package/src/standard/index.ts +1 -0
- package/src/standard/input/index.ts +30 -19
- package/src/standard/loading/index.ts +18 -13
- package/src/standard/render/bvh/blas.ts +752 -0
- package/src/standard/render/bvh/radix.ts +476 -0
- package/src/standard/render/bvh/structs.ts +167 -0
- package/src/standard/render/bvh/tlas.ts +886 -0
- package/src/standard/render/bvh/traverse.ts +467 -0
- package/src/standard/render/camera.ts +302 -27
- package/src/standard/render/data.ts +93 -0
- package/src/standard/render/depth.ts +117 -0
- package/src/standard/render/forward/index.ts +259 -0
- package/src/standard/render/forward/raster.ts +228 -0
- package/src/standard/render/index.ts +443 -70
- package/src/standard/render/indirect.ts +40 -0
- package/src/standard/render/instance.ts +214 -0
- package/src/standard/render/intersection.ts +72 -0
- package/src/standard/render/light.ts +16 -16
- package/src/standard/render/mesh/index.ts +67 -75
- package/src/standard/render/mesh/unified.ts +96 -0
- package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
- package/src/standard/render/pass.ts +10 -4
- package/src/standard/render/postprocess.ts +142 -64
- package/src/standard/render/ray.ts +61 -0
- package/src/standard/render/scene.ts +38 -164
- package/src/standard/render/shaders.ts +484 -0
- package/src/standard/render/surface/compile.ts +3 -10
- package/src/standard/render/surface/index.ts +60 -30
- package/src/standard/render/surface/noise.ts +45 -0
- package/src/standard/render/surface/structs.ts +60 -19
- package/src/standard/render/surface/wgsl.ts +573 -0
- package/src/standard/render/triangle.ts +84 -0
- package/src/standard/transforms/index.ts +4 -6
- package/src/standard/tween/index.ts +10 -1
- package/src/standard/tween/sequence.ts +24 -16
- package/src/standard/tween/tween.ts +67 -16
- package/src/core/types.ts +0 -37
- package/src/standard/compute/inspect.ts +0 -201
- package/src/standard/compute/pass.ts +0 -23
- package/src/standard/compute/timing.ts +0 -139
- package/src/standard/render/forward.ts +0 -273
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { MeshData } from "./mesh";
|
|
2
|
+
import type { Vec3, Triangle } from "./bvh/structs";
|
|
3
|
+
|
|
4
|
+
export type { Triangle };
|
|
5
|
+
|
|
6
|
+
export interface FlatTriangle {
|
|
7
|
+
v0x: number;
|
|
8
|
+
v0y: number;
|
|
9
|
+
v0z: number;
|
|
10
|
+
v1x: number;
|
|
11
|
+
v1y: number;
|
|
12
|
+
v1z: number;
|
|
13
|
+
v2x: number;
|
|
14
|
+
v2y: number;
|
|
15
|
+
v2z: number;
|
|
16
|
+
n0x: number;
|
|
17
|
+
n0y: number;
|
|
18
|
+
n0z: number;
|
|
19
|
+
n1x: number;
|
|
20
|
+
n1y: number;
|
|
21
|
+
n1z: number;
|
|
22
|
+
n2x: number;
|
|
23
|
+
n2y: number;
|
|
24
|
+
n2z: number;
|
|
25
|
+
entityId: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function transformPoint(x: number, y: number, z: number, m: Float32Array): Vec3 {
|
|
29
|
+
return {
|
|
30
|
+
x: m[0] * x + m[4] * y + m[8] * z + m[12],
|
|
31
|
+
y: m[1] * x + m[5] * y + m[9] * z + m[13],
|
|
32
|
+
z: m[2] * x + m[6] * y + m[10] * z + m[14],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function transformNormal(nx: number, ny: number, nz: number, m: Float32Array): Vec3 {
|
|
37
|
+
const x = m[0] * nx + m[4] * ny + m[8] * nz;
|
|
38
|
+
const y = m[1] * nx + m[5] * ny + m[9] * nz;
|
|
39
|
+
const z = m[2] * nx + m[6] * ny + m[10] * nz;
|
|
40
|
+
const len = Math.sqrt(x * x + y * y + z * z);
|
|
41
|
+
if (len === 0) return { x: 0, y: 0, z: 0 };
|
|
42
|
+
return { x: x / len, y: y / len, z: z / len };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function extractTriangles(
|
|
46
|
+
mesh: MeshData,
|
|
47
|
+
entityId: number,
|
|
48
|
+
transform?: Float32Array
|
|
49
|
+
): Triangle[] {
|
|
50
|
+
const triangles: Triangle[] = [];
|
|
51
|
+
const { vertices, indices } = mesh;
|
|
52
|
+
const stride = 6;
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
55
|
+
const i0 = indices[i] * stride;
|
|
56
|
+
const i1 = indices[i + 1] * stride;
|
|
57
|
+
const i2 = indices[i + 2] * stride;
|
|
58
|
+
|
|
59
|
+
let v0: Vec3 = { x: vertices[i0], y: vertices[i0 + 1], z: vertices[i0 + 2] };
|
|
60
|
+
let n0: Vec3 = { x: vertices[i0 + 3], y: vertices[i0 + 4], z: vertices[i0 + 5] };
|
|
61
|
+
|
|
62
|
+
let v1: Vec3 = { x: vertices[i1], y: vertices[i1 + 1], z: vertices[i1 + 2] };
|
|
63
|
+
let n1: Vec3 = { x: vertices[i1 + 3], y: vertices[i1 + 4], z: vertices[i1 + 5] };
|
|
64
|
+
|
|
65
|
+
let v2: Vec3 = { x: vertices[i2], y: vertices[i2 + 1], z: vertices[i2 + 2] };
|
|
66
|
+
let n2: Vec3 = { x: vertices[i2 + 3], y: vertices[i2 + 4], z: vertices[i2 + 5] };
|
|
67
|
+
|
|
68
|
+
if (transform) {
|
|
69
|
+
v0 = transformPoint(v0.x, v0.y, v0.z, transform);
|
|
70
|
+
v1 = transformPoint(v1.x, v1.y, v1.z, transform);
|
|
71
|
+
v2 = transformPoint(v2.x, v2.y, v2.z, transform);
|
|
72
|
+
n0 = transformNormal(n0.x, n0.y, n0.z, transform);
|
|
73
|
+
n1 = transformNormal(n1.x, n1.y, n1.z, transform);
|
|
74
|
+
n2 = transformNormal(n2.x, n2.y, n2.z, transform);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const e1: Vec3 = { x: v1.x - v0.x, y: v1.y - v0.y, z: v1.z - v0.z };
|
|
78
|
+
const e2: Vec3 = { x: v2.x - v0.x, y: v2.y - v0.y, z: v2.z - v0.z };
|
|
79
|
+
|
|
80
|
+
triangles.push({ v0, e1, e2, n0, n1, n2, entityId });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return triangles;
|
|
84
|
+
}
|
|
@@ -123,11 +123,6 @@ setTraits(Transform, {
|
|
|
123
123
|
scaleY: 1,
|
|
124
124
|
scaleZ: 1,
|
|
125
125
|
}),
|
|
126
|
-
accessors: {
|
|
127
|
-
eulerX: Transform.eulerX,
|
|
128
|
-
eulerY: Transform.eulerY,
|
|
129
|
-
eulerZ: Transform.eulerZ,
|
|
130
|
-
},
|
|
131
126
|
});
|
|
132
127
|
|
|
133
128
|
async function init(): Promise<void> {
|
|
@@ -180,5 +175,8 @@ const TransformSystem: System = {
|
|
|
180
175
|
export const TransformsPlugin: Plugin = {
|
|
181
176
|
systems: [TransformSystem],
|
|
182
177
|
components: { Transform, WorldTransform },
|
|
183
|
-
initialize
|
|
178
|
+
async initialize(_state, onProgress) {
|
|
179
|
+
await init();
|
|
180
|
+
onProgress?.(1);
|
|
181
|
+
},
|
|
184
182
|
};
|
|
@@ -8,6 +8,15 @@ export {
|
|
|
8
8
|
type TweenOptions,
|
|
9
9
|
} from "./tween";
|
|
10
10
|
|
|
11
|
-
export {
|
|
11
|
+
export {
|
|
12
|
+
Sequence,
|
|
13
|
+
SequenceState,
|
|
14
|
+
Pause,
|
|
15
|
+
updateSequences,
|
|
16
|
+
resolveAll,
|
|
17
|
+
resolveSequence,
|
|
18
|
+
resetSequence,
|
|
19
|
+
checkCompletion,
|
|
20
|
+
} from "./sequence";
|
|
12
21
|
|
|
13
22
|
export { EASING_FUNCTIONS, getEasing, getEasingIndex, type EasingFn } from "./easing";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Pair } from "bitecs";
|
|
2
2
|
import { setTraits } from "../../core/component";
|
|
3
|
-
import { ChildOf, type State
|
|
3
|
+
import { ChildOf, type State } from "../../core";
|
|
4
4
|
import { Tween, TweenState, ensureResolved, captureFromValue } from "./tween";
|
|
5
5
|
|
|
6
6
|
const compareNumbers = (a: number, b: number) => a - b;
|
|
@@ -35,9 +35,7 @@ setTraits(Sequence, {
|
|
|
35
35
|
}),
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
function getChildrenSorted(state: State, parentEid: number): number[] {
|
|
38
|
+
function sortedChildren(state: State, parentEid: number): number[] {
|
|
41
39
|
childrenBuffer.length = 0;
|
|
42
40
|
for (const childEid of state.query([Pair(ChildOf.relation, parentEid)])) {
|
|
43
41
|
childrenBuffer.push(childEid);
|
|
@@ -47,7 +45,7 @@ function getChildrenSorted(state: State, parentEid: number): number[] {
|
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
export function computeTweenDelays(state: State, seqEid: number): void {
|
|
50
|
-
const children =
|
|
48
|
+
const children = sortedChildren(state, seqEid);
|
|
51
49
|
let cumulativeDelay = 0;
|
|
52
50
|
|
|
53
51
|
for (const childEid of children) {
|
|
@@ -59,7 +57,7 @@ export function computeTweenDelays(state: State, seqEid: number): void {
|
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
function
|
|
60
|
+
export function updateSequences(state: State, dt: number): void {
|
|
63
61
|
for (const seqEid of state.query([Sequence])) {
|
|
64
62
|
if (Sequence.state[seqEid] !== SequenceState.PLAYING) continue;
|
|
65
63
|
|
|
@@ -87,7 +85,8 @@ function updateSequencePlayheads(state: State, dt: number): void {
|
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
function
|
|
88
|
+
function resolve(state: State, seqEid: number): void {
|
|
89
|
+
computeTweenDelays(state, seqEid);
|
|
91
90
|
tweensBuffer.length = 0;
|
|
92
91
|
|
|
93
92
|
for (const childEid of state.query([Pair(ChildOf.relation, seqEid), Tween])) {
|
|
@@ -110,15 +109,30 @@ function ensureSequenceResolved(state: State, seqEid: number): void {
|
|
|
110
109
|
}
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
function
|
|
112
|
+
export function resolveSequence(state: State, seqEid: number): void {
|
|
113
|
+
if (Sequence.state[seqEid] === SequenceState.COMPLETE) return;
|
|
114
|
+
Sequence.state[seqEid] = SequenceState.COMPLETE;
|
|
115
|
+
resolve(state, seqEid);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function resetSequence(state: State, seqEid: number): void {
|
|
119
|
+
Sequence.state[seqEid] = SequenceState.IDLE;
|
|
120
|
+
Sequence.elapsed[seqEid] = 0;
|
|
121
|
+
for (const childEid of state.query([Pair(ChildOf.relation, seqEid), Tween])) {
|
|
122
|
+
Tween.state[childEid] = TweenState.IDLE;
|
|
123
|
+
Tween.elapsed[childEid] = 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function resolveAll(state: State): void {
|
|
114
128
|
for (const seqEid of state.query([Sequence])) {
|
|
115
129
|
if (Sequence.state[seqEid] === SequenceState.COMPLETE) {
|
|
116
|
-
|
|
130
|
+
resolve(state, seqEid);
|
|
117
131
|
}
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
|
|
121
|
-
function
|
|
135
|
+
export function checkCompletion(state: State): void {
|
|
122
136
|
for (const seqEid of state.query([Sequence])) {
|
|
123
137
|
if (Sequence.state[seqEid] !== SequenceState.PLAYING) continue;
|
|
124
138
|
|
|
@@ -138,9 +152,3 @@ function checkSequenceCompletion(state: State): void {
|
|
|
138
152
|
}
|
|
139
153
|
}
|
|
140
154
|
}
|
|
141
|
-
|
|
142
|
-
export function updateSequences(state: State, dt: number): void {
|
|
143
|
-
updateSequencePlayheads(state, dt);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export { ensureSequencesResolved, checkSequenceCompletion };
|
|
@@ -1,20 +1,71 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineRelation,
|
|
3
3
|
registerPostLoadHook,
|
|
4
|
+
toCamelCase,
|
|
4
5
|
type State,
|
|
5
6
|
type System,
|
|
6
7
|
type Plugin,
|
|
7
8
|
type PostLoadContext,
|
|
8
9
|
} from "../../core";
|
|
9
|
-
import { setTraits } from "../../core/component";
|
|
10
|
-
import { getEasingIndex, getEasing } from "./easing";
|
|
11
10
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "./
|
|
11
|
+
setTraits,
|
|
12
|
+
getRegisteredComponent,
|
|
13
|
+
type FieldAccessor,
|
|
14
|
+
type ComponentLike,
|
|
15
|
+
} from "../../core/component";
|
|
16
|
+
import { getEasingIndex, getEasing } from "./easing";
|
|
17
|
+
import { Sequence, Pause, updateSequences, resolveAll, checkCompletion } from "./sequence";
|
|
18
|
+
|
|
19
|
+
const fieldAccessors = new Map<number, FieldAccessor>();
|
|
20
|
+
|
|
21
|
+
function resolveFieldPath(path: string): { component: string; field: string } | null {
|
|
22
|
+
const dotIndex = path.lastIndexOf(".");
|
|
23
|
+
if (dotIndex === -1) return null;
|
|
24
|
+
return {
|
|
25
|
+
component: path.slice(0, dotIndex),
|
|
26
|
+
field: path.slice(dotIndex + 1),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function bindFieldAccessor(
|
|
31
|
+
bindingId: number,
|
|
32
|
+
componentName: string,
|
|
33
|
+
fieldPath: string
|
|
34
|
+
): FieldAccessor | null {
|
|
35
|
+
const registered = getRegisteredComponent(componentName);
|
|
36
|
+
if (!registered) return null;
|
|
37
|
+
|
|
38
|
+
const camelPath = toCamelCase(fieldPath);
|
|
39
|
+
const field = (registered.component as ComponentLike)[camelPath];
|
|
40
|
+
if (field == null) return null;
|
|
41
|
+
|
|
42
|
+
let accessor: FieldAccessor;
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
typeof field === "object" &&
|
|
46
|
+
typeof (field as FieldAccessor).get === "function" &&
|
|
47
|
+
typeof (field as FieldAccessor).set === "function"
|
|
48
|
+
) {
|
|
49
|
+
accessor = field as FieldAccessor;
|
|
50
|
+
} else if (ArrayBuffer.isView(field) || Array.isArray(field)) {
|
|
51
|
+
const data = field as number[];
|
|
52
|
+
accessor = {
|
|
53
|
+
get: (eid) => data[eid],
|
|
54
|
+
set: (eid, v) => {
|
|
55
|
+
data[eid] = v;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
} else {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fieldAccessors.set(bindingId, accessor);
|
|
63
|
+
return accessor;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getFieldAccessor(bindingId: number): FieldAccessor | undefined {
|
|
67
|
+
return fieldAccessors.get(bindingId);
|
|
68
|
+
}
|
|
18
69
|
|
|
19
70
|
function parseTweenAttrs(attrs: Record<string, string>): Record<string, string> {
|
|
20
71
|
if (attrs._value) {
|
|
@@ -126,7 +177,7 @@ export function finalizePendingTweens(state: State, context: PostLoadContext): v
|
|
|
126
177
|
const targetEid = context.getEntityByName(parsed.entity);
|
|
127
178
|
if (targetEid === null) continue;
|
|
128
179
|
|
|
129
|
-
const binding =
|
|
180
|
+
const binding = bindFieldAccessor(pending.tweenEid, parsed.component, parsed.field);
|
|
130
181
|
if (!binding) continue;
|
|
131
182
|
|
|
132
183
|
state.addRelation(pending.tweenEid, TweenTarget, targetEid);
|
|
@@ -144,7 +195,7 @@ export function finalizePendingTweens(state: State, context: PostLoadContext): v
|
|
|
144
195
|
|
|
145
196
|
export function captureFromValue(state: State, tweenEid: number): void {
|
|
146
197
|
const targetEid = state.getFirstRelationTarget(tweenEid, TweenTarget);
|
|
147
|
-
const binding =
|
|
198
|
+
const binding = getFieldAccessor(tweenEid);
|
|
148
199
|
|
|
149
200
|
if (binding && targetEid >= 0) {
|
|
150
201
|
Tween.from[tweenEid] = binding.get(targetEid) ?? 0;
|
|
@@ -158,7 +209,7 @@ export function ensureResolved(state: State, tweenEid: number): void {
|
|
|
158
209
|
if (elapsed >= duration) return;
|
|
159
210
|
|
|
160
211
|
const targetEid = state.getFirstRelationTarget(tweenEid, TweenTarget);
|
|
161
|
-
const binding =
|
|
212
|
+
const binding = getFieldAccessor(tweenEid);
|
|
162
213
|
|
|
163
214
|
if (binding && targetEid >= 0) {
|
|
164
215
|
const toValue = Tween.to[tweenEid];
|
|
@@ -184,7 +235,7 @@ function updateTweens(state: State, dt: number): void {
|
|
|
184
235
|
if (tweenState !== TweenState.PLAYING) continue;
|
|
185
236
|
|
|
186
237
|
const targetEid = state.getFirstRelationTarget(tweenEid, TweenTarget);
|
|
187
|
-
const binding =
|
|
238
|
+
const binding = getFieldAccessor(tweenEid);
|
|
188
239
|
|
|
189
240
|
if (Tween.elapsed[tweenEid] === 0 && binding && targetEid >= 0) {
|
|
190
241
|
Tween.from[tweenEid] = binding.get(targetEid) ?? 0;
|
|
@@ -237,13 +288,13 @@ export function createTween(
|
|
|
237
288
|
fieldPath: string,
|
|
238
289
|
options: TweenOptions
|
|
239
290
|
): number | null {
|
|
240
|
-
const parsed =
|
|
291
|
+
const parsed = resolveFieldPath(fieldPath);
|
|
241
292
|
if (!parsed) return null;
|
|
242
293
|
|
|
243
294
|
const tweenEid = state.addEntity();
|
|
244
295
|
state.addComponent(tweenEid, Tween);
|
|
245
296
|
|
|
246
|
-
const binding =
|
|
297
|
+
const binding = bindFieldAccessor(tweenEid, parsed.component, parsed.field);
|
|
247
298
|
if (!binding) {
|
|
248
299
|
state.removeEntity(tweenEid);
|
|
249
300
|
return null;
|
|
@@ -265,10 +316,10 @@ export const TweenSystem: System = {
|
|
|
265
316
|
update(state: State) {
|
|
266
317
|
const dt = state.time.deltaTime;
|
|
267
318
|
|
|
268
|
-
|
|
319
|
+
resolveAll(state);
|
|
269
320
|
updateSequences(state, dt);
|
|
270
321
|
updateTweens(state, dt);
|
|
271
|
-
|
|
322
|
+
checkCompletion(state);
|
|
272
323
|
},
|
|
273
324
|
};
|
|
274
325
|
|
package/src/core/types.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { System } from "./scheduler";
|
|
2
|
-
import type { ComponentLike } from "./component";
|
|
3
|
-
import type { RelationDef } from "./relation";
|
|
4
|
-
import type { State } from "./state";
|
|
5
|
-
|
|
6
|
-
export interface Plugin {
|
|
7
|
-
readonly systems?: readonly System[];
|
|
8
|
-
readonly components?: Record<string, ComponentLike>;
|
|
9
|
-
readonly relations?: readonly RelationDef[];
|
|
10
|
-
readonly dependencies?: readonly Plugin[];
|
|
11
|
-
readonly initialize?: (state: State) => void | Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface Loading {
|
|
15
|
-
show(): (() => void) | void;
|
|
16
|
-
update(progress: number): void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface MouseState {
|
|
20
|
-
deltaX: number;
|
|
21
|
-
deltaY: number;
|
|
22
|
-
scrollDelta: number;
|
|
23
|
-
left: boolean;
|
|
24
|
-
right: boolean;
|
|
25
|
-
middle: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface InputState {
|
|
29
|
-
readonly mouse: Readonly<MouseState>;
|
|
30
|
-
isKeyDown(code: string): boolean;
|
|
31
|
-
isKeyPressed(code: string): boolean;
|
|
32
|
-
isKeyReleased(code: string): boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface RenderContext {
|
|
36
|
-
readonly gpu: unknown;
|
|
37
|
-
}
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import type { ComputeNode, NodeId, ResourceId, ResourceRef } from "./graph";
|
|
2
|
-
import type { FrameTiming } from "./timing";
|
|
3
|
-
import { Pass, PASS_ORDER } from "./pass";
|
|
4
|
-
|
|
5
|
-
export interface NodeInfo {
|
|
6
|
-
readonly id: NodeId;
|
|
7
|
-
readonly pass: Pass;
|
|
8
|
-
readonly inputs: readonly ResourceRef[];
|
|
9
|
-
readonly outputs: readonly ResourceRef[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface EdgeInfo {
|
|
13
|
-
readonly from: NodeId;
|
|
14
|
-
readonly to: NodeId;
|
|
15
|
-
readonly resource: ResourceId;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface GraphInspection {
|
|
19
|
-
readonly nodes: readonly NodeInfo[];
|
|
20
|
-
readonly edges: readonly EdgeInfo[];
|
|
21
|
-
readonly executionOrder: readonly NodeId[];
|
|
22
|
-
readonly byPass: ReadonlyMap<Pass, readonly NodeId[]>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function inspect(nodes: readonly ComputeNode[]): GraphInspection {
|
|
26
|
-
const nodeInfos: NodeInfo[] = nodes.map((n) => ({
|
|
27
|
-
id: n.id,
|
|
28
|
-
pass: n.pass ?? Pass.Opaque,
|
|
29
|
-
inputs: n.inputs,
|
|
30
|
-
outputs: n.outputs,
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
const producers = new Map<ResourceId, NodeId>();
|
|
34
|
-
for (const n of nodes) {
|
|
35
|
-
for (const out of n.outputs) {
|
|
36
|
-
producers.set(out.id, n.id);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const edges: EdgeInfo[] = [];
|
|
41
|
-
for (const n of nodes) {
|
|
42
|
-
for (const inp of n.inputs) {
|
|
43
|
-
const producer = producers.get(inp.id);
|
|
44
|
-
if (producer) {
|
|
45
|
-
edges.push({
|
|
46
|
-
from: producer,
|
|
47
|
-
to: n.id,
|
|
48
|
-
resource: inp.id,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const byPass = new Map<Pass, NodeId[]>();
|
|
55
|
-
for (const pass of PASS_ORDER) {
|
|
56
|
-
byPass.set(pass, []);
|
|
57
|
-
}
|
|
58
|
-
for (const n of nodeInfos) {
|
|
59
|
-
byPass.get(n.pass)!.push(n.id);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const executionOrder = topoSortIds(nodes);
|
|
63
|
-
|
|
64
|
-
return { nodes: nodeInfos, edges, executionOrder, byPass };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function topoSortIds(nodes: readonly ComputeNode[]): NodeId[] {
|
|
68
|
-
if (nodes.length === 0) return [];
|
|
69
|
-
|
|
70
|
-
const producers = new Map<ResourceId, ComputeNode>();
|
|
71
|
-
for (const node of nodes) {
|
|
72
|
-
for (const output of node.outputs) {
|
|
73
|
-
producers.set(output.id, node);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const byPass = new Map<Pass, ComputeNode[]>();
|
|
78
|
-
for (const pass of PASS_ORDER) {
|
|
79
|
-
byPass.set(pass, []);
|
|
80
|
-
}
|
|
81
|
-
for (const node of nodes) {
|
|
82
|
-
const pass = node.pass ?? Pass.Opaque;
|
|
83
|
-
byPass.get(pass)!.push(node);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const sorted: NodeId[] = [];
|
|
87
|
-
for (const pass of PASS_ORDER) {
|
|
88
|
-
const passNodes = byPass.get(pass)!;
|
|
89
|
-
const passInDegree = new Map<ComputeNode, number>();
|
|
90
|
-
|
|
91
|
-
for (const node of passNodes) {
|
|
92
|
-
let degree = 0;
|
|
93
|
-
for (const input of node.inputs) {
|
|
94
|
-
const producer = producers.get(input.id);
|
|
95
|
-
if (producer && passNodes.includes(producer)) {
|
|
96
|
-
degree++;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
passInDegree.set(node, degree);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const queue: ComputeNode[] = [];
|
|
103
|
-
for (const node of passNodes) {
|
|
104
|
-
if (passInDegree.get(node) === 0) {
|
|
105
|
-
queue.push(node);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let i = 0;
|
|
110
|
-
while (i < queue.length) {
|
|
111
|
-
const node = queue[i++];
|
|
112
|
-
sorted.push(node.id);
|
|
113
|
-
|
|
114
|
-
for (const dep of passNodes) {
|
|
115
|
-
for (const input of dep.inputs) {
|
|
116
|
-
const producer = producers.get(input.id);
|
|
117
|
-
if (producer === node) {
|
|
118
|
-
const newDegree = passInDegree.get(dep)! - 1;
|
|
119
|
-
passInDegree.set(dep, newDegree);
|
|
120
|
-
if (newDegree === 0) {
|
|
121
|
-
queue.push(dep);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return sorted;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function formatGraph(info: GraphInspection, timing?: FrameTiming): string {
|
|
133
|
-
const lines: string[] = [];
|
|
134
|
-
lines.push("=== Compute Graph ===");
|
|
135
|
-
lines.push("");
|
|
136
|
-
|
|
137
|
-
const nodeMap = new Map(info.nodes.map((n) => [n.id, n]));
|
|
138
|
-
const timingMap = new Map(timing?.nodes.map((t) => [t.nodeId, t]));
|
|
139
|
-
|
|
140
|
-
for (const pass of PASS_ORDER) {
|
|
141
|
-
const nodeIds = info.byPass.get(pass) ?? [];
|
|
142
|
-
if (nodeIds.length === 0) continue;
|
|
143
|
-
|
|
144
|
-
lines.push(`[${Pass[pass]}]`);
|
|
145
|
-
for (const id of nodeIds) {
|
|
146
|
-
const node = nodeMap.get(id)!;
|
|
147
|
-
const ins = node.inputs.map((r) => r.id).join(", ") || "none";
|
|
148
|
-
const outs = node.outputs.map((r) => r.id).join(", ") || "none";
|
|
149
|
-
lines.push(` ${id}: ${ins} -> ${outs}`);
|
|
150
|
-
}
|
|
151
|
-
lines.push("");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
lines.push("Execution Order:");
|
|
155
|
-
info.executionOrder.forEach((id, i) => {
|
|
156
|
-
lines.push(` ${i + 1}. ${id}`);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (timing) {
|
|
160
|
-
lines.push("");
|
|
161
|
-
lines.push("Timing:");
|
|
162
|
-
for (const nodeId of info.executionOrder) {
|
|
163
|
-
const nodeTiming = timingMap.get(nodeId);
|
|
164
|
-
if (nodeTiming) {
|
|
165
|
-
lines.push(` ${nodeId}: ${nodeTiming.cpuMs.toFixed(2)}ms`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
lines.push(` Total: ${timing.totalCpuMs.toFixed(2)}ms`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return lines.join("\n");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function toDot(info: GraphInspection): string {
|
|
175
|
-
const lines: string[] = ["digraph ComputeGraph {"];
|
|
176
|
-
lines.push(" rankdir=LR;");
|
|
177
|
-
lines.push(" node [shape=box];");
|
|
178
|
-
lines.push("");
|
|
179
|
-
|
|
180
|
-
for (const pass of PASS_ORDER) {
|
|
181
|
-
const nodeIds = info.byPass.get(pass) ?? [];
|
|
182
|
-
if (nodeIds.length === 0) continue;
|
|
183
|
-
|
|
184
|
-
const passName = Pass[pass];
|
|
185
|
-
lines.push(` subgraph cluster_${passName} {`);
|
|
186
|
-
lines.push(` label="${passName}";`);
|
|
187
|
-
for (const id of nodeIds) {
|
|
188
|
-
lines.push(` "${id}";`);
|
|
189
|
-
}
|
|
190
|
-
lines.push(" }");
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
lines.push("");
|
|
194
|
-
|
|
195
|
-
for (const edge of info.edges) {
|
|
196
|
-
lines.push(` "${edge.from}" -> "${edge.to}" [label="${edge.resource}"];`);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
lines.push("}");
|
|
200
|
-
return lines.join("\n");
|
|
201
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export enum Pass {
|
|
2
|
-
BeforeOpaque,
|
|
3
|
-
Opaque,
|
|
4
|
-
AfterOpaque,
|
|
5
|
-
BeforeTransparent,
|
|
6
|
-
Transparent,
|
|
7
|
-
AfterTransparent,
|
|
8
|
-
BeforePost,
|
|
9
|
-
Post,
|
|
10
|
-
AfterPost,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const PASS_ORDER: Pass[] = [
|
|
14
|
-
Pass.BeforeOpaque,
|
|
15
|
-
Pass.Opaque,
|
|
16
|
-
Pass.AfterOpaque,
|
|
17
|
-
Pass.BeforeTransparent,
|
|
18
|
-
Pass.Transparent,
|
|
19
|
-
Pass.AfterTransparent,
|
|
20
|
-
Pass.BeforePost,
|
|
21
|
-
Pass.Post,
|
|
22
|
-
Pass.AfterPost,
|
|
23
|
-
];
|