@aethervtt/shard-render 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/assets.d.ts +104 -0
- package/dist/assets.js +213 -0
- package/dist/atmosphere-model.d.ts +96 -0
- package/dist/atmosphere-model.js +370 -0
- package/dist/atmosphere-nodes.d.ts +55 -0
- package/dist/atmosphere-nodes.js +780 -0
- package/dist/atmosphere-plugin.d.ts +5 -0
- package/dist/atmosphere-plugin.js +42 -0
- package/dist/atmosphere-shaders.d.ts +7 -0
- package/dist/atmosphere-shaders.js +595 -0
- package/dist/atmosphere-state.d.ts +72 -0
- package/dist/atmosphere-state.js +18 -0
- package/dist/atmosphere.d.ts +191 -0
- package/dist/atmosphere.js +950 -0
- package/dist/camera.d.ts +83 -0
- package/dist/camera.js +163 -0
- package/dist/clusters.d.ts +68 -0
- package/dist/clusters.js +288 -0
- package/dist/culling-shaders.d.ts +2 -0
- package/dist/culling-shaders.js +203 -0
- package/dist/culling.d.ts +84 -0
- package/dist/culling.js +498 -0
- package/dist/debug-shaders.d.ts +3 -0
- package/dist/debug-shaders.js +207 -0
- package/dist/debug-views.d.ts +60 -0
- package/dist/debug-views.js +197 -0
- package/dist/deferred-plugin.d.ts +5 -0
- package/dist/deferred-plugin.js +20 -0
- package/dist/deferred-shaders.d.ts +2 -0
- package/dist/deferred-shaders.js +151 -0
- package/dist/deferred.d.ts +22 -0
- package/dist/deferred.js +296 -0
- package/dist/deform.d.ts +80 -0
- package/dist/deform.js +213 -0
- package/dist/describe-plugin.d.ts +5 -0
- package/dist/describe-plugin.js +19 -0
- package/dist/display-nodes.d.ts +5 -0
- package/dist/display-nodes.js +189 -0
- package/dist/dynamic-resolution.d.ts +47 -0
- package/dist/dynamic-resolution.js +191 -0
- package/dist/environment-plugin.d.ts +6 -0
- package/dist/environment-plugin.js +44 -0
- package/dist/environment-shaders.d.ts +2 -0
- package/dist/environment-shaders.js +260 -0
- package/dist/environment-state.d.ts +71 -0
- package/dist/environment-state.js +144 -0
- package/dist/environment.d.ts +70 -0
- package/dist/environment.js +529 -0
- package/dist/forward.d.ts +123 -0
- package/dist/forward.js +1216 -0
- package/dist/fxaa.d.ts +2 -0
- package/dist/fxaa.js +65 -0
- package/dist/gizmo-font.d.ts +21 -0
- package/dist/gizmo-font.js +132 -0
- package/dist/gizmos-plugin.d.ts +5 -0
- package/dist/gizmos-plugin.js +34 -0
- package/dist/gizmos.d.ts +156 -0
- package/dist/gizmos.js +772 -0
- package/dist/gpu-assets.d.ts +114 -0
- package/dist/gpu-assets.js +494 -0
- package/dist/graph.d.ts +204 -0
- package/dist/graph.js +417 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +540 -0
- package/dist/instances.d.ts +349 -0
- package/dist/instances.js +1335 -0
- package/dist/large-world.d.ts +13 -0
- package/dist/large-world.js +20 -0
- package/dist/lights.d.ts +160 -0
- package/dist/lights.js +468 -0
- package/dist/material-noise.d.ts +19 -0
- package/dist/material-noise.js +7 -0
- package/dist/material-pipelines.d.ts +46 -0
- package/dist/material-pipelines.js +166 -0
- package/dist/materials.d.ts +108 -0
- package/dist/materials.js +219 -0
- package/dist/noise.d.ts +76 -0
- package/dist/noise.js +286 -0
- package/dist/overlay-registry.d.ts +18 -0
- package/dist/overlay-registry.js +30 -0
- package/dist/overlays.d.ts +35 -0
- package/dist/overlays.js +344 -0
- package/dist/paths.d.ts +20 -0
- package/dist/paths.js +46 -0
- package/dist/picking-plugin.d.ts +2 -0
- package/dist/picking-plugin.js +20 -0
- package/dist/picking.d.ts +94 -0
- package/dist/picking.js +456 -0
- package/dist/pixel-perfect-plugin.d.ts +9 -0
- package/dist/pixel-perfect-plugin.js +135 -0
- package/dist/pixel-perfect.d.ts +27 -0
- package/dist/pixel-perfect.js +70 -0
- package/dist/plugin.d.ts +111 -0
- package/dist/plugin.js +182 -0
- package/dist/pool.d.ts +19 -0
- package/dist/pool.js +43 -0
- package/dist/post-common.d.ts +51 -0
- package/dist/post-common.js +148 -0
- package/dist/post-nodes.d.ts +39 -0
- package/dist/post-nodes.js +849 -0
- package/dist/post-plugin.d.ts +5 -0
- package/dist/post-plugin.js +26 -0
- package/dist/post-shaders.d.ts +8 -0
- package/dist/post-shaders.js +698 -0
- package/dist/post.d.ts +175 -0
- package/dist/post.js +453 -0
- package/dist/readback.d.ts +4 -0
- package/dist/readback.js +95 -0
- package/dist/render-scale.d.ts +53 -0
- package/dist/render-scale.js +63 -0
- package/dist/shaders.d.ts +122 -0
- package/dist/shaders.js +1168 -0
- package/dist/shadows.d.ts +119 -0
- package/dist/shadows.js +562 -0
- package/dist/skin-asset.d.ts +26 -0
- package/dist/skin-asset.js +25 -0
- package/dist/skinning-plugin.d.ts +5 -0
- package/dist/skinning-plugin.js +16 -0
- package/dist/skinning.d.ts +19 -0
- package/dist/skinning.js +343 -0
- package/dist/standard-fields.d.ts +67 -0
- package/dist/standard-fields.js +73 -0
- package/dist/standard.d.ts +7 -0
- package/dist/standard.js +37 -0
- package/dist/stats.d.ts +25 -0
- package/dist/stats.js +18 -0
- package/dist/target.d.ts +55 -0
- package/dist/target.js +70 -0
- package/dist/testing.d.ts +34 -0
- package/dist/testing.js +126 -0
- package/dist/timer.d.ts +28 -0
- package/dist/timer.js +136 -0
- package/dist/view.d.ts +117 -0
- package/dist/view.js +389 -0
- package/dist/visibility.d.ts +10 -0
- package/dist/visibility.js +84 -0
- package/package.json +49 -0
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { type World } from '@aethervtt/shard-core';
|
|
2
|
+
import { type GpuContext } from '@aethervtt/shard-gpu';
|
|
3
|
+
import { type Plugin } from '@aethervtt/shard-runtime';
|
|
4
|
+
import { type ShaderBake, ShaderLibrary } from '@aethervtt/shard-shader';
|
|
5
|
+
import { type CapturedBuffer, type CapturedImage, RenderGraph, type RenderView } from './graph.js';
|
|
6
|
+
import { type RenderTarget } from './target.js';
|
|
7
|
+
export declare const Gpu: import("@aethervtt/shard-core").ResourceDef<GpuContext>;
|
|
8
|
+
export declare const Graph: import("@aethervtt/shard-core").ResourceDef<RenderGraph>;
|
|
9
|
+
export declare const Shaders: import("@aethervtt/shard-core").ResourceDef<ShaderLibrary>;
|
|
10
|
+
export declare const Views: import("@aethervtt/shard-core").ResourceDef<{
|
|
11
|
+
list: RenderView[];
|
|
12
|
+
}>;
|
|
13
|
+
export declare const Window: import("@aethervtt/shard-core").ResourceDef<RenderTarget>;
|
|
14
|
+
export declare const GpuDeviceLost: import("@aethervtt/shard-core").EventDef<{
|
|
15
|
+
reason: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* The frame's render work, in `Last`, in this order. Plugins add systems to these sets:
|
|
20
|
+
* Begin clears per-frame state, Extract reads the world, Prepare uploads, Queue builds draw lists,
|
|
21
|
+
* Upload writes what the queue produced, Graph runs the render graph and submits.
|
|
22
|
+
*/
|
|
23
|
+
export declare const RenderSet: {
|
|
24
|
+
readonly Begin: import("@aethervtt/shard-core").SystemSetDef;
|
|
25
|
+
readonly Extract: import("@aethervtt/shard-core").SystemSetDef;
|
|
26
|
+
readonly Prepare: import("@aethervtt/shard-core").SystemSetDef;
|
|
27
|
+
readonly Queue: import("@aethervtt/shard-core").SystemSetDef;
|
|
28
|
+
readonly Upload: import("@aethervtt/shard-core").SystemSetDef;
|
|
29
|
+
readonly Graph: import("@aethervtt/shard-core").SystemSetDef;
|
|
30
|
+
};
|
|
31
|
+
export interface RenderPluginOptions {
|
|
32
|
+
/** Render into this canvas. Omit for headless (views use offscreen targets). */
|
|
33
|
+
canvas?: HTMLCanvasElement | OffscreenCanvas;
|
|
34
|
+
/** An existing GPU context (e.g. Dawn in Node). Otherwise one is created in `ready`. */
|
|
35
|
+
gpu?: GpuContext;
|
|
36
|
+
/**
|
|
37
|
+
* Headless stand-in for the window: cameras without a target render here. Used by the CLI for
|
|
38
|
+
* screenshots. Ignored when there's a canvas.
|
|
39
|
+
*/
|
|
40
|
+
target?: RenderTarget;
|
|
41
|
+
features?: GPUFeatureName[];
|
|
42
|
+
/** An existing shader library to share (e.g. a preview rendering the game's own shaders). */
|
|
43
|
+
shaders?: ShaderLibrary;
|
|
44
|
+
/**
|
|
45
|
+
* Shader variants linked ahead of time (`Shaders.bake()` after a run, saved as JSON). With every
|
|
46
|
+
* variant baked, WESL never loads (spec 0056).
|
|
47
|
+
*/
|
|
48
|
+
shaderBake?: ShaderBake;
|
|
49
|
+
/**
|
|
50
|
+
* Adds a 'window' view each frame when there's a canvas and no extract system added a view.
|
|
51
|
+
* Default true. Camera plugins turn this off by adding their own views.
|
|
52
|
+
*/
|
|
53
|
+
windowView?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Extra sections for `describeRender`, keyed by name. Plugins add theirs (lighting, post effects),
|
|
57
|
+
* so the renderer's description grows with what's installed.
|
|
58
|
+
*/
|
|
59
|
+
export declare const RenderDescribers: import("@aethervtt/shard-core").ResourceDef<Map<string, (world: World) => unknown>>;
|
|
60
|
+
export declare const RenderOptions: import("@aethervtt/shard-core").ResourceDef<Required<Pick<RenderPluginOptions, "windowView">>>;
|
|
61
|
+
export declare function renderPlugin(options?: RenderPluginOptions): Plugin;
|
|
62
|
+
/** A screenshot of a view (default: the window) after the next frame renders it. */
|
|
63
|
+
export declare function captureView(world: World, view?: string): Promise<CapturedImage>;
|
|
64
|
+
/**
|
|
65
|
+
* Any texture of a view after the next frame renders it, as floats: `hdr` (in cd/m²: the
|
|
66
|
+
* pre-exposure is divided out), `post-hdr` (the same after the HDR post effects: aerial
|
|
67
|
+
* perspective, fog, bloom…), `depth`, G-buffer channels, effect buffers.
|
|
68
|
+
*/
|
|
69
|
+
export declare function captureBuffer(world: World, view: string, buffer: string): Promise<CapturedBuffer>;
|
|
70
|
+
/** What the renderer is doing: graph order, culled nodes, views, pending pipelines. For agents. */
|
|
71
|
+
export declare function describeRender(world: World): {
|
|
72
|
+
order: {
|
|
73
|
+
name: string;
|
|
74
|
+
kind: "compute" | "raw" | "render";
|
|
75
|
+
reads: string[];
|
|
76
|
+
writes: string[];
|
|
77
|
+
}[];
|
|
78
|
+
culled: string[];
|
|
79
|
+
perView: {
|
|
80
|
+
[k: string]: {
|
|
81
|
+
order: string[];
|
|
82
|
+
culled: string[];
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
pooledTextures: number;
|
|
86
|
+
views: {
|
|
87
|
+
name: string;
|
|
88
|
+
target: string;
|
|
89
|
+
size: number[];
|
|
90
|
+
renderSize: number[];
|
|
91
|
+
order: number;
|
|
92
|
+
}[];
|
|
93
|
+
pipelinesCompiling: number;
|
|
94
|
+
drawsSkipped: number;
|
|
95
|
+
gpuTimings: boolean;
|
|
96
|
+
stats: {
|
|
97
|
+
[k: string]: import("./stats.js").ViewStats;
|
|
98
|
+
};
|
|
99
|
+
counters: {
|
|
100
|
+
taaResets: number;
|
|
101
|
+
originShifts: number;
|
|
102
|
+
};
|
|
103
|
+
memory: import("./stats.js").GpuMemoryData;
|
|
104
|
+
recentErrors: {
|
|
105
|
+
code: string;
|
|
106
|
+
message: string;
|
|
107
|
+
hint?: string;
|
|
108
|
+
path?: string;
|
|
109
|
+
details?: unknown[];
|
|
110
|
+
}[];
|
|
111
|
+
} & Record<string, unknown>;
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineEvent,
|
|
3
|
+
defineResource,
|
|
4
|
+
defineSystem,
|
|
5
|
+
defineSystemSet,
|
|
6
|
+
Last,
|
|
7
|
+
ShardError
|
|
8
|
+
} from "@aethervtt/shard-core";
|
|
9
|
+
import { createGpuContext } from "@aethervtt/shard-gpu";
|
|
10
|
+
import { definePlugin, LogResource } from "@aethervtt/shard-runtime";
|
|
11
|
+
import { ShaderLibrary } from "@aethervtt/shard-shader";
|
|
12
|
+
import { RenderGraph } from "./graph.js";
|
|
13
|
+
import { registerEngineShaders } from "./shaders.js";
|
|
14
|
+
import { GpuMemory, RenderCounters, RenderStats } from "./stats.js";
|
|
15
|
+
import { WindowTarget } from "./target.js";
|
|
16
|
+
const Gpu = defineResource("render/Gpu", {
|
|
17
|
+
description: "The GPU device, caches, and canvas."
|
|
18
|
+
});
|
|
19
|
+
const Graph = defineResource("render/Graph", {
|
|
20
|
+
description: "Render graph nodes, run for every view each frame."
|
|
21
|
+
});
|
|
22
|
+
const Shaders = defineResource("render/Shaders", {
|
|
23
|
+
description: "WESL shader modules: engine (shard::), plugins, and project (project::)."
|
|
24
|
+
});
|
|
25
|
+
const Views = defineResource("render/Views", {
|
|
26
|
+
description: "Views to render this frame. Rebuilt every frame by extract systems."
|
|
27
|
+
});
|
|
28
|
+
const Window = defineResource("render/Window", {
|
|
29
|
+
description: "Where cameras without a target render: the canvas swapchain, or an offscreen target when headless."
|
|
30
|
+
});
|
|
31
|
+
const GpuDeviceLost = defineEvent(
|
|
32
|
+
"render/GpuDeviceLost",
|
|
33
|
+
{
|
|
34
|
+
description: "The GPU device was lost; the renderer is recreating it."
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
const RenderSet = {
|
|
38
|
+
Begin: defineSystemSet("render/Begin"),
|
|
39
|
+
Extract: defineSystemSet("render/Extract"),
|
|
40
|
+
Prepare: defineSystemSet("render/Prepare"),
|
|
41
|
+
Queue: defineSystemSet("render/Queue"),
|
|
42
|
+
Upload: defineSystemSet("render/Upload"),
|
|
43
|
+
Graph: defineSystemSet("render/Graph")
|
|
44
|
+
};
|
|
45
|
+
const WINDOW_VIEW = "window";
|
|
46
|
+
const begin = defineSystem({
|
|
47
|
+
name: "render/begin-frame",
|
|
48
|
+
description: "Clears the frame view list.",
|
|
49
|
+
run: (_, world) => {
|
|
50
|
+
world.resource(Views).list.length = 0;
|
|
51
|
+
world.resource(Gpu).pipelines.skipped = 0;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const execute = defineSystem({
|
|
55
|
+
name: "render/execute-graph",
|
|
56
|
+
description: "Runs the render graph for every view and submits.",
|
|
57
|
+
setup: () => ({ recovering: { value: false } }),
|
|
58
|
+
run: ({ recovering }, world) => {
|
|
59
|
+
if (recovering.value) return;
|
|
60
|
+
const views = world.resource(Views).list;
|
|
61
|
+
const window = world.tryResource(Window);
|
|
62
|
+
const options = world.resource(RenderOptions);
|
|
63
|
+
if (views.length === 0 && window && options.windowView) {
|
|
64
|
+
views.push({ name: WINDOW_VIEW, target: window, order: 0, data: {} });
|
|
65
|
+
}
|
|
66
|
+
if (views.length === 0) return;
|
|
67
|
+
world.resource(Graph).execute(world, views);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const RenderDescribers = defineResource(
|
|
71
|
+
"render/Describers",
|
|
72
|
+
{ description: "Sections plugins add to render.describe.", init: () => /* @__PURE__ */ new Map() }
|
|
73
|
+
);
|
|
74
|
+
const RenderOptions = defineResource("render/Options");
|
|
75
|
+
function renderPlugin(options = {}) {
|
|
76
|
+
return definePlugin({
|
|
77
|
+
name: "render",
|
|
78
|
+
provides: [
|
|
79
|
+
// graph and device
|
|
80
|
+
Gpu,
|
|
81
|
+
GpuDeviceLost,
|
|
82
|
+
Graph,
|
|
83
|
+
RenderDescribers,
|
|
84
|
+
RenderOptions,
|
|
85
|
+
Shaders,
|
|
86
|
+
Views,
|
|
87
|
+
Window
|
|
88
|
+
],
|
|
89
|
+
dependencies: ["core/time"],
|
|
90
|
+
build(app) {
|
|
91
|
+
app.insertResource(Views, { list: [] }).insertResource(RenderOptions, { windowView: options.windowView ?? true }).configureSets(
|
|
92
|
+
Last,
|
|
93
|
+
RenderSet.Extract.after(RenderSet.Begin),
|
|
94
|
+
RenderSet.Prepare.after(RenderSet.Extract),
|
|
95
|
+
RenderSet.Queue.after(RenderSet.Prepare),
|
|
96
|
+
RenderSet.Upload.after(RenderSet.Queue),
|
|
97
|
+
RenderSet.Graph.after(RenderSet.Upload)
|
|
98
|
+
).addSystems(Last, begin.inSet(RenderSet.Begin), execute.inSet(RenderSet.Graph));
|
|
99
|
+
},
|
|
100
|
+
async ready(app) {
|
|
101
|
+
const gpu = options.gpu ?? await createGpuContext({ canvas: options.canvas, features: options.features });
|
|
102
|
+
app.insertResource(Gpu, gpu);
|
|
103
|
+
app.insertResource(Graph, new RenderGraph(gpu));
|
|
104
|
+
const shaders = options.shaders ?? new ShaderLibrary();
|
|
105
|
+
if (!options.shaders) registerEngineShaders(shaders);
|
|
106
|
+
if (options.shaderBake) shaders.preload(options.shaderBake);
|
|
107
|
+
app.insertResource(Shaders, shaders);
|
|
108
|
+
if (gpu.context) app.insertResource(Window, new WindowTarget(gpu));
|
|
109
|
+
else if (options.target) app.insertResource(Window, options.target);
|
|
110
|
+
const log = app.world.tryResource(LogResource);
|
|
111
|
+
if (log) gpu.onError((error) => log.error(error));
|
|
112
|
+
gpu.onDeviceLost((info) => {
|
|
113
|
+
app.world.send(GpuDeviceLost, info);
|
|
114
|
+
void gpu.recreate();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function captureView(world, view = WINDOW_VIEW) {
|
|
120
|
+
return world.resource(Graph).capture(view);
|
|
121
|
+
}
|
|
122
|
+
async function captureBuffer(world, view, buffer) {
|
|
123
|
+
const graph = world.resource(Graph);
|
|
124
|
+
const result = await graph.capture(view, buffer);
|
|
125
|
+
if (!("format" in result)) {
|
|
126
|
+
const data = new Float32Array(result.data.length);
|
|
127
|
+
for (let i = 0; i < data.length; i++) data[i] = result.data[i] / 255;
|
|
128
|
+
return { width: result.width, height: result.height, format: "rgba8unorm", data };
|
|
129
|
+
}
|
|
130
|
+
if (buffer === "hdr" || buffer === "scene-color" || buffer === "post-hdr") {
|
|
131
|
+
const exposure = graph.lastViewData(view)?.camera?.exposure;
|
|
132
|
+
if (exposure) {
|
|
133
|
+
const d = result.data;
|
|
134
|
+
for (let i = 0; i < d.length; i++) if (i % 4 !== 3) d[i] = d[i] / exposure;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
function describeRender(world) {
|
|
140
|
+
const graph = world.tryResource(Graph);
|
|
141
|
+
const gpu = world.tryResource(Gpu);
|
|
142
|
+
if (!graph || !gpu) {
|
|
143
|
+
throw new ShardError("render/not-ready", "The renderer has not initialized yet", {
|
|
144
|
+
hint: "Await app.init() so the render plugin can create the GPU device."
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const base = {
|
|
148
|
+
...graph.describe(),
|
|
149
|
+
views: world.resource(Views).list.map((v) => ({
|
|
150
|
+
name: v.name,
|
|
151
|
+
target: v.target.label,
|
|
152
|
+
size: [v.target.width, v.target.height],
|
|
153
|
+
renderSize: [v.width ?? v.target.width, v.height ?? v.target.height],
|
|
154
|
+
order: v.order
|
|
155
|
+
})),
|
|
156
|
+
pipelinesCompiling: gpu.pipelines.pending,
|
|
157
|
+
drawsSkipped: gpu.pipelines.skipped,
|
|
158
|
+
gpuTimings: gpu.features.has("timestamp-query"),
|
|
159
|
+
stats: Object.fromEntries(world.tryResource(RenderStats) ?? []),
|
|
160
|
+
counters: { ...world.tryResource(RenderCounters) ?? { taaResets: 0, originShifts: 0 } },
|
|
161
|
+
memory: world.tryResource(GpuMemory) ?? { textures: 0, textureBytes: 0 },
|
|
162
|
+
recentErrors: gpu.errors.slice(-5).map((e) => e.toJSON())
|
|
163
|
+
};
|
|
164
|
+
const sections = {};
|
|
165
|
+
for (const [name, fn] of world.tryResource(RenderDescribers) ?? []) sections[name] = fn(world);
|
|
166
|
+
return Object.assign(base, sections);
|
|
167
|
+
}
|
|
168
|
+
export {
|
|
169
|
+
Gpu,
|
|
170
|
+
GpuDeviceLost,
|
|
171
|
+
Graph,
|
|
172
|
+
RenderDescribers,
|
|
173
|
+
RenderOptions,
|
|
174
|
+
RenderSet,
|
|
175
|
+
Shaders,
|
|
176
|
+
Views,
|
|
177
|
+
Window,
|
|
178
|
+
captureBuffer,
|
|
179
|
+
captureView,
|
|
180
|
+
describeRender,
|
|
181
|
+
renderPlugin
|
|
182
|
+
};
|
package/dist/pool.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { GpuContext } from '@aethervtt/shard-gpu';
|
|
2
|
+
/**
|
|
3
|
+
* Transient textures, reused across frames. Textures with the same format, size, usage, and sample
|
|
4
|
+
* count come from the same free list. Aliasing different resources within a frame is a later
|
|
5
|
+
* optimization; the API won't change for it.
|
|
6
|
+
*/
|
|
7
|
+
export declare class TexturePool {
|
|
8
|
+
private readonly entries;
|
|
9
|
+
private readonly gpu;
|
|
10
|
+
private frame;
|
|
11
|
+
private generation;
|
|
12
|
+
constructor(gpu: GpuContext);
|
|
13
|
+
/** Total textures currently held (in use or free). */
|
|
14
|
+
get size(): number;
|
|
15
|
+
beginFrame(): void;
|
|
16
|
+
acquire(descriptor: GPUTextureDescriptor & {
|
|
17
|
+
size: [number, number];
|
|
18
|
+
}): GPUTexture;
|
|
19
|
+
}
|
package/dist/pool.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const EXPIRY_FRAMES = 60;
|
|
2
|
+
class TexturePool {
|
|
3
|
+
entries = [];
|
|
4
|
+
gpu;
|
|
5
|
+
frame = 0;
|
|
6
|
+
generation = -1;
|
|
7
|
+
constructor(gpu) {
|
|
8
|
+
this.gpu = gpu;
|
|
9
|
+
}
|
|
10
|
+
/** Total textures currently held (in use or free). */
|
|
11
|
+
get size() {
|
|
12
|
+
return this.entries.length;
|
|
13
|
+
}
|
|
14
|
+
beginFrame() {
|
|
15
|
+
this.frame++;
|
|
16
|
+
if (this.generation !== this.gpu.generation) {
|
|
17
|
+
this.entries.length = 0;
|
|
18
|
+
this.generation = this.gpu.generation;
|
|
19
|
+
}
|
|
20
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
21
|
+
const entry = this.entries[i];
|
|
22
|
+
if (this.frame - entry.lastUsed > EXPIRY_FRAMES) {
|
|
23
|
+
entry.texture.destroy();
|
|
24
|
+
this.entries.splice(i, 1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
acquire(descriptor) {
|
|
29
|
+
const key = `${descriptor.format}|${descriptor.size[0]}x${descriptor.size[1]}|${descriptor.usage}|${descriptor.sampleCount ?? 1}|${descriptor.mipLevelCount ?? 1}`;
|
|
30
|
+
for (const entry of this.entries) {
|
|
31
|
+
if (entry.key === key && entry.lastUsed !== this.frame) {
|
|
32
|
+
entry.lastUsed = this.frame;
|
|
33
|
+
return entry.texture;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const texture = this.gpu.device.createTexture(descriptor);
|
|
37
|
+
this.entries.push({ texture, key, lastUsed: this.frame });
|
|
38
|
+
return texture;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
TexturePool
|
|
43
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
|
|
2
|
+
import { type ViewGpu } from './forward.js';
|
|
3
|
+
import type { NodeContext } from './graph.js';
|
|
4
|
+
import { type CameraData } from './view.js';
|
|
5
|
+
export declare function idOf(o: object): number;
|
|
6
|
+
export declare const F: () => number;
|
|
7
|
+
export declare const tex: (binding: number, sampleType?: GPUTextureSampleType) => {
|
|
8
|
+
binding: number;
|
|
9
|
+
visibility: number;
|
|
10
|
+
texture: {
|
|
11
|
+
sampleType: GPUTextureSampleType;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export declare const uniform: (binding: number, visibility?: number) => {
|
|
15
|
+
binding: number;
|
|
16
|
+
visibility: number;
|
|
17
|
+
buffer: {
|
|
18
|
+
type: 'uniform';
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare const sampler: (binding: number) => {
|
|
22
|
+
binding: number;
|
|
23
|
+
visibility: number;
|
|
24
|
+
sampler: {
|
|
25
|
+
type: 'filtering';
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/** Per-node caches: pipelines by key, bind groups by slot, uniform buffers by view. */
|
|
29
|
+
export declare class PostCache {
|
|
30
|
+
private generation;
|
|
31
|
+
private readonly pipelines;
|
|
32
|
+
private readonly groups;
|
|
33
|
+
private readonly buffers;
|
|
34
|
+
private linear;
|
|
35
|
+
check(gpu: GpuContext): void;
|
|
36
|
+
/** A fullscreen render pipeline: `shard::fullscreen` vertex stage and `root`'s `entry`. */
|
|
37
|
+
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;
|
|
39
|
+
/** A bind group, rebuilt when `key` (the ids of what it binds) changes. */
|
|
40
|
+
group(gpu: GpuContext, slot: string, key: string, layout: GPUBindGroupLayout, entries: () => GPUBindGroupEntry[]): GPUBindGroup;
|
|
41
|
+
buffer(gpu: GpuContext, name: string, size: number): GpuBuffer;
|
|
42
|
+
sampler(gpu: GpuContext): GPUSampler;
|
|
43
|
+
}
|
|
44
|
+
export declare function forwardView(ctx: NodeContext): {
|
|
45
|
+
cam: CameraData;
|
|
46
|
+
pv: ViewGpu;
|
|
47
|
+
} | undefined;
|
|
48
|
+
/** A fullscreen pass the node begins itself (raw nodes with several passes). */
|
|
49
|
+
export declare function beginPass(ctx: NodeContext, name: string, view: GPUTextureView, load?: boolean): GPURenderPassEncoder;
|
|
50
|
+
export declare const HDR: GPUColorTargetState[];
|
|
51
|
+
export declare const scratch: Float32Array<ArrayBuffer>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { GpuBuffer } from "@aethervtt/shard-gpu";
|
|
2
|
+
import { ForwardStateResource } from "./forward.js";
|
|
3
|
+
import { Shaders } from "./plugin.js";
|
|
4
|
+
import { cameraOf } from "./view.js";
|
|
5
|
+
const textureIds = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
let nextId = 1;
|
|
7
|
+
function idOf(o) {
|
|
8
|
+
let id = textureIds.get(o);
|
|
9
|
+
if (id === void 0) {
|
|
10
|
+
id = nextId++;
|
|
11
|
+
textureIds.set(o, id);
|
|
12
|
+
}
|
|
13
|
+
return id;
|
|
14
|
+
}
|
|
15
|
+
const F = () => GPUShaderStage.FRAGMENT;
|
|
16
|
+
const tex = (binding, sampleType = "float") => ({
|
|
17
|
+
binding,
|
|
18
|
+
visibility: F(),
|
|
19
|
+
texture: { sampleType }
|
|
20
|
+
});
|
|
21
|
+
const uniform = (binding, visibility = F()) => ({
|
|
22
|
+
binding,
|
|
23
|
+
visibility,
|
|
24
|
+
buffer: { type: "uniform" }
|
|
25
|
+
});
|
|
26
|
+
const sampler = (binding) => ({
|
|
27
|
+
binding,
|
|
28
|
+
visibility: F(),
|
|
29
|
+
sampler: { type: "filtering" }
|
|
30
|
+
});
|
|
31
|
+
class PostCache {
|
|
32
|
+
generation = -1;
|
|
33
|
+
pipelines = /* @__PURE__ */ new Map();
|
|
34
|
+
groups = /* @__PURE__ */ new Map();
|
|
35
|
+
buffers = /* @__PURE__ */ new Map();
|
|
36
|
+
linear;
|
|
37
|
+
check(gpu) {
|
|
38
|
+
if (this.generation === gpu.generation) return;
|
|
39
|
+
this.generation = gpu.generation;
|
|
40
|
+
this.pipelines.clear();
|
|
41
|
+
this.groups.clear();
|
|
42
|
+
this.buffers.clear();
|
|
43
|
+
this.linear = void 0;
|
|
44
|
+
}
|
|
45
|
+
/** A fullscreen render pipeline: `shard::fullscreen` vertex stage and `root`'s `entry`. */
|
|
46
|
+
render(ctx, key, root, entry, layouts, targets, defines) {
|
|
47
|
+
const gpu = ctx.gpu;
|
|
48
|
+
this.check(gpu);
|
|
49
|
+
const cached = this.pipelines.get(key);
|
|
50
|
+
if (cached) return cached;
|
|
51
|
+
const shaders = ctx.world.resource(Shaders);
|
|
52
|
+
const fs = shaders.module(gpu, { root, defines });
|
|
53
|
+
const vs = shaders.module(gpu, { root: "shard::fullscreen" });
|
|
54
|
+
if (!fs || !vs) {
|
|
55
|
+
gpu.pipelines.skipped++;
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
const pipeline = gpu.pipelines.render({
|
|
59
|
+
label: key,
|
|
60
|
+
layout: gpu.layouts.pipelineLayout({ label: key, bindGroupLayouts: layouts }),
|
|
61
|
+
vertex: { module: vs, entryPoint: "vs" },
|
|
62
|
+
fragment: { module: fs, entryPoint: entry, targets }
|
|
63
|
+
});
|
|
64
|
+
if (pipeline) this.pipelines.set(key, pipeline);
|
|
65
|
+
return pipeline;
|
|
66
|
+
}
|
|
67
|
+
compute(ctx, key, root, entry, layouts) {
|
|
68
|
+
const gpu = ctx.gpu;
|
|
69
|
+
this.check(gpu);
|
|
70
|
+
const cached = this.pipelines.get(key);
|
|
71
|
+
if (cached) return cached;
|
|
72
|
+
const module = ctx.world.resource(Shaders).module(gpu, { root });
|
|
73
|
+
if (!module) {
|
|
74
|
+
gpu.pipelines.skipped++;
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
const pipeline = gpu.pipelines.compute({
|
|
78
|
+
label: key,
|
|
79
|
+
layout: gpu.layouts.pipelineLayout({ label: key, bindGroupLayouts: layouts }),
|
|
80
|
+
compute: { module, entryPoint: entry }
|
|
81
|
+
});
|
|
82
|
+
if (pipeline) this.pipelines.set(key, pipeline);
|
|
83
|
+
return pipeline;
|
|
84
|
+
}
|
|
85
|
+
/** A bind group, rebuilt when `key` (the ids of what it binds) changes. */
|
|
86
|
+
group(gpu, slot, key, layout, entries) {
|
|
87
|
+
this.check(gpu);
|
|
88
|
+
let g = this.groups.get(slot);
|
|
89
|
+
if (!g || g.key !== key) {
|
|
90
|
+
g = { key, group: gpu.device.createBindGroup({ label: slot, layout, entries: entries() }) };
|
|
91
|
+
this.groups.set(slot, g);
|
|
92
|
+
}
|
|
93
|
+
return g.group;
|
|
94
|
+
}
|
|
95
|
+
buffer(gpu, name, size) {
|
|
96
|
+
this.check(gpu);
|
|
97
|
+
let b = this.buffers.get(name);
|
|
98
|
+
if (!b) {
|
|
99
|
+
b = new GpuBuffer(gpu, { label: name, usage: GPUBufferUsage.UNIFORM, size });
|
|
100
|
+
this.buffers.set(name, b);
|
|
101
|
+
}
|
|
102
|
+
return b;
|
|
103
|
+
}
|
|
104
|
+
sampler(gpu) {
|
|
105
|
+
this.check(gpu);
|
|
106
|
+
this.linear ??= gpu.device.createSampler({
|
|
107
|
+
label: "post/linear",
|
|
108
|
+
magFilter: "linear",
|
|
109
|
+
minFilter: "linear",
|
|
110
|
+
addressModeU: "clamp-to-edge",
|
|
111
|
+
addressModeV: "clamp-to-edge"
|
|
112
|
+
});
|
|
113
|
+
return this.linear;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function forwardView(ctx) {
|
|
117
|
+
const cam = cameraOf(ctx.view);
|
|
118
|
+
const pv = ctx.world.resource(ForwardStateResource).views.get(ctx.view.name);
|
|
119
|
+
return cam && pv ? { cam, pv } : void 0;
|
|
120
|
+
}
|
|
121
|
+
function beginPass(ctx, name, view, load = false) {
|
|
122
|
+
return ctx.encoder.beginRenderPass({
|
|
123
|
+
label: `${ctx.view.name}/${name}`,
|
|
124
|
+
colorAttachments: [
|
|
125
|
+
{
|
|
126
|
+
view,
|
|
127
|
+
loadOp: load ? "load" : "clear",
|
|
128
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
129
|
+
storeOp: "store"
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
timestampWrites: ctx.timestamps(name)
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const HDR = [{ format: "rgba16float" }];
|
|
136
|
+
const scratch = new Float32Array(64);
|
|
137
|
+
export {
|
|
138
|
+
F,
|
|
139
|
+
HDR,
|
|
140
|
+
PostCache,
|
|
141
|
+
beginPass,
|
|
142
|
+
forwardView,
|
|
143
|
+
idOf,
|
|
144
|
+
sampler,
|
|
145
|
+
scratch,
|
|
146
|
+
tex,
|
|
147
|
+
uniform
|
|
148
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type World } from '@aethervtt/shard-core';
|
|
2
|
+
import { type CameraData } from './view.js';
|
|
3
|
+
/** Mip levels of a view's bloom chain: `radius` spreads it from 3 levels to the whole view. */
|
|
4
|
+
export declare function bloomLevels(cam: CameraData): number;
|
|
5
|
+
/** Metered EV100 per camera (from the GPU histogram, a frame or two late) and the adapted EV. */
|
|
6
|
+
export interface ExposureState {
|
|
7
|
+
metered: number | undefined;
|
|
8
|
+
/** Histogram readings landed so far. Each lands a few frames after its own, when the GPU gets to it. */
|
|
9
|
+
readings: number;
|
|
10
|
+
/** Readbacks issued for this camera, one per metered frame (numbered from 1). */
|
|
11
|
+
submitted: number;
|
|
12
|
+
/** Which readback `metered` came from. Older readings that land late are ignored. */
|
|
13
|
+
reading: number;
|
|
14
|
+
ev: number;
|
|
15
|
+
started: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Histogram readbacks in flight per camera. A frame whose readbacks are all still mapping isn't metered. */
|
|
18
|
+
export declare const METER_READBACKS = 3;
|
|
19
|
+
export declare const ExposureMeters: import("@aethervtt/shard-core").ResourceDef<Map<number, ExposureState>>;
|
|
20
|
+
/**
|
|
21
|
+
* The mean EV of a histogram, ignoring the darkest 10% and brightest 2% of the weight so a few
|
|
22
|
+
* dark or blown-out pixels don't swing the exposure.
|
|
23
|
+
*/
|
|
24
|
+
export declare function histogramEv(bins: Uint32Array): number | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Moves each AutoExposure camera's Exposure.ev100 toward its metered EV (minus compensation,
|
|
27
|
+
* within bounds) at its adaptation speed. Starts from the camera's own exposure.
|
|
28
|
+
*/
|
|
29
|
+
export declare const adaptExposure: import("@aethervtt/shard-core").SystemDef<{
|
|
30
|
+
q: import("@aethervtt/shard-core").Query;
|
|
31
|
+
}>;
|
|
32
|
+
/** Graph node names of the post chain, in order (for describe and tests). */
|
|
33
|
+
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
|
+
/** Adds the prepass, SSAO, and the post chain to the graph. */
|
|
35
|
+
export declare function addPostNodes(world: World): void;
|
|
36
|
+
/** The post section of `render.describe`: per view, active effects in order with GPU time. */
|
|
37
|
+
export declare function describePost(world: World): {
|
|
38
|
+
views: Record<string, unknown>;
|
|
39
|
+
};
|