@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
@@ -6,18 +6,23 @@ import {
6
6
  } from "@aethervtt/shard-core";
7
7
  import { Time } from "@aethervtt/shard-runtime";
8
8
  import { Exposure } from "./camera.js";
9
+ import { addRenderFeatures } from "./features.js";
9
10
  import { drawMaterials, ForwardStateResource, PASS_PREPASS, viewBindGroup } from "./forward.js";
10
11
  import { RenderPhase } from "./graph.js";
11
- import { Graph, Views } from "./plugin.js";
12
+ import { Gpu, Graph, Views } from "./plugin.js";
12
13
  import { AutoExposure, cocParams, hasEffect, needsPrepass, PostEffect } from "./post.js";
13
14
  import {
14
15
  beginPass,
16
+ depthTex,
15
17
  forwardView,
16
18
  HDR,
17
19
  idOf,
18
20
  PostCache,
21
+ postFormat,
22
+ roundsRg11,
19
23
  sampler,
20
24
  scratch,
25
+ targetsFor,
21
26
  tex,
22
27
  uniform
23
28
  } from "./post-common.js";
@@ -42,6 +47,16 @@ const prepassNode = {
42
47
  if (v.cam.deferred) drawMaterials(ctx, state, v.pv, v.cam, v.cam.forwardOnly, PASS_PREPASS);
43
48
  }
44
49
  };
