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