@zkmake/three-meter 0.9.1 → 0.10.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/README.md +38 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{mount-perf-hud-BKpk62Pp.d.ts → mount-perf-hud-MTPYNhG9.d.ts} +29 -9
- package/dist/{mount-perf-hud-CebnfB8t.js → mount-perf-hud-U_Xlj0Od.js} +260 -42
- package/dist/mount-perf-hud-U_Xlj0Od.js.map +1 -0
- package/dist/{performance-monitor-DgHxeQ2X.d.ts → performance-monitor-BoY_-GTt.d.ts} +13 -1
- package/dist/{performance-monitor-DsBp13ts.js → performance-monitor-BqnoiLEs.js} +87 -9
- package/dist/performance-monitor-BqnoiLEs.js.map +1 -0
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -2
- package/dist/styles.css +64 -3
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +1 -1
- package/package.json +15 -25
- package/dist/mount-perf-hud-CebnfB8t.js.map +0 -1
- package/dist/performance-monitor-DsBp13ts.js.map +0 -1
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
//#region src/ui/budgets.ts
|
|
2
2
|
const DEFAULT_TARGET_FPS = 60;
|
|
3
|
-
const FLOORS = /* @__PURE__ */ new Set([
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const FLOORS = /* @__PURE__ */ new Set([
|
|
4
|
+
"fps",
|
|
5
|
+
"headroom",
|
|
6
|
+
"low"
|
|
7
|
+
]);
|
|
8
|
+
/** `detectedHz` is the monitor's refresh reading; an explicit `targetFps` wins over it. */
|
|
9
|
+
const resolveBudgets = (budgets, detectedHz = null) => {
|
|
10
|
+
const targetFps = budgets && budgets.targetFps || detectedHz || DEFAULT_TARGET_FPS;
|
|
6
11
|
const frameMs = 1e3 / targetFps;
|
|
7
12
|
if (budgets === false) return {
|
|
8
13
|
frameMs,
|
|
9
|
-
limits: {}
|
|
14
|
+
limits: {},
|
|
15
|
+
targetFps
|
|
10
16
|
};
|
|
11
17
|
const merged = {
|
|
12
18
|
cpu: frameMs,
|
|
13
19
|
fps: targetFps * .95,
|
|
14
20
|
gpu: frameMs,
|
|
21
|
+
headroom: 0,
|
|
15
22
|
low: targetFps / 2,
|
|
16
23
|
p99: frameMs * 1.5,
|
|
17
24
|
...budgets
|
|
@@ -20,7 +27,8 @@ const resolveBudgets = (budgets) => {
|
|
|
20
27
|
for (const [key, limit] of Object.entries(merged)) if (key !== "targetFps" && typeof limit === "number" && Number.isFinite(limit)) limits[key] = limit;
|
|
21
28
|
return {
|
|
22
29
|
frameMs,
|
|
23
|
-
limits
|
|
30
|
+
limits,
|
|
31
|
+
targetFps
|
|
24
32
|
};
|
|
25
33
|
};
|
|
26
34
|
const isFloor = (key) => FLOORS.has(key);
|
|
@@ -486,6 +494,9 @@ const dockPanel = (element, options = {}) => {
|
|
|
486
494
|
//#endregion
|
|
487
495
|
//#region src/ui/icons.ts
|
|
488
496
|
const ICON_MARKUP = {
|
|
497
|
+
eye: "<path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>",
|
|
498
|
+
eyeOff: "<path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\"/><path d=\"M14.084 14.158a3 3 0 0 1-4.242-4.242\"/><path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\"/><path d=\"m2 2 20 20\"/>",
|
|
499
|
+
hourglass: "<path d=\"M5 22h14\"/><path d=\"M5 2h14\"/><path d=\"M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22\"/><path d=\"M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2\"/>",
|
|
489
500
|
flame: "<path d=\"M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z\"/>",
|
|
490
501
|
activity: "<path d=\"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2\"/>",
|
|
491
502
|
box: "<path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>",
|
|
@@ -527,7 +538,8 @@ const createIcon = (name, className) => {
|
|
|
527
538
|
};
|
|
528
539
|
//#endregion
|
|
529
540
|
//#region package.json
|
|
530
|
-
var
|
|
541
|
+
var name = "@zkmake/three-meter";
|
|
542
|
+
var version = "0.10.0";
|
|
531
543
|
//#endregion
|
|
532
544
|
//#region src/ui/clipboard.ts
|
|
533
545
|
/**
|
|
@@ -579,6 +591,45 @@ const compact = new Intl.NumberFormat("en-US", {
|
|
|
579
591
|
});
|
|
580
592
|
const formatCount = (value) => Math.abs(value) < COMPACT_FROM ? exact.format(value) : compact.format(value);
|
|
581
593
|
//#endregion
|
|
594
|
+
//#region src/ui/hider.ts
|
|
595
|
+
var Hider = class {
|
|
596
|
+
groups = /* @__PURE__ */ new Map();
|
|
597
|
+
/** Each hidden object's `visible` from before the first group hid it. */
|
|
598
|
+
original = /* @__PURE__ */ new Map();
|
|
599
|
+
hide(key, objects) {
|
|
600
|
+
if (this.groups.has(key)) return;
|
|
601
|
+
const hideable = objects.filter((object) => typeof object === "object" && object !== null);
|
|
602
|
+
this.groups.set(key, hideable);
|
|
603
|
+
for (const object of hideable) {
|
|
604
|
+
if (!this.original.has(object)) this.original.set(object, object.visible !== false);
|
|
605
|
+
object.visible = false;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
show(key) {
|
|
609
|
+
const objects = this.groups.get(key);
|
|
610
|
+
if (!objects) return;
|
|
611
|
+
this.groups.delete(key);
|
|
612
|
+
for (const object of objects) if (!this.heldElsewhere(object)) {
|
|
613
|
+
object.visible = this.original.get(object) ?? true;
|
|
614
|
+
this.original.delete(object);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
isHidden(key) {
|
|
618
|
+
return this.groups.has(key);
|
|
619
|
+
}
|
|
620
|
+
/** Hidden group keys, in the order they were hidden. */
|
|
621
|
+
keys() {
|
|
622
|
+
return [...this.groups.keys()];
|
|
623
|
+
}
|
|
624
|
+
restoreAll() {
|
|
625
|
+
for (const key of this.keys()) this.show(key);
|
|
626
|
+
}
|
|
627
|
+
heldElsewhere(object) {
|
|
628
|
+
for (const objects of this.groups.values()) if (objects.includes(object)) return true;
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
//#endregion
|
|
582
633
|
//#region src/ui/metric-help.ts
|
|
583
634
|
/**
|
|
584
635
|
* One plain-language line per metric: what it is, and what a bad reading
|
|
@@ -590,6 +641,8 @@ const METRIC_HELP = {
|
|
|
590
641
|
cpu: "JavaScript time per frame: your update code plus three.js preparing the render. It has to fit the frame budget (16.7 ms at 60 FPS).",
|
|
591
642
|
fps: "Frames drawn per second. Higher is smoother; most displays top out at 60 or 120.",
|
|
592
643
|
geometries: "Geometries held in GPU memory. Should level off; a steady climb is a leak, so dispose() what you remove.",
|
|
644
|
+
headroom: "Spare time left in each frame: the frame budget minus whichever of CPU and GPU took longer. Below zero, frames miss the display's refresh.",
|
|
645
|
+
refresh: "The rate the loop runs at when it keeps up: the display's refresh rate, or the app's own cap. Budgets use it unless you set targetFps.",
|
|
593
646
|
gpu: "Time the graphics card spent drawing the frame. High means shaders, overdraw or resolution are the bottleneck.",
|
|
594
647
|
hitches: "Frames that took over twice as long as usual, in the last 1,000 frames. Each one is a visible stutter.",
|
|
595
648
|
lines: "Line segments drawn this frame.",
|
|
@@ -640,10 +693,15 @@ const renderReport = ({ cost, environment, page, sample, stats }) => {
|
|
|
640
693
|
`p99 ${ms(stats.p99Ms)}`,
|
|
641
694
|
`hitches ${formatCount(stats.hitches)} / ${formatCount(stats.frames)} frames`
|
|
642
695
|
].join(" · ");
|
|
696
|
+
const timing = [`CPU ${ms(sample.cpu)}`, `GPU ${sample.gpu.available ? ms(sample.gpu.ms) : "unavailable"}`];
|
|
697
|
+
if (stats.refreshHz) {
|
|
698
|
+
const busiest = Math.max(sample.cpu, sample.gpu.available ? sample.gpu.ms : 0);
|
|
699
|
+
timing.push(`refresh ${stats.refreshHz} Hz`, `headroom ${ms(1e3 / stats.refreshHz - busiest)}`);
|
|
700
|
+
}
|
|
643
701
|
const lines = [
|
|
644
702
|
heading.join(" · "),
|
|
645
703
|
frames,
|
|
646
|
-
|
|
704
|
+
timing.join(" · "),
|
|
647
705
|
[
|
|
648
706
|
`Calls ${formatCount(render.calls)}`,
|
|
649
707
|
`passes ${formatCount(render.passes)}`,
|
|
@@ -784,7 +842,8 @@ const THEME_OPTIONS = [
|
|
|
784
842
|
mode: "dark"
|
|
785
843
|
}
|
|
786
844
|
];
|
|
787
|
-
|
|
845
|
+
/** The monorepo tags each package's releases `@zkmake/three-meter@0.9.2`. */
|
|
846
|
+
const RELEASE_URL = `https://github.com/zkmake/three-kit/releases/tag/${encodeURIComponent(`${name}@${version}`)}`;
|
|
788
847
|
/** How long the report button says `copied` / `failed` before reading `copy` again. */
|
|
789
848
|
const COPY_FEEDBACK_MS = 1500;
|
|
790
849
|
/** Walking the scene graph isn't free; the top-costs list refreshes at 2 Hz while open. */
|
|
@@ -797,7 +856,15 @@ const COST_GROUPS = [{
|
|
|
797
856
|
group: "materials",
|
|
798
857
|
label: "materials"
|
|
799
858
|
}];
|
|
800
|
-
const COSTS_HELP = "The scene's biggest draw costs in the main render pass, estimated from the scene graph. Hidden and off-screen objects are left out; shadow and post-processing passes aren't counted. Click a row to log its objects to the console.";
|
|
859
|
+
const COSTS_HELP = "The scene's biggest draw costs in the main render pass, estimated from the scene graph. Hidden and off-screen objects are left out; shadow and post-processing passes aren't counted. Click a row to log its objects to the console; use the eye to hide them and measure the time they cost.";
|
|
860
|
+
/** Frames averaged on each side of hiding a row. */
|
|
861
|
+
const MEASURE_FRAMES = 30;
|
|
862
|
+
/** How long after hiding before the saving is read, so the average holds only frames without the row. */
|
|
863
|
+
const MEASURE_SETTLE_MS = 700;
|
|
864
|
+
const meanOfLast = (values, count) => {
|
|
865
|
+
const tail = values.slice(-count);
|
|
866
|
+
return tail.length === 0 ? 0 : tail.reduce((sum, value) => sum + value, 0) / tail.length;
|
|
867
|
+
};
|
|
801
868
|
/** `×2,000` for instances or copies; nothing for a single object. */
|
|
802
869
|
const copiesOf = (entry) => entry.instances > 1 ? `×${formatCount(entry.instances)}` : "";
|
|
803
870
|
const costTitle = (entry) => {
|
|
@@ -846,6 +913,22 @@ const NUMBERS = [
|
|
|
846
913
|
unit: "ms",
|
|
847
914
|
value: (sample) => sample.gpu.available ? sample.gpu.ms : null
|
|
848
915
|
},
|
|
916
|
+
{
|
|
917
|
+
format: tenths,
|
|
918
|
+
icon: "hourglass",
|
|
919
|
+
key: "headroom",
|
|
920
|
+
label: "Headroom",
|
|
921
|
+
unit: "ms",
|
|
922
|
+
value: (sample, _stats, frameMs) => sample.fps ? frameMs - Math.max(sample.cpu, sample.gpu.available ? sample.gpu.ms : 0) : null
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
format: (value) => `${Math.round(value)} Hz`,
|
|
926
|
+
icon: "monitor",
|
|
927
|
+
key: "refresh",
|
|
928
|
+
label: "Refresh",
|
|
929
|
+
unit: "Hz",
|
|
930
|
+
value: (_sample, stats) => stats.refreshHz
|
|
931
|
+
},
|
|
849
932
|
{
|
|
850
933
|
format: whole,
|
|
851
934
|
icon: "trendingDown",
|
|
@@ -969,6 +1052,10 @@ var PerformanceView = class {
|
|
|
969
1052
|
hudGraphs = /* @__PURE__ */ new Map();
|
|
970
1053
|
hudNumberEls = /* @__PURE__ */ new Map();
|
|
971
1054
|
budgets;
|
|
1055
|
+
budgetsInput;
|
|
1056
|
+
detectedHz = null;
|
|
1057
|
+
hider = new Hider();
|
|
1058
|
+
hiddenRows = /* @__PURE__ */ new Map();
|
|
972
1059
|
rafId = null;
|
|
973
1060
|
lastPaintAt = 0;
|
|
974
1061
|
resizeObserver;
|
|
@@ -989,6 +1076,7 @@ var PerformanceView = class {
|
|
|
989
1076
|
this.theme.setOverride(this.settings.theme);
|
|
990
1077
|
this.mode = options.mode ?? "full";
|
|
991
1078
|
this.minIntervalMs = 1e3 / (options.refreshHz ?? 10);
|
|
1079
|
+
this.budgetsInput = options.budgets;
|
|
992
1080
|
this.budgets = resolveBudgets(options.budgets);
|
|
993
1081
|
this.element = document.createElement("div");
|
|
994
1082
|
this.element.className = "perf-monitor";
|
|
@@ -1021,11 +1109,13 @@ var PerformanceView = class {
|
|
|
1021
1109
|
}
|
|
1022
1110
|
/** Replace the budgets; `false` turns them all off. Applies on the next paint. */
|
|
1023
1111
|
setBudgets(budgets) {
|
|
1024
|
-
this.
|
|
1112
|
+
this.budgetsInput = budgets;
|
|
1113
|
+
this.budgets = resolveBudgets(budgets, this.detectedHz);
|
|
1025
1114
|
this.applyBudgetTitles();
|
|
1026
1115
|
}
|
|
1027
1116
|
dispose() {
|
|
1028
1117
|
this.stop();
|
|
1118
|
+
this.showAllHidden();
|
|
1029
1119
|
this.unsubscribe();
|
|
1030
1120
|
this.unsubscribeTheme();
|
|
1031
1121
|
if (this.ownsTheme) this.theme.dispose();
|
|
@@ -1040,13 +1130,14 @@ var PerformanceView = class {
|
|
|
1040
1130
|
};
|
|
1041
1131
|
render() {
|
|
1042
1132
|
const sample = this.monitor.getSample();
|
|
1133
|
+
const stats = this.monitor.getFrameStats();
|
|
1134
|
+
this.followRefresh(stats.refreshHz);
|
|
1043
1135
|
if (this.mode === "compact") {
|
|
1044
|
-
this.renderHud(sample);
|
|
1136
|
+
this.renderHud(sample, stats);
|
|
1045
1137
|
if (this.settings.info) this.renderFooter();
|
|
1046
1138
|
return;
|
|
1047
1139
|
}
|
|
1048
1140
|
for (const config of TIMINGS) this.paintGraph(config, sample, this.graphValueEls.get(config.metric), this.graphCanvases.get(config.metric), "unavailable");
|
|
1049
|
-
const stats = this.monitor.getFrameStats();
|
|
1050
1141
|
for (const config of NUMBERS) this.paintNumber(config, this.statValueEls.get(config.key), sample, stats);
|
|
1051
1142
|
if (this.costsOpen && performance.now() - this.lastCostsAt >= COSTS_INTERVAL_MS) this.renderCosts();
|
|
1052
1143
|
this.renderFooter();
|
|
@@ -1054,44 +1145,102 @@ var PerformanceView = class {
|
|
|
1054
1145
|
renderCosts() {
|
|
1055
1146
|
this.lastCostsAt = performance.now();
|
|
1056
1147
|
const cost = this.monitor.getSceneCost();
|
|
1148
|
+
const hidden = [...this.hiddenRows.values()].map((row) => this.hiddenCostRow(row));
|
|
1057
1149
|
if (!cost) {
|
|
1058
|
-
this.costsListEl.replaceChildren(this.costsNote("No scene rendered yet."));
|
|
1150
|
+
this.costsListEl.replaceChildren(...hidden, this.costsNote("No scene rendered yet."));
|
|
1059
1151
|
return;
|
|
1060
1152
|
}
|
|
1061
|
-
const entries =
|
|
1153
|
+
const entries = (this.costGroup === "meshes" ? cost.meshes : cost.materials).filter((entry) => !this.hiddenRows.has(entry.key));
|
|
1062
1154
|
if (this.costSort === "triangles") entries.sort((a, b) => b.triangles - a.triangles || b.calls - a.calls);
|
|
1063
|
-
if (entries.length === 0) {
|
|
1155
|
+
if (entries.length === 0 && hidden.length === 0) {
|
|
1064
1156
|
this.costsListEl.replaceChildren(this.costsNote("Nothing drawn."));
|
|
1065
1157
|
return;
|
|
1066
1158
|
}
|
|
1067
1159
|
const rows = entries.slice(0, COSTS_ROWS).map((entry) => {
|
|
1068
|
-
const row =
|
|
1069
|
-
row.type = "button";
|
|
1070
|
-
row.className = "perf-monitor__cost";
|
|
1071
|
-
row.title = costTitle(entry);
|
|
1072
|
-
const name = document.createElement("span");
|
|
1073
|
-
name.className = "perf-monitor__cost-name";
|
|
1074
|
-
name.textContent = entry.label;
|
|
1075
|
-
const copies = copiesOf(entry);
|
|
1076
|
-
if (copies) {
|
|
1077
|
-
const suffix = document.createElement("span");
|
|
1078
|
-
suffix.className = "perf-monitor__cost-copies";
|
|
1079
|
-
suffix.textContent = ` ${copies}`;
|
|
1080
|
-
name.append(suffix);
|
|
1081
|
-
}
|
|
1160
|
+
const row = this.costRowShell(entry);
|
|
1082
1161
|
const calls = document.createElement("span");
|
|
1083
1162
|
calls.className = "perf-monitor__cost-value";
|
|
1084
1163
|
calls.textContent = formatCount(entry.calls);
|
|
1085
1164
|
const triangles = document.createElement("span");
|
|
1086
1165
|
triangles.className = "perf-monitor__cost-value";
|
|
1087
1166
|
triangles.textContent = formatCount(entry.triangles);
|
|
1088
|
-
row.append(
|
|
1089
|
-
row.addEventListener("click", () => {
|
|
1090
|
-
console.log(`three-meter: ${entry.label}`, entry.objects);
|
|
1091
|
-
});
|
|
1167
|
+
row.append(calls, triangles, this.eyeButton(entry, false));
|
|
1092
1168
|
return row;
|
|
1093
1169
|
});
|
|
1094
|
-
this.costsListEl.replaceChildren(...rows);
|
|
1170
|
+
this.costsListEl.replaceChildren(...hidden, ...rows);
|
|
1171
|
+
}
|
|
1172
|
+
/** Name button (logs the objects) in a row; the caller appends the value cells and the eye. */
|
|
1173
|
+
costRowShell(entry) {
|
|
1174
|
+
const row = document.createElement("div");
|
|
1175
|
+
row.className = "perf-monitor__cost";
|
|
1176
|
+
const name = document.createElement("button");
|
|
1177
|
+
name.type = "button";
|
|
1178
|
+
name.className = "perf-monitor__cost-name";
|
|
1179
|
+
name.title = costTitle(entry);
|
|
1180
|
+
name.textContent = entry.label;
|
|
1181
|
+
const copies = copiesOf(entry);
|
|
1182
|
+
if (copies) {
|
|
1183
|
+
const suffix = document.createElement("span");
|
|
1184
|
+
suffix.className = "perf-monitor__cost-copies";
|
|
1185
|
+
suffix.textContent = ` ${copies}`;
|
|
1186
|
+
name.append(suffix);
|
|
1187
|
+
}
|
|
1188
|
+
name.addEventListener("click", () => {
|
|
1189
|
+
console.log(`three-meter: ${entry.label}`, entry.objects);
|
|
1190
|
+
});
|
|
1191
|
+
row.append(name);
|
|
1192
|
+
return row;
|
|
1193
|
+
}
|
|
1194
|
+
/** A hidden row: what hiding it saved, measured from the timing history. */
|
|
1195
|
+
hiddenCostRow(hidden) {
|
|
1196
|
+
const row = this.costRowShell(hidden.entry);
|
|
1197
|
+
row.classList.add("is-hidden");
|
|
1198
|
+
const saved = document.createElement("span");
|
|
1199
|
+
saved.className = "perf-monitor__cost-saved";
|
|
1200
|
+
const label = hidden.metric.toUpperCase();
|
|
1201
|
+
if (performance.now() - hidden.hiddenAt < MEASURE_SETTLE_MS) saved.textContent = "measuring…";
|
|
1202
|
+
else {
|
|
1203
|
+
const now = meanOfLast(this.monitor.getHistory(hidden.metric), MEASURE_FRAMES);
|
|
1204
|
+
const delta = hidden.baseline - now;
|
|
1205
|
+
saved.textContent = `${delta >= 0 ? "−" : "+"}${Math.abs(delta).toFixed(1)} ms ${label}`;
|
|
1206
|
+
saved.title = `${label} time went from ${hidden.baseline.toFixed(1)} ms to ${now.toFixed(1)} ms with this hidden, averaged over ${MEASURE_FRAMES} frames each side.`;
|
|
1207
|
+
}
|
|
1208
|
+
row.append(saved, this.eyeButton(hidden.entry, true));
|
|
1209
|
+
return row;
|
|
1210
|
+
}
|
|
1211
|
+
eyeButton(entry, hidden) {
|
|
1212
|
+
const eye = document.createElement("button");
|
|
1213
|
+
eye.type = "button";
|
|
1214
|
+
eye.className = "perf-monitor__cost-eye";
|
|
1215
|
+
eye.setAttribute("aria-pressed", String(hidden));
|
|
1216
|
+
const label = hidden ? `Show ${entry.label} again` : `Hide ${entry.label} to measure its cost`;
|
|
1217
|
+
eye.title = label;
|
|
1218
|
+
eye.setAttribute("aria-label", label);
|
|
1219
|
+
eye.append(createIcon(hidden ? "eyeOff" : "eye", "perf-monitor__icon"));
|
|
1220
|
+
eye.addEventListener("click", () => hidden ? this.showRow(entry.key) : this.hideRow(entry));
|
|
1221
|
+
return eye;
|
|
1222
|
+
}
|
|
1223
|
+
hideRow(entry) {
|
|
1224
|
+
const metric = this.monitor.getSample().gpu.available ? "gpu" : "cpu";
|
|
1225
|
+
this.hiddenRows.set(entry.key, {
|
|
1226
|
+
baseline: meanOfLast(this.monitor.getHistory(metric), MEASURE_FRAMES),
|
|
1227
|
+
entry,
|
|
1228
|
+
hiddenAt: performance.now(),
|
|
1229
|
+
metric
|
|
1230
|
+
});
|
|
1231
|
+
const all = this.monitor.getObjectsForKey(entry.key);
|
|
1232
|
+
this.hider.hide(entry.key, all.length > 0 ? all : entry.objects);
|
|
1233
|
+
this.renderCosts();
|
|
1234
|
+
}
|
|
1235
|
+
showRow(key) {
|
|
1236
|
+
this.hider.show(key);
|
|
1237
|
+
this.hiddenRows.delete(key);
|
|
1238
|
+
this.renderCosts();
|
|
1239
|
+
}
|
|
1240
|
+
/** Everything hidden goes back: on closing top costs and on dispose. */
|
|
1241
|
+
showAllHidden() {
|
|
1242
|
+
this.hider.restoreAll();
|
|
1243
|
+
this.hiddenRows.clear();
|
|
1095
1244
|
}
|
|
1096
1245
|
costsNote(text) {
|
|
1097
1246
|
const note = document.createElement("div");
|
|
@@ -1117,11 +1266,17 @@ var PerformanceView = class {
|
|
|
1117
1266
|
this.footerGpuEl.title = gpu;
|
|
1118
1267
|
this.footerHardwareEl.hidden = backend === "" && gpu === "";
|
|
1119
1268
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1269
|
+
/** Budgets follow the detected refresh rate unless `targetFps` was given. */
|
|
1270
|
+
followRefresh(hz) {
|
|
1271
|
+
if (hz === this.detectedHz) return;
|
|
1272
|
+
this.detectedHz = hz;
|
|
1273
|
+
this.budgets = resolveBudgets(this.budgetsInput, hz);
|
|
1274
|
+
this.applyBudgetTitles();
|
|
1275
|
+
}
|
|
1276
|
+
renderHud(sample, stats) {
|
|
1122
1277
|
for (const [key, element] of this.hudNumberEls) {
|
|
1123
1278
|
const config = NUMBER_BY_KEY.get(key);
|
|
1124
|
-
if (config
|
|
1279
|
+
if (config) this.paintNumber(config, element, sample, stats);
|
|
1125
1280
|
}
|
|
1126
1281
|
for (const [metric, graph] of this.hudGraphs) {
|
|
1127
1282
|
const config = TIMINGS.find((timing) => timing.metric === metric);
|
|
@@ -1129,7 +1284,7 @@ var PerformanceView = class {
|
|
|
1129
1284
|
}
|
|
1130
1285
|
}
|
|
1131
1286
|
paintNumber(config, element, sample, stats) {
|
|
1132
|
-
const value = config.value(sample, stats);
|
|
1287
|
+
const value = config.value(sample, stats, this.budgets.frameMs);
|
|
1133
1288
|
element.textContent = value === null ? "—" : config.format(value);
|
|
1134
1289
|
element.classList.toggle("is-over", isOverBudget(this.budgets, config.key, value));
|
|
1135
1290
|
}
|
|
@@ -1369,6 +1524,7 @@ var PerformanceView = class {
|
|
|
1369
1524
|
const checkbox = this.buildCheckbox(false, "Show the scene's biggest draw costs", (on) => {
|
|
1370
1525
|
this.costsOpen = on;
|
|
1371
1526
|
this.costsBodyEl.hidden = !on;
|
|
1527
|
+
if (!on) this.showAllHidden();
|
|
1372
1528
|
if (on) this.renderCosts();
|
|
1373
1529
|
});
|
|
1374
1530
|
toggleRow.append(createIcon("flame", "perf-monitor__icon"), label, checkbox);
|
|
@@ -1412,6 +1568,7 @@ var PerformanceView = class {
|
|
|
1412
1568
|
this.costSortButtons.set(sort, button);
|
|
1413
1569
|
head.append(button);
|
|
1414
1570
|
}
|
|
1571
|
+
head.append(document.createElement("span"));
|
|
1415
1572
|
this.costsListEl = document.createElement("div");
|
|
1416
1573
|
this.costsListEl.className = "perf-monitor__costs-list";
|
|
1417
1574
|
const hint = document.createElement("span");
|
|
@@ -1730,11 +1887,10 @@ const PERF_HUD_STYLES = `
|
|
|
1730
1887
|
}
|
|
1731
1888
|
|
|
1732
1889
|
.perf-monitor__cost {
|
|
1733
|
-
/* Rows are buttons, which shrink to their content without a width. */
|
|
1734
1890
|
box-sizing: border-box;
|
|
1735
1891
|
width: 100%;
|
|
1736
1892
|
display: grid;
|
|
1737
|
-
grid-template-columns: minmax(0, 1fr) 5ch 6ch;
|
|
1893
|
+
grid-template-columns: minmax(0, 1fr) 5ch 6ch 14px;
|
|
1738
1894
|
gap: 6px;
|
|
1739
1895
|
align-items: baseline;
|
|
1740
1896
|
padding: 2px 6px;
|
|
@@ -1744,13 +1900,58 @@ const PERF_HUD_STYLES = `
|
|
|
1744
1900
|
color: inherit;
|
|
1745
1901
|
font: inherit;
|
|
1746
1902
|
text-align: left;
|
|
1747
|
-
cursor:
|
|
1903
|
+
cursor: default;
|
|
1748
1904
|
}
|
|
1749
1905
|
|
|
1750
1906
|
.perf-monitor__cost:not(.perf-monitor__cost--head):hover {
|
|
1751
1907
|
background: var(--perf-row);
|
|
1752
1908
|
}
|
|
1753
1909
|
|
|
1910
|
+
.perf-monitor__cost.is-hidden .perf-monitor__cost-name {
|
|
1911
|
+
opacity: 0.6;
|
|
1912
|
+
text-decoration: line-through;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
/* What hiding saved, across the calls and tris columns. */
|
|
1916
|
+
.perf-monitor__cost-saved {
|
|
1917
|
+
grid-column: 2 / span 2;
|
|
1918
|
+
color: var(--perf-accent);
|
|
1919
|
+
font-variant-numeric: tabular-nums lining-nums;
|
|
1920
|
+
text-align: right;
|
|
1921
|
+
white-space: nowrap;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
.perf-monitor__cost-eye {
|
|
1925
|
+
display: flex;
|
|
1926
|
+
align-items: center;
|
|
1927
|
+
justify-content: center;
|
|
1928
|
+
width: 14px;
|
|
1929
|
+
height: 14px;
|
|
1930
|
+
padding: 0;
|
|
1931
|
+
border: 0;
|
|
1932
|
+
border-radius: 3px;
|
|
1933
|
+
background: transparent;
|
|
1934
|
+
color: var(--perf-muted);
|
|
1935
|
+
cursor: pointer;
|
|
1936
|
+
align-self: center;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
.perf-monitor__cost-eye .perf-monitor__icon {
|
|
1940
|
+
width: 12px;
|
|
1941
|
+
height: 12px;
|
|
1942
|
+
color: inherit;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
.perf-monitor__cost-eye:hover,
|
|
1946
|
+
.perf-monitor__cost-eye[aria-pressed="true"] {
|
|
1947
|
+
color: var(--perf-fg);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
.perf-monitor__cost-eye:focus-visible {
|
|
1951
|
+
outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
|
|
1952
|
+
outline-offset: 1px;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1754
1955
|
.perf-monitor__cost:focus-visible {
|
|
1755
1956
|
outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
|
|
1756
1957
|
outline-offset: -1px;
|
|
@@ -1761,13 +1962,30 @@ const PERF_HUD_STYLES = `
|
|
|
1761
1962
|
cursor: default;
|
|
1762
1963
|
}
|
|
1763
1964
|
|
|
1965
|
+
/* The name is a button: a click logs the row's objects. */
|
|
1764
1966
|
.perf-monitor__cost-name {
|
|
1967
|
+
min-width: 0;
|
|
1968
|
+
padding: 0;
|
|
1969
|
+
border: 0;
|
|
1970
|
+
background: transparent;
|
|
1971
|
+
font: inherit;
|
|
1972
|
+
text-align: left;
|
|
1973
|
+
cursor: pointer;
|
|
1765
1974
|
overflow: hidden;
|
|
1766
1975
|
text-overflow: ellipsis;
|
|
1767
1976
|
white-space: nowrap;
|
|
1768
1977
|
color: var(--perf-fg-dim);
|
|
1769
1978
|
}
|
|
1770
1979
|
|
|
1980
|
+
.perf-monitor__cost-name:hover {
|
|
1981
|
+
color: var(--perf-fg);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
.perf-monitor__cost-name:focus-visible {
|
|
1985
|
+
outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
|
|
1986
|
+
outline-offset: 1px;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1771
1989
|
.perf-monitor__cost-copies {
|
|
1772
1990
|
color: var(--perf-muted);
|
|
1773
1991
|
}
|
|
@@ -2234,4 +2452,4 @@ const mountPerfHud = (monitor, options = {}) => {
|
|
|
2234
2452
|
//#endregion
|
|
2235
2453
|
export { formatReport as a, HudSettings as c, isThemeMode as d, resolveBudgets as f, PerformanceView as i, HudTheme as l, PERF_HUD_STYLES as n, dockPanel as o, injectStyles as r, DEFAULT_STORAGE_KEY as s, mountPerfHud as t, THEME_MODES as u };
|
|
2236
2454
|
|
|
2237
|
-
//# sourceMappingURL=mount-perf-hud-
|
|
2455
|
+
//# sourceMappingURL=mount-perf-hud-U_Xlj0Od.js.map
|