@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/plugin.js CHANGED
@@ -1,20 +1,39 @@
1
+ import { AssetServerResource } from "@aethervtt/shard-assets";
1
2
  import {
2
3
  defineEvent,
3
4
  defineResource,
4
5
  defineSystem,
5
6
  defineSystemSet,
6
7
  Last,
8
+ ProfilerResource,
7
9
  ShardError
8
10
  } from "@aethervtt/shard-core";
9
- import { createGpuContext } from "@aethervtt/shard-gpu";
10
- import { definePlugin, LogResource } from "@aethervtt/shard-runtime";
11
+ import {
12
+ createGpuContext
13
+ } from "@aethervtt/shard-gpu";
14
+ import {
15
+ definePlugin,
16
+ FrameDemand,
17
+ LOADING_DEMAND,
18
+ LogResource,
19
+ PerfProviders
20
+ } from "@aethervtt/shard-runtime";
11
21
  import { ShaderLibrary } from "@aethervtt/shard-shader";
22
+ import { PassCosts } from "./ablation.js";
23
+ import { loadDataTextures, releaseDataStores } from "./data-store.js";
24
+ import { checkUnsupportedFeatures, describeFeatures, RenderFeatures } from "./features.js";
12
25
  import { RenderGraph } from "./graph.js";
26
+ import {
27
+ healthSystem,
28
+ RenderHealth,
29
+ RenderHealthChanged,
30
+ RenderHealthReports,
31
+ raiseHealthIssue
32
+ } from "./health.js";
13
33
  import { registerEngineShaders } from "./shaders.js";
14
34
  import { GpuMemory, RenderCounters, RenderStats } from "./stats.js";
15
- import { WindowTarget } from "./target.js";
16
35
  const Gpu = defineResource("render/Gpu", {
17
- description: "The GPU device, caches, and canvas."
36
+ description: "The GPU device, its surfaces (canvases), and caches. Apps can share one."
18
37
  });
