@aethervtt/shard-protocol 0.1.0 → 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/ablate.d.ts +2 -0
- package/dist/ablate.js +62 -0
- package/dist/budgets.d.ts +2 -0
- package/dist/budgets.js +20 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/server.js +90 -2
- package/package.json +12 -12
package/dist/ablate.d.ts
ADDED
package/dist/ablate.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { defineSchema, Last, PostUpdate, t } from "@aethervtt/shard-core";
|
|
2
|
+
import { ablatePasses, Gpu, Graph, Views } from "@aethervtt/shard-render";
|
|
3
|
+
import { AppControlResource } from "@aethervtt/shard-runtime";
|
|
4
|
+
function renderOnly(app) {
|
|
5
|
+
app.runSchedule(PostUpdate);
|
|
6
|
+
app.runSchedule(Last);
|
|
7
|
+
}
|
|
8
|
+
function nextFrame(app) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const off = app.onFrame(() => {
|
|
11
|
+
off();
|
|
12
|
+
resolve();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const ABLATE_METHODS = [
|
|
17
|
+
{
|
|
18
|
+
name: "perf.ablate",
|
|
19
|
+
description: "Each render-graph pass's GPU cost, measured honestly on any GPU: rounds alternate the pass disabled and enabled, and its cost is the median drop in gpu:frame (first pass start to last pass end). Use it where per-pass timestamps overlap (tile-based GPUs such as Apple's, where pass times add up to more than the frame). A disabled pass still begins and ends, so what reads its textures stays valid; the image is wrong while it runs. Needs timestamp-query. It runs (passes + 2) \xD7 rounds \xD7 (frames + 4) frames: three passes at the defaults take about 50 s at 60 fps.",
|
|
20
|
+
params: defineSchema("protocol/AblateParams", {
|
|
21
|
+
passes: t.list(t.string, {
|
|
22
|
+
description: "Render-graph nodes to measure (render.describe lists them). Default: every node the last frame ran."
|
|
23
|
+
}),
|
|
24
|
+
frames: t.u32({ default: 120, min: 1, max: 1e4, description: "Frames per measurement." }),
|
|
25
|
+
rounds: t.u32({ default: 5, min: 1, max: 100, description: "Interleaved rounds." }),
|
|
26
|
+
together: t.bool({ description: "Also measure every pass disabled at once." })
|
|
27
|
+
}),
|
|
28
|
+
handler: async ({ app, world, options }, p) => {
|
|
29
|
+
const manual = (options.frames ?? "manual") === "manual" || world.resource(AppControlResource).paused;
|
|
30
|
+
const frame = () => manual ? renderOnly(app) : nextFrame(app);
|
|
31
|
+
const graph = world.resource(Graph);
|
|
32
|
+
const gpu = world.resource(Gpu);
|
|
33
|
+
for (let i = 0; i < 20; i++) {
|
|
34
|
+
await frame();
|
|
35
|
+
await gpu.pipelines.whenIdle();
|
|
36
|
+
if (i > 0 && gpu.pipelines.pending === 0 && gpu.pipelines.skipped === 0) break;
|
|
37
|
+
}
|
|
38
|
+
let passes = p.passes ?? [];
|
|
39
|
+
if (passes.length === 0) {
|
|
40
|
+
const ran = /* @__PURE__ */ new Set();
|
|
41
|
+
const perView = graph.describe().perView;
|
|
42
|
+
for (const view of world.resource(Views).list) {
|
|
43
|
+
for (const name of perView[view.name]?.order ?? []) ran.add(name);
|
|
44
|
+
}
|
|
45
|
+
passes = [...ran];
|
|
46
|
+
}
|
|
47
|
+
return ablatePasses(
|
|
48
|
+
world,
|
|
49
|
+
{
|
|
50
|
+
passes,
|
|
51
|
+
frames: p.frames,
|
|
52
|
+
rounds: p.rounds,
|
|
53
|
+
together: p.together
|
|
54
|
+
},
|
|
55
|
+
frame
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
export {
|
|
61
|
+
ABLATE_METHODS
|
|
62
|
+
};
|
package/dist/budgets.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineSchema, t } from "@aethervtt/shard-core";
|
|
2
|
+
import { describeBudgets } from "@aethervtt/shard-runtime";
|
|
3
|
+
const BUDGET_METHODS = [
|
|
4
|
+
{
|
|
5
|
+
name: "perf.budgets",
|
|
6
|
+
description: "Performance budgets (0075, bench/perf/budgets.json) for this app: the named machine it runs on (detected from the CPU model where the host exposes it, and the GPU adapter; null with a perf/unknown-machine warning when it can't tell, and then numbers are the closest machine's), every budget resolved for it with the profiler's latest p95 against it, over budget first; and the scenario's slices (the app's App.perfScenario, or `scenario`): each slice's measured share of the frame (from the profiler's aggregates, gpu:frame for GPU slices, frame for CPU) next to its budget share, over first. Hosts supply the files: the CLI loads them from the repo or project, shard dev serves them to its page.",
|
|
7
|
+
params: defineSchema("protocol/BudgetsParams", {
|
|
8
|
+
scenario: t.string({
|
|
9
|
+
description: "A scenario of budgets.json to measure (scatter-walk, crowd, planet-descent, tabletop-max). Default: the app's own (App.perfScenario)."
|
|
10
|
+
})
|
|
11
|
+
}),
|
|
12
|
+
handler: ({ world }, p) => {
|
|
13
|
+
const scenario = p.scenario;
|
|
14
|
+
return describeBudgets(world, { scenario: scenario ? scenario : void 0 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
export {
|
|
19
|
+
BUDGET_METHODS
|
|
20
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { ABLATE_METHODS } from './ablate.js';
|
|
2
|
+
export { BUDGET_METHODS } from './budgets.js';
|
|
1
3
|
export { decodePng, encodePng, toBase64 } from './png.js';
|
|
2
4
|
export { type PreviewImage, previewAsset, previewKtx2 } from './preview.js';
|
|
3
5
|
export { createProtocolServer, type HandlerContext, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, METHODS, type MethodDef, type ProtocolServer, type ProtocolServerOptions, type Topic, } from './server.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ABLATE_METHODS } from "./ablate.js";
|
|
2
|
+
import { BUDGET_METHODS } from "./budgets.js";
|
|
1
3
|
import { decodePng, encodePng, toBase64 } from "./png.js";
|
|
2
4
|
import { previewAsset, previewKtx2 } from "./preview.js";
|
|
3
5
|
import {
|
|
@@ -6,6 +8,8 @@ import {
|
|
|
6
8
|
} from "./server.js";
|
|
7
9
|
import { connectToHub, DEFAULT_HUB_PORT } from "./transport.js";
|
|
8
10
|
export {
|
|
11
|
+
ABLATE_METHODS,
|
|
12
|
+
BUDGET_METHODS,
|
|
9
13
|
DEFAULT_HUB_PORT,
|
|
10
14
|
METHODS,
|
|
11
15
|
connectToHub,
|
package/dist/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isPlainObject,
|
|
10
10
|
Last,
|
|
11
11
|
PostUpdate,
|
|
12
|
+
ProfilerResource,
|
|
12
13
|
ShardError,
|
|
13
14
|
t
|
|
14
15
|
} from "@aethervtt/shard-core";
|
|
@@ -42,6 +43,8 @@ import {
|
|
|
42
43
|
} from "@aethervtt/shard-render";
|
|
43
44
|
import {
|
|
44
45
|
AppControlResource,
|
|
46
|
+
capturePerf,
|
|
47
|
+
describePerf,
|
|
45
48
|
LogResource,
|
|
46
49
|
Time
|
|
47
50
|
} from "@aethervtt/shard-runtime";
|
|
@@ -74,6 +77,8 @@ import {
|
|
|
74
77
|
Transform,
|
|
75
78
|
worldPosition64
|
|
76
79
|
} from "@aethervtt/shard-transform";
|
|
80
|
+
import { ABLATE_METHODS } from "./ablate.js";
|
|
81
|
+
import { BUDGET_METHODS } from "./budgets.js";
|
|
77
82
|
import { encodePng, toBase64 } from "./png.js";
|
|
78
83
|
const CAPTURE_DEBUG_VIEWS = ["clusters", "cascades", "lod", "culling", "none"];
|
|
79
84
|
import { previewAsset } from "./preview.js";
|
|
@@ -599,7 +604,7 @@ const METHODS = [
|
|
|
599
604
|
description: "Limit overlays to entities with all these components."
|
|
600
605
|
}),
|
|
601
606
|
buffer: t.string({
|
|
602
|
-
description: "Raw floats of a render buffer instead of the display image: 'hdr' (luminance in cd/m\xB2, before post-processing and tonemapping), 'post-hdr' (after the HDR effects, pre-exposed), 'depth', 'velocity' (screen motion in uv units, TAA and motion blur), 'ssao' (ambient occlusion), 'bloom' (the glow's first level), 'dof-half' (color and signed CoC in pixels), or another graph texture. Returned as base64 little-endian rgba32float."
|
|
607
|
+
description: "Raw floats of a render buffer instead of the display image: 'hdr' (luminance in cd/m\xB2, before post-processing and tonemapping), 'post-hdr' (after the HDR effects, pre-exposed; bloom's glow is added by the tonemap, so it's not in it), 'depth', 'velocity' (screen motion in uv units, TAA and motion blur), 'ssao' (ambient occlusion and view depth, at half resolution), 'bloom' (the glow's first level), 'dof-half' (color and signed CoC in pixels), or another graph texture. Returned as base64 little-endian rgba32float."
|
|
603
608
|
})
|
|
604
609
|
}),
|
|
605
610
|
handler: async ({ app, world, options }, p) => {
|
|
@@ -1038,6 +1043,87 @@ const METHODS = [
|
|
|
1038
1043
|
};
|
|
1039
1044
|
}
|
|
1040
1045
|
},
|
|
1046
|
+
{
|
|
1047
|
+
name: "perf.describe",
|
|
1048
|
+
description: 'Profiler aggregates (0074) over the last 120 runs of each span (last, avg, p95, max ms): the frame, each schedule and its command application, systems and other main-thread spans, GPU passes and the GPU frame ("unavailable" without timestamp queries), worker jobs, async spans; memory (GPU ledger by category, JS heap, ECS tables); and the clock (resolution, isolated, timings under four steps are "coarse").',
|
|
1049
|
+
params: s("PerfDescribeParams", {
|
|
1050
|
+
spans: t.list(t.string, {
|
|
1051
|
+
description: 'Only spans these keys cover: a key covers a span it equals or prefixes up to a "/" ("render" covers "render/opaque").'
|
|
1052
|
+
}),
|
|
1053
|
+
top: t.u32({ default: 30, min: 1, max: 1e3, description: "Most spans listed per track." })
|
|
1054
|
+
}),
|
|
1055
|
+
handler: ({ world }, p) => {
|
|
1056
|
+
const spans = p.spans;
|
|
1057
|
+
return toJson(
|
|
1058
|
+
describePerf(world, {
|
|
1059
|
+
spans: spans && spans.length > 0 ? spans : void 0,
|
|
1060
|
+
top: p.top
|
|
1061
|
+
})
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
name: "perf.capture",
|
|
1067
|
+
description: 'Captures a span timeline (0074) over the next `frames` frames, or with `until.frameMs` a flight recorder that keeps `before` frames and stops `after` frames past the first frame over it. Returns a summary: frame CPU/GPU/interval p50/p95/max, the top spans, and the 5 worst frames, each with the spans most above their median ("over"): the lead for a hitch. The Chrome trace (Perfetto, chrome://tracing) goes to .shard/captures/ (tracePath) where the host can write, else inline (trace).',
|
|
1068
|
+
params: s("PerfCaptureParams", {
|
|
1069
|
+
frames: t.u32({ min: 0, max: 1e5, description: "Frames to capture (default 300)." }),
|
|
1070
|
+
until: t.struct({
|
|
1071
|
+
frameMs: t.f32({
|
|
1072
|
+
min: 0,
|
|
1073
|
+
description: "Flight recorder: stop after the first frame whose CPU time is over this."
|
|
1074
|
+
})
|
|
1075
|
+
}),
|
|
1076
|
+
before: t.u32({
|
|
1077
|
+
default: 120,
|
|
1078
|
+
max: 1e5,
|
|
1079
|
+
description: "Flight recorder: frames kept before."
|
|
1080
|
+
}),
|
|
1081
|
+
after: t.u32({ default: 30, max: 1e5, description: "Flight recorder: frames after." }),
|
|
1082
|
+
timeout: t.f32({
|
|
1083
|
+
default: 60,
|
|
1084
|
+
min: 0,
|
|
1085
|
+
description: "Flight recorder: seconds to wait for a slow frame."
|
|
1086
|
+
}),
|
|
1087
|
+
sample: t.bool({
|
|
1088
|
+
description: "Also sample JavaScript (V8's profiler in Node, JS Self-Profiling in browsers): the hottest functions join the summary."
|
|
1089
|
+
}),
|
|
1090
|
+
devtools: t.bool({
|
|
1091
|
+
description: "Also show spans in Chrome's Performance panel (performance.measure)."
|
|
1092
|
+
}),
|
|
1093
|
+
write: t.bool({ default: true, description: "Write the trace file when the host can." })
|
|
1094
|
+
}),
|
|
1095
|
+
handler: async ({ app, world, options }, p) => {
|
|
1096
|
+
const frameMs = p.until?.frameMs ?? 0;
|
|
1097
|
+
const manual = (options.frames ?? "manual") === "manual";
|
|
1098
|
+
const gpu = world.tryResource(Gpu) !== void 0;
|
|
1099
|
+
return toJson(
|
|
1100
|
+
await capturePerf(world, {
|
|
1101
|
+
frames: p.frames || 300,
|
|
1102
|
+
until: frameMs > 0 ? { frameMs } : void 0,
|
|
1103
|
+
before: p.before,
|
|
1104
|
+
after: p.after,
|
|
1105
|
+
timeout: p.timeout,
|
|
1106
|
+
sample: p.sample,
|
|
1107
|
+
devtools: p.devtools,
|
|
1108
|
+
write: p.write,
|
|
1109
|
+
// Headless: run the frames here, letting GPU readbacks land between them.
|
|
1110
|
+
step: manual ? async () => {
|
|
1111
|
+
app.update(1 / app.fixedHz);
|
|
1112
|
+
if (gpu) await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1113
|
+
} : void 0
|
|
1114
|
+
})
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
name: "perf.reset",
|
|
1120
|
+
description: "Clears the profiler's aggregates: perf.describe starts over.",
|
|
1121
|
+
params: none,
|
|
1122
|
+
handler: ({ world }) => {
|
|
1123
|
+
world.resource(ProfilerResource).reset();
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
1041
1127
|
{
|
|
1042
1128
|
name: "log.tail",
|
|
1043
1129
|
description: "Recent log entries (errors from systems, the GPU, and shaders land here).",
|
|
@@ -1052,7 +1138,9 @@ const METHODS = [
|
|
|
1052
1138
|
description: "Recent errors with code, path, and hint.",
|
|
1053
1139
|
params: s("ErrorsParams", { count: t.u32({ default: 20, min: 1 }) }),
|
|
1054
1140
|
handler: ({ world }, p) => world.resource(LogResource).errors(p.count)
|
|
1055
|
-
}
|
|
1141
|
+
},
|
|
1142
|
+
...ABLATE_METHODS,
|
|
1143
|
+
...BUDGET_METHODS
|
|
1056
1144
|
];
|
|
1057
1145
|
function createProtocolServer(app, options = {}) {
|
|
1058
1146
|
const ctx = { app, world: app.world, options };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethervtt/shard-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@aethervtt/shard-assets": "0.
|
|
20
|
-
"@aethervtt/shard-core": "0.
|
|
21
|
-
"@aethervtt/shard-input": "0.
|
|
22
|
-
"@aethervtt/shard-platform": "0.
|
|
23
|
-
"@aethervtt/shard-render": "0.
|
|
24
|
-
"@aethervtt/shard-runtime": "0.
|
|
25
|
-
"@aethervtt/shard-scene": "0.
|
|
26
|
-
"@aethervtt/shard-text": "0.
|
|
27
|
-
"@aethervtt/shard-texture": "0.
|
|
28
|
-
"@aethervtt/shard-transform": "0.
|
|
19
|
+
"@aethervtt/shard-assets": "0.2.0",
|
|
20
|
+
"@aethervtt/shard-core": "0.2.0",
|
|
21
|
+
"@aethervtt/shard-input": "0.2.0",
|
|
22
|
+
"@aethervtt/shard-platform": "0.2.0",
|
|
23
|
+
"@aethervtt/shard-render": "0.2.0",
|
|
24
|
+
"@aethervtt/shard-runtime": "0.2.0",
|
|
25
|
+
"@aethervtt/shard-scene": "0.2.0",
|
|
26
|
+
"@aethervtt/shard-text": "0.2.0",
|
|
27
|
+
"@aethervtt/shard-texture": "0.2.0",
|
|
28
|
+
"@aethervtt/shard-transform": "0.2.0"
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"provenance": true
|
|
38
38
|
},
|
|
39
39
|
"shard": {
|
|
40
|
-
"buildSha": "
|
|
40
|
+
"buildSha": "32c22a3944ed23f5d1baa90bf7cc9fae724f5fd8"
|
|
41
41
|
}
|
|
42
42
|
}
|