@aethervtt/shard-render 0.0.1 → 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 (170) hide show
  1. package/dist/ablation.d.ts +74 -0
  2. package/dist/ablation.js +117 -0
  3. package/dist/assets.d.ts +3 -0
  4. package/dist/assets.js +43 -2
  5. package/dist/atmosphere-nodes.d.ts +9 -0
  6. package/dist/atmosphere-nodes.js +157 -30
  7. package/dist/atmosphere-plugin.js +8 -2
  8. package/dist/atmosphere-shaders.js +17 -5
  9. package/dist/baseline/atmosphere.d.ts +16 -0
  10. package/dist/baseline/atmosphere.js +301 -0
  11. package/dist/baseline/data-texture.d.ts +36 -0
  12. package/dist/baseline/data-texture.js +181 -0
  13. package/dist/baseline/environment.d.ts +14 -0
  14. package/dist/baseline/environment.js +368 -0
  15. package/dist/baseline/exposure.d.ts +25 -0
  16. package/dist/baseline/exposure.js +133 -0
  17. package/dist/baseline/lights.d.ts +33 -0
  18. package/dist/baseline/lights.js +133 -0
  19. package/dist/baseline/textures.d.ts +97 -0
  20. package/dist/baseline/textures.js +203 -0
  21. package/dist/camera.d.ts +2 -0
  22. package/dist/camera.js +4 -0
  23. package/dist/crowd.d.ts +21 -0
  24. package/dist/crowd.js +121 -0
  25. package/dist/culling-shaders.js +21 -2
  26. package/dist/culling.d.ts +11 -2
  27. package/dist/culling.js +44 -18
  28. package/dist/cutaway-plugin.d.ts +48 -0
  29. package/dist/cutaway-plugin.js +364 -0
  30. package/dist/cutaway.d.ts +27 -0
  31. package/dist/cutaway.js +49 -0
  32. package/dist/data-store.d.ts +69 -0
  33. package/dist/data-store.js +105 -0
  34. package/dist/debug-shaders.js +17 -3
  35. package/dist/debug-views.js +4 -2
  36. package/dist/decals-plugin.d.ts +5 -0
  37. package/dist/decals-plugin.js +54 -0
  38. package/dist/decals.d.ts +69 -0
  39. package/dist/decals.js +326 -0
  40. package/dist/deferred-shaders.js +2 -0
  41. package/dist/deferred.js +28 -4
  42. package/dist/deform.d.ts +5 -4
  43. package/dist/deform.js +4 -9
  44. package/dist/describe-plugin.d.ts +4 -2
  45. package/dist/describe-plugin.js +75 -3
  46. package/dist/display-nodes.js +37 -13
  47. package/dist/dynamic-resolution.d.ts +2 -0
  48. package/dist/dynamic-resolution.js +6 -1
  49. package/dist/environment-plugin.js +17 -2
  50. package/dist/environment-state.d.ts +7 -3
  51. package/dist/environment-state.js +20 -9
  52. package/dist/environment.js +7 -1
  53. package/dist/features.d.ts +49 -0
  54. package/dist/features.js +106 -0
  55. package/dist/foliage-budget.d.ts +64 -0
  56. package/dist/foliage-budget.js +108 -0
  57. package/dist/foliage-path.d.ts +19 -0
  58. package/dist/foliage-path.js +7 -0
  59. package/dist/foliage-shaders.d.ts +11 -0
  60. package/dist/foliage-shaders.js +504 -0
  61. package/dist/foliage.d.ts +194 -0
  62. package/dist/foliage.js +1027 -0
  63. package/dist/forward.d.ts +38 -2
  64. package/dist/forward.js +608 -186
  65. package/dist/fxaa.js +12 -2
  66. package/dist/gizmos-plugin.js +7 -0
  67. package/dist/gizmos.d.ts +3 -3
  68. package/dist/gizmos.js +11 -13
  69. package/dist/gpu-assets.d.ts +58 -4
  70. package/dist/gpu-assets.js +191 -8
  71. package/dist/graph.d.ts +45 -0
  72. package/dist/graph.js +169 -19
  73. package/dist/health.d.ts +58 -0
  74. package/dist/health.js +112 -0
  75. package/dist/index.d.ts +40 -11
  76. package/dist/index.js +343 -9
  77. package/dist/instances.d.ts +119 -8
  78. package/dist/instances.js +387 -78
  79. package/dist/interior-plugin.d.ts +121 -0
  80. package/dist/interior-plugin.js +465 -0
  81. package/dist/interior.d.ts +24 -0
  82. package/dist/interior.js +13 -0
  83. package/dist/layers.d.ts +35 -0
  84. package/dist/layers.js +49 -0
  85. package/dist/lens-plugin.d.ts +43 -0
  86. package/dist/lens-plugin.js +304 -0
  87. package/dist/lens.d.ts +41 -0
  88. package/dist/lens.js +99 -0
  89. package/dist/lights.d.ts +50 -5
  90. package/dist/lights.js +101 -22
  91. package/dist/material-pipelines.d.ts +37 -4
  92. package/dist/material-pipelines.js +102 -5
  93. package/dist/materials.d.ts +23 -0
  94. package/dist/materials.js +44 -1
  95. package/dist/outline-plugin.d.ts +48 -0
  96. package/dist/outline-plugin.js +529 -0
  97. package/dist/outline.d.ts +15 -0
  98. package/dist/outline.js +42 -0
  99. package/dist/overlay-registry.d.ts +3 -1
  100. package/dist/overlay-registry.js +3 -1
  101. package/dist/overlays.js +6 -2
  102. package/dist/perf-overlay.d.ts +9 -0
  103. package/dist/perf-overlay.js +125 -0
  104. package/dist/picking.d.ts +17 -0
  105. package/dist/picking.js +40 -11
  106. package/dist/pixel-perfect-plugin.js +10 -2
  107. package/dist/plugin.d.ts +69 -7
  108. package/dist/plugin.js +223 -25
  109. package/dist/post-common.d.ts +22 -2
  110. package/dist/post-common.js +82 -2
  111. package/dist/post-nodes.d.ts +7 -1
  112. package/dist/post-nodes.js +235 -111
  113. package/dist/post-plugin.js +5 -3
  114. package/dist/post-scene.d.ts +130 -0
  115. package/dist/post-scene.js +164 -0
  116. package/dist/post-shaders.js +85 -62
  117. package/dist/post.js +11 -1
  118. package/dist/projection.d.ts +24 -0
  119. package/dist/projection.js +87 -0
  120. package/dist/readback.d.ts +1 -0
  121. package/dist/readback.js +1 -0
  122. package/dist/render-scale.js +1 -0
  123. package/dist/ribbons-plugin.d.ts +5 -0
  124. package/dist/ribbons-plugin.js +59 -0
  125. package/dist/ribbons.d.ts +44 -0
  126. package/dist/ribbons.js +288 -0
  127. package/dist/scenario.d.ts +70 -0
  128. package/dist/scenario.js +125 -0
  129. package/dist/screen-effects.d.ts +97 -0
  130. package/dist/screen-effects.js +192 -0
  131. package/dist/shaders.d.ts +13 -0
  132. package/dist/shaders.js +189 -34
  133. package/dist/shadow-catcher.d.ts +9 -0
  134. package/dist/shadow-catcher.js +133 -0
  135. package/dist/shadows.d.ts +27 -2
  136. package/dist/shadows.js +100 -20
  137. package/dist/standard-fields.d.ts +1 -0
  138. package/dist/standard-fields.js +5 -0
  139. package/dist/standard.js +18 -0
  140. package/dist/stats.d.ts +45 -1
  141. package/dist/stats.js +91 -3
  142. package/dist/surface-model.d.ts +57 -0
  143. package/dist/surface-model.js +289 -0
  144. package/dist/surface-shaders.d.ts +3 -0
  145. package/dist/surface-shaders.js +160 -0
  146. package/dist/surface.d.ts +40 -0
  147. package/dist/surface.js +86 -0
  148. package/dist/target.d.ts +1 -13
  149. package/dist/target.js +1 -26
  150. package/dist/testing.d.ts +11 -0
  151. package/dist/testing.js +39 -1
  152. package/dist/tier.d.ts +13 -0
  153. package/dist/tier.js +14 -0
  154. package/dist/timer.d.ts +18 -1
  155. package/dist/timer.js +73 -20
  156. package/dist/variants.d.ts +37 -0
  157. package/dist/variants.js +104 -0
  158. package/dist/vfx-draw.d.ts +37 -0
  159. package/dist/vfx-draw.js +291 -0
  160. package/dist/vfx-state.d.ts +42 -0
  161. package/dist/vfx-state.js +81 -0
  162. package/dist/vfx.d.ts +69 -0
  163. package/dist/vfx.js +103 -0
  164. package/dist/view-visibility-plugin.d.ts +61 -0
  165. package/dist/view-visibility-plugin.js +267 -0
  166. package/dist/view-visibility.d.ts +28 -0
  167. package/dist/view-visibility.js +46 -0
  168. package/dist/view.d.ts +29 -1
  169. package/dist/view.js +57 -34
  170. package/package.json +23 -11