50
+ const FOG_KEYS = [
51
+ "post/fog",
52
+ "post/fog/alpha",
53
+ "post/fog/interior",
54
+ "post/fog/alpha/interior",
55
+ "post/fog/rg11",
56
+ "post/fog/alpha/rg11",
57
+ "post/fog/interior/rg11",
58
+ "post/fog/alpha/interior/rg11"
59
+ ];
45
60
  const SSAO_STEPS = [
46
61
  [1, 4],
47
62
  [2, 4],
@@ -50,7 +65,7 @@ const SSAO_STEPS = [
50
65
  const invProj = mat4.create();
51
66
  function ssaoNode(phase, deferred) {
52
67
  const cache = new PostCache();
53
- let layouts;
68
+ let layout;
54
69
  let layoutGen = -1;
55
70
  return {
56
71
  kind: "raw",
@@ -60,41 +75,27 @@ function ssaoNode(phase, deferred) {
60
75
  return cam !== void 0 && cam.deferred === deferred && (cam.post.effects & PostEffect.Ssao) !== 0;
61
76
  },
62
77
  reads: ["ssao-normal", "ssao-depth"],
63
- writes: ["ssao", "ssao-half"],
78
+ writes: ["ssao"],
64
79
  run: (ctx) => {
65
80
  const v = forwardView(ctx);
66
81
  if (!v) return;
67
82
  const gpu = ctx.gpu;
68
- if (!layouts || layoutGen !== gpu.generation) {
83
+ if (!layout || layoutGen !== gpu.generation) {
69
84
  layoutGen = gpu.generation;
70
- layouts = {
71
- gtao: gpu.layouts.bindGroupLayout({
72
- label: "ssao/gtao",
73
- entries: [uniform(0), tex(1, "depth"), tex(2, "unfilterable-float"), uniform(3)]
74
- }),
75
- up: gpu.layouts.bindGroupLayout({
76
- label: "ssao/upsample",
77
- entries: [uniform(0), tex(1, "depth"), uniform(3), tex(4, "unfilterable-float")]
78
- })
79
- };
85
+ layout = gpu.layouts.bindGroupLayout({
86
+ label: "ssao/gtao",
87
+ entries: [uniform(0), depthTex(gpu, 1), tex(2, "unfilterable-float"), uniform(3)]
88
+ });
80
89
  }
81
90
  const gtao = cache.render(
82
91
  ctx,
83
92
  "ssao/gtao",
84
93
  "shard::post::ssao",
85
94
  "gtao",
86
- [layouts.gtao],
95
+ [layout],
87
96
  [{ format: "rg16float" }]
88
97
  );
89
- const up = cache.render(
90
- ctx,
91
- "ssao/upsample",
92
- "shard::post::ssao",
93
- "upsample",
94
- [layouts.up],
95
- [{ format: "r8unorm" }]
96
- );
97
- if (!gtao || !up) return;
98
+ if (!gtao) return;
98
99
  const { cam, pv } = v;
99
100
  const s = cam.post.ssao;
100
101
  const steps = SSAO_STEPS[s.quality] ?? SSAO_STEPS[1];
@@ -110,13 +111,12 @@ function ssaoNode(phase, deferred) {
110
111
  params.write(scratch, 0, 0, 24);
111
112
  const depth = ctx.texture("ssao-depth");
112
113
  const normal = ctx.texture("ssao-normal");
113
- const half = ctx.texture("ssao-half");
114
114
  const out = ctx.texture("ssao");
115
- const g1 = cache.group(
115
+ const group = cache.group(
116
116
  gpu,
117
117
  `${ctx.view.name}/gtao`,
118
118
  `${idOf(depth)}/${idOf(normal)}/${pv.uniform.version}/${params.version}`,
119
- layouts.gtao,
119
+ layout,
120
120
  () => [
121
121
  { binding: 0, resource: { buffer: pv.uniform.buffer } },
122
122
  { binding: 1, resource: depth.createView() },
@@ -124,26 +124,9 @@ function ssaoNode(phase, deferred) {
124
124
  { binding: 3, resource: { buffer: params.buffer } }
125
125
  ]
126
126
  );
127
- let pass = beginPass(ctx, "ssao", half.createView());
127
+ const pass = beginPass(ctx, "ssao", out.createView());
128
128
  pass.setPipeline(gtao);
129
- pass.setBindGroup(0, g1);
130
- pass.draw(3);
131
- pass.end();
132
- const g2 = cache.group(
133
- gpu,
134
- `${ctx.view.name}/ssao-up`,
135
- `${idOf(depth)}/${idOf(half)}/${pv.uniform.version}/${params.version}`,
136
- layouts.up,
137
- () => [
138
- { binding: 0, resource: { buffer: pv.uniform.buffer } },
139
- { binding: 1, resource: depth.createView() },
140
- { binding: 3, resource: { buffer: params.buffer } },
141
- { binding: 4, resource: half.createView() }
142
- ]
143
- );
144
- pass = beginPass(ctx, "ssao/upsample", out.createView());
145
- pass.setPipeline(up);
146
- pass.setBindGroup(0, g2);
129
+ pass.setBindGroup(0, group);
147
130
  pass.draw(3);
148
131
  pass.end();
149
132
  pv.ao = out;
@@ -157,7 +140,8 @@ function fogNode() {
157
140
  return {
158
141
  kind: "render",
159
142
  phase: RenderPhase.Post,
160
- enabled: hasEffect(PostEffect.Fog),
143
+ // With aerial perspective, the atmosphere's composite applies fog in its own pass.
144
+ enabled: (view) => ((cameraOf(view)?.post.effects ?? 0) & (PostEffect.Fog | PostEffect.Atmosphere)) === PostEffect.Fog,
161
145
  reads: ["fog-in", "depth", "environment", "clusters"],
162
146
  writes: ["fog-out"],
163
147
  color: [{ resource: "fog-out", clear: { r: 0, g: 0, b: 0, a: 1 } }],
@@ -170,16 +154,20 @@ function fogNode() {
170
154
  layoutGen = gpu.generation;
171
155
  layout = gpu.layouts.bindGroupLayout({
172
156
  label: "fog",
173
- entries: [tex(0, "unfilterable-float"), tex(1, "depth"), uniform(2)]
157
+ entries: [tex(0, "unfilterable-float"), depthTex(gpu, 1), uniform(2)]
174
158
  });
175
159
  }
160
+ const alpha = v.cam.alphaOutput;
161
+ const out = ctx.texture("fog-out");
162
+ const rg11 = out.format === "rg11b10ufloat";
176
163
  const pipeline = cache.render(
177
164
  ctx,
178
- "post/fog",
165
+ FOG_KEYS[(alpha ? 1 : 0) + (state.interiorMode !== 0 ? 2 : 0) + (rg11 ? 4 : 0)],
179
166
  "shard::post::fog",
180
167
  "fs",
181
168
  [state.layouts.view, layout],
182
- HDR
169
+ targetsFor(out),
170
+ { TRANSPARENT: alpha, RG11_ROUND: roundsRg11(gpu, out) }
183
171
  );
184
172
  if (!pipeline) return;
185
173
  const f = v.cam.post.fog;
@@ -233,7 +221,7 @@ function taaNode() {
233
221
  entries: [
234
222
  uniform(0),
235
223
  tex(1, "unfilterable-float"),
236
- tex(2, "depth"),
224
+ depthTex(gpu, 2),
237
225
  tex(3, "unfilterable-float"),
238
226
  tex(4),
239
227
  sampler(5),
@@ -242,17 +230,18 @@ function taaNode() {
242
230
  });
243
231
  histories.clear();
244
232
  }
233
+ const alpha = cameraOf(ctx.view)?.alphaOutput === true;
245
234
  const pipeline = cache.render(
246
235
  ctx,
247
- "post/taa",
236
+ alpha ? "post/taa/alpha" : "post/taa",
248
237
  "shard::post::taa",
249
238
  "fs",
250
239
  [layout],
251
- [{ format: "rgba16float" }, { format: "rgba16float" }]
240
+ HDR,
241
+ { TRANSPARENT: alpha }
252
242
  );
253
243
  if (!pipeline) return;
254
244
  const input = ctx.texture("taa-in");
255
- const out = ctx.texture("taa-out");
256
245
  let h = histories.get(ctx.view.name);
257
246
  if (!h || h.generation !== gpu.generation || h.textures[0].width !== input.width || h.textures[0].height !== input.height) {
258
247
  for (const t of h?.textures ?? []) t.destroy();
@@ -260,7 +249,7 @@ function taaNode() {
260
249
  label: `${ctx.view.name}/taa-history-${i}`,
261
250
  size: [input.width, input.height],
262
251
  format: "rgba16float",
263
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
252
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
264
253
  });
265
254
  h = { textures: [make(0), make(1)], index: 0, valid: false, generation: gpu.generation };
266
255
  histories.set(ctx.view.name, h);
@@ -294,7 +283,6 @@ function taaNode() {
294
283
  const pass = ctx.encoder.beginRenderPass({
295
284
  label: `${ctx.view.name}/taa`,
296
285
  colorAttachments: [
297
- { view: out.createView(), loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 1] },
298
286
  { view: write.createView(), loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 1] }
299
287
  ],
300
288
  timestampWrites: ctx.timestamps("post/taa")
@@ -303,6 +291,7 @@ function taaNode() {
303
291
  pass.setBindGroup(0, group);
304
292
  pass.draw(3);
305
293
  pass.end();
294
+ ctx.provide("taa-out", write);
306
295
  h.index = 1 - h.index;
307
296
  h.valid = true;
308
297
  }
@@ -330,20 +319,22 @@ function motionBlurNode() {
330
319
  entries: [
331
320
  uniform(0),
332
321
  tex(1),
333
- tex(2, "depth"),
322
+ depthTex(gpu, 2),
334
323
  tex(3, "unfilterable-float"),
335
324
  uniform(4),
336
325
  sampler(5)
337
326
  ]
338
327
  });
339
328
  }
329
+ const out = ctx.texture("motion-blur-out");
340
330
  const pipeline = cache.render(
341
331
  ctx,
342
- "post/motion-blur",
332
+ out.format === "rg11b10ufloat" ? "post/motion-blur/rg11" : "post/motion-blur",
343
333
  "shard::post::motion_blur",
344
334
  "fs",
345
335
  [layout],
346
- HDR
336
+ targetsFor(out),
337
+ { RG11_ROUND: roundsRg11(gpu, out) }
347
338
  );
348
339
  if (!pipeline) return;
349
340
  const m = v.cam.post.motionBlur;
@@ -396,18 +387,28 @@ function dofNode() {
396
387
  layoutGen = gpu.generation;
397
388
  layout = gpu.layouts.bindGroupLayout({
398
389
  label: "dof",
399
- entries: [uniform(0), tex(1), tex(2, "depth"), uniform(3), tex(4), sampler(5)]
390
+ entries: [uniform(0), tex(1), depthTex(gpu, 2), uniform(3), tex(4), sampler(5)]
400
391
  });
401
392
  }
402
393
  const prepare = cache.render(ctx, "dof/prepare", "shard::post::dof", "prepare", [layout], HDR);
403
- const gather = cache.render(ctx, "dof/gather", "shard::post::dof", "gather", [layout], HDR);
394
+ const blur = ctx.texture("dof-blur");
395
+ const out = ctx.texture("dof-out");
396
+ const gather = cache.render(
397
+ ctx,
398
+ blur.format === "rg11b10ufloat" ? "dof/gather/rg11" : "dof/gather",
399
+ "shard::post::dof",
400
+ "gather",
401
+ [layout],
402
+ targetsFor(blur)
403
+ );
404
404
  const composite = cache.render(
405
405
  ctx,
406
- "dof/composite",
406
+ out.format === "rg11b10ufloat" ? "dof/composite/rg11" : "dof/composite",
407
407
  "shard::post::dof",
408
408
  "composite",
409
409
  [layout],
410
- HDR
410
+ targetsFor(out),
411
+ { RG11_ROUND: roundsRg11(gpu, out) }
411
412
  );
412
413
  if (!prepare || !gather || !composite) return;
413
414
  const params = cache.buffer(gpu, `${ctx.view.name}/dof`, 16);
@@ -415,8 +416,6 @@ function dofNode() {
415
416
  const input = ctx.texture("dof-in");
416
417
  const depth = ctx.texture("depth");
417
418
  const half = ctx.texture("dof-half");
418
- const blur = ctx.texture("dof-blur");
419
- const out = ctx.texture("dof-out");
420
419
  const group = (slot, secondary) => cache.group(
421
420
  gpu,
422
421
  `${ctx.view.name}/dof/${slot}`,
@@ -458,7 +457,8 @@ function bloomNode() {
458
457
  phase: RenderPhase.Post + 40,
459
458
  enabled: hasEffect(PostEffect.Bloom),
460
459
  reads: ["bloom-in"],
461
- writes: ["bloom-out", "bloom"],
460
+ // The tonemap composites level 0 over the image (and auto exposure meters the same mix).
461
+ writes: ["bloom"],
462
462
  run: (ctx) => {
463
463
  const v = forwardView(ctx);
464
464
  if (!v) return;
@@ -467,48 +467,40 @@ function bloomNode() {
467
467
  layoutGen = gpu.generation;
468
468
  layout = gpu.layouts.bindGroupLayout({
469
469
  label: "bloom",
470
- entries: [tex(0), sampler(1), uniform(2), tex(3, "unfilterable-float")]
470
+ entries: [tex(0), sampler(1), uniform(2)]
471
471
  });
472
472
  }
473
473
  const add = { srcFactor: "one", dstFactor: "one", operation: "add" };
474
+ const chain = ctx.texture("bloom");
475
+ const rg11 = chain.format === "rg11b10ufloat";
474
476
  const prefilter = cache.render(
475
477
  ctx,
476
- "bloom/prefilter",
478
+ rg11 ? "bloom/prefilter/rg11" : "bloom/prefilter",
477
479
  "shard::post::bloom",
478
480
  "prefilter",
479
481
  [layout],
480
- HDR
482
+ targetsFor(chain)
481
483
  );
482
484
  const down = cache.render(
483
485
  ctx,
484
- "bloom/downsample",
486
+ rg11 ? "bloom/downsample/rg11" : "bloom/downsample",
485
487
  "shard::post::bloom",
486
488
  "downsample",
487
489
  [layout],
488
- HDR
490
+ targetsFor(chain)
489
491
  );
490
492
  const up = cache.render(
491
493
  ctx,
492
- "bloom/upsample",
494
+ rg11 ? "bloom/upsample/rg11" : "bloom/upsample",
493
495
  "shard::post::bloom",
494
496
  "upsample",
495
497
  [layout],
496
- [{ format: "rgba16float", blend: { color: add, alpha: add } }]
497
- );
498
- const composite = cache.render(
499
- ctx,
500
- "bloom/composite",
501
- "shard::post::bloom",
502
- "composite",
503
- [layout],
504
- HDR
498
+ [{ format: chain.format, blend: { color: add, alpha: add } }]
505
499
  );
506
- if (!prefilter || !down || !up || !composite) return;
500
+ if (!prefilter || !down || !up) return;
507
501
  const { cam } = v;
508
502
  const b = cam.post.bloom;
509
503
  const input = ctx.texture("bloom-in");
510
- const chain = ctx.texture("bloom");
511
- const out = ctx.texture("bloom-out");
512
504
  const levels = Math.min(chain.mipLevelCount, bloomLevels(cam));
513
505
  const buffer = cache.buffer(gpu, `${ctx.view.name}/bloom`, 256 * 20);
514
506
  const slot = (i2, w, h) => {
@@ -527,7 +519,6 @@ function bloomNode() {
527
519
  slot(n++, input.width, input.height);
528
520
  for (let k = 1; k < levels; k++) slot(n++, ...mipSize(k - 1));
529
521
  for (let k = levels - 2; k >= 0; k--) slot(n++, ...mipSize(k + 1));
530
- slot(n++, chain.width, chain.height);
531
522
  buffer.write(params, 0, 0, n * 64);
532
523
  const view = (k) => chain.createView({ baseMipLevel: k, mipLevelCount: 1 });
533
524
  const group = (i2, source, mip) => cache.group(
@@ -538,8 +529,7 @@ function bloomNode() {
538
529
  () => [
539
530
  { binding: 0, resource: source === chain ? view(mip) : source.createView() },
540
531
  { binding: 1, resource: cache.sampler(gpu) },
541
- { binding: 2, resource: { buffer: buffer.buffer, offset: i2 * 256, size: 32 } },
542
- { binding: 3, resource: input.createView() }
532
+ { binding: 2, resource: { buffer: buffer.buffer, offset: i2 * 256, size: 32 } }
543
533
  ]
544
534
  );
545
535
  let i = 0;
@@ -553,10 +543,31 @@ function bloomNode() {
553
543
  draw("post/bloom", prefilter, view(0), input, 0);
554
544
  for (let k = 1; k < levels; k++) draw("post/bloom-down", down, view(k), chain, k - 1);
555
545
  for (let k = levels - 2; k >= 0; k--) draw("post/bloom-up", up, view(k), chain, k + 1, true);
556
- draw("post/bloom-composite", composite, out.createView(), chain, 0);
557
546
  }
558
547
  };
559
548
  }
549
+ function bloomGlow(cam, chain, out, at) {
550
+ const b = cam.post.bloom;
551
+ out[at] = chain ? b.intensity : 0;
552
+ out[at + 1] = chain ? chain.mipLevelCount : 1;
553
+ out[at + 2] = chain && b.threshold <= 0 ? 1 : 0;
554
+ out[at + 3] = 0;
555
+ }
556
+ const whites = /* @__PURE__ */ new WeakMap();
557
+ function whiteTexture(gpu) {
558
+ let t = whites.get(gpu.device);
559
+ if (!t) {
560
+ t = gpu.device.createTexture({
561
+ label: "post/white",
562
+ size: [1, 1],
563
+ format: "rgba8unorm",
564
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
565
+ });
566
+ gpu.device.queue.writeTexture({ texture: t }, new Uint8Array([255, 255, 255, 255]), {}, [1, 1]);
567
+ whites.set(gpu.device, t);
568
+ }
569
+ return t;
570
+ }
560
571
  const METER_READBACKS = 3;
561
572
  const ExposureMeters = defineResource("render/ExposureMeters", {
562
573
  description: "Auto exposure per camera: the last metered EV100 and the adapted one.",
@@ -586,22 +597,103 @@ function histogramEv(bins) {
586
597
  }
587
598
  return weight > 0 ? sum / weight : void 0;
588
599
  }
589
- function autoExposureNode() {
600
+ function landReading(meters, entity, index, ev) {
601
+ const m = meters.get(entity);
602
+ if (m && ev !== void 0 && index > m.reading) {
603
+ m.metered = ev;
604
+ m.reading = index;
605
+ m.readings++;
606
+ }
607
+ }
608
+ function autoExposureNode(baseline) {
590
609
  const cache = new PostCache();
591
610
  let layout;
592
611
  let layoutGen = -1;
593
612
  const perView = /* @__PURE__ */ new Map();
613
+ const meterViews = /* @__PURE__ */ new Map();
594
614
  const zero = new Uint32Array(256);
615
+ const bins = new Uint32Array(256);
595
616
  return {
596
617
  kind: "raw",
597
618
  phase: RenderPhase.Post + 50,
598
619
  enabled: hasEffect(PostEffect.AutoExposure),
599
- reads: ["post-hdr"],
620
+ reads: ["post-hdr", "bloom"],
600
621
  sideEffects: true,
601
622
  run: (ctx) => {
602
623
  const v = forwardView(ctx);
603
624
  if (!v) return;
604
625
  const gpu = ctx.gpu;
626
+ const bloom = (v.cam.post.effects & PostEffect.Bloom) !== 0;
627
+ const chain = bloom ? ctx.texture("bloom") : void 0;
628
+ if (baseline) {
629
+ const layout2 = baseline.meterLayout(gpu);
630
+ const pipeline2 = cache.render(
631
+ ctx,
632
+ bloom ? "post/exposure-meter/bloom" : "post/exposure-meter",
633
+ "shard::post::baseline::meter",
634
+ "main",
635
+ [layout2],
636
+ [{ format: "rg16float" }],
637
+ { BLOOM: bloom }
638
+ );
639
+ if (!pipeline2) return;
640
+ let m = meterViews.get(ctx.view.name);
641
+ if (!m || m.generation !== gpu.generation) {
642
+ m = baseline.meterView(gpu, ctx.view.name, METER_READBACKS);
643
+ meterViews.set(ctx.view.name, m);
644
+ }
645
+ const input2 = ctx.texture("post-hdr");
646
+ const level02 = chain ?? whiteTexture(gpu);
647
+ const key = `${idOf(input2)}/${v.pv.uniform.version}/${idOf(level02)}`;
648
+ if (!m.group || m.groupKey !== key) {
649
+ m.groupKey = key;
650
+ m.group = gpu.device.createBindGroup({
651
+ label: `${ctx.view.name}/exposure`,
652
+ layout: layout2,
653
+ entries: [
654
+ { binding: 0, resource: { buffer: v.pv.uniform.buffer } },
655
+ { binding: 1, resource: input2.createView() },
656
+ { binding: 2, resource: { buffer: m.params } },
657
+ { binding: 3, resource: level02.createView({ mipLevelCount: 1 }) },
658
+ { binding: 4, resource: cache.sampler(gpu) }
659
+ ]
660
+ });
661
+ }
662
+ scratch[0] = METER_MIN_EV;
663
+ scratch[1] = METER_BINS_PER_EV;
664
+ scratch[2] = v.cam.post.exposure.metering;
665
+ scratch[3] = 0;
666
+ bloomGlow(v.cam, chain, scratch, 4);
667
+ const k2 = baseline.renderMeter(
668
+ ctx,
669
+ m,
670
+ pipeline2,
671
+ m.group,
672
+ scratch.subarray(0, 4),
673
+ scratch.subarray(4, 8)
674
+ );
675
+ if (k2 === void 0) return;
676
+ const meters2 = ctx.world.resource(ExposureMeters);
677
+ const entity2 = v.cam.entity;
678
+ const meter2 = meters2.get(entity2);
679
+ const index2 = meter2 ? ++meter2.submitted : 0;
680
+ const readback2 = m.readbacks[k2];
681
+ const busy2 = m.busy;
682
+ ctx.afterSubmit(() => {
683
+ readback2.mapAsync(GPUMapMode.READ).then(
684
+ () => {
685
+ baseline.binMeter(readback2.getMappedRange(), METER_MIN_EV, METER_BINS_PER_EV, bins);
686
+ readback2.unmap();
687
+ busy2[k2] = false;
688
+ landReading(meters2, entity2, index2, histogramEv(bins));
689
+ },
690
+ () => {
691
+ busy2[k2] = false;
692
+ }
693
+ );
694
+ });
695
+ return;
696
+ }
605
697
  const C = GPUShaderStage.COMPUTE;
606
698
  if (!layout || layoutGen !== gpu.generation) {
607
699
  layoutGen = gpu.generation;
@@ -611,14 +703,21 @@ function autoExposureNode() {
611
703
  uniform(0, C),
612
704
  { binding: 1, visibility: C, texture: { sampleType: "unfilterable-float" } },
613
705
  { binding: 2, visibility: C, buffer: { type: "storage" } },
614
- uniform(3, C)
706
+ uniform(3, C),
707
+ { binding: 4, visibility: C, texture: { sampleType: "float" } },
708
+ { binding: 5, visibility: C, sampler: { type: "filtering" } }
615
709
  ]
616
710
  });
617
711
  perView.clear();
618
712
  }
619
- const pipeline = cache.compute(ctx, "post/exposure", "shard::post::exposure", "meter", [
620
- layout
621
- ]);
713
+ const pipeline = cache.compute(
714
+ ctx,
715
+ bloom ? "post/exposure/bloom" : "post/exposure",
716
+ "shard::post::exposure",
717
+ "meter",
718
+ [layout],
719
+ { BLOOM: bloom }
720
+ );
622
721
  if (!pipeline) return;
623
722
  let state = perView.get(ctx.view.name);
624
723
  if (!state || state.generation !== gpu.generation) {
@@ -646,20 +745,24 @@ function autoExposureNode() {
646
745
  scratch[1] = METER_BINS_PER_EV;
647
746
  scratch[2] = e.metering;
648
747
  scratch[3] = 0;
649
- const params = cache.buffer(gpu, `${ctx.view.name}/exposure`, 16);
650
- params.write(scratch, 0, 0, 4);
748
+ bloomGlow(v.cam, chain, scratch, 4);
749
+ const params = cache.buffer(gpu, `${ctx.view.name}/exposure`, 32);
750
+ params.write(scratch, 0, 0, 8);
651
751
  const input = ctx.texture("post-hdr");
652
752
  const histogram = state.histogram;
753
+ const level0 = chain ?? whiteTexture(gpu);
653
754
  const group = cache.group(
654
755
  gpu,
655
756
  `${ctx.view.name}/exposure`,
656
- `${idOf(input)}/${idOf(histogram)}/${v.pv.uniform.version}/${params.version}`,
757
+ `${idOf(input)}/${idOf(histogram)}/${v.pv.uniform.version}/${params.version}/${idOf(level0)}`,
657
758
  layout,
658
759
  () => [
659
760
  { binding: 0, resource: { buffer: v.pv.uniform.buffer } },
660
761
  { binding: 1, resource: input.createView() },
661
762
  { binding: 2, resource: { buffer: histogram } },
662
- { binding: 3, resource: { buffer: params.buffer } }
763
+ { binding: 3, resource: { buffer: params.buffer } },
764
+ { binding: 4, resource: level0.createView({ mipLevelCount: 1 }) },
765
+ { binding: 5, resource: cache.sampler(gpu) }
663
766
  ]
664
767
  );
665
768
  gpu.device.queue.writeBuffer(histogram, 0, zero);
@@ -687,12 +790,7 @@ function autoExposureNode() {
687
790
  const ev = histogramEv(new Uint32Array(readback.getMappedRange().slice(0)));
688
791
  readback.unmap();
689
792
  busy[k] = false;
690
- const m = meters.get(entity);
691
- if (m && ev !== void 0 && index > m.reading) {
692
- m.metered = ev;
693
- m.reading = index;
694
- m.readings++;
695
- }
793
+ landReading(meters, entity, index, ev);
696
794
  },
697
795
  () => {
698
796
  busy[k] = false;
@@ -753,16 +851,18 @@ const POST_NODES = [
753
851
  ["post/fxaa", PostEffect.Fxaa]
754
852
  ];
755
853
  const halfSize = { divide: 2 };
756
- function addPostNodes(world) {
854
+ function addPostNodes(world, baseline) {
757
855
  const graph = world.resource(Graph);
758
856
  world.initResource(ExposureMeters);
759
- graph.declare({ name: "prepass-normal", format: "rgba16float" });
857
+ graph.declare({ name: "prepass-normal", format: "rg16float" });
760
858
  graph.declare({ name: "velocity", format: "rg16float" });
761
859
  graph.declare({ name: "prepass-depth", format: "depth32float" });
762
- graph.declare({ name: "ssao", format: "r8unorm" });
763
- graph.declare({ name: "ssao-half", format: "rg16float", size: halfSize });
764
- graph.declare({ name: "post-a", format: "rgba16float" });
765
- graph.declare({ name: "post-b", format: "rgba16float" });
860
+ graph.declare({ name: "ssao", format: "rg16float", size: halfSize });
861
+ const gpu = world.resource(Gpu);
862
+ const chainFormat = (view) => postFormat(gpu, view);
863
+ graph.declare({ name: "post-a", format: chainFormat });
864
+ graph.declare({ name: "post-b", format: chainFormat });
865
+ graph.declare({ name: "taa-history", format: "rgba16float" });
766
866
  graph.declare({ name: "dof-half", format: "rgba16float", size: halfSize });
767
867
  graph.declare({ name: "dof-blur", format: "rgba16float", size: halfSize });
768
868
  graph.declare({
@@ -774,6 +874,29 @@ function addPostNodes(world) {
774
874
  return cam ? bloomLevels(cam) : 1;
775
875
  }
776
876
  });
877
+ addRenderFeatures(world, {
878
+ name: "render/post",
879
+ description: "The prepass, SSAO, fog, TAA, motion blur, depth of field and bloom.",
880
+ nodes: [
881
+ "prepass",
882
+ "ssao",
883
+ "ssao-deferred",
884
+ "post/fog",
885
+ "post/taa",
886
+ "post/motion-blur",
887
+ "post/dof",
888
+ "post/bloom"
889
+ ],
890
+ baseline: { strategy: "The same fragment passes" }
891
+ });
892
+ addRenderFeatures(world, {
893
+ name: "render/auto-exposure",
894
+ description: "Automatic exposure from a luminance histogram.",
895
+ nodes: ["post/exposure"],
896
+ baseline: {
897
+ strategy: "A 64\xD736 meter image read back and binned into the same histogram on the CPU"
898
+ }
899
+ });
777
900
  graph.addNode("prepass", prepassNode);
778
901
  graph.addNode("ssao", ssaoNode(RenderPhase.Prepass + 50, false));
779
902
  graph.addNode("ssao-deferred", ssaoNode(RenderPhase.Opaque + 20, true));
@@ -782,7 +905,7 @@ function addPostNodes(world) {
782
905
  graph.addNode("post/motion-blur", motionBlurNode());
783
906
  graph.addNode("post/dof", dofNode());
784
907
  graph.addNode("post/bloom", bloomNode());
785
- graph.addNode("post/exposure", autoExposureNode());
908
+ graph.addNode("post/exposure", autoExposureNode(baseline));
786
909
  }
787
910
  const DESCRIBED = [
788
911
  ["ssao", "ssao", PostEffect.Ssao],
@@ -843,6 +966,7 @@ export {
843
966
  POST_NODES,
844
967
  adaptExposure,
845
968
  addPostNodes,
969
+ bloomGlow,
846
970
  bloomLevels,
847
971
  describePost,
848
972
  histogramEv
@@ -1,7 +1,7 @@
1
1
  import { PostUpdate } from "@aethervtt/shard-core";
2
2
  import { definePlugin } from "@aethervtt/shard-runtime";
3
3
  import { applyPhysicalCameras } from "./camera.js";
4
- import { RenderDescribers, Shaders } from "./plugin.js";
4
+ import { Gpu, RenderDescribers, Shaders } from "./plugin.js";
5
5
  import { PostEffect, PostFeatures } from "./post.js";
6
6
  import { adaptExposure, addPostNodes, describePost, ExposureMeters } from "./post-nodes.js";
7
7
  import { POST_SHADERS } from "./post-shaders.js";
@@ -15,10 +15,12 @@ const postPlugin = definePlugin({
15
15
  app.world.initResource(PostFeatures).effects |= POST_EFFECTS;
16
16
  app.addSystems(PostUpdate, adaptExposure.after(applyPhysicalCameras));
17
17
  },
18
- ready(app) {
18
+ async ready(app) {
19
+ const baseline = app.world.resource(Gpu).tier === "baseline" ? await import("./baseline/exposure.js") : void 0;
19
20
  registerShaders(app.world.resource(Shaders), POST_SHADERS);
21
+ if (baseline) registerShaders(app.world.resource(Shaders), baseline.BASELINE_EXPOSURE_SHADERS);
20
22
  app.world.initResource(RenderDescribers).set("post", (world) => describePost(world));
21
- addPostNodes(app.world);
23
+ addPostNodes(app.world, baseline);
22
24
  }
23
25
  });
24
26
  export {