@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,780 @@
1
+ import { defineResource, defineSystem, mat4 } from "@aethervtt/shard-core";
2
+ import { GpuBuffer } from "@aethervtt/shard-gpu";
3
+ import { Atmospheres } from "./atmosphere.js";
4
+ import { MULTISCATTER_SIZE, TRANSMITTANCE_H, TRANSMITTANCE_W } from "./atmosphere-model.js";
5
+ import {
6
+ Environments,
7
+ environmentBakers
8
+ } from "./environment.js";
9
+ import { ForwardStateResource, sceneColor } from "./forward.js";
10
+ import { RenderPhase } from "./graph.js";
11
+ import { Gpu, Graph, Shaders, Views } from "./plugin.js";
12
+ import { hasEffect, PostEffect } from "./post.js";
13
+ import { cameraOf } from "./view.js";
14
+ const LUT_LAYERS = 8;
15
+ const PARAMS_FLOATS = 32;
16
+ const VIEW_FLOATS = 184;
17
+ const O_SUNS = 128;
18
+ const O_INFO = 144;
19
+ const O_FRAME = 148;
20
+ const O_FROXELS = 160;
21
+ const O_INV = 164;
22
+ const O_CAMERA = 180;
23
+ class AtmosphereGpu {
24
+ generation = -1;
25
+ transmittance;
26
+ multiscatter;
27
+ transmittanceArray;
28
+ multiscatterArray;
29
+ clamp;
30
+ repeat;
31
+ /** The record in each layer, and the frame it was last listed. */
32
+ owners = new Array(LUT_LAYERS).fill(void 0);
33
+ used = new Int32Array(LUT_LAYERS).fill(-1e3);
34
+ params = [];
35
+ cameras = /* @__PURE__ */ new Map();
36
+ /** LUT recomputations so far (describe, and tests that nothing recomputes per frame). */
37
+ lutComputes = 0;
38
+ lutFrame = -1;
39
+ frame = 0;
40
+ pipelines = /* @__PURE__ */ new Map();
41
+ layouts;
42
+ groups = /* @__PURE__ */ new Map();
43
+ ensure(gpu) {
44
+ if (this.generation === gpu.generation) return;
45
+ this.generation = gpu.generation;
46
+ const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_SRC;
47
+ this.transmittance = gpu.device.createTexture({
48
+ label: "atmosphere/transmittance",
49
+ size: [TRANSMITTANCE_W, TRANSMITTANCE_H, LUT_LAYERS],
50
+ format: "rgba16float",
51
+ usage
52
+ });
53
+ this.multiscatter = gpu.device.createTexture({
54
+ label: "atmosphere/multiscatter",
55
+ size: [MULTISCATTER_SIZE, MULTISCATTER_SIZE, LUT_LAYERS],
56
+ format: "rgba16float",
57
+ usage
58
+ });
59
+ this.transmittanceArray = this.transmittance.createView({ dimension: "2d-array" });
60
+ this.multiscatterArray = this.multiscatter.createView({ dimension: "2d-array" });
61
+ this.clamp = gpu.device.createSampler({
62
+ label: "atmosphere/clamp",
63
+ magFilter: "linear",
64
+ minFilter: "linear",
65
+ addressModeU: "clamp-to-edge",
66
+ addressModeV: "clamp-to-edge",
67
+ addressModeW: "clamp-to-edge"
68
+ });
69
+ this.repeat = gpu.device.createSampler({
70
+ label: "atmosphere/repeat-u",
71
+ magFilter: "linear",
72
+ minFilter: "linear",
73
+ addressModeU: "repeat",
74
+ addressModeV: "clamp-to-edge"
75
+ });
76
+ this.params.length = 0;
77
+ for (let i = 0; i < LUT_LAYERS; i++) {
78
+ this.params.push(
79
+ new GpuBuffer(gpu, {
80
+ label: `atmosphere/params${i}`,
81
+ usage: GPUBufferUsage.UNIFORM,
82
+ size: PARAMS_FLOATS * 4
83
+ })
84
+ );
85
+ }
86
+ this.owners.fill(void 0);
87
+ this.cameras.clear();
88
+ this.pipelines.clear();
89
+ this.groups.clear();
90
+ this.layouts = void 0;
91
+ }
92
+ /** A layer for a record: its own, a free one, or the least recently used. */
93
+ assign(rec) {
94
+ if (rec.layer >= 0 && this.owners[rec.layer] === rec) {
95
+ this.used[rec.layer] = this.frame;
96
+ return;
97
+ }
98
+ let best = 0;
99
+ for (let i = 0; i < LUT_LAYERS; i++) {
100
+ if (!this.owners[i]) {
101
+ best = i;
102
+ break;
103
+ }
104
+ if (this.used[i] < this.used[best]) best = i;
105
+ }
106
+ const old = this.owners[best];
107
+ if (old) old.layer = -1;
108
+ this.owners[best] = rec;
109
+ this.used[best] = this.frame;
110
+ rec.layer = best;
111
+ rec.layerVersion = -1;
112
+ }
113
+ camera(gpu, entity) {
114
+ let c = this.cameras.get(entity);
115
+ if (!c) {
116
+ c = {
117
+ uniform: new GpuBuffer(gpu, {
118
+ label: `atmosphere/view${entity}`,
119
+ usage: GPUBufferUsage.UNIFORM,
120
+ size: VIEW_FLOATS * 4
121
+ }),
122
+ data: new Float32Array(VIEW_FLOATS),
123
+ skyView: void 0,
124
+ scatter: void 0,
125
+ transmittance: void 0,
126
+ computed: -1,
127
+ generation: gpu.generation,
128
+ seen: 0
129
+ };
130
+ this.cameras.set(entity, c);
131
+ }
132
+ c.seen = this.frame;
133
+ return c;
134
+ }
135
+ group(gpu, slot, key, layout, entries) {
136
+ let g = this.groups.get(slot);
137
+ if (!g || g.key !== key) {
138
+ g = { key, group: gpu.device.createBindGroup({ label: slot, layout, entries: entries() }) };
139
+ this.groups.set(slot, g);
140
+ }
141
+ return g.group;
142
+ }
143
+ }
144
+ const AtmosphereGpuResource = defineResource("render/AtmosphereGpu", {
145
+ description: "Atmosphere LUTs, per-camera sky-view LUTs and froxels, and their pipelines.",
146
+ init: () => new AtmosphereGpu()
147
+ });
148
+ function packParams(rec, out, o, origin, oi) {
149
+ const m = rec.model;
150
+ out[o] = m.bottom;
151
+ out[o + 1] = m.top;
152
+ out[o + 2] = m.ground;
153
+ out[o + 3] = m.intensity;
154
+ out[o + 4] = m.rayleigh[0];
155
+ out[o + 5] = m.rayleigh[1];
156
+ out[o + 6] = m.rayleigh[2];
157
+ out[o + 7] = m.rayleighInvScale;
158
+ out[o + 8] = m.mieScattering;
159
+ out[o + 9] = m.mieAbsorption;
160
+ out[o + 10] = m.mieInvScale;
161
+ out[o + 11] = Math.max(rec.layer, 0);
162
+ out[o + 12] = m.mieG[0];
163
+ out[o + 13] = m.mieG[1];
164
+ out[o + 14] = m.mieG[2];
165
+ out[o + 15] = 0;
166
+ out[o + 16] = m.absorption[0];
167
+ out[o + 17] = m.absorption[1];
168
+ out[o + 18] = m.absorption[2];
169
+ out[o + 19] = m.absorptionCenter;
170
+ out[o + 20] = m.absorptionWidth;
171
+ out[o + 21] = 0;
172
+ out[o + 22] = 0;
173
+ out[o + 23] = 0;
174
+ out[o + 24] = m.albedo[0];
175
+ out[o + 25] = m.albedo[1];
176
+ out[o + 26] = m.albedo[2];
177
+ out[o + 27] = 0;
178
+ out[o + 28] = origin ? origin[oi] : 0;
179
+ out[o + 29] = origin ? origin[oi + 1] : 0;
180
+ out[o + 30] = origin ? origin[oi + 2] : 0;
181
+ out[o + 31] = origin ? origin[oi + 3] : 0;
182
+ }
183
+ const paramScratch = new Float32Array(PARAMS_FLOATS);
184
+ const rotView = mat4.create();
185
+ const relViewProj = mat4.create();
186
+ const relInverse = mat4.create();
187
+ function relativeInverse(cam, out) {
188
+ mat4.copy(rotView, cam.view);
189
+ rotView[12] = 0;
190
+ rotView[13] = 0;
191
+ rotView[14] = 0;
192
+ mat4.multiply(relViewProj, cam.proj, rotView);
193
+ return mat4.invert(out, relViewProj) ?? out;
194
+ }
195
+ function packView(ca, cam, c, out) {
196
+ out.fill(0);
197
+ for (const rec of ca.list) if (rec.layerVersion < 0) return;
198
+ const n = Math.min(ca.list.length, 4);
199
+ for (let i = 0; i < n; i++) packParams(ca.list[i], out, i * PARAMS_FLOATS, ca.origins, i * 4);
200
+ out.set(ca.suns.data.subarray(0, 16), O_SUNS);
201
+ const primary = ca.primary ? ca.list.indexOf(ca.primary) : -1;
202
+ out[O_INFO] = n;
203
+ out[O_INFO + 1] = primary;
204
+ out[O_INFO + 2] = ca.suns.count;
205
+ out[O_INFO + 3] = ca.aerialPerspective && (!ca.inside || c.computed >= 0) ? 1 : 0;
206
+ for (let k = 0; k < 3; k++) {
207
+ out[O_FRAME + k] = ca.frame[k];
208
+ out[O_FRAME + 4 + k] = ca.frame[3 + k];
209
+ out[O_FRAME + 8 + k] = ca.frame[6 + k];
210
+ }
211
+ const maxD = ca.maxDistance / 1e3;
212
+ const d0 = maxD / 64;
213
+ out[O_FROXELS] = ca.inside ? maxD : 0;
214
+ out[O_FROXELS + 1] = d0;
215
+ out[O_FROXELS + 2] = ca.froxels[2];
216
+ out[O_FROXELS + 3] = 1 / Math.log(1 + maxD / d0);
217
+ out.set(relativeInverse(cam, relInverse), O_INV);
218
+ out[O_CAMERA] = cam.position[0];
219
+ out[O_CAMERA + 1] = cam.position[1];
220
+ out[O_CAMERA + 2] = cam.position[2];
221
+ out[O_CAMERA + 3] = cam.exposure;
222
+ }
223
+ const uploadAtmospheres = defineSystem({
224
+ name: "render/atmosphere-upload",
225
+ description: "Assigns atmosphere LUT layers and uploads each camera's atmosphere uniform.",
226
+ run: (_, world) => {
227
+ const gpuState = world.resource(AtmosphereGpuResource);
228
+ const store = world.resource(Atmospheres);
229
+ const gpu = world.tryResource(Gpu);
230
+ if (!gpu) return;
231
+ gpuState.ensure(gpu);
232
+ gpuState.frame++;
233
+ for (const view of world.resource(Views).list) {
234
+ const cam = cameraOf(view);
235
+ const ca = view.data.atmosphere;
236
+ if (!cam || !ca) continue;
237
+ for (const rec of ca.list) gpuState.assign(rec);
238
+ const c = gpuState.camera(gpu, cam.entity);
239
+ packView(ca, cam, c, c.data);
240
+ c.uniform.write(c.data);
241
+ if (ca.primary && ca.inside) ensureViewTextures(gpu, c, ca);
242
+ }
243
+ for (const [e, c] of gpuState.cameras) {
244
+ if (gpuState.frame - c.seen > 120 || !store.cameras.has(e)) {
245
+ c.uniform.destroy();
246
+ c.skyView?.destroy();
247
+ c.scatter?.destroy();
248
+ c.transmittance?.destroy();
249
+ gpuState.cameras.delete(e);
250
+ }
251
+ }
252
+ }
253
+ });
254
+ function ensureViewTextures(gpu, c, ca) {
255
+ const [sw, sh] = ca.skyViewSize;
256
+ if (!c.skyView || c.generation !== gpu.generation || c.skyView.width !== sw || c.skyView.height !== sh) {
257
+ c.skyView?.destroy();
258
+ c.skyView = gpu.device.createTexture({
259
+ label: "atmosphere/sky-view",
260
+ size: [sw, sh],
261
+ format: "rgba16float",
262
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING
263
+ });
264
+ }
265
+ const [fx, fy, fz] = ca.froxels;
266
+ if (!c.scatter || c.generation !== gpu.generation || c.scatter.width !== fx || c.scatter.height !== fy || c.scatter.depthOrArrayLayers !== fz) {
267
+ c.scatter?.destroy();
268
+ c.transmittance?.destroy();
269
+ const desc = {
270
+ label: "atmosphere/froxels",
271
+ size: [fx, fy, fz],
272
+ dimension: "3d",
273
+ format: "rgba16float",
274
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING
275
+ };
276
+ c.scatter = gpu.device.createTexture(desc);
277
+ c.transmittance = gpu.device.createTexture({
278
+ ...desc,
279
+ label: "atmosphere/froxel-transmittance"
280
+ });
281
+ }
282
+ c.generation = gpu.generation;
283
+ }
284
+ function layouts(gpu, s) {
285
+ if (s.layouts) return s.layouts;
286
+ const C = GPUShaderStage.COMPUTE;
287
+ const F = GPUShaderStage.FRAGMENT;
288
+ const uniform = (binding, visibility) => ({
289
+ binding,
290
+ visibility,
291
+ buffer: { type: "uniform" }
292
+ });
293
+ const array = (binding, visibility) => ({
294
+ binding,
295
+ visibility,
296
+ texture: { sampleType: "float", viewDimension: "2d-array" }
297
+ });
298
+ const filtering = (binding, visibility) => ({
299
+ binding,
300
+ visibility,
301
+ sampler: { type: "filtering" }
302
+ });
303
+ const storage = (binding, viewDimension) => ({
304
+ binding,
305
+ visibility: C,
306
+ storageTexture: {
307
+ access: "write-only",
308
+ format: "rgba16float",
309
+ viewDimension
310
+ }
311
+ });
312
+ s.layouts = {
313
+ transmittance: gpu.layouts.bindGroupLayout({
314
+ label: "atmosphere/transmittance",
315
+ entries: [uniform(0, C), storage(1, "2d")]
316
+ }),
317
+ multiscatter: gpu.layouts.bindGroupLayout({
318
+ label: "atmosphere/multiscatter",
319
+ entries: [uniform(0, C), array(1, C), filtering(2, C), storage(3, "2d")]
320
+ }),
321
+ skyView: gpu.layouts.bindGroupLayout({
322
+ label: "atmosphere/sky-view",
323
+ entries: [uniform(0, C), array(1, C), array(2, C), filtering(3, C), storage(4, "2d")]
324
+ }),
325
+ aerial: gpu.layouts.bindGroupLayout({
326
+ label: "atmosphere/aerial",
327
+ entries: [
328
+ uniform(0, C),
329
+ array(1, C),
330
+ array(2, C),
331
+ filtering(3, C),
332
+ storage(4, "3d"),
333
+ storage(5, "3d")
334
+ ]
335
+ }),
336
+ bake: gpu.layouts.bindGroupLayout({
337
+ label: "atmosphere/bake",
338
+ entries: [uniform(0, C), array(1, C), array(2, C), filtering(3, C), storage(4, "2d-array")]
339
+ }),
340
+ composite: gpu.layouts.bindGroupLayout({
341
+ label: "atmosphere/composite",
342
+ entries: [
343
+ { binding: 0, visibility: F, texture: { sampleType: "unfilterable-float" } },
344
+ { binding: 1, visibility: F, texture: { sampleType: "depth" } },
345
+ uniform(2, F),
346
+ array(3, F),
347
+ array(4, F),
348
+ filtering(5, F),
349
+ { binding: 6, visibility: F, texture: { sampleType: "float", viewDimension: "3d" } },
350
+ { binding: 7, visibility: F, texture: { sampleType: "float", viewDimension: "3d" } }
351
+ ]
352
+ }),
353
+ viewOnly: gpu.layouts.bindGroupLayout({
354
+ label: "atmosphere/view",
355
+ entries: [uniform(0, GPUShaderStage.VERTEX | F)]
356
+ }),
357
+ sky: gpu.layouts.bindGroupLayout({
358
+ label: "atmosphere/sky",
359
+ entries: [
360
+ uniform(0, F),
361
+ array(1, F),
362
+ array(2, F),
363
+ filtering(3, F),
364
+ { binding: 4, visibility: F, texture: { sampleType: "float" } },
365
+ filtering(5, F),
366
+ { binding: 6, visibility: F, texture: { sampleType: "float", viewDimension: "cube" } },
367
+ uniform(7, F)
368
+ ]
369
+ })
370
+ };
371
+ return s.layouts;
372
+ }
373
+ function compute(ctx, s, name, root) {
374
+ const cached = s.pipelines.get(name);
375
+ if (cached) return cached;
376
+ const gpu = ctx.gpu;
377
+ const module = ctx.world.resource(Shaders).module(gpu, { root });
378
+ if (!module) {
379
+ gpu.pipelines.skipped++;
380
+ return void 0;
381
+ }
382
+ const layout = layouts(gpu, s)[name];
383
+ const p = gpu.pipelines.compute({
384
+ label: `atmosphere/${name}`,
385
+ layout: gpu.layouts.pipelineLayout({ label: `atmosphere/${name}`, bindGroupLayouts: [layout] }),
386
+ compute: { module, entryPoint: "main" }
387
+ });
388
+ if (p) s.pipelines.set(name, p);
389
+ return p;
390
+ }
391
+ const hasAtmosphere = (view) => view.data.atmosphere !== void 0;
392
+ function lutNode() {
393
+ return {
394
+ kind: "raw",
395
+ phase: RenderPhase.Setup,
396
+ enabled: hasAtmosphere,
397
+ writes: ["atmosphere-luts"],
398
+ run: (ctx) => {
399
+ const s = ctx.world.resource(AtmosphereGpuResource);
400
+ if (s.lutFrame === s.frame) return;
401
+ let dirty = false;
402
+ for (const rec of s.owners) if (rec && rec.layerVersion !== rec.version) dirty = true;
403
+ if (!dirty) {
404
+ s.lutFrame = s.frame;
405
+ return;
406
+ }
407
+ const t = compute(ctx, s, "transmittance", "shard::atmosphere::transmittance_lut");
408
+ const m = compute(ctx, s, "multiscatter", "shard::atmosphere::multiscatter_lut");
409
+ if (!t || !m) return;
410
+ s.lutFrame = s.frame;
411
+ const gpu = ctx.gpu;
412
+ const l = layouts(gpu, s);
413
+ const pass = ctx.encoder.beginComputePass({
414
+ label: "atmosphere/luts",
415
+ timestampWrites: ctx.timestamps("atmosphere/luts")
416
+ });
417
+ for (let i = 0; i < LUT_LAYERS; i++) {
418
+ const rec = s.owners[i];
419
+ if (!rec || rec.layerVersion === rec.version) continue;
420
+ packParams(rec, paramScratch, 0, void 0, 0);
421
+ s.params[i].write(paramScratch);
422
+ const layer = (texture) => texture.createView({ dimension: "2d", baseArrayLayer: i, arrayLayerCount: 1 });
423
+ pass.setPipeline(t);
424
+ pass.setBindGroup(
425
+ 0,
426
+ gpu.device.createBindGroup({
427
+ layout: l.transmittance,
428
+ entries: [
429
+ { binding: 0, resource: { buffer: s.params[i].buffer } },
430
+ { binding: 1, resource: layer(s.transmittance) }
431
+ ]
432
+ })
433
+ );
434
+ pass.dispatchWorkgroups(TRANSMITTANCE_W / 8, TRANSMITTANCE_H / 8);
435
+ pass.setPipeline(m);
436
+ pass.setBindGroup(
437
+ 0,
438
+ gpu.device.createBindGroup({
439
+ layout: l.multiscatter,
440
+ entries: [
441
+ { binding: 0, resource: { buffer: s.params[i].buffer } },
442
+ { binding: 1, resource: s.transmittanceArray },
443
+ { binding: 2, resource: s.clamp },
444
+ { binding: 3, resource: layer(s.multiscatter) }
445
+ ]
446
+ })
447
+ );
448
+ pass.dispatchWorkgroups(MULTISCATTER_SIZE / 8, MULTISCATTER_SIZE / 8);
449
+ rec.layerVersion = rec.version;
450
+ s.lutComputes++;
451
+ }
452
+ pass.end();
453
+ }
454
+ };
455
+ }
456
+ function viewNode() {
457
+ return {
458
+ kind: "raw",
459
+ phase: RenderPhase.Setup,
460
+ enabled: (view) => {
461
+ const ca = view.data.atmosphere;
462
+ return ca !== void 0 && ca.primary !== void 0 && ca.inside;
463
+ },
464
+ reads: ["atmosphere-luts"],
465
+ writes: ["atmosphere-view"],
466
+ run: (ctx) => {
467
+ const cam = cameraOf(ctx.view);
468
+ const ca = ctx.view.data.atmosphere;
469
+ if (!cam || !ca?.primary) return;
470
+ const s = ctx.world.resource(AtmosphereGpuResource);
471
+ const c = s.cameras.get(cam.entity);
472
+ if (!c?.skyView || !c.scatter || !c.transmittance) return;
473
+ if (ca.primary.layerVersion !== ca.primary.version) return;
474
+ const sky = compute(ctx, s, "skyView", "shard::atmosphere::sky_view");
475
+ const aerial = compute(ctx, s, "aerial", "shard::atmosphere::aerial");
476
+ if (!sky || !aerial) return;
477
+ const gpu = ctx.gpu;
478
+ const l = layouts(gpu, s);
479
+ const common = () => [
480
+ { binding: 0, resource: { buffer: c.uniform.buffer } },
481
+ { binding: 1, resource: s.transmittanceArray },
482
+ { binding: 2, resource: s.multiscatterArray },
483
+ { binding: 3, resource: s.clamp }
484
+ ];
485
+ const base = `${gpu.generation}/${c.uniform.version}/${idOf(s.transmittance)}`;
486
+ let pass = ctx.encoder.beginComputePass({
487
+ label: `${ctx.view.name}/atmosphere/sky-view`,
488
+ timestampWrites: ctx.timestamps("atmosphere/sky-view")
489
+ });
490
+ pass.setPipeline(sky);
491
+ pass.setBindGroup(
492
+ 0,
493
+ s.group(gpu, `${cam.entity}/sky-view`, `${base}/${idOf(c.skyView)}`, l.skyView, () => [
494
+ ...common(),
495
+ { binding: 4, resource: c.skyView.createView() }
496
+ ])
497
+ );
498
+ pass.dispatchWorkgroups(Math.ceil(c.skyView.width / 8), Math.ceil(c.skyView.height / 8));
499
+ pass.end();
500
+ if (ca.aerialPerspective) {
501
+ pass = ctx.encoder.beginComputePass({
502
+ label: `${ctx.view.name}/atmosphere/aerial`,
503
+ timestampWrites: ctx.timestamps("atmosphere/aerial")
504
+ });
505
+ pass.setPipeline(aerial);
506
+ pass.setBindGroup(
507
+ 0,
508
+ s.group(
509
+ gpu,
510
+ `${cam.entity}/aerial`,
511
+ `${base}/${idOf(c.scatter)}/${idOf(c.transmittance)}`,
512
+ l.aerial,
513
+ () => [
514
+ ...common(),
515
+ { binding: 4, resource: c.scatter.createView() },
516
+ { binding: 5, resource: c.transmittance.createView() }
517
+ ]
518
+ )
519
+ );
520
+ pass.dispatchWorkgroups(Math.ceil(c.scatter.width / 8), Math.ceil(c.scatter.height / 8));
521
+ pass.end();
522
+ }
523
+ c.computed = s.frame;
524
+ }
525
+ };
526
+ }
527
+ const ids = /* @__PURE__ */ new WeakMap();
528
+ let nextId = 1;
529
+ function idOf(o) {
530
+ let id = ids.get(o);
531
+ if (id === void 0) {
532
+ id = nextId++;
533
+ ids.set(o, id);
534
+ }
535
+ return id;
536
+ }
537
+ let empty;
538
+ function emptyTextures(gpu) {
539
+ if (!empty || empty.generation !== gpu.generation) {
540
+ const usage = GPUTextureUsage.TEXTURE_BINDING;
541
+ empty = {
542
+ generation: gpu.generation,
543
+ d2: gpu.device.createTexture({
544
+ label: "atmosphere/empty",
545
+ size: [1, 1],
546
+ format: "rgba16float",
547
+ usage
548
+ }),
549
+ d3: gpu.device.createTexture({
550
+ label: "atmosphere/empty-3d",
551
+ size: [1, 1, 1],
552
+ dimension: "3d",
553
+ format: "rgba16float",
554
+ usage
555
+ })
556
+ };
557
+ }
558
+ return empty;
559
+ }
560
+ function skyNode() {
561
+ const bgData = new Float32Array(8);
562
+ const bgBuffers = /* @__PURE__ */ new Map();
563
+ return {
564
+ kind: "render",
565
+ phase: RenderPhase.Sky,
566
+ enabled: (view) => {
567
+ const entry = view.data.environment;
568
+ return hasAtmosphere(view) && entry?.sky === true && entry.background >= 0;
569
+ },
570
+ reads: ["environment", "atmosphere-luts", "atmosphere-view"],
571
+ writes: ["scene-color", "hdr"],
572
+ color: (view) => sceneColor(view),
573
+ depth: { resource: "scene-depth", readOnly: true },
574
+ run: (ctx) => {
575
+ const cam = cameraOf(ctx.view);
576
+ const ca = ctx.view.data.atmosphere;
577
+ const entry = ctx.view.data.environment;
578
+ if (!cam || !ca) return;
579
+ const s = ctx.world.resource(AtmosphereGpuResource);
580
+ const c = s.cameras.get(cam.entity);
581
+ if (!c) return;
582
+ for (const rec of ca.list) if (rec.layerVersion !== rec.version) return;
583
+ const gpu = ctx.gpu;
584
+ const shaders = ctx.world.resource(Shaders);
585
+ const module = shaders.module(gpu, { root: "shard::atmosphere::sky" });
586
+ if (!module) {
587
+ gpu.pipelines.skipped++;
588
+ return;
589
+ }
590
+ const l = layouts(gpu, s);
591
+ const key = `sky/x${cam.msaa}`;
592
+ let pipeline = s.pipelines.get(key);
593
+ if (!pipeline) {
594
+ pipeline = gpu.pipelines.render({
595
+ label: `atmosphere/${key}`,
596
+ layout: gpu.layouts.pipelineLayout({
597
+ label: "atmosphere/sky",
598
+ bindGroupLayouts: [l.viewOnly, l.sky]
599
+ }),
600
+ vertex: { module, entryPoint: "vs" },
601
+ fragment: { module, entryPoint: "fs", targets: [{ format: "rgba16float" }] },
602
+ depthStencil: { format: "depth32float", depthWriteEnabled: false, depthCompare: "equal" },
603
+ multisample: { count: cam.msaa }
604
+ });
605
+ if (!pipeline) return;
606
+ s.pipelines.set(key, pipeline);
607
+ }
608
+ const pv = ctx.world.resource(ForwardStateResource).views.get(ctx.view.name);
609
+ if (!pv) return;
610
+ const envs = ctx.world.resource(Environments);
611
+ const backdrop = entry.backdrop;
612
+ const env = backdrop && backdrop.environment.bakes > 0 ? backdrop.environment : void 0;
613
+ let bg = bgBuffers.get(cam.entity);
614
+ if (!bg) {
615
+ bg = new GpuBuffer(gpu, {
616
+ label: "atmosphere/background",
617
+ usage: GPUBufferUsage.UNIFORM,
618
+ size: bgData.byteLength
619
+ });
620
+ bgBuffers.set(cam.entity, bg);
621
+ }
622
+ bgData[0] = env ? backdrop.intensity * entry.background : 0;
623
+ bgData[1] = Math.cos(backdrop?.rotation ?? 0);
624
+ bgData[2] = Math.sin(backdrop?.rotation ?? 0);
625
+ bgData[3] = env ? 1 : 0;
626
+ bgData[4] = ca.skyViewSize[0];
627
+ bgData[5] = ca.skyViewSize[1];
628
+ bg.write(bgData);
629
+ const e = emptyTextures(gpu);
630
+ const skyView = ca.inside && c.skyView ? c.skyView : e.d2;
631
+ const cube = env?.sourceView ?? envs.emptyView;
632
+ const group = s.group(
633
+ gpu,
634
+ `${cam.entity}/sky`,
635
+ `${gpu.generation}/${c.uniform.version}/${bg.version}/${idOf(skyView)}/${env ? idOf(env.source) : 0}/${idOf(s.transmittance)}`,
636
+ l.sky,
637
+ () => [
638
+ { binding: 0, resource: { buffer: c.uniform.buffer } },
639
+ { binding: 1, resource: s.transmittanceArray },
640
+ { binding: 2, resource: s.multiscatterArray },
641
+ { binding: 3, resource: s.clamp },
642
+ { binding: 4, resource: skyView.createView() },
643
+ { binding: 5, resource: s.repeat },
644
+ { binding: 6, resource: cube },
645
+ { binding: 7, resource: { buffer: bg.buffer } }
646
+ ]
647
+ );
648
+ const viewGroup = s.group(
649
+ gpu,
650
+ `${cam.entity}/view`,
651
+ `${gpu.generation}/${pv.uniform.version}`,
652
+ l.viewOnly,
653
+ () => [{ binding: 0, resource: { buffer: pv.uniform.buffer } }]
654
+ );
655
+ const pass = ctx.renderPass;
656
+ pass.setPipeline(pipeline);
657
+ pass.setBindGroup(0, viewGroup);
658
+ pass.setBindGroup(1, group);
659
+ pass.draw(3);
660
+ }
661
+ };
662
+ }
663
+ function compositeNode() {
664
+ return {
665
+ kind: "render",
666
+ phase: RenderPhase.Post,
667
+ enabled: hasEffect(PostEffect.Atmosphere),
668
+ reads: ["atmosphere-in", "depth", "atmosphere-luts", "atmosphere-view"],
669
+ writes: ["atmosphere-out"],
670
+ color: [{ resource: "atmosphere-out", clear: { r: 0, g: 0, b: 0, a: 1 } }],
671
+ run: (ctx) => {
672
+ const cam = cameraOf(ctx.view);
673
+ const ca = ctx.view.data.atmosphere;
674
+ const s = ctx.world.resource(AtmosphereGpuResource);
675
+ const c = cam ? s.cameras.get(cam.entity) : void 0;
676
+ const pv = ctx.world.resource(ForwardStateResource).views.get(ctx.view.name);
677
+ if (!cam || !ca || !c || !pv) return;
678
+ const gpu = ctx.gpu;
679
+ const l = layouts(gpu, s);
680
+ let pipeline = s.pipelines.get("composite");
681
+ if (!pipeline) {
682
+ const shaders = ctx.world.resource(Shaders);
683
+ const fs = shaders.module(gpu, { root: "shard::atmosphere::composite" });
684
+ const vs = shaders.module(gpu, { root: "shard::fullscreen" });
685
+ if (!fs || !vs) {
686
+ gpu.pipelines.skipped++;
687
+ return;
688
+ }
689
+ pipeline = gpu.pipelines.render({
690
+ label: "atmosphere/composite",
691
+ layout: gpu.layouts.pipelineLayout({
692
+ label: "atmosphere/composite",
693
+ bindGroupLayouts: [l.viewOnly, l.composite]
694
+ }),
695
+ vertex: { module: vs, entryPoint: "vs" },
696
+ fragment: { module: fs, entryPoint: "fs", targets: [{ format: "rgba16float" }] }
697
+ });
698
+ if (!pipeline) return;
699
+ s.pipelines.set("composite", pipeline);
700
+ }
701
+ const input = ctx.texture("atmosphere-in");
702
+ const depth = ctx.texture("depth");
703
+ const e = emptyTextures(gpu);
704
+ const inside = ca.inside && c.computed === s.frame;
705
+ const scatter = inside && c.scatter ? c.scatter : e.d3;
706
+ const trans = inside && c.transmittance ? c.transmittance : e.d3;
707
+ const group = s.group(
708
+ gpu,
709
+ `${cam.entity}/composite`,
710
+ `${gpu.generation}/${idOf(input)}/${idOf(depth)}/${c.uniform.version}/${idOf(scatter)}/${idOf(trans)}/${idOf(s.transmittance)}`,
711
+ l.composite,
712
+ () => [
713
+ { binding: 0, resource: input.createView() },
714
+ { binding: 1, resource: depth.createView() },
715
+ { binding: 2, resource: { buffer: c.uniform.buffer } },
716
+ { binding: 3, resource: s.transmittanceArray },
717
+ { binding: 4, resource: s.multiscatterArray },
718
+ { binding: 5, resource: s.clamp },
719
+ { binding: 6, resource: scatter.createView() },
720
+ { binding: 7, resource: trans.createView() }
721
+ ]
722
+ );
723
+ const viewGroup = s.group(
724
+ gpu,
725
+ `${cam.entity}/view`,
726
+ `${gpu.generation}/${pv.uniform.version}`,
727
+ l.viewOnly,
728
+ () => [{ binding: 0, resource: { buffer: pv.uniform.buffer } }]
729
+ );
730
+ const pass = ctx.renderPass;
731
+ pass.setPipeline(pipeline);
732
+ pass.setBindGroup(0, viewGroup);
733
+ pass.setBindGroup(1, group);
734
+ pass.draw(3);
735
+ }
736
+ };
737
+ }
738
+ function bakeAtmosphere(ctx, pass, env, level0) {
739
+ const ca = env.atmosphere;
740
+ const s = ctx.world.resource(AtmosphereGpuResource);
741
+ const primary = ca?.primary;
742
+ if (!ca || !primary || primary.layerVersion !== primary.version) return false;
743
+ const c = s.cameras.get(ca.camera);
744
+ if (!c) return false;
745
+ const pipeline = compute(ctx, s, "bake", "shard::atmosphere::bake");
746
+ if (!pipeline) return false;
747
+ const gpu = ctx.gpu;
748
+ pass.setPipeline(pipeline);
749
+ pass.setBindGroup(
750
+ 0,
751
+ gpu.device.createBindGroup({
752
+ layout: layouts(gpu, s).bake,
753
+ entries: [
754
+ { binding: 0, resource: { buffer: c.uniform.buffer } },
755
+ { binding: 1, resource: s.transmittanceArray },
756
+ { binding: 2, resource: s.multiscatterArray },
757
+ { binding: 3, resource: s.clamp },
758
+ { binding: 4, resource: level0 }
759
+ ]
760
+ })
761
+ );
762
+ pass.dispatchWorkgroups(env.size / 8, env.size / 8, 6);
763
+ return true;
764
+ }
765
+ function addAtmosphereNodes(world) {
766
+ const graph = world.resource(Graph);
767
+ world.initResource(AtmosphereGpuResource);
768
+ environmentBakers.set("atmosphere", bakeAtmosphere);
769
+ graph.addNode("atmosphere/luts", lutNode());
770
+ graph.addNode("atmosphere/view", viewNode());
771
+ graph.addNode("atmosphere/sky", skyNode());
772
+ graph.addNode("post/atmosphere", compositeNode());
773
+ }
774
+ export {
775
+ AtmosphereGpu,
776
+ AtmosphereGpuResource,
777
+ LUT_LAYERS,
778
+ addAtmosphereNodes,
779
+ uploadAtmospheres
780
+ };