@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
@@ -0,0 +1,368 @@
1
+ import { GpuBuffer } from "@aethervtt/shard-gpu";
2
+ import { fragmentKernel } from "@aethervtt/shard-shader/fragment-kernel";
3
+ import { ENVIRONMENT_SHADERS } from "../environment-shaders.js";
4
+ import { LUT_SIZE, SPECULAR_MIPS, SPECULAR_SIZE } from "../environment-state.js";
5
+ import { GpuAssetsResource } from "../gpu-assets.js";
6
+ import { Shaders } from "../plugin.js";
7
+ const SLOT = 256;
8
+ const fragment = (root) => fragmentKernel(ENVIRONMENT_SHADERS[root], "main", root).source;
9
+ const BASELINE_ENVIRONMENT_SHADERS = {
10
+ "shard::env::from_equirect::fragment": fragment("shard::env::from_equirect"),
11
+ "shard::env::from_cube::fragment": fragment("shard::env::from_cube"),
12
+ "shard::env::specular::fragment": fragment("shard::env::specular"),
13
+ "shard::env::brdf_lut::fragment": fragment("shard::env::brdf_lut"),
14
+ "shard::env::baseline::downsample": `
15
+ import shard::env::common::cube_dir;
16
+
17
+ /** The face's size and the face (as fragment kernels read their target). */
18
+ @group(0) @binding(0) var<uniform> shard_target: vec4u;
19
+ /** The level above, alone. */
20
+ @group(0) @binding(1) var src: texture_cube<f32>;
21
+ @group(0) @binding(2) var src_sampler: sampler;
22
+
23
+ @fragment fn main(@builtin(position) p: vec4f) -> @location(0) vec4f {
24
+ // The centre of this texel is the corner its 2\xD72 block shares: bilinear weighs them equally.
25
+ let uv = (floor(p.xy) + 0.5) / f32(shard_target.x);
26
+ let c = textureSampleLevel(src, src_sampler, cube_dir(shard_target.w, uv), 0.0);
27
+ return vec4f(c.rgb, 1.0);
28
+ }`,
29
+ "shard::env::baseline::sh": `
30
+ import shard::env::common::{ PI, cube_dir, sh_basis };
31
+
32
+ /** The level's size. */
33
+ @group(0) @binding(0) var<uniform> shard_target: vec4u;
34
+ /** The 32\xB2 level (or the smallest there is), alone. */
35
+ @group(0) @binding(1) var src: texture_cube<f32>;
36
+ @group(0) @binding(2) var src_sampler: sampler;
37
+
38
+ /** Coefficient k (the fragment's x) of the SH9 irradiance, as the compute kernel writes it. */
39
+ @fragment fn main(@builtin(position) p: vec4f) -> @location(0) vec4f {
40
+ let k = u32(p.x);
41
+ let n = shard_target.x;
42
+ var c = vec3f(0.0);
43
+ var total = 0.0;
44
+ for (var face = 0u; face < 6u; face++) {
45
+ for (var y = 0u; y < n; y++) {
46
+ for (var x = 0u; x < n; x++) {
47
+ let uv = (vec2f(f32(x), f32(y)) + 0.5) / f32(n);
48
+ let st = uv * 2.0 - 1.0;
49
+ // Solid angle of the texel.
50
+ let w = 4.0 / (f32(n * n) * pow(1.0 + dot(st, st), 1.5));
51
+ let d = cube_dir(face, uv);
52
+ let radiance = textureSampleLevel(src, src_sampler, d, 0.0).rgb;
53
+ c += radiance * sh_basis(d)[k] * w;
54
+ total += w;
55
+ }
56
+ }
57
+ }
58
+ // Normalize the solid angles to exactly 4\u03C0, then apply the cosine lobe (\xC2_l / \u03C0).
59
+ let band = array<f32, 9>(1.0, 2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 0.25, 0.25, 0.25, 0.25, 0.25);
60
+ return vec4f(c * (4.0 * PI / total) * band[k], 0.0);
61
+ }`
62
+ };
63
+ const baselineBakers = /* @__PURE__ */ new Map();
64
+ const ROOTS = {
65
+ equirect: "shard::env::from_equirect::fragment",
66
+ cube: "shard::env::from_cube::fragment",
67
+ downsample: "shard::env::baseline::downsample",
68
+ sh: "shard::env::baseline::sh",
69
+ specular: "shard::env::specular::fragment",
70
+ lut: "shard::env::brdf_lut::fragment"
71
+ };
72
+ const kernels = /* @__PURE__ */ new WeakMap();
73
+ function kernelsOf(gpu) {
74
+ let k = kernels.get(gpu);
75
+ if (k && k.generation === gpu.generation) return k;
76
+ const F = GPUShaderStage.FRAGMENT;
77
+ const target = (binding) => ({
78
+ binding,
79
+ visibility: F,
80
+ buffer: { type: "uniform", hasDynamicOffset: true }
81
+ });
82
+ const cube = (binding) => ({
83
+ binding,
84
+ visibility: F,
85
+ texture: { sampleType: "float", viewDimension: "cube" }
86
+ });
87
+ const sampler = (binding) => ({
88
+ binding,
89
+ visibility: F,
90
+ sampler: { type: "filtering" }
91
+ });
92
+ const layout = (label, entries) => gpu.layouts.bindGroupLayout({ label: `env/baseline/${label}`, entries });
93
+ k = {
94
+ generation: gpu.generation,
95
+ layouts: {
96
+ equirect: layout("equirect", [
97
+ { binding: 0, visibility: F, texture: { sampleType: "float" } },
98
+ sampler(1),
99
+ target(2)
100
+ ]),
101
+ cube: layout("cube", [cube(0), sampler(1), target(2)]),
102
+ downsample: layout("downsample", [target(0), cube(1), sampler(2)]),
103
+ sh: layout("sh", [target(0), cube(1), sampler(2)]),
104
+ specular: layout("specular", [
105
+ cube(0),
106
+ sampler(1),
107
+ target(2),
108
+ { binding: 3, visibility: F, buffer: { type: "uniform" } }
109
+ ]),
110
+ lut: layout("lut", [target(0)])
111
+ },
112
+ pipelines: {},
113
+ shTarget: gpu.withOwner(
114
+ "gpu",
115
+ () => gpu.device.createTexture({
116
+ label: "environment/sh-target",
117
+ size: [9, 1],
118
+ format: "rgba32float",
119
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
120
+ })
121
+ )
122
+ };
123
+ kernels.set(gpu, k);
124
+ return k;
125
+ }
126
+ function pipeline(world, gpu, k, name) {
127
+ const cached = k.pipelines[name];
128
+ if (cached) return cached;
129
+ const shaders = world.resource(Shaders);
130
+ const fs = shaders.module(gpu, { root: ROOTS[name] });
131
+ const vs = shaders.module(gpu, { root: "shard::fullscreen" });
132
+ if (!fs || !vs) return void 0;
133
+ const made = gpu.pipelines.render({
134
+ label: `env/baseline/${name}`,
135
+ layout: gpu.layouts.pipelineLayout({
136
+ label: `env/baseline/${name}`,
137
+ bindGroupLayouts: [k.layouts[name]]
138
+ }),
139
+ vertex: { module: vs, entryPoint: "vs" },
140
+ fragment: {
141
+ module: fs,
142
+ entryPoint: "main",
143
+ targets: [{ format: name === "sh" ? "rgba32float" : "rgba16float" }]
144
+ }
145
+ });
146
+ if (made) k.pipelines[name] = made;
147
+ return made;
148
+ }
149
+ function faceView(texture, face, mip) {
150
+ return texture.createView({
151
+ dimension: "2d",
152
+ baseArrayLayer: face,
153
+ arrayLayerCount: 1,
154
+ baseMipLevel: mip,
155
+ mipLevelCount: 1
156
+ });
157
+ }
158
+ class Targets {
159
+ data;
160
+ count = 0;
161
+ constructor(slots) {
162
+ this.data = new Uint32Array(slots * SLOT / 4);
163
+ }
164
+ /** Adds a slot for a `size`² face (or a width × 1 row); returns its byte offset. */
165
+ add(width, layer, height = width) {
166
+ const o = this.count * SLOT / 4;
167
+ this.data[o] = width;
168
+ this.data[o + 1] = height;
169
+ this.data[o + 2] = 1;
170
+ this.data[o + 3] = layer;
171
+ return this.count++ * SLOT;
172
+ }
173
+ }
174
+ function encode(ctx, k, draws) {
175
+ for (const d of draws) {
176
+ const pass = ctx.encoder.beginRenderPass({
177
+ label: d.label,
178
+ colorAttachments: [
179
+ { view: d.view, loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 0] }
180
+ ]
181
+ });
182
+ pass.setPipeline(pipeline(ctx.world, ctx.gpu, k, d.kernel));
183
+ pass.setBindGroup(0, d.group, [d.offset]);
184
+ pass.draw(3);
185
+ pass.end();
186
+ }
187
+ draws.length = 0;
188
+ }
189
+ function runBaselineEnvironment(ctx, store) {
190
+ const world = ctx.world;
191
+ const gpu = ctx.gpu;
192
+ if (store.pending.length === 0 && store.lutReady) {
193
+ store.ranFrame = store.frame;
194
+ return;
195
+ }
196
+ const k = kernelsOf(gpu);
197
+ let missing = false;
198
+ for (const name of Object.keys(ROOTS))
199
+ if (!pipeline(world, gpu, k, name)) missing = true;
200
+ if (missing) {
201
+ gpu.pipelines.skipped++;
202
+ return;
203
+ }
204
+ store.ranFrame = store.frame;
205
+ const device = gpu.device;
206
+ const assets = world.resource(GpuAssetsResource);
207
+ const sampler = store.sampler;
208
+ const wrapU = gpu.layouts.sampler({
209
+ label: "environment/equirect",
210
+ magFilter: "linear",
211
+ minFilter: "linear",
212
+ addressModeU: "repeat",
213
+ addressModeV: "clamp-to-edge"
214
+ });
215
+ const targets = new Targets(1 + store.pending.length * (6 + 6 * 10 + 1 + 6 * SPECULAR_MIPS));
216
+ const params = new GpuBuffer(gpu, {
217
+ label: "env/baseline/targets",
218
+ usage: GPUBufferUsage.UNIFORM,
219
+ size: targets.data.byteLength
220
+ });
221
+ const specularData = new Float32Array(SPECULAR_MIPS * SLOT / 4);
222
+ const specularParams = new GpuBuffer(gpu, {
223
+ label: "env/baseline/specular",
224
+ usage: GPUBufferUsage.UNIFORM,
225
+ size: specularData.byteLength
226
+ });
227
+ ctx.afterSubmit(() => {
228
+ params.destroy();
229
+ specularParams.destroy();
230
+ });
231
+ const target = { buffer: params.buffer, size: 16 };
232
+ const draws = [];
233
+ if (!store.lutReady) {
234
+ draws.push({
235
+ label: "environment/brdf-lut",
236
+ view: store.lut.createView(),
237
+ kernel: "lut",
238
+ group: device.createBindGroup({
239
+ layout: k.layouts.lut,
240
+ entries: [{ binding: 0, resource: target }]
241
+ }),
242
+ offset: targets.add(LUT_SIZE, 0)
243
+ });
244
+ store.lutReady = true;
245
+ }
246
+ const done = [];
247
+ for (const env of store.pending) {
248
+ const size = env.size;
249
+ if (env.kind === "map") {
250
+ const input = env.input && assets.texture(env.input);
251
+ if (!input) continue;
252
+ const cube = env.input.faces === 6;
253
+ const group = device.createBindGroup({
254
+ layout: cube ? k.layouts.cube : k.layouts.equirect,
255
+ entries: [
256
+ { binding: 0, resource: input.linear },
257
+ { binding: 1, resource: cube ? sampler : wrapU },
258
+ { binding: 2, resource: target }
259
+ ]
260
+ });
261
+ for (let face = 0; face < 6; face++) {
262
+ draws.push({
263
+ label: "environment/source",
264
+ view: faceView(env.source, face, 0),
265
+ kernel: cube ? "cube" : "equirect",
266
+ group,
267
+ offset: targets.add(size, face)
268
+ });
269
+ }
270
+ } else {
271
+ const bake = baselineBakers.get(env.kind);
272
+ if (!bake) continue;
273
+ encode(ctx, k, draws);
274
+ let ready = true;
275
+ for (let face = 0; face < 6 && ready; face++) {
276
+ ready = bake(ctx, env, face, faceView(env.source, face, 0));
277
+ }
278
+ if (!ready) continue;
279
+ }
280
+ const mips = Math.log2(size) + 1;
281
+ for (let m = 1; m < mips; m++) {
282
+ const group = device.createBindGroup({
283
+ layout: k.layouts.downsample,
284
+ entries: [
285
+ { binding: 0, resource: target },
286
+ {
287
+ binding: 1,
288
+ resource: env.source.createView({
289
+ dimension: "cube",
290
+ baseMipLevel: m - 1,
291
+ mipLevelCount: 1
292
+ })
293
+ },
294
+ { binding: 2, resource: sampler }
295
+ ]
296
+ });
297
+ for (let face = 0; face < 6; face++) {
298
+ draws.push({
299
+ label: "environment/mip",
300
+ view: faceView(env.source, face, m),
301
+ kernel: "downsample",
302
+ group,
303
+ offset: targets.add(Math.max(1, size >> m), face)
304
+ });
305
+ }
306
+ }
307
+ const shLevel = Math.max(0, Math.log2(size) - 5);
308
+ draws.push({
309
+ label: "environment/sh",
310
+ view: k.shTarget.createView(),
311
+ kernel: "sh",
312
+ group: device.createBindGroup({
313
+ layout: k.layouts.sh,
314
+ entries: [
315
+ { binding: 0, resource: target },
316
+ {
317
+ binding: 1,
318
+ resource: env.source.createView({
319
+ dimension: "cube",
320
+ baseMipLevel: shLevel,
321
+ mipLevelCount: 1
322
+ })
323
+ },
324
+ { binding: 2, resource: sampler }
325
+ ]
326
+ }),
327
+ offset: targets.add(Math.max(1, size >> shLevel), 0)
328
+ });
329
+ for (let m = 0; m < SPECULAR_MIPS; m++) {
330
+ const o = m * SLOT / 4;
331
+ specularData[o] = m / (SPECULAR_MIPS - 1);
332
+ specularData[o + 1] = size;
333
+ specularData[o + 2] = SPECULAR_SIZE >> m;
334
+ const group = device.createBindGroup({
335
+ layout: k.layouts.specular,
336
+ entries: [
337
+ { binding: 0, resource: env.sourceView },
338
+ { binding: 1, resource: sampler },
339
+ { binding: 2, resource: target },
340
+ { binding: 3, resource: { buffer: specularParams.buffer, offset: m * SLOT, size: 16 } }
341
+ ]
342
+ });
343
+ for (let face = 0; face < 6; face++) {
344
+ draws.push({
345
+ label: "environment/specular",
346
+ view: faceView(env.specular, face, m),
347
+ kernel: "specular",
348
+ group,
349
+ offset: targets.add(SPECULAR_SIZE >> m, face)
350
+ });
351
+ }
352
+ }
353
+ encode(ctx, k, draws);
354
+ ctx.encoder.copyTextureToBuffer({ texture: k.shTarget }, { buffer: env.sh.gpuBuffer }, [9, 1]);
355
+ env.state = "ready";
356
+ env.bakes++;
357
+ done.push(env);
358
+ }
359
+ encode(ctx, k, draws);
360
+ params.write(targets.data, 0, 0, targets.count * SLOT / 4);
361
+ specularParams.write(specularData);
362
+ for (const env of done) store.pending.splice(store.pending.indexOf(env), 1);
363
+ }
364
+ export {
365
+ BASELINE_ENVIRONMENT_SHADERS,
366
+ baselineBakers,
367
+ runBaselineEnvironment
368
+ };
@@ -0,0 +1,25 @@
1
+ import type { GpuContext } from '@aethervtt/shard-gpu';
2
+ import type { NodeContext } from '../graph.js';
3
+ export declare const METER_WIDTH = 64;
4
+ export declare const METER_HEIGHT = 36;
5
+ export declare const BASELINE_EXPOSURE_SHADERS: Record<string, string>;
6
+ /** A view's meter image and its readbacks in flight. */
7
+ export interface MeterView {
8
+ target: GPUTexture;
9
+ view: GPUTextureView;
10
+ params: GPUBuffer;
11
+ readbacks: GPUBuffer[];
12
+ busy: boolean[];
13
+ generation: number;
14
+ group: GPUBindGroup | undefined;
15
+ groupKey: string;
16
+ }
17
+ export declare function meterView(gpu: GpuContext, name: string, readbacks: number): MeterView;
18
+ export declare function meterLayout(gpu: GpuContext): GPUBindGroupLayout;
19
+ /**
20
+ * Renders the meter image and, when a readback is free, copies it out. Returns the readback that
21
+ * will hold it, or undefined when all are still mapping (this frame isn't metered).
22
+ */
23
+ export declare function renderMeter(ctx: NodeContext, m: MeterView, pipeline: GPURenderPipeline, group: GPUBindGroup, range: Float32Array, glow: Float32Array): number | undefined;
24
+ /** The meter image's samples binned into the full tier's histogram: weight into its EV bin. */
25
+ export declare function binMeter(bytes: ArrayBuffer, minEv: number, binsPerEv: number, bins: Uint32Array): Uint32Array;
@@ -0,0 +1,133 @@
1
+ import { halfToFloat } from "../readback.js";
2
+ const METER_WIDTH = 64;
3
+ const METER_HEIGHT = 36;
4
+ const BASELINE_EXPOSURE_SHADERS = {
5
+ "shard::post::baseline::meter": `
6
+ import shard::view::view;
7
+ import shard::color::luminance;
8
+ @if(BLOOM) import shard::post::glow::add_glow;
9
+
10
+ struct Meter {
11
+ /** EV of bin 0, bins per EV, metering mode, 0. */
12
+ range: vec4f,
13
+ /** The meter image's size. */
14
+ size: vec4f,
15
+ /** Bloom, which the tonemap adds to the image: intensity, level count, mix (1) or add (0). */
16
+ glow: vec4f,
17
+ }
18
+
19
+ @group(0) @binding(1) var input: texture_2d<f32>;
20
+ @group(0) @binding(2) var<uniform> meter: Meter;
21
+ @if(BLOOM) @group(0) @binding(3) var bloom: texture_2d<f32>;
22
+ @if(BLOOM) @group(0) @binding(4) var bloom_sampler: sampler;
23
+
24
+ /** One sample: the EV100 of the input pixel under it, and its metering weight (of 16). */
25
+ @fragment fn main(@builtin(position) p: vec4f) -> @location(0) vec4f {
26
+ let size = textureDimensions(input);
27
+ let px = min(vec2u((floor(p.xy) + 0.5) / meter.size.xy * vec2f(size)), size - 1u);
28
+ var c = textureLoad(input, vec2i(px), 0).rgb;
29
+ // What the tonemap shows: the image with bloom's glow over it.
30
+ @if(BLOOM) {
31
+ let uv = (vec2f(px) + 0.5) / vec2f(size);
32
+ c = add_glow(c, textureSampleLevel(bloom, bloom_sampler, uv, 0.0).rgb, meter.glow);
33
+ }
34
+ let l = luminance(c) / max(view.exposure, 1e-20);
35
+ let ev = log2(max(l, 1e-10) * 8.0);
36
+ let q = vec2f(px) / vec2f(size) * 2.0 - 1.0;
37
+ let r = length(q * vec2f(f32(size.x) / f32(size.y), 1.0)) / 1.4142;
38
+ var w = 16.0;
39
+ if (meter.range.z == 1.0) { w = round(16.0 * exp(-r * r / 0.18)); }
40
+ if (meter.range.z == 2.0) { w = select(0.0, 16.0, r < 0.1); }
41
+ return vec4f(ev, w, 0.0, 1.0);
42
+ }`
43
+ };
44
+ const BYTES = METER_WIDTH * METER_HEIGHT * 4;
45
+ function meterView(gpu, name, readbacks) {
46
+ const target = gpu.device.createTexture({
47
+ label: `${name}/exposure-meter`,
48
+ size: [METER_WIDTH, METER_HEIGHT],
49
+ format: "rg16float",
50
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
51
+ });
52
+ return {
53
+ target,
54
+ view: target.createView(),
55
+ params: gpu.device.createBuffer({
56
+ label: `${name}/exposure-meter`,
57
+ size: 48,
58
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
59
+ }),
60
+ readbacks: Array.from(
61
+ { length: readbacks },
62
+ (_, i) => gpu.device.createBuffer({
63
+ label: `${name}/exposure-readback-${i}`,
64
+ size: BYTES,
65
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
66
+ })
67
+ ),
68
+ busy: Array.from({ length: readbacks }, () => false),
69
+ generation: gpu.generation,
70
+ group: void 0,
71
+ groupKey: ""
72
+ };
73
+ }
74
+ function meterLayout(gpu) {
75
+ const F = GPUShaderStage.FRAGMENT;
76
+ return gpu.layouts.bindGroupLayout({
77
+ label: "exposure/baseline",
78
+ entries: [
79
+ { binding: 0, visibility: F, buffer: { type: "uniform" } },
80
+ { binding: 1, visibility: F, texture: { sampleType: "unfilterable-float" } },
81
+ { binding: 2, visibility: F, buffer: { type: "uniform" } },
82
+ { binding: 3, visibility: F, texture: { sampleType: "float" } },
83
+ { binding: 4, visibility: F, sampler: { type: "filtering" } }
84
+ ]
85
+ });
86
+ }
87
+ const params = new Float32Array(12);
88
+ function renderMeter(ctx, m, pipeline, group, range, glow) {
89
+ params.set(range, 0);
90
+ params[4] = METER_WIDTH;
91
+ params[5] = METER_HEIGHT;
92
+ params.set(glow, 8);
93
+ ctx.gpu.device.queue.writeBuffer(m.params, 0, params);
94
+ const pass = ctx.encoder.beginRenderPass({
95
+ label: `${ctx.view.name}/exposure`,
96
+ colorAttachments: [
97
+ { view: m.view, loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 0] }
98
+ ]
99
+ });
100
+ pass.setPipeline(pipeline);
101
+ pass.setBindGroup(0, group);
102
+ pass.draw(3);
103
+ pass.end();
104
+ const k = m.busy.indexOf(false);
105
+ if (k < 0) return void 0;
106
+ ctx.encoder.copyTextureToBuffer(
107
+ { texture: m.target },
108
+ { buffer: m.readbacks[k], bytesPerRow: METER_WIDTH * 4 },
109
+ [METER_WIDTH, METER_HEIGHT]
110
+ );
111
+ m.busy[k] = true;
112
+ return k;
113
+ }
114
+ function binMeter(bytes, minEv, binsPerEv, bins) {
115
+ bins.fill(0);
116
+ const h = new Uint16Array(bytes);
117
+ for (let i = 0; i < METER_WIDTH * METER_HEIGHT; i++) {
118
+ const w = halfToFloat(h[i * 2 + 1]);
119
+ if (w <= 0) continue;
120
+ const ev = halfToFloat(h[i * 2]);
121
+ bins[Math.min(255, Math.max(0, Math.floor((ev - minEv) * binsPerEv)))] += w;
122
+ }
123
+ return bins;
124
+ }
125
+ export {
126
+ BASELINE_EXPOSURE_SHADERS,
127
+ METER_HEIGHT,
128
+ METER_WIDTH,
129
+ binMeter,
130
+ meterLayout,
131
+ meterView,
132
+ renderMeter
133
+ };
@@ -0,0 +1,33 @@
1
+ import type { GpuContext } from '@aethervtt/shard-gpu';
2
+ import { type ViewLightList } from '../clusters.js';
3
+ import { DataStore } from '../data-store.js';
4
+ import type { CameraData } from '../view.js';
5
+ /** Lights a view's uniform array holds on baseline (the bitmask's width). */
6
+ export declare const BASELINE_MAX_LIGHTS = 128;
7
+ export interface BaselineViewLights {
8
+ /** The packed light records (`@data(uniform, 128)` at binding 1). */
9
+ lights: DataStore;
10
+ /** Four u32 of light bits per cluster (`@data` at binding 2). */
11
+ bits: DataStore;
12
+ packed: Float32Array;
13
+ /** The light store slot of each packed light. */
14
+ slots: Uint32Array;
15
+ words: Uint32Array;
16
+ /** What the AABBs were made for, and the AABBs (8 floats per cluster). */
17
+ aabbKey: string;
18
+ aabbs: Float32Array;
19
+ /** Lights past the budget last frame (dropped, farthest first). */
20
+ dropped: number;
21
+ /** Lights in view last frame, dropped ones included. */
22
+ inView: number;
23
+ /** `dropped` as last reported to RenderHealth. */
24
+ reported: number;
25
+ count: number;
26
+ }
27
+ export declare function baselineViewLights(gpu: GpuContext, name: string): BaselineViewLights;
28
+ /**
29
+ * Packs the view's visible lights (the nearest `max`, at most BASELINE_MAX_LIGHTS, when there are
30
+ * more) and sets each one's bit in every cluster it touches. `records` is the light store's data
31
+ * (LIGHT_FLOATS a slot); `list` the view's visible lights in view space.
32
+ */
33
+ export declare function binBaselineLights(v: BaselineViewLights, list: ViewLightList, records: Float32Array, cam: CameraData, clusterFar: number, max: number): void;
@@ -0,0 +1,133 @@
1
+ import {
2
+ CLUSTER_COUNT,
3
+ CLUSTER_X,
4
+ CLUSTER_Y,
5
+ CLUSTER_Z,
6
+ clusterAabbs,
7
+ clusterKey,
8
+ clusterRange,
9
+ lightTouchesCluster,
10
+ VIEW_LIGHT_FLOATS
11
+ } from "../clusters.js";
12
+ import { DataStore } from "../data-store.js";
13
+ import { LIGHT_FLOATS } from "../lights.js";
14
+ const BASELINE_MAX_LIGHTS = 128;
15
+ function baselineViewLights(gpu, name) {
16
+ return {
17
+ lights: new DataStore(gpu, {
18
+ label: `${name}/lights`,
19
+ kind: "uniform",
20
+ size: BASELINE_MAX_LIGHTS * LIGHT_FLOATS * 4
21
+ }),
22
+ bits: new DataStore(gpu, { label: `${name}/cluster-bits`, size: CLUSTER_COUNT * 16 }),
23
+ packed: new Float32Array(BASELINE_MAX_LIGHTS * LIGHT_FLOATS),
24
+ slots: new Uint32Array(BASELINE_MAX_LIGHTS),
25
+ words: new Uint32Array(CLUSTER_COUNT * 4),
26
+ aabbKey: "",
27
+ aabbs: new Float32Array(CLUSTER_COUNT * 8),
28
+ dropped: 0,
29
+ inView: 0,
30
+ reported: 0,
31
+ count: 0
32
+ };
33
+ }
34
+ const box = new Float32Array(6);
35
+ const order = new Uint32Array(4096);
36
+ const keys = new Float64Array(4096);
37
+ function binBaselineLights(v, list, records, cam, clusterFar, max) {
38
+ const budget = Math.max(0, Math.min(max, BASELINE_MAX_LIGHTS));
39
+ const key = clusterKey(cam, clusterFar);
40
+ if (key !== v.aabbKey) {
41
+ v.aabbKey = key;
42
+ clusterAabbs(v.aabbs, cam, clusterFar);
43
+ }
44
+ const light = list.data;
45
+ const u32 = list.u32;
46
+ let n = Math.min(list.count, order.length);
47
+ v.inView = n;
48
+ for (let i = 0; i < n; i++) order[i] = i;
49
+ if (n > budget) {
50
+ for (let i = 0; i < keys.length; i++) {
51
+ if (i >= n) {
52
+ keys[i] = Number.POSITIVE_INFINITY;
53
+ continue;
54
+ }
55
+ const o = 4 + i * VIEW_LIGHT_FLOATS;
56
+ const x = light[o];
57
+ const y = light[o + 1];
58
+ const z = light[o + 2];
59
+ const d = Math.max(0, Math.sqrt(x * x + y * y + z * z) - light[o + 3]);
60
+ keys[i] = Math.floor(Math.min(d, 1e9) * 256) * 4096 + i;
61
+ }
62
+ keys.sort();
63
+ for (let i = 0; i < n; i++) order[i] = keys[i] % 4096;
64
+ }
65
+ v.dropped = Math.max(0, n - budget);
66
+ n = Math.min(n, budget);
67
+ v.count = n;
68
+ v.words.fill(0);
69
+ const [near, far] = clusterRange(cam, clusterFar);
70
+ const logRatio = Math.log(far / near);
71
+ for (let k = 0; k < n; k++) {
72
+ const i = order[k];
73
+ const o = 4 + i * VIEW_LIGHT_FLOATS;
74
+ const slot = u32[o + 8];
75
+ v.slots[k] = slot;
76
+ v.packed.set(records.subarray(slot * LIGHT_FLOATS, (slot + 1) * LIGHT_FLOATS), k * LIGHT_FLOATS);
77
+ const px = light[o];
78
+ const py = light[o + 1];
79
+ const pz = light[o + 2];
80
+ const range = light[o + 3];
81
+ const d0 = Math.max(near, -pz - range);
82
+ const d1 = -pz + range;
83
+ if (d1 < near) continue;
84
+ const z0 = Math.max(0, Math.floor(Math.log(d0 / near) / logRatio * CLUSTER_Z) - 1);
85
+ const z1 = Math.min(CLUSTER_Z - 1, Math.floor(Math.log(d1 / near) / logRatio * CLUSTER_Z) + 1);
86
+ const p = cam.proj;
87
+ let nx0 = Number.POSITIVE_INFINITY;
88
+ let nx1 = Number.NEGATIVE_INFINITY;
89
+ let ny0 = Number.POSITIVE_INFINITY;
90
+ let ny1 = Number.NEGATIVE_INFINITY;
91
+ for (let e = 0; e < 4; e++) {
92
+ const x = e & 1 ? px + range : px - range;
93
+ const y = e & 1 ? py + range : py - range;
94
+ for (let q = 0; q < 2; q++) {
95
+ const d = q ? d1 : d0;
96
+ const ndcX = cam.orthographic ? x * p[0] + p[12] : x * p[0] / d;
97
+ const ndcY = cam.orthographic ? y * p[5] + p[13] : y * p[5] / d;
98
+ if (ndcX < nx0) nx0 = ndcX;
99
+ if (ndcX > nx1) nx1 = ndcX;
100
+ if (ndcY < ny0) ny0 = ndcY;
101
+ if (ndcY > ny1) ny1 = ndcY;
102
+ }
103
+ }
104
+ const x0 = Math.max(0, Math.floor((nx0 + 1) / 2 * CLUSTER_X) - 1);
105
+ const x1 = Math.min(CLUSTER_X - 1, Math.floor((nx1 + 1) / 2 * CLUSTER_X) + 1);
106
+ const y0 = Math.max(0, Math.floor((1 - ny1) / 2 * CLUSTER_Y) - 1);
107
+ const y1 = Math.min(CLUSTER_Y - 1, Math.floor((1 - ny0) / 2 * CLUSTER_Y) + 1);
108
+ const word = k >> 5;
109
+ const bit = 1 << (k & 31);
110
+ for (let z = z0; z <= z1; z++) {
111
+ for (let y = y0; y <= y1; y++) {
112
+ for (let x = x0; x <= x1; x++) {
113
+ const c = (z * CLUSTER_Y + y) * CLUSTER_X + x;
114
+ const a = c * 8;
115
+ box[0] = v.aabbs[a];
116
+ box[1] = v.aabbs[a + 1];
117
+ box[2] = v.aabbs[a + 2];
118
+ box[3] = v.aabbs[a + 4];
119
+ box[4] = v.aabbs[a + 5];
120
+ box[5] = v.aabbs[a + 6];
121
+ if (lightTouchesCluster(box, light, o)) v.words[c * 4 + word] |= bit;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ v.lights.write(v.packed, 0, 0, Math.max(1, n) * LIGHT_FLOATS);
127
+ v.bits.write(v.words);
128
+ }
129
+ export {
130
+ BASELINE_MAX_LIGHTS,
131
+ baselineViewLights,
132
+ binBaselineLights
133
+ };