19
38
  const Graph = defineResource("render/Graph", {
20
39
  description: "Render graph nodes, run for every view each frame."
@@ -26,7 +45,7 @@ const Views = defineResource("render/Views", {
26
45
  description: "Views to render this frame. Rebuilt every frame by extract systems."
27
46
  });
28
47
  const Window = defineResource("render/Window", {
29
- description: "Where cameras without a target render: the canvas swapchain, or an offscreen target when headless."
48
+ description: "Where cameras without a target render: the app's surface (canvas), or an offscreen target when headless."
30
49
  });
31
50
  const GpuDeviceLost = defineEvent(
32
51
  "render/GpuDeviceLost",
@@ -51,12 +70,37 @@ const begin = defineSystem({
51
70
  world.resource(Gpu).pipelines.skipped = 0;
52
71
  }
53
72
  });
73
+ const markMissing = defineSystem({
74
+ name: "render/mark-missing",
75
+ description: "Puts MissingAsset on entities that reference an asset showing a fallback.",
76
+ run: (_, world) => world.tryResource(AssetServerResource)?.markMissing()
77
+ });
78
+ const checkUnsupported = defineSystem({
79
+ name: "render/check-unsupported",
80
+ description: "On the baseline tier: reports render features the scene uses that it can't run (render/feature-unsupported), and WebGL2 shaders translated at load (render/shader-cache-miss).",
81
+ setup: () => ({ misses: 0 }),
82
+ run: (state, world) => {
83
+ const gpu = world.tryResource(Gpu);
84
+ if (gpu?.tier !== "baseline") return;
85
+ checkUnsupportedFeatures(world);
86
+ const cache = gpu.shaderCache();
87
+ if (!cache || cache.misses.length === state.misses) return;
88
+ state.misses = cache.misses.length;
89
+ let ms = 0;
90
+ for (const m of cache.misses) ms += m.ms;
91
+ const load = cache.nagaLoadMs === void 0 ? "" : `, ${cache.nagaLoadMs.toFixed(0)} ms to load it`;
92
+ raiseHealthIssue(world, {
93
+ code: "render/shader-cache-miss",
94
+ severity: "info",
95
+ message: `${state.misses} shader stage${state.misses === 1 ? "" : "s"} translated to GLSL at load (${ms.toFixed(0)} ms in naga${load}): \`shard shaders bake\` ships them translated`
96
+ });
97
+ }
98
+ });
54
99
  const execute = defineSystem({
55
100
  name: "render/execute-graph",
56
101
  description: "Runs the render graph for every view and submits.",
57
- setup: () => ({ recovering: { value: false } }),
58
- run: ({ recovering }, world) => {
59
- if (recovering.value) return;
102
+ run: (_, world) => {
103
+ if (world.resource(Gpu).status !== "ok") return;
60
104
  const views = world.resource(Views).list;
61
105
  const window = world.tryResource(Window);
62
106
  const options = world.resource(RenderOptions);
@@ -65,13 +109,35 @@ const execute = defineSystem({
65
109
  }
66
110
  if (views.length === 0) return;
67
111
  world.resource(Graph).execute(world, views);
112
+ world.tryResource(RenderStats)?.endFrame(world.resource(Gpu).uploads(options.owner));
113
+ const gpu = world.resource(Gpu);
114
+ anyPending = gpu.pipelines.skipped > 0 || gpu.pipelines.pending > 0;
115
+ if (!anyPending) world.tryResource(RenderStats)?.forEach(notePending);
116
+ world.tryResource(FrameDemand)?.set(LOADING_DEMAND, anyPending);
68
117
  }
69
118
  });
119
+ let anyPending = false;
120
+ function notePending(stats) {
121
+ if (stats.pending > 0) anyPending = true;
122
+ }
70
123
  const RenderDescribers = defineResource(
71
124
  "render/Describers",
72
125
  { description: "Sections plugins add to render.describe.", init: () => /* @__PURE__ */ new Map() }
73
126
  );
74
- const RenderOptions = defineResource("render/Options");
127
+ const RenderOptions = defineResource("render/Options", {
128
+ description: "The render plugin's settings: the fallback window view, and the app's GPU owner."
129
+ });
130
+ const renderApps = /* @__PURE__ */ new WeakMap();
131
+ let nextOwner = 1;
132
+ function nextRefresh() {
133
+ return new Promise((resolve) => requestAnimationFrame(resolve));
134
+ }
135
+ function presentOn(state) {
136
+ return () => {
137
+ const done = state.gpu.device.queue.onSubmittedWorkDone();
138
+ return typeof requestAnimationFrame === "function" ? done.then(nextRefresh) : done.then(() => void 0);
139
+ };
140
+ }
75
141
  function renderPlugin(options = {}) {
76
142
  return definePlugin({
77
143
  name: "render",
@@ -79,8 +145,13 @@ function renderPlugin(options = {}) {
79
145
  // graph and device
80
146
  Gpu,
81
147
  GpuDeviceLost,
148
+ PassCosts,
82
149
  Graph,
83
150
  RenderDescribers,
151
+ RenderFeatures,
152
+ RenderHealth,
153
+ RenderHealthChanged,
154
+ RenderHealthReports,
84
155
  RenderOptions,
85
156
  Shaders,
86
157
  Views,
@@ -88,40 +159,130 @@ function renderPlugin(options = {}) {
88
159
  ],
89
160
  dependencies: ["core/time"],
90
161
  build(app) {
91
- app.insertResource(Views, { list: [] }).insertResource(RenderOptions, { windowView: options.windowView ?? true }).configureSets(
162
+ const state = {
163
+ owner: options.owner ?? `render:${nextOwner++}`,
164
+ gpu: void 0,
165
+ ownsDevice: false,
166
+ surface: void 0,
167
+ saved: [],
168
+ unsubscribe: []
169
+ };
170
+ renderApps.set(app, state);
171
+ app.addScope({
172
+ enter() {
173
+ const gpu = state.gpu;
174
+ state.saved.push(gpu?.owner);
175
+ if (gpu) gpu.owner = state.owner;
176
+ },
177
+ exit() {
178
+ const previous = state.saved.pop();
179
+ if (state.gpu && previous !== void 0) state.gpu.owner = previous;
180
+ }
181
+ });
182
+ app.insertResource(Views, { list: [] }).insertResource(RenderOptions, {
183
+ windowView: options.windowView ?? true,
184
+ owner: state.owner
185
+ }).configureSets(
92
186
  Last,
93
187
  RenderSet.Extract.after(RenderSet.Begin),
94
188
  RenderSet.Prepare.after(RenderSet.Extract),
95
189
  RenderSet.Queue.after(RenderSet.Prepare),
96
190
  RenderSet.Upload.after(RenderSet.Queue),
97
191
  RenderSet.Graph.after(RenderSet.Upload)
98
- ).addSystems(Last, begin.inSet(RenderSet.Begin), execute.inSet(RenderSet.Graph));
192
+ ).addSystems(
193
+ Last,
194
+ begin.inSet(RenderSet.Begin),
195
+ markMissing.inSet(RenderSet.Begin).after(begin),
196
+ checkUnsupported.inSet(RenderSet.Begin).after(begin),
197
+ execute.inSet(RenderSet.Graph),
198
+ healthSystem({ status: () => state.gpu?.status ?? "ok" }).inSet(RenderSet.Graph).after(execute)
199
+ );
99
200
  },
100
201
  async ready(app) {
101
- const gpu = options.gpu ?? await createGpuContext({ canvas: options.canvas, features: options.features });
102
- app.insertResource(Gpu, gpu);
103
- app.insertResource(Graph, new RenderGraph(gpu));
202
+ const state = renderApps.get(app);
203
+ const given = options.gpu ?? options.surface?.gpu;
204
+ if (options.surface && options.gpu && options.surface.gpu !== options.gpu) {
205
+ throw new ShardError("render/surface-device", "The surface belongs to another GpuContext", {
206
+ hint: "Pass the GpuContext the surface was added to (surface.gpu), or omit gpu."
207
+ });
208
+ }
209
+ const gpu = given ?? await createGpuContext({
210
+ features: options.features,
211
+ backend: options.backend,
212
+ webgl2: options.webgl2,
213
+ tier: options.tier,
214
+ canvas: options.canvas,
215
+ alpha: options.alpha
216
+ });
217
+ state.gpu = gpu;
218
+ state.ownsDevice = !given;
219
+ if (gpu.tier === "baseline") await loadDataTextures();
220
+ gpu.withOwner(state.owner, () => {
221
+ app.insertResource(Gpu, gpu);
222
+ app.insertResource(Graph, new RenderGraph(gpu));
223
+ });
224
+ const providers = app.world.initResource(PerfProviders);
225
+ providers.gpuMemory = gpuMemory;
226
+ providers.adapter = adapterIdentity;
227
+ const profiler = app.world.tryResource(ProfilerResource);
228
+ if (profiler) {
229
+ profiler.gpu.status = gpu.features.has("timestamp-query") ? "available" : "unavailable";
230
+ }
231
+ app.setPresenter(presentOn(state));
104
232
  const shaders = options.shaders ?? new ShaderLibrary();
105
233
  if (!options.shaders) registerEngineShaders(shaders);
106
234
  if (options.shaderBake) shaders.preload(options.shaderBake);
107
235
  app.insertResource(Shaders, shaders);
108
- if (gpu.context) app.insertResource(Window, new WindowTarget(gpu));
109
- else if (options.target) app.insertResource(Window, options.target);
236
+ const surface = options.surface ?? (options.canvas ? gpu.surfaces.find((s) => s.canvas === options.canvas) ?? gpu.addSurface(options.canvas, { alpha: options.alpha }) : void 0);
237
+ state.surface = surface;
238
+ if (surface) {
239
+ app.insertResource(Window, surface);
240
+ state.unsubscribe.push(surface.onResize(() => app.disposed || app.requestFrame()));
241
+ } else if (options.target) app.insertResource(Window, options.target);
110
242
  const log = app.world.tryResource(LogResource);
111
- if (log) gpu.onError((error) => log.error(error));
112
- gpu.onDeviceLost((info) => {
113
- app.world.send(GpuDeviceLost, info);
114
- void gpu.recreate();
115
- });
243
+ if (log) state.unsubscribe.push(gpu.onError((error) => log.error(error)));
244
+ state.unsubscribe.push(
245
+ gpu.onDeviceLost((info) => {
246
+ app.world.send(GpuDeviceLost, info);
247
+ gpu.recreate().then(
248
+ () => app.disposed || app.requestFrame(),
249
+ () => app.disposed || app.requestFrame()
250
+ );
251
+ })
252
+ );
253
+ },
254
+ beforeDispose(app) {
255
+ const gpu = renderApps.get(app)?.gpu;
256
+ if (gpu?.status === "ok") return gpu.device.queue.onSubmittedWorkDone();
257
+ },
258
+ dispose(app) {
259
+ const state = renderApps.get(app);
260
+ if (!state) return;
261
+ renderApps.delete(app);
262
+ for (const off of state.unsubscribe.splice(0)) off();
263
+ app.world.tryResource(Graph)?.dispose();
264
+ state.surface?.remove();
265
+ const gpu = state.gpu;
266
+ if (!gpu) return;
267
+ releaseDataStores(gpu, state.owner);
268
+ gpu.release(state.owner);
269
+ if (state.ownsDevice) gpu.destroy();
116
270
  }
117
271
  });
118
272
  }
273
+ function renderOwner(world) {
274
+ return world.resource(RenderOptions).owner;
275
+ }
119
276
  function captureView(world, view = WINDOW_VIEW) {
120
- return world.resource(Graph).capture(view);
277
+ const shot = world.resource(Graph).capture(view);
278
+ world.wake();
279
+ return shot;
121
280
  }
122
281
  async function captureBuffer(world, view, buffer) {
123
282
  const graph = world.resource(Graph);
124
- const result = await graph.capture(view, buffer);
283
+ const pending = graph.capture(view, buffer);
284
+ world.wake();
285
+ const result = await pending;
125
286
  if (!("format" in result)) {
126
287
  const data = new Float32Array(result.data.length);
127
288
  for (let i = 0; i < data.length; i++) data[i] = result.data[i] / 255;
@@ -136,6 +297,20 @@ async function captureBuffer(world, view, buffer) {
136
297
  }
137
298
  return result;
138
299
  }
300
+ function adapterIdentity(world) {
301
+ const info = world.tryResource(Gpu)?.adapter?.info;
302
+ if (!info) return void 0;
303
+ return {
304
+ vendor: info.vendor,
305
+ architecture: info.architecture,
306
+ device: info.device,
307
+ description: info.description
308
+ };
309
+ }
310
+ function gpuMemory(world) {
311
+ const memory = world.tryResource(Gpu)?.memory();
312
+ return memory ? { bytes: memory.bytes, byCategory: { ...memory.byCategory } } : { bytes: 0, byCategory: {} };
313
+ }
139
314
  function describeRender(world) {
140
315
  const graph = world.tryResource(Graph);
141
316
  const gpu = world.tryResource(Gpu);
@@ -158,8 +333,29 @@ function describeRender(world) {
158
333
  gpuTimings: gpu.features.has("timestamp-query"),
159
334
  stats: Object.fromEntries(world.tryResource(RenderStats) ?? []),
160
335
  counters: { ...world.tryResource(RenderCounters) ?? { taaResets: 0, originShifts: 0 } },
161
- memory: world.tryResource(GpuMemory) ?? { textures: 0, textureBytes: 0 },
162
- recentErrors: gpu.errors.slice(-5).map((e) => e.toJSON())
336
+ // The ledger's total, so it agrees with perf.describe and perf records; and the textures the
337
+ // forward renderer uploaded, as before.
338
+ memory: {
339
+ ...gpuMemory(world),
340
+ ...world.tryResource(GpuMemory) ?? { textures: 0, textureBytes: 0 }
341
+ },
342
+ recentErrors: gpu.errors.slice(-5).map((e) => e.toJSON()),
343
+ features: describeFeatures(world),
344
+ health: world.tryResource(RenderHealth) ?? { state: "ok", issues: [] },
345
+ // The API and tier the device runs (0064), what it can do, and why a better option was skipped.
346
+ backend: gpu.backend,
347
+ tier: gpu.tier,
348
+ capabilities: gpu.capabilities,
349
+ // WebGL2: where shader translations came from, and what naga translated this session.
350
+ shaderCache: gpu.shaderCache(),
351
+ limits: {
352
+ maxTextureDimension2D: gpu.device.limits.maxTextureDimension2D,
353
+ maxColorAttachments: gpu.device.limits.maxColorAttachments,
354
+ maxSampledTexturesPerShaderStage: gpu.device.limits.maxSampledTexturesPerShaderStage,
355
+ maxInterStageShaderVariables: gpu.device.limits.maxInterStageShaderVariables,
356
+ maxUniformBufferBindingSize: gpu.device.limits.maxUniformBufferBindingSize
357
+ },
358
+ reasons: gpu.reasons
163
359
  };
164
360
  const sections = {};
165
361
  for (const [name, fn] of world.tryResource(RenderDescribers) ?? []) sections[name] = fn(world);
@@ -178,5 +374,7 @@ export {
178
374
  captureBuffer,
179
375
  captureView,
180
376
  describeRender,
377
+ gpuMemory,
378
+ renderOwner,
181
379
  renderPlugin
182
380
  };
@@ -1,6 +1,6 @@
1
1
  import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
2
2
  import { type ViewGpu } from './forward.js';
3
- import type { NodeContext } from './graph.js';
3
+ import type { NodeContext, RenderView } from './graph.js';
4
4
  import { type CameraData } from './view.js';
5
5
  export declare function idOf(o: object): number;
6
6
  export declare const F: () => number;
@@ -11,6 +11,8 @@ export declare const tex: (binding: number, sampleType?: GPUTextureSampleType) =
11
11
  sampleType: GPUTextureSampleType;
12
12
  };
13
13
  };
14
+ /** Depth read without comparison, in the fragment stage (a float texture on baseline, 0064). */
15
+ export declare const depthTex: (gpu: GpuContext, binding: number) => GPUBindGroupLayoutEntry;
14
16
  export declare const uniform: (binding: number, visibility?: number) => {
15
17
  binding: number;
16
18
  visibility: number;
@@ -35,7 +37,7 @@ export declare class PostCache {
35
37
  check(gpu: GpuContext): void;
36
38
  /** A fullscreen render pipeline: `shard::fullscreen` vertex stage and `root`'s `entry`. */
37
39
  render(ctx: NodeContext, key: string, root: string, entry: string, layouts: GPUBindGroupLayout[], targets: GPUColorTargetState[], defines?: Record<string, boolean>): GPURenderPipeline | undefined;
38
- compute(ctx: NodeContext, key: string, root: string, entry: string, layouts: GPUBindGroupLayout[]): GPUComputePipeline | undefined;
40
+ compute(ctx: NodeContext, key: string, root: string, entry: string, layouts: GPUBindGroupLayout[], defines?: Record<string, boolean>): GPUComputePipeline | undefined;
39
41
  /** A bind group, rebuilt when `key` (the ids of what it binds) changes. */
40
42
  group(gpu: GpuContext, slot: string, key: string, layout: GPUBindGroupLayout, entries: () => GPUBindGroupEntry[]): GPUBindGroup;
41
43
  buffer(gpu: GpuContext, name: string, size: number): GpuBuffer;
@@ -48,4 +50,22 @@ export declare function forwardView(ctx: NodeContext): {
48
50
  /** A fullscreen pass the node begins itself (raw nodes with several passes). */
49
51
  export declare function beginPass(ctx: NodeContext, name: string, view: GPUTextureView, load?: boolean): GPURenderPassEncoder;
50
52
  export declare const HDR: GPUColorTargetState[];
53
+ /** Color targets for a post pass that writes `texture`: the chain's format, per view. */
54
+ export declare function targetsFor(texture: GPUTexture): GPUColorTargetState[];
55
+ /**
56
+ * The format of the post chain's full-resolution ping-pong (post-a, post-b) for a view:
57
+ * rg11b10ufloat, half the bytes of rgba16float, where the device renders it, the view has no alpha
58
+ * to carry (0052's transparent views), and the device's rounding is known (`rg11Truncates`);
59
+ * rgba16float otherwise, and until then.
60
+ */
61
+ export declare function postFormat(gpu: GpuContext, view: RenderView): GPUTextureFormat;
62
+ /** Whether a pass writing `texture` rounds its output in the shader (the RG11_ROUND define). */
63
+ export declare function roundsRg11(gpu: GpuContext, texture: GPUTexture): boolean;
64
+ /**
65
+ * Whether the device truncates when it stores rg11b10ufloat (Metal does; the format leaves the
66
+ * rounding to the device). Post passes writing it then round in the shader (`rg11_round`, the
67
+ * RG11_ROUND define). Probed once per device, rendering 1 + 0.75 of a step and reading it back;
68
+ * undefined until the answer lands.
69
+ */
70
+ export declare function rg11Truncates(gpu: GpuContext): boolean | undefined;
51
71
  export declare const scratch: Float32Array<ArrayBuffer>;
@@ -1,6 +1,7 @@
1
1
  import { GpuBuffer } from "@aethervtt/shard-gpu";
2
2
  import { ForwardStateResource } from "./forward.js";
3
3
  import { Shaders } from "./plugin.js";
4
+ import { depthReadEntry } from "./tier.js";
4
5
  import { cameraOf } from "./view.js";
5
6
  const textureIds = /* @__PURE__ */ new WeakMap();
6
7
  let nextId = 1;
@@ -18,6 +19,7 @@ const tex = (binding, sampleType = "float") => ({
18
19
  visibility: F(),
19
20
  texture: { sampleType }
20
21
  });
22
+ const depthTex = (gpu, binding) => depthReadEntry(gpu, binding, F());
21
23
  const uniform = (binding, visibility = F()) => ({
22
24
  binding,
23
25
  visibility,
@@ -64,12 +66,12 @@ class PostCache {
64
66
  if (pipeline) this.pipelines.set(key, pipeline);
65
67
  return pipeline;
66
68
  }
67
- compute(ctx, key, root, entry, layouts) {
69
+ compute(ctx, key, root, entry, layouts, defines) {
68
70
  const gpu = ctx.gpu;
69
71
  this.check(gpu);
70
72
  const cached = this.pipelines.get(key);
71
73
  if (cached) return cached;
72
- const module = ctx.world.resource(Shaders).module(gpu, { root });
74
+ const module = ctx.world.resource(Shaders).module(gpu, defines ? { root, defines } : { root });
73
75
  if (!module) {
74
76
  gpu.pipelines.skipped++;
75
77
  return void 0;
@@ -133,16 +135,94 @@ function beginPass(ctx, name, view, load = false) {
133
135
  });
134
136
  }
135
137
  const HDR = [{ format: "rgba16float" }];
138
+ const RG11 = [{ format: "rg11b10ufloat" }];
139
+ function targetsFor(texture) {
140
+ return texture.format === "rg11b10ufloat" ? RG11 : HDR;
141
+ }
142
+ function postFormat(gpu, view) {
143
+ const cam = cameraOf(view);
144
+ if (!gpu.features.has("rg11b10ufloat-renderable") || cam === void 0 || cam.alphaOutput)
145
+ return "rgba16float";
146
+ return rg11Truncates(gpu) === void 0 ? "rgba16float" : "rg11b10ufloat";
147
+ }
148
+ function roundsRg11(gpu, texture) {
149
+ return texture.format === "rg11b10ufloat" && rg11Truncates(gpu) === true;
150
+ }
151
+ const truncates = /* @__PURE__ */ new WeakMap();
152
+ function rg11Truncates(gpu) {
153
+ const device = gpu.device;
154
+ const known = truncates.get(device);
155
+ if (known === "probing") return void 0;
156
+ if (known !== void 0) return known;
157
+ truncates.set(device, "probing");
158
+ probeRg11(device).then(
159
+ (v) => truncates.set(device, v),
160
+ // A lost device or a failed map: assume truncation, the safe side (rounding then is exact).
161
+ () => truncates.set(device, true)
162
+ );
163
+ return void 0;
164
+ }
165
+ async function probeRg11(device) {
166
+ const texture = device.createTexture({
167
+ label: "post/rg11-probe",
168
+ size: [1, 1],
169
+ format: "rg11b10ufloat",
170
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
171
+ });
172
+ const buffer = device.createBuffer({
173
+ label: "post/rg11-probe",
174
+ size: 4,
175
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
176
+ });
177
+ const module = device.createShaderModule({
178
+ label: "post/rg11-probe",
179
+ code: `
180
+ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
181
+ let xy = vec2f(f32((i << 1u) & 2u), f32(i & 2u));
182
+ return vec4f(xy * 2.0 - 1.0, 0.0, 1.0);
183
+ }
184
+ @fragment fn fs() -> @location(0) vec4f { return vec4f(1.0 + 0.75 / 64.0, 0.0, 0.0, 1.0); }`
185
+ });
186
+ const pipeline = await device.createRenderPipelineAsync({
187
+ label: "post/rg11-probe",
188
+ layout: "auto",
189
+ vertex: { module, entryPoint: "vs" },
190
+ fragment: { module, entryPoint: "fs", targets: [{ format: "rg11b10ufloat" }] }
191
+ });
192
+ const encoder = device.createCommandEncoder({ label: "post/rg11-probe" });
193
+ const pass = encoder.beginRenderPass({
194
+ colorAttachments: [{ view: texture.createView(), loadOp: "clear", storeOp: "store" }]
195
+ });
196
+ pass.setPipeline(pipeline);
197
+ pass.draw(3);
198
+ pass.end();
199
+ encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow: 256 }, [1, 1]);
200
+ device.queue.submit([encoder.finish()]);
201
+ try {
202
+ await buffer.mapAsync(GPUMapMode.READ);
203
+ const red = new Uint32Array(buffer.getMappedRange())[0] & 2047;
204
+ buffer.unmap();
205
+ return red === 15 << 6;
206
+ } finally {
207
+ texture.destroy();
208
+ buffer.destroy();
209
+ }
210
+ }
136
211
  const scratch = new Float32Array(64);
137
212
  export {
138
213
  F,
139
214
  HDR,
140
215
  PostCache,
141
216
  beginPass,
217
+ depthTex,
142
218
  forwardView,
143
219
  idOf,
220
+ postFormat,
221
+ rg11Truncates,
222
+ roundsRg11,
144
223
  sampler,
145
224
  scratch,
225
+ targetsFor,
146
226
  tex,
147
227
  uniform
148
228
  };
@@ -2,6 +2,11 @@ import { type World } from '@aethervtt/shard-core';
2
2
  import { type CameraData } from './view.js';
3
3
  /** Mip levels of a view's bloom chain: `radius` spreads it from 3 levels to the whole view. */
4
4
  export declare function bloomLevels(cam: CameraData): number;
5
+ /**
6
+ * Bloom's glow parameters for what composites it (the tonemap) and what meters it: intensity, the
7
+ * chain's level count (one mip per level), mix (1, no threshold) or add (0). Zeros without bloom.
8
+ */
9
+ export declare function bloomGlow(cam: CameraData, chain: GPUTexture | undefined, out: Float32Array, at: number): void;
5
10
  /** Metered EV100 per camera (from the GPU histogram, a frame or two late) and the adapted EV. */
6
11
  export interface ExposureState {
7
12
  metered: number | undefined;
@@ -32,7 +37,8 @@ export declare const adaptExposure: import("@aethervtt/shard-core").SystemDef<{
32
37
  /** Graph node names of the post chain, in order (for describe and tests). */
33
38
  export declare const POST_NODES: readonly [readonly ['post/fog', 1], readonly ['post/taa', 2], readonly ['post/motion-blur', 4], readonly ['post/dof', 8], readonly ['post/bloom', 16], readonly ['post/exposure', 32], readonly ['post/fxaa', 64]];
34
39
  /** Adds the prepass, SSAO, and the post chain to the graph. */
35
- export declare function addPostNodes(world: World): void;
40
+ /** The post chain's nodes. `baseline`: the baseline tier's exposure meter (0064), when on one. */
41
+ export declare function addPostNodes(world: World, baseline?: typeof import('./baseline/exposure.js')): void;
36
42
  /** The post section of `render.describe`: per view, active effects in order with GPU time. */
37
43
  export declare function describePost(world: World): {
38
44
  views: Record<string, unknown>;