@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.
Files changed (62) hide show
  1. package/package.json +3 -4
  2. package/src/core/builder.ts +71 -32
  3. package/src/core/component.ts +25 -11
  4. package/src/core/index.ts +14 -13
  5. package/src/core/math.ts +135 -0
  6. package/src/core/runtime.ts +0 -1
  7. package/src/core/state.ts +9 -68
  8. package/src/core/xml.ts +381 -265
  9. package/src/editor/format.ts +5 -0
  10. package/src/editor/index.ts +101 -0
  11. package/src/extras/arrows/index.ts +28 -69
  12. package/src/extras/gradient/index.ts +36 -52
  13. package/src/extras/lines/index.ts +51 -122
  14. package/src/extras/orbit/index.ts +40 -15
  15. package/src/extras/text/font.ts +546 -0
  16. package/src/extras/text/index.ts +158 -204
  17. package/src/extras/text/sdf.ts +429 -0
  18. package/src/standard/activity/index.ts +172 -0
  19. package/src/standard/compute/graph.ts +23 -23
  20. package/src/standard/compute/index.ts +76 -61
  21. package/src/standard/defaults.ts +8 -5
  22. package/src/standard/index.ts +1 -0
  23. package/src/standard/input/index.ts +30 -19
  24. package/src/standard/loading/index.ts +18 -13
  25. package/src/standard/render/bvh/blas.ts +752 -0
  26. package/src/standard/render/bvh/radix.ts +476 -0
  27. package/src/standard/render/bvh/structs.ts +167 -0
  28. package/src/standard/render/bvh/tlas.ts +886 -0
  29. package/src/standard/render/bvh/traverse.ts +467 -0
  30. package/src/standard/render/camera.ts +302 -27
  31. package/src/standard/render/data.ts +93 -0
  32. package/src/standard/render/depth.ts +117 -0
  33. package/src/standard/render/forward/index.ts +259 -0
  34. package/src/standard/render/forward/raster.ts +228 -0
  35. package/src/standard/render/index.ts +443 -70
  36. package/src/standard/render/indirect.ts +40 -0
  37. package/src/standard/render/instance.ts +214 -0
  38. package/src/standard/render/intersection.ts +72 -0
  39. package/src/standard/render/light.ts +16 -16
  40. package/src/standard/render/mesh/index.ts +67 -75
  41. package/src/standard/render/mesh/unified.ts +96 -0
  42. package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
  43. package/src/standard/render/pass.ts +10 -4
  44. package/src/standard/render/postprocess.ts +142 -64
  45. package/src/standard/render/ray.ts +61 -0
  46. package/src/standard/render/scene.ts +38 -164
  47. package/src/standard/render/shaders.ts +484 -0
  48. package/src/standard/render/surface/compile.ts +3 -10
  49. package/src/standard/render/surface/index.ts +60 -30
  50. package/src/standard/render/surface/noise.ts +45 -0
  51. package/src/standard/render/surface/structs.ts +60 -19
  52. package/src/standard/render/surface/wgsl.ts +573 -0
  53. package/src/standard/render/triangle.ts +84 -0
  54. package/src/standard/transforms/index.ts +4 -6
  55. package/src/standard/tween/index.ts +10 -1
  56. package/src/standard/tween/sequence.ts +24 -16
  57. package/src/standard/tween/tween.ts +67 -16
  58. package/src/core/types.ts +0 -37
  59. package/src/standard/compute/inspect.ts +0 -201
  60. package/src/standard/compute/pass.ts +0 -23
  61. package/src/standard/compute/timing.ts +0 -139
  62. 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: init,
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 { Sequence, SequenceState, Pause, finalizeSequences } from "./sequence";
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, type PostLoadContext } from "../../core";
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
- export function finalizeSequences(_state: State, _context: PostLoadContext): void {}
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 = getChildrenSorted(state, seqEid);
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 updateSequencePlayheads(state: State, dt: number): void {
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 ensureSequenceResolved(state: State, seqEid: number): void {
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 ensureSequencesResolved(state: State): void {
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
- ensureSequenceResolved(state, seqEid);
130
+ resolve(state, seqEid);
117
131
  }
118
132
  }
119
133
  }
120
134
 
121
- function checkSequenceCompletion(state: State): void {
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
- Sequence,
13
- Pause,
14
- updateSequences,
15
- ensureSequencesResolved,
16
- checkSequenceCompletion,
17
- } from "./sequence";
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 = state.bindFieldAccessor(pending.tweenEid, parsed.component, parsed.field);
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 = state.getFieldAccessor(tweenEid);
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 = state.getFieldAccessor(tweenEid);
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 = state.getFieldAccessor(tweenEid);
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 = state.resolveFieldPath(fieldPath);
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 = state.bindFieldAccessor(tweenEid, parsed.component, parsed.field);
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
- ensureSequencesResolved(state);
319
+ resolveAll(state);
269
320
  updateSequences(state, dt);
270
321
  updateTweens(state, dt);
271
- checkSequenceCompletion(state);
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
- ];