@aethervtt/shard-render 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/assets.d.ts +104 -0
  4. package/dist/assets.js +213 -0
  5. package/dist/atmosphere-model.d.ts +96 -0
  6. package/dist/atmosphere-model.js +370 -0
  7. package/dist/atmosphere-nodes.d.ts +55 -0
  8. package/dist/atmosphere-nodes.js +780 -0
  9. package/dist/atmosphere-plugin.d.ts +5 -0
  10. package/dist/atmosphere-plugin.js +42 -0
  11. package/dist/atmosphere-shaders.d.ts +7 -0
  12. package/dist/atmosphere-shaders.js +595 -0
  13. package/dist/atmosphere-state.d.ts +72 -0
  14. package/dist/atmosphere-state.js +18 -0
  15. package/dist/atmosphere.d.ts +191 -0
  16. package/dist/atmosphere.js +950 -0
  17. package/dist/camera.d.ts +83 -0
  18. package/dist/camera.js +163 -0
  19. package/dist/clusters.d.ts +68 -0
  20. package/dist/clusters.js +288 -0
  21. package/dist/culling-shaders.d.ts +2 -0
  22. package/dist/culling-shaders.js +203 -0
  23. package/dist/culling.d.ts +84 -0
  24. package/dist/culling.js +498 -0
  25. package/dist/debug-shaders.d.ts +3 -0
  26. package/dist/debug-shaders.js +207 -0
  27. package/dist/debug-views.d.ts +60 -0
  28. package/dist/debug-views.js +197 -0
  29. package/dist/deferred-plugin.d.ts +5 -0
  30. package/dist/deferred-plugin.js +20 -0
  31. package/dist/deferred-shaders.d.ts +2 -0
  32. package/dist/deferred-shaders.js +151 -0
  33. package/dist/deferred.d.ts +22 -0
  34. package/dist/deferred.js +296 -0
  35. package/dist/deform.d.ts +80 -0
  36. package/dist/deform.js +213 -0
  37. package/dist/describe-plugin.d.ts +5 -0
  38. package/dist/describe-plugin.js +19 -0
  39. package/dist/display-nodes.d.ts +5 -0
  40. package/dist/display-nodes.js +189 -0
  41. package/dist/dynamic-resolution.d.ts +47 -0
  42. package/dist/dynamic-resolution.js +191 -0
  43. package/dist/environment-plugin.d.ts +6 -0
  44. package/dist/environment-plugin.js +44 -0
  45. package/dist/environment-shaders.d.ts +2 -0
  46. package/dist/environment-shaders.js +260 -0
  47. package/dist/environment-state.d.ts +71 -0
  48. package/dist/environment-state.js +144 -0
  49. package/dist/environment.d.ts +70 -0
  50. package/dist/environment.js +529 -0
  51. package/dist/forward.d.ts +123 -0
  52. package/dist/forward.js +1216 -0
  53. package/dist/fxaa.d.ts +2 -0
  54. package/dist/fxaa.js +65 -0
  55. package/dist/gizmo-font.d.ts +21 -0
  56. package/dist/gizmo-font.js +132 -0
  57. package/dist/gizmos-plugin.d.ts +5 -0
  58. package/dist/gizmos-plugin.js +34 -0
  59. package/dist/gizmos.d.ts +156 -0
  60. package/dist/gizmos.js +772 -0
  61. package/dist/gpu-assets.d.ts +114 -0
  62. package/dist/gpu-assets.js +494 -0
  63. package/dist/graph.d.ts +204 -0
  64. package/dist/graph.js +417 -0
  65. package/dist/index.d.ts +53 -0
  66. package/dist/index.js +540 -0
  67. package/dist/instances.d.ts +349 -0
  68. package/dist/instances.js +1335 -0
  69. package/dist/large-world.d.ts +13 -0
  70. package/dist/large-world.js +20 -0
  71. package/dist/lights.d.ts +160 -0
  72. package/dist/lights.js +468 -0
  73. package/dist/material-noise.d.ts +19 -0
  74. package/dist/material-noise.js +7 -0
  75. package/dist/material-pipelines.d.ts +46 -0
  76. package/dist/material-pipelines.js +166 -0
  77. package/dist/materials.d.ts +108 -0
  78. package/dist/materials.js +219 -0
  79. package/dist/noise.d.ts +76 -0
  80. package/dist/noise.js +286 -0
  81. package/dist/overlay-registry.d.ts +18 -0
  82. package/dist/overlay-registry.js +30 -0
  83. package/dist/overlays.d.ts +35 -0
  84. package/dist/overlays.js +344 -0
  85. package/dist/paths.d.ts +20 -0
  86. package/dist/paths.js +46 -0
  87. package/dist/picking-plugin.d.ts +2 -0
  88. package/dist/picking-plugin.js +20 -0
  89. package/dist/picking.d.ts +94 -0
  90. package/dist/picking.js +456 -0
  91. package/dist/pixel-perfect-plugin.d.ts +9 -0
  92. package/dist/pixel-perfect-plugin.js +135 -0
  93. package/dist/pixel-perfect.d.ts +27 -0
  94. package/dist/pixel-perfect.js +70 -0
  95. package/dist/plugin.d.ts +111 -0
  96. package/dist/plugin.js +182 -0
  97. package/dist/pool.d.ts +19 -0
  98. package/dist/pool.js +43 -0
  99. package/dist/post-common.d.ts +51 -0
  100. package/dist/post-common.js +148 -0
  101. package/dist/post-nodes.d.ts +39 -0
  102. package/dist/post-nodes.js +849 -0
  103. package/dist/post-plugin.d.ts +5 -0
  104. package/dist/post-plugin.js +26 -0
  105. package/dist/post-shaders.d.ts +8 -0
  106. package/dist/post-shaders.js +698 -0
  107. package/dist/post.d.ts +175 -0
  108. package/dist/post.js +453 -0
  109. package/dist/readback.d.ts +4 -0
  110. package/dist/readback.js +95 -0
  111. package/dist/render-scale.d.ts +53 -0
  112. package/dist/render-scale.js +63 -0
  113. package/dist/shaders.d.ts +122 -0
  114. package/dist/shaders.js +1168 -0
  115. package/dist/shadows.d.ts +119 -0
  116. package/dist/shadows.js +562 -0
  117. package/dist/skin-asset.d.ts +26 -0
  118. package/dist/skin-asset.js +25 -0
  119. package/dist/skinning-plugin.d.ts +5 -0
  120. package/dist/skinning-plugin.js +16 -0
  121. package/dist/skinning.d.ts +19 -0
  122. package/dist/skinning.js +343 -0
  123. package/dist/standard-fields.d.ts +67 -0
  124. package/dist/standard-fields.js +73 -0
  125. package/dist/standard.d.ts +7 -0
  126. package/dist/standard.js +37 -0
  127. package/dist/stats.d.ts +25 -0
  128. package/dist/stats.js +18 -0
  129. package/dist/target.d.ts +55 -0
  130. package/dist/target.js +70 -0
  131. package/dist/testing.d.ts +34 -0
  132. package/dist/testing.js +126 -0
  133. package/dist/timer.d.ts +28 -0
  134. package/dist/timer.js +136 -0
  135. package/dist/view.d.ts +117 -0
  136. package/dist/view.js +389 -0
  137. package/dist/visibility.d.ts +10 -0
  138. package/dist/visibility.js +84 -0
  139. package/package.json +49 -0
