@aethervtt/shard-render 0.0.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.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/assets.d.ts +104 -0
  4. package/dist/assets.js +213 -0
  5. package/dist/atmosphere-model.d.ts +96 -0
  6. package/dist/atmosphere-model.js +370 -0
  7. package/dist/atmosphere-nodes.d.ts +55 -0
  8. package/dist/atmosphere-nodes.js +780 -0
  9. package/dist/atmosphere-plugin.d.ts +5 -0
  10. package/dist/atmosphere-plugin.js +42 -0
  11. package/dist/atmosphere-shaders.d.ts +7 -0
  12. package/dist/atmosphere-shaders.js +595 -0
  13. package/dist/atmosphere-state.d.ts +72 -0
  14. package/dist/atmosphere-state.js +18 -0
  15. package/dist/atmosphere.d.ts +191 -0
  16. package/dist/atmosphere.js +950 -0
  17. package/dist/camera.d.ts +83 -0
  18. package/dist/camera.js +163 -0
  19. package/dist/clusters.d.ts +68 -0
  20. package/dist/clusters.js +288 -0
  21. package/dist/culling-shaders.d.ts +2 -0
  22. package/dist/culling-shaders.js +203 -0
  23. package/dist/culling.d.ts +84 -0
  24. package/dist/culling.js +498 -0
  25. package/dist/debug-shaders.d.ts +3 -0
  26. package/dist/debug-shaders.js +207 -0
  27. package/dist/debug-views.d.ts +60 -0
  28. package/dist/debug-views.js +197 -0
  29. package/dist/deferred-plugin.d.ts +5 -0
  30. package/dist/deferred-plugin.js +20 -0
  31. package/dist/deferred-shaders.d.ts +2 -0
  32. package/dist/deferred-shaders.js +151 -0
  33. package/dist/deferred.d.ts +22 -0
  34. package/dist/deferred.js +296 -0
  35. package/dist/deform.d.ts +80 -0
  36. package/dist/deform.js +213 -0
  37. package/dist/describe-plugin.d.ts +5 -0
  38. package/dist/describe-plugin.js +19 -0
  39. package/dist/display-nodes.d.ts +5 -0
  40. package/dist/display-nodes.js +189 -0
  41. package/dist/dynamic-resolution.d.ts +47 -0
  42. package/dist/dynamic-resolution.js +191 -0
  43. package/dist/environment-plugin.d.ts +6 -0
  44. package/dist/environment-plugin.js +44 -0
  45. package/dist/environment-shaders.d.ts +2 -0
  46. package/dist/environment-shaders.js +260 -0
  47. package/dist/environment-state.d.ts +71 -0
  48. package/dist/environment-state.js +144 -0
  49. package/dist/environment.d.ts +70 -0
  50. package/dist/environment.js +529 -0
  51. package/dist/forward.d.ts +123 -0
  52. package/dist/forward.js +1216 -0
  53. package/dist/fxaa.d.ts +2 -0
  54. package/dist/fxaa.js +65 -0
  55. package/dist/gizmo-font.d.ts +21 -0
  56. package/dist/gizmo-font.js +132 -0
  57. package/dist/gizmos-plugin.d.ts +5 -0
  58. package/dist/gizmos-plugin.js +34 -0
  59. package/dist/gizmos.d.ts +156 -0
  60. package/dist/gizmos.js +772 -0
  61. package/dist/gpu-assets.d.ts +114 -0
  62. package/dist/gpu-assets.js +494 -0
  63. package/dist/graph.d.ts +204 -0
  64. package/dist/graph.js +417 -0
  65. package/dist/index.d.ts +53 -0
  66. package/dist/index.js +540 -0
  67. package/dist/instances.d.ts +349 -0
  68. package/dist/instances.js +1335 -0
  69. package/dist/large-world.d.ts +13 -0
  70. package/dist/large-world.js +20 -0
  71. package/dist/lights.d.ts +160 -0
  72. package/dist/lights.js +468 -0
  73. package/dist/material-noise.d.ts +19 -0
  74. package/dist/material-noise.js +7 -0
  75. package/dist/material-pipelines.d.ts +46 -0
  76. package/dist/material-pipelines.js +166 -0
  77. package/dist/materials.d.ts +108 -0
  78. package/dist/materials.js +219 -0
  79. package/dist/noise.d.ts +76 -0
  80. package/dist/noise.js +286 -0
  81. package/dist/overlay-registry.d.ts +18 -0
  82. package/dist/overlay-registry.js +30 -0
  83. package/dist/overlays.d.ts +35 -0
  84. package/dist/overlays.js +344 -0
  85. package/dist/paths.d.ts +20 -0
  86. package/dist/paths.js +46 -0
  87. package/dist/picking-plugin.d.ts +2 -0
  88. package/dist/picking-plugin.js +20 -0
  89. package/dist/picking.d.ts +94 -0
  90. package/dist/picking.js +456 -0
  91. package/dist/pixel-perfect-plugin.d.ts +9 -0
  92. package/dist/pixel-perfect-plugin.js +135 -0
  93. package/dist/pixel-perfect.d.ts +27 -0
  94. package/dist/pixel-perfect.js +70 -0
  95. package/dist/plugin.d.ts +111 -0
  96. package/dist/plugin.js +182 -0
  97. package/dist/pool.d.ts +19 -0
  98. package/dist/pool.js +43 -0
  99. package/dist/post-common.d.ts +51 -0
  100. package/dist/post-common.js +148 -0
  101. package/dist/post-nodes.d.ts +39 -0
  102. package/dist/post-nodes.js +849 -0
  103. package/dist/post-plugin.d.ts +5 -0
  104. package/dist/post-plugin.js +26 -0
  105. package/dist/post-shaders.d.ts +8 -0
  106. package/dist/post-shaders.js +698 -0
  107. package/dist/post.d.ts +175 -0
  108. package/dist/post.js +453 -0
  109. package/dist/readback.d.ts +4 -0
  110. package/dist/readback.js +95 -0
  111. package/dist/render-scale.d.ts +53 -0
  112. package/dist/render-scale.js +63 -0
  113. package/dist/shaders.d.ts +122 -0
  114. package/dist/shaders.js +1168 -0
  115. package/dist/shadows.d.ts +119 -0
  116. package/dist/shadows.js +562 -0
  117. package/dist/skin-asset.d.ts +26 -0
  118. package/dist/skin-asset.js +25 -0
  119. package/dist/skinning-plugin.d.ts +5 -0
  120. package/dist/skinning-plugin.js +16 -0
  121. package/dist/skinning.d.ts +19 -0
  122. package/dist/skinning.js +343 -0
  123. package/dist/standard-fields.d.ts +67 -0
  124. package/dist/standard-fields.js +73 -0
  125. package/dist/standard.d.ts +7 -0
  126. package/dist/standard.js +37 -0
  127. package/dist/stats.d.ts +25 -0
  128. package/dist/stats.js +18 -0
  129. package/dist/target.d.ts +55 -0
  130. package/dist/target.js +70 -0
  131. package/dist/testing.d.ts +34 -0
  132. package/dist/testing.js +126 -0
  133. package/dist/timer.d.ts +28 -0
  134. package/dist/timer.js +136 -0
  135. package/dist/view.d.ts +117 -0
  136. package/dist/view.js +389 -0
  137. package/dist/visibility.d.ts +10 -0
  138. package/dist/visibility.js +84 -0
  139. package/package.json +49 -0