package/dist/fxaa.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { definePlugin } from "@aethervtt/shard-runtime";
2
+ import { addRenderFeatures } from "./features.js";
2
3
  import { RenderPhase } from "./graph.js";
3
4
  import { Graph, Shaders } from "./plugin.js";
4
5
  import { hasEffect, PostEffect, PostFeatures } from "./post.js";
5
6
  import { idOf, PostCache, sampler, tex } from "./post-common.js";
6
7
  import { FXAA_SHADERS } from "./post-shaders.js";
7
8
  import { registerShaders } from "./shaders.js";
9
+ import { cameraOf } from "./view.js";
8
10
  function fxaaNode() {
9
11
  const cache = new PostCache();
10
12
  let layout;
@@ -24,15 +26,17 @@ function fxaaNode() {
24
26
  }
25
27
  const input = ctx.texture("ldr");
26
28
  const format = ctx.texture("display").format;
29
+ const alpha = cameraOf(ctx.view)?.alphaOutput === true;
27
30
  const pipeline = cache.render(
28
31
  ctx,
29
- `fxaa/${format}`,
32
+ `fxaa/${format}${alpha ? "/alpha" : ""}`,
30
33
  "shard::post::fxaa",
31
34
  "fs",
32
35
  [layout],
33
36
  [{ format }],
34
37
  {
35
- SRGB_TARGET: format.endsWith("-srgb")
38
+ SRGB_TARGET: format.endsWith("-srgb"),
39
+ TRANSPARENT: alpha
36
40
  }
37
41
  );
38
42
  if (!pipeline) return;
@@ -57,6 +61,12 @@ const fxaaPlugin = definePlugin({
57
61
  registerShaders(app.world.resource(Shaders), FXAA_SHADERS);
58
62
  const graph = app.world.resource(Graph);
59
63
  graph.declare({ name: "fxaa-in", format: "view" });
64
+ addRenderFeatures(app.world, {
65
+ name: "render/fxaa",
66
+ description: "Fast approximate anti-aliasing.",
67
+ nodes: ["post/fxaa"],
68
+ baseline: { strategy: "The same fragment pass" }
69
+ });
60
70
  graph.addNode("post/fxaa", fxaaNode());
61
71
  }
62
72
  });