@@ -0,0 +1,849 @@
1
+ import {
2
+ defineResource,
3
+ defineSystem,
4
+ mat4,
5
+ ProfilerResource
6
+ } from "@aethervtt/shard-core";
7
+ import { Time } from "@aethervtt/shard-runtime";
8
+ import { Exposure } from "./camera.js";
9
+ import { drawMaterials, ForwardStateResource, PASS_PREPASS, viewBindGroup } from "./forward.js";
10
+ import { RenderPhase } from "./graph.js";
11
+ import { Graph, Views } from "./plugin.js";
12
+ import { AutoExposure, cocParams, hasEffect, needsPrepass, PostEffect } from "./post.js";
13
+ import {
14
+ beginPass,
15
+ forwardView,
16
+ HDR,
17
+ idOf,
18
+ PostCache,
19
+ sampler,
20
+ scratch,
21
+ tex,
22
+ uniform
23
+ } from "./post-common.js";
24
+ import { RenderCounters } from "./stats.js";
25
+ import { cameraOf } from "./view.js";
26
+ const prepassNode = {
27
+ kind: "render",
28
+ phase: RenderPhase.Prepass,
29
+ enabled: needsPrepass,
30
+ reads: ["culled"],
31
+ writes: ["prepass-normal", "velocity", "prepass-depth"],
32
+ color: [
33
+ { resource: "prepass-normal", clear: { r: 0, g: 0, b: 0, a: 0 } },
34
+ { resource: "velocity", clear: { r: 0, g: 0, b: 0, a: 0 } }
35
+ ],
36
+ depth: { resource: "prepass-depth", clear: 0 },
37
+ run: (ctx) => {
38
+ const v = forwardView(ctx);
39
+ if (!v) return;
40
+ const state = ctx.world.resource(ForwardStateResource);
41
+ drawMaterials(ctx, state, v.pv, v.cam, v.cam.draws, PASS_PREPASS);
42
+ if (v.cam.deferred) drawMaterials(ctx, state, v.pv, v.cam, v.cam.forwardOnly, PASS_PREPASS);
43
+ }
44
+ };
45
+ const SSAO_STEPS = [
46
+ [1, 4],
47
+ [2, 4],
48
+ [3, 6]
49
+ ];
50
+ const invProj = mat4.create();
51
+ function ssaoNode(phase, deferred) {
52
+ const cache = new PostCache();
53
+ let layouts;
54
+ let layoutGen = -1;
55
+ return {
56
+ kind: "raw",
57
+ phase,
58
+ enabled: (view) => {
59
+ const cam = cameraOf(view);
60
+ return cam !== void 0 && cam.deferred === deferred && (cam.post.effects & PostEffect.Ssao) !== 0;
61
+ },
62
+ reads: ["ssao-normal", "ssao-depth"],
63
+ writes: ["ssao", "ssao-half"],
64
+ run: (ctx) => {
65
+ const v = forwardView(ctx);
66
+ if (!v) return;
67
+ const gpu = ctx.gpu;
68
+ if (!layouts || layoutGen !== gpu.generation) {
69
+ 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
+ };
80
+ }
81
+ const gtao = cache.render(
82
+ ctx,
83
+ "ssao/gtao",
84
+ "shard::post::ssao",
85
+ "gtao",
86
+ [layouts.gtao],
87
+ [{ format: "rg16float" }]
88
+ );
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
+ const { cam, pv } = v;
99
+ const s = cam.post.ssao;
100
+ const steps = SSAO_STEPS[s.quality] ?? SSAO_STEPS[1];
101
+ scratch[0] = s.radius;
102
+ scratch[1] = s.intensity;
103
+ scratch[2] = steps[0];
104
+ scratch[3] = steps[1];
105
+ scratch[4] = cam.proj[5] * cam.height * 0.5;
106
+ scratch[5] = scratch[6] = scratch[7] = 0;
107
+ mat4.invert(invProj, cam.proj);
108
+ scratch.set(invProj, 8);
109
+ const params = cache.buffer(gpu, `${ctx.view.name}/ssao`, 96);
110
+ params.write(scratch, 0, 0, 24);
111
+ const depth = ctx.texture("ssao-depth");
112
+ const normal = ctx.texture("ssao-normal");
113
+ const half = ctx.texture("ssao-half");
114
+ const out = ctx.texture("ssao");
115
+ const g1 = cache.group(
116
+ gpu,
117
+ `${ctx.view.name}/gtao`,
118
+ `${idOf(depth)}/${idOf(normal)}/${pv.uniform.version}/${params.version}`,
119
+ layouts.gtao,
120
+ () => [
121
+ { binding: 0, resource: { buffer: pv.uniform.buffer } },
122
+ { binding: 1, resource: depth.createView() },
123
+ { binding: 2, resource: normal.createView() },
124
+ { binding: 3, resource: { buffer: params.buffer } }
125
+ ]
126
+ );
127
+ let pass = beginPass(ctx, "ssao", half.createView());
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);
147
+ pass.draw(3);
148
+ pass.end();
149
+ pv.ao = out;
150
+ }
151
+ };
152
+ }
153
+ function fogNode() {
154
+ const cache = new PostCache();
155
+ let layout;
156
+ let layoutGen = -1;
157
+ return {
158
+ kind: "render",
159
+ phase: RenderPhase.Post,
160
+ enabled: hasEffect(PostEffect.Fog),
161
+ reads: ["fog-in", "depth", "environment", "clusters"],
162
+ writes: ["fog-out"],
163
+ color: [{ resource: "fog-out", clear: { r: 0, g: 0, b: 0, a: 1 } }],
164
+ run: (ctx) => {
165
+ const v = forwardView(ctx);
166
+ if (!v) return;
167
+ const gpu = ctx.gpu;
168
+ const state = ctx.world.resource(ForwardStateResource);
169
+ if (!layout || layoutGen !== gpu.generation) {
170
+ layoutGen = gpu.generation;
171
+ layout = gpu.layouts.bindGroupLayout({
172
+ label: "fog",
173
+ entries: [tex(0, "unfilterable-float"), tex(1, "depth"), uniform(2)]
174
+ });
175
+ }
176
+ const pipeline = cache.render(
177
+ ctx,
178
+ "post/fog",
179
+ "shard::post::fog",
180
+ "fs",
181
+ [state.layouts.view, layout],
182
+ HDR
183
+ );
184
+ if (!pipeline) return;
185
+ const f = v.cam.post.fog;
186
+ scratch.set(f.color, 0);
187
+ scratch[4] = f.density;
188
+ scratch[5] = f.heightFalloff;
189
+ scratch[6] = f.start;
190
+ scratch[7] = f.sunScattering;
191
+ const params = cache.buffer(gpu, `${ctx.view.name}/fog`, 32);
192
+ params.write(scratch, 0, 0, 8);
193
+ const input = ctx.texture("fog-in");
194
+ const depth = ctx.texture("depth");
195
+ const group = cache.group(
196
+ gpu,
197
+ `${ctx.view.name}/fog`,
198
+ `${idOf(input)}/${idOf(depth)}/${params.version}`,
199
+ layout,
200
+ () => [
201
+ { binding: 0, resource: input.createView() },
202
+ { binding: 1, resource: depth.createView() },
203
+ { binding: 2, resource: { buffer: params.buffer } }
204
+ ]
205
+ );
206
+ const pass = ctx.renderPass;
207
+ pass.setPipeline(pipeline);
208
+ pass.setBindGroup(0, viewBindGroup(gpu, ctx.world, v.pv, v.cam));
209
+ pass.setBindGroup(1, group);
210
+ pass.draw(3);
211
+ }
212
+ };
213
+ }
214
+ function taaNode() {
215
+ const cache = new PostCache();
216
+ const histories = /* @__PURE__ */ new Map();
217
+ let layout;
218
+ let layoutGen = -1;
219
+ return {
220
+ kind: "raw",
221
+ phase: RenderPhase.Post + 10,
222
+ enabled: hasEffect(PostEffect.Taa),
223
+ reads: ["taa-in", "depth", "velocity"],
224
+ writes: ["taa-out"],
225
+ run: (ctx) => {
226
+ const v = forwardView(ctx);
227
+ if (!v) return;
228
+ const gpu = ctx.gpu;
229
+ if (!layout || layoutGen !== gpu.generation) {
230
+ layoutGen = gpu.generation;
231
+ layout = gpu.layouts.bindGroupLayout({
232
+ label: "taa",
233
+ entries: [
234
+ uniform(0),
235
+ tex(1, "unfilterable-float"),
236
+ tex(2, "depth"),
237
+ tex(3, "unfilterable-float"),
238
+ tex(4),
239
+ sampler(5),
240
+ uniform(6)
241
+ ]
242
+ });
243
+ histories.clear();
244
+ }
245
+ const pipeline = cache.render(
246
+ ctx,
247
+ "post/taa",
248
+ "shard::post::taa",
249
+ "fs",
250
+ [layout],
251
+ [{ format: "rgba16float" }, { format: "rgba16float" }]
252
+ );
253
+ if (!pipeline) return;
254
+ const input = ctx.texture("taa-in");
255
+ const out = ctx.texture("taa-out");
256
+ let h = histories.get(ctx.view.name);
257
+ if (!h || h.generation !== gpu.generation || h.textures[0].width !== input.width || h.textures[0].height !== input.height) {
258
+ for (const t of h?.textures ?? []) t.destroy();
259
+ const make = (i) => gpu.device.createTexture({
260
+ label: `${ctx.view.name}/taa-history-${i}`,
261
+ size: [input.width, input.height],
262
+ format: "rgba16float",
263
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
264
+ });
265
+ h = { textures: [make(0), make(1)], index: 0, valid: false, generation: gpu.generation };
266
+ histories.set(ctx.view.name, h);
267
+ }
268
+ if (v.cam.frames === 1) h.valid = false;
269
+ const read = h.textures[h.index];
270
+ const write = h.textures[1 - h.index];
271
+ scratch[0] = 0.1;
272
+ scratch[1] = h.valid ? 0 : 1;
273
+ if (!h.valid) ctx.world.initResource(RenderCounters).taaResets++;
274
+ scratch[2] = scratch[3] = 0;
275
+ const params = cache.buffer(gpu, `${ctx.view.name}/taa`, 16);
276
+ params.write(scratch, 0, 0, 4);
277
+ const depth = ctx.texture("depth");
278
+ const velocity = ctx.texture("velocity");
279
+ const group = cache.group(
280
+ gpu,
281
+ `${ctx.view.name}/taa/${h.index}`,
282
+ `${idOf(input)}/${idOf(depth)}/${idOf(velocity)}/${idOf(read)}/${v.pv.uniform.version}/${params.version}`,
283
+ layout,
284
+ () => [
285
+ { binding: 0, resource: { buffer: v.pv.uniform.buffer } },
286
+ { binding: 1, resource: input.createView() },
287
+ { binding: 2, resource: depth.createView() },
288
+ { binding: 3, resource: velocity.createView() },
289
+ { binding: 4, resource: read.createView() },
290
+ { binding: 5, resource: cache.sampler(gpu) },
291
+ { binding: 6, resource: { buffer: params.buffer } }
292
+ ]
293
+ );
294
+ const pass = ctx.encoder.beginRenderPass({
295
+ label: `${ctx.view.name}/taa`,
296
+ colorAttachments: [
297
+ { view: out.createView(), loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 1] },
298
+ { view: write.createView(), loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 1] }
299
+ ],
300
+ timestampWrites: ctx.timestamps("post/taa")
301
+ });
302
+ pass.setPipeline(pipeline);
303
+ pass.setBindGroup(0, group);
304
+ pass.draw(3);
305
+ pass.end();
306
+ h.index = 1 - h.index;
307
+ h.valid = true;
308
+ }
309
+ };
310
+ }
311
+ function motionBlurNode() {
312
+ const cache = new PostCache();
313
+ let layout;
314
+ let layoutGen = -1;
315
+ return {
316
+ kind: "render",
317
+ phase: RenderPhase.Post + 20,
318
+ enabled: hasEffect(PostEffect.MotionBlur),
319
+ reads: ["motion-blur-in", "depth", "velocity"],
320
+ writes: ["motion-blur-out"],
321
+ color: [{ resource: "motion-blur-out", clear: { r: 0, g: 0, b: 0, a: 1 } }],
322
+ run: (ctx) => {
323
+ const v = forwardView(ctx);
324
+ if (!v) return;
325
+ const gpu = ctx.gpu;
326
+ if (!layout || layoutGen !== gpu.generation) {
327
+ layoutGen = gpu.generation;
328
+ layout = gpu.layouts.bindGroupLayout({
329
+ label: "motion-blur",
330
+ entries: [
331
+ uniform(0),
332
+ tex(1),
333
+ tex(2, "depth"),
334
+ tex(3, "unfilterable-float"),
335
+ uniform(4),
336
+ sampler(5)
337
+ ]
338
+ });
339
+ }
340
+ const pipeline = cache.render(
341
+ ctx,
342
+ "post/motion-blur",
343
+ "shard::post::motion_blur",
344
+ "fs",
345
+ [layout],
346
+ HDR
347
+ );
348
+ if (!pipeline) return;
349
+ const m = v.cam.post.motionBlur;
350
+ scratch[0] = m.shutter;
351
+ scratch[1] = m.maxBlur * v.cam.height;
352
+ scratch[2] = m.samples;
353
+ scratch[3] = 0;
354
+ const params = cache.buffer(gpu, `${ctx.view.name}/motion-blur`, 16);
355
+ params.write(scratch, 0, 0, 4);
356
+ const input = ctx.texture("motion-blur-in");
357
+ const depth = ctx.texture("depth");
358
+ const velocity = ctx.texture("velocity");
359
+ const group = cache.group(
360
+ gpu,
361
+ `${ctx.view.name}/motion-blur`,
362
+ `${idOf(input)}/${idOf(depth)}/${idOf(velocity)}/${v.pv.uniform.version}/${params.version}`,
363
+ layout,
364
+ () => [
365
+ { binding: 0, resource: { buffer: v.pv.uniform.buffer } },
366
+ { binding: 1, resource: input.createView() },
367
+ { binding: 2, resource: depth.createView() },
368
+ { binding: 3, resource: velocity.createView() },
369
+ { binding: 4, resource: { buffer: params.buffer } },
370
+ { binding: 5, resource: cache.sampler(gpu) }
371
+ ]
372
+ );
373
+ const pass = ctx.renderPass;
374
+ pass.setPipeline(pipeline);
375
+ pass.setBindGroup(0, group);
376
+ pass.draw(3);
377
+ }
378
+ };
379
+ }
380
+ function dofNode() {
381
+ const cache = new PostCache();
382
+ let layout;
383
+ let layoutGen = -1;
384
+ const cocScratch = new Float32Array(4);
385
+ return {
386
+ kind: "raw",
387
+ phase: RenderPhase.Post + 30,
388
+ enabled: hasEffect(PostEffect.DepthOfField),
389
+ reads: ["dof-in", "depth"],
390
+ writes: ["dof-out", "dof-half", "dof-blur"],
391
+ run: (ctx) => {
392
+ const v = forwardView(ctx);
393
+ if (!v) return;
394
+ const gpu = ctx.gpu;
395
+ if (!layout || layoutGen !== gpu.generation) {
396
+ layoutGen = gpu.generation;
397
+ layout = gpu.layouts.bindGroupLayout({
398
+ label: "dof",
399
+ entries: [uniform(0), tex(1), tex(2, "depth"), uniform(3), tex(4), sampler(5)]
400
+ });
401
+ }
402
+ 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);
404
+ const composite = cache.render(
405
+ ctx,
406
+ "dof/composite",
407
+ "shard::post::dof",
408
+ "composite",
409
+ [layout],
410
+ HDR
411
+ );
412
+ if (!prepare || !gather || !composite) return;
413
+ const params = cache.buffer(gpu, `${ctx.view.name}/dof`, 16);
414
+ params.write(cocParams(v.cam, cocScratch));
415
+ const input = ctx.texture("dof-in");
416
+ const depth = ctx.texture("depth");
417
+ const half = ctx.texture("dof-half");
418
+ const blur = ctx.texture("dof-blur");
419
+ const out = ctx.texture("dof-out");
420
+ const group = (slot, secondary) => cache.group(
421
+ gpu,
422
+ `${ctx.view.name}/dof/${slot}`,
423
+ `${idOf(input)}/${idOf(depth)}/${idOf(secondary)}/${v.pv.uniform.version}/${params.version}`,
424
+ layout,
425
+ () => [
426
+ { binding: 0, resource: { buffer: v.pv.uniform.buffer } },
427
+ { binding: 1, resource: input.createView() },
428
+ { binding: 2, resource: depth.createView() },
429
+ { binding: 3, resource: { buffer: params.buffer } },
430
+ { binding: 4, resource: secondary.createView() },
431
+ { binding: 5, resource: cache.sampler(gpu) }
432
+ ]
433
+ );
434
+ const run = (name, pipeline, target, bound) => {
435
+ const pass = beginPass(ctx, name, target.createView());
436
+ pass.setPipeline(pipeline);
437
+ pass.setBindGroup(0, group(name, bound));
438
+ pass.draw(3);
439
+ pass.end();
440
+ };
441
+ run("post/dof", prepare, half, blur);
442
+ run("post/dof-gather", gather, blur, half);
443
+ run("post/dof-composite", composite, out, blur);
444
+ }
445
+ };
446
+ }
447
+ function bloomLevels(cam) {
448
+ const max = Math.max(1, Math.floor(Math.log2(Math.min(cam.width, cam.height) / 2)) - 1);
449
+ return Math.max(1, Math.min(max, Math.round(3 + cam.post.bloom.radius * (max - 3))));
450
+ }
451
+ function bloomNode() {
452
+ const cache = new PostCache();
453
+ let layout;
454
+ let layoutGen = -1;
455
+ const params = new Float32Array(64 * 20);
456
+ return {
457
+ kind: "raw",
458
+ phase: RenderPhase.Post + 40,
459
+ enabled: hasEffect(PostEffect.Bloom),
460
+ reads: ["bloom-in"],
461
+ writes: ["bloom-out", "bloom"],
462
+ run: (ctx) => {
463
+ const v = forwardView(ctx);
464
+ if (!v) return;
465
+ const gpu = ctx.gpu;
466
+ if (!layout || layoutGen !== gpu.generation) {
467
+ layoutGen = gpu.generation;
468
+ layout = gpu.layouts.bindGroupLayout({
469
+ label: "bloom",
470
+ entries: [tex(0), sampler(1), uniform(2), tex(3, "unfilterable-float")]
471
+ });
472
+ }
473
+ const add = { srcFactor: "one", dstFactor: "one", operation: "add" };
474
+ const prefilter = cache.render(
475
+ ctx,
476
+ "bloom/prefilter",
477
+ "shard::post::bloom",
478
+ "prefilter",
479
+ [layout],
480
+ HDR
481
+ );
482
+ const down = cache.render(
483
+ ctx,
484
+ "bloom/downsample",
485
+ "shard::post::bloom",
486
+ "downsample",
487
+ [layout],
488
+ HDR
489
+ );
490
+ const up = cache.render(
491
+ ctx,
492
+ "bloom/upsample",
493
+ "shard::post::bloom",
494
+ "upsample",
495
+ [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
505
+ );
506
+ if (!prefilter || !down || !up || !composite) return;
507
+ const { cam } = v;
508
+ const b = cam.post.bloom;
509
+ const input = ctx.texture("bloom-in");
510
+ const chain = ctx.texture("bloom");
511
+ const out = ctx.texture("bloom-out");
512
+ const levels = Math.min(chain.mipLevelCount, bloomLevels(cam));
513
+ const buffer = cache.buffer(gpu, `${ctx.view.name}/bloom`, 256 * 20);
514
+ const slot = (i2, w, h) => {
515
+ const o = i2 * 64;
516
+ params[o] = b.threshold * cam.exposure;
517
+ params[o + 1] = b.knee * b.threshold * cam.exposure;
518
+ params[o + 2] = 1 / w;
519
+ params[o + 3] = 1 / h;
520
+ params[o + 4] = b.intensity;
521
+ params[o + 5] = levels;
522
+ params[o + 6] = b.threshold > 0 ? 0 : 1;
523
+ params[o + 7] = 0;
524
+ };
525
+ const mipSize = (k) => [Math.max(1, chain.width >> k), Math.max(1, chain.height >> k)];
526
+ let n = 0;
527
+ slot(n++, input.width, input.height);
528
+ for (let k = 1; k < levels; k++) slot(n++, ...mipSize(k - 1));
529
+ for (let k = levels - 2; k >= 0; k--) slot(n++, ...mipSize(k + 1));
530
+ slot(n++, chain.width, chain.height);
531
+ buffer.write(params, 0, 0, n * 64);
532
+ const view = (k) => chain.createView({ baseMipLevel: k, mipLevelCount: 1 });
533
+ const group = (i2, source, mip) => cache.group(
534
+ gpu,
535
+ `${ctx.view.name}/bloom/${i2}`,
536
+ `${idOf(source)}/${mip}/${idOf(input)}/${buffer.version}`,
537
+ layout,
538
+ () => [
539
+ { binding: 0, resource: source === chain ? view(mip) : source.createView() },
540
+ { binding: 1, resource: cache.sampler(gpu) },
541
+ { binding: 2, resource: { buffer: buffer.buffer, offset: i2 * 256, size: 32 } },
542
+ { binding: 3, resource: input.createView() }
543
+ ]
544
+ );
545
+ let i = 0;
546
+ const draw = (name, pipeline, target, source, mip, load = false) => {
547
+ const pass = beginPass(ctx, name, target, load);
548
+ pass.setPipeline(pipeline);
549
+ pass.setBindGroup(0, group(i++, source, mip));
550
+ pass.draw(3);
551
+ pass.end();
552
+ };
553
+ draw("post/bloom", prefilter, view(0), input, 0);
554
+ for (let k = 1; k < levels; k++) draw("post/bloom-down", down, view(k), chain, k - 1);
555
+ 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
+ }
558
+ };
559
+ }
560
+ const METER_READBACKS = 3;
561
+ const ExposureMeters = defineResource("render/ExposureMeters", {
562
+ description: "Auto exposure per camera: the last metered EV100 and the adapted one.",
563
+ init: () => /* @__PURE__ */ new Map()
564
+ });
565
+ const METER_MIN_EV = -8;
566
+ const METER_BINS_PER_EV = 8;
567
+ const METER_BYTES = 256 * 4;
568
+ function histogramEv(bins) {
569
+ let total = 0;
570
+ for (let i = 0; i < bins.length; i++) total += bins[i];
571
+ if (total === 0) return void 0;
572
+ const lo = total * 0.1;
573
+ const hi = total * 0.98;
574
+ let seen = 0;
575
+ let sum = 0;
576
+ let weight = 0;
577
+ for (let i = 0; i < bins.length; i++) {
578
+ const n = bins[i];
579
+ const a = Math.max(seen, lo);
580
+ const b = Math.min(seen + n, hi);
581
+ if (b > a) {
582
+ sum += (b - a) * (METER_MIN_EV + (i + 0.5) / METER_BINS_PER_EV);
583
+ weight += b - a;
584
+ }
585
+ seen += n;
586
+ }
587
+ return weight > 0 ? sum / weight : void 0;
588
+ }
589
+ function autoExposureNode() {
590
+ const cache = new PostCache();
591
+ let layout;
592
+ let layoutGen = -1;
593
+ const perView = /* @__PURE__ */ new Map();
594
+ const zero = new Uint32Array(256);
595
+ return {
596
+ kind: "raw",
597
+ phase: RenderPhase.Post + 50,
598
+ enabled: hasEffect(PostEffect.AutoExposure),
599
+ reads: ["post-hdr"],
600
+ sideEffects: true,
601
+ run: (ctx) => {
602
+ const v = forwardView(ctx);
603
+ if (!v) return;
604
+ const gpu = ctx.gpu;
605
+ const C = GPUShaderStage.COMPUTE;
606
+ if (!layout || layoutGen !== gpu.generation) {
607
+ layoutGen = gpu.generation;
608
+ layout = gpu.layouts.bindGroupLayout({
609
+ label: "exposure",
610
+ entries: [
611
+ uniform(0, C),
612
+ { binding: 1, visibility: C, texture: { sampleType: "unfilterable-float" } },
613
+ { binding: 2, visibility: C, buffer: { type: "storage" } },
614
+ uniform(3, C)
615
+ ]
616
+ });
617
+ perView.clear();
618
+ }
619
+ const pipeline = cache.compute(ctx, "post/exposure", "shard::post::exposure", "meter", [
620
+ layout
621
+ ]);
622
+ if (!pipeline) return;
623
+ let state = perView.get(ctx.view.name);
624
+ if (!state || state.generation !== gpu.generation) {
625
+ state = {
626
+ histogram: gpu.device.createBuffer({
627
+ label: `${ctx.view.name}/exposure-histogram`,
628
+ size: METER_BYTES,
629
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
630
+ }),
631
+ readbacks: Array.from(
632
+ { length: METER_READBACKS },
633
+ (_, i) => gpu.device.createBuffer({
634
+ label: `${ctx.view.name}/exposure-readback-${i}`,
635
+ size: METER_BYTES,
636
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
637
+ })
638
+ ),
639
+ busy: Array.from({ length: METER_READBACKS }, () => false),
640
+ generation: gpu.generation
641
+ };
642
+ perView.set(ctx.view.name, state);
643
+ }
644
+ const e = v.cam.post.exposure;
645
+ scratch[0] = METER_MIN_EV;
646
+ scratch[1] = METER_BINS_PER_EV;
647
+ scratch[2] = e.metering;
648
+ scratch[3] = 0;
649
+ const params = cache.buffer(gpu, `${ctx.view.name}/exposure`, 16);
650
+ params.write(scratch, 0, 0, 4);
651
+ const input = ctx.texture("post-hdr");
652
+ const histogram = state.histogram;
653
+ const group = cache.group(
654
+ gpu,
655
+ `${ctx.view.name}/exposure`,
656
+ `${idOf(input)}/${idOf(histogram)}/${v.pv.uniform.version}/${params.version}`,
657
+ layout,
658
+ () => [
659
+ { binding: 0, resource: { buffer: v.pv.uniform.buffer } },
660
+ { binding: 1, resource: input.createView() },
661
+ { binding: 2, resource: { buffer: histogram } },
662
+ { binding: 3, resource: { buffer: params.buffer } }
663
+ ]
664
+ );
665
+ gpu.device.queue.writeBuffer(histogram, 0, zero);
666
+ const pass = ctx.encoder.beginComputePass({
667
+ label: `${ctx.view.name}/exposure`,
668
+ timestampWrites: ctx.timestamps("post/exposure")
669
+ });
670
+ pass.setPipeline(pipeline);
671
+ pass.setBindGroup(0, group);
672
+ pass.dispatchWorkgroups(Math.ceil(input.width / 64), Math.ceil(input.height / 64));
673
+ pass.end();
674
+ const k = state.busy.indexOf(false);
675
+ if (k < 0) return;
676
+ const readback = state.readbacks[k];
677
+ ctx.encoder.copyBufferToBuffer(histogram, 0, readback, 0, METER_BYTES);
678
+ state.busy[k] = true;
679
+ const busy = state.busy;
680
+ const meters = ctx.world.resource(ExposureMeters);
681
+ const entity = v.cam.entity;
682
+ const meter = meters.get(entity);
683
+ const index = meter ? ++meter.submitted : 0;
684
+ ctx.afterSubmit(() => {
685
+ readback.mapAsync(GPUMapMode.READ).then(
686
+ () => {
687
+ const ev = histogramEv(new Uint32Array(readback.getMappedRange().slice(0)));
688
+ readback.unmap();
689
+ 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
+ }
696
+ },
697
+ () => {
698
+ busy[k] = false;
699
+ }
700
+ );
701
+ });
702
+ }
703
+ };
704
+ }
705
+ const adaptExposure = defineSystem({
706
+ name: "render/auto-exposure",
707
+ description: "Adapts Exposure.ev100 of AutoExposure cameras toward their metered brightness.",
708
+ setup: (world) => ({ q: world.query({ with: [AutoExposure, Exposure] }) }),
709
+ run: ({ q }, world) => {
710
+ const meters = world.resource(ExposureMeters);
711
+ const dt = world.resource(Time).delta;
712
+ for (const table of q.tables) {
713
+ const ev = table.column(Exposure, "ev100");
714
+ const minEv = table.column(AutoExposure, "minEv");
715
+ const maxEv = table.column(AutoExposure, "maxEv");
716
+ const comp = table.column(AutoExposure, "compensation");
717
+ const upSpeed = table.column(AutoExposure, "speedUp");
718
+ const downSpeed = table.column(AutoExposure, "speedDown");
719
+ for (let i = 0; i < table.count; i++) {
720
+ const entity = table.entities[i];
721
+ let m = meters.get(entity);
722
+ if (!m) {
723
+ m = {
724
+ metered: void 0,
725
+ readings: 0,
726
+ submitted: 0,
727
+ reading: 0,
728
+ ev: ev[i],
729
+ started: true
730
+ };
731
+ meters.set(entity, m);
732
+ }
733
+ if (m.metered !== void 0) {
734
+ const target = Math.min(maxEv[i], Math.max(minEv[i], m.metered - comp[i]));
735
+ const speed = target > m.ev ? upSpeed[i] : downSpeed[i];
736
+ const step = speed * dt;
737
+ const d = target - m.ev;
738
+ m.ev += Math.abs(d) <= step ? d : Math.sign(d) * step;
739
+ }
740
+ ev[i] = m.ev;
741
+ }
742
+ table.markChanged(Exposure);
743
+ }
744
+ }
745
+ });
746
+ const POST_NODES = [
747
+ ["post/fog", PostEffect.Fog],
748
+ ["post/taa", PostEffect.Taa],
749
+ ["post/motion-blur", PostEffect.MotionBlur],
750
+ ["post/dof", PostEffect.DepthOfField],
751
+ ["post/bloom", PostEffect.Bloom],
752
+ ["post/exposure", PostEffect.AutoExposure],
753
+ ["post/fxaa", PostEffect.Fxaa]
754
+ ];
755
+ const halfSize = { divide: 2 };
756
+ function addPostNodes(world) {
757
+ const graph = world.resource(Graph);
758
+ world.initResource(ExposureMeters);
759
+ graph.declare({ name: "prepass-normal", format: "rgba16float" });
760
+ graph.declare({ name: "velocity", format: "rg16float" });
761
+ 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" });
766
+ graph.declare({ name: "dof-half", format: "rgba16float", size: halfSize });
767
+ graph.declare({ name: "dof-blur", format: "rgba16float", size: halfSize });
768
+ graph.declare({
769
+ name: "bloom",
770
+ format: "rgba16float",
771
+ size: halfSize,
772
+ mipLevelCount: (view) => {
773
+ const cam = cameraOf(view);
774
+ return cam ? bloomLevels(cam) : 1;
775
+ }
776
+ });
777
+ graph.addNode("prepass", prepassNode);
778
+ graph.addNode("ssao", ssaoNode(RenderPhase.Prepass + 50, false));
779
+ graph.addNode("ssao-deferred", ssaoNode(RenderPhase.Opaque + 20, true));
780
+ graph.addNode("post/fog", fogNode());
781
+ graph.addNode("post/taa", taaNode());
782
+ graph.addNode("post/motion-blur", motionBlurNode());
783
+ graph.addNode("post/dof", dofNode());
784
+ graph.addNode("post/bloom", bloomNode());
785
+ graph.addNode("post/exposure", autoExposureNode());
786
+ }
787
+ const DESCRIBED = [
788
+ ["ssao", "ssao", PostEffect.Ssao],
789
+ ["fog", "post/fog", PostEffect.Fog],
790
+ ["taa", "post/taa", PostEffect.Taa],
791
+ ["motion-blur", "post/motion-blur", PostEffect.MotionBlur],
792
+ ["dof", "post/dof", PostEffect.DepthOfField],
793
+ ["bloom", "post/bloom", PostEffect.Bloom],
794
+ ["auto-exposure", "post/exposure", PostEffect.AutoExposure],
795
+ ["color-grading", "tonemap", PostEffect.Grading],
796
+ ["vignette", "tonemap", PostEffect.Vignette],
797
+ ["fxaa", "post/fxaa", PostEffect.Fxaa]
798
+ ];
799
+ function gpuMs(timings, prefix) {
800
+ let total;
801
+ const key = `gpu:${prefix}`;
802
+ for (const name in timings) {
803
+ if (name === key || name.startsWith(`${key}-`) || name.startsWith(`${key}/`)) {
804
+ total = (total ?? 0) + timings[name].avg;
805
+ }
806
+ }
807
+ return total;
808
+ }
809
+ function describePost(world) {
810
+ const profiler = world.tryResource(ProfilerResource);
811
+ const timings = profiler?.all() ?? {};
812
+ const meters = world.tryResource(ExposureMeters);
813
+ const views = {};
814
+ for (const view of world.resource(Views).list) {
815
+ const cam = cameraOf(view);
816
+ if (!cam) continue;
817
+ const effects = [];
818
+ for (const [name, node, bit] of DESCRIBED) {
819
+ if ((cam.post.effects & bit) === 0) continue;
820
+ const shared = node === "tonemap";
821
+ effects.push({ name, gpuMs: shared ? void 0 : gpuMs(timings, node) });
822
+ }
823
+ const meter = meters?.get(cam.entity);
824
+ views[view.name] = {
825
+ effects,
826
+ antialiasing: cam.post.effects & PostEffect.Taa ? "taa" : cam.post.effects & PostEffect.Fxaa ? "fxaa" : cam.msaa > 1 ? `msaa x${cam.msaa}` : "none",
827
+ prepass: needsPrepass(view),
828
+ ev100: cam.ev100,
829
+ meteredEv100: meter?.metered,
830
+ // Per-pass times overlap on tile-based GPUs; these spans don't.
831
+ gpuMs: {
832
+ prepass: timings["gpu:prepass"]?.avg,
833
+ ssao: timings["gpu:span/ssao"]?.avg,
834
+ post: timings["gpu:span/post"]?.avg
835
+ }
836
+ };
837
+ }
838
+ return { views };
839
+ }
840
+ export {
841
+ ExposureMeters,
842
+ METER_READBACKS,
843
+ POST_NODES,
844
+ adaptExposure,
845
+ addPostNodes,
846
+ bloomLevels,
847
+ describePost,
848
+ histogramEv
849
+ };