@@ -0,0 +1,204 @@
1
+ import { type World } from '@aethervtt/shard-core';
2
+ import type { GpuContext } from '@aethervtt/shard-gpu';
3
+ import { TexturePool } from './pool.js';
4
+ import type { RenderTarget } from './target.js';
5
+ import { GpuTimer } from './timer.js';
6
+ /** Each view's final output. Nodes that write it are what the graph exists to run. */
7
+ export declare const VIEW_TARGET = "view-target";
8
+ type PerView<T> = T | ((view: RenderView) => T);
9
+ /** A per-frame texture the graph allocates from its pool. */
10
+ export interface TransientTexture {
11
+ name: string;
12
+ /** A format, or 'view' for the view target's format (e.g. an MSAA color buffer). */
13
+ format: PerView<GPUTextureFormat | 'view'>;
14
+ /** Pixel size, 'view' to match the view's target (the default), or a divisor of it. */
15
+ size?: PerView<'view' | [number, number] | {
16
+ divide: number;
17
+ }>;
18
+ sampleCount?: PerView<number>;
19
+ /** Default: RENDER_ATTACHMENT | TEXTURE_BINDING. */
20
+ usage?: GPUTextureUsageFlags;
21
+ mipLevelCount?: PerView<number>;
22
+ }
23
+ export type ResourceRef = string | TransientTexture;
24
+ export interface ColorAttachment {
25
+ resource: string;
26
+ /** Clears to this color (or a per-view color); omit to load existing contents. */
27
+ clear?: GPUColor | ((view: RenderView) => GPUColor);
28
+ /** Resolve target for MSAA. */
29
+ resolve?: string;
30
+ /** Mip level of the attachment (default 0). */
31
+ mip?: number;
32
+ }
33
+ export interface DepthAttachment {
34
+ resource: string;
35
+ /** Clears to this depth; omit to load. Reversed-Z renderers clear to 0. */
36
+ clear?: number;
37
+ /** Depth is only tested, not written: the attachment is bound read-only. */
38
+ readOnly?: boolean;
39
+ }
40
+ export interface RenderView {
41
+ /** Unique per frame, e.g. the camera entity or 'window'. */
42
+ name: string;
43
+ target: RenderTarget;
44
+ /**
45
+ * Render resolution (0051): the size of 'view'-sized textures when it differs from the target's.
46
+ * `view-target` is always the target itself.
47
+ */
48
+ width?: number;
49
+ height?: number;
50
+ /** Lower renders first. */
51
+ order: number;
52
+ /** Per-view data for nodes (camera matrices, etc.). */
53
+ data: Record<string, unknown>;
54
+ /**
55
+ * Resource names that are another resource in this view, e.g. `scene-color → hdr` when there's
56
+ * no MSAA. Writing an alias writes its target.
57
+ */
58
+ aliases?: Readonly<Record<string, string>>;
59
+ }
60
+ export interface NodeContext {
61
+ readonly gpu: GpuContext;
62
+ readonly world: World;
63
+ readonly view: RenderView;
64
+ readonly encoder: GPUCommandEncoder;
65
+ /** Set for render nodes. */
66
+ readonly renderPass: GPURenderPassEncoder | undefined;
67
+ /** Set for compute nodes. */
68
+ readonly computePass: GPUComputePassEncoder | undefined;
69
+ texture(name: string): GPUTexture;
70
+ /** Timestamp writes for a pass the node begins itself (raw nodes), labeled `name`. */
71
+ timestamps(name: string): GPURenderPassTimestampWrites | undefined;
72
+ /** Runs after the frame's commands are submitted (e.g. to map a readback buffer). */
73
+ afterSubmit(fn: () => void): void;
74
+ }
75
+ export interface NodeDescriptor {
76
+ kind: 'render' | 'compute' | 'raw';
77
+ reads?: readonly string[];
78
+ writes?: readonly ResourceRef[];
79
+ /** Explicit ordering for side effects the resource lists don't capture. */
80
+ after?: readonly string[];
81
+ /**
82
+ * Ordering among writers of the same resource, and among independent nodes: lower runs first.
83
+ * Nodes in the same phase keep registration order. See `RenderPhase`.
84
+ */
85
+ phase?: number;
86
+ /** Runs even if nothing reads its outputs. */
87
+ sideEffects?: boolean;
88
+ /**
89
+ * Whether the node runs for a view (e.g. only when the camera has the effect's component).
90
+ * Disabled nodes are removed before resolving, so what they fed is culled too.
91
+ */
92
+ enabled?: (view: RenderView) => boolean;
93
+ /** Render nodes: attachments, set up from resources. */
94
+ color?: readonly ColorAttachment[] | ((view: RenderView) => readonly ColorAttachment[]);
95
+ depth?: DepthAttachment;
96
+ run(ctx: NodeContext): void;
97
+ }
98
+ /** Standard phases, spaced so plugins can slot nodes in between. */
99
+ export declare const RenderPhase: {
100
+ readonly Setup: 0;
101
+ readonly Shadows: 100;
102
+ readonly Prepass: 200;
103
+ readonly Opaque: 300;
104
+ readonly Lighting: 350;
105
+ readonly Sky: 400;
106
+ readonly Sprites: 450;
107
+ readonly Transparent: 500;
108
+ readonly Effects3d: 550;
109
+ readonly Resolve: 600;
110
+ readonly Post: 700;
111
+ readonly Tonemap: 900;
112
+ readonly Display: 950;
113
+ readonly Overlay: 1000;
114
+ readonly Debug: 1100;
115
+ };
116
+ export interface ResolvedGraph {
117
+ order: string[];
118
+ culled: string[];
119
+ }
120
+ export interface CapturedImage {
121
+ width: number;
122
+ height: number;
123
+ /** RGBA8, row by row, no padding. */
124
+ data: Uint8Array;
125
+ }
126
+ /** A captured buffer of any format: floats, 4 channels per pixel (depth in R), row by row. */
127
+ export interface CapturedBuffer {
128
+ width: number;
129
+ height: number;
130
+ format: GPUTextureFormat;
131
+ data: Float32Array;
132
+ }
133
+ /**
134
+ * Orders nodes from their resource dependencies and drops nodes whose outputs nothing needs.
135
+ * Pure: no GPU involved, so it's testable on its own.
136
+ *
137
+ * Rules: writers of a resource run in phase order, then registration order; a reader runs after
138
+ * the writers of what it reads in its own and earlier phases, and before writers in later phases;
139
+ * `after` adds explicit edges. A node is needed if it writes an
140
+ * output (the view target), has `sideEffects`, or writes something a needed node reads. `aliases`
141
+ * maps resource names to the resource they really are for this view.
142
+ */
143
+ export declare function resolveGraph(nodes: ReadonlyMap<string, NodeDescriptor>, outputs?: ReadonlySet<string>, options?: {
144
+ aliases?: Readonly<Record<string, string>>;
145
+ view?: RenderView;
146
+ }): ResolvedGraph;
147
+ /**
148
+ * Named passes that declare what they read and write. The graph resolves an order per view
149
+ * configuration (enabled nodes, aliases), then runs it for every view each frame, allocating
150
+ * transient textures from a pool.
151
+ */
152
+ export declare class RenderGraph {
153
+ readonly pool: TexturePool;
154
+ readonly timer: GpuTimer;
155
+ private readonly nodes;
156
+ /** Transient textures any node can use by name. */
157
+ private readonly declared;
158
+ private readonly gpu;
159
+ private readonly resolutions;
160
+ /** The last resolution per view name, for describe(). */
161
+ private readonly lastByView;
162
+ private captures;
163
+ /** Views rendered in the last frame, in order. */
164
+ private lastViews;
165
+ private readonly viewData;
166
+ constructor(gpu: GpuContext);
167
+ addNode(name: string, node: NodeDescriptor): void;
168
+ removeNode(name: string): void;
169
+ hasNode(name: string): boolean;
170
+ /** The data of a view rendered in the last frame. */
171
+ lastViewData(view: string): Record<string, unknown> | undefined;
172
+ /** Declares a transient texture any node can write, read, or attach by name. */
173
+ declare(texture: TransientTexture): void;
174
+ /** The resolution with every node enabled and no aliases. */
175
+ resolve(): ResolvedGraph;
176
+ private resolveFor;
177
+ /** Whether a node after `k` loads or reads `resource` (so its contents must be stored). */
178
+ private loadedLater;
179
+ /**
180
+ * Copies a view's image after the next frame renders it. `buffer` names any texture resource
181
+ * (default: the view target): 8-bit color formats resolve as `CapturedImage`, others as floats.
182
+ */
183
+ capture(view: string): Promise<CapturedImage>;
184
+ capture(view: string, buffer: string): Promise<CapturedImage | CapturedBuffer>;
185
+ describe(): {
186
+ order: {
187
+ name: string;
188
+ kind: "compute" | "raw" | "render";
189
+ reads: string[];
190
+ writes: string[];
191
+ }[];
192
+ culled: string[];
193
+ perView: {
194
+ [k: string]: {
195
+ order: string[];
196
+ culled: string[];
197
+ };
198
+ };
199
+ pooledTextures: number;
200
+ };
201
+ /** Runs the graph for every view and submits once. */
202
+ execute(world: World, views: readonly RenderView[]): void;
203
+ }
204
+ export {};
package/dist/graph.js ADDED
@@ -0,0 +1,417 @@
1
+ import { ShardError } from "@aethervtt/shard-core";
2
+ import { TexturePool } from "./pool.js";
3
+ import { BYTES_PER_TEXEL, toFloats } from "./readback.js";
4
+ import { GpuTimer } from "./timer.js";
5
+ const VIEW_TARGET = "view-target";
6
+ const RenderPhase = {
7
+ Setup: 0,
8
+ Shadows: 100,
9
+ Prepass: 200,
10
+ Opaque: 300,
11
+ Lighting: 350,
12
+ Sky: 400,
13
+ Sprites: 450,
14
+ Transparent: 500,
15
+ Effects3d: 550,
16
+ Resolve: 600,
17
+ Post: 700,
18
+ Tonemap: 900,
19
+ Display: 950,
20
+ Overlay: 1e3,
21
+ Debug: 1100
22
+ };
23
+ const resourceName = (ref) => typeof ref === "string" ? ref : ref.name;
24
+ function canonicalOf(aliases, name) {
25
+ if (!aliases) return name;
26
+ let n = name;
27
+ for (let i = 0; i < 8 && aliases[n] !== void 0; i++) n = aliases[n];
28
+ return n;
29
+ }
30
+ function colorOf(node, view) {
31
+ const c = node.color;
32
+ if (typeof c === "function") return view ? c(view) : [];
33
+ return c ?? [];
34
+ }
35
+ function resolveGraph(nodes, outputs = /* @__PURE__ */ new Set([VIEW_TARGET]), options = {}) {
36
+ const canon = (name) => canonicalOf(options.aliases, name);
37
+ const registration = [...nodes.keys()];
38
+ const rank = new Map(registration.map((n, i) => [n, i]));
39
+ const names = [...registration].sort(
40
+ (a, b) => (nodes.get(a).phase ?? 0) - (nodes.get(b).phase ?? 0) || rank.get(a) - rank.get(b)
41
+ );
42
+ const writesOf = (node) => (node.writes ?? []).map((r) => canon(resourceName(r)));
43
+ const readsOf = (node) => (node.reads ?? []).map(canon);
44
+ const writers = /* @__PURE__ */ new Map();
45
+ for (const name of names) {
46
+ for (const r of writesOf(nodes.get(name))) writers.set(r, [...writers.get(r) ?? [], name]);
47
+ }
48
+ const needed = /* @__PURE__ */ new Set();
49
+ const stack = [];
50
+ for (const name of names) {
51
+ const node = nodes.get(name);
52
+ if (node.sideEffects || writesOf(node).some((r) => outputs.has(r))) stack.push(name);
53
+ }
54
+ while (stack.length > 0) {
55
+ const name = stack.pop();
56
+ if (needed.has(name)) continue;
57
+ needed.add(name);
58
+ const node = nodes.get(name);
59
+ for (const r of readsOf(node)) for (const w of writers.get(r) ?? []) stack.push(w);
60
+ for (const a of colorOf(node, options.view)) {
61
+ if (a.clear === void 0) for (const w of writers.get(canon(a.resource)) ?? []) stack.push(w);
62
+ }
63
+ if (node.depth && node.depth.clear === void 0) {
64
+ for (const w of writers.get(canon(node.depth.resource)) ?? []) stack.push(w);
65
+ }
66
+ }
67
+ const live = names.filter((n) => needed.has(n));
68
+ const index = new Map(live.map((n, i) => [n, i]));
69
+ const edges = live.map(() => /* @__PURE__ */ new Set());
70
+ const link = (from, to) => {
71
+ const a = index.get(from);
72
+ const b = index.get(to);
73
+ if (a !== void 0 && b !== void 0 && a !== b) edges[a].add(b);
74
+ };
75
+ const phaseOf = (name) => nodes.get(name).phase ?? 0;
76
+ for (const [resource, ws] of writers) {
77
+ const liveWriters = ws.filter((w) => needed.has(w));
78
+ for (let i = 1; i < liveWriters.length; i++) link(liveWriters[i - 1], liveWriters[i]);
79
+ for (const name of live) {
80
+ const node = nodes.get(name);
81
+ if (!writesOf(node).includes(resource) && readsOf(node).includes(resource)) {
82
+ for (const w of liveWriters) {
83
+ if (phaseOf(w) > phaseOf(name)) link(name, w);
84
+ else link(w, name);
85
+ }
86
+ }
87
+ }
88
+ }
89
+ for (const name of live) for (const dep of nodes.get(name).after ?? []) link(dep, name);
90
+ const indegree = live.map(() => 0);
91
+ for (const set of edges) for (const to of set) indegree[to]++;
92
+ const order = [];
93
+ const done = live.map(() => false);
94
+ for (let step = 0; step < live.length; step++) {
95
+ const next = indegree.findIndex((d, i) => d === 0 && !done[i]);
96
+ if (next === -1) {
97
+ const stuck = live.filter((_, i) => !done[i]);
98
+ throw new ShardError(
99
+ "render/graph-cycle",
100
+ `Render graph nodes form a cycle: ${stuck.join(", ")}`,
101
+ { hint: "Check reads/writes and `after` on these nodes." }
102
+ );
103
+ }
104
+ done[next] = true;
105
+ order.push(live[next]);
106
+ for (const to of edges[next]) indegree[to]--;
107
+ }
108
+ return { order, culled: registration.filter((n) => !needed.has(n)) };
109
+ }
110
+ const perView = (value, view) => typeof value === "function" ? value(view) : value;
111
+ class RenderGraph {
112
+ pool;
113
+ timer;
114
+ nodes = /* @__PURE__ */ new Map();
115
+ /** Transient textures any node can use by name. */
116
+ declared = /* @__PURE__ */ new Map();
117
+ gpu;
118
+ resolutions = /* @__PURE__ */ new Map();
119
+ /** The last resolution per view name, for describe(). */
120
+ lastByView = /* @__PURE__ */ new Map();
121
+ captures = [];
122
+ /** Views rendered in the last frame, in order. */
123
+ lastViews = [];
124
+ viewData = /* @__PURE__ */ new Map();
125
+ constructor(gpu) {
126
+ this.gpu = gpu;
127
+ this.pool = new TexturePool(gpu);
128
+ this.timer = new GpuTimer(gpu);
129
+ }
130
+ addNode(name, node) {
131
+ if (this.nodes.has(name)) {
132
+ throw new ShardError("render/duplicate-node", `Render graph node "${name}" already exists`);
133
+ }
134
+ this.nodes.set(name, node);
135
+ this.resolutions.clear();
136
+ }
137
+ removeNode(name) {
138
+ this.nodes.delete(name);
139
+ this.resolutions.clear();
140
+ }
141
+ hasNode(name) {
142
+ return this.nodes.has(name);
143
+ }
144
+ /** The data of a view rendered in the last frame. */
145
+ lastViewData(view) {
146
+ return this.viewData.get(view);
147
+ }
148
+ /** Declares a transient texture any node can write, read, or attach by name. */
149
+ declare(texture) {
150
+ this.declared.set(texture.name, texture);
151
+ }
152
+ /** The resolution with every node enabled and no aliases. */
153
+ resolve() {
154
+ return this.resolveFor(void 0);
155
+ }
156
+ resolveFor(view) {
157
+ let key = "";
158
+ const enabled = /* @__PURE__ */ new Map();
159
+ for (const [name, node] of this.nodes) {
160
+ const on = !view || !node.enabled || node.enabled(view);
161
+ key += on ? "1" : "0";
162
+ if (on) enabled.set(name, node);
163
+ }
164
+ const aliases = view?.aliases;
165
+ if (aliases) for (const k in aliases) key += `|${k}>${aliases[k]}`;
166
+ if (view) {
167
+ for (const node of enabled.values()) {
168
+ if (typeof node.color === "function") {
169
+ for (const a of node.color(view)) key += a.clear === void 0 ? "L" : "C";
170
+ }
171
+ }
172
+ }
173
+ let resolved = this.resolutions.get(key);
174
+ if (!resolved) {
175
+ resolved = resolveGraph(enabled, /* @__PURE__ */ new Set([VIEW_TARGET]), { aliases, view });
176
+ resolved = {
177
+ order: resolved.order,
178
+ culled: [...this.nodes.keys()].filter((n) => !resolved.order.includes(n))
179
+ };
180
+ this.resolutions.set(key, resolved);
181
+ }
182
+ return resolved;
183
+ }
184
+ /** Whether a node after `k` loads or reads `resource` (so its contents must be stored). */
185
+ loadedLater(order, k, resource, view) {
186
+ const canonical = canonicalOf(view.aliases, resource);
187
+ for (let j = k + 1; j < order.length; j++) {
188
+ const node = this.nodes.get(order[j]);
189
+ for (const r of node.reads ?? []) if (canonicalOf(view.aliases, r) === canonical) return true;
190
+ for (const a of colorOf(node, view)) {
191
+ if (canonicalOf(view.aliases, a.resource) === canonical) return a.clear === void 0;
192
+ }
193
+ }
194
+ return this.captures.some((c) => c.view === view.name && c.buffer === resource);
195
+ }
196
+ capture(view, buffer = VIEW_TARGET) {
197
+ return new Promise(
198
+ (resolve, reject) => this.captures.push({ view, buffer, resolve, reject })
199
+ );
200
+ }
201
+ describe() {
202
+ const { order, culled } = this.resolve();
203
+ const describeOrder = (names) => names.map((name) => {
204
+ const node = this.nodes.get(name);
205
+ return {
206
+ name,
207
+ kind: node.kind,
208
+ reads: [...node.reads ?? []],
209
+ writes: (node.writes ?? []).map(resourceName)
210
+ };
211
+ });
212
+ return {
213
+ order: describeOrder(order),
214
+ culled,
215
+ perView: Object.fromEntries(
216
+ this.lastViews.filter((v) => this.lastByView.has(v)).map((v) => [
217
+ v,
218
+ { order: this.lastByView.get(v).order, culled: this.lastByView.get(v).culled }
219
+ ])
220
+ ),
221
+ pooledTextures: this.pool.size
222
+ };
223
+ }
224
+ /** Runs the graph for every view and submits once. */
225
+ execute(world, views) {
226
+ const device = this.gpu.device;
227
+ const encoder = device.createCommandEncoder({ label: "frame" });
228
+ this.pool.beginFrame();
229
+ this.timer.beginFrame();
230
+ const readbacks = [];
231
+ const submitted = [];
232
+ const sorted = [...views].sort((a, b) => a.order - b.order);
233
+ this.lastViews = sorted.map((v) => v.name);
234
+ this.viewData.clear();
235
+ for (const v of sorted) this.viewData.set(v.name, v.data);
236
+ for (const view of sorted) {
237
+ const resolved = this.resolveFor(view);
238
+ const order = resolved.order;
239
+ this.lastByView.set(view.name, resolved);
240
+ const textures = /* @__PURE__ */ new Map();
241
+ textures.set(VIEW_TARGET, view.target.texture());
242
+ const inline = /* @__PURE__ */ new Map();
243
+ for (const name of order) {
244
+ for (const ref of this.nodes.get(name).writes ?? []) {
245
+ if (typeof ref !== "string") inline.set(ref.name, ref);
246
+ }
247
+ }
248
+ const texture = (name) => {
249
+ const canonical = canonicalOf(view.aliases, name);
250
+ let t = textures.get(canonical);
251
+ if (t) return t;
252
+ const desc = inline.get(canonical) ?? this.declared.get(canonical);
253
+ if (!desc) {
254
+ throw new ShardError(
255
+ "render/missing-resource",
256
+ `Render graph resource "${name}" is not available`,
257
+ {
258
+ hint: "Declare it in a node's `writes` (as a transient texture) or with graph.declare."
259
+ }
260
+ );
261
+ }
262
+ const size = perView(desc.size ?? "view", view);
263
+ const vw = view.width ?? view.target.width;
264
+ const vh = view.height ?? view.target.height;
265
+ const px = size === "view" ? [vw, vh] : Array.isArray(size) ? size : [Math.max(1, Math.ceil(vw / size.divide)), Math.max(1, Math.ceil(vh / size.divide))];
266
+ const format = perView(desc.format, view);
267
+ t = this.pool.acquire({
268
+ label: `${view.name}/${canonical}`,
269
+ format: format === "view" ? view.target.format : format,
270
+ size: px,
271
+ sampleCount: perView(desc.sampleCount ?? 1, view),
272
+ mipLevelCount: perView(desc.mipLevelCount ?? 1, view),
273
+ usage: (desc.usage ?? GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING) | GPUTextureUsage.COPY_SRC
274
+ });
275
+ textures.set(canonical, t);
276
+ return t;
277
+ };
278
+ const timestamps = (name) => this.timer.allocate(name);
279
+ const afterSubmit = (fn) => void submitted.push(fn);
280
+ for (let k = 0; k < order.length; k++) {
281
+ const name = order[k];
282
+ const node = this.nodes.get(name);
283
+ let renderPass;
284
+ let computePass;
285
+ if (node.kind === "render") {
286
+ const colors = colorOf(node, view);
287
+ renderPass = encoder.beginRenderPass({
288
+ label: `${view.name}/${name}`,
289
+ colorAttachments: colors.map((a) => ({
290
+ view: texture(a.resource).createView({ baseMipLevel: a.mip ?? 0, mipLevelCount: 1 }),
291
+ resolveTarget: a.resolve ? texture(a.resolve).createView() : void 0,
292
+ loadOp: a.clear === void 0 ? "load" : "clear",
293
+ clearValue: typeof a.clear === "function" ? a.clear(view) : a.clear,
294
+ // A resolved MSAA buffer that no later pass loads never needs to reach memory.
295
+ storeOp: a.resolve && !this.loadedLater(order, k, a.resource, view) ? "discard" : "store"
296
+ })),
297
+ depthStencilAttachment: node.depth ? node.depth.readOnly ? { view: texture(node.depth.resource).createView(), depthReadOnly: true } : {
298
+ view: texture(node.depth.resource).createView(),
299
+ depthLoadOp: node.depth.clear === void 0 ? "load" : "clear",
300
+ depthClearValue: node.depth.clear ?? 0,
301
+ depthStoreOp: "store"
302
+ } : void 0,
303
+ timestampWrites: this.timer.allocate(name)
304
+ });
305
+ } else if (node.kind === "compute") {
306
+ computePass = encoder.beginComputePass({
307
+ label: `${view.name}/${name}`,
308
+ timestampWrites: this.timer.allocate(name)
309
+ });
310
+ }
311
+ node.run({
312
+ gpu: this.gpu,
313
+ world,
314
+ view,
315
+ encoder,
316
+ renderPass,
317
+ computePass,
318
+ texture,
319
+ timestamps,
320
+ afterSubmit
321
+ });
322
+ renderPass?.end();
323
+ computePass?.end();
324
+ }
325
+ for (const capture of this.captures) {
326
+ if (capture.view !== view.name) continue;
327
+ let source;
328
+ try {
329
+ source = texture(capture.buffer);
330
+ } catch (err) {
331
+ capture.reject(err);
332
+ continue;
333
+ }
334
+ const { width, height, format } = source;
335
+ const bpp = BYTES_PER_TEXEL[format];
336
+ if (!bpp || source.sampleCount > 1) {
337
+ capture.reject(
338
+ new ShardError(
339
+ "render/capture-format",
340
+ `Can't capture "${capture.buffer}" (${format})`
341
+ )
342
+ );
343
+ continue;
344
+ }
345
+ const bytesPerRow = Math.ceil(width * bpp / 256) * 256;
346
+ const buffer = device.createBuffer({
347
+ label: `capture/${view.name}/${capture.buffer}`,
348
+ size: bytesPerRow * height,
349
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
350
+ });
351
+ encoder.copyTextureToBuffer(
352
+ { texture: source, aspect: format.startsWith("depth") ? "depth-only" : "all" },
353
+ { buffer, bytesPerRow },
354
+ [width, height]
355
+ );
356
+ readbacks.push({ capture, buffer, width, height, format, bytesPerRow });
357
+ }
358
+ }
359
+ this.captures = this.captures.filter((c) => !readbacks.some((r) => r.capture === c));
360
+ this.timer.resolve(encoder);
361
+ device.queue.submit([encoder.finish()]);
362
+ this.timer.readback(world);
363
+ for (const fn of submitted) fn();
364
+ for (const r of readbacks) {
365
+ const { capture, buffer, width, height, format, bytesPerRow } = r;
366
+ buffer.mapAsync(GPUMapMode.READ).then(
367
+ () => {
368
+ const src = buffer.getMappedRange();
369
+ const eightBit = BYTES_PER_TEXEL[format] === 4 && /^(rgba8|bgra8)/.test(format);
370
+ if (eightBit) {
371
+ const bytes = new Uint8Array(src);
372
+ const data2 = new Uint8Array(width * height * 4);
373
+ const bgra = format.startsWith("bgra");
374
+ for (let y = 0; y < height; y++) {
375
+ const row = bytes.subarray(y * bytesPerRow, y * bytesPerRow + width * 4);
376
+ if (!bgra) {
377
+ data2.set(row, y * width * 4);
378
+ continue;
379
+ }
380
+ for (let x = 0; x < width * 4; x += 4) {
381
+ const o = y * width * 4 + x;
382
+ data2[o] = row[x + 2];
383
+ data2[o + 1] = row[x + 1];
384
+ data2[o + 2] = row[x];
385
+ data2[o + 3] = row[x + 3];
386
+ }
387
+ }
388
+ buffer.unmap();
389
+ buffer.destroy();
390
+ capture.resolve({ width, height, data: data2 });
391
+ return;
392
+ }
393
+ const data = new Float32Array(width * height * 4);
394
+ for (let y = 0; y < height; y++) {
395
+ toFloats(
396
+ src.slice(y * bytesPerRow, (y + 1) * bytesPerRow),
397
+ format,
398
+ width,
399
+ data,
400
+ y * width * 4
401
+ );
402
+ }
403
+ buffer.unmap();
404
+ buffer.destroy();
405
+ capture.resolve({ width, height, format, data });
406
+ },
407
+ (err) => capture.reject(err)
408
+ );
409
+ }
410
+ }
411
+ }
412
+ export {
413
+ RenderGraph,
414
+ RenderPhase,
415
+ VIEW_TARGET,
416
+ resolveGraph
417
+ };
@@ -0,0 +1,53 @@
1
+ export { AssetStore, MaterialAsset, MaterialAssetType, MaterialImporter, Materials, type MaterialValue, MeshAsset, Meshes, materialFields, materialFromJson, materialTypeOf, RenderTargets, STANDARD_TYPE, StandardMaterial, type StandardMaterialValue, validateMaterial, } from './assets.js';
2
+ export { Atmosphere, type AtmospherePreset, AtmospherePresets, type AtmosphereRecord, type AtmosphereSampleResult, AtmosphereSettings, AtmosphereStore, Atmospheres, type AtmosphereValue, atmosphereMethods, atmosphereModel, type CameraAtmosphere, describeAtmospheres, sampleAtmosphere, selectAtmospheres, sunTransmittanceAt, } from './atmosphere.js';
3
+ export { type AtmosphereModel, createSkySample, integrateSky, multiscatterLut, transmittanceLut, transmittanceToTop, } from './atmosphere-model.js';
4
+ export { AtmosphereGpu, AtmosphereGpuResource, LUT_LAYERS } from './atmosphere-nodes.js';
5
+ export { atmospherePlugin } from './atmosphere-plugin.js';
6
+ export { applyPhysicalCameras, Camera3d, type Camera3dValue, camera2d, Exposure, type ExposurePreset, ExposurePresets, ev100, ev100FromCamera, ev100FromIlluminance, exposureScale, type LightPreset, LightPresets, lux, PhysicalCamera, } from './camera.js';
7
+ export { CLUSTER_COUNT, CLUSTER_X, CLUSTER_Y, CLUSTER_Z, ClusterBuffers, type ClusterStats, clusterAabb, clusterAabbs, clusterLightsCpu, clusterRange, MAX_LIGHTS_PER_CLUSTER, ViewLightList, } from './clusters.js';
8
+ export { Culler, GpuCuller, readVisibleSlots, visibleSlots } from './culling.js';
9
+ export { captureShadowMap, DEBUG_VIEWS, type DebugView, describeCulling, describeLighting, readBuffer, readTextureLayer, setDebugView, } from './debug-views.js';
10
+ export { captureGBuffer, describeDeferred, GBUFFER_CHANNELS, type GBufferChannel, } from './deferred.js';
11
+ export { deferredPlugin } from './deferred-plugin.js';
12
+ export { DEFORM_WORDS, DeformStore, type MeshDeform } from './deform.js';
13
+ export { renderDescribePlugin } from './describe-plugin.js';
14
+ export { whiteBalance } from './display-nodes.js';
15
+ export { dynamicResolutionPlugin, type FrameTimings, RenderScaleController, updateRenderScale, } from './dynamic-resolution.js';
16
+ export { type CameraEnvironment, DefaultEnvironment, type DefaultEnvironmentValue, describeEnvironment, Environment, type EnvironmentBaker, EnvironmentMap, type EnvironmentMapValue, EnvironmentPresets, EnvironmentStore, Environments, environmentBakers, ProceduralSky, type ProceduralSkyValue, Skybox, } from './environment.js';
17
+ export { environmentPlugin } from './environment-plugin.js';
18
+ export { type ForwardPluginOptions, type ForwardState, ForwardStateResource, forwardCorePlugin, PICK_TARGETS, sceneColor, type ViewGpu, viewBindGroup, } from './forward.js';
19
+ export { fxaaPlugin } from './fxaa.js';
20
+ export { LABEL_FONT, labelWidth } from './gizmo-font.js';
21
+ export { GIZMO_LINE_FLOATS, type GizmoOptions, GizmoStore, Gizmos, packGizmoColor, } from './gizmos.js';
22
+ export { gizmosPlugin } from './gizmos-plugin.js';
23
+ export { GpuAssets, GpuAssetsResource, type GpuMaterial, type GpuMesh, type GpuTexture, } from './gpu-assets.js';
24
+ export { type CapturedBuffer, type CapturedImage, type ColorAttachment, type DepthAttachment, type NodeContext, type NodeDescriptor, RenderGraph, RenderPhase, type RenderView, type ResolvedGraph, type ResourceRef, resolveGraph, type TransientTexture, VIEW_TARGET, } from './graph.js';
25
+ export { type Batch, createDrawList, DeformPath, type DrawItem, type DrawList, INSTANCE_BYTES, INSTANCE_FLOATS, InstanceData, InstanceFlags, InstanceSlot, InstanceStore, Instances, LOD_BIT, Lod, lodSize, Mesh3d, MeshMaterial, MorphWeights, NotShadowCaster, NotShadowReceiver, SkinnedMesh, selectLod, VisibilityRange, } from './instances.js';
26
+ export { observeOriginShifts, shiftRenderHistory } from './large-world.js';
27
+ export { AmbientLight, type AmbientLightValue, CascadeSettings, DirectionalLight, LIGHT_FLOATS, LightingSettings, type LightingSettingsValue, type LightRecord, LightStore, Lights, type LuminousPowerPreset, LuminousPowerPresets, lumens, PointLight, SpotLight, } from './lights.js';
28
+ export { MaterialNoise, type MaterialNoiseSupport } from './material-noise.js';
29
+ export { registerMaterialModule, typeOrdinal } from './material-pipelines.js';
30
+ export { allMaterialTypes, BLEND_MODES, type BlendMode, defineMaterial, findMaterialType, isTransparent, type MaterialNoiseSlot, MaterialType, type MaterialTypeOptions, materialModulePath, onMaterialTypeChange, } from './materials.js';
31
+ export { allOverlays, DebugOverlays, type DebugOverlaysValue, defineOverlay, gridsOverlay, isOverlayOn, OVERLAYS, type Overlay, type OverlayDef, type OverlayFilter, overlayNames, setOverlays, } from './overlays.js';
32
+ export { descendantPaths, entityName, findModelRoot } from './paths.js';
33
+ export { type PickBlocker, type PickDrawer, type PickHit, Picking, pick, primaryView, type RaycastOptions, raycast, } from './picking.js';
34
+ export { pickingPlugin } from './picking-plugin.js';
35
+ export { PixelPerfect, type PixelPerfectLayout, PixelPerfectPath, PixelTargets, pixelPerfectLayout, } from './pixel-perfect.js';
36
+ export { pixelPerfectPlugin } from './pixel-perfect-plugin.js';
37
+ export { captureBuffer, captureView, describeRender, Gpu, GpuDeviceLost, Graph, RenderDescribers, type RenderPluginOptions, RenderSet, renderPlugin, Shaders, Views, Window, } from './plugin.js';
38
+ export { TexturePool } from './pool.js';
39
+ export { ANTIALIASING_MODES, Antialiasing, AutoExposure, Bloom, ColorGrading, cocRadiusPixels, DepthOfField, Fog, METERING_MODES, MotionBlur, PostEffect, type PostSettings, SSAO_QUALITIES, Ssao, Vignette, } from './post.js';
40
+ export { bloomLevels, describePost, ExposureMeters, type ExposureState, histogramEv, METER_READBACKS, POST_NODES, } from './post-nodes.js';
41
+ export { postPlugin } from './post-plugin.js';
42
+ export { describeRenderScale, RenderScale, type RenderScaleValue, SCALE_STEP, scaledSize, } from './render-scale.js';
43
+ export { ENGINE_SHADERS, materialLayout, registerEngineShaders, registerShaders, ViewUniform, viewLayout, } from './shaders.js';
44
+ export { Cascades, cascadeSplits, LocalShadows, ShadowsResource, sliceSphere, } from './shadows.js';
45
+ export { MAX_JOINTS, type SkinAsset, SkinAssetType, Skins, skinArtifact } from './skin-asset.js';
46
+ export { prepareDeforms } from './skinning.js';
47
+ export { skinningPlugin } from './skinning-plugin.js';
48
+ export { forwardPlugin } from './standard.js';
49
+ export { GpuMemory, type GpuMemoryData, RenderCounters, type RenderCountersData, RenderStats, type ViewStats, } from './stats.js';
50
+ export { OffscreenTarget, type OffscreenTargetOptions, type RenderTarget, WindowTarget, } from './target.js';
51
+ export { GpuTimer } from './timer.js';
52
+ export { type CameraData, Cameras, cameraOf, DEFAULT_CURVE, isScaled, RenderPath, shiftCameraHistory, TONEMAP_CURVES, type TonemapCurve, Tonemapping, ViewSettings, type ViewSettingsValue, viewAliases, } from './view.js';
53
+ export { ComputedVisibility, computeVisibility, Visibility } from './visibility.js';