@pascal-app/viewer 1.0.0-beta.5 → 1.0.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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/capture/asset-url.d.ts +2 -0
- package/dist/capture/asset-url.d.ts.map +1 -0
- package/dist/capture/asset-url.js +13 -0
- package/dist/capture/capture-runtime.d.ts +43 -0
- package/dist/capture/capture-runtime.d.ts.map +1 -0
- package/dist/capture/capture-runtime.js +228 -0
- package/dist/capture/frame.d.ts +4 -0
- package/dist/capture/frame.d.ts.map +1 -0
- package/dist/capture/frame.js +21 -0
- package/dist/capture/index.d.ts +12 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +11 -0
- package/dist/capture/layer-visibility.d.ts +5 -0
- package/dist/capture/layer-visibility.d.ts.map +1 -0
- package/dist/capture/layer-visibility.js +11 -0
- package/dist/capture/layers/clay-matcap.d.ts +3 -0
- package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
- package/dist/capture/layers/clay-matcap.js +26 -0
- package/dist/capture/layers/device-motion-layer.d.ts +7 -0
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
- package/dist/capture/layers/device-motion-layer.js +78 -0
- package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
- package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
- package/dist/capture/layers/point-cloud-layer.js +106 -0
- package/dist/capture/layers/room-model-layer.d.ts +9 -0
- package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
- package/dist/capture/layers/room-model-layer.js +71 -0
- package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
- package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-data.js +74 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-layer.js +16 -0
- package/dist/capture/preview.d.ts +3 -0
- package/dist/capture/preview.d.ts.map +1 -0
- package/dist/capture/preview.js +2 -0
- package/dist/capture/source-state.d.ts +22 -0
- package/dist/capture/source-state.d.ts.map +1 -0
- package/dist/capture/source-state.js +182 -0
- package/dist/capture/stream-rendering.d.ts +14 -0
- package/dist/capture/stream-rendering.d.ts.map +1 -0
- package/dist/capture/stream-rendering.js +69 -0
- package/dist/capture/trajectory.d.ts +20 -0
- package/dist/capture/trajectory.d.ts.map +1 -0
- package/dist/capture/trajectory.js +77 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
- package/dist/components/viewer/batched-mesh-spike.js +64 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +4 -3
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +70 -62
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +123 -89
- package/dist/components/viewer/perf-monitor.d.ts +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +307 -116
- package/dist/components/viewer/perf-panel.d.ts +3 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -0
- package/dist/components/viewer/perf-panel.js +178 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
- package/dist/components/viewer/pointer-raycast-layers.js +28 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +124 -83
- package/dist/components/viewer/registered-systems.d.ts +14 -5
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +22 -10
- package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
- package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/scene-atmosphere.js +89 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +19 -5
- package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
- package/dist/components/viewer/scene-ground-replacement.js +35 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +4 -1
- package/dist/components/viewer/viewer-presentations.d.ts +67 -0
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
- package/dist/components/viewer/viewer-presentations.js +120 -0
- package/dist/hooks/use-library-materials-version.d.ts +7 -0
- package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
- package/dist/hooks/use-library-materials-version.js +10 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +1 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -6
- package/dist/lib/csg-utils.d.ts +1 -0
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +6 -1
- package/dist/lib/gpu-perf.d.ts +0 -2
- package/dist/lib/gpu-perf.d.ts.map +1 -1
- package/dist/lib/gpu-perf.js +7 -21
- package/dist/lib/layer-pass.d.ts +28 -0
- package/dist/lib/layer-pass.d.ts.map +1 -0
- package/dist/lib/layer-pass.js +254 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +51 -17
- package/dist/lib/merged-outline-node.d.ts +17 -8
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +194 -52
- package/dist/lib/perf-actions.d.ts +55 -0
- package/dist/lib/perf-actions.d.ts.map +1 -0
- package/dist/lib/perf-actions.js +211 -0
- package/dist/lib/perf-observers.d.ts +2 -0
- package/dist/lib/perf-observers.d.ts.map +1 -0
- package/dist/lib/perf-observers.js +17 -0
- package/dist/lib/perf-panel-store.d.ts +71 -0
- package/dist/lib/perf-panel-store.d.ts.map +1 -0
- package/dist/lib/perf-panel-store.js +29 -0
- package/dist/lib/perf-tracks.d.ts +43 -0
- package/dist/lib/perf-tracks.d.ts.map +1 -0
- package/dist/lib/perf-tracks.js +113 -0
- package/dist/lib/pointer-events.d.ts +7 -0
- package/dist/lib/pointer-events.d.ts.map +1 -0
- package/dist/lib/pointer-events.js +680 -0
- package/dist/lib/post-processing-resources.d.ts +12 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -0
- package/dist/lib/post-processing-resources.js +19 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +4 -2
- package/dist/lib/scene-visibility.d.ts +2 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +22 -1
- package/dist/store/use-viewer.d.ts +2 -2
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +1 -1
- package/dist/systems/door/door-animation-system.d.ts.map +1 -1
- package/dist/systems/door/door-animation-system.js +6 -9
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +13 -2
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +3 -2
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +1 -1
- package/dist/systems/level/level-utils.d.ts +9 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +14 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1196 -58
- package/dist/systems/scan/scan-system.js +6 -5
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +1 -2
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +9 -8
- package/dist/systems/surface-hole-geometry.d.ts +1 -1
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
- package/dist/systems/surface-hole-geometry.js +1 -1
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
- package/dist/systems/wall/wall-build-lifecycle.js +74 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout-cache.js +304 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +24 -161
- package/dist/systems/wall/wall-material-variant.d.ts +31 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
- package/dist/systems/wall/wall-material-variant.js +10 -0
- package/dist/systems/wall/wall-materials.d.ts +6 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +29 -13
- package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
- package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
- package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
- package/dist/systems/wall/wall-system.d.ts +11 -4
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +405 -153
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +9 -8
- package/dist/systems/window/window-system.d.ts +1 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +16 -2
- package/package.json +17 -4
- package/dist/lib/polygon-union.d.ts +0 -4
- package/dist/lib/polygon-union.d.ts.map +0 -1
- package/dist/lib/polygon-union.js +0 -289
|
@@ -1,38 +1,227 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useScene } from '@pascal-app/core';
|
|
3
|
-
import { Html } from '@react-three/drei';
|
|
1
|
+
import { sceneRegistry, useScene } from '@pascal-app/core';
|
|
4
2
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
5
|
-
import { useEffect, useRef
|
|
6
|
-
import {
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { Vector3 } from 'three';
|
|
5
|
+
import { initPerfObservers } from '../../lib/perf-observers';
|
|
6
|
+
import { publishPerfStats, readPerfBatchStats } from '../../lib/perf-panel-store';
|
|
7
|
+
import { clearPerfMeasures, drainPerfCounters } from '../../lib/perf-tracks';
|
|
7
8
|
const SAMPLE_INTERVAL = 0.5; // seconds between display updates
|
|
9
|
+
// Walking the scene graph is the overlay's own biggest cost on large projects,
|
|
10
|
+
// and the counts barely move between ticks — sample it at 2s instead of 0.5s.
|
|
11
|
+
const CENSUS_EVERY_TICKS = 4;
|
|
12
|
+
const MAX_TRACK_LINES = 8;
|
|
13
|
+
// Tracks printed on their own lines above (render path + the frame-limiter's
|
|
14
|
+
// whole-frame span); everything else drained from perf-tracks lands in TRACKS.
|
|
15
|
+
const RENDER_TRACKS = new Set(['gpu-render', 'gpu-queue', 'render-encode', 'frame-cpu']);
|
|
16
|
+
/**
|
|
17
|
+
* `scene.traverse` descends into hidden subtrees, so a collapsed level or an
|
|
18
|
+
* isolated-away wing still inflated the counts. Recurse manually and cut at the
|
|
19
|
+
* first invisible node — that matches what the renderer actually walks.
|
|
20
|
+
*/
|
|
21
|
+
function countVisible(object, out) {
|
|
22
|
+
if (object.visible === false)
|
|
23
|
+
return;
|
|
24
|
+
if (object.isMesh)
|
|
25
|
+
out.meshes++;
|
|
26
|
+
else if (object.isLine || object.isLineSegments || object.isLineLoop)
|
|
27
|
+
out.lines++;
|
|
28
|
+
else if (object.isSprite)
|
|
29
|
+
out.sprites++;
|
|
30
|
+
else if (object.isLight)
|
|
31
|
+
out.lights++;
|
|
32
|
+
const children = object.children;
|
|
33
|
+
if (!children)
|
|
34
|
+
return;
|
|
35
|
+
for (let i = 0; i < children.length; i++)
|
|
36
|
+
countVisible(children[i], out);
|
|
37
|
+
}
|
|
38
|
+
function averageOf(bucket) {
|
|
39
|
+
if (!bucket || bucket.count === 0)
|
|
40
|
+
return null;
|
|
41
|
+
return bucket.totalMs / bucket.count;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Headless collector. Runs inside <Canvas> (it needs useFrame + gl.info) and
|
|
45
|
+
* publishes each window's stats to perf-panel-store; the visible panel is
|
|
46
|
+
* <PerfPanel>, mounted outside the canvas — see perf-panel.tsx for why.
|
|
47
|
+
*/
|
|
8
48
|
export const PerfMonitor = () => {
|
|
9
|
-
const [stats, setStats] = useState({
|
|
10
|
-
fps: 0,
|
|
11
|
-
frameMs: 0,
|
|
12
|
-
gpuMs: 0,
|
|
13
|
-
gpuMaxMs: 0,
|
|
14
|
-
drawCalls: 0,
|
|
15
|
-
triangles: 0,
|
|
16
|
-
dirty: 0,
|
|
17
|
-
dirtyDetail: '',
|
|
18
|
-
meshes: 0,
|
|
19
|
-
lines: 0,
|
|
20
|
-
sprites: 0,
|
|
21
|
-
lights: 0,
|
|
22
|
-
});
|
|
23
49
|
const frameCount = useRef(0);
|
|
24
50
|
const elapsed = useRef(0);
|
|
25
|
-
const
|
|
51
|
+
const tickCount = useRef(0);
|
|
26
52
|
// Carry the previous tick's reading forward when no fresh samples arrive,
|
|
27
53
|
// so the display doesn't flicker to "—" on slow resolve windows.
|
|
28
|
-
const
|
|
29
|
-
const
|
|
54
|
+
const lastFrame = useRef({ ms: 0, max: 0 });
|
|
55
|
+
const lastGpu = useRef({ ms: 0, max: 0, seen: false });
|
|
56
|
+
const lastQueue = useRef({ ms: 0, max: 0 });
|
|
57
|
+
const lastEncode = useRef({ ms: 0, max: 0 });
|
|
58
|
+
const lastCensus = useRef({ meshes: 0, lines: 0, sprites: 0, lights: 0 });
|
|
30
59
|
// Take ownership of info reset. The custom RenderPipeline.render() path
|
|
31
60
|
// we use in post-processing doesn't trigger three.js's automatic per-frame
|
|
32
|
-
// info reset, so
|
|
61
|
+
// info reset, so drawCalls/triangles accumulate across frames and the display
|
|
33
62
|
// shows lifetime totals. Disabling autoReset and explicitly resetting at
|
|
34
63
|
// each window gives true per-frame averages.
|
|
35
64
|
const gl = useThree((s) => s.gl);
|
|
65
|
+
const getThree = useThree((s) => s.get);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
initPerfObservers();
|
|
68
|
+
}, []);
|
|
69
|
+
// Scripted-probe hooks for the scaling-matrix runner (scripts/perf/…): only
|
|
70
|
+
// mounted with `?perf`, so nothing reaches `window` in normal sessions.
|
|
71
|
+
// `projectNode` returns CSS pixels relative to the canvas, ready for a
|
|
72
|
+
// synthetic click on the node.
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const probe = {
|
|
75
|
+
batchStats: readPerfBatchStats,
|
|
76
|
+
listNodes(type) {
|
|
77
|
+
return Object.values(useScene.getState().nodes)
|
|
78
|
+
.filter((n) => n.type === type)
|
|
79
|
+
.map((n) => n.id);
|
|
80
|
+
},
|
|
81
|
+
// Raw dirty-set census: total marks, marks whose node is gone (phantoms),
|
|
82
|
+
// and live marks bucketed by node kind. The panel's DIRTY readout filters
|
|
83
|
+
// to live nodes, so scripted runs need this to see leaks at all.
|
|
84
|
+
dirtyResidue() {
|
|
85
|
+
const { dirtyNodes, nodes } = useScene.getState();
|
|
86
|
+
const phantomIds = [];
|
|
87
|
+
const liveByType = {};
|
|
88
|
+
for (const id of dirtyNodes) {
|
|
89
|
+
const node = nodes[id];
|
|
90
|
+
if (!node)
|
|
91
|
+
phantomIds.push(id);
|
|
92
|
+
else
|
|
93
|
+
liveByType[node.type] = (liveByType[node.type] ?? 0) + 1;
|
|
94
|
+
}
|
|
95
|
+
return { total: dirtyNodes.size, phantom: phantomIds.length, phantomIds, liveByType };
|
|
96
|
+
},
|
|
97
|
+
// Draw-call composition census for the item/draw-reduction work
|
|
98
|
+
// (charter backlog #3): how item draws decompose per item and per
|
|
99
|
+
// asset, plus projected draw counts for the two candidate techniques —
|
|
100
|
+
// per-item merge-by-material and per-asset instancing.
|
|
101
|
+
drawComposition() {
|
|
102
|
+
const { nodes } = useScene.getState();
|
|
103
|
+
const assets = new Map();
|
|
104
|
+
const meshesPerItem = [];
|
|
105
|
+
let itemMeshes = 0;
|
|
106
|
+
let perItemMerge = 0;
|
|
107
|
+
for (const node of Object.values(nodes)) {
|
|
108
|
+
if (node.type !== 'item')
|
|
109
|
+
continue;
|
|
110
|
+
const group = sceneRegistry.nodes.get(node.id);
|
|
111
|
+
if (!group)
|
|
112
|
+
continue;
|
|
113
|
+
let meshes = 0;
|
|
114
|
+
const materials = new Set();
|
|
115
|
+
const meshKeys = new Set();
|
|
116
|
+
group.traverse((child) => {
|
|
117
|
+
if (!child.isMesh || child.visible === false)
|
|
118
|
+
return;
|
|
119
|
+
meshes++;
|
|
120
|
+
const mats = Array.isArray(child.material) ? child.material : [child.material];
|
|
121
|
+
for (const m of mats)
|
|
122
|
+
if (m)
|
|
123
|
+
materials.add(m.uuid);
|
|
124
|
+
meshKeys.add(`${child.geometry?.uuid ?? '?'}|${mats.map((m) => m?.uuid ?? '?').join(',')}`);
|
|
125
|
+
});
|
|
126
|
+
if (meshes === 0)
|
|
127
|
+
continue;
|
|
128
|
+
itemMeshes += meshes;
|
|
129
|
+
meshesPerItem.push(meshes);
|
|
130
|
+
perItemMerge += materials.size;
|
|
131
|
+
const asset = node.asset;
|
|
132
|
+
const assetId = asset?.id ?? 'unknown';
|
|
133
|
+
const agg = assets.get(assetId) ?? {
|
|
134
|
+
name: asset?.name ?? assetId,
|
|
135
|
+
copies: 0,
|
|
136
|
+
meshesPerCopy: meshes,
|
|
137
|
+
materialsPerCopy: materials.size,
|
|
138
|
+
uniqueMeshKeys: new Set(),
|
|
139
|
+
uniqueMaterials: new Set(),
|
|
140
|
+
};
|
|
141
|
+
agg.copies++;
|
|
142
|
+
for (const k of meshKeys)
|
|
143
|
+
agg.uniqueMeshKeys.add(k);
|
|
144
|
+
for (const m of materials)
|
|
145
|
+
agg.uniqueMaterials.add(m);
|
|
146
|
+
assets.set(assetId, agg);
|
|
147
|
+
}
|
|
148
|
+
// Bucket every registered node's meshes by kind so the non-item side
|
|
149
|
+
// of the draw budget is attributable too.
|
|
150
|
+
const meshesByKind = {};
|
|
151
|
+
let registeredMeshes = 0;
|
|
152
|
+
for (const node of Object.values(nodes)) {
|
|
153
|
+
const group = sceneRegistry.nodes.get(node.id);
|
|
154
|
+
if (!group)
|
|
155
|
+
continue;
|
|
156
|
+
let count = 0;
|
|
157
|
+
group.traverse((child) => {
|
|
158
|
+
if (child.isMesh && child.visible !== false)
|
|
159
|
+
count++;
|
|
160
|
+
});
|
|
161
|
+
if (count === 0)
|
|
162
|
+
continue;
|
|
163
|
+
meshesByKind[node.type] = (meshesByKind[node.type] ?? 0) + count;
|
|
164
|
+
registeredMeshes += count;
|
|
165
|
+
}
|
|
166
|
+
let otherVisibleMeshes = 0;
|
|
167
|
+
const { scene } = getThree();
|
|
168
|
+
scene.traverse((child) => {
|
|
169
|
+
if (child.isMesh && child.visible !== false)
|
|
170
|
+
otherVisibleMeshes++;
|
|
171
|
+
});
|
|
172
|
+
meshesByKind['(unregistered)'] = Math.max(0, otherVisibleMeshes - registeredMeshes);
|
|
173
|
+
otherVisibleMeshes -= itemMeshes;
|
|
174
|
+
let instancedByAsset = 0;
|
|
175
|
+
let both = 0;
|
|
176
|
+
for (const agg of assets.values()) {
|
|
177
|
+
instancedByAsset += agg.uniqueMeshKeys.size;
|
|
178
|
+
both += agg.uniqueMaterials.size;
|
|
179
|
+
}
|
|
180
|
+
const sorted = [...meshesPerItem].sort((a, b) => a - b);
|
|
181
|
+
return {
|
|
182
|
+
items: meshesPerItem.length,
|
|
183
|
+
itemMeshes,
|
|
184
|
+
otherVisibleMeshes,
|
|
185
|
+
meshesByKind,
|
|
186
|
+
perItemMeshes: {
|
|
187
|
+
avg: Number((itemMeshes / Math.max(1, meshesPerItem.length)).toFixed(1)),
|
|
188
|
+
p50: sorted[Math.floor(sorted.length / 2)] ?? 0,
|
|
189
|
+
max: sorted[sorted.length - 1] ?? 0,
|
|
190
|
+
},
|
|
191
|
+
assets: [...assets.entries()]
|
|
192
|
+
.map(([id, a]) => ({
|
|
193
|
+
id,
|
|
194
|
+
name: a.name,
|
|
195
|
+
copies: a.copies,
|
|
196
|
+
meshesPerCopy: a.meshesPerCopy,
|
|
197
|
+
materialsPerCopy: a.materialsPerCopy,
|
|
198
|
+
}))
|
|
199
|
+
.sort((a, b) => b.copies * b.meshesPerCopy - a.copies * a.meshesPerCopy)
|
|
200
|
+
.slice(0, 15),
|
|
201
|
+
projected: { current: itemMeshes, perItemMerge, instancedByAsset, both },
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
projectNode(nodeId) {
|
|
205
|
+
const object = sceneRegistry.nodes.get(nodeId);
|
|
206
|
+
if (!object)
|
|
207
|
+
return null;
|
|
208
|
+
const { camera, size } = getThree();
|
|
209
|
+
const v = new Vector3();
|
|
210
|
+
object.getWorldPosition(v);
|
|
211
|
+
v.project(camera);
|
|
212
|
+
return {
|
|
213
|
+
x: ((v.x + 1) / 2) * size.width,
|
|
214
|
+
y: ((1 - v.y) / 2) * size.height,
|
|
215
|
+
behindCamera: v.z > 1,
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
window.__pascalPerf = probe;
|
|
220
|
+
return () => {
|
|
221
|
+
if (window.__pascalPerf === probe)
|
|
222
|
+
delete window.__pascalPerf;
|
|
223
|
+
};
|
|
224
|
+
}, [getThree]);
|
|
36
225
|
useEffect(() => {
|
|
37
226
|
if (!gl?.info)
|
|
38
227
|
return;
|
|
@@ -43,103 +232,105 @@ export const PerfMonitor = () => {
|
|
|
43
232
|
gl.info.autoReset = previousAutoReset;
|
|
44
233
|
};
|
|
45
234
|
}, [gl]);
|
|
46
|
-
useFrame(({ gl, scene, clock }
|
|
235
|
+
useFrame(({ gl, scene, clock }) => {
|
|
47
236
|
frameCount.current++;
|
|
48
237
|
const now = clock.elapsedTime;
|
|
49
238
|
const dt = now - elapsed.current;
|
|
50
|
-
if (dt
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.map(([type, count]) => `${count} ${type}`)
|
|
73
|
-
.join(', ');
|
|
74
|
-
}
|
|
75
|
-
// Count visible drawables by type so we can match scene contents
|
|
76
|
-
// against the renderer's draw count and find hidden contributors.
|
|
77
|
-
let meshes = 0;
|
|
78
|
-
let lines = 0;
|
|
79
|
-
let sprites = 0;
|
|
80
|
-
let lights = 0;
|
|
81
|
-
scene.traverse((obj) => {
|
|
82
|
-
if (!obj.visible)
|
|
83
|
-
return;
|
|
84
|
-
if (obj.isMesh)
|
|
85
|
-
meshes++;
|
|
86
|
-
else if (obj.isLine || obj.isLineSegments || obj.isLineLoop)
|
|
87
|
-
lines++;
|
|
88
|
-
else if (obj.isSprite)
|
|
89
|
-
sprites++;
|
|
90
|
-
else if (obj.isLight)
|
|
91
|
-
lights++;
|
|
92
|
-
});
|
|
93
|
-
// GPU samples are pushed by post-processing.tsx after each pipeline
|
|
94
|
-
// render via device.queue.onSubmittedWorkDone(). We drain whatever
|
|
95
|
-
// has accumulated since the last tick.
|
|
96
|
-
const samples = drainGpuSamples();
|
|
97
|
-
if (samples.length > 0) {
|
|
98
|
-
let sum = 0;
|
|
99
|
-
let max = 0;
|
|
100
|
-
for (const s of samples) {
|
|
101
|
-
sum += s;
|
|
102
|
-
if (s > max)
|
|
103
|
-
max = s;
|
|
104
|
-
}
|
|
105
|
-
lastGpuMs.current = sum / samples.length;
|
|
106
|
-
lastGpuMaxMs.current = max;
|
|
239
|
+
if (dt < SAMPLE_INTERVAL)
|
|
240
|
+
return;
|
|
241
|
+
tickCount.current++;
|
|
242
|
+
const fps = Math.round(frameCount.current / dt);
|
|
243
|
+
const info = gl.info;
|
|
244
|
+
// drawCalls (NOT `calls`, which counts renderer.render() invocations for the
|
|
245
|
+
// lifetime of the renderer and is never cleared by reset()) has been
|
|
246
|
+
// accumulating since the last reset at the start of this window.
|
|
247
|
+
const totalDrawCalls = info.render?.drawCalls ?? 0;
|
|
248
|
+
const totalTriangles = info.render?.triangles ?? 0;
|
|
249
|
+
const drawCalls = Math.round(totalDrawCalls / Math.max(1, frameCount.current));
|
|
250
|
+
const triangles = totalTriangles / Math.max(1, frameCount.current);
|
|
251
|
+
const memory = info.memory ?? {};
|
|
252
|
+
info.reset();
|
|
253
|
+
const sceneState = useScene.getState();
|
|
254
|
+
const dirty = sceneState.dirtyNodes.size;
|
|
255
|
+
let dirtyDetail = '';
|
|
256
|
+
if (dirty > 0) {
|
|
257
|
+
const counts = new Map();
|
|
258
|
+
for (const id of sceneState.dirtyNodes) {
|
|
259
|
+
const type = sceneState.nodes[id]?.type ?? 'missing';
|
|
260
|
+
counts.set(type, (counts.get(type) ?? 0) + 1);
|
|
107
261
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
262
|
+
dirtyDetail = [...counts.entries()]
|
|
263
|
+
.sort((a, b) => b[1] - a[1])
|
|
264
|
+
.map(([type, count]) => `${count} ${type}`)
|
|
265
|
+
.join(', ');
|
|
266
|
+
}
|
|
267
|
+
if (tickCount.current % CENSUS_EVERY_TICKS === 1) {
|
|
268
|
+
const census = { meshes: 0, lines: 0, sprites: 0, lights: 0 };
|
|
269
|
+
countVisible(scene, census);
|
|
270
|
+
lastCensus.current = census;
|
|
271
|
+
}
|
|
272
|
+
const batch = readPerfBatchStats();
|
|
273
|
+
const counters = drainPerfCounters();
|
|
274
|
+
// Whole-frame main-thread work measured around FrameLimiter's advance()
|
|
275
|
+
// call — this is CPU time per frame, unlike FPS which is just cadence.
|
|
276
|
+
const frameAvg = averageOf(counters.get('frame-cpu'));
|
|
277
|
+
if (frameAvg !== null) {
|
|
278
|
+
lastFrame.current = { ms: frameAvg, max: counters.get('frame-cpu')?.maxMs ?? 0 };
|
|
279
|
+
}
|
|
280
|
+
const gpuAvg = averageOf(counters.get('gpu-render'));
|
|
281
|
+
if (gpuAvg !== null) {
|
|
282
|
+
lastGpu.current = { ms: gpuAvg, max: counters.get('gpu-render')?.maxMs ?? 0, seen: true };
|
|
283
|
+
}
|
|
284
|
+
const queueAvg = averageOf(counters.get('gpu-queue'));
|
|
285
|
+
if (queueAvg !== null) {
|
|
286
|
+
lastQueue.current = { ms: queueAvg, max: counters.get('gpu-queue')?.maxMs ?? 0 };
|
|
287
|
+
}
|
|
288
|
+
const encodeAvg = averageOf(counters.get('render-encode'));
|
|
289
|
+
if (encodeAvg !== null) {
|
|
290
|
+
lastEncode.current = { ms: encodeAvg, max: counters.get('render-encode')?.maxMs ?? 0 };
|
|
124
291
|
}
|
|
125
|
-
|
|
292
|
+
const tracks = [...counters.entries()]
|
|
293
|
+
.filter(([name, bucket]) => !RENDER_TRACKS.has(name) && bucket.count > 0)
|
|
294
|
+
.map(([name, bucket]) => ({
|
|
295
|
+
name,
|
|
296
|
+
totalMs: bucket.totalMs,
|
|
297
|
+
count: bucket.count,
|
|
298
|
+
maxMs: bucket.maxMs,
|
|
299
|
+
}))
|
|
300
|
+
.sort((a, b) => b.totalMs - a.totalMs)
|
|
301
|
+
.slice(0, MAX_TRACK_LINES);
|
|
302
|
+
publishPerfStats({
|
|
303
|
+
fps,
|
|
304
|
+
frameMs: lastFrame.current.ms,
|
|
305
|
+
frameMaxMs: lastFrame.current.max,
|
|
306
|
+
encodeMs: lastEncode.current.ms,
|
|
307
|
+
encodeMaxMs: lastEncode.current.max,
|
|
308
|
+
gpuMs: lastGpu.current.ms,
|
|
309
|
+
gpuMaxMs: lastGpu.current.max,
|
|
310
|
+
gpuTracked: lastGpu.current.seen,
|
|
311
|
+
queueMs: lastQueue.current.ms,
|
|
312
|
+
queueMaxMs: lastQueue.current.max,
|
|
313
|
+
drawCalls,
|
|
314
|
+
triangles,
|
|
315
|
+
batch,
|
|
316
|
+
dirty,
|
|
317
|
+
dirtyDetail,
|
|
318
|
+
geometries: memory.geometries ?? 0,
|
|
319
|
+
textures: memory.textures ?? 0,
|
|
320
|
+
gpuBytes: memory.total ?? 0,
|
|
321
|
+
heapBytes: performance.memory?.usedJSHeapSize ?? 0,
|
|
322
|
+
meshes: lastCensus.current.meshes,
|
|
323
|
+
lines: lastCensus.current.lines,
|
|
324
|
+
sprites: lastCensus.current.sprites,
|
|
325
|
+
lights: lastCensus.current.lights,
|
|
326
|
+
tracks,
|
|
327
|
+
});
|
|
328
|
+
// perf-tracks emits a `performance.measure` per span for the DevTools
|
|
329
|
+
// custom tracks. The recording already captured them; without this the
|
|
330
|
+
// timeline buffer grows for the whole session.
|
|
331
|
+
clearPerfMeasures();
|
|
332
|
+
frameCount.current = 0;
|
|
333
|
+
elapsed.current = now;
|
|
126
334
|
});
|
|
127
|
-
return
|
|
128
|
-
fontFamily: 'monospace',
|
|
129
|
-
fontSize: 11,
|
|
130
|
-
lineHeight: 1.5,
|
|
131
|
-
color: stats.fps < 30 ? '#f87171' : stats.fps < 55 ? '#fbbf24' : '#4ade80',
|
|
132
|
-
background: 'rgba(0,0,0,0.7)',
|
|
133
|
-
borderRadius: 6,
|
|
134
|
-
padding: '6px 10px',
|
|
135
|
-
whiteSpace: 'pre',
|
|
136
|
-
}, children: `FPS ${stats.fps}
|
|
137
|
-
GPU ${stats.gpuMs > 0 ? `${stats.gpuMs.toFixed(1)}ms (max ${stats.gpuMaxMs.toFixed(1)})` : '—'}
|
|
138
|
-
DRAW ${stats.drawCalls}
|
|
139
|
-
TRI ${(stats.triangles / 1000).toFixed(1)}k
|
|
140
|
-
DIRTY ${stats.dirty}${stats.dirtyDetail ? ` (${stats.dirtyDetail})` : ''}
|
|
141
|
-
MESH ${stats.meshes}
|
|
142
|
-
LINE ${stats.lines}
|
|
143
|
-
SPRITE ${stats.sprites}
|
|
144
|
-
LIGHT ${stats.lights}` }) }));
|
|
335
|
+
return null;
|
|
145
336
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-panel.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/perf-panel.tsx"],"names":[],"mappings":"AA2GA,eAAO,MAAM,SAAS,0CA2MrB,CAAA;AAED,eAAe,SAAS,CAAA"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { usePerfActionReceipts } from '../../lib/perf-actions';
|
|
6
|
+
import { usePerfStats } from '../../lib/perf-panel-store';
|
|
7
|
+
// Rendered OUTSIDE <Canvas> (drei <Html> wrappers carry a camera-driven
|
|
8
|
+
// transform, which turns position:fixed into "fixed relative to the wrapper"
|
|
9
|
+
// and made the old overlay drift with the camera). Portal to <body> so no
|
|
10
|
+
// ancestor transform/overflow can capture it.
|
|
11
|
+
const STORAGE_KEY = 'pascal-perf-panel';
|
|
12
|
+
const PANEL_WIDTH = 248;
|
|
13
|
+
function loadPlacement() {
|
|
14
|
+
try {
|
|
15
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
16
|
+
if (raw)
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
catch { }
|
|
20
|
+
return { x: 8, y: 8, docked: null };
|
|
21
|
+
}
|
|
22
|
+
function savePlacement(placement) {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(placement));
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
}
|
|
28
|
+
function fpsColor(fps) {
|
|
29
|
+
return fps < 30 ? '#f87171' : fps < 48 ? '#fbbf24' : '#4ade80';
|
|
30
|
+
}
|
|
31
|
+
function mb(bytes) {
|
|
32
|
+
return `${Math.round(bytes / (1024 * 1024))}MB`;
|
|
33
|
+
}
|
|
34
|
+
const label = { color: '#8b90a0' };
|
|
35
|
+
const value = { textAlign: 'right', color: '#e7e9f0' };
|
|
36
|
+
const grid = {
|
|
37
|
+
display: 'grid',
|
|
38
|
+
gridTemplateColumns: 'auto 1fr',
|
|
39
|
+
columnGap: 12,
|
|
40
|
+
};
|
|
41
|
+
const section = {
|
|
42
|
+
marginTop: 6,
|
|
43
|
+
paddingTop: 6,
|
|
44
|
+
borderTop: '1px solid rgba(255,255,255,0.07)',
|
|
45
|
+
};
|
|
46
|
+
const clip = {
|
|
47
|
+
overflow: 'hidden',
|
|
48
|
+
textOverflow: 'ellipsis',
|
|
49
|
+
whiteSpace: 'nowrap',
|
|
50
|
+
};
|
|
51
|
+
const Row = ({ name, children }) => (_jsxs(_Fragment, { children: [_jsx("span", { style: label, children: name }), _jsx("span", { style: value, children: children })] }));
|
|
52
|
+
const ACTION_TRACK_LINES = 4;
|
|
53
|
+
const OLDER_ACTION_LINES = 2;
|
|
54
|
+
/**
|
|
55
|
+
* Cost of the last edit gesture, from the action ledger — see lib/perf-actions.ts
|
|
56
|
+
* for what counts as settled. Amber total = the action never settled (the user
|
|
57
|
+
* started another one, or it blew the settle budget).
|
|
58
|
+
*/
|
|
59
|
+
const LastAction = () => {
|
|
60
|
+
const [latest, ...older] = usePerfActionReceipts();
|
|
61
|
+
if (!latest)
|
|
62
|
+
return null;
|
|
63
|
+
return (_jsxs("div", { style: section, children: [_jsx("div", { style: { ...label, opacity: 0.7 }, children: "last action" }), _jsxs("div", { style: grid, children: [_jsx("span", { style: { ...value, textAlign: 'left', ...clip }, children: latest.detail ? `${latest.name} ${latest.detail}` : latest.name }), _jsx("span", { style: { ...value, color: latest.outcome === 'settled' ? '#e7e9f0' : '#fbbf24' }, children: latest.outcome === 'settled'
|
|
64
|
+
? `${latest.totalMs.toFixed(0)}ms`
|
|
65
|
+
: `${latest.totalMs.toFixed(0)}ms ${latest.outcome}` })] }), _jsx("div", { style: { ...label, ...clip }, children: `drag ${latest.dragMs.toFixed(0)} / settle ${latest.settleMs.toFixed(0)} (${latest.settleFrames} frames)` }), latest.tracks.slice(0, ACTION_TRACK_LINES).map((track) => (_jsxs("div", { style: grid, children: [_jsx("span", { style: label, children: track.name }), _jsx("span", { style: value, children: `${track.totalMs.toFixed(1)}ms (${track.count}×)` })] }, track.name))), older.slice(0, OLDER_ACTION_LINES).map((receipt) => (_jsxs("div", { style: { ...grid, opacity: 0.55 }, children: [_jsx("span", { style: { ...label, ...clip }, children: receipt.name }), _jsx("span", { style: value, children: `${receipt.totalMs.toFixed(0)}ms` })] }, receipt.endedAt)))] }));
|
|
66
|
+
};
|
|
67
|
+
export const PerfPanel = () => {
|
|
68
|
+
const stats = usePerfStats();
|
|
69
|
+
const [placement, setPlacement] = useState(loadPlacement);
|
|
70
|
+
const dragRef = useRef(null);
|
|
71
|
+
const panelRef = useRef(null);
|
|
72
|
+
useEffect(() => savePlacement(placement), [placement]);
|
|
73
|
+
const onPointerDown = useCallback((e) => {
|
|
74
|
+
const el = panelRef.current;
|
|
75
|
+
if (!el)
|
|
76
|
+
return;
|
|
77
|
+
const rect = el.getBoundingClientRect();
|
|
78
|
+
dragRef.current = {
|
|
79
|
+
pointerId: e.pointerId,
|
|
80
|
+
dx: e.clientX - rect.left,
|
|
81
|
+
dy: e.clientY - rect.top,
|
|
82
|
+
};
|
|
83
|
+
e.target.setPointerCapture(e.pointerId);
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
}, []);
|
|
86
|
+
const onPointerMove = useCallback((e) => {
|
|
87
|
+
const drag = dragRef.current;
|
|
88
|
+
if (!drag || drag.pointerId !== e.pointerId)
|
|
89
|
+
return;
|
|
90
|
+
const el = panelRef.current;
|
|
91
|
+
const w = el?.offsetWidth ?? PANEL_WIDTH;
|
|
92
|
+
const h = el?.offsetHeight ?? 200;
|
|
93
|
+
setPlacement((p) => ({
|
|
94
|
+
...p,
|
|
95
|
+
x: Math.min(Math.max(0, e.clientX - drag.dx), window.innerWidth - w),
|
|
96
|
+
y: Math.min(Math.max(0, e.clientY - drag.dy), window.innerHeight - h),
|
|
97
|
+
}));
|
|
98
|
+
}, []);
|
|
99
|
+
const onPointerUp = useCallback((e) => {
|
|
100
|
+
if (dragRef.current?.pointerId === e.pointerId)
|
|
101
|
+
dragRef.current = null;
|
|
102
|
+
}, []);
|
|
103
|
+
const dock = useCallback(() => {
|
|
104
|
+
setPlacement((p) => ({
|
|
105
|
+
...p,
|
|
106
|
+
docked: p.x + PANEL_WIDTH / 2 < window.innerWidth / 2 ? 'left' : 'right',
|
|
107
|
+
}));
|
|
108
|
+
}, []);
|
|
109
|
+
if (typeof document === 'undefined')
|
|
110
|
+
return null;
|
|
111
|
+
if (placement.docked) {
|
|
112
|
+
const side = placement.docked;
|
|
113
|
+
return createPortal(_jsx("button", { "data-pascal-perf-panel": "docked", onClick: () => setPlacement((p) => ({ ...p, docked: null })), style: {
|
|
114
|
+
position: 'fixed',
|
|
115
|
+
top: Math.min(placement.y, window.innerHeight - 40),
|
|
116
|
+
[side]: 0,
|
|
117
|
+
zIndex: 1000,
|
|
118
|
+
display: 'flex',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
gap: 6,
|
|
121
|
+
padding: '5px 10px',
|
|
122
|
+
border: '1px solid rgba(255,255,255,0.1)',
|
|
123
|
+
[side === 'left' ? 'borderLeft' : 'borderRight']: 'none',
|
|
124
|
+
borderRadius: side === 'left' ? '0 999px 999px 0' : '999px 0 0 999px',
|
|
125
|
+
background: 'rgba(16,18,27,0.85)',
|
|
126
|
+
backdropFilter: 'blur(12px)',
|
|
127
|
+
color: stats ? fpsColor(stats.fps) : '#e7e9f0',
|
|
128
|
+
font: '600 11px ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
129
|
+
cursor: 'pointer',
|
|
130
|
+
}, type: "button", children: stats ? `${stats.fps} fps` : 'perf' }), document.body);
|
|
131
|
+
}
|
|
132
|
+
return createPortal(_jsxs("div", { "data-pascal-perf-panel": "open", ref: panelRef, style: {
|
|
133
|
+
position: 'fixed',
|
|
134
|
+
left: placement.x,
|
|
135
|
+
top: placement.y,
|
|
136
|
+
width: PANEL_WIDTH,
|
|
137
|
+
zIndex: 1000,
|
|
138
|
+
borderRadius: 12,
|
|
139
|
+
border: '1px solid rgba(255,255,255,0.09)',
|
|
140
|
+
background: 'rgba(16,18,27,0.85)',
|
|
141
|
+
backdropFilter: 'blur(12px)',
|
|
142
|
+
boxShadow: '0 8px 28px rgba(0,0,0,0.35)',
|
|
143
|
+
color: '#e7e9f0',
|
|
144
|
+
font: '11px ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
145
|
+
lineHeight: 1.6,
|
|
146
|
+
userSelect: 'none',
|
|
147
|
+
overflow: 'hidden',
|
|
148
|
+
}, children: [_jsxs("div", { onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, style: {
|
|
149
|
+
display: 'flex',
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
gap: 8,
|
|
152
|
+
padding: '7px 10px',
|
|
153
|
+
cursor: 'grab',
|
|
154
|
+
background: 'rgba(255,255,255,0.04)',
|
|
155
|
+
borderBottom: '1px solid rgba(255,255,255,0.07)',
|
|
156
|
+
touchAction: 'none',
|
|
157
|
+
}, children: [_jsx("span", { style: { fontWeight: 600, letterSpacing: 0.2 }, children: "Performance" }), _jsx("span", { style: { marginLeft: 'auto', color: stats ? fpsColor(stats.fps) : '#8b90a0' }, children: stats ? `${stats.fps} fps` : '—' }), _jsx("button", { "aria-label": "Dock panel to the side", onClick: dock, onPointerDown: (e) => e.stopPropagation(), style: {
|
|
158
|
+
border: 'none',
|
|
159
|
+
borderRadius: 999,
|
|
160
|
+
width: 18,
|
|
161
|
+
height: 18,
|
|
162
|
+
background: 'rgba(255,255,255,0.08)',
|
|
163
|
+
color: '#c3c7d4',
|
|
164
|
+
cursor: 'pointer',
|
|
165
|
+
font: '10px ui-monospace, monospace',
|
|
166
|
+
lineHeight: '18px',
|
|
167
|
+
padding: 0,
|
|
168
|
+
}, type: "button", children: "\u00D7" })] }), stats ? (_jsxs("div", { style: { padding: '8px 10px' }, children: [_jsxs("div", { style: grid, children: [_jsx(Row, { name: "frame", children: stats.frameMs > 0
|
|
169
|
+
? `${stats.frameMs.toFixed(1)}ms cpu (max ${stats.frameMaxMs.toFixed(1)})`
|
|
170
|
+
: '—' }), _jsx(Row, { name: "encode", children: stats.encodeMs > 0
|
|
171
|
+
? `${stats.encodeMs.toFixed(1)}ms (max ${stats.encodeMaxMs.toFixed(1)})`
|
|
172
|
+
: '—' }), _jsx(Row, { name: "gpu", children: stats.gpuTracked
|
|
173
|
+
? `${stats.gpuMs.toFixed(1)}ms (max ${stats.gpuMaxMs.toFixed(1)})`
|
|
174
|
+
: 'no timestamp-query' }), _jsx(Row, { name: "queue", children: stats.queueMs > 0
|
|
175
|
+
? `${stats.queueMs.toFixed(1)}ms (max ${stats.queueMaxMs.toFixed(1)})`
|
|
176
|
+
: '—' }), _jsx(Row, { name: "draw", children: stats.drawCalls }), stats.batch.containers > 0 && (_jsx(Row, { name: "batch", children: `${stats.batch.items} items · ${stats.batch.instances} inst · ${stats.batch.containers} mesh` })), _jsx(Row, { name: "tri", children: `${(stats.triangles / 1000).toFixed(1)}k` }), _jsx(Row, { name: "mem", children: `${stats.geometries} geo ${stats.textures} tex ${mb(stats.gpuBytes)}` }), _jsx(Row, { name: "heap", children: stats.heapBytes > 0 ? mb(stats.heapBytes) : '—' }), _jsxs(Row, { name: "dirty", children: [stats.dirty, stats.dirtyDetail ? ` (${stats.dirtyDetail})` : ''] }), _jsx(Row, { name: "visible", children: `${stats.meshes} mesh ${stats.lines} line ${stats.lights} light` })] }), _jsx(LastAction, {}), stats.tracks.length > 0 && (_jsx("div", { style: section, children: stats.tracks.map((t) => (_jsxs("div", { style: grid, children: [_jsx("span", { style: label, children: t.name }), _jsx("span", { style: value, children: `${t.totalMs.toFixed(1)}ms (${t.count}×, max ${t.maxMs.toFixed(1)})` })] }, t.name))) }))] })) : (_jsx("div", { style: { padding: '8px 10px', color: '#8b90a0' }, children: "waiting for samples\u2026" }))] }), document.body);
|
|
177
|
+
};
|
|
178
|
+
export default PerfPanel;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lets R3F's pointer raycaster see geometry a collective batch draws.
|
|
3
|
+
*
|
|
4
|
+
* R3F picks with one shared raycaster whose default mask is `SCENE_LAYER`
|
|
5
|
+
* alone. A wall sewn into its level's merged mesh is moved off that layer
|
|
6
|
+
* (`hideBatchedWall`) while staying in the graph with its pointer handlers
|
|
7
|
+
* intact — so without this the wall answers no hover, paint or click the
|
|
8
|
+
* moment it joins a batch, and a floor's walls go dead a fraction of a second
|
|
9
|
+
* after the last edit settles.
|
|
10
|
+
*
|
|
11
|
+
* Additive rather than `setSurfaceRaycastLayers`: that helper resets the mask
|
|
12
|
+
* for the private raycasters callers build per query, and this one is shared.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PointerRaycastLayers: () => null;
|
|
15
|
+
//# sourceMappingURL=pointer-raycast-layers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointer-raycast-layers.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/pointer-raycast-layers.tsx"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,YAYhC,CAAA"}
|