@@ -2,6 +2,7 @@ import { First, Last } from "@aethervtt/shard-core";
2
2
  import { definePlugin } from "@aethervtt/shard-runtime";
3
3
  import { OriginShift } from "@aethervtt/shard-transform";
4
4
  import { GIZMO_SHADERS } from "./debug-shaders.js";
5
+ import { addRenderFeatures } from "./features.js";
5
6
  import { upload } from "./forward.js";
6
7
  import { beginGizmos, GizmoGpuResource, Gizmos, gizmoNode, uploadGizmos } from "./gizmos.js";
7
8
  import { DebugOverlays, drawOverlays, gridsOverlay } from "./overlays.js";
@@ -26,6 +27,12 @@ const gizmosPlugin = definePlugin({
26
27
  },
27
28
  ready(app) {
28
29
  registerShaders(app.world.resource(Shaders), GIZMO_SHADERS);
30
+ addRenderFeatures(app.world, {
31
+ name: "render/gizmos",
32
+ description: "Debug lines, shapes and labels.",
33
+ nodes: ["gizmos"],
34
+ baseline: { strategy: "Line and glyph records in data textures" }
35
+ });
29
36
  app.world.resource(Graph).addNode("gizmos", gizmoNode(app.world));
30
37
  }
31
38
  });
package/dist/gizmos.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type World } from '@aethervtt/shard-core';
2
- import { GpuBuffer } from '@aethervtt/shard-gpu';
2
+ import { DataStore } from './data-store.js';
3
3
  import { type NodeDescriptor } from './graph.js';
4
4
  /**
5
5
  * Immediate-mode debug drawing. Any system calls `world.resource(Gizmos).line(…)` and the line
@@ -127,8 +127,8 @@ export declare const Gizmos: import("@aethervtt/shard-core").ResourceDef<GizmoSt
127
127
  export declare const beginGizmos: import("@aethervtt/shard-core").SystemDef<undefined>;
128
128
  interface GizmoGpu {
129
129
  generation: number;
130
- lines: GpuBuffer;
131
- glyphs: GpuBuffer;
130
+ lines: DataStore;
131
+ glyphs: DataStore;
132
132
  atlas: GPUTexture;
133
133
  layouts: {
134
134
  view: GPUBindGroupLayout;
package/dist/gizmos.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { defineResource, defineSystem, mat4 } from "@aethervtt/shard-core";
2
- import { GpuBuffer } from "@aethervtt/shard-gpu";
3
2
  import { Time } from "@aethervtt/shard-runtime";
3
+ import { DataStore, dataEntry } from "./data-store.js";
4
4
  import { ForwardStateResource } from "./forward.js";
5
5
  import { LABEL_FONT, labelAtlas, labelWidth } from "./gizmo-font.js";
6
6
  import { RenderPhase } from "./graph.js";
7
7
  import { Gpu, Shaders } from "./plugin.js";
8
+ import { depthReadEntry } from "./tier.js";
8
9
  import { cameraOf } from "./view.js";
9
10
  const GIZMO_LINE_FLOATS = 8;
10
11
  const GLYPH_FLOATS = 12;
@@ -537,12 +538,9 @@ function gizmoGpu(world, gpu) {
537
538
  const glyphData = new Float32Array(GLYPH_FLOATS * 256);
538
539
  r.gpu = {
539
540
  generation: gpu.generation,
540
- lines: new GpuBuffer(gpu, { label: "gizmos/lines", usage: GPUBufferUsage.STORAGE, size: 4096 }),
541
- glyphs: new GpuBuffer(gpu, {
542
- label: "gizmos/glyphs",
543
- usage: GPUBufferUsage.STORAGE,
544
- size: 4096
545
- }),
541
+ // Read by the vertex stage: data textures on baseline (0064).
542
+ lines: new DataStore(gpu, { label: "gizmos/lines", size: 4096 }),
543
+ glyphs: new DataStore(gpu, { label: "gizmos/glyphs", size: 4096 }),
546
544
  atlas,
547
545
  layouts: {
548
546
  view: gpu.layouts.bindGroupLayout({
@@ -552,9 +550,9 @@ function gizmoGpu(world, gpu) {
552
550
  data: gpu.layouts.bindGroupLayout({
553
551
  label: "gizmos/data",
554
552
  entries: [
555
- { binding: 0, visibility: V, buffer: { type: "read-only-storage" } },
556
- { binding: 1, visibility: V, buffer: { type: "read-only-storage" } },
557
- { binding: 2, visibility: F, texture: { sampleType: "depth" } },
553
+ dataEntry(gpu, 0, V),
554
+ dataEntry(gpu, 1, V),
555
+ depthReadEntry(gpu, 2, F),
558
556
  { binding: 3, visibility: F, texture: { sampleType: "float" } }
559
557
  ]
560
558
  })
@@ -733,11 +731,11 @@ function gizmoNode(world) {
733
731
  gpu,
734
732
  r,
735
733
  `data/${ctx.view.name}`,
736
- `${idOf(r.lines.buffer)}/${idOf(r.glyphs.buffer)}/${idOf(depth)}/${idOf(r.atlas)}`,
734
+ `${r.lines.version}/${r.glyphs.version}/${idOf(depth)}/${idOf(r.atlas)}`,
737
735
  r.layouts.data,
738
736
  () => [
739
- { binding: 0, resource: { buffer: r.lines.buffer } },
740
- { binding: 1, resource: { buffer: r.glyphs.buffer } },
737
+ { binding: 0, resource: r.lines.resource() },
738
+ { binding: 1, resource: r.glyphs.resource() },
741
739
  { binding: 2, resource: depth.createView() },
742
740
  { binding: 3, resource: r.atlas.createView() }
743
741
  ]
@@ -1,10 +1,11 @@
1
+ import { type AssetServer } from '@aethervtt/shard-assets';
1
2
  import { type World } from '@aethervtt/shard-core';
2
3
  import type { GpuContext } from '@aethervtt/shard-gpu';
3
- import type { Mesh } from '@aethervtt/shard-mesh';
4
- import { type Texture } from '@aethervtt/shard-texture';
5
- import { type MaterialAsset } from './assets.js';
4
+ import { Mesh } from '@aethervtt/shard-mesh';
5
+ import { Texture } from '@aethervtt/shard-texture';
6
+ import { MaterialAsset } from './assets.js';
6
7
  import type { MaterialType } from './materials.js';
7
- import type { GpuMemoryData } from './stats.js';
8
+ import type { FrameCounts, GpuMemoryData } from './stats.js';
8
9
  export interface GpuMesh {
9
10
  version: number;
10
11
  generation: number;
@@ -48,6 +49,8 @@ export interface GpuTexture {
48
49
  version: number;
49
50
  generation: number;
50
51
  bytes: number;
52
+ /** Frees the texture and anything made from it (baseline twins); else `texture.destroy()`. */
53
+ destroy?(): void;
51
54
  }
