@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/plugin.js
CHANGED
|
@@ -1,20 +1,39 @@
|
|
|
1
|
+
import { AssetServerResource } from "@aethervtt/shard-assets";
|
|
1
2
|
import {
|
|
2
3
|
defineEvent,
|
|
3
4
|
defineResource,
|
|
4
5
|
defineSystem,
|
|
5
6
|
defineSystemSet,
|
|
6
7
|
Last,
|
|
8
|
+
ProfilerResource,
|
|
7
9
|
ShardError
|
|
8
10
|
} from "@aethervtt/shard-core";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
+
import {
|
|
12
|
+
createGpuContext
|
|
13
|
+
} from "@aethervtt/shard-gpu";
|
|
14
|
+
import {
|
|
15
|
+
definePlugin,
|
|
16
|
+
FrameDemand,
|
|
17
|
+
LOADING_DEMAND,
|
|
18
|
+
LogResource,
|
|
19
|
+
PerfProviders
|
|
20
|
+
} from "@aethervtt/shard-runtime";
|
|
11
21
|
import { ShaderLibrary } from "@aethervtt/shard-shader";
|
|
22
|
+
import { PassCosts } from "./ablation.js";
|
|
23
|
+
import { loadDataTextures, releaseDataStores } from "./data-store.js";
|
|
24
|
+
import { checkUnsupportedFeatures, describeFeatures, RenderFeatures } from "./features.js";
|
|
12
25
|
import { RenderGraph } from "./graph.js";
|
|
26
|
+
import {
|
|
27
|
+
healthSystem,
|
|
28
|
+
RenderHealth,
|
|
29
|
+
RenderHealthChanged,
|
|
30
|
+
RenderHealthReports,
|
|
31
|
+
raiseHealthIssue
|
|
32
|
+
} from "./health.js";
|
|
13
33
|
import { registerEngineShaders } from "./shaders.js";
|
|
14
34
|
import { GpuMemory, RenderCounters, RenderStats } from "./stats.js";
|
|
15
|
-
import { WindowTarget } from "./target.js";
|
|
16
35
|
const Gpu = defineResource("render/Gpu", {
|
|
17
|
-
description: "The GPU device,
|
|
36
|
+
description: "The GPU device, its surfaces (canvases), and caches. Apps can share one."
|
|
18
37
|
});
|
|
19
38
|
const Graph = defineResource("render/Graph", {
|
|
20
39
|
description: "Render graph nodes, run for every view each frame."
|
|
@@ -26,7 +45,7 @@ const Views = defineResource("render/Views", {
|
|
|
26
45
|
description: "Views to render this frame. Rebuilt every frame by extract systems."
|
|
27
46
|
});
|
|
28
47
|
const Window = defineResource("render/Window", {
|
|
29
|
-
description: "Where cameras without a target render: the canvas
|
|
48
|
+
description: "Where cameras without a target render: the app's surface (canvas), or an offscreen target when headless."
|
|
30
49
|
});
|
|
31
50
|
const GpuDeviceLost = defineEvent(
|
|
32
51
|
"render/GpuDeviceLost",
|
|
@@ -51,12 +70,37 @@ const begin = defineSystem({
|
|
|
51
70
|
world.resource(Gpu).pipelines.skipped = 0;
|
|
52
71
|
}
|
|
53
72
|
});
|
|
73
|
+
const markMissing = defineSystem({
|
|
74
|
+
name: "render/mark-missing",
|
|
75
|
+
description: "Puts MissingAsset on entities that reference an asset showing a fallback.",
|
|
76
|
+
run: (_, world) => world.tryResource(AssetServerResource)?.markMissing()
|
|
77
|
+
});
|
|
78
|
+
const checkUnsupported = defineSystem({
|
|
79
|
+
name: "render/check-unsupported",
|
|
80
|
+
description: "On the baseline tier: reports render features the scene uses that it can't run (render/feature-unsupported), and WebGL2 shaders translated at load (render/shader-cache-miss).",
|
|
81
|
+
setup: () => ({ misses: 0 }),
|
|
82
|
+
run: (state, world) => {
|
|
83
|
+
const gpu = world.tryResource(Gpu);
|
|
84
|
+
if (gpu?.tier !== "baseline") return;
|
|
85
|
+
checkUnsupportedFeatures(world);
|
|
86
|
+
const cache = gpu.shaderCache();
|
|
87
|
+
if (!cache || cache.misses.length === state.misses) return;
|
|
88
|
+
state.misses = cache.misses.length;
|
|
89
|
+
let ms = 0;
|
|
90
|
+
for (const m of cache.misses) ms += m.ms;
|
|
91
|
+
const load = cache.nagaLoadMs === void 0 ? "" : `, ${cache.nagaLoadMs.toFixed(0)} ms to load it`;
|
|
92
|
+
raiseHealthIssue(world, {
|
|
93
|
+
code: "render/shader-cache-miss",
|
|
94
|
+
severity: "info",
|
|
95
|
+
message: `${state.misses} shader stage${state.misses === 1 ? "" : "s"} translated to GLSL at load (${ms.toFixed(0)} ms in naga${load}): \`shard shaders bake\` ships them translated`
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
54
99
|
const execute = defineSystem({
|
|
55
100
|
name: "render/execute-graph",
|
|
56
101
|
description: "Runs the render graph for every view and submits.",
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (recovering.value) return;
|
|
102
|
+
run: (_, world) => {
|
|
103
|
+
if (world.resource(Gpu).status !== "ok") return;
|
|
60
104
|
const views = world.resource(Views).list;
|
|
61
105
|
const window = world.tryResource(Window);
|
|
62
106
|
const options = world.resource(RenderOptions);
|
|
@@ -65,13 +109,35 @@ const execute = defineSystem({
|
|
|
65
109
|
}
|
|
66
110
|
if (views.length === 0) return;
|
|
67
111
|
world.resource(Graph).execute(world, views);
|
|
112
|
+
world.tryResource(RenderStats)?.endFrame(world.resource(Gpu).uploads(options.owner));
|
|
113
|
+
const gpu = world.resource(Gpu);
|
|
114
|
+
anyPending = gpu.pipelines.skipped > 0 || gpu.pipelines.pending > 0;
|
|
115
|
+
if (!anyPending) world.tryResource(RenderStats)?.forEach(notePending);
|
|
116
|
+
world.tryResource(FrameDemand)?.set(LOADING_DEMAND, anyPending);
|
|
68
117
|
}
|
|
69
118
|
});
|
|
119
|
+
let anyPending = false;
|
|
120
|
+
function notePending(stats) {
|
|
121
|
+
if (stats.pending > 0) anyPending = true;
|
|
122
|
+
}
|
|
70
123
|
const RenderDescribers = defineResource(
|
|
71
124
|
"render/Describers",
|
|
72
125
|
{ description: "Sections plugins add to render.describe.", init: () => /* @__PURE__ */ new Map() }
|
|
73
126
|
);
|
|
74
|
-
const RenderOptions = defineResource("render/Options"
|
|
127
|
+
const RenderOptions = defineResource("render/Options", {
|
|
128
|
+
description: "The render plugin's settings: the fallback window view, and the app's GPU owner."
|
|
129
|
+
});
|
|
130
|
+
const renderApps = /* @__PURE__ */ new WeakMap();
|
|
131
|
+
let nextOwner = 1;
|
|
132
|
+
function nextRefresh() {
|
|
133
|
+
return new Promise((resolve) => requestAnimationFrame(resolve));
|
|
134
|
+
}
|
|
135
|
+
function presentOn(state) {
|
|
136
|
+
return () => {
|
|
137
|
+
const done = state.gpu.device.queue.onSubmittedWorkDone();
|
|
138
|
+
return typeof requestAnimationFrame === "function" ? done.then(nextRefresh) : done.then(() => void 0);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
75
141
|
function renderPlugin(options = {}) {
|
|
76
142
|
return definePlugin({
|
|
77
143
|
name: "render",
|
|
@@ -79,8 +145,13 @@ function renderPlugin(options = {}) {
|
|
|
79
145
|
// graph and device
|
|
80
146
|
Gpu,
|
|
81
147
|
GpuDeviceLost,
|
|
148
|
+
PassCosts,
|
|
82
149
|
Graph,
|
|
83
150
|
RenderDescribers,
|
|
151
|
+
RenderFeatures,
|
|
152
|
+
RenderHealth,
|
|
153
|
+
RenderHealthChanged,
|
|
154
|
+
RenderHealthReports,
|
|
84
155
|
RenderOptions,
|
|
85
156
|
Shaders,
|
|
86
157
|
Views,
|
|
@@ -88,40 +159,130 @@ function renderPlugin(options = {}) {
|
|
|
88
159
|
],
|
|
89
160
|
dependencies: ["core/time"],
|
|
90
161
|
build(app) {
|
|
91
|
-
|
|
162
|
+
const state = {
|
|
163
|
+
owner: options.owner ?? `render:${nextOwner++}`,
|
|
164
|
+
gpu: void 0,
|
|
165
|
+
ownsDevice: false,
|
|
166
|
+
surface: void 0,
|
|
167
|
+
saved: [],
|
|
168
|
+
unsubscribe: []
|
|
169
|
+
};
|
|
170
|
+
renderApps.set(app, state);
|
|
171
|
+
app.addScope({
|
|
172
|
+
enter() {
|
|
173
|
+
const gpu = state.gpu;
|
|
174
|
+
state.saved.push(gpu?.owner);
|
|
175
|
+
if (gpu) gpu.owner = state.owner;
|
|
176
|
+
},
|
|
177
|
+
exit() {
|
|
178
|
+
const previous = state.saved.pop();
|
|
179
|
+
if (state.gpu && previous !== void 0) state.gpu.owner = previous;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
app.insertResource(Views, { list: [] }).insertResource(RenderOptions, {
|
|
183
|
+
windowView: options.windowView ?? true,
|
|
184
|
+
owner: state.owner
|
|
185
|
+
}).configureSets(
|
|
92
186
|
Last,
|
|
93
187
|
RenderSet.Extract.after(RenderSet.Begin),
|
|
94
188
|
RenderSet.Prepare.after(RenderSet.Extract),
|
|
95
189
|
RenderSet.Queue.after(RenderSet.Prepare),
|
|
96
190
|
RenderSet.Upload.after(RenderSet.Queue),
|
|
97
191
|
RenderSet.Graph.after(RenderSet.Upload)
|
|
98
|
-
).addSystems(
|
|
192
|
+
).addSystems(
|
|
193
|
+
Last,
|
|
194
|
+
begin.inSet(RenderSet.Begin),
|
|
195
|
+
markMissing.inSet(RenderSet.Begin).after(begin),
|
|
196
|
+
checkUnsupported.inSet(RenderSet.Begin).after(begin),
|
|
197
|
+
execute.inSet(RenderSet.Graph),
|
|
198
|
+
healthSystem({ status: () => state.gpu?.status ?? "ok" }).inSet(RenderSet.Graph).after(execute)
|
|
199
|
+
);
|
|
99
200
|
},
|
|
100
201
|
async ready(app) {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
202
|
+
const state = renderApps.get(app);
|
|
203
|
+
const given = options.gpu ?? options.surface?.gpu;
|
|
204
|
+
if (options.surface && options.gpu && options.surface.gpu !== options.gpu) {
|
|
205
|
+
throw new ShardError("render/surface-device", "The surface belongs to another GpuContext", {
|
|
206
|
+
hint: "Pass the GpuContext the surface was added to (surface.gpu), or omit gpu."
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const gpu = given ?? await createGpuContext({
|
|
210
|
+
features: options.features,
|
|
211
|
+
backend: options.backend,
|
|
212
|
+
webgl2: options.webgl2,
|
|
213
|
+
tier: options.tier,
|
|
214
|
+
canvas: options.canvas,
|
|
215
|
+
alpha: options.alpha
|
|
216
|
+
});
|
|
217
|
+
state.gpu = gpu;
|
|
218
|
+
state.ownsDevice = !given;
|
|
219
|
+
if (gpu.tier === "baseline") await loadDataTextures();
|
|
220
|
+
gpu.withOwner(state.owner, () => {
|
|
221
|
+
app.insertResource(Gpu, gpu);
|
|
222
|
+
app.insertResource(Graph, new RenderGraph(gpu));
|
|
223
|
+
});
|
|
224
|
+
const providers = app.world.initResource(PerfProviders);
|
|
225
|
+
providers.gpuMemory = gpuMemory;
|
|
226
|
+
providers.adapter = adapterIdentity;
|
|
227
|
+
const profiler = app.world.tryResource(ProfilerResource);
|
|
228
|
+
if (profiler) {
|
|
229
|
+
profiler.gpu.status = gpu.features.has("timestamp-query") ? "available" : "unavailable";
|
|
230
|
+
}
|
|
231
|
+
app.setPresenter(presentOn(state));
|
|
104
232
|
const shaders = options.shaders ?? new ShaderLibrary();
|
|
105
233
|
if (!options.shaders) registerEngineShaders(shaders);
|
|
106
234
|
if (options.shaderBake) shaders.preload(options.shaderBake);
|
|
107
235
|
app.insertResource(Shaders, shaders);
|
|
108
|
-
|
|
109
|
-
|
|
236
|
+
const surface = options.surface ?? (options.canvas ? gpu.surfaces.find((s) => s.canvas === options.canvas) ?? gpu.addSurface(options.canvas, { alpha: options.alpha }) : void 0);
|
|
237
|
+
state.surface = surface;
|
|
238
|
+
if (surface) {
|
|
239
|
+
app.insertResource(Window, surface);
|
|
240
|
+
state.unsubscribe.push(surface.onResize(() => app.disposed || app.requestFrame()));
|
|
241
|
+
} else if (options.target) app.insertResource(Window, options.target);
|
|
110
242
|
const log = app.world.tryResource(LogResource);
|
|
111
|
-
if (log) gpu.onError((error) => log.error(error));
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
243
|
+
if (log) state.unsubscribe.push(gpu.onError((error) => log.error(error)));
|
|
244
|
+
state.unsubscribe.push(
|
|
245
|
+
gpu.onDeviceLost((info) => {
|
|
246
|
+
app.world.send(GpuDeviceLost, info);
|
|
247
|
+
gpu.recreate().then(
|
|
248
|
+
() => app.disposed || app.requestFrame(),
|
|
249
|
+
() => app.disposed || app.requestFrame()
|
|
250
|
+
);
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
beforeDispose(app) {
|
|
255
|
+
const gpu = renderApps.get(app)?.gpu;
|
|
256
|
+
if (gpu?.status === "ok") return gpu.device.queue.onSubmittedWorkDone();
|
|
257
|
+
},
|
|
258
|
+
dispose(app) {
|
|
259
|
+
const state = renderApps.get(app);
|
|
260
|
+
if (!state) return;
|
|
261
|
+
renderApps.delete(app);
|
|
262
|
+
for (const off of state.unsubscribe.splice(0)) off();
|
|
263
|
+
app.world.tryResource(Graph)?.dispose();
|
|
264
|
+
state.surface?.remove();
|
|
265
|
+
const gpu = state.gpu;
|
|
266
|
+
if (!gpu) return;
|
|
267
|
+
releaseDataStores(gpu, state.owner);
|
|
268
|
+
gpu.release(state.owner);
|
|
269
|
+
if (state.ownsDevice) gpu.destroy();
|
|
116
270
|
}
|
|
117
271
|
});
|
|
118
272
|
}
|
|
273
|
+
function renderOwner(world) {
|
|
274
|
+
return world.resource(RenderOptions).owner;
|
|
275
|
+
}
|
|
119
276
|
function captureView(world, view = WINDOW_VIEW) {
|
|
120
|
-
|
|
277
|
+
const shot = world.resource(Graph).capture(view);
|
|
278
|
+
world.wake();
|
|
279
|
+
return shot;
|
|
121
280
|
}
|
|
122
281
|
async function captureBuffer(world, view, buffer) {
|
|
123
282
|
const graph = world.resource(Graph);
|
|
124
|
-
const
|
|
283
|
+
const pending = graph.capture(view, buffer);
|
|
284
|
+
world.wake();
|
|
285
|
+
const result = await pending;
|
|
125
286
|
if (!("format" in result)) {
|
|
126
287
|
const data = new Float32Array(result.data.length);
|
|
127
288
|
for (let i = 0; i < data.length; i++) data[i] = result.data[i] / 255;
|
|
@@ -136,6 +297,20 @@ async function captureBuffer(world, view, buffer) {
|
|
|
136
297
|
}
|
|
137
298
|
return result;
|
|
138
299
|
}
|
|
300
|
+
function adapterIdentity(world) {
|
|
301
|
+
const info = world.tryResource(Gpu)?.adapter?.info;
|
|
302
|
+
if (!info) return void 0;
|
|
303
|
+
return {
|
|
304
|
+
vendor: info.vendor,
|
|
305
|
+
architecture: info.architecture,
|
|
306
|
+
device: info.device,
|
|
307
|
+
description: info.description
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function gpuMemory(world) {
|
|
311
|
+
const memory = world.tryResource(Gpu)?.memory();
|
|
312
|
+
return memory ? { bytes: memory.bytes, byCategory: { ...memory.byCategory } } : { bytes: 0, byCategory: {} };
|
|
313
|
+
}
|
|
139
314
|
function describeRender(world) {
|
|
140
315
|
const graph = world.tryResource(Graph);
|
|
141
316
|
const gpu = world.tryResource(Gpu);
|
|
@@ -158,8 +333,29 @@ function describeRender(world) {
|
|
|
158
333
|
gpuTimings: gpu.features.has("timestamp-query"),
|
|
159
334
|
stats: Object.fromEntries(world.tryResource(RenderStats) ?? []),
|
|
160
335
|
counters: { ...world.tryResource(RenderCounters) ?? { taaResets: 0, originShifts: 0 } },
|
|
161
|
-
|
|
162
|
-
|
|
336
|
+
// The ledger's total, so it agrees with perf.describe and perf records; and the textures the
|
|
337
|
+
// forward renderer uploaded, as before.
|
|
338
|
+
memory: {
|
|
339
|
+
...gpuMemory(world),
|
|
340
|
+
...world.tryResource(GpuMemory) ?? { textures: 0, textureBytes: 0 }
|
|
341
|
+
},
|
|
342
|
+
recentErrors: gpu.errors.slice(-5).map((e) => e.toJSON()),
|
|
343
|
+
features: describeFeatures(world),
|
|
344
|
+
health: world.tryResource(RenderHealth) ?? { state: "ok", issues: [] },
|
|
345
|
+
// The API and tier the device runs (0064), what it can do, and why a better option was skipped.
|
|
346
|
+
backend: gpu.backend,
|
|
347
|
+
tier: gpu.tier,
|
|
348
|
+
capabilities: gpu.capabilities,
|
|
349
|
+
// WebGL2: where shader translations came from, and what naga translated this session.
|
|
350
|
+
shaderCache: gpu.shaderCache(),
|
|
351
|
+
limits: {
|
|
352
|
+
maxTextureDimension2D: gpu.device.limits.maxTextureDimension2D,
|
|
353
|
+
maxColorAttachments: gpu.device.limits.maxColorAttachments,
|
|
354
|
+
maxSampledTexturesPerShaderStage: gpu.device.limits.maxSampledTexturesPerShaderStage,
|
|
355
|
+
maxInterStageShaderVariables: gpu.device.limits.maxInterStageShaderVariables,
|
|
356
|
+
maxUniformBufferBindingSize: gpu.device.limits.maxUniformBufferBindingSize
|
|
357
|
+
},
|
|
358
|
+
reasons: gpu.reasons
|
|
163
359
|
};
|
|
164
360
|
const sections = {};
|
|
165
361
|
for (const [name, fn] of world.tryResource(RenderDescribers) ?? []) sections[name] = fn(world);
|
|
@@ -178,5 +374,7 @@ export {
|
|
|
178
374
|
captureBuffer,
|
|
179
375
|
captureView,
|
|
180
376
|
describeRender,
|
|
377
|
+
gpuMemory,
|
|
378
|
+
renderOwner,
|
|
181
379
|
renderPlugin
|
|
182
380
|
};
|
package/dist/post-common.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
|
|
2
2
|
import { type ViewGpu } from './forward.js';
|
|
3
|
-
import type { NodeContext } from './graph.js';
|
|
3
|
+
import type { NodeContext, RenderView } from './graph.js';
|
|
4
4
|
import { type CameraData } from './view.js';
|
|
5
5
|
export declare function idOf(o: object): number;
|
|
6
6
|
export declare const F: () => number;
|
|
@@ -11,6 +11,8 @@ export declare const tex: (binding: number, sampleType?: GPUTextureSampleType) =
|
|
|
11
11
|
sampleType: GPUTextureSampleType;
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
|
+
/** Depth read without comparison, in the fragment stage (a float texture on baseline, 0064). */
|
|
15
|
+
export declare const depthTex: (gpu: GpuContext, binding: number) => GPUBindGroupLayoutEntry;
|
|
14
16
|
export declare const uniform: (binding: number, visibility?: number) => {
|
|
15
17
|
binding: number;
|
|
16
18
|
visibility: number;
|
|
@@ -35,7 +37,7 @@ export declare class PostCache {
|
|
|
35
37
|
check(gpu: GpuContext): void;
|
|
36
38
|
/** A fullscreen render pipeline: `shard::fullscreen` vertex stage and `root`'s `entry`. */
|
|
37
39
|
render(ctx: NodeContext, key: string, root: string, entry: string, layouts: GPUBindGroupLayout[], targets: GPUColorTargetState[], defines?: Record<string, boolean>): GPURenderPipeline | undefined;
|
|
38
|
-
compute(ctx: NodeContext, key: string, root: string, entry: string, layouts: GPUBindGroupLayout[]): GPUComputePipeline | undefined;
|
|
40
|
+
compute(ctx: NodeContext, key: string, root: string, entry: string, layouts: GPUBindGroupLayout[], defines?: Record<string, boolean>): GPUComputePipeline | undefined;
|
|
39
41
|
/** A bind group, rebuilt when `key` (the ids of what it binds) changes. */
|
|
40
42
|
group(gpu: GpuContext, slot: string, key: string, layout: GPUBindGroupLayout, entries: () => GPUBindGroupEntry[]): GPUBindGroup;
|
|
41
43
|
buffer(gpu: GpuContext, name: string, size: number): GpuBuffer;
|
|
@@ -48,4 +50,22 @@ export declare function forwardView(ctx: NodeContext): {
|
|
|
48
50
|
/** A fullscreen pass the node begins itself (raw nodes with several passes). */
|
|
49
51
|
export declare function beginPass(ctx: NodeContext, name: string, view: GPUTextureView, load?: boolean): GPURenderPassEncoder;
|
|
50
52
|
export declare const HDR: GPUColorTargetState[];
|
|
53
|
+
/** Color targets for a post pass that writes `texture`: the chain's format, per view. */
|
|
54
|
+
export declare function targetsFor(texture: GPUTexture): GPUColorTargetState[];
|
|
55
|
+
/**
|
|
56
|
+
* The format of the post chain's full-resolution ping-pong (post-a, post-b) for a view:
|
|
57
|
+
* rg11b10ufloat, half the bytes of rgba16float, where the device renders it, the view has no alpha
|
|
58
|
+
* to carry (0052's transparent views), and the device's rounding is known (`rg11Truncates`);
|
|
59
|
+
* rgba16float otherwise, and until then.
|
|
60
|
+
*/
|
|
61
|
+
export declare function postFormat(gpu: GpuContext, view: RenderView): GPUTextureFormat;
|
|
62
|
+
/** Whether a pass writing `texture` rounds its output in the shader (the RG11_ROUND define). */
|
|
63
|
+
export declare function roundsRg11(gpu: GpuContext, texture: GPUTexture): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Whether the device truncates when it stores rg11b10ufloat (Metal does; the format leaves the
|
|
66
|
+
* rounding to the device). Post passes writing it then round in the shader (`rg11_round`, the
|
|
67
|
+
* RG11_ROUND define). Probed once per device, rendering 1 + 0.75 of a step and reading it back;
|
|
68
|
+
* undefined until the answer lands.
|
|
69
|
+
*/
|
|
70
|
+
export declare function rg11Truncates(gpu: GpuContext): boolean | undefined;
|
|
51
71
|
export declare const scratch: Float32Array<ArrayBuffer>;
|
package/dist/post-common.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GpuBuffer } from "@aethervtt/shard-gpu";
|
|
2
2
|
import { ForwardStateResource } from "./forward.js";
|
|
3
3
|
import { Shaders } from "./plugin.js";
|
|
4
|
+
import { depthReadEntry } from "./tier.js";
|
|
4
5
|
import { cameraOf } from "./view.js";
|
|
5
6
|
const textureIds = /* @__PURE__ */ new WeakMap();
|
|
6
7
|
let nextId = 1;
|
|
@@ -18,6 +19,7 @@ const tex = (binding, sampleType = "float") => ({
|
|
|
18
19
|
visibility: F(),
|
|
19
20
|
texture: { sampleType }
|
|
20
21
|
});
|
|
22
|
+
const depthTex = (gpu, binding) => depthReadEntry(gpu, binding, F());
|
|
21
23
|
const uniform = (binding, visibility = F()) => ({
|
|
22
24
|
binding,
|
|
23
25
|
visibility,
|
|
@@ -64,12 +66,12 @@ class PostCache {
|
|
|
64
66
|
if (pipeline) this.pipelines.set(key, pipeline);
|
|
65
67
|
return pipeline;
|
|
66
68
|
}
|
|
67
|
-
compute(ctx, key, root, entry, layouts) {
|
|
69
|
+
compute(ctx, key, root, entry, layouts, defines) {
|
|
68
70
|
const gpu = ctx.gpu;
|
|
69
71
|
this.check(gpu);
|
|
70
72
|
const cached = this.pipelines.get(key);
|
|
71
73
|
if (cached) return cached;
|
|
72
|
-
const module = ctx.world.resource(Shaders).module(gpu, { root });
|
|
74
|
+
const module = ctx.world.resource(Shaders).module(gpu, defines ? { root, defines } : { root });
|
|
73
75
|
if (!module) {
|
|
74
76
|
gpu.pipelines.skipped++;
|
|
75
77
|
return void 0;
|
|
@@ -133,16 +135,94 @@ function beginPass(ctx, name, view, load = false) {
|
|
|
133
135
|
});
|
|
134
136
|
}
|
|
135
137
|
const HDR = [{ format: "rgba16float" }];
|
|
138
|
+
const RG11 = [{ format: "rg11b10ufloat" }];
|
|
139
|
+
function targetsFor(texture) {
|
|
140
|
+
return texture.format === "rg11b10ufloat" ? RG11 : HDR;
|
|
141
|
+
}
|
|
142
|
+
function postFormat(gpu, view) {
|
|
143
|
+
const cam = cameraOf(view);
|
|
144
|
+
if (!gpu.features.has("rg11b10ufloat-renderable") || cam === void 0 || cam.alphaOutput)
|
|
145
|
+
return "rgba16float";
|
|
146
|
+
return rg11Truncates(gpu) === void 0 ? "rgba16float" : "rg11b10ufloat";
|
|
147
|
+
}
|
|
148
|
+
function roundsRg11(gpu, texture) {
|
|
149
|
+
return texture.format === "rg11b10ufloat" && rg11Truncates(gpu) === true;
|
|
150
|
+
}
|
|
151
|
+
const truncates = /* @__PURE__ */ new WeakMap();
|
|
152
|
+
function rg11Truncates(gpu) {
|
|
153
|
+
const device = gpu.device;
|
|
154
|
+
const known = truncates.get(device);
|
|
155
|
+
if (known === "probing") return void 0;
|
|
156
|
+
if (known !== void 0) return known;
|
|
157
|
+
truncates.set(device, "probing");
|
|
158
|
+
probeRg11(device).then(
|
|
159
|
+
(v) => truncates.set(device, v),
|
|
160
|
+
// A lost device or a failed map: assume truncation, the safe side (rounding then is exact).
|
|
161
|
+
() => truncates.set(device, true)
|
|
162
|
+
);
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
async function probeRg11(device) {
|
|
166
|
+
const texture = device.createTexture({
|
|
167
|
+
label: "post/rg11-probe",
|
|
168
|
+
size: [1, 1],
|
|
169
|
+
format: "rg11b10ufloat",
|
|
170
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
|
|
171
|
+
});
|
|
172
|
+
const buffer = device.createBuffer({
|
|
173
|
+
label: "post/rg11-probe",
|
|
174
|
+
size: 4,
|
|
175
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
176
|
+
});
|
|
177
|
+
const module = device.createShaderModule({
|
|
178
|
+
label: "post/rg11-probe",
|
|
179
|
+
code: `
|
|
180
|
+
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
|
|
181
|
+
let xy = vec2f(f32((i << 1u) & 2u), f32(i & 2u));
|
|
182
|
+
return vec4f(xy * 2.0 - 1.0, 0.0, 1.0);
|
|
183
|
+
}
|
|
184
|
+
@fragment fn fs() -> @location(0) vec4f { return vec4f(1.0 + 0.75 / 64.0, 0.0, 0.0, 1.0); }`
|
|
185
|
+
});
|
|
186
|
+
const pipeline = await device.createRenderPipelineAsync({
|
|
187
|
+
label: "post/rg11-probe",
|
|
188
|
+
layout: "auto",
|
|
189
|
+
vertex: { module, entryPoint: "vs" },
|
|
190
|
+
fragment: { module, entryPoint: "fs", targets: [{ format: "rg11b10ufloat" }] }
|
|
191
|
+
});
|
|
192
|
+
const encoder = device.createCommandEncoder({ label: "post/rg11-probe" });
|
|
193
|
+
const pass = encoder.beginRenderPass({
|
|
194
|
+
colorAttachments: [{ view: texture.createView(), loadOp: "clear", storeOp: "store" }]
|
|
195
|
+
});
|
|
196
|
+
pass.setPipeline(pipeline);
|
|
197
|
+
pass.draw(3);
|
|
198
|
+
pass.end();
|
|
199
|
+
encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow: 256 }, [1, 1]);
|
|
200
|
+
device.queue.submit([encoder.finish()]);
|
|
201
|
+
try {
|
|
202
|
+
await buffer.mapAsync(GPUMapMode.READ);
|
|
203
|
+
const red = new Uint32Array(buffer.getMappedRange())[0] & 2047;
|
|
204
|
+
buffer.unmap();
|
|
205
|
+
return red === 15 << 6;
|
|
206
|
+
} finally {
|
|
207
|
+
texture.destroy();
|
|
208
|
+
buffer.destroy();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
136
211
|
const scratch = new Float32Array(64);
|
|
137
212
|
export {
|
|
138
213
|
F,
|
|
139
214
|
HDR,
|
|
140
215
|
PostCache,
|
|
141
216
|
beginPass,
|
|
217
|
+
depthTex,
|
|
142
218
|
forwardView,
|
|
143
219
|
idOf,
|
|
220
|
+
postFormat,
|
|
221
|
+
rg11Truncates,
|
|
222
|
+
roundsRg11,
|
|
144
223
|
sampler,
|
|
145
224
|
scratch,
|
|
225
|
+
targetsFor,
|
|
146
226
|
tex,
|
|
147
227
|
uniform
|
|
148
228
|
};
|
package/dist/post-nodes.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { type World } from '@aethervtt/shard-core';
|
|
|
2
2
|
import { type CameraData } from './view.js';
|
|
3
3
|
/** Mip levels of a view's bloom chain: `radius` spreads it from 3 levels to the whole view. */
|
|
4
4
|
export declare function bloomLevels(cam: CameraData): number;
|
|
5
|
+
/**
|
|
6
|
+
* Bloom's glow parameters for what composites it (the tonemap) and what meters it: intensity, the
|
|
7
|
+
* chain's level count (one mip per level), mix (1, no threshold) or add (0). Zeros without bloom.
|
|
8
|
+
*/
|
|
9
|
+
export declare function bloomGlow(cam: CameraData, chain: GPUTexture | undefined, out: Float32Array, at: number): void;
|
|
5
10
|
/** Metered EV100 per camera (from the GPU histogram, a frame or two late) and the adapted EV. */
|
|
6
11
|
export interface ExposureState {
|
|
7
12
|
metered: number | undefined;
|
|
@@ -32,7 +37,8 @@ export declare const adaptExposure: import("@aethervtt/shard-core").SystemDef<{
|
|
|
32
37
|
/** Graph node names of the post chain, in order (for describe and tests). */
|
|
33
38
|
export declare const POST_NODES: readonly [readonly ['post/fog', 1], readonly ['post/taa', 2], readonly ['post/motion-blur', 4], readonly ['post/dof', 8], readonly ['post/bloom', 16], readonly ['post/exposure', 32], readonly ['post/fxaa', 64]];
|
|
34
39
|
/** Adds the prepass, SSAO, and the post chain to the graph. */
|
|
35
|
-
|
|
40
|
+
/** The post chain's nodes. `baseline`: the baseline tier's exposure meter (0064), when on one. */
|
|
41
|
+
export declare function addPostNodes(world: World, baseline?: typeof import('./baseline/exposure.js')): void;
|
|
36
42
|
/** The post section of `render.describe`: per view, active effects in order with GPU time. */
|
|
37
43
|
export declare function describePost(world: World): {
|
|
38
44
|
views: Record<string, unknown>;
|