@multiplekex/shallot 0.1.9 → 0.1.10

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.
@@ -2,3 +2,4 @@ export * from "./orbit";
2
2
  export * from "./lines";
3
3
  export * from "./arrows";
4
4
  export * from "./text";
5
+ export * from "./gradient";
@@ -5,9 +5,10 @@ import {
5
5
  Render,
6
6
  RenderPlugin,
7
7
  DEPTH_FORMAT,
8
- registerDrawContributor,
9
- type DrawContributor,
10
- type DrawContext,
8
+ Pass,
9
+ registerDraw,
10
+ type Draw,
11
+ type SharedPassContext,
11
12
  } from "../../standard/render";
12
13
  import { Transform } from "../../standard/transforms";
13
14
 
@@ -316,15 +317,18 @@ export function createLinesPipeline(
316
317
  });
317
318
  }
318
319
 
319
- function createLinesContributor(config: LinesConfig): DrawContributor {
320
+ function createLinesDraw(config: LinesConfig): Draw {
320
321
  let pipeline: GPURenderPipeline | null = null;
321
322
  let bindGroup: GPUBindGroup | null = null;
322
323
 
323
324
  return {
324
325
  id: "lines",
326
+ pass: Pass.Transparent,
325
327
  order: 0,
326
328
 
327
- draw(pass: GPURenderPassEncoder, ctx: DrawContext) {
329
+ execute() {},
330
+
331
+ draw(pass: GPURenderPassEncoder, ctx: SharedPassContext) {
328
332
  const count = config.getCount();
329
333
  if (count === 0) return;
330
334
 
@@ -377,7 +381,8 @@ const LinesSystem: System = {
377
381
  entityIdArray[count++] = eid;
378
382
  }
379
383
 
380
- device.queue.writeBuffer(lines.buffer, 0, LineData.data);
384
+ const uploadCount = state.maxEid + 1;
385
+ device.queue.writeBuffer(lines.buffer, 0, LineData.data, 0, uploadCount * 12);
381
386
  device.queue.writeBuffer(lines.entityIds, 0, entityIdArray, 0, count);
382
387
  lines.count = count;
383
388
  },
@@ -407,9 +412,9 @@ export const LinesPlugin: Plugin = {
407
412
 
408
413
  state.setResource(Lines, linesState);
409
414
 
410
- registerDrawContributor(
415
+ registerDraw(
411
416
  state,
412
- createLinesContributor({
417
+ createLinesDraw({
413
418
  scene: render.scene,
414
419
  lines: linesState.buffer,
415
420
  entityIds: linesState.entityIds,
@@ -14,9 +14,10 @@ import {
14
14
  Render,
15
15
  RenderPlugin,
16
16
  DEPTH_FORMAT,
17
- registerDrawContributor,
18
- type DrawContributor,
19
- type DrawContext,
17
+ Pass,
18
+ registerDraw,
19
+ type Draw,
20
+ type SharedPassContext,
20
21
  } from "../../standard/render";
21
22
  import { Transform } from "../../standard/transforms";
22
23
 
@@ -646,15 +647,18 @@ export interface TextConfig {
646
647
  getCount: () => number;
647
648
  }
648
649
 
649
- function createTextContributor(config: TextConfig): DrawContributor {
650
+ function createTextDraw(config: TextConfig): Draw {
650
651
  let pipeline: GPURenderPipeline | null = null;
651
652
  let bindGroup: GPUBindGroup | null = null;
652
653
 
653
654
  return {
654
655
  id: "text",
656
+ pass: Pass.Transparent,
655
657
  order: 2,
656
658
 
657
- draw(pass: GPURenderPassEncoder, ctx: DrawContext) {
659
+ execute() {},
660
+
661
+ draw(pass: GPURenderPassEncoder, ctx: SharedPassContext) {
658
662
  const count = config.getCount();
659
663
  if (count === 0) return;
660
664
 
@@ -805,9 +809,9 @@ export const TextPlugin: Plugin = {
805
809
 
806
810
  state.setResource(TextResource, textState);
807
811
 
808
- registerDrawContributor(
812
+ registerDraw(
809
813
  state,
810
- createTextContributor({
814
+ createTextDraw({
811
815
  scene: render.scene,
812
816
  glyphs: textState.buffer,
813
817
  atlas: atlas.textureView,
@@ -1,11 +1,9 @@
1
1
  import { CycleError } from "../../core";
2
2
  import { inspect as inspectGraph, type GraphInspection } from "./inspect";
3
+ import { Pass, PASS_ORDER } from "./pass";
3
4
 
4
5
  export type ResourceId = string;
5
6
  export type NodeId = string;
6
- export type Phase = "opaque" | "transparent" | "postprocess";
7
-
8
- const PHASE_ORDER: Phase[] = ["opaque", "transparent", "postprocess"];
9
7
 
10
8
  export interface ResourceRef {
11
9
  id: ResourceId;
@@ -29,7 +27,7 @@ export interface ExecutionContext {
29
27
 
30
28
  export interface ComputeNode {
31
29
  readonly id: NodeId;
32
- readonly phase?: Phase;
30
+ readonly pass?: Pass;
33
31
  readonly inputs: readonly ResourceRef[];
34
32
  readonly outputs: readonly ResourceRef[];
35
33
  readonly execute: (ctx: ExecutionContext) => void;
@@ -113,20 +111,20 @@ function compile(nodes: ComputeNode[]): ExecutionPlan {
113
111
  return { sorted: [] };
114
112
  }
115
113
 
116
- const byPhase = new Map<Phase, ComputeNode[]>();
117
- for (const phase of PHASE_ORDER) {
118
- byPhase.set(phase, []);
114
+ const byPass = new Map<Pass, ComputeNode[]>();
115
+ for (const pass of PASS_ORDER) {
116
+ byPass.set(pass, []);
119
117
  }
120
118
 
121
119
  for (const node of nodes) {
122
- const phase = node.phase ?? "opaque";
123
- byPhase.get(phase)!.push(node);
120
+ const pass = node.pass ?? Pass.Opaque;
121
+ byPass.get(pass)!.push(node);
124
122
  }
125
123
 
126
124
  const sorted: ComputeNode[] = [];
127
- for (const phase of PHASE_ORDER) {
128
- const phaseNodes = byPhase.get(phase)!;
129
- sorted.push(...topoSort(phaseNodes));
125
+ for (const pass of PASS_ORDER) {
126
+ const passNodes = byPass.get(pass)!;
127
+ sorted.push(...topoSort(passNodes));
130
128
  }
131
129
 
132
130
  return { sorted };
@@ -4,6 +4,7 @@ import { createTimingCollector, type FrameTiming } from "./timing";
4
4
 
5
5
  export * from "./graph";
6
6
  export * from "./inspect";
7
+ export * from "./pass";
7
8
  export * from "./readback";
8
9
  export * from "./timing";
9
10
 
@@ -1,9 +1,10 @@
1
- import type { ComputeNode, NodeId, Phase, ResourceId, ResourceRef } from "./graph";
1
+ import type { ComputeNode, NodeId, ResourceId, ResourceRef } from "./graph";
2
2
  import type { FrameTiming } from "./timing";
3
+ import { Pass, PASS_ORDER } from "./pass";
3
4
 
4
5
  export interface NodeInfo {
5
6
  readonly id: NodeId;
6
- readonly phase: Phase;
7
+ readonly pass: Pass;
7
8
  readonly inputs: readonly ResourceRef[];
8
9
  readonly outputs: readonly ResourceRef[];
9
10
  }
@@ -18,15 +19,13 @@ export interface GraphInspection {
18
19
  readonly nodes: readonly NodeInfo[];
19
20
  readonly edges: readonly EdgeInfo[];
20
21
  readonly executionOrder: readonly NodeId[];
21
- readonly byPhase: ReadonlyMap<Phase, readonly NodeId[]>;
22
+ readonly byPass: ReadonlyMap<Pass, readonly NodeId[]>;
22
23
  }
23
24
 
24
- const PHASE_ORDER: Phase[] = ["opaque", "transparent", "postprocess"];
25
-
26
25
  export function inspect(nodes: readonly ComputeNode[]): GraphInspection {
27
26
  const nodeInfos: NodeInfo[] = nodes.map((n) => ({
28
27
  id: n.id,
29
- phase: n.phase ?? "opaque",
28
+ pass: n.pass ?? Pass.Opaque,
30
29
  inputs: n.inputs,
31
30
  outputs: n.outputs,
32
31
  }));
@@ -52,17 +51,17 @@ export function inspect(nodes: readonly ComputeNode[]): GraphInspection {
52
51
  }
53
52
  }
54
53
 
55
- const byPhase = new Map<Phase, NodeId[]>();
56
- for (const phase of PHASE_ORDER) {
57
- byPhase.set(phase, []);
54
+ const byPass = new Map<Pass, NodeId[]>();
55
+ for (const pass of PASS_ORDER) {
56
+ byPass.set(pass, []);
58
57
  }
59
58
  for (const n of nodeInfos) {
60
- byPhase.get(n.phase)!.push(n.id);
59
+ byPass.get(n.pass)!.push(n.id);
61
60
  }
62
61
 
63
62
  const executionOrder = topoSortIds(nodes);
64
63
 
65
- return { nodes: nodeInfos, edges, executionOrder, byPhase };
64
+ return { nodes: nodeInfos, edges, executionOrder, byPass };
66
65
  }
67
66
 
68
67
  function topoSortIds(nodes: readonly ComputeNode[]): NodeId[] {
@@ -75,52 +74,34 @@ function topoSortIds(nodes: readonly ComputeNode[]): NodeId[] {
75
74
  }
76
75
  }
77
76
 
78
- const adjacency = new Map<ComputeNode, ComputeNode[]>();
79
- const inDegree = new Map<ComputeNode, number>();
80
-
81
- for (const node of nodes) {
82
- adjacency.set(node, []);
83
- inDegree.set(node, 0);
84
- }
85
-
86
- for (const node of nodes) {
87
- for (const input of node.inputs) {
88
- const producer = producers.get(input.id);
89
- if (producer) {
90
- adjacency.get(producer)!.push(node);
91
- inDegree.set(node, inDegree.get(node)! + 1);
92
- }
93
- }
94
- }
95
-
96
- const byPhase = new Map<Phase, ComputeNode[]>();
97
- for (const phase of PHASE_ORDER) {
98
- byPhase.set(phase, []);
77
+ const byPass = new Map<Pass, ComputeNode[]>();
78
+ for (const pass of PASS_ORDER) {
79
+ byPass.set(pass, []);
99
80
  }
100
81
  for (const node of nodes) {
101
- const phase = node.phase ?? "opaque";
102
- byPhase.get(phase)!.push(node);
82
+ const pass = node.pass ?? Pass.Opaque;
83
+ byPass.get(pass)!.push(node);
103
84
  }
104
85
 
105
86
  const sorted: NodeId[] = [];
106
- for (const phase of PHASE_ORDER) {
107
- const phaseNodes = byPhase.get(phase)!;
108
- const phaseInDegree = new Map<ComputeNode, number>();
87
+ for (const pass of PASS_ORDER) {
88
+ const passNodes = byPass.get(pass)!;
89
+ const passInDegree = new Map<ComputeNode, number>();
109
90
 
110
- for (const node of phaseNodes) {
91
+ for (const node of passNodes) {
111
92
  let degree = 0;
112
93
  for (const input of node.inputs) {
113
94
  const producer = producers.get(input.id);
114
- if (producer && phaseNodes.includes(producer)) {
95
+ if (producer && passNodes.includes(producer)) {
115
96
  degree++;
116
97
  }
117
98
  }
118
- phaseInDegree.set(node, degree);
99
+ passInDegree.set(node, degree);
119
100
  }
120
101
 
121
102
  const queue: ComputeNode[] = [];
122
- for (const node of phaseNodes) {
123
- if (phaseInDegree.get(node) === 0) {
103
+ for (const node of passNodes) {
104
+ if (passInDegree.get(node) === 0) {
124
105
  queue.push(node);
125
106
  }
126
107
  }
@@ -130,12 +111,12 @@ function topoSortIds(nodes: readonly ComputeNode[]): NodeId[] {
130
111
  const node = queue[i++];
131
112
  sorted.push(node.id);
132
113
 
133
- for (const dep of phaseNodes) {
114
+ for (const dep of passNodes) {
134
115
  for (const input of dep.inputs) {
135
116
  const producer = producers.get(input.id);
136
117
  if (producer === node) {
137
- const newDegree = phaseInDegree.get(dep)! - 1;
138
- phaseInDegree.set(dep, newDegree);
118
+ const newDegree = passInDegree.get(dep)! - 1;
119
+ passInDegree.set(dep, newDegree);
139
120
  if (newDegree === 0) {
140
121
  queue.push(dep);
141
122
  }
@@ -156,11 +137,11 @@ export function formatGraph(info: GraphInspection, timing?: FrameTiming): string
156
137
  const nodeMap = new Map(info.nodes.map((n) => [n.id, n]));
157
138
  const timingMap = new Map(timing?.nodes.map((t) => [t.nodeId, t]));
158
139
 
159
- for (const phase of PHASE_ORDER) {
160
- const nodeIds = info.byPhase.get(phase) ?? [];
140
+ for (const pass of PASS_ORDER) {
141
+ const nodeIds = info.byPass.get(pass) ?? [];
161
142
  if (nodeIds.length === 0) continue;
162
143
 
163
- lines.push(`[${phase}]`);
144
+ lines.push(`[${Pass[pass]}]`);
164
145
  for (const id of nodeIds) {
165
146
  const node = nodeMap.get(id)!;
166
147
  const ins = node.inputs.map((r) => r.id).join(", ") || "none";
@@ -196,12 +177,13 @@ export function toDot(info: GraphInspection): string {
196
177
  lines.push(" node [shape=box];");
197
178
  lines.push("");
198
179
 
199
- for (const phase of PHASE_ORDER) {
200
- const nodeIds = info.byPhase.get(phase) ?? [];
180
+ for (const pass of PASS_ORDER) {
181
+ const nodeIds = info.byPass.get(pass) ?? [];
201
182
  if (nodeIds.length === 0) continue;
202
183
 
203
- lines.push(` subgraph cluster_${phase} {`);
204
- lines.push(` label="${phase}";`);
184
+ const passName = Pass[pass];
185
+ lines.push(` subgraph cluster_${passName} {`);
186
+ lines.push(` label="${passName}";`);
205
187
  for (const id of nodeIds) {
206
188
  lines.push(` "${id}";`);
207
189
  }
@@ -0,0 +1,23 @@
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
+ ];
@@ -1,7 +1,7 @@
1
1
  import { setTraits } from "../../core/component";
2
2
  import { WorldTransform } from "../transforms";
3
3
  import { clearColor } from "./forward";
4
- import { perspective, orthographic, multiply, invert } from "./scene";
4
+ import { perspective, orthographic, multiply, invert, extractFrustumPlanes } from "./scene";
5
5
 
6
6
  export const RenderMode = {
7
7
  Raster: 0,
@@ -98,9 +98,14 @@ export function uploadCamera(
98
98
 
99
99
  device.queue.writeBuffer(buffer, 0, viewProj as Float32Array<ArrayBuffer>);
100
100
  device.queue.writeBuffer(buffer, 64, world as Float32Array<ArrayBuffer>);
101
+
102
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
101
103
  device.queue.writeBuffer(
102
104
  buffer,
103
105
  176,
104
- new Float32Array([Camera.mode[eid], Camera.size[eid], width, height])
106
+ new Float32Array([Camera.mode[eid], Camera.size[eid], width / dpr, height / dpr])
105
107
  );
108
+
109
+ const planes = extractFrustumPlanes(viewProj);
110
+ device.queue.writeBuffer(buffer, 192, planes as Float32Array<ArrayBuffer>);
106
111
  }