52
55
  /**
53
56
  * GPU copies of meshes, textures, and materials, shared by every pass that draws them (forward,
@@ -56,6 +59,8 @@ export interface GpuTexture {
56
59
  */
57
60
  export declare class GpuAssets {
58
61
  readonly meshes: Map<Mesh, GpuMesh>;
62
+ /** Where mesh uploads are counted (`RenderStats.current.meshesRebuilt`), when set. */
63
+ counts: FrameCounts | undefined;
59
64
  readonly materials: Map<MaterialAsset, GpuMaterial>;
60
65
  readonly textures: Map<Texture, GpuTexture>;
61
66
  private defaults;
@@ -75,10 +80,25 @@ export declare class GpuAssets {
75
80
  readonly gpu: GpuContext;
76
81
  readonly memory: GpuMemoryData;
77
82
  materialLayout: GPUBindGroupLayout;
83
+ /**
84
+ * The baseline tier's texture strategy (0064), set on a baseline device: one format and one
85
+ * binding dimension per texture, and twins for slots that read it otherwise.
86
+ */
87
+ baseline: typeof import('./baseline/textures.js') | undefined;
88
+ /** Uploaded this frame on baseline: their bytes go at the next frame (twins take them first). */
89
+ private readonly releaseLater;
90
+ /** Asset guids of textures, for reloading one a twin needs. */
91
+ private readonly guids;
92
+ private world;
78
93
  constructor(gpu: GpuContext, memory: GpuMemoryData);
79
94
  /** Call once per frame before drawing: rebuilds after device loss and resets readiness. */
80
95
  beginFrame(): void;
81
96
  mesh(mesh: Mesh): GpuMesh;
97
+ /**
98
+ * Writes a CPU mesh's new data into the buffers of its previous version, when each fits and the
99
+ * mesh is still indexed the same way. Returns false when it needs new buffers.
100
+ */
101
+ private rewrite;
82
102
  private destroyMesh;
83
103
  /** Index buffers shared by GPU meshes, per index array. */
84
104
  private readonly sharedIndexBuffers;
@@ -87,8 +107,23 @@ export declare class GpuAssets {
87
107
  * normals, uvs, uvs1, tangents; f32), and the index buffer every mesh with the same array shares.
88
108
  */
89
109
  private gpuMesh;
110
+ /**
111
+ * Frees GPU copies of assets as the asset server drops them: unloaded (an owner released, 0061),
112
+ * or a fallback replaced by the real asset. Returns the unsubscribe.
113
+ */
114
+ watch(server: AssetServer): () => void;
115
+ /** The GPU objects made for one asset object (a texture, mesh or material): owners.describe (0061). */
116
+ objectsOf(item: unknown): {
117
+ buffers: number;
118
+ textures: number;
119
+ bytes: number;
120
+ };
90
121
  /** Frees a mesh's GPU buffers now (they're made again if it's drawn later). */
91
122
  releaseMesh(mesh: Mesh): void;
123
+ /** Frees a texture's GPU copy now (it's uploaded again if it's used later). */
124
+ releaseTexture(texture: Texture): void;
125
+ /** Frees a material's uniform buffers now (they're made again if it's drawn later). */
126
+ releaseMaterial(material: MaterialAsset): void;
92
127
  /** Uploads a texture (all levels) on first use and when its version changes. */
93
128
  texture(texture: Texture): GpuTexture | undefined;
94
129
  /** 1x1 white and flat-normal textures for empty slots. */
@@ -105,6 +140,25 @@ export declare class GpuAssets {
105
140
  private resolveSlots;
106
141
  /** Whether a texture can be uploaded; if its pixels are gone (device loss), reloads it. */
107
142
  private available;
143
+ private twins;
144
+ /** What baseline textures ask of this asset layer: reloads, fallbacks, and twin accounting. */
145
+ private twinHost;
146
+ /**
147
+ * Baseline (0064), `render.describe → twins`: the twins waiting for their bytes and how long
148
+ * they've waited, and the last ones made with their waits (0 when made from kept bytes).
149
+ */
150
+ describeTwins(): {
151
+ pending: {
152
+ texture: string;
153
+ kind: string;
154
+ waitedMs: number;
155
+ }[];
156
+ recent: {
157
+ texture: string;
158
+ kind: string;
159
+ waitedMs: number;
160
+ }[];
161
+ };
108
162
  /** The group-1 layout of a material type on this device. */
109
163
  layoutOf(type: MaterialType): GPUBindGroupLayout;
110
164
  /** The material's uniforms and bind group, uploaded when its version or textures change. */
@@ -1,11 +1,14 @@
1
- import { assetServer } from "@aethervtt/shard-assets";
1
+ import { AssetServerResource, assetServer } from "@aethervtt/shard-assets";
2
2
  import { defineResource } from "@aethervtt/shard-core";
3
- import { FORMAT_INFO, Textures } from "@aethervtt/shard-texture";
4
- import { STANDARD_TYPE, TEXTURE_SLOTS } from "./assets.js";
3
+ import { Mesh } from "@aethervtt/shard-mesh";
4
+ import { FORMAT_INFO, Texture, Textures } from "@aethervtt/shard-texture";
5
+ import { MaterialAsset, STANDARD_TYPE, TEXTURE_SLOTS } from "./assets.js";
5
6
  import { materialLayout } from "./shaders.js";
6
7
  const SRGB_SLOT = [true, false, false, false, true];
7
8
  class GpuAssets {
8
9
  meshes = /* @__PURE__ */ new Map();
10
+ /** Where mesh uploads are counted (`RenderStats.current.meshesRebuilt`), when set. */
11
+ counts;
9
12
  materials = /* @__PURE__ */ new Map();
10
13
  textures = /* @__PURE__ */ new Map();
11
14
  defaults;
@@ -31,6 +34,16 @@ class GpuAssets {
31
34
  gpu;
32
35
  memory;
33
36
  materialLayout;
37
+ /**
38
+ * The baseline tier's texture strategy (0064), set on a baseline device: one format and one
39
+ * binding dimension per texture, and twins for slots that read it otherwise.
40
+ */
41
+ baseline;
42
+ /** Uploaded this frame on baseline: their bytes go at the next frame (twins take them first). */
43
+ releaseLater = [];
44
+ /** Asset guids of textures, for reloading one a twin needs. */
45
+ guids = /* @__PURE__ */ new WeakMap();
46
+ world;
34
47
  constructor(gpu, memory) {
35
48
  this.gpu = gpu;
36
49
  this.memory = memory;
@@ -40,6 +53,11 @@ class GpuAssets {
40
53
  /** Call once per frame before drawing: rebuilds after device loss and resets readiness. */
41
54
  beginFrame() {
42
55
  this.ready.clear();
56
+ for (let i = 0; i < this.releaseLater.length; i++) {
57
+ const t = this.releaseLater[i];
58
+ if (!t.keepCpu) t.levels = void 0;
59
+ }
60
+ this.releaseLater.length = 0;
43
61
  if (this.generation === this.gpu.generation) return;
44
62
  this.generation = this.gpu.generation;
45
63
  this.materialLayout = createMaterialLayout(this.gpu);
@@ -56,7 +74,11 @@ class GpuAssets {
56
74
  if (mesh.gpu) gm.count = mesh.drawCount;
57
75
  return gm;
58
76
  }
59
- if (gm && gm.generation === gpu.generation) this.destroyMesh(gm);
77
+ if (this.counts) this.counts.meshesRebuilt++;
78
+ if (gm && gm.generation === gpu.generation) {
79
+ if (!mesh.gpu && this.rewrite(mesh, gm)) return gm;
80
+ this.destroyMesh(gm);
81
+ }
60
82
  if (mesh.gpu) return this.gpuMesh(mesh);
61
83
  const device = gpu.device;
62
84
  const n = mesh.vertexCount;
@@ -97,6 +119,42 @@ class GpuAssets {
97
119
  this.meshes.set(mesh, gm);
98
120
  return gm;
99
121
  }
122
+ /**
123
+ * Writes a CPU mesh's new data into the buffers of its previous version, when each fits and the
124
+ * mesh is still indexed the same way. Returns false when it needs new buffers.
125
+ */
126
+ rewrite(mesh, gm) {
127
+ if (gm.sharedVertices || gm.sharedIndices) return false;
128
+ const indices = mesh.indices;
129
+ if (indices === void 0 !== (gm.indices === void 0)) return false;
130
+ const n = mesh.vertexCount;
131
+ const fits = (buffer, bytes) => buffer.size >= bytes;
132
+ if (!fits(gm.positions, n * 12) || !fits(gm.normals, n * 12) || !fits(gm.uvs, n * 8) || !fits(gm.uvs1, n * 8) || !fits(gm.tangents, n * 16) || indices !== void 0 && !fits(gm.indices, indices.byteLength + 3 & ~3))
133
+ return false;
134
+ const queue = this.gpu.device.queue;
135
+ const write = (buffer, data) => {
136
+ if (data.length > 0) queue.writeBuffer(buffer, 0, data, 0, data.length);
137
+ };
138
+ write(gm.positions, mesh.positions);
139
+ write(gm.normals, mesh.normals ?? new Float32Array(n * 3).map((_, i) => i % 3 === 1 ? 1 : 0));
140
+ const uvs = mesh.uvs ?? new Float32Array(n * 2);
141
+ write(gm.uvs, uvs);
142
+ write(gm.uvs1, mesh.uvs1 ?? uvs);
143
+ write(gm.tangents, mesh.tangents ?? new Float32Array(n * 4));
144
+ if (indices) {
145
+ let data = indices;
146
+ if (data instanceof Uint16Array && data.length % 2 === 1) {
147
+ const padded = new Uint16Array(data.length + 1);
148
+ padded.set(data);
149
+ data = padded;
150
+ }
151
+ write(gm.indices, data);
152
+ gm.indexFormat = indices instanceof Uint32Array ? "uint32" : "uint16";
153
+ }
154
+ gm.version = mesh.version;
155
+ gm.count = mesh.drawCount;
156
+ return true;
157
+ }
100
158
  destroyMesh(gm) {
101
159
  if (gm.sharedVertices) return;
102
160
  gm.positions.destroy();
@@ -155,6 +213,46 @@ class GpuAssets {
155
213
  this.meshes.set(mesh, gm);
156
214
  return gm;
157
215
  }
216
+ /**
217
+ * Frees GPU copies of assets as the asset server drops them: unloaded (an owner released, 0061),
218
+ * or a fallback replaced by the real asset. Returns the unsubscribe.
219
+ */
220
+ watch(server) {
221
+ return server.onUnload((_, item) => {
222
+ if (item instanceof Texture) this.releaseTexture(item);
223
+ else if (item instanceof Mesh) this.releaseMesh(item);
224
+ else if (item instanceof MaterialAsset) this.releaseMaterial(item);
225
+ });
226
+ }
227
+ /** The GPU objects made for one asset object (a texture, mesh or material): owners.describe (0061). */
228
+ objectsOf(item) {
229
+ const out = { buffers: 0, textures: 0, bytes: 0 };
230
+ const gpu = this.gpu;
231
+ if (item instanceof Texture) {
232
+ const gt = this.textures.get(item);
233
+ if (gt && gt.generation === gpu.generation) {
234
+ out.textures = 1;
235
+ out.bytes = gt.bytes;
236
+ }
237
+ } else if (item instanceof Mesh) {
238
+ const gm = this.meshes.get(item);
239
+ if (gm && gm.generation === gpu.generation && !gm.sharedVertices) {
240
+ const buffers = [gm.positions, gm.normals, gm.uvs, gm.uvs1, gm.tangents];
241
+ if (gm.indices && !gm.sharedIndices) buffers.push(gm.indices);
242
+ out.buffers = buffers.length;
243
+ for (const b of buffers) out.bytes += b.size;
244
+ }
245
+ } else if (item instanceof MaterialAsset) {
246
+ const gm = this.materials.get(item);
247
+ if (gm && gm.generation === gpu.generation) {
248
+ const buffers = [gm.buffer, gm.textureBuffer];
249
+ if (gm.ownBuffer) buffers.push(gm.ownBuffer);
250
+ out.buffers = buffers.length;
251
+ for (const b of buffers) out.bytes += b.size;
252
+ }
253
+ }
254
+ return out;
255
+ }
158
256
  /** Frees a mesh's GPU buffers now (they're made again if it's drawn later). */
159
257
  releaseMesh(mesh) {
160
258
  const gm = this.meshes.get(mesh);
@@ -162,6 +260,30 @@ class GpuAssets {
162
260
  if (gm.generation === this.gpu.generation) this.destroyMesh(gm);
163
261
  this.meshes.delete(mesh);
164
262
  }
263
+ /** Frees a texture's GPU copy now (it's uploaded again if it's used later). */
264
+ releaseTexture(texture) {
265
+ const gt = this.textures.get(texture);
266
+ if (!gt) return;
267
+ if (gt.generation === this.gpu.generation) {
268
+ if (gt.destroy) gt.destroy();
269
+ else gt.texture.destroy();
270
+ this.memory.textures--;
271
+ this.memory.textureBytes -= gt.bytes;
272
+ }
273
+ this.textures.delete(texture);
274
+ }
275
+ /** Frees a material's uniform buffers now (they're made again if it's drawn later). */
276
+ releaseMaterial(material) {
277
+ const gm = this.materials.get(material);
278
+ if (!gm) return;
279
+ if (gm.generation === this.gpu.generation) {
280
+ gm.buffer.destroy();
281
+ gm.textureBuffer.destroy();
282
+ gm.ownBuffer?.destroy();
283
+ }
284
+ this.materials.delete(material);
285
+ this.ready.delete(material);
286
+ }
165
287
  /** Uploads a texture (all levels) on first use and when its version changes. */
166
288
  texture(texture) {
167
289
  const gpu = this.gpu;
@@ -174,7 +296,16 @@ class GpuAssets {
174
296
  this.memory.textures--;
175
297
  this.memory.textureBytes -= existing.bytes;
176
298
  }
177
- existing?.texture.destroy();
299
+ if (existing?.destroy) existing.destroy();
300
+ else existing?.texture.destroy();
301
+ if (this.baseline) {
302
+ const out2 = new this.baseline.BaselineTexture(this.twinHost(), texture);
303
+ this.textures.set(texture, out2);
304
+ this.memory.textures++;
305
+ this.memory.textureBytes += out2.bytes;
306
+ if (!texture.keepCpu) this.releaseLater.push(texture);
307
+ return out2;
308
+ }
178
309
  const info = FORMAT_INFO[texture.format];
179
310
  const srgbView = texture.gpuOnly ? void 0 : info.srgbView;
180
311
  const layers = texture.faces * texture.layers;
@@ -229,6 +360,14 @@ class GpuAssets {
229
360
  defaultTextures() {
230
361
  const gpu = this.gpu;
231
362
  if (this.defaults && this.defaults.white.generation === gpu.generation) return this.defaults;
363
+ if (this.baseline) {
364
+ this.defaults = {
365
+ white: this.baseline.solidTexture(this.twinHost(), [255, 255, 255, 255]),
366
+ normal: this.baseline.solidTexture(this.twinHost(), [128, 128, 255, 255])
367
+ };
368
+ this.samplers.clear();
369
+ return this.defaults;
370
+ }
232
371
  const solid = (label, rgba) => {
233
372
  const texture = gpu.device.createTexture({
234
373
  label,
@@ -289,6 +428,7 @@ class GpuAssets {
289
428
  return ready;
290
429
  }
291
430
  resolveSlots(world, material) {
431
+ this.world = world;
292
432
  const store = world.tryResource(Textures);
293
433
  material.sync();
294
434
  const type = material.type;
@@ -300,7 +440,7 @@ class GpuAssets {
300
440
  this.ownTextures[i] = void 0;
301
441
  continue;
302
442
  }
303
- const texture = store?.get(ref);
443
+ const texture = store?.get(ref) ?? missingTexture(world, ref);
304
444
  if (!texture || !this.available(world, texture, ref)) return false;
305
445
  this.ownTextures[i] = texture;
306
446
  }
@@ -312,14 +452,22 @@ class GpuAssets {
312
452
  this.slotTextures[i] = void 0;
313
453
  continue;
314
454
  }
315
- const texture = store?.get(ref);
455
+ const texture = store?.get(ref) ?? missingTexture(world, ref);
316
456
  if (!texture || !this.available(world, texture, ref)) return false;
317
457
  this.slotTextures[i] = texture;
318
458
  }
459
+ if (this.baseline && material.value.deferUntilReady) {
460
+ for (let i = 0; i < 5; i++) {
461
+ const texture = this.slotTextures[i];
462
+ const g = texture ? this.texture(texture) : void 0;
463
+ if (g && g.waiting(SRGB_SLOT[i], false)) return false;
464
+ }
465
+ }
319
466
  return true;
320
467
  }
321
468
  /** Whether a texture can be uploaded; if its pixels are gone (device loss), reloads it. */
322
469
  available(world, texture, ref) {
470
+ if (ref.guid) this.guids.set(texture, ref.guid);
323
471
  if (texture.levels || this.textures.has(texture)) return true;
324
472
  if (ref.guid && !this.reloading.has(ref.guid)) {
325
473
  this.reloading.add(ref.guid);
@@ -328,6 +476,34 @@ class GpuAssets {
328
476
  }
329
477
  return false;
330
478
  }
479
+ twins;
480
+ /** What baseline textures ask of this asset layer: reloads, fallbacks, and twin accounting. */
481
+ twinHost() {
482
+ const assets = this;
483
+ this.twins ??= new this.baseline.Twins({
484
+ gpu: this.gpu,
485
+ get world() {
486
+ return assets.world;
487
+ },
488
+ guidOf: (texture) => this.guids.get(texture),
489
+ reload: (texture) => {
490
+ const guid = this.guids.get(texture);
491
+ const world = this.world;
492
+ if (!guid || !world || this.reloading.has(guid)) return;
493
+ this.reloading.add(guid);
494
+ void assetServer(world).reload(guid).finally(() => this.reloading.delete(guid));
495
+ },
496
+ white: () => this.defaultTextures().white
497
+ });
498
+ return this.twins;
499
+ }
500
+ /**
501
+ * Baseline (0064), `render.describe → twins`: the twins waiting for their bytes and how long
502
+ * they've waited, and the last ones made with their waits (0 when made from kept bytes).
503
+ */
504
+ describeTwins() {
505
+ return this.twins?.describe() ?? { pending: [], recent: [] };
506
+ }
331
507
  /** The group-1 layout of a material type on this device. */
332
508
  layoutOf(type) {
333
509
  return type.bindGroupLayout(this.gpu, standardEntries());
@@ -451,7 +627,7 @@ class GpuAssets {
451
627
  });
452
628
  } else {
453
629
  const g = gm.ownBound[i] ?? defaults.white;
454
- entries.push({ binding: binding++, resource: g.linear });
630
+ entries.push({ binding: binding++, resource: type.colors.has(name) ? g.srgb : g.linear });
455
631
  }
456
632
  entries.push({ binding: binding++, resource: this.sampler("repeat", "linear") });
457
633
  }
@@ -484,6 +660,13 @@ function standardEntries() {
484
660
  function createMaterialLayout(gpu) {
485
661
  return STANDARD_TYPE.bindGroupLayout(gpu, standardEntries());
486
662
  }
663
+ function missingTexture(world, ref) {
664
+ if (ref.guid === void 0) return void 0;
665
+ const server = world.tryResource(AssetServerResource);
666
+ if (!server || server.entry(ref.guid)) return void 0;
667
+ server.missing(ref, "Texture");
668
+ return world.tryResource(Textures)?.byGuid(ref.guid);
669
+ }
487
670
  const GpuAssetsResource = defineResource("render/GpuAssets", {
488
671
  description: "GPU copies of meshes, textures, and materials."
489
672
  });
package/dist/graph.d.ts CHANGED
@@ -67,6 +67,11 @@ export interface NodeContext {
67
67
  /** Set for compute nodes. */
68
68
  readonly computePass: GPUComputePassEncoder | undefined;
69
69
  texture(name: string): GPUTexture;
70
+ /**
71
+ * Hands later nodes `texture` as resource `name` for the rest of this view's frame, instead of a
72
+ * pooled one: a node that keeps its output across frames (TAA's history) writes it once.
73
+ */
74
+ provide(name: string, texture: GPUTexture): void;
70
75
  /** Timestamp writes for a pass the node begins itself (raw nodes), labeled `name`. */
71
76
  timestamps(name: string): GPURenderPassTimestampWrites | undefined;
72
77
  /** Runs after the frame's commands are submitted (e.g. to map a readback buffer). */
@@ -85,6 +90,11 @@ export interface NodeDescriptor {
85
90
  phase?: number;
86
91
  /** Runs even if nothing reads its outputs. */
87
92
  sideEffects?: boolean;
93
+ /**
94
+ * Render nodes: always a render pass of its own, never shared with the nodes around it (see
95
+ * `continues`), so its GPU time is its own: what reads it (FoliageBudget's `gpu:foliage/*`) needs it.
96
+ */
97
+ ownPass?: boolean;
88
98
  /**
89
99
  * Whether the node runs for a view (e.g. only when the camera has the effect's component).
90
100
  * Disabled nodes are removed before resolving, so what they fed is culled too.
@@ -103,8 +113,14 @@ export declare const RenderPhase: {
103
113
  readonly Opaque: 300;
104
114
  readonly Lighting: 350;
105
115
  readonly Sky: 400;
116
+ /** Coplanar ground bands (0057): after the sky, which draws where no depth was written. */
117
+ readonly Ground: 420;
106
118
  readonly Sprites: 450;
107
119
  readonly Transparent: 500;
120
+ /** Projected fog's composite (0058): after transparent objects, before the overlay band. */
121
+ readonly Fog: 520;
122
+ /** Ground bands from the overlay band up, over fog (0058). */
123
+ readonly Overlay3d: 530;
108
124
  readonly Effects3d: 550;
109
125
  readonly Resolve: 600;
110
126
  readonly Post: 700;
@@ -163,17 +179,46 @@ export declare class RenderGraph {
163
179
  /** Views rendered in the last frame, in order. */
164
180
  private lastViews;
165
181
  private readonly viewData;
182
+ /** Each node's `render/<node>` span and its CPU time this frame, summed over views (0074). */
183
+ private readonly nodeSlots;
184
+ private readonly nodeSpans;
185
+ private nodeMs;
186
+ private nodeRan;
187
+ /** Nodes disabled for measurement (0075): see `ablate`. */
188
+ private readonly ablatedNodes;
166
189
  constructor(gpu: GpuContext);
190
+ /** Rejects pending captures and destroys the timer's query set (the app is being disposed). */
191
+ dispose(): void;
167
192
  addNode(name: string, node: NodeDescriptor): void;
168
193
  removeNode(name: string): void;
169
194
  hasNode(name: string): boolean;
195
+ /** Every node's name, in the order they were added. */
196
+ nodeNames(): string[];
197
+ /**
198
+ * Disables nodes for measurement (0075's ablation), replacing the previous set; `[]` restores
199
+ * them. A disabled render or compute node still begins and ends its pass, with its attachments'
200
+ * clears, so the textures it writes stay valid for what reads them; only its `run` (its draws and
201
+ * dispatches) is skipped. A raw node, which begins its own passes, is skipped whole. The graph
202
+ * resolves as before: what the node fed still runs, on whatever its textures hold. The image is
203
+ * wrong while nodes are disabled. Throws `render/unknown-node` for a name the graph lacks.
204
+ */
205
+ ablate(names: Iterable<string>): void;
206
+ /** The nodes `ablate` disabled. */
207
+ ablated(): string[];
170
208
  /** The data of a view rendered in the last frame. */
171
209
  lastViewData(view: string): Record<string, unknown> | undefined;
172
210
  /** Declares a transient texture any node can write, read, or attach by name. */
173
211
  declare(texture: TransientTexture): void;
174
212
  /** The resolution with every node enabled and no aliases. */
175
213
  resolve(): ResolvedGraph;
214
+ /** `off`: nodes that don't run on this device (features the baseline tier doesn't support). */
176
215
  private resolveFor;
216
+ /**
217
+ * Whether render node `order[k]` can draw in the pass `order[from]` began: the same color
218
+ * attachments, loaded rather than cleared, the same depth (written only if the pass writes it),
219
+ * and nothing it samples bound as an attachment.
220
+ */
221
+ private continues;
177
222
  /** Whether a node after `k` loads or reads `resource` (so its contents must be stored). */
178
223
  private loadedLater;
179
224
  /**